@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +1176 -0
  2. package/HANDOFF.md +173 -0
  3. package/README.md +9 -2
  4. package/bin/w2m-localside.mjs +325 -308
  5. package/bin/w2m-rabbit.mjs +6 -0
  6. package/bin/w2m-stun.mjs +0 -0
  7. package/deploy/mac/mac-self-update.mjs +257 -0
  8. package/deploy/server/README.md +109 -0
  9. package/deploy/server/deploy-w2m.sh +161 -0
  10. package/deploy/server/w2m-localside.service +31 -0
  11. package/deploy/server/w2m-rabbit.service +26 -0
  12. package/deploy/server/w2m-stun.service +26 -0
  13. package/docs/ACCOUNT-FLEET-DESIGN.md +369 -0
  14. package/package.json +34 -22
  15. package/src/agent/agent.mjs +24 -2
  16. package/src/chatops/account.mjs +401 -0
  17. package/src/chatops/actions.mjs +227 -0
  18. package/src/chatops/approval-broker.mjs +320 -0
  19. package/src/chatops/auth.mjs +376 -0
  20. package/src/chatops/bridge.mjs +344 -0
  21. package/src/chatops/channel.mjs +192 -0
  22. package/src/chatops/commands.mjs +409 -0
  23. package/src/chatops/config-route.mjs +332 -0
  24. package/src/chatops/device-signing.mjs +218 -0
  25. package/src/chatops/durability.mjs +202 -0
  26. package/src/chatops/file-safety.mjs +282 -0
  27. package/src/chatops/fleet-join.mjs +164 -0
  28. package/src/chatops/fleet-scope.mjs +176 -0
  29. package/src/chatops/fleet-wiring.mjs +258 -0
  30. package/src/chatops/fleet.mjs +272 -0
  31. package/src/chatops/handoff.mjs +258 -0
  32. package/src/chatops/ilink-fleet-bridge.mjs +197 -0
  33. package/src/chatops/ilink-protocol.mjs +165 -0
  34. package/src/chatops/ilink-transport.mjs +168 -0
  35. package/src/chatops/ilink.mjs +423 -0
  36. package/src/chatops/index.mjs +440 -0
  37. package/src/chatops/outbound-queue.mjs +282 -0
  38. package/src/chatops/policy.mjs +356 -0
  39. package/src/chatops/run-command.mjs +308 -0
  40. package/src/plugin/tools.mjs +1 -1
  41. package/src/relay/server.mjs +96 -0
  42. 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.5.9/twinsearth-w2m-dsh-plugin-0.5.9.tgz
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.5.9/twinsearth-w2m-dsh-plugin-0.5.9.tgz
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,即可直接用自然语言指挥: