From 58baa654417dfad304fcbaba91a824c6376c4b24 Mon Sep 17 00:00:00 2001 From: Aisland Date: Mon, 24 Aug 2026 23:47:26 +0800 Subject: [PATCH] release: prepare v0.2.1 - update checker, mount notification, docs sync --- 01_content/README.md | 21 ++- 01_content/build.mjs | 3 + 01_content/dsh.plugin.json | 2 +- 01_content/lib/client.js | 237 +++++++++++++++++++++++++++++-- 01_content/package.json | 2 +- 01_content/src/client/index.tsx | 148 ++++++++++++++++++- 01_content/src/client/locales.ts | 28 ++++ 01_content/src/client/styles.ts | 26 ++++ 02_process/PRD.md | 3 + AGENTS.md | 20 +++ README.md | 20 ++- README.zh.md | 20 ++- 12 files changed, 508 insertions(+), 22 deletions(-) diff --git a/01_content/README.md b/01_content/README.md index 7eddb6f..bfcc201 100644 --- a/01_content/README.md +++ b/01_content/README.md @@ -18,13 +18,24 @@ ## 安装 +方式 A(推荐,无需 Git)——直接安装 GitHub Release 的安装包: + + dsh plugin --profile web add "https://github.com/Aisland-SJL/dsh-worktable/releases/download/v0.2.0/dsh-worktable-0.2.0.tgz" + +方式 B(想改源码用)——克隆仓库后用本地路径注册(`link:` 只接受本地路径,不要带空格): + + git clone https://github.com/Aisland-SJL/dsh-worktable.git + dsh plugin --profile web add "link:<克隆出来的 dsh-worktable 仓库目录的绝对路径>/01_content" + +两种方式 `add` 都会把 `dsh-worktable` 注册进 profile 的 bundle 列表(写入 `~/.dsh`),装完重启 dsh web、刷新界面生效。 + +## 从源码构建 + cd 01_content npm install npm run build # lib/index.js + lib/client.js node --check lib/index.js -注册到 DSH:`dsh plugin --profile web add "link:/01_content"` - ## 构建注意事项 - **必须在 01_content 目录下构建**:误在仓库根跑会把 lib 写到仓库根,宿主仍加载 01_content/lib 旧 bundle(「改完不生效」假象)。 @@ -32,9 +43,9 @@ ## 相关文档 -- 项目规则:仓库根 AGENTS.md -- 需求与协议:02_process/PRD.md -- 工作日志:02_process/worklogs/ +- 项目规则:https://github.com/Aisland-SJL/dsh-worktable/blob/main/AGENTS.md +- 需求与协议:https://github.com/Aisland-SJL/dsh-worktable/blob/main/02_process/PRD.md +- 工作日志:https://github.com/Aisland-SJL/dsh-worktable/tree/main/02_process/worklogs ## License diff --git a/01_content/build.mjs b/01_content/build.mjs index 5abf129..e789ce4 100644 --- a/01_content/build.mjs +++ b/01_content/build.mjs @@ -13,6 +13,7 @@ import { fileURLToPath } from 'node:url' const here = dirname(fileURLToPath(import.meta.url)) mkdirSync(join(here, 'lib'), { recursive: true }) +const pkg = JSON.parse(readFileSync(join(here, 'package.json'), 'utf8')) const clientBanner = { js: "window.__ModuleLoader__.load({ id: 'dsh-worktable', factory: (require) => { var module = { exports: {} }; var exports = module.exports;", @@ -46,6 +47,8 @@ await build({ loader: { '.css': 'text' }, // xterm.css 以文本内联进样式串(宿主不加载独立 css 文件) banner: clientBanner, footer: clientFooter, + // 客户端自报版本(随 package.json version 走,发版即自动一致) + define: { __WT_VERSION__: JSON.stringify(pkg.version) }, }) console.log('[dsh-worktable build] done: lib/index.js, lib/client.js') diff --git a/01_content/dsh.plugin.json b/01_content/dsh.plugin.json index eb4be8e..ae00f5f 100644 --- a/01_content/dsh.plugin.json +++ b/01_content/dsh.plugin.json @@ -1,7 +1,7 @@ { "name": "dsh-worktable", "description": "工作台:DSH 侧边栏的 agent 级项目容器,收纳如 TravelAtlas 等自装项目", - "version": "0.2.0", + "version": "0.2.1", "entry": { "name": "dsh-worktable", "inject": [] diff --git a/01_content/lib/client.js b/01_content/lib/client.js index 1ccdc42..736f565 100644 --- a/01_content/lib/client.js +++ b/01_content/lib/client.js @@ -7807,6 +7807,8 @@ var css = xterm_default + "\n" + [ ".dsh-wt_manage{position:relative;z-index:12;display:flex;flex-direction:column;gap:4px;padding:6px;border:1px solid var(--dsw-alias-border-l2,#3a4150);border-radius:8px;background:var(--dsw-alias-bg-base,#0b0e14)}", ".dsh-wt_manageHead{display:flex;align-items:center;justify-content:space-between;padding:1px 2px 4px}", ".dsh-wt_manageTitle{font-size:11px;font-weight:600;letter-spacing:.04em;color:var(--dsw-alias-label-primary,#e6e8eb)}", + ".dsh-wt_settingsClose{position:absolute;top:6px;right:6px;width:20px;height:20px;padding:0;display:flex;align-items:center;justify-content:center;border:none;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary,#7d8aa5);font-size:13px;line-height:1;cursor:pointer}", + ".dsh-wt_settingsClose:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06));color:var(--dsw-alias-label-primary,#dbe4f3)}", ".dsh-wt_manageDone{display:flex;align-items:center;padding:2px 8px;border:none;border-radius:5px;background:transparent;color:var(--dsw-alias-state-accent-primary,#4f8ef7);font:inherit;font-size:11px;line-height:16px;cursor:pointer}", ".dsh-wt_manageDone:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}", // 排序方式:一左一右两个选择按钮(手动 / 最近) @@ -8206,7 +8208,31 @@ var css = xterm_default + "\n" + [ ".dsh-wt_consoleErr{margin:0;font-size:11px;line-height:16px;color:var(--dsw-alias-state-danger,#f85149)}", ".dsh-wt_consoleCreateBtn{margin-top:2px;padding:7px 10px;border:1px solid var(--dsw-alias-state-accent-primary,#4f8ef7);border-radius:8px;background:transparent;color:var(--dsw-alias-state-accent-primary,#4f8ef7);font:inherit;font-size:12px;line-height:18px;cursor:pointer}", ".dsh-wt_consoleCreateBtn:hover:not(:disabled){background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}", - ".dsh-wt_consoleCreateBtn:disabled{opacity:.55;cursor:default}" + ".dsh-wt_consoleCreateBtn:disabled{opacity:.55;cursor:default}", + // ── 更新提示:侧栏标题旁徽标(仅图标 + 琥珀呼吸光)与设置面板更新卡 / 版本行 ── + ".dsh-wt_updateBadge{flex:none;display:flex;align-items:center;justify-content:center;width:18px;height:18px;margin-left:3px;padding:0;border:none;border-radius:50%;background:rgba(245,185,66,.16);color:#f7d488;font-size:11px;line-height:1;cursor:pointer;position:relative}", + '.dsh-wt_updateBadge::after{content:"";position:absolute;inset:-1px;border-radius:50%;border:1px solid rgba(245,185,66,.55);animation:dsh-wt_updatePulse 1.8s ease-out infinite}', + "@keyframes dsh-wt_updatePulse{0%{opacity:.9;box-shadow:0 0 0 0 rgba(245,185,66,.5)}70%{opacity:.35;box-shadow:0 0 0 6px rgba(245,185,66,0)}100%{opacity:.9;box-shadow:0 0 0 0 rgba(245,185,66,0)}}", + ".dsh-wt_updateCard{margin:0 0 10px;padding:10px;border:1px solid rgba(245,185,66,.45);border-radius:10px;background:rgba(245,185,66,.08);display:flex;flex-direction:column;gap:6px}", + ".dsh-wt_updateHead{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:#f7d488}", + ".dsh-wt_updateDot{width:7px;height:7px;border-radius:50%;background:#f5b942;animation:dsh-wt_updatePulse 1.8s ease-out infinite}", + ".dsh-wt_updateVers{font-size:11.5px;color:var(--dsw-alias-label-tertiary,#7d8aa5)}", + ".dsh-wt_updateNotes{max-height:96px;overflow:auto;font-size:11px;line-height:1.6;color:var(--dsw-alias-label-tertiary,#7d8aa5);white-space:pre-wrap;word-break:break-all}", + ".dsh-wt_updateCmd{margin:2px 0;padding:7px 8px;background:var(--dsw-alias-bg-base,#0b0e14);border:1px solid var(--dsw-alias-border-l1,#262b36);border-radius:8px;font-family:Consolas,monospace;font-size:10.5px;line-height:1.5;color:#9cc6ff;word-break:break-all}", + ".dsh-wt_updateBtns{display:flex;gap:6px;flex-wrap:wrap}", + ".dsh-wt_updateBtn{display:inline-flex;align-items:center;gap:5px;padding:5px 8px;border-radius:7px;border:1px solid var(--dsw-alias-border-l1,#262b36);background:transparent;color:var(--dsw-alias-label-secondary,#aab6cd);font-size:11px;cursor:pointer}", + ".dsh-wt_updateBtn svg{width:12px;height:12px;flex:none}", + ".dsh-wt_updateBadge svg{width:12px;height:12px;flex:none}", + ".dsh-wt_updateBtn:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06))}", + ".dsh-wt_updateBtnCopy{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7);background:rgba(79,142,247,.18);color:#cfe0ff}", + ".dsh-wt_updateHint{font-size:10.5px;color:var(--dsw-alias-label-tertiary,#7d8aa5)}", + ".dsh-wt_versionRow{margin-top:8px;padding-top:8px;border-top:1px solid var(--dsw-alias-border-l1,#262b36);display:flex;align-items:center;justify-content:space-between;font-size:11px;color:var(--dsw-alias-label-tertiary,#7d8aa5)}", + ".dsh-wt_updateToggle{display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none}", + ".dsh-wt_versionActions{display:flex;align-items:center;gap:8px}", + ".dsh-wt_updateSwitch{position:relative;width:26px;height:14px;border-radius:999px;background:var(--dsw-alias-state-accent-primary,#4f8ef7);cursor:pointer}", + '.dsh-wt_updateSwitch::after{content:"";position:absolute;top:2px;right:2px;width:10px;height:10px;border-radius:50%;background:#fff;transition:right .15s}', + ".dsh-wt_updateSwitch[data-off=true]{background:var(--dsw-alias-border-l1,#333a48)}", + ".dsh-wt_updateSwitch[data-off=true]::after{right:14px}" ].join("\n"); // src/client/locales.ts @@ -8365,7 +8391,21 @@ var zh = { "split.dragSwap": "\u62D6\u52A8\u6807\u9898\u680F\u4E0E\u5176\u4ED6\u7A97\u4EA4\u6362\u4F4D\u7F6E", "icons.title": "\u9009\u62E9\u56FE\u6807", "icons.change": "\u66F4\u6362\u56FE\u6807", - "empty": "\u6682\u65E0\u9879\u76EE" + "empty": "\u6682\u65E0\u9879\u76EE", + "update.badgeTitle": "\u6709\u65B0\u7248\u672C\u53EF\u7528", + "update.available": "\u65B0\u7248\u672C\u53EF\u7528", + "update.current": "\u5F53\u524D\u7248\u672C", + "update.copyAi": "\u590D\u5236 AI \u63D0\u793A\u8BCD", + "update.skip": "\u5FFD\u7565\u6B64\u7248\u672C", + "update.autoCheck": "\u81EA\u52A8\u68C0\u67E5\u66F4\u65B0", + "update.checkNow": "\u7ACB\u5373\u68C0\u67E5", + "update.checkFail": "\u4E0A\u6B21\u68C0\u67E5\u672A\u6210\u529F", + "update.checking": "\u68C0\u67E5\u4E2D\u2026", + "update.upToDate": "\u5DF2\u662F\u6700\u65B0\u7248\u672C", + "update.copied": "\u5DF2\u590D\u5236", + "update.copyFail": "\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u62E9\u6587\u672C", + "update.skipDone": "\u5DF2\u5FFD\u7565\u6B64\u7248\u672C\uFF08\u4E0B\u4E00\u7248\u53D1\u5E03\u65F6\u518D\u63D0\u9192\uFF09", + "update.upgradeHint": "\u6267\u884C\u547D\u4EE4\u540E\u9700\u91CD\u542F dsh web \u5E76\u5237\u65B0\u9875\u9762" }; var en = { "title": "Worktable", @@ -8522,7 +8562,21 @@ var en = { "split.dragSwap": "Drag the title bar to swap panes", "icons.title": "Pick an icon", "icons.change": "Change icon", - "empty": "No projects yet" + "empty": "No projects yet", + "update.badgeTitle": "New version available", + "update.available": "New version available", + "update.current": "Current version", + "update.copyAi": "Copy AI prompt", + "update.skip": "Skip this version", + "update.autoCheck": "Auto-check updates", + "update.checkNow": "Check now", + "update.checkFail": "Last check failed", + "update.checking": "Checking\u2026", + "update.upToDate": "Up to date", + "update.copied": "Copied", + "update.copyFail": "Copy failed \u2014 select the text manually", + "update.skipDone": "Skipped (will remind on the next release)", + "update.upgradeHint": "Run the command, then restart dsh web and refresh" }; var NS = "worktable"; @@ -18212,6 +18266,40 @@ try { // src/client/index.tsx var import_jsx_runtime2 = require("react/jsx-runtime"); +var LOCAL_VERSION = false ? "0.2.0" : "0.2.1"; +var UPDATE_REPO = "Aisland-SJL/dsh-worktable"; +var UPGRADE_CMD = 'dsh plugin --profile web add "https://github.com/Aisland-SJL/dsh-worktable/releases/latest/download/dsh-worktable.tgz"'; +var UPGRADE_AI = "\u5E2E\u6211\u5347\u7EA7 dsh-worktable\uFF1A\u6267\u884C " + UPGRADE_CMD + "\uFF0C\u5B8C\u6210\u540E\u63D0\u9192\u6211\u91CD\u542F dsh web \u5E76\u5237\u65B0\u9875\u9762"; +var ICON_SYNC = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { viewBox: "0 0 16 16", "aria-hidden": true, children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", d: "M1.5 8a6.5 6.5 0 0 1 11.1-4.6L14.5 5" }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", d: "M14.5 1.5V5h-3.5" }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", d: "M14.5 8a6.5 6.5 0 0 1-11.1 4.6L1.5 11" }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", d: "M1.5 14.5V11h3.5" }) +] }); +var ICON_SPARK = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { viewBox: "0 0 16 16", "aria-hidden": true, children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 1.6l1.5 3.9 3.9 1.5-3.9 1.5L8 12.4 6.5 8.5 2.6 7l3.9-1.5z", fill: "none", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12.6 11.2l.5 1.3 1.3.5-1.3.5-.5 1.3-.5-1.3-1.3-.5 1.3-.5z", fill: "currentColor" }) +] }); +function cmpVer(a, b) { + const pa = a.split(".").map(Number); + const pb = b.split(".").map(Number); + for (let i = 0; i < Math.max(pa.length, pb.length); i++) { + const x = pa[i] ?? 0; + const y = pb[i] ?? 0; + if (x !== y) return x - y; + } + return 0; +} +async function copyText(text2) { + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(text2); + return true; + } + } catch { + } + return false; +} var PERSIST_KEY2 = "dsh.worktable.view.v1"; var PROJECTS_KEY = "dsh.worktable.projects.v1"; var MIN_TOP = 56; @@ -19097,6 +19185,62 @@ function WorktableSection(props) { const [registeredIds, setRegisteredIds] = (0, import_react2.useState)(() => [...registryStore.ids]); const [addOpen, setAddOpen] = (0, import_react2.useState)(false); const [viewOptionsOpen, setViewOptionsOpen] = (0, import_react2.useState)(false); + const [updateInfo, setUpdateInfo] = (0, import_react2.useState)(null); + const [updateCheckOn, setUpdateCheckOn] = (0, import_react2.useState)(() => localStorage.getItem("dsh.worktable.updateCheck.v1") !== "0"); + const [updateCopied, setUpdateCopied] = (0, import_react2.useState)(false); + const [updateStatus, setUpdateStatus] = (0, import_react2.useState)("idle"); + const checkUpdates = (0, import_react2.useCallback)(async (force = false) => { + const last = Number(localStorage.getItem("dsh.worktable.lastUpdateCheck.v1") ?? "0"); + if (!force && Date.now() - last < 24 * 3600 * 1e3) return; + setUpdateStatus("checking"); + let d = null; + for (let attempt = 0; attempt < 3 && !d; attempt++) { + try { + const r = await fetch("https://api.github.com/repos/" + UPDATE_REPO + "/releases/latest", { headers: { Accept: "application/vnd.github+json" }, cache: "no-store" }); + if (r.ok) d = await r.json(); + else if (r.status === 403 || r.status === 404) break; + } catch { + } + } + if (!d) { + setUpdateStatus("failed"); + return; + } + localStorage.setItem("dsh.worktable.lastUpdateCheck.v1", String(Date.now())); + const tag = (d.tag_name ?? "").replace(/^v/, ""); + if (!tag || cmpVer(tag, LOCAL_VERSION) <= 0) { + setUpdateStatus("uptodate"); + return; + } + if ((localStorage.getItem("dsh.worktable.skipVersion.v1") ?? "") === tag) { + setUpdateStatus("uptodate"); + return; + } + setUpdateInfo({ latest: tag, notes: (d.body ?? "").slice(0, 800), url: d.html_url ?? "" }); + setUpdateStatus("uptodate"); + }, []); + (0, import_react2.useEffect)(() => { + if (updateCheckOn) void checkUpdates(); + }, [updateCheckOn, checkUpdates]); + const copyUpgradeAi = async () => { + const ok = await copyText(UPGRADE_AI); + if (ok) { + setUpdateCopied(true); + setTimeout(() => setUpdateCopied(false), 2200); + } + }; + const skipUpdate = () => { + if (updateInfo) { + localStorage.setItem("dsh.worktable.skipVersion.v1", updateInfo.latest); + setUpdateInfo(null); + } + }; + const toggleUpdateCheck = () => { + setUpdateCheckOn((v) => { + localStorage.setItem("dsh.worktable.updateCheck.v1", v ? "0" : "1"); + return !v; + }); + }; const [wsPreset, setWsPreset] = (0, import_react2.useState)("2h"); const [wsName, setWsName] = (0, import_react2.useState)(""); const [wsError, setWsError] = (0, import_react2.useState)(false); @@ -20349,6 +20493,21 @@ function WorktableSection(props) { const sectionTop = rootRef.current?.getBoundingClientRect().top ?? 100; const popLeft = sidebarRight != null ? Math.min(sidebarRight + 8, Math.max(16, window.innerWidth - 344)) : 16; const popTop = clamp2(sectionTop, MIN_TOP, Math.max(MIN_TOP, window.innerHeight - 540)); + const POP_BOTTOM_MARGIN = 12; + const settingsRef = (0, import_react2.useRef)(null); + const [settingsTop, setSettingsTop] = (0, import_react2.useState)(null); + (0, import_react2.useLayoutEffect)(() => { + const recalc = () => { + if (!viewOptionsOpen || !settingsRef.current) return; + const h = settingsRef.current.offsetHeight; + const rawTop = Math.max(MIN_TOP, sectionTop); + const maxTop = Math.max(MIN_TOP, window.innerHeight - h - POP_BOTTOM_MARGIN); + setSettingsTop(Math.min(rawTop, maxTop)); + }; + recalc(); + window.addEventListener("resize", recalc); + return () => window.removeEventListener("resize", recalc); + }, [viewOptionsOpen, updateInfo, sectionTop]); const openRailProject = (id) => { const pr = projectsRef.current.projects; const layout = pr.layouts.find((l) => l.id === id); @@ -20426,6 +20585,17 @@ function WorktableSection(props) { children: t("title") } ), + updateInfo && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( + "button", + { + type: "button", + className: "dsh-wt_updateBadge", + title: t("update.badgeTitle") + " \xB7 v" + updateInfo.latest, + "aria-label": t("update.badgeTitle"), + onClick: () => setViewOptionsOpen(true), + children: ICON_SYNC + } + ), /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_actions", children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( "button", @@ -20585,7 +20755,44 @@ function WorktableSection(props) { ] }) ] }), viewOptionsOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-wt_popBackdrop", onClick: () => setViewOptionsOpen(false) }), - viewOptionsOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_manage dsh-wt_pop dsh-wt_settings", style: { position: "fixed", left: popLeft, top: popTop, width: 280, zIndex: 80 }, children: [ + viewOptionsOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: settingsRef, className: "dsh-wt_manage dsh-wt_pop dsh-wt_settings", style: { position: "fixed", left: popLeft, top: settingsTop ?? popTop, width: 280, zIndex: 80 }, children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( + "button", + { + type: "button", + className: "dsh-wt_settingsClose", + "aria-label": t("manage.done"), + title: t("manage.done"), + onClick: () => setViewOptionsOpen(false), + children: "\u2715" + } + ), + updateInfo && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_updateCard", children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_updateHead", children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-wt_updateDot" }), + t("update.available"), + " \xB7 v", + updateInfo.latest + ] }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_updateVers", children: [ + t("update.current"), + " v", + LOCAL_VERSION, + " \u2192 v", + updateInfo.latest + ] }), + updateInfo.notes && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-wt_updateNotes", children: updateInfo.notes }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-wt_updateCmd", children: UPGRADE_CMD }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_updateBtns", children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-wt_updateBtn dsh-wt_updateBtnCopy", onClick: () => void copyUpgradeAi(), children: updateCopied ? "\u2713 " + t("update.copied") : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [ + ICON_SPARK, + " ", + t("update.copyAi") + ] }) }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-wt_updateBtn", onClick: skipUpdate, children: t("update.skip") }) + ] }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-wt_updateHint", children: t("update.upgradeHint") }) + ] }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-wt_manageHead", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-wt_manageTitle", children: t("sort.label") }) }), /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_sortRow", children: [ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)( @@ -20610,10 +20817,7 @@ function WorktableSection(props) { ) ] }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-wt_menuSep" }), - /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_manageHead", children: [ - /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-wt_manageTitle", children: t("manage.title") }), - /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-wt_manageDone", onClick: () => setViewOptionsOpen(false), children: t("manage.done") }) - ] }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-wt_manageHead", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-wt_manageTitle", children: t("manage.title") }) }), effectiveOrder.map((id) => { const meta = metas[id]; const layout = projects.layouts.find((l) => l.id === id); @@ -20705,7 +20909,22 @@ function WorktableSection(props) { ), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-wt_manageScName", children: s.name }), /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-wt_manageBtn", title: t("manage.deleteShortcut"), onClick: () => askDelete("shortcut", s.id, s.name), children: "\u2715" }) - ] }, s.id)) + ] }, s.id)), + /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_versionRow", children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [ + "v", + LOCAL_VERSION, + updateStatus === "uptodate" && !updateInfo ? " \xB7 " + t("update.upToDate") : "", + updateStatus === "failed" && !updateInfo ? " \xB7 " + t("update.checkFail") : "" + ] }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-wt_versionActions", children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "dsh-wt_updateBtn", onClick: () => void checkUpdates(true), children: updateStatus === "checking" ? t("update.checking") : t("update.checkNow") }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "dsh-wt_updateToggle", onClick: toggleUpdateCheck, children: [ + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("update.autoCheck") }), + /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dsh-wt_updateSwitch", "data-off": updateCheckOn ? void 0 : "true" }) + ] }) + ] }) + ] }) ] }), viewPickFor && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dsh-wt_popBackdrop", style: { zIndex: 81 }, onClick: () => setViewPickFor(null) }), viewPickFor && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dsh-wt_menu dsh-wt_pop", style: { position: "fixed", left: popLeft, top: popTop, width: 320, zIndex: 82 }, children: [ diff --git a/01_content/package.json b/01_content/package.json index 05d8dfd..157fb6d 100644 --- a/01_content/package.json +++ b/01_content/package.json @@ -1,7 +1,7 @@ { "name": "dsh-worktable", "description": "工作台 Worktable:DeepSeek Harness 侧边栏的 agent 级项目容器(应用抽屉)", - "version": "0.2.0", + "version": "0.2.1", "license": "MIT", "type": "module", "main": "lib/index.js", diff --git a/01_content/src/client/index.tsx b/01_content/src/client/index.tsx index 64194b6..a2beab7 100644 --- a/01_content/src/client/index.tsx +++ b/01_content/src/client/index.tsx @@ -19,6 +19,45 @@ import { splitStore, SplitWorkspace, setSplitT, setSplitEnv, type LayoutSpec, ty type OrderBy = 'manual' | 'recent' type DockMode = 'footer' | 'float' +// ── 更新检查(客户端直连 GitHub Releases API,只读 GET;失败静默)── +declare const __WT_VERSION__: string +const LOCAL_VERSION = typeof __WT_VERSION__ === 'undefined' ? '0.2.0' : __WT_VERSION__ +const UPDATE_REPO = 'Aisland-SJL/dsh-worktable' +const UPGRADE_CMD = 'dsh plugin --profile web add "https://github.com/Aisland-SJL/dsh-worktable/releases/latest/download/dsh-worktable.tgz"' +const UPGRADE_AI = '帮我升级 dsh-worktable:执行 ' + UPGRADE_CMD + ',完成后提醒我重启 dsh web 并刷新页面' +// 更新提示图标(手绘 SVG,避免 emoji 跨平台渲染差异) +const ICON_SYNC = ( + + + + + + +) +const ICON_SPARK = ( + + + + +) +type UpdateInfo = { latest: string; notes: string; url: string } +function cmpVer(a: string, b: string): number { + const pa = a.split('.').map(Number) + const pb = b.split('.').map(Number) + for (let i = 0; i < Math.max(pa.length, pb.length); i++) { + const x = pa[i] ?? 0 + const y = pb[i] ?? 0 + if (x !== y) return x - y + } + return 0 +} +async function copyText(text: string): Promise { + try { + if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); return true } + } catch { /* fallthrough */ } + return false +} + type ViewState = { query: string searchOpen: boolean @@ -1008,6 +1047,48 @@ function WorktableSection(props: any) { const [registeredIds, setRegisteredIds] = useState(() => [...registryStore.ids]) const [addOpen, setAddOpen] = useState(false) const [viewOptionsOpen, setViewOptionsOpen] = useState(false) + // 更新检查:徽标 / 更新卡 / 版本行共用;节流一天一次,忽略按版本号存 localStorage + const [updateInfo, setUpdateInfo] = useState(null) + const [updateCheckOn, setUpdateCheckOn] = useState(() => localStorage.getItem('dsh.worktable.updateCheck.v1') !== '0') + const [updateCopied, setUpdateCopied] = useState(false) + const [updateStatus, setUpdateStatus] = useState<'idle' | 'checking' | 'uptodate' | 'failed'>('idle') + const checkUpdates = useCallback(async (force = false) => { + const last = Number(localStorage.getItem('dsh.worktable.lastUpdateCheck.v1') ?? '0') + if (!force && Date.now() - last < 24 * 3600 * 1000) return // 自动检查节流;手动「立即检查」绕过节流 + setUpdateStatus('checking') + let d: { tag_name?: string; body?: string; html_url?: string } | null = null + for (let attempt = 0; attempt < 3 && !d; attempt++) { + try { + const r = await fetch('https://api.github.com/repos/' + UPDATE_REPO + '/releases/latest', { headers: { Accept: 'application/vnd.github+json' }, cache: 'no-store' }) + if (r.ok) d = await r.json() as { tag_name?: string; body?: string; html_url?: string } + else if (r.status === 403 || r.status === 404) break // 限流/不存在:不再重试 + } catch { /* 网络抖动:下一轮重试 */ } + } + if (!d) { setUpdateStatus('failed'); return } + localStorage.setItem('dsh.worktable.lastUpdateCheck.v1', String(Date.now())) + const tag = (d.tag_name ?? '').replace(/^v/, '') + if (!tag || cmpVer(tag, LOCAL_VERSION) <= 0) { setUpdateStatus('uptodate'); return } + if ((localStorage.getItem('dsh.worktable.skipVersion.v1') ?? '') === tag) { setUpdateStatus('uptodate'); return } + setUpdateInfo({ latest: tag, notes: (d.body ?? '').slice(0, 800), url: d.html_url ?? '' }) + setUpdateStatus('uptodate') + }, []) + useEffect(() => { if (updateCheckOn) void checkUpdates() }, [updateCheckOn, checkUpdates]) + const copyUpgradeAi = async () => { + const ok = await copyText(UPGRADE_AI) + if (ok) { setUpdateCopied(true); setTimeout(() => setUpdateCopied(false), 2200) } + } + const skipUpdate = () => { + if (updateInfo) { + localStorage.setItem('dsh.worktable.skipVersion.v1', updateInfo.latest) + setUpdateInfo(null) + } + } + const toggleUpdateCheck = () => { + setUpdateCheckOn((v) => { + localStorage.setItem('dsh.worktable.updateCheck.v1', v ? '0' : '1') + return !v + }) + } const [wsPreset, setWsPreset] = useState('2h') const [wsName, setWsName] = useState('') const [wsError, setWsError] = useState(false) @@ -2303,6 +2384,23 @@ function buildCustomLayoutPrompt(req: string): string { : 16 const popTop = clamp(sectionTop, MIN_TOP, Math.max(MIN_TOP, window.innerHeight - 540)) + // 设置弹窗底部防溢出:弹窗底与视口底保持 POP_BOTTOM_MARGIN;触底后以底为基准向上生长 + const POP_BOTTOM_MARGIN = 12 + const settingsRef = useRef(null) + const [settingsTop, setSettingsTop] = useState(null) + useLayoutEffect(() => { + const recalc = () => { + if (!viewOptionsOpen || !settingsRef.current) return + const h = settingsRef.current.offsetHeight + const rawTop = Math.max(MIN_TOP, sectionTop) + const maxTop = Math.max(MIN_TOP, window.innerHeight - h - POP_BOTTOM_MARGIN) + setSettingsTop(Math.min(rawTop, maxTop)) + } + recalc() + window.addEventListener('resize', recalc) + return () => window.removeEventListener('resize', recalc) + }, [viewOptionsOpen, updateInfo, sectionTop]) + /** 收起态点击项目图标:视图覆盖/布局 → openSplit;入驻无视图 → 切绑定对话 */ const openRailProject = (id: string) => { const pr = projectsRef.current.projects @@ -2380,6 +2478,15 @@ function buildCustomLayoutPrompt(req: string): string { onPointerCancel={onHandlePointerUp} onDoubleClick={resetDock} >{t('title')} + {updateInfo && ( + + )}
+ {updateInfo && ( +
+
{t('update.available')} · v{updateInfo.latest}
+
{t('update.current')} v{LOCAL_VERSION} → v{updateInfo.latest}
+ {updateInfo.notes &&
{updateInfo.notes}
} +
{UPGRADE_CMD}
+
+ + +
+
{t('update.upgradeHint')}
+
+ )}
{t('sort.label')}
@@ -2513,7 +2642,6 @@ function buildCustomLayoutPrompt(req: string): string {
{t('manage.title')} -
{effectiveOrder.map((id) => { const meta = metas[id] @@ -2578,6 +2706,22 @@ function buildCustomLayoutPrompt(req: string): string {
))} +
+ + v{LOCAL_VERSION} + {updateStatus === 'uptodate' && !updateInfo ? ' · ' + t('update.upToDate') : ''} + {updateStatus === 'failed' && !updateInfo ? ' · ' + t('update.checkFail') : ''} + + + + + {t('update.autoCheck')} + + + +
)} diff --git a/01_content/src/client/locales.ts b/01_content/src/client/locales.ts index 313f6ad..68b56f4 100644 --- a/01_content/src/client/locales.ts +++ b/01_content/src/client/locales.ts @@ -161,6 +161,20 @@ export const zh = { 'icons.title': '选择图标', 'icons.change': '更换图标', 'empty': '暂无项目', + 'update.badgeTitle': '有新版本可用', + 'update.available': '新版本可用', + 'update.current': '当前版本', + 'update.copyAi': '复制 AI 提示词', + 'update.skip': '忽略此版本', + 'update.autoCheck': '自动检查更新', + 'update.checkNow': '立即检查', + 'update.checkFail': '上次检查未成功', + 'update.checking': '检查中…', + 'update.upToDate': '已是最新版本', + 'update.copied': '已复制', + 'update.copyFail': '复制失败,请手动选择文本', + 'update.skipDone': '已忽略此版本(下一版发布时再提醒)', + 'update.upgradeHint': '执行命令后需重启 dsh web 并刷新页面', } satisfies Record /** The worktable namespace key union. */ @@ -323,6 +337,20 @@ export const en = { 'icons.title': 'Pick an icon', 'icons.change': 'Change icon', 'empty': 'No projects yet', + 'update.badgeTitle': 'New version available', + 'update.available': 'New version available', + 'update.current': 'Current version', + 'update.copyAi': 'Copy AI prompt', + 'update.skip': 'Skip this version', + 'update.autoCheck': 'Auto-check updates', + 'update.checkNow': 'Check now', + 'update.checkFail': 'Last check failed', + 'update.checking': 'Checking…', + 'update.upToDate': 'Up to date', + 'update.copied': 'Copied', + 'update.copyFail': 'Copy failed — select the text manually', + 'update.skipDone': 'Skipped (will remind on the next release)', + 'update.upgradeHint': 'Run the command, then restart dsh web and refresh', } satisfies Record /** Locale namespace id registered under ctx.locale. */ diff --git a/01_content/src/client/styles.ts b/01_content/src/client/styles.ts index 52a41a1..85c3f36 100644 --- a/01_content/src/client/styles.ts +++ b/01_content/src/client/styles.ts @@ -53,6 +53,8 @@ export const css = xtermCss + '\n' + [ '.dsh-wt_manage{position:relative;z-index:12;display:flex;flex-direction:column;gap:4px;padding:6px;border:1px solid var(--dsw-alias-border-l2,#3a4150);border-radius:8px;background:var(--dsw-alias-bg-base,#0b0e14)}', '.dsh-wt_manageHead{display:flex;align-items:center;justify-content:space-between;padding:1px 2px 4px}', '.dsh-wt_manageTitle{font-size:11px;font-weight:600;letter-spacing:.04em;color:var(--dsw-alias-label-primary,#e6e8eb)}', + '.dsh-wt_settingsClose{position:absolute;top:6px;right:6px;width:20px;height:20px;padding:0;display:flex;align-items:center;justify-content:center;border:none;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary,#7d8aa5);font-size:13px;line-height:1;cursor:pointer}', + '.dsh-wt_settingsClose:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06));color:var(--dsw-alias-label-primary,#dbe4f3)}', '.dsh-wt_manageDone{display:flex;align-items:center;padding:2px 8px;border:none;border-radius:5px;background:transparent;color:var(--dsw-alias-state-accent-primary,#4f8ef7);font:inherit;font-size:11px;line-height:16px;cursor:pointer}', '.dsh-wt_manageDone:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}', // 排序方式:一左一右两个选择按钮(手动 / 最近) @@ -455,4 +457,28 @@ export const css = xtermCss + '\n' + [ '.dsh-wt_consoleCreateBtn{margin-top:2px;padding:7px 10px;border:1px solid var(--dsw-alias-state-accent-primary,#4f8ef7);border-radius:8px;background:transparent;color:var(--dsw-alias-state-accent-primary,#4f8ef7);font:inherit;font-size:12px;line-height:18px;cursor:pointer}', '.dsh-wt_consoleCreateBtn:hover:not(:disabled){background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}', '.dsh-wt_consoleCreateBtn:disabled{opacity:.55;cursor:default}', + // ── 更新提示:侧栏标题旁徽标(仅图标 + 琥珀呼吸光)与设置面板更新卡 / 版本行 ── + '.dsh-wt_updateBadge{flex:none;display:flex;align-items:center;justify-content:center;width:18px;height:18px;margin-left:3px;padding:0;border:none;border-radius:50%;background:rgba(245,185,66,.16);color:#f7d488;font-size:11px;line-height:1;cursor:pointer;position:relative}', + '.dsh-wt_updateBadge::after{content:"";position:absolute;inset:-1px;border-radius:50%;border:1px solid rgba(245,185,66,.55);animation:dsh-wt_updatePulse 1.8s ease-out infinite}', + '@keyframes dsh-wt_updatePulse{0%{opacity:.9;box-shadow:0 0 0 0 rgba(245,185,66,.5)}70%{opacity:.35;box-shadow:0 0 0 6px rgba(245,185,66,0)}100%{opacity:.9;box-shadow:0 0 0 0 rgba(245,185,66,0)}}', + '.dsh-wt_updateCard{margin:0 0 10px;padding:10px;border:1px solid rgba(245,185,66,.45);border-radius:10px;background:rgba(245,185,66,.08);display:flex;flex-direction:column;gap:6px}', + '.dsh-wt_updateHead{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:#f7d488}', + '.dsh-wt_updateDot{width:7px;height:7px;border-radius:50%;background:#f5b942;animation:dsh-wt_updatePulse 1.8s ease-out infinite}', + '.dsh-wt_updateVers{font-size:11.5px;color:var(--dsw-alias-label-tertiary,#7d8aa5)}', + '.dsh-wt_updateNotes{max-height:96px;overflow:auto;font-size:11px;line-height:1.6;color:var(--dsw-alias-label-tertiary,#7d8aa5);white-space:pre-wrap;word-break:break-all}', + '.dsh-wt_updateCmd{margin:2px 0;padding:7px 8px;background:var(--dsw-alias-bg-base,#0b0e14);border:1px solid var(--dsw-alias-border-l1,#262b36);border-radius:8px;font-family:Consolas,monospace;font-size:10.5px;line-height:1.5;color:#9cc6ff;word-break:break-all}', + '.dsh-wt_updateBtns{display:flex;gap:6px;flex-wrap:wrap}', + '.dsh-wt_updateBtn{display:inline-flex;align-items:center;gap:5px;padding:5px 8px;border-radius:7px;border:1px solid var(--dsw-alias-border-l1,#262b36);background:transparent;color:var(--dsw-alias-label-secondary,#aab6cd);font-size:11px;cursor:pointer}', + '.dsh-wt_updateBtn svg{width:12px;height:12px;flex:none}', + '.dsh-wt_updateBadge svg{width:12px;height:12px;flex:none}', + '.dsh-wt_updateBtn:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06))}', + '.dsh-wt_updateBtnCopy{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7);background:rgba(79,142,247,.18);color:#cfe0ff}', + '.dsh-wt_updateHint{font-size:10.5px;color:var(--dsw-alias-label-tertiary,#7d8aa5)}', + '.dsh-wt_versionRow{margin-top:8px;padding-top:8px;border-top:1px solid var(--dsw-alias-border-l1,#262b36);display:flex;align-items:center;justify-content:space-between;font-size:11px;color:var(--dsw-alias-label-tertiary,#7d8aa5)}', + '.dsh-wt_updateToggle{display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none}', + '.dsh-wt_versionActions{display:flex;align-items:center;gap:8px}', + '.dsh-wt_updateSwitch{position:relative;width:26px;height:14px;border-radius:999px;background:var(--dsw-alias-state-accent-primary,#4f8ef7);cursor:pointer}', + '.dsh-wt_updateSwitch::after{content:"";position:absolute;top:2px;right:2px;width:10px;height:10px;border-radius:50%;background:#fff;transition:right .15s}', + '.dsh-wt_updateSwitch[data-off=true]{background:var(--dsw-alias-border-l1,#333a48)}', + '.dsh-wt_updateSwitch[data-off=true]::after{right:14px}', ].join('\n') diff --git a/02_process/PRD.md b/02_process/PRD.md index fd1dca4..7d968d7 100644 --- a/02_process/PRD.md +++ b/02_process/PRD.md @@ -172,6 +172,8 @@ ctx.slots.register({ - bindings:项目 → 绑定会话(含 wt-console 管理对话); - folders:项目 → 项目文件夹(含 wt-console); - 卡片上报的 meta 注册表仅存内存,不持久化; + - 更新检查:`dsh.worktable.lastUpdateCheck.v1`(上次成功检查时间戳,节流一天一次)、 + `dsh.worktable.skipVersion.v1`(忽略的版本号)、`dsh.worktable.updateCheck.v1`(自动检查开关,'0'=关); - 样式:暗色优先,跟随 `--dsw-alias-*` 设计变量(与 dsh-usage / dsh-travelatlas 一致)。 ## 7. 与 dsh-travelatlas 的关系 @@ -188,6 +190,7 @@ ctx.slots.register({ - 无个人数据采集;所有状态仅存 localStorage; - 搜索仅在本机过滤项目名; - 不读写任何工作区文件、不请求任何网络资源(除插件自身静态资源)。 +- 更新检查为可关闭的只读 GET(GitHub Releases API,自动每天最多一次 + 手动「立即检查」),不上传任何数据。 ## 9. 验收清单(v1) diff --git a/AGENTS.md b/AGENTS.md index ddcd57b..a01d539 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -129,6 +129,17 @@ node --check lib/index.js previewCache;sweepPreviews 在打开控制室时 + 控制室开着且会话快照变化防抖 6s 触发; 失败静默回退内存路径 lastTextOf。拉取是带宽成本不是 Token 成本。 - 状态指示:卡片右上角小圆点已删(整卡光效表达状态);状态计算不变。 +- **更新检查(v0.2.1)**:客户端直连 GitHub Releases API 比版本(只读 GET;自动每天最多 + 一次,手动「立即检查」绕过节流;失败重试 3 次后状态行显示「上次检查未成功」)。 + 状态四态 idle/checking/uptodate/failed;徽标 = 「工作台」标题右侧琥珀呼吸小圆(SVG 同步 + 图标、不显示版本号),仅发现更新时出现;更新卡在设置面板顶部(复制 AI 提示词 + 忽略此 + 版本 + 命令框供终端用户手抄),版本号与自动检查开关在面板底部;设置弹窗右上角 ✕ 关闭、 + 底部防溢出钳制(POP_BOTTOM_MARGIN=12,贴底后向上生长;按钮与开关必须平级防冒泡)。 + localStorage 键 lastUpdateCheck.v1 / skipVersion.v1 / updateCheck.v1。 + **发布纪律**:改动≠发布,只有 tag+Release 才触发提醒;新版本 = 新 tag + 新 Release,每个 + Release 必须同时附固定名资产 `dsh-worktable.tgz`(供 releases/latest/download 永久链接) + 与版本化资产。版本注入:build.mjs 把 package.json version 打进 __WT_VERSION__,发版前 + 改 version 再构建;升级动作(执行 add + 重启)永远留给用户或其 Agent,插件不自更新。 ## 宿主升级事故(0.1.1-rc.2 link 插件回归) @@ -146,8 +157,17 @@ node --check lib/index.js - 恢复后验收:functional-diag 全 20 STEP ERROR_COUNT: 0;/api/worktable/health ok、 /workspaces 200。 +## 宿主 bug 跟踪(UTF-16 路径截断) + +- 官方 `dsh-host-directory-picker-native` 的 readUtf16 只查 UTF-16LE 码元低字节,含 U+XX00 + 字符(开/一/言/Ā/🀀 等)的文件夹路径在选目录时被截断 → 创建工作区异常。非我们插件问题。 +- 修复与回归测试以 patch 存档:`02_process/upstream/utf16-picker-fix.patch`;官方 Discussions + #580 已接单(tianyicui「我们修复一下」),我们的补证评论已发(Aisland-SJL)。 +- 待办:官方修 master 后核验对应 npm 版本是否含修复,随后可清理 patch 存档。 + ## 安装 / 重启 - 注册:`dsh plugin --profile web add "link:/01_content"`(写 ~/.dsh,需用户授权)。 +- 发布版安装(给用户):`dsh plugin --profile web add "https://github.com/Aisland-SJL/dsh-worktable/releases/latest/download/dsh-worktable.tgz"`(依赖每个 Release 的固定名资产)。 - bundle 层只在启动时组合:改动后必须重启 dsh web 并刷新 GUI。 diff --git a/README.md b/README.md index dc48ced..bf641f4 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,23 @@ ## Quick start -1. **Install**: `dsh plugin --profile web add "link:/01_content"` and register `dsh-worktable` in the profile bundle list +1. **Install** (pick one): + + **A · one-liner (recommended)** — straight from the GitHub Release tarball, no Git needed: + + ```bash + dsh plugin --profile web add "https://github.com/Aisland-SJL/dsh-worktable/releases/latest/download/dsh-worktable.tgz" + ``` + + **B · local clone (for hacking on the source)** — `link:` accepts a local absolute path only (no spaces in the path): + + ```bash + git clone https://github.com/Aisland-SJL/dsh-worktable.git + dsh plugin --profile web add "link:/01_content" + # e.g. cloned into D:\tools → dsh plugin --profile web add "link:D:/tools/dsh-worktable/01_content" + ``` + + Either way the `add` command registers `dsh-worktable` in the profile bundle list (writes to `~/.dsh`, may ask for authorization). If the `dsh` command is missing, use `npx @deepseek-ai/dsh` instead. 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 @@ -97,7 +113,7 @@ node --check lib/index.js ## Privacy -No telemetry, no network calls beyond the host APIs and the plugin routes. All user state stays in localStorage. +No telemetry, no network calls beyond the host APIs and the plugin routes. All user state stays in localStorage. Optional update check: a read-only GET to the GitHub Releases API (automatic at most once a day, plus a manual "Check now" button); nothing is uploaded, and it can be disabled in Settings. --- diff --git a/README.zh.md b/README.zh.md index 063f9a1..b6678bd 100644 --- a/README.zh.md +++ b/README.zh.md @@ -53,7 +53,23 @@ ## 快速开始 -1. **安装**:`dsh plugin --profile web add "link:/01_content"`,并把 `dsh-worktable` 注册进 profile 的 bundle 列表 +1. **安装**(二选一): + + **A · 一行命令(推荐)** —— 直接安装 GitHub Release 的安装包,无需 Git: + + ```bash + dsh plugin --profile web add "https://github.com/Aisland-SJL/dsh-worktable/releases/latest/download/dsh-worktable.tgz" + ``` + + **B · 本地克隆(想改源码用)** —— `link:` 只接受本地绝对路径(路径不要带空格): + + ```bash + git clone https://github.com/Aisland-SJL/dsh-worktable.git + dsh plugin --profile web add "link:<克隆出来的 dsh-worktable 仓库目录的绝对路径>/01_content" + # 例:克隆到 D:\tools 后 → dsh plugin --profile web add "link:D:/tools/dsh-worktable/01_content" + ``` + + 两种方式 `add` 都会把 `dsh-worktable` 注册进 profile 的 bundle 列表(写入 `~/.dsh`,可能需要授权确认);若提示找不到 `dsh` 命令,用 `npx @deepseek-ai/dsh` 代替。 2. **重启** DSH web 进程并刷新界面 3. **打开控制室**:点击固定首位的 🖥️ 控制室卡片 → 绑定一条对话(加入现有或新建)→ 得到实时卡片网格 4. **创建项目**:侧边栏 + → 选布局预设、填项目文件夹 @@ -97,7 +113,7 @@ node --check lib/index.js ## 隐私 -无遥测;除宿主 API 与插件自身路由外无任何网络请求。用户状态全部留在 localStorage。 +无遥测;除宿主 API 与插件自身路由外无任何网络请求。用户状态全部留在 localStorage。可选更新检查:对 GitHub Releases API 做只读 GET(自动每天最多一次,另有手动「立即检查」),不上传任何数据,可在设置中关闭。 ---