Repository navigation
Releases
v0.11.4 — CI 转绿
[0.11.4] — CI 转绿(依赖声明缺失 + 版本锚点写死)
升级收益一眼看:仓库的 CI 从 0.11.0 起一路红灯。这一版修掉两个让它红的原因 ——
都不影响运行时行为,只影响「clone 下来能不能构建/测试」。
Fixed
@deepseek-ai/dsh-user-questions没在dependencies里声明:R8 引入的
user-questions/request类型声明来自这个包,本地能解析只是因为宿主树恰好挡在解析路径上
(与 R11 同一类问题)。干净安装下tsc -b直接报
TS2307: Cannot find module '@deepseek-ai/dsh-user-questions'
以及连带的TS2345: 'user-questions/request' is not assignable to 'keyof Events'。
已按其他 19 个依赖同款声明为^0.1.2-rc.1(npm 上该版本存在且声明了此事件)。- 变异检查 R9-G5 的锚点写死了版本号:
from: "const PLUGIN_VERSION = '0.11.2'"
在发版升到 0.11.3 后失配,报「锚点命中 0 次」→ mutation 步骤红灯。
改成从package.json动态取版本,以后发版不用再改脚本。
Verification
- 在隔离副本里忠实复现 CI 全序列(
npm ci→ symlink Harness-only peers →
tsc -b→ build →npm test→ mutation → contract → py_compile),六步全绿。 - 关键一步是强制用 npm 真包
dsh-user-questions@0.1.2-rc.1(而非本地宿主树 symlink)
跑 typecheck —— 这才等价于 CI 的依赖形态,否则本地会「假绿」。
安装
npm install hermes-dsh-bridgenpm 不通时可下载本页附件:
npm install ./hermes-dsh-bridge-0.11.4.tgz附件 sha256: 4b3c352b6b570fe3521c9aebdabd2f615e482184649a2ffba02ae9a475fc7ac6
v0.11.3 — 照着 README 能真装上了
[0.11.3] — 照着 README 能真装上了(安装文档三处死路 + 第三个假警报)
升级收益一眼看:这一版修的全是新用户第一次安装就会撞上的问题。
照 README 的「30 秒跑通」抄命令,过去第 ① 步和第 ③ 步都会直接报
Cannot find module,第 ④ 步要跑的文件根本不在 npm 包里。
Fixed
- README 第 ① 步装错位置:
cd ~/.dsh/profiles/$PROFILE/node_modules && npm install
会在node_modules/里再套一层node_modules/,插件装到错误路径。
改成在 profile 目录装(cd ~/.dsh/profiles/$PROFILE)。 - README 第 ③ 步路径不成立:
node scripts/doctor.mjs在用户当时的 cwd
(.../node_modules)下解析不到。改成完整路径
node node_modules/hermes-dsh-bridge/scripts/doctor.mjs,并加了一句
「后面所有脚本都从插件目录跑」。 - README 让跑
python3 examples/hermes_dsh_mcp.py,但 npm 包里没有examples/:
files白名单漏了它。已加进白名单 —— 现在 npm 装完examples/就在包里。 doctor的失败指引过时:让人cd ~/.dsh/profiles/<p>/node_modules && npm install,
与上面同一处错误。已修。doctor的dsh settings 文件是第三个假警报:settings.yaml是 dsh
已移除的旧格式 —— 现代 dsh 会把它迁移进 profile 并改名.imported,宿主树里
没有任何写入方会生成它。所以「没有 settings.yaml」是正常状态,
这一项在干净机器上恒为失败。改成信息项(只有检测到旧文件时才提示一句迁移)。- README 示例输出与实际不符:
symlink 20(实测新装是 23)、9 项通过
(改完 settings 后是 10 项)、dsh 版本号、缺dump-config行 —— 全部按真实输出重写。
Changed
- npm 包
files白名单收窄:过去把整个scripts/塞进包里,含 9 个
mutation_check_*/e2e_r7/realchain_r9等开发期脚本,对新用户是噪音。
现在只保留运行/自检必需的四个(link-host-deps、doctor、contract_probe、
check-build-artifacts)。包内文件数 27 → 21。
Verification
- 照修好的 README 完整跑一遍干净安装:
npm install→ symlink 23 自动对齐 →
doctor报10 项通过, 0 项失败→examples/在包里可执行。全程零手工补救。 - 全量
npm test:700 项全绿(与 R11 一致,本轮未动逻辑)。
安装
npm install hermes-dsh-bridgenpm 不通时可下载本页附件:
npm install ./hermes-dsh-bridge-0.11.3.tgz附件 sha256: ef5423ed9dc741c4a7e71328ce6866118aa111ac67b0788f5db369b7baef80c5
(与 npm registry 上的 tarball 逐字节一致)
v0.9.0
修复 GitHub issue #1:session_list / session_search 在大会话库上慢到被判为挂死
性能(issue #1)
根因:排序键逐条调用 sessionPersistence.stat(),而 jsonl 后端的 stat() 是 O(项目目录数) 的(内部遍历整棵树),197 次 × ≈50 ms ≈ 9.3 s;再加上逐行读取整条事件流算 messageCount/token。所以 limit=1 就要 13 s —— 耗时大头在全量扫描,与返回条数无关。
本机实测(200 个会话):
| 调用 | 0.8.1 | 0.9.0 |
|---|---|---|
session_list{limit:1} |
≈ 13 s | ≈ 0.3 s |
session_list{limit:50} |
≈ 31 s | ≈ 0.3 s |
session_search |
≈ 7 s | ≈ 0.6 s |
做法:新增 listCorpus() 走官方 ctx.sessionQuery.listSessions()(只读 header,运行时探测 + 回退,旧版不炸);batchUpdatedAt() 整批解析落盘 mtime,绝不调 stat();session_list 新增 detail 参数(默认 brief,完全不读事件流)。
⚠️ 行为变更:session_list默认不再返回messageCount/inputTokens/outputTokens(改为tokensAvailable: false)。需要这些字段请传detail: "full"。
callback 部署级预设
新增 callbackPreset 配置,部署配一次,之后派发只传逐次变化的字段:
- insert:
- id: hermes-dsh-bridge
config:
callbackPreset:
url: "http://127.0.0.1:8644/webhooks/dsh-task-done"
headers: { "X-Gitlab-Token": "<secret>" }
events: []task_inbox(task="...", callback={"replyContext": {"replyChatId": "..."}}) # 一行不配置 = 与旧版完全一致。
dsh 0.1.7 适配(静默空跑修复,重要)
0.1.7 的 MessageSourceMap 只接受 user | model | tool | system-prompt(官方注释:"there is no shared catch-all plugin kind")。原代码用 source: { kind: 'plugin' } 构造 user message 会被判非法并静默丢弃——异常被 loop 的 kick() 里 catch (_error) {} 吞掉,表现为 agent 秒退、0 token、完全没有任何报错。已改为官方的 source: { kind: 'user' }。
自 fork / 二次开发者升级 dsh 0.1.7 后若遇到这个静默空跑,症状与排查步骤见 docs/TROUBLESHOOTING.md。
验证
- 全套单测 442 PASS / 0 FAIL
- 变异检查 14/14 全部抓红(0 漏),证明测试不是摆设
- 生产 8090 冒烟:25 个工具全部有响应,无空响应
- cwd 过滤回归红线 29 / 27 / 47 与基线一致
tsc -b+tsdown构建通过
上游限制(已记录,非本版缺陷)
SessionPersistence.locate()恒用SESSION_FORMAT_VERSION拼文件名,忽略实际落盘 generation —— 本机 198 个会话中 186 个路径不存在。本版已绕开。- 官方全文索引默认
openAt: "never",且受上述locate()bug 影响,session_search默认仍走内置扫描(可根据部署情况启用)。
v0.8.1: Task Callback Notifications & SSRF Protection
What's Changed
- Task Callback Notifications (P0/P1):
- Add optional
notifyUrlandreplyContextparameters totask_inbox. - Asynchronous HTTP Webhook notification with HMAC-SHA256 signature verification.
- SSRF protection, loopback/private IP blocking, and DNS pin caching.
- Backward compatible with zero changes when callbacks are not passed.
- Add optional
- Documentation & Sync:
- Update README and tool references to v0.8.1.
v0.7.0 — dsh 0.1.5 适配 + agent 体验深化 + R4 开箱即用
R4: 安装体验与文档 (开箱即用)
R4 目标:用户拿到包之后 README 5 分钟内跑通、每个报错都能自查、AI agent 装也能一次成功。
本轮不改 src/index.ts 核心逻辑(发现的缺陷只记录到 docs/KNOWN_ISSUES.md),
全部改动为文档 + 新增自检脚本。
Added
scripts/doctor.mjs— 零依赖、node直接跑的安装自检(只读,不改文件/不重启服务):
7 项检查(Node 版本 / dsh 可执行与版本 / profile 存在 / settings 文件 / patch 是否配了插件 /
端口监听 / MCP 握手 +tools/list),每项输出 ✓/✗ + 一条修复建议,最后汇总
「N 项通过,M 项失败」并以退出码0/1区分。支持--url/--host/--port/--profile/--token,
自动探测 profile,识别历史包名别名(dsh-harness-mcp-server),区分dump-config的
EACCES 权限问题与真实配置问题。docs/KNOWN_ISSUES.md— 逐字段核对代码时发现的缺陷清单(10 条,含 4 个真实缺陷:
fs_readoffset 越界静默、fs_list.total硬上限下偏小、rename_session错误串破坏统一句式、
set_policy冷会话错误串前缀不在统一家族;以及 3 条文档/代码口径不一致的⚠️ )。按约束本轮不修。
Changed
- README 重写为 quickstart 优先:首屏 = 一句话是什么 → 30 秒 quickstart(4 步命令跑通
echo)
→ 分场景进阶。新增前置依赖表(每项带检查命令)、三种安装路径分人群说明(npm / 源码构建 /
Hermes 一键配置片段)、常见错误排查表(把src/index.ts全部面向用户的错误文案逐条过了一遍,
22 行「原因 + 修复步骤」)、0.1.5 用户升级特别注意表、0.5.x/0.6.x → 0.7.0 升级指南与破坏性变更清单。
README 内嵌的 doctor 预期输出是本机真实运行原文。 docs/CONFIG.md与实际 zod schema /Config接口逐字段核对:16 个字段全部补齐
(此前缺approvalFileDir;approvalsBridge补上第四值file-push)。修正两处长期口径错误:approvalTimeoutMs真实默认值是 300000 ms(5 分钟),此前文档写 120s —— 以代码为准修正;provider默认值实为'deepseek-official'(非"必填无默认"),model默认空串 = 不覆盖。
配置示例全部改成<your-provider-id>/<your-model-id>占位符,可复制即用,不写死任何本机配置。
docs/TOOLS.md与 26 个工具逐个核对:每个工具补全参数表(名称/类型/必填/说明)、
返回字段示例与错误码表。修正:fs_stat不再宣称输出不存在的symlinkTarget;
session_search补充pageSize/offset/matched/scanned并标注total= 扫描数而非命中数;
session_log补head;fs_list补offset/limit与真实分页默认值;
approval_list补分页与pending/summary/hint;config_get标注实际没有嵌套timeouts键;
task_list补上被遗漏的offset/limit参数。敏感路径错误码更正为代码里的真实文案
path denied by policy (sensitive name)。
Verification
- 26 个工具的「schema 参数名 ↔ TOOLS.md 入参表」自动化比对:26/26 通过。
- CONFIG.md 表格 vs
Config接口 +runtimeConfigDefaults():16/16 字段覆盖(port/host
在apply()里直接读取、不在 defaults 中,已在文档显式标注)。 scripts/doctor.mjs在本机真实环境跑通(Node v22.22.3 / dsh 0.1.5-rc.2 / 真实 profile):
8 项通过 1 项失败(失败项为本机 profile 目录不可写导致的dump-configEACCES,运行态握手通过)。npm test:三套 mock 单测 178 断言全绿,未回归。
0.7.0
dsh runtime upgraded 0.1.2-rc.1 → 0.1.5-rc.2. This release fixes the
session_list crash reported in R1 and reworks all 26 tool descriptions for
agent callers. No breaking changes: tool names, parameter names and existing
response fields are unchanged — only new fields were added.
R3 (agent 使用体验深化) follows up on R2 by cutting the response payload and
unifying error strings — the part an agent actually reads on every call.
Fixed
session_listno-argument crash (session_list failed: Cannot read properties of undefined (reading 'length')). Root cause: dsh 0.1.5 changed thesessionPersistenceservice contract —list()now returnsSessionPersistenceSnapshot[]({ header, revision, sizeBytes }) instead of bareSessionHeader[], andinspect(id)was removed in favour ofopen(id, 'read')+handle.read(). The old code readsnapshot.id/snapshot.cwd(bothundefined), soSessionId(undefined)andlocate(undefined)fedundefinedinto path building and produced the exactreading 'length'TypeError. Reproduced against the real 0.1.5 backend plus the real v3 session file, then fixed via a new compatibility layer (unwrapPersistedEntry/persistedInspect/persistedRowMeta) that transparently supports both contracts (0.1.2 bare header +inspect, and 0.1.5 snapshot +open/read/stat). Persisted sessions now read their events again on 0.1.5 instead of silently falling back to an empty live store.- v3 session format support (
session.v3.jsonl.zstdin a directory without thesession-prefix, plusagentPresets/permission/presetevents).session_list,session_log,session_search,session_stats,preset_getand orphan reattach all verified against a real v3 session (messageCount, foldedtitle, token stats,sandboxModeandpresetall resolve correctly). - Per-row fault isolation in
session_list: a single unreadable or malformed persisted entry is now skipped and counted instead of failing the entire listing. - Token stats lost when
assistant/messagecarries noturn/step: the stats fold used tobreakbefore accumulatingusage, so such messages contributed 0 toinputTokens/outputTokens. Usage is now accumulated independently of step binding.
Added
session_listresponse gained a self-describingskippedcounter (number of sessions skipped by per-row fault isolation;0means all rows were read).total/count/truncated/sessionsare unchanged.session_loggained an optionalpresetparameter:"dialog"(user + assistant messages only),"tools"(tool calls + results only),"all"(no type filter). Explicittypesstill takes precedence; the default behaviour is unchanged. The response echoes the effectivepreset.- Self-describing
nextfield on results:agent_run(how to continue the session),task_inbox(how to fetch results),task_result(what to do per status),session_search(how to use the hits, or what to try when nothing matched),session_stats,session_log(when truncated),set_policy,approval_list,approval_respond,fs_read(when truncated),fs_write,policy_get,task_cancel. tests/probe_v3_real.mjs: real-environment probe that mounts the actual dsh 0.1.5dsh-session-persistence-jsonlbackend over the real session store, reproduces the originalreading 'length'crash on the old code path, then asserts the fix (20/20).
Changed
- All 26 tool descriptions rewritten for agent callers (when to use it / what it returns / what to do next) instead of implementation details.
agent_runandtask_inboxnow explicitly cross-reference each other andtask_result, so an agent can pick the right tool at a glance. - Every error message now carries a next step, e.g.
task not found: <id>→… (已过期或从未存在; 用 task_list 查看当前队列 …);session not found: <id>→… (用 session_list 查看当前会话列表 …);no active agent session yet→ points atagent_run/ an explicitsessionId. - Simplified default
cwd:agent_run/task_inbox(andpolicy_get) now default toworkspaceRoots[0]when configured, instead of the meaningless-for-remote-agentsprocess.cwd(). The effective default is stated in the parameter description, andprocess.cwd()remains the fallback when noworkspaceRootsis configured. status_getandconfig_getdescriptions now explain when to reach for which one.- Version 0.6.0 → 0.7.0;
tests/unit_mock_p3.mjsextended with a 0.1.5-contract instance (v3 snapshot shape,open/readhandle close, malformed-entryskippedcounting, per-row read failure, and the A/B UX contract); R3 adds a further 62 assertions for A/B/C — suite now 178 assertions, up from 79.
Compatibility notes (0.1.5 audit, no refactor required)
ctx.agent(singular) removal: 0 direct accesses in the plugin — it only usesctx.agentsandctx.agentPresets, both still present in 0.1.5.- P3 panel/sidebar slot change (
'conversation'→'main'): not applicable — this plugin registers no web panel or UI slot. The only web-channel use is the optional approval bridge'sapiProxysubscription, which already degrades safely tobuiltin/file-push;dsh-host-apiproxyis no longer shipped in 0.1.5 andctx.get('apiProxy', false)returnsundefinedthere instead of throwing.
R3 — A: 返回结构精简 (lower per-call context cost)
- List responses are paginated (default page 20, max 100) with a uniform envelope:
total(post-filter count) /count(this page) /offset/limit/truncated/next(the exact call to fetch the next page). Addedoffsettofs_list,session_list,session_search(aspageSizealongside the existing scan-limit),task_listandapproval_list.task_listno longer hard-truncates at 100 rows without recourse. session_logdefaults to ≤50 events: whentotalMatchedexceeds the cap it returns head + tail (newheadparameter, default 5;head: 0= newest only), setstruncated: trueand addsomitted/order/nextwith the three ways to get more (largertailup to 500,head=0, or narrowerpreset/types).- Timestamps are human-readable: every
*At/time/mtimeoutput is now ISO8601 in the local timezone with an explicit±HH:MMoffset, and the original epoch is preserved alongside as*_at_epoch/time_epoch(same field name, so existing readers of the ISO field keep working). Covered:echo.at,status_get.startedAt,fs_read.modifiedAt,fs_list[].mtime,fs_stat.mtime,session_list[].createdAt/updatedAt,session_log.header.createdAtandevents[].time,task_inbox.createdAt,task_result/task_list.createdAt/finishedAt,approval_list[].requestedAt. - Byte counts and durations are formatted (
9.4KB,8.8s,1.5m) with the raw value kept for sorting:fs_stat.size+size_bytes,fs_list[].size+size_bytes,fs_write.bytes+bytes_raw,status_get.uptime(rawuptimeSec),config_get.taskTtl/approvalTimeout(raw*Ms),session_stats.llmTimeHuman/toolTimeHuman/ttftHuman,session_list[].llmTimeHuman,task_list/task_result[].waited,approval_list[].waited. - Redundant / internal fields dropped:
config_getno longer echoes the maskedauthToken: '***'placeholder (onlyauthTokenSet: booleanremains).
R3 — B: agent 工作流指引 (agent_run / task_inbox / 审批流)
- File-landing hint (
B4): when anagent_run/task_resultpayload mentions writing a file ("已写入 / created file / wrote to …") but contains no absolute path, the response addslanding: { hint, likelyDir, mentionedWrite, pathsInResult }pointing at the run's sandboxcwdas the common landing spot, with the `fs_lis...
v0.6.0 — dsh 0.1.2-rc.1 适配
[0.6.0] - 2026-09-04
Added
approvalsBridge: 'file-push'mode: pending approvals are mirrored to a file (default~/.dsh/approvals/,approvalFileDirconfig) so an out-of-band MCP client (e.g. Hermes) can detect and answer them whileagent_runis blocked; first-responder-wins semantics unchanged. Approval timeout default raised 120000 → 300000 to match.
Changed
- dsh 0.1.2-rc.1 adaptation (previous src failed to start under dsh 0.1.2):
isTokenDelta(removed from@deepseek-ai/dsh-llm/messagein 0.1.2) replaced with an inline check preserving the old semantics:text-delta/reasoning-delta→ non-emptytext;tool-call-delta→ non-emptyargumentsDeltaorname !== undefined.resolveSessionPreset(removed from@deepseek-ai/dsh-agent-presetsin 0.1.2) replaced with a localpresetFromEventsscanningagent-preset/selectedevents newest-first for the last non-emptydata.preset, falling back toheader.agentPreset.apiProxyremoved from theinjectarray (the service no longer exists in dsh 0.1.2 headless compositions; injecting it crashed startup);apiProxyOfnow reads it leniently viactx.get('apiProxy', false)(returnsundefined, never throws) — thewebbridge auto-degrades tobuiltinthere as before.@deepseek-ai/*dsh dependencies loosened from pinned0.1.0-rc.6to^0.1.2-rc.1;peerDependencies.@deepseek-ai/cordis^4.0.1 → ^4.0.2.
- Version 0.5.0 → 0.6.0;
tests/unit_mock_p3.mjsmock ctx now servesapiProxythroughctx.get('apiProxy', false)(matching the cordis hard rule that non-injected services must not be read as properties) and asserts the new defaults (79 assertions).
v0.5.0 — 权限三档与审批桥
[0.5.0] - 2026-08-26
Added
- Sandbox tiers (权限三档):
agent_run/task_inboxacceptsandbox: read-only|workspace-write|danger-full-access— a per-task override that seeds a session-logsandbox/modeevent on newly created/resumed sessions (same write path as dshsetSandboxMode; effective on the next confined call, survives restarts via replay). Pool hygiene follows the preset precedent: requests whose tier differs from the pooled session's fixed tier skip the pool, and non-default-tier dedicated sessions never enter it — the three tiers never pollute each other on one cwd. NewdefaultSandboxconfig (defaultworkspace-write). set_policy(sessionId, mode)tool: switch an existing live session's tier (cold/persisted sessions error with a resume hint).policy_get(sessionId?)tool:{ sessionId, sandboxMode, source: "override"|"default", workspaceRoot, approvalPolicy }(folds lastsandbox/mode+approval/policyevents; no-arg returns deployment defaults).- Approval bridge (审批桥): new
approval_list()andapproval_respond(approvalId, sessionId, outcome)tools. Bridge modeweb(default) subscribesctx.apiProxy.events.mux(), tracks pending approvals in memory, syncs onapproval/resolvedframes (first responder wins across Web UI/Hermes; the loser getsreceipt=not-pending) and answers throughapiProxy.respond({type:'client-response', …}). Automatic degradation to a builtin'approval/request'answerer (asked/decided scan) when apiProxy is absent orapprovalsBridge: 'builtin'is set;'off'disables bridging.approvalTimeoutMs(default 120000) settles timed-out approvals cancelled (builtin) / rejected (web) — never auto-allows.task_inboxis the primary async path;agent_runblocks while an approval is pending. - Status exposure:
status_getnow reportssandboxPolicy { defaultMode, bridge, pendingApprovals };config_getreportsdefaultSandbox/approvalsBridge/approvalTimeoutMs;session_listrows carrysandboxModewhen the session has a tier record;task_list/task_result/agent_runecho the requested tier.
Changed
- Version 0.4.0 → 0.5.0; docs (README/TOOLS/CONFIG/SECURITY) updated for the 25-tool surface.
- CI runs
tests/unit_mock_p3.mjs(77 assertions: tier pass-through & pool isolation, set_policy live/cold, approval web/builtin flows incl. not-pending races and timeout cancellation, bridge off/explicit-builtin, status/config exposure).