🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

本地长期记忆 qinghui316/dsh-memory

DeepSeek Harness
qinghui31673 天前有提交已提供中文文档扫描:含敏感能力

让新会话继承上次进度,召回项目背景与偏好

25.9/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/12 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add qinghui316/dsh-memory
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:需留意
是什么
dsh 原生插件 · chat
装得上吗
实装验证未通过(dependency_conflict),装前请到仓库确认最近更新与 issue
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 73 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
⚠︎ 实装验证未通过(dependency_conflict · 2026/10/12) ——可能是验证环境差异,装前建议到 GitHub 仓库确认最近更新与 issue。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 176 处,展示前 91 条)
  • 发起网络请求
    packages/client/src/controller.ts:9
    const response = await fetch(`/api/dsh-memory${path}`, {
  • 发起网络请求
    packages/client/src/controller.ts:25
    const value = await this.request(path, { signal: controller.signal })
  • 发起网络请求
    packages/client/src/controller.ts:36
    return this.request(path, { method: 'POST', body: JSON.stringify(payload) })
  • 写入本地文件
    packages/core/src/store.ts:348
    mkdirSync(dirname(path), { recursive: true, mode: 0o700 })
  • 写入本地文件
    packages/core/src/store.ts:351
    mkdirSync(this.skillsRoot, { recursive: true, mode: 0o700 })
  • 写入本地文件
    packages/core/src/store.ts:353
    mkdirSync(this.knowledgeRoot, { recursive: true, mode: 0o700 })
  • 写入本地文件
    packages/core/src/store.ts:356
    mkdirSync(this.offloadRoot, { recursive: true, mode: 0o700 })
  • 删除本地文件
    packages/core/src/store.ts:366
    rmSync(`${path}-wal`, { force: true })
  • 删除本地文件
    packages/core/src/store.ts:367
    rmSync(`${path}-shm`, { force: true })
  • 写入本地文件
    packages/core/src/store.ts:368
    copyFileSync(backup, path)
  • 读取本地文件
    packages/core/src/store.ts:381
    if (!existsSync(this.path)) return undefined
  • 写入本地文件
    packages/core/src/store.ts:394
    if (!existsSync(backup)) copyFileSync(this.path, backup)
  • 读取本地文件
    packages/core/src/store.ts:394
    if (!existsSync(backup)) copyFileSync(this.path, backup)
  • 读取本地文件
    packages/core/src/store.ts:400
    if (process.platform === 'win32' || !existsSync(path)) return
  • 删除本地文件递归
    packages/core/src/store.ts:2453
    rmSync(stage, { recursive: true, force: true })
  • 删除本地文件递归
    packages/core/src/store.ts:2454
    rmSync(destination, { recursive: true, force: true })
  • 读取本地文件
    packages/core/src/store.ts:2460
    if (lstatSync(source).isSymbolicLink()) throw new Error('Skill resources may not be symbolic links')
  • 读取本地文件
    packages/core/src/store.ts:2461
    files.set(resource.path, { bytes: readFileSync(source), ...(resource.mimeType === undefined ? {} : { mimeType: resource.mimeType }) })
  • 删除本地文件递归
    packages/core/src/store.ts:2481
    rmSync(stage, { recursive: true, force: true })
  • 删除本地文件递归
    packages/core/src/store.ts:2546
    rmSync(this.versionRoot(skillId, version), { recursive: true, force: true })
  • 删除本地文件
    packages/plugin/src/artifacts.ts:288
    else await rm(join(directory, 'content.txt'), { force: true })
  • 写入本地文件
    packages/plugin/src/artifacts.ts:313
    await mkdir(path, { recursive: false, mode: 0o700 }).catch((error: NodeJS.ErrnoException) => {
  • 读取本地文件
    packages/plugin/src/artifacts.ts:316
    const stat = await lstat(path)
  • 写入本地文件
    packages/plugin/src/artifacts.ts:318
    await chmod(path, 0o700)
  • 读取本地文件
    packages/plugin/src/artifacts.ts:331
    const existing = await lstat(path).catch((error: NodeJS.ErrnoException) => error.code === 'ENOENT' ? undefined : Promise.reject(error))
  • 删除本地文件
    packages/plugin/src/artifacts.ts:332
    if (existing?.isSymbolicLink()) { await rm(temp, { force: true }); throw new Error('refusing to replace Artifact symlink') }
  • 写入本地文件
    packages/plugin/src/artifacts.ts:333
    await rename(temp, path)
  • 写入本地文件
    packages/plugin/src/artifacts.ts:334
    await chmod(path, 0o600)
  • 读取本地文件
    packages/plugin/src/artifacts.ts:368
    const stat = await lstat(path)
  • 读取本地文件
    packages/plugin/src/artifacts.ts:370
    return JSON.parse(await readFile(path, 'utf8')) as ArtifactEnvelope
  • 读取本地文件
    packages/plugin/src/artifacts.ts:412
    payload = await readFile(join(directory, 'content.txt'), 'utf8').catch(async (error: NodeJS.ErrnoException) => {
  • 写入本地文件
    packages/plugin/src/artifacts.ts:420
    await chmod(directory, 0o700)
  • 写入本地文件
    packages/plugin/src/artifacts.ts:421
    await chmod(join(directory, 'envelope.json'), 0o600)
  • 读取本地文件
    packages/plugin/src/artifacts.ts:437
    const stat = await lstat(directory)
  • 删除本地文件递归
    packages/plugin/src/artifacts.ts:454
    if (artifact.relativePath !== undefined) await rm(await this.resolveRecordedDirectory(artifact.relativePath), { recursive: true, force: true })
  • 执行外部命令固定命令
    packages/plugin/src/git-snapshot.ts:10
    const child = spawn('git', ['-c', 'core.hooksPath=/dev/null', ...args], {
  • 写入本地文件
    packages/plugin/src/git-snapshot.ts:63
    mkdirSync(parent, { recursive: true, mode: 0o700 })
  • 删除本地文件递归
    packages/plugin/src/git-snapshot.ts:64
    rmSync(destination, { recursive: true, force: true })
  • 写入本地文件
    packages/plugin/src/git-snapshot.ts:65
    mkdirSync(destination, { recursive: true, mode: 0o700 })
  • 执行外部命令固定命令
    packages/plugin/src/git-snapshot.ts:69
    const git = spawn('git', ['-c', 'core.hooksPath=/dev/null', 'archive', '--format=tar', commit], {
  • 执行外部命令固定命令
    packages/plugin/src/git-snapshot.ts:72
    const tar = spawn('tar', ['-xf', '-', '-C', destination], { stdio: ['pipe', 'ignore', 'pipe'] })
  • 读取本地文件
    packages/plugin/src/http.ts:276
    await new Promise<void>((resolvePromise, reject) => createReadStream(downloadPath).on('error', reject).on('end', resolvePromise).pipe(res))
  • 读取本地文件
    packages/plugin/src/http.ts:294
    await new Promise<void>((resolvePromise, reject) => createReadStream(path).on('error', reject).on('end', resolvePromise).pipe(res))
  • 读取本地文件
    packages/plugin/src/knowledge-worker.ts:24
    if (!existsSync(path)) return 0
  • 读取本地文件
    packages/plugin/src/knowledge-worker.ts:25
    const entry = lstatSync(path)
  • 读取本地文件
    packages/plugin/src/knowledge-worker.ts:28
    return readdirSync(path).reduce((total, name) => total + directorySize(join(path, name)), 0)
  • 删除本地文件递归
    packages/plugin/src/knowledge-worker.ts:33
    rmSync(dirname(request.sourceRoot), { recursive: true, force: true })
  • 写入本地文件
    packages/plugin/src/knowledge-worker.ts:34
    mkdirSync(dirname(request.sourceRoot), { recursive: true, mode: 0o700 })
  • 读取本地文件
    packages/plugin/src/knowledge-worker.ts:37
    if (existsSync(previousIndex) && statSync(previousIndex).isDirectory()) cpSync(previousIndex, savedIndex, { recursive: true })
  • 读取本地文件
    packages/plugin/src/knowledge-worker.ts:37
    if (existsSync(previousIndex) && statSync(previousIndex).isDirectory()) cpSync(previousIndex, savedIndex, { recursive: true })
  • 读取本地文件
    packages/plugin/src/knowledge-worker.ts:44
    if (existsSync(savedIndex)) {
  • 删除本地文件递归
    packages/plugin/src/knowledge-worker.ts:46
    rmSync(savedIndex, { recursive: true, force: true })
  • 删除本地文件递归
    packages/plugin/src/knowledge-worker.ts:53
    rmSync(join(request.sourceRoot, '.codegraph'), { recursive: true, force: true })
  • 读取本地文件
    packages/plugin/src/knowledge.ts:255
    const entry = lstatSync(absolute)
  • 读取本地文件
    packages/plugin/src/maintenance.ts:53
    if (!existsSync(root)) return { bytes: 0, files: 0 }
  • 读取本地文件
    packages/plugin/src/maintenance.ts:58
    try { directory = await opendir(current) } catch { continue }
  • 读取本地文件
    packages/plugin/src/maintenance.ts:62
    try { info = await lstat(path) } catch { continue }
  • 读取本地文件
    packages/plugin/src/maintenance.ts:75
    for await (const chunk of createReadStream(path)) { const buffer = Buffer.from(chunk); bytes += buffer.length; hash.update(buffer) }
  • 读取本地文件
    packages/plugin/src/maintenance.ts:205
    const bytes = statSync(path).size
  • 读取本地文件
    packages/plugin/src/maintenance.ts:223
    .reduce((sum, path) => sum + (existsSync(path) ? statSync(path).size : 0), 0)
  • 写入本地文件
    packages/plugin/src/maintenance.ts:314
    const root = join(dirname(this.store.path), '.cleanup', runId); await mkdir(root, { recursive: true, mode: 0o700 })
  • 删除本地文件递归
    packages/plugin/src/maintenance.ts:339
    await rm(root, { recursive: true, force: true })
  • 写入本地文件
    packages/plugin/src/maintenance.ts:352
    await rename(source, destination); journal.moves.push({ from: source, to: destination, kind: candidate.kind, recordId: candidate.recordId })
  • 删除本地文件递归
    packages/plugin/src/maintenance.ts:386
    if (candidate?.status === 'deleted') { await rm(item.to, { recursive: true, force: true }); continue }
  • 写入本地文件
    packages/plugin/src/maintenance.ts:387
    if (existsSync(item.to) && !existsSync(item.from)) { await mkdir(dirname(item.from), { recursive: true }); await rename(item.to, item.from) }
  • 写入本地文件
    packages/plugin/src/maintenance.ts:387
    if (existsSync(item.to) && !existsSync(item.from)) { await mkdir(dirname(item.from), { recursive: true }); await rename(item.to, item.from) }
  • 写入本地文件
    packages/plugin/src/maintenance.ts:505
    await mkdir(dirname(outputPath), { recursive: true, mode: 0o700 })
  • 写入本地文件
    packages/plugin/src/maintenance.ts:508
    const zip = new yazl.ZipFile(); const output = createWriteStream(outputPath, { mode: 0o600 })
  • 删除本地文件递归
    packages/plugin/src/portability-archive.ts:73
    await rm(options.extractTo, { recursive: true, force: true })
  • 写入本地文件
    packages/plugin/src/portability-archive.ts:74
    await mkdir(options.extractTo, { recursive: true, mode: 0o700 })
  • 写入本地文件
    packages/plugin/src/portability-archive.ts:150
    mkdirSync(dirname(destination), { recursive: true, mode: 0o700 })
  • 写入本地文件
    packages/plugin/src/portability-archive.ts:152
    const output = destination === undefined ? undefined : createWriteStream(destination, { flags: 'wx', mode: 0o600 })
  • 读取本地文件
    packages/plugin/src/portability.ts:104
    async function hashFile(path: string): Promise<{ bytes: number; sha256: string }> { const hash = createHash('sha256'); let bytes = 0; for await (const
  • 读取本地文件
    packages/plugin/src/portability.ts:106
    function walk(root: string): string[] { if (!existsSync(root)) return []; const result: string[] = []; const visit = (current: string): void => { for
  • 读取本地文件
    packages/plugin/src/portability.ts:106
    function walk(root: string): string[] { if (!existsSync(root)) return []; const result: string[] = []; const visit = (current: string): void => { for
  • 读取本地文件
    packages/plugin/src/portability.ts:106
    function walk(root: string): string[] { if (!existsSync(root)) return []; const result: string[] = []; const visit = (current: string): void => { for
  • 写入本地文件
    packages/plugin/src/portability.ts:107
    function copyTreeSync(source: string, target: string): void { mkdirSync(target, { recursive: true, mode: 0o700 }); for (const file of walk(source)) {
  • 写入本地文件
    packages/plugin/src/portability.ts:107
    function copyTreeSync(source: string, target: string): void { mkdirSync(target, { recursive: true, mode: 0o700 }); for (const file of walk(source)) {
  • 读取本地文件
    packages/plugin/src/portability.ts:108
    function verifyManifestFiles(root: string, manifest: PortabilityManifest, activated = false): void { for (const file of manifest.files) { const safe =
  • 读取本地文件
    packages/plugin/src/portability.ts:111
    const markerPath = join(memoryRoot, RESTORE_MARKER); if (!existsSync(markerPath)) return { status: 'none' }
  • 写入本地文件
    packages/plugin/src/portability.ts:121
    mkdirSync(rollback, { recursive: true, mode: 0o700 })
  • 写入本地文件
    packages/plugin/src/portability.ts:122
    for (const name of ['memory.sqlite3', 'skills', 'knowledge', 'offload']) { const active = join(memoryRoot, name); if (!existsSync(active)) continue; c
  • 写入本地文件
    packages/plugin/src/portability.ts:122
    for (const name of ['memory.sqlite3', 'skills', 'knowledge', 'offload']) { const active = join(memoryRoot, name); if (!existsSync(active)) continue; c
  • 写入本地文件
    packages/plugin/src/portability.ts:123
    copyFileSync(join(marker.stagedPath, 'database.sqlite3'), join(memoryRoot, 'memory.sqlite3'))
  • 删除本地文件递归
    packages/plugin/src/portability.ts:128
    rmSync(markerPath, { force: true }); rmSync(marker.stagedPath, { recursive: true, force: true }); return { status: 'restored' }
  • 删除本地文件递归
    packages/plugin/src/portability.ts:130
    for (const name of ['memory.sqlite3', 'skills', 'knowledge', 'offload']) rmSync(join(memoryRoot, name), { recursive: true, force: true }); for (const
  • 删除本地文件
    packages/plugin/src/portability.ts:147
    for (const created of [...journal.createdFiles].reverse()) rmSync(created, { force: true })
  • 删除本地文件
    packages/plugin/src/portability.ts:148
    for (const directory of [...journal.createdDirectories].reverse()) { try { rmSync(directory) } catch { } }
  • 删除本地文件递归
    packages/plugin/src/portability.ts:149
    rmSync(journal.stagingPath, { recursive: true, force: true })
  • 删除本地文件
    packages/plugin/src/portability.ts:153
    rmSync(file, { force: true })
  • 读取本地文件
    packages/plugin/src/wiki.ts:89
    const text = readFileSync(absolute, 'utf8').slice(0, 500_000)
扫描于 2026/11/2(1 天前) · 范围 全包扫描(包内未声明入口) · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-agent@deepseek-ai/dsh-client-connection@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-runtime@deepseek-ai/dsh-client-ui-primitives@deepseek-ai/dsh-client-ui-layout@deepseek-ai/dsh-client-ui-sidebar@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-compaction@deepseek-ai/dsh-llm@deepseek-ai/dsh-scope
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 继承上次会话进度
  • 召回项目背景
  • 召回用户偏好

DeepSeek Harness 本地单用户长期记忆插件

README

由 DeepSeek 最新模型翻译生成
dsh-memory 让下一次对话继承上一次工作的结果。 Local memory and project knowledge for DeepSeek Harness. 核心能力 ·工作原理 ·安装 ·模式 ·安全 ·文档 它解决什么问题 一个项目做得越久,新会话的启动成本就越高:你需要重新解释项目结构、当前进度、个人偏好、已经验证过的做法,以及上一次工具调用留下了什么结果。 dsh-memory 将这些内容整理为本地、可查询、可维护的长期资产。它不只是保存聊天记录,还会把事实、工作方式、代码知识、工具产物和任务进度连接起来,让后续会话能够召回背景、复用经验并继续未完成的工作。 它是 DeepSeek Harness 的外部插件,通过 Harness 提供的 Bundle、Agent、Tool、Skill 和 Web 扩展接口接入,不修改或 fork DeepSeek Harness。
展开完整文档(README 剩余 1 节)
dsh-memory

让下一次对话继承上一次工作的结果。
Local memory and project knowledge for DeepSeek Harness.

核心能力 ·工作原理 ·安装 ·模式 ·安全 ·文档

它解决什么问题

一个项目做得越久,新会话的启动成本就越高:你需要重新解释项目结构、当前进度、个人偏好、已经验证过的做法,以及上一次工具调用留下了什么结果。

dsh-memory 将这些内容整理为本地、可查询、可维护的长期资产。它不只是保存聊天记录,还会把事实、工作方式、代码知识、工具产物和任务进度连接起来,让后续会话能够召回背景、复用经验并继续未完成的工作。

它是 DeepSeek Harness 的外部插件,通过 Harness 提供的 Bundle、Agent、Tool、Skill 和 Web 扩展接口接入,不修改或 fork DeepSeek Harness。

核心能力

| 能力 | 它会保存什么 | 下一次任务如何使用 |
| 分层记忆 | 带事件时间的 L0 对话、七类结构化 L1、L2 项目 Scene、L3 个人 Persona | 按 workspace、类型、现实有效期和状态召回事实、项目状态、偏好与工作方式 |
| Skill | 从完整工作轨迹中沉淀的 SOP、背景知识和偏好 | Harness 按当前问题路由相关 Skill,需要时再加载正文 |
| 项目知识 | CodeGraph、Wiki、源码索引、调用关系和影响范围 | 搜索符号、浏览源码、追踪 callers/callees、分析改动影响 |
| 任务连续性 | 完整工具结果 Artifact 与版本化 Task Canvas | 找回证据、产物、依赖、进度和下一步,继续历史任务 |
| 本地工作台 | 记忆、技能、项目知识、产物、任务、运行与设置 | 浏览、纠正、保护、回滚、备份、导入和维护全部本地数据 |

界面

工作原理

flowchart LR
A["DSH 会话与工具事件"] --> B["本地采集与后台任务"]
B --> C["SQLite、FTS5 与私有资源文件"]
C --> D["Recall、Skill、CodeGraph、Wiki 与 Canvas"]
D --> E["Agent 工具与记忆工作台"]

- 记忆索引、数据库、代码快照和工具产物均保存在本机。
- L1、Reviewer、Scene、Persona、Wiki 和 Task Canvas 等后台任务使用 DSH 已配置的模型;插件不保存 Provider URL 或 API Key。
- 后台任务首次解析模型后会固定该模型,重试或重启不会静默切换。
- L1 使用本地 BM25 候选检索、现实时间/系统时间和不可变替代链;不需要 Embedding、向量数据库或 RRF。
- 采集、归档、建图和模型任务失败不会改变工具结果,也不会阻塞当前聊天。

更完整的数据流见运行流程,模块关系见架构说明。

快速开始

前置条件

- Node.js 24
- pnpm
- DeepSeek Harness dsh-v0.1.0-rc.8

从源码安装

git clone https://github.com/qinghui316/dsh-memory.git
cd dsh-memory
pnpm install
pnpm test
pnpm typecheck
pnpm build
dsh plugin --profile web add .

启动 Web profile:

dsh --profile web

首次使用:

1. 在 DeepSeek Harness 中配置 Provider 并导入模型。
2. 从侧边栏打开“记忆”。
3. 为会话选择 full 或 recall 模式。
4. 需要项目代码知识时,在“项目知识”中构建 CodeGraph 和 Wiki。

移除插件:

dsh plugin --profile web remove dsh-memory

在仓库目录重新安装:

dsh plugin --profile web remove dsh-memory
dsh plugin --profile web add .

普通移除不会删除已有记忆,重新安装后可以继续使用原数据。

模式

| 模式 | 召回已有数据 | 自动写入与后台演进 | 适合场景 |
| off | 否 | 否 | 当前会话不使用记忆能力 |
| recall | 是 | 否 | 只读取已有记忆、Skill、项目知识和任务产物 |
| full | 是 | 是 | 持续采集对话与工具轨迹,并生成记忆、Skill、Scene、Persona 和 Canvas |

模式在会话首个 step 后锁定,避免同一条执行链中途改变采集边界。工作台始终可以浏览和手动管理已有数据。

数据与隐私

数据默认存放在 $DSH_HOME/memory/;未设置 DSH_HOME 时位于 ~/.dsh/memory/。其中包括 SQLite 数据库、Skill 资源、CodeGraph/Wiki generation、Artifact、备份、导出和维护记录。

需要注意:

- full 会保存模型实际看到的完整工作轨迹以及工具参数、结果、错误和产物,这些内容可能包含敏感信息。
- 项目知识只从当前 workspace 已提交的 Git 内容构建私有快照,不读取 dirty 或 untracked 文件。
- 插件不会把 Provider 凭据保存进自己的数据库,也不提供通用密钥脱敏。
- 普通卸载会保留数据;只有在工作台执行并确认 purge 才会物理删除。
- 这是本地单用户实现,不包含团队 ACL、云端 Memory 服务、远程仓库注册或跨机器同步。

写操作受 loopback 与同源边界保护,Agent 只获得 workspace-scoped 的只读项目知识工具。完整约束见安全边界。

致谢

感谢 TencentDB Agent Memory 的开源实践,为 dsh-memory 的设计提供了启发。

深入文档

- 架构
- 数据模型
- 运行流程
- 安全边界
- 路线图
- 腾讯参考映射

社区

欢迎通过 GitHub Issues 报告问题、讨论需求或提交改进建议。
贡献前请先说明问题和预期行为,便于对齐实现边界。项目采用 MIT License。
预览分享卡片长什么样 ↓qinghui316/dsh-memory
分享 · DeepSeek Harness Hub
qinghui316/dsh-memory

让新会话继承上次进度,召回项目背景与偏好

这是你的仓库?认领条目

回到顶部