@twinsearth/w2m-dsh-plugin 1.1.15 → 1.1.17
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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +112 -0
- package/README.md +252 -870
- package/codex/mcp-servers.toml +1 -1
- package/docs/PLATFORMS.md +22 -0
- package/package.json +8 -4
- package/plugin.json +1 -1
- package/src/plugin/tools.mjs +119 -5
- package/src/plugin/tools.mjs.bak-c7 +7029 -0
- package/src/relay/fleet-messages.mjs +145 -0
- package/src/relay/message-shards.mjs +124 -0
- package/src/relay/server.mjs +71 -1
package/codex/mcp-servers.toml
CHANGED
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.
|
|
3
|
+
"version": "1.1.17",
|
|
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",
|
|
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",
|
|
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,11 @@
|
|
|
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"
|
|
179
183
|
},
|
|
180
184
|
"keywords": [
|
|
181
185
|
"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.
|
|
4
|
+
"version": "1.1.17",
|
|
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": [
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
* DSH: Windows2MacO - the DSH Cordis plugin.
|
|
3
3
|
*
|
|
4
4
|
* # What this exposes
|
|
@@ -214,7 +214,7 @@ const MAX_STAGES = 16;
|
|
|
214
214
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
215
215
|
* overrides it at runtime for tests and packagers.
|
|
216
216
|
*/
|
|
217
|
-
const PLUGIN_VERSION = '1.1.
|
|
217
|
+
const PLUGIN_VERSION = '1.1.17';
|
|
218
218
|
|
|
219
219
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
220
220
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
@@ -5228,7 +5228,7 @@ export async function apply(ctx, config = {}) {
|
|
|
5228
5228
|
* at Object.apply (src/plugin/tools.mjs:5025)
|
|
5229
5229
|
*
|
|
5230
5230
|
* **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
|
|
5231
|
+
* reports the package as `异常`, none of the twelve tools appear, and **the message is only visible if
|
|
5232
5232
|
* something gets far enough to print it.** This parameter has had that shape since v0.41.0, so the
|
|
5233
5233
|
* failure dates from then.
|
|
5234
5234
|
*
|
|
@@ -6138,6 +6138,39 @@ export async function apply(ctx, config = {}) {
|
|
|
6138
6138
|
}
|
|
6139
6139
|
const actor = { id: typeof args?.actor_id === 'string' && args.actor_id.trim() !== '' ? args.actor_id.trim() : cfg.machineName, level: args.actor_level };
|
|
6140
6140
|
const outcome = action === 'approve' ? 'approved' : 'rejected';
|
|
6141
|
+
/**
|
|
6142
|
+
* v1.1.16: a fleet-admission request is settled at the RELAY, because that is where the request lives and where
|
|
6143
|
+
* the quorum rules are enforced. The local index would accept the write and change nothing that matters.
|
|
6144
|
+
*
|
|
6145
|
+
* The actor id is the person answering; the relay records it on the decision. A refusal is reported as such
|
|
6146
|
+
* (`ALREADY_DECIDED` included) rather than thrown: the caller is looking at a list that may be a second old.
|
|
6147
|
+
*/
|
|
6148
|
+
if (/^adm-/.test(approvalId)) {
|
|
6149
|
+
const decideDevice = await readDevice(cfg);
|
|
6150
|
+
const decideRelay = cfg.rabbitUrl ?? null;
|
|
6151
|
+
const settled = decideRelay === null || decideDevice.device_token === null
|
|
6152
|
+
? { ok: false, json: { code: 'W2M_NO_RELAY' } }
|
|
6153
|
+
: await request({
|
|
6154
|
+
rabbitUrl: decideRelay,
|
|
6155
|
+
token: requireToken(decideDevice),
|
|
6156
|
+
method: 'POST',
|
|
6157
|
+
pathname: '/v1/fleet/admission/' + encodeURIComponent(approvalId) + '/decide',
|
|
6158
|
+
body: { decision: action === 'approve' ? 'approve' : 'reject', decided_by: actor.id, auto: false },
|
|
6159
|
+
});
|
|
6160
|
+
return JSON.stringify({
|
|
6161
|
+
ok: settled?.ok === true,
|
|
6162
|
+
action,
|
|
6163
|
+
approval_id: approvalId,
|
|
6164
|
+
kind: 'fleet_admission',
|
|
6165
|
+
settled: settled?.ok === true ? outcome : null,
|
|
6166
|
+
reason_code: settled?.ok === true ? null : (settled?.json?.error?.code ?? settled?.json?.code ?? settled?.error?.code ?? null),
|
|
6167
|
+
detail: settled?.ok === true ? null : (settled?.json?.error?.message ?? null),
|
|
6168
|
+
acted_by: actor,
|
|
6169
|
+
note: settled?.ok === true
|
|
6170
|
+
? 'the machine is admitted; it will appear in w2m_devices once its agent reconnects'
|
|
6171
|
+
: 'the decision was refused; the request is unchanged (already decided requests are normal when the ten-second window has passed)',
|
|
6172
|
+
});
|
|
6173
|
+
}
|
|
6141
6174
|
const result = queue.decide(approvalId, actor, outcome);
|
|
6142
6175
|
return JSON.stringify({
|
|
6143
6176
|
ok: result.ok === true,
|
|
@@ -6161,6 +6194,43 @@ export async function apply(ctx, config = {}) {
|
|
|
6161
6194
|
};
|
|
6162
6195
|
// An actor with no level still gets the list: knowing what is waiting does not require the authority to
|
|
6163
6196
|
// answer it, and a queue only its supervisors can see is a queue that stays full.
|
|
6197
|
+
/**
|
|
6198
|
+
* v1.1.16: a machine waiting to join the fleet is a request waiting for a human, which is what this tool is.
|
|
6199
|
+
*
|
|
6200
|
+
* It is NOT a permission escalation, so it must not be shoved through `approvalRequestFor` -- that shape requires
|
|
6201
|
+
* a needs-approval verdict and a capability, and faking one would record a lie in the audit trail. The list is
|
|
6202
|
+
* shared; the store underneath is not.
|
|
6203
|
+
*
|
|
6204
|
+
* The countdown uses the RELAY's clock: `requested_at_ms` was stamped there, and three machines in this fleet
|
|
6205
|
+
* disagree on the time by tens of seconds. A local-clock countdown would fire the automatic approval early on a
|
|
6206
|
+
* fast machine and late on a slow one. The relay still owns the decision (it refuses to auto-approve with no
|
|
6207
|
+
* member online); this is display only.
|
|
6208
|
+
*/
|
|
6209
|
+
let admissionRows = [];
|
|
6210
|
+
try {
|
|
6211
|
+
const admissionDevice = await readDevice(cfg);
|
|
6212
|
+
const admissionRelay = cfg.rabbitUrl ?? null;
|
|
6213
|
+
// A relay URL and a device token are what the rest of this file passes on every authenticated call; omitting
|
|
6214
|
+
// them made this request throw into the catch below, which is why the merged rows were silently missing.
|
|
6215
|
+
const admissionPending = admissionRelay === null || admissionDevice.device_token === null
|
|
6216
|
+
? { ok: false }
|
|
6217
|
+
: await request({ rabbitUrl: admissionRelay, token: requireToken(admissionDevice), method: 'GET', pathname: '/v1/fleet/admission/pending' });
|
|
6218
|
+
if (admissionPending?.ok === true && Array.isArray(admissionPending.json?.pending)) {
|
|
6219
|
+
const relayNow = Date.parse(String(admissionPending.json.rabbit_time ?? '')) || Date.now();
|
|
6220
|
+
admissionRows = admissionPending.json.pending.map((row) => ({
|
|
6221
|
+
approval_id: row.request_id,
|
|
6222
|
+
kind: 'fleet_admission',
|
|
6223
|
+
machine_id: row.machine_id ?? null,
|
|
6224
|
+
machine_name: row.machine_name ?? null,
|
|
6225
|
+
requested_at_ms: row.requested_at_ms ?? null,
|
|
6226
|
+
countdown_ms: Number.isFinite(row.requested_at_ms) ? Math.max(0, 10_000 - (relayNow - row.requested_at_ms)) : null,
|
|
6227
|
+
may_act: true,
|
|
6228
|
+
note: 'approving admits this machine to the fleet; the ten-second window is enforced by the relay and needs a member online',
|
|
6229
|
+
}));
|
|
6230
|
+
}
|
|
6231
|
+
} catch {
|
|
6232
|
+
// The approval list must still work when the relay is unreachable: the permission queue is local.
|
|
6233
|
+
}
|
|
6164
6234
|
const rows = queue.listFor(
|
|
6165
6235
|
actor.level === null ? { id: actor.id, level: 0 } : actor,
|
|
6166
6236
|
{ includeSettled: args?.include_settled === true },
|
|
@@ -6172,8 +6242,8 @@ export async function apply(ctx, config = {}) {
|
|
|
6172
6242
|
as: actor,
|
|
6173
6243
|
level_known: actor.level !== null,
|
|
6174
6244
|
summary,
|
|
6175
|
-
count: rows.length,
|
|
6176
|
-
rows,
|
|
6245
|
+
count: rows.length + admissionRows.length,
|
|
6246
|
+
rows: rows.concat(admissionRows),
|
|
6177
6247
|
note: actor.level === null
|
|
6178
6248
|
? 'no actor_level was supplied, so may_act is reported as if the level were 0: the queue is visible, but whether you may answer any of it is not known'
|
|
6179
6249
|
: 'may_act reflects the actor_level supplied',
|
|
@@ -6181,6 +6251,50 @@ export async function apply(ctx, config = {}) {
|
|
|
6181
6251
|
},
|
|
6182
6252
|
}));
|
|
6183
6253
|
|
|
6254
|
+
/**
|
|
6255
|
+
* v1.1.17: fleet chat, one session at a time. The text never travels over the notification stream -- this tool reads
|
|
6256
|
+
* it through the endpoint that checks who is asking, and the author of a sent message is the token holder.
|
|
6257
|
+
*/
|
|
6258
|
+
ctx.tools.register(defineTool({
|
|
6259
|
+
name: 'w2m_chat',
|
|
6260
|
+
description:
|
|
6261
|
+
'Fleet chat for one session: send plain text to the machines in this conversation, or read what was said. ' +
|
|
6262
|
+
'A refused write comes back with its named reason (quota, size, or non-text) rather than as a bare failure.',
|
|
6263
|
+
parameters: {
|
|
6264
|
+
action: { type: 'string', enum: ['send', 'list'], description: 'send posts text; list reads the flow (default).', default: 'list' },
|
|
6265
|
+
session_id: { type: 'string', description: 'Which conversation. Chat is scoped to a session, not a machine.' },
|
|
6266
|
+
text: { type: 'string', description: 'The message for send. Plain text, UTF-8, at most 4 KiB.' },
|
|
6267
|
+
since_ms: { type: 'number', description: 'For list: only messages newer than this relay timestamp.' },
|
|
6268
|
+
limit: { type: 'number', description: 'For list: how many of the newest messages (default 50).' },
|
|
6269
|
+
},
|
|
6270
|
+
output: jsonOutput,
|
|
6271
|
+
presentCall: (args) => ({ card: 'generic', title: 'w2m chat ' + (typeof args?.action === 'string' ? args.action : 'list'), kind: args?.action === 'send' ? 'write' : 'read', rawInput: {} }),
|
|
6272
|
+
async execute(args, exec) {
|
|
6273
|
+
throwIfAborted(exec?.signal);
|
|
6274
|
+
const action = typeof args?.action === 'string' ? args.action : 'list';
|
|
6275
|
+
const sessionId = typeof args?.session_id === 'string' ? args.session_id.trim() : '';
|
|
6276
|
+
if (sessionId === '') throw new W2MError('W2M_CHAT_SESSION', 'W2M_CHAT_SESSION: session_id is required', { hint: 'chat is scoped to a conversation' });
|
|
6277
|
+
const device = await readDevice(cfg);
|
|
6278
|
+
const rabbitUrl = cfg.rabbitUrl ?? null;
|
|
6279
|
+
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' });
|
|
6280
|
+
const token = requireToken(device);
|
|
6281
|
+
if (action === 'send') {
|
|
6282
|
+
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 : '' } });
|
|
6283
|
+
if (posted?.ok !== true) {
|
|
6284
|
+
const code = posted?.json?.error?.code ?? posted?.error?.code ?? 'W2M_CHAT_SEND';
|
|
6285
|
+
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' });
|
|
6286
|
+
}
|
|
6287
|
+
return JSON.stringify({ ok: true, action, message: posted.json?.message ?? null });
|
|
6288
|
+
}
|
|
6289
|
+
const query = { session_id: sessionId };
|
|
6290
|
+
if (Number.isFinite(args?.since_ms)) query.since_ms = String(args.since_ms);
|
|
6291
|
+
if (Number.isInteger(args?.limit)) query.limit = String(args.limit);
|
|
6292
|
+
const listed = await request({ rabbitUrl, token, signal: exec?.signal, method: 'GET', pathname: '/v1/fleet/messages', query });
|
|
6293
|
+
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' });
|
|
6294
|
+
return JSON.stringify({ ok: true, action, session_id: sessionId, messages: listed.json?.messages ?? [], count: listed.json?.count ?? 0, usage: listed.json?.usage ?? null });
|
|
6295
|
+
},
|
|
6296
|
+
}));
|
|
6297
|
+
|
|
6184
6298
|
/**
|
|
6185
6299
|
* v0.37.0: the eleventh tool -- a question put to a council, and the outcome read back with its dissent.
|
|
6186
6300
|
*
|