thincoder 0.12.68 → 0.12.69

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 CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.12.69] — 2026-10-03
2
+
3
+ > 0.12.68 → 0.12.69(连续号——发布时定号)
4
+
5
+ ### Changed
6
+
7
+ - **核依赖升级**:`@thincoder/core` `^0.10.1` → `^0.10.2`(统一 provider 解析随核生效)。
8
+ - **无效 provider 态分流(#841)**:TUI——`fallback`(可运行但未设默认模型)⇒ 不弹选择器 + 一行明示(`模型` 缺 ⇒ 词形仅渠道名);真无效(无 provider ∕ 无 key)⇒ 选择器 + 引导提示行。headless——真无效 ⇒ 可读错误 + 明确退出码;`fallback` ⇒ stderr 一行 + **继续运行**(不退出)。
9
+
1
10
  ## [0.12.68] — 2026-10-03
2
11
 
3
12
  > 0.12.67 → 0.12.68(连续号——发布时定号)
package/README.md CHANGED
@@ -231,6 +231,10 @@ Code conventions: pure `.mjs`, no semicolons, no third-party npm dependencies al
231
231
 
232
232
  ## Changelog
233
233
 
234
+ ### 0.12.69 (2026-10)
235
+ - **Provider-state unify (#841)** — one core resolution for the three ends (slot → `defaultModel` → first keyed channel) with three states: `ok`; `fallback` (runnable but no default model — one clear notice line, never blocked); `invalid` (no provider/key — guided picker; headless exits with a readable message and a clear code, while `fallback` prints one stderr line and keeps running). No more false "API key not configured" on a working channel.
236
+ - **Core `^0.10.2`** — unified provider resolution (`resolveProviderPlan` / `resolveChannelModel`), `providerState` / `providerStateReason` keys, slot key gate.
237
+
234
238
  ### 0.12.68 (2026-10)
235
239
  - **Idle wake** — suspended sessions wake to timers: they resume on schedule (visibility surface + abort accommodation).
236
240
  - **Ledger health signals** — an unhealthy ledger database now shows a warning line in the TUI (empty values render as `—`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thincoder",
3
- "version": "0.12.68",
3
+ "version": "0.12.69",
4
4
  "description": "Thin coding agent - zero third-party dependencies, no build step, Node.js native. Sharp code, zero bloat.",
5
5
  "keywords": [
6
6
  "ai",
@@ -20,7 +20,7 @@
20
20
  "node": ">=24"
21
21
  },
22
22
  "dependencies": {
23
- "@thincoder/core": "^0.10.1"
23
+ "@thincoder/core": "^0.10.2"
24
24
  },
25
25
  "files": [
26
26
  "bin/",
@@ -30,14 +30,23 @@ export async function chatCommand(args, ctx) {
30
30
  if (crashNotice) console.error(crashNotice)
31
31
 
32
32
  const agent = await assembleAgent()
33
- // SESSION.md §6.8 D-S4(F4)+ MODEL-MERGE-SESSION:headless 无 TUI —— 可读错误 + 退出码 1,
34
- // 不弹 UI、不崩溃;文案改引 defaultModel(F-6 引导 A headless 面——/config → 默认模型)
35
- if (agent._providerInvalid) {
36
- const dm = agent.config?.defaultModel
37
- console.error(`[error] 未配置有效模型(defaultModel "${dm ?? "(not set)"}":${agent._providerInvalidReason})。请运行 thincoder 进入 TUI 设置(/config → 默认模型;或 /model 选择后以会话槽生效),或编辑 ${configPath}`)
33
+ // SESSION.md §6.8 D-S4(#841 收正):headless 无 TUI——按**核统一态分流**(判据单源 = PROVIDER.md §6.22,
34
+ // 与 TUI 面同式):**invalid 类**(`state === "invalid"` ∨ `providerInvalidReason` 非空——无渠/key
35
+ // ⇒ 不可运行)⇒ 可读错误 + 退出码 1(不弹 UI、不崩溃);**fallback**(无有效 defaultModel 但可运行)
36
+ // ⇒ stderr 一行明示 + 继续运行(不退出——D-S2 同态同解)。条目结构不全不在本门(负向锁 =
37
+ // PROVIDER.md:437)——归运行期失败径(stderr + 非 0 退出)。
38
+ const headlessCfg = agent.config ?? {}
39
+ const invalidClass = headlessCfg.providerState === "invalid"
40
+ || (typeof headlessCfg.providerInvalidReason === "string" && headlessCfg.providerInvalidReason !== "")
41
+ if (invalidClass) {
42
+ console.error(`[error] 未配置有效渠道(${agent._providerInvalidReason ?? headlessCfg.providerStateReason ?? "provider unavailable"})。请运行 thincoder 进入 TUI 设置(/config 设置渠道与密钥;或 /model 选择后以会话槽生效),或编辑 ${configPath}`)
38
43
  exitSoon(1)
39
44
  return
40
45
  }
46
+ if (headlessCfg.providerState === "fallback" && agent.provider?.name) {
47
+ const m = typeof agent.provider.model === "string" && agent.provider.model ? `:${agent.provider.model}` : ""
48
+ console.error(`尚未设置默认模型:本次使用 \`${agent.provider.name}${m}\`——/config → 默认模型 设置一次;/model 仅改本会话`)
49
+ }
41
50
  if (!agent.provider.apiKey) {
42
51
  if (!process.stdin.isTTY) {
43
52
  console.error(noKeyMessage())
@@ -121,9 +130,10 @@ export async function tuiCommand(ctx) {
121
130
  // F-MI7:resumeSlot = async(探测束不阻塞事件循环)——此处必须 await
122
131
  const { slot, data } = await resumeSlot(process.cwd())
123
132
  const agent = await assembleAgent({ slotData: data })
124
- // SESSION.md §6.8 D-S1:TUI 路径在 startTUI 前清空无效 provider——空 provider 不流入 runAgent
125
- // (崩溃源:chat() 缺 model → 网关 400 或 fetch("undefined/...") TypeError)
126
- if (agent._providerInvalid) agent.provider = null
133
+ // SESSION.md §6.8 D-S1(#841 收正):TUI 路径**仅在 `state === "invalid"`**(无渠/key)于 startTUI
134
+ // 前置 `agent.provider = null`——空 provider 不流入 runAgent(崩溃源:chat() 缺 model → 网关 400
135
+ // 或 fetch("undefined/...") TypeError);`fallback` 态 provider 有效——不清(可运行 + 明示)。
136
+ if (agent.config?.providerState === "invalid") agent.provider = null
127
137
  const config = loadConfig()
128
138
  if (data) {
129
139
  // applySession 内部已按槽复合重算 compactThreshold(auto 时)——不再需要 switched 分支
package/src/tui/index.mjs CHANGED
@@ -47,24 +47,40 @@ import { createTimerWatch, fireTimerWake } from "./timer-watch.mjs"
47
47
  export { upgradeFailureText, pendingNoticeReady } from "./update-notice.mjs"
48
48
 
49
49
  /**
50
- * SESSION.md §6.8 D-S2 — TUI 启动首帧前的 provider 重选流程:
51
- * provider 无效(`_providerInvalid` 标记或 provider 为 null)→ 先弹模型选择 picker
52
- * (复用 openModelPicker,展示当前可用 providers);用户选定后继续正常启动。
53
- * 选择取消(Esc)→ 仍进入 TUI,推送提示行("未配置有效 provider,可用 /model 配置
54
- * 渠道与模型")——绝不因无 provider 拒绝进入。返回 true 表示弹过选择流程。
50
+ * SESSION.md §6.8 D-S2 — TUI 启动首帧前的 provider 分流(判据单源 = PROVIDER.md §6.22):
51
+ * · **invalid 类**(`state === "invalid"` ∨ `providerInvalidReason` 非空——核「provider 不完整面」
52
+ * 快照的合成式)→ 先弹模型选择 picker(复用 openModelPicker,展示当前可用 providers);
53
+ * 用户选定后继续正常启动。选择取消(Esc)→ 仍进入 TUI,推送提示行(措辞指渠道 ∕ 密钥——
54
+ * 绝不因无 provider 拒绝进入)。
55
+ * · **`fallback`**(无有效 defaultModel 但可运行)→ **不弹 picker**(不打断)+ 提示行明示
56
+ * (「尚未设置默认模型:本次使用 `<渠道>[:<模型>]`——…」;`model` 缺省 ⇒ 仅渠道名)。
57
+ * 返回 true 表示弹过选择流程。
55
58
  */
56
59
  export async function promptProviderIfInvalid(agent, openModelPicker, pushLine) {
57
- if (!(agent._providerInvalid || !agent.provider)) return false
58
- await openModelPicker()
59
- if (!agent.provider) {
60
- // MODEL-MERGE-SESSION 引导 A(F-6):空槽 + defaultModel 未设 → 提示 /config 默认模型入口
61
- // (index.mjs 本行为 D-S2 取消提示扩展——设计文件清单外——随批上报)
62
- const hasProviders = (agent.providers?.length ?? 0) > 0
63
- pushLine(hasProviders && !agent.config?.defaultModel
64
- ? "尚未设置默认模型(config.defaultModel——新会话起点):/config → 默认模型 设置一次;/model 仅改本会话"
65
- : "未配置有效 provider,可用 /model 配置渠道与模型", C.warn)
60
+ const cfg = agent.config ?? {}
61
+ // invalid 类(合成式——单源 = PROVIDER.md §6.22):`state === "invalid"` ∨ `providerInvalidReason`
62
+ // 非空(核 provider 不完整面快照);`!agent.provider`(`{}` 已被上游清成 null)同判。
63
+ // 结构不全(缺 baseURL 等)不在本门——归发送失败面(#840 `provider` 类词);负向锁 = PROVIDER.md:437。
64
+ const invalidClass = cfg.providerState === "invalid"
65
+ || (typeof cfg.providerInvalidReason === "string" && cfg.providerInvalidReason !== "")
66
+ || !agent.provider
67
+ if (invalidClass) {
68
+ await openModelPicker()
69
+ if (!agent.provider) {
70
+ // D-S2 取消提示(#841 措辞收正 = 渠道 ∕ 密钥——配置入口 /config)
71
+ pushLine("未配置有效渠道(渠道 ∥ 密钥缺位):/config 设置渠道与密钥;/model 仅改本会话", C.warn)
72
+ }
73
+ return true
66
74
  }
67
- return true
75
+ if (cfg.providerState === "fallback") {
76
+ const name = agent.provider?.name
77
+ if (name) {
78
+ const model = typeof agent.provider.model === "string" && agent.provider.model ? agent.provider.model : null
79
+ pushLine(`尚未设置默认模型:本次使用 \`${name}${model ? `:${model}` : ""}\`——/config → 默认模型 设置一次;/model 仅改本会话`, C.warn)
80
+ }
81
+ return false
82
+ }
83
+ return false
68
84
  }
69
85
 
70
86
  /**
@@ -220,8 +236,9 @@ export async function startTUI(agent, opts = {}) {
220
236
 
221
237
  // ---------------------------------------------------------- Startup screen + background indexing
222
238
 
223
- // SESSION.md §6.8 D-S2:startTUI 首帧前 —— provider 无效(_providerInvalid / provider 为 null)
224
- // → 先弹模型选择 picker(keyStream 已挂 keypress,Esc/Enter 可用);Esc → 提示行,仍进 TUI
239
+ // SESSION.md §6.8 D-S2:startTUI 首帧前——按核统一态分流(单源 = PROVIDER.md §6.22):
240
+ // invalid 类 → 先弹模型选择 picker(keyStream 已挂 keypress,Esc/Enter 可用);Esc → 提示行,仍进 TUI;
241
+ // fallback → 不弹 picker + 提示行明示(本次使用 `<渠道>[:<模型>]`)
225
242
  await promptProviderIfInvalid(agent, () => openModelPicker(), pushLine)
226
243
 
227
244
  showStartup({ agent, state, opts, pushLine, pushLabel, render, startWizard })