dskcode 0.1.28 → 0.1.29

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.
package/dist/index.js CHANGED
@@ -772,7 +772,7 @@ async function getAllSkills(cwd) {
772
772
 
773
773
  // src/cli/index.tsx
774
774
  import { readFile as readFile11 } from "fs/promises";
775
- import { join as join10 } from "path";
775
+ import { join as join12 } from "path";
776
776
 
777
777
  // src/ui/RenderScope.tsx
778
778
  import { render } from "ink";
@@ -2277,6 +2277,153 @@ function isENOENT(err) {
2277
2277
  return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
2278
2278
  }
2279
2279
 
2280
+ // src/logger/logger.ts
2281
+ import { mkdir as mkdir5, appendFile } from "fs/promises";
2282
+ import { join as join5, basename } from "path";
2283
+ var MAX_DATA_LEN = 2e3;
2284
+ var MAX_QUEUE = 500;
2285
+ function defaultLogsDir() {
2286
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
2287
+ return join5(home, ".dskcode", "logs");
2288
+ }
2289
+ function projectNameFromCwd(cwd) {
2290
+ const name = basename(cwd);
2291
+ return name || "_root";
2292
+ }
2293
+ var ConversationLogger = class _ConversationLogger {
2294
+ /** 所有活跃的 logger 实例(用于退出时统一 flush) */
2295
+ static #instances = /* @__PURE__ */ new Set();
2296
+ /**
2297
+ * 刷新所有活跃 logger 实例的缓冲,确保事件落盘。
2298
+ * 进程退出前调用。
2299
+ */
2300
+ static async flushAll() {
2301
+ const promises = [];
2302
+ for (const logger of _ConversationLogger.#instances) {
2303
+ promises.push(logger.flush());
2304
+ }
2305
+ await Promise.allSettled(promises);
2306
+ }
2307
+ #logPath;
2308
+ #enabled;
2309
+ #queue = Promise.resolve();
2310
+ #queueLen = 0;
2311
+ #closed = false;
2312
+ constructor(sessionId, cwd, options) {
2313
+ this.#enabled = options?.enabled ?? true;
2314
+ if (!this.#enabled) {
2315
+ this.#logPath = "";
2316
+ return;
2317
+ }
2318
+ const baseDir = options?.logsDir ?? defaultLogsDir();
2319
+ const projectName = projectNameFromCwd(cwd);
2320
+ this.#logPath = join5(baseDir, projectName, `${sessionId}.jsonl`);
2321
+ _ConversationLogger.#instances.add(this);
2322
+ }
2323
+ /** 日志文件完整路径(禁用时返回空字符串) */
2324
+ get logPath() {
2325
+ return this.#logPath;
2326
+ }
2327
+ /** 是否启用日志记录 */
2328
+ get enabled() {
2329
+ return this.#enabled;
2330
+ }
2331
+ /**
2332
+ * 记录一个事件。
2333
+ *
2334
+ * 内部将事件序列化为 JSON 并追加到日志文件。
2335
+ * 写入操作串行排队,保证事件顺序。调用方无需 await。
2336
+ */
2337
+ log(event) {
2338
+ if (!this.#enabled || this.#closed) return;
2339
+ if (this.#queueLen >= MAX_QUEUE) return;
2340
+ this.#queueLen++;
2341
+ this.#queue = this.#queue.then(() => this.#writeLine(event)).catch(() => {
2342
+ }).finally(() => this.#queueLen--);
2343
+ }
2344
+ /**
2345
+ * 关闭日志记录器。
2346
+ *
2347
+ * 标记关闭,不再接受新事件,但会等待队列中已有事件写入完成。
2348
+ * 返回的 Promise resolve 后保证所有已 log 的事件都已落盘。
2349
+ */
2350
+ async flush() {
2351
+ this.#closed = true;
2352
+ await this.#queue.catch(() => {
2353
+ });
2354
+ _ConversationLogger.#instances.delete(this);
2355
+ }
2356
+ /** 将一行 JSON 写入日志文件 */
2357
+ async #writeLine(event) {
2358
+ const dir = join5(this.#logPath, "..");
2359
+ await mkdir5(dir, { recursive: true });
2360
+ const line = JSON.stringify(event) + "\n";
2361
+ await appendFile(this.#logPath, line, "utf-8");
2362
+ }
2363
+ // -----------------------------------------------------------------------
2364
+ // 便捷方法 — 封装常见事件类型,减少调用方样板代码
2365
+ // -----------------------------------------------------------------------
2366
+ /** 记录会话开始 */
2367
+ logSessionStart(sessionId, cwd, model, mode) {
2368
+ this.log({ ts: Date.now(), type: "session_start", sessionId, cwd, model, mode });
2369
+ }
2370
+ /** 记录用户消息 */
2371
+ logUserMessage(content) {
2372
+ this.log({ ts: Date.now(), type: "user_message", content });
2373
+ }
2374
+ /** 记录助手文本(一轮中模型输出的完整文本) */
2375
+ logAssistantText(content, round) {
2376
+ this.log({ ts: Date.now(), type: "assistant_text", content, round });
2377
+ }
2378
+ /** 记录工具调用 */
2379
+ logToolCall(name, callId, args, round) {
2380
+ this.log({ ts: Date.now(), type: "tool_call", name, callId, arguments: args, round });
2381
+ }
2382
+ /** 记录工具结果 */
2383
+ logToolResult(name, callId, success, data, error, elapsed, round) {
2384
+ this.log({
2385
+ ts: Date.now(),
2386
+ type: "tool_result",
2387
+ name,
2388
+ callId,
2389
+ success,
2390
+ data: truncate(data),
2391
+ ...error ? { error } : {},
2392
+ ...elapsed !== void 0 ? { elapsed } : {},
2393
+ round
2394
+ });
2395
+ }
2396
+ /** 记录 Token 用量与费用 */
2397
+ logUsage(model, promptTokens, completionTokens, cachedPromptTokens, cost, round) {
2398
+ this.log({
2399
+ ts: Date.now(),
2400
+ type: "usage",
2401
+ model,
2402
+ promptTokens,
2403
+ completionTokens,
2404
+ ...cachedPromptTokens !== void 0 ? { cachedPromptTokens } : {},
2405
+ cost,
2406
+ round
2407
+ });
2408
+ }
2409
+ /** 记录错误 */
2410
+ logError(message, stack) {
2411
+ this.log({ ts: Date.now(), type: "error", message, ...stack ? { stack } : {} });
2412
+ }
2413
+ /** 记录一轮对话完成 */
2414
+ logTurnDone(elapsed, toolRounds) {
2415
+ this.log({ ts: Date.now(), type: "turn_done", elapsed, toolRounds });
2416
+ }
2417
+ /** 记录会话结束 */
2418
+ logSessionEnd(elapsed) {
2419
+ this.log({ ts: Date.now(), type: "session_end", elapsed });
2420
+ }
2421
+ };
2422
+ function truncate(text) {
2423
+ if (text.length <= MAX_DATA_LEN) return text;
2424
+ return text.slice(0, MAX_DATA_LEN) + `...[\u5DF2\u622A\u65AD\uFF0C\u539F\u59CB\u957F\u5EA6 ${text.length}]`;
2425
+ }
2426
+
2280
2427
  // src/agent/index.ts
2281
2428
  var Session = class _Session {
2282
2429
  #messages = [];
@@ -2292,6 +2439,7 @@ var Session = class _Session {
2292
2439
  #checkpoints = /* @__PURE__ */ new Map();
2293
2440
  #stormRecords = [];
2294
2441
  #mode = "code";
2442
+ #logger;
2295
2443
  constructor(provider, tools = [], costTracker, options) {
2296
2444
  this.#provider = provider;
2297
2445
  if (tools instanceof ToolRegistry) {
@@ -2309,11 +2457,16 @@ var Session = class _Session {
2309
2457
  projectContext: options?.projectContext,
2310
2458
  gate: options?.gate ?? new AlwaysAllowGate(),
2311
2459
  writeRoots: options?.writeRoots ?? [options?.cwd ?? process.cwd()],
2312
- enableCheckpoint: options?.enableCheckpoint ?? true
2460
+ enableCheckpoint: options?.enableCheckpoint ?? true,
2461
+ enableLog: options?.enableLog ?? true
2313
2462
  };
2314
2463
  this.#sessionId = options?.sessionId ?? SessionStore.newId();
2315
2464
  this.#store = options?.store === false ? null : options?.store ?? new SessionStore();
2316
2465
  this.#createdAt = Date.now();
2466
+ this.#logger = new ConversationLogger(this.#sessionId, this.#options.cwd, {
2467
+ enabled: this.#options.enableLog
2468
+ });
2469
+ this.#logger.logSessionStart(this.#sessionId, this.#options.cwd, this.#provider.model(), this.#mode);
2317
2470
  }
2318
2471
  get messages() {
2319
2472
  return this.#messages;
@@ -2348,6 +2501,7 @@ var Session = class _Session {
2348
2501
  }
2349
2502
  async *chat(userInput, opts) {
2350
2503
  this.#messages.push({ role: "user", content: userInput });
2504
+ this.#logger.logUserMessage(userInput);
2351
2505
  const userMsgIndex = this.#messages.length - 1;
2352
2506
  if (this.#options.enableCheckpoint) {
2353
2507
  try {
@@ -2396,13 +2550,26 @@ var Session = class _Session {
2396
2550
  if (lastUsage) {
2397
2551
  const modelId = this.#provider.model();
2398
2552
  this.#costTracker.record(lastUsage, modelId);
2553
+ const cost = calculateCost(lastUsage, modelId);
2554
+ this.#logger.logUsage(
2555
+ modelId,
2556
+ lastUsage.promptTokens,
2557
+ lastUsage.completionTokens,
2558
+ lastUsage.cachedPromptTokens,
2559
+ cost.totalCost,
2560
+ toolRounds
2561
+ );
2399
2562
  yield { type: "usage", usage: lastUsage, model: modelId };
2400
2563
  }
2401
2564
  const assistantMsg = { role: "assistant", content: accumulatedText };
2402
2565
  if (lastToolCalls && lastToolCalls.length > 0) assistantMsg.toolCalls = lastToolCalls;
2403
2566
  this.#messages.push(assistantMsg);
2567
+ this.#logger.logAssistantText(accumulatedText, toolRounds);
2404
2568
  if (lastToolCalls && lastToolCalls.length > 0) {
2405
2569
  yield { type: "tool_calls", calls: lastToolCalls };
2570
+ for (const tc of lastToolCalls) {
2571
+ this.#logger.logToolCall(tc.name, tc.id, tc.arguments, toolRounds);
2572
+ }
2406
2573
  const stormBroken = this.#checkStormBreak(lastToolCalls);
2407
2574
  if (stormBroken) {
2408
2575
  const stormMsg = "\n\u26A0\uFE0F \u540C\u4E00\u5DE5\u5177\u91CD\u590D\u51FA\u9519\uFF0C\u5DF2\u5F3A\u5236\u5207\u6362\u7B56\u7565\n";
@@ -2417,6 +2584,15 @@ var Session = class _Session {
2417
2584
  this.#stormRecords = results.records;
2418
2585
  for (const item of results.items) {
2419
2586
  yield { type: "tool_result", name: item.name, result: item.result };
2587
+ this.#logger.logToolResult(
2588
+ item.name,
2589
+ item.callId,
2590
+ item.result.success,
2591
+ item.result.data,
2592
+ item.result.error,
2593
+ void 0,
2594
+ toolRounds
2595
+ );
2420
2596
  let toolContent = item.result.data;
2421
2597
  if (item.result.diff && item.result.diff.patch) toolContent += `
2422
2598
 
@@ -2436,10 +2612,15 @@ ${item.result.diff.patch}`;
2436
2612
  } catch (err) {
2437
2613
  if (err instanceof DOMException && err.name === "AbortError") return;
2438
2614
  if (err instanceof Error && err.name === "AbortError") return;
2615
+ this.#logger.logError(
2616
+ err instanceof Error ? err.message : String(err),
2617
+ err instanceof Error ? err.stack : void 0
2618
+ );
2439
2619
  yield { type: "error", error: err instanceof Error ? err : new Error(String(err)) };
2440
2620
  return;
2441
2621
  }
2442
2622
  const elapsed = Date.now() - startTime;
2623
+ this.#logger.logTurnDone(elapsed, toolRounds);
2443
2624
  void this.#persist();
2444
2625
  await this.#costTracker.flush().catch(() => {
2445
2626
  });
@@ -2544,6 +2725,10 @@ ${item.result.diff.patch}`;
2544
2725
  this.#persistTimer = null;
2545
2726
  }
2546
2727
  }
2728
+ /** 刷新日志缓冲,确保所有已记录的事件落盘 */
2729
+ async flushLog() {
2730
+ await this.#logger.flush();
2731
+ }
2547
2732
  reset() {
2548
2733
  this.#messages.length = 0;
2549
2734
  this.#costTracker.resetSession();
@@ -2685,6 +2870,8 @@ ${item.result.diff.patch}`;
2685
2870
  void discardCheckpoint(cp2);
2686
2871
  }
2687
2872
  this.#checkpoints.clear();
2873
+ this.#logger.logSessionEnd(Date.now() - this.#createdAt);
2874
+ await this.#logger.flush();
2688
2875
  }
2689
2876
  // -------------------------------------------------------------------------
2690
2877
  // 内部方法
@@ -2780,6 +2967,10 @@ async function execCommand(command, args, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, s
2780
2967
  spawnCmd = command;
2781
2968
  spawnArgs = args;
2782
2969
  useShell = false;
2970
+ if (isWindows && spawnCmd === "cmd" && spawnArgs[0] === "/c") {
2971
+ const prefixed = `chcp 65001 >nul && ${spawnArgs[1] ?? ""}`;
2972
+ spawnArgs = ["/c", prefixed];
2973
+ }
2783
2974
  } else {
2784
2975
  useShell = !isWindows;
2785
2976
  spawnCmd = isWindows ? "cmd" : command;
@@ -2794,10 +2985,10 @@ async function execCommand(command, args, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, s
2794
2985
  let stdout = "";
2795
2986
  let stderr = "";
2796
2987
  child.stdout.on("data", (data) => {
2797
- stdout += data.toString();
2988
+ stdout += data.toString("utf8");
2798
2989
  });
2799
2990
  child.stderr.on("data", (data) => {
2800
- stderr += data.toString();
2991
+ stderr += data.toString("utf8");
2801
2992
  });
2802
2993
  const timeout = setTimeout(() => {
2803
2994
  child.kill("SIGTERM");
@@ -3243,8 +3434,8 @@ var readFileTool = {
3243
3434
  };
3244
3435
 
3245
3436
  // src/tool/builtins/write-file.ts
3246
- import { mkdir as mkdir5, readFile as readFile6 } from "fs/promises";
3247
- import { dirname, relative as relative3, basename } from "path";
3437
+ import { mkdir as mkdir6, readFile as readFile6 } from "fs/promises";
3438
+ import { dirname, relative as relative3, basename as basename2 } from "path";
3248
3439
  var writeFileTool = {
3249
3440
  name: "write_file",
3250
3441
  kind: "edit" /* Edit */,
@@ -3286,7 +3477,7 @@ var writeFileTool = {
3286
3477
  existedBefore = true;
3287
3478
  } catch {
3288
3479
  }
3289
- await mkdir5(dirname(filePath), { recursive: true });
3480
+ await mkdir6(dirname(filePath), { recursive: true });
3290
3481
  const content = args.content;
3291
3482
  await writeFileWithEol(filePath, oldContent, content);
3292
3483
  const diff = computeFileDiff(oldContent, content, filePath);
@@ -3295,7 +3486,7 @@ var writeFileTool = {
3295
3486
  const byteSize = Buffer.byteLength(content, "utf-8");
3296
3487
  const action = existedBefore ? "\u5DF2\u4FEE\u6539" : "\u5DF2\u521B\u5EFA";
3297
3488
  const diffSummary = existedBefore ? `\uFF0C+${diff.additions} -${diff.deletions}` : `\uFF0C+${diff.additions} \u884C\uFF08\u65B0\u5EFA\uFF09`;
3298
- const fileName = basename(filePath);
3489
+ const fileName = basename2(filePath);
3299
3490
  const summary = existedBefore ? `\u{1F4DD} \u4FEE\u6539: ${fileName} (+${diff.additions} -${diff.deletions})` : `\u{1F4DD} \u65B0\u5EFA: ${fileName} (+${diff.additions} \u884C)`;
3300
3491
  return {
3301
3492
  success: true,
@@ -3316,7 +3507,7 @@ var writeFileTool = {
3316
3507
 
3317
3508
  // src/tool/builtins/edit-file.ts
3318
3509
  import { readFile as readFile7 } from "fs/promises";
3319
- import { basename as basename2 } from "path";
3510
+ import { basename as basename3 } from "path";
3320
3511
  var editFileTool = {
3321
3512
  name: "edit_file",
3322
3513
  kind: "edit" /* Edit */,
@@ -3384,7 +3575,7 @@ var editFileTool = {
3384
3575
  const oldLines = args.old_text.split("\n").length;
3385
3576
  const newLines = args.new_text.split("\n").length;
3386
3577
  const diffSummary = `+${diff.additions} -${diff.deletions}`;
3387
- const summary = `\u{1F4DD} \u4FEE\u6539: ${basename2(filePath)} (+${diff.additions} -${diff.deletions})`;
3578
+ const summary = `\u{1F4DD} \u4FEE\u6539: ${basename3(filePath)} (+${diff.additions} -${diff.deletions})`;
3388
3579
  return {
3389
3580
  success: true,
3390
3581
  data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
@@ -3407,7 +3598,7 @@ ${oldLines} \u884C \u2192 ${newLines} \u884C
3407
3598
 
3408
3599
  // src/tool/builtins/multi-edit.ts
3409
3600
  import { readFile as readFile8 } from "fs/promises";
3410
- import { basename as basename3 } from "path";
3601
+ import { basename as basename4 } from "path";
3411
3602
  var multiEditTool = {
3412
3603
  name: "multi_edit",
3413
3604
  kind: "edit" /* Edit */,
@@ -3505,7 +3696,7 @@ var multiEditTool = {
3505
3696
  data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
3506
3697
  \u5171\u6267\u884C ${args.edits.length} \u6B65\u66FF\u6362
3507
3698
  \u53D8\u66F4\uFF1A+${diff.additions} -${diff.deletions}`,
3508
- summary: `\u{1F4DD} \u4FEE\u6539: ${basename3(filePath)} (${args.edits.length} \u6B65, +${diff.additions} -${diff.deletions})`,
3699
+ summary: `\u{1F4DD} \u4FEE\u6539: ${basename4(filePath)} (${args.edits.length} \u6B65, +${diff.additions} -${diff.deletions})`,
3509
3700
  diff
3510
3701
  };
3511
3702
  } catch (err) {
@@ -3521,7 +3712,7 @@ var multiEditTool = {
3521
3712
 
3522
3713
  // src/tool/builtins/delete-range.ts
3523
3714
  import { readFile as readFile9 } from "fs/promises";
3524
- import { basename as basename4 } from "path";
3715
+ import { basename as basename5 } from "path";
3525
3716
  function findUniqueLine(lines, anchor, label) {
3526
3717
  const matches = [];
3527
3718
  for (let i = 0; i < lines.length; i++) {
@@ -3617,7 +3808,7 @@ var deleteRangeTool = {
3617
3808
  const diff = computeFileDiff(content, newContent, filePath);
3618
3809
  diff.existedBefore = true;
3619
3810
  const deletedLines = rangeEnd - rangeStart + 1;
3620
- const summary = `\u{1F4DD} \u4FEE\u6539: ${basename4(filePath)} (\u5220 ${deletedLines} \u884C, +${diff.additions} -${diff.deletions})`;
3811
+ const summary = `\u{1F4DD} \u4FEE\u6539: ${basename5(filePath)} (\u5220 ${deletedLines} \u884C, +${diff.additions} -${diff.deletions})`;
3621
3812
  return {
3622
3813
  success: true,
3623
3814
  data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
@@ -3639,17 +3830,62 @@ var deleteRangeTool = {
3639
3830
 
3640
3831
  // src/tool/builtins/bash.ts
3641
3832
  import process3 from "process";
3833
+ import { existsSync as existsSync3 } from "fs";
3834
+ import { join as join6 } from "path";
3642
3835
  var isWindows2 = process3.platform === "win32";
3836
+ function detectShell() {
3837
+ if (!isWindows2) {
3838
+ return {
3839
+ bin: "sh",
3840
+ args: ["-c"],
3841
+ label: "sh",
3842
+ hint: "\u5F53\u524D shell \u662F bash \u517C\u5BB9\u8BED\u6CD5\uFF0C\u652F\u6301 pwd/ls/&&/\u7BA1\u9053\u7B49\u6807\u51C6 Unix \u5DE5\u5177\u3002"
3843
+ };
3844
+ }
3845
+ const envBash = process3.env.DSKCODE_BASH;
3846
+ if (envBash && existsSync3(envBash)) {
3847
+ return { bin: envBash, args: ["-c"], label: "Git Bash", hint: SHELL_HINT_BASH };
3848
+ }
3849
+ const roots = [
3850
+ process3.env.ProgramFiles,
3851
+ process3.env["ProgramFiles(x86)"],
3852
+ process3.env.LOCALAPPDATA ? join6(process3.env.LOCALAPPDATA, "Programs") : null,
3853
+ // 非 C 盘安装(你机器就是 D 盘),枚举几个常见盘符根
3854
+ "D:\\Program Files",
3855
+ "E:\\Program Files"
3856
+ ].filter((v) => Boolean(v));
3857
+ for (const root of roots) {
3858
+ for (const sub of ["Git\\bin\\bash.exe", "Git\\usr\\bin\\bash.exe"]) {
3859
+ const candidate = join6(root, sub);
3860
+ if (existsSync3(candidate)) {
3861
+ return { bin: candidate, args: ["-c"], label: "Git Bash", hint: SHELL_HINT_BASH };
3862
+ }
3863
+ }
3864
+ }
3865
+ return {
3866
+ bin: "cmd",
3867
+ args: ["/c"],
3868
+ label: "cmd.exe",
3869
+ hint: "\u5F53\u524D shell \u662F Windows cmd.exe\u3002\u8BF7\u4F7F\u7528 cmd \u8BED\u6CD5\uFF1A\u8DEF\u5F84\u7528\u53CD\u659C\u6760\u6216\u6B63\u659C\u6760\u5747\u53EF\u3001\u547D\u4EE4\u94FE\u7528 & \u6216\u6362\u884C\uFF0C\u907F\u514D\u4F7F\u7528 pwd/ls/&& \u7B49 Unix \u547D\u4EE4\u3002"
3870
+ };
3871
+ }
3872
+ var SHELL_HINT_BASH = "\u5F53\u524D shell \u662F bash \u517C\u5BB9\u8BED\u6CD5\uFF0C\u652F\u6301 pwd/ls/&&/\u7BA1\u9053\u7B49\u6807\u51C6 Unix \u5DE5\u5177\u3002";
3873
+ var SHELL_INFO = detectShell();
3643
3874
  var bashTool = {
3644
3875
  name: "bash",
3645
3876
  kind: "other" /* Other */,
3646
- description: "\u5728 shell \u4E2D\u6267\u884C\u547D\u4EE4\u3002\u8FD4\u56DE\u6807\u51C6\u8F93\u51FA\u3001\u6807\u51C6\u9519\u8BEF\u548C\u9000\u51FA\u7801\u3002\u652F\u6301\u8D85\u65F6\u63A7\u5236\u548C\u4FE1\u53F7\u4E2D\u6B62\u3002\u9002\u7528\u4E8E\u8FD0\u884C\u6784\u5EFA\u3001\u6D4B\u8BD5\u3001Git \u64CD\u4F5C\u7B49\u547D\u4EE4\u3002",
3877
+ description: [
3878
+ "\u5728 shell \u4E2D\u6267\u884C\u547D\u4EE4\uFF0C\u8FD4\u56DE stdout / stderr / \u9000\u51FA\u7801\u3002\u652F\u6301\u8D85\u65F6\u63A7\u5236\u548C\u4FE1\u53F7\u4E2D\u6B62\u3002",
3879
+ `\u5F53\u524D shell\uFF1A${SHELL_INFO.label}\u3002${SHELL_INFO.hint}`,
3880
+ "\u8BFB\u53D6\u6587\u4EF6\u8BF7\u4F18\u5148\u4F7F\u7528 read_file \u5DE5\u5177\uFF0C\u4E0D\u8981\u7528 cat/type/Get-Content\uFF1B",
3881
+ "\u641C\u7D22\u4EE3\u7801\u7528 grep / glob\uFF0C\u5217\u76EE\u5F55\u7528 ls\u2014\u2014\u8FD9\u4E9B\u4E13\u7528\u5DE5\u5177\u5728\u6240\u6709\u5E73\u53F0\u90FD\u53EF\u7528\u4E14\u65E0\u9700\u5173\u5FC3 shell \u8BED\u6CD5\u3002"
3882
+ ].join(""),
3647
3883
  parameters: {
3648
3884
  type: "object",
3649
3885
  properties: {
3650
3886
  command: {
3651
3887
  type: "string",
3652
- description: "\u8981\u6267\u884C\u7684 shell \u547D\u4EE4"
3888
+ description: `\u8981\u6267\u884C\u7684 shell \u547D\u4EE4\uFF08\u5F53\u524D shell\uFF1A${SHELL_INFO.label}\uFF09`
3653
3889
  },
3654
3890
  timeout: {
3655
3891
  type: "number",
@@ -3665,8 +3901,8 @@ var bashTool = {
3665
3901
  }
3666
3902
  const timeout = args.timeout ?? ctx.timeout ?? getDefaultTimeout();
3667
3903
  try {
3668
- const shellCommand = isWindows2 ? "cmd" : "sh";
3669
- const shellArgs = isWindows2 ? ["/c", args.command] : ["-c", args.command];
3904
+ const shellCommand = SHELL_INFO.bin ?? "cmd";
3905
+ const shellArgs = [...SHELL_INFO.args, args.command];
3670
3906
  const result = await execCommand(
3671
3907
  shellCommand,
3672
3908
  shellArgs,
@@ -3707,7 +3943,7 @@ ${truncateOutput(result.stderr)}`);
3707
3943
 
3708
3944
  // src/tool/builtins/glob.ts
3709
3945
  import { readdir as readdir3, stat as stat2 } from "fs/promises";
3710
- import { join as join5, relative as relative4, isAbsolute as isAbsolute2 } from "path";
3946
+ import { join as join7, relative as relative4, isAbsolute as isAbsolute2 } from "path";
3711
3947
  function globToRegex(pattern) {
3712
3948
  let regexStr = pattern;
3713
3949
  regexStr = regexStr.replace(/\*\*\//g, "<<GLOBSTAR_SLASH>>");
@@ -3732,7 +3968,7 @@ async function walkDir(dir, baseDir) {
3732
3968
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
3733
3969
  continue;
3734
3970
  }
3735
- const fullPath = join5(dir, entry.name);
3971
+ const fullPath = join7(dir, entry.name);
3736
3972
  const relPath = relative4(baseDir, fullPath);
3737
3973
  if (entry.isDirectory()) {
3738
3974
  results.push(...await walkDir(fullPath, baseDir));
@@ -3765,7 +4001,7 @@ var globTool = {
3765
4001
  if (!args?.pattern || typeof args.pattern !== "string") {
3766
4002
  return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 pattern", error: "INVALID_ARGS" };
3767
4003
  }
3768
- const searchDir = args.directory ? isAbsolute2(args.directory) ? args.directory : join5(ctx.cwd, args.directory) : ctx.cwd;
4004
+ const searchDir = args.directory ? isAbsolute2(args.directory) ? args.directory : join7(ctx.cwd, args.directory) : ctx.cwd;
3769
4005
  const regex = globToRegex(args.pattern);
3770
4006
  try {
3771
4007
  const dirStat = await stat2(searchDir);
@@ -3805,7 +4041,7 @@ var globTool = {
3805
4041
 
3806
4042
  // src/tool/builtins/grep.ts
3807
4043
  import { readdir as readdir4, readFile as readFile10, stat as stat3 } from "fs/promises";
3808
- import { join as join6, relative as relative5, isAbsolute as isAbsolute3 } from "path";
4044
+ import { join as join8, relative as relative5, isAbsolute as isAbsolute3 } from "path";
3809
4045
  async function collectFiles(dir, extension, maxFiles = 200) {
3810
4046
  const results = [];
3811
4047
  let entries;
@@ -3819,7 +4055,7 @@ async function collectFiles(dir, extension, maxFiles = 200) {
3819
4055
  if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist")) {
3820
4056
  continue;
3821
4057
  }
3822
- const fullPath = join6(dir, entry.name);
4058
+ const fullPath = join8(dir, entry.name);
3823
4059
  if (entry.isDirectory()) {
3824
4060
  results.push(...await collectFiles(fullPath, extension, maxFiles - results.length));
3825
4061
  } else {
@@ -3866,7 +4102,7 @@ var grepTool = {
3866
4102
  if (!args?.pattern || typeof args.pattern !== "string") {
3867
4103
  return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 pattern", error: "INVALID_ARGS" };
3868
4104
  }
3869
- const searchDir = args.directory ? isAbsolute3(args.directory) ? args.directory : join6(ctx.cwd, args.directory) : ctx.cwd;
4105
+ const searchDir = args.directory ? isAbsolute3(args.directory) ? args.directory : join8(ctx.cwd, args.directory) : ctx.cwd;
3870
4106
  const maxFiles = args.max_files ?? 200;
3871
4107
  try {
3872
4108
  const flags = args.case_sensitive ? "g" : "gi";
@@ -3926,7 +4162,7 @@ var grepTool = {
3926
4162
 
3927
4163
  // src/tool/builtins/ls.ts
3928
4164
  import { readdir as readdir5, stat as stat4 } from "fs/promises";
3929
- import { join as join7, relative as relative6 } from "path";
4165
+ import { join as join9, relative as relative6 } from "path";
3930
4166
  var lsTool = {
3931
4167
  name: "ls",
3932
4168
  kind: "read" /* Read */,
@@ -3964,7 +4200,7 @@ var lsTool = {
3964
4200
  let sizeStr = "";
3965
4201
  if (typeMark === "FILE") {
3966
4202
  try {
3967
- const fileStat = await stat4(join7(dirPath, entry.name));
4203
+ const fileStat = await stat4(join9(dirPath, entry.name));
3968
4204
  if (fileStat.size < 1024) {
3969
4205
  sizeStr = `${fileStat.size}B`;
3970
4206
  } else if (fileStat.size < 1024 * 1024) {
@@ -4403,7 +4639,12 @@ function ChatSession({
4403
4639
  }
4404
4640
  }, []);
4405
4641
  const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => {
4406
- process.exit(0);
4642
+ const s = sessionRef.current;
4643
+ if (s) {
4644
+ void s.flushLog().finally(() => process.exit(0));
4645
+ } else {
4646
+ process.exit(0);
4647
+ }
4407
4648
  });
4408
4649
  useInput(
4409
4650
  useCallback2(
@@ -4803,6 +5044,7 @@ function ChatSession({
4803
5044
  const result = cmd.handler();
4804
5045
  switch (result.kind) {
4805
5046
  case "exit":
5047
+ await sessionRef.current?.flushLog();
4806
5048
  process.exit(0);
4807
5049
  return;
4808
5050
  case "clear":
@@ -4988,7 +5230,7 @@ function ChatSession({
4988
5230
  }, 2e3);
4989
5231
  }
4990
5232
  }
4991
- }, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls, skills, skillSelectIndex, getFilteredSkills, thinkingEnabled, thinkingEffort, responseFormat, toolChoice, activeModel, sessionMode]);
5233
+ }, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls, skills, skillSelectIndex, getFilteredSkills, thinkingEnabled, thinkingEffort, responseFormat, toolChoice, activeModel, sessionMode, isStreaming, rewinding]);
4992
5234
  useEffect3(() => {
4993
5235
  if (!isStreaming && externalCostTracker) {
4994
5236
  setTodayCost(externalCostTracker.todayTotalCost);
@@ -6449,9 +6691,9 @@ import { Box as Box13, Text as Text14, useInput as useInput5 } from "ink";
6449
6691
  import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6, useMemo as useMemo2 } from "react";
6450
6692
  import asciichart from "asciichart";
6451
6693
  import os from "os";
6452
- import { join as join8 } from "path";
6694
+ import { join as join10 } from "path";
6453
6695
  import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
6454
- var SETTINGS_PATH = join8(os.homedir(), ".dskcode", "settings.json");
6696
+ var SETTINGS_PATH = join10(os.homedir(), ".dskcode", "settings.json");
6455
6697
  function toFileUrl(p) {
6456
6698
  const norm = p.replace(/\\/g, "/");
6457
6699
  if (process.platform === "win32") {
@@ -6786,7 +7028,7 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
6786
7028
 
6787
7029
  // src/utils/scan-files.ts
6788
7030
  import { readdir as readdir6 } from "fs/promises";
6789
- import { join as join9, relative as relative7 } from "path";
7031
+ import { join as join11, relative as relative7 } from "path";
6790
7032
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
6791
7033
  "node_modules",
6792
7034
  ".git",
@@ -6851,12 +7093,12 @@ async function scanProjectFiles(baseDir, dir) {
6851
7093
  } else if (entry.isFile()) {
6852
7094
  const ext = name.slice(name.lastIndexOf(".")).toLowerCase();
6853
7095
  if (SOURCE_EXTS.has(ext)) {
6854
- files.push(relative7(baseDir, join9(currentDir, name)));
7096
+ files.push(relative7(baseDir, join11(currentDir, name)));
6855
7097
  }
6856
7098
  }
6857
7099
  }
6858
7100
  const nested = await Promise.all(
6859
- dirs.map((d) => scanProjectFiles(baseDir, join9(currentDir, d)))
7101
+ dirs.map((d) => scanProjectFiles(baseDir, join11(currentDir, d)))
6860
7102
  );
6861
7103
  for (const n of nested) {
6862
7104
  files.push(...n);
@@ -6944,7 +7186,7 @@ compdef _dskcode_completion dskcode`);
6944
7186
  program2.command("stock").description("\u67E5\u770B\u81EA\u9009\u80A1\u5B9E\u65F6\u884C\u60C5").argument("[codes...]", "\u80A1\u7968\u4EE3\u7801\uFF08\u7A7A\u683C\u5206\u9694\uFF09\uFF0C\u5982 513090 600519").action(async function(codes) {
6945
7187
  const _ctx = this.dskcodeCtx;
6946
7188
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
6947
- const globalConfigPath = join10(home, ".dskcode", "settings.json");
7189
+ const globalConfigPath = join12(home, ".dskcode", "settings.json");
6948
7190
  let globalConfigHasStock = false;
6949
7191
  try {
6950
7192
  const raw = await readFile11(globalConfigPath, "utf-8");
@@ -7114,7 +7356,10 @@ var sigintCount = 0;
7114
7356
  var sigintTimer = null;
7115
7357
  async function gracefulExit(code) {
7116
7358
  try {
7117
- await CostTracker.flushAll();
7359
+ await Promise.all([
7360
+ CostTracker.flushAll(),
7361
+ ConversationLogger.flushAll()
7362
+ ]);
7118
7363
  } catch {
7119
7364
  }
7120
7365
  process.exit(code);
@@ -7137,19 +7382,19 @@ process.on("SIGTERM", () => {
7137
7382
  var program = createCli();
7138
7383
  try {
7139
7384
  await program.parseAsync(process.argv);
7140
- await CostTracker.flushAll();
7385
+ await Promise.all([CostTracker.flushAll(), ConversationLogger.flushAll()]);
7141
7386
  } catch (err) {
7142
7387
  const error = err;
7143
7388
  if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
7144
- await CostTracker.flushAll();
7389
+ await Promise.all([CostTracker.flushAll(), ConversationLogger.flushAll()]);
7145
7390
  process.exit(error.exitCode ?? ExitCode.SUCCESS);
7146
7391
  }
7147
7392
  if (typeof error.exitCode === "number") {
7148
- await CostTracker.flushAll();
7393
+ await Promise.all([CostTracker.flushAll(), ConversationLogger.flushAll()]);
7149
7394
  process.exit(error.exitCode);
7150
7395
  }
7151
7396
  console.error(String(err));
7152
- await CostTracker.flushAll();
7397
+ await Promise.all([CostTracker.flushAll(), ConversationLogger.flushAll()]);
7153
7398
  process.exit(ExitCode.GENERAL_ERROR);
7154
7399
  }
7155
7400
  //# sourceMappingURL=index.js.map