@thincoder/core 0.10.3 → 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.
Files changed (172) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +1 -1
  3. package/advisor/citations.mjs +29 -3
  4. package/advisor/compaction.mjs +31 -7
  5. package/advisor/history.mjs +8 -6
  6. package/advisor/loop.mjs +29 -27
  7. package/advisor/messages.mjs +2 -1
  8. package/advisor/run.mjs +9 -5
  9. package/advisor/timeline.mjs +26 -0
  10. package/advisor.mjs +4 -2
  11. package/agent/assemble.mjs +1 -1
  12. package/agent/chat-call.mjs +1 -1
  13. package/agent/child-marks.mjs +2 -2
  14. package/agent/completion.mjs +3 -1
  15. package/agent/digest-account.mjs +189 -0
  16. package/agent/dispatch-gates.mjs +8 -0
  17. package/agent/dispatch.mjs +39 -18
  18. package/agent/family-tools.mjs +27 -16
  19. package/agent/helpers.mjs +12 -3
  20. package/agent/light-round.mjs +66 -0
  21. package/agent/post-turn.mjs +8 -4
  22. package/agent/record-results.mjs +4 -0
  23. package/agent/run-stages.mjs +32 -7
  24. package/agent/run-start.mjs +50 -3
  25. package/agent/setup-reminders.mjs +1 -1
  26. package/agent/setup.mjs +18 -3
  27. package/agent/subagent-event.mjs +1 -1
  28. package/agent/suspension.mjs +37 -12
  29. package/agent/timers.mjs +56 -5
  30. package/agent/turn-loop.mjs +30 -5
  31. package/agent/write-gate.mjs +20 -32
  32. package/agent-tools/advisor-async.mjs +15 -7
  33. package/agent-tools/advisor-settle.mjs +2 -2
  34. package/agent-tools/advisor.mjs +34 -33
  35. package/agent-tools/async-discard.mjs +34 -0
  36. package/agent-tools/async-settle.mjs +32 -7
  37. package/agent-tools/audit-block.mjs +1 -1
  38. package/agent-tools/bash-async.mjs +1 -1
  39. package/agent-tools/batch-lifecycle.mjs +59 -17
  40. package/agent-tools/batch-paths.mjs +24 -4
  41. package/agent-tools/batch-skeleton.mjs +59 -43
  42. package/agent-tools/batch.mjs +39 -18
  43. package/agent-tools/browser-async.mjs +198 -0
  44. package/agent-tools/checkpoint.mjs +1 -1
  45. package/agent-tools/design-slots.mjs +232 -0
  46. package/agent-tools/design-token.mjs +20 -0
  47. package/agent-tools/digest-budget.mjs +1 -1
  48. package/agent-tools/escalate-async.mjs +4 -2
  49. package/agent-tools/manifest.mjs +193 -0
  50. package/agent-tools/parent-channel.mjs +7 -4
  51. package/agent-tools/review-facts.mjs +143 -16
  52. package/agent-tools/settings.mjs +1 -1
  53. package/agent-tools/skill.mjs +1 -1
  54. package/agent-tools/subagent-actions-query.mjs +13 -6
  55. package/agent-tools/subagent-async.mjs +31 -15
  56. package/agent-tools/subagent-panel.mjs +2 -1
  57. package/agent-tools/subagent-scheduler.mjs +8 -5
  58. package/agent-tools/subagent-spawn.mjs +30 -56
  59. package/agent-tools/subagent.mjs +21 -8
  60. package/agent-tools/timer.mjs +186 -12
  61. package/agent-tools/zero-write-watch.mjs +77 -0
  62. package/agent-tools.mjs +2 -1
  63. package/agent.mjs +2 -2
  64. package/attachments.mjs +1 -1
  65. package/config-io.mjs +18 -13
  66. package/config-migrate.mjs +13 -29
  67. package/config-presets.mjs +33 -29
  68. package/config.mjs +68 -25
  69. package/context-degrade.mjs +1 -1
  70. package/context-echo.mjs +1 -1
  71. package/context-push.mjs +2 -2
  72. package/context.mjs +1 -1
  73. package/conventions.mjs +28 -8
  74. package/declaration.mjs +18 -2
  75. package/escape.mjs +1 -1
  76. package/file-links.mjs +1 -1
  77. package/file-refs.mjs +1 -1
  78. package/history-window.mjs +1 -1
  79. package/i18n.mjs +7 -0
  80. package/ledger-cmd.mjs +62 -16
  81. package/ledger-db.mjs +6 -0
  82. package/ledger-surface.mjs +49 -5
  83. package/ledger-tools.mjs +158 -87
  84. package/ledger-variant-notice.mjs +47 -0
  85. package/ledger.mjs +11 -18
  86. package/log.mjs +1 -1
  87. package/manifest-discovery.mjs +1 -1
  88. package/manifest-schema.mjs +7 -5
  89. package/manifest.mjs +5 -3
  90. package/mcp/transport-stdio.mjs +1 -1
  91. package/mcp.mjs +1 -1
  92. package/memory/code-index.mjs +1 -1
  93. package/memory/code-search.mjs +1 -1
  94. package/memory/code-sync.mjs +1 -1
  95. package/memory/docs.mjs +6 -2
  96. package/memory/file-list.mjs +1 -1
  97. package/memory/file-walk.mjs +17 -5
  98. package/memory/maintain.mjs +410 -0
  99. package/memory/memory-tool.mjs +21 -6
  100. package/memory/schema.mjs +19 -7
  101. package/memory/sweep.mjs +8 -5
  102. package/memory/sync-tail.mjs +16 -10
  103. package/model-ref.mjs +16 -13
  104. package/model-specs.mjs +21 -9
  105. package/package.json +1 -1
  106. package/peer-instances.mjs +1 -1
  107. package/permission.mjs +33 -0
  108. package/process-probe.mjs +2 -2
  109. package/prompts/advisor-design.md +4 -2
  110. package/prompts/advisor-round1.md +1 -1
  111. package/prompts/advisor-round2.md +1 -1
  112. package/prompts/advisor-round3.md +1 -1
  113. package/prompts/common.md +38 -4
  114. package/prompts/consult-base.md +1 -1
  115. package/prompts/discipline-engineering.md +32 -15
  116. package/prompts/discipline-normal.md +46 -2
  117. package/prompts/persona-eng-designer.md +6 -6
  118. package/prompts/persona-engineering.md +18 -9
  119. package/prompts/persona-normal.md +2 -0
  120. package/prompts/subagent-base.md +3 -0
  121. package/provider/anthropic.mjs +14 -2
  122. package/provider/core.mjs +6 -0
  123. package/provider/errors.mjs +28 -3
  124. package/provider/google.mjs +22 -10
  125. package/provider/list-models.mjs +3 -2
  126. package/provider/responses-request.mjs +3 -1
  127. package/provider/responses.mjs +38 -4
  128. package/provider/sse.mjs +3 -0
  129. package/provider-flows.mjs +14 -15
  130. package/proxy-chunked.mjs +102 -0
  131. package/proxy-target.mjs +54 -0
  132. package/proxy-transport.mjs +298 -0
  133. package/proxy.mjs +17 -248
  134. package/session-gc.mjs +2 -2
  135. package/session-lifecycle.mjs +18 -14
  136. package/session-slot-claims.mjs +2 -2
  137. package/session-slots-manifest.mjs +1 -1
  138. package/session-slots.mjs +3 -3
  139. package/shell-identity.mjs +107 -0
  140. package/team-ledger.mjs +282 -0
  141. package/team.mjs +223 -0
  142. package/tool-docs/advisor.md +1 -1
  143. package/tool-docs/bash.md +2 -1
  144. package/tool-docs/batch.md +1 -1
  145. package/tool-docs/browser.md +48 -0
  146. package/tool-docs/delete.md +7 -5
  147. package/tool-docs/ledger.md +6 -0
  148. package/tool-docs/manifest.md +16 -0
  149. package/tool-docs/memory.md +1 -0
  150. package/tool-docs/notify_parent.md +5 -0
  151. package/tool-docs/process.md +4 -4
  152. package/tool-docs/question.md +1 -0
  153. package/tool-docs/subagent.md +14 -3
  154. package/tool-docs/team_ledger.md +9 -0
  155. package/tool-docs/timer.md +6 -0
  156. package/tool-docs/wait_for.md +1 -0
  157. package/tools/bash.mjs +9 -4
  158. package/tools/browser.mjs +161 -0
  159. package/tools/git.mjs +1 -1
  160. package/tools/index.mjs +4 -4
  161. package/tools/ops.mjs +26 -10
  162. package/tools/patch.mjs +8 -2
  163. package/tools/repomap.mjs +1 -1
  164. package/tools/write-path.mjs +8 -6
  165. package/traces/trace-cleanup.mjs +1 -1
  166. package/traces/trace-store.mjs +1 -1
  167. package/vision-reader.mjs +10 -16
  168. package/tool-docs/ledger_add.md +0 -1
  169. package/tool-docs/ledger_close.md +0 -1
  170. package/tool-docs/ledger_count.md +0 -1
  171. package/tool-docs/ledger_query.md +0 -1
  172. package/tool-docs/ledger_update.md +0 -1
@@ -8,52 +8,56 @@
8
8
  * - `presetToEntry` = 取 VSC 侧独有导出(CLI 侧对应实现在
9
9
  * `cli/setup-wizard.mjs`,同字节语义)。
10
10
  *
11
- * 预先声明(MODEL-SELECTION v2,2026-09-10):每个预设恰好携带一个默认模型(`model` ——
12
- * 新装启动种子 / 槽位空兜底;候选清单字段不再预置——运行期从 provider 拉取)。
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", model: "deepseek-flash", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 384_000, desc: "DeepSeek" },
18
- kimi: { baseURL: "https://api.moonshot.cn/v1", model: "kimi-k3", thinking: null, reasoningEffort: "max", maxTokens: 131072, desc: "Kimi / Moonshot" },
19
- "kimi-code": { baseURL: "https://api.kimi.com/coding/v1", model: "k3", thinking: null, reasoningEffort: "max", maxTokens: 131072, desc: "Kimi For Coding (platform.kimi.com — sk-kimi- keys; NOT interchangeable with Moonshot)" },
20
- glm: { baseURL: "https://open.bigmodel.cn/api/paas/v4", model: "glm-5.2", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 128000, desc: "Zhipu GLM" },
21
- "glm-code": { baseURL: "https://open.bigmodel.cn/api/coding/paas/v4", model: "glm-5.2", thinking: { type: "enabled" }, reasoningEffort: "max", maxTokens: 128000, desc: "Zhipu GLM Coding Plan (coding endpoint — same key as GLM; server-forced thinking)" },
22
- qwen: { baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1", model: "qwen3.7-max", reasoningEffort: "high", maxTokens: 131072, desc: "Qwen / Alibaba" },
23
- qwenplan: { baseURL: "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1", model: "qwen3.7-max", reasoningEffort: "high", maxTokens: 131072, desc: "Qwen Token Plan (百炼套餐)" },
24
- mimo: { baseURL: "https://api.xiaomimimo.com/v1", model: "mimo-v2.6-pro", thinking: { type: "enabled" }, maxTokens: 131072, desc: "MiMo (Xiaomi)" },
25
- mimoplan: { baseURL: "https://token-plan-cn.xiaomimimo.com/v1", model: "mimo-v2.6-pro", thinking: { type: "enabled" }, maxTokens: 131072, desc: "MiMo Token Plan (小米套餐 — tp- keys; 与按量付费 sk- 密钥不通用)" },
26
- minimax: { baseURL: "https://api.minimaxi.com/v1", model: "MiniMax-M3", thinking: { type: "adaptive" }, maxTokens: 128000, chatPath: "/text/chatcompletion_v2", desc: "MiniMax" },
27
- openai: { baseURL: "https://api.openai.com/v1", model: "gpt-4o", desc: "OpenAI" },
28
- claude: { baseURL: "https://api.anthropic.com/v1", model: "claude-sonnet-4", format: "anthropic", maxTokens: 8192, desc: "Claude (Anthropic)" },
29
- gemini: { baseURL: "https://generativelanguage.googleapis.com/v1beta", model: "gemini-2.5-flash", format: "google", maxTokens: 8192, desc: "Gemini (Google)" },
30
- grok: { baseURL: "https://api.x.ai/v1", model: "grok-4.5", maxTokens: 64_000, desc: "Grok (xAI)" },
31
- mistral: { baseURL: "https://api.mistral.ai/v1", model: "mistral-large", maxTokens: 32_000, desc: "Mistral" },
32
- volcengine: { baseURL: "https://ark.cn-beijing.volces.com/api/v3", model: "doubao-seed-2-0-code-preview-260215", maxTokens: 131072, desc: "Volcengine Ark (豆包)" },
33
- hunyuan: { baseURL: "https://api.hunyuan.cloud.tencent.com/v1", model: "hunyuan-pro", maxTokens: 32_000, desc: "Hunyuan (腾讯混元)" },
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", model: "hy3", desc: "Tencent TokenHub (腾讯混元网关)" },
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)。默认模型 `glm-5.3` = 官方口径(MaaS 最佳实践 ∕ 快速体验页的
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", model: "glm-5.3", desc: "Huawei Cloud ModelArts Studio (华为云 MaaS)" },
42
- siliconflow: { baseURL: "https://api.siliconflow.cn/v1", model: "deepseek-ai/DeepSeek-V3", maxTokens: 32_000, desc: "SiliconFlow (硅基流动)" },
43
- openrouter: { baseURL: "https://openrouter.ai/api/v1", model: "anthropic/claude-sonnet-4", maxTokens: 32_000, desc: "OpenRouter" },
44
- groq: { baseURL: "https://api.groq.com/openai/v1", model: "llama-3.3-70b-versatile", maxTokens: 32_000, desc: "Groq" },
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", model: "glm-5.2", desc: "OpenCode Go subscription (sk- key from opencode.ai/zen; OpenAI-compatible models — MiniMax/Qwen via opencode-go-anthropic)" },
52
- "opencode-go-anthropic": { baseURL: "https://opencode.ai/zen/go/v1", model: "qwen3.7-max", format: "anthropic", maxTokens: 65536, desc: "OpenCode Go — Anthropic Messages side (same sk- key as opencode-go; MiniMax/Qwen models)" },
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
- * (含单值 `model`——候选清单不再由预设播种)。 */
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): providers[] carry ONE default model
4
- * per channel (`providers[].model` — the new-install seed / empty-slot fallback); the
5
- * candidates list field models[] is gone — the available-model list is fetched from the
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" | provider name | model name (parent provider); null = inherit parent provider
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% context, reserving 40% headroom
103
- // for injected context (directory tree, git context, outline, project instructions,
104
- // memory/doc search results) which can consume 30-50K tokens each turn.
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&apos;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 value = Math.floor(spec.context * COMPACT_RATIO)
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" | provider | model) — got ${Array.isArray(v) ? "array" : typeof v} — ignored (inherits the parent provider)`]
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" | provider | model) — got ${Array.isArray(val) ? "array" : typeof val} — dropped`)
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" | 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 activeProvider silently falling to the first provider would use the wrong key on the wrong endpoint.
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(`activeProvider "${name}" not in providers list (available: ${available}); check for a typo in: ${configPath}`)
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: "", model: "" }
257
+ return providers[0] ?? { name: "default", baseURL: "" }
229
258
  }
230
259
 
231
- /** Normalize proxy config to { uri, web, model } or undefined (uri/url both accepted; invalid types dropped) */
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, model: false } : undefined
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, model: proxy.model === true }
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, model, activeProvider all come from the file).
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 内存归一(v2 M3:非空字符串保留;非字符串/空串归一删除)——渠道默认模型
310
- // 单值;无默认模型合法(模型选择经 /models 拉取候选——准入判据见 M8/M9)。
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, model:false }; object 补默认值;非法类型丢弃。
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/activeProvider stay as loaded).
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.
@@ -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` 转口保名(消费面 / 批内件 import 面零改);
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` 转口保名(消费面 / 批内件 import 面零改);
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` 转口保名(消费面 / 批内件 import 面零改)。
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` 缺)⇒ 人读线追加照常 ∥ 存储腿空转(零抛——模式 F
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):写缝 ∥ 回声 ∥ 降级三面外提——消费面 / 批内件 import 面零改 ───
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
@@ -26,9 +26,10 @@
26
26
  * code — inside a declared code segment (default: the path segment `src`), or the
27
27
  * fallback; doc — documentation extension outside any code segment and not
28
28
  * scratch; temp — tmp-* name or .tmp/.temp extension; aux — a default
29
- * auxiliary-path segment sequence (test / tests / scripts / .thincoder/tmp —
30
- * F9) that matched no code segment and neither doc nor temp.
31
- * Precedence: code segment → temp → doc → aux → fallback code.
29
+ * auxiliary-path segment sequence (test / tests / scripts / .thincoder/tmp — F9)
30
+ * that matched no code segment and neither doc nor temp; state — a project state
31
+ * file (`PROJECT-MANIFEST.json` basename ∥ `.thincoder/conventions.json` pair — #941).
32
+ * Precedence: code segment → temp → doc → aux → state → fallback code.
32
33
  *
33
34
  * Segment matching runs on the PROJECT-ROOT-RELATIVE FACE of a path (D17 · 2026-09-28): the
34
35
  * machine layout ABOVE the project root never participates. An absolute path outside the
@@ -128,18 +129,32 @@ function hasAuxSegment(p, conv) {
128
129
  return hasSegmentSequence(p, DEFAULT_AUX_PATHS, conv?.root, false)
129
130
  }
130
131
 
131
- /** "code" | "doc" | "temp" | "aux" — the single classification decision.
132
+ /** Project state-file markers (data — #941 · PORTABILITY D19): manifest basename (any layer) ∥
133
+ * retired-carrier pair; single sources: `MANIFEST_REL` ∥ `RETIRED_REL_PATH`. */
134
+ const STATE_BASENAME = "project-manifest.json"
135
+ const STATE_PAIR = ".thincoder/conventions.json"
136
+
137
+ /** True when `s` names a project state file (basename any layer ∥ segment pair; case-insensitive — no face, D17). */
138
+ function isStateFile(s) {
139
+ const segs = segmentsOf(s)
140
+ if (segs.length > 0 && lowerSeg(segs[segs.length - 1]) === STATE_BASENAME) return true
141
+ return hasSequenceIn(segs, [STATE_PAIR])
142
+ }
143
+
144
+ /** "code" | "doc" | "temp" | "aux" | "state" — the single classification decision.
132
145
  * Precedence: code segment first (src/** stays product code even when the name
133
146
  * looks scratch — the pre-existing unconditional-src rule), then temp, then a
134
147
  * documentation extension, then an auxiliary-path segment sequence (test / tests
135
- * / scripts / .thincoder/tmp — F9), else code (anything not doc/temp/aux is product
136
- * code). Both segment checks run on the project-root-relative face (D17 — `faceOf`). */
148
+ * / scripts / .thincoder/tmp — F9), then a project state file (#941), else code
149
+ * (anything not doc/temp/aux/state is product code). Segment checks face-bound (D17);
150
+ * state matches by name marker. */
137
151
  export function classifyPath(p, conv) {
138
152
  const s = String(p ?? "")
139
153
  if (hasCodeSegment(s, conv)) return "code"
140
154
  if (TEMP_FILE.test(s)) return "temp"
141
155
  if (DOC_FILE.test(s)) return "doc"
142
156
  if (hasAuxSegment(s, conv)) return "aux"
157
+ if (isStateFile(s)) return "state"
143
158
  return "code"
144
159
  }
145
160
 
@@ -165,8 +180,13 @@ export function isAuxPath(p, conv) {
165
180
  return classifyPath(p, conv) === "aux"
166
181
  }
167
182
 
183
+ /** True when the path is a project state file (`classifyPath === "state"` — #941; derived form). */
184
+ export function isStatePath(p, conv) {
185
+ return classifyPath(p, conv) === "state"
186
+ }
187
+
168
188
  // ─────────────────────────────────────────────────────────────────────────────
169
189
  // 迁出面(2026-10-01 拆分批 · #755 ∥ #786):声明装载面外提 `declaration.mjs`——本档经
170
- // 转口保名(消费面 / 批内件 import 面零改;`DEFAULT_DECLARATION` 另供本档分类裁判缺省取用)。
190
+ // 转口保名(消费面 / 单元测试档 import 面零改;`DEFAULT_DECLARATION` 另供本档分类裁判缺省取用)。
171
191
  // ─────────────────────────────────────────────────────────────────────────────
172
- 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` 转口保名(消费面 / 批内件 import 面零改)。
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/escape.mjs CHANGED
@@ -52,7 +52,7 @@ export function escapeLiteralEscapes(text) {
52
52
  return out
53
53
  }
54
54
 
55
- /** 净化孤立 UTF-16 代理对(毒源②):高代理无低代理跟随 / 低代理无高代理前置 → 替换为 。 */
55
+ /** 净化孤立 UTF-16 代理对(毒源②):高代理无低代理跟随 / 低代理无高代理前置 → 剥除(出串零该码元——码 :66/:68 实读为准)。 */
56
56
  export function sanitizeLoneSurrogates(text) {
57
57
  text = String(text ?? "")
58
58
  let out = ""
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
- * 零文本(非串 ∥ 空串)先于探针校验早退(零判据面——探针不检,同序沿核 `file-links.mjs:28`)。 */
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)
@@ -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
- * 逐字等价(负控腿);缺 `opts` ∕ 非 `true` ⇒ 同默认径。
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
@@ -41,6 +41,9 @@ export const CORE_MESSAGES = Object.freeze({
41
41
  "digest.turnLabelAsk": { en: "[auto-turn: answering ${from}: ${msg}]", zh: "自动回合:答复 ${from}:${msg}" },
42
42
  "digest.capAuto": { en: "[auto-turn: continuing past turn cap…]", zh: "自动回合:越过轮次上限,继续推进…" },
43
43
  "digest.capStop": { en: "[auto-turn stopped at ${turns} turns — partial digest; finished reports stay in history]", zh: "自动回合在 ${turns} 轮处停止——部分消化;已完成的报告保留在历史中" },
44
+ // 消化账务批 · 2026-10-05 · 台账 #930(§6.31.6 端面残余行——判据 = `unsettledCount(carrier)`;
45
+ // 消费面 = CLI 痕行 ∥ 桌面 / VSC 行元素;三端共读本键)
46
+ "digest.residue": { en: "${n} background report(s) not accounted — they will be re-delivered", zh: "有 ${n} 份后台报告未销账——将自动重投" },
44
47
  // ── 限流 / 供应商状态行 ──
45
48
  "status.rateWait": { en: "TPM throttle wait ~${s}s", zh: "TPM 限流等待 ~${s}s" },
46
49
  "status.rateLimited": { en: "Rate-limited 429, retry in ${s}s", zh: "限流 429,${s}s 后重试" },
@@ -67,8 +70,12 @@ export const CORE_MESSAGES = Object.freeze({
67
70
  "status.stopped": { en: "[stopped]", zh: "[已停止]" },
68
71
  "status.thinking": { en: "Thinking", zh: "思考中" },
69
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: "已失效——重新登录" },
70
75
  "tool.running": { en: "running…", zh: "执行中…" },
71
76
  "tool.done": { en: "done", zh: "完成" },
77
+ // ── 台账(ledger)· F-LX3 变体键库首跑提示(批 2026-10-05-ledger-variant-db-notice · 台账 #935 · LEDGER.md §12.2 逐字;消费面 = CLI TUI 启动行)
78
+ "ledger.variantDbNotice": { en: "Legacy variant-key ledger DB found for this project (pre-upgrade; rows may be outside the current ledger): thincoder ledger audit to inspect; thincoder ledger migrate to reconcile", zh: "检测到本项目的存量变体键台账库(升级遗留,数据可能未并入当前库):thincoder ledger audit 查看;thincoder ledger migrate 收正" },
72
79
  })
73
80
  // 深度冻结(机器消费面冻结——嵌套 { en, zh } 也冻结;浅冻结下值可被静默改写)。
74
81
  for (const entry of Object.values(CORE_MESSAGES)) Object.freeze(entry)