@twinsearth/w2m-dsh-plugin 2.0.6 → 2.0.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.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +9 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/2.0.7/RELEASE-2070.json +21 -0
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/src/plugin/chat-v2-gate.mjs +40 -0
- package/src/plugin/fleet-chat-route.mjs +32 -1
- package/src/plugin/tools.mjs +17 -5
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "The w2m fleet plugins: one account, many machines, any of Windows, macOS or Linux.",
|
|
8
|
-
"version": "2.0.
|
|
8
|
+
"version": "2.0.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": "2.0.
|
|
15
|
+
"version": "2.0.7",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "2.0.
|
|
29
|
+
"version": "2.0.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": "2.0.
|
|
4
|
+
"version": "2.0.7",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@
|
|
|
7
7
|
|
|
8
8
|
All notable changes to this project are documented here.
|
|
9
9
|
|
|
10
|
+
## [2.0.7] - 2026-10-29
|
|
11
|
+
|
|
12
|
+
- A7 encrypted mirror:/healthz.features 新增 `chat-enc-v2` 门控(默认 off);支持时用
|
|
13
|
+
`encryptChatTextV2`(AAD=session/machine/message_id)投递会话镜像,否则保持 v1;
|
|
14
|
+
读取端用 `decryptChatMessageV2` 的上下文校验并保持 v1 兼容。
|
|
15
|
+
- 聊天发送:relay 广告 v2 且本机有 machine id 时发 v2,否则回落 v1;同一 idempotency_key 作为
|
|
16
|
+
AAD message_id,重试不会产生第二行。
|
|
17
|
+
- 插件设置中开启 Node 代码执行能力后,任务派发无需 relay 改动;本机已切换为 permanent。
|
|
18
|
+
|
|
10
19
|
## [2.0.6] - 2026-10-29
|
|
11
20
|
|
|
12
21
|
- 修复 CI 跨平台失败:sink 授权 route 先把 stateDir 解析为 realpath(Windows RUNNER~1 / macOS
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.7",
|
|
3
|
+
"case": "RELEASE-2070",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-28T17:37:31.887Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "pre-release-2070-1793209051887",
|
|
11
|
+
"requirement": "v2.0.7: chat-enc-v2 门控 + encrypted mirror + v2 发送/读取;Node 执行能力开启",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"preflight": "GREEN",
|
|
16
|
+
"tests": "all declared suites",
|
|
17
|
+
"lint": "0 errors",
|
|
18
|
+
"scope": "local; relay already advertises chat-enc-v2"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.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": {
|
|
@@ -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 test/gpw-recent-fold.test.mjs test/gpw-auto-approve.test.mjs test/joint-dev-dispatch.test.mjs test/joint-dev-loop.test.mjs test/joint-dev-audit.test.mjs test/joint-dev-redline.test.mjs test/relay-redline-guard.test.mjs test/relay-p0-projection.test.mjs test/guarded-dispatch.test.mjs test/p0-single-matcher.test.mjs test/path-policy.test.mjs test/path-policy-fs.test.mjs test/gpw-inheritance-table.test.mjs test/gpw-policy-adapter.test.mjs test/target-policy.test.mjs test/relay-redline-audit.test.mjs test/relay-repair-ledger.test.mjs test/relay-purge-blobs.test.mjs test/relay-scrub.test.mjs test/joint-dev-plan-store.test.mjs test/relay-basepath-blobs.test.mjs test/operator-token.test.mjs test/relay-health-capabilities.test.mjs test/joint-relay-dispatch.test.mjs test/operator-token-route.test.mjs test/relay-scrub-task-projection.test.mjs test/operator-key-approval.test.mjs test/relay-offer-journal.test.mjs test/joint-r5-wiring.test.mjs test/p1-hardening.test.mjs test/joint-merge-engine.test.mjs test/artifact-ref-dir-sink.test.mjs test/chat-sticker-guard.test.mjs test/relay-task-progress.test.mjs test/gpw-modes.test.mjs test/joint-cli.test.mjs test/joint-command-hash.test.mjs test/joint-dispatch-audit.test.mjs test/tools-cancel-auth.test.mjs test/fleet-mirror.test.mjs test/sink-git-ssh.test.mjs test/agent-sse-watchdog.test.mjs test/version-gate.test.mjs test/joint-merge-cli.test.mjs test/joint-progress.test.mjs test/project-room-truncation.test.mjs test/chat-crypto-aad.test.mjs test/agent-artifact-delivery.test.mjs test/dataplane-sink-route.test.mjs test/artifact-sink-e2e.test.mjs test/project-room-persistence.test.mjs test/project-room-disk-persistence.test.mjs test/project-stream-route.test.mjs",
|
|
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 test/joint-dev-dispatch.test.mjs test/joint-dev-loop.test.mjs test/joint-dev-audit.test.mjs test/joint-dev-redline.test.mjs test/relay-redline-guard.test.mjs test/relay-p0-projection.test.mjs test/guarded-dispatch.test.mjs test/p0-single-matcher.test.mjs test/path-policy.test.mjs test/path-policy-fs.test.mjs test/gpw-inheritance-table.test.mjs test/gpw-policy-adapter.test.mjs test/target-policy.test.mjs test/relay-redline-audit.test.mjs test/relay-repair-ledger.test.mjs test/relay-purge-blobs.test.mjs test/relay-scrub.test.mjs test/joint-dev-plan-store.test.mjs test/relay-basepath-blobs.test.mjs test/operator-token.test.mjs test/relay-health-capabilities.test.mjs test/joint-relay-dispatch.test.mjs test/operator-token-route.test.mjs test/relay-scrub-task-projection.test.mjs test/operator-key-approval.test.mjs test/relay-offer-journal.test.mjs test/joint-r5-wiring.test.mjs test/p1-hardening.test.mjs test/joint-merge-engine.test.mjs test/artifact-ref-dir-sink.test.mjs test/chat-sticker-guard.test.mjs test/relay-task-progress.test.mjs test/gpw-modes.test.mjs test/joint-cli.test.mjs test/joint-command-hash.test.mjs test/joint-dispatch-audit.test.mjs test/tools-cancel-auth.test.mjs test/fleet-mirror.test.mjs test/sink-git-ssh.test.mjs test/agent-sse-watchdog.test.mjs test/version-gate.test.mjs test/joint-merge-cli.test.mjs test/joint-progress.test.mjs test/project-room-truncation.test.mjs test/chat-crypto-aad.test.mjs test/agent-artifact-delivery.test.mjs test/dataplane-sink-route.test.mjs test/artifact-sink-e2e.test.mjs test/project-room-persistence.test.mjs test/project-room-disk-persistence.test.mjs test/project-stream-route.test.mjs test/chat-v2-gate.test.mjs",
|
|
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",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "twinsearth-w2m-dsh",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.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": [
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chat-enc-v2 capability gate (A7 wiring).
|
|
3
|
+
*
|
|
4
|
+
* The relay advertises `chat-enc-v2` in `/healthz.features`; when it does not, the plugin must keep
|
|
5
|
+
* sending v1 envelopes (which every 1.x relay accepts) rather than inventing a v2 shape. The answer
|
|
6
|
+
* is cached briefly so a chatty panel does not probe healthz on every message.
|
|
7
|
+
*/
|
|
8
|
+
const TTL_MS = 60_000;
|
|
9
|
+
const cache = new Map();
|
|
10
|
+
|
|
11
|
+
export function resetChatV2Cache() {
|
|
12
|
+
cache.clear();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {{rabbitUrl?: string|null, fetchImpl?: Function, now?: () => number, signal?: AbortSignal, ttlMs?: number}} input
|
|
17
|
+
* @returns {Promise<boolean>}
|
|
18
|
+
*/
|
|
19
|
+
export async function chatEncV2Supported(input = {}) {
|
|
20
|
+
const base = typeof input.rabbitUrl === 'string' ? input.rabbitUrl.replace(/\/$/, '') : '';
|
|
21
|
+
if (base === '') return false;
|
|
22
|
+
const now = typeof input.now === 'function' ? input.now() : Date.now();
|
|
23
|
+
const ttl = Number.isInteger(input.ttlMs) ? input.ttlMs : TTL_MS;
|
|
24
|
+
const hit = cache.get(base);
|
|
25
|
+
if (hit !== undefined && now - hit.at < ttl) return hit.supported;
|
|
26
|
+
const fetchImpl = typeof input.fetchImpl === 'function' ? input.fetchImpl : globalThis.fetch;
|
|
27
|
+
let supported = false;
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetchImpl(base + '/healthz', { headers: { accept: 'application/json' }, signal: input.signal });
|
|
30
|
+
if (response?.ok === true) {
|
|
31
|
+
const body = await response.json();
|
|
32
|
+
const features = Array.isArray(body?.features) ? body.features : [];
|
|
33
|
+
supported = features.includes('chat-enc-v2');
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
supported = false;
|
|
37
|
+
}
|
|
38
|
+
cache.set(base, { at: now, supported });
|
|
39
|
+
return supported;
|
|
40
|
+
}
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
decodeTranscript, extractUserMessages, sessionDirName,
|
|
23
23
|
} from './session-watch.mjs';
|
|
24
24
|
import { createSessionSync } from './session-sync.mjs';
|
|
25
|
+
import { deriveChatKey, encryptChatTextV2 } from '../util/chat-crypto.mjs';
|
|
26
|
+
import { chatEncV2Supported } from './chat-v2-gate.mjs';
|
|
25
27
|
|
|
26
28
|
/** Session prompts are mirrored only when the operator opts in; the default is off. */
|
|
27
29
|
export const DEFAULT_MIRROR_MODE = 'off';
|
|
@@ -174,9 +176,38 @@ export function registerChatRoutes(ctx, deps) {
|
|
|
174
176
|
const mirrorOnce = async () => {
|
|
175
177
|
const mode = mirrorModeOf(deps.cfg());
|
|
176
178
|
if (mode === 'off') return;
|
|
177
|
-
if (mode === 'encrypted') return; // encrypted mirroring lands with the AEAD v2 slice
|
|
178
179
|
const identity = await identityOf();
|
|
179
180
|
if (identity.conversationKey === null) return;
|
|
181
|
+
if (mode === 'encrypted') {
|
|
182
|
+
// Relay gate: without chat-enc-v2 the mirror stays off (never send a shape the relay cannot
|
|
183
|
+
// validate). The fleet key derives the same group key every machine uses; the AAD binds the
|
|
184
|
+
// message to this conversation, this machine and a deterministic message id, so a ciphertext
|
|
185
|
+
// copied into another session is refused at decryption time.
|
|
186
|
+
const supported = await chatEncV2Supported({ rabbitUrl: deps.cfg()?.rabbitUrl ?? null });
|
|
187
|
+
if (!supported) return;
|
|
188
|
+
const accountMaterial = typeof deps.accountMaterial === 'function' ? await deps.accountMaterial() : null;
|
|
189
|
+
const chatKey = deriveChatKey(accountMaterial ?? deps.cfg()?.fleetKey ?? null);
|
|
190
|
+
if (chatKey === null) return;
|
|
191
|
+
const file = newestTranscript(projectDirOf());
|
|
192
|
+
if (file === null) return;
|
|
193
|
+
let messages;
|
|
194
|
+
try { messages = extractUserMessages(decodeTranscript(readFileSync(file))); } catch { return; }
|
|
195
|
+
for (const payload of sync.pending(messages)) {
|
|
196
|
+
const plain = truncateUtf8(payload.text, MIRROR_MAX_BYTES);
|
|
197
|
+
const messageId = createHash('sha256').update(identity.conversationKey + '|' + identity.machineId + '|' + plain).digest('hex').slice(0, 32);
|
|
198
|
+
const sealed = encryptChatTextV2(chatKey, plain, { sessionId: identity.conversationKey, machineId: identity.machineId, messageId });
|
|
199
|
+
if (sealed.ok !== true) continue;
|
|
200
|
+
await postToRelay(identity, {
|
|
201
|
+
text: sealed.text,
|
|
202
|
+
enc: sealed.enc,
|
|
203
|
+
machine_id: identity.machineId,
|
|
204
|
+
machine_name: identity.machineName,
|
|
205
|
+
origin: 'session',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (identity.conversationKey === null) return;
|
|
180
211
|
const file = newestTranscript(projectDirOf());
|
|
181
212
|
if (file === null) return;
|
|
182
213
|
let messages;
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -206,7 +206,8 @@ import { buildBoard } from './task-graph.mjs';
|
|
|
206
206
|
import { verifyEnvelopeSignature } from '../util/envelope-signature.mjs';
|
|
207
207
|
import { createAskStore } from './ask-store.mjs';
|
|
208
208
|
import { deriveTurns } from './turn.mjs';
|
|
209
|
-
import { decryptChatMessage, deriveChatKey, encryptChatText, isEncrypted } from '../util/chat-crypto.mjs';
|
|
209
|
+
import { decryptChatMessage, deriveChatKey, encryptChatText, encryptChatTextV2, isEncrypted } from '../util/chat-crypto.mjs';
|
|
210
|
+
import { chatEncV2Supported } from './chat-v2-gate.mjs';
|
|
210
211
|
|
|
211
212
|
/**
|
|
212
213
|
* v1.2.6 reliability: two small files beside the plugin's state.
|
|
@@ -289,7 +290,7 @@ const MAX_STAGES = 16;
|
|
|
289
290
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
290
291
|
* overrides it at runtime for tests and packagers.
|
|
291
292
|
*/
|
|
292
|
-
const PLUGIN_VERSION = '2.0.
|
|
293
|
+
const PLUGIN_VERSION = '2.0.7';
|
|
293
294
|
|
|
294
295
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
295
296
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
@@ -6989,7 +6990,18 @@ hint: 'the local fleet-memory.md is unchanged; action read/show still reads loca
|
|
|
6989
6990
|
*/
|
|
6990
6991
|
const chatKey = deriveChatKey(cfg.fleetKey ?? resolvedFleetKey.value ?? null);
|
|
6991
6992
|
const plainText = typeof args?.text === 'string' ? args.text : '';
|
|
6992
|
-
const
|
|
6993
|
+
const idempotencyKey = typeof args?.idempotency_key === 'string' && args.idempotency_key.trim() !== ''
|
|
6994
|
+
? args.idempotency_key.trim()
|
|
6995
|
+
: 'spool-' + Date.now() + '-' + Math.random().toString(36).slice(2);
|
|
6996
|
+
// A7: v2 only when the relay advertises chat-enc-v2 AND this machine has an id to bind. Otherwise
|
|
6997
|
+
// send v1 (every relay accepts it) rather than an unverifiable v2 envelope.
|
|
6998
|
+
const canV2 = chatKey !== null && typeof cfg.machineId === 'string' && cfg.machineId !== ''
|
|
6999
|
+
&& await chatEncV2Supported({ rabbitUrl: cfg.rabbitUrl ?? null });
|
|
7000
|
+
const sealed = chatKey === null
|
|
7001
|
+
? null
|
|
7002
|
+
: (canV2
|
|
7003
|
+
? encryptChatTextV2(chatKey, plainText, { sessionId, machineId: cfg.machineId, messageId: idempotencyKey })
|
|
7004
|
+
: encryptChatText(chatKey, plainText));
|
|
6993
7005
|
/**
|
|
6994
7006
|
* v1.2.6 reliability: an unreachable Rabbit does not lose the message. The exact body that could not be sent is
|
|
6995
7007
|
* spooled to disk and replayed with the SAME idempotency key next time, so a message the relay actually received
|
|
@@ -7001,7 +7013,7 @@ hint: 'the local fleet-memory.md is unchanged; action read/show still reads loca
|
|
|
7001
7013
|
...(sealed !== null && sealed.ok === true ? { enc: sealed.enc } : {}),
|
|
7002
7014
|
...addressing,
|
|
7003
7015
|
...(mentions.length === 0 ? {} : { mentions }),
|
|
7004
|
-
...
|
|
7016
|
+
...{ idempotency_key: idempotencyKey },
|
|
7005
7017
|
...(typeof args?.reply_state === 'string' && (args.reply_state === 'answered' || args.reply_state === 'refused') ? { reply_state: args.reply_state } : {}),
|
|
7006
7018
|
};
|
|
7007
7019
|
let posted = null;
|
|
@@ -7058,7 +7070,7 @@ hint: 'the local fleet-memory.md is unchanged; action read/show still reads loca
|
|
|
7058
7070
|
const listKey = deriveChatKey(cfg.fleetKey ?? resolvedFleetKey.value ?? null);
|
|
7059
7071
|
const messages = rawMessages.map((message) => {
|
|
7060
7072
|
if (!isEncrypted(message)) return message;
|
|
7061
|
-
const opened = decryptChatMessage(listKey, message);
|
|
7073
|
+
const opened = decryptChatMessage(listKey, message, { sessionId: String(query.session_id ?? message.session_id ?? ''), machineId: typeof message.machine_id === 'string' ? message.machine_id : undefined });
|
|
7062
7074
|
if (opened.ok === true) return { ...message, text: opened.text, decrypted: true };
|
|
7063
7075
|
return { ...message, text: '', decrypted: false, encrypted: true, decrypt_error: opened.code };
|
|
7064
7076
|
});
|