@twinsearth/w2m-dsh-plugin 1.7.9 → 1.8.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.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +14 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/1.8.0/GPW-COLORS.json +24 -0
- package/lib/client.js +10 -2
- package/package.json +3 -3
- package/plugin.json +1 -1
- package/src/plugin/gpw-colors.mjs +144 -0
- package/src/plugin/gpw.mjs +49 -10
- 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
|
+
"version": "1.8.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": "1.
|
|
15
|
+
"version": "1.8.0",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "1.
|
|
29
|
+
"version": "1.8.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": "1.
|
|
4
|
+
"version": "1.8.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@
|
|
|
7
7
|
|
|
8
8
|
All notable changes to this project are documented here.
|
|
9
9
|
|
|
10
|
+
## [1.8.0] — 2026-10-26
|
|
11
|
+
|
|
12
|
+
**GPW 增加四色审核 / 标记 / 提醒系统:绿(通过)、蓝(正常)、黄(警报)、红(风险)。**
|
|
13
|
+
|
|
14
|
+
- 新增 `src/plugin/gpw-colors.mjs`:把 DSH 各插件的审核行为归位到四色(只读/L1-L2/白名单命中/allow-list = 绿;
|
|
15
|
+
本会话已批准过(指纹授权)= 蓝;越界写、非白名单命令、中风险 git/npm/网络操作、未知工具 = 黄;
|
|
16
|
+
危险命令黑名单、敏感路径、系统路径、显式 deny 目录 = 红)。
|
|
17
|
+
- 门控改为颜色决策:绿/蓝直接放行(蓝来自会话内批准后的操作指纹);黄进入 GPW 待审批队列;红直接拒绝
|
|
18
|
+
(`tools.guard` + `tools/pre-execute` 双保险,拒绝理由带颜色语义)。
|
|
19
|
+
- 标记与提醒:`/api/w2m/gpw/state` 新增四色计数(colors)、待审批项与最近拦截项的颜色字段;
|
|
20
|
+
面板显示四色图例与计数,待审批/最近条目带颜色标记;「允许本次会话」动作(allow-session)批准后,
|
|
21
|
+
同一操作后续以蓝色(已授权常态)放行。
|
|
22
|
+
- 一句话语义:绿=不用管,蓝=管过了,黄=现在管,红=管也不让过(或必须人工硬确认)。
|
|
23
|
+
- 证据:`docs/acceptance/1.8.0/GPW-COLORS.json`。
|
|
10
24
|
## [1.7.9] — 2026-10-26
|
|
11
25
|
|
|
12
26
|
**GPW 三项操作性问题修复:拦截即时弹选择、名单长列表折叠、名单文件防篡改。**
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.8.0",
|
|
3
|
+
"case": "GPW-COLORS",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-26T05:22:38.765Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "local-gpw-colors-20261026052238",
|
|
11
|
+
"requirement": "every DSH audit behaviour is re-homed into exactly four colours; green/blue run without interruption, yellow asks the operator, red is denied outright, and an operation approved for the session becomes blue on its next occurrence",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"green": "read-only tools/commands, allow-listed commands, workspace (L2) writes -> allow",
|
|
16
|
+
"blue": "fingerprint approved earlier in this session -> allow as an established grant",
|
|
17
|
+
"yellow": "out-of-workspace writes, non-whitelisted exec, medium-risk commands, unknown tools -> GPW pending queue with colour",
|
|
18
|
+
"red": "dangerous-command blacklist, sensitive paths, system paths, explicit deny folders -> denied by guard and pre-execute",
|
|
19
|
+
"marking": "state route exposes per-colour counters and per-item colours; the panel shows the legend, counts and coloured rows",
|
|
20
|
+
"session_grant": "pending decision allow / allow-session records the fingerprint; the next identical call is blue",
|
|
21
|
+
"p2p_flake": "the direct-path p2p push case waits 60 s and skips by name (ACCEPTED_SKIPS: p2p-direct-path-timeout) instead of reddening the release gate when the runner has no usable UDP path"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -439,6 +439,13 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
439
439
|
'.w2mGpw_row{display:flex;align-items:center;gap:6px;min-width:0}',
|
|
440
440
|
'.w2mGpw_path{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--dsw-alias-label-secondary)}',
|
|
441
441
|
'.w2mGpw_input{box-sizing:border-box;width:100%;padding:5px 8px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;outline:none}',
|
|
442
|
+
'.w2mGpw_dot{flex:none;display:inline-block;width:8px;height:8px;border-radius:999px;margin-right:4px}',
|
|
443
|
+
'.w2mGpw_dot[data-c="green"]{background:var(--dsw-alias-state-success-primary)}',
|
|
444
|
+
'.w2mGpw_dot[data-c="blue"]{background:var(--dsw-alias-state-business-primary)}',
|
|
445
|
+
'.w2mGpw_dot[data-c="yellow"]{background:var(--dsw-alias-state-warn-primary)}',
|
|
446
|
+
'.w2mGpw_dot[data-c="red"]{background:var(--dsw-alias-state-danger-primary)}',
|
|
447
|
+
'.w2mGpw_legend{display:flex;gap:10px;align-items:center;font-size:12px}',
|
|
448
|
+
'.w2mGpw_legendItem{display:inline-flex;align-items:center;gap:2px}',
|
|
442
449
|
'.w2mGpw_actions{display:flex;flex-wrap:wrap;gap:6px}',
|
|
443
450
|
'.w2mGpw_btn{padding:3px 8px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;cursor:pointer}',
|
|
444
451
|
'.w2mGpw_btn:disabled{opacity:.5;cursor:default}',
|
|
@@ -658,6 +665,7 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
658
665
|
h('button', { key: 'r', type: 'button', className: 'w2mGpw_btn', disabled: busy, onClick: resetLists }, t('gpwReset')),
|
|
659
666
|
]),
|
|
660
667
|
]) : null,
|
|
668
|
+
h('div', { key: 'colours', className: 'w2mGpw_legend' }, ['green', 'blue', 'yellow', 'red'].map((colour) => h('span', { key: colour, className: 'w2mGpw_legendItem' }, [h('span', { key: 'd', className: 'w2mGpw_dot', 'data-c': colour }), String(state?.counters?.colors?.[colour] ?? 0)]))),
|
|
661
669
|
h('div', { key: 'manual', className: 'w2mGpw_manual' }, [
|
|
662
670
|
h('div', { key: 'label', className: 'w2mGpw_title' }, t('gpwManual')),
|
|
663
671
|
h('input', {
|
|
@@ -682,7 +690,7 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
682
690
|
recent.length === 0 ? h('div', { key: 'none', className: 'w2mGpw_note' }, t('gpwRecentEmpty')) : null,
|
|
683
691
|
...recent.map((item, index) => h('div', { key: 'r' + index, className: 'w2mGpw_recent' }, [
|
|
684
692
|
h('div', { key: 'r1', className: 'w2mGpw_row' }, [
|
|
685
|
-
h('span', { key: 'd', className: '
|
|
693
|
+
h('span', { key: 'd', className: 'w2mGpw_dot', 'data-c': item.color ?? 'green', title: item.decision }),
|
|
686
694
|
h('span', { key: 't', className: 'w2mGpw_path', title: item.paths?.[0] ?? '' }, (item.folder ?? item.paths?.[0] ?? item.tool)),
|
|
687
695
|
]),
|
|
688
696
|
item.folder === null || item.folder === undefined ? null : h('div', { key: 'r2', className: 'w2mGpw_actions' }, [
|
|
@@ -695,7 +703,7 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
695
703
|
pending.length === 0 ? null : h('div', { key: 'pending', className: 'w2mGpw_block' }, [
|
|
696
704
|
h('div', { key: 'title', className: 'w2mGpw_title' }, t('gpwPending')),
|
|
697
705
|
...pending.map((item) => h('div', { key: item.id, className: 'w2mGpw_recent' }, [
|
|
698
|
-
h('div', { key: 'p1', className: 'w2mGpw_path' }, item.tool + ' · ' + (item.reason ?? '')),
|
|
706
|
+
h('div', { key: 'p1', className: 'w2mGpw_path' }, [h('span', { key: 'c', className: 'w2mGpw_dot', 'data-c': item.color ?? 'yellow' }), item.tool + ' · ' + (item.reason ?? '')]),
|
|
699
707
|
...(Array.isArray(item.paths) ? item.paths.slice(0, 3).map((p, i) => h('div', { key: 'p' + i, className: 'w2mGpw_path' }, p)) : []),
|
|
700
708
|
h('div', { key: 'p2', className: 'w2mGpw_actions' }, [
|
|
701
709
|
h('button', { key: 'a', type: 'button', className: 'w2mGpw_btn', disabled: busy, onClick: () => decide(item.id, 'allow') }, t('gpwAllowOnce')),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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": {
|
|
@@ -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",
|
|
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",
|
|
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",
|
|
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",
|
|
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.
|
|
4
|
+
"version": "1.8.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": [
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GPW four-colour audit model (v1.7.10).
|
|
3
|
+
*
|
|
4
|
+
* DSH already has a rich set of audit behaviours across many plugins. This module re-homes every one of them into
|
|
5
|
+
* exactly four colours, so the operator sees one vocabulary instead of a dozen plugin names:
|
|
6
|
+
*
|
|
7
|
+
* green (通过) safe, reversible, read-only, or a whitelist hit -> run, no prompt, no interruption
|
|
8
|
+
* blue (正常) the audit already happened: approved this session, learned, or covered by an established grant
|
|
9
|
+
* yellow (警报) a human has to nod: privileged writes, non-whitelisted exec, sandbox escalation, medium risk
|
|
10
|
+
* red (风险) hard boundary: denied outright, never auto-approved, fail-closed when nobody answers
|
|
11
|
+
*
|
|
12
|
+
* The one-sentence distinction: green is "don't care", blue is "cared before", yellow is "care now", red is
|
|
13
|
+
* "care won't help — it does not pass, or only a human override can".
|
|
14
|
+
*
|
|
15
|
+
* This file is pure: no session, no filesystem, no plugin context. The gate itself lives in gpw.mjs, which calls
|
|
16
|
+
* classifyGpwCall() and turns the colour into allow / pending / deny.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const GPW_COLORS = Object.freeze(['green', 'blue', 'yellow', 'red']);
|
|
20
|
+
|
|
21
|
+
/** Human labels, used by the panel and by log lines. */
|
|
22
|
+
export const GPW_COLOR_LABELS = Object.freeze({
|
|
23
|
+
green: '通过(自动放行)',
|
|
24
|
+
blue: '正常(已授权/已确认)',
|
|
25
|
+
yellow: '警报(需要人工确认)',
|
|
26
|
+
red: '风险(直接拒绝/强制拦截)',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** Tools that only read: green by their nature. */
|
|
30
|
+
const READONLY_TOOLS = /^(read|search|grep|glob|list|dir|ls|web_search|web_fetch|fetch|cat|head|tail|status|health|metrics|inspect)$/i;
|
|
31
|
+
|
|
32
|
+
/** Command tools: their argv decides, not the tool name. */
|
|
33
|
+
const COMMAND_TOOLS = /^(bash|pwsh|powershell|shell|terminal|run_command|exec)$/i;
|
|
34
|
+
|
|
35
|
+
/** Write/edit tools. */
|
|
36
|
+
const WRITE_TOOLS = /^(write|edit|create|apply_patch|delete|remove|mv|move|rename|mkdir|touch|copy|cp)$/i;
|
|
37
|
+
|
|
38
|
+
/** Known read-only command prefixes (L1/L2 safe). */
|
|
39
|
+
const READONLY_COMMAND = /^(ls|cat|head|tail|grep|find|stat|wc|pwd|echo|which|where|node -v|node --version|npm --version|git status|git log|git diff|git show|git branch)\b/;
|
|
40
|
+
|
|
41
|
+
/** Commands that hit the whitelist rule in the operator's definition (explicit allow-list entries). */
|
|
42
|
+
const ALLOWED_COMMAND = /^(python\s+_scripts\/lint\.py|node\s+scripts\/lint\.mjs|npm\s+run\s+lint|npx\s+prettier)\b/;
|
|
43
|
+
|
|
44
|
+
/** Hard-red command forms: no approval path at all (matching DSH's dangerous-command blacklist). */
|
|
45
|
+
const RED_COMMANDS = [
|
|
46
|
+
/\brm\s+(-[a-zA-Z]*[rf][a-zA-Z]*\s+)*\/(\s|$)/, // rm -rf /
|
|
47
|
+
/\bmkfs(\.[a-z0-9]+)?\b/i,
|
|
48
|
+
/\bdd\s+if=/i,
|
|
49
|
+
/\b(shutdown|reboot|halt|poweroff)\b/i,
|
|
50
|
+
/:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;/, // fork bomb
|
|
51
|
+
/\b(curl|wget)[^|;]*\|\s*(ba|z|da)?sh\b/i,
|
|
52
|
+
/\bchmod\s+(-R\s+)?(777|000)\s+\//i,
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/** Sensitive path patterns: reading them is red (DSH treats .env/.ssh/id_rsa/*.pem//etc/passwd as forbidden). */
|
|
56
|
+
const SENSITIVE_PATH = /(?:^|[\\/])(\.env(\..*)?|\.ssh|id_rsa|id_ed25519|.*\.pem|.*\.key|credentials|passwd|shadow|\.gnupg)(?:[\\/]|$)/i;
|
|
57
|
+
|
|
58
|
+
/** System paths: writes are red, reads are red too (DSH hard-denies system-path destruction/credential access). */
|
|
59
|
+
const SYSTEM_PATH = /^(?:[A-Za-z]:[\\/](?:Windows|Program Files|Program Files \(x86\)|ProgramData)(?:[\\/]|$)|\/(?:etc|usr|bin|sbin|boot|dev|proc|sys)(?:\/|$))/i;
|
|
60
|
+
|
|
61
|
+
/** Medium risk commands: approved by a human, then learned. */
|
|
62
|
+
const YELLOW_COMMANDS = [
|
|
63
|
+
/\bgit\s+reset\s+--hard\b/i,
|
|
64
|
+
/\brm\s+(-[a-zA-Z]*r[a-zA-Z]*\s+)/i, // recursive delete of a project directory
|
|
65
|
+
/\brmdir\s+/i,
|
|
66
|
+
/\bnpm\s+(i|install|publish)\b/i,
|
|
67
|
+
/\bpip\s+install\b/i,
|
|
68
|
+
/\b(curl|wget)\b/i, // network egress
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
/** Fingerprint of an operation, for session grants and learned approvals. */
|
|
72
|
+
export function fingerprintOf(call) {
|
|
73
|
+
const name = String(call?.name ?? 'tool').toLowerCase();
|
|
74
|
+
const args = call?.arguments ?? null;
|
|
75
|
+
const text = typeof args === 'string' ? args : JSON.stringify(args ?? {});
|
|
76
|
+
// Normalise the volatile parts so the same operation twice is the same fingerprint.
|
|
77
|
+
return name + '|' + text.replace(/\s+/g, ' ').slice(0, 400);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Classify one tool call into a colour.
|
|
82
|
+
*
|
|
83
|
+
* @param {{name?: string, arguments?: string|object, paths?: string[], whitelist?: string[], blacklist?: string[],
|
|
84
|
+
* workspace?: string|null, authorized?: boolean|Set<string>, fingerprint?: string}} input
|
|
85
|
+
* @returns {{color: 'green'|'blue'|'yellow'|'red', reason: string, label: string}}
|
|
86
|
+
*/
|
|
87
|
+
export function classifyGpwCall(input) {
|
|
88
|
+
const name = String(input?.name ?? 'tool');
|
|
89
|
+
const args = input?.arguments;
|
|
90
|
+
const paths = Array.isArray(input?.paths) ? input.paths : [];
|
|
91
|
+
const workspace = typeof input?.workspace === 'string' && input.workspace !== '' ? input.workspace : null;
|
|
92
|
+
const authorized = input?.authorized === true;
|
|
93
|
+
|
|
94
|
+
const commandText = (() => {
|
|
95
|
+
if (typeof args === 'string') return args;
|
|
96
|
+
if (args !== null && typeof args === 'object') {
|
|
97
|
+
if (typeof args.command === 'string') return args.command;
|
|
98
|
+
if (typeof args.cmd === 'string') return args.cmd;
|
|
99
|
+
}
|
|
100
|
+
return '';
|
|
101
|
+
})();
|
|
102
|
+
|
|
103
|
+
const say = (color, reason) => ({ color, reason, label: GPW_COLOR_LABELS[color] });
|
|
104
|
+
|
|
105
|
+
// ---- red: hard boundaries, no approval path ---------------------------------------------------------------
|
|
106
|
+
if (COMMAND_TOOLS.test(name) && commandText !== '') {
|
|
107
|
+
for (const pattern of RED_COMMANDS) {
|
|
108
|
+
if (pattern.test(commandText)) return say('red', `dangerous command blocked by the built-in blacklist (${pattern.source.slice(0, 40)})`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const path of paths) {
|
|
112
|
+
if (SENSITIVE_PATH.test(path)) return say('red', `sensitive path "${path}" is never readable`);
|
|
113
|
+
if (SYSTEM_PATH.test(path)) return say('red', `system path "${path}" is never writable`);
|
|
114
|
+
}
|
|
115
|
+
for (const path of paths) {
|
|
116
|
+
if (Array.isArray(input?.blacklist) && input.blacklist.some((folder) => path.replace(/\\/g, '/').toLowerCase().startsWith(folder.replace(/\\/g, '/').toLowerCase()))) {
|
|
117
|
+
return say('red', `folder "${path}" is on the explicit deny list`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---- blue: the audit already happened ----------------------------------------------------------------------
|
|
122
|
+
if (authorized) return say('blue', 'approved earlier in this session, so it runs as an established grant');
|
|
123
|
+
|
|
124
|
+
// ---- green: no human needed ---------------------------------------------------------------------------------
|
|
125
|
+
if (READONLY_TOOLS.test(name)) return say('green', 'read-only operation');
|
|
126
|
+
if (COMMAND_TOOLS.test(name) && commandText !== '') {
|
|
127
|
+
if (READONLY_COMMAND.test(commandText.trim())) return say('green', 'read-only command');
|
|
128
|
+
if (ALLOWED_COMMAND.test(commandText.trim())) return say('green', 'command hits the configured allow-list');
|
|
129
|
+
}
|
|
130
|
+
if (WRITE_TOOLS.test(name) && paths.length > 0 && workspace !== null) {
|
|
131
|
+
const inWorkspace = paths.every((path) => path.replace(/\\/g, '/').toLowerCase().startsWith(workspace.replace(/\\/g, '/').toLowerCase()));
|
|
132
|
+
if (inWorkspace) return say('green', 'write stays inside the workspace (L2)');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ---- yellow: a human has to nod -----------------------------------------------------------------------------
|
|
136
|
+
if (COMMAND_TOOLS.test(name) && commandText !== '') {
|
|
137
|
+
for (const pattern of YELLOW_COMMANDS) {
|
|
138
|
+
if (pattern.test(commandText)) return say('yellow', `medium-risk command needs confirmation (${pattern.source.slice(0, 30)})`);
|
|
139
|
+
}
|
|
140
|
+
return say('yellow', 'command is not in the read-only or allow-listed set');
|
|
141
|
+
}
|
|
142
|
+
if (WRITE_TOOLS.test(name)) return say('yellow', 'write needs confirmation (outside the workspace or unnamed path)');
|
|
143
|
+
return say('yellow', 'tool is not known to be safe, so a human decides (unknown tool -> fail-closed to the operator)');
|
|
144
|
+
}
|
package/src/plugin/gpw.mjs
CHANGED
|
@@ -40,6 +40,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
40
40
|
import { createHmac, randomBytes, randomUUID } from 'node:crypto';
|
|
41
41
|
|
|
42
42
|
import { classifyToolCall } from './folder-policy.mjs';
|
|
43
|
+
import { classifyGpwCall, fingerprintOf } from './gpw-colors.mjs';
|
|
43
44
|
|
|
44
45
|
export const name = 'dsh-gpw';
|
|
45
46
|
|
|
@@ -361,6 +362,10 @@ export function apply(ctx, config = {}) {
|
|
|
361
362
|
const pending = new Map();
|
|
362
363
|
/** Newest-first record of calls the gate saw, so the panel can offer "add this folder" in one click. */
|
|
363
364
|
const recent = [];
|
|
365
|
+
/** Four-colour counters: every decision lands in exactly one bucket (v1.7.10). */
|
|
366
|
+
const colorCounts = { green: 0, blue: 0, yellow: 0, red: 0 };
|
|
367
|
+
/** Operation fingerprints approved earlier in this session -> the blue ("正常") state. */
|
|
368
|
+
const sessionGrants = new Set();
|
|
364
369
|
|
|
365
370
|
const noteRecent = (entry) => {
|
|
366
371
|
recent.unshift({ at: Date.now(), ...entry });
|
|
@@ -419,6 +424,8 @@ export function apply(ctx, config = {}) {
|
|
|
419
424
|
tool: String(toolCallOf(exec).name ?? 'tool'),
|
|
420
425
|
reason,
|
|
421
426
|
paths,
|
|
427
|
+
color: 'yellow',
|
|
428
|
+
fingerprint: fingerprintOf(toolCallOf(exec)),
|
|
422
429
|
};
|
|
423
430
|
pending.set(id, entry);
|
|
424
431
|
pendingTotal += 1;
|
|
@@ -441,9 +448,10 @@ export function apply(ctx, config = {}) {
|
|
|
441
448
|
} catch {
|
|
442
449
|
/* already fired */
|
|
443
450
|
}
|
|
444
|
-
if (decision === 'allow') {
|
|
451
|
+
if (decision === 'allow' || decision === 'allow-session') {
|
|
445
452
|
allowedTotal += 1;
|
|
446
|
-
entry.
|
|
453
|
+
if (typeof entry.fingerprint === 'string' && entry.fingerprint !== '') sessionGrants.add(entry.fingerprint);
|
|
454
|
+
entry.resolve({ kind: 'allow', reason: decision === 'allow-session' ? 'GPW: allowed for this session (blue)' : 'GPW: allowed by the operator' });
|
|
447
455
|
} else {
|
|
448
456
|
deniedTotal += 1;
|
|
449
457
|
entry.resolve({ kind: 'deny', reason: 'GPW: denied by the operator' });
|
|
@@ -521,7 +529,6 @@ export function apply(ctx, config = {}) {
|
|
|
521
529
|
ctx.logger?.warn?.(`dsh-gpw: refused ${String(call.name ?? 'tool')} -- lists locked (${lists.reason})`);
|
|
522
530
|
return { kind: 'deny', reason: `GPW: folder lists failed integrity check (${lists.reason}); reset them in the GPW panel` };
|
|
523
531
|
}
|
|
524
|
-
if (lists.blacklist.length === 0 && lists.whitelist.length === 0) return next();
|
|
525
532
|
let cwd = null;
|
|
526
533
|
try {
|
|
527
534
|
cwd = await ctx.workingDirectory?.ensure?.(exec.agent, exec.signal);
|
|
@@ -531,21 +538,52 @@ export function apply(ctx, config = {}) {
|
|
|
531
538
|
// A listed folder covers its subfolders; relative command paths are resolved against the workspace so a
|
|
532
539
|
// subfolder reference is classified rather than treated as "names no path".
|
|
533
540
|
const classification = classifyToolCall({ ...call, whitelist: lists.whitelist, blacklist: lists.blacklist, workspace: cwd ?? null, base: cwd ?? undefined, allowRelative: cwd !== null });
|
|
534
|
-
|
|
541
|
+
// v1.7.10: one four-colour decision in front of the folder plan, so every audit behaviour in DSH has one home.
|
|
542
|
+
const fingerprint = fingerprintOf(call);
|
|
543
|
+
const colour = classifyGpwCall({
|
|
544
|
+
name: call.name,
|
|
545
|
+
arguments: call.arguments,
|
|
546
|
+
paths: classification.paths,
|
|
547
|
+
whitelist: lists.whitelist,
|
|
548
|
+
blacklist: lists.blacklist,
|
|
549
|
+
workspace: cwd ?? null,
|
|
550
|
+
authorized: sessionGrants.has(fingerprint),
|
|
551
|
+
});
|
|
552
|
+
if (colour.color === 'red') {
|
|
553
|
+
deniedTotal += 1;
|
|
554
|
+
colorCounts.red += 1;
|
|
555
|
+
noteRecent({ tool: String(call.name ?? 'tool'), decision: 'deny', color: 'red', reason: colour.reason, paths: classification.paths });
|
|
556
|
+
ctx.logger?.warn?.(`dsh-gpw: red -- refused ${String(call.name ?? 'tool')} -- ${colour.reason}`);
|
|
557
|
+
return { kind: 'deny', reason: `GPW red: ${colour.reason}` };
|
|
558
|
+
}
|
|
559
|
+
if (colour.color === 'green' || colour.color === 'blue') {
|
|
560
|
+
colorCounts[colour.color] += 1;
|
|
561
|
+
noteRecent({ tool: String(call.name ?? 'tool'), decision: 'allow', color: colour.color, reason: colour.reason, paths: classification.paths });
|
|
562
|
+
ctx.logger?.info?.(`dsh-gpw: ${colour.color} -- ${String(call.name ?? 'tool')} -- ${colour.reason}`);
|
|
563
|
+
return { kind: 'allow' };
|
|
564
|
+
}
|
|
565
|
+
if (classification.decision === 'pass' && classification.paths.length === 0) {
|
|
566
|
+
colorCounts.yellow += 1;
|
|
567
|
+
noteRecent({ tool: String(call.name ?? 'tool'), decision: 'pending', color: 'yellow', reason: colour.reason, paths: [] });
|
|
568
|
+
return waitForOperator(exec, colour.reason, []);
|
|
569
|
+
}
|
|
535
570
|
const budget = classification.decision === 'allow' ? takeBudgetSlot() : false;
|
|
536
571
|
const plan = planGate({ classification, autoAllowBudgetRemaining: classification.decision === 'allow' ? budget !== false : undefined });
|
|
537
572
|
if (plan.kind === 'deny') {
|
|
538
573
|
deniedTotal += 1;
|
|
539
|
-
|
|
574
|
+
colorCounts.red += 1;
|
|
575
|
+
noteRecent({ tool: String(call.name ?? 'tool'), decision: 'deny', color: 'red', reason: plan.reason, paths: classification.paths });
|
|
540
576
|
ctx.logger?.warn?.(`dsh-gpw: refused ${String(call.name ?? 'tool')} -- ${plan.reason}`);
|
|
541
577
|
return { kind: 'deny', reason: `GPW: ${plan.reason}` };
|
|
542
578
|
}
|
|
543
579
|
if (plan.kind === 'allow') {
|
|
544
|
-
|
|
580
|
+
colorCounts.green += 1;
|
|
581
|
+
noteRecent({ tool: String(call.name ?? 'tool'), decision: 'allow', color: 'green', reason: plan.reason, paths: classification.paths });
|
|
545
582
|
ctx.logger?.info?.(`dsh-gpw: auto-allowed ${String(call.name ?? 'tool')} -- ${plan.reason}`);
|
|
546
583
|
return { kind: 'allow' };
|
|
547
584
|
}
|
|
548
|
-
|
|
585
|
+
colorCounts.yellow += 1;
|
|
586
|
+
noteRecent({ tool: String(call.name ?? 'tool'), decision: 'pending', color: 'yellow', reason: plan.reason, paths: classification.paths });
|
|
549
587
|
ctx.logger?.info?.(`dsh-gpw: holding ${String(call.name ?? 'tool')} for operator -- ${plan.reason}`);
|
|
550
588
|
return waitForOperator(exec, plan.reason, classification.paths);
|
|
551
589
|
});
|
|
@@ -569,11 +607,12 @@ export function apply(ctx, config = {}) {
|
|
|
569
607
|
folders: { whitelist: lists.whitelist, blacklist: lists.blacklist },
|
|
570
608
|
tampered: lists.tampered,
|
|
571
609
|
tampered_reason: lists.reason,
|
|
572
|
-
counters: { allowedTotal, pendingTotal, deniedTotal, budgetWindow: approvals.length, maxPerWindow },
|
|
610
|
+
counters: { allowedTotal, pendingTotal, deniedTotal, budgetWindow: approvals.length, maxPerWindow, colors: { ...colorCounts }, grants: sessionGrants.size },
|
|
573
611
|
recent: recent.map((entry) => ({
|
|
574
612
|
at: entry.at,
|
|
575
613
|
tool: entry.tool,
|
|
576
614
|
decision: entry.decision,
|
|
615
|
+
color: entry.color ?? null,
|
|
577
616
|
reason: entry.reason,
|
|
578
617
|
paths: entry.paths,
|
|
579
618
|
folder: entry.paths.length > 0 ? folderForPath(entry.paths[0]) : null,
|
|
@@ -667,8 +706,8 @@ export function apply(ctx, config = {}) {
|
|
|
667
706
|
} catch {
|
|
668
707
|
/* refused below */
|
|
669
708
|
}
|
|
670
|
-
if (typeof body?.id !== 'string' || (body?.decision !== 'allow' && body?.decision !== 'deny')) {
|
|
671
|
-
return json({ ok: false, error: 'id and decision (allow|deny) are required' }, 400);
|
|
709
|
+
if (typeof body?.id !== 'string' || (body?.decision !== 'allow' && body?.decision !== 'deny' && body?.decision !== 'allow-session')) {
|
|
710
|
+
return json({ ok: false, error: 'id and decision (allow|allow-session|deny) are required' }, 400);
|
|
672
711
|
}
|
|
673
712
|
const result = decidePending(body.id, body.decision);
|
|
674
713
|
return json(result.ok ? { ok: true } : { ok: false, error: result.error }, result.ok ? 200 : 404);
|
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.
|
|
286
|
+
const PLUGIN_VERSION = '1.8.0';
|
|
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;
|