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

多仓联合工作区 ninglovegithub/dsh-workspace-combiner

DeepSeek Harnessv0.8.5
ninglovegithub22 天前有提交已提供中文文档扫描:含敏感能力

当前插件 manifest 已适配 DSH 0.2.0-rc.2 seam API,peer…

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

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-workspace-combiner
npm 源 · dsh-workspace-combiner 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · dev
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功(非静态推断);本次验证针对插件 v0.8.8,当前版本为 v0.8.5
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 22 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-workspace-combiner @ 0.8.5
✓Node 引擎要求 ^22.19.0 || >=24.0.0 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/13 14:46:49

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 128 处,展示前 83 条)
  • 读取本地文件
    lib/host/index.js:963
    return parseGitignore(await readFile(file, "utf8"));
  • 读取本地文件
    lib/host/index.js:995
    entries = await readdir(dir, { withFileTypes: true });
  • 读取本地文件
    lib/host/index.js:1052
    entries = await readdir(dir, { withFileTypes: true });
  • 读取本地文件
    lib/host/index.js:1141
    mtime = (await stat(root)).mtimeMs;
  • 读取本地文件
    lib/host/index.js:1173
    mtime = (await stat(root)).mtimeMs;
  • 读取本地文件
    lib/host/index.js:1244
    const raw = await readFile(join(dir, "package.json"), "utf8");
  • 写入本地文件
    lib/host/index.js:2538
    await rename(this.file, `${this.file}.corrupt-${Date.now()}`);
  • 写入本地文件
    lib/host/index.js:2594
    await mkdir(dirname(this.file), {
  • 写入本地文件
    lib/host/index.js:2599
    await writeFile(tmp, JSON.stringify(this.shape, null, 2), { mode: 384 });
  • 写入本地文件
    lib/host/index.js:2600
    await rename(tmp, this.file);
  • 写入本地文件
    lib/host/index.js:2601
    await chmod(this.file, 384).catch(() => {});
  • 写入本地文件
    lib/host/index.js:3420
    await mkdir(dirname(this.file), {
  • 删除本地文件
    lib/host/index.js:3858
    await rm(file, { force: true }).catch(() => {});
  • 删除本地文件
    lib/host/index.js:3926
    await rm(sessionContextDirectory(sessionId), {
  • 删除本地文件
    lib/host/index.js:4033
    if (now - (await stat(path)).mtimeMs > maxAgeMs) await rm(path, {
  • 执行外部命令固定命令
    lib/host/index.js:4204
    execFile("git", [
  • 执行外部命令固定命令
    lib/host/index.js:4303
    execFile("git", [
  • 发起网络请求
    lib/client.js:1185
    res = await fetch(path, {
  • 写入剪贴板
    lib/client.js:2861
    await clipboard.writeText(text);
  • 写入剪贴板
    lib/client.js:5723
    await navigator.clipboard.writeText(text);
  • 发起网络请求
    src/client/api.ts:41
    res = await fetch(path, {
  • 读取本地文件
    src/host/codeIndex.ts:225
    entries = await readdir(dir, { withFileTypes: true })
  • 读取本地文件
    src/host/codeIndex.ts:294
    const info = await stat(abs)
  • 读取本地文件
    src/host/codeIndex.ts:296
    content = await readFile(abs, 'utf8')
  • 读取本地文件
    src/host/codeIndex.ts:748
    mtime = (await stat(d.path)).mtimeMs
  • 读取本地文件
    src/host/codeIndex.ts:813
    const parsed: unknown = JSON.parse(await readFile(this.file, 'utf8'))
  • 写入本地文件
    src/host/codeIndex.ts:830
    await mkdir(dirname(this.file), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/host/codeIndex.ts:832
    await writeFile(tmp, JSON.stringify(payload), { mode: 0o600 })
  • 写入本地文件
    src/host/codeIndex.ts:833
    await rename(tmp, this.file)
  • 写入本地文件
    src/host/codeIndex.ts:834
    await chmod(this.file, 0o600).catch(() => {})
  • 写入本地文件
    src/host/codeIndex.ts:837
    await writeFile(textTmp, renderCodeIndexText(payload.entries), { mode: 0o600 })
  • 写入本地文件
    src/host/codeIndex.ts:838
    await rename(textTmp, codeIndexTextFile())
  • 读取本地文件
    src/host/codeIndexSummary.ts:179
    const parsed: unknown = JSON.parse(await readFile(this.file, 'utf8'))
  • 写入本地文件
    src/host/codeIndexSummary.ts:204
    await mkdir(dirname(this.file), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/host/codeIndexSummary.ts:210
    await writeFile(tmp, JSON.stringify(payload), { mode: 0o600 })
  • 写入本地文件
    src/host/codeIndexSummary.ts:211
    await rename(tmp, this.file)
  • 写入本地文件
    src/host/codeIndexSummary.ts:212
    await chmod(this.file, 0o600).catch(() => {})
  • 读取本地文件
    src/host/diagnostics.ts:29
    const raw = JSON.parse(await readFile(new URL('../../package.json', import.meta.url), 'utf8')) as { version?: unknown }
  • 读取本地文件
    src/host/diagnostics.ts:47
    try { if (!(await stat(directory.path)).isDirectory()) invalid.push(directory.path) } catch { invalid.push(directory.path) }
  • 读取本地文件
    src/host/diagnostics.ts:102
    const parsed: unknown = JSON.parse(await readFile(defaultStoreFile(), 'utf8'))
  • 读取本地文件
    src/host/fileIndex.ts:145
    return parseGitignore(await readFile(file, 'utf8'))
  • 读取本地文件
    src/host/fileIndex.ts:182
    entries = await readdir(dir, { withFileTypes: true })
  • 读取本地文件
    src/host/fileIndex.ts:223
    entries = await readdir(dir, { withFileTypes: true })
  • 读取本地文件
    src/host/fileIndex.ts:311
    mtime = (await stat(root)).mtimeMs
  • 读取本地文件
    src/host/fileIndex.ts:338
    mtime = (await stat(root)).mtimeMs
  • 执行外部命令固定命令
    src/host/gitStatus.ts:101
    execFile(
  • 执行外部命令固定命令
    src/host/gitStatus.ts:186
    execFile(
  • 删除本地文件
    src/host/onDemandContext.ts:58
    await rm(file, { force: true }).catch(() => {})
  • 写入本地文件
    src/host/onDemandContext.ts:62
    await mkdir(dirname(file), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/host/onDemandContext.ts:64
    await writeFile(tmp, text, { mode: 0o600 })
  • 写入本地文件
    src/host/onDemandContext.ts:65
    await rename(tmp, file)
  • 写入本地文件
    src/host/onDemandContext.ts:66
    await chmod(file, 0o600).catch(() => {})
  • 删除本地文件递归
    src/host/onDemandContext.ts:143
    await rm(sessionContextDirectory(sessionId), { recursive: true, force: true }).catch(() => {})
  • 读取本地文件
    src/host/onDemandContext.ts:264
    try { entries = await readdir(sessionsDirectory(), { withFileTypes: true }) } catch { return }
  • 删除本地文件递归
    src/host/onDemandContext.ts:269
    if (now - (await stat(path)).mtimeMs > maxAgeMs) await rm(path, { recursive: true, force: true })
  • 读取本地文件
    src/host/onDemandContext.ts:269
    if (now - (await stat(path)).mtimeMs > maxAgeMs) await rm(path, { recursive: true, force: true })
  • 读取本地文件
    src/host/projectDetector.ts:37
    const raw = await readFile(join(dir, 'package.json'), 'utf8')
  • 读取本地文件
    src/host/projectDetector.ts:83
    try { return await readFile(path, 'utf8') } catch (error) { warnIoFailure('project-scan', path, error); return undefined }
  • 读取本地文件
    src/host/projectDetector.ts:93
    try { entries = await readdir(dir, { withFileTypes: true }) } catch (error) { warnIoFailure('monorepo-scan', dir, error); return }
  • 读取本地文件
    src/host/projectDetector.ts:175
    try { childEntries = await readdir(dir, { withFileTypes: true }) } catch (error) { warnIoFailure('monorepo-scan', dir, error); continue }
  • 读取本地文件
    src/host/projectDetector.ts:305
    entries = await readdir(dir, { withFileTypes: true })
  • 读取本地文件
    src/host/standardsLibrary.ts:69
    this.library = parseLibrary(JSON.parse(await readFile(this.file, 'utf8')))
  • 写入本地文件
    src/host/standardsLibrary.ts:78
    await mkdir(dirname(this.file), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/host/standardsLibrary.ts:80
    await writeFile(tmp, JSON.stringify(this.library, null, 2), { mode: 0o600 })
  • 写入本地文件
    src/host/standardsLibrary.ts:81
    await rename(tmp, this.file)
  • 写入本地文件
    src/host/standardsLibrary.ts:82
    await chmod(this.file, 0o600).catch(() => {})
  • 写入本地文件
    src/routes.ts:254
    await mkdir(targetPath, { recursive: true })
  • 写入本地文件
    src/routes.ts:300
    await mkdir(targetPath, { recursive: true })
  • 读取本地文件
    src/routes.ts:591
    const info = await stat(path)
  • 读取本地文件
    src/routes.ts:615
    const info = await stat(path)
  • 读取本地文件
    src/sandbox-sync.ts:195
    return await readFile(sandboxConfigFile(), 'utf8')
  • 写入本地文件
    src/sandbox-sync.ts:266
    await mkdir(dirname(file), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/sandbox-sync.ts:269
    await writeFile(tmp, String(doc), { mode: 0o600 })
  • 写入本地文件
    src/sandbox-sync.ts:270
    await rename(tmp, file)
  • 写入本地文件
    src/sandbox-sync.ts:271
    await chmod(file, 0o600).catch(() => {})
  • 读取本地文件
    src/sandbox-sync.ts:293
    if (!existsSync(sandboxConfigFile())) return true
  • 读取本地文件
    src/store.ts:483
    raw = await readFile(this.file, 'utf8')
  • 写入本地文件
    src/store.ts:514
    await rename(this.file, `${this.file}.corrupt-${Date.now()}`)
  • 读取本地文件
    src/store.ts:556
    raw = await readFile(this.file, 'utf8')
  • 写入本地文件
    src/store.ts:580
    await mkdir(dirname(this.file), { recursive: true, mode: 0o700 })
  • 写入本地文件
    src/store.ts:583
    await writeFile(tmp, JSON.stringify(this.shape, null, 2), { mode: 0o600 })
  • 写入本地文件
    src/store.ts:584
    await rename(tmp, this.file)
  • 写入本地文件
    src/store.ts:585
    await chmod(this.file, 0o600).catch(() => {
扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

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

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-session@deepseek-ai/dsh-system-prompt@deepseek-ai/dsh-client-locale
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 适配DSHseamAPI

README

由 DeepSeek 最新模型翻译生成
dsh-workspace-combiner 当前插件 manifest 已适配 DSH 0.2.0-rc.2 seam API,peer 版本用范围(>=0.2.0-rc.1 <0.3.0)而非精确锁定,同一 rc 线内的后续版本无需重新发布即可加载;pnpm smoke:dsh-compat 守住这一声明。DSH 宿主已经提供的服务不会重复打包进插件。 为全栈开发者而生:把前端仓库、后端仓库与文档锚点捆进同一个 AI 会话。绝对路径注入、沙盒可写根自动同步,接口变更在同一轮对话里前后端一起改。 中文 · English 一个用于 DSH(DeepSeek Harness)的 Cordis 双面插件,把一组仓库组织成协同的 多工作区联合开发 上下文。 插件在左侧边栏注册一个 工作区组合器 面板。每个「工作空间」绑定一个主目录(工作区锚点——放文档与非代码资产)加一组有序的 代码项目 目录(例如一个后端仓库、一个前端仓库)。新建会话时,插件把所有生效目录的绝对路径注入 system prompt,并把可写目录同步进 dsh-write-protect 白名单。 为抑制注入上下文膨胀,插件提供 五层上下文控制: 1. 目录层 — 逐目录访问模式(readwrite / readonly / disabled)、主目录锚定、拖拽排序。
展开完整文档(README 剩余 1 节)
dsh-workspace-combiner

当前插件 manifest 已适配 DSH 0.2.0-rc.2 seam API,peer 版本用范围(>=0.2.0-rc.1 <0.3.0)而非精确锁定,同一 rc 线内的后续版本无需重新发布即可加载;pnpm smoke:dsh-compat 守住这一声明。DSH 宿主已经提供的服务不会重复打包进插件。

为全栈开发者而生:把前端仓库、后端仓库与文档锚点捆进同一个 AI 会话。绝对路径注入、沙盒可写根自动同步,接口变更在同一轮对话里前后端一起改。

中文 · English

一个用于 DSH(DeepSeek Harness)的 Cordis 双面插件,把一组仓库组织成协同的 多工作区联合开发 上下文。

插件在左侧边栏注册一个 工作区组合器 面板。每个「工作空间」绑定一个主目录(工作区锚点——放文档与非代码资产)加一组有序的 代码项目 目录(例如一个后端仓库、一个前端仓库)。新建会话时,插件把所有生效目录的绝对路径注入 system prompt,并把可写目录同步进 dsh-write-protect 白名单。

为抑制注入上下文膨胀,插件提供 五层上下文控制:

1. 目录层 — 逐目录访问模式(readwrite / readonly / disabled)、主目录锚定、拖拽排序。
2. 配置层 — anchor / single 工作空间模式,以及可选的分组标签。
3. 加载层 — full / summary / tree 控制注入的文件细节量。
4. 命令层 — @ 指令动态范围(@dir/、@file、@功能名)。
5. 索引层 — 功能/接口索引(端点 ↔ 服务端 ↔ 前端落点)+ token 预算按目录配额 + 低信号优先截断。

功能特性

- 工作空间 — 新建 / 重命名 / 删除 / 切换命名工作空间;每个工作空间拥有一个主目录与有序的代码项目目录,并带逐目录访问模式。
- 工作空间就绪报告 — 顶部状态实时汇总目录有效性、前后端识别、沙盒写权限、Git 改动、接口配对、项目命令、上下文预算与关联会话;区分「已就绪 / 需关注 / 不可用」,点击可查看阻断问题和优化建议。
- 开始任务向导 — 输入业务目标并选择任务类型;只有高可信接口会自动收敛目录。可编辑启动/测试/构建/影响复核要求并查看命令覆盖;确认页会展示逐项目执行计划,并检查缺失目录、脏仓库、只读项目和验证缺口,阻断问题修复前不会创建会话。
- TaskMode 稳定工作流 — 功能开发、API 联调、Bug 修复、代码审查、跨仓重构和自定义任务由统一注册表驱动;每种模式都有明确的目录策略、上下文预设、必读数据块、验证、写权限和最终输出格式。
- 新建向导 — 填名称 + 基础路径,宿主创建同名主目录,并扫描识别 Java / Vue / React / Python / Go 等项目供多选;按技术栈自动分组、从真实项目脚本生成命令,并按项目数量建议加载模式。
- Monorepo 原生识别 — 支持 pnpm/npm/yarn/bun workspaces、Turbo、Nx、Lerna、Maven modules 与 Gradle multi-project;根目录只加载一次,同时把内部包边界和绝对路径注入会话。
- 三种添加目录方式 — 从 DSH 原生工作区选择、打开宿主目录选择器、或粘贴绝对路径。
- 目录三态访问 — readwrite(沙盒可写)、readonly(可见不可写)、disabled(不注入 prompt)。主目录恒为 readwrite。
- 工作空间模式 — anchor(主目录 = 文档锚点)与 single(主目录 = 核心业务代码)。
- 目录分组 — 可选分组标签(文档 / 后端 / 前端 / 参考 / 其他),在 prompt 中作为小标题渲染。
- 目录快照 — 保存 / 恢复 / 删除目录配置;恢复后立即重新同步沙盒白名单。
- 文件索引 + 加载模式 — 感知 .gitignore 的有界文件树扫描 + mtime 缓存;按工作空间选择加载模式控制注入细节。
- 功能/接口索引 — 从代码里抽取 HTTP 端点,并联结 服务端注册处 与 前端调用处 的 文件:行号;覆盖 TS/JS/TSX/JSX/Vue 与 Java/Kotlin/Go/Python/Ruby/C#。每条结果显示「精确 / 标准化 / 推断 / 单边」置信度,悬停可查看来源、匹配依据和索引时间。
- 符号级代码骨架 — 复用同一遍源码扫描抽取函数、类、接口、类型、枚举和常见组件,并保存压缩声明签名;任务胶囊只常驻命中的骨架,完整清单按会话隔离并按需查询,让 AI 先理解参数与类型边界,再决定是否读取源码。
- 最小依赖邻域 — 确定性解析本地导入,只把任务证据周围的一跳直接依赖与直接调用方常驻;完整依赖图按需查询,避免一个相关文件触发整模块读取。
- 精准测试映射 — 通过测试直接导入或保守的文件名规则关联源码与测试;任务胶囊只推荐相关测试,完整映射按需查询,验证先小范围执行再逐步扩大。
- 可解释端点关联 — 确定性优先、标准化匹配、启发式降级、结果可解释:/api/user/detail 与 /user/detail 可在剥一层 /api(可跟 /vN)后联结;Spring 类级与方法级路由会组合;命名端点引用则明确标为推断。动态或生成式路由仍应以实际代码为准。
- 本次变更涉及的端点 — 从 git 工作区变更出发,用同一遍扫描产出的「文件 → 端点」反向索引,列出这些改动文件声明或调用的端点及对端文件。这是「影响分析」的确定性版本:只查表,不预测。
- 逐项目命令手册 — 每个目录可填 run / test / build 三条命令(卡片内联编辑);Node 项目读取包管理器、锁文件和真实 scripts,Java 识别 Maven/Gradle wrapper,Go/Python 使用保守默认值。命令带绝对路径注入 prompt,让模型知道在哪个目录自启与自证。
- AI 功能摘要(可选) — 开启后由模型为每个功能生成一句话摘要;按功能签名缓存并落盘,后台异步生成(不阻塞会话创建与首轮),任何失败静默降级。
- 上下文监控(估算) — 弹窗显示逐目录文件数、估算 token 与总预算:环形图总览 + 单行统计卡 + 横向可滚动条形图,超过 80% / 100% 给出警告。固定开销按 整块渲染量 − 各区块 推导,各项相加即实际注入量。
- 上下文预设 + 全局预算闸 — 新工作空间默认使用「平衡」,也可切换「省钱 / 深度分析 / 自定义」;预设统一调整加载模式和文件索引、功能索引、开发规范、项目命令预算。超出全局上限时优先保留任务、目录、显式 @ 引用与开发规范,并在面板和 prompt 中解释被截断或跳过的区块。
- 统一诊断中心 — 一次检查目录、沙盒白名单、DSH 服务、关联会话配置、索引缓存、token 偏差、项目命令、超大目录和配置版本;安全问题可一键刷新,复制报告会自动隐藏用户名和密钥。
- 真实 token 用量(实测) — 同一弹窗顶部显示模型上报的真实消耗:未命中输入 / 缓存读 / 缓存写 / 输出 / 缓存命中率 / 上下文占用条,以及 插件注入占比(注入估算 ÷ 最近一次请求的 prompt 总量)。估算与实测并排,差距大就意味着估算模型要修。
- @ 指令动态范围 — prompt 分节教会模型把 @ 前缀 token 解析为显式引用路径(@dir/、@file、@"含空格的路径"、@功能名),按需把文件纳入范围。
- 会话级按需上下文隔离 — 开发规范、项目命令和功能索引写入 ~/.dsh/dsh-workspace-combiner/sessions/<会话哈希>/;并行会话互不覆盖,关闭后自动清理。
- 任务上下文胶囊 — 开始任务后只常驻项目入口、配置、测试位置、当前改动、命中接口和建议读取顺序;完整文件树转为会话级按需文件,大型项目显著降低常驻 token。
- 沙盒联动 — 读写目录被推入 dsh-write-protect 的 writablePaths(写入 desktop profile 的 cordis.patch.yml,靠 patchReload: live 热生效);底部图例显示实际进入白名单的目录数。
- 逐目录 Git 状态 — 每行显示分支,有未提交/未跟踪改动时以琥珀色 *N 标注,干净时为灰色,领先上游时 ↑N;非仓库目录不显示。
- 目录备注 — 任意目录可挂一段随手备注,行内编辑,随目录配置一起持久化。
- 目录角色徽标 — 主目录标记 Docs only、其余标记 Code,一眼区分锚点与源码(悬停有说明)。
- 注入 prompt 实时预览 — 展开只读区块,渲染当前配置将注入的 完整文本,可一键复制。
- 固定高度面板 + 局部滚动 — 面板填满侧栏,只有列表滚动,标题与「开始任务」按钮不会滚出屏幕。
- 紧凑入口卡 + 弹窗 — 高级配置、开发规范、注入预览、上下文预算都收成左栏入口卡,点开才是弹窗,不再长期占据面板高度。
- 键盘 — Cmd/Ctrl+N 打开开始任务向导、Cmd/Ctrl+K 命令面板、Esc 关闭;命令面板同时保留无任务范围的快速会话。

架构

~~~text
dsh-workspace-combiner/
├── package.json # dsh 字段:bundle.patch + client.inject ├── cordis.patch.yml # 注册补丁 ├── tsconfig.json # 类型检查 ├── tsdown.config.ts # 双入口构建:host + client └── src/ ├── invariant.ts # 共享常量(插件 id / API 路径 / prompt 排序 / 默认预算) ├── prompt.ts # 多工作区 prompt + 文件索引 + 功能索引渲染 ├── store.ts # 宿主持久化(~/.dsh/dsh-workspace-combiner.json) ├── sandbox-sync.ts # dsh-write-protect 可写根同步 ├── routes.ts # /api/dsh-workspace-combiner 路由(仅 loopback) ├── core/ │ ├── types.ts # 共享类型(WorkspaceRef / Workspace / CodeIndexEntry / ...) │ ├── fileTree.ts # 平台无关的文件树类型与纯工具(计数/渲染/token 估算) │ └── validate.ts # 共享的目录项校验 ├── host/ │ ├── index.ts # host 入口:prompt 分节 + session/created + 路由 + 缓存 │ ├── projectDetector.ts # 扫描目录识别项目类型 │ ├── fileIndex.ts # 感知 gitignore 的有界文件树扫描 + 递归计数 + mtime/TTL 缓存 │ ├── codeIndex.ts # 功能/接口索引抽取 + 内存/磁盘缓存 │ ├── codeIndexSummary.ts # 可选:AI 功能摘要(后台生成 + 落盘缓存) │ ├── gitStatus.ts # 逐目录 分支 / 改动 / 未跟踪 / 领先 │ └── contextStats.ts # token 估算 + 逐目录上下文统计 └── client/ ├── index.ts # client 入口:侧栏图标 + 中心列面板 ├── types.ts # client 侧镜像类型 ├── locales.ts # 中英词典 + tt() 助手 ├── api.ts # client -> host fetch API └── panel/ ├── WorkspaceCombinerPanel.tsx # 面板主体 + 图标 ├── controller.ts # 状态管理 ├── NewWorkspaceWizard.tsx # 新建工作空间向导 ├── typeBadge.tsx # 项目类型徽标 ├── naming.ts # 名称清洗 / 查重 └── styles.ts # 注入的 style 块(随主题适配)

数据流

~~~text
[客户端面板] --POST /api/.../...--> [宿主 store]
│
session/created(顶层新建会话) ▼ selectionBySession: sessionId -> { directories, mode, loadMode, tokenBudget, entries, codeEntries, codeIndexBudget } ▼
systemPrompt.section(text 函数按会话渲染;同会话快照复用已渲染文本)
▼
注入「多工作区联合开发模式生效」+ 目录清单 + 文件索引 + 功能/接口索引 + @规则

目录变化时宿主会更新 desktop profile 的 cordis.patch.yml 中 dsh-write-protect-policy 的 writablePaths;DSH 以 patchReload: live 热加载,无需重启即生效(不再依赖任何第三方私有远程服务)。

安装(本地目录)

前置:已安装 DSH,且依赖插件可用。

~~~bash

0) 依赖插件(必需)

dsh plugin --profile desktop add dsh-write-protect

1) 构建 lib/(host + client)

cd /path/to/dsh-workspace-combiner
pnpm install
pnpm build

2) 加载本插件

dsh plugin --profile desktop add file:./dsh-workspace-combiner

3) 校验

dsh plugin --profile desktop ls dsh-workspace-combiner

4) 重启 DSH Desktop(web profile 则重跑 dsh web)

改动源码后:pnpm build,然后把构建产物同步到 profile 的插件副本目录并重启(或移除插件后重新添加)。

使用

1. 打开侧栏的 工作区组合器 标签。
2. 在工作空间列表里选一个,或用 新建工作空间 向导创建(名称 + 基础路径;宿主创建主目录并扫描代码项目)。
3. 添加目录(目录选择器或粘贴绝对路径)并核对每行:主目录标记 Docs only,其余 Code,附带 Git 分支、分组与访问模式。可拖拽排序;多选后可批量改访问模式、分组或删除。
4. 展开 高级配置 设置工作空间模式、文件加载模式、快照,以及 @ 指令速查卡。
5. 展开 注入 prompt 预览将发送的完整内容,必要时复制。
6. 点击 开始任务,输入业务目标并确认推荐目录、加载模式和验证要求;也可用 快速会话 直接沿用工作空间配置。
7. 看 上下文预算 卡:环形图、单行统计卡与 横向条形图;拖动上方分隔条可在它与目录列表之间分配空间。
8. 看 功能/接口索引 卡:核对端点与前后端落点;点条目复制 @功能名;用筛选框按功能/端点/路径过滤;右上可开关、调预算、切 AI 摘要。

注意:任务范围只属于本次新会话,不会写回工作空间。工作空间配置变化会刷新本进程内已经绑定的会话,并从下一轮请求生效。

注入的 prompt(追加到 system prompt)

~~~text

多工作区联合开发模式生效

当前会话加载【2】个项目目录: 1.example-anchor【主项目 · 工作区锚点(文档/非代码文件保存区)】绝对路径:/abs/path 【后端】 2.example-backend【代码项目】绝对路径:/abs/path

文件索引(加载模式:summary)

【example-anchor】/abs/path 12 文件 / 3 目录【example-backend】/abs/path 210 文件 / 42 目录

各项目常用命令(必须在对应绝对路径下执行;用于自启与自证)

- example-backend(/abs/path): 启动 mvn spring-boot:run | 测试 mvn test

功能/接口索引(自动抽取,用于定位;以实际代码为准)

- @workspaceCreate → /api/dsh-workspace-combiner/workspace-create | 新建工作空间并切换为当前 | 服务端 src/routes.ts:130 | 前端 src/client/api.ts:39
- @state → /api/dsh-workspace-combiner/state | 服务端 src/routes.ts:116 | 前端 src/client/api.ts:34

@指令 · 动态范围

- 消息中以 @ 开头的 token 是被显式引用的路径:@绝对路径,或 @相对某工作区根的相对路径。
- @结尾带 / 的是目录:需要其内容时列出其目录树(ls / read)。
- 其它是文件:需要其内容时先用 read 读取,禁止未读就声称已检查。
- 含空格的路径用 @"路径 with spaces" 包裹。
- @功能名(如 @workspaceCreate):指上方「功能/接口索引」里的名字,展开即读取该项列出的服务端/前端文件,用于快速定位。

开发强制规则:
1. 读写文件、查看代码必须使用完整绝对路径,禁止相对路径
2. 多个仓库Git相互独立,提交互不干扰
3. 做接口变更时,同步修改后端代码与前端请求代码
4. 终端执行命令,必须填写文件完整绝对路径,不允许直接使用相对路径执行

配置

插件级(cordis.patch.yml,无 schema)

~~~yaml
workspace-combiner:
enabled: true # 总开关 announceToAgent: true # 是否注入多工作区 prompt 分节

工作空间级(面板内可改,随 dsh-workspace-combiner.json 持久化)

| 字段 | 默认 | 说明 |
| model | anchor | 工作空间模式(anchor / single) |
| loadMode | summary | 文件加载模式(summary / tree / full) |
| tokenBudget | 60000 | 文件索引注入的 token 预算(实际生效) |
| codeIndexEnabled | true | 是否注入功能/接口索引 |
| codeIndexBudget | 400 | 功能索引的独立 token 预算 |
| codeIndexSummary | off | 摘要模式:off / llm(AI 生成一句话摘要) |
| directories[].commands | 无 | 逐目录 run / test / build,带绝对路径注入(独立预算 400) |

上下文与 token 优化

- 递归真实计数 — 摘要模式报告的是 递归 文件/目录总数(而非只数顶层),面板数字与实际体量一致。
- 按目录配额 + 余量回填 — 文件索引预算先保证每个目录都能出现,再把剩余预算补给被截断的目录,避免一个大目录把后面的代码项目整块挤掉。
- 低信号优先丢弃 — 预算不足时优先丢测试、夹具、快照、锁文件、sourcemap、声明文件与生成物,而不是按文件名顺序误伤源码。
- 预算真正生效 — 文件索引与功能索引各自受预算约束,超出时以块内标注说明。
- 同会话渲染缓存 — prompt 分节每个模型步都会求值一次;同会话快照直接复用已渲染文本,不再逐步重拼。
- 按需注入 — @功能名 规则仅在实际存在功能索引时注入;功能索引可整卡关闭。

性能与缓存

- 并行扫描 — 建会话时各目录并行扫描(保序);文件树同级目录以有界并发(8)下钻,避免深/宽目录串行等待。
- 轻量探测 — 目录失效检测走 /stat(只 stat),不再为判断存在性构建整棵文件树。
- 缓存分层 — 文件树/计数缓存(根 mtime + 30s TTL);功能索引缓存(目录签名 + 60s TTL + 落盘 ~/.dsh/dsh-workspace-combiner-code-index.json);AI 摘要缓存(签名 + 落盘 ~/.dsh/dsh-workspace-combiner-feature-summaries.json,并发去重)。
- 深度统一 — loadModeMaxDepth() 是 summary/tree/full → 1/3/4 的唯一来源;/file-index 接收 loadMode,保证预览深度与真实注入一致(并复用同一份缓存)。
- 忽略目录收敛 — 默认忽略额外覆盖 .pnpm-store、.next、.nuxt、.turbo、.venv、__pycache__、.gradle 等。

设计约束

1. 会话 shell 只有一个不可改的 cwd。 插件不改 cwd,而是靠 prompt 规则强制绝对路径。
2. 只对新建会话生效。 session/created 时快照勾选并绑定到该会话 id;旧会话与子/分支会话(带 parentSession)不注入。
3. 读模型 — DSH 沙盒的读本就不受限;三态访问只约束 写(经 writablePaths)与 是否进 prompt。
4. 依赖 声明在 package.json 的 dsh.client.inject 与 dependencies/peerDependencies(yaml 运行时依赖 + DSH host 服务)。沙盒可写根由 dsh-write-protect 提供,不再依赖 @chaoset/sandbox-extra-roots。
5. 功能索引不使用 AST:它按「精确 → 标准化 → 启发式 → 单边」标注可信等级与来源;动态路由、别名导入、生成的客户端仍可能漏,同名常量仍可能误联。注入文本会标出推断/单边结果并注明「以实际代码为准」。
6. 辅助路由(仅 loopback):scan、stat、file-index、code-index、git-status、context-stats、workspace-patch,以及 state / 工作空间 CRUD。

开发

~~~bash
pnpm install
pnpm typecheck # tsc --noEmit
pnpm build # tsdown -> lib/host/index.js + lib/client.js
pnpm watch # 监视重建

- 宿主日志:同步失败等场景走 ctx.logger.warn(...)。
- 持久化:~/.dsh/dsh-workspace-combiner.json。
- 功能索引缓存:~/.dsh/dsh-workspace-combiner-code-index.json。
- 功能摘要缓存:~/.dsh/dsh-workspace-combiner-feature-summaries.json。
- 沙盒配置:desktop profile 的 cordis.patch.yml(dsh-write-protect-policy 的 writablePaths)。

变更日志

见 CHANGELOG.zh.md(English)。

许可

MIT
预览分享卡片长什么样 ↓ninglovegithub/dsh-workspace-combiner
分享 · DeepSeek Harness Hub
ninglovegithub/dsh-workspace-combiner

当前插件 manifest 已适配 DSH 0.2.0-rc.2 seam API,peer…

回到顶部