RELAY-PHASE1 Wave A2/B/C/D/E (12-agent workflow, all tsc-clean, 314/314 tests pass): - A2: control-plane server.ts entry + boot/redis.ts revocation-bus wiring + start script. - B1: relay-run shared-store EnforceDeps (relay-auth ports over the SAME Postgres+Redis as P3). - B2: registry-backed MtlsVerifier (verifyAgentCert, fail-closed, INV14). - B3: store-backed RouteResolver (subdomain->hostId). - B4: Redis relay:revocations subscriber -> tunnel teardown (INV12). - B5: main-phase1.ts production entry (public bind, real TLS, async-mTLS prefetch bridge) + staging /auth/mint. - B6 (PARTIAL): relay-web operator login + browser DPoP; proof offered via term.dpop.<b64u> subprotocol. - C: agent dist/cli.js build (esbuild) + runTunnel run-loop + CliDeps. - D1: same-origin static serve of relay-web/public from the browser WSS. - E: systemd units + gen-ca/gen-capability-key/issue-tls-cert scripts + deploy/RUNBOOK.md. Adversarial review: all hard invariants PASS. Follow-ups (B7): close DPoP-subprotocol read on browser-server (blocks browser connect); rate-limit /auth/mint (F1); wire activeSessionCount (F2); scrub error logs (F5). Excludes unrelated public/style.css (concurrent iOS job).
114 KiB
Web Terminal 实施进度日志 (Progress Log)
用途: 记录 PLAN.md 各任务的完成情况,作为跨会话记忆文件。 新会话开工前必读本文件。只记已发生的事实(含失败、跳过),不预填未来,不写愿望。 谁来写(G1): 本文件仅由 orchestrator(主会话)写入 —— 它不在任何任务的
Owns:里, 防止并行 subagent 并发写冲突。被派的 subagent 不写本文件,而是把日志条目作为返回结果交回, 由 orchestrator 在每批 agent 返回后立即追加(不要攒到最后)。 完整维护规则见 CLAUDE.md → Development Workflow。
状态图例
| 标记 | 含义 |
|---|---|
[ ] |
TODO — 未开始 |
[~] |
IN PROGRESS — 进行中 |
[x] |
DONE — 完成且已验证 |
[!] |
BLOCKED — 受阻(须在详细日志写明阻塞原因) |
当前焦点 (Current Focus)
新会话读到的第一块。保持准确,只描述"此刻"。
🚧 RELAY-PHASE1 — 把原生 rendezvous-relay 部署到单台 VPS(8.138.1.192,阿里云;2026-07-06)
- 目标: DEPLOY_RELAY §4 的 Phase 1「1b 全量持久化 staging」。VPS 上 Docker 自建 Postgres+Redis;已备案域名走 443 + Let's Encrypt;agent 跑在操作者本机向外拨号。完整文件级方案见 PLAN_RELAY_PHASE1.md。
- 进度: Wave A/B/C/D/E 代码全部完成并通过 Verify —— 一个后台 Workflow(12 agents,0 error)跑完 A2、B1–B5、B6、C、D1、E。Verify 门槛:四个包 tsc 全干净、可构建包全 build 成功、314/314 测试通过(control-plane 103 / relay-run 74 / agent 137 + relay-web 118)。对抗式安全评审:所有硬不变量 PASS(INV2 opaque splice、Origin/CSWSH 精确匹配、mTLS INV14 registry 门控、token-mint 密码学、撤销 INV12、jti 单次、参数化 SQL)。
- 各任务: A2 P3 server 入口+Redis 总线
[x];B1 共享 store EnforceDeps[x];B2 registry-backed MtlsVerifier[x](异步阻抗由 B5 的bridgeAsyncMtlsper-DER 预取桥无锁解决);B3 store RouteResolver[x];B4 撤销订阅[x];B5main-phase1.ts组合入口 + staging/auth/mint签发[x];C agentdist/cli.js构建+runTunnel运行入口[x];D1 同源托管 relay-web[x];E systemd+脚本+RUNBOOK[x];B6 relay-web 登录/DPoP[~] PARTIAL。 - 两个待收尾(B7): ① 功能阻塞 — 浏览器 WS 无法设请求头,B6 把 DPoP proof 放进
term.dpop.<b64u>子协议,relay 侧(browser-server.ts)必须从子协议读取并喂给UpgradeRequest.dpop,否则真实浏览器一律被 DPoP 拒(401)。② F1 安全 — 公网 :443 上的/auth/mint无限速/锁定(评审最大攻击面);顺带 F2(activeSessionCount硬编码 0,并发上限失效)、F5(裸 error 日志或泄露 DSN)。F3/F4(撤销后 agent 空转重连、slowloris)为 LOW,记入 Phase 2 backlog。 - 远端(需 VPS 执行,见
deploy/RUNBOOK.md): DNS、Let's Encrypt 签证、私有 enrollment CA + capability keypair 生成、阿里云安全组放行 443/AGENT_PORT、systemd 起两个服务、端到端 enroll→dial→浏览器点进 shell。 - 未做/推迟到 Phase 2: 真 KMS、F6 replay、WebAuthn step-up(staging 用
NO_STEPUP_POLICY)、通配多租户、F3/F4。 - 未做/推迟到 Phase 2: 真 KMS(dev signer 暂用)、F6 replay(
loadReplayfail-closed)、WebAuthn step-up(staging 用NO_STEPUP_POLICY)、通配多租户。
[x] A1 — Postgres store 适配器(P3)+ 迁移运行器(2026-07-06)
- 交付:
createPgStores(query): Stores把 9 个 store 端口映射为参数化 SQL(零字符串拼接);runMigrations(query)按文件名顺序跑db/migrations/*.sql(幂等)。 - 文件(全在 Owns 内):
control-plane/src/store/pg.ts、src/db/migrate.ts、db/migrations/0002_routes.sql(新增 — 0001 无 routes 表)、test/store/pg.test.ts。 - 语义对齐 memory.ts: INV8 accounts/hosts
swapStatus用单条 writable CTE(UPDATE 指针 + INSERT 版本行)原子完成,无显式事务;重复插入捕获 pg23505→throw duplicate;casRedeem单赢家UPDATE...WHERE redeemed_at IS NULL RETURNING;RouteStore 存expires_at,expires_at<=now()视为不存在(fail-closed INV7)+ 惰性 DELETE。 - 验证(实测):
npx vitest run test/store/pg.test.ts→ 23/23 通过(自起postgres:16一次性容器 :5433,afterAll 自动docker rm -f);pg.ts覆盖 96.72% stmts / 100% funcs。 - 偏差: ①
0002_routes.sql新增;② SubdomainStore 无独立预留表,按hosts.subdomain UNIQUE归属裁决;③ migrate 按;切分逐条执行(参数化路径拒绝多语句)。阻塞: 无。
[x] A3 — 真能力验证器(relay-auth verifyCapabilityToken)+ 同步→异步 seam(2026-07-06)
- 交付: 抛异常的
refuseAllVerifierstub 换成委托 P5 异步verifyCapabilityToken的真验证器;await贯通整个 authorizer 路径。默认仍 fail-closed(异步化后 reject→401)。 - 文件:
src/api/authz.ts(CapabilityVerifier.verify→Promise)、src/api/provision.ts(5 个路由 call site 改await principal(req))、src/boot/verifier.ts(新;configureCapabilityVerifyKey把env.capabilitySignPubkey32 字节导入 WebCrypto verify key 后调 relay-authconfigureVerifyKey,桥接CAPABILITY_SIGN_PUBKEY_B64vsRELAY_AUTH_VERIFY_PUBKEY命名差)、src/main.ts、package.json(加relay-auth: file:../relay-auth)、test/verifier.test.ts(新)+ 更新 api 测试 stub 为异步。 - 验证(实测):
npx tsc --noEmit全项目干净;npx vitest run15 files / 101 tests pass。 - 偏差: 手建
control-plane/node_modules/relay-auth符号链接以配合新 file: 依赖(npm install会重建)。阻塞: 无。
✅ 修复:底部快捷键栏遮挡终端内容(iOS 安全区,2026-07-06,main)
- 现象(用户截图,iPhone): 底部快捷键栏(Esc/Esc²/⇧Tab…)压住终端最后一行(Claude Code "bypass permissions" 提示行只露上半)。
- 根因(
public/):index.html设了viewport-fit=cover(布局铺满物理屏、延伸进刘海/Home 指示条),但style.css全程没有任何env(safe-area-inset-*)补偿。于是#keybar(fixed; bottom:0; height:--keybar-h)整条压进 Home 指示条区,而#term只预留了裸--keybar-h,末行与键栏相撞。教科书级 cover-无-safe-area 缺陷。 - 修复(纯 CSS,零 JS):
:root新增--safe-t/--safe-b = env(safe-area-inset-top/bottom, 0px),穿过所有贴边固定元素:#tabbar顶部让出刘海(border-box +padding-top:--safe-t)、#terminset 改calc(--tabbar-h+--safe-t) … calc(--keybar-h+--safe-b)、#keybar高度calc(--keybar-h+--safe-b)+padding-bottom:--safe-b(芯片留在顶部 --keybar-h,Home 条上方)、#approvalbar与body.home-open #term底部、#searchbox/#settingspanel顶部同步。inset=0 时全部塌回原值 → 桌面/无安全区平台零回归。 - 验证(headless,390×844,注入
--safe-t:59px --safe-b:34px模拟 iPhone): 开一个真实会话后量测term_bottom==keybar_top==764(gap=0,无重叠)、tabbar_bottom==99(=40+59,让出刘海);截图确认芯片位于 Home 指示条上方、终端不再被遮。npm run build:web通过。 - 待办: 未提交;未在真机 iPhone 上复验(仅 headless 模拟安全区)。
✅ 修复:项目面板把父文件夹当项目 & 会话点亮所有祖先项目(2026-07-06,分支 feat/ios-client)
- 现象(用户截图): 只在
web-terminal跑了一个会话,但 "Active now" 同时显示web-terminal/Documents/yiukai三张卡,且父文件夹本身被列为项目。 - 根因(
src/http/projects.ts): ①belongsTo纯前缀匹配 → 会话按 cwd 归属到每一个祖先项目;② 历史合并(mergeHistory)把曾经跑过会话的 cwd(如~、~/Documents)原样列为项目。 - 修复(TDD,先红后绿): ① 新增
assignSessions—— 每个 live 会话只归属最深的包含项目(buildProjects改用;buildProjectDetail保留前缀匹配,单项目详情页语义不变);② 新增dropParentFolders—— 丢弃"非 git 且包含其他已列项目"的父文件夹条目(discovery 阶段过滤,进缓存)。 - 验证: 新增 3 测(最深归属/父文件夹剔除/独立非 git 历史项目保留);
test/projects.test.ts29/29 绿;全量npm test53 files / 1473 全绿;tsc --noEmit干净。前端零改动("Active now" 分组消费sessions[],服务器修正后自然收敛)。
✅ 全端应用图标 "Orbit" + 托盘模板图(完成 — 2026-07-06,main,commit 86d100a)
用户选定 "Orbit"(节点绕方块光标核环绕=agent 自主替你运行),品牌沿用桌面 --accent #E3A64A 琥珀金 + 暖近黑 #100F0D。矢量母版三份(App 圆角暖底 / iOS 满幅不透明 / 托盘纯黑透明加粗),rsvg-convert(brew install librsvg)栅格化铺各端:
- iOS/iPad:
Assets.xcassets/AppIcon.appiconset(单尺寸 1024 不透明,Xcode 自动生成全套;此前无图标)。构建验证AppIcon60x60@2x/76x76@2x~ipad入 Assets.car;模拟器主屏实拍确认。 - 桌面 App:
desktop/resources/icon.png(圆角 1024)→npm run dist:mac重打包出新 icns + 156MB DMG(dist-app/Web Terminal-0.1.0-arm64.dmg),顺带带上 projects 修复。装机需xattr -cr清 quarantine(ad-hoc 签名 Gatekeeper)。 - 桌面托盘:
trayTemplate.png/@2x换成正确的 macOS 模板图(黑形状+透明底,系统按亮/暗菜单栏自动染色)——根治"一坨白"。 - web/PWA:
public/icon.svg矢量。待办: 分支/main 未推送;图标对比页(5 创新+5 经典)在 job scratchpad 未入库。
✅ iOS 客户端全面 UX/UI 打磨(完成 — 2026-07-05,分支 feat/ios-client,commit 660a404;精致原生方向)
用户定方向「精致原生 + 可重构」(靛紫 #7C8CFF 强调、语义状态色、SF Mono 数字、系统材质)。编排 = 一个 ultracode Workflow(9 agents,4 阶段):3 路审计(易用性/视觉/无障碍,各 boot 两 sim 取证)→ 冻结 DesignSystem(协调点,防各 agent 各挑配色)→ 4 组文件互斥并行套用 → 验收。
- 审计: 3 视角共产出 ~40 findings,共识根因=「无设计系统」——全 App stock 系统蓝、状态仅靠颜色(违反色+形,idle 蓝撞 unread 蓝)、gate 决策按钮 <44pt、间距/圆角魔数无刻度、数字非 tabular、缩略图纯黑块、gate 中英混排、reduceMotion 未处理。
- 冻结 DesignSystem(
ios/App/WebTerm/DesignSystem/**4 文件): Tokens(自适应 accent 靛紫、语义 status 色、间距 2-24 刻度、圆角 sm8/md12/lg16、Motion gated by reduceMotion、Haptics)、Typography(SF ramp + mono tabular)、StatusStyle(status→色+SF Symbol+中文,单一真源,色+形)、Primitives(StatusBadge/TelemetryChip/Card/SectionHeader/DSButtonStyle 可复用)。根.tint(DS.Palette.accent)单点注入。 - 4 组套用(全 DONE,纯视觉零逻辑改动): G1 列表行重构+状态徽标+telemetry 芯片+缩略图占位;G2 终端 gate 卡(≥44pt approve/reject)+SwiftTerm accent 主题+KeyBar/reconnect/quick-reply/digest;G3 配对 hero+警告分层视觉化+Projects 卡片+Timeline/Diff;G4 导航 chrome+iPad 分栏占位(accent 图标)+隐私遮罩+动效+
ContinueLastBanner/ProjectsToolbarItem抽共享组件。gate 文案落中文。 - 验收 PASS_WITH_FINDINGS,设计评分 8.5/10,1 finding: TimelineSheet class→色仍裸色(全改动里唯一越界)→ orchestrator 补
DS.Palette.timelineTool/timelineUsertoken + TimelineSheet 全走 DS + 更新钉旧裸色的测试。 - 验证(亲验): iPhone 16 290 + iPad Pro 11 290 全绿(修 finding 后);包+集成绿;iPad 分栏占位截图确认靛紫强调色生效。未推送。待用户目视: 会话列表行/gate 卡/配对页真机观感(截图受通知授权弹窗遮挡,建议真机或点过授权后看)。iPad 侧栏 List(selection:) 高亮审计标记为「列表组 Owns 后续 pass」——当前 onOpen 已驱动切换,视觉选中高亮可再补。
✅ iOS 客户端 iPad 适配(完成 — 2026-07-05,分支 feat/ios-client;自适应布局,非分叉)
按 docs/PLAN_IOS_IPAD.md(5 人日)编排:W0 orchestrator 亲做 → 一个 ultracode Workflow(4 agents: T-iPad-2 核心串行 → T-iPad-3 ∥ T-iPad-4 → T-iPad-5 验收)。
- [x] T-iPad-1 device family(orchestrator 亲做,commit
77502ec):TARGETED_DEVICE_FAMILY全 target1→1,2;iPad~ipad四方向(iPhone 不动);ios.yml 加 iPad Pro 11 测试腿。实测:产物UIDeviceFamily [1,2]、iPad 原生满屏(不再 iPhone 黑边)。 - [x] T-iPad-2 AdaptiveRootView + NavigationSplitView + LayoutPolicy(核心):
LayoutPolicy.mode(hSizeClass)唯一 size-class 决策点(regular→split/compact→stack,仿 PrivacyShadePolicy);AdaptiveRootView把横切布线(隐私遮罩 ZStack 顶层两分支共享/scenePhase/deepLink/sheets)上提唯一一处;StackRootView=原 RootView body 原样搬迁(iPhone 字节级零回归);SplitRootView=左 SessionListScreen(复用不改)+ 右 TerminalContainerView(复用不改,带.id);AppCoordinator 加 SidebarItem 路由桥(非平行生命周期)。 - [x] T-iPad-3 KeyBar 自适应 + 指针右键菜单:
KeyBarVisibility纯谓词(硬件键盘在场默认隐);TerminalContextMenu(iPad idiom,复制选区/开新会话/结束会话,一一路由既有通道);kill 经 APIClient 带 Origin 单测。 - [x] T-iPad-4 Projects 大屏化:
ProjectsGridLayout.columnCount(iPhone 恒 1/iPad 按宽 1-3);iPad 多列网格复用同款 row/分组/prefs 逻辑零改;Timeline 已有 detent 两端合理。决策: Projects 在 iPad 以 form sheet 呈现,其内 hSizeClass 恒 compact → 改用userInterfaceIdiom判据(与 size class 正交)。 - [x] T-iPad-5 验收 PASS_WITH_FINDINGS → orchestrator 修复 4/4: iPhone 零回归硬门守住(277 App+261 包+10 集成);iPad 分栏工作;无 CRITICAL/HIGH/安全回归。修复:①MED kill 菜单项无法渲染 →
onKillSession贯穿 TerminalContainerView→两根视图 +AppCoordinator.killCurrentSession()(复用 kill 通道);②MED split 未消费 route →AdaptiveRootView对.loading/.pairing强制走 StackRootView(iPad 首启配对不被困在空态);③LOW 「继续上次」横幅补进 split sidebar(截图确认);④LOW iPad happy-path XCUITest 接受推迟(split 选中逻辑已由 SidebarSelectionTests 9 测真 engine 覆盖,stack happy-path 未变,XCUITest 单跑 7-11min 且脆,成本不划算)。验证(亲验): iPhone 16 277 + iPad Pro 11 278(+kill 桥测试)全绿;iPad 分栏 sidebar+detail 截图确认。 - 合计: iPad 适配零改
ios/Packages/**/src//public/;纯逻辑包设备无关直接复用。待用户: 真机 iPad(分栏手势/硬件键盘/指针右键/Stage Manager)DEFERRED;多窗口(拖会话开新窗并排两终端)另立下一期(本期单场景)。
✅ iOS 客户端 P1 实施(完成 — 2026-07-05,分支 feat/ios-client,P0+P1 共 14 commits,未推送/未合并)
- [x] P1-A(W6+W7 首发: T-iOS-37 ∥ T-iOS-20 ∥ T-iOS-38,3 builders ∥ + verify 6/6 PASS;
/push/apns-token线形状由 orchestrator 预冻结,两侧并行编码零失配——verify 逐项对照 method/path/body key/hex 规则/状态码/守卫/限额全 match):- T-iOS-37 server lastOutputAt:
LiveSessionInfo.lastOutputAt?: number可选增量 +manager.list()一行映射 + 4 测;web 端 grep 零消费者,tsc+全量双证兼容。 - T-iOS-20 server APNs:
src/push/apns.ts(576 行)——env 三件套 all-or-disabled(缺失→路由不挂载,启动不 crash,密钥材料零日志)、token store 照抄 subscription-store 约定(幂等/上限/0600)、手写 ES256 JWT(node:crypto ieee-p1363,零新依赖)、NEEDS-INPUT(priority 10+WEBTERM_GATE+capability token)/DONE(priority 5)、payload 最小化结构性保证(构造函数只读 meta.id,cwd/命令内容不可能出现)、410/BadDeviceToken 逐出、combineNotifyServices与 web-push 并联互不阻塞;hasPushTarget扩展(仅 APNs token 也能 hold gate)。65 测含双 e2e(本地 h2c 假 APNs:held gate→双通道同发同 token→/hook/decision 放行)。新增APNS_HOSTenv(sandbox 切换+测试零网络)。npm test 53 files/1470 全绿,tsc 干净。 - T-iOS-38 APIClient P1 契约: APNs token builders(hex 校验镜像服务器、联网前拒绝、显式 ASCII 字符集防全角)、projects/detail(证实线上无 namespace 字段——纯 web 端分组概念;lossy 解码;percent-encode 单点+裸 + 规避)、prefs(未知顶层键往返逐字节存活——防 iOS 覆写 web 偏好,关键陷阱已测;JSONValue 独立 integer case 防 42→42.0)、公开四层 HostNetworkTier(W3 dedup 欠账,PairingVM 后续 pass 可切换)。76/76 绿,覆盖 95.26%。
- 遗留(LOW/cosmetic): Swift doc comment 里 server 行号引用漂移;PairingVM 分类去重待后续 pass。
- T-iOS-37 server lastOutputAt:
- [x] P1-B UI 九连链(22→24→25→27→26→23→29→28→21 全 DONE,10 agents;verify 判 BLOCKED→orchestrator 修复后放行):
- T-iOS-22 DeepLinkRouter: 全字段白名单(scheme/action/path 空/query 键精确/重复键拒)、UUID 复用冻结 Validation(v4);冷启动单槽 stash;
route(from:)供 push-tap 复用;非法链接计数不回显内容(防日志注入)。21 测。 - T-iOS-24 Timeline sheet: 呈现顺序逐行镜像 web render()(slice 50→reverse);
[]=disabled→空态非错误;复用 AwayDigestView 既有 onExpand(T-iOS-14 零改动);label Text(verbatim:)。14 测。 - T-iOS-25 Quick-reply: 内置 6 chips 逐项镜像 quick-reply.ts(Esc/Enter 经 KeyByteMap 零手写转义);显示条件=持牌 gate(流上唯一 waiting 信号)&&!readOnly;UserDefaults lossy 载入。16 测。
- T-iOS-27 Diff 查看器: App 层 DiffFetcher(RO 无 Origin;并注记归 T-iOS-38 后续收编);hunks 着色/staged 切换/truncated 横幅/lazy list;diff 字节 verbatim 渲染。
- T-iOS-26 Projects: 分组逐条镜像 projects.ts 且组 key 与 web 逐字节一致(collapsed 经 /prefs 跨端共享,key 漂移=两端互丢折叠);prefs clobber 防线——GET 失败→toggle 仅本地绝不 PUT(空底盘 PUT=清库)、PUT 采纳服务器 echo;
claude\rbootstrap 镜像 tabs.ts:679。 - T-iOS-23 切换器: UnreadLedger(单调水位,严格 >)+TitleSanitizer(C0/C1/DEL+bidi/零宽全表,字素簇截断 256)入 SessionCore(+15 包测);LiveSessionInfo.lastOutputAt 容错解码(授权增量);列表边界二次净化。
- T-iOS-29 杂项: new-in-cwd(cwd 不可信,非绝对路径按未知;bootstrap 绝不复注入——开新 shell≠起 claude,专项断言);exit 横幅"开新会话";加建修复潜在 bug: RootView 给容器补
.id(controller.id)(呈现中换 controller 不换 SwiftTerm 视图→终端假死)。 - T-iOS-28 缩略图: 离屏 SwiftTerm 渲染管线(LRU 32/并发闸 2/预览 256KiB 上限/网格钳制),缓存键 (sessionId,lastOutputAt),CADisplayLink 注销不泄漏。
- T-iOS-21 PushRegistrar+锁屏: WEBTERM_GATE category(Allow=.authenticationRequired、Deny 免认证 fail-safe、均无 .foreground,形状测试钉死);device token 对每 host 注册+记账重试;NotificationActionHandler(sessionId 复用 DeepLinkRouter 白名单、token 校验后仅入参绝不落盘、beginBackgroundTask 包 POST、403→本地通知兜底)。锁屏真机行为 DEFERRED(需付费账号)。
- verify(独立 agent): 包 261(SessionCore 92/APIClient 77/…)+App 247+集成 10 全绿;7 项语义抽查全 PASS(含恶意标题/putPrefs 唯一调用点走保留路径/token 零持久化 grep);Owns 审计干净;但抓到 CRITICAL——生产模式启动即崩: @MainActor UNNotificationCenterAdapter 的 completion 闭包继承主 actor 隔离,UN center 在后台队列回调→Swift 6 executor 断言 trap(测试全绿盖不住:push 接线在测试宿主下被跳过——测试盲区实证)。orchestrator 修复: 三处闭包加
@Sendable字面量标注(不继承隔离);重建+实机启动验证:进程存活、崩溃报告零新增、通知授权弹窗弹出(=走过原 trap 点)、弹窗期间隐私遮罩正确覆盖(scenePhase .inactive)。 - 遗留(LOW,归 P1-C 复核): deep link 未断言 userinfo/port(
webterminal://user:pass@open仍路由;id 均 v4 校验+host 只经 store 解析,影响可忽略);Diff fetcher 收编 APIClient 待后续 pass。
- T-iOS-22 DeepLinkRouter: 全字段白名单(scheme/action/path 空/query 键精确/重复键拒)、UUID 复用冻结 Validation(v4);冷启动单槽 stash;
- [x] P1-C 验收+安全复核(T-iOS-30,2 reviewer ∥,report-only)—— 双双 PASS,零 findings:
- Part A 功能验收: 亲跑 261 包测+247 App 测+10 集成全绿;启动崩溃修复复核 PASS(3 次实机启动零崩溃,通知授权弹窗弹出=走过原 trap 点,崩溃报告计数停在修复前的 2);
simctl push实测注入 WEBTERM_GATE payload → 通知横幅真实渲染("Needs input / Session 3d34818d",目视确认最小化:仅状态词+8 位前缀,无 cwd/命令/token);F-iOS-15 服务端切片实测(双会话lastOutputAt差分正确);意外收获——对你的真实服务器(127.0.0.1:3000,pre-T-iOS-37 旧构建)实测旧版兼容降级:App 直接渲染真实会话列表(stuck 红点/未知目录标题回退/0 台设备在看 · 51×50与服务器精确一致/无 unread 点=正确降级)。真机项(锁屏 tap/Face ID、unread 点亮目视、OSC 标题目视)DEFERRED 附手工步骤。 - Part B 安全复核(对抗式): APNs payload 最小化 post-P1-B 仍成立(OSC 标题不进 alert,grep 证实);capability token 全链路 CLEAN——issue(server randomUUID+TTL)→payload(仅 gate)→handler 校验后仅入参 hookDecision→零落盘(grep Push/ 及全 App 无 UserDefaults/Keychain/file 写;唯一持久 token 是内存态 device token)→单次消费(server resolvePending 一次性)→两侧日志零 token;deep-link 50+ fuzz 全部安全(userinfo/port LOW 判定 inert——从不用于构造请求、id 仍 v4 校验,继续接受);锁屏 Allow
.authenticationRequired形状测试绿;prefs 唯一写入点仍守空底盘;P1-B 新攻击面(缩略图字节只进 SwiftTerm、diff verbatim、标题净化覆盖全渲染点)扫盘无泄漏;残余风险台账完整。
- Part A 功能验收: 亲跑 261 包测+247 App 测+10 集成全绿;启动崩溃修复复核 PASS(3 次实机启动零崩溃,通知授权弹窗弹出=走过原 trap 点,崩溃报告计数停在修复前的 2);
- P1 收官统计: 累计 518 项自动化(261 包测 + 247 App 测 + 10 集成)+ 服务器 1470 测全绿;新增能力=远程 APNs 推送(锁屏 Allow/Deny+Face ID)、deep link、Projects 项目列表(分组/收藏/在仓库起 Claude)、多会话切换器(unread+OSC 标题净化)、Timeline/Diff/Quick-reply/缩略图;服务器仅 §0.3 声明的两处增量触点(lastOutputAt + APNs)。待用户: ①真机走查(T-iOS-30 报告手工清单);②Apple 付费账号(APNs 真机端到端+TestFlight);③推送分支跑首轮 GH Actions(ui-test 腿/ios17 runtime);④P2(语音 PTT/worktree/终端内搜索/主题/web ?join= 互通)是否开工——P2 任务需先按 P0 规格扩写 RED 清单再分派(§7 P2 注)。
✅ iOS 客户端 P0 实施(完成 — 2026-07-05,分支 feat/ios-client,7 commits,未推送/未合并)
按 PLAN_IOS_CLIENT §8 批次推进;编排 = 每波一个 ultracode Workflow(builder 按任务派发 + 独立 verify agent 复跑验收)。
- [x] W0 基础(T-iOS-1/2/3/4,5 agents,verify 6/6 PASS):
- T-iOS-1 脚手架:
ios/全结构(project.yml/XcodeGen、5 包空壳、WebTermApp 空窗、.github/workflows/ios.yml);Info.plist 经 plutil 核对 = §5.2 逐字(五段 CIDR,全程零 NSAllowsArbitraryLoads);bundle idcom.yaojia.webterm。偏差: "default MainActor isolation" 属 Swift 6.2+,本机 6.1 → Swift 6 语言模式 + strict concurrency + 显式 @MainActor 替代(已注 project.yml)。环境事实: 本机 Xcode 16.3 原本无 iOS 模拟器 runtime →xcodebuild -downloadPlatform iOS装 iOS 18.4(22E238)后模拟器构建绿。 - T-iOS-2 Day-1 双 spike: 平台事实定音——URLSessionWebSocketTask 可发自定义 Origin(①无 Origin→401 ②精确匹配→101+attached ④端口失配→401),不切 Starscream;③ 1.5MB 回放在默认 1MiB 上限失败(withKnownIssue 记录)、16MiB 成功,ESC/C0 对抗(转义≈3.6MB)成功。勘误(已写回 PLAN §1/§3.2/T-iOS-9): 超限 errno 实测 NSPOSIXErrorDomain 40=EMSGSIZE(计划原文误标 ENOBUFS=55),T-iOS-9 归类以 40 为主、55 兜底。ServerHarness 自举真服务器(tsx spawn+death-pipe watchdog,无孤儿进程)。真机 smoke 4 项 DEFERRED(无真机),SpikeTerminalScreen 已编入 App target 待 T-iOS-18。
- T-iOS-3 WireProtocol(冻结): §3.1 全类型 + Tunables §3.2.1 全表;59 tests 全绿、覆盖率 100%(llvm-cov 226/226);300 轮 roundtrip property + 500 轮 fuzz(decodeServer 永不 crash);跨实现实测: Swift encoder 输出经 tsx 喂给真
src/protocol.tsparseClientMessage → 13 帧+mode 顶层恢复全 PASS。自此冻结。 - T-iOS-4 TestSupport: FakeTransport(actor,scripted failures+按连接记帧)/FakeClock(class+锁,
waitForSleepers确定性栅栏,零真实等待)/FakeHTTPTransport(记录 headers 供 Origin-iff-G 断言);3 smoke 全绿。 - verify(独立 agent 复跑): 模拟器构建 exit 0;59+3 tests;覆盖率 100%;spike 6 tests+1 known issue;
git status零越界。
- T-iOS-1 脚手架:
- [x] W1 叶子包(T-iOS-5/6/7/8,4 builders;编排 [5→6 串行] ∥ 7 ∥ 8——5/6 同在 SessionCore 包,主树并行会互坏编译):
- T-iOS-5 ReconnectMachine+PingScheduler: 纯 reducer,§3.2 签名逐字;backoff 1s→…→30s 封顶镜像 terminal-session.ts(连上才归零,retry/foregrounded 不重置阶梯——防狂按打穿);Ping 25s/连续 2 miss→connectionLost/取消与断线显式区分。16 测,0 真实等待,文件级覆盖 100%/95%。
- T-iOS-6 GateState+AwayDigest: epoch 语义逐行镜像 terminal-session.ts:306-311(上升沿 +1、持续帧刷新 detail、下降沿不重置计数器);
canDecide(epoch:)防误批;affordance 映射逐字对齐 tabs.ts:345-347(绝不发 raw auto);digest 词表与TimelineEvent.knownClasses有相等性绊线测试;防御:乱序容忍、Date→ms clamp 不 trap。25 测,两文件 100%。 - T-iOS-7 HostRegistry: §3.3 契约;KeychainHostStore 经 SecItemShim 缝(swift test 打 fake;真 shim kSecUseDataProtectionKeychain+AfterFirstUnlockThisDeviceOnly,属性字典单点构造+逐字单测);InMemoryHostStore 入 Sources 供 App-VM 测试。30 测,own-Sources 覆盖 88.1%。
- T-iOS-8 APIClient+探针: 44 测先全绿后
[!] BLOCKED正确上报冻结契约自相矛盾——§3.4runPairingProbe → Result<Host,…>中 Host 是 §3.3 HostRegistry 类型,而 APIClient 依赖边仅 WireProtocol(§1 零耦合),签名无法编译(两轮 review 均漏)。裁定(orchestrator,三选一取 c): 探针职责=验证 endpoint → 返回Result<HostEndpoint,…>,Host{id,name} 由 T-iOS-12 VM 构造(id/name 本非探针所知);同时按法定路径加Tunables.pairingProbeTimeout(10s,§3.2.1 新增行+绊线断言)。裁定已写回 PLAN §3.4/§3.2.1/T-iOS-8,公开包装+冒烟测试由 orchestrator 落地。Origin iff-G 入测试名;探针②成功后必带 Origin kill 不留孤儿;403 kill→originRejected(HTTP 侧 G 守卫也是配对必验项)。45 测,own-Sources 覆盖 98.1%。 - W1 验证(orchestrator 亲验): 5 包 178 tests 全绿(59+3+41+30+45);覆盖率(own-Sources 口径)SessionCore 99.1%/HostRegistry 88.1%/APIClient 98.1%,全过 80% 门;
git status全部在 Owns 内零越界。发现归 T-iOS-16: §9 覆盖率命令的 ignore regex 不排静态链入的依赖包源码,CI 接线时须按包过滤(本次已按正确口径亲算)。
- [x] W2 连接核心(T-iOS-9→10 串行;首次派发 T-iOS-9 agent 死于 API 连接中断——现场零文件、直接 resume 重跑,非任务失败):
- T-iOS-9 URLSessionTermTransport: 冻结 TransportConnection 不动,ping 经 SessionCore 内部(非冻结)
PingableTermTransport/ConnectionPinger扩展点接入;pong 时限复用Tunables.pingInterval(无新常量);EMSGSIZE 归类 40 主/55 兜底→类型化.replayTooLarge;connect 失败原样重抛底层错误(保住 PairingError.classify);状态 delegate 驱动,close 三态可区分;测试服务器 = 裸 TCP 手写 RFC6455 framing(可逐字验 Origin/注入 binary/超帧)。11 测,文件覆盖 97.83%,iOS triple 编译过。 - T-iOS-10 SessionEngine actor: 单 Task 生命周期循环 + generation 计数防陈旧任务复活;首帧铁律 attach→lastKnownDims resize→排队帧按序 flush;终态(exit / replayTooLarge)绝不进 backoff;digest 断点原子消费恰好一次;gate 决策 canDecide 兜底、断线期决策丢弃不入队(重连后 gate 可能换代);防御:非 v4 UUID/相对 cwd 剥离(服务器会静默丢帧致挂死)。决策: 初始 resize 依赖首次 sizeChanged/notifyForegrounded(冻结 open 无 dims 参数,SwiftTerm 首次布局即触发,已注 doc);digest limit 传 Int.max(截断属 UI,T-iOS-14)。20 测,SessionEngine.swift 94.98%(480 行,<800 硬上限,内聚裁量已记)。
- W2 验证(独立 agent,5/5 PASS): SessionCore 72 测(41 W1 完好+31 新);四包回归全绿(累计 209 tests);own-Sources 覆盖 96.68%;冻结契约零改动;4 项语义抽查(Origin 单点 :169/16MiB 来自 Tunables/replayTooLarge 断重连路径逐行核/decodeServer 唯一入口 nil-drop)全数在源码落实。
- T-iOS-9 URLSessionTermTransport: 冻结 TransportConnection 不动,ping 经 SessionCore 内部(非冻结)
- [x] W3 UI+提前项(7 agents: [T-iOS-11→12→13→14] 串行链 ∥ T-iOS-16 ∥ T-iOS-17;协调点 = orchestrator 预建 WebTermTests 单测 bundle(project.yml+5 包依赖,模拟器实测过)并单独提交):
- T-iOS-11 Terminal+KeyBar: KeyByteMap(SessionCore 纯数据)逐字节复刻 keybar.ts 全 17 键(⏎=\r 非 \n 专项断言);TerminalViewModel 消费 engine.events,replayTooLarge→不可重试态+话术逐字断言;决策: 硬件 UIKeyCommand 仅注册 Esc/⇧Tab/Ctrl 和弦,箭头不注册(固定 CSI 会破 DECCKM 应用光标模式,vim/htop 错乱)——有排除测试;OSC52 剪贴板拒绝、链接仅 http(s)。
- T-iOS-12 Pairing: Phase 状态机;扫码→confirmingHost,确认前零网络(recordedRequests/connectAttempts 空断言);公网需显式勾选确认;§5.4 四层警告 13 组参数化;§3.4 裁定落地(VM 构造 Host 入 store);host 分级 VM 内重实现(APIClient 的 internal 粒度不足,重复已标注归 T-iOS-38 去重);真机 DataScanner 编译隔离,generic/platform=iOS 构建过。
- T-iOS-13 SessionList: 轮询 Tunables.listPollInterval,离开断言 0 sleeper+10 周期零请求(无泄漏);发现: LiveSessionInfo 无 pending 字段(已核 manager.list())→ ⚠ 徽标经 setPendingApproval overlay 由 T-iOS-15 从 gate 事件接入;kill 乐观移除+404=成功/403 回滚;staleness 严格 > 镜像 web;PR 链接 https-only(SEC-L5 镜像)。
- T-iOS-14 Gate/Digest UI: GateViewModel 独立 VM(events 与 engine 分离注入,T-iOS-15 fan-out 零改动);三选一映射唯一事实源=SessionCore Affordance.clientMessage(acceptEdits/default/reject,App 层零 allowAutoMode 逻辑);tap-epoch+affordance 双守卫;haptic 每 epoch 恰一次(HapticSignaling 注入);digest 自动淡出 FakeClock 驱动;文案逐字镜像 tabs.ts:326-350。
- T-iOS-16 集成 CI: 吸收 spike 重构 harness;10 测对真自举 Node 服务器(echo/stty resize 1..1000/16MiB+ESC/C0 对抗回放/JOIN mirror/401/401/403/kill→WS close 而非 exit 帧/自然 exit 广播);ios.yml=包矩阵+coverage-gate.sh(own-Sources 过滤,修正 §9 已知缺陷)+App 测试+集成 job;覆盖率门红→绿演示实录;CI 平台未跑(如实注明)。
- T-iOS-17 ntfy 文档: ios/README.md;只读验证(引 setup-hooks.mjs file:line),未触碰用户真实 hook 配置(orchestrator 安全约束);payload 最小化逐行核;真机端到端 DEFERRED。
- W3 验证(独立 agent,全项 PASS): 包套件 59+3+77+30+45;App 55 测 TEST SUCCEEDED;集成 10 测+1 known issue(故意的 1MiB 复现);语义抽查 5/5(KeyByteMap 逐字节 16 项、三选一字面量+零 allowAutoMode、零网络断言实测在 :60、轮询走 Tunables、CI 覆盖率 own-Sources 过滤);Owns 审计零越界。累计 224 单测 + 10 集成。
- [x] W4 汇合(T-iOS-15,1 builder + verify 8/8 PASS):
- T-iOS-15 App 接线:
Wiring/9 文件——AppEnvironment 生产 DI(真 Keychain/真探针/URLSessionHTTPTransport 零逻辑透传,Origin 规则全留 APIClient)、EventFanOut(单消费者 engine.events → TerminalVM+GateVM+SessionActivityBridge 三分支保序广播)、bridge(.adopted→存 lastSessionId/.gate→列表 ⚠ overlay/.exited→清两者,防「继续上次」指向死会话)、TerminalSessionController(.background→close 干净 detach;suspend 后 .active→重建栈+open,generation 换 SwiftUI identity 触发首次 sizeChanged 夺回全屏)、PrivacyShade 严格scenePhase != .active(三相位测试)、冷启动路由纯函数;WebTermApp 薄 @main;删除 SpikeTerminalScreen(grep 零残留)。 - 两项自动化走查代理: ①LiveServerSmokeTests——签名宿主内真生产 DI 图端到端(探针→host 入库→liveSessions→attach→echo 回显→close→kill 清理);②KeychainHostStoreLiveTests——真 Keychain 往返+
SecItemCopyMatching断言kSecAttrAccessible==AfterFirstUnlockThisDeviceOnly+非 synchronizable(T-iOS-7 递延项闭合)。模拟器 boot 截屏:配对页正常渲染。 - 环境硬墙(新发现,已记 harness): repo 在
~/Documents(TCC 保护)时,模拟器 App 语境 posix_spawn 的 node 读 repo 文件被 tccd 无限阻塞(sample 实测卡uv_cwd→__open_nocancel;平台二进制豁免)→ SimServerHarness 双模式:A 自举(CI/非 TCC 路径)/BWEBTERM_SERVER_URL外部注入;TCC 下 0.001s 快速失败+指引。本机跑法: 起服务器后xcrun simctl spawn booted launchctl setenv WEBTERM_SERVER_URL …再 xcodebuild test(命令行 TEST_RUNNER_ 前缀实测传不进宿主 App 测试进程);裸跑 smoke 红=有意显式失败非回归。 - 偏差闭合(orchestrator 代 T-iOS-11 owner 修): W4 曾记「alive-engine
.active未调 notifyForegrounded(dims:)——W3 无 dims 钩子」→ TerminalViewModel 增lastSentDims(仅记有效 resize,invalid 不覆盖,有测试)+ controller.active分支接notifyForegrounded(dims:)(无有效 dims 时跳过,SwiftTerm 首次布局兜底)。76 App 测全绿(75+1)。 - verify(独立 agent,8/8): 214 包测+76 App 测+10 集成;spike 删净;遮罩规则/生命周期双向 close 路径/组装安全(G 全走 APIClient、零 ad-hoc URLSession、零 ArbitraryLoads、Origin 零手拼)逐项在源码核实;Owns 零越界。
- T-iOS-15 App 接线:
- [x] W5 验收 + findings 修复(2 reviewer ∥ + 1 fix builder;均 PASS_WITH_FINDINGS→已闭合):
- T-iOS-18 F 走查: 全部机器可执行项本轮亲自重跑(集成 10/App 76/包 214 + 模拟器冷启动·后台·重启截屏);F-iOS-1..13 逐条 EXECUTED/EVIDENCED(指认测试名)/DEFERRED(真机)(QR 扫码/IME/震动/切换器遮罩目检/ntfy 端到端,附手工步骤清单)。
- T-iOS-19 安全核对: 对产物审——Origin 写入点全树恰两处且单点派生;G/RO 分界;拆构建产物验五段 CIDR+零 ArbitraryLoads+双 usage description;Keychain 属性;P0 不该在的面(deep link/麦克风)确认缺席;残余风险(isCaptured 跳过/ws 明文模型/epoch 仅客户端)记录完整。
- findings(0 CRITICAL/HIGH; 1 MED+3 LOW,全部闭合): ①MED §9 XCUITest happy path 缺失 → orchestrator 建 WebTermUITests target/scheme,fix agent 落地唯一用例:配对(手输)→列表→attach→KeyBar ^L→注入 held gate(POST /hook/permission)→点 Approve→断言服务器真放行 behavior==allow,服务器为 oracle(XCUITest 读不了 SwiftTerm 字形);幂等三分支(全新/已配对/空态)全被实走;两次真实跑通(439s/677s,EXIT=0)。②LOW CI 缺 iOS17 下限腿 → ios.yml 增 ios17-floor job(runtime 缺失大声跳过/存在必判,CI-only 本机未验)。③LOW URLSession.shared 磁盘缓存可落盘 preview 终端字节 → 生产 HTTPTransport 默认 .ephemeral。④LOW XcodeGen target 级默认覆盖 project 级 TARGETED_DEVICE_FAMILY(实际打包 [1,2]) → 移 target 级,产物已验 [1]。
- XCUITest 实证发现(已记 ios.yml/用例注释):
TEST_RUNNER_env 只在 xcodebuild 进程环境里才透传 runner(命令行 KEY=VALUE 是 build setting 到不了);CODE_SIGNING_ALLOWED=NO打断真 Keychain(-34018)→ 顺手修掉既有 app-tests 腿同 flag(否则 KeychainHostStoreLiveTests 在 GH runner 必红);XCUITest typeText 够不着 SwiftTerm(无 AX 键盘焦点)→ 输入断言走 KeyBar ^L+清屏应答;SwiftTerm 光标闪烁烧 XCUITest 60s idle-wait/事件(单跑 7-11min,已压到 3 次终端交互)。 - 遗留观察(report-only,非阻塞): 配对探针偶发残留 clientCount=0 会话(killProbeSession 竞态,run 8 一例)——归 T-iOS-8 owner 复核;真 GH Actions 首跑待推送后确认(ui-test 腿/ios17 runtime 镜像现状)。
- P0 收官统计: 306 项自动化(214 包测 + 76 App 测 + 10 集成 + 1 XCUITest[计 1,参数化更多]) 全绿;覆盖率 own-Sources SessionCore 96.7%/HostRegistry 88.1%/APIClient 98.1%/WireProtocol 100%;19/19 任务完成(全部偏差有日志);服务器 src/ public/ 零改动。待用户: ①真机走查清单(T-iOS-18 报告内);②推送分支跑首轮 GH Actions;③P1 开工前拍板:Apple 付费账号(APNs/TestFlight)与 bundle id/App 名(现 com.yaojia.webterm/WebTerm)。P1 首批 = T-iOS-20(APNs server) ∥ T-iOS-37(lastOutputAt) ∥ T-iOS-38(APIClient P1 契约)。偏差(orchestrator 决策,W1 起生效): 并行 builder 不用 git worktree(Workflow worktree 合并回主树增加失败面),改为主树直跑——文件互斥 + 同包任务串行化,禁 agent 碰 git;每波末独立 verify + orchestrator commit 兜底。
✅ iOS 客户端实施计划 — docs/PLAN_IOS_CLIENT.md(规划完成 — 2026-07-04,main,未提交;纯文档,零代码改动)
多 agent 编排 = 一个 ultracode Workflow(66 agents,7 阶段):5 读者并行(wire contract 逐 file:line / 功能矩阵 / relay 现状 / 模板规范 / iOS 平台联网调研)→ 3 架构师独立竞标(full-native / WKWebView-hybrid / phased)→ 3 评委多视角打分(UX/工程/安全,phased 26.5 胜出,native 23.5、hybrid 16.5,21 条 mustSteal 嫁接)→ 综合简报 → 写手出稿(843 行)→ 4 评审交叉验证(对照 src/ 源码逐条核实 / iOS API 联网核查 / 安全 / 计划质量)→ 每条 finding 对抗式 verify(CRITICAL/HIGH 双票)→ fixer 应用。
- 方案: Phased-Native「口袋驾驶舱」—— SwiftUI + SwiftTerm(MIT/SPM),4 个纯 SwiftPM 包(WireProtocol/SessionCore/HostRegistry/APIClient)+ 薄 App 胶水,
ios/顶层新包;前台会话单条活 WS + 其余 HTTP 轮询;iOS 17+/Swift 6/XcodeGen;P0 服务器零触点(通知复用既有 ntfy 桥 scripts/setup-hooks.mjs:227-238),P1 仅两处声明触点(APNs sender +LiveSessionInfo.lastOutputAt,后者为 TS 任务 T-iOS-37)。relay/E2E 明确推迟(已审计 TS 加密不得随手 Swift 重写)。 - 任务机: 38 个任务(T-iOS-1…38),P0=19 任务 W0–W5 ≈13 人日、P1 W6–W8 ≈17.5、P2 W9–W10 ≈8,总 ≈38.5 人日;每任务 Owns/Depends/测试先行 Steps/Accept/估算/分派模型。关键平台事实:原生客户端 WS 握手必须显式带白名单内
Origin头(src/http/origin.ts:27 对 undefined 默认拒);URLSessionWebSocketTask.maximumMessageSize需调大到 16 MiB(回放单帧 JSON \u 转义最坏 6×2MB)。 - Review 结果: 35 findings → 32 confirmed 全部修复、3 rejected(含「ATS 不收 CIDR」被 verify 驳回 —— iOS 17+ 确实支持 CIDR exception domains)。修复亮点:plan-gate 三选一映射纠正为
acceptEdits/default/reject(对照 public/tabs.ts:345-347);「:443 破坏 Origin 匹配」为臆造 gotcha,反向纠正(new URL() 双侧归一化);ntfy 桥从「新建」改「复用既有」;CRITICAL 共享类型无主 → 全部收编进 WireProtocol(T-iOS-3);W7 Owns 重叠拆出 T-iOS-38;锁屏 Allow 加.authenticationRequired(Face ID 确认才批 shell 命令);QR 扫码改「先确认再发包」防恶意配对。 - 验证(orchestrator 亲验): 文档 943 行落盘、11 节结构完整、fixer 27 组修复日志逐条对账;
git status仅新增该 .md,src/ 零改动。未提交。下一步:用户批准后按 §8 分派批次开工(W0 首日 spike 实测 Origin header)。
✅ 安全审计 relay-e2e + relay-auth — 上线前深度审计 + 修复(完成 — 2026-07-02,分支 security/relay-auth-audit-fixes,未提交)
上线门:自动生成的 E2E/鉴权核心须过安全专家审计(前次复审判 approve-with-changes)。多 agent 编排 = 两个 ultracode Workflow:①审计(99 agents)—— 11 维 finder 并行(两包)→ 每个 finding 3 lens 对抗式 verify(exploitability/crypto-spec/refuter 多数票)→ 第二轮「重复 review」深挖 → 每包 completeness critic → dedup+定级;②修复循环(10 agents)—— foundation(types)→ 3 组文件互斥并行修复(module fixers)→ verify 循环至 tsc 干净 + 全绿 → 5 路对抗式 re-audit 确认每个 exploit 已闭合。
- 审计结论: relay-e2e = PASS 零改动(所有候选在 verify 阶段被驳回:确定性 nonce 安全 —— 方向分离 + 真实 agent 侧
createReplaySealer各自持独立单调 seq;strict-successor recv guard 无法导致 replay-accept)。relay-auth = approve-with-changes,5 条确认(2 HIGH / 1 MED / 2 LOW),全部在 human-auth/enforcement 层,全部包内可修。驳回但存档(不再复议):E2E replay nonce reuse、mTLS EKU、SPIFFE 域未 pin、reattach host-scope、TOTP 时序。完整报告见docs/REVIEW_RELAY_SECURITY.md。 - 修复(均含回归测试): F1 HIGH step-up 因子降级 ——
needsStepUp现把新鲜度绑定到精确方法(stepUpMethod),钓来的 TOTP 不再满足 passkey;F2 HIGH step-up 在新建会话路径被完全跳过(生产 callerterm-relay/data-plane/upgrade.ts:105硬编码principal:null→ 生产环境 step-up 形同虚设)—— 改为主机驱动 + fail-closed(policy.required时 principal 缺失/过期/错方法一律 403);F3 MED WebAuthn 断言未绑定已存凭据 —— 现校验 credentialId 且转发 pubkey 给 verifier;F4 LOW DPoP verify 未捕获抛出 → INV15 门未审计地崩(审计逃逸+DoS)——verifyDpopProof全体 try/catch→false +coreAuthorize边界兜成干净审计 401 + issue 校验 cnfJkt 格式;F5 LOW 验证过的 signCount 被丢弃 → 克隆检测失效 ——finishAuthentication现返回{principal,newSignCount}供持久化。 - 验证(orchestrator 亲验): relay-auth
tsc --noEmit干净 + 122 测试全绿(基线 104,+18 回归);relay-e2e 未动、tsc 干净、76 全绿。re-audit 5/5 闭合(置信 0.95–0.97,均有回归测试)。 - 遗留/behavioral note(非阻塞): F2 改为 fail-closed 后,任何
stepUpPolicyFor(host).required===true的主机,P1(term-relay)必须在 upgrade ctx 传入已认证 principal,否则正确地被拒;当前 v0.9 默认NO_STEPUP_POLICY.required===false,现网非 step-up 主机不受影响。P1 集成时须补此接线(见 relay-stepup-needs-principal-wiring)。 - 第二轮独立 review(Fable 5,换模型增强多样性)+ F6 修复: 第二个
Workflow(fix-regression 猎取 + 新角度 sweep + 对抗式 re-attack 被驳回项)判:5 条修复 0 regression、被驳回/D-i-D 项 re-attack 0 存活(确认真不可利用),但新增 1 条 HIGH——F6:relay-e2ereplay-key.ts的可恢复K_content仅由(hostContentSecret, sessionId)决定(稳定),而真实消费者agent/src/e2e/replaySeal.ts的let seq=0n在每次重建(重启/重连)时归零 → 两代 sealer 在相同 (key, nonce) 下封不同明文 = 灾难性 AEAD 复用。第一轮曾误驳此项(verifier 看到"独立单调计数器"就类比 live 路径判安全,漏了 K_content 跨重启稳定);第二轮定向 re-attack + orchestrator 亲读replaySeal.ts证实为真。修法(用户选 epoch-in-key,4 包):ReplayKeyParams加epoch;deriveContentKey把 epoch 以sessionId‖0x1f‖epoch混入 HKDF salt;createReplaySealer每代randomUUID()新 epoch 并暴露;ReplaySource带 epoch、浏览器按之重推。验证(亲验):4 包 tsc 干净 + 全绿(relay-contracts 81 / relay-e2e 78[+2 F6 回归] / agent 133 / relay-web 99);re-audit 判闭合(回归证:两代同 seq=0 nonce 但不同 key)。残留(fail-closed 非复用):ring-buffer 传输(P1/P2)须持久化并按代下发 epoch —— 已记 TODO,当前 replay 路径尚未端到端接线(manage-page.loadReplay仍是 throw stub),故 F6 此刻是潜伏漏洞。完整报告docs/REVIEW_RELAY_SECURITY.md(含 F6 + RESOLUTION)。 - 动态 e2e 安全测试台(新增
e2e/包): 跨包 harnessbuildRelayWorld()把真实 P5(auth)+P4(crypto)+P2(agent replay) 通过内存 seam + 不可信 relay「RelaySpy」攻击者视角接起来(仅 I/O 边界 fake,所有安全检查走生产代码路径)。全流程 issue cap→upgrade/authz→handshake→sealed session 过 relay→reattach→revoke。21 测试全绿、tsc 干净(亲验)。把 F1/F2/F3/F4/F6 从「静态+单测」升级为「动态攻击者实测」,另加 MITM/reflection/replay/reorder/INV2(live+replay)/cross-tenant/single-use。F5(signCount)为返回形状改动、由 relay-auth 单测覆盖;RelaySpy 代替 term-relay mux(真多进程/浏览器 e2e 需尚未建的 run tooling)。用符号链接免npm install(e2e/node_modules/*→ 各包源码)。覆盖表见docs/REVIEW_RELAY_SECURITY.md。未提交,留待用户决定。
✅ 桌面客户端 v0.1 — Electron 一体化壳(代码完成 — 2026-07-01,分支 feat/desktop-electron,未提交)
Mac/Windows 桌面 App,内嵌现有 Node 服务器 + node-pty(all-in-one):主进程 startEmbeddedServer 复用 src/server.ts 的 startServer(cfg)/loadConfig(服务器零改动 —— 它本就导出可编程启动且 import 无副作用),窗口 loadURL('http://127.0.0.1:<port>/') 复用前端零改动(前端严格同源,location.host 自动指向内嵌服务器;Origin 白名单默认含 localhost)。核心价值 = 原生通知(轮询 /live-sessions → computeNotifications → 系统通知)+ 托盘常驻 + 深链 terminalapp:// + 开机自启。
- 编排: ultracode
Workflow—— 脚手架(orchestrator 冻结desktop/src/types.ts契约 + package/tsconfig/esbuild/electron-builder)→ Build(4 builder 并行、文件互斥、对齐冻结签名)→ Review(correctness/security/typescript 三 lens 并行、schema 结构化 findings)。纯逻辑尽量抽出可单测;Electron glue(main/window/tray/menu/preload/embedded-server/logger)作为不可单测 wiring 排除出覆盖率(沿用既有 vitest.config 先例)。无用 zod、无用 electron-store(遵循项目手写校验 + 手写 JSON 持久化的最小依赖惯例)。 - 交付(
desktop/src/17 文件 +test/desktop/9 测试文件): 纯模块port(pickFreePort)/shell(平台默认 shell,Win→powershell)/server-config(env 覆盖映射)/deep-link(解析+防注入)/notify-policy(通知策略)/notifications(批量 diff)/live-poll(边界校验 /live-sessions)/prefs(手写校验)/settings-store(JSON 持久化);glueembedded-server/window(加固:contextIsolation+sandbox+拒绝外域导航)/menu/tray/preload(最小 bridge)/main/logger。 - Review 结果: 无 CRITICAL/HIGH 安全问题(Electron 加固与不可信输入处理被评为扎实)。修复的真实 bug(orchestrator 直接改+补测): ① 冷启动深链丢失(缓冲 open-url/argv 到 ready 后派发) ②
createTray无守卫会崩启动(try/catch,失败无托盘继续) ③ 首轮轮询通知轰炸(首 tick 只建基线) ④ poller 捕获过期 prefs(每 tick 读 settings.get) ⑤ notify 'tool tool' 病句→'tool approval' ⑥ live-poll Windows 反斜杠路径 + 未过滤 exited ⑦NotificationsResult.next→ReadonlyMap。 - 验证: 桌面
tsc --noEmit0 错;全量 1401 tests 绿(基线 1307 +94 桌面,零回归);覆盖率全局 91.65/85.39/92.44/93.48(阈值 80×4 通过)、desktop/src97.56/95.04/100/97.81;esbuild 打包main.cjs/preload.cjs产出、electron/node-pty 保持 external、dist 走运行时动态 import。 - 本环境无法验证(留真机/CI,DESKTOP_PLAN P1 D5–D7):
.exe(Windows,需真机/无 wine)。审批门通知源(/live-sessions不带 pendingApproval)留 P2/D8 接 hook 侧信道 —— 策略已实现且测试,当前以 status→'waiting' 为代理。方案文档docs/DESKTOP_PLAN.md。 - ✅ Mac 打包 + 启动验证(2026-07-01,本机 arm64,已实测):
electron-builder出.dmg(arm64,深度 ad-hoc 签名)。关键修复:首版只打了 node-pty、漏了服务器其它 npm 依赖(express/ws/web-push),导致仅原地能跑(沿目录树借了仓库根node_modules),移到/Applications即崩Cannot find package 'express'。改electron-builder.yml:把dist/+public/+node_modules(过滤 electron/electron-builder/esbuild/typescript 等构建工具;node-pty 经npmRebuild为 Electron 43 ABI 重编译)全部铺到磁盘 Resources(Model 2,与embedded-server.ts的process.resourcesPath解析匹配),desktop/package.json 补 express/ws/web-push 依赖。仓库外副本直接执行验证通过:服务器起、GET /+/build/main.js → 200、/live-sessions → []、node-pty 加载 OK、无Cannot find报错。main.ts默认不注册开机自启(消除 native "Operation not permitted" 噪音)。Gatekeeper:未签名 ad-hoc 在 Sequoia(darwin24)双击被拦 →xattr -cr清 quarantine/provenance + 深度签名后open/双击可正常启动(实测 PID 存活、200)。产物desktop/dist-app/Web Terminal-0.1.0-arm64.dmg(~206M,含完整 node_modules)。仅 arm64;Intel 需--x64/--universal;Windows 需真机;对外分发需 Apple Developer 签名+公证。
✅ VC 语音命令映射 — 语音直接 approve/reject 权限门(完成 — 2026-07-01,分支 v0.6-projects,未提交)
上下文门控:仅当活动 tab 有 held 权限门(pendingApproval===true 且 gate==='tool')时,语音「确认/批准/yes…」→ 复用现有 {type:'approve'} WS 通道、「拒绝/取消/no…」→ {type:'reject'};其余一律口述不变。后端零改动。安全设计:整句精确匹配(否定/口水词攻不破)+ 前导否定守卫 + reject 优先 + 置信度门(仅 approve)+ 可撤销确认窗口(1.5s,提交时复检门身份/连接,防 TOCTOU) + stale-gate epoch。新增 public/voice-commands.ts(纯匹配器 100% 覆盖)+ public/voice-confirm.ts(确认窗口 100%),接线 tabs/voice/terminal-session/main.ts。1307 tests 绿,双 review(code+security)判 CRITICAL 已全修复并补测。规划 docs/PLAN_VOICE_COMMANDS.md,详见 Detailed Log 首条。待办:confidence fail-open 取舍留给用户;tabs.ts 968 行待拆分。
✅ v0.6 Projects 命名空间自动分组 + 跨设备 prefs(完成 — 2026-07-01,分支 v0.6-projects,未提交)
Projects 页去杂乱:按点分隔命名空间自动折叠成 section(Billo.Platform ·8 / Billo.Infrastructure ·2 / Other)+ 置顶 "Active now" + 无前缀时退化为扁平网格。收藏/折叠态改服务端持久化(GET/PUT /prefs → ~/.web-terminal-prefs.json,离线回退 localStorage 镜像 + 迁移旧 proj-favs)跨设备同步。1227 tests 绿,真浏览器验证折叠跨 reload 从服务端载回。详见 Detailed Log 首条。
✅ v0.6 Project Manager 项目工作台(完成 — 2026-06-30,分支 v0.6-projects)
-
全部 6 任务完成并验证(P1–P6,多 agent 编排)。398 测试全绿(自 341 +57)· 双 typecheck 干净 ·
build:webOK · 覆盖率 90.98/81.95/89.28/93.16(阈值 80×4 通过)· 真浏览器 smoke 通过。 -
编排方式: ultracode
Workflow动态编排——Build(P2 后端 ∥ P5 前端,module-builder)→ Wire(P4 端点 ∥ P6 接线)→ Review(module-reviewer验收 +typescript-reviewer+security-reviewer,report-only 并行)→ 对每个 HIGH/CRITICAL 对抗式 Verify(module-reviewer)→ Fix(module-builder仅改已确认项)。P6 首跑因 transientserver_error挂掉(未落任何改动)→ stop +resumeFromRunId恢复:P2/P4/P5 命中缓存秒回,P6 重跑成功。 -
交付:
- P2
src/http/projects.ts(15 测试,93.37% 覆盖):parseGitHead(纯) +buildProjects(cfg, liveSessions);BFS 扫 .git(深度上限、跳过 node_modules/dotdir/symlink、命中即停下钻)→ 读.git/HEAD分支 +git status --porcelaindirty(execFile无 shell、2s 超时、并发 8)→ 合并~/.claude/projectscwd(复用history.listSessions)→ 按 cwd 前缀归并 live sessions(每次 fresh)→ TTL 缓存(仅缓存磁盘发现)+ in-flight 去重。 - P5
public/projects.ts(纯 helper 30+ 测试) + style 追加:mountProjects(镜像mountLauncher);1:N 卡片(分支 chip / dirty 点 / session 行 /+ start claude here)、搜索过滤、收藏置顶(localStorage);.home-seg段控样式也在此定义。 - P4
src/server.ts:GET /projects(只读、无 Origin 守卫、try/catch 兜底[])+ 5 集成测试(真起 server)。 - P6
public/tabs.ts:openProject(同名#n后缀、addEntry(null,label,cwd,'claude\r'))+countOpenWithTitlePrefix+ 首页 Sessions↔Projects 段控(updateHomeView);7 新测试。
- P2
-
Review 12 findings → 4 HIGH 全确认并修复: ① A3 关键:
openProject/newTabForResume缺回车\r(claude 不自动执行)→ 补\r;② 段控被定位面板盖住 → 面板inset:44px 0 0 0;③mergeHistory串行 I/O →mapWithConcurrency;④ discover 并发缓存击穿 → in-flight promise 去重。 -
Orchestrator 追加加固(MEDIUM/LOW,本人直接改 + 补测): 前端
fetchProjects加normalizeProject(API 响应边界校验,缺sessions不再 TypeError)+getFavs过滤非字符串(localStorage 防污);config.parseProjectRoots相对路径 fail-fast(+ config 补 v0.6 解析测试,config.ts 95.87%→98%)。style.css1143 行 > 800 上限属既有债(v0.6 前已超),esbuild 仍打单文件,留作后续拆分。 -
真浏览器 smoke(
PROJECT_ROOTS=~/Documents/git,83 真 repo): 首页段控 Sessions/Projects 切换 ✓;Projects 面板 83 卡片(web-terminal显示branch:v0.6-projects+ dirty 正确,含feat/...斜杠分支)+ 搜索框 ✓;点web-terminal卡 → 开 tab 标签web-terminal、首页隐藏、终端出现 claude 启动界面(A3 端到端)✓;console 仅既有/favicon.ico404 + apple-mobile 警告,无 v0.6 报错。 -
待办: 真机/手机验收(同历史 F4/F7 遗留);可选 server.ts 抽 routes;
style.css拆分。 -
当前阶段: v0.3 全部完成(H1–H4 + M3/M6/M7 已并入 main;O2 历史浏览 在分支
o2-history,待合并)。- ✅ Step1 前端快赢 · ✅ H2/H4 状态感知 · ✅ H3 远程批准 · ✅ H1 tmux 保活 · ✅ M3/M7/M6 · ✅ O2 历史会话浏览/resume · ✅ tech-debt 清理(@ts-ignore 去掉)· ✅ UI:终端不再打印 Connecting/Connected(靠标签点)。
- ✅ 现代化 UI 主题(design-token 调色板/靛蓝强调色/圆角芯片标签+键栏/毛玻璃弹层)· ✅ 键栏功能字幕(每键下方标作用)+ 新增 ^R/^L/^D(共 17 键)· ✅ ⌨ 快捷键速查弹层(
public/shortcuts.ts)。 - ✅ v0.4 多设备共享会话:放宽不变式 #5 —— 一个 session 可挂多个 WS 客户端(镜像;输出/退出/状态广播,谁都能打字)。
GET /live-sessions+ 打开应用自动把主机活动会话显示为 tab;🔗分享二维码 /?join=<id>加入指定会话。 - ✅ 镜像尺寸 = 最近活跃端优先(latest-writer-wins):共享 PTY 只能一个尺寸,之前取最小值导致宽屏桌面被 iPad 夹小(iPad 全屏、桌面留黑边)。改为"最近 fit/聚焦的设备决定尺寸",你正在用的设备永远全屏;attach/detach/
blur都不改尺寸,前端在 pane 显示 + 窗口聚焦时重发 dims(切设备即夺回全屏)。实测两端:200×50 → iPad 100×40 → 桌面 refit 200×50 → iPad blur 不变。后端纯客户端实测镜像输出 + 共享输入通过。 - ✅ 🗂 会话管理页(
/manage.html独立页,非弹窗):实时预览缩略图网格 —— 每张卡片用只读 xterm 渲染该 session 当前屏幕(像截图,缩放),一眼看出每个 session 在干嘛;RingBuffer.tail()+GET /live-sessions/:id/preview(不 attach、不影响观看数/回收),每 4s 刷新;可点缩略图/Open 进入、Kill / 批量 Kill。实测 3 个 session 缩略图(top / git log / ls)彩色可辨,Kill 3→2→0。 - ✅ v0.5 首页 = 会话选择器(launcher):打开应用不再自动建/恢复 tab,而是落到选择器 —— 主机所有运行中 session 的实时缩略图网格,用户自己点开(回放完整 scrollback)或
+ New session;关掉最后一个 tab 回到选择器。public/launcher.ts,复用.mg-*卡片样式。实测:首次加载 0 tab + 缩略图;Open→建 tab、选择器隐藏;关最后一个→选择器回来。 - ⬜ 仅剩:O1 token 认证(可选,默认关)+ F4/F7 真机验收(局域网/手机)。
- 225 测试全绿;工具栏 🔍搜索 ⚙设置 ▦仪表盘 🕘历史 ⌨快捷键 🔗分享 🗂管理 📱QR。
- 下一步: O1(可选)或真机验收。
- v0.2/v0.1 历史见下方条目。
-
Wave: W0–W5 基本完成。v0.1 功能完整、测试与真机浏览器验证通过。仅剩 2 项需用户真设备。
-
进度: 20/21 任务(T19 待真手机)· 187 自动化测试全绿 · 真 PTY 端到端验证 · 真浏览器 smoke 通过。
-
验收 F1–F9: ✅ F1/F2(浏览器 smoke:渲染+输入输出+全彩)· ✅ F3(resize 无错)· ⬜ F4 需局域网真机 · ✅ F5/F6(集成 ⑤⑥ 回放 CJK/ANSI)· ⬜ F7 需真手机(软键盘栏)· ✅ F8(浏览器 exit→重连提示)· ✅ F9(集成+curl 401)。
-
下一步(交用户): 真手机/另一台电脑开
http://<你IP>:3000实测 F4(局域网可用)与 F7(Esc/Shift+Tab/方向键等触摸键生效)。 -
阻塞: 无(F4/F7 非阻塞,属需物理设备的人工验收)。
-
次要 tech-debt: main.ts 1 处
@ts-ignore(CSS import);未来加*.cssd.ts。 -
最后更新: 2026-06-20(并入 review 全面修复,见下方条目)
维护性修复条目 (Review Fixes)
2026-06-20 · 按 REVIEW_REPORT 逐项修复(安全/架构/质量/测试)
- 状态:
[x]DONE(typecheck 干净 · 341 测试全绿(16 文件,自 228 起 +113)·build:web通过 · 覆盖率 90.3/80.7/87.6/92.4 ≥80 阈值已在vitest.config.ts强制)。 - 来源: 4 个 agent 并行 review 产出
docs/REVIEW_REPORT.md;用户决定修全部四档,/sessions保持现状仅加说明,clientDims/blur死状态彻底删除。计划见~/.claude/plans/piped-marinating-parrot.md。 - 🔴 关键:
- 多设备审批竞态(
server.tsws.close):仅当最后一个 client 离开时才resolvePending—— 关一个镜像不再取消另一台正在看的待批准。 - 会话无上限 DoS:新增
Config.maxSessions(默认 50,envMAX_SESSIONS)+manager.assertUnderSessionCap(),超限抛错复用 M4 的 exit(-1) 路径。 - 信号处理器泄漏:
onSignal/onUncaught提具名引用,close()里process.off全移除(去掉测试setMaxListeners(50)遮掩)。 - 前端
initialInput700ms 定时器:跟踪initialInputTimer、dispose()清理、disposed守卫、INITIAL_INPUT_DELAY_MS常量。 tabs.ts addEntry的null as unknown as TerminalSession类型洞:先建 session 再建 entry。
- 多设备审批竞态(
- 🟠 应修: 安全头中间件(无 helmet)+
DELETE /live-sessions[/:id]Origin/CSRF 守卫(403);history.ts改fs/promises(listSessions异步,/sessionsawait);彻底删除clientDims+blur(types/session/protocol/server/manager 注释/terminal-session.hide + 相关测试);WS 连接级限频(maxMsgsPerSec默认 2000,超限丢帧不断连);/sessions保持行为,加注释 +TECH_DOC §7记录已接受风险。 - 🟡 测试: 新增
test/{tmux,preview-grid,terminal-session,tabs}.test.ts(后两者 jsdom + mock WebSocket);扩history/config/manager/integration覆盖 killById/handleHookEvent/maxSessions/resolveUseTmux/live-sessions/preview-404/approve-reject/origin-guard/限频/信号泄漏回归。 - ⚪ 卫生:
parsePositiveInt→parseNonNegativeInt;ALLOWED_ORIGINS scheme 校验;server.ts日志注入 sanitize +isLoopback健壮化(127.0.0.0/8 + IPv4-mapped);运维常量(PERM_TIMEOUT_MS/REAP_INTERVAL_MS/PREVIEW_BYTES)入 Config;抽public/preview-grid.ts消除 launcher/manage 重复(类型复用LiveSessionInfo);ARCHITECTURE §8 记录运行时句柄可变例外(H6)+ #5/#7/#8 更新;CLAUDE.md「auto-show as tabs」→ v0.5 launcher;「min across clients」陈旧注释清扫。 - 验证:
npm run typecheckPASS ·npx vitest run341/341 ·npm run build:webOK ·npx vitest run --coverage退出 0(阈值 80×4)。 - 决策 / 偏离: 覆盖率
include收窄到src/**+ 四个有逻辑的前端模块,排除纯 DOM 接线/入口胶水(E2E 范畴),未下调任何阈值。未把审批状态机搬进 manager(报告 #10)—— close-handler 修法已正确最小,server.ts抽 routes 留作可选后续。 - 遗留 / 待办: 真机验收 F4/F7(同 v0.1 遗留);可选 server.ts 抽
http/routes.ts;胶水文件若要纳入覆盖率需补 E2E。 - commit: (本次提交)
任务总览 (Task Overview)
任务定义见 PLAN.md;按 Wave 分组。
Steps完成数 = PLAN 中该任务的勾选项。 多 agent 并行:同一 Wave 内任务互不依赖,可并行;跨 Wave 按依赖推进。
| 任务 | Wave | 模块 | 状态 | Steps (完成/总) | 完成日期 |
|---|---|---|---|---|---|
| T1 | W0 | 脚手架 + 装齐依赖 | [x] |
5/5 | 2026-06-16 |
| T2 | W0 | src/types.ts 共享契约(冻结源) |
[x] |
8/8 | 2026-06-16 |
| T3 | W0 | mock IPty 测试替身 | [x] |
2/2 | 2026-06-16 |
| T4 | W1 | config.ts |
[x] |
5/5 | 2026-06-16 |
| T5 | W1 | protocol.ts |
[x] |
4/4 | 2026-06-16 |
| T6 | W1 | ring-buffer.ts |
[x] |
4/4 | 2026-06-16 |
| T7 | W1 | http/origin.ts |
[x] |
3/3 | 2026-06-16 |
| T8 | W1 | public/index.html |
[x] |
1/1 | 2026-06-16 |
| T9 | W1 | public/style.css |
[x] |
1/1 | 2026-06-16 |
| T10 | W1 | public/keybar.ts |
[x] |
4/4 | 2026-06-16 |
| T11 | W1 | public/main.ts |
[x] |
6/6 | 2026-06-16 |
| T12 | W2 | session/session.ts |
[x] |
7/7 | 2026-06-16 |
| T13 | W3 | session/manager.ts |
[x] |
6/6 | 2026-06-16 |
| T14 | W4 | server.ts 接线 |
[x] |
6/6 | 2026-06-17 |
| T15 | W4 | 集成 / E2E 测试 | [x] |
2/2 | 2026-06-17 |
| T16 | W5 | 验收 F1–F3(终端/TUI/resize) | [x] |
3/3 | 2026-06-17 |
| T17 | W5 | 验收 F4+F9(LAN/Origin) | [~] |
1/2 | 2026-06-17 |
| T18 | W5 | 验收 F5+F6(保活/回放) | [x] |
2/2 | 2026-06-17 |
| T19 | W5 | 验收 F7(移动键栏) | [ ] |
0/1 | 需真机 |
| T20 | W5 | 验收 F8(退出处理) | [x] |
2/2 | 2026-06-17 |
| T21 | W5 | 安全核对 + README + LOG 收尾 | [x] |
3/3 | 2026-06-17 |
验收清单映射 (Acceptance — TECH_DOC §8)
功能验收独立于阶段勾选;F* 全绿才算 v0.1 完成。
| 编号 | 功能 | 状态 | 验证方式 / 备注 |
|---|---|---|---|
| F1 | 交互式终端(ls/补全/Ctrl+C/历史) | [x] |
浏览器 smoke:输入→输出往返 |
| F2 | 全彩 + 全屏程序(vim/top/claude) | [x] |
浏览器 smoke:彩色提示渲染(深 TUI 同理) |
| F3 | 终端自适应(resize 不错位) | [x] |
viewport 变更存活、无 error |
| F4 | 局域网访问(手机/他机) | [ ] |
需真机(同 WiFi 开 http://:3000);Origin 白名单已含网卡 IP |
| F5 | 会话保活(断开重连仍在跑) | [x] |
集成 ⑤;tmux 重启保活(H1) |
| F6 | 重连回放(环形缓冲) | [x] |
集成 ⑥:ANSI/中文不乱码(M2) |
| F7 | 移动快捷键栏 | [ ] |
需真手机(触摸键生效、不弹软键盘) |
| F8 | 进程退出处理 | [x] |
浏览器 smoke:exit→重连提示;detach 后退出 L1 |
| F9 | Origin 校验(401) | [x] |
集成 ①②;curl 坏 Origin→401;/term 限制(L3) |
详细日志 (Detailed Log)
倒序,最新在最上。每个子任务一条。复制下方模板填写。
2026-07-01 · VC 语音命令映射 — 上下文门控 confirm/reject(多 agent 编排 + 双 review)
-
状态:
[x]DONE(未提交,分支v0.6-projects) -
背景: 已有 v0.7 A2 push-to-talk 只做「语音→文字」口述;本次加语音命令层,让语音直接 approve/reject 权限门。规划见
docs/PLAN_VOICE_COMMANDS.md(多 agent:理解→前后端计划→4 路对抗 review→综合)。锁定决策:①上下文感知(仅当活动 tabpendingApproval===true才当命令,否则口述不变)②只做 confirm 类,复用现有approve/rejectWS 通道(后端零改动)③危险键不接线,two-step 组件仅用于 approve 确认窗口。 -
改动:
- 新增
public/voice-commands.ts(纯匹配器,100% 覆盖):matchCommand= 整句精确 + 前导否定守卫 + reject 优先 + 置信度门(仅 approve);中英确认/否定词表。 - 新增
public/voice-confirm.ts(确认窗口控制器,100% 覆盖):createApproveConfirm(1500)arm/cancel/isArmed,纯计时(注入 setTimeout,无 DOM)。 public/voice.ts:onTranscript多传confidence(仍只转写)。public/terminal-session.ts:加pendingEpoch(false→true 翻转自增,stale-gate nonce)。public/tabs.ts:handleVoiceTranscript分派 + 确认窗口 arm + WS 断开闪「未连接」+ 单独#voice-confirmoverlay(避 hideInterim 清屏)。public/main.ts:接onVoiceTrigger点亮 🎤。vitest.config.ts:两新模块入 coverage.include(orchestrator 协调编辑)。- 测试:
test/voice-commands.test.ts/test/voice-confirm.test.ts新建;test/tabs.test.ts加 VC 集成(含 6 条 review-fix 用例);test/voice.test.ts4 处旧断言随(text,confidence)新签名更新 + 补 confidence 转发断言。
- 新增
-
验证:
npm run typecheck干净;npm test→ 1307 passed / 43 files;build:webOK;覆盖率 voice-commands/voice-confirm 100×4、tabs.ts 94/84/89/97、TOTAL 91.3/84.6/91.7/93.2(80×4 通过)。git diff src/空(后端 NONE 成立)。 -
决策 / 偏离 PLAN:
- 实现 workflow 首跑失败(review 阶段某 agent 6 次 stall)——但 Build V1/V2 已落文件;改为 orchestrator 亲自跑验证 + 用普通 agent 并行做 code/security review(避开 workflow 卡死)。
- 两份 review 独立收敛,判 CRITICAL/BLOCK,均已修复并补测:①确认窗口 TOCTOU:
onCommit现复检pendingApproval && gate==='tool' && pendingEpoch===armedEpoch && connected,否则丢弃;②**cancel()从未接线**:接入新话语顶替(handler 顶部)、reject 分支、#voice-confirm点击;③WS 断开延迟提交错位:提交前复检连接。 - 偏离 PLAN §4:「松开麦克风取消」在 PTT 时序下不成立(松麦克风正是触发最终转录/arm 的动作),改为 tap + 新话语顶替 + reject 取消 三条真实取消路径。
startsWithNegation重写(review MEDIUM):原prefix+rest[0]实际只测 prefix0,改为显式测rest。
-
遗留 / 待办:
- confidence fail-open(review MEDIUM,保留):
confidence===undefined时置信度门短路(符合 §D「有置信度时才门控」),靠确认窗口兜底误听;若要 fail-safe(undefined→不批准)是一行改动,但会让不报告置信度的引擎(部分非 Chromium)语音批准失效——留给用户定。 public/tabs.ts已 968 行 > 800 上限(既有债 + 本次 ~99 行):本次仅加最小 diff,待后续抽voice-controller拆分(decision F)。
- confidence fail-open(review MEDIUM,保留):
-
commit: N/A(未提交)
-
状态:
[x]DONE。双 typecheck 干净 ·build:webOK · 全量vitest run1227 passed (41 files) · 真浏览器端到端验证通过。 -
需求(用户): Projects 页扁平网格太乱(50–100 repo)。多 agent 探索(4 路:自动分组/手动文件夹/密度分段/竞品调研)结论一致 → 主方向=按点分隔命名空间自动分组(repo 名如
Billo.Platform.Payment天然带层级),配 Active-now 置顶;手动文件夹只是加分项非主线。用户选:范围=自动分组+Active band,持久化=服务端(收藏/折叠态跨设备同步,修掉 localStorage 每设备各一份的缺陷)。 -
改动:
- 后端:
src/types.ts加UiPrefs {favourites[], collapsed{}}+Config.prefsStorePath;src/config.ts解析PREFS_STORE_PATH(默认~/.web-terminal-prefs.json,仿 push-store);新增src/http/prefs-store.ts(sanitizePrefs/loadPrefsStore,镜像 subscription-store:全字段有界强制、缺失/损坏→空默认、不可变替换);src/server.ts加GET /prefs(只读无 Origin 守卫,同 /projects)+PUT /prefs(状态变更→requireAllowedOriginCSRF 守卫 + body sanitize)。 - 前端: 新增
public/prefs.ts(loadPrefs/savePrefs:服务端为准、离线回退 localStorage 镜像、迁移旧proj-favs);public/projects.ts加纯函数groupProjects()(depth-2 命名空间键 +MIN_GROUP_SIZE=2阈值→单成员落 "Other" + 无命名空间收益时退化为单 flat 组 + 命名空间组按最近活跃排序 + 复制活跃项进置顶 "Active now" 组)、displayLabel()(组内卡片只显示前缀后的尾段,如Payment)、makeGroupHeader()(caret+label+count+"N active" 徽标,可折叠/键盘可达,Active-now 不可折叠);makeProjectCard加可选displayName形参;renderGrid改为按组渲染(每组一个.proj-grid-cards子网格 + 折叠态来自内存 prefs + 搜索时强制展开命中组 + 保留 scroll);mountProjects状态改用内存favs/collapsed,init()首次loadPrefs后再渲染,5s tick 只拉 projects 不重载 prefs(避免覆盖本地编辑);折叠/收藏改走persist()。style.css:.proj-grid改为纵向 section 栈 + 新.proj-grid-cards承接原 minmax 网格 +.proj-group*组头(sticky、hover、focus-visible、● N active绿色徽标)。 - 未做(按用户选的最小范围): 密度/紧凑列表切换、group-by 控件、frecency、模糊搜索、手动文件夹——均为探索报告里的后续可选层。
- 后端:
-
验证:
npx tsc --noEmit干净;npm run build:webOK(bundle 665kb);vitest run1227 passed(新增test/prefs-store.test.ts12 项 sanitize/load/persist +test/projects-panel.test.ts加 21 项 groupProjects/displayLabel)。真浏览器(临时 server + 假点名 repo):Projects 页渲染出▼ Billo.Platform ·8(卡片尾段 B2B.Api/Payment/…)、▼ Billo.Infrastructure ·2(EventBus/MongoDb)、▼ Other ·12;点组头折叠 → live DOMcollapsed+0 卡、PUT /prefs落{"Billo.Platform":true};整页 reload 后仍折叠(从服务端 /prefs 载回)=跨设备持久化打通。控制台 503/404 为既有(无 VAPID 的 push + 缺图标),非本次改动;/prefs、/projects、/config/ui 均 200。 -
决策 / 偏离 PLAN: 无既有 PLAN 覆盖此增量;探索用 4 个并行 general-purpose agent(用户明确"用多 agent 探索")。
getFavs/saveFavs仍导出(旧测试引用)但 mountProjects 不再调用,由public/prefs.ts取代。 -
遗留 / 待办: ① 两个同名 depth-2 repo(如两个
Billo.Customer)会成一个以自身命名的组、卡片显示全名(displayLabel 无尾段)——功能正确、观感略冗余,可接受。② 5s tick 不重载 prefs → 另一设备的改动要下次进页才见(单用户可接受)。③ 后续如上手动文件夹/密度切换,持久化接口已就绪(prefs-store 可扩字段)。 -
commit: (待提交)
2026-06-30 · v0.7 Walk-away Workbench — 多 agent 并行实现 (Band A+B)
- 状态:
[x]BUILT + 全量门禁绿(详细验收/打磨待续)。双 typecheck 干净 ·build:webOK · 全量 vitest + 覆盖率 91.37/84.06/92.18/93.37(≥80×4)。 - 实现: 按
docs/PLAN_WALKAWAY_WORKBENCH.md的 27 任务波次(R0→W0→W1×14→W2→W3→W4)以module-builder并行实现;23 任务 done, 0 blocked。功能: A1 Web Push + 锁屏审批(新依赖 web-push)、A2 语音口述、A3 快捷回复 chips + 提示词调色板、A4 活动时间线、A5 卡住/静默告警;B1 只读 git diff、B2 statusLine 遥测仪表、B3 从 UI 建 worktree、B4 plan/权限模式中继。新增src/push/*、src/http/{diff,statusline}.ts、public/{diff,timeline,...}.ts、statusLine 脚本等;扩 hook/manager/server-wire/termsession/tabs/projects-ui/sw。 - Orchestrator 修复 (W0 协调缺口): T-types 漏给
Config接口加 21 个 v0.7 字段(只在 config.ts 返回对象里);手工把 21 字段补进src/types.ts的 Config + 调config.ts(签名回:Config、base 去 satisfies)使其编译。移除过严的 W0 gate(全量 tsc 在契约领先实现期本就不能过——该检查归 W4/收尾)后resumeFromRunId续跑 W1–W4。 - W4 评审: 安全 6 项全过(每个状态变更路由带 Origin/CSRF;VAPID 私钥不外泄/不记日志;git
execFile无注入;WS Origin 防御无回归)。遗留(非阻塞, 无 CRITICAL/HIGH): ① protocol.tsapprove.mode校验应入parseClientMessage(现 server.tsparseApproveMode兜底, 功能与安全正确)② CSPconnect-src去掉ws:/wss:通配(既有, 低危)③ worktreebaseref 加格式校验 ④ protocol/preview-grid 补 2 个针对性测试。完整 27 条任务日志见 workflow result 文件。 - 验证:
npx tsc -p tsconfig.json/.web.json --noEmit干净;npx vitest run --coverage全绿 ≥80×4;npm run build:webOK;web-push已 npm install。 - commit: (本次提交)
2026-06-30 · v0.6 工具栏图标换成线性图标 + hover tooltip
- 状态:
[x]DONE。web typecheck 干净 ·build:webOK · 真浏览器验证通过。 - 需求(用户): 工具栏那些 emoji 图标(🔍⚙▦🕘⌨🔗📱)不好看,换成符合主题色的图标;hover 时显示图标含义。
- 改动(纯前端):
public/icons.ts加 7 个 lucide 线性图标(MIT,stroke=currentColor,脚本从 unpkg 抓取生成):ICON_SEARCH/SETTINGS/DASHBOARD/HISTORY/KEYBOARD/SHARE/DEVICE。7 个工具栏模块(search/settings/dashboard/history/shortcuts/share/qr)把textContent=emoji换成innerHTML=ICON_*+dataset.tip(tooltip 文案)+aria-label。style.css.toolbtn:svg18px、默认--text-dim、hover 变 Amber;新增[data-tip]::afterCSS hover tooltip(右对齐避免右边缘裁切)。 - 验证:
npx tsc -p tsconfig.web.json干净;build:webOK;bundle 内 0 toolbar emoji;真浏览器:7 按钮均为线性图标(--text-dim色)、hover 变琥珀 + tooltip(实测 "All sessions" 等显示)。 - 遗留: share/qr 的 aria-label 仍是旧描述(无害,屏幕阅读器友好);可选统一。
- commit: (本次提交)
2026-06-30 · v0.6 "New session" 改为网格卡片(去掉别扭的大按钮)
- 状态:
[x]DONE。web typecheck 干净 ·build:webOK · 真浏览器验证通过。 - 需求(用户): 头部那个大的实心琥珀 "+ New session" 按钮很别扭(与精致的分段 pill 不协调)。
- 改动(纯前端
public/launcher.ts+ style): 删头部.launcher-new按钮 +.launcher-actions;改为 grid 内的.mg-new-card虚线"+ New session"瓦片(与 session 卡同尺寸,始终排在网格第一格,每次 refresh 重新 prepend),hover 变琥珀。删冗余.mg-empty空态(瓦片即 CTA)。头部只剩标题 + 段控,更干净。 - 验证:
npx tsc -p tsconfig.web.json干净;build:webOK;preview-grid测试 14 全绿;真浏览器:Sessions 网格首格为虚线 New 瓦片 + 其后 session 卡(Open/Kill),无旧大按钮。 - commit: (本次提交)
2026-06-30 · v0.6 删除 manage 独立页 — Sessions 选择器即会话管理
- 状态:
[x]DONE。453 测试全绿 · web typecheck 干净 ·build:webOK(仅 main,无 manage 产物)· 真浏览器验证通过。 - 需求(用户): manage 页面不要了——在 Sessions 选择器这里直接管理 session(添加 + 删除)。
- 改动:
- 删
public/manage.html+public/manage.ts(及 build 产物);package.json的build:web/dev:web去掉public/manage.ts入口。 - 删
public/main.ts工具栏 🗂 按钮(原跳/manage.html);删public/launcher.ts头部 "🗂 Manage" 链接。 - 加
launcher.ts每张 session 卡 Kill ✕(makePreviewCard的extraActions,复用 manage 的killOne:DELETE /live-sessions/:id→ refresh)。.mg-kill样式已存在,直接复用。 - 后端 DELETE/preview 路由保留(仍由 launcher kill + 缩略图 + projects 复用);注释里的 "manage page" 字样属历史,无害。
- 删
- 结果: Sessions 视图 = 完整会话管理——+New session(加)· Open ↗(进)· Kill ✕(删),配实时缩略图。bulk「Kill all/detached」未迁移(如需可加)。
- 验证:
npx tsc -p tsconfig.web.json干净;npx vitest run453/453;build:web仅出 main(无 manage.*);真浏览器:Sessions 卡含 Open+Kill、无 Manage 入口、点 Kill 2→1、GET /manage.html→404。 - 遗留: manage-only 死 CSS(
#manage-root/.mg-header/.mg-bar/.mg-title/.mg-sub/.mg-btn)留作可选清理;README 若提 manage 页待更新。 - commit: (本次提交)
2026-06-30 · v0.6 项目网格卡片 — 直接关掉 session
- 状态:
[x]DONE。51 projects-panel 测试全绿(+1)· web typecheck 干净 ·build:webOK · 真浏览器验证通过。 - 需求(用户): Projects 页面(网格卡片)下应该能直接关掉 session(此前只有详情页有 Kill,卡片 session 行只能点进入)。
- 改动(纯前端
public/projects.ts+ style):makeSessionRow加可选onKill→ 行尾 ✕ 按钮(.proj-session-kill,stopPropagation 不触发进入);makeProjectCard加可选onKillSession透传;mountProjects加killAndRefresh(DELETE /live-sessions/:id后 refresh)传入网格卡片。CSS.proj-session-kill(hover 变红)。 - 测试(+1): 卡片 session 行——无 onKillSession 时无 ✕;有时点 ✕ 调 onKill(id) 且不触发 onEnterSession。
- 验证:
npx tsc -p tsconfig.web.json干净;npx vitest run projects-panel51/51;真浏览器:web-terminal 卡 2 个 session 行 → 点 ✕ → 杀掉并刷新为 1 行。 - commit: (本次提交)
2026-06-30 · v0.6 项目详情页 — 展示 CLAUDE.md + /init 生成/更新按钮
- 状态:
[x]DONE。458 测试全绿(+5)· 双 typecheck 干净 ·build:webOK · 真端到端(curl + 真浏览器)验证通过。 - 需求(用户): 详情页展示 CLAUDE.md,加一个按钮自动更新 / 生成 CLAUDE.md。决策(AskUserQuestion): 展示 + 一个交互式 /init 按钮(非静默后台覆盖,便于 review)。
- 改动:
- 后端:
ProjectDetail加hasClaudeMd/claudeMd?;projects.tsreadClaudeMd(读<repo>/CLAUDE.md,截断 64KB,best-effort)并入buildProjectDetail的 Promise.all。 - 前端:
renderProjectDetail加 CLAUDE.md 段——标题行带按钮(有则↻ Update、无则✨ Generate),内容滚动框(.proj-claudemd,monospace,max-h 320);无则空态提示。按钮 →onOpenProject(path, name, 'claude "/init"\\r')开交互式 Claude 跑/init(复用 launcher,零新后端,byte-shuttle 不变)。style 加.proj-section-row/.proj-claudemd-btn/.proj-claudemd。 - 测试(+5): buildProjectDetail 读/无 CLAUDE.md;renderProjectDetail 有内容+Update、无内容+Generate+空态、按钮调
onOpenProject(...,'claude "/init"\\r')。
- 后端:
- 验证:
npx tsc(双)干净;npx vitest run458/458;build:webOK;curl/projects/detail返回hasClaudeMd:true+ 13KB 内容;真浏览器:web-terminal 详情显示 CLAUDE.md 滚动框 + ↻ Update 按钮(段序 Branch/Active sessions/CLAUDE.md/Start)。 - 注:
/init经claude "/init"作为初始 prompt;若该形式不直接触发斜杠命令,会话是交互式的、用户可手动输/init(一行可调)。 - commit: (本次提交)
2026-06-30 · v0.6 项目详情页 — branch/worktree + 详细 active session
- 状态:
[x]DONE。452 测试全绿(+21)· 双 typecheck 干净 ·build:webOK · 覆盖率 90.16/81.32(worktrees.ts100% /projects.ts93.8%)· 真端到端(curl + 真浏览器)验证通过。 - 需求(用户): 每个 project 的详情页——详细显示具体 active 的 session,以及具体的 branch 或 worktree。
- 设计: 点项目卡的名字进入应用内详情视图(
renderProjectDetail,在 Projects 面板内切换,带"← All projects"返回)。分区:① 头部(名/路径/branch chip/dirty)② Branch / Worktrees(git worktree list每条:branch 或detached @ head+ main/current/locked/prunable 标签 + 路径;单 worktree 即显示分支)③ Active sessions(N)(每条:状态点 + 标题 +状态·👁设备数·started 多久前+ Open + Kill ✕)④ Start 启动器行(复用 Claude/Codex/Code)。再进 Projects 总是回到网格(不残留详情)。 - 后端: 新增
src/http/worktrees.ts(parseWorktrees(纯)+listWorktrees,git worktree list --porcelain,execFile 无 shell,2s 超时,best-effort→[]);projects.ts加buildProjectDetail(cfg, path, liveSessions)(校验绝对存在目录→否则 null;复用 readBranch/readDirty/hasGitEntry/matchSessions;不缓存,详情按需要新鲜)。src/server.tsGET /projects/detail?path=(只读、无 Origin 守卫,同 /projects;path 缺→400、非法/不存在→404)。types 加WorktreeInfo/ProjectDetail。 - 前端(
public/projects.ts+ style):fetchProjectDetail/killSession;makeProjectCard加可选onOpenDetail(名字变可点链接.proj-name-link);mountProjects加 grid↔detail 视图切换 + 自动刷新路由(详情每 5s 刷新);Kill 走DELETE /live-sessions/:id(同源带 Origin)。详情视图样式.proj-detail*/.proj-wt-*/.proj-dsession*。session meta 在 status=unknown 时省略状态点(不显示· ·)。 - 测试(+21):
test/worktrees.test.ts(parse:单/多/detached/locked/prunable/无尾空行/空);projects.test.tsbuildProjectDetail(相对/不存在/文件→null、非 git 目录、假 repo 读 branch、session 按 cwd 前缀归并、真 git init worktree(realpath 规避 /var symlink));projects-panel.test.tsrenderProjectDetail(null→not found+Back、头部、worktree 行+current tag、detached、session Open/Kill 接线+忽略 exited、空态+启动器、非 git)+ 名字链接。 - 验证:
npx tsc(双)干净;npx vitest run --coverage452/452(≥80×4);真浏览器:开 web-terminal 的 Claude session→进详情→显示 branchv0.6-projects(MAIN/CURRENT)+ Active sessions(1)(web-terminal,👁1,Open/✕)+ Start 启动器 + 返回。curl/projects/detail返回 worktree(main/current/head)+ 缺/相对 path→400/404。 - 安全:
/projects/detail只读(git branch/status/worktree-list,execFile 无 shell + 超时),path 校验绝对存在目录;与 /projects 同威胁模型(不新增可变更面)。 - commit: (本次提交)
2026-06-30 · v0.6 项目卡启动器 — Claude · Codex · VS Code logo 按钮
- 状态:
[x]DONE。覆盖率 90.6/81.48/88.71/92.71(≥80×4)· 双 typecheck 干净 ·build:webOK · 真浏览器 + 真 VS Code 端到端验证通过。 - 需求(用户): 把卡片上大的"+ start claude here"换成 Claude logo;新增 VS Code logo(点击在主机打开 VSC)与 Codex logo 按钮;logo 编排要"符合应用性"。
- 设计: 每张项目卡的动作区改为启动器行——3 个品牌 logo 图标按钮(logo 在上、小字标签在下、touch 友好、各带 tooltip),有运行中 session 时 session 行列在启动器上方。Claude=珊瑚色爆发、Codex=OpenAI blossom(近白)、Code=VS Code 蓝丝带;logo 用
fill=currentColor,颜色由 CSS 品牌色驱动。 - 行为: Claude→新终端 tab 跑
claude;Codex→新终端 tab 跑codex(复用openProject(path,name,cmd),onOpenProject加cmd?参数);VS Code→POST /open-in-editor让主机跑code <path>(文件在主机,故在主机开;不分查看设备)。主机已确认装有 claude/codex/code。 - 活跃高亮(用户追加): 项目若有活跃 Claude session(运行中且 hook 状态 ≠
unknown——纯 shell/codex 保持 unknown 不算),Claude logo 按钮珊瑚色高亮(coral ring + 淡底 + 浅色标签);>1 个时角标显示数量。activeClaudeCount(project)判定。 - 改动:
- 新增
public/icons.ts(simple-icons 的 Claude/OpenAI/VSCode 单路径 SVG,脚本生成避免手抄出错)。 public/projects.ts:makeLauncherRow/makeLauncher+openProjectInEditor(fetch/open-in-editor);ProjectsHooks.onOpenProject加cmd?;导出makeProjectCard便于测试;删旧.proj-new用法。public/tabs.ts透传 cmd。public/style.css新增.proj-launchers/.proj-launch*(品牌色),删旧.proj-new规则。- 后端:
src/http/editor.tsopenInEditor(cfg,path)(校验绝对路径+存在+目录→execFile(无 shell)启动,best-effort,error 事件兜底+unref);src/server.tsPOST /open-in-editor(Origin 守卫,与 DELETE 同档);Config.editorCmd(EDITOR_CMD,默认code)入 types/config。 - 测试: 新增
test/editor.test.ts(校验 400/404 +editorCmd='true'成功 204);config.test.ts加 editorCmd;projects-panel.test.ts加 makeProjectCard 启动器行(3 按钮/logo/Claude·Codex cmd)。
- 新增
- 验证:
npx tsc(双)干净;npx vitest run --coverage全绿(≥80×4);build:webOK。真端到端:curl/open-in-editor无 Origin→403、缺/相对/不存在 path→400/400/404、合法 dir→204 且 VS Code(Electron pid)实际打开;真浏览器 Projects 视图每卡 3 logo 按钮(珊瑚/白/蓝,标签 Claude/Codex/Code,tooltip 正确)。 - 安全:
/open-in-editor状态变更→Origin 守卫(CSRF);execFileargv 传参无 shell 注入面;path 校验为绝对存在目录;不超出"应用本就给主机 shell"的既有威胁模型。命令来自 config 非用户。 - commit: (本次提交)
2026-06-30 · v0.6 UI 打磨 — 首页段控重设计 + 选择器隐藏键栏
- 状态:
[x]DONE。415 测试全绿 · 双 typecheck 干净 ·build:webOK · 真浏览器验证通过。 - 反馈(用户): ① Sessions/Projects 段控在 UI 上"有点别扭"(原为两个独立描边方块浮在顶部);② 首页(Sessions/Projects 视图)底部那排终端快捷键栏没必要显示。
- 修复(纯前端,
public/style.css+public/tabs.ts): ①.home-seg重设计为 iOS 风分段控件——一个圆角"轨道"(surface-2底 + 圆角 999px + 内边距)内含两个 pill 按钮,激活项填 Amber;width:fit-content + margin:auto居中;去掉 first/last/only-child 圆角拼接;面板inset44→68px 适配新高度。②TabApp.updateHomeView在显示首页时给document.body加home-open类,CSSbody.home-open #keybar{display:none}隐藏键栏;打开 tab(终端激活)即恢复。 - 追加(用户反馈"段控独占一行、空白太多"): 桌面端(
@media min-width:769px)把段控position:absolute浮到右上角,与过滤框/启动器表头同一行;面板inset归 0(从顶部铺满),.launcher-head留 240px 右内边距避开段控;移动端保持居中堆叠(防止与全宽过滤框重叠)。另:首页隐藏键栏时body.home-open #term{bottom:0}收回键栏占位。纯 CSS(style.css直出,无需打包)。真浏览器实测:Projects 段控与过滤框同行(seg.top 56 / filter.top 62)、卡片在其下;Sessions 视图段控不与 New/Manage 重叠。 - commit: (本次提交)
2026-06-30 · v0.6 UX 修复 — 会话内无法返回 Projects 首页
- 状态:
[x]DONE。415 测试全绿(+3)· 双 typecheck 干净 ·build:webOK · 真浏览器验证通过。 - 问题(用户反馈): 打开某个项目进入终端 tab 后,首页的 Sessions/Projects 段控被隐藏,只能关掉所有 tab 才能回到选择器再开下一个项目——没有"返回 Projects"的入口。
- 修复(
public/tabs.ts+public/style.css): 标签栏新增 ⌂ Home 按钮;点击把首页选择器(Sessions/Projects 段控 + 对应面板)叠加显示在当前 tab 之上(homeForced标志:updateHomeView改为tabs.length===0 || homeForced时显示并隐藏所有终端 pane)。再点 ⌂ 或点任一 tab(activate重置homeForced)即回到终端;从叠加层开新会话/项目也会自动收起。关最后一个 tab 时复位homeForced。⌂ 叠加时高亮(.tab-home.active,Amber)。 - 测试(
test/tabs.test.ts+3): ⌂ 叠加首页再切回;叠加层切到 Projects 并开第二个项目后自动收起;0 tab 时 ⌂ 为 no-op。 - 验证:
npx tsc -p tsconfig.json/.web.json干净;npx vitest run415/415;真浏览器:开 shell tab→段控隐藏→点 ⌂→段控+面板叠加显示、⌂ 高亮→切 Projects 面板可见→再点 ⌂→回到终端。 - commit: (本次提交)
2026-06-30 · v0.6 Project Manager(P1–P6,多 agent Workflow 编排)
- 状态:
[x]DONE(分支v0.6-projects)。398 测试全绿 · 双 typecheck 干净 ·build:webOK · 覆盖率 90.98/81.95/89.28/93.16(≥80×4)· 真浏览器 smoke 通过。 - 目标: 首页新增 Projects 视图——发现主机上的 git 仓库,点卡片 → 开 tab(标签=repo 名、cwd=仓库、自动跑
claude);卡片显示分支/dirty/该项目运行中的 sessions(1:N)。设计见docs/FEATURE_PROJECT_MANAGER.md。 - 编排(ultracode
Workflow动态): Build(P2 后端 ∥ P5 前端)→ Wire(P4 端点 ∥ P6 接线)→ Review(module-reviewer验收 +typescript-reviewer+security-reviewer并行 report-only)→ HIGH/CRITICAL 对抗式 Verify → Fix。P6 首跑 transientserver_error挂掉(0 改动落地)→TaskStop+Workflow{resumeFromRunId}恢复;P2/P4/P5 缓存命中,P6 重跑成功。 地基 P1(types)/P3(config)上一会话已落,本会话先 commit 为dc5d073作干净基线。 - 改动:
src/http/projects.ts(新,P2):parseGitHead(纯)、buildProjects(cfg, liveSessions);内部scanRepos(BFS 深度上限/跳 node_modules·dotdir·symlink/命中即停)、readBranch、readDirty(execFilegit status,2s 超时,并发 8)、mergeHistory(复用history.listSessions)、缓存discoverProjects(TTL + in-flight 去重)、fresh session 按 cwd 前缀归并、_clearProjectCache(测试)。test/projects.test.ts(15)。public/projects.ts(新,P5):mountProjects+ 纯 helperfilterProjects/sortProjects/getFavs/saveFavs/toggleFav/normalizeProject。test/projects-panel.test.ts(36)。public/style.css追加 Projects 面板 +.home-seg段控样式。src/server.ts(P4):GET /projects(只读、无 Origin 守卫、try/catch→[])。test/integration/projects-endpoint.test.ts(5,真起 server)。public/tabs.ts(P6):openProject(同名#n、addEntry(...,'claude\r'))、countOpenWithTitlePrefix、updateHomeView+.home-seg段控。test/tabs.test.ts+7。
- Review→Fix: 12 findings,4 HIGH 全确认修复——① A3 关键缺回车
\r(openProject/newTabForResume)② 段控被定位面板遮挡(面板inset:44px 0 0 0)③mergeHistory串行→mapWithConcurrency④ discover 并发缓存击穿→in-flight 去重。Orchestrator 追加加固(MEDIUM/LOW + 测试):normalizeProject(API 边界校验)、getFavs滤非字符串、parseProjectRoots相对路径 fail-fast、config v0.6 解析补测(config.ts→98%)。 - 验证:
npx tsc -p tsconfig.json/.web.json --noEmit干净;npx vitest run398/398;npm run build:webOK;npx vitest run --coverage退出 0(阈值 80×4)。真浏览器(PROJECT_ROOTS=~/Documents/git,83 repo):段控切换 ✓、面板 83 卡片(分支/dirty/斜杠分支正确)+ 搜索 ✓、点卡 → tabweb-terminal+ 终端出现 claude 启动界面(A3 端到端)✓、console 无 v0.6 报错(仅既有 favicon 404)。 - 决策 / 偏离: 缓存只缓存磁盘发现、session 每次 fresh 归并(避免陈旧);git worktree 看板/文件树编辑器/ticket 拉取 = v0.6 明确不做(FEATURE §9)。
public/projects.ts/tabs.ts的 mount* DOM 接线沿用既有约定不纳入覆盖率(纯 helper 已测)。style.css1143>800 = 既有债,留作拆分。 - 遗留 / 待办: 真机 F4/F7(同历史);可选
server.ts抽http/routes.ts;style.css拆分。 - commit: (本次提交)
2026-06-16 · T1 脚手架 + 一次性装齐依赖
- 状态:
[x]DONE - 改动:
package.json(scripts + 全部依赖 +postinstall)、tsconfig.json(后端 NodeNext/strict)、tsconfig.web.json(前端 DOM/Bundler,当前 noEmit 仅类型检查)、vitest.config.ts(node 环境 +passWithNoTests)、.gitignore、package-lock.json、目录骨架src{,/http,/session} public test{,/helpers,/integration}(各带.gitkeep)。 - 验证:
npm install成功(运行时 72 包 + 开发包,0 漏洞)。解析版本(均比预期新的主版本):express 5.2、 @xterm/xterm 6.0、addon-fit 0.11、ws 8.21、node-pty 1.1;dev:typescript 6.0、vitest 4.1、tsx 4.22、@types/node 25。node -e require('node-pty')→ OK;真实 PTY spawnecho hello-from-pty→ 16 bytes、exitCode 0(沙箱外验证)。npm test→ exit 0(passWithNoTests)。
- 决策 / 偏离 PLAN:
- node-pty spawn-helper 修复:1.1.0 的 prebuild 自带
spawn-helper但权限-rw-r--r--(缺 +x), 导致posix_spawnp failed。加postinstall: chmod +x .../prebuilds/darwin-*/spawn-helper(对 Linux 无害,|| true), 使每次 install / 每个 worktree 自动修复。T12 spawn PTY 依赖此修复。 - 依赖解析到较新主版本(express 5 / xterm 6 / typescript 6 / vitest 4):T7/T11/T14/T15 注意 API 差异 (express 5 路由、xterm 6 import 形态)。
- 加了 PLAN 未列的脚本
dev/typecheck与postinstall(便利 + 必需修复),属合理补充,未改 PLAN 意图。
- node-pty spawn-helper 修复:1.1.0 的 prebuild 自带
- 遗留 / 待办: 无(原前端打包待决项已解决,见下补记)。
- commit:
b126cf0
2026-06-18 · v0.3 Step 5 — 中价值 M3/M7/M6(主题/仪表盘/同目录新标签)
- 状态:
[x]DONE。真浏览器验证通过。212 测试全绿。 - M3 主题/字体(commit
f79f14b):settings.ts(⚙ 面板:dark/light/solarized + 字号 A−/A+),localStorage 持久化;terminal-session.applyTheme;TabApp.applySettings 应用到所有+新标签。验证:light 主题渲染+持久化。 - M7 仪表盘(commit
f79f14b):dashboard.ts(▦ 浮层列出所有标签:连接点+文件夹+Claude 状态;点行切换;打开时每秒刷新);TabApp.snapshot()/focusTab()。验证:列出 2 标签、点击切换。 - M6 同目录新标签(commit
87e1173):title-util.cwdFromOsc7(解析 OSC 7 cwd)+4 单测;terminal-session 注册 OSC7 handler→cwd getter,新会话 attach 带 cwd;protocol/types attach.cwd(绝对路径);session/manager/server 透传到 spawn cwd;tabs+在当前 tab 的 cwd 开新标签(无则回 home)。验证:server attach.cwd 生效;cd /private/tmp→+→新标签 pwd=/private/tmp(此 zsh 发 OSC7)。 - 工具栏: 🔍搜索 · ⚙设置 · ▦仪表盘 · 📱QR。
- commit:
f79f14b(M3+M7)、87e1173(M6)
2026-06-17 · v0.3 Step 4 — H1 tmux 保活(服务重启不丢会话)
- 状态:
[x]DONE。真 tmux 集成测试验证通过(commit9099f73)。至此 H1–H4 全部高价值功能完成。 - 机制: useTmux 时把 shell 跑进
tmux new-session -A -s web_<id>;node-pty 进程只是 tmux 客户端。服务重启后,客户端带同 sessionId 重连 → manager 发现web_<id>tmux 会话仍在 → 用同 id 重建客户端(-A附着)→ 接回原 shell。 - 改动: 新增
src/session/tmux.ts(同步 CLI 封装 available/has/kill,best-effort);configuseTmux(envUSE_TMUX1/0/auto→检测 PATH);session createSession 加可选 id(重连用)+ tmux 分支 spawn +Session.tmuxName+ kill 走tmux kill-session;manager handleAttach 加 Case 3.5(重启后重附)+ shutdown 对 tmux 会话只杀客户端 pty 不杀 tmux(保活)。 - 测试: 共享集成 cfg 设
USE_TMUX:0(防 tmux 泄漏);单测 CFGuseTmux:false;集成H1(真 tmux,gate=tmux+PTY):设 shell 变量 → 重启 server → 同 id 重连 → 变量仍在(MARK-tmuxlives)。208 测试全绿,tsc 通过。 - 注: 当前后台 dev server(:3000)是 H3 之前启动的、不含 H1;要试 tmux 保活需
USE_TMUX=1 npm start。 - commit:
9099f73
2026-06-17 · v0.3 Step 3 — H3 远程批准/拒绝(不打字)
- 状态:
[x]DONE。端到端真浏览器验证通过(commit9a6150c)。 - 机制:
PermissionRequesthook 用 curl POST 到/hook/permission并阻塞等响应;服务端长挂该 HTTP 响应,推status{waiting,pending:true}给前端 → 前端显示 [✓Approve][✗Reject] 横幅 → 点按发 wsapprove/reject→ 服务端用{hookSpecificOutput.decision.behavior:allow|deny}回应被挂的请求 → curl stdout 把决定交给 Claude。全程不打字。 - 兜底: 5 分钟超时 / 断开即释放 → 回空
{},Claude 回退到自己的交互提示;web-terminal 外 curl 无 URL → no-op。 - 改动: types(ClientMessage approve/reject、status.pending)、protocol(白名单+解析)、manager(pending 透传)、server(
/hook/permission长挂 + approve/reject 路由 + resolvePending)、setup-hooks(PermissionRequest 用 held 命令)、FE(terminal-session approve()/reject()+pending、tabs 批准横幅)。 - 验证: 207 测试全绿(协议 approve/reject 单测 + 集成 ⑧ held→approve→allow);浏览器实测横幅"Claude wants to use Bash"→Approve→held 请求得到 allow→横幅消失(截图 /tmp/v03-approve.png)。
- commit:
9a6150c
2026-06-17 · v0.3 Step 2 — H2/H4 Claude 状态感知(headline)
- 状态:
[x]DONE(分支v0.3-cockpit)。端到端真浏览器验证通过。 - 机制: Claude Code hooks → 服务端
/hook侧信道 → WSstatus帧 → 标签角标实时更新。架构演进:服务端获得 hook 侧信道(终端流仍是 byte-shuttle)。 - H2 服务端(commit
a411c89):- types:
ClaudeStatus+ ServerMessagestatus;Session.claudeStatus;manager.handleHookEvent。 - session:spawn env 注入
WEBTERM_SESSION(哪个标签)+WEBTERM_HOOK_URL(本机 /hook)。 src/http/hook.ts纯映射(PreToolUse→working、PermissionRequest/permission_prompt→waiting、Stop/idle_prompt→idle);6 单测。- server:
POST /hook(仅 loopback,express.json 64kb)→ 推 status。顺带修真 bug:closeIdleConnections()让 hook keep-alive 空闲连接不再卡住 close()/SIGTERM。 - 集成 ⑦(真 PTY):attach → POST /hook → ws 收到 status(注意测试要在 fetch 前挂监听,推送是同步的)。
- types:
- H4 前端(commit
c81cebd):- terminal-session 处理 status → claudeStatus + onClaudeStatus。
- tabs:每标签 Claude 角标(⚙工作/⏳等批准/✓空闲);非活动 + waiting 标签加琥珀高亮 + 浏览器通知(首次切换请求权限)。
scripts/setup-hooks.mjs+npm run setup-hooks:幂等装/卸 ~/.claude/settings.json 的 command hook(内联 curl,web-terminal 外是 no-op),自动备份。
- 验证: 205 测试全绿;浏览器实测 PermissionRequest→⏳、PreToolUse→⚙、Stop→✓;非活动 waiting 标签琥珀+⏳(截图 /tmp/v03-status.png)。0 console error。
- commit:
a411c89(H2 服务端)、c81cebd(H4 前端+脚本)
2026-06-17 · v0.3 Step 1 — 前端快赢(键栏/搜索/链接/QR/PWA)
- 状态:
[x]DONE(分支v0.3-cockpit;纯前端,无服务端改动) - 计划:
~/.claude/plans/shimmering-wondering-island.md(v0.3 cockpit,已批准)。 - H4-keybar: 键栏加 Claude 快捷键 Esc·Esc/^O/^T/^B + 每键 tooltip(KEY_MAP 14 键;commit
eaeacf3)。 - M1 搜索:
@xterm/addon-search每终端 + 🔍 工具栏按钮 + 搜索框(Enter 下一个/Shift+Enter 上一个/Esc 关)。 - M2 可点击链接:
@xterm/addon-web-links。 - 结构: 标签栏拆成
#tabs(可滚)+#toolbar(工具按钮区),为后续 QR/设置/仪表盘复用。 - M5 QR: 📱 按钮 → 客户端渲染 location.origin 的二维码(经 LAN IP 打开即可分享)+ localhost 提示(commit
fbc218c)。 - M4 PWA: manifest + icon.svg + sw.js(network-first,不拦 /term//hook)+ 注册;可安装。
- 验证(真浏览器): 14 键栏按钮、搜索高亮命中、🔍📱 工具按钮、SW controlled、QR canvas 渲染、0 console error。199 测试全绿、双 tsc、build 通过。截图 /tmp/v03-quickwins.png、/tmp/v03-qr.png。
- commit:
eaeacf3、fbc218c
2026-06-17 · v0.2 修复 — 切换标签要点多次
- 状态:
[x]FIXED - 现象: 点标签切换常常点一两下不生效,要多点几次。
- 根因: 整个标签
draggable=true,点击时鼠标微动一点点就被当成拖拽(而非点击)→ 切换失效;且每次 activity/title/status 事件都replaceChildren()全量重建标签栏,点击瞬间元素可能被销毁。 - 修复(
tabs.ts重构渲染):① 切换改pointerdown(按下即切,先于拖拽判定,触摸也生效);② 标签 DOM 建一次,状态点/未读/标题就地更新(refreshTab),仅增删/排序/改名才全量重建;③ 关闭按钮/改名框 pointerdown stopPropagation 避免误激活。 - 验证(真浏览器): 连续单击 tab0→2→1→0 active 每次都跟随;CDP 真实点击也切换;改名/拖拽不受影响。198 测试全绿。
- commit:
e058751
2026-06-17 · v0.2 — 标签按当前文件夹自动命名
- 状态:
[x]DONE(纯前端) - 需求: 标签名用当前文件夹,易区分。
- 改动: 新增
public/title-util.ts的folderFromTitle()——解析 shell OSC 标题user@host:~/path取最后一段目录名(home 保留~);纯函数、无依赖、7 单测。terminal-session.ts把onTitleChange经此函数转为文件夹名。标题优先级:手动改名 > 文件夹名 > Term N。 - 验证(真浏览器): 198 测试全绿、tsc+build ✓。实测
cd实时更新:~→ 项目目录web-terminal→docs→~。 - commit:
4091283
2026-06-17 · v0.2 — 标签状态点 + 活动指示 + 拖拽排序
- 状态:
[x]DONE(纯前端) - 改动:
terminal-session.ts:跟踪SessionStatus(connecting/connected/reconnecting/exited)+onStatus回调。Bug 修复:onTitle之前只在 opts 里、构造函数从没绑定onTitleChange—— 自动标题原本是死代码,现已接上(实测两标签显示host:~自动标题)。tabs.ts:每标签状态点(颜色=连接态:绿/琥珀/红);未读活动(后台标签来新输出→hasActivity→蓝环,切到该标签即清);HTML5 拖拽排序(保留活动标签、持久化)。style.css:.tab-dot配色、.unread蓝环、.dragging/.drag-over反馈。
- 验证(orchestrator,真浏览器): 191 测试全绿、双 typecheck、build:web ✓。browse 实测:连接后绿点;Term1 后台收到延迟输出→未读蓝环(
unread:true);拖拽AAA|BBB→BBB|AAA且reload 后顺序+自定义名都保留。截图 /tmp/webterm-dots.png。 - commit:
b5f21e5
2026-06-17 · v0.2 — 多标签 + 标题 + Claude Code 快捷键栏
- 状态:
[x]DONE(纯前端;后端 manager 早已多会话,无需改后端/协议) - 需求: 用户要 ①多标签 ②标签加标题方便区分 ③适配平板/手机 ④移动端易触发 Claude Code 快捷键(按钮触发快捷键)。
- UX 调研(general-purpose 联网,参考 iTerm2/Warp/VS Code/Windows Terminal/Ghostty/Chrome):双击就地改名是现代主流(Warp/VS Code/Arc);自动标题用 OSC 0/2;中键关闭、× 悬停显示、省略号+tooltip、Cmd/Ctrl+数字切换是 Tier-1。验证了"OSC 自动标题 + 双击改名"方案正确。
- 改动(
public/):- 新增
terminal-session.ts:从旧 main.ts 抽出"单会话 = 1 Terminal + 1 WS + 重连",加 show/hide/fit/focus/dispose + onTitle/onActivity 回调。 - 新增
tabs.ts:TabApp 多标签管理 + 标签栏 UI。每标签独立 WS/会话(沿用 1WS↔1会话协议);自动标题(onTitleChange)+ 双击就地改名(手动>自动>Term N);中键关闭、× 按钮、省略号+tooltip;localStorage 持久化{id,title}[](兼容 v0.1 单会话 key 迁移)。关闭标签=detach(PTY 不死)。 keybar.ts:加slash键;导出KEYBAR_BUTTONS按 Claude 频率排序(Esc 标 primary);+4 单测。main.ts瘦身为引导;index.html加#tabbar;style.css重写:键栏全设备常显(桌面也可点按钮触发快捷键)、@media(pointer:coarse)触摸加大、Esc 琥珀色高亮、标签栏/改名框/活动下划线。
- 新增
- 决策:键盘快捷键(Cmd+T 等)不做——浏览器宿主会抢,改走点击/触摸 UI(对 web 终端正确取舍)。
- 验证(orchestrator,关沙箱真浏览器): 两 typecheck ✓;
npm test191 全绿(+4 keybar);build:web✓。browse 实测:双击改名 Term1→"claude"、两标签会话独立(claude | Term 2)、键栏桌面可见 10 键 Esc primary、窄屏可滚动可用。截图 /tmp/webterm-ux-desktop.png、-phone.png。 - commit:
4f66016
2026-06-17 · W5 — 验收 + 收尾(T16–T21)
- 状态: T16 ✅ · T17 部分(F9✅/F4 待真机)· T18 ✅ · T19 待真机 · T20 ✅ · T21 ✅
- T21(orchestrator):写
README.md(install/run/config/security);安全自查——无硬编码 secret(grep 空)、Origin+路径+maxPayload 校验已接线(server.ts 确认)、无 eval/exec、input 原样透传给 PTY(byte-shuttle 设计意图);npm run build→dist/server.js可产出。 - F1/F2/F3/F8 浏览器 smoke(关沙箱起 server + gstack browse 无头 Chromium):
· 加载 http://127.0.0.1:38080 → .xterm 挂载、0 console error、WS 连上、彩色状态行 + 真 zsh 彩色提示(F1/F2)。
· 聚焦
.xterm-helper-textarea输入echo hello-webterm→ 回显 + 输出hello-webterm(输入→WS→PTY→shell→输出→xterm 全链路)。截图 /tmp/webterm-smoke2.png。 · 输入exit→ 终端显示 "Process exited code=0 Press Enter to reconnect…"(F8)。 · 改 viewport(F3)→ 终端存活、无 console error。 · curl 复核:GET / 200、/build/main.js 200、坏 Origin upgrade → 401。 - 决策: F4(局域网多设备)、F7(手机软键盘栏)需物理设备,无法在本机无头验证 → 交用户真机。其余 7/9 F 项已自动化验证。
- 遗留: T19(F7)待真手机;F4 待局域网真机;main.ts 的 css
@ts-ignore小债。 - commit:
81585ad(README/安全)+ 本条 LOG
2026-06-17 · W2/W3/W4 — 会话核心 + 集成(T12/T13/T14/T15)
- 状态:
[x]ALL DONE · 串行(各有依赖),单 builder/任务 - T12
session/session.ts(opus,19 测试):createSession/attachWs/detachWs/writeInput/resize/kill;onData→buffer+lastOutputAt+转发;onExit→exitedAt/exitCode+发exit+注入onExit;spawn 失败抛(M4);sendIfOpen 守卫 readyState(M5);detach 不杀 PTY;exit 后 write/resize 忽略(L4);detach 后退出保留会话(L1)。测试vi.mock('node-pty')→mock IPty,沙箱内可跑。 - T13
session/manager.ts(sonnet,21 测试):handleAttach 四路径(新建/存活/已退出 L1/查不到);reapIdlenow-max(detachedAt,lastOutputAt)>idleTtl(M3);shutdown 全 kill;注入 onExit 在线退出删表、detach 后退出保留待 L1(L2)。 - T14
server.ts(sonnet,~200 行):Express 静态 public/+build/;WebSocketServer({noServer,maxPayload})(L5);upgrade 门(非 wsPath→destroy、坏 Origin→401、过→handleUpgrade,L3);connection 等 attach,try/catch→spawn 失败 exit(-1)+关连接(M4);message 路由;close→detach(不杀);SIGINT/TERM→shutdown;reapIdle 定时器;safeSend 守卫(M5)。导出 startServer 供 T15。 - T15 集成(sonnet,6 用例):真 ws 客户端打 startServer。①坏Origin 401 ②错路径 destroy ③超 maxPayload 1009 ④spawn失败 exit(-1) —— 沙箱内全绿;⑤attach→output ⑥重连回放(F5/F6,含
__MARK_测试__CJK)—— orchestrator 加PTY_AVAILABLE门控(沙箱自动 skip,真机跑),关沙箱验证 6/6 全绿。 - 验证(orchestrator 独立复核): 每步
tsc --noEmit✓ + 全量npm test递增至 187 全绿;集成 sandbox-off 6/6(真 PTY)。 - 决策 / 偏离: T12 onData 时间戳用 Date.now()(事件时刻,符合 lastOutputAt 语义);T15 由 orchestrator 加 PTY 可用性门控(原
it.skip→itPty),使真机会跑、沙箱自动跳过。 - commit:
20212dd(T12)·a2897b2(T13)·0dac54b(T14)·cdec19a(T15)
2026-06-16 · W1 前端批次(T8/T9/T10/T11)
- 状态:
[x]ALL DONE - 派活方式: 因 T11 import T10 的 mountKeybar 且验收是整目录 tsc.web,分两步:先并行 T8+T9+T10(haiku),后 T11(sonnet)。general-purpose 内联角色,共享树 + 文件不交叉。
- 改动:
- T8
public/index.html:#term/#keybar、viewport、<script type=module src=./build/main.js>+ link ./build/main.css 与 ./style.css(按 §1 约定)。 - T9
public/style.css:全屏终端、键栏 fixed 底部 flex、>768px 隐藏、深色;不碰 xterm 内部样式。 - T10
public/keybar.ts(+ test):KEY_MAP 纯数据(Esc/Shift+Tab/方向/Enter=\r/Ctrl+C/Tab)+ mountKeybar(onSend) touchstart→onSend+preventDefault。13 测试。 - T11
public/main.ts:esbuild 入口(import xterm css);xterm+FitAddon;WS scheme 随协议(M6);attach 首帧带 localStorage sessionId;onData→input、output→write、exit→ANSI 提示按回车重连;ResizeObserver 防抖 resize;指数退避重连≤30s;mountKeybar 接 ws.send;无 console.log(状态写终端)。
- T8
- 验证(orchestrator 独立复核): backend
tsc --noEmit✓;frontendtsc -p tsconfig.web.json✓;npm test6 文件 / 141 测试全绿(新增 keybar 13);npm run build:webesbuild 打包成功(main.js 430kb + main.css 6kb);build/ 已 gitignore 未入库。 - 决策 / 偏离: T11 用 1 处
@ts-ignore压 CSS import(Owns 仅 main.ts,无法另建 d.ts;记为 tech-debt);exit 后按回车 clear sessionId 再新建(契合 L1:已退出会话由服务端回放+补发 exit,前端不 reattach)。 - 遗留: 上述 css d.ts 小债;前端真实浏览器行为留 W5(T16–T20)实测。
- commit:
326d347
2026-06-16 · W1 后端批次(T3/T4/T5/T6/T7)— 5 个并行 builder
- 状态:
[x]ALL DONE - 派活方式: 5 个
general-purposesubagent 并行(自定义 module-builder agent 需重启会话才加载,故本会话内联角色), 共享工作树 + 文件所有权严格不交叉 + scopedvitest run <name>(官方文件分区法,替代 worktree;合并即 commit)。模型按 §4。 - 改动(各 agent 仅在 Owns 内):
- T3
test/helpers/mock-pty.ts(+ smoke test):IPty 替身,emitData/emitExit手动触发、记录 write/resize/kill、onData/onExit 返回 IDisposable。 - T4
src/config.ts(+ test):loadConfig(EnvLike);M1 origins 由os.networkInterfaces()推导(含 localhost,剔除 0.0.0.0/internal,http+https),wsPath 默认 /term,非法值 fail-fast,Object.freeze。 - T5
src/protocol.ts(+ test):SESSION_ID_RE(UUID v4)、parseClientMessage(永不抛,全分支校验,M7 拒abc123)、serialize。 - T6
src/session/ring-buffer.ts(+ test):M2 按真实字节(Buffer)计量、按 chunk 边界淘汰、不切碎 UTF-8/ANSI、snapshot 补\x1b[0m。 - T7
src/http/origin.ts(+ test):用 WHATWGURL比 protocol+host+port,undefined/解析失败拒绝。
- T3
- 验证(orchestrator 独立复核,非仅信 agent):
npx tsc -p tsconfig.json --noEmitPASS;npm test(全量) 5 文件 / 128 测试全绿(T4 32 + T5 60 + T6 15 + T7 12 + T3 9)。 - 决策 / 偏离: 无源码偏离。流程上:本批用 general-purpose 内联角色(见上)。T6 对"单块 > 容量"按 M2"绝不切碎"保留整块(测试固化),属忠实落地。
- 遗留 / 待办: W1 前端 T8–T11 待派;之后 T12 session(mock+ring-buffer 已就绪)。
- commit:
0fa0b69
2026-06-16 · T2 冻结共享类型契约 src/types.ts
- 状态:
[x]DONE - 改动: 新建
src/types.ts(纯类型,无实现);删src/.gitkeep。含 Config、ClientMessage/ServerMessage/ ParseResult、Dims、IPty(子集)+ IDisposable/PtyEvent、RingBuffer、WebSocketLike + WS_OPEN、SessionMeta/Session (lastOutputAt/exitedAt/exitCode)、SessionManager、MountKeybar;各模块工厂函数签名以注释列为 import 锚点。 同步更新 ARCHITECTURE §3.1/§3.4(加 wsPath、EnvLike、WebSocketLike 注记,防漂移)。 - 验证:
npx tsc -p tsconfig.json --noEmitPASS;grep 确认无ws/NodeJS/DOM 外部类型依赖(仅注释提及)。 - 决策 / 偏离 ARCHITECTURE §3(为"前后端共享 → 必须无外部类型依赖"):
loadConfig(env: EnvLike)替代NodeJS.ProcessEnv(EnvLike = Readonly<Record<string,string|undefined>>,process.env 可赋值)。Session.attachedWs: WebSocketLike替代ws的WebSocket(真实 ws 结构兼容);导出WS_OPEN=1供 M5 守卫。- 新增
Config.wsPath(默认 '/term'):不变量 8 要求 WS 路径入 config,§3.1 原 Config 漏列,已补;T4 需从 envWS_PATH读、T14 用cfg.wsPath。 三处均已回写 ARCHITECTURE,types.ts 为权威冻结源。
- 遗留 / 待办: 无。W1 全部任务现可只读 import
src/types.ts。注意:IPty/WebSocketLike 是结构子集, 实现方(T12/T14)可把真实 node-pty IPty / ws.WebSocket 直接赋值(结构兼容)。 - commit: (见下次提交)
2026-06-16 · T1 补记 — 前端打包定为 esbuild
- 状态:
[x]DONE - 改动: 装
esbuild(dev);package.json接build:web/dev:web(打包public/main.ts→public/build/main.js);.gitignore改忽略public/build/;tsconfig.web.json注释更新为"仅类型检查";前端构建约定写入PLAN §1并更新 T8/T11/T14 与DISPATCH.md、ARCHITECTURE §5。 - 验证: esbuild 0.28.1;临时入口
--bundle --format=esm --outdir=public/build出main.js+.map,smoke 通过。 - 决策: 三选一中选 esbuild(极快、零配置、与 node-pty/tsx 生态一致);现在装好避免 W1 并发 install。
约定:入口 main.ts、产物 public/build/(已 gitignore)、index.html 用
<script type=module src=./build/main.js>、server 静态托管 public/。 - commit: (见下次提交)
2026-06-15 · T0 项目记录起点(计划就绪)
- 状态:
[x]DONE - 改动: 创建
docs/PROGRESS_LOG.md、docs/PLAN.md(细粒度 T1–T21、为多 agent 并行设计);CLAUDE.md加入 plan/log 工作流约束。 - 验证: 文档自检;LOG 任务总览的 Steps 总数已与 PLAN 各任务勾选项对齐。
- 决策 / 偏离 PLAN: 采用 TypeScript(ARCHITECTURE §0);两份设计文档已多团队交叉验证并修订 12 项;计划按依赖分 W0–W5 波次,W1 含 9 个零交叉并行任务。
- 遗留 / 待办: 进入 W0 / T1(脚手架 + 装齐依赖);T1 完成后 T2 冻结
src/types.ts,随后 W1 可大规模并行。 - commit: N/A