dskcode 0.1.15 → 0.1.17

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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  DeepSeekProvider,
4
4
  ModelNotSupportedError
5
- } from "./chunk-HWZZJB6I.js";
5
+ } from "./chunk-BA7UJPKJ.js";
6
6
  import {
7
7
  SUPPORTED_MODELS,
8
8
  calculateCost,
@@ -451,7 +451,7 @@ async function promptForApiKey() {
451
451
  input: process.stdin,
452
452
  output: process.stdout
453
453
  });
454
- return new Promise((resolve) => {
454
+ return new Promise((resolve2) => {
455
455
  let resolved = false;
456
456
  const cleanup = () => {
457
457
  if (resolved) return;
@@ -462,7 +462,7 @@ async function promptForApiKey() {
462
462
  const onKeypress = (_, key) => {
463
463
  if (key.ctrl && key.name === "c") {
464
464
  cleanup();
465
- resolve(null);
465
+ resolve2(null);
466
466
  }
467
467
  };
468
468
  process.stdin.on("keypress", onKeypress);
@@ -473,15 +473,15 @@ async function promptForApiKey() {
473
473
  const trimmed = answer.trim();
474
474
  if (!trimmed) {
475
475
  console.log(chalk2.red(" \u2716 API Key \u4E0D\u80FD\u4E3A\u7A7A"));
476
- resolve(null);
476
+ resolve2(null);
477
477
  return;
478
478
  }
479
479
  if (trimmed.length < MIN_API_KEY_LENGTH) {
480
480
  console.log(chalk2.red(" \u2716 API Key \u683C\u5F0F\u4E0D\u6B63\u786E\uFF0C\u957F\u5EA6\u81F3\u5C11 10 \u4F4D"));
481
- resolve(null);
481
+ resolve2(null);
482
482
  return;
483
483
  }
484
- resolve(trimmed);
484
+ resolve2(trimmed);
485
485
  }
486
486
  );
487
487
  });
@@ -490,7 +490,7 @@ async function promptForApiKey() {
490
490
  // src/cli/skill-import.ts
491
491
  import { createInterface as createInterface2 } from "readline";
492
492
  import { existsSync as existsSync2, statSync, lstatSync } from "fs";
493
- import { mkdir as mkdir2, readdir, cp, access, realpath } from "fs/promises";
493
+ import { mkdir as mkdir2, readdir, cp, access, realpath, readFile as readFile2 } from "fs/promises";
494
494
  import { join as join2 } from "path";
495
495
  import chalk3 from "chalk";
496
496
  function getClaudeSkillsDir() {
@@ -521,8 +521,8 @@ async function listClaudeSkills() {
521
521
  const skills = [];
522
522
  for (const name of entries) {
523
523
  const full = join2(claudeDir, name);
524
- const stat = statSync(full);
525
- if (stat.isDirectory() && await isSkillDir(full)) {
524
+ const stat5 = statSync(full);
525
+ if (stat5.isDirectory() && await isSkillDir(full)) {
526
526
  skills.push(name);
527
527
  }
528
528
  }
@@ -533,7 +533,7 @@ function askConfirm(question) {
533
533
  input: process.stdin,
534
534
  output: process.stdout
535
535
  });
536
- return new Promise((resolve) => {
536
+ return new Promise((resolve2) => {
537
537
  let resolved = false;
538
538
  const cleanup = () => {
539
539
  if (resolved) return;
@@ -544,14 +544,14 @@ function askConfirm(question) {
544
544
  const onKeypress = (_, key) => {
545
545
  if (key.ctrl && key.name === "c") {
546
546
  cleanup();
547
- resolve(false);
547
+ resolve2(false);
548
548
  }
549
549
  };
550
550
  process.stdin.on("keypress", onKeypress);
551
551
  rl.question(question, (answer) => {
552
552
  cleanup();
553
553
  const trimmed = answer.trim().toLowerCase();
554
- resolve(trimmed === "y" || trimmed === "yes");
554
+ resolve2(trimmed === "y" || trimmed === "yes");
555
555
  });
556
556
  });
557
557
  }
@@ -611,8 +611,8 @@ async function countProjectLocalSkills(cwd) {
611
611
  let count = 0;
612
612
  for (const name of entries) {
613
613
  const full = join2(skillDir, name);
614
- const stat = statSync(full);
615
- if (stat.isDirectory() && await isSkillDir(full)) {
614
+ const stat5 = statSync(full);
615
+ if (stat5.isDirectory() && await isSkillDir(full)) {
616
616
  count++;
617
617
  }
618
618
  }
@@ -629,8 +629,8 @@ async function hasDskcodeSkills() {
629
629
  }
630
630
  for (const name of entries) {
631
631
  const full = join2(dskcodeDir, name);
632
- const stat = statSync(full);
633
- if (stat.isDirectory() && await isSkillDir(full)) {
632
+ const stat5 = statSync(full);
633
+ if (stat5.isDirectory() && await isSkillDir(full)) {
634
634
  return true;
635
635
  }
636
636
  }
@@ -687,10 +687,94 @@ async function promptImportClaudeSkills(cwd) {
687
687
  console.log(chalk3.dim(" \u4F60\u53EF\u4EE5\u7A0D\u540E\u624B\u52A8\u590D\u5236 ~/.claude/skills \u5230 ~/.dskcode/skills\n"));
688
688
  }
689
689
  }
690
+ async function listDskcodeSkills() {
691
+ const dskcodeDir = getDskcodeSkillsDir();
692
+ if (!existsSync2(dskcodeDir)) return [];
693
+ let entries;
694
+ try {
695
+ entries = await readdir(dskcodeDir);
696
+ } catch {
697
+ return [];
698
+ }
699
+ const skills = [];
700
+ for (const name of entries) {
701
+ const full = join2(dskcodeDir, name);
702
+ if (statSync(full).isDirectory() && await isSkillDir(full)) {
703
+ skills.push(name);
704
+ }
705
+ }
706
+ return skills;
707
+ }
708
+ async function listProjectSkills(cwd) {
709
+ const skillDir = getProjectSkillDir(cwd);
710
+ if (!existsSync2(skillDir)) return [];
711
+ let entries;
712
+ try {
713
+ entries = await readdir(skillDir);
714
+ } catch {
715
+ return [];
716
+ }
717
+ const skills = [];
718
+ for (const name of entries) {
719
+ const full = join2(skillDir, name);
720
+ if (statSync(full).isDirectory() && await isSkillDir(full)) {
721
+ skills.push(name);
722
+ }
723
+ }
724
+ return skills;
725
+ }
726
+ async function readSkillInfo(skillDir) {
727
+ try {
728
+ const content = await readFile2(join2(skillDir, "SKILL.md"), "utf-8");
729
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
730
+ if (!match) return null;
731
+ const frontMatter = match[1];
732
+ const nameMatch = frontMatter.match(/^name:\s*(.+)$/m);
733
+ const descMatch = frontMatter.match(/^description:\s*(.+)$/m);
734
+ return {
735
+ name: nameMatch?.[1]?.trim() ?? "",
736
+ description: descMatch?.[1]?.trim() ?? ""
737
+ };
738
+ } catch {
739
+ return null;
740
+ }
741
+ }
742
+ async function getAllSkills(cwd) {
743
+ const [globalNames, localNames] = await Promise.all([
744
+ listDskcodeSkills(),
745
+ listProjectSkills(cwd)
746
+ ]);
747
+ const seen = /* @__PURE__ */ new Set();
748
+ const allNames = [];
749
+ for (const name of [...localNames, ...globalNames]) {
750
+ if (!seen.has(name)) {
751
+ seen.add(name);
752
+ allNames.push(name);
753
+ }
754
+ }
755
+ const dskcodeDir = getDskcodeSkillsDir();
756
+ const projectDir = getProjectSkillDir(cwd);
757
+ const results = [];
758
+ for (const name of allNames) {
759
+ const localPath = join2(projectDir, name);
760
+ let info = null;
761
+ if (existsSync2(localPath)) {
762
+ info = await readSkillInfo(localPath);
763
+ }
764
+ if (!info) {
765
+ const globalPath = join2(dskcodeDir, name);
766
+ if (existsSync2(globalPath)) {
767
+ info = await readSkillInfo(globalPath);
768
+ }
769
+ }
770
+ results.push(info ?? { name, description: "" });
771
+ }
772
+ return results;
773
+ }
690
774
 
691
775
  // src/cli/index.tsx
692
- import { readFile as readFile3 } from "fs/promises";
693
- import { join as join4 } from "path";
776
+ import { readFile as readFile8 } from "fs/promises";
777
+ import { join as join8 } from "path";
694
778
 
695
779
  // src/ui/RenderScope.tsx
696
780
  import { render } from "ink";
@@ -732,7 +816,7 @@ var LOGO_LINES = [
732
816
  ];
733
817
 
734
818
  // src/ui/ChatSession.tsx
735
- import { Box as Box5, Text as Text6, useInput, Static } from "ink";
819
+ import { Box as Box8, Text as Text9, useInput, Static } from "ink";
736
820
  import TextInput from "ink-text-input";
737
821
  import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2, useRef as useRef2 } from "react";
738
822
 
@@ -768,7 +852,7 @@ function useDoubleCtrlC(onExit) {
768
852
  import { Box as Box4, Text as Text5 } from "ink";
769
853
 
770
854
  // src/provider/cost-tracker.ts
771
- import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
855
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
772
856
  import { join as join3 } from "path";
773
857
  var CostTracker = class {
774
858
  #costDir;
@@ -1003,7 +1087,7 @@ var CostTracker = class {
1003
1087
  async #loadStore() {
1004
1088
  const filePath = join3(this.#costDir, "history.json");
1005
1089
  try {
1006
- const raw = await readFile2(filePath, "utf-8");
1090
+ const raw = await readFile3(filePath, "utf-8");
1007
1091
  return JSON.parse(raw);
1008
1092
  } catch {
1009
1093
  return { version: 1, daily: {} };
@@ -1196,6 +1280,95 @@ function AssistantMessage({
1196
1280
  ] });
1197
1281
  }
1198
1282
 
1283
+ // src/ui/DiffPreview.tsx
1284
+ import { Box as Box5, Text as Text6 } from "ink";
1285
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1286
+ function DiffPreview({ diff }) {
1287
+ const { patch, additions, deletions, existedBefore, filePath } = diff;
1288
+ if (!patch || patch.length === 0) {
1289
+ return null;
1290
+ }
1291
+ const lines = patch.split("\n");
1292
+ const fileName = filePath.replace(/\\/g, "/").split("/").pop() ?? filePath;
1293
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1294
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", gap: 1, children: [
1295
+ /* @__PURE__ */ jsxs5(Text6, { bold: true, color: "#00ffff", children: [
1296
+ "\u{1F4DD} ",
1297
+ existedBefore ? "\u4FEE\u6539" : "\u65B0\u5EFA",
1298
+ ":"
1299
+ ] }),
1300
+ /* @__PURE__ */ jsx5(Text6, { bold: true, children: fileName }),
1301
+ /* @__PURE__ */ jsxs5(Text6, { color: "#555555", children: [
1302
+ "(+",
1303
+ additions,
1304
+ " -",
1305
+ deletions,
1306
+ ")"
1307
+ ] })
1308
+ ] }),
1309
+ /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginLeft: 2, children: lines.map((line, i) => /* @__PURE__ */ jsx5(DiffLine, { line }, i)) })
1310
+ ] });
1311
+ }
1312
+ function DiffLine({ line }) {
1313
+ if (line.startsWith("---") || line.startsWith("+++")) {
1314
+ return /* @__PURE__ */ jsx5(Text6, { color: "#00cccc", bold: true, children: line });
1315
+ }
1316
+ if (line.startsWith("@@")) {
1317
+ return /* @__PURE__ */ jsx5(Text6, { color: "#00cccc", children: line });
1318
+ }
1319
+ if (line.startsWith("+")) {
1320
+ return /* @__PURE__ */ jsx5(Text6, { color: "#22c55e", children: line });
1321
+ }
1322
+ if (line.startsWith("-")) {
1323
+ return /* @__PURE__ */ jsx5(Text6, { color: "#ef4444", children: line });
1324
+ }
1325
+ return /* @__PURE__ */ jsx5(Text6, { color: "#6b7280", children: line });
1326
+ }
1327
+
1328
+ // src/ui/SkillSelector.tsx
1329
+ import { Box as Box6, Text as Text7 } from "ink";
1330
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1331
+ var HIGHLIGHT_COLOR = "#00bfff";
1332
+ function SkillSelector({ skills, input, selectedIndex }) {
1333
+ const match = input.match(/(?:^|\s)\/([^/]*)$/);
1334
+ if (!match) return null;
1335
+ const query = match[1].toLowerCase().trim();
1336
+ if (skills.length === 0) return null;
1337
+ const matched = !query && input.startsWith("/") ? skills.slice(0, 3) : skills.filter((s) => s.name.toLowerCase().includes(query)).slice(0, 3);
1338
+ if (query && matched.length > 0 && matched.some((s) => s.name.toLowerCase() === query)) return null;
1339
+ if (matched.length === 0) return null;
1340
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginLeft: 4, marginTop: 1, children: [
1341
+ /* @__PURE__ */ jsx6(Text7, { color: "#808080", dimColor: true, children: "\u652F\u6301\u7684 Skill\uFF1A" }),
1342
+ matched.map((skill, i) => /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text7, { color: i === selectedIndex ? HIGHLIGHT_COLOR : "#808080", children: [
1343
+ i === selectedIndex ? " \u203A " : " ",
1344
+ skill.name
1345
+ ] }) }, skill.name)),
1346
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text7, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Tab \u8865\u5168" }) })
1347
+ ] });
1348
+ }
1349
+
1350
+ // src/ui/FileSelector.tsx
1351
+ import { Box as Box7, Text as Text8 } from "ink";
1352
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1353
+ var HIGHLIGHT_COLOR2 = "#00ff41";
1354
+ function FileSelector({ files, input, selectedIndex }) {
1355
+ const match = input.match(/(?:^|\s)@([^@]*)$/);
1356
+ if (!match) return null;
1357
+ const query = match[1].toLowerCase().trim();
1358
+ if (files.length === 0) return null;
1359
+ const matched = !query && input.startsWith("@") ? files.slice(0, 5) : files.filter((f) => f.toLowerCase().includes(query)).slice(0, 5);
1360
+ if (query && matched.some((f) => f.toLowerCase() === query)) return null;
1361
+ if (matched.length === 0) return null;
1362
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginLeft: 4, marginTop: 1, children: [
1363
+ /* @__PURE__ */ jsx7(Text8, { color: "#808080", dimColor: true, children: "\u9879\u76EE\u6587\u4EF6\uFF1A" }),
1364
+ matched.map((file, i) => /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: i === selectedIndex ? HIGHLIGHT_COLOR2 : "#808080", children: [
1365
+ i === selectedIndex ? " \u203A " : " ",
1366
+ file
1367
+ ] }) }, file)),
1368
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Tab \u8865\u5168" }) })
1369
+ ] });
1370
+ }
1371
+
1199
1372
  // src/provider/registry.ts
1200
1373
  var ProviderRegistry = class {
1201
1374
  #factories = /* @__PURE__ */ new Map();
@@ -1345,17 +1518,132 @@ ${opts.projectContext}`);
1345
1518
  return sections.join("\n\n");
1346
1519
  }
1347
1520
 
1521
+ // src/tool/registry.ts
1522
+ var ToolRegistry = class {
1523
+ #tools = /* @__PURE__ */ new Map();
1524
+ #disabledNames;
1525
+ constructor(opts) {
1526
+ this.#disabledNames = new Set(opts?.disabledTools ?? []);
1527
+ }
1528
+ /**
1529
+ * 注册一个工具。
1530
+ * 如果同名工具已存在则抛出错误。
1531
+ */
1532
+ register(tool) {
1533
+ if (this.#tools.has(tool.name)) {
1534
+ throw new Error(`\u5DE5\u5177 "${tool.name}" \u5DF2\u6CE8\u518C\uFF0C\u4E0D\u80FD\u91CD\u590D\u6CE8\u518C`);
1535
+ }
1536
+ this.#tools.set(tool.name, tool);
1537
+ return this;
1538
+ }
1539
+ /**
1540
+ * 批量注册工具。
1541
+ */
1542
+ registerAll(tools) {
1543
+ for (const tool of tools) {
1544
+ this.register(tool);
1545
+ }
1546
+ return this;
1547
+ }
1548
+ /**
1549
+ * 注销一个工具。
1550
+ */
1551
+ unregister(name) {
1552
+ return this.#tools.delete(name);
1553
+ }
1554
+ /**
1555
+ * 按名称获取工具。
1556
+ * 如果工具被禁用或不存在,返回 undefined。
1557
+ */
1558
+ get(name) {
1559
+ if (this.#disabledNames.has(name)) return void 0;
1560
+ return this.#tools.get(name);
1561
+ }
1562
+ /**
1563
+ * 获取所有启用的工具列表。
1564
+ */
1565
+ list() {
1566
+ const result = [];
1567
+ for (const [name, tool] of this.#tools) {
1568
+ if (!this.#disabledNames.has(name)) {
1569
+ result.push(tool);
1570
+ }
1571
+ }
1572
+ return result;
1573
+ }
1574
+ /**
1575
+ * 获取所有已注册的工具名称(含禁用的)。
1576
+ */
1577
+ names() {
1578
+ return [...this.#tools.keys()];
1579
+ }
1580
+ /**
1581
+ * 检查工具是否已注册(含禁用的)。
1582
+ */
1583
+ has(name) {
1584
+ return this.#tools.has(name);
1585
+ }
1586
+ /**
1587
+ * 检查工具是否已启用。
1588
+ */
1589
+ isEnabled(name) {
1590
+ return this.#tools.has(name) && !this.#disabledNames.has(name);
1591
+ }
1592
+ /**
1593
+ * 禁用一个工具。
1594
+ */
1595
+ disable(name) {
1596
+ this.#disabledNames.add(name);
1597
+ }
1598
+ /**
1599
+ * 启用一个之前被禁用的工具。
1600
+ */
1601
+ enable(name) {
1602
+ this.#disabledNames.delete(name);
1603
+ }
1604
+ /**
1605
+ * 执行指定工具。
1606
+ *
1607
+ * @returns 工具执行结果;如果工具不存在或被禁用,返回失败结果
1608
+ */
1609
+ async execute(name, args, ctx) {
1610
+ const tool = this.get(name);
1611
+ if (!tool) {
1612
+ return {
1613
+ success: false,
1614
+ data: `\u5DE5\u5177 "${name}" \u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u7981\u7528`,
1615
+ error: "TOOL_NOT_FOUND"
1616
+ };
1617
+ }
1618
+ try {
1619
+ return await tool.execute(args, ctx);
1620
+ } catch (err) {
1621
+ const message = err instanceof Error ? err.message : String(err);
1622
+ return {
1623
+ success: false,
1624
+ data: `\u5DE5\u5177 "${name}" \u6267\u884C\u5F02\u5E38\uFF1A${message}`,
1625
+ error: "EXECUTION_ERROR"
1626
+ };
1627
+ }
1628
+ }
1629
+ };
1630
+
1348
1631
  // src/agent/index.ts
1349
1632
  var Session = class {
1350
1633
  #messages = [];
1351
1634
  #provider;
1352
- #tools;
1635
+ #toolRegistry;
1353
1636
  #costTracker;
1354
1637
  #options;
1355
1638
  #abortController = new AbortController();
1356
1639
  constructor(provider, tools = [], costTracker, options) {
1357
1640
  this.#provider = provider;
1358
- this.#tools = tools;
1641
+ if (tools instanceof ToolRegistry) {
1642
+ this.#toolRegistry = tools;
1643
+ } else {
1644
+ this.#toolRegistry = new ToolRegistry();
1645
+ this.#toolRegistry.registerAll(tools);
1646
+ }
1359
1647
  this.#costTracker = costTracker ?? new CostTracker();
1360
1648
  this.#options = {
1361
1649
  cwd: options?.cwd ?? process.cwd(),
@@ -1380,18 +1668,32 @@ var Session = class {
1380
1668
  get model() {
1381
1669
  return this.#provider.model();
1382
1670
  }
1671
+ /** 获取工具注册表(只读视图) */
1672
+ get toolRegistry() {
1673
+ return this.#toolRegistry;
1674
+ }
1383
1675
  // -------------------------------------------------------------------------
1384
1676
  // 流式对话 — Agent 主循环
1385
1677
  // -------------------------------------------------------------------------
1386
1678
  /**
1387
- * 执行一轮对话,以 AsyncGenerator 形式逐步 yield 事件。
1679
+ * 执行一轮用户对话,以 AsyncGenerator 形式逐步 yield 事件。
1680
+ *
1681
+ * 主循环流程:
1682
+ * 1. 追加用户消息
1683
+ * 2. 进入 Agent 循环(最多 maxToolRounds 轮)
1684
+ * a. 构建消息 → 裁剪 → 调用 Provider 流式接口
1685
+ * b. 解析响应:文本增量、工具调用、使用量
1686
+ * c. 如果有工具调用 → 执行工具 → 追加结果 → 继续循环
1687
+ * d. 如果没有工具调用 → 退出循环
1688
+ * 3. yield done 事件
1388
1689
  *
1389
1690
  * 调用方式:
1390
1691
  * ```ts
1391
1692
  * for await (const event of session.chat("你好")) {
1392
1693
  * switch (event.type) {
1393
- * case "text_delta": // 追加文本
1694
+ * case "text_delta": // 追加文本
1394
1695
  * case "tool_calls": // 展示工具调用
1696
+ * case "tool_result": // 工具执行结果
1395
1697
  * case "usage": // 记录使用量
1396
1698
  * case "done": // 本轮完成
1397
1699
  * case "error": // 处理错误
@@ -1401,75 +1703,91 @@ var Session = class {
1401
1703
  */
1402
1704
  async *chat(userInput) {
1403
1705
  this.#messages.push({ role: "user", content: userInput });
1404
- const systemPrompt = this.#buildSystemPrompt();
1405
- const [trimmed, wasTrimmed] = trimMessages(
1406
- [...this.#messages],
1407
- {
1408
- model: this.#provider.model(),
1409
- reservedForOutput: this.#options.reservedForOutput,
1410
- systemPrompt,
1411
- preserveRecentRounds: this.#options.preserveRecentRounds
1412
- }
1413
- );
1414
- if (wasTrimmed) {
1415
- }
1416
- const apiMessages = buildApiMessages(systemPrompt, trimmed);
1417
- const toolDefs = this.#buildToolDefinitions();
1418
1706
  const startTime = Date.now();
1707
+ let toolRounds = 0;
1419
1708
  try {
1420
- const stream = this.#provider.chat(apiMessages, {
1421
- signal: this.#abortController.signal
1422
- // 将工具定义传给 provider(如支持 function calling)
1423
- // 注意:当前 chat() 签名不含 tools 参数,
1424
- // 工具定义在 system prompt 中描述,由模型通过文本方式请求
1425
- });
1426
- let accumulatedText = "";
1427
- let lastUsage;
1428
- let lastToolCalls;
1429
- let lastFinishReason = null;
1430
- for await (const chunk of stream) {
1431
- if (chunk.content) {
1432
- accumulatedText += chunk.content;
1433
- yield { type: "text_delta", content: chunk.content };
1434
- }
1435
- if (chunk.toolCalls && chunk.toolCalls.length > 0) {
1436
- lastToolCalls = chunk.toolCalls;
1709
+ while (toolRounds < this.#options.maxToolRounds) {
1710
+ const systemPrompt = this.#buildSystemPrompt();
1711
+ const [trimmed] = trimMessages(
1712
+ [...this.#messages],
1713
+ {
1714
+ model: this.#provider.model(),
1715
+ reservedForOutput: this.#options.reservedForOutput,
1716
+ systemPrompt,
1717
+ preserveRecentRounds: this.#options.preserveRecentRounds
1718
+ }
1719
+ );
1720
+ const apiMessages = buildApiMessages(systemPrompt, trimmed);
1721
+ const toolDefs = this.#buildToolDefinitions();
1722
+ const stream = this.#provider.chat(apiMessages, {
1723
+ signal: this.#abortController.signal,
1724
+ tools: toolDefs.length > 0 ? toolDefs : void 0
1725
+ });
1726
+ let accumulatedText = "";
1727
+ let lastUsage;
1728
+ let lastToolCalls;
1729
+ let lastFinishReason = null;
1730
+ for await (const chunk of stream) {
1731
+ if (chunk.content) {
1732
+ accumulatedText += chunk.content;
1733
+ yield { type: "text_delta", content: chunk.content };
1734
+ }
1735
+ if (chunk.toolCalls && chunk.toolCalls.length > 0) {
1736
+ lastToolCalls = chunk.toolCalls;
1737
+ }
1738
+ if (chunk.usage) {
1739
+ lastUsage = chunk.usage;
1740
+ }
1741
+ if (chunk.finishReason) {
1742
+ lastFinishReason = chunk.finishReason;
1743
+ }
1437
1744
  }
1438
- if (chunk.usage) {
1439
- lastUsage = chunk.usage;
1745
+ if (lastUsage) {
1746
+ const modelId = this.#provider.model();
1747
+ this.#costTracker.record(lastUsage, modelId);
1748
+ yield { type: "usage", usage: lastUsage, model: modelId };
1440
1749
  }
1441
- if (chunk.finishReason) {
1442
- lastFinishReason = chunk.finishReason;
1750
+ const assistantMsg = {
1751
+ role: "assistant",
1752
+ content: accumulatedText
1753
+ };
1754
+ if (lastToolCalls && lastToolCalls.length > 0) {
1755
+ assistantMsg.toolCalls = lastToolCalls;
1443
1756
  }
1444
- }
1445
- if (lastToolCalls && lastToolCalls.length > 0) {
1446
- yield { type: "tool_calls", calls: lastToolCalls };
1447
- }
1448
- if (lastUsage) {
1449
- const modelId = this.#provider.model();
1450
- const costInfo = this.#costTracker.record(lastUsage, modelId);
1451
- yield { type: "usage", usage: lastUsage, model: modelId };
1452
- }
1453
- const assistantMsg = {
1454
- role: "assistant",
1455
- content: accumulatedText
1456
- };
1457
- if (lastToolCalls && lastToolCalls.length > 0) {
1458
- assistantMsg.toolCalls = lastToolCalls;
1459
- }
1460
- this.#messages.push(assistantMsg);
1461
- if (lastToolCalls && lastToolCalls.length > 0) {
1462
- for (const tc of lastToolCalls) {
1463
- this.#messages.push({
1464
- role: "tool",
1465
- content: `\u26A0 \u5DE5\u5177 "${tc.name}" \u7B49\u5F85\u6267\u884C\uFF08\u5DE5\u5177\u7CFB\u7EDF\u5C06\u5728\u7B2C08\u7AE0\u5B9E\u73B0\uFF09`,
1466
- toolCallId: tc.id,
1467
- name: tc.name
1468
- });
1757
+ this.#messages.push(assistantMsg);
1758
+ if (lastToolCalls && lastToolCalls.length > 0) {
1759
+ yield { type: "tool_calls", calls: lastToolCalls };
1760
+ const toolCtx = {
1761
+ cwd: this.#options.cwd,
1762
+ signal: this.#abortController.signal
1763
+ };
1764
+ for (const tc of lastToolCalls) {
1765
+ let toolArgs;
1766
+ try {
1767
+ toolArgs = tc.arguments ? JSON.parse(tc.arguments) : {};
1768
+ } catch {
1769
+ toolArgs = {};
1770
+ }
1771
+ const result = await this.#toolRegistry.execute(tc.name, toolArgs, toolCtx);
1772
+ yield { type: "tool_result", name: tc.name, result };
1773
+ let toolContent = result.data;
1774
+ if (result.diff && result.diff.patch) {
1775
+ toolContent += `
1776
+
1777
+ ${result.diff.patch}`;
1778
+ }
1779
+ this.#messages.push({
1780
+ role: "tool",
1781
+ content: toolContent,
1782
+ toolCallId: tc.id,
1783
+ name: tc.name
1784
+ });
1785
+ }
1786
+ toolRounds++;
1787
+ continue;
1469
1788
  }
1789
+ break;
1470
1790
  }
1471
- const elapsed = Date.now() - startTime;
1472
- yield { type: "done", elapsed };
1473
1791
  } catch (err) {
1474
1792
  if (err instanceof DOMException && err.name === "AbortError") {
1475
1793
  return;
@@ -1481,7 +1799,10 @@ var Session = class {
1481
1799
  type: "error",
1482
1800
  error: err instanceof Error ? err : new Error(String(err))
1483
1801
  };
1802
+ return;
1484
1803
  }
1804
+ const elapsed = Date.now() - startTime;
1805
+ yield { type: "done", elapsed };
1485
1806
  }
1486
1807
  // -------------------------------------------------------------------------
1487
1808
  // 会话管理
@@ -1500,7 +1821,8 @@ var Session = class {
1500
1821
  // -------------------------------------------------------------------------
1501
1822
  /** 构建系统提示词 */
1502
1823
  #buildSystemPrompt() {
1503
- const toolDescs = this.#tools.map((t) => ({
1824
+ const enabledTools = this.#toolRegistry.list();
1825
+ const toolDescs = enabledTools.map((t) => ({
1504
1826
  name: t.name,
1505
1827
  description: t.description,
1506
1828
  parameters: t.parameters
@@ -1515,7 +1837,7 @@ var Session = class {
1515
1837
  }
1516
1838
  /** 将注册的工具转为 ToolDefinition 格式(预留给 function calling) */
1517
1839
  #buildToolDefinitions() {
1518
- return this.#tools.map((t) => ({
1840
+ return this.#toolRegistry.list().map((t) => ({
1519
1841
  type: "function",
1520
1842
  function: {
1521
1843
  name: t.name,
@@ -1526,6 +1848,956 @@ var Session = class {
1526
1848
  }
1527
1849
  };
1528
1850
 
1851
+ // src/tool/sandbox.ts
1852
+ import { resolve, relative, isAbsolute } from "path";
1853
+ import { spawn } from "child_process";
1854
+ import process2 from "process";
1855
+ var DEFAULT_TIMEOUT_MS = 3e4;
1856
+ var DEFAULT_MAX_OUTPUT_LENGTH = 5e4;
1857
+ var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
1858
+ var isWindows = process2.platform === "win32";
1859
+ function resolvePath(inputPath, cwd) {
1860
+ const resolved = isAbsolute(inputPath) ? inputPath : resolve(cwd, inputPath);
1861
+ const normalized = resolve(resolved);
1862
+ const rel = relative(cwd, normalized);
1863
+ if (rel.startsWith("..") || normalized !== resolve(cwd) && !rel) {
1864
+ }
1865
+ return normalized;
1866
+ }
1867
+ function truncateOutput(content, maxLength = DEFAULT_MAX_OUTPUT_LENGTH) {
1868
+ if (content.length <= maxLength) return content;
1869
+ const truncated = content.slice(0, maxLength);
1870
+ return `${truncated}
1871
+
1872
+ ... [\u8F93\u51FA\u8FC7\u957F\uFF0C\u5DF2\u622A\u65AD\uFF0C\u5171 ${content.length} \u5B57\u7B26]`;
1873
+ }
1874
+ function getDefaultTimeout() {
1875
+ return DEFAULT_TIMEOUT_MS;
1876
+ }
1877
+ async function execCommand(command, args, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, signal, isShellCommand) {
1878
+ return new Promise((resolve2) => {
1879
+ let spawnCmd;
1880
+ let spawnArgs;
1881
+ let useShell;
1882
+ if (isShellCommand) {
1883
+ spawnCmd = command;
1884
+ spawnArgs = args;
1885
+ useShell = false;
1886
+ } else {
1887
+ useShell = !isWindows;
1888
+ spawnCmd = isWindows ? "cmd" : command;
1889
+ spawnArgs = isWindows ? ["/c", command, ...args] : args;
1890
+ }
1891
+ const child = spawn(spawnCmd, spawnArgs, {
1892
+ cwd,
1893
+ shell: useShell,
1894
+ env: { ...process2.env },
1895
+ stdio: ["pipe", "pipe", "pipe"]
1896
+ });
1897
+ let stdout = "";
1898
+ let stderr = "";
1899
+ child.stdout.on("data", (data) => {
1900
+ stdout += data.toString();
1901
+ });
1902
+ child.stderr.on("data", (data) => {
1903
+ stderr += data.toString();
1904
+ });
1905
+ const timeout = setTimeout(() => {
1906
+ child.kill("SIGTERM");
1907
+ setTimeout(() => {
1908
+ child.kill("SIGKILL");
1909
+ }, 5e3);
1910
+ }, timeoutMs);
1911
+ if (signal) {
1912
+ if (signal.aborted) {
1913
+ child.kill("SIGTERM");
1914
+ }
1915
+ signal.addEventListener("abort", () => {
1916
+ child.kill("SIGTERM");
1917
+ }, { once: true });
1918
+ }
1919
+ child.on("close", (code) => {
1920
+ clearTimeout(timeout);
1921
+ resolve2({ stdout, stderr, exitCode: code });
1922
+ });
1923
+ child.on("error", (err) => {
1924
+ clearTimeout(timeout);
1925
+ stderr += `
1926
+ \u8FDB\u7A0B\u542F\u52A8\u5931\u8D25\uFF1A${err.message}`;
1927
+ resolve2({ stdout, stderr, exitCode: null });
1928
+ });
1929
+ });
1930
+ }
1931
+
1932
+ // src/tool/diff.ts
1933
+ function computeFileDiff(oldContent, newContent, filePath) {
1934
+ if (oldContent === newContent) {
1935
+ return {
1936
+ filePath,
1937
+ patch: "",
1938
+ existedBefore: oldContent.length > 0,
1939
+ additions: 0,
1940
+ deletions: 0
1941
+ };
1942
+ }
1943
+ if (oldContent.length === 0) {
1944
+ const newLines2 = splitLines(newContent);
1945
+ const patch2 = formatNewFileDiff(newLines2, filePath);
1946
+ return {
1947
+ filePath,
1948
+ patch: patch2,
1949
+ existedBefore: false,
1950
+ additions: newLines2.length,
1951
+ deletions: 0
1952
+ };
1953
+ }
1954
+ if (newContent.length === 0) {
1955
+ const oldLines2 = splitLines(oldContent);
1956
+ const patch2 = formatDeletedFileDiff(oldLines2, filePath);
1957
+ return {
1958
+ filePath,
1959
+ patch: patch2,
1960
+ existedBefore: true,
1961
+ additions: 0,
1962
+ deletions: oldLines2.length
1963
+ };
1964
+ }
1965
+ const oldLines = splitLines(oldContent);
1966
+ const newLines = splitLines(newContent);
1967
+ const diffLines = computeLineDiff(oldLines, newLines);
1968
+ let additions = 0;
1969
+ let deletions = 0;
1970
+ for (const line of diffLines) {
1971
+ if (line.op === "add") additions++;
1972
+ if (line.op === "remove") deletions++;
1973
+ }
1974
+ const hunks = groupIntoHunks(diffLines);
1975
+ const patch = formatUnifiedDiff(hunks, filePath);
1976
+ return {
1977
+ filePath,
1978
+ patch,
1979
+ existedBefore: true,
1980
+ additions,
1981
+ deletions
1982
+ };
1983
+ }
1984
+ function splitLines(text) {
1985
+ const raw = text.split("\n");
1986
+ if (text.endsWith("\n") && raw.length > 0 && raw[raw.length - 1] === "") {
1987
+ return raw.slice(0, -1);
1988
+ }
1989
+ return raw;
1990
+ }
1991
+ function computeLineDiff(oldLines, newLines) {
1992
+ const N = oldLines.length;
1993
+ const M = newLines.length;
1994
+ const dp = [];
1995
+ for (let i2 = 0; i2 <= N; i2++) {
1996
+ dp[i2] = new Array(M + 1).fill(0);
1997
+ }
1998
+ for (let i2 = 1; i2 <= N; i2++) {
1999
+ for (let j2 = 1; j2 <= M; j2++) {
2000
+ if (oldLines[i2 - 1] === newLines[j2 - 1]) {
2001
+ dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
2002
+ } else {
2003
+ dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
2004
+ }
2005
+ }
2006
+ }
2007
+ const result = [];
2008
+ let i = N;
2009
+ let j = M;
2010
+ while (i > 0 || j > 0) {
2011
+ const oldLine = i > 0 ? oldLines[i - 1] : void 0;
2012
+ const newLine = j > 0 ? newLines[j - 1] : void 0;
2013
+ if (i > 0 && j > 0 && oldLine !== void 0 && newLine !== void 0 && oldLine === newLine) {
2014
+ result.unshift({ op: "equal", line: oldLine });
2015
+ i--;
2016
+ j--;
2017
+ } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
2018
+ result.unshift({ op: "add", line: newLines[j - 1] });
2019
+ j--;
2020
+ } else {
2021
+ result.unshift({ op: "remove", line: oldLines[i - 1] });
2022
+ i--;
2023
+ }
2024
+ }
2025
+ return result;
2026
+ }
2027
+ var CONTEXT_LINES = 3;
2028
+ function groupIntoHunks(diffLines) {
2029
+ if (diffLines.length === 0) return [];
2030
+ const changeIndices = [];
2031
+ for (let i = 0; i < diffLines.length; i++) {
2032
+ if (diffLines[i].op !== "equal") {
2033
+ changeIndices.push(i);
2034
+ }
2035
+ }
2036
+ if (changeIndices.length === 0) return [];
2037
+ const groups = [[changeIndices[0]]];
2038
+ for (let idx = 1; idx < changeIndices.length; idx++) {
2039
+ const prevIdx = groups[groups.length - 1][groups[groups.length - 1].length - 1];
2040
+ const currIdx = changeIndices[idx];
2041
+ let gap = 0;
2042
+ for (let k = prevIdx + 1; k < currIdx; k++) {
2043
+ if (diffLines[k].op === "equal") gap++;
2044
+ }
2045
+ if (gap <= 2 * CONTEXT_LINES) {
2046
+ groups[groups.length - 1].push(currIdx);
2047
+ } else {
2048
+ groups.push([currIdx]);
2049
+ }
2050
+ }
2051
+ const hunks = [];
2052
+ for (const group of groups) {
2053
+ const firstChangeIdx = group[0];
2054
+ const lastChangeIdx = group[group.length - 1];
2055
+ const startIdx = Math.max(0, firstChangeIdx - CONTEXT_LINES);
2056
+ const endIdx = Math.min(diffLines.length - 1, lastChangeIdx + CONTEXT_LINES);
2057
+ const lines = diffLines.slice(startIdx, endIdx + 1);
2058
+ let oldLine = 0;
2059
+ let newLine = 0;
2060
+ for (let i = 0; i < startIdx; i++) {
2061
+ const op = diffLines[i].op;
2062
+ if (op === "equal" || op === "remove") oldLine++;
2063
+ if (op === "equal" || op === "add") newLine++;
2064
+ }
2065
+ let oldCount = 0;
2066
+ let newCount = 0;
2067
+ for (const line of lines) {
2068
+ if (line.op === "equal" || line.op === "remove") oldCount++;
2069
+ if (line.op === "equal" || line.op === "add") newCount++;
2070
+ }
2071
+ hunks.push({
2072
+ oldStart: oldLine + 1,
2073
+ oldCount,
2074
+ newStart: newLine + 1,
2075
+ newCount,
2076
+ lines
2077
+ });
2078
+ }
2079
+ return hunks;
2080
+ }
2081
+ function formatUnifiedDiff(hunks, filePath) {
2082
+ if (hunks.length === 0) return "";
2083
+ const parts = [];
2084
+ const fileName = extractFileName(filePath);
2085
+ parts.push(`--- a/${fileName}`);
2086
+ parts.push(`+++ b/${fileName}`);
2087
+ for (const hunk of hunks) {
2088
+ parts.push(`@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@`);
2089
+ for (const line of hunk.lines) {
2090
+ switch (line.op) {
2091
+ case "equal":
2092
+ parts.push(` ${line.line}`);
2093
+ break;
2094
+ case "remove":
2095
+ parts.push(`-${line.line}`);
2096
+ break;
2097
+ case "add":
2098
+ parts.push(`+${line.line}`);
2099
+ break;
2100
+ }
2101
+ }
2102
+ }
2103
+ return parts.join("\n");
2104
+ }
2105
+ function formatNewFileDiff(lines, filePath) {
2106
+ const fileName = extractFileName(filePath);
2107
+ const parts = [];
2108
+ parts.push(`--- /dev/null`);
2109
+ parts.push(`+++ b/${fileName}`);
2110
+ parts.push(`@@ -0,0 +1,${lines.length} @@`);
2111
+ for (const line of lines) {
2112
+ parts.push(`+${line}`);
2113
+ }
2114
+ return parts.join("\n");
2115
+ }
2116
+ function formatDeletedFileDiff(lines, filePath) {
2117
+ const fileName = extractFileName(filePath);
2118
+ const parts = [];
2119
+ parts.push(`--- a/${fileName}`);
2120
+ parts.push(`+++ /dev/null`);
2121
+ parts.push(`@@ -1,${lines.length} +0,0 @@`);
2122
+ for (const line of lines) {
2123
+ parts.push(`-${line}`);
2124
+ }
2125
+ return parts.join("\n");
2126
+ }
2127
+ function extractFileName(filePath) {
2128
+ const normalized = filePath.replace(/\\/g, "/");
2129
+ return normalized.split("/").pop() ?? filePath;
2130
+ }
2131
+
2132
+ // src/tool/builtins/read-file.ts
2133
+ import { readFile as readFile4 } from "fs/promises";
2134
+ import { stat } from "fs/promises";
2135
+ var readFileSchema = {
2136
+ type: "object",
2137
+ properties: {
2138
+ path: {
2139
+ type: "string",
2140
+ description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09"
2141
+ },
2142
+ start_line: {
2143
+ type: "number",
2144
+ description: "\u8D77\u59CB\u884C\u53F7\uFF08\u4ECE 1 \u5F00\u59CB\uFF09\uFF0C\u9ED8\u8BA4\u4E3A 1"
2145
+ },
2146
+ end_line: {
2147
+ type: "number",
2148
+ description: "\u7ED3\u675F\u884C\u53F7\uFF08\u5305\u542B\uFF09\uFF0C\u9ED8\u8BA4\u5230\u6587\u4EF6\u672B\u5C3E"
2149
+ }
2150
+ },
2151
+ required: ["path"],
2152
+ additionalProperties: false
2153
+ };
2154
+ var readFileTool = {
2155
+ name: "read_file",
2156
+ description: "\u8BFB\u53D6\u6307\u5B9A\u8DEF\u5F84\u7684\u6587\u4EF6\u5185\u5BB9\u3002\u652F\u6301\u884C\u53F7\u8303\u56F4\u9009\u62E9\uFF0C\u8F93\u51FA\u5E26\u884C\u53F7\u3002\u9002\u7528\u4E8E\u67E5\u770B\u6E90\u4EE3\u7801\u3001\u914D\u7F6E\u6587\u4EF6\u7B49\u6587\u672C\u6587\u4EF6\u3002",
2157
+ parameters: readFileSchema,
2158
+ async execute(args, ctx) {
2159
+ const params = args;
2160
+ if (!params?.path || typeof params.path !== "string") {
2161
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 path", error: "INVALID_ARGS" };
2162
+ }
2163
+ const filePath = resolvePath(params.path, ctx.cwd);
2164
+ try {
2165
+ const fileStat = await stat(filePath);
2166
+ const maxSize = 10 * 1024 * 1024;
2167
+ if (fileStat.size > maxSize) {
2168
+ return {
2169
+ success: false,
2170
+ data: `\u6587\u4EF6\u8FC7\u5927\uFF08${(fileStat.size / 1024 / 1024).toFixed(1)}MB\uFF09\uFF0C\u8D85\u8FC7 10MB \u9650\u5236`,
2171
+ error: "FILE_TOO_LARGE"
2172
+ };
2173
+ }
2174
+ const content = await readFile4(filePath, "utf-8");
2175
+ const lines = content.split("\n");
2176
+ const startLine = Math.max(1, params.start_line ?? 1) - 1;
2177
+ const endLine = params.end_line ? Math.min(params.end_line, lines.length) : lines.length;
2178
+ const selectedLines = lines.slice(startLine, endLine);
2179
+ const lineNumWidth = String(endLine).length;
2180
+ const result = selectedLines.map((line, i) => {
2181
+ const lineNum = String(startLine + i + 1).padStart(lineNumWidth, " ");
2182
+ return `${lineNum} | ${line}`;
2183
+ }).join("\n");
2184
+ return {
2185
+ success: true,
2186
+ data: truncateOutput(result)
2187
+ };
2188
+ } catch (err) {
2189
+ const message = err instanceof Error ? err.message : String(err);
2190
+ return {
2191
+ success: false,
2192
+ data: `\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25\uFF1A${message}`,
2193
+ error: "READ_ERROR"
2194
+ };
2195
+ }
2196
+ }
2197
+ };
2198
+
2199
+ // src/tool/builtins/write-file.ts
2200
+ import { writeFile as writeFile3, mkdir as mkdir4, readFile as readFile5 } from "fs/promises";
2201
+ import { dirname } from "path";
2202
+ var writeFileSchema = {
2203
+ type: "object",
2204
+ properties: {
2205
+ path: {
2206
+ type: "string",
2207
+ description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09"
2208
+ },
2209
+ content: {
2210
+ type: "string",
2211
+ description: "\u8981\u5199\u5165\u7684\u6587\u4EF6\u5185\u5BB9"
2212
+ }
2213
+ },
2214
+ required: ["path", "content"],
2215
+ additionalProperties: false
2216
+ };
2217
+ var writeFileTool = {
2218
+ name: "write_file",
2219
+ description: "\u521B\u5EFA\u6216\u8986\u76D6\u6587\u4EF6\u3002\u5982\u679C\u7236\u76EE\u5F55\u4E0D\u5B58\u5728\u4F1A\u81EA\u52A8\u521B\u5EFA\u3002\u9002\u7528\u4E8E\u521B\u5EFA\u65B0\u6587\u4EF6\u6216\u5B8C\u5168\u66FF\u6362\u6587\u4EF6\u5185\u5BB9\u3002",
2220
+ parameters: writeFileSchema,
2221
+ async execute(args, ctx) {
2222
+ const params = args;
2223
+ if (!params?.path || typeof params.path !== "string") {
2224
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 path", error: "INVALID_ARGS" };
2225
+ }
2226
+ if (params.content === void 0 || params.content === null) {
2227
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 content", error: "INVALID_ARGS" };
2228
+ }
2229
+ const filePath = resolvePath(params.path, ctx.cwd);
2230
+ try {
2231
+ let oldContent = "";
2232
+ let existedBefore = false;
2233
+ try {
2234
+ oldContent = await readFile5(filePath, "utf-8");
2235
+ existedBefore = true;
2236
+ } catch {
2237
+ }
2238
+ await mkdir4(dirname(filePath), { recursive: true });
2239
+ const content = String(params.content);
2240
+ await writeFile3(filePath, content, "utf-8");
2241
+ const diff = computeFileDiff(oldContent, content, filePath);
2242
+ diff.existedBefore = existedBefore;
2243
+ const lineCount = content.split("\n").length;
2244
+ const byteSize = Buffer.byteLength(content, "utf-8");
2245
+ const action = existedBefore ? "\u5DF2\u4FEE\u6539" : "\u5DF2\u521B\u5EFA";
2246
+ const diffSummary = existedBefore ? `\uFF0C+${diff.additions} -${diff.deletions}` : `\uFF0C+${diff.additions} \u884C\uFF08\u65B0\u5EFA\uFF09`;
2247
+ return {
2248
+ success: true,
2249
+ data: `\u6587\u4EF6${action}\uFF1A${filePath}\uFF08${lineCount} \u884C\uFF0C${byteSize} \u5B57\u8282${diffSummary}\uFF09`,
2250
+ diff
2251
+ };
2252
+ } catch (err) {
2253
+ const message = err instanceof Error ? err.message : String(err);
2254
+ return {
2255
+ success: false,
2256
+ data: `\u5199\u5165\u6587\u4EF6\u5931\u8D25\uFF1A${message}`,
2257
+ error: "WRITE_ERROR"
2258
+ };
2259
+ }
2260
+ }
2261
+ };
2262
+
2263
+ // src/tool/builtins/edit-file.ts
2264
+ import { readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
2265
+ var editFileSchema = {
2266
+ type: "object",
2267
+ properties: {
2268
+ path: {
2269
+ type: "string",
2270
+ description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09"
2271
+ },
2272
+ old_text: {
2273
+ type: "string",
2274
+ description: "\u8981\u67E5\u627E\u7684\u539F\u59CB\u6587\u672C\uFF08\u7CBE\u786E\u5339\u914D\uFF09"
2275
+ },
2276
+ new_text: {
2277
+ type: "string",
2278
+ description: "\u66FF\u6362\u540E\u7684\u65B0\u6587\u672C"
2279
+ }
2280
+ },
2281
+ required: ["path", "old_text", "new_text"],
2282
+ additionalProperties: false
2283
+ };
2284
+ var editFileTool = {
2285
+ name: "edit_file",
2286
+ description: "\u5BF9\u6587\u4EF6\u8FDB\u884C\u7CBE\u786E\u5B57\u7B26\u4E32\u66FF\u6362\u3002\u67E5\u627E\u6587\u4EF6\u4E2D\u7684 old_text \u5E76\u66FF\u6362\u4E3A new_text\u3002\u5982\u679C old_text \u51FA\u73B0\u591A\u6B21\u6216\u672A\u627E\u5230\u5219\u62A5\u9519\u3002\u9002\u7528\u4E8E\u5C0F\u8303\u56F4\u7CBE\u786E\u4FEE\u6539\u3002",
2287
+ parameters: editFileSchema,
2288
+ async execute(args, ctx) {
2289
+ const params = args;
2290
+ if (!params?.path || typeof params.path !== "string") {
2291
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 path", error: "INVALID_ARGS" };
2292
+ }
2293
+ if (typeof params.old_text !== "string") {
2294
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 old_text", error: "INVALID_ARGS" };
2295
+ }
2296
+ if (typeof params.new_text !== "string") {
2297
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 new_text", error: "INVALID_ARGS" };
2298
+ }
2299
+ const filePath = resolvePath(params.path, ctx.cwd);
2300
+ try {
2301
+ const content = await readFile6(filePath, "utf-8");
2302
+ const firstIndex = content.indexOf(params.old_text);
2303
+ if (firstIndex === -1) {
2304
+ return {
2305
+ success: false,
2306
+ data: `\u672A\u627E\u5230\u8981\u66FF\u6362\u7684\u6587\u672C\u3002\u8BF7\u786E\u8BA4 old_text \u4E0E\u6587\u4EF6\u5185\u5BB9\u5B8C\u5168\u4E00\u81F4\uFF08\u5305\u62EC\u7F29\u8FDB\u548C\u7A7A\u683C\uFF09\u3002`,
2307
+ error: "TEXT_NOT_FOUND"
2308
+ };
2309
+ }
2310
+ const secondIndex = content.indexOf(params.old_text, firstIndex + 1);
2311
+ if (secondIndex !== -1) {
2312
+ return {
2313
+ success: false,
2314
+ data: `\u8981\u66FF\u6362\u7684\u6587\u672C\u5728\u6587\u4EF6\u4E2D\u51FA\u73B0\u591A\u6B21\uFF0C\u8BF7\u63D0\u4F9B\u66F4\u591A\u4E0A\u4E0B\u6587\u4EE5\u7CBE\u786E\u5B9A\u4F4D\u3002`,
2315
+ error: "TEXT_MULTIPLE_MATCHES"
2316
+ };
2317
+ }
2318
+ const newContent = content.replace(params.old_text, params.new_text);
2319
+ await writeFile4(filePath, newContent, "utf-8");
2320
+ const diff = computeFileDiff(content, newContent, filePath);
2321
+ diff.existedBefore = true;
2322
+ const beforeText = content.slice(0, firstIndex);
2323
+ const startLine = beforeText.split("\n").length;
2324
+ const oldLines = params.old_text.split("\n").length;
2325
+ const newLines = params.new_text.split("\n").length;
2326
+ const diffSummary = `+${diff.additions} -${diff.deletions}`;
2327
+ return {
2328
+ success: true,
2329
+ data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
2330
+ \u66FF\u6362\u4F4D\u7F6E\uFF1A\u7B2C ${startLine} \u884C
2331
+ ${oldLines} \u884C \u2192 ${newLines} \u884C
2332
+ \u53D8\u66F4\uFF1A${diffSummary}`,
2333
+ diff
2334
+ };
2335
+ } catch (err) {
2336
+ const message = err instanceof Error ? err.message : String(err);
2337
+ return {
2338
+ success: false,
2339
+ data: `\u7F16\u8F91\u6587\u4EF6\u5931\u8D25\uFF1A${message}`,
2340
+ error: "EDIT_ERROR"
2341
+ };
2342
+ }
2343
+ }
2344
+ };
2345
+
2346
+ // src/tool/builtins/bash.ts
2347
+ import process3 from "process";
2348
+ var isWindows2 = process3.platform === "win32";
2349
+ var bashSchema = {
2350
+ type: "object",
2351
+ properties: {
2352
+ command: {
2353
+ type: "string",
2354
+ description: "\u8981\u6267\u884C\u7684 shell \u547D\u4EE4"
2355
+ },
2356
+ timeout: {
2357
+ type: "number",
2358
+ description: "\u6267\u884C\u8D85\u65F6\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 30000"
2359
+ }
2360
+ },
2361
+ required: ["command"],
2362
+ additionalProperties: false
2363
+ };
2364
+ var bashTool = {
2365
+ name: "bash",
2366
+ 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",
2367
+ parameters: bashSchema,
2368
+ async execute(args, ctx) {
2369
+ const params = args;
2370
+ if (!params?.command || typeof params.command !== "string") {
2371
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 command", error: "INVALID_ARGS" };
2372
+ }
2373
+ const timeout = params.timeout ?? ctx.timeout ?? getDefaultTimeout();
2374
+ try {
2375
+ const shellCommand = isWindows2 ? "cmd" : "sh";
2376
+ const shellArgs = isWindows2 ? ["/c", params.command] : ["-c", params.command];
2377
+ const result = await execCommand(
2378
+ shellCommand,
2379
+ shellArgs,
2380
+ ctx.cwd,
2381
+ timeout,
2382
+ ctx.signal,
2383
+ true
2384
+ // isShellCommand — 已指定 shell 程序,不需要二次包装
2385
+ );
2386
+ const parts = [];
2387
+ if (result.stdout) {
2388
+ parts.push(truncateOutput(result.stdout));
2389
+ }
2390
+ if (result.stderr) {
2391
+ parts.push(`[stderr]
2392
+ ${truncateOutput(result.stderr)}`);
2393
+ }
2394
+ const success = result.exitCode === 0;
2395
+ const output = parts.length > 0 ? parts.join("\n") : "(\u65E0\u8F93\u51FA)";
2396
+ return {
2397
+ success,
2398
+ data: `${output}
2399
+ [\u9000\u51FA\u7801: ${result.exitCode ?? "\u672A\u77E5"}]`,
2400
+ error: success ? void 0 : `EXIT_CODE_${result.exitCode ?? "UNKNOWN"}`
2401
+ };
2402
+ } catch (err) {
2403
+ const message = err instanceof Error ? err.message : String(err);
2404
+ return {
2405
+ success: false,
2406
+ data: `\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF1A${message}`,
2407
+ error: "EXECUTION_ERROR"
2408
+ };
2409
+ }
2410
+ }
2411
+ };
2412
+
2413
+ // src/tool/builtins/glob.ts
2414
+ import { readdir as readdir2, stat as stat2 } from "fs/promises";
2415
+ import { join as join4, relative as relative2, isAbsolute as isAbsolute2 } from "path";
2416
+ var globSchema = {
2417
+ type: "object",
2418
+ properties: {
2419
+ pattern: {
2420
+ type: "string",
2421
+ description: "\u641C\u7D22\u6A21\u5F0F\uFF08\u652F\u6301 * \u548C ** \u901A\u914D\u7B26\uFF0C\u5982 **/*.ts\u3001src/**/*.test.ts\uFF09"
2422
+ },
2423
+ directory: {
2424
+ type: "string",
2425
+ description: "\u641C\u7D22\u7684\u8D77\u59CB\u76EE\u5F55\uFF0C\u9ED8\u8BA4\u4E3A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55"
2426
+ }
2427
+ },
2428
+ required: ["pattern"],
2429
+ additionalProperties: false
2430
+ };
2431
+ function globToRegex(pattern) {
2432
+ let regexStr = pattern;
2433
+ regexStr = regexStr.replace(/\*\*\//g, "<<GLOBSTAR_SLASH>>");
2434
+ regexStr = regexStr.replace(/\*\*/g, "<<GLOBSTAR>>");
2435
+ regexStr = regexStr.replace(/\?/g, "<<QUESTION>>");
2436
+ regexStr = regexStr.replace(/[.+^${}()|[\]\\]/g, "\\$&");
2437
+ regexStr = regexStr.replace(/\*/g, "[^/]*");
2438
+ regexStr = regexStr.replace(/<<GLOBSTAR_SLASH>>/g, "(.*/)?");
2439
+ regexStr = regexStr.replace(/<<GLOBSTAR>>/g, ".*");
2440
+ regexStr = regexStr.replace(/<<QUESTION>>/g, "[^/]");
2441
+ return new RegExp(`^${regexStr}$`, "i");
2442
+ }
2443
+ async function walkDir(dir, baseDir) {
2444
+ const results = [];
2445
+ let entries;
2446
+ try {
2447
+ entries = await readdir2(dir, { withFileTypes: true });
2448
+ } catch {
2449
+ return results;
2450
+ }
2451
+ for (const entry of entries) {
2452
+ if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
2453
+ continue;
2454
+ }
2455
+ const fullPath = join4(dir, entry.name);
2456
+ const relPath = relative2(baseDir, fullPath);
2457
+ if (entry.isDirectory()) {
2458
+ results.push(...await walkDir(fullPath, baseDir));
2459
+ } else {
2460
+ results.push(relPath);
2461
+ }
2462
+ }
2463
+ return results;
2464
+ }
2465
+ var globTool = {
2466
+ name: "glob",
2467
+ description: "\u6309\u6A21\u5F0F\u641C\u7D22\u6587\u4EF6\u8DEF\u5F84\u3002\u652F\u6301 *\uFF08\u5339\u914D\u6587\u4EF6\u540D\u90E8\u5206\uFF09\u548C **\uFF08\u5339\u914D\u591A\u5C42\u76EE\u5F55\uFF09\u901A\u914D\u7B26\u3002\u81EA\u52A8\u8DF3\u8FC7 node_modules \u548C .git \u76EE\u5F55\u3002",
2468
+ parameters: globSchema,
2469
+ async execute(args, ctx) {
2470
+ const params = args;
2471
+ if (!params?.pattern || typeof params.pattern !== "string") {
2472
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 pattern", error: "INVALID_ARGS" };
2473
+ }
2474
+ const searchDir = params.directory ? isAbsolute2(params.directory) ? params.directory : join4(ctx.cwd, params.directory) : ctx.cwd;
2475
+ const regex = globToRegex(params.pattern);
2476
+ try {
2477
+ const dirStat = await stat2(searchDir);
2478
+ if (!dirStat.isDirectory()) {
2479
+ return { success: false, data: `\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55\uFF1A${searchDir}`, error: "NOT_DIRECTORY" };
2480
+ }
2481
+ const allFiles = await walkDir(searchDir, searchDir);
2482
+ const matched = allFiles.filter((f) => regex.test(f));
2483
+ if (matched.length === 0) {
2484
+ return {
2485
+ success: true,
2486
+ data: `\u672A\u627E\u5230\u5339\u914D "${params.pattern}" \u7684\u6587\u4EF6`
2487
+ };
2488
+ }
2489
+ const limit = 200;
2490
+ const limited = matched.slice(0, limit);
2491
+ const output = limited.join("\n");
2492
+ const suffix = matched.length > limit ? `
2493
+
2494
+ ... \u5171 ${matched.length} \u4E2A\u6587\u4EF6\uFF0C\u53EA\u663E\u793A\u524D ${limit} \u4E2A` : "";
2495
+ return {
2496
+ success: true,
2497
+ data: truncateOutput(output + suffix)
2498
+ };
2499
+ } catch (err) {
2500
+ const message = err instanceof Error ? err.message : String(err);
2501
+ return {
2502
+ success: false,
2503
+ data: `\u6587\u4EF6\u641C\u7D22\u5931\u8D25\uFF1A${message}`,
2504
+ error: "GLOB_ERROR"
2505
+ };
2506
+ }
2507
+ }
2508
+ };
2509
+
2510
+ // src/tool/builtins/grep.ts
2511
+ import { readdir as readdir3, readFile as readFile7, stat as stat3 } from "fs/promises";
2512
+ import { join as join5, relative as relative3, isAbsolute as isAbsolute3 } from "path";
2513
+ var grepSchema = {
2514
+ type: "object",
2515
+ properties: {
2516
+ pattern: {
2517
+ type: "string",
2518
+ description: "\u6B63\u5219\u8868\u8FBE\u5F0F\u641C\u7D22\u6A21\u5F0F"
2519
+ },
2520
+ directory: {
2521
+ type: "string",
2522
+ description: "\u641C\u7D22\u7684\u76EE\u5F55\u8DEF\u5F84\uFF0C\u9ED8\u8BA4\u4E3A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55"
2523
+ },
2524
+ include: {
2525
+ type: "string",
2526
+ description: "\u6587\u4EF6\u6269\u5C55\u540D\u8FC7\u6EE4\uFF08\u5982 ts\u3001json\uFF09\uFF0C\u4E0D\u542B\u70B9\u53F7"
2527
+ },
2528
+ case_sensitive: {
2529
+ type: "boolean",
2530
+ description: "\u662F\u5426\u5927\u5C0F\u5199\u654F\u611F\uFF0C\u9ED8\u8BA4 false"
2531
+ },
2532
+ max_files: {
2533
+ type: "number",
2534
+ description: "\u6700\u5927\u641C\u7D22\u6587\u4EF6\u6570\uFF0C\u9ED8\u8BA4 200"
2535
+ }
2536
+ },
2537
+ required: ["pattern"],
2538
+ additionalProperties: false
2539
+ };
2540
+ async function collectFiles(dir, baseDir, extension, maxFiles = 200) {
2541
+ const results = [];
2542
+ let entries;
2543
+ try {
2544
+ entries = await readdir3(dir, { withFileTypes: true });
2545
+ } catch {
2546
+ return results;
2547
+ }
2548
+ for (const entry of entries) {
2549
+ if (results.length >= maxFiles) break;
2550
+ if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist")) {
2551
+ continue;
2552
+ }
2553
+ const fullPath = join5(dir, entry.name);
2554
+ if (entry.isDirectory()) {
2555
+ results.push(...await collectFiles(fullPath, baseDir, extension, maxFiles - results.length));
2556
+ } else {
2557
+ if (extension && !entry.name.endsWith(`.${extension}`)) {
2558
+ continue;
2559
+ }
2560
+ results.push(fullPath);
2561
+ }
2562
+ }
2563
+ return results;
2564
+ }
2565
+ var grepTool = {
2566
+ name: "grep",
2567
+ description: "\u5728\u6587\u4EF6\u5185\u5BB9\u4E2D\u641C\u7D22\u6B63\u5219\u8868\u8FBE\u5F0F\u3002\u8FD4\u56DE\u5339\u914D\u884C\u7684\u6587\u4EF6\u8DEF\u5F84\u3001\u884C\u53F7\u548C\u5185\u5BB9\u3002\u652F\u6301\u5927\u5C0F\u5199\u654F\u611F\u3001\u6587\u4EF6\u6269\u5C55\u540D\u8FC7\u6EE4\u3002",
2568
+ parameters: grepSchema,
2569
+ async execute(args, ctx) {
2570
+ const params = args;
2571
+ if (!params?.pattern || typeof params.pattern !== "string") {
2572
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 pattern", error: "INVALID_ARGS" };
2573
+ }
2574
+ const searchDir = params.directory ? isAbsolute3(params.directory) ? params.directory : join5(ctx.cwd, params.directory) : ctx.cwd;
2575
+ const maxFiles = params.max_files ?? 200;
2576
+ try {
2577
+ const flags = params.case_sensitive ? "g" : "gi";
2578
+ const regex = new RegExp(params.pattern, flags);
2579
+ const dirStat = await stat3(searchDir);
2580
+ if (!dirStat.isDirectory()) {
2581
+ return { success: false, data: `\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55\uFF1A${searchDir}`, error: "NOT_DIRECTORY" };
2582
+ }
2583
+ const files = await collectFiles(searchDir, searchDir, params.include, maxFiles);
2584
+ const matches = [];
2585
+ for (const filePath of files) {
2586
+ try {
2587
+ const content = await readFile7(filePath, "utf-8");
2588
+ const lines = content.split("\n");
2589
+ const relPath = relative3(searchDir, filePath);
2590
+ for (let i = 0; i < lines.length; i++) {
2591
+ if (regex.test(lines[i])) {
2592
+ regex.lastIndex = 0;
2593
+ matches.push({
2594
+ file: relPath,
2595
+ line: i + 1,
2596
+ content: lines[i]
2597
+ });
2598
+ if (matches.length >= 500) break;
2599
+ }
2600
+ }
2601
+ if (matches.length >= 500) break;
2602
+ } catch {
2603
+ continue;
2604
+ }
2605
+ }
2606
+ if (matches.length === 0) {
2607
+ return {
2608
+ success: true,
2609
+ data: `\u672A\u627E\u5230\u5339\u914D "${params.pattern}" \u7684\u5185\u5BB9`
2610
+ };
2611
+ }
2612
+ const output = matches.map((m) => `${m.file}:${m.line}: ${m.content}`).join("\n");
2613
+ return {
2614
+ success: true,
2615
+ data: truncateOutput(output)
2616
+ };
2617
+ } catch (err) {
2618
+ const message = err instanceof Error ? err.message : String(err);
2619
+ return {
2620
+ success: false,
2621
+ data: `\u5185\u5BB9\u641C\u7D22\u5931\u8D25\uFF1A${message}`,
2622
+ error: "GREP_ERROR"
2623
+ };
2624
+ }
2625
+ }
2626
+ };
2627
+
2628
+ // src/tool/builtins/ls.ts
2629
+ import { readdir as readdir4, stat as stat4 } from "fs/promises";
2630
+ import { join as join6 } from "path";
2631
+ var lsSchema = {
2632
+ type: "object",
2633
+ properties: {
2634
+ path: {
2635
+ type: "string",
2636
+ description: "\u76EE\u5F55\u8DEF\u5F84\uFF08\u76F8\u5BF9\u4E8E\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09\uFF0C\u9ED8\u8BA4\u4E3A\u5F53\u524D\u76EE\u5F55"
2637
+ },
2638
+ all: {
2639
+ type: "boolean",
2640
+ description: "\u662F\u5426\u663E\u793A\u9690\u85CF\u6587\u4EF6\uFF08\u4EE5 . \u5F00\u5934\u7684\u6587\u4EF6\uFF09\uFF0C\u9ED8\u8BA4 false"
2641
+ }
2642
+ },
2643
+ required: [],
2644
+ additionalProperties: false
2645
+ };
2646
+ var lsTool = {
2647
+ name: "ls",
2648
+ description: "\u5217\u51FA\u76EE\u5F55\u5185\u5BB9\u3002\u663E\u793A\u6761\u76EE\u7C7B\u578B\uFF08\u6587\u4EF6/\u76EE\u5F55/\u94FE\u63A5\uFF09\u548C\u5927\u5C0F\u3002\u53EF\u9009\u62E9\u662F\u5426\u663E\u793A\u9690\u85CF\u6587\u4EF6\u3002",
2649
+ parameters: lsSchema,
2650
+ async execute(args, ctx) {
2651
+ const params = args ?? {};
2652
+ const dirPath = params.path ? resolvePath(params.path, ctx.cwd) : ctx.cwd;
2653
+ const showAll = params.all ?? false;
2654
+ try {
2655
+ const entries = await readdir4(dirPath, { withFileTypes: true });
2656
+ const lines = [];
2657
+ const sorted = [...entries].sort((a, b) => {
2658
+ if (a.isDirectory() !== b.isDirectory()) {
2659
+ return a.isDirectory() ? -1 : 1;
2660
+ }
2661
+ return a.name.localeCompare(b.name);
2662
+ });
2663
+ for (const entry of sorted) {
2664
+ if (!showAll && entry.name.startsWith(".")) continue;
2665
+ const typeMark = entry.isDirectory() ? "DIR" : entry.isSymbolicLink() ? "LINK" : "FILE";
2666
+ let sizeStr = "";
2667
+ if (typeMark === "FILE") {
2668
+ try {
2669
+ const fileStat = await stat4(join6(dirPath, entry.name));
2670
+ if (fileStat.size < 1024) {
2671
+ sizeStr = `${fileStat.size}B`;
2672
+ } else if (fileStat.size < 1024 * 1024) {
2673
+ sizeStr = `${(fileStat.size / 1024).toFixed(1)}KB`;
2674
+ } else {
2675
+ sizeStr = `${(fileStat.size / 1024 / 1024).toFixed(1)}MB`;
2676
+ }
2677
+ } catch {
2678
+ sizeStr = "?";
2679
+ }
2680
+ }
2681
+ const typeLabel = typeMark === "DIR" ? "\u{1F4C1}" : typeMark === "LINK" ? "\u{1F517}" : "\u{1F4C4}";
2682
+ lines.push(`${typeLabel} ${entry.name}${sizeStr ? ` (${sizeStr})` : ""}`);
2683
+ }
2684
+ if (lines.length === 0) {
2685
+ return { success: true, data: "\u76EE\u5F55\u4E3A\u7A7A" };
2686
+ }
2687
+ return {
2688
+ success: true,
2689
+ data: truncateOutput(`\u76EE\u5F55\uFF1A${dirPath}
2690
+ ${lines.join("\n")}`)
2691
+ };
2692
+ } catch (err) {
2693
+ const message = err instanceof Error ? err.message : String(err);
2694
+ return {
2695
+ success: false,
2696
+ data: `\u5217\u76EE\u5F55\u5931\u8D25\uFF1A${message}`,
2697
+ error: "LS_ERROR"
2698
+ };
2699
+ }
2700
+ }
2701
+ };
2702
+
2703
+ // src/tool/builtins/fetch.ts
2704
+ var fetchSchema = {
2705
+ type: "object",
2706
+ properties: {
2707
+ url: {
2708
+ type: "string",
2709
+ description: "\u8BF7\u6C42\u7684 URL"
2710
+ },
2711
+ method: {
2712
+ type: "string",
2713
+ description: "HTTP \u65B9\u6CD5\uFF08GET\u3001POST\u3001PUT\u3001DELETE \u7B49\uFF09\uFF0C\u9ED8\u8BA4 GET"
2714
+ },
2715
+ headers: {
2716
+ type: "object",
2717
+ description: "\u8BF7\u6C42\u5934\u952E\u503C\u5BF9",
2718
+ additionalProperties: { type: "string" }
2719
+ },
2720
+ body: {
2721
+ type: "string",
2722
+ description: "\u8BF7\u6C42\u4F53\u5185\u5BB9\uFF08POST/PUT \u65F6\u4F7F\u7528\uFF09"
2723
+ },
2724
+ max_length: {
2725
+ type: "number",
2726
+ description: "\u54CD\u5E94\u5185\u5BB9\u6700\u5927\u957F\u5EA6\uFF08\u5B57\u7B26\uFF09\uFF0C\u9ED8\u8BA4 50000"
2727
+ }
2728
+ },
2729
+ required: ["url"],
2730
+ additionalProperties: false
2731
+ };
2732
+ var fetchTool = {
2733
+ name: "fetch",
2734
+ description: "\u53D1\u8D77 HTTP \u8BF7\u6C42\u5E76\u8FD4\u56DE\u54CD\u5E94\u5185\u5BB9\u3002\u652F\u6301\u81EA\u5B9A\u4E49\u65B9\u6CD5\u548C\u8BF7\u6C42\u5934\u3002\u9002\u7528\u4E8E\u83B7\u53D6\u7F51\u9875\u5185\u5BB9\u3001API \u8C03\u7528\u7B49\u573A\u666F\u3002",
2735
+ parameters: fetchSchema,
2736
+ async execute(args, ctx) {
2737
+ const params = args;
2738
+ if (!params?.url || typeof params.url !== "string") {
2739
+ return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 url", error: "INVALID_ARGS" };
2740
+ }
2741
+ const method = (params.method ?? "GET").toUpperCase();
2742
+ const timeout = 3e4;
2743
+ const maxLength = params.max_length ?? 5e4;
2744
+ try {
2745
+ const fetchOptions = {
2746
+ method,
2747
+ headers: params.headers ?? {},
2748
+ signal: ctx.signal ?? AbortSignal.timeout(timeout)
2749
+ };
2750
+ if (params.body && (method === "POST" || method === "PUT" || method === "PATCH")) {
2751
+ fetchOptions.headers["Content-Type"] ??= "application/json";
2752
+ fetchOptions.body = params.body;
2753
+ }
2754
+ const response = await fetch(params.url, fetchOptions);
2755
+ const contentType = response.headers.get("content-type") ?? "unknown";
2756
+ const statusText = `${response.status} ${response.statusText}`;
2757
+ let body;
2758
+ try {
2759
+ body = await response.text();
2760
+ } catch {
2761
+ body = "(\u65E0\u6CD5\u8BFB\u53D6\u54CD\u5E94\u4F53)";
2762
+ }
2763
+ const truncatedBody = truncateOutput(body, maxLength);
2764
+ const header = `\u72B6\u6001: ${statusText}
2765
+ \u5185\u5BB9\u7C7B\u578B: ${contentType}`;
2766
+ const separator = body.length > 0 ? "\n---\n" : "";
2767
+ return {
2768
+ success: response.ok,
2769
+ data: `${header}${separator}${truncatedBody}`,
2770
+ error: response.ok ? void 0 : `HTTP_${response.status}`
2771
+ };
2772
+ } catch (err) {
2773
+ if (err instanceof DOMException && err.name === "AbortError") {
2774
+ return { success: false, data: "\u8BF7\u6C42\u88AB\u4E2D\u6B62", error: "ABORTED" };
2775
+ }
2776
+ if (err instanceof TypeError && err.message.includes("fetch")) {
2777
+ return { success: false, data: `\u7F51\u7EDC\u9519\u8BEF\uFF1A${err.message}`, error: "NETWORK_ERROR" };
2778
+ }
2779
+ const message = err instanceof Error ? err.message : String(err);
2780
+ return {
2781
+ success: false,
2782
+ data: `HTTP \u8BF7\u6C42\u5931\u8D25\uFF1A${message}`,
2783
+ error: "FETCH_ERROR"
2784
+ };
2785
+ }
2786
+ }
2787
+ };
2788
+
2789
+ // src/tool/builtins/index.ts
2790
+ var builtinTools = [
2791
+ readFileTool,
2792
+ writeFileTool,
2793
+ editFileTool,
2794
+ bashTool,
2795
+ globTool,
2796
+ grepTool,
2797
+ lsTool,
2798
+ fetchTool
2799
+ ];
2800
+
1529
2801
  // src/utils/gradient.ts
1530
2802
  var IDLE_GRADIENT_STOPS = [
1531
2803
  [0, 255, 65],
@@ -1590,7 +2862,7 @@ function getGradientColors(text, phase, stops) {
1590
2862
  }
1591
2863
 
1592
2864
  // src/ui/ChatSession.tsx
1593
- import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2865
+ import { Fragment, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1594
2866
  var commandRegistry = /* @__PURE__ */ new Map();
1595
2867
  function registerCommand(name, cmd) {
1596
2868
  commandRegistry.set(name, cmd);
@@ -1637,6 +2909,8 @@ function pickRandom(arr) {
1637
2909
  }
1638
2910
  function ChatSession({
1639
2911
  skillCount,
2912
+ skills = [],
2913
+ files = [],
1640
2914
  toolCount,
1641
2915
  verbose,
1642
2916
  apiKey,
@@ -1674,6 +2948,9 @@ function ChatSession({
1674
2948
  const [cmdTipIndex, setCmdTipIndex] = useState3(0);
1675
2949
  const [cmdTipGradientColors, setCmdTipGradientColors] = useState3([]);
1676
2950
  const cmdTipPhaseRef = useRef2(0);
2951
+ const [skillSelectIndex, setSkillSelectIndex] = useState3(0);
2952
+ const [fileSelectIndex, setFileSelectIndex] = useState3(0);
2953
+ const [inputKey, setInputKey] = useState3(0);
1677
2954
  const [selectingModel, setSelectingModel] = useState3(false);
1678
2955
  const [modelSelectIndex, setModelSelectIndex] = useState3(0);
1679
2956
  const modelOptions = ["deepseek-v4-flash", "deepseek-v4-pro"];
@@ -1686,6 +2963,40 @@ function ChatSession({
1686
2963
  const currentCostRef = useRef2(void 0);
1687
2964
  const currentModelRef = useRef2(void 0);
1688
2965
  const streamErrorRef = useRef2(void 0);
2966
+ useEffect3(() => {
2967
+ setSkillSelectIndex(0);
2968
+ setFileSelectIndex(0);
2969
+ }, [input]);
2970
+ const getFilteredSkills = useCallback2(
2971
+ (value) => {
2972
+ const match = value.match(/(?:^|\s)\/([^/]*)$/);
2973
+ if (!match) return [];
2974
+ const q = match[1].toLowerCase().trim();
2975
+ if (!q) {
2976
+ if (value.startsWith("/")) return skills.slice(0, 3);
2977
+ return [];
2978
+ }
2979
+ const matched = skills.filter((s) => s.name.toLowerCase().includes(q)).slice(0, 3);
2980
+ if (matched.some((s) => s.name.toLowerCase() === q)) return [];
2981
+ return matched;
2982
+ },
2983
+ [skills]
2984
+ );
2985
+ const getFilteredFiles = useCallback2(
2986
+ (value) => {
2987
+ const match = value.match(/(?:^|\s)@([^@]*)$/);
2988
+ if (!match) return [];
2989
+ const q = match[1].toLowerCase().trim();
2990
+ if (!q) {
2991
+ if (value.startsWith("@")) return files.slice(0, 5);
2992
+ return [];
2993
+ }
2994
+ const matched = files.filter((f) => f.toLowerCase().includes(q)).slice(0, 5);
2995
+ if (matched.some((f) => f.toLowerCase() === q)) return [];
2996
+ return matched;
2997
+ },
2998
+ [files]
2999
+ );
1689
3000
  const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => {
1690
3001
  process.exit(0);
1691
3002
  });
@@ -1720,6 +3031,50 @@ function ChatSession({
1720
3031
  }
1721
3032
  return;
1722
3033
  }
3034
+ const fileList = getFilteredFiles(input);
3035
+ const skillList = fileList.length === 0 ? getFilteredSkills(input) : [];
3036
+ if (fileList.length > 0) {
3037
+ if (key.upArrow) {
3038
+ setFileSelectIndex((prev) => (prev - 1 + fileList.length) % fileList.length);
3039
+ return;
3040
+ }
3041
+ if (key.downArrow) {
3042
+ setFileSelectIndex((prev) => (prev + 1) % fileList.length);
3043
+ return;
3044
+ }
3045
+ if (key.tab) {
3046
+ const selected = fileList[fileSelectIndex];
3047
+ if (selected) {
3048
+ const atIdx = input.lastIndexOf("@");
3049
+ if (atIdx >= 0) {
3050
+ setInput(input.slice(0, atIdx) + "@" + selected + " ");
3051
+ setInputKey((k) => k + 1);
3052
+ }
3053
+ }
3054
+ return;
3055
+ }
3056
+ }
3057
+ if (skillList.length > 0) {
3058
+ if (key.upArrow) {
3059
+ setSkillSelectIndex((prev) => (prev - 1 + skillList.length) % skillList.length);
3060
+ return;
3061
+ }
3062
+ if (key.downArrow) {
3063
+ setSkillSelectIndex((prev) => (prev + 1) % skillList.length);
3064
+ return;
3065
+ }
3066
+ if (key.tab) {
3067
+ const selected = skillList[skillSelectIndex];
3068
+ if (selected) {
3069
+ const slashIdx = input.lastIndexOf("/");
3070
+ if (slashIdx >= 0) {
3071
+ setInput(input.slice(0, slashIdx) + "/" + selected.name + " ");
3072
+ setInputKey((k) => k + 1);
3073
+ }
3074
+ }
3075
+ return;
3076
+ }
3077
+ }
1723
3078
  if (key.ctrl && _input === "c") {
1724
3079
  if (isStreaming) {
1725
3080
  abortRef.current?.abort();
@@ -1732,7 +3087,7 @@ function ChatSession({
1732
3087
  setInput(_input);
1733
3088
  }
1734
3089
  },
1735
- [selectingModel, modelSelectIndex, modelOptions, activeModel, isStreaming, handleCtrlC, input]
3090
+ [selectingModel, modelSelectIndex, modelOptions, activeModel, isStreaming, handleCtrlC, input, skills, skillSelectIndex, fileSelectIndex, getFilteredSkills, getFilteredFiles]
1736
3091
  )
1737
3092
  );
1738
3093
  useEffect3(() => {
@@ -1772,7 +3127,9 @@ function ChatSession({
1772
3127
  model: activeModel
1773
3128
  });
1774
3129
  const tracker = externalCostTracker ?? new CostTracker();
1775
- const session = new Session(provider, [], tracker, {
3130
+ const toolRegistry = new ToolRegistry();
3131
+ toolRegistry.registerAll(builtinTools);
3132
+ const session = new Session(provider, toolRegistry, tracker, {
1776
3133
  cwd: process.cwd()
1777
3134
  });
1778
3135
  sessionRef.current = session;
@@ -1784,7 +3141,7 @@ function ChatSession({
1784
3141
  if (!apiKey || !baseUrl) return;
1785
3142
  let cancelled = false;
1786
3143
  setBalanceLoading(true);
1787
- import("./deepseek-OUOEENTK.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
3144
+ import("./deepseek-UJXV2D6K.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
1788
3145
  const provider = new DeepSeekProvider2({
1789
3146
  apiKey,
1790
3147
  baseUrl,
@@ -1852,7 +3209,7 @@ function ChatSession({
1852
3209
  const handleSubmit = useCallback2(async (value) => {
1853
3210
  const trimmed = value.trim();
1854
3211
  if (!trimmed) return;
1855
- if (trimmed.startsWith("/")) {
3212
+ if (trimmed.startsWith("/") && trimmed.length > 1) {
1856
3213
  if (trimmed.toLowerCase() === "/model") {
1857
3214
  const curIdx = modelOptions.indexOf(activeModel);
1858
3215
  setModelSelectIndex(curIdx >= 0 ? curIdx : 0);
@@ -1952,6 +3309,20 @@ function ChatSession({
1952
3309
  return next;
1953
3310
  });
1954
3311
  break;
3312
+ case "tool_result":
3313
+ setCurrentContent("");
3314
+ currentContentRef.current = "";
3315
+ setCurrentToolCalls([]);
3316
+ currentToolCallsRef.current = [];
3317
+ setDisplayMessages((prev) => [
3318
+ ...prev,
3319
+ {
3320
+ role: "tool",
3321
+ content: event.result.success ? `\u2705 ${event.name}: ${event.result.data.slice(0, 500)}${event.result.data.length > 500 ? "..." : ""}` : `\u274C ${event.name}: ${event.result.error ?? "\u6267\u884C\u5931\u8D25"}`,
3322
+ diff: event.result.diff
3323
+ }
3324
+ ]);
3325
+ break;
1955
3326
  case "usage":
1956
3327
  setCurrentUsage(event.usage);
1957
3328
  setStreamingModel(event.model);
@@ -1998,7 +3369,7 @@ function ChatSession({
1998
3369
  ]);
1999
3370
  }
2000
3371
  }
2001
- }, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls]);
3372
+ }, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls, skills, skillSelectIndex, getFilteredSkills]);
2002
3373
  useEffect3(() => {
2003
3374
  if (!isStreaming && externalCostTracker) {
2004
3375
  setTodayCost(externalCostTracker.todayTotalCost);
@@ -2013,26 +3384,26 @@ function ChatSession({
2013
3384
  });
2014
3385
  }
2015
3386
  }, [currentUsage, streamingModel, currentCost]);
2016
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
2017
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", marginBottom: 1, children: [
2018
- /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
3387
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
3388
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", marginBottom: 1, children: [
3389
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
2019
3390
  const colorIndex = (i + offset) % CYBER_PALETTE.length;
2020
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
3391
+ return /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(Text9, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
2021
3392
  }) }),
2022
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", justifyContent: "center", children: [
2023
- /* @__PURE__ */ jsxs5(Text6, { color: "#00ff41", children: [
3393
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", justifyContent: "center", children: [
3394
+ /* @__PURE__ */ jsxs8(Text9, { color: "#00ff41", children: [
2024
3395
  " \u2714 ",
2025
- "\u5DF2\u52A0\u8F7D ",
3396
+ "\u5DF2\u5C31\u7EEA ",
2026
3397
  skillCount,
2027
3398
  " \u4E2A Skill"
2028
3399
  ] }),
2029
- /* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
3400
+ /* @__PURE__ */ jsxs8(Text9, { color: "#00ffff", children: [
2030
3401
  " \u2139 ",
2031
3402
  "\u5DF2\u5C31\u7EEA ",
2032
3403
  toolCount,
2033
3404
  " \u4E2A\u5DE5\u5177"
2034
3405
  ] }),
2035
- /* @__PURE__ */ jsxs5(Text6, { color: "#00ffff", children: [
3406
+ /* @__PURE__ */ jsxs8(Text9, { color: "#00ffff", children: [
2036
3407
  " \u{1F527} \u6A21\u578B ",
2037
3408
  SUPPORTED_MODELS[activeModel]?.displayName ?? activeModel
2038
3409
  ] }),
@@ -2040,40 +3411,49 @@ function ChatSession({
2040
3411
  const tip = cmdTips[cmdTipIndex % cmdTips.length];
2041
3412
  if (!tip) return null;
2042
3413
  const text = `${tip.name} ${tip.desc}`;
2043
- return /* @__PURE__ */ jsxs5(Text6, { children: [
2044
- /* @__PURE__ */ jsx5(Text6, { color: "#808080", children: " \u{1F4A1} " }),
2045
- cmdTipGradientColors.length > 0 ? text.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: cmdTipGradientColors[i] || void 0, children: ch }, i)) : /* @__PURE__ */ jsx5(Text6, { color: "#808080", children: text })
3414
+ return /* @__PURE__ */ jsxs8(Text9, { children: [
3415
+ /* @__PURE__ */ jsx8(Text9, { color: "#808080", children: " \u{1F4A1} " }),
3416
+ cmdTipGradientColors.length > 0 ? text.split("").map((ch, i) => /* @__PURE__ */ jsx8(Text9, { color: cmdTipGradientColors[i] || void 0, children: ch }, i)) : /* @__PURE__ */ jsx8(Text9, { color: "#808080", children: text })
2046
3417
  ] });
2047
3418
  })(),
2048
- verbose ? /* @__PURE__ */ jsx5(Text6, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
3419
+ verbose ? /* @__PURE__ */ jsx8(Text9, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
2049
3420
  ] }),
2050
- /* @__PURE__ */ jsxs5(Box5, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "flex-end", children: [
2051
- balanceLoading && balance === null ? /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: " \u23F3 \u67E5\u8BE2\u4F59\u989D..." }) : balance !== null ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
2052
- /* @__PURE__ */ jsx5(Text6, { color: "yellow", children: "\u{1F4B0} " }),
2053
- /* @__PURE__ */ jsxs5(Text6, { color: "yellow", children: [
3421
+ /* @__PURE__ */ jsxs8(Box8, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "flex-end", children: [
3422
+ balanceLoading && balance === null ? /* @__PURE__ */ jsx8(Text9, { color: "yellow", children: " \u23F3 \u67E5\u8BE2\u4F59\u989D..." }) : balance !== null ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", children: [
3423
+ /* @__PURE__ */ jsx8(Text9, { color: "yellow", children: "\u{1F4B0} " }),
3424
+ /* @__PURE__ */ jsxs8(Text9, { color: "yellow", children: [
2054
3425
  "\u4F59\u989D \xA5",
2055
3426
  balance.toFixed(2)
2056
3427
  ] })
2057
3428
  ] }) : null,
2058
- todayCost !== null ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
2059
- /* @__PURE__ */ jsx5(Text6, { color: "cyan", children: "\u{1F4CA} " }),
2060
- /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
3429
+ todayCost !== null ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", children: [
3430
+ /* @__PURE__ */ jsx8(Text9, { color: "cyan", children: "\u{1F4CA} " }),
3431
+ /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
2061
3432
  "\u4ECA\u65E5 \xA5",
2062
3433
  todayCost.toFixed(2)
2063
3434
  ] })
2064
3435
  ] }) : null
2065
3436
  ] })
2066
3437
  ] }),
2067
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
2068
- /* @__PURE__ */ jsx5(Static, { items: displayMessages, children: (msg, i) => {
3438
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
3439
+ /* @__PURE__ */ jsx8(Static, { items: displayMessages, children: (msg, i) => {
2069
3440
  if (msg.role === "user") {
2070
- return /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
2071
- /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u{1F464}" }) }),
2072
- /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: /* @__PURE__ */ jsx5(Text6, { wrap: "wrap", children: msg.content }) })
3441
+ return /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
3442
+ /* @__PURE__ */ jsx8(Box8, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text9, { bold: true, color: "#00ff41", children: "\u{1F464}" }) }),
3443
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx8(Text9, { wrap: "wrap", children: msg.content }) })
3444
+ ] }, i);
3445
+ }
3446
+ if (msg.role === "tool") {
3447
+ return /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
3448
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "row", children: [
3449
+ /* @__PURE__ */ jsx8(Box8, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text9, { bold: true, color: "#f59e0b", children: "\u{1F527}" }) }),
3450
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, children: /* @__PURE__ */ jsx8(Text9, { wrap: "wrap", children: msg.content }) })
3451
+ ] }),
3452
+ msg.diff && /* @__PURE__ */ jsx8(DiffPreview, { diff: msg.diff })
2073
3453
  ] }, i);
2074
3454
  }
2075
3455
  const detail = msg.assistantDetail;
2076
- return /* @__PURE__ */ jsx5(
3456
+ return /* @__PURE__ */ jsx8(
2077
3457
  AssistantMessage,
2078
3458
  {
2079
3459
  content: msg.content,
@@ -2087,7 +3467,7 @@ function ChatSession({
2087
3467
  i
2088
3468
  );
2089
3469
  } }, staticKey),
2090
- isStreaming && /* @__PURE__ */ jsx5(
3470
+ isStreaming && /* @__PURE__ */ jsx8(
2091
3471
  AssistantMessage,
2092
3472
  {
2093
3473
  content: currentContent,
@@ -2095,25 +3475,25 @@ function ChatSession({
2095
3475
  isStreaming: true
2096
3476
  }
2097
3477
  ),
2098
- isStreaming && !currentContent && currentToolCalls.length === 0 && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, marginLeft: 4, children: /* @__PURE__ */ jsx5(Spinner, { type: "dots", label: "\u601D\u8003\u4E2D..." }) }),
2099
- !isStreaming && streamError && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
3478
+ isStreaming && !currentContent && currentToolCalls.length === 0 && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, marginLeft: 4, children: /* @__PURE__ */ jsx8(Spinner, { type: "dots", label: "\u601D\u8003\u4E2D..." }) }),
3479
+ !isStreaming && streamError && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs8(Text9, { color: "red", children: [
2100
3480
  "\u26A0 ",
2101
3481
  streamError
2102
3482
  ] }) })
2103
3483
  ] }),
2104
- selectingModel ? /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
2105
- /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
2106
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
2107
- /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ffff", children: "\u9009\u62E9\u6A21\u578B\uFF1A" }),
3484
+ selectingModel ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
3485
+ /* @__PURE__ */ jsx8(Text9, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
3486
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
3487
+ /* @__PURE__ */ jsx8(Text9, { bold: true, color: "#00ffff", children: "\u9009\u62E9\u6A21\u578B\uFF1A" }),
2108
3488
  modelOptions.map((id, i) => {
2109
3489
  const meta = SUPPORTED_MODELS[id];
2110
3490
  const isCurrent = id === activeModel;
2111
3491
  const isSelected = i === modelSelectIndex;
2112
3492
  const marker = isSelected ? " > " : " ";
2113
3493
  const suffix = isCurrent ? " (\u5F53\u524D)" : "";
2114
- return /* @__PURE__ */ jsxs5(Box5, { children: [
2115
- /* @__PURE__ */ jsxs5(
2116
- Text6,
3494
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
3495
+ /* @__PURE__ */ jsxs8(
3496
+ Text9,
2117
3497
  {
2118
3498
  color: isSelected ? "#00ff41" : void 0,
2119
3499
  bold: isSelected,
@@ -2124,40 +3504,43 @@ function ChatSession({
2124
3504
  ]
2125
3505
  }
2126
3506
  ),
2127
- isSelected && /* @__PURE__ */ jsxs5(Text6, { color: "#808080", children: [
3507
+ isSelected && /* @__PURE__ */ jsxs8(Text9, { color: "#808080", children: [
2128
3508
  " \u2014 ",
2129
3509
  id
2130
3510
  ] })
2131
3511
  ] }, id);
2132
3512
  }),
2133
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
3513
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text9, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
2134
3514
  ] }),
2135
- /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
2136
- ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
2137
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
2138
- /* @__PURE__ */ jsxs5(Box5, { children: [
2139
- /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
2140
- /* @__PURE__ */ jsx5(Box5, { flexGrow: 1, children: !input && !isStreaming && idlePlaceholder && gradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: idlePlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: gradientColors[i] ?? void 0, children: ch }, i)) }) : !input && isStreaming && streamingPlaceholder && streamingGradientColors.length > 0 ? /* @__PURE__ */ jsx5(Text6, { children: streamingPlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx5(Text6, { color: streamingGradientColors[i] ?? void 0, children: ch }, i)) }) : /* @__PURE__ */ jsx5(
3515
+ /* @__PURE__ */ jsx8(Text9, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
3516
+ ] }) : /* @__PURE__ */ jsxs8(Fragment, { children: [
3517
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text9, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
3518
+ /* @__PURE__ */ jsxs8(Box8, { children: [
3519
+ /* @__PURE__ */ jsx8(Box8, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx8(Text9, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
3520
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, children: !input && !isStreaming && idlePlaceholder && gradientColors.length > 0 ? /* @__PURE__ */ jsx8(Text9, { children: idlePlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx8(Text9, { color: gradientColors[i] ?? void 0, children: ch }, i)) }) : !input && isStreaming && streamingPlaceholder && streamingGradientColors.length > 0 ? /* @__PURE__ */ jsx8(Text9, { children: streamingPlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx8(Text9, { color: streamingGradientColors[i] ?? void 0, children: ch }, i)) }) : /* @__PURE__ */ jsx8(
2141
3521
  TextInput,
2142
3522
  {
2143
3523
  value: input,
2144
3524
  onChange: setInput,
2145
3525
  onSubmit: handleSubmit,
2146
3526
  placeholder: ""
2147
- }
3527
+ },
3528
+ inputKey
2148
3529
  ) })
2149
3530
  ] }),
2150
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text6, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) })
3531
+ /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(Text9, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
3532
+ /* @__PURE__ */ jsx8(SkillSelector, { skills, input, selectedIndex: skillSelectIndex }),
3533
+ /* @__PURE__ */ jsx8(FileSelector, { files, input, selectedIndex: fileSelectIndex })
2151
3534
  ] }),
2152
- doubleCtrlC && !isStreaming && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) }),
2153
- isStreaming && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text6, { color: "yellow", dimColor: true, children: " \u63D0\u793A\uFF1A\u6309 Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42" }) })
3535
+ doubleCtrlC && !isStreaming && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text9, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) }),
3536
+ isStreaming && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text9, { color: "yellow", dimColor: true, children: " \u63D0\u793A\uFF1A\u6309 Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42" }) })
2154
3537
  ] });
2155
3538
  }
2156
3539
 
2157
3540
  // src/ui/GamePicker.tsx
2158
- import { Box as Box6, Text as Text7, useInput as useInput2 } from "ink";
3541
+ import { Box as Box9, Text as Text10, useInput as useInput2 } from "ink";
2159
3542
  import { useState as useState4, useCallback as useCallback3 } from "react";
2160
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3543
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2161
3544
  function GamePicker({ games, onSelect, onExit, onBackToChat }) {
2162
3545
  const [selectedIndex, setSelectedIndex] = useState4(0);
2163
3546
  const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
@@ -2185,18 +3568,18 @@ function GamePicker({ games, onSelect, onExit, onBackToChat }) {
2185
3568
  [games, selectedIndex, onSelect, onExit, onBackToChat, handleCtrlC]
2186
3569
  )
2187
3570
  );
2188
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2189
- /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text7, { bold: true, color: "#00ffff", children: "\u{1F3AE} \u6E38\u620F\u5217\u8868" }) }),
2190
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: games.map((game, index) => {
3571
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3572
+ /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: "\u{1F3AE} \u6E38\u620F\u5217\u8868" }) }),
3573
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: games.map((game, index) => {
2191
3574
  const isSelected = index === selectedIndex;
2192
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "row", children: [
2193
- /* @__PURE__ */ jsx6(Box6, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx6(Text7, { bold: true, color: "#00ff41", children: "\u25B8 " }) : /* @__PURE__ */ jsx6(Text7, { children: " " }) }),
2194
- /* @__PURE__ */ jsx6(Box6, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text7, { bold: true, color: isSelected ? "#00ff41" : "#ffffff", children: game.name }) }),
2195
- /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text7, { color: "#888888", children: game.description }) })
3575
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", children: [
3576
+ /* @__PURE__ */ jsx9(Box9, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ff41", children: "\u25B8 " }) : /* @__PURE__ */ jsx9(Text10, { children: " " }) }),
3577
+ /* @__PURE__ */ jsx9(Box9, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: isSelected ? "#00ff41" : "#ffffff", children: game.name }) }),
3578
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: game.description }) })
2196
3579
  ] }, game.id);
2197
3580
  }) }),
2198
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
2199
- doubleCtrlC && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text7, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
3581
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
3582
+ doubleCtrlC && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
2200
3583
  ] });
2201
3584
  }
2202
3585
 
@@ -2213,9 +3596,9 @@ function listGames() {
2213
3596
  }
2214
3597
 
2215
3598
  // src/game/brick-breaker/index.tsx
2216
- import { Box as Box7, Text as Text8, useInput as useInput3, render as render2 } from "ink";
3599
+ import { Box as Box10, Text as Text11, useInput as useInput3, render as render2 } from "ink";
2217
3600
  import { useState as useState5, useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4 } from "react";
2218
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3601
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2219
3602
  var GAME_WIDTH = 40;
2220
3603
  var GAME_HEIGHT = 18;
2221
3604
  var PADDLE_WIDTH = 9;
@@ -2405,49 +3788,49 @@ function BrickBreakerGame({ onExit: _onExit }) {
2405
3788
  const board = buildBoard(s);
2406
3789
  const def = getLevel(s.level);
2407
3790
  void tick;
2408
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2409
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "row", children: [
2410
- /* @__PURE__ */ jsx7(Box7, { width: 20, children: /* @__PURE__ */ jsxs7(Text8, { children: [
3791
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
3792
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "row", children: [
3793
+ /* @__PURE__ */ jsx10(Box10, { width: 20, children: /* @__PURE__ */ jsxs10(Text11, { children: [
2411
3794
  "\u5173\u5361 ",
2412
3795
  s.level,
2413
3796
  ": ",
2414
- /* @__PURE__ */ jsx7(Text8, { color: "cyan", children: def.desc })
3797
+ /* @__PURE__ */ jsx10(Text11, { color: "cyan", children: def.desc })
2415
3798
  ] }) }),
2416
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
3799
+ /* @__PURE__ */ jsx10(Box10, { width: 12, children: /* @__PURE__ */ jsxs10(Text11, { children: [
2417
3800
  "\u5206\u6570: ",
2418
- /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: String(s.score).padStart(3, "0") })
3801
+ /* @__PURE__ */ jsx10(Text11, { color: "yellow", children: String(s.score).padStart(3, "0") })
2419
3802
  ] }) }),
2420
- /* @__PURE__ */ jsx7(Box7, { width: 12, children: /* @__PURE__ */ jsxs7(Text8, { children: [
3803
+ /* @__PURE__ */ jsx10(Box10, { width: 12, children: /* @__PURE__ */ jsxs10(Text11, { children: [
2421
3804
  "\u751F\u547D: ",
2422
- /* @__PURE__ */ jsx7(Text8, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
3805
+ /* @__PURE__ */ jsx10(Text11, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
2423
3806
  ] }) }),
2424
- /* @__PURE__ */ jsx7(Box7, { width: 10, children: /* @__PURE__ */ jsxs7(Text8, { children: [
3807
+ /* @__PURE__ */ jsx10(Box10, { width: 10, children: /* @__PURE__ */ jsxs10(Text11, { children: [
2425
3808
  "\u7816\u5757: ",
2426
- /* @__PURE__ */ jsx7(Text8, { color: "cyan", children: aliveCount })
3809
+ /* @__PURE__ */ jsx10(Text11, { color: "cyan", children: aliveCount })
2427
3810
  ] }) }),
2428
- /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text8, { color: s.paused ? "gray" : "green", children: [
3811
+ /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsxs10(Text11, { color: s.paused ? "gray" : "green", children: [
2429
3812
  "[",
2430
3813
  s.paused ? "\u6682\u505C" : "\u8FD0\u884C\u4E2D",
2431
3814
  "]"
2432
3815
  ] }) })
2433
3816
  ] }),
2434
- /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2435
- /* @__PURE__ */ jsxs7(Text8, { children: [
3817
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
3818
+ /* @__PURE__ */ jsxs10(Text11, { children: [
2436
3819
  "\u250C",
2437
3820
  "\u2500".repeat(GAME_WIDTH),
2438
3821
  "\u2510"
2439
3822
  ] }),
2440
- /* @__PURE__ */ jsx7(Text8, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
2441
- /* @__PURE__ */ jsxs7(Text8, { children: [
3823
+ /* @__PURE__ */ jsx10(Text11, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
3824
+ /* @__PURE__ */ jsxs10(Text11, { children: [
2442
3825
  "\u2514",
2443
3826
  "\u2500".repeat(GAME_WIDTH),
2444
3827
  "\u2518"
2445
3828
  ] })
2446
3829
  ] }),
2447
- selectingLevel && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2448
- /* @__PURE__ */ jsx7(Text8, { bold: true, color: "yellow", children: "\u9009\u62E9\u5173\u5361" }),
2449
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "row", flexWrap: "wrap", children: LEVELS.map((lv, i) => /* @__PURE__ */ jsx7(Box7, { width: 22, children: /* @__PURE__ */ jsxs7(Text8, { children: [
2450
- /* @__PURE__ */ jsx7(Text8, { color: initialLevel === i + 1 ? "green" : "white", children: i + 1 === 10 ? "0" : String(i + 1) }),
3830
+ selectingLevel && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
3831
+ /* @__PURE__ */ jsx10(Text11, { bold: true, color: "yellow", children: "\u9009\u62E9\u5173\u5361" }),
3832
+ /* @__PURE__ */ jsx10(Box10, { flexDirection: "row", flexWrap: "wrap", children: LEVELS.map((lv, i) => /* @__PURE__ */ jsx10(Box10, { width: 22, children: /* @__PURE__ */ jsxs10(Text11, { children: [
3833
+ /* @__PURE__ */ jsx10(Text11, { color: initialLevel === i + 1 ? "green" : "white", children: i + 1 === 10 ? "0" : String(i + 1) }),
2451
3834
  ". ",
2452
3835
  lv.desc,
2453
3836
  " (",
@@ -2456,16 +3839,16 @@ function BrickBreakerGame({ onExit: _onExit }) {
2456
3839
  lv.cols,
2457
3840
  ")"
2458
3841
  ] }) }, i)) }),
2459
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
3842
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text11, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
2460
3843
  ] }),
2461
- !selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
2462
- /* @__PURE__ */ jsx7(Text8, { bold: true, color: s.gameOver ? "red" : "green", children: s.gameOver ? "\u6E38\u620F\u7ED3\u675F\uFF01" : "\u606D\u559C\u901A\u5173\uFF01" }),
2463
- /* @__PURE__ */ jsxs7(Text8, { children: [
3844
+ !selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, children: [
3845
+ /* @__PURE__ */ jsx10(Text11, { bold: true, color: s.gameOver ? "red" : "green", children: s.gameOver ? "\u6E38\u620F\u7ED3\u675F\uFF01" : "\u606D\u559C\u901A\u5173\uFF01" }),
3846
+ /* @__PURE__ */ jsxs10(Text11, { children: [
2464
3847
  " \u5206\u6570: ",
2465
- /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: s.score })
3848
+ /* @__PURE__ */ jsx10(Text11, { color: "yellow", children: s.score })
2466
3849
  ] })
2467
3850
  ] }),
2468
- !selectingLevel && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: s.gameOver || s.win ? /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 r \u91CD\u5F00 l \u9009\u5173 q \u9000\u51FA" }) : /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 p \u6682\u505C q \u9000\u51FA" }) })
3851
+ !selectingLevel && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: s.gameOver || s.win ? /* @__PURE__ */ jsx10(Text11, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 r \u91CD\u5F00 l \u9009\u5173 q \u9000\u51FA" }) : /* @__PURE__ */ jsx10(Text11, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 p \u6682\u505C q \u9000\u51FA" }) })
2469
3852
  ] });
2470
3853
  }
2471
3854
  var brick_breaker_default = {
@@ -2473,11 +3856,11 @@ var brick_breaker_default = {
2473
3856
  name: "Brick Breaker",
2474
3857
  description: "\u7ECF\u5178\u6253\u7816\u5757\u6E38\u620F\uFF0C10 \u4E2A\u5173\u5361\u53EF\u9009\uFF01",
2475
3858
  play: async () => {
2476
- await new Promise((resolve) => {
3859
+ await new Promise((resolve2) => {
2477
3860
  const { unmount } = render2(
2478
- /* @__PURE__ */ jsx7(BrickBreakerGame, { onExit: () => {
3861
+ /* @__PURE__ */ jsx10(BrickBreakerGame, { onExit: () => {
2479
3862
  unmount();
2480
- resolve();
3863
+ resolve2();
2481
3864
  } })
2482
3865
  );
2483
3866
  });
@@ -2485,9 +3868,9 @@ var brick_breaker_default = {
2485
3868
  };
2486
3869
 
2487
3870
  // src/game/coder-check/index.tsx
2488
- import { Box as Box8, Text as Text9, useInput as useInput4, render as render3 } from "ink";
3871
+ import { Box as Box11, Text as Text12, useInput as useInput4, render as render3 } from "ink";
2489
3872
  import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback5 } from "react";
2490
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3873
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2491
3874
  var GAME_W = 66;
2492
3875
  var GAME_H = 20;
2493
3876
  var SCORE_H = 6;
@@ -2958,44 +4341,44 @@ function CoderCheck({ onExit: _onExit }) {
2958
4341
  const scoreLines = buildScoreLines(scoreStr);
2959
4342
  const view = buildGameView(s, scoreLines, scoreColor, s.message);
2960
4343
  void tick;
2961
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, children: [
2962
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "row", children: /* @__PURE__ */ jsxs8(Text9, { children: [
4344
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, children: [
4345
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", children: /* @__PURE__ */ jsxs11(Text12, { children: [
2963
4346
  "\u751F\u547D ",
2964
- /* @__PURE__ */ jsx8(Text9, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
4347
+ /* @__PURE__ */ jsx11(Text12, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
2965
4348
  " ",
2966
4349
  "\u901F\u5EA6 ",
2967
- /* @__PURE__ */ jsxs8(Text9, { color: "cyan", children: [
4350
+ /* @__PURE__ */ jsxs11(Text12, { color: "cyan", children: [
2968
4351
  "Lv.",
2969
4352
  Math.floor(s.speed * 10)
2970
4353
  ] })
2971
4354
  ] }) }),
2972
- !s.gameOver && s.target && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text9, { children: [
4355
+ !s.gameOver && s.target && /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text12, { children: [
2973
4356
  "\u6253\u5B57: ",
2974
- /* @__PURE__ */ jsx8(Text9, { color: "green", children: s.typed }),
2975
- /* @__PURE__ */ jsx8(Text9, { color: "white", children: s.target.slice(s.typed.length) })
4357
+ /* @__PURE__ */ jsx11(Text12, { color: "green", children: s.typed }),
4358
+ /* @__PURE__ */ jsx11(Text12, { color: "white", children: s.target.slice(s.typed.length) })
2976
4359
  ] }) }),
2977
- /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
2978
- /* @__PURE__ */ jsxs8(Text9, { children: [
4360
+ /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, children: [
4361
+ /* @__PURE__ */ jsxs11(Text12, { children: [
2979
4362
  "\u250C",
2980
4363
  "\u2500".repeat(GAME_W),
2981
4364
  "\u2510"
2982
4365
  ] }),
2983
- view.map((row, i) => /* @__PURE__ */ jsx8(Text9, { children: `\u2502${row}\u2502` }, i)),
2984
- /* @__PURE__ */ jsxs8(Text9, { children: [
4366
+ view.map((row, i) => /* @__PURE__ */ jsx11(Text12, { children: `\u2502${row}\u2502` }, i)),
4367
+ /* @__PURE__ */ jsxs11(Text12, { children: [
2985
4368
  "\u2514",
2986
4369
  "\u2500".repeat(GAME_W),
2987
4370
  "\u2518"
2988
4371
  ] })
2989
4372
  ] }),
2990
- s.gameOver && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
2991
- /* @__PURE__ */ jsx8(Text9, { bold: true, color: "red", children: "\u6E38\u620F\u7ED3\u675F\uFF01" }),
2992
- /* @__PURE__ */ jsxs8(Text9, { children: [
4373
+ s.gameOver && /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
4374
+ /* @__PURE__ */ jsx11(Text12, { bold: true, color: "red", children: "\u6E38\u620F\u7ED3\u675F\uFF01" }),
4375
+ /* @__PURE__ */ jsxs11(Text12, { children: [
2993
4376
  " \u5F97\u5206: ",
2994
- /* @__PURE__ */ jsx8(Text9, { color: "yellow", children: s.score }),
4377
+ /* @__PURE__ */ jsx11(Text12, { color: "yellow", children: s.score }),
2995
4378
  " r \u91CD\u5F00 q \u9000\u51FA"
2996
4379
  ] })
2997
4380
  ] }),
2998
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text9, { dimColor: true, children: "\u6253\u5B57\u6D88\u9664\u5355\u8BCD \u7A7A\u683C/Ctrl+P\u6682\u505C Ctrl+Q\u9000\u51FA" }) })
4381
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: "\u6253\u5B57\u6D88\u9664\u5355\u8BCD \u7A7A\u683C/Ctrl+P\u6682\u505C Ctrl+Q\u9000\u51FA" }) })
2999
4382
  ] });
3000
4383
  }
3001
4384
  var coder_check_default = {
@@ -3003,11 +4386,11 @@ var coder_check_default = {
3003
4386
  name: "Coder Check",
3004
4387
  description: "\u6781\u901F\u6253\u5B57\u6E38\u620F\uFF0C\u8F93\u5165\u5355\u8BCD\u6D88\u9664\u5B83\u4EEC\uFF01",
3005
4388
  play: async () => {
3006
- await new Promise((resolve) => {
4389
+ await new Promise((resolve2) => {
3007
4390
  const { unmount } = render3(
3008
- /* @__PURE__ */ jsx8(CoderCheck, { onExit: () => {
4391
+ /* @__PURE__ */ jsx11(CoderCheck, { onExit: () => {
3009
4392
  unmount();
3010
- resolve();
4393
+ resolve2();
3011
4394
  } })
3012
4395
  );
3013
4396
  });
@@ -3026,10 +4409,10 @@ import { render as render4 } from "ink";
3026
4409
  import chalk4 from "chalk";
3027
4410
 
3028
4411
  // src/stock/StockList.tsx
3029
- import { Box as Box9, Text as Text10, useInput as useInput5 } from "ink";
4412
+ import { Box as Box12, Text as Text13, useInput as useInput5 } from "ink";
3030
4413
  import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6 } from "react";
3031
4414
  import asciichart from "asciichart";
3032
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
4415
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3033
4416
  var MINUTE_API = "https://web.ifzq.gtimg.cn/appstock/app/minute/query?code={code}&r=0.1";
3034
4417
  function normalizeApiCode(code) {
3035
4418
  if (code.startsWith("sh") || code.startsWith("sz")) return code;
@@ -3226,57 +4609,57 @@ function StockList({ codes, onExit, onBackToChat }) {
3226
4609
  );
3227
4610
  if (detailView) {
3228
4611
  if (detailLoading) {
3229
- return /* @__PURE__ */ jsx9(Box9, { paddingLeft: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
4612
+ return /* @__PURE__ */ jsx12(Box12, { paddingLeft: 1, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
3230
4613
  }
3231
4614
  return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown, currentTime);
3232
4615
  }
3233
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3234
- /* @__PURE__ */ jsxs9(Box9, { marginBottom: 1, justifyContent: "space-between", children: [
3235
- /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
3236
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
4616
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
4617
+ /* @__PURE__ */ jsxs12(Box12, { marginBottom: 1, justifyContent: "space-between", children: [
4618
+ /* @__PURE__ */ jsx12(Text13, { bold: true, color: "#00ffff", children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
4619
+ /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
3237
4620
  " \u{1F550} ",
3238
4621
  currentTime
3239
4622
  ] }),
3240
- /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
4623
+ /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
3241
4624
  ] }),
3242
- /* @__PURE__ */ jsxs9(Box9, { children: [
3243
- /* @__PURE__ */ jsx9(Box9, { width: 3 }),
3244
- /* @__PURE__ */ jsx9(Box9, { width: 9, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u4EE3\u7801" }) }),
3245
- /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u540D\u79F0" }) }),
3246
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
3247
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6DA8\u8DCC\u5E45" }) }),
3248
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
3249
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u9AD8" }) }),
3250
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6700\u4F4E" }) }),
3251
- /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u6210\u4EA4\u989D" }) })
4625
+ /* @__PURE__ */ jsxs12(Box12, { children: [
4626
+ /* @__PURE__ */ jsx12(Box12, { width: 3 }),
4627
+ /* @__PURE__ */ jsx12(Box12, { width: 9, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u4EE3\u7801" }) }),
4628
+ /* @__PURE__ */ jsx12(Box12, { width: 16, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u540D\u79F0" }) }),
4629
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
4630
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u6DA8\u8DCC\u5E45" }) }),
4631
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
4632
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u6700\u9AD8" }) }),
4633
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u6700\u4F4E" }) }),
4634
+ /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u6210\u4EA4\u989D" }) })
3252
4635
  ] }),
3253
- /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
3254
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: stocks.map((stock, index) => {
4636
+ /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
4637
+ /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", children: stocks.map((stock, index) => {
3255
4638
  const isSelected = index === selectedIndex;
3256
4639
  const isUp = stock.changePercent >= 0;
3257
4640
  const color = isUp ? "#ff1493" : "#00ff41";
3258
- return /* @__PURE__ */ jsxs9(Box9, { children: [
3259
- /* @__PURE__ */ jsx9(Box9, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: "\u25B8 " }) : /* @__PURE__ */ jsx9(Text10, { children: " " }) }),
3260
- /* @__PURE__ */ jsx9(Box9, { width: 9, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: isSelected ? "#00ffff" : "#ffffff", children: stock.code }) }),
3261
- /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { color: isSelected ? "#ffffff" : "#cccccc", children: stock.name }) }),
3262
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color, children: formatPrice(stock.price) }) }),
3263
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
4641
+ return /* @__PURE__ */ jsxs12(Box12, { children: [
4642
+ /* @__PURE__ */ jsx12(Box12, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx12(Text13, { bold: true, color: "#00ffff", children: "\u25B8 " }) : /* @__PURE__ */ jsx12(Text13, { children: " " }) }),
4643
+ /* @__PURE__ */ jsx12(Box12, { width: 9, children: /* @__PURE__ */ jsx12(Text13, { bold: true, color: isSelected ? "#00ffff" : "#ffffff", children: stock.code }) }),
4644
+ /* @__PURE__ */ jsx12(Box12, { width: 16, children: /* @__PURE__ */ jsx12(Text13, { color: isSelected ? "#ffffff" : "#cccccc", children: stock.name }) }),
4645
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsx12(Text13, { bold: true, color, children: formatPrice(stock.price) }) }),
4646
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsxs12(Text13, { color, children: [
3264
4647
  isUp ? "+" : "",
3265
4648
  stock.changePercent.toFixed(2),
3266
4649
  "%"
3267
4650
  ] }) }),
3268
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsxs9(Text10, { color, children: [
4651
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsxs12(Text13, { color, children: [
3269
4652
  isUp ? "+" : "",
3270
4653
  stock.changeAmount.toFixed(3)
3271
4654
  ] }) }),
3272
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { color: "#cccccc", children: formatPrice(stock.high) }) }),
3273
- /* @__PURE__ */ jsx9(Box9, { width: 12, children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: formatPrice(stock.low) }) }),
3274
- /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: formatAmount(stock.amount) }) })
4655
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsx12(Text13, { color: "#cccccc", children: formatPrice(stock.high) }) }),
4656
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsx12(Text13, { color: "#888888", children: formatPrice(stock.low) }) }),
4657
+ /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsx12(Text13, { color: "#888888", children: formatAmount(stock.amount) }) })
3275
4658
  ] }, stock.code);
3276
4659
  }) }),
3277
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: ` \u2191/\u2193 \u9009\u62E9 Enter \u8BE6\u60C5 r \u624B\u52A8\u5237\u65B0 q \u8FD4\u56DE` }) }),
3278
- /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ~/.dskcode/settings.json` }) }),
3279
- doubleCtrlC && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
4660
+ /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: ` \u2191/\u2193 \u9009\u62E9 Enter \u8BE6\u60C5 r \u624B\u52A8\u5237\u65B0 q \u8FD4\u56DE` }) }),
4661
+ /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ~/.dskcode/settings.json` }) }),
4662
+ doubleCtrlC && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text13, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
3280
4663
  ] });
3281
4664
  }
3282
4665
  function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
@@ -3294,33 +4677,33 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
3294
4677
  raw = raw.replaceAll("\u256D", "\u250C").replaceAll("\u256E", "\u2510").replaceAll("\u2570", "\u2514").replaceAll("\u256F", "\u2518");
3295
4678
  chartLines = raw.split("\n");
3296
4679
  }
3297
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingLeft: 1, children: [
3298
- /* @__PURE__ */ jsxs9(Box9, { marginBottom: 1, justifyContent: "space-between", children: [
3299
- /* @__PURE__ */ jsxs9(Box9, { children: [
3300
- /* @__PURE__ */ jsxs9(Text10, { bold: true, color: "#00ffff", children: [
4680
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingLeft: 1, children: [
4681
+ /* @__PURE__ */ jsxs12(Box12, { marginBottom: 1, justifyContent: "space-between", children: [
4682
+ /* @__PURE__ */ jsxs12(Box12, { children: [
4683
+ /* @__PURE__ */ jsxs12(Text13, { bold: true, color: "#00ffff", children: [
3301
4684
  " \u{1F4CA} ",
3302
4685
  stock.name,
3303
4686
  " "
3304
4687
  ] }),
3305
- /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: stock.code }),
3306
- currentTime && /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
4688
+ /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: stock.code }),
4689
+ currentTime && /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
3307
4690
  " \u{1F550} ",
3308
4691
  currentTime
3309
4692
  ] })
3310
4693
  ] }),
3311
- /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
4694
+ /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
3312
4695
  ] }),
3313
- /* @__PURE__ */ jsxs9(Box9, { children: [
3314
- /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
3315
- /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { bold: true, color: colorCode, children: [
4696
+ /* @__PURE__ */ jsxs12(Box12, { children: [
4697
+ /* @__PURE__ */ jsx12(Box12, { width: 16, children: /* @__PURE__ */ jsx12(Text13, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
4698
+ /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { bold: true, color: colorCode, children: [
3316
4699
  arrow,
3317
4700
  " ",
3318
4701
  formatPrice(stock.price)
3319
4702
  ] }) })
3320
4703
  ] }),
3321
- /* @__PURE__ */ jsxs9(Box9, { children: [
3322
- /* @__PURE__ */ jsx9(Box9, { width: 16, children: /* @__PURE__ */ jsx9(Text10, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
3323
- /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text10, { color: colorCode, children: [
4704
+ /* @__PURE__ */ jsxs12(Box12, { children: [
4705
+ /* @__PURE__ */ jsx12(Box12, { width: 16, children: /* @__PURE__ */ jsx12(Text13, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
4706
+ /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: colorCode, children: [
3324
4707
  isUp ? "+" : "",
3325
4708
  stock.changePercent.toFixed(2),
3326
4709
  "%",
@@ -3329,13 +4712,93 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
3329
4712
  stock.changeAmount.toFixed(3)
3330
4713
  ] }) })
3331
4714
  ] }),
3332
- chartLines.length > 0 && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: chartLines.map((line, i) => /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { color: colorCode, children: line || " " }) }, i)) }),
3333
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
4715
+ chartLines.length > 0 && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, flexDirection: "column", children: chartLines.map((line, i) => /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsx12(Text13, { color: colorCode, children: line || " " }) }, i)) }),
4716
+ /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
3334
4717
  ] });
3335
4718
  }
3336
4719
 
4720
+ // src/utils/scan-files.ts
4721
+ import { readdir as readdir5 } from "fs/promises";
4722
+ import { join as join7, relative as relative4 } from "path";
4723
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([
4724
+ "node_modules",
4725
+ ".git",
4726
+ ".svn",
4727
+ ".hg",
4728
+ ".dskcode",
4729
+ ".claude",
4730
+ "dist",
4731
+ "build",
4732
+ ".next",
4733
+ ".turbo",
4734
+ ".nx",
4735
+ "coverage",
4736
+ ".cache",
4737
+ ".nyc_output",
4738
+ ".vscode",
4739
+ ".idea",
4740
+ "__pycache__",
4741
+ ".venv",
4742
+ "venv",
4743
+ ".tox",
4744
+ "target",
4745
+ "vendor",
4746
+ "bower_components",
4747
+ "jspm_packages"
4748
+ ]);
4749
+ var SOURCE_EXTS = /* @__PURE__ */ new Set([
4750
+ ".ts",
4751
+ ".tsx",
4752
+ ".js",
4753
+ ".jsx",
4754
+ ".mjs",
4755
+ ".cjs",
4756
+ ".vue",
4757
+ ".svelte",
4758
+ ".astro",
4759
+ ".css",
4760
+ ".scss",
4761
+ ".less",
4762
+ ".html",
4763
+ ".json",
4764
+ ".md",
4765
+ ".yaml",
4766
+ ".yml",
4767
+ ".toml"
4768
+ ]);
4769
+ async function scanProjectFiles(baseDir, dir) {
4770
+ const currentDir = dir ?? baseDir;
4771
+ let entries;
4772
+ try {
4773
+ entries = await readdir5(currentDir, { withFileTypes: true });
4774
+ } catch {
4775
+ return [];
4776
+ }
4777
+ const files = [];
4778
+ const dirs = [];
4779
+ for (const entry of entries) {
4780
+ const name = entry.name;
4781
+ if (IGNORE_DIRS.has(name) || name.startsWith(".")) continue;
4782
+ if (entry.isDirectory()) {
4783
+ dirs.push(name);
4784
+ } else if (entry.isFile()) {
4785
+ const ext = name.slice(name.lastIndexOf(".")).toLowerCase();
4786
+ if (SOURCE_EXTS.has(ext)) {
4787
+ files.push(relative4(baseDir, join7(currentDir, name)));
4788
+ }
4789
+ }
4790
+ }
4791
+ const nested = await Promise.all(
4792
+ dirs.map((d) => scanProjectFiles(baseDir, join7(currentDir, d)))
4793
+ );
4794
+ for (const n of nested) {
4795
+ files.push(...n);
4796
+ }
4797
+ return files;
4798
+ }
4799
+
3337
4800
  // src/cli/index.tsx
3338
- import { jsx as jsx10 } from "react/jsx-runtime";
4801
+ import { jsx as jsx13 } from "react/jsx-runtime";
3339
4802
  var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game", "stock"];
3340
4803
  function createCli() {
3341
4804
  const program2 = new Command();
@@ -3369,9 +4832,11 @@ function createCli() {
3369
4832
  startChat(ctx, costTracker);
3370
4833
  });
3371
4834
  async function startChat(ctx, costTracker) {
3372
- const [globalSkillCount, localSkillCount] = await Promise.all([
4835
+ const [globalSkillCount, localSkillCount, skills, files] = await Promise.all([
3373
4836
  countDskcodeSkills(),
3374
- countProjectLocalSkills(process.cwd())
4837
+ countProjectLocalSkills(process.cwd()),
4838
+ getAllSkills(process.cwd()),
4839
+ scanProjectFiles(process.cwd())
3375
4840
  ]);
3376
4841
  const skillCount = globalSkillCount + localSkillCount;
3377
4842
  const defaultProvider = ctx?.config.providers.find(
@@ -3379,10 +4844,12 @@ function createCli() {
3379
4844
  );
3380
4845
  const model = defaultProvider?.model ?? "deepseek-v4-flash";
3381
4846
  const chatApp = renderApp(
3382
- /* @__PURE__ */ jsx10(
4847
+ /* @__PURE__ */ jsx13(
3383
4848
  ChatSession,
3384
4849
  {
3385
4850
  skillCount,
4851
+ skills,
4852
+ files,
3386
4853
  toolCount: ctx?.config.tools.length ?? 0,
3387
4854
  verbose: ctx?.verbose ?? false,
3388
4855
  apiKey: defaultProvider?.apiKey,
@@ -3395,7 +4862,7 @@ function createCli() {
3395
4862
  initGames();
3396
4863
  const games = listGames();
3397
4864
  const { unmount } = render4(
3398
- /* @__PURE__ */ jsx10(
4865
+ /* @__PURE__ */ jsx13(
3399
4866
  GamePicker,
3400
4867
  {
3401
4868
  games,
@@ -3419,7 +4886,7 @@ function createCli() {
3419
4886
  setImmediate(() => {
3420
4887
  const defaultStockCodes = ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
3421
4888
  const stockApp = renderApp(
3422
- /* @__PURE__ */ jsx10(
4889
+ /* @__PURE__ */ jsx13(
3423
4890
  StockList,
3424
4891
  {
3425
4892
  codes: defaultStockCodes,
@@ -3483,10 +4950,10 @@ compdef _dskcode_completion dskcode`);
3483
4950
  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) {
3484
4951
  const ctx = this.dskcodeCtx;
3485
4952
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
3486
- const globalConfigPath = join4(home, ".dskcode", "settings.json");
4953
+ const globalConfigPath = join8(home, ".dskcode", "settings.json");
3487
4954
  let globalConfigHasStock = false;
3488
4955
  try {
3489
- const raw = await readFile3(globalConfigPath, "utf-8");
4956
+ const raw = await readFile8(globalConfigPath, "utf-8");
3490
4957
  const parsed = JSON.parse(raw);
3491
4958
  const stock = parsed.stock;
3492
4959
  globalConfigHasStock = Array.isArray(stock?.symbols) && stock.symbols.length > 0;
@@ -3506,7 +4973,7 @@ compdef _dskcode_completion dskcode`);
3506
4973
  const freshResult = await loadAndValidate();
3507
4974
  const codeList = codes && codes.length > 0 ? codes : freshResult.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
3508
4975
  const app = renderApp(
3509
- /* @__PURE__ */ jsx10(
4976
+ /* @__PURE__ */ jsx13(
3510
4977
  StockList,
3511
4978
  {
3512
4979
  codes: codeList,
@@ -3533,19 +5000,19 @@ compdef _dskcode_completion dskcode`);
3533
5000
  console.log("\u6682\u65E0\u53EF\u7528\u6E38\u620F\u3002");
3534
5001
  return;
3535
5002
  }
3536
- const selectedGame = await new Promise((resolve) => {
5003
+ const selectedGame = await new Promise((resolve2) => {
3537
5004
  const { unmount } = render4(
3538
- /* @__PURE__ */ jsx10(
5005
+ /* @__PURE__ */ jsx13(
3539
5006
  GamePicker,
3540
5007
  {
3541
5008
  games,
3542
5009
  onSelect: (game) => {
3543
5010
  unmount();
3544
- resolve(game);
5011
+ resolve2(game);
3545
5012
  },
3546
5013
  onExit: () => {
3547
5014
  unmount();
3548
- resolve(null);
5015
+ resolve2(null);
3549
5016
  }
3550
5017
  }
3551
5018
  ),