dsh-dispatch 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/approvals.ts","../../shared/src/crypto.ts","../../shared/src/limits.ts","../src/commands.ts","../src/dispatch.ts","../src/pairing.ts","../src/worktree.ts","../src/hub.ts","../src/relay-client.ts","../src/sessions.ts","../src/version.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["/**\n * Approval forwarding — the M1 core.\n *\n * Registers a waterfall answerer on `approval/request` and races three ways:\n * the phone's decision, a local answer arriving through `next()`, and the\n * request's own abort. A timeout is deliberately NOT one of them: docs/PROTOCOL.md\n * invariant 1 forbids auto-allow, so an unanswered question stays pending and\n * is re-pushed instead.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { Agent } from '@deepseek-ai/dsh-agent';\nimport type { CallId } from '@deepseek-ai/dsh-llm';\nimport type { ApprovalOutcome, ApprovalRequest } from '@deepseek-ai/dsh-user-approval';\nimport { clampBytes, MAX_DETAIL_CHARS, MAX_FIELD_BYTES, truncate } from '@dsh-dispatch/shared';\nimport type {\n ApprovalRequestMsg,\n ApprovalRespondMsg,\n ApprovalResolution,\n PushCompactMsg,\n} from '@dsh-dispatch/shared';\nimport type { Hub } from './hub.js';\n\nconst TITLE_CHARS = 200;\n/** How often an unanswered approval nudges the phone again. */\nconst REMINDER_MS = 5 * 60_000;\n/** Stop nudging after this many reminders; the question still stays open. */\nconst MAX_REMINDERS = 6;\n\ntype Settle = (outcome: ApprovalOutcome, resolution: ApprovalResolution) => void;\n\ninterface Pending {\n readonly frame: ApprovalRequestMsg;\n readonly push: PushCompactMsg;\n readonly settle: Settle;\n reminders: number;\n timer: ReturnType<typeof setTimeout> | undefined;\n}\n\nexport interface ApprovalRouter {\n /** Apply a phone decision. Unknown ids are answered `expired` so cards clear. */\n respond(message: ApprovalRespondMsg): void;\n /** Re-push every still-open question after a reconnect. */\n resendPending(): void;\n openCount(): number;\n}\n\nexport function installApprovals(hub: Hub): ApprovalRouter {\n const pending = new Map<string, Pending>();\n\n hub.ctx.on('approval/request', (request, next) => {\n // Nothing to forward to: delegate immediately so an unpaired or offline\n // machine pays zero added latency on its local approval UI.\n if (!hub.pairing.everPaired || !hub.relay.connected) return next();\n if (request.signal?.aborted === true) return Promise.resolve<ApprovalOutcome>('cancelled');\n return forward(hub, pending, request, next);\n });\n\n return {\n respond(message: ApprovalRespondMsg): void {\n const entry = pending.get(message.approvalId);\n if (entry === undefined) {\n // Double-tap, or the desktop already answered. Tell the phone to clear.\n hub.relay.send(closedMsg(message.approvalId, 'expired'));\n return;\n }\n entry.settle(message.decision === 'allow' ? 'allowed-once' : 'rejected', message.decision);\n },\n resendPending(): void {\n for (const entry of pending.values()) hub.relay.send(entry.frame, entry.push);\n },\n openCount: () => pending.size,\n };\n}\n\nfunction forward(\n hub: Hub,\n pending: Map<string, Pending>,\n request: ApprovalRequest,\n next: () => Promise<ApprovalOutcome>,\n): Promise<ApprovalOutcome> {\n const approvalId = randomUUID();\n const sessionId = request.agent.id;\n const frame = buildRequestMsg(approvalId, sessionId, request);\n const push = buildPush(sessionId, frame.title);\n if (!hub.relay.send(frame, push)) {\n hub.report('approval', `审批 ${frame.title} 未能推送到手机(relay 未连接),已交回本机处理`);\n return next();\n }\n return race(hub, pending, { approvalId, sessionId, frame, push }, request, next);\n}\n\ninterface Forwarded {\n readonly approvalId: string;\n readonly sessionId: string;\n readonly frame: ApprovalRequestMsg;\n readonly push: PushCompactMsg;\n}\n\nfunction race(\n hub: Hub,\n pending: Map<string, Pending>,\n forwarded: Forwarded,\n request: ApprovalRequest,\n next: () => Promise<ApprovalOutcome>,\n): Promise<ApprovalOutcome> {\n const { approvalId, sessionId } = forwarded;\n return new Promise<ApprovalOutcome>((resolve) => {\n let settled = false;\n const settle: Settle = (outcome, resolution) => {\n if (settled) return;\n settled = true;\n const entry = pending.get(approvalId);\n if (entry?.timer !== undefined) clearTimeout(entry.timer);\n pending.delete(approvalId);\n request.signal?.removeEventListener('abort', onAbort);\n hub.sessions.approvalClosed(sessionId);\n hub.relay.send(closedMsg(approvalId, resolution));\n resolve(outcome);\n };\n function onAbort(): void {\n settle('cancelled', 'superseded');\n }\n request.signal?.addEventListener('abort', onAbort, { once: true });\n const entry: Pending = { ...forwarded, settle, reminders: 0, timer: undefined };\n pending.set(approvalId, entry);\n hub.sessions.approvalOpened(sessionId);\n armReminder(hub, entry);\n // `unavailable` means nobody downstream answered — keep waiting for the\n // phone. Any real decision means a human answered on the desktop first.\n void next().then(\n (outcome) => { if (outcome !== 'unavailable') settle(outcome, 'local'); },\n (error: unknown) => { hub.fail(`approval ${approvalId}: local answerer failed`, error); },\n );\n });\n}\n\n/** Never auto-allow; nudge instead, then go quiet while staying open. */\nfunction armReminder(hub: Hub, entry: Pending): void {\n entry.timer = setTimeout(() => {\n entry.timer = undefined;\n entry.reminders += 1;\n hub.relay.send(entry.frame, entry.push);\n if (entry.reminders >= MAX_REMINDERS) {\n hub.log.warn(\n 'approval %s still unanswered after %d reminders; it stays open (never auto-approved)',\n entry.frame.approvalId, entry.reminders,\n );\n return;\n }\n armReminder(hub, entry);\n }, REMINDER_MS);\n entry.timer.unref?.();\n}\n\nfunction buildRequestMsg(\n approvalId: string,\n sessionId: string,\n request: ApprovalRequest,\n): ApprovalRequestMsg {\n return {\n v: 1,\n ts: Date.now(),\n type: 'approval.request',\n approvalId,\n sessionId,\n title: truncate(request.toolName, TITLE_CHARS),\n // Chars first (protocol conformance), then bytes: 8 192 chars of CJK is\n // ~24 KB of UTF-8, which base64 would expand past the 16 KB envelope.\n detail: clampBytes(truncate(buildDetail(request), MAX_DETAIL_CHARS), MAX_FIELD_BYTES),\n createdAt: Date.now(),\n };\n}\n\n/**\n * The phone must be able to see exactly what it is allowing before allowing it\n * (docs/PRODUCT.md 防呆). `ApprovalRequest` deliberately omits tool arguments —\n * they live on the already-logged `tool/call` this `callId` points at.\n */\nfunction buildDetail(request: ApprovalRequest): string {\n const parts = [`工具: ${request.toolName}`];\n if (request.reason !== undefined && request.reason !== '') parts.push(`原因: ${request.reason}`);\n const args = toolCallArguments(request.agent, request.callId);\n parts.push(args === undefined ? '参数: (调用未记录参数)' : `参数:\\n${args}`);\n return parts.join('\\n\\n');\n}\n\nfunction toolCallArguments(agent: Agent, callId: CallId | undefined): string | undefined {\n if (callId === undefined) return undefined;\n const events = agent.session.events;\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n if (event?.type === 'tool/call' && event.data.callId === callId) return event.data.arguments;\n }\n return undefined;\n}\n\nfunction buildPush(sessionId: string, title: string): PushCompactMsg {\n return { v: 1, ts: Date.now(), type: 'push', kind: 'approval', sessionId, title };\n}\n\nfunction closedMsg(approvalId: string, resolution: ApprovalResolution) {\n return { v: 1, ts: Date.now(), type: 'approval.closed', approvalId, resolution } as const;\n}\n","// E2E crypto for dsh-dispatch. Isomorphic (Node + browser) — tweetnacl only.\n// Key/room derivation uses SHA-512 (nacl.hash) truncated; see docs/PROTOCOL.md.\n\nimport nacl from 'tweetnacl';\nimport type { PairingInfo } from './types.js';\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\nconst KEY_PREFIX = encoder.encode('dsh-dispatch/key');\nconst ROOM_PREFIX = encoder.encode('dsh-dispatch/room');\n\nconst NONCE_LENGTH = nacl.secretbox.nonceLength;\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n const out = new Uint8Array(a.length + b.length);\n out.set(a, 0);\n out.set(b, a.length);\n return out;\n}\n\nconst B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\nexport function toBase64(bytes: Uint8Array): string {\n let out = '';\n for (let i = 0; i < bytes.length; i += 3) {\n const b0 = bytes[i]!;\n const b1 = bytes[i + 1];\n const b2 = bytes[i + 2];\n out += B64_ALPHABET[b0 >> 2]!;\n out += B64_ALPHABET[((b0 & 3) << 4) | ((b1 ?? 0) >> 4)]!;\n out += b1 === undefined ? '=' : B64_ALPHABET[((b1 & 15) << 2) | ((b2 ?? 0) >> 6)]!;\n out += b2 === undefined ? '=' : B64_ALPHABET[b2 & 63]!;\n }\n return out;\n}\n\nexport function fromBase64(text: string): Uint8Array | null {\n const clean = text.replace(/=+$/, '');\n if (!/^[A-Za-z0-9+/]*$/.test(clean)) return null;\n const out = new Uint8Array(Math.floor((clean.length * 3) / 4));\n let bits = 0;\n let value = 0;\n let index = 0;\n for (const char of clean) {\n value = (value << 6) | B64_ALPHABET.indexOf(char);\n bits += 6;\n if (bits >= 8) {\n bits -= 8;\n out[index++] = (value >> bits) & 0xff;\n }\n }\n return out.slice(0, index);\n}\n\nexport function toBase64Url(bytes: Uint8Array): string {\n return toBase64(bytes).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport function fromBase64Url(text: string): Uint8Array | null {\n return fromBase64(text.replace(/-/g, '+').replace(/_/g, '/'));\n}\n\nexport function generateSecret(): Uint8Array {\n return nacl.randomBytes(32);\n}\n\nexport function deriveKey(secret: Uint8Array): Uint8Array {\n return nacl.hash(concat(KEY_PREFIX, secret)).slice(0, nacl.secretbox.keyLength);\n}\n\nexport function deriveRoomId(secret: Uint8Array): string {\n return toBase64Url(nacl.hash(concat(ROOM_PREFIX, secret)).slice(0, 16));\n}\n\nexport function seal(message: unknown, key: Uint8Array): string {\n const nonce = nacl.randomBytes(NONCE_LENGTH);\n const box = nacl.secretbox(encoder.encode(JSON.stringify(message)), nonce, key);\n return toBase64(concat(nonce, box));\n}\n\n// Returns null on any tamper/garbage/wrong-key input — caller MUST surface it, not drop it.\nexport function open(payload: string, key: Uint8Array): unknown | null {\n const bytes = fromBase64(payload);\n if (bytes === null || bytes.length < NONCE_LENGTH + nacl.secretbox.overheadLength) return null;\n const box = nacl.secretbox.open(bytes.slice(NONCE_LENGTH), bytes.slice(0, NONCE_LENGTH), key);\n if (box === null) return null;\n try {\n return JSON.parse(decoder.decode(box));\n } catch {\n return null;\n }\n}\n\nexport function encodePairing(info: PairingInfo): string {\n const json = JSON.stringify({\n v: 1,\n relay: info.relay,\n secret: toBase64Url(info.secret),\n machine: info.machine,\n });\n return toBase64Url(encoder.encode(json));\n}\n\nexport function decodePairing(code: string): PairingInfo | null {\n const bytes = fromBase64Url(code.trim());\n if (bytes === null) return null;\n try {\n const parsed = JSON.parse(decoder.decode(bytes)) as {\n v?: number;\n relay?: string;\n secret?: string;\n machine?: string;\n };\n if (parsed.v !== 1 || !parsed.relay || !parsed.secret || !parsed.machine) return null;\n const secret = fromBase64Url(parsed.secret);\n if (secret === null || secret.length !== 32) return null;\n return { relay: parsed.relay, secret, machine: parsed.machine };\n } catch {\n return null;\n }\n}\n","// Payload limits from docs/PROTOCOL.md. The sender truncates BEFORE encryption.\n// The relay imports THIS FILE by subpath (src/limits.js) to keep tweetnacl out of its\n// bundle — if this package ever gains a package.json \"exports\" map, add a matching entry.\n\nexport const MAX_ENVELOPE_BYTES = 16 * 1024;\nexport const MAX_DETAIL_CHARS = 8 * 1024;\nexport const MAX_SUMMARY_CHARS = 4 * 1024;\n// Char limits alone don't bound the envelope: CJK is ~3 bytes/char in UTF-8.\n// Free-text fields are clamped by chars first, then by bytes (clampBytes) before seal.\nexport const MAX_PROMPT_CHARS = 8 * 1024;\nexport const MAX_PROMPT_BYTES = 10 * 1024;\nexport const MAX_FIELD_BYTES = 10 * 1024;\nexport const MAX_PUSH_BYTES = 3 * 1024;\nexport const TRUNCATION_SUFFIX = '…[truncated]';\n\nexport function truncate(text: string, maxChars: number): string {\n if (text.length <= maxChars) return text;\n return text.slice(0, maxChars - TRUNCATION_SUFFIX.length) + TRUNCATION_SUFFIX;\n}\n\nconst byteEncoder = new TextEncoder();\n\nexport function utf8Length(text: string): number {\n return byteEncoder.encode(text).length;\n}\n\n// Clamp to a UTF-8 byte budget without splitting a multi-byte char or surrogate pair.\nexport function clampBytes(text: string, maxBytes: number): string {\n if (utf8Length(text) <= maxBytes) return text;\n const budget = maxBytes - utf8Length(TRUNCATION_SUFFIX);\n let low = 0;\n let high = text.length;\n while (low < high) {\n const mid = Math.ceil((low + high) / 2);\n if (utf8Length(text.slice(0, mid)) <= budget) low = mid;\n else high = mid - 1;\n }\n const tail = text.charCodeAt(low - 1);\n if (tail >= 0xd800 && tail <= 0xdbff) low -= 1;\n return text.slice(0, low) + TRUNCATION_SUFFIX;\n}\n","/**\n * The desktop-side surfaces: pairing, re-pairing, and the status line that\n * makes a broken link visible without leaving dsh (docs/PRODUCT.md 失败可见).\n */\n\nimport type { CommandResult } from '@deepseek-ai/dsh-commands';\nimport type { ApprovalRouter } from './approvals.js';\nimport type { DispatchRouter } from './dispatch.js';\nimport type { Hub } from './hub.js';\n\nexport interface CommandDeps {\n readonly approvals: ApprovalRouter;\n readonly dispatch: DispatchRouter;\n}\n\nexport function installCommands(hub: Hub, deps: CommandDeps): void {\n hub.ctx.effect(() => hub.ctx.commands.register({\n name: 'dispatch-pair',\n description: 'Show the dsh-dispatch pairing code for this machine',\n handler: () => pairingResult(hub),\n }), 'dsh-dispatch: /dispatch-pair');\n\n hub.ctx.effect(() => hub.ctx.commands.register({\n name: 'dispatch-repair',\n description: 'Generate a new dsh-dispatch pairing secret (invalidates every paired phone)',\n handler: () => {\n hub.relay.rekey(hub.pairing.regenerate());\n hub.log.warn('pairing secret regenerated; every previously paired phone is now locked out');\n return pairingResult(hub, '旧配对码已作废,请在每台手机上重新扫码。\\n\\n');\n },\n }), 'dsh-dispatch: /dispatch-repair');\n\n hub.ctx.effect(() => hub.ctx.commands.register({\n name: 'dispatch-status',\n description: 'Show the dsh-dispatch relay connection and remote session state',\n handler: () => ({ kind: 'success', text: statusText(hub, deps) }),\n }), 'dsh-dispatch: /dispatch-status');\n}\n\nfunction pairingResult(hub: Hub, prefix = ''): CommandResult {\n if (hub.config.relay === '') {\n return {\n kind: 'error',\n text: 'relay 未配置:请在 profile 的 cordis.patch.yml 中为 dsh-dispatch 设置 relay: \\'wss://…\\'',\n };\n }\n const code = hub.pairing.pairingCode(hub.config.relay, hub.config.machineName);\n return {\n kind: 'success',\n text: `${prefix}配对码(${hub.config.machineName}):\\n${code}\\n\\n`\n + `或在手机上打开:\\n${hub.config.pwaUrl}/#pair=${code}\\n\\n`\n + '⚠️ 此码等同于本机的控制权:持有者可以批准工具调用并在允许目录中派发任务。'\n + '不要发到群里或截图外传;一旦泄露立即运行 /dispatch-repair。',\n };\n}\n\nfunction statusText(hub: Hub, deps: CommandDeps): string {\n const status = hub.relay.status();\n const link = hub.config.relay === ''\n ? '未配置(请设置 relay)'\n : status.connected\n ? `已连接 ${hub.config.relay}`\n : status.gaveUp\n ? `连接失败 ${String(status.consecutiveFailures)} 次,仍在每 60s 重试 — 请检查 relay`\n : `未连接(重试中,已失败 ${String(status.consecutiveFailures)} 次)`;\n return [\n `relay: ${link}`,\n `房间: ${status.room.slice(0, 8)}…`,\n `已配对: ${hub.pairing.everPaired ? 'yes' : 'no'}`\n + `${status.phoneOnline ? '(手机在线)' : '(手机离线)'}`,\n `待批审批: ${String(deps.approvals.openCount())}`,\n `派发会话: ${String(deps.dispatch.dispatchedCount())}`,\n `解密失败: ${String(status.decryptFailures)}`\n + `${status.decryptFailures > 0 ? ' — 密钥不匹配,请重新配对' : ''}`,\n `allowedRoots: ${hub.config.allowedRoots.length === 0\n ? '(空 — 远程派任务已禁用)'\n : hub.config.allowedRoots.join(', ')}`,\n ].join('\\n');\n}\n","/**\n * Remote dispatch — the M2 core. Starts agent sessions on request from the\n * phone, guards the working directory, and reports the final assistant text\n * when a dispatched turn closes.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { isAbsolute, resolve, sep } from 'node:path';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { installModelSelection } from '@deepseek-ai/dsh-agent';\nimport type { AgentHandle, ModelSelection } from '@deepseek-ai/dsh-agent';\nimport type {} from '@deepseek-ai/dsh-agent-default-model';\n// Carries the `ctx.agentPresets` Context merge; the roster is optional at\n// runtime, so it is read with ctx.get rather than declared in `inject`.\nimport type {} from '@deepseek-ai/dsh-agent-presets';\nimport { createUserMessage } from '@deepseek-ai/dsh-llm';\nimport { SessionId } from '@deepseek-ai/dsh-session';\nimport type { SurfaceEvent } from '@deepseek-ai/dsh-session';\nimport {\n clampBytes,\n MAX_FIELD_BYTES,\n MAX_PROMPT_BYTES,\n MAX_PROMPT_CHARS,\n MAX_SUMMARY_CHARS,\n truncate,\n utf8Length,\n} from '@dsh-dispatch/shared';\nimport type { DispatchRequestMsg, DispatchResultMsg, SessionMessageMsg } from '@dsh-dispatch/shared';\nimport type { Hub } from './hub.js';\nimport { expandHome } from './pairing.js';\nimport { prepareWorkspace, WorktreeError } from './worktree.js';\n\n/** docs/PROTOCOL.md: LRU of seen request ids, for double-tap safety. */\nconst SEEN_LIMIT = 256;\n\nconst DISPATCH_DISABLED = 'dispatch 未启用:请在插件配置中设置 allowedRoots';\nconst NO_REMOTE_FOLLOWUP = '该会话不支持远程追加';\n\nexport interface DispatchRouter {\n request(message: DispatchRequestMsg): Promise<void>;\n followup(message: SessionMessageMsg): Promise<void>;\n dispatchedCount(): number;\n}\n\n/** Per-plugin dispatch state; kept in one object so the handlers stay flat. */\ninterface DispatchState {\n readonly handles: Map<string, AgentHandle>;\n readonly seen: ResultCache;\n readonly inflight: Map<string, Promise<DispatchResultMsg>>;\n loaderReady: Promise<void> | undefined;\n}\n\nexport function installDispatch(hub: Hub): DispatchRouter {\n const state: DispatchState = {\n handles: new Map(),\n seen: new ResultCache(),\n inflight: new Map(),\n loaderReady: undefined,\n };\n hub.ctx.on('agent/turn-stopping', ({ agent }) => {\n if (!hub.sessions.isDispatched(agent.id)) return;\n // Latch the card synchronously: the idle status that closes this turn\n // races the async surface read below and would otherwise land first.\n hub.sessions.markTurnComplete(agent.id);\n void reportTurnFinal(hub, agent.id).catch((error: unknown) => {\n hub.fail(`turn.final for ${agent.id}`, error);\n });\n });\n hub.ctx.on('agent/disposed', ({ agent }) => { state.handles.delete(agent.id); });\n return {\n request: message => handleRequest(hub, state, message),\n followup: message => handleFollowup(hub, state, message),\n dispatchedCount: () => state.handles.size,\n };\n}\n\n/** Answer exactly once per requestId, even under a double-tap. */\nasync function handleRequest(\n hub: Hub,\n state: DispatchState,\n message: DispatchRequestMsg,\n): Promise<void> {\n const replay = state.seen.get(message.requestId);\n if (replay !== undefined) {\n if (replay !== null) hub.relay.send(replay);\n return;\n }\n const pending = state.inflight.get(message.requestId) ?? startSession(hub, state, message);\n state.inflight.set(message.requestId, pending);\n try {\n const result = await pending;\n state.seen.set(message.requestId, result);\n hub.relay.send(result);\n } finally {\n state.inflight.delete(message.requestId);\n }\n}\n\nasync function startSession(\n hub: Hub,\n state: DispatchState,\n message: DispatchRequestMsg,\n): Promise<DispatchResultMsg> {\n const prompt = message.prompt;\n const oversized = overLimit('prompt', prompt);\n if (oversized !== undefined) return failure(message.requestId, oversized);\n const target = resolveCwd(hub, message.cwd);\n if (typeof target !== 'string') return failure(message.requestId, target.error);\n try {\n const workspace = await prepareWorkspace({\n cwd: target,\n dataDir: hub.pairing.dataDir,\n worktree: message.worktree,\n });\n // Loader siblings mount concurrently; never create an agent into a\n // half-composed application (dossier §3.2).\n state.loaderReady ??= Promise.resolve(hub.ctx.get('loader')?.await()).then(() => undefined);\n await state.loaderReady;\n const handle = await createSession(hub.ctx, workspace.cwd, prompt);\n state.handles.set(handle.agent.id, handle);\n hub.sessions.markDispatched(handle.agent.id, prompt);\n const result = ok(message.requestId, handle.agent.id);\n // `note` is advisory on a successful result; `error` never appears with\n // ok: true, so the phone can key its failure state on `ok` alone.\n return workspace.note === undefined ? result : { ...result, note: workspace.note };\n } catch (error) {\n const detail = error instanceof WorktreeError ? error.message : String(error);\n hub.log.error('dispatch %s failed: %s', message.requestId, detail);\n return failure(message.requestId, detail);\n }\n}\n\n/**\n * Append a phone-typed message to a live session. Cold (persisted-only)\n * sessions are refused out loud rather than silently resumed.\n */\nasync function handleFollowup(\n hub: Hub,\n state: DispatchState,\n message: SessionMessageMsg,\n): Promise<void> {\n // Malformed input can never double-apply, so it is answered before the\n // idempotency mark: a retry deserves the same visible complaint.\n const oversized = overLimit('消息', message.text);\n if (oversized !== undefined) {\n hub.report(`session.message ${message.sessionId}`, oversized);\n return;\n }\n if (state.seen.get(message.requestId) !== undefined) return;\n state.seen.set(message.requestId, null);\n const agent = state.handles.get(message.sessionId)?.agent\n ?? hub.sessions.agentOf(message.sessionId);\n if (agent === undefined) {\n hub.report(`session.message ${message.sessionId}`, NO_REMOTE_FOLLOWUP);\n return;\n }\n agent.followup(createUserMessage({\n content: [{ type: 'text', text: message.text }],\n source: { kind: 'user' },\n }));\n}\n\n/**\n * Reject over-limit inbound text instead of clamping it. A well-behaved phone\n * clamps client-side (docs/PROTOCOL.md), so an oversized prompt is malformed\n * input — silently trimming it would run a task the user never wrote.\n * @returns the visible complaint, or undefined when the text is within limits.\n */\nfunction overLimit(field: string, text: string): string | undefined {\n const tail = '请在手机端缩短后重发。';\n if (text.length > MAX_PROMPT_CHARS) {\n return `${field} 超长:${String(text.length)} 字符,上限 ${String(MAX_PROMPT_CHARS)} 字符。${tail}`;\n }\n const bytes = utf8Length(text);\n if (bytes > MAX_PROMPT_BYTES) {\n return `${field} 超长:${String(bytes)} 字节(UTF-8),上限 ${String(MAX_PROMPT_BYTES)} 字节。${tail}`;\n }\n return undefined;\n}\n\n/**\n * Compose the agent's model-facing world: the model selection, and — when the\n * deployment runs a preset roster — the preset that carries its tools.\n *\n * Mirrors `composeAgent()` in packages/host/apiproxy/src/api-proxy.ts:1168,\n * which is what a user-created web session goes through. Skipping the mount is\n * not a SMALLER toolset, it is NO toolset: the web bundle disables every\n * model-facing row in the host plane (`tool-bash`, `tool-fs`, `tool-todo`, …\n * all `disabled: true` in packages/bundle/web-app/cordis.patch.yml) and moves\n * them into each preset's own scope layer, so an agent that joins no preset\n * reaches the model with an empty tool registry and no persona sections.\n */\nasync function composeAgent(ctx: Context, selection: ModelSelection | undefined): Promise<{\n agentPreset?: string;\n setup: (agentCtx: Context) => Promise<void>;\n}> {\n const install = (agentCtx: Context): void => {\n if (selection === undefined) return;\n installModelSelection(agentCtx, { current: selection, assembled: undefined });\n };\n const presets = ctx.get('agentPresets');\n // No roster composed (the headless shape): model-facing rows sit in the host\n // plane and the agent reads them from the global layer.\n if (presets === undefined) {\n return { setup: (agentCtx: Context) => { install(agentCtx); return Promise.resolve(); } };\n }\n // Name no preset, exactly as when a web client creates a session without\n // picking one: the roster's configured default wins.\n const resolvedId = (await presets.resolve()).id;\n return {\n agentPreset: resolvedId,\n setup: async (agentCtx: Context) => {\n install(agentCtx);\n await presets.mount(agentCtx, resolvedId);\n },\n };\n}\n\n/** Create a session in `cwd` with the same composition a web session gets. */\nasync function createSession(ctx: Context, cwd: string, prompt: string): Promise<AgentHandle> {\n const selection = ctx.get('agentDefaultModel')?.currentSelection();\n const composition = await composeAgent(ctx, selection);\n const handle = await ctx.agents.create({\n sessionId: SessionId(`session-${randomUUID()}`),\n // The preset is recorded on the header so a later cold resume rebuilds the\n // composition this session's history was actually produced under.\n meta: composition.agentPreset === undefined\n ? { cwd }\n : { cwd, agentPreset: composition.agentPreset },\n agentOptions: selection === undefined\n ? undefined\n : { provider: selection.provider, model: selection.model },\n setup: composition.setup,\n });\n await handle.agent.whenIdle();\n handle.agent.followup(createUserMessage({\n content: [{ type: 'text', text: prompt }],\n source: { kind: 'user' },\n }));\n return handle;\n}\n\n/**\n * Validate the requested cwd against the configured roots.\n * @returns the absolute directory, or the visible failure to send back.\n */\nfunction resolveCwd(hub: Hub, requested: string | undefined): string | { error: string } {\n const roots = hub.config.allowedRoots.map(root => resolve(expandHome(root)));\n if (roots.length === 0) return { error: DISPATCH_DISABLED };\n const first = roots[0];\n if (requested === undefined) return first === undefined ? { error: DISPATCH_DISABLED } : first;\n const target = resolve(isAbsolute(requested) ? requested : expandHome(requested));\n const inside = roots.some(root => target === root || target.startsWith(root + sep));\n return inside ? target : { error: `cwd 不在允许目录内:${target}` };\n}\n\nasync function reportTurnFinal(hub: Hub, sessionId: string): Promise<void> {\n const snapshot = await hub.ctx.sessionQuery.readSurface(SessionId(sessionId));\n const ok = !hub.sessions.hasError(sessionId);\n const summary = clampBytes(\n truncate(lastAssistantText(snapshot.events), MAX_SUMMARY_CHARS),\n MAX_FIELD_BYTES,\n );\n hub.relay.send(\n { v: 1, ts: Date.now(), type: 'turn.final', sessionId, ok, summary },\n {\n v: 1,\n ts: Date.now(),\n type: 'push',\n kind: ok ? 'done' : 'error',\n sessionId,\n title: truncate(summary.split('\\n', 1)[0] ?? '', 120),\n },\n );\n}\n\nfunction lastAssistantText(events: readonly SurfaceEvent[]): string {\n for (let index = events.length - 1; index >= 0; index -= 1) {\n const event = events[index];\n if (event?.type !== 'assistant/message') continue;\n const text = event.data.message.content\n .filter(block => block.type === 'text')\n .map(block => block.text)\n .join('')\n .trim();\n if (text !== '') return text;\n }\n return '(本轮没有产生助手文本)';\n}\n\nfunction ok(requestId: string, sessionId: string): DispatchResultMsg {\n return { v: 1, ts: Date.now(), type: 'dispatch.result', requestId, ok: true, sessionId };\n}\n\nfunction failure(requestId: string, error: string): DispatchResultMsg {\n return { v: 1, ts: Date.now(), type: 'dispatch.result', requestId, ok: false, error };\n}\n\n/** Bounded replay cache. `null` marks a request that succeeded with no reply frame. */\nclass ResultCache {\n readonly #entries = new Map<string, DispatchResultMsg | null>();\n\n get(requestId: string): DispatchResultMsg | null | undefined {\n return this.#entries.get(requestId);\n }\n\n set(requestId: string, value: DispatchResultMsg | null): void {\n this.#entries.delete(requestId);\n this.#entries.set(requestId, value);\n while (this.#entries.size > SEEN_LIMIT) {\n const oldest = this.#entries.keys().next();\n if (oldest.done === true) break;\n this.#entries.delete(oldest.value);\n }\n }\n}\n","/**\n * Pairing secret lifecycle: the 32 random bytes that are the ONLY thing\n * standing between a stranger and control of this machine. Stored at\n * `<dataDir>/secret` with mode 0600 and never sent to the relay.\n */\n\nimport { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { encodePairing, fromBase64, generateSecret, toBase64 } from '@dsh-dispatch/shared';\nimport type { Log } from './log.js';\n\nconst SECRET_FILE = 'secret';\nconst PAIRED_FILE = 'paired.json';\nconst SECRET_BYTES = 32;\n\n/** Expand a leading `~` so a configured `~/.dsh-dispatch` resolves to a real path. */\nexport function expandHome(input: string): string {\n if (input === '~') return homedir();\n if (input.startsWith('~/')) return join(homedir(), input.slice(2));\n return resolve(input);\n}\n\n/** Everything the plugin needs to prove a phone is allowed to talk to it. */\nexport class PairingStore {\n readonly dataDir: string;\n #secret: Uint8Array;\n #pairedAt: number | undefined;\n\n private constructor(dataDir: string, secret: Uint8Array, pairedAt: number | undefined) {\n this.dataDir = dataDir;\n this.#secret = secret;\n this.#pairedAt = pairedAt;\n }\n\n /** Load the stored secret, generating and persisting one on first start. */\n static open(dataDir: string, log: Log): PairingStore {\n const dir = expandHome(dataDir);\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n const secretPath = join(dir, SECRET_FILE);\n let secret: Uint8Array | null = null;\n if (existsSync(secretPath)) {\n secret = fromBase64(readFileSync(secretPath, 'utf8').trim());\n if (secret === null || secret.length !== SECRET_BYTES) {\n throw new Error(\n `dsh-dispatch: ${secretPath} is not a valid pairing secret. `\n + 'Delete the file to generate a new one (this invalidates existing pairings).',\n );\n }\n } else {\n secret = generateSecret();\n writeSecret(secretPath, secret);\n }\n return new PairingStore(dir, secret, readPairedAt(join(dir, PAIRED_FILE), log));\n }\n\n get secret(): Uint8Array {\n return this.#secret;\n }\n\n /**\n * Whether a phone has ever completed a pairing with this machine. Approval\n * forwarding stays out of the way entirely until this is true, so an\n * unpaired machine pays zero added approval latency.\n */\n get everPaired(): boolean {\n return this.#pairedAt !== undefined;\n }\n\n get pairedAt(): number | undefined {\n return this.#pairedAt;\n }\n\n /** Record the first sighting of a phone in this room. Idempotent. */\n markPaired(): void {\n if (this.#pairedAt !== undefined) return;\n this.#pairedAt = Date.now();\n writeFileSync(join(this.dataDir, PAIRED_FILE), JSON.stringify({ pairedAt: this.#pairedAt }), {\n mode: 0o600,\n });\n }\n\n /** Replace the secret, invalidating every existing pairing. */\n regenerate(): Uint8Array {\n this.#secret = generateSecret();\n writeSecret(join(this.dataDir, SECRET_FILE), this.#secret);\n this.#pairedAt = undefined;\n rmSync(join(this.dataDir, PAIRED_FILE), { force: true });\n return this.#secret;\n }\n\n /** The out-of-band pairing payload: relay + secret + machine name. */\n pairingCode(relay: string, machine: string): string {\n return encodePairing({ relay, secret: this.#secret, machine });\n }\n}\n\nfunction writeSecret(path: string, secret: Uint8Array): void {\n writeFileSync(path, toBase64(secret), { mode: 0o600 });\n // writeFileSync only applies `mode` when it creates the file; a rewrite of an\n // existing loose-permission file would otherwise stay world-readable.\n chmodSync(path, 0o600);\n}\n\nfunction readPairedAt(path: string, log: Log): number | undefined {\n if (!existsSync(path)) return undefined;\n try {\n const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'));\n const value = (parsed as { pairedAt?: unknown }).pairedAt;\n return typeof value === 'number' ? value : undefined;\n } catch (error) {\n // A corrupt marker only costs us the \"have we ever paired\" shortcut; the\n // next phone connection rewrites it. Loud, then treat as never paired.\n log.error('pairing: %s is unreadable, treating this machine as never paired: %s', path, error);\n return undefined;\n }\n}\n","/**\n * Git worktree preparation for dispatched tasks.\n *\n * v0 never deletes a worktree (docs/PRODUCT.md 防呆): cleanup is the user's\n * call, because guessing wrong destroys real work.\n */\n\nimport { execFile } from 'node:child_process';\nimport { randomBytes } from 'node:crypto';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nconst run = promisify(execFile);\n\n/** docs/PROTOCOL.md: a failure result carries at most 500 chars of stderr. */\nconst STDERR_TAIL = 500;\n\nexport class WorktreeError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'WorktreeError';\n }\n}\n\nexport interface Workspace {\n /** Directory the agent will actually run in. */\n readonly cwd: string;\n /** Branch created for this task, absent when running in `cwd` directly. */\n readonly branch?: string;\n /** Human-readable degradation note the phone must show, when degraded. */\n readonly note?: string;\n}\n\nexport interface PrepareOptions {\n readonly cwd: string;\n readonly dataDir: string;\n readonly worktree: boolean;\n}\n\n/**\n * Resolve the directory a dispatched task runs in.\n * @throws WorktreeError when `git worktree add` fails; the message carries the stderr tail.\n */\nexport async function prepareWorkspace(options: PrepareOptions): Promise<Workspace> {\n if (!options.worktree) return { cwd: options.cwd };\n if (!await isGitRepo(options.cwd)) {\n return {\n cwd: options.cwd,\n note: `已降级:${options.cwd} 不是 git 仓库,任务直接在该目录运行(未创建 worktree)`,\n };\n }\n const root = join(options.dataDir, 'worktrees');\n try {\n return await addWorktree(options.cwd, root, freshSlug());\n } catch (error) {\n const stderr = stderrOf(error);\n if (!isCollision(stderr)) throw new WorktreeError(stderr);\n return await addWorktree(options.cwd, root, freshSlug()).catch((retry: unknown) => {\n throw new WorktreeError(stderrOf(retry));\n });\n }\n}\n\nasync function addWorktree(cwd: string, root: string, slug: string): Promise<Workspace> {\n const target = join(root, slug);\n const branch = `dsh-dispatch/${slug}`;\n await run('git', ['-C', cwd, 'worktree', 'add', target, '-b', branch]);\n return { cwd: target, branch };\n}\n\nasync function isGitRepo(cwd: string): Promise<boolean> {\n try {\n const { stdout } = await run('git', ['-C', cwd, 'rev-parse', '--is-inside-work-tree']);\n return stdout.trim() === 'true';\n } catch {\n // Not a repo, or git is missing. Both mean \"no worktree here\"; the caller\n // reports the degradation to the phone, so this is not a silent swallow.\n return false;\n }\n}\n\n/** `timestamp + short random` — sortable, collision-resistant, readable in `git worktree list`. */\nfunction freshSlug(): string {\n const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\\..+$/, '');\n return `${stamp}-${randomBytes(3).toString('hex')}`;\n}\n\nfunction isCollision(stderr: string): boolean {\n return /already exists|already used by worktree|already checked out/i.test(stderr);\n}\n\nfunction stderrOf(error: unknown): string {\n const raw = (error as { stderr?: unknown }).stderr;\n const text = typeof raw === 'string' && raw.trim() !== ''\n ? raw.trim()\n : error instanceof Error ? error.message : String(error);\n return text.length <= STDERR_TAIL ? text : text.slice(-STDERR_TAIL);\n}\n","/**\n * The shared runtime handle every feature module receives. Also the single\n * place a failure becomes visible: `fail()` logs on the desktop AND pushes an\n * `error` message to the phone, so no catch can quietly swallow anything.\n */\n\nimport type { Context } from '@deepseek-ai/cordis';\nimport { truncate } from '@dsh-dispatch/shared';\nimport type { Config } from './config.js';\nimport type { Log } from './log.js';\nimport type { PairingStore } from './pairing.js';\nimport type { RelayClient } from './relay-client.js';\nimport type { SessionTracker } from './sessions.js';\n\n/** Phone screens are small; a wall of stack trace helps nobody there. */\nconst ERROR_CHARS = 1_000;\n\nexport interface Hub {\n readonly ctx: Context;\n readonly config: Config;\n readonly log: Log;\n readonly relay: RelayClient;\n readonly sessions: SessionTracker;\n readonly pairing: PairingStore;\n /** Report a caught error on both surfaces. */\n fail(context: string, error: unknown): void;\n /** Report an already human-readable failure on both surfaces. */\n report(context: string, message: string): void;\n}\n\nexport function createHub(parts: Omit<Hub, 'fail' | 'report'>): Hub {\n const report = (context: string, message: string): void => {\n parts.log.error('%s: %s', context, message);\n parts.relay.send({\n v: 1,\n ts: Date.now(),\n type: 'error',\n message: truncate(message, ERROR_CHARS),\n context,\n });\n };\n return {\n ...parts,\n report,\n fail(context: string, error: unknown): void {\n report(context, error instanceof Error ? error.message : String(error));\n },\n };\n}\n","/**\n * Outbound wss client to the dsh-dispatch relay. Uses Node's global\n * `WebSocket` (Node >= 22), so the plugin adds no transport dependency.\n *\n * Everything crossing this boundary is sealed with the pairing key: the relay\n * routes ciphertext and learns nothing but room id, role and message size.\n */\n\nimport {\n deriveKey,\n deriveRoomId,\n MAX_ENVELOPE_BYTES,\n open,\n seal,\n} from '@dsh-dispatch/shared';\nimport type {\n MachineToPhoneMsg,\n MsgFrame,\n PhoneToMachineMsg,\n PushCompactMsg,\n} from '@dsh-dispatch/shared';\nimport type { Log } from './log.js';\n\nconst BACKOFF_BASE_MS = 500;\nconst BACKOFF_MAX_MS = 30_000;\n/** Consecutive connect failures after which we surface a visible error state. */\nconst GIVE_UP_AFTER = 10;\n/** Retry interval once we have surfaced the error; we never stop trying. */\nconst GIVE_UP_RETRY_MS = 60_000;\n/** Decryption failures inside this window collapse into one log line. */\nconst TAMPER_LOG_WINDOW_MS = 30_000;\n\n/** Everything `/dispatch-status` needs to describe the link in one line. */\nexport interface RelayStatus {\n readonly connected: boolean;\n readonly phoneOnline: boolean;\n readonly consecutiveFailures: number;\n readonly gaveUp: boolean;\n readonly room: string;\n readonly decryptFailures: number;\n}\n\nexport interface RelayClientOptions {\n readonly url: string;\n readonly secret: Uint8Array;\n readonly log: Log;\n /** A validated inner message arrived from the phone. */\n onMessage(message: PhoneToMachineMsg): void;\n /** The relay accepted our hello — time to publish status and a snapshot. */\n onConnected(): void;\n /** A phone joined or left this room. */\n onPhonePresence(online: boolean): void;\n}\n\nexport class RelayClient {\n readonly #options: RelayClientOptions;\n #key: Uint8Array;\n #room: string;\n #socket: WebSocket | undefined;\n #retry: ReturnType<typeof setTimeout> | undefined;\n #stopped = true;\n #connected = false;\n #phoneOnline = false;\n #failures = 0;\n #gaveUp = false;\n #tamperBurst = 0;\n #tamperWindowStart = 0;\n #tamperTotal = 0;\n\n constructor(options: RelayClientOptions) {\n this.#options = options;\n this.#key = deriveKey(options.secret);\n this.#room = deriveRoomId(options.secret);\n }\n\n get connected(): boolean {\n return this.#connected;\n }\n\n get phoneOnline(): boolean {\n return this.#phoneOnline;\n }\n\n status(): RelayStatus {\n return {\n connected: this.#connected,\n phoneOnline: this.#phoneOnline,\n consecutiveFailures: this.#failures,\n gaveUp: this.#gaveUp,\n room: this.#room,\n decryptFailures: this.#tamperTotal,\n };\n }\n\n start(): void {\n if (!this.#stopped) return;\n this.#stopped = false;\n this.#connect();\n }\n\n stop(): void {\n this.#stopped = true;\n if (this.#retry !== undefined) clearTimeout(this.#retry);\n this.#retry = undefined;\n this.#teardownSocket();\n this.#connected = false;\n this.#phoneOnline = false;\n }\n\n /** Adopt a freshly generated secret: new room, new key, new connection. */\n rekey(secret: Uint8Array): void {\n this.#key = deriveKey(secret);\n this.#room = deriveRoomId(secret);\n this.#failures = 0;\n this.#gaveUp = false;\n if (this.#stopped) return;\n this.stop();\n this.start();\n }\n\n /**\n * Seal and send one inner message. `push` rides along so the relay can wake\n * an offline phone through Web Push without ever seeing the plaintext.\n * @returns whether the frame reached the socket.\n */\n send(message: MachineToPhoneMsg, push?: PushCompactMsg): boolean {\n const socket = this.#socket;\n if (socket === undefined || !this.#connected) return false;\n const frame: MsgFrame = {\n kind: 'msg',\n room: this.#room,\n payload: seal(message, this.#key),\n push: push === undefined ? null : { payload: seal(push, this.#key), tag: push.kind },\n };\n const text = JSON.stringify(frame);\n if (text.length > MAX_ENVELOPE_BYTES) {\n this.#options.log.error(\n 'relay: refusing to send an oversized %s envelope (%d bytes > %d); this is a truncation bug',\n message.type, text.length, MAX_ENVELOPE_BYTES,\n );\n return false;\n }\n socket.send(text);\n return true;\n }\n\n #connect(): void {\n if (this.#stopped) return;\n let socket: WebSocket;\n try {\n socket = new WebSocket(this.#options.url);\n } catch (error) {\n this.#options.log.error('relay: cannot open %s: %s', this.#options.url, error);\n this.#scheduleRetry();\n return;\n }\n this.#socket = socket;\n socket.addEventListener('open', () => {\n socket.send(JSON.stringify({ kind: 'hello', room: this.#room, role: 'machine' }));\n });\n socket.addEventListener('message', (event: MessageEvent) => {\n this.#onFrame(event.data);\n });\n socket.addEventListener('error', () => {\n // 'close' always follows; the event itself carries no useful detail.\n });\n socket.addEventListener('close', (event) => {\n if (this.#socket !== socket) return;\n const code = (event as unknown as { code?: number }).code;\n this.#onDown(`socket closed (${String(code ?? 'no code')})`);\n });\n }\n\n #onFrame(data: unknown): void {\n if (typeof data !== 'string') return;\n let frame: unknown;\n try {\n frame = JSON.parse(data);\n } catch (error) {\n this.#options.log.error('relay: dropped an unparseable frame: %s', error);\n return;\n }\n const kind = (frame as { kind?: unknown }).kind;\n if (kind === 'hello-ok') this.#onHelloOk(frame);\n else if (kind === 'presence') this.#onPresence(frame);\n else if (kind === 'msg') this.#onData(frame);\n else if (kind === 'error') {\n const code = String((frame as { code?: unknown }).code ?? 'unknown');\n this.#options.log.error('relay: rejected our frame with code \"%s\"', code);\n } else {\n this.#options.log.warn('relay: ignoring unknown frame kind \"%s\"', String(kind));\n }\n }\n\n #onHelloOk(frame: unknown): void {\n this.#connected = true;\n this.#failures = 0;\n this.#gaveUp = false;\n const peers = (frame as { peers?: { phone?: unknown } }).peers;\n const phones = typeof peers?.phone === 'number' ? peers.phone : 0;\n this.#options.log.info('relay: connected (room %s…, phones online: %d)', this.#room.slice(0, 8), phones);\n this.#options.onConnected();\n if (phones > 0) this.#setPhoneOnline(true);\n }\n\n #onPresence(frame: unknown): void {\n const { role, online } = frame as { role?: unknown; online?: unknown };\n if (role !== 'phone' || typeof online !== 'boolean') return;\n this.#setPhoneOnline(online);\n }\n\n #setPhoneOnline(online: boolean): void {\n if (this.#phoneOnline === online) return;\n this.#phoneOnline = online;\n this.#options.onPhonePresence(online);\n }\n\n #onData(frame: unknown): void {\n const payload = (frame as { payload?: unknown }).payload;\n if (typeof payload !== 'string') {\n this.#options.log.error('relay: dropped a msg frame with no payload');\n return;\n }\n const plain = open(payload, this.#key);\n if (plain === null) {\n this.#onTamper();\n return;\n }\n const message = parseInbound(plain);\n if (message === null) {\n this.#options.log.warn(\n 'relay: ignoring inner message of type \"%s\" — unknown type or missing/invalid fields '\n + '(the phone may be newer than this plugin)',\n String((plain as { type?: unknown }).type),\n );\n return;\n }\n this.#options.onMessage(message);\n }\n\n /** Decryption failure is never silent: it means tampering or a stale pairing. */\n #onTamper(): void {\n this.#tamperTotal += 1;\n this.#tamperBurst += 1;\n const now = Date.now();\n if (now - this.#tamperWindowStart < TAMPER_LOG_WINDOW_MS) return;\n this.#tamperWindowStart = now;\n this.#options.log.error(\n 'relay: %d message(s) failed to decrypt — wrong key or tampering. Re-pair with /dispatch-repair '\n + 'and re-scan the QR on every phone.',\n this.#tamperBurst,\n );\n this.#tamperBurst = 0;\n }\n\n #onDown(reason: string): void {\n this.#teardownSocket();\n if (this.#stopped) return;\n this.#connected = false;\n this.#setPhoneOnline(false);\n this.#failures += 1;\n this.#options.log.debug('relay: %s, attempt %d', reason, this.#failures);\n this.#scheduleRetry();\n }\n\n #scheduleRetry(): void {\n if (this.#stopped || this.#retry !== undefined) return;\n if (this.#failures >= GIVE_UP_AFTER && !this.#gaveUp) {\n this.#gaveUp = true;\n this.#options.log.error(\n 'relay: %d consecutive failures connecting to %s — check that the relay is reachable. '\n + 'Still retrying every %ds.',\n this.#failures, this.#options.url, GIVE_UP_RETRY_MS / 1000,\n );\n }\n this.#retry = setTimeout(() => {\n this.#retry = undefined;\n this.#connect();\n }, this.#backoffMs());\n this.#retry.unref?.();\n }\n\n #backoffMs(): number {\n if (this.#gaveUp) return GIVE_UP_RETRY_MS;\n const exponential = Math.min(BACKOFF_BASE_MS * 2 ** this.#failures, BACKOFF_MAX_MS);\n return Math.round(exponential * (0.75 + Math.random() * 0.5));\n }\n\n #teardownSocket(): void {\n const socket = this.#socket;\n this.#socket = undefined;\n if (socket === undefined) return;\n try {\n socket.close();\n } catch (error) {\n this.#options.log.debug('relay: error closing socket: %s', error);\n }\n }\n}\n\nconst INBOUND_FIELDS: Record<PhoneToMachineMsg['type'], readonly string[]> = {\n 'sessions.get': [],\n 'approval.respond': ['requestId', 'approvalId', 'decision'],\n 'dispatch.request': ['requestId', 'prompt'],\n 'session.message': ['requestId', 'sessionId', 'text'],\n};\n\n/**\n * Accept only inner messages this plugin understands, with their required\n * string fields present. Unknown `type`/`v` returns null so the caller logs it\n * (forward compatibility per docs/PROTOCOL.md) rather than crashing.\n */\nexport function parseInbound(value: unknown): PhoneToMachineMsg | null {\n if (typeof value !== 'object' || value === null) return null;\n const record = value as Record<string, unknown>;\n if (record['v'] !== 1) return null;\n const type = record['type'];\n if (typeof type !== 'string' || !(type in INBOUND_FIELDS)) return null;\n const required = INBOUND_FIELDS[type as PhoneToMachineMsg['type']];\n for (const field of required) {\n if (typeof record[field] !== 'string' || record[field] === '') return null;\n }\n if (type === 'approval.respond' && record['decision'] !== 'allow' && record['decision'] !== 'deny') {\n return null;\n }\n if (type === 'dispatch.request' && typeof record['worktree'] !== 'boolean') return null;\n return record as unknown as PhoneToMachineMsg;\n}\n","/**\n * Live session board: maps dsh agent lifecycle events onto the protocol's\n * `Session` objects and pushes `session.update` / `session.snapshot` to the\n * phone.\n */\n\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { Agent } from '@deepseek-ai/dsh-agent';\nimport { SessionId } from '@deepseek-ai/dsh-session';\nimport type {} from '@deepseek-ai/dsh-session-query';\nimport { truncate } from '@dsh-dispatch/shared';\nimport type { Session as WireSession, SessionState } from '@dsh-dispatch/shared';\nimport type { Log } from './log.js';\nimport type { RelayClient } from './relay-client.js';\n\n/** docs/PROTOCOL.md: `title` = first 80 chars of the initial prompt. */\nconst TITLE_CHARS = 80;\n\n/** Lifecycle state before the approval overlay is applied. */\ntype BaseState = 'idle' | 'running' | 'done' | 'error';\n\ninterface Tracked {\n sessionId: string;\n title: string;\n cwd: string;\n base: BaseState;\n openApprovals: number;\n lastActivity: number;\n dispatched: boolean;\n /**\n * A dispatched session whose `turn.final` has been sent. It stays `done` on\n * the phone until a NEW turn starts — the idle status that follows every\n * turn must not demote the card back to 空闲.\n */\n finalized: boolean;\n}\n\nexport class SessionTracker {\n readonly #ctx: Context;\n readonly #relay: RelayClient;\n readonly #log: Log;\n readonly #tracked = new Map<string, Tracked>();\n readonly #published = new Map<string, string>();\n\n constructor(ctx: Context, relay: RelayClient, log: Log) {\n this.#ctx = ctx;\n this.#relay = relay;\n this.#log = log;\n }\n\n /** Subscribe the agent lifecycle. Registrations unwind with the plugin. */\n install(): void {\n this.#ctx.on('agent/created', ({ agent }) => {\n this.#adopt(agent);\n this.#publish(agent.id);\n void this.#refreshTitle(agent);\n });\n this.#ctx.on('agent/session-start', ({ agent }) => {\n void this.#refreshTitle(agent);\n });\n this.#ctx.on('agent/status', ({ agent, status }) => {\n this.#update(agent.id, (entry) => {\n // The trailing idle of a finished turn is not new information; only a\n // fresh `running` reopens a finalized session.\n if (status === 'idle' && entry.finalized) return;\n entry.finalized = false;\n entry.base = status;\n });\n });\n this.#ctx.on('agent/error', ({ agent, error }) => {\n this.#log.error('session %s: agent error: %s', agent.id, error);\n this.#update(agent.id, entry => { entry.base = 'error'; });\n });\n this.#ctx.on('agent/turn-stopping', ({ agent }) => {\n void this.#refreshTitle(agent);\n });\n this.#ctx.on('agent/disposed', ({ agent }) => {\n this.#update(agent.id, entry => { entry.base = 'done'; });\n this.#tracked.delete(agent.id);\n this.#published.delete(agent.id);\n });\n }\n\n /** Mark a session as started by us, with the dispatch prompt as its title. */\n markDispatched(sessionId: string, prompt: string): void {\n this.#update(sessionId, entry => {\n entry.dispatched = true;\n entry.title = truncate(prompt.trim().split('\\n', 1)[0] ?? '', TITLE_CHARS);\n });\n }\n\n isDispatched(sessionId: string): boolean {\n return this.#tracked.get(sessionId)?.dispatched ?? false;\n }\n\n /**\n * A dispatched turn has closed and its `turn.final` is on its way. Latch the\n * card at done/error so the trailing idle status cannot demote it.\n */\n markTurnComplete(sessionId: string): void {\n this.#update(sessionId, (entry) => {\n entry.finalized = true;\n if (entry.base !== 'error') entry.base = 'done';\n });\n }\n\n hasError(sessionId: string): boolean {\n return this.#tracked.get(sessionId)?.base === 'error';\n }\n\n /** The live agent for a session id, or undefined when it is not live. */\n agentOf(sessionId: string): Agent | undefined {\n return this.#ctx.agents.get(SessionId(sessionId));\n }\n\n /** An approval for this session is now waiting on a human. */\n approvalOpened(sessionId: string): void {\n this.#update(sessionId, entry => { entry.openApprovals += 1; });\n }\n\n approvalClosed(sessionId: string): void {\n this.#update(sessionId, entry => {\n entry.openApprovals = Math.max(0, entry.openApprovals - 1);\n });\n }\n\n /** Active sessions only — the phone board never shows cold history. */\n async snapshot(): Promise<WireSession[]> {\n const records = await this.#ctx.sessionQuery.listSessions();\n const wire: WireSession[] = [];\n for (const record of records) {\n if (!record.live) continue;\n const id = record.header.id;\n const known = this.#tracked.get(id) ?? this.#adopt(this.#ctx.agents.get(id));\n wire.push(known === undefined\n ? this.#fallbackWire(id, record.header.cwd ?? '', record.header.createdAt)\n : toWire(known));\n }\n return wire;\n }\n\n #fallbackWire(sessionId: string, cwd: string, createdAt: number): WireSession {\n return {\n sessionId,\n title: truncate(sessionId, TITLE_CHARS),\n cwd,\n state: 'idle',\n lastActivity: createdAt,\n dispatched: false,\n };\n }\n\n #adopt(agent: Agent | undefined): Tracked | undefined {\n if (agent === undefined) return undefined;\n const existing = this.#tracked.get(agent.id);\n if (existing !== undefined) return existing;\n const entry: Tracked = {\n sessionId: agent.id,\n title: truncate(firstPrompt(agent) ?? agent.id, TITLE_CHARS),\n cwd: agent.session.header.cwd ?? '',\n base: agent.status,\n openApprovals: 0,\n lastActivity: Date.now(),\n dispatched: false,\n finalized: false,\n };\n this.#tracked.set(agent.id, entry);\n return entry;\n }\n\n #update(sessionId: string, mutate: (entry: Tracked) => void): void {\n const entry = this.#tracked.get(sessionId) ?? this.#adopt(this.#ctx.agents.get(SessionId(sessionId)));\n if (entry === undefined) return;\n mutate(entry);\n entry.lastActivity = Date.now();\n this.#publish(sessionId);\n }\n\n /** Emit `session.update` only when the phone-visible projection changed. */\n #publish(sessionId: string): void {\n const entry = this.#tracked.get(sessionId);\n if (entry === undefined) return;\n const session = toWire(entry);\n const fingerprint = `${session.state}|${session.title}|${session.cwd}|${String(session.dispatched)}`;\n if (this.#published.get(sessionId) === fingerprint) return;\n this.#published.set(sessionId, fingerprint);\n this.#relay.send({ v: 1, ts: Date.now(), type: 'session.update', session });\n }\n\n async #refreshTitle(agent: Agent): Promise<void> {\n const entry = this.#tracked.get(agent.id);\n if (entry === undefined || entry.dispatched) return;\n try {\n const snapshot = await this.#ctx.sessionQuery.readTitle(SessionId(agent.id));\n const title = snapshot?.title ?? firstPrompt(agent);\n if (title === undefined || title === '') return;\n entry.title = truncate(title, TITLE_CHARS);\n this.#publish(agent.id);\n } catch (error) {\n this.#log.error('session %s: could not read title: %s', agent.id, error);\n }\n }\n}\n\nfunction toWire(entry: Tracked): WireSession {\n return {\n sessionId: entry.sessionId,\n title: entry.title,\n cwd: entry.cwd,\n state: stateOf(entry),\n lastActivity: entry.lastActivity,\n dispatched: entry.dispatched,\n };\n}\n\n/**\n * `awaiting_approval` is an overlay, not a lifecycle state: it holds exactly\n * while our answerer has an open question for this session.\n */\nfunction stateOf(entry: Tracked): SessionState {\n if (entry.base === 'done' || entry.base === 'error') return entry.base;\n return entry.openApprovals > 0 ? 'awaiting_approval' : entry.base;\n}\n\n/** Fallback title source: the first human prompt in the session log. */\nfunction firstPrompt(agent: Agent): string | undefined {\n for (const event of agent.session.events) {\n if (event.type !== 'user/message') continue;\n const text = event.data.content\n .filter(block => block.type === 'text')\n .map(block => block.text)\n .join('')\n .trim();\n if (text !== '') return text.split('\\n', 1)[0];\n }\n return undefined;\n}\n","/**\n * Plugin version reported in `machine.status`.\n *\n * Kept as a literal rather than read from package.json so the bundled ESM\n * output has no runtime filesystem dependency. `tests/version.test.ts` fails\n * the build if this drifts from package.json.\n */\nexport const PLUGIN_VERSION = '0.1.0';\n","/**\n * Plugin configuration. Validated by Schemastery before `apply` runs, so every\n * field below is present with its default already filled in.\n */\n\nimport { hostname } from 'node:os';\nimport z from '@deepseek-ai/schemastery';\n\nexport interface Config {\n /** Relay websocket URL, e.g. `wss://relay.example.com`. */\n relay: string;\n /** Name shown on the phone's machine card. */\n machineName: string;\n /**\n * Absolute directories a dispatched agent may run in. Empty (the default)\n * keeps remote dispatch off — approval forwarding still works.\n */\n allowedRoots: string[];\n /** Where the pairing secret and dispatch worktrees live. */\n dataDir: string;\n /** Base URL of the PWA, used only to print the pairing convenience link. */\n pwaUrl: string;\n}\n\nexport const Config: z<Config> = z.object({\n relay: z.string().required(),\n machineName: z.string().default(hostname()),\n allowedRoots: z.array(z.string()).default([]),\n dataDir: z.string().default('~/.dsh-dispatch'),\n pwaUrl: z.string().default('http://localhost:5173'),\n});\n","/**\n * dsh-dispatch — command your DeepSeek Harness machines from your phone.\n *\n * Approvals raised anywhere in this dsh instance are forwarded to a paired\n * phone and answered there; the phone can also start new agent sessions in\n * configured directories and follow up on live ones. Everything between this\n * plugin and the phone is end-to-end encrypted; the relay only routes\n * ciphertext.\n */\n\nimport type { Context } from '@deepseek-ai/cordis';\n// Empty type imports carry the Context/Events declaration merges this plugin\n// listens on and calls into.\nimport type {} from '@deepseek-ai/dsh-agent';\nimport type {} from '@deepseek-ai/dsh-commands';\nimport type {} from '@deepseek-ai/dsh-session-query';\nimport type {} from '@deepseek-ai/dsh-user-approval';\nimport type { PhoneToMachineMsg } from '@dsh-dispatch/shared';\nimport { installApprovals } from './approvals.js';\nimport type { ApprovalRouter } from './approvals.js';\nimport { installCommands } from './commands.js';\nimport type { Config } from './config.js';\nimport { installDispatch } from './dispatch.js';\nimport type { DispatchRouter } from './dispatch.js';\nimport { createHub } from './hub.js';\nimport type { Hub } from './hub.js';\nimport { PairingStore } from './pairing.js';\nimport { RelayClient } from './relay-client.js';\nimport { SessionTracker } from './sessions.js';\nimport { PLUGIN_VERSION } from './version.js';\n\nexport { Config } from './config.js';\n\nexport const name = 'dsh-dispatch';\n\n/**\n * `approval` is deliberately absent: the plugin is useful (dispatch, session\n * board) in a composition without it, and the dossier's apiproxy precedent\n * guards the service rather than requiring it.\n */\nexport const inject = ['agents', 'commands', 'sessionQuery'];\n\n/** docs/PROTOCOL.md: machine.status on connect and every 60s. */\nconst STATUS_INTERVAL_MS = 60_000;\n\nexport function apply(ctx: Context, config: Config): void {\n const log = ctx.logger('dsh-dispatch');\n const pairing = PairingStore.open(config.dataDir, log);\n // The router is built from the relay, and the relay calls into the router:\n // late-bind the two callbacks rather than smuggling a half-built object.\n const deferred = { message(_: PhoneToMachineMsg): void {}, hello(): void {} };\n const relay = new RelayClient({\n url: config.relay,\n secret: pairing.secret,\n log,\n onMessage: message => { deferred.message(message); },\n onConnected: () => { deferred.hello(); },\n onPhonePresence: (online) => {\n if (!online) {\n log.info('relay: phone left the room');\n return;\n }\n pairing.markPaired();\n log.info('relay: phone joined the room');\n deferred.hello();\n },\n });\n const sessions = new SessionTracker(ctx, relay, log);\n const hub = createHub({ ctx, config, log, relay, sessions, pairing });\n const approvals = installApprovals(hub);\n const dispatch = installDispatch(hub);\n deferred.message = message => { route(hub, approvals, dispatch, message); };\n deferred.hello = () => {\n publishStatus(hub);\n approvals.resendPending();\n void publishSnapshot(hub);\n };\n sessions.install();\n installCommands(hub, { approvals, dispatch });\n startRelay(hub);\n}\n\n/** Bring the link up, unless it cannot possibly work — say so once, loudly. */\nfunction startRelay(hub: Hub): void {\n const url = hub.config.relay;\n if (url === '') {\n hub.log.error(\n 'relay is not configured: set `relay: \\'wss://…\\'` on the dsh-dispatch row in your profile\\'s '\n + 'cordis.patch.yml. Approval forwarding and dispatch stay off until then.',\n );\n return;\n }\n if (!/^wss?:\\/\\//.test(url)) {\n hub.log.error('relay \"%s\" is not a ws:// or wss:// URL; refusing to connect', url);\n return;\n }\n hub.ctx.effect(() => {\n hub.relay.start();\n return () => { hub.relay.stop(); };\n }, 'dsh-dispatch: relay client');\n hub.ctx.effect(() => {\n const timer = setInterval(() => { publishStatus(hub); }, STATUS_INTERVAL_MS);\n timer.unref?.();\n return () => { clearInterval(timer); };\n }, 'dsh-dispatch: status heartbeat');\n}\n\nfunction route(\n hub: Hub,\n approvals: ApprovalRouter,\n dispatch: DispatchRouter,\n message: PhoneToMachineMsg,\n): void {\n switch (message.type) {\n case 'sessions.get':\n void publishSnapshot(hub);\n return;\n case 'approval.respond':\n approvals.respond(message);\n return;\n case 'dispatch.request':\n void dispatch.request(message).catch((error: unknown) => {\n hub.fail(`dispatch.request ${message.requestId}`, error);\n });\n return;\n case 'session.message':\n void dispatch.followup(message).catch((error: unknown) => {\n hub.fail(`session.message ${message.requestId}`, error);\n });\n }\n}\n\nfunction publishStatus(hub: Hub): void {\n hub.relay.send({\n v: 1,\n ts: Date.now(),\n type: 'machine.status',\n machine: hub.config.machineName,\n pluginVersion: PLUGIN_VERSION,\n });\n}\n\nasync function publishSnapshot(hub: Hub): Promise<void> {\n try {\n hub.relay.send({\n v: 1,\n ts: Date.now(),\n type: 'session.snapshot',\n sessions: await hub.sessions.snapshot(),\n });\n } catch (error) {\n hub.fail('session.snapshot', error);\n }\n}\n"],"mappings":"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","names":["resolve","entry","randomUUID","resolve","join","randomUUID","resolve","ok","SessionId","TITLE_CHARS","SessionId"]}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "dsh-dispatch",
3
+ "version": "0.1.0",
4
+ "description": "Command your DeepSeek Harness machines from your phone: approval push, remote dispatch, session board. Built on DeepSeek Harness.",
5
+ "keywords": [
6
+ "dsh",
7
+ "dsh-plugin",
8
+ "deepseek-harness",
9
+ "deepseek",
10
+ "remote-control",
11
+ "mobile",
12
+ "dispatch",
13
+ "approvals",
14
+ "e2ee",
15
+ "phone"
16
+ ],
17
+ "license": "MIT",
18
+ "type": "module",
19
+ "main": "dist/index.js",
20
+ "exports": {
21
+ ".": "./dist/index.js",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "cordis.patch.yml"
27
+ ],
28
+ "dsh": {
29
+ "bundle": {
30
+ "patch": "./cordis.patch.yml"
31
+ }
32
+ },
33
+ "engines": {
34
+ "node": "^22.19.0 || >=24.0.0"
35
+ },
36
+ "dependencies": {
37
+ "@deepseek-ai/schemastery": "^3.18.1",
38
+ "tweetnacl": "^1.0.3"
39
+ },
40
+ "peerDependencies": {
41
+ "@deepseek-ai/cordis": "^4.0.1",
42
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
43
+ "@deepseek-ai/dsh-agent-default-model": "^0.1.1-rc.2",
44
+ "@deepseek-ai/dsh-agent-presets": "^0.1.1-rc.2",
45
+ "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
46
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
47
+ "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
48
+ "@deepseek-ai/dsh-session-query": "^0.1.1-rc.2",
49
+ "@deepseek-ai/dsh-user-approval": "^0.1.1-rc.2"
50
+ },
51
+ "devDependencies": {
52
+ "@deepseek-ai/cordis": "^4.0.1",
53
+ "@deepseek-ai/dsh-agent": "^0.1.1-rc.2",
54
+ "@deepseek-ai/dsh-agent-default-model": "^0.1.1-rc.2",
55
+ "@deepseek-ai/dsh-agent-presets": "^0.1.1-rc.2",
56
+ "@deepseek-ai/dsh-commands": "^0.1.1-rc.2",
57
+ "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
58
+ "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
59
+ "@deepseek-ai/dsh-session-query": "^0.1.1-rc.2",
60
+ "@deepseek-ai/dsh-user-approval": "^0.1.1-rc.2",
61
+ "@types/node": "^22.19.0",
62
+ "@types/ws": "^8.18.1",
63
+ "tsup": "^8.5.1",
64
+ "typescript": "^7.0.2",
65
+ "vitest": "^4.1.11",
66
+ "ws": "^8.18.3",
67
+ "@dsh-dispatch/shared": "0.1.0"
68
+ },
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "git+https://github.com/alextangson/dsh-dispatch.git",
72
+ "directory": "packages/plugin"
73
+ },
74
+ "homepage": "https://github.com/alextangson/dsh-dispatch#readme",
75
+ "bugs": "https://github.com/alextangson/dsh-dispatch/issues",
76
+ "scripts": {
77
+ "build": "tsup",
78
+ "typecheck": "tsc --noEmit",
79
+ "test": "vitest run"
80
+ }
81
+ }