@thincoder/core 0.10.7 → 0.10.9
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/CHANGELOG.md +21 -0
- package/advisor/compaction.mjs +1 -1
- package/advisor/run.mjs +1 -1
- package/agent/chat-call.mjs +40 -27
- package/agent/child-marks.mjs +1 -1
- package/agent/dispatch-gates.mjs +7 -4
- package/agent/dispatch-run.mjs +1 -0
- package/agent/family-tools.mjs +13 -8
- package/agent/helpers.mjs +12 -10
- package/agent/image-heal.mjs +65 -0
- package/agent/light-round.mjs +4 -4
- package/agent/live-beat.mjs +4 -4
- package/agent/post-turn.mjs +34 -0
- package/agent/record-results.mjs +9 -1
- package/agent/run-stages.mjs +45 -10
- package/agent/setup-reminders.mjs +14 -13
- package/agent/setup.mjs +72 -48
- package/agent/spawn-child.mjs +1 -1
- package/agent/subagent-event.mjs +1 -1
- package/agent/suspension.mjs +2 -1
- package/agent/timers.mjs +8 -8
- package/agent/turn-loop.mjs +7 -1
- package/agent-tools/advisor.mjs +1 -1
- package/agent-tools/async-discard.mjs +40 -5
- package/agent-tools/async-settle.mjs +3 -1
- package/agent-tools/audit-block.mjs +5 -3
- package/agent-tools/batch-gate.mjs +198 -0
- package/agent-tools/batch-lifecycle.mjs +216 -36
- package/agent-tools/batch-paths.mjs +20 -20
- package/agent-tools/batch-skeleton.mjs +87 -0
- package/agent-tools/batch.mjs +11 -3
- package/agent-tools/checkpoint.mjs +1 -1
- package/agent-tools/context.mjs +28 -5
- package/agent-tools/design-slots.mjs +20 -7
- package/agent-tools/doc-check.mjs +75 -0
- package/agent-tools/eng.mjs +5 -3
- package/agent-tools/manifest.mjs +18 -18
- package/agent-tools/parent-channel.mjs +3 -3
- package/agent-tools/review-facts.mjs +2 -2
- package/agent-tools/settings.mjs +1 -1
- package/agent-tools/spawn-gates.mjs +108 -47
- package/agent-tools/ssh-session.mjs +486 -0
- package/agent-tools/ssh-transport.mjs +324 -0
- package/agent-tools/ssh-vault.mjs +291 -0
- package/agent-tools/subagent-scheduler.mjs +6 -5
- package/agent-tools/subagent-spawn.mjs +18 -11
- package/agent-tools/subagent.mjs +17 -9
- package/agent-tools/task.mjs +1 -1
- package/agent-tools/timer.mjs +8 -2
- package/agent-tools/zero-write-watch.mjs +2 -2
- package/agent-tools.mjs +3 -2
- package/attachments.mjs +4 -4
- package/browser/cdp.mjs +2 -2
- package/browser/launch.mjs +50 -4
- package/browser/snapshot.mjs +1 -1
- package/config-defaults.mjs +92 -0
- package/config-io.mjs +7 -2
- package/config-normalize.mjs +109 -0
- package/config-watch.mjs +2 -2
- package/config.mjs +88 -190
- package/declaration.mjs +5 -1
- package/doc-check/anchors.mjs +142 -0
- package/doc-check/index.mjs +210 -0
- package/doc-check/version.mjs +84 -0
- package/doc-check/width.mjs +132 -0
- package/file-refs.mjs +6 -6
- package/generate-title.mjs +1 -1
- package/hooks.mjs +1 -1
- package/i18n.mjs +13 -1
- package/ledger-cmd.mjs +314 -62
- package/ledger-db.mjs +122 -31
- package/ledger-migrate.mjs +18 -12
- package/ledger-read.mjs +6 -7
- package/ledger-tools.mjs +138 -77
- package/ledger.mjs +34 -62
- package/log.mjs +17 -3
- package/manifest-discovery.mjs +21 -18
- package/manifest-schema.mjs +17 -14
- package/manifest.mjs +37 -22
- package/mcp/transport-stdio.mjs +1 -1
- package/mcp.mjs +2 -2
- package/memory/core.mjs +1 -1
- package/memory/scan.mjs +1 -1
- package/memory/sweep.mjs +1 -1
- package/model-specs.mjs +7 -7
- package/notify-policy.mjs +6 -6
- package/package.json +2 -1
- package/permission.mjs +21 -1
- package/prompts/advisor-design.md +53 -23
- package/prompts/advisor-round1.md +34 -14
- package/prompts/advisor-round2.md +32 -19
- package/prompts/advisor-round3.md +34 -21
- package/prompts/common.md +148 -79
- package/prompts/consult-base.md +14 -8
- package/prompts/discipline-engineering.md +70 -66
- package/prompts/discipline-normal.md +86 -77
- package/prompts/persona-coder.md +6 -6
- package/prompts/persona-eng-coder.md +19 -18
- package/prompts/persona-eng-designer.md +40 -31
- package/prompts/persona-engineering.md +124 -92
- package/prompts/persona-explore.md +2 -2
- package/prompts/persona-normal.md +20 -17
- package/prompts/persona-plan.md +7 -7
- package/prompts/subagent-base.md +4 -4
- package/provider/anthropic.mjs +4 -2
- package/provider/core.mjs +4 -2
- package/provider/errors.mjs +8 -0
- package/provider/google.mjs +8 -1
- package/provider/normalize.mjs +42 -12
- package/provider/responses-request.mjs +4 -3
- package/provider/responses.mjs +2 -2
- package/provider/wait-status.mjs +1 -1
- package/provider-flows.mjs +1 -1
- package/queued.mjs +2 -2
- package/rules.mjs +4 -4
- package/session-gc.mjs +1 -1
- package/session-lifecycle.mjs +7 -7
- package/session-slot-write.mjs +2 -2
- package/session-slots-manifest.mjs +1 -1
- package/session.mjs +1 -1
- package/shell-candidates.mjs +2 -2
- package/shell-identity.mjs +1 -1
- package/team.mjs +63 -5
- package/think-off.mjs +3 -3
- package/token-ttl.mjs +1 -1
- package/token-window.mjs +1 -1
- package/tool-docs/bash.md +1 -1
- package/tool-docs/batch.md +1 -1
- package/tool-docs/browser.md +1 -1
- package/tool-docs/doc_check.md +54 -0
- package/tool-docs/fetch.md +3 -2
- package/tool-docs/ledger.md +7 -5
- package/tool-docs/manifest.md +14 -2
- package/tool-docs/ssh.md +51 -0
- package/tool-docs/subagent.md +6 -9
- package/tool-docs/timer.md +2 -1
- package/tool-docs/websearch.md +3 -1
- package/tools/browser.mjs +1 -1
- package/tools/execute.mjs +1 -1
- package/tools/git-run.mjs +2 -2
- package/tools/git.mjs +8 -8
- package/tools/index.mjs +3 -0
- package/tools/ops.mjs +1 -1
- package/tools/process-tree.mjs +2 -2
- package/tools/ssh-askpass.mjs +35 -0
- package/tools/ssh.mjs +105 -0
- package/tools/web-deepseek.mjs +172 -0
- package/tools/web.mjs +29 -4
- package/vision-reader.mjs +4 -4
package/ledger-tools.mjs
CHANGED
|
@@ -10,12 +10,21 @@
|
|
|
10
10
|
* 接线:族出口 = `ledger.mjs`(re-export——消费面 agent/family-tools.mjs;旧读二装配面已清零);
|
|
11
11
|
* 消费侧一律动态 import(`ledger-db.mjs` 静态 import node:sqlite ⇒ W8 契约②)。
|
|
12
12
|
*/
|
|
13
|
-
import { ledgerAdd, ledgerClose, ledgerCount, ledgerQuery, ledgerUpdate } from "./ledger-cmd.mjs"
|
|
13
|
+
import { ledgerAdd, ledgerClose, ledgerCloseBatch, ledgerCount, ledgerQuery, ledgerUpdate, ledgerUpdateBatch } from "./ledger-cmd.mjs"
|
|
14
|
+
import { resolveLedgerRoot } from "./ledger-db.mjs"
|
|
14
15
|
import { DESC } from "./tools/shared.mjs" // #15 描述外置:文本单点 = tool-docs/ledger.md(DESC 单解析面)
|
|
15
16
|
|
|
16
|
-
/** 工具 cwd 供值面(缺省 →
|
|
17
|
+
/** 工具 cwd 供值面(缺省 → 当前会话锚——「动作目标路径」;项目根由核 `resolveLedgerRoot` 推导)。 */
|
|
17
18
|
const cwdOf = (ctx) => ctx?.agent?.cwd ?? ctx?.cwd ?? process.cwd()
|
|
18
19
|
|
|
20
|
+
/** 根推导**报明面**(设计档 §3.8「报明形态」):向下发现(`basis === 'descendant'`——容器锚收敛序)⇒
|
|
21
|
+
* 对象形回执增 `root` 键;祖先支(自明路径)⇒ `null`(展开零键——回执逐字零改)。供值面 = 核同一
|
|
22
|
+
* 单源 `resolveLedgerRoot`(工具层零自算根;单实现、多调用点)。 */
|
|
23
|
+
const reportedRoot = (cwd) => {
|
|
24
|
+
const derived = resolveLedgerRoot(cwd)
|
|
25
|
+
return derived && derived.basis === "descendant" ? { root: derived.root } : null
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
// ── 工具层参数守卫(设计档 §3.2 · 2026-09-27 快车道修复 · Gitee #IKIQGK / 台账 #472) ──
|
|
20
29
|
|
|
21
30
|
/** 值预览(P1–P5 文案的 `<预览>`——设计档 §3.2):`JSON.stringify`,超 80 字符截断加 `…`
|
|
@@ -28,37 +37,47 @@ const argPreview = (v) => {
|
|
|
28
37
|
/** 枚举取值域文本(P3 / P5 文案同源)。 */
|
|
29
38
|
const enumText = (values) => `{${values.join(", ")}}`
|
|
30
39
|
|
|
31
|
-
/** P3 后缀(声明派生):枚举字段带取值域 / 非枚举必填串带「非空字符串」/
|
|
32
|
-
|
|
40
|
+
/** P3 后缀(声明派生):枚举字段带取值域 / 非枚举必填串带「非空字符串」/ 数字字段仅「必填」
|
|
41
|
+
* (`domain` = 按形条件判据给出的取值域——`whenPresent.enum` 优先于基本形)。 */
|
|
42
|
+
const requiredSuffix = (spec, domain) => (domain ? `;取值 ∈ ${enumText(domain)}` : spec.type === "string" ? ";非空字符串" : "")
|
|
43
|
+
|
|
44
|
+
/** 值非空字段(P6 字段级:空串 / 全空白 ⇒ 拒——声明面不表达值级判据,与 `title` 同源一处)。 */
|
|
45
|
+
const NON_EMPTY_FIELDS = ["title", "resume_when"]
|
|
33
46
|
|
|
34
47
|
/**
|
|
35
|
-
* 工具层参数守卫(设计档 §3.2 · Gitee #IKIQGK / 台账 #472):判序 P1–
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* ⇒ `{}`)。非法 ⇒ throw——判于核函数之前(零写、零库动作)。
|
|
48
|
+
* 工具层参数守卫(设计档 §3.2 · Gitee #IKIQGK / 台账 #472):判序 P1–P7,文案逐字 = §3.2 模板;
|
|
49
|
+
* 消费工具自身声明派生(`required` / `type` / `enum` / `oneOfRequired` / `whenPresent`——枚举 / 必填 /
|
|
50
|
+
* 类型 / 组判据不在守卫内复写);只判不改(零缺省填充 / 零类型转换 / 零文本改写),返回入参对象
|
|
51
|
+
* (唯一归一 = 判序① 的缺省 / `null` ⇒ `{}`)。非法 ⇒ throw——判于核函数之前(零写、零库动作)。
|
|
52
|
+
* 按形条件判据(`whenPresent`):`when` 字段在本笔入参**在场**(非 `null`)⇒ 该条生效(`required` 按
|
|
53
|
+
* P3 判、`enum` 按 P5 判并取代基本形取值域);不在场 ⇒ 本条不生效、按基本形判。
|
|
39
54
|
*/
|
|
40
55
|
function assertToolArgs(tool, args) {
|
|
41
56
|
const name = tool.name
|
|
42
57
|
const decl = tool.parameters ?? {}
|
|
43
58
|
const props = decl.properties ?? {}
|
|
44
59
|
const fields = Object.keys(props)
|
|
60
|
+
// 组判据两键居声明条目顶层(不入 `parameters`——自定义键不随声明交模型 / provider)
|
|
61
|
+
const conditional = tool.whenPresent ?? []
|
|
45
62
|
// P1:入参缺省 / `null` ⇒ 视作 `{}`;非对象(数组 / 标量)⇒ 拒
|
|
46
63
|
if (args == null) args = {}
|
|
47
64
|
else if (typeof args !== "object" || Array.isArray(args)) throw new Error(`${name}:参数须为对象(收到 ${argPreview(args)})`)
|
|
48
65
|
// P2:未知键 ⇒ 拒(判序先于缺参——typo 键先给可用参数集)
|
|
49
66
|
const unknown = Object.keys(args).filter((k) => !Object.hasOwn(props, k))
|
|
50
67
|
if (unknown.length > 0) throw new Error(`${name}:未知参数:${unknown.join(" / ")}(可用参数 = ${fields.join(" / ")})`)
|
|
51
|
-
// P3–P6:逐字段(声明序)——缺失(必填;显式 `null` 同判)→ 类型 / 枚举 →
|
|
68
|
+
// P3–P6:逐字段(声明序)——缺失(必填;显式 `null` 同判)→ 类型 / 枚举 → 值非空
|
|
52
69
|
for (const field of fields) {
|
|
53
70
|
const spec = props[field]
|
|
54
71
|
const value = args[field]
|
|
55
|
-
const
|
|
72
|
+
const rule = conditional.find((c) => c.field === field && args[c.when] != null)
|
|
73
|
+
const required = rule?.required ?? (decl.required ?? []).includes(field)
|
|
74
|
+
const domain = rule?.enum ?? spec.enum
|
|
56
75
|
if (value === undefined || value === null) {
|
|
57
|
-
if (required) throw new Error(`${name}:${field} 缺失(必填${requiredSuffix(spec)})`)
|
|
76
|
+
if (required) throw new Error(`${name}:${field} 缺失(必填${requiredSuffix(spec, domain)})`)
|
|
58
77
|
continue // 可空字段 `null` ⇒ 放行(≡ 略去——与 `patch.x ?? 行值` 既有语义同源)
|
|
59
78
|
}
|
|
60
|
-
if (
|
|
61
|
-
if (!
|
|
79
|
+
if (domain) {
|
|
80
|
+
if (!domain.includes(value)) throw new Error(`${name}:${field} 非法:${argPreview(value)}(取值 ∈ ${enumText(domain)})`)
|
|
62
81
|
} else if (spec.type === "string" && typeof value !== "string") {
|
|
63
82
|
throw new Error(`${name}:${field} 非法:${argPreview(value)}(应为字符串)`)
|
|
64
83
|
} else if (spec.type === "number" && typeof value !== "number") {
|
|
@@ -66,10 +85,16 @@ function assertToolArgs(tool, args) {
|
|
|
66
85
|
} else if (spec.type === "boolean" && typeof value !== "boolean") {
|
|
67
86
|
throw new Error(`${name}:${field} 非法:${argPreview(value)}(应为布尔)`)
|
|
68
87
|
}
|
|
69
|
-
if (field
|
|
88
|
+
if (NON_EMPTY_FIELDS.includes(field) && typeof value === "string" && value.trim() === "") {
|
|
70
89
|
throw new Error(`${name}:${field} 为空(${required ? "必填;" : ""}非空字符串)`)
|
|
71
90
|
}
|
|
72
91
|
}
|
|
92
|
+
// P7:组判据(声明派生 `oneOfRequired`)——组内恰携其一 ⇒ 过;同携 / 双缺 ⇒ 拒
|
|
93
|
+
for (const group of tool.oneOfRequired ?? []) {
|
|
94
|
+
if (group.filter((f) => args[f] != null).length === 1) continue
|
|
95
|
+
const received = Object.fromEntries(group.map((f) => [f, args[f] ?? null]))
|
|
96
|
+
throw new Error(`${name}:${group.join(" ∥ ")} 须恰携其一(收到 ${argPreview(received)})`)
|
|
97
|
+
}
|
|
73
98
|
return args
|
|
74
99
|
}
|
|
75
100
|
|
|
@@ -80,70 +105,92 @@ const FULL_ACTIONS = ["add", "update", "close", "query", "count"]
|
|
|
80
105
|
const READ_ACTIONS = ["query", "count"]
|
|
81
106
|
|
|
82
107
|
/** cwd 参数描述(五前身声明同文——文本单点)。 */
|
|
83
|
-
const CWD_DESC = "
|
|
108
|
+
const CWD_DESC = "本次动作的目标路径——项目根由它推导:沿祖先链最近的项目目录(带 PROJECT-MANIFEST.json 或已有台账库);容器目录下收敛到子项目。台账按项目根关联存储于用户数据目录(工作树外、不进 git)。相对路径按当前工作目录解析;查/写别的项目请给绝对路径。缺省 = 当前会话锚"
|
|
84
109
|
|
|
85
|
-
/** 各 action 声明条目(= 前身旧工具 `parameters` 逐字;伪工具对象守卫 + 弃用壳共用单源)。
|
|
110
|
+
/** 各 action 声明条目(= 前身旧工具 `parameters` 逐字;伪工具对象守卫 + 弃用壳共用单源)。
|
|
111
|
+
* `parameters` 内的键 = 交模型 / provider 的那一份声明;**组判据两键(`oneOfRequired` / `whenPresent`)
|
|
112
|
+
* 居本条条目顶层、不入 `parameters`**——自定义键不随声明外泄(provider 面零风险),守卫仍只读声明。 */
|
|
86
113
|
const ACTION_SPECS = {
|
|
87
114
|
add: {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
parameters: {
|
|
116
|
+
type: "object",
|
|
117
|
+
required: ["kind", "title"],
|
|
118
|
+
properties: {
|
|
119
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
120
|
+
kind: { type: "string", enum: ["requirement", "tech_todo"], description: "类别" },
|
|
121
|
+
title: { type: "string", description: "条目标题" },
|
|
122
|
+
board: { type: "string", description: "归属板块(需求档文档名,可空)" },
|
|
123
|
+
req_doc: { type: "string", description: "需求档指针(可空)" },
|
|
124
|
+
task_book: { type: "string", description: "任务书指针(可空;在途 / 待核销必填——咬合 CHECK)" },
|
|
125
|
+
evidence: { type: "string", description: "最小证据行(可空)" },
|
|
126
|
+
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "技术待办触发(可空)" },
|
|
127
|
+
resume_when: { type: "string", description: "复启条件句(可空;`trigger=条件` 行的一句话条件 + 可选锚 `@YYYY-MM-DD` ∥ `@事件件名`)——值非空:空串 / 全空白 ⇒ 拒" },
|
|
128
|
+
},
|
|
129
|
+
additionalProperties: false,
|
|
99
130
|
},
|
|
100
|
-
additionalProperties: false,
|
|
101
131
|
},
|
|
102
132
|
update: {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
parameters: {
|
|
134
|
+
type: "object",
|
|
135
|
+
properties: {
|
|
136
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
137
|
+
id: { type: "number", description: "条目 id(与 batch 恰携其一)" },
|
|
138
|
+
batch: { type: "string", description: "批档指针(与 id 恰携其一)——批级批量:一次结算该批的行;携 status ⇒ 逐行按迁移表重判,不携 status ⇒ 纯字段补丁(status 零写)" },
|
|
139
|
+
status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "目标状态(六态之一,缺省 = 不变)" },
|
|
140
|
+
title: { type: "string", description: "标题(缺省 = 不变)" },
|
|
141
|
+
board: { type: "string", description: "归属板块(缺省 = 不变)" },
|
|
142
|
+
req_doc: { type: "string", description: "需求档指针(缺省 = 不变)" },
|
|
143
|
+
task_book: { type: "string", description: "任务书指针(缺省 = 不变)" },
|
|
144
|
+
evidence: { type: "string", description: "证据(缺省 = 不变;传入 = 追加——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传)" },
|
|
145
|
+
evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换(旧文零保留);缺省 / false = 追加;旗独传(无新文)⇒ 拒" },
|
|
146
|
+
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "触发(缺省 = 不变)" },
|
|
147
|
+
resume_when: { type: "string", description: "复启条件句(缺省 = 不变;值非空——空串 / 全空白 ⇒ 拒)" },
|
|
148
|
+
executor: { type: "string", description: "执行者 sessionId(可选——接手改写归属用;缺省 = 按状态迁移语义:进入待设计 / 在途自动写本会话;出在途 / 撤回清空)" },
|
|
149
|
+
},
|
|
150
|
+
additionalProperties: false,
|
|
117
151
|
},
|
|
118
|
-
|
|
152
|
+
oneOfRequired: [["id", "batch"]],
|
|
119
153
|
},
|
|
120
154
|
close: {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
155
|
+
parameters: {
|
|
156
|
+
type: "object",
|
|
157
|
+
required: ["status"],
|
|
158
|
+
properties: {
|
|
159
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
160
|
+
id: { type: "number", description: "条目 id(与 batch 恰携其一)" },
|
|
161
|
+
batch: { type: "string", description: "批档指针(与 id 恰携其一)——批级批量结算:只销已到 待核销 的行;待讨论 / 待设计 点名跳过(追认核销不入批量面)" },
|
|
162
|
+
status: { type: "string", enum: ["已核销", "已废弃"], description: "目标态(勾销 / 追认核销 / 撤回)——携 batch 时可不给(缺省 = 勾销单值),给了只能是 已核销" },
|
|
163
|
+
evidence: { type: "string", description: "最小证据行(可选;传入 = 追加缺省——旧值非空 ⇒ 拼 `旧|新`(「|」零空格)、旧空直写;新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传。追认核销面判结果值须非空:缺 / 全空白 ⇒ 拒)" },
|
|
164
|
+
evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换(旧文零保留);缺省 / false = 追加;旗独传(无新文)⇒ 拒" },
|
|
165
|
+
},
|
|
166
|
+
additionalProperties: false,
|
|
129
167
|
},
|
|
130
|
-
|
|
168
|
+
oneOfRequired: [["id", "batch"]],
|
|
169
|
+
whenPresent: [{ when: "batch", field: "status", required: false, enum: ["已核销"] }],
|
|
131
170
|
},
|
|
132
171
|
query: {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
172
|
+
parameters: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {
|
|
175
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
176
|
+
status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "按状态过滤(六态之一,缺省 = 不过滤)" },
|
|
177
|
+
kind: { type: "string", enum: ["requirement", "tech_todo"], description: "按类别过滤" },
|
|
178
|
+
board: { type: "string", description: "按归属板块过滤(需求档文档名)" },
|
|
179
|
+
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "按技术待办触发过滤(三枚举,缺省 = 不过滤——无触发(NULL)不过滤)" },
|
|
180
|
+
task_book: { type: "string", description: "按任务书指针过滤(原值等值——批→行反查;缺省 = 不过滤)" },
|
|
181
|
+
},
|
|
182
|
+
additionalProperties: false,
|
|
140
183
|
},
|
|
141
|
-
additionalProperties: false,
|
|
142
184
|
},
|
|
143
185
|
count: {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
186
|
+
parameters: {
|
|
187
|
+
type: "object",
|
|
188
|
+
properties: {
|
|
189
|
+
cwd: { type: "string", description: CWD_DESC },
|
|
190
|
+
task_book: { type: "string", description: "按任务书指针过滤(原值等值)" },
|
|
191
|
+
},
|
|
192
|
+
additionalProperties: false,
|
|
193
|
+
},
|
|
147
194
|
},
|
|
148
195
|
}
|
|
149
196
|
|
|
@@ -162,8 +209,10 @@ const UNION_PARAMETERS = {
|
|
|
162
209
|
evidence: { type: "string", description: "最小证据行(add 可空;update / close 传入 = 追加缺省——旧空直写、旧非空拼 `旧|新`(「|」零空格)、新值全空白 ⇒ 拒;覆盖 = 与 `evidenceReplace: true` 同传;update 缺省 = 不变 / close 缺省 = 判行值;追认核销须非空)" },
|
|
163
210
|
evidenceReplace: { type: "boolean", description: "覆盖旗(布尔):与 evidence 同传 = 整段替换;缺省 / false = 追加;旗独传(无新文)⇒ 拒(update / close)" },
|
|
164
211
|
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "技术待办触发(add / update 可空 / query 过滤)" },
|
|
165
|
-
|
|
166
|
-
|
|
212
|
+
resume_when: { type: "string", description: "复启条件句(add / update 可空;值非空——空串 / 全空白 ⇒ 拒;`trigger=条件` 行的一句话条件 + 可选锚 `@YYYY-MM-DD` ∥ `@事件件名`)" },
|
|
213
|
+
id: { type: "number", description: "条目 id(update / close:与 batch 恰携其一)" },
|
|
214
|
+
batch: { type: "string", description: "批档指针(update / close:与 id 恰携其一)——批级批量:一次结算该批的行(update 携 status ⇒ 逐行迁移表重判;不携 status ⇒ 纯字段补丁;close 只销 待核销 行、status 可空)" },
|
|
215
|
+
status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "状态(单行 close 必填 ∈ {已核销, 已废弃};close 携 batch 时可空、给了只能是 已核销;update 迁移 / query 过滤——六态之一)" },
|
|
167
216
|
executor: { type: "string", description: "执行者 sessionId(update 可选——接手改写归属;缺省 = 按状态迁移语义:进入待设计 / 在途自动写本会话;出在途 / 撤回清空)" },
|
|
168
217
|
},
|
|
169
218
|
additionalProperties: false,
|
|
@@ -201,31 +250,43 @@ function makeLedgerTool(actions, { readonly = false, writeFace = false } = {}) {
|
|
|
201
250
|
// ② 入口级 action 判(P3 缺失 / P5 非法——消费 action;取值域 = 本装配面枚举)
|
|
202
251
|
const { action, ...rest0 } = args
|
|
203
252
|
assertToolArgs(entryTool, { action })
|
|
204
|
-
// ③ 余键喂该 action 伪工具对象(声明源 = 前身工具 parameters 逐字——`action` 键不入 P2
|
|
205
|
-
|
|
253
|
+
// ③ 余键喂该 action 伪工具对象(声明源 = 前身工具 parameters 逐字——`action` 键不入 P2 域;
|
|
254
|
+
// 组判据两键在条目顶层,与 `parameters` 同送守卫)
|
|
255
|
+
const spec = ACTION_SPECS[action]
|
|
256
|
+
const rest = assertToolArgs({ name: `ledger(${action})`, ...spec }, rest0)
|
|
206
257
|
// ④ 路由既有核函数(包装细节携自原工具 execute 逐字保留:cwd 缺省 / JSON 缩进 / getSessionId 动态 import)
|
|
207
258
|
switch (action) {
|
|
208
259
|
case "add": {
|
|
209
260
|
const { cwd, ...row } = rest
|
|
210
|
-
const
|
|
211
|
-
|
|
261
|
+
const target = cwd ?? cwdOf(ctx)
|
|
262
|
+
const id = ledgerAdd({ cwd: target, row })
|
|
263
|
+
return JSON.stringify({ id, status: "待讨论", ...reportedRoot(target) })
|
|
212
264
|
}
|
|
213
265
|
case "update": {
|
|
214
|
-
const { cwd, id, ...patch } = rest
|
|
266
|
+
const { cwd, id, batch, ...patch } = rest
|
|
267
|
+
const target = cwd ?? cwdOf(ctx)
|
|
215
268
|
let executorSessionId
|
|
216
269
|
if (patch.executor == null) {
|
|
217
270
|
// 显式 `null` ≡ 略去(§3.1 `null` 口径 · #474——与缺省同分支);动态 import——零新静态边(K-LX1)
|
|
218
271
|
const { getSessionId } = await import("./session-slots.mjs")
|
|
219
272
|
executorSessionId = getSessionId()
|
|
220
273
|
}
|
|
221
|
-
|
|
274
|
+
// 批级批量(§3.3)——`batch` 与 `id` 恰携其一(守卫 P7 已先裁)
|
|
275
|
+
if (batch != null) return JSON.stringify({ ...ledgerUpdateBatch({ cwd: target, batch, patch, executorSessionId }), ...reportedRoot(target) })
|
|
276
|
+
return JSON.stringify({ ...ledgerUpdate({ cwd: target, id, patch, executorSessionId }), ...reportedRoot(target) })
|
|
277
|
+
}
|
|
278
|
+
case "close": {
|
|
279
|
+
const target = rest.cwd ?? cwdOf(ctx)
|
|
280
|
+
// 批级批量结算(§3.3——勾销径)
|
|
281
|
+
if (rest.batch != null) return JSON.stringify({ ...ledgerCloseBatch({ cwd: target, batch: rest.batch, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace }), ...reportedRoot(target) })
|
|
282
|
+
return JSON.stringify({ ...ledgerClose({ cwd: target, id: rest.id, status: rest.status, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace }), ...reportedRoot(target) })
|
|
222
283
|
}
|
|
223
|
-
case "close":
|
|
224
|
-
return JSON.stringify(ledgerClose({ cwd: rest.cwd ?? cwdOf(ctx), id: rest.id, status: rest.status, evidence: rest.evidence, evidenceReplace: rest.evidenceReplace }))
|
|
225
284
|
case "query":
|
|
226
|
-
return JSON.stringify(ledgerQuery({ cwd: rest.cwd ?? cwdOf(ctx), status: rest.status, kind: rest.kind, board: rest.board, trigger: rest.trigger }), null, 2)
|
|
227
|
-
case "count":
|
|
228
|
-
|
|
285
|
+
return JSON.stringify(ledgerQuery({ cwd: rest.cwd ?? cwdOf(ctx), status: rest.status, kind: rest.kind, board: rest.board, trigger: rest.trigger, task_book: rest.task_book }), null, 2)
|
|
286
|
+
case "count": {
|
|
287
|
+
const target = rest.cwd ?? cwdOf(ctx)
|
|
288
|
+
return JSON.stringify({ ...ledgerCount({ cwd: target, task_book: rest.task_book }), ...reportedRoot(target) })
|
|
289
|
+
}
|
|
229
290
|
default:
|
|
230
291
|
// 不可达(② 枚举守门已先拒)——fail-loud 兜底(防未来变体增量 drift)
|
|
231
292
|
throw new Error(`ledger:action 非法:${argPreview(action)}(取值 ∈ ${enumText(actions)})`)
|
|
@@ -247,7 +308,7 @@ const deprecatedLine = (old, action) => `${old} 已退役(本名不再装配
|
|
|
247
308
|
const deprecatedShell = (old, action, readonly) => ({
|
|
248
309
|
name: old,
|
|
249
310
|
description: deprecatedLine(old, action),
|
|
250
|
-
parameters: ACTION_SPECS[action],
|
|
311
|
+
parameters: ACTION_SPECS[action].parameters,
|
|
251
312
|
readonly,
|
|
252
313
|
async execute() { throw new Error(deprecatedLine(old, action)) },
|
|
253
314
|
})
|
package/ledger.mjs
CHANGED
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
*
|
|
16
16
|
* W8 契约②:ledger-db.mjs 静态 import node:sqlite ⇒ 消费侧一律**动态 import** 本档(禁止静态 import)。
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { projectRootView } from "./manifest.mjs"
|
|
21
|
-
import { ledgerDbPath, PENDING_STATUSES, AGING_DAYS } from "./ledger-db.mjs"
|
|
18
|
+
import { basename, dirname, resolve } from "node:path"
|
|
19
|
+
import { AGING_DAYS, PENDING_STATUSES, ledgerChildren, ledgerDbPath, resolveLedgerRoot } from "./ledger-db.mjs"
|
|
22
20
|
import { ledgerQuery } from "./ledger-cmd.mjs"
|
|
23
21
|
import { executorTail } from "./ledger-executors.mjs"
|
|
24
22
|
|
|
@@ -45,55 +43,20 @@ export function entryTitle(text) {
|
|
|
45
43
|
return norm.length > 20 ? norm.slice(0, 20) + "…" : norm
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
* 标记 = 用户数据目录里以该项目路径为键的台账库——项目目录内不落任何标记文件)。 */
|
|
46
|
+
/** 向上(含自身)最近的**已注册**项目根 → {root, ledger} / null(F4——CLI 锚 = cwd)。
|
|
47
|
+
* 薄委托 `resolveLedgerRoot(anchor, { descend: false })`(设计档 §3.8——**取祖先支**):`root` = 推导所
|
|
48
|
+
* 得项目根(非锚);容器 ∥ 工作区锚 ⇒ `null` ⇒ 族发现按容器形(族求和——§7.2 / AC-M2-18 / AC-M2-19)。 */
|
|
52
49
|
export function findProject(anchor) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const ledger = ledgerDbPath(dir)
|
|
56
|
-
if (isFile(ledger)) return { root: dir, ledger }
|
|
57
|
-
const parent = dirname(dir)
|
|
58
|
-
if (parent === dir) return null
|
|
59
|
-
dir = parent
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** 同级枚举上限(N2 成本有界:父目录超此规模 = 缓存 / 临时等非仓族形态 → 退化空集,只显当前项目)。 */
|
|
64
|
-
export const MAX_SIBLING_SCAN = 100
|
|
65
|
-
|
|
66
|
-
/** 目录下已注册台账的直接子目录(目录名升序;不可读 → [];**惰性枚举 + 上限**——大目录零全量扫描)。
|
|
67
|
-
* 导出面 = 族发现(本档)∥ 轻通道闸候选枚举(`agent/light-round.mjs`——同级判据单源)。 */
|
|
68
|
-
export function ledgerChildren(dir) {
|
|
69
|
-
const out = []
|
|
70
|
-
let handle
|
|
71
|
-
try { handle = opendirSync(dir) } catch { return out }
|
|
72
|
-
try {
|
|
73
|
-
let n = 0
|
|
74
|
-
for (;;) {
|
|
75
|
-
const ent = handle.readSync()
|
|
76
|
-
if (!ent) break
|
|
77
|
-
if (++n > MAX_SIBLING_SCAN) return []
|
|
78
|
-
if (!ent.isDirectory()) continue
|
|
79
|
-
const root = join(dir, ent.name)
|
|
80
|
-
const ledger = ledgerDbPath(root)
|
|
81
|
-
if (isFile(ledger)) out.push({ root, ledger })
|
|
82
|
-
}
|
|
83
|
-
} catch { /* 读中断 → 已得子集(尽力而为) */ }
|
|
84
|
-
finally { try { handle.closeSync() } catch { /* 已关 */ } }
|
|
85
|
-
return out.sort((a, b) => (a.root < b.root ? -1 : a.root > b.root ? 1 : 0))
|
|
50
|
+
const derived = resolveLedgerRoot(anchor, { descend: false })
|
|
51
|
+
return derived ? { root: derived.root, ledger: ledgerDbPath(derived.root) } : null
|
|
86
52
|
}
|
|
87
53
|
|
|
88
|
-
/** 项目族发现:{current, projects}——
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* 按容器形落子目录族(需求:打开容器根 ⇒ 族内合计——标记范围批 §1 原话)。 */
|
|
54
|
+
/** 项目族发现:{current, projects}——current = `findProject`(**祖先支**:容器 ∥ 工作区锚 ⇒ `null` ⇒ 按
|
|
55
|
+
* 容器形落子目录族);projects = current + 其**同级已注册**目录(**发现序:current 在前**,其余按目录
|
|
56
|
+
* 名升序);current 缺(容器目录,K6)→ **锚本地**:取锚自身**已注册**的直接子目录(不沿祖先链上找)。
|
|
57
|
+
* 「工作区根不充当项目」的判据单源 = `resolveLedgerRoot` 的库臂排除(§2.1)——本函数零自持判据。 */
|
|
93
58
|
export function discoverFamily(anchor) {
|
|
94
|
-
const
|
|
95
|
-
// 歧义根跳过(键控库存在 ≠ 可作项目——扫描面 `buildScan` 对歧义锚必拒;遮蔽则标记静默缺席)。
|
|
96
|
-
const current = hit && projectRootView(hit.root).state === "ambiguous" ? null : hit
|
|
59
|
+
const current = findProject(anchor)
|
|
97
60
|
if (current) {
|
|
98
61
|
const siblings = ledgerChildren(dirname(current.root)).filter((p) => p.root !== current.root)
|
|
99
62
|
return { current, projects: [current, ...siblings] }
|
|
@@ -102,13 +65,21 @@ export function discoverFamily(anchor) {
|
|
|
102
65
|
}
|
|
103
66
|
|
|
104
67
|
/** scan 组装(SQLite 行集 + 计数单源 → scan 对象)——形状契约(设计档 §2.2):pool/tech/aged/
|
|
105
|
-
* agedKeys/agedTitles/thresholdReached/boards/actionable/root/name/ledger。行龄 = 时间戳
|
|
68
|
+
* agedKeys/agedTitles/overdue/thresholdReached/boards/actionable/root/name/ledger。行龄 = 时间戳
|
|
106
69
|
* (updated_at ?? created_at 距今 > days;未知不计——零假阳降级,同 v1 行龄未知口径)。
|
|
107
70
|
* `now`/`days` = 注入面(确定性用例——替代 v1 ageOf git 注入面)。
|
|
71
|
+
* **`days` 只作用于展示面 `aged`**(本函数局部阈值);`overdue`(出口态)恒按核常量 `AGING_DAYS`
|
|
72
|
+
* (`overdueOf` 单源)——故 `days` 调小时 `aged` 会先于 `overdue` 涨(两数口径不同,非矛盾)。
|
|
73
|
+
* **根推导**(设计档 §3.8):`root` / `name` / `ledger` = `resolveLedgerRoot(anchor)` 推导所得项目根(非锚
|
|
74
|
+
* ——容器 ∥ 工作区锚取收敛序之根)——与行集读取同一单源;无根 ⇒ `ledger` = `null`(不落兜底根——
|
|
75
|
+
* 该态下 `root` / `name` 回落锚、行集为空,且该 scan 不进族枚举(`discoverFamily` 容器支只列已注册子目录))。
|
|
108
76
|
* `inflightExecutors`(F-LX1 · sync 零变——K-LX2):在途且 executor 非空行集原样携带
|
|
109
77
|
* {executor, updated_at, created_at}——判活解析由 `resolveExecutorStates` async 单源负责,本函数零探测。 */
|
|
110
78
|
export function buildScan({ cwd, days = AGING_DAYS, now = Date.now() } = {}) {
|
|
111
|
-
const
|
|
79
|
+
const anchor = resolve(cwd)
|
|
80
|
+
const derived = resolveLedgerRoot(anchor)
|
|
81
|
+
const root = derived ? derived.root : anchor
|
|
82
|
+
const rows = ledgerQuery({ cwd: anchor, now }) // `now` 透传(两读数同时钟——`overdue` 的行键由核同一 `now` 算)
|
|
112
83
|
const pending = rows.filter((r) => PENDING_STATUSES.includes(r.status))
|
|
113
84
|
const poolEntries = pending.filter((r) => r.kind === "requirement")
|
|
114
85
|
const techEntries = pending.filter((r) => r.kind === "tech_todo")
|
|
@@ -123,13 +94,13 @@ export function buildScan({ cwd, days = AGING_DAYS, now = Date.now() } = {}) {
|
|
|
123
94
|
if (key) boards.set(key, (boards.get(key) ?? 0) + 1)
|
|
124
95
|
}
|
|
125
96
|
const thresholdReached = poolEntries.length >= THRESHOLD_POOL || [...boards.values()].some((n) => n >= THRESHOLD_BOARD)
|
|
126
|
-
const root = resolve(cwd)
|
|
127
97
|
const inflightExecutors = pending
|
|
128
98
|
.filter((r) => r.status === "在途" && r.executor)
|
|
129
99
|
.map((r) => ({ executor: r.executor, updated_at: r.updated_at ?? null, created_at: r.created_at ?? null }))
|
|
130
100
|
return {
|
|
131
|
-
name: basename(root), root, ledger: ledgerDbPath(root),
|
|
101
|
+
name: basename(root), root, ledger: derived ? ledgerDbPath(root) : null,
|
|
132
102
|
pool: poolEntries.length, tech: techEntries.length, aged: aged.length,
|
|
103
|
+
overdue: pending.filter((r) => r.overdue != null).length, // 待处置(§3.7——机判单源 = 行集 `overdue` 键,行值与 `now` 均同源)
|
|
133
104
|
agedKeys: aged.map((r) => normalizeEntry(r.title)), agedTitles: aged.map((r) => entryTitle(r.title)),
|
|
134
105
|
thresholdReached, boards, actionable: aged.length > 0 || thresholdReached,
|
|
135
106
|
inflightExecutors,
|
|
@@ -145,8 +116,8 @@ export function formatMarker(scan) {
|
|
|
145
116
|
* `root` = `family.current.root` 的 scan(命中但不在 `scans`——不可读 ⇒ 空范围,不掺兄弟);
|
|
146
117
|
* `current` 缺席(容器根锚)⇒ 范围 = `scans` 全体(族内已读项目——构建期不可读已跳过)。
|
|
147
118
|
* 空范围 ⇒ `{ marker: null, warn: false }`(不落 `0·0`);范围非空 ⇒ `marker` = `formatMarker`
|
|
148
|
-
* 逐字(两池分列求和——F1);`warn` = 范围内任一项 `
|
|
149
|
-
*
|
|
119
|
+
* 逐字(两池分列求和——F1);`warn` = 范围内任一项 `overdue>0 ∨ deadExecutors>0`(§3.7——`aged ⊂
|
|
120
|
+
* overdue` ⇒ 警示色不缩;端零重算)。消费面 = 核拍面(`ledger-surface.mjs`)∥ VSC item。 */
|
|
150
121
|
export function scopeMarkerOf(scans, family) {
|
|
151
122
|
const list = Array.isArray(scans) ? scans : []
|
|
152
123
|
const currentRoot = family?.current?.root ?? null
|
|
@@ -158,15 +129,15 @@ export function scopeMarkerOf(scans, family) {
|
|
|
158
129
|
for (const s of range) {
|
|
159
130
|
pool += s.pool
|
|
160
131
|
tech += s.tech
|
|
161
|
-
if (s.
|
|
132
|
+
if (s.overdue > 0 || s.deadExecutors > 0) warn = true
|
|
162
133
|
}
|
|
163
134
|
return { marker: formatMarker({ pool, tech }), warn }
|
|
164
135
|
}
|
|
165
136
|
|
|
166
|
-
/** L2 明细行(逐字——`(老化 <n
|
|
167
|
-
*
|
|
137
|
+
/** L2 明细行(逐字——`(老化 <n> · 待处置 <m>)` 两数恒显(含 0——§3.7 出口面恒显位扩);
|
|
138
|
+
* 阈值达成加 ` — 可开批`;判活尾段 = executorTail 三态——在途 executor 缺席时尾段空串)。 */
|
|
168
139
|
export function formatDetailLine(scan) {
|
|
169
|
-
return `台账 ${scan.name}:需求池 ${scan.pool} · 技术待办 ${scan.tech}(老化 ${scan.aged})${scan.thresholdReached ? " — 可开批" : ""}${executorTail(scan)}`
|
|
140
|
+
return `台账 ${scan.name}:需求池 ${scan.pool} · 技术待办 ${scan.tech}(老化 ${scan.aged} · 待处置 ${scan.overdue})${scan.thresholdReached ? " — 可开批" : ""}${executorTail(scan)}`
|
|
170
141
|
}
|
|
171
142
|
|
|
172
143
|
/** 明细行集 = {current} ∪ {可动作项目}(发现序——current 在前,其余按目录名升序;同项去重)。 */
|
|
@@ -178,9 +149,10 @@ export function detailScans(scans, current) {
|
|
|
178
149
|
}
|
|
179
150
|
|
|
180
151
|
// ── re-export(拆分件接口——命令面接线 = 动态 import 本档,KD-M2-3;`AGING_DAYS` 常量单源 = `ledger-db.mjs`
|
|
181
|
-
// ——2026-10-07 批 ledger-tool
|
|
182
|
-
|
|
183
|
-
export {
|
|
152
|
+
// ——2026-10-07 批 ledger-tool 自本档迁入,公共面零变;`ledgerChildren` / `MAX_SIBLING_SCAN` / `resolveLedgerRoot`
|
|
153
|
+
// ——2026-10-16 批 ledger-root-derivation 随迁 / 新增,公共面形零改) ──
|
|
154
|
+
export { AGING_DAYS, ALLOWED_MIGRATIONS, EVIDENCE_SOFT_LIMIT, MAX_SIBLING_SCAN, ledgerChildren, ledgerDbPath, ledgerKey, ledgerDirPath, openLedger, PENDING_STATUSES, resolveLedgerRoot, _setLedgerDirForTest, _resetLedgerDirForTest, ensureColumn, ensureExecutorColumn } from "./ledger-db.mjs"
|
|
155
|
+
export { ledgerAdd, ledgerBatchRows, ledgerClose, ledgerCloseBatch, ledgerCount, ledgerEvidenceOversize, ledgerQuery, ledgerReanchorBatch, ledgerUpdate, ledgerUpdateBatch, overdueOf } from "./ledger-cmd.mjs"
|
|
184
156
|
export { ledgerTool, ledgerReadTool, ledgerAddTool, ledgerCloseTool, ledgerCountTool, ledgerQueryTool, ledgerUpdateTool } from "./ledger-tools.mjs"
|
|
185
157
|
export { runLedgerAudit, runLedgerMigrate } from "./ledger-migrate.mjs"
|
|
186
158
|
export { resolveExecutorStates, executorTail, _setExecutorProbeTtlForTest } from "./ledger-executors.mjs"
|
package/log.mjs
CHANGED
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* ① 字段名精确匹配黑名单(apiKey/designToken/password/secret/token,大小写不敏感;
|
|
17
17
|
* 另加防御性名称 authorization/proxy/proxyUri——防凭据型 URL/头字段泄漏——§2.5)
|
|
18
18
|
* → 整个字段丢弃;
|
|
19
|
-
* ② 内容只扫密钥形态(sk-xxx / Bearer xxx / key=…
|
|
19
|
+
* ② 内容只扫密钥形态(sk-xxx / Bearer xxx / key=… / token 值形等)→ 截断到形态之前
|
|
20
20
|
* ——宁可丢信息不漏密钥。
|
|
21
|
+
* token 值形(`uuid:epoch`——2026-10-14 批并入):工具结果经 `tool:done` head 落盘
|
|
22
|
+
* 时在形态前截断、不落值(与 batch.mjs 凭据第三形同判据)。
|
|
21
23
|
* 工具事件不记 args;URL 不入事件(llm/tool 事件从不携带 URL;err 文本经形态扫描)。
|
|
22
24
|
* - 摘要截断(B 方案,2026-09-03 用户裁定):截断处带 "…" 标记(截后仍 ≤上限)。
|
|
23
25
|
* - 测试隔离:node --test 进程(NODE_TEST_CONTEXT)默认不写盘——防测试事件污染真实
|
|
@@ -37,8 +39,8 @@ import { join } from "node:path"
|
|
|
37
39
|
export const MAX_LINE = 512
|
|
38
40
|
/** 字段名精确黑名单(NF-L3/§2.4——2026-09-03 评审 refinement #7:与 §2.4 词表一致含 token) */
|
|
39
41
|
const BLACKLIST_FIELDS = new Set(["apikey", "designtoken", "password", "secret", "token", "authorization", "proxyuri", "proxy"])
|
|
40
|
-
/**
|
|
41
|
-
const SECRET_FORM = /(\bsk-[A-Za-z0-9_-]{6,}|\bBearer\s+[A-Za-z0-9._~+/=-]{6,}|\b(?:api[_-]?key|key|token|secret|password|pwd|passwd)\s*[=:]\s*['"]?[A-Za-z0-9._~+/=-]{6,})/i
|
|
42
|
+
/** 内容密钥形态扫描(大小写不敏感)——命中即截断到形态前(含 token 值形 `uuid:epoch`——同 batch.mjs 凭据第三形判据) */
|
|
43
|
+
const SECRET_FORM = /(\bsk-[A-Za-z0-9_-]{6,}|\bBearer\s+[A-Za-z0-9._~+/=-]{6,}|\b(?:api[_-]?key|key|token|secret|password|pwd|passwd)\s*[=:]\s*['"]?[A-Za-z0-9._~+/=-]{6,}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:\d{10,16})/i
|
|
42
44
|
/** 字符串字段长度兜底:head ≤300 / err ≤200 / 其余 ≤120 */
|
|
43
45
|
const FIELD_CAPS = { head: 300, err: 200 }
|
|
44
46
|
|
|
@@ -154,6 +156,18 @@ export function redactSecret(fieldKey, value) {
|
|
|
154
156
|
return hit ? s.slice(0, hit.index) + "[redacted]" : s
|
|
155
157
|
}
|
|
156
158
|
|
|
159
|
+
/** 结构面遮蔽(#1432——tool-errors args 落痕前过同一链):键级 ∥ 内容级复用 `redactSecret`;嵌套对象 ∥ 数组递归;
|
|
160
|
+
* 非串值仅在键名命中黑名单时整键遮蔽(否则原样 ∥ 递归)——「宁可丢信息不漏密钥」同判据。 */
|
|
161
|
+
export function redactArgs(value) {
|
|
162
|
+
if (Array.isArray(value)) return value.map(redactArgs)
|
|
163
|
+
if (value && typeof value === "object") {
|
|
164
|
+
const out = {}
|
|
165
|
+
for (const [k, v] of Object.entries(value)) out[k] = typeof v === "string" || BLACKLIST_FIELDS.has(String(k).toLowerCase()) ? redactSecret(k, v) : redactArgs(v)
|
|
166
|
+
return out
|
|
167
|
+
}
|
|
168
|
+
return typeof value === "string" ? redactSecret("", value) : value
|
|
169
|
+
}
|
|
170
|
+
|
|
157
171
|
/** 内容级净化:密钥形态截断到形态前(②);超长截断 + "…" 标记。 */
|
|
158
172
|
export function sanitizeString(key, value) {
|
|
159
173
|
let s = String(value)
|