@thincoder/core 0.10.4 → 0.10.5
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 +31 -1
- package/README.md +1 -1
- package/advisor/compaction.mjs +11 -6
- package/advisor/run.mjs +4 -4
- package/agent/assemble.mjs +1 -1
- package/agent/chat-call.mjs +1 -1
- package/agent/child-marks.mjs +2 -2
- package/agent/digest-account.mjs +5 -4
- package/agent/dispatch-gates.mjs +8 -0
- package/agent/dispatch.mjs +39 -18
- package/agent/family-tools.mjs +22 -11
- package/agent/helpers.mjs +4 -3
- package/agent/light-round.mjs +66 -0
- package/agent/post-turn.mjs +8 -4
- package/agent/run-stages.mjs +5 -3
- package/agent/run-start.mjs +3 -2
- package/agent/setup-reminders.mjs +1 -1
- package/agent/setup.mjs +18 -3
- package/agent/subagent-event.mjs +1 -1
- package/agent/suspension.mjs +8 -5
- package/agent/timers.mjs +56 -5
- package/agent/turn-loop.mjs +5 -3
- package/agent/write-gate.mjs +7 -7
- package/agent-tools/advisor-async.mjs +2 -2
- package/agent-tools/advisor-settle.mjs +2 -2
- package/agent-tools/advisor.mjs +1 -1
- package/agent-tools/async-discard.mjs +34 -0
- package/agent-tools/async-settle.mjs +3 -2
- package/agent-tools/audit-block.mjs +1 -1
- package/agent-tools/bash-async.mjs +1 -1
- package/agent-tools/batch-lifecycle.mjs +35 -14
- package/agent-tools/batch-paths.mjs +4 -4
- package/agent-tools/batch-skeleton.mjs +29 -11
- package/agent-tools/batch.mjs +28 -15
- package/agent-tools/browser-async.mjs +198 -0
- package/agent-tools/checkpoint.mjs +1 -1
- package/agent-tools/design-slots.mjs +232 -0
- package/agent-tools/digest-budget.mjs +1 -1
- package/agent-tools/manifest.mjs +193 -0
- package/agent-tools/parent-channel.mjs +7 -4
- package/agent-tools/review-facts.mjs +13 -13
- package/agent-tools/settings.mjs +1 -1
- package/agent-tools/skill.mjs +1 -1
- package/agent-tools/subagent-async.mjs +21 -12
- package/agent-tools/subagent-scheduler.mjs +8 -5
- package/agent-tools/subagent-spawn.mjs +29 -55
- package/agent-tools/subagent.mjs +21 -8
- package/agent-tools/timer.mjs +186 -12
- package/agent-tools/zero-write-watch.mjs +2 -2
- package/agent-tools.mjs +2 -1
- package/agent.mjs +1 -1
- package/attachments.mjs +1 -1
- package/config-io.mjs +18 -13
- package/config-migrate.mjs +13 -29
- package/config-presets.mjs +33 -29
- package/config.mjs +68 -25
- package/context-degrade.mjs +1 -1
- package/context-echo.mjs +1 -1
- package/context-push.mjs +2 -2
- package/context.mjs +1 -1
- package/conventions.mjs +2 -2
- package/declaration.mjs +18 -2
- package/file-links.mjs +1 -1
- package/file-refs.mjs +1 -1
- package/history-window.mjs +1 -1
- package/i18n.mjs +2 -0
- package/ledger-cmd.mjs +62 -16
- package/ledger-db.mjs +6 -0
- package/ledger-surface.mjs +49 -5
- package/ledger-tools.mjs +16 -8
- package/ledger.mjs +9 -16
- package/log.mjs +1 -1
- package/manifest-discovery.mjs +1 -1
- package/manifest-schema.mjs +1 -1
- package/mcp/transport-stdio.mjs +1 -1
- package/mcp.mjs +1 -1
- package/memory/code-index.mjs +1 -1
- package/memory/code-search.mjs +1 -1
- package/memory/code-sync.mjs +1 -1
- package/memory/docs.mjs +6 -2
- package/memory/file-list.mjs +1 -1
- package/memory/file-walk.mjs +17 -5
- package/memory/maintain.mjs +410 -0
- package/memory/memory-tool.mjs +21 -6
- package/memory/schema.mjs +19 -7
- package/memory/sweep.mjs +8 -5
- package/memory/sync-tail.mjs +16 -10
- package/model-ref.mjs +16 -13
- package/model-specs.mjs +21 -9
- package/package.json +1 -1
- package/peer-instances.mjs +1 -1
- package/permission.mjs +33 -0
- package/process-probe.mjs +2 -2
- package/prompts/advisor-design.md +4 -2
- package/prompts/advisor-round1.md +1 -1
- package/prompts/advisor-round2.md +1 -1
- package/prompts/advisor-round3.md +1 -1
- package/prompts/common.md +36 -2
- package/prompts/consult-base.md +1 -1
- package/prompts/discipline-engineering.md +32 -15
- package/prompts/discipline-normal.md +46 -2
- package/prompts/persona-eng-designer.md +6 -6
- package/prompts/persona-engineering.md +14 -6
- package/prompts/persona-normal.md +2 -0
- package/prompts/subagent-base.md +3 -0
- package/provider/core.mjs +6 -0
- package/provider/errors.mjs +5 -3
- package/provider/google.mjs +9 -8
- package/provider/list-models.mjs +3 -2
- package/provider/sse.mjs +3 -0
- package/provider-flows.mjs +14 -15
- package/proxy-chunked.mjs +102 -0
- package/proxy-target.mjs +54 -0
- package/proxy-transport.mjs +298 -0
- package/proxy.mjs +17 -248
- package/session-gc.mjs +2 -2
- package/session-lifecycle.mjs +18 -14
- package/session-slot-claims.mjs +2 -2
- package/session-slots-manifest.mjs +1 -1
- package/session-slots.mjs +3 -3
- package/team-ledger.mjs +282 -0
- package/team.mjs +223 -0
- package/tool-docs/bash.md +1 -0
- package/tool-docs/batch.md +1 -1
- package/tool-docs/browser.md +48 -0
- package/tool-docs/ledger.md +3 -3
- package/tool-docs/manifest.md +16 -0
- package/tool-docs/memory.md +1 -0
- package/tool-docs/notify_parent.md +5 -0
- package/tool-docs/process.md +4 -4
- package/tool-docs/question.md +1 -0
- package/tool-docs/subagent.md +4 -3
- package/tool-docs/team_ledger.md +9 -0
- package/tool-docs/timer.md +6 -0
- package/tool-docs/wait_for.md +1 -0
- package/tools/browser.mjs +161 -0
- package/tools/git.mjs +1 -1
- package/tools/index.mjs +3 -0
- package/tools/ops.mjs +26 -10
- package/tools/repomap.mjs +1 -1
- package/tools/write-path.mjs +2 -2
- package/traces/trace-cleanup.mjs +1 -1
- package/traces/trace-store.mjs +1 -1
- package/vision-reader.mjs +10 -16
package/config-presets.mjs
CHANGED
|
@@ -8,52 +8,56 @@
|
|
|
8
8
|
* - `presetToEntry` = 取 VSC 侧独有导出(CLI 侧对应实现在
|
|
9
9
|
* `cli/setup-wizard.mjs`,同字节语义)。
|
|
10
10
|
*
|
|
11
|
-
* 预先声明(
|
|
12
|
-
*
|
|
11
|
+
* 预先声明(2026-10-09 清除批:渠道单值 `model` 退场——PROVIDER.md §6.16 M3):预置**不携带**
|
|
12
|
+
* 默认模型(新装种子 = 零播种);模型身份唯二 = 顶层 `defaultModel` 复合串 ∥ 会话槽选定;
|
|
13
|
+
* 候选清单字段亦不预置——运行期从 provider 拉取(`GET /models`)。
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
/** Built-in provider presets: shared by /provider add <preset> and first-run wizard. */
|
|
16
17
|
export const PROVIDER_PRESETS = {
|
|
17
|
-
deepseek: { baseURL: "https://api.deepseek.com",
|
|
18
|
-
kimi: { baseURL: "https://api.moonshot.cn/v1",
|
|
19
|
-
"kimi-code": { baseURL: "https://api.kimi.com/coding/v1",
|
|
20
|
-
glm: { baseURL: "https://open.bigmodel.cn/api/paas/v4",
|
|
21
|
-
"glm-code": { baseURL: "https://open.bigmodel.cn/api/coding/paas/v4",
|
|
22
|
-
qwen: { baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
|
23
|
-
qwenplan: { baseURL: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1",
|
|
24
|
-
mimo: { baseURL: "https://api.xiaomimimo.com/v1",
|
|
25
|
-
mimoplan: { baseURL: "https://token-plan-cn.xiaomimimo.com/v1",
|
|
26
|
-
minimax: { baseURL: "https://api.minimaxi.com/v1",
|
|
27
|
-
openai: { baseURL: "https://api.openai.com/v1",
|
|
28
|
-
claude: { baseURL: "https://api.anthropic.com/v1",
|
|
29
|
-
gemini: { baseURL: "https://generativelanguage.googleapis.com/v1beta",
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
deepseek: { baseURL: "https://api.deepseek.com", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 384_000, desc: "DeepSeek" },
|
|
19
|
+
kimi: { baseURL: "https://api.moonshot.cn/v1", thinking: null, reasoningEffort: "max", maxTokens: 131072, desc: "Kimi / Moonshot" },
|
|
20
|
+
"kimi-code": { baseURL: "https://api.kimi.com/coding/v1", thinking: null, reasoningEffort: "max", maxTokens: 131072, desc: "Kimi For Coding (platform.kimi.com — sk-kimi- keys; NOT interchangeable with Moonshot)" },
|
|
21
|
+
glm: { baseURL: "https://open.bigmodel.cn/api/paas/v4", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 128000, desc: "Zhipu GLM" },
|
|
22
|
+
"glm-code": { baseURL: "https://open.bigmodel.cn/api/coding/paas/v4", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 128000, desc: "Zhipu GLM Coding Plan (coding endpoint — same key as GLM; server-forced thinking)" },
|
|
23
|
+
qwen: { baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", reasoningEffort: "high", maxTokens: 131072, desc: "Qwen / Alibaba" },
|
|
24
|
+
qwenplan: { baseURL: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1", reasoningEffort: "high", maxTokens: 131072, desc: "Qwen Token Plan (百炼套餐)" },
|
|
25
|
+
mimo: { baseURL: "https://api.xiaomimimo.com/v1", thinking: { type: "enabled" }, maxTokens: 131072, desc: "MiMo (Xiaomi)" },
|
|
26
|
+
mimoplan: { baseURL: "https://token-plan-cn.xiaomimimo.com/v1", thinking: { type: "enabled" }, maxTokens: 131072, desc: "MiMo Token Plan (小米套餐 — tp- keys; 与按量付费 sk- 密钥不通用)" },
|
|
27
|
+
minimax: { baseURL: "https://api.minimaxi.com/v1", thinking: { type: "adaptive" }, maxTokens: 128000, chatPath: "/text/chatcompletion_v2", desc: "MiniMax" },
|
|
28
|
+
openai: { baseURL: "https://api.openai.com/v1", desc: "OpenAI" },
|
|
29
|
+
claude: { baseURL: "https://api.anthropic.com/v1", format: "anthropic", maxTokens: 8192, desc: "Claude (Anthropic)" },
|
|
30
|
+
gemini: { baseURL: "https://generativelanguage.googleapis.com/v1beta", format: "google", maxTokens: 8192, desc: "Gemini (Google)" },
|
|
31
|
+
// Gemini OpenAI 兼容端点(Google 官方——2026-10-09 server 预设批 · 台账 #1128):与 `gemini`(原生
|
|
32
|
+
// `format:"google"`)同 key、两形态并存——本键 = OpenAI `/chat/completions` 侧(PROVIDER.md §6.11)。
|
|
33
|
+
// **待验(无 key——未实拉 `/models`;接入后按实拉复核)**;`thinking` / `reasoningEffort` / `maxTokens` 不设 = 不发(载荷面未测——D-13)。
|
|
34
|
+
"gemini-openai": { baseURL: "https://generativelanguage.googleapis.com/v1beta/openai", desc: "Gemini (Google) — OpenAI-compatible side (same key as gemini)" },
|
|
35
|
+
grok: { baseURL: "https://api.x.ai/v1", maxTokens: 64_000, desc: "Grok (xAI)" },
|
|
36
|
+
mistral: { baseURL: "https://api.mistral.ai/v1", maxTokens: 32_000, desc: "Mistral" },
|
|
37
|
+
volcengine: { baseURL: "https://ark.cn-beijing.volces.com/api/v3", maxTokens: 131072, desc: "Volcengine Ark (豆包)" },
|
|
38
|
+
hunyuan: { baseURL: "https://api.hunyuan.cloud.tencent.com/v1", maxTokens: 32_000, desc: "Hunyuan (腾讯混元)" },
|
|
34
39
|
// TokenHub = Tencent MaaS 聚合网关(另一主机;`hy3` 在本规格表有行)。`thinking` / `reasoningEffort` /
|
|
35
40
|
// `maxTokens` 一律**不设 = 不发**(两新渠道的 thinking 载荷与 max_tokens 行为未测 —— MODEL-SPECS §9.6 D-13)。
|
|
36
|
-
tokenhub: { baseURL: "https://tokenhub.tencentmaas.com/v1",
|
|
41
|
+
tokenhub: { baseURL: "https://tokenhub.tencentmaas.com/v1", desc: "Tencent TokenHub (腾讯混元网关)" },
|
|
37
42
|
// Huawei Cloud MaaS(ModelArts Studio)——OpenAI 兼容 `…/openai/v1`(区域端点变体走自定义渠道
|
|
38
|
-
// 自助路径——PROVIDER.md §6.21
|
|
39
|
-
// QS 示例模型 · 官方 API 样例 `model` 逐字)· **待验(无 key——未实拉 /models;接入后按实拉取值复核)**;
|
|
43
|
+
// 自助路径——PROVIDER.md §6.21)。**待验(无 key——未实拉 /models;接入后按实拉取值复核)**;
|
|
40
44
|
// `thinking` / `reasoningEffort` / `maxTokens` 不设 = 不发(载荷面未测——D-13)。
|
|
41
|
-
huawei: { baseURL: "https://api.modelarts-maas.com/openai/v1",
|
|
42
|
-
siliconflow: { baseURL: "https://api.siliconflow.cn/v1",
|
|
43
|
-
openrouter: { baseURL: "https://openrouter.ai/api/v1",
|
|
44
|
-
groq: { baseURL: "https://api.groq.com/openai/v1",
|
|
45
|
+
huawei: { baseURL: "https://api.modelarts-maas.com/openai/v1", desc: "Huawei Cloud ModelArts Studio (华为云 MaaS)" },
|
|
46
|
+
siliconflow: { baseURL: "https://api.siliconflow.cn/v1", maxTokens: 32_000, desc: "SiliconFlow (硅基流动)" },
|
|
47
|
+
openrouter: { baseURL: "https://openrouter.ai/api/v1", maxTokens: 32_000, desc: "OpenRouter" },
|
|
48
|
+
groq: { baseURL: "https://api.groq.com/openai/v1", maxTokens: 32_000, desc: "Groq" },
|
|
45
49
|
// OpenCode Go 订阅网关(opencode.ai/zen 取 sk- key ∥ 无 OAuth)——官方档(go.mdx)双协议混装 ⇒ 拆双预设
|
|
46
50
|
// (PROVIDER.md §6.11):本键 = OpenAI 兼容 `/chat/completions` 侧;`opencode-go-anthropic` = Anthropic
|
|
47
51
|
// `/messages` 侧(同 key——模型按侧选)。字段集 = 最小面:`thinking` / `reasoningEffort` 不设 = 不发
|
|
48
52
|
// (D-13 同口径);`maxTokens` 仅 anthropic 侧设 `65536`(该 transport `max_tokens` 必发——不设即落规格
|
|
49
53
|
// 行值 131072,超渠道口径)。**待验(无 key——未实拉 `/models` ∕ 载荷未实测;接入后按实拉复核)**;
|
|
50
54
|
// `x-opencode-session` 头 = 可选遥测——**不发**(亦不动 `providers[].headers` 面)。
|
|
51
|
-
"opencode-go": { baseURL: "https://opencode.ai/zen/go/v1",
|
|
52
|
-
"opencode-go-anthropic": { baseURL: "https://opencode.ai/zen/go/v1",
|
|
55
|
+
"opencode-go": { baseURL: "https://opencode.ai/zen/go/v1", desc: "OpenCode Go subscription (sk- key from opencode.ai/zen; OpenAI-compatible models — MiniMax/Qwen via opencode-go-anthropic)" },
|
|
56
|
+
"opencode-go-anthropic": { baseURL: "https://opencode.ai/zen/go/v1", format: "anthropic", maxTokens: 65536, desc: "OpenCode Go — Anthropic Messages side (same sk- key as opencode-go; MiniMax/Qwen models)" },
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
/** Build the stored provider entry from a preset — strip the display field, keep the rest
|
|
56
|
-
*
|
|
60
|
+
* (2026-10-09 清除批:不产 `model` 键——渠道不携模型;候选清单不再由预设播种)。 */
|
|
57
61
|
export function presetToEntry(name) {
|
|
58
62
|
const preset = PROVIDER_PRESETS[name]
|
|
59
63
|
if (!preset) return null
|
package/config.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* config.mjs — configuration loading and saving
|
|
3
|
-
* Model-merge schema v2 (2026-09-10 MODEL-SELECTION):
|
|
4
|
-
*
|
|
5
|
-
* candidates list field models[] is gone — the available-model list is fetched from
|
|
6
|
-
* provider at runtime (`GET /models`, PROVIDER.md §16). config.defaultModel (top level,
|
|
3
|
+
* Model-merge schema v2 (2026-09-10 MODEL-SELECTION; 2026-10-09 清除批:渠道单值模型退场):
|
|
4
|
+
* providers[] carry NO channel default model (`providers[].model` retired — 渠道不是模型选择
|
|
5
|
+
* 单位); the candidates list field models[] is gone — the available-model list is fetched from
|
|
6
|
+
* the provider at runtime (`GET /models`, PROVIDER.md §16). config.defaultModel (top level,
|
|
7
7
|
* "provider:model" composite) is the new-session starting point; activeProvider/activeModel
|
|
8
8
|
* are gone from config (session slots keep their own double fields). Legacy fields migrate
|
|
9
9
|
* on load (write-back failure never blocks startup).
|
|
@@ -35,7 +35,7 @@ export const DEFAULTS = {
|
|
|
35
35
|
agent: {
|
|
36
36
|
maxTurns: 200,
|
|
37
37
|
subagentTurns: 100,
|
|
38
|
-
subagentModel: null, // default subagent model: "provider:model" |
|
|
38
|
+
subagentModel: null, // default subagent model: "provider:model" | model name (parent provider); null = inherit parent provider
|
|
39
39
|
subagentModels: {}, // per-type override: { explore, plan, coder, "eng-coder" } — priority: subagent tool model arg > this[role] > subagentModel > parent provider
|
|
40
40
|
goalTurns: 200,
|
|
41
41
|
compactThreshold: 100000,
|
|
@@ -69,6 +69,8 @@ export const DEFAULTS = {
|
|
|
69
69
|
dbPath: join(configDir, "memory.db"),
|
|
70
70
|
projectDir: ".thincoder/memory",
|
|
71
71
|
team: null,
|
|
72
|
+
// 索引自维护(§6.14 面⑤ · 台账 #1096):维护型备份保留枚数(轮转保最新 N——引擎钳 ≥ 1)
|
|
73
|
+
maintain: { backupKeep: 2 },
|
|
72
74
|
},
|
|
73
75
|
shell: null, // bash tool shell executable (e.g. "C:\\Program Files\\Git\\bin\\bash.exe" or "pwsh"); null = system default (cmd on Windows, /bin/sh elsewhere)
|
|
74
76
|
embedding: {
|
|
@@ -82,11 +84,14 @@ export const DEFAULTS = {
|
|
|
82
84
|
// Structured search via Tavily when a key is set — empty apiKey → Bing RSS/HTML fallback (zero-config).
|
|
83
85
|
apiKey: "", // Tavily key (tvly-...) — optional
|
|
84
86
|
},
|
|
87
|
+
// 浏览器工具(BROWSER-TOOL.md §3.2):非空 ⇒ navigate 目标 host ∥ click/evaluate/type 的当前页 host 须命中
|
|
88
|
+
//(全等(大小写不敏感)∥ `*.` 前缀 = 子域通配)——收束面非沙箱;缺省空 = 不设限。
|
|
89
|
+
browser: { allowDomains: [] },
|
|
85
90
|
traces: {
|
|
86
91
|
enabled: false, // §18.6 D-TR6 修订(2026-09-05 用户裁定——发布隐私:"不希望用户那边也采集"):轨迹存档默认 OFF——新用户零采集;本地调试分析可显式开(~/.thincoder/config.json traces.enabled:true)
|
|
87
92
|
retentionHours: 24, // D-TR10:轨迹文件保留小时数——CLI 启动时删除超过该时长的文件(默认 24h)
|
|
88
93
|
},
|
|
89
|
-
// 诊断开关(用户面 ① 配置键——CONFIG.md §6.2 名录):`heapWatch` 默认开(设 false
|
|
94
|
+
// 诊断开关(用户面 ① 配置键——CONFIG.md §6.2 名录):`heapWatch` 默认开(设 false 关闭)——自愈分支
|
|
90
95
|
// 遥测;`heapSnapshot` 默认关——采集 opt-in(堆转储敏感度 ≥ traces ⇒ 对齐发布隐私先例 D-TR6;
|
|
91
96
|
// 2026-09-30 采集收网批;桌面端运行期热读 ∥ CLI 本进程下次启动)
|
|
92
97
|
diagnostics: { heapWatch: true, heapSnapshot: false },
|
|
@@ -99,11 +104,34 @@ import { specForModel, providerSpec, specMatch, assistantToolCallMessage } from
|
|
|
99
104
|
export { specForModel, providerSpec, specMatch, assistantToolCallMessage }
|
|
100
105
|
|
|
101
106
|
|
|
102
|
-
// Window utilization threshold: compacts at 60%
|
|
103
|
-
//
|
|
104
|
-
//
|
|
107
|
+
// Window utilization threshold: compacts at 60% of the USABLE window (window − output
|
|
108
|
+
// reservation — providers require input + max_tokens ≤ window; 2026-10-06 user ruling:
|
|
109
|
+
// the reservation folds into the budget so compaction fires first, never a provider
|
|
110
|
+
// rejection), the remaining 40% stays as headroom for injected context (directory tree,
|
|
111
|
+
// git context, outline, project instructions, memory/doc search results — 30-50K tokens
|
|
112
|
+
// per turn).
|
|
105
113
|
const COMPACT_RATIO = 0.6
|
|
106
114
|
|
|
115
|
+
/** Output reservation the request counts against the window: explicit `provider.maxTokens`
|
|
116
|
+
* (the value actually sent — `provider/core.mjs`) when set, else the spec `maxOutput`, else 0. */
|
|
117
|
+
export function outputReserve(provider, spec) {
|
|
118
|
+
const explicit = provider?.maxTokens
|
|
119
|
+
if (Number.isFinite(explicit) && explicit > 0) return explicit
|
|
120
|
+
return spec?.maxOutput ?? 0
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Usable input window = window − output reservation. Degenerate guard: a reservation
|
|
124
|
+
* ≥ the window is non-physical (no input could ever fit — e.g. the doubao spec
|
|
125
|
+
* `maxOutput` 524_288 > `context` 256_000 ∥ the test harness's 1K window vs the default
|
|
126
|
+
* spec); collapsing the budget to 0 would be instant `context_limit` death — the very
|
|
127
|
+
* failure the reservation fold exists to prevent. Fall back to half the window.
|
|
128
|
+
* (2026-10-06——收口核验捕获:advisor-loop-split B5a 红。) */
|
|
129
|
+
export function usableWindow(context, reserve) {
|
|
130
|
+
const ctx = context ?? 0
|
|
131
|
+
const usable = ctx - reserve
|
|
132
|
+
return usable > 0 ? usable : Math.floor(ctx / 2)
|
|
133
|
+
}
|
|
134
|
+
|
|
107
135
|
/** Derive compaction threshold; explicit is the value explicitly set in config file (takes priority), otherwise auto-computed from model.
|
|
108
136
|
* Second param accepts EITHER a model name string (pure spec lookup — legacy caller:
|
|
109
137
|
* first-run wizard) OR a provider object (providerSpec — the providers[].context
|
|
@@ -112,7 +140,8 @@ export function resolveCompactThreshold(explicit, modelOrProvider) {
|
|
|
112
140
|
if (explicit != null) return { value: explicit, auto: false }
|
|
113
141
|
const provider = typeof modelOrProvider === "string" ? { model: modelOrProvider } : (modelOrProvider ?? {})
|
|
114
142
|
const spec = providerSpec(provider)
|
|
115
|
-
const
|
|
143
|
+
const usable = usableWindow(spec.context, outputReserve(provider, spec))
|
|
144
|
+
const value = Math.floor(usable * COMPACT_RATIO)
|
|
116
145
|
return { value, auto: true }
|
|
117
146
|
}
|
|
118
147
|
|
|
@@ -191,7 +220,7 @@ function warnConsultModelsFiltered(dropped, path) {
|
|
|
191
220
|
/** #861(AGENT-LOOP-SUBAGENT.md §6.7.1):subagent 模型覆盖加载期清洗——单源纯函数(核 loadConfig ∥ VSC 端壳 raw 读点同消费);非法形态不 throw(`subagentModel` 非法 ⇒ null、`subagentModels` 非对象 ⇒ {}、值非法 ⇒ 剔除该键)。 */
|
|
192
221
|
export function sanitizeSubagentModel(v) {
|
|
193
222
|
const ok = typeof v === "string" && v.trim()
|
|
194
|
-
const dropped = ok || v == null ? [] : [`agent.subagentModel must be a non-empty string ("provider:model" |
|
|
223
|
+
const dropped = ok || v == null ? [] : [`agent.subagentModel must be a non-empty string ("provider:model" | model) — got ${Array.isArray(v) ? "array" : typeof v} — ignored (inherits the parent provider)`]
|
|
195
224
|
return { value: ok ? v : null, dropped }
|
|
196
225
|
}
|
|
197
226
|
export function sanitizeSubagentModels(v) {
|
|
@@ -200,7 +229,7 @@ export function sanitizeSubagentModels(v) {
|
|
|
200
229
|
const value = {}, dropped = []
|
|
201
230
|
for (const [role, val] of Object.entries(v)) {
|
|
202
231
|
if (typeof val === "string" && val.trim()) { value[role] = val; continue }
|
|
203
|
-
dropped.push(`agent.subagentModels.${role} must be a non-empty string ("provider:model" |
|
|
232
|
+
dropped.push(`agent.subagentModels.${role} must be a non-empty string ("provider:model" | model) — got ${Array.isArray(val) ? "array" : typeof val} — dropped`)
|
|
204
233
|
}
|
|
205
234
|
return { value, dropped }
|
|
206
235
|
}
|
|
@@ -210,12 +239,12 @@ function warnSubagentModelsFiltered(dropped, path) {
|
|
|
210
239
|
if (warnedSubagentModels || dropped.length === 0) return
|
|
211
240
|
warnedSubagentModels = true
|
|
212
241
|
console.warn(`[config] agent.subagentModel/subagentModels: ${dropped.length} invalid entr${dropped.length === 1 ? "y ignored" : "ies ignored"} (filtered — startup continues; no crash):\n` +
|
|
213
|
-
`${dropped.map((d) => ` - ${d}`).join("\n")}\n Fix: clean it in ${path} — "provider:model" |
|
|
242
|
+
`${dropped.map((d) => ` - ${d}`).join("\n")}\n Fix: clean it in ${path} — "provider:model" | model.`)
|
|
214
243
|
}
|
|
215
244
|
|
|
216
245
|
/**
|
|
217
246
|
* Find provider by name in providers[].
|
|
218
|
-
* Throws if name is non-empty but not found — a typo in
|
|
247
|
+
* Throws if name is non-empty but not found — a typo in the caller-supplied name (e.g. `defaultModel`) silently falling to the first provider would use the wrong key on the wrong endpoint.
|
|
219
248
|
* Returns the first provider when name is empty.
|
|
220
249
|
*/
|
|
221
250
|
export function findProvider(providers, name) {
|
|
@@ -223,24 +252,35 @@ export function findProvider(providers, name) {
|
|
|
223
252
|
const found = providers.find((p) => p.name === name)
|
|
224
253
|
if (found) return found
|
|
225
254
|
const available = providers.map((p) => p.name).join(", ") || "(empty)"
|
|
226
|
-
throw new Error(`
|
|
255
|
+
throw new Error(`provider "${name}" not in providers list (available: ${available}); check for a typo in: ${configPath}`)
|
|
227
256
|
}
|
|
228
|
-
return providers[0] ?? { name: "default", baseURL: ""
|
|
257
|
+
return providers[0] ?? { name: "default", baseURL: "" }
|
|
229
258
|
}
|
|
230
259
|
|
|
231
|
-
/** Normalize proxy config to { uri, web
|
|
260
|
+
/** Normalize proxy config to { uri, web } or undefined (uri/url both accepted; invalid types dropped) */
|
|
232
261
|
export function normalizeProxy(proxy) {
|
|
233
|
-
if (typeof proxy === "string") return proxy ? { uri: proxy, web: true
|
|
262
|
+
if (typeof proxy === "string") return proxy ? { uri: proxy, web: true } : undefined
|
|
234
263
|
if (!proxy || typeof proxy !== "object" || Array.isArray(proxy)) return undefined
|
|
235
264
|
const uri = proxy.uri || proxy.url || ""
|
|
236
265
|
if (typeof uri !== "string" || !uri) return undefined
|
|
237
|
-
return { uri, web: proxy.web !== false
|
|
266
|
+
return { uri, web: proxy.web !== false }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** 团队登录段加载归一(B1 批——`docs/core/design/TEAM.md` §2.1 ∥ CONFIG.md §6.4):非对象 ⇒ null(软失败
|
|
270
|
+
* 不阻启动——沿 proxy 段先例);键级 = trim 非空字符串(非串 ∥ 空 ⇒ null);`member` 浅形 `{ username, name }`。
|
|
271
|
+
* **不入 `DEFAULTS`**(settings 工具面零涉——agent 不可写;`team.token` 经读面到达 = 敏感段词表遮罩)。 */
|
|
272
|
+
export function normalizeTeamSection(value) {
|
|
273
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return null
|
|
274
|
+
const str = (v) => (typeof v === "string" && v.trim() !== "" ? v.trim() : null)
|
|
275
|
+
const raw = value.member
|
|
276
|
+
const member = raw !== null && typeof raw === "object" && !Array.isArray(raw) ? { username: str(raw.username), name: str(raw.name) } : null
|
|
277
|
+
return { server: str(value.server), member, label: str(value.label), token: str(value.token) }
|
|
238
278
|
}
|
|
239
279
|
|
|
240
280
|
/**
|
|
241
281
|
* Load configuration.
|
|
242
282
|
* No env-var overrides — config.json is the single source of truth
|
|
243
|
-
* (API keys, baseURL,
|
|
283
|
+
* (API keys, baseURL, defaultModel all come from the file).
|
|
244
284
|
*/
|
|
245
285
|
/** Keep only { header: "string value" } pairs from a provider's headers field — anything
|
|
246
286
|
* else (null, arrays, nested objects) is dropped so it can never reach a fetch call.
|
|
@@ -289,6 +329,7 @@ export function loadConfig() {
|
|
|
289
329
|
agent: { ...DEFAULTS.agent, ...config.agent },
|
|
290
330
|
memory: { ...DEFAULTS.memory, ...config.memory },
|
|
291
331
|
embedding: { ...DEFAULTS.embedding, ...config.embedding },
|
|
332
|
+
browser: { ...DEFAULTS.browser, ...config.browser },
|
|
292
333
|
traces: { ...DEFAULTS.traces, ...config.traces },
|
|
293
334
|
diagnostics: { ...DEFAULTS.diagnostics },
|
|
294
335
|
}
|
|
@@ -306,10 +347,9 @@ export function loadConfig() {
|
|
|
306
347
|
const _diagRaw = config.diagnostics && typeof config.diagnostics === "object" && !Array.isArray(config.diagnostics) ? config.diagnostics : {}
|
|
307
348
|
for (const key of ["heapWatch", "heapSnapshot"]) if (typeof _diagRaw[key] === "boolean") merged.diagnostics[key] = _diagRaw[key]
|
|
308
349
|
|
|
309
|
-
// providers[].model
|
|
310
|
-
//
|
|
350
|
+
// providers[].model 一律删除(2026-10-09 清除批——渠道单值模型退场:渠道不携模型,不落任何
|
|
351
|
+
// 回退;模型身份唯二 = 顶层 defaultModel 复合串 ∥ 会话槽选定——PROVIDER.md §6.16 M3)。
|
|
311
352
|
for (const p of merged.providers) {
|
|
312
|
-
if (typeof p.model === "string" && p.model.trim()) continue
|
|
313
353
|
if (p.model !== undefined) delete p.model
|
|
314
354
|
}
|
|
315
355
|
|
|
@@ -351,10 +391,13 @@ export function loadConfig() {
|
|
|
351
391
|
if (p.baseURL) p.baseURL = p.baseURL.replace(/\/+$/, "")
|
|
352
392
|
}
|
|
353
393
|
|
|
354
|
-
// Normalize proxy: string → { uri, web:true
|
|
394
|
+
// Normalize proxy: string → { uri, web:true }; object 补默认值;非法类型丢弃。
|
|
355
395
|
// 保证 agent.config.proxy 永远是规范形态或 undefined
|
|
356
396
|
merged.proxy = normalizeProxy(merged.proxy)
|
|
357
397
|
|
|
398
|
+
// 团队登录段(B1 批——TEAM.md §2.1):加载归一(形不符 ⇒ null——软失败不阻启动);不入 DEFAULTS
|
|
399
|
+
merged.team = normalizeTeamSection(merged.team)
|
|
400
|
+
|
|
358
401
|
// Runtime provider = 统一解析(#841——resolveProviderPlan 单源:回退序 + 三态;PROVIDER.md §6.22)。
|
|
359
402
|
// 不可运行(渠表空 ∥ 全表无 key)⇒ {} + providerInvalidReason(D-S1 处置不 throw——make-agent 打
|
|
360
403
|
// _providerInvalid 标记 → TUI 首帧弹选择 / headless 报可读错误;同 2026-09-02 Q1 语义——不复用
|
|
@@ -387,7 +430,7 @@ export function loadConfig() {
|
|
|
387
430
|
/**
|
|
388
431
|
* MCP.md §5 D-3 (2026-09-01): re-read config.json and replace ONLY the agent's mcp section
|
|
389
432
|
* — the agent 代配 closed loop (agent edits config.json with its edit tool, /mcp picks it
|
|
390
|
-
* up). Never touches other config sections (providers/
|
|
433
|
+
* up). Never touches other config sections (providers/defaultModel stay as loaded).
|
|
391
434
|
*
|
|
392
435
|
* Malformed disk config → memory state kept, { ok:false, error } returned (the /mcp menu
|
|
393
436
|
* shows "⚠ disk config unreadable"). Never throws.
|
package/context-degrade.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* context-degrade.mjs — 确定性降级面(无 LLM 的有损收缩 · 自 `context.mjs` 迁出 · 2026-10-01 core 拆分批 #755 ∥ #786)。
|
|
3
3
|
* 内容 = `pruneStub` ∥ `pruneStaleToolOutputs`(F-CC3 陈旧工具输出清理)∥ `OVERSIZE_CONTENT_LIMIT` ∥
|
|
4
4
|
* `shrinkOversized`(splitHistory 无中间段时的单条超限收缩)——迁出块逐字。
|
|
5
|
-
* 原档 `context.mjs` 经 `export { … } from` 转口保名(消费面 /
|
|
5
|
+
* 原档 `context.mjs` 经 `export { … } from` 转口保名(消费面 / 单元测试档 import 面零改);
|
|
6
6
|
* 跨档被引的原私有面(`shrinkOversized`)补 `export` 关键字。
|
|
7
7
|
*/
|
|
8
8
|
import { collectStaleToolOutputs } from "./token-window.mjs"
|
package/context-echo.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* context-echo.mjs — 回声安全面(D-CC18 ∥ D-CC19 恢复面归并 · 自 `context.mjs` 迁出 · 2026-10-01 core 拆分批 #755 ∥ #786)。
|
|
3
3
|
* 内容 = `COMPACTION_PLACEHOLDER` ∥ 前缀器族(`prefixContent` / `withPlaceholderPrefix`)∥ 回声对归并
|
|
4
4
|
* (`isAssistantEchoPair` / `contentTextOf` / `absorbEchoContent` / `mergeAdjacentAssistantEchoes`)——迁出块逐字。
|
|
5
|
-
* 原档 `context.mjs` 经 `export { … } from` 转口保名(消费面 /
|
|
5
|
+
* 原档 `context.mjs` 经 `export { … } from` 转口保名(消费面 / 单元测试档 import 面零改);
|
|
6
6
|
* 跨档被引的原私有面(`COMPACTION_PLACEHOLDER` ∥ `withPlaceholderPrefix`)补 `export` 关键字。
|
|
7
7
|
*/
|
|
8
8
|
|
package/context-push.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* context-push.mjs — 消息双线写入单点(写缝面 · 自 `context.mjs` 迁出 · 2026-10-01 core 拆分批 #755 ∥ #786)。
|
|
3
3
|
* `pushReal` = 真实消息写缝(机器线 `agent.history` + 人读线 `agent._fullHistory` 同源双写);
|
|
4
4
|
* `pushRecord` = 人读线留档记录单点(机器线零触)。迁出块逐字;原档 `context.mjs` 经
|
|
5
|
-
* `export { … } from` 转口保名(消费面 /
|
|
5
|
+
* `export { … } from` 转口保名(消费面 / 单元测试档 import 面零改)。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -40,7 +40,7 @@ export function pushReal(agent, msg) {
|
|
|
40
40
|
* pushRecord — 人读线留档记录单点(消化生命周期面 · #726;形 ∥ 写缝 ∥ 判据单源 = SESSION.md §6.26)。
|
|
41
41
|
* `pushReal` 双胞(直复用——零算法副本):`ts` 打点 ∥ 记录存储追加(`_recordStore?.append`——§6.14)∥
|
|
42
42
|
* 尾窗驱逐三面同源;**机器线零触** = `history` 以一次性弃数组承接 ⇒ 记录不入 `agent.history` /
|
|
43
|
-
* `contextHistory`(不喂模型)。未绑定(`_recordStore` 缺)⇒ 人读线追加照常 ∥
|
|
43
|
+
* `contextHistory`(不喂模型)。未绑定(`_recordStore` 缺)⇒ 人读线追加照常 ∥ 存储分支空转(零抛——模式 F
|
|
44
44
|
* 零回归);载体缺位(无活跃会话)⇒ 零动作(零抛——日志归端侧)。
|
|
45
45
|
*/
|
|
46
46
|
export function pushRecord(agent, record) {
|
package/context.mjs
CHANGED
|
@@ -250,7 +250,7 @@ export function compressFallback(agent) {
|
|
|
250
250
|
|
|
251
251
|
// ─── 迁出面(import 面保持:TUI / verify-compress / VSC 对拍经本档取——先例 = 下方 explore-distill 再导出)──
|
|
252
252
|
export { estimateTokens }
|
|
253
|
-
// ─── 拆分转口(2026-10-01 拆分批 · #755 ∥ #786):写缝 ∥ 回声 ∥ 降级三面外提——消费面 /
|
|
253
|
+
// ─── 拆分转口(2026-10-01 拆分批 · #755 ∥ #786):写缝 ∥ 回声 ∥ 降级三面外提——消费面 / 单元测试档 import 面零改 ───
|
|
254
254
|
export { pushReal, pushRecord } from "./context-push.mjs"
|
|
255
255
|
export { isAssistantEchoPair, mergeAdjacentAssistantEchoes } from "./context-echo.mjs"
|
|
256
256
|
export { pruneStaleToolOutputs } from "./context-degrade.mjs"
|
package/conventions.mjs
CHANGED
|
@@ -187,6 +187,6 @@ export function isStatePath(p, conv) {
|
|
|
187
187
|
|
|
188
188
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
189
189
|
// 迁出面(2026-10-01 拆分批 · #755 ∥ #786):声明装载面外提 `declaration.mjs`——本档经
|
|
190
|
-
// 转口保名(消费面 /
|
|
190
|
+
// 转口保名(消费面 / 单元测试档 import 面零改;`DEFAULT_DECLARATION` 另供本档分类裁判缺省取用)。
|
|
191
191
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
192
|
-
export { loadProjectDeclaration, DEFAULT_DECLARATION, isExcludedRelPath, declaredPublicRoots, clearDeclarationCache } from "./declaration.mjs"
|
|
192
|
+
export { loadProjectDeclaration, declarationForTarget, DEFAULT_DECLARATION, isExcludedRelPath, declaredPublicRoots, clearDeclarationCache } from "./declaration.mjs"
|
package/declaration.mjs
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* · 自 `conventions.mjs` 迁出 · 2026-10-01 core 拆分批 #755 ∥ #786。
|
|
4
4
|
* 投影单源 = `manifest.mjs`(ONE reader / ONE root resolution——`readManifest` + `manifestFilePath`;
|
|
5
5
|
* name / shape / defaults = schema 面 `docs/core/design/MANIFEST.md` §2.2)。迁出块逐字;
|
|
6
|
-
* 原档 `conventions.mjs` 经 `export { … } from` 转口保名(消费面 /
|
|
6
|
+
* 原档 `conventions.mjs` 经 `export { … } from` 转口保名(消费面 / 单元测试档 import 面零改)。
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, statSync } from "node:fs"
|
|
9
9
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path"
|
|
10
|
-
import { DEFAULT_MANIFEST, manifestFilePath, readManifest } from "./manifest.mjs"
|
|
10
|
+
import { DEFAULT_MANIFEST, manifestFilePath, owningProject, readManifest } from "./manifest.mjs"
|
|
11
11
|
import { logEvent } from "./log.mjs"
|
|
12
12
|
|
|
13
13
|
/** Retired declaration carrier (project-root relative). Its EXISTENCE is checked, its
|
|
@@ -216,3 +216,19 @@ export function loadProjectDeclaration(cwd) {
|
|
|
216
216
|
_cache.set(file, decl)
|
|
217
217
|
return decl
|
|
218
218
|
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 辖域单点(F11 · 2026-10-08 · #1104 · PORTABILITY §3.2 行 8 / §3.9 / D20):目标**绝对路径**
|
|
222
|
+
* ⇒ 沿祖先链取**最近**带档目录(`owningProject` 归属形——nearest wins ∕ 纯 fs)⇒ 该项目的声明
|
|
223
|
+
* 对象(`loadProjectDeclaration` 本体零改——按档路径缓存);祖先链无档 ⇒ `null` = **出辖**
|
|
224
|
+
* (不在任何 manifest 树内——父侧门不判不拦)。
|
|
225
|
+
* **发现梯(`discoverProjects`)不参与**——无候选 ∥ 歧义均出辖(不在无档面凭空落脚)。
|
|
226
|
+
* 相对形由消费面按会话 cwd 先解析(判据 = 目标所属项目,非会话 cwd)。
|
|
227
|
+
* @param {string} targetAbs 目标绝对路径(目录 / 文件)
|
|
228
|
+
* @returns {Readonly<object>|null} 所属项目的声明对象 / null = 出辖
|
|
229
|
+
*/
|
|
230
|
+
export function declarationForTarget(targetAbs) {
|
|
231
|
+
const root = owningProject(targetAbs)
|
|
232
|
+
if (!root) return null
|
|
233
|
+
return loadProjectDeclaration(root)
|
|
234
|
+
}
|
package/file-links.mjs
CHANGED
|
@@ -22,7 +22,7 @@ export const MAX_LINKS = 50
|
|
|
22
22
|
/**
|
|
23
23
|
* 工具结果文本 ⇒ 验存文件链接(去重 + 封顶)。`cwd` = 项目根(相对 token 的解析基);缺 ∕ 空 ⇒ 相对 token
|
|
24
24
|
* 一律不检(禁假链接);绝对 token 与 cwd 无关,照检。`probe` = 盘面探针注入缝(`{ existsSync(p), statSync(p) }`——
|
|
25
|
-
* `node:fs` 同名面;桌面端侧注入)。零文本(`""` ∕
|
|
25
|
+
* `node:fs` 同名面;桌面端侧注入)。零文本(`""` ∕ 非串)先于探针校验早退(零判据 —— 探针不检)。
|
|
26
26
|
*/
|
|
27
27
|
export function extractFileLinks(cwd, text, probe) {
|
|
28
28
|
if (typeof text !== "string" || text === "") return []
|
package/file-refs.mjs
CHANGED
|
@@ -26,7 +26,7 @@ function probeOf(probe) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** Scan text for @path references and replace with inline file content.
|
|
29
|
-
* 零文本(非串 ∥
|
|
29
|
+
* 零文本(非串 ∥ 空串)先于探针校验早退(零判据——探针不检,同序沿核 `file-links.mjs:28`)。 */
|
|
30
30
|
export function injectAtRefs(text, cwd, probe) {
|
|
31
31
|
if (typeof text !== "string" || text === "") return text
|
|
32
32
|
const io = probeOf(probe)
|
package/history-window.mjs
CHANGED
|
@@ -112,7 +112,7 @@ function pairToolEntries(history) {
|
|
|
112
112
|
* `opts.records === true`(opt-in —— 留档批 · #719)⇒ `digest` ∕ `subagent` 两型
|
|
113
113
|
* 记录原样入窗(`{ ...record, idx }`,字段零改名)并占条目位(游标按条目推进);
|
|
114
114
|
* 记录不参与 turn ∕ 可见性谓词(`visible` 面语义不动)。**默认关** ⇒ 输出与改前
|
|
115
|
-
*
|
|
115
|
+
* 逐字等价(负控分支);缺 `opts` ∕ 非 `true` ⇒ 同默认径。
|
|
116
116
|
*/
|
|
117
117
|
export function historyWindow(history, before, pageSize = HISTORY_PAGE_SIZE, opts = {}) {
|
|
118
118
|
const total = Array.isArray(history) ? history.length : 0
|
package/i18n.mjs
CHANGED
|
@@ -70,6 +70,8 @@ export const CORE_MESSAGES = Object.freeze({
|
|
|
70
70
|
"status.stopped": { en: "[stopped]", zh: "[已停止]" },
|
|
71
71
|
"status.thinking": { en: "Thinking", zh: "思考中" },
|
|
72
72
|
"status.turn": { en: "turn ${n}/${m}", zh: "轮次 ${n}/${m}" },
|
|
73
|
+
// ── 团队登录态(登录面补全批 · 2026-10-10 · 台账 #1231——TEAM.md §2.6:已失效段 ∥ 面板失效行;图形端经核字典投影直取)──
|
|
74
|
+
"status.team.invalid": { en: "Session expired — log in again", zh: "已失效——重新登录" },
|
|
73
75
|
"tool.running": { en: "running…", zh: "执行中…" },
|
|
74
76
|
"tool.done": { en: "done", zh: "完成" },
|
|
75
77
|
// ── 台账(ledger)· F-LX3 变体键库首跑提示(批 2026-10-05-ledger-variant-db-notice · 台账 #935 · LEDGER.md §12.2 逐字;消费面 = CLI TUI 启动行)
|
package/ledger-cmd.mjs
CHANGED
|
@@ -11,13 +11,26 @@
|
|
|
11
11
|
* 消费侧静态 import 会破 W8 契约②)。
|
|
12
12
|
*/
|
|
13
13
|
import { resolve } from "node:path"
|
|
14
|
-
import { ALLOWED_MIGRATIONS, nowIso, openLedger, PENDING_STATUSES } from "./ledger-db.mjs"
|
|
14
|
+
import { ALLOWED_MIGRATIONS, nowIso, openLedger, PENDING_STATUSES, AGING_DAYS } from "./ledger-db.mjs"
|
|
15
15
|
import { resolveProjectRoot } from "./manifest.mjs"
|
|
16
16
|
import { resolveDeclaredRef } from "./declaration.mjs"
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
20
|
-
|
|
18
|
+
/** 逐行老化判真(LEDGER.md §13.4 · 2026-10-07 批 ledger-tool):未决四态(`PENDING_STATUSES`)∧
|
|
19
|
+
* 行龄 > `AGING_DAYS` 天;行龄源 = `updated_at ?? created_at`(§5 同源);时间戳缺 / 不可解析
|
|
20
|
+
* ⇒ `false`(零假阳降级——与 `buildScan` 行龄口径同源)。 */
|
|
21
|
+
function isRowAged(row, now) {
|
|
22
|
+
if (!PENDING_STATUSES.includes(row.status)) return false
|
|
23
|
+
const t = row.updated_at ?? row.created_at
|
|
24
|
+
if (t == null) return false
|
|
25
|
+
const ms = Date.parse(String(t))
|
|
26
|
+
return Number.isFinite(ms) && (now - ms) / 86400000 > AGING_DAYS
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 查询(只读,全角色):SELECT 行集(status / kind / board / trigger 等值过滤并取,缺省 = 全部行;id 升序)。
|
|
30
|
+
* 库不在 = 空账。只读开库(readOnly 句柄 + 零 DDL/ALTER——read-data-interface 批 FR2③);库档非台账库 ⇒ 空账。
|
|
31
|
+
* 逐行携计算字段 `aged: boolean`(§13.4——不入库、不入导出白名单 DATA_COLUMNS);`trigger` 过滤
|
|
32
|
+
* 仅三枚举等值(`NULL` 不过滤——批面未列,§13.2);`now` = 注入缝(确定性用例)。 */
|
|
33
|
+
export function ledgerQuery({ cwd, status = null, kind = null, board = null, trigger = null, now = Date.now() } = {}) {
|
|
21
34
|
const db = openLedger(cwd, { readOnly: true })
|
|
22
35
|
if (!db) return []
|
|
23
36
|
try {
|
|
@@ -25,7 +38,9 @@ export function ledgerQuery({ cwd, status = null, kind = null, board = null } =
|
|
|
25
38
|
if (status) { where.push("status = ?"); args.push(status) }
|
|
26
39
|
if (kind) { where.push("kind = ?"); args.push(kind) }
|
|
27
40
|
if (board) { where.push("board = ?"); args.push(board) }
|
|
28
|
-
|
|
41
|
+
if (trigger) { where.push("trigger = ?"); args.push(trigger) }
|
|
42
|
+
const rows = db.prepare(`SELECT * FROM items${where.length ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY id`).all(...args)
|
|
43
|
+
return rows.map((r) => ({ ...r, aged: isRowAged(r, now) }))
|
|
29
44
|
} finally { db.close() }
|
|
30
45
|
}
|
|
31
46
|
|
|
@@ -75,9 +90,11 @@ export function ledgerAdd({ cwd, row }) {
|
|
|
75
90
|
}
|
|
76
91
|
|
|
77
92
|
/** executor 目标值计算(设计档 docs/core/design/LEDGER.md §3.1——判位在 ledgerUpdate 体内:迁移表判 → 本语义 → 写门 → UPDATE)。
|
|
78
|
-
* 优先级 = patch 显式 >
|
|
93
|
+
* 优先级 = patch 显式 > 自动语义(进边 = executorSessionId / 出边 = NULL)> 行现值兜底 > NULL。
|
|
94
|
+
* 进边集(2026-10-07 批 ledger-tool 扩面——§13.3 / KD-LT1):`待讨论 → 待设计`(批点火边)∥
|
|
95
|
+
* `待设计 → 在途`(实施入边)——两枚同式。 */
|
|
79
96
|
function resolveExecutorTarget(row, to, patch, executorSessionId) {
|
|
80
|
-
if (row.status === "待设计" && to === "在途") {
|
|
97
|
+
if ((row.status === "待讨论" && to === "待设计") || (row.status === "待设计" && to === "在途")) {
|
|
81
98
|
return patch.executor ?? executorSessionId ?? row.executor ?? null
|
|
82
99
|
}
|
|
83
100
|
if (row.status === "在途" && (to === "待核销" || to === "已废弃")) {
|
|
@@ -86,9 +103,29 @@ function resolveExecutorTarget(row, to, patch, executorSessionId) {
|
|
|
86
103
|
return patch.executor ?? row.executor ?? null
|
|
87
104
|
}
|
|
88
105
|
|
|
106
|
+
/** `evidence` 值计算(设计档 LEDGER.md §13.9——update / close 两函数同源一处落):追加缺省(旧值非空 ⇒
|
|
107
|
+
* `旧 + "|" + 新`——「|」两侧零空格直接相接;旧空(`null` ∥ 空串 ∥ 全空白)⇒ 直写,零分隔符残留);
|
|
108
|
+
* `evidenceReplace === true` 与值同传 ⇒ 整段替换(旧文零保留);`evidence` 缺 / `null` ≡ 略去(零变)。
|
|
109
|
+
* 拒面①(旗独传——无新文可算)判于本阶段(close 侧先于追认门);核内旗判只认 `=== true`(类型严判在
|
|
110
|
+
* 工具层)。两值逐字落行(不裁 / 不判内容)。 */
|
|
111
|
+
function evidenceValue(fnName, oldValue, evidence, evidenceReplace) {
|
|
112
|
+
if (evidenceReplace === true && evidence == null) throw new Error(`${fnName}:evidenceReplace 须与 evidence 同传(缺新文)`)
|
|
113
|
+
if (evidence == null) return oldValue
|
|
114
|
+
const old = oldValue == null || String(oldValue).trim() === "" ? "" : String(oldValue)
|
|
115
|
+
return evidenceReplace === true || old === "" ? evidence : `${old}|${evidence}`
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** 拒面②(新值全空白——追加 ∥ 覆盖两径同判)判点(§13.9 判序):update 随值计算同拍 ∥ close 落
|
|
119
|
+
* 追认门之后;`evidence` 未传(`null` / 略去)⇒ 不判。 */
|
|
120
|
+
function assertEvidenceValue(fnName, evidence) {
|
|
121
|
+
if (evidence != null && String(evidence).trim() === "") throw new Error(`${fnName}:evidence 为空(非空字符串)`)
|
|
122
|
+
}
|
|
123
|
+
|
|
89
124
|
/** 更新(写命令,仅主 agent):UPDATE——状态迁移前判允许迁移表(不在表内 → 拒,行不变)。
|
|
90
125
|
* executor 目标值随同一 UPDATE 落列(LEDGER.md §3.1);executorSessionId = 调用会话(函数参数注入——K-LX1
|
|
91
|
-
* 纯函数可测;工具层动态 import getSessionId() 供值,零新静态边)。
|
|
126
|
+
* 纯函数可测;工具层动态 import getSessionId() 供值,零新静态边)。
|
|
127
|
+
* `evidence` 传值 = 追加缺省、`patch.evidenceReplace` = 布尔覆盖旗(§13.9——值计算 / 两拒面 = `evidenceValue`
|
|
128
|
+
* / `assertEvidenceValue`;判位 = 本语义段,随 executor 目标值同拍)。 */
|
|
92
129
|
export function ledgerUpdate({ cwd, id, patch, executorSessionId }) {
|
|
93
130
|
const db = openLedger(cwd, { create: true })
|
|
94
131
|
try {
|
|
@@ -98,20 +135,27 @@ export function ledgerUpdate({ cwd, id, patch, executorSessionId }) {
|
|
|
98
135
|
if (to !== row.status && !(ALLOWED_MIGRATIONS[row.status] ?? []).includes(to)) {
|
|
99
136
|
throw new Error(`ledgerUpdate:迁移 ${row.status} → ${to} 不在允许迁移表`)
|
|
100
137
|
}
|
|
101
|
-
|
|
102
|
-
assertTaskBookGate(cwd, "ledgerUpdate", to, nextTaskBook) // 判序:迁移表判 → 本门 → UPDATE
|
|
138
|
+
// 判序(§13.9):迁移表判 → 本语义(executor 目标值 + evidence 值计算——含两拒面)→ 写门 → UPDATE
|
|
103
139
|
const nextExecutor = resolveExecutorTarget(row, to, patch, executorSessionId)
|
|
140
|
+
const nextEvidence = evidenceValue("ledgerUpdate", row.evidence, patch.evidence, patch.evidenceReplace)
|
|
141
|
+
assertEvidenceValue("ledgerUpdate", patch.evidence)
|
|
142
|
+
const nextTaskBook = patch.task_book ?? row.task_book
|
|
143
|
+
assertTaskBookGate(cwd, "ledgerUpdate", to, nextTaskBook) // → 写门 → UPDATE
|
|
104
144
|
const now = nowIso()
|
|
105
145
|
db.prepare(`UPDATE items SET status = ?, title = ?, board = ?, req_doc = ?, task_book = ?, evidence = ?, trigger = ?, executor = ?, updated_at = ? WHERE id = ?`)
|
|
106
|
-
.run(to, patch.title ?? row.title, patch.board ?? row.board, patch.req_doc ?? row.req_doc, nextTaskBook,
|
|
146
|
+
.run(to, patch.title ?? row.title, patch.board ?? row.board, patch.req_doc ?? row.req_doc, nextTaskBook, nextEvidence, patch.trigger ?? row.trigger, nextExecutor, now, id)
|
|
107
147
|
return { id }
|
|
108
148
|
} finally { db.close() }
|
|
109
149
|
}
|
|
110
150
|
|
|
111
151
|
/** 收口(写命令,仅主 agent):核销两源(勾销:待核销 → 已核销;追认核销:待讨论 / 待设计 → 已核销,
|
|
112
152
|
* 行 `evidence` 非空,缺 / 全空白 ⇒ 拒)/ 撤回(任意态 → 已废弃),事务包裹;归档 = 软删除。
|
|
113
|
-
*
|
|
114
|
-
|
|
153
|
+
* 可选 `evidence` 参:传值 = 追加缺省(§13.9——旧空直写 / 旧非空拼 `旧|新`)、`evidenceReplace: true`
|
|
154
|
+
* 同传 = 整段替换;勾销 / 追认 / 撤回三径同携(写值落行,内容不判);两拒面(旗独传 @值计算 ∥
|
|
155
|
+
* 新值空白 @追认门后)见 `evidenceValue` / `assertEvidenceValue`。
|
|
156
|
+
* 判序(同函数体同层,§13.9):目标集判 → 行取 → 源态判 → 值计算 → 追认门(原样——判**结果值**缺 /
|
|
157
|
+
* 全空白 ⇒ 拒,文案逐字不变)→ 拒面②(新值空白)→ UPDATE(LEDGER.md §3 收口两源)。 */
|
|
158
|
+
export function ledgerClose({ cwd, id, status, evidence, evidenceReplace }) {
|
|
115
159
|
if (status !== "已核销" && status !== "已废弃") throw new Error(`ledgerClose:目标态 ${status} ∉ {已核销, 已废弃}`)
|
|
116
160
|
const db = openLedger(cwd, { create: true })
|
|
117
161
|
try {
|
|
@@ -119,12 +163,14 @@ export function ledgerClose({ cwd, id, status }) {
|
|
|
119
163
|
try {
|
|
120
164
|
const row = db.prepare("SELECT * FROM items WHERE id = ?").get(id)
|
|
121
165
|
if (!row) throw new Error(`ledgerClose:行 ${id} 不存在`)
|
|
122
|
-
//
|
|
166
|
+
// 判序(§13.9):源态判(核销仅三源——在途不可跳)→ 值计算(拒面① 旗独传判于此)→ 追认口 `evidence` 门
|
|
123
167
|
if (status === "已核销" && !["待讨论", "待设计", "待核销"].includes(row.status)) throw new Error(`ledgerClose:核销仅限 待讨论 / 待设计 / 待核销(现态 ${row.status})`)
|
|
124
|
-
|
|
168
|
+
const nextEvidence = evidenceValue("ledgerClose", row.evidence, evidence, evidenceReplace)
|
|
169
|
+
if (status === "已核销" && ["待讨论", "待设计"].includes(row.status) && (nextEvidence == null || String(nextEvidence).trim() === "")) throw new Error(`ledgerClose:追认核销须带 evidence(现态 ${row.status})`)
|
|
170
|
+
assertEvidenceValue("ledgerClose", evidence) // 判序:拒面②(新值空白)落追认门之后 → UPDATE
|
|
125
171
|
const now = nowIso()
|
|
126
172
|
// 撤回(任意态 → 已废弃——含在途直撤)同步 executor = NULL(LEDGER.md §3.1 ④);核销两源(勾销 / 追认)executor 零触碰(非在途出边)
|
|
127
|
-
db.prepare("UPDATE items SET status = ?, closed_at = ?, updated_at = ?, executor = ? WHERE id = ?").run(status, now, now, status === "已废弃" ? null : row.executor, id)
|
|
173
|
+
db.prepare("UPDATE items SET status = ?, closed_at = ?, updated_at = ?, executor = ?, evidence = ? WHERE id = ?").run(status, now, now, status === "已废弃" ? null : row.executor, nextEvidence, id)
|
|
128
174
|
db.exec("COMMIT")
|
|
129
175
|
return { id, status }
|
|
130
176
|
} catch (e) { db.exec("ROLLBACK"); throw e }
|
package/ledger-db.mjs
CHANGED
|
@@ -55,6 +55,12 @@ export function ledgerDbPath(cwd) {
|
|
|
55
55
|
/** 未决四态(「活条目」口径 = 计数单源 WHERE 集;已核销 / 已废弃 = 归档态)。 */
|
|
56
56
|
export const PENDING_STATUSES = ["待讨论", "待设计", "在途", "待核销"]
|
|
57
57
|
|
|
58
|
+
/** 老化阈值(天——口径 = 需求档;消费面 = `buildScan`(展示,`days` 可覆盖)+ `ledgerQuery` 逐行
|
|
59
|
+
* `aged`(查询面,`now` 可注入))。2026-10-07 批 ledger-tool:自 `ledger.mjs` 迁入(枚举常量档单源
|
|
60
|
+
* ——`ledgerQuery` 计算面直引本档,消 `ledger-cmd ↔ ledger.mjs` 新环;`ledger.mjs` 同名 re-export
|
|
61
|
+
* 保公共面零变——LEDGER.md §13.4)。 */
|
|
62
|
+
export const AGING_DAYS = 30
|
|
63
|
+
|
|
58
64
|
/** 台账库结构版本(N2 标记**单源**——`PRAGMA user_version` 落标:写面开库 `v < 本值 ⇒ 置`;
|
|
59
65
|
* 读面如实回读(旧库未标 = 0);结构变更(新列 / 新表)同步升号——2026-10-03 read-data-interface 批)。 */
|
|
60
66
|
export const LEDGER_SCHEMA_VERSION = 1
|