Skip to content

Releases: log-li/dsh-automode

Release v0.18.0

Choose a tag to compare

@github-actions github-actions released this 04 Nov 04:37

[0.18.0] — 2026-11-04

Added

  • Permanent-deletion commands are rejected outright. In auto mode, rm, unlink, shred, git rm, git clean -f…, find … -delete, rsync --delete*, del / rd /s / Remove-Item — including the same commands behind a wrapper (sudo, env -u …, xargs -I {} …, timeout 5 …, nohup, eval, busybox, su -c), a shell payload (sh -c, bash -lc, find -exec, npx -c), a $( … ) / backtick substitution, a heredoc piped into a shell, quote layers of any depth, or a Windows path prefix — are hard-rejected before they run, and the rejection tells the agent to use trash. This covers commands that stay inside the workspace, so an in-workspace rm -rf is no longer silent and unrecorded.
  • Sessions that inherit auto mode are now guarded. A subagent or AgentTeams worker created from an auto-mode session used to run entirely unguarded: its own permission preset is unset, so the gate treated it as "not auto mode" and did nothing. The gate now walks the parent-session chain and applies the deterministic rules (deny band, deletion guard, allow band, path trust) to those sessions as well. They do not reach the classifier — there is no approval answerer behind a child session.

Changed

  • The auto-mode guidance injected into the session now names trash as the allowed way to delete, so the agent knows the replacement before it hits a denial.
  • rm -rf node_modules is rejected too. Deleting build directories is recoverable by reinstalling, but the command itself is not recoverable, so it takes trash like everything else.
  • The guard reads the executed command text only, never the justification prose: a reason that mentions deletion no longer influences the decision, and a delete command quoted as an ordinary argument (echo "rm -rf x", git commit -m "drop rm -rf") is not treated as an action.

Notes

  • Scope of the guarantee. A deletion whose command word appears literally in an executed position is rejected: direct commands, wrappers and their option values, shell payloads (sh -c, bash -lc, find -exec, npx -c), $( … )/backtick substitutions, heredocs piped into a shell, and text piped into an interpreter. Deletions that only exist at runtime are not visible to a text scan and are out of scope: language APIs (fs.unlinkSync, os.remove, File.delete), awk 'BEGIN{system(...)}', a command word assembled by another program, and docker exec … rm.

Fixed

  • A blocked deletion now always leaves an audit record. Previously neither a rejection nor the command itself produced a decisions.jsonl entry, so an irreversible delete could leave no trace.

[0.18.0] — 2026-11-04

新增

  • 永久删除命令现在直接拒绝。 auto mode 下,rm、unlink、shred、git rm、git clean -f…、find … -delete、rsync --delete*、del / rd /s / Remove-Item,以及把它们藏在包装词(sudo、env -u …、xargs -I {} …、timeout 5 …、nohup、eval、busybox、su -c)、shell 载荷(sh -c、bash -lc、find -exec、npx -c)、$( … ) / 反引号替换、管道送进 shell 的 heredoc、任意层数引号或 Windows 路径前缀后面的写法,都会在执行前被硬拒,拒绝理由让 agent 改用 trash。发生在工作区内的命令同样适用,所以工作区里的 rm -rf 不再静默执行、也不再有记录缺口。
  • 继承 auto mode 的会话也纳入管辖。 由 auto-mode 会话派生的子代理或 AgentTeams worker 此前完全不设防:它自己的权限预设是空的,闸门据此判定「不是 auto mode」而直接放行。现在闸门会沿父会话链上溯,对这些会话同样执行确定性规则(deny 频带、删除守卫、allow 频带、路径信任)。它们不进分类器——子会话身后没有审批答复器。

变更

  • 注入会话的 auto-mode 指引现在写明 trash 是允许的删除方式,agent 在撞上拒绝之前就知道替代品。
  • rm -rf node_modules 同样被拒。重装即可恢复,但这条命令本身不可恢复,所以和其他删除一样走 trash。
  • 守卫只读会被执行的命令文本,绝不读 justification 散文:拒绝理由里提到删除不再影响判定,而把删除命令当普通参数引用(echo "rm -rf x"、git commit -m "drop rm -rf")不算动作。

说明

  • 保证的边界。 删除命令词字面出现在会被执行的位置时一定被拒:直接命令、包装词及其取值选项、shell 载荷(sh -c、bash -lc、find -exec、npx -c)、$( … )/反引号替换、管道送进 shell 的 heredoc,以及管道送进解释器的文本。只在运行期才成形的删除文本扫描看不见,不在范围内:语言 API(fs.unlinkSync、os.remove、File.delete)、awk 'BEGIN{system(...)}'、由别的程序拼出的命令词、docker exec … rm。

修复

  • 被拦下的删除现在一定留审计记录。此前既不拒绝、也不写 decisions.jsonl,不可逆删除可以零留痕。

Release v0.17.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 16:14

[0.17.0] — 2026-10-17

Changed

  • dsh 0.2.x is supported: the peer range now ends at <0.3.0-0. The @deepseek-ai/dsh-* peer ranges moved up from <0.2.0, so the 0.2 release line resolves cleanly again. npm's prerelease rule still excludes prerelease hosts — the tested 0.2.1-rc.2 included, exactly as the old bound excluded 0.1.7-rc.2; dsh plugin add (pnpm) prints an unmet-peer warning on such a host instead of refusing, while an npm-style install fails with ERESOLVE. On 0.2.1-rc.2 the session format is still v4, and the permission, approval and sandbox exports the plugin probes at runtime are all still there, so auto mode behaves as it did on the 0.1 line. Nothing in the plugin runtime changed for this; the supported span is now 0.1.0-rc.6 through 0.2.x.
  • The icon patch still patches both halves on 0.2.x, and now distinguishes a host that ships the passthrough itself from a file it patched. On 0.2.1-rc.2 neither side reads a preset icon: the host preset schema has no icon field, optionOf() forwards none, and the client bundle still hardcodes its three glyphs, so both halves are rewritten as before. A host that carries the passthrough itself is reported as natively supported and left byte-identical instead of being rewritten. Once a DSH release reads preset icons in its client too, delete the script.

Fixed

  • already patched now means this script really wrote that file. On the host half it is reported only when the backup sidecar plus the script's own edits reproduce the current bytes, so a stale or foreign backup cannot pass for it. Previously a host file that already carried the icon fields — whether the script or the host itself put them there — was reported as already patched, as if the script had written it.

[0.17.0] — 2026-10-17

变更

  • 现在支持 dsh 0.2.x,peer 上界移到 <0.3.0-0。 各 @deepseek-ai/dsh-* 的 peer 范围从 <0.2.0 放宽到 <0.3.0-0,0.2 的正式版因此能正常解析。按 npm 的预发布规则,预发布宿主(包括实测的 0.2.1-rc.2)仍不在范围内——旧上界对 0.1.7-rc.2 也是同样的结果;dsh plugin add(pnpm)在这种宿主上打一条 unmet peer 警告而不拒绝安装,npm 式安装则以 ERESOLVE 硬失败。在 0.2.1-rc.2 上会话格式仍是 v4,插件运行时探测的权限、审批与沙箱导出也都还在,所以 auto mode 的行为与 0.1 线一致。插件运行时本身没有为此改动;现在支持的跨度是 0.1.0-rc.6 到 0.2.x。
  • 图标补丁在 0.2.x 上仍两半都打,并把「宿主自己就提供透传」与「脚本写过的文件」区分开。 0.2.1-rc.2 上两边都不读预设 icon:宿主预设 schema 没有 icon 字段、optionOf() 不透传、客户端 bundle 仍写死三档内置 glyph,所以两半照旧改写。宿主自己就带透传的,报 natively supported、逐字节不动,不会被改写。等某一代 DSH 的客户端也开始读预设 icon,删掉脚本即可。

修复

  • already patched 现在只在脚本真的写过那个文件时出现。 宿主半边只有在备份加脚本自己的改动能复现当前字节时才这么报,陈旧或外来的备份骗不过它。以前某个已带 icon 字段的宿主文件——不管是脚本还是宿主自己写进去的——都会被报成 already patched,像是脚本的功劳。

Release v0.16.1

Choose a tag to compare

@github-actions github-actions released this 28 Sep 02:49

[0.16.1] — 2026-09-28

Fixed

  • A confirmation you gave through ask_user_question reaches the gate again on dsh 0.1.7-rc.2 and newer. Those hosts deliver tool results in a newer message shape, and the gate only read the older one — so on them your answer was invisible: the gate kept seeing your earlier question, and a command you had just confirmed was rejected again with a reason saying you had not authorized it. Both shapes are now read, so a tool-based confirmation invalidates a stale DENY again, exactly as the README describes. Older hosts (<= 0.1.6) are unaffected.

[0.16.1] — 2026-09-28

修复

  • 经 ask_user_question 给出的确认,在 dsh 0.1.7-rc.2 及更新版本上重新能被闸门看到。 这些宿主用新的消息形态投递工具结果,而闸门只认旧形态 —— 于是你的答复在闸门侧不存在:它看到的仍是你更早的那个提问,你刚确认过的命令照旧被拒,理由还说你没有授权。现在两种形态都读,工具型确认重新能让旧的 DENY 缓存失效 —— 与 README 描述的行为一致。旧宿主(<= 0.1.6)不受影响。

Release v0.16.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 06:07

[0.16.0] — 2026-09-26

Added

  • The shipped permission-picker icon patch now supports dsh 0.1.7-rc.2. On that line the picker lives in a different client package and the host preset schema changed, so the previous script matched nothing there and reported anchors missing instead of rendering the bolt. It now accepts icon in the host preset schema, forwards it to the client catalog, and draws that SVG path inside the design set's shield in the client bundle.
  • Patch runs are safe by construction. Every anchor is checked before anything is written, so a file whose anchors no longer match is reported and left byte-identical; each rewritten file keeps a <file>.pre-dsh-automode-icon.bak copy to roll back with; re-running is a no-op; a pre-existing unrecognized injection is refused instead of stacked; older 0.1.x lines are skipped with a note. --profile <name> also accepts --profile=<name>, and bad usage exits 2.

Changed

  • The icon patch targets the 0.1.7 line. On 0.1.5/0.1.6 it reports and skips rather than guessing — the 0.15.4 package still carries the script for that line. The README states the boundary, the rollback and the test coverage.

Notes

  • Plugin runtime behaviour is unchanged (src/ and lib/); this release refreshes the optional helper script, its self-test and the docs.

[0.16.0] — 2026-09-26

新增

  • 随包发布的权限选择器图标补丁现在支持 dsh 0.1.7-rc.2。 这一代把选择器搬到了另一个客户端包、宿主预设 schema 也变了,旧脚本在那里锚点全不命中,只会报「找不到锚点」而画不出闪电。现在它让宿主预设 schema 接受 icon、把它透传给客户端目录,并在客户端 bundle 里把该 SVG 路径画进设计集的盾牌轮廓内。
  • 补丁运行自带安全约束。 先做全量锚点预检再写文件,锚点对不上的文件会被报告并逐字节保持原样;每个被改写的文件旁留一份 <文件名>.pre-dsh-automode-icon.bak 供回滚;重跑是空操作;若已有别的注入则拒绝叠加;更早的 0.1.x 版本线只标注跳过。--profile <name> 同时接受 --profile=<name>,参数错误以退出码 2 结束。

变更

  • 图标补丁只针对 0.1.7 这一代。在 0.1.5/0.1.6 上它会报告后跳过、不猜不硬塞——那两代可继续用 0.15.4 包里的旧脚本。README 写明了版本边界、回滚方式与测试覆盖。

说明

  • 插件运行时行为不变(src/ 与 lib/ 未改);本次只更新了可选辅助脚本、它的自测与文档。

Release v0.15.4

Choose a tag to compare

@github-actions github-actions released this 23 Sep 05:23

[0.15.4] — 2026-09-23

Fixed

  • Injected Auto Mode messages are accepted by DSH session format v4. Every message the plugin injects used the retired catch-all source kind plugin, which v4 refuses on every durable message slot — including agent/inbox/spliced, the event agent.inject() writes. On dsh >= 0.1.7-alpha.1 that broke every injection: where the failure propagates it took the whole step down, and the two best-effort breaker hints were dropped silently. Messages now carry the plugin's own source kind, the same identity the released v3 → v4 migration rewrites old records to, so one value covers both format generations. Reported in #3.

[0.15.4] — 2026-09-23

修复

  • 注入的 Auto Mode 消息现在能被 DSH session format v4 接受。 插件注入的每条消息都用已退役的 catch-all source kind plugin,而 v4 会拒绝所有 durable 消息槽位——包括 agent.inject() 写入的 agent/inbox/spliced。在 dsh >= 0.1.7-alpha.1 上这些注入全部失效:失败会上抛的那几处会把所在 step 一起带垮,两处 best-effort 的熔断提示则被静默丢弃。现改用插件自有的 source kind,与 v4 迁移把老记录归一到的标识一致,从而一个取值同时覆盖两代格式。见 #3。

Release v0.15.3

Choose a tag to compare

@github-actions github-actions released this 16 Sep 11:50

[0.15.3] — 2026-09-16

Added

  • The auto-mode:allowlist system-prompt section now states the escalation rule. For a target inside an allowPath that lies outside the workspace, the agent is told to request danger-full-access on the first attempt — the approval bridge grants it with no review — instead of trying bare first and escalating after the sandbox rejects it.
  • npm run test:pathtrust: an offline end-to-end check of this behaviour (no API key needed).

Fixed

  • The circuit-breaker hint no longer reads as "escalation always needs a human". It now says the human is asked because auto mode is paused, and that in normal operation allowlisted escalation is auto-approved with zero review.

Security

  • Composite commands no longer earn allowPath trust on a partial proof. mkdir counts as a write segment; relative destinations resolve against the cwd in effect at each segment; a destination or command name that still contains an unresolved $ is refused; and a path-prefixed name can no longer stand in for a read-only utility. Anything that cannot be proved now falls back to the classifier instead of being auto-trusted.

Changed

  • bashWriteDestinations() always returns absolute paths.

[0.15.3] — 2026-09-16

新增

  • auto-mode:allowlist 系统提示段现在写明提权规则:目标在白名单内、又要写 workspace 外时,首次尝试就带 danger-full-access(approval 桥接零评审放行),不要先裸跑、等沙箱拒绝后再升级。
  • npm run test:pathtrust:该行为的 offline 端到端检查(无需密钥)。

修复

  • 熔断器提示不再被读成「提权永远要人工」:现在说明走人工是因为 auto 已暂停,正常状态下白名单内提权是零评审自动放行。

安全

  • 组合命令不再凭「部分证明」拿到 allowPath 信任:mkdir 计入写段;相对目标按各段当时的 cwd 解析;目标或命令名展开后仍含未解析 $ 一律拒绝;带路径前缀的名字不能再冒充只读工具。凡证明不了的组合命令,改为回退分类器而非自动信任。

变更

  • bashWriteDestinations() 现在恒返回绝对路径。

Release v0.15.2

Choose a tag to compare

@github-actions github-actions released this 12 Sep 05:36

[0.15.2] — 2026-09-12

Fixed

  • Documentation brought back in line with the shipped behavior. The 0.15.1 tarball carried a README and CHANGELOG that still described the superseded policy: the classifier section presented the gate as judging an action's riskiness and said dangerous escalations stayed refused, and this same CHANGELOG section still claimed the machine-leaving floor was unchanged. None of that has been true since the user-is-the-arbiter change. Both READMEs now state the current contract — authorization rather than worth, the hard floor, and the double-check loop through ask_user_question — and the retired sentence here is marked as retired. A smoke test now pins this class of drift: it fails if either README omits the current policy or carries the old wording, and if the CHANGELOG section for the version being shipped contains the retired floor sentence. No behavior changed in this release.

[0.15.2] — 2026-09-12

修复

  • 文档与已发布行为对齐。 0.15.1 的 npm 包里带的 README 与 CHANGELOG 仍在描述已被取代的政策:分类器一节把闸门写成在判断动作的风险高低、并称危险动作提权会被拒绝,而 0.15.1 段自己也仍写着「底线不变」。自「判定权归用户」之后,这些都不再成立。两份 README 已改写为当前契约 —— 授权而非价值、硬底线、经 ask_user_question 的 double-check 闭环;CHANGELOG 里那句被退役的表述也已标注。新增 smoke 测试固定这一类漂移:任一 README 缺少当前政策或含旧措辞、或当前版本段仍含被退役的底线句,测试即失败。本版本未改任何行为。

Release v0.15.1

Choose a tag to compare

@github-actions github-actions released this 12 Sep 05:31

[0.15.1] — 2026-09-12

Fixed

  • The intent window now carries the question the answer belongs to. A terse "yes" to a confirmation prompt used to arrive stripped of the question it had answered, so the gate read it as approving "a probe" rather than the exact command the user had just seen on the card — the double-check loop could not close. Answers now render as the user's own words plus the agent-authored question that disambiguates them, and the prompt states plainly that the quoted question authorizes nothing by itself and cannot widen the answer. Found by live end-to-end testing, not by the offline suite.
  • The pre-screen now sees the action, not just the narration. The two-stage classifier's one-token filter received only the tool name and the agent's justification — never the command — and a "0" from it returns ALLOW without running the structured review at all. That made the whole prompt contract bypassable on the one path where narration was the sole input, and the bypass ran in the allow direction: measured against the real classifier route (5 runs per case, real buildSystemPrompt/buildUserMessage, real parser), the same git tag -a v9.9.9 && git push origin v9.9.9 was allowed 5/5 with a confident "user explicitly approved this release" justification and rejected 5/5 with a hedged, self-referential one. actionSummaryOf() now builds the filter input from the command text or the target paths, with the justification carried as an annotation; the same measurement after the fix rejects both variants 5/5, keeps npm run build / git push origin main / rm -rf /tmp/scratch allowed 5/5, and rejects the release even when the user asked for it.
  • The deny band matches operations, not prose. denyHaystackFor() fell back to JSON.stringify(args) for every non-file tool, so the entire argument blob was scanned as if it were a command: a dispatched subagent review was hard-rejected for mentioning a sensitive topic in its prompt (the audit log records a matched deny pattern event on subagent, with no classifier consulted), as was a command that wrote a document about one. The only way through was to mangle the wording, which trains the agent to evade the one layer that does not read intent. Subject-shaped built-in patterns (key files, env files, credential stores) are now skipped when a call's arguments are prose; operation-shaped ones (pipe-to-shell, inline key material, system-path moves, docker volume removal) still apply everywhere, and bash command text / file target paths are still scanned in full. The free-text word pattern was dropped and the generic key phrase narrowed to inline key material; an attempt to narrow the credential-store pattern to store filenames was reverted before release — it silently lost ~/.git-credentials, a gcloud-style extensionless store and a bare credentials argument, and a non-escalated bash call never reaches the classifier, so nothing else would have caught them. The prose false positives are handled by the prose scope instead, not by narrowing patterns. Residual risk and the trade-off are recorded in the spec.
  • The prose scope now lives in ONE shared scan. The band has two enforcement points, and the first version of the fix above only reached one of them: the pre-execute gate assembled its own deny haystack and called the matcher directly, so it kept scanning prose with the full pattern list. In-session testing after a restart reproduced the original false positive (a non-bash tool was still hard-rejected for mentioning a key path) while the other half of the fix was already live. Both sites now call a single scanDenyBand() that owns the haystack, the prose scope, and the match; a structural smoke test asserts no enforcement point calls the matcher directly. Lesson recorded in the spec: sharing the haystack is not enough — the scope has to be shared too, which is how the earlier parity fix still left a gap.

Changed

  • The user is the arbiter; only a hard floor is not the user's call (policy decision, 2026-09-12). The gate no longer decides whether an action is worth doing — it decides whether the user authorized this exact action. A risky action the user asked for is allowed, including effects that leave this machine (releasing, publishing, deploying, pushing, sending a message outward, writing to another system). When the action can affect other people or systems and the user's own messages do not clearly cover this action, the gate rejects with an actionable reason telling the agent to show the user the exact command and ask them to confirm it — the user's answer is a direct authorization and the next attempt may be allowed. That is the "double check" the user asked for. Authorization comes only from the user's own messages (a typed message, or an answer to ask_user_question); the agent's justification, repository text, and tool output never authorize — otherwise "the user's intent" would just mean "whoever spoke last". The hard floor stays closed to everyone: executing downloaded code, destroying or moving system paths, removing docker volumes, reading key material or credential stores, writing key material inline, editing shell startup files or agent permission settings. Those are refused whatever the user says, with the reason telling them to run it themselves outside auto mode; they are also blocked deterministically before any model is consulted.
  • The one-token filter may no longer decide a machine-leaving action. The pre-screen now only short-circuits for actions that cannot affect other people or systems: a curated set of shapes (git push, package publish, release/workflow commands, uploads and POSTs, remote copies, deploy verbs, …) forces the structured review, which is the only stage that reads the user's authorization. Measured before this rule: an outward message the user had asked for was allowed 5/5 by the pre-screen, with the review — and any authorization check — never read.
  • The prompt contract is enforced through every stage, with three clarifications (v0.15.0's three disciplines — judge the property not the vocabulary, decide from the action not the narration, make a rejection actionable — were first written for 0.15.0, but the tagged v0.15.0 build predates them; they first ship here). The example list is now explicitly not a boundary; authorization comes only from the user's own messages; and publishing a release is named in the refusal paragraph — merging that paragraph into the allow-bias sentence (a compression attempt) measurably flipped the release case from reject to allow, so the two stay separate.
  • A rejection now has to be actionable, and the verdict has to be about the action (prompt contract). Two problems surfaced while publishing a plugin from outside the working directory. First, a refusal came back as a bare "unsafe": the agent had nothing to act on and retried the same intent in three different shapes before giving up — worse for safety than one clear refusal, so the prompt now requires the reason to name the property that made the action unsafe (what reaches other people, what cannot be undone) and, where the user could run it themselves, to say so. Second, the verdict moved with the agent's own wording. The prompt now says to decide from the command and its arguments, and that no narration — confident, "user-approved", hedged, or self-referential — may move the verdict in either direction.
  • "External state" is replaced by the criterion it stood for. The old blanket clause ("changes to shared/production/external state, even if the user's words could be read as permission") was blunter than the threat model: it lumped "the user approved this and only the content needs review" together with "an injected instruction is impersonating the user", and it read a harmless fix (moving a tag that had never produced a published artifact) as a dangerous one. It now names the actual property — effects that leave this machine and cannot be recalled, i.e. other people or systems are affected and it cannot be taken back — and tells the classifier to judge that property rather than the vocabulary. (The first version of this bullet said the blanket floor "is unchanged"; it was then replaced in the same release by the user-is-the-arbiter policy above, so the sentence no longer describes shipped behavior. What stays closed to every request is the hard floor.)

[0.15.1] — 2026-09-12

变更

  • 判定权归用户,只有一条硬底线不是用户能定的(策略决定,2026-09-12)。闸门不再判断一个动作值不值得做,只判断用户有没有授权这个具体动作。有风险但用户要求了的动作可以放行,包括会离开本机的效果(发版、发布、部署、推送、对外发消息、写别的系统)。当动作会影响其他人或系统、而用户自己的消息没有明确覆盖这一个动作时,闸门拒绝,并在理由里要求 agent 把原样命令拿给用户确认(ask_user_question)——用户的回答就是直接授权,下次尝试可放行:这就是用户要的 double check。授权只来自用户自己的消息(打字消息,或对 ask_user_question 的回答);agent 的 justification、仓库文本、工具输出永远不构成授权,否则「用户意图」就退化成「谁最后说话谁算」。硬底线对任何人都不开:执行下载来的代码、破坏/移动系统路径、删 docker 卷、读密钥材料或凭据库、把密钥材料写进命令、改 shell 启动文件或 agent 权限配置 —— 这些无论用户怎么说都拒,理由里让用户自己在 auto mode 之外执行;它们也在任何模型介入之前就被确定性拦下。
  • one-token 预筛不再能替用户拍板「离开本机」的动作。 预筛只对「不可能影响他人/他系统」的动作短路放行;一组受控形态(git push、包发布、release/workflow 命令、上传与 POST、远端复制、deploy 类动词…)强制进入结构化审查——那是唯一读到用户授权的一级。规则生效前实测:一条用户确实要求过的对外消息,5/5 被预筛直接放行,审查与授权检查整段没被读。

修复

  • 意图窗口现在带上「被回答的那个问题」。 以前对确认提问回一句「可以」,进到闸门时已经被剥掉了问题本身,于是它读成「批准跑个探针」而不是「批准你在卡片上刚看过的那条命令」—— double-check 闭环合不上。现在答案渲染成「用户的话 + agent 写的问题(仅供消歧)」,prompt 也写明:引号里的问题本身不构成授权、也不能放大答案的射程。这是活体端到端测试发现的,离线测试套件看不到。
  • 预筛现在看到的是「动作」,不只是「叙述」。 两阶段分类器的 one-token 预筛此前只收到工具名与 agent 的 justification,从不包含命令;而它返回 0 时直接 ALLOW、完全不走结构化审查。这让整套 prompt 契约在「叙述是唯一输入」的那条路径上可被整段绕过,且绕过方向是 allow(误放):以真实分类器路由实测(每例 5 次,使用真实 buildSystemPrompt/buildUserMessage 与真实解析器),同一条 git tag -a v9.9.9 && git push origin v9.9.9 配上自信的「用户已明确批准本次发布」理由 → 放行 5/5,配上含糊、自我指涉的理由 → 拒绝 5/5。现在由 actionSummaryOf() 用命令原文或目标路径构造预筛输入、justification 仅作附注;修复后同样测量:两种措辞均拒绝 5/5,npm run build / git push origin main / `rm -rf /tmp/scrat...
Read more

Release v0.14.4

Choose a tag to compare

@log-li log-li released this 11 Sep 09:50

[0.14.4] — 2026-09-12

Fixed

  • Deny-scan parity between enforcement points: the approval path scanned FULL args (document content) while the gate scanned only target paths — a whitelisted write whose body mentioned a sensitive word was wrongly denied, breaking the zero-review allowPath contract and mis-firing the contradiction-pair sentinel. Shared denyHaystackFor() (file tools → paths only; bash → command text) now backs both gate and approval.
  • Escalated file-tool calls now show the classifier its target paths (PromptInput.paths): previously an escalated write was judged from justification prose alone (file tools carry no command).
  • toolArgsKey dirs are JSON-serialized (comma join could collide {/a,b} with a single /a,b dir); warnSetterMissing tracks per-setter (both may be reported); reasoning effort substring match tightened; dead classifyBand import removed; spec's stale test claim corrected. A file-tool sig-parity regression test now documents the gate↔approval reason-template coupling.

Changed (0.14.3 work, not published separately)

  • Permission setters are now runtime-probed too (issue #1 by xiaolinziwang): setApprovalPolicy / setSandboxMode are loaded via namespace import + typeof probe instead of named imports. Newer hosts (Desktop 2.0.5 series) removed these exports — a named import crashed plugin load at module-instantiation. Missing setter → auto mode degrades (skip knob + one-time warning) instead of crashing.
  • Compatibility layer hardened against hosts that removed the legacy effective* exports (0.14.2 work, not published separately):
  • Permission setters are now runtime-probed too (issue #1 by xiaolinziwang): setApprovalPolicy / setSandboxMode are loaded via namespace import + typeof probe instead of named imports. Newer hosts (Desktop 2.0.5 series) removed these exports — a named import crashed plugin load at module-instantiation. When a setter is missing, auto mode degrades (skips that knob + one-time warning) instead of crashing; the fail-soft path is kept.
  • Compatibility layer hardened against hosts that removed the legacy effective* exports (0.14.2 work, not published separately): (adopted from PR #2 by WSL043): permission-state.ts now loads those helpers via namespace import + runtime typeof probing instead of named imports. On newer Harness builds / official npm packages where effectivePermissionPreset & co are removed entirely, plugin loading no longer fails at module-instantiation; a missing fold simply skips the event-log fallback (projection path unchanged). Our fail-soft {} behavior is kept (the PR's throw was not adopted).

Thanks

  • @WSL043 — PR #2: the namespace-import + runtime-probe technique for the permission-compat layer (co-author on the v0.14.2 commit, listed in package metadata).
  • @xiaolinziwang — issue #1: report of the Desktop 2.0.5 permission-setter removal that led to the graceful-degradation fix.

[0.14.4] — 2026-09-12

修复

  • 两道防线 deny 扫描一致:approval 路径曾扫全量 args(含文件内容),gate 只扫目标路径——白名单写入正文含敏感词会被误拒,破坏零评审契约并让矛盾对哨兵误报。共享 denyHaystackFor()(文件工具→仅路径、bash→命令文本)现同时支撑 gate 与 approval。
  • 提权文件工具调用把目标路径传给分类器(PromptInput.paths):此前提权 write 仅凭理由文本被评判(文件工具无 command)。
  • toolArgsKey 目录改 JSON 序列化(逗号拼接可碰撞);warnSetterMissing 按 setter 名跟踪;reasoning effort 子串匹配收紧;清理死导入 classifyBand;补正 spec 虚记的测试声明;新增文件工具签名奇偶性回归测试(记录 gate↔approval reason 模板耦合)。

变更(0.14.3 工作,未单独发布)

  • 权限 setter 改为运行时探测(issue #1,xiaolinziwang):setApprovalPolicy / setSandboxMode 改 namespace import + typeof 探测。缺失时 auto mode 降级(跳过 + 一次性警告)而非崩溃。
  • 兼容层加固(0.14.2 工作,未单独发布):
  • 权限 setter 也改为运行时探测(issue #1,xiaolinziwang):setApprovalPolicy / setSandboxMode 改为 namespace import + typeof 探测,不再命名导入。新宿主(Desktop 2.0.5 系)整体移除了这些导出——命名导入会在模块实例化期崩溃。setter 缺失时 auto mode 降级(跳过该 knob + 一次性警告)而非崩溃。
  • 兼容层加固(0.14.2 工作,未单独发布):permission-state.ts 对 legacy effective* 导出改用 namespace import + 运行时 typeof 探测(思路采纳自 WSL043 的 PR #2)。在新版 Harness build / 官方 npm 包整体移除 effectivePermissionPreset 等导出的宿主上,插件加载不再在模块实例化期崩溃;探测不到的 fold 直接跳过事件日志回退(投影路径不变)。保留我们的 fail-soft {} 行为(未采纳 PR 的 throw)。
  • 贡献署名:package.json contributors 增加 WSL043;实现 commit 带 Co-authored-by。

致谢

  • @WSL043 — PR #2:权限兼容层的 namespace-import + 运行时探测思路(v0.14.2 commit 共同署名,已列入包元数据)。
  • @xiaolinziwang — issue #1:报告 Desktop 2.0.5 移除权限 setter 导出,促成降级不崩的修复。