Repository navigation
Conversation
Let an agent run an interactive CLI (REPL, agent CLI, curses app) inside a tmux session on the daemon host and drive it as a first-class background task, while the Web Shell renders a live xterm.js view of that terminal. - core: new `tmux` tool (create/send/capture/list/kill). create registers a `kind: 'shell'` task carrying `terminal` metadata on a dedicated `qwen-serve` tmux socket; pipe-pane fills the task output file and a pane-dead poller settles the entry with the real exit code. - acp-bridge / sdk / serve tasks snapshot: additive optional `terminal` field on the shell task status; no task-kind widening. - daemon: `/terminal?sessionId=&taskId=` WebSocket (ExtraWsRoute) that validates the task against the live registry, then attaches via node-pty `tmux attach-session` and streams pty bytes both ways (4-per-session cap, 60-minute lifetime, 16 MB backpressure bound). - web-shell: `useTerminalSocket` hook, xterm.js `TerminalPanel`, a `terminal` ArtifactPanel tab, and an "Open terminal" button on running terminal shell tasks. Design: docs/design/web-shell-tmux-terminal.md
36989ed to
e27fc56
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
|
@qwen-code /takeover |
|
🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
- cli i18n (zh/zh-TW): add the `Tmux` tool display-name translation. - serve process-env-guard: allowlist the terminal attach pty's process.env base with a documented reason. - web-shell toolFormatting: add the `tmux` display-name entry. - ArtifactPanel: lazy-load TerminalPanel so importing the panel does not eagerly pull @XTerm into unrelated test module graphs (self is not defined under non-jsdom environments).
|
🤖 AutoFix ran out of time before finishing (timeout (7200000ms)) (attempt 1/100) — it will retry on the next scan. What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31062402343 🧠 Handled by Qwen Code · model/模型 |
Web Shell session showing the agent-created terminal task, and the same tmux session attached and running Claude Code live.
|
🤖 Could not produce a passing fix for this feedback (round 2/100). This item now needs a human; the loop stays engaged and still picks up new feedback and base conflicts, but will not retry this item on its own. What I found before stopping: See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31062442060 🧠 Handled by Qwen Code · model/模型 |
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🤖 AutoFix ran out of time before finishing (timeout (1080000ms)) (attempt 3/100) — it will retry on the next scan.
See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31077407265 🧠 Handled by Qwen Code · model/模型 |
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x), Post Coverage Comment (ubuntu-latest, 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x), Post Coverage Comment (ubuntu-latest, 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🤖 AutoFix stopped: this counting window now contains 3 time-budget exhaustions (pushed rounds in between included; this round itself may have failed differently). That is 3 full agent runs that pushed nothing. A human should split or reduce the PR (or raise the agent time budget AND its step backstop together), then comment
See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31092422661 🧠 Handled by Qwen Code · model/模型 |
|
⏸️ Takeover paused: this PR reached its round cap (100/100). Comment 中文说明⏸️ 托管已暂停:本 PR 达到轮次上限(100/100)。评论 |
|
@qwen-code /resolve |
|
Qwen Code resolved the merge conflicts and pushed the branch update. Merge summary — PR #8613 ← mainRoot cause. Main's v0.21.7 release commit ( Textual or semantic. The conflict was a single version string, resolved by manifest consistency to "integrations/external-context": {
"name": "@qwen-code/external-context",
"version": "0.20.1",Verified: the release swept 22 lockfile entries; the merge has 21 at 0.21.7 + this one at 0.20.1, zero at 0.21.6; JSON parses; every entry matches its workspace Load-bearing. (1) This lockfile line must equal Could not verify. No build/typecheck/tests run (out of scope). Main's web-shell changes — workspace-scoped artifact actions (#8510), stale-recap discard (#8573), Markdown AST throttling (#7904) — land in the same 中文说明根因:main 的 v0.21.7 发布提交( 文本还是语义:冲突仅为版本号,按 manifest 一致性取 关键点:该 lockfile 行必须与对应 package.json 版本一致;web-shell 下嵌套的 未能验证:未运行构建/测试;main 侧 #8510、#8573、#7904 与本 PR 终端功能落在同一批组件逻辑中,相关无冲突测试只能由 PR 自身 CI 覆盖。 |
|
🔄 Takeover re-armed: the round counter starts a fresh window (previous rounds no longer count toward the cap); management continues. 中文说明🔄 已重新武装:轮次计数开启新窗口(此前轮次不再计入上限),托管继续。 |
Security/correctness: - apply Shell(...) permission rules to tmux via a rule-engine bridge; persistable per-action rules use tmux(action:<action>) param matchers - deny tmux in CLI non-interactive mode unless explicitly allowed - validate create cwd (absolute + workspace-contained), clamp cols/rows, reject empty send keys - send-keys argv safety (-- separator and bare-; escaping) - treat an empty pane_dead_status as failure, never exit 0 - settle poller: probe the tracked pane, tolerate transient tmux failures, reclaim sessions that exit naturally - await kill in executeKill; clean up the output file on failed create - daemon terminal endpoint: byte-safe close reasons, race-free socket accounting, runtime env for the attach pty, inbound frame bound, non-zero attach-exit surfacing, drain-gate bypass for multi-workspace Web Shell: - terminal panel height chain, focus, resize dedupe, overlay pointer-events passthrough, reconnect buffer reset, connect timeout, hello-send guard, xterm externalized from the lib bundle, /terminal dev proxy entry, tmux task discovery, Open terminal button gating Also regenerates the lockfile with npm 10 (restores peer metadata), removes the PII screenshot, fixes design-doc inaccuracies, strips whole ANSI sequences from shell output tails, and adds focused coverage for all of the above.
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Autofix address-review summary — PR #8613 (web-shell tmux terminal)Processed 126 inline findings across review rounds 1–4 (plus the two wenshao verification findings). 116 resolved in code ( Critical findings — all fixed and test-pinned
Suggestions addressed (highlights)
Declined / deferred (replies in
|
| 方面 | 修复 |
|---|---|
| 权限绕过(R1-5/R3) | 在 toolMatchesRuleToolName 中新增 run_shell_command → tmux 桥接,并添加 Tmux/TmuxTool 别名;Shell deny/allow 规则现在会评估 tmux create 的命令(已有端到端回归测试) |
| 无法匹配的动作级规则(R1-7) | 确认框持久化为 tmux(action:<action>) — 解析为对调用参数求值的 toolParamMatcher(已测试) |
| AUTO 模式缺口(R1-52) | tmux 加入 autoMode 的 SHELL_LIKE_TOOL_NAMES(确定性破坏性命令拦截生效) |
| 无头模式拒绝(R1-11/R3) | PLAN/DEFAULT、AUTO、AUTO_EDIT 的 denyUnlessAllowed 均加入 tmux;defaultExcludes 与显式放行测试同步更新 |
| 注册门控(R1-33/R1-34) | 注册保持走 registerLazy;新增测试固化受门控的注册路径与 CORE_TOOLS 条目 |
| cwd 校验(R1-8) | create 要求 cwd 为工作区内绝对路径(沿用 shell 工具约定);schema 描述同步更新 |
| send-keys argv 安全(R3-8) | 加入 -- 选项终止符;裸 ; 转义(tmux argv 层命令分隔符)— 探测出的两类载荷均有测试 |
| 空 dead-status(R1-9) | tmux 未给出 pane_dead_status 时解析为 undefined;按失败结算,绝不按 exit 0 处理;TmuxBackend 同步适配(?? 1) |
| 会话泄漏(R1-10/R3) | 自然退出的 pane 结算后由轮询器回收会话;kill 现在 await tmuxKillSession 并在失败时报错(R4-3) |
| 瞬时探测失败即结算(R4-7) | 轮询器改为探测被跟踪的 pane(同时修复活动窗口盲区 R1-41),移除冗余 has-session 启动(R2-10),仅在连续 3 次探测失败后结算(abort 时立即结算) |
| win32 守卫(R1-6/R3-4) | 守卫移入 validateToolParamValues;执行类套件用 describe.skipIf(process.platform === 'win32') 包裹;守卫分支本身用平台 stub 测试 |
| attach 环境变量(R1-2/R3-2) | spawnAttach 在所属 runtime 的 effective env 下运行(沿用 voice 路由模式);server.ts 接入新的 resolveAttachEnv 缝隙 |
| close reason 字节截断(R1-3/R3-3) | truncateCloseReason 按 120 UTF-8 字节封顶且不切断多字节字符;多字节测试固化 |
| 连接计数(R1-4/R3-1) | 槽位在任何 await 之前同步预留;cleanup() 幂等;spawn 后关闭直接杀掉新 attach;新增上限一致性测试组(拒绝、错误、重复关闭、4 连接上限) |
| capture maxBuffer(R4-11) | capture 路径使用 16 MiB maxBuffer(默认 1 MiB 会溢出) |
| 入站帧上限(R4-17) | 入站帧上限 1 MB;超帧以 1009 关闭且不写入 pty(已测试) |
| 输出尾部 ANSI(rc:3729386568) | readOutputTail 剥离完整 ANSI 序列(沿用 monitor/shell 的 strip-ansi 模式),而非仅控制字节(已测试) |
| 任务发现(R4-5) | isBackgroundTaskToolCall 将 tmux 与 monitor 并列分类(无 is_background 参数);App.test.tsx 固化 |
| 高度链(R3-19) | 新增 .bodyTerminal flex 列变体并应用于终端标签页;xterm 现在能填满面板 |
| lockfile(R3-14) | 用 npm 10.9.8(runner 的 npm)重新生成:恢复 11 个平台二进制条目的 "peer": true 元数据;已验证 npm install 现在能正确装出 @xterm/[email protected] + @xterm/[email protected](此前缺失/版本无效) |
| lib 产物(R1-12/R3-7) | 此前已由 inlineDynamicImports 修复;本轮进一步将 @xterm/* 外部化(约 400 KiB 移出产物),build-artifact.test.ts 仍然全绿 |
| 开发代理(R1-14/R3) | vite.config.ts 增加 '/terminal': { ...daemonProxy, ws: true };vite-config.test.ts 固化 |
已处理的 Suggestion(要点)
- 守护进程端点:非零 attach 退出以 error+1011 呈现而非干净的 1000;hello/resize 尺寸封顶 500;新增背压测试;
resolveRuntime包入 try/catch(wenshao 发现);/terminal增加 drain 门控旁路标志(主 runtime drain 期间多工作空间 attach 仍可用;R3-24);协议文档更正(hello 在ready之后发送,R4-16)。 - Web Shell 客户端:握手停滞 10 秒连接超时(R4-13);hello 发送加守卫(R3-26);同一终端重连保留缓冲按键、目标变化时重置(R2-14/R3-20);
term.focus()(R4-8);resize 去重(R2-13);overlaypointer-events穿透(R2-16);本地化错误标题(R2-18);重连时重置 xterm 缓冲区(R4-22);接入onBinary;移除无用的fitAddonRef;“打开终端”按钮受 handler 门控并改用中性样式(rc:614/622)。 - 测试:新增约 70 个聚焦测试,覆盖
tmux.test.ts(42)、terminal-ws.test.ts(23)、useTerminalSocket.test.tsx(22)、TasksStatusMessage.test.tsx(13)、权限(桥接/别名/匹配器/CORE_TOOLS)、CLI 配置(拒绝列表)、tasksSnapshot(terminal 透传)、backgroundShellRegistry(ANSI 尾部)、App(任务 key)、i18n(zh-TW 门控)、vite-config(代理)。 - 文档/设计:Files Affected 表按真实 diff 重写(R4-1);capture 行更正(
capture-pane -p、不含转义码、2000 行上限 — R4-2);孤儿清理改为定向kill-session并警告不要用kill-server(R1-15);SDK JSDoc 路由修正(rc:601);删除含个人信息的截图terminal-claude.png及其图注(R2-4 — 该图暴露个人账号名/邮箱;无头环境无法重新截取干净截图,删除是安全修法)。
婉拒 / 暂缓(回复见 comment-replies.json)
- R4-21(终端阻塞
/clear)— 已升级:生命周期门控语义属产品决策(豁免终端 vs 保持阻塞 vs 维持现状);已附建议,线程保持打开。 - R2-11/R2-17(切换标签页 / drawer-停靠互换会清掉终端):需要把终端生命周期提升到
ArtifactPanel实例之上 — 架构性后续工作;期间重连可优雅降级。 - R3-18:已移除 has-session 探测(spawn 减半);间隔退避暂缓(结算时延权衡)。
- R3-25(spawn 前定尺寸):hello-resize 在一个往返内即校正;握手重设计留待后续。
- R1-18(应用级接线测试):仓库惯例是 handler 层测试(voice 亦然);接线与 voice 1:1 镜像。
- R1-26:背压一半已固化;60 分钟定时器测试暂缓(假定时器与套件冲刷方式冲突)。
- lazy 导入的 ErrorBoundary:单文件产物下不存在可捕获的失败模式(附证据婉拒)。
- voice/terminal 常量去重:跨文件重构、无行为变化 — 留待后续。
- TerminalPanel 组件测试:需要新的 xterm/ResizeObserver jsdom 测试底座 — 留待后续;hook 层套件已覆盖其逻辑。
观察到的既有失败(与本 PR 无关,已通过 stash 在干净 HEAD 上验证):packages/cli/src/config/config.test.ts 中 5 个 sandbox 镜像/sandbox 会话 ID 测试在本 runner 环境下失败。
验证
以下命令均在最终提交后实际执行:
npm run build(根目录,全部包)— 通过npm run typecheck— core / cli / web-shell / acp-bridge / sdk-typescript — 通过(按包执行)npm run lint(全仓库)— 通过(exit 0)npx prettier --check(所有变更文件)— 通过npm run check-i18n(cli)— 通过- Core 聚焦 vitest:
tmux.test.ts、tmux-commands.test.ts、TmuxBackend.test.ts、src/permissions/、config.test.ts、backgroundShellRegistry.test.ts— 1412 通过 - CLI 聚焦 vitest:
src/serve/terminal/、src/serve/acp-http/、process-env-guard.test.ts、tasksSnapshot.test.ts、src/i18n/index.test.ts— 476 通过;src/config/config.test.ts— 328 通过、5 个既有 sandbox 环境失败(已在干净 HEAD 验证) - Web-shell 全量 vitest — 3096 通过(178 个文件)
- Web-shell lib + app 构建(
vite build、vite.lib.config.ts、tsc -p tsconfig.lib.json)— 通过;build-artifact.test.ts— 11 通过 npm run bundle后的集成测试:未运行 — 本次改动的行为已由上述单元/handler 套件及 CI 自身的集成任务覆盖;未改动仅经打包 CLI 才触发的路径。
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。
Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。
🧠 Handled by Qwen Code · model/模型 qwen3.8-max
…erminal # Conflicts: # package-lock.json
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Autofix review round — PR #8613Feedback triage
Base-conflict resolution (
|
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
|
🤖 Addressed the latest review feedback (round 3/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 3/100 轮)。改动内容与我反驳保留之处如下: Address Review Summary — PR #8613Feedback received: No PR reviews, no inline review comments, and no issue-level comments were present this round. The only actionable item was the failed Test (ubuntu-latest, Node 22.x) check reported for this PR (the Diagnosis (evidence-based, reproduced locally)The failing Test check was reproduced with the repository's own
Change made
No other code was changed; the terminal feature code itself needed no modification. Dispositions
VerificationCommands actually run on this branch (clean CI-equivalent env where relevant):
Commit: 中文说明处理评审总结 — PR #8613收到的反馈: 本轮没有 PR 评审、没有行内评审意见,也没有 issue 层面的评论。唯一需要处理的是本 PR 上报的 Test (ubuntu-latest, Node 22.x) 检查失败( 诊断(基于证据,已在本地复现)使用仓库自身的
所做修改
未改动其他代码;terminal 功能本身的代码无需修改。 各项处理结论
验证以下是在本分支上实际执行的命令(相关处使用与 CI 等价的干净环境):
提交: Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
|
🤖 AutoFix ran out of time before finishing (timeout (1080000ms)) (attempt 4/100) — it will retry on the next scan.
See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31516789251 🧠 Handled by Qwen Code · model/模型 |
Resolves the nine Critical review findings on the tmux terminal feature. Pane targeting (R5-1): store the created pane id in the shell task's terminal metadata and target it directly in send/capture, so the tool no longer resolves by session name and drives the wrong pane once the session's active window changes. Permission integrity (R5-3, R5-4, R5-5, R5-8): add tmux to the AUTO-mode dangerous-rule strip; scope persisted allow rules to the payload (create -> command-scoped Bash rules, send -> no payload-blind grant); and extend shell-style rule treatment to tmux's command-bearing calls so Write/Edit deny rules, compound-command recursion, and cwd-relative matching can no longer be bypassed through `tmux create`. Plan mode (R5-6): include tmux in the plan-mode shell policy so create commands are AST-classified and send/kill are blocked even with a persisted allow rule; map the tmux `cwd` parameter for working-directory attribution. Dev proxy (R5-2): qualify the `/terminal` dev-server proxy key so it no longer shadows the client's own `terminal/*` source modules. Tests (R5-7, R5-9): pin the platform for the platform-independent tmux suite so it runs on Windows CI, and transition the terminal socket to a closed state (dropping buffered input) when its connection is disabled. Also refreshes the design doc to match the shipped rule syntax, parameter names, and plan-mode behavior.
|
🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下: Autofix address-review summary — PR #8613 (round 5)Scoped this round to the blocking subset: the nine Critical findings were Critical findings — all fixed
Suggestion resolved this round
Suggestions deferred to a follow-upR5-16, R5-17, R5-18, R5-19, R5-20, R5-21, R5-22, R5-23, R5-24, R5-25, R5-26, Review-body "Not reviewed" items
Conflict notes
VerificationCommands actually run this round (all on the touched packages):
中文说明Autofix address-review 摘要 — PR #8613(第 5 轮)本轮将范围限定为阻断性子集:九个 Critical 发现均已对照代码核实并修复, Critical 发现 — 全部修复
本轮处理的 Suggestion
推迟到后续处理的 SuggestionR5-16、R5-17、R5-18、R5-19、R5-20、R5-21、R5-22、R5-23、R5-24、R5-25、R5-26、 评审正文中的"未审查"项
冲突说明
验证本轮实际运行的命令(均针对改动涉及的包):
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
|
🤖 AutoFix ran out of time before finishing (timeout (7200000ms)) (attempt 6/100) — it will retry on the next scan.
See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31554581505 🧠 Handled by Qwen Code · model/模型 |
|
🤖 AutoFix updated a stale base — the fix did not pass verification, but this PR was behind What I found before stopping: Autofix round stopped: environment blocker (stale node_modules), not a code defectBlocker
Exact commands and output
That is the ONLY error reported by typecheck across all workspaces. Evidence this is an install/environment problem, not the diff
Run log: https://github.com/QwenLM/qwen-code/actions/runs/31562666122 🧠 Handled by Qwen Code · model/模型 |
|
🤖 AutoFix stopped: this counting window now contains 3 time-budget exhaustions (pushed rounds in between included; this round itself may have failed differently). That is 3 full agent runs that pushed nothing. A human should split or reduce the PR (or raise the agent time budget AND its step backstop together), then comment
See the Qwen Autofix agent step logs for model/tool output. Run log: https://github.com/QwenLM/qwen-code/actions/runs/31568696360 🧠 Handled by Qwen Code · model/模型 |
|
⏸️ Takeover paused: this PR reached its round cap (100/100). Comment 中文说明⏸️ 托管已暂停:本 PR 达到轮次上限(100/100)。评论 |
QwenLM#8961) * fix(ci): make autofix verification gates hermetic to runner git config A leaked global exec knob on the persistent pool (run 31516789251: diff.external=global-driver in the runner user's ~/.gitconfig) failed four per-hunk probe tests in packages/cli on QwenLM#8613. The rejection was charged to the round (package tests are A/B-exempt), which burned the 18-minute repair on a failure no repair can reach and ended the round as a timeout — attempt 4 of the failure window, with nothing pushed. Three layers against that class: - Both verification gates (the review script and the issue-fix inline twin) now export a per-run throwaway GIT_CONFIG_GLOBAL (seeded with the workspace safe.directory) and GIT_CONFIG_SYSTEM=/dev/null before their first git command, so every check they spawn — vitest fixture repos included — is hermetic to the host, and a branch-authored `git config --global` dies with the run instead of poisoning the next one. - The sanitize step (all three byte-identical copies) now also scrubs the runner USER's global config — denylist of the command-execution families only, so infra-owned routing/credential keys survive. This self-heals the live pollution on the affected runner on its next job and removes (not merely bypasses) a planted global core.hooksPath. - test-efficacy.integration.test.ts gets the same GIT_CONFIG_GLOBAL / HOME isolation as git.integration.test.ts: the code under test pins --no-ext-diff, but the test scaffolding's plain `git diff` did not, so a hostile user git config could fail the suite anywhere. Contract tests pin the gate redirects (before the first git command, truncated per run) and functionally run the extracted scrub pipeline against a poisoned fixture config, asserting the kept/removed sets. * fix(ci): widen the config scrub and re-sanitize before PAT-bearing steps Address the QwenLM#8961 review findings (2 Critical, 8 Suggestions across two reviews), all probe-verified by the reviewers: - Denylist regex: subsection slots are .+ (git subsection names may contain dots — diff."a.b".command flattened past [^.]+), and the missing exec families are added: gpg.(*.)program, pager.*, interactive.diffFilter, difftool./mergetool., remote.*.uploadpack/ receivepack. The functional fixture now covers every alternation plus dotted subsections, non-exec/corrupt/missing-config arms pin the two load-bearing '|| true' guards. - The global scrub moved above the .git early-exit: host hygiene owes nothing to the workspace existing. - New resanitize-git-config.sh (staged from the trusted base) re-runs the local allowlist sweep and the global denylist scrub at the top of both PAT-bearing git steps — the gates run branch test code on the host after the job-start sanitize, and the env redirect is not a filesystem boundary. Contract tests pin script/step lists equal, the staging in both jobs, the call-before-credential ordering, and run the script functionally against planted local+global keys. - All three one-shot credential helpers lead with -c credential.helper= (empty resets the helper list; the first helper to answer wins, so a planted one must never run first). Count-pinned in the contract test. - comment-status.integration.test.ts gets the same git-config isolation as its siblings; test-efficacy gains an isolation tripwire test that goes red if the redirect is removed, instead of only on hostile hosts. - Comment fixes: the copies are cross-referenced as contract-test-pinned, and the system-config bypass is documented in both gates. * fix(ci): close the XDG/env/transport bypasses around the config scrub Address the QwenLM#8961 round-2 review findings (5 Critical + 8 Suggestions, probe-verified by the reviewers; the XDG listing gap independently reproduced on git 2.55): - The global scrub sweeps BOTH files of the global scope: with ~/.gitconfig and $XDG_CONFIG_HOME/git/config both present, `git config --global` lists/unsets only the former, so keys planted in the XDG file survived every copy. The scrub is now a loop that redirects GIT_CONFIG_GLOBAL at each file in turn. - Denylist adds url.*.insteadOf/pushInsteadOf (transport rewrite of the PAT push/fetch; rest of url.* stays) and http.*.sslVerify/sslCAInfo (turns a kept http.proxy into a TLS-terminating interceptor); the three PAT helper chains lead with -c http.sslVerify=true. - The staged resanitize script's provenance holds at cp time only — RUNNER_TEMP is writable by the branch code that runs in between — so the staging steps record its sha256 in GITHUB_OUTPUT and the PAT steps verify before executing. - Both gates and both PAT steps export GIT_CONFIG_COUNT=0: GITHUB_ENV-injected GIT_CONFIG_KEY/VALUE entries apply at command-line precedence and outrank every file-level guard. - Gates emit a ::notice when /etc/gitconfig exists (bypassed by the redirect — replicate needed settings via per-job env). - Tests: the scrub's functional harness drives HOME/XDG fixtures and covers the new families; the resanitize run plants worktree-scoped config (deleting the rm -f line previously stayed green); the gate redirect block is executed against a hostile HOME and an env-planted GIT_CONFIG_* key; the isolation tripwire pins the NOSYSTEM leg and probes system-scope leakage. - The process-env git isolation pattern is extracted into isolateHostGitConfig() in review/lib/test-utils.ts and adopted by all five suites that duplicated it; comment-status gains the same tripwire. * fix(ci): take PAT git steps off host scopes and close the env channels Address the QwenLM#8961 round-3 review (5 Critical + 6 Suggestions, probe-verified by the reviewer): - Both PAT-bearing steps now run fully hermetic, same shape as the gates: a per-run throwaway GIT_CONFIG_GLOBAL + GIT_CONFIG_SYSTEM= /dev/null, so a concurrent job rewriting the shared ~/.gitconfig in the sweep->push window (max-parallel, one HOME across ~27 runner registrations) can no longer steer the push, and a URL-scoped sslVerify=false there can no longer override the -c pin. Both steps and both gates also strip the git ENV channels that outrank file config: GIT_CONFIG_PARAMETERS, GIT_SSL_NO_VERIFY/CAINFO, GIT_PROXY_COMMAND, GIT_EXEC_PATH, GIT_DIR/WORK_TREE, GIT_ASKPASS, GIT_SSH/_COMMAND, plus GIT_CONFIG_COUNT=0. - The push-race salvage merge runs -c commit.gpgsign=false: a global commit.gpgsign=true with no key would exit 128 and be misread as a content conflict, discarding a verified round (R2-10). - The maintainer-fork fetch, the one PAT-bearing network site the round-2 rollout skipped, leads with -c http.sslVerify=true -c credential.helper= (anonymous; public fork heads need no auth, so it fails closed on a 401 instead of feeding a planted helper the PAT). - Denylist widens protocol.ext.allow to protocol.(ext.)?allow (the top-level fallback policy arms ext:: too) in all four copies. - Tests: the two PAT hermetic blocks and the two gate blocks are pinned equal; the sha256 verify line is pinned verbatim and asserted to carry no bypass; the resanitize fixture plants a live XDG exec key (drops of the loop's XDG leg now fail); the gate redirect functional exec adds the env-channel unsets; diff-plan adopts isolateHostGitConfig (sixth suite) keeping its GIT_TERMINAL_PROMPT delta; comment-status tripwire gains the GIT_CONFIG_GLOBAL assertion. * fix(ci): pin PATH, seal repo-redirect and env channels, harden all PAT sites Address the QwenLM#8961 round-4 review (6 Critical + suggestions, probe-verified by the reviewer): - PATH is pinned to a value the stage step records before any branch code runs, and LD_PRELOAD/LD_AUDIT/LD_LIBRARY_PATH are dropped, at the top of every PAT step and both gate steps — a $GITHUB_ENV-planted PATH or preload would otherwise swap the git/sha256sum/bash the digest gate itself runs on. - The Prepare step (PAT-bearing, previously unhardened) now takes the same hermetic preamble as the push steps; all three PAT preambles are pinned identical by the contract test. - The throwaway global config is created with mktemp, not a fixed literal path a same-user watcher could re-plant into after the seed. - The env-strip list gains GIT_ALLOW_PROTOCOL (env twin of protocol.allow), GIT_COMMON_DIR / GIT_OBJECT_DIRECTORY / GIT_ALTERNATE_OBJECT_DIRECTORIES / GIT_SHALLOW_FILE (repo-redirect twins), across all PAT and gate copies; the salvage/fork fetches carry -c fetch.recurseSubmodules=false -c protocol.ext.allow=never so a planted submodule cannot execute an ext:: URL with the PAT. - resanitize removes .git/commondir and .git/shallow (the file twins of GIT_COMMON_DIR/GIT_SHALLOW_FILE), and Push-and-report refuses to push a HEAD that is not the gate's recorded verified_head — closing the repo-redirect path that pushed attacker content. - The gate runner (run-autofix-review-verification.sh) is now digest- verified before both gate passes, like resanitize already was: the branch runs its own build/test between the passes, so an unverified copy would let it define its own verdict. - Contract tests pin every new surface: the three identical PAT preambles, the full unset var set, the mktemp path, the trusted-PATH wiring, the two gate-runner digest checks, the recurse/protocol pins, the HEAD==verified_head guard, and the commondir/shallow removal. * fix(ci): pin gh env channels, harmonize allowlist subsection slots Round-4 follow-ups: - Pin GH_HOST=github.com and unset GH_TOKEN/GH_ENTERPRISE_TOKEN/ GH_CONFIG_DIR before the first gh call in all three PAT steps, so a $GITHUB_ENV-planted GH_HOST cannot spoof the identity check and a planted GH_TOKEN cannot outrank the inline one. - Harmonize the local allowlist's remote/submodule subsection slots to .+ (matching the denylist comment and preventing a dotted-name remote from silently losing its url/fetch on every resanitize). * fix(ci): pin gh config dir and push the exact verified object Round-5 closable findings before landing: - R5-7: pin GH_CONFIG_DIR to a fresh mktemp -d instead of unsetting it, so PAT-bearing gh calls no longer fall back to the attacker-writable ~/.config/gh (whose config.yml can carry http_unix_socket and other transport reroutes) on the shared HOME. - R5-8: push the exact verified commit object (PUSH_SHA:refs/heads/...), not symbolic HEAD which the push would re-resolve — closing the check-then-use race the verified-HEAD guard was added to close. PUSH_SHA is pinned to VERIFIED_HEAD under the guard and re-pinned to the merge result after each salvage merge. The remaining round-5 Criticals (BASH_ENV/BASH_FUNC_* and LD_PRELOAD executing at step-shell startup before any unset runs; GITHUB_OUTPUT writable by gate-run branch code) are not closable from inside a Actions step — they require runner-level isolation and are tracked as a follow-up.
|
🔓 Takeover auto-released: the autofix loop paused on this PR 3 day(s) ago (🤖 AutoFix stopped: this counting window now contains 3 time-budget exhaustions (pushed rounds in between included; this ) and no re-arm followed, so the 中文说明🔓 已自动释放接管:autofix 循环在 3 天前暂停于此 PR(🤖 AutoFix stopped: this counting window now contains 3 time-budget exhaustions (pushed rounds in between included; this ),此后无人重新武装,现移除 |
What this PR does
This PR lets an agent run an interactive CLI — a REPL, another agent CLI, or a curses/TUI app — inside a tmux session on the daemon host and drive it as a first-class background task, while the Web Shell shows a live, interactive terminal view of that session.
It adds a new
tmuxcore tool withcreate/send/capture/list/killactions.createstarts a detached tmux session on a dedicatedqwen-servesocket and registers it as a normalkind: 'shell'background task carrying a smallterminalmetadata blob; the task appears in the task list, is cancellable viatask_stopand the daemon cancel route, and is cleaned up on shutdown.pipe-panekeeps filling the task output file and a pane-dead poller settles the entry with the real exit code.On the daemon, a new
/terminal?sessionId=&taskId=WebSocket (registered through the existingextraWsRoutesmechanism so it inherits the loopback / host-allowlist / CSRF / bearer checks) validates the requested task against the live registry and then attaches vianode-ptyrunningtmux attach-session, streaming pty bytes in both directions. In the Web Shell, a newuseTerminalSockethook and an xterm.jsTerminalPanelrender the live terminal as an ArtifactPanel tab, with an "Open terminal" button on running terminal shell tasks.Why it's needed
Today the agent can already drive an interactive CLI by hand-rolling
tmuxcommands through the shell tool, but that pattern has no lifecycle integration (the session is invisible to the task system, can't be cancelled from the UI, and isn't cleaned up), no live view (the user only seescapture-panetext snapshots), no approval granularity, and fragile per-run orchestration. This makes interactive-CLI sub-agents a supported, observable, user-attachable capability. The design is CLI-agnostic;claudeis just the motivating example.Design:
docs/design/web-shell-tmux-terminal.md.Real scenario
The agent is asked, in the Web Shell, to start an interactive CLI; it calls the
tmuxtool, which registers a terminal task. The user can then attach and watch the session live.Web Shell session after the agent created the terminal (
bg_25154e98):The same tmux session attached (
tmux -L qwen-serve attach -t qsh-bg_25154e98), running Claude Code live:Reviewer Test Plan
How to verify
The model-facing path (headless): build (
npm run build && npm run bundle), then ask a session to create a terminal running an interactive script and drive it.Confirm the task is registered as a
shelltask withterminalmetadata (task_list),send/captureround-trip,killremoves theqsh-*tmux session, and graceful exit kills it on shutdown.The live-terminal path: start
node dist/cli.js serve --port 7899 --token t, create a terminal task in a session, then connect a WebSocket tows://127.0.0.1:7899/terminal?sessionId=<id>&taskId=<bg_xxx>(subprotocolsqwen-ws,qwen-bearer.<base64url(t)>) — expectready, banner bytes, anECHOreply to a binary keystroke, and socket close after cancelling the task. In the browser Web Shell, the shell task detail shows "Open terminal" and the tab renders the live REPL.Automated coverage is in
.qwen/e2e-tests/web-shell-tmux-terminal.md(Groups A–E) with a scripted harness at.qwen/e2e-tests/tmux-terminal/ws-check.mjs.Unit tests:
packages/coretmux tool (26) + tmux-commands (11) + backgroundShellRegistry (57);packages/cliterminal-ws (8) + tasksSnapshot (6);packages/web-shelluseTerminalSocket (9) + ArtifactPanel (24) + TasksStatusMessage (10). All green.npm run build,npm run typecheck,npm run bundle, and scoped ESLint all pass.Evidence (Before & After)
Baseline dry-run confirmed the gap before implementation: with the global CLI, the model had no
tmuxtool and improvised through the shell tool on the default tmux socket (noqwen-servesocket, noqsh-*naming, no task registration); the daemon returned 404 for the terminal route. After implementation the dedicated tool, task metadata, and WS attach all work as above.Tested on
Environment (optional)
Local
node dist/cli.js(build + bundle). tmux 3.7b. Browser terminal verified against a local daemon on the Web Shell SPA.Risk & Scope
terminalfield); the task kind stays'shell', so existing task consumers are untouched.Linked Issues
References the design doc
docs/design/web-shell-tmux-terminal.md. No tracking issue.中文说明
本 PR 做了什么
让 agent 能在 daemon 宿主机的 tmux 会话里运行交互式 CLI(REPL、其它 agent CLI、curses/TUI 应用),并把它作为一等后台任务来驱动,同时 Web Shell 提供该会话的实时、可交互终端视图。
新增 core 的
tmux工具,含create/send/capture/list/kill。create在专用qwen-servesocket 上启动 detached tmux 会话,并注册为普通kind: 'shell'后台任务(附带一个小的terminal元数据);该任务会出现在任务列表、可通过task_stop与 daemon 取消路由取消、并在关闭时清理。pipe-pane持续写入任务输出文件,pane-dead 轮询用真实退出码结算任务。daemon 侧新增
/terminal?sessionId=&taskId=WebSocket(经现有extraWsRoutes注册,继承 loopback/host-allowlist/CSRF/bearer 校验),先对活跃注册表校验任务,再用node-pty执行tmux attach-session双向流式传输 pty 字节。Web Shell 侧新增useTerminalSockethook 与 xterm.jsTerminalPanel,把实时终端渲染为 ArtifactPanel 的一个 tab,并在运行中的终端 shell 任务上提供「打开终端」按钮。为什么需要
目前 agent 已能通过 shell 工具手工拼 tmux 命令来驱动交互式 CLI,但该模式没有生命周期集成(会话对任务系统不可见、无法从 UI 取消、关闭时不清理)、没有实时视图(用户只能看到
capture-pane文本快照)、没有审批粒度、且每次运行都要脆弱地重新编排。本 PR 让「交互式 CLI 子代理」成为受支持、可观测、用户可接入的能力。设计与具体 CLI 无关,claude只是最初的动机示例。设计文档:
docs/design/web-shell-tmux-terminal.md。真实场景
在 Web Shell 中让 agent 启动一个交互式 CLI;它调用
tmux工具并注册一个终端任务,用户随后可接入并实时观看该会话。agent 创建终端(
bg_25154e98)后的 Web Shell 会话:同一 tmux 会话被 attach(
tmux -L qwen-serve attach -t qsh-bg_25154e98),实时运行 Claude Code:审阅者测试计划
如何验证
模型侧(headless):构建后让会话创建一个运行交互脚本的终端并驱动它,确认任务注册为带
terminal元数据的shell任务、send/capture往返、kill删除qsh-*会话、优雅退出时清理。实时终端侧:启动
serve,创建终端任务,用 WebSocket 连/terminal?...(子协议qwen-ws、qwen-bearer.<token>),预期收到ready、banner 字节、对二进制按键的ECHO回复、取消任务后 socket 关闭。浏览器 Web Shell 中任务详情显示「打开终端」,tab 渲染实时 REPL。自动化覆盖见
.qwen/e2e-tests/web-shell-tmux-terminal.md(A–E 组)与脚本.qwen/e2e-tests/tmux-terminal/ws-check.mjs。单测:core tmux(26)+tmux-commands(11)+backgroundShellRegistry(57);cli terminal-ws(8)+tasksSnapshot(6);web-shell useTerminalSocket(9)+ArtifactPanel(24)+TasksStatusMessage(10)。全绿。build/typecheck/bundle/作用域 ESLint 均通过。
证据(前后对比)
实现前的基线 dry-run 确认了缺口:全局 CLI 下模型没有
tmux工具、只能在默认 tmux socket 上用 shell 工具即兴完成(无qwen-servesocket、无qsh-*命名、无任务注册);daemon 对终端路由返回 404。实现后专用工具、任务元数据与 WS attach 均按上述工作。测试环境
本地
node dist/cli.js(build+bundle),tmux 3.7b,浏览器终端在本地 daemon 的 Web Shell SPA 上验证。风险与范围
terminal字段),任务类型仍为'shell',现有任务消费方不受影响。关联 Issue
引用设计文档
docs/design/web-shell-tmux-terminal.md,无跟踪 issue。