@zqagent/core 0.3.12 → 0.3.16

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 (43) hide show
  1. package/dist/{TurnEngine-BU4R7q_0.d.ts → TurnEngine-C8qqbilC.d.ts} +29 -3
  2. package/dist/{TurnEngine-BSciNywD.d.cts → TurnEngine-DKSczYaA.d.cts} +29 -3
  3. package/dist/{conflictGuard-BBOOIxxK.d.cts → conflictGuard-BuICnWEI.d.cts} +8 -0
  4. package/dist/{conflictGuard-BBOOIxxK.d.ts → conflictGuard-BuICnWEI.d.ts} +8 -0
  5. package/dist/index.cjs +3364 -1107
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +221 -6
  8. package/dist/index.d.ts +221 -6
  9. package/dist/index.js +3414 -1157
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal/engine/FileLogHook.d.cts +1 -1
  12. package/dist/internal/engine/FileLogHook.d.ts +1 -1
  13. package/dist/internal/llmConfig.cjs +15 -5
  14. package/dist/internal/llmConfig.cjs.map +1 -1
  15. package/dist/internal/llmConfig.d.cts +17 -1
  16. package/dist/internal/llmConfig.d.ts +17 -1
  17. package/dist/internal/llmConfig.js +15 -5
  18. package/dist/internal/llmConfig.js.map +1 -1
  19. package/dist/internal/update/controller.cjs +1 -1
  20. package/dist/internal/update/controller.js +1 -1
  21. package/dist/internal/update/platform.cjs +1 -1
  22. package/dist/internal/update/platform.js +1 -1
  23. package/dist/tools/diff.d.cts +1 -1
  24. package/dist/tools/diff.d.ts +1 -1
  25. package/dist/tools/explore.cjs +504 -127
  26. package/dist/tools/explore.cjs.map +1 -1
  27. package/dist/tools/explore.d.cts +1 -1
  28. package/dist/tools/explore.d.ts +1 -1
  29. package/dist/tools/explore.js +504 -127
  30. package/dist/tools/explore.js.map +1 -1
  31. package/dist/tools/files.cjs +267 -47
  32. package/dist/tools/files.cjs.map +1 -1
  33. package/dist/tools/files.d.cts +1 -1
  34. package/dist/tools/files.d.ts +1 -1
  35. package/dist/tools/files.js +267 -47
  36. package/dist/tools/files.js.map +1 -1
  37. package/dist/tools/search.cjs +103 -24
  38. package/dist/tools/search.cjs.map +1 -1
  39. package/dist/tools/search.d.cts +8 -2
  40. package/dist/tools/search.d.ts +8 -2
  41. package/dist/tools/search.js +103 -24
  42. package/dist/tools/search.js.map +1 -1
  43. package/package.json +6 -5
@@ -345,8 +345,17 @@ var zh = {
345
345
  - \u4E0D\u8981\u5C06\u5B8C\u6574\u7684\u6587\u4EF6\u5185\u5BB9\u5F53\u4F5C\u771F\u7406\u3002\u53EA\u8BB0\u5F55\u67D0\u4E2A\u6587\u4EF6\u88AB\u8BFB\u53D6/\u7F16\u8F91\u8FC7\u8FD9\u4E00\u4E8B\u5B9E\uFF1B\u534F\u4F5C\u8005\u5982\u679C\u9700\u8981\u518D\u6B21\u83B7\u53D6\u5185\u5BB9\u4F1A\u91CD\u65B0\u8BFB\u53D6\u3002\u9648\u65E7\u7684\u6587\u4EF6\u8BB0\u5FC6\u6BD4\u6CA1\u6709\u8BB0\u5FC6\u66F4\u7CDF\u7CD5\u3002
346
346
  - \u82E5\u63D0\u4F9B\u4E86\u4E0A\u4E00\u7248\u6458\u8981\uFF1A\u53EA\u628A\u5B83\u4ECD\u7136\u6709\u6548\u7684\u7ED3\u8BBA\u5E76\u5165\u5BF9\u5E94\u7AE0\u8282\uFF0C\u4E0D\u8981\u6574\u6BB5\u590D\u8FF0\u6216\u9010\u6761\u8F6C\u6284\u3002
347
347
  - \u8981\u5177\u4F53\uFF1A\u8DEF\u5F84\u3001\u540D\u79F0\u3001\u547D\u4EE4\u3001id\u2014\u2014\u800C\u4E0D\u662F\u6A21\u7CCA\u7684\u5F15\u7528\u3002
348
+ - \u7528\u7B80\u4F53\u4E2D\u6587\u64B0\u5199\u6458\u8981\u3002\u6458\u8981\u5728\u540E\u7EED\u6BCF\u4E00\u8F6E\u90FD\u4F1A\u968F\u4E0A\u4E0B\u6587\u91CD\u53D1\uFF0C\u8BED\u8A00\u4E0D\u4E00\u81F4\u4F1A\u8BA9\u534F\u4F5C\u8005\u6539\u7528\u82F1\u6587\u4F5C\u7B54\u3002
348
349
  - \u4EC5\u8F93\u51FA\u6458\u8981\u7AE0\u8282\uFF0C\u4E0D\u9644\u52A0\u524D\u8A00\u3002`,
349
350
  continuationContract: "\u4ECE\u4F60\u4E4B\u524D\u4E2D\u65AD\u7684\u5730\u65B9\u7EE7\u7EED\uFF1A\u5B8C\u5168\u6309\u7167\u63CF\u8FF0\u63A5\u7EED\u5F53\u524D\u5DE5\u4F5C\u548C\u4E0B\u4E00\u6B65\u3002\u4E0D\u8981\u91CD\u65B0\u8BE2\u95EE\u5DF2\u56DE\u7B54\u7684\u95EE\u9898\uFF0C\u4E0D\u8981\u56DE\u987E\uFF0C\u4E0D\u8981\u63D0\u53CA\u4E0A\u4E0B\u6587\u5DF2\u88AB\u538B\u7F29\u3002\u5982\u679C\u4F60\u9700\u8981\u4E0A\u8FF0\u63D0\u5230\u7684\u6587\u4EF6\u7684\u5185\u5BB9\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u5B83\u3002",
351
+ compactedIntro: "\u672C\u4F1A\u8BDD\u8F83\u65E9\u7684\u8F6E\u6B21\u5DF2\u88AB\u538B\u7F29\u3002\u4E0B\u9762\u7684\u6458\u8981\u5C31\u662F\u4F60\u5BF9\u90A3\u4E9B\u8F6E\u6B21\u7684\u8BB0\u5FC6\u3002",
352
+ compactedUserMessagesHeader: "## \u538B\u7F29\u533A\u95F4\u5185\u7684\u7528\u6237\u6D88\u606F\uFF08\u539F\u6587\uFF0C\u6309\u65F6\u95F4\u987A\u5E8F\uFF09",
353
+ compactedOmittedNote: (n) => `\uFF08\u66F4\u65E9\u7684 ${n} \u6761\u7528\u6237\u6D88\u606F\u5DF2\u7701\u7565 \u2014\u2014 \u5176\u610F\u56FE\u5DF2\u5305\u542B\u5728\u4E0A\u9762\u7684\u6458\u8981\u4E2D\uFF09`,
354
+ compactedTrimmedNote: "\uFF08\u8F83\u65E9\u7684\u8F6E\u6B21\u56E0\u4E0A\u4E0B\u6587\u7A97\u53E3\u9650\u5236\u88AB\u88C1\u526A\uFF0C\u6CA1\u6709\u53EF\u7528\u6458\u8981\u3002\u5982\u679C\u9700\u8981\u65E9\u5148\u7684\u7ED3\u679C\uFF0C\u8BF7\u91CD\u65B0\u8BFB\u53D6\u6587\u4EF6\u6216\u91CD\u65B0\u6267\u884C\u547D\u4EE4\u3002\uFF09",
355
+ workingStateHeader: "## \u5DE5\u4F5C\u72B6\u6001\uFF08\u4ECE\u5DE5\u5177\u8BB0\u5F55\u4E2D\u673A\u68B0\u63D0\u53D6\uFF09",
356
+ workingStateFiles: "\u5DF2\u5199\u5165/\u4FEE\u6539\u7684\u6587\u4EF6\uFF08\u6700\u8FD1\u7684\u5728\u524D\uFF09\uFF1A",
357
+ workingStateCommands: "\u6700\u8FD1\u7684 shell \u547D\u4EE4\uFF1A",
358
+ workingStateTools: (list) => `\u538B\u7F29\u533A\u95F4\u5185\u4F7F\u7528\u8FC7\u7684\u5DE5\u5177\uFF1A${list}`,
350
359
  genCommitMessageInstructions: `\u4F60\u662F Git \u63D0\u4EA4\u8BF4\u660E\u751F\u6210\u52A9\u624B\u3002\u6839\u636E\u7ED9\u5B9A\u7684\u4EE3\u7801\u5DEE\u5F02\uFF0C\u751F\u6210\u4E00\u6761\u51C6\u786E\u3001\u7B80\u6D01\u7684\u63D0\u4EA4\u8BF4\u660E\u3002
351
360
 
352
361
  \u8981\u6C42\uFF1A
@@ -500,8 +509,17 @@ Rules:
500
509
  - Do not treat full file contents as truth. Record only the fact that a file was read/edited; the collaborator will re-read it if it needs the content again. Stale file memory is worse than no memory.
501
510
  - If a previous summary is provided: fold only its still-valid conclusions into the matching sections; do not restate it wholesale or copy it entry by entry.
502
511
  - Be specific: paths, names, commands, ids \u2014 not vague references.
512
+ - Write the summary in English. It is re-sent with the context on every later turn; a language mismatch makes the collaborator switch to another language.
503
513
  - Output only the summary sections, with no preamble.`,
504
514
  continuationContract: "Continue from where you left off: pick up the current work and next step exactly as described. Do not re-ask questions that were already answered, do not recap, and do not mention that the context was compacted. If you need the contents of a file mentioned above, read it again.",
515
+ compactedIntro: "Earlier turns of this session were compacted. The summary below is your memory of them.",
516
+ compactedUserMessagesHeader: "## User messages in the compacted span (verbatim, chronological)",
517
+ compactedOmittedNote: (n) => `(${n} earlier user messages omitted \u2014 their intent is covered by the summary above)`,
518
+ compactedTrimmedNote: "(Older turns were trimmed to fit the context window; no summary is available for them. Re-read files and re-run commands if earlier results are needed.)",
519
+ workingStateHeader: "## Working state (extracted mechanically from tool records)",
520
+ workingStateFiles: "Files written/edited (most recent first):",
521
+ workingStateCommands: "Recent shell commands:",
522
+ workingStateTools: (list) => `Tools used in the summarized span: ${list}`,
505
523
  genCommitMessageInstructions: `You are a Git commit message assistant. Given a code diff, produce an accurate, concise commit message.
506
524
 
507
525
  Requirements:
@@ -655,8 +673,17 @@ var zhTW = {
655
673
  - \u4E0D\u8981\u5C07\u5B8C\u6574\u7684\u6A94\u6848\u5167\u5BB9\u7576\u4F5C\u771F\u7406\u3002\u53EA\u8A18\u9304\u67D0\u500B\u6A94\u6848\u88AB\u8B80\u53D6/\u7DE8\u8F2F\u904E\u9019\u4E00\u4E8B\u5BE6\uFF1B\u5354\u4F5C\u8005\u5982\u679C\u9700\u8981\u518D\u6B21\u7372\u53D6\u5167\u5BB9\u6703\u91CD\u65B0\u8B80\u53D6\u3002\u9673\u820A\u7684\u6A94\u6848\u8A18\u61B6\u6BD4\u6C92\u6709\u8A18\u61B6\u66F4\u7CDF\u7CD5\u3002
656
674
  - \u82E5\u63D0\u4F9B\u4E86\u4E0A\u4E00\u7248\u6458\u8981\uFF1A\u53EA\u628A\u5B83\u4ECD\u7136\u6709\u6548\u7684\u7D50\u8AD6\u4F75\u5165\u5C0D\u61C9\u7AE0\u7BC0\uFF0C\u4E0D\u8981\u6574\u6BB5\u8907\u8FF0\u6216\u9010\u689D\u8F49\u6284\u3002
657
675
  - \u8981\u5177\u9AD4\uFF1A\u8DEF\u5F91\u3001\u540D\u7A31\u3001\u547D\u4EE4\u3001id\u2014\u2014\u800C\u4E0D\u662F\u6A21\u7CCA\u7684\u5F15\u7528\u3002
676
+ - \u7528\u7E41\u9AD4\u4E2D\u6587\u64B0\u5BEB\u6458\u8981\u3002\u6458\u8981\u5728\u5F8C\u7E8C\u6BCF\u4E00\u8F2A\u90FD\u6703\u96A8\u4E0A\u4E0B\u6587\u91CD\u767C\uFF0C\u8A9E\u8A00\u4E0D\u4E00\u81F4\u6703\u8B93\u5354\u4F5C\u8005\u6539\u7528\u5176\u4ED6\u8A9E\u8A00\u4F5C\u7B54\u3002
658
677
  - \u50C5\u8F38\u51FA\u6458\u8981\u7AE0\u7BC0\uFF0C\u4E0D\u9644\u52A0\u524D\u8A00\u3002`,
659
678
  continuationContract: "\u5F9E\u4F60\u4E4B\u524D\u4E2D\u65B7\u7684\u5730\u65B9\u7E7C\u7E8C\uFF1A\u5B8C\u5168\u6309\u7167\u63CF\u8FF0\u63A5\u7E8C\u7576\u524D\u5DE5\u4F5C\u548C\u4E0B\u4E00\u6B65\u3002\u4E0D\u8981\u91CD\u65B0\u8A62\u554F\u5DF2\u56DE\u7B54\u7684\u554F\u984C\uFF0C\u4E0D\u8981\u56DE\u9867\uFF0C\u4E0D\u8981\u63D0\u53CA\u4E0A\u4E0B\u6587\u5DF2\u88AB\u58D3\u7E2E\u3002\u5982\u679C\u4F60\u9700\u8981\u4E0A\u8FF0\u63D0\u5230\u7684\u6A94\u6848\u7684\u5167\u5BB9\uFF0C\u8ACB\u91CD\u65B0\u8B80\u53D6\u5B83\u3002",
679
+ compactedIntro: "\u672C\u6703\u8A71\u8F03\u65E9\u7684\u8F2A\u6B21\u5DF2\u88AB\u58D3\u7E2E\u3002\u4E0B\u9762\u7684\u6458\u8981\u5C31\u662F\u4F60\u5C0D\u90A3\u4E9B\u8F2A\u6B21\u7684\u8A18\u61B6\u3002",
680
+ compactedUserMessagesHeader: "## \u58D3\u7E2E\u5340\u9593\u5167\u7684\u4F7F\u7528\u8005\u8A0A\u606F\uFF08\u539F\u6587\uFF0C\u6309\u6642\u9593\u9806\u5E8F\uFF09",
681
+ compactedOmittedNote: (n) => `\uFF08\u66F4\u65E9\u7684 ${n} \u5247\u4F7F\u7528\u8005\u8A0A\u606F\u5DF2\u7701\u7565 \u2014\u2014 \u5176\u610F\u5716\u5DF2\u5305\u542B\u5728\u4E0A\u9762\u7684\u6458\u8981\u4E2D\uFF09`,
682
+ compactedTrimmedNote: "\uFF08\u8F03\u65E9\u7684\u8F2A\u6B21\u56E0\u4E0A\u4E0B\u6587\u8996\u7A97\u9650\u5236\u88AB\u88C1\u526A\uFF0C\u6C92\u6709\u53EF\u7528\u6458\u8981\u3002\u5982\u679C\u9700\u8981\u65E9\u5148\u7684\u7D50\u679C\uFF0C\u8ACB\u91CD\u65B0\u8B80\u53D6\u6A94\u6848\u6216\u91CD\u65B0\u57F7\u884C\u547D\u4EE4\u3002\uFF09",
683
+ workingStateHeader: "## \u5DE5\u4F5C\u72C0\u614B\uFF08\u5F9E\u5DE5\u5177\u8A18\u9304\u4E2D\u6A5F\u68B0\u63D0\u53D6\uFF09",
684
+ workingStateFiles: "\u5DF2\u5BEB\u5165/\u4FEE\u6539\u7684\u6A94\u6848\uFF08\u6700\u8FD1\u7684\u5728\u524D\uFF09\uFF1A",
685
+ workingStateCommands: "\u6700\u8FD1\u7684 shell \u547D\u4EE4\uFF1A",
686
+ workingStateTools: (list) => `\u58D3\u7E2E\u5340\u9593\u5167\u4F7F\u7528\u904E\u7684\u5DE5\u5177\uFF1A${list}`,
660
687
  genCommitMessageInstructions: `\u4F60\u662F Git \u63D0\u4EA4\u8AAA\u660E\u751F\u6210\u52A9\u624B\u3002\u6839\u64DA\u7D66\u5B9A\u7684\u7A0B\u5F0F\u78BC\u5DEE\u7570\uFF0C\u751F\u6210\u4E00\u689D\u6E96\u78BA\u3001\u7C21\u6F54\u7684\u63D0\u4EA4\u8AAA\u660E\u3002
661
688
 
662
689
  \u8981\u6C42\uFF1A
@@ -810,8 +837,17 @@ shell \u30B3\u30DE\u30F3\u30C9\u306B\u8907\u6570\u884C\u306E\u30B9\u30AF\u30EA\u
810
837
  - \u30D5\u30A1\u30A4\u30EB\u306E\u5185\u5BB9\u5168\u4F53\u3092\u771F\u5B9F\u3068\u3057\u3066\u6271\u308F\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\u3042\u308B\u30D5\u30A1\u30A4\u30EB\u304C\u8AAD\u307E\u308C\u305F\uFF0F\u7DE8\u96C6\u3055\u308C\u305F\u3068\u3044\u3046\u4E8B\u5B9F\u3060\u3051\u3092\u8A18\u9332\u3057\u307E\u3059\u3002\u5354\u50CD\u8005\u306F\u5185\u5BB9\u304C\u518D\u3073\u5FC5\u8981\u306B\u306A\u308C\u3070\u8AAD\u307F\u76F4\u3057\u307E\u3059\u3002\u53E4\u3044\u30D5\u30A1\u30A4\u30EB\u8A18\u61B6\u306F\u8A18\u61B6\u304C\u306A\u3044\u3088\u308A\u60AA\u3044\u3002
811
838
  - \u524D\u56DE\u306E\u8981\u7D04\u304C\u63D0\u4F9B\u3055\u308C\u305F\u5834\u5408\uFF1A\u307E\u3060\u6709\u52B9\u306A\u7D50\u8AD6\u3060\u3051\u3092\u8A72\u5F53\u30BB\u30AF\u30B7\u30E7\u30F3\u306B\u7D71\u5408\u3057\u3001\u5168\u4F53\u3092\u66F8\u304D\u76F4\u3057\u305F\u308A\u9805\u76EE\u3054\u3068\u306B\u8EE2\u8A18\u3057\u305F\u308A\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
812
839
  - \u5177\u4F53\u7684\u306B\uFF1A\u30D1\u30B9\u3001\u540D\u524D\u3001\u30B3\u30DE\u30F3\u30C9\u3001id \u3092\u66F8\u304F\u3002\u66D6\u6627\u306A\u53C2\u7167\u306B\u3057\u306A\u3044\u3002
840
+ - \u8981\u7D04\u306F\u65E5\u672C\u8A9E\u3067\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002\u8981\u7D04\u306F\u4EE5\u964D\u306E\u6BCE\u30BF\u30FC\u30F3\u3067\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3068\u3068\u3082\u306B\u518D\u9001\u3055\u308C\u308B\u305F\u3081\u3001\u8A00\u8A9E\u304C\u98DF\u3044\u9055\u3046\u3068\u5354\u50CD\u8005\u304C\u5225\u306E\u8A00\u8A9E\u3067\u7B54\u3048\u308B\u3088\u3046\u306B\u306A\u308A\u307E\u3059\u3002
813
841
  - \u8981\u7D04\u30BB\u30AF\u30B7\u30E7\u30F3\u3060\u3051\u3092\u51FA\u529B\u3057\u3001\u524D\u7F6E\u304D\u3092\u4ED8\u3051\u306A\u3044\u3002`,
814
842
  continuationContract: "\u524D\u56DE\u4E2D\u65AD\u3057\u305F\u3068\u3053\u308D\u304B\u3089\u7D9A\u3051\u3066\u304F\u3060\u3055\u3044\uFF1A\u8AAC\u660E\u3069\u304A\u308A\u306B\u73FE\u5728\u306E\u4F5C\u696D\u3068\u6B21\u306E\u624B\u9806\u3092\u5F15\u304D\u7D99\u304E\u307E\u3059\u3002\u65E2\u306B\u56DE\u7B54\u6E08\u307F\u306E\u8CEA\u554F\u3092\u7E70\u308A\u8FD4\u3055\u305A\u3001\u632F\u308A\u8FD4\u3089\u305A\u3001\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u304C\u5727\u7E2E\u3055\u308C\u305F\u3053\u3068\u306B\u3082\u89E6\u308C\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002\u4E0A\u8A18\u3067\u8A00\u53CA\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB\u306E\u5185\u5BB9\u304C\u5FC5\u8981\u306A\u5834\u5408\u306F\u3001\u3082\u3046\u4E00\u5EA6\u8AAD\u3093\u3067\u304F\u3060\u3055\u3044\u3002",
843
+ compactedIntro: "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u4EE5\u524D\u306E\u30BF\u30FC\u30F3\u306F\u5727\u7E2E\u3055\u308C\u307E\u3057\u305F\u3002\u4EE5\u4E0B\u306E\u8981\u7D04\u304C\u305D\u308C\u3089\u306E\u30BF\u30FC\u30F3\u306B\u3064\u3044\u3066\u306E\u3042\u306A\u305F\u306E\u8A18\u61B6\u3067\u3059\u3002",
844
+ compactedUserMessagesHeader: "## \u5727\u7E2E\u533A\u9593\u5185\u306E\u30E6\u30FC\u30B6\u30FC\u30E1\u30C3\u30BB\u30FC\u30B8\uFF08\u539F\u6587\u3001\u6642\u7CFB\u5217\uFF09",
845
+ compactedOmittedNote: (n) => `\uFF08\u4EE5\u524D\u306E\u30E6\u30FC\u30B6\u30FC\u30E1\u30C3\u30BB\u30FC\u30B8 ${n} \u4EF6\u306F\u7701\u7565\u3055\u308C\u307E\u3057\u305F \u2014 \u305D\u306E\u610F\u56F3\u306F\u4E0A\u8A18\u306E\u8981\u7D04\u306B\u542B\u307E\u308C\u3066\u3044\u307E\u3059\uFF09`,
846
+ compactedTrimmedNote: "\uFF08\u4EE5\u524D\u306E\u30BF\u30FC\u30F3\u306F\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u30A6\u30A3\u30F3\u30C9\u30A6\u306E\u5236\u9650\u306B\u3088\u308A\u5207\u308A\u8A70\u3081\u3089\u308C\u3001\u8981\u7D04\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u4EE5\u524D\u306E\u7D50\u679C\u304C\u5FC5\u8981\u306A\u5834\u5408\u306F\u3001\u30D5\u30A1\u30A4\u30EB\u3092\u518D\u8AAD\u307F\u8FBC\u307F\u3059\u308B\u304B\u30B3\u30DE\u30F3\u30C9\u3092\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\uFF09",
847
+ workingStateHeader: "## \u4F5C\u696D\u72B6\u614B\uFF08\u30C4\u30FC\u30EB\u8A18\u9332\u304B\u3089\u6A5F\u68B0\u7684\u306B\u62BD\u51FA\uFF09",
848
+ workingStateFiles: "\u66F8\u304D\u8FBC\u307F/\u7DE8\u96C6\u3057\u305F\u30D5\u30A1\u30A4\u30EB\uFF08\u65B0\u3057\u3044\u9806\uFF09\uFF1A",
849
+ workingStateCommands: "\u6700\u8FD1\u306E\u30B7\u30A7\u30EB\u30B3\u30DE\u30F3\u30C9\uFF1A",
850
+ workingStateTools: (list) => `\u5727\u7E2E\u533A\u9593\u5185\u3067\u4F7F\u7528\u3057\u305F\u30C4\u30FC\u30EB\uFF1A${list}`,
815
851
  genCommitMessageInstructions: `\u3042\u306A\u305F\u306F Git \u30B3\u30DF\u30C3\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u751F\u6210\u30A2\u30B7\u30B9\u30BF\u30F3\u30C8\u3067\u3059\u3002\u4E0E\u3048\u3089\u308C\u305F\u30B3\u30FC\u30C9\u5DEE\u5206\u304B\u3089\u3001\u6B63\u78BA\u3067\u7C21\u6F54\u306A\u30B3\u30DF\u30C3\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002
816
852
 
817
853
  \u8981\u4EF6\uFF1A
@@ -965,8 +1001,17 @@ shell \uBA85\uB839\uC5D0 \uC5EC\uB7EC \uC904 \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uC7
965
1001
  - \uD30C\uC77C \uB0B4\uC6A9 \uC804\uCCB4\uB97C \uC9C4\uC2E4\uB85C \uCDE8\uAE09\uD558\uC9C0 \uB9C8\uC138\uC694. \uC5B4\uB5A4 \uD30C\uC77C\uC774 \uC77D\uD788\uAC70\uB098 \uD3B8\uC9D1\uB418\uC5C8\uB2E4\uB294 \uC0AC\uC2E4\uB9CC \uAE30\uB85D\uD569\uB2C8\uB2E4. \uD611\uC5C5\uC790\uB294 \uB0B4\uC6A9\uC774 \uB2E4\uC2DC \uD544\uC694\uD558\uBA74 \uB2E4\uC2DC \uC77D\uC2B5\uB2C8\uB2E4. \uC624\uB798\uB41C \uD30C\uC77C \uAE30\uC5B5\uC740 \uAE30\uC5B5\uC774 \uC5C6\uB294 \uAC83\uBCF4\uB2E4 \uB098\uC069\uB2C8\uB2E4.
966
1002
  - \uC774\uC804 \uC694\uC57D\uC774 \uC81C\uACF5\uB418\uC5C8\uB2E4\uBA74: \uC5EC\uC804\uD788 \uC720\uD6A8\uD55C \uACB0\uB860\uB9CC \uD574\uB2F9 \uC139\uC158\uC5D0 \uBCD1\uD569\uD558\uACE0, \uC804\uCCB4\uB97C \uB2E4\uC2DC \uC4F0\uAC70\uB098 \uD56D\uBAA9\uBCC4\uB85C \uC62E\uACA8 \uC801\uC9C0 \uB9C8\uC138\uC694.
967
1003
  - \uAD6C\uCCB4\uC801\uC73C\uB85C: \uACBD\uB85C, \uC774\uB984, \uBA85\uB839, id\uB97C \uC4F8 \uAC83. \uBAA8\uD638\uD55C \uCC38\uC870\uB294 \uAE08\uC9C0.
1004
+ - \uC694\uC57D\uC740 \uD55C\uAD6D\uC5B4\uB85C \uC791\uC131\uD558\uC138\uC694. \uC694\uC57D\uC740 \uC774\uD6C4 \uB9E4 \uD134\uB9C8\uB2E4 \uCEE8\uD14D\uC2A4\uD2B8\uC640 \uD568\uAED8 \uB2E4\uC2DC \uC804\uC1A1\uB418\uBBC0\uB85C, \uC5B8\uC5B4\uAC00 \uC5B4\uAE0B\uB098\uBA74 \uD611\uC5C5\uC790\uAC00 \uB2E4\uB978 \uC5B8\uC5B4\uB85C \uB2F5\uD558\uAC8C \uB429\uB2C8\uB2E4.
968
1005
  - \uC694\uC57D \uC139\uC158\uB9CC \uCD9C\uB825\uD558\uACE0 \uC11C\uB450\uB97C \uBD99\uC774\uC9C0 \uB9C8\uC138\uC694.`,
969
1006
  continuationContract: "\uC911\uB2E8\uD55C \uC9C0\uC810\uBD80\uD130 \uC774\uC5B4\uAC00\uC138\uC694: \uC124\uBA85\uD55C \uB300\uB85C \uD604\uC7AC \uC791\uC5C5\uACFC \uB2E4\uC74C \uB2E8\uACC4\uB97C \uADF8\uB300\uB85C \uC774\uC5B4\uBC1B\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 \uB2F5\uD55C \uC9C8\uBB38\uC744 \uB2E4\uC2DC \uBB3B\uC9C0 \uB9D0\uACE0, \uB418\uB3CC\uC544\uBCF4\uC9C0 \uB9D0\uACE0, \uCEE8\uD14D\uC2A4\uD2B8\uAC00 \uC555\uCD95\uB418\uC5C8\uB2E4\uB294 \uC0AC\uC2E4\uB3C4 \uC5B8\uAE09\uD558\uC9C0 \uB9C8\uC138\uC694. \uC704\uC5D0\uC11C \uC5B8\uAE09\uD55C \uD30C\uC77C\uC758 \uB0B4\uC6A9\uC774 \uD544\uC694\uD558\uBA74 \uB2E4\uC2DC \uC77D\uC73C\uC138\uC694.",
1007
+ compactedIntro: "\uC774 \uC138\uC158\uC758 \uC774\uC804 \uD134\uC740 \uC555\uCD95\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC544\uB798 \uC694\uC57D\uC774 \uADF8 \uD134\uB4E4\uC5D0 \uB300\uD55C \uB2F9\uC2E0\uC758 \uAE30\uC5B5\uC785\uB2C8\uB2E4.",
1008
+ compactedUserMessagesHeader: "## \uC555\uCD95 \uAD6C\uAC04\uC758 \uC0AC\uC6A9\uC790 \uBA54\uC2DC\uC9C0 (\uC6D0\uBB38, \uC2DC\uAC04 \uC21C)",
1009
+ compactedOmittedNote: (n) => `(\uC774\uC804 \uC0AC\uC6A9\uC790 \uBA54\uC2DC\uC9C0 ${n}\uAC1C\uB294 \uC0DD\uB7B5\uB428 \u2014 \uADF8 \uC758\uB3C4\uB294 \uC704 \uC694\uC57D\uC5D0 \uD3EC\uD568\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4)`,
1010
+ compactedTrimmedNote: "(\uC774\uC804 \uD134\uC740 \uCEE8\uD14D\uC2A4\uD2B8 \uC708\uB3C4 \uC81C\uD55C\uC73C\uB85C \uC798\uB838\uC73C\uBA70 \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC694\uC57D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774\uC804 \uACB0\uACFC\uAC00 \uD544\uC694\uD558\uBA74 \uD30C\uC77C\uC744 \uB2E4\uC2DC \uC77D\uAC70\uB098 \uBA85\uB839\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.)",
1011
+ workingStateHeader: "## \uC791\uC5C5 \uC0C1\uD0DC (\uB3C4\uAD6C \uAE30\uB85D\uC5D0\uC11C \uAE30\uACC4\uC801\uC73C\uB85C \uCD94\uCD9C)",
1012
+ workingStateFiles: "\uC791\uC131/\uC218\uC815\uD55C \uD30C\uC77C (\uCD5C\uADFC \uC21C):",
1013
+ workingStateCommands: "\uCD5C\uADFC \uC178 \uBA85\uB839:",
1014
+ workingStateTools: (list) => `\uC555\uCD95 \uAD6C\uAC04\uC5D0\uC11C \uC0AC\uC6A9\uD55C \uB3C4\uAD6C: ${list}`,
970
1015
  genCommitMessageInstructions: `\uB2F9\uC2E0\uC740 Git \uCEE4\uBC0B \uBA54\uC2DC\uC9C0 \uC0DD\uC131 \uC5B4\uC2DC\uC2A4\uD134\uD2B8\uC785\uB2C8\uB2E4. \uC8FC\uC5B4\uC9C4 \uCF54\uB4DC \uCC28\uC774\uB85C \uC815\uD655\uD558\uACE0 \uAC04\uACB0\uD55C \uCEE4\uBC0B \uBA54\uC2DC\uC9C0\uB97C \uC0DD\uC131\uD558\uC138\uC694.
971
1016
 
972
1017
  \uC694\uAD6C \uC0AC\uD56D:
@@ -1120,8 +1165,17 @@ Reglas:
1120
1165
  - No trates el contenido completo de un archivo como verdad. Registra solo el hecho de que un archivo se ley\xF3 o edit\xF3; el colaborador volver\xE1 a leerlo si necesita el contenido. Una memoria obsoleta de un archivo es peor que no tener memoria.
1121
1166
  - Si se proporciona un resumen anterior: integra solo sus conclusiones a\xFAn v\xE1lidas en las secciones correspondientes; no lo repitas entero ni lo copies entrada por entrada.
1122
1167
  - S\xE9 concreto: rutas, nombres, comandos, ids, no referencias vagas.
1168
+ - Escribe el resumen en espa\xF1ol. Se reenv\xEDa con el contexto en cada turno posterior; una diferencia de idioma hace que el colaborador responda en otro idioma.
1123
1169
  - Devuelve solo las secciones del resumen, sin pre\xE1mbulo.`,
1124
1170
  continuationContract: "Contin\xFAa donde lo dejaste: retoma el trabajo actual y el siguiente paso exactamente como se describe. No vuelvas a preguntar lo que ya se respondi\xF3, no recapitules y no menciones que el contexto se ha compactado. Si necesitas el contenido de alg\xFAn archivo mencionado arriba, vuelve a leerlo.",
1171
+ compactedIntro: "Los turnos anteriores de esta sesi\xF3n se compactaron. El resumen de abajo es tu memoria de ellos.",
1172
+ compactedUserMessagesHeader: "## Mensajes del usuario en el tramo compactado (textual, cronol\xF3gico)",
1173
+ compactedOmittedNote: (n) => `(${n} mensajes anteriores del usuario omitidos \u2014 su intenci\xF3n est\xE1 cubierta por el resumen de arriba)`,
1174
+ compactedTrimmedNote: "(Los turnos anteriores se recortaron para ajustarse a la ventana de contexto; no hay resumen disponible. Vuelve a leer archivos y reejecuta comandos si necesitas resultados previos.)",
1175
+ workingStateHeader: "## Estado de trabajo (extra\xEDdo mec\xE1nicamente de los registros de herramientas)",
1176
+ workingStateFiles: "Archivos escritos/editados (m\xE1s recientes primero):",
1177
+ workingStateCommands: "Comandos de shell recientes:",
1178
+ workingStateTools: (list) => `Herramientas usadas en el tramo resumido: ${list}`,
1125
1179
  genCommitMessageInstructions: `Eres un asistente de mensajes de commit de Git. Dado un diff de c\xF3digo, produce un mensaje de commit preciso y conciso.
1126
1180
 
1127
1181
  Requisitos:
@@ -1378,7 +1432,7 @@ function pickBoundary(messages, keepTokens) {
1378
1432
  return boundary;
1379
1433
  }
1380
1434
  var WRITE_HINTS = ["write", "edit", "append", "save", "create", "patch"];
1381
- function extractWorkingState(span) {
1435
+ function extractWorkingState(span, lang) {
1382
1436
  const files = [];
1383
1437
  const commands = [];
1384
1438
  const tools = /* @__PURE__ */ new Set();
@@ -1421,23 +1475,20 @@ function extractWorkingState(span) {
1421
1475
  }
1422
1476
  return seen;
1423
1477
  };
1424
- const lines = [
1425
- "## Working state (extracted mechanically from tool records)"
1426
- ];
1478
+ const p = getPrompts(lang);
1479
+ const lines = [p.workingStateHeader];
1427
1480
  const written = dedupe(files, 20);
1428
1481
  if (written.length) {
1429
- lines.push("Files written/edited (most recent first):");
1482
+ lines.push(p.workingStateFiles);
1430
1483
  lines.push(...written.map((f) => `- ${f}`));
1431
1484
  }
1432
1485
  const recentCmds = commands.slice(-10);
1433
1486
  if (recentCmds.length) {
1434
- lines.push("Recent shell commands:");
1487
+ lines.push(p.workingStateCommands);
1435
1488
  lines.push(...recentCmds.map((c) => `- ${c}`));
1436
1489
  }
1437
1490
  if (tools.size) {
1438
- lines.push(
1439
- "Tools used in the summarized span: " + [...tools].sort().join(", ")
1440
- );
1491
+ lines.push(p.workingStateTools([...tools].sort().join(", ")));
1441
1492
  }
1442
1493
  return lines.length > 1 ? lines.join("\n") : "";
1443
1494
  }
@@ -1561,7 +1612,7 @@ async function buildState(messages, provider, model, keepTokens, prior = null, o
1561
1612
  return {
1562
1613
  boundaryIndex: boundary,
1563
1614
  summaryText: summary,
1564
- workingState: extractWorkingState(span),
1615
+ workingState: extractWorkingState(span, lang),
1565
1616
  userMessages: cappedUsers,
1566
1617
  userMessagesDropped: dropped,
1567
1618
  createdAt: Date.now(),
@@ -1569,7 +1620,7 @@ async function buildState(messages, provider, model, keepTokens, prior = null, o
1569
1620
  trimmed: false
1570
1621
  };
1571
1622
  }
1572
- function trimState(messages, prior = null, fraction = TRIM_FRACTION) {
1623
+ function trimState(messages, prior = null, fraction = TRIM_FRACTION, lang) {
1573
1624
  const start = prior?.boundaryIndex ?? 0;
1574
1625
  const remaining = messages.length - start;
1575
1626
  if (remaining <= 2) return null;
@@ -1587,7 +1638,7 @@ function trimState(messages, prior = null, fraction = TRIM_FRACTION) {
1587
1638
  const span = messages.slice(start, boundary);
1588
1639
  const priorUsers = prior?.userMessages ? [...prior.userMessages] : [];
1589
1640
  const priorSummary = prior?.summaryText ?? "";
1590
- const summary = (priorSummary ? priorSummary + "\n\n" : "") + "(Older turns were trimmed to fit the context window; no summary is available for them. Re-read files and re-run commands if earlier results are needed.)";
1641
+ const summary = (priorSummary ? priorSummary + "\n\n" : "") + getPrompts(lang).compactedTrimmedNote;
1591
1642
  const allUsers = [...priorUsers, ...extractUserMessages(span)];
1592
1643
  const { messages: cappedUsers, dropped } = capUserMessages(
1593
1644
  allUsers,
@@ -1596,7 +1647,7 @@ function trimState(messages, prior = null, fraction = TRIM_FRACTION) {
1596
1647
  return {
1597
1648
  boundaryIndex: boundary,
1598
1649
  summaryText: summary,
1599
- workingState: extractWorkingState(span),
1650
+ workingState: extractWorkingState(span, lang),
1600
1651
  userMessages: cappedUsers,
1601
1652
  userMessagesDropped: dropped,
1602
1653
  createdAt: Date.now(),
@@ -1607,7 +1658,7 @@ function trimState(messages, prior = null, fraction = TRIM_FRACTION) {
1607
1658
  function compactedBlock(state, lang) {
1608
1659
  const parts = [
1609
1660
  "<compacted-history>",
1610
- "Earlier turns of this session were compacted. The summary below is your memory of them.",
1661
+ getPrompts(lang).compactedIntro,
1611
1662
  "",
1612
1663
  state.summaryText
1613
1664
  ];
@@ -1617,12 +1668,10 @@ function compactedBlock(state, lang) {
1617
1668
  if (state.userMessages.length > 0) {
1618
1669
  parts.push(
1619
1670
  "",
1620
- "## User messages in the compacted span (verbatim, chronological)"
1671
+ getPrompts(lang).compactedUserMessagesHeader
1621
1672
  );
1622
1673
  if (state.userMessagesDropped > 0) {
1623
- parts.push(
1624
- `(${state.userMessagesDropped} earlier user messages omitted \u2014 their intent is covered by the summary above)`
1625
- );
1674
+ parts.push(getPrompts(lang).compactedOmittedNote(state.userMessagesDropped));
1626
1675
  }
1627
1676
  parts.push(...state.userMessages.map((u) => `- ${u}`));
1628
1677
  }
@@ -1724,12 +1773,12 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
1724
1773
  ]);
1725
1774
  function parseReadFileSpan(content) {
1726
1775
  const body = content.replace(/^\[tool output truncated:[^\n]*\]\n\n/, "");
1727
- let path12;
1776
+ let path13;
1728
1777
  let start;
1729
1778
  let end;
1730
1779
  try {
1731
1780
  const obj = JSON.parse(body);
1732
- path12 = obj.path;
1781
+ path13 = obj.path;
1733
1782
  start = obj.start_line;
1734
1783
  end = obj.end_line;
1735
1784
  } catch {
@@ -1738,14 +1787,14 @@ function parseReadFileSpan(content) {
1738
1787
  const m = new RegExp(`"${key}"\\s*:\\s*(\\d+)`).exec(body);
1739
1788
  return m ? Number(m[1]) : void 0;
1740
1789
  };
1741
- path12 = str("path");
1790
+ path13 = str("path");
1742
1791
  start = num("start_line");
1743
1792
  end = num("end_line");
1744
1793
  }
1745
- if (typeof path12 !== "string" || !path12) return null;
1794
+ if (typeof path13 !== "string" || !path13) return null;
1746
1795
  if (typeof start !== "number" || typeof end !== "number") return null;
1747
1796
  if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
1748
- return { path: path12, start, end };
1797
+ return { path: path13, start, end };
1749
1798
  }
1750
1799
  function addSpan(spans, start, end) {
1751
1800
  spans.push([start, end]);
@@ -1802,16 +1851,45 @@ function applyReadFileDedup(messages) {
1802
1851
  }
1803
1852
  var OVERFLOW_MARKERS = [
1804
1853
  "context_length_exceeded",
1805
- "maximum context length",
1854
+ "context length",
1806
1855
  "context window",
1807
1856
  "prompt is too long",
1808
1857
  "input is too long",
1809
1858
  "too many tokens",
1810
1859
  "input length and `max_tokens` exceed",
1811
- "exceeds the maximum number of tokens"
1860
+ "exceeds the maximum number of tokens",
1861
+ "\u8D85\u51FA\u6A21\u578B\u4E0A\u4E0B\u6587",
1862
+ "\u8D85\u8FC7\u6A21\u578B\u4E0A\u4E0B\u6587",
1863
+ "\u8D85\u904E\u6A21\u578B\u4E0A\u4E0B\u6587",
1864
+ "\u4E0A\u4E0B\u6587\u957F\u5EA6\u8D85\u8FC7",
1865
+ "\u8D85\u8FC7\u6700\u5927\u4E0A\u4E0B\u6587",
1866
+ "\u8D85\u904E\u6700\u5927\u4E0A\u4E0B\u6587",
1867
+ "\u4E0A\u4E0B\u6587\u8D85\u9650",
1868
+ "\u8F93\u5165\u8FC7\u957F"
1812
1869
  ];
1870
+ function overflowText(exc) {
1871
+ const parts = [];
1872
+ const push = (v) => {
1873
+ if (typeof v === "string" && v) parts.push(v);
1874
+ };
1875
+ push(String(exc));
1876
+ if (exc && typeof exc === "object") {
1877
+ const rec = exc;
1878
+ push(rec["message"]);
1879
+ push(rec["detail"]);
1880
+ const body = rec["error"];
1881
+ if (body && typeof body === "object") {
1882
+ const b = body;
1883
+ push(b["message"]);
1884
+ push(b["code"]);
1885
+ } else {
1886
+ push(body);
1887
+ }
1888
+ }
1889
+ return parts.join(" ").toLowerCase();
1890
+ }
1813
1891
  function isContextOverflow(exc) {
1814
- const text = String(exc).toLowerCase();
1892
+ const text = overflowText(exc);
1815
1893
  return OVERFLOW_MARKERS.some((m) => text.includes(m));
1816
1894
  }
1817
1895
 
@@ -1844,6 +1922,9 @@ var TOOL_MAX_CHARS = {
1844
1922
  web_search: 8e3,
1845
1923
  list_files: 8e3,
1846
1924
  shell_task_output: 8e3,
1925
+ /* repo_map 自带 16K 上限(tools/repoMap.ts 的 MAX_CHARS)。这里显式登记,
1926
+ * 而不是落到 24K 默认值 —— 免得将来工具上限调大时被二次裁剪,口径也保持一致。 */
1927
+ repo_map: 16e3,
1847
1928
  git_log: 6e3,
1848
1929
  git_status: 4e3
1849
1930
  };
@@ -2151,6 +2232,7 @@ var INTERACTIVE_TOOLS = /* @__PURE__ */ new Set([
2151
2232
  var MAX_COMPACT_ROUNDS = 4;
2152
2233
  var OVERFLOW_TARGET_RATIO = 0.5;
2153
2234
  var OVERFLOW_TRIM_FRACTION = 0.5;
2235
+ var OVERFLOW_ELIDE_KEEP_FRACTION = 0.02;
2154
2236
  var COMPACT_STALL_RETRY_DELTA = 20;
2155
2237
  var FINALIZE_ON_LIMIT_PROMPT = "\u5DF2\u8FBE\u5230\u672C\u8F6E\u8FED\u4EE3\u4E0A\u9650\uFF0C\u4F60\u4E0D\u80FD\u518D\u8C03\u7528\u4EFB\u4F55\u5DE5\u5177\u3002\u8BF7\u7ACB\u5373\u57FA\u4E8E\u4E0A\u9762\u5DF2\u7ECF\u6536\u96C6\u5230\u7684\u4FE1\u606F\u8F93\u51FA\u6700\u7EC8\u62A5\u544A\uFF1A\u76F4\u63A5\u56DE\u7B54\u4EFB\u52A1\u3001\u7ED9\u51FA\u7ED3\u8BBA\u4E0E path:line \u5F15\u7528\uFF0C\u5E76\u660E\u786E\u8BF4\u660E\u54EA\u4E9B\u90E8\u5206\u5C1A\u672A\u67E5\u8BC1\u3002\u4E0D\u8981\u8BE2\u95EE\u3001\u4E0D\u8981\u7EE7\u7EED\u63A2\u7D22\u3002";
2156
2238
  function toolResultMessage(tc, result, taskId, toolResultDir2) {
@@ -2279,6 +2361,8 @@ var TurnEngine = class _TurnEngine {
2279
2361
  _lastContextTokens = null;
2280
2362
  /** 压缩无进展时的消息条数快照;-1 表示无冷却 */
2281
2363
  _compactStalledAt = -1;
2364
+ /** 溢出兜底:已 400 时无条件淘汰旧工具结果(下一次成功调用后复位) */
2365
+ _overflowElide = false;
2282
2366
  /** 显式配置的上下文窗口大小(token 数),未设置时为 undefined(走自动检测) */
2283
2367
  _contextWindow;
2284
2368
  /** 压缩触发阈值比例(默认 0.8) */
@@ -2417,11 +2501,12 @@ ${notice}` : text,
2417
2501
  };
2418
2502
  }
2419
2503
  /** 排队 steering 消息(下一迭代间隙注入为 user 消息) */
2420
- queueSteering(text, attachments, attachmentRefs) {
2504
+ queueSteering(text, attachments, attachmentRefs, actor) {
2421
2505
  this._steering.push({
2422
2506
  text,
2423
2507
  ...attachments?.length ? { attachments } : {},
2424
- ...attachmentRefs?.length ? { attachmentRefs } : {}
2508
+ ...attachmentRefs?.length ? { attachmentRefs } : {},
2509
+ ...actor ? { actor } : {}
2425
2510
  });
2426
2511
  }
2427
2512
  /**
@@ -2446,7 +2531,7 @@ ${notice}` : text,
2446
2531
  * 追加用户输入到历史,产出 TURN_START 事件,然后转发 _loop()
2447
2532
  * 的全部事件。收到中断后仍会消费完剩余事件(保证 TURN_END 产出)。
2448
2533
  */
2449
- async *run(userInput, taskId, attachments, attachmentRefs) {
2534
+ async *run(userInput, taskId, attachments, attachmentRefs, actor) {
2450
2535
  if (!this.model.trim()) {
2451
2536
  throw new Error(
2452
2537
  "\u672A\u6307\u5B9A\u6A21\u578B\uFF1A\u8BF7\u5148\u9009\u62E9\u6A21\u578B\uFF08CLI \u8FD0\u884C /connect\uFF1B\u63A7\u5236\u53F0\u5728\u300C\u8BBE\u7F6E \u2192 AI \u6A21\u578B\u300D\u4E2D\u6DFB\u52A0\u4F9B\u5E94\u5546\u5E76\u9009\u62E9\u6A21\u578B\uFF09"
@@ -2469,12 +2554,15 @@ ${notice}` : text,
2469
2554
  this._cancel = false;
2470
2555
  this._producedFiles = [];
2471
2556
  this._compactStalledAt = -1;
2557
+ this._overflowElide = false;
2472
2558
  this._emitHook((h) => h.onTurnStart?.(userInput));
2473
2559
  yield {
2474
2560
  type: "turn_start" /* TURN_START */,
2475
2561
  data: {
2476
2562
  taskId: this.taskId,
2477
2563
  input: userInput,
2564
+ // 谁发起的本轮:会话详情据此标注(本地/旧网关没有该字段)
2565
+ ...actor ? { actor } : {},
2478
2566
  // 只报个数:附件字节不进事件流(事件会落盘,base64 会把日志撑爆)
2479
2567
  ...attachments?.length ? { attachmentCount: attachments.length } : {},
2480
2568
  // 落盘引用足够小,随事件带给控制台渲染缩略图 / 详情回看
@@ -2778,6 +2866,12 @@ ${notice}` : text,
2778
2866
  type: "compacting" /* COMPACTING */,
2779
2867
  data: { taskId: this.taskId, ...startInfo }
2780
2868
  };
2869
+ let elided = false;
2870
+ if (this._elisionEnabled) {
2871
+ const before = this._estimateContextTokens();
2872
+ this._overflowElide = true;
2873
+ elided = this._estimateContextTokens() < before;
2874
+ }
2781
2875
  const result = await this._compactNow(overflowReqId, true);
2782
2876
  if (result.notice) {
2783
2877
  const notice = result.notice;
@@ -2816,6 +2910,11 @@ ${notice}` : text,
2816
2910
  }
2817
2911
  continue;
2818
2912
  }
2913
+ if (elided) {
2914
+ this._appendNotice("compacted", "\u4E0A\u4E0B\u6587\u6EA2\u51FA \u2014 \u5DF2\u6DD8\u6C70\u65E7\u5DE5\u5177\u7ED3\u679C\u540E\u91CD\u8BD5");
2915
+ continue;
2916
+ }
2917
+ this._overflowElide = false;
2819
2918
  }
2820
2919
  if (!this._cancel && !streamed && isTransientNetworkError(exc) && netRetries < netMax) {
2821
2920
  netRetries++;
@@ -2886,6 +2985,7 @@ ${notice}` : text,
2886
2985
  return;
2887
2986
  }
2888
2987
  netRetries = 0;
2988
+ this._overflowElide = false;
2889
2989
  if (this._cancel && turn === null) {
2890
2990
  yield* this._finishInterrupted({
2891
2991
  iterations,
@@ -2944,6 +3044,7 @@ ${notice}` : text,
2944
3044
  data: {
2945
3045
  text: s.text,
2946
3046
  taskId: this.taskId,
3047
+ ...s.actor ? { actor: s.actor } : {},
2947
3048
  ...s.attachmentRefs?.length ? { attachments: s.attachmentRefs } : {}
2948
3049
  }
2949
3050
  };
@@ -3008,6 +3109,7 @@ ${notice}` : text,
3008
3109
  data: {
3009
3110
  text: s.text,
3010
3111
  taskId: this.taskId,
3112
+ ...s.actor ? { actor: s.actor } : {},
3011
3113
  ...s.attachmentRefs?.length ? { attachments: s.attachmentRefs } : {}
3012
3114
  }
3013
3115
  };
@@ -3432,13 +3534,14 @@ ${notice}` : text,
3432
3534
  const deduped = this._dedupEnabled ? applyReadFileDedup(base).messages : base;
3433
3535
  let view = this._finalizeOutbound(deduped);
3434
3536
  if (!this._elisionEnabled) return view;
3435
- if (estimateTokens(view) < this._softTriggerTokens()) {
3537
+ const soft = this._softTriggerTokens();
3538
+ if (!this._overflowElide && estimateTokens(view) < soft) {
3436
3539
  return view;
3437
3540
  }
3438
3541
  const oneShot = applyOneShotElision(deduped);
3439
3542
  if (oneShot.freedChars > 0) {
3440
3543
  view = this._finalizeOutbound(oneShot.messages);
3441
- if (estimateTokens(view) < this._softTriggerTokens()) {
3544
+ if (!this._overflowElide && estimateTokens(view) < soft) {
3442
3545
  return view;
3443
3546
  }
3444
3547
  }
@@ -3447,7 +3550,8 @@ ${notice}` : text,
3447
3550
  this._getContextWindow(),
3448
3551
  this._thresholdPct,
3449
3552
  this._capTokens
3450
- )
3553
+ ),
3554
+ this._overflowElide ? OVERFLOW_ELIDE_KEEP_FRACTION : ELIDE_KEEP_FRACTION
3451
3555
  );
3452
3556
  const elided = applyToolResultElision(
3453
3557
  oneShot.messages,
@@ -3646,7 +3750,8 @@ ${notice}` : text,
3646
3750
  const trimmed = trimState(
3647
3751
  this.messages,
3648
3752
  this.compactionState,
3649
- force ? OVERFLOW_TRIM_FRACTION : 0.25
3753
+ force ? OVERFLOW_TRIM_FRACTION : 0.25,
3754
+ this.lang
3650
3755
  );
3651
3756
  if (!trimmed) break;
3652
3757
  this.compactionState = trimmed;
@@ -3691,9 +3796,9 @@ ${notice}` : text,
3691
3796
  return;
3692
3797
  }
3693
3798
  if (result && typeof result === "object") {
3694
- const path12 = result.path;
3695
- if (path12) {
3696
- this._producedFiles.push(path12);
3799
+ const path13 = result.path;
3800
+ if (path13) {
3801
+ this._producedFiles.push(path13);
3697
3802
  return;
3698
3803
  }
3699
3804
  const changed = result.changed_files;
@@ -4232,62 +4337,196 @@ var PermissionEngine = class {
4232
4337
  };
4233
4338
 
4234
4339
  // src/tools/files.ts
4340
+ var fs6 = __toESM(require("fs"), 1);
4341
+ var path8 = __toESM(require("path"), 1);
4342
+ var import_node_crypto = require("crypto");
4343
+ var import_node_string_decoder = require("string_decoder");
4344
+
4345
+ // src/tools/ignore.ts
4235
4346
  var fs5 = __toESM(require("fs"), 1);
4236
4347
  var path7 = __toESM(require("path"), 1);
4348
+ var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
4349
+ // JS / TS
4350
+ "node_modules",
4351
+ "dist",
4352
+ "build",
4353
+ "out",
4354
+ ".next",
4355
+ ".nuxt",
4356
+ ".cache",
4357
+ // JVM
4358
+ "target",
4359
+ ".gradle",
4360
+ ".mvn",
4361
+ // .NET
4362
+ "bin",
4363
+ "obj",
4364
+ ".vs",
4365
+ "TestResults",
4366
+ // Python / Ruby / PHP
4367
+ "__pycache__",
4368
+ ".venv",
4369
+ "venv",
4370
+ ".pytest_cache",
4371
+ ".mypy_cache",
4372
+ "vendor",
4373
+ // 移动端
4374
+ "Pods",
4375
+ ".dart_tool",
4376
+ "DerivedData",
4377
+ // 覆盖率 / 通用
4378
+ "coverage",
4379
+ // 版本库
4380
+ ".git",
4381
+ ".svn",
4382
+ ".hg"
4383
+ ]);
4384
+ function loadGitignore(dir) {
4385
+ const file = path7.join(dir, ".gitignore");
4386
+ let text;
4387
+ try {
4388
+ const stat = fs5.statSync(file);
4389
+ if (stat.size > 64 * 1024) return [];
4390
+ text = fs5.readFileSync(file, "utf-8");
4391
+ } catch {
4392
+ return [];
4393
+ }
4394
+ const matchers = [];
4395
+ for (const rawLine of text.split("\n")) {
4396
+ let line = rawLine.replace(/\r$/, "").trim();
4397
+ if (!line || line.startsWith("#")) continue;
4398
+ if (line.startsWith("!")) continue;
4399
+ const anchored = rawLine.trim().startsWith("/");
4400
+ const dirOnly = /\/\s*$/.test(rawLine.trim());
4401
+ line = line.replace(/^\/+/, "").replace(/\/+$/, "");
4402
+ if (!line) continue;
4403
+ const pattern = line.split("").map((ch) => {
4404
+ if (ch === "*") return ".*";
4405
+ if (ch === "?") return ".";
4406
+ return ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
4407
+ }).join("");
4408
+ const re = new RegExp(line.includes("/") || anchored ? `^${pattern}$` : `(^|/)${pattern}$`);
4409
+ matchers.push((rel, isDir) => {
4410
+ if (dirOnly && !isDir) return false;
4411
+ return re.test(rel);
4412
+ });
4413
+ }
4414
+ return matchers;
4415
+ }
4416
+ function makeIgnoreFilter(root) {
4417
+ const matchers = loadGitignore(root);
4418
+ return (rel, isDir) => {
4419
+ const name = rel.slice(rel.lastIndexOf("/") + 1);
4420
+ if (name.startsWith(".")) return true;
4421
+ if (isDir && ALWAYS_SKIP_DIRS.has(name)) return true;
4422
+ return matchers.some((m) => m(rel, isDir));
4423
+ };
4424
+ }
4425
+
4426
+ // src/tools/files.ts
4237
4427
  var DEFAULT_MAX_LINES = 2e3;
4238
4428
  var MULTI_READ_MAX_TOTAL_LINES = 1500;
4239
4429
  var MAX_LINE_CHARS = 500;
4430
+ var READ_CHUNK_BYTES = 64 * 1024;
4431
+ var RESULT_META_RESERVE = 800;
4240
4432
  function makeReadFile(workspace, guard) {
4241
- const root = path7.resolve(workspace);
4242
- function readOne(filePath, startLine, maxLines) {
4433
+ const root = path8.resolve(workspace);
4434
+ function readWindow(target, startLine, maxLines, charBudget, onDigest) {
4435
+ const fd = fs6.openSync(target, "r");
4436
+ try {
4437
+ const decoder = new import_node_string_decoder.StringDecoder("utf-8");
4438
+ const hash = (0, import_node_crypto.createHash)("sha256");
4439
+ const buf = Buffer.allocUnsafe(READ_CHUNK_BYTES);
4440
+ const lines = [];
4441
+ let lineNo = 0;
4442
+ let pending = "";
4443
+ let chars = 0;
4444
+ let capped = false;
4445
+ let pos = 0;
4446
+ const takeLine = (text) => {
4447
+ lineNo += 1;
4448
+ if (capped || lineNo < startLine || lines.length >= maxLines) return;
4449
+ const clipped = text.length > MAX_LINE_CHARS ? text.slice(0, MAX_LINE_CHARS) + "\u2026 (line truncated)" : text;
4450
+ const rendered = `${lineNo}|${clipped}`;
4451
+ if (chars + rendered.length + 1 > charBudget) {
4452
+ capped = true;
4453
+ return;
4454
+ }
4455
+ chars += rendered.length + 1;
4456
+ lines.push(rendered);
4457
+ };
4458
+ for (; ; ) {
4459
+ const n = fs6.readSync(fd, buf, 0, READ_CHUNK_BYTES, pos);
4460
+ if (n <= 0) break;
4461
+ pos += n;
4462
+ const decoded = decoder.write(buf.subarray(0, n));
4463
+ hash.update(decoded, "utf8");
4464
+ const text = pending + decoded;
4465
+ let from = 0;
4466
+ for (; ; ) {
4467
+ const idx = text.indexOf("\n", from);
4468
+ if (idx < 0) break;
4469
+ takeLine(text.slice(from, idx).replace(/\r$/, ""));
4470
+ from = idx + 1;
4471
+ }
4472
+ pending = text.slice(from);
4473
+ }
4474
+ const rest = decoder.end();
4475
+ if (rest) hash.update(rest, "utf8");
4476
+ const tail = pending + rest;
4477
+ if (tail.length > 0) takeLine(tail.replace(/\r$/, ""));
4478
+ onDigest(hash.digest("hex").slice(0, 16));
4479
+ return { lines, totalLines: lineNo, capped };
4480
+ } finally {
4481
+ fs6.closeSync(fd);
4482
+ }
4483
+ }
4484
+ function readOne(filePath, startLine, maxLines, charBudget) {
4243
4485
  const resolved = resolveWithinRoot(root, filePath);
4244
4486
  if (!resolved) {
4245
4487
  return { path: filePath, error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
4246
4488
  }
4247
4489
  const target = resolved.real;
4248
- if (!fs5.existsSync(target) || !fs5.statSync(target).isFile()) {
4490
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isFile()) {
4249
4491
  return { path: filePath, error: `\u4E0D\u662F\u6587\u4EF6: ${filePath}` };
4250
4492
  }
4251
- const selected = [];
4252
- let total = 0;
4493
+ let read;
4253
4494
  try {
4254
- const content = fs5.readFileSync(target, "utf-8");
4255
- guard?.noteRead(target, content);
4256
- const lines = content.split("\n");
4257
- total = lines.length;
4258
- for (let i = startLine - 1; i < Math.min(startLine - 1 + maxLines, lines.length); i++) {
4259
- let text = lines[i];
4260
- if (text.length > MAX_LINE_CHARS) {
4261
- text = text.slice(0, MAX_LINE_CHARS) + "\u2026 (line truncated)";
4262
- }
4263
- selected.push(`${i + 1}|${text}`);
4264
- }
4495
+ read = readWindow(target, startLine, maxLines, charBudget, (digest) => {
4496
+ guard?.noteReadDigest(target, digest);
4497
+ });
4265
4498
  } catch (exc) {
4266
4499
  return { path: filePath, error: `\u8BFB\u53D6\u5931\u8D25: ${String(exc)}` };
4267
4500
  }
4501
+ const { lines: selected, totalLines: total, capped } = read;
4268
4502
  const end = startLine + selected.length - 1;
4269
4503
  const result = {
4270
- path: path7.relative(resolved.root, target),
4504
+ path: path8.relative(resolved.root, target),
4271
4505
  start_line: startLine,
4272
4506
  end_line: end,
4273
4507
  total_lines: total,
4274
4508
  content: selected.join("\n")
4275
4509
  };
4276
- if (end < total) {
4510
+ if (capped) {
4511
+ result.note = `\u5DF2\u8FD4\u56DE\u7B2C ${startLine}-${end} \u884C\uFF08\u672C\u6B21\u5DF2\u8FBE\u5355\u6B21\u8BFB\u53D6\u4E0A\u9650\u7EA6 ${charBudget} \u5B57\u7B26\uFF0C\u5171 ${total} \u884C\uFF09\uFF1B\u8C03\u7528 read_file(start_line=${end + 1}) \u7EE7\u7EED\u9605\u8BFB`;
4512
+ } else if (end < total) {
4277
4513
  result.note = `\u663E\u793A\u7B2C ${startLine}-${end} \u884C\uFF0C\u5171 ${total} \u884C\uFF1B\u8C03\u7528 read_file(start_line=${end + 1}) \u7EE7\u7EED\u9605\u8BFB`;
4278
4514
  }
4279
4515
  return result;
4280
4516
  }
4281
4517
  return function read_file(args) {
4282
4518
  const startLine = Math.max(1, Number(args.start_line ?? 1));
4283
- const maxLines = Math.min(Number(args.max_lines ?? DEFAULT_MAX_LINES), DEFAULT_MAX_LINES);
4519
+ const requestedLines = Number(args.max_lines ?? DEFAULT_MAX_LINES);
4520
+ const maxLines = Number.isFinite(requestedLines) ? Math.min(Math.max(1, Math.floor(requestedLines)), DEFAULT_MAX_LINES) : DEFAULT_MAX_LINES;
4521
+ const fullBudget = Math.max(1e3, toolResultBudget("read_file") - RESULT_META_RESERVE);
4284
4522
  const paths = Array.isArray(args.paths) ? args.paths.map((p) => String(p ?? "")).filter(Boolean) : [];
4285
4523
  if (paths.length > 0) {
4524
+ const perFileBudget = Math.max(1e3, Math.floor(fullBudget / paths.length));
4286
4525
  const files = [];
4287
4526
  let remainingLines = MULTI_READ_MAX_TOTAL_LINES;
4288
4527
  for (const filePath2 of paths) {
4289
4528
  const perFile = Math.min(maxLines, Math.max(1, remainingLines));
4290
- const one = readOne(filePath2, startLine, perFile);
4529
+ const one = readOne(filePath2, startLine, perFile, perFileBudget);
4291
4530
  files.push(one);
4292
4531
  if (!one.error) remainingLines -= one.content.split("\n").length;
4293
4532
  if (remainingLines <= 0) break;
@@ -4302,31 +4541,79 @@ function makeReadFile(workspace, guard) {
4302
4541
  if (!filePath) {
4303
4542
  return { error: "\u7F3A\u5C11 path\uFF08\u6216 paths\uFF09\u53C2\u6570" };
4304
4543
  }
4305
- return readOne(filePath, startLine, maxLines);
4544
+ return readOne(filePath, startLine, maxLines, fullBudget);
4306
4545
  };
4307
4546
  }
4547
+ var DEFAULT_LIST_DEPTH = 3;
4548
+ var MAX_LIST_DEPTH = 6;
4549
+ var DEFAULT_RECURSIVE_LIMIT = 400;
4550
+ var MAX_RECURSIVE_LIMIT = 1e3;
4308
4551
  function makeListFiles(workspace) {
4309
- const root = path7.resolve(workspace);
4552
+ const root = path8.resolve(workspace);
4310
4553
  return function list_files(args) {
4311
4554
  const dirPath = String(args.path ?? ".");
4312
- const maxResults = Math.min(Number(args.max_results ?? 100), 100);
4555
+ const recursive = args.recursive === true;
4556
+ const requestedMax = Number(args.max_results ?? (recursive ? DEFAULT_RECURSIVE_LIMIT : 100));
4557
+ const hardCap = recursive ? MAX_RECURSIVE_LIMIT : 100;
4558
+ const maxResults = Number.isFinite(requestedMax) ? Math.max(1, Math.min(Math.floor(requestedMax), hardCap)) : hardCap;
4559
+ const requestedDepth = Number(args.max_depth ?? DEFAULT_LIST_DEPTH);
4560
+ const maxDepth = Number.isFinite(requestedDepth) ? Math.max(1, Math.min(Math.floor(requestedDepth), MAX_LIST_DEPTH)) : DEFAULT_LIST_DEPTH;
4313
4561
  const resolved = resolveWithinRoot(root, dirPath);
4314
4562
  if (!resolved) return { error: "\u8DEF\u5F84\u5728\u5DE5\u4F5C\u533A\u4E4B\u5916" };
4315
4563
  const target = resolved.real;
4316
- if (!fs5.existsSync(target) || !fs5.statSync(target).isDirectory()) {
4564
+ if (!fs6.existsSync(target) || !fs6.statSync(target).isDirectory()) {
4317
4565
  return { error: `\u4E0D\u662F\u76EE\u5F55: ${dirPath}` };
4318
4566
  }
4567
+ const realRoot = resolved.root;
4568
+ const isIgnored = makeIgnoreFilter(realRoot);
4319
4569
  try {
4320
- const entries = fs5.readdirSync(target, { withFileTypes: true });
4570
+ if (!recursive) {
4571
+ const results2 = [];
4572
+ for (const e of fs6.readdirSync(target, { withFileTypes: true })) {
4573
+ if (results2.length >= maxResults) break;
4574
+ const rel = path8.relative(realRoot, path8.join(target, e.name)).split(path8.sep).join("/");
4575
+ if (isIgnored(rel, e.isDirectory())) continue;
4576
+ results2.push(e.isDirectory() ? `${e.name}/` : e.name);
4577
+ }
4578
+ results2.sort();
4579
+ return { path: path8.relative(realRoot, target), entries: results2.slice(0, maxResults) };
4580
+ }
4321
4581
  const results = [];
4322
- for (const e of entries) {
4323
- if (results.length >= maxResults) break;
4324
- if (e.name.startsWith(".")) continue;
4325
- if (e.name === "node_modules" || e.name === "__pycache__" || e.name === "dist") continue;
4326
- results.push(e.isDirectory() ? `${e.name}/` : e.name);
4582
+ let truncated = false;
4583
+ const queue = [{ dir: target, depth: 1 }];
4584
+ while (queue.length > 0) {
4585
+ const { dir, depth } = queue.shift();
4586
+ let entries;
4587
+ try {
4588
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
4589
+ } catch {
4590
+ continue;
4591
+ }
4592
+ entries.sort((a, b) => {
4593
+ if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
4594
+ return a.name.localeCompare(b.name, "zh");
4595
+ });
4596
+ for (const e of entries) {
4597
+ if (results.length >= maxResults) {
4598
+ truncated = true;
4599
+ break;
4600
+ }
4601
+ const full = path8.join(dir, e.name);
4602
+ const rel = path8.relative(realRoot, full).split(path8.sep).join("/");
4603
+ const isDir = e.isDirectory();
4604
+ if (isIgnored(rel, isDir)) continue;
4605
+ results.push(isDir ? `${rel}/` : rel);
4606
+ if (isDir && depth < maxDepth) queue.push({ dir: full, depth: depth + 1 });
4607
+ }
4608
+ if (truncated) break;
4327
4609
  }
4328
- results.sort();
4329
- return { path: path7.relative(resolved.root, target), entries: results.slice(0, maxResults) };
4610
+ return {
4611
+ path: path8.relative(realRoot, target),
4612
+ entries: results,
4613
+ ...truncated ? {
4614
+ note: `\u5DF2\u8FBE\u5230\u6761\u76EE\u4E0A\u9650 ${maxResults}\uFF0C\u7ED3\u679C\u88AB\u622A\u65AD\uFF1B\u8BF7\u7F29\u5C0F path \u5230\u5177\u4F53\u5B50\u76EE\u5F55\uFF0C\u6216\u964D\u4F4E max_depth\uFF08\u5F53\u524D ${maxDepth}\uFF09`
4615
+ } : {}
4616
+ };
4330
4617
  } catch (exc) {
4331
4618
  return { error: `\u5217\u51FA\u6587\u4EF6\u5931\u8D25: ${String(exc)}` };
4332
4619
  }
@@ -4366,12 +4653,23 @@ function registerReadOnlyFileTools(registry, workspace, guard) {
4366
4653
  type: "function",
4367
4654
  function: {
4368
4655
  name: "list_files",
4369
- description: "\u5217\u51FA\u76EE\u5F55\u4E0B\u7684\u6587\u4EF6\u548C\u5B50\u76EE\u5F55\u3002\u8DF3\u8FC7\u9690\u85CF\u6587\u4EF6\u548C node_modules \u7B49\u76EE\u5F55\u3002",
4656
+ description: "\u5217\u51FA\u76EE\u5F55\u4E0B\u7684\u6587\u4EF6\u548C\u5B50\u76EE\u5F55\u3002\u8DF3\u8FC7\u9690\u85CF\u9879\u3001\u5E38\u89C1\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\uFF0C\u5E76\u9075\u5B88\u5DE5\u4F5C\u533A\u6839\u76EE\u5F55\u7684 .gitignore\u3002\u4E86\u89E3\u5927\u4ED3\u5E93\u7ED3\u6784\u65F6\u7528 recursive=true \u4E00\u6B21\u62FF\u5230\u6709\u9650\u6DF1\u5EA6\uFF08\u9ED8\u8BA4 3 \u5C42\uFF09\u7684\u6811\uFF0C\u6BD4\u9010\u5C42\u8C03\u7528\u7701\u5F88\u591A\u5F80\u8FD4\uFF1B\u6761\u76EE\u8FC7\u591A\u65F6\u4F1A\u622A\u65AD\u5E76\u63D0\u793A\u3002",
4370
4657
  parameters: {
4371
4658
  type: "object",
4372
4659
  properties: {
4373
4660
  path: { type: "string", description: "\u76EE\u5F55\u8DEF\u5F84 (\u76F8\u5BF9\u4E8E\u5DE5\u4F5C\u533A\uFF0C\u9ED8\u8BA4 '.')" },
4374
- max_results: { type: "integer", description: "\u6700\u5927\u7ED3\u679C\u6570 (\u9ED8\u8BA4 100)" }
4661
+ recursive: {
4662
+ type: "boolean",
4663
+ description: "\u9012\u5F52\u5217\u51FA\u5B50\u76EE\u5F55\uFF08\u9ED8\u8BA4 false\uFF0C\u53EA\u5217\u4E00\u5C42\uFF09"
4664
+ },
4665
+ max_depth: {
4666
+ type: "integer",
4667
+ description: `\u9012\u5F52\u6DF1\u5EA6 (\u9ED8\u8BA4 ${DEFAULT_LIST_DEPTH}\uFF0C\u4E0A\u9650 ${MAX_LIST_DEPTH}\uFF1B\u4EC5 recursive \u65F6\u751F\u6548)`
4668
+ },
4669
+ max_results: {
4670
+ type: "integer",
4671
+ description: `\u6700\u5927\u7ED3\u679C\u6570\uFF08\u975E\u9012\u5F52\u9ED8\u8BA4 100 / \u4E0A\u9650 100\uFF1B\u9012\u5F52\u9ED8\u8BA4 ${DEFAULT_RECURSIVE_LIMIT} / \u4E0A\u9650 ${MAX_RECURSIVE_LIMIT}\uFF09`
4672
+ }
4375
4673
  }
4376
4674
  }
4377
4675
  }
@@ -4539,10 +4837,10 @@ function execAsync(file, argv, opts = {}) {
4539
4837
  }
4540
4838
 
4541
4839
  // src/tools/shellRuntime.ts
4542
- var path8 = __toESM(require("path"), 1);
4840
+ var path9 = __toESM(require("path"), 1);
4543
4841
  var WINDOWS_SUFFIXES = [".exe", ".cmd", ".bat", ".com", ""];
4544
4842
  function pathApi(platform) {
4545
- return platform === "win32" ? path8.win32 : path8.posix;
4843
+ return platform === "win32" ? path9.win32 : path9.posix;
4546
4844
  }
4547
4845
  function joinPath(platform, ...parts) {
4548
4846
  return pathApi(platform).join(...parts);
@@ -4755,8 +5053,8 @@ function decodeOutput(buf) {
4755
5053
  var shellTasks = new ShellTaskManager();
4756
5054
 
4757
5055
  // src/tools/nodeGrep.ts
4758
- var fs6 = __toESM(require("fs"), 1);
4759
- var path9 = __toESM(require("path"), 1);
5056
+ var fs7 = __toESM(require("fs"), 1);
5057
+ var path10 = __toESM(require("path"), 1);
4760
5058
  var SKIP_DIRS = /* @__PURE__ */ new Set([
4761
5059
  ".git",
4762
5060
  "node_modules",
@@ -4781,27 +5079,27 @@ function nodeGrep(opts) {
4781
5079
  } catch (err) {
4782
5080
  throw new Error(`\u65E0\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F: ${opts.pattern}\uFF08${String(err)}\uFF09`);
4783
5081
  }
4784
- const target = path9.resolve(opts.workspace, opts.target);
4785
- if (!fs6.existsSync(target)) {
5082
+ const target = path10.resolve(opts.workspace, opts.target);
5083
+ if (!fs7.existsSync(target)) {
4786
5084
  throw new Error(`\u641C\u7D22\u8DEF\u5F84\u4E0D\u5B58\u5728: ${opts.target}`);
4787
5085
  }
4788
5086
  const matches = [];
4789
5087
  const scanFile = (file) => {
4790
5088
  let stat2;
4791
5089
  try {
4792
- stat2 = fs6.statSync(file);
5090
+ stat2 = fs7.statSync(file);
4793
5091
  } catch {
4794
5092
  return false;
4795
5093
  }
4796
5094
  if (!stat2.isFile() || stat2.size > maxFileBytes) return false;
4797
5095
  let buf;
4798
5096
  try {
4799
- buf = fs6.readFileSync(file);
5097
+ buf = fs7.readFileSync(file);
4800
5098
  } catch {
4801
5099
  return false;
4802
5100
  }
4803
5101
  if (looksBinary(buf)) return false;
4804
- const rel = path9.relative(opts.workspace, file) || path9.basename(file);
5102
+ const rel = path10.relative(opts.workspace, file) || path10.basename(file);
4805
5103
  const lines = buf.toString("utf-8").split("\n");
4806
5104
  for (let i = 0; i < lines.length; i++) {
4807
5105
  const raw = lines[i].replace(/\r$/, "");
@@ -4812,7 +5110,7 @@ function nodeGrep(opts) {
4812
5110
  }
4813
5111
  return false;
4814
5112
  };
4815
- const stat = fs6.statSync(target);
5113
+ const stat = fs7.statSync(target);
4816
5114
  if (stat.isFile()) {
4817
5115
  scanFile(target);
4818
5116
  return matches;
@@ -4822,12 +5120,12 @@ function nodeGrep(opts) {
4822
5120
  const dir = stack.pop();
4823
5121
  let entries;
4824
5122
  try {
4825
- entries = fs6.readdirSync(dir, { withFileTypes: true });
5123
+ entries = fs7.readdirSync(dir, { withFileTypes: true });
4826
5124
  } catch {
4827
5125
  continue;
4828
5126
  }
4829
5127
  for (const entry of entries) {
4830
- const full = path9.join(dir, entry.name);
5128
+ const full = path10.join(dir, entry.name);
4831
5129
  if (entry.isDirectory()) {
4832
5130
  if (!SKIP_DIRS.has(entry.name)) stack.push(full);
4833
5131
  } else if (entry.isFile()) {
@@ -4839,22 +5137,22 @@ function nodeGrep(opts) {
4839
5137
  }
4840
5138
 
4841
5139
  // src/tools/ripgrep.ts
4842
- var fs7 = __toESM(require("fs"), 1);
5140
+ var fs8 = __toESM(require("fs"), 1);
4843
5141
  var os3 = __toESM(require("os"), 1);
4844
- var path10 = __toESM(require("path"), 1);
5142
+ var path11 = __toESM(require("path"), 1);
4845
5143
  var import_node_module = require("module");
4846
5144
  var cached;
4847
5145
  function extractBundledRipgrep() {
4848
5146
  try {
4849
- const req = typeof require === "function" ? require : (0, import_node_module.createRequire)(path10.join(process.cwd(), "__zqagent__.js"));
5147
+ const req = typeof require === "function" ? require : (0, import_node_module.createRequire)(path11.join(process.cwd(), "__zqagent__.js"));
4850
5148
  const sea = req("node:sea");
4851
5149
  if (!sea?.isSea?.() || typeof sea.getAsset !== "function") return null;
4852
5150
  const asset = sea.getAsset("rg");
4853
5151
  if (!asset) return null;
4854
- const dir = fs7.mkdtempSync(path10.join(os3.tmpdir(), "zqagent-rg-"));
4855
- const file = path10.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
4856
- fs7.writeFileSync(file, Buffer.from(asset));
4857
- fs7.chmodSync(file, 493);
5152
+ const dir = fs8.mkdtempSync(path11.join(os3.tmpdir(), "zqagent-rg-"));
5153
+ const file = path11.join(dir, process.platform === "win32" ? "rg.exe" : "rg");
5154
+ fs8.writeFileSync(file, Buffer.from(asset));
5155
+ fs8.chmodSync(file, 493);
4858
5156
  return file;
4859
5157
  } catch {
4860
5158
  return null;
@@ -4863,7 +5161,7 @@ function extractBundledRipgrep() {
4863
5161
  function resolveRipgrep() {
4864
5162
  if (cached !== void 0) return cached;
4865
5163
  const explicit = (process.env.ZQAGENT_RG ?? "").trim();
4866
- if (explicit && fs7.existsSync(explicit)) {
5164
+ if (explicit && fs8.existsSync(explicit)) {
4867
5165
  cached = explicit;
4868
5166
  return cached;
4869
5167
  }
@@ -4877,7 +5175,7 @@ function resolveRipgrep() {
4877
5175
  env: process.env,
4878
5176
  fileExists: (p) => {
4879
5177
  try {
4880
- return fs7.statSync(p).isFile();
5178
+ return fs8.statSync(p).isFile();
4881
5179
  } catch {
4882
5180
  return false;
4883
5181
  }
@@ -4887,12 +5185,61 @@ function resolveRipgrep() {
4887
5185
  }
4888
5186
 
4889
5187
  // src/tools/search.ts
4890
- function parseRgOutput(result) {
4891
- return result.split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.length > 0).slice(0, 50).map((line) => {
4892
- const m = /^(.+?):(\d+):(.*)$/.exec(line);
4893
- if (!m) return { file: line.trim(), line: 0, text: "" };
4894
- return { file: m[1], line: Number(m[2]), text: m[3].trim().slice(0, 200) };
4895
- });
5188
+ var DEFAULT_MAX_RESULTS = 50;
5189
+ var MAX_RESULTS_CAP = 200;
5190
+ var MAX_TEXT_CHARS = 200;
5191
+ var RG_TIMEOUT_MS = 1e4;
5192
+ function parseRgOutput(result, limit) {
5193
+ const out = [];
5194
+ for (const raw of result.split("\n")) {
5195
+ if (out.length >= limit) break;
5196
+ const line = raw.replace(/\r$/, "");
5197
+ if (!line) continue;
5198
+ const m = /^(.+?)[:-](\d+)[:-](.*)$/.exec(line);
5199
+ if (!m) continue;
5200
+ out.push({
5201
+ file: m[1],
5202
+ line: Number(m[2]),
5203
+ text: m[3].trim().slice(0, MAX_TEXT_CHARS)
5204
+ });
5205
+ }
5206
+ return out;
5207
+ }
5208
+ function parseRgFiles(result, limit) {
5209
+ const files = [];
5210
+ const seen = /* @__PURE__ */ new Set();
5211
+ for (const raw of result.split("\n")) {
5212
+ if (files.length >= limit) break;
5213
+ const f = raw.replace(/\r$/, "").trim();
5214
+ if (!f || seen.has(f)) continue;
5215
+ seen.add(f);
5216
+ files.push(f);
5217
+ }
5218
+ return files;
5219
+ }
5220
+ function rgArgs(opts) {
5221
+ const args = [];
5222
+ if (opts.filesOnly) {
5223
+ args.push("--files-with-matches");
5224
+ } else {
5225
+ args.push("--with-filename", "--line-number", "--no-heading");
5226
+ args.push("--max-count", String(Math.max(opts.maxResults, 1)));
5227
+ }
5228
+ if (opts.ignoreCase) args.push("--ignore-case");
5229
+ if (opts.glob) args.push("--glob", opts.glob);
5230
+ if (opts.contextLines > 0 && !opts.filesOnly) {
5231
+ args.push("--context", String(opts.contextLines));
5232
+ }
5233
+ args.push("--", opts.pattern, opts.path);
5234
+ return args;
5235
+ }
5236
+ function rgFailureHint(res) {
5237
+ if (res.timedOut) {
5238
+ return `\u641C\u7D22\u8D85\u65F6\uFF08${RG_TIMEOUT_MS / 1e3}s\uFF09\u3002\u5927\u4ED3\u5E93\u5168\u91CF\u641C\u7D22\u5F88\u5BB9\u6613\u8D85\u65F6\uFF1A\u8BF7\u7F29\u5C0F path \u5230\u5177\u4F53\u5B50\u76EE\u5F55\uFF0C\u6216\u7528 glob \u53C2\u6570\u9650\u5236\u6587\u4EF6\u7C7B\u578B\uFF08\u5982 "*.ts"\uFF09\u540E\u91CD\u8BD5`;
5239
+ }
5240
+ if (res.aborted) return "\u641C\u7D22\u5DF2\u4E2D\u65AD";
5241
+ const stderr = decodeOutput(res.stderr).trim().slice(0, 300);
5242
+ return `\u641C\u7D22\u5931\u8D25\uFF08rg \u9000\u51FA\u7801 ${String(res.status)}\uFF09${stderr ? `\uFF1A${stderr}` : ""}`;
4896
5243
  }
4897
5244
  function makeGrep(workspace) {
4898
5245
  return async function grep(args) {
@@ -4901,37 +5248,50 @@ function makeGrep(workspace) {
4901
5248
  if (!pattern.trim()) {
4902
5249
  return { error: "\u641C\u7D22\u6A21\u5F0F\u4E0D\u80FD\u4E3A\u7A7A" };
4903
5250
  }
5251
+ const filesOnly = args.files_only === true;
5252
+ const ignoreCase = args.ignore_case === true;
5253
+ const glob = typeof args.glob === "string" && args.glob.trim() ? args.glob.trim() : void 0;
5254
+ const contextLines = Math.max(
5255
+ 0,
5256
+ Math.min(Number(args.context_lines ?? 0) || 0, 5)
5257
+ );
5258
+ const maxResults = Math.max(
5259
+ 1,
5260
+ Math.min(Number(args.max_results ?? DEFAULT_MAX_RESULTS) || DEFAULT_MAX_RESULTS, MAX_RESULTS_CAP)
5261
+ );
4904
5262
  const rg = resolveRipgrep();
4905
5263
  if (rg) {
4906
5264
  const res = await execAsync(
4907
5265
  rg,
4908
- [
4909
- // --with-filename:单文件模式下 rg 默认不输出文件名前缀,会导致
4910
- // "行号:内容" 被误当成 "文件:行号:内容" 解析而错位;显式要求带文件名。
4911
- "--with-filename",
4912
- "--line-number",
4913
- "--no-heading",
4914
- "--max-count",
4915
- "50",
4916
- pattern,
4917
- filePath
4918
- ],
5266
+ rgArgs({ pattern, path: filePath, filesOnly, ignoreCase, glob, contextLines, maxResults }),
4919
5267
  {
4920
5268
  cwd: workspace,
4921
- timeoutMs: 1e4,
5269
+ timeoutMs: RG_TIMEOUT_MS,
4922
5270
  maxBuffer: 1024 * 1024 * 2
4923
5271
  }
4924
5272
  );
4925
5273
  if (res.status === 0) {
4926
- const matches = parseRgOutput(decodeOutput(res.stdout));
4927
- return { query: pattern, engine: "ripgrep", matches, count: matches.length };
5274
+ const out = decodeOutput(res.stdout);
5275
+ return filesOnly ? { query: pattern, engine: "ripgrep", files: parseRgFiles(out, maxResults) } : (() => {
5276
+ const matches = parseRgOutput(out, maxResults);
5277
+ return { query: pattern, engine: "ripgrep", matches, count: matches.length };
5278
+ })();
4928
5279
  }
4929
5280
  if (res.status === 1) {
4930
- return { query: pattern, engine: "ripgrep", matches: [], count: 0 };
5281
+ return filesOnly ? { query: pattern, engine: "ripgrep", files: [], count: 0 } : { query: pattern, engine: "ripgrep", matches: [], count: 0 };
5282
+ }
5283
+ if (!res.error) {
5284
+ return { query: pattern, error: rgFailureHint(res) };
4931
5285
  }
4932
5286
  }
5287
+ if (filesOnly || glob || ignoreCase || contextLines > 0) {
5288
+ return {
5289
+ query: pattern,
5290
+ error: "\u5F53\u524D\u8282\u70B9\u672A\u5B89\u88C5 ripgrep\uFF0Cfiles_only / glob / ignore_case / context_lines \u9700\u8981 ripgrep \u624D\u80FD\u751F\u6548\uFF1B\u8BF7\u53BB\u6389\u8FD9\u4E9B\u53C2\u6570\u540E\u91CD\u8BD5\uFF0C\u6216\u5148\u5B89\u88C5 ripgrep"
5291
+ };
5292
+ }
4933
5293
  try {
4934
- const matches = nodeGrep({ pattern, target: filePath, workspace });
5294
+ const matches = nodeGrep({ pattern, target: filePath, workspace, maxMatches: maxResults });
4935
5295
  return { query: pattern, engine: "node", matches, count: matches.length };
4936
5296
  } catch (exc) {
4937
5297
  return { error: `\u641C\u7D22\u5931\u8D25: ${String(exc instanceof Error ? exc.message : exc)}` };
@@ -4947,12 +5307,29 @@ function registerSearchTool(registry, workspace) {
4947
5307
  type: "function",
4948
5308
  function: {
4949
5309
  name: "grep",
4950
- description: "\u5728\u6587\u4EF6\u4E2D\u641C\u7D22\u6587\u672C\u6A21\u5F0F\uFF08\u652F\u6301\u5B8C\u6574 JS \u6B63\u5219\u8BED\u6CD5\uFF09\u3002\u8FD4\u56DE\u5339\u914D\u7684\u6587\u4EF6\u540D\u3001\u884C\u53F7\u548C\u5185\u5BB9\uFF0C\u6700\u591A 50 \u6761\u3002\u4F18\u5148\u4F7F\u7528 ripgrep (rg) \u4EE5\u83B7\u5F97\u66F4\u597D\u7684\u6027\u80FD\uFF1Brg \u4E0D\u53EF\u7528\u65F6\u81EA\u52A8\u56DE\u9000\u5230\u5185\u7F6E\u626B\u63CF\u5668\u3002",
5310
+ description: "\u5728\u6587\u4EF6\u4E2D\u641C\u7D22\u6587\u672C\u6A21\u5F0F\uFF08\u652F\u6301\u5B8C\u6574 JS \u6B63\u5219\u8BED\u6CD5\uFF09\u3002\u9ED8\u8BA4\u8FD4\u56DE\u5339\u914D\u7684\u6587\u4EF6\u540D\u3001\u884C\u53F7\u548C\u5185\u5BB9\uFF08\u6700\u591A 50 \u6761\uFF09\u3002\u5B9A\u4F4D\u4EE3\u7801\u4F4D\u7F6E\u65F6\u4F18\u5148\u7528 files_only=true \u53EA\u53D6\u6587\u4EF6\u5217\u8868\uFF08\u4F53\u79EF\u5C0F\u4E00\u4E2A\u6570\u91CF\u7EA7\uFF09\uFF0C\u518D\u7528 read_file \u7CBE\u51C6\u8BFB\u53D6\uFF1B\u5927\u4ED3\u5E93\u8BF7\u914D\u5408 path \u7F29\u5C0F\u8303\u56F4\u6216 glob \u9650\u5236\u6587\u4EF6\u7C7B\u578B\uFF0C\u907F\u514D\u5168\u91CF\u626B\u63CF\u8D85\u65F6\u3002",
4951
5311
  parameters: {
4952
5312
  type: "object",
4953
5313
  properties: {
4954
5314
  pattern: { type: "string", description: "\u641C\u7D22\u6A21\u5F0F (\u6587\u672C\u6216\u6B63\u5219\u8868\u8FBE\u5F0F)" },
4955
- path: { type: "string", description: "\u641C\u7D22\u8DEF\u5F84 (\u6587\u4EF6\u6216\u76EE\u5F55\uFF0C\u9ED8\u8BA4 '.')" }
5315
+ path: { type: "string", description: "\u641C\u7D22\u8DEF\u5F84 (\u6587\u4EF6\u6216\u76EE\u5F55\uFF0C\u9ED8\u8BA4 '.'\uFF1B\u5927\u4ED3\u5E93\u5EFA\u8BAE\u5148\u7F29\u5C0F\u5230\u5B50\u76EE\u5F55)" },
5316
+ files_only: {
5317
+ type: "boolean",
5318
+ description: "\u53EA\u8FD4\u56DE\u5339\u914D\u7684\u6587\u4EF6\u8DEF\u5F84\uFF0C\u4E0D\u8FD4\u56DE\u884C\u5185\u5BB9\uFF08\u63A8\u8350\u7528\u4E8E\u5B9A\u4F4D\uFF0C\u663E\u8457\u8282\u7701\u4E0A\u4E0B\u6587\uFF09"
5319
+ },
5320
+ glob: {
5321
+ type: "string",
5322
+ description: '\u6587\u4EF6\u8FC7\u6EE4\uFF0C\u5982 "*.ts" \u6216 "!*.min.js"\uFF08\u53EF\u591A\u6B21\u6392\u9664\u9700\u81EA\u884C\u7EC4\u5408\u4E3A {a,b} \u5F62\u5F0F\uFF09'
5323
+ },
5324
+ ignore_case: { type: "boolean", description: "\u5FFD\u7565\u5927\u5C0F\u5199" },
5325
+ context_lines: {
5326
+ type: "integer",
5327
+ description: "\u6BCF\u6761\u547D\u4E2D\u9644\u5E26\u7684\u524D\u540E\u4E0A\u4E0B\u6587\u884C\u6570\uFF080-5\uFF0C\u9ED8\u8BA4 0\uFF09"
5328
+ },
5329
+ max_results: {
5330
+ type: "integer",
5331
+ description: `\u6700\u591A\u8FD4\u56DE\u6761\u6570\uFF08\u9ED8\u8BA4 ${DEFAULT_MAX_RESULTS}\uFF0C\u4E0A\u9650 ${MAX_RESULTS_CAP}\uFF09`
5332
+ }
4956
5333
  },
4957
5334
  required: ["pattern"]
4958
5335
  }
@@ -4962,8 +5339,8 @@ function registerSearchTool(registry, workspace) {
4962
5339
  }
4963
5340
 
4964
5341
  // src/tools/git.ts
4965
- var fs8 = __toESM(require("fs"), 1);
4966
- var path11 = __toESM(require("path"), 1);
5342
+ var fs9 = __toESM(require("fs"), 1);
5343
+ var path12 = __toESM(require("path"), 1);
4967
5344
  async function runGit(repoDir, args, timeout = 15e3) {
4968
5345
  const res = await execAsync("git", ["-c", "core.quotepath=false", ...args], {
4969
5346
  cwd: repoDir,
@@ -4980,9 +5357,9 @@ async function runGit(repoDir, args, timeout = 15e3) {
4980
5357
  async function resolveRepoRoot(workspace, pathArg) {
4981
5358
  let dir = workspace;
4982
5359
  if (pathArg) {
4983
- const abs = path11.resolve(workspace, pathArg);
5360
+ const abs = path12.resolve(workspace, pathArg);
4984
5361
  try {
4985
- dir = fs8.statSync(abs).isDirectory() ? abs : path11.dirname(abs);
5362
+ dir = fs9.statSync(abs).isDirectory() ? abs : path12.dirname(abs);
4986
5363
  } catch {
4987
5364
  dir = abs;
4988
5365
  }
@@ -4995,22 +5372,22 @@ function repoDirArg(workspace, raw) {
4995
5372
  if (typeof raw !== "string") return void 0;
4996
5373
  const v = raw.trim();
4997
5374
  if (!v) return void 0;
4998
- const base = path11.resolve(workspace);
4999
- const abs = path11.resolve(base, v);
5000
- const rel = path11.relative(base, abs);
5375
+ const base = path12.resolve(workspace);
5376
+ const abs = path12.resolve(base, v);
5377
+ const rel = path12.relative(base, abs);
5001
5378
  if (!isInsideRelative(rel)) return void 0;
5002
5379
  return abs;
5003
5380
  }
5004
5381
  function toPathspec(repoRoot, workspace, pathArg) {
5005
5382
  if (!pathArg) return null;
5006
- const abs = path11.resolve(workspace, pathArg);
5007
- let rel = path11.relative(repoRoot, abs);
5383
+ const abs = path12.resolve(workspace, pathArg);
5384
+ let rel = path12.relative(repoRoot, abs);
5008
5385
  if (rel === "") return ".";
5009
5386
  if (!isInsideRelative(rel)) {
5010
5387
  try {
5011
- const realRepo = fs8.realpathSync(repoRoot);
5012
- const realAbs = fs8.realpathSync(abs);
5013
- const rel2 = path11.relative(realRepo, realAbs);
5388
+ const realRepo = fs9.realpathSync(repoRoot);
5389
+ const realAbs = fs9.realpathSync(abs);
5390
+ const rel2 = path12.relative(realRepo, realAbs);
5014
5391
  if (rel2 === "") return ".";
5015
5392
  if (isInsideRelative(rel2)) return rel2;
5016
5393
  } catch {