@ww_nero/mini-cli 1.0.102 → 1.0.103

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/chat.js +143 -69
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ww_nero/mini-cli",
3
- "version": "1.0.102",
3
+ "version": "1.0.103",
4
4
  "description": "极简的 AI 命令行助手",
5
5
  "bin": {
6
6
  "mini": "bin/mini.js"
package/src/chat.js CHANGED
@@ -760,6 +760,145 @@ const startChatSession = async ({
760
760
  loadingPromptVisible = false;
761
761
  };
762
762
 
763
+ const displayToolExecutionResult = (functionName, toolResult, rawToolContent) => {
764
+ if (functionName === 'write') {
765
+ const writeOutput = formatWriteOutput(toolResult);
766
+ if (writeOutput.isError) {
767
+ console.log(chalk.red(formatTreeResult(writeOutput.message)));
768
+ } else {
769
+ console.log(chalk.gray(formatTreeResult(`已写入,共${writeOutput.lineCount}行`)));
770
+ }
771
+ return;
772
+ }
773
+
774
+ if (functionName === 'edit') {
775
+ const editOutput = formatEditOutput(toolResult);
776
+ if (editOutput.isError) {
777
+ console.log(chalk.red(indentToolResult(editOutput.message)));
778
+ } else if (editOutput.diff) {
779
+ console.log(editOutput.diff);
780
+ }
781
+ return;
782
+ }
783
+
784
+ if (functionName === 'read') {
785
+ const isError = rawToolContent.startsWith('[Error]');
786
+ if (isError) {
787
+ console.log(chalk.red(formatTreeResult(rawToolContent.replace('[Error] ', ''))));
788
+ } else {
789
+ const normalized = rawToolContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
790
+ const lineCount = countLines(normalized);
791
+ console.log(chalk.gray(formatTreeResult(`已读取,共${lineCount}行`)));
792
+ }
793
+ return;
794
+ }
795
+
796
+ const isError = rawToolContent.startsWith('[Error]');
797
+ const displayResult = formatToolResultForDisplay(rawToolContent, 100);
798
+ if (displayResult) {
799
+ console.log((isError ? chalk.red : chalk.gray)(formatTreeResult(displayResult.replace('[Error] ', ''))));
800
+ }
801
+ };
802
+
803
+ const executeToolCallAndAppendResult = async (toolCall, { ensureNotAborted } = {}) => {
804
+ if (typeof ensureNotAborted === 'function') {
805
+ ensureNotAborted();
806
+ }
807
+
808
+ const functionName = toolCall?.function?.name || 'unknown_tool';
809
+ const rawArgs = toolCall?.function?.arguments || '';
810
+ const parsedArgs = safeJsonParse(rawArgs);
811
+ const handler = toolHandlers[functionName];
812
+ const displayArgs = parsedArgs && typeof parsedArgs === 'object'
813
+ ? parsedArgs
814
+ : { raw: typeof rawArgs === 'string' ? rawArgs : '' };
815
+
816
+ renderToolCall({ functionName, args: displayArgs }, mcpToolNames);
817
+
818
+ let toolResult;
819
+ if (!parsedArgs || typeof parsedArgs !== 'object') {
820
+ toolResult = '工具调用失败:参数解析错误';
821
+ } else if (!handler) {
822
+ toolResult = `工具 ${functionName} 未实现`;
823
+ } else {
824
+ showLoadingPrompt();
825
+ try {
826
+ toolResult = await handler(parsedArgs);
827
+ } catch (toolError) {
828
+ toolResult = `[Error] 工具执行异常: ${toolError.message}`;
829
+ } finally {
830
+ clearLoadingPrompt();
831
+ }
832
+ }
833
+
834
+ const rawToolContent = stringifyToolResult(toolResult);
835
+ const toolContent = enforceToolOutputLimit(rawToolContent, toolOutputTokenLimit);
836
+
837
+ displayToolExecutionResult(functionName, toolResult, rawToolContent);
838
+ process.stdout.write('\n');
839
+
840
+ messages.push({
841
+ role: 'tool',
842
+ tool_call_id: toolCall.id,
843
+ content: toolContent
844
+ });
845
+ };
846
+
847
+ const getPendingToolCallsAtTail = () => {
848
+ let assistantIndex = -1;
849
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
850
+ const message = messages[index];
851
+ if (!message || typeof message !== 'object') {
852
+ continue;
853
+ }
854
+ if (message.role === 'tool') {
855
+ continue;
856
+ }
857
+ assistantIndex = index;
858
+ break;
859
+ }
860
+
861
+ if (assistantIndex < 0) {
862
+ return [];
863
+ }
864
+
865
+ const assistantMessage = messages[assistantIndex];
866
+ const toolCalls = Array.isArray(assistantMessage?.tool_calls) ? assistantMessage.tool_calls.filter(Boolean) : [];
867
+ if (assistantMessage?.role !== 'assistant' || toolCalls.length === 0) {
868
+ return [];
869
+ }
870
+
871
+ const completedToolCallIds = new Set();
872
+ for (let index = assistantIndex + 1; index < messages.length; index += 1) {
873
+ const message = messages[index];
874
+ if (!message || typeof message !== 'object') {
875
+ continue;
876
+ }
877
+ if (message.role !== 'tool' || !message.tool_call_id) {
878
+ return [];
879
+ }
880
+ completedToolCallIds.add(message.tool_call_id);
881
+ }
882
+
883
+ return toolCalls.filter((toolCall) => !completedToolCallIds.has(toolCall.id));
884
+ };
885
+
886
+ const resolvePendingToolCallsIfNeeded = async () => {
887
+ const pendingToolCalls = getPendingToolCallsAtTail();
888
+ if (pendingToolCalls.length === 0) {
889
+ return;
890
+ }
891
+
892
+ console.log(chalk.yellow(`检测到上次中断后遗留的 ${pendingToolCalls.length} 个工具调用,正在继续执行...`));
893
+ process.stdout.write('\n');
894
+
895
+ for (const toolCall of pendingToolCalls) {
896
+ await executeToolCallAndAppendResult(toolCall);
897
+ }
898
+
899
+ persistHistorySafely();
900
+ };
901
+
763
902
  let inFlightController = null;
764
903
  let closing = false;
765
904
  let cancelNoticeShown = false;
@@ -880,6 +1019,8 @@ const startChatSession = async ({
880
1019
  return !slashCommandResult.shouldExit;
881
1020
  }
882
1021
 
1022
+ await resolvePendingToolCallsIfNeeded();
1023
+
883
1024
  const targetEndpoint = modelManager.getActiveEndpoint();
884
1025
 
885
1026
  const userMessage = { role: 'user', content: text };
@@ -1009,76 +1150,9 @@ const startChatSession = async ({
1009
1150
  messages.push(assistantMessage);
1010
1151
 
1011
1152
  for (const toolCall of toolCalls) {
1012
- ensureNotAborted();
1013
- const functionName = toolCall?.function?.name || 'unknown_tool';
1014
- const rawArgs = toolCall?.function?.arguments || '';
1015
- const parsedArgs = safeJsonParse(rawArgs);
1016
- const handler = toolHandlers[functionName];
1017
- const displayArgs = parsedArgs && typeof parsedArgs === 'object'
1018
- ? parsedArgs
1019
- : { raw: typeof rawArgs === 'string' ? rawArgs : '' };
1020
-
1021
1153
  ensureNewline();
1022
- renderToolCall({ functionName, args: displayArgs }, mcpToolNames);
1023
-
1024
- let toolResult;
1025
-
1026
- if (!parsedArgs || typeof parsedArgs !== 'object') {
1027
- toolResult = '工具调用失败:参数解析错误';
1028
- } else if (!handler) {
1029
- toolResult = `工具 ${functionName} 未实现`;
1030
- } else {
1031
- showLoadingPrompt();
1032
- try {
1033
- toolResult = await handler(parsedArgs);
1034
- } catch (toolError) {
1035
- toolResult = `[Error] 工具执行异常: ${toolError.message}`;
1036
- } finally {
1037
- clearLoadingPrompt();
1038
- }
1039
- }
1040
-
1041
- const rawToolContent = stringifyToolResult(toolResult);
1042
- const toolContent = enforceToolOutputLimit(rawToolContent, toolOutputTokenLimit);
1043
-
1044
- // Display tool output
1045
- if (functionName === 'write') {
1046
- const writeOutput = formatWriteOutput(toolResult);
1047
- if (writeOutput.isError) {
1048
- console.log(chalk.red(formatTreeResult(writeOutput.message)));
1049
- } else {
1050
- console.log(chalk.gray(formatTreeResult(`已写入,共${writeOutput.lineCount}行`)));
1051
- }
1052
- } else if (functionName === 'edit') {
1053
- const editOutput = formatEditOutput(toolResult);
1054
- if (editOutput.isError) {
1055
- console.log(chalk.red(indentToolResult(editOutput.message)));
1056
- } else if (editOutput.diff) {
1057
- console.log(editOutput.diff);
1058
- }
1059
- } else if (functionName === 'read') {
1060
- const isError = rawToolContent.startsWith('[Error]');
1061
- if (isError) {
1062
- console.log(chalk.red(formatTreeResult(rawToolContent.replace('[Error] ', ''))));
1063
- } else {
1064
- const normalized = rawToolContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
1065
- const lineCount = countLines(normalized);
1066
- console.log(chalk.gray(formatTreeResult(`已读取,共${lineCount}行`)));
1067
- }
1068
- } else {
1069
- const isError = rawToolContent.startsWith('[Error]');
1070
- const displayResult = formatToolResultForDisplay(rawToolContent, 100);
1071
- if (displayResult) {
1072
- console.log((isError ? chalk.red : chalk.gray)(formatTreeResult(displayResult.replace('[Error] ', ''))));
1073
- }
1074
- }
1075
-
1076
- process.stdout.write('\n');
1077
-
1078
- messages.push({
1079
- role: 'tool',
1080
- tool_call_id: toolCall.id,
1081
- content: toolContent
1154
+ await executeToolCallAndAppendResult(toolCall, {
1155
+ ensureNotAborted
1082
1156
  });
1083
1157
  }
1084
1158
  persistHistorySafely();