@twinsearth/w2m-dsh-plugin 1.0.15 → 1.1.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/CHANGELOG.md +90 -0
- package/package.json +38 -37
- package/plugin.json +1 -1
- package/src/agent/blake2s.mjs +151 -0
- package/src/agent/crypto.mjs +57 -14
- package/src/plugin/tools.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,96 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
+
## [1.1.0] — 2026-10-17
|
|
6
|
+
|
|
7
|
+
**The direct path now works on runtimes whose OpenSSL has no BLAKE2s, and the compatibility gate that measures the real
|
|
8
|
+
Loader was asserting a tool list that had gone stale.**
|
|
9
|
+
|
|
10
|
+
### The handshake hash no longer depends on the runtime's OpenSSL
|
|
11
|
+
|
|
12
|
+
One dispatch reported, per machine:
|
|
13
|
+
|
|
14
|
+
> `P2P_PUNCH_FAILED: handshakeHash requires BLAKE2s-256: BLAKE2s-256 unavailable on this runtime: Digest method not
|
|
15
|
+
> supported. It is a WIRE PROTOCOL primitive -- the handshake hash, the prologue hash and the session binding -- so it
|
|
16
|
+
> cannot be replaced with SHA-256 without breaking interoperability with every peer.`
|
|
17
|
+
|
|
18
|
+
That message names the constraint correctly and draws the wrong conclusion. **BLAKE2s is public and deterministic**, so
|
|
19
|
+
the digest can be computed here instead of by OpenSSL: the bytes are identical to what a peer with a native
|
|
20
|
+
implementation produces, and interoperability is preserved exactly. The alternative -- refusing to handshake -- makes
|
|
21
|
+
**every** direct path fall back to the relay on that runtime, which is what the fleet was doing without saying so.
|
|
22
|
+
|
|
23
|
+
* `src/agent/blake2s.mjs` — BLAKE2s-256 in plain JavaScript (unkeyed, which is all the wire protocol uses).
|
|
24
|
+
* `src/agent/crypto.mjs` — `handshakeHash` tries the native digest and falls back to it; `blake2s256Support()`
|
|
25
|
+
now reports **how** the digest is reached (`native` or `js-fallback`); `cryptoCapabilities()` counts the fallback
|
|
26
|
+
as present, because what the handshake needs is the digest, not OpenSSL's implementation of it.
|
|
27
|
+
* The fallback is verified before it is trusted: its empty-input digest is compared against the published vector, so a
|
|
28
|
+
broken fallback reports unavailable rather than producing hashes no peer will agree with.
|
|
29
|
+
|
|
30
|
+
**The bug that made this take longer than it should have.** The first version inverted both `v[14]` and `v[15]` for
|
|
31
|
+
the final block. BLAKE2 sets `f0 = 0xFFFFFFFF` on the last block and leaves `f1 = 0` for sequential hashing, so
|
|
32
|
+
only `v[14]` is inverted. Inverting both produces a **stable, plausible-looking digest that matches no reference**:
|
|
33
|
+
the three RFC vectors failed while the compression function -- checked against an independent implementation -- was
|
|
34
|
+
correct the whole time. `test/blake2s.test.mjs` pins the vectors and compares against `node:crypto` across every
|
|
35
|
+
block boundary, so that class of mistake cannot survive again.
|
|
36
|
+
|
|
37
|
+
### The compatibility gate was asserting a stale tool count
|
|
38
|
+
|
|
39
|
+
`test/loader-entry.test.mjs` asserted the plugin registers a fixed set of tools, under a name that still said "eight".
|
|
40
|
+
The set had grown to **eleven** (`w2m_approvals` and `w2m_council` were missing), so the gate that mounts the packed
|
|
41
|
+
artifact in a real profile and runs the real Loader failed at:
|
|
42
|
+
|
|
43
|
+
> `registers all eight w2m_* tools`
|
|
44
|
+
|
|
45
|
+
The list is now
|
|
46
|
+
the actual eleven, sorted so a missing name and a renamed name fail differently, and the test says what it means.
|
|
47
|
+
|
|
48
|
+
### Two configuration facts this exposed, recorded because they are not obvious
|
|
49
|
+
|
|
50
|
+
A machine can be **installed, running, and still outside the fleet** -- both were true here:
|
|
51
|
+
|
|
52
|
+
* `dsh.profile.bundles` must list the package. Installing it as a dependency is not enough: the host composes the
|
|
53
|
+
entry list from that array, so a package present in `node_modules` and absent from `bundles` simply never loads.
|
|
54
|
+
* `autoStartAgent: true` is **off by default**, and joining happens inside the agent it starts. Without it a machine
|
|
55
|
+
never joins, no matter how correctly it is installed.
|
|
56
|
+
|
|
57
|
+
With both set, this machine joined, listed its peers, accepted a dispatch and executed it (`node --version`, exit 0).
|
|
58
|
+
|
|
59
|
+
### Verified
|
|
60
|
+
|
|
61
|
+
Full suite green; pre-flight **GREEN**, 15 of 15 checks. `test/blake2s.test.mjs`: **10 pass, 0 fail**, including
|
|
62
|
+
byte-identical agreement with `node:crypto` at lengths 0, 1, 2, 3, 31, 32, 33, 63, 64, 65, 127, 128, 129, 191, 192,
|
|
63
|
+
193, 255, 256, 257 and 1000.
|
|
64
|
+
|
|
65
|
+
## [1.0.16] — 2026-10-17
|
|
66
|
+
|
|
67
|
+
**The last two failures on Linux CI, and one of them was a defect in the release process: a release was advertised as
|
|
68
|
+
"latest" before its assets had finished uploading.**
|
|
69
|
+
|
|
70
|
+
### A release must not be "latest" until it can be downloaded
|
|
71
|
+
|
|
72
|
+
The release script created the release with `make_latest: 'true'` and *then* uploaded the tarball. That leaves a
|
|
73
|
+
window in which `releases/latest` points at a release **with no tarball** -- and CI's live-API job read exactly that,
|
|
74
|
+
failing with:
|
|
75
|
+
|
|
76
|
+
> `ASSET_MISSING no release asset matches /\.(?:tgz|tar\.gz)$/i`
|
|
77
|
+
|
|
78
|
+
on a release that was complete seconds later. **The ordering was the defect, not the test.** The release is now created
|
|
79
|
+
without `make_latest`, the assets are attached, **and only then is it promoted** -- so a reader following the
|
|
80
|
+
"latest" link always lands on something downloadable.
|
|
81
|
+
|
|
82
|
+
### A test that was only true on a developer machine
|
|
83
|
+
|
|
84
|
+
`test/fleet-sync.test.mjs` removes the repository's git identity to prove the tool refuses to rebase without one. It
|
|
85
|
+
checked `git config user.email`, which **falls back to the global and system config** -- absent on a developer machine,
|
|
86
|
+
**present on GitHub's runners**. So the tool correctly found an identity and the test's premise was false, on CI only.
|
|
87
|
+
|
|
88
|
+
The test now points `GIT_CONFIG_GLOBAL` and `GIT_CONFIG_SYSTEM` at paths that do not exist, so git's config is exactly
|
|
89
|
+
as empty as the test claims. It also restores every environment variable it touches.
|
|
90
|
+
|
|
91
|
+
### Verified
|
|
92
|
+
|
|
93
|
+
Full suite **2380 tests in 93 files, 2366 pass, 0 fail, 14 skip**; pre-flight **GREEN, 15 of 15 checks**.
|
|
94
|
+
|
|
5
95
|
## [1.0.15] — 2026-10-17
|
|
6
96
|
|
|
7
97
|
**Every 1.0.x GitHub release was missing its `SHA256SUMS` asset, so updating from GitHub could not be verified and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.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
|
"main": "lib/index.js",
|
|
@@ -117,53 +117,54 @@
|
|
|
117
117
|
}
|
|
118
118
|
},
|
|
119
119
|
"scripts": {
|
|
120
|
-
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.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/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",
|
|
121
|
-
"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",
|
|
122
|
-
"test:relay": "node --test --test-force-exit test/relay.test.mjs",
|
|
123
|
-
"test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
|
|
124
|
-
"test:tools": "node --test --test-force-exit test/tools.test.mjs",
|
|
125
|
-
"test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
|
|
126
|
-
"test:all": "node --test --test-force-exit test/relay.test.mjs test/broadcast.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/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/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-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.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/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",
|
|
127
|
-
"pack": "node scripts/pack.mjs",
|
|
128
|
-
"verify:join": "node scripts/verify-join.mjs",
|
|
129
|
-
"lint": "eslint .",
|
|
130
|
-
"lint:fix": "eslint . --fix",
|
|
131
120
|
"format": "prettier --write .",
|
|
132
121
|
"format:check": "prettier --check .",
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
122
|
+
"layer-check": "node scripts/layer-check.mjs",
|
|
123
|
+
"lint": "eslint .",
|
|
124
|
+
"lint:fix": "eslint . --fix",
|
|
125
|
+
"orphans": "node --test --test-force-exit test/accepted-orphans.test.mjs",
|
|
126
|
+
"pack": "node scripts/pack.mjs",
|
|
127
|
+
"reachability": "node scripts/reachability-check.mjs",
|
|
128
|
+
"reachability:classify": "node scripts/reachability-classify.mjs",
|
|
129
|
+
"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/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",
|
|
130
|
+
"test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
|
|
131
|
+
"test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
|
|
132
|
+
"test:all": "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/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/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-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.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/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",
|
|
133
|
+
"test:approval-gate": "node --test --test-force-exit test/approval-gate.test.mjs",
|
|
134
|
+
"test:approval-index": "node --test --test-force-exit test/approval-index.test.mjs",
|
|
136
135
|
"test:authority": "node --test --test-force-exit test/authority.test.mjs",
|
|
136
|
+
"test:authority-choice": "node --test --test-force-exit test/authority-choice.test.mjs",
|
|
137
|
+
"test:blake2s": "node --test --test-force-exit test/blake2s.test.mjs",
|
|
137
138
|
"test:blocks": "node --test --test-force-exit test/blocks.test.mjs",
|
|
138
|
-
"test:
|
|
139
|
-
"test:
|
|
140
|
-
"test:
|
|
141
|
-
"test:
|
|
139
|
+
"test:council": "node --test --test-force-exit test/council.test.mjs",
|
|
140
|
+
"test:council-session": "node --test --test-force-exit test/council-session.test.mjs",
|
|
141
|
+
"test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
|
|
142
|
+
"test:crypto-blake2s": "node --test --test-force-exit test/crypto-blake2s.test.mjs",
|
|
143
|
+
"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",
|
|
144
|
+
"test:exit-channel": "node --test --test-force-exit test/exit-channel.test.mjs",
|
|
145
|
+
"test:field-authority": "node --test --test-force-exit test/field-authority.test.mjs",
|
|
142
146
|
"test:fleet-concurrency": "node --test --test-force-exit test/fleet-concurrency.test.mjs",
|
|
147
|
+
"test:fleet-memory-e2e": "node --test --test-force-exit test/fleet-memory-e2e.test.mjs",
|
|
148
|
+
"test:fleet-merge": "node --test --test-force-exit test/fleet-merge.test.mjs",
|
|
143
149
|
"test:fleet-merge-race": "node --test --test-force-exit test/fleet-merge-race.test.mjs",
|
|
144
|
-
"test:health": "node --test --test-force-exit test/health.test.mjs",
|
|
145
|
-
"test:storage": "node --test --test-force-exit test/storage.test.mjs",
|
|
146
|
-
"test:exit-channel": "node --test --test-force-exit test/exit-channel.test.mjs",
|
|
147
|
-
"test:council": "node --test --test-force-exit test/council.test.mjs",
|
|
148
|
-
"test:layers": "node --test --test-force-exit test/layer-model.test.mjs",
|
|
149
|
-
"test:log-bounds": "node --test --test-force-exit test/log-bounds.test.mjs",
|
|
150
|
-
"layer-check": "node scripts/layer-check.mjs",
|
|
151
|
-
"test:test-report": "node --test --test-force-exit test/test-report.test.mjs",
|
|
152
150
|
"test:fleet-scale": "node --test --test-force-exit test/fleet-scale.test.mjs",
|
|
153
151
|
"test:fleet-state": "node --test --test-force-exit test/fleet-state.test.mjs",
|
|
154
|
-
"test:
|
|
152
|
+
"test:fleet-sync": "node --test --test-force-exit test/fleet-sync.test.mjs",
|
|
153
|
+
"test:health": "node --test --test-force-exit test/health.test.mjs",
|
|
155
154
|
"test:installed-version": "node --test --test-force-exit test/installed-version.test.mjs",
|
|
156
|
-
"test:
|
|
155
|
+
"test:layers": "node --test --test-force-exit test/layer-model.test.mjs",
|
|
156
|
+
"test:log-bounds": "node --test --test-force-exit test/log-bounds.test.mjs",
|
|
157
|
+
"test:memory": "node --test --test-force-exit test/memory.test.mjs",
|
|
158
|
+
"test:p2p": "node --test --test-force-exit test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/stun-server.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs",
|
|
157
159
|
"test:packet": "node --test --test-force-exit test/machine-test-packet.test.mjs",
|
|
158
|
-
"test:
|
|
159
|
-
"test:approval-gate": "node --test --test-force-exit test/approval-gate.test.mjs",
|
|
160
|
-
"test:authority-choice": "node --test --test-force-exit test/authority-choice.test.mjs",
|
|
160
|
+
"test:permission": "node --test --test-force-exit test/permission.test.mjs",
|
|
161
161
|
"test:reachability": "node --test --test-force-exit test/reachability-check.test.mjs",
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"test:
|
|
165
|
-
"
|
|
166
|
-
"
|
|
162
|
+
"test:relay": "node --test --test-force-exit test/relay.test.mjs",
|
|
163
|
+
"test:storage": "node --test --test-force-exit test/storage.test.mjs",
|
|
164
|
+
"test:test-report": "node --test --test-force-exit test/test-report.test.mjs",
|
|
165
|
+
"test:tools": "node --test --test-force-exit test/tools.test.mjs",
|
|
166
|
+
"verify": "node scripts/check-entrypoints.mjs && node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/tools.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/p2p-node.test.mjs test/p2p-agent.test.mjs test/client-bundle.test.mjs test/loader-entry.test.mjs",
|
|
167
|
+
"verify:join": "node scripts/verify-join.mjs"
|
|
167
168
|
},
|
|
168
169
|
"keywords": [
|
|
169
170
|
"dsh-bundle",
|
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.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"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.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BLAKE2s-256 in plain JavaScript -- the handshake hash when the runtime cannot provide it.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* `src/agent/crypto.mjs` computes the handshake hash, the prologue hash and the session binding with
|
|
7
|
+
* `createHash('blake2s256')`. On the DSH desktop runtime that call fails:
|
|
8
|
+
*
|
|
9
|
+
* P2P_PUNCH_FAILED: handshakeHash requires BLAKE2s-256: BLAKE2s-256 unavailable on this runtime:
|
|
10
|
+
* Digest method not supported. It is a WIRE PROTOCOL primitive -- the handshake hash, the prologue hash and
|
|
11
|
+
* the session binding -- so it cannot be replaced with SHA-256 without breaking interoperability with every peer.
|
|
12
|
+
*
|
|
13
|
+
* That message is right about the constraint and wrong about the conclusion. BLAKE2s is **public and
|
|
14
|
+
* deterministic**, so the algorithm can be computed here instead of by OpenSSL: the bytes are identical to what a
|
|
15
|
+
* peer with a native implementation produces, and interoperability is preserved exactly. The alternative -- refusing
|
|
16
|
+
* to handshake -- makes every direct path fall back to the relay on this runtime, which is what was observed.
|
|
17
|
+
*
|
|
18
|
+
* ## Scope: unkeyed only
|
|
19
|
+
*
|
|
20
|
+
* The wire protocol needs the **unkeyed** 32-byte digest (prologue, session binding, MAC inputs). Keyed BLAKE2s
|
|
21
|
+
* (`mac1`/`mac2`) is explicitly not implemented in this build either way, so this module implements exactly the
|
|
22
|
+
* unkeyed case and refuses a key rather than pretending.
|
|
23
|
+
*
|
|
24
|
+
* ## Correctness
|
|
25
|
+
*
|
|
26
|
+
* Verified against the three published vectors (empty, "abc", the pangram) and, when the runtime does have a native
|
|
27
|
+
* `blake2s256`, cross-checked byte-for-byte against it over many lengths -- the test file asserts both.
|
|
28
|
+
*
|
|
29
|
+
* @see https://www.rfc-editor.org/rfc/rfc7693 (BLAKE2 -- BLAKE2s, 32-bit words, 10 rounds)
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const IV = new Uint32Array([
|
|
33
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
/** Message-word permutation, one row per round (10 rounds for BLAKE2s). */
|
|
37
|
+
const SIGMA = [
|
|
38
|
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
|
39
|
+
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
|
|
40
|
+
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
|
|
41
|
+
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
|
|
42
|
+
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
|
|
43
|
+
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
|
|
44
|
+
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
|
|
45
|
+
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
|
|
46
|
+
[6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
|
|
47
|
+
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const BLOCK = 64;
|
|
51
|
+
const OUT_BYTES = 32;
|
|
52
|
+
|
|
53
|
+
const rotr = (x, n) => ((x >>> n) | (x << (32 - n))) >>> 0;
|
|
54
|
+
|
|
55
|
+
/** The G mixing function, rotation constants 16/12/8/7 for BLAKE2s. */
|
|
56
|
+
function mix(v, a, b, c, d, x, y) {
|
|
57
|
+
v[a] = (v[a] + v[b] + x) >>> 0;
|
|
58
|
+
v[d] = rotr(v[d] ^ v[a], 16);
|
|
59
|
+
v[c] = (v[c] + v[d]) >>> 0;
|
|
60
|
+
v[b] = rotr(v[b] ^ v[c], 12);
|
|
61
|
+
v[a] = (v[a] + v[b] + y) >>> 0;
|
|
62
|
+
v[d] = rotr(v[d] ^ v[a], 8);
|
|
63
|
+
v[c] = (v[c] + v[d]) >>> 0;
|
|
64
|
+
v[b] = rotr(v[b] ^ v[c], 7);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Read 16 little-endian 32-bit words out of a 64-byte block. */
|
|
68
|
+
function words(block, offset, m) {
|
|
69
|
+
for (let i = 0; i < 16; i += 1) {
|
|
70
|
+
const p = offset + i * 4;
|
|
71
|
+
m[i] = (block[p] | (block[p + 1] << 8) | (block[p + 2] << 16) | (block[p + 3] << 24)) >>> 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** One compression call: 64 bytes of message, a 64-bit byte counter, and the final-block flag. */
|
|
76
|
+
function compress(h, block, offset, counter, last) {
|
|
77
|
+
const v = new Uint32Array(16);
|
|
78
|
+
const m = new Uint32Array(16);
|
|
79
|
+
v.set(h, 0);
|
|
80
|
+
v.set(IV, 8);
|
|
81
|
+
v[12] ^= counter >>> 0;
|
|
82
|
+
v[13] ^= Math.floor(counter / 0x100000000) >>> 0;
|
|
83
|
+
if (last) {
|
|
84
|
+
/**
|
|
85
|
+
* Only `v[14]` is inverted -- `f0 = 0xFFFFFFFF` marks the last block, and **`f1` stays 0** for sequential
|
|
86
|
+
* hashing (it is only 1 for the last node of a tree). Inverting `v[15]` as well is the classic wrong
|
|
87
|
+
* implementation: it produces a stable, plausible-looking digest that matches no reference, which is exactly how
|
|
88
|
+
* this was found -- three RFC vectors failed and the compression function, checked against a reference
|
|
89
|
+
* implementation, turned out to be correct while the final-block setup was not.
|
|
90
|
+
*/
|
|
91
|
+
v[14] = (v[14] ^ 0xffffffff) >>> 0;
|
|
92
|
+
}
|
|
93
|
+
words(block, offset, m);
|
|
94
|
+
|
|
95
|
+
for (let r = 0; r < 10; r += 1) {
|
|
96
|
+
const s = SIGMA[r];
|
|
97
|
+
mix(v, 0, 4, 8, 12, m[s[0]], m[s[1]]);
|
|
98
|
+
mix(v, 1, 5, 9, 13, m[s[2]], m[s[3]]);
|
|
99
|
+
mix(v, 2, 6, 10, 14, m[s[4]], m[s[5]]);
|
|
100
|
+
mix(v, 3, 7, 11, 15, m[s[6]], m[s[7]]);
|
|
101
|
+
mix(v, 0, 5, 10, 15, m[s[8]], m[s[9]]);
|
|
102
|
+
mix(v, 1, 6, 11, 12, m[s[10]], m[s[11]]);
|
|
103
|
+
mix(v, 2, 7, 8, 13, m[s[12]], m[s[13]]);
|
|
104
|
+
mix(v, 3, 4, 9, 14, m[s[14]], m[s[15]]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < 8; i += 1) h[i] = (h[i] ^ v[i] ^ v[i + 8]) >>> 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* BLAKE2s-256 of a buffer. Unkeyed; a key is refused rather than silently ignored.
|
|
112
|
+
*
|
|
113
|
+
* @param {Buffer|Uint8Array|string} input
|
|
114
|
+
* @returns {Buffer} 32 bytes.
|
|
115
|
+
*/
|
|
116
|
+
export function blake2s256(input) {
|
|
117
|
+
const data = typeof input === 'string' ? Buffer.from(input, 'utf8') : Buffer.from(input);
|
|
118
|
+
const h = new Uint32Array(IV);
|
|
119
|
+
// Parameter block: digest length 32, key length 0, fanout 1, depth 1.
|
|
120
|
+
h[0] = (h[0] ^ 0x01010000 ^ OUT_BYTES) >>> 0;
|
|
121
|
+
|
|
122
|
+
let counter = 0;
|
|
123
|
+
// Every block except the last is hashed as a non-final block, so the final block (possibly all zeros) is handled
|
|
124
|
+
// separately -- BLAKE2 pads the last block and sets the final flag, it does not append an extra block.
|
|
125
|
+
const full = data.length === 0 ? 0 : Math.ceil(data.length / BLOCK) - 1;
|
|
126
|
+
for (let i = 0; i < full; i += 1) {
|
|
127
|
+
counter += BLOCK;
|
|
128
|
+
compress(h, data, i * BLOCK, counter, false);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const lastOffset = full * BLOCK;
|
|
132
|
+
const lastLength = data.length - lastOffset;
|
|
133
|
+
const tail = Buffer.alloc(BLOCK);
|
|
134
|
+
data.copy(tail, 0, lastOffset, data.length);
|
|
135
|
+
counter += lastLength;
|
|
136
|
+
compress(h, tail, 0, counter, true);
|
|
137
|
+
|
|
138
|
+
const out = Buffer.alloc(OUT_BYTES);
|
|
139
|
+
for (let i = 0; i < 8; i += 1) out.writeUInt32LE(h[i], i * 4);
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The three RFC 7693 vectors, exported so a test can assert them without copying hex. */
|
|
144
|
+
export const BLAKE2S_VECTORS = [
|
|
145
|
+
{ input: '', hex: '69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9' },
|
|
146
|
+
{ input: 'abc', hex: '508c5e8c327c14e2e1a72ba34eeb452f37458b209ed63a294d999b4c86675982' },
|
|
147
|
+
{
|
|
148
|
+
input: 'The quick brown fox jumps over the lazy dog',
|
|
149
|
+
hex: '606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812',
|
|
150
|
+
},
|
|
151
|
+
];
|
package/src/agent/crypto.mjs
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
} from 'node:crypto';
|
|
56
56
|
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
57
57
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
58
|
+
import { blake2s256 as blake2s256Js } from './blake2s.mjs';
|
|
58
59
|
|
|
59
60
|
/* -------------------------------------------------------------------------- */
|
|
60
61
|
/* Names that travel on the wire, or into a report */
|
|
@@ -738,7 +739,8 @@ export function cryptoCapabilities() {
|
|
|
738
739
|
}
|
|
739
740
|
};
|
|
740
741
|
|
|
741
|
-
|
|
742
|
+
// The JavaScript fallback counts here: what the handshake needs is the digest, not OpenSSL's implementation of it.
|
|
743
|
+
check('blake2s256', () => blake2s256Support().ok);
|
|
742
744
|
check('sha256', () => createHash('sha256').update('').digest().length === 32);
|
|
743
745
|
check('chacha20-poly1305', () => {
|
|
744
746
|
const key = randomBytes(32);
|
|
@@ -801,16 +803,47 @@ export function blake2s256Support() {
|
|
|
801
803
|
blake2sSupport = {
|
|
802
804
|
ok: bytes === 32,
|
|
803
805
|
advertised,
|
|
806
|
+
source: 'native',
|
|
804
807
|
detail: bytes === 32
|
|
805
808
|
? `BLAKE2s-256 available (${advertised ? 'advertised by getHashes' : 'works though not advertised'})`
|
|
806
809
|
: `BLAKE2s-256 produced ${bytes} bytes rather than 32`,
|
|
807
810
|
};
|
|
808
811
|
} catch (e) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
812
|
+
/**
|
|
813
|
+
* **The native digest is unavailable, but that is not the end of the handshake.**
|
|
814
|
+
*
|
|
815
|
+
* `P2P_PUNCH_FAILED: Digest method not supported` -- OpenSSL's message, naming no algorithm -- was observed on the
|
|
816
|
+
* DSH desktop runtime. BLAKE2s is public and deterministic, so the digest can be computed in JavaScript and the
|
|
817
|
+
* bytes are identical to a peer with a native implementation: **interoperability is preserved exactly**, which is
|
|
818
|
+
* the constraint the old message correctly named and then wrongly concluded from.
|
|
819
|
+
*
|
|
820
|
+
* The fallback is verified before it is trusted: its digest of the empty input is compared against the published
|
|
821
|
+
* vector, so a broken fallback reports unavailable rather than producing hashes no peer will agree with.
|
|
822
|
+
*/
|
|
823
|
+
const KNOWN_EMPTY = '69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9';
|
|
824
|
+
let jsOk = false;
|
|
825
|
+
try {
|
|
826
|
+
jsOk = blake2s256Js(Buffer.alloc(0)).toString('hex') === KNOWN_EMPTY;
|
|
827
|
+
} catch {
|
|
828
|
+
jsOk = false;
|
|
829
|
+
}
|
|
830
|
+
blake2sSupport = jsOk
|
|
831
|
+
? {
|
|
832
|
+
ok: true,
|
|
833
|
+
advertised,
|
|
834
|
+
source: 'js-fallback',
|
|
835
|
+
detail: `BLAKE2s-256 computed in JavaScript: the runtime's digest is unavailable (${e?.message ?? String(e)}), `
|
|
836
|
+
+ 'and the fallback matches the published empty-input vector, so handshakes stay interoperable',
|
|
837
|
+
}
|
|
838
|
+
: {
|
|
839
|
+
ok: false,
|
|
840
|
+
advertised,
|
|
841
|
+
source: 'none',
|
|
842
|
+
detail: `BLAKE2s-256 unavailable on this runtime: ${e?.message ?? String(e)}, and the JavaScript fallback failed `
|
|
843
|
+
+ 'its known-answer check. It is a WIRE PROTOCOL primitive -- the handshake hash, the prologue hash and the '
|
|
844
|
+
+ 'session binding -- so it cannot be replaced with SHA-256 without breaking interoperability with every peer. '
|
|
845
|
+
+ 'This machine cannot take part in a handshake until one of the two works.',
|
|
846
|
+
};
|
|
814
847
|
}
|
|
815
848
|
return blake2sSupport;
|
|
816
849
|
}
|
|
@@ -826,18 +859,28 @@ export function blake2s256Support() {
|
|
|
826
859
|
* @returns {Buffer} 32 bytes.
|
|
827
860
|
*/
|
|
828
861
|
export function handshakeHash(...parts) {
|
|
829
|
-
let
|
|
862
|
+
let out;
|
|
830
863
|
try {
|
|
831
|
-
hash = createHash('blake2s256');
|
|
864
|
+
const hash = createHash('blake2s256');
|
|
865
|
+
for (const part of parts) hash.update(part);
|
|
866
|
+
out = hash.digest();
|
|
832
867
|
} catch (e) {
|
|
868
|
+
/**
|
|
869
|
+
* The runtime has no native BLAKE2s. Compute the same 32 bytes in JavaScript instead of refusing the handshake:
|
|
870
|
+
* the algorithm is public and deterministic, so the bytes a peer computes are unchanged and interoperability is
|
|
871
|
+
* preserved. A runtime where neither path works still fails loudly, with the algorithm and the consequence named.
|
|
872
|
+
*/
|
|
833
873
|
const support = blake2s256Support();
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
874
|
+
if (!support.ok) {
|
|
875
|
+
const error = new Error(`handshakeHash requires BLAKE2s-256: ${support.detail}`);
|
|
876
|
+
error.code = 'CRYPTO_BLAKE2S256_UNAVAILABLE';
|
|
877
|
+
error.cause = e;
|
|
878
|
+
throw error;
|
|
879
|
+
}
|
|
880
|
+
out = blake2s256Js(Buffer.concat(
|
|
881
|
+
parts.map((part) => (typeof part === 'string' ? Buffer.from(part, 'utf8') : Buffer.from(part))),
|
|
882
|
+
));
|
|
838
883
|
}
|
|
839
|
-
for (const part of parts) hash.update(part);
|
|
840
|
-
const out = hash.digest();
|
|
841
884
|
if (out.length !== 32) {
|
|
842
885
|
const error = new Error(`handshakeHash expected a 32-byte digest and produced ${out.length}; peers would compute a different handshake hash`);
|
|
843
886
|
error.code = 'CRYPTO_BLAKE2S256_WRONG_LENGTH';
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -211,7 +211,7 @@ const MAX_STAGES = 16;
|
|
|
211
211
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
212
212
|
* overrides it at runtime for tests and packagers.
|
|
213
213
|
*/
|
|
214
|
-
const PLUGIN_VERSION = '1.0
|
|
214
|
+
const PLUGIN_VERSION = '1.1.0';
|
|
215
215
|
|
|
216
216
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
217
217
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|