@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
@@ -1,4 +1,4 @@
1
- import { n as SyncSessionError } from "../../session-sync-BQt3HXkp.js";
1
+ import { n as SyncSessionError } from "../../session-sync-CIqjuWkh.js";
2
2
  import { SyncOfflineArchiveError } from "./offline-archive.js";
3
3
  import { createReadStream } from "node:fs";
4
4
  import { randomBytes } from "node:crypto";
@@ -1,2 +1,2 @@
1
- import { a as SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY, c as SyncFrame, d as encodeSyncControlFrame, f as encodeSyncDataFrame, i as SYNC_PROTOCOL_VERSION, l as SyncFrameDecoder, n as MAX_SYNC_DATA_CHUNK_BYTES, o as SYNC_SESSION_EVENT_TAIL_CAPABILITY, r as MAX_SYNC_TRANSFER_BYTES, s as SYNC_SESSION_MANIFEST_CAPABILITY, t as MAX_SYNC_CONTROL_FRAME_BYTES, u as SyncProtocolError } from "../../protocol-KaWZArY8.js";
2
- export { MAX_SYNC_CONTROL_FRAME_BYTES, MAX_SYNC_DATA_CHUNK_BYTES, MAX_SYNC_TRANSFER_BYTES, SYNC_PROTOCOL_VERSION, SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY, SYNC_SESSION_EVENT_TAIL_CAPABILITY, SYNC_SESSION_MANIFEST_CAPABILITY, SyncFrame, SyncFrameDecoder, SyncProtocolError, encodeSyncControlFrame, encodeSyncDataFrame };
1
+ import { a as MAX_SYNC_TRANSFER_BYTES, c as SYNC_SESSION_EVENT_TAIL_CAPABILITY, d as SyncFrame, f as SyncFrameDecoder, h as encodeSyncDataFrame, i as MAX_SYNC_DATA_CHUNK_BYTES, l as SYNC_SESSION_MANIFEST_CAPABILITY, m as encodeSyncControlFrame, n as MAX_SESSION_OBJECT_STREAM_BYTES, o as SYNC_PROTOCOL_VERSION, p as SyncProtocolError, r as MAX_SYNC_CONTROL_FRAME_BYTES, s as SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY, t as MAX_SESSION_OBJECT_INLINE_BYTES, u as SYNC_SESSION_OBJECT_STREAM_CAPABILITY } from "../../protocol-DJ0zyVlB.js";
2
+ export { MAX_SESSION_OBJECT_INLINE_BYTES, MAX_SESSION_OBJECT_STREAM_BYTES, MAX_SYNC_CONTROL_FRAME_BYTES, MAX_SYNC_DATA_CHUNK_BYTES, MAX_SYNC_TRANSFER_BYTES, SYNC_PROTOCOL_VERSION, SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY, SYNC_SESSION_EVENT_TAIL_CAPABILITY, SYNC_SESSION_MANIFEST_CAPABILITY, SYNC_SESSION_OBJECT_STREAM_CAPABILITY, SyncFrame, SyncFrameDecoder, SyncProtocolError, encodeSyncControlFrame, encodeSyncDataFrame };
@@ -4,9 +4,12 @@ const SYNC_PROTOCOL_VERSION = 1;
4
4
  const SYNC_SESSION_MANIFEST_CAPABILITY = "session-manifest-v1";
5
5
  const SYNC_SESSION_EVENT_TAIL_CAPABILITY = "session-event-tail-delta-v1";
6
6
  const SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY = "session-bundle-objects-v1";
7
+ const SYNC_SESSION_OBJECT_STREAM_CAPABILITY = "session-object-stream-v1";
7
8
  const MAX_SYNC_CONTROL_FRAME_BYTES = 65536;
8
9
  const MAX_SYNC_DATA_CHUNK_BYTES = 1048576;
9
10
  const MAX_SYNC_TRANSFER_BYTES = 134217728;
11
+ const MAX_SESSION_OBJECT_INLINE_BYTES = 262144;
12
+ const MAX_SESSION_OBJECT_STREAM_BYTES = 68719476736;
10
13
  const FRAME_HEADER_BYTES = 5;
11
14
  const CONTROL_FRAME = 0;
12
15
  const DATA_FRAME = 1;
@@ -122,6 +125,6 @@ var SyncFrameDecoder = class {
122
125
  }
123
126
  };
124
127
  //#endregion
125
- export { MAX_SYNC_CONTROL_FRAME_BYTES, MAX_SYNC_DATA_CHUNK_BYTES, MAX_SYNC_TRANSFER_BYTES, SYNC_PROTOCOL_VERSION, SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY, SYNC_SESSION_EVENT_TAIL_CAPABILITY, SYNC_SESSION_MANIFEST_CAPABILITY, SyncFrameDecoder, SyncProtocolError, encodeSyncControlFrame, encodeSyncDataFrame };
128
+ export { MAX_SESSION_OBJECT_INLINE_BYTES, MAX_SESSION_OBJECT_STREAM_BYTES, MAX_SYNC_CONTROL_FRAME_BYTES, MAX_SYNC_DATA_CHUNK_BYTES, MAX_SYNC_TRANSFER_BYTES, SYNC_PROTOCOL_VERSION, SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY, SYNC_SESSION_EVENT_TAIL_CAPABILITY, SYNC_SESSION_MANIFEST_CAPABILITY, SYNC_SESSION_OBJECT_STREAM_CAPABILITY, SyncFrameDecoder, SyncProtocolError, encodeSyncControlFrame, encodeSyncDataFrame };
126
129
 
127
130
  //# sourceMappingURL=protocol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","names":[],"sources":["../../../src/core/sync/protocol.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto'\n\nexport const SYNC_PROTOCOL_VERSION = 1\nexport const SYNC_SESSION_MANIFEST_CAPABILITY = 'session-manifest-v1'\nexport const SYNC_SESSION_EVENT_TAIL_CAPABILITY = 'session-event-tail-delta-v1'\nexport const SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY = 'session-bundle-objects-v1'\nexport const MAX_SYNC_CONTROL_FRAME_BYTES = 64 * 1024\nexport const MAX_SYNC_DATA_CHUNK_BYTES = 1024 * 1024\n// 单次传输(bundle 请求/响应、原始附件/溢出流)的硬上限。必须远小于宿主进程\n// JS 堆(桌面端约 1.4GB):JSON.parse 的对象图膨胀约为 JSON 文本的 5-10 倍,\n// 100GB 级上限等于无内存保护。协调器按更小的目标分批传输(见 coordinator.ts)。\nexport const MAX_SYNC_TRANSFER_BYTES = 128 * 1024 * 1024\n\nconst FRAME_HEADER_BYTES = 5\nconst CONTROL_FRAME = 0\nconst DATA_FRAME = 1\nconst DATA_METADATA_BYTES = 36\n\nexport type SyncFrame =\n | { kind: 'control'; value: Record<string, unknown> }\n | { kind: 'data'; sequence: number; digest: string; data: Buffer }\n\nexport class SyncProtocolError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SyncProtocolError'\n }\n}\n\nfunction makeHeader(type: number, payloadLength: number): Buffer {\n const header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n header[0] = type\n header.writeUInt32BE(payloadLength, 1)\n return header\n}\n\nexport function encodeSyncControlFrame(value: Record<string, unknown>): Buffer {\n const payload = Buffer.from(`${JSON.stringify(value)}\\n`, 'utf8')\n if (payload.length > MAX_SYNC_CONTROL_FRAME_BYTES) throw new SyncProtocolError('control frame exceeds size limit')\n return Buffer.concat([makeHeader(CONTROL_FRAME, payload.length), payload])\n}\n\nexport function encodeSyncDataFrame(sequence: number, data: Buffer): Buffer {\n if (!Number.isSafeInteger(sequence) || sequence < 0 || sequence > 0xffffffff) {\n throw new SyncProtocolError('data sequence is invalid')\n }\n if (data.length > MAX_SYNC_DATA_CHUNK_BYTES) throw new SyncProtocolError('data chunk exceeds size limit')\n const metadata = Buffer.allocUnsafe(DATA_METADATA_BYTES)\n metadata.writeUInt32BE(sequence, 0)\n createHash('sha256').update(data).digest().copy(metadata, 4)\n return Buffer.concat([makeHeader(DATA_FRAME, metadata.length + data.length), metadata, data])\n}\n\nexport class SyncFrameDecoder {\n private readonly header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n private headerOffset = 0\n private frameType = -1\n private payload: Buffer | undefined\n private payloadOffset = 0\n private nextDataSequence = 0\n private ended = false\n\n push(chunk: Buffer): SyncFrame[] {\n if (this.ended) throw new SyncProtocolError('frame decoder is already finished')\n const frames: SyncFrame[] = []\n let inputOffset = 0\n\n while (inputOffset < chunk.length) {\n if (!this.payload) {\n const copied = Math.min(FRAME_HEADER_BYTES - this.headerOffset, chunk.length - inputOffset)\n chunk.copy(this.header, this.headerOffset, inputOffset, inputOffset + copied)\n this.headerOffset += copied\n inputOffset += copied\n if (this.headerOffset < FRAME_HEADER_BYTES) continue\n\n this.frameType = this.header[0]\n const payloadLength = this.header.readUInt32BE(1)\n if (this.frameType === CONTROL_FRAME) {\n if (payloadLength < 2 || payloadLength > MAX_SYNC_CONTROL_FRAME_BYTES) {\n throw new SyncProtocolError('control frame length is invalid')\n }\n } else if (this.frameType === DATA_FRAME) {\n if (payloadLength < DATA_METADATA_BYTES || payloadLength > DATA_METADATA_BYTES + MAX_SYNC_DATA_CHUNK_BYTES) {\n throw new SyncProtocolError('data frame length is invalid')\n }\n } else {\n throw new SyncProtocolError('unknown frame type')\n }\n this.payload = Buffer.allocUnsafe(payloadLength)\n this.payloadOffset = 0\n }\n\n const payload = this.payload\n const copied = Math.min(payload.length - this.payloadOffset, chunk.length - inputOffset)\n chunk.copy(payload, this.payloadOffset, inputOffset, inputOffset + copied)\n this.payloadOffset += copied\n inputOffset += copied\n\n if (this.payloadOffset === payload.length) {\n frames.push(this.decodeFrame(this.frameType, payload))\n this.resetFrame()\n }\n }\n return frames\n }\n\n finish(): void {\n if (this.headerOffset !== 0 || this.payload !== undefined) throw new SyncProtocolError('truncated frame stream')\n this.ended = true\n }\n\n private decodeFrame(type: number, payload: Buffer): SyncFrame {\n if (type === CONTROL_FRAME) {\n if (payload[payload.length - 1] !== 0x0a) throw new SyncProtocolError('control frame must end with a newline')\n let value: unknown\n try {\n value = JSON.parse(payload.subarray(0, -1).toString('utf8'))\n } catch {\n throw new SyncProtocolError('control frame contains invalid JSON')\n }\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new SyncProtocolError('control frame must contain an object')\n }\n return { kind: 'control', value: value as Record<string, unknown> }\n }\n\n const sequence = payload.readUInt32BE(0)\n if (sequence !== this.nextDataSequence) throw new SyncProtocolError('data sequence is out of order')\n const expectedDigest = payload.subarray(4, DATA_METADATA_BYTES)\n const data = payload.subarray(DATA_METADATA_BYTES)\n const actualDigest = createHash('sha256').update(data).digest()\n if (!timingSafeEqual(expectedDigest, actualDigest)) throw new SyncProtocolError('data chunk digest mismatch')\n this.nextDataSequence += 1\n return { kind: 'data', sequence, digest: actualDigest.toString('hex'), data: Buffer.from(data) }\n }\n\n private resetFrame(): void {\n this.headerOffset = 0\n this.frameType = -1\n this.payload = undefined\n this.payloadOffset = 0\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"protocol.js","names":[],"sources":["../../../src/core/sync/protocol.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto'\n\nexport const SYNC_PROTOCOL_VERSION = 1\nexport const SYNC_SESSION_MANIFEST_CAPABILITY = 'session-manifest-v1'\nexport const SYNC_SESSION_EVENT_TAIL_CAPABILITY = 'session-event-tail-delta-v1'\nexport const SYNC_SESSION_BUNDLE_OBJECTS_CAPABILITY = 'session-bundle-objects-v1'\n// 对象带外流:附件/spill 负载不再内联进 bundle JSON,而是通过专用 RPC 以\n// 磁盘到磁盘的帧流传输(内存占用 O(单帧)),单对象与会话引用总量不再受\n// JSON 传输上限约束。两端都声明该能力时才启用,旧版本对端回退内联路径。\nexport const SYNC_SESSION_OBJECT_STREAM_CAPABILITY = 'session-object-stream-v1'\nexport const MAX_SYNC_CONTROL_FRAME_BYTES = 64 * 1024\nexport const MAX_SYNC_DATA_CHUNK_BYTES = 1024 * 1024\n// 单次传输(bundle 请求/响应、原始附件/溢出流)的硬上限。必须远小于宿主进程\n// JS 堆(桌面端约 1.4GB):JSON.parse 的对象图膨胀约为 JSON 文本的 5-10 倍,\n// 100GB 级上限等于无内存保护。协调器按更小的目标分批传输(见 coordinator.ts)。\n// 注意:该上限只约束 JSON bundle;对象带外流是磁盘流式传输,走独立上限。\nexport const MAX_SYNC_TRANSFER_BYTES = 128 * 1024 * 1024\n// 对象内联进 bundle JSON 的字节阈值:小对象内联(减少往返),大对象走带外流。\nexport const MAX_SESSION_OBJECT_INLINE_BYTES = 256 * 1024\n// 单个带外对象流的字节上限:磁盘到磁盘传输,内存不随体积增长,上限只为\n// 防失控对端无限发送。64GB 覆盖一切真实会话负载。\nexport const MAX_SESSION_OBJECT_STREAM_BYTES = 64 * 1024 * 1024 * 1024\n\nconst FRAME_HEADER_BYTES = 5\nconst CONTROL_FRAME = 0\nconst DATA_FRAME = 1\nconst DATA_METADATA_BYTES = 36\n\nexport type SyncFrame =\n | { kind: 'control'; value: Record<string, unknown> }\n | { kind: 'data'; sequence: number; digest: string; data: Buffer }\n\nexport class SyncProtocolError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'SyncProtocolError'\n }\n}\n\nfunction makeHeader(type: number, payloadLength: number): Buffer {\n const header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n header[0] = type\n header.writeUInt32BE(payloadLength, 1)\n return header\n}\n\nexport function encodeSyncControlFrame(value: Record<string, unknown>): Buffer {\n const payload = Buffer.from(`${JSON.stringify(value)}\\n`, 'utf8')\n if (payload.length > MAX_SYNC_CONTROL_FRAME_BYTES) throw new SyncProtocolError('control frame exceeds size limit')\n return Buffer.concat([makeHeader(CONTROL_FRAME, payload.length), payload])\n}\n\nexport function encodeSyncDataFrame(sequence: number, data: Buffer): Buffer {\n if (!Number.isSafeInteger(sequence) || sequence < 0 || sequence > 0xffffffff) {\n throw new SyncProtocolError('data sequence is invalid')\n }\n if (data.length > MAX_SYNC_DATA_CHUNK_BYTES) throw new SyncProtocolError('data chunk exceeds size limit')\n const metadata = Buffer.allocUnsafe(DATA_METADATA_BYTES)\n metadata.writeUInt32BE(sequence, 0)\n createHash('sha256').update(data).digest().copy(metadata, 4)\n return Buffer.concat([makeHeader(DATA_FRAME, metadata.length + data.length), metadata, data])\n}\n\nexport class SyncFrameDecoder {\n private readonly header = Buffer.allocUnsafe(FRAME_HEADER_BYTES)\n private headerOffset = 0\n private frameType = -1\n private payload: Buffer | undefined\n private payloadOffset = 0\n private nextDataSequence = 0\n private ended = false\n\n push(chunk: Buffer): SyncFrame[] {\n if (this.ended) throw new SyncProtocolError('frame decoder is already finished')\n const frames: SyncFrame[] = []\n let inputOffset = 0\n\n while (inputOffset < chunk.length) {\n if (!this.payload) {\n const copied = Math.min(FRAME_HEADER_BYTES - this.headerOffset, chunk.length - inputOffset)\n chunk.copy(this.header, this.headerOffset, inputOffset, inputOffset + copied)\n this.headerOffset += copied\n inputOffset += copied\n if (this.headerOffset < FRAME_HEADER_BYTES) continue\n\n this.frameType = this.header[0]\n const payloadLength = this.header.readUInt32BE(1)\n if (this.frameType === CONTROL_FRAME) {\n if (payloadLength < 2 || payloadLength > MAX_SYNC_CONTROL_FRAME_BYTES) {\n throw new SyncProtocolError('control frame length is invalid')\n }\n } else if (this.frameType === DATA_FRAME) {\n if (payloadLength < DATA_METADATA_BYTES || payloadLength > DATA_METADATA_BYTES + MAX_SYNC_DATA_CHUNK_BYTES) {\n throw new SyncProtocolError('data frame length is invalid')\n }\n } else {\n throw new SyncProtocolError('unknown frame type')\n }\n this.payload = Buffer.allocUnsafe(payloadLength)\n this.payloadOffset = 0\n }\n\n const payload = this.payload\n const copied = Math.min(payload.length - this.payloadOffset, chunk.length - inputOffset)\n chunk.copy(payload, this.payloadOffset, inputOffset, inputOffset + copied)\n this.payloadOffset += copied\n inputOffset += copied\n\n if (this.payloadOffset === payload.length) {\n frames.push(this.decodeFrame(this.frameType, payload))\n this.resetFrame()\n }\n }\n return frames\n }\n\n finish(): void {\n if (this.headerOffset !== 0 || this.payload !== undefined) throw new SyncProtocolError('truncated frame stream')\n this.ended = true\n }\n\n private decodeFrame(type: number, payload: Buffer): SyncFrame {\n if (type === CONTROL_FRAME) {\n if (payload[payload.length - 1] !== 0x0a) throw new SyncProtocolError('control frame must end with a newline')\n let value: unknown\n try {\n value = JSON.parse(payload.subarray(0, -1).toString('utf8'))\n } catch {\n throw new SyncProtocolError('control frame contains invalid JSON')\n }\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n throw new SyncProtocolError('control frame must contain an object')\n }\n return { kind: 'control', value: value as Record<string, unknown> }\n }\n\n const sequence = payload.readUInt32BE(0)\n if (sequence !== this.nextDataSequence) throw new SyncProtocolError('data sequence is out of order')\n const expectedDigest = payload.subarray(4, DATA_METADATA_BYTES)\n const data = payload.subarray(DATA_METADATA_BYTES)\n const actualDigest = createHash('sha256').update(data).digest()\n if (!timingSafeEqual(expectedDigest, actualDigest)) throw new SyncProtocolError('data chunk digest mismatch')\n this.nextDataSequence += 1\n return { kind: 'data', sequence, digest: actualDigest.toString('hex'), data: Buffer.from(data) }\n }\n\n private resetFrame(): void {\n this.headerOffset = 0\n this.frameType = -1\n this.payload = undefined\n this.payloadOffset = 0\n }\n}\n"],"mappings":"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"}
@@ -5,6 +5,7 @@ const { createHash, timingSafeEqual } = require('node:crypto')
5
5
  const MAX_CONTROL = 65536
6
6
  const MAX_CHUNK = 1048576
7
7
  const MAX_TRANSFER = 107374182400
8
+ const OBJECT_STREAM_LIMIT = 68719476736
8
9
  const PATHS = {
9
10
  handshake: { path: '/api/dshb/sync/internal/handshake', method: 'GET' },
10
11
  'node-manifest': { path: '/api/dshb/sync/internal/nodes/manifest', method: 'GET' },
@@ -13,9 +14,11 @@ const PATHS = {
13
14
  'session-manifest': { path: '/api/dshb/sync/internal/sessions/manifest', method: 'GET' },
14
15
  'session-entries': { path: '/api/dshb/sync/internal/sessions/entries', method: 'POST' },
15
16
  'session-apply': { path: '/api/dshb/sync/internal/sessions/apply', method: 'POST' },
17
+ 'session-objects': { path: '/api/dshb/sync/internal/sessions/objects', method: 'POST' },
18
+ 'session-objects-push': { path: '/api/dshb/sync/internal/sessions/objects', method: 'POST' },
16
19
  }
17
- const input = []
18
- let inputBytes = 0
20
+ const BUFFERED_BODY_OPERATIONS = ['node-apply', 'session-entries', 'session-apply', 'session-objects']
21
+ const STREAMING_RESPONSE_OPERATIONS = ['node-manifest', 'session-bundle', 'session-manifest', 'session-entries', 'session-objects']
19
22
 
20
23
  function encodeControl(value) {
21
24
  const body = Buffer.from(JSON.stringify(value) + '\n', 'utf8')
@@ -38,69 +41,116 @@ function encodeData(sequence, data) {
38
41
  return Buffer.concat([header, payload])
39
42
  }
40
43
 
41
- function decodeFrames(buffer) {
42
- const frames = []
43
- let offset = 0
44
- let sequence = 0
45
- let totalData = 0
46
- while (offset < buffer.length) {
47
- if (buffer.length - offset < 5) throw new Error('truncated frame header')
48
- const kind = buffer[offset]
49
- const length = buffer.readUInt32BE(offset + 1)
50
- offset += 5
51
- const maxLength = kind === 0 ? MAX_CONTROL : 36 + MAX_CHUNK
52
- if ((kind !== 0 && kind !== 1) || length > maxLength || length > buffer.length - offset) {
53
- throw new Error('invalid frame length')
54
- }
55
- const payload = buffer.subarray(offset, offset + length)
56
- offset += length
57
- if (kind === 0) {
58
- if (length < 2 || payload[length - 1] !== 10) throw new Error('invalid control frame')
59
- frames.push({ kind: 'control', value: JSON.parse(payload.subarray(0, -1).toString('utf8')) })
60
- } else {
61
- if (length < 36) throw new Error('invalid data frame')
62
- const currentSequence = payload.readUInt32BE(0)
63
- const expected = payload.subarray(4, 36)
64
- const data = payload.subarray(36)
65
- const actual = createHash('sha256').update(data).digest()
66
- if (currentSequence !== sequence || !timingSafeEqual(expected, actual)) throw new Error('invalid data chunk')
67
- sequence += 1
68
- totalData += data.length
69
- if (totalData > MAX_TRANSFER) throw new Error('transfer exceeds size limit')
70
- frames.push({ kind: 'data', data })
44
+ function decodeOneFrame(kind, payload, state) {
45
+ if (kind === 0) {
46
+ if (payload.length < 2 || payload[payload.length - 1] !== 10) throw new Error('invalid control frame')
47
+ return { kind: 'control', value: JSON.parse(payload.subarray(0, -1).toString('utf8')) }
48
+ }
49
+ if (payload.length < 36) throw new Error('invalid data frame')
50
+ const currentSequence = payload.readUInt32BE(0)
51
+ const expected = payload.subarray(4, 36)
52
+ const data = payload.subarray(36)
53
+ const actual = createHash('sha256').update(data).digest()
54
+ if (currentSequence !== state.sequence || !timingSafeEqual(expected, actual)) throw new Error('invalid data chunk')
55
+ state.sequence += 1
56
+ state.totalData += data.length
57
+ if (state.totalData > MAX_TRANSFER && state.totalData > OBJECT_STREAM_LIMIT) throw new Error('transfer exceeds size limit')
58
+ return { kind: 'data', data }
59
+ }
60
+
61
+ async function readFrame(iterator, state) {
62
+ for (;;) {
63
+ if (state.buffer.length >= 5) {
64
+ const kind = state.buffer[0]
65
+ const length = state.buffer.readUInt32BE(1)
66
+ const maxLength = kind === 0 ? MAX_CONTROL : 36 + MAX_CHUNK
67
+ if ((kind !== 0 && kind !== 1) || length > maxLength) throw new Error('invalid frame length')
68
+ if (state.buffer.length >= 5 + length) {
69
+ const payload = state.buffer.subarray(5, 5 + length)
70
+ state.buffer = state.buffer.subarray(5 + length)
71
+ return decodeOneFrame(kind, payload, state)
72
+ }
71
73
  }
74
+ const next = await iterator.next()
75
+ if (next.done) throw new Error('helper input ended inside a frame')
76
+ state.buffer = Buffer.concat([state.buffer, Buffer.from(next.value)])
72
77
  }
73
- return frames
74
78
  }
75
79
 
76
- function parseRequest(buffer) {
77
- const frames = decodeFrames(buffer)
78
- const first = frames[0]
79
- if (!first || first.kind !== 'control') throw new Error('request control frame required')
80
- const request = first.value
80
+ function validateRequest(request) {
81
81
  if (!request || request.kind !== 'request' || request.protocolVersion !== 1 || !Object.prototype.hasOwnProperty.call(PATHS, request.operation)) {
82
82
  throw new Error('unsupported helper request')
83
83
  }
84
- if (Object.keys(request).some((key) => !['kind', 'protocolVersion', 'operation', 'port'].includes(key))) {
84
+ if (Object.keys(request).some(function (key) { return ['kind', 'protocolVersion', 'operation', 'port'].indexOf(key) < 0 })) {
85
85
  throw new Error('unsupported helper request field')
86
86
  }
87
87
  if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) throw new Error('invalid loopback port')
88
- const operation = PATHS[request.operation]
89
- if (request.operation === 'node-apply' || request.operation === 'session-entries' || request.operation === 'session-apply') {
90
- const last = frames[frames.length - 1]
91
- if (!last || last.kind !== 'control' || last.value.kind !== 'request-end') throw new Error('apply request terminator required')
92
- if (frames.slice(1, -1).some((frame) => frame.kind !== 'data')) throw new Error('invalid apply request body')
93
- const chunks = frames.slice(1, -1).map((frame) => frame.data)
94
- return { request, operation, body: Buffer.concat(chunks) }
95
- }
96
- if (frames.length !== 1) throw new Error('unexpected helper request frames')
97
- return { request, operation }
88
+ return PATHS[request.operation]
89
+ }
90
+
91
+ function writeStdout(data) {
92
+ return new Promise(function (resolve, reject) {
93
+ if (process.stdout.write(data)) resolve()
94
+ else process.stdout.once('drain', resolve)
95
+ })
98
96
  }
99
97
 
100
98
  async function run() {
101
- const buffer = Buffer.concat(input, inputBytes)
102
- const { request, operation, body } = parseRequest(buffer)
99
+ const iterator = process.stdin[Symbol.asyncIterator]()
100
+ const state = { buffer: Buffer.alloc(0), sequence: 0, totalData: 0 }
101
+ const first = await readFrame(iterator, state)
102
+ if (first.kind !== 'control' || first.value.kind !== 'request') throw new Error('request control frame required')
103
+ const request = first.value
104
+ const operation = validateRequest(request)
103
105
  const url = 'http://127.0.0.1:' + request.port + operation.path
106
+
107
+ if (request.operation === 'session-objects-push') {
108
+ // 对象推送:请求体(对象帧流)直接转发为 HTTP 流式请求体,内存 O(帧)。
109
+ const { Readable } = require('node:stream')
110
+ const bodyStream = new Readable({ read: function () {} })
111
+ const pump = (async function () {
112
+ try {
113
+ for (;;) {
114
+ const frame = await readFrame(iterator, state)
115
+ if (frame.kind === 'control' && frame.value.kind === 'request-end') break
116
+ if (frame.kind !== 'data') throw new Error('invalid object push frame')
117
+ if (!bodyStream.push(Buffer.from(frame.data))) {
118
+ await new Promise(function (resolve) { bodyStream.once('drain', resolve) })
119
+ }
120
+ }
121
+ bodyStream.push(null)
122
+ } catch (error) {
123
+ bodyStream.destroy(error)
124
+ }
125
+ })()
126
+ const response = await fetch(url, {
127
+ method: 'POST',
128
+ redirect: 'error',
129
+ headers: { 'content-type': 'application/x-dshb-object-stream' },
130
+ body: bodyStream,
131
+ duplex: 'half',
132
+ })
133
+ const text = await response.text()
134
+ if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')
135
+ await writeStdout(encodeControl({ kind: 'response', status: response.status, body: JSON.parse(text) }))
136
+ if (!response.ok) process.exitCode = 1
137
+ await pump
138
+ return
139
+ }
140
+
141
+ let body
142
+ if (BUFFERED_BODY_OPERATIONS.indexOf(request.operation) >= 0) {
143
+ // 帧序:control(request) → data… → control(request-end)。
144
+ const chunks = []
145
+ for (;;) {
146
+ const frame = await readFrame(iterator, state)
147
+ if (frame.kind === 'control' && frame.value.kind === 'request-end') break
148
+ if (frame.kind !== 'data') throw new Error('invalid apply request body')
149
+ chunks.push(frame.data)
150
+ }
151
+ body = Buffer.concat(chunks)
152
+ }
153
+
104
154
  const response = await fetch(url, {
105
155
  method: operation.method,
106
156
  redirect: 'error',
@@ -108,23 +158,15 @@ async function run() {
108
158
  ...(body ? { body } : {}),
109
159
  })
110
160
 
111
- function writeStdout(data) {
112
- return new Promise(function(resolve, reject) {
113
- if (process.stdout.write(data)) resolve()
114
- else process.stdout.once('drain', resolve)
115
- })
116
- }
117
-
118
- if (request.operation === 'node-manifest' || request.operation === 'session-bundle' ||
119
- request.operation === 'session-manifest' || request.operation === 'session-entries') {
161
+ if (STREAMING_RESPONSE_OPERATIONS.indexOf(request.operation) >= 0) {
120
162
  await writeStdout(encodeControl({ kind: 'response', status: response.status, bodyMode: 'data' }))
121
- if (!response.body) throw new Error('manifest response body missing')
163
+ if (!response.body) throw new Error('streaming response body missing')
122
164
  var sequence = 0
123
165
  var total = 0
124
166
  for await (var chunk of response.body) {
125
167
  var data = Buffer.from(chunk)
126
168
  total += data.length
127
- if (total > MAX_TRANSFER) throw new Error('manifest exceeds size limit')
169
+ if (total > MAX_TRANSFER && total > OBJECT_STREAM_LIMIT) throw new Error('response exceeds size limit')
128
170
  for (var offset = 0; offset < data.length; offset += MAX_CHUNK) {
129
171
  await writeStdout(encodeData(sequence++, data.subarray(offset, Math.min(offset + MAX_CHUNK, data.length))))
130
172
  }
@@ -143,20 +185,9 @@ async function run() {
143
185
  if (!response.ok && !recoverableDeltaMismatch) process.exitCode = 1
144
186
  }
145
187
 
146
- process.stdin.on('data', (chunk) => {
147
- inputBytes += chunk.length
148
- if (inputBytes > MAX_TRANSFER + 2 * MAX_CONTROL) {
149
- process.exitCode = 1
150
- process.stdin.destroy()
151
- return
152
- }
153
- input.push(Buffer.from(chunk))
154
- })
155
- process.stdin.on('end', () => {
156
- run().catch(() => {
157
- try { process.stdout.write(encodeControl({ kind: 'error', code: 'helper-failed' })) } catch {}
158
- process.exitCode = 1
159
- })
188
+ run().catch(function () {
189
+ try { process.stdout.write(encodeControl({ kind: 'error', code: 'helper-failed' })) } catch (err) {}
190
+ process.exitCode = 1
160
191
  })
161
192
  `;
162
193
  const REMOTE_SYNC_HELPER_COMMAND = `node -e 'eval(Buffer.from("${Buffer.from(REMOTE_HELPER_SOURCE, "utf8").toString("base64")}","base64").toString("utf8"))'`;
@@ -1 +1 @@
1
- {"version":3,"file":"remote-helper.js","names":[],"sources":["../../../src/core/sync/remote-helper.ts"],"sourcesContent":["import { Buffer } from 'node:buffer'\n\nconst REMOTE_HELPER_SOURCE = String.raw`'use strict'\nconst { createHash, timingSafeEqual } = require('node:crypto')\nconst MAX_CONTROL = 65536\nconst MAX_CHUNK = 1048576\nconst MAX_TRANSFER = 107374182400\nconst PATHS = {\n handshake: { path: '/api/dshb/sync/internal/handshake', method: 'GET' },\n 'node-manifest': { path: '/api/dshb/sync/internal/nodes/manifest', method: 'GET' },\n 'node-apply': { path: '/api/dshb/sync/internal/nodes/apply', method: 'POST' },\n 'session-bundle': { path: '/api/dshb/sync/internal/sessions/bundle', method: 'GET' },\n 'session-manifest': { path: '/api/dshb/sync/internal/sessions/manifest', method: 'GET' },\n 'session-entries': { path: '/api/dshb/sync/internal/sessions/entries', method: 'POST' },\n 'session-apply': { path: '/api/dshb/sync/internal/sessions/apply', method: 'POST' },\n}\nconst input = []\nlet inputBytes = 0\n\nfunction encodeControl(value) {\n const body = Buffer.from(JSON.stringify(value) + '\\n', 'utf8')\n if (body.length > MAX_CONTROL) throw new Error('control frame too large')\n const header = Buffer.alloc(5)\n header[0] = 0\n header.writeUInt32BE(body.length, 1)\n return Buffer.concat([header, body])\n}\n\nfunction encodeData(sequence, data) {\n const digest = createHash('sha256').update(data).digest()\n const metadata = Buffer.alloc(36)\n metadata.writeUInt32BE(sequence, 0)\n digest.copy(metadata, 4)\n const payload = Buffer.concat([metadata, data])\n const header = Buffer.alloc(5)\n header[0] = 1\n header.writeUInt32BE(payload.length, 1)\n return Buffer.concat([header, payload])\n}\n\nfunction decodeFrames(buffer) {\n const frames = []\n let offset = 0\n let sequence = 0\n let totalData = 0\n while (offset < buffer.length) {\n if (buffer.length - offset < 5) throw new Error('truncated frame header')\n const kind = buffer[offset]\n const length = buffer.readUInt32BE(offset + 1)\n offset += 5\n const maxLength = kind === 0 ? MAX_CONTROL : 36 + MAX_CHUNK\n if ((kind !== 0 && kind !== 1) || length > maxLength || length > buffer.length - offset) {\n throw new Error('invalid frame length')\n }\n const payload = buffer.subarray(offset, offset + length)\n offset += length\n if (kind === 0) {\n if (length < 2 || payload[length - 1] !== 10) throw new Error('invalid control frame')\n frames.push({ kind: 'control', value: JSON.parse(payload.subarray(0, -1).toString('utf8')) })\n } else {\n if (length < 36) throw new Error('invalid data frame')\n const currentSequence = payload.readUInt32BE(0)\n const expected = payload.subarray(4, 36)\n const data = payload.subarray(36)\n const actual = createHash('sha256').update(data).digest()\n if (currentSequence !== sequence || !timingSafeEqual(expected, actual)) throw new Error('invalid data chunk')\n sequence += 1\n totalData += data.length\n if (totalData > MAX_TRANSFER) throw new Error('transfer exceeds size limit')\n frames.push({ kind: 'data', data })\n }\n }\n return frames\n}\n\nfunction parseRequest(buffer) {\n const frames = decodeFrames(buffer)\n const first = frames[0]\n if (!first || first.kind !== 'control') throw new Error('request control frame required')\n const request = first.value\n if (!request || request.kind !== 'request' || request.protocolVersion !== 1 || !Object.prototype.hasOwnProperty.call(PATHS, request.operation)) {\n throw new Error('unsupported helper request')\n }\n if (Object.keys(request).some((key) => !['kind', 'protocolVersion', 'operation', 'port'].includes(key))) {\n throw new Error('unsupported helper request field')\n }\n if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) throw new Error('invalid loopback port')\n const operation = PATHS[request.operation]\n if (request.operation === 'node-apply' || request.operation === 'session-entries' || request.operation === 'session-apply') {\n const last = frames[frames.length - 1]\n if (!last || last.kind !== 'control' || last.value.kind !== 'request-end') throw new Error('apply request terminator required')\n if (frames.slice(1, -1).some((frame) => frame.kind !== 'data')) throw new Error('invalid apply request body')\n const chunks = frames.slice(1, -1).map((frame) => frame.data)\n return { request, operation, body: Buffer.concat(chunks) }\n }\n if (frames.length !== 1) throw new Error('unexpected helper request frames')\n return { request, operation }\n}\n\nasync function run() {\n const buffer = Buffer.concat(input, inputBytes)\n const { request, operation, body } = parseRequest(buffer)\n const url = 'http://127.0.0.1:' + request.port + operation.path\n const response = await fetch(url, {\n method: operation.method,\n redirect: 'error',\n headers: { accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) },\n ...(body ? { body } : {}),\n })\n\n function writeStdout(data) {\n return new Promise(function(resolve, reject) {\n if (process.stdout.write(data)) resolve()\n else process.stdout.once('drain', resolve)\n })\n }\n\n if (request.operation === 'node-manifest' || request.operation === 'session-bundle' ||\n request.operation === 'session-manifest' || request.operation === 'session-entries') {\n await writeStdout(encodeControl({ kind: 'response', status: response.status, bodyMode: 'data' }))\n if (!response.body) throw new Error('manifest response body missing')\n var sequence = 0\n var total = 0\n for await (var chunk of response.body) {\n var data = Buffer.from(chunk)\n total += data.length\n if (total > MAX_TRANSFER) throw new Error('manifest exceeds size limit')\n for (var offset = 0; offset < data.length; offset += MAX_CHUNK) {\n await writeStdout(encodeData(sequence++, data.subarray(offset, Math.min(offset + MAX_CHUNK, data.length))))\n }\n }\n await writeStdout(encodeControl({ kind: 'response-end' }))\n if (!response.ok) process.exitCode = 1\n return\n }\n\n var text = await response.text()\n if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')\n var responseBody = JSON.parse(text)\n await writeStdout(encodeControl({ kind: 'response', status: response.status, body: responseBody }))\n var recoverableDeltaMismatch = request.operation === 'session-apply' && response.status === 409 &&\n responseBody && responseBody.code === 'session-delta-prefix'\n if (!response.ok && !recoverableDeltaMismatch) process.exitCode = 1\n}\n\nprocess.stdin.on('data', (chunk) => {\n inputBytes += chunk.length\n if (inputBytes > MAX_TRANSFER + 2 * MAX_CONTROL) {\n process.exitCode = 1\n process.stdin.destroy()\n return\n }\n input.push(Buffer.from(chunk))\n})\nprocess.stdin.on('end', () => {\n run().catch(() => {\n try { process.stdout.write(encodeControl({ kind: 'error', code: 'helper-failed' })) } catch {}\n process.exitCode = 1\n })\n})\n`\n\nconst REMOTE_HELPER_BASE64 = Buffer.from(REMOTE_HELPER_SOURCE, 'utf8').toString('base64')\n\nexport const REMOTE_SYNC_HELPER_COMMAND =\n `node -e 'eval(Buffer.from(\"${REMOTE_HELPER_BASE64}\",\"base64\").toString(\"utf8\"))'`\n"],"mappings":";;AAEA,MAAM,uBAAuB,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkKvC,MAAa,6BACX,8BAH2B,OAAO,KAAK,sBAAsB,MAAM,CAAC,CAAC,SAAS,QAGhD,EAAqB"}
1
+ {"version":3,"file":"remote-helper.js","names":[],"sources":["../../../src/core/sync/remote-helper.ts"],"sourcesContent":["import { Buffer } from 'node:buffer'\n\nconst REMOTE_HELPER_SOURCE = String.raw`'use strict'\nconst { createHash, timingSafeEqual } = require('node:crypto')\nconst MAX_CONTROL = 65536\nconst MAX_CHUNK = 1048576\nconst MAX_TRANSFER = 107374182400\nconst OBJECT_STREAM_LIMIT = 68719476736\nconst PATHS = {\n handshake: { path: '/api/dshb/sync/internal/handshake', method: 'GET' },\n 'node-manifest': { path: '/api/dshb/sync/internal/nodes/manifest', method: 'GET' },\n 'node-apply': { path: '/api/dshb/sync/internal/nodes/apply', method: 'POST' },\n 'session-bundle': { path: '/api/dshb/sync/internal/sessions/bundle', method: 'GET' },\n 'session-manifest': { path: '/api/dshb/sync/internal/sessions/manifest', method: 'GET' },\n 'session-entries': { path: '/api/dshb/sync/internal/sessions/entries', method: 'POST' },\n 'session-apply': { path: '/api/dshb/sync/internal/sessions/apply', method: 'POST' },\n 'session-objects': { path: '/api/dshb/sync/internal/sessions/objects', method: 'POST' },\n 'session-objects-push': { path: '/api/dshb/sync/internal/sessions/objects', method: 'POST' },\n}\nconst BUFFERED_BODY_OPERATIONS = ['node-apply', 'session-entries', 'session-apply', 'session-objects']\nconst STREAMING_RESPONSE_OPERATIONS = ['node-manifest', 'session-bundle', 'session-manifest', 'session-entries', 'session-objects']\n\nfunction encodeControl(value) {\n const body = Buffer.from(JSON.stringify(value) + '\\n', 'utf8')\n if (body.length > MAX_CONTROL) throw new Error('control frame too large')\n const header = Buffer.alloc(5)\n header[0] = 0\n header.writeUInt32BE(body.length, 1)\n return Buffer.concat([header, body])\n}\n\nfunction encodeData(sequence, data) {\n const digest = createHash('sha256').update(data).digest()\n const metadata = Buffer.alloc(36)\n metadata.writeUInt32BE(sequence, 0)\n digest.copy(metadata, 4)\n const payload = Buffer.concat([metadata, data])\n const header = Buffer.alloc(5)\n header[0] = 1\n header.writeUInt32BE(payload.length, 1)\n return Buffer.concat([header, payload])\n}\n\nfunction decodeOneFrame(kind, payload, state) {\n if (kind === 0) {\n if (payload.length < 2 || payload[payload.length - 1] !== 10) throw new Error('invalid control frame')\n return { kind: 'control', value: JSON.parse(payload.subarray(0, -1).toString('utf8')) }\n }\n if (payload.length < 36) throw new Error('invalid data frame')\n const currentSequence = payload.readUInt32BE(0)\n const expected = payload.subarray(4, 36)\n const data = payload.subarray(36)\n const actual = createHash('sha256').update(data).digest()\n if (currentSequence !== state.sequence || !timingSafeEqual(expected, actual)) throw new Error('invalid data chunk')\n state.sequence += 1\n state.totalData += data.length\n if (state.totalData > MAX_TRANSFER && state.totalData > OBJECT_STREAM_LIMIT) throw new Error('transfer exceeds size limit')\n return { kind: 'data', data }\n}\n\nasync function readFrame(iterator, state) {\n for (;;) {\n if (state.buffer.length >= 5) {\n const kind = state.buffer[0]\n const length = state.buffer.readUInt32BE(1)\n const maxLength = kind === 0 ? MAX_CONTROL : 36 + MAX_CHUNK\n if ((kind !== 0 && kind !== 1) || length > maxLength) throw new Error('invalid frame length')\n if (state.buffer.length >= 5 + length) {\n const payload = state.buffer.subarray(5, 5 + length)\n state.buffer = state.buffer.subarray(5 + length)\n return decodeOneFrame(kind, payload, state)\n }\n }\n const next = await iterator.next()\n if (next.done) throw new Error('helper input ended inside a frame')\n state.buffer = Buffer.concat([state.buffer, Buffer.from(next.value)])\n }\n}\n\nfunction validateRequest(request) {\n if (!request || request.kind !== 'request' || request.protocolVersion !== 1 || !Object.prototype.hasOwnProperty.call(PATHS, request.operation)) {\n throw new Error('unsupported helper request')\n }\n if (Object.keys(request).some(function (key) { return ['kind', 'protocolVersion', 'operation', 'port'].indexOf(key) < 0 })) {\n throw new Error('unsupported helper request field')\n }\n if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) throw new Error('invalid loopback port')\n return PATHS[request.operation]\n}\n\nfunction writeStdout(data) {\n return new Promise(function (resolve, reject) {\n if (process.stdout.write(data)) resolve()\n else process.stdout.once('drain', resolve)\n })\n}\n\nasync function run() {\n const iterator = process.stdin[Symbol.asyncIterator]()\n const state = { buffer: Buffer.alloc(0), sequence: 0, totalData: 0 }\n const first = await readFrame(iterator, state)\n if (first.kind !== 'control' || first.value.kind !== 'request') throw new Error('request control frame required')\n const request = first.value\n const operation = validateRequest(request)\n const url = 'http://127.0.0.1:' + request.port + operation.path\n\n if (request.operation === 'session-objects-push') {\n // 对象推送:请求体(对象帧流)直接转发为 HTTP 流式请求体,内存 O(帧)。\n const { Readable } = require('node:stream')\n const bodyStream = new Readable({ read: function () {} })\n const pump = (async function () {\n try {\n for (;;) {\n const frame = await readFrame(iterator, state)\n if (frame.kind === 'control' && frame.value.kind === 'request-end') break\n if (frame.kind !== 'data') throw new Error('invalid object push frame')\n if (!bodyStream.push(Buffer.from(frame.data))) {\n await new Promise(function (resolve) { bodyStream.once('drain', resolve) })\n }\n }\n bodyStream.push(null)\n } catch (error) {\n bodyStream.destroy(error)\n }\n })()\n const response = await fetch(url, {\n method: 'POST',\n redirect: 'error',\n headers: { 'content-type': 'application/x-dshb-object-stream' },\n body: bodyStream,\n duplex: 'half',\n })\n const text = await response.text()\n if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')\n await writeStdout(encodeControl({ kind: 'response', status: response.status, body: JSON.parse(text) }))\n if (!response.ok) process.exitCode = 1\n await pump\n return\n }\n\n let body\n if (BUFFERED_BODY_OPERATIONS.indexOf(request.operation) >= 0) {\n // 帧序:control(request) → data… → control(request-end)。\n const chunks = []\n for (;;) {\n const frame = await readFrame(iterator, state)\n if (frame.kind === 'control' && frame.value.kind === 'request-end') break\n if (frame.kind !== 'data') throw new Error('invalid apply request body')\n chunks.push(frame.data)\n }\n body = Buffer.concat(chunks)\n }\n\n const response = await fetch(url, {\n method: operation.method,\n redirect: 'error',\n headers: { accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) },\n ...(body ? { body } : {}),\n })\n\n if (STREAMING_RESPONSE_OPERATIONS.indexOf(request.operation) >= 0) {\n await writeStdout(encodeControl({ kind: 'response', status: response.status, bodyMode: 'data' }))\n if (!response.body) throw new Error('streaming response body missing')\n var sequence = 0\n var total = 0\n for await (var chunk of response.body) {\n var data = Buffer.from(chunk)\n total += data.length\n if (total > MAX_TRANSFER && total > OBJECT_STREAM_LIMIT) throw new Error('response exceeds size limit')\n for (var offset = 0; offset < data.length; offset += MAX_CHUNK) {\n await writeStdout(encodeData(sequence++, data.subarray(offset, Math.min(offset + MAX_CHUNK, data.length))))\n }\n }\n await writeStdout(encodeControl({ kind: 'response-end' }))\n if (!response.ok) process.exitCode = 1\n return\n }\n\n var text = await response.text()\n if (Buffer.byteLength(text, 'utf8') > MAX_CONTROL) throw new Error('response control frame too large')\n var responseBody = JSON.parse(text)\n await writeStdout(encodeControl({ kind: 'response', status: response.status, body: responseBody }))\n var recoverableDeltaMismatch = request.operation === 'session-apply' && response.status === 409 &&\n responseBody && responseBody.code === 'session-delta-prefix'\n if (!response.ok && !recoverableDeltaMismatch) process.exitCode = 1\n}\n\nrun().catch(function () {\n try { process.stdout.write(encodeControl({ kind: 'error', code: 'helper-failed' })) } catch (err) {}\n process.exitCode = 1\n})\n`\n\nconst REMOTE_HELPER_BASE64 = Buffer.from(REMOTE_HELPER_SOURCE, 'utf8').toString('base64')\n\nexport const REMOTE_SYNC_HELPER_COMMAND =\n `node -e 'eval(Buffer.from(\"${REMOTE_HELPER_BASE64}\",\"base64\").toString(\"utf8\"))'`\n"],"mappings":";;AAEA,MAAM,uBAAuB,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiMvC,MAAa,6BACX,8BAH2B,OAAO,KAAK,sBAAsB,MAAM,CAAC,CAAC,SAAS,QAGhD,EAAqB"}
@@ -1,5 +1,5 @@
1
1
  import { SyncPeerManager } from "./peer-manager.js";
2
- import { s as SyncSshTransport } from "../../ssh-transport-Cfb_nwkY.js";
2
+ import { m as SyncSshTransport } from "../../ssh-transport-C7povM0s.js";
3
3
  import { SyncCoordinator } from "./coordinator.js";
4
4
  import { Context } from "@deepseek-ai/cordis";
5
5
  //#region src/core/sync/routes.d.ts
@@ -1,6 +1,6 @@
1
1
  import { SyncPeerInputError, SyncPeerNotFoundError } from "./peer-manager.js";
2
2
  import { SyncHostKeyChallengeError, SyncHostKeyChallengeExpiredError } from "./host-key.js";
3
- import { SyncSshProbeError } from "./ssh-transport.js";
3
+ import { SyncRemoteProbe, SyncSshProbeError } from "./ssh-transport.js";
4
4
  import { SyncCoordinatorError } from "./coordinator.js";
5
5
  //#region src/core/sync/routes.ts
6
6
  const MAX_BODY_BYTES = 131072;
@@ -160,6 +160,66 @@ function registerSyncPeerRoutes(ctx, manager, transport, coordinator) {
160
160
  }
161
161
  return;
162
162
  }
163
+ if (req.method === "POST" && peerId === "probe-remote") {
164
+ const body = await readBody(req);
165
+ if (!body || typeof body.peerId !== "string" && (typeof body.host !== "string" || !body.host.trim() || typeof body.username !== "string" || !body.username.trim() || !Number.isInteger(body.port) || body.port < 1 || body.port > 65535 || body.authKind !== "password" && body.authKind !== "key")) {
166
+ sendJson(res, 400, {
167
+ ok: false,
168
+ error: "either peerId or host/port/username/authKind are required"
169
+ });
170
+ return;
171
+ }
172
+ try {
173
+ const probe = new SyncRemoteProbe();
174
+ let result;
175
+ if (typeof body.peerId === "string" && body.peerId !== "new") {
176
+ const { peer, secrets } = await manager.connectionData(body.peerId);
177
+ result = await probe.probeRemote({
178
+ host: peer.host,
179
+ port: peer.port,
180
+ username: peer.username,
181
+ authKind: peer.authKind
182
+ }, secrets, peer.remoteWebPort);
183
+ } else {
184
+ const secrets = body.secrets ?? {};
185
+ if (body.authKind === "password" && typeof secrets.password !== "string") {
186
+ sendJson(res, 400, {
187
+ ok: false,
188
+ error: "SSH password is required for probing"
189
+ });
190
+ return;
191
+ }
192
+ if (body.authKind === "key" && typeof secrets.privateKey !== "string") {
193
+ sendJson(res, 400, {
194
+ ok: false,
195
+ error: "SSH private key is required for probing"
196
+ });
197
+ return;
198
+ }
199
+ const authKind = body.authKind === "key" ? "key" : "password";
200
+ result = await probe.probeRemote({
201
+ host: body.host.trim(),
202
+ port: body.port,
203
+ username: body.username.trim(),
204
+ authKind
205
+ }, secrets);
206
+ }
207
+ sendJson(res, 200, {
208
+ ok: true,
209
+ probe: result
210
+ });
211
+ } catch (error) {
212
+ if (error instanceof SyncPeerNotFoundError) {
213
+ sendJson(res, 404, {
214
+ ok: false,
215
+ error: "同步节点已不存在"
216
+ });
217
+ return;
218
+ }
219
+ sendProbeFailure(res, error);
220
+ }
221
+ return;
222
+ }
163
223
  if (req.method === "POST" && peerId === void 0) {
164
224
  const body = await readBody(req);
165
225
  if (!body) {
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","names":[],"sources":["../../../src/core/sync/routes.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport { SyncHostKeyChallengeError, SyncHostKeyChallengeExpiredError } from './host-key.js'\nimport { SyncPeerInputError, SyncPeerManager, SyncPeerNotFoundError } from './peer-manager.js'\nimport { SyncSshProbeError, SyncSshTransport } from './ssh-transport.js'\nimport { SyncCoordinator, SyncCoordinatorError } from './coordinator.js'\n\nconst MAX_BODY_BYTES = 128 * 1024\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction readBody(req: IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve) => {\n const contentType = req.headers['content-type']?.split(';', 1)[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n resolve(null)\n return\n }\n\n const chunks: Buffer[] = []\n let size = 0\n let settled = false\n const fail = (): void => {\n if (settled) return\n settled = true\n resolve(null)\n }\n req.on('data', (chunk: Buffer) => {\n if (settled) return\n size += chunk.length\n if (size > MAX_BODY_BYTES) {\n fail()\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n if (settled) return\n try {\n const value: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n fail()\n return\n }\n settled = true\n resolve(value as Record<string, unknown>)\n } catch {\n fail()\n }\n })\n req.on('error', fail)\n })\n}\n\nfunction pathSegments(req: IncomingMessage): string[] {\n return new URL(req.url ?? '/', 'http://x').pathname.split('/').filter(Boolean)\n}\n\nfunction splitBody(body: Record<string, unknown>): { fields: Record<string, unknown>; secrets: unknown } {\n const { secrets, ...fields } = body\n return { fields, secrets }\n}\n\nfunction sendFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncPeerInputError) {\n sendJson(res, 400, { ok: false, error: error.message })\n return\n }\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync peer operation failed' })\n}\n\nfunction sendProbeFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncHostKeyChallengeError) {\n sendJson(res, 409, {\n ok: false,\n code: `host-key-${error.decision}`,\n fingerprint: error.fingerprint,\n ...(error.expected ? { expected: error.expected } : {}),\n })\n return\n }\n if (error instanceof SyncHostKeyChallengeExpiredError) {\n sendJson(res, 409, { ok: false, code: 'host-key-challenge-expired', error: error.message })\n return\n }\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n if (error instanceof SyncSshProbeError) {\n const status = error.category === 'auth' ? 502 : error.category === 'protocol' || error.category === 'hostkey' ? 409 : 503\n sendJson(res, status, { ok: false, code: `ssh-${error.category}`, error: error.message })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync peer probe failed' })\n}\n\nfunction sendRunFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncCoordinatorError) {\n const status = error.code === 'peer-not-found' ? 404\n : error.code === 'peer-disabled' || error.code === 'sessions-unavailable' || error.code === 'revision-conflict' ? 409\n : error.code === 'session-bundle-too-large' || error.code === 'session-too-large' ? 413\n : error.code === 'session-workspace-unmapped' || error.code === 'session-format' || error.code === 'session-revision-conflict' || error.code === 'session-same-instance' ? 409\n : error.code.startsWith('ssh-') ? 502 : 500\n sendJson(res, status, { ok: false, code: error.code, error: error.message })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync run failed' })\n}\n\nexport function registerSyncPeerRoutes(\n ctx: Context,\n manager: SyncPeerManager,\n transport?: SyncSshTransport,\n coordinator?: SyncCoordinator,\n): void {\n ctx.effect(() =>\n ctx.webServer.register({\n kind: 'prefix',\n path: '/api/dshb/sync/peers',\n handler: async (req, res) => {\n const segments = pathSegments(req)\n const peerId = segments[4]\n const action = segments[5]\n if (segments.length > 6) {\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n\n if (req.method === 'GET' && peerId === undefined) {\n try {\n sendJson(res, 200, { ok: true, peers: await manager.list() })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer list unavailable' })\n }\n return\n }\n\n if (req.method === 'POST' && peerId === undefined) {\n const body = await readBody(req)\n if (!body) {\n sendJson(res, 400, { ok: false, error: 'request body must be JSON' })\n return\n }\n const { fields, secrets } = splitBody(body)\n try {\n sendJson(res, 201, { ok: true, peer: await manager.create(fields, secrets) })\n } catch (error) {\n sendFailure(res, error)\n }\n return\n }\n\n if (!peerId) {\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n return\n }\n\n if (action !== undefined) {\n if (segments.length !== 6 || req.method !== 'POST') {\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n return\n }\n if (!transport) {\n sendJson(res, 503, { ok: false, error: 'SSH sync transport unavailable' })\n return\n }\n if (action === 'probe') {\n try {\n const handshake = await transport.probe(peerId)\n sendJson(res, 200, { ok: true, handshake })\n } catch (error) {\n sendProbeFailure(res, error)\n }\n return\n }\n if (action === 'trust-host-key') {\n try {\n await transport.trustObservedHostKey(peerId)\n const peer = await manager.get(peerId)\n sendJson(res, 200, { ok: true, peer })\n } catch (error) {\n sendProbeFailure(res, error)\n }\n return\n }\n if (action === 'run') {\n if (!coordinator) {\n sendJson(res, 503, { ok: false, error: 'sync coordinator unavailable' })\n return\n }\n try {\n const peer = await manager.get(peerId)\n if (!peer) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n if (!peer.enabled) {\n sendJson(res, 409, { ok: false, code: 'peer-disabled', error: '此同步节点已停用' })\n return\n }\n void coordinator.run(peerId).catch(() => undefined)\n sendJson(res, 202, { ok: true, accepted: true })\n } catch (error) {\n sendRunFailure(res, error)\n }\n return\n }\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n\n if (req.method === 'GET') {\n try {\n const peer = await manager.get(peerId)\n if (!peer) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 200, { ok: true, peer })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer lookup failed' })\n }\n return\n }\n\n if (req.method === 'PATCH') {\n const body = await readBody(req)\n if (!body) {\n sendJson(res, 400, { ok: false, error: 'request body must be JSON' })\n return\n }\n const { fields, secrets } = splitBody(body)\n try {\n sendJson(res, 200, { ok: true, peer: await manager.update(peerId, fields, secrets) })\n } catch (error) {\n sendFailure(res, error)\n }\n return\n }\n\n if (req.method === 'DELETE') {\n try {\n if (!(await manager.remove(peerId))) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 200, { ok: true })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer removal failed' })\n }\n return\n }\n\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n },\n }),\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"routes.js","names":[],"sources":["../../../src/core/sync/routes.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Context } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-host-webserver'\nimport { SyncHostKeyChallengeError, SyncHostKeyChallengeExpiredError } from './host-key.js'\nimport { SyncPeerInputError, SyncPeerManager, SyncPeerNotFoundError } from './peer-manager.js'\nimport { SyncRemoteProbe, SyncSshProbeError, SyncSshTransport } from './ssh-transport.js'\nimport { SyncCoordinator, SyncCoordinatorError } from './coordinator.js'\n\nconst MAX_BODY_BYTES = 128 * 1024\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { 'content-type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction readBody(req: IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve) => {\n const contentType = req.headers['content-type']?.split(';', 1)[0]?.trim().toLowerCase()\n if (contentType !== 'application/json') {\n resolve(null)\n return\n }\n\n const chunks: Buffer[] = []\n let size = 0\n let settled = false\n const fail = (): void => {\n if (settled) return\n settled = true\n resolve(null)\n }\n req.on('data', (chunk: Buffer) => {\n if (settled) return\n size += chunk.length\n if (size > MAX_BODY_BYTES) {\n fail()\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => {\n if (settled) return\n try {\n const value: unknown = JSON.parse(Buffer.concat(chunks).toString('utf8'))\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n fail()\n return\n }\n settled = true\n resolve(value as Record<string, unknown>)\n } catch {\n fail()\n }\n })\n req.on('error', fail)\n })\n}\n\nfunction pathSegments(req: IncomingMessage): string[] {\n return new URL(req.url ?? '/', 'http://x').pathname.split('/').filter(Boolean)\n}\n\nfunction splitBody(body: Record<string, unknown>): { fields: Record<string, unknown>; secrets: unknown } {\n const { secrets, ...fields } = body\n return { fields, secrets }\n}\n\nfunction sendFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncPeerInputError) {\n sendJson(res, 400, { ok: false, error: error.message })\n return\n }\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync peer operation failed' })\n}\n\nfunction sendProbeFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncHostKeyChallengeError) {\n sendJson(res, 409, {\n ok: false,\n code: `host-key-${error.decision}`,\n fingerprint: error.fingerprint,\n ...(error.expected ? { expected: error.expected } : {}),\n })\n return\n }\n if (error instanceof SyncHostKeyChallengeExpiredError) {\n sendJson(res, 409, { ok: false, code: 'host-key-challenge-expired', error: error.message })\n return\n }\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n if (error instanceof SyncSshProbeError) {\n const status = error.category === 'auth' ? 502 : error.category === 'protocol' || error.category === 'hostkey' ? 409 : 503\n sendJson(res, status, { ok: false, code: `ssh-${error.category}`, error: error.message })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync peer probe failed' })\n}\n\nfunction sendRunFailure(res: ServerResponse, error: unknown): void {\n if (error instanceof SyncCoordinatorError) {\n const status = error.code === 'peer-not-found' ? 404\n : error.code === 'peer-disabled' || error.code === 'sessions-unavailable' || error.code === 'revision-conflict' ? 409\n : error.code === 'session-bundle-too-large' || error.code === 'session-too-large' ? 413\n : error.code === 'session-workspace-unmapped' || error.code === 'session-format' || error.code === 'session-revision-conflict' || error.code === 'session-same-instance' ? 409\n : error.code.startsWith('ssh-') ? 502 : 500\n sendJson(res, status, { ok: false, code: error.code, error: error.message })\n return\n }\n sendJson(res, 500, { ok: false, error: 'sync run failed' })\n}\n\nexport function registerSyncPeerRoutes(\n ctx: Context,\n manager: SyncPeerManager,\n transport?: SyncSshTransport,\n coordinator?: SyncCoordinator,\n): void {\n ctx.effect(() =>\n ctx.webServer.register({\n kind: 'prefix',\n path: '/api/dshb/sync/peers',\n handler: async (req, res) => {\n const segments = pathSegments(req)\n const peerId = segments[4]\n const action = segments[5]\n if (segments.length > 6) {\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n\n if (req.method === 'GET' && peerId === undefined) {\n try {\n sendJson(res, 200, { ok: true, peers: await manager.list() })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer list unavailable' })\n }\n return\n }\n\n if (req.method === 'POST' && peerId === 'probe-remote') {\n // 远端配置探测:① peerId 模式——用已保存对端的存储凭据;② 显式模式——\n // 用表单填写的 SSH 连接信息(新建对端场景)。\n const body = await readBody(req)\n if (!body || (typeof body.peerId !== 'string'\n && (typeof body.host !== 'string' || !body.host.trim()\n || typeof body.username !== 'string' || !body.username.trim()\n || !Number.isInteger(body.port) || (body.port as number) < 1 || (body.port as number) > 65535\n || (body.authKind !== 'password' && body.authKind !== 'key')))) {\n sendJson(res, 400, { ok: false, error: 'either peerId or host/port/username/authKind are required' })\n return\n }\n try {\n const probe = new SyncRemoteProbe()\n let result\n if (typeof body.peerId === 'string' && body.peerId !== 'new') {\n const { peer, secrets } = await manager.connectionData(body.peerId)\n result = await probe.probeRemote(\n { host: peer.host, port: peer.port, username: peer.username, authKind: peer.authKind },\n secrets,\n peer.remoteWebPort,\n )\n } else {\n const secrets = (body.secrets ?? {}) as Record<string, unknown>\n if (body.authKind === 'password' && typeof secrets.password !== 'string') {\n sendJson(res, 400, { ok: false, error: 'SSH password is required for probing' })\n return\n }\n if (body.authKind === 'key' && typeof secrets.privateKey !== 'string') {\n sendJson(res, 400, { ok: false, error: 'SSH private key is required for probing' })\n return\n }\n const authKind = body.authKind === 'key' ? 'key' : 'password'\n result = await probe.probeRemote(\n { host: (body.host as string).trim(), port: body.port as number, username: (body.username as string).trim(), authKind },\n secrets as { password?: string; privateKey?: string; passphrase?: string },\n )\n }\n sendJson(res, 200, { ok: true, probe: result })\n } catch (error) {\n if (error instanceof SyncPeerNotFoundError) {\n sendJson(res, 404, { ok: false, error: '同步节点已不存在' })\n return\n }\n sendProbeFailure(res, error)\n }\n return\n }\n if (req.method === 'POST' && peerId === undefined) {\n const body = await readBody(req)\n if (!body) {\n sendJson(res, 400, { ok: false, error: 'request body must be JSON' })\n return\n }\n const { fields, secrets } = splitBody(body)\n try {\n sendJson(res, 201, { ok: true, peer: await manager.create(fields, secrets) })\n } catch (error) {\n sendFailure(res, error)\n }\n return\n }\n\n if (!peerId) {\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n return\n }\n\n if (action !== undefined) {\n if (segments.length !== 6 || req.method !== 'POST') {\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n return\n }\n if (!transport) {\n sendJson(res, 503, { ok: false, error: 'SSH sync transport unavailable' })\n return\n }\n if (action === 'probe') {\n try {\n const handshake = await transport.probe(peerId)\n sendJson(res, 200, { ok: true, handshake })\n } catch (error) {\n sendProbeFailure(res, error)\n }\n return\n }\n if (action === 'trust-host-key') {\n try {\n await transport.trustObservedHostKey(peerId)\n const peer = await manager.get(peerId)\n sendJson(res, 200, { ok: true, peer })\n } catch (error) {\n sendProbeFailure(res, error)\n }\n return\n }\n if (action === 'run') {\n if (!coordinator) {\n sendJson(res, 503, { ok: false, error: 'sync coordinator unavailable' })\n return\n }\n try {\n const peer = await manager.get(peerId)\n if (!peer) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n if (!peer.enabled) {\n sendJson(res, 409, { ok: false, code: 'peer-disabled', error: '此同步节点已停用' })\n return\n }\n void coordinator.run(peerId).catch(() => undefined)\n sendJson(res, 202, { ok: true, accepted: true })\n } catch (error) {\n sendRunFailure(res, error)\n }\n return\n }\n sendJson(res, 404, { ok: false, error: 'not found' })\n return\n }\n\n if (req.method === 'GET') {\n try {\n const peer = await manager.get(peerId)\n if (!peer) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 200, { ok: true, peer })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer lookup failed' })\n }\n return\n }\n\n if (req.method === 'PATCH') {\n const body = await readBody(req)\n if (!body) {\n sendJson(res, 400, { ok: false, error: 'request body must be JSON' })\n return\n }\n const { fields, secrets } = splitBody(body)\n try {\n sendJson(res, 200, { ok: true, peer: await manager.update(peerId, fields, secrets) })\n } catch (error) {\n sendFailure(res, error)\n }\n return\n }\n\n if (req.method === 'DELETE') {\n try {\n if (!(await manager.remove(peerId))) {\n sendJson(res, 404, { ok: false, error: 'sync peer not found' })\n return\n }\n sendJson(res, 200, { ok: true })\n } catch {\n sendJson(res, 500, { ok: false, error: 'sync peer removal failed' })\n }\n return\n }\n\n sendJson(res, 405, { ok: false, error: 'method not allowed' })\n },\n }),\n )\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { p as SyncSessionService } from "../../session-sync-Zfo1Q-qT.js";
1
+ import { h as SyncSessionService } from "../../session-sync-BuOJvFxK.js";
2
2
  import { Context } from "@deepseek-ai/cordis";
3
3
  //#region src/core/sync/session-routes.d.ts
4
4
  declare function registerSyncSessionRoutes(ctx: Context, sessions: SyncSessionService): void;
@@ -1,4 +1,4 @@
1
- import { n as SyncSessionError } from "../../session-sync-BQt3HXkp.js";
1
+ import { n as SyncSessionError } from "../../session-sync-CIqjuWkh.js";
2
2
  //#region src/core/sync/session-routes.ts
3
3
  function sendJson(res, status, body) {
4
4
  res.writeHead(status, {
@@ -1,2 +1,2 @@
1
- import { C as validateSyncSessionBundle, S as syncSessionEntryKey, _ as estimateJsonBytes, a as SyncSessionEntry, b as planSessionTransfers, c as SyncSessionEventPrefix, d as SyncSessionManifestEntry, f as SyncSessionObject, g as WorkspaceRegistryLike, h as SyncSessionWorkspace, i as SyncSessionBundle, l as SyncSessionInstructionSnapshot, m as SyncSessionTransferPlan, n as SyncSessionApplyOptions, o as SyncSessionError, p as SyncSessionService, r as SyncSessionApplyResult, s as SyncSessionEventDelta, t as ExportObjectCache, u as SyncSessionManifest, v as makeSyncSessionBundle, w as validateSyncSessionManifest, x as sessionEventListDigest, y as makeSyncSessionManifest } from "../../session-sync-Zfo1Q-qT.js";
2
- export { ExportObjectCache, SyncSessionApplyOptions, SyncSessionApplyResult, SyncSessionBundle, SyncSessionEntry, SyncSessionError, SyncSessionEventDelta, SyncSessionEventPrefix, SyncSessionInstructionSnapshot, SyncSessionManifest, SyncSessionManifestEntry, SyncSessionObject, SyncSessionService, SyncSessionTransferPlan, SyncSessionWorkspace, WorkspaceRegistryLike, estimateJsonBytes, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateSyncSessionBundle, validateSyncSessionManifest };
1
+ import { C as sessionEventListDigest, D as validateSyncSessionManifest, E as validateSyncSessionBundle, S as planSessionTransfers, T as validateObjectPayload, _ as SyncSessionWorkspace, a as SyncSessionBundle, b as makeSyncSessionBundle, c as SyncSessionEventDelta, d as SyncSessionManifest, f as SyncSessionManifestEntry, g as SyncSessionTransferPlan, h as SyncSessionService, i as SyncSessionApplyResult, l as SyncSessionEventPrefix, m as SyncSessionObjectInbox, n as SyncObjectInboxWriter, o as SyncSessionEntry, p as SyncSessionObject, r as SyncSessionApplyOptions, s as SyncSessionError, t as ExportObjectCache, u as SyncSessionInstructionSnapshot, v as WorkspaceRegistryLike, w as syncSessionEntryKey, x as makeSyncSessionManifest, y as estimateJsonBytes } from "../../session-sync-BuOJvFxK.js";
2
+ export { ExportObjectCache, SyncObjectInboxWriter, SyncSessionApplyOptions, SyncSessionApplyResult, SyncSessionBundle, SyncSessionEntry, SyncSessionError, SyncSessionEventDelta, SyncSessionEventPrefix, SyncSessionInstructionSnapshot, SyncSessionManifest, SyncSessionManifestEntry, SyncSessionObject, SyncSessionObjectInbox, SyncSessionService, SyncSessionTransferPlan, SyncSessionWorkspace, WorkspaceRegistryLike, estimateJsonBytes, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateObjectPayload, validateSyncSessionBundle, validateSyncSessionManifest };
@@ -1,2 +1,2 @@
1
- import { a as makeSyncSessionBundle, c as sessionEventListDigest, d as validateSyncSessionManifest, i as estimateJsonBytes, l as syncSessionEntryKey, n as SyncSessionError, o as makeSyncSessionManifest, r as SyncSessionService, s as planSessionTransfers, t as ExportObjectCache, u as validateSyncSessionBundle } from "../../session-sync-BQt3HXkp.js";
2
- export { ExportObjectCache, SyncSessionError, SyncSessionService, estimateJsonBytes, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateSyncSessionBundle, validateSyncSessionManifest };
1
+ import { a as estimateJsonBytes, c as planSessionTransfers, d as validateObjectPayload, f as validateSyncSessionBundle, i as SyncSessionService, l as sessionEventListDigest, n as SyncSessionError, o as makeSyncSessionBundle, p as validateSyncSessionManifest, r as SyncSessionObjectInbox, s as makeSyncSessionManifest, t as ExportObjectCache, u as syncSessionEntryKey } from "../../session-sync-CIqjuWkh.js";
2
+ export { ExportObjectCache, SyncSessionError, SyncSessionObjectInbox, SyncSessionService, estimateJsonBytes, makeSyncSessionBundle, makeSyncSessionManifest, planSessionTransfers, sessionEventListDigest, syncSessionEntryKey, validateObjectPayload, validateSyncSessionBundle, validateSyncSessionManifest };
@@ -1,2 +1,2 @@
1
- import { a as SyncSshProbeError, c as buildSyncSshConnectConfig, i as SyncSshErrorCategory, n as SyncRemoteNodeManifest, o as SyncSshSession, r as SyncSessionDeltaPrefixError, s as SyncSshTransport, t as SyncRemoteHandshake } from "../../ssh-transport-Cfb_nwkY.js";
2
- export { SyncRemoteHandshake, SyncRemoteNodeManifest, SyncSessionDeltaPrefixError, SyncSshErrorCategory, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildSyncSshConnectConfig };
1
+ import { _ as parseRemoteProbeOutput, a as SyncRemoteProbe, c as SyncRemoteProbeProfile, d as SyncSshErrorCategory, f as SyncSshProbeError, g as buildSyncSshConnectConfig, h as buildRemoteProbeScript, i as SyncRemoteNodeManifest, l as SyncRemoteProbeResult, m as SyncSshTransport, n as REMOTE_PROBE_END_MARKER, o as SyncRemoteProbeDraft, p as SyncSshSession, r as SyncRemoteHandshake, s as SyncRemoteProbeHandshake, t as REMOTE_PROBE_BEGIN_MARKER, u as SyncSessionDeltaPrefixError } from "../../ssh-transport-C7povM0s.js";
2
+ export { REMOTE_PROBE_BEGIN_MARKER, REMOTE_PROBE_END_MARKER, SyncRemoteHandshake, SyncRemoteNodeManifest, SyncRemoteProbe, SyncRemoteProbeDraft, SyncRemoteProbeHandshake, SyncRemoteProbeProfile, SyncRemoteProbeResult, SyncSessionDeltaPrefixError, SyncSshErrorCategory, SyncSshProbeError, SyncSshSession, SyncSshTransport, buildRemoteProbeScript, buildSyncSshConnectConfig, parseRemoteProbeOutput };