@twinsearth/w2m-dsh-plugin 2.2.6 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,14 +5,14 @@
5
5
  },
6
6
  "metadata": {
7
7
  "description": "The w2m fleet plugins: one account, many machines, any of Windows, macOS or Linux.",
8
- "version": "2.2.6"
8
+ "version": "3.0.0"
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.2.6",
15
+ "version": "3.0.0",
16
16
  "author": {
17
17
  "name": "TwinsEarth"
18
18
  },
@@ -26,5 +26,5 @@
26
26
  ]
27
27
  }
28
28
  ],
29
- "version": "2.2.6"
29
+ "version": "3.0.0"
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.2.6",
4
+ "version": "3.0.0",
5
5
  "author": {
6
6
  "name": "TwinsEarth"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to this project are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [3.0.0] - 2026-11-14
8
+
9
+ - 决策中心子插件:`@twinsearth/dsh-fang/decision-center`(瓮城)完整移植为
10
+ `@twinsearth/w2m-dsh-plugin/decision-center`。宿主模块逐字移植(`src/decision-center/index.mjs`,
11
+ 27 个服务方法、`request_human_decision` 工具),四个依赖(AI 密钥库、决策 outbox、生产者契约、
12
+ SSRF 守卫)与 MIT 内置 schemastery 原样随包;Fang 内核 barrel 由 `kernel.mjs` 顶替,提供同样的
13
+ `z`/`SERVICE`/bus/原子读写/数据目录解析/角色门。
14
+ - 新增 `/api/w2m/decisions*` 14 条路由:列表、单票、配额、分身聚合、生产者目录、外部 ingest、
15
+ SSE 实时推送、解决(含批量与 actionId)、放弃(含批量)、AI 状态/测试/配置/建议/流式建议;
16
+ `routes.mjs` 用 Connection fetch 注册表承载,SSE 事件名与负载与 Fang 一致。
17
+ - 决策中心面板进入 GPW:`GpwSettingsPage` 与 `GpwLauncher` 共用同一个 `GpwPanel`,因此设置页与浮标
18
+ 同时显示——4 秒轮询、红票置顶、绿/灰折叠、按票面选项一键解决、无选项可放弃、AI 建议只推荐不执行。
19
+ - 测试:移植 Fang 决策套件 79 项 + 新增路由面 3 项通过;Fang 的 Node-SSE 辅助(decision-events)与 fork 侧 ingest 客户端不随包(W2M 路由走 fetch SSE、没有 fork 半体)。
20
+ `decision-center-fork` 与 `decision-action-integration` 不移植:前者依赖 Fang 母体/分身角色矩阵,
21
+ 后者的主体是 Fang orchestrator 与 restart-guard,W2M 里没有对应组件。
22
+ - 包元数据:新增 `./decision-center` 子路径导出与`dsh-decision-center` patch 行(启用),
23
+ `engines.dsh` 保持 `>=0.2.0 <0.3.0`。
24
+
7
25
  ## [2.2.6] - 2026-11-14
8
26
 
9
27
  - Fixes 搜索本对话: the walk covered only the viewport and stopped counting at six matches, so a
package/README.md CHANGED
@@ -93,7 +93,7 @@ w2m-mcp --help
93
93
  # 复制进你自己的 codex/mcp-servers.toml
94
94
  [mcp_servers.w2m]
95
95
  command = "npx"
96
- args = ["-y", "@twinsearth/w2m-dsh-plugin@2.2.6", "w2m-mcp"]
96
+ args = ["-y", "@twinsearth/w2m-dsh-plugin@3.0.0", "w2m-mcp"]
97
97
  ```
98
98
 
99
99
  ### 5. 验证过的 release tarball(离线)
@@ -12,6 +12,6 @@
12
12
 
13
13
  [mcp_servers.w2m-fleet]
14
14
  command = "npx"
15
- args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@2.2.6", "w2m-mcp"]
15
+ args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@3.0.0", "w2m-mcp"]
16
16
  startup_timeout_sec = 20
17
17
  tool_timeout_sec = 60
package/cordis.patch.yml CHANGED
@@ -144,4 +144,15 @@
144
144
  whitelist: []
145
145
  blacklist: []
146
146
  pendingTimeoutMs: 120000
147
+ # 决策中心 (decision center): the human-attention queue, ported from dsh-Fang's
148
+ # `@twinsearth/dsh-fang/decision-center` in v3.0.0.
149
+ #
150
+ # Unlike GPW this row is **enabled**: the center is the panel the GPW settings page and the GPW
151
+ # floating launcher render (read through /api/w2m/decisions), and a disabled row would leave that
152
+ # panel with no store behind it. The store lives under $DSH_HOME/xclient/decision-center; the row
153
+ # carries no config so the plugin's own defaults apply.
154
+ - id: dsh-decision-center
155
+ name: '@twinsearth/w2m-dsh-plugin/decision-center'
156
+ config: {}
157
+
147
158
 
@@ -0,0 +1,50 @@
1
+ {
2
+ "version": "3.0.0",
3
+ "case": "RELEASE-3000",
4
+ "kind": "positive",
5
+ "machines": [
6
+ "b53a37f4-1464-4512-9259-33a7df091e3e"
7
+ ],
8
+ "executed_at": "2026-11-13T20:45:02.369Z",
9
+ "executor": "lead",
10
+ "task_id": "pre-release-3000",
11
+ "requirement": "v3.0.0: the Fang decision center is ported to W2M as @twinsearth/w2m-dsh-plugin/decision-center (host module, /api/w2m/decisions* routes, GPW page and GPW launcher panel) and its ported test suites pass",
12
+ "commands": [
13
+ [
14
+ "node",
15
+ "--test",
16
+ "--test-force-exit",
17
+ "test/decision-center.test.mjs",
18
+ "test/decision-routes.test.mjs",
19
+ "test/decision-outbox.test.mjs"
20
+ ],
21
+ [
22
+ "node",
23
+ "--test",
24
+ "--test-force-exit",
25
+ "test/client-bundle.test.mjs"
26
+ ],
27
+ [
28
+ "node",
29
+ "--test",
30
+ "--test-force-exit",
31
+ "test/loader-entry.test.mjs"
32
+ ],
33
+ [
34
+ "node",
35
+ "scripts/pack.mjs"
36
+ ]
37
+ ],
38
+ "result": {
39
+ "outcome": "ok",
40
+ "assertions": {
41
+ "port": "src/decision-center/index.mjs is the Fang plugin module with import specifiers and plugin name rewritten only; its four lib dependencies and the MIT schemastery are copied verbatim, and kernel.mjs supplies the Fang kernel names on W2M paths",
42
+ "service": "apply() provides w2mDecisionCenter with 27 methods and registers request_human_decision / ask_user_choice",
43
+ "routes": "14 /api/w2m/decisions* routes registered on the connection fetch registry, including the SSE events and AI advise streams; create -> list -> item -> quota -> resolve round-trips through HTTP",
44
+ "panel": "DecisionCenterSection renders inside GpwPanel, which GpwSettingsPage embeds and GpwLauncher opens, so the GPW page and the GPW launcher show the same center; it polls every 4s, groups by level, resolves via the ticket options, abandons option-less tickets and only displays AI advice",
45
+ "suites": "82 tests pass: 79 ported Fang decision-center tests + 3 new route-surface tests; the outbox suite is wired separately in scripts.test. The ported Node-SSE helper (decision-events) and fork-side ingest client were not carried over: W2M's routes stream through fetch and it has no fork half",
46
+ "not_ported": "decision-center-fork and decision-action-integration stay out: the former needs the Fang mother/fork role matrix, the latter the Fang orchestrator and restart-guard, neither of which exists in W2M",
47
+ "artifact": "scripts/pack.mjs packs dist/twinsearth-w2m-dsh-plugin-3.0.0.tgz deterministically; the published sha256 is the one release.yml writes into dist/SHA256SUMS, so the record does not restate the hash of the archive that contains it"
48
+ }
49
+ }
50
+ }
package/lib/client.js CHANGED
@@ -488,6 +488,14 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
488
488
  '.w2mGpw_tag[data-d="deny"]{border-color:var(--dsw-alias-state-danger-primary);color:var(--dsw-alias-state-danger-primary)}',
489
489
  '.w2mGpw_note{font-size:12px;color:var(--dsw-alias-label-secondary)}',
490
490
  '.w2mGpw_hint{font-size:11px;color:var(--dsw-alias-label-tertiary)}',
491
+ '.w2mDc_level{display:flex;flex-direction:column;gap:4px}',
492
+ '.w2mDc_ticket{display:flex;flex-direction:column;gap:4px;padding:5px 7px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-default);background:var(--dsw-alias-bg-layer-2)}',
493
+ '.w2mDc_head{display:flex;align-items:center;gap:6px;min-width:0}',
494
+ '.w2mDc_title{flex:1;min-width:0;font-size:12px;line-height:18px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
495
+ '.w2mDc_meta{font-size:11px;color:var(--dsw-alias-label-tertiary)}',
496
+ '.w2mDc_context{font-size:12px;color:var(--dsw-alias-label-secondary);white-space:pre-wrap;word-break:break-word;max-height:88px;overflow:auto}',
497
+ '.w2mDc_actions{display:flex;flex-wrap:wrap;gap:6px}',
498
+ '.w2mDc_advice{font-size:12px;color:var(--dsw-alias-label-secondary);padding:4px 6px;border-left:2px solid var(--dsw-alias-state-business-primary);white-space:pre-wrap}',
491
499
  '.w2mGpw{margin-top:6px;padding-top:6px;border-top:.5px solid var(--dsw-alias-border-default);display:flex;flex-direction:column;gap:4px;max-height:320px;overflow:auto}',
492
500
  '.w2mGpw_title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-primary)}',
493
501
  '.w2mGpw_block{display:flex;flex-direction:column;gap:2px;margin:2px 0 6px}',
@@ -838,6 +846,7 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
838
846
  ]),
839
847
  ])) : []),
840
848
  ]),
849
+ h(DecisionCenterSection, { key: 'decision-center' }),
841
850
  pending.length === 0 ? null : h('div', { key: 'pending', className: 'w2mGpw_block' }, [
842
851
  h('div', { key: 'title', className: 'w2mGpw_title' }, t('gpwPending')),
843
852
  ...pending.map((item) => h('div', { key: item.id, className: 'w2mGpw_recent' }, [
@@ -855,6 +864,156 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
855
864
  ]);
856
865
  }
857
866
 
867
+ /**
868
+ * 决策中心(v3.0.0,移植自 dsh-Fang 的决策收件箱):GPW 设置页与 GPW 浮标渲染同一个面板。
869
+ *
870
+ * 数据面就是本包自己的 `/api/w2m/decisions*`(见 src/decision-center/routes.mjs):4 秒轮询,
871
+ * 红票置顶、黄/蓝展开、绿/灰默认折叠;解决/放弃/批量都走服务层,UI 不猜状态。
872
+ * AI 建议只做「推荐」,不替人做决定。
873
+ */
874
+ function DecisionCenterSection() {
875
+ const LEVELS = [
876
+ ['red', '紧急决策', '会阻塞 Agent'],
877
+ ['yellow', '建议决策', '影响方向/进度'],
878
+ ['blue', '日常建议', '可按推荐处理'],
879
+ ['green', '通知与汇报', '默认折叠'],
880
+ ['gray', '已归档 / 过期', '只读'],
881
+ ];
882
+ const [state, setState] = React.useState({ status: 'loading', items: [], error: '', busy: '', ai: null, advice: {}, collapsed: { green: true, gray: true }, open: true });
883
+ const load = React.useCallback(async () => {
884
+ try {
885
+ const [listResponse, aiResponse] = await Promise.all([
886
+ fetch('/api/w2m/decisions?includeArchived=1', { credentials: 'same-origin' }),
887
+ fetch('/api/w2m/decisions/ai', { credentials: 'same-origin' }).catch(() => null),
888
+ ]);
889
+ if (!listResponse.ok) {
890
+ setState((prev) => ({ ...prev, status: listResponse.status === 404 ? 'absent' : 'error', error: 'HTTP ' + listResponse.status }));
891
+ return;
892
+ }
893
+ const body = await listResponse.json();
894
+ const aiBody = aiResponse !== null && aiResponse.ok ? await aiResponse.json() : null;
895
+ setState((prev) => ({
896
+ ...prev,
897
+ status: 'ready',
898
+ items: Array.isArray(body?.items) ? body.items : [],
899
+ error: '',
900
+ ai: aiBody,
901
+ }));
902
+ } catch (error) {
903
+ setState((prev) => ({ ...prev, status: 'error', error: String(error?.message ?? error) }));
904
+ }
905
+ }, []);
906
+ React.useEffect(() => {
907
+ let alive = true;
908
+ void load();
909
+ const timer = setInterval(() => { if (alive) void load(); }, 4000);
910
+ return () => { alive = false; clearInterval(timer); };
911
+ }, [load]);
912
+ const post = async (path, body) => {
913
+ const response = await fetch(path, {
914
+ method: 'POST',
915
+ credentials: 'same-origin',
916
+ headers: { 'content-type': 'application/json' },
917
+ body: JSON.stringify(body ?? {}),
918
+ });
919
+ const payload = await response.json().catch(() => null);
920
+ if (!response.ok || payload?.ok === false) throw new Error(payload?.error ?? 'HTTP ' + response.status);
921
+ return payload;
922
+ };
923
+ const act = async (kind, ticket, option) => {
924
+ setState((prev) => ({ ...prev, busy: ticket.id, error: '' }));
925
+ try {
926
+ await post('/api/w2m/decisions/' + kind, kind === 'resolve'
927
+ ? { id: ticket.id, option, by: 'w2m-gpw' }
928
+ : { id: ticket.id, by: 'w2m-gpw' });
929
+ } catch (error) {
930
+ setState((prev) => ({ ...prev, error: String(error?.message ?? error) }));
931
+ } finally {
932
+ setState((prev) => ({ ...prev, busy: '' }));
933
+ await load();
934
+ }
935
+ };
936
+ const advise = async (ticket) => {
937
+ setState((prev) => ({ ...prev, busy: 'ai:' + ticket.id, error: '' }));
938
+ try {
939
+ const payload = await post('/api/w2m/decisions/ai/advise', { id: ticket.id });
940
+ const text = payload?.advice?.text ?? payload?.advice ?? payload?.ticket?.advice ?? '(无建议文本)';
941
+ setState((prev) => ({ ...prev, advice: { ...prev.advice, [ticket.id]: typeof text === 'string' ? text : JSON.stringify(text) } }));
942
+ } catch (error) {
943
+ setState((prev) => ({ ...prev, error: String(error?.message ?? error) }));
944
+ } finally {
945
+ setState((prev) => ({ ...prev, busy: '' }));
946
+ }
947
+ };
948
+ const toggle = (level) => setState((prev) => ({ ...prev, collapsed: { ...prev.collapsed, [level]: !prev.collapsed[level] } }));
949
+ const pending = state.items.filter((item) => item.status === 'pending');
950
+ const title = '决策中心' + (pending.length > 0 ? ' · ' + pending.length + ' 待处理' : '');
951
+ const ticketRow = (ticket) => {
952
+ const options = Array.isArray(ticket.options) ? ticket.options : [];
953
+ return h('div', { key: ticket.id, className: 'w2mDc_ticket' }, [
954
+ h('div', { key: 'head', className: 'w2mDc_head' }, [
955
+ h('span', { key: 'dot', className: 'w2mGpw_dot', 'data-c': ticket.level ?? 'blue' }),
956
+ h('span', { key: 'title', className: 'w2mDc_title', title: ticket.title ?? '' }, ticket.title ?? '(未命名)'),
957
+ h('span', { key: 'meta', className: 'w2mDc_meta' }, (ticket.source?.agentRole ?? ticket.sourceRole ?? '') + (ticket.status !== 'pending' ? ' · ' + ticket.status : '')),
958
+ ]),
959
+ ticket.context ? h('div', { key: 'ctx', className: 'w2mDc_context' }, String(ticket.context)) : null,
960
+ h('div', { key: 'actions', className: 'w2mDc_actions' }, [
961
+ ...options.slice(0, 4).map((option) => h('button', {
962
+ key: 'opt:' + option.value,
963
+ type: 'button',
964
+ className: 'w2mGpw_btn',
965
+ disabled: state.busy !== '' || ticket.status !== 'pending',
966
+ onClick: () => void act('resolve', ticket, option.value),
967
+ }, option.label ?? option.value)),
968
+ ticket.status === 'pending' ? h('button', {
969
+ key: 'ai',
970
+ type: 'button',
971
+ className: 'w2mGpw_btn',
972
+ disabled: state.busy !== '',
973
+ onClick: () => void advise(ticket),
974
+ }, 'AI 建议') : null,
975
+ ticket.status === 'pending' ? h('button', {
976
+ key: 'abandon',
977
+ type: 'button',
978
+ className: 'w2mGpw_btn',
979
+ disabled: state.busy !== '' || options.length > 0,
980
+ onClick: () => void act('abandon', ticket),
981
+ }, '放弃') : null,
982
+ ]),
983
+ state.advice[ticket.id] ? h('div', { key: 'advice', className: 'w2mDc_advice' }, state.advice[ticket.id]) : null,
984
+ ]);
985
+ };
986
+ if (state.status === 'absent') {
987
+ return h('div', { className: 'w2mGpw_block' }, [
988
+ h('div', { key: 't', className: 'w2mGpw_title' }, '决策中心'),
989
+ h('div', { key: 'h', className: 'w2mGpw_hint' }, '决策中心插件未启用:在 profile 的 dsh.profile.bundles 或 patch 中启用 @twinsearth/w2m-dsh-plugin/decision-center'),
990
+ ]);
991
+ }
992
+ return h('div', { className: 'w2mGpw_block' }, [
993
+ h('div', { key: 'head', className: 'w2mGpw_row' }, [
994
+ h('span', { key: 't', className: 'w2mGpw_title' }, title),
995
+ h('button', { key: 'toggle', type: 'button', className: 'w2mGpw_btn', onClick: () => setState((prev) => ({ ...prev, open: !prev.open })) }, state.open ? '收起' : '展开'),
996
+ h('button', { key: 'reload', type: 'button', className: 'w2mGpw_btn', onClick: () => void load() }, '刷新'),
997
+ ]),
998
+ state.error !== '' ? h('div', { key: 'error', className: 'w2mGpw_note' }, '决策中心不可用:' + state.error) : null,
999
+ state.status === 'loading' ? h('div', { key: 'loading', className: 'w2mGpw_hint' }, '正在读取决策队列…') : null,
1000
+ state.open ? h('div', { key: 'levels' }, LEVELS.map(([level, label, hint]) => {
1001
+ const tickets = state.items.filter((item) => (item.level ?? 'blue') === level && (level === 'gray' ? item.status !== 'pending' : true));
1002
+ if (tickets.length === 0) return null;
1003
+ const collapsed = state.collapsed[level] === true;
1004
+ return h('div', { key: level, className: 'w2mDc_level' }, [
1005
+ h('div', { key: 'h', className: 'w2mGpw_row' }, [
1006
+ h('span', { key: 'dot', className: 'w2mGpw_dot', 'data-c': level }),
1007
+ h('span', { key: 'label', className: 'w2mGpw_title' }, label + ' · ' + tickets.length),
1008
+ h('span', { key: 'hint', className: 'w2mGpw_hint' }, hint),
1009
+ h('button', { key: 'fold', type: 'button', className: 'w2mGpw_small', onClick: () => toggle(level) }, collapsed ? '+' : '-'),
1010
+ ]),
1011
+ collapsed ? null : h('div', { key: 'list', className: 'w2mDc_level' }, tickets.map(ticketRow)),
1012
+ ]);
1013
+ })) : null,
1014
+ ]);
1015
+ }
1016
+
858
1017
  /** v1.9.4: the left sidebar entry: globe + GPW, between Plugins and Automation. */
859
1018
  function GpwSidebarIcon() {
860
1019
  return h('button', {
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Package-facing entry for W2M's decision center: `@twinsearth/w2m-dsh-plugin/decision-center`.
3
+ *
4
+ * The documented subpath a profile row names to mount the center. A seam, not a second
5
+ * implementation -- the code lives in `src/decision-center/`, and `mount.mjs` is where the ported
6
+ * plugin module and its HTTP routes become one mountable unit.
7
+ *
8
+ * Like GPW, it is deliberately **not** re-exported from `lib/index.js`: the package root installs by
9
+ * default, and a human-attention queue must not arrive because someone installed a fleet tool.
10
+ */
11
+ export { apply, inject, name, Config, _internals } from '../src/decision-center/mount.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "2.2.6",
3
+ "version": "3.0.0",
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": {
@@ -13,6 +13,7 @@
13
13
  "./package.json": "./package.json",
14
14
  "./gpw": "./lib/gpw.js",
15
15
  "./desktop": "./lib/desktop.js",
16
+ "./decision-center": "./lib/decision-center.js",
16
17
  "./locale/en.json": "./locale/en.json",
17
18
  "./locale/zh.json": "./locale/zh.json"
18
19
  },
@@ -72,7 +73,7 @@
72
73
  "pack": "node scripts/pack.mjs",
73
74
  "reachability": "node scripts/reachability-check.mjs",
74
75
  "reachability:classify": "node scripts/reachability-classify.mjs",
75
- "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/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 test/gpw-modes.test.mjs test/joint-cli.test.mjs test/joint-command-hash.test.mjs test/joint-dispatch-audit.test.mjs test/tools-cancel-auth.test.mjs test/fleet-mirror.test.mjs test/sink-git-ssh.test.mjs test/agent-sse-watchdog.test.mjs test/version-gate.test.mjs test/joint-merge-cli.test.mjs test/joint-progress.test.mjs test/project-room-truncation.test.mjs test/chat-crypto-aad.test.mjs test/agent-artifact-delivery.test.mjs test/dataplane-sink-route.test.mjs test/artifact-sink-e2e.test.mjs test/project-room-persistence.test.mjs test/project-room-disk-persistence.test.mjs test/project-stream-route.test.mjs test/chat-v2-gate.test.mjs test/xiangqi.test.mjs test/xfer-foundation.test.mjs test/xfer-ferry.test.mjs test/xfer-publish.test.mjs test/xfer-want-block.test.mjs test/p2p-cipher-diag.test.mjs test/chacha-fallback.test.mjs test/fleet-binding.test.mjs test/fleet-home.test.mjs test/fleet-config.test.mjs test/fleet-config-route.test.mjs test/xfer-channel.test.mjs test/p2p-race.test.mjs test/path-scheduler.test.mjs test/transport.test.mjs test/iroh-sidecar.test.mjs test/discovery.test.mjs test/p2p-rtt.test.mjs test/artifact-ref-v3.test.mjs test/xfer-recovery.test.mjs test/xfer-router.test.mjs test/remote-s2-capture.test.mjs test/view-capture.test.mjs test/view-frame-channel.test.mjs test/view-consent.test.mjs test/remote-view-route.test.mjs test/remote-media.test.mjs test/view-session-key.test.mjs test/remote-requests.test.mjs test/relay-target.test.mjs",
76
+ "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/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 test/gpw-modes.test.mjs test/joint-cli.test.mjs test/joint-command-hash.test.mjs test/joint-dispatch-audit.test.mjs test/tools-cancel-auth.test.mjs test/fleet-mirror.test.mjs test/sink-git-ssh.test.mjs test/agent-sse-watchdog.test.mjs test/version-gate.test.mjs test/joint-merge-cli.test.mjs test/joint-progress.test.mjs test/project-room-truncation.test.mjs test/chat-crypto-aad.test.mjs test/agent-artifact-delivery.test.mjs test/dataplane-sink-route.test.mjs test/artifact-sink-e2e.test.mjs test/project-room-persistence.test.mjs test/project-room-disk-persistence.test.mjs test/project-stream-route.test.mjs test/chat-v2-gate.test.mjs test/xiangqi.test.mjs test/xfer-foundation.test.mjs test/xfer-ferry.test.mjs test/xfer-publish.test.mjs test/xfer-want-block.test.mjs test/p2p-cipher-diag.test.mjs test/chacha-fallback.test.mjs test/fleet-binding.test.mjs test/fleet-home.test.mjs test/fleet-config.test.mjs test/fleet-config-route.test.mjs test/xfer-channel.test.mjs test/p2p-race.test.mjs test/path-scheduler.test.mjs test/transport.test.mjs test/iroh-sidecar.test.mjs test/discovery.test.mjs test/p2p-rtt.test.mjs test/artifact-ref-v3.test.mjs test/xfer-recovery.test.mjs test/xfer-router.test.mjs test/remote-s2-capture.test.mjs test/view-capture.test.mjs test/view-frame-channel.test.mjs test/view-consent.test.mjs test/remote-view-route.test.mjs test/remote-media.test.mjs test/view-session-key.test.mjs test/remote-requests.test.mjs test/relay-target.test.mjs test/decision-center.test.mjs test/decision-routes.test.mjs test/decision-outbox.test.mjs",
76
77
  "test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
77
78
  "test:account-identity": "node --test --test-force-exit test/account-identity.test.mjs",
78
79
  "test:account-join": "node --test --test-force-exit test/account-join.test.mjs",
@@ -171,7 +172,8 @@
171
172
  "test:grant-issue": "node --test --test-force-exit test/grant-issue.test.mjs",
172
173
  "test:project-refs": "node --test --test-force-exit test/project-refs.test.mjs",
173
174
  "test:secondary-link": "node --test --test-force-exit test/secondary-link.test.mjs",
174
- "verify:fleet-isolation": "node scripts/verify-fleet-isolation.mjs"
175
+ "verify:fleet-isolation": "node scripts/verify-fleet-isolation.mjs",
176
+ "test:decision-center": "node --test --test-force-exit test/decision-center.test.mjs test/decision-routes.test.mjs test/decision-outbox.test.mjs"
175
177
  },
176
178
  "devDependencies": {
177
179
  "@eslint/js": "^10.0.1",
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.2.6",
4
+ "version": "3.0.0",
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": [