dsh-recall-plugin 2.0.0 → 2.1.1
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 +63 -29
- package/README.en.md +2 -1
- package/README.md +2 -1
- package/lib/client.js +1459 -1479
- package/lib/diagnostics.js +59 -0
- package/lib/errors.js +73 -0
- package/lib/index.js +426 -1129
- package/lib/routes-core.js +162 -0
- package/lib/routes-manage.js +541 -0
- package/lib/scripts.posix.js +195 -27
- package/lib/scripts.pwsh.js +194 -33
- package/lib/session-info.js +71 -0
- package/lib/snapshots.js +243 -39
- package/lib/store.js +157 -19
- package/package.json +65 -61
package/lib/store.js
CHANGED
|
@@ -13,6 +13,7 @@ import os from 'node:os'
|
|
|
13
13
|
import crypto from 'node:crypto'
|
|
14
14
|
import * as pwshScripts from './scripts.pwsh.js'
|
|
15
15
|
import * as posixScripts from './scripts.posix.js'
|
|
16
|
+
import { classifyEnvError } from './diagnostics.js'
|
|
16
17
|
|
|
17
18
|
// home 不可写时迁移重试的节流间隔:避免每条消息都白试一次注定失败的迁移
|
|
18
19
|
const HOME_RETRY_MS = 300000
|
|
@@ -20,6 +21,69 @@ const HOME_RETRY_MS = 300000
|
|
|
20
21
|
// 最近错误环形缓冲容量:设置页排障用,20 条足够回溯一轮快照/gc 的失败
|
|
21
22
|
const ERROR_BUFFER_MAX = 20
|
|
22
23
|
|
|
24
|
+
// ---- POSIX home 基底解析(模块级纯逻辑,单测直测;工厂内 posixHomeBaseResolve
|
|
25
|
+
// 委托到这里)。放模块级而非工厂闭包:分支行为要在 win32 CI 上可测,而
|
|
26
|
+
// posixHomeBaseResolve 只在 POSIX 运行时被触达。----
|
|
27
|
+
|
|
28
|
+
// 三档回退选择:bash env $DSH_HOME → Node 主进程 DSH_HOME → os.homedir()。
|
|
29
|
+
// 第三档必须补 /.dsh 子目录(I24):win32 版第三档是 Join-Path USERPROFILE .dsh
|
|
30
|
+
// (scripts.pwsh.js homeDirScript),POSIX 版曾直接用裸 homedir,快照落
|
|
31
|
+
// ~/dsh-recall-snapshots 而非 ~/.dsh/dsh-recall-snapshots(issue #11 实证)。
|
|
32
|
+
// 返回 third 标记是否走了第三档——只有第三档才涉及旧容器迁移(存量用户
|
|
33
|
+
// 的数据在旧位,改 base 前要先搬)。
|
|
34
|
+
export function selectPosixHomeBase({ probed, envHome, homedir }) {
|
|
35
|
+
if (probed) return { base: probed, third: false }
|
|
36
|
+
if (envHome) return { base: envHome, third: false }
|
|
37
|
+
return { base: homedir + '/.dsh', third: true }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 第三档命中时的一次性旧容器迁移编排(best-effort,数据安全优先):
|
|
41
|
+
// legacyHomeMigrateScript 只在「旧容器存在且新容器不存在」时整容器 mv,
|
|
42
|
+
// 输出四态由这里裁决——MIGRATE_OK / OLD_ABSENT 落规范位置(~/.dsh/…);
|
|
43
|
+
// BOTH_PRESENT(双容器并存)/ MIGRATE_FAIL(mv 失败)沿用旧位并 recordError,
|
|
44
|
+
// 与 tryUpgradeToHome 的非致命迁移哲学一致:数据不丢永远优先于路径规范。
|
|
45
|
+
// 探测命令自身失败按同策略回落旧位——此刻无法判断旧容器是否存在,选新位
|
|
46
|
+
// 会让存量用户「看不到」历史快照,选旧位对新装机只是维持修复前的行为。
|
|
47
|
+
export async function resolvePosixHomeBase(deps, { probed, envHome, homedir }) {
|
|
48
|
+
const sel = selectPosixHomeBase({ probed, envHome, homedir })
|
|
49
|
+
if (!sel.third) return sel.base
|
|
50
|
+
try {
|
|
51
|
+
const out = String(await deps.runShell(deps.scripts.legacyHomeMigrateScript(homedir), { timeoutMs: 300000, stdoutMaxBytes: 4096 })).trim()
|
|
52
|
+
if (out === 'MIGRATE_OK' || out === 'OLD_ABSENT') return sel.base
|
|
53
|
+
deps.recordError(
|
|
54
|
+
out === 'BOTH_PRESENT'
|
|
55
|
+
? 'recall home store 新旧容器并存(' + homedir + '/dsh-recall-snapshots 与 ' + homedir + '/.dsh/dsh-recall-snapshots),沿用旧位,未做任何改动'
|
|
56
|
+
: 'recall 旧快照容器迁移失败(MIGRATE_FAIL),沿用旧位 ' + homedir + '/dsh-recall-snapshots'
|
|
57
|
+
)
|
|
58
|
+
return homedir
|
|
59
|
+
} catch (error) {
|
|
60
|
+
deps.recordError('recall 旧快照容器迁移探测失败,沿用旧位: ' + String(error))
|
|
61
|
+
return homedir
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 失败清扫脚本输出解读(M3 纯逻辑,供单测;cleanupAfterGitFailure 消费)。
|
|
66
|
+
// killOrphansScript 的三级出口:CLEANUP_OTHER_INSTANCE <pid> = 检测到另一个
|
|
67
|
+
// 活实例正在使用同一快照库(心跳有效且进程存活),清扫已让路;
|
|
68
|
+
// CLEANUP_SKIPPED_FRESH_LOCK = 存在 5 分钟内的新锁(疑似 git 操作进行中),
|
|
69
|
+
// 清扫未触碰;CLEANUP_DONE = 原有清扫路径执行完毕。解析按标记行匹配,
|
|
70
|
+
// 与模板输出逐字对应(改标记必须两侧同步)。
|
|
71
|
+
export function parseCleanupResult(out) {
|
|
72
|
+
const m = String(out || '').match(/CLEANUP_OTHER_INSTANCE\s+(\d+)/)
|
|
73
|
+
if (m) return { otherPid: parseInt(m[1], 10), skippedFresh: false }
|
|
74
|
+
if (String(out || '').indexOf('CLEANUP_SKIPPED_FRESH_LOCK') >= 0) return { otherPid: null, skippedFresh: true }
|
|
75
|
+
return { otherPid: null, skippedFresh: false }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// rename 步 ENOENT 判定(模块级纯逻辑,单测直接覆盖):POSIX mv 与
|
|
79
|
+
// pwsh Move-Item 的「目标不存在」文案集合,且错误必须提到 tmp 文件名
|
|
80
|
+
//(basename)——只认 rename 步的错误形态,误吞面最小。
|
|
81
|
+
export function isTmpConsumedError(error, basename) {
|
|
82
|
+
const s = String(error || '')
|
|
83
|
+
if (!basename || s.indexOf(basename) < 0) return false
|
|
84
|
+
return /No such file/i.test(s) || /does not exist/i.test(s) || /cannot find path/i.test(s)
|
|
85
|
+
}
|
|
86
|
+
|
|
23
87
|
export function createRuntime(ctx, config) {
|
|
24
88
|
const shell = ctx.shell
|
|
25
89
|
const sessions = ctx.sessions
|
|
@@ -53,9 +117,19 @@ export function createRuntime(ctx, config) {
|
|
|
53
117
|
// 最近错误环形缓冲:Host 侧所有失败原本只进 console.error(宿主进程
|
|
54
118
|
// 日志,用户在页面上不可见),这里留最近 20 条经 /api/recall/status
|
|
55
119
|
// 下发给设置页展示。同时转发 console.error 保持原有宿主日志不变。
|
|
120
|
+
// 尾部去重(issue #11):环境性错误随每条消息重复抛出,逐条 push 会把
|
|
121
|
+
// 20 条环形缓冲刷成同一条目、console.error 同步刷屏,其他诊断信息全被
|
|
122
|
+
// 挤掉。相邻重复只更新 time/count——间隔其他错误的重复仍新建条目,错误
|
|
123
|
+
// 时序不丢;kind 随条目富集(classifyEnvError),供 status 端点机器分流。
|
|
56
124
|
function recordError(text) {
|
|
57
125
|
const message = String(text)
|
|
58
|
-
state.errors.
|
|
126
|
+
const last = state.errors[state.errors.length - 1]
|
|
127
|
+
if (last && last.message === message) {
|
|
128
|
+
last.time = Date.now()
|
|
129
|
+
last.count += 1
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
state.errors.push({ time: Date.now(), message, count: 1, kind: classifyEnvError(message) })
|
|
59
133
|
if (state.errors.length > ERROR_BUFFER_MAX) state.errors.splice(0, state.errors.length - ERROR_BUFFER_MAX)
|
|
60
134
|
console.error(message)
|
|
61
135
|
}
|
|
@@ -67,8 +141,10 @@ export function createRuntime(ctx, config) {
|
|
|
67
141
|
// 基底走 probeHomeScript + Node 侧推导;常量与转义工具不承载命令)。
|
|
68
142
|
;(function checkScriptParity() {
|
|
69
143
|
// fileWriteCmd 仅 pwsh 版存在:POSIX 的文本落盘走 stdin(store.js
|
|
70
|
-
// writeTextViaShell 的 POSIX
|
|
71
|
-
|
|
144
|
+
// writeTextViaShell 的 POSIX 分支不经命令行传参),不需要该模板函数;
|
|
145
|
+
// legacyHomeMigrateScript 仅 posix 版存在:旧容器迁移是 POSIX 漂移
|
|
146
|
+
// (I24)专属的存量数据兜底,win32 无此问题
|
|
147
|
+
const SKIP = new Set(['homeDirScript', 'probeHomeScript', 'fileWriteCmd', 'legacyHomeMigrateScript'])
|
|
72
148
|
const pwshKeys = Object.keys(pwshScripts).filter((k) => !SKIP.has(k) && typeof pwshScripts[k] === 'function')
|
|
73
149
|
const posixKeys = Object.keys(posixScripts).filter((k) => !SKIP.has(k) && typeof posixScripts[k] === 'function')
|
|
74
150
|
const missing = pwshKeys.filter((k) => posixKeys.indexOf(k) < 0)
|
|
@@ -84,7 +160,12 @@ export function createRuntime(ctx, config) {
|
|
|
84
160
|
// home,快照被迫降级进项目目录(污染);read-only 会话连项目都写不了,
|
|
85
161
|
// 回退恢复直接失败。pwsh-sandbox / bash-sandbox 对 danger-full-access
|
|
86
162
|
// 直接不约束(等价本地执行器),无沙箱后端的部署则忽略该字段,两边都成立。
|
|
87
|
-
|
|
163
|
+
// F-G3:runShell 的元数据变体——stdout 截断可判定(官方 ShellRunResult.stdout
|
|
164
|
+
// 是 CollectedOutput{text, truncated, spillPath?},见 dsh-shell 与
|
|
165
|
+
// dsh-subprocess 的 lib/types/types.d.ts;截断时 text 只剩流尾部)。需要
|
|
166
|
+
// 「解析完整 stdout」的调用方(loadIndex)用它区分「读截断」与「内容损坏」;
|
|
167
|
+
// 其余调用方继续用 runShell 拿纯文本,签名不变。
|
|
168
|
+
async function runShellMeta(command, opts) {
|
|
88
169
|
const sp = ctx.get('sandboxPolicy')
|
|
89
170
|
const spec = shell.resolve({
|
|
90
171
|
// 编码前导:pwsh 侧统一 UTF-8 输出(中文机器 GBK 代码页不再乱码);
|
|
@@ -108,7 +189,14 @@ export function createRuntime(ctx, config) {
|
|
|
108
189
|
const err = ((res && res.stderr && res.stderr.text) || '').trim() || ('exit ' + String(res.exitCode))
|
|
109
190
|
throw new Error(err.slice(0, 1500))
|
|
110
191
|
}
|
|
111
|
-
return
|
|
192
|
+
return {
|
|
193
|
+
text: out,
|
|
194
|
+
truncated: Boolean(res && res.stdout && res.stdout.truncated),
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function runShell(command, opts) {
|
|
199
|
+
return (await runShellMeta(command, opts)).text
|
|
112
200
|
}
|
|
113
201
|
|
|
114
202
|
// 从脚本文本提取影子仓库 git-dir:两套模板的 git 命令脚本都以
|
|
@@ -129,7 +217,13 @@ export function createRuntime(ctx, config) {
|
|
|
129
217
|
const gitDir = extractGitDir(command)
|
|
130
218
|
if (!gitDir) return
|
|
131
219
|
try {
|
|
132
|
-
await runShell(scripts.killOrphansScript(gitDir), { timeoutMs: 60000, stdoutMaxBytes: 4096 })
|
|
220
|
+
const out = await runShell(scripts.killOrphansScript(gitDir), { timeoutMs: 60000, stdoutMaxBytes: 4096 })
|
|
221
|
+
// M3:清扫让路的两种情形都值得一条记录——前者把 issue #11 的「疑似
|
|
222
|
+
// 多实例」升级为「确认」(点名 PID),后者解释了环境为何没有被自动
|
|
223
|
+
// 清理。recordError 的尾部去重保证逐消息重复失败不会刷屏。
|
|
224
|
+
const r = parseCleanupResult(out)
|
|
225
|
+
if (r.otherPid !== null) recordError('recall 检测到另一个 DSH 实例(PID ' + r.otherPid + ')正在使用此快照库,失败清扫已让路:未杀进程、未动锁')
|
|
226
|
+
else if (r.skippedFresh) recordError('recall 检测到 5 分钟内的新锁文件,疑似 git 操作正在进行,失败清扫已让路(锁陈旧后会自动清理)')
|
|
133
227
|
} catch (error) { /* best-effort:清扫失败不影响原始错误的抛出 */ }
|
|
134
228
|
}
|
|
135
229
|
|
|
@@ -210,8 +304,11 @@ export function createRuntime(ctx, config) {
|
|
|
210
304
|
|
|
211
305
|
// POSIX:shell 侧只探 bash env 里显式的 $DSH_HOME(DSH 执行器洗刷
|
|
212
306
|
// DSH_* 变量后通常为空);为空时依次回退 Node 主进程的 DSH_HOME
|
|
213
|
-
// (宿主进程 env,用户导出可见)与 os.homedir()
|
|
214
|
-
//
|
|
307
|
+
// (宿主进程 env,用户导出可见)与 os.homedir()(补 /.dsh 层,见
|
|
308
|
+
// selectPosixHomeBase 的 I24 注释)。哈希用 Node crypto 统一算,规避
|
|
309
|
+
// Linux sha256sum / macOS shasum 的二选一移植成本。三档选择与旧容器
|
|
310
|
+
// 迁移编排都委托模块级纯函数(resolvePosixHomeBase),本方法只负责探测
|
|
311
|
+
// 输入与结果缓存(迁移随缓存每进程至多跑一次)。
|
|
215
312
|
async function posixHomeBaseResolve() {
|
|
216
313
|
if (state.posixHomeBase === null) {
|
|
217
314
|
let probed = ''
|
|
@@ -220,7 +317,10 @@ export function createRuntime(ctx, config) {
|
|
|
220
317
|
} catch (error) {
|
|
221
318
|
probed = ''
|
|
222
319
|
}
|
|
223
|
-
state.posixHomeBase =
|
|
320
|
+
state.posixHomeBase = await resolvePosixHomeBase(
|
|
321
|
+
{ runShell, scripts, recordError },
|
|
322
|
+
{ probed, envHome: (process.env && process.env.DSH_HOME) || '', homedir: os.homedir() }
|
|
323
|
+
)
|
|
224
324
|
}
|
|
225
325
|
return state.posixHomeBase
|
|
226
326
|
}
|
|
@@ -362,45 +462,83 @@ export function createRuntime(ctx, config) {
|
|
|
362
462
|
}
|
|
363
463
|
}
|
|
364
464
|
|
|
365
|
-
// 任意长度文本落盘(index.json / exclude.txt
|
|
465
|
+
// 任意长度文本落盘(index.json / exclude.txt 共用),原子写(H2):
|
|
466
|
+
// 先写 <file>.tmp 再 rename 替换目标——多块序列中途崩溃最多留一个无害
|
|
467
|
+
// 的 .tmp 残留(下次写覆盖),绝不会留下截断 JSON。win32 走 base64
|
|
366
468
|
// 分块内联(每块 20000 字符,规避 Windows 命令行 32767 上限——DSH 的
|
|
367
469
|
// pwsh 执行器把命令串作为 -Command 的单个 argv 元素 spawn,快照攒到
|
|
368
470
|
// 几百条就超限),首块覆盖、续块追加;POSIX 用官方 ShellExecRequest
|
|
369
|
-
// 的 stdin
|
|
471
|
+
// 的 stdin 契约字段直写全文到 tmp,不经命令行传参,天然没有 argv 上限。
|
|
370
472
|
// 空内容也落一次写(清空配置/空索引是合法状态),所以 base64 为空串
|
|
371
|
-
// 时仍发一块空 piece,而不是整段跳过留下旧文件。
|
|
473
|
+
// 时仍发一块空 piece,而不是整段跳过留下旧文件。rename 是同卷 O(1)
|
|
474
|
+
// 元数据操作,索引写频率为每消息一次,额外开销可忽略。
|
|
475
|
+
// rename 步的 ENOENT 容忍(WSL 双实例实弹发现):每实例写同一个
|
|
476
|
+
// <file>.tmp 路径,并发时一方 rename 把 tmp 消费掉,另一方 rename 报
|
|
477
|
+
// 「No such file / does not exist」。容忍是安全的,因为能走到 rename
|
|
478
|
+
// 的前提是写侧(分块 Add-Content / cat)已完整成功——任一写步失败都
|
|
479
|
+
// 在写侧直接抛(POSIX set -e / pwsh EAP=Stop),进不到这里;所以此刻
|
|
480
|
+
// tmp 消失只可能是同伴先把完整内容 rename 到了目标——本侧写语义已被
|
|
481
|
+
// 达成。Windows 侧「偶发一次 Move-Item: index.json.tmp does not
|
|
482
|
+
// exist」即同根。不进 recordError(用户错误列表刷屏正是要消除的
|
|
483
|
+
// 症状),console.error 留诊断痕迹;其余错误原样抛出。
|
|
484
|
+
async function renameTmpQuietly(tmp, file) {
|
|
485
|
+
try {
|
|
486
|
+
await runShell(scripts.renameFileCmd(tmp, file), { stdoutMaxBytes: 4096 })
|
|
487
|
+
} catch (error) {
|
|
488
|
+
const basename = tmp.slice(tmp.lastIndexOf(SEP) + 1)
|
|
489
|
+
if (isTmpConsumedError(error, basename)) {
|
|
490
|
+
console.error('recall writeTextViaShell: ' + basename + ' 已被并发写者 rename 消费,视同成功')
|
|
491
|
+
return
|
|
492
|
+
}
|
|
493
|
+
throw error
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
372
497
|
async function writeTextViaShell(file, text) {
|
|
373
498
|
const body = String(text == null ? '' : text)
|
|
499
|
+
const tmp = file + '.tmp'
|
|
374
500
|
if (isWin) {
|
|
375
501
|
const b64 = Buffer.from(body, 'utf8').toString('base64')
|
|
376
502
|
const chunks = b64 ? b64.match(/.{1,20000}/g) : ['']
|
|
377
503
|
let first = true
|
|
378
504
|
for (const chunk of chunks) {
|
|
379
505
|
const piece = "[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('" + chunk + "')) | "
|
|
380
|
-
await runShell(scripts.fileWriteCmd(
|
|
506
|
+
await runShell(scripts.fileWriteCmd(tmp, piece, first), { stdoutMaxBytes: 4096 })
|
|
381
507
|
first = false
|
|
382
508
|
}
|
|
509
|
+
await renameTmpQuietly(tmp, file)
|
|
383
510
|
} else {
|
|
384
|
-
await runShell('cat > ' + scripts.psq(
|
|
511
|
+
await runShell('cat > ' + scripts.psq(tmp), { stdin: body, stdoutMaxBytes: 4096 })
|
|
512
|
+
await renameTmpQuietly(tmp, file)
|
|
385
513
|
}
|
|
386
514
|
}
|
|
387
515
|
|
|
388
516
|
// 建立影子仓库(幂等:gitReady 命中后直接跳过,省掉每条消息一次的
|
|
389
517
|
// config/exclude 重写)。同时回读 gc.stamp 种子化 gc 节流:让「上次 gc
|
|
390
518
|
// 时间」跨重启续存,避免天天重启的机器每开机都来一次全量 gc。
|
|
519
|
+
// 返回 {ok, error}(M1-D2):失败原因必须传出——captureSnapshot 要把它
|
|
520
|
+
// 分类成 snapFeedback 的可行动提示(此前吞成布尔,客户端空轮询 20 次、
|
|
521
|
+
// 用户零感知,issue #11 主线缺口);init/预热调用方忽略返回值,不受形状
|
|
522
|
+
// 变化影响。
|
|
391
523
|
async function ensureGit(root, store) {
|
|
392
|
-
if (state.gitReady.has(store.git)) return true
|
|
524
|
+
if (state.gitReady.has(store.git)) return { ok: true }
|
|
393
525
|
const gitExe = await resolveGit()
|
|
394
|
-
if (!gitExe)
|
|
526
|
+
if (!gitExe) {
|
|
527
|
+
// git 缺失分支:原先静默 return false,连 recordError 都没有(用户
|
|
528
|
+
// 重启也查不到原因的盲区)。进错误环靠上方尾部去重天然免刷屏。
|
|
529
|
+
const error = '未检测到 git CLI,快照不可用'
|
|
530
|
+
recordError('recall ensureGit: ' + error + ':请安装 git 或检查其是否在 PATH 中')
|
|
531
|
+
return { ok: false, error }
|
|
532
|
+
}
|
|
395
533
|
try {
|
|
396
534
|
const out = scripts.stripBom(await runShell(scripts.ensureGitScript(store, gitExe, config.baseExcludes), { stdoutMaxBytes: 4096 }))
|
|
397
535
|
state.gitReady.add(store.git)
|
|
398
536
|
const m = out.match(/GIT_OK\s+(\d+)/)
|
|
399
537
|
state.gcLastAt.set(store.git, m ? parseInt(m[1], 10) * 1000 : Date.now())
|
|
400
|
-
return true
|
|
538
|
+
return { ok: true }
|
|
401
539
|
} catch (error) {
|
|
402
540
|
recordError('recall ensureGit failed: ' + String(error))
|
|
403
|
-
return false
|
|
541
|
+
return { ok: false, error: String(error) }
|
|
404
542
|
}
|
|
405
543
|
}
|
|
406
544
|
|
|
@@ -412,5 +550,5 @@ export function createRuntime(ctx, config) {
|
|
|
412
550
|
runShell(scripts.legacyRmScript(root + SEP + '.dsh-recall-snapshots'), { timeoutMs: 120000, stdoutMaxBytes: 4096 }).catch(() => {})
|
|
413
551
|
}
|
|
414
552
|
|
|
415
|
-
return { state, isWin, scripts, recordError, runShell, writeTextViaShell, resolveRoot, resolveGit, homeDirFor, resolveHomeContainer, resolveStore, storeFromDir, tryUpgradeToHome, ensureGit, cleanupLegacy }
|
|
553
|
+
return { state, isWin, scripts, recordError, runShell, runShellMeta, writeTextViaShell, resolveRoot, resolveGit, homeDirFor, resolveHomeContainer, resolveStore, storeFromDir, tryUpgradeToHome, ensureGit, cleanupLegacy, cleanupAfterGitFailure }
|
|
416
554
|
}
|
package/package.json
CHANGED
|
@@ -1,61 +1,65 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-recall-plugin",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "DSH 消息撤回插件:在用户消息气泡旁加「撤回」按钮,把项目文件(独立影子 git 仓库快照)与对话历史(官方 fork)一并回退到该消息发送之前。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/limbo947/dsh-recall-plugin.git"
|
|
9
|
-
},
|
|
10
|
-
"publishConfig": {
|
|
11
|
-
"access": "public"
|
|
12
|
-
},
|
|
13
|
-
"main": "lib/index.js",
|
|
14
|
-
"exports": {
|
|
15
|
-
".": "./lib/index.js",
|
|
16
|
-
"./client": "./lib/client.js",
|
|
17
|
-
"./package.json": "./package.json"
|
|
18
|
-
},
|
|
19
|
-
"files": [
|
|
20
|
-
"lib",
|
|
21
|
-
"cordis.patch.yml",
|
|
22
|
-
"README.md",
|
|
23
|
-
"README.en.md",
|
|
24
|
-
"CHANGELOG.md",
|
|
25
|
-
"LICENSE"
|
|
26
|
-
],
|
|
27
|
-
"engines": {
|
|
28
|
-
"node": ">=20"
|
|
29
|
-
},
|
|
30
|
-
"scripts": {
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"@deepseek-ai/
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-
|
|
53
|
-
"@deepseek-ai/dsh-
|
|
54
|
-
"@deepseek-ai/
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-recall-plugin",
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "DSH 消息撤回插件:在用户消息气泡旁加「撤回」按钮,把项目文件(独立影子 git 仓库快照)与对话历史(官方 fork)一并回退到该消息发送之前。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/limbo947/dsh-recall-plugin.git"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"main": "lib/index.js",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./lib/index.js",
|
|
16
|
+
"./client": "./lib/client.js",
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"lib",
|
|
21
|
+
"cordis.patch.yml",
|
|
22
|
+
"README.md",
|
|
23
|
+
"README.en.md",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"check:dsh": "node scripts/check-dsh-version.mjs",
|
|
32
|
+
"build": "node scripts/build-client.mjs",
|
|
33
|
+
"test": "vitest run tests/unit",
|
|
34
|
+
"test:probe": "vitest run tests/probe",
|
|
35
|
+
"verify:host": "node scripts/verify-host.mjs"
|
|
36
|
+
},
|
|
37
|
+
"dsh": {
|
|
38
|
+
"bundle": {
|
|
39
|
+
"patch": "./cordis.patch.yml"
|
|
40
|
+
},
|
|
41
|
+
"client": {
|
|
42
|
+
"platform": "web",
|
|
43
|
+
"inject": [
|
|
44
|
+
"@deepseek-ai/dsh-client-web-react"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
50
|
+
"@deepseek-ai/dsh-client-web-react": "^0.1.1-rc.2",
|
|
51
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.2",
|
|
52
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.1.1-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-session": "^0.1.1-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-session-query": "^0.1.1-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-shell": "^0.1.1-rc.2",
|
|
57
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
58
|
+
"react": "^18.2.0"
|
|
59
|
+
},
|
|
60
|
+
"license": "MIT",
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"esbuild": "^0.28.1",
|
|
63
|
+
"vitest": "^4.1.11"
|
|
64
|
+
}
|
|
65
|
+
}
|