@amical/aiagent-sdk 0.1.10 → 0.1.12

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.
@@ -140,9 +140,11 @@ return (
140
140
  },
141
141
  {
142
142
  name: "agent_read_file" /* FILE_READ */,
143
- description: "Read the contents of a specific file.",
143
+ description: "Read the contents of a specific file. The returned content includes line numbers (e.g. '10|const x = 1;') so you can reference exact lines when editing.",
144
144
  params: {
145
- path: "(required) The absolute or relative file path to read."
145
+ path: "(required) The absolute or relative file path to read.",
146
+ start_line: "(optional) The 1-based line number to start reading from (inclusive). Defaults to 1.",
147
+ end_line: "(optional) The 1-based line number to stop reading at (inclusive). Defaults to the last line."
146
148
  }
147
149
  },
148
150
  {
@@ -489,6 +491,44 @@ var toolParamNames = [
489
491
  "start_line",
490
492
  "end_line"
491
493
  ];
494
+
495
+ // src/shared/custom-tools.ts
496
+ function isCustomToolGroup(tool) {
497
+ return Array.isArray(tool.children) && tool.children.length > 0;
498
+ }
499
+ function collectCustomToolNames(tools) {
500
+ const names = [];
501
+ const visit = (list) => {
502
+ for (const tool of list) {
503
+ names.push(tool.name);
504
+ if (Array.isArray(tool.children) && tool.children.length > 0) {
505
+ visit(tool.children);
506
+ }
507
+ }
508
+ };
509
+ visit(tools);
510
+ return names;
511
+ }
512
+ function renderCustomToolPrompt(tool, cwd = "") {
513
+ const params = tool.params ? Object.entries(tool.params) : [];
514
+ const replaceCwd = (text) => String(text).replaceAll("${context.cwd}", cwd);
515
+ const paramLines = params.map(([name, description]) => `- ${name}: ${replaceCwd(description)}`).join("\n");
516
+ const usageLines = params.map(([name, description]) => `<${name}>${replaceCwd(description)}</${name}>`).join("\n");
517
+ return `### ${tool.name}
518
+ Description: ${tool.description}
519
+ Parameters:
520
+ ${paramLines}
521
+ - task_progress: (optional) A checklist showing task progress after this tool use is completed. The task_progress parameter must be included as a separate parameter inside of the parent tool call, it must be separate from other parameters such as content, arguments, etc. (See 'UPDATING TASK PROGRESS' section for more details)
522
+ Usage:
523
+ <${tool.name}>
524
+ ${usageLines}
525
+ <task_progress>Checklist here (optional)</task_progress>
526
+ </${tool.name}>
527
+ `;
528
+ }
529
+ function renderCustomToolsPrompt(tools, cwd = "") {
530
+ return tools.map((tool) => renderCustomToolPrompt(tool, cwd)).join("\n");
531
+ }
492
532
  var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
493
533
  "txt",
494
534
  "md",
@@ -547,7 +587,17 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
547
587
  "dockerfile",
548
588
  "makefile",
549
589
  "properties",
550
- "lock"
590
+ "lock",
591
+ "node-version",
592
+ "nvmrc",
593
+ "editorconfig",
594
+ "prettierrc",
595
+ "eslintrc",
596
+ "babelrc",
597
+ "stylelintrc",
598
+ "huskyrc",
599
+ "commitlintrc",
600
+ "lintstagedrc"
551
601
  ]);
552
602
  var AgentState = /* @__PURE__ */ ((AgentState2) => {
553
603
  AgentState2["IDLE"] = "idle";
@@ -572,9 +622,19 @@ var ClineAgent = class {
572
622
  skillDir;
573
623
  rulesDir;
574
624
  customTools;
625
+ /** 当前可用(已展开集合)的顶层工具列表,传给 PromptManager 渲染 */
626
+ activeCustomTools;
627
+ /** 所有已知工具名(含未激活 children),便于查找定义 */
628
+ customToolsByName;
575
629
  customToolNames;
576
630
  allowDefaultTools;
577
631
  customInstructions;
632
+ /** 已激活的工具集入口名 */
633
+ activatedGroups = /* @__PURE__ */ new Set();
634
+ /** activeCustomTools 变更版本号,用于 Prompt 缓存失效 */
635
+ activeToolsVersion = 0;
636
+ /** 缓存 sysPrompt 时记录的版本号 */
637
+ promptActiveToolsVersion = -1;
578
638
  sysPrompt;
579
639
  noUseTollSysPrompt;
580
640
  // 简单的事件回调系统,供宿主环境注册以监听 Agent 内部事件
@@ -594,33 +654,47 @@ var ClineAgent = class {
594
654
  this.allowDefaultTools.push("agent_attempt_completion" /* ATTEMPT */);
595
655
  }
596
656
  this.customInstructions = options.customInstructions;
657
+ this.customToolsByName = /* @__PURE__ */ new Map();
658
+ this.activeCustomTools = [...this.customTools];
659
+ this.customToolNames = new Set(collectCustomToolNames(this.customTools));
597
660
  if (this.customTools.length > 0) {
598
- const dynamicToolNames = /* @__PURE__ */ new Set();
599
661
  const customToolParams = /* @__PURE__ */ new Set();
600
- for (const customTool of this.customTools) {
601
- dynamicToolNames.add(customTool.name);
602
- if (customTool.params) {
603
- for (const key in customTool.params) {
604
- if (!Object.hasOwn(customTool.params, key)) continue;
605
- customToolParams.add(key);
662
+ const visit = (tools) => {
663
+ for (const customTool of tools) {
664
+ this.customToolsByName.set(customTool.name, customTool);
665
+ if (customTool.params) {
666
+ for (const key in customTool.params) {
667
+ if (!Object.hasOwn(customTool.params, key)) continue;
668
+ customToolParams.add(key);
669
+ }
670
+ }
671
+ if (Array.isArray(customTool.children) && customTool.children.length > 0) {
672
+ visit(customTool.children);
606
673
  }
607
674
  }
608
- }
675
+ };
676
+ visit(this.customTools);
609
677
  const defaultToolNames = Object.values(AgentDefaultTool);
610
- const overlappingTools = Array.from(dynamicToolNames).filter((name) => defaultToolNames.includes(name));
678
+ const overlappingTools = Array.from(this.customToolNames).filter((name) => defaultToolNames.includes(name));
611
679
  if (overlappingTools.length > 0) {
612
680
  throw new Error(`Custom tools and default tools have overlapping names: ${overlappingTools.join(", ")}`);
613
681
  }
614
- if (dynamicToolNames.size > 0) {
615
- setDynamicToolUseNames("customUser", Array.from(dynamicToolNames));
682
+ if (this.customToolNames.size > 0) {
683
+ setDynamicToolUseNames("customUser", Array.from(this.customToolNames));
616
684
  }
617
685
  if (customToolParams.size > 0) {
618
686
  this.allToolParamNames = Array.from(/* @__PURE__ */ new Set([...this.allToolParamNames, ...Array.from(customToolParams)]));
619
687
  }
620
688
  }
621
- this.customToolNames = new Set(this.customTools.map((t) => t.name));
622
689
  this.skillDir = this.runtime.path.join(this.runtime.env.cwd(), ".agents/skills");
623
690
  this.rulesDir = this.runtime.path.join(this.runtime.env.cwd(), ".agents/rules");
691
+ const originalOnCondense = this.memory.onCondense;
692
+ this.memory.onCondense = async () => {
693
+ this.api.clearConversationId();
694
+ if (originalOnCondense) {
695
+ await originalOnCondense();
696
+ }
697
+ };
624
698
  }
625
699
  setState(newState) {
626
700
  this.state = newState;
@@ -642,10 +716,11 @@ var ClineAgent = class {
642
716
  shell: this.runtime.env.shell(),
643
717
  skills: await this.getSkills(),
644
718
  agentRules: await this.getAgentRules(),
645
- customTools: this.customTools,
719
+ customTools: this.activeCustomTools,
646
720
  allowDefaultTools: this.allowDefaultTools,
647
721
  customInstructions: this.customInstructions
648
722
  };
723
+ this.promptActiveToolsVersion = this.activeToolsVersion;
649
724
  this.sysPrompt = await this.promptManager.getSystemPrompt(context);
650
725
  this.noUseTollSysPrompt = this.promptManager.getNoUseToolInstructions(context);
651
726
  return { sysPrompt: this.sysPrompt, noUseTollSysPrompt: this.noUseTollSysPrompt };
@@ -677,16 +752,6 @@ var ClineAgent = class {
677
752
  async getAgentRules() {
678
753
  const cwd = this.runtime.env.cwd();
679
754
  const rules = [];
680
- const agentsMdPath = this.runtime.path.join(cwd, "Agents.md");
681
- try {
682
- if (await this.runtime.fs.fileExists(agentsMdPath)) {
683
- const content = await this.runtime.fs.readFile(agentsMdPath);
684
- if (content && content.trim()) {
685
- rules.push(content.trim());
686
- }
687
- }
688
- } catch (e) {
689
- }
690
755
  try {
691
756
  if (await this.runtime.fs.fileExists(this.rulesDir)) {
692
757
  const files = await this.runtime.fs.readDir(this.rulesDir);
@@ -712,6 +777,28 @@ ${content.trim()}\`\`\``);
712
777
  }
713
778
  } catch (e) {
714
779
  }
780
+ try {
781
+ const getAgentsMdPath = async () => {
782
+ const agentsMdPathList = [
783
+ this.runtime.path.join(cwd, "AGENTS.md"),
784
+ this.runtime.path.join(cwd, "Agents.md"),
785
+ this.runtime.path.join(cwd, "agents.md")
786
+ ];
787
+ for (const item of agentsMdPathList) {
788
+ if (await this.runtime.fs.fileExists(item)) {
789
+ return item;
790
+ }
791
+ }
792
+ };
793
+ const agentsMdPath = await getAgentsMdPath();
794
+ if (agentsMdPath) {
795
+ const content = await this.runtime.fs.readFile(agentsMdPath);
796
+ if (content && content.trim()) {
797
+ rules.push(content.trim());
798
+ }
799
+ }
800
+ } catch (e) {
801
+ }
715
802
  return rules.join("\n---\n");
716
803
  }
717
804
  async getSkills() {
@@ -754,9 +841,9 @@ ${content.trim()}\`\`\``);
754
841
  * 核心任务循环
755
842
  */
756
843
  async loop() {
757
- const { sysPrompt, noUseTollSysPrompt } = await this.getPrompt(true);
758
- const { customTools, customToolNames } = this;
844
+ const { customToolsByName, customToolNames } = this;
759
845
  while (this.state === "thinking" /* THINKING */) {
846
+ const { sysPrompt, noUseTollSysPrompt } = await this.getCurrentPrompt();
760
847
  this.loopCount++;
761
848
  if (this.loopCount > this.maxLoopCount) {
762
849
  await this.memory.appendMessage({
@@ -801,7 +888,33 @@ ${content.trim()}\`\`\``);
801
888
  }
802
889
  }
803
890
  if (customToolNames.has(tool.name)) {
804
- const customTool = customTools.find((t) => t.name === tool.name);
891
+ const customTool = customToolsByName.get(tool.name);
892
+ if (!customTool) {
893
+ toolResults.push({ tool, result: `Error: Custom tool "${tool.name}" not found.` });
894
+ continue;
895
+ }
896
+ if (isCustomToolGroup(customTool)) {
897
+ let groupResult;
898
+ if (customTool.onCall) {
899
+ try {
900
+ groupResult = await customTool.onCall(tool.params);
901
+ } catch (error) {
902
+ toolResults.push({ tool, result: `Error: ${error.message}` });
903
+ continue;
904
+ }
905
+ }
906
+ const childrenPrompt = this.activateCustomToolGroup(customTool);
907
+ const finalPrompt = `# New Available Tools
908
+
909
+ The following are instructions for using the new available tools.
910
+
911
+ ${childrenPrompt}`;
912
+ const promptBlock = groupResult !== void 0 ? `${typeof groupResult === "string" ? groupResult : JSON.stringify(groupResult)}
913
+
914
+ ${finalPrompt}` : finalPrompt;
915
+ toolResults.push({ tool, result: promptBlock });
916
+ continue;
917
+ }
805
918
  if (customTool.onCall) {
806
919
  try {
807
920
  const res = await customTool.onCall(tool.params);
@@ -823,7 +936,8 @@ ${content.trim()}\`\`\``);
823
936
  if (path3) {
824
937
  try {
825
938
  const content = await this.runtime.fs.readFile(path3);
826
- toolResults.push({ tool, result: content });
939
+ const result = this.formatFileContentWithLineNumbers(content, tool.params.start_line, tool.params.end_line);
940
+ toolResults.push({ tool, result });
827
941
  } catch (error) {
828
942
  toolResults.push({ tool, result: `Error: ${error.message}` });
829
943
  }
@@ -836,6 +950,8 @@ ${content.trim()}\`\`\``);
836
950
  const result = await this.runtime.terminal.execCommand(cmd);
837
951
  toolResults.push({ tool, result: `STDOUT: ${result.stdout}
838
952
  STDERR: ${result.stderr}` });
953
+ } else {
954
+ toolResults.push({ tool, result: "Error: No command provided." });
839
955
  }
840
956
  } else if (tool.name === "agent_write_to_file" /* FILE_NEW */) {
841
957
  const path3 = tool.params.path;
@@ -847,12 +963,18 @@ STDERR: ${result.stderr}` });
847
963
  } catch (error) {
848
964
  toolResults.push({ tool, result: `Error: ${error.message}` });
849
965
  }
966
+ } else {
967
+ toolResults.push({ tool, result: "Error: Missing path or content." });
850
968
  }
851
969
  } else if (tool.name === "agent_list_files" /* LIST_FILES */) {
852
970
  const path3 = tool.params.path || this.runtime.env.cwd();
971
+ const recursive = tool.params.recursive === "true";
853
972
  try {
854
- const files = await this.runtime.fs.readDir(path3);
855
- toolResults.push({ tool, result: files.map((f) => f.name + (f.isDirectory() ? "/" : "")).join("\n") });
973
+ const files = await this.runtime.fs.readDir(path3, { recursive });
974
+ toolResults.push({
975
+ tool,
976
+ result: files.map((f) => `${f.isDirectory() ? "[DIR] " : " "}${f.name}${f.isDirectory() ? "/" : ""}`).join("\n")
977
+ });
856
978
  } catch (e) {
857
979
  toolResults.push({ tool, result: `Error: ${e.message}` });
858
980
  }
@@ -992,10 +1114,15 @@ STDERR: ${result.stderr}` });
992
1114
  try {
993
1115
  const res = await fetch(url);
994
1116
  const text = await res.text();
995
- toolResults.push({ tool, result: text.substring(0, 3e3) });
1117
+ const MAX_LEN = 8e3;
1118
+ const truncated = text.length > MAX_LEN ? `${text.substring(0, MAX_LEN)}
1119
+ ...[truncated ${text.length - MAX_LEN} chars]` : text;
1120
+ toolResults.push({ tool, result: truncated });
996
1121
  } catch (e) {
997
1122
  toolResults.push({ tool, result: `Error: ${e.message}` });
998
1123
  }
1124
+ } else {
1125
+ toolResults.push({ tool, result: "Error: No url provided." });
999
1126
  }
1000
1127
  } else if (tool.name === "agent_focus_chain" /* TODO */) {
1001
1128
  toolResults.push({ tool, result: `Focus chain updated successfully.` });
@@ -1062,6 +1189,65 @@ ${this.promptManager.getUsedToolResultInstructions()}`;
1062
1189
  }
1063
1190
  }
1064
1191
  }
1192
+ /**
1193
+ * 激活自定义工具集:将入口的 children 注册到活跃工具列表并返回渲染后的 prompt。
1194
+ */
1195
+ activateCustomToolGroup(group) {
1196
+ if (this.activatedGroups.has(group.name)) {
1197
+ return renderCustomToolsPrompt(group.children || [], this.runtime.env.cwd());
1198
+ }
1199
+ this.activatedGroups.add(group.name);
1200
+ this.activeCustomTools = [...this.activeCustomTools, ...group.children || []];
1201
+ this.activeToolsVersion++;
1202
+ return renderCustomToolsPrompt(group.children || [], this.runtime.env.cwd());
1203
+ }
1204
+ /**
1205
+ * 检查自上次获取 prompt 后是否有工具集被激活,若有则刷新缓存。
1206
+ */
1207
+ async getCurrentPrompt() {
1208
+ if (this.promptActiveToolsVersion !== this.activeToolsVersion) {
1209
+ return this.getPrompt(true);
1210
+ }
1211
+ return this.getPrompt();
1212
+ }
1213
+ /**
1214
+ * 将文件内容格式化为带行号文本(模拟编辑器行号显示),
1215
+ * 支持通过 start_line / end_line 截取读取范围。
1216
+ */
1217
+ formatFileContentWithLineNumbers(content, startLineParam, endLineParam) {
1218
+ const lines = content.split(/\r?\n/);
1219
+ const normalized = lines.length > 0 && lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines;
1220
+ const total = normalized.length;
1221
+ if (total === 0) {
1222
+ return "1|(empty file)";
1223
+ }
1224
+ const parseLine = (val, fallback) => {
1225
+ if (val === void 0 || val === null || val.trim() === "") {
1226
+ return fallback;
1227
+ }
1228
+ const num = Number.parseInt(val, 10);
1229
+ if (!Number.isFinite(num) || num <= 0) {
1230
+ return fallback;
1231
+ }
1232
+ return num;
1233
+ };
1234
+ const start = parseLine(startLineParam, 1);
1235
+ const end = parseLine(endLineParam, total);
1236
+ if (start > end) {
1237
+ return `Error: start_line (${start}) cannot be greater than end_line (${end}).`;
1238
+ }
1239
+ if (start > total) {
1240
+ return `Error: start_line (${start}) is out of range. The file has ${total} lines.`;
1241
+ }
1242
+ const from = Math.max(1, start);
1243
+ const to = Math.min(total, end);
1244
+ const width = String(total).length;
1245
+ const selected = normalized.slice(from - 1, to);
1246
+ const numbered = selected.map((line, idx) => `${String(from + idx).padStart(width)}|${line}`).join("\n");
1247
+ const header = from === 1 && to === total ? `1-${total}/${total}` : `${from}-${to}/${total}`;
1248
+ return `${header}
1249
+ ${numbered}`;
1250
+ }
1065
1251
  /**
1066
1252
  * 简单的 Diff 解析与替换辅助方法
1067
1253
  */
@@ -1311,6 +1497,9 @@ var NodeApiHandler = class {
1311
1497
  };
1312
1498
  }
1313
1499
  }
1500
+ clearConversationId() {
1501
+ this.conversationId = void 0;
1502
+ }
1314
1503
  };
1315
1504
 
1316
1505
  // src/prompt/NodePromptManager.ts
@@ -1423,10 +1612,11 @@ ${context.customInstructions}
1423
1612
  if (allowDefaultTools) {
1424
1613
  useDefaultTools = defaultTools.filter((tool) => allowDefaultTools.includes(tool.name));
1425
1614
  }
1615
+ const renderedCustomTools = renderCustomToolsPrompt(customTools, context.cwd);
1426
1616
  return `# Available Tools
1427
1617
  You can use the following tools by formatting your output as XML blocks. Only use ONE tool per message, and ALWAYS wait for the user's response with the execution result before proceeding.
1428
1618
 
1429
- ${[...useDefaultTools, ...customTools].map((tool) => {
1619
+ ${[...useDefaultTools].map((tool) => {
1430
1620
  return `### ${tool.name}
1431
1621
  Description: ${tool.description}
1432
1622
  Parameters:
@@ -1439,6 +1629,8 @@ ${Object.entries(tool.params).map(([name, description]) => `<${name}>${descripti
1439
1629
  </${tool.name}>
1440
1630
  `;
1441
1631
  }).join("\n")}
1632
+ ${renderedCustomTools ? `
1633
+ ${renderedCustomTools}` : ""}
1442
1634
 
1443
1635
  # Tool Use Examples
1444
1636
  ${useDefaultTools.filter((item) => Boolean(item.example)).map((item) => item.example).join("\n")}}
@@ -1612,14 +1804,125 @@ ${this?.getEnvironmentInfoPrompt?.(context)}`;
1612
1804
  4. Ensure the list accurately reflects the current state`;
1613
1805
  }
1614
1806
  };
1807
+
1808
+ // src/memory/condense.ts
1809
+ var TOOL_RESULT_CONTENT_REGEX = /<tool_call_result[^>]*>[\s\S]*?<\/tool_call_result>/;
1810
+ var EXPIRED_MARKER_TEXT = "[The result has expired and has been removed from the context]";
1811
+ var ATTEMPT_TOOL_NAME = "agent_attempt_completion" /* ATTEMPT */;
1812
+ function getContentString(content) {
1813
+ return typeof content === "string" ? content : "";
1814
+ }
1815
+ function isToolResultMessage(content) {
1816
+ if (typeof content !== "string") {
1817
+ return false;
1818
+ }
1819
+ return TOOL_RESULT_CONTENT_REGEX.test(content);
1820
+ }
1821
+ function isAttemptCallMessage(message) {
1822
+ if (message.role !== "assistant") {
1823
+ return false;
1824
+ }
1825
+ const content = getContentString(message.content);
1826
+ return new RegExp(`<${ATTEMPT_TOOL_NAME}[\\s>]`).test(content) || new RegExp(`<name>\\s*${ATTEMPT_TOOL_NAME}\\s*</name>`).test(content);
1827
+ }
1828
+ function markExpiredToolResult(content) {
1829
+ if (typeof content !== "string") {
1830
+ return content;
1831
+ }
1832
+ return content.replace(
1833
+ /(<tool_call_result[^>]*tool_name="([^"]*)"[^>]*>)[\s\S]*?(<\/tool_call_result>)/g,
1834
+ (_match, openTag, _toolName, closeTag) => {
1835
+ return `${openTag}
1836
+ ${EXPIRED_MARKER_TEXT}
1837
+ ${closeTag}`;
1838
+ }
1839
+ );
1840
+ }
1841
+ function splitConversations(messages) {
1842
+ const conversations = [];
1843
+ let current = [];
1844
+ for (const message of messages) {
1845
+ current.push(message);
1846
+ if (isAttemptCallMessage(message)) {
1847
+ conversations.push(current);
1848
+ current = [];
1849
+ }
1850
+ }
1851
+ if (current.length > 0) {
1852
+ conversations.push(current);
1853
+ }
1854
+ return conversations;
1855
+ }
1856
+ function isCompleteConversation(conversation) {
1857
+ if (conversation.length === 0) {
1858
+ return false;
1859
+ }
1860
+ return isAttemptCallMessage(conversation[conversation.length - 1]);
1861
+ }
1862
+ function compressCompleteConversation(conversation) {
1863
+ if (conversation.length <= 2) {
1864
+ return conversation;
1865
+ }
1866
+ const head = conversation[0];
1867
+ const tail = conversation[conversation.length - 1];
1868
+ const mergedMarker = {
1869
+ role: "user",
1870
+ content: "[History Condensed] The intermediate tool calls and tool call results of this conversation have been merged. Please refer to the `agent_attempt_completion` summary below for the final conclusion."
1871
+ };
1872
+ return [head, mergedMarker, tail];
1873
+ }
1874
+ function markExpiredFirstHalf(messages) {
1875
+ const half = Math.floor(messages.length / 2);
1876
+ return messages.map((message, index) => {
1877
+ if (index < half && isToolResultMessage(message.content)) {
1878
+ return { ...message, content: markExpiredToolResult(message.content) };
1879
+ }
1880
+ return message;
1881
+ });
1882
+ }
1883
+ function condenseMessages(messages, maxMessages) {
1884
+ if (messages.length <= maxMessages) {
1885
+ return messages;
1886
+ }
1887
+ const systemMessages = messages.filter((message) => message.role === "system");
1888
+ const restMessages = messages.filter((message) => message.role !== "system");
1889
+ const conversations = splitConversations(restMessages);
1890
+ const completeIndices = [];
1891
+ for (let i = 0; i < conversations.length; i++) {
1892
+ if (isCompleteConversation(conversations[i])) {
1893
+ completeIndices.push(i);
1894
+ }
1895
+ }
1896
+ const currentLength = () => systemMessages.length + conversations.reduce((acc, c) => acc + c.length, 0);
1897
+ if (completeIndices.length >= 2) {
1898
+ const lastCompleteIndex = completeIndices[completeIndices.length - 1];
1899
+ for (const idx of completeIndices) {
1900
+ if (idx === lastCompleteIndex) {
1901
+ break;
1902
+ }
1903
+ conversations[idx] = compressCompleteConversation(conversations[idx]);
1904
+ if (currentLength() <= maxMessages) {
1905
+ break;
1906
+ }
1907
+ }
1908
+ }
1909
+ let condensed = conversations.flat();
1910
+ condensed = markExpiredFirstHalf(condensed);
1911
+ if (systemMessages.length + condensed.length > maxMessages) {
1912
+ const nonSystemBudget = Math.max(0, maxMessages - systemMessages.length);
1913
+ condensed = condensed.slice(-nonSystemBudget);
1914
+ }
1915
+ return [...systemMessages, ...condensed];
1916
+ }
1615
1917
  var NodeMemoryManager = class {
1616
1918
  filePath;
1617
1919
  maxMessages;
1920
+ onCondense;
1618
1921
  /**
1619
1922
  * @param dbPath 存储历史记录的本地文件路径(例如:'./.cline_memory.json')
1620
- * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 150)
1923
+ * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 200)
1621
1924
  */
1622
- constructor(dbPath, maxMessages = 150) {
1925
+ constructor(dbPath, maxMessages = 200) {
1623
1926
  this.filePath = path.resolve(dbPath);
1624
1927
  this.maxMessages = maxMessages;
1625
1928
  }
@@ -1654,19 +1957,22 @@ var NodeMemoryManager = class {
1654
1957
  await this.writeMessages(messages);
1655
1958
  await this.condenseHistory();
1656
1959
  }
1960
+ /**
1961
+ * 压缩历史:
1962
+ * - system 消息保持不变
1963
+ * - 以 agent_attempt_completion 为边界切分完整对话,仅合并“后面还有其他完整对话”的旧完整对话
1964
+ * - 其余需要压缩的场景下,对前半段工具调用结果标记为“结果已过期”
1965
+ * - 同时触发 onCondense 回调(宿主可据此清空 dify conversationId 新开窗口)
1966
+ */
1657
1967
  async condenseHistory() {
1658
1968
  const messages = await this.readMessages();
1659
- if (messages.length > this.maxMessages) {
1660
- const firstMessage = messages[0];
1661
- const isFirstSystem = firstMessage && firstMessage.role === "system";
1662
- const sliceCount = messages.length - this.maxMessages;
1663
- let condensed;
1664
- if (isFirstSystem) {
1665
- condensed = [firstMessage, ...messages.slice(sliceCount + 1)];
1666
- } else {
1667
- condensed = messages.slice(sliceCount);
1668
- }
1669
- await this.writeMessages(condensed);
1969
+ const condensed = condenseMessages(messages, this.maxMessages);
1970
+ if (condensed === messages) {
1971
+ return;
1972
+ }
1973
+ await this.writeMessages(condensed);
1974
+ if (this.onCondense) {
1975
+ await this.onCondense();
1670
1976
  }
1671
1977
  }
1672
1978
  async clear() {
@@ -1875,6 +2181,6 @@ function createNodeAgent(options) {
1875
2181
  return agent;
1876
2182
  }
1877
2183
 
1878
- export { AgentDefaultTool, AgentState, ClineAgent, DefaultNodeRuntime, NodeApiHandler, NodeMcpManager, NodeMemoryManager, NodePromptManager, READ_ONLY_TOOLS, TOOL_RESULT_BLOCK, VERSION, createNodeAgent, defaultTools, getToolUseNames, parseAssistantMessageV2, setDynamicToolUseNames, toolParamNames, toolUseNames };
2184
+ export { AgentDefaultTool, AgentState, ClineAgent, DefaultNodeRuntime, NodeApiHandler, NodeMcpManager, NodeMemoryManager, NodePromptManager, READ_ONLY_TOOLS, TOOL_RESULT_BLOCK, VERSION, collectCustomToolNames, createNodeAgent, defaultTools, getToolUseNames, isCustomToolGroup, parseAssistantMessageV2, renderCustomToolPrompt, renderCustomToolsPrompt, setDynamicToolUseNames, toolParamNames, toolUseNames };
1879
2185
  //# sourceMappingURL=nodeRuntime.mjs.map
1880
2186
  //# sourceMappingURL=nodeRuntime.mjs.map