@twinsearth/w2m-dsh-plugin 2.0.0 → 2.0.2
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.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +15 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/2.0.1/RELEASE-2010.json +22 -0
- package/docs/acceptance/2.0.2/RELEASE-2020.json +21 -0
- package/lib/client.js +119 -67
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/src/plugin/tools.mjs +1 -1
|
@@ -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": "2.0.
|
|
8
|
+
"version": "2.0.2"
|
|
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": "2.0.
|
|
15
|
+
"version": "2.0.2",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "2.0.
|
|
29
|
+
"version": "2.0.2"
|
|
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": "2.0.
|
|
4
|
+
"version": "2.0.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@
|
|
|
7
7
|
|
|
8
8
|
All notable changes to this project are documented here.
|
|
9
9
|
|
|
10
|
+
## [2.0.2] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
- 滚动按钮移到右下角,与 DSH 官方浮动滚动控件对齐(同右边缘、位于其上方;官方控件出现前后都会重新对齐)。
|
|
13
|
+
- 修复点击无反应:滚动容器改为“试滚探针”(scrollTop ±1 实际是否被浏览器接受)寻找,
|
|
14
|
+
不再依赖 overflow 样式;命中后直接写 scrollTop。
|
|
15
|
+
- 交互改为:点一次向上滚动约 3 轮;1.2 秒内连续点击 3 次回到第一轮;不再使用长按。
|
|
16
|
+
|
|
17
|
+
## [2.0.1] - 2026-10-28
|
|
18
|
+
|
|
19
|
+
- 修复右上角滚动按钮无效:滚动容器识别改为“命中测试 + 递归 shadow root + 允许 overflow:hidden 的程序化滚动容器”,
|
|
20
|
+
并直接写 scrollTop/scrollTo;找不到容器时回退到 window。旧实现只找 overflow:auto,虚拟列表/隐藏溢出容器下点击是空操作。
|
|
21
|
+
- 只保留一个滚动图标(SVG 向上箭头,样式与右上角官方图标对齐:32px、8px 圆角、1px 边框、right:16px/top:64px),
|
|
22
|
+
删除原来的第二个“一键到顶”按钮与会话头部的重复“⇧3”按钮。
|
|
23
|
+
- 交互:点一次向上滚动约 3 轮;长按 3 秒直接回到第一轮(长按后松手的 click 不会重复触发)。
|
|
24
|
+
|
|
10
25
|
## [2.0.0] - 2026-10-28
|
|
11
26
|
|
|
12
27
|
主插件与既有 Relay 的对接按《W2M Relay × ToFuture 外部对接 API v1.2》收敛;ToFuture 子插件按该文档
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.1",
|
|
3
|
+
"case": "RELEASE-2010",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-28T09:49:39.826Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "pre-release-2010-1793180979827",
|
|
11
|
+
"requirement": "v2.0.1:右上角单个滚动图标,点击滚动 3 轮、长按 3 秒到顶,图标与官方一键到顶对齐,修复点击无效",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"single_icon": "one SVG button; second top button and header duplicate removed",
|
|
16
|
+
"interaction": "click -> scrollTop -1.8*clientHeight; long press 3s -> top; click after long press suppressed",
|
|
17
|
+
"resolver": "elementFromPoint ancestors + shadow roots + overflow hidden + scrollTop direct write + window fallback",
|
|
18
|
+
"alignment": "right:16px, top:64px, 32x32, radius 8px, 1px border",
|
|
19
|
+
"tests": "client-bundle 26/26, lint 0 errors"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.2",
|
|
3
|
+
"case": "RELEASE-2020",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-28T10:09:24.639Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "pre-release-2020-1793182164640",
|
|
11
|
+
"requirement": "v2.0.2:滚动按钮移至右下与官方控件对齐;探针式滚动容器识别修复点击无效;改为点击 3 轮、连续 3 次到顶",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"position": "fixed bottom-right, aligned above the shell floating scroll control",
|
|
16
|
+
"resolver": "probe scrollTop ±1 acceptance; no overflow-style dependency",
|
|
17
|
+
"interaction": "click 1/2 scroll 3 turns; third click within 1.2s jumps to top",
|
|
18
|
+
"tests": "client-bundle 26/26, lint 0 errors"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -507,10 +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:
|
|
511
|
-
'.w2mScrollFloatBtn{width:
|
|
510
|
+
'.w2mScrollFloatWrap{position:fixed;right:16px;bottom:132px;z-index:2147483000;display:flex;align-items:center;justify-content:center}',
|
|
511
|
+
'.w2mScrollFloatBtn{width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:50%;border:1px solid var(--dsw-alias-border-l2, rgba(128,128,128,.4));background:var(--dsw-alias-background-secondary, transparent);color:inherit;opacity:.8;line-height:1;cursor:pointer;padding:0;box-shadow:0 1px 4px rgba(0,0,0,.12)}',
|
|
512
512
|
'.w2mScrollFloatBtn:hover{opacity:1}',
|
|
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}',
|
|
514
513
|
'::highlight(w2m-conversation-search){background-color:rgba(255,235,59,.85);color:rgba(11,122,51,1)}',
|
|
515
514
|
'.w2mSearchMark{background-color:rgba(255,235,59,.85);color:rgba(11,122,51,1);border-radius:2px}',
|
|
516
515
|
'.w2mSearchBtn{height:24px;min-width:24px;border:0;border-radius:4px;background:transparent;color:inherit;cursor:pointer}',
|
|
@@ -1041,64 +1040,143 @@ function paintSearchMarks(query) {
|
|
|
1041
1040
|
}
|
|
1042
1041
|
return total;
|
|
1043
1042
|
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Probe-scroll: assign a tiny scrollTop and see whether the browser keeps it.
|
|
1045
|
+
*
|
|
1046
|
+
* This is the only reliable way to find the conversation surface: overflow styles lie (a shell can
|
|
1047
|
+
* scroll an overflow:hidden element), and a virtualised list may report equal scrollHeight and
|
|
1048
|
+
* clientHeight until it is scrolled. The original scrollTop is restored immediately.
|
|
1049
|
+
*/
|
|
1050
|
+
function isScrollableElement(el) {
|
|
1051
|
+
if (el === null || el === undefined || el === document.body || el === document.documentElement) return false;
|
|
1052
|
+
if (typeof el.scrollTop !== 'number' || typeof el.scrollHeight !== 'number' || typeof el.clientHeight !== 'number') return false;
|
|
1053
|
+
if (el.scrollHeight <= el.clientHeight + 8) return false;
|
|
1054
|
+
const rect = typeof el.getBoundingClientRect === 'function' ? el.getBoundingClientRect() : null;
|
|
1055
|
+
if (rect !== null && (rect.width < 200 || rect.height < 120)) return false;
|
|
1056
|
+
const before = el.scrollTop;
|
|
1057
|
+
try {
|
|
1058
|
+
el.scrollTop = before + 1;
|
|
1059
|
+
if (el.scrollTop !== before) { el.scrollTop = before; return true; }
|
|
1060
|
+
el.scrollTop = Math.max(0, before - 1);
|
|
1061
|
+
if (el.scrollTop !== before) { el.scrollTop = before; return true; }
|
|
1062
|
+
el.scrollTop = before;
|
|
1063
|
+
return false;
|
|
1064
|
+
} catch {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1044
1068
|
function findScrollSurface() {
|
|
1045
1069
|
if (typeof document === 'undefined') return null;
|
|
1046
|
-
|
|
1070
|
+
const found = new Set();
|
|
1071
|
+
const addAncestors = (node) => {
|
|
1072
|
+
let current = node;
|
|
1073
|
+
while (current !== null && current !== undefined) {
|
|
1074
|
+
if (isScrollableElement(current)) { found.add(current); return; }
|
|
1075
|
+
current = current.parentElement ?? (current.parentNode && current.parentNode.host ? current.parentNode.host : null);
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
if (typeof document.elementFromPoint === 'function' && typeof window !== 'undefined') {
|
|
1079
|
+
const points = [[window.innerWidth / 2, window.innerHeight / 2], [window.innerWidth / 2, window.innerHeight * 0.7], [window.innerWidth * 0.7, window.innerHeight * 0.4]];
|
|
1080
|
+
for (const point of points) {
|
|
1081
|
+
try { addAncestors(document.elementFromPoint(point[0], point[1])); } catch { /* ignore */ }
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1047
1084
|
for (const root of searchRoots()) {
|
|
1048
1085
|
let all = [];
|
|
1049
1086
|
try { all = root.querySelectorAll ? [...root.querySelectorAll('*')] : []; } catch { continue; }
|
|
1050
|
-
for (const el of all)
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1087
|
+
for (const el of all) if (isScrollableElement(el)) found.add(el);
|
|
1088
|
+
}
|
|
1089
|
+
let best = null;
|
|
1090
|
+
let bestArea = 0;
|
|
1091
|
+
for (const el of found) {
|
|
1092
|
+
const rect = typeof el.getBoundingClientRect === 'function' ? el.getBoundingClientRect() : null;
|
|
1093
|
+
const area = rect === null ? 0 : rect.width * rect.height;
|
|
1094
|
+
if (best === null || area > bestArea) { best = el; bestArea = area; }
|
|
1057
1095
|
}
|
|
1058
1096
|
return best;
|
|
1059
1097
|
}
|
|
1060
1098
|
function scrollChatTurns() {
|
|
1061
1099
|
const surface = findScrollSurface();
|
|
1062
|
-
|
|
1063
|
-
|
|
1100
|
+
const viewport = typeof window !== 'undefined' ? window.innerHeight : 800;
|
|
1101
|
+
const step = Math.round(Math.max(240, (surface !== null ? surface.clientHeight : viewport) * 1.8));
|
|
1102
|
+
if (surface !== null) surface.scrollTop = Math.max(0, surface.scrollTop - step);
|
|
1103
|
+
else if (typeof window !== 'undefined') window.scrollBy({ top: -step, behavior: 'smooth' });
|
|
1064
1104
|
}
|
|
1065
1105
|
function scrollChatTop() {
|
|
1066
1106
|
const surface = findScrollSurface();
|
|
1067
|
-
if (surface !== null) surface.
|
|
1107
|
+
if (surface !== null) surface.scrollTop = 0;
|
|
1068
1108
|
else if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1069
1109
|
}
|
|
1070
|
-
/**
|
|
1071
|
-
function
|
|
1110
|
+
/** One circular button, aligned with the shell's own floating scroll control. */
|
|
1111
|
+
function installFloatingScrollControl() {
|
|
1072
1112
|
if (typeof document === 'undefined' || document.body === null) return null;
|
|
1073
1113
|
const wrap = document.createElement('div');
|
|
1074
1114
|
wrap.className = 'w2mScrollFloatWrap';
|
|
1075
1115
|
wrap.setAttribute('data-w2m-scroll-float', 'true');
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1116
|
+
const button = document.createElement('button');
|
|
1117
|
+
button.type = 'button';
|
|
1118
|
+
button.className = 'w2mScrollFloatBtn';
|
|
1119
|
+
button.title = '向上滚动 3 轮(连续点击 3 次回到第一轮)';
|
|
1120
|
+
button.setAttribute('aria-label', button.title);
|
|
1121
|
+
button.addEventListener('contextmenu', (event) => event.preventDefault());
|
|
1122
|
+
const ns = 'http://www.w3.org/2000/svg';
|
|
1123
|
+
const svg = document.createElementNS(ns, 'svg');
|
|
1124
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
1125
|
+
svg.setAttribute('width', '18');
|
|
1126
|
+
svg.setAttribute('height', '18');
|
|
1127
|
+
svg.setAttribute('fill', 'none');
|
|
1128
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
1129
|
+
svg.setAttribute('stroke-width', '2');
|
|
1130
|
+
svg.setAttribute('stroke-linecap', 'round');
|
|
1131
|
+
svg.setAttribute('stroke-linejoin', 'round');
|
|
1132
|
+
const shaft = document.createElementNS(ns, 'path');
|
|
1133
|
+
shaft.setAttribute('d', 'M12 19V5');
|
|
1134
|
+
const head = document.createElementNS(ns, 'path');
|
|
1135
|
+
head.setAttribute('d', 'M5 12l7-7 7 7');
|
|
1136
|
+
svg.appendChild(shaft);
|
|
1137
|
+
svg.appendChild(head);
|
|
1138
|
+
button.appendChild(svg);
|
|
1139
|
+
let clicks = 0;
|
|
1140
|
+
let resetTimer = null;
|
|
1141
|
+
button.addEventListener('click', () => {
|
|
1142
|
+
clicks += 1;
|
|
1143
|
+
if (resetTimer !== null) clearTimeout(resetTimer);
|
|
1144
|
+
if (clicks >= 3) { clicks = 0; scrollChatTop(); return; }
|
|
1145
|
+
scrollChatTurns();
|
|
1146
|
+
resetTimer = setTimeout(() => { clicks = 0; resetTimer = null; }, 1200);
|
|
1096
1147
|
});
|
|
1097
|
-
|
|
1098
|
-
up.addEventListener('click', () => { if (longFired) { longFired = false; return; } scrollChatTurns(); });
|
|
1099
|
-
top.addEventListener('click', () => scrollChatTop());
|
|
1148
|
+
wrap.appendChild(button);
|
|
1100
1149
|
document.body.appendChild(wrap);
|
|
1101
|
-
|
|
1150
|
+
// Align with the shell's own floating scroll control when it is present: same right edge, just
|
|
1151
|
+
// above it. Re-run on resize because the comparison is against a live bounding rect.
|
|
1152
|
+
const align = () => {
|
|
1153
|
+
let official = null;
|
|
1154
|
+
try {
|
|
1155
|
+
const candidates = [...document.querySelectorAll('button, [role="button"]')];
|
|
1156
|
+
official = candidates.find((el) => {
|
|
1157
|
+
const label = String(el.getAttribute('aria-label') ?? el.getAttribute('title') ?? '');
|
|
1158
|
+
const rect = el.getBoundingClientRect();
|
|
1159
|
+
return /滚动到底部|滚动到顶部|回到底部|back to bottom|scroll to bottom/i.test(label) && rect.width > 0 && rect.height > 0;
|
|
1160
|
+
}) ?? null;
|
|
1161
|
+
} catch { official = null; }
|
|
1162
|
+
if (official === null) {
|
|
1163
|
+
wrap.style.right = '16px';
|
|
1164
|
+
wrap.style.bottom = '132px';
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const rect = official.getBoundingClientRect();
|
|
1168
|
+
wrap.style.right = Math.max(8, Math.round(window.innerWidth - rect.right)) + 'px';
|
|
1169
|
+
wrap.style.bottom = Math.max(8, Math.round(window.innerHeight - rect.top + 8)) + 'px';
|
|
1170
|
+
};
|
|
1171
|
+
align();
|
|
1172
|
+
for (const delay of [120, 600, 1500]) setTimeout(align, delay);
|
|
1173
|
+
const onResize = () => align();
|
|
1174
|
+
if (typeof window !== 'undefined') window.addEventListener('resize', onResize);
|
|
1175
|
+
return () => {
|
|
1176
|
+
if (resetTimer !== null) clearTimeout(resetTimer);
|
|
1177
|
+
if (typeof window !== 'undefined') window.removeEventListener('resize', onResize);
|
|
1178
|
+
wrap.remove();
|
|
1179
|
+
};
|
|
1102
1180
|
}
|
|
1103
1181
|
function ConversationSearch() {
|
|
1104
1182
|
const [open, setOpen] = React.useState(false);
|
|
@@ -1106,8 +1184,6 @@ function ConversationSearch() {
|
|
|
1106
1184
|
const [count, setCount] = React.useState(0);
|
|
1107
1185
|
const [cursor, setCursor] = React.useState(-1);
|
|
1108
1186
|
const inputRef = React.useRef(null);
|
|
1109
|
-
const holdRef = React.useRef(null);
|
|
1110
|
-
const longFiredRef = React.useRef(false);
|
|
1111
1187
|
React.useEffect(() => { if (open && inputRef.current && inputRef.current.focus) inputRef.current.focus(); }, [open]);
|
|
1112
1188
|
React.useEffect(() => {
|
|
1113
1189
|
if (!open) { clearSearchMarks(); setCount(0); setCursor(-1); return undefined; }
|
|
@@ -1125,32 +1201,8 @@ function ConversationSearch() {
|
|
|
1125
1201
|
setCursor(next);
|
|
1126
1202
|
if (marks[next] && marks[next].scrollIntoView) marks[next].scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
1127
1203
|
};
|
|
1128
|
-
const scrollTurns = () => {
|
|
1129
|
-
const surface = findScrollSurface();
|
|
1130
|
-
if (surface !== null) surface.scrollBy({ top: -Math.round(Math.max(240, surface.clientHeight * 1.8)), behavior: 'smooth' });
|
|
1131
|
-
else if (typeof window !== 'undefined') window.scrollBy({ top: -Math.round(window.innerHeight * 1.8), behavior: 'smooth' });
|
|
1132
|
-
};
|
|
1133
|
-
const scrollFirstTurn = () => {
|
|
1134
|
-
const surface = findScrollSurface();
|
|
1135
|
-
if (surface !== null) surface.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1136
|
-
else if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1137
|
-
};
|
|
1138
1204
|
if (!open) {
|
|
1139
1205
|
return h('span', { className: 'w2mSearchControls' }, [
|
|
1140
|
-
h('button', {
|
|
1141
|
-
key: 'up',
|
|
1142
|
-
type: 'button',
|
|
1143
|
-
className: 'w2mScrollUp',
|
|
1144
|
-
title: '向上滚动 3 轮(长按 3 秒到第一轮)',
|
|
1145
|
-
'aria-label': '向上滚动 3 轮,长按 3 秒到第一轮',
|
|
1146
|
-
onMouseDown: () => {
|
|
1147
|
-
longFiredRef.current = false;
|
|
1148
|
-
holdRef.current = setTimeout(() => { longFiredRef.current = true; scrollFirstTurn(); }, 3000);
|
|
1149
|
-
},
|
|
1150
|
-
onMouseUp: () => { if (holdRef.current) { clearTimeout(holdRef.current); holdRef.current = null; } },
|
|
1151
|
-
onMouseLeave: () => { if (holdRef.current) { clearTimeout(holdRef.current); holdRef.current = null; } },
|
|
1152
|
-
onClick: () => { if (longFiredRef.current) { longFiredRef.current = false; return; } scrollTurns(); },
|
|
1153
|
-
}, '⇧3'),
|
|
1154
1206
|
h('button', {
|
|
1155
1207
|
key: 'find',
|
|
1156
1208
|
type: 'button',
|
|
@@ -2417,7 +2469,7 @@ const CHAT_EMOJIS = CHAT_COMMANDS;
|
|
|
2417
2469
|
// v1.9.20: floating scroll controls, fixed top-right of the workspace. DOM nodes, not slot
|
|
2418
2470
|
// entries, so they appear whichever conversation view is active; ctx.effect disposes them.
|
|
2419
2471
|
try {
|
|
2420
|
-
const disposeFloat =
|
|
2472
|
+
const disposeFloat = installFloatingScrollControl();
|
|
2421
2473
|
if (typeof disposeFloat === 'function' && typeof ctx.effect === 'function') {
|
|
2422
2474
|
ctx.effect(() => disposeFloat, 'w2m: floating scroll buttons');
|
|
2423
2475
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
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": "2.0.
|
|
4
|
+
"version": "2.0.2",
|
|
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": [
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -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 = '2.0.
|
|
289
|
+
const PLUGIN_VERSION = '2.0.2';
|
|
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;
|