@twinsearth/w2m-dsh-plugin 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +22 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/1.8.1/GPW-RECENT-FOLD.json +21 -0
- package/docs/acceptance/1.8.2/GPW-AUTO-APPROVE.json +23 -0
- package/lib/client.js +24 -8
- package/package.json +3 -3
- package/plugin.json +1 -1
- package/src/plugin/gpw-colors.mjs +12 -1
- package/src/plugin/gpw.mjs +64 -14
- package/src/plugin/tools.mjs +1 -1
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "The w2m fleet plugins: one account, many machines, any of Windows, macOS or Linux.",
|
|
8
|
-
"version": "1.8.
|
|
8
|
+
"version": "1.8.2"
|
|
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.8.
|
|
15
|
+
"version": "1.8.2",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "1.8.
|
|
29
|
+
"version": "1.8.2"
|
|
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.8.
|
|
4
|
+
"version": "1.8.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@
|
|
|
7
7
|
|
|
8
8
|
All notable changes to this project are documented here.
|
|
9
9
|
|
|
10
|
+
## [1.8.2] — 2026-10-26
|
|
11
|
+
|
|
12
|
+
**GPW 超时策略:黄类(名单外)100 秒无人应答自动同意;黑白名单规则不变;已覆盖父目录的子文件夹拦截记录折叠。**
|
|
13
|
+
|
|
14
|
+
- **只读工具绿名单扩展**:`read/read_file/search/grep/glob/list/…`、`job_list/job_output/job_status/job_wait`、`w2m_status/w2m_devices/w2m_tasks/w2m_history/w2m_report/w2m_memory/w2m_stats` 判为绿色(无打扰放行);`job_kill/job_start/w2m_run/w2m_cancel/w2m_intent` 仍为黄色(可变更,需人工)。
|
|
15
|
+
- 超时默认值 120s → **100s**;超时结果由「拒绝」改为「自动同意」(仅黄类 / 名单外路径调用)。
|
|
16
|
+
- 红线(危险命令、敏感/系统路径、显式 deny)仍然立即拒绝,不进入待审批队列;白名单命中仍然立即放行。
|
|
17
|
+
因此超时规则不会扩大任何名单的授权范围。
|
|
18
|
+
- 自动同意的调用会记录为会话授权(指纹),同一操作的后续出现以蓝色(已授权常态)直接放行。
|
|
19
|
+
- 「最近被拦截」折叠逻辑确认覆盖父目录下的子文件夹:名单条目是根目录,其任意深度子文件夹的拦截记录都会进入
|
|
20
|
+
「已在名单中 N 条」折叠组(`folder_in` 双向包含匹配)。
|
|
21
|
+
- 新增 `test/gpw-auto-approve.test.mjs`(4 项:超时自动同意→下次变蓝;红线仍拒;白名单立即放行;100s 默认值)。
|
|
22
|
+
- 证据:`docs/acceptance/1.8.2/GPW-AUTO-APPROVE.json`。
|
|
23
|
+
## [1.8.1] — 2026-10-26
|
|
24
|
+
|
|
25
|
+
**GPW「最近被拦截」去噪:已在名单中的条目折叠显示;加入名单后条目立即消失。**
|
|
26
|
+
|
|
27
|
+
- 状态接口为每条 recent 增加 `folder_in`(whitelist / blacklist / null):文件夹已被名单覆盖的拦截记录不再占用主列表。
|
|
28
|
+
- 面板把已覆盖条目折叠为「已在名单中 N 条 · 展开/收起」,展开后只读展示(不重复给加入按钮)。
|
|
29
|
+
- 从最近拦截一键加入白/黑名单时,服务端同步裁剪该条目(以及被同一父目录覆盖的条目),下一次轮询不再出现。
|
|
30
|
+
- 新增 `test/gpw-recent-fold.test.mjs`(4 项:覆盖归属、嵌套覆盖、客户端折叠静态断言、状态接口字段)。
|
|
31
|
+
- 证据:`docs/acceptance/1.8.1/GPW-RECENT-FOLD.json`。
|
|
10
32
|
## [1.8.0] — 2026-10-26
|
|
11
33
|
|
|
12
34
|
**GPW 增加四色审核 / 标记 / 提醒系统:绿(通过)、蓝(正常)、黄(警报)、红(风险)。**
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.8.1",
|
|
3
|
+
"case": "GPW-RECENT-FOLD",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-26T06:55:26.042Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "local-gpw-fold-20261026065526",
|
|
11
|
+
"requirement": "recently-intercepted rows already covered by a list are collapsed, and a row the operator adds to a list disappears from the list immediately",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"folder_in": "the state route annotates each recent row with whitelist / blacklist / null",
|
|
16
|
+
"collapse": "the panel shows covered rows behind \"已在名单中 N 条 · 展开/收起\" and they carry no add buttons",
|
|
17
|
+
"disappear": "adding a folder from a recent row prunes that row and every row inside the folder server-side",
|
|
18
|
+
"tests": "test/gpw-recent-fold.test.mjs 4/4"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.8.2",
|
|
3
|
+
"case": "GPW-AUTO-APPROVE",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-26T07:41:28.057Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "local-gpw-timeout-20261026074128",
|
|
11
|
+
"requirement": "a class-C (yellow) call with no operator answer is approved after 100 s, while blacklist/whitelist rules and red denials are unchanged; subfolders of a listed parent folder are folded in recently-intercepted",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"timeout": "DEFAULT_PENDING_TIMEOUT_MS = 100_000; on expiry the pending call resolves allow and records a session grant",
|
|
16
|
+
"lists_unchanged": "red denies immediately (never queued); whitelist hits allow immediately (never queued)",
|
|
17
|
+
"next_call_blue": "the auto-approved fingerprint is added to session grants, so the identical next call runs immediately",
|
|
18
|
+
"fold_parents": "folder_in uses bidirectional containment: a child folder under a listed parent maps to that list and is collapsed",
|
|
19
|
+
"tests": "test/gpw-auto-approve.test.mjs 4/4",
|
|
20
|
+
"readonly_green": "read-only tools are green (read/grep/glob/web_*/status/health/metrics/inspect, job_list/job_output/job_status/job_wait, w2m_status/devices/tasks/history/report/memory/stats); mutating tools (job_kill/job_start/w2m_run/w2m_cancel/w2m_intent) stay yellow"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -569,7 +569,7 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
569
569
|
* ways: typed here and validated by the host, or taken from a recently intercepted call in one click. The lists
|
|
570
570
|
* live in $DSH_HOME/xclient/gpw-folders.json; no profile YAML is edited by this feature.
|
|
571
571
|
*/
|
|
572
|
-
function GpwPanel({ t, onClose }) {
|
|
572
|
+
function GpwPanel({ t, sessionId, onClose }) {
|
|
573
573
|
const [state, setState] = React.useState(null);
|
|
574
574
|
const [error, setError] = React.useState(null);
|
|
575
575
|
const [busy, setBusy] = React.useState(false);
|
|
@@ -650,8 +650,13 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
650
650
|
foldRow(items, list),
|
|
651
651
|
]);
|
|
652
652
|
};
|
|
653
|
-
const
|
|
654
|
-
const
|
|
653
|
+
const recentAll = Array.isArray(state?.recent) ? state.recent : [];
|
|
654
|
+
const recent = recentAll.filter((item) => item?.folder_in === null || item?.folder_in === undefined);
|
|
655
|
+
const recentInLists = recentAll.filter((item) => item?.folder_in === 'whitelist' || item?.folder_in === 'blacklist');
|
|
656
|
+
const [showRecentInLists, setShowRecentInLists] = React.useState(false);
|
|
657
|
+
const pendingAll = Array.isArray(state?.pending) ? state.pending : [];
|
|
658
|
+
// v1.8.1: an approval belongs to the conversation that raised it; another session must never show it.
|
|
659
|
+
const pending = pendingAll.filter((item) => item?.session_id === null || item?.session_id === undefined || item?.session_id === sessionId);
|
|
655
660
|
return h('div', { className: 'w2mGpw_overlay', role: 'dialog', 'aria-label': t('gpwTitle') }, [
|
|
656
661
|
h('div', { key: 'card', className: 'w2mGpw_card' }, [
|
|
657
662
|
h('div', { key: 'head', className: 'w2mGpw_head' }, [
|
|
@@ -700,6 +705,15 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
700
705
|
])),
|
|
701
706
|
foldRow(recent, 'recent'),
|
|
702
707
|
]),
|
|
708
|
+
recentInLists.length === 0 ? null : h('div', { key: 'recentInLists', className: 'w2mGpw_block' }, [
|
|
709
|
+
h('button', { key: 'toggle', type: 'button', className: 'w2mGpw_btn', 'aria-expanded': showRecentInLists, onClick: () => setShowRecentInLists((value) => !value) }, (showRecentInLists ? '收起' : '展开') + ' · 已在名单中 ' + recentInLists.length + ' 条'),
|
|
710
|
+
...(showRecentInLists ? recentInLists.map((item, index) => h('div', { key: 'inlist' + index, className: 'w2mGpw_recent' }, [
|
|
711
|
+
h('div', { key: 'r1', className: 'w2mGpw_row' }, [
|
|
712
|
+
h('span', { key: 'd', className: 'w2mGpw_dot', 'data-c': item.color ?? 'green' }),
|
|
713
|
+
h('span', { key: 't', className: 'w2mGpw_path' }, (item.folder ?? '') + ' · 已加入' + (item.folder_in === 'whitelist' ? '白名单' : '黑名单')),
|
|
714
|
+
]),
|
|
715
|
+
])) : []),
|
|
716
|
+
]),
|
|
703
717
|
pending.length === 0 ? null : h('div', { key: 'pending', className: 'w2mGpw_block' }, [
|
|
704
718
|
h('div', { key: 'title', className: 'w2mGpw_title' }, t('gpwPending')),
|
|
705
719
|
...pending.map((item) => h('div', { key: item.id, className: 'w2mGpw_recent' }, [
|
|
@@ -718,7 +732,7 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
718
732
|
}
|
|
719
733
|
|
|
720
734
|
/** The floating launcher: a pending badge, and the panel fixed to the lower-right corner. */
|
|
721
|
-
function GpwLauncher({ t }) {
|
|
735
|
+
function GpwLauncher({ t, sessionId }) {
|
|
722
736
|
const [open, setOpen] = React.useState(false);
|
|
723
737
|
const [pendingCount, setPendingCount] = React.useState(0);
|
|
724
738
|
React.useEffect(() => {
|
|
@@ -754,7 +768,7 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
754
768
|
t('gpwLauncher'),
|
|
755
769
|
pendingCount > 0 ? h('span', { key: 'badge', className: 'w2mGpw_badge' }, String(pendingCount)) : null,
|
|
756
770
|
]),
|
|
757
|
-
open ? h(GpwPanel, { key: 'panel', t, onClose: () => setOpen(false) }) : null,
|
|
771
|
+
open ? h(GpwPanel, { key: 'panel', t, sessionId, onClose: () => setOpen(false) }) : null,
|
|
758
772
|
]);
|
|
759
773
|
}
|
|
760
774
|
|
|
@@ -762,7 +776,7 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
762
776
|
* v1.7.9: the inline approval prompt -- an interception asks a question in the composer row
|
|
763
777
|
* (allow once / deny once / open the panel) instead of waiting silently for the operator.
|
|
764
778
|
*/
|
|
765
|
-
function GpwPrompt({ t }) {
|
|
779
|
+
function GpwPrompt({ t, sessionId }) {
|
|
766
780
|
const [state, setState] = React.useState(null);
|
|
767
781
|
const [busy, setBusy] = React.useState(false);
|
|
768
782
|
React.useEffect(() => {
|
|
@@ -783,7 +797,9 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
783
797
|
clearInterval(timer);
|
|
784
798
|
};
|
|
785
799
|
}, []);
|
|
786
|
-
const
|
|
800
|
+
const pendingAll = Array.isArray(state?.pending) ? state.pending : [];
|
|
801
|
+
// v1.8.1: an approval belongs to the conversation that raised it; another session must never show it.
|
|
802
|
+
const pending = pendingAll.filter((item) => item?.session_id === null || item?.session_id === undefined || item?.session_id === sessionId);
|
|
787
803
|
if (pending.length === 0) return null;
|
|
788
804
|
const item = pending[0];
|
|
789
805
|
const decide = async (decision) => {
|
|
@@ -1688,7 +1704,7 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
|
|
|
1688
1704
|
id: 'w2m-gpw',
|
|
1689
1705
|
order: 43,
|
|
1690
1706
|
label: 'GPW folders',
|
|
1691
|
-
inject: () => ({ t }),
|
|
1707
|
+
inject: (sessionId) => ({ t, sessionId }),
|
|
1692
1708
|
}, GpwLauncher));
|
|
1693
1709
|
} catch (error) {
|
|
1694
1710
|
console.error('@twinsearth/w2m-dsh-plugin: the GPW folder panel was not mounted; the rest of the page is unaffected', error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
4
4
|
"description": "Run one project across your own Windows and macOS machines from a single DeepSeek account, through 11 w2m_* tools. Four coordination modes (replicate, split, broadcast, pipeline) and six aggregate verdicts (consistent, divergent, divergent-platform, failed, partial, unverifiable). P2P hole punching by default, with a bundled relay, STUN responder and ledger fallback, so the fleet needs no third-party service. Joining needs a pairing code plus autoStartAgent: true; installing alone does not join.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"pack": "node scripts/pack.mjs",
|
|
65
65
|
"reachability": "node scripts/reachability-check.mjs",
|
|
66
66
|
"reachability:classify": "node scripts/reachability-classify.mjs",
|
|
67
|
-
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/blake2s.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.test.mjs test/fleet-memory-e2e.test.mjs test/fleet-concurrency.test.mjs test/fleet-merge-race.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/portable-plugins.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs test/admission.test.mjs test/admission-routes.test.mjs test/join-degradation.test.mjs test/account-identity.test.mjs test/join-challenge.test.mjs test/exec-env-injection.test.mjs test/account-join.test.mjs test/fleet-messages.test.mjs test/message-shards.test.mjs test/fleet-messages-http.test.mjs test/message-notify.test.mjs test/update-channels.test.mjs test/update-prerelease.test.mjs test/session-watch.test.mjs test/session-readonly.test.mjs test/session-sync.test.mjs test/fleet-chat-identity.test.mjs test/identity-reader.test.mjs test/intents.test.mjs test/dataflow.test.mjs test/council-fleet.test.mjs test/fleet-memory.test.mjs test/memory-view.test.mjs test/schema-spec.test.mjs test/fleet-weight-parity.test.mjs test/task-deps.test.mjs test/dataflow-wiring.test.mjs test/task-graph.test.mjs test/ask-store.test.mjs test/turn.test.mjs test/fleet-members.test.mjs test/redact.test.mjs test/policy-matrix.test.mjs test/envelope-signature.test.mjs test/chat-crypto.test.mjs test/reduce-suggestion.test.mjs test/merge-hint.test.mjs test/fleet-chat-route.test.mjs test/maintenance.test.mjs test/maintenance-ferry.test.mjs test/grant.test.mjs test/grant-gate.test.mjs test/worktree.test.mjs test/patch-artifact.test.mjs test/merge-queue.test.mjs test/m3-workflow.test.mjs test/project-room.test.mjs test/project-attribution.test.mjs test/project-locks.test.mjs test/project-reviews.test.mjs test/project-notifications.test.mjs test/blob-sessions.test.mjs test/outbox.test.mjs test/reliability-metrics.test.mjs test/evidence.test.mjs test/grant-issue.test.mjs test/project-refs.test.mjs test/exec-capability.test.mjs test/secondary-link.test.mjs test/bootstrap-cli.test.mjs test/bootstrap.test.mjs test/windows-hidden-task.test.mjs test/relay-failover.test.mjs test/npm-update.test.mjs test/agent-start-nonblocking.test.mjs test/fleet-chat-capture.test.mjs test/capture-route.test.mjs test/update-resolver.test.mjs test/agent-identity-safe.test.mjs test/re-pair-stale-token.test.mjs test/gpw-folder-policy.test.mjs test/gpw-activation.test.mjs test/gpw-hardening.test.mjs test/gpw-tamper-gate.test.mjs test/gpw-persistence.test.mjs test/gpw-colors.test.mjs",
|
|
67
|
+
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/blake2s.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.test.mjs test/fleet-memory-e2e.test.mjs test/fleet-concurrency.test.mjs test/fleet-merge-race.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/portable-plugins.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs test/admission.test.mjs test/admission-routes.test.mjs test/join-degradation.test.mjs test/account-identity.test.mjs test/join-challenge.test.mjs test/exec-env-injection.test.mjs test/account-join.test.mjs test/fleet-messages.test.mjs test/message-shards.test.mjs test/fleet-messages-http.test.mjs test/message-notify.test.mjs test/update-channels.test.mjs test/update-prerelease.test.mjs test/session-watch.test.mjs test/session-readonly.test.mjs test/session-sync.test.mjs test/fleet-chat-identity.test.mjs test/identity-reader.test.mjs test/intents.test.mjs test/dataflow.test.mjs test/council-fleet.test.mjs test/fleet-memory.test.mjs test/memory-view.test.mjs test/schema-spec.test.mjs test/fleet-weight-parity.test.mjs test/task-deps.test.mjs test/dataflow-wiring.test.mjs test/task-graph.test.mjs test/ask-store.test.mjs test/turn.test.mjs test/fleet-members.test.mjs test/redact.test.mjs test/policy-matrix.test.mjs test/envelope-signature.test.mjs test/chat-crypto.test.mjs test/reduce-suggestion.test.mjs test/merge-hint.test.mjs test/fleet-chat-route.test.mjs test/maintenance.test.mjs test/maintenance-ferry.test.mjs test/grant.test.mjs test/grant-gate.test.mjs test/worktree.test.mjs test/patch-artifact.test.mjs test/merge-queue.test.mjs test/m3-workflow.test.mjs test/project-room.test.mjs test/project-attribution.test.mjs test/project-locks.test.mjs test/project-reviews.test.mjs test/project-notifications.test.mjs test/blob-sessions.test.mjs test/outbox.test.mjs test/reliability-metrics.test.mjs test/evidence.test.mjs test/grant-issue.test.mjs test/project-refs.test.mjs test/exec-capability.test.mjs test/secondary-link.test.mjs test/bootstrap-cli.test.mjs test/bootstrap.test.mjs test/windows-hidden-task.test.mjs test/relay-failover.test.mjs test/npm-update.test.mjs test/agent-start-nonblocking.test.mjs test/fleet-chat-capture.test.mjs test/capture-route.test.mjs test/update-resolver.test.mjs test/agent-identity-safe.test.mjs test/re-pair-stale-token.test.mjs test/gpw-folder-policy.test.mjs test/gpw-activation.test.mjs test/gpw-hardening.test.mjs test/gpw-tamper-gate.test.mjs test/gpw-persistence.test.mjs test/gpw-colors.test.mjs test/gpw-recent-fold.test.mjs test/gpw-auto-approve.test.mjs",
|
|
68
68
|
"test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
|
|
69
69
|
"test:account-identity": "node --test --test-force-exit test/account-identity.test.mjs",
|
|
70
70
|
"test:account-join": "node --test --test-force-exit test/account-join.test.mjs",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"test:council-session": "node --test --test-force-exit test/council-session.test.mjs",
|
|
83
83
|
"test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
|
|
84
84
|
"test:crypto-blake2s": "node --test --test-force-exit test/crypto-blake2s.test.mjs",
|
|
85
|
-
"test:e2e": "node --test --test-force-exit 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/p2p-agent.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",
|
|
85
|
+
"test:e2e": "node --test --test-force-exit 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/p2p-agent.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",
|
|
86
86
|
"test:exec-env": "node --test --test-force-exit test/exec-env-injection.test.mjs",
|
|
87
87
|
"test:exit-channel": "node --test --test-force-exit test/exit-channel.test.mjs",
|
|
88
88
|
"test:field-authority": "node --test --test-force-exit test/field-authority.test.mjs",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "twinsearth-w2m-dsh",
|
|
4
|
-
"version": "1.8.
|
|
4
|
+
"version": "1.8.2",
|
|
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": [
|
|
@@ -27,7 +27,18 @@ export const GPW_COLOR_LABELS = Object.freeze({
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
/** Tools that only read: green by their nature. */
|
|
30
|
-
const READONLY_TOOLS =
|
|
30
|
+
const READONLY_TOOLS = new RegExp(
|
|
31
|
+
'^(' +
|
|
32
|
+
// local read-only tools
|
|
33
|
+
'read|read_file|search|grep|glob|glob_files|list|list_files|dir|ls|cat|head|tail|view|' +
|
|
34
|
+
'web_search|web_fetch|fetch|status|health|metrics|inspect|' +
|
|
35
|
+
// background-job observation is read-only; kill/spawn are not in this list on purpose
|
|
36
|
+
'job_list|job_output|job_status|job_wait|' +
|
|
37
|
+
// read-only fleet queries (w2m_run / w2m_cancel / w2m_intent mutate, so they stay yellow)
|
|
38
|
+
'w2m_status|w2m_devices|w2m_tasks|w2m_history|w2m_report|w2m_memory|w2m_wait|w2m_stats' +
|
|
39
|
+
')$',
|
|
40
|
+
'i',
|
|
41
|
+
);
|
|
31
42
|
|
|
32
43
|
/** Command tools: their argv decides, not the tool name. */
|
|
33
44
|
const COMMAND_TOOLS = /^(bash|pwsh|powershell|shell|terminal|run_command|exec)$/i;
|
package/src/plugin/gpw.mjs
CHANGED
|
@@ -39,11 +39,51 @@ import { homedir } from 'node:os';
|
|
|
39
39
|
import { dirname, join, resolve } from 'node:path';
|
|
40
40
|
import { createHmac, randomBytes, randomUUID } from 'node:crypto';
|
|
41
41
|
|
|
42
|
-
import { classifyToolCall } from './folder-policy.mjs';
|
|
42
|
+
import { classifyToolCall, isInside, normalizeFolder } from './folder-policy.mjs';
|
|
43
43
|
import { classifyGpwCall, fingerprintOf } from './gpw-colors.mjs';
|
|
44
44
|
|
|
45
45
|
export const name = 'dsh-gpw';
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Which list (if any) already covers a folder, for the "recently intercepted" fold.
|
|
49
|
+
*
|
|
50
|
+
* A recent row whose folder is already covered by a list is noise: the operator already made that decision. Those rows
|
|
51
|
+
* are collapsed in the panel, and adding a folder from a recent row removes the row outright.
|
|
52
|
+
*/
|
|
53
|
+
export function folderInLists(folder, lists) {
|
|
54
|
+
if (typeof folder !== 'string' || folder.trim() === '') return null;
|
|
55
|
+
let target;
|
|
56
|
+
try {
|
|
57
|
+
target = normalizeFolder(folder);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const inList = (entries) => (Array.isArray(entries) ? entries : []).some((entry) => {
|
|
62
|
+
try {
|
|
63
|
+
const root = normalizeFolder(entry);
|
|
64
|
+
return isInside(target, root) || isInside(root, target);
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
if (inList(lists?.whitelist)) return 'whitelist';
|
|
70
|
+
if (inList(lists?.blacklist)) return 'blacklist';
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** True when a recent row's folder is the folder just added, or lives inside it. */
|
|
75
|
+
export function foldIsCoveredBy(addedFolder, candidate) {
|
|
76
|
+
if (typeof addedFolder !== 'string' || typeof candidate !== 'string') return false;
|
|
77
|
+
try {
|
|
78
|
+
const added = normalizeFolder(addedFolder);
|
|
79
|
+
const target = normalizeFolder(candidate);
|
|
80
|
+
return target === added || isInside(target, added);
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
47
87
|
/** Services this plugin cannot work without. `connection` is used only for the operator routes. */
|
|
48
88
|
export const inject = ['permissionPresets'];
|
|
49
89
|
|
|
@@ -55,7 +95,7 @@ export const GPW_PICK_ROUTE = '/api/w2m/gpw/pick';
|
|
|
55
95
|
export const GPW_RESET_ROUTE = '/api/w2m/gpw/reset';
|
|
56
96
|
|
|
57
97
|
const DEFAULT_BUDGET = { maxPerWindow: 10, windowMs: 60_000, minGapMs: 3_000 };
|
|
58
|
-
const DEFAULT_PENDING_TIMEOUT_MS =
|
|
98
|
+
const DEFAULT_PENDING_TIMEOUT_MS = 100_000;
|
|
59
99
|
const RECENT_LIMIT = 20;
|
|
60
100
|
const NO_STORE = { 'cache-control': 'no-store' };
|
|
61
101
|
/** v1.7.9: lists are MAC-signed (v2) so an external edit is detectable instead of silently trusted. */
|
|
@@ -426,16 +466,22 @@ export function apply(ctx, config = {}) {
|
|
|
426
466
|
paths,
|
|
427
467
|
color: 'yellow',
|
|
428
468
|
fingerprint: fingerprintOf(toolCallOf(exec)),
|
|
469
|
+
sessionId: String(exec?.agent?.session?.header?.id ?? ''),
|
|
429
470
|
};
|
|
430
471
|
pending.set(id, entry);
|
|
431
472
|
pendingTotal += 1;
|
|
432
473
|
const timer = setTimeout(() => {
|
|
433
474
|
if (!pending.has(id)) return;
|
|
434
475
|
pending.delete(id);
|
|
435
|
-
|
|
436
|
-
|
|
476
|
+
// v1.8.2 timeout policy: a yellow (class-C) call that nobody answered is approved after 100 s. Red never reaches
|
|
477
|
+
// this queue (it is denied outright) and black/white list hits never become pending, so this rule cannot widen
|
|
478
|
+
// either list's authority. The approval is recorded as a session grant, which is what turns its next occurrence
|
|
479
|
+
// blue.
|
|
480
|
+
allowedTotal += 1;
|
|
481
|
+
colorCounts.blue += 1;
|
|
482
|
+
if (typeof entry.fingerprint === 'string' && entry.fingerprint !== '') sessionGrants.add(entry.fingerprint);
|
|
483
|
+
resolveDecision({ kind: 'allow', reason: `GPW: auto-approved after ${Math.round(pendingTimeoutMs / 1000)} s with no operator answer (yellow path; black/white lists keep their own rules)` });
|
|
437
484
|
}, pendingTimeoutMs);
|
|
438
|
-
timer.unref?.();
|
|
439
485
|
entry.timer = timer;
|
|
440
486
|
});
|
|
441
487
|
|
|
@@ -608,15 +654,19 @@ export function apply(ctx, config = {}) {
|
|
|
608
654
|
tampered: lists.tampered,
|
|
609
655
|
tampered_reason: lists.reason,
|
|
610
656
|
counters: { allowedTotal, pendingTotal, deniedTotal, budgetWindow: approvals.length, maxPerWindow, colors: { ...colorCounts }, grants: sessionGrants.size },
|
|
611
|
-
recent: recent.map((entry) =>
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
657
|
+
recent: recent.map((entry) => {
|
|
658
|
+
const folder = entry.paths.length > 0 ? folderForPath(entry.paths[0]) : null;
|
|
659
|
+
return {
|
|
660
|
+
at: entry.at,
|
|
661
|
+
tool: entry.tool,
|
|
662
|
+
decision: entry.decision,
|
|
663
|
+
color: entry.color ?? null,
|
|
664
|
+
reason: entry.reason,
|
|
665
|
+
paths: entry.paths,
|
|
666
|
+
folder,
|
|
667
|
+
folder_in: folderInLists(folder, lists),
|
|
668
|
+
};
|
|
669
|
+
}),
|
|
620
670
|
pending: [...pending.entries()].map(([id, entry]) => ({
|
|
621
671
|
id,
|
|
622
672
|
tool: entry.tool,
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -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.8.
|
|
286
|
+
const PLUGIN_VERSION = '1.8.2';
|
|
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;
|