diff --git a/04_test/probe-screenshots.cjs b/04_test/probe-screenshots.cjs new file mode 100644 index 0000000..c468d54 --- /dev/null +++ b/04_test/probe-screenshots.cjs @@ -0,0 +1,63 @@ +// probe-screenshots.cjs — 真实界面截图:侧栏工作台 / 控制室 / 分栏工作区 → docs/assets/*.png +const http = require('http'); +const { spawn } = require('child_process'); +const WebSocket = require('ws'); +const fs = require('fs'); +const path = require('path'); +const PORT = 9382; +const OUT_DIR = path.join(__dirname, '..', 'docs', 'assets'); +fs.mkdirSync(OUT_DIR, { recursive: true }); +const proc = spawn('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', [ + '--headless=new', '--disable-gpu', '--no-first-run', '--no-default-browser-check', + '--window-size=1600,1000', '--force-device-scale-factor=1', + '--remote-debugging-port=' + PORT, + '--user-data-dir=C:\\Users\\SJL\\AppData\\Local\\Temp\\wt-chrome-shots', + 'about:blank', +], { stdio: 'ignore' }); +const getJSON = (p) => new Promise((res, rej) => { + http.get({ host: '127.0.0.1', port: PORT, path: p }, (r) => { let d=''; r.on('data',c=>d+=c); r.on('end',()=>{try{res(JSON.parse(d))}catch(e){rej(e)}}) }).on('error', rej); +}); +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +(async () => { + let list = null; + for (let i = 0; i < 40; i++) { try { list = await getJSON('/json/list'); if (list && list.length) break; } catch {} await sleep(500); } + const target = list.find((t) => t.type === 'page') || list[0]; + const ws = new WebSocket(target.webSocketDebuggerUrl); + let id = 0; const pending = new Map(); + const send = (method, params) => new Promise((res) => { const mid = ++id; pending.set(mid, res); ws.send(JSON.stringify({ id: mid, method, params })); }); + ws.on('message', (raw) => { const m = JSON.parse(String(raw)); if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id); } }); + await new Promise((r) => ws.on('open', r)); + await send('Runtime.enable'); + await send('Page.enable'); + await send('Page.addScriptToEvaluateOnNewDocument', { + source: "try{ if(location.origin==='http://127.0.0.1:3080'){ localStorage.setItem('dsh.worktable.view.v1', JSON.stringify({query:'',searchOpen:false,orderBy:'manual',dock:'footer',floatTop:null,sortMigratedV2:true})); localStorage.setItem('dsh.worktable.projects.v1', JSON.stringify({order:['t-shot'],lastUsed:{},hidden:[],nameOverrides:{},iconOverrides:{},shortcuts:[],layouts:[{id:'t-shot',title:'Demo Workspace',icon:'🧱',top:null,left:null,main:[{id:'p1',title:'Pane 1',min:200,content:null,tabs:[],active:0}],leftWidth:{default:260,min:160,max:480},chatWidth:{default:360,min:240,max:600},topHeight:{default:200,min:120,max:480},chatSide:'right',chatFullHeight:false}],bindings:{},folders:{},views:{},removed:[]})); } }catch(e){}", + }); + await send('Page.navigate', { url: 'http://127.0.0.1:3080/' }); + const evaluate = async (expr) => { + const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); + if (r.result && r.result.exceptionDetails) return { __error: (r.result.exceptionDetails.exception || {}).description || r.result.exceptionDetails.text }; + return r.result && r.result.result ? r.result.result.value : undefined; + }; + let ready = null; + for (let i = 0; i < 30; i++) { await sleep(1000); ready = await evaluate("!!window.__dshWorktable"); if (ready === true) break; } + await sleep(1800); + const shot = async (file) => { + const r = await send('Page.captureScreenshot', { format: 'png' }); + fs.writeFileSync(path.join(OUT_DIR, file), Buffer.from(r.result.data, 'base64')); + console.log('SHOT', file); + }; + // 1) 展开侧栏 → 工作台区块 + await evaluate("(function(){ var b=document.querySelector('button[title=\"Open sidebar\"],button[aria-label=\"Open sidebar\"]'); if(b){b.click(); return true} return false })()"); + await sleep(1600); + await shot('shot-1-sidebar.png'); + // 2) 控制室(未绑定 → 绑定第一个会话 → 打开面板) + const r2 = await evaluate("(async function(){ var sleep=function(ms){return new Promise(function(r){setTimeout(r,ms)})}; var vis=function(sel){return [].slice.call(document.querySelectorAll(sel)).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)})}; var card=vis('.dsh-wt_projects .dsh-wt_consoleEntry'); if(!card) return 'no-card'; card.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true})); await sleep(600); var bpop=vis('.dsh-wt_consoleBindPop'); var item=bpop&&bpop.querySelector('.dsh-wt_consoleBindList .dsh-wt_selectItem'); if(item){ item.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true})); } await sleep(3000); return 'ok'; })()"); + console.log('CONSOLE:', JSON.stringify(r2)); + await shot('shot-2-console.png'); + // 3) 分栏工作区(浏览器 + 终端) + await evaluate("(function(){ var st=window.__dshWorktable.splitStore; st.open({id:'shot-demo',title:'Worktable',top:[{id:'tp',title:'Browser',min:120,content:{kind:'builtin',type:'browser'}}],main:[{id:'mp',title:'Terminal',min:120,content:{kind:'builtin',type:'terminal'}}],chatWidth:{default:360,min:240,max:600},topHeight:{default:220,min:120,max:480},chatSide:'right'}); return 'ok'; })()"); + await sleep(2500); + await shot('shot-3-workspace.png'); + try { ws.close(); } catch {} + proc.kill(); +})().catch((e) => { console.error('PROBE FAIL', e); try { proc.kill(); } catch {} }); diff --git a/README.md b/README.md index 8c6a8a0..d5893a8 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,107 @@ -# dsh-worktable · 工作台 +# dsh-worktable 🖥️ -> DeepSeek Harness 侧边栏「工作台」容器插件:在「工作区」下方开辟一个收纳 agent 级项目(如 TravelAtlas 旅行图鉴)的应用抽屉。 -> A DeepSeek Harness sidebar "worktable" container: a project drawer below the official workspace section, hosting agent-level projects (e.g. TravelAtlas). +

English · 简体中文

-## 目录结构(仿 dsh-reminder) +**An agent-project workbench for DeepSeek Harness** — a sidebar app drawer that turns every project into dockable windows, plus a built-in control room that watches them all in real time. -``` -dsh-worktable/ -├── 00_index/ # 项目导航 -├── 01_content/ # 插件包(dsh bundle:cordis 服务端 + 客户端 CJS) -├── 02_process/ # PRD.md / drafts / research / worklogs -├── 03_local/ # 本机脚本与日志(不入库) -├── 04_test/ # 测试与验证产物 -├── README.md # 本文件 -└── AGENTS.md # 项目规则 -``` +Sidebar app drawer Control room -## 当前状态 +Split workspace -- v2 稳定版:侧边栏区块(拖动/停靠/折叠态项目图标框)+ 排序(手动/最近)+ 管理项目(改名/隐藏/拖拽排序)+ 添加面板(接入指引 + 本地快捷方式)+ zh/en 词典 + 使用埋点 ✅ -- 项目协议 v2:`sidebar.worktable.project` 子座位 + 渐进上报协议(reportMeta/reportUsed/order/hidden/nameOverrides,全部可选,v1 卡片兼容)✅ -- 悬浮窗几何与 sidebar 联动(ResizeObserver)、底部悬浮面板避让 ✅ -- 首个入驻项目:dsh-travelatlas(图标 🌏,协议 v2 参考实现)✅ +--- -## 构建与安装 +## ✨ Feature tour -```powershell +### 🧩 Sidebar app drawer + +- Collects your self-hosted projects (and resident plugins like dsh-travelatlas) in one place +- Rename / icon / reorder / hide each project; per-project folder; **project ↔ conversation binding** — opening a project switches the chat pane to its bound conversation +- Collapse the sidebar and every project becomes a tappable square tile (icon only) + +### 🪟 Dockable split workspace + +- Declarative layout presets (left column / top row / main grid + right chat pane) +- Draggable dividers, per-pane tabs, per-layout width persistence +- Built-in panes: **file explorer, terminal, browser, animation site, custom window** +- Custom window: send a requirement to a new or existing conversation; the agent builds it and the result auto-mounts into the window (locked) + +### 🖥️ Control room (built-in default project) + +- A pinned, undeletable first project — bind one management conversation on first open +- 3-column card grid mirrors **every** project: working / needs you / done with live runtime, subagent counts and a cleaned message preview +- Event-driven host snapshot mirroring — **zero polling, zero tokens** +- Glassmorphism cards, dark / light / system theme, neon status glows and a rotating comet on busy cards + +--- + +## At a glance + +| | | +|---|------| +| 🧩 Plugin type | Cordis plugin — host routes + web client, pure additive (no official plugin replaced) | +| 🪟 Workspace engine | Self-built split engine rendered into the host shell overlay seat | +| 💬 Chat pane | Reuses the host conversation — the plugin only selects sessions (`sessions.open`) | +| 📡 Status data | Mirror of the host session runtime snapshots (subscription-driven) | +| 💾 State | localStorage only (`dsh.worktable.*`); no workspace files touched | +| 🎨 UI | TypeScript + React (host externals) + vanilla CSS, dark-first with light theme | + +--- + +## Quick start + +1. **Install**: `dsh plugin --profile web add "link:/01_content"` and register `dsh-worktable` in the profile bundle list +2. **Restart** the DSH web process, refresh the GUI +3. **Open the control room**: click the pinned 🖥️ control-room card → bind one conversation (join existing or create new) → you get the live card grid +4. **Create projects**: sidebar + → pick a layout preset, set a project folder + +--- + +## Architecture + +One package ships the **host Cordis plugin** and the **web client**: + +- **host**: `/api/worktable/*` routes — health, file system, git, file read/write, site serving, mkdir, workspaces, native skin template; WebSocket `/api/worktable/term` for the terminal pane (PowerShell on Windows) +- **client**: injected into the sidebar and the shell overlay via the slot protocol; the split engine, tab model, drag/drop and persistence are self-built +- **control room**: reads the host session list snapshot (running / pending / completed, jobs, subagent catalogs) — an event-driven mirror, no model involvement +- **window tasks**: the agent writes `widget-result.json` into the project folder on completion; the client mounts the artifact into the addressed window and locks it + +--- + +## Development & testing + +```bash cd 01_content -npm install # 安装 esbuild 构建工具 -npm run build # esbuild 打包 lib/ -# 注册进本机 web profile(写入 ~/.dsh/profiles/web,需用户授权) -dsh plugin --profile web add "link:E:/AI_Workspace/DeepseekHarness/Projects/dsh-worktable/01_content" -# 重启 dsh web 后刷新 GUI 查看侧边栏底部「工作台」 +npm install +npm run build # lib/index.js + lib/client.js +node --check lib/index.js ``` -## 详见 +- **Build must run inside `01_content`** — building from the repo root writes `lib/` to the wrong place while the host keeps loading the old bundle +- The client bundle keeps the `window.__ModuleLoader__.load` handshake; `react` and `@deepseek-ai/*` stay external +- Regression: `04_test/functional-diag.cjs` (20 steps) plus targeted probes (control room, bind panel, collapsed rail, model inheritance) -- 需求与设计:`02_process/PRD.md` -- 项目规则:`AGENTS.md` -- 导航:`00_index/README.md` +--- +## Known limits + +- State lives in the browser (`localStorage`) — projects, bindings and views do not sync across machines +- The terminal pane is a plain PowerShell host on Windows (no PTY feature parity with the native terminal app) +- Auto-mount requires the agent to actually write `widget-result.json` in the project folder +- The control room monitors projects that are **bound** to a conversation; unbound projects show as idle + +--- + +## Privacy + +No telemetry, no network calls beyond the host APIs and the plugin routes. All user state stays in localStorage. + +--- + +## License + +MIT + +## Related + +- [dsh-reminder](https://github.com/Aisland-SJL/dsh-reminder) — cross-window completion & approval notifications +- [dsh-usage](https://github.com/Aisland-SJL/dsh-usage) — persistent balance/usage dock diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..de99b28 --- /dev/null +++ b/README.zh.md @@ -0,0 +1,107 @@ +# dsh-worktable 🖥️ + +

English · 简体中文

+ +**DeepSeek Harness 的 agent 项目工作台**——侧边栏「应用抽屉」把每个项目变成可停靠的窗口,再加一个实时监控所有项目的内置「控制室」。 + +侧边栏应用抽屉 控制室 + +分栏工作区 + +--- + +## ✨ 功能导览 + +### 🧩 侧边栏应用抽屉 + +- 收纳你的自建项目(以及 dsh-travelatlas 等入驻插件项目) +- 项目支持改名 / 图标 / 排序 / 隐藏;每个项目有专属文件夹;**项目 ↔ 对话绑定**——打开项目时右侧对话窗自动切到其绑定对话 +- 收起侧边栏后,每个项目变成可点击的方形小贴片(只留 emoji) + +### 🪟 可停靠的分栏工作区 + +- 声明式布局预设(左栏 / 顶行 / 主网格 + 右侧对话窗) +- 可拖拽分割线、窗格标签页、按布局持久化的宽度记忆 +- 内置窗格:**文件资源管理器、终端、浏览器、动画站点、自定义窗口** +- 自定义窗口:把需求发给新建或已有对话,agent 完成后产物自动挂载进对应窗口(锁死) + +### 🖥️ 控制室(内置默认项目) + +- 固定在首位的不可删除项目——首次打开绑定一条管理对话即可 +- 三列卡片网格实时镜像**每一个**项目的状态:工作中 / 待你决定 / 已完成,附带运行时长与清洗后的最近消息预览 +- 宿主会话快照的事件订阅镜像——**零轮询、零 Token** +- 玻璃拟态卡片、深色/白色/跟随系统主题、霓虹状态光效与工作中卡片的旋转彗星光点 + +--- + +## 一览 + +| | | +|---|------| +| 🧩 插件类型 | Cordis 插件——服务端路由 + Web 客户端,纯增量(不替换任何官方插件) | +| 🪟 工作区引擎 | 自研分栏引擎,渲染进宿主的 shell overlay 座位 | +| 💬 对话窗 | 复用宿主对话——插件只做会话选择(sessions.open) | +| 📡 状态数据 | 宿主会话运行时快照的镜像(订阅驱动) | +| 💾 状态存储 | 仅 localStorage(dsh.worktable.*),不碰工作区文件 | +| 🎨 界面 | TypeScript + React(宿主 external)+ 原生 CSS,暗色优先 + 浅色主题 | + +--- + +## 快速开始 + +1. **安装**:`dsh plugin --profile web add "link:/01_content"`,并把 `dsh-worktable` 注册进 profile 的 bundle 列表 +2. **重启** DSH web 进程并刷新界面 +3. **打开控制室**:点击固定首位的 🖥️ 控制室卡片 → 绑定一条对话(加入现有或新建)→ 得到实时卡片网格 +4. **创建项目**:侧边栏 + → 选布局预设、填项目文件夹 + +--- + +## 架构 + +一个包同时包含**宿主 Cordis 插件**与 **Web 客户端**: + +- **宿主**:`/api/worktable/*` 路由——健康检查、文件系统、git、文件读写、站点托管、mkdir、工作区、原生皮肤模板;WebSocket `/api/worktable/term` 提供终端窗格(Windows 下为 PowerShell) +- **客户端**:经 slot 协议注入侧边栏与 shell overlay;分栏引擎、标签模型、拖拽与持久化均为自研 +- **控制室**:读取宿主会话列表快照(运行中/待决/已完成、后台任务、子代理目录)——事件驱动镜像,模型不参与 +- **窗口任务**:agent 完成后在项目文件夹写 `widget-result.json`,客户端把产物挂载进指定窗口并锁死 + +--- + +## 开发与测试 + +```bash +cd 01_content +npm install +npm run build # lib/index.js + lib/client.js +node --check lib/index.js +``` + +- **构建必须在 01_content 内执行**——在仓库根构建会把 lib 写到错误位置,宿主仍加载旧 bundle +- 客户端 bundle 保持 `window.__ModuleLoader__.load` 握手;react 与 @deepseek-ai/* 全部 external +- 回归:`04_test/functional-diag.cjs`(20 步)+ 专项探测(控制室、绑定弹窗、收起态贴片、模型继承) + +--- + +## 已知限制 + +- 状态在浏览器本地(localStorage)——项目、绑定与视图不跨设备同步 +- 终端窗格在 Windows 上是朴素的 PowerShell 宿主(与原生终端应用无 PTY 对等) +- 自动挂载要求 agent 确实在项目文件夹写出 `widget-result.json` +- 控制室只监控**已绑定对话**的项目;未绑定的项目显示为空闲 + +--- + +## 隐私 + +无遥测;除宿主 API 与插件自身路由外无任何网络请求。用户状态全部留在 localStorage。 + +--- + +## License + +MIT + +## 相关项目 + +- [dsh-reminder](https://github.com/Aisland-SJL/dsh-reminder) — 跨窗口的任务完成与审批提醒 +- [dsh-usage](https://github.com/Aisland-SJL/dsh-usage) — 常驻的余额/用量面板 diff --git a/docs/assets/shot-1-sidebar.png b/docs/assets/shot-1-sidebar.png new file mode 100644 index 0000000..9410ed7 Binary files /dev/null and b/docs/assets/shot-1-sidebar.png differ diff --git a/docs/assets/shot-2-console.png b/docs/assets/shot-2-console.png new file mode 100644 index 0000000..b06cebd Binary files /dev/null and b/docs/assets/shot-2-console.png differ diff --git a/docs/assets/shot-3-workspace.png b/docs/assets/shot-3-workspace.png new file mode 100644 index 0000000..2f0d513 Binary files /dev/null and b/docs/assets/shot-3-workspace.png differ