@twinsearth/w2m-dsh-plugin 1.2.1 → 1.2.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/README.md CHANGED
@@ -14,7 +14,7 @@ English | [中文](#中文说明)
14
14
 
15
15
  ## Install
16
16
 
17
- Five routes in. They all end with **12 tools registered** in your agent. Pick one.
17
+ Five routes in. They all end with **13 tools registered** in your agent. Pick one.
18
18
 
19
19
  ### 1. DSH plugin (recommended — zero configuration)
20
20
 
@@ -137,6 +137,7 @@ hand-maintained host list.
137
137
 
138
138
  `w2m_status` · `w2m_devices` · `w2m_run` · `w2m_wait` · `w2m_history` · `w2m_stats` · `w2m_report` ·
139
139
  `w2m_memory` · `w2m_approvals` · `w2m_council` · `w2m_chat` · `w2m_update`
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.
140
141
 
141
142
  ### Environment a dispatched command receives (1.1.15+)
142
143
 
@@ -260,7 +261,7 @@ MIT.
260
261
 
261
262
  ### 安装
262
263
 
263
- 五种方式,最终都会在你的 agent 里注册 **12 tools registered**。任选其一。
264
+ 五种方式,最终都会在你的 agent 里注册 **13 tools registered**。任选其一。
264
265
 
265
266
  #### 1. DSH 插件(推荐,零配置)
266
267
 
@@ -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.2.1", "w2m-mcp"]
15
+ args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@1.2.3", "w2m-mcp"]
16
16
  startup_timeout_sec = 20
17
17
  tool_timeout_sec = 60
package/lib/client.js CHANGED
@@ -295,6 +295,17 @@ window.__ModuleLoader__.load({
295
295
  '.w2mChat_empty{color:var(--dsw-alias-label-tertiary)}',
296
296
  '.w2mChat_msg{display:flex;flex-direction:column;gap:2px}',
297
297
  '.w2mChat_meta{color:var(--dsw-alias-label-tertiary);font-size:11px}',
298
+ '.w2mChat_to{font:inherit;font-size:11px;max-width:120px;border-radius:6px;border:.5px solid var(--dsw-alias-border-default);background:var(--dsw-alias-surface-default);color:var(--dsw-alias-label-primary)}',
299
+ '.w2mChat_reply{display:flex;align-items:center;gap:6px;padding:4px 10px;font-size:11px;color:var(--dsw-alias-label-secondary);border-top:.5px solid var(--dsw-alias-border-default)}',
300
+ '.w2mChat_replyCancel{margin-left:auto;border:none;background:none;color:var(--dsw-alias-label-secondary);cursor:pointer}',
301
+ '.w2mChat_msg{cursor:pointer}',
302
+ '.w2mChat_thread{margin-left:6px;opacity:.7}',
303
+ '.w2mChat_threadHead{font-size:11px;color:var(--dsw-alias-label-tertiary);padding:4px 10px 0}',
304
+ '.w2mChat_board{display:flex;flex-direction:column;gap:2px;padding:6px 10px;border-bottom:.5px solid var(--dsw-alias-border-default)}',
305
+ '.w2mChat_boardTitle{font-size:11px;color:var(--dsw-alias-label-tertiary)}',
306
+ '.w2mChat_boardRow{font-size:11px;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,monospace}',
307
+ '.w2mChat_threadHead{font-size:11px;color:var(--dsw-alias-label-tertiary);padding:4px 10px 0}',
308
+ '.w2mChat_unseen{margin-left:6px;min-width:16px;padding:0 5px;border-radius:8px;background:var(--dsw-alias-accent-default);color:var(--dsw-alias-label-on-accent);font-size:10px;line-height:16px;text-align:center}',
298
309
  '.w2mChat_text{white-space:pre-wrap;word-break:break-word}',
299
310
  '.w2mChat_msg.is-session .w2mChat_text{color:var(--dsw-danger)}', '.w2mChat_msg.is-chat .w2mChat_text{color:var(--dsw-success)}', '.w2mChat_msg.is-remote .w2mChat_text{color:var(--dsw-text)}', '.w2mChat_avatar{display:inline-flex;width:16px;height:16px;margin-right:5px;vertical-align:-3px}',
300
311
  '.w2mChat_compose{display:flex;gap:8px;padding:8px 10px;border-top:.5px solid var(--dsw-alias-border-l2);align-items:flex-end}',
@@ -614,6 +625,18 @@ window.__ModuleLoader__.load({
614
625
  const [usage, setUsage] = React.useState(null);
615
626
  const [localMachineName, setLocalMachineName] = React.useState(null);
616
627
  const [roster, setRoster] = React.useState(null);
628
+ const [toRecipient, setToRecipient] = React.useState('all');
629
+ const [replyTo, setReplyTo] = React.useState(null);
630
+ const [tasks, setTasks] = React.useState([]);
631
+ const [lastSeenMs, setLastSeenMs] = React.useState(() => {
632
+ try {
633
+ const raw = globalThis.localStorage?.getItem('w2m:chat:seen:' + sessionId);
634
+ const parsed = Number.parseInt(raw ?? '', 10);
635
+ return Number.isFinite(parsed) ? parsed : 0;
636
+ } catch {
637
+ return 0;
638
+ }
639
+ });
617
640
  const [text, setText] = React.useState('');
618
641
  const [error, setError] = React.useState(null);
619
642
  const [busy, setBusy] = React.useState(false);
@@ -659,6 +682,40 @@ window.__ModuleLoader__.load({
659
682
  });
660
683
  return () => controller.abort();
661
684
  }, [open, sessionId]);
685
+
686
+ // The task board is the relay's own list: the host proxies it so the panel never holds a device token.
687
+ React.useEffect(() => {
688
+ if (!open) return undefined;
689
+ const controller = new AbortController();
690
+ chatCall({ url: '/api/w2m/tasks?limit=20', init: { signal: controller.signal } })
691
+ .then((result) => { if (!controller.signal.aborted) setTasks(result.ok && Array.isArray(result.data?.tasks) ? result.data.tasks : []); })
692
+ .catch(() => { /* the board is an extra; a failed read must not break the chat */ });
693
+ return () => controller.abort();
694
+ }, [open]);
695
+
696
+ // Unread is measured against a per-conversation watermark in local storage: the relay stays stateless, and
697
+ // the count answers "is there anything I have not looked at" without claiming which messages a machine read.
698
+ React.useEffect(() => {
699
+ if (!open || messages.length === 0) return undefined;
700
+ const latest = messages.reduce((max, message) => (Number.isFinite(message?.at_ms) ? Math.max(max, message.at_ms) : max), 0);
701
+ if (latest <= 0 || latest <= lastSeenMs) return undefined;
702
+ setLastSeenMs(latest);
703
+ try { globalThis.localStorage?.setItem('w2m:chat:seen:' + sessionId, String(latest)); } catch { /* storage may be unavailable */ }
704
+ return undefined;
705
+ }, [open, messages, sessionId, lastSeenMs]);
706
+
707
+ // Consecutive messages that share a thread are shown under one thread head. Grouping only consecutive runs keeps
708
+ // the reading order the relay assigned -- re-sorting by thread would put an answer above the question it answers.
709
+ const groupThreads = (list) => {
710
+ const groups = [];
711
+ for (const message of list ?? []) {
712
+ const threadId = typeof message?.thread_id === 'string' && message.thread_id !== '' ? message.thread_id : null;
713
+ const last = groups[groups.length - 1] ?? null;
714
+ if (last !== null && last.threadId === threadId) { last.messages.push(message); continue; }
715
+ groups.push({ threadId, messages: [message] });
716
+ }
717
+ return groups;
718
+ };
662
719
 
663
720
  const send = React.useCallback(async () => {
664
721
  const value = text.trim();
@@ -670,12 +727,18 @@ window.__ModuleLoader__.load({
670
727
  method: 'POST',
671
728
  credentials: 'same-origin',
672
729
  headers: { 'content-type': 'application/json', accept: 'application/json' },
673
- body: JSON.stringify({ session_id: sessionId, text: value }),
730
+ body: JSON.stringify({
731
+ session_id: sessionId,
732
+ text: value,
733
+ ...(toRecipient === 'all' ? {} : { to: toRecipient }),
734
+ ...(replyTo === null ? {} : { reply_to: replyTo.id, ...(replyTo.threadId === null ? {} : { thread_id: replyTo.threadId }) }),
735
+ }),
674
736
  },
675
737
  });
676
738
  setBusy(false);
677
739
  if (result.ok) {
678
740
  setText('');
741
+ setReplyTo(null);
679
742
  await load();
680
743
  } else {
681
744
  setError(result.error);
@@ -683,6 +746,7 @@ window.__ModuleLoader__.load({
683
746
  return undefined;
684
747
  }, [busy, load, sessionId, text]);
685
748
 
749
+ const unseenCount = messages.reduce((count, message) => (Number.isFinite(message?.at_ms) && message.at_ms > lastSeenMs ? count + 1 : count), 0);
686
750
  const disabled = typeof sessionId !== 'string' || sessionId === '';
687
751
  const usageText = usage === null ? null : `${Math.round((usage.used_24h_bytes ?? 0) / 1024)} KiB/24h · ${usage.messages ?? 0} 条`;
688
752
  const machineLabel = (message) => {
@@ -709,26 +773,47 @@ window.__ModuleLoader__.load({
709
773
  'aria-expanded': open,
710
774
  disabled,
711
775
  onClick: () => setOpen((value) => !value),
712
- }, '💬', h('span', { className: 'w2mChat_label' }, '编队聊天')),
776
+ }, '💬', h('span', { className: 'w2mChat_label' }, '编队聊天'), unseenCount > 0 && !open ? h('span', { className: 'w2mChat_unseen' }, String(unseenCount)) : null),
713
777
  open
714
778
  ? h('div', { className: 'w2mChat_panel', role: 'dialog', 'aria-label': '编队聊天' },
715
779
  h('div', { className: 'w2mChat_head' },
780
+ h('select', {
781
+ className: 'w2mChat_to',
782
+ value: toRecipient,
783
+ title: '收件人',
784
+ onChange: (event) => setToRecipient(event.target.value),
785
+ }, [
786
+ h('option', { key: '__all__', value: 'all' }, '全部'),
787
+ ...(Array.isArray(roster?.machines) ? roster.machines.filter((machine) => machine.online === true && typeof machine.machine_id === 'string').map((machine) => h('option', { key: machine.machine_id, value: machine.machine_id }, machine.name ?? machine.machine_id)) : []),
788
+ ]),
716
789
  h('span', null, '编队聊天'),
717
790
  usageText === null ? null : h('span', { className: 'w2mChat_usage' }, usageText),
718
791
  h('button', { type: 'button', className: 'w2mChat_close', title: '关闭', onClick: () => setOpen(false) }, '×')),
792
+ tasks.length === 0 ? null : h('div', { className: 'w2mChat_board' }, [
793
+ h('div', { key: 'title', className: 'w2mChat_boardTitle' }, '任务看板'),
794
+ ...tasks.slice(0, 6).map((task) => h('div', { key: String(task?.task_id ?? Math.random()), className: 'w2mChat_boardRow' },
795
+ `${String(task?.task_id ?? '').slice(0, 8)} ${task?.mode ?? '?'} ${task?.status ?? '?'}`)),
796
+ ]),
719
797
  h('div', { className: 'w2mChat_list' },
720
798
  error !== null ? h('div', { className: 'w2mChat_error' }, error) : null,
721
799
  messages.length === 0
722
800
  ? (error === null ? h('div', { className: 'w2mChat_empty' }, '本会话还没有消息') : null)
723
- : messages.map((message) => {
801
+ : groupThreads(messages).flatMap((group, groupIndex) => [
802
+ group.threadId === null ? null : h('div', { key: 'thread:' + group.threadId + ':' + groupIndex, className: 'w2mChat_threadHead' }, '#' + group.threadId),
803
+ ...group.messages.map((message) => {
724
804
  var kind = messageKind(message, localMachineId);
725
- return h('div', { key: message?.message_id ?? `${message?.machine_id}:${message?.at_ms}`, className: 'w2mChat_msg is-' + kind },
726
- h('div', { className: 'w2mChat_meta' }, h(WhaleAvatar, { machineId: message?.machine_id }), `${machineLabel(message)} · ${timeLabel(message)}`, kind === 'session' ? ' · 来自会话' : ''),
805
+ return h('div', { key: message?.message_id ?? `${message?.machine_id}:${message?.at_ms}`, className: 'w2mChat_msg is-' + kind, onClick: () => setReplyTo({ id: message?.message_id ?? null, threadId: message?.thread_id ?? null, text: typeof message?.text === 'string' ? message.text : '' }) },
806
+ h('div', { className: 'w2mChat_meta' }, h(WhaleAvatar, { machineId: message?.machine_id }), `${machineLabel(message)} · ${timeLabel(message)}`, kind === 'session' ? ' · 来自会话' : '', message?.thread_id ? h('span', { className: 'w2mChat_thread' }, '#' + message.thread_id) : null),
727
807
  h('div', { className: 'w2mChat_text' }, message?.text ?? ''));
728
- })),
808
+ }),
809
+ ].filter((node) => node !== null))),
729
810
 
730
811
 
731
812
  h('div', { className: 'w2mChat_compose' },
813
+ replyTo === null ? null : h('div', { className: 'w2mChat_reply' }, [
814
+ h('span', { key: 'label' }, '回复:' + (replyTo.text.length > 40 ? replyTo.text.slice(0, 40) + '…' : replyTo.text)),
815
+ h('button', { key: 'cancel', type: 'button', className: 'w2mChat_replyCancel', title: '取消回复', onClick: () => setReplyTo(null) }, '×'),
816
+ ]),
732
817
  h('textarea', {
733
818
  className: 'w2mChat_input',
734
819
  value: text,
package/package.json CHANGED
@@ -1,15 +1,8 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "1.2.1",
3
+ "version": "1.2.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
- "main": "lib/index.js",
7
- "bin": {
8
- "w2m-localside": "bin/w2m-localside.mjs",
9
- "w2m-mcp": "bin/w2m-mcp.mjs",
10
- "w2m-rabbit": "bin/w2m-rabbit.mjs",
11
- "w2m-stun": "bin/w2m-stun.mjs"
12
- },
13
6
  "exports": {
14
7
  ".": "./lib/index.js",
15
8
  "./tools": "./lib/tools.js",
@@ -20,6 +13,16 @@
20
13
  "./package.json": "./package.json",
21
14
  "./gpw": "./lib/gpw.js"
22
15
  },
16
+ "dsh": {
17
+ "plugin": true,
18
+ "kind": "server",
19
+ "bundle": {
20
+ "patch": "cordis.patch.yml"
21
+ },
22
+ "client": {
23
+ "platform": "web"
24
+ }
25
+ },
23
26
  "files": [
24
27
  "mcp.json",
25
28
  "plugin.json",
@@ -47,80 +50,10 @@
47
50
  ".claude-plugin",
48
51
  "codex"
49
52
  ],
50
- "dsh": {
51
- "plugin": true,
52
- "kind": "server",
53
- "bundle": {
54
- "patch": "cordis.patch.yml"
55
- },
56
- "client": {
57
- "platform": "web"
58
- }
59
- },
60
- "disclosure": {
61
- "cloud": true,
62
- "network": [
63
- "http://202.182.123.154:8787",
64
- "202.182.123.154:3478",
65
- "stun.cloudflare.com:3478",
66
- "stun.l.google.com:19302",
67
- "stun.nextcloud.com:443",
68
- "ilinkai.weixin.qq.com:443",
69
- "weixin.qq.com:443",
70
- "api.weixin.qq.com:443"
71
- ],
72
- "offlineMode": false,
73
- "apiKeys": [
74
- {
75
- "env": "W2M_OPERATOR_TOKEN",
76
- "storage": "profile-config"
77
- },
78
- {
79
- "env": null,
80
- "storage": "file-0600"
81
- },
82
- {
83
- "env": null,
84
- "storage": "profile-config"
85
- },
86
- {
87
- "env": null,
88
- "storage": "im-bot-token"
89
- }
90
- ],
91
- "permissions": {
92
- "network": "the configured relay (default: the shared server above), STUN servers, and UDP to the peer machines. The optional chatops channels reach one instant-messaging platform each, and only when that channel is named in `channels` and the host has supplied the transport: the WeChat channel reaches ilinkai.weixin.qq.com and sends the bot token as a bearer credential, and both that host and the base url in a login response are checked against an allow-list before a token is sent anywhere",
93
- "filesystem": "read/write inside the project directory and DSH_HOME; the agent writes its spool and state under --state; the chatops file fence reads inside `projectRoot` only, after resolving symlinks",
94
- "env": [
95
- "W2M_*"
96
- ]
97
- },
98
- "jurisdiction": [
99
- "CN",
100
- "JP"
101
- ],
102
- "retention": "server"
103
- },
104
53
  "engines": {
105
54
  "node": "\u003e=20.19.0"
106
55
  },
107
- "dependencies": {
108
-
109
- },
110
- "devDependencies": {
111
- "@eslint/js": "^10.0.1",
112
- "eslint": "^10.12.0",
113
- "globals": "^17.13.0",
114
- "prettier": "^3.9.9"
115
- },
116
- "peerDependencies": {
117
- "@deepseek-ai/dsh-tools": "*"
118
- },
119
- "peerDependenciesMeta": {
120
- "@deepseek-ai/dsh-tools": {
121
- "optional": true
122
- }
123
- },
56
+ "license": "MIT",
124
57
  "scripts": {
125
58
  "format": "prettier --write .",
126
59
  "format:check": "prettier --check .",
@@ -131,14 +64,14 @@
131
64
  "pack": "node scripts/pack.mjs",
132
65
  "reachability": "node scripts/reachability-check.mjs",
133
66
  "reachability:classify": "node scripts/reachability-classify.mjs",
134
- "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",
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",
135
68
  "test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
136
69
  "test:account-identity": "node --test --test-force-exit test/account-identity.test.mjs",
137
70
  "test:account-join": "node --test --test-force-exit test/account-join.test.mjs",
138
71
  "test:admission": "node --test --test-force-exit test/admission.test.mjs",
139
72
  "test:admission-routes": "node --test --test-force-exit test/admission-routes.test.mjs",
140
73
  "test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
141
- "test:all": "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/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.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-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.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",
74
+ "test:all": "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/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.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-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.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",
142
75
  "test:approval-gate": "node --test --test-force-exit test/approval-gate.test.mjs",
143
76
  "test:approval-index": "node --test --test-force-exit test/approval-index.test.mjs",
144
77
  "test:authority": "node --test --test-force-exit test/authority.test.mjs",
@@ -188,8 +121,32 @@
188
121
  "test:update-channels": "node --test --test-force-exit test/update-channels.test.mjs",
189
122
  "test:update-prerelease": "node --test --test-force-exit test/update-prerelease.test.mjs",
190
123
  "verify": "node scripts/check-entrypoints.mjs \u0026\u0026 node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/tools.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/p2p-node.test.mjs test/p2p-agent.test.mjs test/client-bundle.test.mjs test/loader-entry.test.mjs",
191
- "verify:join": "node scripts/verify-join.mjs"
124
+ "verify:join": "node scripts/verify-join.mjs",
125
+ "test:intents": "node --test --test-force-exit test/intents.test.mjs",
126
+ "test:dataflow": "node --test --test-force-exit test/dataflow.test.mjs",
127
+ "test:council-fleet": "node --test --test-force-exit test/council-fleet.test.mjs",
128
+ "test:fleet-memory": "node --test --test-force-exit test/fleet-memory.test.mjs",
129
+ "test:memory-view": "node --test --test-force-exit test/memory-view.test.mjs",
130
+ "test:schema-spec": "node --test --test-force-exit test/schema-spec.test.mjs",
131
+ "test:fleet-weight-parity": "node --test --test-force-exit test/fleet-weight-parity.test.mjs"
192
132
  },
133
+ "devDependencies": {
134
+ "@eslint/js": "^10.0.1",
135
+ "eslint": "^10.12.0",
136
+ "globals": "^17.13.0",
137
+ "prettier": "^3.9.9"
138
+ },
139
+ "publishConfig": {
140
+ "access": "public"
141
+ },
142
+ "repository": {
143
+ "type": "git",
144
+ "url": "git+https://github.com/TwinsEarth/dsh-windows2macos.git"
145
+ },
146
+ "homepage": "https://github.com/TwinsEarth/dsh-windows2macos",
147
+ "bugs": {
148
+ "url": "https://github.com/TwinsEarth/dsh-windows2macos/issues"
149
+ },
193
150
  "keywords": [
194
151
  "dsh-bundle",
195
152
  "dsh-plugin",
@@ -203,16 +160,66 @@
203
160
  "replicate",
204
161
  "sharding"
205
162
  ],
206
- "repository": {
207
- "type": "git",
208
- "url": "git+https://github.com/TwinsEarth/dsh-windows2macos.git"
163
+ "main": "lib/index.js",
164
+ "bin": {
165
+ "w2m-localside": "bin/w2m-localside.mjs",
166
+ "w2m-mcp": "bin/w2m-mcp.mjs",
167
+ "w2m-rabbit": "bin/w2m-rabbit.mjs",
168
+ "w2m-stun": "bin/w2m-stun.mjs"
169
+ },
170
+ "disclosure": {
171
+ "cloud": true,
172
+ "network": [
173
+ "http://202.182.123.154:8787",
174
+ "202.182.123.154:3478",
175
+ "stun.cloudflare.com:3478",
176
+ "stun.l.google.com:19302",
177
+ "stun.nextcloud.com:443",
178
+ "ilinkai.weixin.qq.com:443",
179
+ "weixin.qq.com:443",
180
+ "api.weixin.qq.com:443"
181
+ ],
182
+ "offlineMode": false,
183
+ "apiKeys": [
184
+ {
185
+ "env": "W2M_OPERATOR_TOKEN",
186
+ "storage": "profile-config"
187
+ },
188
+ {
189
+ "env": null,
190
+ "storage": "file-0600"
191
+ },
192
+ {
193
+ "env": null,
194
+ "storage": "profile-config"
195
+ },
196
+ {
197
+ "env": null,
198
+ "storage": "im-bot-token"
199
+ }
200
+ ],
201
+ "permissions": {
202
+ "network": "the configured relay (default: the shared server above), STUN servers, and UDP to the peer machines. The optional chatops channels reach one instant-messaging platform each, and only when that channel is named in `channels` and the host has supplied the transport: the WeChat channel reaches ilinkai.weixin.qq.com and sends the bot token as a bearer credential, and both that host and the base url in a login response are checked against an allow-list before a token is sent anywhere",
203
+ "filesystem": "read/write inside the project directory and DSH_HOME; the agent writes its spool and state under --state; the chatops file fence reads inside `projectRoot` only, after resolving symlinks",
204
+ "env": [
205
+ "W2M_*"
206
+ ]
207
+ },
208
+ "jurisdiction": [
209
+ "CN",
210
+ "JP"
211
+ ],
212
+ "retention": "server"
209
213
  },
210
- "homepage": "https://github.com/TwinsEarth/dsh-windows2macos",
211
- "bugs": {
212
- "url": "https://github.com/TwinsEarth/dsh-windows2macos/issues"
213
- },
214
- "publishConfig": {
215
- "access": "public"
216
- },
217
- "license": "MIT"
214
+ "dependencies": {
215
+
216
+ },
217
+ "peerDependencies": {
218
+ "@deepseek-ai/dsh-tools": "*"
219
+ },
220
+ "peerDependenciesMeta": {
221
+ "@deepseek-ai/dsh-tools": {
222
+ "optional": true
223
+ }
224
+ }
218
225
  }
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.2.1",
4
+ "version": "1.2.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": [