dskcode 0.1.28 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1210 -260
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -772,7 +772,7 @@ async function getAllSkills(cwd) {
|
|
|
772
772
|
|
|
773
773
|
// src/cli/index.tsx
|
|
774
774
|
import { readFile as readFile11 } from "fs/promises";
|
|
775
|
-
import { join as
|
|
775
|
+
import { join as join12 } from "path";
|
|
776
776
|
|
|
777
777
|
// src/ui/RenderScope.tsx
|
|
778
778
|
import { render } from "ink";
|
|
@@ -807,7 +807,7 @@ var LOGO_LINES = [
|
|
|
807
807
|
// src/ui/ChatSession.tsx
|
|
808
808
|
import { Box as Box9, Text as Text10, 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";
|
|
@@ -842,6 +842,7 @@ import InkSpinner2 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";
|
|
@@ -1674,12 +1675,90 @@ function HighlightedText({ children: text }) {
|
|
|
1674
1675
|
}
|
|
1675
1676
|
|
|
1676
1677
|
// src/ui/AssistantMessage.tsx
|
|
1677
|
-
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1678
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1678
1679
|
function formatElapsed(ms) {
|
|
1679
1680
|
if (ms < 1e3) return `${ms}ms`;
|
|
1680
1681
|
const seconds = (ms / 1e3).toFixed(1);
|
|
1681
1682
|
return `${seconds}s`;
|
|
1682
1683
|
}
|
|
1684
|
+
function AnimatedUsage({ usage, cost }) {
|
|
1685
|
+
const targetTokens = usage ? usage.promptTokens + usage.completionTokens : 0;
|
|
1686
|
+
const targetCost = cost ?? 0;
|
|
1687
|
+
const [displayedTokens, setDisplayedTokens] = useState3(targetTokens);
|
|
1688
|
+
const [displayedCost, setDisplayedCost] = useState3(targetCost);
|
|
1689
|
+
const startRef = useRef2(null);
|
|
1690
|
+
const rafRef = useRef2(null);
|
|
1691
|
+
useEffect3(() => {
|
|
1692
|
+
if (startRef.current && startRef.current.toTokens === targetTokens && startRef.current.toCost === targetCost) {
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
if (targetTokens <= displayedTokens && targetCost <= displayedCost) {
|
|
1696
|
+
setDisplayedTokens(targetTokens);
|
|
1697
|
+
setDisplayedCost(targetCost);
|
|
1698
|
+
startRef.current = {
|
|
1699
|
+
fromTokens: targetTokens,
|
|
1700
|
+
fromCost: targetCost,
|
|
1701
|
+
toTokens: targetTokens,
|
|
1702
|
+
toCost: targetCost,
|
|
1703
|
+
startMs: Date.now(),
|
|
1704
|
+
durationMs: 0
|
|
1705
|
+
};
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
const tokensDelta = targetTokens - displayedTokens;
|
|
1709
|
+
const costDelta = targetCost - displayedCost;
|
|
1710
|
+
const durationMs = Math.min(
|
|
1711
|
+
600,
|
|
1712
|
+
Math.max(220, Math.max(tokensDelta, 0) * 1.2 + 220)
|
|
1713
|
+
);
|
|
1714
|
+
startRef.current = {
|
|
1715
|
+
fromTokens: displayedTokens,
|
|
1716
|
+
fromCost: displayedCost,
|
|
1717
|
+
toTokens: targetTokens,
|
|
1718
|
+
toCost: targetCost,
|
|
1719
|
+
startMs: Date.now(),
|
|
1720
|
+
durationMs
|
|
1721
|
+
};
|
|
1722
|
+
if (rafRef.current) clearInterval(rafRef.current);
|
|
1723
|
+
rafRef.current = setInterval(() => {
|
|
1724
|
+
const s = startRef.current;
|
|
1725
|
+
if (!s) return;
|
|
1726
|
+
const elapsed = Date.now() - s.startMs;
|
|
1727
|
+
if (elapsed >= s.durationMs) {
|
|
1728
|
+
setDisplayedTokens(s.toTokens);
|
|
1729
|
+
setDisplayedCost(s.toCost);
|
|
1730
|
+
if (rafRef.current) {
|
|
1731
|
+
clearInterval(rafRef.current);
|
|
1732
|
+
rafRef.current = null;
|
|
1733
|
+
}
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
const t = elapsed / s.durationMs;
|
|
1737
|
+
const eased = 1 - Math.pow(1 - t, 3);
|
|
1738
|
+
const tokens = s.fromTokens + (s.toTokens - s.fromTokens) * eased;
|
|
1739
|
+
const cost2 = s.fromCost + (s.toCost - s.fromCost) * eased;
|
|
1740
|
+
setDisplayedTokens(tokens);
|
|
1741
|
+
setDisplayedCost(cost2);
|
|
1742
|
+
}, 33);
|
|
1743
|
+
return () => {
|
|
1744
|
+
if (rafRef.current) {
|
|
1745
|
+
clearInterval(rafRef.current);
|
|
1746
|
+
rafRef.current = null;
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
}, [targetTokens, targetCost]);
|
|
1750
|
+
return /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1751
|
+
usage && /* @__PURE__ */ jsxs5(Text6, { color: "#888888", children: [
|
|
1752
|
+
Math.round(displayedTokens).toLocaleString(),
|
|
1753
|
+
" tokens"
|
|
1754
|
+
] }),
|
|
1755
|
+
cost !== void 0 && cost > 0 && /* @__PURE__ */ jsxs5(Text6, { color: "#888888", children: [
|
|
1756
|
+
" \xB7 ",
|
|
1757
|
+
"\xA5",
|
|
1758
|
+
displayedCost.toFixed(4)
|
|
1759
|
+
] })
|
|
1760
|
+
] });
|
|
1761
|
+
}
|
|
1683
1762
|
function AssistantMessage({
|
|
1684
1763
|
content,
|
|
1685
1764
|
toolCalls,
|
|
@@ -1701,6 +1780,11 @@ function AssistantMessage({
|
|
|
1701
1780
|
] })
|
|
1702
1781
|
] }),
|
|
1703
1782
|
toolCalls && toolCalls.length > 0 && /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: toolCalls.map((tc, i) => /* @__PURE__ */ jsx5(ToolCallBlock, { call: tc }, tc.id ?? i)) }),
|
|
1783
|
+
isStreaming && (usage || cost !== void 0) && /* @__PURE__ */ jsx5(Box5, { flexDirection: "row", marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs5(Text6, { color: "#666666", dimColor: true, children: [
|
|
1784
|
+
"\u23F3 \u5DF2\u6D88\u8017",
|
|
1785
|
+
" ",
|
|
1786
|
+
/* @__PURE__ */ jsx5(AnimatedUsage, { usage, cost })
|
|
1787
|
+
] }) }),
|
|
1704
1788
|
!isStreaming && (usage || elapsed !== void 0) && /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, marginLeft: 3, children: [
|
|
1705
1789
|
/* @__PURE__ */ jsx5(Text6, { color: "#555555", children: "\u2500".repeat(36) }),
|
|
1706
1790
|
/* @__PURE__ */ jsxs5(Box5, { flexDirection: "row", gap: 2, children: [
|
|
@@ -2114,6 +2198,389 @@ var ToolRegistry = class {
|
|
|
2114
2198
|
}
|
|
2115
2199
|
};
|
|
2116
2200
|
|
|
2201
|
+
// src/agent/tool-executor.ts
|
|
2202
|
+
var ToolExecutor = class {
|
|
2203
|
+
/** 工具注册表(只读引用) */
|
|
2204
|
+
#registry;
|
|
2205
|
+
/** 权限门(只读引用) */
|
|
2206
|
+
#gate;
|
|
2207
|
+
/** 工具执行上下文(每次执行都共用) */
|
|
2208
|
+
#baseCtx;
|
|
2209
|
+
/** 并行执行的最大同时执行数(默认 8) */
|
|
2210
|
+
#maxParallel;
|
|
2211
|
+
/**
|
|
2212
|
+
* 构造一个 ToolExecutor。
|
|
2213
|
+
*
|
|
2214
|
+
* @param deps — 注入依赖(registry / gate / baseCtx / maxParallel)
|
|
2215
|
+
* @pure 仅保存引用,不调用任何外部 IO
|
|
2216
|
+
*/
|
|
2217
|
+
constructor(deps) {
|
|
2218
|
+
this.#registry = deps.registry;
|
|
2219
|
+
this.#gate = deps.gate;
|
|
2220
|
+
this.#baseCtx = {
|
|
2221
|
+
cwd: deps.baseCtx.cwd,
|
|
2222
|
+
signal: deps.baseCtx.signal,
|
|
2223
|
+
writeRoots: deps.baseCtx.writeRoots,
|
|
2224
|
+
...deps.baseCtx.timeout !== void 0 ? { timeout: deps.baseCtx.timeout } : {}
|
|
2225
|
+
};
|
|
2226
|
+
this.#maxParallel = deps.maxParallel ?? 8;
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* 执行一批工具调用。
|
|
2230
|
+
*
|
|
2231
|
+
* 自动选择并行 / 串行:
|
|
2232
|
+
* - 全部为只读工具 且 calls.length > 1:分批并行(每批最多 #maxParallel 个)
|
|
2233
|
+
* - 其他:严格串行
|
|
2234
|
+
*
|
|
2235
|
+
* @param calls — LLM 决定的本轮工具调用列表
|
|
2236
|
+
* @returns items(全部结果)+ records(仅失败项,用于风暴检测)
|
|
2237
|
+
*
|
|
2238
|
+
* @sideEffect 调用 registry / gate / tool.execute;不修改 Session.messages
|
|
2239
|
+
*/
|
|
2240
|
+
async executeBatch(calls) {
|
|
2241
|
+
if (calls.length === 0) return { items: [], records: [] };
|
|
2242
|
+
if (this.#allReadOnly(calls) && calls.length > 1) {
|
|
2243
|
+
return this.#executeParallel(calls);
|
|
2244
|
+
}
|
|
2245
|
+
return this.#executeSequential(calls);
|
|
2246
|
+
}
|
|
2247
|
+
// -------------------------------------------------------------------------
|
|
2248
|
+
// 内部
|
|
2249
|
+
// -------------------------------------------------------------------------
|
|
2250
|
+
/**
|
|
2251
|
+
* 判定这一批调用是否全部为只读工具。
|
|
2252
|
+
* 工具未找到时按"只读"处理(不会因为没注册就拒绝并行)。
|
|
2253
|
+
*
|
|
2254
|
+
* @pure 不修改任何状态
|
|
2255
|
+
*/
|
|
2256
|
+
#allReadOnly(calls) {
|
|
2257
|
+
return calls.every((tc) => {
|
|
2258
|
+
const tool = this.#registry.get(tc.name);
|
|
2259
|
+
return tool ? isReadOnly(tool.kind) : true;
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* 并行执行:按 #maxParallel 分批,每批内 Promise.all。
|
|
2264
|
+
* 用于"全部只读 + 多于 1 个"的场景。
|
|
2265
|
+
*
|
|
2266
|
+
* @param calls — 全部只读的工具调用
|
|
2267
|
+
* @returns items + records
|
|
2268
|
+
*
|
|
2269
|
+
* @sideEffect 调用 tool.execute
|
|
2270
|
+
*/
|
|
2271
|
+
async #executeParallel(calls) {
|
|
2272
|
+
const items = [];
|
|
2273
|
+
const records = [];
|
|
2274
|
+
for (let i = 0; i < calls.length; i += this.#maxParallel) {
|
|
2275
|
+
const batch = calls.slice(i, i + this.#maxParallel);
|
|
2276
|
+
const results = await Promise.all(batch.map((tc) => this.#executeOne(tc)));
|
|
2277
|
+
for (const r of results) {
|
|
2278
|
+
items.push(r.item);
|
|
2279
|
+
if (!r.record.success) records.push(r.record);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
return { items, records };
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* 串行执行:按 calls 顺序逐个执行。
|
|
2286
|
+
* 用于含写工具的场景(保证副作用顺序与 LLM 给出顺序一致)。
|
|
2287
|
+
*
|
|
2288
|
+
* @param calls — 工具调用列表
|
|
2289
|
+
* @returns items + records
|
|
2290
|
+
*
|
|
2291
|
+
* @sideEffect 按序调用 tool.execute
|
|
2292
|
+
*/
|
|
2293
|
+
async #executeSequential(calls) {
|
|
2294
|
+
const items = [];
|
|
2295
|
+
const records = [];
|
|
2296
|
+
for (const tc of calls) {
|
|
2297
|
+
const r = await this.#executeOne(tc);
|
|
2298
|
+
items.push(r.item);
|
|
2299
|
+
if (!r.record.success) records.push(r.record);
|
|
2300
|
+
}
|
|
2301
|
+
return { items, records };
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* 执行单个工具调用,包含查找 / 参数解析 / 权限门 / 写工具 preview / 异常捕获。
|
|
2305
|
+
*
|
|
2306
|
+
* 失败映射:
|
|
2307
|
+
* - 工具不存在或被禁用 → `TOOL_NOT_FOUND`
|
|
2308
|
+
* - 权限门拒绝 → `GATE_DENIED`
|
|
2309
|
+
* - 工具 execute 抛错 → `EXECUTION_ERROR`
|
|
2310
|
+
*
|
|
2311
|
+
* @param tc — 单个工具调用(含 id / name / arguments 字符串)
|
|
2312
|
+
* @returns { item, record } — item 喂回 Session;record 用于风暴检测
|
|
2313
|
+
*
|
|
2314
|
+
* @sideEffect 调 tool.execute、可能调 tool.preview;不抛错给调用方
|
|
2315
|
+
*/
|
|
2316
|
+
async #executeOne(tc) {
|
|
2317
|
+
const toolName = tc.name;
|
|
2318
|
+
const timestamp = Date.now();
|
|
2319
|
+
const tool = this.#registry.get(toolName);
|
|
2320
|
+
if (!tool) {
|
|
2321
|
+
const errMsg = `\u5DE5\u5177 "${toolName}" \u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u7981\u7528`;
|
|
2322
|
+
return {
|
|
2323
|
+
item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "TOOL_NOT_FOUND" } },
|
|
2324
|
+
record: { name: toolName, success: false, error: "TOOL_NOT_FOUND", timestamp }
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
2327
|
+
let toolArgs;
|
|
2328
|
+
try {
|
|
2329
|
+
toolArgs = tc.arguments ? JSON.parse(tc.arguments) : {};
|
|
2330
|
+
} catch {
|
|
2331
|
+
toolArgs = {};
|
|
2332
|
+
}
|
|
2333
|
+
const gateResult = await this.#gate.check(toolName, toolArgs);
|
|
2334
|
+
if (!gateResult) {
|
|
2335
|
+
const errMsg = `\u5DE5\u5177 "${toolName}" \u88AB\u6743\u9650\u95E8\u62D2\u7EDD`;
|
|
2336
|
+
return {
|
|
2337
|
+
item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "GATE_DENIED" } },
|
|
2338
|
+
record: { name: toolName, success: false, error: "GATE_DENIED", timestamp }
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
if (!isReadOnly(tool.kind)) {
|
|
2342
|
+
const maybePreview = tool.preview;
|
|
2343
|
+
if (typeof maybePreview === "function") {
|
|
2344
|
+
try {
|
|
2345
|
+
await maybePreview(toolArgs, this.#baseCtx);
|
|
2346
|
+
} catch {
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
try {
|
|
2351
|
+
const result = await this.#invokeTool(tool, toolArgs);
|
|
2352
|
+
return {
|
|
2353
|
+
item: { name: toolName, callId: tc.id, result },
|
|
2354
|
+
record: { name: toolName, success: result.success, error: result.error, timestamp }
|
|
2355
|
+
};
|
|
2356
|
+
} catch (err) {
|
|
2357
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2358
|
+
const errorResult = {
|
|
2359
|
+
success: false,
|
|
2360
|
+
data: `\u5DE5\u5177 "${toolName}" \u6267\u884C\u5F02\u5E38\uFF1A${message}`,
|
|
2361
|
+
error: "EXECUTION_ERROR"
|
|
2362
|
+
};
|
|
2363
|
+
return {
|
|
2364
|
+
item: { name: toolName, callId: tc.id, result: errorResult },
|
|
2365
|
+
record: { name: toolName, success: false, error: "EXECUTION_ERROR", timestamp }
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
/**
|
|
2370
|
+
* 真正调 tool.execute 的薄包装(拆出来便于以后插入横切关注点,如重试 / 计时)。
|
|
2371
|
+
*
|
|
2372
|
+
* @param tool — 类型擦除后的工具
|
|
2373
|
+
* @param args — 解析后的参数(unknown)
|
|
2374
|
+
* @returns 工具的 ToolResult
|
|
2375
|
+
*
|
|
2376
|
+
* @sideEffect 调 tool.execute
|
|
2377
|
+
*/
|
|
2378
|
+
async #invokeTool(tool, args) {
|
|
2379
|
+
return tool.execute(args, this.#baseCtx);
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
|
|
2383
|
+
// src/agent/storm-detector.ts
|
|
2384
|
+
var StormDetector = class {
|
|
2385
|
+
/** 触发风暴需要的最少连续失败次数(默认 3) */
|
|
2386
|
+
#threshold;
|
|
2387
|
+
/**
|
|
2388
|
+
* 构造一个 StormDetector。
|
|
2389
|
+
*
|
|
2390
|
+
* @param opts — 判定选项(threshold)
|
|
2391
|
+
* @pure 仅保存配置
|
|
2392
|
+
*/
|
|
2393
|
+
constructor(opts = {}) {
|
|
2394
|
+
this.#threshold = opts.threshold ?? 3;
|
|
2395
|
+
}
|
|
2396
|
+
/**
|
|
2397
|
+
* 判定本轮是否应该中断风暴。
|
|
2398
|
+
*
|
|
2399
|
+
* @param recentRecords — 历史的失败记录(不含成功的;通常来自 ToolExecutor 的 records)
|
|
2400
|
+
* @param currentCalls — 本轮 LLM 决定调用的工具
|
|
2401
|
+
* @returns true 表示触发风暴,调用方应放弃本轮工具执行并提示切换策略
|
|
2402
|
+
*
|
|
2403
|
+
* @pure 不修改任何状态,仅做比较判断
|
|
2404
|
+
*/
|
|
2405
|
+
shouldBreak(recentRecords, currentCalls) {
|
|
2406
|
+
if (recentRecords.length < this.#threshold) return false;
|
|
2407
|
+
const lastN = recentRecords.slice(-this.#threshold);
|
|
2408
|
+
if (lastN.length < this.#threshold) return false;
|
|
2409
|
+
const first = lastN[0];
|
|
2410
|
+
const allSame = lastN.every((r) => r.name === first.name && r.error === first.error && !r.success);
|
|
2411
|
+
if (!allSame) return false;
|
|
2412
|
+
return currentCalls.some((tc) => tc.name === first.name);
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
|
|
2416
|
+
// src/agent/reflector.ts
|
|
2417
|
+
var Reflector = class {
|
|
2418
|
+
/** 触发 R1 需要的最小连续失败次数 */
|
|
2419
|
+
#repeatThreshold;
|
|
2420
|
+
/** 注入到 prompt 的最大反射数 */
|
|
2421
|
+
#maxReflections;
|
|
2422
|
+
/** 单条 hint 的最大字符数 */
|
|
2423
|
+
#maxHintChars;
|
|
2424
|
+
/**
|
|
2425
|
+
* 构造一个 Reflector。
|
|
2426
|
+
*
|
|
2427
|
+
* @param options — 限流与阈值配置
|
|
2428
|
+
* @pure 仅保存配置
|
|
2429
|
+
*/
|
|
2430
|
+
constructor(options = {}) {
|
|
2431
|
+
this.#repeatThreshold = options.repeatThreshold ?? 2;
|
|
2432
|
+
this.#maxReflections = options.maxReflections ?? 5;
|
|
2433
|
+
this.#maxHintChars = options.maxHintChars ?? 800;
|
|
2434
|
+
}
|
|
2435
|
+
/**
|
|
2436
|
+
* 分析一批工具执行结果,返回要注入的反射列表。
|
|
2437
|
+
*
|
|
2438
|
+
* 行为:
|
|
2439
|
+
* - 成功项直接跳过
|
|
2440
|
+
* - 失败项按 R1 → R2 → R3 → R4 顺序检查,先命中先用,单条目最多产生 1 条
|
|
2441
|
+
* - 总数超过 maxReflections 时截断到前 N 条
|
|
2442
|
+
*
|
|
2443
|
+
* @param items — 本轮工具执行结果列表
|
|
2444
|
+
* @param ctx — 上下文(writeRoots / cwd,用于生成 hint)
|
|
2445
|
+
* @returns 反射列表(可能为空;最多 maxReflections 条)
|
|
2446
|
+
*
|
|
2447
|
+
* @pure 不修改任何状态
|
|
2448
|
+
*/
|
|
2449
|
+
analyze(items, ctx) {
|
|
2450
|
+
const reflections = [];
|
|
2451
|
+
for (const item of items) {
|
|
2452
|
+
if (item.result.success) continue;
|
|
2453
|
+
const r = this.#ruleRepeatedFailure(item) ?? this.#ruleFileNotFound(item) ?? this.#rulePermissionDenied(item) ?? this.#ruleOutOfWriteRoot(item, ctx.writeRoots);
|
|
2454
|
+
if (r) reflections.push(r);
|
|
2455
|
+
if (reflections.length >= this.#maxReflections) break;
|
|
2456
|
+
}
|
|
2457
|
+
return reflections;
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* 把反射列表拼成"反思"section,追加到 system prompt 末尾。
|
|
2461
|
+
*
|
|
2462
|
+
* 输出格式:
|
|
2463
|
+
* ```
|
|
2464
|
+
* {原 prompt}
|
|
2465
|
+
*
|
|
2466
|
+
* ## ⚠️ 上一轮工具调用的反思
|
|
2467
|
+
* - 工具 `<tool>`:<hint>
|
|
2468
|
+
* - 工具 `<tool>`:<hint>
|
|
2469
|
+
* ```
|
|
2470
|
+
*
|
|
2471
|
+
* @param systemPrompt — 原始 system prompt
|
|
2472
|
+
* @param reflections — 反射列表(空数组时原样返回)
|
|
2473
|
+
* @returns 拼装后的新 prompt
|
|
2474
|
+
*
|
|
2475
|
+
* @pure 不修改入参
|
|
2476
|
+
*/
|
|
2477
|
+
injectIntoPrompt(systemPrompt, reflections) {
|
|
2478
|
+
if (reflections.length === 0) return systemPrompt;
|
|
2479
|
+
const lines = reflections.map((r) => {
|
|
2480
|
+
const hint = this.#truncate(r.hint);
|
|
2481
|
+
return `- \u5DE5\u5177 \`${r.toolName}\` \u5931\u8D25\uFF1A${hint}`;
|
|
2482
|
+
});
|
|
2483
|
+
const section = [
|
|
2484
|
+
"## \u26A0\uFE0F \u4E0A\u4E00\u8F6E\u5DE5\u5177\u8C03\u7528\u7684\u53CD\u601D",
|
|
2485
|
+
"\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",
|
|
2486
|
+
...lines
|
|
2487
|
+
].join("\n");
|
|
2488
|
+
return systemPrompt + "\n\n" + section;
|
|
2489
|
+
}
|
|
2490
|
+
// -------------------------------------------------------------------------
|
|
2491
|
+
// 规则实现(按 R1 → R2 → R3 → R4 顺序)
|
|
2492
|
+
// -------------------------------------------------------------------------
|
|
2493
|
+
/**
|
|
2494
|
+
* R1 连续失败:同一工具连续 ≥#repeatThreshold 次相同错误码失败。
|
|
2495
|
+
*
|
|
2496
|
+
* @pure 仅读入参
|
|
2497
|
+
*/
|
|
2498
|
+
#ruleRepeatedFailure(item) {
|
|
2499
|
+
const recent = item.recentSameTool;
|
|
2500
|
+
if (recent.length < this.#repeatThreshold) return null;
|
|
2501
|
+
const lastN = recent.slice(-this.#repeatThreshold);
|
|
2502
|
+
const first = lastN[0];
|
|
2503
|
+
if (!lastN.every((r) => !r.success && r.error === first.error)) {
|
|
2504
|
+
return null;
|
|
2505
|
+
}
|
|
2506
|
+
const errText = first.error ?? "\u672A\u77E5\u9519\u8BEF";
|
|
2507
|
+
return {
|
|
2508
|
+
category: "repeated_failure",
|
|
2509
|
+
toolName: item.name,
|
|
2510
|
+
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`
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
/**
|
|
2514
|
+
* R2 文件不存在:TOOL_NOT_FOUND 错误码 或 data 含 ENOENT / not found / No such file。
|
|
2515
|
+
*
|
|
2516
|
+
* @pure 仅读入参
|
|
2517
|
+
*/
|
|
2518
|
+
#ruleFileNotFound(item) {
|
|
2519
|
+
const isToolNotFound = item.result.error === "TOOL_NOT_FOUND";
|
|
2520
|
+
const data = item.result.data ?? "";
|
|
2521
|
+
const hasKeyword = data.includes("ENOENT") || data.includes("No such file") || /\bnot found\b/i.test(data);
|
|
2522
|
+
if (!isToolNotFound && !hasKeyword) return null;
|
|
2523
|
+
return {
|
|
2524
|
+
category: "file_not_found",
|
|
2525
|
+
toolName: item.name,
|
|
2526
|
+
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`
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
/**
|
|
2530
|
+
* R3 权限拒绝:GATE_DENIED 错误码 或 data 含 EACCES / permission / denied(不区分大小写)。
|
|
2531
|
+
*
|
|
2532
|
+
* @pure 仅读入参
|
|
2533
|
+
*/
|
|
2534
|
+
#rulePermissionDenied(item) {
|
|
2535
|
+
const isGateDenied = item.result.error === "GATE_DENIED";
|
|
2536
|
+
const data = item.result.data ?? "";
|
|
2537
|
+
const hasKeyword = data.includes("EACCES") || /\bpermission\b/i.test(data) || /\bdenied\b/i.test(data);
|
|
2538
|
+
if (!isGateDenied && !hasKeyword) return null;
|
|
2539
|
+
return {
|
|
2540
|
+
category: "permission_denied",
|
|
2541
|
+
toolName: item.name,
|
|
2542
|
+
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`
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
/**
|
|
2546
|
+
* R4 写根外:kind ∈ Edit/Delete/Move 且失败。
|
|
2547
|
+
*
|
|
2548
|
+
* @pure 仅读入参
|
|
2549
|
+
*/
|
|
2550
|
+
#ruleOutOfWriteRoot(item, writeRoots) {
|
|
2551
|
+
const isWriteKind = item.kind === "edit" /* Edit */ || item.kind === "delete" /* Delete */ || item.kind === "move" /* Move */;
|
|
2552
|
+
if (!isWriteKind) return null;
|
|
2553
|
+
const primary = writeRoots[0] ?? "<\u672A\u914D\u7F6E>";
|
|
2554
|
+
return {
|
|
2555
|
+
category: "out_of_write_root",
|
|
2556
|
+
toolName: item.name,
|
|
2557
|
+
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`
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* 截断超长 hint,末尾加省略号。
|
|
2562
|
+
*
|
|
2563
|
+
* @pure 字符串处理
|
|
2564
|
+
*/
|
|
2565
|
+
#truncate(text) {
|
|
2566
|
+
if (text.length <= this.#maxHintChars) return text;
|
|
2567
|
+
return text.slice(0, this.#maxHintChars - 3) + "...";
|
|
2568
|
+
}
|
|
2569
|
+
};
|
|
2570
|
+
|
|
2571
|
+
// src/agent/tool-definitions.ts
|
|
2572
|
+
function buildToolDefinitions(registry2, mode) {
|
|
2573
|
+
const tools = mode === "plan" ? registry2.listReadTools() : registry2.list();
|
|
2574
|
+
return tools.map((t) => ({
|
|
2575
|
+
type: "function",
|
|
2576
|
+
function: {
|
|
2577
|
+
name: t.name,
|
|
2578
|
+
description: t.description,
|
|
2579
|
+
parameters: t.parameters
|
|
2580
|
+
}
|
|
2581
|
+
}));
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2117
2584
|
// src/checkpoint/git-checkpoint.ts
|
|
2118
2585
|
import { execFile } from "child_process";
|
|
2119
2586
|
import { promisify } from "util";
|
|
@@ -2277,21 +2744,271 @@ function isENOENT(err) {
|
|
|
2277
2744
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
2278
2745
|
}
|
|
2279
2746
|
|
|
2747
|
+
// src/logger/logger.ts
|
|
2748
|
+
import { mkdir as mkdir5, appendFile } from "fs/promises";
|
|
2749
|
+
import { fileURLToPath } from "url";
|
|
2750
|
+
import { join as join5, basename, sep } from "path";
|
|
2751
|
+
var LOGGER_DIR = "logger";
|
|
2752
|
+
var MAX_DATA_LEN = 2e3;
|
|
2753
|
+
var MAX_QUEUE = 500;
|
|
2754
|
+
function defaultLogsDir() {
|
|
2755
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
2756
|
+
return join5(home, ".dskcode", "logs");
|
|
2757
|
+
}
|
|
2758
|
+
function projectNameFromCwd(cwd) {
|
|
2759
|
+
const name = basename(cwd);
|
|
2760
|
+
return name || "_root";
|
|
2761
|
+
}
|
|
2762
|
+
var ConversationLogger = class _ConversationLogger {
|
|
2763
|
+
/** 所有活跃的 logger 实例(用于退出时统一 flush) */
|
|
2764
|
+
static #instances = /* @__PURE__ */ new Set();
|
|
2765
|
+
/**
|
|
2766
|
+
* 刷新所有活跃 logger 实例的缓冲,确保事件落盘。
|
|
2767
|
+
* 进程退出前调用。
|
|
2768
|
+
*/
|
|
2769
|
+
static async flushAll() {
|
|
2770
|
+
const promises = [];
|
|
2771
|
+
for (const logger of _ConversationLogger.#instances) {
|
|
2772
|
+
promises.push(logger.flush());
|
|
2773
|
+
}
|
|
2774
|
+
await Promise.allSettled(promises);
|
|
2775
|
+
}
|
|
2776
|
+
#logPath;
|
|
2777
|
+
#enabled;
|
|
2778
|
+
#queue = Promise.resolve();
|
|
2779
|
+
#queueLen = 0;
|
|
2780
|
+
#closed = false;
|
|
2781
|
+
constructor(sessionId, cwd, options) {
|
|
2782
|
+
this.#enabled = options?.enabled ?? true;
|
|
2783
|
+
if (!this.#enabled) {
|
|
2784
|
+
this.#logPath = "";
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
const baseDir = options?.logsDir ?? defaultLogsDir();
|
|
2788
|
+
const projectName = projectNameFromCwd(cwd);
|
|
2789
|
+
this.#logPath = join5(baseDir, projectName, `${sessionId}.jsonl`);
|
|
2790
|
+
_ConversationLogger.#instances.add(this);
|
|
2791
|
+
}
|
|
2792
|
+
/** 日志文件完整路径(禁用时返回空字符串) */
|
|
2793
|
+
get logPath() {
|
|
2794
|
+
return this.#logPath;
|
|
2795
|
+
}
|
|
2796
|
+
/** 是否启用日志记录 */
|
|
2797
|
+
get enabled() {
|
|
2798
|
+
return this.#enabled;
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* 记录一个事件。
|
|
2802
|
+
*
|
|
2803
|
+
* 内部将事件序列化为 pretty JSON 并以多行块的形式追加到日志文件。
|
|
2804
|
+
* 写入操作串行排队,保证事件顺序。调用方无需 await。
|
|
2805
|
+
*/
|
|
2806
|
+
log(event) {
|
|
2807
|
+
if (!this.#enabled || this.#closed) return;
|
|
2808
|
+
if (this.#queueLen >= MAX_QUEUE) return;
|
|
2809
|
+
const enriched = {
|
|
2810
|
+
...event,
|
|
2811
|
+
time: event.time ?? formatTime(event.ts),
|
|
2812
|
+
loc: event.loc ?? captureCallerLocation()
|
|
2813
|
+
};
|
|
2814
|
+
this.#queueLen++;
|
|
2815
|
+
this.#queue = this.#queue.then(() => this.#writeBlock(enriched)).catch(() => {
|
|
2816
|
+
}).finally(() => this.#queueLen--);
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* 关闭日志记录器。
|
|
2820
|
+
*
|
|
2821
|
+
* 标记关闭,不再接受新事件,但会等待队列中已有事件写入完成。
|
|
2822
|
+
* 返回的 Promise resolve 后保证所有已 log 的事件都已落盘。
|
|
2823
|
+
*/
|
|
2824
|
+
async flush() {
|
|
2825
|
+
this.#closed = true;
|
|
2826
|
+
await this.#queue.catch(() => {
|
|
2827
|
+
});
|
|
2828
|
+
_ConversationLogger.#instances.delete(this);
|
|
2829
|
+
}
|
|
2830
|
+
/** 将一条事件以多行块的形式写入日志文件 */
|
|
2831
|
+
async #writeBlock(event) {
|
|
2832
|
+
const dir = join5(this.#logPath, "..");
|
|
2833
|
+
await mkdir5(dir, { recursive: true });
|
|
2834
|
+
const block = formatBlock(event);
|
|
2835
|
+
await appendFile(this.#logPath, block, "utf-8");
|
|
2836
|
+
}
|
|
2837
|
+
// -----------------------------------------------------------------------
|
|
2838
|
+
// 便捷方法 — 封装常见事件类型,减少调用方样板代码
|
|
2839
|
+
// -----------------------------------------------------------------------
|
|
2840
|
+
/** 记录会话开始 */
|
|
2841
|
+
logSessionStart(sessionId, cwd, model, mode) {
|
|
2842
|
+
this.log({ ts: Date.now(), type: "session_start", sessionId, cwd, model, mode });
|
|
2843
|
+
}
|
|
2844
|
+
/** 记录用户消息 */
|
|
2845
|
+
logUserMessage(content) {
|
|
2846
|
+
this.log({ ts: Date.now(), type: "user_message", content });
|
|
2847
|
+
}
|
|
2848
|
+
/** 记录助手文本(一轮中模型输出的完整文本) */
|
|
2849
|
+
logAssistantText(content, round) {
|
|
2850
|
+
this.log({ ts: Date.now(), type: "assistant_text", content, round });
|
|
2851
|
+
}
|
|
2852
|
+
/** 记录工具调用 */
|
|
2853
|
+
logToolCall(name, callId, args, round) {
|
|
2854
|
+
this.log({ ts: Date.now(), type: "tool_call", name, callId, arguments: args, round });
|
|
2855
|
+
}
|
|
2856
|
+
/** 记录工具结果 */
|
|
2857
|
+
logToolResult(name, callId, success, data, error, elapsed, round) {
|
|
2858
|
+
this.log({
|
|
2859
|
+
ts: Date.now(),
|
|
2860
|
+
type: "tool_result",
|
|
2861
|
+
name,
|
|
2862
|
+
callId,
|
|
2863
|
+
success,
|
|
2864
|
+
data: truncate(data),
|
|
2865
|
+
...error ? { error } : {},
|
|
2866
|
+
...elapsed !== void 0 ? { elapsed } : {},
|
|
2867
|
+
round
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
/** 记录 Token 用量与费用 */
|
|
2871
|
+
logUsage(model, promptTokens, completionTokens, cachedPromptTokens, cost, round) {
|
|
2872
|
+
this.log({
|
|
2873
|
+
ts: Date.now(),
|
|
2874
|
+
type: "usage",
|
|
2875
|
+
model,
|
|
2876
|
+
promptTokens,
|
|
2877
|
+
completionTokens,
|
|
2878
|
+
...cachedPromptTokens !== void 0 ? { cachedPromptTokens } : {},
|
|
2879
|
+
cost,
|
|
2880
|
+
round
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
/** 记录错误 */
|
|
2884
|
+
logError(message, stack) {
|
|
2885
|
+
this.log({ ts: Date.now(), type: "error", message, ...stack ? { stack } : {} });
|
|
2886
|
+
}
|
|
2887
|
+
/** 记录一轮对话完成 */
|
|
2888
|
+
logTurnDone(elapsed, toolRounds) {
|
|
2889
|
+
this.log({ ts: Date.now(), type: "turn_done", elapsed, toolRounds });
|
|
2890
|
+
}
|
|
2891
|
+
/** 记录会话结束 */
|
|
2892
|
+
logSessionEnd(elapsed) {
|
|
2893
|
+
this.log({ ts: Date.now(), type: "session_end", elapsed });
|
|
2894
|
+
}
|
|
2895
|
+
/** 记录反射(工具失败归因注入到下一轮 prompt 时) */
|
|
2896
|
+
logReflections(items) {
|
|
2897
|
+
this.log({ ts: Date.now(), type: "reflection", items });
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
function truncate(text) {
|
|
2901
|
+
if (text.length <= MAX_DATA_LEN) return text;
|
|
2902
|
+
return text.slice(0, MAX_DATA_LEN) + `...[\u5DF2\u622A\u65AD\uFF0C\u539F\u59CB\u957F\u5EA6 ${text.length}]`;
|
|
2903
|
+
}
|
|
2904
|
+
function formatTime(ts) {
|
|
2905
|
+
const d = new Date(ts);
|
|
2906
|
+
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
2907
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
|
|
2908
|
+
}
|
|
2909
|
+
var projectRootCache = null;
|
|
2910
|
+
function getProjectRoot() {
|
|
2911
|
+
if (projectRootCache !== null) return projectRootCache;
|
|
2912
|
+
try {
|
|
2913
|
+
const here = fileURLToPath(import.meta.url);
|
|
2914
|
+
const segments = here.split(sep);
|
|
2915
|
+
const srcIdx = segments.lastIndexOf("src");
|
|
2916
|
+
projectRootCache = srcIdx > 0 ? segments.slice(0, srcIdx).join(sep) : here;
|
|
2917
|
+
} catch {
|
|
2918
|
+
projectRootCache = "";
|
|
2919
|
+
}
|
|
2920
|
+
return projectRootCache;
|
|
2921
|
+
}
|
|
2922
|
+
var STACK_FRAME_RE = /\s+at\s+.+?\((.+):(\d+):\d+\)|\s+at\s+(.+):(\d+):\d+/;
|
|
2923
|
+
function captureCallerLocation() {
|
|
2924
|
+
let stack;
|
|
2925
|
+
try {
|
|
2926
|
+
const err = new Error();
|
|
2927
|
+
Error.captureStackTrace?.(err, captureCallerLocation);
|
|
2928
|
+
stack = err.stack;
|
|
2929
|
+
} catch {
|
|
2930
|
+
return { file: "<unknown>", line: 0 };
|
|
2931
|
+
}
|
|
2932
|
+
if (!stack) return { file: "<unknown>", line: 0 };
|
|
2933
|
+
const root = getProjectRoot();
|
|
2934
|
+
const lines = stack.split("\n");
|
|
2935
|
+
for (const raw of lines) {
|
|
2936
|
+
const m = STACK_FRAME_RE.exec(raw);
|
|
2937
|
+
if (!m) continue;
|
|
2938
|
+
const filePath = m[1] ?? m[3] ?? "";
|
|
2939
|
+
const lineNo = Number(m[2] ?? m[4] ?? "0");
|
|
2940
|
+
if (!filePath) continue;
|
|
2941
|
+
const normalized = filePath.replace(/^file:\/\/\//, "").replace(/^file:\/\//, "");
|
|
2942
|
+
if (normalized.includes(`${sep}${LOGGER_DIR}${sep}`) || normalized.endsWith(`${sep}${LOGGER_DIR}`)) {
|
|
2943
|
+
continue;
|
|
2944
|
+
}
|
|
2945
|
+
if (normalized.includes(`${sep}node_modules${sep}`)) continue;
|
|
2946
|
+
let rel = normalized;
|
|
2947
|
+
if (root && normalized.startsWith(root)) {
|
|
2948
|
+
rel = normalized.slice(root.length).replace(/^[/\\]/, "") || basename(normalized);
|
|
2949
|
+
}
|
|
2950
|
+
return { file: rel || basename(normalized), line: lineNo };
|
|
2951
|
+
}
|
|
2952
|
+
return { file: "<unknown>", line: 0 };
|
|
2953
|
+
}
|
|
2954
|
+
var SEPARATOR_WIDTH = 80;
|
|
2955
|
+
var SEPARATOR = "\u2500".repeat(SEPARATOR_WIDTH);
|
|
2956
|
+
function formatBlock(event) {
|
|
2957
|
+
const header = `[${event.time}] [${event.type}] @ ${event.loc.file}:${event.loc.line}`;
|
|
2958
|
+
const body = JSON.stringify(event, null, 2);
|
|
2959
|
+
return `${SEPARATOR}
|
|
2960
|
+
${header}
|
|
2961
|
+
${body}
|
|
2962
|
+
|
|
2963
|
+
`;
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2280
2966
|
// src/agent/index.ts
|
|
2281
2967
|
var Session = class _Session {
|
|
2968
|
+
/** 当前会话的消息历史(含 system/user/assistant/tool),可外部只读 */
|
|
2282
2969
|
#messages = [];
|
|
2970
|
+
/** LLM Provider(DeepSeek 等) */
|
|
2283
2971
|
#provider;
|
|
2972
|
+
/** 工具注册表(所有可用工具) */
|
|
2284
2973
|
#toolRegistry;
|
|
2974
|
+
/** 成本追踪器(今日 + 本会话) */
|
|
2285
2975
|
#costTracker;
|
|
2976
|
+
/** 归一化后的构造选项(带默认值) */
|
|
2286
2977
|
#options;
|
|
2978
|
+
/** 中止信号控制器(abort() 时触发,传递给 LLM 和工具) */
|
|
2287
2979
|
#abortController = new AbortController();
|
|
2980
|
+
/** 会话唯一 ID(UUID) */
|
|
2288
2981
|
#sessionId;
|
|
2982
|
+
/** 持久化存储;传 false 时为 null */
|
|
2289
2983
|
#store;
|
|
2984
|
+
/** 会话创建时间(毫秒) */
|
|
2290
2985
|
#createdAt;
|
|
2986
|
+
/** 节流持久化定时器(500ms debounce) */
|
|
2291
2987
|
#persistTimer = null;
|
|
2988
|
+
/** user 消息 → Checkpoint 映射(仅给 user 消息建点) */
|
|
2292
2989
|
#checkpoints = /* @__PURE__ */ new Map();
|
|
2990
|
+
/** 最近的失败记录(仅失败的,用于风暴检测) */
|
|
2293
2991
|
#stormRecords = [];
|
|
2992
|
+
/** 当前会话模式:code(默认)/ plan(只读) */
|
|
2294
2993
|
#mode = "code";
|
|
2994
|
+
/** 对话日志记录器(写入 .dskcode/logs/) */
|
|
2995
|
+
#logger;
|
|
2996
|
+
/** 风暴检测器(连续 3 次同工具同错码失败时中断本轮) */
|
|
2997
|
+
#stormDetector;
|
|
2998
|
+
/** 失败归因 Reflector(将本轮失败原因拼到下一轮 prompt;null 表示已关闭) */
|
|
2999
|
+
#reflector;
|
|
3000
|
+
/** 本轮工具执行结果(仅在 chat() 循环内使用;用于下一轮注入 reflection) */
|
|
3001
|
+
#lastRoundResults = null;
|
|
3002
|
+
/**
|
|
3003
|
+
* 构造一个 Session。
|
|
3004
|
+
*
|
|
3005
|
+
* @param provider — LLM Provider 实例(必须)
|
|
3006
|
+
* @param tools — 工具列表或已初始化的 ToolRegistry(默认空)
|
|
3007
|
+
* @param costTracker — 成本追踪器(默认新建)
|
|
3008
|
+
* @param options — 会话选项(详见 SessionOptions)
|
|
3009
|
+
*
|
|
3010
|
+
* @sideEffect 创建 .dskcode/logs/ 下的日志文件并写入 session_start 事件
|
|
3011
|
+
*/
|
|
2295
3012
|
constructor(provider, tools = [], costTracker, options) {
|
|
2296
3013
|
this.#provider = provider;
|
|
2297
3014
|
if (tools instanceof ToolRegistry) {
|
|
@@ -2309,45 +3026,87 @@ var Session = class _Session {
|
|
|
2309
3026
|
projectContext: options?.projectContext,
|
|
2310
3027
|
gate: options?.gate ?? new AlwaysAllowGate(),
|
|
2311
3028
|
writeRoots: options?.writeRoots ?? [options?.cwd ?? process.cwd()],
|
|
2312
|
-
enableCheckpoint: options?.enableCheckpoint ?? true
|
|
3029
|
+
enableCheckpoint: options?.enableCheckpoint ?? true,
|
|
3030
|
+
enableLog: options?.enableLog ?? true,
|
|
3031
|
+
enableReflection: options?.enableReflection !== false
|
|
2313
3032
|
};
|
|
2314
3033
|
this.#sessionId = options?.sessionId ?? SessionStore.newId();
|
|
2315
3034
|
this.#store = options?.store === false ? null : options?.store ?? new SessionStore();
|
|
2316
3035
|
this.#createdAt = Date.now();
|
|
3036
|
+
this.#logger = new ConversationLogger(this.#sessionId, this.#options.cwd, {
|
|
3037
|
+
enabled: this.#options.enableLog
|
|
3038
|
+
});
|
|
3039
|
+
this.#logger.logSessionStart(this.#sessionId, this.#options.cwd, this.#provider.model(), this.#mode);
|
|
3040
|
+
this.#stormDetector = new StormDetector({ threshold: 3 });
|
|
3041
|
+
this.#reflector = this.#options.enableReflection ? new Reflector() : null;
|
|
2317
3042
|
}
|
|
3043
|
+
/** 当前会话的完整消息历史(只读视图) */
|
|
2318
3044
|
get messages() {
|
|
2319
3045
|
return this.#messages;
|
|
2320
3046
|
}
|
|
3047
|
+
/** 本次会话累计成本(人民币元) */
|
|
2321
3048
|
get accumulatedCost() {
|
|
2322
3049
|
return this.#costTracker.sessionTotalCost;
|
|
2323
3050
|
}
|
|
3051
|
+
/** 成本追踪器(含今日总成本、会话成本、模型维度统计) */
|
|
2324
3052
|
get costTracker() {
|
|
2325
3053
|
return this.#costTracker;
|
|
2326
3054
|
}
|
|
3055
|
+
/** 当前模型标识(如 "deepseek-v4-flash") */
|
|
2327
3056
|
get model() {
|
|
2328
3057
|
return this.#provider.model();
|
|
2329
3058
|
}
|
|
3059
|
+
/** 工具注册表(可外部读 / 运行时 register) */
|
|
2330
3060
|
get toolRegistry() {
|
|
2331
3061
|
return this.#toolRegistry;
|
|
2332
3062
|
}
|
|
3063
|
+
/** 当前模式:"code" | "plan" */
|
|
2333
3064
|
get mode() {
|
|
2334
3065
|
return this.#mode;
|
|
2335
3066
|
}
|
|
3067
|
+
/** 会话 ID(UUID) */
|
|
2336
3068
|
get id() {
|
|
2337
3069
|
return this.#sessionId;
|
|
2338
3070
|
}
|
|
3071
|
+
/** 持久化存储实例(禁用持久化时为 null) */
|
|
2339
3072
|
get store() {
|
|
2340
3073
|
return this.#store;
|
|
2341
3074
|
}
|
|
3075
|
+
/** 会话创建时间戳(毫秒) */
|
|
2342
3076
|
get createdAt() {
|
|
2343
3077
|
return this.#createdAt;
|
|
2344
3078
|
}
|
|
3079
|
+
/**
|
|
3080
|
+
* 切换会话模式。
|
|
3081
|
+
*
|
|
3082
|
+
* @param mode — "code":全部工具;"plan":只暴露只读工具
|
|
3083
|
+
* @returns 设置后的模式(便于链式调用)
|
|
3084
|
+
*/
|
|
2345
3085
|
setMode(mode) {
|
|
2346
3086
|
this.#mode = mode;
|
|
2347
3087
|
return this.#mode;
|
|
2348
3088
|
}
|
|
3089
|
+
/**
|
|
3090
|
+
* chat() — 接收一轮用户输入,串起 LLM 调用 / 工具执行 / 风暴中断 / 持久化。
|
|
3091
|
+
*
|
|
3092
|
+
* 主循环每轮做:
|
|
3093
|
+
* 1. 构建 system prompt + 裁剪消息 + 拼装 apiMessages
|
|
3094
|
+
* 2. 调 provider.chat() 拿流式 chunks,向外 yield text_delta / usage 事件
|
|
3095
|
+
* 3. 若本轮有 tool_calls:
|
|
3096
|
+
* a. 先调 StormDetector.shouldBreak 判定是否中断风暴
|
|
3097
|
+
* b. 调 ToolExecutor.executeBatch 执行工具
|
|
3098
|
+
* c. 把工具结果 yield 出去并写入 messages
|
|
3099
|
+
* 4. 持续到 LLM 不再调工具 或 达到 maxToolRounds
|
|
3100
|
+
*
|
|
3101
|
+
* @param userInput — 用户本轮输入
|
|
3102
|
+
* @param opts — 透传给 provider.chat() 的 ChatOptions(thinking / tool_choice 等)
|
|
3103
|
+
* @yields AgentEvent — text_delta / tool_calls / tool_result / usage / done / error
|
|
3104
|
+
*
|
|
3105
|
+
* @sideEffect 写入 messages、logger、costTracker、可能触发 checkpoint / persist
|
|
3106
|
+
*/
|
|
2349
3107
|
async *chat(userInput, opts) {
|
|
2350
3108
|
this.#messages.push({ role: "user", content: userInput });
|
|
3109
|
+
this.#logger.logUserMessage(userInput);
|
|
2351
3110
|
const userMsgIndex = this.#messages.length - 1;
|
|
2352
3111
|
if (this.#options.enableCheckpoint) {
|
|
2353
3112
|
try {
|
|
@@ -2358,9 +3117,23 @@ var Session = class _Session {
|
|
|
2358
3117
|
}
|
|
2359
3118
|
const startTime = Date.now();
|
|
2360
3119
|
let toolRounds = 0;
|
|
3120
|
+
const toolExecutor = this.#buildToolExecutor();
|
|
2361
3121
|
try {
|
|
2362
3122
|
while (toolRounds < this.#options.maxToolRounds) {
|
|
2363
|
-
|
|
3123
|
+
let systemPrompt = this.#buildSystemPrompt();
|
|
3124
|
+
if (this.#reflector && this.#lastRoundResults) {
|
|
3125
|
+
const reflections = this.#reflector.analyze(this.#lastRoundResults, {
|
|
3126
|
+
writeRoots: this.#options.writeRoots,
|
|
3127
|
+
cwd: this.#options.cwd
|
|
3128
|
+
});
|
|
3129
|
+
if (reflections.length > 0) {
|
|
3130
|
+
systemPrompt = this.#reflector.injectIntoPrompt(systemPrompt, reflections);
|
|
3131
|
+
this.#logger.logReflections(
|
|
3132
|
+
reflections.map((r) => ({ category: r.category, toolName: r.toolName, hint: r.hint }))
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
this.#lastRoundResults = null;
|
|
3136
|
+
}
|
|
2364
3137
|
const [trimmed] = trimMessages(
|
|
2365
3138
|
[...this.#messages],
|
|
2366
3139
|
{
|
|
@@ -2371,7 +3144,7 @@ var Session = class _Session {
|
|
|
2371
3144
|
}
|
|
2372
3145
|
);
|
|
2373
3146
|
const apiMessages = buildApiMessages(systemPrompt, trimmed);
|
|
2374
|
-
const toolDefs = this.#
|
|
3147
|
+
const toolDefs = buildToolDefinitions(this.#toolRegistry, this.#mode);
|
|
2375
3148
|
const stream = this.#provider.chat(apiMessages, {
|
|
2376
3149
|
signal: this.#abortController.signal,
|
|
2377
3150
|
tools: toolDefs.length > 0 ? toolDefs : void 0,
|
|
@@ -2380,30 +3153,91 @@ var Session = class _Session {
|
|
|
2380
3153
|
responseFormat: opts?.responseFormat,
|
|
2381
3154
|
toolChoice: opts?.toolChoice
|
|
2382
3155
|
});
|
|
3156
|
+
const modelId = this.#provider.model();
|
|
2383
3157
|
let accumulatedText = "";
|
|
2384
3158
|
let lastUsage;
|
|
2385
3159
|
let lastToolCalls;
|
|
2386
3160
|
let _lastFinishReason = null;
|
|
3161
|
+
let estimatedInputTokens = 0;
|
|
3162
|
+
for (const m of apiMessages) {
|
|
3163
|
+
estimatedInputTokens += this.#provider.countTokens(m.content) + 10;
|
|
3164
|
+
}
|
|
3165
|
+
let lastEmitMs = 0;
|
|
3166
|
+
const EST_EMIT_INTERVAL_MS = 300;
|
|
2387
3167
|
for await (const chunk of stream) {
|
|
2388
3168
|
if (chunk.content) {
|
|
2389
3169
|
accumulatedText += chunk.content;
|
|
2390
3170
|
yield { type: "text_delta", content: chunk.content };
|
|
3171
|
+
const now = Date.now();
|
|
3172
|
+
if (now - lastEmitMs >= EST_EMIT_INTERVAL_MS) {
|
|
3173
|
+
lastEmitMs = now;
|
|
3174
|
+
const estOutput = this.#provider.countTokens(accumulatedText);
|
|
3175
|
+
const estUsage = {
|
|
3176
|
+
promptTokens: estimatedInputTokens,
|
|
3177
|
+
completionTokens: estOutput
|
|
3178
|
+
};
|
|
3179
|
+
const estCost = calculateCost(estUsage, modelId);
|
|
3180
|
+
yield {
|
|
3181
|
+
type: "usage",
|
|
3182
|
+
usage: estUsage,
|
|
3183
|
+
model: modelId,
|
|
3184
|
+
cost: estCost.totalCost,
|
|
3185
|
+
estimated: true
|
|
3186
|
+
};
|
|
3187
|
+
}
|
|
2391
3188
|
}
|
|
2392
3189
|
if (chunk.toolCalls && chunk.toolCalls.length > 0) lastToolCalls = chunk.toolCalls;
|
|
2393
|
-
if (chunk.usage)
|
|
3190
|
+
if (chunk.usage) {
|
|
3191
|
+
lastUsage = chunk.usage;
|
|
3192
|
+
const realCost = calculateCost(chunk.usage, modelId);
|
|
3193
|
+
yield {
|
|
3194
|
+
type: "usage",
|
|
3195
|
+
usage: chunk.usage,
|
|
3196
|
+
model: modelId,
|
|
3197
|
+
cost: realCost.totalCost,
|
|
3198
|
+
estimated: false
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
2394
3201
|
if (chunk.finishReason) _lastFinishReason = chunk.finishReason;
|
|
2395
3202
|
}
|
|
3203
|
+
if (accumulatedText && !lastUsage) {
|
|
3204
|
+
const estOutput = this.#provider.countTokens(accumulatedText);
|
|
3205
|
+
const estUsage = {
|
|
3206
|
+
promptTokens: estimatedInputTokens,
|
|
3207
|
+
completionTokens: estOutput
|
|
3208
|
+
};
|
|
3209
|
+
const estCost = calculateCost(estUsage, modelId);
|
|
3210
|
+
yield {
|
|
3211
|
+
type: "usage",
|
|
3212
|
+
usage: estUsage,
|
|
3213
|
+
model: modelId,
|
|
3214
|
+
cost: estCost.totalCost,
|
|
3215
|
+
estimated: true
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
2396
3218
|
if (lastUsage) {
|
|
2397
|
-
const modelId = this.#provider.model();
|
|
2398
3219
|
this.#costTracker.record(lastUsage, modelId);
|
|
3220
|
+
const cost = calculateCost(lastUsage, modelId);
|
|
3221
|
+
this.#logger.logUsage(
|
|
3222
|
+
modelId,
|
|
3223
|
+
lastUsage.promptTokens,
|
|
3224
|
+
lastUsage.completionTokens,
|
|
3225
|
+
lastUsage.cachedPromptTokens,
|
|
3226
|
+
cost.totalCost,
|
|
3227
|
+
toolRounds
|
|
3228
|
+
);
|
|
2399
3229
|
yield { type: "usage", usage: lastUsage, model: modelId };
|
|
2400
3230
|
}
|
|
2401
3231
|
const assistantMsg = { role: "assistant", content: accumulatedText };
|
|
2402
3232
|
if (lastToolCalls && lastToolCalls.length > 0) assistantMsg.toolCalls = lastToolCalls;
|
|
2403
3233
|
this.#messages.push(assistantMsg);
|
|
3234
|
+
this.#logger.logAssistantText(accumulatedText, toolRounds);
|
|
2404
3235
|
if (lastToolCalls && lastToolCalls.length > 0) {
|
|
2405
3236
|
yield { type: "tool_calls", calls: lastToolCalls };
|
|
2406
|
-
const
|
|
3237
|
+
for (const tc of lastToolCalls) {
|
|
3238
|
+
this.#logger.logToolCall(tc.name, tc.id, tc.arguments, toolRounds);
|
|
3239
|
+
}
|
|
3240
|
+
const stormBroken = this.#stormDetector.shouldBreak(this.#stormRecords, lastToolCalls);
|
|
2407
3241
|
if (stormBroken) {
|
|
2408
3242
|
const stormMsg = "\n\u26A0\uFE0F \u540C\u4E00\u5DE5\u5177\u91CD\u590D\u51FA\u9519\uFF0C\u5DF2\u5F3A\u5236\u5207\u6362\u7B56\u7565\n";
|
|
2409
3243
|
yield { type: "text_delta", content: stormMsg };
|
|
@@ -2413,10 +3247,29 @@ var Session = class _Session {
|
|
|
2413
3247
|
toolRounds++;
|
|
2414
3248
|
continue;
|
|
2415
3249
|
}
|
|
2416
|
-
const results = await
|
|
3250
|
+
const results = await toolExecutor.executeBatch(lastToolCalls);
|
|
2417
3251
|
this.#stormRecords = results.records;
|
|
3252
|
+
console.log("results", results);
|
|
3253
|
+
this.#lastRoundResults = results.items.map((it) => {
|
|
3254
|
+
const tool = this.#toolRegistry.get(it.name);
|
|
3255
|
+
return {
|
|
3256
|
+
name: it.name,
|
|
3257
|
+
result: it.result,
|
|
3258
|
+
kind: tool?.kind ?? "other" /* Other */,
|
|
3259
|
+
recentSameTool: results.records.filter((r) => r.name === it.name).map((r) => ({ success: r.success, error: r.error }))
|
|
3260
|
+
};
|
|
3261
|
+
});
|
|
2418
3262
|
for (const item of results.items) {
|
|
2419
3263
|
yield { type: "tool_result", name: item.name, result: item.result };
|
|
3264
|
+
this.#logger.logToolResult(
|
|
3265
|
+
item.name,
|
|
3266
|
+
item.callId,
|
|
3267
|
+
item.result.success,
|
|
3268
|
+
item.result.data,
|
|
3269
|
+
item.result.error,
|
|
3270
|
+
void 0,
|
|
3271
|
+
toolRounds
|
|
3272
|
+
);
|
|
2420
3273
|
let toolContent = item.result.data;
|
|
2421
3274
|
if (item.result.diff && item.result.diff.patch) toolContent += `
|
|
2422
3275
|
|
|
@@ -2436,107 +3289,29 @@ ${item.result.diff.patch}`;
|
|
|
2436
3289
|
} catch (err) {
|
|
2437
3290
|
if (err instanceof DOMException && err.name === "AbortError") return;
|
|
2438
3291
|
if (err instanceof Error && err.name === "AbortError") return;
|
|
3292
|
+
this.#logger.logError(
|
|
3293
|
+
err instanceof Error ? err.message : String(err),
|
|
3294
|
+
err instanceof Error ? err.stack : void 0
|
|
3295
|
+
);
|
|
2439
3296
|
yield { type: "error", error: err instanceof Error ? err : new Error(String(err)) };
|
|
2440
3297
|
return;
|
|
2441
3298
|
}
|
|
2442
3299
|
const elapsed = Date.now() - startTime;
|
|
3300
|
+
this.#logger.logTurnDone(elapsed, toolRounds);
|
|
2443
3301
|
void this.#persist();
|
|
2444
3302
|
await this.#costTracker.flush().catch(() => {
|
|
2445
3303
|
});
|
|
2446
3304
|
yield { type: "done", elapsed };
|
|
2447
3305
|
}
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
});
|
|
2458
|
-
if (allReadOnly && calls.length > 1) {
|
|
2459
|
-
const MAX_PARALLEL = 8;
|
|
2460
|
-
const items2 = [];
|
|
2461
|
-
const records2 = [];
|
|
2462
|
-
for (let i = 0; i < calls.length; i += MAX_PARALLEL) {
|
|
2463
|
-
const batch = calls.slice(i, i + MAX_PARALLEL);
|
|
2464
|
-
const promises = batch.map((tc) => this.#executeOne(tc, toolCtx));
|
|
2465
|
-
const batchResults = await Promise.all(promises);
|
|
2466
|
-
for (const r of batchResults) {
|
|
2467
|
-
items2.push(r.item);
|
|
2468
|
-
records2.push(r.record);
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
return { items: items2, records: records2 };
|
|
2472
|
-
}
|
|
2473
|
-
const items = [];
|
|
2474
|
-
const records = [];
|
|
2475
|
-
for (const tc of calls) {
|
|
2476
|
-
const r = await this.#executeOne(tc, toolCtx);
|
|
2477
|
-
items.push(r.item);
|
|
2478
|
-
records.push(r.record);
|
|
2479
|
-
}
|
|
2480
|
-
return { items, records };
|
|
2481
|
-
}
|
|
2482
|
-
async #executeOne(tc, ctx) {
|
|
2483
|
-
const toolName = tc.name;
|
|
2484
|
-
const timestamp = Date.now();
|
|
2485
|
-
const tool = this.#toolRegistry.get(toolName);
|
|
2486
|
-
if (!tool) {
|
|
2487
|
-
const errMsg = `\u5DE5\u5177 "${toolName}" \u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u7981\u7528`;
|
|
2488
|
-
return {
|
|
2489
|
-
item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "TOOL_NOT_FOUND" } },
|
|
2490
|
-
record: { name: toolName, success: false, error: "TOOL_NOT_FOUND", timestamp }
|
|
2491
|
-
};
|
|
2492
|
-
}
|
|
2493
|
-
let toolArgs;
|
|
2494
|
-
try {
|
|
2495
|
-
toolArgs = tc.arguments ? JSON.parse(tc.arguments) : {};
|
|
2496
|
-
} catch {
|
|
2497
|
-
toolArgs = {};
|
|
2498
|
-
}
|
|
2499
|
-
const gateResult = await this.#options.gate.check(toolName, toolArgs);
|
|
2500
|
-
if (!gateResult) {
|
|
2501
|
-
const errMsg = `\u5DE5\u5177 "${toolName}" \u88AB\u6743\u9650\u95E8\u62D2\u7EDD`;
|
|
2502
|
-
return {
|
|
2503
|
-
item: { name: toolName, callId: tc.id, result: { success: false, data: errMsg, error: "GATE_DENIED" } },
|
|
2504
|
-
record: { name: toolName, success: false, error: "GATE_DENIED", timestamp }
|
|
2505
|
-
};
|
|
2506
|
-
}
|
|
2507
|
-
if (!isReadOnly(tool.kind)) {
|
|
2508
|
-
const maybePreview = tool.preview;
|
|
2509
|
-
if (typeof maybePreview === "function") {
|
|
2510
|
-
try {
|
|
2511
|
-
await maybePreview(toolArgs, ctx);
|
|
2512
|
-
} catch {
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
try {
|
|
2517
|
-
const result = await tool.execute(toolArgs, ctx);
|
|
2518
|
-
return {
|
|
2519
|
-
item: { name: toolName, callId: tc.id, result },
|
|
2520
|
-
record: { name: toolName, success: result.success, error: result.error, timestamp }
|
|
2521
|
-
};
|
|
2522
|
-
} catch (err) {
|
|
2523
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2524
|
-
const errorResult = { success: false, data: `\u5DE5\u5177 "${toolName}" \u6267\u884C\u5F02\u5E38\uFF1A${message}`, error: "EXECUTION_ERROR" };
|
|
2525
|
-
return {
|
|
2526
|
-
item: { name: toolName, callId: tc.id, result: errorResult },
|
|
2527
|
-
record: { name: toolName, success: false, error: "EXECUTION_ERROR", timestamp }
|
|
2528
|
-
};
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
#checkStormBreak(currentCalls) {
|
|
2532
|
-
if (this.#stormRecords.length < 3) return false;
|
|
2533
|
-
const recentErrors = this.#stormRecords.slice(-3);
|
|
2534
|
-
if (recentErrors.length < 3) return false;
|
|
2535
|
-
const first = recentErrors[0];
|
|
2536
|
-
const allSame = recentErrors.every((r) => r.name === first.name && r.error === first.error && !r.success);
|
|
2537
|
-
if (!allSame) return false;
|
|
2538
|
-
return currentCalls.some((tc) => tc.name === first.name);
|
|
2539
|
-
}
|
|
3306
|
+
// -------------------------------------------------------------------------
|
|
3307
|
+
// 持久化与恢复
|
|
3308
|
+
// -------------------------------------------------------------------------
|
|
3309
|
+
/**
|
|
3310
|
+
* 中止当前会话:触发 AbortController,停止流式 LLM 和正在执行的工具。
|
|
3311
|
+
* 同时清掉节流持久化定时器。
|
|
3312
|
+
*
|
|
3313
|
+
* @sideEffect abort 当前 chat 循环、取消所有 #abortController 监听者
|
|
3314
|
+
*/
|
|
2540
3315
|
abort() {
|
|
2541
3316
|
this.#abortController.abort();
|
|
2542
3317
|
if (this.#persistTimer) {
|
|
@@ -2544,15 +3319,34 @@ ${item.result.diff.patch}`;
|
|
|
2544
3319
|
this.#persistTimer = null;
|
|
2545
3320
|
}
|
|
2546
3321
|
}
|
|
3322
|
+
/**
|
|
3323
|
+
* 刷新日志缓冲,确保所有已记录的事件落盘。
|
|
3324
|
+
* 通常在进程退出前调用,避免日志丢失。
|
|
3325
|
+
*
|
|
3326
|
+
* @sideEffect 写入 .dskcode/logs/ 下的日志文件
|
|
3327
|
+
*/
|
|
3328
|
+
async flushLog() {
|
|
3329
|
+
await this.#logger.flush();
|
|
3330
|
+
}
|
|
3331
|
+
/**
|
|
3332
|
+
* 清空消息历史、会话成本、风暴记录、checkpoints。
|
|
3333
|
+
* 注意:不会清掉磁盘上的会话记录(用 delete() 删除),仅重置内存状态。
|
|
3334
|
+
*
|
|
3335
|
+
* @sideEffect 抹掉 messages / costTracker / stormRecords / checkpoints
|
|
3336
|
+
*/
|
|
2547
3337
|
reset() {
|
|
2548
3338
|
this.#messages.length = 0;
|
|
2549
3339
|
this.#costTracker.resetSession();
|
|
2550
3340
|
this.#stormRecords = [];
|
|
2551
3341
|
this.#checkpoints.clear();
|
|
3342
|
+
this.#lastRoundResults = null;
|
|
2552
3343
|
}
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
3344
|
+
/**
|
|
3345
|
+
* 立即把当前状态写入持久化(不等 500ms debounce)。
|
|
3346
|
+
* 用于 UI 上"立即保存"按钮或异常退出前的最后兜底。
|
|
3347
|
+
*
|
|
3348
|
+
* @sideEffect 调用 #doPersist(),写入磁盘
|
|
3349
|
+
*/
|
|
2556
3350
|
async persistNow() {
|
|
2557
3351
|
if (this.#persistTimer) {
|
|
2558
3352
|
clearTimeout(this.#persistTimer);
|
|
@@ -2560,6 +3354,12 @@ ${item.result.diff.patch}`;
|
|
|
2560
3354
|
}
|
|
2561
3355
|
await this.#doPersist();
|
|
2562
3356
|
}
|
|
3357
|
+
/**
|
|
3358
|
+
* 节流持久化:500ms 内多次调用只会真正落盘一次。
|
|
3359
|
+
* 通过 setTimeout + timer.refresh() 实现 debounce。
|
|
3360
|
+
*
|
|
3361
|
+
* @sideEffect 调度一个 500ms 后的 #doPersist() 任务
|
|
3362
|
+
*/
|
|
2563
3363
|
#persist() {
|
|
2564
3364
|
if (!this.#store) return;
|
|
2565
3365
|
if (this.#persistTimer) {
|
|
@@ -2572,6 +3372,12 @@ ${item.result.diff.patch}`;
|
|
|
2572
3372
|
}, 500);
|
|
2573
3373
|
this.#persistTimer.unref();
|
|
2574
3374
|
}
|
|
3375
|
+
/**
|
|
3376
|
+
* 真正执行持久化:构造 StoredSession 并写入 store。
|
|
3377
|
+
* 失败仅 console.error,不抛给调用方(持久化失败不应阻塞对话)。
|
|
3378
|
+
*
|
|
3379
|
+
* @sideEffect 写磁盘 ~/.dskcode/sessions/<id>.json
|
|
3380
|
+
*/
|
|
2575
3381
|
async #doPersist() {
|
|
2576
3382
|
if (!this.#store) return;
|
|
2577
3383
|
const stored = {
|
|
@@ -2590,12 +3396,24 @@ ${item.result.diff.patch}`;
|
|
|
2590
3396
|
console.error("[Session] \u6301\u4E45\u5316\u5931\u8D25:", err);
|
|
2591
3397
|
}
|
|
2592
3398
|
}
|
|
3399
|
+
/**
|
|
3400
|
+
* 从消息历史推导会话标题:取第一条非空 user 消息的前 40 字。
|
|
3401
|
+
* 无任何 user 消息时返回 "新会话"。
|
|
3402
|
+
*
|
|
3403
|
+
* @pure 不修改任何状态
|
|
3404
|
+
*/
|
|
2593
3405
|
#deriveTitle() {
|
|
2594
3406
|
for (const m of this.#messages) {
|
|
2595
3407
|
if (m.role === "user" && m.content.trim()) return m.content.trim().slice(0, 40);
|
|
2596
3408
|
}
|
|
2597
3409
|
return "\u65B0\u4F1A\u8BDD";
|
|
2598
3410
|
}
|
|
3411
|
+
/**
|
|
3412
|
+
* 把内存 messages 转成可持久化的 StoredSession["messages"]。
|
|
3413
|
+
* 给 user 消息挂上对应 checkpoint(若存在),用于 rewind 时文件回退。
|
|
3414
|
+
*
|
|
3415
|
+
* @pure 不修改任何状态
|
|
3416
|
+
*/
|
|
2599
3417
|
#serializeMessages() {
|
|
2600
3418
|
return this.#messages.map((msg, idx) => {
|
|
2601
3419
|
const checkpoint = this.#checkpoints.get(idx);
|
|
@@ -2603,6 +3421,19 @@ ${item.result.diff.patch}`;
|
|
|
2603
3421
|
return { ...msg };
|
|
2604
3422
|
});
|
|
2605
3423
|
}
|
|
3424
|
+
/**
|
|
3425
|
+
* 静态方法:从磁盘恢复一个之前保存的 Session。
|
|
3426
|
+
*
|
|
3427
|
+
* @param id — 之前 SessionStore 保存的会话 ID
|
|
3428
|
+
* @param provider — LLM Provider(必须)
|
|
3429
|
+
* @param tools — 工具列表/Registry(必须重新提供,因为工具不持久化)
|
|
3430
|
+
* @param costTracker — 成本追踪器(可选)
|
|
3431
|
+
* @param options — 会话选项(sessionId / store 必须与持久化 ID 对应)
|
|
3432
|
+
* @returns 恢复后的 Session 实例
|
|
3433
|
+
* @throws 持久化被禁用时、ID 不存在时抛错
|
|
3434
|
+
*
|
|
3435
|
+
* @sideEffect 从磁盘读 messages + checkpoints
|
|
3436
|
+
*/
|
|
2606
3437
|
static async resume(id, provider, tools = [], costTracker, options) {
|
|
2607
3438
|
const store = options?.store === false ? null : options?.store ?? new SessionStore();
|
|
2608
3439
|
if (!store) throw new Error("resume \u9700\u8981\u542F\u7528\u6301\u4E45\u5316\uFF08options.store \u4E0D\u80FD\u4E3A false\uFF09");
|
|
@@ -2628,6 +3459,13 @@ ${item.result.diff.patch}`;
|
|
|
2628
3459
|
// -------------------------------------------------------------------------
|
|
2629
3460
|
// 检查点与 Rewind
|
|
2630
3461
|
// -------------------------------------------------------------------------
|
|
3462
|
+
/**
|
|
3463
|
+
* 列出所有 user 消息对应的 checkpoint(按 index 升序)。
|
|
3464
|
+
* 用于 UI 展示 /rewind 列表。
|
|
3465
|
+
*
|
|
3466
|
+
* @returns 每个元素的 index = messages 数组索引,可直接传给 rewind()
|
|
3467
|
+
* @pure 不修改任何状态
|
|
3468
|
+
*/
|
|
2631
3469
|
listCheckpoints() {
|
|
2632
3470
|
const result = [];
|
|
2633
3471
|
for (const [index, checkpoint] of this.#checkpoints) {
|
|
@@ -2637,6 +3475,25 @@ ${item.result.diff.patch}`;
|
|
|
2637
3475
|
}
|
|
2638
3476
|
return result.sort((a, b) => a.index - b.index);
|
|
2639
3477
|
}
|
|
3478
|
+
/**
|
|
3479
|
+
* rewind — 把消息历史截断到 targetIndex 对应的 user 消息,
|
|
3480
|
+
* 并尝试把文件工作区也回退到那一刻。
|
|
3481
|
+
*
|
|
3482
|
+
* @param targetIndex — 要回退到的 user 消息索引(来自 listCheckpoints())
|
|
3483
|
+
* @returns
|
|
3484
|
+
* - `{ ok: true, fileRestored }`:成功;fileRestored 表示工作区是否也被还原
|
|
3485
|
+
* - `{ ok: false, error }`:失败(无效索引 / 非 user / 无 checkpoint / 文件恢复失败)
|
|
3486
|
+
*
|
|
3487
|
+
* 行为:
|
|
3488
|
+
* 1. 截断 messages 数组到 targetIndex + 1
|
|
3489
|
+
* 2. 移除并丢弃所有 > targetIndex 的 checkpoints
|
|
3490
|
+
* 3. 若目标 checkpoint 是 git 仓库:
|
|
3491
|
+
* - 有 stashSha:恢复该 stash(force)
|
|
3492
|
+
* - 无 stashSha:把工作区 restore 到 HEAD 干净状态
|
|
3493
|
+
* 4. 触发 #persist() 把截断后的状态写盘
|
|
3494
|
+
*
|
|
3495
|
+
* @sideEffect 改写 messages、checkpoints、磁盘
|
|
3496
|
+
*/
|
|
2640
3497
|
async rewind(targetIndex) {
|
|
2641
3498
|
if (targetIndex < 0 || targetIndex >= this.#messages.length) {
|
|
2642
3499
|
return { ok: false, error: `\u65E0\u6548\u7684\u6D88\u606F\u7D22\u5F15 ${targetIndex}` };
|
|
@@ -2676,19 +3533,58 @@ ${item.result.diff.patch}`;
|
|
|
2676
3533
|
this.#persist();
|
|
2677
3534
|
return { ok: true, fileRestored };
|
|
2678
3535
|
}
|
|
3536
|
+
/**
|
|
3537
|
+
* 是否存在可用的 checkpoint(决定 UI 是否显示 /rewind 入口)。
|
|
3538
|
+
*
|
|
3539
|
+
* @pure 不修改任何状态
|
|
3540
|
+
*/
|
|
2679
3541
|
hasCheckpoints() {
|
|
2680
3542
|
return this.listCheckpoints().length > 0;
|
|
2681
3543
|
}
|
|
3544
|
+
/**
|
|
3545
|
+
* 彻底删除会话:从磁盘移除 SessionStore 条目、丢弃所有 checkpoint、关闭日志。
|
|
3546
|
+
* 删除后该 Session 实例不应再被使用。
|
|
3547
|
+
*
|
|
3548
|
+
* @sideEffect 删磁盘文件、清 checkpoints、flush 并关闭 logger
|
|
3549
|
+
*/
|
|
2682
3550
|
async delete() {
|
|
2683
3551
|
if (this.#store) await this.#store.delete(this.#sessionId);
|
|
2684
3552
|
for (const cp2 of this.#checkpoints.values()) {
|
|
2685
3553
|
void discardCheckpoint(cp2);
|
|
2686
3554
|
}
|
|
2687
3555
|
this.#checkpoints.clear();
|
|
3556
|
+
this.#logger.logSessionEnd(Date.now() - this.#createdAt);
|
|
3557
|
+
await this.#logger.flush();
|
|
2688
3558
|
}
|
|
2689
3559
|
// -------------------------------------------------------------------------
|
|
2690
3560
|
// 内部方法
|
|
2691
3561
|
// -------------------------------------------------------------------------
|
|
3562
|
+
/**
|
|
3563
|
+
* 构建本轮使用的工具执行器(每次 chat() 调用一次,绑定当前 signal)。
|
|
3564
|
+
* 每次新建确保 abort signal 是当下有效的,且 #baseCtx 不会被多次调用共享篡改。
|
|
3565
|
+
*
|
|
3566
|
+
* @returns 新的 ToolExecutor 实例
|
|
3567
|
+
* @pure 仅组装参数,不修改任何状态
|
|
3568
|
+
*/
|
|
3569
|
+
#buildToolExecutor() {
|
|
3570
|
+
return new ToolExecutor({
|
|
3571
|
+
registry: this.#toolRegistry,
|
|
3572
|
+
gate: this.#options.gate,
|
|
3573
|
+
baseCtx: {
|
|
3574
|
+
cwd: this.#options.cwd,
|
|
3575
|
+
signal: this.#abortController.signal,
|
|
3576
|
+
writeRoots: this.#options.writeRoots
|
|
3577
|
+
}
|
|
3578
|
+
});
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3581
|
+
* 构建 system prompt:注入当前模型、cwd、工具清单、项目上下文。
|
|
3582
|
+
* plan 模式使用 buildPlanSystemPrompt(只读工具 + 计划输出格式),
|
|
3583
|
+
* code 模式使用 buildSystemPrompt。
|
|
3584
|
+
*
|
|
3585
|
+
* @returns 渲染好的 prompt 字符串
|
|
3586
|
+
* @pure 不修改任何状态
|
|
3587
|
+
*/
|
|
2692
3588
|
#buildSystemPrompt() {
|
|
2693
3589
|
const enabledTools = this.#toolRegistry.list();
|
|
2694
3590
|
const toolDescs = enabledTools.map((t) => ({
|
|
@@ -2706,17 +3602,10 @@ ${item.result.diff.patch}`;
|
|
|
2706
3602
|
if (this.#mode === "plan") return buildPlanSystemPrompt(opts);
|
|
2707
3603
|
return buildSystemPrompt(opts);
|
|
2708
3604
|
}
|
|
2709
|
-
#buildToolDefinitions() {
|
|
2710
|
-
const tools = this.#mode === "plan" ? this.#toolRegistry.listReadTools() : this.#toolRegistry.list();
|
|
2711
|
-
return tools.map((t) => ({
|
|
2712
|
-
type: "function",
|
|
2713
|
-
function: { name: t.name, description: t.description, parameters: t.parameters }
|
|
2714
|
-
}));
|
|
2715
|
-
}
|
|
2716
3605
|
};
|
|
2717
3606
|
|
|
2718
3607
|
// src/tool/sandbox.ts
|
|
2719
|
-
import { resolve, relative, isAbsolute } from "path";
|
|
3608
|
+
import { resolve, relative as relative2, isAbsolute } from "path";
|
|
2720
3609
|
import { realpath as realpath2 } from "fs/promises";
|
|
2721
3610
|
import { spawn } from "child_process";
|
|
2722
3611
|
import process2 from "process";
|
|
@@ -2735,7 +3624,7 @@ async function realPath(target) {
|
|
|
2735
3624
|
const parent = resolve(target, "..");
|
|
2736
3625
|
try {
|
|
2737
3626
|
const realParent = await realpath2(parent);
|
|
2738
|
-
return resolve(realParent,
|
|
3627
|
+
return resolve(realParent, relative2(parent, target));
|
|
2739
3628
|
} catch {
|
|
2740
3629
|
return resolve(target);
|
|
2741
3630
|
}
|
|
@@ -2748,7 +3637,7 @@ async function confine(allowedRoots, target) {
|
|
|
2748
3637
|
const realTarget = await realPath(target);
|
|
2749
3638
|
for (const root of allowedRoots) {
|
|
2750
3639
|
const realRoot = await realPath(root);
|
|
2751
|
-
const rel =
|
|
3640
|
+
const rel = relative2(realRoot, realTarget);
|
|
2752
3641
|
if (!rel.startsWith("..") && rel !== "" && !rel.startsWith("/") && !rel.startsWith("\\")) {
|
|
2753
3642
|
return { ok: true };
|
|
2754
3643
|
}
|
|
@@ -2780,6 +3669,10 @@ async function execCommand(command, args, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, s
|
|
|
2780
3669
|
spawnCmd = command;
|
|
2781
3670
|
spawnArgs = args;
|
|
2782
3671
|
useShell = false;
|
|
3672
|
+
if (isWindows && spawnCmd === "cmd" && spawnArgs[0] === "/c") {
|
|
3673
|
+
const prefixed = `chcp 65001 >nul && ${spawnArgs[1] ?? ""}`;
|
|
3674
|
+
spawnArgs = ["/c", prefixed];
|
|
3675
|
+
}
|
|
2783
3676
|
} else {
|
|
2784
3677
|
useShell = !isWindows;
|
|
2785
3678
|
spawnCmd = isWindows ? "cmd" : command;
|
|
@@ -2794,10 +3687,10 @@ async function execCommand(command, args, cwd, timeoutMs = DEFAULT_TIMEOUT_MS, s
|
|
|
2794
3687
|
let stdout = "";
|
|
2795
3688
|
let stderr = "";
|
|
2796
3689
|
child.stdout.on("data", (data) => {
|
|
2797
|
-
stdout += data.toString();
|
|
3690
|
+
stdout += data.toString("utf8");
|
|
2798
3691
|
});
|
|
2799
3692
|
child.stderr.on("data", (data) => {
|
|
2800
|
-
stderr += data.toString();
|
|
3693
|
+
stderr += data.toString("utf8");
|
|
2801
3694
|
});
|
|
2802
3695
|
const timeout = setTimeout(() => {
|
|
2803
3696
|
child.kill("SIGTERM");
|
|
@@ -3141,7 +4034,7 @@ async function writeFileWithEol(filePath, originalContent, newContent) {
|
|
|
3141
4034
|
// src/tool/builtins/read-file.ts
|
|
3142
4035
|
import { readFile as readFile5, stat } from "fs/promises";
|
|
3143
4036
|
import { open } from "fs/promises";
|
|
3144
|
-
import { relative as
|
|
4037
|
+
import { relative as relative3 } from "path";
|
|
3145
4038
|
async function checkBinary(filePath) {
|
|
3146
4039
|
const fileHandle = await open(filePath, "r");
|
|
3147
4040
|
try {
|
|
@@ -3224,7 +4117,7 @@ var readFileTool = {
|
|
|
3224
4117
|
const tailHint = remaining > 0 ? `
|
|
3225
4118
|
|
|
3226
4119
|
[\u8FD8\u6709 ${remaining} \u884C\uFF1B\u4F7F\u7528 startLine=${endLine + 1} \u7EE7\u7EED\u67E5\u770B]` : "";
|
|
3227
|
-
const relPath =
|
|
4120
|
+
const relPath = relative3(ctx.cwd, filePath).replace(/\\/g, "/");
|
|
3228
4121
|
const rangeLabel = startLine > 0 || endLine < lines.length ? `\u7B2C ${startLine + 1}-${endLine} \u884C` : `${lines.length} \u884C`;
|
|
3229
4122
|
return {
|
|
3230
4123
|
success: true,
|
|
@@ -3243,8 +4136,8 @@ var readFileTool = {
|
|
|
3243
4136
|
};
|
|
3244
4137
|
|
|
3245
4138
|
// src/tool/builtins/write-file.ts
|
|
3246
|
-
import { mkdir as
|
|
3247
|
-
import { dirname, relative as
|
|
4139
|
+
import { mkdir as mkdir6, readFile as readFile6 } from "fs/promises";
|
|
4140
|
+
import { dirname, relative as relative4, basename as basename2 } from "path";
|
|
3248
4141
|
var writeFileTool = {
|
|
3249
4142
|
name: "write_file",
|
|
3250
4143
|
kind: "edit" /* Edit */,
|
|
@@ -3286,7 +4179,7 @@ var writeFileTool = {
|
|
|
3286
4179
|
existedBefore = true;
|
|
3287
4180
|
} catch {
|
|
3288
4181
|
}
|
|
3289
|
-
await
|
|
4182
|
+
await mkdir6(dirname(filePath), { recursive: true });
|
|
3290
4183
|
const content = args.content;
|
|
3291
4184
|
await writeFileWithEol(filePath, oldContent, content);
|
|
3292
4185
|
const diff = computeFileDiff(oldContent, content, filePath);
|
|
@@ -3295,11 +4188,11 @@ var writeFileTool = {
|
|
|
3295
4188
|
const byteSize = Buffer.byteLength(content, "utf-8");
|
|
3296
4189
|
const action = existedBefore ? "\u5DF2\u4FEE\u6539" : "\u5DF2\u521B\u5EFA";
|
|
3297
4190
|
const diffSummary = existedBefore ? `\uFF0C+${diff.additions} -${diff.deletions}` : `\uFF0C+${diff.additions} \u884C\uFF08\u65B0\u5EFA\uFF09`;
|
|
3298
|
-
const fileName =
|
|
4191
|
+
const fileName = basename2(filePath);
|
|
3299
4192
|
const summary = existedBefore ? `\u{1F4DD} \u4FEE\u6539: ${fileName} (+${diff.additions} -${diff.deletions})` : `\u{1F4DD} \u65B0\u5EFA: ${fileName} (+${diff.additions} \u884C)`;
|
|
3300
4193
|
return {
|
|
3301
4194
|
success: true,
|
|
3302
|
-
data: `\u6587\u4EF6${action}\uFF1A${
|
|
4195
|
+
data: `\u6587\u4EF6${action}\uFF1A${relative4(ctx.cwd, filePath).replace(/\\/g, "/")}\uFF08${lineCount} \u884C\uFF0C${byteSize} \u5B57\u8282${diffSummary}\uFF09`,
|
|
3303
4196
|
summary,
|
|
3304
4197
|
diff
|
|
3305
4198
|
};
|
|
@@ -3316,7 +4209,7 @@ var writeFileTool = {
|
|
|
3316
4209
|
|
|
3317
4210
|
// src/tool/builtins/edit-file.ts
|
|
3318
4211
|
import { readFile as readFile7 } from "fs/promises";
|
|
3319
|
-
import { basename as
|
|
4212
|
+
import { basename as basename3 } from "path";
|
|
3320
4213
|
var editFileTool = {
|
|
3321
4214
|
name: "edit_file",
|
|
3322
4215
|
kind: "edit" /* Edit */,
|
|
@@ -3384,7 +4277,7 @@ var editFileTool = {
|
|
|
3384
4277
|
const oldLines = args.old_text.split("\n").length;
|
|
3385
4278
|
const newLines = args.new_text.split("\n").length;
|
|
3386
4279
|
const diffSummary = `+${diff.additions} -${diff.deletions}`;
|
|
3387
|
-
const summary = `\u{1F4DD} \u4FEE\u6539: ${
|
|
4280
|
+
const summary = `\u{1F4DD} \u4FEE\u6539: ${basename3(filePath)} (+${diff.additions} -${diff.deletions})`;
|
|
3388
4281
|
return {
|
|
3389
4282
|
success: true,
|
|
3390
4283
|
data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
|
|
@@ -3407,7 +4300,7 @@ ${oldLines} \u884C \u2192 ${newLines} \u884C
|
|
|
3407
4300
|
|
|
3408
4301
|
// src/tool/builtins/multi-edit.ts
|
|
3409
4302
|
import { readFile as readFile8 } from "fs/promises";
|
|
3410
|
-
import { basename as
|
|
4303
|
+
import { basename as basename4 } from "path";
|
|
3411
4304
|
var multiEditTool = {
|
|
3412
4305
|
name: "multi_edit",
|
|
3413
4306
|
kind: "edit" /* Edit */,
|
|
@@ -3505,7 +4398,7 @@ var multiEditTool = {
|
|
|
3505
4398
|
data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
|
|
3506
4399
|
\u5171\u6267\u884C ${args.edits.length} \u6B65\u66FF\u6362
|
|
3507
4400
|
\u53D8\u66F4\uFF1A+${diff.additions} -${diff.deletions}`,
|
|
3508
|
-
summary: `\u{1F4DD} \u4FEE\u6539: ${
|
|
4401
|
+
summary: `\u{1F4DD} \u4FEE\u6539: ${basename4(filePath)} (${args.edits.length} \u6B65, +${diff.additions} -${diff.deletions})`,
|
|
3509
4402
|
diff
|
|
3510
4403
|
};
|
|
3511
4404
|
} catch (err) {
|
|
@@ -3521,7 +4414,7 @@ var multiEditTool = {
|
|
|
3521
4414
|
|
|
3522
4415
|
// src/tool/builtins/delete-range.ts
|
|
3523
4416
|
import { readFile as readFile9 } from "fs/promises";
|
|
3524
|
-
import { basename as
|
|
4417
|
+
import { basename as basename5 } from "path";
|
|
3525
4418
|
function findUniqueLine(lines, anchor, label) {
|
|
3526
4419
|
const matches = [];
|
|
3527
4420
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -3617,7 +4510,7 @@ var deleteRangeTool = {
|
|
|
3617
4510
|
const diff = computeFileDiff(content, newContent, filePath);
|
|
3618
4511
|
diff.existedBefore = true;
|
|
3619
4512
|
const deletedLines = rangeEnd - rangeStart + 1;
|
|
3620
|
-
const summary = `\u{1F4DD} \u4FEE\u6539: ${
|
|
4513
|
+
const summary = `\u{1F4DD} \u4FEE\u6539: ${basename5(filePath)} (\u5220 ${deletedLines} \u884C, +${diff.additions} -${diff.deletions})`;
|
|
3621
4514
|
return {
|
|
3622
4515
|
success: true,
|
|
3623
4516
|
data: `\u6587\u4EF6\u5DF2\u7F16\u8F91\uFF1A${filePath}
|
|
@@ -3639,17 +4532,62 @@ var deleteRangeTool = {
|
|
|
3639
4532
|
|
|
3640
4533
|
// src/tool/builtins/bash.ts
|
|
3641
4534
|
import process3 from "process";
|
|
4535
|
+
import { existsSync as existsSync3 } from "fs";
|
|
4536
|
+
import { join as join6 } from "path";
|
|
3642
4537
|
var isWindows2 = process3.platform === "win32";
|
|
4538
|
+
function detectShell() {
|
|
4539
|
+
if (!isWindows2) {
|
|
4540
|
+
return {
|
|
4541
|
+
bin: "sh",
|
|
4542
|
+
args: ["-c"],
|
|
4543
|
+
label: "sh",
|
|
4544
|
+
hint: "\u5F53\u524D shell \u662F bash \u517C\u5BB9\u8BED\u6CD5\uFF0C\u652F\u6301 pwd/ls/&&/\u7BA1\u9053\u7B49\u6807\u51C6 Unix \u5DE5\u5177\u3002"
|
|
4545
|
+
};
|
|
4546
|
+
}
|
|
4547
|
+
const envBash = process3.env.DSKCODE_BASH;
|
|
4548
|
+
if (envBash && existsSync3(envBash)) {
|
|
4549
|
+
return { bin: envBash, args: ["-c"], label: "Git Bash", hint: SHELL_HINT_BASH };
|
|
4550
|
+
}
|
|
4551
|
+
const roots = [
|
|
4552
|
+
process3.env.ProgramFiles,
|
|
4553
|
+
process3.env["ProgramFiles(x86)"],
|
|
4554
|
+
process3.env.LOCALAPPDATA ? join6(process3.env.LOCALAPPDATA, "Programs") : null,
|
|
4555
|
+
// 非 C 盘安装(你机器就是 D 盘),枚举几个常见盘符根
|
|
4556
|
+
"D:\\Program Files",
|
|
4557
|
+
"E:\\Program Files"
|
|
4558
|
+
].filter((v) => Boolean(v));
|
|
4559
|
+
for (const root of roots) {
|
|
4560
|
+
for (const sub of ["Git\\bin\\bash.exe", "Git\\usr\\bin\\bash.exe"]) {
|
|
4561
|
+
const candidate = join6(root, sub);
|
|
4562
|
+
if (existsSync3(candidate)) {
|
|
4563
|
+
return { bin: candidate, args: ["-c"], label: "Git Bash", hint: SHELL_HINT_BASH };
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
return {
|
|
4568
|
+
bin: "cmd",
|
|
4569
|
+
args: ["/c"],
|
|
4570
|
+
label: "cmd.exe",
|
|
4571
|
+
hint: "\u5F53\u524D shell \u662F Windows cmd.exe\u3002\u8BF7\u4F7F\u7528 cmd \u8BED\u6CD5\uFF1A\u8DEF\u5F84\u7528\u53CD\u659C\u6760\u6216\u6B63\u659C\u6760\u5747\u53EF\u3001\u547D\u4EE4\u94FE\u7528 & \u6216\u6362\u884C\uFF0C\u907F\u514D\u4F7F\u7528 pwd/ls/&& \u7B49 Unix \u547D\u4EE4\u3002"
|
|
4572
|
+
};
|
|
4573
|
+
}
|
|
4574
|
+
var SHELL_HINT_BASH = "\u5F53\u524D shell \u662F bash \u517C\u5BB9\u8BED\u6CD5\uFF0C\u652F\u6301 pwd/ls/&&/\u7BA1\u9053\u7B49\u6807\u51C6 Unix \u5DE5\u5177\u3002";
|
|
4575
|
+
var SHELL_INFO = detectShell();
|
|
3643
4576
|
var bashTool = {
|
|
3644
4577
|
name: "bash",
|
|
3645
4578
|
kind: "other" /* Other */,
|
|
3646
|
-
description:
|
|
4579
|
+
description: [
|
|
4580
|
+
"\u5728 shell \u4E2D\u6267\u884C\u547D\u4EE4\uFF0C\u8FD4\u56DE stdout / stderr / \u9000\u51FA\u7801\u3002\u652F\u6301\u8D85\u65F6\u63A7\u5236\u548C\u4FE1\u53F7\u4E2D\u6B62\u3002",
|
|
4581
|
+
`\u5F53\u524D shell\uFF1A${SHELL_INFO.label}\u3002${SHELL_INFO.hint}`,
|
|
4582
|
+
"\u8BFB\u53D6\u6587\u4EF6\u8BF7\u4F18\u5148\u4F7F\u7528 read_file \u5DE5\u5177\uFF0C\u4E0D\u8981\u7528 cat/type/Get-Content\uFF1B",
|
|
4583
|
+
"\u641C\u7D22\u4EE3\u7801\u7528 grep / glob\uFF0C\u5217\u76EE\u5F55\u7528 ls\u2014\u2014\u8FD9\u4E9B\u4E13\u7528\u5DE5\u5177\u5728\u6240\u6709\u5E73\u53F0\u90FD\u53EF\u7528\u4E14\u65E0\u9700\u5173\u5FC3 shell \u8BED\u6CD5\u3002"
|
|
4584
|
+
].join(""),
|
|
3647
4585
|
parameters: {
|
|
3648
4586
|
type: "object",
|
|
3649
4587
|
properties: {
|
|
3650
4588
|
command: {
|
|
3651
4589
|
type: "string",
|
|
3652
|
-
description:
|
|
4590
|
+
description: `\u8981\u6267\u884C\u7684 shell \u547D\u4EE4\uFF08\u5F53\u524D shell\uFF1A${SHELL_INFO.label}\uFF09`
|
|
3653
4591
|
},
|
|
3654
4592
|
timeout: {
|
|
3655
4593
|
type: "number",
|
|
@@ -3665,8 +4603,8 @@ var bashTool = {
|
|
|
3665
4603
|
}
|
|
3666
4604
|
const timeout = args.timeout ?? ctx.timeout ?? getDefaultTimeout();
|
|
3667
4605
|
try {
|
|
3668
|
-
const shellCommand =
|
|
3669
|
-
const shellArgs =
|
|
4606
|
+
const shellCommand = SHELL_INFO.bin ?? "cmd";
|
|
4607
|
+
const shellArgs = [...SHELL_INFO.args, args.command];
|
|
3670
4608
|
const result = await execCommand(
|
|
3671
4609
|
shellCommand,
|
|
3672
4610
|
shellArgs,
|
|
@@ -3707,7 +4645,7 @@ ${truncateOutput(result.stderr)}`);
|
|
|
3707
4645
|
|
|
3708
4646
|
// src/tool/builtins/glob.ts
|
|
3709
4647
|
import { readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
3710
|
-
import { join as
|
|
4648
|
+
import { join as join7, relative as relative5, isAbsolute as isAbsolute2 } from "path";
|
|
3711
4649
|
function globToRegex(pattern) {
|
|
3712
4650
|
let regexStr = pattern;
|
|
3713
4651
|
regexStr = regexStr.replace(/\*\*\//g, "<<GLOBSTAR_SLASH>>");
|
|
@@ -3732,8 +4670,8 @@ async function walkDir(dir, baseDir) {
|
|
|
3732
4670
|
if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git")) {
|
|
3733
4671
|
continue;
|
|
3734
4672
|
}
|
|
3735
|
-
const fullPath =
|
|
3736
|
-
const relPath =
|
|
4673
|
+
const fullPath = join7(dir, entry.name);
|
|
4674
|
+
const relPath = relative5(baseDir, fullPath);
|
|
3737
4675
|
if (entry.isDirectory()) {
|
|
3738
4676
|
results.push(...await walkDir(fullPath, baseDir));
|
|
3739
4677
|
} else {
|
|
@@ -3765,7 +4703,7 @@ var globTool = {
|
|
|
3765
4703
|
if (!args?.pattern || typeof args.pattern !== "string") {
|
|
3766
4704
|
return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 pattern", error: "INVALID_ARGS" };
|
|
3767
4705
|
}
|
|
3768
|
-
const searchDir = args.directory ? isAbsolute2(args.directory) ? args.directory :
|
|
4706
|
+
const searchDir = args.directory ? isAbsolute2(args.directory) ? args.directory : join7(ctx.cwd, args.directory) : ctx.cwd;
|
|
3769
4707
|
const regex = globToRegex(args.pattern);
|
|
3770
4708
|
try {
|
|
3771
4709
|
const dirStat = await stat2(searchDir);
|
|
@@ -3805,7 +4743,7 @@ var globTool = {
|
|
|
3805
4743
|
|
|
3806
4744
|
// src/tool/builtins/grep.ts
|
|
3807
4745
|
import { readdir as readdir4, readFile as readFile10, stat as stat3 } from "fs/promises";
|
|
3808
|
-
import { join as
|
|
4746
|
+
import { join as join8, relative as relative6, isAbsolute as isAbsolute3 } from "path";
|
|
3809
4747
|
async function collectFiles(dir, extension, maxFiles = 200) {
|
|
3810
4748
|
const results = [];
|
|
3811
4749
|
let entries;
|
|
@@ -3819,7 +4757,7 @@ async function collectFiles(dir, extension, maxFiles = 200) {
|
|
|
3819
4757
|
if (entry.isDirectory() && (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist")) {
|
|
3820
4758
|
continue;
|
|
3821
4759
|
}
|
|
3822
|
-
const fullPath =
|
|
4760
|
+
const fullPath = join8(dir, entry.name);
|
|
3823
4761
|
if (entry.isDirectory()) {
|
|
3824
4762
|
results.push(...await collectFiles(fullPath, extension, maxFiles - results.length));
|
|
3825
4763
|
} else {
|
|
@@ -3866,7 +4804,7 @@ var grepTool = {
|
|
|
3866
4804
|
if (!args?.pattern || typeof args.pattern !== "string") {
|
|
3867
4805
|
return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 pattern", error: "INVALID_ARGS" };
|
|
3868
4806
|
}
|
|
3869
|
-
const searchDir = args.directory ? isAbsolute3(args.directory) ? args.directory :
|
|
4807
|
+
const searchDir = args.directory ? isAbsolute3(args.directory) ? args.directory : join8(ctx.cwd, args.directory) : ctx.cwd;
|
|
3870
4808
|
const maxFiles = args.max_files ?? 200;
|
|
3871
4809
|
try {
|
|
3872
4810
|
const flags = args.case_sensitive ? "g" : "gi";
|
|
@@ -3881,7 +4819,7 @@ var grepTool = {
|
|
|
3881
4819
|
try {
|
|
3882
4820
|
const content = await readFile10(filePath, "utf-8");
|
|
3883
4821
|
const lines = content.split("\n");
|
|
3884
|
-
const relPath =
|
|
4822
|
+
const relPath = relative6(searchDir, filePath);
|
|
3885
4823
|
for (let i = 0; i < lines.length; i++) {
|
|
3886
4824
|
if (regex.test(lines[i] ?? "")) {
|
|
3887
4825
|
regex.lastIndex = 0;
|
|
@@ -3926,7 +4864,7 @@ var grepTool = {
|
|
|
3926
4864
|
|
|
3927
4865
|
// src/tool/builtins/ls.ts
|
|
3928
4866
|
import { readdir as readdir5, stat as stat4 } from "fs/promises";
|
|
3929
|
-
import { join as
|
|
4867
|
+
import { join as join9, relative as relative7 } from "path";
|
|
3930
4868
|
var lsTool = {
|
|
3931
4869
|
name: "ls",
|
|
3932
4870
|
kind: "read" /* Read */,
|
|
@@ -3964,7 +4902,7 @@ var lsTool = {
|
|
|
3964
4902
|
let sizeStr = "";
|
|
3965
4903
|
if (typeMark === "FILE") {
|
|
3966
4904
|
try {
|
|
3967
|
-
const fileStat = await stat4(
|
|
4905
|
+
const fileStat = await stat4(join9(dirPath, entry.name));
|
|
3968
4906
|
if (fileStat.size < 1024) {
|
|
3969
4907
|
sizeStr = `${fileStat.size}B`;
|
|
3970
4908
|
} else if (fileStat.size < 1024 * 1024) {
|
|
@@ -3980,9 +4918,9 @@ var lsTool = {
|
|
|
3980
4918
|
lines.push(`${typeLabel} ${entry.name}${sizeStr ? ` (${sizeStr})` : ""}`);
|
|
3981
4919
|
}
|
|
3982
4920
|
if (lines.length === 0) {
|
|
3983
|
-
return { success: true, data: "\u76EE\u5F55\u4E3A\u7A7A", summary: `\u{1F4C2} ${
|
|
4921
|
+
return { success: true, data: "\u76EE\u5F55\u4E3A\u7A7A", summary: `\u{1F4C2} ${relative7(ctx.cwd, dirPath).replace(/\\/g, "/")}\uFF08\u7A7A\uFF09` };
|
|
3984
4922
|
}
|
|
3985
|
-
const relPath =
|
|
4923
|
+
const relPath = relative7(ctx.cwd, dirPath).replace(/\\/g, "/");
|
|
3986
4924
|
return {
|
|
3987
4925
|
success: true,
|
|
3988
4926
|
data: truncateOutput(`\u76EE\u5F55\uFF1A${dirPath}
|
|
@@ -4166,9 +5104,9 @@ function getGradientColors(text, phase, stops) {
|
|
|
4166
5104
|
}
|
|
4167
5105
|
|
|
4168
5106
|
// src/ui/ChatSession.tsx
|
|
4169
|
-
import { Fragment, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
5107
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
4170
5108
|
var PHASE_CONFIG = {
|
|
4171
|
-
thinking: { icon: "\u{1F9E0}", label: "\u601D\u8003\u4E2D", color: "#ff9800" },
|
|
5109
|
+
thinking: { icon: "\u{1F9E0}", label: "\u6DF1\u5EA6\u601D\u8003\u4E2D", color: "#ff9800" },
|
|
4172
5110
|
generating: { icon: "\u2728", label: "\u751F\u6210\u4E2D", color: "#00ff41" },
|
|
4173
5111
|
calling_tools: { icon: "\u{1F6E0}", label: "\u8C03\u7528\u5DE5\u5177", color: "#f59e0b" },
|
|
4174
5112
|
executing_tools: { icon: "\u26A1", label: "\u6267\u884C\u5DE5\u5177", color: "#00ffff" }
|
|
@@ -4240,34 +5178,34 @@ function ChatSession({
|
|
|
4240
5178
|
}) {
|
|
4241
5179
|
const termWidth = typeof process.stdout.columns === "number" ? process.stdout.columns : 80;
|
|
4242
5180
|
const dividerWidth = Math.max(termWidth - 2, 1);
|
|
4243
|
-
const [offset, setOffset] =
|
|
4244
|
-
const [displayMessages, setDisplayMessages] =
|
|
4245
|
-
const [staticKey, setStaticKey] =
|
|
4246
|
-
const [input, setInput] =
|
|
4247
|
-
const [balance, setBalance] =
|
|
4248
|
-
const [balanceLoading, setBalanceLoading] =
|
|
4249
|
-
const [todayCost, setTodayCost] =
|
|
4250
|
-
const [isStreaming, setIsStreaming] =
|
|
4251
|
-
const [streamingPhase, setStreamingPhase] =
|
|
4252
|
-
const [streamingPlaceholder, setStreamingPlaceholder] =
|
|
4253
|
-
const [idlePlaceholder, setIdlePlaceholder] =
|
|
4254
|
-
const [gradientColors, setGradientColors] =
|
|
4255
|
-
const gradientPhaseRef =
|
|
4256
|
-
const [streamingGradientColors, setStreamingGradientColors] =
|
|
4257
|
-
const streamingPhaseRef =
|
|
4258
|
-
const [currentContent, setCurrentContent] =
|
|
4259
|
-
const [currentToolCalls, setCurrentToolCalls] =
|
|
4260
|
-
const [_currentUsage, setCurrentUsage] =
|
|
4261
|
-
const [_currentElapsed, setCurrentElapsed] =
|
|
4262
|
-
const [_currentCost, setCurrentCost] =
|
|
4263
|
-
const [activeModel, setActiveModel] =
|
|
4264
|
-
const [_streamingModel, setStreamingModel] =
|
|
4265
|
-
const [streamError, setStreamError] =
|
|
4266
|
-
const [sessionMode, setSessionMode] =
|
|
4267
|
-
const [thinkingEnabled, setThinkingEnabled] =
|
|
4268
|
-
const [thinkingEffort, setThinkingEffort] =
|
|
4269
|
-
const [responseFormat] =
|
|
4270
|
-
const [toolChoice, setToolChoice] =
|
|
5181
|
+
const [offset, setOffset] = useState4(0);
|
|
5182
|
+
const [displayMessages, setDisplayMessages] = useState4([]);
|
|
5183
|
+
const [staticKey, setStaticKey] = useState4(0);
|
|
5184
|
+
const [input, setInput] = useState4("");
|
|
5185
|
+
const [balance, setBalance] = useState4(null);
|
|
5186
|
+
const [balanceLoading, setBalanceLoading] = useState4(false);
|
|
5187
|
+
const [todayCost, setTodayCost] = useState4(null);
|
|
5188
|
+
const [isStreaming, setIsStreaming] = useState4(false);
|
|
5189
|
+
const [streamingPhase, setStreamingPhase] = useState4(null);
|
|
5190
|
+
const [streamingPlaceholder, setStreamingPlaceholder] = useState4("");
|
|
5191
|
+
const [idlePlaceholder, setIdlePlaceholder] = useState4(() => pickRandom(IDLE_PLACEHOLDERS));
|
|
5192
|
+
const [gradientColors, setGradientColors] = useState4([]);
|
|
5193
|
+
const gradientPhaseRef = useRef3(0);
|
|
5194
|
+
const [streamingGradientColors, setStreamingGradientColors] = useState4([]);
|
|
5195
|
+
const streamingPhaseRef = useRef3(0);
|
|
5196
|
+
const [currentContent, setCurrentContent] = useState4("");
|
|
5197
|
+
const [currentToolCalls, setCurrentToolCalls] = useState4([]);
|
|
5198
|
+
const [_currentUsage, setCurrentUsage] = useState4(void 0);
|
|
5199
|
+
const [_currentElapsed, setCurrentElapsed] = useState4(void 0);
|
|
5200
|
+
const [_currentCost, setCurrentCost] = useState4(void 0);
|
|
5201
|
+
const [activeModel, setActiveModel] = useState4(model);
|
|
5202
|
+
const [_streamingModel, setStreamingModel] = useState4(void 0);
|
|
5203
|
+
const [streamError, setStreamError] = useState4(void 0);
|
|
5204
|
+
const [sessionMode, setSessionMode] = useState4("code");
|
|
5205
|
+
const [thinkingEnabled, setThinkingEnabled] = useState4(true);
|
|
5206
|
+
const [thinkingEffort, setThinkingEffort] = useState4("high");
|
|
5207
|
+
const [responseFormat] = useState4("text");
|
|
5208
|
+
const [toolChoice, setToolChoice] = useState4(void 0);
|
|
4271
5209
|
const sessionCost = useMemo(() => {
|
|
4272
5210
|
return displayMessages.reduce((sum, msg) => {
|
|
4273
5211
|
if (msg.assistantDetail?.cost) {
|
|
@@ -4278,36 +5216,36 @@ function ChatSession({
|
|
|
4278
5216
|
}, [displayMessages]);
|
|
4279
5217
|
const hasConversationStarted = displayMessages.length > 0;
|
|
4280
5218
|
const cmdTips = Array.from(getRegisteredCommands()).filter(([name]) => name !== "/exit" && name !== "/quit").map(([name, cmd]) => ({ name, desc: cmd.desc }));
|
|
4281
|
-
const [cmdTipIndex, setCmdTipIndex] =
|
|
4282
|
-
const [cmdTipGradientColors, setCmdTipGradientColors] =
|
|
4283
|
-
const cmdTipPhaseRef =
|
|
4284
|
-
const [skillSelectIndex, setSkillSelectIndex] =
|
|
4285
|
-
const [fileSelectIndex, setFileSelectIndex] =
|
|
4286
|
-
const [inputKey, setInputKey] =
|
|
4287
|
-
const [rewindSelecting, setRewindSelecting] =
|
|
4288
|
-
const [rewindSelectIndex, setRewindSelectIndex] =
|
|
4289
|
-
const [rewindList, setRewindList] =
|
|
4290
|
-
const [rewinding, setRewinding] =
|
|
4291
|
-
const [rewindHintPhase, setRewindHintPhase] =
|
|
4292
|
-
const currentRoundModifiedRef =
|
|
4293
|
-
const rewindHintTimerRef =
|
|
4294
|
-
const [selectingModel, setSelectingModel] =
|
|
4295
|
-
const [modelSelectIndex, setModelSelectIndex] =
|
|
5219
|
+
const [cmdTipIndex, setCmdTipIndex] = useState4(0);
|
|
5220
|
+
const [cmdTipGradientColors, setCmdTipGradientColors] = useState4([]);
|
|
5221
|
+
const cmdTipPhaseRef = useRef3(0);
|
|
5222
|
+
const [skillSelectIndex, setSkillSelectIndex] = useState4(0);
|
|
5223
|
+
const [fileSelectIndex, setFileSelectIndex] = useState4(0);
|
|
5224
|
+
const [inputKey, setInputKey] = useState4(0);
|
|
5225
|
+
const [rewindSelecting, setRewindSelecting] = useState4(false);
|
|
5226
|
+
const [rewindSelectIndex, setRewindSelectIndex] = useState4(0);
|
|
5227
|
+
const [rewindList, setRewindList] = useState4([]);
|
|
5228
|
+
const [rewinding, setRewinding] = useState4(false);
|
|
5229
|
+
const [rewindHintPhase, setRewindHintPhase] = useState4("idle");
|
|
5230
|
+
const currentRoundModifiedRef = useRef3(false);
|
|
5231
|
+
const rewindHintTimerRef = useRef3(null);
|
|
5232
|
+
const [selectingModel, setSelectingModel] = useState4(false);
|
|
5233
|
+
const [modelSelectIndex, setModelSelectIndex] = useState4(0);
|
|
4296
5234
|
const modelOptions = ["deepseek-v4-flash", "deepseek-v4-pro"];
|
|
4297
|
-
const sessionRef =
|
|
4298
|
-
const abortRef =
|
|
4299
|
-
const currentContentRef =
|
|
4300
|
-
const currentToolCallsRef =
|
|
4301
|
-
const currentUsageRef =
|
|
4302
|
-
const currentElapsedRef =
|
|
4303
|
-
const currentCostRef =
|
|
4304
|
-
const currentModelRef =
|
|
4305
|
-
const streamErrorRef =
|
|
4306
|
-
|
|
5235
|
+
const sessionRef = useRef3(null);
|
|
5236
|
+
const abortRef = useRef3(null);
|
|
5237
|
+
const currentContentRef = useRef3("");
|
|
5238
|
+
const currentToolCallsRef = useRef3([]);
|
|
5239
|
+
const currentUsageRef = useRef3(void 0);
|
|
5240
|
+
const currentElapsedRef = useRef3(void 0);
|
|
5241
|
+
const currentCostRef = useRef3(void 0);
|
|
5242
|
+
const currentModelRef = useRef3(void 0);
|
|
5243
|
+
const streamErrorRef = useRef3(void 0);
|
|
5244
|
+
useEffect4(() => {
|
|
4307
5245
|
setSkillSelectIndex(0);
|
|
4308
5246
|
setFileSelectIndex(0);
|
|
4309
5247
|
}, [input]);
|
|
4310
|
-
|
|
5248
|
+
useEffect4(() => {
|
|
4311
5249
|
return () => {
|
|
4312
5250
|
if (rewindHintTimerRef.current) {
|
|
4313
5251
|
clearTimeout(rewindHintTimerRef.current);
|
|
@@ -4403,7 +5341,12 @@ function ChatSession({
|
|
|
4403
5341
|
}
|
|
4404
5342
|
}, []);
|
|
4405
5343
|
const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(() => {
|
|
4406
|
-
|
|
5344
|
+
const s = sessionRef.current;
|
|
5345
|
+
if (s) {
|
|
5346
|
+
void s.flushLog().finally(() => process.exit(0));
|
|
5347
|
+
} else {
|
|
5348
|
+
process.exit(0);
|
|
5349
|
+
}
|
|
4407
5350
|
});
|
|
4408
5351
|
useInput(
|
|
4409
5352
|
useCallback2(
|
|
@@ -4537,14 +5480,14 @@ function ChatSession({
|
|
|
4537
5480
|
[selectingModel, modelSelectIndex, modelOptions, activeModel, isStreaming, handleCtrlC, input, skills, skillSelectIndex, fileSelectIndex, getFilteredSkills, getFilteredFiles, sessionMode, setSessionMode, rewindSelecting, rewindSelectIndex, rewindList, doRewind]
|
|
4538
5481
|
)
|
|
4539
5482
|
);
|
|
4540
|
-
|
|
5483
|
+
useEffect4(() => {
|
|
4541
5484
|
if (cmdTips.length <= 1) return;
|
|
4542
5485
|
const timer = setInterval(() => {
|
|
4543
5486
|
setCmdTipIndex((prev) => (prev + 1) % cmdTips.length);
|
|
4544
5487
|
}, 2e3);
|
|
4545
5488
|
return () => clearInterval(timer);
|
|
4546
5489
|
}, [cmdTips.length]);
|
|
4547
|
-
|
|
5490
|
+
useEffect4(() => {
|
|
4548
5491
|
const tip = cmdTips[cmdTipIndex % cmdTips.length];
|
|
4549
5492
|
if (!tip) {
|
|
4550
5493
|
setCmdTipGradientColors([]);
|
|
@@ -4559,13 +5502,13 @@ function ChatSession({
|
|
|
4559
5502
|
}, GRADIENT_ANIMATION.cmdTipInterval);
|
|
4560
5503
|
return () => clearInterval(interval);
|
|
4561
5504
|
}, [cmdTipIndex, cmdTips.length]);
|
|
4562
|
-
|
|
5505
|
+
useEffect4(() => {
|
|
4563
5506
|
const timer = setInterval(() => {
|
|
4564
5507
|
setOffset((prev) => (prev + 1) % CYBER_PALETTE.length);
|
|
4565
5508
|
}, 500);
|
|
4566
5509
|
return () => clearInterval(timer);
|
|
4567
5510
|
}, []);
|
|
4568
|
-
|
|
5511
|
+
useEffect4(() => {
|
|
4569
5512
|
if (!apiKey || !baseUrl) return;
|
|
4570
5513
|
const provider = createProvider({
|
|
4571
5514
|
name: "deepseek",
|
|
@@ -4584,7 +5527,7 @@ function ChatSession({
|
|
|
4584
5527
|
sessionRef.current = null;
|
|
4585
5528
|
};
|
|
4586
5529
|
}, [apiKey, baseUrl, activeModel, externalCostTracker]);
|
|
4587
|
-
|
|
5530
|
+
useEffect4(() => {
|
|
4588
5531
|
if (!apiKey || !baseUrl) return;
|
|
4589
5532
|
let cancelled = false;
|
|
4590
5533
|
setBalanceLoading(true);
|
|
@@ -4609,7 +5552,7 @@ function ChatSession({
|
|
|
4609
5552
|
cancelled = true;
|
|
4610
5553
|
};
|
|
4611
5554
|
}, [apiKey, baseUrl]);
|
|
4612
|
-
|
|
5555
|
+
useEffect4(() => {
|
|
4613
5556
|
if (!externalCostTracker) return;
|
|
4614
5557
|
let cancelled = false;
|
|
4615
5558
|
let timer;
|
|
@@ -4627,7 +5570,7 @@ function ChatSession({
|
|
|
4627
5570
|
if (timer) clearInterval(timer);
|
|
4628
5571
|
};
|
|
4629
5572
|
}, [externalCostTracker]);
|
|
4630
|
-
|
|
5573
|
+
useEffect4(() => {
|
|
4631
5574
|
if (isStreaming || !idlePlaceholder) {
|
|
4632
5575
|
setGradientColors([]);
|
|
4633
5576
|
return;
|
|
@@ -4640,7 +5583,7 @@ function ChatSession({
|
|
|
4640
5583
|
}, GRADIENT_ANIMATION.idleInterval);
|
|
4641
5584
|
return () => clearInterval(interval);
|
|
4642
5585
|
}, [isStreaming, idlePlaceholder]);
|
|
4643
|
-
|
|
5586
|
+
useEffect4(() => {
|
|
4644
5587
|
if (!isStreaming || !streamingPlaceholder) {
|
|
4645
5588
|
setStreamingGradientColors([]);
|
|
4646
5589
|
return;
|
|
@@ -4803,6 +5746,7 @@ function ChatSession({
|
|
|
4803
5746
|
const result = cmd.handler();
|
|
4804
5747
|
switch (result.kind) {
|
|
4805
5748
|
case "exit":
|
|
5749
|
+
await sessionRef.current?.flushLog();
|
|
4806
5750
|
process.exit(0);
|
|
4807
5751
|
return;
|
|
4808
5752
|
case "clear":
|
|
@@ -4988,8 +5932,8 @@ function ChatSession({
|
|
|
4988
5932
|
}, 2e3);
|
|
4989
5933
|
}
|
|
4990
5934
|
}
|
|
4991
|
-
}, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls, skills, skillSelectIndex, getFilteredSkills, thinkingEnabled, thinkingEffort, responseFormat, toolChoice, activeModel, sessionMode]);
|
|
4992
|
-
|
|
5935
|
+
}, [onLaunchGame, onLaunchStock, currentContent, currentToolCalls, skills, skillSelectIndex, getFilteredSkills, thinkingEnabled, thinkingEffort, responseFormat, toolChoice, activeModel, sessionMode, isStreaming, rewinding]);
|
|
5936
|
+
useEffect4(() => {
|
|
4993
5937
|
if (!isStreaming && externalCostTracker) {
|
|
4994
5938
|
setTodayCost(externalCostTracker.todayTotalCost);
|
|
4995
5939
|
}
|
|
@@ -5092,7 +6036,10 @@ function ChatSession({
|
|
|
5092
6036
|
{
|
|
5093
6037
|
content: currentContent,
|
|
5094
6038
|
toolCalls: currentToolCalls.length > 0 ? currentToolCalls : void 0,
|
|
5095
|
-
isStreaming: true
|
|
6039
|
+
isStreaming: true,
|
|
6040
|
+
usage: _currentUsage,
|
|
6041
|
+
cost: _currentCost,
|
|
6042
|
+
model: _streamingModel
|
|
5096
6043
|
}
|
|
5097
6044
|
),
|
|
5098
6045
|
!isStreaming && streamError && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, marginLeft: 3, children: /* @__PURE__ */ jsxs9(Text10, { color: "red", children: [
|
|
@@ -5164,7 +6111,7 @@ function ChatSession({
|
|
|
5164
6111
|
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text10, { color: "#808080", dimColor: true, children: "\u2191\u2193 \u9009\u62E9 \xB7 Enter \u786E\u8BA4 \xB7 Esc \u53D6\u6D88" }) })
|
|
5165
6112
|
] }),
|
|
5166
6113
|
/* @__PURE__ */ jsx9(Text10, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) })
|
|
5167
|
-
] }) : /* @__PURE__ */ jsxs9(
|
|
6114
|
+
] }) : /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
5168
6115
|
(hasConversationStarted || sessionMode === "plan") && isStreaming && streamingPhase ? /* @__PURE__ */ jsx9(Box9, { marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ jsxs9(Text10, { bold: true, color: PHASE_CONFIG[streamingPhase].color, children: [
|
|
5169
6116
|
PHASE_CONFIG[streamingPhase].icon,
|
|
5170
6117
|
" ",
|
|
@@ -5216,10 +6163,10 @@ function ChatSession({
|
|
|
5216
6163
|
|
|
5217
6164
|
// src/ui/GamePicker.tsx
|
|
5218
6165
|
import { Box as Box10, Text as Text11, useInput as useInput2 } from "ink";
|
|
5219
|
-
import { useState as
|
|
6166
|
+
import { useState as useState5, useCallback as useCallback3 } from "react";
|
|
5220
6167
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
5221
6168
|
function GamePicker({ games, onSelect, onExit, onBackToChat }) {
|
|
5222
|
-
const [selectedIndex, setSelectedIndex] =
|
|
6169
|
+
const [selectedIndex, setSelectedIndex] = useState5(0);
|
|
5223
6170
|
const exitAction = onBackToChat ?? onExit ?? (() => process.exit(0));
|
|
5224
6171
|
const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(exitAction);
|
|
5225
6172
|
useInput2(
|
|
@@ -5274,7 +6221,7 @@ function listGames() {
|
|
|
5274
6221
|
|
|
5275
6222
|
// src/game/brick-breaker/index.tsx
|
|
5276
6223
|
import { Box as Box11, Text as Text12, useInput as useInput3, render as render2 } from "ink";
|
|
5277
|
-
import { useState as
|
|
6224
|
+
import { useState as useState6, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback4 } from "react";
|
|
5278
6225
|
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
5279
6226
|
var GAME_WIDTH = 40;
|
|
5280
6227
|
var GAME_HEIGHT = 18;
|
|
@@ -5406,13 +6353,13 @@ function buildBoard(state) {
|
|
|
5406
6353
|
return lines.map((l) => `\u2502${l}\u2502`).join("\n");
|
|
5407
6354
|
}
|
|
5408
6355
|
function BrickBreakerGame({ onExit: _onExit }) {
|
|
5409
|
-
const [initialLevel, setInitialLevel] =
|
|
5410
|
-
const [selectingLevel, setSelectingLevel] =
|
|
5411
|
-
const stateRef =
|
|
5412
|
-
const [tick2, setTick] =
|
|
5413
|
-
const onExitRef =
|
|
6356
|
+
const [initialLevel, setInitialLevel] = useState6(1);
|
|
6357
|
+
const [selectingLevel, setSelectingLevel] = useState6(true);
|
|
6358
|
+
const stateRef = useRef4(createInitialState(initialLevel));
|
|
6359
|
+
const [tick2, setTick] = useState6(0);
|
|
6360
|
+
const onExitRef = useRef4(_onExit);
|
|
5414
6361
|
onExitRef.current = _onExit;
|
|
5415
|
-
|
|
6362
|
+
useEffect5(() => {
|
|
5416
6363
|
if (selectingLevel) return;
|
|
5417
6364
|
const interval = setInterval(() => {
|
|
5418
6365
|
update(stateRef.current);
|
|
@@ -5546,7 +6493,7 @@ var brick_breaker_default = {
|
|
|
5546
6493
|
|
|
5547
6494
|
// src/game/coder-check/index.tsx
|
|
5548
6495
|
import { Box as Box12, Text as Text13, useInput as useInput4, render as render3 } from "ink";
|
|
5549
|
-
import { useState as
|
|
6496
|
+
import { useState as useState7, useEffect as useEffect6, useRef as useRef5, useCallback as useCallback5 } from "react";
|
|
5550
6497
|
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
5551
6498
|
var GAME_W = 66;
|
|
5552
6499
|
var GAME_H = 20;
|
|
@@ -5938,18 +6885,18 @@ function buildGameView(s, scoreLines, scoreColor, message) {
|
|
|
5938
6885
|
return rows;
|
|
5939
6886
|
}
|
|
5940
6887
|
function CoderCheck({ onExit: _onExit }) {
|
|
5941
|
-
const stateRef =
|
|
5942
|
-
const [tick2, setTick] =
|
|
5943
|
-
const [colorOffset, setColorOffset] =
|
|
5944
|
-
const onExitRef =
|
|
6888
|
+
const stateRef = useRef5(createInitialState2());
|
|
6889
|
+
const [tick2, setTick] = useState7(0);
|
|
6890
|
+
const [colorOffset, setColorOffset] = useState7(0);
|
|
6891
|
+
const onExitRef = useRef5(_onExit);
|
|
5945
6892
|
onExitRef.current = _onExit;
|
|
5946
|
-
|
|
6893
|
+
useEffect6(() => {
|
|
5947
6894
|
const timer = setInterval(() => {
|
|
5948
6895
|
setColorOffset((prev) => (prev + 1) % CYBER_PALETTE2.length);
|
|
5949
6896
|
}, 400);
|
|
5950
6897
|
return () => clearInterval(timer);
|
|
5951
6898
|
}, []);
|
|
5952
|
-
|
|
6899
|
+
useEffect6(() => {
|
|
5953
6900
|
const interval = setInterval(() => {
|
|
5954
6901
|
update2(stateRef.current);
|
|
5955
6902
|
setTick((t) => t + 1);
|
|
@@ -6446,12 +7393,12 @@ import chalk5 from "chalk";
|
|
|
6446
7393
|
|
|
6447
7394
|
// src/stock/StockList.tsx
|
|
6448
7395
|
import { Box as Box13, Text as Text14, useInput as useInput5 } from "ink";
|
|
6449
|
-
import { useState as
|
|
7396
|
+
import { useState as useState8, useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo2 } from "react";
|
|
6450
7397
|
import asciichart from "asciichart";
|
|
6451
7398
|
import os from "os";
|
|
6452
|
-
import { join as
|
|
7399
|
+
import { join as join10 } from "path";
|
|
6453
7400
|
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
6454
|
-
var SETTINGS_PATH =
|
|
7401
|
+
var SETTINGS_PATH = join10(os.homedir(), ".dskcode", "settings.json");
|
|
6455
7402
|
function toFileUrl(p) {
|
|
6456
7403
|
const norm = p.replace(/\\/g, "/");
|
|
6457
7404
|
if (process.platform === "win32") {
|
|
@@ -6553,20 +7500,20 @@ function latestPoints(data, maxPoints = 60) {
|
|
|
6553
7500
|
return data.slice(data.length - maxPoints);
|
|
6554
7501
|
}
|
|
6555
7502
|
function StockList({ codes, onExit, onBackToChat }) {
|
|
6556
|
-
const [stocks, setStocks] =
|
|
6557
|
-
const [selectedIndex, setSelectedIndex] =
|
|
6558
|
-
const [loading, setLoading] =
|
|
6559
|
-
const [lastUpdate, setLastUpdate] =
|
|
6560
|
-
const [detailView, setDetailView] =
|
|
6561
|
-
const [detailPrices, setDetailPrices] =
|
|
6562
|
-
const [detailLoading, setDetailLoading] =
|
|
6563
|
-
const [detailCountdown, setDetailCountdown] =
|
|
6564
|
-
const [countdown, setCountdown] =
|
|
6565
|
-
const [currentTime, setCurrentTime] =
|
|
7503
|
+
const [stocks, setStocks] = useState8([]);
|
|
7504
|
+
const [selectedIndex, setSelectedIndex] = useState8(0);
|
|
7505
|
+
const [loading, setLoading] = useState8(true);
|
|
7506
|
+
const [lastUpdate, setLastUpdate] = useState8("");
|
|
7507
|
+
const [detailView, setDetailView] = useState8(null);
|
|
7508
|
+
const [detailPrices, setDetailPrices] = useState8(null);
|
|
7509
|
+
const [detailLoading, setDetailLoading] = useState8(false);
|
|
7510
|
+
const [detailCountdown, setDetailCountdown] = useState8(10);
|
|
7511
|
+
const [countdown, setCountdown] = useState8(5);
|
|
7512
|
+
const [currentTime, setCurrentTime] = useState8(
|
|
6566
7513
|
() => (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false })
|
|
6567
7514
|
);
|
|
6568
|
-
const [dimMode, setDimMode] =
|
|
6569
|
-
const [sortOrder, setSortOrder] =
|
|
7515
|
+
const [dimMode, setDimMode] = useState8(false);
|
|
7516
|
+
const [sortOrder, setSortOrder] = useState8("default");
|
|
6570
7517
|
const sortedStocks = useMemo2(() => {
|
|
6571
7518
|
if (sortOrder === "default") return stocks;
|
|
6572
7519
|
return [...stocks].toSorted(
|
|
@@ -6574,7 +7521,7 @@ function StockList({ codes, onExit, onBackToChat }) {
|
|
|
6574
7521
|
);
|
|
6575
7522
|
}, [stocks, sortOrder]);
|
|
6576
7523
|
const { doubleCtrlC, handleCtrlC } = useDoubleCtrlC(onExit);
|
|
6577
|
-
|
|
7524
|
+
useEffect7(() => {
|
|
6578
7525
|
const timer = setInterval(() => {
|
|
6579
7526
|
setCurrentTime((/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false }));
|
|
6580
7527
|
}, 1e3);
|
|
@@ -6590,10 +7537,10 @@ function StockList({ codes, onExit, onBackToChat }) {
|
|
|
6590
7537
|
}
|
|
6591
7538
|
setLoading(false);
|
|
6592
7539
|
}, [codes]);
|
|
6593
|
-
|
|
7540
|
+
useEffect7(() => {
|
|
6594
7541
|
void loadData();
|
|
6595
7542
|
}, [loadData]);
|
|
6596
|
-
|
|
7543
|
+
useEffect7(() => {
|
|
6597
7544
|
const interval = setInterval(() => {
|
|
6598
7545
|
setCountdown((prev) => {
|
|
6599
7546
|
if (prev <= 1) {
|
|
@@ -6605,7 +7552,7 @@ function StockList({ codes, onExit, onBackToChat }) {
|
|
|
6605
7552
|
}, 1e3);
|
|
6606
7553
|
return () => clearInterval(interval);
|
|
6607
7554
|
}, [loadData]);
|
|
6608
|
-
|
|
7555
|
+
useEffect7(() => {
|
|
6609
7556
|
if (detailView) {
|
|
6610
7557
|
const loadDetail = () => {
|
|
6611
7558
|
minuteCache.delete(detailView.code);
|
|
@@ -6624,7 +7571,7 @@ function StockList({ codes, onExit, onBackToChat }) {
|
|
|
6624
7571
|
setDetailPrices(null);
|
|
6625
7572
|
setDetailLoading(false);
|
|
6626
7573
|
}, [detailView]);
|
|
6627
|
-
|
|
7574
|
+
useEffect7(() => {
|
|
6628
7575
|
if (!detailView) return;
|
|
6629
7576
|
const timer = setInterval(() => {
|
|
6630
7577
|
setDetailCountdown((prev) => prev > 0 ? prev - 1 : 10);
|
|
@@ -6786,7 +7733,7 @@ function renderDetail(stock, _onBack, prices, countdown = 10, currentTime) {
|
|
|
6786
7733
|
|
|
6787
7734
|
// src/utils/scan-files.ts
|
|
6788
7735
|
import { readdir as readdir6 } from "fs/promises";
|
|
6789
|
-
import { join as
|
|
7736
|
+
import { join as join11, relative as relative8 } from "path";
|
|
6790
7737
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
6791
7738
|
"node_modules",
|
|
6792
7739
|
".git",
|
|
@@ -6851,12 +7798,12 @@ async function scanProjectFiles(baseDir, dir) {
|
|
|
6851
7798
|
} else if (entry.isFile()) {
|
|
6852
7799
|
const ext = name.slice(name.lastIndexOf(".")).toLowerCase();
|
|
6853
7800
|
if (SOURCE_EXTS.has(ext)) {
|
|
6854
|
-
files.push(
|
|
7801
|
+
files.push(relative8(baseDir, join11(currentDir, name)));
|
|
6855
7802
|
}
|
|
6856
7803
|
}
|
|
6857
7804
|
}
|
|
6858
7805
|
const nested = await Promise.all(
|
|
6859
|
-
dirs.map((d) => scanProjectFiles(baseDir,
|
|
7806
|
+
dirs.map((d) => scanProjectFiles(baseDir, join11(currentDir, d)))
|
|
6860
7807
|
);
|
|
6861
7808
|
for (const n of nested) {
|
|
6862
7809
|
files.push(...n);
|
|
@@ -6944,7 +7891,7 @@ compdef _dskcode_completion dskcode`);
|
|
|
6944
7891
|
program2.command("stock").description("\u67E5\u770B\u81EA\u9009\u80A1\u5B9E\u65F6\u884C\u60C5").argument("[codes...]", "\u80A1\u7968\u4EE3\u7801\uFF08\u7A7A\u683C\u5206\u9694\uFF09\uFF0C\u5982 513090 600519").action(async function(codes) {
|
|
6945
7892
|
const _ctx = this.dskcodeCtx;
|
|
6946
7893
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
|
|
6947
|
-
const globalConfigPath =
|
|
7894
|
+
const globalConfigPath = join12(home, ".dskcode", "settings.json");
|
|
6948
7895
|
let globalConfigHasStock = false;
|
|
6949
7896
|
try {
|
|
6950
7897
|
const raw = await readFile11(globalConfigPath, "utf-8");
|
|
@@ -7114,7 +8061,10 @@ var sigintCount = 0;
|
|
|
7114
8061
|
var sigintTimer = null;
|
|
7115
8062
|
async function gracefulExit(code) {
|
|
7116
8063
|
try {
|
|
7117
|
-
await
|
|
8064
|
+
await Promise.all([
|
|
8065
|
+
CostTracker.flushAll(),
|
|
8066
|
+
ConversationLogger.flushAll()
|
|
8067
|
+
]);
|
|
7118
8068
|
} catch {
|
|
7119
8069
|
}
|
|
7120
8070
|
process.exit(code);
|
|
@@ -7137,19 +8087,19 @@ process.on("SIGTERM", () => {
|
|
|
7137
8087
|
var program = createCli();
|
|
7138
8088
|
try {
|
|
7139
8089
|
await program.parseAsync(process.argv);
|
|
7140
|
-
await CostTracker.flushAll();
|
|
8090
|
+
await Promise.all([CostTracker.flushAll(), ConversationLogger.flushAll()]);
|
|
7141
8091
|
} catch (err) {
|
|
7142
8092
|
const error = err;
|
|
7143
8093
|
if (error.code === "commander.helpDisplayed" || error.code === "commander.version") {
|
|
7144
|
-
await CostTracker.flushAll();
|
|
8094
|
+
await Promise.all([CostTracker.flushAll(), ConversationLogger.flushAll()]);
|
|
7145
8095
|
process.exit(error.exitCode ?? ExitCode.SUCCESS);
|
|
7146
8096
|
}
|
|
7147
8097
|
if (typeof error.exitCode === "number") {
|
|
7148
|
-
await CostTracker.flushAll();
|
|
8098
|
+
await Promise.all([CostTracker.flushAll(), ConversationLogger.flushAll()]);
|
|
7149
8099
|
process.exit(error.exitCode);
|
|
7150
8100
|
}
|
|
7151
8101
|
console.error(String(err));
|
|
7152
|
-
await CostTracker.flushAll();
|
|
8102
|
+
await Promise.all([CostTracker.flushAll(), ConversationLogger.flushAll()]);
|
|
7153
8103
|
process.exit(ExitCode.GENERAL_ERROR);
|
|
7154
8104
|
}
|
|
7155
8105
|
//# sourceMappingURL=index.js.map
|