Skip to content

Releases

v0.11.4 — CI 转绿

Choose a tag to compare

@Emilia-awa Emilia-awa released this 06 Oct 02:07

[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-bridge

npm 不通时可下载本页附件:

npm install ./hermes-dsh-bridge-0.11.4.tgz

附件 sha256: 4b3c352b6b570fe3521c9aebdabd2f615e482184649a2ffba02ae9a475fc7ac6

v0.11.3 — 照着 README 能真装上了

Choose a tag to compare

@Emilia-awa Emilia-awa released this 05 Oct 13:34

[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-bridge

npm 不通时可下载本页附件:

npm install ./hermes-dsh-bridge-0.11.3.tgz

附件 sha256: ef5423ed9dc741c4a7e71328ce6866118aa111ac67b0788f5db369b7baef80c5
(与 npm registry 上的 tarball 逐字节一致)

v0.9.0

Choose a tag to compare

@Emilia-awa Emilia-awa released this 26 Sep 07:39

修复 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

Choose a tag to compare

@Emilia-awa Emilia-awa released this 19 Sep 14:34

What's Changed

  • Task Callback Notifications (P0/P1):
    • Add optional notifyUrl and replyContext parameters to task_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.
  • Documentation & Sync:
    • Update README and tool references to v0.8.1.

v0.7.0 — dsh 0.1.5 适配 + agent 体验深化 + R4 开箱即用

Choose a tag to compare

@Emilia-awa Emilia-awa released this 12 Sep 17:37

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_read offset 越界静默、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-config EACCES,运行态握手通过)。
  • 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_list no-argument crash (session_list failed: Cannot read properties of undefined (reading 'length')). Root cause: dsh 0.1.5 changed the sessionPersistence service contract — list() now returns SessionPersistenceSnapshot[] ({ header, revision, sizeBytes }) instead of bare SessionHeader[], and inspect(id) was removed in favour of open(id, 'read') + handle.read(). The old code read snapshot.id / snapshot.cwd (both undefined), so SessionId(undefined) and locate(undefined) fed undefined into path building and produced the exact reading '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.zstd in a directory without the session- prefix, plus agentPresets/permission/preset events). session_list, session_log, session_search, session_stats, preset_get and orphan reattach all verified against a real v3 session (messageCount, folded title, token stats, sandboxMode and preset all 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/message carries no turn/step: the stats fold used to break before accumulating usage, so such messages contributed 0 to inputTokens/outputTokens. Usage is now accumulated independently of step binding.

Added

  • session_list response gained a self-describing skipped counter (number of sessions skipped by per-row fault isolation; 0 means all rows were read). total / count / truncated / sessions are unchanged.
  • session_log gained an optional preset parameter: "dialog" (user + assistant messages only), "tools" (tool calls + results only), "all" (no type filter). Explicit types still takes precedence; the default behaviour is unchanged. The response echoes the effective preset.
  • Self-describing next field 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.5 dsh-session-persistence-jsonl backend over the real session store, reproduces the original reading '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_run and task_inbox now explicitly cross-reference each other and task_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 at agent_run / an explicit sessionId.
  • Simplified default cwd: agent_run / task_inbox (and policy_get) now default to workspaceRoots[0] when configured, instead of the meaningless-for-remote-agents process.cwd(). The effective default is stated in the parameter description, and process.cwd() remains the fallback when no workspaceRoots is configured.
  • status_get and config_get descriptions now explain when to reach for which one.
  • Version 0.6.0 → 0.7.0; tests/unit_mock_p3.mjs extended with a 0.1.5-contract instance (v3 snapshot shape, open/read handle close, malformed-entry skipped counting, 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 uses ctx.agents and ctx.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's apiProxy subscription, which already degrades safely to builtin/file-push; dsh-host-apiproxy is no longer shipped in 0.1.5 and ctx.get('apiProxy', false) returns undefined there 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). Added offset to fs_list, session_list, session_search (as pageSize alongside the existing scan-limit), task_list and approval_list. task_list no longer hard-truncates at 100 rows without recourse.
  • session_log defaults to ≤50 events: when totalMatched exceeds the cap it returns head + tail (new head parameter, default 5; head: 0 = newest only), sets truncated: true and adds omitted / order / next with the three ways to get more (larger tail up to 500, head=0, or narrower preset/types).
  • Timestamps are human-readable: every *At / time / mtime output is now ISO8601 in the local timezone with an explicit ±HH:MM offset, 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.createdAt and events[].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 (raw uptimeSec), 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_get no longer echoes the masked authToken: '***' placeholder (only authTokenSet: boolean remains).

R3 — B: agent 工作流指引 (agent_run / task_inbox / 审批流)

  • File-landing hint (B4): when an agent_run / task_result payload mentions writing a file ("已写入 / created file / wrote to …") but contains no absolute path, the response adds landing: { hint, likelyDir, mentionedWrite, pathsInResult } pointing at the run's sandbox cwd as the common landing spot, with the `fs_lis...
Read more

v0.6.0 — dsh 0.1.2-rc.1 适配

Choose a tag to compare

@Emilia-awa Emilia-awa released this 12 Sep 17:37

[0.6.0] - 2026-09-04

Added

  • approvalsBridge: 'file-push' mode: pending approvals are mirrored to a file (default ~/.dsh/approvals/, approvalFileDir config) so an out-of-band MCP client (e.g. Hermes) can detect and answer them while agent_run is 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/message in 0.1.2) replaced with an inline check preserving the old semantics: text-delta/reasoning-delta → non-empty text; tool-call-delta → non-empty argumentsDelta or name !== undefined.
    • resolveSessionPreset (removed from @deepseek-ai/dsh-agent-presets in 0.1.2) replaced with a local presetFromEvents scanning agent-preset/selected events newest-first for the last non-empty data.preset, falling back to header.agentPreset.
    • apiProxy removed from the inject array (the service no longer exists in dsh 0.1.2 headless compositions; injecting it crashed startup); apiProxyOf now reads it leniently via ctx.get('apiProxy', false) (returns undefined, never throws) — the web bridge auto-degrades to builtin there as before.
    • @deepseek-ai/* dsh dependencies loosened from pinned 0.1.0-rc.6 to ^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.mjs mock ctx now serves apiProxy through ctx.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 — 权限三档与审批桥

Choose a tag to compare

@Emilia-awa Emilia-awa released this 12 Sep 17:37

[0.5.0] - 2026-08-26

Added

  • Sandbox tiers (权限三档): agent_run/task_inbox accept sandbox: read-only|workspace-write|danger-full-access — a per-task override that seeds a session-log sandbox/mode event on newly created/resumed sessions (same write path as dsh setSandboxMode; 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. New defaultSandbox config (default workspace-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 last sandbox/mode + approval/policy events; no-arg returns deployment defaults).
  • Approval bridge (审批桥): new approval_list() and approval_respond(approvalId, sessionId, outcome) tools. Bridge mode web (default) subscribes ctx.apiProxy.events.mux(), tracks pending approvals in memory, syncs on approval/resolved frames (first responder wins across Web UI/Hermes; the loser gets receipt=not-pending) and answers through apiProxy.respond({type:'client-response', …}). Automatic degradation to a builtin 'approval/request' answerer (asked/decided scan) when apiProxy is absent or approvalsBridge: 'builtin' is set; 'off' disables bridging. approvalTimeoutMs (default 120000) settles timed-out approvals cancelled (builtin) / rejected (web) — never auto-allows. task_inbox is the primary async path; agent_run blocks while an approval is pending.
  • Status exposure: status_get now reports sandboxPolicy { defaultMode, bridge, pendingApprovals }; config_get reports defaultSandbox/approvalsBridge/approvalTimeoutMs; session_list rows carry sandboxMode when the session has a tier record; task_list/task_result/agent_run echo 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).