@twinsearth/w2m-dsh-plugin 1.7.3 → 1.7.6

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.
@@ -283,7 +283,7 @@ const MAX_STAGES = 16;
283
283
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
284
284
  * overrides it at runtime for tests and packagers.
285
285
  */
286
- const PLUGIN_VERSION = '1.7.3';
286
+ const PLUGIN_VERSION = '1.7.6';
287
287
 
288
288
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
289
289
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
@@ -7469,6 +7469,25 @@ async function startFailoverStack(ctx, cfg) {
7469
7469
  await stop();
7470
7470
  };
7471
7471
  }
7472
+
7473
+ /**
7474
+ * Read an agent's identity without assuming `identity` is a method.
7475
+ *
7476
+ * v1.7.4: calling the optional method form on agent.identity crashed the DESKTOP host with
7477
+ * dsh: fatal load failure: TypeError: agent.identity is not a function
7478
+ * because the desktop host's agent exposes `identity` as a property while the in-process agent
7479
+ * exposes it as a function. Optional CALL only guards null/undefined; a present non-function still
7480
+ * throws, and inside a promise callback that became an unhandled rejection that took the host down.
7481
+ * Reading the value with a type check covers both shapes.
7482
+ */
7483
+ function agentIdentityOf(agent) {
7484
+ try {
7485
+ if (agent === null || agent === undefined) return null;
7486
+ return typeof agent.identity === 'function' ? agent.identity() : (agent.identity ?? null);
7487
+ } catch {
7488
+ return null;
7489
+ }
7490
+ }
7472
7491
  async function startLocalStack(ctx, cfg) {
7473
7492
 
7474
7493
  /**
@@ -7822,7 +7841,37 @@ async function startLocalStack(ctx, cfg) {
7822
7841
  * the unconditional pairing here were the same defect seen from two sides.
7823
7842
  */
7824
7843
  const existingToken = loaded.identity.device_token;
7825
- const alreadyPaired = typeof existingToken === 'string' && existingToken !== '';
7844
+ let alreadyPaired = typeof existingToken === 'string' && existingToken !== '';
7845
+
7846
+ /**
7847
+ * v1.7.4: a stored token is only "paired" for the relay that issued it.
7848
+ *
7849
+ * Moving the fleet to a new primary relay reuses device.json. The stored token belongs to the OLD
7850
+ * relay, the new one answers `401 UNAUTHORIZED: missing or invalid device_token`, and because the
7851
+ * startup path saw a token it skipped pairing entirely: every machine showed "the Rabbit refused
7852
+ * GET /v1/devices with HTTP 401 ... 无列表" and joined no fleet.
7853
+ *
7854
+ * Probe the configured relay with the stored token. An explicit 401/403 means the token is stale
7855
+ * FOR THIS RELAY, so drop it and pair again through the fleet key. Any other outcome (200, network
7856
+ * error, unexpected status) keeps the token -- a transient outage must not trigger a re-pair.
7857
+ */
7858
+ if (alreadyPaired) {
7859
+ const relayForToken = relay?.url ?? cfg.rabbitUrl;
7860
+ if (typeof relayForToken === 'string' && relayForToken !== '') {
7861
+ try {
7862
+ const probe = await fetch(relayForToken.replace(/\/+$/, '') + '/v1/devices', {
7863
+ headers: { authorization: 'Bearer ' + existingToken, accept: 'application/json' },
7864
+ signal: typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function' ? AbortSignal.timeout(4000) : undefined,
7865
+ });
7866
+ if (probe.status === 401 || probe.status === 403) {
7867
+ alreadyPaired = false;
7868
+ loaded.identity.device_token = null;
7869
+ if (identityOptions.identity !== undefined && identityOptions.identity !== null) identityOptions.identity.device_token = null;
7870
+ ctx.logger?.warn?.('w2m: stored device token was rejected by ' + relayForToken + ' (HTTP ' + probe.status + '); pairing again with the fleet key');
7871
+ }
7872
+ } catch { /* network error: keep the token, do not force a re-pair */ }
7873
+ }
7874
+ }
7826
7875
 
7827
7876
  /**
7828
7877
  * Pair with the key that is in hand, and **persist the token**.
@@ -8026,7 +8075,7 @@ async function startLocalStack(ctx, cfg) {
8026
8075
  // the relay, and the value startFailoverStack needs from this function is the cleanup handle. So
8027
8076
  // start it in the background and record a failure instead of hanging the whole stack.
8028
8077
  agent.start().then(
8029
- () => ctx.logger?.info?.('w2m: agent started as ' + JSON.stringify(agent.identity?.() ?? null)),
8078
+ () => ctx.logger?.info?.('w2m: agent started as ' + JSON.stringify(agentIdentityOf(agent))),
8030
8079
  (error) => ctx.logger?.warn?.('w2m: agent start failed: ' + (error instanceof Error ? error.message : String(error))),
8031
8080
  );
8032
8081