dsh-writing-viewer
writing-anything 工作区的只读写作面板:作品总览卡片、单元树、YAML 产物的语义卡片与源码高亮视图、素材清单与决策/复核清单,以及面板内单元审阅(反馈经 wa-cli 写回)。
安装
dsh plugin --profile web add dsh-writing-viewer
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-writing-viewer(工作区查看插件)
DSH 插件(DSH Plugin)dsh-writing-viewer 是 writing-anything 仓库的 DSH 工作区查看插件:只读定位一个包含 .writing-anything/ 工作区(Workspace)的项目目录,在 DeepSeek Harness(DSH)Web GUI 的全局面板中以总览仪表盘(Overview Dashboard)呈现:作品(Work)卡片、工作区级计数、校验诊断与单元(Unit)树导航;定位不到时给出 wa-cli init 空态指引,面板内可切换到显式输入的项目路径。host 侧提供只读的工作区快照(Workspace Snapshot)路由,把工作区的配置、作品与单元树、各单元产物(Artifact)全文、素材、决策、规范包(Pack)与校验诊断聚合为结构化 JSON 供 client 拉取——产物语义视图与语法高亮由本里程碑(dsh-workspace-viewer)的后续变更在同一包内扩展。
- 默认严格只读:host 侧的全部文件系统操作都是读取(对
.writing-anything的stat、目录列举readdir与文件读取readFile,均不跟随符号链接);不提供任何写入工作区的能力。 - 一个例外要如实说明:快照经子进程调用 wa-cli 只读命令(
status/validate/next/timeline)取结构状态,而 wa-cli 的审计日志(Audit Log)契约规定每次调用(含只读命令)都向.writing-anything/audit.log追加一行运维痕迹——该追加出自 wa-cli 而非插件代码,且 audit.log 不是产物(Artifact);插件在每次快照请求内有界调用这些命令,不做轮询式反复调用。 - 一个由作者显式触发的工作区写入面:单元审阅(见下文「单元审阅」专节,默认开启)以审阅视图(Review View)原生呈现 wa-cli 阅读页(Reading View)的数据,作者在其中提交的反馈、确认与复核经同源代理交给 wa-cli 子进程写入工作区;插件代码只做同源代理,自身不写任何文件,确认渠道(Confirmation Channel)如实记为
preview。 - 一个可选的写入面:默认关闭的「内置技能与 CLI」设置能力(
enableSetup,见下文专节)在作者显式点击启用后,向配置的 CLI 安装目录与操作系统临时目录写入——绝不写入.writing-anything/、用户级或项目级技能目录;静态只读测试把该写入面钉在 setup 三个模块内,viewer 路径保持零写入调用。 - 不属于产品运行时:wa-cli(Go 内核)、内置技能与 MCP 服务不依赖本包;本包也不进入 Go 模块与覆盖率范围。
结构
cordis.patch.yml bundle patch:向 DSH profile 组合插入插件行 src/host.ts host 入口(cordis 插件:导出 name 与 apply(ctx, config),含只读 fs/spawn 适配器与两个路由注册) src/client.ts web client 入口(window.__ModuleLoader__.load 工厂):总览、单元树、路径切换、加载态与键盘导航的渲染与交互 src/overview.ts 总览视图模型的纯函数:作品卡片、工作区计数、单元树与单元详情(client 打包内联) src/treekeys.ts 单元树键盘导航的纯逻辑:可见节点过滤与方向键决策(选中/收起/展开),无 React 与 DOM 依赖 src/root.ts 工作区定位的纯逻辑(候选顺序、去重、探测) src/registry.ts DSH 工作区注册表(workspaceRegistry.list())的容错读取 src/snapshot.ts 工作区快照聚合:类型定义与注入式聚合入口(每次请求重读,无缓存) src/wacli.ts wa-cli 只读命令桥:--json 输出映射与按区域容错降级 src/routeguard.ts 路由请求来源校验(Request Origin Guard):与宿主 /api 信任栅栏同构的 Host/cross-site/Origin 判定,纯函数、永不抛错 src/artifacts.ts 产物清单与全文读取(按 JSON Schema 约束的路径,跳过符号链接) src/queueview.ts 决策队列(Decision Queue)与作者复核记录的纯函数视图模型(消费快照携带的 decisions.yaml / author-reviews.yaml 全文) src/setup.ts 「内置技能与 CLI」设置核心(纯逻辑):配置解析、发布产物下载与 SHA256SUMS 校验、原子安装、技能物化与解析、技能提供者注册、PATH 追加(内置后备) src/setuproutes.ts setup 路由与启用动作管线(状态聚合、单飞行、宿主启动时的免下载注册) src/setupfs.ts setup 的真实文件系统适配器——包内唯一持有写入型 node:fs 调用的模块之一(与上两个 setup 模块同为静态只读测试声明的写入面) src/setupview.ts client 侧设置区的纯视图模型:状态解析、双语文案、idle/busy/enabled/error 状态机 src/reviewsession.ts 审阅会话(Review Session):注入式 spawn 管理唯一的 wa-cli preview --serve 子进程(首行信封取端口与令牌、空闲与替换时终止) src/reviewproxy.ts 阅读页服务同源代理的纯逻辑:只放行 page.json、配图图片(illustration,只转发 unit 与 id)与三个提交接口、令牌注入、只去掉传输头、JSON 形式的无会话提示 src/reviewroutes.ts 审阅会话路由与代理 prefix 路由的注册(先过请求来源校验) src/reviewview.ts client 侧审阅控件的纯视图模型:启动/进行中/失败/来源拒绝/已结束状态与双语文案 src/reviewmodel.ts client 侧审阅视图(Review View)的纯视图模型:page.json 解析(含配图(Illustration))、配图布局与固定代理取图地址、提交请求体、单段选区规则与「待审阅」标记索引 src/copyroutes.ts 「复制图文」只读路由:来源校验、作品 id 校验,调用一次 wa-cli export --format paste-html --print --json,只回传正文片段、配图数与诊断(含 severity) src/copyview.ts client 侧「复制图文」:路由响应解析、text/plain 生成、剪贴板写入(异步剪贴板接口,非安全上下文退回 copy 事件写入,片段不进 DOM)与双语文案 scripts/gen-large-workspace.mjs 经 wa-cli 生成多卷多场景长篇合成工作区(性能检查夹具) scripts/perf-check.mjs 端到端大工作区性能检查并把耗时记录进 docs/evidence/perf-check.txt testdata/ 测试夹具:真实命令生成的多作品工作区 + 捕获的 --json 输出 dist/index.js 构建产物:host 入口(package.json 的 "." 导出) dist/client.js 构建产物:client 入口(package.json 的 "./client" 导出)
package.json 声明:
dsh.bundle.patch→./cordis.patch.yml(insert 一个插件行);dsh.client→{ inject: ["@deepseek-ai/dsh-client-ui-sidebar-right"], platform: "web", immediately: true }(先载入宿主右侧栏模块,其 client 提供sidebarRightTabs服务);engines.dsh→>=0.2.1-rc.2 <0.3.0(实测验证用的宿主为 DSH 0.2.1-rc.2)。
host 侧行为
apply(ctx, config) 通过 scoped ctx.inject 获取两个可选服务,宿主没有它们时插件保持安静:
workspaceRegistry(DSH 工作区注册表,root 作用域):每次请求同步list()全部已注册工作区的path(0.2.1-rc.2 实测:workspaceController.follow挂在按连接的 remotes API 组合上,静态插件行的ctx.inject闭包不触发,不可用)。webServer(web profile):注册两个只读 GET 路由(enableSetup开启时另注册一条 setup 路由,见下方专节):/dsh-writing-viewer/workspace:轻量定位状态,返回 JSON:
{
"ok": true,
"root": "/home/user/Code/writing-anything",
"source": "workspace-registry",
"workspaceDir": "/home/user/Code/writing-anything/.writing-anything",
"workspaceExists": true,
"candidates": [{ "source": "workspace-registry", "path": "…", "workspaceExists": true }],
"emptyState": null
}
项目目录解析规则:
- 配置了
workspaceRoot(在 profile 的cordis.patch.yml中为dsh-writing-viewer行设置config.workspaceRoot,或经--patch覆盖层注入)时为排他固定:只探测该目录,目录下没有.writing-anything/就进入空态,不再回退到其他来源; - 未配置时按顺序探测:DSH 工作区注册表的全部路径(注册表顺序)→ 宿主进程工作目录
process.cwd(),第一个探测到.writing-anything/的候选胜出;
candidates 按实际探测顺序记录轨迹;全部落空时 root 为 null、emptyState.command 为 wa-cli init。
/dsh-writing-viewer/snapshot:工作区快照(Workspace Snapshot),聚合结构如下(字段只增不改):
{
"ok": true,
"root": "…", "source": "workspace-registry", "workspaceDir": "…/.writing-anything",
"workspaceExists": true, "candidates": [], "emptyState": null,
"degraded": [],
"snapshot": {
"config": { "path": "config.yaml", "format": "yaml", "text": "…" },
"decisions": { "…": "…" }, "authorReviews": null, "handoffs": { "…": "…" },
"packLock": { "…": "…" }, "packs": [ { "path": "packs/lean-style.yaml", "…": "…" } ],
"sources": [ { "id": "src-note", "sourceYaml": { "…": "…" }, "original": { "…": "…" } } ],
"works": [
{ "id": "my-novel", "dir": "works/my-novel", "workYaml": { "…": "…" },
"units": [ { "ref": "my-novel/scene-1", "type": "scene", "stage": "brief", "blockers": [], "progress": null, "commands": [], "dir": "works/my-novel/units/scene-1", "artifacts": { "outline": { "path": "…", "format": "yaml", "text": "…" } }, "draft": null } ],
"ledgers": { "canon": { "…": "…" }, "threads": { "…": "…" }, "state": { "…": "…" }, "knowledge": { "…": "…" } },
"timeline": [ { "unit": "my-novel/scene-1", "time": "第
…(truncated)以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:4,最后更新 2026-11-01