dskcode 0.1.29 → 0.1.31

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
@@ -7,7 +7,7 @@ import {
7
7
  estimateTokens,
8
8
  getModelMeta,
9
9
  isSupportedModel
10
- } from "./chunk-UV4IWYHZ.js";
10
+ } from "./chunk-LWOBXEMJ.js";
11
11
 
12
12
  // src/cli/index.tsx
13
13
  import { Command } from "commander";
@@ -805,9 +805,9 @@ var LOGO_LINES = [
805
805
  ];
806
806
 
807
807
  // src/ui/ChatSession.tsx
808
- import { Box as Box9, Text as Text10, useInput, Static } from "ink";
808
+ import { Box as Box10, Text as Text11, useInput, Static } from "ink";
809
809
  import TextInput from "ink-text-input";
810
- import { useEffect as useEffect3, useState as useState3, useCallback as useCallback2, useMemo, useRef as useRef2 } from "react";
810
+ import { useEffect as useEffect4, useState as useState4, useCallback as useCallback2, useMemo, useRef as useRef3 } from "react";
811
811
 
812
812
  // src/ui/useDoubleCtrlC.ts
813
813
  import { useState as useState2, useCallback, useEffect as useEffect2, useRef } from "react";
@@ -838,10 +838,11 @@ function useDoubleCtrlC(onExit) {
838
838
  }
839
839
 
840
840
  // src/ui/ChatSession.tsx
841
- import InkSpinner2 from "ink-spinner";
841
+ import InkSpinner3 from "ink-spinner";
842
842
 
843
843
  // src/ui/AssistantMessage.tsx
844
844
  import { Box as Box5, Text as Text6 } from "ink";
845
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
845
846
 
846
847
  // src/provider/cost-tracker.ts
847
848
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
@@ -1178,17 +1179,17 @@ function ToolCallBlock({ call, showPendingHint = true }) {
1178
1179
  const argsDisplay = formatArgsSummary(call.arguments);
1179
1180
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: [
1180
1181
  /* @__PURE__ */ jsxs3(Box3, { children: [
1181
- /* @__PURE__ */ jsxs3(Text4, { color: "#00ffff", bold: true, children: [
1182
+ /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1182
1183
  "\u{1F4E6} ",
1183
1184
  call.name
1184
1185
  ] }),
1185
- /* @__PURE__ */ jsxs3(Text4, { color: "#555555", children: [
1186
+ /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
1186
1187
  " ",
1187
1188
  "\u2500".repeat(Math.max(1, 30 - call.name.length))
1188
1189
  ] })
1189
1190
  ] }),
1190
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text4, { color: "#888888", children: argsDisplay }) }),
1191
- showPendingHint && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text4, { color: "yellow", children: "\u23F3 \u7B49\u5F85\u6267\u884C" }) })
1191
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text4, { dimColor: true, children: argsDisplay }) }),
1192
+ showPendingHint && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text4, { dimColor: true, children: "\u23F3 \u7B49\u5F85\u6267\u884C" }) })
1192
1193
  ] });
1193
1194
  }
1194
1195
 
@@ -1277,6 +1278,226 @@ function formatUsageSummary(usage) {
1277
1278
 
1278
1279
  // src/ui/HighlightedText.tsx
1279
1280
  import { Box as Box4, Text as Text5 } from "ink";
1281
+
1282
+ // src/ui/table-layout.ts
1283
+ var TABLE_MIN_COL_WIDTH = 3;
1284
+ var DEFAULT_TERM_WIDTH = 80;
1285
+ var TABLE_MARGIN = 2;
1286
+ function cpWidth(cp2) {
1287
+ if (cp2 >= 4352 && cp2 <= 4447 || // Hangul Jamo
1288
+ cp2 === 9001 || cp2 === 9002 || cp2 >= 11904 && cp2 <= 42191 || // CJK Radicals ~ Yi
1289
+ cp2 >= 43360 && cp2 <= 43391 || // Hangul Extended
1290
+ cp2 >= 44032 && cp2 <= 55215 || // Hangul Syllables
1291
+ cp2 >= 63744 && cp2 <= 64255 || // CJK Compat
1292
+ cp2 >= 65040 && cp2 <= 65055 || // Vertical forms
1293
+ cp2 >= 65281 && cp2 <= 65376 || // Fullwidth ASCII / 标点
1294
+ cp2 >= 65504 && cp2 <= 65510 || // Fullwidth signs
1295
+ cp2 >= 126976 && cp2 <= 131071 || // Emoji / Symbols
1296
+ cp2 >= 12288 && cp2 <= 12351 || // CJK Symbols & Punctuation(含「、。·」等)
1297
+ cp2 >= 131072 && cp2 <= 196607) {
1298
+ return 2;
1299
+ }
1300
+ if (cp2 < 32 || cp2 >= 127 && cp2 < 160) return 0;
1301
+ return 1;
1302
+ }
1303
+ function visualWidth(text) {
1304
+ let w = 0;
1305
+ for (const ch of text) {
1306
+ const cp2 = ch.codePointAt(0);
1307
+ if (cp2 === void 0) continue;
1308
+ if (cp2 === 8205) continue;
1309
+ if (cp2 >= 65024 && cp2 <= 65039) continue;
1310
+ w += cpWidth(cp2);
1311
+ }
1312
+ return w;
1313
+ }
1314
+ function* iterateChars(text) {
1315
+ for (const ch of text) {
1316
+ const cp2 = ch.codePointAt(0);
1317
+ if (cp2 === void 0) continue;
1318
+ if (cp2 === 8205) continue;
1319
+ if (cp2 >= 65024 && cp2 <= 65039) continue;
1320
+ yield { ch, width: cpWidth(cp2) };
1321
+ }
1322
+ }
1323
+ function wrapByWidth(text, maxWidth) {
1324
+ if (maxWidth <= 0) return [text];
1325
+ const out = [];
1326
+ for (const para of text.split("\n")) {
1327
+ if (para.length === 0) {
1328
+ out.push("");
1329
+ continue;
1330
+ }
1331
+ const wrapped = wrapOneLine(para, maxWidth);
1332
+ for (const w of wrapped) out.push(w);
1333
+ }
1334
+ return out.length === 0 ? [""] : out;
1335
+ }
1336
+ function wrapOneLine(text, maxWidth) {
1337
+ if (visualWidth(text) <= maxWidth) return [text];
1338
+ const lines = [];
1339
+ let current = "";
1340
+ let currentW = 0;
1341
+ let breakCandidate = -1;
1342
+ let breakCandidateW = 0;
1343
+ for (const { ch, width } of iterateChars(text)) {
1344
+ if (ch === " " || ch === " ") {
1345
+ breakCandidate = current.length;
1346
+ breakCandidateW = currentW + width;
1347
+ current += ch;
1348
+ currentW += width;
1349
+ continue;
1350
+ }
1351
+ if (currentW + width > maxWidth) {
1352
+ if (breakCandidate > 0) {
1353
+ const split = current.slice(0, breakCandidate).replace(/[ \t]+$/, "");
1354
+ lines.push(split);
1355
+ const rest = current.slice(breakCandidate).replace(/^[ \t]+/, "");
1356
+ current = rest + ch;
1357
+ currentW = visualWidth(current);
1358
+ breakCandidate = -1;
1359
+ breakCandidateW = 0;
1360
+ } else {
1361
+ lines.push(current);
1362
+ current = ch;
1363
+ currentW = width;
1364
+ }
1365
+ } else {
1366
+ current += ch;
1367
+ currentW += width;
1368
+ }
1369
+ }
1370
+ if (current.length > 0) lines.push(current);
1371
+ return lines;
1372
+ }
1373
+ function wrapRowCells(cells, colWidths, alignments) {
1374
+ return cells.map((cell, ci) => {
1375
+ const w = colWidths[ci] ?? TABLE_MIN_COL_WIDTH;
1376
+ const lines = wrapByWidth(cell, w);
1377
+ return lines.map((l) => padToWidth(l, w, alignments[ci] ?? "left"));
1378
+ });
1379
+ }
1380
+ function padToWidth(text, targetWidth, align = "left") {
1381
+ const vw = visualWidth(text);
1382
+ const delta = targetWidth - vw;
1383
+ if (delta <= 0) return text;
1384
+ const leftPad = align === "right" ? delta : align === "center" ? Math.floor(delta / 2) : 0;
1385
+ const rightPad = delta - leftPad;
1386
+ return " ".repeat(leftPad) + text + " ".repeat(rightPad);
1387
+ }
1388
+ function parseTableCells(line) {
1389
+ const t = line.trim();
1390
+ if (!t.startsWith("|") || !t.endsWith("|")) return [];
1391
+ const inner = t.slice(1, t.length - 1);
1392
+ return inner.split("|").map((c) => c.trim());
1393
+ }
1394
+ function parseAlignments(sepLine) {
1395
+ return parseTableCells(sepLine).map((c) => {
1396
+ const l = c.startsWith(":");
1397
+ const r = c.endsWith(":");
1398
+ if (l && r) return "center";
1399
+ if (r) return "right";
1400
+ return "left";
1401
+ });
1402
+ }
1403
+ function layoutTable(text, options = {}) {
1404
+ const termWidth = options.termWidth ?? DEFAULT_TERM_WIDTH;
1405
+ const outerMargin = options.outerMargin ?? 0;
1406
+ const usable = Math.max(termWidth - outerMargin, 20);
1407
+ const rawLines = text.split("\n").filter((l) => l.trim().length > 0);
1408
+ if (rawLines.length < 2) {
1409
+ return { lines: rawLines, colWidths: [], totalWidth: 0 };
1410
+ }
1411
+ let sepIdx = -1;
1412
+ for (let k = 0; k < rawLines.length; k++) {
1413
+ const t = (rawLines[k] ?? "").trim();
1414
+ if (/^\|[-: |]+\|$/.test(t) && t.includes("-")) {
1415
+ sepIdx = k;
1416
+ break;
1417
+ }
1418
+ }
1419
+ if (sepIdx === -1) {
1420
+ return { lines: rawLines, colWidths: [], totalWidth: 0 };
1421
+ }
1422
+ const headerCells = parseTableCells(rawLines.slice(0, sepIdx).join(""));
1423
+ const alignments = parseAlignments(rawLines[sepIdx] ?? "");
1424
+ const dataRows = rawLines.slice(sepIdx + 1).map(parseTableCells);
1425
+ const colCount = headerCells.length;
1426
+ if (colCount === 0) {
1427
+ return { lines: rawLines, colWidths: [], totalWidth: 0 };
1428
+ }
1429
+ const colMaxWidths = [];
1430
+ for (let ci = 0; ci < colCount; ci++) {
1431
+ let max = visualWidth(headerCells[ci] ?? "");
1432
+ for (const row of dataRows) {
1433
+ const w = visualWidth(row[ci] ?? "");
1434
+ if (w > max) max = w;
1435
+ }
1436
+ colMaxWidths.push(Math.max(max, TABLE_MIN_COL_WIDTH));
1437
+ }
1438
+ const margin = TABLE_MARGIN;
1439
+ const totalMinWidth = colMaxWidths.reduce((s, w) => s + w + margin, 1);
1440
+ let colWidths;
1441
+ if (totalMinWidth <= usable) {
1442
+ colWidths = colMaxWidths;
1443
+ } else {
1444
+ const available = usable - 1 - margin * colCount;
1445
+ const sum = colMaxWidths.reduce((s, w) => s + w, 0);
1446
+ colWidths = colMaxWidths.map(
1447
+ (w) => Math.max(TABLE_MIN_COL_WIDTH, Math.floor(w / sum * available))
1448
+ );
1449
+ let total = colWidths.reduce((s, w) => s + w + margin, 1);
1450
+ while (total > usable && colWidths.length > 0) {
1451
+ const last = colWidths.length - 1;
1452
+ const cur = colWidths[last] ?? TABLE_MIN_COL_WIDTH;
1453
+ if (cur <= TABLE_MIN_COL_WIDTH) break;
1454
+ colWidths[last] = cur - 1;
1455
+ total = colWidths.reduce((s, w) => s + w + margin, 1);
1456
+ }
1457
+ }
1458
+ const H = "\u2500";
1459
+ const makeSep = (l, m, r) => l + colWidths.map((w) => H.repeat(w + margin)).join(m) + r;
1460
+ const topBorder = makeSep("\u250C", "\u252C", "\u2510");
1461
+ const midBorder = makeSep("\u251C", "\u253C", "\u2524");
1462
+ const botBorder = makeSep("\u2514", "\u2534", "\u2518");
1463
+ function buildRow(cells) {
1464
+ const wrapped = wrapRowCells(cells, colWidths, alignments);
1465
+ const rowHeight = Math.max(1, ...wrapped.map((lines2) => lines2.length));
1466
+ const lines = [];
1467
+ for (let li = 0; li < rowHeight; li++) {
1468
+ let row = "\u2502";
1469
+ for (let ci = 0; ci < colCount; ci++) {
1470
+ const cellLines = wrapped[ci] ?? [];
1471
+ const segment = cellLines[li] ?? " ".repeat(colWidths[ci] ?? TABLE_MIN_COL_WIDTH);
1472
+ row += " " + segment + " ";
1473
+ if (ci < colCount - 1) row += "\u2502";
1474
+ }
1475
+ row += "\u2502";
1476
+ lines.push(row);
1477
+ }
1478
+ return lines;
1479
+ }
1480
+ const headerRow = buildRow(headerCells);
1481
+ const dataRowsOut = dataRows.map(buildRow);
1482
+ const totalWidth = colWidths.reduce((s, w) => s + w + margin, 1);
1483
+ return {
1484
+ lines: [
1485
+ topBorder,
1486
+ ...headerRow,
1487
+ midBorder,
1488
+ ...dataRowsOut.flat(),
1489
+ botBorder
1490
+ ],
1491
+ colWidths,
1492
+ totalWidth
1493
+ };
1494
+ }
1495
+ function detectTermWidth() {
1496
+ const c = process.stdout?.columns;
1497
+ return typeof c === "number" && c > 0 ? c : void 0;
1498
+ }
1499
+
1500
+ // src/ui/HighlightedText.tsx
1280
1501
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1281
1502
  function parseCodeBlocks(text) {
1282
1503
  const segments = [];
@@ -1449,111 +1670,19 @@ function CodeBlockRenderer({ code }) {
1449
1670
  })
1450
1671
  ] });
1451
1672
  }
1452
- var TABLE_MAX_WIDTH = 90;
1453
- var TABLE_MIN_COL_WIDTH = 3;
1454
- function charWidth(ch) {
1455
- const cp2 = ch.codePointAt(0);
1456
- if (cp2 === void 0) return 0;
1457
- if (cp2 >= 4352 && cp2 <= 4447 || // Hangul Jamo
1458
- cp2 === 9001 || cp2 === 9002 || cp2 >= 11904 && cp2 <= 42191 || // CJK Radicals ~ Yi
1459
- cp2 >= 43360 && cp2 <= 43391 || // Hangul Extended
1460
- cp2 >= 44032 && cp2 <= 55215 || // Hangul Syllables
1461
- cp2 >= 63744 && cp2 <= 64255 || // CJK Compat
1462
- cp2 >= 65040 && cp2 <= 65055 || // Vertical forms
1463
- cp2 >= 65281 && cp2 <= 65376 || // Fullwidth
1464
- cp2 >= 65504 && cp2 <= 65510 || cp2 >= 126976 && cp2 <= 131071) {
1465
- return 2;
1466
- }
1467
- return 1;
1468
- }
1469
- function visualWidth(text) {
1470
- let w = 0;
1471
- for (const ch of text) {
1472
- w += charWidth(ch);
1473
- }
1474
- return w;
1475
- }
1476
- function padToWidth(text, targetWidth, align) {
1477
- const vw = visualWidth(text);
1478
- const delta = targetWidth - vw;
1479
- if (delta <= 0) return text;
1480
- const leftPad = align === "right" ? delta : align === "center" ? Math.floor(delta / 2) : 0;
1481
- const rightPad = delta - leftPad;
1482
- return " ".repeat(leftPad) + text + " ".repeat(rightPad);
1483
- }
1484
- function parseTableCells(line) {
1485
- const t = line.trim();
1486
- const inner = t.slice(1, t.length - 1);
1487
- return inner.split("|").map((c) => c.trim());
1488
- }
1489
- function parseAlignments(sepLine) {
1490
- const cells = parseTableCells(sepLine);
1491
- return cells.map((c) => {
1492
- const l = c.startsWith(":");
1493
- const r = c.endsWith(":");
1494
- if (l && r) return "center";
1495
- if (r) return "right";
1496
- return "left";
1497
- });
1498
- }
1499
1673
  function TableRenderer({ text }) {
1500
- const rawLines = text.split("\n").filter((l) => l.trim().length > 0);
1501
- if (rawLines.length < 2) return /* @__PURE__ */ jsx4(Text5, { children: text });
1502
- let sepIdx = -1;
1503
- for (let k = 0; k < rawLines.length; k++) {
1504
- if (isTableSepRow(rawLines[k])) {
1505
- sepIdx = k;
1506
- break;
1507
- }
1508
- }
1509
- if (sepIdx === -1) return /* @__PURE__ */ jsx4(Text5, { children: text });
1510
- const headerText = rawLines.slice(0, sepIdx).join("");
1511
- const headerCells = parseTableCells(headerText);
1512
- const alignments = parseAlignments(rawLines[sepIdx]);
1513
- const dataRows = rawLines.slice(sepIdx + 1).map(parseTableCells);
1514
- const colCount = headerCells.length;
1515
- const colMaxWidths = headerCells.map((_, ci) => {
1516
- let max = visualWidth(headerCells[ci] ?? "");
1517
- for (const row of dataRows) {
1518
- const w = visualWidth(row[ci] ?? "");
1519
- if (w > max) max = w;
1520
- }
1521
- return Math.max(max, TABLE_MIN_COL_WIDTH);
1522
- });
1523
- const margin = 2;
1524
- const totalMinWidth = colMaxWidths.reduce((s, w) => s + w + margin, 1);
1525
- let colWidths;
1526
- if (totalMinWidth <= TABLE_MAX_WIDTH) {
1527
- colWidths = colMaxWidths;
1528
- } else {
1529
- const available = TABLE_MAX_WIDTH - 1 - margin * colCount;
1530
- const sum = colMaxWidths.reduce((s, w) => s + w, 0);
1531
- colWidths = colMaxWidths.map((w) => Math.max(TABLE_MIN_COL_WIDTH, Math.floor(w / sum * available)));
1532
- }
1533
- const H = "\u2500";
1534
- const makeSep = (l, m, r) => l + colWidths.map((w) => H.repeat(w + margin)).join(m) + r;
1535
- const topBorder = makeSep("\u250C", "\u252C", "\u2510");
1536
- const midBorder = makeSep("\u251C", "\u253C", "\u2524");
1537
- const botBorder = makeSep("\u2514", "\u2534", "\u2518");
1538
- function rowLine(cells, keyBase) {
1539
- return /* @__PURE__ */ jsxs4(Text5, { children: [
1540
- "\u2502",
1541
- cells.map((cell, ci) => /* @__PURE__ */ jsxs4(Text5, { children: [
1542
- " ",
1543
- padToWidth(cell.slice(0, colWidths[ci] ?? TABLE_MIN_COL_WIDTH), colWidths[ci] ?? TABLE_MIN_COL_WIDTH, alignments[ci] ?? "left"),
1544
- " ",
1545
- "\u2502"
1546
- ] }, ci))
1547
- ] }, keyBase);
1548
- }
1549
- const rows = [
1550
- /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: topBorder }, "top"),
1551
- rowLine(headerCells, "hdr"),
1552
- /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: midBorder }, "mid"),
1553
- ...dataRows.map((cells, ri) => rowLine(cells, `d${ri}`)),
1554
- /* @__PURE__ */ jsx4(Text5, { color: "#888888", children: botBorder }, "bot")
1555
- ];
1556
- return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginTop: 1, children: rows });
1674
+ const termWidth = detectTermWidth();
1675
+ const layout = layoutTable(text, { termWidth });
1676
+ if (layout.colWidths.length === 0) {
1677
+ return /* @__PURE__ */ jsx4(Text5, { children: text });
1678
+ }
1679
+ return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", marginTop: 1, children: layout.lines.map((line, i) => {
1680
+ const isFirst = i === 0;
1681
+ const isLast = i === layout.lines.length - 1;
1682
+ const isMid = !isFirst && !isLast && line.startsWith("\u251C");
1683
+ const color = isFirst || isLast || isMid ? "#888888" : void 0;
1684
+ return /* @__PURE__ */ jsx4(Text5, { color, children: line }, i);
1685
+ }) });
1557
1686
  }
1558
1687
  var EMOJI_CP_RE = /[\u{1F000}-\u{1FFFF}\u{FE00}-\u{FE0F}\u{200D}\u{20E3}\u{2600}-\u{27BF}\u{231A}-\u{23FF}\u{2934}\u{2935}\u{25AA}-\u{25FE}\u{2B50}\u{2B55}\u{00A9}\u{00AE}\u{2122}\u{3030}\u{303D}\u{3297}\u{3299}]/u;
1559
1688
  function isEmojiCluster(text) {
@@ -1673,15 +1802,108 @@ function HighlightedText({ children: text }) {
1673
1802
  return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: rendered });
1674
1803
  }
1675
1804
 
1805
+ // src/ui/reasoning-utils.ts
1806
+ var DEFAULT_REASONING_MAX_LINES = 8;
1807
+ function joinReasoningSegments(segments) {
1808
+ return segments.map((s) => s.trim()).filter((s) => s.length > 0).join("\n");
1809
+ }
1810
+ function truncateReasoningLines(text, maxLines) {
1811
+ const lines = text.split("\n");
1812
+ if (lines.length <= maxLines) {
1813
+ return { visible: text, hiddenLines: 0, totalLines: lines.length };
1814
+ }
1815
+ return {
1816
+ visible: lines.slice(-maxLines).join("\n"),
1817
+ hiddenLines: lines.length - maxLines,
1818
+ totalLines: lines.length
1819
+ };
1820
+ }
1821
+
1676
1822
  // src/ui/AssistantMessage.tsx
1677
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1823
+ import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1678
1824
  function formatElapsed(ms) {
1679
1825
  if (ms < 1e3) return `${ms}ms`;
1680
1826
  const seconds = (ms / 1e3).toFixed(1);
1681
1827
  return `${seconds}s`;
1682
1828
  }
1829
+ function AnimatedUsage({ usage, cost }) {
1830
+ const targetTokens = usage ? usage.promptTokens + usage.completionTokens : 0;
1831
+ const targetCost = cost ?? 0;
1832
+ const [displayedTokens, setDisplayedTokens] = useState3(targetTokens);
1833
+ const [displayedCost, setDisplayedCost] = useState3(targetCost);
1834
+ const startRef = useRef2(null);
1835
+ const rafRef = useRef2(null);
1836
+ useEffect3(() => {
1837
+ if (startRef.current && startRef.current.toTokens === targetTokens && startRef.current.toCost === targetCost) {
1838
+ return;
1839
+ }
1840
+ if (targetTokens <= displayedTokens && targetCost <= displayedCost) {
1841
+ setDisplayedTokens(targetTokens);
1842
+ setDisplayedCost(targetCost);
1843
+ startRef.current = {
1844
+ fromTokens: targetTokens,
1845
+ fromCost: targetCost,
1846
+ toTokens: targetTokens,
1847
+ toCost: targetCost,
1848
+ startMs: Date.now(),
1849
+ durationMs: 0
1850
+ };
1851
+ return;
1852
+ }
1853
+ const tokensDelta = targetTokens - displayedTokens;
1854
+ const costDelta = targetCost - displayedCost;
1855
+ const durationMs = Math.min(600, Math.max(220, Math.max(tokensDelta, 0) * 1.2 + 220));
1856
+ startRef.current = {
1857
+ fromTokens: displayedTokens,
1858
+ fromCost: displayedCost,
1859
+ toTokens: targetTokens,
1860
+ toCost: targetCost,
1861
+ startMs: Date.now(),
1862
+ durationMs
1863
+ };
1864
+ if (rafRef.current) clearInterval(rafRef.current);
1865
+ rafRef.current = setInterval(() => {
1866
+ const s = startRef.current;
1867
+ if (!s) return;
1868
+ const elapsed = Date.now() - s.startMs;
1869
+ if (elapsed >= s.durationMs) {
1870
+ setDisplayedTokens(s.toTokens);
1871
+ setDisplayedCost(s.toCost);
1872
+ if (rafRef.current) {
1873
+ clearInterval(rafRef.current);
1874
+ rafRef.current = null;
1875
+ }
1876
+ return;
1877
+ }
1878
+ const t = elapsed / s.durationMs;
1879
+ const eased = 1 - Math.pow(1 - t, 3);
1880
+ const tokens = s.fromTokens + (s.toTokens - s.fromTokens) * eased;
1881
+ const cost2 = s.fromCost + (s.toCost - s.fromCost) * eased;
1882
+ setDisplayedTokens(tokens);
1883
+ setDisplayedCost(cost2);
1884
+ }, 33);
1885
+ return () => {
1886
+ if (rafRef.current) {
1887
+ clearInterval(rafRef.current);
1888
+ rafRef.current = null;
1889
+ }
1890
+ };
1891
+ }, [targetTokens, targetCost]);
1892
+ return /* @__PURE__ */ jsxs5(Fragment, { children: [
1893
+ usage && /* @__PURE__ */ jsxs5(Text6, { color: "#888888", children: [
1894
+ Math.round(displayedTokens).toLocaleString(),
1895
+ " tokens"
1896
+ ] }),
1897
+ cost !== void 0 && cost > 0 && /* @__PURE__ */ jsxs5(Text6, { color: "#888888", children: [
1898
+ " \xB7 ",
1899
+ "\xA5",
1900
+ displayedCost.toFixed(4)
1901
+ ] })
1902
+ ] });
1903
+ }
1683
1904
  function AssistantMessage({
1684
1905
  content,
1906
+ reasoning,
1685
1907
  toolCalls,
1686
1908
  isStreaming = false,
1687
1909
  usage,
@@ -1689,18 +1911,45 @@ function AssistantMessage({
1689
1911
  cost,
1690
1912
  model: _model
1691
1913
  }) {
1692
- if (!content && (!toolCalls || toolCalls.length === 0) && !isStreaming) {
1914
+ if (!content && (!toolCalls || toolCalls.length === 0) && (!reasoning || reasoning.length === 0) && !isStreaming) {
1693
1915
  return null;
1694
1916
  }
1695
1917
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1918
+ reasoning && reasoning.length > 0 && (() => {
1919
+ const merged = joinReasoningSegments(reasoning);
1920
+ if (!merged) return null;
1921
+ const { visible } = truncateReasoningLines(
1922
+ merged,
1923
+ DEFAULT_REASONING_MAX_LINES
1924
+ );
1925
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", marginBottom: 1, children: [
1926
+ /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { dimColor: true, children: "\u{1F9E0}" }) }),
1927
+ /* @__PURE__ */ jsx5(
1928
+ Box5,
1929
+ {
1930
+ flexGrow: 1,
1931
+ flexDirection: "column",
1932
+ borderStyle: "single",
1933
+ borderColor: "#444444",
1934
+ paddingLeft: 1,
1935
+ paddingRight: 1,
1936
+ children: /* @__PURE__ */ jsx5(Text6, { dimColor: true, wrap: "wrap", children: visible })
1937
+ }
1938
+ )
1939
+ ] });
1940
+ })(),
1696
1941
  /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", children: [
1697
1942
  /* @__PURE__ */ jsx5(Box5, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text6, { bold: true, color: "#ff00ff", children: "\u{1F916}" }) }),
1698
1943
  /* @__PURE__ */ jsxs5(Box5, { flexGrow: 1, flexDirection: "column", children: [
1699
1944
  content && /* @__PURE__ */ jsx5(HighlightedText, { children: content }),
1700
- isStreaming && !content && /* @__PURE__ */ jsx5(Text6, { color: "#888888", children: "..." })
1945
+ isStreaming && !content && (!reasoning || reasoning.length === 0) && /* @__PURE__ */ jsx5(Text6, { color: "#888888", children: "..." })
1701
1946
  ] })
1702
1947
  ] }),
1703
1948
  toolCalls && toolCalls.length > 0 && /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: toolCalls.map((tc, i) => /* @__PURE__ */ jsx5(ToolCallBlock, { call: tc }, tc.id ?? i)) }),
1949
+ isStreaming && (usage || cost !== void 0) && /* @__PURE__ */ jsx5(Box5, { flexDirection: "row", marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs5(Text6, { color: "#666666", dimColor: true, children: [
1950
+ "\u23F3 \u5DF2\u6D88\u8017 ",
1951
+ /* @__PURE__ */ jsx5(AnimatedUsage, { usage, cost })
1952
+ ] }) }),
1704
1953
  !isStreaming && (usage || elapsed !== void 0) && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, marginLeft: 3, children: [
1705
1954
  /* @__PURE__ */ jsx5(Text6, { color: "#555555", children: "\u2500".repeat(36) }),
1706
1955
  /* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", gap: 2, children: [
@@ -1807,6 +2056,49 @@ function FileSelector({ files, input, selectedIndex }) {
1807
2056
  ] });
1808
2057
  }
1809
2058
 
2059
+ // src/ui/TodoListPanel.tsx
2060
+ import { Box as Box9, Text as Text10 } from "ink";
2061
+ import InkSpinner2 from "ink-spinner";
2062
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2063
+ function statusIcon(status) {
2064
+ switch (status) {
2065
+ case "pending":
2066
+ return "\u2610";
2067
+ case "done":
2068
+ return "\u2611";
2069
+ case "failed":
2070
+ return "\u2717";
2071
+ case "skipped":
2072
+ return "\u2298";
2073
+ // running 不走这里,由 TodoIcon 走 Spinner 组件
2074
+ case "running":
2075
+ return "\u25B6";
2076
+ }
2077
+ }
2078
+ function TodoIcon({ status }) {
2079
+ if (status === "running") {
2080
+ return /* @__PURE__ */ jsx9(Text10, { color: "cyan", children: /* @__PURE__ */ jsx9(InkSpinner2, { type: "dots" }) });
2081
+ }
2082
+ if (status === "done") {
2083
+ return /* @__PURE__ */ jsx9(Text10, { color: "green", children: "\u2705" });
2084
+ }
2085
+ if (status === "failed") {
2086
+ return /* @__PURE__ */ jsx9(Text10, { color: "red", children: statusIcon(status) });
2087
+ }
2088
+ return /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: statusIcon(status) });
2089
+ }
2090
+ function TodoListPanel({ items }) {
2091
+ if (items.length === 0) return null;
2092
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, children: [
2093
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u{1F527} \u4EFB\u52A1\u8FDB\u5EA6" }) }),
2094
+ items.map((it) => /* @__PURE__ */ jsxs9(Box9, { children: [
2095
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " " }),
2096
+ /* @__PURE__ */ jsx9(TodoIcon, { status: it.status }),
2097
+ /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: ` ${it.content}` })
2098
+ ] }, it.id))
2099
+ ] });
2100
+ }
2101
+
1810
2102
  // src/provider/registry.ts
1811
2103
  var ProviderRegistry = class {
1812
2104
  #factories = /* @__PURE__ */ new Map();
@@ -1901,10 +2193,10 @@ var AlwaysAllowGate = class {
1901
2193
  import Handlebars from "handlebars";
1902
2194
 
1903
2195
  // src/agent/prompts/system-prompt.hbs
1904
- var system_prompt_default = '\u4F60\u662F dskcode\uFF0C\u4E00\u4E2A\u57FA\u4E8E DeepSeek \u7684 AI \u7F16\u7A0B\u52A9\u624B\uFF0C\u8FD0\u884C\u5728\u7528\u6237\u7EC8\u7AEF\u4E2D\u3002\u4F60\u7684\u804C\u8D23\u662F\u5E2E\u52A9\u5F00\u53D1\u8005\u7F16\u5199\u3001\u7406\u89E3\u3001\u8C03\u8BD5\u548C\u91CD\u6784\u4EE3\u7801\u3002\r\n\r\n## \u6838\u5FC3\u539F\u5219\r\n- \u56DE\u7B54\u4F7F\u7528\u4E2D\u6587\uFF0C\u4EE3\u7801\u6807\u8BC6\u7B26\u4FDD\u6301\u82F1\u6587\r\n- \u63D0\u4F9B\u51C6\u786E\u3001\u5B9E\u7528\u3001\u53EF\u64CD\u4F5C\u7684\u5EFA\u8BAE\r\n- \u5BF9\u4E0D\u786E\u5B9A\u7684\u5185\u5BB9\u660E\u786E\u6807\u6CE8\uFF0C\u4E0D\u7F16\u9020\u4E8B\u5B9E\r\n- \u4EE3\u7801\u793A\u4F8B\u4FDD\u6301\u7B80\u6D01\uFF0C\u9644\u5E26\u5FC5\u8981\u7684\u6CE8\u91CA\r\n\r\n## \u5DE5\u4F5C\u6D41\u7A0B\r\n\r\n\u4F60\u5904\u4E8E\u4E00\u4E2A\u5DE5\u5177\u8C03\u7528\u5FAA\u73AF\u4E2D\uFF1A\r\n1. \u6536\u5230\u7528\u6237\u8BF7\u6C42\r\n2. \u8C03\u7528\u5DE5\u5177\u83B7\u53D6\u4FE1\u606F\u6216\u4FEE\u6539\u6587\u4EF6\r\n3. \u5DE5\u5177\u7ED3\u679C\u8FD4\u56DE\u7ED9\u4F60\r\n4. \u6839\u636E\u7ED3\u679C\u51B3\u5B9A\u4E0B\u4E00\u6B65\uFF08\u7EE7\u7EED\u8C03\u7528\u5DE5\u5177\u6216\u7ED9\u51FA\u6700\u7EC8\u56DE\u7B54\uFF09\r\n5. \u4E00\u8F6E\u6700\u591A {{maxToolRounds}} \u6B21\u5DE5\u5177\u8C03\u7528\r\n\r\n## \u5DE5\u5177\u4F7F\u7528\u7B56\u7565\r\n\r\n{{#if tools}}\r\n\u5F53\u524D\u53EF\u7528\u7684\u5DE5\u5177\uFF1A\r\n{{#each tools}}\r\n- **{{name}}**\uFF1A{{description}}{{#if params}}\uFF08\u53C2\u6570\uFF1A{{params}}\uFF09{{/if}}\r\n{{/each}}\r\n\r\n\u4F7F\u7528\u539F\u5219\uFF1A\r\n- **\u5148\u641C\u7D22\u518D\u8BFB\u53D6** \u2014 \u4E0D\u786E\u5B9A\u76EE\u6807\u4F4D\u7F6E\u65F6\u5148\u7528 grep/glob \u5B9A\u4F4D\uFF0C\u518D\u7528 read_file \u8BFB\u53D6\r\n- **\u5148\u8BFB\u53D6\u518D\u4FEE\u6539** \u2014 \u7F16\u8F91\u6587\u4EF6\u524D\u5148 read_file \u786E\u8BA4\u5F53\u524D\u5185\u5BB9\r\n- **\u6279\u91CF\u64CD\u4F5C\u4E00\u6B21\u5B8C\u6210** \u2014 \u540C\u4E00\u6587\u4EF6\u7684\u591A\u5904\u6539\u52A8\u5728\u5355\u6B21 edit_file \u4E2D\u5B8C\u6210\r\n- **\u5DE5\u5177\u51FA\u9519\u65F6** \u2014 \u5206\u6790\u9519\u8BEF\u539F\u56E0\uFF08\u8DEF\u5F84\u4E0D\u5BF9\uFF1F\u683C\u5F0F\u4E0D\u5BF9\uFF1F\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09\uFF0C\u4FEE\u6B63\u540E\u91CD\u8BD5\uFF0C\u4E0D\u8981\u91CD\u590D\u76F8\u540C\u7684\u9519\u8BEF\u8C03\u7528\r\n- **\u5BA1\u614E\u6267\u884C** \u2014 bash \u6267\u884C\u5371\u9669\u547D\u4EE4\uFF08rm -rf\u3001\u5F3A\u5236\u5199\u5165\u7B49\uFF09\u524D\u5148\u786E\u8BA4\r\n{{/if}}\r\n\r\n{{#if projectContext}}\r\n## \u9879\u76EE\u4E0A\u4E0B\u6587\r\n\r\n{{projectContext}}\r\n{{/if}}\r\n\r\n## \u56DE\u7B54\u683C\u5F0F\r\n\r\n\u7EC8\u7AEF\u4E0D\u652F\u6301 Markdown \u6E32\u67D3\u3002\u56DE\u590D\u683C\u5F0F\u8981\u6C42\uFF1A\r\n\r\n- \u7528 emoji \u505A\u89C6\u89C9\u6807\u8BB0\uFF08\u{1F4CC} \u8981\u70B9 \u26A0\uFE0F \u6CE8\u610F \u{1F4DD} \u5F85\u529E \u2705 \u5B8C\u6210\uFF09\r\n- \u7528\u6570\u5B57\u5E8F\u53F7\u6216\u77ED\u6A2A\u7EBF\u7EC4\u7EC7\u5217\u8868\r\n- \u4EE3\u7801\u6807\u8BC6\u7B26\u3001\u6587\u4EF6\u8DEF\u5F84\u3001\u7C7B\u578B\u540D\u7528\u5355\u4E2A\u53CD\u5F15\u53F7 ` \u5305\u88F9\uFF0C\u4F1A\u88AB\u81EA\u52A8\u9AD8\u4EAE\r\n- \u5F3A\u8C03\u5185\u5BB9\u7528 **\u52A0\u7C97** \u5305\u88F9\uFF0C\u4F1A\u88AB\u81EA\u52A8\u9AD8\u4EAE\r\n- \u591A\u884C\u4EE3\u7801\u7528\u7F29\u8FDB 4 \u4E2A\u7A7A\u683C\u8868\u793A\uFF0C\u4E0D\u8981\u7528\u4E09\u53CD\u5F15\u53F7\u4EE3\u7801\u5757\r\n- \u6BB5\u843D\u95F4\u7528\u7A7A\u884C\u5206\u9694\r\n- **\u4E0D\u8981\u4F7F\u7528 `#` \u7B26\u53F7** \u2014 \u7EC8\u7AEF\u4E0D\u6E32\u67D3 Markdown \u6807\u9898\uFF0C\u8BF7\u7528 emoji + \u52A0\u7C97\u66FF\u4EE3\uFF08\u5982 `\u{1F4CC} **\u6807\u9898**`\uFF09\r\n\r\n\u6B63\u786E\u793A\u4F8B\uFF1A\r\n\r\n\u{1F4CC} **\u4FEE\u6539\u8BA1\u5212**\r\n\r\n\u{1F4CC} \u9700\u8981\u4FEE\u6539\u4E24\u4E2A\u5730\u65B9\uFF1A\r\n1. \u7B2C 12 \u884C\u7684 `interface FileDiff` \u6539\u4E3A `interface Diff`\r\n2. \u7B2C 432 \u884C\u7684 `): FileDiff` \u6539\u4E3A `): Diff`\r\n\r\n \u4EE3\u7801\u793A\u4F8B\uFF1A\r\n const pool = new Pool({ max: 20 });\r\n await pool.query("SELECT * FROM users");\r\n\r\n## \u884C\u4E3A\u7EA6\u675F\r\n- \u5982\u679C\u7528\u6237\u8BF7\u6C42\u4E0D\u660E\u786E\uFF0C\u4E3B\u52A8\u8BE2\u95EE\u6F84\u6E05\r\n- \u5BF9\u4E0D\u786E\u5B9A\u7684\u4FE1\u606F\u660E\u786E\u6807\u6CE8\uFF0C\u4E0D\u7F16\u9020\u4E8B\u5B9E\r\n- \u4E0D\u6267\u884C\u53EF\u80FD\u9020\u6210\u4E0D\u53EF\u9006\u635F\u5BB3\u7684\u64CD\u4F5C\uFF08\u5982 rm -rf\uFF09\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u9010\u6761\u786E\u8BA4\r\n- \u5DE5\u5177\u8C03\u7528\u8FD4\u56DE\u9519\u8BEF\u65F6\uFF0C\u5148\u5206\u6790\u539F\u56E0\u518D\u91CD\u8BD5\uFF0C\u4E0D\u8981\u76F2\u76EE\u91CD\u8BD5\r\n\r\n## \u5F53\u524D\u6A21\u578B\r\n- \u6A21\u578B\uFF1A{{model}}\r\n\r\n## \u65F6\u95F4\u4E0A\u4E0B\u6587\r\n- \u5F53\u524D\u65E5\u671F\uFF1A{{date}}\r\n- \u5F53\u524D\u65F6\u95F4\uFF1A{{time}}\r\n- \u5DE5\u4F5C\u76EE\u5F55\uFF1A{{cwd}}\r\n';
2196
+ var system_prompt_default = '\u4F60\u662F dskcode\uFF0C\u4E00\u4E2A\u57FA\u4E8E DeepSeek \u7684 AI \u7F16\u7A0B\u52A9\u624B\uFF0C\u8FD0\u884C\u5728\u7528\u6237\u7EC8\u7AEF\u4E2D\u3002\u4F60\u7684\u804C\u8D23\u662F\u5E2E\u52A9\u5F00\u53D1\u8005\u7F16\u5199\u3001\u7406\u89E3\u3001\u8C03\u8BD5\u548C\u91CD\u6784\u4EE3\u7801\u3002\r\n\r\n## \u6838\u5FC3\u539F\u5219\r\n- \u56DE\u7B54\u4F7F\u7528\u4E2D\u6587\uFF0C\u4EE3\u7801\u6807\u8BC6\u7B26\u4FDD\u6301\u82F1\u6587\r\n- **\u601D\u8003\u8FC7\u7A0B\uFF08CoT\uFF09\u4E5F\u4F7F\u7528\u4E2D\u6587**\uFF0C\u4E0D\u8981\u7528\u82F1\u6587\u601D\u8003\u3002\u5982\u679C\u4F60\u53D1\u73B0\u81EA\u5DF1\u5728\u7528\u82F1\u6587\r\n \u60F3\uFF0C\u8BF7\u5207\u6362\u5230\u4E2D\u6587\u3002\u4F60\u7684\u6240\u6709\u5185\u5FC3\u72EC\u767D\u548C\u6700\u7EC8\u56DE\u7B54\u90FD\u5E94\u7528\u4E2D\u6587\u8868\u8FBE\u3002\r\n- \u63D0\u4F9B\u51C6\u786E\u3001\u5B9E\u7528\u3001\u53EF\u64CD\u4F5C\u7684\u5EFA\u8BAE\r\n- \u5BF9\u4E0D\u786E\u5B9A\u7684\u5185\u5BB9\u660E\u786E\u6807\u6CE8\uFF0C\u4E0D\u7F16\u9020\u4E8B\u5B9E\r\n- \u4EE3\u7801\u793A\u4F8B\u4FDD\u6301\u7B80\u6D01\uFF0C\u9644\u5E26\u5FC5\u8981\u7684\u6CE8\u91CA\r\n\r\n## \u7528\u6237\u8F93\u5165\u8BED\u6CD5\r\n- `@<\u8DEF\u5F84>` \u8868\u793A\u4E00\u4E2A**\u6587\u4EF6\u8DEF\u5F84**\u7684\u5F15\u7528\u6807\u8BC6\u3002`@` \u4E0D\u662F\u90AE\u7BB1\u7B26\u53F7\u3001\u4E5F\u4E0D\u662F\u88C5\u9970\u540E\u7F00\u3002\r\n - `@test.ts` = \u5F15\u7528\u5F53\u524D\u76EE\u5F55\u4E0B\u540D\u4E3A `test.ts` \u7684\u6587\u4EF6\r\n - `@src/foo/bar.ts` = \u5F15\u7528 `src/foo/bar.ts`\r\n - \u591A\u4E2A\u5F15\u7528\uFF1A`\u8BF7\u8BFB @a.ts \u548C @b/d.ts`\r\n - \u8DEF\u5F84\u53EF\u4EE5\u662F\u76F8\u5BF9\u8DEF\u5F84\uFF08\u76F8\u5BF9\u5DE5\u4F5C\u76EE\u5F55\uFF09\u6216\u7EDD\u5BF9\u8DEF\u5F84\r\n- `\\/<\u547D\u4EE4>` \u8868\u793A\u4E00\u4E2A**\u659C\u6760\u547D\u4EE4**\uFF08\u5982 `\\/help`\u3001`\\/plan`\u3001`\\/code`\uFF09\uFF0C\u4E0D\u662F\u76EE\u5F55\u3002\r\n - \u6536\u5230 `/xxx` \u5F00\u5934\u7684\u5185\u5BB9\u5148\u5224\u5B9A\u662F\u4E0D\u662F\u5DF2\u6CE8\u518C\u547D\u4EE4\uFF1B\u4E0D\u662F\u5219\u5F53\u4F5C\u666E\u901A\u6587\u672C\u5904\u7406\u3002\r\n- \u4E0A\u8FF0\u4E24\u79CD\u524D\u7F00\u662F**\u7528\u6237\u8F93\u5165\u9762**\u8BED\u6CD5\uFF0C\u4F60**\u4E0D\u8981\u5728\u56DE\u590D\u4E2D\u8F93\u51FA**\u5B83\u4EEC\uFF08\u4E0D\u8981\u4F2A\u9020 `@/path` \u8FD9\u79CD\u4F2A\u5F15\u7528\uFF09\u3002\u5982\u679C\u8981\u5F15\u7528\u6587\u4EF6\uFF0C\u76F4\u63A5\u7528\u5355\u53CD\u5F15\u53F7\u5305\u88F9\u8DEF\u5F84\uFF08\u5982 `src/foo.ts`\uFF09\u3002\r\n\r\n## \u5DE5\u4F5C\u6D41\u7A0B\r\n\r\n\u4F60\u5904\u4E8E\u4E00\u4E2A\u5DE5\u5177\u8C03\u7528\u5FAA\u73AF\u4E2D\uFF1A\r\n1. \u6536\u5230\u7528\u6237\u8BF7\u6C42\r\n2. \u8C03\u7528\u5DE5\u5177\u83B7\u53D6\u4FE1\u606F\u6216\u4FEE\u6539\u6587\u4EF6\r\n3. \u5DE5\u5177\u7ED3\u679C\u8FD4\u56DE\u7ED9\u4F60\r\n4. \u6839\u636E\u7ED3\u679C\u51B3\u5B9A\u4E0B\u4E00\u6B65\uFF08\u7EE7\u7EED\u8C03\u7528\u5DE5\u5177\u6216\u7ED9\u51FA\u6700\u7EC8\u56DE\u7B54\uFF09\r\n5. \u4E00\u8F6E\u6700\u591A {{maxToolRounds}} \u6B21\u5DE5\u5177\u8C03\u7528\r\n\r\n## \u5DE5\u5177\u4F7F\u7528\u7B56\u7565\r\n\r\n{{#if tools}}\r\n\u5F53\u524D\u53EF\u7528\u7684\u5DE5\u5177\uFF1A\r\n{{#each tools}}\r\n- **{{name}}**\uFF1A{{description}}{{#if params}}\uFF08\u53C2\u6570\uFF1A{{params}}\uFF09{{/if}}\r\n{{/each}}\r\n\r\n\u4F7F\u7528\u539F\u5219\uFF1A\r\n- **\u5148\u641C\u7D22\u518D\u8BFB\u53D6** \u2014 \u4E0D\u786E\u5B9A\u76EE\u6807\u4F4D\u7F6E\u65F6\u5148\u7528 grep/glob \u5B9A\u4F4D\uFF0C\u518D\u7528 read_file \u8BFB\u53D6\r\n- **\u5148\u8BFB\u53D6\u518D\u4FEE\u6539** \u2014 \u7F16\u8F91\u6587\u4EF6\u524D\u5148 read_file \u786E\u8BA4\u5F53\u524D\u5185\u5BB9\r\n- **\u6279\u91CF\u64CD\u4F5C\u4E00\u6B21\u5B8C\u6210** \u2014 \u540C\u4E00\u6587\u4EF6\u7684\u591A\u5904\u6539\u52A8\u5728\u5355\u6B21 edit_file \u4E2D\u5B8C\u6210\r\n- **\u5DE5\u5177\u51FA\u9519\u65F6** \u2014 \u5206\u6790\u9519\u8BEF\u539F\u56E0\uFF08\u8DEF\u5F84\u4E0D\u5BF9\uFF1F\u683C\u5F0F\u4E0D\u5BF9\uFF1F\u6743\u9650\u4E0D\u8DB3\uFF1F\uFF09\uFF0C\u4FEE\u6B63\u540E\u91CD\u8BD5\uFF0C\u4E0D\u8981\u91CD\u590D\u76F8\u540C\u7684\u9519\u8BEF\u8C03\u7528\r\n- **\u5BA1\u614E\u6267\u884C** \u2014 bash \u6267\u884C\u5371\u9669\u547D\u4EE4\uFF08rm -rf\u3001\u5F3A\u5236\u5199\u5165\u7B49\uFF09\u524D\u5148\u786E\u8BA4\r\n{{/if}}\r\n\r\n## \u5DE5\u4F5C\u6D41\uFF08\u91CD\u8981\uFF09\u2014 \u590D\u6742\u4EFB\u52A1\u5224\u5B9A\r\n\r\n\u6536\u5230\u7528\u6237\u8BF7\u6C42\u540E\uFF0C**\u5148\u5224\u5B9A\u590D\u6742\u5EA6**\uFF0C\u518D\u51B3\u5B9A\u8981\u4E0D\u8981\u62C6 todo\u3002\r\n\r\n### \u9ED8\u8BA4\u5047\u8BBE\u662F\u300C\u590D\u6742\u300D\r\n\r\n\u9664\u975E\u660E\u786E\u5C5E\u4E8E\u4E0B\u9762\u300C\u7B80\u5355\u300D\u4E09\u9009\u4E00\uFF0C**\u9ED8\u8BA4\u6309\u590D\u6742\u5904\u7406\uFF0C\u5148 todo_add \u62C6 3-7 \u6B65**\u3002\r\n\r\n### \u5224\u5B9A\u95EE\u5377\uFF08\u9010\u6761 yes/no\uFF09\r\n\r\n\u95EE\u81EA\u5DF1\u4EE5\u4E0B\u95EE\u9898\uFF0C**\u4EFB\u4E00\u56DE\u7B54 yes \u2192 \u62C6 todo**\uFF1A\r\n- 1\uFE0F\u20E3 \u8981\u5199/\u6539/\u521B\u5EFA **\u8D85\u8FC7 1 \u4E2A**\u6587\u4EF6\uFF1F\r\n- 2\uFE0F\u20E3 \u8981\u5148 **\u8BFB\u540E\u6539**\uFF08edit_file / write_file \u4E4B\u524D\u5FC5\u987B read_file\uFF09\uFF1F\r\n- 3\uFE0F\u20E3 \u8981\u8DE8 **2 \u79CD\u4EE5\u4E0A**\u5DE5\u5177\uFF08read + edit + bash + ...\uFF09\uFF1F\r\n- 4\uFE0F\u20E3 \u8981 **\u5BF9\u6BD4 2 \u4E2A\u4EE5\u4E0A\u4E8B\u5B9E\u6E90**\uFF08\u5982 README \u8BF4\u7684 vs \u4EE3\u7801\u5B9E\u9645\u5199\u7684\u3001\u8FD9\u91CC vs \u90A3\u91CC\uFF09\uFF1F\r\n- 5\uFE0F\u20E3 \u53E5\u5B50\u8D85\u8FC7 **20 \u4E2A\u5B57** \u4E14 **\u5305\u542B\u300C\u6539\u6210 / \u52A0\u4E2A / \u5B9E\u73B0 / \u5199\u4E00\u4E2A / \u91CD\u6784 / \u4FEE\u590D / \u8C03\u6574\u300D** \u8FD9\u7C7B\u52A8\u8BCD\uFF1F\r\n\r\n### \u300C\u7B80\u5355\u300D\u4E09\u9009\u4E00\uFF08\u53EF\u4EE5\u4E0D\u62C6 todo\uFF09\r\n\r\n- \u7EAF\u67E5\u8BE2\uFF1A\u300C\u5217\u51FA X\u300D\u300C\u770B\u770B Y\u300D\u300C\u641C\u4E00\u4E0B Z\u300D\u2192 1 \u4E2A\u5DE5\u5177 1 \u8F6E\u641E\u5B9A\r\n- 1 \u4E2A\u5DE5\u5177 1 \u8F6E\u80FD\u641E\u5B9A\u7684\u8BFB/\u5217/\u67E5\u64CD\u4F5C\r\n- \u95F2\u804A / \u89E3\u91CA\u6027\u95EE\u9898\uFF08\u4E0D\u8C03\u4EFB\u4F55\u5DE5\u5177\uFF09\r\n\r\n### \u5224\u5B9A\u51B3\u7B56\u6811\r\n\r\n```\r\n\u7528\u6237\u8F93\u5165\r\n \u251C\u2500 \u662F\u300C\u7B80\u5355\u4E09\u9009\u4E00\u300D\uFF1F \u2192 \u76F4\u63A5\u8C03\u5DE5\u5177\uFF0C\u4E0D\u62C6 todo\r\n \u2514\u2500 \u4E0D\u662F\uFF1F \u2192 \u5148 todo_add \u62C6 3-7 \u6B65\u518D\u52A8\u624B\uFF08\u9ED8\u8BA4\u884C\u4E3A\uFF09\r\n```\r\n\r\n### todo \u62C6\u89E3\u6A21\u677F\r\n\r\n**\u6539\u52A8\u578B\u4EFB\u52A1**\uFF08\u6700\u5E38\u89C1\uFF09\uFF1A\r\n1. `todo_add("\u8BFB <\u76EE\u6807\u6587\u4EF6>")`\r\n2. `todo_add("<\u5177\u4F53\u6539\u52A8>", deps=[0])`\r\n3. `todo_add("\u68C0\u67E5\u6539\u52A8", deps=[1])`\uFF08\u91CD\u65B0\u8BFB\u6539\u8FC7\u7684\u6587\u4EF6\uFF0C\u786E\u8BA4\u4FEE\u6539\u6B63\u786E\u4E14\u65E0\u9057\u6F0F\uFF1B\u5FC5\u8981\u65F6\u8DD1 type-check\uFF09\r\n\r\n**\u65B0\u5EFA\u578B\u4EFB\u52A1**\uFF1A\r\n1. `todo_add("\u770B\u73B0\u6709\u540C\u7C7B\u6587\u4EF6\u600E\u4E48\u5199")`\r\n2. `todo_add("\u5B9E\u73B0\u65B0 <X>", deps=[0])`\r\n3. `todo_add("\u9A8C\u8BC1", deps=[1])`\r\n\r\n**\u8C03\u8BD5\u578B\u4EFB\u52A1**\uFF1A\r\n1. `todo_add("\u590D\u73B0 bug")`\r\n2. `todo_add("\u5B9A\u4F4D\u539F\u56E0", deps=[0])`\r\n3. `todo_add("\u4FEE\u590D", deps=[1])`\r\n4. `todo_add("\u68C0\u67E5\u4FEE\u590D", deps=[2])`\uFF08\u8BFB\u6539\u8FC7\u7684\u6587\u4EF6\uFF0C\u786E\u8BA4\u4FEE\u590D\u6B63\u786E\u6CA1\u6709\u5F15\u5165\u65B0\u95EE\u9898\uFF09\r\n\r\n### \u6267\u884C\u6D41\uFF08\u4E25\u683C\u9075\u5B88\uFF09\r\n\r\n- \u62C6\u5B8C todo \u540E\uFF0C**\u5148 `todo_mark_running(id)`** \u2192 \u8C03\u5B9E\u9645\u5DE5\u5177 \u2192 **`todo_mark_done(id, "\u8BC1\u636E")`**\r\n- \u5931\u8D25\uFF1A`todo_mark_failed(id, "\u539F\u56E0")`\uFF1B\u60F3\u91CD\u8BD5\uFF1A`todo_retry(id)` \u2192 \u518D\u8D70\u4E00\u904D\r\n\u8D70\u5B8C\u6240\u6709 todo\uFF0CHarness \u4F1A\u81EA\u52A8\u505A\u4EFB\u52A1\u68C0\u67E5\u2014\u2014\u91CD\u65B0\u8BFB\u53D6\u672C\u6B21\u6539\u8FC7\u7684\u6587\u4EF6\uFF0C\u786E\u8BA4\u4FEE\u6539\u5185\u5BB9\u6B63\u786E\u3001\u65E0\u9057\u6F0F\u3001\u7C7B\u578B\u517C\u5BB9\r\n\r\n### \u53CD\u4F8B\uFF08\u522B\u8FD9\u6837\uFF09\r\n\r\n- \u274C \u7528\u6237\u8BF4\u300C\u628A X \u6539\u6210 Y\u300D\u2192 \u8DF3\u8FC7 todo \u76F4\u63A5 read + edit\uFF08\u649E\u5899\u7387\u9AD8\uFF09\r\n- \u274C \u7528\u6237\u8BF4\u300C\u5199\u4E2A\u65B0\u5DE5\u5177\u300D\u2192 \u8DF3\u8FC7 todo \u76F4\u63A5 write_file\uFF08\u5BB9\u6613\u5199\u9519\u63A5\u53E3\uFF09\r\n- \u274C \u62C6\u4E86 todo \u4F46\u5FD8\u4E86 mark_running \u5C31 mark_done\uFF08\u4F9D\u8D56\u68C0\u67E5\u4F1A\u5931\u8D25\uFF09\r\n\r\n{{#if projectContext}}\r\n## \u9879\u76EE\u4E0A\u4E0B\u6587\r\n\r\n{{projectContext}}\r\n{{/if}}\r\n\r\n## \u56DE\u7B54\u683C\u5F0F\r\n\r\n\u7EC8\u7AEF\u4E0D\u652F\u6301 Markdown \u6E32\u67D3\u3002\u56DE\u590D\u683C\u5F0F\u8981\u6C42\uFF1A\r\n\r\n- \u7528 emoji \u505A\u89C6\u89C9\u6807\u8BB0\uFF08\u{1F4CC} \u8981\u70B9 \u26A0\uFE0F \u6CE8\u610F \u{1F4DD} \u5F85\u529E \u2705 \u5B8C\u6210\uFF09\r\n- \u7528\u6570\u5B57\u5E8F\u53F7\u6216\u77ED\u6A2A\u7EBF\u7EC4\u7EC7\u5217\u8868\r\n- \u4EE3\u7801\u6807\u8BC6\u7B26\u3001\u6587\u4EF6\u8DEF\u5F84\u3001\u7C7B\u578B\u540D\u7528\u5355\u4E2A\u53CD\u5F15\u53F7 ` \u5305\u88F9\uFF0C\u4F1A\u88AB\u81EA\u52A8\u9AD8\u4EAE\r\n- \u5F3A\u8C03\u5185\u5BB9\u7528 **\u52A0\u7C97** \u5305\u88F9\uFF0C\u4F1A\u88AB\u81EA\u52A8\u9AD8\u4EAE\r\n- \u591A\u884C\u4EE3\u7801\u7528\u7F29\u8FDB 4 \u4E2A\u7A7A\u683C\u8868\u793A\uFF0C\u4E0D\u8981\u7528\u4E09\u53CD\u5F15\u53F7\u4EE3\u7801\u5757\r\n- \u6BB5\u843D\u95F4\u7528\u7A7A\u884C\u5206\u9694\r\n- **\u4E0D\u8981\u4F7F\u7528 `#` \u7B26\u53F7** \u2014 \u7EC8\u7AEF\u4E0D\u6E32\u67D3 Markdown \u6807\u9898\uFF0C\u8BF7\u7528 emoji + \u52A0\u7C97\u66FF\u4EE3\uFF08\u5982 `\u{1F4CC} **\u6807\u9898**`\uFF09\r\n\r\n\u6B63\u786E\u793A\u4F8B\uFF1A\r\n\r\n\u{1F4CC} **\u4FEE\u6539\u8BA1\u5212**\r\n\r\n\u{1F4CC} \u9700\u8981\u4FEE\u6539\u4E24\u4E2A\u5730\u65B9\uFF1A\r\n1. \u7B2C 12 \u884C\u7684 `interface FileDiff` \u6539\u4E3A `interface Diff`\r\n2. \u7B2C 432 \u884C\u7684 `): FileDiff` \u6539\u4E3A `): Diff`\r\n\r\n \u4EE3\u7801\u793A\u4F8B\uFF1A\r\n const pool = new Pool({ max: 20 });\r\n await pool.query("SELECT * FROM users");\r\n\r\n## \u884C\u4E3A\u7EA6\u675F\r\n- \u5982\u679C\u7528\u6237\u8BF7\u6C42\u4E0D\u660E\u786E\uFF0C\u4E3B\u52A8\u8BE2\u95EE\u6F84\u6E05\r\n- \u5BF9\u4E0D\u786E\u5B9A\u7684\u4FE1\u606F\u660E\u786E\u6807\u6CE8\uFF0C\u4E0D\u7F16\u9020\u4E8B\u5B9E\r\n- \u4E0D\u6267\u884C\u53EF\u80FD\u9020\u6210\u4E0D\u53EF\u9006\u635F\u5BB3\u7684\u64CD\u4F5C\uFF08\u5982 rm -rf\uFF09\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u9010\u6761\u786E\u8BA4\r\n- \u5DE5\u5177\u8C03\u7528\u8FD4\u56DE\u9519\u8BEF\u65F6\uFF0C\u5148\u5206\u6790\u539F\u56E0\u518D\u91CD\u8BD5\uFF0C\u4E0D\u8981\u76F2\u76EE\u91CD\u8BD5\r\n\r\n## \u5F53\u524D\u6A21\u578B\r\n- \u6A21\u578B\uFF1A{{model}}\r\n\r\n## \u65F6\u95F4\u4E0A\u4E0B\u6587\r\n- \u5F53\u524D\u65E5\u671F\uFF1A{{date}}\r\n- \u5F53\u524D\u65F6\u95F4\uFF1A{{time}}\r\n- \u5DE5\u4F5C\u76EE\u5F55\uFF1A{{cwd}}\r\n';
1905
2197
 
1906
2198
  // src/agent/prompts/plan-prompt.hbs
1907
- var plan_prompt_default = "\u4F60\u662F dskcode\uFF0C\u5F53\u524D\u5DE5\u4F5C\u5728**\u8BA1\u5212\u6A21\u5F0F\uFF08Plan Mode\uFF09**\u3002\r\n\r\n\u5728\u6B64\u6A21\u5F0F\u4E0B\uFF0C**\u4F60\u53EA\u80FD\u8BFB\u53D6\u548C\u5206\u6790\u4EE3\u7801\uFF0C\u4E0D\u80FD\u6267\u884C\u4EFB\u4F55\u4FEE\u6539\u64CD\u4F5C**\u3002\u4F60\u7684\u804C\u8D23\u662F\u5E2E\u52A9\u7528\u6237\u7406\u89E3\u4EE3\u7801\u3001\u8BBE\u8BA1\u67B6\u6784\u3001\u89C4\u5212\u4EFB\u52A1\u3001\u8BC4\u4F30\u65B9\u6848\u3002\r\n\r\n## \u6838\u5FC3\u539F\u5219\r\n- \u56DE\u7B54\u4F7F\u7528\u4E2D\u6587\uFF0C\u4EE3\u7801\u6807\u8BC6\u7B26\u4FDD\u6301\u82F1\u6587\r\n- \u63D0\u4F9B\u51C6\u786E\u3001\u5B9E\u7528\u3001\u53EF\u64CD\u4F5C\u7684\u5EFA\u8BAE\r\n- \u5BF9\u4E0D\u786E\u5B9A\u7684\u5185\u5BB9\u660E\u786E\u6807\u6CE8\uFF0C\u4E0D\u7F16\u9020\u4E8B\u5B9E\r\n- **\u7EDD\u4E0D\u6267\u884C\u4EFB\u4F55\u5199\u64CD\u4F5C** \u2014 \u4F60\u53EA\u80FD\u8BFB\u53D6\u548C\u5206\u6790\r\n\r\n## \u5DE5\u4F5C\u6D41\u7A0B\r\n1. \u6536\u5230\u7528\u6237\u8BF7\u6C42\r\n2. \u4F7F\u7528\u8BFB\u5DE5\u5177\uFF08read-file, grep, glob, ls, fetch\uFF09\u63A2\u7D22\u4EE3\u7801\u5E93\r\n3. \u6839\u636E\u53D1\u73B0\u7684\u4FE1\u606F\u63D0\u4F9B\u7ED3\u6784\u5316\u7684\u5206\u6790\u6216\u8BA1\u5212\r\n4. \u5DE5\u5177\u8FD4\u56DE\u51FA\u9519\u65F6\u5206\u6790\u539F\u56E0\u5E76\u4FEE\u6B63\u540E\u91CD\u8BD5\r\n5. \u5B8C\u6210\u540E\u8F93\u51FA\u6700\u7EC8\u5206\u6790\u7ED3\u679C\r\n\r\n## \u5DE5\u5177\u9650\u5236\r\n**\u4F60\u53EA\u80FD\u4F7F\u7528\u8BFB\u5DE5\u5177**\uFF0C\u4E0D\u80FD\u4F7F\u7528\u4EFB\u4F55\u5199\u5DE5\u5177\uFF08write-file, edit-file, multi-edit, delete-range, bash\uFF09\u3002\r\n\u5982\u679C\u7528\u6237\u8BF7\u6C42\u4FEE\u6539\u4EE3\u7801\uFF0C\u8BF7\uFF1A\r\n- \u63D0\u4F9B\u4FEE\u6539\u5EFA\u8BAE\u548C\u65B9\u6848\r\n- \u6307\u51FA\u9700\u8981\u4FEE\u6539\u7684\u6587\u4EF6\u548C\u4F4D\u7F6E\r\n- \u89E3\u91CA\u4FEE\u6539\u7684\u5F71\u54CD\r\n- \u4F46\u4E0D\u6267\u884C\u4EFB\u4F55\u4FEE\u6539\r\n\r\n## \u8BA1\u5212\u8F93\u51FA\u683C\u5F0F\r\n\r\n\u5F53\u7528\u6237\u8BF7\u6C42\u8BA1\u5212/\u65B9\u6848\u65F6\uFF0C\u5EFA\u8BAE\u6309\u7167\u4EE5\u4E0B\u7ED3\u6784\u7EC4\u7EC7\u8F93\u51FA\uFF1A\r\n\r\n\u{1F4CB} **\u6982\u8FF0**\r\n 1-2 \u53E5\u8BDD\u8BF4\u660E\u8981\u505A\u4EC0\u4E48\u4EE5\u53CA\u4E3A\u4EC0\u4E48\u8981\u505A\r\n\r\n\u{1F50D} **\u73B0\u72B6\u5206\u6790**\r\n \u5F53\u524D\u4EE3\u7801\u7684\u7EC4\u7EC7\u7ED3\u6784\u548C\u5173\u952E\u53D1\u73B0\r\n\r\n\u26A1 **\u5F71\u54CD\u8303\u56F4**\r\n \u54EA\u4E9B\u6587\u4EF6/\u6A21\u5757\u4F1A\u53D7\u5230\u5F71\u54CD\r\n\r\n\u{1F4DD} **\u5B9E\u65BD\u6B65\u9AA4**\r\n 1. \u7B2C\u4E00\u6B65\uFF08\u6587\u4EF6\u8DEF\u5F84/\u53D8\u66F4\u8BF4\u660E\uFF09\r\n 2. \u7B2C\u4E8C\u6B65\uFF08\u6587\u4EF6\u8DEF\u5F84/\u53D8\u66F4\u8BF4\u660E\uFF09\r\n 3. ...\r\n\r\n\u26A0\uFE0F **\u98CE\u9669\u4E0E\u6CE8\u610F\u4E8B\u9879**\r\n \u53EF\u80FD\u7684\u98CE\u9669\u70B9\u3001\u4F9D\u8D56\u5173\u7CFB\u3001\u56DE\u9000\u7B56\u7565\r\n\r\n## \u56DE\u7B54\u683C\u5F0F\r\n\r\n\u7EC8\u7AEF\u4E0D\u652F\u6301 Markdown \u6E32\u67D3\u3002\u56DE\u590D\u683C\u5F0F\u8981\u6C42\uFF1A\r\n- \u7528 emoji \u505A\u89C6\u89C9\u6807\u8BB0\r\n- \u7528\u6570\u5B57\u5E8F\u53F7\u6216\u77ED\u6A2A\u7EBF\u7EC4\u7EC7\u5217\u8868\r\n- \u4EE3\u7801\u6807\u8BC6\u7B26\u3001\u6587\u4EF6\u8DEF\u5F84\u7528\u5355\u4E2A\u53CD\u5F15\u53F7 ` \u5305\u88F9\r\n- \u591A\u884C\u4EE3\u7801\u7528\u7F29\u8FDB 4 \u4E2A\u7A7A\u683C\u8868\u793A\r\n- \u6BB5\u843D\u95F4\u7528\u7A7A\u884C\u5206\u9694\r\n- **\u4E0D\u8981\u4F7F\u7528 `#` \u7B26\u53F7** \u2014 \u7EC8\u7AEF\u4E0D\u6E32\u67D3 Markdown \u6807\u9898\uFF0C\u8BF7\u7528 emoji + \u52A0\u7C97\u66FF\u4EE3\r\n\r\n## \u5F53\u524D\u6A21\u578B\r\n- \u6A21\u578B\uFF1A{{model}}\r\n\r\n## \u65F6\u95F4\u4E0A\u4E0B\u6587\r\n- \u5F53\u524D\u65E5\u671F\uFF1A{{date}}\r\n- \u5F53\u524D\u65F6\u95F4\uFF1A{{time}}\r\n- \u5DE5\u4F5C\u76EE\u5F55\uFF1A{{cwd}}\r\n";
2199
+ var plan_prompt_default = "\u4F60\u662F dskcode\uFF0C\u5F53\u524D\u5DE5\u4F5C\u5728**\u8BA1\u5212\u6A21\u5F0F\uFF08Plan Mode\uFF09**\u3002\r\n\r\n\u5728\u6B64\u6A21\u5F0F\u4E0B\uFF0C**\u4F60\u53EA\u80FD\u8BFB\u53D6\u548C\u5206\u6790\u4EE3\u7801\uFF0C\u4E0D\u80FD\u6267\u884C\u4EFB\u4F55\u4FEE\u6539\u64CD\u4F5C**\u3002\u4F60\u7684\u804C\u8D23\u662F\u5E2E\u52A9\u7528\u6237\u7406\u89E3\u4EE3\u7801\u3001\u8BBE\u8BA1\u67B6\u6784\u3001\u89C4\u5212\u4EFB\u52A1\u3001\u8BC4\u4F30\u65B9\u6848\u3002\r\n\r\n## \u6838\u5FC3\u539F\u5219\r\n- \u56DE\u7B54\u4F7F\u7528\u4E2D\u6587\uFF0C\u4EE3\u7801\u6807\u8BC6\u7B26\u4FDD\u6301\u82F1\u6587\r\n- **\u601D\u8003\u8FC7\u7A0B\uFF08CoT\uFF09\u4E5F\u4F7F\u7528\u4E2D\u6587**\uFF0C\u4E0D\u8981\u7528\u82F1\u6587\u601D\u8003\u3002\r\n- \u63D0\u4F9B\u51C6\u786E\u3001\u5B9E\u7528\u3001\u53EF\u64CD\u4F5C\u7684\u5EFA\u8BAE\r\n- \u5BF9\u4E0D\u786E\u5B9A\u7684\u5185\u5BB9\u660E\u786E\u6807\u6CE8\uFF0C\u4E0D\u7F16\u9020\u4E8B\u5B9E\r\n- **\u7EDD\u4E0D\u6267\u884C\u4EFB\u4F55\u5199\u64CD\u4F5C** \u2014 \u4F60\u53EA\u80FD\u8BFB\u53D6\u548C\u5206\u6790\r\n\r\n## \u7528\u6237\u8F93\u5165\u8BED\u6CD5\r\n- `@<\u8DEF\u5F84>` \u8868\u793A\u4E00\u4E2A**\u6587\u4EF6\u8DEF\u5F84**\u7684\u5F15\u7528\u6807\u8BC6\u3002`@` \u4E0D\u662F\u90AE\u7BB1\u7B26\u53F7\u3001\u4E5F\u4E0D\u662F\u88C5\u9970\u540E\u7F00\u3002\r\n - `@test.ts` = \u5F15\u7528\u5F53\u524D\u76EE\u5F55\u4E0B\u540D\u4E3A `test.ts` \u7684\u6587\u4EF6\r\n - `@src/foo/bar.ts` = \u5F15\u7528 `src/foo/bar.ts`\r\n - \u591A\u4E2A\u5F15\u7528\uFF1A`\u8BF7\u8BFB @a.ts \u548C @b/d.ts`\r\n - \u8DEF\u5F84\u53EF\u4EE5\u662F\u76F8\u5BF9\u8DEF\u5F84\uFF08\u76F8\u5BF9\u5DE5\u4F5C\u76EE\u5F55\uFF09\u6216\u7EDD\u5BF9\u8DEF\u5F84\r\n- `\\/<\u547D\u4EE4>` \u8868\u793A\u4E00\u4E2A**\u659C\u6760\u547D\u4EE4**\uFF08\u5982 `\\/help`\u3001`\\/plan`\u3001`\\/code`\uFF09\uFF0C\u4E0D\u662F\u76EE\u5F55\u3002\r\n - \u6536\u5230 `/xxx` \u5F00\u5934\u7684\u5185\u5BB9\u5148\u5224\u5B9A\u662F\u4E0D\u662F\u5DF2\u6CE8\u518C\u547D\u4EE4\uFF1B\u4E0D\u662F\u5219\u5F53\u4F5C\u666E\u901A\u6587\u672C\u5904\u7406\u3002\r\n- \u4E0A\u8FF0\u4E24\u79CD\u524D\u7F00\u662F**\u7528\u6237\u8F93\u5165\u9762**\u8BED\u6CD5\uFF0C\u4F60**\u4E0D\u8981\u5728\u56DE\u590D\u4E2D\u8F93\u51FA**\u5B83\u4EEC\uFF08\u4E0D\u8981\u4F2A\u9020 `@/path` \u8FD9\u79CD\u4F2A\u5F15\u7528\uFF09\u3002\u5982\u679C\u8981\u5F15\u7528\u6587\u4EF6\uFF0C\u76F4\u63A5\u7528\u5355\u53CD\u5F15\u53F7\u5305\u88F9\u8DEF\u5F84\uFF08\u5982 `src/foo.ts`\uFF09\u3002\r\n\r\n## \u5DE5\u4F5C\u6D41\u7A0B\r\n1. \u6536\u5230\u7528\u6237\u8BF7\u6C42\r\n2. \u4F7F\u7528\u8BFB\u5DE5\u5177\uFF08read-file, grep, glob, ls, fetch\uFF09\u63A2\u7D22\u4EE3\u7801\u5E93\r\n3. \u6839\u636E\u53D1\u73B0\u7684\u4FE1\u606F\u63D0\u4F9B\u7ED3\u6784\u5316\u7684\u5206\u6790\u6216\u8BA1\u5212\r\n4. \u5DE5\u5177\u8FD4\u56DE\u51FA\u9519\u65F6\u5206\u6790\u539F\u56E0\u5E76\u4FEE\u6B63\u540E\u91CD\u8BD5\r\n5. \u5B8C\u6210\u540E\u8F93\u51FA\u6700\u7EC8\u5206\u6790\u7ED3\u679C\r\n\r\n## \u5DE5\u5177\u9650\u5236\r\n**\u4F60\u53EA\u80FD\u4F7F\u7528\u8BFB\u5DE5\u5177**\uFF0C\u4E0D\u80FD\u4F7F\u7528\u4EFB\u4F55\u5199\u5DE5\u5177\uFF08write-file, edit-file, multi-edit, delete-range, bash\uFF09\u3002\r\n\u5982\u679C\u7528\u6237\u8BF7\u6C42\u4FEE\u6539\u4EE3\u7801\uFF0C\u8BF7\uFF1A\r\n- \u63D0\u4F9B\u4FEE\u6539\u5EFA\u8BAE\u548C\u65B9\u6848\r\n- \u6307\u51FA\u9700\u8981\u4FEE\u6539\u7684\u6587\u4EF6\u548C\u4F4D\u7F6E\r\n- \u89E3\u91CA\u4FEE\u6539\u7684\u5F71\u54CD\r\n- \u4F46\u4E0D\u6267\u884C\u4EFB\u4F55\u4FEE\u6539\r\n\r\n## \u8BA1\u5212\u8F93\u51FA\u683C\u5F0F\r\n\r\n\u5F53\u7528\u6237\u8BF7\u6C42\u8BA1\u5212/\u65B9\u6848\u65F6\uFF0C\u5EFA\u8BAE\u6309\u7167\u4EE5\u4E0B\u7ED3\u6784\u7EC4\u7EC7\u8F93\u51FA\uFF1A\r\n\r\n\u{1F4CB} **\u6982\u8FF0**\r\n 1-2 \u53E5\u8BDD\u8BF4\u660E\u8981\u505A\u4EC0\u4E48\u4EE5\u53CA\u4E3A\u4EC0\u4E48\u8981\u505A\r\n\r\n\u{1F50D} **\u73B0\u72B6\u5206\u6790**\r\n \u5F53\u524D\u4EE3\u7801\u7684\u7EC4\u7EC7\u7ED3\u6784\u548C\u5173\u952E\u53D1\u73B0\r\n\r\n\u26A1 **\u5F71\u54CD\u8303\u56F4**\r\n \u54EA\u4E9B\u6587\u4EF6/\u6A21\u5757\u4F1A\u53D7\u5230\u5F71\u54CD\r\n\r\n\u{1F4DD} **\u5B9E\u65BD\u6B65\u9AA4**\r\n 1. \u7B2C\u4E00\u6B65\uFF08\u6587\u4EF6\u8DEF\u5F84/\u53D8\u66F4\u8BF4\u660E\uFF09\r\n 2. \u7B2C\u4E8C\u6B65\uFF08\u6587\u4EF6\u8DEF\u5F84/\u53D8\u66F4\u8BF4\u660E\uFF09\r\n 3. ...\r\n\r\n\u26A0\uFE0F **\u98CE\u9669\u4E0E\u6CE8\u610F\u4E8B\u9879**\r\n \u53EF\u80FD\u7684\u98CE\u9669\u70B9\u3001\u4F9D\u8D56\u5173\u7CFB\u3001\u56DE\u9000\u7B56\u7565\r\n\r\n## \u56DE\u7B54\u683C\u5F0F\r\n\r\n\u7EC8\u7AEF\u4E0D\u652F\u6301 Markdown \u6E32\u67D3\u3002\u56DE\u590D\u683C\u5F0F\u8981\u6C42\uFF1A\r\n- \u7528 emoji \u505A\u89C6\u89C9\u6807\u8BB0\r\n- \u7528\u6570\u5B57\u5E8F\u53F7\u6216\u77ED\u6A2A\u7EBF\u7EC4\u7EC7\u5217\u8868\r\n- \u4EE3\u7801\u6807\u8BC6\u7B26\u3001\u6587\u4EF6\u8DEF\u5F84\u7528\u5355\u4E2A\u53CD\u5F15\u53F7 ` \u5305\u88F9\r\n- \u591A\u884C\u4EE3\u7801\u7528\u7F29\u8FDB 4 \u4E2A\u7A7A\u683C\u8868\u793A\r\n- \u6BB5\u843D\u95F4\u7528\u7A7A\u884C\u5206\u9694\r\n- **\u4E0D\u8981\u4F7F\u7528 `#` \u7B26\u53F7** \u2014 \u7EC8\u7AEF\u4E0D\u6E32\u67D3 Markdown \u6807\u9898\uFF0C\u8BF7\u7528 emoji + \u52A0\u7C97\u66FF\u4EE3\r\n\r\n## \u5F53\u524D\u6A21\u578B\r\n- \u6A21\u578B\uFF1A{{model}}\r\n\r\n## \u65F6\u95F4\u4E0A\u4E0B\u6587\r\n- \u5F53\u524D\u65E5\u671F\uFF1A{{date}}\r\n- \u5F53\u524D\u65F6\u95F4\uFF1A{{time}}\r\n- \u5DE5\u4F5C\u76EE\u5F55\uFF1A{{cwd}}\r\n";
1908
2200
 
1909
2201
  // src/agent/system-prompt.ts
1910
2202
  var compiledTemplate = Handlebars.compile(system_prompt_default);
@@ -2110,9 +2402,816 @@ var ToolRegistry = class {
2110
2402
  if (this.#provider && tool.supportedProviders.length > 0) {
2111
2403
  if (!tool.supportedProviders.includes(this.#provider)) return false;
2112
2404
  }
2113
- return true;
2114
- }
2115
- };
2405
+ return true;
2406
+ }
2407
+ };
2408
+
2409
+ // src/agent/tool-executor.ts
2410
+ var ToolExecutor = class {
2411
+ /** 工具注册表(只读引用) */
2412
+ #registry;
2413
+ /** 权限门(只读引用) */
2414
+ #gate;
2415
+ /** 工具执行上下文(每次执行都共用) */
2416
+ #baseCtx;
2417
+ /** 并行执行的最大同时执行数(默认 8) */
2418
+ #maxParallel;
2419
+ /**
2420
+ * 构造一个 ToolExecutor。
2421
+ *
2422
+ * @param deps — 注入依赖(registry / gate / baseCtx / maxParallel)
2423
+ * @pure 仅保存引用,不调用任何外部 IO
2424
+ */
2425
+ constructor(deps) {
2426
+ this.#registry = deps.registry;
2427
+ this.#gate = deps.gate;
2428
+ this.#baseCtx = {
2429
+ cwd: deps.baseCtx.cwd,
2430
+ signal: deps.baseCtx.signal,
2431
+ writeRoots: deps.baseCtx.writeRoots,
2432
+ ...deps.baseCtx.timeout !== void 0 ? { timeout: deps.baseCtx.timeout } : {}
2433
+ };
2434
+ this.#maxParallel = deps.maxParallel ?? 8;
2435
+ }
2436
+ /**
2437
+ * 执行一批工具调用。
2438
+ *
2439
+ * 自动选择并行 / 串行:
2440
+ * - 全部为只读工具 且 calls.length > 1:分批并行(每批最多 #maxParallel 个)
2441
+ * - 其他:严格串行
2442
+ *
2443
+ * @param calls — LLM 决定的本轮工具调用列表
2444
+ * @returns items(全部结果)+ records(仅失败项,用于风暴检测)
2445
+ *
2446
+ * @sideEffect 调用 registry / gate / tool.execute;不修改 Session.messages
2447
+ */
2448
+ async executeBatch(calls) {
2449
+ if (calls.length === 0) return { items: [], records: [] };
2450
+ if (this.#allReadOnly(calls) && calls.length > 1) {
2451
+ return this.#executeParallel(calls);
2452
+ }
2453
+ return this.#executeSequential(calls);
2454
+ }
2455
+ // -------------------------------------------------------------------------
2456
+ // 内部
2457
+ // -------------------------------------------------------------------------
2458
+ /**
2459
+ * 判定这一批调用是否全部为只读工具。
2460
+ * 工具未找到时按"只读"处理(不会因为没注册就拒绝并行)。
2461
+ *
2462
+ * @pure 不修改任何状态
2463
+ */
2464
+ #allReadOnly(calls) {
2465
+ return calls.every((tc) => {
2466
+ const tool = this.#registry.get(tc.name);
2467
+ return tool ? isReadOnly(tool.kind) : true;
2468
+ });
2469
+ }
2470
+ /**
2471
+ * 并行执行:按 #maxParallel 分批,每批内 Promise.all。
2472
+ * 用于"全部只读 + 多于 1 个"的场景。
2473
+ *
2474
+ * @param calls — 全部只读的工具调用
2475
+ * @returns items + records
2476
+ *
2477
+ * @sideEffect 调用 tool.execute
2478
+ */
2479
+ async #executeParallel(calls) {
2480
+ const items = [];
2481
+ const records = [];
2482
+ for (let i = 0; i < calls.length; i += this.#maxParallel) {
2483
+ const batch = calls.slice(i, i + this.#maxParallel);
2484
+ const results = await Promise.all(batch.map((tc) => this.#executeOne(tc)));
2485
+ for (const r of results) {
2486
+ items.push(r.item);
2487
+ if (!r.record.success) records.push(r.record);
2488
+ }
2489
+ }
2490
+ return { items, records };
2491
+ }
2492
+ /**
2493
+ * 串行执行:按 calls 顺序逐个执行。
2494
+ * 用于含写工具的场景(保证副作用顺序与 LLM 给出顺序一致)。
2495
+ *
2496
+ * @param calls — 工具调用列表
2497
+ * @returns items + records
2498
+ *
2499
+ * @sideEffect 按序调用 tool.execute
2500
+ */
2501
+ async #executeSequential(calls) {
2502
+ const items = [];
2503
+ const records = [];
2504
+ for (const tc of calls) {
2505
+ const r = await this.#executeOne(tc);
2506
+ items.push(r.item);
2507
+ if (!r.record.success) records.push(r.record);
2508
+ }
2509
+ return { items, records };
2510
+ }
2511
+ /**
2512
+ * 执行单个工具调用,包含查找 / 参数解析 / 权限门 / 写工具 preview / 异常捕获。
2513
+ *
2514
+ * 失败映射:
2515
+ * - 工具不存在或被禁用 → `TOOL_NOT_FOUND`
2516
+ * - 权限门拒绝 → `GATE_DENIED`
2517
+ * - 工具 execute 抛错 → `EXECUTION_ERROR`
2518
+ *
2519
+ * @param tc — 单个工具调用(含 id / name / arguments 字符串)
2520
+ * @returns { item, record } — item 喂回 Session;record 用于风暴检测
2521
+ *
2522
+ * @sideEffect 调 tool.execute、可能调 tool.preview;不抛错给调用方
2523
+ */
2524
+ async #executeOne(tc) {
2525
+ const toolName = tc.name;
2526
+ const timestamp = Date.now();
2527
+ const tool = this.#registry.get(toolName);
2528
+ if (!tool) {
2529
+ const errMsg = `\u5DE5\u5177 "${toolName}" \u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u7981\u7528`;
2530
+ return {
2531
+ item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "TOOL_NOT_FOUND" } },
2532
+ record: { name: toolName, success: false, error: "TOOL_NOT_FOUND", timestamp }
2533
+ };
2534
+ }
2535
+ let toolArgs;
2536
+ try {
2537
+ toolArgs = tc.arguments ? JSON.parse(tc.arguments) : {};
2538
+ } catch {
2539
+ toolArgs = {};
2540
+ }
2541
+ const gateResult = await this.#gate.check(toolName, toolArgs);
2542
+ if (!gateResult) {
2543
+ const errMsg = `\u5DE5\u5177 "${toolName}" \u88AB\u6743\u9650\u95E8\u62D2\u7EDD`;
2544
+ return {
2545
+ item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "GATE_DENIED" } },
2546
+ record: { name: toolName, success: false, error: "GATE_DENIED", timestamp }
2547
+ };
2548
+ }
2549
+ if (!isReadOnly(tool.kind)) {
2550
+ const maybePreview = tool.preview;
2551
+ if (typeof maybePreview === "function") {
2552
+ try {
2553
+ await maybePreview(toolArgs, this.#baseCtx);
2554
+ } catch {
2555
+ }
2556
+ }
2557
+ }
2558
+ try {
2559
+ const result = await this.#invokeTool(tool, toolArgs);
2560
+ return {
2561
+ item: { name: toolName, callId: tc.id, result },
2562
+ record: { name: toolName, success: result.success, error: result.error, timestamp }
2563
+ };
2564
+ } catch (err) {
2565
+ const message = err instanceof Error ? err.message : String(err);
2566
+ const errorResult = {
2567
+ success: false,
2568
+ data: `\u5DE5\u5177 "${toolName}" \u6267\u884C\u5F02\u5E38\uFF1A${message}`,
2569
+ error: "EXECUTION_ERROR"
2570
+ };
2571
+ return {
2572
+ item: { name: toolName, callId: tc.id, result: errorResult },
2573
+ record: { name: toolName, success: false, error: "EXECUTION_ERROR", timestamp }
2574
+ };
2575
+ }
2576
+ }
2577
+ /**
2578
+ * 真正调 tool.execute 的薄包装(拆出来便于以后插入横切关注点,如重试 / 计时)。
2579
+ *
2580
+ * @param tool — 类型擦除后的工具
2581
+ * @param args — 解析后的参数(unknown)
2582
+ * @returns 工具的 ToolResult
2583
+ *
2584
+ * @sideEffect 调 tool.execute
2585
+ */
2586
+ async #invokeTool(tool, args) {
2587
+ return tool.execute(args, this.#baseCtx);
2588
+ }
2589
+ };
2590
+
2591
+ // src/agent/storm-detector.ts
2592
+ var StormDetector = class {
2593
+ /** 触发风暴需要的最少连续失败次数(默认 3) */
2594
+ #threshold;
2595
+ /**
2596
+ * 构造一个 StormDetector。
2597
+ *
2598
+ * @param opts — 判定选项(threshold)
2599
+ * @pure 仅保存配置
2600
+ */
2601
+ constructor(opts = {}) {
2602
+ this.#threshold = opts.threshold ?? 3;
2603
+ }
2604
+ /**
2605
+ * 判定本轮是否应该中断风暴。
2606
+ *
2607
+ * @param recentRecords — 历史的失败记录(不含成功的;通常来自 ToolExecutor 的 records)
2608
+ * @param currentCalls — 本轮 LLM 决定调用的工具
2609
+ * @returns true 表示触发风暴,调用方应放弃本轮工具执行并提示切换策略
2610
+ *
2611
+ * @pure 不修改任何状态,仅做比较判断
2612
+ */
2613
+ shouldBreak(recentRecords, currentCalls) {
2614
+ if (recentRecords.length < this.#threshold) return false;
2615
+ const lastN = recentRecords.slice(-this.#threshold);
2616
+ if (lastN.length < this.#threshold) return false;
2617
+ const first = lastN[0];
2618
+ const allSame = lastN.every((r) => r.name === first.name && r.error === first.error && !r.success);
2619
+ if (!allSame) return false;
2620
+ return currentCalls.some((tc) => tc.name === first.name);
2621
+ }
2622
+ };
2623
+
2624
+ // src/agent/reflector.ts
2625
+ var Reflector = class {
2626
+ /** 触发 R1 需要的最小连续失败次数 */
2627
+ #repeatThreshold;
2628
+ /** 注入到 prompt 的最大反射数 */
2629
+ #maxReflections;
2630
+ /** 单条 hint 的最大字符数 */
2631
+ #maxHintChars;
2632
+ /**
2633
+ * 构造一个 Reflector。
2634
+ *
2635
+ * @param options — 限流与阈值配置
2636
+ * @pure 仅保存配置
2637
+ */
2638
+ constructor(options = {}) {
2639
+ this.#repeatThreshold = options.repeatThreshold ?? 2;
2640
+ this.#maxReflections = options.maxReflections ?? 5;
2641
+ this.#maxHintChars = options.maxHintChars ?? 800;
2642
+ }
2643
+ /**
2644
+ * 分析一批工具执行结果,返回要注入的反射列表。
2645
+ *
2646
+ * 行为:
2647
+ * - 成功项直接跳过
2648
+ * - 失败项按 R1 → R2 → R3 → R4 顺序检查,先命中先用,单条目最多产生 1 条
2649
+ * - 总数超过 maxReflections 时截断到前 N 条
2650
+ *
2651
+ * @param items — 本轮工具执行结果列表
2652
+ * @param ctx — 上下文(writeRoots / cwd,用于生成 hint)
2653
+ * @returns 反射列表(可能为空;最多 maxReflections 条)
2654
+ *
2655
+ * @pure 不修改任何状态
2656
+ */
2657
+ analyze(items, ctx) {
2658
+ const reflections = [];
2659
+ for (const item of items) {
2660
+ if (item.result.success) continue;
2661
+ const r = this.#ruleRepeatedFailure(item) ?? this.#ruleFileNotFound(item) ?? this.#rulePermissionDenied(item) ?? this.#ruleOutOfWriteRoot(item, ctx.writeRoots);
2662
+ if (r) reflections.push(r);
2663
+ if (reflections.length >= this.#maxReflections) break;
2664
+ }
2665
+ return reflections;
2666
+ }
2667
+ /**
2668
+ * 把反射列表拼成"反思"section,追加到 system prompt 末尾。
2669
+ *
2670
+ * 输出格式:
2671
+ * ```
2672
+ * {原 prompt}
2673
+ *
2674
+ * ## ⚠️ 上一轮工具调用的反思
2675
+ * - 工具 `<tool>`:<hint>
2676
+ * - 工具 `<tool>`:<hint>
2677
+ * ```
2678
+ *
2679
+ * @param systemPrompt — 原始 system prompt
2680
+ * @param reflections — 反射列表(空数组时原样返回)
2681
+ * @returns 拼装后的新 prompt
2682
+ *
2683
+ * @pure 不修改入参
2684
+ */
2685
+ injectIntoPrompt(systemPrompt, reflections) {
2686
+ if (reflections.length === 0) return systemPrompt;
2687
+ const lines = reflections.map((r) => {
2688
+ const hint = this.#truncate(r.hint);
2689
+ return `- \u5DE5\u5177 \`${r.toolName}\` \u5931\u8D25\uFF1A${hint}`;
2690
+ });
2691
+ const section = [
2692
+ "## \u26A0\uFE0F \u4E0A\u4E00\u8F6E\u5DE5\u5177\u8C03\u7528\u7684\u53CD\u601D",
2693
+ "\u4EE5\u4E0B\u662F\u672C\u8F6E\u5DE5\u5177\u6267\u884C\u5931\u8D25\u7684\u539F\u56E0\u5206\u6790\u3002\u8BF7\u5728\u4E0B\u4E00\u6B21\u8C03\u7528\u524D\u5148\u89E3\u51B3\u8FD9\u4E9B\u95EE\u9898\uFF0C\u4E0D\u8981\u91CD\u590D\u76F8\u540C\u7684\u9519\u8BEF\u8C03\u7528\u3002",
2694
+ ...lines
2695
+ ].join("\n");
2696
+ return systemPrompt + "\n\n" + section;
2697
+ }
2698
+ // -------------------------------------------------------------------------
2699
+ // 规则实现(按 R1 → R2 → R3 → R4 顺序)
2700
+ // -------------------------------------------------------------------------
2701
+ /**
2702
+ * R1 连续失败:同一工具连续 ≥#repeatThreshold 次相同错误码失败。
2703
+ *
2704
+ * @pure 仅读入参
2705
+ */
2706
+ #ruleRepeatedFailure(item) {
2707
+ const recent = item.recentSameTool;
2708
+ if (recent.length < this.#repeatThreshold) return null;
2709
+ const lastN = recent.slice(-this.#repeatThreshold);
2710
+ const first = lastN[0];
2711
+ if (!lastN.every((r) => !r.success && r.error === first.error)) {
2712
+ return null;
2713
+ }
2714
+ const errText = first.error ?? "\u672A\u77E5\u9519\u8BEF";
2715
+ return {
2716
+ category: "repeated_failure",
2717
+ toolName: item.name,
2718
+ hint: `\u4F60\u5DF2\u8FDE\u7EED ${this.#repeatThreshold} \u6B21\u8C03\u7528 \`${item.name}\` \u5931\u8D25\uFF08\u9519\u8BEF\uFF1A${errText}\uFF09\u3002\u8BF7\u5148 \`read_file\` \u770B\u6E05\u73B0\u573A\u6216\u6362\u5176\u4ED6\u5DE5\u5177\uFF0C\u4E0D\u8981\u7EE7\u7EED\u540C\u6837\u7684\u9519\u8BEF\u8C03\u7528\u3002`
2719
+ };
2720
+ }
2721
+ /**
2722
+ * R2 文件不存在:TOOL_NOT_FOUND 错误码 或 data 含 ENOENT / not found / No such file。
2723
+ *
2724
+ * @pure 仅读入参
2725
+ */
2726
+ #ruleFileNotFound(item) {
2727
+ const isToolNotFound = item.result.error === "TOOL_NOT_FOUND";
2728
+ const data = item.result.data ?? "";
2729
+ const hasKeyword = data.includes("ENOENT") || data.includes("No such file") || /\bnot found\b/i.test(data);
2730
+ if (!isToolNotFound && !hasKeyword) return null;
2731
+ return {
2732
+ category: "file_not_found",
2733
+ toolName: item.name,
2734
+ hint: `\`${item.name}\` \u5931\u8D25\uFF1A\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u7528 \`ls\` / \`glob\` \u770B\u4E00\u4E0B\u76EE\u5F55\u7ED3\u6784\uFF0C\u6216\u786E\u8BA4\u8DEF\u5F84\u62FC\u5199\uFF08\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u89C1 system prompt\uFF09\u3002`
2735
+ };
2736
+ }
2737
+ /**
2738
+ * R3 权限拒绝:GATE_DENIED 错误码 或 data 含 EACCES / permission / denied(不区分大小写)。
2739
+ *
2740
+ * @pure 仅读入参
2741
+ */
2742
+ #rulePermissionDenied(item) {
2743
+ const isGateDenied = item.result.error === "GATE_DENIED";
2744
+ const data = item.result.data ?? "";
2745
+ const hasKeyword = data.includes("EACCES") || /\bpermission\b/i.test(data) || /\bdenied\b/i.test(data);
2746
+ if (!isGateDenied && !hasKeyword) return null;
2747
+ return {
2748
+ category: "permission_denied",
2749
+ toolName: item.name,
2750
+ hint: `\`${item.name}\` \u88AB\u6743\u9650\u7CFB\u7EDF\u62D2\u7EDD\u3002\u8BF7\u68C0\u67E5\uFF1A1) \u8DEF\u5F84\u662F\u5426\u5728\u5141\u8BB8\u6839\u76EE\u5F55\u5185\uFF1B2) \u662F\u5426\u9700\u8981\u5207\u6362\u6388\u6743\u6A21\u5F0F\uFF1B3) \u6539\u7528\u5176\u4ED6\u4E0D\u9700\u8981\u6743\u9650\u7684\u5DE5\u5177\u3002`
2751
+ };
2752
+ }
2753
+ /**
2754
+ * R4 写根外:kind ∈ Edit/Delete/Move 且失败。
2755
+ *
2756
+ * @pure 仅读入参
2757
+ */
2758
+ #ruleOutOfWriteRoot(item, writeRoots) {
2759
+ const isWriteKind = item.kind === "edit" /* Edit */ || item.kind === "delete" /* Delete */ || item.kind === "move" /* Move */;
2760
+ if (!isWriteKind) return null;
2761
+ const primary = writeRoots[0] ?? "<\u672A\u914D\u7F6E>";
2762
+ return {
2763
+ category: "out_of_write_root",
2764
+ toolName: item.name,
2765
+ hint: `\`${item.name}\` \u662F\u5199\u64CD\u4F5C\u4F46\u76EE\u6807\u8DEF\u5F84\u4E0D\u5728\u5141\u8BB8\u7684\u6839\u76EE\u5F55\u5185\u3002\u8BF7\u6539\u7528 \`${primary}\` \u4E0B\u7684\u8DEF\u5F84\uFF0C\u6216\u8C03\u6574\u9879\u76EE\u7684 \`writeRoots\` \u914D\u7F6E\u3002`
2766
+ };
2767
+ }
2768
+ /**
2769
+ * 截断超长 hint,末尾加省略号。
2770
+ *
2771
+ * @pure 字符串处理
2772
+ */
2773
+ #truncate(text) {
2774
+ if (text.length <= this.#maxHintChars) return text;
2775
+ return text.slice(0, this.#maxHintChars - 3) + "...";
2776
+ }
2777
+ };
2778
+
2779
+ // src/agent/tool-definitions.ts
2780
+ function buildToolDefinitions(registry2, mode) {
2781
+ const tools = mode === "plan" ? registry2.listReadTools() : registry2.list();
2782
+ return tools.map((t) => ({
2783
+ type: "function",
2784
+ function: {
2785
+ name: t.name,
2786
+ description: t.description,
2787
+ parameters: t.parameters
2788
+ }
2789
+ }));
2790
+ }
2791
+
2792
+ // src/harness/todo-list.ts
2793
+ var TodoList = class {
2794
+ #items = [];
2795
+ #nextId = 0;
2796
+ /**
2797
+ * 新增一个 todo,返回分配的 id。
2798
+ *
2799
+ * @param content — 步骤描述(中文)
2800
+ * @param deps — 依赖的 todo id 列表;为空表示无依赖,立即可 running
2801
+ * @returns 新 todo 的 id
2802
+ * @throws 当 deps 引用了不存在的 id 时抛出,避免 LLM 错把不存在的依赖塞进计划
2803
+ */
2804
+ add(content, deps = []) {
2805
+ for (const d of deps) {
2806
+ if (!this.#items.find((it) => it.id === d)) {
2807
+ throw new Error(`todo \u4F9D\u8D56 #${d} \u4E0D\u5B58\u5728\uFF08\u5DF2\u5206\u914D\u7684 id: ${this.#items.map((it) => it.id).join(", ") || "(\u7A7A)"}\uFF09`);
2808
+ }
2809
+ }
2810
+ const id = this.#nextId++;
2811
+ this.#items.push({
2812
+ id,
2813
+ content,
2814
+ status: "pending",
2815
+ deps: [...deps],
2816
+ updatedAt: Date.now()
2817
+ });
2818
+ return id;
2819
+ }
2820
+ /**
2821
+ * 把失败的 todo 重置回 pending(用于"重试"工作流)。
2822
+ *
2823
+ * 不允许把 done / skipped 重置(一旦完成不应反悔)。
2824
+ * 不允许把 running 重置(应该先 markFailed 再重试)。
2825
+ *
2826
+ * @param id — todo id
2827
+ * @returns 是否成功
2828
+ */
2829
+ resetForRetry(id) {
2830
+ const item = this.#find(id);
2831
+ if (!item) return false;
2832
+ if (item.status !== "failed") return false;
2833
+ item.status = "pending";
2834
+ item.evidence = void 0;
2835
+ item.updatedAt = Date.now();
2836
+ return true;
2837
+ }
2838
+ /**
2839
+ * 把 todo 标记为 running。
2840
+ *
2841
+ * 前提:该 todo 存在 + 当前是 pending + 依赖全 done。
2842
+ * 不满足时静默返回 false(不抛错,避免污染 agent 主循环)。
2843
+ *
2844
+ * @param id — todo id
2845
+ * @returns 是否成功转换
2846
+ */
2847
+ markRunning(id) {
2848
+ const item = this.#find(id);
2849
+ if (!item) return false;
2850
+ if (item.status !== "pending") return false;
2851
+ if (!this.#depsAllDone(item.deps)) return false;
2852
+ item.status = "running";
2853
+ item.updatedAt = Date.now();
2854
+ return true;
2855
+ }
2856
+ /**
2857
+ * 标记完成。
2858
+ *
2859
+ * @param id — todo id
2860
+ * @param evidence — 完成证据(如 "读取成功"、"type-check 通过")
2861
+ * @returns 是否成功
2862
+ */
2863
+ markDone(id, evidence) {
2864
+ const item = this.#find(id);
2865
+ if (!item) return false;
2866
+ if (item.status !== "running" && item.status !== "pending") return false;
2867
+ item.status = "done";
2868
+ if (evidence !== void 0) item.evidence = evidence;
2869
+ item.updatedAt = Date.now();
2870
+ return true;
2871
+ }
2872
+ /**
2873
+ * 标记失败。
2874
+ *
2875
+ * @param id — todo id
2876
+ * @param reason — 失败原因(人类可读)
2877
+ * @returns 是否成功
2878
+ */
2879
+ markFailed(id, reason) {
2880
+ const item = this.#find(id);
2881
+ if (!item) return false;
2882
+ if (item.status === "done") return false;
2883
+ item.status = "failed";
2884
+ item.evidence = reason;
2885
+ item.updatedAt = Date.now();
2886
+ return true;
2887
+ }
2888
+ /**
2889
+ * 标记跳过。
2890
+ *
2891
+ * @param id — todo id
2892
+ * @param reason — 跳过原因(如 "项目无测试基建")
2893
+ * @returns 是否成功
2894
+ */
2895
+ markSkipped(id, reason) {
2896
+ const item = this.#find(id);
2897
+ if (!item) return false;
2898
+ if (item.status === "done" || item.status === "failed") return false;
2899
+ item.status = "skipped";
2900
+ item.evidence = reason;
2901
+ item.updatedAt = Date.now();
2902
+ return true;
2903
+ }
2904
+ /**
2905
+ * 取出所有"立即可做"的 todo(deps 全 done 且 pending)。
2906
+ *
2907
+ * @returns 可执行的 todo 列表
2908
+ */
2909
+ pending() {
2910
+ return this.#items.filter(
2911
+ (it) => it.status === "pending" && this.#depsAllDone(it.deps)
2912
+ );
2913
+ }
2914
+ /**
2915
+ * 取出所有未完成项(pending / running / failed)。
2916
+ */
2917
+ unfinished() {
2918
+ return this.#items.filter(
2919
+ (it) => it.status === "pending" || it.status === "running" || it.status === "failed"
2920
+ );
2921
+ }
2922
+ /**
2923
+ * 是否全部结束(done / failed / skipped)。
2924
+ */
2925
+ isAllTerminated() {
2926
+ return this.#items.every(
2927
+ (it) => it.status === "done" || it.status === "failed" || it.status === "skipped"
2928
+ );
2929
+ }
2930
+ /** 全部条目(只读快照) */
2931
+ get items() {
2932
+ return this.#items;
2933
+ }
2934
+ /**
2935
+ * 把 todo 列表拼成 markdown,用于注入 system prompt。
2936
+ *
2937
+ * @param maxItems — 最多展示多少条;超出时保留"最近 N 条 + 进行中",折叠已完成
2938
+ * @returns markdown 字符串
2939
+ */
2940
+ toMarkdown(maxItems = 20) {
2941
+ if (this.#items.length === 0) return "";
2942
+ const active = this.#items.filter(
2943
+ (it) => it.status === "pending" || it.status === "running" || it.status === "failed"
2944
+ );
2945
+ const finished = this.#items.filter(
2946
+ (it) => it.status === "done" || it.status === "skipped"
2947
+ );
2948
+ const finishedSorted = [...finished].sort((a, b) => b.updatedAt - a.updatedAt);
2949
+ const truncated = finishedSorted.length > maxItems;
2950
+ const finishedToShow = truncated ? finishedSorted.slice(0, Math.max(0, maxItems - active.length)) : finishedSorted;
2951
+ const lines = [...active, ...finishedToShow].map((it) => {
2952
+ const box = this.#statusBox(it.status);
2953
+ const depStr = it.deps.length > 0 ? ` (\u4F9D\u8D56: #${it.deps.join(", #")})` : "";
2954
+ const eviStr = it.evidence ? ` \u2014 ${it.evidence}` : "";
2955
+ return `- ${box} #${it.id} ${it.content}${depStr}${eviStr}`;
2956
+ });
2957
+ if (truncated) {
2958
+ const hidden = finishedSorted.length - finishedToShow.length;
2959
+ lines.push(`- ...\uFF08\u53E6\u6709 ${hidden} \u6761\u5DF2\u5B8C\u6210\u5DF2\u6298\u53E0\uFF09`);
2960
+ }
2961
+ return [
2962
+ "## \u{1F4CB} \u5F53\u524D\u4EFB\u52A1\u8FDB\u5EA6",
2963
+ ...lines,
2964
+ "",
2965
+ "\u89C4\u5219\uFF1A\u6309\u987A\u5E8F\u63A8\u8FDB\uFF1B\u672A\u5B8C\u6210\u4F9D\u8D56\u65F6\u4E0D\u8981\u8DF3\u6B65\uFF1B\u5B8C\u6210\u8BF7\u8C03\u7528 todo_mark_done\uFF0C\u5931\u8D25\u8BF7\u8C03\u7528 todo_mark_failed\uFF1B\u91CD\u8BD5\u5931\u8D25\u9879\u5148\u8C03 todo_retry \u518D\u91CD\u8DD1\u3002"
2966
+ ].join("\n");
2967
+ }
2968
+ // -------------------------------------------------------------------------
2969
+ // 内部
2970
+ // -------------------------------------------------------------------------
2971
+ #find(id) {
2972
+ return this.#items.find((it) => it.id === id);
2973
+ }
2974
+ #depsAllDone(deps) {
2975
+ return deps.every((d) => {
2976
+ const item = this.#items.find((it) => it.id === d);
2977
+ return item?.status === "done" || item?.status === "skipped";
2978
+ });
2979
+ }
2980
+ #statusBox(status) {
2981
+ switch (status) {
2982
+ case "pending":
2983
+ return "\u2610";
2984
+ case "running":
2985
+ return "\u25B6";
2986
+ case "done":
2987
+ return "\u2705";
2988
+ case "failed":
2989
+ return "\u274C";
2990
+ case "skipped":
2991
+ return "\u23ED";
2992
+ }
2993
+ }
2994
+ };
2995
+
2996
+ // src/harness/tools.ts
2997
+ function createHarnessTools(todoList) {
2998
+ return [
2999
+ makeTodoAddTool(todoList),
3000
+ makeTodoMarkRunningTool(todoList),
3001
+ makeTodoMarkDoneTool(todoList),
3002
+ makeTodoMarkFailedTool(todoList),
3003
+ makeTodoRetryTool(todoList)
3004
+ ];
3005
+ }
3006
+ function makeTodoAddTool(todoList) {
3007
+ return {
3008
+ name: "todo_add",
3009
+ kind: "other" /* Other */,
3010
+ description: '\u26A0\uFE0F \u3010\u590D\u6742\u4EFB\u52A1\u7B2C\u4E00\u6B65\u3011\u6DFB\u52A0\u4E00\u4E2A\u5F85\u529E\u5230\u5F53\u524D\u4EFB\u52A1\u8FDB\u5EA6\u3002\n\n\u4EC0\u4E48\u60C5\u51B5\u4E0B\u8C03\u6211\uFF1A\n - \u9700\u8981\u591A\u6B65\u624D\u80FD\u5B8C\u6210\uFF08\u6539\u591A\u4E2A\u6587\u4EF6 / \u8DE8\u591A\u8F6E\uFF09\n - \u9700\u5148\u8BFB\u540E\u6539\uFF08edit/write \u4E4B\u524D\uFF09\n - \u4F9D\u8D56\u5916\u90E8\u72B6\u6001\uFF08\u6587\u4EF6\u3001API\u3001\u8FD0\u884C\u7ED3\u679C\uFF09\n\n\u4EC0\u4E48\u60C5\u51B5\u4E0B\u4E0D\u8981\u8C03\uFF1A\n - \u4E00\u53E5\u8BDD\u80FD\u5B8C\u6210\u7684\u4E8B\uFF08\u67E5\u3001\u5217\u3001\u95EE\uFF09\n\n\u7528\u6CD5\uFF1A\u6BCF\u6B21\u52A0\u4E00\u4E2A step\uFF0C\u591A\u6B65\u5C31\u8C03\u591A\u6B21\u3002\u6709\u4F9D\u8D56\u5173\u7CFB\u4F20 deps=[\u524D\u6B65 id]\u3002\u8FD4\u56DE\u5206\u914D\u7684 id\uFF0C\u540E\u7EED\u7528 todo_mark_done / todo_mark_failed \u5F15\u7528\u3002\n\n\u793A\u4F8B\uFF1A\u7528\u6237\u8BF4\u300C\u628A X \u6539\u6210 Y\u300D\u2192 \u8C03 1) todo_add("\u8BFB X") 2) todo_add("\u6539 X", deps=[0]) 3) todo_add("\u68C0\u67E5\u6539\u52A8", deps=[1])',
3011
+ parameters: {
3012
+ type: "object",
3013
+ properties: {
3014
+ content: { type: "string", description: "\u6B65\u9AA4\u63CF\u8FF0\uFF08\u4E2D\u6587\uFF0C\u5EFA\u8BAE\u7B80\u6D01\uFF09" },
3015
+ deps: {
3016
+ type: "array",
3017
+ description: "\u4F9D\u8D56\u7684 todo id \u6570\u7EC4\uFF08\u8FD9\u4E9B\u90FD done \u540E\u672C\u9879\u624D\u53EF\u6267\u884C\uFF09",
3018
+ items: { type: "number" }
3019
+ }
3020
+ },
3021
+ required: ["content"],
3022
+ additionalProperties: false
3023
+ },
3024
+ async execute(args) {
3025
+ if (typeof args?.content !== "string" || args.content.length === 0) {
3026
+ return { success: false, data: "\u7F3A\u5C11 content \u53C2\u6570", error: "INVALID_ARGS" };
3027
+ }
3028
+ const deps = Array.isArray(args.deps) ? args.deps.filter((d) => typeof d === "number") : [];
3029
+ try {
3030
+ const id = todoList.add(args.content, deps);
3031
+ return {
3032
+ success: true,
3033
+ data: `\u5DF2\u6DFB\u52A0 todo #${id}\uFF1A${args.content}${deps.length > 0 ? `\uFF08\u4F9D\u8D56: ${deps.join(", ")})` : ""}`,
3034
+ summary: `\u{1F4CB} \u6DFB\u52A0 #${id}`
3035
+ };
3036
+ } catch (err) {
3037
+ return {
3038
+ success: false,
3039
+ data: err instanceof Error ? err.message : String(err),
3040
+ error: "TODO_DEPS_INVALID"
3041
+ };
3042
+ }
3043
+ }
3044
+ };
3045
+ }
3046
+ function makeTodoMarkRunningTool(todoList) {
3047
+ return {
3048
+ name: "todo_mark_running",
3049
+ kind: "other" /* Other */,
3050
+ description: "\u628A todo \u4ECE pending \u63A8\u8FDB\u5230 running\uFF0C\u8868\u793A\u4F60\u300C\u6B63\u5728\u505A\u300D\u8FD9\u4E00\u6B65\u3002\n\u524D\u63D0\uFF1A\u8BE5 todo \u5B58\u5728\u3001\u5F53\u524D\u662F pending\u3001\u4E14\u6240\u6709 deps \u5DF2 done \u6216 skipped\u3002\n\u5DE5\u4F5C\u6D41\uFF1A\u5148\u8C03 todo_mark_running\uFF0C\u518D\u53BB\u8C03\u5B9E\u9645\u7684\u5DE5\u5177\uFF08read_file / edit_file \u7B49\uFF09\uFF0C\u6700\u540E\u7528 todo_mark_done \u6216 todo_mark_failed \u6536\u5C3E\u3002\n**\u4E0D\u8981\u8DF3\u8FC7 mark_running \u76F4\u63A5 mark_done** \u2014 \u4F1A\u7834\u574F\u4F9D\u8D56\u68C0\u67E5\uFF0C\u4E0B\u6E38\u6B65\u9AA4\u53EF\u80FD\u8BEF\u4EE5\u4E3A\u4F9D\u8D56\u5DF2\u6EE1\u8DB3\u3002",
3051
+ parameters: {
3052
+ type: "object",
3053
+ properties: {
3054
+ id: { type: "number", description: "todo \u7684 id" }
3055
+ },
3056
+ required: ["id"],
3057
+ additionalProperties: false
3058
+ },
3059
+ async execute(args) {
3060
+ if (typeof args?.id !== "number") {
3061
+ return { success: false, data: "\u7F3A\u5C11 id \u53C2\u6570", error: "INVALID_ARGS" };
3062
+ }
3063
+ const item = todoList.items.find((it) => it.id === args.id);
3064
+ if (!item) {
3065
+ return { success: false, data: `todo #${args.id} \u4E0D\u5B58\u5728`, error: "TODO_NOT_FOUND" };
3066
+ }
3067
+ if (item.status !== "pending") {
3068
+ return {
3069
+ success: false,
3070
+ data: `todo #${args.id} \u5F53\u524D\u72B6\u6001\u662F ${item.status}\uFF0C\u53EA\u6709 pending \u53EF\u4EE5\u63A8\u8FDB\u5230 running`,
3071
+ error: "TODO_INVALID_STATE"
3072
+ };
3073
+ }
3074
+ const ok = todoList.markRunning(args.id);
3075
+ if (!ok) {
3076
+ return {
3077
+ success: false,
3078
+ data: `todo #${args.id} \u4F9D\u8D56\u672A\u5168\u90E8\u5B8C\u6210\uFF08\u4F9D\u8D56: #${item.deps.join(", #")}\uFF09`,
3079
+ error: "TODO_DEPS_NOT_READY"
3080
+ };
3081
+ }
3082
+ return {
3083
+ success: true,
3084
+ data: `\u5DF2\u5F00\u59CB todo #${args.id}\uFF1A${item.content}`,
3085
+ summary: `\u25B6 \u5F00\u59CB #${args.id}`
3086
+ };
3087
+ }
3088
+ };
3089
+ }
3090
+ function makeTodoMarkDoneTool(todoList) {
3091
+ return {
3092
+ name: "todo_mark_done",
3093
+ kind: "other" /* Other */,
3094
+ description: "\u628A todo \u6807\u8BB0\u4E3A\u5B8C\u6210\u3002evidence \u586B\u300C\u5B8C\u6210\u8BC1\u636E\u300D\uFF08\u5982\u300C\u8BFB\u5230 120 \u884C\u300D\u3001\u300Ctypecheck \u901A\u8FC7\u300D\u3001\u300Cedit \u6210\u529F\u300D\uFF09\uFF0C\u4E0D\u586B\u4E5F\u884C\u3002\n\u524D\u63D0\uFF1A\u5F53\u524D\u72B6\u6001\u5FC5\u987B\u662F running\u3002\u82E5\u8FD8\u662F pending\uFF0C\u5148\u8C03 todo_mark_running\u3002",
3095
+ parameters: {
3096
+ type: "object",
3097
+ properties: {
3098
+ id: { type: "number", description: "todo \u7684 id" },
3099
+ evidence: { type: "string", description: "\u5B8C\u6210\u8BC1\u636E\uFF08\u4E00\u53E5\u8BDD\uFF0C\u53EF\u9009\uFF09" }
3100
+ },
3101
+ required: ["id"],
3102
+ additionalProperties: false
3103
+ },
3104
+ async execute(args) {
3105
+ if (typeof args?.id !== "number") {
3106
+ return { success: false, data: "\u7F3A\u5C11 id \u53C2\u6570", error: "INVALID_ARGS" };
3107
+ }
3108
+ const item = todoList.items.find((it) => it.id === args.id);
3109
+ if (!item) {
3110
+ return { success: false, data: `todo #${args.id} \u4E0D\u5B58\u5728`, error: "TODO_NOT_FOUND" };
3111
+ }
3112
+ if (item.status !== "running") {
3113
+ return {
3114
+ success: false,
3115
+ data: `todo #${args.id} \u5F53\u524D\u72B6\u6001\u662F ${item.status}\uFF0C\u53EA\u6709 running \u53EF\u4EE5 mark_done\uFF1B\u5982\u9700\u91CD\u8BD5\u8BF7\u5148 todo_retry`,
3116
+ error: "TODO_INVALID_STATE"
3117
+ };
3118
+ }
3119
+ const ok = todoList.markDone(args.id, args.evidence);
3120
+ if (!ok) {
3121
+ return { success: false, data: `todo #${args.id} \u72B6\u6001\u8F6C\u6362\u5931\u8D25`, error: "TODO_INVALID" };
3122
+ }
3123
+ return {
3124
+ success: true,
3125
+ data: `\u5DF2\u6807\u8BB0 todo #${args.id} \u5B8C\u6210${args.evidence ? `\uFF1A${args.evidence}` : ""}`,
3126
+ summary: `\u2705 \u5B8C\u6210 #${args.id}`
3127
+ };
3128
+ }
3129
+ };
3130
+ }
3131
+ function makeTodoMarkFailedTool(todoList) {
3132
+ return {
3133
+ name: "todo_mark_failed",
3134
+ kind: "other" /* Other */,
3135
+ description: "\u628A todo \u6807\u8BB0\u4E3A\u5931\u8D25\uFF0Creason \u586B\u300C\u5931\u8D25\u539F\u56E0\u300D\uFF08\u5982\u300Cold_text \u62FC\u9519\u300D\u3001\u300C\u6587\u4EF6\u4E0D\u5B58\u5728\u300D\uFF09\u3002\n\u524D\u63D0\uFF1A\u5F53\u524D\u72B6\u6001\u5FC5\u987B\u662F running\u3002\u82E5\u8FD8\u662F pending\uFF0C\u5148\u8C03 todo_mark_running\u3002\n\u8C03\u7528\u540E\u8BE5 step \u72B6\u6001\u53D8\u4E3A failed\u3002\u60F3\u91CD\u8BD5\uFF1A\u8C03 todo_retry(id) \u91CD\u7F6E\u56DE pending\uFF0C\u518D mark_running \u2192 \u8DD1\u5DE5\u5177 \u2192 mark_done\u3002",
3136
+ parameters: {
3137
+ type: "object",
3138
+ properties: {
3139
+ id: { type: "number", description: "todo \u7684 id" },
3140
+ reason: { type: "string", description: "\u5931\u8D25\u539F\u56E0\uFF08\u4E00\u53E5\u8BDD\uFF09" }
3141
+ },
3142
+ required: ["id", "reason"],
3143
+ additionalProperties: false
3144
+ },
3145
+ async execute(args) {
3146
+ if (typeof args?.id !== "number") {
3147
+ return { success: false, data: "\u7F3A\u5C11 id \u53C2\u6570", error: "INVALID_ARGS" };
3148
+ }
3149
+ if (typeof args?.reason !== "string") {
3150
+ return { success: false, data: "\u7F3A\u5C11 reason \u53C2\u6570", error: "INVALID_ARGS" };
3151
+ }
3152
+ const item = todoList.items.find((it) => it.id === args.id);
3153
+ if (!item) {
3154
+ return { success: false, data: `todo #${args.id} \u4E0D\u5B58\u5728`, error: "TODO_NOT_FOUND" };
3155
+ }
3156
+ if (item.status !== "running") {
3157
+ return {
3158
+ success: false,
3159
+ data: `todo #${args.id} \u5F53\u524D\u72B6\u6001\u662F ${item.status}\uFF0C\u53EA\u6709 running \u53EF\u4EE5 mark_failed`,
3160
+ error: "TODO_INVALID_STATE"
3161
+ };
3162
+ }
3163
+ const ok = todoList.markFailed(args.id, args.reason);
3164
+ if (!ok) {
3165
+ return { success: false, data: `todo #${args.id} \u72B6\u6001\u8F6C\u6362\u5931\u8D25`, error: "TODO_INVALID" };
3166
+ }
3167
+ return {
3168
+ success: true,
3169
+ data: `\u5DF2\u6807\u8BB0 todo #${args.id} \u5931\u8D25\uFF1A${args.reason}`,
3170
+ summary: `\u274C \u5931\u8D25 #${args.id}`
3171
+ };
3172
+ }
3173
+ };
3174
+ }
3175
+ function makeTodoRetryTool(todoList) {
3176
+ return {
3177
+ name: "todo_retry",
3178
+ kind: "other" /* Other */,
3179
+ description: "\u628A\u5931\u8D25\u7684 todo \u91CD\u7F6E\u56DE pending\uFF08\u6E05\u7A7A evidence\uFF09\uFF0C\u7136\u540E\u4F60\u53EF\u4EE5\u91CD\u65B0\u8D70 todo_mark_running \u2192 \u8DD1\u5DE5\u5177 \u2192 todo_mark_done \u7684\u6D41\u7A0B\u3002\n\u524D\u63D0\uFF1A\u5F53\u524D\u72B6\u6001\u5FC5\u987B\u662F failed\u3002done / skipped / running \u4E0D\u5141\u8BB8\u91CD\u8BD5\uFF08done \u4E0D\u53EF\u53CD\u6094\u3001running \u5E94\u5148 mark_failed\uFF09\u3002",
3180
+ parameters: {
3181
+ type: "object",
3182
+ properties: {
3183
+ id: { type: "number", description: "todo \u7684 id" }
3184
+ },
3185
+ required: ["id"],
3186
+ additionalProperties: false
3187
+ },
3188
+ async execute(args) {
3189
+ if (typeof args?.id !== "number") {
3190
+ return { success: false, data: "\u7F3A\u5C11 id \u53C2\u6570", error: "INVALID_ARGS" };
3191
+ }
3192
+ const item = todoList.items.find((it) => it.id === args.id);
3193
+ if (!item) {
3194
+ return { success: false, data: `todo #${args.id} \u4E0D\u5B58\u5728`, error: "TODO_NOT_FOUND" };
3195
+ }
3196
+ if (item.status !== "failed") {
3197
+ return {
3198
+ success: false,
3199
+ data: `todo #${args.id} \u5F53\u524D\u72B6\u6001\u662F ${item.status}\uFF0C\u53EA\u6709 failed \u53EF\u4EE5\u91CD\u8BD5`,
3200
+ error: "TODO_INVALID_STATE"
3201
+ };
3202
+ }
3203
+ const ok = todoList.resetForRetry(args.id);
3204
+ if (!ok) {
3205
+ return { success: false, data: `todo #${args.id} \u72B6\u6001\u8F6C\u6362\u5931\u8D25`, error: "TODO_INVALID" };
3206
+ }
3207
+ return {
3208
+ success: true,
3209
+ data: `\u5DF2\u91CD\u7F6E todo #${args.id} \u4E3A pending\uFF0C\u53EF\u91CD\u65B0\u8DD1`,
3210
+ summary: `\u{1F504} \u91CD\u8BD5 #${args.id}`
3211
+ };
3212
+ }
3213
+ };
3214
+ }
2116
3215
 
2117
3216
  // src/checkpoint/git-checkpoint.ts
2118
3217
  import { execFile } from "child_process";
@@ -2279,7 +3378,9 @@ function isENOENT(err) {
2279
3378
 
2280
3379
  // src/logger/logger.ts
2281
3380
  import { mkdir as mkdir5, appendFile } from "fs/promises";
2282
- import { join as join5, basename } from "path";
3381
+ import { fileURLToPath } from "url";
3382
+ import { join as join5, basename, sep } from "path";
3383
+ var LOGGER_DIR = "logger";
2283
3384
  var MAX_DATA_LEN = 2e3;
2284
3385
  var MAX_QUEUE = 500;
2285
3386
  function defaultLogsDir() {
@@ -2331,14 +3432,19 @@ var ConversationLogger = class _ConversationLogger {
2331
3432
  /**
2332
3433
  * 记录一个事件。
2333
3434
  *
2334
- * 内部将事件序列化为 JSON 并追加到日志文件。
3435
+ * 内部将事件序列化为 pretty JSON 并以多行块的形式追加到日志文件。
2335
3436
  * 写入操作串行排队,保证事件顺序。调用方无需 await。
2336
3437
  */
2337
3438
  log(event) {
2338
3439
  if (!this.#enabled || this.#closed) return;
2339
3440
  if (this.#queueLen >= MAX_QUEUE) return;
3441
+ const enriched = {
3442
+ ...event,
3443
+ time: event.time ?? formatTime(event.ts),
3444
+ loc: event.loc ?? captureCallerLocation()
3445
+ };
2340
3446
  this.#queueLen++;
2341
- this.#queue = this.#queue.then(() => this.#writeLine(event)).catch(() => {
3447
+ this.#queue = this.#queue.then(() => this.#writeBlock(enriched)).catch(() => {
2342
3448
  }).finally(() => this.#queueLen--);
2343
3449
  }
2344
3450
  /**
@@ -2353,12 +3459,12 @@ var ConversationLogger = class _ConversationLogger {
2353
3459
  });
2354
3460
  _ConversationLogger.#instances.delete(this);
2355
3461
  }
2356
- /** 将一行 JSON 写入日志文件 */
2357
- async #writeLine(event) {
3462
+ /** 将一条事件以多行块的形式写入日志文件 */
3463
+ async #writeBlock(event) {
2358
3464
  const dir = join5(this.#logPath, "..");
2359
3465
  await mkdir5(dir, { recursive: true });
2360
- const line = JSON.stringify(event) + "\n";
2361
- await appendFile(this.#logPath, line, "utf-8");
3466
+ const block = formatBlock(event);
3467
+ await appendFile(this.#logPath, block, "utf-8");
2362
3468
  }
2363
3469
  // -----------------------------------------------------------------------
2364
3470
  // 便捷方法 — 封装常见事件类型,减少调用方样板代码
@@ -2375,6 +3481,10 @@ var ConversationLogger = class _ConversationLogger {
2375
3481
  logAssistantText(content, round) {
2376
3482
  this.log({ ts: Date.now(), type: "assistant_text", content, round });
2377
3483
  }
3484
+ /** 记录思考链(thinking 模式下模型的 CoT) */
3485
+ logReasoning(content, round) {
3486
+ this.log({ ts: Date.now(), type: "reasoning", content, round });
3487
+ }
2378
3488
  /** 记录工具调用 */
2379
3489
  logToolCall(name, callId, args, round) {
2380
3490
  this.log({ ts: Date.now(), type: "tool_call", name, callId, arguments: args, round });
@@ -2418,28 +3528,127 @@ var ConversationLogger = class _ConversationLogger {
2418
3528
  logSessionEnd(elapsed) {
2419
3529
  this.log({ ts: Date.now(), type: "session_end", elapsed });
2420
3530
  }
3531
+ /** 记录反射(工具失败归因注入到下一轮 prompt 时) */
3532
+ logReflections(items) {
3533
+ this.log({ ts: Date.now(), type: "reflection", items });
3534
+ }
2421
3535
  };
2422
3536
  function truncate(text) {
2423
3537
  if (text.length <= MAX_DATA_LEN) return text;
2424
3538
  return text.slice(0, MAX_DATA_LEN) + `...[\u5DF2\u622A\u65AD\uFF0C\u539F\u59CB\u957F\u5EA6 ${text.length}]`;
2425
3539
  }
3540
+ function formatTime(ts) {
3541
+ const d = new Date(ts);
3542
+ const pad = (n, w = 2) => String(n).padStart(w, "0");
3543
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
3544
+ }
3545
+ var projectRootCache = null;
3546
+ function getProjectRoot() {
3547
+ if (projectRootCache !== null) return projectRootCache;
3548
+ try {
3549
+ const here = fileURLToPath(import.meta.url);
3550
+ const segments = here.split(sep);
3551
+ const srcIdx = segments.lastIndexOf("src");
3552
+ projectRootCache = srcIdx > 0 ? segments.slice(0, srcIdx).join(sep) : here;
3553
+ } catch {
3554
+ projectRootCache = "";
3555
+ }
3556
+ return projectRootCache;
3557
+ }
3558
+ var STACK_FRAME_RE = /\s+at\s+.+?\((.+):(\d+):\d+\)|\s+at\s+(.+):(\d+):\d+/;
3559
+ function captureCallerLocation() {
3560
+ let stack;
3561
+ try {
3562
+ const err = new Error();
3563
+ Error.captureStackTrace?.(err, captureCallerLocation);
3564
+ stack = err.stack;
3565
+ } catch {
3566
+ return { file: "<unknown>", line: 0 };
3567
+ }
3568
+ if (!stack) return { file: "<unknown>", line: 0 };
3569
+ const root = getProjectRoot();
3570
+ const lines = stack.split("\n");
3571
+ for (const raw of lines) {
3572
+ const m = STACK_FRAME_RE.exec(raw);
3573
+ if (!m) continue;
3574
+ const filePath = m[1] ?? m[3] ?? "";
3575
+ const lineNo = Number(m[2] ?? m[4] ?? "0");
3576
+ if (!filePath) continue;
3577
+ const normalized = filePath.replace(/^file:\/\/\//, "").replace(/^file:\/\//, "");
3578
+ if (normalized.includes(`${sep}${LOGGER_DIR}${sep}`) || normalized.endsWith(`${sep}${LOGGER_DIR}`)) {
3579
+ continue;
3580
+ }
3581
+ if (normalized.includes(`${sep}node_modules${sep}`)) continue;
3582
+ let rel = normalized;
3583
+ if (root && normalized.startsWith(root)) {
3584
+ rel = normalized.slice(root.length).replace(/^[/\\]/, "") || basename(normalized);
3585
+ }
3586
+ return { file: rel || basename(normalized), line: lineNo };
3587
+ }
3588
+ return { file: "<unknown>", line: 0 };
3589
+ }
3590
+ var SEPARATOR_WIDTH = 80;
3591
+ var SEPARATOR = "\u2500".repeat(SEPARATOR_WIDTH);
3592
+ function formatBlock(event) {
3593
+ const header = `[${event.time}] [${event.type}] @ ${event.loc.file}:${event.loc.line}`;
3594
+ const body = JSON.stringify(event, null, 2);
3595
+ return `${SEPARATOR}
3596
+ ${header}
3597
+ ${body}
3598
+
3599
+ `;
3600
+ }
2426
3601
 
2427
3602
  // src/agent/index.ts
2428
3603
  var Session = class _Session {
3604
+ /** 当前会话的消息历史(含 system/user/assistant/tool),可外部只读 */
2429
3605
  #messages = [];
3606
+ /** LLM Provider(DeepSeek 等) */
2430
3607
  #provider;
3608
+ /** 工具注册表(所有可用工具) */
2431
3609
  #toolRegistry;
3610
+ /** 成本追踪器(今日 + 本会话) */
2432
3611
  #costTracker;
3612
+ /** 归一化后的构造选项(带默认值) */
2433
3613
  #options;
3614
+ /** 中止信号控制器(abort() 时触发,传递给 LLM 和工具) */
2434
3615
  #abortController = new AbortController();
3616
+ /** 会话唯一 ID(UUID) */
2435
3617
  #sessionId;
3618
+ /** 持久化存储;传 false 时为 null */
2436
3619
  #store;
3620
+ /** 会话创建时间(毫秒) */
2437
3621
  #createdAt;
3622
+ /** 节流持久化定时器(500ms debounce) */
2438
3623
  #persistTimer = null;
3624
+ /** user 消息 → Checkpoint 映射(仅给 user 消息建点) */
2439
3625
  #checkpoints = /* @__PURE__ */ new Map();
3626
+ /** 最近的失败记录(仅失败的,用于风暴检测) */
2440
3627
  #stormRecords = [];
3628
+ /** 当前会话模式:code(默认)/ plan(只读) */
2441
3629
  #mode = "code";
3630
+ /** 对话日志记录器(写入 .dskcode/logs/) */
2442
3631
  #logger;
3632
+ /** 风暴检测器(连续 3 次同工具同错码失败时中断本轮) */
3633
+ #stormDetector;
3634
+ /** 失败归因 Reflector(将本轮失败原因拼到下一轮 prompt;null 表示已关闭) */
3635
+ #reflector;
3636
+ /** 本轮工具执行结果(仅在 chat() 循环内使用;用于下一轮注入 reflection) */
3637
+ #lastRoundResults = null;
3638
+ /** Harness 任务列表(仅在 enableHarness 时非 null) */
3639
+ #todoList;
3640
+ /** 本会话内是否已发出过「未拆 todo」护栏提示(避免每轮骚扰) */
3641
+ #harnessHintEmitted = false;
3642
+ /**
3643
+ * 构造一个 Session。
3644
+ *
3645
+ * @param provider — LLM Provider 实例(必须)
3646
+ * @param tools — 工具列表或已初始化的 ToolRegistry(默认空)
3647
+ * @param costTracker — 成本追踪器(默认新建)
3648
+ * @param options — 会话选项(详见 SessionOptions)
3649
+ *
3650
+ * @sideEffect 创建 .dskcode/logs/ 下的日志文件并写入 session_start 事件
3651
+ */
2443
3652
  constructor(provider, tools = [], costTracker, options) {
2444
3653
  this.#provider = provider;
2445
3654
  if (tools instanceof ToolRegistry) {
@@ -2458,7 +3667,9 @@ var Session = class _Session {
2458
3667
  gate: options?.gate ?? new AlwaysAllowGate(),
2459
3668
  writeRoots: options?.writeRoots ?? [options?.cwd ?? process.cwd()],
2460
3669
  enableCheckpoint: options?.enableCheckpoint ?? true,
2461
- enableLog: options?.enableLog ?? true
3670
+ enableLog: options?.enableLog ?? true,
3671
+ enableReflection: options?.enableReflection !== false,
3672
+ enableHarness: options?.enableHarness !== false
2462
3673
  };
2463
3674
  this.#sessionId = options?.sessionId ?? SessionStore.newId();
2464
3675
  this.#store = options?.store === false ? null : options?.store ?? new SessionStore();
@@ -2466,39 +3677,88 @@ var Session = class _Session {
2466
3677
  this.#logger = new ConversationLogger(this.#sessionId, this.#options.cwd, {
2467
3678
  enabled: this.#options.enableLog
2468
3679
  });
2469
- this.#logger.logSessionStart(this.#sessionId, this.#options.cwd, this.#provider.model(), this.#mode);
3680
+ this.#logger.logSessionStart(
3681
+ this.#sessionId,
3682
+ this.#options.cwd,
3683
+ this.#provider.model(),
3684
+ this.#mode
3685
+ );
3686
+ this.#stormDetector = new StormDetector({ threshold: 3 });
3687
+ this.#reflector = this.#options.enableReflection ? new Reflector() : null;
3688
+ if (this.#options.enableHarness) {
3689
+ const todoList = new TodoList();
3690
+ this.#todoList = todoList;
3691
+ for (const t of createHarnessTools(todoList)) {
3692
+ this.#toolRegistry.registerErased(eraseTool(t));
3693
+ }
3694
+ } else {
3695
+ this.#todoList = null;
3696
+ }
2470
3697
  }
3698
+ /** 当前会话的完整消息历史(只读视图) */
2471
3699
  get messages() {
2472
3700
  return this.#messages;
2473
3701
  }
3702
+ /** 本次会话累计成本(人民币元) */
2474
3703
  get accumulatedCost() {
2475
3704
  return this.#costTracker.sessionTotalCost;
2476
3705
  }
3706
+ /** 成本追踪器(含今日总成本、会话成本、模型维度统计) */
2477
3707
  get costTracker() {
2478
3708
  return this.#costTracker;
2479
3709
  }
3710
+ /** 当前模型标识(如 "deepseek-v4-flash") */
2480
3711
  get model() {
2481
3712
  return this.#provider.model();
2482
3713
  }
3714
+ /** 工具注册表(可外部读 / 运行时 register) */
2483
3715
  get toolRegistry() {
2484
3716
  return this.#toolRegistry;
2485
3717
  }
3718
+ /** 当前模式:"code" | "plan" */
2486
3719
  get mode() {
2487
3720
  return this.#mode;
2488
3721
  }
3722
+ /** 会话 ID(UUID) */
2489
3723
  get id() {
2490
3724
  return this.#sessionId;
2491
3725
  }
3726
+ /** 持久化存储实例(禁用持久化时为 null) */
2492
3727
  get store() {
2493
3728
  return this.#store;
2494
3729
  }
3730
+ /** 会话创建时间戳(毫秒) */
2495
3731
  get createdAt() {
2496
3732
  return this.#createdAt;
2497
3733
  }
3734
+ /**
3735
+ * 切换会话模式。
3736
+ *
3737
+ * @param mode — "code":全部工具;"plan":只暴露只读工具
3738
+ * @returns 设置后的模式(便于链式调用)
3739
+ */
2498
3740
  setMode(mode) {
2499
3741
  this.#mode = mode;
2500
3742
  return this.#mode;
2501
3743
  }
3744
+ /**
3745
+ * chat() — 接收一轮用户输入,串起 LLM 调用 / 工具执行 / 风暴中断 / 持久化。
3746
+ *
3747
+ * 主循环每轮做:
3748
+ * 1. 构建 system prompt + 裁剪消息 + 拼装 apiMessages
3749
+ * 2. 调 provider.chat() 拿流式 chunks,向外 yield text_delta / usage 事件
3750
+ * 3. 若本轮有 tool_calls:
3751
+ * a. 先调 StormDetector.shouldBreak 判定是否中断风暴
3752
+ * b. 调 ToolExecutor.executeBatch 执行工具
3753
+ * c. 把工具结果 yield 出去并写入 messages
3754
+ * 4. 持续到 LLM 不再调工具 或 达到 maxToolRounds
3755
+ *
3756
+ * @param userInput — 用户本轮输入
3757
+ * @param opts — 透传给 provider.chat() 的 ChatOptions(thinking / tool_choice 等)
3758
+ * @yields AgentEvent — text_delta / tool_calls / tool_result / usage / done / error
3759
+ *
3760
+ * @sideEffect 写入 messages、logger、costTracker、可能触发 checkpoint / persist
3761
+ */
2502
3762
  async *chat(userInput, opts) {
2503
3763
  this.#messages.push({ role: "user", content: userInput });
2504
3764
  this.#logger.logUserMessage(userInput);
@@ -2512,20 +3772,36 @@ var Session = class _Session {
2512
3772
  }
2513
3773
  const startTime = Date.now();
2514
3774
  let toolRounds = 0;
3775
+ const toolExecutor = this.#buildToolExecutor();
3776
+ this.#harnessHintEmitted = false;
2515
3777
  try {
2516
3778
  while (toolRounds < this.#options.maxToolRounds) {
2517
- const systemPrompt = this.#buildSystemPrompt();
2518
- const [trimmed] = trimMessages(
2519
- [...this.#messages],
2520
- {
2521
- model: this.#provider.model(),
2522
- reservedForOutput: this.#options.reservedForOutput,
2523
- systemPrompt,
2524
- preserveRecentRounds: this.#options.preserveRecentRounds
3779
+ let systemPrompt = this.#buildSystemPrompt();
3780
+ if (this.#reflector && this.#lastRoundResults) {
3781
+ const reflections = this.#reflector.analyze(this.#lastRoundResults, {
3782
+ writeRoots: this.#options.writeRoots,
3783
+ cwd: this.#options.cwd
3784
+ });
3785
+ if (reflections.length > 0) {
3786
+ systemPrompt = this.#reflector.injectIntoPrompt(systemPrompt, reflections);
3787
+ this.#logger.logReflections(
3788
+ reflections.map((r) => ({
3789
+ category: r.category,
3790
+ toolName: r.toolName,
3791
+ hint: r.hint
3792
+ }))
3793
+ );
2525
3794
  }
2526
- );
3795
+ this.#lastRoundResults = null;
3796
+ }
3797
+ const [trimmed] = trimMessages([...this.#messages], {
3798
+ model: this.#provider.model(),
3799
+ reservedForOutput: this.#options.reservedForOutput,
3800
+ systemPrompt,
3801
+ preserveRecentRounds: this.#options.preserveRecentRounds
3802
+ });
2527
3803
  const apiMessages = buildApiMessages(systemPrompt, trimmed);
2528
- const toolDefs = this.#buildToolDefinitions();
3804
+ const toolDefs = buildToolDefinitions(this.#toolRegistry, this.#mode);
2529
3805
  const stream = this.#provider.chat(apiMessages, {
2530
3806
  signal: this.#abortController.signal,
2531
3807
  tools: toolDefs.length > 0 ? toolDefs : void 0,
@@ -2534,21 +3810,75 @@ var Session = class _Session {
2534
3810
  responseFormat: opts?.responseFormat,
2535
3811
  toolChoice: opts?.toolChoice
2536
3812
  });
3813
+ const modelId = this.#provider.model();
2537
3814
  let accumulatedText = "";
3815
+ let accumulatedReasoning = "";
2538
3816
  let lastUsage;
2539
3817
  let lastToolCalls;
2540
3818
  let _lastFinishReason = null;
3819
+ let estimatedInputTokens = 0;
3820
+ for (const m of apiMessages) {
3821
+ estimatedInputTokens += this.#provider.countTokens(m.content) + 10;
3822
+ }
3823
+ let lastEmitMs = 0;
3824
+ const EST_EMIT_INTERVAL_MS = 300;
2541
3825
  for await (const chunk of stream) {
3826
+ if (chunk.reasoningContent) {
3827
+ accumulatedReasoning += chunk.reasoningContent;
3828
+ yield { type: "reasoning_delta", content: chunk.reasoningContent };
3829
+ }
2542
3830
  if (chunk.content) {
2543
3831
  accumulatedText += chunk.content;
2544
3832
  yield { type: "text_delta", content: chunk.content };
3833
+ const now = Date.now();
3834
+ if (now - lastEmitMs >= EST_EMIT_INTERVAL_MS) {
3835
+ lastEmitMs = now;
3836
+ const estOutput = this.#provider.countTokens(accumulatedText);
3837
+ const estUsage = {
3838
+ promptTokens: estimatedInputTokens,
3839
+ completionTokens: estOutput
3840
+ };
3841
+ const estCost = calculateCost(estUsage, modelId);
3842
+ yield {
3843
+ type: "usage",
3844
+ usage: estUsage,
3845
+ model: modelId,
3846
+ cost: estCost.totalCost,
3847
+ estimated: true
3848
+ };
3849
+ }
3850
+ }
3851
+ if (chunk.toolCalls && chunk.toolCalls.length > 0)
3852
+ lastToolCalls = chunk.toolCalls;
3853
+ if (chunk.usage) {
3854
+ lastUsage = chunk.usage;
3855
+ const realCost = calculateCost(chunk.usage, modelId);
3856
+ yield {
3857
+ type: "usage",
3858
+ usage: chunk.usage,
3859
+ model: modelId,
3860
+ cost: realCost.totalCost,
3861
+ estimated: false
3862
+ };
2545
3863
  }
2546
- if (chunk.toolCalls && chunk.toolCalls.length > 0) lastToolCalls = chunk.toolCalls;
2547
- if (chunk.usage) lastUsage = chunk.usage;
2548
3864
  if (chunk.finishReason) _lastFinishReason = chunk.finishReason;
2549
3865
  }
3866
+ if (accumulatedText && !lastUsage) {
3867
+ const estOutput = this.#provider.countTokens(accumulatedText);
3868
+ const estUsage = {
3869
+ promptTokens: estimatedInputTokens,
3870
+ completionTokens: estOutput
3871
+ };
3872
+ const estCost = calculateCost(estUsage, modelId);
3873
+ yield {
3874
+ type: "usage",
3875
+ usage: estUsage,
3876
+ model: modelId,
3877
+ cost: estCost.totalCost,
3878
+ estimated: true
3879
+ };
3880
+ }
2550
3881
  if (lastUsage) {
2551
- const modelId = this.#provider.model();
2552
3882
  this.#costTracker.record(lastUsage, modelId);
2553
3883
  const cost = calculateCost(lastUsage, modelId);
2554
3884
  this.#logger.logUsage(
@@ -2562,15 +3892,22 @@ var Session = class _Session {
2562
3892
  yield { type: "usage", usage: lastUsage, model: modelId };
2563
3893
  }
2564
3894
  const assistantMsg = { role: "assistant", content: accumulatedText };
2565
- if (lastToolCalls && lastToolCalls.length > 0) assistantMsg.toolCalls = lastToolCalls;
3895
+ if (lastToolCalls && lastToolCalls.length > 0)
3896
+ assistantMsg.toolCalls = lastToolCalls;
2566
3897
  this.#messages.push(assistantMsg);
2567
3898
  this.#logger.logAssistantText(accumulatedText, toolRounds);
3899
+ if (accumulatedReasoning) {
3900
+ this.#logger.logReasoning(accumulatedReasoning, toolRounds);
3901
+ }
2568
3902
  if (lastToolCalls && lastToolCalls.length > 0) {
2569
3903
  yield { type: "tool_calls", calls: lastToolCalls };
2570
3904
  for (const tc of lastToolCalls) {
2571
3905
  this.#logger.logToolCall(tc.name, tc.id, tc.arguments, toolRounds);
2572
3906
  }
2573
- const stormBroken = this.#checkStormBreak(lastToolCalls);
3907
+ const stormBroken = this.#stormDetector.shouldBreak(
3908
+ this.#stormRecords,
3909
+ lastToolCalls
3910
+ );
2574
3911
  if (stormBroken) {
2575
3912
  const stormMsg = "\n\u26A0\uFE0F \u540C\u4E00\u5DE5\u5177\u91CD\u590D\u51FA\u9519\uFF0C\u5DF2\u5F3A\u5236\u5207\u6362\u7B56\u7565\n";
2576
3913
  yield { type: "text_delta", content: stormMsg };
@@ -2580,10 +3917,25 @@ var Session = class _Session {
2580
3917
  toolRounds++;
2581
3918
  continue;
2582
3919
  }
2583
- const results = await this.#executeBatch(lastToolCalls);
3920
+ const results = await toolExecutor.executeBatch(lastToolCalls);
2584
3921
  this.#stormRecords = results.records;
3922
+ this.#lastRoundResults = results.items.map((it) => {
3923
+ const tool = this.#toolRegistry.get(it.name);
3924
+ return {
3925
+ name: it.name,
3926
+ result: it.result,
3927
+ kind: tool?.kind ?? "other" /* Other */,
3928
+ recentSameTool: results.records.filter((r) => r.name === it.name).map((r) => ({ success: r.success, error: r.error }))
3929
+ };
3930
+ });
2585
3931
  for (const item of results.items) {
2586
- yield { type: "tool_result", name: item.name, result: item.result };
3932
+ const todoSnapshot = item.name.startsWith("todo_") && this.#todoList ? this.#todoList.items : void 0;
3933
+ yield {
3934
+ type: "tool_result",
3935
+ name: item.name,
3936
+ result: item.result,
3937
+ todoSnapshot
3938
+ };
2587
3939
  this.#logger.logToolResult(
2588
3940
  item.name,
2589
3941
  item.callId,
@@ -2594,7 +3946,8 @@ var Session = class _Session {
2594
3946
  toolRounds
2595
3947
  );
2596
3948
  let toolContent = item.result.data;
2597
- if (item.result.diff && item.result.diff.patch) toolContent += `
3949
+ if (item.result.diff && item.result.diff.patch)
3950
+ toolContent += `
2598
3951
 
2599
3952
  ${item.result.diff.patch}`;
2600
3953
  this.#messages.push({
@@ -2604,9 +3957,16 @@ ${item.result.diff.patch}`;
2604
3957
  name: item.name
2605
3958
  });
2606
3959
  }
3960
+ this.#maybeEmitHarnessHint(results.items);
2607
3961
  toolRounds++;
2608
3962
  continue;
2609
3963
  }
3964
+ if (this.#todoList && this.#todoList.items.length > 0 && this.#todoList.isAllTerminated()) {
3965
+ this.#messages.push({
3966
+ role: "user",
3967
+ content: "[\u7CFB\u7EDF] \u5168\u90E8 todo \u5DF2\u5B8C\u6210\u3002\u8BF7\u505A\u4E00\u6B21\u4EFB\u52A1\u81EA\u68C0\uFF1A\u91CD\u65B0\u8BFB\u53D6\u672C\u6B21\u4FEE\u6539\u8FC7\u7684\u6587\u4EF6\uFF0C\u786E\u8BA4\u6539\u52A8\u6B63\u786E\u65E0\u8BEF\u3001\u7C7B\u578B\u517C\u5BB9\u3001\u6CA1\u6709\u9057\u6F0F\u3002\u5982\u6709\u95EE\u9898\u8BF7\u65B0\u589E todo \u4FEE\u590D\uFF1B\u786E\u8BA4\u65E0\u8BEF\u540E\u8F93\u51FA\u6700\u7EC8\u603B\u7ED3\u3002"
3968
+ });
3969
+ }
2610
3970
  break;
2611
3971
  }
2612
3972
  } catch (err) {
@@ -2626,98 +3986,15 @@ ${item.result.diff.patch}`;
2626
3986
  });
2627
3987
  yield { type: "done", elapsed };
2628
3988
  }
2629
- async #executeBatch(calls) {
2630
- const toolCtx = {
2631
- cwd: this.#options.cwd,
2632
- signal: this.#abortController.signal,
2633
- writeRoots: this.#options.writeRoots
2634
- };
2635
- const allReadOnly = calls.every((tc) => {
2636
- const tool = this.#toolRegistry.get(tc.name);
2637
- return tool ? isReadOnly(tool.kind) : true;
2638
- });
2639
- if (allReadOnly && calls.length > 1) {
2640
- const MAX_PARALLEL = 8;
2641
- const items2 = [];
2642
- const records2 = [];
2643
- for (let i = 0; i < calls.length; i += MAX_PARALLEL) {
2644
- const batch = calls.slice(i, i + MAX_PARALLEL);
2645
- const promises = batch.map((tc) => this.#executeOne(tc, toolCtx));
2646
- const batchResults = await Promise.all(promises);
2647
- for (const r of batchResults) {
2648
- items2.push(r.item);
2649
- records2.push(r.record);
2650
- }
2651
- }
2652
- return { items: items2, records: records2 };
2653
- }
2654
- const items = [];
2655
- const records = [];
2656
- for (const tc of calls) {
2657
- const r = await this.#executeOne(tc, toolCtx);
2658
- items.push(r.item);
2659
- records.push(r.record);
2660
- }
2661
- return { items, records };
2662
- }
2663
- async #executeOne(tc, ctx) {
2664
- const toolName = tc.name;
2665
- const timestamp = Date.now();
2666
- const tool = this.#toolRegistry.get(toolName);
2667
- if (!tool) {
2668
- const errMsg = `\u5DE5\u5177 "${toolName}" \u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u7981\u7528`;
2669
- return {
2670
- item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "TOOL_NOT_FOUND" } },
2671
- record: { name: toolName, success: false, error: "TOOL_NOT_FOUND", timestamp }
2672
- };
2673
- }
2674
- let toolArgs;
2675
- try {
2676
- toolArgs = tc.arguments ? JSON.parse(tc.arguments) : {};
2677
- } catch {
2678
- toolArgs = {};
2679
- }
2680
- const gateResult = await this.#options.gate.check(toolName, toolArgs);
2681
- if (!gateResult) {
2682
- const errMsg = `\u5DE5\u5177 "${toolName}" \u88AB\u6743\u9650\u95E8\u62D2\u7EDD`;
2683
- return {
2684
- item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "GATE_DENIED" } },
2685
- record: { name: toolName, success: false, error: "GATE_DENIED", timestamp }
2686
- };
2687
- }
2688
- if (!isReadOnly(tool.kind)) {
2689
- const maybePreview = tool.preview;
2690
- if (typeof maybePreview === "function") {
2691
- try {
2692
- await maybePreview(toolArgs, ctx);
2693
- } catch {
2694
- }
2695
- }
2696
- }
2697
- try {
2698
- const result = await tool.execute(toolArgs, ctx);
2699
- return {
2700
- item: { name: toolName, callId: tc.id, result },
2701
- record: { name: toolName, success: result.success, error: result.error, timestamp }
2702
- };
2703
- } catch (err) {
2704
- const message = err instanceof Error ? err.message : String(err);
2705
- const errorResult = { success: false, data: `\u5DE5\u5177 "${toolName}" \u6267\u884C\u5F02\u5E38\uFF1A${message}`, error: "EXECUTION_ERROR" };
2706
- return {
2707
- item: { name: toolName, callId: tc.id, result: errorResult },
2708
- record: { name: toolName, success: false, error: "EXECUTION_ERROR", timestamp }
2709
- };
2710
- }
2711
- }
2712
- #checkStormBreak(currentCalls) {
2713
- if (this.#stormRecords.length < 3) return false;
2714
- const recentErrors = this.#stormRecords.slice(-3);
2715
- if (recentErrors.length < 3) return false;
2716
- const first = recentErrors[0];
2717
- const allSame = recentErrors.every((r) => r.name === first.name && r.error === first.error && !r.success);
2718
- if (!allSame) return false;
2719
- return currentCalls.some((tc) => tc.name === first.name);
2720
- }
3989
+ // -------------------------------------------------------------------------
3990
+ // 持久化与恢复
3991
+ // -------------------------------------------------------------------------
3992
+ /**
3993
+ * 中止当前会话:触发 AbortController,停止流式 LLM 和正在执行的工具。
3994
+ * 同时清掉节流持久化定时器。
3995
+ *
3996
+ * @sideEffect abort 当前 chat 循环、取消所有 #abortController 监听者
3997
+ */
2721
3998
  abort() {
2722
3999
  this.#abortController.abort();
2723
4000
  if (this.#persistTimer) {
@@ -2725,19 +4002,34 @@ ${item.result.diff.patch}`;
2725
4002
  this.#persistTimer = null;
2726
4003
  }
2727
4004
  }
2728
- /** 刷新日志缓冲,确保所有已记录的事件落盘 */
4005
+ /**
4006
+ * 刷新日志缓冲,确保所有已记录的事件落盘。
4007
+ * 通常在进程退出前调用,避免日志丢失。
4008
+ *
4009
+ * @sideEffect 写入 .dskcode/logs/ 下的日志文件
4010
+ */
2729
4011
  async flushLog() {
2730
4012
  await this.#logger.flush();
2731
4013
  }
4014
+ /**
4015
+ * 清空消息历史、会话成本、风暴记录、checkpoints。
4016
+ * 注意:不会清掉磁盘上的会话记录(用 delete() 删除),仅重置内存状态。
4017
+ *
4018
+ * @sideEffect 抹掉 messages / costTracker / stormRecords / checkpoints
4019
+ */
2732
4020
  reset() {
2733
4021
  this.#messages.length = 0;
2734
4022
  this.#costTracker.resetSession();
2735
4023
  this.#stormRecords = [];
2736
4024
  this.#checkpoints.clear();
4025
+ this.#lastRoundResults = null;
2737
4026
  }
2738
- // -------------------------------------------------------------------------
2739
- // 持久化与恢复
2740
- // -------------------------------------------------------------------------
4027
+ /**
4028
+ * 立即把当前状态写入持久化(不等 500ms debounce)。
4029
+ * 用于 UI 上"立即保存"按钮或异常退出前的最后兜底。
4030
+ *
4031
+ * @sideEffect 调用 #doPersist(),写入磁盘
4032
+ */
2741
4033
  async persistNow() {
2742
4034
  if (this.#persistTimer) {
2743
4035
  clearTimeout(this.#persistTimer);
@@ -2745,6 +4037,12 @@ ${item.result.diff.patch}`;
2745
4037
  }
2746
4038
  await this.#doPersist();
2747
4039
  }
4040
+ /**
4041
+ * 节流持久化:500ms 内多次调用只会真正落盘一次。
4042
+ * 通过 setTimeout + timer.refresh() 实现 debounce。
4043
+ *
4044
+ * @sideEffect 调度一个 500ms 后的 #doPersist() 任务
4045
+ */
2748
4046
  #persist() {
2749
4047
  if (!this.#store) return;
2750
4048
  if (this.#persistTimer) {
@@ -2757,6 +4055,12 @@ ${item.result.diff.patch}`;
2757
4055
  }, 500);
2758
4056
  this.#persistTimer.unref();
2759
4057
  }
4058
+ /**
4059
+ * 真正执行持久化:构造 StoredSession 并写入 store。
4060
+ * 失败仅 console.error,不抛给调用方(持久化失败不应阻塞对话)。
4061
+ *
4062
+ * @sideEffect 写磁盘 ~/.dskcode/sessions/<id>.json
4063
+ */
2760
4064
  async #doPersist() {
2761
4065
  if (!this.#store) return;
2762
4066
  const stored = {
@@ -2775,12 +4079,24 @@ ${item.result.diff.patch}`;
2775
4079
  console.error("[Session] \u6301\u4E45\u5316\u5931\u8D25:", err);
2776
4080
  }
2777
4081
  }
4082
+ /**
4083
+ * 从消息历史推导会话标题:取第一条非空 user 消息的前 40 字。
4084
+ * 无任何 user 消息时返回 "新会话"。
4085
+ *
4086
+ * @pure 不修改任何状态
4087
+ */
2778
4088
  #deriveTitle() {
2779
4089
  for (const m of this.#messages) {
2780
4090
  if (m.role === "user" && m.content.trim()) return m.content.trim().slice(0, 40);
2781
4091
  }
2782
4092
  return "\u65B0\u4F1A\u8BDD";
2783
4093
  }
4094
+ /**
4095
+ * 把内存 messages 转成可持久化的 StoredSession["messages"]。
4096
+ * 给 user 消息挂上对应 checkpoint(若存在),用于 rewind 时文件回退。
4097
+ *
4098
+ * @pure 不修改任何状态
4099
+ */
2784
4100
  #serializeMessages() {
2785
4101
  return this.#messages.map((msg, idx) => {
2786
4102
  const checkpoint = this.#checkpoints.get(idx);
@@ -2788,12 +4104,29 @@ ${item.result.diff.patch}`;
2788
4104
  return { ...msg };
2789
4105
  });
2790
4106
  }
4107
+ /**
4108
+ * 静态方法:从磁盘恢复一个之前保存的 Session。
4109
+ *
4110
+ * @param id — 之前 SessionStore 保存的会话 ID
4111
+ * @param provider — LLM Provider(必须)
4112
+ * @param tools — 工具列表/Registry(必须重新提供,因为工具不持久化)
4113
+ * @param costTracker — 成本追踪器(可选)
4114
+ * @param options — 会话选项(sessionId / store 必须与持久化 ID 对应)
4115
+ * @returns 恢复后的 Session 实例
4116
+ * @throws 持久化被禁用时、ID 不存在时抛错
4117
+ *
4118
+ * @sideEffect 从磁盘读 messages + checkpoints
4119
+ */
2791
4120
  static async resume(id, provider, tools = [], costTracker, options) {
2792
4121
  const store = options?.store === false ? null : options?.store ?? new SessionStore();
2793
4122
  if (!store) throw new Error("resume \u9700\u8981\u542F\u7528\u6301\u4E45\u5316\uFF08options.store \u4E0D\u80FD\u4E3A false\uFF09");
2794
4123
  const stored = await store.load(id);
2795
4124
  if (!stored) throw new Error(`\u4F1A\u8BDD ${id} \u4E0D\u5B58\u5728`);
2796
- const session = new _Session(provider, tools, costTracker, { ...options, sessionId: id, store });
4125
+ const session = new _Session(provider, tools, costTracker, {
4126
+ ...options,
4127
+ sessionId: id,
4128
+ store
4129
+ });
2797
4130
  for (const m of stored.messages) {
2798
4131
  session.#messages.push({
2799
4132
  role: m.role,
@@ -2813,15 +4146,46 @@ ${item.result.diff.patch}`;
2813
4146
  // -------------------------------------------------------------------------
2814
4147
  // 检查点与 Rewind
2815
4148
  // -------------------------------------------------------------------------
4149
+ /**
4150
+ * 列出所有 user 消息对应的 checkpoint(按 index 升序)。
4151
+ * 用于 UI 展示 /rewind 列表。
4152
+ *
4153
+ * @returns 每个元素的 index = messages 数组索引,可直接传给 rewind()
4154
+ * @pure 不修改任何状态
4155
+ */
2816
4156
  listCheckpoints() {
2817
4157
  const result = [];
2818
4158
  for (const [index, checkpoint] of this.#checkpoints) {
2819
4159
  const msg = this.#messages[index];
2820
4160
  if (!msg || msg.role !== "user") continue;
2821
- result.push({ index, preview: msg.content.slice(0, 80), timestamp: checkpoint.timestamp, isGitRepo: checkpoint.isGitRepo });
4161
+ result.push({
4162
+ index,
4163
+ preview: msg.content.slice(0, 80),
4164
+ timestamp: checkpoint.timestamp,
4165
+ isGitRepo: checkpoint.isGitRepo
4166
+ });
2822
4167
  }
2823
4168
  return result.sort((a, b) => a.index - b.index);
2824
4169
  }
4170
+ /**
4171
+ * rewind — 把消息历史截断到 targetIndex 对应的 user 消息,
4172
+ * 并尝试把文件工作区也回退到那一刻。
4173
+ *
4174
+ * @param targetIndex — 要回退到的 user 消息索引(来自 listCheckpoints())
4175
+ * @returns
4176
+ * - `{ ok: true, fileRestored }`:成功;fileRestored 表示工作区是否也被还原
4177
+ * - `{ ok: false, error }`:失败(无效索引 / 非 user / 无 checkpoint / 文件恢复失败)
4178
+ *
4179
+ * 行为:
4180
+ * 1. 截断 messages 数组到 targetIndex + 1
4181
+ * 2. 移除并丢弃所有 > targetIndex 的 checkpoints
4182
+ * 3. 若目标 checkpoint 是 git 仓库:
4183
+ * - 有 stashSha:恢复该 stash(force)
4184
+ * - 无 stashSha:把工作区 restore 到 HEAD 干净状态
4185
+ * 4. 触发 #persist() 把截断后的状态写盘
4186
+ *
4187
+ * @sideEffect 改写 messages、checkpoints、磁盘
4188
+ */
2825
4189
  async rewind(targetIndex) {
2826
4190
  if (targetIndex < 0 || targetIndex >= this.#messages.length) {
2827
4191
  return { ok: false, error: `\u65E0\u6548\u7684\u6D88\u606F\u7D22\u5F15 ${targetIndex}` };
@@ -2861,9 +4225,20 @@ ${item.result.diff.patch}`;
2861
4225
  this.#persist();
2862
4226
  return { ok: true, fileRestored };
2863
4227
  }
4228
+ /**
4229
+ * 是否存在可用的 checkpoint(决定 UI 是否显示 /rewind 入口)。
4230
+ *
4231
+ * @pure 不修改任何状态
4232
+ */
2864
4233
  hasCheckpoints() {
2865
4234
  return this.listCheckpoints().length > 0;
2866
4235
  }
4236
+ /**
4237
+ * 彻底删除会话:从磁盘移除 SessionStore 条目、丢弃所有 checkpoint、关闭日志。
4238
+ * 删除后该 Session 实例不应再被使用。
4239
+ *
4240
+ * @sideEffect 删磁盘文件、清 checkpoints、flush 并关闭 logger
4241
+ */
2867
4242
  async delete() {
2868
4243
  if (this.#store) await this.#store.delete(this.#sessionId);
2869
4244
  for (const cp2 of this.#checkpoints.values()) {
@@ -2876,6 +4251,32 @@ ${item.result.diff.patch}`;
2876
4251
  // -------------------------------------------------------------------------
2877
4252
  // 内部方法
2878
4253
  // -------------------------------------------------------------------------
4254
+ /**
4255
+ * 构建本轮使用的工具执行器(每次 chat() 调用一次,绑定当前 signal)。
4256
+ * 每次新建确保 abort signal 是当下有效的,且 #baseCtx 不会被多次调用共享篡改。
4257
+ *
4258
+ * @returns 新的 ToolExecutor 实例
4259
+ * @pure 仅组装参数,不修改任何状态
4260
+ */
4261
+ #buildToolExecutor() {
4262
+ return new ToolExecutor({
4263
+ registry: this.#toolRegistry,
4264
+ gate: this.#options.gate,
4265
+ baseCtx: {
4266
+ cwd: this.#options.cwd,
4267
+ signal: this.#abortController.signal,
4268
+ writeRoots: this.#options.writeRoots
4269
+ }
4270
+ });
4271
+ }
4272
+ /**
4273
+ * 构建 system prompt:注入当前模型、cwd、工具清单、项目上下文。
4274
+ * plan 模式使用 buildPlanSystemPrompt(只读工具 + 计划输出格式),
4275
+ * code 模式使用 buildSystemPrompt。
4276
+ *
4277
+ * @returns 渲染好的 prompt 字符串
4278
+ * @pure 不修改任何状态
4279
+ */
2879
4280
  #buildSystemPrompt() {
2880
4281
  const enabledTools = this.#toolRegistry.list();
2881
4282
  const toolDescs = enabledTools.map((t) => ({
@@ -2890,20 +4291,43 @@ ${item.result.diff.patch}`;
2890
4291
  projectContext: this.#options.projectContext ?? void 0,
2891
4292
  cwd: this.#options.cwd
2892
4293
  };
2893
- if (this.#mode === "plan") return buildPlanSystemPrompt(opts);
2894
- return buildSystemPrompt(opts);
2895
- }
2896
- #buildToolDefinitions() {
2897
- const tools = this.#mode === "plan" ? this.#toolRegistry.listReadTools() : this.#toolRegistry.list();
2898
- return tools.map((t) => ({
2899
- type: "function",
2900
- function: { name: t.name, description: t.description, parameters: t.parameters }
2901
- }));
4294
+ let base;
4295
+ if (this.#mode === "plan") base = buildPlanSystemPrompt(opts);
4296
+ else base = buildSystemPrompt(opts);
4297
+ if (this.#todoList && this.#todoList.items.length > 0) {
4298
+ base = base + "\n\n" + this.#todoList.toMarkdown();
4299
+ }
4300
+ return base;
4301
+ }
4302
+ /**
4303
+ * Harness 护栏:模型跳过 todo_add 直接动手时,主动发一条提示让其重评。
4304
+ *
4305
+ * 触发条件(全部需满足):
4306
+ * 1. Harness 开启(#todoList 非空)
4307
+ * 2. todoList 仍为空(未拆 todo)
4308
+ * 3. 本会话内未提示过(#harnessHintEmitted 为 false)
4309
+ * 4. 本轮调了 ≥1 个**非 todo_* 的工具**(说明模型跳过了 todo_add 直接干活)
4310
+ *
4311
+ * 动作:push 一条 user role 消息到 #messages,让下一轮 LLM 看到;设 #harnessHintEmitted = true。
4312
+ *
4313
+ * @sideEffect 写 #messages / #harnessHintEmitted
4314
+ */
4315
+ #maybeEmitHarnessHint(items) {
4316
+ if (!this.#todoList) return;
4317
+ if (this.#todoList.items.length > 0) return;
4318
+ if (this.#harnessHintEmitted) return;
4319
+ const hasNonTodo = items.some((it) => !it.name.startsWith("todo_"));
4320
+ if (!hasNonTodo) return;
4321
+ this.#harnessHintEmitted = true;
4322
+ this.#messages.push({
4323
+ role: "user",
4324
+ content: "[\u7CFB\u7EDF\u63D0\u793A] \u4F60\u8FD8\u6CA1\u6709 todo \u8FC7\uFF0C\u4F46\u5DF2\u7ECF\u8C03\u4E86\u5177\u4F53\u5DE5\u5177\u3002\n\u8BF7\u56DE\u987E\u4F60\u521A\u505A\u7684\u4E8B\uFF1A\u8FD9\u662F\u300C\u7B80\u5355\u300D\u4EFB\u52A1\uFF081 \u8F6E 1 \u5DE5\u5177\u80FD\u5B8C\u6210\uFF09\u8FD8\u662F\u300C\u590D\u6742\u300D\u4EFB\u52A1\uFF1F\n\u5982\u679C\u662F\u590D\u6742\uFF08\u6539\u4E86/\u521B\u5EFA\u4E86/\u8981\u8BFB\u540E\u6539/\u8DE8\u591A\u79CD\u5DE5\u5177\uFF09\uFF0C\u8BF7\u505C\u4E0B\uFF0C\u5148\u8C03 todo_add \u62C6\u89E3 3-7 \u6B65\u518D\u7EE7\u7EED\u3002\n\u5982\u679C\u662F\u7B80\u5355\uFF0C\u5FFD\u7565\u672C\u63D0\u793A\u3002\n\uFF08\u672C\u63D0\u793A\u672C\u4F1A\u8BDD\u4EC5\u53D1\u4E00\u6B21\uFF0C\u540E\u7EED\u4E0D\u518D\u63D0\u9192\u3002\uFF09"
4325
+ });
2902
4326
  }
2903
4327
  };
2904
4328
 
2905
4329
  // src/tool/sandbox.ts
2906
- import { resolve, relative, isAbsolute } from "path";
4330
+ import { resolve, relative as relative2, isAbsolute } from "path";
2907
4331
  import { realpath as realpath2 } from "fs/promises";
2908
4332
  import { spawn } from "child_process";
2909
4333
  import process2 from "process";
@@ -2922,7 +4346,7 @@ async function realPath(target) {
2922
4346
  const parent = resolve(target, "..");
2923
4347
  try {
2924
4348
  const realParent = await realpath2(parent);
2925
- return resolve(realParent, relative(parent, target));
4349
+ return resolve(realParent, relative2(parent, target));
2926
4350
  } catch {
2927
4351
  return resolve(target);
2928
4352
  }
@@ -2935,7 +4359,7 @@ async function confine(allowedRoots, target) {
2935
4359
  const realTarget = await realPath(target);
2936
4360
  for (const root of allowedRoots) {
2937
4361
  const realRoot = await realPath(root);
2938
- const rel = relative(realRoot, realTarget);
4362
+ const rel = relative2(realRoot, realTarget);
2939
4363
  if (!rel.startsWith("..") && rel !== "" && !rel.startsWith("/") && !rel.startsWith("\\")) {
2940
4364
  return { ok: true };
2941
4365
  }
@@ -3332,7 +4756,7 @@ async function writeFileWithEol(filePath, originalContent, newContent) {
3332
4756
  // src/tool/builtins/read-file.ts
3333
4757
  import { readFile as readFile5, stat } from "fs/promises";
3334
4758
  import { open } from "fs/promises";
3335
- import { relative as relative2 } from "path";
4759
+ import { relative as relative3 } from "path";
3336
4760
  async function checkBinary(filePath) {
3337
4761
  const fileHandle = await open(filePath, "r");
3338
4762
  try {
@@ -3415,7 +4839,7 @@ var readFileTool = {
3415
4839
  const tailHint = remaining > 0 ? `
3416
4840
 
3417
4841
  [\u8FD8\u6709 ${remaining} \u884C\uFF1B\u4F7F\u7528 startLine=${endLine + 1} \u7EE7\u7EED\u67E5\u770B]` : "";
3418
- const relPath = relative2(ctx.cwd, filePath).replace(/\\/g, "/");
4842
+ const relPath = relative3(ctx.cwd, filePath).replace(/\\/g, "/");
3419
4843
  const rangeLabel = startLine > 0 || endLine < lines.length ? `\u7B2C ${startLine + 1}-${endLine} \u884C` : `${lines.length} \u884C`;
3420
4844
  return {
3421
4845
  success: true,
@@ -3435,7 +4859,7 @@ var readFileTool = {
3435
4859
 
3436
4860
  // src/tool/builtins/write-file.ts
3437
4861
  import { mkdir as mkdir6, readFile as readFile6 } from "fs/promises";
3438
- import { dirname, relative as relative3, basename as basename2 } from "path";
4862
+ import { dirname, relative as relative4, basename as basename2 } from "path";
3439
4863
  var writeFileTool = {
3440
4864
  name: "write_file",
3441
4865
  kind: "edit" /* Edit */,
@@ -3490,7 +4914,7 @@ var writeFileTool = {
3490
4914
  const summary = existedBefore ? `\u{1F4DD} \u4FEE\u6539: ${fileName} (+${diff.additions} -${diff.deletions})` : `\u{1F4DD} \u65B0\u5EFA: ${fileName} (+${diff.additions} \u884C)`;
3491
4915
  return {
3492
4916
  success: true,
3493
- data: `\u6587\u4EF6${action}\uFF1A${relative3(ctx.cwd, filePath).replace(/\\/g, "/")}\uFF08${lineCount} \u884C\uFF0C${byteSize} \u5B57\u8282${diffSummary}\uFF09`,
4917
+ data: `\u6587\u4EF6${action}\uFF1A${relative4(ctx.cwd, filePath).replace(/\\/g, "/")}\uFF08${lineCount} \u884C\uFF0C${byteSize} \u5B57\u8282${diffSummary}\uFF09`,
3494
4918
  summary,
3495
4919
  diff
3496
4920
  };
@@ -3943,7 +5367,7 @@ ${truncateOutput(result.stderr)}`);
3943
5367
 
3944
5368
  // src/tool/builtins/glob.ts
3945
5369
  import { readdir as readdir3, stat as stat2 } from "fs/promises";
3946
- import { join as join7, relative as relative4, isAbsolute as isAbsolute2 } from "path";
5370
+ import { join as join7, relative as relative5, isAbsolute as isAbsolute2 } from "path";
3947
5371
  function globToRegex(pattern) {
3948
5372
  let regexStr = pattern;
3949
5373
  regexStr = regexStr.replace(/\*\*\//g, "<<GLOBSTAR_SLASH>>");
@@ -3969,7 +5393,7 @@ async function walkDir(dir, baseDir) {
3969
5393
  continue;
3970
5394
  }
3971
5395
  const fullPath = join7(dir, entry.name);
3972
- const relPath = relative4(baseDir, fullPath);
5396
+ const relPath = relative5(baseDir, fullPath);
3973
5397
  if (entry.isDirectory()) {
3974
5398
  results.push(...await walkDir(fullPath, baseDir));
3975
5399
  } else {
@@ -4041,7 +5465,7 @@ var globTool = {
4041
5465
 
4042
5466
  // src/tool/builtins/grep.ts
4043
5467
  import { readdir as readdir4, readFile as readFile10, stat as stat3 } from "fs/promises";
4044
- import { join as join8, relative as relative5, isAbsolute as isAbsolute3 } from "path";
5468
+ import { join as join8, relative as relative6, isAbsolute as isAbsolute3 } from "path";
4045
5469
  async function collectFiles(dir, extension, maxFiles = 200) {
4046
5470
  const results = [];
4047
5471
  let entries;
@@ -4117,7 +5541,7 @@ var grepTool = {
4117
5541
  try {
4118
5542
  const content = await readFile10(filePath, "utf-8");
4119
5543
  const lines = content.split("\n");
4120
- const relPath = relative5(searchDir, filePath);
5544
+ const relPath = relative6(searchDir, filePath);
4121
5545
  for (let i = 0; i < lines.length; i++) {
4122
5546
  if (regex.test(lines[i] ?? "")) {
4123
5547
  regex.lastIndex = 0;
@@ -4162,7 +5586,7 @@ var grepTool = {
4162
5586
 
4163
5587
  // src/tool/builtins/ls.ts
4164
5588
  import { readdir as readdir5, stat as stat4 } from "fs/promises";
4165
- import { join as join9, relative as relative6 } from "path";
5589
+ import { join as join9, relative as relative7 } from "path";
4166
5590
  var lsTool = {
4167
5591
  name: "ls",
4168
5592
  kind: "read" /* Read */,
@@ -4216,9 +5640,9 @@ var lsTool = {
4216
5640
  lines.push(`${typeLabel} ${entry.name}${sizeStr ? ` (${sizeStr})` : ""}`);
4217
5641
  }
4218
5642
  if (lines.length === 0) {
4219
- return { success: true, data: "\u76EE\u5F55\u4E3A\u7A7A", summary: `\u{1F4C2} ${relative6(ctx.cwd, dirPath).replace(/\\/g, "/")}\uFF08\u7A7A\uFF09` };
5643
+ return { success: true, data: "\u76EE\u5F55\u4E3A\u7A7A", summary: `\u{1F4C2} ${relative7(ctx.cwd, dirPath).replace(/\\/g, "/")}\uFF08\u7A7A\uFF09` };
4220
5644
  }
4221
- const relPath = relative6(ctx.cwd, dirPath).replace(/\\/g, "/");
5645
+ const relPath = relative7(ctx.cwd, dirPath).replace(/\\/g, "/");
4222
5646
  return {
4223
5647
  success: true,
4224
5648
  data: truncateOutput(`\u76EE\u5F55\uFF1A${dirPath}
@@ -4402,9 +5826,9 @@ function getGradientColors(text, phase, stops) {
4402
5826
  }
4403
5827
 
4404
5828
  // src/ui/ChatSession.tsx
4405
- import { Fragment, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
5829
+ import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
4406
5830
  var PHASE_CONFIG = {
4407
- thinking: { icon: "\u{1F9E0}", label: "\u601D\u8003\u4E2D", color: "#ff9800" },
5831
+ thinking: { icon: "\u{1F9E0}", label: "\u6DF1\u5EA6\u601D\u8003\u4E2D", color: "#ff9800" },
4408
5832
  generating: { icon: "\u2728", label: "\u751F\u6210\u4E2D", color: "#00ff41" },
4409
5833
  calling_tools: { icon: "\u{1F6E0}", label: "\u8C03\u7528\u5DE5\u5177", color: "#f59e0b" },
4410
5834
  executing_tools: { icon: "\u26A1", label: "\u6267\u884C\u5DE5\u5177", color: "#00ffff" }
@@ -4433,15 +5857,45 @@ registerCommand("/help", {
4433
5857
  }
4434
5858
  });
4435
5859
  registerCommand("/clear", { desc: "\u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2", handler: () => ({ kind: "clear" }) });
4436
- registerCommand("/version", { desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F", handler: () => ({ kind: "text", content: "dskcode v0.1.10" }) });
4437
- registerCommand("/model", { desc: "\u5207\u6362\u6A21\u578B", handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /model \u8FDB\u5165\u9009\u62E9\u754C\u9762" }) });
4438
- registerCommand("/thinking", { desc: "\u5207\u6362\u6DF1\u5EA6\u601D\u8003\u6A21\u5F0F", handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /thinking \u5207\u6362" }) });
4439
- registerCommand("/effort", { desc: "\u5207\u6362\u63A8\u7406\u7B49\u7EA7 High/Max", handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /effort \u5207\u6362" }) });
4440
- registerCommand("/game", { desc: "\u542F\u52A8\u6E38\u620F", handler: () => ({ kind: "navigate", target: "game" }) });
4441
- registerCommand("/stock", { desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5", handler: () => ({ kind: "navigate", target: "stock" }) });
4442
- registerCommand("/plan", { desc: "\u5207\u6362\u4E3A\u8BA1\u5212\u6A21\u5F0F\uFF08Shift+Tab\uFF09", handler: () => ({ kind: "text", content: "\u8F93\u5165 /plan \u6216\u6309 Shift+Tab \u5207\u6362\u4E3A\u8BA1\u5212\u6A21\u5F0F" }) });
4443
- registerCommand("/code", { desc: "\u5207\u6362\u56DE\u4EE3\u7801\u6A21\u5F0F\uFF08Shift+Tab\uFF09", handler: () => ({ kind: "text", content: "\u8F93\u5165 /code \u6216\u6309 Shift+Tab \u5207\u6362\u56DE\u4EE3\u7801\u6A21\u5F0F" }) });
4444
- registerCommand("/rewind", { desc: "\u56DE\u9000\u5230\u5386\u53F2\u68C0\u67E5\u70B9\uFF081 = \u6700\u65B0\uFF09", handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /rewind \u67E5\u770B\u53EF\u56DE\u9000\u7684\u68C0\u67E5\u70B9\u5217\u8868\uFF0C\u6216 /rewind <\u5E8F\u53F7> \u76F4\u63A5\u56DE\u9000\uFF081 = \u6700\u65B0\uFF0C2 = \u4E0A\u4E00\u6B21\uFF0C\u4F9D\u6B64\u7C7B\u63A8\uFF09" }) });
5860
+ registerCommand("/version", {
5861
+ desc: "\u663E\u793A\u7248\u672C\u4FE1\u606F",
5862
+ handler: () => ({ kind: "text", content: "dskcode v0.1.10" })
5863
+ });
5864
+ registerCommand("/model", {
5865
+ desc: "\u5207\u6362\u6A21\u578B",
5866
+ handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /model \u8FDB\u5165\u9009\u62E9\u754C\u9762" })
5867
+ });
5868
+ registerCommand("/thinking", {
5869
+ desc: "\u5207\u6362\u6DF1\u5EA6\u601D\u8003\u6A21\u5F0F",
5870
+ handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /thinking \u5207\u6362" })
5871
+ });
5872
+ registerCommand("/effort", {
5873
+ desc: "\u5207\u6362\u63A8\u7406\u7B49\u7EA7 High/Max",
5874
+ handler: () => ({ kind: "text", content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /effort \u5207\u6362" })
5875
+ });
5876
+ registerCommand("/game", {
5877
+ desc: "\u542F\u52A8\u6E38\u620F",
5878
+ handler: () => ({ kind: "navigate", target: "game" })
5879
+ });
5880
+ registerCommand("/stock", {
5881
+ desc: "\u67E5\u770B\u80A1\u7968\u884C\u60C5",
5882
+ handler: () => ({ kind: "navigate", target: "stock" })
5883
+ });
5884
+ registerCommand("/plan", {
5885
+ desc: "\u5207\u6362\u4E3A\u8BA1\u5212\u6A21\u5F0F\uFF08Shift+Tab\uFF09",
5886
+ handler: () => ({ kind: "text", content: "\u8F93\u5165 /plan \u6216\u6309 Shift+Tab \u5207\u6362\u4E3A\u8BA1\u5212\u6A21\u5F0F" })
5887
+ });
5888
+ registerCommand("/code", {
5889
+ desc: "\u5207\u6362\u56DE\u4EE3\u7801\u6A21\u5F0F\uFF08Shift+Tab\uFF09",
5890
+ handler: () => ({ kind: "text", content: "\u8F93\u5165 /code \u6216\u6309 Shift+Tab \u5207\u6362\u56DE\u4EE3\u7801\u6A21\u5F0F" })
5891
+ });
5892
+ registerCommand("/rewind", {
5893
+ desc: "\u56DE\u9000\u5230\u5386\u53F2\u68C0\u67E5\u70B9\uFF081 = \u6700\u65B0\uFF09",
5894
+ handler: () => ({
5895
+ kind: "text",
5896
+ content: "\u8BF7\u76F4\u63A5\u8F93\u5165 /rewind \u67E5\u770B\u53EF\u56DE\u9000\u7684\u68C0\u67E5\u70B9\u5217\u8868\uFF0C\u6216 /rewind <\u5E8F\u53F7> \u76F4\u63A5\u56DE\u9000\uFF081 = \u6700\u65B0\uFF0C2 = \u4E0A\u4E00\u6B21\uFF0C\u4F9D\u6B64\u7C7B\u63A8\uFF09"
5897
+ })
5898
+ });
4445
5899
  var STREAMING_PLACEHOLDERS = [
4446
5900
  "\u8BA9\u5B50\u5F39\u98DE\u4E00\u4F1A\u513F...",
4447
5901
  "\u9A6C\u4E0A\u5C31\u597D...",
@@ -4476,34 +5930,40 @@ function ChatSession({
4476
5930
  }) {
4477
5931
  const termWidth = typeof process.stdout.columns === "number" ? process.stdout.columns : 80;
4478
5932
  const dividerWidth = Math.max(termWidth - 2, 1);
4479
- const [offset, setOffset] = useState3(0);
4480
- const [displayMessages, setDisplayMessages] = useState3([]);
4481
- const [staticKey, setStaticKey] = useState3(0);
4482
- const [input, setInput] = useState3("");
4483
- const [balance, setBalance] = useState3(null);
4484
- const [balanceLoading, setBalanceLoading] = useState3(false);
4485
- const [todayCost, setTodayCost] = useState3(null);
4486
- const [isStreaming, setIsStreaming] = useState3(false);
4487
- const [streamingPhase, setStreamingPhase] = useState3(null);
4488
- const [streamingPlaceholder, setStreamingPlaceholder] = useState3("");
4489
- const [idlePlaceholder, setIdlePlaceholder] = useState3(() => pickRandom(IDLE_PLACEHOLDERS));
4490
- const [gradientColors, setGradientColors] = useState3([]);
4491
- const gradientPhaseRef = useRef2(0);
4492
- const [streamingGradientColors, setStreamingGradientColors] = useState3([]);
4493
- const streamingPhaseRef = useRef2(0);
4494
- const [currentContent, setCurrentContent] = useState3("");
4495
- const [currentToolCalls, setCurrentToolCalls] = useState3([]);
4496
- const [_currentUsage, setCurrentUsage] = useState3(void 0);
4497
- const [_currentElapsed, setCurrentElapsed] = useState3(void 0);
4498
- const [_currentCost, setCurrentCost] = useState3(void 0);
4499
- const [activeModel, setActiveModel] = useState3(model);
4500
- const [_streamingModel, setStreamingModel] = useState3(void 0);
4501
- const [streamError, setStreamError] = useState3(void 0);
4502
- const [sessionMode, setSessionMode] = useState3("code");
4503
- const [thinkingEnabled, setThinkingEnabled] = useState3(true);
4504
- const [thinkingEffort, setThinkingEffort] = useState3("high");
4505
- const [responseFormat] = useState3("text");
4506
- const [toolChoice, setToolChoice] = useState3(void 0);
5933
+ const [offset, setOffset] = useState4(0);
5934
+ const [displayMessages, setDisplayMessages] = useState4([]);
5935
+ const [staticKey, setStaticKey] = useState4(0);
5936
+ const [input, setInput] = useState4("");
5937
+ const [balance, setBalance] = useState4(null);
5938
+ const [balanceLoading, setBalanceLoading] = useState4(false);
5939
+ const [todayCost, setTodayCost] = useState4(null);
5940
+ const [isStreaming, setIsStreaming] = useState4(false);
5941
+ const [streamingPhase, setStreamingPhase] = useState4(null);
5942
+ const [streamingPlaceholder, setStreamingPlaceholder] = useState4("");
5943
+ const [idlePlaceholder, setIdlePlaceholder] = useState4(
5944
+ () => pickRandom(IDLE_PLACEHOLDERS)
5945
+ );
5946
+ const [gradientColors, setGradientColors] = useState4([]);
5947
+ const gradientPhaseRef = useRef3(0);
5948
+ const [streamingGradientColors, setStreamingGradientColors] = useState4([]);
5949
+ const streamingPhaseRef = useRef3(0);
5950
+ const [currentContent, setCurrentContent] = useState4("");
5951
+ const [currentReasoning, setCurrentReasoning] = useState4([]);
5952
+ const [currentToolCalls, setCurrentToolCalls] = useState4([]);
5953
+ const [_currentUsage, setCurrentUsage] = useState4(void 0);
5954
+ const [_currentElapsed, setCurrentElapsed] = useState4(void 0);
5955
+ const [_currentCost, setCurrentCost] = useState4(void 0);
5956
+ const [activeModel, setActiveModel] = useState4(model);
5957
+ const [_streamingModel, setStreamingModel] = useState4(void 0);
5958
+ const [streamError, setStreamError] = useState4(void 0);
5959
+ const [todoSnapshot, setTodoSnapshot] = useState4([]);
5960
+ const [sessionMode, setSessionMode] = useState4("code");
5961
+ const [thinkingEnabled, setThinkingEnabled] = useState4(true);
5962
+ const [thinkingEffort, setThinkingEffort] = useState4("high");
5963
+ const [responseFormat] = useState4("text");
5964
+ const [toolChoice, setToolChoice] = useState4(
5965
+ void 0
5966
+ );
4507
5967
  const sessionCost = useMemo(() => {
4508
5968
  return displayMessages.reduce((sum, msg) => {
4509
5969
  if (msg.assistantDetail?.cost) {
@@ -4514,36 +5974,39 @@ function ChatSession({
4514
5974
  }, [displayMessages]);
4515
5975
  const hasConversationStarted = displayMessages.length > 0;
4516
5976
  const cmdTips = Array.from(getRegisteredCommands()).filter(([name]) => name !== "/exit" && name !== "/quit").map(([name, cmd]) => ({ name, desc: cmd.desc }));
4517
- const [cmdTipIndex, setCmdTipIndex] = useState3(0);
4518
- const [cmdTipGradientColors, setCmdTipGradientColors] = useState3([]);
4519
- const cmdTipPhaseRef = useRef2(0);
4520
- const [skillSelectIndex, setSkillSelectIndex] = useState3(0);
4521
- const [fileSelectIndex, setFileSelectIndex] = useState3(0);
4522
- const [inputKey, setInputKey] = useState3(0);
4523
- const [rewindSelecting, setRewindSelecting] = useState3(false);
4524
- const [rewindSelectIndex, setRewindSelectIndex] = useState3(0);
4525
- const [rewindList, setRewindList] = useState3([]);
4526
- const [rewinding, setRewinding] = useState3(false);
4527
- const [rewindHintPhase, setRewindHintPhase] = useState3("idle");
4528
- const currentRoundModifiedRef = useRef2(false);
4529
- const rewindHintTimerRef = useRef2(null);
4530
- const [selectingModel, setSelectingModel] = useState3(false);
4531
- const [modelSelectIndex, setModelSelectIndex] = useState3(0);
5977
+ const [cmdTipIndex, setCmdTipIndex] = useState4(0);
5978
+ const [cmdTipGradientColors, setCmdTipGradientColors] = useState4([]);
5979
+ const cmdTipPhaseRef = useRef3(0);
5980
+ const [skillSelectIndex, setSkillSelectIndex] = useState4(0);
5981
+ const [fileSelectIndex, setFileSelectIndex] = useState4(0);
5982
+ const [inputKey, setInputKey] = useState4(0);
5983
+ const [rewindSelecting, setRewindSelecting] = useState4(false);
5984
+ const [rewindSelectIndex, setRewindSelectIndex] = useState4(0);
5985
+ const [rewindList, setRewindList] = useState4([]);
5986
+ const [rewinding, setRewinding] = useState4(false);
5987
+ const [rewindHintPhase, setRewindHintPhase] = useState4(
5988
+ "idle"
5989
+ );
5990
+ const currentRoundModifiedRef = useRef3(false);
5991
+ const rewindHintTimerRef = useRef3(null);
5992
+ const [selectingModel, setSelectingModel] = useState4(false);
5993
+ const [modelSelectIndex, setModelSelectIndex] = useState4(0);
4532
5994
  const modelOptions = ["deepseek-v4-flash", "deepseek-v4-pro"];
4533
- const sessionRef = useRef2(null);
4534
- const abortRef = useRef2(null);
4535
- const currentContentRef = useRef2("");
4536
- const currentToolCallsRef = useRef2([]);
4537
- const currentUsageRef = useRef2(void 0);
4538
- const currentElapsedRef = useRef2(void 0);
4539
- const currentCostRef = useRef2(void 0);
4540
- const currentModelRef = useRef2(void 0);
4541
- const streamErrorRef = useRef2(void 0);
4542
- useEffect3(() => {
5995
+ const sessionRef = useRef3(null);
5996
+ const abortRef = useRef3(null);
5997
+ const currentContentRef = useRef3("");
5998
+ const currentReasoningRef = useRef3([]);
5999
+ const currentToolCallsRef = useRef3([]);
6000
+ const currentUsageRef = useRef3(void 0);
6001
+ const currentElapsedRef = useRef3(void 0);
6002
+ const currentCostRef = useRef3(void 0);
6003
+ const currentModelRef = useRef3(void 0);
6004
+ const streamErrorRef = useRef3(void 0);
6005
+ useEffect4(() => {
4543
6006
  setSkillSelectIndex(0);
4544
6007
  setFileSelectIndex(0);
4545
6008
  }, [input]);
4546
- useEffect3(() => {
6009
+ useEffect4(() => {
4547
6010
  return () => {
4548
6011
  if (rewindHintTimerRef.current) {
4549
6012
  clearTimeout(rewindHintTimerRef.current);
@@ -4602,42 +6065,48 @@ function ChatSession({
4602
6065
  setDisplayMessages(next);
4603
6066
  setStaticKey((prev) => prev + 1);
4604
6067
  }
4605
- const doRewind = useCallback2(async (target, displayNumber) => {
4606
- const session = sessionRef.current;
4607
- if (!session) return;
4608
- setRewinding(true);
4609
- setIsStreaming(true);
4610
- setStreamingPhase("thinking");
4611
- setStreamingPlaceholder("\u23EA \u6B63\u5728\u56DE\u9000\u5230\u68C0\u67E5\u70B9\u2026");
4612
- setInput("");
4613
- try {
4614
- const r = await session.rewind(target.index);
4615
- if (r.ok) {
4616
- rebuildDisplayFromSession(session);
4617
- const tail = r.fileRestored ? "\uFF0C\u5DE5\u4F5C\u533A\u6587\u4EF6\u5DF2\u6062\u590D" : "\uFF08\u4EC5\u5BF9\u8BDD\u56DE\u9000\uFF0C\u672A\u6062\u590D\u6587\u4EF6\uFF09";
4618
- setDisplayMessages((prev) => [
4619
- ...prev,
4620
- { role: "assistant", content: `\u23EA \u5DF2\u56DE\u9000\u5230\u68C0\u67E5\u70B9 #${displayNumber}${tail}\u3002` }
4621
- ]);
4622
- } else {
6068
+ const doRewind = useCallback2(
6069
+ async (target, displayNumber) => {
6070
+ const session = sessionRef.current;
6071
+ if (!session) return;
6072
+ setRewinding(true);
6073
+ setIsStreaming(true);
6074
+ setStreamingPhase("thinking");
6075
+ setStreamingPlaceholder("\u23EA \u6B63\u5728\u56DE\u9000\u5230\u68C0\u67E5\u70B9\u2026");
6076
+ setInput("");
6077
+ try {
6078
+ const r = await session.rewind(target.index);
6079
+ if (r.ok) {
6080
+ rebuildDisplayFromSession(session);
6081
+ const tail = r.fileRestored ? "\uFF0C\u5DE5\u4F5C\u533A\u6587\u4EF6\u5DF2\u6062\u590D" : "\uFF08\u4EC5\u5BF9\u8BDD\u56DE\u9000\uFF0C\u672A\u6062\u590D\u6587\u4EF6\uFF09";
6082
+ setDisplayMessages((prev) => [
6083
+ ...prev,
6084
+ {
6085
+ role: "assistant",
6086
+ content: `\u23EA \u5DF2\u56DE\u9000\u5230\u68C0\u67E5\u70B9 #${displayNumber}${tail}\u3002`
6087
+ }
6088
+ ]);
6089
+ } else {
6090
+ setDisplayMessages((prev) => [
6091
+ ...prev,
6092
+ { role: "assistant", content: `\u26A0 \u56DE\u9000\u5931\u8D25\uFF1A${r.error}` }
6093
+ ]);
6094
+ }
6095
+ } catch (err) {
6096
+ const msg = err instanceof Error ? err.message : String(err);
4623
6097
  setDisplayMessages((prev) => [
4624
6098
  ...prev,
4625
- { role: "assistant", content: `\u26A0 \u56DE\u9000\u5931\u8D25\uFF1A${r.error}` }
6099
+ { role: "assistant", content: `\u26A0 \u56DE\u9000\u5F02\u5E38\uFF1A${msg}` }
4626
6100
  ]);
6101
+ } finally {
6102
+ setRewinding(false);
6103
+ setIsStreaming(false);
6104
+ setStreamingPhase(null);
6105
+ setStreamingPlaceholder("");
4627
6106
  }
4628
- } catch (err) {
4629
- const msg = err instanceof Error ? err.message : String(err);
4630
- setDisplayMessages((prev) => [
4631
- ...prev,
4632
- { role: "assistant", content: `\u26A0 \u56DE\u9000\u5F02\u5E38\uFF1A${msg}` }
4633
- ]);
4634
- } finally {
4635
- setRewinding(false);
4636
- setIsStreaming(false);
4637
- setStreamingPhase(null);
4638
- setStreamingPlaceholder("");
4639
- }
4640
- }, []);
6107
+ },
6108
+ []
6109
+ );
4641
6110
  const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => {
4642
6111
  const s = sessionRef.current;
4643
6112
  if (s) {
@@ -4651,7 +6120,9 @@ function ChatSession({
4651
6120
  (_input, key) => {
4652
6121
  if (rewindSelecting) {
4653
6122
  if (key.upArrow) {
4654
- setRewindSelectIndex((prev) => (prev - 1 + rewindList.length) % rewindList.length);
6123
+ setRewindSelectIndex(
6124
+ (prev) => (prev - 1 + rewindList.length) % rewindList.length
6125
+ );
4655
6126
  } else if (key.downArrow) {
4656
6127
  setRewindSelectIndex((prev) => (prev + 1) % rewindList.length);
4657
6128
  } else if (key.return) {
@@ -4671,7 +6142,9 @@ function ChatSession({
4671
6142
  }
4672
6143
  if (selectingModel) {
4673
6144
  if (key.upArrow) {
4674
- setModelSelectIndex((prev) => (prev - 1 + modelOptions.length) % modelOptions.length);
6145
+ setModelSelectIndex(
6146
+ (prev) => (prev - 1 + modelOptions.length) % modelOptions.length
6147
+ );
4675
6148
  } else if (key.downArrow) {
4676
6149
  setModelSelectIndex((prev) => (prev + 1) % modelOptions.length);
4677
6150
  } else if (key.return) {
@@ -4679,7 +6152,10 @@ function ChatSession({
4679
6152
  if (selected === activeModel) {
4680
6153
  setDisplayMessages((prev) => [
4681
6154
  ...prev,
4682
- { role: "assistant", content: `\u5DF2\u7ECF\u5728\u4F7F\u7528 ${SUPPORTED_MODELS[selected].displayName}` }
6155
+ {
6156
+ role: "assistant",
6157
+ content: `\u5DF2\u7ECF\u5728\u4F7F\u7528 ${SUPPORTED_MODELS[selected].displayName}`
6158
+ }
4683
6159
  ]);
4684
6160
  } else {
4685
6161
  setActiveModel(selected);
@@ -4688,7 +6164,10 @@ function ChatSession({
4688
6164
  });
4689
6165
  setDisplayMessages((prev) => [
4690
6166
  ...prev,
4691
- { role: "assistant", content: `\u6A21\u578B\u5DF2\u5207\u6362\u4E3A ${SUPPORTED_MODELS[selected].displayName}\uFF08${selected}\uFF09` }
6167
+ {
6168
+ role: "assistant",
6169
+ content: `\u6A21\u578B\u5DF2\u5207\u6362\u4E3A ${SUPPORTED_MODELS[selected].displayName}\uFF08${selected}\uFF09`
6170
+ }
4692
6171
  ]);
4693
6172
  }
4694
6173
  setSelectingModel(false);
@@ -4722,7 +6201,9 @@ function ChatSession({
4722
6201
  }
4723
6202
  if (skillList.length > 0) {
4724
6203
  if (key.upArrow) {
4725
- setSkillSelectIndex((prev) => (prev - 1 + skillList.length) % skillList.length);
6204
+ setSkillSelectIndex(
6205
+ (prev) => (prev - 1 + skillList.length) % skillList.length
6206
+ );
4726
6207
  return;
4727
6208
  }
4728
6209
  if (key.downArrow) {
@@ -4775,17 +6256,36 @@ function ChatSession({
4775
6256
  setInput(_input);
4776
6257
  }
4777
6258
  },
4778
- [selectingModel, modelSelectIndex, modelOptions, activeModel, isStreaming, handleCtrlC, input, skills, skillSelectIndex, fileSelectIndex, getFilteredSkills, getFilteredFiles, sessionMode, setSessionMode, rewindSelecting, rewindSelectIndex, rewindList, doRewind]
6259
+ [
6260
+ selectingModel,
6261
+ modelSelectIndex,
6262
+ modelOptions,
6263
+ activeModel,
6264
+ isStreaming,
6265
+ handleCtrlC,
6266
+ input,
6267
+ skills,
6268
+ skillSelectIndex,
6269
+ fileSelectIndex,
6270
+ getFilteredSkills,
6271
+ getFilteredFiles,
6272
+ sessionMode,
6273
+ setSessionMode,
6274
+ rewindSelecting,
6275
+ rewindSelectIndex,
6276
+ rewindList,
6277
+ doRewind
6278
+ ]
4779
6279
  )
4780
6280
  );
4781
- useEffect3(() => {
6281
+ useEffect4(() => {
4782
6282
  if (cmdTips.length <= 1) return;
4783
6283
  const timer = setInterval(() => {
4784
6284
  setCmdTipIndex((prev) => (prev + 1) % cmdTips.length);
4785
6285
  }, 2e3);
4786
6286
  return () => clearInterval(timer);
4787
6287
  }, [cmdTips.length]);
4788
- useEffect3(() => {
6288
+ useEffect4(() => {
4789
6289
  const tip = cmdTips[cmdTipIndex % cmdTips.length];
4790
6290
  if (!tip) {
4791
6291
  setCmdTipGradientColors([]);
@@ -4796,17 +6296,19 @@ function ChatSession({
4796
6296
  setCmdTipGradientColors(getGradientColors(text, 1, CMD_TIP_GRADIENT_STOPS));
4797
6297
  const interval = setInterval(() => {
4798
6298
  cmdTipPhaseRef.current = (cmdTipPhaseRef.current + GRADIENT_ANIMATION.cmdTipPhaseStep) % 1;
4799
- setCmdTipGradientColors(getGradientColors(text, 1 - cmdTipPhaseRef.current, CMD_TIP_GRADIENT_STOPS));
6299
+ setCmdTipGradientColors(
6300
+ getGradientColors(text, 1 - cmdTipPhaseRef.current, CMD_TIP_GRADIENT_STOPS)
6301
+ );
4800
6302
  }, GRADIENT_ANIMATION.cmdTipInterval);
4801
6303
  return () => clearInterval(interval);
4802
6304
  }, [cmdTipIndex, cmdTips.length]);
4803
- useEffect3(() => {
6305
+ useEffect4(() => {
4804
6306
  const timer = setInterval(() => {
4805
6307
  setOffset((prev) => (prev + 1) % CYBER_PALETTE.length);
4806
6308
  }, 500);
4807
6309
  return () => clearInterval(timer);
4808
6310
  }, []);
4809
- useEffect3(() => {
6311
+ useEffect4(() => {
4810
6312
  if (!apiKey || !baseUrl) return;
4811
6313
  const provider = createProvider({
4812
6314
  name: "deepseek",
@@ -4825,11 +6327,11 @@ function ChatSession({
4825
6327
  sessionRef.current = null;
4826
6328
  };
4827
6329
  }, [apiKey, baseUrl, activeModel, externalCostTracker]);
4828
- useEffect3(() => {
6330
+ useEffect4(() => {
4829
6331
  if (!apiKey || !baseUrl) return;
4830
6332
  let cancelled = false;
4831
6333
  setBalanceLoading(true);
4832
- import("./deepseek-PGX76BK5.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
6334
+ import("./deepseek-WFSVA5UQ.js").then(({ DeepSeekProvider: DeepSeekProvider2 }) => {
4833
6335
  const provider = new DeepSeekProvider2({
4834
6336
  apiKey,
4835
6337
  baseUrl,
@@ -4850,7 +6352,7 @@ function ChatSession({
4850
6352
  cancelled = true;
4851
6353
  };
4852
6354
  }, [apiKey, baseUrl]);
4853
- useEffect3(() => {
6355
+ useEffect4(() => {
4854
6356
  if (!externalCostTracker) return;
4855
6357
  let cancelled = false;
4856
6358
  let timer;
@@ -4868,7 +6370,7 @@ function ChatSession({
4868
6370
  if (timer) clearInterval(timer);
4869
6371
  };
4870
6372
  }, [externalCostTracker]);
4871
- useEffect3(() => {
6373
+ useEffect4(() => {
4872
6374
  if (isStreaming || !idlePlaceholder) {
4873
6375
  setGradientColors([]);
4874
6376
  return;
@@ -4877,395 +6379,481 @@ function ChatSession({
4877
6379
  setGradientColors(getGradientColors(idlePlaceholder, 1, IDLE_GRADIENT_STOPS));
4878
6380
  const interval = setInterval(() => {
4879
6381
  gradientPhaseRef.current = (gradientPhaseRef.current + GRADIENT_ANIMATION.idlePhaseStep) % 1;
4880
- setGradientColors(getGradientColors(idlePlaceholder, 1 - gradientPhaseRef.current, IDLE_GRADIENT_STOPS));
6382
+ setGradientColors(
6383
+ getGradientColors(
6384
+ idlePlaceholder,
6385
+ 1 - gradientPhaseRef.current,
6386
+ IDLE_GRADIENT_STOPS
6387
+ )
6388
+ );
4881
6389
  }, GRADIENT_ANIMATION.idleInterval);
4882
6390
  return () => clearInterval(interval);
4883
6391
  }, [isStreaming, idlePlaceholder]);
4884
- useEffect3(() => {
6392
+ useEffect4(() => {
4885
6393
  if (!isStreaming || !streamingPlaceholder) {
4886
6394
  setStreamingGradientColors([]);
4887
6395
  return;
4888
6396
  }
4889
6397
  streamingPhaseRef.current = 0;
4890
- setStreamingGradientColors(getGradientColors(streamingPlaceholder, 1, STREAMING_GRADIENT_STOPS));
6398
+ setStreamingGradientColors(
6399
+ getGradientColors(streamingPlaceholder, 1, STREAMING_GRADIENT_STOPS)
6400
+ );
4891
6401
  const interval = setInterval(() => {
4892
6402
  streamingPhaseRef.current = (streamingPhaseRef.current + GRADIENT_ANIMATION.streamingPhaseStep) % 1;
4893
- setStreamingGradientColors(getGradientColors(streamingPlaceholder, 1 - streamingPhaseRef.current, STREAMING_GRADIENT_STOPS));
6403
+ setStreamingGradientColors(
6404
+ getGradientColors(
6405
+ streamingPlaceholder,
6406
+ 1 - streamingPhaseRef.current,
6407
+ STREAMING_GRADIENT_STOPS
6408
+ )
6409
+ );
4894
6410
  }, GRADIENT_ANIMATION.streamingInterval);
4895
6411
  return () => clearInterval(interval);
4896
6412
  }, [isStreaming, streamingPlaceholder]);
4897
- const handleSubmit = useCallback2(async (value) => {
4898
- const trimmed = value.trim();
4899
- if (!trimmed) return;
4900
- if (trimmed.startsWith("/") && trimmed.length > 1) {
4901
- const cmdLower = trimmed.toLowerCase();
4902
- if (cmdLower === "/rewind" || cmdLower.startsWith("/rewind ")) {
4903
- if (isStreaming || rewinding) {
4904
- const reason = rewinding ? "\u56DE\u9000\u4E2D" : "\u751F\u6210\u4E2D";
6413
+ const handleSubmit = useCallback2(
6414
+ async (value) => {
6415
+ const trimmed = value.trim();
6416
+ if (!trimmed) return;
6417
+ if (trimmed.startsWith("/") && trimmed.length > 1) {
6418
+ const cmdLower = trimmed.toLowerCase();
6419
+ if (cmdLower === "/rewind" || cmdLower.startsWith("/rewind ")) {
6420
+ if (isStreaming || rewinding) {
6421
+ const reason = rewinding ? "\u56DE\u9000\u4E2D" : "\u751F\u6210\u4E2D";
6422
+ setDisplayMessages((prev) => [
6423
+ ...prev,
6424
+ { role: "user", content: trimmed },
6425
+ { role: "assistant", content: `\u26A0 \u6B63\u5728${reason}\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5 /rewind\u3002` }
6426
+ ]);
6427
+ setInput("");
6428
+ return;
6429
+ }
6430
+ if (!sessionRef.current) {
6431
+ setDisplayMessages((prev) => [
6432
+ ...prev,
6433
+ { role: "user", content: trimmed },
6434
+ { role: "assistant", content: "\u26A0 Session \u672A\u5C31\u7EEA\uFF0C\u65E0\u6CD5\u56DE\u9000\u3002" }
6435
+ ]);
6436
+ setInput("");
6437
+ return;
6438
+ }
6439
+ const cps = sessionRef.current.listCheckpoints();
6440
+ if (cps.length === 0) {
6441
+ setDisplayMessages((prev) => [
6442
+ ...prev,
6443
+ { role: "user", content: trimmed },
6444
+ {
6445
+ role: "assistant",
6446
+ content: "\u26A0 \u6CA1\u6709\u53EF\u56DE\u9000\u7684\u68C0\u67E5\u70B9\u3002\n\u53EA\u6709\u5728 git \u4ED3\u5E93\u5185\u4E14\u53D1\u751F\u8FC7\u5BF9\u8BDD\u540E\u624D\u4F1A\u751F\u6210\u68C0\u67E5\u70B9\u3002"
6447
+ }
6448
+ ]);
6449
+ setInput("");
6450
+ return;
6451
+ }
6452
+ const parts = trimmed.split(/\s+/);
6453
+ if (parts.length >= 2) {
6454
+ const n = Number(parts[1]);
6455
+ if (!Number.isInteger(n) || n < 1 || n > cps.length) {
6456
+ setDisplayMessages((prev) => [
6457
+ ...prev,
6458
+ { role: "user", content: trimmed },
6459
+ {
6460
+ role: "assistant",
6461
+ content: `\u26A0 \u65E0\u6548\u7684\u5E8F\u53F7\u300C${parts[1]}\u300D\u3002\u53EF\u7528\u8303\u56F4 1~${cps.length}\uFF081 \u8868\u793A\u6700\u65B0\uFF09\u3002`
6462
+ }
6463
+ ]);
6464
+ setInput("");
6465
+ return;
6466
+ }
6467
+ const target = cps[cps.length - n];
6468
+ setInput("");
6469
+ await doRewind(target, n);
6470
+ return;
6471
+ }
6472
+ setRewindList([...cps].reverse());
6473
+ setRewindSelectIndex(0);
6474
+ setRewindSelecting(true);
4905
6475
  setDisplayMessages((prev) => [
4906
6476
  ...prev,
4907
6477
  { role: "user", content: trimmed },
4908
- { role: "assistant", content: `\u26A0 \u6B63\u5728${reason}\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5 /rewind\u3002` }
6478
+ {
6479
+ role: "assistant",
6480
+ content: `\u23F7\u2191\u2193 \u9009\u62E9\u68C0\u67E5\u70B9\uFF0CEnter \u786E\u8BA4\uFF0CEsc \u53D6\u6D88\uFF08\u5171 ${cps.length} \u4E2A\u53EF\u56DE\u9000\u4F4D\u7F6E\uFF09`
6481
+ }
4909
6482
  ]);
4910
6483
  setInput("");
4911
6484
  return;
4912
6485
  }
4913
- if (!sessionRef.current) {
6486
+ if (cmdLower === "/model") {
6487
+ const curIdx = modelOptions.indexOf(activeModel);
6488
+ setModelSelectIndex(curIdx >= 0 ? curIdx : 0);
6489
+ setSelectingModel(true);
6490
+ setInput("");
6491
+ return;
6492
+ }
6493
+ if (cmdLower === "/thinking") {
6494
+ setThinkingEnabled((prev) => !prev);
4914
6495
  setDisplayMessages((prev) => [
4915
6496
  ...prev,
4916
6497
  { role: "user", content: trimmed },
4917
- { role: "assistant", content: "\u26A0 Session \u672A\u5C31\u7EEA\uFF0C\u65E0\u6CD5\u56DE\u9000\u3002" }
6498
+ {
6499
+ role: "assistant",
6500
+ content: `\u6DF1\u5EA6\u601D\u8003\u5DF2${thinkingEnabled ? "\u5173\u95ED" : "\u5F00\u542F"}`
6501
+ }
4918
6502
  ]);
4919
6503
  setInput("");
4920
6504
  return;
4921
6505
  }
4922
- const cps = sessionRef.current.listCheckpoints();
4923
- if (cps.length === 0) {
6506
+ if (cmdLower === "/effort") {
6507
+ const next = thinkingEffort === "high" ? "max" : "high";
6508
+ setThinkingEffort(next);
4924
6509
  setDisplayMessages((prev) => [
4925
6510
  ...prev,
4926
6511
  { role: "user", content: trimmed },
4927
- { role: "assistant", content: "\u26A0 \u6CA1\u6709\u53EF\u56DE\u9000\u7684\u68C0\u67E5\u70B9\u3002\n\u53EA\u6709\u5728 git \u4ED3\u5E93\u5185\u4E14\u53D1\u751F\u8FC7\u5BF9\u8BDD\u540E\u624D\u4F1A\u751F\u6210\u68C0\u67E5\u70B9\u3002" }
6512
+ {
6513
+ role: "assistant",
6514
+ content: `\u63A8\u7406\u7B49\u7EA7\u5DF2\u5207\u6362\u4E3A ${next === "high" ? "High" : "Max"}`
6515
+ }
4928
6516
  ]);
4929
6517
  setInput("");
4930
6518
  return;
4931
6519
  }
4932
- const parts = trimmed.split(/\s+/);
4933
- if (parts.length >= 2) {
4934
- const n = Number(parts[1]);
4935
- if (!Number.isInteger(n) || n < 1 || n > cps.length) {
6520
+ if (cmdLower === "/plan") {
6521
+ if (sessionMode === "plan") {
4936
6522
  setDisplayMessages((prev) => [
4937
6523
  ...prev,
4938
6524
  { role: "user", content: trimmed },
4939
- { role: "assistant", content: `\u26A0 \u65E0\u6548\u7684\u5E8F\u53F7\u300C${parts[1]}\u300D\u3002\u53EF\u7528\u8303\u56F4 1~${cps.length}\uFF081 \u8868\u793A\u6700\u65B0\uFF09\u3002` }
6525
+ {
6526
+ role: "assistant",
6527
+ content: "\u5DF2\u7ECF\u5728\u8BA1\u5212\u6A21\u5F0F\u4E2D\u3002\u8F93\u5165 /code \u5207\u56DE\u4EE3\u7801\u6A21\u5F0F\u3002"
6528
+ }
4940
6529
  ]);
4941
- setInput("");
4942
- return;
6530
+ } else {
6531
+ setSessionMode("plan");
6532
+ sessionRef.current?.setMode("plan");
6533
+ setToolChoice(void 0);
6534
+ setDisplayMessages((prev) => [
6535
+ ...prev,
6536
+ { role: "user", content: trimmed },
6537
+ {
6538
+ role: "assistant",
6539
+ content: "\u{1F4CB} \u5DF2\u5207\u6362\u4E3A **\u8BA1\u5212\u6A21\u5F0F**\n\n\u5728\u6B64\u6A21\u5F0F\u4E0B\uFF0C\u6211\u53EA\u80FD\u8BFB\u53D6\u548C\u5206\u6790\u4EE3\u7801\uFF0C\u4E0D\u4F1A\u6267\u884C\u4EFB\u4F55\u4FEE\u6539\u3002\n\u8F93\u5165 /code \u5207\u56DE\u4EE3\u7801\u6A21\u5F0F\u3002"
6540
+ }
6541
+ ]);
6542
+ sessionRef.current?.reset();
4943
6543
  }
4944
- const target = cps[cps.length - n];
4945
6544
  setInput("");
4946
- await doRewind(target, n);
4947
6545
  return;
4948
6546
  }
4949
- setRewindList([...cps].reverse());
4950
- setRewindSelectIndex(0);
4951
- setRewindSelecting(true);
4952
- setDisplayMessages((prev) => [
4953
- ...prev,
4954
- { role: "user", content: trimmed },
4955
- { role: "assistant", content: `\u23F7\u2191\u2193 \u9009\u62E9\u68C0\u67E5\u70B9\uFF0CEnter \u786E\u8BA4\uFF0CEsc \u53D6\u6D88\uFF08\u5171 ${cps.length} \u4E2A\u53EF\u56DE\u9000\u4F4D\u7F6E\uFF09` }
4956
- ]);
4957
- setInput("");
4958
- return;
4959
- }
4960
- if (cmdLower === "/model") {
4961
- const curIdx = modelOptions.indexOf(activeModel);
4962
- setModelSelectIndex(curIdx >= 0 ? curIdx : 0);
4963
- setSelectingModel(true);
4964
- setInput("");
4965
- return;
4966
- }
4967
- if (cmdLower === "/thinking") {
4968
- setThinkingEnabled((prev) => !prev);
4969
- setDisplayMessages((prev) => [
4970
- ...prev,
4971
- { role: "user", content: trimmed },
4972
- { role: "assistant", content: `\u6DF1\u5EA6\u601D\u8003\u5DF2${thinkingEnabled ? "\u5173\u95ED" : "\u5F00\u542F"}` }
4973
- ]);
4974
- setInput("");
4975
- return;
4976
- }
4977
- if (cmdLower === "/effort") {
4978
- const next = thinkingEffort === "high" ? "max" : "high";
4979
- setThinkingEffort(next);
4980
- setDisplayMessages((prev) => [
4981
- ...prev,
4982
- { role: "user", content: trimmed },
4983
- { role: "assistant", content: `\u63A8\u7406\u7B49\u7EA7\u5DF2\u5207\u6362\u4E3A ${next === "high" ? "High" : "Max"}` }
4984
- ]);
4985
- setInput("");
4986
- return;
4987
- }
4988
- if (cmdLower === "/plan") {
4989
- if (sessionMode === "plan") {
4990
- setDisplayMessages((prev) => [
4991
- ...prev,
4992
- { role: "user", content: trimmed },
4993
- { role: "assistant", content: "\u5DF2\u7ECF\u5728\u8BA1\u5212\u6A21\u5F0F\u4E2D\u3002\u8F93\u5165 /code \u5207\u56DE\u4EE3\u7801\u6A21\u5F0F\u3002" }
4994
- ]);
4995
- } else {
4996
- setSessionMode("plan");
4997
- sessionRef.current?.setMode("plan");
4998
- setToolChoice(void 0);
4999
- setDisplayMessages((prev) => [
5000
- ...prev,
5001
- { role: "user", content: trimmed },
5002
- { role: "assistant", content: "\u{1F4CB} \u5DF2\u5207\u6362\u4E3A **\u8BA1\u5212\u6A21\u5F0F**\n\n\u5728\u6B64\u6A21\u5F0F\u4E0B\uFF0C\u6211\u53EA\u80FD\u8BFB\u53D6\u548C\u5206\u6790\u4EE3\u7801\uFF0C\u4E0D\u4F1A\u6267\u884C\u4EFB\u4F55\u4FEE\u6539\u3002\n\u8F93\u5165 /code \u5207\u56DE\u4EE3\u7801\u6A21\u5F0F\u3002" }
5003
- ]);
5004
- sessionRef.current?.reset();
6547
+ if (cmdLower === "/code") {
6548
+ if (sessionMode === "code") {
6549
+ setDisplayMessages((prev) => [
6550
+ ...prev,
6551
+ { role: "user", content: trimmed },
6552
+ {
6553
+ role: "assistant",
6554
+ content: "\u5DF2\u7ECF\u5728\u4EE3\u7801\u6A21\u5F0F\u4E2D\u3002\u8F93\u5165 /plan \u5207\u56DE\u8BA1\u5212\u6A21\u5F0F\u3002"
6555
+ }
6556
+ ]);
6557
+ } else {
6558
+ setSessionMode("code");
6559
+ sessionRef.current?.setMode("code");
6560
+ setToolChoice(void 0);
6561
+ setDisplayMessages((prev) => [
6562
+ ...prev,
6563
+ { role: "user", content: trimmed },
6564
+ {
6565
+ role: "assistant",
6566
+ content: "\u{1F6E0} \u5DF2\u5207\u6362\u4E3A **\u4EE3\u7801\u6A21\u5F0F**\n\n\u73B0\u5728\u53EF\u4EE5\u6B63\u5E38\u8BFB\u53D6\u548C\u4FEE\u6539\u4EE3\u7801\u4E86\u3002\n\u8F93\u5165 /plan \u5207\u6362\u4E3A\u8BA1\u5212\u6A21\u5F0F\uFF08\u53EA\u8BFB\u5206\u6790\uFF09\u3002"
6567
+ }
6568
+ ]);
6569
+ sessionRef.current?.reset();
6570
+ }
6571
+ setInput("");
6572
+ return;
5005
6573
  }
5006
- setInput("");
5007
- return;
5008
- }
5009
- if (cmdLower === "/code") {
5010
- if (sessionMode === "code") {
5011
- setDisplayMessages((prev) => [
5012
- ...prev,
5013
- { role: "user", content: trimmed },
5014
- { role: "assistant", content: "\u5DF2\u7ECF\u5728\u4EE3\u7801\u6A21\u5F0F\u4E2D\u3002\u8F93\u5165 /plan \u5207\u56DE\u8BA1\u5212\u6A21\u5F0F\u3002" }
5015
- ]);
5016
- } else {
5017
- setSessionMode("code");
5018
- sessionRef.current?.setMode("code");
5019
- setToolChoice(void 0);
6574
+ if (cmdLower === "/tools") {
6575
+ const next = toolChoice === void 0 ? "none" : toolChoice === "none" ? "required" : toolChoice === "required" ? "auto" : void 0;
6576
+ setToolChoice(next);
6577
+ const label = next === void 0 ? "\u81EA\u52A8\uFF08\u9ED8\u8BA4\uFF09" : next === "none" ? "\u7981\u6B62\u8C03\u7528" : next === "required" ? "\u5F3A\u5236\u8C03\u7528" : "\u81EA\u52A8\uFF08\u9ED8\u8BA4\uFF09";
5020
6578
  setDisplayMessages((prev) => [
5021
6579
  ...prev,
5022
6580
  { role: "user", content: trimmed },
5023
- { role: "assistant", content: "\u{1F6E0} \u5DF2\u5207\u6362\u4E3A **\u4EE3\u7801\u6A21\u5F0F**\n\n\u73B0\u5728\u53EF\u4EE5\u6B63\u5E38\u8BFB\u53D6\u548C\u4FEE\u6539\u4EE3\u7801\u4E86\u3002\n\u8F93\u5165 /plan \u5207\u6362\u4E3A\u8BA1\u5212\u6A21\u5F0F\uFF08\u53EA\u8BFB\u5206\u6790\uFF09\u3002" }
6581
+ { role: "assistant", content: `\u5DE5\u5177\u8C03\u7528\u7B56\u7565\u5DF2\u5207\u6362\u4E3A ${label}` }
5024
6582
  ]);
5025
- sessionRef.current?.reset();
6583
+ setInput("");
6584
+ return;
6585
+ }
6586
+ const cmd = commandRegistry.get(cmdLower);
6587
+ if (cmd) {
6588
+ const result = cmd.handler();
6589
+ switch (result.kind) {
6590
+ case "exit":
6591
+ await sessionRef.current?.flushLog();
6592
+ process.exit(0);
6593
+ return;
6594
+ case "clear":
6595
+ setDisplayMessages([]);
6596
+ setStaticKey((prev) => prev + 1);
6597
+ setInput("");
6598
+ setStreamError(void 0);
6599
+ process.stdout.write("\x1B[2J\x1B[H\x1B[3J");
6600
+ sessionRef.current?.reset();
6601
+ return;
6602
+ case "navigate":
6603
+ setInput("");
6604
+ if (result.target === "game") {
6605
+ onLaunchGame?.();
6606
+ } else if (result.target === "stock") {
6607
+ onLaunchStock?.();
6608
+ }
6609
+ return;
6610
+ case "text":
6611
+ setDisplayMessages((prev) => [
6612
+ ...prev,
6613
+ { role: "user", content: trimmed },
6614
+ { role: "assistant", content: result.content }
6615
+ ]);
6616
+ setInput("");
6617
+ return;
6618
+ }
5026
6619
  }
6620
+ setDisplayMessages((prev) => [
6621
+ ...prev,
6622
+ { role: "user", content: trimmed },
6623
+ { role: "assistant", content: `\u672A\u77E5\u547D\u4EE4\uFF1A${trimmed}\u3002\u8F93\u5165 /help \u67E5\u770B\u3002` }
6624
+ ]);
5027
6625
  setInput("");
5028
6626
  return;
5029
6627
  }
5030
- if (cmdLower === "/tools") {
5031
- const next = toolChoice === void 0 ? "none" : toolChoice === "none" ? "required" : toolChoice === "required" ? "auto" : void 0;
5032
- setToolChoice(next);
5033
- const label = next === void 0 ? "\u81EA\u52A8\uFF08\u9ED8\u8BA4\uFF09" : next === "none" ? "\u7981\u6B62\u8C03\u7528" : next === "required" ? "\u5F3A\u5236\u8C03\u7528" : "\u81EA\u52A8\uFF08\u9ED8\u8BA4\uFF09";
6628
+ if (!sessionRef.current) {
5034
6629
  setDisplayMessages((prev) => [
5035
6630
  ...prev,
5036
6631
  { role: "user", content: trimmed },
5037
- { role: "assistant", content: `\u5DE5\u5177\u8C03\u7528\u7B56\u7565\u5DF2\u5207\u6362\u4E3A ${label}` }
6632
+ {
6633
+ role: "assistant",
6634
+ content: "\u26A0 \u65E0\u6CD5\u8FDE\u63A5\u5230 Provider\u3002\u8BF7\u68C0\u67E5 API Key \u548C\u7F51\u7EDC\u914D\u7F6E\u3002"
6635
+ }
5038
6636
  ]);
5039
6637
  setInput("");
5040
6638
  return;
5041
6639
  }
5042
- const cmd = commandRegistry.get(cmdLower);
5043
- if (cmd) {
5044
- const result = cmd.handler();
5045
- switch (result.kind) {
5046
- case "exit":
5047
- await sessionRef.current?.flushLog();
5048
- process.exit(0);
5049
- return;
5050
- case "clear":
5051
- setDisplayMessages([]);
5052
- setStaticKey((prev) => prev + 1);
5053
- setInput("");
5054
- setStreamError(void 0);
5055
- process.stdout.write("\x1B[2J\x1B[H\x1B[3J");
5056
- sessionRef.current?.reset();
5057
- return;
5058
- case "navigate":
5059
- setInput("");
5060
- if (result.target === "game") {
5061
- onLaunchGame?.();
5062
- } else if (result.target === "stock") {
5063
- onLaunchStock?.();
5064
- }
5065
- return;
5066
- case "text":
5067
- setDisplayMessages((prev) => [
5068
- ...prev,
5069
- { role: "user", content: trimmed },
5070
- { role: "assistant", content: result.content }
5071
- ]);
5072
- setInput("");
5073
- return;
5074
- }
5075
- }
5076
- setDisplayMessages((prev) => [
5077
- ...prev,
5078
- { role: "user", content: trimmed },
5079
- { role: "assistant", content: `\u672A\u77E5\u547D\u4EE4\uFF1A${trimmed}\u3002\u8F93\u5165 /help \u67E5\u770B\u3002` }
5080
- ]);
5081
- setInput("");
5082
- return;
5083
- }
5084
- if (!sessionRef.current) {
5085
- setDisplayMessages((prev) => [
5086
- ...prev,
5087
- { role: "user", content: trimmed },
5088
- { role: "assistant", content: "\u26A0 \u65E0\u6CD5\u8FDE\u63A5\u5230 Provider\u3002\u8BF7\u68C0\u67E5 API Key \u548C\u7F51\u7EDC\u914D\u7F6E\u3002" }
5089
- ]);
6640
+ setDisplayMessages((prev) => [...prev, { role: "user", content: trimmed }]);
5090
6641
  setInput("");
5091
- return;
5092
- }
5093
- setDisplayMessages((prev) => [
5094
- ...prev,
5095
- { role: "user", content: trimmed }
5096
- ]);
5097
- setInput("");
5098
- setIsStreaming(true);
5099
- setStreamingPhase("thinking");
5100
- setStreamingPlaceholder(pickRandom(STREAMING_PLACEHOLDERS));
5101
- setCurrentContent("");
5102
- setCurrentToolCalls([]);
5103
- setCurrentUsage(void 0);
5104
- setCurrentElapsed(void 0);
5105
- setCurrentCost(void 0);
5106
- setStreamingModel(void 0);
5107
- setStreamError(void 0);
5108
- currentContentRef.current = "";
5109
- currentToolCallsRef.current = [];
5110
- currentUsageRef.current = void 0;
5111
- currentElapsedRef.current = void 0;
5112
- currentCostRef.current = void 0;
5113
- currentModelRef.current = void 0;
5114
- streamErrorRef.current = void 0;
5115
- currentRoundModifiedRef.current = false;
5116
- setRewindHintPhase("idle");
5117
- if (rewindHintTimerRef.current) {
5118
- clearTimeout(rewindHintTimerRef.current);
5119
- rewindHintTimerRef.current = null;
5120
- }
5121
- const session = sessionRef.current;
5122
- const abortController = new AbortController();
5123
- abortRef.current = abortController;
5124
- try {
5125
- for await (const event of session.chat(trimmed, {
5126
- thinkingAllowed: thinkingEnabled || void 0,
5127
- thinkingEffort: thinkingEnabled ? thinkingEffort : void 0,
5128
- responseFormat: responseFormat !== "text" ? responseFormat : void 0,
5129
- toolChoice
5130
- })) {
5131
- if (abortController.signal.aborted) break;
5132
- switch (event.type) {
5133
- case "text_delta":
5134
- setStreamingPhase("generating");
5135
- setCurrentContent((prev) => {
5136
- const next = prev + event.content;
5137
- currentContentRef.current = next;
5138
- return next;
5139
- });
5140
- break;
5141
- case "tool_calls":
5142
- setStreamingPhase("calling_tools");
5143
- setCurrentToolCalls((prev) => {
5144
- const next = [...prev, ...event.calls];
5145
- currentToolCallsRef.current = next;
5146
- return next;
5147
- });
5148
- for (const call of event.calls) {
5149
- if (isFileMutatingTool(call.name)) {
5150
- currentRoundModifiedRef.current = true;
5151
- break;
6642
+ setIsStreaming(true);
6643
+ setStreamingPhase("thinking");
6644
+ setStreamingPlaceholder(pickRandom(STREAMING_PLACEHOLDERS));
6645
+ setCurrentContent("");
6646
+ setCurrentReasoning([]);
6647
+ setCurrentToolCalls([]);
6648
+ setCurrentUsage(void 0);
6649
+ setCurrentElapsed(void 0);
6650
+ setCurrentCost(void 0);
6651
+ setStreamingModel(void 0);
6652
+ setStreamError(void 0);
6653
+ currentContentRef.current = "";
6654
+ currentReasoningRef.current = [];
6655
+ currentToolCallsRef.current = [];
6656
+ currentUsageRef.current = void 0;
6657
+ currentElapsedRef.current = void 0;
6658
+ currentCostRef.current = void 0;
6659
+ currentModelRef.current = void 0;
6660
+ streamErrorRef.current = void 0;
6661
+ currentRoundModifiedRef.current = false;
6662
+ setRewindHintPhase("idle");
6663
+ if (rewindHintTimerRef.current) {
6664
+ clearTimeout(rewindHintTimerRef.current);
6665
+ rewindHintTimerRef.current = null;
6666
+ }
6667
+ const session = sessionRef.current;
6668
+ const abortController = new AbortController();
6669
+ abortRef.current = abortController;
6670
+ try {
6671
+ for await (const event of session.chat(trimmed, {
6672
+ thinkingAllowed: thinkingEnabled || void 0,
6673
+ thinkingEffort: thinkingEnabled ? thinkingEffort : void 0,
6674
+ responseFormat: responseFormat !== "text" ? responseFormat : void 0,
6675
+ toolChoice
6676
+ })) {
6677
+ if (abortController.signal.aborted) break;
6678
+ switch (event.type) {
6679
+ case "text_delta":
6680
+ setStreamingPhase("generating");
6681
+ setCurrentContent((prev) => {
6682
+ const next = prev + event.content;
6683
+ currentContentRef.current = next;
6684
+ return next;
6685
+ });
6686
+ break;
6687
+ case "reasoning_delta":
6688
+ setStreamingPhase("thinking");
6689
+ setCurrentReasoning((prev) => {
6690
+ const last = prev[prev.length - 1];
6691
+ const next = last !== void 0 ? [...prev.slice(0, -1), last + event.content] : [...prev, event.content];
6692
+ currentReasoningRef.current = next;
6693
+ return next;
6694
+ });
6695
+ break;
6696
+ case "tool_calls":
6697
+ setStreamingPhase("calling_tools");
6698
+ setCurrentToolCalls((prev) => {
6699
+ const next = [...prev, ...event.calls];
6700
+ currentToolCallsRef.current = next;
6701
+ return next;
6702
+ });
6703
+ for (const call of event.calls) {
6704
+ if (isFileMutatingTool(call.name)) {
6705
+ currentRoundModifiedRef.current = true;
6706
+ break;
6707
+ }
5152
6708
  }
5153
- }
5154
- break;
5155
- case "tool_result":
5156
- setStreamingPhase("executing_tools");
5157
- setTimeout(() => setStreamingPhase("thinking"), 300);
5158
- setCurrentContent("");
5159
- currentContentRef.current = "";
5160
- setCurrentToolCalls([]);
5161
- currentToolCallsRef.current = [];
5162
- const r = event.result;
5163
- const line = r.success ? r.summary ?? `\u2705 ${event.name}: ${r.data.slice(0, 500)}${r.data.length > 500 ? "..." : ""}` : `\u274C ${event.name}: ${r.error ?? "\u6267\u884C\u5931\u8D25"}`;
5164
- setDisplayMessages((prev) => [
5165
- ...prev,
6709
+ break;
6710
+ case "tool_result":
6711
+ setStreamingPhase("executing_tools");
6712
+ setTimeout(() => setStreamingPhase("thinking"), 300);
6713
+ setCurrentContent("");
6714
+ currentContentRef.current = "";
6715
+ setCurrentReasoning((prev) => {
6716
+ const next = [...prev, ""];
6717
+ currentReasoningRef.current = next;
6718
+ return next;
6719
+ });
6720
+ setCurrentToolCalls([]);
6721
+ currentToolCallsRef.current = [];
6722
+ const r = event.result;
6723
+ if (event.name.startsWith("todo_") && event.todoSnapshot) {
6724
+ setTodoSnapshot(event.todoSnapshot);
6725
+ } else {
6726
+ const line = r.success ? r.summary ?? `\u2705 ${event.name}: ${r.data.slice(0, 500)}${r.data.length > 500 ? "..." : ""}` : `\u274C ${event.name}: ${r.error ?? "\u6267\u884C\u5931\u8D25"}`;
6727
+ setDisplayMessages((prev) => [
6728
+ ...prev,
6729
+ {
6730
+ role: "tool",
6731
+ content: line,
6732
+ diff: r.diff
6733
+ }
6734
+ ]);
6735
+ }
6736
+ break;
6737
+ case "usage":
6738
+ setCurrentUsage(event.usage);
6739
+ setStreamingModel(event.model);
6740
+ currentUsageRef.current = event.usage;
6741
+ currentModelRef.current = event.model;
5166
6742
  {
5167
- role: "tool",
5168
- content: line,
5169
- diff: r.diff
6743
+ const cost = calculateCost(
6744
+ event.usage,
6745
+ event.model
6746
+ );
6747
+ setCurrentCost(cost.totalCost);
6748
+ currentCostRef.current = cost.totalCost;
5170
6749
  }
5171
- ]);
5172
- break;
5173
- case "usage":
5174
- setCurrentUsage(event.usage);
5175
- setStreamingModel(event.model);
5176
- currentUsageRef.current = event.usage;
5177
- currentModelRef.current = event.model;
6750
+ break;
6751
+ case "done":
6752
+ setCurrentElapsed(event.elapsed);
6753
+ currentElapsedRef.current = event.elapsed;
6754
+ break;
6755
+ case "error":
6756
+ setStreamError(event.error.message);
6757
+ streamErrorRef.current = event.error.message;
6758
+ break;
6759
+ }
6760
+ }
6761
+ } catch (err) {
6762
+ const msg = err instanceof Error ? err.message : String(err);
6763
+ setStreamError(msg);
6764
+ streamErrorRef.current = msg;
6765
+ } finally {
6766
+ setIsStreaming(false);
6767
+ setStreamingPhase(null);
6768
+ setIdlePlaceholder(pickRandom(IDLE_PLACEHOLDERS));
6769
+ abortRef.current = null;
6770
+ const finContent = currentContentRef.current;
6771
+ const finReasoning = currentReasoningRef.current.map((s) => s.trim()).filter((s) => s.length > 0);
6772
+ const finToolCalls = currentToolCallsRef.current.length > 0 ? currentToolCallsRef.current : void 0;
6773
+ const finStreamError = streamErrorRef.current;
6774
+ if (finContent || finToolCalls || finStreamError) {
6775
+ const completed = {
6776
+ content: finStreamError ? `\u26A0 \u8BF7\u6C42\u51FA\u9519\uFF1A${finStreamError}` : finContent || "",
6777
+ ...finReasoning.length > 0 ? { reasoning: finReasoning } : {},
6778
+ toolCalls: finToolCalls,
6779
+ usage: currentUsageRef.current,
6780
+ elapsed: currentElapsedRef.current,
6781
+ cost: currentCostRef.current,
6782
+ model: currentModelRef.current
6783
+ };
6784
+ setDisplayMessages((prev) => [
6785
+ ...prev,
5178
6786
  {
5179
- const cost = calculateCost(event.usage, event.model);
5180
- setCurrentCost(cost.totalCost);
5181
- currentCostRef.current = cost.totalCost;
6787
+ role: "assistant",
6788
+ content: completed.content,
6789
+ assistantDetail: completed
5182
6790
  }
5183
- break;
5184
- case "done":
5185
- setCurrentElapsed(event.elapsed);
5186
- currentElapsedRef.current = event.elapsed;
5187
- break;
5188
- case "error":
5189
- setStreamError(event.error.message);
5190
- streamErrorRef.current = event.error.message;
5191
- break;
6791
+ ]);
6792
+ }
6793
+ if (currentRoundModifiedRef.current && !finStreamError) {
6794
+ setRewindHintPhase("pending");
6795
+ if (rewindHintTimerRef.current) clearTimeout(rewindHintTimerRef.current);
6796
+ rewindHintTimerRef.current = setTimeout(() => {
6797
+ setRewindHintPhase((prev) => prev === "pending" ? "visible" : prev);
6798
+ rewindHintTimerRef.current = null;
6799
+ }, 2e3);
5192
6800
  }
5193
6801
  }
5194
- } catch (err) {
5195
- const msg = err instanceof Error ? err.message : String(err);
5196
- setStreamError(msg);
5197
- streamErrorRef.current = msg;
5198
- } finally {
5199
- setIsStreaming(false);
5200
- setStreamingPhase(null);
5201
- setIdlePlaceholder(pickRandom(IDLE_PLACEHOLDERS));
5202
- abortRef.current = null;
5203
- const finContent = currentContentRef.current;
5204
- const finToolCalls = currentToolCallsRef.current.length > 0 ? currentToolCallsRef.current : void 0;
5205
- const finStreamError = streamErrorRef.current;
5206
- if (finContent || finToolCalls || finStreamError) {
5207
- const completed = {
5208
- content: finStreamError ? `\u26A0 \u8BF7\u6C42\u51FA\u9519\uFF1A${finStreamError}` : finContent || "",
5209
- toolCalls: finToolCalls,
5210
- usage: currentUsageRef.current,
5211
- elapsed: currentElapsedRef.current,
5212
- cost: currentCostRef.current,
5213
- model: currentModelRef.current
5214
- };
5215
- setDisplayMessages((prev) => [
5216
- ...prev,
5217
- {
5218
- role: "assistant",
5219
- content: completed.content,
5220
- assistantDetail: completed
5221
- }
5222
- ]);
5223
- }
5224
- if (currentRoundModifiedRef.current && !finStreamError) {
5225
- setRewindHintPhase("pending");
5226
- if (rewindHintTimerRef.current) clearTimeout(rewindHintTimerRef.current);
5227
- rewindHintTimerRef.current = setTimeout(() => {
5228
- setRewindHintPhase((prev) => prev === "pending" ? "visible" : prev);
5229
- rewindHintTimerRef.current = null;
5230
- }, 2e3);
5231
- }
5232
- }
5233
- }, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls, skills, skillSelectIndex, getFilteredSkills, thinkingEnabled, thinkingEffort, responseFormat, toolChoice, activeModel, sessionMode, isStreaming, rewinding]);
5234
- useEffect3(() => {
6802
+ },
6803
+ [
6804
+ onLaunchGame,
6805
+ onLaunchStock,
6806
+ currentContent,
6807
+ currentReasoning,
6808
+ currentToolCalls,
6809
+ skills,
6810
+ skillSelectIndex,
6811
+ getFilteredSkills,
6812
+ thinkingEnabled,
6813
+ thinkingEffort,
6814
+ responseFormat,
6815
+ toolChoice,
6816
+ activeModel,
6817
+ sessionMode,
6818
+ isStreaming,
6819
+ rewinding
6820
+ ]
6821
+ );
6822
+ useEffect4(() => {
5235
6823
  if (!isStreaming && externalCostTracker) {
5236
6824
  setTodayCost(externalCostTracker.todayTotalCost);
5237
6825
  }
5238
6826
  }, [isStreaming, externalCostTracker]);
5239
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
5240
- !hasConversationStarted && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", marginBottom: 1, children: [
5241
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
6827
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, children: [
6828
+ !hasConversationStarted && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "row", marginBottom: 1, children: [
6829
+ /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", marginRight: 4, children: LOGO_LINES.map((line, i) => {
5242
6830
  const colorIndex = (i + offset) % CYBER_PALETTE.length;
5243
- return /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
6831
+ return /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsx10(Text11, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
5244
6832
  }) }),
5245
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", justifyContent: "center", children: [
5246
- /* @__PURE__ */ jsxs9(Text10, { color: "#00ff41", children: [
6833
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", justifyContent: "center", children: [
6834
+ /* @__PURE__ */ jsxs10(Text11, { color: "#00ff41", children: [
5247
6835
  " \u2714 ",
5248
6836
  "\u5DF2\u5C31\u7EEA ",
5249
6837
  skillCount,
5250
6838
  " \u4E2A Skill"
5251
6839
  ] }),
5252
- /* @__PURE__ */ jsxs9(Text10, { color: "#00ffff", children: [
6840
+ /* @__PURE__ */ jsxs10(Text11, { color: "#00ffff", children: [
5253
6841
  " \u2139 ",
5254
6842
  "\u5DF2\u5C31\u7EEA ",
5255
6843
  toolCount,
5256
6844
  " \u4E2A\u5DE5\u5177"
5257
6845
  ] }),
5258
- /* @__PURE__ */ jsxs9(Text10, { color: "#00ffff", children: [
6846
+ /* @__PURE__ */ jsxs10(Text11, { color: "#00ffff", children: [
5259
6847
  " \u{1F527} \u6A21\u578B ",
5260
6848
  SUPPORTED_MODELS[activeModel]?.displayName ?? activeModel
5261
6849
  ] }),
5262
- thinkingEnabled && /* @__PURE__ */ jsxs9(Text10, { color: "#ff9800", children: [
6850
+ thinkingEnabled && /* @__PURE__ */ jsxs10(Text11, { color: "#ff9800", children: [
5263
6851
  " \u{1F9E0} \u6DF1\u5EA6\u601D\u8003 ",
5264
6852
  thinkingEffort === "max" ? "Max" : "High"
5265
6853
  ] }),
5266
- sessionMode === "plan" && /* @__PURE__ */ jsx9(Text10, { color: "#ff69b4", bold: true, children: " \u{1F4CB} \u8BA1\u5212\u6A21\u5F0F" }),
5267
- responseFormat === "json_object" && /* @__PURE__ */ jsx9(Text10, { color: "#4caf50", children: " \u{1F4C4} JSON" }),
5268
- toolChoice !== void 0 && /* @__PURE__ */ jsxs9(Text10, { color: "#e91e63", children: [
6854
+ sessionMode === "plan" && /* @__PURE__ */ jsx10(Text11, { color: "#ff69b4", bold: true, children: " \u{1F4CB} \u8BA1\u5212\u6A21\u5F0F" }),
6855
+ responseFormat === "json_object" && /* @__PURE__ */ jsx10(Text11, { color: "#4caf50", children: " \u{1F4C4} JSON" }),
6856
+ toolChoice !== void 0 && /* @__PURE__ */ jsxs10(Text11, { color: "#e91e63", children: [
5269
6857
  " \u{1F6E0} ",
5270
6858
  toolChoice === "none" ? "\u7981\u6B62\u5DE5\u5177" : toolChoice === "required" ? "\u5F3A\u5236\u5DE5\u5177" : ""
5271
6859
  ] }),
@@ -5273,52 +6861,62 @@ function ChatSession({
5273
6861
  const tip = cmdTips[cmdTipIndex % cmdTips.length];
5274
6862
  if (!tip) return null;
5275
6863
  const text = `${tip.name} ${tip.desc}`;
5276
- return /* @__PURE__ */ jsxs9(Text10, { children: [
5277
- /* @__PURE__ */ jsx9(Text10, { color: "#808080", children: " \u{1F4A1} " }),
5278
- cmdTipGradientColors.length > 0 ? text.split("").map((ch, i) => /* @__PURE__ */ jsx9(Text10, { color: cmdTipGradientColors[i] || void 0, children: ch }, i)) : /* @__PURE__ */ jsx9(Text10, { color: "#808080", children: text })
6864
+ return /* @__PURE__ */ jsxs10(Text11, { children: [
6865
+ /* @__PURE__ */ jsx10(Text11, { color: "#808080", children: " \u{1F4A1} " }),
6866
+ cmdTipGradientColors.length > 0 ? text.split("").map((ch, i) => /* @__PURE__ */ jsx10(Text11, { color: cmdTipGradientColors[i] || void 0, children: ch }, i)) : /* @__PURE__ */ jsx10(Text11, { color: "#808080", children: text })
5279
6867
  ] });
5280
6868
  })(),
5281
- verbose ? /* @__PURE__ */ jsx9(Text10, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
6869
+ verbose ? /* @__PURE__ */ jsx10(Text11, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
5282
6870
  ] }),
5283
- /* @__PURE__ */ jsxs9(Box9, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "flex-end", children: [
5284
- balanceLoading && balance === null ? /* @__PURE__ */ jsx9(Text10, { color: "yellow", children: " \u23F3 \u67E5\u8BE2\u4F59\u989D..." }) : balance !== null ? /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", children: [
5285
- /* @__PURE__ */ jsx9(Text10, { color: "yellow", children: "\u{1F4B0} " }),
5286
- /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
5287
- "\u4F59\u989D \xA5",
5288
- balance.toFixed(2)
5289
- ] })
5290
- ] }) : null,
5291
- todayCost !== null ? /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", children: [
5292
- /* @__PURE__ */ jsx9(Text10, { color: "cyan", children: "\u{1F4CA} " }),
5293
- /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
5294
- "\u4ECA\u65E5 \xA5",
5295
- todayCost.toFixed(2)
5296
- ] })
5297
- ] }) : null
5298
- ] })
6871
+ /* @__PURE__ */ jsxs10(
6872
+ Box10,
6873
+ {
6874
+ flexGrow: 1,
6875
+ flexDirection: "column",
6876
+ justifyContent: "center",
6877
+ alignItems: "flex-end",
6878
+ children: [
6879
+ balanceLoading && balance === null ? /* @__PURE__ */ jsx10(Text11, { color: "yellow", children: " \u23F3 \u67E5\u8BE2\u4F59\u989D..." }) : balance !== null ? /* @__PURE__ */ jsxs10(Box10, { flexDirection: "row", children: [
6880
+ /* @__PURE__ */ jsx10(Text11, { color: "yellow", children: "\u{1F4B0} " }),
6881
+ /* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
6882
+ "\u4F59\u989D \xA5",
6883
+ balance.toFixed(2)
6884
+ ] })
6885
+ ] }) : null,
6886
+ todayCost !== null ? /* @__PURE__ */ jsxs10(Box10, { flexDirection: "row", children: [
6887
+ /* @__PURE__ */ jsx10(Text11, { color: "cyan", children: "\u{1F4CA} " }),
6888
+ /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
6889
+ "\u4ECA\u65E5 \xA5",
6890
+ todayCost.toFixed(2)
6891
+ ] })
6892
+ ] }) : null
6893
+ ]
6894
+ }
6895
+ )
5299
6896
  ] }),
5300
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, children: [
5301
- /* @__PURE__ */ jsx9(Static, { items: displayMessages, children: (msg, i) => {
6897
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginTop: 1, children: [
6898
+ /* @__PURE__ */ jsx10(Static, { items: displayMessages, children: (msg, i) => {
5302
6899
  if (msg.role === "user") {
5303
- return /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, children: [
5304
- /* @__PURE__ */ jsx9(Box9, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ff41", children: "\u{1F464}" }) }),
5305
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text10, { wrap: "wrap", children: msg.content }) })
6900
+ return /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, children: [
6901
+ /* @__PURE__ */ jsx10(Box10, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx10(Text11, { bold: true, color: "#00ff41", children: "\u{1F464}" }) }),
6902
+ /* @__PURE__ */ jsx10(Box10, { flexGrow: 1, children: /* @__PURE__ */ jsx10(Text11, { wrap: "wrap", children: msg.content }) })
5306
6903
  ] }, i);
5307
6904
  }
5308
6905
  if (msg.role === "tool") {
5309
- return /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
5310
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "row", children: [
5311
- /* @__PURE__ */ jsx9(Box9, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#f59e0b", children: "\u{1F527}" }) }),
5312
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx9(Text10, { wrap: "wrap", children: msg.content }) })
6906
+ return /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
6907
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "row", children: [
6908
+ /* @__PURE__ */ jsx10(Box10, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx10(Text11, { dimColor: true, children: "\u{1F527}" }) }),
6909
+ /* @__PURE__ */ jsx10(Box10, { flexGrow: 1, children: /* @__PURE__ */ jsx10(Text11, { dimColor: true, wrap: "wrap", children: msg.content }) })
5313
6910
  ] }),
5314
- msg.diff && /* @__PURE__ */ jsx9(DiffPreview, { diff: msg.diff })
6911
+ msg.diff && /* @__PURE__ */ jsx10(DiffPreview, { diff: msg.diff })
5315
6912
  ] }, i);
5316
6913
  }
5317
6914
  const detail = msg.assistantDetail;
5318
- return /* @__PURE__ */ jsx9(
6915
+ return /* @__PURE__ */ jsx10(
5319
6916
  AssistantMessage,
5320
6917
  {
5321
6918
  content: msg.content,
6919
+ reasoning: detail?.reasoning,
5322
6920
  toolCalls: detail?.toolCalls,
5323
6921
  isStreaming: false,
5324
6922
  usage: detail?.usage,
@@ -5329,114 +6927,112 @@ function ChatSession({
5329
6927
  i
5330
6928
  );
5331
6929
  } }, staticKey),
5332
- isStreaming && /* @__PURE__ */ jsx9(
6930
+ isStreaming && /* @__PURE__ */ jsx10(
5333
6931
  AssistantMessage,
5334
6932
  {
5335
6933
  content: currentContent,
6934
+ reasoning: currentReasoning,
5336
6935
  toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
5337
- isStreaming: true
6936
+ isStreaming: true,
6937
+ usage: _currentUsage,
6938
+ cost: _currentCost,
6939
+ model: _streamingModel
5338
6940
  }
5339
6941
  ),
5340
- !isStreaming && streamError && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs9(Text10, { color: "red", children: [
6942
+ !isStreaming && streamError && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs10(Text11, { color: "red", children: [
5341
6943
  "\u26A0 ",
5342
6944
  streamError
5343
6945
  ] }) })
5344
6946
  ] }),
5345
- selectingModel ? /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
5346
- /* @__PURE__ */ jsx9(Text10, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
5347
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, children: [
5348
- /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ffff", children: "\u9009\u62E9\u6A21\u578B\uFF1A" }),
6947
+ todoSnapshot.length > 0 && /* @__PURE__ */ jsx10(TodoListPanel, { items: todoSnapshot }),
6948
+ selectingModel ? /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
6949
+ /* @__PURE__ */ jsx10(Text11, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
6950
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginTop: 1, children: [
6951
+ /* @__PURE__ */ jsx10(Text11, { bold: true, color: "#00ffff", children: "\u9009\u62E9\u6A21\u578B\uFF1A" }),
5349
6952
  modelOptions.map((id, i) => {
5350
6953
  const meta = SUPPORTED_MODELS[id];
5351
6954
  const isCurrent = id === activeModel;
5352
6955
  const isSelected = i === modelSelectIndex;
5353
6956
  const marker = isSelected ? " > " : " ";
5354
6957
  const suffix = isCurrent ? " (\u5F53\u524D)" : "";
5355
- return /* @__PURE__ */ jsxs9(Box9, { children: [
5356
- /* @__PURE__ */ jsxs9(
5357
- Text10,
5358
- {
5359
- color: isSelected ? "#00ff41" : void 0,
5360
- bold: isSelected,
5361
- children: [
5362
- marker,
5363
- meta.displayName,
5364
- suffix
5365
- ]
5366
- }
5367
- ),
5368
- isSelected && /* @__PURE__ */ jsxs9(Text10, { color: "#808080", children: [
6958
+ return /* @__PURE__ */ jsxs10(Box10, { children: [
6959
+ /* @__PURE__ */ jsxs10(Text11, { color: isSelected ? "#00ff41" : void 0, bold: isSelected, children: [
6960
+ marker,
6961
+ meta.displayName,
6962
+ suffix
6963
+ ] }),
6964
+ isSelected && /* @__PURE__ */ jsxs10(Text11, { color: "#808080", children: [
5369
6965
  " \u2014 ",
5370
6966
  id
5371
6967
  ] })
5372
6968
  ] }, id);
5373
6969
  }),
5374
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
6970
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text11, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
5375
6971
  ] }),
5376
- /* @__PURE__ */ jsx9(Text10, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
5377
- ] }) : rewindSelecting ? /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
5378
- /* @__PURE__ */ jsx9(Text10, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
5379
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginTop: 1, children: [
5380
- /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#ff9800", children: "\u9009\u62E9\u8981\u56DE\u9000\u7684\u68C0\u67E5\u70B9\uFF1A" }),
6972
+ /* @__PURE__ */ jsx10(Text11, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
6973
+ ] }) : rewindSelecting ? /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
6974
+ /* @__PURE__ */ jsx10(Text11, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
6975
+ /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginTop: 1, children: [
6976
+ /* @__PURE__ */ jsx10(Text11, { bold: true, color: "#ff9800", children: "\u9009\u62E9\u8981\u56DE\u9000\u7684\u68C0\u67E5\u70B9\uFF1A" }),
5381
6977
  rewindList.map((cp2, i) => {
5382
6978
  const isSelected = i === rewindSelectIndex;
5383
6979
  const marker = isSelected ? " > " : " ";
5384
6980
  const time = new Date(cp2.timestamp).toLocaleTimeString();
5385
6981
  const preview = cp2.preview || "(\u7A7A)";
5386
6982
  const tag = cp2.isGitRepo ? "" : " [\u975E git\uFF0C\u4EC5\u5BF9\u8BDD]";
5387
- return /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(
5388
- Text10,
5389
- {
5390
- color: isSelected ? "#ff9800" : void 0,
5391
- bold: isSelected,
5392
- children: [
5393
- marker,
5394
- "#",
5395
- i + 1,
5396
- " ",
5397
- time,
5398
- " `",
5399
- preview,
5400
- tag,
5401
- "`"
5402
- ]
5403
- }
5404
- ) }, cp2.index);
6983
+ return /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsxs10(Text11, { color: isSelected ? "#ff9800" : void 0, bold: isSelected, children: [
6984
+ marker,
6985
+ "#",
6986
+ i + 1,
6987
+ " ",
6988
+ time,
6989
+ " `",
6990
+ preview,
6991
+ tag,
6992
+ "`"
6993
+ ] }) }, cp2.index);
5405
6994
  }),
5406
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
6995
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text11, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
5407
6996
  ] }),
5408
- /* @__PURE__ */ jsx9(Text10, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
5409
- ] }) : /* @__PURE__ */ jsxs9(Fragment, { children: [
5410
- (hasConversationStarted || sessionMode === "plan") && isStreaming && streamingPhase ? /* @__PURE__ */ jsx9(Box9, { marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ jsxs9(Text10, { bold: true, color: PHASE_CONFIG[streamingPhase].color, children: [
6997
+ /* @__PURE__ */ jsx10(Text11, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
6998
+ ] }) : /* @__PURE__ */ jsxs10(Fragment2, { children: [
6999
+ (hasConversationStarted || sessionMode === "plan") && isStreaming && streamingPhase ? /* @__PURE__ */ jsx10(Box10, { marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ jsxs10(Text11, { bold: true, color: PHASE_CONFIG[streamingPhase].color, children: [
5411
7000
  PHASE_CONFIG[streamingPhase].icon,
5412
7001
  " ",
5413
7002
  PHASE_CONFIG[streamingPhase].label,
5414
7003
  " ",
5415
- /* @__PURE__ */ jsx9(InkSpinner2, { type: "dots" })
7004
+ /* @__PURE__ */ jsx10(InkSpinner3, { type: "dots" })
5416
7005
  ] }) }) : rewindHintPhase === "visible" ? (
5417
7006
  // 本轮修改了文件时,流式结束后 2s 在原位置展示 /rewind 提示
5418
7007
  // 一直保留到下次对话开始,与 /rewind 1(最新检查点)配套
5419
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ jsx9(Text10, { color: "#808080", children: "\u21A9 /rewind 1 \u53EF\u64A4\u56DE\u672C\u6B21\u4FEE\u6539" }) })
7008
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ jsx10(Text11, { color: "#808080", children: "\u21A9 /rewind 1 \u53EF\u64A4\u56DE\u672C\u6B21\u4FEE\u6539" }) })
5420
7009
  ) : null,
5421
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: hasConversationStarted || sessionMode === "plan" ? /* @__PURE__ */ jsxs9(Text10, { color: sessionMode === "plan" ? "#ff69b4" : "#00ffff", dimColor: sessionMode !== "plan", children: [
5422
- /* @__PURE__ */ jsx9(Text10, { children: sessionMode === "plan" ? "\u2500".repeat(Math.max(dividerWidth - 48, 1)) : "\u2500".repeat(Math.max(dividerWidth - 35, 10)) }),
5423
- balance !== null && /* @__PURE__ */ jsxs9(Text10, { color: "yellow", children: [
5424
- " \u{1F4B0} \u4F59\u989D \xA5",
5425
- balance.toFixed(2)
5426
- ] }),
5427
- isStreaming ? /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
5428
- " \u{1F4CA} \u672C\u6B21 \xA5",
5429
- sessionCost > 0 ? sessionCost.toFixed(4) + " " : "",
5430
- /* @__PURE__ */ jsx9(InkSpinner2, { type: "dots" })
5431
- ] }) : sessionCost > 0 ? /* @__PURE__ */ jsxs9(Text10, { color: "cyan", children: [
5432
- " \u{1F4CA} \u672C\u6B21 \xA5",
5433
- sessionCost.toFixed(4)
5434
- ] }) : null,
5435
- sessionMode === "plan" && /* @__PURE__ */ jsx9(Text10, { color: "#ff69b4", bold: true, children: " \u{1F4CB} \u8BA1\u5212\u6A21\u5F0F" })
5436
- ] }) : /* @__PURE__ */ jsx9(Text10, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
5437
- /* @__PURE__ */ jsxs9(Box9, { children: [
5438
- /* @__PURE__ */ jsx9(Box9, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx9(Text10, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
5439
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, children: !input && !isStreaming && idlePlaceholder && gradientColors.length > 0 ? /* @__PURE__ */ jsx9(Text10, { children: idlePlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx9(Text10, { color: gradientColors[i] ?? void 0, children: ch }, i)) }) : !input && isStreaming && streamingPlaceholder && streamingGradientColors.length > 0 ? /* @__PURE__ */ jsx9(Text10, { children: streamingPlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx9(Text10, { color: streamingGradientColors[i] ?? void 0, children: ch }, i)) }) : /* @__PURE__ */ jsx9(
7010
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: hasConversationStarted || sessionMode === "plan" ? /* @__PURE__ */ jsxs10(
7011
+ Text11,
7012
+ {
7013
+ color: sessionMode === "plan" ? "#ff69b4" : "#00ffff",
7014
+ dimColor: sessionMode !== "plan",
7015
+ children: [
7016
+ /* @__PURE__ */ jsx10(Text11, { children: sessionMode === "plan" ? "\u2500".repeat(Math.max(dividerWidth - 48, 1)) : "\u2500".repeat(Math.max(dividerWidth - 35, 10)) }),
7017
+ balance !== null && /* @__PURE__ */ jsxs10(Text11, { color: "yellow", children: [
7018
+ " \u{1F4B0} \u4F59\u989D \xA5",
7019
+ balance.toFixed(2)
7020
+ ] }),
7021
+ isStreaming ? /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
7022
+ " \u{1F4CA} \u672C\u6B21 \xA5",
7023
+ sessionCost > 0 ? sessionCost.toFixed(4) + " " : "",
7024
+ /* @__PURE__ */ jsx10(InkSpinner3, { type: "dots" })
7025
+ ] }) : sessionCost > 0 ? /* @__PURE__ */ jsxs10(Text11, { color: "cyan", children: [
7026
+ " \u{1F4CA} \u672C\u6B21 \xA5",
7027
+ sessionCost.toFixed(4)
7028
+ ] }) : null,
7029
+ sessionMode === "plan" && /* @__PURE__ */ jsx10(Text11, { color: "#ff69b4", bold: true, children: " \u{1F4CB} \u8BA1\u5212\u6A21\u5F0F" })
7030
+ ]
7031
+ }
7032
+ ) : /* @__PURE__ */ jsx10(Text11, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
7033
+ /* @__PURE__ */ jsxs10(Box10, { children: [
7034
+ /* @__PURE__ */ jsx10(Box10, { width: 4, flexShrink: 0, children: /* @__PURE__ */ jsx10(Text11, { bold: true, color: "#00ff41", children: "\u26A1" }) }),
7035
+ /* @__PURE__ */ jsx10(Box10, { flexGrow: 1, children: !input && !isStreaming && idlePlaceholder && gradientColors.length > 0 ? /* @__PURE__ */ jsx10(Text11, { children: idlePlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx10(Text11, { color: gradientColors[i] ?? void 0, children: ch }, i)) }) : !input && isStreaming && streamingPlaceholder && streamingGradientColors.length > 0 ? /* @__PURE__ */ jsx10(Text11, { children: streamingPlaceholder.split("").map((ch, i) => /* @__PURE__ */ jsx10(Text11, { color: streamingGradientColors[i] ?? void 0, children: ch }, i)) }) : /* @__PURE__ */ jsx10(
5440
7036
  TextInput,
5441
7037
  {
5442
7038
  value: input,
@@ -5447,21 +7043,21 @@ function ChatSession({
5447
7043
  inputKey
5448
7044
  ) })
5449
7045
  ] }),
5450
- /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsx9(Text10, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
5451
- /* @__PURE__ */ jsx9(SkillSelector, { skills, input, selectedIndex: skillSelectIndex }),
5452
- /* @__PURE__ */ jsx9(FileSelector, { files, input, selectedIndex: fileSelectIndex })
7046
+ /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsx10(Text11, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }) }),
7047
+ /* @__PURE__ */ jsx10(SkillSelector, { skills, input, selectedIndex: skillSelectIndex }),
7048
+ /* @__PURE__ */ jsx10(FileSelector, { files, input, selectedIndex: fileSelectIndex })
5453
7049
  ] }),
5454
- doubleCtrlC && !isStreaming && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) }),
5455
- isStreaming && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { color: "yellow", dimColor: true, children: " \u63D0\u793A\uFF1A\u6309 Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42" }) })
7050
+ doubleCtrlC && !isStreaming && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text11, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) }),
7051
+ isStreaming && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text11, { color: "yellow", dimColor: true, children: " \u63D0\u793A\uFF1A\u6309 Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42" }) })
5456
7052
  ] });
5457
7053
  }
5458
7054
 
5459
7055
  // src/ui/GamePicker.tsx
5460
- import { Box as Box10, Text as Text11, useInput as useInput2 } from "ink";
5461
- import { useState as useState4, useCallback as useCallback3 } from "react";
5462
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
7056
+ import { Box as Box11, Text as Text12, useInput as useInput2 } from "ink";
7057
+ import { useState as useState5, useCallback as useCallback3 } from "react";
7058
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
5463
7059
  function GamePicker({ games, onSelect, onExit, onBackToChat }) {
5464
- const [selectedIndex, setSelectedIndex] = useState4(0);
7060
+ const [selectedIndex, setSelectedIndex] = useState5(0);
5465
7061
  const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
5466
7062
  const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(exitAction);
5467
7063
  useInput2(
@@ -5487,18 +7083,18 @@ function GamePicker({ games, onSelect, onExit, onBackToChat }) {
5487
7083
  [games, selectedIndex, onSelect, onExit, onBackToChat, handleCtrlC]
5488
7084
  )
5489
7085
  );
5490
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
5491
- /* @__PURE__ */ jsx10(Box10, { marginBottom: 1, children: /* @__PURE__ */ jsx10(Text11, { bold: true, color: "#00ffff", children: "\u{1F3AE} \u6E38\u620F\u5217\u8868" }) }),
5492
- /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: games.map((game, index) => {
7086
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
7087
+ /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text12, { bold: true, color: "#00ffff", children: "\u{1F3AE} \u6E38\u620F\u5217\u8868" }) }),
7088
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: games.map((game, index) => {
5493
7089
  const isSelected = index === selectedIndex;
5494
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "row", children: [
5495
- /* @__PURE__ */ jsx10(Box10, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx10(Text11, { bold: true, color: "#00ff41", children: "\u25B8 " }) : /* @__PURE__ */ jsx10(Text11, { children: " " }) }),
5496
- /* @__PURE__ */ jsx10(Box10, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsx10(Text11, { bold: true, color: isSelected ? "#00ff41" : "#ffffff", children: game.name }) }),
5497
- /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsx10(Text11, { color: "#888888", children: game.description }) })
7090
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "row", children: [
7091
+ /* @__PURE__ */ jsx11(Box11, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx11(Text12, { bold: true, color: "#00ff41", children: "\u25B8 " }) : /* @__PURE__ */ jsx11(Text12, { children: " " }) }),
7092
+ /* @__PURE__ */ jsx11(Box11, { width: 20, flexShrink: 0, children: /* @__PURE__ */ jsx11(Text12, { bold: true, color: isSelected ? "#00ff41" : "#ffffff", children: game.name }) }),
7093
+ /* @__PURE__ */ jsx11(Box11, { children: /* @__PURE__ */ jsx11(Text12, { color: "#888888", children: game.description }) })
5498
7094
  ] }, game.id);
5499
7095
  }) }),
5500
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text11, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
5501
- doubleCtrlC && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text11, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
7096
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: " \u2191/\u2193 \u9009\u62E9 Enter \u542F\u52A8 q \u8FD4\u56DE" }) }),
7097
+ doubleCtrlC && /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text12, { color: "#ff1493", bold: true, children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
5502
7098
  ] });
5503
7099
  }
5504
7100
 
@@ -5515,9 +7111,9 @@ function listGames() {
5515
7111
  }
5516
7112
 
5517
7113
  // src/game/brick-breaker/index.tsx
5518
- import { Box as Box11, Text as Text12, useInput as useInput3, render as render2 } from "ink";
5519
- import { useState as useState5, useEffect as useEffect4, useRef as useRef3, useCallback as useCallback4 } from "react";
5520
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
7114
+ import { Box as Box12, Text as Text13, useInput as useInput3, render as render2 } from "ink";
7115
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback4 } from "react";
7116
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
5521
7117
  var GAME_WIDTH = 40;
5522
7118
  var GAME_HEIGHT = 18;
5523
7119
  var PADDLE_WIDTH = 9;
@@ -5648,13 +7244,13 @@ function buildBoard(state) {
5648
7244
  return lines.map((l) => `\u2502${l}\u2502`).join("\n");
5649
7245
  }
5650
7246
  function BrickBreakerGame({ onExit: _onExit }) {
5651
- const [initialLevel, setInitialLevel] = useState5(1);
5652
- const [selectingLevel, setSelectingLevel] = useState5(true);
5653
- const stateRef = useRef3(createInitialState(initialLevel));
5654
- const [tick2, setTick] = useState5(0);
5655
- const onExitRef = useRef3(_onExit);
7247
+ const [initialLevel, setInitialLevel] = useState6(1);
7248
+ const [selectingLevel, setSelectingLevel] = useState6(true);
7249
+ const stateRef = useRef4(createInitialState(initialLevel));
7250
+ const [tick2, setTick] = useState6(0);
7251
+ const onExitRef = useRef4(_onExit);
5656
7252
  onExitRef.current = _onExit;
5657
- useEffect4(() => {
7253
+ useEffect5(() => {
5658
7254
  if (selectingLevel) return;
5659
7255
  const interval = setInterval(() => {
5660
7256
  update(stateRef.current);
@@ -5707,49 +7303,49 @@ function BrickBreakerGame({ onExit: _onExit }) {
5707
7303
  const board = buildBoard(s);
5708
7304
  const def = getLevel(s.level);
5709
7305
  void tick2;
5710
- return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
5711
- /* @__PURE__ */ jsxs11(Box11, { flexDirection: "row", children: [
5712
- /* @__PURE__ */ jsx11(Box11, { width: 20, children: /* @__PURE__ */ jsxs11(Text12, { children: [
7306
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
7307
+ /* @__PURE__ */ jsxs12(Box12, { flexDirection: "row", children: [
7308
+ /* @__PURE__ */ jsx12(Box12, { width: 20, children: /* @__PURE__ */ jsxs12(Text13, { children: [
5713
7309
  "\u5173\u5361 ",
5714
7310
  s.level,
5715
7311
  ": ",
5716
- /* @__PURE__ */ jsx11(Text12, { color: "cyan", children: def.desc })
7312
+ /* @__PURE__ */ jsx12(Text13, { color: "cyan", children: def.desc })
5717
7313
  ] }) }),
5718
- /* @__PURE__ */ jsx11(Box11, { width: 12, children: /* @__PURE__ */ jsxs11(Text12, { children: [
7314
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsxs12(Text13, { children: [
5719
7315
  "\u5206\u6570: ",
5720
- /* @__PURE__ */ jsx11(Text12, { color: "yellow", children: String(s.score).padStart(3, "0") })
7316
+ /* @__PURE__ */ jsx12(Text13, { color: "yellow", children: String(s.score).padStart(3, "0") })
5721
7317
  ] }) }),
5722
- /* @__PURE__ */ jsx11(Box11, { width: 12, children: /* @__PURE__ */ jsxs11(Text12, { children: [
7318
+ /* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsxs12(Text13, { children: [
5723
7319
  "\u751F\u547D: ",
5724
- /* @__PURE__ */ jsx11(Text12, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
7320
+ /* @__PURE__ */ jsx12(Text13, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
5725
7321
  ] }) }),
5726
- /* @__PURE__ */ jsx11(Box11, { width: 10, children: /* @__PURE__ */ jsxs11(Text12, { children: [
7322
+ /* @__PURE__ */ jsx12(Box12, { width: 10, children: /* @__PURE__ */ jsxs12(Text13, { children: [
5727
7323
  "\u7816\u5757: ",
5728
- /* @__PURE__ */ jsx11(Text12, { color: "cyan", children: aliveCount })
7324
+ /* @__PURE__ */ jsx12(Text13, { color: "cyan", children: aliveCount })
5729
7325
  ] }) }),
5730
- /* @__PURE__ */ jsx11(Box11, { children: /* @__PURE__ */ jsxs11(Text12, { color: s.paused ? "gray" : "green", children: [
7326
+ /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsxs12(Text13, { color: s.paused ? "gray" : "green", children: [
5731
7327
  "[",
5732
7328
  s.paused ? "\u6682\u505C" : "\u8FD0\u884C\u4E2D",
5733
7329
  "]"
5734
7330
  ] }) })
5735
7331
  ] }),
5736
- /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
5737
- /* @__PURE__ */ jsxs11(Text12, { children: [
7332
+ /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
7333
+ /* @__PURE__ */ jsxs12(Text13, { children: [
5738
7334
  "\u250C",
5739
7335
  "\u2500".repeat(GAME_WIDTH),
5740
7336
  "\u2510"
5741
7337
  ] }),
5742
- /* @__PURE__ */ jsx11(Text12, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
5743
- /* @__PURE__ */ jsxs11(Text12, { children: [
7338
+ /* @__PURE__ */ jsx12(Text13, { children: selectingLevel ? " \x1B[90m\u9009\u62E9\u5173\u5361\u540E\u5F00\u59CB...\x1B[0m" : board }),
7339
+ /* @__PURE__ */ jsxs12(Text13, { children: [
5744
7340
  "\u2514",
5745
7341
  "\u2500".repeat(GAME_WIDTH),
5746
7342
  "\u2518"
5747
7343
  ] })
5748
7344
  ] }),
5749
- selectingLevel && /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, flexDirection: "column", children: [
5750
- /* @__PURE__ */ jsx11(Text12, { bold: true, color: "yellow", children: "\u9009\u62E9\u5173\u5361" }),
5751
- /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", flexWrap: "wrap", children: LEVELS.map((lv, i) => /* @__PURE__ */ jsx11(Box11, { width: 22, children: /* @__PURE__ */ jsxs11(Text12, { children: [
5752
- /* @__PURE__ */ jsx11(Text12, { color: initialLevel === i + 1 ? "green" : "white", children: i + 1 === 10 ? "0" : String(i + 1) }),
7345
+ selectingLevel && /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, flexDirection: "column", children: [
7346
+ /* @__PURE__ */ jsx12(Text13, { bold: true, color: "yellow", children: "\u9009\u62E9\u5173\u5361" }),
7347
+ /* @__PURE__ */ jsx12(Box12, { flexDirection: "row", flexWrap: "wrap", children: LEVELS.map((lv, i) => /* @__PURE__ */ jsx12(Box12, { width: 22, children: /* @__PURE__ */ jsxs12(Text13, { children: [
7348
+ /* @__PURE__ */ jsx12(Text13, { color: initialLevel === i + 1 ? "green" : "white", children: i + 1 === 10 ? "0" : String(i + 1) }),
5753
7349
  ". ",
5754
7350
  lv.desc,
5755
7351
  " (",
@@ -5758,16 +7354,16 @@ function BrickBreakerGame({ onExit: _onExit }) {
5758
7354
  lv.cols,
5759
7355
  ")"
5760
7356
  ] }) }, i)) }),
5761
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
7357
+ /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u6309\u6570\u5B57\u9009\u5173 q \u9000\u51FA" }) })
5762
7358
  ] }),
5763
- !selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
5764
- /* @__PURE__ */ jsx11(Text12, { bold: true, color: s.gameOver ? "red" : "green", children: s.gameOver ? "\u6E38\u620F\u7ED3\u675F\uFF01" : "\u606D\u559C\u901A\u5173\uFF01" }),
5765
- /* @__PURE__ */ jsxs11(Text12, { children: [
7359
+ !selectingLevel && (s.gameOver || s.win) && /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, children: [
7360
+ /* @__PURE__ */ jsx12(Text13, { bold: true, color: s.gameOver ? "red" : "green", children: s.gameOver ? "\u6E38\u620F\u7ED3\u675F\uFF01" : "\u606D\u559C\u901A\u5173\uFF01" }),
7361
+ /* @__PURE__ */ jsxs12(Text13, { children: [
5766
7362
  " \u5206\u6570: ",
5767
- /* @__PURE__ */ jsx11(Text12, { color: "yellow", children: s.score })
7363
+ /* @__PURE__ */ jsx12(Text13, { color: "yellow", children: s.score })
5768
7364
  ] })
5769
7365
  ] }),
5770
- !selectingLevel && /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: s.gameOver || s.win ? /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 r \u91CD\u5F00 l \u9009\u5173 q \u9000\u51FA" }) : /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 p \u6682\u505C q \u9000\u51FA" }) })
7366
+ !selectingLevel && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: s.gameOver || s.win ? /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 r \u91CD\u5F00 l \u9009\u5173 q \u9000\u51FA" }) : /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u2190 \u2192 \u79FB\u52A8 p \u6682\u505C q \u9000\u51FA" }) })
5771
7367
  ] });
5772
7368
  }
5773
7369
  var brick_breaker_default = {
@@ -5777,7 +7373,7 @@ var brick_breaker_default = {
5777
7373
  play: async () => {
5778
7374
  await new Promise((resolve2) => {
5779
7375
  const { unmount } = render2(
5780
- /* @__PURE__ */ jsx11(BrickBreakerGame, { onExit: () => {
7376
+ /* @__PURE__ */ jsx12(BrickBreakerGame, { onExit: () => {
5781
7377
  unmount();
5782
7378
  resolve2();
5783
7379
  } })
@@ -5787,9 +7383,9 @@ var brick_breaker_default = {
5787
7383
  };
5788
7384
 
5789
7385
  // src/game/coder-check/index.tsx
5790
- import { Box as Box12, Text as Text13, useInput as useInput4, render as render3 } from "ink";
5791
- import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback5 } from "react";
5792
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
7386
+ import { Box as Box13, Text as Text14, useInput as useInput4, render as render3 } from "ink";
7387
+ import { useState as useState7, useEffect as useEffect6, useRef as useRef5, useCallback as useCallback5 } from "react";
7388
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
5793
7389
  var GAME_W = 66;
5794
7390
  var GAME_H = 20;
5795
7391
  var SCORE_H = 6;
@@ -6180,18 +7776,18 @@ function buildGameView(s, scoreLines, scoreColor, message) {
6180
7776
  return rows;
6181
7777
  }
6182
7778
  function CoderCheck({ onExit: _onExit }) {
6183
- const stateRef = useRef4(createInitialState2());
6184
- const [tick2, setTick] = useState6(0);
6185
- const [colorOffset, setColorOffset] = useState6(0);
6186
- const onExitRef = useRef4(_onExit);
7779
+ const stateRef = useRef5(createInitialState2());
7780
+ const [tick2, setTick] = useState7(0);
7781
+ const [colorOffset, setColorOffset] = useState7(0);
7782
+ const onExitRef = useRef5(_onExit);
6187
7783
  onExitRef.current = _onExit;
6188
- useEffect5(() => {
7784
+ useEffect6(() => {
6189
7785
  const timer = setInterval(() => {
6190
7786
  setColorOffset((prev) => (prev + 1) % CYBER_PALETTE2.length);
6191
7787
  }, 400);
6192
7788
  return () => clearInterval(timer);
6193
7789
  }, []);
6194
- useEffect5(() => {
7790
+ useEffect6(() => {
6195
7791
  const interval = setInterval(() => {
6196
7792
  update2(stateRef.current);
6197
7793
  setTick((t) => t + 1);
@@ -6260,44 +7856,44 @@ function CoderCheck({ onExit: _onExit }) {
6260
7856
  const scoreLines = buildScoreLines(scoreStr);
6261
7857
  const view = buildGameView(s, scoreLines, scoreColor, s.message);
6262
7858
  void tick2;
6263
- return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, children: [
6264
- /* @__PURE__ */ jsx12(Box12, { flexDirection: "row", children: /* @__PURE__ */ jsxs12(Text13, { children: [
7859
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, children: [
7860
+ /* @__PURE__ */ jsx13(Box13, { flexDirection: "row", children: /* @__PURE__ */ jsxs13(Text14, { children: [
6265
7861
  "\u751F\u547D ",
6266
- /* @__PURE__ */ jsx12(Text13, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
7862
+ /* @__PURE__ */ jsx13(Text14, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
6267
7863
  " ",
6268
7864
  "\u901F\u5EA6 ",
6269
- /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
7865
+ /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
6270
7866
  "Lv.",
6271
7867
  Math.floor(s.speed * 10)
6272
7868
  ] })
6273
7869
  ] }) }),
6274
- !s.gameOver && s.target && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { children: [
7870
+ !s.gameOver && s.target && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { children: [
6275
7871
  "\u6253\u5B57: ",
6276
- /* @__PURE__ */ jsx12(Text13, { color: "green", children: s.typed }),
6277
- /* @__PURE__ */ jsx12(Text13, { color: "white", children: s.target.slice(s.typed.length) })
7872
+ /* @__PURE__ */ jsx13(Text14, { color: "green", children: s.typed }),
7873
+ /* @__PURE__ */ jsx13(Text14, { color: "white", children: s.target.slice(s.typed.length) })
6278
7874
  ] }) }),
6279
- /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", marginTop: 1, children: [
6280
- /* @__PURE__ */ jsxs12(Text13, { children: [
7875
+ /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginTop: 1, children: [
7876
+ /* @__PURE__ */ jsxs13(Text14, { children: [
6281
7877
  "\u250C",
6282
7878
  "\u2500".repeat(GAME_W),
6283
7879
  "\u2510"
6284
7880
  ] }),
6285
- view.map((row, i) => /* @__PURE__ */ jsx12(Text13, { children: `\u2502${row}\u2502` }, i)),
6286
- /* @__PURE__ */ jsxs12(Text13, { children: [
7881
+ view.map((row, i) => /* @__PURE__ */ jsx13(Text14, { children: `\u2502${row}\u2502` }, i)),
7882
+ /* @__PURE__ */ jsxs13(Text14, { children: [
6287
7883
  "\u2514",
6288
7884
  "\u2500".repeat(GAME_W),
6289
7885
  "\u2518"
6290
7886
  ] })
6291
7887
  ] }),
6292
- s.gameOver && /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, children: [
6293
- /* @__PURE__ */ jsx12(Text13, { bold: true, color: "red", children: "\u6E38\u620F\u7ED3\u675F\uFF01" }),
6294
- /* @__PURE__ */ jsxs12(Text13, { children: [
7888
+ s.gameOver && /* @__PURE__ */ jsxs13(Box13, { marginTop: 1, children: [
7889
+ /* @__PURE__ */ jsx13(Text14, { bold: true, color: "red", children: "\u6E38\u620F\u7ED3\u675F\uFF01" }),
7890
+ /* @__PURE__ */ jsxs13(Text14, { children: [
6295
7891
  " \u5F97\u5206: ",
6296
- /* @__PURE__ */ jsx12(Text13, { color: "yellow", children: s.score }),
7892
+ /* @__PURE__ */ jsx13(Text14, { color: "yellow", children: s.score }),
6297
7893
  " r \u91CD\u5F00 q \u9000\u51FA"
6298
7894
  ] })
6299
7895
  ] }),
6300
- /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text13, { dimColor: true, children: "\u6253\u5B57\u6D88\u9664\u5355\u8BCD \u7A7A\u683C/Ctrl+P\u6682\u505C Ctrl+Q\u9000\u51FA" }) })
7896
+ /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: "\u6253\u5B57\u6D88\u9664\u5355\u8BCD \u7A7A\u683C/Ctrl+P\u6682\u505C Ctrl+Q\u9000\u51FA" }) })
6301
7897
  ] });
6302
7898
  }
6303
7899
  var coder_check_default = {
@@ -6307,7 +7903,7 @@ var coder_check_default = {
6307
7903
  play: async () => {
6308
7904
  await new Promise((resolve2) => {
6309
7905
  const { unmount } = render3(
6310
- /* @__PURE__ */ jsx12(CoderCheck, { onExit: () => {
7906
+ /* @__PURE__ */ jsx13(CoderCheck, { onExit: () => {
6311
7907
  unmount();
6312
7908
  resolve2();
6313
7909
  } })
@@ -6687,12 +8283,12 @@ import { render as render4 } from "ink";
6687
8283
  import chalk5 from "chalk";
6688
8284
 
6689
8285
  // src/stock/StockList.tsx
6690
- import { Box as Box13, Text as Text14, useInput as useInput5 } from "ink";
6691
- import { useState as useState7, useCallback as useCallback6, useEffect as useEffect6, useMemo as useMemo2 } from "react";
8286
+ import { Box as Box14, Text as Text15, useInput as useInput5 } from "ink";
8287
+ import { useState as useState8, useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo2 } from "react";
6692
8288
  import asciichart from "asciichart";
6693
8289
  import os from "os";
6694
8290
  import { join as join10 } from "path";
6695
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
8291
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
6696
8292
  var SETTINGS_PATH = join10(os.homedir(), ".dskcode", "settings.json");
6697
8293
  function toFileUrl(p) {
6698
8294
  const norm = p.replace(/\\/g, "/");
@@ -6795,20 +8391,20 @@ function latestPoints(data, maxPoints = 60) {
6795
8391
  return data.slice(data.length - maxPoints);
6796
8392
  }
6797
8393
  function StockList({ codes, onExit, onBackToChat }) {
6798
- const [stocks, setStocks] = useState7([]);
6799
- const [selectedIndex, setSelectedIndex] = useState7(0);
6800
- const [loading, setLoading] = useState7(true);
6801
- const [lastUpdate, setLastUpdate] = useState7("");
6802
- const [detailView, setDetailView] = useState7(null);
6803
- const [detailPrices, setDetailPrices] = useState7(null);
6804
- const [detailLoading, setDetailLoading] = useState7(false);
6805
- const [detailCountdown, setDetailCountdown] = useState7(10);
6806
- const [countdown, setCountdown] = useState7(5);
6807
- const [currentTime, setCurrentTime] = useState7(
8394
+ const [stocks, setStocks] = useState8([]);
8395
+ const [selectedIndex, setSelectedIndex] = useState8(0);
8396
+ const [loading, setLoading] = useState8(true);
8397
+ const [lastUpdate, setLastUpdate] = useState8("");
8398
+ const [detailView, setDetailView] = useState8(null);
8399
+ const [detailPrices, setDetailPrices] = useState8(null);
8400
+ const [detailLoading, setDetailLoading] = useState8(false);
8401
+ const [detailCountdown, setDetailCountdown] = useState8(10);
8402
+ const [countdown, setCountdown] = useState8(5);
8403
+ const [currentTime, setCurrentTime] = useState8(
6808
8404
  () => (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false })
6809
8405
  );
6810
- const [dimMode, setDimMode] = useState7(false);
6811
- const [sortOrder, setSortOrder] = useState7("default");
8406
+ const [dimMode, setDimMode] = useState8(false);
8407
+ const [sortOrder, setSortOrder] = useState8("default");
6812
8408
  const sortedStocks = useMemo2(() => {
6813
8409
  if (sortOrder === "default") return stocks;
6814
8410
  return [...stocks].toSorted(
@@ -6816,7 +8412,7 @@ function StockList({ codes, onExit, onBackToChat }) {
6816
8412
  );
6817
8413
  }, [stocks, sortOrder]);
6818
8414
  const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(onExit);
6819
- useEffect6(() => {
8415
+ useEffect7(() => {
6820
8416
  const timer = setInterval(() => {
6821
8417
  setCurrentTime((/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false }));
6822
8418
  }, 1e3);
@@ -6832,10 +8428,10 @@ function StockList({ codes, onExit, onBackToChat }) {
6832
8428
  }
6833
8429
  setLoading(false);
6834
8430
  }, [codes]);
6835
- useEffect6(() => {
8431
+ useEffect7(() => {
6836
8432
  void loadData();
6837
8433
  }, [loadData]);
6838
- useEffect6(() => {
8434
+ useEffect7(() => {
6839
8435
  const interval = setInterval(() => {
6840
8436
  setCountdown((prev) => {
6841
8437
  if (prev <= 1) {
@@ -6847,7 +8443,7 @@ function StockList({ codes, onExit, onBackToChat }) {
6847
8443
  }, 1e3);
6848
8444
  return () => clearInterval(interval);
6849
8445
  }, [loadData]);
6850
- useEffect6(() => {
8446
+ useEffect7(() => {
6851
8447
  if (detailView) {
6852
8448
  const loadDetail = () => {
6853
8449
  minuteCache.delete(detailView.code);
@@ -6866,7 +8462,7 @@ function StockList({ codes, onExit, onBackToChat }) {
6866
8462
  setDetailPrices(null);
6867
8463
  setDetailLoading(false);
6868
8464
  }, [detailView]);
6869
- useEffect6(() => {
8465
+ useEffect7(() => {
6870
8466
  if (!detailView) return;
6871
8467
  const timer = setInterval(() => {
6872
8468
  setDetailCountdown((prev) => prev > 0 ? prev - 1 : 10);
@@ -6911,64 +8507,64 @@ function StockList({ codes, onExit, onBackToChat }) {
6911
8507
  );
6912
8508
  if (detailView) {
6913
8509
  if (detailLoading) {
6914
- return /* @__PURE__ */ jsx13(Box13, { paddingLeft: 1, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
8510
+ return /* @__PURE__ */ jsx14(Box14, { paddingLeft: 1, children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: " \u27F3 \u52A0\u8F7D\u5206\u65F6\u6570\u636E..." }) });
6915
8511
  }
6916
8512
  return renderDetail(detailView, () => setDetailView(null), detailPrices ?? void 0, detailCountdown, currentTime);
6917
8513
  }
6918
8514
  const cp2 = (c) => dimMode ? { dimColor: true } : { color: c };
6919
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
6920
- /* @__PURE__ */ jsxs13(Box13, { marginBottom: 1, justifyContent: "space-between", children: [
6921
- /* @__PURE__ */ jsx13(Text14, { bold: true, ...cp2("#00ffff"), children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
6922
- /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
8515
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
8516
+ /* @__PURE__ */ jsxs14(Box14, { marginBottom: 1, justifyContent: "space-between", children: [
8517
+ /* @__PURE__ */ jsx14(Text15, { bold: true, ...cp2("#00ffff"), children: " \u{1F4C8} \u81EA\u9009\u80A1\u76D1\u63A7" }),
8518
+ /* @__PURE__ */ jsxs14(Text15, { dimColor: true, children: [
6923
8519
  " \u{1F550} ",
6924
8520
  currentTime
6925
8521
  ] }),
6926
- /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
8522
+ /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
6927
8523
  ] }),
6928
- /* @__PURE__ */ jsxs13(Box13, { children: [
6929
- /* @__PURE__ */ jsx13(Box13, { width: 3 }),
6930
- /* @__PURE__ */ jsx13(Box13, { width: 9, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: "\u4EE3\u7801" }) }),
6931
- /* @__PURE__ */ jsx13(Box13, { width: 16, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: "\u540D\u79F0" }) }),
6932
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
6933
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
8524
+ /* @__PURE__ */ jsxs14(Box14, { children: [
8525
+ /* @__PURE__ */ jsx14(Box14, { width: 3 }),
8526
+ /* @__PURE__ */ jsx14(Box14, { width: 9, children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: "\u4EE3\u7801" }) }),
8527
+ /* @__PURE__ */ jsx14(Box14, { width: 16, children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: "\u540D\u79F0" }) }),
8528
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: "\u6700\u65B0\u4EF7" }) }),
8529
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsxs14(Text15, { dimColor: true, children: [
6934
8530
  "\u6DA8\u8DCC\u5E45",
6935
8531
  sortOrder === "desc" ? " \u25BC" : sortOrder === "asc" ? " \u25B2" : ""
6936
8532
  ] }) }),
6937
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
6938
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: "\u6700\u9AD8" }) }),
6939
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: "\u6700\u4F4E" }) }),
6940
- /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: "\u6210\u4EA4\u989D" }) })
8533
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: "\u6DA8\u8DCC\u989D" }) }),
8534
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: "\u6700\u9AD8" }) }),
8535
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: "\u6700\u4F4E" }) }),
8536
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: "\u6210\u4EA4\u989D" }) })
6941
8537
  ] }),
6942
- /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
6943
- /* @__PURE__ */ jsx13(Box13, { flexDirection: "column", children: sortedStocks.map((stock, index) => {
8538
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: " " + "\u2500".repeat(100) }) }),
8539
+ /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", children: sortedStocks.map((stock, index) => {
6944
8540
  const isSelected = index === selectedIndex;
6945
8541
  const isUp = stock.changePercent >= 0;
6946
8542
  const color = isUp ? "#ff1493" : "#00ff41";
6947
- return /* @__PURE__ */ jsxs13(Box13, { children: [
6948
- /* @__PURE__ */ jsx13(Box13, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx13(Text14, { bold: true, ...cp2("#00ffff"), children: "\u25B8 " }) : /* @__PURE__ */ jsx13(Text14, { children: " " }) }),
6949
- /* @__PURE__ */ jsx13(Box13, { width: 9, children: /* @__PURE__ */ jsx13(Text14, { bold: true, ...cp2(isSelected ? "#00ffff" : "#ffffff"), children: stock.code }) }),
6950
- /* @__PURE__ */ jsx13(Box13, { width: 16, children: /* @__PURE__ */ jsx13(Text14, { ...cp2(isSelected ? "#ffffff" : "#cccccc"), children: stock.name }) }),
6951
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsx13(Text14, { bold: true, ...cp2(color), children: formatPrice(stock.price) }) }),
6952
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsxs13(Text14, { ...cp2(color), children: [
8543
+ return /* @__PURE__ */ jsxs14(Box14, { children: [
8544
+ /* @__PURE__ */ jsx14(Box14, { width: 3, flexShrink: 0, children: isSelected ? /* @__PURE__ */ jsx14(Text15, { bold: true, ...cp2("#00ffff"), children: "\u25B8 " }) : /* @__PURE__ */ jsx14(Text15, { children: " " }) }),
8545
+ /* @__PURE__ */ jsx14(Box14, { width: 9, children: /* @__PURE__ */ jsx14(Text15, { bold: true, ...cp2(isSelected ? "#00ffff" : "#ffffff"), children: stock.code }) }),
8546
+ /* @__PURE__ */ jsx14(Box14, { width: 16, children: /* @__PURE__ */ jsx14(Text15, { ...cp2(isSelected ? "#ffffff" : "#cccccc"), children: stock.name }) }),
8547
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsx14(Text15, { bold: true, ...cp2(color), children: formatPrice(stock.price) }) }),
8548
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsxs14(Text15, { ...cp2(color), children: [
6953
8549
  isUp ? "+" : "",
6954
8550
  stock.changePercent.toFixed(2),
6955
8551
  "%"
6956
8552
  ] }) }),
6957
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsxs13(Text14, { ...cp2(color), children: [
8553
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsxs14(Text15, { ...cp2(color), children: [
6958
8554
  isUp ? "+" : "",
6959
8555
  stock.changeAmount.toFixed(3)
6960
8556
  ] }) }),
6961
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsx13(Text14, { ...cp2("#cccccc"), children: formatPrice(stock.high) }) }),
6962
- /* @__PURE__ */ jsx13(Box13, { width: 12, children: /* @__PURE__ */ jsx13(Text14, { ...cp2("#888888"), children: formatPrice(stock.low) }) }),
6963
- /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text14, { ...cp2("#888888"), children: formatAmount(stock.amount) }) })
8557
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsx14(Text15, { ...cp2("#cccccc"), children: formatPrice(stock.high) }) }),
8558
+ /* @__PURE__ */ jsx14(Box14, { width: 12, children: /* @__PURE__ */ jsx14(Text15, { ...cp2("#888888"), children: formatPrice(stock.low) }) }),
8559
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text15, { ...cp2("#888888"), children: formatAmount(stock.amount) }) })
6964
8560
  ] }, stock.code);
6965
8561
  }) }),
6966
- /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: ` \u2191/\u2193 \u9009\u62E9 Enter \u8BE6\u60C5 r \u624B\u52A8\u5237\u65B0 o \u6392\u5E8F h \u7F6E\u7070/\u6062\u590D q \u8FD4\u56DE` }) }),
6967
- /* @__PURE__ */ jsxs13(Box13, { children: [
6968
- /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ` }),
6969
- /* @__PURE__ */ jsx13(Text14, { ...cp2("#c792ea"), children: osc8Link(toFileUrl(SETTINGS_PATH), SETTINGS_PATH) })
8562
+ /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: ` \u2191/\u2193 \u9009\u62E9 Enter \u8BE6\u60C5 r \u624B\u52A8\u5237\u65B0 o \u6392\u5E8F h \u7F6E\u7070/\u6062\u590D q \u8FD4\u56DE` }) }),
8563
+ /* @__PURE__ */ jsxs14(Box14, { children: [
8564
+ /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: ` \u6700\u540E\u66F4\u65B0: ${lastUpdate} \u7F16\u8F91\u81EA\u9009\u80A1: ` }),
8565
+ /* @__PURE__ */ jsx14(Text15, { ...cp2("#c792ea"), children: osc8Link(toFileUrl(SETTINGS_PATH), SETTINGS_PATH) })
6970
8566
  ] }),
6971
- doubleCtrlC && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text14, { bold: true, ...cp2("#ff1493"), children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
8567
+ doubleCtrlC && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text15, { bold: true, ...cp2("#ff1493"), children: " \u26A0 \u518D\u6309\u4E00\u6B21 Ctrl+C \u9000\u51FA dskcode" }) })
6972
8568
  ] });
6973
8569
  }
6974
8570
  function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
@@ -6986,33 +8582,33 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
6986
8582
  raw = raw.replaceAll("\u256D", "\u250C").replaceAll("\u256E", "\u2510").replaceAll("\u2570", "\u2514").replaceAll("\u256F", "\u2518");
6987
8583
  chartLines = raw.split("\n");
6988
8584
  }
6989
- return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingLeft: 1, children: [
6990
- /* @__PURE__ */ jsxs13(Box13, { marginBottom: 1, justifyContent: "space-between", children: [
6991
- /* @__PURE__ */ jsxs13(Box13, { children: [
6992
- /* @__PURE__ */ jsxs13(Text14, { bold: true, color: "#00ffff", children: [
8585
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingLeft: 1, children: [
8586
+ /* @__PURE__ */ jsxs14(Box14, { marginBottom: 1, justifyContent: "space-between", children: [
8587
+ /* @__PURE__ */ jsxs14(Box14, { children: [
8588
+ /* @__PURE__ */ jsxs14(Text15, { bold: true, color: "#00ffff", children: [
6993
8589
  " \u{1F4CA} ",
6994
8590
  stock.name,
6995
8591
  " "
6996
8592
  ] }),
6997
- /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: stock.code }),
6998
- currentTime && /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
8593
+ /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: stock.code }),
8594
+ currentTime && /* @__PURE__ */ jsxs14(Text15, { dimColor: true, children: [
6999
8595
  " \u{1F550} ",
7000
8596
  currentTime
7001
8597
  ] })
7002
8598
  ] }),
7003
- /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
8599
+ /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
7004
8600
  ] }),
7005
- /* @__PURE__ */ jsxs13(Box13, { children: [
7006
- /* @__PURE__ */ jsx13(Box13, { width: 16, children: /* @__PURE__ */ jsx13(Text14, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
7007
- /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs13(Text14, { bold: true, color: colorCode, children: [
8601
+ /* @__PURE__ */ jsxs14(Box14, { children: [
8602
+ /* @__PURE__ */ jsx14(Box14, { width: 16, children: /* @__PURE__ */ jsx14(Text15, { bold: true, color: "#888888", children: "\u5F53\u524D\u4EF7" }) }),
8603
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs14(Text15, { bold: true, color: colorCode, children: [
7008
8604
  arrow,
7009
8605
  " ",
7010
8606
  formatPrice(stock.price)
7011
8607
  ] }) })
7012
8608
  ] }),
7013
- /* @__PURE__ */ jsxs13(Box13, { children: [
7014
- /* @__PURE__ */ jsx13(Box13, { width: 16, children: /* @__PURE__ */ jsx13(Text14, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
7015
- /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs13(Text14, { color: colorCode, children: [
8609
+ /* @__PURE__ */ jsxs14(Box14, { children: [
8610
+ /* @__PURE__ */ jsx14(Box14, { width: 16, children: /* @__PURE__ */ jsx14(Text15, { color: "#888888", children: "\u6DA8\u8DCC\u5E45" }) }),
8611
+ /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs14(Text15, { color: colorCode, children: [
7016
8612
  isUp ? "+" : "",
7017
8613
  stock.changePercent.toFixed(2),
7018
8614
  "%",
@@ -7021,14 +8617,14 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
7021
8617
  stock.changeAmount.toFixed(3)
7022
8618
  ] }) })
7023
8619
  ] }),
7024
- chartLines.length > 0 && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, flexDirection: "column", children: chartLines.map((line, i) => /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text14, { color: colorCode, children: line || " " }) }, i)) }),
7025
- /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text14, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
8620
+ chartLines.length > 0 && /* @__PURE__ */ jsx14(Box14, { marginTop: 1, flexDirection: "column", children: chartLines.map((line, i) => /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text15, { color: colorCode, children: line || " " }) }, i)) }),
8621
+ /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text15, { dimColor: true, children: " Space/q \u8FD4\u56DE\u5217\u8868" }) })
7026
8622
  ] });
7027
8623
  }
7028
8624
 
7029
8625
  // src/utils/scan-files.ts
7030
8626
  import { readdir as readdir6 } from "fs/promises";
7031
- import { join as join11, relative as relative7 } from "path";
8627
+ import { join as join11, relative as relative8 } from "path";
7032
8628
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
7033
8629
  "node_modules",
7034
8630
  ".git",
@@ -7093,7 +8689,7 @@ async function scanProjectFiles(baseDir, dir) {
7093
8689
  } else if (entry.isFile()) {
7094
8690
  const ext = name.slice(name.lastIndexOf(".")).toLowerCase();
7095
8691
  if (SOURCE_EXTS.has(ext)) {
7096
- files.push(relative7(baseDir, join11(currentDir, name)));
8692
+ files.push(relative8(baseDir, join11(currentDir, name)));
7097
8693
  }
7098
8694
  }
7099
8695
  }
@@ -7107,7 +8703,7 @@ async function scanProjectFiles(baseDir, dir) {
7107
8703
  }
7108
8704
 
7109
8705
  // src/cli/index.tsx
7110
- import { jsx as jsx14 } from "react/jsx-runtime";
8706
+ import { jsx as jsx15 } from "react/jsx-runtime";
7111
8707
  var SUBCOMMANDS = ["chat", "run", "setup", "init", "completion", "game", "stock"];
7112
8708
  function createCli() {
7113
8709
  const program2 = new Command();
@@ -7209,7 +8805,7 @@ compdef _dskcode_completion dskcode`);
7209
8805
  const freshResult = await loadAndValidate();
7210
8806
  const codeList = codes && codes.length > 0 ? codes : freshResult.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
7211
8807
  const app = renderApp(
7212
- /* @__PURE__ */ jsx14(
8808
+ /* @__PURE__ */ jsx15(
7213
8809
  StockList,
7214
8810
  {
7215
8811
  codes: codeList,
@@ -7238,7 +8834,7 @@ compdef _dskcode_completion dskcode`);
7238
8834
  }
7239
8835
  const selectedGame = await new Promise((resolve2) => {
7240
8836
  const { unmount } = render4(
7241
- /* @__PURE__ */ jsx14(
8837
+ /* @__PURE__ */ jsx15(
7242
8838
  GamePicker,
7243
8839
  {
7244
8840
  games,
@@ -7278,7 +8874,7 @@ async function startChat(ctx, costTracker) {
7278
8874
  );
7279
8875
  const model = defaultProvider?.model ?? "deepseek-v4-flash";
7280
8876
  const chatApp = renderApp(
7281
- /* @__PURE__ */ jsx14(
8877
+ /* @__PURE__ */ jsx15(
7282
8878
  ChatSession,
7283
8879
  {
7284
8880
  skillCount,
@@ -7296,7 +8892,7 @@ async function startChat(ctx, costTracker) {
7296
8892
  initGames();
7297
8893
  const games = listGames();
7298
8894
  const { unmount } = render4(
7299
- /* @__PURE__ */ jsx14(
8895
+ /* @__PURE__ */ jsx15(
7300
8896
  GamePicker,
7301
8897
  {
7302
8898
  games,
@@ -7320,7 +8916,7 @@ async function startChat(ctx, costTracker) {
7320
8916
  setImmediate(() => {
7321
8917
  const defaultStockCodes = ctx?.config.stock?.symbols?.map((s) => s.code) ?? ["sh000001", "sz399006", "sh601688"];
7322
8918
  const stockApp = renderApp(
7323
- /* @__PURE__ */ jsx14(
8919
+ /* @__PURE__ */ jsx15(
7324
8920
  StockList,
7325
8921
  {
7326
8922
  codes: defaultStockCodes,