dsh-fish-memory 1.4.7 → 1.4.9
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/lib/client.js +19 -7
- package/lib/index.js +7 -0
- package/lib/selfupdate.mjs +119 -40
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -132,15 +132,27 @@ const 洗键进度 = { 已: 0, 总: 0, 在跑: false, 干跑: false }
|
|
|
132
132
|
]
|
|
133
133
|
|
|
134
134
|
// ⭐ 版本 / 检查更新 —— 单独一条,不掺进那个大页面(改它的渲染风险太大)
|
|
135
|
-
const 更新命令 = ['dsh plugin --profile web remove dsh-fish-memory', 'dsh plugin --profile web add dsh-fish-memory'].join(String.fromCharCode(10))
|
|
136
135
|
function 更新条() {
|
|
137
136
|
const [up, setUp] = React.useState(null)
|
|
138
137
|
const [busy, setBusy] = React.useState(false)
|
|
138
|
+
const [装况, set装况] = React.useState('')
|
|
139
139
|
const 查 = async () => {
|
|
140
140
|
setBusy(true)
|
|
141
141
|
try { setUp(await get('/update?refresh=1')) } catch (e) { setUp({ ok: false, error: String((e && e.message) || e) }) }
|
|
142
142
|
setBusy(false)
|
|
143
143
|
}
|
|
144
|
+
// ⭐ 2026-11-01:查到新版就一键装(走宿主的 pluginManager.installBundle)
|
|
145
|
+
const 装 = async () => {
|
|
146
|
+
setBusy(true)
|
|
147
|
+
set装况('正在装…')
|
|
148
|
+
try {
|
|
149
|
+
const r = await post('/install', {})
|
|
150
|
+
set装况(r && r.ok ? '已经开始装了 —— 装完重启(或重载)一次 dsh 才生效' : '没装成:' + ((r && r.error) || '不知道原因'))
|
|
151
|
+
} catch (e) {
|
|
152
|
+
set装况('没装成:' + ((e && e.message) || e))
|
|
153
|
+
}
|
|
154
|
+
setBusy(false)
|
|
155
|
+
}
|
|
144
156
|
React.useEffect(() => { get('/update').then((r) => setUp(r)).catch(() => {}) }, [])
|
|
145
157
|
const 说 = !up ? '正在看有没有新版…'
|
|
146
158
|
: up.hasUpdate ? '仓库里已经有 v' + up.latest + ' 了'
|
|
@@ -158,15 +170,15 @@ const 洗键进度 = { 已: 0, 总: 0, 在跑: false, 干跑: false }
|
|
|
158
170
|
? React.createElement(
|
|
159
171
|
'div',
|
|
160
172
|
null,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
style: S.
|
|
165
|
-
|
|
166
|
-
}, '复制命令'),
|
|
173
|
+
(up && up.canInstall)
|
|
174
|
+
? React.createElement('button', { style: S.btn, disabled: busy, onClick: 装 },
|
|
175
|
+
busy ? '装…' : '一键更新到 v' + up.latest)
|
|
176
|
+
: React.createElement('div', { style: S.dim }, '这是本地开发副本(link: 进来的),请手动更新。'),
|
|
177
|
+
React.createElement('div', { style: S.dim }, 装况 || '装完重启(或重载)一次 dsh 才生效。'),
|
|
167
178
|
)
|
|
168
179
|
: null,
|
|
169
180
|
)
|
|
181
|
+
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
function Section() {
|
package/lib/index.js
CHANGED
|
@@ -1409,12 +1409,19 @@ async function 问模型(ctx, agent, 系统, 用户, 最多, 指定) {
|
|
|
1409
1409
|
return { ok: false, message: '构造消息失败:' + ((error && error.message) || error) }
|
|
1410
1410
|
}
|
|
1411
1411
|
let out = ''
|
|
1412
|
+
// ⭐ 1.4.8:**带上会话号** —— opencode 的网关(opencode.ai/zen/go)要求请求头里有
|
|
1413
|
+
// `x-opencode-session`,不带就一律 `400 MissingSessionID`(记忆就静默不生成)。
|
|
1414
|
+
// 宿主靠这个头把请求路由到「持有这段对话缓存」的节点上;插件自己发起的调用以前没带,
|
|
1415
|
+
// 所以只有普通会话能用。这里把本会话的 id 交出去,和心情插件 0.7.9 的做法一致。
|
|
1416
|
+
let 会话号 = ''
|
|
1417
|
+
try { 会话号 = sessionIdOf(agent && agent.session) } catch { 会话号 = '' }
|
|
1412
1418
|
try {
|
|
1413
1419
|
for await (const chunk of ctx.llm.stream({
|
|
1414
1420
|
provider: t.provider,
|
|
1415
1421
|
model: t.model,
|
|
1416
1422
|
system: 系统,
|
|
1417
1423
|
messages: messages,
|
|
1424
|
+
...(会话号 ? { sessionId: 会话号 } : {}),
|
|
1418
1425
|
maxTokens: 最多 || 600,
|
|
1419
1426
|
temperature: 0.1,
|
|
1420
1427
|
})) {
|
package/lib/selfupdate.mjs
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
// 插件自查更新 ——
|
|
1
|
+
// 插件自查更新 —— 查的是**这个插件自己的仓库**,并且能一键装
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// 每个插件在自己设置页放一个「检查更新」:点一下去自己的仓库 / npm 看最新版本。
|
|
4
4
|
//
|
|
5
5
|
// 原则:
|
|
6
6
|
// · 仓库地址从包里自己的 package.json 读(`repository`),不在代码里写死
|
|
7
|
-
// ·
|
|
8
|
-
//
|
|
7
|
+
// · **多源全查、取最大的那个版本** —— 以前是"谁先返回用谁",GitHub 一超时就落到
|
|
8
|
+
// jsDelivr 那个**带 CDN 缓存**的源上,于是同一分钟能报出两个不同的"最新",甚至比本机还旧
|
|
9
|
+
// · 查不到要如实说"查不到",绝不能显示成"已是最新"
|
|
9
10
|
// · 结果缓存 6 小时;点按钮(refresh)就绕过缓存
|
|
11
|
+
// · 「一键更新」走宿主自己的 pluginManager.installBundle —— 装完由宿主决定要不要重载;
|
|
12
|
+
// **本地开发副本(link:)不给装**,那会把她正在改的目录换成仓库里的版本
|
|
10
13
|
//
|
|
11
|
-
// ⚠
|
|
12
|
-
//
|
|
14
|
+
// ⚠ 这个文件是**复制多份**到各个插件里的同一份实现,只在这一个地方改了要同步给其它插件。
|
|
15
|
+
//
|
|
16
|
+
// 2026-11-01:多源取最大 + 一键更新(install)+ 装完把结果原样带回界面。
|
|
13
17
|
|
|
14
|
-
import { readFileSync } from 'node:fs'
|
|
18
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs'
|
|
19
|
+
import { homedir } from 'node:os'
|
|
15
20
|
import { join } from 'node:path'
|
|
16
21
|
|
|
17
22
|
const TTL = 6 * 3600 * 1000
|
|
@@ -39,17 +44,23 @@ export function cmpVer(a, b) {
|
|
|
39
44
|
return x.pre > y.pre ? 1 : -1
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
|
|
47
|
+
// 三个源都要查:GitHub raw(真值)· jsDelivr(有缓存,可能滞后)· npm registry(发过就有)
|
|
48
|
+
function sources(meta, repo) {
|
|
49
|
+
const out = []
|
|
43
50
|
const s = String(repo || '').trim()
|
|
44
51
|
const m = /github\.com[/:]([^/]+)\/([^/#?]+)/i.exec(s)
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`https://
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
if (m) {
|
|
53
|
+
const owner = m[1]
|
|
54
|
+
const name = m[2].replace(/\.git$/i, '')
|
|
55
|
+
out.push({ url: `https://raw.githubusercontent.com/${owner}/${name}/HEAD/package.json`, from: 'GitHub' })
|
|
56
|
+
out.push({ url: `https://cdn.jsdelivr.net/gh/${owner}/${name}@main/package.json`, from: 'jsDelivr' })
|
|
57
|
+
}
|
|
58
|
+
const pkg = String((meta && meta.name) || '').trim()
|
|
59
|
+
if (pkg) {
|
|
60
|
+
const enc = pkg.startsWith('@') ? '@' + pkg.slice(1).replace('/', '%2F') : encodeURIComponent(pkg)
|
|
61
|
+
out.push({ url: `https://registry.npmjs.org/${enc}/latest`, from: 'npm' })
|
|
62
|
+
}
|
|
63
|
+
return out
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
export function createSelfUpdate({ packageRoot, diag }) {
|
|
@@ -73,30 +84,40 @@ export function createSelfUpdate({ packageRoot, diag }) {
|
|
|
73
84
|
.replace(/\.git$/, '')
|
|
74
85
|
.replace(/#readme$/i, '')
|
|
75
86
|
|
|
87
|
+
// 装在自己的 node_modules 里 = 正常安装;不在 = 本地 link 的开发副本
|
|
88
|
+
const 是开发副本 = !/[\\/]node_modules[\\/]/.test(String(packageRoot))
|
|
89
|
+
|
|
76
90
|
let cache = null
|
|
77
91
|
|
|
78
92
|
async function fetchLatest() {
|
|
79
|
-
const
|
|
80
|
-
if (!
|
|
81
|
-
|
|
82
|
-
for (const url of urls) {
|
|
93
|
+
const list = sources(meta, repo)
|
|
94
|
+
if (!list.length) return { latest: null, error: '包里没写仓库地址,也不像在 npm 上发过,没法查' }
|
|
95
|
+
const 结果 = await Promise.all(list.map(async (s) => {
|
|
83
96
|
try {
|
|
84
|
-
const res = await fetch(url, {
|
|
97
|
+
const res = await fetch(s.url, {
|
|
85
98
|
signal: AbortSignal.timeout(TIMEOUT),
|
|
86
99
|
headers: { accept: 'application/json', 'user-agent': 'dsh-plugin-selfupdate' },
|
|
87
100
|
})
|
|
88
|
-
if (!res.ok) {
|
|
101
|
+
if (!res.ok) return { err: s.from + ' HTTP ' + res.status }
|
|
89
102
|
const j = await res.json()
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
|
|
93
|
-
from: url.includes('jsdelivr') ? 'jsDelivr(有缓存,可能滞后)' : 'GitHub',
|
|
94
|
-
}
|
|
103
|
+
const v = String((j && j.version) || '')
|
|
104
|
+
if (!v) return { err: s.from + ' 没有 version' }
|
|
105
|
+
return { v, from: s.from }
|
|
95
106
|
} catch (e) {
|
|
96
|
-
|
|
107
|
+
return { err: s.from + ' ' + String((e && e.message) || e) }
|
|
97
108
|
}
|
|
109
|
+
}))
|
|
110
|
+
// ⭐ 取**最大**的那个:版本只会往前走,任何一个源报小了都只是它滞后
|
|
111
|
+
let best = null
|
|
112
|
+
let bestFrom = null
|
|
113
|
+
for (const r of 结果) {
|
|
114
|
+
if (!r.v) continue
|
|
115
|
+
if (best === null || cmpVer(r.v, best) > 0) { best = r.v; bestFrom = r.from }
|
|
98
116
|
}
|
|
99
|
-
|
|
117
|
+
if (best === null) {
|
|
118
|
+
return { latest: null, error: 结果.map((r) => r.err).filter(Boolean).join(' · ') || '查询失败' }
|
|
119
|
+
}
|
|
120
|
+
return { latest: best, from: bestFrom }
|
|
100
121
|
}
|
|
101
122
|
|
|
102
123
|
async function check(force) {
|
|
@@ -113,32 +134,90 @@ export function createSelfUpdate({ packageRoot, diag }) {
|
|
|
113
134
|
error: r.error || null,
|
|
114
135
|
from: r.from || null,
|
|
115
136
|
repoUrl: repoUrl || null,
|
|
137
|
+
devCopy: 是开发副本,
|
|
138
|
+
canInstall: !是开发副本,
|
|
116
139
|
checkedAt: Date.now(),
|
|
117
140
|
}
|
|
118
|
-
log('查更新:当前 ' + current + ' / 最新 ' + (r.latest || '查不到') + (r.error ? '(' + r.error + ')' : ''))
|
|
141
|
+
log('查更新:当前 ' + current + ' / 最新 ' + (r.latest || '查不到') + (r.from ? '(' + r.from + ')' : '') + (r.error ? '(' + r.error + ')' : ''))
|
|
119
142
|
return cache
|
|
120
143
|
}
|
|
121
144
|
|
|
145
|
+
// 这个插件是以什么身份装的?从 profile 的 package.json 里把它那行依赖原样取出来 ——
|
|
146
|
+
// 用它当 spec 才是"同源更新",不会把 github 装的换成 npm 版。
|
|
147
|
+
function 取安装来源() {
|
|
148
|
+
const HOME = process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
149
|
+
const name = String(meta.name || '')
|
|
150
|
+
if (!name) return ''
|
|
151
|
+
let profiles = []
|
|
152
|
+
try { profiles = readdirSync(join(HOME, 'profiles')) } catch (e) { return '' }
|
|
153
|
+
for (const p of profiles) {
|
|
154
|
+
const f = join(HOME, 'profiles', p, 'package.json')
|
|
155
|
+
if (!existsSync(f)) continue
|
|
156
|
+
try {
|
|
157
|
+
const j = JSON.parse(readFileSync(f, 'utf8'))
|
|
158
|
+
const dep = String((j.dependencies && j.dependencies[name]) || '')
|
|
159
|
+
if (/^(github:|git\+|https?:)/i.test(dep)) return dep
|
|
160
|
+
} catch (e) { /* 读不了就下一个 */ }
|
|
161
|
+
}
|
|
162
|
+
// 兜底:从 package.json 的仓库地址拼
|
|
163
|
+
const m = /github\.com[/:]([^/]+)\/([^/#?]+)/i.exec(repoUrl)
|
|
164
|
+
if (m) return 'github:' + m[1] + '/' + m[2].replace(/\.git$/i, '')
|
|
165
|
+
return ''
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function install(ctx) {
|
|
169
|
+
if (是开发副本) {
|
|
170
|
+
return { ok: false, error: '这是本地开发副本(link: 进来的),一键更新会把你正在改的目录换成仓库里的版本 —— 要更新请在仓库里 git pull' }
|
|
171
|
+
}
|
|
172
|
+
const pm = ctx && ctx.pluginManager
|
|
173
|
+
if (!pm || typeof pm.installBundle !== 'function') {
|
|
174
|
+
return { ok: false, error: '这版 DSH 没有插件管理接口,装不了;请手动更新' }
|
|
175
|
+
}
|
|
176
|
+
const spec = 取安装来源() || (meta.name ? String(meta.name) : '')
|
|
177
|
+
if (!spec) return { ok: false, error: '不知道该从哪儿装(既没有仓库地址,也没记录安装来源)' }
|
|
178
|
+
try {
|
|
179
|
+
log('一键更新:开始装 ' + spec)
|
|
180
|
+
const r = await pm.installBundle(spec)
|
|
181
|
+
log('一键更新:' + JSON.stringify(r && (r.applied ?? r.application ?? r.reason ?? r)))
|
|
182
|
+
return { ok: true, spec, result: r || null }
|
|
183
|
+
} catch (e) {
|
|
184
|
+
log('一键更新失败:' + String((e && e.message) || e))
|
|
185
|
+
return { ok: false, spec, error: String((e && e.message) || e) }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
122
189
|
function register(ctx, route) {
|
|
190
|
+
const send = (res, code, obj) => {
|
|
191
|
+
res.writeHead(code, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
|
|
192
|
+
res.end(JSON.stringify(obj))
|
|
193
|
+
}
|
|
123
194
|
try {
|
|
124
195
|
ctx.effect(() => ctx.webServer.register({
|
|
125
196
|
kind: 'exact',
|
|
126
197
|
path: route + '/update',
|
|
127
198
|
handler: async (req, res) => {
|
|
128
|
-
const send = (code, obj) => {
|
|
129
|
-
res.writeHead(code, {
|
|
130
|
-
'content-type': 'application/json; charset=utf-8',
|
|
131
|
-
'cache-control': 'no-store',
|
|
132
|
-
})
|
|
133
|
-
res.end(JSON.stringify(obj))
|
|
134
|
-
}
|
|
135
199
|
try {
|
|
136
200
|
const url = new URL(req.url, 'http://127.0.0.1')
|
|
137
201
|
const force = url.searchParams.get('refresh') === '1'
|
|
138
|
-
send(200, { ok: true, ...(await check(force)) })
|
|
202
|
+
send(res, 200, { ok: true, ...(await check(force)) })
|
|
139
203
|
} catch (e) {
|
|
140
204
|
log('查更新路由失败:' + (e && e.message))
|
|
141
|
-
send(200, { ok: false, current, latest: null, error: String((e && e.message) || e) })
|
|
205
|
+
send(res, 200, { ok: false, current, latest: null, error: String((e && e.message) || e) })
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
}))
|
|
209
|
+
// ⭐ 一键更新:POST。装完的结果原样带回去(可能要重启才生效)
|
|
210
|
+
ctx.effect(() => ctx.webServer.register({
|
|
211
|
+
kind: 'exact',
|
|
212
|
+
path: route + '/install',
|
|
213
|
+
handler: async (req, res) => {
|
|
214
|
+
if (req.method !== 'POST') { send(res, 405, { ok: false, error: '只接受 POST' }); return }
|
|
215
|
+
try {
|
|
216
|
+
const r = await install(ctx)
|
|
217
|
+
send(res, 200, r)
|
|
218
|
+
} catch (e) {
|
|
219
|
+
log('一键更新路由失败:' + (e && e.message))
|
|
220
|
+
send(res, 200, { ok: false, error: String((e && e.message) || e) })
|
|
142
221
|
}
|
|
143
222
|
},
|
|
144
223
|
}))
|
|
@@ -147,5 +226,5 @@ export function createSelfUpdate({ packageRoot, diag }) {
|
|
|
147
226
|
}
|
|
148
227
|
}
|
|
149
228
|
|
|
150
|
-
return { current, repo, repoUrl, check, register }
|
|
229
|
+
return { current, repo, repoUrl, check, install, register }
|
|
151
230
|
}
|