@artenx/dshb 0.1.31 → 0.1.33

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 (36) hide show
  1. package/lib/client.js +133 -22
  2. package/lib/client.js.map +1 -1
  3. package/lib/core/index.js +6 -1
  4. package/lib/core/index.js.map +1 -1
  5. package/lib/core/sync/coordinator.d.ts +2 -2
  6. package/lib/core/sync/coordinator.js +23 -6
  7. package/lib/core/sync/coordinator.js.map +1 -1
  8. package/lib/core/sync/internal-routes.d.ts +1 -1
  9. package/lib/core/sync/internal-routes.js +80 -4
  10. package/lib/core/sync/internal-routes.js.map +1 -1
  11. package/lib/core/sync/offline-archive.d.ts +1 -1
  12. package/lib/core/sync/offline-archive.js +1 -1
  13. package/lib/core/sync/offline-routes.js +1 -1
  14. package/lib/core/sync/protocol.d.ts +2 -2
  15. package/lib/core/sync/protocol.js +4 -1
  16. package/lib/core/sync/protocol.js.map +1 -1
  17. package/lib/core/sync/remote-helper.js +107 -76
  18. package/lib/core/sync/remote-helper.js.map +1 -1
  19. package/lib/core/sync/routes.d.ts +1 -1
  20. package/lib/core/sync/routes.js +61 -1
  21. package/lib/core/sync/routes.js.map +1 -1
  22. package/lib/core/sync/session-routes.d.ts +1 -1
  23. package/lib/core/sync/session-routes.js +1 -1
  24. package/lib/core/sync/session-sync.d.ts +2 -2
  25. package/lib/core/sync/session-sync.js +2 -2
  26. package/lib/core/sync/ssh-transport.d.ts +2 -2
  27. package/lib/core/sync/ssh-transport.js +267 -3
  28. package/lib/core/sync/ssh-transport.js.map +1 -1
  29. package/lib/{protocol-KaWZArY8.d.ts → protocol-DJ0zyVlB.d.ts} +5 -2
  30. package/lib/{session-sync-Zfo1Q-qT.d.ts → session-sync-BuOJvFxK.d.ts} +54 -2
  31. package/lib/{session-sync-BQt3HXkp.js → session-sync-CIqjuWkh.js} +455 -48
  32. package/lib/session-sync-CIqjuWkh.js.map +1 -0
  33. package/lib/ssh-transport-C7povM0s.d.ts +115 -0
  34. package/package.json +1 -1
  35. package/lib/session-sync-BQt3HXkp.js.map +0 -1
  36. package/lib/ssh-transport-Cfb_nwkY.d.ts +0 -65
@@ -296,11 +296,12 @@ var SyncSshTransport = class {
296
296
  if (manifest.protocolVersion !== 1 || typeof manifest.instanceId !== "string" || !Array.isArray(manifest.entries) || !Array.isArray(manifest.skipped) || typeof manifest.digest !== "string") throw new SyncSshProbeError("protocol");
297
297
  return manifest;
298
298
  }
299
- async fetchSessionEntries(peerId, keys, manifestDigest, prefixes = []) {
299
+ async fetchSessionEntries(peerId, keys, manifestDigest, prefixes = [], deferredObjects = false) {
300
300
  const value = await this.fetchDataOperation(peerId, "session-entries", {
301
301
  keys,
302
302
  manifestDigest,
303
- prefixes
303
+ prefixes,
304
+ deferredObjects
304
305
  });
305
306
  if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError("helper");
306
307
  const bundle = value.bundle;
@@ -359,6 +360,103 @@ var SyncSshTransport = class {
359
360
  const fingerprint = this.hostKeyChallenges.acceptObserved(peer);
360
361
  await this.peerManager.pinHostKey(peerId, fingerprint);
361
362
  }
363
+ /**
364
+ * 拉取带外对象流:响应以帧流形式逐块 yield,调用方(收件箱)边收边落盘,
365
+ * 内存 O(单帧)。会话在流结束/出错时关闭(生成器 finally)。
366
+ */
367
+ async *fetchSessionObjects(peerId, objects) {
368
+ const body = Buffer.from(JSON.stringify({ objects: [...objects] }), "utf8");
369
+ if (body.length > 134217728) throw new SyncSshProbeError("protocol");
370
+ const { peer, session } = await this.openSession(peerId);
371
+ try {
372
+ await session.sendControl({
373
+ kind: "request",
374
+ protocolVersion: 1,
375
+ operation: "session-objects",
376
+ port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
377
+ });
378
+ for (let offset = 0, sequence = 0; offset < body.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, body.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, body.length)));
379
+ await session.sendControl({ kind: "request-end" });
380
+ session.endInput();
381
+ let responseStatus;
382
+ let bodyMode;
383
+ let responseEnded = false;
384
+ let totalBytes = 0;
385
+ for await (const frame of session.readFrames()) {
386
+ if (frame.kind === "control") {
387
+ if (frame.value.kind === "error") throw new SyncSshProbeError("helper");
388
+ if (frame.value.kind === "response" && responseStatus === void 0) {
389
+ responseStatus = typeof frame.value.status === "number" ? frame.value.status : void 0;
390
+ bodyMode = typeof frame.value.bodyMode === "string" ? frame.value.bodyMode : void 0;
391
+ continue;
392
+ }
393
+ if (frame.value.kind === "response-end" && responseStatus !== void 0 && !responseEnded) {
394
+ responseEnded = true;
395
+ continue;
396
+ }
397
+ throw new SyncProtocolError("unexpected session-objects control frame");
398
+ }
399
+ if (responseStatus === void 0 || bodyMode !== "data" || responseEnded) throw new SyncProtocolError("unexpected session-objects data frame");
400
+ totalBytes += frame.data.length;
401
+ if (totalBytes > 68719476736) throw new SyncProtocolError("session-objects exceeds object stream limit");
402
+ yield frame.data;
403
+ }
404
+ if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError("helper");
405
+ } catch (error) {
406
+ if (error instanceof SyncSshProbeError) throw error;
407
+ if (error instanceof SyncProtocolError) {
408
+ const probe = new SyncSshProbeError("protocol");
409
+ probe.detail = error.message;
410
+ throw probe;
411
+ }
412
+ throw new SyncSshProbeError("helper");
413
+ } finally {
414
+ session.close();
415
+ }
416
+ }
417
+ /**
418
+ * 推送带外对象流:把帧流按 1MB 分块写入请求体,逐块经 SSH 发送;
419
+ * 对端流式落盘收件箱后返回 JSON 摘要。
420
+ */
421
+ async pushSessionObjects(peerId, frames) {
422
+ const { peer, session } = await this.openSession(peerId);
423
+ try {
424
+ await session.sendControl({
425
+ kind: "request",
426
+ protocolVersion: 1,
427
+ operation: "session-objects-push",
428
+ port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT
429
+ });
430
+ let sequence = 0;
431
+ let totalBytes = 0;
432
+ for await (const chunk of frames) {
433
+ if (!(chunk instanceof Uint8Array)) throw new SyncProtocolError("session-objects-push contains a non-byte chunk");
434
+ const data = Buffer.from(chunk);
435
+ totalBytes += data.byteLength;
436
+ if (totalBytes > 68719476736) throw new SyncProtocolError("session-objects-push exceeds object stream limit");
437
+ for (let offset = 0; offset < data.byteLength; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) await session.sendData(sequence, data.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, data.byteLength)));
438
+ }
439
+ await session.sendControl({ kind: "request-end" });
440
+ session.endInput();
441
+ let response;
442
+ for await (const frame of session.readFrames()) {
443
+ if (frame.kind !== "control" || response !== void 0) throw new SyncProtocolError("unexpected session-objects-push frame");
444
+ if (frame.value.kind === "error") throw new SyncSshProbeError("helper");
445
+ if (frame.value.kind === "response") response = frame.value;
446
+ }
447
+ if (!response || response.status !== 200) throw new SyncSshProbeError("helper");
448
+ } catch (error) {
449
+ if (error instanceof SyncSshProbeError) throw error;
450
+ if (error instanceof SyncProtocolError) {
451
+ const probe = new SyncSshProbeError("protocol");
452
+ probe.detail = error.message;
453
+ throw probe;
454
+ }
455
+ throw new SyncSshProbeError("helper");
456
+ } finally {
457
+ session.close();
458
+ }
459
+ }
362
460
  async fetchDataOperation(peerId, operation, requestBody) {
363
461
  const body = requestBody === void 0 ? void 0 : Buffer.from(JSON.stringify(requestBody), "utf8");
364
462
  if (body && body.length > 134217728) throw new SyncSshProbeError("protocol");
@@ -445,7 +543,173 @@ function validateHandshakeResponse(value) {
445
543
  capabilities: body.capabilities
446
544
  };
447
545
  }
546
+ const REMOTE_PROBE_BEGIN_MARKER = "DSHB_PROBE_BEGIN";
547
+ const REMOTE_PROBE_END_MARKER = "DSHB_PROBE_END";
548
+ const REMOTE_PROBE_TIMEOUT_MS = 12e3;
549
+ const REMOTE_PROBE_MAX_OUTPUT_BYTES = 262144;
550
+ /**
551
+ * 生成远端发现脚本:纯 POSIX sh + 远端 node(DSH 所在机器必然有 node)。
552
+ * 三个动作:① 多候选定位 DSH_HOME(含桌面版常见路径);② 列出 profiles 及
553
+ * dshb 安装标记;③ 握手探测真实 Web 端口(仅远端回环,桌面版 127.0.0.1:3080
554
+ * 即可命中)。输出以 BEGIN/END 标记包裹,逐行 "key value"。
555
+ */
556
+ function buildRemoteProbeScript(dshHome, preferredWebPort) {
557
+ const homeOverride = dshHome === void 0 ? "" : JSON.stringify(dshHome);
558
+ const homeCandidates = (homeOverride ? ` ${homeOverride}` : "") + " \"$DSH_HOME\" \"$HOME/.dsh\" \"$HOME/Library/Application Support/dsh\" \"$HOME/Library/Application Support/DeepSeek Harness\"";
559
+ return [
560
+ `echo ${REMOTE_PROBE_BEGIN_MARKER}`,
561
+ `for c in${homeCandidates}; do`,
562
+ " [ -n \"$c\" ] && [ -d \"$c/profiles\" ] && echo \"home $c\"",
563
+ "done",
564
+ `for c in${homeCandidates}; do`,
565
+ " [ -n \"$c\" ] && [ -d \"$c/profiles\" ] || continue",
566
+ " for p in \"$c\"/profiles/*/; do",
567
+ " [ -d \"$p\" ] || continue",
568
+ " b=no; [ -d \"${p}node_modules/@artenx/dshb\" ] && b=yes",
569
+ " [ -f \"${p}package.json\" ] || continue",
570
+ " echo \"profile $(basename \"$p\") $b $c\"",
571
+ " done",
572
+ "done",
573
+ "ps -eo args= 2>/dev/null | grep -E \"dsh .*--profile|--profile .*--port|dsh web\" | grep -v grep | head -6",
574
+ "node -e 'const http=require(\"http\");const ports=process.argv.slice(1).map(Number).filter(Number.isInteger);let i=0;function probe(){if(i>=ports.length)process.exit(0);const port=ports[i++];const req=http.get({host:\"127.0.0.1\",port,path:\"/api/dshb/sync/internal/handshake\",timeout:700},res=>{let body=\"\";res.on(\"data\",c=>body+=c);res.on(\"end\",()=>{try{const j=JSON.parse(body);if(j&&j.ok===true&&typeof j.instanceId===\"string\"){console.log(\"handshake \"+port+\" \"+j.instanceId)}}catch(e){}probe()})});req.on(\"timeout\",()=>{req.destroy();probe()});req.on(\"error\",()=>probe());}probe()' " + (preferredWebPort !== void 0 ? ` ${preferredWebPort}` : "") + " 3080 8080 3079 3204 3206 3207 9080 $(ps -eo args= 2>/dev/null | grep -o \"\\-\\-port [0-9]\\+\" | awk '{print $2}' | head -6)",
575
+ `echo ${REMOTE_PROBE_END_MARKER}`
576
+ ].join("\n");
577
+ }
578
+ /** 解析探测输出:仅在 BEGIN/END 标记之间取行,容忍未知行。 */
579
+ function parseRemoteProbeOutput(output, hostKeyFingerprint, preferredWebPort) {
580
+ const begin = output.indexOf(REMOTE_PROBE_BEGIN_MARKER);
581
+ const end = output.indexOf(REMOTE_PROBE_END_MARKER);
582
+ if (begin < 0 || end < begin) throw new SyncSshProbeError("helper");
583
+ const lines = output.slice(begin + 16, end).split("\n").map((line) => line.trim()).filter(Boolean);
584
+ const result = {
585
+ homes: [],
586
+ profiles: [],
587
+ handshakes: [],
588
+ processHints: [],
589
+ hostKeyFingerprint
590
+ };
591
+ for (const line of lines) {
592
+ const [head, ...rest] = line.split(/\s+/);
593
+ const value = rest.join(" ");
594
+ if (head === "home" && value) result.homes.push(value);
595
+ else if (head === "profile" && rest.length >= 3) {
596
+ const [name, dshb, home] = rest;
597
+ result.profiles.push({
598
+ name,
599
+ dshbInstalled: dshb === "yes",
600
+ home
601
+ });
602
+ } else if (head === "handshake" && rest.length >= 2) {
603
+ const port = Number(rest[0]);
604
+ if (Number.isInteger(port) && port >= 1 && port <= 65535 && !result.handshakes.some((hit) => hit.port === port)) result.handshakes.push({
605
+ port,
606
+ instanceId: rest[1]
607
+ });
608
+ } else if (line && !line.startsWith("home ") && !line.startsWith("profile ") && !line.startsWith("handshake ")) result.processHints.push(line);
609
+ }
610
+ result.homes = [...new Set(result.homes)];
611
+ result.profiles = result.profiles.filter((profile, index, all) => all.findIndex((candidate) => candidate.name === profile.name && candidate.home === profile.home) === index);
612
+ const preferred = result.handshakes.find((hit) => hit.port === preferredWebPort) ?? result.handshakes[0];
613
+ if (preferred) {
614
+ result.webPort = preferred.port;
615
+ result.instanceId = preferred.instanceId;
616
+ }
617
+ return result;
618
+ }
619
+ var SyncRemoteProbe = class {
620
+ /** 探测远端:SSH 登录 + 单次 exec 发现脚本;主机密钥自动接受(只读发现,指纹随结果返回)。 */
621
+ async probeRemote(draft, secrets, preferredWebPort) {
622
+ if (draft.authKind === "password" && !secrets.password) throw new SyncSshProbeError("auth");
623
+ if (draft.authKind === "key" && !secrets.privateKey) throw new SyncSshProbeError("auth");
624
+ let fingerprint = "";
625
+ const client = new Client();
626
+ try {
627
+ await new Promise((resolve, reject) => {
628
+ let settled = false;
629
+ const finish = (error) => {
630
+ if (settled) return;
631
+ settled = true;
632
+ clearTimeout(deadline);
633
+ if (error) reject(error);
634
+ else resolve();
635
+ };
636
+ const deadline = setTimeout(() => {
637
+ const timeout = /* @__PURE__ */ new Error("Timed out while waiting for SSH handshake");
638
+ timeout.level = "client-timeout";
639
+ finish(timeout);
640
+ client.end();
641
+ }, SSH_READY_TIMEOUT_MS);
642
+ client.once("ready", () => finish());
643
+ client.on("error", (error) => finish(error));
644
+ const config = {
645
+ host: draft.host,
646
+ port: draft.port,
647
+ username: draft.username,
648
+ hostHash: "sha256",
649
+ readyTimeout: SSH_READY_TIMEOUT_MS,
650
+ hostVerifier: (sha256Hex) => {
651
+ fingerprint = sha256Hex;
652
+ return true;
653
+ },
654
+ ...draft.authKind === "password" ? { password: secrets.password } : {
655
+ privateKey: secrets.privateKey,
656
+ ...secrets.passphrase ? { passphrase: secrets.passphrase } : {}
657
+ }
658
+ };
659
+ try {
660
+ client.connect(config);
661
+ } catch (error) {
662
+ finish(error);
663
+ }
664
+ });
665
+ return parseRemoteProbeOutput(await new Promise((resolve, reject) => {
666
+ const chunks = [];
667
+ let size = 0;
668
+ let settled = false;
669
+ const finish = (error, text) => {
670
+ if (settled) return;
671
+ settled = true;
672
+ clearTimeout(deadline);
673
+ if (error) reject(error);
674
+ else resolve(text ?? "");
675
+ };
676
+ const deadline = setTimeout(() => finish(/* @__PURE__ */ new Error("Timed out while waiting for the remote probe script")), REMOTE_PROBE_TIMEOUT_MS);
677
+ client.exec(buildRemoteProbeScript(void 0, preferredWebPort), (error, channel) => {
678
+ if (error) {
679
+ finish(error);
680
+ return;
681
+ }
682
+ channel.on("data", (chunk) => {
683
+ size += chunk.length;
684
+ if (size > REMOTE_PROBE_MAX_OUTPUT_BYTES) {
685
+ finish(/* @__PURE__ */ new Error("remote probe output exceeds size limit"));
686
+ return;
687
+ }
688
+ chunks.push(chunk);
689
+ });
690
+ channel.on("error", (error) => finish(error));
691
+ channel.on("close", (code) => {
692
+ const text = Buffer.concat(chunks).toString("utf8");
693
+ if (code !== 0 && !text.includes("DSHB_PROBE_END")) finish(/* @__PURE__ */ new Error(`remote probe script exited with ${code}`));
694
+ else finish(void 0, text);
695
+ });
696
+ channel.stderr.resume();
697
+ });
698
+ }), fingerprint, preferredWebPort);
699
+ } catch (error) {
700
+ if (error instanceof SyncSshProbeError) throw error;
701
+ const category = classifySshError(error);
702
+ if (category === "auth" || category === "network" || category === "timeout") throw new SyncSshProbeError(category);
703
+ if (category === "hostkey") throw new SyncSshProbeError("hostkey");
704
+ throw new SyncSshProbeError("helper");
705
+ } finally {
706
+ try {
707
+ client.end();
708
+ } catch {}
709
+ }
710
+ }
711
+ };
448
712
  //#endregion
449
- export { SyncSessionDeltaPrefixError, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
713
+ export { REMOTE_PROBE_BEGIN_MARKER, REMOTE_PROBE_END_MARKER, SyncRemoteProbe, SyncSessionDeltaPrefixError, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildRemoteProbeScript, buildSyncSshConnectConfig, parseRemoteProbeOutput };
450
714
 
451
715
  //# sourceMappingURL=ssh-transport.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ssh-transport.js","names":[],"sources":["../../../src/core/sync/ssh-transport.ts"],"sourcesContent":["import { Client, type ConnectConfig } from 'ssh2'\nimport type { ClientChannel } from 'ssh2'\nimport { once } from 'node:events'\nimport { classifySshError } from '../../exec-ssh/connection.js'\nimport { SyncPeerManager, SyncPeerNotFoundError, type SyncPeerSecrets } from './peer-manager.js'\nimport { isRecord, type SyncPeer } from './model.js'\nimport { SyncHostKeyChallengeError, SyncHostKeyChallenges, inspectSyncHostKey } from './host-key.js'\nimport type { SyncNodeApplyResult, SyncNodeManifest } from './node-sync.js'\nimport {\n MAX_SYNC_DATA_CHUNK_BYTES,\n MAX_SYNC_TRANSFER_BYTES,\n SYNC_PROTOCOL_VERSION,\n SyncFrameDecoder,\n SyncProtocolError,\n encodeSyncControlFrame,\n encodeSyncDataFrame,\n type SyncFrame,\n} from './protocol.js'\nimport { REMOTE_SYNC_HELPER_COMMAND } from './remote-helper.js'\nimport type { SyncSessionApplyResult, SyncSessionBundle, SyncSessionEventPrefix, SyncSessionManifest } from './session-sync.js'\n\nconst SSH_READY_TIMEOUT_MS = 30_000\nconst SSH_READ_IDLE_TIMEOUT_MS = 60_000\nconst DEFAULT_REMOTE_WEB_PORT = 3080\n\nexport type SyncSshErrorCategory = 'auth' | 'network' | 'hostkey' | 'timeout' | 'helper' | 'protocol'\n\nexport class SyncSshProbeError extends Error {\n constructor(readonly category: SyncSshErrorCategory) {\n super(`SSH sync probe failed: ${category}`)\n this.name = 'SyncSshProbeError'\n }\n}\n\nexport class SyncSessionDeltaPrefixError extends Error {\n constructor() {\n super('remote session event prefix changed during transfer')\n this.name = 'SyncSessionDeltaPrefixError'\n }\n}\n\nexport interface SyncRemoteHandshake {\n protocolVersion: number\n instanceId: string\n capabilities: string[]\n}\n\nexport interface SyncRemoteNodeManifest extends SyncNodeManifest {\n capabilities?: string[]\n}\n\nexport class SyncSshSession {\n private readonly decoder = new SyncFrameDecoder()\n private readonly closeSignal = new AbortController()\n private reading = false\n private closed = false\n private inputEnded = false\n private nextDataSequence = 0\n private pendingError: Error | undefined\n\n constructor(\n private readonly client: Client,\n private readonly channel: ClientChannel,\n ) {\n channel.on('error', (error: Error) => { this.pendingError = error })\n channel.on('exit', (code: number | null) => {\n if (code !== null && code !== 0) this.pendingError = new Error(`remote helper exited with code ${code}`)\n })\n channel.on('close', () => { this.closeSignal.abort() })\n }\n\n async sendControl(value: Record<string, unknown>): Promise<void> {\n await this.write(encodeSyncControlFrame(value))\n }\n\n async sendData(sequence: number, data: Buffer): Promise<void> {\n if (sequence !== this.nextDataSequence) throw new SyncProtocolError('outgoing data sequence is out of order')\n await this.write(encodeSyncDataFrame(sequence, data))\n this.nextDataSequence += 1\n }\n\n endInput(): void {\n if (this.closed || this.inputEnded) return\n this.inputEnded = true\n this.channel.end()\n }\n\n async *readFrames(idleTimeoutMs = SSH_READ_IDLE_TIMEOUT_MS): AsyncGenerator<SyncFrame> {\n if (this.reading) throw new SyncProtocolError('SSH sync channel can only be read once')\n this.reading = true\n const channel = this.channel\n const decoder = this.decoder\n let idleDeadline: NodeJS.Timeout | undefined\n const armIdleDeadline = (): void => {\n clearTimeout(idleDeadline)\n idleDeadline = setTimeout(() => {\n this.pendingError = new Error(`SSH channel idle for more than ${Math.round(idleTimeoutMs / 1000)}s`)\n // Full teardown: destroying the channel alone may leave the stdout\n // stream open, which would keep a plain for-await suspended forever.\n this.close()\n }, idleTimeoutMs)\n }\n // channel.destroy() does not reliably end ssh2's stdout stream; racing the\n // iterator against the close signal guarantees this generator settles.\n // A channel close resolves with a sentinel instead of rejecting: a normal\n // helper exit closes the channel around the same moment stdout reaches EOF,\n // and only a pendingError (idle timeout, helper crash) makes it an error.\n const CLOSE: unique symbol = Symbol('channel-close')\n const closed: Promise<IteratorResult<Buffer> | typeof CLOSE> = new Promise((resolve) => {\n this.closeSignal.signal.addEventListener('abort', () => resolve(CLOSE), { once: true })\n })\n const iterator = (channel.stdout as AsyncIterable<Buffer>)[Symbol.asyncIterator]()\n try {\n armIdleDeadline()\n for (;;) {\n const next = await Promise.race([iterator.next(), closed])\n if (next === CLOSE || next.done) {\n if (this.pendingError) throw new SyncProtocolError(`SSH channel error: ${this.pendingError.message}`)\n break\n }\n armIdleDeadline()\n for (const frame of decoder.push(Buffer.from(next.value))) yield frame\n }\n decoder.finish()\n } catch (error) {\n if (error instanceof SyncProtocolError) throw error\n const detail = this.pendingError ? this.pendingError.message : error instanceof Error ? error.message : 'unknown error'\n throw new SyncProtocolError(`SSH channel read failed: ${detail}`)\n } finally {\n clearTimeout(idleDeadline)\n void iterator.return?.().catch(() => {})\n }\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.channel.destroy()\n this.client.end()\n }\n\n private async write(frame: Buffer): Promise<void> {\n if (this.closed || this.inputEnded) throw new SyncProtocolError('SSH sync channel input is closed')\n if (this.channel.write(frame)) return\n // A frozen connection never drains; bound the wait so writes cannot hang.\n const drainDeadline = setTimeout(() => this.closeSignal.abort(), SSH_READ_IDLE_TIMEOUT_MS)\n try {\n await once(this.channel, 'drain', { signal: this.closeSignal.signal })\n } catch (error) {\n if (this.closeSignal.signal.aborted) throw new SyncProtocolError('SSH channel closed before write could drain')\n throw error\n } finally {\n clearTimeout(drainDeadline)\n }\n }\n}\n\nexport function buildSyncSshConnectConfig(\n peer: SyncPeer,\n secrets: SyncPeerSecrets,\n challenges: SyncHostKeyChallenges,\n onChallenge: (error: SyncHostKeyChallengeError) => void,\n): ConnectConfig {\n return {\n host: peer.host,\n port: peer.port,\n username: peer.username,\n hostHash: 'sha256',\n readyTimeout: SSH_READY_TIMEOUT_MS,\n keepaliveInterval: 15_000,\n hostVerifier: (sha256Hex: string): boolean => {\n try {\n const decision = inspectSyncHostKey(peer, sha256Hex, challenges)\n if (!decision.trusted) onChallenge(decision.challenge)\n return decision.trusted\n } catch (error) {\n if (error instanceof SyncHostKeyChallengeError) onChallenge(error)\n return false\n }\n },\n ...(peer.authKind === 'password'\n ? { password: secrets.password }\n : { privateKey: secrets.privateKey, ...(secrets.passphrase ? { passphrase: secrets.passphrase } : {}) }),\n }\n}\n\nexport class SyncSshTransport {\n private readonly hostKeyChallenges = new SyncHostKeyChallenges()\n\n constructor(private readonly peerManager: SyncPeerManager) {}\n\n async probe(peerId: string): Promise<SyncRemoteHandshake> {\n const { peer, session } = await this.openSession(peerId)\n try {\n const handshake = await this.requestHandshake(session, peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT)\n if (handshake.protocolVersion !== SYNC_PROTOCOL_VERSION) throw new SyncSshProbeError('protocol')\n return handshake\n } catch (error) {\n if (\n error instanceof SyncHostKeyChallengeError ||\n error instanceof SyncPeerNotFoundError ||\n error instanceof SyncSshProbeError\n ) {\n throw error\n }\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async openSession(peerId: string): Promise<{ peer: SyncPeer; session: SyncSshSession }> {\n const { peer, secrets } = await this.peerManager.connectionData(peerId)\n if (peer.authKind === 'password' && !secrets.password) throw new SyncSshProbeError('auth')\n if (peer.authKind === 'key' && !secrets.privateKey) throw new SyncSshProbeError('auth')\n\n const client = new Client()\n let challenge: SyncHostKeyChallengeError | undefined\n let sessionOpened = false\n try {\n await new Promise<void>((resolve, reject) => {\n let settled = false\n const finish = (error?: unknown): void => {\n if (settled) return\n settled = true\n clearTimeout(readyDeadline)\n if (error) reject(error)\n else resolve()\n }\n\n // ssh2's readyTimeout can be defeated by stalls behind transparent\n // proxies; this deadline guarantees the connect promise settles.\n const readyDeadline = setTimeout(() => {\n const timeout = new Error('Timed out while waiting for SSH handshake')\n ;(timeout as Error & { level?: string }).level = 'client-timeout'\n finish(timeout)\n client.end()\n }, SSH_READY_TIMEOUT_MS * 2)\n client.once('ready', () => finish())\n client.on('error', (error: Error) => finish(challenge ?? error))\n const config = buildSyncSshConnectConfig(peer, secrets, this.hostKeyChallenges, (error) => {\n challenge = error\n })\n\n try {\n client.connect(config)\n } catch (error) {\n finish(error)\n }\n })\n\n const channel = await new Promise<ClientChannel>((resolve, reject) => {\n const execDeadline = setTimeout(() => {\n reject(new Error('Timed out while waiting for the sync helper channel'))\n client.end()\n }, SSH_READY_TIMEOUT_MS)\n client.exec(REMOTE_SYNC_HELPER_COMMAND, (error, opened) => {\n clearTimeout(execDeadline)\n if (error) reject(error)\n else resolve(opened)\n })\n })\n const session = new SyncSshSession(client, channel)\n sessionOpened = true\n return { peer, session }\n } catch (error) {\n if (error instanceof SyncHostKeyChallengeError || error instanceof SyncPeerNotFoundError || error instanceof SyncSshProbeError) {\n throw error\n }\n if (challenge) throw challenge\n const category = classifySshError(error)\n if (category === 'auth' || category === 'network' || category === 'timeout') {\n throw new SyncSshProbeError(category)\n }\n if (category === 'hostkey') throw new SyncSshProbeError('hostkey')\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n if (!sessionOpened) {\n try {\n client.end()\n } catch {}\n }\n }\n }\n\n async fetchNodeManifest(peerId: string): Promise<SyncRemoteNodeManifest> {\n const value = await this.fetchDataOperation(peerId, 'node-manifest')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError('helper')\n const manifest = value.manifest\n if (manifest.protocolVersion !== SYNC_PROTOCOL_VERSION || typeof manifest.instanceId !== 'string' || !Array.isArray(manifest.records)) {\n throw new SyncSshProbeError('protocol')\n }\n const capabilities = value.capabilities === undefined ? undefined : value.capabilities\n if (capabilities !== undefined && (!Array.isArray(capabilities) || !capabilities.every((item) => typeof item === 'string'))) {\n throw new SyncSshProbeError('protocol')\n }\n return { ...manifest, ...(capabilities ? { capabilities } : {}) } as unknown as SyncRemoteNodeManifest\n }\n\n async applyNodeManifest(\n peerId: string,\n records: SyncNodeManifest['records'],\n sourceName: string,\n ): Promise<SyncNodeApplyResult> {\n const requestBody = Buffer.from(JSON.stringify({ records, sourceName }), 'utf8')\n if (requestBody.length > MAX_SYNC_TRANSFER_BYTES) throw new SyncSshProbeError('protocol')\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'node-apply',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response) throw new SyncProtocolError('unexpected node apply response frame')\n response = frame.value\n }\n if (!response || response.kind === 'error') throw new SyncSshProbeError('helper')\n if (response.kind !== 'response' || response.status !== 200 || response.body === null || typeof response.body !== 'object') {\n throw new SyncSshProbeError('helper')\n }\n const body = response.body as Record<string, unknown>\n if (body.ok !== true || body.result === null || typeof body.result !== 'object') throw new SyncSshProbeError('helper')\n const result = body.result as Record<string, unknown>\n for (const key of ['created', 'updated', 'deleted', 'pendingRemoval', 'skipped'] as const) {\n if (!Number.isSafeInteger(result[key]) || (result[key] as number) < 0) throw new SyncSshProbeError('protocol')\n }\n return result as unknown as SyncNodeApplyResult\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async fetchSessionBundle(peerId: string): Promise<SyncSessionBundle> {\n const value = await this.fetchDataOperation(peerId, 'session-bundle')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError('helper')\n const bundle = value.bundle\n if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== 'string' ||\n !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== 'string') {\n throw new SyncSshProbeError('protocol')\n }\n return bundle as unknown as SyncSessionBundle\n }\n\n async fetchSessionManifest(peerId: string): Promise<SyncSessionManifest> {\n const value = await this.fetchDataOperation(peerId, 'session-manifest')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError('helper')\n const manifest = value.manifest\n if (manifest.protocolVersion !== 1 || typeof manifest.instanceId !== 'string' ||\n !Array.isArray(manifest.entries) || !Array.isArray(manifest.skipped) || typeof manifest.digest !== 'string') {\n throw new SyncSshProbeError('protocol')\n }\n return manifest as unknown as SyncSessionManifest\n }\n\n async fetchSessionEntries(\n peerId: string,\n keys: readonly string[],\n manifestDigest: string,\n prefixes: readonly SyncSessionEventPrefix[] = [],\n ): Promise<SyncSessionBundle> {\n const value = await this.fetchDataOperation(peerId, 'session-entries', { keys, manifestDigest, prefixes })\n if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError('helper')\n const bundle = value.bundle\n if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== 'string' ||\n !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== 'string') {\n throw new SyncSshProbeError('protocol')\n }\n return bundle as unknown as SyncSessionBundle\n }\n\n async applySessionBundle(peerId: string, bundle: SyncSessionBundle, sourceName: string): Promise<SyncSessionApplyResult> {\n const requestBody = Buffer.from(JSON.stringify({ bundle, sourceName, peerId }), 'utf8')\n if (requestBody.length > MAX_SYNC_TRANSFER_BYTES) throw new SyncSshProbeError('protocol')\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'session-apply',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response) throw new SyncProtocolError('unexpected session apply response frame')\n response = frame.value\n }\n if (!response || response.kind === 'error') throw new SyncSshProbeError('helper')\n if (response.kind === 'response' && response.status === 409 && isRecord(response.body) &&\n response.body.code === 'session-delta-prefix') throw new SyncSessionDeltaPrefixError()\n if (response.kind !== 'response' || response.status !== 200 || response.body === null || typeof response.body !== 'object') {\n throw new SyncSshProbeError('helper')\n }\n const body = response.body as Record<string, unknown>\n if (body.ok !== true || body.result === null || typeof body.result !== 'object') throw new SyncSshProbeError('helper')\n const result = body.result as Record<string, unknown>\n for (const key of ['created', 'updated', 'archived', 'deleted', 'skipped', 'unmapped'] as const) {\n if (!Number.isSafeInteger(result[key]) || (result[key] as number) < 0) throw new SyncSshProbeError('protocol')\n }\n return result as unknown as SyncSessionApplyResult\n } catch (error) {\n if (error instanceof SyncSessionDeltaPrefixError) throw error\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async trustObservedHostKey(peerId: string): Promise<void> {\n const { peer } = await this.peerManager.connectionData(peerId)\n const fingerprint = this.hostKeyChallenges.acceptObserved(peer)\n await this.peerManager.pinHostKey(peerId, fingerprint)\n }\n\n private async fetchDataOperation(\n peerId: string,\n operation: 'node-manifest' | 'session-bundle' | 'session-manifest' | 'session-entries',\n requestBody?: Record<string, unknown>,\n ): Promise<unknown> {\n const body = requestBody === undefined ? undefined : Buffer.from(JSON.stringify(requestBody), 'utf8')\n if (body && body.length > MAX_SYNC_TRANSFER_BYTES) throw new SyncSshProbeError('protocol')\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation,\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n if (body) {\n for (let offset = 0, sequence = 0; offset < body.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, body.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, body.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n }\n session.endInput()\n\n let responseStatus: number | undefined\n let bodyMode: string | undefined\n let responseEnded = false\n let totalBytes = 0\n const chunks: Buffer[] = []\n for await (const frame of session.readFrames()) {\n if (frame.kind === 'control') {\n if (frame.value.kind === 'error') throw new SyncSshProbeError('helper')\n if (frame.value.kind === 'response' && responseStatus === undefined) {\n responseStatus = typeof frame.value.status === 'number' ? frame.value.status : undefined\n bodyMode = typeof frame.value.bodyMode === 'string' ? frame.value.bodyMode : undefined\n continue\n }\n if (frame.value.kind === 'response-end' && responseStatus !== undefined && !responseEnded) {\n responseEnded = true\n continue\n }\n throw new SyncProtocolError(`unexpected ${operation} control frame`)\n }\n if (responseStatus === undefined || bodyMode !== 'data' || responseEnded) {\n throw new SyncProtocolError(`unexpected ${operation} data frame`)\n }\n totalBytes += frame.data.length\n if (totalBytes > MAX_SYNC_TRANSFER_BYTES) throw new SyncProtocolError(`${operation} exceeds transfer limit`)\n chunks.push(frame.data)\n }\n if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError('helper')\n try {\n return JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8')) as unknown\n } catch {\n throw new SyncProtocolError(`${operation} response is invalid JSON`)\n }\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) {\n const probe = new SyncSshProbeError('protocol')\n ;(probe as Error & { detail?: string }).detail = error.message\n throw probe\n }\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n private async requestHandshake(session: SyncSshSession, port: number): Promise<SyncRemoteHandshake> {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'handshake',\n port,\n })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response) throw new SyncProtocolError('unexpected helper frame')\n response = frame.value\n }\n if (!response) throw new SyncProtocolError('helper returned no handshake response')\n return validateHandshakeResponse(response)\n }\n}\n\nfunction validateHandshakeResponse(value: Record<string, unknown>): SyncRemoteHandshake {\n if (value.kind === 'error') throw new SyncSshProbeError('helper')\n if (value.kind !== 'response' || value.status !== 200 || value.body === null || typeof value.body !== 'object') {\n throw new SyncSshProbeError('helper')\n }\n const body = value.body as Record<string, unknown>\n if (\n typeof body.protocolVersion !== 'number' ||\n typeof body.instanceId !== 'string' ||\n !Array.isArray(body.capabilities) ||\n !body.capabilities.every((capability) => typeof capability === 'string')\n ) {\n throw new SyncSshProbeError('protocol')\n }\n return {\n protocolVersion: body.protocolVersion,\n instanceId: body.instanceId,\n capabilities: body.capabilities,\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ssh-transport.js","names":[],"sources":["../../../src/core/sync/ssh-transport.ts"],"sourcesContent":["import { Client, type ConnectConfig } from 'ssh2'\nimport type { ClientChannel } from 'ssh2'\nimport { once } from 'node:events'\nimport { classifySshError } from '../../exec-ssh/connection.js'\nimport { SyncPeerManager, SyncPeerNotFoundError, type SyncPeerSecrets } from './peer-manager.js'\nimport { isRecord, type SyncPeer } from './model.js'\nimport { SyncHostKeyChallengeError, SyncHostKeyChallenges, inspectSyncHostKey } from './host-key.js'\nimport type { SyncNodeApplyResult, SyncNodeManifest } from './node-sync.js'\nimport {\n MAX_SESSION_OBJECT_STREAM_BYTES,\n MAX_SYNC_DATA_CHUNK_BYTES,\n MAX_SYNC_TRANSFER_BYTES,\n SYNC_PROTOCOL_VERSION,\n SyncFrameDecoder,\n SyncProtocolError,\n encodeSyncControlFrame,\n encodeSyncDataFrame,\n type SyncFrame,\n} from './protocol.js'\nimport { REMOTE_SYNC_HELPER_COMMAND } from './remote-helper.js'\nimport type { SyncSessionApplyResult, SyncSessionBundle, SyncSessionEventPrefix, SyncSessionManifest, SyncSessionObject } from './session-sync.js'\n\nconst SSH_READY_TIMEOUT_MS = 30_000\nconst SSH_READ_IDLE_TIMEOUT_MS = 60_000\nconst DEFAULT_REMOTE_WEB_PORT = 3080\n\nexport type SyncSshErrorCategory = 'auth' | 'network' | 'hostkey' | 'timeout' | 'helper' | 'protocol'\n\nexport class SyncSshProbeError extends Error {\n constructor(readonly category: SyncSshErrorCategory) {\n super(`SSH sync probe failed: ${category}`)\n this.name = 'SyncSshProbeError'\n }\n}\n\nexport class SyncSessionDeltaPrefixError extends Error {\n constructor() {\n super('remote session event prefix changed during transfer')\n this.name = 'SyncSessionDeltaPrefixError'\n }\n}\n\nexport interface SyncRemoteHandshake {\n protocolVersion: number\n instanceId: string\n capabilities: string[]\n}\n\nexport interface SyncRemoteNodeManifest extends SyncNodeManifest {\n capabilities?: string[]\n}\n\nexport class SyncSshSession {\n private readonly decoder = new SyncFrameDecoder()\n private readonly closeSignal = new AbortController()\n private reading = false\n private closed = false\n private inputEnded = false\n private nextDataSequence = 0\n private pendingError: Error | undefined\n\n constructor(\n private readonly client: Client,\n private readonly channel: ClientChannel,\n ) {\n channel.on('error', (error: Error) => { this.pendingError = error })\n channel.on('exit', (code: number | null) => {\n if (code !== null && code !== 0) this.pendingError = new Error(`remote helper exited with code ${code}`)\n })\n channel.on('close', () => { this.closeSignal.abort() })\n }\n\n async sendControl(value: Record<string, unknown>): Promise<void> {\n await this.write(encodeSyncControlFrame(value))\n }\n\n async sendData(sequence: number, data: Buffer): Promise<void> {\n if (sequence !== this.nextDataSequence) throw new SyncProtocolError('outgoing data sequence is out of order')\n await this.write(encodeSyncDataFrame(sequence, data))\n this.nextDataSequence += 1\n }\n\n endInput(): void {\n if (this.closed || this.inputEnded) return\n this.inputEnded = true\n this.channel.end()\n }\n\n async *readFrames(idleTimeoutMs = SSH_READ_IDLE_TIMEOUT_MS): AsyncGenerator<SyncFrame> {\n if (this.reading) throw new SyncProtocolError('SSH sync channel can only be read once')\n this.reading = true\n const channel = this.channel\n const decoder = this.decoder\n let idleDeadline: NodeJS.Timeout | undefined\n const armIdleDeadline = (): void => {\n clearTimeout(idleDeadline)\n idleDeadline = setTimeout(() => {\n this.pendingError = new Error(`SSH channel idle for more than ${Math.round(idleTimeoutMs / 1000)}s`)\n // Full teardown: destroying the channel alone may leave the stdout\n // stream open, which would keep a plain for-await suspended forever.\n this.close()\n }, idleTimeoutMs)\n }\n // channel.destroy() does not reliably end ssh2's stdout stream; racing the\n // iterator against the close signal guarantees this generator settles.\n // A channel close resolves with a sentinel instead of rejecting: a normal\n // helper exit closes the channel around the same moment stdout reaches EOF,\n // and only a pendingError (idle timeout, helper crash) makes it an error.\n const CLOSE: unique symbol = Symbol('channel-close')\n const closed: Promise<IteratorResult<Buffer> | typeof CLOSE> = new Promise((resolve) => {\n this.closeSignal.signal.addEventListener('abort', () => resolve(CLOSE), { once: true })\n })\n const iterator = (channel.stdout as AsyncIterable<Buffer>)[Symbol.asyncIterator]()\n try {\n armIdleDeadline()\n for (;;) {\n const next = await Promise.race([iterator.next(), closed])\n if (next === CLOSE || next.done) {\n if (this.pendingError) throw new SyncProtocolError(`SSH channel error: ${this.pendingError.message}`)\n break\n }\n armIdleDeadline()\n for (const frame of decoder.push(Buffer.from(next.value))) yield frame\n }\n decoder.finish()\n } catch (error) {\n if (error instanceof SyncProtocolError) throw error\n const detail = this.pendingError ? this.pendingError.message : error instanceof Error ? error.message : 'unknown error'\n throw new SyncProtocolError(`SSH channel read failed: ${detail}`)\n } finally {\n clearTimeout(idleDeadline)\n void iterator.return?.().catch(() => {})\n }\n }\n\n close(): void {\n if (this.closed) return\n this.closed = true\n this.channel.destroy()\n this.client.end()\n }\n\n private async write(frame: Buffer): Promise<void> {\n if (this.closed || this.inputEnded) throw new SyncProtocolError('SSH sync channel input is closed')\n if (this.channel.write(frame)) return\n // A frozen connection never drains; bound the wait so writes cannot hang.\n const drainDeadline = setTimeout(() => this.closeSignal.abort(), SSH_READ_IDLE_TIMEOUT_MS)\n try {\n await once(this.channel, 'drain', { signal: this.closeSignal.signal })\n } catch (error) {\n if (this.closeSignal.signal.aborted) throw new SyncProtocolError('SSH channel closed before write could drain')\n throw error\n } finally {\n clearTimeout(drainDeadline)\n }\n }\n}\n\nexport function buildSyncSshConnectConfig(\n peer: SyncPeer,\n secrets: SyncPeerSecrets,\n challenges: SyncHostKeyChallenges,\n onChallenge: (error: SyncHostKeyChallengeError) => void,\n): ConnectConfig {\n return {\n host: peer.host,\n port: peer.port,\n username: peer.username,\n hostHash: 'sha256',\n readyTimeout: SSH_READY_TIMEOUT_MS,\n keepaliveInterval: 15_000,\n hostVerifier: (sha256Hex: string): boolean => {\n try {\n const decision = inspectSyncHostKey(peer, sha256Hex, challenges)\n if (!decision.trusted) onChallenge(decision.challenge)\n return decision.trusted\n } catch (error) {\n if (error instanceof SyncHostKeyChallengeError) onChallenge(error)\n return false\n }\n },\n ...(peer.authKind === 'password'\n ? { password: secrets.password }\n : { privateKey: secrets.privateKey, ...(secrets.passphrase ? { passphrase: secrets.passphrase } : {}) }),\n }\n}\n\nexport class SyncSshTransport {\n private readonly hostKeyChallenges = new SyncHostKeyChallenges()\n\n constructor(private readonly peerManager: SyncPeerManager) {}\n\n async probe(peerId: string): Promise<SyncRemoteHandshake> {\n const { peer, session } = await this.openSession(peerId)\n try {\n const handshake = await this.requestHandshake(session, peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT)\n if (handshake.protocolVersion !== SYNC_PROTOCOL_VERSION) throw new SyncSshProbeError('protocol')\n return handshake\n } catch (error) {\n if (\n error instanceof SyncHostKeyChallengeError ||\n error instanceof SyncPeerNotFoundError ||\n error instanceof SyncSshProbeError\n ) {\n throw error\n }\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async openSession(peerId: string): Promise<{ peer: SyncPeer; session: SyncSshSession }> {\n const { peer, secrets } = await this.peerManager.connectionData(peerId)\n if (peer.authKind === 'password' && !secrets.password) throw new SyncSshProbeError('auth')\n if (peer.authKind === 'key' && !secrets.privateKey) throw new SyncSshProbeError('auth')\n\n const client = new Client()\n let challenge: SyncHostKeyChallengeError | undefined\n let sessionOpened = false\n try {\n await new Promise<void>((resolve, reject) => {\n let settled = false\n const finish = (error?: unknown): void => {\n if (settled) return\n settled = true\n clearTimeout(readyDeadline)\n if (error) reject(error)\n else resolve()\n }\n\n // ssh2's readyTimeout can be defeated by stalls behind transparent\n // proxies; this deadline guarantees the connect promise settles.\n const readyDeadline = setTimeout(() => {\n const timeout = new Error('Timed out while waiting for SSH handshake')\n ;(timeout as Error & { level?: string }).level = 'client-timeout'\n finish(timeout)\n client.end()\n }, SSH_READY_TIMEOUT_MS * 2)\n client.once('ready', () => finish())\n client.on('error', (error: Error) => finish(challenge ?? error))\n const config = buildSyncSshConnectConfig(peer, secrets, this.hostKeyChallenges, (error) => {\n challenge = error\n })\n\n try {\n client.connect(config)\n } catch (error) {\n finish(error)\n }\n })\n\n const channel = await new Promise<ClientChannel>((resolve, reject) => {\n const execDeadline = setTimeout(() => {\n reject(new Error('Timed out while waiting for the sync helper channel'))\n client.end()\n }, SSH_READY_TIMEOUT_MS)\n client.exec(REMOTE_SYNC_HELPER_COMMAND, (error, opened) => {\n clearTimeout(execDeadline)\n if (error) reject(error)\n else resolve(opened)\n })\n })\n const session = new SyncSshSession(client, channel)\n sessionOpened = true\n return { peer, session }\n } catch (error) {\n if (error instanceof SyncHostKeyChallengeError || error instanceof SyncPeerNotFoundError || error instanceof SyncSshProbeError) {\n throw error\n }\n if (challenge) throw challenge\n const category = classifySshError(error)\n if (category === 'auth' || category === 'network' || category === 'timeout') {\n throw new SyncSshProbeError(category)\n }\n if (category === 'hostkey') throw new SyncSshProbeError('hostkey')\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n if (!sessionOpened) {\n try {\n client.end()\n } catch {}\n }\n }\n }\n\n async fetchNodeManifest(peerId: string): Promise<SyncRemoteNodeManifest> {\n const value = await this.fetchDataOperation(peerId, 'node-manifest')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError('helper')\n const manifest = value.manifest\n if (manifest.protocolVersion !== SYNC_PROTOCOL_VERSION || typeof manifest.instanceId !== 'string' || !Array.isArray(manifest.records)) {\n throw new SyncSshProbeError('protocol')\n }\n const capabilities = value.capabilities === undefined ? undefined : value.capabilities\n if (capabilities !== undefined && (!Array.isArray(capabilities) || !capabilities.every((item) => typeof item === 'string'))) {\n throw new SyncSshProbeError('protocol')\n }\n return { ...manifest, ...(capabilities ? { capabilities } : {}) } as unknown as SyncRemoteNodeManifest\n }\n\n async applyNodeManifest(\n peerId: string,\n records: SyncNodeManifest['records'],\n sourceName: string,\n ): Promise<SyncNodeApplyResult> {\n const requestBody = Buffer.from(JSON.stringify({ records, sourceName }), 'utf8')\n if (requestBody.length > MAX_SYNC_TRANSFER_BYTES) throw new SyncSshProbeError('protocol')\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'node-apply',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response) throw new SyncProtocolError('unexpected node apply response frame')\n response = frame.value\n }\n if (!response || response.kind === 'error') throw new SyncSshProbeError('helper')\n if (response.kind !== 'response' || response.status !== 200 || response.body === null || typeof response.body !== 'object') {\n throw new SyncSshProbeError('helper')\n }\n const body = response.body as Record<string, unknown>\n if (body.ok !== true || body.result === null || typeof body.result !== 'object') throw new SyncSshProbeError('helper')\n const result = body.result as Record<string, unknown>\n for (const key of ['created', 'updated', 'deleted', 'pendingRemoval', 'skipped'] as const) {\n if (!Number.isSafeInteger(result[key]) || (result[key] as number) < 0) throw new SyncSshProbeError('protocol')\n }\n return result as unknown as SyncNodeApplyResult\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async fetchSessionBundle(peerId: string): Promise<SyncSessionBundle> {\n const value = await this.fetchDataOperation(peerId, 'session-bundle')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError('helper')\n const bundle = value.bundle\n if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== 'string' ||\n !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== 'string') {\n throw new SyncSshProbeError('protocol')\n }\n return bundle as unknown as SyncSessionBundle\n }\n\n async fetchSessionManifest(peerId: string): Promise<SyncSessionManifest> {\n const value = await this.fetchDataOperation(peerId, 'session-manifest')\n if (!isRecord(value) || value.ok !== true || !isRecord(value.manifest)) throw new SyncSshProbeError('helper')\n const manifest = value.manifest\n if (manifest.protocolVersion !== 1 || typeof manifest.instanceId !== 'string' ||\n !Array.isArray(manifest.entries) || !Array.isArray(manifest.skipped) || typeof manifest.digest !== 'string') {\n throw new SyncSshProbeError('protocol')\n }\n return manifest as unknown as SyncSessionManifest\n }\n\n async fetchSessionEntries(\n peerId: string,\n keys: readonly string[],\n manifestDigest: string,\n prefixes: readonly SyncSessionEventPrefix[] = [],\n deferredObjects = false,\n ): Promise<SyncSessionBundle> {\n const value = await this.fetchDataOperation(peerId, 'session-entries', { keys, manifestDigest, prefixes, deferredObjects })\n if (!isRecord(value) || value.ok !== true || !isRecord(value.bundle)) throw new SyncSshProbeError('helper')\n const bundle = value.bundle\n if (bundle.protocolVersion !== 1 || typeof bundle.instanceId !== 'string' ||\n !Array.isArray(bundle.entries) || !Array.isArray(bundle.skipped) || typeof bundle.digest !== 'string') {\n throw new SyncSshProbeError('protocol')\n }\n return bundle as unknown as SyncSessionBundle\n }\n\n async applySessionBundle(peerId: string, bundle: SyncSessionBundle, sourceName: string): Promise<SyncSessionApplyResult> {\n const requestBody = Buffer.from(JSON.stringify({ bundle, sourceName, peerId }), 'utf8')\n if (requestBody.length > MAX_SYNC_TRANSFER_BYTES) throw new SyncSshProbeError('protocol')\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'session-apply',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n for (let offset = 0, sequence = 0; offset < requestBody.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, requestBody.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, requestBody.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response) throw new SyncProtocolError('unexpected session apply response frame')\n response = frame.value\n }\n if (!response || response.kind === 'error') throw new SyncSshProbeError('helper')\n if (response.kind === 'response' && response.status === 409 && isRecord(response.body) &&\n response.body.code === 'session-delta-prefix') throw new SyncSessionDeltaPrefixError()\n if (response.kind !== 'response' || response.status !== 200 || response.body === null || typeof response.body !== 'object') {\n throw new SyncSshProbeError('helper')\n }\n const body = response.body as Record<string, unknown>\n if (body.ok !== true || body.result === null || typeof body.result !== 'object') throw new SyncSshProbeError('helper')\n const result = body.result as Record<string, unknown>\n for (const key of ['created', 'updated', 'archived', 'deleted', 'skipped', 'unmapped'] as const) {\n if (!Number.isSafeInteger(result[key]) || (result[key] as number) < 0) throw new SyncSshProbeError('protocol')\n }\n return result as unknown as SyncSessionApplyResult\n } catch (error) {\n if (error instanceof SyncSessionDeltaPrefixError) throw error\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) throw new SyncSshProbeError('protocol')\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n async trustObservedHostKey(peerId: string): Promise<void> {\n const { peer } = await this.peerManager.connectionData(peerId)\n const fingerprint = this.hostKeyChallenges.acceptObserved(peer)\n await this.peerManager.pinHostKey(peerId, fingerprint)\n }\n\n /**\n * 拉取带外对象流:响应以帧流形式逐块 yield,调用方(收件箱)边收边落盘,\n * 内存 O(单帧)。会话在流结束/出错时关闭(生成器 finally)。\n */\n async *fetchSessionObjects(\n peerId: string,\n objects: readonly SyncSessionObject[],\n ): AsyncGenerator<Uint8Array> {\n const body = Buffer.from(JSON.stringify({ objects: [...objects] }), 'utf8')\n if (body.length > MAX_SYNC_TRANSFER_BYTES) throw new SyncSshProbeError('protocol')\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'session-objects',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n for (let offset = 0, sequence = 0; offset < body.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, body.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, body.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n session.endInput()\n\n let responseStatus: number | undefined\n let bodyMode: string | undefined\n let responseEnded = false\n let totalBytes = 0\n for await (const frame of session.readFrames()) {\n if (frame.kind === 'control') {\n if (frame.value.kind === 'error') throw new SyncSshProbeError('helper')\n if (frame.value.kind === 'response' && responseStatus === undefined) {\n responseStatus = typeof frame.value.status === 'number' ? frame.value.status : undefined\n bodyMode = typeof frame.value.bodyMode === 'string' ? frame.value.bodyMode : undefined\n continue\n }\n if (frame.value.kind === 'response-end' && responseStatus !== undefined && !responseEnded) {\n responseEnded = true\n continue\n }\n throw new SyncProtocolError('unexpected session-objects control frame')\n }\n if (responseStatus === undefined || bodyMode !== 'data' || responseEnded) {\n throw new SyncProtocolError('unexpected session-objects data frame')\n }\n totalBytes += frame.data.length\n if (totalBytes > MAX_SESSION_OBJECT_STREAM_BYTES) {\n throw new SyncProtocolError('session-objects exceeds object stream limit')\n }\n yield frame.data\n }\n if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError('helper')\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) {\n const probe = new SyncSshProbeError('protocol')\n ;(probe as Error & { detail?: string }).detail = error.message\n throw probe\n }\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n /**\n * 推送带外对象流:把帧流按 1MB 分块写入请求体,逐块经 SSH 发送;\n * 对端流式落盘收件箱后返回 JSON 摘要。\n */\n async pushSessionObjects(peerId: string, frames: AsyncIterable<Uint8Array>): Promise<void> {\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'session-objects-push',\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n let sequence = 0\n let totalBytes = 0\n for await (const chunk of frames) {\n if (!(chunk instanceof Uint8Array)) throw new SyncProtocolError('session-objects-push contains a non-byte chunk')\n const data = Buffer.from(chunk)\n totalBytes += data.byteLength\n if (totalBytes > MAX_SESSION_OBJECT_STREAM_BYTES) {\n throw new SyncProtocolError('session-objects-push exceeds object stream limit')\n }\n for (let offset = 0; offset < data.byteLength; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, data.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, data.byteLength)))\n }\n }\n await session.sendControl({ kind: 'request-end' })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response !== undefined) throw new SyncProtocolError('unexpected session-objects-push frame')\n if (frame.value.kind === 'error') throw new SyncSshProbeError('helper')\n if (frame.value.kind === 'response') response = frame.value\n }\n if (!response || response.status !== 200) throw new SyncSshProbeError('helper')\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) {\n const probe = new SyncSshProbeError('protocol')\n ;(probe as Error & { detail?: string }).detail = error.message\n throw probe\n }\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n private async fetchDataOperation(\n peerId: string,\n operation: 'node-manifest' | 'session-bundle' | 'session-manifest' | 'session-entries',\n requestBody?: Record<string, unknown>,\n ): Promise<unknown> {\n const body = requestBody === undefined ? undefined : Buffer.from(JSON.stringify(requestBody), 'utf8')\n if (body && body.length > MAX_SYNC_TRANSFER_BYTES) throw new SyncSshProbeError('protocol')\n const { peer, session } = await this.openSession(peerId)\n try {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation,\n port: peer.remoteWebPort ?? DEFAULT_REMOTE_WEB_PORT,\n })\n if (body) {\n for (let offset = 0, sequence = 0; offset < body.length; offset += MAX_SYNC_DATA_CHUNK_BYTES, sequence += 1) {\n await session.sendData(sequence, body.subarray(offset, Math.min(offset + MAX_SYNC_DATA_CHUNK_BYTES, body.length)))\n }\n await session.sendControl({ kind: 'request-end' })\n }\n session.endInput()\n\n let responseStatus: number | undefined\n let bodyMode: string | undefined\n let responseEnded = false\n let totalBytes = 0\n const chunks: Buffer[] = []\n for await (const frame of session.readFrames()) {\n if (frame.kind === 'control') {\n if (frame.value.kind === 'error') throw new SyncSshProbeError('helper')\n if (frame.value.kind === 'response' && responseStatus === undefined) {\n responseStatus = typeof frame.value.status === 'number' ? frame.value.status : undefined\n bodyMode = typeof frame.value.bodyMode === 'string' ? frame.value.bodyMode : undefined\n continue\n }\n if (frame.value.kind === 'response-end' && responseStatus !== undefined && !responseEnded) {\n responseEnded = true\n continue\n }\n throw new SyncProtocolError(`unexpected ${operation} control frame`)\n }\n if (responseStatus === undefined || bodyMode !== 'data' || responseEnded) {\n throw new SyncProtocolError(`unexpected ${operation} data frame`)\n }\n totalBytes += frame.data.length\n if (totalBytes > MAX_SYNC_TRANSFER_BYTES) throw new SyncProtocolError(`${operation} exceeds transfer limit`)\n chunks.push(frame.data)\n }\n if (responseStatus !== 200 || !responseEnded) throw new SyncSshProbeError('helper')\n try {\n return JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8')) as unknown\n } catch {\n throw new SyncProtocolError(`${operation} response is invalid JSON`)\n }\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n if (error instanceof SyncProtocolError) {\n const probe = new SyncSshProbeError('protocol')\n ;(probe as Error & { detail?: string }).detail = error.message\n throw probe\n }\n throw new SyncSshProbeError('helper')\n } finally {\n session.close()\n }\n }\n\n private async requestHandshake(session: SyncSshSession, port: number): Promise<SyncRemoteHandshake> {\n await session.sendControl({\n kind: 'request',\n protocolVersion: SYNC_PROTOCOL_VERSION,\n operation: 'handshake',\n port,\n })\n session.endInput()\n\n let response: Record<string, unknown> | undefined\n for await (const frame of session.readFrames()) {\n if (frame.kind !== 'control' || response) throw new SyncProtocolError('unexpected helper frame')\n response = frame.value\n }\n if (!response) throw new SyncProtocolError('helper returned no handshake response')\n return validateHandshakeResponse(response)\n }\n}\n\nfunction validateHandshakeResponse(value: Record<string, unknown>): SyncRemoteHandshake {\n if (value.kind === 'error') throw new SyncSshProbeError('helper')\n if (value.kind !== 'response' || value.status !== 200 || value.body === null || typeof value.body !== 'object') {\n throw new SyncSshProbeError('helper')\n }\n const body = value.body as Record<string, unknown>\n if (\n typeof body.protocolVersion !== 'number' ||\n typeof body.instanceId !== 'string' ||\n !Array.isArray(body.capabilities) ||\n !body.capabilities.every((capability) => typeof capability === 'string')\n ) {\n throw new SyncSshProbeError('protocol')\n }\n return {\n protocolVersion: body.protocolVersion,\n instanceId: body.instanceId,\n capabilities: body.capabilities,\n }\n}\n\n//#region 远端配置探测(SSH 登录后自动发现 DSH_HOME / profile / Web 端口)\n\nexport interface SyncRemoteProbeDraft {\n host: string\n port: number\n username: string\n authKind: 'password' | 'key'\n}\n\nexport interface SyncRemoteProbeProfile {\n name: string\n dshbInstalled: boolean\n home: string\n}\n\nexport interface SyncRemoteProbeHandshake {\n port: number\n instanceId: string\n}\n\nexport interface SyncRemoteProbeResult {\n homes: string[]\n profiles: SyncRemoteProbeProfile[]\n /** 握手命中的全部端口(同机多实例时可能有多个;配置端口的命中排最前)。 */\n handshakes: SyncRemoteProbeHandshake[]\n webPort?: number\n instanceId?: string\n processHints: string[]\n hostKeyFingerprint: string\n}\n\nexport const REMOTE_PROBE_BEGIN_MARKER = 'DSHB_PROBE_BEGIN'\nexport const REMOTE_PROBE_END_MARKER = 'DSHB_PROBE_END'\nconst REMOTE_PROBE_TIMEOUT_MS = 12_000\nconst REMOTE_PROBE_MAX_OUTPUT_BYTES = 256 * 1024\n\n/**\n * 生成远端发现脚本:纯 POSIX sh + 远端 node(DSH 所在机器必然有 node)。\n * 三个动作:① 多候选定位 DSH_HOME(含桌面版常见路径);② 列出 profiles 及\n * dshb 安装标记;③ 握手探测真实 Web 端口(仅远端回环,桌面版 127.0.0.1:3080\n * 即可命中)。输出以 BEGIN/END 标记包裹,逐行 \"key value\"。\n */\nexport function buildRemoteProbeScript(dshHome?: string, preferredWebPort?: number): string {\n const homeOverride = dshHome === undefined ? '' : JSON.stringify(dshHome)\n const homeCandidates = (homeOverride ? ` ${homeOverride}` : '')\n + ' \"$DSH_HOME\" \"$HOME/.dsh\" \"$HOME/Library/Application Support/dsh\"'\n + ' \"$HOME/Library/Application Support/DeepSeek Harness\"'\n return [\n `echo ${REMOTE_PROBE_BEGIN_MARKER}`,\n `for c in${homeCandidates}; do`,\n ' [ -n \"$c\" ] && [ -d \"$c/profiles\" ] && echo \"home $c\"',\n 'done',\n `for c in${homeCandidates}; do`,\n ' [ -n \"$c\" ] && [ -d \"$c/profiles\" ] || continue',\n ' for p in \"$c\"/profiles/*/; do',\n ' [ -d \"$p\" ] || continue',\n ' b=no; [ -d \"${p}node_modules/@artenx/dshb\" ] && b=yes',\n ' [ -f \"${p}package.json\" ] || continue',\n ' echo \"profile $(basename \"$p\") $b $c\"',\n ' done',\n 'done',\n 'ps -eo args= 2>/dev/null | grep -E \"dsh .*--profile|--profile .*--port|dsh web\" | grep -v grep | head -6',\n `node -e '`\n + 'const http=require(\"http\");'\n + 'const ports=process.argv.slice(1).map(Number).filter(Number.isInteger);'\n + 'let i=0;'\n + 'function probe(){'\n + 'if(i>=ports.length)process.exit(0);'\n + 'const port=ports[i++];'\n + 'const req=http.get({host:\"127.0.0.1\",port,path:\"/api/dshb/sync/internal/handshake\",timeout:700},res=>{'\n + 'let body=\"\";res.on(\"data\",c=>body+=c);res.on(\"end\",()=>{'\n + 'try{const j=JSON.parse(body);if(j&&j.ok===true&&typeof j.instanceId===\"string\")'\n + '{console.log(\"handshake \"+port+\" \"+j.instanceId)}}catch(e){}probe()})});'\n + 'req.on(\"timeout\",()=>{req.destroy();probe()});'\n + 'req.on(\"error\",()=>probe());'\n + '}probe()'\n + \"' \"\n + (preferredWebPort !== undefined ? ` ${preferredWebPort}` : '')\n + ' 3080 8080 3079 3204 3206 3207 9080'\n + ` $(ps -eo args= 2>/dev/null | grep -o \"\\\\-\\\\-port [0-9]\\\\+\" | awk '{print $2}' | head -6)`,\n `echo ${REMOTE_PROBE_END_MARKER}`,\n ].join('\\n')\n}\n\n/** 解析探测输出:仅在 BEGIN/END 标记之间取行,容忍未知行。 */\nexport function parseRemoteProbeOutput(output: string, hostKeyFingerprint: string, preferredWebPort?: number): SyncRemoteProbeResult {\n const begin = output.indexOf(REMOTE_PROBE_BEGIN_MARKER)\n const end = output.indexOf(REMOTE_PROBE_END_MARKER)\n if (begin < 0 || end < begin) throw new SyncSshProbeError('helper')\n const lines = output.slice(begin + REMOTE_PROBE_BEGIN_MARKER.length, end).split('\\n')\n .map((line) => line.trim()).filter(Boolean)\n const result: SyncRemoteProbeResult = { homes: [], profiles: [], handshakes: [], processHints: [], hostKeyFingerprint }\n for (const line of lines) {\n const [head, ...rest] = line.split(/\\s+/)\n const value = rest.join(' ')\n if (head === 'home' && value) result.homes.push(value)\n else if (head === 'profile' && rest.length >= 3) {\n const [name, dshb, home] = rest\n result.profiles.push({ name, dshbInstalled: dshb === 'yes', home })\n } else if (head === 'handshake' && rest.length >= 2) {\n const port = Number(rest[0])\n if (Number.isInteger(port) && port >= 1 && port <= 65535\n && !result.handshakes.some((hit) => hit.port === port)) {\n result.handshakes.push({ port, instanceId: rest[1] })\n }\n } else if (line && !line.startsWith('home ') && !line.startsWith('profile ') && !line.startsWith('handshake ')) {\n result.processHints.push(line)\n }\n }\n result.homes = [...new Set(result.homes)]\n result.profiles = result.profiles.filter((profile, index, all) =>\n all.findIndex((candidate) => candidate.name === profile.name && candidate.home === profile.home) === index)\n // 首选端口(已保存对端的配置值)的命中作为自动回填值;其余命中仅提示。\n const preferred = result.handshakes.find((hit) => hit.port === preferredWebPort)\n ?? result.handshakes[0]\n if (preferred) {\n result.webPort = preferred.port\n result.instanceId = preferred.instanceId\n }\n return result\n}\n\nexport class SyncRemoteProbe {\n /** 探测远端:SSH 登录 + 单次 exec 发现脚本;主机密钥自动接受(只读发现,指纹随结果返回)。 */\n async probeRemote(draft: SyncRemoteProbeDraft, secrets: SyncPeerSecrets, preferredWebPort?: number): Promise<SyncRemoteProbeResult> {\n if (draft.authKind === 'password' && !secrets.password) throw new SyncSshProbeError('auth')\n if (draft.authKind === 'key' && !secrets.privateKey) throw new SyncSshProbeError('auth')\n let fingerprint = ''\n const client = new Client()\n try {\n await new Promise<void>((resolve, reject) => {\n let settled = false\n const finish = (error?: unknown): void => {\n if (settled) return\n settled = true\n clearTimeout(deadline)\n if (error) reject(error)\n else resolve()\n }\n const deadline = setTimeout(() => {\n const timeout = new Error('Timed out while waiting for SSH handshake')\n ;(timeout as Error & { level?: string }).level = 'client-timeout'\n finish(timeout)\n client.end()\n }, SSH_READY_TIMEOUT_MS)\n client.once('ready', () => finish())\n client.on('error', (error: Error) => finish(error))\n const config: ConnectConfig = {\n host: draft.host,\n port: draft.port,\n username: draft.username,\n hostHash: 'sha256',\n readyTimeout: SSH_READY_TIMEOUT_MS,\n // 只读探测:自动接受指纹并记录,随结果返回供 UI 展示;\n // 保存对端后的真实同步仍走 run/trust-host-key 的显式信任流程。\n hostVerifier: (sha256Hex: string): boolean => {\n fingerprint = sha256Hex\n return true\n },\n ...(draft.authKind === 'password'\n ? { password: secrets.password }\n : { privateKey: secrets.privateKey, ...(secrets.passphrase ? { passphrase: secrets.passphrase } : {}) }),\n }\n try {\n client.connect(config)\n } catch (error) {\n finish(error)\n }\n })\n const stdout = await new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n let settled = false\n const finish = (error?: unknown, text?: string): void => {\n if (settled) return\n settled = true\n clearTimeout(deadline)\n if (error) reject(error)\n else resolve(text ?? '')\n }\n const deadline = setTimeout(() => finish(new Error('Timed out while waiting for the remote probe script')), REMOTE_PROBE_TIMEOUT_MS)\n client.exec(buildRemoteProbeScript(undefined, preferredWebPort), (error, channel) => {\n if (error) { finish(error); return }\n channel.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > REMOTE_PROBE_MAX_OUTPUT_BYTES) { finish(new Error('remote probe output exceeds size limit')); return }\n chunks.push(chunk)\n })\n channel.on('error', (error: Error) => finish(error))\n channel.on('close', (code: number | null) => {\n const text = Buffer.concat(chunks).toString('utf8')\n if (code !== 0 && !text.includes(REMOTE_PROBE_END_MARKER)) finish(new Error(`remote probe script exited with ${code}`))\n else finish(undefined, text)\n })\n channel.stderr.resume()\n })\n })\n return parseRemoteProbeOutput(stdout, fingerprint, preferredWebPort)\n } catch (error) {\n if (error instanceof SyncSshProbeError) throw error\n const category = classifySshError(error)\n if (category === 'auth' || category === 'network' || category === 'timeout') throw new SyncSshProbeError(category)\n if (category === 'hostkey') throw new SyncSshProbeError('hostkey')\n throw new SyncSshProbeError('helper')\n } finally {\n try {\n client.end()\n } catch {}\n }\n }\n}\n"],"mappings":"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"}
@@ -3,9 +3,12 @@ declare const SYNC_PROTOCOL_VERSION = 1;
3
3
  declare const SYNC_SESSION_MANIFEST_CAPABILITY = "session-manifest-v1";
4
4
  declare const SYNC_SESSION_EVENT_TAIL_CAPABILITY = "session-event-tail-delta-v1";
5
5
  declare const SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY = "session-bundle-objects-v1";
6
+ declare const SYNC_SESSION_OBJECT_STREAM_CAPABILITY = "session-object-stream-v1";
6
7
  declare const MAX_SYNC_CONTROL_FRAME_BYTES: number;
7
8
  declare const MAX_SYNC_DATA_CHUNK_BYTES: number;
8
9
  declare const MAX_SYNC_TRANSFER_BYTES: number;
10
+ declare const MAX_SESSION_OBJECT_INLINE_BYTES: number;
11
+ declare const MAX_SESSION_OBJECT_STREAM_BYTES: number;
9
12
  type SyncFrame = {
10
13
  kind: 'control';
11
14
  value: Record<string, unknown>;
@@ -34,5 +37,5 @@ declare class SyncFrameDecoder {
34
37
  private resetFrame;
35
38
  }
36
39
  //#endregion
37
- export { SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY as a, SyncFrame as c, encodeSyncControlFrame as d, encodeSyncDataFrame as f, SYNC_PROTOCOL_VERSION as i, SyncFrameDecoder as l, MAX_SYNC_DATA_CHUNK_BYTES as n, SYNC_SESSION_EVENT_TAIL_CAPABILITY as o, MAX_SYNC_TRANSFER_BYTES as r, SYNC_SESSION_MANIFEST_CAPABILITY as s, MAX_SYNC_CONTROL_FRAME_BYTES as t, SyncProtocolError as u };
38
- //# sourceMappingURL=protocol-KaWZArY8.d.ts.map
40
+ export { MAX_SYNC_TRANSFER_BYTES as a, SYNC_SESSION_EVENT_TAIL_CAPABILITY as c, SyncFrame as d, SyncFrameDecoder as f, encodeSyncDataFrame as h, MAX_SYNC_DATA_CHUNK_BYTES as i, SYNC_SESSION_MANIFEST_CAPABILITY as l, encodeSyncControlFrame as m, MAX_SESSION_OBJECT_STREAM_BYTES as n, SYNC_PROTOCOL_VERSION as o, SyncProtocolError as p, MAX_SYNC_CONTROL_FRAME_BYTES as r, SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY as s, MAX_SESSION_OBJECT_INLINE_BYTES as t, SYNC_SESSION_OBJECT_STREAM_CAPABILITY as u };
41
+ //# sourceMappingURL=protocol-DJ0zyVlB.d.ts.map