jinzd-ai-cli 0.4.101 → 0.4.102

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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-GXR2VZDI.js";
4
+ } from "./chunk-K6RVCRFK.js";
5
5
  import "./chunk-2ZD3YTVM.js";
6
- import "./chunk-3MEH7D36.js";
6
+ import "./chunk-5IMEELWI.js";
7
7
 
8
8
  // src/cli/batch.ts
9
9
  import Anthropic from "@anthropic-ai/sdk";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.101";
4
+ var VERSION = "0.4.102";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -23,7 +23,7 @@ import {
23
23
  } from "./chunk-6VRJGH25.js";
24
24
  import {
25
25
  runTestsTool
26
- } from "./chunk-TFCBOPQV.js";
26
+ } from "./chunk-QLTWUTEO.js";
27
27
  import {
28
28
  CONFIG_DIR_NAME,
29
29
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
@@ -31,7 +31,7 @@ import {
31
31
  SUBAGENT_ALLOWED_TOOLS,
32
32
  SUBAGENT_DEFAULT_MAX_ROUNDS,
33
33
  SUBAGENT_MAX_ROUNDS_LIMIT
34
- } from "./chunk-3MEH7D36.js";
34
+ } from "./chunk-5IMEELWI.js";
35
35
 
36
36
  // src/tools/types.ts
37
37
  function isFileWriteTool(name) {
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-3MEH7D36.js";
11
+ } from "./chunk-5IMEELWI.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -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.101";
9
+ var VERSION = "0.4.102";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-3MEH7D36.js";
4
+ } from "./chunk-5IMEELWI.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync } from "child_process";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-MBPA2FMK.js";
5
+ } from "./chunk-CFPZYNSE.js";
6
6
  import {
7
7
  AuthError,
8
8
  ProviderError,
@@ -21,7 +21,7 @@ import {
21
21
  MCP_PROTOCOL_VERSION,
22
22
  MCP_TOOL_PREFIX,
23
23
  VERSION
24
- } from "./chunk-3MEH7D36.js";
24
+ } from "./chunk-5IMEELWI.js";
25
25
 
26
26
  // src/providers/claude.ts
27
27
  import Anthropic from "@anthropic-ai/sdk";
@@ -36,7 +36,7 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-5EQP4IAM.js";
39
+ } from "./chunk-LAYBLRQG.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -56,7 +56,7 @@ import "./chunk-JV5N65KN.js";
56
56
  import express from "express";
57
57
  import { createServer } from "http";
58
58
  import { WebSocketServer } from "ws";
59
- import { join as join15, dirname as dirname4, resolve as resolve5, relative as relative3, sep as sep2 } from "path";
59
+ import { join as join15, dirname as dirname5, resolve as resolve5, relative as relative3, sep as sep2 } from "path";
60
60
  import { existsSync as existsSync22, readFileSync as readFileSync15, readdirSync as readdirSync11, statSync as statSync9, realpathSync } from "fs";
61
61
  import { networkInterfaces } from "os";
62
62
 
@@ -9615,8 +9615,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
9615
9615
  }
9616
9616
 
9617
9617
  // src/web/session-handler.ts
9618
- import { existsSync as existsSync20, readFileSync as readFileSync13, appendFileSync as appendFileSync3, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
9619
- import { join as join13, resolve as resolve4 } from "path";
9618
+ import { existsSync as existsSync20, readFileSync as readFileSync13, appendFileSync as appendFileSync3, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync9, statSync as statSync8, createWriteStream } from "fs";
9619
+ import { join as join13, resolve as resolve4, dirname as dirname4 } from "path";
9620
9620
  import { execSync as execSync3 } from "child_process";
9621
9621
 
9622
9622
  // src/tools/git-context.ts
@@ -10306,6 +10306,45 @@ ${systemPromptVolatile}` : systemPrompt;
10306
10306
  spawnAgentContext.modelParams = modelParams;
10307
10307
  spawnAgentContext.configManager = this.config;
10308
10308
  ToolExecutor.currentMessageIndex = this.sessions.current?.messages.length ?? 0;
10309
+ const saveLastResponseCall = result.toolCalls.find((tc) => tc.name === "save_last_response");
10310
+ const saveLastResponsePath = saveLastResponseCall ? String(saveLastResponseCall.arguments["path"] ?? "") : "";
10311
+ if (saveLastResponseCall && saveLastResponsePath) {
10312
+ const teeResult = await this.runSaveLastResponseTee(
10313
+ provider,
10314
+ saveLastResponseCall,
10315
+ saveLastResponsePath,
10316
+ apiMessages,
10317
+ extraMessages,
10318
+ systemPrompt,
10319
+ systemPromptVolatile,
10320
+ modelParams,
10321
+ ac,
10322
+ roundUsage
10323
+ );
10324
+ const teeToolResults = result.toolCalls.map((tc) => {
10325
+ if (tc.id === saveLastResponseCall.id) {
10326
+ return {
10327
+ callId: tc.id,
10328
+ content: teeResult.summary,
10329
+ isError: teeResult.isError
10330
+ };
10331
+ }
10332
+ return {
10333
+ callId: tc.id,
10334
+ content: "[skipped: file already saved by tee streaming]",
10335
+ isError: false
10336
+ };
10337
+ });
10338
+ const reasoningContent2 = result.reasoningContent;
10339
+ const newMsgs2 = provider.buildToolResultMessages(result.toolCalls, teeToolResults, reasoningContent2);
10340
+ extraMessages.push(...newMsgs2);
10341
+ persistToolRound(session, result.toolCalls, teeToolResults, {
10342
+ assistantContent: teeResult.content,
10343
+ reasoningContent: reasoningContent2
10344
+ });
10345
+ consecutiveFreeRounds = 0;
10346
+ continue;
10347
+ }
10309
10348
  const toolResults = await this.toolExecutor.executeAll(result.toolCalls);
10310
10349
  const reasoningContent = result.reasoningContent;
10311
10350
  const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
@@ -10417,6 +10456,98 @@ ${summaryResult.content}`,
10417
10456
  this.abortController = null;
10418
10457
  }
10419
10458
  }
10459
+ /**
10460
+ * Tee-streaming for save_last_response in Web mode (v0.4.102+).
10461
+ *
10462
+ * 复刻 REPL 的 [repl.ts:2576] 路径——AI 调用 save_last_response(path) 时,
10463
+ * 不走默认 executor,而是发起一次新的 chatStream,把生成的内容同步推送到
10464
+ * WebSocket(`text_delta` 事件,Web UI 实时渲染)+ 写入磁盘文件。这样 AI
10465
+ * 就能一次性输出 >2KB 的大文档(如教材目录、模考试卷),不会被 tool_call
10466
+ * arguments 截断。
10467
+ *
10468
+ * 已知约束:
10469
+ * - 直接跳过 confirm 流程(与 REPL tee 路径一致)。tee 文本会实时显示,用户
10470
+ * 看到不对可中断。
10471
+ * - 同一轮其余 tool_calls 会被标记为 skipped——AI 通常只调一个工具,多并发
10472
+ * 场景极少。
10473
+ */
10474
+ async runSaveLastResponseTee(provider, call, saveToFile, apiMessages, extraMessages, systemPrompt, systemPromptVolatile, modelParams, ac, roundUsage) {
10475
+ this.send({
10476
+ type: "tool_call_start",
10477
+ callId: call.id,
10478
+ toolName: call.name,
10479
+ args: call.arguments,
10480
+ dangerLevel: "write",
10481
+ round: 0,
10482
+ totalRounds: 0,
10483
+ startTime: Date.now()
10484
+ });
10485
+ let fileStream = null;
10486
+ let fullContent = "";
10487
+ let teeUsage;
10488
+ let isError = false;
10489
+ let summary;
10490
+ try {
10491
+ mkdirSync9(dirname4(saveToFile), { recursive: true });
10492
+ fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
10493
+ const chatRequest = {
10494
+ messages: apiMessages,
10495
+ model: this.currentModel,
10496
+ systemPrompt,
10497
+ systemPromptVolatile,
10498
+ stream: true,
10499
+ temperature: modelParams.temperature,
10500
+ maxTokens: modelParams.maxTokens,
10501
+ timeout: modelParams.timeout,
10502
+ thinking: modelParams.thinking,
10503
+ thinkingBudget: modelParams.thinkingBudget,
10504
+ signal: ac.signal,
10505
+ ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
10506
+ };
10507
+ const stream = provider.chatStream(chatRequest);
10508
+ for await (const chunk of stream) {
10509
+ if (ac.signal.aborted) break;
10510
+ if (chunk.usage) teeUsage = chunk.usage;
10511
+ if (chunk.delta) {
10512
+ fullContent += chunk.delta;
10513
+ this.send({ type: "text_delta", delta: chunk.delta });
10514
+ fileStream.write(chunk.delta);
10515
+ }
10516
+ if (chunk.done) break;
10517
+ }
10518
+ await new Promise((resolve6, reject) => {
10519
+ fileStream.end((err) => err ? reject(err) : resolve6());
10520
+ });
10521
+ const lines = fullContent.split("\n").length;
10522
+ const bytes = Buffer.byteLength(fullContent, "utf-8");
10523
+ summary = `File saved: ${saveToFile} (${lines} lines, ${bytes} bytes)`;
10524
+ undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
10525
+ if (teeUsage) {
10526
+ roundUsage.inputTokens += teeUsage.inputTokens;
10527
+ roundUsage.outputTokens += teeUsage.outputTokens;
10528
+ roundUsage.cacheCreationTokens += teeUsage.cacheCreationTokens ?? 0;
10529
+ roundUsage.cacheReadTokens += teeUsage.cacheReadTokens ?? 0;
10530
+ }
10531
+ } catch (err) {
10532
+ if (fileStream) {
10533
+ try {
10534
+ await new Promise((resolve6) => fileStream.end(() => resolve6()));
10535
+ } catch {
10536
+ }
10537
+ }
10538
+ isError = true;
10539
+ const msg = err instanceof Error ? err.message : String(err);
10540
+ summary = `[save_last_response failed] ${msg}`;
10541
+ }
10542
+ this.send({
10543
+ type: "tool_call_result",
10544
+ callId: call.id,
10545
+ toolName: call.name,
10546
+ content: summary,
10547
+ isError
10548
+ });
10549
+ return { content: fullContent, summary, isError };
10550
+ }
10420
10551
  /** Consume streaming tool call events and forward to client */
10421
10552
  async consumeToolStream(streamGen, ac) {
10422
10553
  let textContent = "";
@@ -11485,7 +11616,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
11485
11616
  case "test": {
11486
11617
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
11487
11618
  try {
11488
- const { executeTests } = await import("./run-tests-7LVMNX4S.js");
11619
+ const { executeTests } = await import("./run-tests-3Z6YDZ6Q.js");
11489
11620
  const argStr = args.join(" ").trim();
11490
11621
  let testArgs = {};
11491
11622
  if (argStr) {
@@ -12174,17 +12305,16 @@ Add .md files to create commands.` });
12174
12305
  };
12175
12306
  }
12176
12307
  getFilteredToolDefs() {
12177
- const excludeWeb = (t) => t.name !== "save_last_response";
12178
12308
  if (this.planMode) {
12179
12309
  return {
12180
- toolDefs: this.toolRegistry.getDefinitions().filter((t) => PLAN_MODE_READONLY_TOOLS.has(t.name) && excludeWeb(t)),
12310
+ toolDefs: this.toolRegistry.getDefinitions().filter((t) => PLAN_MODE_READONLY_TOOLS.has(t.name)),
12181
12311
  mcpBudgetNote: null
12182
12312
  };
12183
12313
  }
12184
12314
  const skillFilter = this.skillManager?.getActiveToolFilter();
12185
12315
  if (skillFilter) {
12186
12316
  return {
12187
- toolDefs: this.toolRegistry.getDefinitions().filter((t) => skillFilter.has(t.name) && excludeWeb(t)),
12317
+ toolDefs: this.toolRegistry.getDefinitions().filter((t) => skillFilter.has(t.name)),
12188
12318
  mcpBudgetNote: null
12189
12319
  };
12190
12320
  }
@@ -12192,9 +12322,9 @@ Add .md files to create commands.` });
12192
12322
  if (contextWindow > 0) {
12193
12323
  const toolBudget = Math.floor(contextWindow * 0.2);
12194
12324
  const { definitions, systemNote } = this.toolRegistry.getDefinitionsWithBudget(toolBudget, this.usedMcpToolNames);
12195
- return { toolDefs: definitions.filter(excludeWeb), mcpBudgetNote: systemNote };
12325
+ return { toolDefs: definitions, mcpBudgetNote: systemNote };
12196
12326
  }
12197
- return { toolDefs: this.toolRegistry.getDefinitions().filter(excludeWeb), mcpBudgetNote: null };
12327
+ return { toolDefs: this.toolRegistry.getDefinitions(), mcpBudgetNote: null };
12198
12328
  }
12199
12329
  /**
12200
12330
  * Find first matching context file in a directory.
@@ -12686,7 +12816,7 @@ function getModuleDir() {
12686
12816
  if (typeof import.meta?.url === "string") {
12687
12817
  const url = new URL(import.meta.url);
12688
12818
  const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
12689
- return dirname4(filePath);
12819
+ return dirname5(filePath);
12690
12820
  }
12691
12821
  } catch {
12692
12822
  }
@@ -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-ZATGIRNE.js");
388
+ const { TaskOrchestrator } = await import("./task-orchestrator-FFOUDDVB.js");
389
389
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
390
390
  let interrupted = false;
391
391
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -28,10 +28,10 @@ import {
28
28
  saveDevState,
29
29
  sessionHasMeaningfulContent,
30
30
  setupProxy
31
- } from "./chunk-VWZW5JWK.js";
31
+ } from "./chunk-VFYKMIQE.js";
32
32
  import {
33
33
  ConfigManager
34
- } from "./chunk-GXR2VZDI.js";
34
+ } from "./chunk-K6RVCRFK.js";
35
35
  import {
36
36
  ToolExecutor,
37
37
  ToolRegistry,
@@ -50,7 +50,7 @@ import {
50
50
  spawnAgentContext,
51
51
  theme,
52
52
  undoStack
53
- } from "./chunk-MBPA2FMK.js";
53
+ } from "./chunk-CFPZYNSE.js";
54
54
  import "./chunk-2ZD3YTVM.js";
55
55
  import {
56
56
  fileCheckpoints
@@ -68,7 +68,7 @@ import "./chunk-KJLJPUY2.js";
68
68
  import "./chunk-6VRJGH25.js";
69
69
  import "./chunk-2DXY7UGF.js";
70
70
  import "./chunk-KHYD3WXE.js";
71
- import "./chunk-TFCBOPQV.js";
71
+ import "./chunk-QLTWUTEO.js";
72
72
  import {
73
73
  AGENTIC_BEHAVIOR_GUIDELINE,
74
74
  AUTHOR,
@@ -90,7 +90,7 @@ import {
90
90
  SKILLS_DIR_NAME,
91
91
  VERSION,
92
92
  buildUserIdentityPrompt
93
- } from "./chunk-3MEH7D36.js";
93
+ } from "./chunk-5IMEELWI.js";
94
94
 
95
95
  // src/index.ts
96
96
  import { program } from "commander";
@@ -2610,7 +2610,7 @@ ${hint}` : "")
2610
2610
  usage: "/test [command|filter]",
2611
2611
  async execute(args, ctx) {
2612
2612
  try {
2613
- const { executeTests } = await import("./run-tests-IG45VWEM.js");
2613
+ const { executeTests } = await import("./run-tests-K2EJOXH2.js");
2614
2614
  const argStr = args.join(" ").trim();
2615
2615
  let testArgs = {};
2616
2616
  if (argStr) {
@@ -6749,7 +6749,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6749
6749
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6750
6750
  process.exit(1);
6751
6751
  }
6752
- const { startWebServer } = await import("./server-UBHVIJA6.js");
6752
+ const { startWebServer } = await import("./server-J2TQBUA5.js");
6753
6753
  await startWebServer({ port, host: options.host });
6754
6754
  });
6755
6755
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6872,7 +6872,7 @@ program.command("sessions").description("List recent conversation sessions").act
6872
6872
  });
6873
6873
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
6874
6874
  try {
6875
- const batch = await import("./batch-DL3IJ7JQ.js");
6875
+ const batch = await import("./batch-TD5ZKJBZ.js");
6876
6876
  switch (action) {
6877
6877
  case "submit":
6878
6878
  if (!arg) {
@@ -6915,7 +6915,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
6915
6915
  }
6916
6916
  });
6917
6917
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
6918
- const { startMcpServer } = await import("./server-X5DQ5V6S.js");
6918
+ const { startMcpServer } = await import("./server-DE7WP5J2.js");
6919
6919
  await startMcpServer({
6920
6920
  allowDestructive: !!options.allowDestructive,
6921
6921
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7042,7 +7042,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7042
7042
  }),
7043
7043
  config.get("customProviders")
7044
7044
  );
7045
- const { startHub } = await import("./hub-O5OX6KWP.js");
7045
+ const { startHub } = await import("./hub-4MMNKPXW.js");
7046
7046
  await startHub(
7047
7047
  {
7048
7048
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-5EQP4IAM.js";
4
+ } from "./chunk-LAYBLRQG.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-TFCBOPQV.js";
6
- import "./chunk-3MEH7D36.js";
5
+ } from "./chunk-QLTWUTEO.js";
6
+ import "./chunk-5IMEELWI.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -3,7 +3,7 @@ import {
3
3
  ToolRegistry,
4
4
  getDangerLevel,
5
5
  schemaToJsonSchema
6
- } from "./chunk-MBPA2FMK.js";
6
+ } from "./chunk-CFPZYNSE.js";
7
7
  import "./chunk-2ZD3YTVM.js";
8
8
  import "./chunk-4BKXL7SM.js";
9
9
  import "./chunk-ANYYM4CF.js";
@@ -12,10 +12,10 @@ import "./chunk-KJLJPUY2.js";
12
12
  import "./chunk-6VRJGH25.js";
13
13
  import "./chunk-2DXY7UGF.js";
14
14
  import "./chunk-KHYD3WXE.js";
15
- import "./chunk-TFCBOPQV.js";
15
+ import "./chunk-QLTWUTEO.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-3MEH7D36.js";
18
+ } from "./chunk-5IMEELWI.js";
19
19
 
20
20
  // src/mcp/server.ts
21
21
  import { createInterface } from "readline";
@@ -21,10 +21,10 @@ import {
21
21
  loadDevState,
22
22
  persistToolRound,
23
23
  setupProxy
24
- } from "./chunk-VWZW5JWK.js";
24
+ } from "./chunk-VFYKMIQE.js";
25
25
  import {
26
26
  ConfigManager
27
- } from "./chunk-GXR2VZDI.js";
27
+ } from "./chunk-K6RVCRFK.js";
28
28
  import {
29
29
  ToolExecutor,
30
30
  ToolRegistry,
@@ -42,7 +42,7 @@ import {
42
42
  spawnAgentContext,
43
43
  truncateOutput,
44
44
  undoStack
45
- } from "./chunk-MBPA2FMK.js";
45
+ } from "./chunk-CFPZYNSE.js";
46
46
  import "./chunk-2ZD3YTVM.js";
47
47
  import "./chunk-4BKXL7SM.js";
48
48
  import "./chunk-ANYYM4CF.js";
@@ -51,7 +51,7 @@ import "./chunk-KJLJPUY2.js";
51
51
  import "./chunk-6VRJGH25.js";
52
52
  import "./chunk-2DXY7UGF.js";
53
53
  import "./chunk-KHYD3WXE.js";
54
- import "./chunk-TFCBOPQV.js";
54
+ import "./chunk-QLTWUTEO.js";
55
55
  import {
56
56
  AGENTIC_BEHAVIOR_GUIDELINE,
57
57
  AUTHOR,
@@ -70,13 +70,13 @@ import {
70
70
  SKILLS_DIR_NAME,
71
71
  VERSION,
72
72
  buildUserIdentityPrompt
73
- } from "./chunk-3MEH7D36.js";
73
+ } from "./chunk-5IMEELWI.js";
74
74
 
75
75
  // src/web/server.ts
76
76
  import express from "express";
77
77
  import { createServer } from "http";
78
78
  import { WebSocketServer } from "ws";
79
- import { join as join3, dirname, resolve as resolve2, relative, sep } from "path";
79
+ import { join as join3, dirname as dirname2, resolve as resolve2, relative, sep } from "path";
80
80
  import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, realpathSync } from "fs";
81
81
  import { networkInterfaces } from "os";
82
82
 
@@ -470,8 +470,8 @@ function loadMemoryContent(configDir) {
470
470
  }
471
471
 
472
472
  // src/web/session-handler.ts
473
- import { existsSync as existsSync3, readFileSync as readFileSync3, appendFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "fs";
474
- import { join as join2, resolve } from "path";
473
+ import { existsSync as existsSync3, readFileSync as readFileSync3, appendFileSync, writeFileSync, mkdirSync, readdirSync, statSync, createWriteStream } from "fs";
474
+ import { join as join2, resolve, dirname } from "path";
475
475
  import { execSync } from "child_process";
476
476
  var FREE_ROUND_TOOLS = /* @__PURE__ */ new Set(["write_todos"]);
477
477
  var MAX_CONSECUTIVE_FREE_ROUNDS = 5;
@@ -1069,6 +1069,45 @@ ${systemPromptVolatile}` : systemPrompt;
1069
1069
  spawnAgentContext.modelParams = modelParams;
1070
1070
  spawnAgentContext.configManager = this.config;
1071
1071
  ToolExecutor.currentMessageIndex = this.sessions.current?.messages.length ?? 0;
1072
+ const saveLastResponseCall = result.toolCalls.find((tc) => tc.name === "save_last_response");
1073
+ const saveLastResponsePath = saveLastResponseCall ? String(saveLastResponseCall.arguments["path"] ?? "") : "";
1074
+ if (saveLastResponseCall && saveLastResponsePath) {
1075
+ const teeResult = await this.runSaveLastResponseTee(
1076
+ provider,
1077
+ saveLastResponseCall,
1078
+ saveLastResponsePath,
1079
+ apiMessages,
1080
+ extraMessages,
1081
+ systemPrompt,
1082
+ systemPromptVolatile,
1083
+ modelParams,
1084
+ ac,
1085
+ roundUsage
1086
+ );
1087
+ const teeToolResults = result.toolCalls.map((tc) => {
1088
+ if (tc.id === saveLastResponseCall.id) {
1089
+ return {
1090
+ callId: tc.id,
1091
+ content: teeResult.summary,
1092
+ isError: teeResult.isError
1093
+ };
1094
+ }
1095
+ return {
1096
+ callId: tc.id,
1097
+ content: "[skipped: file already saved by tee streaming]",
1098
+ isError: false
1099
+ };
1100
+ });
1101
+ const reasoningContent2 = result.reasoningContent;
1102
+ const newMsgs2 = provider.buildToolResultMessages(result.toolCalls, teeToolResults, reasoningContent2);
1103
+ extraMessages.push(...newMsgs2);
1104
+ persistToolRound(session, result.toolCalls, teeToolResults, {
1105
+ assistantContent: teeResult.content,
1106
+ reasoningContent: reasoningContent2
1107
+ });
1108
+ consecutiveFreeRounds = 0;
1109
+ continue;
1110
+ }
1072
1111
  const toolResults = await this.toolExecutor.executeAll(result.toolCalls);
1073
1112
  const reasoningContent = result.reasoningContent;
1074
1113
  const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
@@ -1180,6 +1219,98 @@ ${summaryResult.content}`,
1180
1219
  this.abortController = null;
1181
1220
  }
1182
1221
  }
1222
+ /**
1223
+ * Tee-streaming for save_last_response in Web mode (v0.4.102+).
1224
+ *
1225
+ * 复刻 REPL 的 [repl.ts:2576] 路径——AI 调用 save_last_response(path) 时,
1226
+ * 不走默认 executor,而是发起一次新的 chatStream,把生成的内容同步推送到
1227
+ * WebSocket(`text_delta` 事件,Web UI 实时渲染)+ 写入磁盘文件。这样 AI
1228
+ * 就能一次性输出 >2KB 的大文档(如教材目录、模考试卷),不会被 tool_call
1229
+ * arguments 截断。
1230
+ *
1231
+ * 已知约束:
1232
+ * - 直接跳过 confirm 流程(与 REPL tee 路径一致)。tee 文本会实时显示,用户
1233
+ * 看到不对可中断。
1234
+ * - 同一轮其余 tool_calls 会被标记为 skipped——AI 通常只调一个工具,多并发
1235
+ * 场景极少。
1236
+ */
1237
+ async runSaveLastResponseTee(provider, call, saveToFile, apiMessages, extraMessages, systemPrompt, systemPromptVolatile, modelParams, ac, roundUsage) {
1238
+ this.send({
1239
+ type: "tool_call_start",
1240
+ callId: call.id,
1241
+ toolName: call.name,
1242
+ args: call.arguments,
1243
+ dangerLevel: "write",
1244
+ round: 0,
1245
+ totalRounds: 0,
1246
+ startTime: Date.now()
1247
+ });
1248
+ let fileStream = null;
1249
+ let fullContent = "";
1250
+ let teeUsage;
1251
+ let isError = false;
1252
+ let summary;
1253
+ try {
1254
+ mkdirSync(dirname(saveToFile), { recursive: true });
1255
+ fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
1256
+ const chatRequest = {
1257
+ messages: apiMessages,
1258
+ model: this.currentModel,
1259
+ systemPrompt,
1260
+ systemPromptVolatile,
1261
+ stream: true,
1262
+ temperature: modelParams.temperature,
1263
+ maxTokens: modelParams.maxTokens,
1264
+ timeout: modelParams.timeout,
1265
+ thinking: modelParams.thinking,
1266
+ thinkingBudget: modelParams.thinkingBudget,
1267
+ signal: ac.signal,
1268
+ ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
1269
+ };
1270
+ const stream = provider.chatStream(chatRequest);
1271
+ for await (const chunk of stream) {
1272
+ if (ac.signal.aborted) break;
1273
+ if (chunk.usage) teeUsage = chunk.usage;
1274
+ if (chunk.delta) {
1275
+ fullContent += chunk.delta;
1276
+ this.send({ type: "text_delta", delta: chunk.delta });
1277
+ fileStream.write(chunk.delta);
1278
+ }
1279
+ if (chunk.done) break;
1280
+ }
1281
+ await new Promise((resolve3, reject) => {
1282
+ fileStream.end((err) => err ? reject(err) : resolve3());
1283
+ });
1284
+ const lines = fullContent.split("\n").length;
1285
+ const bytes = Buffer.byteLength(fullContent, "utf-8");
1286
+ summary = `File saved: ${saveToFile} (${lines} lines, ${bytes} bytes)`;
1287
+ undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
1288
+ if (teeUsage) {
1289
+ roundUsage.inputTokens += teeUsage.inputTokens;
1290
+ roundUsage.outputTokens += teeUsage.outputTokens;
1291
+ roundUsage.cacheCreationTokens += teeUsage.cacheCreationTokens ?? 0;
1292
+ roundUsage.cacheReadTokens += teeUsage.cacheReadTokens ?? 0;
1293
+ }
1294
+ } catch (err) {
1295
+ if (fileStream) {
1296
+ try {
1297
+ await new Promise((resolve3) => fileStream.end(() => resolve3()));
1298
+ } catch {
1299
+ }
1300
+ }
1301
+ isError = true;
1302
+ const msg = err instanceof Error ? err.message : String(err);
1303
+ summary = `[save_last_response failed] ${msg}`;
1304
+ }
1305
+ this.send({
1306
+ type: "tool_call_result",
1307
+ callId: call.id,
1308
+ toolName: call.name,
1309
+ content: summary,
1310
+ isError
1311
+ });
1312
+ return { content: fullContent, summary, isError };
1313
+ }
1183
1314
  /** Consume streaming tool call events and forward to client */
1184
1315
  async consumeToolStream(streamGen, ac) {
1185
1316
  let textContent = "";
@@ -2248,7 +2379,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2248
2379
  case "test": {
2249
2380
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2250
2381
  try {
2251
- const { executeTests } = await import("./run-tests-IG45VWEM.js");
2382
+ const { executeTests } = await import("./run-tests-K2EJOXH2.js");
2252
2383
  const argStr = args.join(" ").trim();
2253
2384
  let testArgs = {};
2254
2385
  if (argStr) {
@@ -2937,17 +3068,16 @@ Add .md files to create commands.` });
2937
3068
  };
2938
3069
  }
2939
3070
  getFilteredToolDefs() {
2940
- const excludeWeb = (t) => t.name !== "save_last_response";
2941
3071
  if (this.planMode) {
2942
3072
  return {
2943
- toolDefs: this.toolRegistry.getDefinitions().filter((t) => PLAN_MODE_READONLY_TOOLS.has(t.name) && excludeWeb(t)),
3073
+ toolDefs: this.toolRegistry.getDefinitions().filter((t) => PLAN_MODE_READONLY_TOOLS.has(t.name)),
2944
3074
  mcpBudgetNote: null
2945
3075
  };
2946
3076
  }
2947
3077
  const skillFilter = this.skillManager?.getActiveToolFilter();
2948
3078
  if (skillFilter) {
2949
3079
  return {
2950
- toolDefs: this.toolRegistry.getDefinitions().filter((t) => skillFilter.has(t.name) && excludeWeb(t)),
3080
+ toolDefs: this.toolRegistry.getDefinitions().filter((t) => skillFilter.has(t.name)),
2951
3081
  mcpBudgetNote: null
2952
3082
  };
2953
3083
  }
@@ -2955,9 +3085,9 @@ Add .md files to create commands.` });
2955
3085
  if (contextWindow > 0) {
2956
3086
  const toolBudget = Math.floor(contextWindow * 0.2);
2957
3087
  const { definitions, systemNote } = this.toolRegistry.getDefinitionsWithBudget(toolBudget, this.usedMcpToolNames);
2958
- return { toolDefs: definitions.filter(excludeWeb), mcpBudgetNote: systemNote };
3088
+ return { toolDefs: definitions, mcpBudgetNote: systemNote };
2959
3089
  }
2960
- return { toolDefs: this.toolRegistry.getDefinitions().filter(excludeWeb), mcpBudgetNote: null };
3090
+ return { toolDefs: this.toolRegistry.getDefinitions(), mcpBudgetNote: null };
2961
3091
  }
2962
3092
  /**
2963
3093
  * Find first matching context file in a directory.
@@ -3227,7 +3357,7 @@ function getModuleDir() {
3227
3357
  if (typeof import.meta?.url === "string") {
3228
3358
  const url = new URL(import.meta.url);
3229
3359
  const filePath = url.pathname.replace(/^\/([A-Z]:)/i, "$1");
3230
- return dirname(filePath);
3360
+ return dirname2(filePath);
3231
3361
  }
3232
3362
  } catch {
3233
3363
  }
@@ -4,7 +4,7 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-MBPA2FMK.js";
7
+ } from "./chunk-CFPZYNSE.js";
8
8
  import "./chunk-2ZD3YTVM.js";
9
9
  import "./chunk-4BKXL7SM.js";
10
10
  import "./chunk-ANYYM4CF.js";
@@ -13,10 +13,10 @@ import "./chunk-KJLJPUY2.js";
13
13
  import "./chunk-6VRJGH25.js";
14
14
  import "./chunk-2DXY7UGF.js";
15
15
  import "./chunk-KHYD3WXE.js";
16
- import "./chunk-TFCBOPQV.js";
16
+ import "./chunk-QLTWUTEO.js";
17
17
  import {
18
18
  SUBAGENT_ALLOWED_TOOLS
19
- } from "./chunk-3MEH7D36.js";
19
+ } from "./chunk-5IMEELWI.js";
20
20
 
21
21
  // src/hub/task-orchestrator.ts
22
22
  import { createInterface } from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.101",
3
+ "version": "0.4.102",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",