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/{chunk-UV4IWYHZ.js → chunk-LWOBXEMJ.js} +9 -9
- package/dist/{chunk-UV4IWYHZ.js.map → chunk-LWOBXEMJ.js.map} +1 -1
- package/dist/deepseek-WFSVA5UQ.js +9 -0
- package/dist/index.js +2533 -937
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/deepseek-PGX76BK5.js +0 -7
- /package/dist/{deepseek-PGX76BK5.js.map → deepseek-WFSVA5UQ.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
estimateTokens,
|
|
8
8
|
getModelMeta,
|
|
9
9
|
isSupportedModel
|
|
10
|
-
} from "./chunk-
|
|
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
|
|
808
|
+
import { Box as Box10, Text as Text11, useInput, Static } from "ink";
|
|
809
809
|
import TextInput from "ink-text-input";
|
|
810
|
-
import { useEffect as
|
|
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
|
|
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, {
|
|
1182
|
+
/* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
|
|
1182
1183
|
"\u{1F4E6} ",
|
|
1183
1184
|
call.name
|
|
1184
1185
|
] }),
|
|
1185
|
-
/* @__PURE__ */ jsxs3(Text4, {
|
|
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, {
|
|
1191
|
-
showPendingHint && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text4, {
|
|
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
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
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 {
|
|
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.#
|
|
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
|
-
/**
|
|
2357
|
-
async #
|
|
3462
|
+
/** 将一条事件以多行块的形式写入日志文件 */
|
|
3463
|
+
async #writeBlock(event) {
|
|
2358
3464
|
const dir = join5(this.#logPath, "..");
|
|
2359
3465
|
await mkdir5(dir, { recursive: true });
|
|
2360
|
-
const
|
|
2361
|
-
await appendFile(this.#logPath,
|
|
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(
|
|
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
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
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.#
|
|
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)
|
|
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.#
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
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, {
|
|
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({
|
|
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
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
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,
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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${
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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} ${
|
|
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 =
|
|
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
|
|
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", {
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
registerCommand("/
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
registerCommand("/
|
|
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] =
|
|
4480
|
-
const [displayMessages, setDisplayMessages] =
|
|
4481
|
-
const [staticKey, setStaticKey] =
|
|
4482
|
-
const [input, setInput] =
|
|
4483
|
-
const [balance, setBalance] =
|
|
4484
|
-
const [balanceLoading, setBalanceLoading] =
|
|
4485
|
-
const [todayCost, setTodayCost] =
|
|
4486
|
-
const [isStreaming, setIsStreaming] =
|
|
4487
|
-
const [streamingPhase, setStreamingPhase] =
|
|
4488
|
-
const [streamingPlaceholder, setStreamingPlaceholder] =
|
|
4489
|
-
const [idlePlaceholder, setIdlePlaceholder] =
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
const [
|
|
4493
|
-
const
|
|
4494
|
-
const [
|
|
4495
|
-
const
|
|
4496
|
-
const [
|
|
4497
|
-
const [
|
|
4498
|
-
const [
|
|
4499
|
-
const [
|
|
4500
|
-
const [
|
|
4501
|
-
const [
|
|
4502
|
-
const [
|
|
4503
|
-
const [
|
|
4504
|
-
const [
|
|
4505
|
-
const [
|
|
4506
|
-
const [
|
|
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] =
|
|
4518
|
-
const [cmdTipGradientColors, setCmdTipGradientColors] =
|
|
4519
|
-
const cmdTipPhaseRef =
|
|
4520
|
-
const [skillSelectIndex, setSkillSelectIndex] =
|
|
4521
|
-
const [fileSelectIndex, setFileSelectIndex] =
|
|
4522
|
-
const [inputKey, setInputKey] =
|
|
4523
|
-
const [rewindSelecting, setRewindSelecting] =
|
|
4524
|
-
const [rewindSelectIndex, setRewindSelectIndex] =
|
|
4525
|
-
const [rewindList, setRewindList] =
|
|
4526
|
-
const [rewinding, setRewinding] =
|
|
4527
|
-
const [rewindHintPhase, setRewindHintPhase] =
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
const
|
|
4531
|
-
const
|
|
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 =
|
|
4534
|
-
const abortRef =
|
|
4535
|
-
const currentContentRef =
|
|
4536
|
-
const
|
|
4537
|
-
const
|
|
4538
|
-
const
|
|
4539
|
-
const
|
|
4540
|
-
const
|
|
4541
|
-
const
|
|
4542
|
-
|
|
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
|
-
|
|
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(
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
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\
|
|
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
|
-
}
|
|
4629
|
-
|
|
4630
|
-
|
|
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(
|
|
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(
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
|
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
|
-
[
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6330
|
+
useEffect4(() => {
|
|
4829
6331
|
if (!apiKey || !baseUrl) return;
|
|
4830
6332
|
let cancelled = false;
|
|
4831
6333
|
setBalanceLoading(true);
|
|
4832
|
-
import("./deepseek-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
6392
|
+
useEffect4(() => {
|
|
4885
6393
|
if (!isStreaming || !streamingPlaceholder) {
|
|
4886
6394
|
setStreamingGradientColors([]);
|
|
4887
6395
|
return;
|
|
4888
6396
|
}
|
|
4889
6397
|
streamingPhaseRef.current = 0;
|
|
4890
|
-
setStreamingGradientColors(
|
|
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(
|
|
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(
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
if (
|
|
4904
|
-
|
|
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
|
-
{
|
|
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 (
|
|
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
|
-
{
|
|
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
|
-
|
|
4923
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
4933
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
4942
|
-
|
|
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
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
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
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
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:
|
|
6581
|
+
{ role: "assistant", content: `\u5DE5\u5177\u8C03\u7528\u7B56\u7565\u5DF2\u5207\u6362\u4E3A ${label}` }
|
|
5024
6582
|
]);
|
|
5025
|
-
|
|
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 (
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
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
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
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
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
6743
|
+
const cost = calculateCost(
|
|
6744
|
+
event.usage,
|
|
6745
|
+
event.model
|
|
6746
|
+
);
|
|
6747
|
+
setCurrentCost(cost.totalCost);
|
|
6748
|
+
currentCostRef.current = cost.totalCost;
|
|
5170
6749
|
}
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
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
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
6787
|
+
role: "assistant",
|
|
6788
|
+
content: completed.content,
|
|
6789
|
+
assistantDetail: completed
|
|
5182
6790
|
}
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
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
|
-
}
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
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__ */
|
|
5240
|
-
!hasConversationStarted && /* @__PURE__ */
|
|
5241
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6831
|
+
return /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsx10(Text11, { bold: true, color: CYBER_PALETTE[colorIndex], children: line }) }, i);
|
|
5244
6832
|
}) }),
|
|
5245
|
-
/* @__PURE__ */
|
|
5246
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
5267
|
-
responseFormat === "json_object" && /* @__PURE__ */
|
|
5268
|
-
toolChoice !== void 0 && /* @__PURE__ */
|
|
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__ */
|
|
5277
|
-
/* @__PURE__ */
|
|
5278
|
-
cmdTipGradientColors.length > 0 ? text.split("").map((ch, i) => /* @__PURE__ */
|
|
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__ */
|
|
6869
|
+
verbose ? /* @__PURE__ */ jsx10(Text11, { color: "#ff1493", children: " \u26A1 Verbose" }) : null
|
|
5282
6870
|
] }),
|
|
5283
|
-
/* @__PURE__ */
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
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__ */
|
|
5301
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5304
|
-
/* @__PURE__ */
|
|
5305
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5310
|
-
/* @__PURE__ */
|
|
5311
|
-
/* @__PURE__ */
|
|
5312
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6911
|
+
msg.diff && /* @__PURE__ */ jsx10(DiffPreview, { diff: msg.diff })
|
|
5315
6912
|
] }, i);
|
|
5316
6913
|
}
|
|
5317
6914
|
const detail = msg.assistantDetail;
|
|
5318
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
-
|
|
5346
|
-
|
|
5347
|
-
/* @__PURE__ */
|
|
5348
|
-
|
|
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__ */
|
|
5356
|
-
/* @__PURE__ */
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
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__ */
|
|
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__ */
|
|
5377
|
-
] }) : rewindSelecting ? /* @__PURE__ */
|
|
5378
|
-
/* @__PURE__ */
|
|
5379
|
-
/* @__PURE__ */
|
|
5380
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
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__ */
|
|
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__ */
|
|
5409
|
-
] }) : /* @__PURE__ */
|
|
5410
|
-
(hasConversationStarted || sessionMode === "plan") && isStreaming && streamingPhase ? /* @__PURE__ */
|
|
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__ */
|
|
7004
|
+
/* @__PURE__ */ jsx10(InkSpinner3, { type: "dots" })
|
|
5416
7005
|
] }) }) : rewindHintPhase === "visible" ? (
|
|
5417
7006
|
// 本轮修改了文件时,流式结束后 2s 在原位置展示 /rewind 提示
|
|
5418
7007
|
// 一直保留到下次对话开始,与 /rewind 1(最新检查点)配套
|
|
5419
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
"
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
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__ */
|
|
5451
|
-
/* @__PURE__ */
|
|
5452
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5455
|
-
isStreaming && /* @__PURE__ */
|
|
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
|
|
5461
|
-
import { useState as
|
|
5462
|
-
import { jsx as
|
|
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] =
|
|
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__ */
|
|
5491
|
-
/* @__PURE__ */
|
|
5492
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5495
|
-
/* @__PURE__ */
|
|
5496
|
-
/* @__PURE__ */
|
|
5497
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5501
|
-
doubleCtrlC && /* @__PURE__ */
|
|
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
|
|
5519
|
-
import { useState as
|
|
5520
|
-
import { jsx as
|
|
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] =
|
|
5652
|
-
const [selectingLevel, setSelectingLevel] =
|
|
5653
|
-
const stateRef =
|
|
5654
|
-
const [tick2, setTick] =
|
|
5655
|
-
const onExitRef =
|
|
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
|
-
|
|
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__ */
|
|
5711
|
-
/* @__PURE__ */
|
|
5712
|
-
/* @__PURE__ */
|
|
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__ */
|
|
7312
|
+
/* @__PURE__ */ jsx12(Text13, { color: "cyan", children: def.desc })
|
|
5717
7313
|
] }) }),
|
|
5718
|
-
/* @__PURE__ */
|
|
7314
|
+
/* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsxs12(Text13, { children: [
|
|
5719
7315
|
"\u5206\u6570: ",
|
|
5720
|
-
/* @__PURE__ */
|
|
7316
|
+
/* @__PURE__ */ jsx12(Text13, { color: "yellow", children: String(s.score).padStart(3, "0") })
|
|
5721
7317
|
] }) }),
|
|
5722
|
-
/* @__PURE__ */
|
|
7318
|
+
/* @__PURE__ */ jsx12(Box12, { width: 12, children: /* @__PURE__ */ jsxs12(Text13, { children: [
|
|
5723
7319
|
"\u751F\u547D: ",
|
|
5724
|
-
/* @__PURE__ */
|
|
7320
|
+
/* @__PURE__ */ jsx12(Text13, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) })
|
|
5725
7321
|
] }) }),
|
|
5726
|
-
/* @__PURE__ */
|
|
7322
|
+
/* @__PURE__ */ jsx12(Box12, { width: 10, children: /* @__PURE__ */ jsxs12(Text13, { children: [
|
|
5727
7323
|
"\u7816\u5757: ",
|
|
5728
|
-
/* @__PURE__ */
|
|
7324
|
+
/* @__PURE__ */ jsx12(Text13, { color: "cyan", children: aliveCount })
|
|
5729
7325
|
] }) }),
|
|
5730
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5737
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5743
|
-
/* @__PURE__ */
|
|
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__ */
|
|
5750
|
-
/* @__PURE__ */
|
|
5751
|
-
/* @__PURE__ */
|
|
5752
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
5764
|
-
/* @__PURE__ */
|
|
5765
|
-
/* @__PURE__ */
|
|
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__ */
|
|
7363
|
+
/* @__PURE__ */ jsx12(Text13, { color: "yellow", children: s.score })
|
|
5768
7364
|
] })
|
|
5769
7365
|
] }),
|
|
5770
|
-
!selectingLevel && /* @__PURE__ */
|
|
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__ */
|
|
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
|
|
5791
|
-
import { useState as
|
|
5792
|
-
import { jsx as
|
|
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 =
|
|
6184
|
-
const [tick2, setTick] =
|
|
6185
|
-
const [colorOffset, setColorOffset] =
|
|
6186
|
-
const onExitRef =
|
|
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
|
-
|
|
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
|
-
|
|
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__ */
|
|
6264
|
-
/* @__PURE__ */
|
|
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__ */
|
|
7862
|
+
/* @__PURE__ */ jsx13(Text14, { color: "red", children: "\u2665".repeat(Math.max(0, s.lives)) }),
|
|
6267
7863
|
" ",
|
|
6268
7864
|
"\u901F\u5EA6 ",
|
|
6269
|
-
/* @__PURE__ */
|
|
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__ */
|
|
7870
|
+
!s.gameOver && s.target && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { children: [
|
|
6275
7871
|
"\u6253\u5B57: ",
|
|
6276
|
-
/* @__PURE__ */
|
|
6277
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6280
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6286
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6293
|
-
/* @__PURE__ */
|
|
6294
|
-
/* @__PURE__ */
|
|
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__ */
|
|
7892
|
+
/* @__PURE__ */ jsx13(Text14, { color: "yellow", children: s.score }),
|
|
6297
7893
|
" r \u91CD\u5F00 q \u9000\u51FA"
|
|
6298
7894
|
] })
|
|
6299
7895
|
] }),
|
|
6300
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
6691
|
-
import { useState as
|
|
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
|
|
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] =
|
|
6799
|
-
const [selectedIndex, setSelectedIndex] =
|
|
6800
|
-
const [loading, setLoading] =
|
|
6801
|
-
const [lastUpdate, setLastUpdate] =
|
|
6802
|
-
const [detailView, setDetailView] =
|
|
6803
|
-
const [detailPrices, setDetailPrices] =
|
|
6804
|
-
const [detailLoading, setDetailLoading] =
|
|
6805
|
-
const [detailCountdown, setDetailCountdown] =
|
|
6806
|
-
const [countdown, setCountdown] =
|
|
6807
|
-
const [currentTime, setCurrentTime] =
|
|
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] =
|
|
6811
|
-
const [sortOrder, setSortOrder] =
|
|
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
|
-
|
|
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
|
-
|
|
8431
|
+
useEffect7(() => {
|
|
6836
8432
|
void loadData();
|
|
6837
8433
|
}, [loadData]);
|
|
6838
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
6920
|
-
/* @__PURE__ */
|
|
6921
|
-
/* @__PURE__ */
|
|
6922
|
-
/* @__PURE__ */
|
|
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__ */
|
|
8522
|
+
/* @__PURE__ */ jsx14(Text15, { dimColor: true, children: loading ? " \u27F3 \u5237\u65B0\u4E2D..." : ` ${countdown}s \u540E\u81EA\u52A8\u5237\u65B0` })
|
|
6927
8523
|
] }),
|
|
6928
|
-
/* @__PURE__ */
|
|
6929
|
-
/* @__PURE__ */
|
|
6930
|
-
/* @__PURE__ */
|
|
6931
|
-
/* @__PURE__ */
|
|
6932
|
-
/* @__PURE__ */
|
|
6933
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6938
|
-
/* @__PURE__ */
|
|
6939
|
-
/* @__PURE__ */
|
|
6940
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6943
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6948
|
-
/* @__PURE__ */
|
|
6949
|
-
/* @__PURE__ */
|
|
6950
|
-
/* @__PURE__ */
|
|
6951
|
-
/* @__PURE__ */
|
|
6952
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
6962
|
-
/* @__PURE__ */
|
|
6963
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6967
|
-
/* @__PURE__ */
|
|
6968
|
-
/* @__PURE__ */
|
|
6969
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
6990
|
-
/* @__PURE__ */
|
|
6991
|
-
/* @__PURE__ */
|
|
6992
|
-
/* @__PURE__ */
|
|
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__ */
|
|
6998
|
-
currentTime && /* @__PURE__ */
|
|
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__ */
|
|
8599
|
+
/* @__PURE__ */ jsx14(Text15, { dimColor: true, children: `${countdown}s \u540E\u5237\u65B0` })
|
|
7004
8600
|
] }),
|
|
7005
|
-
/* @__PURE__ */
|
|
7006
|
-
/* @__PURE__ */
|
|
7007
|
-
/* @__PURE__ */
|
|
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__ */
|
|
7014
|
-
/* @__PURE__ */
|
|
7015
|
-
/* @__PURE__ */
|
|
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__ */
|
|
7025
|
-
/* @__PURE__ */
|
|
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
|
|
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(
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
8919
|
+
/* @__PURE__ */ jsx15(
|
|
7324
8920
|
StockList,
|
|
7325
8921
|
{
|
|
7326
8922
|
codes: defaultStockCodes,
|