@twinsearth/w2m-dsh-plugin 1.0.1 → 1.0.3

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 CHANGED
@@ -2,6 +2,121 @@
2
2
 
3
3
  All notable changes to this project are documented here.
4
4
 
5
+ ## [1.0.3] — 2026-10-17
6
+
7
+ **The warning that predicted this failure now says what to do about it.**
8
+
9
+ ### What was wrong with the old line
10
+
11
+ w2m: no agent is running on this machine, so it is in no fleet and will receive no work.
12
+ Set autoStartAgent: true to join (the fleet key is derived from the signed-in account),
13
+ or run w2m-localside yourself and point it at the relay.
14
+
15
+ **Accurate, and insufficient.** It names **one** of the two things required and does not say *where* to set it.
16
+
17
+ **Measured on a real machine:** plugin installed, activated, **no error anywhere**, and
18
+ `device_token: null` with `autoStartAgent: false`. The operator's own summary was *"it installed but the machine
19
+ is not in the fleet"* -- **exactly what this warning predicts, and nothing in it resolved that.** The line was
20
+ written for the v0.8.5 failure mode and the operator still had to discover both steps.
21
+
22
+ ### The guidance is now a procedure, and it is ordered
23
+
24
+ ```
25
+ This machine is NOT paired yet, so both steps below are required.
26
+ To join, two things are needed:
27
+ 1. a one-time pairing code. `w2m-rabbit` prints it once, in its startup banner:
28
+ [w2m-rabbit] listening on <url> pairing code: PAIR-XXXXXXXX
29
+ It lives in memory only and is never written to the state directory...
30
+ 2. `autoStartAgent: true` plus that code, on the `w2m` row that ALREADY EXISTS...
31
+ - id: w2m
32
+ config:
33
+ autoStartAgent: true
34
+ pairingCode: PAIR-XXXXXXXX
35
+ Do NOT add an `insert:` for it. An insert appends a SECOND entry with the same id, and the
36
+ client then refuses to open -- measured on this project, not theorised.
37
+ ```
38
+
39
+ **It states the current pairing state, because the two cases need different first steps.** An unpaired machine
40
+ must fetch a code; a paired one only has to turn the switch on, and **sending that machine after a code would send
41
+ it somewhere it has no business.** Verifying that branch required reading `device.json` and finding
42
+ `device_token: null`, which is exactly what the rendered warning then said.
43
+
44
+ **Why pairing cannot be automatic, stated where the operator will read it:** the relay's own header says *"the
45
+ pairing code is the only credential that grants admission"*, and **a plugin able to join unattended would be one
46
+ that lets any machine holding the package into the fleet.** So the fix is guidance, not auto-joining.
47
+
48
+ ### A parameter I wrote into the text before checking that it exists
49
+
50
+ The first draft told the operator to use **`--print-pairing-code`**. **There is no such flag.** Measured from
51
+ `w2m-rabbit --help`, the real options are:
52
+
53
+ --pairing-code <c> Use a fixed pairing code instead of a generated one.
54
+ --pair-rate-limit <n> Pairing attempts per IP per minute. Default 5. 0 = off.
55
+
56
+ **And `--pairing-code` does the opposite of what I described**: it *chooses* a known code rather than printing an
57
+ existing one. The text now says that, **because a command in an error message is read as an instruction and is
58
+ followed.**
59
+
60
+ The warning was also **rendered and read** rather than assumed -- a warning nobody has printed is a warning whose
61
+ formatting is guesswork.
62
+
63
+ ## [1.0.2] — 2026-10-16
64
+
65
+ **One missing line made the whole plugin fail to mount, and it had been missing since v0.41.0.**
66
+
67
+ ### The defect
68
+
69
+ JsonSchemaError: unsupported JSON schema:
70
+ parameters.accept_block.additionalProperties must be explicitly true or false at authorError
71
+ at parameterSchemaSpecToJsonSchema (dsh-tools/lib/index.js:803)
72
+ at defineTool (dsh-tools/lib/index.js:848)
73
+ at Object.apply (src/plugin/tools.mjs:5025)
74
+
75
+ `w2m_memory`'s `accept_block` was declared as `type: 'object'` **without `additionalProperties`**, and DSH's
76
+ compiler requires it to be explicit. **The throw happens while the tool is being defined, so the entire fiber fails
77
+ to mount** — the client shows the package as `异常`, **all eleven tools disappear**, and the message is visible
78
+ only if something gets far enough to print it.
79
+
80
+ **That single parameter is the whole failure.** Checked across the entire tool surface: **one `type: 'object'`
81
+ parameter exists in this file, and it was the one.**
82
+
83
+ ### Verified against the real compiler, not the shim
84
+
85
+ `verify-installed.mjs` runs against an offline shim that checks shape only, **which is why it reported
86
+ `PASS: 11 tools registered` for every broken version**. The real `@deepseek-ai/dsh-tools` was extracted from
87
+ `app.asar` — 10,913 entries, preserving the `node_modules` layout so bare specifiers resolve — and the same call
88
+ was made against it both ways:
89
+
90
+ WITHOUT additionalProperties -> THREW JsonSchemaError: ... must be explicitly true or false
91
+ WITH additionalProperties -> ACCEPTED
92
+
93
+ **Same error, same code path as the client reported.** That is the first measurement in this session taken from the
94
+ compiler that actually runs.
95
+
96
+ ### `true`, not `false`
97
+
98
+ `verifyBlock()` and `acceptWithAuthority()` already validate the block's structure and its hash, **so the schema
99
+ is not the guard here**. A peer that adds a field outside the seven would be refused **for a reason that has nothing
100
+ to do with trust**, and refusing a legitimate block is the worse of the two errors.
101
+
102
+ ### Why this took so long to find, stated plainly
103
+
104
+ **The error was invisible until the installer was used.** When the package was installed with `pnpm` and then
105
+ removed, the client failed *earlier*, with `cannot resolve profile bundle`, which names a resolution problem and
106
+ not a schema one. **Installing through the plugin manager took it far enough to reach `defineTool` and print the
107
+ real cause.**
108
+
109
+ **So the user's instinct was right in the way that mattered**: not that the official path behaves differently, but
110
+ that it is the path which gets far enough to report the truth. Four earlier rounds of diagnosis — a fabricated
111
+ incompatibility with the host, a mount-row duplicate, a release-notes defect, a runtime-discovery defect — were all
112
+ real, and **none of them was this**.
113
+
114
+ ### The method that worked
115
+
116
+ I extracted the host's own compiler from `app.asar` and called it directly. **Every previous attempt had tested
117
+ against a shim, a clone, or an inference**, and each of those agreed with the broken declaration. **The only
118
+ measurement that disagreed was the one taken from the code that throws.**
119
+
5
120
  ## [1.0.1] — 2026-10-16
6
121
 
7
122
  **The first stable release. Three defects fixed, two of them mine and one of them published 56 times.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "DSH: Windows2MacOS — drive the same project on online Windows and macOS machines from one DeepSeek account, across networks and regions. P2P hole punching is the default path (relay + shared server as rendezvous, ledger and fallback), with a bundled STUN responder, Tailscale/public-VPS/tunnel shapes, Rabbit relay + per-machine Localside + eight DSH tools (replicate / split), nine aggregate verdicts and signed requests.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
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.1",
4
+ "version": "1.0.3",
5
5
  "description": "Cross-platform fleet execution between Windows and macOS: one DeepSeek Harness account, a Rabbit relay, and signed device identity. Ships four Agent Skills describing fleet dispatch, field-level memory acceptance, session references, and resource scheduling.",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -149,7 +149,7 @@ import { AUTHORITY_CHOICES, resolveConfiguredLevel } from '../agent/authority-ch
149
149
  // rather than reimplemented: the dispatcher and the executor have to derive the *same* keys, and two
150
150
  // implementations of a handshake are two chances to disagree about it.
151
151
  import { DeviceKey } from '../agent/crypto.mjs';
152
- import { loadOrCreateIdentity, saveDeviceToken } from '../agent/identity.mjs';
152
+ import { loadIdentity, loadOrCreateIdentity, saveDeviceToken } from '../agent/identity.mjs';
153
153
  import { probeCapsDetailed, detectPlatform } from '../agent/caps.mjs';
154
154
  import { resolveStateDir } from '../util/cli.mjs';
155
155
  import { parseServer } from '../agent/stun.mjs';
@@ -208,7 +208,7 @@ const MAX_STAGES = 16;
208
208
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
209
209
  * overrides it at runtime for tests and packagers.
210
210
  */
211
- const PLUGIN_VERSION = '1.0.1';
211
+ const PLUGIN_VERSION = '1.0.3';
212
212
 
213
213
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
214
214
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
@@ -4655,11 +4655,91 @@ export async function apply(ctx, config = {}) {
4655
4655
  * A silent no-op that looks like success is the worst failure mode this plugin has had. One
4656
4656
  * line at warn level turns *"why is this machine not in my fleet"* from an investigation into a
4657
4657
  * log search.
4658
+ *
4659
+ * ## v1.0.3: the line was accurate and still left the operator stuck
4660
+ *
4661
+ * "Set `autoStartAgent: true` to join" names **one** of the two things required and does not say where to set
4662
+ * it. **Both omissions were measured on a real machine**: installed, activated, no error anywhere, and
4663
+ * `device_token: null` with `autoStartAgent: false`. The operator's own summary was *"it installed but the
4664
+ * machine is not in the fleet"* -- exactly what this warning predicts, and nothing in it resolves that.
4665
+ *
4666
+ * So the guidance is now **a procedure, and the procedure is ordered**:
4667
+ *
4668
+ * 1. Pairing needs a **one-time code the operator obtains**. It cannot be self-issued, and that is design
4669
+ * rather than oversight -- the relay's own header says *"the pairing code is the only credential that
4670
+ * grants admission"*, and a plugin able to join unattended would be one that lets any machine holding
4671
+ * the package into the fleet.
4672
+ * 2. `autoStartAgent: true` goes on **the `w2m` row that already exists**, matched by `id` -- **not** as an
4673
+ * `insert:`, which appends a second row with the same `id` and stops the client from opening.
4674
+ *
4675
+ * **The warning also states the current pairing state, because the two cases need different first steps.**
4676
+ * An unpaired machine must fetch a code; a paired one only has to turn the switch on, and sending *that*
4677
+ * machine after a code would send it somewhere it has no business.
4678
+ */
4679
+ let paired = false;
4680
+ /**
4681
+ * Read the pairing state, **read-only**, and only when a state directory is configured.
4682
+ *
4683
+ * ## Two defects this line caused before it was right, both measured
4684
+ *
4685
+ * **First it called `loadOrCreateIdentity({ dir: cfg.stateDir ?? undefined })`.**
4686
+ *
4687
+ * * With `stateDir` unset -- the default -- that `undefined` falls back to `~/.dsh/xclient/device.json`,
4688
+ * **the operator's real file**, and `loadOrCreateIdentity` **creates it when absent**. A message about
4689
+ * the machine's state must not be able to change it. Four tests in `test/update-wiring.test.mjs` failed
4690
+ * because the plugin read the real home instead of the profile they supplied.
4691
+ * * Guarding on `stateDir` fixed those, **and broke a different one**: `test/tools.test.mjs:593` sets an
4692
+ * empty `stateDir` and asserts `reason_code: DEVICE_NOT_PAIRED` for an **absent** `device.json`.
4693
+ * `loadOrCreateIdentity` **created that file**, so the very next read found it present-but-unpaired and
4694
+ * reported `DEVICE_TOKEN_ABSENT` instead. Isolated both ways: `tools.mjs` at 1.0.2 gives `115 pass,
4695
+ * 0 fail`; the version with this call gave `114 pass, 1 fail`.
4696
+ *
4697
+ * **`loadIdentity` is the read-only one** -- `ENOENT` returns `exists: false` and creates nothing -- which is
4698
+ * the only behaviour a diagnostic is entitled to.
4699
+ *
4700
+ * With nothing configured there is no location to consult, so the state is reported as unknown rather than
4701
+ * guessed at, which is what `w2m_status` already says for the same reason (`"stateDir is not set, so no agent
4702
+ * state could be located"`).
4658
4703
  */
4704
+ let stateDirConfigured = false;
4705
+ try {
4706
+ stateDirConfigured = typeof cfg.stateDir === 'string' && cfg.stateDir.trim() !== '';
4707
+ if (stateDirConfigured) {
4708
+ const { identity } = loadIdentity({ dir: cfg.stateDir });
4709
+ paired = typeof identity?.device_token === 'string' && identity.device_token !== '';
4710
+ }
4711
+ } catch {
4712
+ // An unreadable identity is `w2m_status`' business; this line is about joining, so it stays quiet.
4713
+ stateDirConfigured = false;
4714
+ }
4659
4715
  ctx.logger?.warn?.(
4660
- 'w2m: no agent is running on this machine, so it is in no fleet and will receive no work. '
4661
- + 'Set autoStartAgent: true to join (the fleet key is derived from the signed-in account), '
4662
- + 'or run w2m-localside yourself and point it at the relay.',
4716
+ 'w2m: no agent is running on this machine, so it is in no fleet, will receive no work, and no dispatch '
4717
+ + 'will ever reach it.\n'
4718
+ + ` ${stateDirConfigured
4719
+ ? (paired
4720
+ ? 'This machine IS paired, so step 1 is already done -- only the switch is missing.'
4721
+ : 'This machine is NOT paired yet, so both steps below are required.')
4722
+ : 'No stateDir is configured, so this machine\'s pairing state could not be read; assume it is not '
4723
+ + 'paired and treat both steps below as required.'}\n`
4724
+ + ' To join, two things are needed:\n'
4725
+ + ' 1. a one-time pairing code. `w2m-rabbit` prints it once, in its startup banner:\n'
4726
+ + ' [w2m-rabbit] listening on <url> pairing code: PAIR-XXXXXXXX\n'
4727
+ + ' It lives in memory only and is never written to the state directory, so it is read from\n'
4728
+ + ' the relay\'s console. There is no flag that prints an existing code; starting the relay\n'
4729
+ + ' with `--pairing-code <c>` chooses a KNOWN code instead of a generated one, which is the\n'
4730
+ + ' supported way to make pairing repeatable.\n'
4731
+ + ' 2. `autoStartAgent: true` plus that code, on the `w2m` row that ALREADY EXISTS in the\n'
4732
+ + ' profile\'s cordis.patch.yml, matched by id:\n'
4733
+ + ' - id: w2m\n'
4734
+ + ' config:\n'
4735
+ + ' autoStartAgent: true\n'
4736
+ + ' pairingCode: PAIR-XXXXXXXX\n'
4737
+ + ' Do NOT add an `insert:` for it. An insert appends a SECOND entry with the same id, and the\n'
4738
+ + ' client then refuses to open -- measured on this project, not theorised.\n'
4739
+ + ' Or pair without touching this profile at all: run\n'
4740
+ + ' w2m-localside --rabbit <url> --pair <CODE>\n'
4741
+ + ' `w2m_status` reports both facts this line is about -- `identity.paired` and\n'
4742
+ + ' `config.autoStartAgent` -- and is the first thing to read when it appears.',
4663
4743
  );
4664
4744
  }
4665
4745
 
@@ -5067,6 +5147,33 @@ export async function apply(ctx, config = {}) {
5067
5147
  },
5068
5148
  accept_block: {
5069
5149
  type: 'object',
5150
+ /**
5151
+ * `true`, and it has to be stated.
5152
+ *
5153
+ * ## The defect this closes, and why it took so long to see
5154
+ *
5155
+ * DSH's `parameterSchemaSpecToJsonSchema` **requires an explicit `additionalProperties` on every
5156
+ * `type: 'object'` parameter** and throws otherwise:
5157
+ *
5158
+ * JsonSchemaError: unsupported JSON schema:
5159
+ * parameters.accept_block.additionalProperties must be explicitly true or false at authorError
5160
+ * at parameterSchemaSpecToJsonSchema (dsh-tools/lib/index.js:803)
5161
+ * at defineTool (dsh-tools/lib/index.js:848)
5162
+ * at Object.apply (src/plugin/tools.mjs:5025)
5163
+ *
5164
+ * **The throw happens while the tool is being defined, so the whole fiber fails to mount** -- the client
5165
+ * reports the package as `异常`, none of the eleven tools appear, and **the message is only visible if
5166
+ * something gets far enough to print it.** This parameter has had that shape since v0.41.0, so the
5167
+ * failure dates from then.
5168
+ *
5169
+ * **`true` rather than `false`:** `verifyBlock()` and `acceptWithAuthority()` already validate the block's
5170
+ * structure and hash, so the schema is not the guard here, and a peer that adds a field outside the seven
5171
+ * would be **refused for a reason that has nothing to do with trust**. Refusing a legitimate block is the
5172
+ * worse error of the two.
5173
+ *
5174
+ * Only one parameter in this file had the shape -- checked across the whole tool surface, not just here.
5175
+ */
5176
+ additionalProperties: true,
5070
5177
  description:
5071
5178
  'v0.41.0. Accept a CAT7 cognitive block received from another machine, per field, DRIVEN BY AN ' +
5072
5179
  'AUTHORITY LEVEL (actor_authority below) rather than accepted whole. A field the level cannot ' +