@twinsearth/w2m-dsh-plugin 1.9.17 → 1.9.19
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 +6 -0
- package/README.md +3 -3
- package/bin/w2m-joint.mjs +1 -1
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/1.9.19/RELEASE-1919.json +21 -0
- package/docs/acceptance-ops/2026-10-28/AG-SMOKE.json +24 -0
- package/lib/client.js +185 -105
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/src/agent/agent.mjs +6 -1
- package/src/agent/sinks/dir-sink.mjs +59 -13
- package/src/plugin/fleet-chat-route.mjs +7 -1
- package/src/plugin/joint-dev/audit-file.mjs +70 -37
- package/src/plugin/joint-dev/merge-engine.mjs +102 -0
- package/src/plugin/joint-dev/relay-dispatch.mjs +103 -21
- package/src/plugin/tools-joint.mjs +97 -0
- package/src/plugin/tools.mjs +5 -2
- package/src/relay/fleet-messages.mjs +22 -0
- package/src/relay/report.mjs +35 -0
- package/src/relay/server.mjs +2043 -2038
- package/src/relay/state.mjs +11 -0
- package/src/util/artifact-ref.mjs +88 -0
- package/src/util/redline-core.mjs +2 -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": "1.9.
|
|
8
|
+
"version": "1.9.19"
|
|
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.
|
|
15
|
+
"version": "1.9.19",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "1.9.
|
|
29
|
+
"version": "1.9.19"
|
|
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.
|
|
4
|
+
"version": "1.9.19",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
All notable changes to this project are documented here.
|
|
9
9
|
|
|
10
|
+
## [1.9.19] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
- 对话右上角新增「⇧3」按钮:单击向上滚动约 3 轮对话,长按 3 秒直接回到第一轮。
|
|
13
|
+
- 全文搜索高亮修复:改为直接插入本地 mark 元素(黄色背景 rgba(255,235,59,.85) + 绿色文字 rgba(11,122,51,1)),并递归遍历 shadow DOM;不再依赖 CSS Custom Highlight API,命中项一定可见。
|
|
14
|
+
- 同版带上 v1.9.18 的聊天收紧:relay 只接收文字与 /微笑 /火箭 /烟花 指令(图片字段、data:image、Markdown 图片、base64 图片魔数一律 400 拒绝;加密消息按设计豁免);表情按钮改为指令按钮;长提示词镜像按 3500 B 截断,避免被 4096 B 上限静默丢弃。
|
|
15
|
+
|
|
10
16
|
## [1.9.17] - 2026-10-28
|
|
11
17
|
|
|
12
18
|
- 修复左侧栏 GPW 入口只剩小图标:恢复 🌐 GPW 文字(侧栏 label 仍为空,不会重复)。
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ English | [中文](#中文说明)
|
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
|
-
Five routes in. They all end with **
|
|
17
|
+
Five routes in. They all end with **18 tools registered** in your agent. Pick one.
|
|
18
18
|
|
|
19
19
|
### 1. DSH plugin (recommended — zero configuration)
|
|
20
20
|
|
|
@@ -136,7 +136,7 @@ hand-maintained host list.
|
|
|
136
136
|
### Tools
|
|
137
137
|
|
|
138
138
|
`w2m_status` · `w2m_devices` · `w2m_run` · `w2m_wait` · `w2m_history` · `w2m_stats` · `w2m_report` ·
|
|
139
|
-
`w2m_memory` · `w2m_approvals` · `w2m_cancel` · `w2m_council` · `w2m_chat` · `w2m_update` `w2m_tasks` · `w2m_reduce`
|
|
139
|
+
`w2m_memory` · `w2m_approvals` · `w2m_cancel` · `w2m_council` · `w2m_chat` · `w2m_update` `w2m_tasks` · `w2m_reduce` · `w2m_joint_plan` · `w2m_joint_status`
|
|
140
140
|
- `w2m_intent` — map a structured intent (run-tests, node-version, git-status, git-head, fleet-facts, shard-probe) onto an allow-listed command; unknown intents are refused by name, never guessed.
|
|
141
141
|
|
|
142
142
|
### Environment a dispatched command receives (1.1.15+)
|
|
@@ -261,7 +261,7 @@ MIT.
|
|
|
261
261
|
|
|
262
262
|
### 安装
|
|
263
263
|
|
|
264
|
-
五种方式,最终都会在你的 agent 里注册 **
|
|
264
|
+
五种方式,最终都会在你的 agent 里注册 **18 tools registered**。任选其一。
|
|
265
265
|
|
|
266
266
|
#### 1. DSH 插件(推荐,零配置)
|
|
267
267
|
|
package/bin/w2m-joint.mjs
CHANGED
|
@@ -163,7 +163,7 @@ if (command === 'dispatch') {
|
|
|
163
163
|
// decision 2: one approval authorises exactly one dispatch.
|
|
164
164
|
const consumed = consumeApproval({ stateDir, planSha: args.plan, approvalSha: gate.approval.approval.approval_sha256 });
|
|
165
165
|
if (consumed.ok !== true) fail(consumed.code ?? 'APPROVAL_CONSUME_FAILED', consumed.detail ?? null);
|
|
166
|
-
const sent = await dispatchPlanToRelay({ rabbitUrl: relayUrl, token, plan: gate.plan, approval: gate.approval.approval });
|
|
166
|
+
const sent = await dispatchPlanToRelay({ rabbitUrl: relayUrl, token, plan: gate.plan, approval: gate.approval.approval, auditFile: stateDir + '/joint-dev/audit.jsonl' });
|
|
167
167
|
print({ ...sent, plan_sha256: gate.plan.plan_sha256, relay: relayUrl });
|
|
168
168
|
process.exit(sent.ok === true ? 0 : 1);
|
|
169
169
|
}
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.9.19",
|
|
3
|
+
"case": "RELEASE-1919",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-28T05:08:27.700Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "pre-release-1919-1793164107702",
|
|
11
|
+
"requirement": "V1.9.19:对话向上滚动按钮(点击 3 轮/长按 3 秒到首轮)+ 搜索命中绿字黄底(DOM mark,含 shadow DOM)",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"scroll": "header ⇧3: click scrollBy 3 turns, hold 3s scrollTo top",
|
|
16
|
+
"search": "mark.w2mSearchMark with rgba yellow background + rgba green text, shadow-root aware",
|
|
17
|
+
"chat": "text-only guard retained from v1.9.18 work",
|
|
18
|
+
"tests": "client-bundle 26/26"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "ops-smoke",
|
|
3
|
+
"case": "AG-SMOKE",
|
|
4
|
+
"executed_at": "2026-10-28T05:02:36.913Z",
|
|
5
|
+
"executor": "lead",
|
|
6
|
+
"task_id": "01M60E2W38FFKXKRZS95JSVWFM",
|
|
7
|
+
"setup": "relay 43.142.74.71:8787 upgraded to 1.9.16; operator token used from ~/.w2m/operator-token; read via local device token",
|
|
8
|
+
"attempt": "broadcast executor_machine_id=ToFuture command_argv=[\"node\",\"-v\"] at 2.0.0-tree relay",
|
|
9
|
+
"observed": {
|
|
10
|
+
"dispatch": "POST /v1/task 200; executor lease on ToFuture=offered; DSH/macmini and 15 other devices=observing",
|
|
11
|
+
"final": "aggregate=refused; ToFuture lease=refused (agent allow-list), outcome=refused; observers did not execute"
|
|
12
|
+
},
|
|
13
|
+
"conclusions": {
|
|
14
|
+
"selection_routing": "PASS at relay level: only the named executor gets an executing lease; every other device is observing",
|
|
15
|
+
"execution": "NOT PROVEN: the target agent refused the command (allow-list)",
|
|
16
|
+
"ag_matrix": "PARTIAL: A/B/C/D routing smoke only; E/F/G and real execution blocked"
|
|
17
|
+
},
|
|
18
|
+
"blockers": [
|
|
19
|
+
"agent allow-list does not include node -v on ToFuture",
|
|
20
|
+
"running DSH process still predates the operator-token change (restart required)",
|
|
21
|
+
"macmini/DSH plugin versions unknown/unverified; same-version requirement not yet met",
|
|
22
|
+
"S4 data plane (agent putBlobBytes reroute + no-sink degradation) not implemented"
|
|
23
|
+
]
|
|
24
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -495,6 +495,8 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
495
495
|
// v1.6.10: the transcript is selectable text; screenshots get a preview and a copy affordance.
|
|
496
496
|
'.w2mChatTab{padding:4px 10px;border-radius:6px;border:1px solid rgba(127,127,127,.35);background:transparent;color:inherit;font:inherit;cursor:pointer}',
|
|
497
497
|
'.w2mChat_panelFull{position:fixed;inset:5vh 8vw;width:auto;max-height:90vh;z-index:70}',
|
|
498
|
+
'.w2mChat_sticker{font-size:16px;line-height:1}',
|
|
499
|
+
'.w2mChat_stickerBig{font-size:28px;line-height:1.1}',
|
|
498
500
|
'.w2mChat_emojiRow{display:flex;gap:4px;padding:4px 8px;flex-wrap:wrap}',
|
|
499
501
|
'.w2mChat_emoji{padding:2px 4px;border:0;background:transparent;font-size:16px;line-height:1;cursor:pointer}',
|
|
500
502
|
'.w2mFleetStatus{display:flex;gap:16px;align-items:center;padding:2px 12px;font-size:11px;opacity:.8;white-space:nowrap}',
|
|
@@ -504,6 +506,8 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
504
506
|
'.w2mSearchBox{display:flex;align-items:center;gap:4px;padding:2px 4px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-background-secondary)}',
|
|
505
507
|
'.w2mSearchInput{height:24px;min-width:220px;border:0;background:transparent;color:inherit;font:inherit;outline:none}',
|
|
506
508
|
'.w2mSearchCount{font-size:11px;opacity:.75;min-width:34px;text-align:center}',
|
|
509
|
+
'.w2mSearchControls{display:inline-flex;align-items:center;gap:4px}',
|
|
510
|
+
'.w2mScrollUp{font-size:12px;padding:2px 6px;border:1px solid rgba(128,128,128,.4);border-radius:4px;background:transparent;color:inherit;cursor:pointer}',
|
|
507
511
|
'::highlight(w2m-conversation-search){background-color:rgba(255,235,59,.85);color:rgba(11,122,51,1)}',
|
|
508
512
|
'.w2mSearchMark{background-color:rgba(255,235,59,.85);color:rgba(11,122,51,1);border-radius:2px}',
|
|
509
513
|
'.w2mSearchBtn{height:24px;min-width:24px;border:0;border-radius:4px;background:transparent;color:inherit;cursor:pointer}',
|
|
@@ -964,112 +968,173 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
964
968
|
* Custom Highlight API (no DOM mutation, so React's tree stays intact), then scrolls to the
|
|
965
969
|
* focused match. Matches stay yellow until the query changes or the box closes.
|
|
966
970
|
*/
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
try {
|
|
1023
|
-
const parent = ranges[0].startContainer.parentElement;
|
|
1024
|
-
if (parent && parent.scrollIntoView) parent.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
1025
|
-
} catch { /* scroll is best effort */ }
|
|
1026
|
-
} else setCursor(-1);
|
|
1027
|
-
return ranges.length;
|
|
1028
|
-
}, [collect]);
|
|
1029
|
-
React.useEffect(() => {
|
|
1030
|
-
if (!open) { clearSearchHighlight(); setCount(0); setCursor(-1); return undefined; }
|
|
1031
|
-
paint();
|
|
1032
|
-
return () => clearSearchHighlight();
|
|
1033
|
-
}, [open, query, paint]);
|
|
1034
|
-
const step = (backwards) => {
|
|
1035
|
-
const ranges = rangesRef.current;
|
|
1036
|
-
if (ranges.length === 0) { paint(); return; }
|
|
1037
|
-
const next = (cursor + (backwards ? -1 : 1) + ranges.length) % ranges.length;
|
|
1038
|
-
setCursor(next);
|
|
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
|
+
}
|
|
986
|
+
function clearSearchMarks() {
|
|
987
|
+
if (typeof document === 'undefined') return;
|
|
988
|
+
const marks = document.querySelectorAll('mark.w2mSearchMark');
|
|
989
|
+
for (const mark of marks) {
|
|
990
|
+
const parent = mark.parentNode;
|
|
991
|
+
if (parent === null) continue;
|
|
992
|
+
parent.replaceChild(document.createTextNode(mark.textContent ?? ''), mark);
|
|
993
|
+
if (parent.normalize) parent.normalize();
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function paintSearchMarks(query) {
|
|
997
|
+
clearSearchMarks();
|
|
998
|
+
if (typeof document === 'undefined' || query === '') return 0;
|
|
999
|
+
const needle = query.toLowerCase();
|
|
1000
|
+
let total = 0;
|
|
1001
|
+
for (const root of searchRoots()) {
|
|
1002
|
+
let walker;
|
|
1003
|
+
try {
|
|
1004
|
+
walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
1005
|
+
acceptNode(node) {
|
|
1006
|
+
const parent = node.parentElement;
|
|
1007
|
+
if (parent === null) return NodeFilter.FILTER_REJECT;
|
|
1008
|
+
const tag = parent.tagName;
|
|
1009
|
+
if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'TEXTAREA' || tag === 'INPUT') return NodeFilter.FILTER_REJECT;
|
|
1010
|
+
if (parent.closest && parent.closest('[data-w2m-search]') !== null) return NodeFilter.FILTER_REJECT;
|
|
1011
|
+
if (parent.closest && parent.closest('mark.w2mSearchMark') !== null) return NodeFilter.FILTER_REJECT;
|
|
1012
|
+
return node.nodeValue && node.nodeValue.trim() !== '' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
1013
|
+
},
|
|
1014
|
+
});
|
|
1015
|
+
} catch { continue; }
|
|
1016
|
+
const nodes = [];
|
|
1017
|
+
let node = walker.nextNode();
|
|
1018
|
+
while (node !== null) { nodes.push(node); node = walker.nextNode(); }
|
|
1019
|
+
for (const textNode of nodes) {
|
|
1020
|
+
const text = textNode.nodeValue ?? '';
|
|
1021
|
+
const lower = text.toLowerCase();
|
|
1022
|
+
const hits = [];
|
|
1023
|
+
let index = lower.indexOf(needle);
|
|
1024
|
+
while (index >= 0) { hits.push(index); index = lower.indexOf(needle, index + query.length); }
|
|
1025
|
+
for (let k = hits.length - 1; k >= 0; k -= 1) {
|
|
1039
1026
|
try {
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
'aria-label': '搜索本对话',
|
|
1050
|
-
onClick: () => setOpen(true),
|
|
1051
|
-
}, '🔍');
|
|
1027
|
+
const range = document.createRange();
|
|
1028
|
+
range.setStart(textNode, hits[k]);
|
|
1029
|
+
range.setEnd(textNode, hits[k] + query.length);
|
|
1030
|
+
const mark = document.createElement('mark');
|
|
1031
|
+
mark.className = 'w2mSearchMark';
|
|
1032
|
+
mark.setAttribute('style', 'background-color:rgba(255,235,59,.85);color:rgba(11,122,51,1)');
|
|
1033
|
+
range.surroundContents(mark);
|
|
1034
|
+
total += 1;
|
|
1035
|
+
} catch { /* a range that crosses elements cannot be wrapped; skip it */ }
|
|
1052
1036
|
}
|
|
1053
|
-
return h('div', { className: 'w2mSearchBox', 'data-w2m-search': 'box' }, [
|
|
1054
|
-
h('input', {
|
|
1055
|
-
key: 'q',
|
|
1056
|
-
ref: inputRef,
|
|
1057
|
-
className: 'w2mSearchInput',
|
|
1058
|
-
value: query,
|
|
1059
|
-
placeholder: '搜索本对话全部内容',
|
|
1060
|
-
onChange: (event) => setQuery(event.target.value),
|
|
1061
|
-
onKeyDown: (event) => {
|
|
1062
|
-
if (event.key === 'Enter') { event.preventDefault(); step(event.shiftKey); }
|
|
1063
|
-
if (event.key === 'Escape') { setOpen(false); setQuery(''); }
|
|
1064
|
-
},
|
|
1065
|
-
}),
|
|
1066
|
-
h('span', { key: 'count', className: 'w2mSearchCount' }, count === 0 ? (query === '' ? '' : '0') : whenCount(count, cursor)),
|
|
1067
|
-
h('button', { key: 'prev', type: 'button', className: 'w2mSearchBtn', title: '上一个', onClick: () => step(true) }, '↑'),
|
|
1068
|
-
h('button', { key: 'next', type: 'button', className: 'w2mSearchBtn', title: '下一个', onClick: () => step(false) }, '↓'),
|
|
1069
|
-
h('button', { key: 'close', type: 'button', className: 'w2mSearchBtn', title: '关闭搜索', onClick: () => { setOpen(false); setQuery(''); clearSearchHighlight(); } }, '×'),
|
|
1070
|
-
]);
|
|
1071
1037
|
}
|
|
1072
|
-
|
|
1038
|
+
}
|
|
1039
|
+
return total;
|
|
1040
|
+
}
|
|
1041
|
+
function findScrollSurface() {
|
|
1042
|
+
if (typeof document === 'undefined') return null;
|
|
1043
|
+
let best = null;
|
|
1044
|
+
for (const root of searchRoots()) {
|
|
1045
|
+
let all = [];
|
|
1046
|
+
try { all = root.querySelectorAll ? [...root.querySelectorAll('*')] : []; } catch { continue; }
|
|
1047
|
+
for (const el of all) {
|
|
1048
|
+
if (el.scrollHeight <= el.clientHeight + 40) continue;
|
|
1049
|
+
const style = typeof getComputedStyle === 'function' ? getComputedStyle(el) : null;
|
|
1050
|
+
const overflow = style === null ? '' : style.overflowY;
|
|
1051
|
+
if (overflow !== 'auto' && overflow !== 'scroll') continue;
|
|
1052
|
+
if (best === null || el.scrollHeight > best.scrollHeight) best = el;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return best;
|
|
1056
|
+
}
|
|
1057
|
+
function ConversationSearch() {
|
|
1058
|
+
const [open, setOpen] = React.useState(false);
|
|
1059
|
+
const [query, setQuery] = React.useState('');
|
|
1060
|
+
const [count, setCount] = React.useState(0);
|
|
1061
|
+
const [cursor, setCursor] = React.useState(-1);
|
|
1062
|
+
const inputRef = React.useRef(null);
|
|
1063
|
+
const holdRef = React.useRef(null);
|
|
1064
|
+
const longFiredRef = React.useRef(false);
|
|
1065
|
+
React.useEffect(() => { if (open && inputRef.current && inputRef.current.focus) inputRef.current.focus(); }, [open]);
|
|
1066
|
+
React.useEffect(() => {
|
|
1067
|
+
if (!open) { clearSearchMarks(); setCount(0); setCursor(-1); return undefined; }
|
|
1068
|
+
const total = paintSearchMarks(query);
|
|
1069
|
+
setCount(total);
|
|
1070
|
+
setCursor(total > 0 ? 0 : -1);
|
|
1071
|
+
const first = document.querySelector('mark.w2mSearchMark');
|
|
1072
|
+
if (first && first.scrollIntoView) first.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
1073
|
+
return () => clearSearchMarks();
|
|
1074
|
+
}, [open, query]);
|
|
1075
|
+
const step = (backwards) => {
|
|
1076
|
+
const marks = [...document.querySelectorAll('mark.w2mSearchMark')];
|
|
1077
|
+
if (marks.length === 0) { paintSearchMarks(query); return; }
|
|
1078
|
+
const next = (cursor + (backwards ? -1 : 1) + marks.length) % marks.length;
|
|
1079
|
+
setCursor(next);
|
|
1080
|
+
if (marks[next] && marks[next].scrollIntoView) marks[next].scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
1081
|
+
};
|
|
1082
|
+
const scrollTurns = () => {
|
|
1083
|
+
const surface = findScrollSurface();
|
|
1084
|
+
if (surface !== null) surface.scrollBy({ top: -Math.round(Math.max(240, surface.clientHeight * 1.8)), behavior: 'smooth' });
|
|
1085
|
+
else if (typeof window !== 'undefined') window.scrollBy({ top: -Math.round(window.innerHeight * 1.8), behavior: 'smooth' });
|
|
1086
|
+
};
|
|
1087
|
+
const scrollFirstTurn = () => {
|
|
1088
|
+
const surface = findScrollSurface();
|
|
1089
|
+
if (surface !== null) surface.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1090
|
+
else if (typeof window !== 'undefined') window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
1091
|
+
};
|
|
1092
|
+
if (!open) {
|
|
1093
|
+
return h('span', { className: 'w2mSearchControls' }, [
|
|
1094
|
+
h('button', {
|
|
1095
|
+
key: 'up',
|
|
1096
|
+
type: 'button',
|
|
1097
|
+
className: 'w2mScrollUp',
|
|
1098
|
+
title: '向上滚动 3 轮(长按 3 秒到第一轮)',
|
|
1099
|
+
'aria-label': '向上滚动 3 轮,长按 3 秒到第一轮',
|
|
1100
|
+
onMouseDown: () => {
|
|
1101
|
+
longFiredRef.current = false;
|
|
1102
|
+
holdRef.current = setTimeout(() => { longFiredRef.current = true; scrollFirstTurn(); }, 3000);
|
|
1103
|
+
},
|
|
1104
|
+
onMouseUp: () => { if (holdRef.current) { clearTimeout(holdRef.current); holdRef.current = null; } },
|
|
1105
|
+
onMouseLeave: () => { if (holdRef.current) { clearTimeout(holdRef.current); holdRef.current = null; } },
|
|
1106
|
+
onClick: () => { if (longFiredRef.current) { longFiredRef.current = false; return; } scrollTurns(); },
|
|
1107
|
+
}, '⇧3'),
|
|
1108
|
+
h('button', {
|
|
1109
|
+
key: 'find',
|
|
1110
|
+
type: 'button',
|
|
1111
|
+
className: 'w2mSearchToggle',
|
|
1112
|
+
title: '搜索本对话',
|
|
1113
|
+
'aria-label': '搜索本对话',
|
|
1114
|
+
onClick: () => setOpen(true),
|
|
1115
|
+
}, '🔍'),
|
|
1116
|
+
]);
|
|
1117
|
+
}
|
|
1118
|
+
return h('div', { className: 'w2mSearchBox', 'data-w2m-search': 'box' }, [
|
|
1119
|
+
h('input', {
|
|
1120
|
+
key: 'q',
|
|
1121
|
+
ref: inputRef,
|
|
1122
|
+
className: 'w2mSearchInput',
|
|
1123
|
+
value: query,
|
|
1124
|
+
placeholder: '搜索本对话全部内容',
|
|
1125
|
+
onChange: (event) => setQuery(event.target.value),
|
|
1126
|
+
onKeyDown: (event) => {
|
|
1127
|
+
if (event.key === 'Enter') { event.preventDefault(); step(event.shiftKey); }
|
|
1128
|
+
if (event.key === 'Escape') { setOpen(false); setQuery(''); }
|
|
1129
|
+
},
|
|
1130
|
+
}),
|
|
1131
|
+
h('span', { key: 'count', className: 'w2mSearchCount' }, count === 0 ? (query === '' ? '' : '0') : whenCount(count, cursor)),
|
|
1132
|
+
h('button', { key: 'prev', type: 'button', className: 'w2mSearchBtn', title: '上一个', onClick: () => step(true) }, '↑'),
|
|
1133
|
+
h('button', { key: 'next', type: 'button', className: 'w2mSearchBtn', title: '下一个', onClick: () => step(false) }, '↓'),
|
|
1134
|
+
h('button', { key: 'close', type: 'button', className: 'w2mSearchBtn', title: '关闭搜索', onClick: () => { setOpen(false); setQuery(''); clearSearchMarks(); } }, '×'),
|
|
1135
|
+
]);
|
|
1136
|
+
}
|
|
1137
|
+
function whenCount(total, at) {
|
|
1073
1138
|
return (at >= 0 ? at + 1 : 0) + '/' + total;
|
|
1074
1139
|
}
|
|
1075
1140
|
|
|
@@ -1667,7 +1732,22 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
|
|
|
1667
1732
|
}
|
|
1668
1733
|
|
|
1669
1734
|
function FleetChat({ sessionId, viewMode }) {
|
|
1670
|
-
|
|
1735
|
+
// v1.9.18: chat emotes are local slash commands, never image bytes. The relay only sees text.
|
|
1736
|
+
const CHAT_COMMANDS = ['/微笑', '/火箭', '/烟花'];
|
|
1737
|
+
// v1.9.18: the sticker pack is local to the plugin. Commands travel as text; rendering never requests bytes.
|
|
1738
|
+
const CHAT_STICKER_GLYPHS = { '/微笑': '\u{1F60A}', '/火箭': '\u{1F680}', '/烟花': '\u{1F386}' };
|
|
1739
|
+
const CHAT_STICKER_RE = /(\/微笑|\/火箭|\/烟花)/g;
|
|
1740
|
+
function renderChatText(text) {
|
|
1741
|
+
const raw = String(text ?? '');
|
|
1742
|
+
const whole = CHAT_STICKER_GLYPHS[raw.trim()];
|
|
1743
|
+
if (whole !== undefined) return h('span', { className: 'w2mChat_stickerBig', 'data-sticker': raw.trim(), title: raw.trim() }, whole);
|
|
1744
|
+
const parts = raw.split(CHAT_STICKER_RE);
|
|
1745
|
+
if (parts.length === 1) return raw;
|
|
1746
|
+
return parts.map((part, index) => (CHAT_STICKER_GLYPHS[part] !== undefined
|
|
1747
|
+
? h('span', { key: 'st' + index, className: 'w2mChat_sticker', 'data-sticker': part, title: part }, CHAT_STICKER_GLYPHS[part])
|
|
1748
|
+
: part));
|
|
1749
|
+
}
|
|
1750
|
+
const CHAT_EMOJIS = CHAT_COMMANDS;
|
|
1671
1751
|
const [open, setOpen] = React.useState(false);
|
|
1672
1752
|
const [messages, setMessages] = React.useState([]);
|
|
1673
1753
|
const [usage, setUsage] = React.useState(null);
|
|
@@ -1973,7 +2053,7 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
|
|
|
1973
2053
|
var kind = messageKind(message, localMachineId);
|
|
1974
2054
|
return h('div', { key: message?.message_id ?? `${message?.machine_id}:${message?.at_ms}`, className: 'w2mChat_msg is-' + kind, onClick: () => { const selection = typeof window.getSelection === 'function' ? String(window.getSelection() ?? '') : ''; if (selection !== '') return; setReplyTo({ id: message?.message_id ?? null, threadId: message?.thread_id ?? null, text: typeof message?.text === 'string' ? message.text : '' }); } },
|
|
1975
2055
|
h('div', { className: 'w2mChat_meta' }, h(WhaleAvatar, { machineId: message?.machine_id }), `${machineLabel(message)} · ${timeLabel(message)}`, kind === 'session' ? ' · 来自会话' : '', h('button', { key: 'copy', type: 'button', className: 'w2mChat_copy', title: '复制这条消息', onClick: (event) => { event.stopPropagation(); copyText(String(message?.text ?? '')); } }, '⧉'), message?.thread_id ? h('span', { className: 'w2mChat_thread' }, '#' + message.thread_id) : null),
|
|
1976
|
-
h('div', { className: 'w2mChat_text' }, message?.text ?? ''));
|
|
2056
|
+
h('div', { className: 'w2mChat_text' }, renderChatText(message?.text ?? '')));
|
|
1977
2057
|
}),
|
|
1978
2058
|
].filter((node) => node !== null))),
|
|
1979
2059
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.19",
|
|
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": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"pack": "node scripts/pack.mjs",
|
|
65
65
|
"reachability": "node scripts/reachability-check.mjs",
|
|
66
66
|
"reachability:classify": "node scripts/reachability-classify.mjs",
|
|
67
|
-
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/blake2s.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.test.mjs test/fleet-memory-e2e.test.mjs test/fleet-concurrency.test.mjs test/fleet-merge-race.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/portable-plugins.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs test/admission.test.mjs test/admission-routes.test.mjs test/join-degradation.test.mjs test/account-identity.test.mjs test/join-challenge.test.mjs test/exec-env-injection.test.mjs test/account-join.test.mjs test/fleet-messages.test.mjs test/message-shards.test.mjs test/fleet-messages-http.test.mjs test/message-notify.test.mjs test/update-channels.test.mjs test/update-prerelease.test.mjs test/session-watch.test.mjs test/session-readonly.test.mjs test/session-sync.test.mjs test/fleet-chat-identity.test.mjs test/identity-reader.test.mjs test/intents.test.mjs test/dataflow.test.mjs test/council-fleet.test.mjs test/fleet-memory.test.mjs test/memory-view.test.mjs test/schema-spec.test.mjs test/fleet-weight-parity.test.mjs test/task-deps.test.mjs test/dataflow-wiring.test.mjs test/task-graph.test.mjs test/ask-store.test.mjs test/turn.test.mjs test/fleet-members.test.mjs test/redact.test.mjs test/policy-matrix.test.mjs test/envelope-signature.test.mjs test/chat-crypto.test.mjs test/reduce-suggestion.test.mjs test/merge-hint.test.mjs test/fleet-chat-route.test.mjs test/maintenance.test.mjs test/maintenance-ferry.test.mjs test/grant.test.mjs test/grant-gate.test.mjs test/worktree.test.mjs test/patch-artifact.test.mjs test/merge-queue.test.mjs test/m3-workflow.test.mjs test/project-room.test.mjs test/project-attribution.test.mjs test/project-locks.test.mjs test/project-reviews.test.mjs test/project-notifications.test.mjs test/blob-sessions.test.mjs test/outbox.test.mjs test/reliability-metrics.test.mjs test/evidence.test.mjs test/grant-issue.test.mjs test/project-refs.test.mjs test/exec-capability.test.mjs test/secondary-link.test.mjs test/bootstrap-cli.test.mjs test/bootstrap.test.mjs test/windows-hidden-task.test.mjs test/relay-failover.test.mjs test/npm-update.test.mjs test/agent-start-nonblocking.test.mjs test/fleet-chat-capture.test.mjs test/capture-route.test.mjs test/update-resolver.test.mjs test/agent-identity-safe.test.mjs test/re-pair-stale-token.test.mjs test/gpw-folder-policy.test.mjs test/gpw-activation.test.mjs test/gpw-hardening.test.mjs test/gpw-tamper-gate.test.mjs test/gpw-persistence.test.mjs test/gpw-colors.test.mjs test/gpw-recent-fold.test.mjs test/gpw-auto-approve.test.mjs test/data-plane.test.mjs test/sealed-object.test.mjs test/joint-dev-dispatch.test.mjs test/joint-dev-loop.test.mjs test/joint-dev-audit.test.mjs test/joint-dev-redline.test.mjs test/relay-redline-guard.test.mjs test/relay-p0-projection.test.mjs test/guarded-dispatch.test.mjs test/p0-single-matcher.test.mjs test/path-policy.test.mjs test/path-policy-fs.test.mjs test/gpw-inheritance-table.test.mjs test/gpw-policy-adapter.test.mjs test/target-policy.test.mjs test/relay-redline-audit.test.mjs test/relay-repair-ledger.test.mjs test/relay-purge-blobs.test.mjs test/relay-scrub.test.mjs test/joint-dev-plan-store.test.mjs test/relay-basepath-blobs.test.mjs test/operator-token.test.mjs test/relay-health-capabilities.test.mjs test/joint-relay-dispatch.test.mjs test/operator-token-route.test.mjs test/relay-scrub-task-projection.test.mjs test/operator-key-approval.test.mjs test/relay-offer-journal.test.mjs",
|
|
67
|
+
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/blake2s.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.test.mjs test/fleet-memory-e2e.test.mjs test/fleet-concurrency.test.mjs test/fleet-merge-race.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/portable-plugins.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs test/admission.test.mjs test/admission-routes.test.mjs test/join-degradation.test.mjs test/account-identity.test.mjs test/join-challenge.test.mjs test/exec-env-injection.test.mjs test/account-join.test.mjs test/fleet-messages.test.mjs test/message-shards.test.mjs test/fleet-messages-http.test.mjs test/message-notify.test.mjs test/update-channels.test.mjs test/update-prerelease.test.mjs test/session-watch.test.mjs test/session-readonly.test.mjs test/session-sync.test.mjs test/fleet-chat-identity.test.mjs test/identity-reader.test.mjs test/intents.test.mjs test/dataflow.test.mjs test/council-fleet.test.mjs test/fleet-memory.test.mjs test/memory-view.test.mjs test/schema-spec.test.mjs test/fleet-weight-parity.test.mjs test/task-deps.test.mjs test/dataflow-wiring.test.mjs test/task-graph.test.mjs test/ask-store.test.mjs test/turn.test.mjs test/fleet-members.test.mjs test/redact.test.mjs test/policy-matrix.test.mjs test/envelope-signature.test.mjs test/chat-crypto.test.mjs test/reduce-suggestion.test.mjs test/merge-hint.test.mjs test/fleet-chat-route.test.mjs test/maintenance.test.mjs test/maintenance-ferry.test.mjs test/grant.test.mjs test/grant-gate.test.mjs test/worktree.test.mjs test/patch-artifact.test.mjs test/merge-queue.test.mjs test/m3-workflow.test.mjs test/project-room.test.mjs test/project-attribution.test.mjs test/project-locks.test.mjs test/project-reviews.test.mjs test/project-notifications.test.mjs test/blob-sessions.test.mjs test/outbox.test.mjs test/reliability-metrics.test.mjs test/evidence.test.mjs test/grant-issue.test.mjs test/project-refs.test.mjs test/exec-capability.test.mjs test/secondary-link.test.mjs test/bootstrap-cli.test.mjs test/bootstrap.test.mjs test/windows-hidden-task.test.mjs test/relay-failover.test.mjs test/npm-update.test.mjs test/agent-start-nonblocking.test.mjs test/fleet-chat-capture.test.mjs test/capture-route.test.mjs test/update-resolver.test.mjs test/agent-identity-safe.test.mjs test/re-pair-stale-token.test.mjs test/gpw-folder-policy.test.mjs test/gpw-activation.test.mjs test/gpw-hardening.test.mjs test/gpw-tamper-gate.test.mjs test/gpw-persistence.test.mjs test/gpw-colors.test.mjs test/gpw-recent-fold.test.mjs test/gpw-auto-approve.test.mjs test/data-plane.test.mjs test/sealed-object.test.mjs test/joint-dev-dispatch.test.mjs test/joint-dev-loop.test.mjs test/joint-dev-audit.test.mjs test/joint-dev-redline.test.mjs test/relay-redline-guard.test.mjs test/relay-p0-projection.test.mjs test/guarded-dispatch.test.mjs test/p0-single-matcher.test.mjs test/path-policy.test.mjs test/path-policy-fs.test.mjs test/gpw-inheritance-table.test.mjs test/gpw-policy-adapter.test.mjs test/target-policy.test.mjs test/relay-redline-audit.test.mjs test/relay-repair-ledger.test.mjs test/relay-purge-blobs.test.mjs test/relay-scrub.test.mjs test/joint-dev-plan-store.test.mjs test/relay-basepath-blobs.test.mjs test/operator-token.test.mjs test/relay-health-capabilities.test.mjs test/joint-relay-dispatch.test.mjs test/operator-token-route.test.mjs test/relay-scrub-task-projection.test.mjs test/operator-key-approval.test.mjs test/relay-offer-journal.test.mjs test/joint-r5-wiring.test.mjs test/p1-hardening.test.mjs test/joint-merge-engine.test.mjs test/artifact-ref-dir-sink.test.mjs test/chat-sticker-guard.test.mjs test/relay-task-progress.test.mjs",
|
|
68
68
|
"test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
|
|
69
69
|
"test:account-identity": "node --test --test-force-exit test/account-identity.test.mjs",
|
|
70
70
|
"test:account-join": "node --test --test-force-exit test/account-join.test.mjs",
|
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.
|
|
4
|
+
"version": "1.9.19",
|
|
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/agent/agent.mjs
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import { Readable } from 'node:stream';
|
|
25
25
|
import { access, constants as fsConstants } from 'node:fs/promises';
|
|
26
|
-
import { existsSync, mkdirSync, mkdtempSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
26
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
27
27
|
import { dirname, join } from 'node:path';
|
|
28
28
|
import { commitTaskChanges, createWorktree, listChangedFiles, producePatch, removeWorktree } from './worktree.mjs';
|
|
29
29
|
import { describePatchArtifact } from './patch-artifact.mjs';
|
|
@@ -31,6 +31,7 @@ import { ferryMaintenance, maintenancePaths } from './maintenance-ferry.mjs';
|
|
|
31
31
|
import { authorizeWithGrant } from './grant-gate.mjs';
|
|
32
32
|
import { tmpdir } from 'node:os';
|
|
33
33
|
import { randomUUID , sign as signBytes } from 'node:crypto';
|
|
34
|
+
import { fileURLToPath } from 'node:url';
|
|
34
35
|
|
|
35
36
|
import {
|
|
36
37
|
EMPTY_SHA256,
|
|
@@ -2122,6 +2123,10 @@ export function createAgent(options) {
|
|
|
2122
2123
|
python: caps.python ?? null,
|
|
2123
2124
|
npm: caps.npm ?? null,
|
|
2124
2125
|
git,
|
|
2126
|
+
plugin_version: (() => {
|
|
2127
|
+
try { return JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json'), 'utf8')).version ?? null; } catch { return null; }
|
|
2128
|
+
})(),
|
|
2129
|
+
joint_dev: 'v2',
|
|
2125
2130
|
};
|
|
2126
2131
|
return toolchainCache;
|
|
2127
2132
|
}
|