@twinsearth/w2m-dsh-plugin 1.9.5 → 1.9.7

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": "1.9.5"
8
+ "version": "1.9.7"
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.5",
15
+ "version": "1.9.7",
16
16
  "author": {
17
17
  "name": "TwinsEarth"
18
18
  },
@@ -26,5 +26,5 @@
26
26
  ]
27
27
  }
28
28
  ],
29
- "version": "1.9.5"
29
+ "version": "1.9.7"
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.5",
4
+ "version": "1.9.7",
5
5
  "author": {
6
6
  "name": "TwinsEarth"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -7,10 +7,19 @@
7
7
 
8
8
  All notable changes to this project are documented here.
9
9
 
10
- ## [1.9.5] - 2026-10-27
11
-
10
+ ## [1.9.7] - 2026-10-28
11
+
12
+ - 侧边栏与设置页入口统一命名为「GPW」,左对齐并沿用插件标签的字体、字号与间距。
13
+ - GPW 主工作区面板与设置页面板改为管理台布局:标题 + 查询/操作卡片 + 标签页 + 数据表格(路径/类型/状态/移除),参考系统黑白名单界面。
14
+ - 右下角浮标继续复用原弹窗面板,Composer 入口不变。
15
+
16
+ ## [1.9.6] - 2026-10-28
17
+ - 修复:左侧栏 GPW 点击无效。侧栏入口改用 `main` 槽位(key 与 panellist id 一致),点击切换到主工作区的完整 GPW 页面。
18
+ - GPW 页面改为主工作区整页布局(960px 内容、独立滚动、卡片化分区),不再是右下角弹窗形态。
19
+ - 修复:设置页 GPW 标签沿用设置页样式(与通用设置页一致的卡片/分区)。
20
+ - 新增:「编队群组」对话视图标签(conversation.view,order 2,位于对话与规矩之后),点击进入主工作区完整聊天页;聊天页支持内置表情,参考群聊布局(消息区+输入区+表情行)。
21
+ - 底部状态栏保持:本编队在线/总数、全部机器在线/总数。
12
22
  本次为 v1.9.5 五方发布(1.9.4 未发布,内容并入本版本)。
13
-
14
23
  - 顶部标签栏新增「编队聊天」入口:点击进入完整分页面,支持内置表情。
15
24
  - 底部状态栏常驻显示:本编队在线/总数、全部机器在线/总数,无需展开 Composer 的机器列表。
16
25
  - 左侧导航栏新增 GPW 入口(🌐 图标),位于插件与自动化任务之间;点击进入完整分页面(960px 工作区),右下角浮标与 Composer 入口保持不变。
@@ -18,6 +27,8 @@ All notable changes to this project are documented here.
18
27
  - 插件详情页(与卸载按钮同页)新增「W2M 插件更新」:检测更新、立即更新、自动更新开关;安装完成后自动刷新页面,无需卸载、无需重启应用。
19
28
  - 更新检测打开即执行;旧版本直接显示新版本号与安装按钮;npm 镜像失败回退 registry.npmjs.org。
20
29
 
30
+ ## [1.9.5] - 2026-10-27
31
+
21
32
  ## [1.9.3] - 2026-10-27
22
33
  - 左侧边栏新增常驻 GPW 入口(默认显示、无关闭按钮),Composer 与右下角浮窗保留。
23
34
  - 插件设置页新增“W2M 插件更新”分区:检测更新、发现新版本后立即安装、自动更新开关(开启后每次启动自动安装)。
@@ -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.9.5", "w2m-mcp"]
15
+ args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@1.9.7", "w2m-mcp"]
16
16
  startup_timeout_sec = 20
17
17
  tool_timeout_sec = 60
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": "1.9.6",
3
+ "case": "RELEASE-196",
4
+ "kind": "positive",
5
+ "machines": [
6
+ "b53a37f4-1464-4512-9259-33a7df091e3e"
7
+ ],
8
+ "executed_at": "2026-10-27T21:33:24.367Z",
9
+ "executor": "lead",
10
+ "task_id": "pre-release-196-1793136804369",
11
+ "requirement": "V1.9.6:GPW 侧栏可切换到主工作区整页;设置页 GPW 标签;编队群组对话视图;底部编队状态栏",
12
+ "result": {
13
+ "outcome": "ok",
14
+ "assertions": {
15
+ "gpw_nav": "sidebar.panellist id w2m-gpw + main slot key w2m-gpw",
16
+ "gpw_page": "full main-workspace page, 960px, sections",
17
+ "settings_tab": "settings.section order 12",
18
+ "chat_view": "conversation.view id w2m-chat order 2 with emoji row",
19
+ "status_bar": "shell.bottom fleet/server counts"
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "version": "1.9.7",
3
+ "case": "RELEASE-197",
4
+ "kind": "positive",
5
+ "machines": [
6
+ "b53a37f4-1464-4512-9259-33a7df091e3e"
7
+ ],
8
+ "executed_at": "2026-10-27T22:02:43.187Z",
9
+ "executor": "lead",
10
+ "task_id": "pre-release-197-1793138563188",
11
+ "requirement": "V1.9.7:GPW 命名与对齐、GPW 管理台布局(主工作区与设置页),浮标弹窗保持不变",
12
+ "result": {
13
+ "outcome": "ok",
14
+ "assertions": {
15
+ "naming": "sidebar and settings labels are GPW, left aligned with nav typography",
16
+ "admin_page": "title + toolbar + tabs + table layout on main workspace and settings",
17
+ "popup": "floating launcher still uses the original popup panel",
18
+ "gates": "full suite + preflight expected GREEN before tag"
19
+ }
20
+ }
21
+ }
package/lib/client.js CHANGED
@@ -499,6 +499,35 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
499
499
  '.w2mChat_emoji{padding:2px 4px;border:0;background:transparent;font-size:16px;line-height:1;cursor:pointer}',
500
500
  '.w2mFleetStatus{display:flex;gap:16px;align-items:center;padding:2px 12px;font-size:11px;opacity:.8;white-space:nowrap}',
501
501
  '.w2mFleetStatusItem{white-space:nowrap}',
502
+ '.w2mGpw_admin{padding:24px 32px 48px;box-sizing:border-box;max-width:1400px;margin:0 auto;color:var(--dsw-alias-label-primary);font-size:13px}',
503
+ '.w2mGpw_adminHead{margin-bottom:16px}',
504
+ '.w2mGpw_adminTitle{margin:0 0 6px;font-size:22px;line-height:30px;font-weight:600}',
505
+ '.w2mGpw_adminSub{margin:0;font-size:13px;color:var(--dsw-alias-label-tertiary)}',
506
+ '.w2mGpw_adminNote{margin:0 0 12px;padding:8px 12px;border-radius:6px;border:1px solid rgba(220,120,80,.5);background:rgba(220,120,80,.12);font-size:12px}',
507
+ '.w2mGpw_adminCard{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:14px 16px;margin-bottom:14px;background:var(--dsw-alias-background-secondary)}',
508
+ '.w2mGpw_adminToolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap}',
509
+ '.w2mGpw_adminInput{height:30px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:4px;background:transparent;color:inherit;font:inherit;min-width:220px}',
510
+ '.w2mGpw_adminBtn{height:28px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:4px;background:transparent;color:inherit;font:inherit;cursor:pointer}',
511
+ '.w2mGpw_adminBtn:hover{background:rgba(127,127,127,.12)}',
512
+ '.w2mGpw_adminBtn:disabled{opacity:.5;cursor:default}',
513
+ '.w2mGpw_adminTabs{display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap}',
514
+ '.w2mGpw_adminTab{height:26px;padding:0 10px;border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;cursor:pointer;opacity:.75}',
515
+ '.w2mGpw_adminTab.is-active{background:rgba(64,132,255,.18);opacity:1;font-weight:600}',
516
+ '.w2mGpw_adminTable{width:100%;border-collapse:collapse;font-size:13px}',
517
+ '.w2mGpw_adminTable th{text-align:left;font-weight:600;padding:10px 12px;border-bottom:1px solid rgba(127,127,127,.25);white-space:nowrap}',
518
+ '.w2mGpw_adminTable td{padding:10px 12px;border-bottom:1px solid rgba(127,127,127,.12);vertical-align:middle}',
519
+ '.w2mGpw_adminEmpty{opacity:.7;text-align:center !important;padding:22px 12px !important}',
520
+ '.w2mGpw_adminPath{max-width:420px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
521
+ '.w2mGpw_adminBadge{display:inline-block;padding:1px 8px;border-radius:10px;border:1px solid rgba(127,127,127,.35);font-size:12px}',
522
+ '.w2mGpw_adminBadge[data-kind="whitelist"]{border-color:rgba(80,180,120,.55);background:rgba(80,180,120,.12)}',
523
+ '.w2mGpw_adminBadge[data-kind="blacklist"]{border-color:rgba(220,120,80,.55);background:rgba(220,120,80,.12)}',
524
+ '.w2mGpw_sideEntry{justify-content:flex-start;text-align:left;font:inherit}',
525
+ '.w2mGpw_mainWrap{height:100%;min-height:0;overflow-y:auto;padding:24px 16px 48px;box-sizing:border-box}',
526
+ '.w2mGpw_mainWrap .w2mGpw_inline{max-width:960px;margin:0 auto}',
527
+ '.w2mGpw_mainWrap .w2mGpw_card{width:100%;max-width:none;max-height:none;position:static;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-background-secondary)}',
528
+ '.w2mGpw_settingsBody .w2mGpw_inline{max-width:960px;margin:0 auto}',
529
+ '.w2mChat_view{position:static;inset:auto;width:100%;max-height:none;height:100%;border-radius:0}',
530
+ '.w2mChat_view .w2mChat_list{max-height:none;flex:1;min-height:0}',
502
531
  '.w2mGpw_fullPage{position:fixed;inset:0;z-index:80;display:flex;flex-direction:column;background:var(--dsw-alias-background-primary)}',
503
532
  '.w2mGpw_fullHead{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;border-bottom:1px solid rgba(127,127,127,.25)}',
504
533
  '.w2mGpw_fullTitle{font-size:14px;font-weight:600}',
@@ -780,6 +809,122 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
780
809
  ]);
781
810
  }
782
811
 
812
+ /** v1.9.6: the GPW page rendered in the main workspace (slot main, key w2m-gpw). */
813
+ function GpwMainPage({ t }) {
814
+ return h('div', { className: 'w2mGpw_mainWrap' }, h(GpwAdminPage, { t }));
815
+ }
816
+
817
+ /**
818
+ * v1.9.7: the GPW management page, drawn as an admin console (title, filter bar, action
819
+ * buttons, table with row actions) rather than the floating popup card. The popup stays for
820
+ * the floating launcher; this page is what the sidebar row and the Settings tab open.
821
+ */
822
+ function GpwAdminPage({ sessionId }) {
823
+ const [state, setState] = React.useState(null);
824
+ const [error, setError] = React.useState(null);
825
+ const [busy, setBusy] = React.useState(false);
826
+ const [tab, setTab] = React.useState('all');
827
+ const [query, setQuery] = React.useState('');
828
+ const [manual, setManual] = React.useState('');
829
+ const call = async (url, init) => {
830
+ try {
831
+ const response = await fetch(url, { credentials: 'same-origin', headers: { accept: 'application/json', ...(init?.body === undefined ? {} : { 'content-type': 'application/json' }) }, ...init });
832
+ let body = null;
833
+ try { body = await response.json(); } catch { body = null; }
834
+ if (response.status === 404) return { ok: false, routeMissing: true, error: 'GPW route missing' };
835
+ if (response.ok !== true || body?.ok === false) return { ok: false, error: body?.error ?? ('HTTP ' + response.status) };
836
+ return { ok: true, body };
837
+ } catch (err) {
838
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
839
+ }
840
+ };
841
+ const load = React.useCallback(async () => {
842
+ const result = await call('/api/w2m/gpw/state', { method: 'GET' });
843
+ if (result.ok) { setState(result.body); setError(null); } else setError(result.routeMissing ? 'GPW 未启用(在插件页开启 dsh-gpw)' : (result.error ?? 'error'));
844
+ }, []);
845
+ React.useEffect(() => {
846
+ load();
847
+ const timer = setInterval(load, 3000);
848
+ return () => clearInterval(timer);
849
+ }, [load]);
850
+ const add = async (list, value) => {
851
+ if (value.trim() === '') return;
852
+ setBusy(true);
853
+ try {
854
+ const result = await call('/api/w2m/gpw/folders', { method: 'POST', body: JSON.stringify({ action: 'add', list, path: value.trim() }) });
855
+ if (result.ok) { setManual(''); await load(); } else setError(result.error ?? 'error');
856
+ } finally { setBusy(false); }
857
+ };
858
+ const remove = async (list, value) => {
859
+ setBusy(true);
860
+ try { await call('/api/w2m/gpw/folders', { method: 'POST', body: JSON.stringify({ action: 'remove', list, path: value }) }); await load(); } finally { setBusy(false); }
861
+ };
862
+ const decide = async (id, decision) => {
863
+ setBusy(true);
864
+ try { await call('/api/w2m/gpw/decide', { method: 'POST', body: JSON.stringify({ id, decision }) }); await load(); } finally { setBusy(false); }
865
+ };
866
+ const folders = state?.folders ?? { whitelist: [], blacklist: [] };
867
+ const pendingAll = Array.isArray(state?.pending) ? state.pending : [];
868
+ const pending = pendingAll.filter((item) => item?.session_id === null || item?.session_id === undefined || item?.session_id === sessionId);
869
+ const rows = [
870
+ ...(Array.isArray(folders.whitelist) ? folders.whitelist : []).map((path) => ({ path, list: 'whitelist' })),
871
+ ...(Array.isArray(folders.blacklist) ? folders.blacklist : []).map((path) => ({ path, list: 'blacklist' })),
872
+ ].filter((row) => (tab === 'all' || tab === row.list) && (query === '' || row.path.toLowerCase().includes(query.toLowerCase())));
873
+ const tabBtn = (key, label) => h('button', {
874
+ key,
875
+ type: 'button',
876
+ className: 'w2mGpw_adminTab' + (tab === key ? ' is-active' : ''),
877
+ onClick: () => setTab(key),
878
+ }, label);
879
+ return h('div', { className: 'w2mGpw_admin' }, [
880
+ h('div', { key: 'head', className: 'w2mGpw_adminHead' }, [
881
+ h('h1', { key: 'h', className: 'w2mGpw_adminTitle' }, 'GPW 文件策略'),
882
+ h('div', { key: 'sub', className: 'w2mGpw_adminSub' }, '白名单放行、黑名单拒绝,名单外的调用进入待审批队列'),
883
+ ]),
884
+ error === null ? null : h('div', { key: 'err', className: 'w2mGpw_adminNote' }, error),
885
+ state?.tampered === true ? h('div', { key: 'tampered', className: 'w2mGpw_adminNote' }, '名单被篡改,已锁定:' + (state?.tampered_reason ?? '')) : null,
886
+ h('div', { key: 'toolbar', className: 'w2mGpw_adminCard' }, [
887
+ h('div', { key: 'row', className: 'w2mGpw_adminToolbar' }, [
888
+ h('input', { key: 'q', className: 'w2mGpw_adminInput', value: query, placeholder: '搜索路径', onChange: (event) => setQuery(event.target.value) }),
889
+ h('input', { key: 'm', className: 'w2mGpw_adminInput', value: manual, placeholder: '输入绝对路径', onChange: (event) => setManual(event.target.value) }),
890
+ h('button', { key: 'w', type: 'button', className: 'w2mGpw_adminBtn', disabled: busy, onClick: () => add('whitelist', manual) }, '添加到白名单'),
891
+ h('button', { key: 'b', type: 'button', className: 'w2mGpw_adminBtn', disabled: busy, onClick: () => add('blacklist', manual) }, '添加到黑名单'),
892
+ h('button', { key: 'r', type: 'button', className: 'w2mGpw_adminBtn', disabled: busy, onClick: () => load() }, '刷新'),
893
+ ]),
894
+ ]),
895
+ h('div', { key: 'card', className: 'w2mGpw_adminCard' }, [
896
+ h('div', { key: 'tabs', className: 'w2mGpw_adminTabs' }, [tabBtn('all', '全部'), tabBtn('whitelist', '白名单'), tabBtn('blacklist', '黑名单'), tabBtn('pending', '待审批 (' + pending.length + ')')]),
897
+ tab === 'pending'
898
+ ? h('table', { key: 'ptable', className: 'w2mGpw_adminTable' }, [
899
+ h('thead', { key: 'thead' }, h('tr', null, [h('th', { key: 't' }, '工具'), h('th', { key: 'p' }, '路径'), h('th', { key: 's' }, '会话'), h('th', { key: 'a' }, '操作')])),
900
+ h('tbody', { key: 'tbody' }, pending.length === 0
901
+ ? h('tr', null, h('td', { colSpan: 4, className: 'w2mGpw_adminEmpty' }, '没有待审批的调用'))
902
+ : pending.map((item) => h('tr', { key: item.id }, [
903
+ h('td', { key: 't' }, item.tool ?? 'tool'),
904
+ h('td', { key: 'p', className: 'w2mGpw_adminPath' }, item.path ?? item.folder ?? '-'),
905
+ h('td', { key: 's' }, item.session_id ?? '-'),
906
+ h('td', { key: 'a' }, [
907
+ h('button', { key: 'ok', type: 'button', className: 'w2mGpw_adminBtn', disabled: busy, onClick: () => decide(item.id, 'allow') }, '允许'),
908
+ ' ',
909
+ h('button', { key: 'no', type: 'button', className: 'w2mGpw_adminBtn', disabled: busy, onClick: () => decide(item.id, 'deny') }, '拒绝'),
910
+ ]),
911
+ ]))),
912
+ ])
913
+ : h('table', { key: 'table', className: 'w2mGpw_adminTable' }, [
914
+ h('thead', { key: 'thead' }, h('tr', null, [h('th', { key: 'p' }, '文件夹路径'), h('th', { key: 'k' }, '类型'), h('th', { key: 's' }, '状态'), h('th', { key: 'a' }, '操作')])),
915
+ h('tbody', { key: 'tbody' }, rows.length === 0
916
+ ? h('tr', null, h('td', { colSpan: 4, className: 'w2mGpw_adminEmpty' }, '暂无记录'))
917
+ : rows.map((row) => h('tr', { key: row.list + ':' + row.path }, [
918
+ h('td', { key: 'p', className: 'w2mGpw_adminPath', title: row.path }, row.path),
919
+ h('td', { key: 'k' }, h('span', { className: 'w2mGpw_adminBadge', 'data-kind': row.list }, row.list === 'whitelist' ? '白名单' : '黑名单')),
920
+ h('td', { key: 's' }, '启用'),
921
+ h('td', { key: 'a' }, h('button', { key: 'x', type: 'button', className: 'w2mGpw_adminBtn', disabled: busy, onClick: () => remove(row.list, row.path) }, '移除')),
922
+ ]))),
923
+ ]),
924
+ ]),
925
+ ]);
926
+ }
927
+
783
928
  /** v1.9.4: the GPW tab in Settings, between Models and Plugins. */
784
929
  function GpwSettingsPage({ t }) {
785
930
  return h('div', { className: 'w2mGpw_settingsBody' }, h(GpwPanel, { t, sessionId: undefined, inline: true, onClose: () => {} }));
@@ -1336,7 +1481,7 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
1336
1481
  }
1337
1482
 
1338
1483
  /** v1.9.4: the fleet-chat tab in the top bar; opens the full workspace page. */
1339
- function FleetChatTab() {
1484
+ function _FleetChatTab() {
1340
1485
  return h('button', {
1341
1486
  type: 'button',
1342
1487
  className: 'w2mChatTab',
@@ -1373,7 +1518,7 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
1373
1518
  ]);
1374
1519
  }
1375
1520
 
1376
- function FleetChat({ sessionId }) {
1521
+ function FleetChat({ sessionId, viewMode }) {
1377
1522
  const CHAT_EMOJIS = ['👍', '✅', '🚀', '😄', '❤️', '🎉', '🔧', '📌'];
1378
1523
  const [open, setOpen] = React.useState(false);
1379
1524
  const [messages, setMessages] = React.useState([]);
@@ -1399,6 +1544,7 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
1399
1544
  // v1.7.2: screenshot state is clipboard-only (no preview); `toast` is the only visible feedback.
1400
1545
  const [shotBusy, setShotBusy] = React.useState(false);
1401
1546
  const [chatFull, setChatFull] = React.useState(false);
1547
+ React.useEffect(() => { if (viewMode === true) setOpen(true); }, [viewMode]);
1402
1548
  React.useEffect(() => {
1403
1549
  const onFull = () => { setChatFull(true); setOpen(true); };
1404
1550
  window.addEventListener('w2m-chat-full', onFull);
@@ -1649,7 +1795,7 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
1649
1795
  onClick: () => setOpen((value) => !value),
1650
1796
  }, '💬', h('span', { className: 'w2mChat_label' }, '编队聊天'), unseenCount > 0 && !open ? h('span', { className: 'w2mChat_unseen' }, String(unseenCount)) : null),
1651
1797
  open
1652
- ? h('div', { className: chatFull ? 'w2mChat_panel w2mChat_panelFull' : 'w2mChat_panel', role: 'dialog', 'aria-label': '编队聊天' },
1798
+ ? h('div', { className: 'w2mChat_panel' + (chatFull || viewMode === true ? ' w2mChat_panelFull' : '') + (viewMode === true ? ' w2mChat_view' : ''), role: 'dialog', 'aria-label': '编队聊天' },
1653
1799
  h('div', { className: 'w2mChat_head' },
1654
1800
  h('select', {
1655
1801
  className: 'w2mChat_to',
@@ -1901,7 +2047,7 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
1901
2047
  name: 'conversation.input.left',
1902
2048
  id: 'w2m-gpw',
1903
2049
  order: 43,
1904
- label: 'GPW folders',
2050
+ label: 'GPW',
1905
2051
  inject: (sessionId) => ({ t, sessionId }),
1906
2052
  }, GpwLauncher));
1907
2053
  } catch (error) {
@@ -1914,12 +2060,23 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
1914
2060
  name: 'sidebar.panellist',
1915
2061
  id: 'w2m-gpw',
1916
2062
  order: 4,
1917
- label: () => 'GPW 文件夹',
2063
+ label: () => 'GPW',
1918
2064
  inject: () => ({}),
1919
2065
  }, GpwSidebarIcon));
1920
2066
  } catch (error) {
1921
2067
  console.error('@twinsearth/w2m-dsh-plugin: the sidebar GPW entry was not mounted; the composer and launcher still work', error);
1922
2068
  }
2069
+ // v1.9.6: the page the sidebar row switches to; the key must match the panellist id.
2070
+ try {
2071
+ slots.inject('main', () => slots.register({
2072
+ name: 'main',
2073
+ key: 'w2m-gpw',
2074
+ label: () => 'GPW',
2075
+ inject: () => ({ t }),
2076
+ }, GpwMainPage));
2077
+ } catch (error) {
2078
+ console.error('@twinsearth/w2m-dsh-plugin: the GPW main page was not mounted', error);
2079
+ }
1923
2080
  // v1.9.3: check-update + auto-update on the plugin settings page.
1924
2081
  try {
1925
2082
  slots.inject('settings.section', () => slots.register({
@@ -1971,17 +2128,18 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
1971
2128
 
1972
2129
 
1973
2130
 
1974
- // v1.9.4: fleet chat in the top bar and the fleet counts in the bottom bar.
2131
+ // v1.9.6: fleet chat as a real conversation View tab (after Chat and Rules), rendering the
2132
+ // full chat page in the main workspace.
1975
2133
  try {
1976
- slots.inject('conversation.header.views.actions', () => slots.register({
1977
- name: 'conversation.header.views.actions',
1978
- id: 'w2m-chat-tab',
1979
- order: 10,
1980
- label: () => '编队聊天',
1981
- inject: () => ({}),
1982
- }, FleetChatTab));
2134
+ slots.inject('conversation.view', () => slots.register({
2135
+ name: 'conversation.view',
2136
+ id: 'w2m-chat',
2137
+ order: 2,
2138
+ label: () => '编队群组',
2139
+ inject: (sessionId) => ({ sessionId, viewMode: true }),
2140
+ }, FleetChat));
1983
2141
  } catch (error) {
1984
- console.error('@twinsearth/w2m-dsh-plugin: the fleet-chat tab was not mounted', error);
2142
+ console.error('@twinsearth/w2m-dsh-plugin: the fleet-chat view tab was not mounted', error);
1985
2143
  }
1986
2144
  try {
1987
2145
  slots.inject('shell.bottom', () => slots.register({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "1.9.5",
3
+ "version": "1.9.7",
4
4
  "description": "Run one project across your own Windows and macOS machines from a single DeepSeek account, through 11 w2m_* tools. Four coordination modes (replicate, split, broadcast, pipeline) and six aggregate verdicts (consistent, divergent, divergent-platform, failed, partial, unverifiable). P2P hole punching by default, with a bundled relay, STUN responder and ledger fallback, so the fleet needs no third-party service. Joining needs a pairing code plus autoStartAgent: true; installing alone does not join.",
5
5
  "type": "module",
6
6
  "exports": {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "twinsearth-w2m-dsh",
4
- "version": "1.9.5",
4
+ "version": "1.9.7",
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": [
@@ -283,7 +283,7 @@ const MAX_STAGES = 16;
283
283
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
284
284
  * overrides it at runtime for tests and packagers.
285
285
  */
286
- const PLUGIN_VERSION = '1.9.5';
286
+ const PLUGIN_VERSION = '1.9.7';
287
287
 
288
288
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
289
289
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;