@twinsearth/w2m-dsh-plugin 2.0.1 → 2.0.3
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 +17 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/2.0.2/RELEASE-2020.json +21 -0
- package/docs/acceptance/2.0.3/RELEASE-2030.json +22 -0
- package/lib/client.js +132 -48
- 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.3"
|
|
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.3",
|
|
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.3"
|
|
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.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,23 @@
|
|
|
7
7
|
|
|
8
8
|
All notable changes to this project are documented here.
|
|
9
9
|
|
|
10
|
+
## [2.0.3] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
- 滚动按钮移到会话正文列右上角(黑框位置):按官方浮动圆钮规格 34px、无边框、elevation 阴影、
|
|
13
|
+
label-primary 图标色(亮色显示为黑色,暗色自动反白)、hover/active/focus-visible 状态。
|
|
14
|
+
- 位置由会话滚动区实时计算(正文列右缘 + 顶部内缩 8px),窗口缩放与侧栏变化时重新对齐。
|
|
15
|
+
- 关键修复:自动完成 DSH 官方“加载更早”——会话是虚拟列表,不点“加载更早”时 scrollTop 到顶即止,
|
|
16
|
+
这是之前“点击无效/无法到顶”的真正原因。现在滚动前若已在顶部会先点一次“加载更早”等待 350ms,
|
|
17
|
+
“连续点击 3 次到顶”会循环点击“加载更早”直到它消失,再把滚动区钉在顶部。
|
|
18
|
+
- 交互:点一次向上滚动约 3 轮;1.2 秒内连续点击 3 次回到第一轮。
|
|
19
|
+
|
|
20
|
+
## [2.0.2] - 2026-10-28
|
|
21
|
+
|
|
22
|
+
- 滚动按钮移到右下角,与 DSH 官方浮动滚动控件对齐(同右边缘、位于其上方;官方控件出现前后都会重新对齐)。
|
|
23
|
+
- 修复点击无反应:滚动容器改为“试滚探针”(scrollTop ±1 实际是否被浏览器接受)寻找,
|
|
24
|
+
不再依赖 overflow 样式;命中后直接写 scrollTop。
|
|
25
|
+
- 交互改为:点一次向上滚动约 3 轮;1.2 秒内连续点击 3 次回到第一轮;不再使用长按。
|
|
26
|
+
|
|
10
27
|
## [2.0.1] - 2026-10-28
|
|
11
28
|
|
|
12
29
|
- 修复右上角滚动按钮无效:滚动容器识别改为“命中测试 + 递归 shadow root + 允许 overflow:hidden 的程序化滚动容器”,
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.3",
|
|
3
|
+
"case": "RELEASE-2030",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-28T10:35:33.753Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "pre-release-2030-1793183733754",
|
|
11
|
+
"requirement": "v2.0.3:滚动按钮移到会话区右上黑框位置并变黑;自动点击“加载更早”解除虚拟列表阻断;点击 3 轮、连续 3 次到顶",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"position": "conversation column top-right, 8px inset from surface top, right edge aligned",
|
|
16
|
+
"style": "34px circle, no border, elevation shadow, label-primary icon via theme tokens",
|
|
17
|
+
"load_earlier": "auto-click before scrolling at top and repeatedly during scroll-to-top",
|
|
18
|
+
"interaction": "click 3 turns; 3 clicks within 1.2s to top",
|
|
19
|
+
"tests": "client-bundle 26/26, lint 0 errors"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -507,9 +507,12 @@ 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:16px;
|
|
511
|
-
'.w2mScrollFloatBtn{width:
|
|
512
|
-
'.w2mScrollFloatBtn:hover{
|
|
510
|
+
'.w2mScrollFloatWrap{position:fixed;top:120px;right:16px;bottom:auto;left:auto;z-index:20;display:flex;align-items:center;justify-content:center;pointer-events:none}',
|
|
511
|
+
'.w2mScrollFloatBtn{box-sizing:border-box;width:34px;height:34px;padding:0;border:0;border-radius:100px;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-button-floating-fill);box-shadow:0 0 0 .5px var(--dsw-alias-border-l3,rgba(0,0,0,.12)),0 3px 8px 0 rgba(0,0,0,.031),0 0 16px 0 rgba(0,0,0,.02);opacity:1;line-height:1;cursor:pointer;pointer-events:auto;transition:background-color 150ms ease,transform 150ms ease}',
|
|
512
|
+
'.w2mScrollFloatBtn:hover{background:var(--dsw-alias-button-floating-hover)}',
|
|
513
|
+
'.w2mScrollFloatBtn:active{background:var(--dsw-alias-button-ghost-active-fill);transform:scale(.96)}',
|
|
514
|
+
'.w2mScrollFloatBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}',
|
|
515
|
+
'.w2mScrollFloatBtn svg{display:block;width:16px;height:16px;stroke-width:1.5}',
|
|
513
516
|
'::highlight(w2m-conversation-search){background-color:rgba(255,235,59,.85);color:rgba(11,122,51,1)}',
|
|
514
517
|
'.w2mSearchMark{background-color:rgba(255,235,59,.85);color:rgba(11,122,51,1);border-radius:2px}',
|
|
515
518
|
'.w2mSearchBtn{height:24px;min-width:24px;border:0;border-radius:4px;background:transparent;color:inherit;cursor:pointer}',
|
|
@@ -1040,13 +1043,30 @@ function paintSearchMarks(query) {
|
|
|
1040
1043
|
}
|
|
1041
1044
|
return total;
|
|
1042
1045
|
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Probe-scroll: assign a tiny scrollTop and see whether the browser keeps it.
|
|
1048
|
+
*
|
|
1049
|
+
* Overflow styles lie (a shell can scroll an overflow:hidden element), and a virtualised list may
|
|
1050
|
+
* report equal scrollHeight and clientHeight until it is scrolled. The original scrollTop is
|
|
1051
|
+
* restored immediately.
|
|
1052
|
+
*/
|
|
1043
1053
|
function isScrollableElement(el) {
|
|
1044
1054
|
if (el === null || el === undefined || el === document.body || el === document.documentElement) return false;
|
|
1045
|
-
if (typeof el.scrollHeight !== 'number' || typeof el.clientHeight !== 'number') return false;
|
|
1055
|
+
if (typeof el.scrollTop !== 'number' || typeof el.scrollHeight !== 'number' || typeof el.clientHeight !== 'number') return false;
|
|
1046
1056
|
if (el.scrollHeight <= el.clientHeight + 8) return false;
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1057
|
+
const rect = typeof el.getBoundingClientRect === 'function' ? el.getBoundingClientRect() : null;
|
|
1058
|
+
if (rect !== null && (rect.width < 200 || rect.height < 120)) return false;
|
|
1059
|
+
const before = el.scrollTop;
|
|
1060
|
+
try {
|
|
1061
|
+
el.scrollTop = before + 1;
|
|
1062
|
+
if (el.scrollTop !== before) { el.scrollTop = before; return true; }
|
|
1063
|
+
el.scrollTop = Math.max(0, before - 1);
|
|
1064
|
+
if (el.scrollTop !== before) { el.scrollTop = before; return true; }
|
|
1065
|
+
el.scrollTop = before;
|
|
1066
|
+
return false;
|
|
1067
|
+
} catch {
|
|
1068
|
+
return false;
|
|
1069
|
+
}
|
|
1050
1070
|
}
|
|
1051
1071
|
function findScrollSurface() {
|
|
1052
1072
|
if (typeof document === 'undefined') return null;
|
|
@@ -1055,49 +1075,81 @@ function findScrollSurface() {
|
|
|
1055
1075
|
let current = node;
|
|
1056
1076
|
while (current !== null && current !== undefined) {
|
|
1057
1077
|
if (isScrollableElement(current)) { found.add(current); return; }
|
|
1058
|
-
current = current.parentElement ?? (current.parentNode
|
|
1078
|
+
current = current.parentElement ?? (current.parentNode && current.parentNode.host ? current.parentNode.host : null);
|
|
1059
1079
|
}
|
|
1060
1080
|
};
|
|
1061
1081
|
if (typeof document.elementFromPoint === 'function' && typeof window !== 'undefined') {
|
|
1062
|
-
const points = [[window.innerWidth / 2, window.innerHeight / 2], [window.innerWidth / 2, window.innerHeight * 0.
|
|
1082
|
+
const points = [[window.innerWidth / 2, window.innerHeight / 2], [window.innerWidth / 2, window.innerHeight * 0.7], [window.innerWidth * 0.7, window.innerHeight * 0.4]];
|
|
1063
1083
|
for (const point of points) {
|
|
1064
1084
|
try { addAncestors(document.elementFromPoint(point[0], point[1])); } catch { /* ignore */ }
|
|
1065
1085
|
}
|
|
1066
1086
|
}
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
for (const el of all) if (isScrollableElement(el)) found.add(el);
|
|
1072
|
-
}
|
|
1087
|
+
for (const root of searchRoots()) {
|
|
1088
|
+
let all = [];
|
|
1089
|
+
try { all = root.querySelectorAll ? [...root.querySelectorAll('*')] : []; } catch { continue; }
|
|
1090
|
+
for (const el of all) if (isScrollableElement(el)) found.add(el);
|
|
1073
1091
|
}
|
|
1074
1092
|
let best = null;
|
|
1093
|
+
let bestArea = 0;
|
|
1075
1094
|
for (const el of found) {
|
|
1076
|
-
|
|
1095
|
+
const rect = typeof el.getBoundingClientRect === 'function' ? el.getBoundingClientRect() : null;
|
|
1096
|
+
const area = rect === null ? 0 : rect.width * rect.height;
|
|
1097
|
+
if (best === null || area > bestArea) { best = el; bestArea = area; }
|
|
1077
1098
|
}
|
|
1078
1099
|
return best;
|
|
1079
1100
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1101
|
+
/**
|
|
1102
|
+
* The shell virtualises history: scrolling up stops at the loaded window and only the "load
|
|
1103
|
+
* earlier" row fetches more. A scroll control that ignores that row can never reach the top, which
|
|
1104
|
+
* is exactly why the previous version looked like a no-op. Find and press it before scrolling.
|
|
1105
|
+
*/
|
|
1106
|
+
function findLoadEarlierButton() {
|
|
1107
|
+
if (typeof document === 'undefined') return null;
|
|
1108
|
+
for (const root of searchRoots()) {
|
|
1109
|
+
let nodes = [];
|
|
1110
|
+
try { nodes = root.querySelectorAll ? [...root.querySelectorAll('button, [role="button"], a, span, div')] : []; } catch { continue; }
|
|
1111
|
+
for (const el of nodes) {
|
|
1112
|
+
const text = String(el.textContent ?? '').trim();
|
|
1113
|
+
if (text === '' || text.length > 24) continue;
|
|
1114
|
+
if (!/加载更早|加载更多|更早|load earlier|load more/i.test(text)) continue;
|
|
1115
|
+
const rect = typeof el.getBoundingClientRect === 'function' ? el.getBoundingClientRect() : null;
|
|
1116
|
+
if (rect === null || rect.width < 8 || rect.height < 8) continue;
|
|
1117
|
+
if (typeof el.click === 'function') return el;
|
|
1118
|
+
}
|
|
1088
1119
|
}
|
|
1089
|
-
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
function clickLoadEarlier() {
|
|
1123
|
+
const el = findLoadEarlierButton();
|
|
1124
|
+
if (el === null) return false;
|
|
1125
|
+
try { el.click(); return true; } catch { return false; }
|
|
1090
1126
|
}
|
|
1091
|
-
|
|
1127
|
+
const waitFor = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); });
|
|
1128
|
+
async function scrollChatTurns() {
|
|
1092
1129
|
const surface = findScrollSurface();
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1130
|
+
const viewport = typeof window !== 'undefined' ? window.innerHeight : 800;
|
|
1131
|
+
const step = Math.round(Math.max(240, (surface !== null ? surface.clientHeight : viewport) * 1.8));
|
|
1132
|
+
if (surface !== null && surface.scrollTop <= 8 && clickLoadEarlier()) await waitFor(350);
|
|
1133
|
+
const after = surface ?? findScrollSurface();
|
|
1134
|
+
if (after !== null) after.scrollTop = Math.max(0, after.scrollTop - step);
|
|
1135
|
+
else if (typeof window !== 'undefined') window.scrollBy({ top: -step, behavior: 'smooth' });
|
|
1136
|
+
}
|
|
1137
|
+
async function scrollChatTop() {
|
|
1138
|
+
// Load history until the "load earlier" row is gone, then pin the surface to the top.
|
|
1139
|
+
for (let i = 0; i < 60; i += 1) {
|
|
1140
|
+
const surface = findScrollSurface();
|
|
1141
|
+
if (surface !== null) surface.scrollTop = 0;
|
|
1142
|
+
if (!clickLoadEarlier()) break;
|
|
1143
|
+
await waitFor(280);
|
|
1144
|
+
}
|
|
1145
|
+
for (let i = 0; i < 8; i += 1) {
|
|
1146
|
+
const surface = findScrollSurface();
|
|
1147
|
+
if (surface !== null) surface.scrollTop = 0;
|
|
1148
|
+
else if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1149
|
+
await waitFor(70);
|
|
1097
1150
|
}
|
|
1098
|
-
if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1099
1151
|
}
|
|
1100
|
-
/** One
|
|
1152
|
+
/** One black circular button, anchored to the conversation column's top-right corner. */
|
|
1101
1153
|
function installFloatingScrollControl() {
|
|
1102
1154
|
if (typeof document === 'undefined' || document.body === null) return null;
|
|
1103
1155
|
const wrap = document.createElement('div');
|
|
@@ -1106,17 +1158,15 @@ function installFloatingScrollControl() {
|
|
|
1106
1158
|
const button = document.createElement('button');
|
|
1107
1159
|
button.type = 'button';
|
|
1108
1160
|
button.className = 'w2mScrollFloatBtn';
|
|
1109
|
-
button.title = '向上滚动 3
|
|
1110
|
-
button.setAttribute('aria-label',
|
|
1161
|
+
button.title = '向上滚动 3 轮(连续点击 3 次回到第一轮)';
|
|
1162
|
+
button.setAttribute('aria-label', '向上滚动');
|
|
1163
|
+
button.setAttribute('aria-description', '单次点击向上滚动三轮,连续点击三次回到第一轮');
|
|
1111
1164
|
button.addEventListener('contextmenu', (event) => event.preventDefault());
|
|
1112
1165
|
const ns = 'http://www.w3.org/2000/svg';
|
|
1113
1166
|
const svg = document.createElementNS(ns, 'svg');
|
|
1114
1167
|
svg.setAttribute('viewBox', '0 0 24 24');
|
|
1115
|
-
svg.setAttribute('width', '18');
|
|
1116
|
-
svg.setAttribute('height', '18');
|
|
1117
1168
|
svg.setAttribute('fill', 'none');
|
|
1118
1169
|
svg.setAttribute('stroke', 'currentColor');
|
|
1119
|
-
svg.setAttribute('stroke-width', '2');
|
|
1120
1170
|
svg.setAttribute('stroke-linecap', 'round');
|
|
1121
1171
|
svg.setAttribute('stroke-linejoin', 'round');
|
|
1122
1172
|
const shaft = document.createElementNS(ns, 'path');
|
|
@@ -1126,20 +1176,54 @@ function installFloatingScrollControl() {
|
|
|
1126
1176
|
svg.appendChild(shaft);
|
|
1127
1177
|
svg.appendChild(head);
|
|
1128
1178
|
button.appendChild(svg);
|
|
1129
|
-
let
|
|
1130
|
-
let
|
|
1131
|
-
const
|
|
1132
|
-
button.addEventListener('
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1179
|
+
let clicks = 0;
|
|
1180
|
+
let resetTimer = null;
|
|
1181
|
+
const reset = () => { if (resetTimer !== null) { clearTimeout(resetTimer); resetTimer = null; } clicks = 0; };
|
|
1182
|
+
button.addEventListener('click', () => {
|
|
1183
|
+
clicks += 1;
|
|
1184
|
+
if (resetTimer !== null) clearTimeout(resetTimer);
|
|
1185
|
+
if (clicks >= 3) { reset(); void scrollChatTop(); return; }
|
|
1186
|
+
void scrollChatTurns();
|
|
1187
|
+
resetTimer = setTimeout(reset, 1200);
|
|
1137
1188
|
});
|
|
1138
|
-
for (const type of ['pointerup', 'pointerleave', 'pointercancel']) button.addEventListener(type, clearHold);
|
|
1139
|
-
button.addEventListener('click', () => { if (longFired) { longFired = false; return; } scrollChatTurns(); });
|
|
1140
1189
|
wrap.appendChild(button);
|
|
1141
1190
|
document.body.appendChild(wrap);
|
|
1142
|
-
|
|
1191
|
+
const place = () => {
|
|
1192
|
+
const surface = findScrollSurface();
|
|
1193
|
+
if (surface === null) {
|
|
1194
|
+
wrap.style.top = '120px';
|
|
1195
|
+
wrap.style.right = '16px';
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
const rect = surface.getBoundingClientRect();
|
|
1199
|
+
wrap.style.top = Math.max(8, Math.round(rect.top + 8)) + 'px';
|
|
1200
|
+
wrap.style.right = Math.max(12, Math.round(window.innerWidth - rect.right)) + 'px';
|
|
1201
|
+
};
|
|
1202
|
+
place();
|
|
1203
|
+
for (const delay of [120, 600, 1500]) setTimeout(place, delay);
|
|
1204
|
+
window.addEventListener('resize', place);
|
|
1205
|
+
let observer = null;
|
|
1206
|
+
let observed = null;
|
|
1207
|
+
const observe = () => {
|
|
1208
|
+
if (typeof ResizeObserver !== 'function') return;
|
|
1209
|
+
const surface = findScrollSurface();
|
|
1210
|
+
if (surface === observed) return;
|
|
1211
|
+
if (observer !== null) observer.disconnect();
|
|
1212
|
+
observed = surface;
|
|
1213
|
+
observer = new ResizeObserver(() => place());
|
|
1214
|
+
if (surface !== null) observer.observe(surface);
|
|
1215
|
+
};
|
|
1216
|
+
observe();
|
|
1217
|
+
const reObserve = setTimeout(observe, 800);
|
|
1218
|
+
const poll = setInterval(place, 1000);
|
|
1219
|
+
return () => {
|
|
1220
|
+
reset();
|
|
1221
|
+
clearTimeout(reObserve);
|
|
1222
|
+
clearInterval(poll);
|
|
1223
|
+
window.removeEventListener('resize', place);
|
|
1224
|
+
if (observer !== null) observer.disconnect();
|
|
1225
|
+
wrap.remove();
|
|
1226
|
+
};
|
|
1143
1227
|
}
|
|
1144
1228
|
function ConversationSearch() {
|
|
1145
1229
|
const [open, setOpen] = React.useState(false);
|
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.3",
|
|
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.3",
|
|
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.3';
|
|
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;
|