@twinsearth/w2m-dsh-plugin 2.0.6 → 2.0.8

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.
@@ -1,4 +1,5 @@
1
- import { FAILOVER_DEFAULTS, createRelayFailover, relayPool } from './relay-failover.mjs';
1
+ import { FAILOVER_DEFAULTS, createRelayFailover, relayPool } from './relay-failover.mjs';
2
+ import { diagnoseFleetBinding, shouldRejoin } from './fleet-binding.mjs';
2
3
  import { DEFAULT_BOOTSTRAP_URL, DEFAULT_BOOTSTRAP_OPERATOR_KEY_PEM } from './bootstrap.mjs';
3
4
  /**
4
5
  * DSH: Windows2MacO - the DSH Cordis plugin.
@@ -206,7 +207,8 @@ import { buildBoard } from './task-graph.mjs';
206
207
  import { verifyEnvelopeSignature } from '../util/envelope-signature.mjs';
207
208
  import { createAskStore } from './ask-store.mjs';
208
209
  import { deriveTurns } from './turn.mjs';
209
- import { decryptChatMessage, deriveChatKey, encryptChatText, isEncrypted } from '../util/chat-crypto.mjs';
210
+ import { decryptChatMessage, deriveChatKey, encryptChatText, encryptChatTextV2, isEncrypted } from '../util/chat-crypto.mjs';
211
+ import { chatEncV2Supported } from './chat-v2-gate.mjs';
210
212
 
211
213
  /**
212
214
  * v1.2.6 reliability: two small files beside the plugin's state.
@@ -289,7 +291,7 @@ const MAX_STAGES = 16;
289
291
  * `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
290
292
  * overrides it at runtime for tests and packagers.
291
293
  */
292
- const PLUGIN_VERSION = '2.0.6';
294
+ const PLUGIN_VERSION = '2.0.8';
293
295
 
294
296
  /** How long any single HTTP call may take before it is aborted, in milliseconds. */
295
297
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
@@ -4996,6 +4998,13 @@ const result = await request({
4996
4998
  };
4997
4999
  })();
4998
5000
 
5001
+ const fleetBinding = await diagnoseFleetBinding({
5002
+ relayUrl: cfg.rabbitUrl,
5003
+ token: device.device_token,
5004
+ machineId: device.machine_id,
5005
+ fleetKey: cfg.fleetKey,
5006
+ });
5007
+
4999
5008
  return JSON.stringify(
5000
5009
  {
5001
5010
  ok: true,
@@ -5019,7 +5028,8 @@ const result = await request({
5019
5028
  */
5020
5029
  rabbit_source: cfg.rabbitSource,
5021
5030
  rabbit_url: cfg.rabbitUrl,
5022
- p2p: p2pStatus,
5031
+ p2p: p2pStatus,
5032
+ fleet: fleetBinding,
5023
5033
  rtt,
5024
5034
  config: {
5025
5035
  rabbitUrl: cfg.rabbitUrl,
@@ -5029,7 +5039,8 @@ const result = await request({
5029
5039
  projectDir: cfg.projectDir,
5030
5040
  autoStartAgent: cfg.autoStartAgent,
5031
5041
  allowedCommands: cfg.allowedCommands,
5032
- pairingCode_configured: cfg.pairingCode !== null,
5042
+ pairingCode_configured: cfg.pairingCode !== null,
5043
+ fleetKey_configured: cfg.fleetKey !== null,
5033
5044
  p2pMode: cfg.p2pMode,
5034
5045
  // The resolved list, shared server first, exactly as the node will query it.
5035
5046
  stunServers: cfg.stunServers,
@@ -6989,7 +7000,18 @@ hint: 'the local fleet-memory.md is unchanged; action read/show still reads loca
6989
7000
  */
6990
7001
  const chatKey = deriveChatKey(cfg.fleetKey ?? resolvedFleetKey.value ?? null);
6991
7002
  const plainText = typeof args?.text === 'string' ? args.text : '';
6992
- const sealed = chatKey === null ? null : encryptChatText(chatKey, plainText);
7003
+ const idempotencyKey = typeof args?.idempotency_key === 'string' && args.idempotency_key.trim() !== ''
7004
+ ? args.idempotency_key.trim()
7005
+ : 'spool-' + Date.now() + '-' + Math.random().toString(36).slice(2);
7006
+ // A7: v2 only when the relay advertises chat-enc-v2 AND this machine has an id to bind. Otherwise
7007
+ // send v1 (every relay accepts it) rather than an unverifiable v2 envelope.
7008
+ const canV2 = chatKey !== null && typeof cfg.machineId === 'string' && cfg.machineId !== ''
7009
+ && await chatEncV2Supported({ rabbitUrl: cfg.rabbitUrl ?? null });
7010
+ const sealed = chatKey === null
7011
+ ? null
7012
+ : (canV2
7013
+ ? encryptChatTextV2(chatKey, plainText, { sessionId, machineId: cfg.machineId, messageId: idempotencyKey })
7014
+ : encryptChatText(chatKey, plainText));
6993
7015
  /**
6994
7016
  * v1.2.6 reliability: an unreachable Rabbit does not lose the message. The exact body that could not be sent is
6995
7017
  * spooled to disk and replayed with the SAME idempotency key next time, so a message the relay actually received
@@ -7001,7 +7023,7 @@ hint: 'the local fleet-memory.md is unchanged; action read/show still reads loca
7001
7023
  ...(sealed !== null && sealed.ok === true ? { enc: sealed.enc } : {}),
7002
7024
  ...addressing,
7003
7025
  ...(mentions.length === 0 ? {} : { mentions }),
7004
- ...(typeof args?.idempotency_key === 'string' && args.idempotency_key.trim() !== '' ? { idempotency_key: args.idempotency_key.trim() } : { idempotency_key: 'spool-' + Date.now() + '-' + Math.random().toString(36).slice(2) }),
7026
+ ...{ idempotency_key: idempotencyKey },
7005
7027
  ...(typeof args?.reply_state === 'string' && (args.reply_state === 'answered' || args.reply_state === 'refused') ? { reply_state: args.reply_state } : {}),
7006
7028
  };
7007
7029
  let posted = null;
@@ -7058,7 +7080,7 @@ hint: 'the local fleet-memory.md is unchanged; action read/show still reads loca
7058
7080
  const listKey = deriveChatKey(cfg.fleetKey ?? resolvedFleetKey.value ?? null);
7059
7081
  const messages = rawMessages.map((message) => {
7060
7082
  if (!isEncrypted(message)) return message;
7061
- const opened = decryptChatMessage(listKey, message);
7083
+ const opened = decryptChatMessage(listKey, message, { sessionId: String(query.session_id ?? message.session_id ?? ''), machineId: typeof message.machine_id === 'string' ? message.machine_id : undefined });
7062
7084
  if (opened.ok === true) return { ...message, text: opened.text, decrypted: true };
7063
7085
  return { ...message, text: '', decrypted: false, encrypted: true, decrypt_error: opened.code };
7064
7086
  });
@@ -8017,6 +8039,18 @@ async function startLocalStack(ctx, cfg) {
8017
8039
  if (identityOptions.identity !== undefined && identityOptions.identity !== null) identityOptions.identity.device_token = null;
8018
8040
  ctx.logger?.warn?.('w2m: stored device token was rejected by ' + relayForToken + ' (HTTP ' + probe.status + '); pairing again with the fleet key');
8019
8041
  }
8042
+ if (alreadyPaired && fleetKeyFromConfig !== null) {
8043
+ const binding = await diagnoseFleetBinding({
8044
+ relayUrl: relayForToken,
8045
+ token: existingToken,
8046
+ machineId: loaded.identity.machine_id,
8047
+ fleetKey: fleetKeyFromConfig,
8048
+ });
8049
+ if (shouldRejoin({ token: existingToken, fleetId: binding.fleet_id, fleetKey: fleetKeyFromConfig })) {
8050
+ alreadyPaired = false;
8051
+ ctx.logger?.warn?.('w2m: FLEET_UNBOUND (token valid, fleet_id=null); re-admitting with the configured fleet key');
8052
+ }
8053
+ }
8020
8054
  } catch { /* network error: keep the token, do not force a re-pair */ }
8021
8055
  }
8022
8056
  }