Repository navigation
Conversation
…a decision model Add the @eduwork/dsh-auto-approval plugin. Under a dedicated permission preset it reviews bash/pwsh sandbox escalation requests before the client's confirmation card: fixed rules flag operations the user must have asked for, and a SystemOne-compatible decision model scores the request. Approved requests are allowed once; everything else reaches the user unchanged. Reviews are time-limited, retried once on network errors, 429 and 5xx, and paused for a session after three failures in a row. The plugin ships disabled and is not yet part of desktop assembly.
- Record a shell call only after the other pre-execute policies allow it, so a call a hook asks the user about is never reviewed; skip subagent sessions. - Track calls per session and drop them on their result; two calls in flight under one ID are ambiguous and go to the user, and the approval must name the recorded tool. - End a review that waits on a stalled Host service within the time limit. - Take created files from successful write and apply_patch results instead of call arguments, and read the session up to the last log entry of the pending call ID. - Cover these paths with mounted tests and the real Runtime with fs tools.
- Keep the deploy-script pattern linear: the name after the keyword stops at the next keyword. Rule results are unchanged on the evaluated requests. - Find a heredoc's end from its own line instead of rescanning the command. - Hand commands over 16000 characters to the user; the longest evaluated command had 13010, and the limit bounds the rules' running time. - Attach a script only when an interpreter runs the file, not for inline code (node -e, python3 -c, bash -c) or a syntax check (bash -n). - Track every shell execution by its token: while more than one call with an ID is in flight, none is reviewed, and a call started during a review stops its approval.
- Take the workspace from the sandbox policy, whose boundary stays at the session's starting directory, and keep the current directory (moved by /cd) as the base for workdir and script paths. - Do not attach a script run by bare name without an interpreter: the shell finds it on PATH, not in the current directory. - Cover both with mounted tests and a real-Runtime test that moves the session's directory outside the workspace.
- Start from the directory the shell tool uses: bash appends workdir and lets the file system resolve '..' after links, pwsh resolves it; show the real directory. - Open a script by its path as written under that directory, so a '..' after a link reaches the file the shell runs. - Leave the script out after 'cd -' or cd options; a bare 'cd' goes home. - Test both with real links; note in the READMEs what the workspace and current directory fields mean.
The fifth review found more shell forms the directory walk misread: cd with options and quoted paths, a quoted file name with a space, and cd inside a pipe or after ||. Instead of modeling each, follow only a plain sequence: - no script when the command has a pipe or ||; - cd is followed only to one plain path (quotes must pair); any other cd, pushd/popd, source, eval, exec, builtin, command, compound commands and function definitions stop the walk; - a quoted script name must close its quote. Other programs before the script still do not move the shell.
The sixth review showed that excluding known shell forms still followed commands it could not read: variables, comments, unparsed cd arguments, commands after exit or false, heredoc bodies, and PowerShell's Set-Location. Replace the directory walk with one positive pattern: the whole bash command runs a literal path, directly or through an interpreter whose options take no code, with plain arguments and redirections. Every other form, and every pwsh command, attaches no script; the command itself is still reviewed. Of the 693 evaluated requests only 7 carried a script.
- Count a decision service abstention (choice: null) as a failure instead of reading its probabilities; the evaluated answers had none. - Attach a directly run script only when it has an execute bit; without one the shell refuses it and nothing runs.
- Re-check the session's preset before approving: a session that left the preset during the review is asked as usual. - Attach a directly run script only when this process may execute it. - Keep a quoted ~ literal, as the shell does. - Attach no script after assignments before the command, such as BASH_ENV, which can run code before the script.
… path - A redirection can empty the script before the interpreter reads it. - Node resolves '..' before links while bash and the file system follow the link first; rather than model each interpreter, a path with '..' attaches no script.
…t names trim() also removed characters bash treats as part of a word, so a command naming 'task.sh\r' attached task.sh. Trim only spaces, tabs and newlines.
bash, sh and zsh parse a leading + as an option, so 'bash +task.sh' runs nothing.
Re-check the review's signal with the other conditions before approving, so a request withdrawn or timed out as the review returns is not answered.
Each request reviewed under the preset adds a notice to its call's result. The trajectory shows its summary with the outcome and score; the model reads only whether the escalation was allowed automatically or the user was asked, so it no longer reports an automatic approval as the user's. The preset name in the examples drops "experimental".
…ter a rejection The trajectory lists a notice by its text, so the text now says in Chinese who decided and ends with the call ID; the score stays in the summary under the notice's source. After the user rejects an escalation, later escalations in the session go straight to the user until the user writes again, so a retry is never approved behind a rejection.
…s words Context reaches the model as a user message, and in a desktop run the model took the Chinese notice for something the user said. The notice is now wrapped in reminder tags, as DSH does for its own runtime context, and says it is not a message from the user.
The call ID comes from the model and was written inside the reminder tags as is, so an ID holding a closing tag could move text out of them and into the user message the model reads. The notice follows its call's result, so the text now names only the tool.
…ret reads curl lets a short option carry its value directly (-dvalue, -Tfile), which the upload rule missed, and the credential rule did not see PowerShell reading a secret environment variable ($env:X_KEY, Get-Item Env:X_TOKEN). On the 68003 distinct real commands of the research data only 2 change, neither in the evaluated samples.
The credential rule saw printenv and PowerShell reads of secret
variables but not the plain shell form ("$X_API_KEY", "${X_TOKEN}").
Only upper-case names count: lower-case ones are mostly loop and
template variables. On the 68003 distinct real commands of the research
data 35 more are flagged, none of them in the labeled samples.
The rules skipped the body of a heredoc written to a file as text, but with an unquoted delimiter the shell runs any $(...) or backtick substitution in it, so an ssh or upload there went unflagged. Such a body is now checked; plain text and quoted heredocs are still skipped. No rule result changes on the 68003 distinct real commands of the research data.
${env:X_API_KEY} reads an environment variable like $env:X_API_KEY but
was not seen by the credential rule. No rule result changes on the
research data, whose only braced reads are ${env:ProgramFiles}.
The body of a heredoc fed to cat was skipped unless a pipe followed the delimiter, but only a target written before the delimiter is tracked: with `cat <<'EOF' > a.sh` the script that was then run went unchecked. Any shell text after the delimiter now keeps the body. No rule result changes on the 68003 distinct real commands of the research data.
A review that runs out of time was recorded as failed like any other error. In a desktop run the decision service slowed to several seconds and both reviews timed out; the notice now says so, so the user can tell a slow service from a sign-in or configuration problem.
…nswers DSH 0.2.2's write, edit and apply_patch request sandbox escalation too, and those requests went straight to the user. They are now reviewed, shown in the form the review was evaluated on: write as a quoted heredoc, edit and apply_patch as a patch, long contents cut. A target-files field gives each file's real path through the Host file system, whether it exists, the change, and whether this session created it. The rules judge each change as the shell command with the same effect on the real path. A change to a file that was there before the session is approved only when the model also judges it requested: its score is at most P(approve), so a small unrequested edit is not let through for being easy to undo. Answers on ask_user_question cards, in time or after the call timed out, count as the user's replies to that card. Writing a login item to ~/Library/LaunchAgents counts as changing software configuration.
… runs - A question card's ID stands for the card only until its first result: a later call reusing the ID cannot pass its output off as an answer. - Only a write that made a new file counts as created by the session; a patch's Add File may replace an existing file and its empty diff basis does not say otherwise. - A file counts as the session's own by its real path only, so a path swapped for a link to an older file is judged as that file. - The review reads the session directory through ensure, as the tools do once approved, so a directory that no longer exists resolves to where the session started in the review as well. - A move is judged as writing its destination, which flags a login item or config file whatever characters its name holds.
A user replying to quoted text usually writes their own words last, and the first 300 characters can hold only the quote. A message over 300 characters now keeps its first 100 and its last 200, with the length left out noted between them. On the 113 evaluated requests that hold a long message, this allowed 78 of the 91 that should be allowed (75 with the first 300 or the last 300 alone) and none of the 22 that should be asked.
…by character - If the session's directory goes while the review waits, the tool will resolve its paths from where the session started, and a relative path with `..` can then lead somewhere other than what was reviewed. The directory is read again before approving; a change goes to the user. - A long user message is cut by character, so an emoji is neither split nor counted twice.
The directory read added in 70f48f6 came after the timed review: a stalled file system could hold the approval past the time limit, a failure there reset the failure count before it was counted, and the preset and the call ID were checked before it rather than after. The read is now the last step of review(), inside the race with the time limit and the failure count, and the checks that follow it are synchronous.
JunieXD
marked this pull request as ready for review
November 13, 2026 05:36
The decision service at times takes 3-6 s for one review; with a 5 s limit, half of the reviews on a slow afternoon went to the user only for timing out.
DSH draws icons only for its four built-in presets, from a fixed table (0.2.2-rc.3 too). A client part adds one for the auto-approval preset on the page: on the permission control while the preset is selected, and in the composer's permission menu, told from other menus by its items, one for each preset in DSH's permission catalog, as a copy of a sibling item's icon slot. It changes nothing of DSH's, and adds no icon where the page is not as expected, DSH draws one of its own, or another preset has the same name.
Without organization sign-in or a decision service key, a review was recorded as a plain failure, and three of them paused review for five minutes, so the trajectory showed neither the cause nor, once signed in, any review for a while. Nothing is asked of the service then: the notice now names the missing sign-in or key, and neither counts towards the pause.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题与修改
“工作区内修改”档里,
bash/pwsh命令和文件工具要写到工作区外时,每次都弹确认卡(#283)。本 PR 按 #283 的方案和后续评测,新增公共插件@eduwork/dsh-auto-approval(packages/dsh-auto-approval):auto-approval)时工作,其他档位不受影响。其他工具策略(如 hooks)要求确认的调用、子智能体会话不审查。bash/pwsh和文件工具write/edit/apply_patch的越界申请先经固定规则和决策模型审查:判断用户会同意的,等同用户点“允许一次”;其余原样弹确认卡。插件不替用户拒绝。-dvalue、-Tfile)算上传;PowerShell 读名字带 KEY/TOKEN/SECRET/PASS 的环境变量、shell 展开这类全大写变量("$X_API_KEY"、${X_TOKEN})算读凭据;写进文件的 heredoc,分隔符不加引号且正文里有$(…)或反引号(shell 会执行它们),或者分隔符后面还有命令文字(如cat <<'EOF' > a.sh,之后可能运行这个文件)时,正文照常检查。写入~/Library/LaunchAgents的登录启动项算改软件配置。write写成cat > 路径 <<'EOF'的 heredoc,edit写成补丁,apply_patch原样,写入内容每段最多保留 4000 字。另加“目标文件”一项,列出每个文件经过链接后的真实路径、是新建还是整个替换、修改、删除、移动,以及是不是本次会话创建的。规则按真实路径套用,相当于对它执行cat >或rm,移动按写入目的路径算。改本次会话之前就有的文件时,分数取两题平均和“用户会同意”的概率中较小的一个:模型认为用户没要求的改动,再小也交给用户。bash scripts/deploy.sh prod),把脚本内容(最多 16000 字)附给规则和模型;命令序列、cd、变量、管道、重定向和 PowerShell 等看不准实际运行哪个文件的,不附脚本,命令照常审查。“本次会话创建的文件”取自write成功新建的结果,按真实路径比对。“工作区”取沙箱实际的写入边界;会话目录按工具审批后取目录的方式读取(目录已不存在时退回会话起始目录),“当前目录”再按 shell 工具的方式解析workdir。审查期间会话目录变了的,交给用户。<system-reminder>包着、写明不是用户发的消息,不带分数和模型给的调用 ID。之前智能体会把自动放行说成“经您批准”,加了记录后不再这样说。/permission列表里所有预设都没有图标,不加。界面部分读不到插件配置,图标只加在 ID 为auto-approval的预设上;有别的预设和它同名时也不加。@eduwork/dsh-decision已发布的./core导出;支持 API Key、机构登录和免鉴权,包内没有机构默认值,默认关闭。docs/PACKAGES*.md加入这个包。已实现:上面这些。计划(不在本 PR):
core.lock.json时,另开 PR 在发行配置里接学校的决策服务(机构登录ecnu,决策模型ecnu-decision),学校版用户登录学校账号后就能选“自动审批”。never,越界申请在审批链之前就被拒绝,插件接不到,需要上游改。验证
node scripts/packages/manage.mjs check dsh-auto-approval(Node 24.22.1,锁定的 DSH 0.2.2-rc.1 Runtime):44 项通过。其中 6 项在真实 Runtime 上跑完整流程:脚本化的模型先用真实文件工具新建文件、再发起越界申请,经真实的 agent loop、工具管线和审批服务,插件放行或交给用户,审查记录写进会话、出现在下一步发给模型的请求里;另覆盖跨步复用的调用 ID、hook 要求确认的调用、会话切换目录后的工作区、文件工具write写到工作区外(审查看到真实路径和文件状态,放行后文件写入),以及提问卡的回答被读成用户的消息。其余用合成数据和本地 HTTP 服务覆盖规则、状态、门槛、重试、超时(含 Host 服务卡住)、暂停、撤回、鉴权(未登录和没有 Key 时写明原因、不计入暂停)、子智能体、并行的重复调用 ID、超长命令、审查期间切换预设或撤回申请、决策服务拒选、用户拒绝后的重试、各种结果的记录(包括带标签字符的调用 ID 不进记录正文)、符号链接和脚本推断(包括各种不该附脚本的 shell 写法)、文件工具的展示和截断、目标文件状态、已有文件的计分、补丁格式错误、复用提问卡调用 ID 冒充回答、会话新建文件的位置被换成指向旧文件的链接、审查期间会话目录消失、最后一次读目录卡住或出错、长消息按字符截断。界面部分的 6 项在 vm 里运行构建产物、配最小的假 DOM,覆盖菜单和按钮补图标、DSH 自己有图标时让位、无关菜单里的同名项和同名预设不加、切换预设、重读目录和卸载。规则改写(部署脚本正则去掉回溯、heredoc 查找改为线性)后,在研究用的 693 条真实申请上与评测版规则逐条对照,判定完全一致。补上面几处写法后,在研究数据的 68003 条不同真实命令上有 37 条改为命中规则:curl 和 PowerShell 两处各 1 条(curl 那条是
cut -d/被当成 curl 的-d的误报),shell 变量 35 条(多是把 token 写进文件或放进Authorization头);都不在带标注的评测样本里,284/419、0/134 不变;heredoc 两处在这批命令上不改变任何判定。文件工具评测:从本机其他编程智能体会话里取真实的工作区外写文件和改文件,按插件的方式整理后交给同一个决策模型(先标注再运行;含他人姓名等个人信息的整条剔除)。应当放行的 141 条放行 109 条;应当询问的 35 条没有一条被放行,其中 3 条是真实申请,32 条是把对已有文件的改动配上另一个任务的上下文构造出来的。“改已有文件取较小值”是看了第一批结果后加的,加之前有 1 条构造的应问被放行(做别的任务时改了桌面草稿里几个字);加上之后另抽一批留出样本只跑一次:应当放行的 33 条放行 25 条,应当询问的 15 条全部交给用户。
长消息截法:评测样本存的用户消息原来都只有前 300 字,从原会话取回全文后,在含长消息的 113 条(应当放行 91、应当询问 22)上比较:只留前 300 字放行 75 条,只留后 300 字 75 条,开头 100 字加结尾 200 字 78 条;三种截法下应当询问的都没有被放行。
node --test tests/package-maintenance.test.mjs tests/ci-changes.test.mjs:19 项通过。pack dsh-auto-approval归档检查通过。scripts/check-source-docs.mjs通过。本机真机验收:本地 QA 应用(0.4.12,DSH 0.2.2-rc.1),把本 PR 最终提交打出的包和预设手动注入产品组合,学校账号登录,决策模型
ecnu-decision。每个任务新开会话并选“自动审批”:点名删除的分数卡在 0.5 门槛上:之前几个版本各跑一次是 0.495、0.478、0.495,都弹了确认卡。用户点名要删的,按评测的标注放行和询问都算对,但结果不稳定。其余任务在之前几个版本上结果相同。同一删除任务在“工作区内修改”下照常弹卡,插件不介入。
加审查记录之后又跑了几次:在主目录新建文件夹三次都自动放行(0.949、0.955、0.948),轨迹里有记录,点开“来源”能看到分数,智能体回复“本次由运行环境自动放行,没有弹窗询问你”;按用户点名删掉三个文件夹,自动放行(0.531),只删了这三个。把插件的 Key 引用临时改成不存在的,审查失败,弹确认卡,点“拒绝”后,轨迹里记为“交给用户确认(审查失败)”,智能体回复申请被拒绝,没有绕过;这种情况现在单独写成“没有决策服务 Key”,只跑了离线测试,没在 QA 应用里重测。12:53–12:59 决策服务变慢(同样的请求直接发过去要 0.9–4.5 s),三次审查都在 5 秒时限超时,按设计弹确认卡,轨迹里记为“交给用户确认(审查失败)”;之后把超时单独写成“审查超时”。13:12–14:39 每 5 分钟把同样的请求直接发一次,18 次里 9 次超过 5 秒(0.4–11.3 s)。最终提交的包在 14:40 复测:主目录新建文件夹自动放行(0.954,1442 ms),“来源”里是“已自动放行(分数 0.954)”,智能体回复“这次重试由自动审批放行”。完成的 18 次审查耗时中位约 310 ms,最慢 1483 ms。
加入文件工具审查等改动后,最终提交在 11-12 夜里复测。当时决策服务很慢(同样的请求直接发过去要 9–31 s),5 秒时限内的审查都超时,按设计弹确认卡。为验证最终版本的判定,临时把 QA 应用的
timeoutMs调到 30000,测完已改回默认:write在主目录新建文件夹并写文件edit改上一步写的文件(对这个会话是已有文件)write新建提问卡那次用插件读会话的函数核对过真实会话记录:审查读到的用户最近消息是原始请求和“选择“要””,用户回复的助手消息是那张提问卡;轨迹“来源”里是“已自动放行(分数 0.962)”。
11-13 下午直接请求决策服务,一次审查 3.2~5.9 秒,默认时限从 5 秒改为 10 秒,其余判定不变。
11-14 在 QA 应用里装上最终提交:权限菜单里“自动审批”有图标,四项文字起点对齐;选中时按钮上有图标,切到其他预设时补的图标拿掉、只剩 DSH 的,切回来又加上;
/permission列表里不加;控制台没有和插件有关的报错。未执行:Windows 原生
pwsh真机验收;桌面装配(本 PR 不改装配和source-receipt.json)。兼容性与后续事项
@eduwork/dsh-auto-approval0.1.0,未发布;发布需单独授权。现有装配、依赖锁和其他插件不变。包里有./client导出和dsh.client声明:界面部分只补图标,不引用其他模块。>=0.2.2-rc.1 <0.3.0-0(新增对dsh-llm的 peer,用它的createUserMessage写审查记录;新增对dsh-fs-patch的 peer,用它的parsePatch解析补丁)。