Skip to content

Releases: dingchenhui0618-arch/dsh-in-hand

dsh-in-hand v1.8.18

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 02 Nov 04:12

dsh-in-hand v1.8.18

为什么改(2026-11-02)

原话:「将左上角三横线点击以后 工作台和操作页面往上提 关闭实验功能德州的内容剔除掉」。

上一版的会话抽屉从上往下是:标题 → 搜索 → 字号 → 会话列表 → 工作台 → 操作 → 主题。
列表占了上半屏,想开个新会话、切入口、看设备,每次都要先滑过整段会话列表 ——
这些入口是"打开抽屉就要做的事",却被放在最难够到的位置。

新的自上而下

  1. 标题「会话」(带会话数)
  2. 工作台:工作区 / 插件 / 自动化
  3. 操作:子代理 / 开一个新会话 / 设备 / 入口 · 当前入口 / 通知 / 并行视图 / 主题
  4. 搜索框
  5. 字号三档 + 「只看对话」
  6. 会话列表(运行中 / 最近)

只挪了字符串拼接顺序:data-act 的含义、事件的委托方式、所有 CSS 类名都没动。
搜索框与字号仍紧贴列表(它们作用于列表)。整块「工作台 / 操作」用一层 3% 前景色的
分组底色 + 上下发丝线和下面的「筛选 + 列表」分开 —— 前置之后动作区有 10 个入口,
给它一个明确的区块边界,读起来是一组入口而不是压住列表的一堵墙;分组标题仍是原来的
16px 上 / 8px 下节奏,明暗两套下都成立(暗色下用同一份 color-mix 规则,不另写一套)。

移除「实验功能:德州扑克」

  • 抽屉里那一行 <button data-act="poker"> 整行删除,[data-act="poker"] 在
    面板 DOM 里已经不存在(scripts/check-sheet.mjs 按选择器数量断言)。
  • act === 'poker' 的分支保留(已不可达),#poker 这个牌桌节点和 pk* 那段
    旧 JS 也保留:页面加载时会无条件执行 $('poker').innerHTML = …,删掉节点会
    在启动时直接抛错。真要清干净,得连牌桌 DOM、样式与 pk* 一起删,那是另一件事。

验收

  • npm run check 全绿(退出码 0)。新增 scripts/check-sheet.mjs(已并入
    npm run check):真 Chrome + CDP、DPR2、390/360 两档下断言
    「工作台」「操作」两个标题都排在 #sessionList 之前、面板里不存在
    [data-act="poker"]、搜索框仍在列表之前、字号在搜索框之后且列表之前、
    点「通知」仍进通知页。
  • lib/sw.js 的 CACHE 升到 taskwatch-v33(抽屉结构在外壳里,不升号已装的
    PWA 会继续跑旧顺序)。
  • 截图:_ui-review/round23/sheet-new.png、sheet-new-dark.png、sheet-390.png
    (npm run shots:sheet)。

Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md

dsh-in-hand v1.8.17

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 30 Oct 07:41

dsh-in-hand v1.8.17

为什么改(2026-10-30)

v1.8.15 起「有事情等你」能推到飞书,v1.8.16 能推到微信,但两条路都要经过别人的服务器:
群里多一条、微信里多一条,还都要一个 webhook 或 SendKey。手机自己就能做的事,没必要绕。
这一版用 Web Push:浏览器把订阅交给它自己的推送服务(Chrome 的 FCM、Safari 的 APNs),
由系统把通知递到锁屏。插件只存订阅、签 VAPID、加密正文 —— 不新增任何第三方账号,
不产生费用,运行时依赖仍然为零(dependencies 依旧缺省)。

加密与签名

  • lib/webpush.js(新文件):RFC 8291 的 aes128gcm 正文加密 + RFC 8292 的 VAPID,
    只用 node:crypto(ECDH P-256 + HKDF-SHA256 + AES-128-GCM)。记录头为
    salt(16) | rs(4)=4096 | idlen(1)=65 | keyid(65),正文超过 4079 字节自动拆成多条记录。
  • 与 RFC 8291 附录 A 的测试向量逐字节一致:ecdh_secret / PRK_key / IKM /
    PRK / CEK / NONCE 与整条正文全部比对,所以「能解开」不是自己解自己。
  • VAPID 密钥在 ~/.dsh/taskwatch-vapid.json(仅当前用户可读,首次需要时才生成,
    没开过系统通知的机器上不会出现这个文件)。JWT 用 ES256(dsaEncoding:'ieee-p1363'),
    claims 是 {aud: endpoint 的 origin, exp: now+12h, sub: 仓库地址};请求头
    Authorization: vapid t=<jwt>,k=<公钥>,TTL: 300。
  • 订阅存 ~/.dsh/taskwatch-push.json(仅当前用户可读):{id, deviceId, endpoint, keys:{p256dh,auth}, origin, uaShort, createdAt, lastOkAt}。deviceId 是设备账本里的
    12 位摘要(twd cookie 派生),不是 cookie 原值;上限 20 条、单设备 5 条。
  • endpoint 只收 https://(本机 / 内网的 http:// 留给自测);推送服务回 404 / 410
    就删掉这条订阅,其余失败只留一行 push failed(不含地址)。

路由

POST /taskwatch/push/subscribe、POST /taskwatch/push/unsubscribe(带 id 删一条,
不带就删这台设备的全部)、POST /taskwatch/push/test、GET /taskwatch/push/state。
四条都在设备闸门内,响应只出 maskPushEndpoint() 的掩码(域名 + 尾部 6 字符);
完整 endpoint 与 keys 不进任何响应,也不进审计和日志。

通知与设置页

  • 标题 N 件事等你,正文沿用既有摘要口径(工具名 + 说明前 30 字,多条列 3 条),
    tag 固定 taskwatch-pending(同一批不叠成一列),点通知回订阅时的 origin + #home。
  • SW 新增 push → showNotification、notificationclick → 已有窗口 focus 并导航、
    没有才 openWindow;CACHE 升到 taskwatch-v32 —— 装到桌面的 PWA 用的是旧 SW,
    不升号它根本不会响应 push。
  • 设置页新增「系统通知」区块:状态(不支持 / 未开启 / 已开启 N 台设备)、
    「开启系统通知」「发一条测试通知」「关闭此设备」。iOS 非主屏幕打开时只给
    「分享 → 添加到主屏幕 → 从主屏幕打开」三步,不显示开启按钮(iOS 16.4+ 才支持);
    权限被拒时给恢复路径,不再调 requestPermission。

测试

scripts/check-webpush.mjs(新,13 组):RFC 向量、记录头结构、独立解密器往返
(空 / 短 / 中文 / 4096 / 4097 字节)、本地假推送服务断言 Authorization / TTL /
Content-Encoding 并解密正文、endpoint 校验、410 删订阅、文件权限与 20 / 5 上限、
三件合并成一条、失败文案不含地址、页面与 SW 静态契约、不新增依赖。npm run check 全绿。

未验证:真实手机(安卓 / iOS)收到通知,以及真实 FCM / APNs 的互通。这两条只能在
真机上点一次「发一条测试通知」才算走通。


Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md

dsh-in-hand v1.8.16

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 30 Oct 05:32

dsh-in-hand v1.8.16

为什么改(2026-10-30)

v1.8.15 的通知主通道是飞书,而「有事情等你」最常发生在手机上。个人微信没有官方 API,
所以这一版把 Server酱 的公众号消息接成一条正经通道:用户只需要一个 SendKey。飞书
通道原样保留,行为不改(这次只加一种通道类型)。

通道与请求形状

  • 新类型 serverchan:用户只粘贴 SendKey(^[A-Za-z0-9_-]{10,64}$,不假设 32 位固定
    长度
    ),内部拼 https://sctapi.ftqq.com/<SENDKEY>.send,POST
    application/x-www-form-urlencoded,只有 title 与 desp 两个字段。
  • title 与飞书标题同口径(1 件事等你 / 3 件事等你,测试消息取首行冒号前那段),
    超过 32 字自己截断并补 … —— 微信通知栏会截标题,不能交给它。
  • desp 仍是摘要:工具名 + 原因前 30 字,多条只列前 3 条 + 「还有 N 件」;不含完整
    命令、路径与会话标题以外的内容。
  • 超时 8 秒、最多 3 次(退避 1s / 3s);失败审计只写一行 notify failed,不含 key。
  • 只存 SendKey,不存完整 URL;对外只出 SCT…abcd(首 3 + 末 4)。配置里若出现
    type:'serverchan' + url,一律忽略 URL、只认 key;只有 URL 没有 key 的条目直接丢弃。

设置页

  • 类型从两项变三项:飞书机器人 / Server酱(推到微信) / 通用 webhook。
  • 选 Server酱 时输入框 label 改「SendKey」,下方一行小字给扫码入口
    (在 sct.ftqq.com 微信扫码登录后可复制 SendKey)与「摘要会经过 Server酱」的克制说明,
    不弹窗、不用红字。
  • 列表徽标「Server酱」、掩码 SendKey、启用 / 关闭 / 测试 / 删除,与飞书一致;
    「会收到什么」补一行「Server酱 会在微信里以公众号消息形式出现」。
  • 页脚写清:个人微信没有官方 API,这条走 Server酱 的公众号消息;要完全私有可走企业微信
    应用消息(一句提及,不展开)。

测试

scripts/check-notify.mjs 追加 5 组:请求路径 /<key>.send + form + 只有 title/desp +
标题 32 字截断;SendKey 校验(abc / SCT123 被拒,SCT + 12 位通过);页面与接口都不
出现完整 SendKey;配置里忽略 url 只认 key;关闭后零请求。npm run check 全绿。

未验证:真实 Server酱 发送(仓库里没有真实 SendKey)。用户自己粘贴 SendKey 后点
「发一条测试消息」才算这条路真正走通。


Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md

dsh-in-hand v1.8.15

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 30 Oct 02:17

dsh-in-hand v1.8.15

为什么改(2026-10-30)

手机页要你主动看。开会、走路、睡前,你不打开就不知道有东西卡在那儿 —— 而
approval/request 与 user-questions/request 是有超时的,等看到时可能已经退回桌面。
这一版让宿主在有事情等你时往外推一条:飞书群机器人优先,另外收任意 JSON / 表单
webhook(Server 酱这一类)。推送是提醒,不是第二套审批入口:点开还是回到手机页处理。

触发与去重

  • 两个闸的 onPending 各多一次 notifyPoke();onRelease 也 poke 一次,用来在
    「等待清空」时重新武装。
  • 1.2 秒合并窗口(notify.debounceMs,200–10000 钳制):窗口内多次 poke 只发一条,
    同时出现三件就是 3 件事等你,不会连发三条。
  • 一次等待期间只推一条:armed=false 后不再排定时器,直到待办清空才复位。切换开关 /
    删除通道只 poke 不重新武装(避免反复刷屏);新增或启用通道会 refresh() 重新武装,
    让已经在等的事能送到刚配好的通道。
  • 没有启用通道时完全静默:不排定时器、不发请求、不写日志、不写审计。

正文口径

  • 单条:1 件事等你:<工具名> · <说明前 30 字>…;提问写成 提问 · <标题>。
  • 多条:标题 N 件事等你,只列前 3 条,末尾 还有 N 件。
  • 不含完整命令、对话正文、目录路径、会话 id —— 用一条带 rm -rf 的 reason 正面断言
    它不会被带出去(check-notify.mjs 第一组)。

通道与发送

  • channels 只存在于 ~/.dsh/taskwatch-notify.json({version:1, channels:[…]}),
    最多 8 条;插件配置里的 notify.channels 会被明确忽略并 warn —— 地址只能由用户
    在手机上粘贴,这一条优先于「配置里也能写 channels」的字面要求。
  • 写盘后立刻收紧权限:POSIX 0600;Windows icacls <file> /inheritance:r /grant:r <user>:F(best-effort,失败只 warn,不阻断保存)。
  • 发送:超时 8 秒,最多 3 次,退避 1s / 3s;只有 2xx 算成功。失败只写一行审计
    {kind:'notify', outcome:'failed', error:'notify failed', status},不含地址。
  • 载荷:飞书 {"msg_type":"text","content":{"text":…}};通用通道 {title, content}
    JSON,或 format:'form' 时的 title + desp 表单编码。

设置页(#notify)

  • 抽屉新增「通知」入口;整屏页依次是:现在会不会推(肯定句横幅,含「当前不会推送」)→
    通道列表(掩码地址、开关、发一条测试消息、两步确认删除)→ 会收到什么 → 添加通道。
  • 页面没有任何能展开完整地址的入口:列表只画宿主算好的 masked,响应里也没有 url
    字段;唯一的完整地址出现在添加时的输入框,保存成功即清空。
  • 「发一条测试消息」是真发,结果写「成功 · HTTP 200 · 0.4 秒」或失败原因 / HTTP 码;
    失败也是一次 200 的结果汇报,不是服务器错误。
  • 地址只收 https://;http:// 仅 127.0.0.1 / 10. / 192.168. / 172.16–31.;
    带 userinfo 的地址直接拒。错误文案固定,不回显地址。

测试与截图

  • 新增 scripts/check-notify.mjs(挂进 npm run check),七组:真实假 webhook 服务器
    收一条审批并断言形状与口径;三件合并成一条;全关时零请求;页面与响应的掩码断言;
    公网 http:// 被拒;500 时不抛错、审计写 notify failed、回执无 URL;通知文件权限
    (Windows 断言 icacls 调用,POSIX 断言 0600)。另含正文 / 校验 / 载荷 / 重试 /
    静默 / 去重的纯函数用例,以及「日志里不出现地址」的总闸。
  • scripts/check-bundle.mjs:/taskwatch/notify 归入只读、四条写路由归入非对话写,
    对话路由仍是 14 条。
  • scripts/make-demo.mjs:演示页带一个已配置的假飞书通道(只给掩码)和一个关闭的通用
    通道,format:'form';npm run demo 已重生成。
  • lib/sw.js 缓存号 taskwatch-v29 → v30;不升号时已安装的 PWA 会继续跑没有通知
    入口的旧外壳。
  • 新截图 docs/screenshots/current/notify.png(另有添加表单 / 测试成功 / 暗色三张在
    本地评审目录)。

兼容

  • 未改 lib/client.js、审批 / 提问失败的语义、票据与设备逻辑;DSH_TASKWATCH_ROOTS
    不变;没有新增依赖。
  • notify.enabled:false 关掉整块功能;默认 true,但没有通道时行为与之前完全一致。
  • package.json 版本 1.8.15,新增 shots:notify。

未验证

  • 真实飞书群机器人的消息外观(包括手机端换行与「还有 N 件」的显示)—— 没有可用凭据,
    只用假服务器校验了请求体形状。
  • 真实 Server 酱(form)转发到微信的效果;title / desp 字段按公开文档实现。
  • Windows 上 icacls 收紧后的实际 ACL:只断言了命令参数,没有在另一台机器上复核。

Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md

dsh-in-hand v1.8.14

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 30 Oct 01:51

dsh-in-hand v1.8.14

为什么改(2026-10-29)

v1.5.2 之后只打了 tag、没有建 GitHub Release:版本页停在 v1.5.2,安装说明与新版本对不上。
补档本身是二十多次 gh release create,手抄 CHANGELOG 段落容易漏、容易贴错版本,半自动脚本
又容易顺手把没核对过的版本发出去。这一版先交只读准备:一条命令列出「谁还没有 Release、
每个 tag 用哪段正文」,核对完再加两个开关补齐。

补档脚本(scripts/publish-releases.mjs)

  • node scripts/publish-releases.mjs [--from 1.8.0] [--to 1.8.13] [--apply] [--yes]
  • 默认 dry-run:只读 git tag 与 gh release list,逐个 tag 打印 create /
    skip(已存在) / missing(无 CHANGELOG 段落);每行附带正文大小与生成后的标题行,
    正文由 scripts/release-notes.mjs --out 写到临时目录、读完即删,不重复它的取段逻辑。
  • --from/--to 含端点,按语义化版本比较(不是字符串比较);输出从新到旧。
  • 浅克隆(CI 默认 depth=1)里本地 tag 不全时,用只读的 git ls-remote --tags 补齐,
    不 fetch、不改 .git。
  • --apply 才执行 gh release create <tag> --title "dsh-in-hand <tag>" --notes-file <临时文件> --verify-tag;
    已有 Release 的 tag 跳过,重复跑不会建第二次。
  • 三道防误发:默认只读;必须显式 --apply;--apply 还必须带 --yes 且 stdin/stdout
    都是交互终端,缺一条就打印提示后退出(退出码 3),一个 Release 都不建。
  • 任一条 missing:dry-run 退出码 2;--apply 直接中止,且不创建任何 Release。
  • 新增 scripts/check-publish-releases.mjs 并挂进 npm run check:五组断言(默认只读、
    假 gh 的 apply、缺 --yes/非终端拒绝、区间与语义化比较、缺段落中止)。测试里的所有
    gh 调用都指向临时目录里的假 gh,只记录参数,不联网、不碰真实 Release。
  • docs/RELEASING.md 增「补发历史 Release」一节,并写明这一步会对外发布、执行前需要用户同意。

兼容

  • 未改 lib/、.github/workflows/*、client.js;scripts/ 本来就不进 npm 包,
    files 白名单未变。
  • package.json 版本 1.8.14,scripts 增加 release:publish。

未验证

  • 真实 gh release create 的线上效果:按要求没有执行 --apply,等用户点头后再补。
  • GitHub Release 页面上的实际排版(本地只验证了正文来源、参数与幂等分支)。

Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md

dsh-in-hand v1.8.13

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 30 Oct 01:51

dsh-in-hand v1.8.13

为什么改(2026-10-29)

每发一版都要手写 GitHub Release 正文:从 CHANGELOG 复制段落、再补安装命令和 changelog
链接,顺手就会漏一段或贴错版本号。这一版把它交给脚本:Release 正文从 CHANGELOG.md
的那一段直接生成,人只负责把 CHANGELOG 写清楚。

生成器(scripts/release-notes.mjs)

  • node scripts/release-notes.mjs [--version 1.8.13] [--out file.md] [--check];
    不带 --version 时取 package.json 的 version,npm run release:notes 是同一个入口。
  • 取段:从 ## v1.8.13 — 标题 到下一个 ## 级别标题或文件末尾;### 子标题属于该段,
    代码围栏里的 ## 也不算标题,段落不会被截断。正文原样保留 Markdown 结构,只收首尾空行。
  • 输出固定三段:## dsh-in-hand v1.8.13 标题、CHANGELOG 该段正文、--- 之后的
    Install: 与 Full changelog: 两行。包名与仓库地址都从 package.json 读,不写死。
  • --out 写 UTF-8 无 BOM、LF 行尾的文件;不给就打到 stdout。
  • --check 校验该版本段落存在,且标题行的版本号等于 package.json;任一不满足退出码 1,
    报错里同时点名两边版本(版本不存在时还会列出 CHANGELOG 里现有的几个)。
  • 新增 scripts/check-release-notes.mjs 并挂进 npm run check:五组断言 —— 当前版本
    能生成、段落逐行不丢且至少 3 个该段关键词、--check 的两条失败路径、临时文件里的
    ### 样例不截断、输出文件前 3 字节无 BOM 且不含 \r\n。
  • 新增 docs/RELEASING.md:发版四步,一段一条命令。

兼容

  • 未改 lib/ 下任何文件,未改 .github/workflows/*,未动 client.js;生成器只读
    CHANGELOG.md 与 package.json,不参与运行时,files 白名单也没变(scripts/
    本来就不进 npm 包)。
  • package.json 版本 1.8.13,scripts 增加 release:notes。

未验证

  • GitHub Release 页面上真实粘贴后的排版(本地只验证了 Markdown 源与输出字节)。

Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md

dsh-in-hand v1.8.12

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 30 Oct 01:51

dsh-in-hand v1.8.12

为什么改(2026-10-29)

多台机器各有各的 /taskwatch。以前换一台就要重新记地址、重新配对,而认证本身
并不是"一个账号":它是每个源各自的 cookie(tw / twd),浏览器只把 cookie
发给发下它的那个源。所以客户端缺的不是令牌同步,只是一份地址清单 ——
切换 = 跳到另一个源的 /taskwatch,落地时那个源自己会认你。

页面(lib/page.html)

  • 抽屉「操作」区新增「入口 · 当前名」一行(地球图标),点开是整屏 #entries
    (与 #devices / #parallel 同一种导航深度与返回手势)。
  • 每行:名称(可空,默认用 host)、「当前」标记(与 location.origin 比对)、
    等宽地址一行;操作是「打开 / 编辑 / 删除」。打开是 target="_blank" +
    rel="noopener noreferrer" + referrerpolicy="no-referrer",不泄露 referrer。
  • 名称就地编辑(不弹第二层);编辑态只让改名字,改地址等于换源,应该删了再加。
  • 底部「添加入口」:名称(≤24 字)+ 地址 + 校验错误。错误分四种:空地址、
    看不懂的地址、非 http(s)、已在列表里;满 8 条时另有上限文案。
  • 当前项只做「左侧 2px 细线 + 4% 淡底」,不是实心强调块;390 宽、明暗两套令牌、
    按钮 / 输入框 44px、prefers-reduced-motion 下去掉 120ms 底色过渡。

存储与规范化(只存地址)

  • localStorage['tw:entries'] = [{"label":"家里","url":"http://192.168.1.10:3099"}],
    只有 label 与 url 两个键;不写 token、cookie、配对码、k= 之类的任何东西。
  • 规范化:去尾斜杠、丢路径 / ? / #、协议与 host 小写、默认端口收干净
    (统一取 URL.origin);没写协议时按 http:// 补。
  • 拒绝:javascript: / data: / file: / ftp: 以及其它一切非 http(s)(白名单
    只有两项,不是黑名单);主机名缺失同样拒绝。
  • 去重按 origin,同一条只留一份;最多 8 条,超出的在加载时截断、添加入口时明确报错。
  • 默认项:首次进入且存储里没有这份清单时,用当前 location.origin 生成一条
    「本机」。已经存在(哪怕用户把它删成 [])就不再自动添加,否则删不干净。

兼容与测试

  • lib/sw.js 的 CACHE 升到 taskwatch-v29(新整屏与抽屉入口都在外壳里);
    docs/demo.html 已重建。
  • scripts/check-entries.mjs 在原有宿主路由契约之后新增 P3 客户端验收
    (真 Chrome + CDP,DPR2,390 宽,16 项 / 7 组):默认项与当前标记、尾斜杠与
    同源去重、三种非 http(s) 拒绝、第 9 条上限、存储只有 label/url 且整份
    localStorage 无 token/cookie/k=、删除与改名同步、390 宽无横向溢出与
    44px 命中区、长地址只在行内截断。
  • 新增 scripts/shoot-entries.mjs(npm run shots:entries):entries.png、
    entries-add.png、entries-dark.png 三张验收图(另留一张 entries-drawer.png
    证明抽屉里的当前入口名),存 _ui-review/round19/;主图同步到
    docs/screenshots/current/entries.png 供 README 引用。
  • 未改 lib/index.js、lib/client.js,未改 DSH_TASKWATCH_ROOTS;
    package.json 版本 1.8.12。

未验证

  • 真机在多入口之间来回切换的体感(切过去后手动返回、再切回来的路径)。
  • iOS Safari 对 localStorage 的持久性(长期不用之后是否被清)。

Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md

dsh-in-hand v1.8.11

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 30 Oct 01:51

dsh-in-hand v1.8.11

为什么改(2026-10-29)

插件市场上非中文用户点进来只有一份中文 README。这一版加一份英文 README,
但不逐段翻译:只写清「这是什么、怎么装、安全边界、诚实的限制」,细节仍以中文
README 为准。代码零改动。

内容

  • 新增 README.en.md:定位一段(DSH 插件,把桌面会话搬到手机浏览器;明确写不是
    云服务、不是原生 App、不上传文件到第三方)+ 安装三步(装插件 → 打开页面 →
    可选配公网入口)+ 安全说明 + 5 条诚实的限制。
  • 两份 README 顶部互相切换:中文 [English](README.en.md) · 简体中文,英文
    English · [简体中文](README.md)。
  • 截图只用仓库里已存在的四张:phone-home.png、gui-taskwatch-entry.png、
    current/risk-card.png、current/approval-card.png;没有新增或重拍。
  • 测试:scripts/check-bundle.mjs 的 README 图片断言改为同时覆盖两份 README,
    并新增「两份 README 互链」与「英文版无中文残留(只允许项目名与语言切换行)」
    两条断言。
  • package.json 的 files 加入 README.en.md,版本 1.8.11。

未验证

  • 英文表达的地道程度(非母语审阅)。

Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md

dsh-in-hand v1.8.10

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 30 Oct 01:51

dsh-in-hand v1.8.10

为什么改(2026-10-29)

首页「在跑」只放三条,开四五个会话就要一页页翻;更糟的是失败的那条会被埋在
运行中下面。这一版加一个整屏的并行视图,只回答三个问题:谁在跑、谁卡住、
谁失败了
。核心不是多一个页面,而是排序 —— 失败的必须永远在运行中之上。

排序(本次交付的实质)

需要你(待批准 / 待回答) > 失败 > 运行中 > 已完成最近;同组按最后活动时间
倒序。实现只有一处:lib/page.html 的 PAR_GROUP 与 parItems()(先按
组号、再按 lastActivity 排),renderParallel() 按这个数组顺序把行
insertBefore 到正确位置。排序是分组而不是视觉强调:把红/黄都拿掉,
失败仍然排在运行中与空闲前面。

页面(lib/page.html)

  • 入口:首页「在跑」右上角「全部」+ 抽屉「并行视图」,整屏路由 #parallel
    (与 #devices 同一种导航深度与返回手势)。
  • 每行四个字段:标题(2 行截断)、状态徽标(需要你 / 失败 / 运行中 / 空闲)、
    一句最新动作(工具名 · 第几步)、已运行 / 结束时长;失败行多一句 ≤60 字
    原因 + 44px 的「查看」。字段就这四个,是刻意取舍:再多一行,390 宽首屏
    就放不下 3 条,这个视图又会退化成翻页。
  • 子代理:data.subagents(宿主已有投影,不新开接口)按 parent 建树,
    父会话下折一行「子代理 N」,展开只切 hidden,不重建;反复展开不重复渲染。
  • 实时:复用现有 5 秒轮询 / SSE。renderParallel() 按会话 id 复用 DOM,只在
    文本/状态真的变化时写,行需要换组时搬同一个节点而不是重画;页面隐藏时
    轮询与每秒时长计时一起停。
  • 空态:「现在没有在跑的会话」+「开一个新会话」(复用现有 openWsPicker)。
  • 移动端:390 宽首屏 ≥5 行、无横向溢出、标题 2 行、命中区 ≥44px、明暗两套
    全走既有令牌;prefers-reduced-motion 下去掉运行脉冲与箭头过渡。

宿主(lib/index.js)

只在 /taskwatch/data 的每个会话上补一个 failure 字段:读最后一条
turn/end 的 reason,error / max-tokens 给 {at, kind, reason},其余
(completed / aborted / interrupted / 还在跑)给 null。读事件有独立缓存
(按 seq 失效、最多回看 40 条),不扩大采集面;审批、问答、票据、设备账本、
文件白名单、上传与 DSH_TASKWATCH_ROOTS 均未改。

兼容与测试

  • lib/sw.js 的 CACHE 升到 taskwatch-v28;docs/demo.html 已重建
    (演示数据:1 需要你 / 1 失败 / 2 运行 / 1 空闲)。
  • 新增 scripts/check-parallel.mjs(真 Chrome + CDP,14 项 / 6 组):
    1. 注入 5 条会话,DOM 顺序严格为需要你 → 失败 → 运行中 → 空闲;
    2. 失败原因可见且 ≤60 字,「查看」≥44px;
    3. 子代理展开 / 收起,反复展开仍只有 2 条;
    4. 390 宽首屏 ≥3 行、无横向溢出、标题 2 行、命中区 ≥44px;
    5. 空态文案与「开一个新会话」按钮;
    6. 状态更新时其他行仍是原 DOM 节点、childList 变更数为 0。
  • 新增 scripts/shoot-parallel.mjs(真 Chrome/CDP、DPR2、390),截图落
    _ui-review/round18/,并把主图同步到 docs/screenshots/current/parallel.png。

Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md

dsh-in-hand v1.8.9

Choose a tag to compare

@dingchenhui0618-arch dingchenhui0618-arch released this 30 Oct 01:51

dsh-in-hand v1.8.9

为什么改(2026-10-29)

手机上最常要发的是「你过来看一眼」——屏幕上的报错码、白板、包装上的标签。打字描述
比拍一张慢一个数量级,而之前的附件流程要先点附件、再进抽屉、再选「上传图片」,
在单手拿着手机对着屏幕的时候是四步。这一版只在输入框旁加了一个相机键,其余全部
复用现有附件通道:没有新增接口、没有新增存储、没有改后端目录。

页面(lib/page.html)

  • 相机键 #camera(aria-label="拍照发送")背后是
    <input id="cameraInput" type="file" accept="image/*" capture="environment">。
    手机上进系统相机,桌面浏览器忽略 capture、退化成选图。不用 getUserMedia:
    那需要权限弹窗、前后摄像头切换和流的状态机,而这三种失败都不该出现在「发张照片」里。
  • 客户端压缩(canvas):长边 >1600 缩到 1600、JPEG 质量 0.82;压缩后 >2.5 MB 时
    降到 0.7 重试一次,仍超过就显示「图片太大」并不上传。方向用
    createImageBitmap(file, { imageOrientation:'from-image' }),EXIF Orientation 参与
    解码,竖拍照片不会横躺;createImageBitmap 不可用时退回 <img>(浏览器同样按
    image-orientation:from-image 解码)。
  • 选中即见:chip 立刻出现(原图预览 + 大小 + ×),状态依次是「压缩中…」→「上传中…」
    →「大小 · 已就绪」;压缩期间取消不发任何请求,上传期间取消会 abort 在途请求。
    失败保留缩略图,并按失败阶段给「重试」(压缩失败从原图重压,上传失败重发同一份)。
  • 上传与失败语义完全复用 uploadAttachment() / runUpload():同一个
    /taskwatch/chat/upload、同样的 sessionId/kind/name/mime 参数与
    content-type: application/octet-stream。发送时可只发图、不带文字。
  • 附件与相机两个键均 44×44、明暗两套走既有令牌、无新增动效(prefers-reduced-motion
    无需额外覆盖);390 宽实测输入框仍有 210px,页面不横向溢出。
  • 附件抽屉里原来的「上传图片」改名为「选择图片」(aria-label="选择图片"),
    说明里补一句「会先压到长边 1600」。
  • SW CACHE 升到 taskwatch-v27;docs/demo.html 已重建。

兼容与测试

  • lib/index.js、lib/client.js、文件白名单、票据、设备账本、审批 / 问答语义、
    DSH_TASKWATCH_ROOTS 均未改;lib/sw.js 只改版本号与注释。
  • 新增 scripts/check-camera.mjs(真 Chrome + CDP,36 项):
    1. DOM 有 input[accept="image/*"][capture="environment"] 与相机键;
    2. 注入 3000×2000 的图 → 从上传体 JPEG 的 SOF 断言长边 ≤1600、宽高比 3:2 不变;
    3. 注入 EXIF Orientation=6 的同一张图 → 输出宽 < 高(1067×1600),不横躺;
    4. 选中即见缩略图/大小/取消,压缩期间点 × → 上传请求数为 0;
    5. 宿主 500 → 保留缩略图并出现「重试」,点重试恢复;
    6. 390 宽 → 输入框 >0、无横向溢出、附件/相机两个键 ≥44px;
    7. 相机与「附件 → 选择图片」的上传请求路径、方法、Content-Type、参数集完全一致
      (图片只多一个 mime)。
      另外断言页面里只有一个 fetch('/taskwatch/chat/upload')、无 getUserMedia。
  • npm run check 全绿;新增 scripts/shoot-camera.mjs(npm run shots:camera)
    出 3 张真渲染截图(亮色已就绪 / 上传中 / 暗色),高清原图在 _ui-review/round17/,
    仓库内放在 docs/screenshots/current/。

未验证

  • 真机:iOS Safari 上 capture 的入口形态(是否直接进相机、返回时是否重载页面)、
    HEIC 照片的输入(Chrome 解不了 HEIC,会落到「图片处理失败:这张图读不出来」)。
  • 弱网:2.5 MB 级图片从「上传中…」到可发送的实际等待体感,只在本地假宿主上测过。
  • 桌面端 capture 被忽略后的行为只在 Chrome 上验证过,Safari 桌面未测。

Install: dsh plugin --profile web add dsh-in-hand
Full changelog: https://github.com/dingchenhui0618-arch/dsh-in-hand/blob/main/CHANGELOG.md