@aiwayds/dsh-tui-pi 2.0.1 → 2.1.0
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/README.md +20 -7
- package/README.zh-CN.md +19 -6
- package/bin/preflight-projcache.mjs +20 -108
- package/cordis.patch.yml +27 -0
- package/lib/preflight-projcache.d.ts +39 -0
- package/lib/preflight-projcache.js +116 -0
- package/lib/preflight-projcache.js.map +1 -0
- package/lib/projcache.d.ts +2 -0
- package/lib/projcache.js +32 -0
- package/lib/projcache.js.map +1 -0
- package/package.json +21 -5
- package/scripts/smoke-boot.mjs +56 -6
- package/lib/model-sync.d.ts +0 -122
- package/lib/model-sync.js +0 -256
- package/lib/model-sync.js.map +0 -1
package/README.md
CHANGED
|
@@ -35,12 +35,10 @@ https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
|
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
37
|
dsh plugin --profile tui add @aiwayds/dsh-tui-pi
|
|
38
|
-
dsh plugin --profile tui add @aiwayds/dsh-subagent-registry # optional
|
|
39
|
-
dsh plugin --profile tui add @aiwayds/dsh-dcp # optional
|
|
40
38
|
dsh --profile tui # launch (or: dsh-tui-pi)
|
|
41
39
|
```
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
Legacy `session_projcache` records (missing `identity.isSeeded`/`identity.inheritedEventCount`, written before dsh 0.1.2-alpha.4) are migrated at the profile layer: the bundle patch replaces the stock `session-projection-cache` row with a wrapper (`@aiwayds/dsh-tui-pi/projcache`) that backfills the records while its module loads — strictly before the stock plugin could open the domain and crash the boot — so every `dsh --profile tui` start is covered, launcher or not. Migration is idempotent, backs up every rewritten file next to the original, and never blocks startup. The `dsh-tui-pi` launcher additionally runs the same migration as a CLI preflight before `exec dsh`.
|
|
44
42
|
|
|
45
43
|
Everything that used to need manual patching — the canvas background, the `@deepseek-ai` module closure, the compaction backend — now happens automatically. Upgrade an existing profile after a release:
|
|
46
44
|
|
|
@@ -53,9 +51,24 @@ node scripts/dev-upgrade.mjs 1.0.5 --dry-run # preview the plan first
|
|
|
53
51
|
|
|
54
52
|
## Companion plugins
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- [@aiwayds/dsh-
|
|
54
|
+
**Default dependencies** — the eight plugins below ship with this package (installed into the profile's `node_modules`); activation still follows the profile's `bundles` list — list each one there to activate it.
|
|
55
|
+
|
|
56
|
+
- [@aiwayds/dsh-ask-router](https://www.npmjs.com/package/@aiwayds/dsh-ask-router) — fans every `ask_user_question` out to all answering surfaces (TUI panel, Feishu card); the first answer wins — list it in `bundles` before the UI bundles to activate.
|
|
57
|
+
- [@aiwayds/dsh-dcp](https://github.com/fan56/dsh-dcp) — the deterministic zero-LLM compaction backend.
|
|
58
|
+
- [@aiwayds/dsh-llm-proxy](https://github.com/fan56/dsh-llm-proxy) — SYSTEM proxy + per-host LLM outbound routing.
|
|
59
|
+
- [@aiwayds/dsh-llm-stats](https://github.com/fan56/dsh-llm-stats) — the `/llm-stats` usage ledger.
|
|
60
|
+
- [@aiwayds/dsh-mcp-adapter](https://github.com/fan56/dsh-mcp-adapter) — folds MCP tool schemas out of every prompt and adds the `/mcp` command ([demo](docs/features/mcp-adapter.md)).
|
|
61
|
+
- [@aiwayds/dsh-model-sync](https://github.com/fan56/dsh-model-sync) — syncs provider routes with the pi.dev model catalog.
|
|
62
|
+
- [@aiwayds/dsh-subagent-registry](https://github.com/fan56/dsh-subagent-registry) — registers `~/.dsh/agents/*.md` as `use_agent` subagents.
|
|
63
|
+
- [@aiwayds/dsh-web-search-anysearch](https://github.com/fan56/dsh-web-search-anysearch) — the AnySearch web search provider.
|
|
64
|
+
|
|
65
|
+
**Recommended install** — [@aiwayds/dsh-llmwiki-memory](https://github.com/fan56/dsh-llmwiki-memory), OKF topic memory for dsh (zero-LLM hot-path injection + a local git-tracked bundle):
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
dsh plugin --profile tui add @aiwayds/dsh-llmwiki-memory
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Optional** — [@aiwayds/dsh-feishu](https://github.com/fan56/dsh-feishu) — drives the same dsh session from Feishu/Lark on your phone ([demo](docs/features/feishu-demo.md)).
|
|
59
72
|
|
|
60
73
|
---
|
|
61
74
|
|
|
@@ -102,7 +115,7 @@ Other knobs: `dsh-tui.panelHeight` (think/tool panel height), `dsh-tui.iconSet`
|
|
|
102
115
|
```sh
|
|
103
116
|
pnpm check # tsc --noEmit
|
|
104
117
|
pnpm build # emit lib/
|
|
105
|
-
pnpm test # unit tests, node --test against lib/ (pretest builds;
|
|
118
|
+
pnpm test # unit tests, node --test against lib/ (pretest builds; 1,100+ tests across 60+ files — see HANDOFF.md for the current baseline)
|
|
106
119
|
```
|
|
107
120
|
|
|
108
121
|
`pi-tui` runs pristine from npm — no patches, no fork. See [AGENTS.md](AGENTS.md) for the iron rules and quality gates.
|
package/README.zh-CN.md
CHANGED
|
@@ -35,8 +35,6 @@ https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
|
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
37
|
dsh plugin --profile tui add @aiwayds/dsh-tui-pi
|
|
38
|
-
dsh plugin --profile tui add @aiwayds/dsh-subagent-registry # optional
|
|
39
|
-
dsh plugin --profile tui add @aiwayds/dsh-dcp # optional
|
|
40
38
|
dsh --profile tui # 启动(或:dsh-tui-pi)
|
|
41
39
|
```
|
|
42
40
|
|
|
@@ -51,9 +49,24 @@ node scripts/dev-upgrade.mjs 1.0.5 --dry-run # 先预览执行计划
|
|
|
51
49
|
|
|
52
50
|
## Companion plugins 伴生插件
|
|
53
51
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- [@aiwayds/dsh-
|
|
52
|
+
**默认依赖** —— 以下 8 个插件随本包自带(安装进 profile 的 `node_modules`);激活仍以 profile 的 `bundles` 列表为准——把要用的逐个列进去即可。
|
|
53
|
+
|
|
54
|
+
- [@aiwayds/dsh-ask-router](https://www.npmjs.com/package/@aiwayds/dsh-ask-router) —— 把每个 `ask_user_question` 扇出到所有应答面(TUI 面板、飞书卡片),第一个答案获胜;激活时在 `bundles` 里放在 UI bundle 之前。
|
|
55
|
+
- [@aiwayds/dsh-dcp](https://github.com/fan56/dsh-dcp) —— 确定性零 LLM 压缩后端。
|
|
56
|
+
- [@aiwayds/dsh-llm-proxy](https://github.com/fan56/dsh-llm-proxy) —— SYSTEM 代理 + 按 host 的 LLM 出站分流。
|
|
57
|
+
- [@aiwayds/dsh-llm-stats](https://github.com/fan56/dsh-llm-stats) —— `/llm-stats` 用量台账。
|
|
58
|
+
- [@aiwayds/dsh-mcp-adapter](https://github.com/fan56/dsh-mcp-adapter) —— 把 MCP 工具 schema 折出每个请求,并提供 `/mcp` 命令([演示](docs/features/mcp-adapter.md))。
|
|
59
|
+
- [@aiwayds/dsh-model-sync](https://github.com/fan56/dsh-model-sync) —— pi.dev 模型目录同步进 provider 路由。
|
|
60
|
+
- [@aiwayds/dsh-subagent-registry](https://github.com/fan56/dsh-subagent-registry) —— 把 `~/.dsh/agents/*.md` 注册成 `use_agent` 子代理。
|
|
61
|
+
- [@aiwayds/dsh-web-search-anysearch](https://github.com/fan56/dsh-web-search-anysearch) —— AnySearch web 搜索 provider。
|
|
62
|
+
|
|
63
|
+
**推荐安装** —— [@aiwayds/dsh-llmwiki-memory](https://github.com/fan56/dsh-llmwiki-memory),dsh 的 OKF topic 记忆插件(零 LLM 热路径注入 + 本地 git 可追溯 bundle):
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
dsh plugin --profile tui add @aiwayds/dsh-llmwiki-memory
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**可选** —— [@aiwayds/dsh-feishu](https://github.com/fan56/dsh-feishu) —— 用手机上的飞书/Lark 驱动同一个 dsh session([演示](docs/features/feishu-demo.md))。
|
|
57
70
|
|
|
58
71
|
---
|
|
59
72
|
|
|
@@ -100,7 +113,7 @@ dsh-tui:
|
|
|
100
113
|
```sh
|
|
101
114
|
pnpm check # tsc --noEmit
|
|
102
115
|
pnpm build # 输出 lib/
|
|
103
|
-
pnpm test # 单元测试,node --test 对 lib/ 执行(pretest 构建;
|
|
116
|
+
pnpm test # 单元测试,node --test 对 lib/ 执行(pretest 构建;1100+ 测试、60+ 文件——当前基数见 HANDOFF.md)
|
|
104
117
|
```
|
|
105
118
|
|
|
106
119
|
`pi-tui` 从 npm 原样运行——无补丁、无 fork。铁律与质量门禁见 [AGENTS.md](AGENTS.md)。
|
|
@@ -1,108 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Preflight migration for the dsh `session_projcache` storage domain
|
|
3
|
+
* Preflight migration for the dsh `session_projcache` storage domain — CLI
|
|
4
|
+
* shell around the shared core (lib/preflight-projcache.js, built from
|
|
5
|
+
* src/preflight-projcache.ts). The `dsh-tui-pi` launcher (bin/dsh-tui-pi)
|
|
6
|
+
* runs this before `exec dsh`. This is one of two mount points for the
|
|
7
|
+
* migration; the other is lib/projcache.js, the wrapper module the bundle
|
|
8
|
+
* patch mounts in place of the stock session-projection-cache row, so plain
|
|
9
|
+
* `dsh --profile tui` boots are covered without the launcher.
|
|
4
10
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* <DSH_HOME>/storages/session_projcache/sessions/session-*.json
|
|
15
|
-
*
|
|
16
|
-
* - records that already carry both fields are left byte-identical (no IO);
|
|
17
|
-
* - every changed record is backed up next to the original, then rewritten
|
|
18
|
-
* atomically (tmp file + rename);
|
|
19
|
-
* - the script NEVER blocks startup: any unexpected error is swallowed with
|
|
20
|
-
* a one-line stderr warning and a 0 exit code.
|
|
11
|
+
* Contract (unchanged since 2.0.1):
|
|
12
|
+
* - scans <DSH_HOME||~/.dsh>/storages/session_projcache/sessions/session-*.json
|
|
13
|
+
* - backfills identity.isSeeded:false / identity.inheritedEventCount:0
|
|
14
|
+
* - records that already carry both fields are left byte-identical (no IO);
|
|
15
|
+
* - every changed record is backed up next to the original, then rewritten
|
|
16
|
+
* atomically (tmp file + rename);
|
|
17
|
+
* - per-record problems warn on stderr and never abort the scan;
|
|
18
|
+
* - ALWAYS exits 0: a preflight failure must never block startup.
|
|
21
19
|
*/
|
|
22
20
|
|
|
23
21
|
import fs from 'node:fs'
|
|
24
|
-
import os from 'node:os'
|
|
25
|
-
import path from 'node:path'
|
|
26
22
|
import { fileURLToPath } from 'node:url'
|
|
27
|
-
|
|
28
|
-
const BACKFILL = { isSeeded: false, inheritedEventCount: 0 }
|
|
29
|
-
|
|
30
|
-
function isPlainObject(value) {
|
|
31
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function backupPathFor(file) {
|
|
35
|
-
const stamp = new Date().toISOString()
|
|
36
|
-
let candidate = `${file}.bak-preflight-${stamp}`
|
|
37
|
-
for (let n = 2; fs.existsSync(candidate); n++) {
|
|
38
|
-
candidate = `${file}.bak-preflight-${stamp}-${n}`
|
|
39
|
-
}
|
|
40
|
-
return candidate
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Backfill missing identity fields on every session-*.json record under dir.
|
|
45
|
-
* Returns { checked, fixed } — `fixed` counts records rewritten (or, in
|
|
46
|
-
* check mode, records that would be rewritten). Unparsable records are
|
|
47
|
-
* warned about on stderr and skipped.
|
|
48
|
-
*/
|
|
49
|
-
export function preflightProjcache(dir, { check = false } = {}) {
|
|
50
|
-
const result = { checked: 0, fixed: 0 }
|
|
51
|
-
if (!fs.existsSync(dir)) return result
|
|
52
|
-
|
|
53
|
-
for (const name of fs.readdirSync(dir)) {
|
|
54
|
-
if (!/^session-.*\.json$/.test(name)) continue
|
|
55
|
-
const file = path.join(dir, name)
|
|
56
|
-
if (!fs.statSync(file).isFile()) continue
|
|
57
|
-
result.checked++
|
|
58
|
-
|
|
59
|
-
let text
|
|
60
|
-
try {
|
|
61
|
-
text = fs.readFileSync(file, 'utf8')
|
|
62
|
-
} catch (err) {
|
|
63
|
-
process.stderr.write(`[preflight-projcache] unreadable ${name}: ${err?.message ?? err}\n`)
|
|
64
|
-
continue
|
|
65
|
-
}
|
|
66
|
-
let obj
|
|
67
|
-
try {
|
|
68
|
-
obj = JSON.parse(text)
|
|
69
|
-
} catch {
|
|
70
|
-
process.stderr.write(`[preflight-projcache] skipping unparsable ${name}\n`)
|
|
71
|
-
continue
|
|
72
|
-
}
|
|
73
|
-
if (!isPlainObject(obj)) continue
|
|
74
|
-
|
|
75
|
-
let identity = obj.identity
|
|
76
|
-
if (identity === undefined) {
|
|
77
|
-
identity = {}
|
|
78
|
-
obj.identity = identity
|
|
79
|
-
}
|
|
80
|
-
if (!isPlainObject(identity)) continue
|
|
81
|
-
|
|
82
|
-
let changed = false
|
|
83
|
-
for (const [field, value] of Object.entries(BACKFILL)) {
|
|
84
|
-
if (identity[field] === undefined) {
|
|
85
|
-
identity[field] = value
|
|
86
|
-
changed = true
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (!changed) continue
|
|
90
|
-
|
|
91
|
-
result.fixed++
|
|
92
|
-
if (check) continue
|
|
93
|
-
try {
|
|
94
|
-
fs.writeFileSync(backupPathFor(file), text)
|
|
95
|
-
const migrated = JSON.stringify(obj, null, 2) + (text.endsWith('\n') ? '\n' : '')
|
|
96
|
-
const tmp = `${file}.tmp-preflight-${process.pid}`
|
|
97
|
-
fs.writeFileSync(tmp, migrated)
|
|
98
|
-
fs.renameSync(tmp, file)
|
|
99
|
-
} catch (err) {
|
|
100
|
-
result.fixed--
|
|
101
|
-
process.stderr.write(`[preflight-projcache] failed to rewrite ${name}: ${err?.message ?? err}\n`)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return result
|
|
105
|
-
}
|
|
23
|
+
import { preflightProjcache, projcacheSessionsDir } from '../lib/preflight-projcache.js'
|
|
106
24
|
|
|
107
25
|
function isMainEntry() {
|
|
108
26
|
try {
|
|
@@ -113,19 +31,13 @@ function isMainEntry() {
|
|
|
113
31
|
}
|
|
114
32
|
}
|
|
115
33
|
|
|
116
|
-
function main() {
|
|
117
|
-
const home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
|
118
|
-
const dir = path.join(home, 'storages', 'session_projcache', 'sessions')
|
|
119
|
-
const check = process.argv.includes('--check')
|
|
120
|
-
const { fixed } = preflightProjcache(dir, { check })
|
|
121
|
-
if (check) process.stdout.write(`${fixed} session_projcache record(s) need migration\n`)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
34
|
// Run the CLI shell only when executed directly — importing this module
|
|
125
|
-
//
|
|
35
|
+
// must have no side effects.
|
|
126
36
|
if (isMainEntry()) {
|
|
127
37
|
try {
|
|
128
|
-
|
|
38
|
+
const check = process.argv.includes('--check')
|
|
39
|
+
const { fixed } = preflightProjcache(projcacheSessionsDir(), { check })
|
|
40
|
+
if (check) process.stdout.write(`${fixed} session_projcache record(s) need migration\n`)
|
|
129
41
|
} catch (err) {
|
|
130
42
|
// Never block startup: a preflight failure is a warning, not an error.
|
|
131
43
|
process.stderr.write(`[preflight-projcache] skipped: ${err?.message ?? err}\n`)
|
package/cordis.patch.yml
CHANGED
|
@@ -14,9 +14,36 @@
|
|
|
14
14
|
# the bundle to be wired into the model-facing tool catalog. Without this
|
|
15
15
|
# insert, the `ask_user_question` tool never registers with the model and
|
|
16
16
|
# the user-questions seam has no visible surface in this profile.
|
|
17
|
+
#
|
|
18
|
+
# The stock session-projection-cache row is REPLACED (disable + wrapper), not
|
|
19
|
+
# extended: the alpha.4 record schema fail-fasts at storage-open on records
|
|
20
|
+
# written by 0.1.1-rc.2-era hosts (missing identity.isSeeded /
|
|
21
|
+
# identity.inheritedEventCount), and the crash happens inside the stock
|
|
22
|
+
# plugin's own Service.init — the loader collects it from the parallel
|
|
23
|
+
# allSettled and tears down the whole boot before any later-initializing
|
|
24
|
+
# plugin could repair the data. The wrapper entry below (tui-pi-projcache)
|
|
25
|
+
# mounts @aiwayds/dsh-tui-pi/projcache, which backfills the legacy fields at
|
|
26
|
+
# module-evaluation time — the one hook the loader reaches strictly before
|
|
27
|
+
# Service.init — and otherwise re-exports the stock module unchanged.
|
|
28
|
+
#
|
|
29
|
+
# The `name` field on the disable patch is a guard, not an override: if a
|
|
30
|
+
# future host renames or drops the row, the patch is skipped with a loader
|
|
31
|
+
# warning instead of silently disabling an unrelated entry.
|
|
32
|
+
|
|
33
|
+
- id: session-projection-cache
|
|
34
|
+
name: '@deepseek-ai/dsh-session-projection-cache'
|
|
35
|
+
disabled: true
|
|
17
36
|
|
|
18
37
|
- insert:
|
|
19
38
|
- id: tui-pi
|
|
20
39
|
name: '@aiwayds/dsh-tui-pi'
|
|
21
40
|
- id: tool-ask-user
|
|
22
41
|
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
42
|
+
# Config mirrors the stock row's values (the plugin's Config schema
|
|
43
|
+
# requires both fields); a later upstream retune of the base row does not
|
|
44
|
+
# propagate through a disabled entry, so revisit on host upgrades.
|
|
45
|
+
- id: tui-pi-projcache
|
|
46
|
+
name: '@aiwayds/dsh-tui-pi/projcache'
|
|
47
|
+
config:
|
|
48
|
+
writeEveryEvents: 200
|
|
49
|
+
writeIntervalMs: 5000
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session_projcache record migration, shared by both mount points:
|
|
3
|
+
*
|
|
4
|
+
* - `bin/preflight-projcache.mjs` — the CLI shell the `dsh-tui-pi` launcher
|
|
5
|
+
* runs before `exec dsh` (covers users starting through the launcher);
|
|
6
|
+
* - `src/projcache.ts` — the wrapper module the bundle patch mounts in place
|
|
7
|
+
* of the stock `session-projection-cache` row (covers every `dsh --profile
|
|
8
|
+
* tui` boot, launcher or not).
|
|
9
|
+
*
|
|
10
|
+
* The dsh 0.1.2-alpha.4 projection-cache schema hard-requires
|
|
11
|
+
* `identity.isSeeded: boolean` and `identity.inheritedEventCount: number`;
|
|
12
|
+
* records written by 0.1.1-rc.2-era hosts lack those fields and fail zod
|
|
13
|
+
* validation at storage-open time (`invalid-record`), which crashes the
|
|
14
|
+
* whole boot. The migration backfills exactly the missing fields, backs up
|
|
15
|
+
* every rewritten file next to the original, and always fails open — any
|
|
16
|
+
* error is a stderr warning, never a startup blocker.
|
|
17
|
+
*/
|
|
18
|
+
export interface PreflightResult {
|
|
19
|
+
checked: number;
|
|
20
|
+
fixed: number;
|
|
21
|
+
}
|
|
22
|
+
export interface PreflightOptions {
|
|
23
|
+
/** Report what would change without touching anything. */
|
|
24
|
+
check?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The per-record sessions directory the stock plugin's storage domain opens
|
|
28
|
+
* at boot: `<DSH_HOME || ~/.dsh>/storages/session_projcache/sessions`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function projcacheSessionsDir(home?: string): string;
|
|
31
|
+
/**
|
|
32
|
+
* Backfill missing identity fields on every `session-*.json` record in
|
|
33
|
+
* `dir`. Already-migrated records are left byte-identical (no backup, no
|
|
34
|
+
* rewrite); unparsable or unreadable records are warned about and skipped;
|
|
35
|
+
* rewrite failures roll that record's count back and warn. Never throws for
|
|
36
|
+
* per-record problems — only a catastrophic `dir` scan failure propagates,
|
|
37
|
+
* and both callers catch it.
|
|
38
|
+
*/
|
|
39
|
+
export declare function preflightProjcache(dir: string, { check }?: PreflightOptions): PreflightResult;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session_projcache record migration, shared by both mount points:
|
|
3
|
+
*
|
|
4
|
+
* - `bin/preflight-projcache.mjs` — the CLI shell the `dsh-tui-pi` launcher
|
|
5
|
+
* runs before `exec dsh` (covers users starting through the launcher);
|
|
6
|
+
* - `src/projcache.ts` — the wrapper module the bundle patch mounts in place
|
|
7
|
+
* of the stock `session-projection-cache` row (covers every `dsh --profile
|
|
8
|
+
* tui` boot, launcher or not).
|
|
9
|
+
*
|
|
10
|
+
* The dsh 0.1.2-alpha.4 projection-cache schema hard-requires
|
|
11
|
+
* `identity.isSeeded: boolean` and `identity.inheritedEventCount: number`;
|
|
12
|
+
* records written by 0.1.1-rc.2-era hosts lack those fields and fail zod
|
|
13
|
+
* validation at storage-open time (`invalid-record`), which crashes the
|
|
14
|
+
* whole boot. The migration backfills exactly the missing fields, backs up
|
|
15
|
+
* every rewritten file next to the original, and always fails open — any
|
|
16
|
+
* error is a stderr warning, never a startup blocker.
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
const BACKFILL = { isSeeded: false, inheritedEventCount: 0 };
|
|
22
|
+
function isPlainObject(value) {
|
|
23
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function errorMessage(err) {
|
|
26
|
+
return err instanceof Error ? err.message : String(err);
|
|
27
|
+
}
|
|
28
|
+
function warn(message) {
|
|
29
|
+
process.stderr.write(`[preflight-projcache] ${message}\n`);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The per-record sessions directory the stock plugin's storage domain opens
|
|
33
|
+
* at boot: `<DSH_HOME || ~/.dsh>/storages/session_projcache/sessions`.
|
|
34
|
+
*/
|
|
35
|
+
export function projcacheSessionsDir(home = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')) {
|
|
36
|
+
return path.join(home, 'storages', 'session_projcache', 'sessions');
|
|
37
|
+
}
|
|
38
|
+
function backupPathFor(file) {
|
|
39
|
+
const stamp = new Date().toISOString();
|
|
40
|
+
let candidate = `${file}.bak-preflight-${stamp}`;
|
|
41
|
+
for (let n = 2; fs.existsSync(candidate); n++) {
|
|
42
|
+
candidate = `${file}.bak-preflight-${stamp}-${n}`;
|
|
43
|
+
}
|
|
44
|
+
return candidate;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Backfill missing identity fields on every `session-*.json` record in
|
|
48
|
+
* `dir`. Already-migrated records are left byte-identical (no backup, no
|
|
49
|
+
* rewrite); unparsable or unreadable records are warned about and skipped;
|
|
50
|
+
* rewrite failures roll that record's count back and warn. Never throws for
|
|
51
|
+
* per-record problems — only a catastrophic `dir` scan failure propagates,
|
|
52
|
+
* and both callers catch it.
|
|
53
|
+
*/
|
|
54
|
+
export function preflightProjcache(dir, { check = false } = {}) {
|
|
55
|
+
const result = { checked: 0, fixed: 0 };
|
|
56
|
+
if (!fs.existsSync(dir))
|
|
57
|
+
return result;
|
|
58
|
+
for (const name of fs.readdirSync(dir)) {
|
|
59
|
+
if (!/^session-.*\.json$/.test(name))
|
|
60
|
+
continue;
|
|
61
|
+
const file = path.join(dir, name);
|
|
62
|
+
if (!fs.statSync(file).isFile())
|
|
63
|
+
continue;
|
|
64
|
+
result.checked++;
|
|
65
|
+
let text;
|
|
66
|
+
try {
|
|
67
|
+
text = fs.readFileSync(file, 'utf8');
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
warn(`unreadable ${name}: ${errorMessage(err)}`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
let obj;
|
|
74
|
+
try {
|
|
75
|
+
obj = JSON.parse(text);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
warn(`skipping unparsable ${name}`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!isPlainObject(obj))
|
|
82
|
+
continue;
|
|
83
|
+
let identity = obj.identity;
|
|
84
|
+
if (identity === undefined) {
|
|
85
|
+
identity = {};
|
|
86
|
+
obj.identity = identity;
|
|
87
|
+
}
|
|
88
|
+
if (!isPlainObject(identity))
|
|
89
|
+
continue;
|
|
90
|
+
let changed = false;
|
|
91
|
+
for (const [field, value] of Object.entries(BACKFILL)) {
|
|
92
|
+
if (identity[field] === undefined) {
|
|
93
|
+
identity[field] = value;
|
|
94
|
+
changed = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!changed)
|
|
98
|
+
continue;
|
|
99
|
+
result.fixed++;
|
|
100
|
+
if (check)
|
|
101
|
+
continue;
|
|
102
|
+
try {
|
|
103
|
+
fs.writeFileSync(backupPathFor(file), text);
|
|
104
|
+
const migrated = JSON.stringify(obj, null, 2) + (text.endsWith('\n') ? '\n' : '');
|
|
105
|
+
const tmp = `${file}.tmp-preflight-${process.pid}`;
|
|
106
|
+
fs.writeFileSync(tmp, migrated);
|
|
107
|
+
fs.renameSync(tmp, file);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
result.fixed--;
|
|
111
|
+
warn(`failed to rewrite ${name}: ${errorMessage(err)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=preflight-projcache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight-projcache.js","sourceRoot":"","sources":["../src/preflight-projcache.ts"],"names":[],"mappings":"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"}
|
package/lib/projcache.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boot-time replacement for the stock `session-projection-cache` bundle row.
|
|
3
|
+
*
|
|
4
|
+
* cordis.patch.yml disables the stock row and inserts an entry mounting this
|
|
5
|
+
* module instead. The alpha.4 schema fail-fasts at storage-open on records
|
|
6
|
+
* written by 0.1.1-rc.2-era hosts, and that happens inside the stock
|
|
7
|
+
* plugin's own Service.init — no later-initializing plugin can intercept it,
|
|
8
|
+
* the loader tears down the whole boot. Module evaluation is the only hook
|
|
9
|
+
* the loader reaches strictly before Service.init, so the migration runs
|
|
10
|
+
* here, at import time, backfilling the legacy records on disk before the
|
|
11
|
+
* stock code opens its storage domain.
|
|
12
|
+
*
|
|
13
|
+
* The stock module itself has no import-time side effects (class and zod
|
|
14
|
+
* schema definitions only), so the hoisted re-export below evaluating first
|
|
15
|
+
* is harmless. Migration is fail-open: any error warns on stderr and leaves
|
|
16
|
+
* the boot exactly where it would have been without the wrapper.
|
|
17
|
+
*
|
|
18
|
+
* Consumers of the stock package's named exports import
|
|
19
|
+
* `@deepseek-ai/dsh-session-projection-cache` directly and share this same
|
|
20
|
+
* module instance; only the loader reaches the plugin through here.
|
|
21
|
+
*/
|
|
22
|
+
import { preflightProjcache, projcacheSessionsDir } from './preflight-projcache.js';
|
|
23
|
+
try {
|
|
24
|
+
preflightProjcache(projcacheSessionsDir());
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28
|
+
process.stderr.write(`[preflight-projcache] skipped: ${message}\n`);
|
|
29
|
+
}
|
|
30
|
+
export * from '@deepseek-ai/dsh-session-projection-cache';
|
|
31
|
+
export { default } from '@deepseek-ai/dsh-session-projection-cache';
|
|
32
|
+
//# sourceMappingURL=projcache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projcache.js","sourceRoot":"","sources":["../src/projcache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAA;AAEnF,IAAI,CAAC;IACH,kBAAkB,CAAC,oBAAoB,EAAE,CAAC,CAAA;AAC5C,CAAC;AAAC,OAAO,GAAG,EAAE,CAAC;IACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,OAAO,IAAI,CAAC,CAAA;AACrE,CAAC;AAED,cAAc,2CAA2C,CAAA;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,2CAA2C,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwayds/dsh-tui-pi",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "pi-style terminal UI for DeepSeek Harness (dsh) — pi-tui look & feel, dsh slash commands, GitHub light/dark themes, powerline footer",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,6 +19,18 @@
|
|
|
19
19
|
"type": "module",
|
|
20
20
|
"main": "lib/index.js",
|
|
21
21
|
"types": "lib/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./lib/index.d.ts",
|
|
25
|
+
"default": "./lib/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./projcache": {
|
|
28
|
+
"types": "./lib/projcache.d.ts",
|
|
29
|
+
"default": "./lib/projcache.js"
|
|
30
|
+
},
|
|
31
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
22
34
|
"bin": {
|
|
23
35
|
"dsh-tui-pi": "bin/dsh-tui-pi"
|
|
24
36
|
},
|
|
@@ -46,10 +58,14 @@
|
|
|
46
58
|
"smoke": "node scripts/smoke-boot.mjs"
|
|
47
59
|
},
|
|
48
60
|
"dependencies": {
|
|
49
|
-
"@aiwayds/dsh-ask-router": "^0.
|
|
50
|
-
"@aiwayds/dsh-dcp": "^0.
|
|
51
|
-
"@aiwayds/dsh-llm-
|
|
52
|
-
"@aiwayds/dsh-
|
|
61
|
+
"@aiwayds/dsh-ask-router": "^0.3.0",
|
|
62
|
+
"@aiwayds/dsh-dcp": "^0.6.0",
|
|
63
|
+
"@aiwayds/dsh-llm-proxy": "^0.3.0",
|
|
64
|
+
"@aiwayds/dsh-llm-stats": "^0.4.0",
|
|
65
|
+
"@aiwayds/dsh-mcp-adapter": "^0.3.0",
|
|
66
|
+
"@aiwayds/dsh-model-sync": "^0.2.1",
|
|
67
|
+
"@aiwayds/dsh-subagent-registry": "^0.5.0",
|
|
68
|
+
"@aiwayds/dsh-web-search-anysearch": "^0.2.0",
|
|
53
69
|
"@earendil-works/pi-tui": "0.84.4"
|
|
54
70
|
},
|
|
55
71
|
"peerDependencies": {
|
package/scripts/smoke-boot.mjs
CHANGED
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
// dsh.profile.bundles entry (same shape as the user's real profiles)
|
|
9
9
|
// 3. pnpm install
|
|
10
10
|
// 4. `dsh --profile smoke --dump-config` must compose the plugin into the
|
|
11
|
-
// tree (mount/patch-layer proof)
|
|
12
|
-
//
|
|
11
|
+
// tree (mount/patch-layer proof), disable the stock projection-cache
|
|
12
|
+
// row and mount the projcache wrapper in its place
|
|
13
|
+
// 5. a legacy session_projcache record (the 0.1.1-rc.2 shape that the
|
|
14
|
+
// alpha.4 schema fail-fasts on) is seeded into the scratch home; the
|
|
15
|
+
// boot below must migrate it, not crash on it
|
|
16
|
+
// 6. a real boot under a timeout must load the plugin tree without a
|
|
13
17
|
// loader error (a healthy boot is silent and survives to the kill
|
|
14
|
-
// signal; a broken plugin dies within ~1s with the loader error)
|
|
18
|
+
// signal; a broken plugin dies within ~1s with the loader error), and
|
|
19
|
+
// the seeded record must come out backfilled + backed up
|
|
15
20
|
//
|
|
16
21
|
// The boot runs piped (no TTY). Verified empirically: the TUI plugin's
|
|
17
22
|
// apply() tolerates a non-terminal (pi-tui guards raw mode), so the piped
|
|
@@ -21,7 +26,7 @@
|
|
|
21
26
|
// removed on success.
|
|
22
27
|
|
|
23
28
|
import { spawnSync } from 'node:child_process'
|
|
24
|
-
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
29
|
+
import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
25
30
|
import { readFile } from 'node:fs/promises'
|
|
26
31
|
import { tmpdir } from 'node:os'
|
|
27
32
|
import path from 'node:path'
|
|
@@ -75,12 +80,32 @@ if (install.status !== 0 || install.error) fail('pnpm install in the scratch pro
|
|
|
75
80
|
|
|
76
81
|
const dshEnv = { ...process.env, DSH_HOME: home, TERM: process.env.TERM ?? 'xterm-256color' }
|
|
77
82
|
|
|
78
|
-
// Phase 1 — mount proof: the composed tree must include the plugin
|
|
83
|
+
// Phase 1 — mount proof: the composed tree must include the plugin, disable
|
|
84
|
+
// the stock projection-cache row and mount the projcache wrapper instead.
|
|
79
85
|
const dump = spawnSync('dsh', ['--profile', 'smoke', '--dump-config'], { cwd: profile, encoding: 'utf8', env: dshEnv })
|
|
80
86
|
if (dump.status !== 0 || dump.error) fail('dsh --dump-config failed on the scratch profile', `${dump.stdout}\n${dump.stderr}`)
|
|
81
87
|
if (!dump.stdout.includes(ownName)) {
|
|
82
88
|
fail(`the composed profile tree does not contain ${ownName} — the bundle patch insert is broken`, dump.stdout)
|
|
83
89
|
}
|
|
90
|
+
if (!/id: session-projection-cache[\s\S]*?disabled: true/.test(dump.stdout)) {
|
|
91
|
+
fail('the stock session-projection-cache row is not disabled — the wrapper would race a second mount', dump.stdout)
|
|
92
|
+
}
|
|
93
|
+
if (!dump.stdout.includes('tui-pi-projcache')) {
|
|
94
|
+
fail('the projcache wrapper entry is missing from the composed tree — legacy records would crash the boot', dump.stdout)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Phase 1.5 — seed a legacy projection-cache record: the 0.1.1-rc.2 shape
|
|
98
|
+
// lacks the identity fields the alpha.4 schema requires. The boot in phase 2
|
|
99
|
+
// must migrate it (lib/projcache.js, module-evaluation time) instead of
|
|
100
|
+
// dying on it at storage-open.
|
|
101
|
+
const sessionsDir = path.join(home, 'storages', 'session_projcache', 'sessions')
|
|
102
|
+
mkdirSync(sessionsDir, { recursive: true })
|
|
103
|
+
const legacyName = 'session-smoke-legacy.json'
|
|
104
|
+
const legacyOriginal = JSON.stringify({
|
|
105
|
+
identity: { createdAt: 1756000000000, cwd: '/tmp/smoke' },
|
|
106
|
+
events: [{ seq: 1 }],
|
|
107
|
+
}, null, 2)
|
|
108
|
+
writeFileSync(path.join(sessionsDir, legacyName), legacyOriginal)
|
|
84
109
|
|
|
85
110
|
// Phase 2 — boot proof: the plugin tree must LOAD without a loader error.
|
|
86
111
|
const bootSeconds = 25
|
|
@@ -95,6 +120,7 @@ const output = `${boot.stdout ?? ''}\n${boot.stderr ?? ''}`
|
|
|
95
120
|
const loaderErrors = [
|
|
96
121
|
/plugin tree failed to load/,
|
|
97
122
|
/failed to apply loader entry/,
|
|
123
|
+
/does not match its schema/,
|
|
98
124
|
/cannot get property ".*" without inject/,
|
|
99
125
|
/cannot get required service/,
|
|
100
126
|
/Cannot find (package|module)/,
|
|
@@ -113,5 +139,29 @@ if (!survived && boot.status !== 0) {
|
|
|
113
139
|
fail(`dsh exited early with code ${boot.status} and no loader error — unexpected`, output)
|
|
114
140
|
}
|
|
115
141
|
|
|
116
|
-
|
|
142
|
+
// Phase 3 — rescue proof: the seeded legacy record must have been backfilled
|
|
143
|
+
// by the wrapper before the stock plugin could open the domain, with the
|
|
144
|
+
// original bytes backed up next to it.
|
|
145
|
+
const legacyNow = path.join(sessionsDir, legacyName)
|
|
146
|
+
const backups = (() => {
|
|
147
|
+
try {
|
|
148
|
+
return readdirSync(sessionsDir).filter((n) => n.startsWith(`${legacyName}.bak-preflight-`))
|
|
149
|
+
} catch {
|
|
150
|
+
return []
|
|
151
|
+
}
|
|
152
|
+
})()
|
|
153
|
+
if (!backups.length) {
|
|
154
|
+
fail('the seeded legacy record was never migrated — the wrapper did not run at module-evaluation time', output)
|
|
155
|
+
}
|
|
156
|
+
let migrated
|
|
157
|
+
try {
|
|
158
|
+
migrated = JSON.parse(readFileSync(legacyNow, 'utf8'))
|
|
159
|
+
} catch (err) {
|
|
160
|
+
fail(`the migrated legacy record is unparsable: ${err.message}`)
|
|
161
|
+
}
|
|
162
|
+
if (migrated.identity?.isSeeded !== false || migrated.identity?.inheritedEventCount !== 0) {
|
|
163
|
+
fail('the seeded legacy record survived the boot without being backfilled', JSON.stringify(migrated.identity))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(`smoke-boot: PASS — ${ownName} composed into the scratch profile tree and booted clean in real dsh (${survived ? `survived the ${bootSeconds}s boot window` : `exited ${boot.status}`}); the seeded legacy projection-cache record was migrated with a backup`)
|
|
117
167
|
rmSync(work, { recursive: true, force: true })
|
package/lib/model-sync.d.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /model-sync (singular) — discover models for CUSTOM provider routes and
|
|
3
|
-
* merge them back into the llm-pi-ai settings section.
|
|
4
|
-
*
|
|
5
|
-
* A custom route is a hand-declared `llm-pi-ai.providers.<id>` profile that
|
|
6
|
-
* carries a `baseURL` and is NOT one of the built-in catalog routes
|
|
7
|
-
* (provider-catalog.ts): discovery against a catalog route would short-circuit
|
|
8
|
-
* back to the installed pi-ai catalog. This command interrogates
|
|
9
|
-
* each route's `GET ${baseURL}/models` through the official seam
|
|
10
|
-
* (`LlmRuntime.discoverModels`, registered for the `llm-pi-ai` namespace by
|
|
11
|
-
* dsh-llm-pi-ai with catalog short-circuit + stored-credential resolution),
|
|
12
|
-
* then merges the answer into the profile's `models` array.
|
|
13
|
-
*
|
|
14
|
-
* The merge is additive-only: existing entries are preserved verbatim (local
|
|
15
|
-
* overrides such as reasoningEfforts survive), discovered ids already present
|
|
16
|
-
* are skipped, new ids are appended with sanitized metadata. The write goes
|
|
17
|
-
* through `settings.mutate` at the revision read at execution time, with one
|
|
18
|
-
* retry after SettingsConflictError (the persistDefaultModel pattern in
|
|
19
|
-
* session.ts). Pure logic lives here as exported functions; src/index.ts only
|
|
20
|
-
* wires the command.
|
|
21
|
-
*
|
|
22
|
-
* @module dsh-tui-pi/model-sync
|
|
23
|
-
*/
|
|
24
|
-
import type { SettingsProvider } from '@deepseek-ai/dsh-settings';
|
|
25
|
-
import type { LlmRuntime } from '@deepseek-ai/dsh-llm';
|
|
26
|
-
/**
|
|
27
|
-
* The services /model-sync needs, narrowed to the exact surface it touches so
|
|
28
|
-
* tests can supply fakes without a live tree.
|
|
29
|
-
*/
|
|
30
|
-
export interface ModelSyncDeps {
|
|
31
|
-
/** The settings provider (describe for revisions, mutate for the write). */
|
|
32
|
-
settings: Pick<SettingsProvider, 'describe' | 'mutate'>;
|
|
33
|
-
/** The LLM runtime whose discoverModels serves the `llm-pi-ai` namespace. */
|
|
34
|
-
llm: Pick<LlmRuntime, 'discoverModels'>;
|
|
35
|
-
}
|
|
36
|
-
/** One models entry as stored in a profile — passed through untouched. */
|
|
37
|
-
type StoredModel = Record<string, unknown>;
|
|
38
|
-
/**
|
|
39
|
-
* A custom route selected for sync: the dict key plus the endpoint facts
|
|
40
|
-
* discovery needs. `api` is optional — discovery defaults the protocol.
|
|
41
|
-
*/
|
|
42
|
-
export interface CustomProviderRoute {
|
|
43
|
-
id: string;
|
|
44
|
-
baseURL: string;
|
|
45
|
-
api?: string;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* A sanitized discovered model — the writable shape of one endpoint row:
|
|
49
|
-
* always an id; name/capacities carried only when present and valid.
|
|
50
|
-
*/
|
|
51
|
-
export interface SanitizedModel {
|
|
52
|
-
id: string;
|
|
53
|
-
name?: string;
|
|
54
|
-
contextWindow?: number;
|
|
55
|
-
maxTokens?: number;
|
|
56
|
-
}
|
|
57
|
-
/** Outcome of merging one route's discovery into its stored models. */
|
|
58
|
-
export interface ModelSyncMergeResult {
|
|
59
|
-
/**
|
|
60
|
-
* The merged models array to store: existing entries first in their stored
|
|
61
|
-
* order, newly discovered ids appended at the end in discovery order.
|
|
62
|
-
*/
|
|
63
|
-
models: StoredModel[];
|
|
64
|
-
/** Discovered rows appended as new entries. */
|
|
65
|
-
added: number;
|
|
66
|
-
/** Existing entries preserved verbatim. */
|
|
67
|
-
kept: number;
|
|
68
|
-
/**
|
|
69
|
-
* Discovered rows dropped entirely: unusable id, or an id already covered
|
|
70
|
-
* by an existing entry (local state wins) or by an earlier row of this batch.
|
|
71
|
-
*/
|
|
72
|
-
skipped: number;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Select the custom (hand-declared) routes worth syncing from a providers
|
|
76
|
-
* dict: profiles carrying a non-empty `baseURL` whose key is not a built-in
|
|
77
|
-
* catalog route — a catalog key would short-circuit discovery back to the
|
|
78
|
-
* installed catalog. Sorted by route key so the
|
|
79
|
-
* report order is stable regardless of document order.
|
|
80
|
-
*/
|
|
81
|
-
export declare function selectCustomProviders(providers: Record<string, unknown> | undefined): CustomProviderRoute[];
|
|
82
|
-
/**
|
|
83
|
-
* Clean one discovered row into a writable models entry. The id is mandatory
|
|
84
|
-
* (blank/absent → undefined); name must be a non-blank string; capacities
|
|
85
|
-
* must be integers ≥ 1 — the llm-pi-ai schema rejects anything else at write
|
|
86
|
-
* time (`contextWindow >= 1`, integer step), and a field that fails stays
|
|
87
|
-
* behind rather than sinking the whole row (verified against the shipped
|
|
88
|
-
* schema: absent name/contextWindow/maxTokens validate fine).
|
|
89
|
-
*/
|
|
90
|
-
export declare function sanitizeDiscoveredModel(raw: unknown): SanitizedModel | undefined;
|
|
91
|
-
/**
|
|
92
|
-
* Merge one route's discovery into its stored models — additive-only:
|
|
93
|
-
*
|
|
94
|
-
* - existing entries are the base and are preserved VERBATIM (local overrides
|
|
95
|
-
* like reasoningEfforts/input/compat survive; nothing is overwritten or
|
|
96
|
-
* deleted);
|
|
97
|
-
* - discovered rows join by id: an id already present (or repeated within the
|
|
98
|
-
* batch) counts as skipped, local state wins;
|
|
99
|
-
* - new ids are appended with `sanitizeDiscoveredModel`'s cleaned fields;
|
|
100
|
-
* - the stored order is never touched: existing entries keep their positions
|
|
101
|
-
* (id-less rows included) and new ids join at the end in discovery order.
|
|
102
|
-
*/
|
|
103
|
-
export declare function mergeModels(existing: ReadonlyArray<object>, discovered: ReadonlyArray<unknown>): ModelSyncMergeResult;
|
|
104
|
-
/**
|
|
105
|
-
* Run one /model-sync round: select the target routes (the rawInput-named one,
|
|
106
|
-
* or every custom route), discover each endpoint's models, merge, and write
|
|
107
|
-
* back. One route's failure never aborts the rest — its line reports the
|
|
108
|
-
* reason instead. Agentless by design: nothing here touches a session.
|
|
109
|
-
*
|
|
110
|
-
* @returns the command result — per-provider `<id>: added N · kept M ·
|
|
111
|
-
* skipped K` lines on success, `<id>: <reason>` for failed routes; `kind:
|
|
112
|
-
* 'error'` only for preconditions (missing namespace, unknown/non-custom
|
|
113
|
-
* named route) or when EVERY route failed.
|
|
114
|
-
*/
|
|
115
|
-
export declare function runModelSync(deps: ModelSyncDeps, options?: {
|
|
116
|
-
rawInput?: string;
|
|
117
|
-
signal?: AbortSignal;
|
|
118
|
-
}): Promise<{
|
|
119
|
-
kind: 'success' | 'error';
|
|
120
|
-
text: string;
|
|
121
|
-
}>;
|
|
122
|
-
export {};
|
package/lib/model-sync.js
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /model-sync (singular) — discover models for CUSTOM provider routes and
|
|
3
|
-
* merge them back into the llm-pi-ai settings section.
|
|
4
|
-
*
|
|
5
|
-
* A custom route is a hand-declared `llm-pi-ai.providers.<id>` profile that
|
|
6
|
-
* carries a `baseURL` and is NOT one of the built-in catalog routes
|
|
7
|
-
* (provider-catalog.ts): discovery against a catalog route would short-circuit
|
|
8
|
-
* back to the installed pi-ai catalog. This command interrogates
|
|
9
|
-
* each route's `GET ${baseURL}/models` through the official seam
|
|
10
|
-
* (`LlmRuntime.discoverModels`, registered for the `llm-pi-ai` namespace by
|
|
11
|
-
* dsh-llm-pi-ai with catalog short-circuit + stored-credential resolution),
|
|
12
|
-
* then merges the answer into the profile's `models` array.
|
|
13
|
-
*
|
|
14
|
-
* The merge is additive-only: existing entries are preserved verbatim (local
|
|
15
|
-
* overrides such as reasoningEfforts survive), discovered ids already present
|
|
16
|
-
* are skipped, new ids are appended with sanitized metadata. The write goes
|
|
17
|
-
* through `settings.mutate` at the revision read at execution time, with one
|
|
18
|
-
* retry after SettingsConflictError (the persistDefaultModel pattern in
|
|
19
|
-
* session.ts). Pure logic lives here as exported functions; src/index.ts only
|
|
20
|
-
* wires the command.
|
|
21
|
-
*
|
|
22
|
-
* @module dsh-tui-pi/model-sync
|
|
23
|
-
*/
|
|
24
|
-
import { settingsNamespace, SettingsConflictError } from '@deepseek-ai/dsh-settings';
|
|
25
|
-
import { catalogEntry } from "./provider-catalog.js";
|
|
26
|
-
/** The settings namespace dsh-llm-pi-ai owns its provider routes under. */
|
|
27
|
-
const NS_LLM_PI_AI = settingsNamespace('llm-pi-ai');
|
|
28
|
-
/**
|
|
29
|
-
* Read the providers dict out of an llm-pi-ai descriptor value. Tolerant of
|
|
30
|
-
* every malformed shape — a section that is not an object yields no routes.
|
|
31
|
-
*/
|
|
32
|
-
function readProviders(value) {
|
|
33
|
-
if (typeof value !== 'object' || value === null)
|
|
34
|
-
return {};
|
|
35
|
-
const providers = value.providers;
|
|
36
|
-
if (typeof providers !== 'object' || providers === null)
|
|
37
|
-
return {};
|
|
38
|
-
return providers;
|
|
39
|
-
}
|
|
40
|
-
function readProfile(raw) {
|
|
41
|
-
const source = typeof raw === 'object' && raw !== null ? raw : {};
|
|
42
|
-
const baseURL = typeof source.baseURL === 'string' && source.baseURL !== '' ? source.baseURL : undefined;
|
|
43
|
-
const api = typeof source.api === 'string' && source.api !== '' ? source.api : undefined;
|
|
44
|
-
const rawModels = Array.isArray(source.models) ? source.models : [];
|
|
45
|
-
// Only well-formed entries are merged against; a malformed stored row is
|
|
46
|
-
// left alone rather than deleted (additive-only merge).
|
|
47
|
-
const models = rawModels.filter((entry) => typeof entry === 'object' && entry !== null);
|
|
48
|
-
return { ...(baseURL !== undefined ? { baseURL } : {}), ...(api !== undefined ? { api } : {}), models };
|
|
49
|
-
}
|
|
50
|
-
/** The stored entry's id, when it is a non-empty string. */
|
|
51
|
-
function storedModelId(entry) {
|
|
52
|
-
const id = entry.id;
|
|
53
|
-
return typeof id === 'string' && id !== '' ? id : undefined;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Select the custom (hand-declared) routes worth syncing from a providers
|
|
57
|
-
* dict: profiles carrying a non-empty `baseURL` whose key is not a built-in
|
|
58
|
-
* catalog route — a catalog key would short-circuit discovery back to the
|
|
59
|
-
* installed catalog. Sorted by route key so the
|
|
60
|
-
* report order is stable regardless of document order.
|
|
61
|
-
*/
|
|
62
|
-
export function selectCustomProviders(providers) {
|
|
63
|
-
const selected = [];
|
|
64
|
-
for (const [id, raw] of Object.entries(providers ?? {})) {
|
|
65
|
-
if (catalogEntry(id) !== undefined)
|
|
66
|
-
continue;
|
|
67
|
-
const profile = readProfile(raw);
|
|
68
|
-
if (profile.baseURL === undefined)
|
|
69
|
-
continue;
|
|
70
|
-
selected.push({
|
|
71
|
-
id,
|
|
72
|
-
baseURL: profile.baseURL,
|
|
73
|
-
...(profile.api !== undefined ? { api: profile.api } : {}),
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
selected.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
77
|
-
return selected;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Clean one discovered row into a writable models entry. The id is mandatory
|
|
81
|
-
* (blank/absent → undefined); name must be a non-blank string; capacities
|
|
82
|
-
* must be integers ≥ 1 — the llm-pi-ai schema rejects anything else at write
|
|
83
|
-
* time (`contextWindow >= 1`, integer step), and a field that fails stays
|
|
84
|
-
* behind rather than sinking the whole row (verified against the shipped
|
|
85
|
-
* schema: absent name/contextWindow/maxTokens validate fine).
|
|
86
|
-
*/
|
|
87
|
-
export function sanitizeDiscoveredModel(raw) {
|
|
88
|
-
if (typeof raw !== 'object' || raw === null)
|
|
89
|
-
return undefined;
|
|
90
|
-
const row = raw;
|
|
91
|
-
if (typeof row.id !== 'string')
|
|
92
|
-
return undefined;
|
|
93
|
-
const id = row.id.trim();
|
|
94
|
-
if (id === '')
|
|
95
|
-
return undefined;
|
|
96
|
-
const name = typeof row.name === 'string' && row.name.trim() !== '' ? row.name.trim() : undefined;
|
|
97
|
-
const capacity = (value) => typeof value === 'number' && Number.isInteger(value) && value >= 1 ? value : undefined;
|
|
98
|
-
const contextWindow = capacity(row.contextWindow);
|
|
99
|
-
const maxTokens = capacity(row.maxTokens);
|
|
100
|
-
return {
|
|
101
|
-
id,
|
|
102
|
-
...(name !== undefined ? { name } : {}),
|
|
103
|
-
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
104
|
-
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Merge one route's discovery into its stored models — additive-only:
|
|
109
|
-
*
|
|
110
|
-
* - existing entries are the base and are preserved VERBATIM (local overrides
|
|
111
|
-
* like reasoningEfforts/input/compat survive; nothing is overwritten or
|
|
112
|
-
* deleted);
|
|
113
|
-
* - discovered rows join by id: an id already present (or repeated within the
|
|
114
|
-
* batch) counts as skipped, local state wins;
|
|
115
|
-
* - new ids are appended with `sanitizeDiscoveredModel`'s cleaned fields;
|
|
116
|
-
* - the stored order is never touched: existing entries keep their positions
|
|
117
|
-
* (id-less rows included) and new ids join at the end in discovery order.
|
|
118
|
-
*/
|
|
119
|
-
export function mergeModels(existing, discovered) {
|
|
120
|
-
const models = existing.map(entry => entry);
|
|
121
|
-
const ids = new Set();
|
|
122
|
-
for (const entry of models) {
|
|
123
|
-
const id = storedModelId(entry);
|
|
124
|
-
if (id !== undefined)
|
|
125
|
-
ids.add(id);
|
|
126
|
-
}
|
|
127
|
-
let added = 0;
|
|
128
|
-
let skipped = 0;
|
|
129
|
-
for (const raw of discovered) {
|
|
130
|
-
const clean = sanitizeDiscoveredModel(raw);
|
|
131
|
-
if (clean === undefined || ids.has(clean.id)) {
|
|
132
|
-
skipped += 1;
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
ids.add(clean.id);
|
|
136
|
-
// A fresh literal (not a cast): interfaces carry no implicit index
|
|
137
|
-
// signature, so the entry must be rebuilt as a plain record.
|
|
138
|
-
models.push({ ...clean });
|
|
139
|
-
added += 1;
|
|
140
|
-
}
|
|
141
|
-
// No reordering: existing entries stay exactly where the user stored them
|
|
142
|
-
// and new ids are appended at the end in discovery order — a no-op sync
|
|
143
|
-
// therefore leaves the stored array byte-identical.
|
|
144
|
-
return { models, added, kept: existing.length, skipped };
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Write one route's merged models with optimistic concurrency: mutate at the
|
|
148
|
-
* revision read NOW (not at command start), one retry after
|
|
149
|
-
* SettingsConflictError with a freshly read revision — the same bottom pattern
|
|
150
|
-
* as persistDefaultModel in session.ts. A second conflict surfaces.
|
|
151
|
-
*/
|
|
152
|
-
async function writeModels(settings, providerId, models) {
|
|
153
|
-
const ops = [
|
|
154
|
-
{ op: 'set', path: ['providers', providerId, 'models'], value: models },
|
|
155
|
-
];
|
|
156
|
-
const ns = NS_LLM_PI_AI;
|
|
157
|
-
for (let attempt = 0;; attempt++) {
|
|
158
|
-
const descriptor = currentDescriptor(settings);
|
|
159
|
-
try {
|
|
160
|
-
await settings.mutate(ns, ops, descriptor?.revision);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
if (attempt === 0 && error instanceof SettingsConflictError)
|
|
165
|
-
continue;
|
|
166
|
-
throw error;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
/** The live llm-pi-ai descriptor, re-read on demand (revision freshness). */
|
|
171
|
-
function currentDescriptor(settings) {
|
|
172
|
-
return settings.describe().find(d => d.ns === NS_LLM_PI_AI);
|
|
173
|
-
}
|
|
174
|
-
function errorText(error) {
|
|
175
|
-
return error instanceof Error ? error.message : String(error);
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Run one /model-sync round: select the target routes (the rawInput-named one,
|
|
179
|
-
* or every custom route), discover each endpoint's models, merge, and write
|
|
180
|
-
* back. One route's failure never aborts the rest — its line reports the
|
|
181
|
-
* reason instead. Agentless by design: nothing here touches a session.
|
|
182
|
-
*
|
|
183
|
-
* @returns the command result — per-provider `<id>: added N · kept M ·
|
|
184
|
-
* skipped K` lines on success, `<id>: <reason>` for failed routes; `kind:
|
|
185
|
-
* 'error'` only for preconditions (missing namespace, unknown/non-custom
|
|
186
|
-
* named route) or when EVERY route failed.
|
|
187
|
-
*/
|
|
188
|
-
export async function runModelSync(deps, options = {}) {
|
|
189
|
-
const descriptor = currentDescriptor(deps.settings);
|
|
190
|
-
if (descriptor === undefined) {
|
|
191
|
-
return { kind: 'error', text: 'The llm-pi-ai settings namespace is not registered — nothing to sync.' };
|
|
192
|
-
}
|
|
193
|
-
const wanted = options.rawInput?.trim() ?? '';
|
|
194
|
-
let targets;
|
|
195
|
-
if (wanted !== '') {
|
|
196
|
-
const providers = readProviders(descriptor.value);
|
|
197
|
-
if (!(wanted in providers)) {
|
|
198
|
-
return { kind: 'error', text: `Provider "${wanted}" is not configured under llm-pi-ai providers.` };
|
|
199
|
-
}
|
|
200
|
-
targets = selectCustomProviders({ [wanted]: providers[wanted] });
|
|
201
|
-
if (targets.length === 0) {
|
|
202
|
-
// Two distinct shapes land here: a built-in catalog key (excluded by
|
|
203
|
-
// selection even when the profile carries a baseURL) and a genuinely
|
|
204
|
-
// non-custom route without a baseURL. Name each accurately.
|
|
205
|
-
if (catalogEntry(wanted) !== undefined) {
|
|
206
|
-
return {
|
|
207
|
-
kind: 'error',
|
|
208
|
-
text: `Provider "${wanted}" is a built-in catalog route — /model-sync syncs hand-declared (baseURL) routes only.`,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
kind: 'error',
|
|
213
|
-
text: `Provider "${wanted}" has no baseURL — /model-sync syncs hand-declared routes only.`,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
targets = selectCustomProviders(readProviders(descriptor.value));
|
|
219
|
-
if (targets.length === 0) {
|
|
220
|
-
return { kind: 'success', text: 'No hand-declared (baseURL) providers configured — nothing to sync.' };
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const lines = [];
|
|
224
|
-
let failures = 0;
|
|
225
|
-
for (const target of targets) {
|
|
226
|
-
try {
|
|
227
|
-
// Re-read the live section per route: an api added/removed between the
|
|
228
|
-
// selection and this write still reaches discovery (and a vanished
|
|
229
|
-
// namespace degrades into that route's failure line, not a crash).
|
|
230
|
-
const liveValue = currentDescriptor(deps.settings)?.value;
|
|
231
|
-
const profile = readProfile(readProviders(liveValue)[target.id]);
|
|
232
|
-
const discovered = await deps.llm.discoverModels(NS_LLM_PI_AI, {
|
|
233
|
-
provider: target.id,
|
|
234
|
-
baseURL: target.baseURL,
|
|
235
|
-
...(profile.api !== undefined ? { api: profile.api } : {}),
|
|
236
|
-
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
237
|
-
});
|
|
238
|
-
const merged = mergeModels(profile.models, discovered);
|
|
239
|
-
// A no-op sync must produce zero settings mutations: write only when
|
|
240
|
-
// discovery actually contributed new ids.
|
|
241
|
-
if (merged.added > 0) {
|
|
242
|
-
await writeModels(deps.settings, target.id, merged.models);
|
|
243
|
-
}
|
|
244
|
-
lines.push(`${target.id}: added ${merged.added} · kept ${merged.kept} · skipped ${merged.skipped}`);
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
failures += 1;
|
|
248
|
-
lines.push(`${target.id}: ${errorText(error)}`);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
if (failures === targets.length && failures > 0) {
|
|
252
|
-
return { kind: 'error', text: lines.join('\n') };
|
|
253
|
-
}
|
|
254
|
-
return { kind: 'success', text: lines.join('\n') };
|
|
255
|
-
}
|
|
256
|
-
//# sourceMappingURL=model-sync.js.map
|
package/lib/model-sync.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"model-sync.js","sourceRoot":"","sources":["../src/model-sync.ts"],"names":[],"mappings":"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"}
|