@artenx/dshb 0.1.34 → 0.1.35

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 (44) hide show
  1. package/lib/client.js +34 -3
  2. package/lib/client.js.map +1 -1
  3. package/lib/core/index.js +1 -1
  4. package/lib/core/routes.d.ts +1 -1
  5. package/lib/core/sync/coordinator.d.ts +14 -5
  6. package/lib/core/sync/coordinator.js +83 -18
  7. package/lib/core/sync/coordinator.js.map +1 -1
  8. package/lib/core/sync/host-key.d.ts +1 -1
  9. package/lib/core/sync/internal-routes.d.ts +3 -3
  10. package/lib/core/sync/internal-routes.js +1 -1
  11. package/lib/core/sync/model.d.ts +1 -1
  12. package/lib/core/sync/model.js.map +1 -1
  13. package/lib/core/sync/node-sync.d.ts +1 -1
  14. package/lib/core/sync/offline-archive.d.ts +3 -3
  15. package/lib/core/sync/offline-archive.js +1 -1
  16. package/lib/core/sync/offline-routes.js +1 -1
  17. package/lib/core/sync/peer-manager.d.ts +2 -2
  18. package/lib/core/sync/routes.d.ts +1 -1
  19. package/lib/core/sync/routes.js +19 -0
  20. package/lib/core/sync/routes.js.map +1 -1
  21. package/lib/core/sync/session-journal.d.ts +1 -1
  22. package/lib/core/sync/session-journal.js +16 -0
  23. package/lib/core/sync/session-journal.js.map +1 -1
  24. package/lib/core/sync/session-routes.d.ts +1 -1
  25. package/lib/core/sync/session-routes.js +1 -1
  26. package/lib/core/sync/session-sync.d.ts +1 -1
  27. package/lib/core/sync/session-sync.js +1 -1
  28. package/lib/core/sync/ssh-transport.d.ts +1 -1
  29. package/lib/core/sync/ssh-transport.js +29 -2
  30. package/lib/core/sync/ssh-transport.js.map +1 -1
  31. package/lib/core/sync/stores.d.ts +1 -1
  32. package/lib/core/sync/stores.js +1 -1
  33. package/lib/core/sync/stores.js.map +1 -1
  34. package/lib/core/workspaces.d.ts +1 -1
  35. package/lib/{host-key-C5afhAUd.d.ts → host-key-CSnpIrjq.d.ts} +2 -2
  36. package/lib/{model-BP7Gw1yk.d.ts → model-CKTBf-Pl.d.ts} +2 -2
  37. package/lib/{node-sync-3Z9QLz2n.d.ts → node-sync-C3Ym-HDu.d.ts} +3 -3
  38. package/lib/{session-journal-pgPqcWn9.d.ts → session-journal-VE_1tA2-.d.ts} +8 -2
  39. package/lib/{session-sync-CIqjuWkh.js → session-sync-DLr_BdvN.js} +15 -1
  40. package/lib/{session-sync-CIqjuWkh.js.map → session-sync-DLr_BdvN.js.map} +1 -1
  41. package/lib/{session-sync-BuOJvFxK.d.ts → session-sync-DrKShc5c.d.ts} +11 -5
  42. package/lib/{ssh-transport-C7povM0s.d.ts → ssh-transport-BxPWfu9X.d.ts} +13 -6
  43. package/lib/{stores-CKyEt0yk.d.ts → stores-puyTC075.d.ts} +2 -2
  44. package/package.json +1 -1
@@ -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_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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"}
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 private readonly onClosed?: () => void,\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 try {\n this.onClosed?.()\n } catch {}\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 /** 每对端当前打开的 SSH 会话:手动停止同步时立即关闭通道,等待中的 readFrames 随即返回。 */\n private readonly openSessions = new Map<string, Set<SyncSshSession>>()\n\n constructor(private readonly peerManager: SyncPeerManager) {}\n\n private trackSession(peerId: string, session: SyncSshSession): void {\n const sessions = this.openSessions.get(peerId) ?? new Set<SyncSshSession>()\n sessions.add(session)\n this.openSessions.set(peerId, sessions)\n }\n\n private untrackSession(peerId: string, session: SyncSshSession): void {\n const sessions = this.openSessions.get(peerId)\n sessions?.delete(session)\n if (sessions !== undefined && sessions.size === 0) this.openSessions.delete(peerId)\n }\n\n /** 立即关闭该对端的全部活跃 SSH 会话(幂等;用于手动停止同步)。 */\n stopActiveSessions(peerId: string): number {\n const sessions = this.openSessions.get(peerId)\n if (sessions === undefined) return 0\n for (const session of [...sessions]) {\n try {\n session.close()\n } catch {}\n }\n return sessions.size\n }\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, () => this.untrackSession(peer.id, session))\n sessionOpened = true\n this.trackSession(peer.id, session)\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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"}
@@ -1,3 +1,3 @@
1
1
  import { t as HlcSnapshot } from "../../revision-C9sOlDZ6.js";
2
- import { n as SyncStateStore, r as getOrCreateSyncInstanceId, t as SyncPeerStore } from "../../stores-CKyEt0yk.js";
2
+ import { n as SyncStateStore, r as getOrCreateSyncInstanceId, t as SyncPeerStore } from "../../stores-puyTC075.js";
3
3
  export { type HlcSnapshot, SyncPeerStore, SyncStateStore, getOrCreateSyncInstanceId };
@@ -170,7 +170,7 @@ function validateProgress(value) {
170
170
  }
171
171
  }
172
172
  if (value.lastRunStatus !== void 0) {
173
- if (value.lastRunStatus !== "running" && value.lastRunStatus !== "succeeded" && value.lastRunStatus !== "failed") throw new Error("Invalid peer sync run status");
173
+ if (value.lastRunStatus !== "running" && value.lastRunStatus !== "succeeded" && value.lastRunStatus !== "failed" && value.lastRunStatus !== "stopped") throw new Error("Invalid peer sync run status");
174
174
  progress.lastRunStatus = value.lastRunStatus;
175
175
  }
176
176
  if (value.lastResult !== void 0) {
@@ -1 +1 @@
1
- {"version":3,"file":"stores.js","names":[],"sources":["../../../src/core/sync/stores.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport {\n chmodSync,\n closeSync,\n fsyncSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join, posix } from 'node:path'\nimport { HybridLogicalClock, type HlcSnapshot, type SyncRevision } from './revision.js'\nimport {\n isNonNegativeSafeInteger,\n isRecord,\n parseSyncPeer,\n syncRecordKey,\n syncWorkspaceKey,\n type SyncPeer,\n type SyncPeerDraft,\n type SyncPeerProgress,\n type SyncRecordMetadata,\n type SyncSessionArchive,\n type SyncState,\n type SyncWorkspaceMapping,\n} from './model.js'\n\nfunction dshHome(home?: string): string {\n return home ?? process.env.DSH_HOME ?? join(homedir(), '.dsh')\n}\n\nfunction readJson<T>(file: string, validate: (value: unknown) => T): T | undefined {\n let content: string\n try {\n content = readFileSync(file, 'utf8')\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') return undefined\n throw error\n }\n let value: unknown\n try {\n value = JSON.parse(content)\n } catch {\n throw new Error(`Invalid JSON in ${basename(file)}`)\n }\n return validate(value)\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && 'code' in error\n}\n\nfunction atomicWrite(file: string, content: string): void {\n const dir = dirname(file)\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n const temporary = `${file}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`\n let fd: number | undefined\n try {\n fd = openSync(temporary, 'wx', 0o600)\n writeFileSync(fd, content, 'utf8')\n fsyncSync(fd)\n closeSync(fd)\n fd = undefined\n renameSync(temporary, file)\n chmodSync(file, 0o600)\n } catch (error) {\n if (fd !== undefined) closeSync(fd)\n try {\n unlinkSync(temporary)\n } catch {}\n throw error\n }\n}\n\nfunction clone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T\n}\n\nfunction instanceIdFile(home: string): string {\n return join(home, 'dshb', 'instance-id')\n}\n\nfunction parseInstanceId(value: string): string {\n const id = value.trim()\n if (!/^i-[a-f0-9]{32}$/.test(id)) throw new Error('Invalid DSHB sync instance ID')\n return id\n}\n\nexport function getOrCreateSyncInstanceId(home?: string): string {\n const root = dshHome(home)\n const file = instanceIdFile(root)\n try {\n return parseInstanceId(readFileSync(file, 'utf8'))\n } catch (error) {\n if (!(isNodeError(error) && error.code === 'ENOENT')) throw error\n }\n\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 })\n const candidate = `i-${randomBytes(16).toString('hex')}`\n let fd: number | undefined\n try {\n fd = openSync(file, 'wx', 0o600)\n writeFileSync(fd, `${candidate}\\n`, 'utf8')\n fsyncSync(fd)\n closeSync(fd)\n fd = undefined\n chmodSync(file, 0o600)\n return candidate\n } catch (error) {\n if (fd !== undefined) closeSync(fd)\n if (isNodeError(error) && error.code === 'EEXIST') {\n return parseInstanceId(readFileSync(file, 'utf8'))\n }\n throw error\n }\n}\n\nfunction newPeerId(): string {\n return `p-${randomBytes(12).toString('hex')}`\n}\n\nfunction validatePeers(value: unknown): SyncPeer[] {\n if (!Array.isArray(value)) throw new Error('Invalid sync peer store')\n const peers = value.map(parseSyncPeer)\n if (new Set(peers.map((peer) => peer.id)).size !== peers.length) {\n throw new Error('Sync peer store contains duplicate IDs')\n }\n return peers\n}\n\nexport class SyncPeerStore {\n private peers: SyncPeer[] | undefined\n private readonly filePath: string\n\n constructor(home?: string) {\n this.filePath = join(dshHome(home), 'dshb', 'sync-peers.json')\n }\n\n list(): SyncPeer[] {\n return clone(this.load())\n }\n\n get(id: string): SyncPeer | undefined {\n const peer = this.load().find((item) => item.id === id)\n return peer ? clone(peer) : undefined\n }\n\n create(draft: SyncPeerDraft): SyncPeer {\n const peer = parseSyncPeer({ ...draft, id: newPeerId() })\n const next = [...this.load(), peer]\n this.save(next)\n return clone(peer)\n }\n\n update(id: string, patch: Partial<Omit<SyncPeer, 'id'>>): SyncPeer {\n const current = this.load()\n const index = current.findIndex((peer) => peer.id === id)\n if (index < 0) throw new Error(`sync peer ${id} not found`)\n const updated = parseSyncPeer({ ...current[index], ...patch, id })\n const next = [...current]\n next[index] = updated\n this.save(next)\n return clone(updated)\n }\n\n remove(id: string): boolean {\n const current = this.load()\n const next = current.filter((peer) => peer.id !== id)\n if (next.length === current.length) return false\n this.save(next)\n return true\n }\n\n private load(): SyncPeer[] {\n if (this.peers) return this.peers\n const stored = readJson(this.filePath, validatePeers)\n this.peers = stored ?? []\n return this.peers\n }\n\n private save(peers: SyncPeer[]): void {\n atomicWrite(this.filePath, `${JSON.stringify(peers, null, 2)}\\n`)\n this.peers = peers\n }\n}\n\nfunction validateProgress(value: unknown): SyncPeerProgress {\n if (!isRecord(value)) throw new Error('Invalid peer sync progress')\n const progress: SyncPeerProgress = {}\n for (const field of ['lastRunAt', 'lastSuccessAt', 'lastManifestDigest', 'watermark', 'lastErrorCode', 'lastErrorMessage'] as const) {\n const entry = value[field]\n if (entry !== undefined) {\n if (\n typeof entry !== 'string' || !entry ||\n entry.length > (field === 'lastErrorMessage' ? 256 : 512) ||\n /[\\u0000-\\u001f\\u007f]/.test(entry)\n ) {\n throw new Error(`Invalid peer sync progress field: ${field}`)\n }\n progress[field] = entry\n }\n }\n if (value.lastRunStatus !== undefined) {\n if (value.lastRunStatus !== 'running' && value.lastRunStatus !== 'succeeded' && value.lastRunStatus !== 'failed') {\n throw new Error('Invalid peer sync run status')\n }\n progress.lastRunStatus = value.lastRunStatus\n }\n if (value.lastResult !== undefined) {\n if (!isRecord(value.lastResult)) throw new Error('Invalid peer sync run result')\n const counts = ['imported', 'exported', 'skipped', 'pendingRemoval'] as const\n for (const field of counts) {\n if (!isNonNegativeSafeInteger(value.lastResult[field])) throw new Error(`Invalid peer sync result field: ${field}`)\n }\n const sessionCounts = ['importedSessions', 'exportedSessions', 'skippedSessions'] as const\n for (const field of sessionCounts) {\n if (value.lastResult[field] !== undefined && !isNonNegativeSafeInteger(value.lastResult[field])) {\n throw new Error(`Invalid peer sync result field: ${field}`)\n }\n }\n progress.lastResult = {\n imported: value.lastResult.imported as number,\n exported: value.lastResult.exported as number,\n skipped: value.lastResult.skipped as number,\n pendingRemoval: value.lastResult.pendingRemoval as number,\n importedSessions: (value.lastResult.importedSessions as number | undefined) ?? 0,\n exportedSessions: (value.lastResult.exportedSessions as number | undefined) ?? 0,\n skippedSessions: (value.lastResult.skippedSessions as number | undefined) ?? 0,\n }\n }\n return progress\n}\n\nfunction validateRevision(value: unknown, label: string): SyncRevision {\n if (\n !isRecord(value) ||\n !isNonNegativeSafeInteger(value.physicalTimeMs) ||\n !isNonNegativeSafeInteger(value.logicalCounter) ||\n typeof value.writerInstanceId !== 'string' ||\n !value.writerInstanceId ||\n /[\\u0000-\\u001f\\u007f]/.test(value.writerInstanceId)\n ) {\n throw new Error(`Invalid ${label} revision`)\n }\n return {\n physicalTimeMs: value.physicalTimeMs,\n logicalCounter: value.logicalCounter,\n writerInstanceId: value.writerInstanceId,\n }\n}\n\nfunction validateRecord(value: unknown): SyncRecordMetadata {\n if (!isRecord(value)) throw new Error('Invalid sync record metadata')\n if (value.kind !== 'node' && value.kind !== 'session') throw new Error('Invalid sync entity kind')\n const requiredText = ['originInstanceId', 'originEntityId', 'localEntityId', 'contentDigest'] as const\n for (const key of requiredText) {\n if (\n typeof value[key] !== 'string' ||\n !(value[key] as string).trim() ||\n (value[key] as string).length > 4096 ||\n /[\\u0000-\\u001f\\u007f]/.test(value[key] as string)\n ) {\n throw new Error(`Invalid sync record field: ${key}`)\n }\n }\n if (value.stateDigest !== undefined && (typeof value.stateDigest !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(value.stateDigest))) {\n throw new Error('Invalid sync record state digest')\n }\n validateRevision(value.revision, 'sync record')\n if (value.deleted !== undefined && typeof value.deleted !== 'boolean') throw new Error('Invalid sync tombstone')\n if (value.pendingRemoval !== undefined && typeof value.pendingRemoval !== 'boolean') {\n throw new Error('Invalid pending removal state')\n }\n if (value.credentialsRequired !== undefined && typeof value.credentialsRequired !== 'boolean') {\n throw new Error('Invalid sync credential requirement state')\n }\n if (value.workspaceOrigin !== undefined) {\n if (!isRecord(value.workspaceOrigin)) throw new Error('Invalid sync workspace origin')\n validateOrigin(value.workspaceOrigin)\n }\n if (value.importedVia !== undefined && value.importedVia !== 'peer' && value.importedVia !== 'offline') {\n throw new Error('Invalid sync import method')\n }\n if (\n value.sourceName !== undefined &&\n (typeof value.sourceName !== 'string' || value.sourceName.length > 256 || /[\\u0000-\\u001f\\u007f]/.test(value.sourceName))\n ) {\n throw new Error('Invalid sync source name')\n }\n return clone(value) as unknown as SyncRecordMetadata\n}\n\nfunction validateWorkspaceMapping(value: unknown): SyncWorkspaceMapping {\n if (!isRecord(value)) throw new Error('Invalid sync workspace mapping')\n const originInstanceId = requiredString(value.originInstanceId, 'workspace origin instance ID', 128)\n const originWorkspaceId = requiredString(value.originWorkspaceId, 'workspace origin ID', 256)\n const localWorkspaceId = requiredString(value.localWorkspaceId, 'local workspace ID', 256)\n const localPath = requiredString(value.localPath, 'local workspace path', 4096)\n const localNodeId = requiredString(value.localNodeId, 'local workspace node ID', 128)\n const remotePath = requiredString(value.remotePath, 'workspace remote path', 4096)\n const title = requiredString(value.title, 'workspace title', 256)\n if (!posix.isAbsolute(remotePath) || posix.normalize(remotePath) !== remotePath || remotePath.split('/').includes('..')) {\n throw new Error('Invalid workspace remote path')\n }\n const contentDigest = requiredString(value.contentDigest, 'workspace content digest', 256)\n if (!/^sha256:[a-f0-9]{64}$/.test(contentDigest)) throw new Error('Invalid workspace content digest')\n return {\n originInstanceId,\n originWorkspaceId,\n localWorkspaceId,\n localPath,\n localNodeId,\n remotePath,\n title,\n revision: validateRevision(value.revision, 'workspace'),\n contentDigest,\n }\n}\n\nfunction validateArchive(value: unknown, branchId: string): SyncSessionArchive {\n if (!isRecord(value) || value.branchId !== branchId || !/^b-[a-f0-9]{32}$/.test(branchId)) {\n throw new Error('Invalid sync session archive')\n }\n const contentDigest = requiredString(value.contentDigest, 'archive content digest', 256)\n const stateDigest = value.stateDigest === undefined\n ? contentDigest\n : requiredString(value.stateDigest, 'archive state digest', 256)\n const workspacePosition = value.workspacePosition === undefined ? 0 : value.workspacePosition\n if (!/^sha256:[a-f0-9]{64}$/.test(contentDigest) || !/^sha256:[a-f0-9]{64}$/.test(stateDigest) ||\n !isNonNegativeSafeInteger(workspacePosition)) throw new Error('Invalid sync session archive digest or order')\n return {\n branchId,\n originInstanceId: requiredString(value.originInstanceId, 'archive origin instance ID', 128),\n originSessionId: requiredString(value.originSessionId, 'archive origin session ID', 256),\n localSessionId: requiredString(value.localSessionId, 'archive local session ID', 256),\n revision: validateRevision(value.revision, 'archive'),\n contentDigest,\n stateDigest,\n workspacePosition,\n ...(value.workspaceId === undefined ? {} : { workspaceId: requiredString(value.workspaceId, 'archive workspace ID', 256) }),\n ...(value.workspaceOrigin === undefined ? {} : { workspaceOrigin: validateOrigin(value.workspaceOrigin) }),\n archivedAt: requiredString(value.archivedAt, 'archive timestamp', 64),\n }\n}\n\nfunction validateOrigin(value: unknown): { originInstanceId: string; originEntityId: string } {\n if (\n !isRecord(value) || typeof value.originInstanceId !== 'string' || !value.originInstanceId ||\n typeof value.originEntityId !== 'string' || !value.originEntityId ||\n value.originInstanceId.length > 512 || value.originEntityId.length > 512 ||\n /[\\u0000-\\u001f\\u007f]/.test(value.originInstanceId) || /[\\u0000-\\u001f\\u007f]/.test(value.originEntityId)\n ) throw new Error('Invalid sync origin reference')\n return { originInstanceId: value.originInstanceId, originEntityId: value.originEntityId }\n}\n\nfunction requiredString(value: unknown, label: string, maxLength: number): string {\n if (typeof value !== 'string' || !value.trim() || value.length > maxLength || /[\\u0000-\\u001f\\u007f]/.test(value)) {\n throw new Error(`Invalid ${label}`)\n }\n return value\n}\n\nfunction validateSyncState(value: unknown): SyncState {\n if (!isRecord(value) || value.version !== 1) throw new Error('Unsupported sync state version')\n if (\n !isRecord(value.clock) ||\n !isNonNegativeSafeInteger(value.clock.physicalTimeMs) ||\n !isNonNegativeSafeInteger(value.clock.logicalCounter)\n ) {\n throw new Error('Invalid sync clock state')\n }\n if (!isRecord(value.peers) || !isRecord(value.records)) throw new Error('Invalid sync state maps')\n const workspaceValues = value.workspaces === undefined ? {} : value.workspaces\n const archiveValues = value.archives === undefined ? {} : value.archives\n if (!isRecord(workspaceValues) || !isRecord(archiveValues)) throw new Error('Invalid sync session state maps')\n\n const peers = Object.create(null) as Record<string, SyncPeerProgress>\n for (const [id, progress] of Object.entries(value.peers)) {\n if (!/^p-[a-f0-9]{24}$/.test(id)) throw new Error('Invalid sync peer ID in state')\n peers[id] = validateProgress(progress)\n }\n\n const records = Object.create(null) as Record<string, SyncRecordMetadata>\n for (const [key, metadata] of Object.entries(value.records)) {\n const validated = validateRecord(metadata)\n if (key !== syncRecordKey(validated.kind, validated)) throw new Error('Sync record key does not match its origin')\n records[key] = validated\n }\n\n const workspaces = Object.create(null) as Record<string, SyncWorkspaceMapping>\n for (const [key, mapping] of Object.entries(workspaceValues)) {\n const validated = validateWorkspaceMapping(mapping)\n if (key !== syncWorkspaceKey(validated.originInstanceId, validated.originWorkspaceId, validated.remotePath)) {\n throw new Error('Sync workspace mapping key does not match its origin')\n }\n workspaces[key] = validated\n }\n\n const archives = Object.create(null) as Record<string, SyncSessionArchive>\n for (const [branchId, archive] of Object.entries(archiveValues)) archives[branchId] = validateArchive(archive, branchId)\n\n return {\n version: 1,\n clock: { physicalTimeMs: value.clock.physicalTimeMs, logicalCounter: value.clock.logicalCounter },\n peers,\n records,\n workspaces,\n archives,\n }\n}\n\nfunction emptySyncState(): SyncState {\n return { version: 1, clock: { physicalTimeMs: 0, logicalCounter: 0 }, peers: {}, records: {}, workspaces: {}, archives: {} }\n}\n\nexport class SyncStateStore {\n private state: SyncState | undefined\n private readonly home: string\n private readonly filePath: string\n\n constructor(home?: string) {\n this.home = dshHome(home)\n this.filePath = join(this.home, 'dshb', 'sync-state.json')\n }\n\n instanceId(): string {\n return getOrCreateSyncInstanceId(this.home)\n }\n\n read(): SyncState {\n return clone(this.load())\n }\n\n update(mutate: (draft: SyncState) => void): SyncState {\n const next = clone(this.load())\n mutate(next)\n const validated = validateSyncState(next)\n atomicWrite(this.filePath, `${JSON.stringify(validated, null, 2)}\\n`)\n this.state = validated\n return clone(validated)\n }\n\n nextRevision(now = Date.now()): SyncRevision {\n const clock = new HybridLogicalClock(this.instanceId(), this.load().clock)\n const revision = clock.tick(now)\n this.update((state) => {\n state.clock = clock.snapshot()\n })\n return revision\n }\n\n observeRevision(remote: SyncRevision, now = Date.now()): SyncRevision {\n const clock = new HybridLogicalClock(this.instanceId(), this.load().clock)\n const revision = clock.observe(remote, now)\n this.update((state) => {\n state.clock = clock.snapshot()\n })\n return revision\n }\n\n private load(): SyncState {\n if (this.state) return this.state\n const state = readJson(this.filePath, validateSyncState)\n this.state = state ?? emptySyncState()\n return this.state\n }\n}\n\nexport type { HlcSnapshot }\n"],"mappings":"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"}
1
+ {"version":3,"file":"stores.js","names":[],"sources":["../../../src/core/sync/stores.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\nimport {\n chmodSync,\n closeSync,\n fsyncSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join, posix } from 'node:path'\nimport { HybridLogicalClock, type HlcSnapshot, type SyncRevision } from './revision.js'\nimport {\n isNonNegativeSafeInteger,\n isRecord,\n parseSyncPeer,\n syncRecordKey,\n syncWorkspaceKey,\n type SyncPeer,\n type SyncPeerDraft,\n type SyncPeerProgress,\n type SyncRecordMetadata,\n type SyncSessionArchive,\n type SyncState,\n type SyncWorkspaceMapping,\n} from './model.js'\n\nfunction dshHome(home?: string): string {\n return home ?? process.env.DSH_HOME ?? join(homedir(), '.dsh')\n}\n\nfunction readJson<T>(file: string, validate: (value: unknown) => T): T | undefined {\n let content: string\n try {\n content = readFileSync(file, 'utf8')\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') return undefined\n throw error\n }\n let value: unknown\n try {\n value = JSON.parse(content)\n } catch {\n throw new Error(`Invalid JSON in ${basename(file)}`)\n }\n return validate(value)\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && 'code' in error\n}\n\nfunction atomicWrite(file: string, content: string): void {\n const dir = dirname(file)\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n const temporary = `${file}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`\n let fd: number | undefined\n try {\n fd = openSync(temporary, 'wx', 0o600)\n writeFileSync(fd, content, 'utf8')\n fsyncSync(fd)\n closeSync(fd)\n fd = undefined\n renameSync(temporary, file)\n chmodSync(file, 0o600)\n } catch (error) {\n if (fd !== undefined) closeSync(fd)\n try {\n unlinkSync(temporary)\n } catch {}\n throw error\n }\n}\n\nfunction clone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T\n}\n\nfunction instanceIdFile(home: string): string {\n return join(home, 'dshb', 'instance-id')\n}\n\nfunction parseInstanceId(value: string): string {\n const id = value.trim()\n if (!/^i-[a-f0-9]{32}$/.test(id)) throw new Error('Invalid DSHB sync instance ID')\n return id\n}\n\nexport function getOrCreateSyncInstanceId(home?: string): string {\n const root = dshHome(home)\n const file = instanceIdFile(root)\n try {\n return parseInstanceId(readFileSync(file, 'utf8'))\n } catch (error) {\n if (!(isNodeError(error) && error.code === 'ENOENT')) throw error\n }\n\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 })\n const candidate = `i-${randomBytes(16).toString('hex')}`\n let fd: number | undefined\n try {\n fd = openSync(file, 'wx', 0o600)\n writeFileSync(fd, `${candidate}\\n`, 'utf8')\n fsyncSync(fd)\n closeSync(fd)\n fd = undefined\n chmodSync(file, 0o600)\n return candidate\n } catch (error) {\n if (fd !== undefined) closeSync(fd)\n if (isNodeError(error) && error.code === 'EEXIST') {\n return parseInstanceId(readFileSync(file, 'utf8'))\n }\n throw error\n }\n}\n\nfunction newPeerId(): string {\n return `p-${randomBytes(12).toString('hex')}`\n}\n\nfunction validatePeers(value: unknown): SyncPeer[] {\n if (!Array.isArray(value)) throw new Error('Invalid sync peer store')\n const peers = value.map(parseSyncPeer)\n if (new Set(peers.map((peer) => peer.id)).size !== peers.length) {\n throw new Error('Sync peer store contains duplicate IDs')\n }\n return peers\n}\n\nexport class SyncPeerStore {\n private peers: SyncPeer[] | undefined\n private readonly filePath: string\n\n constructor(home?: string) {\n this.filePath = join(dshHome(home), 'dshb', 'sync-peers.json')\n }\n\n list(): SyncPeer[] {\n return clone(this.load())\n }\n\n get(id: string): SyncPeer | undefined {\n const peer = this.load().find((item) => item.id === id)\n return peer ? clone(peer) : undefined\n }\n\n create(draft: SyncPeerDraft): SyncPeer {\n const peer = parseSyncPeer({ ...draft, id: newPeerId() })\n const next = [...this.load(), peer]\n this.save(next)\n return clone(peer)\n }\n\n update(id: string, patch: Partial<Omit<SyncPeer, 'id'>>): SyncPeer {\n const current = this.load()\n const index = current.findIndex((peer) => peer.id === id)\n if (index < 0) throw new Error(`sync peer ${id} not found`)\n const updated = parseSyncPeer({ ...current[index], ...patch, id })\n const next = [...current]\n next[index] = updated\n this.save(next)\n return clone(updated)\n }\n\n remove(id: string): boolean {\n const current = this.load()\n const next = current.filter((peer) => peer.id !== id)\n if (next.length === current.length) return false\n this.save(next)\n return true\n }\n\n private load(): SyncPeer[] {\n if (this.peers) return this.peers\n const stored = readJson(this.filePath, validatePeers)\n this.peers = stored ?? []\n return this.peers\n }\n\n private save(peers: SyncPeer[]): void {\n atomicWrite(this.filePath, `${JSON.stringify(peers, null, 2)}\\n`)\n this.peers = peers\n }\n}\n\nfunction validateProgress(value: unknown): SyncPeerProgress {\n if (!isRecord(value)) throw new Error('Invalid peer sync progress')\n const progress: SyncPeerProgress = {}\n for (const field of ['lastRunAt', 'lastSuccessAt', 'lastManifestDigest', 'watermark', 'lastErrorCode', 'lastErrorMessage'] as const) {\n const entry = value[field]\n if (entry !== undefined) {\n if (\n typeof entry !== 'string' || !entry ||\n entry.length > (field === 'lastErrorMessage' ? 256 : 512) ||\n /[\\u0000-\\u001f\\u007f]/.test(entry)\n ) {\n throw new Error(`Invalid peer sync progress field: ${field}`)\n }\n progress[field] = entry\n }\n }\n if (value.lastRunStatus !== undefined) {\n if (value.lastRunStatus !== 'running' && value.lastRunStatus !== 'succeeded' && value.lastRunStatus !== 'failed' && value.lastRunStatus !== 'stopped') {\n throw new Error('Invalid peer sync run status')\n }\n progress.lastRunStatus = value.lastRunStatus\n }\n if (value.lastResult !== undefined) {\n if (!isRecord(value.lastResult)) throw new Error('Invalid peer sync run result')\n const counts = ['imported', 'exported', 'skipped', 'pendingRemoval'] as const\n for (const field of counts) {\n if (!isNonNegativeSafeInteger(value.lastResult[field])) throw new Error(`Invalid peer sync result field: ${field}`)\n }\n const sessionCounts = ['importedSessions', 'exportedSessions', 'skippedSessions'] as const\n for (const field of sessionCounts) {\n if (value.lastResult[field] !== undefined && !isNonNegativeSafeInteger(value.lastResult[field])) {\n throw new Error(`Invalid peer sync result field: ${field}`)\n }\n }\n progress.lastResult = {\n imported: value.lastResult.imported as number,\n exported: value.lastResult.exported as number,\n skipped: value.lastResult.skipped as number,\n pendingRemoval: value.lastResult.pendingRemoval as number,\n importedSessions: (value.lastResult.importedSessions as number | undefined) ?? 0,\n exportedSessions: (value.lastResult.exportedSessions as number | undefined) ?? 0,\n skippedSessions: (value.lastResult.skippedSessions as number | undefined) ?? 0,\n }\n }\n return progress\n}\n\nfunction validateRevision(value: unknown, label: string): SyncRevision {\n if (\n !isRecord(value) ||\n !isNonNegativeSafeInteger(value.physicalTimeMs) ||\n !isNonNegativeSafeInteger(value.logicalCounter) ||\n typeof value.writerInstanceId !== 'string' ||\n !value.writerInstanceId ||\n /[\\u0000-\\u001f\\u007f]/.test(value.writerInstanceId)\n ) {\n throw new Error(`Invalid ${label} revision`)\n }\n return {\n physicalTimeMs: value.physicalTimeMs,\n logicalCounter: value.logicalCounter,\n writerInstanceId: value.writerInstanceId,\n }\n}\n\nfunction validateRecord(value: unknown): SyncRecordMetadata {\n if (!isRecord(value)) throw new Error('Invalid sync record metadata')\n if (value.kind !== 'node' && value.kind !== 'session') throw new Error('Invalid sync entity kind')\n const requiredText = ['originInstanceId', 'originEntityId', 'localEntityId', 'contentDigest'] as const\n for (const key of requiredText) {\n if (\n typeof value[key] !== 'string' ||\n !(value[key] as string).trim() ||\n (value[key] as string).length > 4096 ||\n /[\\u0000-\\u001f\\u007f]/.test(value[key] as string)\n ) {\n throw new Error(`Invalid sync record field: ${key}`)\n }\n }\n if (value.stateDigest !== undefined && (typeof value.stateDigest !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(value.stateDigest))) {\n throw new Error('Invalid sync record state digest')\n }\n validateRevision(value.revision, 'sync record')\n if (value.deleted !== undefined && typeof value.deleted !== 'boolean') throw new Error('Invalid sync tombstone')\n if (value.pendingRemoval !== undefined && typeof value.pendingRemoval !== 'boolean') {\n throw new Error('Invalid pending removal state')\n }\n if (value.credentialsRequired !== undefined && typeof value.credentialsRequired !== 'boolean') {\n throw new Error('Invalid sync credential requirement state')\n }\n if (value.workspaceOrigin !== undefined) {\n if (!isRecord(value.workspaceOrigin)) throw new Error('Invalid sync workspace origin')\n validateOrigin(value.workspaceOrigin)\n }\n if (value.importedVia !== undefined && value.importedVia !== 'peer' && value.importedVia !== 'offline') {\n throw new Error('Invalid sync import method')\n }\n if (\n value.sourceName !== undefined &&\n (typeof value.sourceName !== 'string' || value.sourceName.length > 256 || /[\\u0000-\\u001f\\u007f]/.test(value.sourceName))\n ) {\n throw new Error('Invalid sync source name')\n }\n return clone(value) as unknown as SyncRecordMetadata\n}\n\nfunction validateWorkspaceMapping(value: unknown): SyncWorkspaceMapping {\n if (!isRecord(value)) throw new Error('Invalid sync workspace mapping')\n const originInstanceId = requiredString(value.originInstanceId, 'workspace origin instance ID', 128)\n const originWorkspaceId = requiredString(value.originWorkspaceId, 'workspace origin ID', 256)\n const localWorkspaceId = requiredString(value.localWorkspaceId, 'local workspace ID', 256)\n const localPath = requiredString(value.localPath, 'local workspace path', 4096)\n const localNodeId = requiredString(value.localNodeId, 'local workspace node ID', 128)\n const remotePath = requiredString(value.remotePath, 'workspace remote path', 4096)\n const title = requiredString(value.title, 'workspace title', 256)\n if (!posix.isAbsolute(remotePath) || posix.normalize(remotePath) !== remotePath || remotePath.split('/').includes('..')) {\n throw new Error('Invalid workspace remote path')\n }\n const contentDigest = requiredString(value.contentDigest, 'workspace content digest', 256)\n if (!/^sha256:[a-f0-9]{64}$/.test(contentDigest)) throw new Error('Invalid workspace content digest')\n return {\n originInstanceId,\n originWorkspaceId,\n localWorkspaceId,\n localPath,\n localNodeId,\n remotePath,\n title,\n revision: validateRevision(value.revision, 'workspace'),\n contentDigest,\n }\n}\n\nfunction validateArchive(value: unknown, branchId: string): SyncSessionArchive {\n if (!isRecord(value) || value.branchId !== branchId || !/^b-[a-f0-9]{32}$/.test(branchId)) {\n throw new Error('Invalid sync session archive')\n }\n const contentDigest = requiredString(value.contentDigest, 'archive content digest', 256)\n const stateDigest = value.stateDigest === undefined\n ? contentDigest\n : requiredString(value.stateDigest, 'archive state digest', 256)\n const workspacePosition = value.workspacePosition === undefined ? 0 : value.workspacePosition\n if (!/^sha256:[a-f0-9]{64}$/.test(contentDigest) || !/^sha256:[a-f0-9]{64}$/.test(stateDigest) ||\n !isNonNegativeSafeInteger(workspacePosition)) throw new Error('Invalid sync session archive digest or order')\n return {\n branchId,\n originInstanceId: requiredString(value.originInstanceId, 'archive origin instance ID', 128),\n originSessionId: requiredString(value.originSessionId, 'archive origin session ID', 256),\n localSessionId: requiredString(value.localSessionId, 'archive local session ID', 256),\n revision: validateRevision(value.revision, 'archive'),\n contentDigest,\n stateDigest,\n workspacePosition,\n ...(value.workspaceId === undefined ? {} : { workspaceId: requiredString(value.workspaceId, 'archive workspace ID', 256) }),\n ...(value.workspaceOrigin === undefined ? {} : { workspaceOrigin: validateOrigin(value.workspaceOrigin) }),\n archivedAt: requiredString(value.archivedAt, 'archive timestamp', 64),\n }\n}\n\nfunction validateOrigin(value: unknown): { originInstanceId: string; originEntityId: string } {\n if (\n !isRecord(value) || typeof value.originInstanceId !== 'string' || !value.originInstanceId ||\n typeof value.originEntityId !== 'string' || !value.originEntityId ||\n value.originInstanceId.length > 512 || value.originEntityId.length > 512 ||\n /[\\u0000-\\u001f\\u007f]/.test(value.originInstanceId) || /[\\u0000-\\u001f\\u007f]/.test(value.originEntityId)\n ) throw new Error('Invalid sync origin reference')\n return { originInstanceId: value.originInstanceId, originEntityId: value.originEntityId }\n}\n\nfunction requiredString(value: unknown, label: string, maxLength: number): string {\n if (typeof value !== 'string' || !value.trim() || value.length > maxLength || /[\\u0000-\\u001f\\u007f]/.test(value)) {\n throw new Error(`Invalid ${label}`)\n }\n return value\n}\n\nfunction validateSyncState(value: unknown): SyncState {\n if (!isRecord(value) || value.version !== 1) throw new Error('Unsupported sync state version')\n if (\n !isRecord(value.clock) ||\n !isNonNegativeSafeInteger(value.clock.physicalTimeMs) ||\n !isNonNegativeSafeInteger(value.clock.logicalCounter)\n ) {\n throw new Error('Invalid sync clock state')\n }\n if (!isRecord(value.peers) || !isRecord(value.records)) throw new Error('Invalid sync state maps')\n const workspaceValues = value.workspaces === undefined ? {} : value.workspaces\n const archiveValues = value.archives === undefined ? {} : value.archives\n if (!isRecord(workspaceValues) || !isRecord(archiveValues)) throw new Error('Invalid sync session state maps')\n\n const peers = Object.create(null) as Record<string, SyncPeerProgress>\n for (const [id, progress] of Object.entries(value.peers)) {\n if (!/^p-[a-f0-9]{24}$/.test(id)) throw new Error('Invalid sync peer ID in state')\n peers[id] = validateProgress(progress)\n }\n\n const records = Object.create(null) as Record<string, SyncRecordMetadata>\n for (const [key, metadata] of Object.entries(value.records)) {\n const validated = validateRecord(metadata)\n if (key !== syncRecordKey(validated.kind, validated)) throw new Error('Sync record key does not match its origin')\n records[key] = validated\n }\n\n const workspaces = Object.create(null) as Record<string, SyncWorkspaceMapping>\n for (const [key, mapping] of Object.entries(workspaceValues)) {\n const validated = validateWorkspaceMapping(mapping)\n if (key !== syncWorkspaceKey(validated.originInstanceId, validated.originWorkspaceId, validated.remotePath)) {\n throw new Error('Sync workspace mapping key does not match its origin')\n }\n workspaces[key] = validated\n }\n\n const archives = Object.create(null) as Record<string, SyncSessionArchive>\n for (const [branchId, archive] of Object.entries(archiveValues)) archives[branchId] = validateArchive(archive, branchId)\n\n return {\n version: 1,\n clock: { physicalTimeMs: value.clock.physicalTimeMs, logicalCounter: value.clock.logicalCounter },\n peers,\n records,\n workspaces,\n archives,\n }\n}\n\nfunction emptySyncState(): SyncState {\n return { version: 1, clock: { physicalTimeMs: 0, logicalCounter: 0 }, peers: {}, records: {}, workspaces: {}, archives: {} }\n}\n\nexport class SyncStateStore {\n private state: SyncState | undefined\n private readonly home: string\n private readonly filePath: string\n\n constructor(home?: string) {\n this.home = dshHome(home)\n this.filePath = join(this.home, 'dshb', 'sync-state.json')\n }\n\n instanceId(): string {\n return getOrCreateSyncInstanceId(this.home)\n }\n\n read(): SyncState {\n return clone(this.load())\n }\n\n update(mutate: (draft: SyncState) => void): SyncState {\n const next = clone(this.load())\n mutate(next)\n const validated = validateSyncState(next)\n atomicWrite(this.filePath, `${JSON.stringify(validated, null, 2)}\\n`)\n this.state = validated\n return clone(validated)\n }\n\n nextRevision(now = Date.now()): SyncRevision {\n const clock = new HybridLogicalClock(this.instanceId(), this.load().clock)\n const revision = clock.tick(now)\n this.update((state) => {\n state.clock = clock.snapshot()\n })\n return revision\n }\n\n observeRevision(remote: SyncRevision, now = Date.now()): SyncRevision {\n const clock = new HybridLogicalClock(this.instanceId(), this.load().clock)\n const revision = clock.observe(remote, now)\n this.update((state) => {\n state.clock = clock.snapshot()\n })\n return revision\n }\n\n private load(): SyncState {\n if (this.state) return this.state\n const state = readJson(this.filePath, validateSyncState)\n this.state = state ?? emptySyncState()\n return this.state\n }\n}\n\nexport type { HlcSnapshot }\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { o as NodeRegistry } from "../node-registry-BTnnM5-k.js";
2
2
  import { r as WorkspaceBindingsStore } from "../workspace-bindings-CsB0TIJB.js";
3
- import { s as SyncNodeService } from "../node-sync-3Z9QLz2n.js";
3
+ import { s as SyncNodeService } from "../node-sync-C3Ym-HDu.js";
4
4
  import { Context } from "@deepseek-ai/cordis";
5
5
  //#region src/core/workspaces.d.ts
6
6
  declare function registerWorkspaceRoutes(ctx: Context, registry: NodeRegistry, bindings: WorkspaceBindingsStore, syncNodes?: SyncNodeService): void;
@@ -1,4 +1,4 @@
1
- import { a as SyncPeer } from "./model-BP7Gw1yk.js";
1
+ import { a as SyncPeer } from "./model-CKTBf-Pl.js";
2
2
  //#region src/core/sync/host-key.d.ts
3
3
  type SyncHostKeyDecision = 'trusted' | 'untrusted' | 'changed';
4
4
  declare class SyncHostKeyChallengeError extends Error {
@@ -27,4 +27,4 @@ declare class SyncHostKeyChallenges {
27
27
  }
28
28
  //#endregion
29
29
  export { inspectSyncHostKey as a, SyncHostKeyDecision as i, SyncHostKeyChallengeExpiredError as n, isOpenSshFingerprint as o, SyncHostKeyChallenges as r, opensshFingerprintFromSha256Hex as s, SyncHostKeyChallengeError as t };
30
- //# sourceMappingURL=host-key-C5afhAUd.d.ts.map
30
+ //# sourceMappingURL=host-key-CSnpIrjq.d.ts.map
@@ -42,7 +42,7 @@ interface SyncRecordMetadata extends SyncProvenance {
42
42
  }
43
43
  interface SyncPeerProgress {
44
44
  lastRunAt?: string;
45
- lastRunStatus?: 'running' | 'succeeded' | 'failed';
45
+ lastRunStatus?: 'running' | 'succeeded' | 'failed' | 'stopped';
46
46
  lastSuccessAt?: string;
47
47
  lastManifestDigest?: string;
48
48
  watermark?: string;
@@ -101,4 +101,4 @@ declare function isRecord(value: unknown): value is Record<string, unknown>;
101
101
  declare function isNonNegativeSafeInteger(value: unknown): value is number;
102
102
  //#endregion
103
103
  export { parseSyncPeerDraft as _, SyncPeer as a, SyncProvenance as c, SyncSessionArchive as d, SyncState as f, parseSyncPeer as g, isRecord as h, SyncOrigin as i, SyncRecordMetadata as l, isNonNegativeSafeInteger as m, SyncEntityKind as n, SyncPeerDraft as o, SyncWorkspaceMapping as p, SyncImportMethod as r, SyncPeerProgress as s, SyncDirection as t, SyncSchedule as u, syncRecordKey as v, syncWorkspaceKey as y };
104
- //# sourceMappingURL=model-BP7Gw1yk.d.ts.map
104
+ //# sourceMappingURL=model-CKTBf-Pl.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { c as NodeSsh, o as NodeRegistry, r as NodeDocker, s as NodeSecrets } from "./node-registry-BTnnM5-k.js";
2
2
  import { r as WorkspaceBindingsStore } from "./workspace-bindings-CsB0TIJB.js";
3
- import { l as SyncRecordMetadata, r as SyncImportMethod } from "./model-BP7Gw1yk.js";
4
- import { n as SyncStateStore } from "./stores-CKyEt0yk.js";
3
+ import { l as SyncRecordMetadata, r as SyncImportMethod } from "./model-CKTBf-Pl.js";
4
+ import { n as SyncStateStore } from "./stores-puyTC075.js";
5
5
  //#region src/core/sync/node-sync.d.ts
6
6
  interface SyncNodeProfile {
7
7
  name: string;
@@ -86,4 +86,4 @@ declare class SyncNodeService {
86
86
  }
87
87
  //#endregion
88
88
  export { SyncNodeManifestError as a, SyncNodeView as c, SyncNodeManifest as i, SyncNodeEntry as n, SyncNodeProfile as o, SyncNodeInUseError as r, SyncNodeService as s, SyncNodeApplyResult as t };
89
- //# sourceMappingURL=node-sync-3Z9QLz2n.d.ts.map
89
+ //# sourceMappingURL=node-sync-C3Ym-HDu.d.ts.map
@@ -1,4 +1,4 @@
1
- import { r as SyncImportMethod } from "./model-BP7Gw1yk.js";
1
+ import { r as SyncImportMethod } from "./model-CKTBf-Pl.js";
2
2
  //#region src/core/sync/session-journal.d.ts
3
3
  declare class SyncSessionJournalStore {
4
4
  private readonly home;
@@ -29,6 +29,12 @@ declare class SyncSessionJournalStore {
29
29
  writePrepared(batchId: string, recordKey: string, value: unknown): void;
30
30
  readPrepared(batchId: string, recordKey: string): unknown | undefined;
31
31
  complete(batchId: string): void;
32
+ /**
33
+ * 丢弃一个未完成批次:删除批次记录与暂存目录。用于手动停止同步——
34
+ * 该批未应用的条目不再落地,重启后也不会被 recoverPending 回放。
35
+ * 已应用完成的条目不受影响(它们是完整会话,不是半文件)。
36
+ */
37
+ discard(batchId: string): void;
32
38
  cleanupOrphanedStaging(): void;
33
39
  private preparedPath;
34
40
  private bundlePath;
@@ -37,4 +43,4 @@ declare class SyncSessionJournalStore {
37
43
  }
38
44
  //#endregion
39
45
  export { SyncSessionJournalStore as t };
40
- //# sourceMappingURL=session-journal-pgPqcWn9.d.ts.map
46
+ //# sourceMappingURL=session-journal-VE_1tA2-.d.ts.map
@@ -1864,6 +1864,20 @@ var SyncSessionService = class SyncSessionService {
1864
1864
  pendingPeerIds() {
1865
1865
  return this.journal.pendingPeerIds();
1866
1866
  }
1867
+ /**
1868
+ * 丢弃指定对端的全部未完成 journal 批次(含正在应用中的当前批)。
1869
+ * 手动停止同步时调用:未应用的条目不再落地,重启后也不会回放;
1870
+ * 已应用完成的条目不受影响。
1871
+ */
1872
+ discardPendingBatches(peerId) {
1873
+ let discarded = 0;
1874
+ for (const batch of this.journal.pendingBatches()) {
1875
+ if (batch.peerId !== peerId) continue;
1876
+ this.journal.discard(batch.batchId);
1877
+ discarded += 1;
1878
+ }
1879
+ return discarded;
1880
+ }
1867
1881
  async recoverPending() {
1868
1882
  const failures = [];
1869
1883
  for (const batch of this.journal.pendingBatches()) try {
@@ -3384,4 +3398,4 @@ var SyncSessionService = class SyncSessionService {
3384
3398
  //#endregion
3385
3399
  export { estimateJsonBytes as a, planSessionTransfers as c, validateObjectPayload as d, validateSyncSessionBundle as f, SyncSessionService as i, sessionEventListDigest as l, SyncSessionError as n, makeSyncSessionBundle as o, validateSyncSessionManifest as p, SyncSessionObjectInbox as r, makeSyncSessionManifest as s, ExportObjectCache as t, syncSessionEntryKey as u };
3386
3400
 
3387
- //# sourceMappingURL=session-sync-CIqjuWkh.js.map
3401
+ //# sourceMappingURL=session-sync-DLr_BdvN.js.map