dsh-fish-memory 1.4.4 → 1.4.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.
- package/lib/client.js +30 -0
- package/lib/index.js +171 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -3949,6 +3949,36 @@ const 洗键进度 = { 已: 0, 总: 0, 在跑: false, 干跑: false }
|
|
|
3949
3949
|
: '全关着 —— 哪个会话都不记也不读,工具列表里也不会出现它。',
|
|
3950
3950
|
),
|
|
3951
3951
|
),
|
|
3952
|
+
// ⭐ 1.4.5:记忆按什么归类 —— 按会话 / 按工作区
|
|
3953
|
+
React.createElement(
|
|
3954
|
+
'div',
|
|
3955
|
+
{ style: Object.assign({}, S.row, S.条, { alignItems: 'center' }) },
|
|
3956
|
+
React.createElement(
|
|
3957
|
+
'button',
|
|
3958
|
+
{
|
|
3959
|
+
style: Object.assign({}, S.btn, {
|
|
3960
|
+
flex: 'none',
|
|
3961
|
+
color: d.模式 === 'workspace' ? '#8fd0a0' : 'inherit',
|
|
3962
|
+
borderColor: d.模式 === 'workspace' ? 'rgba(143,208,160,.6)' : 'rgba(128,128,128,.35)',
|
|
3963
|
+
}),
|
|
3964
|
+
disabled: busy,
|
|
3965
|
+
onClick: () => {
|
|
3966
|
+
setBusy(true)
|
|
3967
|
+
post('/mode', { 模式: d.模式 === 'workspace' ? 'session' : 'workspace' })
|
|
3968
|
+
.then(() => { setBusy(false); load() })
|
|
3969
|
+
.catch(() => setBusy(false))
|
|
3970
|
+
},
|
|
3971
|
+
},
|
|
3972
|
+
(d.模式 === 'workspace' ? '按工作区' : '按会话'),
|
|
3973
|
+
),
|
|
3974
|
+
React.createElement(
|
|
3975
|
+
'div',
|
|
3976
|
+
{ style: Object.assign({}, S.dim, { flex: 1, minWidth: 0 }) },
|
|
3977
|
+
d.模式 === 'workspace'
|
|
3978
|
+
? '整个工作区共用一个记忆库 —— 新开的会话自动接上,归档旧会话记忆也不会跟着走。'
|
|
3979
|
+
: '每个会话各记各的。会话开久了变卡、要归档换新会话时,切成「按工作区」就接得上了。',
|
|
3980
|
+
),
|
|
3981
|
+
),
|
|
3952
3982
|
(d.sessions || []).map((s) =>
|
|
3953
3983
|
React.createElement(
|
|
3954
3984
|
'div',
|
package/lib/index.js
CHANGED
|
@@ -39,6 +39,50 @@ const CFG_DIR = process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
|
39
39
|
const STORE_DIR = join(CFG_DIR, 'fish-memory')
|
|
40
40
|
const BACKUP_DIR = join(CFG_DIR, 'fish-memory-backup')
|
|
41
41
|
const CFG_PATH = join(CFG_DIR, 'fish-memory-config.json')
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* ⭐ 1.4.5:工作区标识 —— 跟 DSH 自己的 `projectKey(cwd)` **一字不差**。
|
|
45
|
+
*
|
|
46
|
+
* DSH 把会话存在 `~/.dsh/sessions/<工作区目录名>/<会话id>/`,那层目录名就是它算出来的键。
|
|
47
|
+
* 规则(跟着 DSH 抄,别自己发明):
|
|
48
|
+
* · 分隔符(`/` `\` `:`)压成一个 `-`(连续的分隔符只留一个);
|
|
49
|
+
* · 只有 `[A-Za-z0-9._-]` 原样保留 —— 其余字符(含 `~`、`'`、中文)一律写成
|
|
50
|
+
* `~` + 4 位大写十六进制(按 UTF-16 码元);
|
|
51
|
+
* · 去掉开头连续的 `-`(空了就是 `root`),截到 251 个字符,两头各包一个 `-`。
|
|
52
|
+
* 例:`C:\Users\me\Desktop\项目目录\dsh`
|
|
53
|
+
* → `--C-Users-me-Desktop-~9879~76EE~76EE~5F55-dsh--`
|
|
54
|
+
*
|
|
55
|
+
* ⚠ 算法必须跟 DSH 完全一致:差一个字符,同一个工作区就会裂成两份。
|
|
56
|
+
* 单引号 / `~` 以前被简化成 `-` 就踩过这个坑(心情插件那边修过一次)。
|
|
57
|
+
*/
|
|
58
|
+
export function 工作区目录名(p) {
|
|
59
|
+
const s = String(p == null ? '' : p).trim()
|
|
60
|
+
if (!s) return ''
|
|
61
|
+
if (/^--.*--$/.test(s)) return s // 给的本来就是目录名
|
|
62
|
+
let 可读 = ''
|
|
63
|
+
let 连续分隔 = false
|
|
64
|
+
for (let i = 0; i < s.length; i++) {
|
|
65
|
+
const ch = s[i]
|
|
66
|
+
if (ch === '/' || ch === '\\' || ch === ':') {
|
|
67
|
+
if (!连续分隔) 可读 += '-'
|
|
68
|
+
连续分隔 = true
|
|
69
|
+
} else if (ch !== '~' && /^[A-Za-z0-9._-]$/.test(ch)) {
|
|
70
|
+
可读 += ch
|
|
71
|
+
连续分隔 = false
|
|
72
|
+
} else {
|
|
73
|
+
可读 += '~' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')
|
|
74
|
+
连续分隔 = false
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
可读 = 可读.replace(/^-+/, '') || 'root'
|
|
78
|
+
return '--' + 可读.slice(0, 251) + '--'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 工作区的可读路径:从会话目录名反查不回来,就直接给目录名 */
|
|
82
|
+
function 工作区可读名(键) {
|
|
83
|
+
return String(键 || '').replace(/^--/, '').replace(/--$/, '') || '(未分组)'
|
|
84
|
+
}
|
|
85
|
+
|
|
42
86
|
const DAY = 86400000
|
|
43
87
|
// ⭐⭐ 2026-09-21(反馈:「别人导入会踩什么坑」时改的):**时区不能再写死北京** ✗
|
|
44
88
|
// 原来写死 +8。实测:换纽约、换东京,日期显示一模一样 ——
|
|
@@ -225,9 +269,93 @@ function diag(msg) {
|
|
|
225
269
|
// ---------- 每个会话一份库 ----------
|
|
226
270
|
|
|
227
271
|
function storePath(sid) {
|
|
228
|
-
|
|
272
|
+
// ⭐ 1.4.5:工作区模式下,同一工作区的所有会话指向**同一个库文件**。
|
|
273
|
+
// 放在最底层改(而不是改每个调用点)—— 少一处漏改就少一个「这个库怎么没带上记忆」。
|
|
274
|
+
// 模式从配置里读一次(用户可能在设置页切);读不到 / 算不出工作区都退回按会话,
|
|
275
|
+
// 绝不因为工作区算不出来就把记忆弄丢。
|
|
276
|
+
const 用名 = String(库名(sid, { 模式: 读模式() }) || sid)
|
|
277
|
+
return join(STORE_DIR, 用名.replace(/[^a-zA-Z0-9_-]/g, '_') + '.json')
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// --------------------------------------------------------------------------
|
|
281
|
+
// ⭐ 1.4.5:工作区模式
|
|
282
|
+
// DSH 的会话都躺在 `~/.dsh/sessions/<工作区目录名>/<会话 id>/`,所以:
|
|
283
|
+
// · 工作区标识 = 中间那层目录名(跨机器可复制、不依赖绝对路径)
|
|
284
|
+
// · 工作区模式下:**同一工作区的会话共用一个记忆库**
|
|
285
|
+
// · 跟「按会话」二选一:同时开等于一份记忆被两套归属抢,必出 bug
|
|
286
|
+
//
|
|
287
|
+
// 为什么需要:一个会话久了会大到打开都卡,为了流畅只能归档、开新会话。
|
|
288
|
+
// 可记忆是按会话存的,新会话里什么都不记得 —— 工作区模式就是治这个的。
|
|
289
|
+
// --------------------------------------------------------------------------
|
|
290
|
+
const 工作区缓存 = { at: 0, 数据: null }
|
|
291
|
+
|
|
292
|
+
function 目录们(p) {
|
|
293
|
+
try {
|
|
294
|
+
return readdirSync(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name)
|
|
295
|
+
} catch (error) {
|
|
296
|
+
return []
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 扫一遍会话根目录:会话 → 工作区目录名、工作区目录名 → 会话 ids。
|
|
302
|
+
* 结果缓存 5 秒(心跳每 3 秒一次,别每次都读盘)。
|
|
303
|
+
* ⚠ 跟心情插件一个写法:**只认 CFG_DIR**,不给测试留参数(测试靠先设 DSH_HOME 再 import)。
|
|
304
|
+
*/
|
|
305
|
+
export function 扫会话目录(强制 = false) {
|
|
306
|
+
const 现在 = Date.now()
|
|
307
|
+
if (!强制 && 工作区缓存.数据 && 现在 - 工作区缓存.at < 5000) return 工作区缓存.数据
|
|
308
|
+
const 根 = join(CFG_DIR, 'sessions')
|
|
309
|
+
const bySession = new Map() // 会话 id -> 工作区目录名
|
|
310
|
+
const byWorkspace = new Map() // 工作区目录名 -> [会话 id]
|
|
311
|
+
for (const ws of 目录们(根)) {
|
|
312
|
+
const 会话们 = []
|
|
313
|
+
for (const sid of 目录们(join(根, ws))) {
|
|
314
|
+
const k = String(sid || '').trim()
|
|
315
|
+
if (!k) continue
|
|
316
|
+
会话们.push(sid)
|
|
317
|
+
if (!bySession.has(k)) bySession.set(k, ws)
|
|
318
|
+
// 老会话可能带 `session-` 前缀,两种写法都记上
|
|
319
|
+
if (k.startsWith('session-')) {
|
|
320
|
+
const 短 = k.slice('session-'.length)
|
|
321
|
+
if (短 && !bySession.has(短)) bySession.set(短, ws)
|
|
322
|
+
} else if (!bySession.has('session-' + k)) {
|
|
323
|
+
bySession.set('session-' + k, ws)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (会话们.length) byWorkspace.set(ws, 会话们)
|
|
327
|
+
}
|
|
328
|
+
const 数据 = { bySession, byWorkspace, at: 现在 }
|
|
329
|
+
工作区缓存.data = 数据
|
|
330
|
+
工作区缓存.at = 现在
|
|
331
|
+
return 数据
|
|
229
332
|
}
|
|
230
333
|
|
|
334
|
+
/** 这个会话属于哪个工作区(认不出来就空串 → 退回按会话) */
|
|
335
|
+
export function 会话的工作区(sid) {
|
|
336
|
+
const k = String(sid == null ? '' : sid).trim()
|
|
337
|
+
if (!k) return ''
|
|
338
|
+
const 表 = 扫会话目录()
|
|
339
|
+
return 表.bySession.get(k) || ''
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* ⭐ 1.4.5:工作区模式下,一份记忆归**哪个库**。
|
|
344
|
+
*
|
|
345
|
+
* 用工作区目录名当库名(加个前缀,别跟会话 id 撞)。这样:
|
|
346
|
+
* · 同一个工作区里开多少新会话,读写的都是同一份;
|
|
347
|
+
* · 归档旧会话不会把记忆带走 —— 新会话一开就接上了。
|
|
348
|
+
*/
|
|
349
|
+
export function 库名(sid, 配置) {
|
|
350
|
+
// ⚠ 不在这里读全局配置 —— 调用方把「模式」传进来。
|
|
351
|
+
// 自己读配置会让这个函数没法单测(而且 storePath 在模块加载早期就会被调用)。
|
|
352
|
+
const 模式 = String((配置 && 配置.模式) || 'session')
|
|
353
|
+
if (模式 !== 'workspace') return String(sid)
|
|
354
|
+
const ws = 会话的工作区(sid)
|
|
355
|
+
return ws ? 'ws_' + ws : String(sid)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
231
359
|
// ⭐ 1.2.7(issue #7):**全局参数**
|
|
232
360
|
// 以前参数只存在每个记忆库(memory-store/<会话>.json)里 —— 一个库都没有时,
|
|
233
361
|
// 保存等于没保存,F5 一刷就回默认值。
|
|
@@ -546,6 +674,17 @@ function setGlobalOn(on) {
|
|
|
546
674
|
}
|
|
547
675
|
}
|
|
548
676
|
|
|
677
|
+
// ⭐ 1.4.5:记忆按什么归类 —— 'session'(默认,跟以前一样)/ 'workspace'(同一工作区共用一份)。
|
|
678
|
+
// 两种模式二选一:同时开等于一份记忆被两套归属抢,必出 bug。
|
|
679
|
+
function 读模式() {
|
|
680
|
+
try {
|
|
681
|
+
const c = loadCfg()
|
|
682
|
+
return String(c.模式 || 'session') === 'workspace' ? 'workspace' : 'session'
|
|
683
|
+
} catch (error) {
|
|
684
|
+
return 'session'
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
549
688
|
// 取消一个注册(DSH 那套返回的是**函数**,个别地方是 { off })
|
|
550
689
|
function 卸掉效果(d) {
|
|
551
690
|
try {
|
|
@@ -3643,7 +3782,7 @@ function 分支来处Of(ctx, sid) {
|
|
|
3643
3782
|
}
|
|
3644
3783
|
|
|
3645
3784
|
function buildMemoryList(ctx) {
|
|
3646
|
-
const out = { ok: true, sessions: [], policy: null, ghosts: 0, 全局开关: globalOn() }
|
|
3785
|
+
const out = { ok: true, sessions: [], policy: null, ghosts: 0, 全局开关: globalOn(), 模式: 读模式() }
|
|
3647
3786
|
// ⭐ 1.2.7:设置页显示「全局参数」—— 以前是「碰到的第一个库的参数」,没有库就什么都没有
|
|
3648
3787
|
try {
|
|
3649
3788
|
const c0 = loadCfg()
|
|
@@ -4544,8 +4683,25 @@ export function apply(ctx) {
|
|
|
4544
4683
|
res.end(JSON.stringify({ ok: true, on }))
|
|
4545
4684
|
})
|
|
4546
4685
|
|
|
4547
|
-
|
|
4686
|
+
// ⭐ 1.4.5:切换「按会话 / 按工作区」。切换不会搬动已有记忆 ——
|
|
4687
|
+
// 只是此后读写的库文件换了,旧的还在(想合并得单独做,不在这条路上)。
|
|
4688
|
+
mroute('/dsh-fish-memory/mode', async (req, res) => {
|
|
4548
4689
|
let body = ''
|
|
4690
|
+
for await (const chunk of req) body += chunk
|
|
4691
|
+
let b = {}
|
|
4692
|
+
try { b = JSON.parse(body || '{}') } catch (error) {}
|
|
4693
|
+
const 模式 = String(b.模式 || '') === 'workspace' ? 'workspace' : 'session'
|
|
4694
|
+
try {
|
|
4695
|
+
const c = loadCfg()
|
|
4696
|
+
c.模式 = 模式
|
|
4697
|
+
saveCfg(c)
|
|
4698
|
+
diag('记忆归类模式 → ' + 模式)
|
|
4699
|
+
} catch (error) {}
|
|
4700
|
+
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })
|
|
4701
|
+
res.end(JSON.stringify({ ok: true, 模式 }))
|
|
4702
|
+
})
|
|
4703
|
+
|
|
4704
|
+
mroute('/dsh-fish-memory/toggle', async (req, res) => { let body = ''
|
|
4549
4705
|
for await (const chunk of req) body += chunk
|
|
4550
4706
|
let b = {}
|
|
4551
4707
|
try {
|
|
@@ -6620,3 +6776,15 @@ export {
|
|
|
6620
6776
|
标作废,
|
|
6621
6777
|
真重复门槛,
|
|
6622
6778
|
}
|
|
6779
|
+
|
|
6780
|
+
/**
|
|
6781
|
+
* ⭐ 1.4.5:给自测用的出口(跟心情插件一个写法)。
|
|
6782
|
+
* ⚠ 只给测试用,不要在生产代码里调;里面都是纯函数或内部状态读取。
|
|
6783
|
+
*/
|
|
6784
|
+
export const __测试 = {
|
|
6785
|
+
工作区目录名,
|
|
6786
|
+
扫会话目录,
|
|
6787
|
+
会话的工作区,
|
|
6788
|
+
库名,
|
|
6789
|
+
读模式,
|
|
6790
|
+
}
|