@thincoder/core 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/abort-provenance.mjs +116 -0
- package/advisor/citations.mjs +139 -0
- package/advisor/compaction.mjs +174 -0
- package/advisor/convergence.mjs +80 -0
- package/advisor/history.mjs +77 -0
- package/advisor/loop.mjs +288 -0
- package/advisor/messages.mjs +299 -0
- package/advisor/notice.mjs +141 -0
- package/advisor/project-context.mjs +197 -0
- package/advisor/repos.mjs +150 -0
- package/advisor/run.mjs +190 -0
- package/advisor/truncate.mjs +57 -0
- package/advisor.mjs +281 -0
- package/agent/child-marks.mjs +24 -0
- package/agent/completion.mjs +145 -0
- package/agent/dispatch.mjs +493 -0
- package/agent/family-tools.mjs +174 -0
- package/agent/helpers.mjs +412 -0
- package/agent/post-turn.mjs +70 -0
- package/agent/record-results.mjs +174 -0
- package/agent/relay-prefix.mjs +39 -0
- package/agent/run-stages.mjs +244 -0
- package/agent/setup-reminders.mjs +199 -0
- package/agent/setup.mjs +234 -0
- package/agent/spawn-child.mjs +258 -0
- package/agent/suspension.mjs +240 -0
- package/agent/write-gate.mjs +87 -0
- package/agent-tools/advisor-async.mjs +481 -0
- package/agent-tools/advisor-settle.mjs +240 -0
- package/agent-tools/advisor.mjs +280 -0
- package/agent-tools/async-discard.mjs +143 -0
- package/agent-tools/async-settle.mjs +299 -0
- package/agent-tools/batch-segment.mjs +265 -0
- package/agent-tools/child-permission.mjs +45 -0
- package/agent-tools/consult.mjs +471 -0
- package/agent-tools/design-token.mjs +117 -0
- package/agent-tools/digest-budget.mjs +76 -0
- package/agent-tools/eng.mjs +102 -0
- package/agent-tools/escalate-async.mjs +302 -0
- package/agent-tools/goal.mjs +119 -0
- package/agent-tools/panel-blocks.mjs +24 -0
- package/agent-tools/parent-channel.mjs +231 -0
- package/agent-tools/plan.mjs +86 -0
- package/agent-tools/read-history.mjs +309 -0
- package/agent-tools/recent-changes.mjs +24 -0
- package/agent-tools/review-facts.mjs +31 -0
- package/agent-tools/settings.mjs +268 -0
- package/agent-tools/skill.mjs +63 -0
- package/agent-tools/spawn-gates.mjs +109 -0
- package/agent-tools/subagent-actions.mjs +496 -0
- package/agent-tools/subagent-async.mjs +456 -0
- package/agent-tools/subagent-panel.mjs +160 -0
- package/agent-tools/subagent-run.mjs +208 -0
- package/agent-tools/subagent-scheduler.mjs +446 -0
- package/agent-tools/subagent-spawn.mjs +478 -0
- package/agent-tools/subagent.mjs +419 -0
- package/agent-tools/task.mjs +87 -0
- package/agent-tools/timer.mjs +46 -0
- package/agent-tools/verify.mjs +295 -0
- package/agent-tools.mjs +23 -0
- package/agent.mjs +430 -0
- package/auto-think.mjs +115 -0
- package/compress-form.mjs +24 -0
- package/config-io.mjs +277 -0
- package/config-migrate.mjs +178 -0
- package/config-presets.mjs +49 -0
- package/config.mjs +419 -0
- package/context.mjs +495 -0
- package/conventions.mjs +223 -0
- package/embedding.mjs +120 -0
- package/escape.mjs +152 -0
- package/expand-home.mjs +16 -0
- package/explore-distill.mjs +152 -0
- package/generate-title.mjs +123 -0
- package/git/checkpoint.mjs +448 -0
- package/git/gitmem.mjs +100 -0
- package/history-window.mjs +179 -0
- package/hooks.mjs +108 -0
- package/i18n.mjs +106 -0
- package/index-bin.mjs +48 -0
- package/index-discover.mjs +176 -0
- package/ledger-cmd.mjs +209 -0
- package/ledger-db.mjs +86 -0
- package/ledger-surface.mjs +76 -0
- package/ledger.mjs +202 -0
- package/log.mjs +195 -0
- package/manifest.mjs +338 -0
- package/markdown.mjs +106 -0
- package/mcp/helpers.mjs +51 -0
- package/mcp/transport-http.mjs +248 -0
- package/mcp/transport-stdio.mjs +140 -0
- package/mcp/transport-ws.mjs +122 -0
- package/mcp.mjs +295 -0
- package/memory/code-index.mjs +219 -0
- package/memory/code-sync.mjs +427 -0
- package/memory/core.mjs +318 -0
- package/memory/delete.mjs +242 -0
- package/memory/docs.mjs +431 -0
- package/memory/file-walk.mjs +109 -0
- package/memory/origin.mjs +24 -0
- package/memory/scan.mjs +177 -0
- package/memory/schema.mjs +460 -0
- package/memory.mjs +21 -0
- package/model-ref.mjs +66 -0
- package/model-specs.mjs +277 -0
- package/package.json +32 -0
- package/peer-domains.mjs +265 -0
- package/peer-instances.mjs +178 -0
- package/permission.mjs +79 -0
- package/process-probe.mjs +315 -0
- package/prompt-files.mjs +113 -0
- package/prompt-overlays.mjs +78 -0
- package/prompts/advisor-design.md +43 -0
- package/prompts/advisor-round1.md +41 -0
- package/prompts/advisor-round2.md +46 -0
- package/prompts/advisor-round3.md +42 -0
- package/prompts/common.md +158 -0
- package/prompts/consult-base.md +19 -0
- package/prompts/discipline-engineering.md +123 -0
- package/prompts/discipline-normal.md +206 -0
- package/prompts/persona-coder.md +21 -0
- package/prompts/persona-eng-coder.md +41 -0
- package/prompts/persona-eng-designer.md +80 -0
- package/prompts/persona-engineering.md +160 -0
- package/prompts/persona-explore.md +15 -0
- package/prompts/persona-normal.md +35 -0
- package/prompts/persona-plan.md +27 -0
- package/provider/anthropic.mjs +225 -0
- package/provider/core.mjs +491 -0
- package/provider/errors.mjs +101 -0
- package/provider/google.mjs +257 -0
- package/provider/index.mjs +7 -0
- package/provider/list-models.mjs +163 -0
- package/provider/normalize.mjs +81 -0
- package/provider/rate.mjs +168 -0
- package/provider/responses.mjs +495 -0
- package/provider/retry.mjs +88 -0
- package/provider/sse.mjs +264 -0
- package/provider/wait-status.mjs +59 -0
- package/proxy.mjs +274 -0
- package/rules.mjs +53 -0
- package/session-gc.mjs +248 -0
- package/session-guard.mjs +59 -0
- package/session-lifecycle.mjs +305 -0
- package/session-migrate.mjs +48 -0
- package/session-rename.mjs +38 -0
- package/session-segments.mjs +99 -0
- package/session-slot-write.mjs +168 -0
- package/session-slots-manifest.mjs +264 -0
- package/session-slots.mjs +298 -0
- package/session-store.mjs +441 -0
- package/session.mjs +244 -0
- package/skills.mjs +234 -0
- package/text-budget.mjs +79 -0
- package/token-ttl.mjs +285 -0
- package/tool-docs/apply_patch.md +15 -0
- package/tool-docs/bash.md +38 -0
- package/tool-docs/delete.md +13 -0
- package/tool-docs/edit.md +30 -0
- package/tool-docs/execute.md +21 -0
- package/tool-docs/fetch.md +12 -0
- package/tool-docs/file_ops.md +17 -0
- package/tool-docs/get_current_time.md +8 -0
- package/tool-docs/git.md +54 -0
- package/tool-docs/glob.md +11 -0
- package/tool-docs/grep.md +19 -0
- package/tool-docs/hashline_edit.md +14 -0
- package/tool-docs/insert_after.md +15 -0
- package/tool-docs/lint.md +10 -0
- package/tool-docs/ls.md +12 -0
- package/tool-docs/lsp.md +10 -0
- package/tool-docs/process.md +10 -0
- package/tool-docs/question.md +16 -0
- package/tool-docs/read.md +20 -0
- package/tool-docs/read_image.md +8 -0
- package/tool-docs/tree.md +14 -0
- package/tool-docs/wait_for.md +22 -0
- package/tool-docs/websearch.md +16 -0
- package/tool-docs/write.md +11 -0
- package/tools/bash.mjs +276 -0
- package/tools/edit-batch.mjs +204 -0
- package/tools/edit-diff.mjs +388 -0
- package/tools/exec-run.mjs +43 -0
- package/tools/execute.mjs +243 -0
- package/tools/file.mjs +464 -0
- package/tools/git-checkpoint.mjs +143 -0
- package/tools/git-ext.mjs +173 -0
- package/tools/git.mjs +415 -0
- package/tools/glob-dialect.mjs +130 -0
- package/tools/index.mjs +76 -0
- package/tools/linter.mjs +120 -0
- package/tools/lsp.mjs +335 -0
- package/tools/ops.mjs +293 -0
- package/tools/patch.mjs +290 -0
- package/tools/question.mjs +26 -0
- package/tools/repomap.mjs +314 -0
- package/tools/search.mjs +248 -0
- package/tools/shared.mjs +467 -0
- package/tools/tree.mjs +81 -0
- package/tools/web.mjs +224 -0
- package/tools/write-path.mjs +191 -0
- package/traces/trace-store.mjs +303 -0
- package/undo-stack.mjs +47 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trace-store.mjs — §18.6 完整轨迹存档(AGENT-LOOP.md §18.6 D-TR1..TR8 权威规格)。
|
|
3
|
+
*
|
|
4
|
+
* 完整请求-响应轨迹落盘:每次 chat() 调用一个 JSONL 文件到
|
|
5
|
+
* ~/.thincoder/traces/YYYY-MM-DD/<sessionKey>-<seq>.jsonl。采集点唯一 = core.mjs
|
|
6
|
+
* chat() 导出出口(所有模型调用——主回合/消化轮/compress/distill/advisor/子代理/
|
|
7
|
+
* auto-think/consult——都经该函数)——续写/重试在出口已合并(reasoning 全量)。
|
|
8
|
+
*
|
|
9
|
+
* 纪律(与 log.mjs 同源惯例):
|
|
10
|
+
* - 真 fire-and-forget(F-TR3——模型调用路径零额外阻塞):seq 同步预留(原子号位),
|
|
11
|
+
* 写盘异步(node:fs/promises——不 await);落盘失败静默降级(不抛错、不阻塞
|
|
12
|
+
* chat() 返回)。recordChatTrace 返回落盘 promise——仅供测试/显式消费方 await。
|
|
13
|
+
* - 测试隔离:node --test 进程(NODE_TEST_CONTEXT)默认不写盘——防测试事件污染真实
|
|
14
|
+
* 轨迹目录(既有测试跑真实 agent 管线会产生数百次 chat 调用);显式设置
|
|
15
|
+
* THINCODER_TRACES_DIR 强制写入该目录(traces.test.mjs 用它隔离临时目录——
|
|
16
|
+
* 与 log.mjs 的 THINCODER_LOG_DIR 同惯例)。
|
|
17
|
+
* - 脱敏(D-TR2):复用 log.mjs 字段名黑名单(apikey/designtoken/password/secret/
|
|
18
|
+
* token/authorization/proxyuri/proxy)+ SECRET_FORM 形态扫描(redactSecret)——对
|
|
19
|
+
* messages/content/reasoning/toolCalls/error 全字段应用;不发明新遮蔽模式。
|
|
20
|
+
* **单遍序列化**:脱敏内联进输出缓冲(不构造复制图 + 二次全量字符串)——字段名/次序
|
|
21
|
+
* 与 `JSON.stringify` 形态同构(T-TR1 等价断言)。
|
|
22
|
+
* - 容量 / 清理(§2.5 #116 / A21 已裁 · 按建议——取 VSC 侧):**完整落盘**(不截断、
|
|
23
|
+
* 不丢行、不丢记录);**清理 = 每写一次 prune**(写盘成功后顺带 `cleanupTraces`——
|
|
24
|
+
* 与写盘同在 fire-and-forget 异步体内)。目录按日组织(YYYY-MM-DD),可按日/会话过滤。
|
|
25
|
+
* - seq = 当日目录内最大已有 seq + 1(D-TR3——跨会话/进程重启不覆写既有旧轨迹——
|
|
26
|
+
* 18.6.1 评审 #3);**进程内 seqCache(§23.3.2——消除逐调用同步扫目录)**:首次
|
|
27
|
+
* readdirSync 后进程内递增预留,目录被清理(retention)后取下界重扫一次(防御)。
|
|
28
|
+
* 多进程语义(修正轮 #10——登记):同 cwd 多实例各自进程内缓存——seq 可撞、同
|
|
29
|
+
* sessionKey 记录并入同名文件(append 不覆写);**可容忍**(本机 traces = 诊断面、
|
|
30
|
+
* 默认 OFF、撞号不损坏数据);不做跨进程协调/落盘校验。
|
|
31
|
+
* - 日期分日按本地时区(D-TR3——2026-09-04 fix round1:初版 toISOString()=UTC——
|
|
32
|
+
* 本地 00:00-08:00 的调用会落进前一日目录——改本地日期字符串 YYYY-MM-DD——T-TR12)。
|
|
33
|
+
* - isContinuation(D-TR1——2026-09-04 fix round1):续写/重试链标记——true = 该调用
|
|
34
|
+
* 是续写链的一环(core.mjs 续写递归传出 logCtx.isContinuation);新调用 false。
|
|
35
|
+
* 记录不输出 round 字段(全设计无 round 定义——删——不发明无来源字段)。
|
|
36
|
+
*/
|
|
37
|
+
import { readdirSync, existsSync } from "node:fs"
|
|
38
|
+
import { appendFile, mkdir, readdir, stat, unlink, rmdir } from "node:fs/promises"
|
|
39
|
+
import { join } from "node:path"
|
|
40
|
+
import { createHash } from "node:crypto"
|
|
41
|
+
import { configDir, loadConfig } from "../config.mjs"
|
|
42
|
+
import { redactSecret, errText, classifyErr } from "../log.mjs"
|
|
43
|
+
import { normalizeCwd } from "../session-slots.mjs"
|
|
44
|
+
|
|
45
|
+
/** 轨迹根目录:THINCODER_TRACES_DIR(测试隔离/override——同 THINCODER_LOG_DIR
|
|
46
|
+
* 惯例)> ~/.thincoder/traces(configDir——D-TR3——与 sessions/ 同域)。 */
|
|
47
|
+
export function tracesRoot() {
|
|
48
|
+
return process.env.THINCODER_TRACES_DIR ?? join(configDir, "traces")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 写门(测试隔离):test runner 进程(NODE_TEST_CONTEXT)默认跳过——除显式
|
|
52
|
+
* THINCODER_TRACES_DIR override(traces.test.mjs 隔离临时目录)。 */
|
|
53
|
+
function writeEnabled() {
|
|
54
|
+
if (process.env.NODE_TEST_CONTEXT && !process.env.THINCODER_TRACES_DIR) return false
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** sessionKey = sha1(normalizeCwd(cwd))[:12](D-TR3——与 session-slots sessionPath
|
|
59
|
+
* 同算法同 cwd 归一——两端 hash 一致)。 */
|
|
60
|
+
export function traceSessionKey(cwd) {
|
|
61
|
+
return createHash("sha1").update(normalizeCwd(cwd)).digest("hex").slice(0, 12)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 本地时区日期字符串 YYYY-MM-DD(D-TR3——2026-09-04 fix round1:初版
|
|
65
|
+
* toISOString()=UTC 日期——本地 00:00-08:00 落前一日目录;本地日期才是用户视角的
|
|
66
|
+
* "今天"——T-TR12)。recordChatTrace 与测试读取 helpers 共用同一实现——不双写。 */
|
|
67
|
+
export function localDateStr(date = new Date()) {
|
|
68
|
+
const y = date.getFullYear()
|
|
69
|
+
const m = String(date.getMonth() + 1).padStart(2, "0")
|
|
70
|
+
const d = String(date.getDate()).padStart(2, "0")
|
|
71
|
+
return `${y}-${m}-${d}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Test hooks(_rateHooks 同惯例——rate.mjs):now/readdir/exists/mkdir/append 注入缝
|
|
75
|
+
* (T-TR5 计数 readdirSync;T-TR4 慢写替身;T-TR12 注入本地日 ≠ UTC 日的时刻)。
|
|
76
|
+
* 生产永远走真实实现。 */
|
|
77
|
+
export const _traceHooks = {
|
|
78
|
+
now: () => new Date(),
|
|
79
|
+
readdir: (dir) => readdirSync(dir),
|
|
80
|
+
exists: (p) => existsSync(p),
|
|
81
|
+
mkdir: (dir) => mkdir(dir, { recursive: true }),
|
|
82
|
+
append: (path, text) => appendFile(path, text, "utf8"),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 当日轨迹目录(D-TR3:traces/YYYY-MM-DD——分日组织——N-TR1)。 */
|
|
86
|
+
export function tracesDirFor(dateStr) {
|
|
87
|
+
return join(tracesRoot(), dateStr)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── 容量 / 清理策略(§2.5 #116 / A21 已裁 · 按建议——取 VSC 侧)───────────────
|
|
91
|
+
// 完整落盘:不截断、不丢行(无单消息/单记录额度,无在途丢弃);清理 = 每写一次 prune
|
|
92
|
+
// (写盘成功后顺带 cleanupTraces——与写盘同在 fire-and-forget 异步体内,永不阻塞 chat)。
|
|
93
|
+
|
|
94
|
+
// 进程内 seq 缓存(§23.3.2:dir → 已见/已预留 max——首次 readdirSync 后递增预留;
|
|
95
|
+
// 目录被清理(retention)后取下界重扫一次(防御))。
|
|
96
|
+
const _seqCache = new Map()
|
|
97
|
+
|
|
98
|
+
/** 测试缝:清进程内状态(seqCache)。 */
|
|
99
|
+
export function _resetTraceStateForTest() {
|
|
100
|
+
_seqCache.clear()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function scanMaxSeq(dir) {
|
|
104
|
+
if (!_traceHooks.exists(dir)) return 0
|
|
105
|
+
let names
|
|
106
|
+
try { names = _traceHooks.readdir(dir) } catch { return 0 }
|
|
107
|
+
let max = 0
|
|
108
|
+
for (const name of names) {
|
|
109
|
+
const m = name.match(/-(\d+)\.jsonl$/)
|
|
110
|
+
if (m) max = Math.max(max, Number(m[1]))
|
|
111
|
+
}
|
|
112
|
+
return max
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** seq = max(当日目录最大已有 seq, 进程内已预留) + 1(D-TR3——跨会话/进程重启
|
|
116
|
+
* 不覆写——T-TR8/T-TR10;同步预留 = 异步写盘启动前的原子号位分配)。
|
|
117
|
+
* §23.3.2:seqCache 进程内递增——不逐调用 readdirSync(目录被清理/缺失 → 重扫一次)。 */
|
|
118
|
+
export function nextTraceSeq(dateStr) {
|
|
119
|
+
const dir = tracesDirFor(dateStr)
|
|
120
|
+
let max = _seqCache.get(dir)
|
|
121
|
+
if (max === undefined || !_traceHooks.exists(dir)) {
|
|
122
|
+
// 首次调用(或目录被 retention 清理/缺失)——重扫磁盘取真实下界(防御:清除后重号安全)
|
|
123
|
+
const disk = scanMaxSeq(dir)
|
|
124
|
+
max = Math.max(disk, max ?? 0)
|
|
125
|
+
}
|
|
126
|
+
const seq = max + 1
|
|
127
|
+
_seqCache.set(dir, seq)
|
|
128
|
+
return seq
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 开关(D-TR6):traces.enabled 缺省 on(默认全采集);logCtx.traces === false
|
|
132
|
+
* 显式关闭(调用点读取 agent.config.traces.enabled——D-TR6)。 */
|
|
133
|
+
export function tracesEnabled(logCtx) {
|
|
134
|
+
return logCtx?.traces !== false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── 单遍序列化(§23.3.2 D-TR1——脱敏内联、零复制图、字段名/次序同构)────────
|
|
138
|
+
|
|
139
|
+
const OMIT = Symbol("omit") // JSON.stringify 语义:对象属性省略(数组元素 → null)
|
|
140
|
+
|
|
141
|
+
function scalarJson(key, v) {
|
|
142
|
+
if (typeof v === "string") {
|
|
143
|
+
// 脱敏(D-TR2)单点:完整落盘(#116 取 VSC——不截断);redactSecret 内联写出。
|
|
144
|
+
return JSON.stringify(redactSecret(key, v))
|
|
145
|
+
}
|
|
146
|
+
if (v === null) return "null"
|
|
147
|
+
if (typeof v === "number") return Number.isFinite(v) ? String(v) : "null" // JSON.stringify(NaN/±Infinity) === "null"
|
|
148
|
+
if (typeof v === "boolean") return v ? "true" : "false"
|
|
149
|
+
return undefined // undefined/function/symbol/bigint → 省略(JSON.stringify 同语义)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function writeValue(out, key, v) {
|
|
153
|
+
const s = scalarJson(key, v)
|
|
154
|
+
if (s !== undefined) { out.push(s); return true }
|
|
155
|
+
if (Array.isArray(v)) {
|
|
156
|
+
out.push("[")
|
|
157
|
+
for (let i = 0; i < v.length; i++) {
|
|
158
|
+
if (i) out.push(",")
|
|
159
|
+
if (!writeValue(out, key, v[i])) out.push("null") // 数组元素不可省略
|
|
160
|
+
}
|
|
161
|
+
out.push("]")
|
|
162
|
+
return true
|
|
163
|
+
}
|
|
164
|
+
if (v && typeof v === "object") {
|
|
165
|
+
if (typeof v.toJSON === "function") { // Date/宿主对象:保持 JSON.stringify 等价
|
|
166
|
+
const js = JSON.stringify(v)
|
|
167
|
+
out.push(js === undefined ? "null" : js)
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
out.push("{")
|
|
171
|
+
let first = true
|
|
172
|
+
for (const [k, val] of Object.entries(v)) {
|
|
173
|
+
const omit = val === undefined || typeof val === "function" || typeof val === "symbol" || typeof val === "bigint"
|
|
174
|
+
if (omit) continue
|
|
175
|
+
if (!first) out.push(",")
|
|
176
|
+
first = false
|
|
177
|
+
out.push(JSON.stringify(k), ":")
|
|
178
|
+
writeValue(out, k, val)
|
|
179
|
+
}
|
|
180
|
+
out.push("}")
|
|
181
|
+
return true
|
|
182
|
+
}
|
|
183
|
+
return false // OMIT
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 收集一次 chat 调用轨迹(D-TR1 字段集)——fire-and-forget(F-TR3)。
|
|
188
|
+
* 由 core.mjs chat() 导出出口调用(唯一采集点——N-TR2);签名零参数膨胀——
|
|
189
|
+
* 数据全部来自调用方已传入的 opts(logCtx 元数据——D-TR4)+ provider + result/error。
|
|
190
|
+
*
|
|
191
|
+
* @param {Object} provider chat() 的 provider 参数(provider/model 字段来源)
|
|
192
|
+
* @param {Object} opts chat() 原始 opts —— messages(输入)与 logCtx(元数据:
|
|
193
|
+
* role/depth/kind/session/cwd/stage/turn/traces——调用点增补,D-TR4)
|
|
194
|
+
* @param {Object|null} result chatImpl 返回值(成功路径——content/reasoning/
|
|
195
|
+
* toolCalls/usage/finishReason;N-TR3:出口汇总——续写/重试后全量)
|
|
196
|
+
* @param {Error|null} error chat() 抛出的错误(失败路径——D-TR5:error(errText
|
|
197
|
+
* 截断 + 类别)+ finishReason:null——失败轨迹恰是分析纠结点最有效的材料)
|
|
198
|
+
*/
|
|
199
|
+
export function recordChatTrace(provider, opts = {}, result = null, error = null) {
|
|
200
|
+
if (!writeEnabled()) return
|
|
201
|
+
const logCtx = opts.logCtx ?? {}
|
|
202
|
+
if (!tracesEnabled(logCtx)) return
|
|
203
|
+
// cwd/session 经 logCtx 增补(调用点传 agent.cwd / agent._sessionStart——
|
|
204
|
+
// D-TR3 的 cwdHash/命名与 D-TR1 的 session 字段所需);无 agent 作用域的调用点
|
|
205
|
+
// 回退 process.cwd()——CLI 会话即工作区。该类点开关状态:distill 已含
|
|
206
|
+
// traces 开关(D-TR6 fix round1);auto-think 已闭环(D-TS12——chat 调用点
|
|
207
|
+
// logCtx 全字段补传:traces/session/cwd/role/depth/kind——无残留点)。
|
|
208
|
+
const cwd = logCtx.cwd ?? process.cwd()
|
|
209
|
+
// D-TR3(fix round1):分日按本地时区(_traceHooks.now——T-TR12 注入点);
|
|
210
|
+
// ts 与 dateStr 同源(同一时刻)——记录时间戳与目录日不撕裂。
|
|
211
|
+
const now = _traceHooks.now()
|
|
212
|
+
const dateStr = localDateStr(now)
|
|
213
|
+
const seq = nextTraceSeq(dateStr)
|
|
214
|
+
const sessionKey = traceSessionKey(cwd)
|
|
215
|
+
|
|
216
|
+
// 单遍序列化:头段字段 + messages(独立缓冲——字段次序与既有 JSON.stringify 形态同构)+ 尾段字段。
|
|
217
|
+
const parts = ["{"]
|
|
218
|
+
let first = true
|
|
219
|
+
const put = (k, v) => {
|
|
220
|
+
if (!first) parts.push(",")
|
|
221
|
+
first = false
|
|
222
|
+
parts.push(JSON.stringify(k), ":")
|
|
223
|
+
writeValue(parts, k, v)
|
|
224
|
+
}
|
|
225
|
+
put("ts", now.toISOString())
|
|
226
|
+
put("session", logCtx.session ?? null)
|
|
227
|
+
put("cwdHash", createHash("sha1").update(normalizeCwd(cwd)).digest("hex"))
|
|
228
|
+
put("role", logCtx.role ?? null)
|
|
229
|
+
put("depth", logCtx.depth ?? null)
|
|
230
|
+
put("turn", logCtx.turn ?? null)
|
|
231
|
+
put("provider", provider?.name ?? provider?.model ?? "unknown")
|
|
232
|
+
put("model", provider?.model ?? "")
|
|
233
|
+
put("stage", logCtx.stage ?? null)
|
|
234
|
+
put("kind", logCtx.kind ?? null)
|
|
235
|
+
// D-TR1(fix round1):续写/重试链标记——true = 该调用是续写链的一环
|
|
236
|
+
// (core.mjs 续写递归传出);新调用缺省 false——round 字段已删(无来源)。
|
|
237
|
+
put("isContinuation", logCtx.isContinuation === true)
|
|
238
|
+
const msgAt = parts.length
|
|
239
|
+
const msgParts = []
|
|
240
|
+
writeValue(msgParts, "messages", opts.messages ?? [])
|
|
241
|
+
put("content", result?.content ?? null)
|
|
242
|
+
put("reasoning", result?.reasoning ?? null)
|
|
243
|
+
put("toolCalls", result?.toolCalls ?? null)
|
|
244
|
+
put("usage", result?.usage ?? null)
|
|
245
|
+
put("finishReason", result?.finishReason ?? null)
|
|
246
|
+
if (error != null) {
|
|
247
|
+
// D-TR5:错误路径轨迹——error(errText 截断 + 类别)+ finishReason:null
|
|
248
|
+
put("error", {
|
|
249
|
+
err: redactSecret("err", errText(error, 500)),
|
|
250
|
+
kind: classifyErr(error, opts.signal),
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
const headStr = parts.slice(0, msgAt).join("")
|
|
254
|
+
const tailStr = parts.slice(msgAt).join("")
|
|
255
|
+
const msgStr = msgParts.join("")
|
|
256
|
+
const record = `${headStr},${JSON.stringify("messages")}:${msgStr}${tailStr}}`
|
|
257
|
+
|
|
258
|
+
// 真 fire-and-forget(F-TR3——模型调用路径零额外阻塞):seq 已在上面同步预留
|
|
259
|
+
// (原子号位——写盘在途并发不撞号);写盘异步(不 await——chat() 出口立即返回)。
|
|
260
|
+
// 返回值 = 落盘 promise(测试 await 用;chat() 不消费——fire-and-forget 语义)。
|
|
261
|
+
// §2.5 #116(A21 已裁):写盘成功后每写一次 prune(与写盘同异步体内——零阻塞)。
|
|
262
|
+
return (async () => {
|
|
263
|
+
try {
|
|
264
|
+
const dir = tracesDirFor(dateStr)
|
|
265
|
+
await _traceHooks.mkdir(dir) // D-TR3:写前建目录(与 sessions/tool-results 同惯例)
|
|
266
|
+
await _traceHooks.append(join(dir, `${sessionKey}-${seq}.jsonl`), record + "\n")
|
|
267
|
+
let retentionHours = 24
|
|
268
|
+
try { retentionHours = loadConfig()?.traces?.retentionHours ?? 24 } catch { /* 默认 24h */ }
|
|
269
|
+
await cleanupTraces({ dir: tracesRoot(), retentionHours })
|
|
270
|
+
} catch {
|
|
271
|
+
// F-TR3:落盘/prune 失败静默降级——不抛错、不阻塞 chat() 返回
|
|
272
|
+
}
|
|
273
|
+
})()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* D-TR10(用户裁定——发布隐私 + 磁盘卫生):清理——删除 traces 根下
|
|
278
|
+
* mtime 超过保留期的轨迹文件(保留期 = config.traces.retentionHours,默认 24h);
|
|
279
|
+
* 删空的日期目录(YYYY-MM-DD)。目录里非 .jsonl 文件不碰。
|
|
280
|
+
* §2.5 #116 / A21(已裁 · 按建议):调用点 = 每写一次 prune(`recordChatTrace` 写盘成功
|
|
281
|
+
* 后顺带调用——fire-and-forget 异步体内);CLI 启动点的启动清理保留(本端不动)。
|
|
282
|
+
* 返回删除文件数。
|
|
283
|
+
*/
|
|
284
|
+
export async function cleanupTraces({ dir = tracesRoot(), retentionHours = 24 } = {}) {
|
|
285
|
+
const cutoff = Date.now() - retentionHours * 3_600_000
|
|
286
|
+
let days
|
|
287
|
+
try { days = await readdir(dir) } catch { return 0 } // 目录不存在/不可读 → 无事可做
|
|
288
|
+
let removed = 0
|
|
289
|
+
for (const day of days) {
|
|
290
|
+
const dayDir = join(dir, day)
|
|
291
|
+
try { if (!(await stat(dayDir)).isDirectory()) continue } catch { continue }
|
|
292
|
+
let names
|
|
293
|
+
try { names = await readdir(dayDir) } catch { continue }
|
|
294
|
+
for (const n of names) {
|
|
295
|
+
if (!n.endsWith(".jsonl")) continue
|
|
296
|
+
try {
|
|
297
|
+
if ((await stat(join(dayDir, n))).mtimeMs < cutoff) { await unlink(join(dayDir, n)); removed++ }
|
|
298
|
+
} catch { /* 单个文件失败不影响其余 */ }
|
|
299
|
+
}
|
|
300
|
+
try { if ((await readdir(dayDir)).length === 0) await rmdir(dayDir) } catch {}
|
|
301
|
+
}
|
|
302
|
+
return removed
|
|
303
|
+
}
|
package/undo-stack.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* undo-stack.mjs — 写工具副作用前的文件快照(`agent._undoStack`)。
|
|
3
|
+
*
|
|
4
|
+
* 来源 = `thincoder-cli/src/tui/cmd-undo.mjs` 的 `snapshotForUndo` + `MAX_UNDO`——**逐字随迁**
|
|
5
|
+
* (CORE-UNIFICATION §2.5 #149「两阶段执行 + 前置门禁按核内结构归位」;dispatch 的唯一调用点
|
|
6
|
+
* 属核内面,函数本体零 TUI 依赖 ⇒ 按核内结构归位到核;`/undo` 命令面 `handleUndoCommand`
|
|
7
|
+
* 留 CLI 壳——S2 由壳 import 本档)。
|
|
8
|
+
*
|
|
9
|
+
* Tracks write/edit/delete/hashline_edit/apply_patch operations in agent._undoStack.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
13
|
+
import { isAbsolute, join } from "node:path"
|
|
14
|
+
|
|
15
|
+
const MAX_UNDO = 50
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Snapshot a file before a side-effect tool modifies it.
|
|
19
|
+
* Called from dispatch.mjs before each write/edit/delete/apply_patch/hashline_edit.
|
|
20
|
+
*/
|
|
21
|
+
export function snapshotForUndo(agent, toolName, args, cwd) {
|
|
22
|
+
if (!agent._undoStack) agent._undoStack = []
|
|
23
|
+
const path = args.path ?? args.file
|
|
24
|
+
if (!path || typeof path !== "string") return
|
|
25
|
+
|
|
26
|
+
// 绝对路径直接用(path.join 对绝对段不重置——Windows 反斜杠路径也不按 "/" 切分);相对路径整段 join(cwd)。
|
|
27
|
+
const abs = isAbsolute(path) ? path : join(cwd, path)
|
|
28
|
+
let backup = null
|
|
29
|
+
try {
|
|
30
|
+
if (existsSync(abs)) {
|
|
31
|
+
backup = readFileSync(abs, "utf8")
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
// can't read — maybe binary, skip
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
agent._undoStack.push({
|
|
39
|
+
tool: toolName,
|
|
40
|
+
path,
|
|
41
|
+
backup,
|
|
42
|
+
timestamp: Date.now(),
|
|
43
|
+
})
|
|
44
|
+
if (agent._undoStack.length > MAX_UNDO) agent._undoStack.shift()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { MAX_UNDO }
|