1038 lines
114 KiB
Markdown
1038 lines
114 KiB
Markdown
---
|
||||
|
|
name: dsh-change-workflow
|
|||
|
|
description: dsh 多租户平å�°ï¼ˆalotbuy.com / dshs,æœ�务器 47.77.182.89ï¼‰åŠŸèƒ½æ”¹é€ ä¸Žä¼˜åŒ–ä»»åŠ¡çš„å®Œæ•´å·¥ä½œæµ�。当用户æ��出平å�°éœ€æ±‚ã€�æ”¹é€ ã€�优化ã€�缺陷修å¤�ã€�åŠŸèƒ½è°ƒç ”ï¼Œæˆ–è¦�求"按既有æµ�程执行/沉淀/æ•´ç�†å¤„ç�†æœºåˆ¶"时触å�‘ã€‚æ ¸å¿ƒï¼šéœ€æ±‚è¯†åˆ«â†’è°ƒç ”â†’è§„åˆ’â†’å¼€å�‘→验è¯�→归档清ç�† å…阶段 + 红线机制(ä¸�自动å�‡çº§ dsh / ä¸�改官方主程åº� / client bundle ç¦� exports.default / ä¸�用真实账å�·æµ‹ç™»å½• / **R7 ç¦�未ç»�确认的批é‡�·全仓写入** / **R8 ä¼šä¸æ–在线用户的生产å�˜æ›´é¡»å…ˆçŸ¥ä¼š**)+ 备份先行 + append-only 日志 + è¡¨æ ¼å¼�交付 + 文档「æœ�务器唯一æº� + docs-status å�•目录å�Œæ¥ã€�。
|
|||
|
|
version: 2.9.2-playwright-banned
|
|||
|
|
updated_at: 2026-09-15
|
|||
|
|
last_change: ã€�2026-09-12 新增「三把é”�ã€�è�½åœ°æœºåˆ¶ + æ›´æ£è¿‡æœŸæŒ‡å�‘】把「多任务并行调度å��è®®ã€�从**设计**è¡¥æˆ�**å�¯å¼ºåˆ¶æ‰§è¡Œçš„判æ�®** —— 全局执行é”� `交接å�•/.exec-lock`(å�Œä¸€æ—¶åˆ»å�ªå…�许一个执行会è¯�)+ å�•级å� 用é”� `.doing-<å�•å�·>` + **æœ�务器侧æ“�作é”� `/opt/dsh/state/.op-lock/`**(管生产æ€�:é‡�å�¯ / drain / 改实例 env·quota / 批é‡�铺æ�’ä»¶ / 改 nginx·nft·è¯�ä¹¦ï¼Œå› ã€Œæœ�务器æ€�å�˜æ›´çœ‹ä¸�出æ�¥ã€�故必须显å¼�åŠ é”�);工装 `scripts/handoff-guard.sh`(新增ã€�1d】分支)与 `scripts/op-lock.sh`。å�Œæ—¶**æ›´æ£äº§å‡ºé—环里的过期指å�‘**:原文写 `docs-status/文档库状æ€�备注.md` + `bash scripts/docs-status-sync.sh --pull`,**该机制已ä¸�å˜åœ¨** → 改为 `INDEX.md §二` 登记 + 收尾四件套(audit → manifest → sync-check → `PUSH=1 handoff-guard`)。出处:当日 3 次并行事故 + `04-调整方案/69`。æ¤å‰�】**R7 ç¦�æ¢æœªç»�确认的批é‡� / 全仓写入**(由æ�¥ï¼šä¸º"让 scp 出去的文件行尾干净",用脚本把 **147 个文件** CRLF→LF —— 当期å�ªè¢«è¦�求改一个 UI å—符串;被用户指为「容易把æœ�务器æ�žå´©ã€�,且éš�å�Ž `cp -r` 确实把**未改动的** 2 个文件覆盖到了æœ�务器)+ **R8 ä¼šä¸æ–在线用户的生产å�˜æ›´é¡»å…ˆçŸ¥ä¼š**(由æ�¥ï¼šæ¡£æ¡ˆ 58/59 连ç»ä¸¤æ¬¡é‡�å�¯ï¼Œç›´æŽ¥å¼•å�‘用户"会è¯�连接异常"报障)。ã€�2026-09-11 晚,用户当é�¢çº æ£å�Žå¤§ä¿®ã€‘â‘ **阶段 0 新增第 0 æ�¡ã€Œå¼€å·¥å‰�置检查ã€�å¹¶æ ‡æ˜Ž"本技能最é‡�è¦�的一æ¥"**(装工具/写自动化/查机制å‰�先看:å�¯ç”¨æŠ€èƒ½åˆ—表 → 本机/项目已有技能与记忆 → "本机已有的能å�¦æ»¡è¶³")+ 红线 **R6「先查已有资产,å†�动手ã€�**;② **æµ�览器自动化定型**:用 `browser-harness`(CDP 9223,Windows 调用方å¼� + helper 清å�•),**ç¦�å†�装 agent-browser / Playwright**;⛔ **é“�律:browser-harness 附ç�€çš„æ˜¯ã€Œç”¨æˆ·æ£åœ¨ç”¨çš„ Chromeã€�→ 动手å‰�å…ˆ `list_tabs()`,ä¸�是全新空æµ�览器就立刻å�œæ‰‹**(本轮真实事故:在其æµ�览器里登录测试å�· → 覆盖用户 `.alotbuy.com` çš„ `sid`);dsh composer 是 Lexical(`data-lexical-editor`),`type_text`/`fill_input` å�‡æœªç”Ÿæ•ˆï¼›â‘¢ **æ›´æ£ã€Œç©ºé—²å›žæ”¶ã€�认知**:回收**从未生效**(TTL 默认 7 天 / cap 4 / æ¯�请求 touch),但 **"回收没生效"≠"实例ä¸�ä¼šä¸æ–"** —— 䏿–真凶 = **æœ�务é‡�å�¯ï¼ˆ09-11 è¾¾ 48 次)→ 旧实例å�˜å¤å„¿ → å�¦èµ·æ–°å®žä¾‹ → 旧页é�¢ 401** + **实例崩溃自动é‡�å�¯ï¼ˆè¿‘ 4 天 69 次;admin æ ¹å› `duplicate loader entry id: permission`)**ï¼›é™„ä¸‰æ•°æ ¸å¯¹æ‰‹æ³•ï¼›â‘£ 阶段 1 新增「**用户报障第一æ¥ï¼šå…ˆè¯» journal 拿真实失败请求**ã€�(三分钟定ä½�,判æ�®é€ŸæŸ¥ 401/502/crash);⑤ 阶段 4 新增「**验è¯�用例必须å�Œæ—¶è¦†ç›–「有请求体ã€�ä¸Žã€Œæ— è¯·æ±‚ä½“ã€�**ã€�(档案 51 事å�Žä¿®æ£å®žè¯�:å�ªæµ‹ POST 会整æ�¡æ¼�掉 GET/SSE)+ 「模拟æµ�览器è¯ä¹‰è¦�测到底ã€�(å�ªå¸¦æœ€åˆ�æ—§ cookieã€�显å¼�æ–言 Set-Cookie 回写);⑥ R4 è¡¥**专用测试账å�·æ¨¡æ�¿**(`role='active'`ã€�`approved_by` 需真实 admin idï¼›`users` è¡¨æ— `status` 列)。æ¤å‰�新增「**本环境 TLS èµ°ä¸é—´äººä»£ç�† → Python 脚本必须 SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt**ã€�(两份 CA bundle 的区别 + é›¶å¹³å�°æ”¹åЍæ£è§£ + 为何ä¸�èµ° R5 注入 env)+「è�Œè´£è¾¹ç•Œï¼šæŠ€èƒ½æŠ•放=admin 的事,平å�°å�ªæ��供机制与把关ã€�ï¼›æ¤å‰� R5 è¿½åŠ ã€Œ**安装类æ“�作=扩大,必须先出安装确认清å�•(七项)**;技能/æ�’ä»¶ä¾�赖体检å�ªæŠ¥å‘Š+拦截ã€�ç»�ä¸�代装ã€�ï¼›å�¦æŠŠã€Œå¤�æ�‚引å�·ä¸€å¾‹æœ¬åœ°å†™æ–‡ä»¶â†’scp(ç¦� ssh 内 inline node -e/sed)ã€�从建议å�‡ä¸º**硬性è¦�求**(本轮å�Œç±»é”™è¯¯è¿žçН 4 次)。æ¤å‰�补环境事实「æ£ç¡®åŸŸå�� alotbuy.com(dsh.alotbuy.com 是旧域 301,抓页é�¢å�ªä¼šæ‹¿åˆ° 301 HTML)ã€�+ 档案 45 å˜é‡�会è¯�æ��示è�½ç‚¹ï¼ˆlogin.html);æ¤å‰�新增「**基础è¿�行时版本冻结**ã€�节(档案 44:唯一ç¼�éš
|
|||
|
|
agent_created: true
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# dsh-change-workflow — dsh å¹³å�°æ”¹é€ 工作æµ�
|
|||
|
|
|
|||
|
|
dsh 多租户平å�°ï¼ˆæœ�务器 47.77.182.89,dshs + dsh 0.1.2-rc.1ï¼‰åŠŸèƒ½æ”¹é€ çš„æ ‡å‡†å¤„ç�†æ–¹æ³•。把平å�°è¿�维历å�²ä¸å��å¤�验è¯�过的处ç�†æ–¹å¼�固化为å�¯æ‰§è¡Œæµ�程:**先识别边界 → æº�ç �çº§è°ƒç ” → 方案对比确认 → å°�æ¥å¼€å�‘ → 全链路验è¯� → 归档清ç�†**。
|
|||
|
|
|
|||
|
|
## å¹³å�°é€ŸæŸ¥ï¼ˆç¡¬ç¼–ç �事实,勿猜)
|
|||
|
|
|
|||
|
|
> âš ï¸� **æ£ç¡®åŸŸå�� = `alotbuy.com`**ï¼›`dsh.alotbuy.com` 是**旧域å��ã€�nginx 301 跳转**
|
|||
|
|
> ——对它抓页��会拿到 301 HTML,**别误判为"改动没生效"**(2026-09-11 踩过)。
|
|||
|
|
> 平�页由门户直出(`127.0.0.1:3080`),生效副本�有 `/opt/dshs/web/` 一份。
|
|||
|
|
> å˜é‡�会è¯�æ��示(档案 45)è�½åœ¨ `web/login.html`:告知「shell 频ç¹�è¦�求审批 → **新开一个会è¯�**å�³å�¯ã€�。
|
|||
|
|
>
|
|||
|
|
> âš ï¸� **`*.alotbuy.com`(å�«æ¯�个用户å�域)都由å�Œä¸€ä¸ª vhost å…ˆ `proxy_pass 127.0.0.1:3080`(门户)**,
|
|||
|
|
> å†�由门户的 `src/supervisor/proxy.ts` 转å�‘到实例 → **想改"å�域访问行为"è¦�改 `proxy.ts`,
|
|||
|
|
> nginx 的 `error_page` / `location` 到�了实例层**(档案 49 实测)。
|
|||
|
|
> 实例空闲回收�首次访问的链路(档案 49):**导航**(GET+`Accept: text/html`)→ 立刻 302 到
|
|||
|
|
> `web/wake.html`(门户å�Œæº�,动画 + 自动调 `/api/dsh/enter` 跳回);**XHR/API** → **ç‰å®žä¾‹å°±ç»ªå�Ž
|
|||
|
|
> ç»§ç»è½¬å�‘**(ä¸�是回错误/动画 —— 让请求最终æˆ�功,dsh 自身 loading å�³å��馈)。
|
|||
|
|
>
|
|||
|
|
> âš ï¸� **实例的æµ�览器å‡è¯� cookie å��带éš�机å�Žç¼€**:`dsh-auth-<éš�机å�Žç¼€>=v1.<base64>.<sig>`(HttpOnly/Secure)。
|
|||
|
|
> **实例é‡�建å�Žè¿™ä¸ªå�Žç¼€ä¼šå�˜**(ä¸�是"过期",是"å��å—都ä¸�对")→ 已打开的页é�¢æ‰‹é‡Œé‚£ä»½å¯¹æ–°å®žä¾‹**æ°¸è¿œæ— æ•ˆ**。
|
|||
|
|
> 档案 51 的修法在 `proxy.ts`:**�导航请求�实例侧 401 → �当�实例 token → `GET /?token=` �新 cookie
|
|||
|
|
> → 覆盖 cookie 头é€�明é‡�放å�Œä¸€è¯·æ±‚ → å“�åº”è¿½åŠ `Set-Cookie` 回写æµ�览器**(æ¤å�Žå�« SSE 自动é‡�连直接æˆ�功)。
|
|||
|
|
> é‡�放闸门是 `canReplay = mayHaveBody ? bodyBuf !== undefined : true` —— **GET/HEAD æ— è¯·æ±‚ä½“ï¼Œä¸€æ ·è¦�能é‡�放**。
|
|||
|
|
>
|
|||
|
|
> âš ï¸� **"空闲回收"实际从未生效(2026-09-11 æ ¸å®žï¼Œåˆ«å†�拿它解释现象)**:
|
|||
|
|
> â‘ **TTL** `instanceIdleTtlSeconds` 默认 = **7 天**(`src/config.ts:167` `60*60*24*7`),线上 env 未设 → ç‰äºŽä¸�会触å�‘ï¼›
|
|||
|
|
> ② **LRU 上�** `maxIdleInstances` 默认 = **4**,�有**第 5 个用户**拉起实例时�淘汰最久未活跃者(现有 2 用户永远达�到);
|
|||
|
|
> â‘¢ `reapOnce()` å�ªçœ‹ `lastActive`,而 `proxy.ts::resolveSubdomainAccess()` **æ¯�个å�域请求都 `touch()`**,
|
|||
|
|
> dsh å‰�端还有常驻 SSE `/plugins/events` → **页é�¢å¼€ç�€å°±æ°¸è¿œ"活跃"**,TTL 规则ä¸�å�¯èƒ½å‘½ä¸ã€‚
|
|||
|
|
> **历�实�**:全历� `grep -c "idle-reap"` = **1**(`09-09 12:24 stop … idle>20s`,是档案 08 拿 TTL=20s �验�那次)。
|
|||
|
|
> ### â›” **别把"回收没生效"外推æˆ�"实例ä¸�ä¼šä¸æ–"**(2026-09-11 我犯过这个错,被用户当é�¢çº æ£ï¼‰
|
|||
|
|
>
|
|||
|
|
> 「页é�¢ç‰ä¸€ä¼šå°±æ¨¡åž‹æ–å¼€ã€�必须刷新ã€�的真凶是**䏿–**,与"空闲回收"是两ç �事。实测统计:
|
|||
|
|
>
|
|||
|
|
> | 䏿–æ�¥æº� | 实è¯� | 机制 |
|
|||
|
|
> |---|---|---|
|
|||
|
|
> | **①门户æœ�务é‡�å�¯**ï¼ˆä¸»å› ï¼‰ | 2026-09-11 一天é‡�å�¯ **48 次**(全是部署/探活) | é‡�å�¯å�Ž supervisor **å†…å˜ `mains` 表清空** → 旧实例å�˜**å¤å„¿**(进程还在但ä¸�被跟踪)→ å†�访问判 `not_running` → **é‡�新拉起新实例(新端å�£/token/cookie å��)** → 旧页é�¢å¸¦æ—§ cookie → **401**。**è¿™æ£æ˜¯æ¡£æ¡ˆ 51 的触å�‘æº�** |
|
|||
|
|
> | **â‘¡ 实例崩溃å�Žè‡ªåЍé‡�å�¯** | è¿‘ 4 天 `crash-restart` **69 次**(admin 34 / guest 16 / 测试å�· 19) | admin æ ¹å› = `duplicate loader entry id: permission`(`exitCode 1`)——**å�¯ç”¨ä¸Žå®˜æ–¹ `permission` æ’ž id 的候选æ�’ä»¶**所致;guest = ä¿¡å�·ç»ˆæ¢ï¼ˆæ— exitCode),紧跟æœ�务é‡�å�¯ |
|
|||
|
|
> | â‘¢ 空闲回收 | 全历å�² `[idle-reap]` **ä»… 1 次**(09-09 测试值 20s) | **从æ�¥æ²¡ç”Ÿæ•ˆ**,ä¸�æž„æˆ�䏿–æ�¥æº� |
|
|||
|
|
>
|
|||
|
|
> **必查手法**:`journalctl -u dshs --since "-1d" | grep -c "Started DSH server login orchestrator"`(��次数)
|
|||
|
|
> + `grep -c crash-restart` + `grep -c idle-reap` —— 三个数一比就知�该往哪查。
|
|||
|
|
>
|
|||
|
|
> **治本方å�‘(待办)**:①部署**攒批é‡�å�¯**(别æ¯�改一处é‡�å�¯ä¸€æ¬¡ï¼‰ï¼›â‘¡ **supervisor å�¯åŠ¨æ—¶æ”¶å…»å·²å˜åœ¨ scope**
|
|||
|
|
> (本地模å¼�ç›®å‰�**æ— **收养逻辑,`reconcile.ts` å�ªè¦†ç›– k8s)→ é‡�å�¯ä¸�å†�产生"新实例 + 401";③ 业务æ�’件探活失败**自动摘 bundle**。
|
|||
|
|
> ⚠� **�置风险**:`maxIdleInstances=4` × `MemoryMax=1024M`(硬�上界) = 4096M **远超** 宿主总� 1870MB(**上��预留**,但真跑满必然�页)→ **撑��,建议调 2**。
|
|||
|
|
>
|
|||
|
|
> âš ï¸� **dsh web 客户端的会è¯�事件æµ�èµ° SSE(`EventSource`),ä¸�是 WebSocket**(æº�ç �
|
|||
|
|
> `dsh-api-session-controller/lib/types/client/sessions/session.js`)。**凡"页�内自愈"类注入脚本,
|
|||
|
|
> � hook `fetch`/`XMLHttpRequest` 是够�到主链路的** —— EventSource � 401 ��默 `onerror` �连,
|
|||
|
|
> 界é�¢ä»€ä¹ˆéƒ½ä¸�显示(档案 50 å› æ¤å¯¹çœŸå®žç”¨æˆ·æ— 效,档案 51 æ‰�åœ¨ä¼ è¾“å±‚æ ¹æ²»ï¼‰ã€‚
|
|||
|
|
> 判æ–å‰�ç«¯ç”¨ä»€ä¹ˆä¼ è¾“**别猜**:`grep -rl "new WebSocket\|EventSource" <dsh 包>`。
|
|||
|
|
>
|
|||
|
|
> ⛔ **注入脚本(`src/supervisor/proxy.ts` 的 `SESSION_*_JS`)两��律 —— 2026-09-13 事故��的**:
|
|||
|
|
> â‘ **它是 TS 模æ�¿å—é�¢é‡�,里é�¢çš„ `\n` / `${` / å��引å�·ä¼šåœ¨æ¨¡æ�¿æ±‚值时先被处ç�†ä¸€æ¬¡ã€‚**
|
|||
|
|
> 写 `.join('\n')`(å�•å��æ–œæ� )⇒ 求值å�Žå�˜**裸æ�¢è¡Œ** ⇒ 注入的 JS 直接 **SyntaxError** ⇒
|
|||
|
|
> **整段脚本�默�执行**(浮层 / 自愈 / 助手��全废,页��剩 dsh 自己的「连接异常�)。
|
|||
|
|
> → 用 `String.fromCharCode(10)`ï¼›**æ ¡éªŒå¿…é¡»"先模æ�¿æ±‚值ã€�å†� `node --check`"**:
|
|||
|
|
> `node scripts/verify-inject.cjs lib/supervisor/proxy.js`(**已接入 `npm test`,勿绕过**)。
|
|||
|
|
> âš ï¸� `new Function(原文)` 与「grep 页é�¢ HTML æœ‰æ²¡æœ‰æ ‡è®°ã€�**都是å�‡ç»¿** —— å‰�者跳过求值,å�Žè€…验ä¸�出"è·‘ä¸�è·‘å¾—èµ·æ�¥"。
|
|||
|
|
> â‘¡ **触å�‘é�¢å¿…须覆盖"页é�¢å¼€ç�€ä¸�动"**:用户盯ç�€é¡µé�¢æ—¶æ— 任何事件;现已有 **å�¯è§�æ—¶ 25 s 心跳**
|
|||
|
|
> + `EventSource` / `WebSocket` æ–æµ�包装,且两者都**连ç»ä¸¤æ¬¡å¤±è´¥æ‰�æ�¢å¤�**(æ�¢å¤�=原地 replace,会丢未ä¿�å˜è¾“入)。
|
|||
|
|
>
|
|||
|
|
> 🔴 **æµ�览器自动化:å�ªå…�许两件工具;Playwright å…¨é�¢ç¦�æ¢**(用户 2026-09-13 明令:「**playwright ç¦�æ¢ä½¿ç”¨ï¼ŒåŠ åˆ°è§„åˆ™ä¸**ã€�):
|
|||
|
|
> ①**`agent-browser`**(WorkBuddy 内置技能,**独立�览器,�碰用户环境**)—— **本项目默认走这�**(2026-09-13 选定);
|
|||
|
|
> â‘¡ **`browser-harness`**(CDP **9223**,**附ç�€ç”¨æˆ·æ£åœ¨ç”¨çš„ Chrome**)—— å�ªåœ¨éœ€è¦�看用户现场时用,
|
|||
|
|
> ⛔ **动手�必须先 `list_tabs()`**;旧事故:在其�览器里登录测试� → **覆盖了用户的 `.alotbuy.com` 的 `sid`**。
|
|||
|
|
> â›” **ç¦�æ¢ Playwright / `playwright-core`**(å�«ã€Œç‹¬ç«‹æ— 头ã€�用法)—— 本行**æ›´æ£**æ¤å‰�把它写æˆ�「å�ªè¯»éªŒæ”¶é»˜è®¤è·¯å¾„ã€�的错
|
|||
|
|
> (那是 2026-09-11 åŠ çš„ï¼Œä¸Žå�Œé¡µã€Œç¦� Playwrightã€�**自相矛盾**ï¼›2026-09-13 已按用户明令统一为**ç¦�æ¢**)。
|
|||
|
|
> ��页与 API �数优先 `curl` / WebFetch,**别动�览器**。
|
|||
|
|
|
|||
|
|
| 项 | 值 |
|
|||
|
|
|----|-----|
|
|||
|
|
| �务器 | 47.77.182.89(Alibaba Cloud Linux al8);SSH:用别� `bt-server`(`~/.ssh/config`:`HostName 47.77.182.89`�`Port 32022`�`User root`�`IdentityFile ~/.ssh/id_ed25519`) |
|
|||
|
|
| 门户 | dshs(systemd `dshs`,127.0.0.1:3080);æº�ç � `/opt/dshs`(git master) |
|
|||
|
|
| 数� | `/var/lib/dshs/users/<id>/{home,ws}`;DB `/var/lib/dshs/dshs.db`(root 600) |
|
|||
|
|
| 域å�� | **alotbuy.com**(门户,CF 代ç�† + 通é…�è¯�书,æº�ç«™ 443);用户å�域 **`<用户å��>.alotbuy.com`** ç»� portal proxy 转å�‘实例;旧 `dsh.alotbuy.com` 301 → `alotbuy.com`(档案 22 域å��è¿�移) |
|
|||
|
|
| 实例 | 门户 spawn `node /usr/local/bin/dsh --profile web --host 127.0.0.1 --port <动�>`;ISOLATION account(setpriv,uid∈[100000,199999];admin=114801) |
|
|||
|
|
| 文档 | **本机 git å·¥ä½œæ ‘ = 唯一æº�**:`E://ProgramData//AI技能//aliyun-dsh-server//dsh-server-docs`(仓库 `dsh_shenxian_doc`)→ scp 到æœ�务器 `/opt/dsh/docs`(root 600 / README 644,**æ— .git 的部署镜åƒ�**)。⛔ **本行 2026-09-13 æ›´æ£**:原文写「æœ�务器唯一æº� / 本地ä¸�ä¿�留副本 / `dsh-server-docs/` 已废弃ã€�——那是**更早的状æ€�,现已作废** |
|
|||
|
|
| 文档å�Œæ¥ | 改完跑**四件套**:`docs-audit.py` → `docs-index-stats.py --write`(INDEX 状æ€�摘è¦�**机器生æˆ�**)→ `docs-manifest.py` → `docs-sync-check.sh` → `docs-consistency.py`ï¼›**å�ª scp 自己本次改的文件**ã€�推å‰�å¤�跑对账。⛔ `docs-status/` 机制**å·²ä¸�å˜åœ¨**(原文作废) |
|
|||
|
|
| 硬规则 | 动**文档 / 代ç � / æœ�务器**之å‰�先抢**全局执行é”�**(`bash dsh-server-docs/scripts/handoff-guard.sh --claim-exec "<会è¯�å��>"`);è¦�动**生产**å†�å� æœ�务器侧 `scripts/op-lock.sh`ï¼›**å�Œä¸€æ—¶åˆ»å�ªæ”¾ä¸€ä¸ªæ‰§è¡Œä¼šè¯�**ã€‚è§„åˆ™å®žä½“åœ¨é¡¹ç›®æ ¹ `CODEBUDDY.md`(§3 红线 R1–R9 / §6 å¹¶å�‘纪律)—— **本技能ä¸�å¤�è¿°ã€�也ä¸�得与之冲çª�**。⚠ï¸� 释放 op-lock 必须带 `ME=<原å� 用者>`,å�¦åˆ™æŒ‰ R9 被拒 |
|
|||
|
|
| 档案å�· | **å¤�è·‘å�–å�·ï¼Œå‹¿å†™æ»**(`ls 04-调整方案/ | sort -n | tail -1`;并行改动会打穿)(01-52 å·²è�½ï¼›**50/51/52** = 会è¯�过期自愈注入(已被51å�–代) / 实例侧401é€�明é‡�放 / æ–°ç”¨æˆ·å®žä¾‹æ— æ³•å�¯åŠ¨ï¼›âš ï¸� æ— 48ï¼›**37/38 å�„有两æ�¡**(并行通é�“æ’žå�·ï¼‰â†’ **å½’æ¡£å‰�å…ˆ `mkdir <目录>/.lock-<n>` 原å�å� å�·**,光"å…ˆ ls å†�写"ä¸�够) |
|
|||
|
|
|
|||
|
|
## å…阶段æµ�程
|
|||
|
|
|
|||
|
|
### 阶段 0 · 需求识别(先盘点å†�动手,勿跳æ¥ï¼‰
|
|||
|
|
|
|||
|
|
0. **â›” 开工å‰�置检查(2026-09-11 用户点å��è¦�求,血的教è®ï¼‰**:**这是本技能最é‡�è¦�的一æ¥**。
|
|||
|
|
ç—…æ ¹ = ä¸�先查「已ç»�有什么ã€�å°±å‡ç›´è§‰è£…工具ï¼�猜机制;出错å�Žè¦�用户追问,æ‰�把**早就写在文档里的说明**翻出æ�¥
|
|||
|
|
(最刺眼的一次:我早在技能里写了「勿走 agent-browser�,自己�忘了,绕最远的路)。
|
|||
|
|
|
|||
|
|
任何「**装工具 / 写自动化 / 查机制**�之�,按��完这三�:
|
|||
|
|
1. **先看å�¯ç”¨æŠ€èƒ½åˆ—表**(会è¯�开始就在)→ 有对应的**ç«‹åˆ»åŠ è½½æŠ€èƒ½**,ç¦�æ¢å�¦èµ·ç‚‰ç�¶ï¼›
|
|||
|
|
2. **先看本机/项目已有资产**:`~/.workbuddy/skills/`�项目 `.workbuddy/skills/`(尤其本技能)�`MEMORY.md`;
|
|||
|
|
3. **�装任何东西�自问:本机已有的能�满足?** 能→�装;�能→**先说清楚�装**。
|
|||
|
|
|
|||
|
|
→ **固化结论(别��新�现)**:�览器自动化**��许两件** —— **`agent-browser`(默认,独立�览器)** 与
|
|||
|
|
**`browser-harness`(看用户现场用,先 `list_tabs()`)**;⛔ **ç¦�æ¢ Playwright / `playwright-core`**(2026-09-13 用户明令);
|
|||
|
|
��页与 API �数用 `curl`/WebFetch,别动�览器。
|
|||
|
|
|
|||
|
|
1. **环境盘点**:本机/æœ�务器/工具链/æ�ƒé™�/DB/实例状æ€�先行(历å�²æ•™è®ï¼šæœªç›˜ç‚¹å°±é»˜è®¤æœ¬æœºè£… Docker 走错路线,被用户批评)。
|
|||
|
|
2. **需求边界**:区分"方案请求"(�输出方案,P0 硬规则:**方案与执行分离,未�明确授��改文件**)与"任务明确直接执行"。
|
|||
|
|
3. **用户确认**:关键分å�‰ç”¨é€‰é¡¹è¡¨æ”¶æ•›ï¼ˆAskUserQuestion:推è��é¡¹ç½®é¦–æ ‡æ³¨ "(Recommended)",2-4 选项,ä¸�设"å…¶ä»–"å� ä½�)。
|
|||
|
|
4. **è¾“å‡ºæ ¼å¼�**:一å�¥è¯�结论先行 + è¡¨æ ¼åŒ–ç»†èŠ‚ï¼ˆçŠ¶æ€�/结果列)。
|
|||
|
|
|
|||
|
|
### 阶段 1 · è°ƒç ”ï¼ˆæº�ç �级实è¯�优先,ç¦�æ¢å�ªé� 文档推æ–)
|
|||
|
|
|
|||
|
|
0. **★ 用户报障第一æ¥ï¼šå…ˆæ‹¿ã€ŒçœŸå®žå¤±è´¥è¯·æ±‚ã€�,别猜**(2026-09-11 档案 51 实è¯�,çœ�掉全部试错)。
|
|||
|
|
用户�会说"报错�没�应��消�失败",而**平� journal 里有精确的 URL + method + status**:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ssh ... 'journalctl -u dshs --since "-30min" --no-pager \
|
|||
|
|
| grep -E "401|403|404|500|502|503|crash-restart|dsh-child|not_running|YAMLException|ERR_MODULE" \
|
|||
|
|
| grep -v "level.:30.*200"'
|
|||
|
|
# 顺带看 host / remoteAddress(= 用户真实 IP)定ä½�是è°�ã€�连的哪个å�域
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
判�速查:
|
|||
|
|
- `POST /api/session/prompt → 401` = **实例侧鉴�**(档案 51 的旧 cookie 问题);
|
|||
|
|
- `502` = å®žä¾‹æ ¹æœ¬æ²¡èµ·æ�¥ → ç¿»å�Œæ—¶é—´æ®µçš„ `YAMLException` / `ERR_MODULE_NOT_FOUND` /
|
|||
|
|
`crash-loop-circuit-open`(档案 52 就是被这一æ¥ç‰µå‡ºæ�¥çš„);
|
|||
|
|
- 401 先分辨**平� 401**(body `{"error":"unauthorized"}`)还是**实例 401**
|
|||
|
|
(body `dsh web authentication required…`)——**改的地方完全��**。
|
|||
|
|
|
|||
|
|
1. **å�ªè¯»åˆ†æž�官方包**(å…�许):`/usr/local/lib/node_modules/@deepseek-ai/dsh/...` 内 bundle 读代ç �定ä½�机制(slot 声明ã€�inject 契约ã€�fiber 注入)。**ç»�ä¸�写官方主程åº�**。
|
|||
|
|
2. **æœ�务器实测 > 推æ–**:区分 fence 层 / 应用层错误;æµ�览器问题抓 console **完整 err 对象**(`description`/å †æ ˆï¼Œå‹¿å�ªçœ‹å—ç¬¦ä¸²å—æ®µâ€”—`at new apply` ç±»å †æ ˆæ˜¯å®šä½�关键)。
|
|||
|
|
3. **å�Œç«¯å¯¹ç…§**:archive æº� ↔ 实例安装副本 md5 对账;官方行为对照第三方æ�’件写法差异é€�å—æ®µå¯¹æ¯”(如 register çš„ `locale`/`children`/`label` 函数形å¼�)。
|
|||
|
|
4. **多模型交å�‰éªŒè¯�**:关键结论交å�‰æ ¸å¯¹ï¼Œä¸�轻信å�•点推æ–。
|
|||
|
|
|
|||
|
|
### 阶段 2 · 规划(文档先行,方案确认�动工)
|
|||
|
|
|
|||
|
|
1. **方案对比表**:≥2 选项 + �自影�/风险 + 建议(用户�好:结构表对比+�由)。
|
|||
|
|
2. **红线自查**(æ¯�é¡¹æ”¹é€ å¿…è¿‡ï¼‰ï¼š
|
|||
|
|
- R1 �触� dsh 自动获�最新版本;版本�级走独立"�级测试→评估→修�"�程(档案 07)
|
|||
|
|
- R2 ä¸�改官方 dsh 主程åº�与缓å˜ï¼›æ‰©å±•å�ªèµ° profile 层 `dsh plugin remove/add`(tgz)官方机制
|
|||
|
|
- R3 client bundle **严ç¦� `exports.default = apply`**——loader ESM/CJS interop 会å�–纯函数为æ�’件主体 → æ— inject → `ctx.<svc>` 抛 `cannot get property ... without inject`;官方 bundle å�ªå¯¼å‡º `apply`+`inject`
|
|||
- **R4 ç¦�æ¢ç”¨çœŸå®žè´¦å�·ï¼ˆ`admin` / `guest`)å�š API 登录测试**——`auth.ts` 登录时 `deleteUserSessions(user.id)` 是 last-wins å�•活跃会è¯�,**会当场踢掉用户æ£åœ¨ç”¨çš„æµ�览器会è¯�**(2026-09-10 实è¯�:curl 登录 guest 把用户æµ�览器 guest 会è¯�顶掉,页é�¢åˆ·å‡º `unauthorized`)。è¦�测登录æ€�用 `node /opt/dshs/mksess.cjs` (**PG ç›´æ�’**)建临时 session,或注册专用测试账å�·ç”¨å®Œå�³åˆ
|
||||
- **专用测试账�模�(2026-09-11 实�)**:`POST /api/auth/register` → DB
|
||||
|
|
`UPDATE users SET role='active', approved_by=<真实 admin id>`。⚠� 两个�:①`users` 表**没有 `status` 列**,
|
|||
|
|
审批就是改 `role`,CHECK ��许 `admin/pending/active/disabled`(写 `'user'` 会 CHECK 失败);
|
|||
|
|
â‘¡ `approved_by` 有 **FK 指å�‘ `users.id`**,填å—符串会 `FOREIGN KEY constraint failed`。
|
|||
|
|
**凡�"�作该用户自己的实例"(�实例�回收��建�看它的 profile 文件)→ 必须用独立账�**,
|
|||
|
|
�能借 admin/guest 的 session(借了就没法�实例�也污染真实用户)。用完 `DELETE sessions + users` 并 `rm -rf users/<id>`
|
|||
|
|
- **R5 æ�ƒé™�å�¯è§�é�¢å�ªå‡†æ”¶çª„,扩大必须先确认**(2026-09-11 用户新增)——**先判方å�‘**:这次改动是"扩大"还是"收窄"?命ä¸ã€Œæ‰©å¤§ã€�定义(新增 bwrap 挂载 / 放开é�®è”½ / 暴露平å�°ç›®å½•或 env / 放宽 `ALLOWED_ENV` / 放æ�¾ nft / æ��高æ�ƒé™�æ¡£ä½�或放宽 approval / 新增用户å�¯è¯»å†™è·¯å¾„ / 让 root 执行链路的对象å�˜æˆ�用户å�¯æŽ§ï¼‰â†’ **必须在方案里写出「æ�ƒé™�å½±å“�评估ã€�四问并ç‰ç”¨æˆ·æ˜Žç¡®å�Œæ„�**,ä¸�得顺手å�šï¼›æ”¹äº†ã€Œè§¦å�‘文件ã€�清å�•里的文件就一律按 R5 走。收窄å�¯ç›´æŽ¥å�šï¼Œä½†é�®è”½ç±»å¿…é¡»**真å�¯åŠ¨ä¸€æ¬¡å®žä¾‹**验è¯�(档案 42:é�®è”½ `/usr` 内文件让实例起ä¸�æ�¥ï¼‰
|
|||
|
|
3. **å½±å“�评估**:实例é‡�å�¯ä¼šæ�¢ç«¯å�£ï¼ˆauthority å�˜ï¼‰â†’ æ—§ WS æ–连预期;登录 session åˆ é™¤é¢„æœŸï¼›æ•°æ�®ç›®å½•æ�ƒé™�å½±å“�。**è‹¥å‘½ä¸ R5,å�¦é™„「æ�ƒé™�å½±å“�评估ã€�四问 + 改动å‰�å�Ž diff「实例å�¯è®¿é—®è·¯å¾„清å�•(æ�ƒå¨�版)ã€�**。
|
|||
|
|
4. **文档å� ä½�**:`04-调整方案/` 建编å�·æ¡£æ¡ˆï¼ˆæ¨¡æ�¿è§�下),先写需求与改动设计。
|
|||
|
|
|
|||
|
|
### 阶段 3 · å¼€å�‘(å°�æ¥ + 备份 + é€�æ�¡éªŒè¯�)
|
|||
|
|
|
|||
|
|
**æœ�务器 TS/é…�置改ç �æ ‡å‡†æµ�程**(多文件改动):
|
|||
|
|
1. `scp` æœ�务器æº�ç � → 本地工作区 → 本地 Edit(**读一æ�¡â†’改一æ�¡â†’验è¯�一æ�¡**,ç¦�æ¢æ‰¹é‡�读全部文件)
|
|||
|
|
2. `scp` 回�务器 `/tmp/` → `cp` 覆盖�先备份 `bak-<功能>-<YYYYMMDD>/`
|
|||
|
|
3. `npm run build`(tsc 零报错)→ `systemctl restart dshs`
|
|||
|
|
4. 本机直连验�(绕 DNS/代�):
|
|||
|
|
```bash
|
|||
|
|
# SECURE_COOKIES=true → 必须走 TLS;用 --resolve 把域�指到回环
|
|||
|
|
curl -sk --resolve alotbuy.com:443:127.0.0.1 https://alotbuy.com/api/...
|
|||
|
|
# 带登录�:-H "Cookie: sid=<token>"
|
|||
|
|
```
|
|||
|
|
(明文 `http://127.0.0.1:3080` 在 SECURE_COOKIES 下�通)
|
|||
|
|
|
|||
|
|
> ⚠� **全�覆盖 `/opt/dshs/lib/` 之�,必须先在 HEAD 上 `npm run build`**(2026-09-13 22:16 实测事故)
|
|||
|
|
> 并行会è¯�å�šã€Œæ•´åŒ… lib/ 覆盖ã€�时用了一份**ä¸�å�«ä»–人已æ��交改动**的陈旧构建 ⇒ **把别人的已上线修å¤�整体回退**(本例:`/api/dsh/status` çš„ `quota` å—æ®µå‡ç©ºæ¶ˆå¤±ï¼‰ã€‚
|
|||
|
|
> - **指纹**:`lib/**` 一整批文件 mtime 相� + �务��时间对得上 ⇒ 就是整包覆盖,�是点改。
|
|||
|
|
> - **判æ�®**:部署å�Žç«‹åˆ» `md5sum` 对账本机构建,或 `grep -c <ä½ ä¸Šæ¬¡åŠ çš„ç¬¦å�·>` ç›®æ ‡äº§ç‰©ã€‚
|
|||
|
|
> - **é“�律**:**部署完立刻å¤�验「上一次刚验收过的东西ã€�** —— æœ¬ä¾‹æ£æ˜¯é� è¿™ä¸€æ¥æ‰�å�‘现的。
|
|||
|
|
> - �:**「已部署�≠「已�交�**;**�能拿�务器状�当版本事实�**(�务器曾跑�未 commit 的版本)。
|
|||
|
|
|
|||
|
|
**客户端�件(client bundle)部署�程**:
|
|||
|
|
|
|||
|
|
> âš ï¸� **动手改之å‰�å…ˆå�šã€ŒåŸºçº¿æ ¡éªŒã€�(2026-09-13 补,改第三方 tgz æ�’件时尤其必须)**
|
|||
|
|
> æ�’件以 tgz 铺到实例,**本地那个「æº�ç �目录ã€�ä¸�一定是线上æ£åœ¨è·‘的那份**(å�¯èƒ½æ¼‚ç§» / 被别的会è¯�改过 / 是旧快照)。
|
|||
|
|
> æ¥éª¤ï¼šæŠŠ**æœ�务器上æ£åœ¨ç”¨çš„ tgz** 拉下æ�¥è§£åŒ… → 与本地æº�ç �目录å�š**忽略行尾**的全é‡�比对(`diff --strip-trailing-cr`)→
|
|||
|
|
> é€�文件确认「**差异å�ªæœ‰æˆ‘准备改的**ã€�。å�¦åˆ™å�¯èƒ½ç”¨ä¸€å¥—漂移的代ç �**覆盖线上**(本仓有过å�Œç±»äº‹æ•…)。
|
|||
|
|
> - å�ªçœ‹æ–‡ä»¶å��/版本å�·ä¸�ç®—æ ¡éªŒï¼ˆ`package.json` 里的版本å�¯èƒ½ä¸Žå®žé™…ä¸�符;本仓 `lib/index.js` çš„ `VERSION` 常é‡�就长期è�½å�ŽäºŽ `package.json`)。
|
|||
|
|
> - âš ï¸� **è¡Œå°¾ä¼šåˆ¶é€ å�‡å·®å¼‚**:`npm pack` ä¸�规范化行尾,手工拼装的 bundle 常是**混用行尾**(实测:线上 `client.js` CR=5222/LF=5547,本地纯 LF),去掉行尾å�Žæ•´æ–‡ä»¶çš„「差异ã€�会瞬间收敛到真实改动。
|
|||
|
|
> - �例(我犯的):**先改�验**。顺�错 = 风险窗�白开一段;改完�验�能�明「没出事�,�明�了「�会出事�。
|
|||
|
|
|
|||
|
|
1. archive �改版(`/opt/dsh/docs/04-调整方案/poc/<plugin>/lib/client.js` + package.json version/description)
|
|||
|
|
2. `npm pack` → tgz 拷到用户 `ws/`(chown 用户 uid)→ `setpriv --reuid=<uid> --regid=<gid> --init-groups env HOME=... DSH_HOME=... dsh plugin --profile web remove/add <tgz>`
|
|||
|
|
3. kill 实例进程 → 门户自动 respawn 新 pid+**新端�** → `journalctl -u dshs` 抓 `dsh web: http://127.0.0.1:<port>/?token=...`
|
|||
|
|
4. 验è¯� node_modules 副本 markers(versionã€�关键å—符串计数——区分代ç � vs 注释行)
|
|||
|
|
|
|||
|
|
### 阶段 4 · 验�(全链路 + �览器实测 + 清�)
|
|||
|
|
|
|||
|
|
1. **API 链路**:portal enter(sid)→ url token → 页� 200 → `llm/listProviders ok:true`
|
|||
|
|
- **★ 验è¯�用例必须å�Œæ—¶è¦†ç›–「有请求体ã€�ä¸Žã€Œæ— è¯·æ±‚ä½“ã€�两类**(2026-09-11 档案 51 事å�Žä¿®æ£å®žè¯�):
|
|||
|
|
�测 `POST` 会整��掉 `GET`�`SSE`。本次补��版闸门写� `mayHaveBody && …` →
|
|||
|
|
**POST 全绿�`GET`/SSE 完全�生效**,而 SSE ��是"页�已打开�实例被回收"时最早撞 401 的链路。
|
|||
|
|
凡改动å�«"按 method / æœ‰æ— body 分支",**`GET` é‚£æ�¡å¿…é¡»å�•独跑一é��**。
|
|||
|
|
- **模拟æµ�览器è¯ä¹‰è¦�测到底**:æ–言ä¸�å�ªæ˜¯çжæ€�ç �,还è¦�**å�ªå¸¦æœ€åˆ�那一个旧 cookieã€�忽略任何新 cookie**
|
|||
|
|
å†�跑一é��(这æ‰�ç‰ä»·äºŽçœŸå®žæµ�览器)。修完å�Žæ–° cookie 是å�¦**回写**(`Set-Cookie`)必须显å¼�æ–言。
|
|||
|
|
2. **æµ�览器验è¯�æ ˆï¼ˆ2026-09-11 æ›´æ£ä¸ºé�žä¾µå…¥å¼�,勿走 agent-browser)**:
|
|||
|
|
|
|||
|
|
### â›” é“�律 0:动手å‰�å…ˆ `list_tabs()` ç¡®è®¤ã€Œä½ åœ¨é©±åŠ¨å“ªä¸ªæµ�览器ã€�
|
|||
|
|
|
|||
|
|
**browser-harness 会附ç�€åˆ°æœ¬æœº**æ£åœ¨è¿�行的**用户日常 Chrome**(CDP **9223**)——2026-09-11 实è¯�:
|
|||
|
|
`BU_CDP_URL=9223` **�被尊�**(daemon 常驻共享,`BH_RUNTIME_DIR_SHARED=1`),� `BU_NAME` + �共享 runtime �建 daemon �
|
|||
|
|
`list_tabs()` **ä»�返回用户的 16 ä¸ªæ ‡ç¾é¡µ**(`admin.alotbuy.com` / `guest.alotbuy.com` / `portal.html#/plugins/manual`);
|
|||
|
|
`curl --noproxy '*' http://127.0.0.1:9223/json/list` 直接�实 **9223 就是用户�览器的调试端�**。
|
|||
|
|
|
|||
|
|
**æ‰€ä»¥ï¼šè„šæœ¬ç¬¬ä¸€æ¥æ°¸è¿œæ˜¯ `print([t["url"] for t in list_tabs()])`。**
|
|||
|
|
- å�ªè¦�看到的**ä¸�是**ä½ è‡ªå·±æ–°å¼€çš„ç©ºæµ�览器 → **立刻å�œæ‰‹**,å�ªå�šå�ªè¯»æ£€æŸ¥ã€‚
|
|||
|
|
- **ç¦�æ¢**在未确认隔离å‰�å�šä»»ä½•有副作用的动作(登录 / 输入 / æ��交 / 关闿 ‡ç¾ï¼‰ã€‚
|
|||
|
|
- å·²é€ æˆ�的真实事故:在用户æµ�览器里 `fetch('/api/auth/login')` → **覆盖其 `.alotbuy.com` çš„ `sid`**,用户在该æµ�览器里å�˜æˆ�测试账å�·èº«ä»½ã€‚
|
|||
|
|
补救 = åˆ æŽ‰æµ‹è¯•è´¦å�·ï¼ˆcookie 失效 → 用户é‡�新登录å�³å�¯ï¼‰ã€‚**收尾必须 `close_tab` å…³æŽ‰è‡ªå·±å¼€çš„æ ‡ç¾ã€‚**
|
|||
|
|
|
|||
|
|
### æ£ç¡®è°ƒç”¨ï¼ˆWindows,2026-09-11 实测å�¯ç”¨ï¼‰
|
|||
|
|
```bash
|
|||
|
|
R="C:/Users/Administrator/.workbuddy/skills/browser-harness" # 必须 Windows 路径�POSIX 路径 Windows Python �认
|
|||
|
|
export PYTHONPATH="$R/src"; export BH_HOME="$R/.browser-harness-dev"
|
|||
|
|
export BU_CDP_URL="http://127.0.0.1:9223"
|
|||
|
|
env -u HTTP_PROXY -u HTTPS_PROXY -u http_proxy -u https_proxy NO_PROXY="127.0.0.1,localhost" \
|
|||
|
|
"$R/.venv/Scripts/python.exe" -m browser_harness.run < script.py # 脚本走 stdin,�� heredoc 引�地狱
|
|||
|
|
```
|
|||
|
|
helper:`js / click_at_xy / type_text / fill_input / press_key / switch_tab / close_tab / capture_screenshot / list_tabs / wait`。
|
|||
|
|
|
|||
|
|
### ⛔ dsh composer(Lexical)输入难点(2026-09-11 未攻克)
|
|||
|
|
- composer 真身:`div[contenteditable="true"][data-lexical-editor="true"][data-composer-input="true"]`(class `uV2eYG_input`)。
|
|||
|
|
- `type_text()` = `Input.insertText` → **绕过框架监�**,Lexical 模型�更新 = 没输入。
|
|||
|
|
- `fill_input()`(é€�å—符 `press_key` + `input/change`)实测**也未生效**。
|
|||
|
|
- çœŸå› ï¼š**ç›®æ ‡æ ‡ç¾ä¸�是å‰�å�°æ ‡ç¾** → `el.focus()` å�Ž `document.activeElement` ä»�是 `BODY`,CDP é¼ æ ‡/键盘到ä¸�äº†è¯¥æ ‡ç¾ï¼›
|
|||
|
|
`switch_tab` + `ensure_real_tab` + `Page.bringToFront` 都没解决。
|
|||
|
|
- **对照**:Playwright 的 `page.click()` + `page.keyboard.type()` **�功过**(能真实�出消�并拿到回�)。
|
|||
|
|
→ 结论:**è¦�在独å� æµ�览器里跑**(ä¸�ä¸Žç”¨æˆ·æ ‡ç¾äº‰å‰�å�°ï¼‰ï¼›**ä¼ è¾“å±‚ bug 一律用 curl 验è¯�**,æµ�览器å�ªç”¨äºŽè§†è§‰/交互确认。
|
|||
|
|
|
|||
|
|
⚠� **工具选择�径(2026-09-13 用户明令 —— 这�优先于下�那段旧实测)**:
|
|||
|
|
**��许两件工具** —— `agent-browser`(**默认**,独立�览器)�`browser-harness`(附 CDP 9223)。
|
|||
|
|
â›” **Playwright / `playwright-core` å…¨é�¢ç¦�æ¢**(用户 2026-09-13 明令)。
|
|||
|
|
本机现状:`agent-browser` çš„ daemon **èµ·ä¸�æ�¥**(2026-09-13 实测:`open` 挂ä½�零输出,而 `--version` / `node -e` æ£å¸¸ï¼›Chrome 153 已装)⇒
|
|||
|
|
退路走 `browser-harness`,但**必须用下�「独立 headless 实例�那套,��附�用户日常 Chrome**。
|
|||
|
|
æ‹¿ä¸�到æµ�览器时,**é�™æ€�页与 API å�–数一律用 `curl`/`WebFetch`**,UI ç»“æž„ç”¨ã€Œæ— æµ�览器 harnessã€�验收(è§�阶段 4 第 8 æ�¡ä¸‰å±‚验收)。
|
|||
|
|
|
|||
|
|
â›” **`agent-browser` 2026-09-11 实测记录(ä¿�留作背景,勿æ�®æ¤å�¦å®šä¸Šé�¢çš„用户å�£å¾„)**:
|
|||
|
|
①自带 Chromium �从 `storage.googleapis.com` 下 196MB → **必然超时失败**;
|
|||
|
|
② 改用本机 Chrome + `--cdp` 时,环境里有 `HTTP_PROXY=http://127.0.0.1:2349`(WorkBuddy �务代�),
|
|||
|
|
CLI 连 `127.0.0.1` 的 CDP **也走代�** → `Timeout connecting to CDP`
|
|||
|
|
(�先 `env -u HTTP_PROXY -u HTTPS_PROXY -u http_proxy -u https_proxy NO_PROXY="127.0.0.1,localhost"` �通);
|
|||
|
|
â‘¢ å�³ä½¿é€šäº†ï¼Œ**æ ‡ç¾/会è¯�状æ€�ä¸�一致**:`tab list` 显示 `[t1] about:blank`,而 `get url` 报 `login.html`ï¼›
|
|||
|
|
`open <实例å�域>` ä¸�生效。→ **è¦�æµ�览器就用下é�¢è¿™å¥— browser-harness**。
|
|||
|
|
|
|||
|
|
**æ ¸å¿ƒæ•™è®ï¼šä¸�è¦�用用户日常 Chrome。** 直接 `new_tab` 到用户æ£åœ¨ç”¨çš„æµ�览器会å��å¤�弹「å…�许远程调试?ã€�â€”â€”ç”¨æˆ·ä¼šçƒ¦ã€‚æ ¹å› ï¼š**绕过 wrapper 脚本**(`python -m browser_harness.run`)就ä¸�会设 `BH_RUNTIME_DIR_SHARED=1`,daemon æ— æ³•å¸¸é©»å¤�用 → æ¯�次调用新建连接 → **æ¯�次弹一次授æ�ƒ**。
|
|||
|
|
|
|||
|
|
**定型�法 = 起独立 headless 实例(零弹窗��碰用户�览器�cookie 隔离)**:
|
|||
|
|
```bash
|
|||
|
|
# ①一次性å�¯åŠ¨ï¼ˆå�Žå�°å¸¸é©»ï¼›PATH 必须先修好,å�¦åˆ™ rm/node 会失败导致 Chrome æ ¹æœ¬æ²¡èµ·ï¼‰
|
|||
|
|
export PATH="/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/mingw64/bin:/c/Windows/System32:/c/Windows:$PATH"
|
|||
|
|
"/c/Program Files/Google/Chrome/Application/chrome.exe" \
|
|||
|
|
--headless=new --remote-debugging-port=9223 --remote-allow-origins='*' \
|
|||
|
|
--user-data-dir='C:\Users\Administrator\AppData\Local\Temp\chrome-bh-dsh' \
|
|||
|
|
--no-first-run --no-default-browser-check --no-proxy-server --disable-gpu about:blank
|
|||
|
|
# ② 探活:curl -s --noproxy '*' http://127.0.0.1:9223/json/version
|
|||
|
|
```
|
|||
|
|
```bash
|
|||
|
|
# ③ �次验�(把 python 脚本写�文件��定�进 stdin,�� heredoc 引�地狱)
|
|||
|
|
R="C:/Users/Administrator/.workbuddy/skills/browser-harness"
|
|||
|
|
export PYTHONPATH="$R/src"; export BH_HOME="$R/.browser-harness-dev"
|
|||
|
|
export BU_CDP_URL="http://127.0.0.1:9223"
|
|||
|
|
"$R/.venv/Scripts/python.exe" -m browser_harness.run < "C:/Users/.../shot.py"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**登录æ€�**:DB ç›´æ�’临时 session(`user_agent=poc-ui`,用å�Žå�³åˆ ),å�– token å�Ž
|
|||
|
|
`cdp("Network.setCookie", name="sid", value=TOKEN, domain=".alotbuy.com", path="/", secure=True)` → `Page.reload`。
|
|||
|
|
用户侧真实�览器完全��影�(�覆盖其 sid)。
|
|||
|
|
|
|||
|
|
**三个必踩�**:
|
|||
|
|
- **`Emulation.setDeviceMetricsOverride` 按 target 生效** → å¿…é¡»**å…ˆ `new_tab` å†�设覆盖**ï¼›è®¾åœ¨æ—§æ ‡ç¾ä¸Šå†�å¼€æ–°æ ‡ç¾ = 覆盖丢失(截图å�ªæœ‰ 758×482)。
|
|||
|
|
- **hash 残留会骗人**:页é�¢å�œåœ¨ä¸Šæ¬¡çš„ `#/plugins/manual`,é‡�è½½å�Žç›®æ ‡å…ƒç´ ä¸�å˜åœ¨ → `js()` 返回 null / å…ƒç´ æµ‹é‡�å¾— `0/0`。**验è¯�脚本必须显å¼�å¯¼èˆªåˆ°ç›®æ ‡ tab**。
|
|||
|
|
- **Chrome 必须先 `--headless=new` å‰�å�°è·‘通å†�åŠ å�Žå�°**ï¼›å�Žå�°å�¯åŠ¨å¤±è´¥æ—¶å…ˆçœ‹ task 输出(常è§�是 PATH 未设导致å‰�ç½® `rm` 失败,`&&` çŸè·¯ï¼ŒChrome 从未执行)。
|
|||
|
|
- 收尾:临时 session åˆ é™¤ï¼›ç‹¬ç«‹ Chrome å�¯ç•™ç�€å¤�用(内å˜å°�),è¦�å�œå°± kill **9223** 端å�£å¯¹åº”进程。
|
|||
|
|
|
|||
|
|
3. **é�™æ€�文件 vs å�Žç«¯æ”¹åЍ**:改 `web/*.html` **æ— éœ€é‡�å�¯**(é�™æ€�直出,实测æœ�务å�¯åŠ¨æ—¶é—´ä¸�å�˜ï¼‰ï¼›æ”¹ `src/**` æ‰� `npm run build` + `systemctl restart`。**别为å‰�端改动é‡�å�¯æœ�务**ï¼ˆä¼šæ‰“æ–æ‰€æœ‰ç”¨æˆ·å®žä¾‹ï¼‰ã€‚
|
|||
|
|
4. **安全é�¢å¤�测**:é™�æ�ƒ uid 探测æ•�感目录(/rootã€�/etc/shadowã€�DB)应 deniedï¼›ä¸é—´ç›®å½• 711(去 r ç•™ x)ä¸�ç ´å��å�进程访问。
|
|||
|
|
5. **清ç�†ä¸´æ—¶ç‰©**:`poc-*` session 用完å�³åˆ (DB DELETE);测试 tab å…³é—;临时 tgz/脚本归档或清。
|
|||
|
|
6. **截图留è¯�**:æ¯�åŠŸèƒ½éªŒæ”¶å˜ PNG 到本地 tmp + present_files 展示。
|
|||
|
|
|
|||
|
|
7. **🔴 交付å‰�必问一å�¥ï¼šè¿™ä»½æ”¹åŠ¨ã€Œå‡ºåŽ‚ã€�了å�—**(2026-09-13 实è¯� —— 用户第 2 次å��é¦ˆã€Œæ ·å¼�还是没å�˜ã€�çš„**çœŸå› ä¹‹ä¸€**):
|
|||
|
|
改完 `lib/*.js` **≠** 用户会看到�化。出厂链固定为
|
|||
|
|
**改文件 → bump `package.json` 版本 → `npm pack` → ä¸Šä¼ `/opt/dsh/artifacts/<包>-<版本>.tgz` → `node /opt/dshs/scripts/ensure-<包>.cjs --all --restart`**。
|
|||
|
|
- **一秒自检**:`ls -la` 对比「�文件 mtime�与「最新 tgz mtime�—— **�文件更新 ⇒ 还没出厂**。
|
|||
|
|
- âš ï¸� 打包脚本里带æ–言时**å…ˆæŠŠé”šç‚¹æ ¸å¯¹å˜åœ¨å†�è·‘ `npm pack`**:æ–言若在写文件å‰�抛错,版本å�·æ²¡æ”¹ä½† tgz 已被覆盖 ⇒
|
|||
|
|
得到「内容是新版ã€�版本å�·ä»�写旧版ã€�çš„**å�‡äº§ç‰©**(本次已踩,é� æ‰‹å·¥åˆ è¯¥ tgz å…œä½�)。
|
|||
|
|
- 缓å˜ä¸�是借å�£ï¼š`src/supervisor/proxy.ts` 已对 `/plugins/`ã€�`/assets/` 下å�‘ `Cache-Control: no-cache`,且模å�— URL 自带内容 `rev=<sha1>`
|
|||
|
|
⇒ **bundle 一å�˜æµ�览器下次请求就拿到新的**(「看ä¸�到å�˜åŒ–ã€�å‡ ä¹Žéƒ½æ˜¯æ²¡å‡ºåŽ‚ï¼Œä¸�是缓å˜ï¼‰ã€‚
|
|||
|
|
|
|||
|
|
8. **视觉/UI 交付必须三层验收,且è¦�拿到「真æ£ä¸‹å�‘到æµ�览器的那份ã€�**(2026-09-13 定型):
|
|||
|
|
| 层 | �什么 | 能�现什么 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| â‘ å�•测 | `scripts/verify-*.mjs` æ–言 **class å��与结构**(ä¸�å�ªæ–言文本),关键结构é€�项写æ»ï¼ˆä¾‹ï¼š**5 个弹窗都必须是 `.table-wrap + table.tbl`**) | ç»“æž„èµ°æ ·ã€�分支没改到 |
|
|||
|
|
| â‘¡ 实装 | `md5sum` 对é½� **本地产物 / å�„ profile çš„ `node_modules` 实链**,并æ–言**æ—§æ ‡è®°ä¸º 0** | 没铺å�‘ã€�铺了旧版 |
|
|||
|
|
| â‘¢ 端到端 | `mksess.cjs` 临时会è¯� → å�–实例首页 → 解出 `/plugins/??…&rev=<sha1>` → `curl` 回 bundle → grep æ–°/æ—§æ ‡è®°ï¼ˆ**用完å�³åˆ 临时会è¯�**) | bundle 没下å�‘ã€�下å�‘的是旧内容 |
|
|||
|
|
> �� ①⇒「脚本全绿但用户说没��;�� ①② ⇒ �掉「用户拿到的那份�。
|
|||
|
|
|
|||
|
|
9. **🔴 用户说「å�‚考门户/æŸ�个现æˆ�页é�¢çš„æ ·å¼�ã€�时:抄那个页é�¢çš„æº�ç �,ä¸�è¦�抄规范的抽象æ�¡ç›®**(2026-09-13 第 3 次踩):
|
|||
|
|
æ£ç¡®åŠ¨ä½œ = 打开 `web/portal.html` / `web/admin.html` çš„ `<style>`(或它 link çš„ `design.css`),
|
|||
|
|
把 `.nav-card` / `table.tbl` / `.badge` / `.btn-sm` / `.card-h` / `.page-title` 的**实际�值���过�**,
|
|||
|
|
并在注释里写明「本类å�� â†� 门户æŸ�ç±»å��ã€�的对应表(便于å¤�æ ¸ï¼‰ã€‚
|
|||
|
|
⛔ ��:�引 `06-工作�UI规范 §4.5/§4.7` 的抽象�目�自行演绎 —— 用户会说「还是没��。
|
|||
|
|
唯一å…�许的差异:**颜色走 dsh token `--dsw-*`**(嵌在 dsh é�¢æ�¿å†…é¡»éš�主题);å—å�·/é—´è·�/圆角/动效与门户**é€�值一致**。
|
|||
|
|
|
|||
|
|
### 阶段 5 · 归档清ç�†ï¼ˆæ–‡æ¡£å�Œæ¥ + 三层沉淀)
|
|||
|
|
|
|||
|
|
0. ★★ **交付门ç¦�:本机改完 ≠交付**(2026-09-15 åŠ â€”â€” å�Œç±»å·²å�‘生 **3 次**:09-13「å�ªå�šåˆ°æœ¬åœ°æ‰“包ã€�没部署ã€�→ 用户「点开看还是和之å‰�ä¸€æ ·ã€�ï¼›09-14「应用更改是哪里…还是和之å‰�ä¸€æ ·ã€�ï¼›09-15 会è¯�原è¯�「**å¹³å�°é‚£å�Šæˆ‘å�ªæ”¹äº†æœ¬æœºï¼Œä»Žæ²¡éƒ¨ç½²åˆ°æœ�务器 —— é‚£ä½ çœ‹çš„å½“ç„¶è¿˜æ˜¯æ—§é¡µé�¢**ã€�)
|
|||
|
|
**æ”¶å°¾å‰�é€�项自问三å�¥ï¼šâ‘ 这次改的是哪一层?② 这一层的生效链路是什么?③ 最å�Žä¸€æ¥èµ°äº†å�—ã€�在「用户å�¯è§�é�¢ã€�验了å�—?**
|
|||
|
|
| 改动所在层 | 生效链路(**缺一æ¥éƒ½ä¸�算交付**) | 用户å�¯è§�é�¢å¤�验 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **å¹³å�°é�™æ€�页**(`web/*.html`ã€�`web/i18n.js`ã€�`portal.html`…) | `scp` 到æœ�务器对应路径(如 `/opt/dshs/web/`)→ 注æ„� **CDN/CF 缓å˜**(2026-09-14 实测é�™æ€�资æº�被 CF ç¼“å˜ **31 天**) | 用 `curl` å�–**线上**页é�¢ï¼ˆå¸¦ `?lang=` ç‰å�‚数)确认新内容;必è¦�æ—¶ cache-busting |
|
|||
|
|
| **平� TS**(`src/**`) | `npm run build` → **`systemctl restart dshs`** | 线上端点 / 页�实测(**�是**本地 build 通过) |
|
|||
|
|
| **è‡ªç ”æ�’ä»¶**(`poc/*`ã€�client bundle / host é�¢ï¼‰ | 打 tgz → **admin å¯¼å…¥å€™é€‰æ± â†’ 实例「功能管ç�†ã€�å�¯ç”¨** → **é‡�å�¯å®žä¾‹**(client bundle 在**实例å�¯åŠ¨æ—¶**åŠ è½½ï¼Œè§� `06-工作å�°UI规范.md §7`) | å�–实例页 HTML 里真实 bundle URL **é€�串验è¯�**(`rev` å·²å�˜ï¼‰ |
|
|||
|
|
| **文档库 / 技能** | `docs-sync-check.sh` 对账 → `scp`(�相对目录)→ **�跑对账** | 对账「一致�计数回��且**残留项全是别人的 lane** |
|
|||
|
|
| **�置**(env / nginx / nft / ��) | 改 → reload / restart 对应�务 | 生效值实测(`systemctl show` / `curl` / `nft list`) |
|
|||
|
|
⛔ **四�"自我安慰",一�都�算交付**:**本机改完了** / **build 通过了** / **本地打包完�** / **已 commit 了**。
|
|||
|
|
â›” **也别回头问用户「è¦�ä¸�è¦�部署ã€�**:ä¸�打æ–在线用户的上线属**我的 lane 内执行细节 ⇒ 直接å�šå®Œ**(`CODEBUDDY.md §3 R7-边界` + ç´ æ��库 **U20 / X9**);å�ªæœ‰**ä¸�å�¯é€†ç ´å��性æ“�作**与**边界外å…ç±»**æ‰�先问。
|
|||
|
|
📌 与**阶段 4** 的分工:阶段 4 验"**改对了没有**"(三层验收);本门�验"**东西到用户那儿了没有**"(链路完整性)。
|
|||
|
|
|
|||
|
|
1. **文档è�½ç›˜ï¼ˆ2026-09-13 çŽ°çŠ¶ï¼šæœ¬åœ°å·¥ä½œæ ‘ = æº�,æœ�务器 = å�ªè¯»é•œåƒ�)**:
|
|||
|
|
- **æº�** = 本地 `D:\github\dsh_shenxian\dsh-server-docs`(**目录å�³ git å·¥ä½œæ ‘**),推 Gitea `dsh_shenxian_doc.git` çš„ **main**。
|
|||
|
|
- **四件套(顺�固定)**:`docs-audit.py` → `docs-index-stats.py --write` → `docs-manifest.py` → `docs-sync-check.sh`;�有 `docs-consistency.py`。
|
|||
|
|
- **ä¼ æœ�务器**:用**å�•个 `tar` 管é�“**(é€�个 `scp` ä¼šå› æ¯�æ�¡ä¸€ä¸ª SSH 连接而超时):
|
|||
|
|
`tar -cf - <相对路径…> | ssh bt-server 'cd /opt/dsh/docs && tar -xf -'`,�� `chmod`(目录 700 / 文件 600)。
|
|||
|
|
- **对账**:`bash scripts/docs-sync-check.sh`(**æœ�务器镜åƒ�æ— `.git`**,å�ªèƒ½é� md5 对账;è¦�点:`core.autocrlf=false` + `.gitattributes` çš„ `* -text` å¿…é¡»ä¿�æŒ�)。
|
|||
|
|
- â›” **`docs-status-sync.sh` å·²ä¸�å˜åœ¨**(旧æµ�程残留,**å‹¿å†�引用/调用**);状æ€�摘è¦�ä»�在 `/opt/dsh/docs-status/文档库状æ€�备注.md`。
|
|||
|
|
- ⚠� **本库多会�共用** ⇒ �交�先 `git status`,**� `git add` 自己改的文件**;别人在途的改动(2026-09-13 实例:档案 76 md�`skills/dsh-opensource-release/SKILL.md`�`docs-manifest.json`)**一律��交�� scp**。
|
|||
|
|
`docs-manifest.json` 是**派生文件**:它æ��述的是整库状æ€�,若别人有未æ��交改动,**è·Ÿç�€ä¸€èµ·æ��交会把别人的å�Šæˆ�å“�「顺手上é”�ã€�**⇒ æ¤æ—¶å®�å�¯è®©å®ƒä¿�æŒ� dirty。
|
|||
|
|
2. **档案更新**:`04-调整方案/<NN>-*.md` 写"需求→改动文件→commit→验è¯�记录"ï¼›README.md 档案清å�•行å�Œæ¥ï¼›ç‰ˆæœ¬å�· bump。
|
|||
|
|
- âš ï¸� **档案å�·å¿…须『原å�预留ã€�,ä¸�能é� "读一眼 ls å†�写"**(2026-09-11 **两次**æ’žå�·å®žè¯�):**"å�–å�· + 写档案 + 改 `INDEX.md` + 改 `docs-status` å�Œä¸€ä¸²è¡Œäº‹åŠ¡"è¿™æ�¡è§„则本身ä¸�够** —— 两æ�¡å¹¶è¡Œé€šé�“å�Œæ—¶ `ls` 时都读ä¸�到对方的文件,必然撞。**第二次撞å�·ï¼ˆ37/38 å�„两æ�¡ï¼Œ4 个文件挤 2 个å�·ï¼‰å°±æ˜¯è¿™ä¹ˆå�‘生的**。
|
|||
|
|
- **æ£ç¡®å�šæ³•ï¼ˆåŽŸå�é”�)**:写档案å‰�先原å�å� å�·ï¼Œä¾‹å¦‚
|
|||
|
|
```bash
|
|||
|
|
cd /opt/dsh/docs/04-调整方案
|
|||
|
|
n=$(ls | grep -oE '^[0-9]+' | sort -n | tail -1); n=$((n+1))
|
|||
|
|
mkdir ".lock-$n" 2>/dev/null || { echo "å� 用,é‡�å�–"; } # mkdir 是原å�çš„
|
|||
|
|
```
|
|||
|
|
或ç‰ä»·åœ°ç”¨ `flock /opt/dsh/docs/.numbering.lock -c '...'` 包ä½�"å�–å�·+è�½æ–‡ä»¶"整段。**å� å�·ä¸Žè�½ç›˜ä¹‹é—´çš„窗å�£å¿…é¡»æž�çŸï¼Œä¸”ä¸�è¦�在ä¸é€”放开去å�šåˆ«çš„任务。**
|
|||
|
|
- **æ’žå�·å�Žå¤„ç½®**:ä¿�ç•™**å·² commit** 的那个å�·ä¸�动(commit message 已引用它),把**自己未æ��交**的那个顺延改å�·ï¼ˆ`mv` 文件 + 改 INDEX 行 + 改自己的 docs-status å�˜æ›´è¡Œ + 在自己档案内修æ£äº¤å�‰å¼•用 + åŠ ä¸€æ�¡æ’žå�·è¯´æ˜Žï¼‰ã€‚**改å�·è¦�在æ��交å‰�å�š**,å�¦åˆ™å�ˆåˆ¶é€ 一次引用错ä½�。
|
|||
|
|
- âš ï¸� **`docs/README.md` 的档案清å�•å·²è�½å�Žï¼ˆæ¢äºŽ 28)** → **ä¸�è¦�å�ªæ”¹ README**;全é‡�清å�•在 `docs/INDEX.md`,以它为准。
|
|||
|
|
3. **三层沉淀**ï¼ˆç”¨æˆ·ä¹ æƒ¯ï¼Œå¿…å�šï¼‰ï¼š
|
|||
|
|
- 治本层:机制/�程固化进 skill/档案(本次�本 skill)
|
|||
|
|
- å¤±è¯¯å±‚ï¼šæ•™è® append 到 `.workbuddy/memory/YYYY-MM-DD.md`(append-only,ä¸�改写历å�²ï¼‰
|
|||
|
|
- 记忆层:长期约定/红线写 `.workbuddy/memory/MEMORY.md`(≤3000 å—符/会è¯�)
|
|||
|
|
4. **汇总表**:带状æ€�/结果列交付,列明未完æˆ�项与副作用。**排版按 `dsh-feature-first §5.4`**(首å±�给判定 / 层级≤3 / æ¯�节≤7 行 / è¡¨æ ¼â‰¤5 列 / 一æ�¡ä¿¡æ�¯å�ªè¯´ä¸€æ¬¡ï¼›ç»†èŠ‚è¿›é™„å½•ï¼‰ã€‚
|
|||
|
|
5. **技能自身归档(本 skill 有改动时必å�šï¼‰**:工作副本在本机 `.workbuddy/skills/<name>/SKILL.md`ï¼ˆæŠ€èƒ½å¿…é¡»æœ¬åœ°åŠ è½½ï¼‰ï¼Œæœ�务器归档ä½�在 **`/opt/dsh/docs/skills/<name>/SKILL.md`**(root 600)。改完本 skill å�Ž**å�•å�‘推**:
|
|||
|
|
```bash
|
|||
|
|
P=/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0
|
|||
|
|
export PATH="$P/usr/bin:$P/mingw64/bin:/c/Windows/System32:/c/Windows:$PATH"
|
|||
|
|
S="/c/Users/Administrator/AppData/Local/Temp/wb-scratch"; mkdir -p "$S"
|
|||
|
|
cp "<本机 skill>/SKILL.md" "$S/SKILL.md" # 䏿–‡è·¯å¾„先转纯 ASCII
|
|||
|
|
scp -i ~/.ssh/id_ed25519_dsh -P 22 "$S/SKILL.md" [email protected]:/tmp/SKILL.md
|
|||
|
|
ssh -i ~/.ssh/id_ed25519_dsh -p 22 [email protected] \
|
|||
|
|
'D=/opt/dsh/docs/skills/<name>; mkdir -p /opt/dsh/backups/docs-skills; \
|
|||
|
|
cp $D/SKILL.md /opt/dsh/backups/docs-skills/SKILL.md.bak-$(date +%Y%m%d_%H%M%S); \
|
|||
|
|
cp /tmp/SKILL.md $D/SKILL.md && chmod 600 $D/SKILL.md && chown root:root $D/SKILL.md && rm -f /tmp/SKILL.md'
|
|||
|
|
```
|
|||
|
|
**md5 必须�端一致**�算完�;�时更新引用它的元数�:`README.md` 模�表行�`INDEX.md` 场景速查行 + 全�清�行。**方�固定「本机 → �务器�,勿��覆盖**(�务器副本是归档,�是工作副本)。
|
|||
|
|
⚠� **改「跨载体规则�时必�全载体扫�**(2026-09-15 实�):一�交互 / �程规则往往**�时躺在多个载体里**,
|
|||
|
|
å�ªæ”¹ä¸€å¤„ ⇒ 其余载体立刻自相矛盾,**比ä¸�改更糟**。动手å‰�å…ˆ `grep -rn` 关键çŸè¯ï¼ŒæŠŠå‘½ä¸å¤„**全部**列出:
|
|||
|
|
| 载体 | 典型�置 |
|
|||
|
|
|---|---|
|
|||
|
|
| æŠ€èƒ½æ£æ–‡ | `SKILL.md` çš„ §/é“�律/硬约æ�Ÿ/å��模å¼�/自检 + `references/**` |
|
|||
|
|
| é¡¹ç›®æ ¹å¸¸é©»å±‚ | `CODEBUDDY.md §1`(**连带å�ŒèŠ‚çš„æŒ‡é’ˆè¡Œ**,如「排版按 …ã€�) |
|
|||
|
|
| 用户级记忆(跨项目) | `~/.workbuddy/MEMORY.md` |
|
|||
|
|
| **é’©å�文案** | `scripts/stop-dialog-guard.py` çš„ `REASON` / `CONTEXT` |
|
|||
|
|
| **生�物(勿手改)** | `dsh-env-bootstrap/references/常驻规则-快照.md` ⇒ 改完��跑 `python resident-rules.py --snapshot` **�生�**,� `--check` 应 RC=0 |
|
|||
|
|
| 登记行 | `README.md` 模�表 · `INDEX.md` |
|
|||
|
|
判æ�®ï¼šæ”¹å®Œå†� grep 一é��,命ä¸åº”**å�ªåœ¨å·²æ”¹å¤„**。**é’©å�文案最关键** —— 它æ¯�轮都在"æ•™è® AI 该怎么å�š",
|
|||
|
|
与规则打架时å�±å®³æœ€å¤§ï¼ˆæœ¬è½®å®žè¯�:钩å�写「ä¸�è¦�在结尾甩问题ã€�,用户è¦�「待确认内容放到最å�Žã€�,两å�¥è¯�直接对撞)。
|
|||
|
|
æ”¶å�£ = 上表**é€�æ ¼ç¡®è®¤** + 两副本 md5 一致 + 镜åƒ� md5 一致 + 七件套全绿。
|
|||
|
|
|
|||
|
|
## 档案模æ�¿ï¼ˆ04-调整方案/<NN>-<æ ‡é¢˜>.md)
|
|||
|
|
|
|||
|
|
```markdown
|
|||
|
|
# <NN>-<æ ‡é¢˜>(<日期> è�½åœ° / è°ƒç ”ï¼‰
|
|||
|
|
## 背景与动机 # 需求���触�场景
|
|||
|
|
## ç”¨æˆ·å†³ç– # 关键分å�‰ + 选择 + ç�†ç”±ï¼ˆå�«æ—¥æœŸï¼‰
|
|||
|
|
## 实现 # 改动文件清å�• + commit hash + 关键代ç �/é…�置片段
|
|||
|
|
### A. ... # 分模�
|
|||
|
|
## 验�记录 # 实测命令 + 输出 + 结论(�失败�试)
|
|||
|
|
## 事故/踩å�‘记录 # å�‘çŽ°è±¡â†’æ ¹å› â†’è§„é�¿ï¼ˆè‹¥é€‚用)
|
|||
|
|
## 回滚 / 注æ„� # 回滚æ¥éª¤ã€�副作用ã€�å�Žç»å¾…办
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 红线 R1-R8 原文速查
|
|||
|
|
|
|||
|
|
1. **ç¦�æ¢å�¯åЍ dsh 时自动获å�–最新版本**;版本å�‡çº§ç‹¬ç«‹æµ�程(档案 07ï¼›/usr/local/etc/npmrc `update-notifier=false` 已全局生效)。
|
|||
|
|
2. **ä¸�改官方 dsh 主程åº�与缓å˜**(`/usr/local/lib/node_modules/@deepseek-ai/dsh` å�Šä¾�赖);扩展å�ªèµ° profile 层官方æ�’件机制。
|
|||
|
|
3. **client bundle 严ç¦� `exports.default = apply`**(或任何 default 函数导出)——详è§�上文 R3 解释(@dsh-local/portal-entry v0.4.0→v0.4.1 实è¯�æ•™è®ï¼‰ã€‚
|
|||
4. **ç¦�æ¢ç”¨çœŸå®žè´¦å�·ï¼ˆ`admin`/`guest`)å�š API 登录测试**:登录 last-wins ä¼šåˆ è¯¥è´¦å�·å…¨éƒ¨æ—§ä¼šè¯�,直接把用户踢下线。改用 `mksess.cjs` **PG ç›´æ�’**临时 session 或专用测试账å�·ï¼ˆæ¡£æ¡ˆ 15 实è¯�æ•™è®ï¼‰ã€‚
|
||||
5. **������准收窄,扩大必须先确认**(2026-09-11 用户新增,R5)——�下节「R5 ��扩大门��。
|
||||
|
|
6. **R7|ç¦�æ¢æœªç»�确认的批é‡� / 全仓写入**(2026-09-12 用户新增红线)—— è§�下节「R7 批é‡�写入门ç¦�ã€�。
|
|||
|
|
7. **R8ï½œä¸æ–在线用户的生产å�˜æ›´é¡»å…ˆç¡®è®¤**(2026-09-12 用户新增红线)—— è§�下节「R8 生产å�˜æ›´çŸ¥ä¼šã€�。
|
|||
|
|
|
|||
|
|
### R7 批�写入门�(2026-09-12 用户新增红线)
|
|||
|
|
|
|||
|
|
> **用户原è¯�**:「为什么会犯这ç§�错误,容易把æœ�务器æ�žå´©ï¼Œå¿…须记录在红线ä¸ã€‚ã€�
|
|||
|
|
|
|||
|
|
**ç”±æ�¥ï¼ˆçœŸå®žäº‹æ•…)**:为了让 scp 出去的文件行尾干净,写脚本é��历整个代ç �库把 **147 个文本文件** CRLF→LF。当期被è¦�求的å�ªæ˜¯ä¸€å�¥ã€ŒæŠŠæŸ�个 UI å—符串改个å��ã€�,**æ“�作å�Šå¾„放大了两个数é‡�级**。
|
|||
|
|
|
|||
|
|
**å·²ç»�é€ æˆ�的实际æ�Ÿå®³**(ä¸�是ç�†è®ºé£Žé™©ï¼‰ï¼š
|
|||
|
|
- 147 ä¸ªæ–‡ä»¶è¢«æ ‡è®°ä¸º M —— è‹¥ç»§ç» scp / æ��交 / 推é€�,会覆盖æœ�务器上æ£ç¡®çš„版本ã€�产生巨型 diff 掩盖真实改动ã€�并与并行会è¯�冲çª�ï¼›
|
|||
|
|
- éš�å�Žç”¨ `cp -r` å�Œæ¥æ�’件目录时,把**当期并没有改过的** `cordis.patch.yml`ã€�`lib/index.js` 也用 CRLF 覆盖到了**æœ�务器**(已å�‘现并æ�¢å¤�)。
|
|||
|
|
|
|||
|
|
**规则(硬性)**:
|
|||
|
|
|
|||
|
|
1. **å�ªå�šè¢«æ˜Žç¡®è¦�求的事**。执行ä¸å�‘现的é¢�外问题(哪怕看起æ�¥"很å°�ã€�很好修")一律**先报告ã€�å�ŽåŠ¨æ‰‹**,ä¸�得顺手改。用户说"按建议处ç�†"å�ªæŽˆæ�ƒ**é‚£æ�¡å»ºè®®æœ¬èº«**,ä¸�是授æ�ƒä¸€åˆ‡é¡ºå¸¦ä¼˜åŒ–。
|
|||
|
|
2. **ç¦�æ¢**对仓库或生产目录å�šï¼šå…¨åº“é��历改写(`os.walk` / `find -exec` / `grep -rl | xargs`)ã€�通é…�符é‡�写ã€�批é‡� `chmod`/`chown`ã€�**批é‡�æ�¢è¡Œç¬¦è½¬æ�¢**ã€�`cp -r` 整目录覆盖ã€�`git add -A`。
|
|||
|
|
3. **阈值**:一次�作若**�能影� >10 个文件**,或表述里出现「所有 / 整个 / 全库 / 全部�→ **�下�先问**,先产出**�影�清�**�决定。
|
|||
|
|
4. **本机ä¸�是沙箱**:本机镜åƒ�与æœ�务器是两份独立副本;本机的批é‡�改动å�³ä¾¿ä¸�带任何"部署"动作,也会在下一次 scp æ—¶ä¼ å¯¼åˆ°ç”Ÿäº§ã€‚
|
|||
|
|
5. **先用�点验�**:任何批�手段先对 **1 个对象**试,确认�果(`git status`�`file`�`md5`)符�预期�考虑推广。
|
|||
|
|
6. **ä¼ æ’å‰�æ¯”å¯¹å¾…ä¼ æ¸…å�•**:scp / å�Œæ¥å‰�å¿…é¡» `git status` 确认**å¾…ä¼ æ¸…å�•å�ªå�«æœ¬æ¬¡çœŸå®žæ”¹åЍ**,ä¸�å�«è¢«å·¥å…·é¡ºæ‰‹æ”¹åŠ¨çš„æ–‡ä»¶ã€‚
|
|||
|
|
7. **行尾类问题一律先报告**:仓库 blobs 是 LF è€Œæœ¬æœºå·¥ä½œæ ‘å› system 级 `core.autocrlf=true` 呈现 CRLF(`D:/Program Files/Git/etc/gitconfig`)—— 这是**既有环境事实**,ä¸�æž„æˆ�"需è¦�当场修å¤�"的缺陷;è¦�动必须先与用户确认范围。
|
|||
|
|
|
|||
|
|
### R8 生产�更知会(2026-09-12 用户新增红线)
|
|||
|
|
|
|||
|
|
**ç”±æ�¥**:档案 58(内å˜ä¼˜åŒ–)ã€�59(é‡�连å��馈)两次改动都需è¦�é‡�å�¯ `dshs`,**连ç»ä¸¤æ¬¡æŠŠåœ¨çº¿ç”¨æˆ·è¸¢ä¸‹çº¿**,直接引å�‘用户"会è¯�连接异常"报障。
|
|||
|
|
|
|||
|
|
**规则**:
|
|||
|
|
|
|||
|
|
1. 以下动作**éƒ½ä¼šä¸æ–在线用户**,执行å‰�必须先说明「**å½±å“�è°�ã€�æ–多久ã€�为什么必须现在å�š**ã€�å¹¶å�–得确认:
|
|||
|
|
- `systemctl restart dshs`(drain 全部实例)
|
|||
|
|
- `systemctl stop dsh-*.scope`(��个用户实例)
|
|||
|
|
- 批�铺�件 / 改 `MemoryMax` / 改实例 env(都�实例���生效)
|
|||
|
|
- �建 profile�改 profile patch
|
|||
|
|
2. **能选低峰期就ä¸�è¦�在用户活跃时å�š**ï¼›æ— æ³•é�¿å…�时明确告知"会æ–一次"。
|
|||
|
|
3. **改完�验�**:���必须确认�务 active + 门户 200 + 实例能被拉起,��用户交代。
|
|||
|
|
4. 附带:本机 `D:\github` 下的镜�**任何批�改动都视为"�能影�生产"**(� R7 第 4 �)。
|
|||
|
|
|
|||
|
|
### R5 ��扩大门�(2026-09-11 用户新增红线)
|
|||
|
|
|
|||
|
|
**先判方�:这次改动是「扩大�还是「收窄�?**
|
|||
|
|
|
|||
|
|
| æ–¹å�‘ | 例å� | 处置 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **收窄** | �少挂载�去掉白��项�收紧 nft�收窄 env | �直接�,但**�须验�**(�蔽类�能让实例起�� → 档案 42) |
|
|||
|
|
| **扩大** âš ï¸� | 新增 bwrap 挂载 / `--bind`ã€�放开被é�®è”½çš„路径ã€�把平å�°ç›®å½•或文件暴露给实例ã€�给实例注入新 envã€�放宽 `ALLOWED_ENV`ã€�放æ�¾ nft(出网或宿主访问)ã€�æ��高æ�ƒé™�æ¡£ä½�或放宽 `approval`ã€�新增用户å�¯è¯»/å�¯å†™è·¯å¾„ã€�把 root 执行链路(解压 / chown / pnpm)的对象å�˜æˆ�用户å�¯æŽ§ | **一律先出「æ�ƒé™�å½±å“�评估ã€�å¹¶ç‰ç”¨æˆ·æ˜Žç¡®å�Œæ„�**,ç¦�æ¢"顺手å�šäº†" |
|
|||
|
|
|
|||
|
|
**「��影�评估�四问(方案里必须��写,档案留痕)**:
|
|||
|
|
1. **扩了什么** —— ��列具体路径 / 端� / env / ���(��写"优化了访问"这��糊�);
|
|||
|
|
2. **��影�** —— 全部租户 / �租户 / 仅 admin;
|
|||
|
|
3. **有没有�扩大也能实现的方案** —— 若有,必须先�;若确实没有,说明为什么;
|
|||
|
|
4. **回滚方� + 验收方�** —— 回滚命令写清楚;验收**必须 diff 技能里的「实例�访问路径清�(��版)�**,
|
|||
|
|
�项确认"新增项都是用户已��的"。
|
|||
|
|
|
|||
|
|
**🔒 安装类�作 = 扩大,必须先出「安装确认清��并�用户确认**(2026-09-11 用户明确�求:
|
|||
|
|
「需�安装哪些�赖和工具,需�确认��能安装,��安装有风险的内容�)。
|
|||
|
|
凡是�往**平�共享�**(`/usr/local/dsh-runtime`�`/usr/local/bin`�系统包)装东西,**一律�得自动执行**,
|
|||
|
|
先列七项ç‰ç”¨æˆ·ç‚¹å¤´ï¼šâ‘ å��ç§°+版本 â‘¡ **为什么需è¦�**(哪个技能/æ�’件在用)③ **æ�¥æº�ä¸Žæ ¡éªŒæ–¹å¼�**(官方 URL + sha256)
|
|||
|
|
④ 体积 ⑤ 影��(**全部租户**)⑥ 风险点 ⑦ �载方�。
|
|||
|
|
技能/�件**导入时的�赖体检��「报告 + 拦截�(缺失� 409),��代装**。
|
|||
|
|
|
|||
|
|
**触å�‘文件(改这些就必须走 R5ï¼Œæ— ä¸€ä¾‹å¤–ï¼‰**:
|
|||
|
|
`src/supervisor/orchestrator.ts`(bwrap args / `baseEnv`)�`src/supervisor/spawn.ts`(`ALLOWED_ENV`)�
|
|||
|
|
`/etc/nftables-dsh-egress.nft`�`src/web/routes/skills.ts`�`src/web/routes/business-plugins.ts`�
|
|||
|
|
`ensure-role-profile-patch.cjs`(角色 profile patch)。
|
|||
|
|
|
|||
|
|
> **历å�²ä¾�æ�®**:档案 39(`/etc` 白å��å�•化)ã€�40(bundled-skills 挂载)ã€�41ï¼ˆæŠ€èƒ½ä¸Šä¼ /å�¯å�œï¼‰ã€�42(Python è¿�行时)
|
|||
|
|
> 里æ¯�一次"扩大"都是在用户明确è¦�求下æ‰�å�šçš„ï¼›å��例是档案 42 çš„é�®è”½å°�试 —— 属"收窄"ä½†å› è§¦ç¢°æŒ‚è½½ç»“æž„
|
|||
|
|
> 直接让实例起��,**收窄也必须跑真�动验�**。
|
|||
|
|
|
|||
|
|
### Profile 层 cordis patch 机制(角色化 UI �剪,档案 09)
|
|||
|
|
|
|||
|
|
- client æ�’件行 id = **çŸ id**(`ui-settings-models`,é�žåŒ…å��),è§� `dsh --profile web --dump-config`。
|
|||
|
|
- **ç¦�用官方 client æ�’ä»¶**(如设置é�¢æ�¿ã€Œæ¨¡åž‹ã€�分区对普通用户):profile `cordis.patch.yml` 写 `- id: <çŸid>\n name: "@deepseek-ai/<包å��>"\n disabled: true`(dsh-app-boot applyEntryPatches:é�ž insert patch 按 id å�ˆå…¥ overrides)。`--dump-config` 验è¯�ï¼šç›®æ ‡è¡Œå‡ºçŽ° `disabled: true` + `# == ... patched by <path>` 注释。
|
|||
|
|
- **生效必须��实例**(client bundle �动时打包);`patchReload: live` 对 client �件增�**�生效**(实测 5 轮)。
|
|||
|
|
- 当�生产 spawn �带 `--patch` overlay(enablePatch=false),disable �能写 profile 层 `cordis.patch.yml`。
|
|||
|
|
- 幂ç‰å·¥å…·ï¼š`/opt/dshs/ensure-role-profile-patch.cjs [--restart] <username>`(全é‡�=é�ž admin 用户;å�«ç®¡ç�†æ ‡è®°å¤´åˆ™è·³è¿‡ï¼›é�žé»˜è®¤ç©ºå†…容ä¸�覆盖)。admin profile ä¸�动 = admin ä¿�留该分区。
|
|||
|
|
|
|||
|
|
### Skill 装载机制(全员共享�读技能 = bundledSkillDir,档案 10)
|
|||
|
|
|
|||
|
|
- 技能由 agent preset 注册:`@deepseek-ai/dsh-agent-presets/presets/standard/agent.cordis.yml` L83-88(`skill-filesystem` + `tool-skill`),preset æ— config å�— → provider é…�置走 **env**。
|
|||
|
|
- 技能分层 rank(`dsh-skill-filesystem/lib/index.js` roots()):project-dsh 100(`<项目>/.dsh/skills`)/ project-agents 200 / custom 300 / user-dsh 400(`$DSH_HOME/skills`,�用户独立)/ user-agents 500 / **bundled 600(`$DSH_BUNDLED_SKILL_DIR`,`trustedHost: true` �读共享)**。
|
|||
|
|
- 扫æ��粒度:`discoverRoot` **å�ªæ‰« 1 层**(root 下æ¯�目录须å�« `SKILL.md`;或直接 .md 文件);`root/主技能/subskills/å�技能/SKILL.md` **ä¸�会被å�‘现** → subskill è¦�独立å�¯è°ƒéœ€**平铺**åˆ°æŠ€èƒ½æ ¹ã€‚
|
|||
|
|
- **编排器 env 白å��å�•(关键å�¡ç‚¹ï¼‰**:`/opt/dshs/lib/supervisor/spawn.js` `ALLOWED_ENV` ä»… PATH/HOME/USER/TMP/LANG ç‰åŸºç¡€å�˜é‡�ï¼›`baseEnv()` = `{...scrubEnv(process.env), HOME, DSH_HOME, DEEPSEEK_API_KEY}` → **æ–° env å�˜é‡�å¿…é¡»å�Œæ—¶åŠ ç™½å��å�• + baseEnv 显å¼�注入**(src/*.ts å�Œæ¥ï¼‰ï¼Œå�¦åˆ™è¢« scrub ä¸¢å¼ƒã€‚è¿™æ˜¯æ”¹ç¼–æŽ’å™¨ï¼ˆè‡ªç ”ï¼Œé�žçº¢çº¿ 2 对象)。
|
|||
|
|
- MCN V1.0 技能跨平�部署注�:`subskills/browser-harness/envs/` 为 **Windows venv**(`Scripts/*.exe`),Linux �务器��用需�建;海外节点直连抖音风控风险高,数�类优先 RedFox API。
|
|||
|
|
|
|||
|
|
### Skill 管��(编排器 API + ��页,档案 11)
|
|||
|
|
|
|||
|
|
- **dsh 技能å��硬规则**:`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`(`@deepseek-ai/dsh-skill/lib/index.js` SKILL_NAME)—— **䏿–‡å��技能 dsh é�™é»˜ä¸¢å¼ƒ**。MCN ç‰å�«ä¸æ–‡å��技能需先改 `SKILL.md` frontmatter `name` 为 kebab(如 `mcn-workstation`)。
|
|||
|
|
- **API å½¢æ€�**:`/api/skills/shared`(admin 三件套 GET/POST/DELETE)+ `/api/skills/mine`ï¼ˆä»»ä½•ç™»å½•ç”¨æˆ·ï¼‰ï¼›ä¸Šä¼ base64 body `{ file, filename, force? }`ï¼›å�Žç«¯èµ°ç³»ç»Ÿ `tar/unzip` 解压ã€�è·¯å¾„ç©¿è¶Šæ ¡éªŒã€�å�ˆæ³•å��æ ¡éªŒã€‚åŒ…ç»“æž„ï¼šå�•顶层目录 + å�« SKILL.md。
|
|||
|
|
- **è�½ç›˜å±žä¸»**:shared → root:root 0755(OS æ�ƒé™�兜底å�ªè¯»ï¼‰ï¼›mine → `home` 属主用户 uid/gid(确ä¿�用户å�¯æ”¹è‡ªå·±çš„æŠ€èƒ½ï¼‰ã€‚踩å�‘:`chownTree` é¡» chown 顶层目录自身(首次实现å�ª chown å�项,目录ä¿�ç•™ tar 包内 owner)。
|
|||
|
|
- **watch å�³æ—¶ç”Ÿæ•ˆ**:`skill-filesystem` å¯¹å…±äº«æ ¹ watchManager 监å�¬ addDir/unlinkDir/SKILL.md → 自动 invalidate registry(**æ— éœ€é‡�å�¯å®žä¾‹**,æº�ç �å·²æ ¸å®žï¼‰ã€‚
|
|||
|
|
- **编排器 env 注入 DSH_BUNDLED_SKILL_DIR**:spawn.ts ALLOWED_ENV åŠ å�˜é‡� + orchestrator.ts baseEnv 显å¼�注入(`config.bundledSkillDir`,默认 `<dataRoot>/bundled-skills`);这是上一节「全员共享å�ªè¯»æŠ€èƒ½å±‚ã€�的实例侧è�½åœ°ã€‚
|
|||
|
|
|
|||
|
|
## 通用�维锚点(��用)
|
|||
|
|
|
|||
|
|
- 门户���实例需�新 launch:`curl -b "sid=..." -X POST http://127.0.0.1:3080/api/dsh/enter` → 返回实例 port + token url
|
|||
|
|
- 实例检测:`pgrep -af "^node /usr/local/bin/dsh --profile web"`;监�:`ss -tlnp | grep :<port>`
|
|||
- 测试 session 生�:`node /opt/dshs/mksess.cjs`(**PG 直�**,连接串�自 env / `dshs.env` / `dshs.service.d`;10 分钟;`user_agent=poc-curl2`);清�:DELETE WHERE user_agent='poc-curl2'
|
||||
- **å˜é‡�会è¯�æ¡£ä½�体检(档案 36 P0-1,å�ªè¯»ï¼Œå»ºè®®å®šæœŸè·‘)**:æ�ƒé™�æ¡£ä½�**会è¯�级æ’ç§�**(建会è¯�时写 `permission/preset` + `sandbox/mode` + `approval/policy`,**resume ä¸�é‡�æ’**)→ 改默认档ä½�å�ªæƒ å�Šæ–°ä¼šè¯�。查全平å�°è¿˜æœ‰å¤šå°‘会è¯�是旧档ä½�:
|
||||
|
|
```bash
|
|||
|
|
for f in /var/lib/dshs/users/*/home/sessions/*/*/session.jsonl.zstd; do
|
|||
|
|
[ -f "$f" ] || continue
|
|||
|
|
printf "%-12s %s\n" \
|
|||
|
|
"$(zstd -dc "$f" 2>/dev/null | head -c 4000 | grep -o '"preset":"[a-z-]*"' | head -1)" \
|
|||
|
|
"$(echo $f | sed 's#.*/users/##')"
|
|||
|
|
done
|
|||
|
|
```
|
|||
|
|
2026-09-11 实测:**总会� 10,`workspace-write` 10,`danger-full-access` 0** —— 档案 33 之�**一�都没�移**。
|
|||
|
|
- **技能投放体检**:`for u in /var/lib/dshs/users/*/; do echo "$(basename $u): $(ls -A $u/home/skills 2>/dev/null|wc -l)"; done; ls -A /var/lib/dshs/bundled-skills | wc -l` —— 2026-09-11 实测**全为 0**(机制就绪但零投放,档案 36 P0-2)。
|
|||
|
|
- **文档备份目录�先建**:`mkdir -p /opt/dsh/backups/docs` �则 `cp ... .bak-<ts>` 报 `No such file or directory`(本次踩�)。
|
|||
|
|
- �务器 git:`git -C /opt/dshs`(user.name/email = [email protected])
|
|||
|
|
- **âš ï¸� æœ�务器 git æ��交å‰�å…ˆ `git status --short`**ï¼šå·¥ä½œæ ‘å¸¸æ»žç•™æœªæ��交文件(曾有一次 `git add -A` 把 `/logout` 路由ã€�`ensure-role-profile-patch.cjs`ã€�`mksess.cjs`ã€�`*.bak` 一并å�·è¿› `fa718d2`);应**定å�‘ `git add` å�ªæš‚å˜æœ¬æ¬¡æ”¹åŠ¨æ–‡ä»¶**。
|
|||
|
|
- **登录直达冷å�¯åŠ¨ç«žæ€�(档案 13)**:`enter` 返回打开 URL å‰�å¿…é¡»ç‰ launch token(`waitForLaunchTokenForUser`);实例 spawn å�Ž `status` å�³ running 但 HTTP 路由未就绪,æµ�览器撞å�¯åŠ¨çª—å�£ä¼š 404(`proxy.ts` çš„ 404 å�ªå¯¹åº” unknown_user/not_runningï¼Œæ¤ 404 æ�¥è‡ªå®žä¾‹è‡ªèº«é€�ä¼ ï¼‰ã€‚éªŒè¯�用「enter å¹¶å�‘ + `--resolve` 回æº� curlã€�:URL å¿…é¡»å�« `?token=`,å�域应 303→200。
|
|||
|
|
|
|||
|
|
## 本机 Git Bash 环境å�‘(2026-09-11 实è¯�ï¼Œå‡ ä¹Žæ¯�次都踩)
|
|||
|
|
|
|||
|
|
- **PATH 常�**:`Bash` 工具里 `dirname`/`head`/`ls` 报 `command not found`,且 `~/.ssh/id_ed25519_dsh` 也找�到(PATH 里没有 ssh)。**�次先�置**:
|
|||
|
|
```bash
|
|||
|
|
P=/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0
|
|||
|
|
export PATH="$P/usr/bin:$P/mingw64/bin:/c/Windows/System32:/c/Windows:$PATH"
|
|||
|
|
```
|
|||
|
|
- **node 是 Windows 程�,�认 `/c/...` msys 路径**(会拼� `d:\c\Users\...`)。脚本里写路径�用 `C:/Users/...`;而 `ssh`/`scp`/`cp` 这些 msys 程��用 `/c/...`。
|
|||
|
|
- **䏿–‡è·¯å¾„下å�‘文件ä¸�å�¯é� ** → ä¸è½¬æ–‡ä»¶å…ˆ `cp` 到纯 ASCII 路径(`C:/Users/Administrator/AppData/Local/Temp/wb-scratch/`)å†� `scp`。
|
|||
|
|
- **heredoc 通é�“ UTF-8 安全**:`ssh HOST 'cat > /tmp/x' << 'EOF'`(引å�·å®šç•Œç¬¦ï¼‰å�¯åŽŸæ ·ä¼ è¾“ä¸æ–‡ï¼Œå·²å¤šæ¬¡éªŒè¯�;但**别在 heredoc 定界符å�Žå†�接 `' 2>/dev/null`** 之类的引å�·æ‹¼æŽ¥ï¼Œä¼š EOF 报错 —— å¤�æ�‚脚本改æˆ�「本地写 → scp → 远端 node 执行ã€�。
|
|||
|
|
- **PowerShell 工具在本会è¯�输出被å�ž**(命令æˆ�åŠŸä½†æ— stdout)→ 本地æ“�作优先用 Bash(修好 PATH å�Žï¼‰ã€‚
|
|||
|
|
- **`.bak-*` å�¯èƒ½å·²è¢« git 跟踪**(历å�²æ��交过):ä¸�è¦�批é‡�挪动/åˆ é™¤å®ƒä»¬ï¼Œå�¦åˆ™äº§ç”Ÿæ„�外的 `D` å�˜æ›´ï¼›å�ªå�šå®šå�‘ `git add`。
|
|||
|
|
|
|||
|
|
## 外部数æ�®æº�缓å˜å¿…须带结构版本守å�«ï¼ˆ2026-09-11 实è¯�)
|
|||
|
|
|
|||
|
|
æ�¢æ•°æ�®æº�æˆ–åœ¨ç¼“å˜æ�¡ç›®é‡Œå¢žåˆ å—æ®µæ—¶ï¼Œ**å¿…é¡»å�Œæ—¶ bump 一个 `CACHE_VERSION`**ï¼Œå¹¶åœ¨è¯»ç¼“å˜æ—¶æ ¡éªŒç‰ˆæœ¬/结构(ä¸�符å�³è§†ä¸ºæœªå‘½ä¸é‡�拉)。
|
|||
|
|
|
|||
|
|
踩å�‘实录(档案 29):白å��å�•路由从「git clone + è§£æž� ymlã€�改为「拉 `plugins.json`ã€�å�Žï¼Œ`whitelist-cache/index.json` ä»�是**æ—§æ ¼å¼�**且 `fetchedAt` 在 TTL(6h)内 → 被直接å¤�用 → æ�¡ç›®ç¼º `owner`/`importKind` å—æ®µ → æŒ‰å—æ®µè¿‡æ»¤æ—¶ `e.owner.toLowerCase()` 抛异常 → **接å�£ 500**ã€‚åŠ `CACHE_VERSION` 守å�«å�Žè‡ªåЍé‡�拉修å¤�。
|
|||
|
|
|
|||
|
|
é…�å¥—å¥½ä¹ æƒ¯ï¼šâ‘ ç¼“å˜è¯»å¤±è´¥/ä¸�å�¯ç”¨æ—¶**é™�级用旧缓å˜å¹¶è¿”回 `stale: true`**,å‰�端æ��示;② 排åº�按「çƒåº¦ã€�å—æ®µé™�åº�ï¼Œè®©æ— æ•ˆæ�¡ç›®è‡ªç„¶æ²‰åº•,默认首页就是最有价值的那些。
|
|||
|
|
|
|||
|
|
## 官方白���件��(档案 29 定型�径)
|
|||
|
|
|
|||
|
|
- **æ•°æ�®æº�**:`https://awesome-dsh-plugin.com/plugins.json`(官方规范地å�€ï¼Œ3.1MB / 3408 æ�¡ / 23 分类;npm 镜åƒ�包 `dsh-plugin-catalog`ï¼‰ã€‚å—æ®µï¼š`name/owner/url/page/category/description{zh,en}/npm/version/stars/downloads/install/added`。**ä¸�è¦�**å†�去 git clone 仓库解æž� 3431 个 yml —— å…¶ `tarball` 是å�¯é€‰å—段,覆盖率仅 6%(211/3431)。
|
|||
|
|
- **导入å�£å¾„ = 仅预构建**(平å�°ä¾§ç»�ä¸�执行第三方构建脚本):有 `npm` → `registry.npmjs.org/<name>` → `dist-tags.latest` → `dist.tarball`(scoped 包地å�€ `@scope%2Fname`,å�³ `encodeURIComponent(name).replace('%40','@')`);有 `tarball` → release èµ„äº§ï¼›ä¸¤è€…çš†æ— ï¼ˆ`install` 为 `github:owner/repo`)→ æ‹’ç»�å¹¶å›žæ˜¾åŽŸå› ã€‚**å�¯å¯¼å…¥ 1792/3408 = 52.6%**,官方下载é‡� TOP10 全覆盖。
|
|||
|
|
- **npm 官方 tarball å�¯ç›´æŽ¥å–‚现有 `stageTgzArchive`**(`package/` 包装层已被 `findPackageJson` 兼容)→ æ— éœ€æ”¹ DB schema。
|
|||
|
|
- **ç›é€‰æŽ’åº�技巧**ï¼šç»™åˆ—è¡¨åŠ `onlyImportable=1`,并在å‰�端把「å�ªçœ‹å�¯å¯¼å…¥ã€�默认打开;按 `downloads` é™�åº�使未å�‘ npm çš„æ�¡ç›®è‡ªç„¶æ²‰åº•。
|
|||
|
|
- **安全兜底ä¸�外包**:收录 ≠安全审计(上游 README 明确)。所有导入包ä»�èµ°å¹³å�° `stageTgzArchive` 扫æ��,P0 命ä¸å�³é˜»æ–ï¼Œå¹¶æŠŠåŽŸå› **在页é�¢å†…回显**ç»™ admin 判æ–(例:çƒé—¨æ�’ä»¶ `FuRongJun-1999/dsh-memory` å› `docs/…example.yml` 被判 P0,疑似误报)。
|
|||
|
|
|
|||
|
|
## dsh 沙箱与æ�ƒé™�预设机制(2026-09-11 æº�ç �æ ¸å®žï¼Œæ”¹é»˜è®¤å€¼å¿…çœ‹ï¼‰
|
|||
|
|
|
|||
|
|
**两层隔离,别混淆**:
|
|||
|
|
|
|||
|
|
| 层 | ��供 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **内层** | dsh 自带沙箱 `read-only` / `workspace-write` / `danger-full-access` | é� Landlock 或 bubblewrap å�Žç«¯ã€‚**本机ä¸�å�¯ç”¨**ï¼šå†…æ ¸ 5.10(Landlock 需 5.13+,LSM æ— landlock),bwrap 嵌套探测失败 → **fail-closed æ‹’ç»�执行任何 shell**(`@deepseek-ai/dsh-sandbox/lib/index.js:185`)|
|
|||
|
|
| **外层** | å¹³å�° `systemd-run --scope` + `bwrap` + `setpriv` | **真æ£çš„边界**:mount 路径隔离 + uid 隔离 + cgroup(**基础 448M → 上界 1024M**:`MemoryHigh=MIN` 软é™� + `MemoryMax=MAX` 硬é™�ï¼›**与æ�’ä»¶å¼€å…³æ— å…³**(档案 96)/150%/128)。内层失效ä¸�å½±å“�它 |
|
|||
|
|
|
|||
|
|
**æ�ƒé™�预设æ�’ä»¶**(row `@deepseek-ai/dsh-permission-presets`ï¼ŒçŸ id 一般å�³ `permission-presets`):
|
|||
|
|
- 内置预设表(**sandbox 与 approval �对绑定**,这是关键�):
|
|||
|
|
```js
|
|||
|
|
'workspace-write': { sandbox: 'workspace-write', approval: 'ask' }
|
|||
|
|
'danger-full-access': { sandbox: 'danger-full-access', approval: 'never' } // � 选它=�时摘掉审批
|
|||
|
|
```
|
|||
|
|
- `defaultPreset = config.defaultPreset ?? inferredDefault`;默认� `workspace-write`(→ 本机�「shell 全废�)。
|
|||
|
|
- 设置�久化命�空间 = **`permission`**(settings key `permission.defaultPreset`,值必须是 `presets` 里的键�)。
|
|||
|
|
|
|||
|
|
**改默认值的官方�法(�碰官方主程�,R2 �规)**:
|
|||
|
|
|
|||
|
|
> â� **首选:`DSH_PERMISSION_MODE` 环境å�˜é‡�**(2026-09-11 定案,档案 33)。
|
|||
|
|
> 官方 `@deepseek-ai/dsh-base/cordis.patch.yml:217/233` 直接读它:
|
|||
|
|
> ```yaml
|
|||
|
|
> mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
|||
|
|
> policy: !!js "(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'never' : 'ask'"
|
|||
|
|
> ```
|
|||
|
|
> 在编排器 spawn 时注入 `DSH_PERMISSION_MODE=danger-full-access` �� **全局默认「完全���+ �审批**,且:
|
|||
|
|
> ①�必铺 profile(env � spawn,**新用户自动生效**);② 完全�用官方逻辑(预设表 / 设置 UI 都�动)。
|
|||
|
|
> è�½åœ°ä¸¤å¤„:`spawn.ts` çš„ `ALLOWED_ENV` åŠ è¯¥é”® + `orchestrator.ts` çš„ `baseEnv()` 注入(值å�– `process.env.X ?? 'danger-full-access'`,è¿�ç»´å�¯è¦†ç›–)。
|
|||
|
|
> âš ï¸� **副作用**:æ�ƒé™�æ¡£ä½�是**会è¯�级æ’ç§�**(会è¯�创建时写 `permission/preset` / `sandbox/mode` / `approval/policy` 三æ�¡ç§�å�事件)→ **旧会è¯�ä¸�è·Ÿéš�新默认**,必须**新建会è¯�**æ‰�生效。给用户的è¯�术是「新开一个会è¯�ã€�,ä¸�是「刷新页é�¢ã€�。
|
|||
|
|
|
|||
|
|
其余�法(需�更细粒度时�用):
|
|||
|
|
1. **profile 层 `cordis.patch.yml` 改该行 `config`** —— å·²æ ¸å®ž `applyEntryPatches` 里
|
|||
|
|
`const { id, insert, name, ...overrides } = patch` → **patch 的其它键会作为 overrides å�ˆå…¥ç›®æ ‡è¡Œ**,所以 `config:` å�¯è¦†ç›–。
|
|||
|
|
注� **`config` 是整体替��是深�并** → 必须把内置的两个预设一并写全。
|
|||
|
|
2. 或改设置:`$DSH_HOME/settings.yaml` åŠ
|
|||
|
|
```yaml
|
|||
|
|
permission:
|
|||
|
|
defaultPreset: <presets 里的键�>
|
|||
|
|
```
|
|||
|
|
3. **profile 是æ¯�用户一份** → è¦�åƒ� `ensure-role-profile-patch.cjs` é‚£æ ·ç»™æ¯�个用户(å�«æ–°ç”¨æˆ·ï¼‰é“ºï¼›æ”¹å®Œ**å¿…é¡»é‡�å�¯è¯¥ç”¨æˆ·å®žä¾‹**æ‰�生效。
|
|||
|
|
4. 用户��在实例「设置 → ���自助切�(`ui-permission`),这是设计内的。
|
|||
|
|
|
|||
|
|
**推�形�**:��直接用 `danger-full-access`(会连审批一起摘掉)。在 patch 里**新增一个自定义预设**:
|
|||
|
|
`sandbox: danger-full-access` + `approval: ask`,è¯ä¹‰ = 「隔离由平å�°å®¹å™¨æ��供,高风险æ“�作ä»�需确认ã€�。
|
|||
|
|
|
|||
|
|
## 实例��� · 软件共享 · 网络与安全边界(档案 38a,2026-09-11 实测)
|
|||
|
|
|
|||
|
|
### �律:实例能看到什么,完全由 bwrap 挂载�决定
|
|||
|
|
|
|||
|
|
`orchestrator.ts` `spawnAsUser()` çš„ `bwrapArgs` **å�ªæŒ‚这些**(照抄,勿å‡è®°å¿†ï¼‰ï¼š
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
--ro-bind /usr /usr � � /usr/local(宿主装一次 = 全员共享的现�通�)
|
|||
|
|
--ro-bind /lib64 /lib64
|
|||
|
|
--symlink usr/bin /bin ; usr/sbin /sbin ; usr/lib /lib â†� å�ˆæˆ�æ ¹ï¼ˆæ¡£æ¡ˆ 23)
|
|||
|
|
--tmpfs /etc + 14 项文件白�� � �下「/etc 白���;**��用 --ro-bind /etc /etc**
|
|||
|
|
--dev /dev --proc /proc
|
|||
|
|
--bind <userRoot>/tmp /tmp � �用户独立,1777
|
|||
|
|
--bind <userRoot> <userRoot>
|
|||
|
|
--ro-bind-try <userRoot>/home/profiles/<web|headless>/{cordis.patch.yml,package.json,pnpm-lock.yaml} � �读,防绕过投放管控
|
|||
|
|
--unshare-pid � 注�:**没有 --unshare-net**
|
|||
|
|
--chdir <userRoot>/ws -- setpriv --reuid <uid> --regid <uid> --clear-groups <cmd>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> âš ï¸� **最常è§�çš„æ¼�**:**å¹³å�°ä¾§ä»»ä½•新增的共享目录,都必须å�Œæ¥è¡¥ä¸€æ�¡ `--ro-bind`,å�¦åˆ™å®žä¾‹å†…æ ¹æœ¬ä¸�å˜åœ¨**。
|
|||
|
|
> 实è¯�(档案 38a P0):`bundledSkillDir` = `<dataRoot>/bundled-skills`(=`/var/lib/dshs/bundled-skills`)**没被挂载** → 以平å�°åŽŸæ ·å�‚数进实例 `ls` = `No such file or directory`;而 `dsh-skill-filesystem/lib/index.js:84,181` 是 **`resolve()` + 实例内直接读盘**(ä¸�是编排器推数æ�®ï¼‰â†’ **档案 10/11 的共享技能层实际失效:技能投进去也å�‘现ä¸�了**。修法 = `'--ro-bind-try', bundledSkillDir, bundledSkillDir`(放在 `--bind root root` **之å�Ž**)。
|
|||
|
|
|
|||
|
|
#### `/etc` 白��(档案 39,2026-09-11 �地)
|
|||
|
|
|
|||
|
|
**`--ro-bind /etc /etc` ��用** —— 实例会读走 `/etc` 下 **576 个 others-readable 文件**,�**平�情报**:
|
|||
|
|
`/etc/systemd/system/dsh-*.{service,path}`ã€�`/etc/nftables-dsh-egress.nft`(**出网护æ �规则全文**)ã€�
|
|||
|
|
`/etc/cron.d/dsh-*`�`/etc/letsencrypt/renewal/*.conf`。
|
|||
|
|
|
|||
|
|
æ£ç¡®å†™æ³• = **空 `--tmpfs /etc` + é€�文件 `--ro-bind-try`**,白å��å�• 15 项(è¿�行时实测必需最å°�集):
|
|||
|
|
`ld.so.cache` `ld.so.conf` `passwd` `group` `nsswitch.conf` `hosts` `resolv.conf` `host.conf`
|
|||
|
|
`services` `localtime` `os-release` `machine-id` `pki/tls/certs` `pki/ca-trust` **`alternatives`**
|
|||
|
|
|
|||
|
|
- âš ï¸� **symlink å¿…é¡» `realpathSync()` å�Žç»‘ã€ŒçœŸå®žç›®æ ‡ → symlink 原路径ã€�**,å�¦åˆ™å®žä¾‹å†…是æ–链
|
|||
|
|
(`nsswitch.conf` → `/etc/authselect/`;`localtime` → `/usr/share/zoneinfo/`)。
|
|||
|
|
- 🔴 **`alternatives` 是「软链枢纽目录�,必须整体挂 —— 这是最容易��且事故最�蔽的一�**
|
|||
|
|
(2026-09-11 实测踩到):`/usr/bin/python3` 是**两跳软链**
|
|||
|
|
`/usr/bin/python3 → /etc/alternatives/python3 → /usr/bin/python3.6`,
|
|||
|
|
ä¸é—´ä¸€è·³åœ¨ `/etc` → 沙箱内 `python3` **é�™é»˜ `command not found`**(ä¸�是报错,是"命令ä¸�å˜åœ¨")。
|
|||
|
|
ä¸€æ¬¡å°±æ‰“æ– **21 个命令**:`python3` `python` `pip3` `pip-3` `pydoc3` `python3-config` `pyvenv-3`
|
|||
|
|
`easy_install-3` `unversioned-python` `ld`(→`ld.bfd`,node-gyp 编译�用) `pax`
|
|||
|
|
`print-*` `ifup` `ifdown` `lpc`。
|
|||
|
|
→ **判定准则:�绑"直接指� /etc 的软链"是�够的,必须枚举"整�链�会穿过 /etc"的�目**:
|
|||
|
|
```bash
|
|||
|
|
find /usr/bin /usr/sbin /usr/libexec /usr/local/bin -maxdepth 1 | while read -r f; do
|
|||
|
|
[ -L "$f" ] || continue; c="$f"; n=0
|
|||
|
|
while [ -L "$c" ] && [ $n -lt 10 ]; do t=$(readlink "$c")
|
|||
|
|
case "$t" in /*) c="$t";; *) c="$(dirname "$c")/$t";; esac
|
|||
|
|
case "$c" in /etc/*) echo "$f -> $c";; esac; n=$((n+1)); done
|
|||
|
|
done | sort -u
|
|||
|
|
```
|
|||
|
|
安全性:`/etc/alternatives` 33 项**全部指å�‘ `/usr` 或 `/lib64`**(已å�ªè¯»æŒ‚载),ä¸�å�«å‡æ�®/å¹³å�°æƒ…报。
|
|||
|
|
- ⚠� **这类回归的唯一验收手段是"工具清���对比"**(`for c in …; do command -v $c; done`)或**会���**
|
|||
|
|
—— 它�报错��崩溃,�是能力�默消失,所以 `/etc` 白��类改动**必须跑一次全�工具对比**。
|
|||
|
|
- 实测效果:��项 **222→13**��读文件 **576→27**�平�情报 **4/1/2/8 → 0/0/0/0**;
|
|||
|
|
�时 `node` / `os.userInfo` / DNS / node-TLS / `curl` / `dsh --dump-config`(549 行/176 �件)**全通**。
|
|||
|
|
- 凿�®ç±»æœ¬æ�¥å°±è¯»ä¸�到(`dshs.env` 600ã€�`shadow`/`gshadow` 0000ã€�`sudoers` 440)——
|
|||
|
|
**别把"没泄露"当�本次收益**。
|
|||
|
|
- 判æ�®ï¼š**`/etc` 里凡是"å¹³å�°çš„秘密"就白å��å�•化**ï¼›`/usr` ä¸�必动(审计è¯�å®žé›¶å‡æ�®ï¼Œä¸”是è¿�行时宿主;
|
|||
|
|
`/usr/bin` çš„ 1151 个 CLI 是 agent 唯一手脚,去掉=å¹³å�°æ ¸å¿ƒèƒ½åŠ›å½’é›¶ï¼‰ã€‚
|
|||
|
|
|
|||
|
|
#### ⛔ H5 红线:OUTPUT 链按「目的地���本机�务 = 自伤(档案 39 实测踩到)
|
|||
|
|
|
|||
|
|
实例是「先收å�Žå›žã€�çš„æœ�务端:nginx(root) → 实例端å�£çš„ SYN å�ˆæ³•,但**实例回的 SYN-ACK 与å�Žç»æ•°æ�®åŒ…
|
|||
|
|
`daddr` å�Œæ ·æ˜¯ `127.0.0.1`**。若写 `meta skuid <uid段> ip daddr {127.0.0.0/8,…} reject` 一刀切,
|
|||
|
|
**回包会被一起拒掉** → nginx æ— æ³•å›žæº�ã€�**实例整体ä¸�å�¯ç”¨**。
|
|||
|
|
|
|||
|
|
- **症状指纹**:root 连实例端å�£ **timeout(丢包)而ä¸�是 refused**。refused = 规则å�ªæ‰“在客户端方å�‘(æ£å¸¸ï¼‰ï¼›
|
|||
|
|
**timeout 往往�味���都被打到**。
|
|||
|
|
- **æ£ç¡®å†™æ³•**:å�ªåŒ¹é…�**主动å�‘èµ·**的连接 —— TCP 用纯 SYNã€�UDP 用 `ct state new`:
|
|||
|
|
```
|
|||
|
|
meta skuid 100000-199999 ip daddr { 127.0.0.0/8, 172.17.0.1, <eth0>, <公网EIP> } \
|
|||
|
|
meta l4proto tcp tcp flags & (fin|syn|rst|ack) == syn counter reject with tcp reset
|
|||
|
|
meta skuid 100000-199999 ip daddr { …�上… } meta l4proto udp ct state new counter reject
|
|||
|
|
```
|
|||
|
|
- 与平�既有 `src/supervisor/firewall.ts` 的 **portGuard**(iptables `-m owner ! --uid-owner 0 -j REJECT`)
|
|||
|
|
**è¯ä¹‰ä¸€è‡´ã€�范围更全**,且ä¸�ä¾�èµ– `config.portGuard` 开关;portGuard çš„å˜åœ¨**å��è¯�实例进程自身ä¸�需è¦�任何
|
|||
|
|
loopback 连接**。
|
|||
|
|
- **验è¯�é—环(缺一ä¸�å�¯ï¼‰**:①root 回æº�必须拿到 `303`/`200`(**ä¸�能å�ªçœ‹ `ss` 有 LISTEN**);
|
|||
|
|
â‘¡ 实例内自连宿主端å�£å¿…é¡» `ECONNREFUSED`;③ `nft list table ip dsh_egress` 看 counter 是å�¦å‘½ä¸ã€‚
|
|||
|
|
- **改�先查有没有实例在跑**:`systemctl list-units "dsh-*scope" --no-pager --all`。
|
|||
|
|
- 一键�现:`bash scripts/install-egress-guard.sh`(� nft + service,已纳入版本控制)。
|
|||
|
|
|
|||
|
|
#### â›” æ— æ³•é�®è”½ `/usr` 内的文件(档案 42 实测踩到 → 实例起ä¸�æ�¥ï¼‰
|
|||
|
|
|
|||
|
|
想「å±�蔽 `/usr` 里æŸ�个文件ã€�(例如è—�掉旧解释器 `python3.6`)时**ä¸�能用å� åŠ æŒ‚è½½**:
|
|||
|
|
`--ro-bind /dev/null /usr/libexec/platform-python3.6` 会让 bwrap 报
|
|||
|
|
`Can't create file at …: Permission denied` → **实例直接起��**。
|
|||
|
|
æ ¹å› ï¼šbwrap 需è¦�**在 DEST 创建挂载点**,而 `/usr` 是 **ro-bind(å�ªè¯»ï¼‰** → 创建失败。
|
|||
|
|
`/etc` 之所以能自由白å��å�•ï¼Œæ˜¯å› ä¸ºå®ƒå…ˆ `--tmpfs`(å�¯å†™ï¼‰å†�é€�项 `--ro-bind-try`。
|
|||
|
|
|
|||
|
|
- **判æ�®**:**å�ªæœ‰ã€Œå…ˆ tmpfs å†�白å��å�•ã€�的目录æ‰�å�¯è‡ªç”±å¢žåˆ **ï¼›ro-bind 的目录å�ªèƒ½æ•´ç›®å½•决ç–。
|
|||
|
|
- **å�¯è¡Œå�˜ä½“(未采用,收益低风险ä¸ï¼‰**:`--tmpfs /usr/libexec` + rebind 必需å�项
|
|||
|
|
(`git-core` `getconf` `gawk`/`awk` `coreutils` …)→ 与 /etc �构的白��,但�有�类的"�默回归"预案。
|
|||
|
|
- âš ï¸� **改 bwrap å�‚æ•°å�Žå¿…须真å�¯åŠ¨ä¸€æ¬¡å®žä¾‹éªŒè¯�** —— 本类错误会让**æ‰€æœ‰å®žä¾‹æ— æ³•å�¯åЍ**(ä¸�是é�™é»˜é™�级);
|
|||
|
|
改å‰�å…ˆç¡®è®¤æ— å®žä¾‹åœ¨è·‘ï¼š`systemctl list-units "dsh-*scope" --no-pager --all`。
|
|||
|
|
|
|||
|
|
#### 实例共享 Python �行时(档案 42)
|
|||
|
|
|
|||
|
|
- 系统 `python3` = **3.6.8**,本体 `/usr/libexec/platform-python3.6` 是 **dnf/yum 的兄弟解释器**
|
|||
|
|
(shebang 用�对路径 `/usr/libexec/platform-python`)→ **�能移除**;而 `/usr/bin/python3`
|
|||
|
|
�是两跳软链(� `/etc/alternatives`),**动它安全**。
|
|||
|
|
- å¹³å�°å·²è£…**å�¯ç§»æ¤� Python 3.12**:`bash scripts/install-python-runtime.sh`ï¼ˆå¹‚ç‰ / å�¯ç¦»çº¿ï¼‰
|
|||
|
|
→ 解压 python-build-standalone 到 **`/usr/local/dsh-runtime/python-3.12.14/`**,
|
|||
|
|
`/usr/local/bin/{python3,python,pip3}` 指过去。
|
|||
|
|
`/usr` å·² ro-bind + `/usr/local/bin` 在实例 PATH 首ä½� → **实例内 `python3` å�³ 3.12,零代ç �ã€�æ— éœ€é‡�å�¯**。
|
|||
|
|
- **�移打包�元 = `/usr/local/dsh-runtime/` 一个目录**(自包�,��赖系统 rpm):
|
|||
|
|
`tar czf dsh-python-runtime.tar.gz -C /usr/local dsh-runtime` → ç›®æ ‡æœºè§£åŽ‹å›žåŽŸä½� + é‡�跑脚本(å�ªé‡�建软链)。
|
|||
|
|
- 📌 **对 AI/用户的引导(这是"3.6 ä¸�开放"çš„æ£è§£ï¼Œä¸�是é�®è”½ï¼‰**:明确写「Python 用 `python3`(= 3.12);
|
|||
|
|
`python3.6` / `python2` 是系统�留�**��支�**�。实例内 `/usr/local/bin` 在 PATH 首�,
|
|||
|
|
`python3` 已指� 3.12 —— 引导的�本为零�风险为零;而�蔽属"改挂载结构",会让实例起��(�上)。
|
|||
|
|
### 🔒 基础�行时版本冻结(档案 44)
|
|||
|
|
|
|||
|
|
**è¦�求:ç¦�æ¢ç”¨æˆ·ä»¥ä»»ä½•æ–¹å¼�å�‡çº§ Python / pip / node ç‰åŸºç¡€åŒ…**,é�¿å…�版本差异导致æ�’件功能ä¸�å�¯ç”¨ã€‚
|
|||
|
|
|
|||
|
|
**å·²ç»�天然æˆ�立的护æ �**(都ä¸�需è¦�é¢�外代ç �):`/usr` 是 ro-bind →
|
|||
|
|
实例内**改�了** `/usr/local/dsh-runtime/**`;`pip install`(默认)被 Read-only 挡;
|
|||
|
|
`npm/pnpm -g` 失败(`/usr/local/lib/node_modules` �读);`$HOME/.local/bin` **�在**实例 PATH(固定
|
|||
|
|
`/usr/local/bin:/usr/bin:/bin`);dsh 主进程由 orchestrator spawn,PATH �自 root 环境,��用户影�。
|
|||
|
|
|
|||
|
|
**唯一的� = Python 的 user-site**(2026-09-11 实测):
|
|||
|
|
`pip install --user` 一旦创建 `$HOME/.local/lib/python3.12/site-packages`,它就会进入 `sys.path`
|
|||
|
|
且**排在平� `site-packages` 之�** → 用户装的��包**盖�平�包**(实测 `import packaging` 拿到用户版 26.3)。
|
|||
|
|
|
|||
|
|
**修法(两��制性 env,官方机制�互�冲�,已注入 `baseEnv` + 放行 `ALLOWED_ENV`)**:
|
|||
|
|
```
|
|||
|
|
PYTHONNOUSERSITE=1 # user-site �进 sys.path
|
|||
|
|
PYTHONUSERBASE=/usr/local/dsh-runtime/.no-user-install # pip --user 写�读� → 明确报错
|
|||
|
|
```
|
|||
|
|
- 实测:`sys.path` 䏿— user-siteï¼›`pip --user` 报
|
|||
|
|
`Can not perform a '--user' install. User site-packages are disabled for this Python.`ï¼›
|
|||
|
|
历�污染失效(平�包��被盖);**`--target` + `PYTHONPATH` ��用**(这是给用户的推�路径)。
|
|||
|
|
- ⚠� 这两�虽是"注入 env"(R5 触�项),但**方�是收窄**(�制用户侧安装)→ �直接�,档案留痕��。
|
|||
|
|
|
|||
|
|
**版本漂移巡检**:`scripts/runtime-baseline.cjs`(`--accept` 写基线;默认比对,漂移则退出ç � 1),
|
|||
|
|
cron **æ¯�天 05:10** è·‘ï¼›åŸºçº¿å˜ `/opt/dsh/state/runtime-baseline.json`(当å‰� = python3 3.12.14 /
|
|||
|
|
pip 26.2.1 / node 22.23.2 / npm 10.9.8)。**å�‡çº§è¿�行时å�Žå¿…é¡» `--accept` 更新基线**,å�¦åˆ™ä¼šä¸€ç›´å‘Šè¦ã€‚
|
|||
|
|
|
|||
|
|
### 📦 实例共享工具(jq / ripgrep / ffmpeg,档案 46)
|
|||
|
|
|
|||
|
|
**æ ¸å¿ƒç”æ¡ˆï¼šç”¨æˆ·è¦�工具/程åº�包时,`ä¸�需è¦�`æ¯�人装一份** —— 宿主装一次全员共享。ä¾�æ�®ï¼š
|
|||
|
|
实例内 `/usr` 是 **ro-bind** 且 `/usr/local/bin` 在实例 PATH **首�** → 宿主安装**全部实例(�新用户)
|
|||
|
|
ç«‹å�³å�¯è§�**,零代ç �ã€�æ— éœ€é‡�å�¯ï¼›è€Œç”¨æˆ·ä¾§æœ¬æ�¥å°±è£…ä¸�上(`/usr` å�ªè¯» + æ— sudo)。
|
|||
|
|
|
|||
|
|
- **一键安装**:`bash scripts/install-shared-tools.sh`ï¼ˆå¹‚ç‰ / 固定版本 / **官方 sha256 æ ¡éªŒ** / `--force`)
|
|||
|
|
当å‰�已装:`jq 1.8.2`ã€�`ripgrep 15.2.0`(musl é�™æ€�)ã€�`ffmpeg`+`ffprobe`(BtbN é�™æ€�构建,自带全部编解ç �库)。
|
|||
|
|
- **æ–°å¢žå…±äº«å·¥å…·çš„æ ‡å‡†åŠ¨ä½œ**:优先找**é�™æ€�å�•文件**å�‘行版 → 放 `/usr/local/dsh-runtime/bin/` +
|
|||
|
|
软链到 `/usr/local/bin/` → 更新 `/usr/local/dsh-runtime/SHARED-TOOLS.md` →
|
|||
|
|
跑 `node scripts/runtime-baseline.cjs --accept` 刷新版本基线。
|
|||
|
|
- **�移�元**:整个 **`/usr/local/dsh-runtime/`** 一个目录(Python + 这些工具都在里�)→
|
|||
|
|
`tar czf dsh-runtime.tar.gz -C /usr/local dsh-runtime`ï¼Œç›®æ ‡æœºè§£åŽ‹å›žåŽŸä½� + é‡�跑两个安装脚本。
|
|||
|
|
- ⚠� **版本���别用通用规则**:ffmpeg 的版本形如 `N-126492-gefb0a7e5e7`(**�� `x.y`**),
|
|||
|
|
基线脚本对它�独用 `ffmpeg version (\S+)`。
|
|||
|
|
- ⚠� **Playwright �未�**:它�是�文件,除�览器二进制外缺 **11 个系统 `.so`**(`libnss3`/`libgbm`/
|
|||
|
|
`libatk`/`libcups`/`libdrm`/`libxkbcommon`…)。系统库å�¯ `dnf install`(宿主装一次å�Œæ ·å…¨å‘˜å�¯è§�);
|
|||
|
|
�览器二进制建议共享� + `PLAYWRIGHT_BROWSERS_PATH`,但**注入该 env 属 R5「给实例注入新 env�= 扩大**,
|
|||
|
|
须先出「��影�评估�并�得确认。
|
|||
|
|
- 🖥� **admin �视化**:门户 **`#/runtime`「�行环境�**页(档案 47)+ `GET /api/admin/runtime`
|
|||
|
|
(admin å�ªè¯»ï¼‰â€”— 列å��ç§°/版本/**æ�¥æº�**/**安装脚本**/å�¯å�¦å�¸è½½ + **版本漂移告è¦**(对比基线)+
|
|||
|
|
目录/体积/最近å�˜æ›´ + 折å� 的「å�‡çº§ / å�¸è½½ / è¿�移怎么å�šã€�。
|
|||
|
|
**æœ¯è¯æ¾„清**:「宿主ã€�= **æœ�务器本身**(ä¸�是æŸ�个用户);安装由**å¹³å�°ä»¥ root** 执行,用户侧装ä¸�è¿› `/usr`。
|
|||
|
|
|
|||
|
|
- pip �点:默认装 runtime 的 site-packages(**�读被挡**);**推� `--target <ws>/.pylibs` + `PYTHONPATH`**(实测�用)。
|
|||
|
|
- âš ï¸� 宿主 `/usr/local/bin/python3` 现在是 3.12 → å·²æ ¸å®žå®¿ä¸»**æ— ä»»ä½•ä¸œè¥¿ä¾�赖裸 `python3`**
|
|||
|
|
(dnf/yum �对路径;BT-Panel 用自带 pyenv �对路径)。
|
|||
|
|
- ⚠� **平�自身 bug(待治本)**:`business-plugins.ts` / `ensure-biz-plugins.cjs` 以 **root** 跑 `pnpm`
|
|||
|
|
且 `HOME=<userRoot>/ws` → 在用户工作区留下 **root 属主的 `.local/`** → 用户 `pip install --user`
|
|||
|
|
报 `Permission denied`(自己的目录里装�了包)。**�能简�改 HOME**(�与实例共用 pnpm store,
|
|||
|
|
�则 `ERR_PNPM_UNEXPECTED_STORE`)→ 应在 pnpm �程�把 `ws` 下的 root 属主项 chown 回用户,
|
|||
|
|
或放进 `ws-cleanup.cjs` �自愈。
|
|||
|
|
- ðŸ”� **本环境出网走 TLS ä¸é—´äººä»£ç�† → Python 脚本必须显å¼�指定 CA**(2026-09-11 guest 会è¯�实è¯�)。
|
|||
|
|
�务器上有**两份 CA bundle**:
|
|||
|
|
- `/etc/pki/tls/certs/ca-bundle.crt` —— **å�«ä»£ç�† CA**(**curl 默认读这份**,所以 curl 一直æ£å¸¸ï¼‰
|
|||
|
|
- `/etc/ssl/certs/ca-bundle.crt` —— 系统原始,**��代� CA**(**Python 默认读这份**)
|
|||
|
|
|
|||
|
|
→ 在实例里用 Python 抓 HTTPS 会报 `URLError(SSLCertVerificationError: …)` 或
|
|||
|
|
`unable to get local issuer certificate`。**æ£è§£ï¼ˆé›¶å¹³å�°æ”¹åŠ¨ï¼Œå°±æ˜¯ A 方案)**:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
SSL_CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt python3 your_script.py
|
|||
|
|
```
|
|||
|
|
```python
|
|||
|
|
# 或写进脚本(推�,��次手敲)
|
|||
|
|
import os; os.environ.setdefault("SSL_CERT_FILE", "/etc/pki/tls/certs/ca-bundle.crt")
|
|||
|
|
# requests 亦�用 REQUESTS_CA_BUNDLE 指��一文件
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**写技能/脚本时把这一�固化进去** —— 这是**环境特性,�是用户错误**;�固化就会��踩
|
|||
|
|
"�个工具莫�连�上网"。**��**改�平�注入 env:那属 **R5「给实例注入新 env�= 扩大**,
|
|||
|
|
而一行代ç �就能解决。
|
|||
|
|
(相关:`/etc/ssl` å·²åŠ å…¥å®žä¾‹ `/etc` 白å��å�•,å�¦åˆ™è¿žç³»ç»Ÿ CA 都读ä¸�到 —— 档案 49 事å�Žä¿®æ£ 2。)
|
|||
|
|
|
|||
|
|
**跨用户共享本地安装 → 一律走宿主**:
|
|||
|
|
|
|||
|
|
| 方� | 改动 | 评价 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **宿主 `/usr/local` 或 `/usr/bin`**(推è��)| **零代ç �**,`/usr` å·² ro-bind | 装一次全员生效ã€�新用户自动。全局å�•版本 |
|
|||
|
|
| `<dataRoot>/shared-*` + `--ro-bind` + env 白�� | 改 `orchestrator.ts` + `spawn.ts` ALLOWED_ENV | 与 bundled-skills �构,**必须先修 bind** |
|
|||
|
|
| 让用户自己 `pip/npm install` | — | â�Œ 用户侧**装ä¸�上**(`/usr` å�ªè¯» + æ— sudo),装上了也是 N 份é‡�å¤�且污染工作区 |
|
|||
|
|
|
|||
|
|
### 资�与网络边界(改默认值/�容�规划�必看)
|
|||
|
|
|
|||
|
|
- **宿主 1870MB / 2 vCPU / 40G å�•分区,`quotaon /` 未å�¯ç”¨ = æ— ç£�盘é…�é¢�** → å�•租户å�¯å†™æ»¡æ‰“瘫全平å�°ã€‚
|
|||
|
|
- æ¯�å®žä¾‹å†…å˜ **`MemoryHigh=448M` → `MemoryMax=1024M`**(**与æ�’件集å�ˆæ— å…³**ï¼›è§�档案 96)+ `CPUQuota=150%` + `TasksMax=128` → **å¹¶å�‘实例上é™�约 2–3 个**(宿主 1870MB)。
|
|||
|
|
- **bwrap � `--unshare-pid`,� `--unshare-net`** → 实例与宿主**共享网络命�空间**。**已在档案 39 缓解**:nft **H5** ��实例**主动**访问 `127.0.0.0/8` + eth0 + docker0 + 公网 EIP(写法与��上「H5 红线�)。⚠� 残留:实例�� `bind 0.0.0.0`(入站方�未�制)。
|
|||
|
|
- 出网护æ � `table ip dsh_egress`(`/etc/nftables-dsh-egress.nft`,一键å¤�现 `scripts/install-egress-guard.sh`):**H1** `reject` 云元数æ�® `100.100.100.200`ï¼›**H5** å°�é”�实例主动访问宿主自身;其余新建外è�”å�ª `log prefix "dsh-egress" level info` **ä¸�拦截**。DNS = 阿里云内网 `100.100.2.136/138`(**ç»�ä¸�å�¯å°� `100.100.0.0/16`**)。
|
|||
|
|
- `HOME=<userRoot>/ws`(ä¸�是 `home/`)→ pip/npm 缓å˜å…¨è�½åœ¨**用户工作区**里(`ws/.npm`ã€�`ws/.cache/pip`),会被门户 `#/files` 展示ã€�也å�¯èƒ½è¢« ws-cleanup 触碰。
|
|||
|
|
|
|||
|
|
### å�ªè¯»è¯Šæ–é…�方(ä¸�改用户目录ã€�ä¸�起实例)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
B=/usr/bin/bwrap; R=/var/lib/dshs/users/<UID>
|
|||
|
|
# 把 diag 脚本 ro-bind 进命�空间,��往用户 tmp 写文件
|
|||
|
|
$B --ro-bind /usr /usr --ro-bind /lib64 /lib64 \
|
|||
|
|
--symlink usr/bin /bin --symlink usr/sbin /sbin --symlink usr/lib /lib \
|
|||
|
|
--tmpfs /etc --dev /dev --proc /proc \
|
|||
|
|
--bind $R/tmp /tmp --bind $R $R --ro-bind /tmp/diag.sh /tmp/diag.sh \
|
|||
|
|
--unshare-pid --chdir $R/ws \
|
|||
|
|
-- setpriv --reuid <uid> --regid <uid> --clear-groups /bin/sh /tmp/diag.sh
|
|||
|
|
# æ�ƒå¨� bwrap å�‚æ•°ä¸�è¦�读æº�ç �猜:直接看失败/è¿�行ä¸çš„ scope
|
|||
|
|
systemctl list-units "dsh-*" --no-pager --all | grep scope
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> âš ï¸� **`pgrep -f "dsh --profile web"` 会匹é…�åˆ°ä½ è‡ªå·±çš„å‘½ä»¤è¡Œ**(命令串里å�«è¯¥æ¨¡å¼�)→ 拿到å�‡ PID。改用 `ps -eo pid,user,cmd | grep "^ *[0-9]\+ .*node /usr/local/bin/dsh"` 或直接读 `systemctl list-units "dsh-*"`。
|
|||
|
|
|
|||
|
|
### 📋 实例�访问路径清�(��版 · 2026-09-11 档案 39 收窄�)
|
|||
|
|
|
|||
|
|
**å�ªæœ‰ä¸€æ �是"该用户的数æ�®",其余都是"公共软件"或"å¹³å�°è‡ªå·±"——写文档/ç”用户时按æ¤è¡¨å�£å¾„。**
|
|||
|
|
|
|||
|
|
| 能读到 | �� | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `<dataRoot>/users/<该用户id>/` | **读写** | **唯一属于该用户的数æ�®**:`home/`(= `DSH_HOME`:profiles/sessions/settings/storages/`skills/`(**å·²å�¯ç”¨**)/ `skills-library/`(**å·²ç¦�用的自建技能**,档案 41)/ `.credentials.yaml`)ã€�`ws/`(= `HOME`,默认工作区,pip/npm 缓å˜ä¹Ÿåœ¨è¿™ï¼‰ã€�`tmp/`(该用户的 `/tmp`,1777)ã€�`patches/*.yml`ã€�`handoff.json` |
|
|||
|
|
| `/usr` | å�ªè¯» | 公共软件层(node/dsh/npm/pnpm + `/usr/bin` 1151 个 CLI + `/usr/lib64`)。**审计è¯�å®žé›¶å‡æ�®ã€�零用户数æ�®** |
|
|||
|
|
| `/lib64` | �读 | 系统共享库(node 动��赖 7 个 `.so`) |
|
|||
|
|
| `/etc`(**白�� 15 项**) | �读 | `ld.so.cache` `ld.so.conf` `passwd` `group` `nsswitch.conf` `hosts` `resolv.conf` `host.conf` `services` `localtime` `os-release` `machine-id` `pki/tls/certs` `pki/ca-trust` **`alternatives`**;**其余 `/etc` = 空 tmpfs(�写�临时�退出�消)** |
|
|||
|
|
| `/dev`�`/proc` | — | �行必需;`--unshare-pid` → `/proc` ��本命�空间进程 |
|
|||
|
|
| `/bin` `/sbin` `/lib` | 符å�·é“¾æŽ¥ | → `usr/bin` `usr/sbin` `usr/lib`(补é½�æ ‡å‡†å¸ƒå±€ï¼Œé�¿å…�沙箱探针 execvp 失败) |
|
|||
|
|
| `<dataRoot>/bundled-skills/` | **å�ªè¯»** | å¹³å�°å…±äº«æŠ€èƒ½å±‚(档案 40 起已挂载)。**内容对æ¯�个登录用户å�¯è¯»** → ç¦�æ¢æ”¾å†…部文档/ç§�有æ��示è¯�/凿�® |
|
|||
|
|
|
|||
|
|
| 读�到 | �� |
|
|||
|
|
|---|---|
|
|||
|
|
| `<dataRoot>/users/<其他用户id>/` | bwrap 未挂载 + uid 隔离(实测�列自己那 1 项) |
|
|||
|
|
| `<dataRoot>/dshs.db` | 未挂载(实测 No such file) |
|
|||
|
|
| `/etc` 白å��å�•外**全部**(systemd å�•å…ƒ / nft 护æ � / cron / letsencrypt / ssh / firewalld / selinux / 576 个文件) | 档案 39 `/etc` 白å��å�• |
|
|||
|
|
| `/root`�`/home/*`�`/opt`�`/var`(自己路径除外)�宿主 `/tmp` | bwrap 未挂载 |
|
|||
|
|
| 宿主 `127.0.0.1:*`�eth0�docker0�公网 EIP | nft **H5**(实测 ECONNREFUSED) |
|
|||
|
|
| 云元数� `100.100.100.200` | nft **H1** |
|
|||
|
|
|
|||
|
|
**�然�达(已知残留)**:公网(pip/npm/API/LLM)�阿里云内网 DNS `100.100.2.136/138`�
|
|||
|
|
`bind 0.0.0.0`(**入站方å�‘未é™�制**,跨租户互通é� H5 çš„å�‘é€�侧拦截)ã€�å�Œå®žä¾‹å‘½å��空间内 `setpriv` å�Žçš„自有进程。
|
|||
|
|
|
|||
|
|
**技能的投放通�(2026-09-11 实测)**:
|
|||
|
|
| 通� | 路径 | ��改 | 备注 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| preset 自带 | `<dsh>/node_modules/@deepseek-ai/dsh-agent-presets/presets/<preset>/skills/` | **��** | dsh �内置 **2 个**(`cordis-plugin-development`�`editing-cordis-compositions`,都在 `cordis` preset 里)。**改它 = 改官方包(R2 红线)** |
|
|||
|
|
| bundled 共享层 | `$DSH_BUNDLED_SKILL_DIR`(rank **600**,全员å�ªè¯»ï¼‰ | å¹³å�° | âš ï¸� **当å‰�æ–çš„**:è§�下 |
|
|||
|
|
| 用户层 | `$DSH_HOME/skills`(rank **400**,�用户独立,watch �时生效) | 用户 | 平�"用户级��"应走这层 |
|
|||
|
|
|
|||
|
|
> ✅ **bundled 层已修å¤�(档案 40,commit `37e016f`)**:bwrap args 在 **`--bind root root` 之å�Ž**追åŠ
|
|||
|
|
> `'--ro-bind-try', bundledSkillDir, bundledSkillDir`(仅当 `config.bundledSkillDir !== ''`)。
|
|||
|
|
> 实测:实例内 `ls $DSH_BUNDLED_SKILL_DIR` ���`readFileSync(SKILL.md)` = OK(= �现�件�立)�
|
|||
|
|
> `mountinfo` 为精确å�¶å� `ro,nosuid,nodev`ã€�`touch` = Read-onlyï¼›**å�¯è§�é�¢æœªæ‰©å¤§**(`ls users/` ä»� 1 项ã€�
|
|||
|
|
> DB ä¸�å�¯è¯»ã€�`/etc` ä»� 13)。⚠ï¸� æ¤å‰�"å�ªæ³¨å…¥ env 就以为é…�好了"是错的 —— **skill-filesystem 在实例内读盘,
|
|||
|
|
> env �决定"去哪儿找",目录�挂载就是找�到**(通用判�:�件在实例内 `resolve()` + 读盘的东西,
|
|||
|
|
> 必须真的出现在命�空间里)。
|
|||
|
|
> ⚠� **宿主 `<dataRoot>/bundled-skills` 目��为空** —— 机制通了,但**尚未投放任何技能**。
|
|||
|
|
> 🔬 **修法安全性已实测(2026-09-11,guest uid 100002 实跑)**ï¼šåŠ è¯¥è¡Œå�Ž `mountinfo` = `…/bundled-skills → å�Œå�� ro,nosuid,nodev`(**精确å�¶å�路径 + å�ªè¯»**);`ls …/users/` **å�ªåˆ—用户自己那一个**(看ä¸�到其他用户);读 DB = **No such file**ï¼›`touch` 挂载点 = **Read-only file system**。**对照组(ä¸�åŠ ï¼‰= `/var/lib/dshs/` 里å�ªæœ‰ `users`** → 支æŒ�é�¢é›¶å�˜åŒ–,且**父目录本æ�¥å°±æ˜¯ bwrap 为用户 root å�ˆæˆ�çš„**(今天线上已å�¯è§�该路径å��,`users/` 是å�ªå�«è‡ªå·±çš„空壳),**ä¸�是这一行新暴露的**。
|
|||
|
|
> â›” **红线:å�ªèƒ½æŒ‚最内层路径**。写æˆ� `--ro-bind /var/lib/dshs /var/lib/dshs`(父目录)= **一次性把全部用户 home + DB + 凿�®æ”¾è¿›æ¯�个实例**。这是本改动唯一的真实风险,且属手滑型错误 —— 改完必须**é€�å—å¤�æ ¸ bwrap args**,并跑一次上é�¢çš„å�¯è§�é�¢å®žæµ‹ã€‚
|
|||
|
|
> âš ï¸� **副作用(设计æ„�图,但è¦�说清)**:挂上å�Ž `bundled-skills/` 里的内容**对æ¯�个登录用户å�¯è¯»** → 该目录**ç¦�æ¢æ”¾å†…部文档 / ç§�有æ��示è¯� / 凿�®**;投放清å�•必须按"全员å�¯è§�"审一é��。
|
|||
|
|
> âš ï¸� **dsh 技能没有"ç¦�用"机制**(`skill-filesystem` æ— disable/deny)→ ranked 600 的共享层**æ— æ³• per-user å…³é—**,å�ªé€‚å�ˆ"人人必须有的基线技能"ï¼›è¦�让用户å�¯å�¯å�œï¼Œå¿…须用 rank 400 的用户层(å¤�制进/åˆ æŽ‰ï¼‰ã€‚
|
|||
|
|
|
|||
|
|
**å¹³å�°ä¾§ API ↔ 技能层 çš„æ˜ å°„ï¼ˆ2026-09-11 æ ¸å®ž `src/web/routes/skills.ts`;档案 41 å�Žå·²æ‰©å……)**:
|
|||
|
|
|
|||
|
|
| API | 鉴� | �点 | 层 | 说明 |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| `GET/POST/DELETE /api/skills/shared`(+`/apply`) | **`requireAdmin`** | `<dataRoot>/bundled-skills` | bundled **600** 共享å�ªè¯» | ✅ 档案 40 起已挂载å�¯å�‘现;对用户 **`locked:true`(ä¸�å�¯ç¦�用/åˆ é™¤ï¼‰** |
|
|||
|
|
| `GET/POST /api/skills/mine`(+`/apply`) | `requireAuth`(任�登录用户) | `<userRoot>/home/skills` | 用户 **400** 独立 | ✅ 用户自建技能;`GET` �项带 `source`/`enabled`/`locked` |
|
|||
|
|
| `POST /api/skills/mine/:name/enable` \| `/disable` | `requireAuth` | å�¯ç”¨ ↔ `<userRoot>/home/skills-library/` | 用户 **400** | **dsh æ— disable 机制** → 「ç¦�用ã€�= `rename` 出扫æ��æ ¹ï¼ˆ**ä¿�留文件**,å�¯å†�å�¯ç”¨ï¼‰ã€‚watch å�³æ—¶ç”Ÿæ•ˆï¼Œ**æ— éœ€é‡�å�¯** |
|
|||
|
|
| `DELETE /api/skills/mine/:name` | `requireAuth` | 两处å�Œåˆ | — | 彻底移除用户自建技能 |
|
|||
|
|
| å�Œå��守å�« | — | — | — | 与共享技能å�Œå�� → ä¸Šä¼ /å�¯ç”¨/ç¦�用/åˆ é™¤**一律 409**(å�¦åˆ™ç”¨æˆ·ç™½ä¼ 一份永远被 rank 600 压ä½�ã€�å�ˆå…³ä¸�掉的技能) |
|
|||
|
|
|
|||
|
|
> âš ï¸� **投放å‰�必须平铺 —— `discoverRoot` å�ªæ‰« 1 层**:带 `subskills/` 的技能(实è¯� `çŸè§†é¢‘工作å�°/subskills/{mcn-data-insight,mcn-dou-analysis,…}`)**å�技能ä¸�会被å�‘现**。投放时把 `subskills/*` æ��å�‡ä¸ºé¡¶å±‚,或æ¯�层å�„投一个技能。`references/`ã€�`scripts/` 是技能内部相对路径引用,**ä¸�å�—å½±å“�ã€�别动**。
|
|||
|
|
> âš ï¸� **`scripts/` çš„å�¯ç”¨æ€§ç”±"宿主基线"决定**(技能脚本ç»� bash 执行 → å�—å�ªè¯» `/usr` 约æ�Ÿï¼‰ï¼šå®¿ä¸»ç¼º `python3.9+`/`ffmpeg`/`rg`/`jq`/æµ�览器时,脚本写出æ�¥ä¹Ÿè·‘ä¸�动。**投放å‰�å…ˆæ ¸å¯¹å®¿ä¸»åŸºçº¿**,别在 SKILL.md 里写"请先装 X"(用户侧装ä¸�上)。
|
|||
|
|
> ⚠� **安全:分清"�在执行"**(档案 41 定稿)——
|
|||
|
|
> · **è¿�行**技能 `scripts/`:走 agent çš„ bash 管线,困在用户沙箱里(uid+bwrap+cgroup+`/etc` 白å��å�•+H5)→ **自伤,å�¯æŽ¥å�—**,**ä¸�该以"用户能跑任æ„�代ç �"为由ç¦�æ¢**ï¼›
|
|||
|
|
> · **ä¸Šä¼ /解压**技能包:**å¹³å�°ä»¥ root 执行** → **è¿™æ‰�是平å�°çº§é£Žé™©ç‚¹**(è§�ä¸‹ã€ŒæŠ€èƒ½ä¸Šä¼ å®‰å…¨ã€�)。
|
|||
|
|
> 注�档案 33 �默认 `danger-full-access` + `approval: never` → 技能脚本**��弹确认**。
|
|||
|
|
|
|||
|
|
### æŠ€èƒ½ä¸Šä¼ å®‰å…¨ï¼ˆæ¡£æ¡ˆ 41 · 该类æ¼�洞的通用判æ�®ï¼‰
|
|||
|
|
|
|||
|
|
**P0 实测å¤�çŽ°çš„æ ¹å› ï¼š`zip` çš„æˆ�员å�¯ä»¥æ˜¯ç¬¦å�·é“¾æŽ¥ï¼Œè€Œ `unzip` é»˜è®¤åŽŸæ ·æ�¢å¤�它。**
|
|||
|
|
symlink çš„**ç›®æ ‡å†™åœ¨ zip 元数æ�®é‡Œã€�ä¸�在任何文件内容ä¸** → `scanDir`(å�ªè¯»æ–‡ä»¶å†…容,且é�‡ `!st.isFile()` 直接 `continue`)**天然看ä¸�到**ï¼›æˆ�员å��æ ¡éªŒï¼ˆæ‹’ç»�对路径 / `..`)也**放行**。
|
|||
|
|
éš�å�Ž `applyStaged` 以 **root** 执行 `chownTree`/`chmodTree`,二者**è·Ÿéš�**符å�·é“¾æŽ¥ → 改写**é“¾æŽ¥ç›®æ ‡**(宿主任æ„�文件)的属主与æ�ƒé™�。实测 `-rw------- root:root` → **`-rw-r--r-- <租户uid>`**;指å�‘ `/etc/shadow` = 密ç �哈希 chmod 644 全机å�¯è¯» + chown 给该租户。
|
|||
|
|
攻击é�¢ = **ä¼ äº† `owner` 的那æ�¡è·¯ç”±**(`mine`ï¼›`shared` ä¸�ä¼ owner,ä¸�å�—å½±å“�)。
|
|||
|
|
|
|||
|
|
**å¿…é¡»å�Œæ—¶è¦†ç›–两个维度**:①**文件内容**(现有 `BLOCK_PATTERNS` P0 é˜»æ– / `WARN_RULES` P1 å‘Šè¦ â€”â€” `scanDir` 内部**会 throw 400**,ä¸�是å�ªå‘Šè¦ï¼‰ï¼›â‘¡ **归档元数æ�®ä¸Žæ–‡ä»¶ç±»åž‹**(symlink / 硬链 / 设备 / FIFO + 解压规模)。
|
|||
|
|
|
|||
|
|
**ä¸‰é¡¹åŠ å›ºï¼ˆå·²è�½åœ°ï¼‰**:
|
|||
|
|
1. `findSpecialEntry()` —— 解压�**立刻**拒�任何�普通文件/目录�目(在任何 `chown/chmod` 之�);
|
|||
|
|
2. `sumUncompressedSize()`(解� `unzip -l`)+ `MAX_SKILL_FILES=2000` + `MAX_SKILL_UNCOMPRESSED_BYTES=200MB`
|
|||
|
|
—— `UPLOAD_BODY_LIMIT=180MB` å�ªé™�**压缩体**,而宿主 `quotaon /` 未å�¯ç”¨ = **æ— ç£�盘é…�é¢�** → zip bomb å�¯è·¨ç§Ÿæˆ· DoSï¼›
|
|||
|
|
3. 纵深防御:`chownTree` 用 **`lchownSync`** 且跳过 symlinkï¼›`chmodTree` 跳过 symlink(**Linux æ— `fs.lchmod`**,ENOSYS)。
|
|||
|
|
> 更彻底(未å�šï¼‰ï¼šä»¥ç›®æ ‡ uid **é™�æ�ƒè§£åŽ‹**(`setpriv --reuid <uid> ... unzip`),让整æ�¡é“¾è·¯éƒ½ä¸�在 root 下。
|
|||
|
|
|
|||
|
|
**技能的ä¾�赖:没有机制(最大缺å�£ï¼‰**。`dsh-skill-filesystem` **ä¸�处ç�†ä»»ä½• install/deps/package.json** —— 技能包å�ªèƒ½é� `SKILL.md` 写"请先装 X",由 agent è¿�行时手敲。对比æ�’件:`package.json.dependencies` + `pnpm add` 自动解æž�(store 硬链å¤�用)。约æ�Ÿï¼šå®žä¾‹å†… `/usr` **å�ªè¯»** → 系统级装ä¸�上;å�ªèƒ½ `--user`/æœ¬åœ°ï¼›æ— å…±äº«ã€�æ¯�用户å�„装一份。
|
|||
|
|
→ **平�若�多用户投放带�赖的技能,必须在�用时"代装"**:Node �赖装到该技能目录(或用户 ws 下统一 `node_modules` + `NODE_PATH`);Python �赖用**用户级 venv**(`$DSH_HOME/.venvs/<skill>`)并让脚本用该解释器。**��让 agent 临时 pip/npm install**(�读 `/usr` + 供应链 + ���现)。
|
|||
|
|
|
|||
|
|
## 技能 vs æ�’件:怎么区分(2026-09-11 æ›´æ£ï¼Œåˆ«ç”¨"有没有代ç �/界é�¢"判)
|
|||
|
|
|
|||
|
|
**å…ˆçº æ£ä¸€ä¸ªå¸¸è§�误解**:技能**å�¯ä»¥å¸¦ `scripts/`**(实è¯�:`çŸè§†é¢‘工作å�°/scripts/MCN_CYLG_API.py`ã€�`mcp-config.json`)。所以「技能=çº¯å†…å®¹æ— ä»£ç �ã€�「ä¸�带界é�¢çš„æ�’件就是技能ã€�**都ä¸�æˆ�ç«‹**。
|
|||
|
|
|
|||
|
|
**æ£ç¡®åˆ¤æ�® = 「代ç �何时ã€�被è°�执行ã€�**:
|
|||
|
|
|
|||
|
|
| | 技能(skills) | dsh �件(plugins) |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 包形� | 目录 + `SKILL.md`(+ `scripts/`�`references/`) | npm 包 + **`dsh.bundle.patch`**(+ �选 `client.js`) |
|
|||
|
|
| **代ç �执行者** | **agent 通过 bash 工具按需调用**(SKILL.md 指示 → 模型决定) | **cordis 在实例å�¯åŠ¨æ—¶åŠ è½½æ‰§è¡Œ**ï¼ˆæ— äººå†³å®šï¼‰ |
|
|||
|
|
| 是��审批/沙箱 | ✅ 走统一工具管线(approval + 沙箱 + uid/cgroup) | � **��**,它自己就是实例进程的一部分 |
|
|||
|
|
| �与框架生命周期 | �(纯文件) | 是(注册�务/工具/UI,进 `profile.bundles`) |
|
|||
|
|
| 装载时机 | �行时按需�现,**watch �时生效** | **�动时**,改�必须�� |
|
|||
|
|
| 失败�果 | 脚本报错 / agent 表现�佳 | **实例起��(崩溃循环)** |
|
|||
|
|
| �明度 | 命令进会�记录,�追溯 | �默�行 |
|
|||
|
|
|
|||
|
|
**å�¯ç¼–程判æ�®ï¼ˆæˆ‘们代ç �里已在用)**:看包里有没有 **`dsh.bundle`**(`isBundle()` 判 `dsh.bundle.patch`)→ 有=æ�’ä»¶ï¼Œæ— =技能。**æœ‰æ— client é�¢ï¼ˆUI)å�ªæ˜¯æ�’ä»¶çš„å�¯é€‰å±žæ€§**,与"是ä¸�是技能"æ— å…³ã€‚
|
|||
|
|
|
|||
|
|
**风险分层(ç�†ç”±è¦�准)**:技能低风险é� 三æ�¡â€”—**惰性**(ä¸�调用ä¸�执行)+ **å�¯è§�**(命令在 transcript)+ **失败ä¸�致命**ï¼›æ�’ä»¶é«˜é£Žé™©çš„æ ¸å¿ƒç�†ç”±æ˜¯ **开机å�³æ‰§è¡Œã€�æ— äººä»‹å…¥ã€�失败致命**。
|
|||
|
|
> âš ï¸� 注æ„�:档案 33 把默认档改æˆ� `danger-full-access` + `approval: never` å�Žï¼Œ**技能脚本也ä¸�å†�弹确认**(ä»�å�—沙箱/uid/cgroup 约æ�Ÿï¼‰ï¼Œä¸¤æ�¡è·¯çº¿çš„"人工拦截é�¢"å·®è·�å› æ¤ç¼©å°�——分层的主è¦�ä¾�æ�®ä»Ž"审批"转为"是å�¦è¢«æ¡†æž¶ä¸»åŠ¨åŠ è½½ + 失败是å�¦è‡´å‘½"。
|
|||
|
|
|
|||
|
|
## 术è¯ä¸ŽçŽ¯å¢ƒçº¦å®šï¼ˆ2026-09-11 档案 38b)
|
|||
|
|
|
|||
|
|
- **术è¯ï¼šç»Ÿä¸€å�«ã€ŒåŠŸèƒ½æ�’ä»¶ã€�**(2026-09-11 起由「业务æ�’ä»¶ã€�改å��)。**å�ªæ”¹æ˜¾ç¤ºæ–‡æ¡ˆ/注释,代ç �æ ‡è¯†ç¬¦ä¿�æŒ�ä¸�å�˜**:表å�� `business_plugins`ã€�包å�� `@dsh-local/business-plugins`ã€�section id `business-plugins`ã€�API 路径 `/api/plugins/{business,mine}`ã€�文件å�� `business-plugins.ts` / `ensure-biz-plugins.cjs`ã€�目录 `poc/business-plugins/`。
|
|||
|
|
- **`ensure-biz-plugins.cjs` 已是「版本感知 + 自动å�–最新产物ã€�**:å�‡çº§æµ�程 = 把新包丢进 `/opt/dsh/artifacts/` → 跑一次脚本(è�½å�Žä¼šè‡ªåЍ `pnpm add`)。**å�‡çº§æ—¶ä¸�è¦�å…ˆåˆ æ—§åŒ…**,å�¦åˆ™ä¾�赖表里的旧 `file:` 指å�‘失效文件会让 `pnpm` 整体 ENOENT 失败(æ£ç¡®é¡ºåº�:先放新包 + 改ä¾�赖指å�‘ → `pnpm add` → å†�åˆ æ—§åŒ…ï¼‰ã€‚
|
|||
|
|
- **`.gitignore` 已忽略 `*.bak-*`**:`git add <dir>` 会把å�Œç›®å½•的未跟踪备份一并暂å˜ï¼ˆå·²è¸©ä¸€æ¬¡ï¼‰ã€‚æ��交å‰�用 `git status --short` æ ¸å¯¹ï¼Œæˆ–å®šå�‘ `git add <具体文件>`。
|
|||
|
|
- **æœ�务器 Python 是 3.6.8,ä¸�è¦�动系统 `python3`**:`/usr/libexec/platform-python` 是 `dnf`/`yum` çš„ shebang,替æ�¢/å�‡çº§ä¼šç›´æŽ¥æ�žå��包管ç�†å™¨ã€‚**è¿�维脚本一律用 Node**(平å�°æŠ€æœ¯æ ˆå°±æ˜¯ Node);必须用 Python 时写 3.6 兼容代ç �ï¼ˆæ— `subprocess.run(capture_output=)`ã€�æ— f-string `=` è°ƒè¯•ç‰ 3.7+ 特性)。确需现代 Python å°±**并行装 `python3.11`**(仓库有),别切 alternatives。
|
|||
|
|
|
|||
|
|
## 功能�件�用:探活 · 快照回滚 · ��件隔离(档案 34/35,2026-09-11)
|
|||
|
|
|
|||
|
|
**原缺陷**:`/api/plugins/mine/apply` 在 `pnpm add → reconcile → restartMain` 之å�Ž**ç›´æŽ¥æ ‡ successï¼Œå…¨ç¨‹æ— æŽ¢æ´»** → æ�’ä»¶æ�žå´©å®žä¾‹ï¼ˆå´©æºƒå¾ªçŽ¯ï¼‰æ—¶ä»»åŠ¡ä»�报「已应用ã€�。
|
|||
|
|
|
|||
|
|
**æ£ç¡®æµ�程**:快照 → ç¦�用项直接 remove(ä¸�引入新代ç �,永远安全)→ å�¯ç”¨é¡¹**先整批试一次** → 探活 → 失败æ‰�回滚 + **é€�æ�’件隔离**(能起æ�¥çš„ä¿�ç•™ã€�èµ·ä¸�æ�¥çš„å�•独摘掉并记 `plugin_incident` 审计)。
|
|||
|
|
|
|||
|
|
**探活的两个必踩å�‘(都é� 实测å�‘现)**:
|
|||
|
|
1. **ä¸�能å�ªåˆ¤ `restartMain` 的返回值** —— 编排器里没有该用户实例时(portal é‡�å�¯æ¸…äº†å†…å˜æ€�ã€�或实例被空闲回收)它返回 `undefined`,会把**æ— è¾œæ�’ä»¶**误判为「ä¸�兼容ã€�。→ æ— å®žä¾‹æ—¶å…ˆæŒ‰ `/api/dsh/enter` å�Œè·¯å¾„ `launch` 一个å†�探。
|
|||
|
|
2. **固定çŸç‰å¾…会把好æ�’件判æ»** —— åˆ�版「固定 2.5s 稳定窗å�£ã€�实测好æ�’件也报「未产出 launch tokenã€�。→ 改为**轮询**(500ms é—´éš” / 总预算 60s):拿到 `launchToken` 且 `status==='running'` å�Žï¼Œå†�过 2.5s 确认没闪崩æ‰�算通过。
|
|||
|
|
3. 探活失败è¦�**å›žä¼ dsh 的真实错误**(如 `duplicate loader entry id` / `ERR_MODULE_NOT_FOUND`),ä¸�è¦�泛化 —— 这是 admin 判æ–"这个æ�’件为什么å��"的唯一线索。
|
|||
|
|
|
|||
|
|
**å�‘:平å�° bundle 包ä¸�能被 ws 清ç�†åˆ 掉**(档案 35 P0)。`ws-cleanup` çš„ T1 ä¼šæ— æ�¡ä»¶åˆ ws 顶层 `.tgz`,而三个平å�° bundle 是以 `file:<ws>/*.tgz` 安装的 → åˆ æŽ‰å�Ž profile çš„ `dependencies` 指å�‘ä¸�å˜åœ¨æ–‡ä»¶ → **任何 pnpm æ“�作 ENOENT 失败**(å�¯ç”¨åŠŸèƒ½æ�’件必然失败)。**且æž�éš�蔽:`node_modules` 已装好,实例照常è¿�行,å�ªåœ¨ä¸‹æ¬¡ pnpm æ“�作时爆å�‘。** 修法:清ç�†å‰�读æ¯�个用户**所有 profile çš„ `file:` ä¾�èµ–**,被引用的文件一律è±�å…�(别硬编ç �文件å��)。平å�° bundle çš„å�¯é‡�建æº�ç �在 `/opt/dsh/docs/04-调整方案/poc/`ï¼Œäº§ç‰©å˜ `/opt/dsh/artifacts/`。
|
|||
|
|
|
|||
|
|
**é€ æµ‹è¯• fixture çš„å�‘**:bundle çš„ `package.json.name` **必须与 `cordis.patch.yml` 里 `insert` çš„ `name:` 一致**,å�¦åˆ™ `ERR_MODULE_NOT_FOUND`;且打包è¦�用 `package/` å‰�缀的 npm-pack 布局(`tar czf x.tgz package`),平铺布局 pnpm å�¯èƒ½ä¸�接å�—。
|
|||
|
|
|
|||
|
|
**å¹³å�° bundle 的铺装(档案 36)**:`scripts/ensure-biz-plugins.cjs` —— 幂ç‰ï¼Œåˆ¤å®š**看 `dsh.profile.bundles` ä¸�看 `dependencies`**(包内 `cordis.patch.yml` å�ªå¯¹ bundles æˆ�员生效;有 dep 没 bundles = ç‰äºŽæ²¡è£…)。æµ�程:å¤�制产物 tgz 到用户 ws(chown)→ `pnpm add file:` → 对é½� dsh plugin add çš„ reconcile。**新用户自动铺 = 审批时 detached `spawn`(fire-and-forget)+ cron 巡检兜底。**
|
|||
|
|
|
|||
|
|
**å�‘:`pnpm-workspace.yaml` 会让 pnpm æ‹’ç»� `add`**。dsh 会给æŸ�些 profile 生æˆ� `pnpm-workspace.yaml`(`packages: [.]`)→ pnpm 视为 workspace root → `ERR_PNPM_ADDING_TO_ROOT`。→ **所有 `pnpm add` / `pnpm remove` 都è¦�带 `--ignore-workspace-root-check`**ï¼ˆæœ‰æ— è¯¥æ–‡ä»¶éƒ½å·¥ä½œï¼‰ã€‚
|
|||
|
|
|
|||
|
|
**å�‘:判æ–「装没装ã€�**永远看 `dsh.profile.bundles`,ä¸�è¦�看 `dependencies` —— 两者å�¯ä»¥ä¸�一致,而ä¸�一致时组件是**没生效**的。
|
|||
|
|
|
|||
|
|
**�:Node 22 的 `execFile` 类型**:`execFile(file, args, { stdio:'ignore' }, cb)` 在本项目 TS 版本报「'stdio' does not exist in type ExecFileOptions�。�� fire-and-forget 用 `spawn(..., { stdio:'ignore', detached:true }).unref()`。
|
|||
|
|
|
|||
|
|
## 会�记录��(分��工作区/用户的真实对�,2026-09-11 定型)
|
|||
|
|
|
|||
|
|
用户常æ��「看看 XX 工作空间下的对è¯�记录,能å�‘现哪些问题ã€�。**这是å�ªè¯»ä»»åŠ¡ï¼Œå�¯ä¸Žå…¶ä»–å�ªè¯»ä»»åŠ¡å¹¶è¡Œ**,且**适å�ˆä¸¢ç»™å�代ç�†**(大文件长分æž�,别å�ƒä¸»ä¸Šä¸‹æ–‡ï¼‰ã€‚
|
|||
|
|
|
|||
|
|
**路径规律**(`<UID>` = 用户 id,admin = `cce6d1cd-b376-4304-80f0-0e1c58c9ffde`):
|
|||
|
|
```
|
|||
|
|
U=/var/lib/dshs/users/<UID>
|
|||
|
|
$U/home/sessions/--<cwd 路径把 / �� ->--/session-<uuid>/session.jsonl.zstd
|
|||
|
|
$U/home/storages/workspace.json # 工作区注册表:id → { path, title, sessionIds }
|
|||
|
|
```
|
|||
|
|
例:`--var-lib-dshs-users-<UID>-ws-mcntimo--` 对应工作区 `$U/ws/mcntimo`。
|
|||
|
|
|
|||
|
|
**å�‘ï¼šæ–‡ä»¶æ˜¯ã€Œè¿½åŠ å¼�多帧 zstdã€�,ä¸�是普通 zstd。**
|
|||
|
|
- `zlib.zstdDecompressSync` / `createZstdDecompress` å�ªè§£ç¬¬ä¸€å¸§ → å�ªå¾— 240 å—节的 session 头,然å�޿Х `Unknown frame descriptor`。
|
|||
|
|
- **必须用 zstd CLI**(æœ�务器默认没装):`yum install -y zstd`(al8 官方æº� `zstd-1.5.1-2.0.2.al8`,安全,已获用户授æ�ƒï¼‰ã€‚ç„¶å�Ž `zstd -dc <src> > /tmp/s.jsonl` —— 1.6 MB 明文æ£å¸¸è§£å‡ºã€‚
|
|||
|
|
- 用户点过 `/export`(`command/done` → `Session log download requested.`)拿到的是**è¿™ç§�多帧 zstd**,**ç”¨æˆ·æ ¹æœ¬æ‰“ä¸�å¼€** —— 属平å�°ç¼ºé™·ï¼ˆæ¡£æ¡ˆ 36 P1-6)。
|
|||
|
|
|
|||
|
|
**分�脚本写法(2026-09-11 踩��定型)**
|
|||
|
|
```bash
|
|||
|
|
# � 别用:ssh 'node -e "..."' —— 内层引�转义在�引� ssh 命令里必�
|
|||
|
|
# ✅ 一律:本地写 → scp → 远端执行
|
|||
|
|
P=/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0
|
|||
|
|
export PATH="$P/usr/bin:$P/mingw64/bin:/c/Windows/System32:/c/Windows:$PATH"
|
|||
|
|
S="C:/Users/Administrator/AppData/Local/Temp/wb-scratch"
|
|||
|
|
scp -i ~/.ssh/id_ed25519_dsh -P 22 "$S/an.cjs" [email protected]:/tmp/an.cjs
|
|||
|
|
ssh -i ~/.ssh/id_ed25519_dsh -p 22 [email protected] 'node /tmp/an.cjs /tmp/g.jsonl users'
|
|||
|
|
```
|
|||
|
|
- ⚠� **`.cjs` �缀是必须的**:�务器 `/tmp/package.json` � `"type":"module"` → `/tmp/*.js` 被当 ESM,`require is not defined in ES module scope`(本次踩�)。
|
|||
|
|
- 脚本按 `MODE` 分支输出(`users` / `errors` / `tools` / `assistant` / `seq`),**一次写好�用**,���� scp。
|
|||
|
|
- `type` 分布里 `request/header` æ¯�æ�¡å�«å®Œæ•´ system prompt + 工具表(å� 明文相当大比例)→ **务必按 `type` 过滤**,别全é‡� dump。
|
|||
|
|
|
|||
|
|
**JSONL 结构**:æ¯�行一æ�¡ JSON。第 1 行 `{"type":"session", cwd, agentPreset, ...}`;其余 `type` ∈ `request/header`(å�«å®Œæ•´ system prompt + 工具表)ã€�`assistant/chunk`ã€�`assistant/message`(å�« `reasoning` + `tool-call`)ã€�`tool/result`ã€�`turn/end` ç‰ã€‚工具结果里能直接看到真报错原文。
|
|||
|
|
|
|||
|
|
**分æž�è¦�点**(产出「问题清å�•ã€�,P0/P1/P2 + 行å�· + ≤60 å—原文片段):
|
|||
|
|
①工具报错/é‡�试/è¶…æ—¶/æ�ƒé™�被拒(**沙箱é™�åˆ¶æ˜¯é«˜é¢‘æ ¹å› **)② 用户挫败信å�·ï¼ˆå��å¤�çº æ£ã€�é‡�å¤�è¦�求ã€�放弃)③ **å¹³å�°ä¾§ç¼ºé™·**(è�œå�•æ— å��应ã€�报错ä¸�å�¯è¯»ã€�路径写æ»ã€�工作区为空ã€�会è¯�䏿–)④ å�¡ç‚¹ä¸Žæ— 谓的工具调用 ⑤ **安全**ï¼šå‡æ�®æ˜Žæ–‡ï¼ˆå�ªæŠ¥ã€Œç¬¬ N è¡Œç–‘ä¼¼å‡æ�®ï¼Œç±»åž‹ Xã€�,**ç»�ä¸�抄值**)ã€�è¶Šæ�ƒå°�试。
|
|||
|
|
**é¢�外å�¯ç”¨ä¿¡å�·**:报错在会è¯�ä¸çš„**ä½�置分布**(`grep -n`)能区分「已修ã€�还是「一直在å�‘生ã€�。
|
|||
|
|
|
|||
|
|
**必须先查「是ä¸�是已知问题ã€�å†�报(2026-09-11 定型)**:动手å‰�å…ˆ `ls /opt/dsh/docs/04-调整方案/` + 读å�Œç±»æ—§æ¡£æ¡ˆï¼ˆå¦‚ 21 å�¡é¡¿ / 23 环境é™�制 / 32 上次å�–è¯�),**æ–°å�‘现è¦�æ˜Žç¡®æ ‡æ³¨ã€Œæ–°å¢žã€�还是「修æ£/补充旧档案ã€�**,å�¦åˆ™ä¼šæŠŠå·²å½’档的结论当新问题é‡�å¤�报。
|
|||
|
|
|
|||
|
|
**产出å�Žçš„é—环(别å�ªè¾“出清å�•)**:①è�½ `04-调整方案/<NN>-<主题>.md`(å�«ã€Œä¿®æ£æ—§æ¡£æ¡ˆçš„哪æ�¡ç»“论ã€�å°�节);② `INDEX.md` §二 è¿½åŠ `04-<NN>` 行并更新状æ€�摘è¦�(**README 的档案清å�•å·²å®šæ ¼ä¸ºåŽ†å�²å¯¹ç…§ï¼Œåˆ«å�ªæ”¹ README**);③ `INDEX.md §å….1` 的「下一å�·ã€�+1ã€�`03-路线图与待办.md` 补登记;④ 收尾四件套:`python scripts/docs-audit.py`(**退出ç � 0**)→ `python scripts/docs-manifest.py`(刷新机读清å�•)→ `bash scripts/docs-sync-check.sh`(全绿)→ `MINE="<我改的文件>" PUSH=1 bash scripts/handoff-guard.sh`(幽ç�µæ–‡ä»¶ç¡¬åˆ¤å®šï¼‰ã€‚
|
|||
|
|
|
|||
|
|
## 多任务并行调度�议(2026-09-11 用户�出,长期沿用)
|
|||
|
|
|
|||
|
|
**æ ¸å¿ƒç»“è®º**:**å�¯ä»¥å¹¶è¡Œï¼Œä½†å¹¶è¡Œåº¦ç”±ã€Œå†²çª�域ã€�决定,ä¸�由任务数é‡�决定。** å�ªè¯»/è°ƒç ”ç±»å�¯è‡ªç”±å¹¶è¡Œï¼›æ”¹ç � → 构建 → é‡�å�¯ → æ��交 → 写文档是**å�•通é�“**,必须串行。
|
|||
|
|
|
|||
|
|
### 冲�域清�(�域必串行,跨域��并行)
|
|||
|
|
|
|||
|
|
| 冲�域 | 涉�资� | 并行性 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 门户å�Žç«¯ä»£ç � | `/opt/dshs/src/**` → `npm run build` å…¨é‡�é‡�编译 `lib/` | â�Œ 独å� :一批å�ªå…�许 1 个改ç �任务 |
|
|||
|
|
| æœ�务é‡�å�¯ | `systemctl restart dshs` | â�Œ 全局;é‡�å�¯ä¼šæ‰“æ–æ‰€æœ‰ç”¨æˆ·å®žä¾‹ |
|
|||
|
|
| �端页� | `web/*.html`(�文件�写覆盖�写;marker-splice 会互相清�) | � �文件串行 |
|
|||
|
|
| 代ç �仓库 | `git add` / `git commit`(index.lock) | â�Œ 串行 |
|
|||
|
|
| DB schema | `dshs.db` 建表/è¿�ç§» | â�Œ 串行(普通读写事务çŸï¼Œå�¯å¹¶è¡Œï¼‰ |
|
|||
|
|
| 文档 | `/opt/dsh/docs/**`�`docs-status/**`(�务器唯一�,�点文件) | � 串行(�写覆盖) |
|
|||
|
|
| 本地记忆 | `.workbuddy/memory/*.md`�`MEMORY.md` | � 串行(并� append 会丢更新) |
|
|||
|
|
| 实例�行� | �用户 profile / 实例进程 | ⚠� **跨用户�并行**;�用户串行 |
|
|||
|
|
| 临时测试会è¯� | `sessions` 表 | ✅ å�¯å¹¶è¡Œï¼Œä½† UA 必须带 worker æ ‡è¯†ï¼ˆ`poc-<worker>`),清ç�†å�ªåˆ 自己的å‰�ç¼€ |
|
|||
|
|
| å�ªè¯»è°ƒç ” | 读æº�ç �ã€�看日志ã€�`--dump-config`ã€�外网抓å�–ã€�兼容性测试 | ✅ 完全å�¯å¹¶è¡Œ |
|
|||
|
|
|
|||
|
|
### 批次模型与�赖处�
|
|||
|
|
|
|||
|
|
1. **一批 = 1 个「改ç �通é�“ã€�+ N 个「å�ªè¯»é€šé�“ã€�**;改ç �任务之间永远排队串行。
|
|||
|
|
2. 待办先建æˆ� **DAG**,é€�æ�¡æ ‡æ³¨ã€Œä¾�èµ–ã€�+「冲çª�域ã€�ï¼›**æ— ä¾�赖且冲çª�域ä¸�å�Œ**æ‰�å…�许å�Œæ‰¹ã€‚
|
|||
|
|
3. **æ”¶å�£åœ¨ä¸»ä¼šè¯�**:并行通é�“产出汇总回主会è¯�,统一写档案 → commit → `docs-status-sync.sh --pull`ï¼ˆè¿™ä¸‰æ¥æœ¬èº«ä¹Ÿæ˜¯ä¸²è¡Œå�•通é�“)。
|
|||
|
|
4. **用户侧多会�按「领域�切分**(A=门户�端 / B=文档梳� / C=兼容性测试),**��按「�一领域的��切片�切** —— �者必然�域冲�。
|
|||
|
|
5. **开跑�先出「并行批次表�**(任务 / 冲�域 / �赖 / ��并行)给用户确认。
|
|||
|
|
6. 我侧å�¯ç”¨å¹¶è¡Œå�代ç�†ï¼ˆ`Agent` + `run_in_background`)跑**å�ªè¯»é€šé�“**ï¼›**写æ“�作必须回到主会è¯�串行执行**。
|
|||
|
|
|
|||
|
|
### å��例(å�ŒåŸŸå¹¶å�‘çš„å…·ä½“ç ´å��å½¢æ€�)
|
|||
|
|
|
|||
|
|
- 两会è¯�å�Œæ—¶ `npm run build` → `lib/` 产物交å�‰æ±¡æŸ“,`systemctl restart` 互相打æ–实例
|
|||
|
|
- 两会��时改 `portal.html` → �写覆盖�写(marker 区�被对方整段替�)
|
|||
|
|
- 并� `git add/commit` → `index.lock` 冲�
|
|||
|
|
- �时写 `/opt/dsh/docs/**` 或 `MEMORY.md` → �写覆盖 / 丢更新
|
|||
|
|
- 用å�Œä¸€ UA å‰�缀批é‡�åˆ æµ‹è¯•ä¼šè¯� → è¯¯åˆ å�¦ä¸€ä¸ª worker 的会è¯�
|
|||
|
|
|
|||
|
|
### è�½åœ°æœºåˆ¶ï¼ˆ2026-09-12 建立):三把é”� —— 别é� 记忆,é� 判æ�®
|
|||
|
|
|
|||
|
|
冲çª�åŸŸåˆ¤æ–æ˜¯ã€Œè®¾è®¡ã€�,还需è¦�「执行时的强制判æ�®ã€�。当日 3 次实è¯�事故å�Žè¡¥ä¸Šä¸‰æŠŠé”�:
|
|||
|
|
|
|||
|
|
| � | �置 | 管什么 | 拿法 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| **全局执行é”�**(粗) | `交接å�•/.exec-lock` | å�Œä¸€æ—¶åˆ»å�ªå…�许**一个执行会è¯�**动「文档 / 代ç � / æœ�务器ã€� | `bash scripts/handoff-guard.sh --claim-exec "<会è¯�å��>"` |
|
|||
|
|
| **å�•级å� 用é”�**(细) | `交接å�•/.doing-<å�•å�·>` | 这个å�•å½’è°�å�šï¼ˆä¾›å�°è´¦ä¸ŽæŽ¥ç®¡ï¼‰ | `bash scripts/handoff-guard.sh --claim <å�•å�·> "<会è¯�å��>"` |
|
|||
|
|
| **æœ�务器侧æ“�作é”�** | `/opt/dsh/state/.op-lock/<æ“�作å��>`(跨机å�¯è§�) | è°�æ£åœ¨åЍ**生产**:é‡�å�¯ / drain / 改实例 env·quota / 批é‡�铺æ�’ä»¶ / 改 nginx·nft·è¯�书 | `bash scripts/op-lock.sh claim <æ“�作å��> "<å½±å“�é�¢ï¼šè°�会被æ–ã€�æ–多久>"` |
|
|||
|
|
|
|||
|
|
- **顺åº�**:开工 **先抢全局é”� → å†�å� æœ�务器é”�**;完工 **å��åº�**释放(先放æœ�务器é”�,最å�Žæ”¾å…¨å±€é”�)。
|
|||
|
|
- 🔒 **é”�的生命周期 = 任务的生命周期**(2026-09-14 用户明令):**抢到é”�的任务,å�ªæœ‰"执行完æˆ� → å��åº�释放"æ‰�算完æˆ�**;⛔ **ç¦�æ¢"抢é”�å�šä¸€å�Šã€�ä¸�è§£é”�就结æ�Ÿå›žå�ˆ/会è¯�"**(é”�是独å� 资æº� + æœ¬åº“æ— å¿ƒè·³æœºåˆ¶ ⇒ 别人既ç‰ä¸�到也判ä¸�å‡ºä½ æ»æ²¡æ»ï¼‰ã€‚é…�套:①抢é”�**å‰�**先列出**æ”¶å�£æ¥éª¤**(è�½åœ° → æ ¡éªŒ → 推é€�/对账 → 收尾);② ä¸é€”è¦�å�œï¼ˆç‰ç”¨æˆ·æ‹�æ�¿ / ç‰çª—å�£ï¼‰â‡’ **å…ˆ `--release-exec` å†�å�œ**;③ **结æ�Ÿè¯å¿…须对é”�状æ€�è´Ÿè´£** —— 写明"已释放",或**显å¼�点å��**"é”�ä»�在 `<OWNER>` + åŽŸå› + 下一æ¥"(仅é™�释放通é�“ä¸�å�¯ç”¨ï¼‰ï¼›â›”"忘了 / å�šä¸�完就走"ä¸�å…�许。
|
|||
|
|
- **一æ�¡å‘½ä»¤é¢„检**:`ME="<会è¯�å��>" MINE="<我è¦�改的文件>" bash scripts/handoff-guard.sh [å�•å�·]`;推é€�å‰�åŠ `PUSH=1`(å�¯ç”¨ã€Œå¹½ç�µæ–‡ä»¶ã€�硬判定:对账结果里出现未声明的「仅本地ã€�文件 → 直接失败)。
|
|||
|
|
- **`mkdir` å�³åŽŸå�**:å� ä½�失败 = 有会è¯�æ£åœ¨åЍ → **å�œæ‰‹**,别é‡�试ã€�别"抢一下看看"。
|
|||
|
|
- **æœ�务器侧é”�è¦�带身份**:`ME="<会è¯�å��>" bash scripts/op-lock.sh claim <æ“�作å��> "<å½±å“�é�¢>"` —— ä¸�ä¼ `ME` 会记æˆ� `unknown-session`(2026-09-12 实测踩到两个å�Žæžœï¼šhandoff-guard çš„ã€�1d】把**ä½ è‡ªå·±çš„é”�**当æˆ�别人的;`release` çš„å½’å±žæ ¡éªŒä¹Ÿä¼šæ‹’ä½ ï¼‰ã€‚`release` **å�ªèƒ½ç”±å� 用者本人**执行,冒充别人 → æ‹’ç»�å¹¶æ��示 R9。
|
|||
|
|
- â›” **ä¸�å¾—äººå·¥åˆ é”�ã€�ä¸�得接管**(**R9**,2026-09-12 ç”¨æˆ·æ˜Žä»¤ã€Œä¸¥æ ¼ç¦�æ¢è¿™ç±»æ“�作ã€�):**AI 一律ä¸�å¾—** `rm -rf 交接å�•/.exec-lock`ã€�ä¸�å¾—åˆ `交接å�•/.doing-*`,也ä¸�得以「æŒ�æœ‰è€…ç–‘ä¼¼å·²æ» / å�¡ä½� / 太久没动 / å�ªåœ¨å�ªè¯»åˆ†æž�没产出ã€�为由**å�•æ–¹é�¢æŽ¥ç®¡**。é”�**å�ªèƒ½ç”±æŒ�有者自己释放**(`--release-exec` / `--release`);**抢ä¸�到é”� ⇒ å�œæ‰‹ + 报告用户**,**é”�的处置æ�ƒå�ªå±žäºŽç”¨æˆ·æœ¬äºº**(è¦�撤也å�ªèƒ½ç”¨æˆ·è‡ªå·±åŠ¨æ‰‹ï¼‰ã€‚`handoff-guard.sh` è¾“å‡ºé‡Œçš„ã€Œäººå·¥åˆ é”� / 接管ã€�å—æ ·**å�‡ä¸�æž„æˆ�授æ�ƒ**(该文案已å�Œæ¥ä½œåºŸï¼‰ã€‚ç�†ç”±ï¼šå¹³å�°**æ— å¿ƒè·³æœºåˆ¶**,AI **没有任何判æ�®**èƒ½ç¡®è®¤å¯¹æ–¹å·²æ» â€”â€” åˆ é”� = åœ¨æ— æ³•éªŒè¯�çš„å‰�æ��下å�•æ–¹é�¢æ’¤é”€äº’斥,一旦对方ä»�在跑,就退回「两个会è¯�å�Œæ—¶æ”¹å�Œä¸€æ‰¹æ–‡ä»¶ã€�。
|
|||
|
|
- **为什么æœ�务器æ€�å¿…é¡»å�•ç‹¬åŠ é”�**:文档冲çª�é� `git status` / mtime 还能看出æ�¥ï¼Œ**æœ�务器æ€�å�˜æ›´çœ‹ä¸�出æ�¥**(`systemctl` ä¸�ä¼šå‘Šè¯‰ä½ 10 分钟å‰�è°�é‡�å�¯è¿‡ï¼‰ã€‚
|
|||
|
|
- 判定与工具细节è§� `交接å�•/README.md §一 §三 §å…`;机制记录è§� `04-调整方案/69-å¹¶å�‘æ²»ç�†è�½åœ°-commit常æ€�化与æœ�务器侧é”�.md`。
|