@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 +115 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/src/plugin/tools.mjs +112 -5
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.
|
|
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.
|
|
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": [
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -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.
|
|
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
|
|
4661
|
-
+ '
|
|
4662
|
-
+
|
|
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 ' +
|