@twinsearth/w2m-dsh-plugin 1.1.16 → 1.1.18

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.
@@ -12,6 +12,6 @@
12
12
 
13
13
  [mcp_servers.w2m-fleet]
14
14
  command = "npx"
15
- args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@1.1.16", "w2m-mcp"]
15
+ args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@1.1.18", "w2m-mcp"]
16
16
  startup_timeout_sec = 20
17
17
  tool_timeout_sec = 60
package/docs/PLATFORMS.md CHANGED
@@ -81,3 +81,25 @@ So the split is:
81
81
 
82
82
  Marking the last two rows "prepared" rather than "done" is deliberate: a manifest that exists is not a listing, and
83
83
  this project has already shipped a "mechanism with no caller" seven times.
84
+
85
+ ## Fleet chat over MCP (\w2m_chat\)
86
+
87
+ \w2m_chat\ is available on **every** transport described above, because it is an ordinary MCP tool like the rest --
88
+ nothing about it depends on the DSH host, and it is not on the deliberately-host-only list (that list contains
89
+ \w2m_run\ only, for the reason given at the top of this document).
90
+
91
+ What it does, and what it deliberately does not:
92
+
93
+ - \ction: "send"\ posts plain text (≤ 4 KiB, UTF-8, no control characters) to the relay at
94
+ \POST /v1/fleet/messages\. The author is the **device token holder**; a body field cannot name a different machine.
95
+ - \ction: "list"\ reads one session's flow at \GET /v1/fleet/messages?session_id=…\ and returns the messages together
96
+ with the fleet's current byte usage, so a client can warn before the quota refuses a write.
97
+ - A refused write is returned with its named code (\FLEET_QUOTA_EXCEEDED\, \MESSAGE_TOO_LARGE\, \NOT_TEXT\,
98
+ \EMPTY_TEXT\) -- the tool does not flatten them into "failed".
99
+ - **No push inside MCP.** The \
100
+ ew_message\ notification is an SSE stream
101
+ (\GET /v1/fleet/messages/stream\, event id only, never the text); an MCP client that cannot hold a stream polls
102
+ \ction: "list"\ with \since_ms\. The stream is a hint; the text always comes back through the authenticated read.
103
+
104
+ Encryption and sharding are server-side: history is sealed with AES-256-GCM under a key derived from the fleet key and
105
+ split across the fleet's machines with two replicas per part. No client sees another machine's shard bodies.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "1.1.16",
3
+ "version": "1.1.18",
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",
@@ -129,13 +129,13 @@
129
129
  "pack": "node scripts/pack.mjs",
130
130
  "reachability": "node scripts/reachability-check.mjs",
131
131
  "reachability:classify": "node scripts/reachability-classify.mjs",
132
- "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",
132
+ "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",
133
133
  "test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
134
134
  "test:account-identity": "node --test --test-force-exit test/account-identity.test.mjs",
135
135
  "test:admission": "node --test --test-force-exit test/admission.test.mjs",
136
136
  "test:admission-routes": "node --test --test-force-exit test/admission-routes.test.mjs",
137
137
  "test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
138
- "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/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",
138
+ "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/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",
139
139
  "test:approval-gate": "node --test --test-force-exit test/approval-gate.test.mjs",
140
140
  "test:approval-index": "node --test --test-force-exit test/approval-index.test.mjs",
141
141
  "test:authority": "node --test --test-force-exit test/authority.test.mjs",
@@ -175,7 +175,13 @@
175
175
  "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",
176
176
  "verify:join": "node scripts/verify-join.mjs",
177
177
  "test:exec-env": "node --test --test-force-exit test/exec-env-injection.test.mjs",
178
- "test:account-join": "node --test --test-force-exit test/account-join.test.mjs"
178
+ "test:account-join": "node --test --test-force-exit test/account-join.test.mjs",
179
+ "test:fleet-messages": "node --test --test-force-exit test/fleet-messages.test.mjs",
180
+ "test:message-shards": "node --test --test-force-exit test/message-shards.test.mjs",
181
+ "test:fleet-messages-http": "node --test --test-force-exit test/fleet-messages-http.test.mjs",
182
+ "test:message-notify": "node --test --test-force-exit test/message-notify.test.mjs",
183
+ "test:update-channels": "node --test --test-force-exit test/update-channels.test.mjs",
184
+ "test:update-prerelease": "node --test --test-force-exit test/update-prerelease.test.mjs"
179
185
  },
180
186
  "keywords": [
181
187
  "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.1.16",
4
+ "version": "1.1.18",
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": [
@@ -89,7 +89,10 @@
89
89
  */
90
90
 
91
91
  import { execFile } from 'node:child_process';
92
- import { createHash, randomUUID } from 'node:crypto';
92
+
93
+ import { resolveChannels } from './update-channels.mjs';
94
+ import { fetchLatestRelease, fetchPrereleaseRelease, requireChecksum, downloadVerified, DEFAULT_REPO } from './update-source.mjs';
95
+ import { installUpdate } from './update-install.mjs';import { createHash, randomUUID } from 'node:crypto';
93
96
  import { promises as fs } from 'node:fs';
94
97
  import os from 'node:os';
95
98
  import path from 'node:path';
@@ -214,7 +217,7 @@ const MAX_STAGES = 16;
214
217
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
215
218
  * overrides it at runtime for tests and packagers.
216
219
  */
217
- const PLUGIN_VERSION = '1.1.16';
220
+ const PLUGIN_VERSION = '1.1.18';
218
221
 
219
222
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
220
223
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
@@ -4858,7 +4861,50 @@ export async function apply(ctx, config = {}) {
4858
4861
  // `jsonOutput` is a renderer, not a serialiser; the tool still owns turning its value into
4859
4862
  // text.
4860
4863
  if (action === 'status') {
4861
- return JSON.stringify({ ok: true, action, update: updater.describe() }, null, 2);
4864
+ const described = { ok: true, action, update: updater.describe() };
4865
+ // Opt-in on purpose: the plain status call is local and offline, and making it reach GitHub would slow every
4866
+ // w2m_status and fail behind a proxy. The three channels need two API calls, so they are asked for explicitly.
4867
+ if (args?.channels !== true) return JSON.stringify(described, null, 2);
4868
+ const repo = (typeof process.env.W2M_UPDATE_REPO === 'string' && process.env.W2M_UPDATE_REPO.trim() !== '')
4869
+ ? process.env.W2M_UPDATE_REPO.trim() : DEFAULT_REPO;
4870
+ const [stableRelease, betaRelease] = await Promise.all([fetchLatestRelease({ repo }), fetchPrereleaseRelease({ repo })]);
4871
+ const currentVersion = resolveCurrentVersion(null, process.env);
4872
+ return JSON.stringify({ ...described, ...resolveChannels({ stable: stableRelease?.ok === true ? stableRelease : null, beta: betaRelease?.ok === true ? betaRelease : null, currentVersion }), sources: { stable: stableRelease, beta: betaRelease } }, null, 2);
4873
+ }
4874
+
4875
+ /**
4876
+ * v1.1.18: install a specific channel. The beta channel exists only as a GitHub prerelease, so it is verified here
4877
+ * rather than trusted: the tarball sha256 must match the SHA256SUMS shipped with the same release, and
4878
+ * downloadVerified removes its temp file on a mismatch, leaving the installed profile untouched.
4879
+ */
4880
+ if (action === 'apply') {
4881
+ const channel = typeof args?.channel === 'string' ? args.channel : 'stable';
4882
+ if (channel !== 'stable' && channel !== 'beta' && channel !== 'latest') {
4883
+ throw new W2MError('W2M_CHANNEL', 'W2M_CHANNEL: channel must be stable, beta or latest', { hint: 'call status with channels=true to see what each channel offers' });
4884
+ }
4885
+ const repo = (typeof process.env.W2M_UPDATE_REPO === 'string' && process.env.W2M_UPDATE_REPO.trim() !== '')
4886
+ ? process.env.W2M_UPDATE_REPO.trim() : DEFAULT_REPO;
4887
+ const [stableRelease, betaRelease] = await Promise.all([fetchLatestRelease({ repo }), fetchPrereleaseRelease({ repo })]);
4888
+ const currentVersion = resolveCurrentVersion(null, process.env);
4889
+ const resolved = resolveChannels({ stable: stableRelease?.ok === true ? stableRelease : null, beta: betaRelease?.ok === true ? betaRelease : null, currentVersion });
4890
+ const wanted = channel === 'latest' ? resolved.latest.channel : channel;
4891
+ const release = wanted === 'beta' ? betaRelease : stableRelease;
4892
+ if (release?.ok !== true) {
4893
+ throw new W2MError('W2M_NO_CHANNEL', 'W2M_NO_CHANNEL: nothing is available on the ' + channel + ' channel', { hint: release?.code ? release.code + ': ' + release.error : undefined });
4894
+ }
4895
+ const sumsAsset = release.assets?.sums;
4896
+ const tarballAsset = release.assets?.tarball;
4897
+ if (!sumsAsset || !tarballAsset) throw new W2MError('W2M_NO_ASSETS', 'W2M_NO_ASSETS: the release carries no tarball and SHA256SUMS');
4898
+ const sumsResponse = await fetch(sumsAsset.url, { headers: sumsAsset.headers ?? {} });
4899
+ if (sumsResponse?.ok !== true) throw new W2MError('W2M_SUMS', 'W2M_SUMS: could not download SHA256SUMS (HTTP ' + sumsResponse?.status + ')');
4900
+ const sha256 = requireChecksum(await sumsResponse.text(), tarballAsset.name);
4901
+ const profileDir = args?.profile_dir ?? findProfileDir({});
4902
+ const staged = await downloadVerified({ url: tarballAsset.url, sha256, destPath: (args?.staging_dir ?? profileDir) + '/.w2m-update/' + tarballAsset.name });
4903
+ if (staged?.ok !== true) {
4904
+ throw new W2MError('W2M_CHECKSUM', 'W2M_CHECKSUM: refusing to install; ' + (staged?.code ?? 'download-failed') + ' ' + (staged?.error ?? ''), { hint: 'a checksum mismatch is not retried: the asset and its SHA256SUMS disagree' });
4905
+ }
4906
+ const installed = await installUpdate({ tarballPath: staged.path, profileDir, expectedSha256: sha256, dryRun: args?.dry_run === true });
4907
+ return JSON.stringify({ ok: installed?.ok === true, action, channel, version: resolved.channels[wanted]?.available ?? null, sha256, install: installed }, null, 2);
4862
4908
  }
4863
4909
  const result = await updater.check('manual');
4864
4910
  // `ok:false` inside `result` means "the check could not complete", never "the tool failed".
@@ -5228,7 +5274,7 @@ export async function apply(ctx, config = {}) {
5228
5274
  * at Object.apply (src/plugin/tools.mjs:5025)
5229
5275
  *
5230
5276
  * **The throw happens while the tool is being defined, so the whole fiber fails to mount** -- the client
5231
- * reports the package as `异常`, none of the eleven tools appear, and **the message is only visible if
5277
+ * reports the package as `异常`, none of the twelve tools appear, and **the message is only visible if
5232
5278
  * something gets far enough to print it.** This parameter has had that shape since v0.41.0, so the
5233
5279
  * failure dates from then.
5234
5280
  *
@@ -6251,6 +6297,50 @@ export async function apply(ctx, config = {}) {
6251
6297
  },
6252
6298
  }));
6253
6299
 
6300
+ /**
6301
+ * v1.1.17: fleet chat, one session at a time. The text never travels over the notification stream -- this tool reads
6302
+ * it through the endpoint that checks who is asking, and the author of a sent message is the token holder.
6303
+ */
6304
+ ctx.tools.register(defineTool({
6305
+ name: 'w2m_chat',
6306
+ description:
6307
+ 'Fleet chat for one session: send plain text to the machines in this conversation, or read what was said. ' +
6308
+ 'A refused write comes back with its named reason (quota, size, or non-text) rather than as a bare failure.',
6309
+ parameters: {
6310
+ action: { type: 'string', enum: ['send', 'list'], description: 'send posts text; list reads the flow (default).', default: 'list' },
6311
+ session_id: { type: 'string', description: 'Which conversation. Chat is scoped to a session, not a machine.' },
6312
+ text: { type: 'string', description: 'The message for send. Plain text, UTF-8, at most 4 KiB.' },
6313
+ since_ms: { type: 'number', description: 'For list: only messages newer than this relay timestamp.' },
6314
+ limit: { type: 'number', description: 'For list: how many of the newest messages (default 50).' },
6315
+ },
6316
+ output: jsonOutput,
6317
+ presentCall: (args) => ({ card: 'generic', title: 'w2m chat ' + (typeof args?.action === 'string' ? args.action : 'list'), kind: args?.action === 'send' ? 'write' : 'read', rawInput: {} }),
6318
+ async execute(args, exec) {
6319
+ throwIfAborted(exec?.signal);
6320
+ const action = typeof args?.action === 'string' ? args.action : 'list';
6321
+ const sessionId = typeof args?.session_id === 'string' ? args.session_id.trim() : '';
6322
+ if (sessionId === '') throw new W2MError('W2M_CHAT_SESSION', 'W2M_CHAT_SESSION: session_id is required', { hint: 'chat is scoped to a conversation' });
6323
+ const device = await readDevice(cfg);
6324
+ const rabbitUrl = cfg.rabbitUrl ?? null;
6325
+ if (rabbitUrl === null) throw new W2MError('W2M_NO_RELAY', 'W2M_NO_RELAY: no relay is configured, so chat cannot be reached', { hint: 'set rabbitUrl in the profile patch' });
6326
+ const token = requireToken(device);
6327
+ if (action === 'send') {
6328
+ const posted = await request({ rabbitUrl, token, signal: exec?.signal, method: 'POST', pathname: '/v1/fleet/messages', body: { session_id: sessionId, text: typeof args?.text === 'string' ? args.text : '' } });
6329
+ if (posted?.ok !== true) {
6330
+ const code = posted?.json?.error?.code ?? posted?.error?.code ?? 'W2M_CHAT_SEND';
6331
+ throw new W2MError('W2M_CHAT_SEND', code + ': the message was refused', { reason_code: code, hint: 'quota, size and non-text each need a different answer' });
6332
+ }
6333
+ return JSON.stringify({ ok: true, action, message: posted.json?.message ?? null });
6334
+ }
6335
+ const query = { session_id: sessionId };
6336
+ if (Number.isFinite(args?.since_ms)) query.since_ms = String(args.since_ms);
6337
+ if (Number.isInteger(args?.limit)) query.limit = String(args.limit);
6338
+ const listed = await request({ rabbitUrl, token, signal: exec?.signal, method: 'GET', pathname: '/v1/fleet/messages', query });
6339
+ if (listed?.ok !== true) throw new W2MError('W2M_CHAT_LIST', 'W2M_CHAT_LIST: the relay refused the read', { hint: 'check the relay and this machine token' });
6340
+ return JSON.stringify({ ok: true, action, session_id: sessionId, messages: listed.json?.messages ?? [], count: listed.json?.count ?? 0, usage: listed.json?.usage ?? null });
6341
+ },
6342
+ }));
6343
+
6254
6344
  /**
6255
6345
  * v0.37.0: the eleventh tool -- a question put to a council, and the outcome read back with its dissent.
6256
6346
  *