Releases: qiz029/dscode
Release list
DSCODE 0.7.25
DSCODE 0.7.25
This release lets a repeated install keep a tar installation current instead of refusing, and adds a Homebrew formula for the launcher.
- The installer upgrades instead of refusing (change): re-running
curl | shreads the version already installed and acts on it — equal or newer is reported and left alone with exit 0, older is handed to that installation's owndscode update, which downloads the release, verifies the publisher's sha256 digest, migrates.runtime,.envand the local config, and keeps the previous tree as.dscode-backup-<version>. Anything the installer did not create is still refused with the path it found — a directory that is not a DSCODE installation, or adscodecommand belonging to another installation such as a source checkout — and that check now runs before the download instead of after it. Where the behaviour stops: the swap is the installed updater's work, so a tar installation older thandscode updateis refused rather than replaced, an upgrade of the installation a session runs from still needs that session to exit, and a pre-release installation is superseded by its release. - A Homebrew formula (feature):
brew tap qiz029/tap && brew trust qiz029/tap && brew install dscodeinstalls the published launcher tarball into the formula'slibexec— so the pinned dependency graph stays npm's rather than the formula's — linksdscode, and writes.dscode-brewbesidecli.mjs.brewManaged()reads that marker, so on a formula installationdscode updatemoves only the Hub profile and leaves the launcher tobrew upgrade dscode; an explicit version that would put the profile ahead of the launcher is refused. The tap carries its own workflow that followsnpm view @toddzheng024/dscode versiondaily, recomputes the tarball's sha256 and commits the formula update. Where the behaviour stops: Homebrew 7 refuses to load a third-party tap's formula until the tap is trusted, so that step is part of the install; the formula claimsdscodein the Homebrew prefix, so annpm install -glauncher already linked there has to be removed first; and the formula's own install has not run on a machine whose Command Line Tools Homebrew accepts.
Verification: the release build runs npm run check on both Node 24 and 22.19.0, and Linux e2e is green. New cases cover the installer's version decision — an older installation handed to its own updater without any download, an equal or newer one left untouched, a pre-release not mistaken for the release it leads to, and a foreign command still refused with its symlink left in place — and the Homebrew marker the launcher reads. The installer's upgrade path was also run end to end against real releases: 0.7.23 installed into a scratch directory, the installer re-run to move it to 0.7.24 through its own updater with .dscode-backup-0.7.23 left behind, and a third run reporting that nothing was changed. The formula was checked as far as this machine allows — ruby -c on it, brew info loading it, its pinned sha256 equal to the published tarball, and the npm global-prefix install with the marker read back by the new launcher code — but no brew install completed here: brew doctor reports this machine's Command Line Tools do not support macOS 26, which Homebrew refuses to install past, so the formula's install and test blocks have no automated or manual confirmation yet. tests/code-review.test.mjs does not complete inside a DSCODE session, the documented in-session limitation (it spawns real git subprocesses and the run ends with Promise resolution is still pending but the event loop has already resolved).
DSCODE 0.7.24
DSCODE 0.7.24
This release lets an event start a session unattended, caps a goal's rounds from the command plane, adapts MiMo V2.6 on the OpenRouter route and trims that route's /model to a curated list.
- An event starts a session (feature): trigger definitions live in
<state>/triggers/and<workspace>/.dsh/triggers/(a project file wins on the same id) withinterval,calendar,watch,pollandexternalsources, an absolute workspace, a goal objective with a round cap and run limits. An event may carry only data —source,title,textand scalarfieldsunder a requiredeventId— so nothing can smuggle a permission or a session into a run, andaskis refused because an unattended run can never answer an approval.dscode trigger run <id>takes a single-flight lock and refuses a disabled definition, a missing workspace, a repeated event (a scheduled firing is identified by its cadence window), a spent daily cap and a too-recent run, recording every outcome with its reason, exit code, session, cost and rounds intriggers/runs.jsonl.fire,emit,list,show,log,log --failed,events,new,enableanddisablecomplete the CLI, which the published launcher routes likeexec, and the terminal reads the definitions, records and spool through/triggers. Where the behaviour stops: nothing notifies on a failed run yet — the run log is the pipe a notifier will read. - Unattended runs, and launchd carries the schedule (feature): the spawned Host replaces the terminal with the trigger overlay, creates its goal through the goal service (the model-facing tool requires a human turn), delivers the prompt with the event's values and stops on the goal's own end, a round or cost cap, a pause, the timeout or a needed approval — fail closed, marked
approval_required.dscode trigger install <id>writes and loads a launchd LaunchAgent (interval/poll→StartInterval, a five-fieldcalendar→StartCalendarInterval,watch→WatchPaths), refuses any cron shape launchd cannot express instead of approximating it and prints the crontab equivalent, whilepollruns itscheckin the workspace and starts a session only on exit 0. Where the behaviour stops: this scheduling is macOS-only, and apollwhosecheckfails starts nothing and tells nobody. /goal[20]caps the rounds from the command plane (feature):/goal[20] <objective>creates the goal with a 20-round cap and/goal[20]re-caps the current goal, so an unattended task stops after the rounds the human allowed; a cap with no goal, or a control word after a cap, is refused with the usage. Where the behaviour stops: upstream's/goalremains the surface — a runtime patch (dsh-command-goal) adds only the shorthand.- MiMo V2.6 drives the OpenRouter route (feature):
xiaomi/mimo-v2.6-pro,-flashand-pro-ultraspeedjoin DeepSeek V4 and Kimi K2.6 in replayingreasoning_contenton an assistant turn, because Xiaomi answers 400 Invalid Format when a thinking-mode multi-turn request does not pass the field back; the wholexiaomi/mimo-v2.6line is matched, so a new size in that family arrives covered. Context, prices and reasoning bands come from OpenRouter's listing unchanged (off/low/medium/high, defaulthigh; the endpoint is fp8, inside the route's quantization allow-list, sorequire_parameterskeeps it routable). Where the behaviour stops: the ids, the listing fields and the request body were checked against the live listing, but no probe with a real OpenRouter key has run, so MiMo is not yet claimed as tuned and tested. - The OpenRouter picker is curated (change):
/modeloffered every tool-calling model in the live listing — 373 of them, in alphabetical order. The route keepsLISTED_MODELS, 28 ids: the current DeepSeek, GLM, Kimi, Qwen and MiMo models plus the flagship line of Anthropic, OpenAI, Google and xAI, each verified to exist and to call tools against the live listing. Only the picker narrows:resolveModelstill serves every id the listing knows, so a session already on a trimmed model,dscode exec --modeland a provider switch keep working. Where the behaviour stops: the list is edited by hand, so a new flagship and a retired id both need that edit; the Western flagships stay best-effort, and only the five tuned labs are covered by the probe script.
Verification: npm run check — lint; typecheck and typecheck:strict; unit, integration, packaging and evaluation — is green on the code this release ships (05e28ac) on both Node 24 and 22.19.0 through the Checks workflow, Linux e2e is green, and the release build re-runs the same gate before publishing. New cases cover the curated picker (a listed flagship appears, a usable but unlisted model does not, and an off-list model still resolves so an existing session keeps running), the MiMo entries' listing shape and reasoning bands, and the family-wide reasoning_content replay; scripts/verify-login-runtime.mjs asserts the trimmed directory in a real Host. Live checks, none of them model calls: all 28 curated ids exist and can call tools in OpenRouter's listing, and the MiMo entries parse to their real context, prices and bands. No automated case covers: a real OpenRouter key call to MiMo (the 400 it guards against is documented by Xiaomi, not reproduced here), a launchd install on a real schedule, a trigger fired end to end without a terminal, or curated-list drift — a retired id is caught by reading the picker, not by a test. tests/code-review.test.mjs does not complete inside a DSCODE session, the documented in-session limitation (it spawns real git subprocesses and the run ends with Promise resolution is still pending but the event loop has already resolved); macOS-only paths such as TUI rendering, computer use and accessibility permissions have no automated case.
DSCODE 0.7.23
DSCODE 0.7.23
This release pins a session to one folder, makes cross-session traffic visible in the terminal, closes the per-turn cost loop, and removes the per-frame re-wrap that streaming paid for the whole live region.
- A session stays in the folder it was created in (change): the workspace, the durable log and the shell already followed the session header's
cwd; what was missing was the second half of the rule. Resuming a session now runs in that folder whichever entry point asked for it —--resume, the/resumepicker,/search, or a queued switch — and a resume that leaves the folder the terminal was launched in prints one warning notice naming both folders, so a window that silently changed project is visible.--continuekeeps its current-directory scope. Where the behaviour stops: the folder is chosen at creation and no command moves an existing session, so--cwdonly decides where a new session binds; reaching another project's context still means reading that session (ordscode send), not resuming it here.dscode exec --resumeis outside this display path: it writes the session's log in its own folder while the one-shot process keeps the directory it was launched in, so a turn that must touch that folder is run from it. - Cross-session communication is visible (change): while a
send_session/reply_sessioncall is in flight the activity line reads⇄ sending to <peer>, a request awaiting its answer reads⇄ waiting for <peer>, and a settled send or an inbound relay prints one notice line. Both surfaces paint with thesteeredviolet and the⇄/→/←glyph family instead of the brand blue and•every other local notice uses, so another session's traffic never reads as this session's own output./taskslists this session's messages on demand with direction, peer, kind/mode, delivery state and a bounded body. The view is folded from the root log (tool/call,tool/result, and the bridge's relayeduser/message), so a restarted session rebuilds its history and announces only new traffic. Where the behaviour stops: only an explicitreplyclears an outstanding request (a progress notify does not), and a relay whose body does not carry the bridge's documented[kind/mode]header leaves the kind unknown and is therefore never counted as an answer. - The last completed turn's cost, and a cost per turn (feature): the footer's money figure gains the turn it just finished (
$1.23 ❄️ · #12 $0.04), and/usagegains acostcolumn pricing every turn from the same ledger,+marking a turn whose settled calls were not all priceable. Attribution charges a call to the turn that was running when it started, by binary search over the turn windows, so the once-a-second footer read falls from O(calls × turns) to O(calls × log turns) — measured 0.63 ms for 8000 calls across 300 turns. A call in the gap between two turns, or before the first one, is reported as unattributed rather than charged to a neighbour. Where the behaviour stops: a session older than the cost ledger shows--per turn, and the footer's turn figure drops before the session total on a narrow terminal. - Streaming no longer re-wraps the whole live region every frame (fix): the live list was re-created for every event, so unchanged settled-but-unflushed entries were re-flowed on each frame while only the streaming text actually changed. Rows are now memoized by entry identity (the projection replaces an entry object whenever its content changes) under a per-width/verbose cache. Measured on 16 long entries: 3.12 ms/frame → 0.001 ms/frame. Where the behaviour stops: an entry whose object really changes still re-wraps, which is what correctness requires; the cache keys on the floored column count, so a fractional width wraps at the width it keyed.
- The status bar carries the loaded skill count (feature): a
skillsfigure reads the live session catalog, so it follows a skill added mid-session or a session switched to another workspace, and/statuslinetoggles and reorders it. It stays--until the first catalog read settles. - The footer is two balanced rows (change): row 1 names the session and the model with its effort (
○ title · deepseek-flash @ ultra), row 2 carries the status figures and closes with the live metrics as one left-hand cluster (~24.6 tps · 18.2 tps avg · 13% ctx · $0.42 · 87.3% cache). Every figure reads value first with a short qualifier, and a balance the provider cannot report is dropped instead of parked as$--. The order in the cluster is current → average → context → spend → last turn → cache;footer.currentis gone from the interface catalog andfooter.contextis the untranslatedctxabbreviation, liketps. - Ancestor skill discovery is on by default (change):
.dsh/skills,.agents/skillsand.claude/skillsfrom every directory between the working directory and home register at rank 300 with no configuration, nearest first, so a shared~/Workspace/.dsh/skillsis visible to every project below it.DSCODE_SKILL_ANCESTORS=0(alsooff,false,no,none,disable) turns it off; an unrecognised value keeps the default on instead of reading as off, whileDSCODE_PROJECT_HOOKSkeeps its fail-closed enable spellings. - The Ink repaint ledger no longer leaves a blank band (fix): it reserved the whole previous frame height after a static flush, so the live region a settled step freed stayed on screen as blank rows above the composer that the next stream had to refill — 28 rows in a model of one turn on a 40-row terminal. It now reserves only the rows the flush did not consume, which drops that band to what the flush left over (2 rows in the same model) while the composer still never rides up.
scripts/patch-ink.mjsupgrades an already v2-patchednode_modules/inkon the nextnpm run setupor launch.
Verification: npm run lint, node scripts/check-docs.mjs, npm run typecheck and npm run typecheck:strict are clean. npm run test:unit runs 403 cases; 402 pass, and tests/code-review.test.mjs does not complete inside a DSCODE session — it spawns real git subprocesses in temporary directories and the run ends with Promise resolution is still pending but the event loop has already resolved, which AGENTS.md documents as the in-session sandbox limitation, so the suite's exit status is not a usable gate here and that file needs a normal terminal. New cases cover the folder comparison (same/different/prefix/symlink/Unicode normalization), the CLI resume resolver, the cross-session fold (send/reply/relay, reply-only clearing, bounded rows, namespaced ids, the /tasks text), the attribution windows and the binary lookup cross-checked against a linear scan, the /usage cost column, the live-region cache and the projection identity invariant, and the footer's drop order. The per-turn figures were not exercised against a real ledger on a paid session, and the cross-session display was not observed with two live sessions in a real TTY.
DSCODE 0.7.22
DSCODE 0.7.22
This release moves the Hub client to @dsh-plugin-hub/cli 0.5.0 and makes a failed launcher command explain itself: the whole cause chain, the network causes to check on a managed network, and a way out of a leftover profile directory.
- Hub client 0.5.0 (change): the launcher's profile install now runs through Hub CLI 0.5.0 (
@dsh-plugin-hub/schemas0.5.0 as well). The pinned DSH runtime is prepared and verified by the Hub CLI itself — it runs an exact-versionnpm install @deepseek-ai/dsh@<version>into$DSCODE_HOME/.hub/runtimes/<version>, checks the package identity and that the executable really lives inside the package directory, and then launches it with the current Node — instead of the launcher's per-bundlenpm exec/npxwrapper, so an install no longer pays a separate registry round trip for every bundle and the runtime is cached across installs. Where the behaviour stops:pnpmis still required for the profile's dependency install, which is why the launcher keeps its pinnedpnpm10.15.1 onPATH, and itsnpxshim stays as the guard for a Hub release that still spawns a pinned-DSHnpxcommand. The command surface the launcher uses (profile apply|upgrade|rollback|history|doctor), the state layout (.hub/installations/dscode/current.json, schema version 2) and theDSH_HUB_MACHINEnon-interactive behaviour are unchanged, sodscode update,dscode rollbackanddscode historykeep working against an existing installation. Also bumped: imapflow 2.0.2 → 2.0.5, mailparser 3.9.26 → 3.9.28, nodemailer 10.0.9 → 10.0.10, fflate 0.8.2 → 0.8.3,@modelcontextprotocol/sdk1.29.0 → 1.30.0, yaml 2.8.1 → 2.9.1 and eslint 10.10 → 10.11. - A failed command keeps its reason (fix):
dscodeprintederror.messagealone, so the real cause of a fetch, lock or spawn failure — whichError.causecarries — never reached the terminal, and a machine that cannot reach the Hub printed a barefetch failed. The launcher now prints the whole chain (reason <- cause (CODE)), once: a Hub or profile step attaches its hints to the error and the top-level reporter prints the message and the hints together instead of each printing its own copy. Chains deeper than five links truncate with…, links without a message are skipped, and a chain with nothing in it readsUnknown error. - Hub failures name the network causes (fix): when a Hub, update or install step fails, the launcher lists what to check on a managed network — the step needs
api.dshpluginhub.aiand an npm registry; the Hub call does not useHTTP(S)_PROXY, because the pinned client calls plainfetch(15- and 30-second timeouts, no proxy agent); an internal mirror goes inDSH_HUB_API_URLinside$DSCODE_HOME/.env; a TLS-inspecting proxy needs its root CA inNODE_EXTRA_CA_CERTS; and the prebuilt GitHub release installs without the Hub at all. TheHTTP(S)_PROXYline appears only when a proxy variable is set, and the value is never echoed, so a credential inside a proxy URL cannot leak into a terminal or a log. - The install step says what it is doing (fix): first launch printed one line and then stayed silent while npm fetched the pinned runtime and the plugin tree, which reads as a hang on a slow registry. It now also states that the step downloads the pinned Harness runtime and plugins with npm and can take several minutes.
- A leftover profile no longer dead-ends (fix): when
profiles/dscodeexists without the Hub state file, every command — includinginstallandrollback— refused withExisting unmanaged/incomplete dscode profile; inspect <path>, and the only way forward was knowing to delete the directory. The error now names the directory, states that the launcher will not replace it automatically, and prints themvthat moves it aside, with both paths single-quoted for the shell.
Verification: npm run lint clean, node scripts/check-docs.mjs clean, npm run test:unit 357/357, and npm run test:package passes (exact tarball integrity, exports, syntax, launcher lock dependency, and no local state or build paths). npm run verify:hub against the rebuilt candidates reaches PASS real Hub install + DSH compose — a real Hub CLI 0.5.0 install of the profile plus the real DSH composition — and then stops at its agent-loop probe on this machine, because the session's own sandbox refuses to apply a nested profile (sandbox-exec: sandbox_apply: Operation not permitted), the documented in-session limitation; that probe therefore runs in CI. Three new unit cases cover the failure formatting (chain, truncation, empty chain), the unmanaged-profile message, and a profile path containing a quote that must stay one shell argument. The failure output on a real corporate network, and the Hub 0.5.0 upgrade path for an installation that already exists, have no automated end-to-end case and rely on the next real-machine install.
DSCODE 0.7.21
DSCODE 0.7.21
这版让底部状态栏的几何只随终端宽度变化,给到达的消息与回合边界补上隐藏的时钟标记,并新增 /btw:立即在旁支会话里回答问题,答案不并入主线。
- 状态栏几何不再随数值变化(修复):权限徽章改为锚定第一行右缘,
shift+tab提示的列位常驻(运行中不显示也照样预留),每个活跃数字(回合/步数、各类耗时、tps、token、缓存命中、上下文读数)改为在各自固定列内右对齐,空读数渲染成--占位而不是整组消失。此前只要数值变化,徽章就会横向漂移(同一屏实测从第 75 列滑到第 56 列),组也会随阈值出现或消失;现在只有终端宽度决定降级顺序。telemetry 那一串里的 tps/上下文/缓存数字同样定宽,并给槽位预留这 6 列,避免定宽把本来放得下的数字挤掉;金额对(花费/余额)保持不定宽——实测为一个会话只跨几次位数的数字预留列,会在常见宽度下挤掉 per-second 的 tps。 - 隐藏的时钟标记(新增
dscode-time-marks插件):每步被认领的消息、以及每个刚结束的回合,都会在模型上下文里多出一行Time mark: 2026-09-19T01:58:34-07:00[America/Los_Angeles] — steer relay from session:abc arrived, waited 6m24s (composed ...)。它是插件来源的上下文,终端一个字都不渲染(复用既有的按 producer 隐藏规则),也不改写任何消息正文;只有真的发生到达或回合结束才注入,纯工具循环没有开销。中继消息的source现在带上label/mode/composedAt,排队时长才有据可报。 /btw旁支提问(新增):/btw <问题>立即新建一个继承主会话历史的子会话(seed 到最近一个已完成回合;前缀超过 400 事件、或还没有完成回合时退化为 ≤1200 字符的背景摘要),以read-only权限预设和最低可用 effort 运行,答案在/btw面板里流式渲染(esc关闭,多条用←/→切换,最旧的已完结运行自动退休)。旁支会话带parentSession与origin: subagent,因此不生成 session card、在会话列表里是只读行;它的内容不会进入主线 transcript,也不会进入主线的模型上下文。切换会话或退出时子会话被释放。
验证:npm run lint 干净;npm run test:unit 351/351;npm run test:package 与 npm run test:integration(七个探针)通过。新增/扩充的测试覆盖:同一宽度下早晚数值的段列位与徽章列位不变、36–240 列逐帧不溢出、telemetry 数字变大不移动后续段、时钟标记的种子退化与 brief 预算、/btw 面板渲染与 esc 关闭。/btw 的内核路径(真实创建子会话并流式进面板)目前没有自动化端到端用例,靠实机验证。
DSCODE 0.7.20
DSCODE 0.7.20
这版修掉一个让自动审核「看着在工作、其实一直瞎判」的缺陷:Jev 从来没看到过用户的指令。
- Jev 看不到用户指令(修复,自动审核):
contextFor把保留的用户指令以{ seq, text }交出去,而approvalState读的是message.content[].text——于是送去判定的userInstructions永远是空字符串,Jev 每次只看到一个孤立的工具调用。表现就是authorized长期停在 0.04–0.07:你明确要求的发布,在它眼里是「没人要求过」,于是每次都转人工。这也让 0.7.19 引入的「已授权时把风险守卫交给 reviewer 模型」形同虚设——门槛是authorized ≥ 0.6,而它从没到过 0.07 以上。现在状态构造接受三种形状:{ text }、字符串content、内容块数组;字符串content也不再抛出那个会让 Jev 静默失效的TypeError。 authorized改为独立评分(变更,自动审核):这个分数现在只衡量指令是否覆盖了这次动作,与风险分开——指令要求的危险动作也该得高分,没人要求的危险动作得低分。指令几乎不会点名具体命令,所以问题问的是它要求的那件事,并把评分锚点写进问题:≥0.9 直接要求该步骤、[0.6,0.9) 完成已述任务的必要步骤、[0.2,0.6) 可能属于任务但可选或不明确、<0.2 不覆盖。区间互不重叠,避免同一次判定在「交给模型」与「拦住」之间摇摆。同时把verdict.deny里的 "publish something" 收窄为「指令没有要求的发布」,免得用户要求的发布被算进拒绝理由。
校准(2026-09-19,typesafe/jev-1.13-20260917,真实调用四例):「提交加发布」+ 出沙箱跑 make release → authorized 0.68、defer;同一指令点名 make release → 0.86、defer;同一指令 + rm -rf ~/Documents/notes → 0.07、deny;「看一下测试覆盖」+ 同一个 make release → 0.11、human。修复前第一个场景只有 0.06,只能转人工。
验证:npm run check 通过(lint、覆盖率、集成、打包与评测全绿)。新增回归断言要求「{ seq, text } 形状必须产出指令」「字符串 content 不抛错」「混合形状按顺序拼接」——原测试只用了内容块数组,所以这个缺陷一直没被发现。
DSCODE 0.7.19
DSCODE 0.7.19
这版让「用户已经要求的提权」不再每次打断人——由自动审核的第二个模型来判;并修掉 agent 里调用自带凭据的 CLI 时被误判「未登录」的那条连锁。
- 已授权的提权改交模型深审(变更,自动审核):Jev 在放行方向有四道守卫——凭据风险 ≥ 0.5、难以撤销(
score ≥ 2)、choice不是allow、置信度 < 0.85。它们本来一律转人工,而且不看指令是否已经授权:一次「把代码提交并发布」里必须出沙箱的构建验证,就这样每次都弹审批。现在只要authorized ≥ authorizedVeto(0.6)——即保留的指令看起来就是要它做这件事——这四道守卫改为返回defer,交给能看到完整待执行参数与指令的 reviewer 模型裁决。Jev 的分数本身偏粗,而本部署的政策本来就允许常规网络访问、也允许 CLI 使用它自己保存的凭据,所以这些守卫不该单独一票否决。拒绝方向未变:决定性拒绝、以及「指令像是否决了这次拒绝」仍按原样走。defer从不会被当作放行(applyVerdict只认显式allow),reviewer 自己判human时照样找人;一次defer在审计里留下两行——Jev 的判定与风险分数、以及最终裁决。 - Hub 登录对齐到状态目录(新增,启动器):
dsh-hub login写$DSH_HOME/.hub/auth.json,但在 DSCODE 之外登录的用户写在~/.dsh/.hub/auth.json。DSH_HOME 指向别处时,agent 里的 CLI 就会报「Not signed in」——发布时正是这条把人引到「去导出 token」,再被判成凭据风险、又叫人工审批。现在启动器会(在目标不存在时)把那份登录复制到本次会话的$DSH_HOME:目录规范为0700、文件0600,已有副本永远优先(CLI 刷新出的新 token 不会被旧文件覆盖),任何读/写失败都静默忽略——纯便利性复制绝不能让启动失败。副本落在状态目录里,也让 CLI 能刷新自己的会话而不必离开沙箱。
验证:npm run check 通过(lint、覆盖率、集成、打包与评测全绿)。新增测试覆盖「已授权时四道守卫返回 defer」「低于 authorizedVeto 时仍是 human」「已授权的决定性拒绝仍走人工」「defer 交给 reviewer 模型且不花人工」「reviewer 自己判 human 时仍找用户」,以及凭据对齐的「无登录时不动作 / 复制一次 / 已有副本优先 / 松散目录被收紧到 0700 / 源不可读时不抛异常」。
DSCODE 0.7.18
DSCODE 0.7.18
这版给压缩评测补上三件让它能指导决策的东西——确定性的上下文保真度、有区分度样本的预筛、以及把格式失败与模型失败分开——并完成第一次摘要 A/B。
- 上下文保真度(新增,评测):
retention在模型真正看到的上下文上,按 token 边界统计每条探针的原句证据(evidence.quote)以及其中的词是否还在,写进scores.jsonl,报告里是Evidence kept列。它不调用被测模型、也不调用裁判,因此没有采样方差,可以每次运行都记录。在 LongMemEval tier1 上它直接解释了压缩的损失来源:证据原句仍在时通过率接近不压缩,原句被摘要吞掉时只剩 4%–6%,而词级保留仍有 89%–92%——摘要留住的是词,丢掉的是可复述的事实。 - 有区分度样本预筛(新增,评测):
--baseline <run> [--baseline-policy full]只用基线运行中该策略每个 repeat 都全对、且没有基础设施错误的 case,筛选后的数据集写入新运行的dataset.json,manifest.json的selection记录来源与数量。这批 100 个 case 筛出 47 个,后续策略对比省掉一半没有区分度的探针。 - 摘要 A/B 不需要改产品(新增,评测):策略可带
summaryInstruction,由评测侧的EvalCompactionEngine在压缩请求里、上游总结指令之前插入一条指示;不带则与产品行为完全一致。第一次对照(compact-80vscompact-80-keepfacts,47 case × 3 repeats)是否定结果:要求「保留可引用的事实原句」既没有改变保真度,也没有改变通过率(更好 10 次、更差 11 次),成本还略增。结论与复现方式记录在eval/experiments/2026-09-longmemeval-tier1-summary-ab.md。 - 探针协议重试(修复,评测):答复不是单个合法 JSON、或流在 JSON 收全前被截断时,带纠正提示重试一次并记入
answerRetries(计入调用上限);被拒绝的答复不会进入会话,因此不影响后续检查点。另一次运行里这类格式错误为 0(423 个 checkpoint),说明先前 15 个invalid-answer-json来自当时的后端与模型快照,而不是 runner 的普遍缺陷。 - 证据引用的词边界(修复,评测):LongMemEval 样本原来在 160 字符处硬切,87/100 条证据引用被切断在词中间,按 token 边界匹配时会被判成「未保留」(不压缩时也只剩 55%)。现在在词边界截断,修正后不压缩策略恢复 100%;修正后的数据集写到
eval/private/longmemeval/tier1-wordboundary.json,不覆盖原文件,历史运行的 hash 校验仍然有效。 - 预构建发布包(新增):
dscode-<version>-darwin-arm64.tar.gz与-darwin-x64.tar.gz是已装好锁定node_modules的源码树,curl | sh安装器与dscode update优先使用本机对应平台的那份,安装与升级只访问 GitHub、既不需要 npm 也不需要 npm registry——公司 registry 代理不再让npm ci卡死安装。没有预构建包的平台回退到源码 tarball,DSCODE_INSTALL_SOURCE=1可强制选择。npm run dist构建全部三份(DSCODE_PREBUILT_ARCHS可收窄或跳过预构建包),发布 workflow 与make attach负责上传。 - 启动目录的
.env不再影响启动(修复):上游把启动目录的.env当作项目层读取,工作区文件会把变量注入 agent 进程,其中某个仅限引导使用的名字(任何DSH_*、NODE_OPTIONS、CA 路径)会让启动以 "only the launching environment may set" 中止。现在通过固定的dsh-app-boot补丁在仓库与 tar 安装中丢弃这一层;安装自己的.env仍由 launcher 加载。
验证:npm run check 通过(lint、覆盖率、集成、打包与评测全绿)。评测侧新增测试覆盖保真度的 token 边界匹配与压缩后证据丢失、--baseline 的筛选(含跨 repeat 与部分证据)、协议重试只改压缩请求的提示词与拒绝答复不入会话;summaryInstruction 的测试确认它只出现在压缩请求里。
DSCODE 0.7.17
DSCODE 0.7.17
这版把压缩引擎从「构建期改写上游源码」换成「子类覆盖」,补回 /language 的六种语言,并让压缩评测重新对准生产引擎。
- 压缩引擎改为子类覆盖(重构,行为不变):自动压缩原本靠四个
replaceOnce补丁写进@deepseek-ai/dsh-compaction-basic的源码,上游一升级就可能断。现在策略住在自研的plugins/compaction/engine.mjs:DscodeCompactionEngine继承上游引擎,只覆盖compactIfNeeded(阈值定价、完成预算、预取、溢出先剪枝)与summarize(预取摘要的注入点);durable 事务、compaction/start|end标记、稳定性校验仍由上游compactRegion负责。分发侧不再 vendor 该包,安装期也不再改它的源码。 /language恢复六种语言(修复):自研终端接管后,/language落回上游那份只有 en/zh 的实现,而 DSCODE 自己的消息表一直支持en、zh-CN、zh-TW、ja、ko、es——面板却只列两项,/language ja还会被静默存成英文。现在列表、参数解析和language.json持久化都跟随plugins/i18n:zh、jp、繁體中文走别名表解析,已存的zh升级为zh-CN。终端自身的骨架文案仍只有英文与简体两套,其余语言下骨架用英文、DSCODE 自有文案用所选语言——这正是/language一直承诺的语义。- 压缩评测重新对准生产引擎(修复):子类化之后评测 harness 仍挂未打补丁的上游引擎,等于不再测生产配置;现在挂
DscodeCompactionEngine并记录引擎源码哈希,同时把判定模型与被测模型解耦。 - OpenRouter 路由的评测窗口(修复):
OpenRouterAdapter会报实时列表里的 1M 窗口,把阈值抬到任何重放历史之上、压缩永不触发;新增窗口包装器,报告 harness 实际执行的那个窗口。 - 语义裁判的解析容错(修复):裁判只要在 JSON 对象前多写一句话或套一层围栏,整批判定就被记成「未评分」,校准因此失败(三个模型分别 10/14、6/14、4/14)。解析器改为提取最外层对象,校准 14/14 通过。
- 预取的等待不再吞掉压缩指示器(修复):预取尚未完成时提交压缩,原先要等摘要跑完才进入事务,等待期间 TUI 看不到压缩指示器。现在等待发生在事务打开标记之后,指示器照常显示;预取失败或 span 被改写则回退到现场摘要,不会让事务失败。
验证:npm run check 通过(lint 干净、单测、集成、打包与评测全绿)。评测修复带真实契约测试——harness 挂生产引擎、评测窗口按 harness 执行、裁判解析容错——以及引擎级回归:阈值定价与完成预算仍在压力路径生效、溢出先剪枝够用则跳过摘要、预取在标记打开后提交且被改写时回退。/language 侧新增终端断言:六种语言列表与 DSCODE 消息表逐项一致、别名解析、旧值 zh 归一为 zh-CN、其余语言下骨架文案回退英文。
DSCODE 0.7.16
DSCODE 0.7.16
这版把自动压缩真正跑在「消息能占的窗口」上,并给受限环境补上可用的沙箱执行器。
- 压缩阈值按消息真正的余量计价(修复):DeepSeek 适配器把输出预算(默认 256k)算在同一个 1M 窗口内,所以「消息 + 输出」超过窗口的请求会被直接拒掉;而阈值原先按整个窗口计价,90% 那条线(943,718)落在真实消息上限(792,576)之上,压力压缩因此永远不可能先触发。结果是每次压缩都出自溢出恢复:先剪枝、再当场同步写摘要——0.7.15 的日志里
compaction/summary出现在compaction/start之后 26.8 秒,而只挂在压力路径上的预取压缩一次都没跑过。现在effectiveContextWindow会扣掉适配器声明的输出预算,阈值 713,318、预取起点 634,061 都落在上限之下;/model的「切换会压缩」预览用同一个窗口。 - 溢出恢复剪枝够用就不再摘要(修复):溢出恢复先剪枝,然后无论如何都摘要。0.7.15 那次请求只超了 125 token,剪枝却释放了 87,018 token,摘要纯属白花。现在剪枝后会用
fitsInWindow(消息 + 输出预算 ≤ 窗口)复核:装得下就直接返回让调用方重试(剪枝本身以surfaceOp: replace替换了 surface,重试条件成立),装不下才继续压缩;并且必须确实发生过 surface 替换才允许跳过,否则调用方不会重试、会把原始报错抛给用户。 - 沙箱执行器(新,需手动启用):
plugins/tui-tools/sandbox-runner.mjs在原有写策略上补一条(allow file-write* (literal "/dev/ptmx"))——没有它,被限制的命令无法分配 PTY(posix_openpt: Operation not permitted),嵌套 harness、tmux、expect 和 node-pty 测试都会坏;在已经受限的进程里它继承外层 profile,而不是再套一层。复制config/harness.local.example.yml到config/harness.local.yml即可启用,启用后npm run doctor、make release,以及「在 dscode 会话里再开一个 dscode」都能跑。 - runner 的提示不再被误判成故障(修复):provider 会把「非零退出 + stderr 命中
runnerFailureSignatures」判成沙箱故障。runner 原先用同一个前缀打印「正在继承外层 profile」这类提示,于是嵌套会话里任何失败命令都会被报成沙箱损坏。现在真故障走fatal:、提示走notice:,示例配置里的签名只匹配前者。
验证:npm run check 通过(lint 干净,单测 315 项全绿)。压缩修复带真实契约测试——直接调 DeepSeek 适配器的 modelInfoFor,确认 defaultMaxTokens 是顶层字段、effectiveContextWindow 得 792,576——以及引擎级回归:带输出预算时压力路径确实触发;剪枝够用则跳过摘要、未替换 surface 则不跳过;并覆盖 pristine / 仅打 v1 / 仅打 v2 三种旧引擎状态的升级路径。沙箱 runner 的 apply 路径由 shim 记录 profile 后覆盖,继承路径在真机上实测。