@twinsearth/w2m-dsh-plugin 0.6.3 → 0.8.2
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 +1176 -0
- package/HANDOFF.md +173 -0
- package/README.md +9 -2
- package/bin/w2m-localside.mjs +325 -308
- package/bin/w2m-rabbit.mjs +6 -0
- package/bin/w2m-stun.mjs +0 -0
- package/deploy/mac/mac-self-update.mjs +257 -0
- package/deploy/server/README.md +109 -0
- package/deploy/server/deploy-w2m.sh +161 -0
- package/deploy/server/w2m-localside.service +31 -0
- package/deploy/server/w2m-rabbit.service +26 -0
- package/deploy/server/w2m-stun.service +26 -0
- package/docs/ACCOUNT-FLEET-DESIGN.md +369 -0
- package/package.json +34 -22
- package/src/agent/agent.mjs +24 -2
- package/src/chatops/account.mjs +401 -0
- package/src/chatops/actions.mjs +227 -0
- package/src/chatops/approval-broker.mjs +320 -0
- package/src/chatops/auth.mjs +376 -0
- package/src/chatops/bridge.mjs +344 -0
- package/src/chatops/channel.mjs +192 -0
- package/src/chatops/commands.mjs +409 -0
- package/src/chatops/config-route.mjs +332 -0
- package/src/chatops/device-signing.mjs +218 -0
- package/src/chatops/durability.mjs +202 -0
- package/src/chatops/file-safety.mjs +282 -0
- package/src/chatops/fleet-join.mjs +164 -0
- package/src/chatops/fleet-scope.mjs +176 -0
- package/src/chatops/fleet-wiring.mjs +258 -0
- package/src/chatops/fleet.mjs +272 -0
- package/src/chatops/handoff.mjs +258 -0
- package/src/chatops/ilink-fleet-bridge.mjs +197 -0
- package/src/chatops/ilink-protocol.mjs +165 -0
- package/src/chatops/ilink-transport.mjs +168 -0
- package/src/chatops/ilink.mjs +423 -0
- package/src/chatops/index.mjs +440 -0
- package/src/chatops/outbound-queue.mjs +282 -0
- package/src/chatops/policy.mjs +356 -0
- package/src/chatops/run-command.mjs +308 -0
- package/src/plugin/tools.mjs +1 -1
- package/src/relay/server.mjs +96 -0
- package/src/relay/state.mjs +2279 -2011
package/HANDOFF.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Handoff — where this stands, and what to do next
|
|
2
|
+
|
|
3
|
+
Written at the point of switching development from the Windows machine to the Mac. Everything here
|
|
4
|
+
is **committed and pushed**; nothing depends on the machine that wrote it.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## The Mac is reachable by exactly one path
|
|
9
|
+
|
|
10
|
+
Discovered the hard way — four blind attempts failed first, each diagnosed by encoding state into
|
|
11
|
+
an **exit code**, because `unverifiable` tasks do not return their stdout and that is the only
|
|
12
|
+
readable channel from that machine:
|
|
13
|
+
|
|
14
|
+
| target | from the Mac |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `raw.githubusercontent.com` | ❌ |
|
|
17
|
+
| `github.com` release tarball | ❌ |
|
|
18
|
+
| `http://202.182.123.154:8899/pub` | ❌ |
|
|
19
|
+
| `http://202.182.123.154:8787` | ❌ |
|
|
20
|
+
| **`http://geefish.cn/pub/...`** | ✅ **the only one** |
|
|
21
|
+
|
|
22
|
+
Port 80, behind the nginx that already fronts the relay. A `location ^~ /pub/` was added to that
|
|
23
|
+
same server block, so releases travel the one path already proven to work. The nginx change is
|
|
24
|
+
tested with `nginx -t` **before** loading and restores its backup on any failure, because a broken
|
|
25
|
+
config there takes the whole fleet down, not just a download.
|
|
26
|
+
|
|
27
|
+
## Updating the Mac
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
curl -fsSL http://geefish.cn/pub/mac-self-update.mjs -o ~/w2m-self-update.mjs
|
|
31
|
+
curl -fsSL http://geefish.cn/pub/w2m-0.8.2.tgz -o ~/w2m-0.8.2.tgz
|
|
32
|
+
node ~/w2m-self-update.mjs --tarball ~/w2m-0.8.2.tgz \
|
|
33
|
+
--sha256 8ff593845612a4699c11c03ab22bbd81afa02f93b865d2e444badc8b62cfb60a
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`--sha256` is not optional in spirit: the supervisor **refuses** a tarball whose checksum differs,
|
|
37
|
+
before it touches anything.
|
|
38
|
+
|
|
39
|
+
The supervisor is **detached** on purpose — it has to survive the DSH it restarts. Seven steps,
|
|
40
|
+
ordered so the previous state is restorable **without the network**: verify the checksum, back up in
|
|
41
|
+
place, check the staged tree for its entry point, swap, restart, self-check, and **roll back
|
|
42
|
+
automatically** on failure. Every step is appended to `~/w2m-self-update-report.json` as it happens,
|
|
43
|
+
so a process killed mid-way still says where it stopped.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Current version, everywhere
|
|
48
|
+
|
|
49
|
+
| machine | version |
|
|
50
|
+
|---|---|
|
|
51
|
+
| Windows | 0.8.1 |
|
|
52
|
+
| **Linux relay host** | **0.8.2** |
|
|
53
|
+
| **Mac** | **0.8.0 — needs the update above** |
|
|
54
|
+
| npm | 0.8.0 |
|
|
55
|
+
| GitHub | v0.8.0 released; 0.8.1 and 0.8.2 committed, **no release tag yet** |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## What was fixed, and the bug that is now closed
|
|
60
|
+
|
|
61
|
+
**The reported bug:** a new PC signed into the same DeepSeek account did not join the same fleet.
|
|
62
|
+
|
|
63
|
+
**The cause, traced rather than guessed** — four gaps stacked:
|
|
64
|
+
|
|
65
|
+
1. the plugin computed its SN, derived `fleet` from the account code, **logged it, and never used
|
|
66
|
+
it for anything**;
|
|
67
|
+
2. nothing called `/v1/pair`;
|
|
68
|
+
3. `autoStartAgent` defaults to **false**;
|
|
69
|
+
4. and even with an agent, membership needed a **human-typed one-time pairing code**.
|
|
70
|
+
|
|
71
|
+
So *"same account joins the same fleet automatically"* was a requirement with **no code path**.
|
|
72
|
+
|
|
73
|
+
**Why the obvious fix would have been wrong:** using `accountCode` as the credential. It is
|
|
74
|
+
`sha256(userId)` truncated to **32 bits** — documented as fine for *grouping*, brute-forceable as a
|
|
75
|
+
*secret*. The bug was a **conflation: a grouping identifier doing duty as a secret.**
|
|
76
|
+
|
|
77
|
+
**The fix** derives the secret from what same-account machines share and outsiders cannot obtain —
|
|
78
|
+
the ability to read `userId`:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
fleetKey = HMAC-SHA256(userId, "w2m-fleet-v1") 256 bits
|
|
82
|
+
fleetId = SHA-256(fleetKey) what the relay groups by
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Presenting the key is the proof.** The relay *computes* a fleet id from the key rather than
|
|
86
|
+
believing a claimed one, so a device can only land in the fleet whose key it holds. No
|
|
87
|
+
trust-on-first-use, no operator configuration, no account lookup, nothing to forge — and a device
|
|
88
|
+
without the key forms a different, empty fleet rather than joining someone else's.
|
|
89
|
+
|
|
90
|
+
This also removes the deployment problem recorded in v0.7.9: **`--fleet` no longer needs an operator
|
|
91
|
+
to compute and hand-carry a 32-bit code**, because the relay needs to know *nothing* about any
|
|
92
|
+
account.
|
|
93
|
+
|
|
94
|
+
**Verified end to end:** an agent given **only** `--fleet-key`, against a relay with **no fleet of
|
|
95
|
+
its own**, pairs and lands in the account-derived fleet (`d5bb1b9ddf3faad4`, `source=fleet_key`).
|
|
96
|
+
|
|
97
|
+
### The bug that would have made it work exactly once
|
|
98
|
+
|
|
99
|
+
`resolve()` returned early when an SN was already stored and never read the account at all. Right
|
|
100
|
+
for the SN — it must not be regenerated, or every restart looks like a new device — and **wrong for
|
|
101
|
+
the key**: a machine has a stored SN from its *second* start onward, so it would have derived a key
|
|
102
|
+
once and never again.
|
|
103
|
+
|
|
104
|
+
**The auto-join would have worked on a machine's first boot and silently stopped afterwards** — the
|
|
105
|
+
hardest kind of bug to see, because it works when you test it. Found by asking what the stored-SN
|
|
106
|
+
path actually does, not by reading the happy path.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## What is wired, and the one thing that is not
|
|
111
|
+
|
|
112
|
+
| piece | state |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `account.mjs` derives the key on **every** resolve, both paths | ✅ |
|
|
115
|
+
| `index.mjs` exposes it as `fleetKey` (the key, never the account id) | ✅ |
|
|
116
|
+
| `agent.mjs` `pair(code, userId, fleetKey)` | ✅ |
|
|
117
|
+
| `w2m-localside --fleet-key` | ✅ |
|
|
118
|
+
| relay derives the fleet from the key | ✅ |
|
|
119
|
+
| **the plugin spawns the agent and passes its key** | ❌ **not wired** |
|
|
120
|
+
|
|
121
|
+
**So with the default configuration a new PC still will not auto-join.** The mechanism is complete
|
|
122
|
+
and verified; the plugin does not yet start the agent or hand it the key. Until that lands, either:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
w2m-localside --rabbit http://geefish.cn --fleet-key <KEY> --project <dir> --name <name> --state <dir>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
or set `autoStartAgent` and wire the key through.
|
|
129
|
+
|
|
130
|
+
**This is the next task.** It is also the last one: after it, installing the plugin and signing into
|
|
131
|
+
the same account is the whole of the user-facing procedure.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Traps this codebase has already fallen into — worth knowing before editing
|
|
136
|
+
|
|
137
|
+
Each of these cost a debugging round, and most were **silent**:
|
|
138
|
+
|
|
139
|
+
* **A presence check reads `undefined` as "provided".** `createILinkChannel` passed
|
|
140
|
+
`{ fetch: undefined }` and threw on mount (v0.7.0). The same shape appears in the pairing body:
|
|
141
|
+
an absent code must be an **absent key**, not a `null` one.
|
|
142
|
+
* **An anchor in a `patch` file is an assertion, not a rename.** A mismatched `name:` makes the
|
|
143
|
+
Loader skip the whole patch *silently*.
|
|
144
|
+
* **`statSync` on an injected path defeats the injection.** The secrets guard called it
|
|
145
|
+
unconditionally, so an injected reader was never reached and the scan reported **zero findings
|
|
146
|
+
across zero files** — a guard that is silently off.
|
|
147
|
+
* **`createHmac('sha256', Buffer.alloc(0))` is not `createHash('sha256')`.** A signature over the
|
|
148
|
+
wrong bytes verifies nowhere and says nothing about why.
|
|
149
|
+
* **A test that hard-codes a rule name measures the name, not the behaviour** — renaming broke it.
|
|
150
|
+
* **`login` confirms as `confirmed`, not `scaned`.** Waiting on `scaned` misses the token entirely.
|
|
151
|
+
* **The relay needs two credentials.** The operator token dispatches; the **device token** reads the
|
|
152
|
+
verdict. Supplying one gives a *confident wrong answer* — `判定:unknown` — rather than an error.
|
|
153
|
+
|
|
154
|
+
## Reading the Mac without stdout
|
|
155
|
+
|
|
156
|
+
Exit codes are the only channel. Encode state into them and probe:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
// on the Mac, via node -e
|
|
160
|
+
process.exit(tgzSize < 700000 ? 10 : sup.done ? (sup.ok ? 0 : 13) : 12);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Ground rules that have held
|
|
164
|
+
|
|
165
|
+
* `deploy/server/` holds the units **as the live host runs them**; the deploy script's step order
|
|
166
|
+
*is* the safety argument.
|
|
167
|
+
* `/var/lib/w2m/rabbit/` — `operator-token.txt` and the ledger — **must never be committed**.
|
|
168
|
+
`.gitignore` refuses the paths; `scripts/scan-for-secrets.mjs` reads the **content** of every
|
|
169
|
+
tracked file, because a `.gitignore` refuses a path while the ways a credential escapes are
|
|
170
|
+
pasting one into a source file, a fixture, or an example.
|
|
171
|
+
* **A deploy cannot be an outage.** The relay runs **unscoped**, so its four devices keep working.
|
|
172
|
+
Scoping it requires an account code and a re-pair of every device.
|
|
173
|
+
* **Never publish to npm without the token**, and rotate it: it has been pasted into a chat log.
|
package/README.md
CHANGED
|
@@ -250,7 +250,7 @@ dsh plugin --profile <profile-name> add @twinsearth/w2m-dsh-plugin
|
|
|
250
250
|
|
|
251
251
|
```bash
|
|
252
252
|
dsh plugin --profile <profile-name> add \
|
|
253
|
-
https://github.com/TwinsEarth/dsh-windows2macos/releases/download/v0.
|
|
253
|
+
https://github.com/TwinsEarth/dsh-windows2macos/releases/download/v0.6.3/twinsearth-w2m-dsh-plugin-0.6.3.tgz
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
**From the repository** — the form a marketplace's one-click installer uses (it clones
|
|
@@ -624,6 +624,13 @@ move with it: `claims` is 19 tests here and **22 when `dist/` holds a packed tar
|
|
|
624
624
|
its two tarball-vs-`package.json` checks run instead of skipping — so a checkout with `dist/` measures
|
|
625
625
|
**1,142 tests / 1,134 pass / 4 skip**, and that is the number to expect from a release tree.
|
|
626
626
|
|
|
627
|
+
> **These counts are the v0.5.9 measurement and have not been re-taken since.** They are kept, dated
|
|
628
|
+
> and attributed rather than refreshed, because a figure presented as current without having been
|
|
629
|
+
> measured is the failure this project's own rules exist to prevent. Suites added after v0.5.9 —
|
|
630
|
+
> `p2p-crypto`, and from v0.7.0 `chatops-approval` and `chatops-policy` — are therefore **not** in
|
|
631
|
+
> any total on this page. Treat every count below as a v0.5.9 measurement until a release re-runs
|
|
632
|
+
> the suite on its own host and says so.
|
|
633
|
+
|
|
627
634
|
**Which script runs which suite matters, because they are not the same set:**
|
|
628
635
|
|
|
629
636
|
| Command | Suites | Result on this host (v0.5.9) |
|
|
@@ -810,7 +817,7 @@ node bin/w2m-localside.mjs --rabbit http://<中继地址>:8787 --pair PAIR-XXXXX
|
|
|
810
817
|
|
|
811
818
|
# 3) 装 DSH 插件
|
|
812
819
|
dsh plugin --profile <profile-name> add \
|
|
813
|
-
https://github.com/TwinsEarth/dsh-windows2macos/releases/download/v0.
|
|
820
|
+
https://github.com/TwinsEarth/dsh-windows2macos/releases/download/v0.6.3/twinsearth-w2m-dsh-plugin-0.6.3.tgz
|
|
814
821
|
```
|
|
815
822
|
|
|
816
823
|
然后在 profile 的 `cordis.patch.yml` 里给插件 `rabbitUrl`(留空即用共享服务器)与 `operatorToken`,重启 DSH,即可直接用自然语言指挥:
|