thincoder 0.12.69 → 0.12.70

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.
@@ -1,17 +1,15 @@
1
1
  /**
2
2
  * ledger-surface.mjs — TUI 台账可见面胶水(K-LX3 归核:实现单源 = `@thincoder/core/ledger-surface.mjs`,
3
- * 本档只剩**动态 import + TUI 色表注入**两件事——端壳静态链不得达 `node:sqlite`(W8 契约②);
3
+ * 本档只剩**动态 import 转口**一事——端壳静态链不得达 `node:sqlite`(W8 契约②);
4
4
  * 载入失败 = 降级不崩(N1),扫描拍重试)。
5
5
  *
6
- * 三面语义(启动行 / 变化行 / 状态位)见核档头注;判活展示(F-LX1)亦随归核自动带上——
7
- * 本档零实现、零复刻(KD-M2-3)。
6
+ * 状态位语义见核档头注;判活展示(F-LX1)亦随归核自动带上——本档零实现、零复刻(KD-M2-3)。
8
7
  *
9
8
  * 两端契约(#233——端壳与核形一致):`startLedgerSurface` **恒同步返回 `{ dispose }`**——
10
9
  * 核句柄异步就绪(动态 import)后桥接;载入失败 ⇒ 空句柄(`dispose` 恒可调);`dispose()`
11
10
  * 早于核就绪 ⇒ 置位且**跳过启动**(核侧零调用——零周期;N1 降级不崩)。调用点因此恒可按
12
11
  * 同步形态挂进程退钩(`process.on("exit", () => handle.dispose())`)。
13
12
  */
14
- import { C } from "./ansi.mjs"
15
13
 
16
14
  /** 核台账面惰性载入(动态 import——失败不缓存,下次扫描拍重试;N1 降级不崩)。 */
17
15
  let _core = null
@@ -29,10 +27,10 @@ let _loadCoreForTest = null
29
27
  export function _setLoadCoreForTest(fn) { _loadCoreForTest = fn }
30
28
  const loadCoreNow = () => (_loadCoreForTest ?? loadCore)()
31
29
 
32
- /** 单次扫描(直驱面——直通核实现;TUI 色表 `C` 注入)。 */
30
+ /** 单次扫描(直驱面——直通核实现)。 */
33
31
  export async function runLedgerScan(opts = {}) {
34
32
  const core = await loadCoreNow()
35
- return core.runLedgerScan({ ...opts, colors: C })
33
+ return core.runLedgerScan(opts)
36
34
  }
37
35
 
38
36
  /** TUI 挂载(首扫 + 周期——直通核实现):**恒同步返回 `{ dispose }`**——核句柄异步就绪后
@@ -42,7 +40,7 @@ export function startLedgerSurface(ctx = {}) {
42
40
  let disposed = false
43
41
  let handle = null
44
42
  loadCoreNow()
45
- .then((core) => { if (!disposed) handle = core.startLedgerSurface({ ...ctx, colors: C }) })
43
+ .then((core) => { if (!disposed) handle = core.startLedgerSurface(ctx) })
46
44
  .catch(() => { /* 载入失败 ⇒ 空句柄(N1) */ })
47
45
  return {
48
46
  dispose() {
@@ -2,8 +2,9 @@
2
2
  * model-picker.mjs — /model two-level picker + provider management + session-slot model
3
3
  * selection(MODEL-MERGE-SESSION 拆分产物——/model 两级面自 pickers.mjs 迁出)。
4
4
  *
5
- * 语义(2026-09-10 MODEL-SELECTION v2):selectModel 写**会话槽**(agent 内存态 + saveSession)——
6
- * 不写 config(/model 不再串扰全局默认——根治)。候选 = **运行期拉取**(`GET /models`——M2;
5
+ * 语义(2026-09-10 MODEL-SELECTION v2):selectModel 写**会话槽**(agent 内存态 + saveSession);
6
+ * 会话级模型面**实变** ⇒ 同拍写回 config.defaultModel(判据句 6——写面单点 = `config-helpers.mjs`
7
+ * `carryoverDefaultModel`;等值 ∕ 回声零写)。候选 = **运行期拉取**(`GET /models`——M2;
7
8
  * 拉到的行直接可选);显式 `provider:model` 一律放行(M4——仅[空值/裸值/未知 provider]无效)。
8
9
  * 切换成功回显规格来源(M6);渠道默认模型 = `providers[].model` 单值(M3——显示回退读取)。
9
10
  * 配置写入面(加渠道 / 设 key)探一次 `/models`(M9——探不通标「不可用」+ 明示原因,不阻断保存)。
@@ -15,7 +16,8 @@
15
16
  */
16
17
  import { sliceByWidth } from "./render.mjs"
17
18
  import { providerSpec, specMatch } from "@thincoder/core/config.mjs"
18
- import { saveSession } from "@thincoder/core/session.mjs"
19
+ import { saveSession, loadSlotFile } from "@thincoder/core/session.mjs"
20
+ import { carryoverDefaultModel } from "./config-helpers.mjs"
19
21
  import { getProviderModels, modelListFailureText, dedupeModels } from "./model-catalog.mjs"
20
22
  import { createProviderAdmin } from "./provider-admin.mjs"
21
23
 
@@ -218,7 +220,8 @@ export function createModelPicker(ctx) {
218
220
  return entries
219
221
  }
220
222
 
221
- /** F-3 /model 纯会话级:写槽(agent 内存态 + saveSession)——绝不写 config。
223
+ /** F-3 /model 会话级:写槽(agent 内存态 + saveSession);选定实变 ⇒ 同拍写回
224
+ * config.defaultModel(判据句 6——新会话起点随动;等值 ∕ 回声零写;写回失败不反扑会话写、记错零静默)。
222
225
  * 放行语义(M4):仅未知 provider 拒——显式 p:m 一律放行(候选外/多冒号不再拒)。 */
223
226
  async function selectModel(item) {
224
227
  closePicker()
@@ -229,7 +232,9 @@ export function createModelPicker(ctx) {
229
232
  if (!item.model) {
230
233
  throw new Error(`Missing model name for provider: ${item.provider}`)
231
234
  }
232
- // 内存态(agent 会话运行时)——不触碰 config
235
+ // 写前读:槽复合基线(判据句 6 实变单元——档缺 ⇒ null;`_slot` 未钉 ⇒ 无槽面)
236
+ const slotBefore = agent._slot == null ? null : loadSlotFile(agent.cwd, agent._slot)
237
+ // 内存态(agent 会话运行时)——config 写仅经写回单点(下方实变节)
233
238
  agent.activeProvider = target.name
234
239
  agent.activeModel = item.model
235
240
  agent.provider = { ...target }
@@ -238,8 +243,12 @@ export function createModelPicker(ctx) {
238
243
  const { resolveCompactThreshold } = await import("@thincoder/core/config.mjs")
239
244
  agent.config.agent.compactThreshold = resolveCompactThreshold(null, agent.provider).value
240
245
  }
241
- // 写会话槽(saveSession——槽双字段恒非空)——config 文件零写
246
+ // 写会话槽(saveSession——槽双字段恒非空)——写回单点在其后(槽先配置后)
242
247
  saveSession(agent)
248
+ // 选定写回(判据句 6——定序槽先配置后;失败不反扑会话写、记错零静默)
249
+ const carried = await carryoverDefaultModel({ provider: target.name, model: item.model, slotBefore })
250
+ if (carried.ok !== true) console.error(`[model] default model carryover failed: ${carried.reason}`)
251
+ else if (carried.written === true && agent.config) agent.config.defaultModel = `${target.name}:${item.model}` // 内存镜像(/config 显示面新鲜——wizard.mjs:210-211 先例)
243
252
  // M6 切换回显:规格来源一行(DEFAULT 兜底 → 警示色 + /config 提示)
244
253
  const { matched } = specMatch(agent.provider.model)
245
254
  const spec = providerSpec(agent.provider)
@@ -1,3 +1,5 @@
1
+ import { homedir } from "node:os"
2
+ import { resolve } from "node:path"
1
3
  import { listSlots, ledgerHealth } from "@thincoder/core/session.mjs"
2
4
  import { stripAtRefs } from "@thincoder/core/file-refs.mjs"
3
5
  import { ansi, C } from "./ansi.mjs"
@@ -263,12 +265,20 @@ export function showStartup(ctx) {
263
265
  render()
264
266
  }
265
267
 
268
+ /** home 根防护(#867 · `MEMORY.md` §6.14 L-④):`cwd` = 用户主目录(resolve 后;win32 大小写不敏感)。 */
269
+ const isHomeDir = (dir) => typeof dir === "string" && dir !== "" &&
270
+ (process.platform === "win32" ? resolve(dir).toLowerCase() === resolve(homedir()).toLowerCase() : resolve(dir) === resolve(homedir()))
271
+
266
272
  /** Background indexing (runs after startup screen, non-blocking); progress shown in status bar, not conversation.
267
273
  * Prefers git diff incremental (fast); falls back to full scan when git is unavailable or on first run. */
268
274
  export async function backgroundIndex(ctx) {
269
- const { agent, state, render } = ctx
270
- const { codeSync, docSync, gitSync } = await import("@thincoder/core/memory.mjs")
275
+ const { agent, state, render, pushLine } = ctx
271
276
  const cwd = agent.cwd
277
+ if (isHomeDir(cwd)) { // #867:home 根 ⇒ 跳过索引(三 sync 零调用;不阻断启动)
278
+ pushLine("[index] Skipped: working directory is the home directory — start in a project dir", C.warn)
279
+ return
280
+ }
281
+ const { codeSync, docSync, gitSync } = await import("@thincoder/core/memory.mjs")
272
282
  let codeFiles = 0, docFiles = 0
273
283
 
274
284
  state.status = "Indexing..."
@@ -162,7 +162,7 @@ export function createWizard(ctx) {
162
162
  state.wizard = null
163
163
  // MODEL-MERGE-SESSION 引导 A(F-6):有 provider 但 defaultModel 未设时指引 /config 入口
164
164
  const hint = (agent.providers?.length ?? 0) > 0 && !agent.config?.defaultModel
165
- ? "Skipped initial setup. 已配置渠道但 config.defaultModel 未设——新会话无起点:/config → 默认模型 设置一次(或 /model 仅改本会话)。"
165
+ ? "Skipped initial setup. 已配置渠道但 config.defaultModel 未设——新会话无起点:/config → 默认模型 设置一次(或 /model 选定即成为默认模型)。"
166
166
  : "Skipped initial setup. Use /model to add providers and configure API keys anytime."
167
167
  pushLine(hint, C.dim)
168
168
  render()