@thincoder/core 0.9.5 → 0.10.2
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 +49 -0
- package/README.md +1 -1
- package/abort-provenance.mjs +10 -10
- package/advisor/citations.mjs +5 -5
- package/advisor/compaction.mjs +16 -15
- package/advisor/loop.mjs +12 -12
- package/advisor/messages.mjs +6 -6
- package/advisor/project-context.mjs +5 -5
- package/advisor/repos.mjs +3 -3
- package/advisor/run.mjs +22 -12
- package/advisor.mjs +4 -4
- package/agent/assemble.mjs +111 -0
- package/agent/chat-call.mjs +68 -0
- package/agent/child-marks.mjs +2 -2
- package/agent/completion.mjs +9 -2
- package/agent/continue-decision.mjs +25 -0
- package/agent/dispatch-gates.mjs +140 -0
- package/agent/dispatch-run.mjs +167 -0
- package/agent/dispatch.mjs +54 -296
- package/agent/family-tools.mjs +5 -5
- package/agent/helpers.mjs +53 -3
- package/agent/live-beat.mjs +49 -0
- package/agent/post-turn.mjs +4 -8
- package/agent/record-results.mjs +23 -20
- package/agent/relay-prefix.mjs +2 -2
- package/agent/run-stages.mjs +15 -11
- package/agent/run-start.mjs +111 -0
- package/agent/setup-reminders.mjs +9 -9
- package/agent/setup.mjs +32 -4
- package/agent/spawn-child.mjs +9 -9
- package/agent/suspension.mjs +89 -29
- package/agent/timers.mjs +113 -0
- package/agent/turn-loop.mjs +244 -0
- package/agent/write-gate.mjs +103 -6
- package/agent-tools/advisor-async.mjs +9 -9
- package/agent-tools/advisor-settle.mjs +12 -12
- package/agent-tools/advisor.mjs +36 -41
- package/agent-tools/async-discard.mjs +44 -1
- package/agent-tools/async-settle.mjs +21 -18
- package/agent-tools/audit-block.mjs +7 -7
- package/agent-tools/bash-async.mjs +281 -0
- package/agent-tools/batch-lifecycle.mjs +57 -18
- package/agent-tools/batch-paths.mjs +52 -8
- package/agent-tools/batch-skeleton.mjs +19 -1
- package/agent-tools/batch.mjs +12 -65
- package/agent-tools/checkpoint.mjs +121 -0
- package/agent-tools/child-permission.mjs +5 -3
- package/agent-tools/consult.mjs +56 -56
- package/agent-tools/context.mjs +4 -20
- package/agent-tools/design-token.mjs +3 -3
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/eng.mjs +5 -5
- package/agent-tools/escalate-async.mjs +16 -11
- package/agent-tools/goal.mjs +3 -10
- package/agent-tools/panel-blocks.mjs +2 -2
- package/agent-tools/parent-channel.mjs +3 -13
- package/agent-tools/plan.mjs +3 -4
- package/agent-tools/read-history.mjs +12 -28
- package/agent-tools/recent-changes.mjs +4 -5
- package/agent-tools/review-facts.mjs +1 -1
- package/agent-tools/settings.mjs +47 -20
- package/agent-tools/skill.mjs +3 -5
- package/agent-tools/spawn-gates.mjs +3 -3
- package/agent-tools/subagent-actions-query.mjs +270 -0
- package/agent-tools/subagent-actions.mjs +21 -265
- package/agent-tools/subagent-async.mjs +25 -20
- package/agent-tools/subagent-panel.mjs +33 -4
- package/agent-tools/subagent-run.mjs +36 -21
- package/agent-tools/subagent-scheduler.mjs +28 -27
- package/agent-tools/subagent-spawn.mjs +45 -14
- package/agent-tools/subagent.mjs +33 -53
- package/agent-tools/task.mjs +3 -9
- package/agent-tools/timer.mjs +12 -12
- package/agent-tools/verify.mjs +14 -13
- package/agent-tools.mjs +0 -1
- package/agent.mjs +18 -355
- package/attachments.mjs +125 -0
- package/auto-think.mjs +1 -1
- package/config-presets.mjs +11 -6
- package/config-watch.mjs +83 -0
- package/config.mjs +29 -12
- package/context-degrade.mjs +81 -0
- package/context-echo.mjs +88 -0
- package/context-push.mjs +51 -0
- package/context.mjs +8 -187
- package/conventions.mjs +110 -161
- package/declaration.mjs +194 -0
- package/embedding.mjs +3 -1
- package/env-ref.mjs +81 -0
- package/explore-distill.mjs +2 -3
- package/file-links.mjs +53 -0
- package/file-refs.mjs +103 -0
- package/generate-title.mjs +28 -4
- package/history-window.mjs +17 -2
- package/i18n.mjs +2 -0
- package/index-discover.mjs +7 -7
- package/ledger-cmd.mjs +34 -126
- package/ledger-db.mjs +10 -2
- package/ledger-tools.mjs +188 -0
- package/ledger.mjs +9 -4
- package/log.mjs +12 -7
- package/manifest-discovery.mjs +131 -0
- package/manifest-schema.mjs +166 -0
- package/manifest.mjs +18 -217
- package/mcp.mjs +4 -1
- package/memory/code-search.mjs +149 -0
- package/memory/code-sync.mjs +89 -230
- package/memory/core.mjs +47 -32
- package/memory/docs.mjs +63 -262
- package/memory/file-list.mjs +89 -0
- package/memory/file-walk.mjs +7 -4
- package/memory/memory-tool.mjs +229 -0
- package/memory/schema.mjs +18 -3
- package/memory/sweep.mjs +289 -0
- package/memory/sync-tail.mjs +81 -0
- package/memory-status.mjs +69 -0
- package/memory.mjs +3 -0
- package/model-ref.mjs +96 -3
- package/model-specs.mjs +16 -7
- package/notify-policy.mjs +51 -0
- package/package.json +1 -1
- package/peer-claims.mjs +2 -2
- package/peer-domains.mjs +7 -11
- package/peer-instances.mjs +9 -10
- package/process-probe-exec.mjs +187 -0
- package/process-probe.mjs +27 -175
- package/prompt-overlays.mjs +11 -10
- package/prompts/advisor-design.md +2 -2
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +16 -8
- package/prompts/discipline-engineering.md +57 -16
- package/prompts/discipline-normal.md +6 -3
- package/prompts/persona-coder.md +0 -1
- package/prompts/persona-eng-coder.md +1 -5
- package/prompts/persona-eng-designer.md +2 -3
- package/prompts/persona-engineering.md +32 -11
- package/prompts/persona-explore.md +0 -1
- package/prompts/persona-normal.md +1 -1
- package/prompts/persona-plan.md +3 -4
- package/prompts/subagent-base.md +14 -0
- package/provider/core.mjs +11 -48
- package/provider/list-models.mjs +3 -0
- package/provider/responses-request.mjs +237 -0
- package/provider/responses.mjs +6 -228
- package/provider-flows.mjs +248 -0
- package/queued.mjs +90 -0
- package/rules.mjs +209 -2
- package/session-guard.mjs +27 -7
- package/session-lifecycle.mjs +108 -27
- package/session-rename.mjs +5 -1
- package/session-slot-claims.mjs +230 -0
- package/session-slot-scan.mjs +299 -0
- package/session-slot-verify.mjs +304 -0
- package/session-slot-write.mjs +74 -8
- package/session-slots-manifest.mjs +163 -241
- package/session-slots.mjs +99 -71
- package/session.mjs +30 -10
- package/shell-candidates.mjs +78 -0
- package/text-budget.mjs +2 -2
- package/think-off.mjs +49 -0
- package/token-ttl.mjs +15 -4
- package/tool-args.mjs +73 -0
- package/tool-docs/advisor.md +9 -0
- package/tool-docs/batch.md +1 -0
- package/tool-docs/code_search.md +1 -0
- package/tool-docs/consult_start.md +5 -0
- package/tool-docs/consult_stop.md +3 -0
- package/tool-docs/context.md +7 -0
- package/tool-docs/doc_search.md +1 -0
- package/tool-docs/edit.md +2 -13
- package/tool-docs/eng.md +1 -0
- package/tool-docs/execute.md +1 -9
- package/tool-docs/git.md +25 -51
- package/tool-docs/goal.md +6 -0
- package/tool-docs/ledger_add.md +1 -0
- package/tool-docs/ledger_close.md +1 -0
- package/tool-docs/ledger_count.md +1 -0
- package/tool-docs/ledger_query.md +1 -0
- package/tool-docs/ledger_update.md +1 -0
- package/tool-docs/main_history.md +1 -0
- package/tool-docs/memory.md +10 -0
- package/tool-docs/notify_parent.md +9 -0
- package/tool-docs/peer_instances.md +1 -0
- package/tool-docs/plan.md +5 -0
- package/tool-docs/process.md +7 -3
- package/tool-docs/read_history.md +7 -0
- package/tool-docs/recent_changes.md +1 -0
- package/tool-docs/repo_outline.md +1 -0
- package/tool-docs/settings.md +6 -0
- package/tool-docs/skill.md +5 -0
- package/tool-docs/subagent.md +31 -0
- package/tool-docs/task.md +7 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/verify.md +1 -0
- package/tool-docs/wait_for.md +2 -1
- package/tools/bash.mjs +28 -4
- package/tools/edit-batch.mjs +4 -4
- package/tools/edit-diff.mjs +4 -23
- package/tools/exec-run.mjs +102 -4
- package/tools/execute.mjs +5 -2
- package/tools/file.mjs +4 -9
- package/tools/git-run.mjs +1 -1
- package/tools/git.mjs +0 -24
- package/tools/index.mjs +2 -1
- package/tools/linter.mjs +2 -1
- package/tools/node-child.mjs +21 -0
- package/tools/ops.mjs +50 -13
- package/tools/patch.mjs +4 -2
- package/tools/repomap.mjs +20 -9
- package/tools/shared.mjs +8 -6
- package/tools/web.mjs +5 -2
- package/tools/write-path.mjs +1 -1
- package/traces/trace-store.mjs +17 -14
- package/vision-reader.mjs +76 -0
- package/agent-tools/batch-segment.mjs +0 -18
package/file-links.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* file-links.mjs — 工具结果文本的**验存**文件链接抽取(上提核件 —— 处理流批 R2 · 2026-09-28)。
|
|
3
|
+
* 上提源 = VSC `thincoder-vscode/src/extension/file-links.mjs`(token 抽取 ∕ 去重 ∕ 封顶)× 桌面同档
|
|
4
|
+
* (相对径需项目根——无根 ⇒ 相对 token 零判据,不落 `process.cwd()` 第二解析基);本档 = 单源
|
|
5
|
+
* (消费面 = 桌面 `thincoder-desktop/src/main/file-links.mjs` · VSC `thincoder-vscode/src/extension/file-links.mjs`——已随
|
|
6
|
+
* parity-b4 迁移轮改指本档(2026-09-29,双写窗口收口))。
|
|
7
|
+
* 判据 = **盘上存在闸**(存在 ∧ 是文件):路径 token 只在真实存在时才成链接 —— URL ∕ 日志噪音 ∕
|
|
8
|
+
* 版本号恒不成链接(存在检查 = 最后一道闸)。**验存(`existsSync` ∕ `statSync`)转注入缝**:本档零
|
|
9
|
+
* `node:fs`——盘面探针由调用方注入(`probe = { existsSync, statSync }`);缺 ∕ 形违 ⇒ 抛(fail-loud——
|
|
10
|
+
* 存在闸未接线不得静默扮成「零链接」)。返回 `[{ raw, path, line }]`:`raw` = 含行后缀原文 · `path` =
|
|
11
|
+
* 盘上绝对路径 · `line` = 数 ∕ 缺 ⇒ `null`;零抛面 = fs 判据(探针逐条 try/catch:不可达 = 不成链接)。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { isAbsolute, resolve } from "node:path"
|
|
15
|
+
|
|
16
|
+
/** 路径 token:可选 Windows 盘符 + 路径字符 + 点扩展名 + 可选 `:line` / `:line:col` 后缀;m[1] = 路径本体(供探针)、m[2] = 行号、m[0] = 渲染面包裹面 `raw`。 */
|
|
17
|
+
const PATH_TOKEN_RE = /((?:[A-Za-z]:)?[~./\\\w][\w./\\~-]*\.\w{1,10})(?::(\d+)(?::\d+)?)?/g
|
|
18
|
+
|
|
19
|
+
/** 逐结果封顶(值同源 = VSC 同档 `MAX_LINKS`)。 */
|
|
20
|
+
export const MAX_LINKS = 50
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 工具结果文本 ⇒ 验存文件链接(去重 + 封顶)。`cwd` = 项目根(相对 token 的解析基);缺 ∕ 空 ⇒ 相对 token
|
|
24
|
+
* 一律不检(禁假链接);绝对 token 与 cwd 无关,照检。`probe` = 盘面探针注入缝(`{ existsSync(p), statSync(p) }`——
|
|
25
|
+
* `node:fs` 同名面;桌面端侧注入)。零文本(`""` ∕ 非串)先于探针校验早退(零判据面 —— 探针不检)。
|
|
26
|
+
*/
|
|
27
|
+
export function extractFileLinks(cwd, text, probe) {
|
|
28
|
+
if (typeof text !== "string" || text === "") return []
|
|
29
|
+
if (typeof probe?.existsSync !== "function" || typeof probe?.statSync !== "function") {
|
|
30
|
+
throw new TypeError("extractFileLinks: probe seam missing — inject { existsSync, statSync } (the existence guard must not silently degrade)")
|
|
31
|
+
}
|
|
32
|
+
const base = typeof cwd === "string" && cwd !== "" ? cwd : null
|
|
33
|
+
const links = []
|
|
34
|
+
const seen = new Set()
|
|
35
|
+
for (const m of text.matchAll(PATH_TOKEN_RE)) {
|
|
36
|
+
const pathPart = m[1]
|
|
37
|
+
// 协议相对 URL 残体(`//example.com/a.png`)—— 恒非工作区路径。
|
|
38
|
+
if (pathPart.startsWith("//") || pathPart.startsWith("\\\\")) continue
|
|
39
|
+
const absolute = isAbsolute(pathPart)
|
|
40
|
+
if (!absolute && base === null) continue // 无项目根 ⇒ 相对 token 零判据(禁假链接)
|
|
41
|
+
const line = m[2] ? Number(m[2]) : null
|
|
42
|
+
const abs = absolute ? pathPart : resolve(base, pathPart)
|
|
43
|
+
const key = `${abs}:${line ?? ""}`
|
|
44
|
+
if (seen.has(key)) continue
|
|
45
|
+
try {
|
|
46
|
+
if (!probe.existsSync(abs) || !probe.statSync(abs).isFile()) continue
|
|
47
|
+
} catch { continue }
|
|
48
|
+
seen.add(key)
|
|
49
|
+
links.push({ raw: m[0], path: abs, line })
|
|
50
|
+
if (links.length >= MAX_LINKS) break
|
|
51
|
+
}
|
|
52
|
+
return links
|
|
53
|
+
}
|
package/file-refs.mjs
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* file-refs.mjs — @ 文件引用**注入 ∕ 剥离核件**(缺面族批补 #632 · 2026-09-29 上提)。
|
|
3
|
+
* 上提源 = VSC `thincoder-vscode/src/extension/file-refs.mjs` 自持档(逐字搬——regex ∕ 4000 截断 ∕
|
|
4
|
+
* `[File: …]` 围栏 ∕ 摘要块格式零语义改良);本档 = **单源**;两端各持薄壳(端侧 `node:fs` 探针注入):
|
|
5
|
+
* VSC `thincoder-vscode/src/extension/file-refs.mjs` ∥ 桌面 `thincoder-desktop/src/main/file-refs.mjs`。
|
|
6
|
+
* 消费面:VSC 注入 `panel-chat.mjs:187` ∕ 恢复剥离 `panel-session.mjs:185` ∕ 标题剥离
|
|
7
|
+
* `panel-session-write.mjs:139`;桌面注入缝 = `turn-driver.mjs`(起跑前单点,`turn-face.mjs`)∥
|
|
8
|
+
* 恢复剥离 = `session-slots.mjs` `pageHistory` ∥ 标题剥离 = 核 `generate-title.mjs:132`(读源处)。
|
|
9
|
+
* 文本形逐字冻结(`docs/vsc/design/WEBVIEW.md:154`「落线文本逐字不变」契约):上提不携任何行为面改动。
|
|
10
|
+
* 判据 = **盘面探针注入缝**(`probe = { readFileSync, existsSync, statSync }` —— `node:fs` 同名面;
|
|
11
|
+
* 缺 ∕ 形违 ⇒ 抛 —— fail-loud:盘面闸未接线不得静默扮成「零引用」,沿 `file-links.mjs:29-31` 先例)。
|
|
12
|
+
* `stripAtRefs` = 注入语法的逆变换:fail-closed 判据逐字依赖注入输出格式 —— 语法与其逆变换同住一处,
|
|
13
|
+
* 零第二实现(本档为唯一产者)。
|
|
14
|
+
*/
|
|
15
|
+
import { join } from "node:path"
|
|
16
|
+
|
|
17
|
+
/** 探针三件(`node:fs` 同名面)——缺一即缝违。 */
|
|
18
|
+
const PROBE_KEYS = ["readFileSync", "existsSync", "statSync"]
|
|
19
|
+
|
|
20
|
+
/** 探针校验(fail-loud):三件皆须为函数 —— 缺 ∕ 形违 ⇒ 抛。 */
|
|
21
|
+
function probeOf(probe) {
|
|
22
|
+
if (PROBE_KEYS.some((key) => typeof probe?.[key] !== "function")) {
|
|
23
|
+
throw new TypeError("injectAtRefs: probe seam missing — inject { readFileSync, existsSync, statSync } (the disk probe must not silently degrade)")
|
|
24
|
+
}
|
|
25
|
+
return probe
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Scan text for @path references and replace with inline file content.
|
|
29
|
+
* 零文本(非串 ∥ 空串)先于探针校验早退(零判据面——探针不检,同序沿核 `file-links.mjs:28`)。 */
|
|
30
|
+
export function injectAtRefs(text, cwd, probe) {
|
|
31
|
+
if (typeof text !== "string" || text === "") return text
|
|
32
|
+
const io = probeOf(probe)
|
|
33
|
+
const re = /@([^\s,;:()[\]{}"'`]+)/g
|
|
34
|
+
const refs = []
|
|
35
|
+
let m
|
|
36
|
+
while ((m = re.exec(text)) !== null) {
|
|
37
|
+
const raw = m[1]
|
|
38
|
+
// Skip things that aren't paths (emails, mentions like @user)
|
|
39
|
+
if (!/^[a-zA-Z0-9_./\\-]+$/.test(raw)) continue
|
|
40
|
+
if (raw.includes("@")) continue
|
|
41
|
+
// Resolve relative to cwd
|
|
42
|
+
const abs = join(cwd, raw)
|
|
43
|
+
if (!io.existsSync(abs)) continue
|
|
44
|
+
try {
|
|
45
|
+
const stat = io.statSync(abs)
|
|
46
|
+
if (!stat.isFile()) continue
|
|
47
|
+
let content = io.readFileSync(abs, "utf8")
|
|
48
|
+
if (content.length > 4000) content = content.slice(0, 4000) + "\n... (truncated)"
|
|
49
|
+
refs.push({ raw, abs, content })
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error(`[file-refs] failed to read @${raw}:`, e.message)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (refs.length === 0) return text
|
|
55
|
+
|
|
56
|
+
// Replace @refs in the text with inline content blocks
|
|
57
|
+
for (const ref of refs) {
|
|
58
|
+
const placeholder = `@${ref.raw}`
|
|
59
|
+
const injection = `[File: ${ref.raw}]\n\`\`\`\n${ref.content}\n\`\`\``
|
|
60
|
+
text = text.split(placeholder).join(injection)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Append a summary of referenced files
|
|
64
|
+
const list = refs.map(r => ` - ${r.raw} (${r.content.length} chars)`).join("\n")
|
|
65
|
+
text += `\n\n[Referenced files:\n${list}\n]`
|
|
66
|
+
|
|
67
|
+
return text
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 复原注入前的用户原文(F-W15 端侧显示边界剥离 · D-W17)。
|
|
72
|
+
*
|
|
73
|
+
* 契约:同一会话中用户消息的**恢复面**文本 ≡ 活面气泡文本(= 用户所打原文,`@路径` 保持简洁形)。
|
|
74
|
+
* 与产者同档(本档 = 注入语法的唯一产者——语法与其逆变换同住一处,零第二实现)。
|
|
75
|
+
*
|
|
76
|
+
* 剥离判据(fail-closed):仅当文本尾部为注入**摘要块**(`[Referenced files:` 头 +
|
|
77
|
+
* 逐行 ` - <raw> (<N> chars)` + 尾 `]`)时,按「`[File: <raw>]` 头 + 围栏 + 恰 N 字符正文 +
|
|
78
|
+
* 收尾围栏 ⇒ `@<raw>`」逐条还原并删摘要块;**任一条不吻合 ⇒ 整条原样返回**(用户手打的
|
|
79
|
+
* `[File: x]` / 形近文本零误伤)。落线 / 机读线(模型输入)零触碰——本函数只服务显示边界。
|
|
80
|
+
*/
|
|
81
|
+
export function stripAtRefs(text) {
|
|
82
|
+
const s = typeof text === "string" ? text : ""
|
|
83
|
+
const tail = /(?:^|\n\n)\[Referenced files:\n([\s\S]*)\n\]$/.exec(s)
|
|
84
|
+
if (!tail) return s
|
|
85
|
+
const entries = []
|
|
86
|
+
for (const line of tail[1].split("\n")) {
|
|
87
|
+
const m = /^ {2}- (.*) \((\d+) chars\)$/.exec(line)
|
|
88
|
+
if (!m) return s // 摘要块形近(行不吻合)⇒ 原样返回
|
|
89
|
+
entries.push({ raw: m[1], len: Number(m[2]) })
|
|
90
|
+
}
|
|
91
|
+
if (entries.length === 0) return s
|
|
92
|
+
let head = s.slice(0, tail.index) // 删摘要块(含其前空行)
|
|
93
|
+
for (const { raw, len } of entries) {
|
|
94
|
+
const open = `[File: ${raw}]\n\`\`\`\n`
|
|
95
|
+
const at = head.indexOf(open)
|
|
96
|
+
if (at < 0) return s // 头部缺 / 不对应 ⇒ 原样返回
|
|
97
|
+
const bodyStart = at + open.length
|
|
98
|
+
const bodyEnd = bodyStart + len
|
|
99
|
+
if (head.slice(bodyEnd, bodyEnd + 4) !== "\n\`\`\`") return s // 字符数不符 ⇒ 原样返回
|
|
100
|
+
head = head.slice(0, at) + `@${raw}` + head.slice(bodyEnd + 4)
|
|
101
|
+
}
|
|
102
|
+
return head
|
|
103
|
+
}
|
package/generate-title.mjs
CHANGED
|
@@ -9,9 +9,17 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { proxyFetch } from "./proxy.mjs"
|
|
12
|
+
import { resolveProviderSecrets } from "./env-ref.mjs"
|
|
12
13
|
// D7(2026-09-21 块标题行对齐批——标题链单源):首条真实 user 消息谓词收核单源
|
|
13
14
|
// (`isRealUserMsg`——纯函数零依赖,无环)。
|
|
14
15
|
import { isRealUserMsg } from "./history-window.mjs"
|
|
16
|
+
// #632(缺面族批补 · @ 文件引用对齐 · 2026-09-29):标题源剥离单源——注入形不得进标题请求(核件
|
|
17
|
+
// `file-refs.mjs`;CLI 无注入 ⇒ 零动作;VSC 同位转口 = `panel-session-write.mjs:139`)。
|
|
18
|
+
import { stripAtRefs } from "./file-refs.mjs"
|
|
19
|
+
// #363③(off 形族单源——MODEL-SPECS.md §16.2 / §16.4):禁思考形与「是否可禁」一律引核
|
|
20
|
+
// (`thinkOffShape` / `thinkOffPath`——本档零副本);spec 查表同单源(`specForModel`)。
|
|
21
|
+
import { specForModel } from "./model-specs.mjs"
|
|
22
|
+
import { thinkOffShape, thinkOffPath } from "./think-off.mjs"
|
|
15
23
|
|
|
16
24
|
// Test seam (_-prefix, mirrors run.mjs seams): lets the proxy-branch regression
|
|
17
25
|
// test swap the proxy fetch. The branch it exercises used to carry a dynamic
|
|
@@ -31,7 +39,16 @@ export async function generateTitle(userContent, provider) {
|
|
|
31
39
|
if (typeof userText !== "string" || userText.length < 10) return null
|
|
32
40
|
if (!provider?.apiKey || !provider?.baseURL || !provider?.model) return null
|
|
33
41
|
|
|
42
|
+
// #363③:禁思考形(三格式支共用)——off 取形与可宣称性引核单源:`thinkOffPath` 假
|
|
43
|
+
// (thinkAlwaysOn 族)或 `thinkOffShape` 为 `null`(effort 族——`null` 形是核载荷门的
|
|
44
|
+
// 约定,本档无门)⇒ 不发禁形(假标记:被服务端拒 / 静默失效)。
|
|
45
|
+
const spec = specForModel(provider.model)
|
|
46
|
+
const offShape = thinkOffPath(spec) ? thinkOffShape(spec) : null
|
|
47
|
+
|
|
34
48
|
try {
|
|
49
|
+
// #57(provider-config-family 批):凭据值位 `${env:VAR}` 消费侧解析(CONFIG.md §6.3)——解析抛错
|
|
50
|
+
// 落本函数既有非致命兜底(下方 catch ⇒ 标题回落 null——不破坏回合,与「无 key 不生成标题」同形)。
|
|
51
|
+
provider = resolveProviderSecrets(provider)
|
|
35
52
|
const system = "Generate a concise title (max 40 chars, no quotes) for this conversation. Reply ONLY with the title."
|
|
36
53
|
const text = userText.slice(0, 200)
|
|
37
54
|
let url, body, extract
|
|
@@ -43,7 +60,7 @@ export async function generateTitle(userContent, provider) {
|
|
|
43
60
|
messages: [{ role: "user", content: text }],
|
|
44
61
|
max_tokens: MAX_TITLE_TOKENS,
|
|
45
62
|
stream: false,
|
|
46
|
-
|
|
63
|
+
...(offShape !== null ? { thinking: offShape } : {}), // IK9UZ8:不让 reasoning 吃掉输出预算
|
|
47
64
|
})
|
|
48
65
|
extract = (data) => data.content?.map((b) => b.text || "").join("")
|
|
49
66
|
} else if (provider.format === "google") {
|
|
@@ -51,7 +68,11 @@ export async function generateTitle(userContent, provider) {
|
|
|
51
68
|
body = JSON.stringify({
|
|
52
69
|
systemInstruction: { parts: [{ text: system }] },
|
|
53
70
|
contents: [{ role: "user", parts: [{ text }] }],
|
|
54
|
-
generationConfig: {
|
|
71
|
+
generationConfig: {
|
|
72
|
+
maxOutputTokens: MAX_TITLE_TOKENS,
|
|
73
|
+
// google 格式自身的关思考形(wire 原生——非族别取形副本);无有效 off 路径 ⇒ 零发
|
|
74
|
+
...(offShape !== null ? { thinkingConfig: { thinkingLevel: "none" } } : {}),
|
|
75
|
+
}, // IK9UZ8
|
|
55
76
|
})
|
|
56
77
|
extract = (data) => data.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("")
|
|
57
78
|
} else {
|
|
@@ -65,7 +86,9 @@ export async function generateTitle(userContent, provider) {
|
|
|
65
86
|
// leave content empty (IK9UZ8). Providers that don't accept the field ignore it
|
|
66
87
|
// (OpenAI-compatible convention). A 40-char title wants ~60–80 tokens, so 100 is
|
|
67
88
|
// ~2.5x headroom (design decision — docs/core/design/SESSION.md §6.7).
|
|
68
|
-
|
|
89
|
+
// #363③:形自核单源(`thinkOffShape`——type 族 = `{type:"disabled"}`;无有效 off 路径
|
|
90
|
+
// 族不发——见上方 offShape 取形)。
|
|
91
|
+
...(offShape !== null ? { thinking: offShape } : {}),
|
|
69
92
|
max_tokens: MAX_TITLE_TOKENS,
|
|
70
93
|
stream: false,
|
|
71
94
|
}
|
|
@@ -113,7 +136,8 @@ export async function ensureSessionTitle(agent) {
|
|
|
113
136
|
let firstUser = agent._recordStore?.firstUserMessage?.() ?? null
|
|
114
137
|
if (!firstUser) firstUser = (agent._fullHistory ?? agent.history).find(isRealUserMsg)
|
|
115
138
|
if (firstUser) {
|
|
116
|
-
|
|
139
|
+
// #632:读源处剥离(注入形不得入标题请求——与恢复面同读一函数,零第二实现)
|
|
140
|
+
const title = await generateTitle(stripAtRefs(firstUser.content), agent.provider)
|
|
117
141
|
if (title) agent.title = title
|
|
118
142
|
}
|
|
119
143
|
} catch {
|
package/history-window.mjs
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
*
|
|
10
10
|
* 承接:会话数据面的「人读线」窗口切分(分段恢复 / 逐页回看)——与磁盘会话档
|
|
11
11
|
* 同源(`{hash}.json.N`),本档只读数组、不触盘。
|
|
12
|
+
*
|
|
13
|
+
* 留档记录直通(opt-in · 消化面留档批 · #719):`digest` ∕ `subagent` 两型记录
|
|
14
|
+
* 原样入窗(携 `idx`;字段零改名);默认关 ⇒ 输出与改前逐字等价(CLI ∕ VSC 读面
|
|
15
|
+
* 零改零破)——`kindOf` 认 `kind` 键;形 / 在场 / 判据单源 =
|
|
16
|
+
* `docs/desktop/design/RENDERER.md` §1.1「留档记录」条。
|
|
12
17
|
*/
|
|
13
18
|
|
|
14
19
|
/** Real user message predicate (shared by the slot metadata digest and the window).
|
|
@@ -32,7 +37,7 @@ function tsOf(m) {
|
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
function kindOf(m) {
|
|
35
|
-
return m?.type ?? m?.role
|
|
40
|
+
return m?.type ?? m?.role ?? m?.kind
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
function toolCallsOf(m) {
|
|
@@ -103,10 +108,16 @@ function pairToolEntries(history) {
|
|
|
103
108
|
* takes the LAST page (first paint); otherwise the page [s, e) ending just before
|
|
104
109
|
* `before`. Half-open [s, e) keeps loadOlder pages from re-rendering the boundary
|
|
105
110
|
* message. `idx` values are GLOBAL history indexes — pagination never renumbers.
|
|
111
|
+
*
|
|
112
|
+
* `opts.records === true`(opt-in —— 留档批 · #719)⇒ `digest` ∕ `subagent` 两型
|
|
113
|
+
* 记录原样入窗(`{ ...record, idx }`,字段零改名)并占条目位(游标按条目推进);
|
|
114
|
+
* 记录不参与 turn ∕ 可见性谓词(`visible` 面语义不动)。**默认关** ⇒ 输出与改前
|
|
115
|
+
* 逐字等价(负控腿);缺 `opts` ∕ 非 `true` ⇒ 同默认径。
|
|
106
116
|
*/
|
|
107
|
-
export function historyWindow(history, before, pageSize = HISTORY_PAGE_SIZE) {
|
|
117
|
+
export function historyWindow(history, before, pageSize = HISTORY_PAGE_SIZE, opts = {}) {
|
|
108
118
|
const total = Array.isArray(history) ? history.length : 0
|
|
109
119
|
if (total === 0) return { messages: [], hasOlder: false }
|
|
120
|
+
const records = opts?.records === true
|
|
110
121
|
const { frameResults, ownerOf } = pairToolEntries(history)
|
|
111
122
|
const end = before == null ? total : Math.max(0, Math.min(before, total))
|
|
112
123
|
const start = Math.max(0, end - pageSize)
|
|
@@ -161,6 +172,10 @@ export function historyWindow(history, before, pageSize = HISTORY_PAGE_SIZE) {
|
|
|
161
172
|
turnStart: prevVisibleKind === null || prevVisibleKind === "user",
|
|
162
173
|
tools,
|
|
163
174
|
})
|
|
175
|
+
} else if (records && (kind === "digest" || kind === "subagent")) {
|
|
176
|
+
// 留档记录直通(opt-in):人读线条目原样入窗 + 携位次(字段零改名 —— 形单源 =
|
|
177
|
+
// `docs/desktop/design/RENDERER.md` §1.1「留档记录」条)
|
|
178
|
+
messages.push({ ...m, idx: i })
|
|
164
179
|
} else if (kind === "tool") {
|
|
165
180
|
// A consumed tool entry produces no message of its own (rendered with its frame) —
|
|
166
181
|
// only a true orphan falls back to the top level (rules 3/4)
|
package/i18n.mjs
CHANGED
|
@@ -46,6 +46,8 @@ export const CORE_MESSAGES = Object.freeze({
|
|
|
46
46
|
"status.rateLimited": { en: "Rate-limited 429, retry in ${s}s", zh: "限流 429,${s}s 后重试" },
|
|
47
47
|
"status.overloaded": { en: "Server overloaded, retrying in ${s}s", zh: "服务过载,${s}s 后重试" },
|
|
48
48
|
"status.quota": { en: "quota exhausted: ${msg}", zh: "配额耗尽:${msg}" },
|
|
49
|
+
// ── 停滞轻显形(在飞回合静默读数——TUI.md §7.7 · stall-indicator 批 2026-09-29)──
|
|
50
|
+
"status.quiet": { en: "Quiet ${s}s", zh: "已静默 ${s}s" },
|
|
49
51
|
// ── 异步池状态词 ──
|
|
50
52
|
"sub.running": { en: "running", zh: "运行中" },
|
|
51
53
|
"sub.queued": { en: "queued", zh: "排队中" },
|
package/index-discover.mjs
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
* PORTABILITY VSC mirror · VP-9 (P9): the extension tables were 14 code + 5 doc
|
|
9
9
|
* entries with no declaration surface — a project in any other mainstream language
|
|
10
10
|
* was invisible AND undeclarable, silently. Now: the tables carry the CLI-aligned
|
|
11
|
-
* additions,
|
|
12
|
-
*
|
|
11
|
+
* additions, the manifest's `index.codeExtensions` / `index.docExtensions` union in
|
|
12
|
+
* project-specific extensions, and files whose extension is in NO list are
|
|
13
13
|
* TALLIED (`collectUnlisted`) so "my .xyz files are not searchable" stops being
|
|
14
14
|
* invisible (buildIndex → panel hint + a log event).
|
|
15
15
|
*/
|
|
16
16
|
import { readdirSync } from "node:fs"
|
|
17
17
|
import { join, relative } from "node:path"
|
|
18
|
-
import {
|
|
18
|
+
import { DEFAULT_DECLARATION, loadProjectDeclaration } from "./conventions.mjs"
|
|
19
19
|
|
|
20
20
|
const CODE_EXTS = new Set([
|
|
21
21
|
".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx", ".py", ".rs", ".go", ".java", ".c", ".cpp", ".h", ".hpp",
|
|
@@ -36,11 +36,11 @@ function extensionOf(p) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** Effective extension sets for one conventions object: defaults ∪ declared
|
|
39
|
-
* (declaration is a UNION — §
|
|
39
|
+
* (declaration is a UNION — §3.1: index.*Extensions APPEND). Cached per frozen
|
|
40
40
|
* conventions object (the loader hands out stable per-root instances). */
|
|
41
41
|
const _knownCache = new WeakMap()
|
|
42
42
|
function knownExtensions(conv) {
|
|
43
|
-
const key = conv ??
|
|
43
|
+
const key = conv ?? DEFAULT_DECLARATION
|
|
44
44
|
let hit = _knownCache.get(key)
|
|
45
45
|
if (!hit) {
|
|
46
46
|
const code = new Set(CODE_EXTS)
|
|
@@ -126,7 +126,7 @@ function isIndexableName(name, allExts) {
|
|
|
126
126
|
* files array (backward-compatible with needsRebuild / the memory self-check).
|
|
127
127
|
*/
|
|
128
128
|
export function discoverFiles(cwd, signal, { collectUnlisted = false } = {}) {
|
|
129
|
-
const res = collectFiles(cwd, cwd, false, signal,
|
|
129
|
+
const res = collectFiles(cwd, cwd, false, signal, loadProjectDeclaration(cwd), collectUnlisted)
|
|
130
130
|
return collectUnlisted ? res : res.files
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -135,7 +135,7 @@ export function discoverFiles(cwd, signal, { collectUnlisted = false } = {}) {
|
|
|
135
135
|
export function discoverFilesUnder(cwd, subdir, { collectUnlisted = false } = {}) {
|
|
136
136
|
const base = String(subdir ?? "").replaceAll("\\", "/").replace(/\/+$/, "")
|
|
137
137
|
if (!base) return collectUnlisted ? { files: [], unlisted: { count: 0, exts: [] } } : []
|
|
138
|
-
const res = collectFiles(join(cwd, base), cwd, false, undefined,
|
|
138
|
+
const res = collectFiles(join(cwd, base), cwd, false, undefined, loadProjectDeclaration(cwd), collectUnlisted)
|
|
139
139
|
return collectUnlisted ? res : res.files
|
|
140
140
|
}
|
|
141
141
|
|
package/ledger-cmd.mjs
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ledger-cmd.mjs —
|
|
3
|
-
*
|
|
2
|
+
* ledger-cmd.mjs — 台账核心函数档(查询 = 只读全角色 / 写 = 仅主 agent 装配)。
|
|
3
|
+
* 工具定义 + 工具层参数守卫 = 拆分件 `thincoder-core/ledger-tools.mjs`(2026-09-28 守卫族微修批 ·
|
|
4
|
+
* 台账 #473——纯搬移、语义零变;拆分触发 = 本档 298 行贴 300 顾问线,本批读面守卫增量必越线);
|
|
5
|
+
* 核函数(`ledgerQuery` / `ledgerCount` / `ledgerAdd` / `ledgerUpdate` / `ledgerClose`)留本档,
|
|
6
|
+
* 工具档消费之。
|
|
4
7
|
*
|
|
5
|
-
* 接线(设计档 §2.2
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
+
* 接线(设计档 §2.2 命令面接线):族出口 = `ledger.mjs`(re-export 两源——KD-M2-3);查询命令 ←
|
|
9
|
+
* tools/index.mjs(assembleBuiltinTools 全角色面);写命令 ← agent/family-tools.mjs(assembleFamilyTools
|
|
10
|
+
* 的 depthOnly 分支)——一律**动态 import** 本模块链(ledger-db.mjs 静态 import node:sqlite ⇒
|
|
11
|
+
* 消费侧静态 import 会破 W8 契约②)。
|
|
8
12
|
*/
|
|
9
13
|
import { statSync } from "node:fs"
|
|
10
|
-
import { resolve } from "node:path"
|
|
14
|
+
import { basename, resolve } from "node:path"
|
|
11
15
|
import { ALLOWED_MIGRATIONS, nowIso, openLedger, PENDING_STATUSES } from "./ledger-db.mjs"
|
|
12
16
|
import { resolveProjectRoot } from "./manifest.mjs"
|
|
17
|
+
import { declaredPublicRoots } from "./declaration.mjs"
|
|
13
18
|
|
|
14
19
|
/** 查询(只读,全角色):SELECT 行集(status/kind/board 过滤,缺省 = 全部行;id 升序)。库不在 = 空账。 */
|
|
15
20
|
export function ledgerQuery({ cwd, status = null, kind = null, board = null } = {}) {
|
|
@@ -37,21 +42,31 @@ export function ledgerCount({ cwd } = {}) {
|
|
|
37
42
|
/** 路径 = 存在的**档**?(目录 / 缺失 → false——写门存在性判据用)。 */
|
|
38
43
|
const isFile = (p) => { try { return statSync(p).isFile() } catch { return false } }
|
|
39
44
|
|
|
40
|
-
/** 写门·指针存在性(设计档 §6.1 · 台账 #38 · AC-M2-
|
|
41
|
-
* {在途, 待核销}`
|
|
42
|
-
* 指向存在的档;缺文件部分(`§2` /
|
|
45
|
+
/** 写门·指针存在性(设计档 §6.1 · 台账 #38 · AC-M2-10):写命令落盘前判**结果行**——`status ∈
|
|
46
|
+
* {在途, 待核销}` ⇒ 判 `task_book`:`null`(缺指针)⇒ 拒(「必填」文案);非 `null` ⇒ 文件部分
|
|
47
|
+
* (首个 `§` 前子串,trim)须经基准 `resolve(base, …)` 指向存在的档;缺文件部分(`§2` / 空串 /
|
|
48
|
+
* 全空白)⇒ 拒;不在册 / 非文件 ⇒ 拒(throw,行不变)。
|
|
43
49
|
* 判位与迁移表判同层(落盘前);文案前缀 = 调用函数名(与同函数既有两条文案同款)。
|
|
44
50
|
* 基准 `base` = **与台账库关联键同源**(2026-09-18 fix 轮 · O1 收正):`resolveProjectRoot(cwd) ??
|
|
45
51
|
* resolve(cwd ?? ".")`——同表达式见 `ledger-db.mjs` `ledgerDbPath`;容器根会话(锚 cwd 下唯一注册
|
|
46
|
-
* 子仓 P)与子仓会话 ⇒ 库键与指针基准同取 P(同库同基准;原实现 `resolve(cwd, …)` = 原始 cwd 会误拒)。
|
|
52
|
+
* 子仓 P)与子仓会话 ⇒ 库键与指针基准同取 P(同库同基准;原实现 `resolve(cwd, …)` = 原始 cwd 会误拒)。
|
|
53
|
+
* #832:声明源前缀形同判(§6.15——首段 = 声明公共仓目录名 ⇒ 于该声明根解析;可核 ⇒ 通过;缺位拒)。 */
|
|
47
54
|
function assertTaskBookGate(cwd, fnName, status, taskBook) {
|
|
48
55
|
if (status !== "在途" && status !== "待核销") return
|
|
49
|
-
if (taskBook == null
|
|
56
|
+
if (taskBook == null) throw new Error(`${fnName}:task_book 必填(在途 / 待核销 须携任务书指针)`)
|
|
50
57
|
const part = String(taskBook).split("§")[0].trim()
|
|
51
58
|
if (!part) throw new Error(`${fnName}:task_book 不可解析(缺文件部分):${taskBook}`)
|
|
52
59
|
const base = resolveProjectRoot(cwd) ?? resolve(cwd ?? ".")
|
|
53
60
|
const abs = resolve(base, part)
|
|
54
|
-
|
|
61
|
+
// §6.15 声明源候选(#832 · LEDGER.md §6.1-4):首段 = 声明公共仓目录名 ⇒ 该声明根解析余段(同名多仓 ⇒ 声明序首者);未声明 ∥ 缺位 ⇒ 照旧拒。
|
|
62
|
+
if (isFile(abs)) return
|
|
63
|
+
const segs = part.replace(/\\/g, "/").split("/").filter(Boolean)
|
|
64
|
+
for (const root of declaredPublicRoots(base)) {
|
|
65
|
+
if (segs[0] !== basename(root)) continue
|
|
66
|
+
if (segs.length > 1 && isFile(resolve(root, segs.slice(1).join("/")))) return
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`${fnName}:task_book 指向的档不存在:${taskBook}(解析 = ${abs})`)
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
/** 新增(写命令,仅主 agent):INSERT——入待讨论(六态状态机入口)。 */
|
|
@@ -101,7 +116,9 @@ export function ledgerUpdate({ cwd, id, patch, executorSessionId }) {
|
|
|
101
116
|
} finally { db.close() }
|
|
102
117
|
}
|
|
103
118
|
|
|
104
|
-
/** 收口(写命令,仅主 agent
|
|
119
|
+
/** 收口(写命令,仅主 agent):核销两源(勾销:待核销 → 已核销;追认核销:待讨论 / 待设计 → 已核销,
|
|
120
|
+
* 行 `evidence` 非空,缺 / 全空白 ⇒ 拒)/ 撤回(任意态 → 已废弃),事务包裹;归档 = 软删除。
|
|
121
|
+
* 判序(同函数体同层):目标集判 → 行取 → 源态判 → `evidence` 门 → UPDATE(LEDGER.md §3 收口两源)。 */
|
|
105
122
|
export function ledgerClose({ cwd, id, status }) {
|
|
106
123
|
if (status !== "已核销" && status !== "已废弃") throw new Error(`ledgerClose:目标态 ${status} ∉ {已核销, 已废弃}`)
|
|
107
124
|
const db = openLedger(cwd, { create: true })
|
|
@@ -110,123 +127,14 @@ export function ledgerClose({ cwd, id, status }) {
|
|
|
110
127
|
try {
|
|
111
128
|
const row = db.prepare("SELECT * FROM items WHERE id = ?").get(id)
|
|
112
129
|
if (!row) throw new Error(`ledgerClose:行 ${id} 不存在`)
|
|
113
|
-
|
|
130
|
+
// 判序:源态判(核销仅三源——在途不可跳)→ 追认口 `evidence` 门(行字段非空,缺 / 全空白 ⇒ 拒)
|
|
131
|
+
if (status === "已核销" && !["待讨论", "待设计", "待核销"].includes(row.status)) throw new Error(`ledgerClose:核销仅限 待讨论 / 待设计 / 待核销(现态 ${row.status})`)
|
|
132
|
+
if (status === "已核销" && ["待讨论", "待设计"].includes(row.status) && (row.evidence == null || String(row.evidence).trim() === "")) throw new Error(`ledgerClose:追认核销须带 evidence(现态 ${row.status})`)
|
|
114
133
|
const now = nowIso()
|
|
115
|
-
// 撤回(任意态 → 已废弃——含在途直撤)同步 executor = NULL(LEDGER.md §3.1
|
|
134
|
+
// 撤回(任意态 → 已废弃——含在途直撤)同步 executor = NULL(LEDGER.md §3.1 ④);核销两源(勾销 / 追认)executor 零触碰(非在途出边)
|
|
116
135
|
db.prepare("UPDATE items SET status = ?, closed_at = ?, updated_at = ?, executor = ? WHERE id = ?").run(status, now, now, status === "已废弃" ? null : row.executor, id)
|
|
117
136
|
db.exec("COMMIT")
|
|
118
137
|
return { id, status }
|
|
119
138
|
} catch (e) { db.exec("ROLLBACK"); throw e }
|
|
120
139
|
} finally { db.close() }
|
|
121
140
|
}
|
|
122
|
-
|
|
123
|
-
// ── 工具定义(接线:查询 → tools/index.mjs 全角色面;写 → family-tools.mjs depthOnly 分支) ──
|
|
124
|
-
|
|
125
|
-
/** 工具 cwd 供值面(缺省 → 当前项目根)。 */
|
|
126
|
-
const cwdOf = (ctx) => ctx?.agent?.cwd ?? ctx?.cwd ?? process.cwd()
|
|
127
|
-
|
|
128
|
-
export const ledgerQueryTool = {
|
|
129
|
-
name: "ledger_query",
|
|
130
|
-
description: "台账查询(需求池 / 技术待办)——SQLite 行集,只读、全角色可用。未决四态 = 待讨论 / 待设计 / 在途 / 待核销;已核销 / 已废弃 = 归档态(软删除,行保留)。过滤参数缺省 = 全部行。",
|
|
131
|
-
parameters: {
|
|
132
|
-
type: "object",
|
|
133
|
-
properties: {
|
|
134
|
-
cwd: { type: "string", description: "项目根目录——台账按项目根关联存储于用户数据目录(工作树外、不进 git)。相对路径按当前工作目录解析;查/写别的项目请给绝对路径。缺省 = 当前会话项目根" },
|
|
135
|
-
status: { type: "string", enum: ["待讨论", "待设计", "在途", "待核销", "已核销", "已废弃"], description: "按状态过滤(六态之一,缺省 = 不过滤)" },
|
|
136
|
-
kind: { type: "string", enum: ["requirement", "tech_todo"], description: "按类别过滤" },
|
|
137
|
-
board: { type: "string", description: "按归属板块过滤(需求档文档名)" },
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
readonly: true,
|
|
141
|
-
async execute(args, ctx) {
|
|
142
|
-
return JSON.stringify(ledgerQuery({ cwd: args.cwd ?? cwdOf(ctx), status: args.status, kind: args.kind, board: args.board }), null, 2)
|
|
143
|
-
},
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export const ledgerCountTool = {
|
|
147
|
-
name: "ledger_count",
|
|
148
|
-
description: "台账计数单源:未决四态(待讨论 / 待设计 / 在途 / 待核销)COUNT(*),只读、全角色可用。",
|
|
149
|
-
parameters: {
|
|
150
|
-
type: "object",
|
|
151
|
-
properties: { cwd: { type: "string", description: "项目根目录——台账按项目根关联存储于用户数据目录(工作树外、不进 git)。相对路径按当前工作目录解析;查/写别的项目请给绝对路径。缺省 = 当前会话项目根" } },
|
|
152
|
-
},
|
|
153
|
-
readonly: true,
|
|
154
|
-
async execute(args, ctx) {
|
|
155
|
-
return JSON.stringify({ count: ledgerCount({ cwd: args.cwd ?? cwdOf(ctx) }) })
|
|
156
|
-
},
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export const ledgerAddTool = {
|
|
160
|
-
name: "ledger_add",
|
|
161
|
-
description: "台账新增条目(写命令,仅主 agent)——入待讨论(六态状态机入口)。kind = requirement(需求池)/ tech_todo(技术待办);trigger = 归批 / 条件 / 认账不排期(可空)。",
|
|
162
|
-
parameters: {
|
|
163
|
-
type: "object",
|
|
164
|
-
required: ["kind", "title"],
|
|
165
|
-
properties: {
|
|
166
|
-
cwd: { type: "string", description: "项目根目录——台账按项目根关联存储于用户数据目录(工作树外、不进 git)。相对路径按当前工作目录解析;查/写别的项目请给绝对路径。缺省 = 当前会话项目根" },
|
|
167
|
-
kind: { type: "string", enum: ["requirement", "tech_todo"], description: "类别" },
|
|
168
|
-
title: { type: "string", description: "条目标题" },
|
|
169
|
-
board: { type: "string", description: "归属板块(需求档文档名,可空)" },
|
|
170
|
-
req_doc: { type: "string", description: "需求档指针(可空)" },
|
|
171
|
-
task_book: { type: "string", description: "任务书指针(可空;在途 / 待核销必填——咬合 CHECK)" },
|
|
172
|
-
evidence: { type: "string", description: "最小证据行(可空)" },
|
|
173
|
-
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "技术待办触发(可空)" },
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
readonly: false,
|
|
177
|
-
async execute(args, ctx) {
|
|
178
|
-
const { cwd, ...row } = args
|
|
179
|
-
const id = ledgerAdd({ cwd: cwd ?? cwdOf(ctx), row })
|
|
180
|
-
return JSON.stringify({ id, status: "待讨论" })
|
|
181
|
-
},
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export const ledgerUpdateTool = {
|
|
185
|
-
name: "ledger_update",
|
|
186
|
-
description: "台账更新条目(写命令,仅主 agent)——状态迁移按六态允许迁移表:待讨论→待设计 / 待设计→在途 / 在途→待核销 / 待核销→已核销 / 任意态→已废弃;表外迁移被拒。status 缺省 = 仅更新字段。",
|
|
187
|
-
parameters: {
|
|
188
|
-
type: "object",
|
|
189
|
-
required: ["id"],
|
|
190
|
-
properties: {
|
|
191
|
-
cwd: { type: "string", description: "项目根目录——台账按项目根关联存储于用户数据目录(工作树外、不进 git)。相对路径按当前工作目录解析;查/写别的项目请给绝对路径。缺省 = 当前会话项目根" },
|
|
192
|
-
id: { type: "number", description: "条目 id" },
|
|
193
|
-
status: { type: "string", description: "目标状态(六态之一,缺省 = 不变)" },
|
|
194
|
-
title: { type: "string", description: "标题(缺省 = 不变)" },
|
|
195
|
-
board: { type: "string", description: "归属板块(缺省 = 不变)" },
|
|
196
|
-
req_doc: { type: "string", description: "需求档指针(缺省 = 不变)" },
|
|
197
|
-
task_book: { type: "string", description: "任务书指针(缺省 = 不变)" },
|
|
198
|
-
evidence: { type: "string", description: "证据(缺省 = 不变)" },
|
|
199
|
-
trigger: { type: "string", enum: ["归批", "条件", "认账不排期"], description: "触发(缺省 = 不变)" },
|
|
200
|
-
executor: { type: "string", description: "执行者 sessionId(可选——接手改写归属用;缺省 = 按状态迁移语义:进在途自动写本会话 / 出在途自动清空 / 其余不变)" },
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
readonly: false,
|
|
204
|
-
async execute(args, ctx) {
|
|
205
|
-
const { cwd, id, ...patch } = args
|
|
206
|
-
let executorSessionId
|
|
207
|
-
if (patch.executor === undefined) {
|
|
208
|
-
// 动态 import——零新静态边(K-LX1);工具层供值,核心函数保持参数注入纯函数
|
|
209
|
-
const { getSessionId } = await import("./session-slots.mjs")
|
|
210
|
-
executorSessionId = getSessionId()
|
|
211
|
-
}
|
|
212
|
-
return JSON.stringify(ledgerUpdate({ cwd: cwd ?? cwdOf(ctx), id, patch, executorSessionId }))
|
|
213
|
-
},
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export const ledgerCloseTool = {
|
|
217
|
-
name: "ledger_close",
|
|
218
|
-
description: "台账收口(写命令,仅主 agent)——勾销:待核销 → 已核销;撤回:任意态 → 已废弃。归档 = 软删除(写 closed_at,行保留)。",
|
|
219
|
-
parameters: {
|
|
220
|
-
type: "object",
|
|
221
|
-
required: ["id", "status"],
|
|
222
|
-
properties: {
|
|
223
|
-
cwd: { type: "string", description: "项目根目录——台账按项目根关联存储于用户数据目录(工作树外、不进 git)。相对路径按当前工作目录解析;查/写别的项目请给绝对路径。缺省 = 当前会话项目根" },
|
|
224
|
-
id: { type: "number", description: "条目 id" },
|
|
225
|
-
status: { type: "string", enum: ["已核销", "已废弃"], description: "目标态(勾销 / 撤回)" },
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
readonly: false,
|
|
229
|
-
async execute(args, ctx) {
|
|
230
|
-
return JSON.stringify(ledgerClose({ cwd: args.cwd ?? cwdOf(ctx), id: args.id, status: args.status }))
|
|
231
|
-
},
|
|
232
|
-
}
|
package/ledger-db.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { join, resolve } from "node:path"
|
|
|
18
18
|
import { DatabaseSync } from "node:sqlite"
|
|
19
19
|
|
|
20
20
|
import { configDir } from "./config-io.mjs"
|
|
21
|
-
import { resolveProjectRoot } from "./manifest.mjs"
|
|
21
|
+
import { projectRootView, resolveProjectRoot } from "./manifest.mjs"
|
|
22
22
|
// 归一步单源(KD-LN1——直引,不抽新 util:先例 = peer-domains.mjs / traces/trace-store.mjs)。
|
|
23
23
|
import { normalizeCwd } from "./session-slots.mjs"
|
|
24
24
|
|
|
@@ -102,9 +102,17 @@ export function ensureExecutorColumn(db, { exists } = {}) {
|
|
|
102
102
|
|
|
103
103
|
/** 开库(cwd = 项目根——仅作关联键)→ DatabaseSync 句柄 + 幂等建表 + 老库幂等迁移(executor 列)。
|
|
104
104
|
* 读面(create=false):库文件不存在 → 返回 null(空账——读面不建库、无副作用)。
|
|
105
|
-
* 写面(create=true):库文件不存在自动建(台账目录随之创建);项目根目录不存在 → 抛友好错误。
|
|
105
|
+
* 写面(create=true):库文件不存在自动建(台账目录随之创建);项目根目录不存在 → 抛友好错误。
|
|
106
|
+
* **歧义锚**(≥2 候选——`projectRootView` `ambiguous` 态)⇒ **显式拒**(列候选 + 显式项目根指引;
|
|
107
|
+
* 零写——不静默回退锚;族锚「项目不可解析」,AC-M2-17);兜底 `resolve(cwd ?? ".")` 只属 none 态。 */
|
|
106
108
|
export function openLedger(cwd, { create = false } = {}) {
|
|
107
109
|
const dir = resolve(cwd ?? ".")
|
|
110
|
+
// 歧义锚 ⇒ 显式拒(零写——消「缺省空读 / 错写落锚」双面症;读 / 写五工具同经本门)。
|
|
111
|
+
const view = projectRootView(dir)
|
|
112
|
+
if (view.state === "ambiguous") {
|
|
113
|
+
const list = view.candidates.map((c) => `- ${c}`).join("\n")
|
|
114
|
+
throw new Error(`项目不可解析:cwd ${dir} 下候选项目不是恰好一个(下列 ${view.candidates.length} 个)——台账按项目根键控,请显式给出项目根 cwd:\n${list}`)
|
|
115
|
+
}
|
|
108
116
|
const file = ledgerDbPath(dir)
|
|
109
117
|
if (!existsSync(dir)) {
|
|
110
118
|
throw new Error(`项目目录不存在:${dir}——cwd 请给存在的项目根(相对路径按当前工作目录解析;缺省 = 会话项目根)`)
|