dsh-foreground-stream
DSH plugin: 前台命令执行实时输出流式展示(对话历史命令卡片实时输出 + 底部流条 + WebUI 设置)
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 18, 2026
- Updated
- Aug 18, 2026
Introduction
dsh-foreground-stream
DeepSeek Harness Web bundle 插件:前台命令执行(bash / pwsh 工具、非后台模式)期间的实时输出流式展示——对话页输入区上方出现一条"命令输出流条",随命令执行实时滚动显示 stdout/stderr,而不是等命令结束后才一次性看到。
- 不修改任何官方工具包:
tool/result内容与执行路径逐字节不变,模型语义零改动。 - 只旁路展示:命令完成后官方工具卡片照常呈现最终结果;本条只负责"执行期间"的增量画面。
- 纯内存缓冲,进程重启即清,不做任何跨会话持久化。
安装
从 GitHub 一键添加(推上仓库后)
包已按可分发结构打包(dependencies 声明了 host 依赖、files/.gitignore 排除了开发期 junction):
# 在任意位置执行(git 需走本地代理 127.0.0.1:7800):
node "G:\Deepseek Harness Desktop\dependencies\dsh\node_modules\@deepseek-ai\dsh\lib\bin.js" plugin --profile web add github:<owner>/<repo>
# 或带版本:add github:<owner>/<repo>#<tag|commit>
dsh plugin 会自动把声明了 dsh.bundle.patch 的包追加进 dsh.profile.bundles(自动 reconcile)。
安装后必做一步(settings 白名单补丁,幂等脚本):
node "G:\Deepseek Harness Desktop\data\dsh\plugins\dsh-foreground-stream\scripts\patch-allowlist.mjs"
# 或:npm run patch:allowlist
原因:Web 客户端读写 settings 命名空间受 harness
dsh-host-apiproxy的WEB_SETTINGS_NAMESPACES硬编码白名单过滤,插件侧无注册逃生通道;本脚本把dsh-foreground-stream幂等加入该白名单。harness 升级/重装会覆盖补丁,需重跑。
然后重启 DSH web 宿主(Host half 改动不热更新;Client 刷新页面即可)。
本地目录安装(开发中)
# 在插件目录(含 package.json 的目录)下执行:
node "G:\Deepseek Harness Desktop\dependencies\dsh\node_modules\@deepseek-ai\dsh\lib\bin.js" plugin --profile web add .
等价于 pnpm 在 profile 目录里
add file:<插件绝对路径>。Windows 路径含空格时 CLI 可能拆参,可改为:在 profile 目录pnpm add "file:<插件绝对路径>",再手动把包名 追加进dsh.profile.bundles(profiles/web/package.json)。
验证安装:
node "G:\Deepseek Harness Desktop\dependencies\dsh\node_modules\@deepseek-ai\dsh\lib\bin.js" --profile web --dump-config | Select-String -Pattern 'dsh-foreground-stream'
应出现:
- id: dsh-foreground-stream
name: dsh-foreground-stream
重启后如何确认效果
- 重启 DSH web(新组合在启动时加载)。
- 让 agent 跑一条长时间前台命令,例如:
- pwsh(Windows):
1..1000 | ForEach-Object { Write-Output "line $_"; Start-Sleep -Milliseconds 200 } - bash(POSIX):
for i in $(seq 1 1000); do echo "line $i"; sleep 0.2; done - 或
ping -t 8.8.8.8(需注意默认 timeout)。
- pwsh(Windows):
- 观察对话页输入框上方:出现"pwsh · <命令>"条,命令输出随执行逐行滚动; 命令结束显示"命令完成 · 退出码 N"约 4 秒后自动收起,官方工具卡片照常呈现最终结果。
设计说明
为什么是"镜像旁路"而不是改官方包
官方链路(已读源码确认):
dsh-tool-bash/pwsh.execute → ctx.shell.run(spec) → 执行器 runArgv
→ ctx.subprocess.spawn(spec) (stdio 恒为 collect 模式,无增量回调)
→ await handle.done → readFrom(0) 一次性汇总
dsh-subprocess 服务契约明确:collect-mode reader 是 offset-based 非消耗式的
(readFrom(fromByte) 返回 { text, nextOffset, lossy, spillPath },独立读者互不消耗)。
因此插件用自己的游标增量读取 handle.collected.stdout/stderr,官方最终 readFrom(0)
完全不受影响——这就是 task-status "镜像补丁保证零竞争" 哲学的同一个缝。
callId 关联(把输出挂到具体工具调用)
- agent-loop 在 execute 之前同步 append
tool/call({turn, step, callId, name, arguments}), 经session/event派发 → 插件记录"当前前台命令"并建立 per-callId 缓冲。 - 真实事件形态(源码 + 实测确认,勿想当然):
tool/call的arguments是模型侧原始 JSON 字符串(agent-loop 直接落block.arguments,只有exec.arguments经过parseArguments)——插件 先JSON.parse再读command/run_in_background。tool/result的message.content是嵌套形态[{ type:'tool-result', content:[{type:'text', text}] }](createToolResultMessage包装)——退出码提取需穿透包装层。
- 前台 shell 调用在 agent-loop 中严格串行(
startCallawait dispatch 直到进程结束), 因此"当前前台命令"是单槽;subprocess.spawn包装器在 spawn 时校验 argv 包含该命令字符串(bash:["bash","-c",cmd];pwsh:["pwsh",...,"-Command",<preamble>cmd]; win32 sandbox 的 runner argv 同样内嵌原 argv)才挂 tap——MCP / skill / worker 等 其他工具的 spawn 因 argv 不匹配而绝不会被误挂。 tool/result(message.source.callId)收尾:running=false、尽力恢复退出码/信号、 清 pending。
数据通道与节流
-
Host 注册只读路由
GET /plugins/dsh-foreground-stream/active?sessionId=...(流条用,返回当前/最近命令 + 全文 +dock配置位)与GET /plugins/dsh-foreground-stream/output?callId=...(历史卡片实时轮询用); 客户端整段替换渲染(tail 效果)。 -
增量 tap 统一 120ms 心跳 drain(不随输出量放大);缓冲 64KB 保尾截断; 完成后保留 120s 供展示,之后清理;缓冲条目上限 100。
-
客户端运行中 400ms / 空闲 1s 轮询;输出区 max-height 180px 滚动 + 自动跟随底部 (用户上翻暂停跟随,回底恢复);ANSI 转义防御性清洗。
-
长命令行展开:命令行超过 64 字符(或含换行)时,流条标题变为可点击 (带 ▾/▴ 指示与
aria-expanded),点击展开完整命令(等宽字体块、 max-height 120px 可滚动),再点收起;调用切换时自动复位。短命令保持单行省略。 -
历史卡片实时输出:运行中的前台命令,其对话历史卡片自动展开终端面板, 面板输出区按 callId 轮询
/plugins/dsh-foreground-stream/output实时更新 (400ms/次),无需底部流条即可在记录里直接看到命令输出;命令结束后停止 轮询,改由官方最终结果呈现。 -
自动展开/收起(
config.autoExpand,默认开):运行中的新命令面板自动展开、 运行完成后自动收起;用户手动展开/收起后不再自动干涉(手动状态优先)。 -
复制命令按钮:历史卡片终端面板内命令行右侧有复制按钮,悬停卡片时显示, 点击一键复制完整命令行(短暂显示 ✓)。
-
面板展开/收起动画(
config.animMs,默认 150ms):终端面板展开播dshfs-panel-in、收起先播dshfs-panel-out再卸载(opacity + 轻微位移); 时长由设置项"面板动画速度"控制(关闭 0 / 快 80 / 标准 150 / 慢 300),animMs: 0时无动画、即时展开/收起。 -
WebUI 设置栏目(命令输出):
通用设置 → 外观 → 命令输出,两个开关—— "显示底部命令流条"(showDock)、"命令面板自动展开/收起"(autoExpand) 与动画速度下拉(animMs)。 值存 Host settings 命名空间dsh-foreground-stream(settings.yaml 持久化), 即时生效(流条/卡片随下一次轮询应用),无需改组合行配置。 注册方式:Host 在 apply 时经ctx.get('settings')直连注册,服务未就绪时 500ms 轮询重试(最多 60s)——不依赖嵌套ctx.inject(真实宿主上该路径 对部分插件不触发)。诊断:GET /plugins/dsh-foreground-stream/settings-state返回{ state, source, registered }。 -
⚠️ harness 依赖:Web 客户端能否读写某命名空间由
dsh-host-apiproxy的WEB_SETTINGS_NAMESPACES硬编码白名单决定(代码注释明说:未列入即settings-not-exposed,即使命名空间已注册)。本插件已把dsh-foreground-stream加入该白名单(dependencies/dsh/node_modules/ @deepseek-ai/dsh-host-apiproxy/lib/index.js)。升级/重装 harness 会覆盖 该修改,需重新打补丁,否则设置栏目会退回"设置服务暂不可用"。 -
组合行配置作为默认层:
cordis.patch.yml的config.showDock/config.autoExpand仍是 base 默认值;UI 里手动开关后以 settings 用户值为准。- id: dsh-foreground-stream config: showDock: false # base 默认层(UI 可再覆盖) autoExpand: false
槽位选择
| 候选槽 | 结论 |
|---|---|
conversation.input.dock(list)✅ 流条 | 官方既有 additive 座位(task-status 同款),对 bash/pwsh/任何 shell 工具统一生效;负责"执行期间"的实时滚动展示 |
tool.call.toolview(keyed)✅ 历史卡片 | 接管 bash + pwsh 两个 key(priority: -1 最低者胜出,替换官方 bash-toolview-sample 与 pwsh 的通用回退行),原版面板形态:标题行 = 命令执行图标(悬停/已展开变箭头)+ 工具名标题(Bash/PowerShell)· 描述摘要 + 状态;点击行展开终端面板,面板内命令行单行省略、点击命令行本身原位展开为完整多行(无第二面板),下方为输出(等宽、滚动)。数据取自 block 的 callView/resultView(card:'terminal'),缺失回退 parseArgs(argsRaw)/resultText |
conversation.details.tool(single) | 接管 = 渲染所有工具的输出详情面板,太重,未采用 |
settings.general.item(list)✅ 设置栏目 | 通用设置页的"命令输出"行(id command-output,order 15,紧随"外观"行):标题 + 两个开关(showDock / autoExpand),经 settingsScope 绑定 Host 命名空间读写 |
两处都只读展示:官方 execute 行为与模型可见的
tool/result内容零改动。
局限
- 只覆盖经
ctx.shell的前台 bash/pwsh 工具调用;run_code内部子调度、终端 (PTY)路径、tool-bash-persistent不经此链路,无流式旁路(不影响其正常执行)。 - 输出缓冲是"插件视角的镜像",与官方最终 tool/result 在超长输出截断边界上可能有 细微差异(各用各的 64KB 上限),但展示用途完全够用。
开发与自检
node --check lib/index.mjs # Host half 语法
node --check lib/client.js # Client half(浏览器 bundle)语法
node scripts/smoke.mjs # Host 逻辑冒烟:补丁/关联/增量/跳过/隔离/dispose
文件
package.json # dsh.bundle.patch + dsh.client(web platform + inject)
cordis.patch.yml # 挂载行:- id: dsh-foreground-stream
lib/index.mjs # Host half:镜像 spawn 补丁 + session/event 关联 + /active 路由
lib/client.js # Client half:dock 流条 + 历史命令卡片(手写 ModuleLoader bundle)
scripts/smoke.mjs # Host 逻辑冒烟:补丁/关联/增量/跳过/隔离/dispose
scripts/smoke-client.mjs # Client 冒烟:bundle 结构/槽注册/展开交互/卡片渲染
README.md
Client half 是手写 plain-JS
window.__ModuleLoader__.load({ id, factory })bundle (与 dsh-message-edit 同款格式),无需 tsdown 构建步骤,提交的产物即源码; 安装时无 build 脚本,pnpm 不会触发 allowBuilds 拦截。