jinzd-ai-cli 0.4.59 → 0.4.61

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.
@@ -6,8 +6,9 @@ import {
6
6
  ProviderError,
7
7
  ProviderNotFoundError,
8
8
  RateLimitError,
9
- schemaToJsonSchema
10
- } from "./chunk-7RX7675B.js";
9
+ schemaToJsonSchema,
10
+ truncateForPersist
11
+ } from "./chunk-2ZCD5F4X.js";
11
12
  import {
12
13
  APP_NAME,
13
14
  CONFIG_DIR_NAME,
@@ -20,7 +21,7 @@ import {
20
21
  MCP_TOOL_PREFIX,
21
22
  PLUGINS_DIR_NAME,
22
23
  VERSION
23
- } from "./chunk-YJCJBUOG.js";
24
+ } from "./chunk-GA74LZ62.js";
24
25
 
25
26
  // src/config/config-manager.ts
26
27
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -309,9 +310,12 @@ var BaseProvider = class {
309
310
  /**
310
311
  * 将 Message[] 转换为 OpenAI API 格式的消息数组。
311
312
  * content 为 string 时直接传递;为 MessageContentPart[] 时保留数组格式(vision 请求)。
313
+ *
314
+ * 自动跳过 role='tool' 和带 toolCalls 的 assistant 消息——
315
+ * 这些是 v0.4.60+ 持久化的工具历史,由 _extraMessages 机制单独注入。
312
316
  */
313
317
  normalizeMessages(messages) {
314
- return messages.map((m) => ({ role: m.role, content: m.content }));
318
+ return messages.filter((m) => m.role !== "tool" && !m.toolCalls).map((m) => ({ role: m.role, content: m.content }));
315
319
  }
316
320
  };
317
321
 
@@ -475,7 +479,7 @@ var ClaudeProvider = class extends BaseProvider {
475
479
  }
476
480
  async chat(request) {
477
481
  try {
478
- const messages = request.messages.filter((m) => m.role !== "system").map((m) => ({
482
+ const messages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
479
483
  role: m.role,
480
484
  content: this.contentToClaudeParts(m.content)
481
485
  }));
@@ -500,7 +504,7 @@ var ClaudeProvider = class extends BaseProvider {
500
504
  }
501
505
  async *chatStream(request) {
502
506
  try {
503
- const messages = request.messages.filter((m) => m.role !== "system").map((m) => ({
507
+ const messages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
504
508
  role: m.role,
505
509
  content: this.contentToClaudeParts(m.content)
506
510
  }));
@@ -557,7 +561,7 @@ var ClaudeProvider = class extends BaseProvider {
557
561
  }
558
562
  }))
559
563
  );
560
- const baseMessages = request.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: this.contentToClaudeParts(m.content) }));
564
+ const baseMessages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({ role: m.role, content: this.contentToClaudeParts(m.content) }));
561
565
  const extraMessages = request._extraMessages ?? [];
562
566
  const allMessages = [...baseMessages, ...extraMessages];
563
567
  const { thinking, temperature } = this.buildThinkingParams(request);
@@ -869,7 +873,7 @@ var GeminiProvider = class extends BaseProvider {
869
873
  return parts.length > 0 ? parts : [{ text: "" }];
870
874
  }
871
875
  toGeminiHistory(messages) {
872
- return messages.filter((m) => m.role !== "system").map((m) => ({
876
+ return messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
873
877
  role: m.role === "assistant" ? "model" : "user",
874
878
  parts: this.contentToGeminiParts(m.content)
875
879
  }));
@@ -2521,10 +2525,19 @@ var Session = class _Session {
2521
2525
  messageIndex: c.messageIndex,
2522
2526
  timestamp: c.timestamp.toISOString()
2523
2527
  })),
2524
- messages: this.messages.map((m) => ({
2525
- ...m,
2526
- timestamp: m.timestamp.toISOString()
2527
- }))
2528
+ messages: this.messages.map((m) => {
2529
+ const out = {
2530
+ role: m.role,
2531
+ content: m.content,
2532
+ timestamp: m.timestamp.toISOString()
2533
+ };
2534
+ if (m.toolCalls) out.toolCalls = m.toolCalls;
2535
+ if (m.reasoningContent !== void 0) out.reasoningContent = m.reasoningContent;
2536
+ if (m.toolCallId) out.toolCallId = m.toolCallId;
2537
+ if (m.toolName) out.toolName = m.toolName;
2538
+ if (m.isError !== void 0) out.isError = m.isError;
2539
+ return out;
2540
+ })
2528
2541
  };
2529
2542
  }
2530
2543
  /**
@@ -2594,11 +2607,17 @@ var Session = class _Session {
2594
2607
  }
2595
2608
  session.messages = d.messages.map((m) => {
2596
2609
  const ts = new Date(m.timestamp);
2597
- return {
2610
+ const msg = {
2598
2611
  role: m.role ?? "user",
2599
- content: m.content,
2612
+ content: Array.isArray(m.content) ? m.content : String(m.content ?? ""),
2600
2613
  timestamp: isNaN(ts.getTime()) ? /* @__PURE__ */ new Date() : ts
2601
2614
  };
2615
+ if (Array.isArray(m.toolCalls)) msg.toolCalls = m.toolCalls;
2616
+ if (typeof m.reasoningContent === "string") msg.reasoningContent = m.reasoningContent;
2617
+ if (typeof m.toolCallId === "string") msg.toolCallId = m.toolCallId;
2618
+ if (typeof m.toolName === "string") msg.toolName = m.toolName;
2619
+ if (typeof m.isError === "boolean") msg.isError = m.isError;
2620
+ return msg;
2602
2621
  });
2603
2622
  return session;
2604
2623
  }
@@ -3667,6 +3686,89 @@ function formatCost(amount) {
3667
3686
  return `$${amount.toFixed(2)}`;
3668
3687
  }
3669
3688
 
3689
+ // src/session/tool-history.ts
3690
+ function persistToolRound(session, toolCalls, toolResults, opts) {
3691
+ session.addMessage({
3692
+ role: "assistant",
3693
+ content: opts?.assistantContent ?? "",
3694
+ toolCalls,
3695
+ reasoningContent: opts?.reasoningContent,
3696
+ timestamp: /* @__PURE__ */ new Date()
3697
+ });
3698
+ for (let i = 0; i < toolCalls.length; i++) {
3699
+ const tc = toolCalls[i];
3700
+ const tr = toolResults[i];
3701
+ if (!tr) continue;
3702
+ session.addMessage({
3703
+ role: "tool",
3704
+ content: truncateForPersist(tr.content),
3705
+ toolCallId: tr.callId,
3706
+ toolName: tc.name,
3707
+ isError: tr.isError,
3708
+ timestamp: /* @__PURE__ */ new Date()
3709
+ });
3710
+ }
3711
+ }
3712
+ function isToolMessage(m) {
3713
+ return m.role === "tool" || !!(m.toolCalls && m.toolCalls.length > 0);
3714
+ }
3715
+ function extractToolHistory(messages) {
3716
+ const baseMessages = [];
3717
+ const toolHistory = [];
3718
+ for (const m of messages) {
3719
+ if (isToolMessage(m)) {
3720
+ toolHistory.push(m);
3721
+ } else {
3722
+ baseMessages.push(m);
3723
+ }
3724
+ }
3725
+ return { baseMessages, toolHistory };
3726
+ }
3727
+ function rebuildExtraMessages(provider, toolHistory) {
3728
+ if (toolHistory.length === 0) return [];
3729
+ const result = [];
3730
+ let i = 0;
3731
+ while (i < toolHistory.length) {
3732
+ const msg = toolHistory[i];
3733
+ if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
3734
+ const toolCalls = msg.toolCalls;
3735
+ const toolResults = [];
3736
+ let j = i + 1;
3737
+ while (j < toolHistory.length && toolHistory[j].role === "tool") {
3738
+ const tm = toolHistory[j];
3739
+ toolResults.push({
3740
+ callId: tm.toolCallId ?? "",
3741
+ content: typeof tm.content === "string" ? tm.content : getContentText(tm.content),
3742
+ isError: tm.isError ?? false
3743
+ });
3744
+ j++;
3745
+ }
3746
+ result.push(
3747
+ ...provider.buildToolResultMessages(toolCalls, toolResults, msg.reasoningContent)
3748
+ );
3749
+ i = j;
3750
+ } else {
3751
+ result.push({
3752
+ role: msg.role,
3753
+ content: typeof msg.content === "string" ? msg.content : getContentText(msg.content)
3754
+ });
3755
+ i++;
3756
+ }
3757
+ }
3758
+ return result;
3759
+ }
3760
+
3761
+ // src/core/token-estimator.ts
3762
+ var CJK_REGEX = /[\u2E80-\u9FFF\uA000-\uA4FF\uAC00-\uD7FF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF]/g;
3763
+ function estimateTokens(text) {
3764
+ if (!text) return 0;
3765
+ const cjkMatches = text.match(CJK_REGEX);
3766
+ const cjkCount = cjkMatches ? cjkMatches.length : 0;
3767
+ const nonCjkCount = text.length - cjkCount;
3768
+ const tokens = cjkCount * 1.5 + nonCjkCount * 0.25;
3769
+ return Math.ceil(tokens) + 4;
3770
+ }
3771
+
3670
3772
  // src/repl/dev-state.ts
3671
3773
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, mkdirSync as mkdirSync4 } from "fs";
3672
3774
  import { join as join5 } from "path";
@@ -3777,6 +3879,10 @@ export {
3777
3879
  computeCost,
3778
3880
  formatCost,
3779
3881
  parseSimpleYaml,
3882
+ persistToolRound,
3883
+ extractToolHistory,
3884
+ rebuildExtraMessages,
3885
+ estimateTokens,
3780
3886
  SNAPSHOT_PROMPT,
3781
3887
  sessionHasMeaningfulContent,
3782
3888
  saveDevState,
@@ -10,7 +10,7 @@ import {
10
10
  SUBAGENT_DEFAULT_MAX_ROUNDS,
11
11
  SUBAGENT_MAX_ROUNDS_LIMIT,
12
12
  runTestsTool
13
- } from "./chunk-YJCJBUOG.js";
13
+ } from "./chunk-GA74LZ62.js";
14
14
 
15
15
  // src/tools/builtin/bash.ts
16
16
  import { execSync } from "child_process";
@@ -1098,6 +1098,20 @@ function snapToLineBoundary(content, target, direction) {
1098
1098
  return target;
1099
1099
  }
1100
1100
  }
1101
+ var PERSIST_MAX_CHARS = 8192;
1102
+ function truncateForPersist(content, maxChars = PERSIST_MAX_CHARS) {
1103
+ if (content.length <= maxChars) return content;
1104
+ const headSize = Math.floor(maxChars * 0.7);
1105
+ const tailSize = Math.floor(maxChars * 0.2);
1106
+ const head = content.slice(0, headSize);
1107
+ const tail = content.slice(-tailSize);
1108
+ const omitted = content.length - headSize - tailSize;
1109
+ return `${head}
1110
+
1111
+ [... ${omitted} chars omitted for storage ...]
1112
+
1113
+ ${tail}`;
1114
+ }
1101
1115
  function truncateOutput(content, toolName, maxChars) {
1102
1116
  const limit = maxChars ?? activeMaxChars;
1103
1117
  if (content.length <= limit) return content;
@@ -4208,6 +4222,7 @@ export {
4208
4222
  checkPermission,
4209
4223
  setMaxOutputCap,
4210
4224
  setContextWindow,
4225
+ truncateForPersist,
4211
4226
  truncateOutput,
4212
4227
  ToolExecutor,
4213
4228
  lastResponseStore,
@@ -8,7 +8,7 @@ import { platform } from "os";
8
8
  import chalk from "chalk";
9
9
 
10
10
  // src/core/constants.ts
11
- var VERSION = "0.4.59";
11
+ var VERSION = "0.4.61";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.59";
9
+ var VERSION = "0.4.61";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -385,7 +385,7 @@ ${content}`);
385
385
  }
386
386
  }
387
387
  async function runTaskMode(config, providers, configManager, topic) {
388
- const { TaskOrchestrator } = await import("./task-orchestrator-I5YCZ72U.js");
388
+ const { TaskOrchestrator } = await import("./task-orchestrator-W66JWWKF.js");
389
389
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
390
390
  let interrupted = false;
391
391
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -13,6 +13,8 @@ import {
13
13
  clearDevState,
14
14
  computeCost,
15
15
  detectsHallucinatedFileOp,
16
+ estimateTokens,
17
+ extractToolHistory,
16
18
  extractWrittenFilePaths,
17
19
  findPhantomClaims,
18
20
  formatCost,
@@ -24,10 +26,12 @@ import {
24
26
  hadPreviousWriteToolCalls,
25
27
  loadDevState,
26
28
  parseSimpleYaml,
29
+ persistToolRound,
30
+ rebuildExtraMessages,
27
31
  saveDevState,
28
32
  sessionHasMeaningfulContent,
29
33
  setupProxy
30
- } from "./chunk-NXSYL5OP.js";
34
+ } from "./chunk-2WGVM2J2.js";
31
35
  import {
32
36
  ToolExecutor,
33
37
  ToolRegistry,
@@ -41,7 +45,7 @@ import {
41
45
  spawnAgentContext,
42
46
  theme,
43
47
  undoStack
44
- } from "./chunk-7RX7675B.js";
48
+ } from "./chunk-2ZCD5F4X.js";
45
49
  import {
46
50
  fileCheckpoints
47
51
  } from "./chunk-4BKXL7SM.js";
@@ -66,7 +70,7 @@ import {
66
70
  SKILLS_DIR_NAME,
67
71
  VERSION,
68
72
  buildUserIdentityPrompt
69
- } from "./chunk-YJCJBUOG.js";
73
+ } from "./chunk-GA74LZ62.js";
70
74
 
71
75
  // src/index.ts
72
76
  import { program } from "commander";
@@ -2161,7 +2165,7 @@ ${hint}` : "")
2161
2165
  usage: "/test [command|filter]",
2162
2166
  async execute(args, ctx) {
2163
2167
  try {
2164
- const { executeTests } = await import("./run-tests-IW6GHAVV.js");
2168
+ const { executeTests } = await import("./run-tests-CT4PRGGP.js");
2165
2169
  const argStr = args.join(" ").trim();
2166
2170
  let testArgs = {};
2167
2171
  if (argStr) {
@@ -4396,7 +4400,7 @@ Session '${this.resumeSessionId}' not found.
4396
4400
  * 混合 CJK / ASCII 文本平均约 2.5 字符 = 1 token(与 renderer.ts 中的估算公式一致)。
4397
4401
  */
4398
4402
  estimateTokens(text) {
4399
- return Math.ceil(text.length / 2.5);
4403
+ return estimateTokens(text);
4400
4404
  }
4401
4405
  /**
4402
4406
  * 估算当前对话的总 token 消耗(system prompt + 所有 session messages)。
@@ -4415,6 +4419,11 @@ Session '${this.resumeSessionId}' not found.
4415
4419
  if (part.type === "text" && part.text) total += this.estimateTokens(part.text);
4416
4420
  }
4417
4421
  }
4422
+ if (msg.toolCalls) {
4423
+ for (const tc of msg.toolCalls) {
4424
+ total += this.estimateTokens(JSON.stringify(tc.arguments));
4425
+ }
4426
+ }
4418
4427
  }
4419
4428
  }
4420
4429
  return total;
@@ -4869,8 +4878,9 @@ Session '${this.resumeSessionId}' not found.
4869
4878
  if (this.blockedTools) {
4870
4879
  toolDefs = toolDefs.filter((t) => !this.blockedTools.has(t.name));
4871
4880
  }
4872
- const apiMessages = [...messages];
4873
- const extraMessages = [];
4881
+ const { baseMessages: cleanMessages, toolHistory } = extractToolHistory(messages);
4882
+ const apiMessages = [...cleanMessages];
4883
+ const extraMessages = toolHistory.length > 0 ? rebuildExtraMessages(provider, toolHistory) : [];
4874
4884
  const maxToolRounds = this.maxToolRoundsOverride ?? this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
4875
4885
  const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
4876
4886
  const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : DEFAULT_AUTO_PAUSE_INTERVAL;
@@ -5260,6 +5270,11 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
5260
5270
  const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
5261
5271
  const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
5262
5272
  extraMessages.push(...newMsgs);
5273
+ const streamedContent = "content" in result ? result.content : void 0;
5274
+ persistToolRound(session, result.toolCalls, toolResults, {
5275
+ assistantContent: streamedContent,
5276
+ reasoningContent
5277
+ });
5263
5278
  const thisRoundHadWrite = result.toolCalls.some(
5264
5279
  (tc) => tc.name === "write_file" || tc.name === "edit_file"
5265
5280
  );
@@ -5695,7 +5710,7 @@ program.command("web").description("Start Web UI server with browser-based chat
5695
5710
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
5696
5711
  process.exit(1);
5697
5712
  }
5698
- const { startWebServer } = await import("./server-J7PNU32E.js");
5713
+ const { startWebServer } = await import("./server-YJWIPDF2.js");
5699
5714
  await startWebServer({ port, host: options.host });
5700
5715
  });
5701
5716
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -5928,7 +5943,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5928
5943
  }),
5929
5944
  config.get("customProviders")
5930
5945
  );
5931
- const { startHub } = await import("./hub-3BY5W4VE.js");
5946
+ const { startHub } = await import("./hub-WA2DZCSQ.js");
5932
5947
  await startHub(
5933
5948
  {
5934
5949
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-F5WLEWN2.js";
4
+ } from "./chunk-QUD2AVHH.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-YJCJBUOG.js";
5
+ } from "./chunk-GA74LZ62.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
@@ -9,6 +9,8 @@ import {
9
9
  TOOL_CALL_REMINDER,
10
10
  computeCost,
11
11
  detectsHallucinatedFileOp,
12
+ estimateTokens,
13
+ extractToolHistory,
12
14
  formatCost,
13
15
  formatGitContextForPrompt,
14
16
  getContentText,
@@ -16,8 +18,10 @@ import {
16
18
  getGitRoot,
17
19
  hadPreviousWriteToolCalls,
18
20
  loadDevState,
21
+ persistToolRound,
22
+ rebuildExtraMessages,
19
23
  setupProxy
20
- } from "./chunk-NXSYL5OP.js";
24
+ } from "./chunk-2WGVM2J2.js";
21
25
  import {
22
26
  AuthManager
23
27
  } from "./chunk-BYNY5JPB.js";
@@ -36,7 +40,7 @@ import {
36
40
  spawnAgentContext,
37
41
  truncateOutput,
38
42
  undoStack
39
- } from "./chunk-7RX7675B.js";
43
+ } from "./chunk-2ZCD5F4X.js";
40
44
  import "./chunk-4BKXL7SM.js";
41
45
  import {
42
46
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -56,7 +60,7 @@ import {
56
60
  SKILLS_DIR_NAME,
57
61
  VERSION,
58
62
  buildUserIdentityPrompt
59
- } from "./chunk-YJCJBUOG.js";
63
+ } from "./chunk-GA74LZ62.js";
60
64
 
61
65
  // src/web/server.ts
62
66
  import express from "express";
@@ -654,7 +658,7 @@ var SessionHandler = class _SessionHandler {
654
658
  }
655
659
  /** 粗略估算文本 token 数(2.5 chars/token)*/
656
660
  estTokens(text) {
657
- return Math.ceil(text.length / 2.5);
661
+ return estimateTokens(text);
658
662
  }
659
663
  /**
660
664
  * 估算当前 agentic 请求总 token 数(session messages + extraMessages + system prompt)。
@@ -675,6 +679,11 @@ var SessionHandler = class _SessionHandler {
675
679
  }
676
680
  }
677
681
  }
682
+ if (msg.toolCalls) {
683
+ for (const tc of msg.toolCalls) {
684
+ total += this.estTokens(JSON.stringify(tc.arguments));
685
+ }
686
+ }
678
687
  }
679
688
  }
680
689
  if (extraMessages.length > 0) {
@@ -814,8 +823,9 @@ var SessionHandler = class _SessionHandler {
814
823
  }
815
824
  async handleChatWithTools(provider, messages, toolDefs) {
816
825
  const session = this.sessions.current;
817
- const apiMessages = [...messages];
818
- const extraMessages = [];
826
+ const { baseMessages: cleanMessages, toolHistory } = extractToolHistory(messages);
827
+ const apiMessages = [...cleanMessages];
828
+ const extraMessages = toolHistory.length > 0 ? rebuildExtraMessages(provider, toolHistory) : [];
819
829
  const maxToolRounds = this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
820
830
  const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
821
831
  const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : 50;
@@ -1020,6 +1030,10 @@ Details: ${errMsg.split("\n")[0]}
1020
1030
  const reasoningContent = result.reasoningContent;
1021
1031
  const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
1022
1032
  extraMessages.push(...newMsgs);
1033
+ persistToolRound(session, result.toolCalls, toolResults, {
1034
+ assistantContent: result.content,
1035
+ reasoningContent
1036
+ });
1023
1037
  const allFree = result.toolCalls.every((tc) => FREE_ROUND_TOOLS.has(tc.name));
1024
1038
  if (allFree) {
1025
1039
  consecutiveFreeRounds++;
@@ -1915,7 +1929,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1915
1929
  case "test": {
1916
1930
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
1917
1931
  try {
1918
- const { executeTests } = await import("./run-tests-IW6GHAVV.js");
1932
+ const { executeTests } = await import("./run-tests-CT4PRGGP.js");
1919
1933
  const argStr = args.join(" ").trim();
1920
1934
  let testArgs = {};
1921
1935
  if (argStr) {
@@ -2432,11 +2446,18 @@ Add .md files to create commands.` });
2432
2446
  sendSessionMessages() {
2433
2447
  const session = this.sessions.current;
2434
2448
  if (!session) return;
2435
- const messages = session.messages.map((m) => ({
2436
- role: m.role,
2437
- content: getContentText(m.content),
2438
- timestamp: m.timestamp?.toISOString()
2439
- }));
2449
+ const messages = session.messages.map((m) => {
2450
+ const out = {
2451
+ role: m.role,
2452
+ content: getContentText(m.content),
2453
+ timestamp: m.timestamp?.toISOString()
2454
+ };
2455
+ if (m.toolCalls) out.toolCalls = m.toolCalls;
2456
+ if (m.toolCallId) out.toolCallId = m.toolCallId;
2457
+ if (m.toolName) out.toolName = m.toolName;
2458
+ if (m.isError !== void 0) out.isError = m.isError;
2459
+ return out;
2460
+ });
2440
2461
  this.send({
2441
2462
  type: "session_messages",
2442
2463
  sessionId: session.id,
@@ -4,11 +4,11 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-7RX7675B.js";
7
+ } from "./chunk-2ZCD5F4X.js";
8
8
  import "./chunk-4BKXL7SM.js";
9
9
  import {
10
10
  SUBAGENT_ALLOWED_TOOLS
11
- } from "./chunk-YJCJBUOG.js";
11
+ } from "./chunk-GA74LZ62.js";
12
12
 
13
13
  // src/hub/task-orchestrator.ts
14
14
  import { createInterface } from "readline";
@@ -717,6 +717,49 @@ function escapeHtml(str) {
717
717
  return div.innerHTML;
718
718
  }
719
719
 
720
+ /**
721
+ * Create a static tool-call card for session history view.
722
+ * Groups an assistant tool-call message with its subsequent tool results.
723
+ * Unlike live tool cards, these have no timer — just the final state.
724
+ *
725
+ * @param {Object} assistantMsg - The assistant message with toolCalls array
726
+ * @param {Object[]} resultMsgs - Subsequent tool-result messages
727
+ */
728
+ function createHistoryToolCards(assistantMsg, resultMsgs) {
729
+ const toolCalls = assistantMsg.toolCalls || [];
730
+ for (let i = 0; i < toolCalls.length; i++) {
731
+ const tc = toolCalls[i];
732
+ const rm = resultMsgs[i]; // may be undefined if results are missing
733
+ const isError = rm ? rm.isError : false;
734
+ const resultContent = rm ? rm.content : '';
735
+
736
+ const el = document.createElement('details');
737
+ el.className = 'tool-card tool-border-safe my-1';
738
+
739
+ const statusIcon = rm ? (isError ? '✗' : '✓') : '?';
740
+ const statusClass = rm ? (isError ? 'text-error' : 'text-success') : 'opacity-50';
741
+ const badgeClass = isError ? 'badge-error' : 'badge-info';
742
+ const levelIcon = isError ? '⚠' : '⚙';
743
+
744
+ const argsHtml = tc.arguments ? formatToolArgs(tc.arguments) : '';
745
+ const truncResult = resultContent.length > 500
746
+ ? resultContent.slice(0, 500) + '...'
747
+ : resultContent;
748
+
749
+ el.innerHTML = `
750
+ <summary class="flex items-center gap-2 w-full cursor-pointer select-none py-1">
751
+ <span class="badge ${badgeClass} badge-sm gap-1">${levelIcon} ${escapeHtml(tc.name)}</span>
752
+ <span class="tool-result-badge text-xs ml-auto ${statusClass}">${statusIcon}</span>
753
+ </summary>
754
+ <div class="tool-details-body pt-1">
755
+ ${argsHtml ? `<div class="tool-args w-full">${argsHtml}</div>` : ''}
756
+ ${rm ? `<div class="tool-result-content mt-2 pt-2 border-t border-base-content/10 w-full ${isError ? 'text-error' : 'text-success'}">${statusIcon} ${escapeHtml(truncResult)}</div>` : ''}
757
+ </div>
758
+ `;
759
+ messagesEl.appendChild(el);
760
+ }
761
+ }
762
+
720
763
  function scrollToBottom() {
721
764
  requestAnimationFrame(() => {
722
765
  chatArea.scrollTop = chatArea.scrollHeight;
@@ -1074,6 +1117,56 @@ function updateBatchBar() {
1074
1117
  * write it into the tab's `messagesHtml` cache; the live DOM is left
1075
1118
  * untouched so the active tab's content never flashes wrong data.
1076
1119
  */
1120
+
1121
+ /**
1122
+ * Render a messages array into the live DOM (messagesEl).
1123
+ * Handles user, assistant (text), assistant (toolCalls), and tool result messages.
1124
+ * Groups consecutive assistant+toolCalls with subsequent tool results into cards.
1125
+ */
1126
+ function renderMessagesArray(messages) {
1127
+ let i = 0;
1128
+ while (i < messages.length) {
1129
+ const m = messages[i];
1130
+ if (m.role === 'user') {
1131
+ addUserMessage(m.content);
1132
+ i++;
1133
+ } else if (m.role === 'assistant' && m.toolCalls && m.toolCalls.length > 0) {
1134
+ // Assistant message with tool calls — collect subsequent tool results
1135
+ const resultMsgs = [];
1136
+ let j = i + 1;
1137
+ while (j < messages.length && messages[j].role === 'tool') {
1138
+ resultMsgs.push(messages[j]);
1139
+ j++;
1140
+ }
1141
+ // If the assistant also had text content, render it first
1142
+ if (m.content && m.content.trim()) {
1143
+ const el = createAssistantMessage();
1144
+ renderMarkdown(el, m.content);
1145
+ }
1146
+ createHistoryToolCards(m, resultMsgs);
1147
+ i = j;
1148
+ } else if (m.role === 'tool') {
1149
+ // Orphan tool result (no preceding assistant+toolCalls) — render as info card
1150
+ const statusIcon = m.isError ? '✗' : '✓';
1151
+ const statusClass = m.isError ? 'text-error' : 'text-success';
1152
+ const el = document.createElement('div');
1153
+ el.className = `tool-card tool-border-safe my-1 p-2 ${statusClass}`;
1154
+ const toolLabel = m.toolName ? escapeHtml(m.toolName) : 'tool';
1155
+ const truncContent = m.content && m.content.length > 300
1156
+ ? m.content.slice(0, 300) + '...' : (m.content || '');
1157
+ el.innerHTML = `<span class="badge badge-info badge-sm">${toolLabel}</span> ${statusIcon} ${escapeHtml(truncContent)}`;
1158
+ messagesEl.appendChild(el);
1159
+ i++;
1160
+ } else if (m.role === 'assistant') {
1161
+ const el = createAssistantMessage();
1162
+ renderMarkdown(el, m.content);
1163
+ i++;
1164
+ } else {
1165
+ // system or unknown — skip
1166
+ i++;
1167
+ }
1168
+ }
1169
+ }
1077
1170
  function renderSessionMessages(msg) {
1078
1171
  // Back-compat: if called with a bare array (legacy), treat as active-tab apply
1079
1172
  const messages = Array.isArray(msg) ? msg : msg.messages;
@@ -1096,14 +1189,7 @@ function renderSessionMessages(msg) {
1096
1189
  // Active tab: paint directly into the live DOM (preserves any in-flight
1097
1190
  // streaming helpers that rely on messagesEl)
1098
1191
  messagesEl.innerHTML = '';
1099
- for (const m of messages) {
1100
- if (m.role === 'user') {
1101
- addUserMessage(m.content);
1102
- } else if (m.role === 'assistant') {
1103
- const el = createAssistantMessage();
1104
- renderMarkdown(el, m.content);
1105
- }
1106
- }
1192
+ renderMessagesArray(messages);
1107
1193
  scrollToBottom();
1108
1194
  // Snapshot into cache so subsequent tab-snapshots see the latest content
1109
1195
  sessionTabs[targetIdx].messagesHtml = messagesEl.innerHTML;
@@ -1136,14 +1222,7 @@ function buildMessagesHtmlOffDom(messages) {
1136
1222
  currentAssistantContent = '';
1137
1223
  currentThinkingEl = null;
1138
1224
  currentThinkingContent = '';
1139
- for (const m of messages) {
1140
- if (m.role === 'user') {
1141
- addUserMessage(m.content);
1142
- } else if (m.role === 'assistant') {
1143
- const el = createAssistantMessage();
1144
- renderMarkdown(el, m.content);
1145
- }
1146
- }
1225
+ renderMessagesArray(messages);
1147
1226
  return messagesEl.innerHTML;
1148
1227
  } finally {
1149
1228
  messagesEl.innerHTML = savedHtml;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.59",
3
+ "version": "0.4.61",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",