@twinsearth/w2m-dsh-plugin 1.9.19 → 1.9.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "The w2m fleet plugins: one account, many machines, any of Windows, macOS or Linux.",
8
- "version": "1.9.19"
8
+ "version": "1.9.20"
9
9
  },
10
10
  "plugins": [
11
11
  {
12
12
  "name": "w2m-fleet",
13
13
  "source": "./",
14
14
  "description": "Read fleet state and dispatch commands across the machines registered to one account. Read-only MCP surface: the five exposed tools cannot change anything.",
15
- "version": "1.9.19",
15
+ "version": "1.9.20",
16
16
  "author": {
17
17
  "name": "TwinsEarth"
18
18
  },
@@ -26,5 +26,5 @@
26
26
  ]
27
27
  }
28
28
  ],
29
- "version": "1.9.19"
29
+ "version": "1.9.20"
30
30
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "w2m-fleet",
3
3
  "description": "Drive the same project on online Windows, macOS and Linux machines from one account: read fleet state, dispatch commands and read results. The MCP surface is deliberately read-only.",
4
- "version": "1.9.19",
4
+ "version": "1.9.20",
5
5
  "author": {
6
6
  "name": "TwinsEarth"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@
7
7
 
8
8
  All notable changes to this project are documented here.
9
9
 
10
+ ## [1.9.20] - 2026-10-28
11
+
12
+ - 新增浮动滚动浮标(固定在主工作区右上角,不依赖 slot 布局):
13
+ · 「⇧3」:单击向上滚动约 3 轮对话;长按 3 秒直接回到第一轮;
14
+ · 「⤒」:一键到顶。
15
+ - 浮标挂载在插件生命周期内(ctx.effect 卸载时移除),失败只记录、不影响页面启动。
16
+
10
17
  ## [1.9.19] - 2026-10-28
11
18
 
12
19
  - 对话右上角新增「⇧3」按钮:单击向上滚动约 3 轮对话,长按 3 秒直接回到第一轮。
@@ -12,6 +12,6 @@
12
12
 
13
13
  [mcp_servers.w2m-fleet]
14
14
  command = "npx"
15
- args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@1.9.19", "w2m-mcp"]
15
+ args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@1.9.20", "w2m-mcp"]
16
16
  startup_timeout_sec = 20
17
17
  tool_timeout_sec = 60
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": "1.9.20",
3
+ "case": "RELEASE-1920",
4
+ "kind": "positive",
5
+ "machines": [
6
+ "b53a37f4-1464-4512-9259-33a7df091e3e"
7
+ ],
8
+ "executed_at": "2026-10-28T07:38:58.684Z",
9
+ "executor": "lead",
10
+ "task_id": "pre-release-1920-1793173138685",
11
+ "requirement": "V1.9.20:主工作区右上角浮动滚动浮标(⇧3 单击 3 轮/长按 3 秒到顶 + ⤒ 一键到顶)",
12
+ "result": {
13
+ "outcome": "ok",
14
+ "assertions": {
15
+ "float": "fixed top-right DOM controls installed from mount(), removed by ctx.effect",
16
+ "click": "scrollBy ~3 turns on the largest scrollable conversation surface",
17
+ "longpress": "pointerdown 3s -> scrollTo top",
18
+ "oneclick": "top button scrolls to first turn",
19
+ "tests": "client-bundle 26/26"
20
+ }
21
+ }
22
+ }
package/lib/client.js CHANGED
@@ -507,6 +507,9 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
507
507
  '.w2mSearchInput{height:24px;min-width:220px;border:0;background:transparent;color:inherit;font:inherit;outline:none}',
508
508
  '.w2mSearchCount{font-size:11px;opacity:.75;min-width:34px;text-align:center}',
509
509
  '.w2mSearchControls{display:inline-flex;align-items:center;gap:4px}',
510
+ '.w2mScrollFloatWrap{position:fixed;right:18px;top:72px;z-index:2147483000;display:flex;flex-direction:column;gap:6px}',
511
+ '.w2mScrollFloatBtn{width:34px;height:34px;border-radius:50%;border:1px solid currentColor;background:transparent;color:inherit;opacity:.72;font-size:13px;line-height:1;cursor:pointer}',
512
+ '.w2mScrollFloatBtn:hover{opacity:1}',
510
513
  '.w2mScrollUp{font-size:12px;padding:2px 6px;border:1px solid rgba(128,128,128,.4);border-radius:4px;background:transparent;color:inherit;cursor:pointer}',
511
514
  '::highlight(w2m-conversation-search){background-color:rgba(255,235,59,.85);color:rgba(11,122,51,1)}',
512
515
  '.w2mSearchMark{background-color:rgba(255,235,59,.85);color:rgba(11,122,51,1);border-radius:2px}',
@@ -968,21 +971,21 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
968
971
  * Custom Highlight API (no DOM mutation, so React's tree stays intact), then scrolls to the
969
972
  * focused match. Matches stay yellow until the query changes or the box closes.
970
973
  */
971
- function searchRoots() {
972
- const roots = [];
973
- const seen = new Set();
974
- const visit = (node) => {
975
- if (!node || seen.has(node)) return;
976
- seen.add(node);
977
- if (node.nodeType === 1 || node.nodeType === 11) {
978
- if (node.shadowRoot) { roots.push(node.shadowRoot); visit(node.shadowRoot); }
979
- const kids = node.children ?? [];
980
- for (let i = 0; i < kids.length; i += 1) visit(kids[i]);
981
- }
982
- };
983
- if (typeof document !== 'undefined' && document.body) { roots.push(document.body); visit(document.body); }
984
- return roots;
985
- }
974
+ function searchRoots() {
975
+ const roots = [];
976
+ const seen = new Set();
977
+ const visit = (node) => {
978
+ if (!node || seen.has(node)) return;
979
+ seen.add(node);
980
+ if (node.nodeType === 1 || node.nodeType === 11) {
981
+ if (node.shadowRoot) { roots.push(node.shadowRoot); visit(node.shadowRoot); }
982
+ const kids = node.children ?? [];
983
+ for (let i = 0; i < kids.length; i += 1) visit(kids[i]);
984
+ }
985
+ };
986
+ if (typeof document !== 'undefined' && document.body) { roots.push(document.body); visit(document.body); }
987
+ return roots;
988
+ }
986
989
  function clearSearchMarks() {
987
990
  if (typeof document === 'undefined') return;
988
991
  const marks = document.querySelectorAll('mark.w2mSearchMark');
@@ -1054,6 +1057,49 @@ function findScrollSurface() {
1054
1057
  }
1055
1058
  return best;
1056
1059
  }
1060
+ function scrollChatTurns() {
1061
+ const surface = findScrollSurface();
1062
+ if (surface !== null) surface.scrollBy({ top: -Math.round(Math.max(240, surface.clientHeight * 1.8)), behavior: 'smooth' });
1063
+ else if (typeof window !== 'undefined') window.scrollBy({ top: -Math.round(window.innerHeight * 1.8), behavior: 'smooth' });
1064
+ }
1065
+ function scrollChatTop() {
1066
+ const surface = findScrollSurface();
1067
+ if (surface !== null) surface.scrollTo({ top: 0, behavior: 'smooth' });
1068
+ else if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
1069
+ }
1070
+ /** Floating scroll controls: fixed top-right of the workspace, independent of any slot layout. */
1071
+ function installFloatingScrollButtons() {
1072
+ if (typeof document === 'undefined' || document.body === null) return null;
1073
+ const wrap = document.createElement('div');
1074
+ wrap.className = 'w2mScrollFloatWrap';
1075
+ wrap.setAttribute('data-w2m-scroll-float', 'true');
1076
+ const makeButton = (label, title) => {
1077
+ const button = document.createElement('button');
1078
+ button.type = 'button';
1079
+ button.className = 'w2mScrollFloatBtn';
1080
+ button.textContent = label;
1081
+ button.title = title;
1082
+ button.setAttribute('aria-label', title);
1083
+ wrap.appendChild(button);
1084
+ return button;
1085
+ };
1086
+ const up = makeButton('\u21E73', '向上滚动 3 轮(长按 3 秒到第一轮)');
1087
+ const top = makeButton('\u2912', '一键到顶');
1088
+ let holdTimer = null;
1089
+ let longFired = false;
1090
+ const clearHold = () => { if (holdTimer !== null) { clearTimeout(holdTimer); holdTimer = null; } };
1091
+ up.addEventListener('pointerdown', (event) => {
1092
+ if (event && typeof event.preventDefault === 'function') event.preventDefault();
1093
+ longFired = false;
1094
+ clearHold();
1095
+ holdTimer = setTimeout(() => { longFired = true; scrollChatTop(); }, 3000);
1096
+ });
1097
+ for (const type of ['pointerup', 'pointerleave', 'pointercancel']) up.addEventListener(type, clearHold);
1098
+ up.addEventListener('click', () => { if (longFired) { longFired = false; return; } scrollChatTurns(); });
1099
+ top.addEventListener('click', () => scrollChatTop());
1100
+ document.body.appendChild(wrap);
1101
+ return () => { clearHold(); wrap.remove(); };
1102
+ }
1057
1103
  function ConversationSearch() {
1058
1104
  const [open, setOpen] = React.useState(false);
1059
1105
  const [query, setQuery] = React.useState('');
@@ -2368,6 +2414,16 @@ const CHAT_EMOJIS = CHAT_COMMANDS;
2368
2414
  } catch (error) {
2369
2415
  console.error('@twinsearth/w2m-dsh-plugin: the conversation search control was not mounted', error);
2370
2416
  }
2417
+ // v1.9.20: floating scroll controls, fixed top-right of the workspace. DOM nodes, not slot
2418
+ // entries, so they appear whichever conversation view is active; ctx.effect disposes them.
2419
+ try {
2420
+ const disposeFloat = installFloatingScrollButtons();
2421
+ if (typeof disposeFloat === 'function' && typeof ctx.effect === 'function') {
2422
+ ctx.effect(() => disposeFloat, 'w2m: floating scroll buttons');
2423
+ }
2424
+ } catch (error) {
2425
+ console.error('@twinsearth/w2m-dsh-plugin: the floating scroll controls were not mounted', error);
2426
+ }
2371
2427
  // v1.9.6: fleet chat as a real conversation View tab (after Chat and Rules), rendering the
2372
2428
  // full chat page in the main workspace.
2373
2429
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "1.9.19",
3
+ "version": "1.9.20",
4
4
  "description": "Run one project across your own Windows and macOS machines from a single DeepSeek account, through 11 w2m_* tools. Four coordination modes (replicate, split, broadcast, pipeline) and six aggregate verdicts (consistent, divergent, divergent-platform, failed, partial, unverifiable). P2P hole punching by default, with a bundled relay, STUN responder and ledger fallback, so the fleet needs no third-party service. Joining needs a pairing code plus autoStartAgent: true; installing alone does not join.",
5
5
  "type": "module",
6
6
  "exports": {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "twinsearth-w2m-dsh",
4
- "version": "1.9.19",
4
+ "version": "1.9.20",
5
5
  "description": "Run one project across your own Windows and macOS machines from a single DeepSeek account, through 12 w2m_* tools. Four coordination modes (replicate, split, broadcast, pipeline) and six aggregate verdicts (consistent, divergent, divergent-platform, failed, partial, unverifiable). P2P hole punching by default, with a bundled relay, STUN responder and ledger fallback, so the fleet needs no third-party service. Joining needs a pairing code plus autoStartAgent: true; installing alone does not join.",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -286,7 +286,7 @@ const MAX_STAGES = 16;
286
286
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
287
287
  * overrides it at runtime for tests and packagers.
288
288
  */
289
- const PLUGIN_VERSION = '1.9.19';
289
+ const PLUGIN_VERSION = '1.9.20';
290
290
 
291
291
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
292
292
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;