deeper-cli 1.0.6 → 1.2.1

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/cli/index.js CHANGED
@@ -3285,7 +3285,7 @@ var init_RetryManager = __esm({
3285
3285
  this.baseDelayMs = baseDelayMs;
3286
3286
  this.maxDelayMs = maxDelayMs;
3287
3287
  }
3288
- async execute(fn, shouldRetry) {
3288
+ async execute(fn, shouldRetry, options) {
3289
3289
  let lastError;
3290
3290
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
3291
3291
  try {
@@ -3298,27 +3298,34 @@ var init_RetryManager = __esm({
3298
3298
  if (shouldRetry && !shouldRetry(lastError, attempt)) {
3299
3299
  break;
3300
3300
  }
3301
- const delay = Math.min(
3302
- this.baseDelayMs * Math.pow(2, attempt) + Math.random() * 1e3,
3303
- this.maxDelayMs
3304
- );
3301
+ if (!shouldRetry && this.isDefaultNonRetryable(lastError)) {
3302
+ break;
3303
+ }
3304
+ const delay = this.calculateDelay(attempt);
3305
+ if (options?.onRetry) {
3306
+ options.onRetry(lastError, attempt, delay);
3307
+ }
3305
3308
  await this.sleep(delay);
3306
3309
  }
3307
3310
  }
3308
3311
  throw lastError ?? new Error("Max retries exceeded");
3309
3312
  }
3310
3313
  withTimeout(fn, timeoutMs, signal) {
3314
+ const controller = new AbortController();
3311
3315
  return new Promise((resolve48, reject) => {
3312
3316
  const timer = setTimeout(() => {
3317
+ controller.abort();
3313
3318
  reject(new Error(`Request timed out after ${timeoutMs}ms`));
3314
3319
  }, timeoutMs);
3315
3320
  const onAbort = () => {
3316
3321
  clearTimeout(timer);
3322
+ controller.abort();
3317
3323
  reject(new Error("Request was aborted"));
3318
3324
  };
3319
3325
  if (signal) {
3320
3326
  if (signal.aborted) {
3321
3327
  clearTimeout(timer);
3328
+ controller.abort();
3322
3329
  reject(new Error("Request was aborted"));
3323
3330
  return;
3324
3331
  }
@@ -3339,8 +3346,35 @@ var init_RetryManager = __esm({
3339
3346
  });
3340
3347
  });
3341
3348
  }
3342
- sleep(ms) {
3343
- return new Promise((resolve48) => setTimeout(resolve48, ms));
3349
+ calculateDelay(attempt) {
3350
+ const exponentialDelay = this.baseDelayMs * Math.pow(2, attempt);
3351
+ const jitter = Math.random() * exponentialDelay;
3352
+ return Math.min(exponentialDelay + jitter, this.maxDelayMs);
3353
+ }
3354
+ isDefaultNonRetryable(error) {
3355
+ const msg = error.message.toLowerCase();
3356
+ if (msg.includes("400") && !msg.includes("429")) return true;
3357
+ if (msg.includes("401") || msg.includes("unauthorized")) return true;
3358
+ if (msg.includes("403") || msg.includes("forbidden")) return true;
3359
+ if (msg.includes("404") || msg.includes("not found")) return true;
3360
+ return false;
3361
+ }
3362
+ sleep(ms, signal) {
3363
+ return new Promise((resolve48, reject) => {
3364
+ const timer = setTimeout(resolve48, ms);
3365
+ if (signal) {
3366
+ const onAbort = () => {
3367
+ clearTimeout(timer);
3368
+ reject(new Error("Retry sleep was aborted"));
3369
+ };
3370
+ if (signal.aborted) {
3371
+ clearTimeout(timer);
3372
+ reject(new Error("Retry sleep was aborted"));
3373
+ return;
3374
+ }
3375
+ signal.addEventListener("abort", onAbort, { once: true });
3376
+ }
3377
+ });
3344
3378
  }
3345
3379
  };
3346
3380
  }
@@ -3356,21 +3390,31 @@ var init_StreamHandler = __esm({
3356
3390
  thinkingBuffer;
3357
3391
  toolCallBuffer;
3358
3392
  finished;
3393
+ lastYieldedIndex;
3359
3394
  constructor() {
3360
3395
  this.textBuffer = "";
3361
3396
  this.thinkingBuffer = "";
3362
3397
  this.toolCallBuffer = /* @__PURE__ */ new Map();
3363
3398
  this.finished = false;
3399
+ this.lastYieldedIndex = -1;
3364
3400
  }
3365
3401
  reset() {
3366
3402
  this.textBuffer = "";
3367
3403
  this.thinkingBuffer = "";
3368
3404
  this.toolCallBuffer.clear();
3369
3405
  this.finished = false;
3406
+ this.lastYieldedIndex = -1;
3370
3407
  }
3371
3408
  handleEvent(event, data) {
3409
+ if (event === "error") {
3410
+ return { type: "error", error: data || "SSE error event" };
3411
+ }
3372
3412
  if (event === "[DONE]" || data === "[DONE]") {
3413
+ const results = this.finalizePendingToolCalls();
3373
3414
  this.finished = true;
3415
+ if (results.length > 0) {
3416
+ return [...results, { type: "done" }];
3417
+ }
3374
3418
  return { type: "done" };
3375
3419
  }
3376
3420
  if (!data) {
@@ -3385,11 +3429,23 @@ var init_StreamHandler = __esm({
3385
3429
  const choice = choices[0];
3386
3430
  const delta = choice.delta;
3387
3431
  if (!delta) {
3432
+ const finishReason2 = choice.finish_reason;
3433
+ if (finishReason2 === "stop" || finishReason2 === "length" || finishReason2 === "tool_calls") {
3434
+ const results = this.finalizePendingToolCalls();
3435
+ this.finished = true;
3436
+ if (results.length > 0) {
3437
+ return [...results, { type: "done" }];
3438
+ }
3439
+ return { type: "done" };
3440
+ }
3388
3441
  return null;
3389
3442
  }
3390
3443
  if (delta.reasoning_content) {
3391
3444
  const thinkingChunk = delta.reasoning_content;
3392
3445
  this.thinkingBuffer += thinkingChunk;
3446
+ if (this.thinkingBuffer.length > 1e5) {
3447
+ this.thinkingBuffer = this.thinkingBuffer.slice(-8e4);
3448
+ }
3393
3449
  return {
3394
3450
  type: "thinking",
3395
3451
  content: thinkingChunk
@@ -3401,6 +3457,9 @@ var init_StreamHandler = __esm({
3401
3457
  if (delta.content) {
3402
3458
  const textChunk = delta.content;
3403
3459
  this.textBuffer += textChunk;
3460
+ if (this.textBuffer.length > 5e5) {
3461
+ this.textBuffer = this.textBuffer.slice(-4e5);
3462
+ }
3404
3463
  return {
3405
3464
  type: "text",
3406
3465
  content: textChunk
@@ -3408,7 +3467,11 @@ var init_StreamHandler = __esm({
3408
3467
  }
3409
3468
  const finishReason = choice.finish_reason;
3410
3469
  if (finishReason === "stop" || finishReason === "length" || finishReason === "tool_calls") {
3470
+ const results = this.finalizePendingToolCalls();
3411
3471
  this.finished = true;
3472
+ if (results.length > 0) {
3473
+ return [...results, { type: "done" }];
3474
+ }
3412
3475
  return { type: "done" };
3413
3476
  }
3414
3477
  } catch {
@@ -3427,10 +3490,16 @@ var init_StreamHandler = __esm({
3427
3490
  }
3428
3491
  getToolCalls() {
3429
3492
  const result = [];
3430
- for (let i = 0; i < this.toolCallBuffer.size; i++) {
3431
- const tc = this.toolCallBuffer.get(i);
3432
- if (tc) {
3433
- result.push(tc);
3493
+ const indices = [...this.toolCallBuffer.keys()].sort((a, b2) => a - b2);
3494
+ for (const idx of indices) {
3495
+ const pending = this.toolCallBuffer.get(idx);
3496
+ if (pending) {
3497
+ result.push({
3498
+ id: pending.id,
3499
+ name: pending.name,
3500
+ arguments: this.parseArgsStr(pending.argsStr),
3501
+ index: pending.index
3502
+ });
3434
3503
  }
3435
3504
  }
3436
3505
  return result;
@@ -3448,8 +3517,9 @@ var init_StreamHandler = __esm({
3448
3517
  this.toolCallBuffer.set(index, {
3449
3518
  id: id ?? "",
3450
3519
  name: fn?.name ?? "",
3451
- arguments: {},
3452
- index
3520
+ argsStr: "",
3521
+ index,
3522
+ started: false
3453
3523
  });
3454
3524
  }
3455
3525
  const existing = this.toolCallBuffer.get(index);
@@ -3460,24 +3530,71 @@ var init_StreamHandler = __esm({
3460
3530
  existing.name = fn.name;
3461
3531
  }
3462
3532
  if (fn?.arguments) {
3463
- try {
3464
- const argsStr = fn.arguments;
3465
- const parsed = JSON.parse(argsStr);
3466
- existing.arguments = { ...existing.arguments, ...parsed };
3467
- } catch {
3468
- existing.arguments = existing.arguments || {};
3533
+ existing.argsStr += fn.arguments;
3534
+ }
3535
+ if (!existing.started) {
3536
+ existing.started = true;
3537
+ if (this.lastYieldedIndex >= 0) {
3538
+ const prev = this.toolCallBuffer.get(this.lastYieldedIndex);
3539
+ if (prev && prev !== existing) {
3540
+ results.push({
3541
+ type: "tool_call_end",
3542
+ tool_call: {
3543
+ id: prev.id,
3544
+ name: prev.name,
3545
+ arguments: this.parseArgsStr(prev.argsStr),
3546
+ index: prev.index
3547
+ }
3548
+ });
3549
+ }
3469
3550
  }
3551
+ this.lastYieldedIndex = index;
3552
+ results.push({
3553
+ type: "tool_call_start",
3554
+ tool_call: { id: existing.id, name: existing.name, index: existing.index }
3555
+ });
3470
3556
  }
3471
- const entry = this.toolCallBuffer.get(index);
3472
- if (entry) {
3557
+ }
3558
+ return results;
3559
+ }
3560
+ finalizePendingToolCalls() {
3561
+ const results = [];
3562
+ if (this.lastYieldedIndex >= 0) {
3563
+ const last = this.toolCallBuffer.get(this.lastYieldedIndex);
3564
+ if (last) {
3473
3565
  results.push({
3474
- type: "tool_call",
3475
- tool_call: { ...entry, arguments: { ...entry.arguments } }
3566
+ type: "tool_call_end",
3567
+ tool_call: {
3568
+ id: last.id,
3569
+ name: last.name,
3570
+ arguments: this.parseArgsStr(last.argsStr),
3571
+ index: last.index
3572
+ }
3476
3573
  });
3477
3574
  }
3575
+ this.lastYieldedIndex = -1;
3478
3576
  }
3479
3577
  return results;
3480
3578
  }
3579
+ parseArgsStr(argsStr) {
3580
+ if (!argsStr) return {};
3581
+ try {
3582
+ return JSON.parse(argsStr);
3583
+ } catch {
3584
+ const trimmed = argsStr.trim();
3585
+ if (trimmed.startsWith("{") && !trimmed.endsWith("}")) {
3586
+ try {
3587
+ return JSON.parse(trimmed + "}");
3588
+ } catch {
3589
+ try {
3590
+ return JSON.parse(trimmed + "}}");
3591
+ } catch {
3592
+ }
3593
+ }
3594
+ }
3595
+ return {};
3596
+ }
3597
+ }
3481
3598
  };
3482
3599
  }
3483
3600
  });
@@ -3582,7 +3699,7 @@ ${data.stack ?? ""}`;
3582
3699
  });
3583
3700
 
3584
3701
  // src/model/DeepSeekClient.ts
3585
- var DEFAULT_TIMEOUT_MS, MAX_RETRIES, DeepSeekClient;
3702
+ var DEFAULT_TIMEOUT_MS, MAX_RETRIES, isRetryable, DeepSeekClient;
3586
3703
  var init_DeepSeekClient = __esm({
3587
3704
  "src/model/DeepSeekClient.ts"() {
3588
3705
  "use strict";
@@ -3591,23 +3708,31 @@ var init_DeepSeekClient = __esm({
3591
3708
  init_logger();
3592
3709
  DEFAULT_TIMEOUT_MS = 12e4;
3593
3710
  MAX_RETRIES = 3;
3711
+ isRetryable = (error, _attempt) => {
3712
+ const msg = error.message.toLowerCase();
3713
+ if (msg.includes("429") || msg.includes("rate limit")) return true;
3714
+ if (msg.includes("500") || msg.includes("502") || msg.includes("503") || msg.includes("504")) return true;
3715
+ if (msg.includes("timeout") || msg.includes("abort")) return _attempt < 2;
3716
+ if (msg.includes("econnreset") || msg.includes("econnrefused")) return true;
3717
+ return false;
3718
+ };
3594
3719
  DeepSeekClient = class {
3595
3720
  config;
3596
3721
  retryManager;
3597
3722
  constructor(config) {
3598
3723
  this.config = config;
3599
- this.retryManager = new RetryManager(config.maxTokens > 0 ? MAX_RETRIES : MAX_RETRIES);
3724
+ this.retryManager = new RetryManager(MAX_RETRIES);
3600
3725
  }
3601
3726
  async chat(messages, tools, overrides) {
3602
3727
  const cfg = this.mergeConfig(overrides);
3603
3728
  const body = this.buildRequestBody(messages, tools, cfg, false);
3604
3729
  const response = await this.retryManager.execute(async () => {
3605
- const result2 = await this.retryManager.withTimeout(
3730
+ return this.retryManager.withTimeout(
3606
3731
  () => this.makeRequest(cfg, body),
3607
- cfg.maxTokens > 0 ? DEFAULT_TIMEOUT_MS : DEFAULT_TIMEOUT_MS
3732
+ DEFAULT_TIMEOUT_MS,
3733
+ cfg.signal
3608
3734
  );
3609
- return result2;
3610
- }, this.shouldRetry);
3735
+ }, isRetryable);
3611
3736
  const data = await response.json();
3612
3737
  if (!data.choices || data.choices.length === 0) {
3613
3738
  throw new Error("No choices returned from API");
@@ -3626,7 +3751,7 @@ var init_DeepSeekClient = __esm({
3626
3751
  }));
3627
3752
  }
3628
3753
  if (message.reasoning_content) {
3629
- result.thinking = message.reasoning_content;
3754
+ result.reasoning_content = message.reasoning_content;
3630
3755
  }
3631
3756
  return result;
3632
3757
  }
@@ -3634,16 +3759,12 @@ var init_DeepSeekClient = __esm({
3634
3759
  const cfg = this.mergeConfig(overrides);
3635
3760
  const body = this.buildRequestBody(messages, tools, cfg, true);
3636
3761
  const response = await this.retryManager.execute(async () => {
3637
- const result = await this.retryManager.withTimeout(
3762
+ return this.retryManager.withTimeout(
3638
3763
  () => this.makeRequest(cfg, body),
3639
- DEFAULT_TIMEOUT_MS
3764
+ DEFAULT_TIMEOUT_MS,
3765
+ cfg.signal
3640
3766
  );
3641
- return result;
3642
- }, this.shouldRetry);
3643
- if (!response.ok) {
3644
- const errorBody = await response.text().catch(() => "");
3645
- throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorBody}`);
3646
- }
3767
+ }, isRetryable);
3647
3768
  if (!response.body) {
3648
3769
  throw new Error("Response body is empty");
3649
3770
  }
@@ -3725,6 +3846,12 @@ var init_DeepSeekClient = __esm({
3725
3846
  max_tokens: config.maxTokens,
3726
3847
  stream
3727
3848
  };
3849
+ if (config.think?.enabled) {
3850
+ body.thinking = {
3851
+ type: "enabled",
3852
+ budget_tokens: config.think.budgetTokens
3853
+ };
3854
+ }
3728
3855
  if (tools && tools.length > 0) {
3729
3856
  body.tools = tools.map((tool) => ({
3730
3857
  type: "function",
@@ -3741,7 +3868,7 @@ var init_DeepSeekClient = __esm({
3741
3868
  async makeRequest(config, body) {
3742
3869
  const url = `${config.baseUrl}/v1/chat/completions`;
3743
3870
  logger.debug("DeepSeek API request", { url, model: config.model });
3744
- const response = await fetch(url, {
3871
+ const fetchOpts = {
3745
3872
  method: "POST",
3746
3873
  headers: {
3747
3874
  "Content-Type": "application/json",
@@ -3749,7 +3876,11 @@ var init_DeepSeekClient = __esm({
3749
3876
  "Accept": "application/json"
3750
3877
  },
3751
3878
  body
3752
- });
3879
+ };
3880
+ if (config.signal) {
3881
+ fetchOpts.signal = config.signal;
3882
+ }
3883
+ const response = await fetch(url, fetchOpts);
3753
3884
  if (!response.ok) {
3754
3885
  const errorBody = await response.text().catch(() => "");
3755
3886
  logger.error("DeepSeek API error", {
@@ -3761,22 +3892,6 @@ var init_DeepSeekClient = __esm({
3761
3892
  }
3762
3893
  return response;
3763
3894
  }
3764
- shouldRetry(error, attempt) {
3765
- const message = error.message.toLowerCase();
3766
- if (message.includes("429") || message.includes("rate limit")) {
3767
- return true;
3768
- }
3769
- if (message.includes("5") && (message.includes("500") || message.includes("502") || message.includes("503") || message.includes("504"))) {
3770
- return true;
3771
- }
3772
- if (message.includes("timeout") || message.includes("abort")) {
3773
- return attempt < 2;
3774
- }
3775
- if (message.includes("econnreset") || message.includes("econnrefused")) {
3776
- return true;
3777
- }
3778
- return false;
3779
- }
3780
3895
  parseArguments(argsStr) {
3781
3896
  try {
3782
3897
  return JSON.parse(argsStr);
@@ -7274,9 +7389,9 @@ var init_format_code = __esm({
7274
7389
  return { success: false, error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`, output: "" };
7275
7390
  }
7276
7391
  try {
7277
- const { execSync: execSync19 } = await import("child_process");
7392
+ const { execSync: execSync20 } = await import("child_process");
7278
7393
  const ext = filePath.split(".").pop() ?? "ts";
7279
- execSync19(`npx prettier --write "${filePath}"`, {
7394
+ execSync20(`npx prettier --write "${filePath}"`, {
7280
7395
  encoding: "utf-8",
7281
7396
  timeout: 3e4,
7282
7397
  stdio: "pipe"
@@ -8462,25 +8577,25 @@ var init_db_restore = __esm({
8462
8577
  try {
8463
8578
  const engine = params.engine;
8464
8579
  const connection = params.connection;
8465
- const backupFile = params.backup_file;
8580
+ const backupFile2 = params.backup_file;
8466
8581
  let cmd = "";
8467
8582
  switch (engine) {
8468
8583
  case "sqlite":
8469
- cmd = `sqlite3 "${connection || "database.db"}" < "${backupFile}"`;
8584
+ cmd = `sqlite3 "${connection || "database.db"}" < "${backupFile2}"`;
8470
8585
  break;
8471
8586
  case "mysql":
8472
- cmd = `mysql ${connection || ""} < "${backupFile}"`;
8587
+ cmd = `mysql ${connection || ""} < "${backupFile2}"`;
8473
8588
  break;
8474
8589
  case "postgresql":
8475
- cmd = `psql ${connection || ""} < "${backupFile}"`;
8590
+ cmd = `psql ${connection || ""} < "${backupFile2}"`;
8476
8591
  break;
8477
8592
  default:
8478
8593
  return { success: false, error: `\u4E0D\u652F\u6301\u7684\u6570\u636E\u5E93\u5F15\u64CE: ${engine}`, output: "" };
8479
8594
  }
8480
8595
  try {
8481
8596
  const output = execSync9(cmd, { encoding: "utf-8", timeout: 12e4, stdio: "pipe" });
8482
- return { success: true, output: `\u6062\u590D\u5B8C\u6210: ${backupFile}
8483
- ${output || ""}`, metadata: { engine, backupFile } };
8597
+ return { success: true, output: `\u6062\u590D\u5B8C\u6210: ${backupFile2}
8598
+ ${output || ""}`, metadata: { engine, backupFile: backupFile2 } };
8484
8599
  } catch (err) {
8485
8600
  const e = err;
8486
8601
  return { success: false, error: `\u6062\u590D\u5931\u8D25: ${e.message || String(err)}`, output: "" };
@@ -10616,8 +10731,8 @@ var init_memory_store = __esm({
10616
10731
  if (!key) return { success: false, error: "delete \u9700\u8981 key \u53C2\u6570", output: "" };
10617
10732
  const memPath = join11(MEMORY_DIR, `${key}.json`);
10618
10733
  if (!existsSync51(memPath)) return { success: false, error: `\u8BB0\u5FC6\u4E0D\u5B58\u5728: ${key}`, output: "" };
10619
- const { unlinkSync: unlinkSync2 } = await import("fs");
10620
- unlinkSync2(memPath);
10734
+ const { unlinkSync: unlinkSync3 } = await import("fs");
10735
+ unlinkSync3(memPath);
10621
10736
  return { success: true, output: `\u8BB0\u5FC6\u5DF2\u5220\u9664: ${key}` };
10622
10737
  }
10623
10738
  case "list": {
@@ -10908,17 +11023,17 @@ var init_notify_user = __esm({
10908
11023
  const output = `${icons[level] || ""} [${level.toUpperCase()}] ${title}: ${message}`;
10909
11024
  try {
10910
11025
  if (process.platform === "win32") {
10911
- const { execSync: execSync19 } = await import("child_process");
10912
- execSync19(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; $notify = New-Object System.Windows.Forms.NotifyIcon; $notify.Icon = [System.Drawing.SystemIcons]::Information; $notify.Visible = $true; $notify.ShowBalloonTip(5000, '${title}', '${message}', [System.Windows.Forms.ToolTipIcon]::${level === "error" ? "Error" : "Info"}); Start-Sleep -Seconds 6; $notify.Dispose()"`, {
11026
+ const { execSync: execSync20 } = await import("child_process");
11027
+ execSync20(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; $notify = New-Object System.Windows.Forms.NotifyIcon; $notify.Icon = [System.Drawing.SystemIcons]::Information; $notify.Visible = $true; $notify.ShowBalloonTip(5000, '${title}', '${message}', [System.Windows.Forms.ToolTipIcon]::${level === "error" ? "Error" : "Info"}); Start-Sleep -Seconds 6; $notify.Dispose()"`, {
10913
11028
  timeout: 1e4,
10914
11029
  stdio: "ignore"
10915
11030
  });
10916
11031
  } else if (process.platform === "darwin") {
10917
- const { execSync: execSync19 } = await import("child_process");
10918
- execSync19(`osascript -e 'display notification "${message}" with title "${title}"'`, { stdio: "ignore" });
11032
+ const { execSync: execSync20 } = await import("child_process");
11033
+ execSync20(`osascript -e 'display notification "${message}" with title "${title}"'`, { stdio: "ignore" });
10919
11034
  } else {
10920
- const { execSync: execSync19 } = await import("child_process");
10921
- execSync19(`notify-send "${title}" "${message}"`, { stdio: "ignore" });
11035
+ const { execSync: execSync20 } = await import("child_process");
11036
+ execSync20(`notify-send "${title}" "${message}"`, { stdio: "ignore" });
10922
11037
  }
10923
11038
  return { success: true, output, metadata: { title, level, notified: true } };
10924
11039
  } catch {
@@ -11259,8 +11374,28 @@ __export(chat_repl_exports, {
11259
11374
  });
11260
11375
  import readline from "readline";
11261
11376
  import process5 from "process";
11262
- import { existsSync as existsSync53, mkdirSync as mkdirSync15, readFileSync as readFileSync37, writeFileSync as writeFileSync14, readdirSync as readdirSync4, statSync as statSync12 } from "fs";
11263
- import { join as join12 } from "path";
11377
+ import { existsSync as existsSync53, mkdirSync as mkdirSync15, readFileSync as readFileSync37, writeFileSync as writeFileSync14, readdirSync as readdirSync4, statSync as statSync12, copyFileSync as copyFileSync2, unlinkSync as unlinkSync2 } from "fs";
11378
+ import { join as join12, relative as relative6 } from "path";
11379
+ import { execSync as execSync19 } from "child_process";
11380
+ function backupFile(filePath) {
11381
+ if (!existsSync53(filePath)) return;
11382
+ try {
11383
+ if (!existsSync53(BACKUP_DIR)) mkdirSync15(BACKUP_DIR, { recursive: true });
11384
+ const rel = relative6(process5.cwd(), filePath).replace(/[/\\]/g, "_");
11385
+ const ts = Date.now();
11386
+ const backupPath = join12(BACKUP_DIR, `${ts}_${rel}`);
11387
+ copyFileSync2(filePath, backupPath);
11388
+ fileBackupStack.push({ path: filePath, backupPath, timestamp: ts });
11389
+ if (fileBackupStack.length > MAX_BACKUPS) {
11390
+ const old = fileBackupStack.shift();
11391
+ try {
11392
+ unlinkSync2(old.backupPath);
11393
+ } catch {
11394
+ }
11395
+ }
11396
+ } catch {
11397
+ }
11398
+ }
11264
11399
  function getSkillSystemPrompt() {
11265
11400
  if (!skillEngine) return "";
11266
11401
  return skillEngine.getSystemPrompt();
@@ -11285,7 +11420,7 @@ async function startRepl(opts) {
11285
11420
  let curTc = null;
11286
11421
  let se = null;
11287
11422
  try {
11288
- const stream = await callApi(opts, msgs, tds, 0, 131072);
11423
+ const stream = await callApi(opts, msgs, tds, 131072);
11289
11424
  GS.api++;
11290
11425
  ce = 0;
11291
11426
  for await (const chunk of stream) {
@@ -11297,10 +11432,11 @@ async function startRepl(opts) {
11297
11432
  const tc = chunk.tool_call;
11298
11433
  if (tc) curTc = { id: tc.id, name: tc.name, argsStr: "" };
11299
11434
  }
11300
- if (chunk.type === "tool_call_args" && curTc) curTc.argsStr += chunk.content || "";
11301
11435
  if (chunk.type === "tool_call_end" && curTc) {
11436
+ const tcData = chunk.tool_call;
11302
11437
  try {
11303
- tcs.push({ id: curTc.id, name: curTc.name, args: JSON.parse(curTc.argsStr || "{}") });
11438
+ const args = tcData?.arguments || JSON.parse(curTc.argsStr || "{}");
11439
+ tcs.push({ id: curTc.id, name: curTc.name, args });
11304
11440
  } catch {
11305
11441
  tcs.push({ id: curTc.id, name: curTc.name, args: {} });
11306
11442
  }
@@ -11337,7 +11473,11 @@ async function startRepl(opts) {
11337
11473
  continue;
11338
11474
  }
11339
11475
  try {
11340
- const r2 = await tool.execute(tc.args, new AbortController().signal);
11476
+ const ac = new AbortController();
11477
+ const timeout = TOOL_TIMEOUT_MAP[tc.name] || DEFAULT_TOOL_TIMEOUT;
11478
+ const timer = setTimeout(() => ac.abort(), timeout);
11479
+ const r2 = await tool.execute(tc.args, ac.signal);
11480
+ clearTimeout(timer);
11341
11481
  const txt = sanitize(r2.output || "").slice(0, TOOL_RESULT_MAX);
11342
11482
  lh.push({ role: "tool", content: r2.success ? txt : `Error: ${r2.error}`, tool_call_id: tc.id });
11343
11483
  GS.tc++;
@@ -11476,6 +11616,12 @@ ${prevSummary.slice(0, 800)}` });
11476
11616
  return ok;
11477
11617
  };
11478
11618
  const onSigint = () => {
11619
+ if (currentAbortController) {
11620
+ currentAbortController.abort();
11621
+ currentAbortController = null;
11622
+ O(y("\n \u26A1 \u5DF2\u4E2D\u65AD\u5F53\u524D\u8BF7\u6C42\n"));
11623
+ return;
11624
+ }
11479
11625
  if (resolveLine) {
11480
11626
  const cb = resolveLine;
11481
11627
  resolveLine = null;
@@ -11495,7 +11641,7 @@ ${prevSummary.slice(0, 800)}` });
11495
11641
  };
11496
11642
  rl.on("SIGINT", onSigint);
11497
11643
  const onUCE = (err) => {
11498
- if (!err.message?.includes("readline")) O(r(`
11644
+ if (!err.message?.includes("readline") && !err.message?.includes("abort")) O(r(`
11499
11645
  \u26A0 ${err.message}`) + "\n");
11500
11646
  };
11501
11647
  process5.on("uncaughtException", onUCE);
@@ -11532,6 +11678,12 @@ ${prevSummary.slice(0, 800)}` });
11532
11678
  O(G(" /mcp") + G(" MCP\u670D\u52A1\u5668") + "\n");
11533
11679
  O(G(" /plan") + G(" <\u4EFB\u52A1> \u5148\u51FA\u65B9\u6848") + "\n");
11534
11680
  O(G(" /spec") + G(" <\u4EFB\u52A1> \u5148\u51FA\u89C4\u683C") + "\n");
11681
+ O(G(" /review") + G(" <\u8DEF\u5F84> \u4EE3\u7801\u5BA1\u67E5") + "\n");
11682
+ O(G(" /fix") + G(" [\u76EE\u6807] \u81EA\u52A8\u4FEE\u590D") + "\n");
11683
+ O(G(" /commit") + G(" \u667A\u80FD\u63D0\u4EA4") + "\n");
11684
+ O(G(" /analyze") + G(" [\u8DEF\u5F84] \u9879\u76EE\u5206\u6790") + "\n");
11685
+ O(G(" /diff") + G(" <\u6587\u4EF6> \u53D8\u66F4\u9884\u89C8") + "\n");
11686
+ O(G(" /undo") + G(" \u64A4\u9500\u64CD\u4F5C") + "\n");
11535
11687
  O(G(" /status") + G(" \u5F53\u524D\u72B6\u6001") + "\n");
11536
11688
  O(G(" /model") + G(" \u6A21\u578B\u8BBE\u7F6E") + "\n");
11537
11689
  O(G(" /config") + G(" \u914D\u7F6E\u7BA1\u7406") + "\n");
@@ -11648,7 +11800,8 @@ ${prevSummary.slice(0, 800)}` });
11648
11800
  if (cmd === "/help") {
11649
11801
  O(c(" /help /clear /quit /save [name] /load|resume [name] /sessions\n"));
11650
11802
  O(c(" /tools [cat] /stats /memory /tasks /model /config /cwd /export /init /mcp /rules\n"));
11651
- O(c(" /plan <\u4EFB\u52A1> /spec <\u4EFB\u52A1> /status\n\n"));
11803
+ O(c(" /plan <\u4EFB\u52A1> /spec <\u4EFB\u52A1> /review <\u8DEF\u5F84> /fix [\u76EE\u6807]\n"));
11804
+ O(c(" /commit /analyze [\u8DEF\u5F84] /diff <\u6587\u4EF6> /undo /status\n\n"));
11652
11805
  continue;
11653
11806
  }
11654
11807
  if (cmd === "/plan") {
@@ -11692,6 +11845,178 @@ ${prevSummary.slice(0, 800)}` });
11692
11845
  await runLoop(opts, history, tools, sfdefs, confirm);
11693
11846
  continue;
11694
11847
  }
11848
+ if (cmd === "/review") {
11849
+ const target = arg || ".";
11850
+ O(b(c(" Review Mode")) + G(` \u2022 ${target}`) + "\n\n");
11851
+ history.push({ role: "system", content: `[Review Mode]
11852
+ \u4F60\u662F\u4E00\u4F4D\u8D44\u6DF1\u4EE3\u7801\u5BA1\u67E5\u4E13\u5BB6\u3002\u8BF7\u5BF9\u6307\u5B9A\u4EE3\u7801\u8FDB\u884C\u5168\u9762\u5BA1\u67E5\uFF1A
11853
+
11854
+ 1. \u5148\u7528 read_file / glob_find / grep_search \u8BFB\u53D6\u76F8\u5173\u4EE3\u7801
11855
+ 2. \u4ECE\u4EE5\u4E0B\u7EF4\u5EA6\u9010\u9879\u5BA1\u67E5\u5E76\u8BC4\u5206\uFF081-10\uFF09\uFF1A
11856
+ - \u4EE3\u7801\u8D28\u91CF\uFF1A\u53EF\u8BFB\u6027\u3001\u547D\u540D\u3001\u7ED3\u6784
11857
+ - \u5B89\u5168\u6027\uFF1A\u6CE8\u5165\u3001XSS\u3001\u5BC6\u94A5\u6CC4\u9732\u3001\u6743\u9650\u95EE\u9898
11858
+ - \u6027\u80FD\uFF1AN+1\u67E5\u8BE2\u3001\u5185\u5B58\u6CC4\u6F0F\u3001\u4E0D\u5FC5\u8981\u7684\u8BA1\u7B97
11859
+ - \u53EF\u7EF4\u62A4\u6027\uFF1A\u8026\u5408\u5EA6\u3001\u91CD\u590D\u4EE3\u7801\u3001\u6D4B\u8BD5\u8986\u76D6
11860
+ - \u6700\u4F73\u5B9E\u8DF5\uFF1A\u8BBE\u8BA1\u6A21\u5F0F\u3001SOLID\u539F\u5219\u3001\u9519\u8BEF\u5904\u7406
11861
+ 3. \u5BF9\u6BCF\u4E2A\u95EE\u9898\u7ED9\u51FA\uFF1A\u6587\u4EF6\u8DEF\u5F84\u3001\u884C\u53F7\u8303\u56F4\u3001\u95EE\u9898\u63CF\u8FF0\u3001\u4FEE\u590D\u5EFA\u8BAE\u3001\u4E25\u91CD\u7A0B\u5EA6(\u{1F534}\u9AD8/\u{1F7E1}\u4E2D/\u{1F7E2}\u4F4E)
11862
+ 4. \u6700\u540E\u7ED9\u51FA\u603B\u4F53\u8BC4\u5206\u548C\u6539\u8FDB\u4F18\u5148\u7EA7\u5217\u8868
11863
+
11864
+ \u5BA1\u67E5\u76EE\u6807\uFF1A${target}` });
11865
+ history.push({ role: "user", content: `\u5BA1\u67E5 ${target} \u7684\u4EE3\u7801\u8D28\u91CF` });
11866
+ const rvdefs = toolsToDefs(tools);
11867
+ await runLoop(opts, history, tools, rvdefs, confirm);
11868
+ continue;
11869
+ }
11870
+ if (cmd === "/commit") {
11871
+ O(b(c(" Commit Mode")) + G(" \u2022 \u667A\u80FD\u63D0\u4EA4") + "\n\n");
11872
+ try {
11873
+ const statusOut = execSync19("git status --porcelain", { encoding: "utf-8", timeout: 1e4, cwd: process5.cwd() });
11874
+ if (!statusOut.trim()) {
11875
+ O(y(" \u6CA1\u6709\u672A\u63D0\u4EA4\u7684\u53D8\u66F4\n\n"));
11876
+ continue;
11877
+ }
11878
+ const diffOut = execSync19("git diff --stat", { encoding: "utf-8", timeout: 15e3, cwd: process5.cwd() });
11879
+ const stagedOut = execSync19("git diff --cached --stat", { encoding: "utf-8", timeout: 15e3, cwd: process5.cwd() });
11880
+ const files = statusOut.trim().split("\n").filter(Boolean);
11881
+ O(G(` ${files.length} \u4E2A\u6587\u4EF6\u53D8\u66F4:
11882
+ `));
11883
+ for (const f of files.slice(0, 20)) {
11884
+ const status = f.slice(0, 2).trim();
11885
+ const path = f.slice(3);
11886
+ const icon = status === "M" ? y("M") : status === "A" ? g("A") : status === "D" ? r("D") : status === "?" ? G("?") : c(status);
11887
+ O(G(` ${icon} ${path.slice(0, 60)}`) + "\n");
11888
+ }
11889
+ if (files.length > 20) O(G(` \u2026\u8FD8\u6709 ${files.length - 20} \u4E2A`) + "\n");
11890
+ O("\n");
11891
+ history.push({ role: "system", content: `[Commit Mode]
11892
+ \u5206\u6790 git \u53D8\u66F4\u5E76\u751F\u6210\u63D0\u4EA4\u4FE1\u606F\uFF1A
11893
+ 1. \u5148\u7528 run_command \u6267\u884C git diff \u67E5\u770B\u5177\u4F53\u53D8\u66F4\u5185\u5BB9
11894
+ 2. \u5206\u6790\u53D8\u66F4\u7C7B\u578B\u548C\u8303\u56F4\uFF0C\u751F\u6210\u7B26\u5408 Conventional Commits \u89C4\u8303\u7684\u63D0\u4EA4\u4FE1\u606F
11895
+ 3. \u683C\u5F0F: type(scope): description
11896
+ - type: feat/fix/refactor/docs/style/test/chore/perf
11897
+ - scope: \u53EF\u9009\uFF0C\u5F71\u54CD\u7684\u6A21\u5757
11898
+ 4. \u7528 run_command \u6267\u884C git add \u548C git commit
11899
+ 5. \u5982\u679C\u53D8\u66F4\u8F83\u591A\uFF0C\u5EFA\u8BAE\u5206\u591A\u6B21\u63D0\u4EA4
11900
+
11901
+ \u5F53\u524D\u53D8\u66F4\u7EDF\u8BA1\uFF1A
11902
+ ${diffOut || stagedOut || "\u65E0staged\u53D8\u66F4"}` });
11903
+ history.push({ role: "user", content: "\u5206\u6790\u53D8\u66F4\u5E76\u63D0\u4EA4" });
11904
+ const cmdefs = toolsToDefs(tools);
11905
+ await runLoop(opts, history, tools, cmdefs, confirm);
11906
+ } catch (e) {
11907
+ O(r(` Git \u64CD\u4F5C\u5931\u8D25: ${e instanceof Error ? e.message.slice(0, 80) : String(e).slice(0, 80)}
11908
+
11909
+ `));
11910
+ }
11911
+ continue;
11912
+ }
11913
+ if (cmd === "/analyze") {
11914
+ const target = arg || process5.cwd();
11915
+ O(b(c(" Analyze Mode")) + G(` \u2022 ${target}`) + "\n\n");
11916
+ history.push({ role: "system", content: `[Analyze Mode]
11917
+ \u4F60\u662F\u4E00\u4F4D\u9879\u76EE\u67B6\u6784\u5206\u6790\u5E08\u3002\u8BF7\u5BF9\u9879\u76EE\u8FDB\u884C\u5168\u9762\u5206\u6790\uFF1A
11918
+
11919
+ 1. \u7528 glob_find \u626B\u63CF\u9879\u76EE\u76EE\u5F55\u7ED3\u6784
11920
+ 2. \u7528 read_file \u8BFB\u53D6 package.json / tsconfig.json / Cargo.toml \u7B49\u914D\u7F6E
11921
+ 3. \u7528 grep_search / codebase_search \u5206\u6790\u4EE3\u7801\u6A21\u5F0F
11922
+ 4. \u8F93\u51FA\u4EE5\u4E0B\u5206\u6790\u62A5\u544A\uFF1A
11923
+ - \u{1F4C1} \u9879\u76EE\u7ED3\u6784\u6811\uFF08\u5173\u952E\u76EE\u5F55\u548C\u6587\u4EF6\uFF09
11924
+ - \u{1F6E0} \u6280\u672F\u6808\u8BC6\u522B\uFF08\u8BED\u8A00\u3001\u6846\u67B6\u3001\u5E93\u3001\u5DE5\u5177\u94FE\uFF09
11925
+ - \u{1F4CA} \u4EE3\u7801\u7EDF\u8BA1\uFF08\u6587\u4EF6\u6570\u3001\u4EE3\u7801\u884C\u6570\u4F30\u7B97\u3001\u5404\u8BED\u8A00\u5360\u6BD4\uFF09
11926
+ - \u{1F3D7}\uFE0F \u67B6\u6784\u6A21\u5F0F\uFF08MVC/\u5FAE\u670D\u52A1/\u5355\u4F53/\u63D2\u4EF6\u5F0F\u7B49\uFF09
11927
+ - \u{1F517} \u4F9D\u8D56\u5173\u7CFB\uFF08\u6838\u5FC3\u4F9D\u8D56\u3001\u5F00\u53D1\u4F9D\u8D56\u3001\u7248\u672C\u98CE\u9669\uFF09
11928
+ - \u26A0\uFE0F \u6F5C\u5728\u95EE\u9898\uFF08\u8FC7\u65F6\u4F9D\u8D56\u3001\u5B89\u5168\u98CE\u9669\u3001\u4EE3\u7801\u5F02\u5473\uFF09
11929
+ - \u{1F4A1} \u6539\u8FDB\u5EFA\u8BAE\uFF08\u4F18\u5148\u7EA7\u6392\u5E8F\uFF09
11930
+
11931
+ \u5206\u6790\u76EE\u6807\uFF1A${target}` });
11932
+ history.push({ role: "user", content: `\u5206\u6790\u9879\u76EE ${target}` });
11933
+ const andefs = toolsToDefs(tools);
11934
+ await runLoop(opts, history, tools, andefs, confirm);
11935
+ continue;
11936
+ }
11937
+ if (cmd === "/fix") {
11938
+ const target = arg || "";
11939
+ O(b(c(" Fix Mode")) + G(" \u2022 \u81EA\u52A8\u4FEE\u590D") + "\n\n");
11940
+ history.push({ role: "system", content: `[Fix Mode]
11941
+ \u4F60\u662F\u4E00\u4F4D\u81EA\u52A8\u4FEE\u590D\u5DE5\u7A0B\u5E08\u3002\u8BF7\u6267\u884C\u4EE5\u4E0B\u6D41\u7A0B\uFF1A
11942
+
11943
+ 1. \u5148\u7528 run_command \u8FD0\u884C\u6784\u5EFA\u547D\u4EE4\uFF08\u5982 npm run build, tsc --noEmit, cargo check \u7B49\uFF09
11944
+ 2. \u5982\u679C\u6784\u5EFA\u6210\u529F\uFF0C\u8FD0\u884C\u6D4B\u8BD5\uFF08npm test, cargo test \u7B49\uFF09
11945
+ 3. \u5982\u679C\u6709\u9519\u8BEF\uFF1A
11946
+ a. \u4ED4\u7EC6\u5206\u6790\u9519\u8BEF\u4FE1\u606F\uFF0C\u5B9A\u4F4D\u5230\u5177\u4F53\u6587\u4EF6\u548C\u884C\u53F7
11947
+ b. \u7528 read_file \u8BFB\u53D6\u51FA\u9519\u6587\u4EF6\u7684\u76F8\u5173\u4EE3\u7801
11948
+ c. \u7406\u89E3\u9519\u8BEF\u6839\u56E0\uFF0C\u5236\u5B9A\u4FEE\u590D\u65B9\u6848
11949
+ d. \u7528 edit_file \u7CBE\u786E\u4FEE\u590D\uFF08\u4F18\u5148\u7528 edit \u800C\u975E\u91CD\u5199\u6574\u4E2A\u6587\u4EF6\uFF09
11950
+ e. \u91CD\u65B0\u8FD0\u884C\u6784\u5EFA\u9A8C\u8BC1\u4FEE\u590D
11951
+ f. \u91CD\u590D\u76F4\u5230\u6240\u6709\u9519\u8BEF\u6E05\u9664
11952
+ 4. \u5982\u679C\u6D4B\u8BD5\u5931\u8D25\uFF0C\u540C\u6837\u5206\u6790\u5E76\u4FEE\u590D
11953
+ 5. \u6700\u591A\u5C1D\u8BD5\u4FEE\u590D 5 \u8F6E\uFF0C\u907F\u514D\u65E0\u9650\u5FAA\u73AF
11954
+ 6. \u6BCF\u6B21\u4FEE\u590D\u540E\u90FD\u91CD\u65B0\u9A8C\u8BC1
11955
+
11956
+ ${target ? `\u4FEE\u590D\u76EE\u6807\uFF1A${target}
11957
+ ` : "\u81EA\u52A8\u68C0\u6D4B\u5E76\u4FEE\u590D\u6240\u6709\u6784\u5EFA/\u6D4B\u8BD5\u9519\u8BEF"}` });
11958
+ history.push({ role: "user", content: target || "\u68C0\u6D4B\u5E76\u4FEE\u590D\u9879\u76EE\u9519\u8BEF" });
11959
+ const fxdefs = toolsToDefs(tools);
11960
+ await runLoop(opts, history, tools, fxdefs, confirm);
11961
+ continue;
11962
+ }
11963
+ if (cmd === "/diff") {
11964
+ if (!arg) {
11965
+ O(y(" \u7528\u6CD5: /diff <\u6587\u4EF6\u8DEF\u5F84>\n \u663E\u793A\u6587\u4EF6\u6700\u8FD1\u7684\u53D8\u66F4\n\n"));
11966
+ continue;
11967
+ }
11968
+ const filePath = arg.startsWith("/") || arg.startsWith("\\") || arg.length > 2 ? arg : join12(process5.cwd(), arg);
11969
+ if (fileBackupStack.length === 0) {
11970
+ O(y(" \u6CA1\u6709\u53EF\u7528\u7684\u5907\u4EFD\u8BB0\u5F55\n\n"));
11971
+ continue;
11972
+ }
11973
+ const backups = fileBackupStack.filter((b2) => b2.path === filePath).reverse();
11974
+ if (backups.length === 0) {
11975
+ const allBackups = [...fileBackupStack].reverse();
11976
+ O(G(" \u6700\u8FD1\u7684\u6587\u4EF6\u53D8\u66F4:\n"));
11977
+ for (const b2 of allBackups.slice(0, 10)) {
11978
+ const rel = relative6(process5.cwd(), b2.path);
11979
+ const time = new Date(b2.timestamp).toLocaleTimeString();
11980
+ O(G(` ${time} ${rel.slice(0, 50)}`) + "\n");
11981
+ }
11982
+ O("\n");
11983
+ continue;
11984
+ }
11985
+ const latest = backups[0];
11986
+ try {
11987
+ const currentContent = existsSync53(filePath) ? readFileSync37(filePath, "utf-8") : "(\u6587\u4EF6\u5DF2\u5220\u9664)";
11988
+ const backupContent = readFileSync37(latest.backupPath, "utf-8");
11989
+ const diffResult = computeSimpleDiff(backupContent, currentContent, filePath);
11990
+ O(b(c(" Diff")) + G(` \u2022 ${relative6(process5.cwd(), filePath)}`) + "\n\n");
11991
+ O(diffResult + "\n\n");
11992
+ } catch {
11993
+ O(y(" \u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6\u8FDB\u884C\u5BF9\u6BD4\n\n"));
11994
+ }
11995
+ continue;
11996
+ }
11997
+ if (cmd === "/undo") {
11998
+ if (fileBackupStack.length === 0) {
11999
+ O(y(" \u6CA1\u6709\u53EF\u64A4\u9500\u7684\u64CD\u4F5C\n\n"));
12000
+ continue;
12001
+ }
12002
+ const last = fileBackupStack[fileBackupStack.length - 1];
12003
+ try {
12004
+ const rel = relative6(process5.cwd(), last.path);
12005
+ copyFileSync2(last.backupPath, last.path);
12006
+ fileBackupStack.pop();
12007
+ try {
12008
+ unlinkSync2(last.backupPath);
12009
+ } catch {
12010
+ }
12011
+ O(g(" \u2713 \u5DF2\u64A4\u9500") + G(` ${rel}`) + "\n");
12012
+ O(G(" \u63D0\u793A: \u53EF\u7EE7\u7EED /undo \u64A4\u9500\u66F4\u591A\u64CD\u4F5C\n\n"));
12013
+ } catch (e) {
12014
+ O(r(` \u64A4\u9500\u5931\u8D25: ${e instanceof Error ? e.message.slice(0, 60) : String(e).slice(0, 60)}
12015
+
12016
+ `));
12017
+ }
12018
+ continue;
12019
+ }
11695
12020
  O(G(` \u672A\u77E5\u547D\u4EE4: ${cmd} (\u8F93\u5165 /help \u67E5\u770B\u5E2E\u52A9)
11696
12021
 
11697
12022
  `));
@@ -11761,8 +12086,9 @@ async function runLoop(opts, history, tools, toolDefs, confirm) {
11761
12086
  thinkAnimIv = null;
11762
12087
  }
11763
12088
  };
12089
+ currentAbortController = new AbortController();
11764
12090
  try {
11765
- const stream = await callApi(opts, msgs, toolDefs, 0, amax);
12091
+ const stream = await callApi(opts, msgs, toolDefs, amax, currentAbortController.signal);
11766
12092
  GS.api++;
11767
12093
  ce = 0;
11768
12094
  const cols = process5.stdout.columns || 80;
@@ -11801,10 +12127,11 @@ async function runLoop(opts, history, tools, toolDefs, confirm) {
11801
12127
  const tc = chunk.tool_call;
11802
12128
  if (tc) curTc = { id: tc.id, name: tc.name, argsStr: "" };
11803
12129
  }
11804
- if (chunk.type === "tool_call_args" && curTc) curTc.argsStr += chunk.content || "";
11805
12130
  if (chunk.type === "tool_call_end" && curTc) {
12131
+ const tcData = chunk.tool_call;
11806
12132
  try {
11807
- tcs.push({ id: curTc.id, name: curTc.name, args: JSON.parse(curTc.argsStr || "{}") });
12133
+ const args = tcData?.arguments || JSON.parse(curTc.argsStr || "{}");
12134
+ tcs.push({ id: curTc.id, name: curTc.name, args });
11808
12135
  } catch {
11809
12136
  tcs.push({ id: curTc.id, name: curTc.name, args: {} });
11810
12137
  }
@@ -11818,8 +12145,15 @@ async function runLoop(opts, history, tools, toolDefs, confirm) {
11818
12145
  }
11819
12146
  } catch (e) {
11820
12147
  stopStreamAnim();
11821
- se = e instanceof Error ? e.message : String(e);
12148
+ const errMsg = e instanceof Error ? e.message : String(e);
12149
+ if (errMsg.includes("abort") || errMsg.includes("cancel")) {
12150
+ se = null;
12151
+ O(y("\n \u26A1 \u8BF7\u6C42\u5DF2\u53D6\u6D88\n"));
12152
+ } else {
12153
+ se = errMsg;
12154
+ }
11822
12155
  }
12156
+ currentAbortController = null;
11823
12157
  stopStreamAnim();
11824
12158
  Oflush();
11825
12159
  const remaining = md.flush();
@@ -11955,14 +12289,19 @@ async function execTool(tc, tools, opts, confirm) {
11955
12289
  execAnimIv = setInterval(() => {
11956
12290
  rawWrite("\r" + thinkingAnimAt(toolName, toolStart));
11957
12291
  }, 80);
11958
- const execed = Promise.race([
11959
- tool.execute(tc.args, new AbortController().signal),
11960
- new Promise((_, rej) => setTimeout(() => rej(new Error("\u5DE5\u5177\u8D85\u65F6 (30s)")), 3e4))
11961
- ]);
12292
+ const timeout = TOOL_TIMEOUT_MAP[tc.name] || DEFAULT_TOOL_TIMEOUT;
12293
+ const ac = new AbortController();
12294
+ const timer = setTimeout(() => ac.abort(), timeout);
12295
+ if (tc.name === "write_file" || tc.name === "edit_file") {
12296
+ const fp = tc.args.file_path || tc.args.path || tc.args.file || "";
12297
+ if (fp) backupFile(fp);
12298
+ }
11962
12299
  let result;
11963
12300
  try {
11964
- result = await execed;
12301
+ result = await tool.execute(tc.args, ac.signal);
12302
+ clearTimeout(timer);
11965
12303
  } catch (e) {
12304
+ clearTimeout(timer);
11966
12305
  result = { success: false, error: e.message, output: "" };
11967
12306
  }
11968
12307
  stopToolAnim();
@@ -12027,24 +12366,57 @@ async function execTool(tc, tools, opts, confirm) {
12027
12366
  }
12028
12367
  function buildMsgs(history) {
12029
12368
  const r2 = [];
12030
- let sysContent = `You are DeeperCode V4-Pro, a full-stack coding agent.
12031
- Rules:
12032
- 1. Read before write. Use tools to inspect project state first.
12033
- 2. Write COMPLETE files. No placeholders like "// ..." or "rest of code".
12034
- 3. One write_file = one complete file. Batch independent writes.
12035
- 4. Use todo_manager for multi-step tasks. Update status as you progress.
12036
- 5. Keep reasoning brief. Prefer action over explanation.
12037
- 6. cwd=${process5.cwd()} plat=${process5.platform}
12038
- 7. Respond in Chinese when user uses Chinese.`;
12369
+ let sysContent = `You are DeeperCode V4-Pro, an elite full-stack AI coding agent with autonomous execution capability.
12370
+
12371
+ ## Core Principles
12372
+ 1. READ BEFORE WRITE \u2014 Always inspect project state before making changes.
12373
+ 2. COMPLETE CODE \u2014 Write full files. Never use placeholders like "// ..." or "rest of code".
12374
+ 3. ONE CALL = ONE FILE \u2014 Each write_file contains exactly one complete file. Batch independent writes.
12375
+ 4. PLAN WITH TODOS \u2014 Use todo_manager for multi-step tasks. Update status as you progress.
12376
+ 5. ACT OVER EXPLAIN \u2014 Keep reasoning brief. Prefer tool calls over lengthy explanations.
12377
+ 6. VERIFY AFTER WRITE \u2014 After writing code, run type checks, linters, or tests to confirm correctness.
12378
+ 7. FIX ERRORS PROACTIVELY \u2014 If a tool returns an error, diagnose and fix it immediately.
12379
+ 8. PARALLEL WHEN POSSIBLE \u2014 Execute independent tool calls simultaneously for efficiency.
12380
+
12381
+ ## Smart Editing Strategy
12382
+ - For EXISTING files, prefer edit_file over write_file to minimize diff size.
12383
+ - For NEW files, use write_file with complete content.
12384
+ - Before editing, read the file first to understand its current state.
12385
+ - Make targeted, minimal edits rather than rewriting entire files.
12386
+
12387
+ ## Auto-Verification Loop
12388
+ After writing or editing code files, ALWAYS verify the changes:
12389
+ 1. Run the project's build command (npm run build, tsc --noEmit, cargo check, etc.)
12390
+ 2. If build fails, read the error output, fix the issues, and re-verify
12391
+ 3. If tests exist, run them to confirm nothing is broken
12392
+ 4. Repeat until all checks pass \u2014 do NOT leave the user with broken code
12393
+
12394
+ ## Error Recovery
12395
+ - When a tool call fails, analyze the error message carefully before retrying
12396
+ - If the same approach fails twice, try a different strategy
12397
+ - For type errors: read the file, understand the types, fix precisely
12398
+ - For runtime errors: add proper error handling and logging
12399
+ - Never give up after one failure \u2014 adapt and overcome
12400
+
12401
+ ## Execution Strategy
12402
+ - Start by reading relevant files and understanding the codebase structure.
12403
+ - Break complex tasks into small, verifiable steps.
12404
+ - After each code change, verify it compiles/passes before moving on.
12405
+ - When stuck, try a different approach rather than repeating the same failed action.
12406
+ - Use subagent for independent subtasks that can run in parallel.
12407
+
12408
+ ## Context
12409
+ - cwd=${process5.cwd()} plat=${process5.platform} arch=${process5.arch}
12410
+ - Respond in the same language as the user's input.`;
12039
12411
  const ts = todoSummary(4);
12040
- if (ts) sysContent += "\n[Remaining]\n" + ts;
12412
+ if (ts) sysContent += "\n[Remaining Tasks]\n" + ts;
12041
12413
  const lastU = history.filter((m) => m.role === "user").pop();
12042
12414
  if (lastU?.content) {
12043
12415
  const hints = xmemory.getProceduralHints(lastU.content || "", 3);
12044
12416
  if (hints) sysContent += "\n" + hints;
12045
12417
  }
12046
12418
  const workCtx = xmemory.getWorkingContext(400);
12047
- if (workCtx) sysContent += "\n[\u6700\u8FD1\u5DE5\u4F5C]\n" + workCtx;
12419
+ if (workCtx) sysContent += "\n[Recent Work]\n" + workCtx;
12048
12420
  const skillPrompt = getSkillSystemPrompt();
12049
12421
  if (skillPrompt) sysContent += "\n" + skillPrompt;
12050
12422
  r2.push({ role: "system", content: sysContent });
@@ -12094,22 +12466,44 @@ function compressHistory(h) {
12094
12466
  const keep = 6;
12095
12467
  const old = h.slice(0, h.length - keep);
12096
12468
  const recent = h.slice(h.length - keep);
12097
- let compressed = "";
12469
+ const sections = [];
12470
+ let userParts = [];
12471
+ let assistantParts = [];
12472
+ let toolParts = [];
12473
+ const flushSection = () => {
12474
+ if (userParts.length > 0) sections.push(`[\u7528\u6237] ${userParts.join(" \u2192 ")}`);
12475
+ if (assistantParts.length > 0) sections.push(`[\u52A9\u624B] ${assistantParts.join(" \u2192 ")}`);
12476
+ if (toolParts.length > 0) sections.push(`[\u5DE5\u5177] ${toolParts.join(", ")}`);
12477
+ userParts = [];
12478
+ assistantParts = [];
12479
+ toolParts = [];
12480
+ };
12098
12481
  for (const m of old) {
12482
+ if (m.role === "system") {
12483
+ flushSection();
12484
+ continue;
12485
+ }
12099
12486
  if (m.role === "user" && m.content) {
12100
- compressed += `[\u7528\u6237] ${m.content.slice(0, 100)}
12101
- `;
12102
- } else if (m.role === "assistant" && m.content) {
12103
- compressed += `[\u52A9\u624B] ${m.content.slice(0, 150)}
12104
- `;
12487
+ if (assistantParts.length > 0 || toolParts.length > 0) flushSection();
12488
+ userParts.push(m.content.slice(0, 120));
12489
+ } else if (m.role === "assistant") {
12490
+ if (m.tool_calls && m.tool_calls.length > 0) {
12491
+ const names = m.tool_calls.map((t) => t.name).join(", ");
12492
+ toolParts.push(names);
12493
+ }
12494
+ if (m.content) {
12495
+ assistantParts.push(m.content.slice(0, 150));
12496
+ }
12105
12497
  } else if (m.role === "tool" && m.content) {
12106
- compressed += `[\u5DE5\u5177] ${m.content.slice(0, 80)}
12107
- `;
12498
+ const isErr = m.content.startsWith("Error:");
12499
+ toolParts.push(isErr ? "\u274C" : "\u2713");
12108
12500
  }
12109
12501
  }
12502
+ flushSection();
12503
+ const compressed = sections.join("\n").slice(0, 4e3);
12110
12504
  h.length = 0;
12111
12505
  h.push({ role: "system", content: `[\u4E0A\u4E0B\u6587\u538B\u7F29\xB7${old.length}\u6761\u6458\u8981]
12112
- ${compressed.slice(0, 3e3)}` });
12506
+ ${compressed}` });
12113
12507
  h.push(...recent);
12114
12508
  }
12115
12509
  function sanitize(t) {
@@ -12119,6 +12513,45 @@ function lastUser(h) {
12119
12513
  for (let i = h.length - 1; i >= 0; i--) if (h[i].role === "user" && h[i].content) return h[i].content;
12120
12514
  return "";
12121
12515
  }
12516
+ function computeSimpleDiff(oldText, newText, filePath) {
12517
+ const oldLines = oldText.split("\n");
12518
+ const newLines = newText.split("\n");
12519
+ const maxLines = Math.max(oldLines.length, newLines.length);
12520
+ const result = [];
12521
+ let changeCount = 0;
12522
+ const contextLines = 3;
12523
+ for (let i = 0; i < maxLines; i++) {
12524
+ const oldLine = oldLines[i];
12525
+ const newLine = newLines[i];
12526
+ if (oldLine !== newLine) {
12527
+ changeCount++;
12528
+ const start = Math.max(0, i - contextLines);
12529
+ const end = Math.min(maxLines, i + contextLines + 1);
12530
+ if (result.length === 0 || result[result.length - 1] !== "...") {
12531
+ result.push(G(` @@ line ${i + 1} @@`));
12532
+ }
12533
+ for (let j = start; j < end; j++) {
12534
+ if (j === i) {
12535
+ if (oldLine !== void 0 && newLine !== void 0) {
12536
+ result.push(r(` - ${oldLines[j]}`));
12537
+ result.push(g(` + ${newLines[j]}`));
12538
+ } else if (oldLine !== void 0) {
12539
+ result.push(r(` - ${oldLines[j]}`));
12540
+ } else {
12541
+ result.push(g(` + ${newLines[j]}`));
12542
+ }
12543
+ } else if (j < oldLines.length && j < newLines.length) {
12544
+ result.push(G(` ${oldLines[j]}`));
12545
+ }
12546
+ }
12547
+ result.push(G(" ..."));
12548
+ }
12549
+ }
12550
+ if (changeCount === 0) return G(" (\u65E0\u53D8\u66F4)");
12551
+ const rel = relative6(process5.cwd(), filePath);
12552
+ return `${G(`\u6587\u4EF6: ${rel} (${changeCount} \u5904\u53D8\u66F4)`)}
12553
+ ${result.join("\n")}`;
12554
+ }
12122
12555
  function toolsToDefs(tools) {
12123
12556
  return tools.map((t) => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.parameters } }));
12124
12557
  }
@@ -12145,7 +12578,6 @@ async function listSessions() {
12145
12578
  const data = JSON.parse(readFileSync37(join12(SESSION_DIR, f), "utf-8"));
12146
12579
  const label = f.replace(/^sess_|\.json$/g, "");
12147
12580
  const msgCount = data.messages?.length || 0;
12148
- const cwd = data.cwd || "?";
12149
12581
  const savedAt = data.savedAt?.slice(0, 16) || "?";
12150
12582
  O(G(` ${i + 1}. `) + c(label) + G(` \xB7 ${msgCount}\u6761 \xB7 ${savedAt}`) + "\n");
12151
12583
  } catch {
@@ -12320,14 +12752,15 @@ async function loadBuiltinTools() {
12320
12752
  const { builtinTools: builtinTools2 } = await Promise.resolve().then(() => (init_builtin(), builtin_exports));
12321
12753
  return builtinTools2;
12322
12754
  }
12323
- async function callApi(opts, msgs, tools, retry = 0, cmt = 8192) {
12755
+ async function callApi(opts, msgs, tools, cmt = 8192, signal) {
12324
12756
  const client = new DeepSeekClient({
12325
12757
  apiKey: opts.apiKey || "",
12326
12758
  model: opts.model || "deepseek-chat",
12327
12759
  baseUrl: opts.baseUrl || "https://api.deepseek.com",
12328
12760
  temperature: opts.temperature ?? 0,
12329
12761
  maxTokens: cmt,
12330
- think: { enabled: true, budgetTokens: cmt }
12762
+ think: { enabled: true, budgetTokens: cmt },
12763
+ signal
12331
12764
  });
12332
12765
  const chatMsgs = msgs.map((m) => ({
12333
12766
  role: m.role || "user",
@@ -12338,51 +12771,15 @@ async function callApi(opts, msgs, tools, retry = 0, cmt = 8192) {
12338
12771
  reasoning_content: m.reasoning_content,
12339
12772
  thinking: m.thinking
12340
12773
  }));
12341
- try {
12342
- const stream = await client.chatStream(chatMsgs, tools.map((t) => ({ name: t.function.name, description: t.function.description, category: "", parameters: t.function.parameters })));
12343
- return adaptStream(stream);
12344
- } catch (e) {
12345
- const m = (e instanceof Error ? e.message : String(e)).toLowerCase();
12346
- if ((m.includes("timeout") || m.includes("abort") || m.includes("econn") || m.includes("429") || m.includes("500")) && retry < 2) {
12347
- const d2 = Math.min(1e3 * (retry + 1), 5e3);
12348
- await new Promise((r2) => setTimeout(r2, d2));
12349
- return callApi(opts, msgs, tools, retry + 1, cmt);
12350
- }
12351
- throw e;
12352
- }
12353
- }
12354
- async function* adaptStream(stream) {
12355
- const seen = /* @__PURE__ */ new Set();
12356
- let hasPending = false;
12357
- for await (const chunk of stream) {
12358
- if (chunk.type === "text" || chunk.type === "thinking") {
12359
- yield { type: chunk.type, content: chunk.content };
12360
- } else if (chunk.type === "tool_call") {
12361
- const tc = chunk.tool_call;
12362
- if (tc) {
12363
- const key = tc.id || tc.name;
12364
- const sa = JSON.stringify(tc.arguments || {});
12365
- if (!seen.has(key)) {
12366
- if (hasPending) yield { type: "tool_call_end" };
12367
- seen.add(key);
12368
- hasPending = true;
12369
- yield { type: "tool_call_start", tool_call: { id: tc.id, name: tc.name } };
12370
- yield { type: "tool_call_args", content: sa };
12371
- } else {
12372
- yield { type: "tool_call_args", content: sa };
12373
- }
12374
- }
12375
- } else if (chunk.type === "done") {
12376
- if (hasPending) yield { type: "tool_call_end" };
12377
- yield { type: "done" };
12378
- return;
12379
- } else {
12380
- yield chunk;
12381
- }
12382
- }
12383
- yield { type: "done" };
12774
+ const stream = await client.chatStream(chatMsgs, tools.map((t) => ({
12775
+ name: t.function.name,
12776
+ description: t.function.description,
12777
+ category: "",
12778
+ parameters: t.function.parameters
12779
+ })));
12780
+ return stream;
12384
12781
  }
12385
- var MAX_HISTORY, CONTEXT_LIMIT, CTX_WARN, TOOL_RESULT_MAX, SESSION_DIR, AUTOSAVE_FILE, GS, skillEngine, mcpClient, validator, _fileCache;
12782
+ var MAX_HISTORY, CONTEXT_LIMIT, CTX_WARN, TOOL_RESULT_MAX, SESSION_DIR, AUTOSAVE_FILE, TOOL_TIMEOUT_MAP, DEFAULT_TOOL_TIMEOUT, GS, skillEngine, mcpClient, validator, currentAbortController, BACKUP_DIR, MAX_BACKUPS, fileBackupStack, _fileCache;
12386
12783
  var init_chat_repl = __esm({
12387
12784
  "src/cli/chat-repl.ts"() {
12388
12785
  "use strict";
@@ -12403,10 +12800,38 @@ var init_chat_repl = __esm({
12403
12800
  TOOL_RESULT_MAX = 4e3;
12404
12801
  SESSION_DIR = join12(DEEPER_HOME, "sessions");
12405
12802
  AUTOSAVE_FILE = join12(SESSION_DIR, "_autosave.json");
12803
+ TOOL_TIMEOUT_MAP = {
12804
+ run_command: 12e4,
12805
+ run_async: 12e4,
12806
+ pipe_commands: 12e4,
12807
+ shell_script: 12e4,
12808
+ build_project: 18e4,
12809
+ run_test: 18e4,
12810
+ npm_manage: 12e4,
12811
+ docker_manage: 12e4,
12812
+ project_init: 12e4,
12813
+ web_fetch: 6e4,
12814
+ web_search: 6e4,
12815
+ http_request: 6e4,
12816
+ browser_action: 6e4,
12817
+ screenshot_page: 6e4,
12818
+ sql_query: 6e4,
12819
+ sql_migrate: 12e4,
12820
+ db_backup: 18e4,
12821
+ db_restore: 18e4,
12822
+ secret_scan: 6e4,
12823
+ vulnerability_check: 6e4,
12824
+ subagent: 18e4
12825
+ };
12826
+ DEFAULT_TOOL_TIMEOUT = 3e4;
12406
12827
  GS = { tc: 0, api: 0, ch: 0 };
12407
12828
  skillEngine = null;
12408
12829
  mcpClient = null;
12409
12830
  validator = new ToolValidator();
12831
+ currentAbortController = null;
12832
+ BACKUP_DIR = join12(DEEPER_HOME, "backups");
12833
+ MAX_BACKUPS = 50;
12834
+ fileBackupStack = [];
12410
12835
  _fileCache = /* @__PURE__ */ new Map();
12411
12836
  }
12412
12837
  });