@adhdev/session-host-core 1.0.58-rc.83 → 1.0.58-rc.84

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.
package/dist/index.d.mts CHANGED
@@ -571,6 +571,16 @@ interface SessionHostDisconnectInfo {
571
571
  /** In-flight requests abandoned by this disconnect (they were rejected). */
572
572
  pendingRequests: number;
573
573
  error?: Error;
574
+ /**
575
+ * Bytes discarded from a newline-unterminated frame at EOF (0 when the peer
576
+ * hung up on a clean frame boundary, which is the normal case).
577
+ *
578
+ * Non-zero means the peer was cut off MID-WRITE — diagnostic context that
579
+ * distinguishes "the host exited" from "the host died with a frame in
580
+ * flight". Before the parser's `end()` was reachable this was unknowable:
581
+ * the bytes were dropped with the decoder and no counter existed.
582
+ */
583
+ droppedTailBytes: number;
574
584
  }
575
585
  declare class SessionHostClient {
576
586
  readonly endpoint: SessionHostEndpoint;
package/dist/index.d.ts CHANGED
@@ -571,6 +571,16 @@ interface SessionHostDisconnectInfo {
571
571
  /** In-flight requests abandoned by this disconnect (they were rejected). */
572
572
  pendingRequests: number;
573
573
  error?: Error;
574
+ /**
575
+ * Bytes discarded from a newline-unterminated frame at EOF (0 when the peer
576
+ * hung up on a clean frame boundary, which is the normal case).
577
+ *
578
+ * Non-zero means the peer was cut off MID-WRITE — diagnostic context that
579
+ * distinguishes "the host exited" from "the host died with a frame in
580
+ * flight". Before the parser's `end()` was reachable this was unknowable:
581
+ * the bytes were dropped with the decoder and no counter existed.
582
+ */
583
+ droppedTailBytes: number;
574
584
  }
575
585
  declare class SessionHostClient {
576
586
  readonly endpoint: SessionHostEndpoint;
package/dist/index.js CHANGED
@@ -665,7 +665,7 @@ var SessionHostClient = class {
665
665
  this.disconnectListeners.delete(listener);
666
666
  };
667
667
  }
668
- handleDisconnect(socket, reason, error) {
668
+ handleDisconnect(socket, reason, error, droppedTailBytes = 0) {
669
669
  if (this.disconnectedSockets.has(socket)) return;
670
670
  this.disconnectedSockets.add(socket);
671
671
  const wasEstablished = establishedSockets.has(socket);
@@ -697,7 +697,8 @@ var SessionHostClient = class {
697
697
  reason,
698
698
  endpointPath: this.endpoint.path,
699
699
  pendingRequests,
700
- error
700
+ error,
701
+ droppedTailBytes
701
702
  };
702
703
  for (const listener of this.disconnectListeners) {
703
704
  try {
@@ -717,7 +718,7 @@ var SessionHostClient = class {
717
718
  }
718
719
  const socket = net.createConnection(this.endpoint.path);
719
720
  this.socket = socket;
720
- socket.on("data", createLineParser((envelope) => {
721
+ const parser = createLineParser((envelope) => {
721
722
  if (envelope.kind === "response") {
722
723
  const waiter = this.requestWaiters.get(envelope.requestId);
723
724
  if (waiter) {
@@ -729,15 +730,24 @@ var SessionHostClient = class {
729
730
  if (envelope.kind === "event") {
730
731
  for (const listener of this.eventListeners) listener(envelope.event);
731
732
  }
732
- }));
733
+ });
734
+ socket.on("data", parser);
735
+ const flushParser = () => {
736
+ try {
737
+ return parser.end();
738
+ } catch {
739
+ return "";
740
+ }
741
+ };
733
742
  socket.on("error", (error) => {
734
- this.handleDisconnect(socket, "error", error);
743
+ const remainder = flushParser();
744
+ this.handleDisconnect(socket, "error", error, remainder.length);
735
745
  });
736
746
  socket.on("close", () => {
737
- this.handleDisconnect(socket, "closed");
747
+ this.handleDisconnect(socket, "closed", void 0, flushParser().length);
738
748
  });
739
749
  socket.on("end", () => {
740
- this.handleDisconnect(socket, "ended");
750
+ this.handleDisconnect(socket, "ended", void 0, flushParser().length);
741
751
  });
742
752
  await new Promise((resolve3, reject) => {
743
753
  socket.once("connect", () => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/termination.ts","../src/buffer.ts","../src/defaults.ts","../src/runtime-labels.ts","../src/registry.ts","../src/ipc.ts","../src/instance-key.ts","../src/spawn-env.ts","../src/control-plane.ts"],"sourcesContent":["export type {\n AcquireWritePayload,\n AttachSessionPayload,\n ClearSessionBufferPayload,\n CreateSessionPayload,\n DetachSessionPayload,\n ForceDetachClientPayload,\n GetHostDiagnosticsPayload,\n GetSnapshotPayload,\n GetTerminalSnapshotPayload,\n PruneDuplicateSessionsPayload,\n ReleaseWritePayload,\n ResumeSessionPayload,\n RestartSessionPayload,\n ResizeSessionPayload,\n SendSignalPayload,\n SendInputPayload,\n UpdateSessionMetaPayload,\n SessionAttachedClient,\n SessionBufferSnapshot,\n SessionClientType,\n SessionHostDiagnostics,\n SessionHostDuplicateSessionGroup,\n SessionHostCategory,\n SessionHostSurfaceKind,\n SessionHostEvent,\n SessionHostEventEnvelope,\n SessionHostLogEntry,\n SessionHostPruneDuplicatesResult,\n SessionHostRecord,\n SessionHostRequest,\n SessionHostRequestEnvelope,\n SessionHostRequestTrace,\n SessionHostRequestType,\n SessionHostResponse,\n SessionHostResponseEnvelope,\n SessionHostRuntimeTransition,\n SessionTerminalSnapshot,\n SessionTerminalState,\n SessionLaunchCommand,\n SessionLifecycle,\n SessionOwnerType,\n SessionTransport,\n SessionTermination,\n SessionHostWireEnvelope,\n SessionWriteOwner,\n StopSessionPayload,\n} from './types.js';\n\nexport { SESSION_HOST_SUPPORTED_REQUEST_TYPES } from './types.js';\nexport { classifyTermination } from './termination.js';\n\nexport { DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, SessionRingBuffer } from './buffer.js';\nexport type { SessionRingBufferOptions } from './buffer.js';\nexport { SessionHostRegistry } from './registry.js';\nexport {\n buildRuntimeDisplayName,\n buildRuntimeKey,\n formatRuntimeOwner,\n getSessionHostRecoveryLabel,\n getSessionHostSurfaceKind,\n getWorkspaceLabel,\n isSessionHostLiveRuntime,\n isSessionHostRecoverySnapshot,\n isTerminalRecord,\n partitionSessionHostDiagnosticsSessions,\n partitionSessionHostRecords,\n resolveAttachableRuntimeRecord,\n resolveRuntimeRecord,\n} from './runtime-labels.js';\nexport type { SessionHostSurfaceRecordLike } from './runtime-labels.js';\nexport {\n SessionHostClient,\n createLineParser,\n createResponseEnvelope,\n getDefaultSessionHostEndpoint,\n writeEnvelope,\n} from './ipc.js';\nexport type {\n SessionHostClientOptions,\n SessionHostEndpoint,\n SessionHostEndpointOptions,\n SessionHostDisconnectInfo,\n SessionHostDisconnectReason,\n} from './ipc.js';\nexport {\n DEFAULT_CONFIG_DIR_NAME,\n canonicalizeInstancePath,\n isDefaultInstanceConfigDir,\n resolveInstanceConfigDir,\n resolveSessionHostIpcKey,\n} from './instance-key.js';\nexport {\n DEFAULT_SESSION_HOST_COLS,\n DEFAULT_SESSION_HOST_ROWS,\n resolveSessionHostCols,\n resolveSessionHostRows,\n} from './defaults.js';\nexport {\n sanitizeSpawnEnv,\n applyTerminalColorEnv,\n ensureNodePtySpawnHelperPermissions,\n} from './spawn-env.js';\nexport { createSessionHostControlPlane } from './control-plane.js';\nexport type { SessionHostControlPlane, SessionHostControlTransport } from './control-plane.js';\n","export type SessionTransport = 'pty';\n\nexport type SessionHostCategory = 'cli' | 'acp' | 'shell';\n\nexport type SessionLifecycle = 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted';\n\nexport type SessionClientType = 'daemon' | 'web' | 'local-terminal';\n\nexport type SessionOwnerType = 'agent' | 'user';\n\nexport interface SessionLaunchCommand {\n command: string;\n args: string[];\n env?: Record<string, string>;\n}\n\nexport interface SessionWriteOwner {\n clientId: string;\n ownerType: SessionOwnerType;\n acquiredAt: number;\n}\n\nexport interface SessionAttachedClient {\n clientId: string;\n type: SessionClientType;\n readOnly: boolean;\n attachedAt: number;\n lastSeenAt: number;\n}\n\nexport interface SessionBufferSnapshot {\n seq: number;\n text: string;\n truncated: boolean;\n cols?: number;\n rows?: number;\n}\n\nexport interface SessionTerminalState {\n cursor: { row: number; col: number };\n altScreen: boolean;\n pasteMode: boolean;\n rawMode: boolean;\n scrollRegion: { top: number; bot: number };\n cols: number;\n rows: number;\n}\n\nexport interface SessionTerminalSnapshot {\n text: string;\n state: SessionTerminalState;\n}\n\nexport interface SessionBufferState {\n scrollbackBytes: number;\n snapshotSeq: number;\n}\n\nexport type SessionHostSurfaceKind = 'live_runtime' | 'recovery_snapshot' | 'inactive_record';\n\n/**\n * Compact, secret-free record of how a session's PTY process terminated.\n *\n * Preserves the nullable/unknown exitCode and signal exactly as observed from\n * the first node-pty `onExit` — an `exitCode` of `null` means \"unknown\" (the\n * process was terminated by a signal, or the exit code was otherwise not\n * reported) and MUST NOT be collapsed to 0. Persisted as a tombstone so\n * termination diagnostics remain inspectable after the live runtime and its\n * transport adapter are torn down.\n */\nexport interface SessionTermination {\n /** Exit code exactly as observed; `null` = unknown / signal-terminated. Never fabricated to 0. */\n exitCode: number | null;\n /** Signal number that terminated the process, if any; `null` when not signalled. */\n signal: number | null;\n /** Classification derived from (exitCode, signal): clean exit vs. failure vs. unknown. */\n reason: 'exit' | 'signal' | 'failed' | 'unknown';\n /** Lifecycle recorded for this termination (`stopped` | `failed`). */\n lifecycle: Extract<SessionLifecycle, 'stopped' | 'failed'>;\n /** Wall-clock time the termination was observed. */\n terminatedAt: number;\n /** OS pid of the terminated process, if it was known. */\n osPid?: number;\n /** Lifecycle the session was in immediately before termination. */\n previousLifecycle?: SessionLifecycle;\n /** Last time output was observed from the session, if known. */\n lastOutputAt?: number;\n /** Whether the termination followed an explicit stop/delete request, and which. */\n requestedStop?: 'stop' | 'delete' | 'restart' | 'prune';\n}\n\nexport interface SessionHostRecord {\n sessionId: string;\n runtimeKey: string;\n displayName: string;\n workspaceLabel: string;\n transport: SessionTransport;\n providerType: string;\n category: SessionHostCategory;\n workspace: string;\n launchCommand: SessionLaunchCommand;\n osPid?: number;\n createdAt: number;\n startedAt?: number;\n lastActivityAt: number;\n lifecycle: SessionLifecycle;\n surfaceKind?: SessionHostSurfaceKind;\n writeOwner: SessionWriteOwner | null;\n attachedClients: SessionAttachedClient[];\n buffer: SessionBufferState;\n meta: Record<string, unknown>;\n /** Termination tombstone, stamped when the PTY process exits. Retained for post-mortem inspection. */\n termination?: SessionTermination;\n}\n\nexport interface CreateSessionPayload {\n sessionId?: string;\n runtimeKey?: string;\n displayName?: string;\n providerType: string;\n category: SessionHostCategory;\n workspace: string;\n launchCommand: SessionLaunchCommand;\n cols?: number;\n rows?: number;\n clientId?: string;\n clientType?: SessionClientType;\n meta?: Record<string, unknown>;\n}\n\nexport interface AttachSessionPayload {\n sessionId: string;\n clientId: string;\n clientType: SessionClientType;\n readOnly?: boolean;\n}\n\nexport interface DetachSessionPayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface SendInputPayload {\n sessionId: string;\n clientId: string;\n data: string;\n}\n\nexport interface ResizeSessionPayload {\n sessionId: string;\n cols: number;\n rows: number;\n}\n\nexport interface StopSessionPayload {\n sessionId: string;\n}\n\nexport interface DeleteSessionPayload {\n sessionId: string;\n /** Stop a live runtime before deleting the persisted record. */\n force?: boolean;\n}\n\nexport interface ResumeSessionPayload {\n sessionId: string;\n}\n\nexport interface AcquireWritePayload {\n sessionId: string;\n clientId: string;\n ownerType: SessionOwnerType;\n force?: boolean;\n}\n\nexport interface ReleaseWritePayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface GetSnapshotPayload {\n sessionId: string;\n sinceSeq?: number;\n}\n\nexport interface GetTerminalSnapshotPayload {\n sessionId: string;\n}\n\nexport interface ClearSessionBufferPayload {\n sessionId: string;\n}\n\nexport interface UpdateSessionMetaPayload {\n sessionId: string;\n meta: Record<string, unknown>;\n replace?: boolean;\n}\n\nexport interface GetHostDiagnosticsPayload {\n includeSessions?: boolean;\n limit?: number;\n}\n\nexport interface ForceDetachClientPayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface SendSignalPayload {\n sessionId: string;\n signal: string;\n}\n\nexport interface RestartSessionPayload {\n sessionId: string;\n}\n\nexport interface PruneDuplicateSessionsPayload {\n providerType?: string;\n workspace?: string;\n dryRun?: boolean;\n}\n\nexport interface SessionHostDuplicateSessionGroup {\n bindingKey: string;\n providerType: string;\n workspace: string;\n providerSessionId: string;\n keptSessionId: string;\n prunedSessionIds: string[];\n}\n\nexport interface SessionHostPruneDuplicatesResult {\n duplicateGroupCount: number;\n keptSessionIds: string[];\n prunedSessionIds: string[];\n groups: SessionHostDuplicateSessionGroup[];\n}\n\nexport interface SessionHostLogEntry {\n timestamp: number;\n level: 'debug' | 'info' | 'warn' | 'error';\n message: string;\n sessionId?: string;\n data?: Record<string, unknown>;\n}\n\nexport interface SessionHostRequestTrace {\n timestamp: number;\n requestId: string;\n type: SessionHostRequest['type'];\n sessionId?: string;\n clientId?: string;\n success: boolean;\n durationMs: number;\n error?: string;\n}\n\nexport interface SessionHostRuntimeTransition {\n timestamp: number;\n sessionId: string;\n action: string;\n lifecycle?: SessionLifecycle;\n detail?: string;\n success?: boolean;\n error?: string;\n}\n\nexport interface SessionHostDiagnostics {\n hostStartedAt: number;\n endpoint: string;\n /**\n * Absolute path of the script this host process is running (its own\n * `__filename`), self-reported.\n *\n * This is the only way a client can learn WHICH install a reachable host came\n * from without process inspection. On Windows the daemon's PowerShell/wmic\n * command-line probe is blocked outright on some boxes (AV/EDR, and `wmic` is\n * removed by default in Win11 24H2+), so identity checks that depend on it\n * fail silently — which let a host from a DELETED install prefix keep serving\n * its socket while every `create_session` died loading node-pty's conpty.node\n * from the missing tree. The host answering with its own path needs no\n * privileges and always works.\n *\n * Optional: an older host predating this field simply omits it, and callers\n * must treat `undefined` as \"unknown\", never as a match.\n */\n hostEntryPath?: string;\n runtimeCount: number;\n supportedRequestTypes?: SessionHostRequestType[];\n sessions?: SessionHostRecord[];\n liveRuntimes?: SessionHostRecord[];\n recoverySnapshots?: SessionHostRecord[];\n inactiveRecords?: SessionHostRecord[];\n recentLogs: SessionHostLogEntry[];\n recentRequests: SessionHostRequestTrace[];\n recentTransitions: SessionHostRuntimeTransition[];\n}\n\nexport type SessionHostRequest =\n | { type: 'create_session'; payload: CreateSessionPayload }\n | { type: 'attach_session'; payload: AttachSessionPayload }\n | { type: 'detach_session'; payload: DetachSessionPayload }\n | { type: 'send_input'; payload: SendInputPayload }\n | { type: 'resize_session'; payload: ResizeSessionPayload }\n | { type: 'stop_session'; payload: StopSessionPayload }\n | { type: 'delete_session'; payload: DeleteSessionPayload }\n | { type: 'resume_session'; payload: ResumeSessionPayload }\n | { type: 'acquire_write'; payload: AcquireWritePayload }\n | { type: 'release_write'; payload: ReleaseWritePayload }\n | { type: 'get_snapshot'; payload: GetSnapshotPayload }\n | { type: 'get_terminal_snapshot'; payload: GetTerminalSnapshotPayload }\n | { type: 'clear_session_buffer'; payload: ClearSessionBufferPayload }\n | { type: 'update_session_meta'; payload: UpdateSessionMetaPayload }\n | { type: 'get_host_diagnostics'; payload?: GetHostDiagnosticsPayload }\n | { type: 'force_detach_client'; payload: ForceDetachClientPayload }\n | { type: 'send_signal'; payload: SendSignalPayload }\n | { type: 'restart_session'; payload: RestartSessionPayload }\n | { type: 'prune_duplicate_sessions'; payload?: PruneDuplicateSessionsPayload }\n | { type: 'list_sessions'; payload?: {} };\n\nexport type SessionHostRequestType = SessionHostRequest['type'];\n\nexport const SESSION_HOST_SUPPORTED_REQUEST_TYPES: readonly SessionHostRequestType[] = [\n 'create_session',\n 'attach_session',\n 'detach_session',\n 'send_input',\n 'resize_session',\n 'stop_session',\n 'delete_session',\n 'resume_session',\n 'acquire_write',\n 'release_write',\n 'get_snapshot',\n 'get_terminal_snapshot',\n 'clear_session_buffer',\n 'update_session_meta',\n 'get_host_diagnostics',\n 'force_detach_client',\n 'send_signal',\n 'restart_session',\n 'prune_duplicate_sessions',\n 'list_sessions',\n];\n\nexport interface SessionHostResponse<T = unknown> {\n success: boolean;\n result?: T;\n error?: string;\n}\n\nexport type SessionHostEvent =\n | { type: 'session_created'; sessionId: string; record: SessionHostRecord }\n | { type: 'session_started'; sessionId: string; pid?: number }\n | { type: 'session_resumed'; sessionId: string; pid?: number }\n | { type: 'session_output'; sessionId: string; seq: number; data: string }\n | { type: 'session_cleared'; sessionId: string }\n | { type: 'session_exit'; sessionId: string; exitCode: number | null; signal?: number | null; termination?: SessionTermination }\n | { type: 'session_stopped'; sessionId: string }\n | { type: 'session_deleted'; sessionId: string }\n | { type: 'session_resized'; sessionId: string; cols: number; rows: number }\n | { type: 'write_owner_changed'; sessionId: string; owner: SessionWriteOwner | null }\n | { type: 'client_attached'; sessionId: string; client: SessionAttachedClient }\n | { type: 'client_detached'; sessionId: string; clientId: string }\n | { type: 'host_log'; entry: SessionHostLogEntry }\n | { type: 'request_trace'; trace: SessionHostRequestTrace }\n | { type: 'runtime_transition'; transition: SessionHostRuntimeTransition };\n\nexport interface SessionHostRequestEnvelope {\n kind: 'request';\n requestId: string;\n request: SessionHostRequest;\n}\n\nexport interface SessionHostResponseEnvelope {\n kind: 'response';\n requestId: string;\n response: SessionHostResponse;\n}\n\nexport interface SessionHostEventEnvelope {\n kind: 'event';\n event: SessionHostEvent;\n}\n\nexport type SessionHostWireEnvelope =\n | SessionHostRequestEnvelope\n | SessionHostResponseEnvelope\n | SessionHostEventEnvelope;\n","import type { SessionLifecycle, SessionTermination } from './types.js';\n\nexport interface ClassifyTerminationInput {\n exitCode: number | null;\n signal?: number | null;\n osPid?: number;\n previousLifecycle?: SessionLifecycle;\n lastOutputAt?: number;\n requestedStop?: SessionTermination['requestedStop'];\n terminatedAt: number;\n}\n\n/**\n * Classify a PTY termination from its raw (exitCode, signal) pair.\n *\n * Invariants:\n * - A `null` exitCode is UNKNOWN, never a clean exit — it MUST NOT collapse to\n * exit 0. This is the regression guard for the old `exitCode ?? 0` behavior.\n * - A non-null, non-zero exitCode or any signal is a failure.\n * - Only an explicit exit code of 0 (with no signal) is a clean stop.\n *\n * The returned lifecycle is `stopped` only for a clean exit 0; every other\n * case (nonzero, signalled, or unknown) is `failed` so that an unknown\n * termination stays distinguishable from a successful one.\n */\nexport function classifyTermination(input: ClassifyTerminationInput): SessionTermination {\n const { exitCode, terminatedAt } = input;\n const signal = input.signal ?? null;\n\n let reason: SessionTermination['reason'];\n let lifecycle: SessionTermination['lifecycle'];\n\n if (signal !== null && signal !== 0) {\n reason = 'signal';\n lifecycle = 'failed';\n } else if (exitCode === null) {\n reason = 'unknown';\n lifecycle = 'failed';\n } else if (exitCode === 0) {\n reason = 'exit';\n lifecycle = 'stopped';\n } else {\n reason = 'failed';\n lifecycle = 'failed';\n }\n\n const termination: SessionTermination = {\n exitCode,\n signal,\n reason,\n lifecycle,\n terminatedAt,\n };\n if (typeof input.osPid === 'number') termination.osPid = input.osPid;\n if (input.previousLifecycle) termination.previousLifecycle = input.previousLifecycle;\n if (typeof input.lastOutputAt === 'number') termination.lastOutputAt = input.lastOutputAt;\n if (input.requestedStop) termination.requestedStop = input.requestedStop;\n return termination;\n}\n","import type { SessionBufferSnapshot } from './types.js';\n\nexport interface SessionRingBufferOptions {\n maxBytes?: number;\n}\n\n// Manual \"Load older terminal output\" replays this raw ring buffer into the\n// browser terminal. 512KiB was too small for long Claude/Codex conversations:\n// by the time the user scrolled to the top and clicked the loader, the output\n// they were trying to recover was often already trimmed.\nexport const DEFAULT_SESSION_RING_BUFFER_MAX_BYTES = 4 * 1024 * 1024;\n\nexport class SessionRingBuffer {\n private maxBytes: number;\n private chunks: { seq: number; data: string; bytes: number }[] = [];\n private nextSeq = 1;\n private totalBytes = 0;\n\n constructor(options: SessionRingBufferOptions = {}) {\n this.maxBytes = options.maxBytes ?? DEFAULT_SESSION_RING_BUFFER_MAX_BYTES;\n }\n\n append(data: string): number {\n const normalized = typeof data === 'string' ? data : String(data ?? '');\n const bytes = Buffer.byteLength(normalized, 'utf8');\n const seq = this.nextSeq++;\n\n this.chunks.push({ seq, data: normalized, bytes });\n this.totalBytes += bytes;\n this.trim();\n return seq;\n }\n\n snapshot(sinceSeq?: number): SessionBufferSnapshot {\n const relevant = typeof sinceSeq === 'number'\n ? this.chunks.filter(chunk => chunk.seq > sinceSeq)\n : this.chunks;\n\n const text = relevant.map(chunk => chunk.data).join('');\n const truncated = !!this.chunks[0] && typeof sinceSeq === 'number' && sinceSeq < this.chunks[0].seq - 1;\n\n return {\n seq: this.nextSeq - 1,\n text,\n truncated,\n };\n }\n\n getState(): { scrollbackBytes: number; snapshotSeq: number } {\n return {\n scrollbackBytes: this.totalBytes,\n snapshotSeq: this.nextSeq - 1,\n };\n }\n\n clear(): void {\n this.chunks = [];\n this.totalBytes = 0;\n this.nextSeq = 1;\n }\n\n restore(snapshot: { seq: number; text: string }): void {\n this.clear();\n const text = String(snapshot.text || '');\n if (!text) {\n this.nextSeq = Math.max(1, Number(snapshot.seq || 0) + 1);\n return;\n }\n const bytes = Buffer.byteLength(text, 'utf8');\n const seq = Math.max(1, Number(snapshot.seq || 1));\n this.chunks = [{ seq, data: text, bytes }];\n this.totalBytes = bytes;\n this.nextSeq = seq + 1;\n this.trim();\n }\n\n private trim(): void {\n while (this.totalBytes > this.maxBytes && this.chunks.length > 1) {\n const removed = this.chunks.shift();\n if (!removed) break;\n this.totalBytes -= removed.bytes;\n }\n }\n}\n","export const DEFAULT_SESSION_HOST_COLS = 80;\nexport const DEFAULT_SESSION_HOST_ROWS = 32;\n\nfunction normalizeSessionHostDimension(value: number | undefined, fallback: number): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;\n const rounded = Math.floor(value);\n return rounded > 0 ? rounded : fallback;\n}\n\nexport function resolveSessionHostCols(value: number | undefined): number {\n return normalizeSessionHostDimension(value, DEFAULT_SESSION_HOST_COLS);\n}\n\nexport function resolveSessionHostRows(value: number | undefined): number {\n return normalizeSessionHostDimension(value, DEFAULT_SESSION_HOST_ROWS);\n}\n","import * as path from 'path';\nimport type { CreateSessionPayload, SessionHostRecord, SessionHostSurfaceKind } from './types.js';\n\nfunction normalizeSlug(input: string): string {\n return input\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48);\n}\n\nfunction normalizeValue(input: string): string {\n return input.trim().toLowerCase();\n}\n\nexport function getWorkspaceLabel(workspace: string): string {\n const trimmed = workspace.trim();\n if (!trimmed) return 'workspace';\n const normalized = trimmed.replace(/[\\\\/]+$/, '');\n const base = path.basename(normalized);\n return base || normalized;\n}\n\nexport function buildRuntimeDisplayName(payload: Pick<CreateSessionPayload, 'displayName' | 'providerType' | 'workspace'>): string {\n const explicit = payload.displayName?.trim();\n if (explicit) return explicit;\n const workspaceLabel = getWorkspaceLabel(payload.workspace);\n const providerLabel = payload.providerType.trim() || 'runtime';\n return `${providerLabel} @ ${workspaceLabel}`;\n}\n\nexport function buildRuntimeKey(\n payload: Pick<CreateSessionPayload, 'runtimeKey' | 'displayName' | 'providerType' | 'workspace'>,\n existingKeys: Iterable<string>,\n): string {\n const requested = payload.runtimeKey?.trim();\n const existing = new Set(Array.from(existingKeys, (key) => key.toLowerCase()));\n const displayName = buildRuntimeDisplayName(payload);\n const baseKey = normalizeSlug(requested || displayName || getWorkspaceLabel(payload.workspace) || payload.providerType || 'runtime') || 'runtime';\n if (!existing.has(baseKey)) return baseKey;\n\n let suffix = 2;\n let candidate = `${baseKey}-${suffix}`;\n while (existing.has(candidate)) {\n suffix += 1;\n candidate = `${baseKey}-${suffix}`;\n }\n return candidate;\n}\n\nexport interface SessionHostSurfaceRecordLike {\n lifecycle?: string | null;\n surfaceKind?: SessionHostSurfaceKind | null;\n meta?: Record<string, unknown> | null;\n termination?: unknown;\n}\n\nconst LIVE_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'interrupted']);\nconst TERMINAL_LIFECYCLES = new Set(['stopped', 'failed']);\n\n/**\n * True when a record has reached a terminal state: either its lifecycle is\n * terminal, or it carries a termination stamp (which the exit handler writes\n * alongside the lifecycle, and which can outlive a stale 'stopping' value).\n *\n * Used both to keep terminated records from being persisted as live files and\n * to keep them from surfacing as attach targets.\n */\nexport function isTerminalRecord(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.termination) return true;\n return TERMINAL_LIFECYCLES.has(String(record.lifecycle || '').trim());\n}\n\nexport function isSessionHostLiveRuntime(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.surfaceKind === 'live_runtime') return true;\n if (record.surfaceKind === 'recovery_snapshot' || record.surfaceKind === 'inactive_record') return false;\n // A record stamped with a termination is terminal even if its lifecycle is\n // still 'stopping' — e.g. a stale file recreated by a late persist that\n // raced the exit/cleanup path. Without this, such records surface as a\n // live runtime (attach target) for a PID that already exited.\n if (record.termination) return false;\n const lifecycle = String(record.lifecycle || '').trim();\n return LIVE_LIFECYCLES.has(lifecycle);\n}\n\nexport function getSessionHostRecoveryLabel(meta: Record<string, unknown> | null | undefined): string | null {\n const recoveryState = typeof meta?.runtimeRecoveryState === 'string'\n ? String(meta.runtimeRecoveryState).trim()\n : '';\n if (!recoveryState) return null;\n if (recoveryState === 'auto_resumed') return 'restored after restart';\n if (recoveryState === 'resume_failed') return 'restore failed';\n if (recoveryState === 'host_restart_interrupted') return 'host restart interrupted';\n if (recoveryState === 'orphan_snapshot') return 'snapshot recovered';\n return recoveryState.replace(/_/g, ' ');\n}\n\nexport function isSessionHostRecoverySnapshot(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.surfaceKind === 'recovery_snapshot') return true;\n if (record.surfaceKind === 'live_runtime' || record.surfaceKind === 'inactive_record') return false;\n if (isSessionHostLiveRuntime(record)) return false;\n\n const lifecycle = String(record.lifecycle || '').trim();\n if (lifecycle && lifecycle !== 'stopped' && lifecycle !== 'failed') {\n return false;\n }\n\n const meta = record.meta || undefined;\n if (meta?.restoredFromStorage === true) return true;\n return getSessionHostRecoveryLabel(meta) !== null;\n}\n\nexport function getSessionHostSurfaceKind(record: SessionHostSurfaceRecordLike | null | undefined): SessionHostSurfaceKind {\n if (record?.surfaceKind === 'live_runtime' || record?.surfaceKind === 'recovery_snapshot' || record?.surfaceKind === 'inactive_record') {\n return record.surfaceKind;\n }\n if (isSessionHostLiveRuntime(record)) return 'live_runtime';\n if (isSessionHostRecoverySnapshot(record)) return 'recovery_snapshot';\n return 'inactive_record';\n}\n\nexport function partitionSessionHostRecords<T extends SessionHostSurfaceRecordLike>(records: T[]): {\n liveRuntimes: T[];\n recoverySnapshots: T[];\n inactiveRecords: T[];\n} {\n const liveRuntimes: T[] = [];\n const recoverySnapshots: T[] = [];\n const inactiveRecords: T[] = [];\n\n for (const record of records) {\n const kind = getSessionHostSurfaceKind(record);\n if (kind === 'live_runtime') {\n liveRuntimes.push(record);\n } else if (kind === 'recovery_snapshot') {\n recoverySnapshots.push(record);\n } else {\n inactiveRecords.push(record);\n }\n }\n\n return {\n liveRuntimes,\n recoverySnapshots,\n inactiveRecords,\n };\n}\n\nexport function partitionSessionHostDiagnosticsSessions(\n records: SessionHostRecord[] | null | undefined,\n): ReturnType<typeof partitionSessionHostRecords<SessionHostRecord>> {\n return partitionSessionHostRecords(records || []);\n}\n\nexport function resolveAttachableRuntimeRecord(records: SessionHostRecord[], identifier: string): SessionHostRecord {\n const record = resolveRuntimeRecord(records, identifier);\n const surfaceKind = getSessionHostSurfaceKind(record);\n if (surfaceKind === 'live_runtime') {\n return record;\n }\n if (surfaceKind === 'recovery_snapshot') {\n throw new Error(`Runtime ${record.runtimeKey} is a recovery snapshot, not a live attach target. Resume or recover it first.`);\n }\n throw new Error(`Runtime ${record.runtimeKey} is ${record.lifecycle}, not a live attach target.`);\n}\n\nfunction uniqueMatch(records: SessionHostRecord[], predicate: (record: SessionHostRecord) => boolean): SessionHostRecord | null {\n const matches = records.filter(predicate);\n if (matches.length === 1) return matches[0] || null;\n if (matches.length === 0) return null;\n const labels = matches.map((record) => `${record.runtimeKey} (${record.sessionId})`).join(', ');\n throw new Error(`Ambiguous runtime target. Matches: ${labels}`);\n}\n\nexport function resolveRuntimeRecord(records: SessionHostRecord[], identifier: string): SessionHostRecord {\n const target = identifier.trim();\n if (!target) {\n throw new Error('Runtime target is required');\n }\n\n const exact = uniqueMatch(records, (record) =>\n record.sessionId === target ||\n normalizeValue(record.runtimeKey) === normalizeValue(target) ||\n normalizeValue(record.displayName) === normalizeValue(target),\n );\n if (exact) return exact;\n\n const prefix = uniqueMatch(records, (record) =>\n record.sessionId.startsWith(target) ||\n normalizeValue(record.runtimeKey).startsWith(normalizeValue(target)),\n );\n if (prefix) return prefix;\n\n throw new Error(`Unknown runtime target: ${target}`);\n}\n\nexport function formatRuntimeOwner(record: Pick<SessionHostRecord, 'writeOwner'>): string {\n if (!record.writeOwner) return 'none';\n return `${record.writeOwner.ownerType}:${record.writeOwner.clientId}`;\n}\n","function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\nimport type {\n AcquireWritePayload,\n AttachSessionPayload,\n CreateSessionPayload,\n DetachSessionPayload,\n ReleaseWritePayload,\n SessionAttachedClient,\n SessionHostRecord,\n SessionTermination,\n} from './types.js';\nimport { SessionRingBuffer } from './buffer.js';\nimport { resolveSessionHostCols, resolveSessionHostRows } from './defaults.js';\nimport { buildRuntimeDisplayName, buildRuntimeKey, getWorkspaceLabel } from './runtime-labels.js';\n\ninterface SessionRuntimeState {\n record: SessionHostRecord;\n buffer: SessionRingBuffer;\n}\n\nexport class SessionHostRegistry {\n private sessions = new Map<string, SessionRuntimeState>();\n\n createSession(payload: CreateSessionPayload): SessionHostRecord {\n const sessionId = payload.sessionId || generateUUID();\n if (this.sessions.has(sessionId)) {\n throw new Error(`Session already exists: ${sessionId}`);\n }\n const now = Date.now();\n const initialClient = payload.clientId\n ? [{\n clientId: payload.clientId,\n type: payload.clientType || 'daemon',\n readOnly: false,\n attachedAt: now,\n lastSeenAt: now,\n } satisfies SessionAttachedClient]\n : [];\n\n const record: SessionHostRecord = {\n sessionId,\n runtimeKey: buildRuntimeKey(\n payload,\n Array.from(this.sessions.values(), (state) => state.record.runtimeKey),\n ),\n displayName: buildRuntimeDisplayName(payload),\n workspaceLabel: getWorkspaceLabel(payload.workspace),\n transport: 'pty',\n providerType: payload.providerType,\n category: payload.category,\n workspace: payload.workspace,\n launchCommand: payload.launchCommand,\n createdAt: now,\n lastActivityAt: now,\n lifecycle: 'starting',\n writeOwner: null,\n attachedClients: initialClient,\n buffer: {\n scrollbackBytes: 0,\n snapshotSeq: 0,\n },\n meta: payload.meta || {},\n };\n\n record.meta = {\n sessionHostCols: resolveSessionHostCols(payload.cols),\n sessionHostRows: resolveSessionHostRows(payload.rows),\n ...record.meta,\n };\n\n this.sessions.set(sessionId, {\n record,\n buffer: new SessionRingBuffer(),\n });\n\n return this.cloneRecord(record);\n }\n\n restoreSession(record: SessionHostRecord, snapshot?: { seq: number; text: string } | null): SessionHostRecord {\n const cloned = this.cloneRecord(record);\n this.sessions.set(cloned.sessionId, {\n record: cloned,\n buffer: (() => {\n const buffer = new SessionRingBuffer();\n if (snapshot) buffer.restore(snapshot);\n return buffer;\n })(),\n });\n return this.cloneRecord(cloned);\n }\n\n listSessions(): SessionHostRecord[] {\n return Array.from(this.sessions.values())\n .map(state => this.cloneRecord(state.record))\n .sort((a, b) => b.lastActivityAt - a.lastActivityAt);\n }\n\n getSession(sessionId: string): SessionHostRecord | null {\n const state = this.sessions.get(sessionId);\n return state ? this.cloneRecord(state.record) : null;\n }\n\n attachClient(payload: AttachSessionPayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n const now = Date.now();\n let removedDaemonOwner = false;\n\n if (payload.clientType === 'daemon') {\n const staleDaemonClientIds = state.record.attachedClients\n .filter(client => client.type === 'daemon' && client.clientId !== payload.clientId)\n .map(client => client.clientId);\n if (staleDaemonClientIds.length > 0) {\n state.record.attachedClients = state.record.attachedClients.filter(\n client => !(client.type === 'daemon' && client.clientId !== payload.clientId),\n );\n if (state.record.writeOwner && staleDaemonClientIds.includes(state.record.writeOwner.clientId)) {\n removedDaemonOwner = true;\n }\n }\n }\n\n const existing = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n\n if (existing) {\n existing.type = payload.clientType;\n existing.readOnly = !!payload.readOnly;\n existing.lastSeenAt = now;\n } else {\n state.record.attachedClients.push({\n clientId: payload.clientId,\n type: payload.clientType,\n readOnly: !!payload.readOnly,\n attachedAt: now,\n lastSeenAt: now,\n });\n }\n\n if (removedDaemonOwner) {\n state.record.writeOwner = null;\n }\n\n state.record.lastActivityAt = now;\n return this.cloneRecord(state.record);\n }\n\n detachClient(payload: DetachSessionPayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n state.record.attachedClients = state.record.attachedClients.filter(client => client.clientId !== payload.clientId);\n if (state.record.writeOwner?.clientId === payload.clientId) {\n state.record.writeOwner = null;\n }\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n acquireWrite(payload: AcquireWritePayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n if (state.record.writeOwner && state.record.writeOwner.clientId !== payload.clientId && !payload.force) {\n throw new Error(`Write owned by ${state.record.writeOwner.clientId}`);\n }\n const attachedClient = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n if (attachedClient) {\n attachedClient.readOnly = false;\n attachedClient.lastSeenAt = Date.now();\n }\n state.record.writeOwner = {\n clientId: payload.clientId,\n ownerType: payload.ownerType,\n acquiredAt: Date.now(),\n };\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n releaseWrite(payload: ReleaseWritePayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n const attachedClient = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n if (attachedClient) {\n attachedClient.readOnly = false;\n attachedClient.lastSeenAt = Date.now();\n }\n if (state.record.writeOwner?.clientId === payload.clientId) {\n state.record.writeOwner = null;\n }\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n appendOutput(sessionId: string, data: string): { record: SessionHostRecord; seq: number } {\n const state = this.requireSession(sessionId);\n const seq = state.buffer.append(data);\n state.record.buffer = state.buffer.getState();\n state.record.lastActivityAt = Date.now();\n return { record: this.cloneRecord(state.record), seq };\n }\n\n getSnapshot(sessionId: string, sinceSeq?: number) {\n const state = this.requireSession(sessionId);\n state.record.buffer = state.buffer.getState();\n return state.buffer.snapshot(sinceSeq);\n }\n\n clearBuffer(sessionId: string): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.buffer.clear();\n state.record.buffer = state.buffer.getState();\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n updateSessionMeta(sessionId: string, meta: Record<string, unknown>, replace = false): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.meta = replace\n ? { ...meta }\n : {\n ...(state.record.meta || {}),\n ...meta,\n };\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n markStarted(sessionId: string, pid?: number): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = 'running';\n state.record.startedAt = state.record.startedAt || Date.now();\n if (typeof pid === 'number') state.record.osPid = pid;\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n markStopped(sessionId: string, lifecycle: 'stopped' | 'failed' = 'stopped', termination?: SessionTermination): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = lifecycle;\n state.record.lastActivityAt = Date.now();\n if (termination) state.record.termination = termination;\n return this.cloneRecord(state.record);\n }\n\n setLifecycle(sessionId: string, lifecycle: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted'): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = lifecycle;\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n deleteSession(sessionId: string): boolean {\n return this.sessions.delete(sessionId);\n }\n\n private requireSession(sessionId: string): SessionRuntimeState {\n const state = this.sessions.get(sessionId);\n if (!state) throw new Error(`Unknown session: ${sessionId}`);\n return state;\n }\n\n private cloneRecord(record: SessionHostRecord): SessionHostRecord {\n return {\n ...record,\n launchCommand: {\n ...record.launchCommand,\n args: [...record.launchCommand.args],\n env: record.launchCommand.env ? { ...record.launchCommand.env } : undefined,\n },\n writeOwner: record.writeOwner ? { ...record.writeOwner } : null,\n attachedClients: record.attachedClients.map(client => ({ ...client })),\n buffer: { ...record.buffer },\n meta: { ...record.meta },\n };\n }\n}\n","import * as os from 'os';\nimport * as path from 'path';\nimport * as net from 'net';\nimport { StringDecoder } from 'string_decoder';\nimport type {\n SessionHostEvent,\n SessionHostRequest,\n SessionHostRequestEnvelope,\n SessionHostResponse,\n SessionHostResponseEnvelope,\n SessionHostWireEnvelope,\n} from './types.js';\n\nfunction generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\nexport interface SessionHostEndpoint {\n kind: 'unix' | 'pipe';\n path: string;\n}\n\nexport interface SessionHostEndpointOptions {\n /**\n * Instance IPC namespace key (see instance-key.ts). '' / omitted yields the\n * legacy default-instance endpoint; a non-empty key suffixes the endpoint so\n * each config-dir instance gets its own socket/pipe.\n */\n ipcKey?: string;\n /** Test seam: override the platform the endpoint shape is derived for. */\n platform?: NodeJS.Platform;\n}\n\nexport function getDefaultSessionHostEndpoint(\n appName = 'adhdev',\n options: SessionHostEndpointOptions = {},\n): SessionHostEndpoint {\n const rawKey = typeof options.ipcKey === 'string' ? options.ipcKey.trim() : '';\n // Fail closed on a malformed key: silently dropping it would collapse the\n // endpoint back onto the default instance's namespace — a cross-instance leak.\n if (rawKey && !/^[0-9a-f]{12}$/.test(rawKey)) {\n throw new Error(`Invalid session-host ipcKey \"${rawKey}\" — expected '' or a 12-hex-char instance key`);\n }\n const suffix = rawKey ? `-${rawKey}` : '';\n const platform = options.platform || process.platform;\n if (platform === 'win32') {\n return {\n kind: 'pipe',\n path: `\\\\\\\\.\\\\pipe\\\\${appName}-session-host${suffix}`,\n };\n }\n\n return {\n kind: 'unix',\n path: path.join(os.tmpdir(), `${appName}-session-host${suffix}.sock`),\n };\n}\n\nfunction serializeEnvelope(envelope: SessionHostWireEnvelope): string {\n return `${JSON.stringify(envelope)}\\n`;\n}\n\n/**\n * UTF8-CHUNK-BOUNDARY: decode the socket stream, never the individual chunk.\n *\n * This used to call `chunk.toString()` on every Buffer independently. A socket\n * hands us arbitrary byte slices — a multi-byte UTF-8 sequence straddling two\n * chunks gets decoded as two truncated sequences, and each half becomes U+FFFD.\n * The damage is silent by construction: the replacement character is valid JSON\n * string content, so the envelope still parses and only the *value* is wrong.\n * A pasted 11,994-char Korean prompt lost exactly one character that way — the\n * `도` beginning at wire offset 8190, split across the 8192-byte chunk boundary,\n * arrived as `�착`.\n *\n * StringDecoder holds an incomplete trailing sequence back until the bytes that\n * finish it arrive, which is the only correct way to turn a byte stream into\n * text. String chunks bypass it: they are already decoded, and feeding them\n * through `Buffer.from` would re-encode text the caller never asked us to touch.\n */\nfunction createLineParser(onEnvelope: (envelope: SessionHostWireEnvelope) => void) {\n let buffer = '';\n const decoder = new StringDecoder('utf8');\n const parser = (chunk: Buffer | string) => {\n buffer += typeof chunk === 'string' ? chunk : decoder.write(chunk);\n let newlineIndex = buffer.indexOf('\\n');\n while (newlineIndex >= 0) {\n const rawLine = buffer.slice(0, newlineIndex).trim();\n buffer = buffer.slice(newlineIndex + 1);\n if (rawLine) {\n onEnvelope(JSON.parse(rawLine) as SessionHostWireEnvelope);\n }\n newlineIndex = buffer.indexOf('\\n');\n }\n };\n /**\n * Flush at EOF, returning whatever text never formed a complete line.\n *\n * `decoder.end()` releases any bytes still held back as an incomplete UTF-8\n * sequence; without it those bytes vanish with the decoder. What is left in\n * the buffer is by definition a partial line — envelopes are newline-framed,\n * so an unterminated tail is a truncated transmission, not a message. It is\n * therefore RETURNED for the caller to log rather than parsed: calling\n * JSON.parse on a half-written envelope inside a socket EOF handler would\n * raise an uncaught exception in the daemon, turning a peer that died\n * mid-write into a crash of the process observing it.\n */\n parser.end = (): string => {\n const remainder = buffer + decoder.end();\n buffer = '';\n return remainder;\n };\n return parser;\n}\n\nexport interface SessionHostClientOptions {\n endpoint?: SessionHostEndpoint;\n appName?: string;\n}\n\n/**\n * Why an established session-host connection went away.\n * - `error` — socket emitted 'error' (includes ECONNRESET on abrupt host death)\n * - `ended` — peer sent FIN ('end'): the host closed the connection cleanly\n * - `closed` — socket fully closed ('close') without a preceding error/end\n */\nexport type SessionHostDisconnectReason = 'error' | 'ended' | 'closed';\n\n/**\n * Sockets that reached 'connect' at least once. Used to distinguish a genuine\n * disconnect (host died under us) from a connect attempt that never landed\n * (host not up yet) — only the former is a session-host death signal.\n */\nconst establishedSockets = new WeakSet<net.Socket>();\n\nexport interface SessionHostDisconnectInfo {\n reason: SessionHostDisconnectReason;\n endpointPath: string;\n /** In-flight requests abandoned by this disconnect (they were rejected). */\n pendingRequests: number;\n error?: Error;\n}\n\nexport class SessionHostClient {\n readonly endpoint: SessionHostEndpoint;\n\n private socket: net.Socket | null = null;\n private requestWaiters = new Map<string, { resolve: (value: SessionHostResponse) => void; reject: (error: Error) => void }>();\n private eventListeners = new Set<(event: SessionHostEvent) => void>();\n private disconnectListeners = new Set<(info: SessionHostDisconnectInfo) => void>();\n /** Guards against emitting both an 'error'-path and a 'close'-path disconnect for one socket. */\n private disconnectedSockets = new WeakSet<net.Socket>();\n\n constructor(options: SessionHostClientOptions = {}) {\n this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || 'adhdev');\n }\n\n /**\n * Fires when an established connection to the session host goes away.\n *\n * Before this existed the client registered only 'data' and 'error' handlers,\n * so a *clean* FIN — the host process exiting normally, or dying to an\n * uncaught exception that took the socket down without an error event on our\n * end — ran nothing at all: `this.socket` stayed non-null pointing at a dead\n * socket, in-flight waiters hung to their 30s timeout, and no caller was told.\n * That silence is what let a session-host death present downstream as an\n * unrelated application-level fault (a missing completion marker) instead of\n * \"the host is gone\".\n */\n onDisconnect(listener: (info: SessionHostDisconnectInfo) => void): () => void {\n this.disconnectListeners.add(listener);\n return () => {\n this.disconnectListeners.delete(listener);\n };\n }\n\n private handleDisconnect(socket: net.Socket, reason: SessionHostDisconnectReason, error?: Error): void {\n if (this.disconnectedSockets.has(socket)) return;\n this.disconnectedSockets.add(socket);\n\n // A connect that never succeeded is a *failed connect*, not a disconnect —\n // reporting it as a host death would make every poll against a not-yet-started\n // host look like a crash. connect() already surfaces that failure to its caller\n // via the awaited 'error'. Still clear socket state below? No: connect()'s own\n // error path handles a never-established socket, and `this.socket` is\n // overwritten on the next connect() attempt regardless.\n const wasEstablished = establishedSockets.has(socket);\n if (!wasEstablished) {\n if (this.socket === socket) this.socket = null;\n try { socket.destroy(); } catch { /* noop */ }\n // Reject waiters anyway — a request() that raced a dying socket must not hang.\n const failure = error || new Error(`Session host connect failed (${this.endpoint.path})`);\n for (const waiter of this.requestWaiters.values()) waiter.reject(failure);\n this.requestWaiters.clear();\n return;\n }\n\n const pendingRequests = this.requestWaiters.size;\n const failure = error || new Error(`Session host connection ${reason} (${this.endpoint.path})`);\n for (const waiter of this.requestWaiters.values()) {\n waiter.reject(failure);\n }\n this.requestWaiters.clear();\n\n // Clear the dead socket reference so the next connect() creates a fresh\n // connection instead of short-circuiting on the `!this.socket.destroyed`\n // guard at the top of connect().\n if (this.socket === socket) {\n this.socket = null;\n }\n try { socket.destroy(); } catch { /* noop */ }\n\n const info: SessionHostDisconnectInfo = {\n reason,\n endpointPath: this.endpoint.path,\n pendingRequests,\n error,\n };\n for (const listener of this.disconnectListeners) {\n // A throwing observer must not suppress the remaining ones, and must not\n // propagate out of a socket event handler (that would be an uncaught\n // exception in the daemon).\n try { listener(info); } catch { /* noop */ }\n }\n }\n\n async connect(): Promise<void> {\n if (this.socket && !this.socket.destroyed) return;\n // Cleanup stale socket reference left after error/disconnect\n if (this.socket) {\n try { this.socket.destroy(); } catch { /* noop */ }\n this.socket = null;\n }\n\n const socket = net.createConnection(this.endpoint.path);\n this.socket = socket;\n\n socket.on('data', createLineParser((envelope) => {\n if (envelope.kind === 'response') {\n const waiter = this.requestWaiters.get(envelope.requestId);\n if (waiter) {\n this.requestWaiters.delete(envelope.requestId);\n waiter.resolve(envelope.response);\n }\n return;\n }\n\n if (envelope.kind === 'event') {\n for (const listener of this.eventListeners) listener(envelope.event);\n }\n }));\n\n socket.on('error', (error) => {\n this.handleDisconnect(socket, 'error', error);\n });\n\n // 'close'/'end' are the clean-FIN counterparts of 'error'. The host server\n // has always handled both directions (server.ts registers close+end); this\n // side handled neither, so host death was observable only if it happened to\n // surface as a socket error.\n socket.on('close', () => {\n this.handleDisconnect(socket, 'closed');\n });\n socket.on('end', () => {\n this.handleDisconnect(socket, 'ended');\n });\n\n await new Promise<void>((resolve, reject) => {\n socket.once('connect', () => {\n establishedSockets.add(socket);\n resolve();\n });\n socket.once('error', reject);\n });\n }\n\n onEvent(listener: (event: SessionHostEvent) => void): () => void {\n this.eventListeners.add(listener);\n return () => {\n this.eventListeners.delete(listener);\n };\n }\n\n async request<T = unknown>(request: SessionHostRequest): Promise<SessionHostResponse<T>> {\n await this.connect();\n if (!this.socket) throw new Error('Session host socket unavailable');\n\n const requestId = generateUUID();\n const envelope: SessionHostRequestEnvelope = {\n kind: 'request',\n requestId,\n request,\n };\n\n const response = await new Promise<SessionHostResponse>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.requestWaiters.delete(requestId);\n reject(new Error(`Session host request timed out after 30s (${request.type})`));\n }, 30_000);\n this.requestWaiters.set(requestId, {\n resolve: (value) => { clearTimeout(timeout); resolve(value); },\n reject: (error) => { clearTimeout(timeout); reject(error); },\n });\n this.socket?.write(serializeEnvelope(envelope));\n });\n\n return response as SessionHostResponse<T>;\n }\n\n async close(): Promise<void> {\n if (!this.socket) return;\n const socket = this.socket;\n this.socket = null;\n // Deliberate teardown: suppress the 'close' handler's disconnect signal so\n // callers don't see an intentional close reported as a session-host death.\n this.disconnectedSockets.add(socket);\n for (const waiter of this.requestWaiters.values()) {\n waiter.reject(new Error('Session host client closed'));\n }\n this.requestWaiters.clear();\n await new Promise<void>((resolve) => {\n let settled = false;\n const done = () => {\n if (settled) return;\n settled = true;\n resolve();\n };\n socket.once('close', done);\n socket.end();\n socket.destroy();\n setTimeout(done, 50);\n });\n }\n}\n\nexport function createResponseEnvelope(requestId: string, response: SessionHostResponse): SessionHostResponseEnvelope {\n return {\n kind: 'response',\n requestId,\n response,\n };\n}\n\nexport function writeEnvelope(socket: Pick<net.Socket, 'write'>, envelope: SessionHostWireEnvelope): void {\n socket.write(serializeEnvelope(envelope));\n}\n\nexport { createLineParser };\n","import * as crypto from 'crypto';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\n/**\n * Instance identity derivation for the session-host IPC namespace.\n *\n * One machine can host several ADHDev installs (stable `~/.adhdev`, preview\n * `~/.adhdev-preview`, standalone `~/.adhdev-standalone`, or any explicit\n * ADHDEV_CONFIG_DIR). Each install is an *instance*: it owns its config dir\n * and everything mutable under it. The session-host socket/pipe must be\n * namespaced by that identity so two simultaneous instances can never attach\n * to, kill, or rebind each other's session host.\n *\n * This module is the single derivation rule, shared by:\n * - the parent daemons (via @adhdev/daemon-core's InstanceContext),\n * - the session-host-daemon child process (which only sees env vars),\n * - CLI attach/list clients.\n *\n * All three compute the endpoint from the same inputs (appName +\n * ADHDEV_CONFIG_DIR), so they always agree without extra plumbing.\n *\n * Compatibility contract: the DEFAULT instance (config dir canonicalizes to\n * `<home>/.adhdev`) yields an EMPTY ipc key, so its socket/pipe path stays\n * byte-for-byte identical to the pre-instance layout.\n */\n\nexport const DEFAULT_CONFIG_DIR_NAME = '.adhdev';\n\n/**\n * Resolve the instance config dir from the environment: ADHDEV_CONFIG_DIR\n * when set (trimmed), else `<homeDir>/.adhdev`. Mirrors daemon-core's\n * getConfigDir() source rule without the mkdir side effect.\n *\n * NOTE (track identity): the no-env fallback here stays the LEGACY stable\n * default `.adhdev` — session-host-core is a pure leaf package that\n * daemon-core depends on, so it cannot import daemon-core's track-identity\n * (dependency direction) and its separately-built dist never sees the\n * `__ADHDEV_BUILD_CHANNEL__` build stamp. This is safe because every tracked\n * runtime path pins ADHDEV_CONFIG_DIR explicitly: the daemon spawns the\n * session-host child with `env.ADHDEV_CONFIG_DIR = instance().configDir`\n * (managed-host.ts buildEnv), and the installer/service pins it for preview.\n * The track-aware no-env default lives in daemon-core (getConfigDir /\n * resolveInstanceContext), which is the authority for daemon-side paths.\n */\nexport function resolveInstanceConfigDir(\n env: NodeJS.ProcessEnv = process.env,\n homeDir: string = os.homedir(),\n): string {\n const override = typeof env.ADHDEV_CONFIG_DIR === 'string' ? env.ADHDEV_CONFIG_DIR.trim() : '';\n return override || path.join(homeDir, DEFAULT_CONFIG_DIR_NAME);\n}\n\n/**\n * Realpath the longest existing prefix of `resolved` and re-append the\n * non-existing remainder. Unlike a plain realpathSync, this is deterministic\n * whether or not the leaf exists yet (a config dir realpath'd only after its\n * first mkdir must not change the derived ipc key between processes).\n */\nfunction realpathLongestExistingPrefix(resolved: string): string {\n let current = resolved;\n const remainder: string[] = [];\n // Bounded walk: a path can have at most its own segment count of parents.\n for (let depth = 0; depth < 256; depth++) {\n try {\n const real = fs.realpathSync.native(current);\n return remainder.length === 0 ? real : path.join(real, ...remainder);\n } catch {\n const parent = path.dirname(current);\n if (parent === current) return resolved;\n remainder.unshift(path.basename(current));\n current = parent;\n }\n }\n return resolved;\n}\n\n/**\n * Canonical form of an instance path for identity comparison/hashing:\n * absolute, symlink-resolved (longest existing prefix), no trailing\n * separator, case-folded on win32.\n */\nexport function canonicalizeInstancePath(\n dir: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n const resolved = path.resolve(dir);\n const real = realpathLongestExistingPrefix(resolved).replace(/[\\\\/]+$/, '');\n return platform === 'win32' ? real.toLowerCase() : real;\n}\n\n/** True when `configDir` is canonically the default `<homeDir>/.adhdev`. */\nexport function isDefaultInstanceConfigDir(\n configDir: string,\n homeDir: string = os.homedir(),\n platform: NodeJS.Platform = process.platform,\n): boolean {\n return canonicalizeInstancePath(configDir, platform)\n === canonicalizeInstancePath(path.join(homeDir, DEFAULT_CONFIG_DIR_NAME), platform);\n}\n\n/**\n * Derive the session-host IPC namespace key for an instance config dir.\n *\n * '' for the default instance (legacy-compatible endpoint), otherwise a\n * 12-hex-char sha256 prefix of the canonical config dir — stable across\n * processes/machines for the same dir, collision-resistant between dirs,\n * and insensitive to symlink/spelling differences of the same dir.\n */\nexport function resolveSessionHostIpcKey(\n configDir: string,\n homeDir: string = os.homedir(),\n platform: NodeJS.Platform = process.platform,\n): string {\n if (isDefaultInstanceConfigDir(configDir, homeDir, platform)) return '';\n const canonical = canonicalizeInstancePath(configDir, platform);\n return crypto.createHash('sha256').update(canonical, 'utf8').digest('hex').slice(0, 12);\n}\n","/**\n * Shared PTY spawn environment utilities.\n *\n * Centralises npm/pnpm/yarn env variable stripping, terminal colour env\n * injection, and node-pty spawn-helper permission fixing.\n *\n * Used by daemon-core (provider-cli-adapter), session-host-daemon (runtime),\n * and daemon-cloud (session-host).\n */\n\nimport * as os from 'os';\nimport * as path from 'path';\n\n/**\n * Strip package-manager injected environment variables that can interfere\n * with child CLI processes and apply terminal colour defaults.\n */\nexport function sanitizeSpawnEnv(\n baseEnv: NodeJS.ProcessEnv,\n overrides?: Record<string, string>,\n): Record<string, string> {\n const env: Record<string, string> = {};\n const source = { ...baseEnv, ...(overrides || {}) } as NodeJS.ProcessEnv;\n\n for (const [key, value] of Object.entries(source)) {\n if (typeof value !== 'string') continue;\n env[key] = value;\n }\n\n for (const key of Object.keys(env)) {\n if (\n key === 'INIT_CWD'\n || key === 'npm_command'\n || key === 'npm_execpath'\n || key === 'npm_node_execpath'\n || key.startsWith('npm_')\n || key.startsWith('npm_config_')\n || key.startsWith('npm_package_')\n || key.startsWith('npm_lifecycle_')\n || key.startsWith('PNPM_')\n || key.startsWith('YARN_')\n || key.startsWith('BUN_')\n || key.startsWith('VSCODE_')\n || key.startsWith('ELECTRON_')\n ) {\n delete env[key];\n }\n }\n\n // Do not leak parent Codex session controls into child CLIs. Thread identity\n // breaks providerSessionId discovery/reconnect semantics, while the parent's\n // network-disabled sandbox flag prevents coordinator MCP children from\n // connecting back to the local ADHDev daemon.\n delete env.CODEX_THREAD_ID;\n delete env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE;\n delete env.CODEX_SANDBOX_NETWORK_DISABLED;\n delete env.NO_COLOR;\n delete env.COLOR;\n\n // Never forward an inherited terminal geometry. COLUMNS/LINES are advisory\n // overrides that a TUI trusts *in preference to* the real TTY size\n // (measured: with COLUMNS=17 exported, a child on a genuine 80-column PTY\n // reports `tput cols` = 17). The PTY here is sized independently from the\n // adapter's own geometry, so any inherited value is by definition unrelated\n // to the terminal the child was actually given.\n //\n // When the two disagree the child lays out for the phantom width and the\n // VT renders at the real one, so in-place repaints (`\\r`, absolute cursor\n // moves) land on the wrong row: content fragments across the buffer with a\n // blank gap between the stale head and the live tail. Observed on\n // antigravity-cli as a \"Running command\" spinner split into two rows 28\n // blank lines apart, which the spec's from_top/from_bottom sections then\n // sliced into separate body/footer values.\n //\n // Deleting them makes the child measure the TTY via ioctl(TIOCGWINSZ) —\n // the PTY's real size, which is exactly what the VT is sized to. node-pty\n // does not set these itself, so a clean parent env is already the normal\n // case; this only removes a value that leaked in from whatever launched\n // the daemon (interactive shell, tmux, CI runner, nested CLI).\n delete env.COLUMNS;\n delete env.LINES;\n\n // Do not leak a parent Claude Code session identity into spawned claude-cli\n // children. CLAUDE_CODE_CHILD_SESSION makes a spawned claude run as a nested\n // child that does NOT persist its ~/.claude/projects transcript, so the\n // native-source history reader finds no file and the live dashboard renders\n // empty. This bites when the daemon itself was launched from inside a Claude\n // Code session (the markers are inherited and forwarded to every child).\n // Unconditional: originally scoped to win32 (35b462c5) because the bug had\n // only been OBSERVED there at the time — the Windows daemon that surfaced it\n // happened to have been launched from inside a Claude Code session, and\n // macOS/Linux \"looked fine\" only because no one had hit that same launch\n // circumstance yet, not because the platform behaves differently. The\n // marker-inheritance mechanism itself was never platform-specific — nothing\n // about how Claude Code detects a nested child session depends on OS.\n // Confirmed reproduced identically on darwin (live coordinator measurement,\n // this task): a daemon started from inside a Claude Code session inherits\n // and forwards the same markers there too. A daemon-spawned claude should\n // always be a fresh top-level session, on every platform — exactly what the\n // original commit already called out as \"safe to make unconditional later.\"\n delete env.CLAUDECODE;\n delete env.CLAUDE_CODE_CHILD_SESSION;\n delete env.CLAUDE_CODE_ENTRYPOINT;\n delete env.CLAUDE_CODE_SESSION_ID;\n delete env.CLAUDE_CODE_EXECPATH;\n // CLAUDE_CODE_BRIDGE_SESSION_ID is the same kind of parent-identity marker\n // (observed grouped with CLAUDECODE/CLAUDE_CODE_SESSION_ID/\n // CLAUDE_CODE_CHILD_SESSION/CLAUDE_CODE_EXECPATH in the installed `claude`\n // binary's own env-var table, alongside a `buildBridgeReattachEnvFromState`\n // helper) — it identifies the parent's remote bridge/reattach session. Left\n // in place, a spawned child could try to reattach to the PARENT's remote\n // session instead of starting fresh, which is a worse variant of the same\n // \"not actually a fresh top-level session\" bug this block exists to prevent.\n delete env.CLAUDE_CODE_BRIDGE_SESSION_ID;\n\n applyTerminalColorEnv(env);\n return env;\n}\n\n/**\n * Apply preferred terminal colour environment variables.\n * Ensures TERM is set to xterm-256color and enables colour on Windows.\n */\nexport function applyTerminalColorEnv(env: Record<string, string>): void {\n if (env.NO_COLOR) return;\n\n if (!env.TERM || env.TERM === 'xterm-color') {\n env.TERM = 'xterm-256color';\n }\n if (!env.COLORTERM) env.COLORTERM = 'truecolor';\n\n if (process.platform === 'win32') {\n if (!env.FORCE_COLOR) env.FORCE_COLOR = '1';\n if (!env.CLICOLOR) env.CLICOLOR = '1';\n }\n}\n\n/**\n * Ensure node-pty's spawn-helper binary has execute permissions.\n *\n * npm's default umask can strip +x from the prebuilt spawn-helper on macOS/Linux,\n * causing EACCES when node-pty tries to fork. Best-effort fix.\n *\n * @param logFn Optional log callback for reporting the fix.\n */\nexport function ensureNodePtySpawnHelperPermissions(\n logFn?: (msg: string) => void,\n): void {\n if (os.platform() === 'win32') return;\n try {\n const fs = require('fs');\n const ptyDir = path.resolve(path.dirname(require.resolve('node-pty')), '..');\n const platformArch = `${os.platform()}-${os.arch()}`;\n const helper = path.join(ptyDir, 'prebuilds', platformArch, 'spawn-helper');\n if (fs.existsSync(helper)) {\n const stat = fs.statSync(helper);\n if (!(stat.mode & 0o111)) {\n fs.chmodSync(helper, stat.mode | 0o755);\n logFn?.(`Fixed spawn-helper permissions: ${helper}`);\n }\n }\n } catch {\n // best-effort: node-pty still works on most installs without this\n }\n}\n","import type {\n AcquireWritePayload,\n GetHostDiagnosticsPayload,\n PruneDuplicateSessionsPayload,\n ReleaseWritePayload,\n SessionHostDiagnostics,\n SessionHostPruneDuplicatesResult,\n SessionHostRecord,\n SessionHostRequestType,\n} from './types.js';\n\n/**\n * The 11-method session-host control-plane surface shared by the cloud and\n * standalone daemons. Both daemons used to carry a byte-for-byte copy of this\n * dispatch table plus the identical throw strings; this is the single source of\n * truth for that mapping. The wire `type` strings and error text here are the\n * daemon IPC contract and MUST match what the session-host daemon accepts.\n */\nexport interface SessionHostControlPlane {\n getDiagnostics(payload?: GetHostDiagnosticsPayload): Promise<SessionHostDiagnostics>;\n listSessions(): Promise<SessionHostRecord[]>;\n stopSession(sessionId: string): Promise<SessionHostRecord | null>;\n deleteSession(sessionId: string, opts?: { force?: boolean }): Promise<SessionHostRecord | null>;\n resumeSession(sessionId: string): Promise<SessionHostRecord | null>;\n restartSession(sessionId: string): Promise<SessionHostRecord | null>;\n sendSignal(sessionId: string, signal: string): Promise<SessionHostRecord | null>;\n forceDetachClient(sessionId: string, clientId: string): Promise<SessionHostRecord | null>;\n pruneDuplicateSessions(payload?: PruneDuplicateSessionsPayload): Promise<SessionHostPruneDuplicatesResult>;\n acquireWrite(payload: AcquireWritePayload): Promise<SessionHostRecord | null>;\n releaseWrite(payload: ReleaseWritePayload): Promise<SessionHostRecord | null>;\n}\n\n/**\n * Minimal request transport the control-plane factory needs. Each daemon injects\n * its own implementation:\n * - cloud reuses a single persistent SessionHostClient (with reconnect); its\n * `request` calls `ensureConnected()` then the shared client.\n * - standalone opens a fresh client per request and closes it in `finally`.\n *\n * The factory below owns ONLY the type/payload dispatch table; the connection\n * lifecycle and success/error unwrapping stay in each daemon's transport so the\n * observable behavior of both paths is preserved exactly.\n */\nexport interface SessionHostControlTransport {\n request<T>(type: SessionHostRequestType, payload: Record<string, unknown>): Promise<T>;\n}\n\n/**\n * Build the shared 11-method control-plane over an injected request transport.\n * The (type string, payload shape) pairs are verbatim what both daemons emitted\n * previously — do not reword them without matching the session-host daemon's\n * request handlers.\n */\nexport function createSessionHostControlPlane(\n transport: SessionHostControlTransport,\n): SessionHostControlPlane {\n return {\n getDiagnostics(payload: GetHostDiagnosticsPayload = {}): Promise<SessionHostDiagnostics> {\n return transport.request<SessionHostDiagnostics>('get_host_diagnostics', payload as Record<string, unknown>);\n },\n listSessions(): Promise<SessionHostRecord[]> {\n return transport.request<SessionHostRecord[]>('list_sessions', {});\n },\n stopSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('stop_session', { sessionId });\n },\n deleteSession(sessionId: string, opts: { force?: boolean } = {}): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('delete_session', { sessionId, force: opts.force === true });\n },\n resumeSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('resume_session', { sessionId });\n },\n restartSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('restart_session', { sessionId });\n },\n sendSignal(sessionId: string, signal: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('send_signal', { sessionId, signal });\n },\n forceDetachClient(sessionId: string, clientId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('force_detach_client', { sessionId, clientId });\n },\n pruneDuplicateSessions(payload: PruneDuplicateSessionsPayload = {}): Promise<SessionHostPruneDuplicatesResult> {\n return transport.request<SessionHostPruneDuplicatesResult>('prune_duplicate_sessions', payload as Record<string, unknown>);\n },\n acquireWrite(payload: AcquireWritePayload): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('acquire_write', payload as unknown as Record<string, unknown>);\n },\n releaseWrite(payload: ReleaseWritePayload): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('release_write', payload as unknown as Record<string, unknown>);\n },\n };\n}\n"],"mappings":"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","names":["path","generateUUID","platform","failure","resolve","crypto","os","path","platform","os","path","fs"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/termination.ts","../src/buffer.ts","../src/defaults.ts","../src/runtime-labels.ts","../src/registry.ts","../src/ipc.ts","../src/instance-key.ts","../src/spawn-env.ts","../src/control-plane.ts"],"sourcesContent":["export type {\n AcquireWritePayload,\n AttachSessionPayload,\n ClearSessionBufferPayload,\n CreateSessionPayload,\n DetachSessionPayload,\n ForceDetachClientPayload,\n GetHostDiagnosticsPayload,\n GetSnapshotPayload,\n GetTerminalSnapshotPayload,\n PruneDuplicateSessionsPayload,\n ReleaseWritePayload,\n ResumeSessionPayload,\n RestartSessionPayload,\n ResizeSessionPayload,\n SendSignalPayload,\n SendInputPayload,\n UpdateSessionMetaPayload,\n SessionAttachedClient,\n SessionBufferSnapshot,\n SessionClientType,\n SessionHostDiagnostics,\n SessionHostDuplicateSessionGroup,\n SessionHostCategory,\n SessionHostSurfaceKind,\n SessionHostEvent,\n SessionHostEventEnvelope,\n SessionHostLogEntry,\n SessionHostPruneDuplicatesResult,\n SessionHostRecord,\n SessionHostRequest,\n SessionHostRequestEnvelope,\n SessionHostRequestTrace,\n SessionHostRequestType,\n SessionHostResponse,\n SessionHostResponseEnvelope,\n SessionHostRuntimeTransition,\n SessionTerminalSnapshot,\n SessionTerminalState,\n SessionLaunchCommand,\n SessionLifecycle,\n SessionOwnerType,\n SessionTransport,\n SessionTermination,\n SessionHostWireEnvelope,\n SessionWriteOwner,\n StopSessionPayload,\n} from './types.js';\n\nexport { SESSION_HOST_SUPPORTED_REQUEST_TYPES } from './types.js';\nexport { classifyTermination } from './termination.js';\n\nexport { DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, SessionRingBuffer } from './buffer.js';\nexport type { SessionRingBufferOptions } from './buffer.js';\nexport { SessionHostRegistry } from './registry.js';\nexport {\n buildRuntimeDisplayName,\n buildRuntimeKey,\n formatRuntimeOwner,\n getSessionHostRecoveryLabel,\n getSessionHostSurfaceKind,\n getWorkspaceLabel,\n isSessionHostLiveRuntime,\n isSessionHostRecoverySnapshot,\n isTerminalRecord,\n partitionSessionHostDiagnosticsSessions,\n partitionSessionHostRecords,\n resolveAttachableRuntimeRecord,\n resolveRuntimeRecord,\n} from './runtime-labels.js';\nexport type { SessionHostSurfaceRecordLike } from './runtime-labels.js';\nexport {\n SessionHostClient,\n createLineParser,\n createResponseEnvelope,\n getDefaultSessionHostEndpoint,\n writeEnvelope,\n} from './ipc.js';\nexport type {\n SessionHostClientOptions,\n SessionHostEndpoint,\n SessionHostEndpointOptions,\n SessionHostDisconnectInfo,\n SessionHostDisconnectReason,\n} from './ipc.js';\nexport {\n DEFAULT_CONFIG_DIR_NAME,\n canonicalizeInstancePath,\n isDefaultInstanceConfigDir,\n resolveInstanceConfigDir,\n resolveSessionHostIpcKey,\n} from './instance-key.js';\nexport {\n DEFAULT_SESSION_HOST_COLS,\n DEFAULT_SESSION_HOST_ROWS,\n resolveSessionHostCols,\n resolveSessionHostRows,\n} from './defaults.js';\nexport {\n sanitizeSpawnEnv,\n applyTerminalColorEnv,\n ensureNodePtySpawnHelperPermissions,\n} from './spawn-env.js';\nexport { createSessionHostControlPlane } from './control-plane.js';\nexport type { SessionHostControlPlane, SessionHostControlTransport } from './control-plane.js';\n","export type SessionTransport = 'pty';\n\nexport type SessionHostCategory = 'cli' | 'acp' | 'shell';\n\nexport type SessionLifecycle = 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted';\n\nexport type SessionClientType = 'daemon' | 'web' | 'local-terminal';\n\nexport type SessionOwnerType = 'agent' | 'user';\n\nexport interface SessionLaunchCommand {\n command: string;\n args: string[];\n env?: Record<string, string>;\n}\n\nexport interface SessionWriteOwner {\n clientId: string;\n ownerType: SessionOwnerType;\n acquiredAt: number;\n}\n\nexport interface SessionAttachedClient {\n clientId: string;\n type: SessionClientType;\n readOnly: boolean;\n attachedAt: number;\n lastSeenAt: number;\n}\n\nexport interface SessionBufferSnapshot {\n seq: number;\n text: string;\n truncated: boolean;\n cols?: number;\n rows?: number;\n}\n\nexport interface SessionTerminalState {\n cursor: { row: number; col: number };\n altScreen: boolean;\n pasteMode: boolean;\n rawMode: boolean;\n scrollRegion: { top: number; bot: number };\n cols: number;\n rows: number;\n}\n\nexport interface SessionTerminalSnapshot {\n text: string;\n state: SessionTerminalState;\n}\n\nexport interface SessionBufferState {\n scrollbackBytes: number;\n snapshotSeq: number;\n}\n\nexport type SessionHostSurfaceKind = 'live_runtime' | 'recovery_snapshot' | 'inactive_record';\n\n/**\n * Compact, secret-free record of how a session's PTY process terminated.\n *\n * Preserves the nullable/unknown exitCode and signal exactly as observed from\n * the first node-pty `onExit` — an `exitCode` of `null` means \"unknown\" (the\n * process was terminated by a signal, or the exit code was otherwise not\n * reported) and MUST NOT be collapsed to 0. Persisted as a tombstone so\n * termination diagnostics remain inspectable after the live runtime and its\n * transport adapter are torn down.\n */\nexport interface SessionTermination {\n /** Exit code exactly as observed; `null` = unknown / signal-terminated. Never fabricated to 0. */\n exitCode: number | null;\n /** Signal number that terminated the process, if any; `null` when not signalled. */\n signal: number | null;\n /** Classification derived from (exitCode, signal): clean exit vs. failure vs. unknown. */\n reason: 'exit' | 'signal' | 'failed' | 'unknown';\n /** Lifecycle recorded for this termination (`stopped` | `failed`). */\n lifecycle: Extract<SessionLifecycle, 'stopped' | 'failed'>;\n /** Wall-clock time the termination was observed. */\n terminatedAt: number;\n /** OS pid of the terminated process, if it was known. */\n osPid?: number;\n /** Lifecycle the session was in immediately before termination. */\n previousLifecycle?: SessionLifecycle;\n /** Last time output was observed from the session, if known. */\n lastOutputAt?: number;\n /** Whether the termination followed an explicit stop/delete request, and which. */\n requestedStop?: 'stop' | 'delete' | 'restart' | 'prune';\n}\n\nexport interface SessionHostRecord {\n sessionId: string;\n runtimeKey: string;\n displayName: string;\n workspaceLabel: string;\n transport: SessionTransport;\n providerType: string;\n category: SessionHostCategory;\n workspace: string;\n launchCommand: SessionLaunchCommand;\n osPid?: number;\n createdAt: number;\n startedAt?: number;\n lastActivityAt: number;\n lifecycle: SessionLifecycle;\n surfaceKind?: SessionHostSurfaceKind;\n writeOwner: SessionWriteOwner | null;\n attachedClients: SessionAttachedClient[];\n buffer: SessionBufferState;\n meta: Record<string, unknown>;\n /** Termination tombstone, stamped when the PTY process exits. Retained for post-mortem inspection. */\n termination?: SessionTermination;\n}\n\nexport interface CreateSessionPayload {\n sessionId?: string;\n runtimeKey?: string;\n displayName?: string;\n providerType: string;\n category: SessionHostCategory;\n workspace: string;\n launchCommand: SessionLaunchCommand;\n cols?: number;\n rows?: number;\n clientId?: string;\n clientType?: SessionClientType;\n meta?: Record<string, unknown>;\n}\n\nexport interface AttachSessionPayload {\n sessionId: string;\n clientId: string;\n clientType: SessionClientType;\n readOnly?: boolean;\n}\n\nexport interface DetachSessionPayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface SendInputPayload {\n sessionId: string;\n clientId: string;\n data: string;\n}\n\nexport interface ResizeSessionPayload {\n sessionId: string;\n cols: number;\n rows: number;\n}\n\nexport interface StopSessionPayload {\n sessionId: string;\n}\n\nexport interface DeleteSessionPayload {\n sessionId: string;\n /** Stop a live runtime before deleting the persisted record. */\n force?: boolean;\n}\n\nexport interface ResumeSessionPayload {\n sessionId: string;\n}\n\nexport interface AcquireWritePayload {\n sessionId: string;\n clientId: string;\n ownerType: SessionOwnerType;\n force?: boolean;\n}\n\nexport interface ReleaseWritePayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface GetSnapshotPayload {\n sessionId: string;\n sinceSeq?: number;\n}\n\nexport interface GetTerminalSnapshotPayload {\n sessionId: string;\n}\n\nexport interface ClearSessionBufferPayload {\n sessionId: string;\n}\n\nexport interface UpdateSessionMetaPayload {\n sessionId: string;\n meta: Record<string, unknown>;\n replace?: boolean;\n}\n\nexport interface GetHostDiagnosticsPayload {\n includeSessions?: boolean;\n limit?: number;\n}\n\nexport interface ForceDetachClientPayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface SendSignalPayload {\n sessionId: string;\n signal: string;\n}\n\nexport interface RestartSessionPayload {\n sessionId: string;\n}\n\nexport interface PruneDuplicateSessionsPayload {\n providerType?: string;\n workspace?: string;\n dryRun?: boolean;\n}\n\nexport interface SessionHostDuplicateSessionGroup {\n bindingKey: string;\n providerType: string;\n workspace: string;\n providerSessionId: string;\n keptSessionId: string;\n prunedSessionIds: string[];\n}\n\nexport interface SessionHostPruneDuplicatesResult {\n duplicateGroupCount: number;\n keptSessionIds: string[];\n prunedSessionIds: string[];\n groups: SessionHostDuplicateSessionGroup[];\n}\n\nexport interface SessionHostLogEntry {\n timestamp: number;\n level: 'debug' | 'info' | 'warn' | 'error';\n message: string;\n sessionId?: string;\n data?: Record<string, unknown>;\n}\n\nexport interface SessionHostRequestTrace {\n timestamp: number;\n requestId: string;\n type: SessionHostRequest['type'];\n sessionId?: string;\n clientId?: string;\n success: boolean;\n durationMs: number;\n error?: string;\n}\n\nexport interface SessionHostRuntimeTransition {\n timestamp: number;\n sessionId: string;\n action: string;\n lifecycle?: SessionLifecycle;\n detail?: string;\n success?: boolean;\n error?: string;\n}\n\nexport interface SessionHostDiagnostics {\n hostStartedAt: number;\n endpoint: string;\n /**\n * Absolute path of the script this host process is running (its own\n * `__filename`), self-reported.\n *\n * This is the only way a client can learn WHICH install a reachable host came\n * from without process inspection. On Windows the daemon's PowerShell/wmic\n * command-line probe is blocked outright on some boxes (AV/EDR, and `wmic` is\n * removed by default in Win11 24H2+), so identity checks that depend on it\n * fail silently — which let a host from a DELETED install prefix keep serving\n * its socket while every `create_session` died loading node-pty's conpty.node\n * from the missing tree. The host answering with its own path needs no\n * privileges and always works.\n *\n * Optional: an older host predating this field simply omits it, and callers\n * must treat `undefined` as \"unknown\", never as a match.\n */\n hostEntryPath?: string;\n runtimeCount: number;\n supportedRequestTypes?: SessionHostRequestType[];\n sessions?: SessionHostRecord[];\n liveRuntimes?: SessionHostRecord[];\n recoverySnapshots?: SessionHostRecord[];\n inactiveRecords?: SessionHostRecord[];\n recentLogs: SessionHostLogEntry[];\n recentRequests: SessionHostRequestTrace[];\n recentTransitions: SessionHostRuntimeTransition[];\n}\n\nexport type SessionHostRequest =\n | { type: 'create_session'; payload: CreateSessionPayload }\n | { type: 'attach_session'; payload: AttachSessionPayload }\n | { type: 'detach_session'; payload: DetachSessionPayload }\n | { type: 'send_input'; payload: SendInputPayload }\n | { type: 'resize_session'; payload: ResizeSessionPayload }\n | { type: 'stop_session'; payload: StopSessionPayload }\n | { type: 'delete_session'; payload: DeleteSessionPayload }\n | { type: 'resume_session'; payload: ResumeSessionPayload }\n | { type: 'acquire_write'; payload: AcquireWritePayload }\n | { type: 'release_write'; payload: ReleaseWritePayload }\n | { type: 'get_snapshot'; payload: GetSnapshotPayload }\n | { type: 'get_terminal_snapshot'; payload: GetTerminalSnapshotPayload }\n | { type: 'clear_session_buffer'; payload: ClearSessionBufferPayload }\n | { type: 'update_session_meta'; payload: UpdateSessionMetaPayload }\n | { type: 'get_host_diagnostics'; payload?: GetHostDiagnosticsPayload }\n | { type: 'force_detach_client'; payload: ForceDetachClientPayload }\n | { type: 'send_signal'; payload: SendSignalPayload }\n | { type: 'restart_session'; payload: RestartSessionPayload }\n | { type: 'prune_duplicate_sessions'; payload?: PruneDuplicateSessionsPayload }\n | { type: 'list_sessions'; payload?: {} };\n\nexport type SessionHostRequestType = SessionHostRequest['type'];\n\nexport const SESSION_HOST_SUPPORTED_REQUEST_TYPES: readonly SessionHostRequestType[] = [\n 'create_session',\n 'attach_session',\n 'detach_session',\n 'send_input',\n 'resize_session',\n 'stop_session',\n 'delete_session',\n 'resume_session',\n 'acquire_write',\n 'release_write',\n 'get_snapshot',\n 'get_terminal_snapshot',\n 'clear_session_buffer',\n 'update_session_meta',\n 'get_host_diagnostics',\n 'force_detach_client',\n 'send_signal',\n 'restart_session',\n 'prune_duplicate_sessions',\n 'list_sessions',\n];\n\nexport interface SessionHostResponse<T = unknown> {\n success: boolean;\n result?: T;\n error?: string;\n}\n\nexport type SessionHostEvent =\n | { type: 'session_created'; sessionId: string; record: SessionHostRecord }\n | { type: 'session_started'; sessionId: string; pid?: number }\n | { type: 'session_resumed'; sessionId: string; pid?: number }\n | { type: 'session_output'; sessionId: string; seq: number; data: string }\n | { type: 'session_cleared'; sessionId: string }\n | { type: 'session_exit'; sessionId: string; exitCode: number | null; signal?: number | null; termination?: SessionTermination }\n | { type: 'session_stopped'; sessionId: string }\n | { type: 'session_deleted'; sessionId: string }\n | { type: 'session_resized'; sessionId: string; cols: number; rows: number }\n | { type: 'write_owner_changed'; sessionId: string; owner: SessionWriteOwner | null }\n | { type: 'client_attached'; sessionId: string; client: SessionAttachedClient }\n | { type: 'client_detached'; sessionId: string; clientId: string }\n | { type: 'host_log'; entry: SessionHostLogEntry }\n | { type: 'request_trace'; trace: SessionHostRequestTrace }\n | { type: 'runtime_transition'; transition: SessionHostRuntimeTransition };\n\nexport interface SessionHostRequestEnvelope {\n kind: 'request';\n requestId: string;\n request: SessionHostRequest;\n}\n\nexport interface SessionHostResponseEnvelope {\n kind: 'response';\n requestId: string;\n response: SessionHostResponse;\n}\n\nexport interface SessionHostEventEnvelope {\n kind: 'event';\n event: SessionHostEvent;\n}\n\nexport type SessionHostWireEnvelope =\n | SessionHostRequestEnvelope\n | SessionHostResponseEnvelope\n | SessionHostEventEnvelope;\n","import type { SessionLifecycle, SessionTermination } from './types.js';\n\nexport interface ClassifyTerminationInput {\n exitCode: number | null;\n signal?: number | null;\n osPid?: number;\n previousLifecycle?: SessionLifecycle;\n lastOutputAt?: number;\n requestedStop?: SessionTermination['requestedStop'];\n terminatedAt: number;\n}\n\n/**\n * Classify a PTY termination from its raw (exitCode, signal) pair.\n *\n * Invariants:\n * - A `null` exitCode is UNKNOWN, never a clean exit — it MUST NOT collapse to\n * exit 0. This is the regression guard for the old `exitCode ?? 0` behavior.\n * - A non-null, non-zero exitCode or any signal is a failure.\n * - Only an explicit exit code of 0 (with no signal) is a clean stop.\n *\n * The returned lifecycle is `stopped` only for a clean exit 0; every other\n * case (nonzero, signalled, or unknown) is `failed` so that an unknown\n * termination stays distinguishable from a successful one.\n */\nexport function classifyTermination(input: ClassifyTerminationInput): SessionTermination {\n const { exitCode, terminatedAt } = input;\n const signal = input.signal ?? null;\n\n let reason: SessionTermination['reason'];\n let lifecycle: SessionTermination['lifecycle'];\n\n if (signal !== null && signal !== 0) {\n reason = 'signal';\n lifecycle = 'failed';\n } else if (exitCode === null) {\n reason = 'unknown';\n lifecycle = 'failed';\n } else if (exitCode === 0) {\n reason = 'exit';\n lifecycle = 'stopped';\n } else {\n reason = 'failed';\n lifecycle = 'failed';\n }\n\n const termination: SessionTermination = {\n exitCode,\n signal,\n reason,\n lifecycle,\n terminatedAt,\n };\n if (typeof input.osPid === 'number') termination.osPid = input.osPid;\n if (input.previousLifecycle) termination.previousLifecycle = input.previousLifecycle;\n if (typeof input.lastOutputAt === 'number') termination.lastOutputAt = input.lastOutputAt;\n if (input.requestedStop) termination.requestedStop = input.requestedStop;\n return termination;\n}\n","import type { SessionBufferSnapshot } from './types.js';\n\nexport interface SessionRingBufferOptions {\n maxBytes?: number;\n}\n\n// Manual \"Load older terminal output\" replays this raw ring buffer into the\n// browser terminal. 512KiB was too small for long Claude/Codex conversations:\n// by the time the user scrolled to the top and clicked the loader, the output\n// they were trying to recover was often already trimmed.\nexport const DEFAULT_SESSION_RING_BUFFER_MAX_BYTES = 4 * 1024 * 1024;\n\nexport class SessionRingBuffer {\n private maxBytes: number;\n private chunks: { seq: number; data: string; bytes: number }[] = [];\n private nextSeq = 1;\n private totalBytes = 0;\n\n constructor(options: SessionRingBufferOptions = {}) {\n this.maxBytes = options.maxBytes ?? DEFAULT_SESSION_RING_BUFFER_MAX_BYTES;\n }\n\n append(data: string): number {\n const normalized = typeof data === 'string' ? data : String(data ?? '');\n const bytes = Buffer.byteLength(normalized, 'utf8');\n const seq = this.nextSeq++;\n\n this.chunks.push({ seq, data: normalized, bytes });\n this.totalBytes += bytes;\n this.trim();\n return seq;\n }\n\n snapshot(sinceSeq?: number): SessionBufferSnapshot {\n const relevant = typeof sinceSeq === 'number'\n ? this.chunks.filter(chunk => chunk.seq > sinceSeq)\n : this.chunks;\n\n const text = relevant.map(chunk => chunk.data).join('');\n const truncated = !!this.chunks[0] && typeof sinceSeq === 'number' && sinceSeq < this.chunks[0].seq - 1;\n\n return {\n seq: this.nextSeq - 1,\n text,\n truncated,\n };\n }\n\n getState(): { scrollbackBytes: number; snapshotSeq: number } {\n return {\n scrollbackBytes: this.totalBytes,\n snapshotSeq: this.nextSeq - 1,\n };\n }\n\n clear(): void {\n this.chunks = [];\n this.totalBytes = 0;\n this.nextSeq = 1;\n }\n\n restore(snapshot: { seq: number; text: string }): void {\n this.clear();\n const text = String(snapshot.text || '');\n if (!text) {\n this.nextSeq = Math.max(1, Number(snapshot.seq || 0) + 1);\n return;\n }\n const bytes = Buffer.byteLength(text, 'utf8');\n const seq = Math.max(1, Number(snapshot.seq || 1));\n this.chunks = [{ seq, data: text, bytes }];\n this.totalBytes = bytes;\n this.nextSeq = seq + 1;\n this.trim();\n }\n\n private trim(): void {\n while (this.totalBytes > this.maxBytes && this.chunks.length > 1) {\n const removed = this.chunks.shift();\n if (!removed) break;\n this.totalBytes -= removed.bytes;\n }\n }\n}\n","export const DEFAULT_SESSION_HOST_COLS = 80;\nexport const DEFAULT_SESSION_HOST_ROWS = 32;\n\nfunction normalizeSessionHostDimension(value: number | undefined, fallback: number): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;\n const rounded = Math.floor(value);\n return rounded > 0 ? rounded : fallback;\n}\n\nexport function resolveSessionHostCols(value: number | undefined): number {\n return normalizeSessionHostDimension(value, DEFAULT_SESSION_HOST_COLS);\n}\n\nexport function resolveSessionHostRows(value: number | undefined): number {\n return normalizeSessionHostDimension(value, DEFAULT_SESSION_HOST_ROWS);\n}\n","import * as path from 'path';\nimport type { CreateSessionPayload, SessionHostRecord, SessionHostSurfaceKind } from './types.js';\n\nfunction normalizeSlug(input: string): string {\n return input\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48);\n}\n\nfunction normalizeValue(input: string): string {\n return input.trim().toLowerCase();\n}\n\nexport function getWorkspaceLabel(workspace: string): string {\n const trimmed = workspace.trim();\n if (!trimmed) return 'workspace';\n const normalized = trimmed.replace(/[\\\\/]+$/, '');\n const base = path.basename(normalized);\n return base || normalized;\n}\n\nexport function buildRuntimeDisplayName(payload: Pick<CreateSessionPayload, 'displayName' | 'providerType' | 'workspace'>): string {\n const explicit = payload.displayName?.trim();\n if (explicit) return explicit;\n const workspaceLabel = getWorkspaceLabel(payload.workspace);\n const providerLabel = payload.providerType.trim() || 'runtime';\n return `${providerLabel} @ ${workspaceLabel}`;\n}\n\nexport function buildRuntimeKey(\n payload: Pick<CreateSessionPayload, 'runtimeKey' | 'displayName' | 'providerType' | 'workspace'>,\n existingKeys: Iterable<string>,\n): string {\n const requested = payload.runtimeKey?.trim();\n const existing = new Set(Array.from(existingKeys, (key) => key.toLowerCase()));\n const displayName = buildRuntimeDisplayName(payload);\n const baseKey = normalizeSlug(requested || displayName || getWorkspaceLabel(payload.workspace) || payload.providerType || 'runtime') || 'runtime';\n if (!existing.has(baseKey)) return baseKey;\n\n let suffix = 2;\n let candidate = `${baseKey}-${suffix}`;\n while (existing.has(candidate)) {\n suffix += 1;\n candidate = `${baseKey}-${suffix}`;\n }\n return candidate;\n}\n\nexport interface SessionHostSurfaceRecordLike {\n lifecycle?: string | null;\n surfaceKind?: SessionHostSurfaceKind | null;\n meta?: Record<string, unknown> | null;\n termination?: unknown;\n}\n\nconst LIVE_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'interrupted']);\nconst TERMINAL_LIFECYCLES = new Set(['stopped', 'failed']);\n\n/**\n * True when a record has reached a terminal state: either its lifecycle is\n * terminal, or it carries a termination stamp (which the exit handler writes\n * alongside the lifecycle, and which can outlive a stale 'stopping' value).\n *\n * Used both to keep terminated records from being persisted as live files and\n * to keep them from surfacing as attach targets.\n */\nexport function isTerminalRecord(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.termination) return true;\n return TERMINAL_LIFECYCLES.has(String(record.lifecycle || '').trim());\n}\n\nexport function isSessionHostLiveRuntime(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.surfaceKind === 'live_runtime') return true;\n if (record.surfaceKind === 'recovery_snapshot' || record.surfaceKind === 'inactive_record') return false;\n // A record stamped with a termination is terminal even if its lifecycle is\n // still 'stopping' — e.g. a stale file recreated by a late persist that\n // raced the exit/cleanup path. Without this, such records surface as a\n // live runtime (attach target) for a PID that already exited.\n if (record.termination) return false;\n const lifecycle = String(record.lifecycle || '').trim();\n return LIVE_LIFECYCLES.has(lifecycle);\n}\n\nexport function getSessionHostRecoveryLabel(meta: Record<string, unknown> | null | undefined): string | null {\n const recoveryState = typeof meta?.runtimeRecoveryState === 'string'\n ? String(meta.runtimeRecoveryState).trim()\n : '';\n if (!recoveryState) return null;\n if (recoveryState === 'auto_resumed') return 'restored after restart';\n if (recoveryState === 'resume_failed') return 'restore failed';\n if (recoveryState === 'host_restart_interrupted') return 'host restart interrupted';\n if (recoveryState === 'orphan_snapshot') return 'snapshot recovered';\n return recoveryState.replace(/_/g, ' ');\n}\n\nexport function isSessionHostRecoverySnapshot(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.surfaceKind === 'recovery_snapshot') return true;\n if (record.surfaceKind === 'live_runtime' || record.surfaceKind === 'inactive_record') return false;\n if (isSessionHostLiveRuntime(record)) return false;\n\n const lifecycle = String(record.lifecycle || '').trim();\n if (lifecycle && lifecycle !== 'stopped' && lifecycle !== 'failed') {\n return false;\n }\n\n const meta = record.meta || undefined;\n if (meta?.restoredFromStorage === true) return true;\n return getSessionHostRecoveryLabel(meta) !== null;\n}\n\nexport function getSessionHostSurfaceKind(record: SessionHostSurfaceRecordLike | null | undefined): SessionHostSurfaceKind {\n if (record?.surfaceKind === 'live_runtime' || record?.surfaceKind === 'recovery_snapshot' || record?.surfaceKind === 'inactive_record') {\n return record.surfaceKind;\n }\n if (isSessionHostLiveRuntime(record)) return 'live_runtime';\n if (isSessionHostRecoverySnapshot(record)) return 'recovery_snapshot';\n return 'inactive_record';\n}\n\nexport function partitionSessionHostRecords<T extends SessionHostSurfaceRecordLike>(records: T[]): {\n liveRuntimes: T[];\n recoverySnapshots: T[];\n inactiveRecords: T[];\n} {\n const liveRuntimes: T[] = [];\n const recoverySnapshots: T[] = [];\n const inactiveRecords: T[] = [];\n\n for (const record of records) {\n const kind = getSessionHostSurfaceKind(record);\n if (kind === 'live_runtime') {\n liveRuntimes.push(record);\n } else if (kind === 'recovery_snapshot') {\n recoverySnapshots.push(record);\n } else {\n inactiveRecords.push(record);\n }\n }\n\n return {\n liveRuntimes,\n recoverySnapshots,\n inactiveRecords,\n };\n}\n\nexport function partitionSessionHostDiagnosticsSessions(\n records: SessionHostRecord[] | null | undefined,\n): ReturnType<typeof partitionSessionHostRecords<SessionHostRecord>> {\n return partitionSessionHostRecords(records || []);\n}\n\nexport function resolveAttachableRuntimeRecord(records: SessionHostRecord[], identifier: string): SessionHostRecord {\n const record = resolveRuntimeRecord(records, identifier);\n const surfaceKind = getSessionHostSurfaceKind(record);\n if (surfaceKind === 'live_runtime') {\n return record;\n }\n if (surfaceKind === 'recovery_snapshot') {\n throw new Error(`Runtime ${record.runtimeKey} is a recovery snapshot, not a live attach target. Resume or recover it first.`);\n }\n throw new Error(`Runtime ${record.runtimeKey} is ${record.lifecycle}, not a live attach target.`);\n}\n\nfunction uniqueMatch(records: SessionHostRecord[], predicate: (record: SessionHostRecord) => boolean): SessionHostRecord | null {\n const matches = records.filter(predicate);\n if (matches.length === 1) return matches[0] || null;\n if (matches.length === 0) return null;\n const labels = matches.map((record) => `${record.runtimeKey} (${record.sessionId})`).join(', ');\n throw new Error(`Ambiguous runtime target. Matches: ${labels}`);\n}\n\nexport function resolveRuntimeRecord(records: SessionHostRecord[], identifier: string): SessionHostRecord {\n const target = identifier.trim();\n if (!target) {\n throw new Error('Runtime target is required');\n }\n\n const exact = uniqueMatch(records, (record) =>\n record.sessionId === target ||\n normalizeValue(record.runtimeKey) === normalizeValue(target) ||\n normalizeValue(record.displayName) === normalizeValue(target),\n );\n if (exact) return exact;\n\n const prefix = uniqueMatch(records, (record) =>\n record.sessionId.startsWith(target) ||\n normalizeValue(record.runtimeKey).startsWith(normalizeValue(target)),\n );\n if (prefix) return prefix;\n\n throw new Error(`Unknown runtime target: ${target}`);\n}\n\nexport function formatRuntimeOwner(record: Pick<SessionHostRecord, 'writeOwner'>): string {\n if (!record.writeOwner) return 'none';\n return `${record.writeOwner.ownerType}:${record.writeOwner.clientId}`;\n}\n","function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\nimport type {\n AcquireWritePayload,\n AttachSessionPayload,\n CreateSessionPayload,\n DetachSessionPayload,\n ReleaseWritePayload,\n SessionAttachedClient,\n SessionHostRecord,\n SessionTermination,\n} from './types.js';\nimport { SessionRingBuffer } from './buffer.js';\nimport { resolveSessionHostCols, resolveSessionHostRows } from './defaults.js';\nimport { buildRuntimeDisplayName, buildRuntimeKey, getWorkspaceLabel } from './runtime-labels.js';\n\ninterface SessionRuntimeState {\n record: SessionHostRecord;\n buffer: SessionRingBuffer;\n}\n\nexport class SessionHostRegistry {\n private sessions = new Map<string, SessionRuntimeState>();\n\n createSession(payload: CreateSessionPayload): SessionHostRecord {\n const sessionId = payload.sessionId || generateUUID();\n if (this.sessions.has(sessionId)) {\n throw new Error(`Session already exists: ${sessionId}`);\n }\n const now = Date.now();\n const initialClient = payload.clientId\n ? [{\n clientId: payload.clientId,\n type: payload.clientType || 'daemon',\n readOnly: false,\n attachedAt: now,\n lastSeenAt: now,\n } satisfies SessionAttachedClient]\n : [];\n\n const record: SessionHostRecord = {\n sessionId,\n runtimeKey: buildRuntimeKey(\n payload,\n Array.from(this.sessions.values(), (state) => state.record.runtimeKey),\n ),\n displayName: buildRuntimeDisplayName(payload),\n workspaceLabel: getWorkspaceLabel(payload.workspace),\n transport: 'pty',\n providerType: payload.providerType,\n category: payload.category,\n workspace: payload.workspace,\n launchCommand: payload.launchCommand,\n createdAt: now,\n lastActivityAt: now,\n lifecycle: 'starting',\n writeOwner: null,\n attachedClients: initialClient,\n buffer: {\n scrollbackBytes: 0,\n snapshotSeq: 0,\n },\n meta: payload.meta || {},\n };\n\n record.meta = {\n sessionHostCols: resolveSessionHostCols(payload.cols),\n sessionHostRows: resolveSessionHostRows(payload.rows),\n ...record.meta,\n };\n\n this.sessions.set(sessionId, {\n record,\n buffer: new SessionRingBuffer(),\n });\n\n return this.cloneRecord(record);\n }\n\n restoreSession(record: SessionHostRecord, snapshot?: { seq: number; text: string } | null): SessionHostRecord {\n const cloned = this.cloneRecord(record);\n this.sessions.set(cloned.sessionId, {\n record: cloned,\n buffer: (() => {\n const buffer = new SessionRingBuffer();\n if (snapshot) buffer.restore(snapshot);\n return buffer;\n })(),\n });\n return this.cloneRecord(cloned);\n }\n\n listSessions(): SessionHostRecord[] {\n return Array.from(this.sessions.values())\n .map(state => this.cloneRecord(state.record))\n .sort((a, b) => b.lastActivityAt - a.lastActivityAt);\n }\n\n getSession(sessionId: string): SessionHostRecord | null {\n const state = this.sessions.get(sessionId);\n return state ? this.cloneRecord(state.record) : null;\n }\n\n attachClient(payload: AttachSessionPayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n const now = Date.now();\n let removedDaemonOwner = false;\n\n if (payload.clientType === 'daemon') {\n const staleDaemonClientIds = state.record.attachedClients\n .filter(client => client.type === 'daemon' && client.clientId !== payload.clientId)\n .map(client => client.clientId);\n if (staleDaemonClientIds.length > 0) {\n state.record.attachedClients = state.record.attachedClients.filter(\n client => !(client.type === 'daemon' && client.clientId !== payload.clientId),\n );\n if (state.record.writeOwner && staleDaemonClientIds.includes(state.record.writeOwner.clientId)) {\n removedDaemonOwner = true;\n }\n }\n }\n\n const existing = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n\n if (existing) {\n existing.type = payload.clientType;\n existing.readOnly = !!payload.readOnly;\n existing.lastSeenAt = now;\n } else {\n state.record.attachedClients.push({\n clientId: payload.clientId,\n type: payload.clientType,\n readOnly: !!payload.readOnly,\n attachedAt: now,\n lastSeenAt: now,\n });\n }\n\n if (removedDaemonOwner) {\n state.record.writeOwner = null;\n }\n\n state.record.lastActivityAt = now;\n return this.cloneRecord(state.record);\n }\n\n detachClient(payload: DetachSessionPayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n state.record.attachedClients = state.record.attachedClients.filter(client => client.clientId !== payload.clientId);\n if (state.record.writeOwner?.clientId === payload.clientId) {\n state.record.writeOwner = null;\n }\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n acquireWrite(payload: AcquireWritePayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n if (state.record.writeOwner && state.record.writeOwner.clientId !== payload.clientId && !payload.force) {\n throw new Error(`Write owned by ${state.record.writeOwner.clientId}`);\n }\n const attachedClient = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n if (attachedClient) {\n attachedClient.readOnly = false;\n attachedClient.lastSeenAt = Date.now();\n }\n state.record.writeOwner = {\n clientId: payload.clientId,\n ownerType: payload.ownerType,\n acquiredAt: Date.now(),\n };\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n releaseWrite(payload: ReleaseWritePayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n const attachedClient = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n if (attachedClient) {\n attachedClient.readOnly = false;\n attachedClient.lastSeenAt = Date.now();\n }\n if (state.record.writeOwner?.clientId === payload.clientId) {\n state.record.writeOwner = null;\n }\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n appendOutput(sessionId: string, data: string): { record: SessionHostRecord; seq: number } {\n const state = this.requireSession(sessionId);\n const seq = state.buffer.append(data);\n state.record.buffer = state.buffer.getState();\n state.record.lastActivityAt = Date.now();\n return { record: this.cloneRecord(state.record), seq };\n }\n\n getSnapshot(sessionId: string, sinceSeq?: number) {\n const state = this.requireSession(sessionId);\n state.record.buffer = state.buffer.getState();\n return state.buffer.snapshot(sinceSeq);\n }\n\n clearBuffer(sessionId: string): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.buffer.clear();\n state.record.buffer = state.buffer.getState();\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n updateSessionMeta(sessionId: string, meta: Record<string, unknown>, replace = false): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.meta = replace\n ? { ...meta }\n : {\n ...(state.record.meta || {}),\n ...meta,\n };\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n markStarted(sessionId: string, pid?: number): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = 'running';\n state.record.startedAt = state.record.startedAt || Date.now();\n if (typeof pid === 'number') state.record.osPid = pid;\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n markStopped(sessionId: string, lifecycle: 'stopped' | 'failed' = 'stopped', termination?: SessionTermination): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = lifecycle;\n state.record.lastActivityAt = Date.now();\n if (termination) state.record.termination = termination;\n return this.cloneRecord(state.record);\n }\n\n setLifecycle(sessionId: string, lifecycle: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted'): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = lifecycle;\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n deleteSession(sessionId: string): boolean {\n return this.sessions.delete(sessionId);\n }\n\n private requireSession(sessionId: string): SessionRuntimeState {\n const state = this.sessions.get(sessionId);\n if (!state) throw new Error(`Unknown session: ${sessionId}`);\n return state;\n }\n\n private cloneRecord(record: SessionHostRecord): SessionHostRecord {\n return {\n ...record,\n launchCommand: {\n ...record.launchCommand,\n args: [...record.launchCommand.args],\n env: record.launchCommand.env ? { ...record.launchCommand.env } : undefined,\n },\n writeOwner: record.writeOwner ? { ...record.writeOwner } : null,\n attachedClients: record.attachedClients.map(client => ({ ...client })),\n buffer: { ...record.buffer },\n meta: { ...record.meta },\n };\n }\n}\n","import * as os from 'os';\nimport * as path from 'path';\nimport * as net from 'net';\nimport { StringDecoder } from 'string_decoder';\nimport type {\n SessionHostEvent,\n SessionHostRequest,\n SessionHostRequestEnvelope,\n SessionHostResponse,\n SessionHostResponseEnvelope,\n SessionHostWireEnvelope,\n} from './types.js';\n\nfunction generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\nexport interface SessionHostEndpoint {\n kind: 'unix' | 'pipe';\n path: string;\n}\n\nexport interface SessionHostEndpointOptions {\n /**\n * Instance IPC namespace key (see instance-key.ts). '' / omitted yields the\n * legacy default-instance endpoint; a non-empty key suffixes the endpoint so\n * each config-dir instance gets its own socket/pipe.\n */\n ipcKey?: string;\n /** Test seam: override the platform the endpoint shape is derived for. */\n platform?: NodeJS.Platform;\n}\n\nexport function getDefaultSessionHostEndpoint(\n appName = 'adhdev',\n options: SessionHostEndpointOptions = {},\n): SessionHostEndpoint {\n const rawKey = typeof options.ipcKey === 'string' ? options.ipcKey.trim() : '';\n // Fail closed on a malformed key: silently dropping it would collapse the\n // endpoint back onto the default instance's namespace — a cross-instance leak.\n if (rawKey && !/^[0-9a-f]{12}$/.test(rawKey)) {\n throw new Error(`Invalid session-host ipcKey \"${rawKey}\" — expected '' or a 12-hex-char instance key`);\n }\n const suffix = rawKey ? `-${rawKey}` : '';\n const platform = options.platform || process.platform;\n if (platform === 'win32') {\n return {\n kind: 'pipe',\n path: `\\\\\\\\.\\\\pipe\\\\${appName}-session-host${suffix}`,\n };\n }\n\n return {\n kind: 'unix',\n path: path.join(os.tmpdir(), `${appName}-session-host${suffix}.sock`),\n };\n}\n\nfunction serializeEnvelope(envelope: SessionHostWireEnvelope): string {\n return `${JSON.stringify(envelope)}\\n`;\n}\n\n/**\n * UTF8-CHUNK-BOUNDARY: decode the socket stream, never the individual chunk.\n *\n * This used to call `chunk.toString()` on every Buffer independently. A socket\n * hands us arbitrary byte slices — a multi-byte UTF-8 sequence straddling two\n * chunks gets decoded as two truncated sequences, and each half becomes U+FFFD.\n * The damage is silent by construction: the replacement character is valid JSON\n * string content, so the envelope still parses and only the *value* is wrong.\n * A pasted 11,994-char Korean prompt lost exactly one character that way — the\n * `도` beginning at wire offset 8190, split across the 8192-byte chunk boundary,\n * arrived as `�착`.\n *\n * StringDecoder holds an incomplete trailing sequence back until the bytes that\n * finish it arrive, which is the only correct way to turn a byte stream into\n * text. String chunks bypass it: they are already decoded, and feeding them\n * through `Buffer.from` would re-encode text the caller never asked us to touch.\n */\nfunction createLineParser(onEnvelope: (envelope: SessionHostWireEnvelope) => void) {\n let buffer = '';\n const decoder = new StringDecoder('utf8');\n const parser = (chunk: Buffer | string) => {\n buffer += typeof chunk === 'string' ? chunk : decoder.write(chunk);\n let newlineIndex = buffer.indexOf('\\n');\n while (newlineIndex >= 0) {\n const rawLine = buffer.slice(0, newlineIndex).trim();\n buffer = buffer.slice(newlineIndex + 1);\n if (rawLine) {\n onEnvelope(JSON.parse(rawLine) as SessionHostWireEnvelope);\n }\n newlineIndex = buffer.indexOf('\\n');\n }\n };\n /**\n * Flush at EOF, returning whatever text never formed a complete line.\n *\n * `decoder.end()` releases any bytes still held back as an incomplete UTF-8\n * sequence; without it those bytes vanish with the decoder. What is left in\n * the buffer is by definition a partial line — envelopes are newline-framed,\n * so an unterminated tail is a truncated transmission, not a message. It is\n * therefore RETURNED for the caller to log rather than parsed: calling\n * JSON.parse on a half-written envelope inside a socket EOF handler would\n * raise an uncaught exception in the daemon, turning a peer that died\n * mid-write into a crash of the process observing it.\n */\n parser.end = (): string => {\n const remainder = buffer + decoder.end();\n buffer = '';\n return remainder;\n };\n return parser;\n}\n\nexport interface SessionHostClientOptions {\n endpoint?: SessionHostEndpoint;\n appName?: string;\n}\n\n/**\n * Why an established session-host connection went away.\n * - `error` — socket emitted 'error' (includes ECONNRESET on abrupt host death)\n * - `ended` — peer sent FIN ('end'): the host closed the connection cleanly\n * - `closed` — socket fully closed ('close') without a preceding error/end\n */\nexport type SessionHostDisconnectReason = 'error' | 'ended' | 'closed';\n\n/**\n * Sockets that reached 'connect' at least once. Used to distinguish a genuine\n * disconnect (host died under us) from a connect attempt that never landed\n * (host not up yet) — only the former is a session-host death signal.\n */\nconst establishedSockets = new WeakSet<net.Socket>();\n\nexport interface SessionHostDisconnectInfo {\n reason: SessionHostDisconnectReason;\n endpointPath: string;\n /** In-flight requests abandoned by this disconnect (they were rejected). */\n pendingRequests: number;\n error?: Error;\n /**\n * Bytes discarded from a newline-unterminated frame at EOF (0 when the peer\n * hung up on a clean frame boundary, which is the normal case).\n *\n * Non-zero means the peer was cut off MID-WRITE — diagnostic context that\n * distinguishes \"the host exited\" from \"the host died with a frame in\n * flight\". Before the parser's `end()` was reachable this was unknowable:\n * the bytes were dropped with the decoder and no counter existed.\n */\n droppedTailBytes: number;\n}\n\nexport class SessionHostClient {\n readonly endpoint: SessionHostEndpoint;\n\n private socket: net.Socket | null = null;\n private requestWaiters = new Map<string, { resolve: (value: SessionHostResponse) => void; reject: (error: Error) => void }>();\n private eventListeners = new Set<(event: SessionHostEvent) => void>();\n private disconnectListeners = new Set<(info: SessionHostDisconnectInfo) => void>();\n /** Guards against emitting both an 'error'-path and a 'close'-path disconnect for one socket. */\n private disconnectedSockets = new WeakSet<net.Socket>();\n\n constructor(options: SessionHostClientOptions = {}) {\n this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || 'adhdev');\n }\n\n /**\n * Fires when an established connection to the session host goes away.\n *\n * Before this existed the client registered only 'data' and 'error' handlers,\n * so a *clean* FIN — the host process exiting normally, or dying to an\n * uncaught exception that took the socket down without an error event on our\n * end — ran nothing at all: `this.socket` stayed non-null pointing at a dead\n * socket, in-flight waiters hung to their 30s timeout, and no caller was told.\n * That silence is what let a session-host death present downstream as an\n * unrelated application-level fault (a missing completion marker) instead of\n * \"the host is gone\".\n */\n onDisconnect(listener: (info: SessionHostDisconnectInfo) => void): () => void {\n this.disconnectListeners.add(listener);\n return () => {\n this.disconnectListeners.delete(listener);\n };\n }\n\n private handleDisconnect(\n socket: net.Socket,\n reason: SessionHostDisconnectReason,\n error?: Error,\n droppedTailBytes = 0,\n ): void {\n if (this.disconnectedSockets.has(socket)) return;\n this.disconnectedSockets.add(socket);\n\n // A connect that never succeeded is a *failed connect*, not a disconnect —\n // reporting it as a host death would make every poll against a not-yet-started\n // host look like a crash. connect() already surfaces that failure to its caller\n // via the awaited 'error'. Still clear socket state below? No: connect()'s own\n // error path handles a never-established socket, and `this.socket` is\n // overwritten on the next connect() attempt regardless.\n const wasEstablished = establishedSockets.has(socket);\n if (!wasEstablished) {\n if (this.socket === socket) this.socket = null;\n try { socket.destroy(); } catch { /* noop */ }\n // Reject waiters anyway — a request() that raced a dying socket must not hang.\n const failure = error || new Error(`Session host connect failed (${this.endpoint.path})`);\n for (const waiter of this.requestWaiters.values()) waiter.reject(failure);\n this.requestWaiters.clear();\n return;\n }\n\n const pendingRequests = this.requestWaiters.size;\n const failure = error || new Error(`Session host connection ${reason} (${this.endpoint.path})`);\n for (const waiter of this.requestWaiters.values()) {\n waiter.reject(failure);\n }\n this.requestWaiters.clear();\n\n // Clear the dead socket reference so the next connect() creates a fresh\n // connection instead of short-circuiting on the `!this.socket.destroyed`\n // guard at the top of connect().\n if (this.socket === socket) {\n this.socket = null;\n }\n try { socket.destroy(); } catch { /* noop */ }\n\n const info: SessionHostDisconnectInfo = {\n reason,\n endpointPath: this.endpoint.path,\n pendingRequests,\n error,\n droppedTailBytes,\n };\n for (const listener of this.disconnectListeners) {\n // A throwing observer must not suppress the remaining ones, and must not\n // propagate out of a socket event handler (that would be an uncaught\n // exception in the daemon).\n try { listener(info); } catch { /* noop */ }\n }\n }\n\n async connect(): Promise<void> {\n if (this.socket && !this.socket.destroyed) return;\n // Cleanup stale socket reference left after error/disconnect\n if (this.socket) {\n try { this.socket.destroy(); } catch { /* noop */ }\n this.socket = null;\n }\n\n const socket = net.createConnection(this.endpoint.path);\n this.socket = socket;\n\n // PARSER-EOF-FLUSH: keep the parser handle so its EOF flush is reachable.\n // Inlining `createLineParser(...)` into the 'data' registration discarded\n // the only reference to `end()`, so bytes held back by the StringDecoder as\n // an incomplete UTF-8 sequence were lost with the decoder when the host\n // disconnected.\n const parser = createLineParser((envelope) => {\n if (envelope.kind === 'response') {\n const waiter = this.requestWaiters.get(envelope.requestId);\n if (waiter) {\n this.requestWaiters.delete(envelope.requestId);\n waiter.resolve(envelope.response);\n }\n return;\n }\n\n if (envelope.kind === 'event') {\n for (const listener of this.eventListeners) listener(envelope.event);\n }\n });\n socket.on('data', parser);\n\n /**\n * Drain the parser at EOF. The remainder is a partial, newline-unterminated\n * frame — a truncated transmission rather than a message — so it is NOT\n * parsed. Feeding a half-written envelope to JSON.parse here would throw\n * inside a socket event handler with no catch above it, converting a host\n * that died mid-write into an uncaught exception in this process. That is\n * exactly the failure the parser's contract set out to avoid.\n *\n * Reported through the existing disconnect surface rather than a new one:\n * a non-empty tail means the peer was cut off mid-frame, which is\n * diagnostic context for the disconnect that is about to be emitted.\n */\n const flushParser = (): string => {\n try {\n return parser.end();\n } catch {\n return '';\n }\n };\n\n socket.on('error', (error) => {\n const remainder = flushParser();\n this.handleDisconnect(socket, 'error', error, remainder.length);\n });\n\n // 'close'/'end' are the clean-FIN counterparts of 'error'. The host server\n // has always handled both directions (server.ts registers close+end); this\n // side handled neither, so host death was observable only if it happened to\n // surface as a socket error.\n socket.on('close', () => {\n this.handleDisconnect(socket, 'closed', undefined, flushParser().length);\n });\n socket.on('end', () => {\n this.handleDisconnect(socket, 'ended', undefined, flushParser().length);\n });\n\n await new Promise<void>((resolve, reject) => {\n socket.once('connect', () => {\n establishedSockets.add(socket);\n resolve();\n });\n socket.once('error', reject);\n });\n }\n\n onEvent(listener: (event: SessionHostEvent) => void): () => void {\n this.eventListeners.add(listener);\n return () => {\n this.eventListeners.delete(listener);\n };\n }\n\n async request<T = unknown>(request: SessionHostRequest): Promise<SessionHostResponse<T>> {\n await this.connect();\n if (!this.socket) throw new Error('Session host socket unavailable');\n\n const requestId = generateUUID();\n const envelope: SessionHostRequestEnvelope = {\n kind: 'request',\n requestId,\n request,\n };\n\n const response = await new Promise<SessionHostResponse>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.requestWaiters.delete(requestId);\n reject(new Error(`Session host request timed out after 30s (${request.type})`));\n }, 30_000);\n this.requestWaiters.set(requestId, {\n resolve: (value) => { clearTimeout(timeout); resolve(value); },\n reject: (error) => { clearTimeout(timeout); reject(error); },\n });\n this.socket?.write(serializeEnvelope(envelope));\n });\n\n return response as SessionHostResponse<T>;\n }\n\n async close(): Promise<void> {\n if (!this.socket) return;\n const socket = this.socket;\n this.socket = null;\n // Deliberate teardown: suppress the 'close' handler's disconnect signal so\n // callers don't see an intentional close reported as a session-host death.\n this.disconnectedSockets.add(socket);\n for (const waiter of this.requestWaiters.values()) {\n waiter.reject(new Error('Session host client closed'));\n }\n this.requestWaiters.clear();\n await new Promise<void>((resolve) => {\n let settled = false;\n const done = () => {\n if (settled) return;\n settled = true;\n resolve();\n };\n socket.once('close', done);\n socket.end();\n socket.destroy();\n setTimeout(done, 50);\n });\n }\n}\n\nexport function createResponseEnvelope(requestId: string, response: SessionHostResponse): SessionHostResponseEnvelope {\n return {\n kind: 'response',\n requestId,\n response,\n };\n}\n\nexport function writeEnvelope(socket: Pick<net.Socket, 'write'>, envelope: SessionHostWireEnvelope): void {\n socket.write(serializeEnvelope(envelope));\n}\n\nexport { createLineParser };\n","import * as crypto from 'crypto';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\n/**\n * Instance identity derivation for the session-host IPC namespace.\n *\n * One machine can host several ADHDev installs (stable `~/.adhdev`, preview\n * `~/.adhdev-preview`, standalone `~/.adhdev-standalone`, or any explicit\n * ADHDEV_CONFIG_DIR). Each install is an *instance*: it owns its config dir\n * and everything mutable under it. The session-host socket/pipe must be\n * namespaced by that identity so two simultaneous instances can never attach\n * to, kill, or rebind each other's session host.\n *\n * This module is the single derivation rule, shared by:\n * - the parent daemons (via @adhdev/daemon-core's InstanceContext),\n * - the session-host-daemon child process (which only sees env vars),\n * - CLI attach/list clients.\n *\n * All three compute the endpoint from the same inputs (appName +\n * ADHDEV_CONFIG_DIR), so they always agree without extra plumbing.\n *\n * Compatibility contract: the DEFAULT instance (config dir canonicalizes to\n * `<home>/.adhdev`) yields an EMPTY ipc key, so its socket/pipe path stays\n * byte-for-byte identical to the pre-instance layout.\n */\n\nexport const DEFAULT_CONFIG_DIR_NAME = '.adhdev';\n\n/**\n * Resolve the instance config dir from the environment: ADHDEV_CONFIG_DIR\n * when set (trimmed), else `<homeDir>/.adhdev`. Mirrors daemon-core's\n * getConfigDir() source rule without the mkdir side effect.\n *\n * NOTE (track identity): the no-env fallback here stays the LEGACY stable\n * default `.adhdev` — session-host-core is a pure leaf package that\n * daemon-core depends on, so it cannot import daemon-core's track-identity\n * (dependency direction) and its separately-built dist never sees the\n * `__ADHDEV_BUILD_CHANNEL__` build stamp. This is safe because every tracked\n * runtime path pins ADHDEV_CONFIG_DIR explicitly: the daemon spawns the\n * session-host child with `env.ADHDEV_CONFIG_DIR = instance().configDir`\n * (managed-host.ts buildEnv), and the installer/service pins it for preview.\n * The track-aware no-env default lives in daemon-core (getConfigDir /\n * resolveInstanceContext), which is the authority for daemon-side paths.\n */\nexport function resolveInstanceConfigDir(\n env: NodeJS.ProcessEnv = process.env,\n homeDir: string = os.homedir(),\n): string {\n const override = typeof env.ADHDEV_CONFIG_DIR === 'string' ? env.ADHDEV_CONFIG_DIR.trim() : '';\n return override || path.join(homeDir, DEFAULT_CONFIG_DIR_NAME);\n}\n\n/**\n * Realpath the longest existing prefix of `resolved` and re-append the\n * non-existing remainder. Unlike a plain realpathSync, this is deterministic\n * whether or not the leaf exists yet (a config dir realpath'd only after its\n * first mkdir must not change the derived ipc key between processes).\n */\nfunction realpathLongestExistingPrefix(resolved: string): string {\n let current = resolved;\n const remainder: string[] = [];\n // Bounded walk: a path can have at most its own segment count of parents.\n for (let depth = 0; depth < 256; depth++) {\n try {\n const real = fs.realpathSync.native(current);\n return remainder.length === 0 ? real : path.join(real, ...remainder);\n } catch {\n const parent = path.dirname(current);\n if (parent === current) return resolved;\n remainder.unshift(path.basename(current));\n current = parent;\n }\n }\n return resolved;\n}\n\n/**\n * Canonical form of an instance path for identity comparison/hashing:\n * absolute, symlink-resolved (longest existing prefix), no trailing\n * separator, case-folded on win32.\n */\nexport function canonicalizeInstancePath(\n dir: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n const resolved = path.resolve(dir);\n const real = realpathLongestExistingPrefix(resolved).replace(/[\\\\/]+$/, '');\n return platform === 'win32' ? real.toLowerCase() : real;\n}\n\n/** True when `configDir` is canonically the default `<homeDir>/.adhdev`. */\nexport function isDefaultInstanceConfigDir(\n configDir: string,\n homeDir: string = os.homedir(),\n platform: NodeJS.Platform = process.platform,\n): boolean {\n return canonicalizeInstancePath(configDir, platform)\n === canonicalizeInstancePath(path.join(homeDir, DEFAULT_CONFIG_DIR_NAME), platform);\n}\n\n/**\n * Derive the session-host IPC namespace key for an instance config dir.\n *\n * '' for the default instance (legacy-compatible endpoint), otherwise a\n * 12-hex-char sha256 prefix of the canonical config dir — stable across\n * processes/machines for the same dir, collision-resistant between dirs,\n * and insensitive to symlink/spelling differences of the same dir.\n */\nexport function resolveSessionHostIpcKey(\n configDir: string,\n homeDir: string = os.homedir(),\n platform: NodeJS.Platform = process.platform,\n): string {\n if (isDefaultInstanceConfigDir(configDir, homeDir, platform)) return '';\n const canonical = canonicalizeInstancePath(configDir, platform);\n return crypto.createHash('sha256').update(canonical, 'utf8').digest('hex').slice(0, 12);\n}\n","/**\n * Shared PTY spawn environment utilities.\n *\n * Centralises npm/pnpm/yarn env variable stripping, terminal colour env\n * injection, and node-pty spawn-helper permission fixing.\n *\n * Used by daemon-core (provider-cli-adapter), session-host-daemon (runtime),\n * and daemon-cloud (session-host).\n */\n\nimport * as os from 'os';\nimport * as path from 'path';\n\n/**\n * Strip package-manager injected environment variables that can interfere\n * with child CLI processes and apply terminal colour defaults.\n */\nexport function sanitizeSpawnEnv(\n baseEnv: NodeJS.ProcessEnv,\n overrides?: Record<string, string>,\n): Record<string, string> {\n const env: Record<string, string> = {};\n const source = { ...baseEnv, ...(overrides || {}) } as NodeJS.ProcessEnv;\n\n for (const [key, value] of Object.entries(source)) {\n if (typeof value !== 'string') continue;\n env[key] = value;\n }\n\n for (const key of Object.keys(env)) {\n if (\n key === 'INIT_CWD'\n || key === 'npm_command'\n || key === 'npm_execpath'\n || key === 'npm_node_execpath'\n || key.startsWith('npm_')\n || key.startsWith('npm_config_')\n || key.startsWith('npm_package_')\n || key.startsWith('npm_lifecycle_')\n || key.startsWith('PNPM_')\n || key.startsWith('YARN_')\n || key.startsWith('BUN_')\n || key.startsWith('VSCODE_')\n || key.startsWith('ELECTRON_')\n ) {\n delete env[key];\n }\n }\n\n // Do not leak parent Codex session controls into child CLIs. Thread identity\n // breaks providerSessionId discovery/reconnect semantics, while the parent's\n // network-disabled sandbox flag prevents coordinator MCP children from\n // connecting back to the local ADHDev daemon.\n delete env.CODEX_THREAD_ID;\n delete env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE;\n delete env.CODEX_SANDBOX_NETWORK_DISABLED;\n delete env.NO_COLOR;\n delete env.COLOR;\n\n // Never forward an inherited terminal geometry. COLUMNS/LINES are advisory\n // overrides that a TUI trusts *in preference to* the real TTY size\n // (measured: with COLUMNS=17 exported, a child on a genuine 80-column PTY\n // reports `tput cols` = 17). The PTY here is sized independently from the\n // adapter's own geometry, so any inherited value is by definition unrelated\n // to the terminal the child was actually given.\n //\n // When the two disagree the child lays out for the phantom width and the\n // VT renders at the real one, so in-place repaints (`\\r`, absolute cursor\n // moves) land on the wrong row: content fragments across the buffer with a\n // blank gap between the stale head and the live tail. Observed on\n // antigravity-cli as a \"Running command\" spinner split into two rows 28\n // blank lines apart, which the spec's from_top/from_bottom sections then\n // sliced into separate body/footer values.\n //\n // Deleting them makes the child measure the TTY via ioctl(TIOCGWINSZ) —\n // the PTY's real size, which is exactly what the VT is sized to. node-pty\n // does not set these itself, so a clean parent env is already the normal\n // case; this only removes a value that leaked in from whatever launched\n // the daemon (interactive shell, tmux, CI runner, nested CLI).\n delete env.COLUMNS;\n delete env.LINES;\n\n // Do not leak a parent Claude Code session identity into spawned claude-cli\n // children. CLAUDE_CODE_CHILD_SESSION makes a spawned claude run as a nested\n // child that does NOT persist its ~/.claude/projects transcript, so the\n // native-source history reader finds no file and the live dashboard renders\n // empty. This bites when the daemon itself was launched from inside a Claude\n // Code session (the markers are inherited and forwarded to every child).\n // Unconditional: originally scoped to win32 (35b462c5) because the bug had\n // only been OBSERVED there at the time — the Windows daemon that surfaced it\n // happened to have been launched from inside a Claude Code session, and\n // macOS/Linux \"looked fine\" only because no one had hit that same launch\n // circumstance yet, not because the platform behaves differently. The\n // marker-inheritance mechanism itself was never platform-specific — nothing\n // about how Claude Code detects a nested child session depends on OS.\n // Confirmed reproduced identically on darwin (live coordinator measurement,\n // this task): a daemon started from inside a Claude Code session inherits\n // and forwards the same markers there too. A daemon-spawned claude should\n // always be a fresh top-level session, on every platform — exactly what the\n // original commit already called out as \"safe to make unconditional later.\"\n delete env.CLAUDECODE;\n delete env.CLAUDE_CODE_CHILD_SESSION;\n delete env.CLAUDE_CODE_ENTRYPOINT;\n delete env.CLAUDE_CODE_SESSION_ID;\n delete env.CLAUDE_CODE_EXECPATH;\n // CLAUDE_CODE_BRIDGE_SESSION_ID is the same kind of parent-identity marker\n // (observed grouped with CLAUDECODE/CLAUDE_CODE_SESSION_ID/\n // CLAUDE_CODE_CHILD_SESSION/CLAUDE_CODE_EXECPATH in the installed `claude`\n // binary's own env-var table, alongside a `buildBridgeReattachEnvFromState`\n // helper) — it identifies the parent's remote bridge/reattach session. Left\n // in place, a spawned child could try to reattach to the PARENT's remote\n // session instead of starting fresh, which is a worse variant of the same\n // \"not actually a fresh top-level session\" bug this block exists to prevent.\n delete env.CLAUDE_CODE_BRIDGE_SESSION_ID;\n\n applyTerminalColorEnv(env);\n return env;\n}\n\n/**\n * Apply preferred terminal colour environment variables.\n * Ensures TERM is set to xterm-256color and enables colour on Windows.\n */\nexport function applyTerminalColorEnv(env: Record<string, string>): void {\n if (env.NO_COLOR) return;\n\n if (!env.TERM || env.TERM === 'xterm-color') {\n env.TERM = 'xterm-256color';\n }\n if (!env.COLORTERM) env.COLORTERM = 'truecolor';\n\n if (process.platform === 'win32') {\n if (!env.FORCE_COLOR) env.FORCE_COLOR = '1';\n if (!env.CLICOLOR) env.CLICOLOR = '1';\n }\n}\n\n/**\n * Ensure node-pty's spawn-helper binary has execute permissions.\n *\n * npm's default umask can strip +x from the prebuilt spawn-helper on macOS/Linux,\n * causing EACCES when node-pty tries to fork. Best-effort fix.\n *\n * @param logFn Optional log callback for reporting the fix.\n */\nexport function ensureNodePtySpawnHelperPermissions(\n logFn?: (msg: string) => void,\n): void {\n if (os.platform() === 'win32') return;\n try {\n const fs = require('fs');\n const ptyDir = path.resolve(path.dirname(require.resolve('node-pty')), '..');\n const platformArch = `${os.platform()}-${os.arch()}`;\n const helper = path.join(ptyDir, 'prebuilds', platformArch, 'spawn-helper');\n if (fs.existsSync(helper)) {\n const stat = fs.statSync(helper);\n if (!(stat.mode & 0o111)) {\n fs.chmodSync(helper, stat.mode | 0o755);\n logFn?.(`Fixed spawn-helper permissions: ${helper}`);\n }\n }\n } catch {\n // best-effort: node-pty still works on most installs without this\n }\n}\n","import type {\n AcquireWritePayload,\n GetHostDiagnosticsPayload,\n PruneDuplicateSessionsPayload,\n ReleaseWritePayload,\n SessionHostDiagnostics,\n SessionHostPruneDuplicatesResult,\n SessionHostRecord,\n SessionHostRequestType,\n} from './types.js';\n\n/**\n * The 11-method session-host control-plane surface shared by the cloud and\n * standalone daemons. Both daemons used to carry a byte-for-byte copy of this\n * dispatch table plus the identical throw strings; this is the single source of\n * truth for that mapping. The wire `type` strings and error text here are the\n * daemon IPC contract and MUST match what the session-host daemon accepts.\n */\nexport interface SessionHostControlPlane {\n getDiagnostics(payload?: GetHostDiagnosticsPayload): Promise<SessionHostDiagnostics>;\n listSessions(): Promise<SessionHostRecord[]>;\n stopSession(sessionId: string): Promise<SessionHostRecord | null>;\n deleteSession(sessionId: string, opts?: { force?: boolean }): Promise<SessionHostRecord | null>;\n resumeSession(sessionId: string): Promise<SessionHostRecord | null>;\n restartSession(sessionId: string): Promise<SessionHostRecord | null>;\n sendSignal(sessionId: string, signal: string): Promise<SessionHostRecord | null>;\n forceDetachClient(sessionId: string, clientId: string): Promise<SessionHostRecord | null>;\n pruneDuplicateSessions(payload?: PruneDuplicateSessionsPayload): Promise<SessionHostPruneDuplicatesResult>;\n acquireWrite(payload: AcquireWritePayload): Promise<SessionHostRecord | null>;\n releaseWrite(payload: ReleaseWritePayload): Promise<SessionHostRecord | null>;\n}\n\n/**\n * Minimal request transport the control-plane factory needs. Each daemon injects\n * its own implementation:\n * - cloud reuses a single persistent SessionHostClient (with reconnect); its\n * `request` calls `ensureConnected()` then the shared client.\n * - standalone opens a fresh client per request and closes it in `finally`.\n *\n * The factory below owns ONLY the type/payload dispatch table; the connection\n * lifecycle and success/error unwrapping stay in each daemon's transport so the\n * observable behavior of both paths is preserved exactly.\n */\nexport interface SessionHostControlTransport {\n request<T>(type: SessionHostRequestType, payload: Record<string, unknown>): Promise<T>;\n}\n\n/**\n * Build the shared 11-method control-plane over an injected request transport.\n * The (type string, payload shape) pairs are verbatim what both daemons emitted\n * previously — do not reword them without matching the session-host daemon's\n * request handlers.\n */\nexport function createSessionHostControlPlane(\n transport: SessionHostControlTransport,\n): SessionHostControlPlane {\n return {\n getDiagnostics(payload: GetHostDiagnosticsPayload = {}): Promise<SessionHostDiagnostics> {\n return transport.request<SessionHostDiagnostics>('get_host_diagnostics', payload as Record<string, unknown>);\n },\n listSessions(): Promise<SessionHostRecord[]> {\n return transport.request<SessionHostRecord[]>('list_sessions', {});\n },\n stopSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('stop_session', { sessionId });\n },\n deleteSession(sessionId: string, opts: { force?: boolean } = {}): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('delete_session', { sessionId, force: opts.force === true });\n },\n resumeSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('resume_session', { sessionId });\n },\n restartSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('restart_session', { sessionId });\n },\n sendSignal(sessionId: string, signal: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('send_signal', { sessionId, signal });\n },\n forceDetachClient(sessionId: string, clientId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('force_detach_client', { sessionId, clientId });\n },\n pruneDuplicateSessions(payload: PruneDuplicateSessionsPayload = {}): Promise<SessionHostPruneDuplicatesResult> {\n return transport.request<SessionHostPruneDuplicatesResult>('prune_duplicate_sessions', payload as Record<string, unknown>);\n },\n acquireWrite(payload: AcquireWritePayload): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('acquire_write', payload as unknown as Record<string, unknown>);\n },\n releaseWrite(payload: ReleaseWritePayload): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('release_write', payload as unknown as Record<string, unknown>);\n },\n };\n}\n"],"mappings":"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","names":["path","generateUUID","platform","failure","resolve","crypto","os","path","platform","os","path","fs"]}
package/dist/index.mjs CHANGED
@@ -601,7 +601,7 @@ var SessionHostClient = class {
601
601
  this.disconnectListeners.delete(listener);
602
602
  };
603
603
  }
604
- handleDisconnect(socket, reason, error) {
604
+ handleDisconnect(socket, reason, error, droppedTailBytes = 0) {
605
605
  if (this.disconnectedSockets.has(socket)) return;
606
606
  this.disconnectedSockets.add(socket);
607
607
  const wasEstablished = establishedSockets.has(socket);
@@ -633,7 +633,8 @@ var SessionHostClient = class {
633
633
  reason,
634
634
  endpointPath: this.endpoint.path,
635
635
  pendingRequests,
636
- error
636
+ error,
637
+ droppedTailBytes
637
638
  };
638
639
  for (const listener of this.disconnectListeners) {
639
640
  try {
@@ -653,7 +654,7 @@ var SessionHostClient = class {
653
654
  }
654
655
  const socket = net.createConnection(this.endpoint.path);
655
656
  this.socket = socket;
656
- socket.on("data", createLineParser((envelope) => {
657
+ const parser = createLineParser((envelope) => {
657
658
  if (envelope.kind === "response") {
658
659
  const waiter = this.requestWaiters.get(envelope.requestId);
659
660
  if (waiter) {
@@ -665,15 +666,24 @@ var SessionHostClient = class {
665
666
  if (envelope.kind === "event") {
666
667
  for (const listener of this.eventListeners) listener(envelope.event);
667
668
  }
668
- }));
669
+ });
670
+ socket.on("data", parser);
671
+ const flushParser = () => {
672
+ try {
673
+ return parser.end();
674
+ } catch {
675
+ return "";
676
+ }
677
+ };
669
678
  socket.on("error", (error) => {
670
- this.handleDisconnect(socket, "error", error);
679
+ const remainder = flushParser();
680
+ this.handleDisconnect(socket, "error", error, remainder.length);
671
681
  });
672
682
  socket.on("close", () => {
673
- this.handleDisconnect(socket, "closed");
683
+ this.handleDisconnect(socket, "closed", void 0, flushParser().length);
674
684
  });
675
685
  socket.on("end", () => {
676
- this.handleDisconnect(socket, "ended");
686
+ this.handleDisconnect(socket, "ended", void 0, flushParser().length);
677
687
  });
678
688
  await new Promise((resolve3, reject) => {
679
689
  socket.once("connect", () => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/termination.ts","../src/buffer.ts","../src/defaults.ts","../src/runtime-labels.ts","../src/registry.ts","../src/ipc.ts","../src/instance-key.ts","../src/spawn-env.ts","../src/control-plane.ts"],"sourcesContent":["export type SessionTransport = 'pty';\n\nexport type SessionHostCategory = 'cli' | 'acp' | 'shell';\n\nexport type SessionLifecycle = 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted';\n\nexport type SessionClientType = 'daemon' | 'web' | 'local-terminal';\n\nexport type SessionOwnerType = 'agent' | 'user';\n\nexport interface SessionLaunchCommand {\n command: string;\n args: string[];\n env?: Record<string, string>;\n}\n\nexport interface SessionWriteOwner {\n clientId: string;\n ownerType: SessionOwnerType;\n acquiredAt: number;\n}\n\nexport interface SessionAttachedClient {\n clientId: string;\n type: SessionClientType;\n readOnly: boolean;\n attachedAt: number;\n lastSeenAt: number;\n}\n\nexport interface SessionBufferSnapshot {\n seq: number;\n text: string;\n truncated: boolean;\n cols?: number;\n rows?: number;\n}\n\nexport interface SessionTerminalState {\n cursor: { row: number; col: number };\n altScreen: boolean;\n pasteMode: boolean;\n rawMode: boolean;\n scrollRegion: { top: number; bot: number };\n cols: number;\n rows: number;\n}\n\nexport interface SessionTerminalSnapshot {\n text: string;\n state: SessionTerminalState;\n}\n\nexport interface SessionBufferState {\n scrollbackBytes: number;\n snapshotSeq: number;\n}\n\nexport type SessionHostSurfaceKind = 'live_runtime' | 'recovery_snapshot' | 'inactive_record';\n\n/**\n * Compact, secret-free record of how a session's PTY process terminated.\n *\n * Preserves the nullable/unknown exitCode and signal exactly as observed from\n * the first node-pty `onExit` — an `exitCode` of `null` means \"unknown\" (the\n * process was terminated by a signal, or the exit code was otherwise not\n * reported) and MUST NOT be collapsed to 0. Persisted as a tombstone so\n * termination diagnostics remain inspectable after the live runtime and its\n * transport adapter are torn down.\n */\nexport interface SessionTermination {\n /** Exit code exactly as observed; `null` = unknown / signal-terminated. Never fabricated to 0. */\n exitCode: number | null;\n /** Signal number that terminated the process, if any; `null` when not signalled. */\n signal: number | null;\n /** Classification derived from (exitCode, signal): clean exit vs. failure vs. unknown. */\n reason: 'exit' | 'signal' | 'failed' | 'unknown';\n /** Lifecycle recorded for this termination (`stopped` | `failed`). */\n lifecycle: Extract<SessionLifecycle, 'stopped' | 'failed'>;\n /** Wall-clock time the termination was observed. */\n terminatedAt: number;\n /** OS pid of the terminated process, if it was known. */\n osPid?: number;\n /** Lifecycle the session was in immediately before termination. */\n previousLifecycle?: SessionLifecycle;\n /** Last time output was observed from the session, if known. */\n lastOutputAt?: number;\n /** Whether the termination followed an explicit stop/delete request, and which. */\n requestedStop?: 'stop' | 'delete' | 'restart' | 'prune';\n}\n\nexport interface SessionHostRecord {\n sessionId: string;\n runtimeKey: string;\n displayName: string;\n workspaceLabel: string;\n transport: SessionTransport;\n providerType: string;\n category: SessionHostCategory;\n workspace: string;\n launchCommand: SessionLaunchCommand;\n osPid?: number;\n createdAt: number;\n startedAt?: number;\n lastActivityAt: number;\n lifecycle: SessionLifecycle;\n surfaceKind?: SessionHostSurfaceKind;\n writeOwner: SessionWriteOwner | null;\n attachedClients: SessionAttachedClient[];\n buffer: SessionBufferState;\n meta: Record<string, unknown>;\n /** Termination tombstone, stamped when the PTY process exits. Retained for post-mortem inspection. */\n termination?: SessionTermination;\n}\n\nexport interface CreateSessionPayload {\n sessionId?: string;\n runtimeKey?: string;\n displayName?: string;\n providerType: string;\n category: SessionHostCategory;\n workspace: string;\n launchCommand: SessionLaunchCommand;\n cols?: number;\n rows?: number;\n clientId?: string;\n clientType?: SessionClientType;\n meta?: Record<string, unknown>;\n}\n\nexport interface AttachSessionPayload {\n sessionId: string;\n clientId: string;\n clientType: SessionClientType;\n readOnly?: boolean;\n}\n\nexport interface DetachSessionPayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface SendInputPayload {\n sessionId: string;\n clientId: string;\n data: string;\n}\n\nexport interface ResizeSessionPayload {\n sessionId: string;\n cols: number;\n rows: number;\n}\n\nexport interface StopSessionPayload {\n sessionId: string;\n}\n\nexport interface DeleteSessionPayload {\n sessionId: string;\n /** Stop a live runtime before deleting the persisted record. */\n force?: boolean;\n}\n\nexport interface ResumeSessionPayload {\n sessionId: string;\n}\n\nexport interface AcquireWritePayload {\n sessionId: string;\n clientId: string;\n ownerType: SessionOwnerType;\n force?: boolean;\n}\n\nexport interface ReleaseWritePayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface GetSnapshotPayload {\n sessionId: string;\n sinceSeq?: number;\n}\n\nexport interface GetTerminalSnapshotPayload {\n sessionId: string;\n}\n\nexport interface ClearSessionBufferPayload {\n sessionId: string;\n}\n\nexport interface UpdateSessionMetaPayload {\n sessionId: string;\n meta: Record<string, unknown>;\n replace?: boolean;\n}\n\nexport interface GetHostDiagnosticsPayload {\n includeSessions?: boolean;\n limit?: number;\n}\n\nexport interface ForceDetachClientPayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface SendSignalPayload {\n sessionId: string;\n signal: string;\n}\n\nexport interface RestartSessionPayload {\n sessionId: string;\n}\n\nexport interface PruneDuplicateSessionsPayload {\n providerType?: string;\n workspace?: string;\n dryRun?: boolean;\n}\n\nexport interface SessionHostDuplicateSessionGroup {\n bindingKey: string;\n providerType: string;\n workspace: string;\n providerSessionId: string;\n keptSessionId: string;\n prunedSessionIds: string[];\n}\n\nexport interface SessionHostPruneDuplicatesResult {\n duplicateGroupCount: number;\n keptSessionIds: string[];\n prunedSessionIds: string[];\n groups: SessionHostDuplicateSessionGroup[];\n}\n\nexport interface SessionHostLogEntry {\n timestamp: number;\n level: 'debug' | 'info' | 'warn' | 'error';\n message: string;\n sessionId?: string;\n data?: Record<string, unknown>;\n}\n\nexport interface SessionHostRequestTrace {\n timestamp: number;\n requestId: string;\n type: SessionHostRequest['type'];\n sessionId?: string;\n clientId?: string;\n success: boolean;\n durationMs: number;\n error?: string;\n}\n\nexport interface SessionHostRuntimeTransition {\n timestamp: number;\n sessionId: string;\n action: string;\n lifecycle?: SessionLifecycle;\n detail?: string;\n success?: boolean;\n error?: string;\n}\n\nexport interface SessionHostDiagnostics {\n hostStartedAt: number;\n endpoint: string;\n /**\n * Absolute path of the script this host process is running (its own\n * `__filename`), self-reported.\n *\n * This is the only way a client can learn WHICH install a reachable host came\n * from without process inspection. On Windows the daemon's PowerShell/wmic\n * command-line probe is blocked outright on some boxes (AV/EDR, and `wmic` is\n * removed by default in Win11 24H2+), so identity checks that depend on it\n * fail silently — which let a host from a DELETED install prefix keep serving\n * its socket while every `create_session` died loading node-pty's conpty.node\n * from the missing tree. The host answering with its own path needs no\n * privileges and always works.\n *\n * Optional: an older host predating this field simply omits it, and callers\n * must treat `undefined` as \"unknown\", never as a match.\n */\n hostEntryPath?: string;\n runtimeCount: number;\n supportedRequestTypes?: SessionHostRequestType[];\n sessions?: SessionHostRecord[];\n liveRuntimes?: SessionHostRecord[];\n recoverySnapshots?: SessionHostRecord[];\n inactiveRecords?: SessionHostRecord[];\n recentLogs: SessionHostLogEntry[];\n recentRequests: SessionHostRequestTrace[];\n recentTransitions: SessionHostRuntimeTransition[];\n}\n\nexport type SessionHostRequest =\n | { type: 'create_session'; payload: CreateSessionPayload }\n | { type: 'attach_session'; payload: AttachSessionPayload }\n | { type: 'detach_session'; payload: DetachSessionPayload }\n | { type: 'send_input'; payload: SendInputPayload }\n | { type: 'resize_session'; payload: ResizeSessionPayload }\n | { type: 'stop_session'; payload: StopSessionPayload }\n | { type: 'delete_session'; payload: DeleteSessionPayload }\n | { type: 'resume_session'; payload: ResumeSessionPayload }\n | { type: 'acquire_write'; payload: AcquireWritePayload }\n | { type: 'release_write'; payload: ReleaseWritePayload }\n | { type: 'get_snapshot'; payload: GetSnapshotPayload }\n | { type: 'get_terminal_snapshot'; payload: GetTerminalSnapshotPayload }\n | { type: 'clear_session_buffer'; payload: ClearSessionBufferPayload }\n | { type: 'update_session_meta'; payload: UpdateSessionMetaPayload }\n | { type: 'get_host_diagnostics'; payload?: GetHostDiagnosticsPayload }\n | { type: 'force_detach_client'; payload: ForceDetachClientPayload }\n | { type: 'send_signal'; payload: SendSignalPayload }\n | { type: 'restart_session'; payload: RestartSessionPayload }\n | { type: 'prune_duplicate_sessions'; payload?: PruneDuplicateSessionsPayload }\n | { type: 'list_sessions'; payload?: {} };\n\nexport type SessionHostRequestType = SessionHostRequest['type'];\n\nexport const SESSION_HOST_SUPPORTED_REQUEST_TYPES: readonly SessionHostRequestType[] = [\n 'create_session',\n 'attach_session',\n 'detach_session',\n 'send_input',\n 'resize_session',\n 'stop_session',\n 'delete_session',\n 'resume_session',\n 'acquire_write',\n 'release_write',\n 'get_snapshot',\n 'get_terminal_snapshot',\n 'clear_session_buffer',\n 'update_session_meta',\n 'get_host_diagnostics',\n 'force_detach_client',\n 'send_signal',\n 'restart_session',\n 'prune_duplicate_sessions',\n 'list_sessions',\n];\n\nexport interface SessionHostResponse<T = unknown> {\n success: boolean;\n result?: T;\n error?: string;\n}\n\nexport type SessionHostEvent =\n | { type: 'session_created'; sessionId: string; record: SessionHostRecord }\n | { type: 'session_started'; sessionId: string; pid?: number }\n | { type: 'session_resumed'; sessionId: string; pid?: number }\n | { type: 'session_output'; sessionId: string; seq: number; data: string }\n | { type: 'session_cleared'; sessionId: string }\n | { type: 'session_exit'; sessionId: string; exitCode: number | null; signal?: number | null; termination?: SessionTermination }\n | { type: 'session_stopped'; sessionId: string }\n | { type: 'session_deleted'; sessionId: string }\n | { type: 'session_resized'; sessionId: string; cols: number; rows: number }\n | { type: 'write_owner_changed'; sessionId: string; owner: SessionWriteOwner | null }\n | { type: 'client_attached'; sessionId: string; client: SessionAttachedClient }\n | { type: 'client_detached'; sessionId: string; clientId: string }\n | { type: 'host_log'; entry: SessionHostLogEntry }\n | { type: 'request_trace'; trace: SessionHostRequestTrace }\n | { type: 'runtime_transition'; transition: SessionHostRuntimeTransition };\n\nexport interface SessionHostRequestEnvelope {\n kind: 'request';\n requestId: string;\n request: SessionHostRequest;\n}\n\nexport interface SessionHostResponseEnvelope {\n kind: 'response';\n requestId: string;\n response: SessionHostResponse;\n}\n\nexport interface SessionHostEventEnvelope {\n kind: 'event';\n event: SessionHostEvent;\n}\n\nexport type SessionHostWireEnvelope =\n | SessionHostRequestEnvelope\n | SessionHostResponseEnvelope\n | SessionHostEventEnvelope;\n","import type { SessionLifecycle, SessionTermination } from './types.js';\n\nexport interface ClassifyTerminationInput {\n exitCode: number | null;\n signal?: number | null;\n osPid?: number;\n previousLifecycle?: SessionLifecycle;\n lastOutputAt?: number;\n requestedStop?: SessionTermination['requestedStop'];\n terminatedAt: number;\n}\n\n/**\n * Classify a PTY termination from its raw (exitCode, signal) pair.\n *\n * Invariants:\n * - A `null` exitCode is UNKNOWN, never a clean exit — it MUST NOT collapse to\n * exit 0. This is the regression guard for the old `exitCode ?? 0` behavior.\n * - A non-null, non-zero exitCode or any signal is a failure.\n * - Only an explicit exit code of 0 (with no signal) is a clean stop.\n *\n * The returned lifecycle is `stopped` only for a clean exit 0; every other\n * case (nonzero, signalled, or unknown) is `failed` so that an unknown\n * termination stays distinguishable from a successful one.\n */\nexport function classifyTermination(input: ClassifyTerminationInput): SessionTermination {\n const { exitCode, terminatedAt } = input;\n const signal = input.signal ?? null;\n\n let reason: SessionTermination['reason'];\n let lifecycle: SessionTermination['lifecycle'];\n\n if (signal !== null && signal !== 0) {\n reason = 'signal';\n lifecycle = 'failed';\n } else if (exitCode === null) {\n reason = 'unknown';\n lifecycle = 'failed';\n } else if (exitCode === 0) {\n reason = 'exit';\n lifecycle = 'stopped';\n } else {\n reason = 'failed';\n lifecycle = 'failed';\n }\n\n const termination: SessionTermination = {\n exitCode,\n signal,\n reason,\n lifecycle,\n terminatedAt,\n };\n if (typeof input.osPid === 'number') termination.osPid = input.osPid;\n if (input.previousLifecycle) termination.previousLifecycle = input.previousLifecycle;\n if (typeof input.lastOutputAt === 'number') termination.lastOutputAt = input.lastOutputAt;\n if (input.requestedStop) termination.requestedStop = input.requestedStop;\n return termination;\n}\n","import type { SessionBufferSnapshot } from './types.js';\n\nexport interface SessionRingBufferOptions {\n maxBytes?: number;\n}\n\n// Manual \"Load older terminal output\" replays this raw ring buffer into the\n// browser terminal. 512KiB was too small for long Claude/Codex conversations:\n// by the time the user scrolled to the top and clicked the loader, the output\n// they were trying to recover was often already trimmed.\nexport const DEFAULT_SESSION_RING_BUFFER_MAX_BYTES = 4 * 1024 * 1024;\n\nexport class SessionRingBuffer {\n private maxBytes: number;\n private chunks: { seq: number; data: string; bytes: number }[] = [];\n private nextSeq = 1;\n private totalBytes = 0;\n\n constructor(options: SessionRingBufferOptions = {}) {\n this.maxBytes = options.maxBytes ?? DEFAULT_SESSION_RING_BUFFER_MAX_BYTES;\n }\n\n append(data: string): number {\n const normalized = typeof data === 'string' ? data : String(data ?? '');\n const bytes = Buffer.byteLength(normalized, 'utf8');\n const seq = this.nextSeq++;\n\n this.chunks.push({ seq, data: normalized, bytes });\n this.totalBytes += bytes;\n this.trim();\n return seq;\n }\n\n snapshot(sinceSeq?: number): SessionBufferSnapshot {\n const relevant = typeof sinceSeq === 'number'\n ? this.chunks.filter(chunk => chunk.seq > sinceSeq)\n : this.chunks;\n\n const text = relevant.map(chunk => chunk.data).join('');\n const truncated = !!this.chunks[0] && typeof sinceSeq === 'number' && sinceSeq < this.chunks[0].seq - 1;\n\n return {\n seq: this.nextSeq - 1,\n text,\n truncated,\n };\n }\n\n getState(): { scrollbackBytes: number; snapshotSeq: number } {\n return {\n scrollbackBytes: this.totalBytes,\n snapshotSeq: this.nextSeq - 1,\n };\n }\n\n clear(): void {\n this.chunks = [];\n this.totalBytes = 0;\n this.nextSeq = 1;\n }\n\n restore(snapshot: { seq: number; text: string }): void {\n this.clear();\n const text = String(snapshot.text || '');\n if (!text) {\n this.nextSeq = Math.max(1, Number(snapshot.seq || 0) + 1);\n return;\n }\n const bytes = Buffer.byteLength(text, 'utf8');\n const seq = Math.max(1, Number(snapshot.seq || 1));\n this.chunks = [{ seq, data: text, bytes }];\n this.totalBytes = bytes;\n this.nextSeq = seq + 1;\n this.trim();\n }\n\n private trim(): void {\n while (this.totalBytes > this.maxBytes && this.chunks.length > 1) {\n const removed = this.chunks.shift();\n if (!removed) break;\n this.totalBytes -= removed.bytes;\n }\n }\n}\n","export const DEFAULT_SESSION_HOST_COLS = 80;\nexport const DEFAULT_SESSION_HOST_ROWS = 32;\n\nfunction normalizeSessionHostDimension(value: number | undefined, fallback: number): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;\n const rounded = Math.floor(value);\n return rounded > 0 ? rounded : fallback;\n}\n\nexport function resolveSessionHostCols(value: number | undefined): number {\n return normalizeSessionHostDimension(value, DEFAULT_SESSION_HOST_COLS);\n}\n\nexport function resolveSessionHostRows(value: number | undefined): number {\n return normalizeSessionHostDimension(value, DEFAULT_SESSION_HOST_ROWS);\n}\n","import * as path from 'path';\nimport type { CreateSessionPayload, SessionHostRecord, SessionHostSurfaceKind } from './types.js';\n\nfunction normalizeSlug(input: string): string {\n return input\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48);\n}\n\nfunction normalizeValue(input: string): string {\n return input.trim().toLowerCase();\n}\n\nexport function getWorkspaceLabel(workspace: string): string {\n const trimmed = workspace.trim();\n if (!trimmed) return 'workspace';\n const normalized = trimmed.replace(/[\\\\/]+$/, '');\n const base = path.basename(normalized);\n return base || normalized;\n}\n\nexport function buildRuntimeDisplayName(payload: Pick<CreateSessionPayload, 'displayName' | 'providerType' | 'workspace'>): string {\n const explicit = payload.displayName?.trim();\n if (explicit) return explicit;\n const workspaceLabel = getWorkspaceLabel(payload.workspace);\n const providerLabel = payload.providerType.trim() || 'runtime';\n return `${providerLabel} @ ${workspaceLabel}`;\n}\n\nexport function buildRuntimeKey(\n payload: Pick<CreateSessionPayload, 'runtimeKey' | 'displayName' | 'providerType' | 'workspace'>,\n existingKeys: Iterable<string>,\n): string {\n const requested = payload.runtimeKey?.trim();\n const existing = new Set(Array.from(existingKeys, (key) => key.toLowerCase()));\n const displayName = buildRuntimeDisplayName(payload);\n const baseKey = normalizeSlug(requested || displayName || getWorkspaceLabel(payload.workspace) || payload.providerType || 'runtime') || 'runtime';\n if (!existing.has(baseKey)) return baseKey;\n\n let suffix = 2;\n let candidate = `${baseKey}-${suffix}`;\n while (existing.has(candidate)) {\n suffix += 1;\n candidate = `${baseKey}-${suffix}`;\n }\n return candidate;\n}\n\nexport interface SessionHostSurfaceRecordLike {\n lifecycle?: string | null;\n surfaceKind?: SessionHostSurfaceKind | null;\n meta?: Record<string, unknown> | null;\n termination?: unknown;\n}\n\nconst LIVE_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'interrupted']);\nconst TERMINAL_LIFECYCLES = new Set(['stopped', 'failed']);\n\n/**\n * True when a record has reached a terminal state: either its lifecycle is\n * terminal, or it carries a termination stamp (which the exit handler writes\n * alongside the lifecycle, and which can outlive a stale 'stopping' value).\n *\n * Used both to keep terminated records from being persisted as live files and\n * to keep them from surfacing as attach targets.\n */\nexport function isTerminalRecord(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.termination) return true;\n return TERMINAL_LIFECYCLES.has(String(record.lifecycle || '').trim());\n}\n\nexport function isSessionHostLiveRuntime(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.surfaceKind === 'live_runtime') return true;\n if (record.surfaceKind === 'recovery_snapshot' || record.surfaceKind === 'inactive_record') return false;\n // A record stamped with a termination is terminal even if its lifecycle is\n // still 'stopping' — e.g. a stale file recreated by a late persist that\n // raced the exit/cleanup path. Without this, such records surface as a\n // live runtime (attach target) for a PID that already exited.\n if (record.termination) return false;\n const lifecycle = String(record.lifecycle || '').trim();\n return LIVE_LIFECYCLES.has(lifecycle);\n}\n\nexport function getSessionHostRecoveryLabel(meta: Record<string, unknown> | null | undefined): string | null {\n const recoveryState = typeof meta?.runtimeRecoveryState === 'string'\n ? String(meta.runtimeRecoveryState).trim()\n : '';\n if (!recoveryState) return null;\n if (recoveryState === 'auto_resumed') return 'restored after restart';\n if (recoveryState === 'resume_failed') return 'restore failed';\n if (recoveryState === 'host_restart_interrupted') return 'host restart interrupted';\n if (recoveryState === 'orphan_snapshot') return 'snapshot recovered';\n return recoveryState.replace(/_/g, ' ');\n}\n\nexport function isSessionHostRecoverySnapshot(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.surfaceKind === 'recovery_snapshot') return true;\n if (record.surfaceKind === 'live_runtime' || record.surfaceKind === 'inactive_record') return false;\n if (isSessionHostLiveRuntime(record)) return false;\n\n const lifecycle = String(record.lifecycle || '').trim();\n if (lifecycle && lifecycle !== 'stopped' && lifecycle !== 'failed') {\n return false;\n }\n\n const meta = record.meta || undefined;\n if (meta?.restoredFromStorage === true) return true;\n return getSessionHostRecoveryLabel(meta) !== null;\n}\n\nexport function getSessionHostSurfaceKind(record: SessionHostSurfaceRecordLike | null | undefined): SessionHostSurfaceKind {\n if (record?.surfaceKind === 'live_runtime' || record?.surfaceKind === 'recovery_snapshot' || record?.surfaceKind === 'inactive_record') {\n return record.surfaceKind;\n }\n if (isSessionHostLiveRuntime(record)) return 'live_runtime';\n if (isSessionHostRecoverySnapshot(record)) return 'recovery_snapshot';\n return 'inactive_record';\n}\n\nexport function partitionSessionHostRecords<T extends SessionHostSurfaceRecordLike>(records: T[]): {\n liveRuntimes: T[];\n recoverySnapshots: T[];\n inactiveRecords: T[];\n} {\n const liveRuntimes: T[] = [];\n const recoverySnapshots: T[] = [];\n const inactiveRecords: T[] = [];\n\n for (const record of records) {\n const kind = getSessionHostSurfaceKind(record);\n if (kind === 'live_runtime') {\n liveRuntimes.push(record);\n } else if (kind === 'recovery_snapshot') {\n recoverySnapshots.push(record);\n } else {\n inactiveRecords.push(record);\n }\n }\n\n return {\n liveRuntimes,\n recoverySnapshots,\n inactiveRecords,\n };\n}\n\nexport function partitionSessionHostDiagnosticsSessions(\n records: SessionHostRecord[] | null | undefined,\n): ReturnType<typeof partitionSessionHostRecords<SessionHostRecord>> {\n return partitionSessionHostRecords(records || []);\n}\n\nexport function resolveAttachableRuntimeRecord(records: SessionHostRecord[], identifier: string): SessionHostRecord {\n const record = resolveRuntimeRecord(records, identifier);\n const surfaceKind = getSessionHostSurfaceKind(record);\n if (surfaceKind === 'live_runtime') {\n return record;\n }\n if (surfaceKind === 'recovery_snapshot') {\n throw new Error(`Runtime ${record.runtimeKey} is a recovery snapshot, not a live attach target. Resume or recover it first.`);\n }\n throw new Error(`Runtime ${record.runtimeKey} is ${record.lifecycle}, not a live attach target.`);\n}\n\nfunction uniqueMatch(records: SessionHostRecord[], predicate: (record: SessionHostRecord) => boolean): SessionHostRecord | null {\n const matches = records.filter(predicate);\n if (matches.length === 1) return matches[0] || null;\n if (matches.length === 0) return null;\n const labels = matches.map((record) => `${record.runtimeKey} (${record.sessionId})`).join(', ');\n throw new Error(`Ambiguous runtime target. Matches: ${labels}`);\n}\n\nexport function resolveRuntimeRecord(records: SessionHostRecord[], identifier: string): SessionHostRecord {\n const target = identifier.trim();\n if (!target) {\n throw new Error('Runtime target is required');\n }\n\n const exact = uniqueMatch(records, (record) =>\n record.sessionId === target ||\n normalizeValue(record.runtimeKey) === normalizeValue(target) ||\n normalizeValue(record.displayName) === normalizeValue(target),\n );\n if (exact) return exact;\n\n const prefix = uniqueMatch(records, (record) =>\n record.sessionId.startsWith(target) ||\n normalizeValue(record.runtimeKey).startsWith(normalizeValue(target)),\n );\n if (prefix) return prefix;\n\n throw new Error(`Unknown runtime target: ${target}`);\n}\n\nexport function formatRuntimeOwner(record: Pick<SessionHostRecord, 'writeOwner'>): string {\n if (!record.writeOwner) return 'none';\n return `${record.writeOwner.ownerType}:${record.writeOwner.clientId}`;\n}\n","function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\nimport type {\n AcquireWritePayload,\n AttachSessionPayload,\n CreateSessionPayload,\n DetachSessionPayload,\n ReleaseWritePayload,\n SessionAttachedClient,\n SessionHostRecord,\n SessionTermination,\n} from './types.js';\nimport { SessionRingBuffer } from './buffer.js';\nimport { resolveSessionHostCols, resolveSessionHostRows } from './defaults.js';\nimport { buildRuntimeDisplayName, buildRuntimeKey, getWorkspaceLabel } from './runtime-labels.js';\n\ninterface SessionRuntimeState {\n record: SessionHostRecord;\n buffer: SessionRingBuffer;\n}\n\nexport class SessionHostRegistry {\n private sessions = new Map<string, SessionRuntimeState>();\n\n createSession(payload: CreateSessionPayload): SessionHostRecord {\n const sessionId = payload.sessionId || generateUUID();\n if (this.sessions.has(sessionId)) {\n throw new Error(`Session already exists: ${sessionId}`);\n }\n const now = Date.now();\n const initialClient = payload.clientId\n ? [{\n clientId: payload.clientId,\n type: payload.clientType || 'daemon',\n readOnly: false,\n attachedAt: now,\n lastSeenAt: now,\n } satisfies SessionAttachedClient]\n : [];\n\n const record: SessionHostRecord = {\n sessionId,\n runtimeKey: buildRuntimeKey(\n payload,\n Array.from(this.sessions.values(), (state) => state.record.runtimeKey),\n ),\n displayName: buildRuntimeDisplayName(payload),\n workspaceLabel: getWorkspaceLabel(payload.workspace),\n transport: 'pty',\n providerType: payload.providerType,\n category: payload.category,\n workspace: payload.workspace,\n launchCommand: payload.launchCommand,\n createdAt: now,\n lastActivityAt: now,\n lifecycle: 'starting',\n writeOwner: null,\n attachedClients: initialClient,\n buffer: {\n scrollbackBytes: 0,\n snapshotSeq: 0,\n },\n meta: payload.meta || {},\n };\n\n record.meta = {\n sessionHostCols: resolveSessionHostCols(payload.cols),\n sessionHostRows: resolveSessionHostRows(payload.rows),\n ...record.meta,\n };\n\n this.sessions.set(sessionId, {\n record,\n buffer: new SessionRingBuffer(),\n });\n\n return this.cloneRecord(record);\n }\n\n restoreSession(record: SessionHostRecord, snapshot?: { seq: number; text: string } | null): SessionHostRecord {\n const cloned = this.cloneRecord(record);\n this.sessions.set(cloned.sessionId, {\n record: cloned,\n buffer: (() => {\n const buffer = new SessionRingBuffer();\n if (snapshot) buffer.restore(snapshot);\n return buffer;\n })(),\n });\n return this.cloneRecord(cloned);\n }\n\n listSessions(): SessionHostRecord[] {\n return Array.from(this.sessions.values())\n .map(state => this.cloneRecord(state.record))\n .sort((a, b) => b.lastActivityAt - a.lastActivityAt);\n }\n\n getSession(sessionId: string): SessionHostRecord | null {\n const state = this.sessions.get(sessionId);\n return state ? this.cloneRecord(state.record) : null;\n }\n\n attachClient(payload: AttachSessionPayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n const now = Date.now();\n let removedDaemonOwner = false;\n\n if (payload.clientType === 'daemon') {\n const staleDaemonClientIds = state.record.attachedClients\n .filter(client => client.type === 'daemon' && client.clientId !== payload.clientId)\n .map(client => client.clientId);\n if (staleDaemonClientIds.length > 0) {\n state.record.attachedClients = state.record.attachedClients.filter(\n client => !(client.type === 'daemon' && client.clientId !== payload.clientId),\n );\n if (state.record.writeOwner && staleDaemonClientIds.includes(state.record.writeOwner.clientId)) {\n removedDaemonOwner = true;\n }\n }\n }\n\n const existing = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n\n if (existing) {\n existing.type = payload.clientType;\n existing.readOnly = !!payload.readOnly;\n existing.lastSeenAt = now;\n } else {\n state.record.attachedClients.push({\n clientId: payload.clientId,\n type: payload.clientType,\n readOnly: !!payload.readOnly,\n attachedAt: now,\n lastSeenAt: now,\n });\n }\n\n if (removedDaemonOwner) {\n state.record.writeOwner = null;\n }\n\n state.record.lastActivityAt = now;\n return this.cloneRecord(state.record);\n }\n\n detachClient(payload: DetachSessionPayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n state.record.attachedClients = state.record.attachedClients.filter(client => client.clientId !== payload.clientId);\n if (state.record.writeOwner?.clientId === payload.clientId) {\n state.record.writeOwner = null;\n }\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n acquireWrite(payload: AcquireWritePayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n if (state.record.writeOwner && state.record.writeOwner.clientId !== payload.clientId && !payload.force) {\n throw new Error(`Write owned by ${state.record.writeOwner.clientId}`);\n }\n const attachedClient = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n if (attachedClient) {\n attachedClient.readOnly = false;\n attachedClient.lastSeenAt = Date.now();\n }\n state.record.writeOwner = {\n clientId: payload.clientId,\n ownerType: payload.ownerType,\n acquiredAt: Date.now(),\n };\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n releaseWrite(payload: ReleaseWritePayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n const attachedClient = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n if (attachedClient) {\n attachedClient.readOnly = false;\n attachedClient.lastSeenAt = Date.now();\n }\n if (state.record.writeOwner?.clientId === payload.clientId) {\n state.record.writeOwner = null;\n }\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n appendOutput(sessionId: string, data: string): { record: SessionHostRecord; seq: number } {\n const state = this.requireSession(sessionId);\n const seq = state.buffer.append(data);\n state.record.buffer = state.buffer.getState();\n state.record.lastActivityAt = Date.now();\n return { record: this.cloneRecord(state.record), seq };\n }\n\n getSnapshot(sessionId: string, sinceSeq?: number) {\n const state = this.requireSession(sessionId);\n state.record.buffer = state.buffer.getState();\n return state.buffer.snapshot(sinceSeq);\n }\n\n clearBuffer(sessionId: string): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.buffer.clear();\n state.record.buffer = state.buffer.getState();\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n updateSessionMeta(sessionId: string, meta: Record<string, unknown>, replace = false): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.meta = replace\n ? { ...meta }\n : {\n ...(state.record.meta || {}),\n ...meta,\n };\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n markStarted(sessionId: string, pid?: number): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = 'running';\n state.record.startedAt = state.record.startedAt || Date.now();\n if (typeof pid === 'number') state.record.osPid = pid;\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n markStopped(sessionId: string, lifecycle: 'stopped' | 'failed' = 'stopped', termination?: SessionTermination): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = lifecycle;\n state.record.lastActivityAt = Date.now();\n if (termination) state.record.termination = termination;\n return this.cloneRecord(state.record);\n }\n\n setLifecycle(sessionId: string, lifecycle: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted'): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = lifecycle;\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n deleteSession(sessionId: string): boolean {\n return this.sessions.delete(sessionId);\n }\n\n private requireSession(sessionId: string): SessionRuntimeState {\n const state = this.sessions.get(sessionId);\n if (!state) throw new Error(`Unknown session: ${sessionId}`);\n return state;\n }\n\n private cloneRecord(record: SessionHostRecord): SessionHostRecord {\n return {\n ...record,\n launchCommand: {\n ...record.launchCommand,\n args: [...record.launchCommand.args],\n env: record.launchCommand.env ? { ...record.launchCommand.env } : undefined,\n },\n writeOwner: record.writeOwner ? { ...record.writeOwner } : null,\n attachedClients: record.attachedClients.map(client => ({ ...client })),\n buffer: { ...record.buffer },\n meta: { ...record.meta },\n };\n }\n}\n","import * as os from 'os';\nimport * as path from 'path';\nimport * as net from 'net';\nimport { StringDecoder } from 'string_decoder';\nimport type {\n SessionHostEvent,\n SessionHostRequest,\n SessionHostRequestEnvelope,\n SessionHostResponse,\n SessionHostResponseEnvelope,\n SessionHostWireEnvelope,\n} from './types.js';\n\nfunction generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\nexport interface SessionHostEndpoint {\n kind: 'unix' | 'pipe';\n path: string;\n}\n\nexport interface SessionHostEndpointOptions {\n /**\n * Instance IPC namespace key (see instance-key.ts). '' / omitted yields the\n * legacy default-instance endpoint; a non-empty key suffixes the endpoint so\n * each config-dir instance gets its own socket/pipe.\n */\n ipcKey?: string;\n /** Test seam: override the platform the endpoint shape is derived for. */\n platform?: NodeJS.Platform;\n}\n\nexport function getDefaultSessionHostEndpoint(\n appName = 'adhdev',\n options: SessionHostEndpointOptions = {},\n): SessionHostEndpoint {\n const rawKey = typeof options.ipcKey === 'string' ? options.ipcKey.trim() : '';\n // Fail closed on a malformed key: silently dropping it would collapse the\n // endpoint back onto the default instance's namespace — a cross-instance leak.\n if (rawKey && !/^[0-9a-f]{12}$/.test(rawKey)) {\n throw new Error(`Invalid session-host ipcKey \"${rawKey}\" — expected '' or a 12-hex-char instance key`);\n }\n const suffix = rawKey ? `-${rawKey}` : '';\n const platform = options.platform || process.platform;\n if (platform === 'win32') {\n return {\n kind: 'pipe',\n path: `\\\\\\\\.\\\\pipe\\\\${appName}-session-host${suffix}`,\n };\n }\n\n return {\n kind: 'unix',\n path: path.join(os.tmpdir(), `${appName}-session-host${suffix}.sock`),\n };\n}\n\nfunction serializeEnvelope(envelope: SessionHostWireEnvelope): string {\n return `${JSON.stringify(envelope)}\\n`;\n}\n\n/**\n * UTF8-CHUNK-BOUNDARY: decode the socket stream, never the individual chunk.\n *\n * This used to call `chunk.toString()` on every Buffer independently. A socket\n * hands us arbitrary byte slices — a multi-byte UTF-8 sequence straddling two\n * chunks gets decoded as two truncated sequences, and each half becomes U+FFFD.\n * The damage is silent by construction: the replacement character is valid JSON\n * string content, so the envelope still parses and only the *value* is wrong.\n * A pasted 11,994-char Korean prompt lost exactly one character that way — the\n * `도` beginning at wire offset 8190, split across the 8192-byte chunk boundary,\n * arrived as `�착`.\n *\n * StringDecoder holds an incomplete trailing sequence back until the bytes that\n * finish it arrive, which is the only correct way to turn a byte stream into\n * text. String chunks bypass it: they are already decoded, and feeding them\n * through `Buffer.from` would re-encode text the caller never asked us to touch.\n */\nfunction createLineParser(onEnvelope: (envelope: SessionHostWireEnvelope) => void) {\n let buffer = '';\n const decoder = new StringDecoder('utf8');\n const parser = (chunk: Buffer | string) => {\n buffer += typeof chunk === 'string' ? chunk : decoder.write(chunk);\n let newlineIndex = buffer.indexOf('\\n');\n while (newlineIndex >= 0) {\n const rawLine = buffer.slice(0, newlineIndex).trim();\n buffer = buffer.slice(newlineIndex + 1);\n if (rawLine) {\n onEnvelope(JSON.parse(rawLine) as SessionHostWireEnvelope);\n }\n newlineIndex = buffer.indexOf('\\n');\n }\n };\n /**\n * Flush at EOF, returning whatever text never formed a complete line.\n *\n * `decoder.end()` releases any bytes still held back as an incomplete UTF-8\n * sequence; without it those bytes vanish with the decoder. What is left in\n * the buffer is by definition a partial line — envelopes are newline-framed,\n * so an unterminated tail is a truncated transmission, not a message. It is\n * therefore RETURNED for the caller to log rather than parsed: calling\n * JSON.parse on a half-written envelope inside a socket EOF handler would\n * raise an uncaught exception in the daemon, turning a peer that died\n * mid-write into a crash of the process observing it.\n */\n parser.end = (): string => {\n const remainder = buffer + decoder.end();\n buffer = '';\n return remainder;\n };\n return parser;\n}\n\nexport interface SessionHostClientOptions {\n endpoint?: SessionHostEndpoint;\n appName?: string;\n}\n\n/**\n * Why an established session-host connection went away.\n * - `error` — socket emitted 'error' (includes ECONNRESET on abrupt host death)\n * - `ended` — peer sent FIN ('end'): the host closed the connection cleanly\n * - `closed` — socket fully closed ('close') without a preceding error/end\n */\nexport type SessionHostDisconnectReason = 'error' | 'ended' | 'closed';\n\n/**\n * Sockets that reached 'connect' at least once. Used to distinguish a genuine\n * disconnect (host died under us) from a connect attempt that never landed\n * (host not up yet) — only the former is a session-host death signal.\n */\nconst establishedSockets = new WeakSet<net.Socket>();\n\nexport interface SessionHostDisconnectInfo {\n reason: SessionHostDisconnectReason;\n endpointPath: string;\n /** In-flight requests abandoned by this disconnect (they were rejected). */\n pendingRequests: number;\n error?: Error;\n}\n\nexport class SessionHostClient {\n readonly endpoint: SessionHostEndpoint;\n\n private socket: net.Socket | null = null;\n private requestWaiters = new Map<string, { resolve: (value: SessionHostResponse) => void; reject: (error: Error) => void }>();\n private eventListeners = new Set<(event: SessionHostEvent) => void>();\n private disconnectListeners = new Set<(info: SessionHostDisconnectInfo) => void>();\n /** Guards against emitting both an 'error'-path and a 'close'-path disconnect for one socket. */\n private disconnectedSockets = new WeakSet<net.Socket>();\n\n constructor(options: SessionHostClientOptions = {}) {\n this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || 'adhdev');\n }\n\n /**\n * Fires when an established connection to the session host goes away.\n *\n * Before this existed the client registered only 'data' and 'error' handlers,\n * so a *clean* FIN — the host process exiting normally, or dying to an\n * uncaught exception that took the socket down without an error event on our\n * end — ran nothing at all: `this.socket` stayed non-null pointing at a dead\n * socket, in-flight waiters hung to their 30s timeout, and no caller was told.\n * That silence is what let a session-host death present downstream as an\n * unrelated application-level fault (a missing completion marker) instead of\n * \"the host is gone\".\n */\n onDisconnect(listener: (info: SessionHostDisconnectInfo) => void): () => void {\n this.disconnectListeners.add(listener);\n return () => {\n this.disconnectListeners.delete(listener);\n };\n }\n\n private handleDisconnect(socket: net.Socket, reason: SessionHostDisconnectReason, error?: Error): void {\n if (this.disconnectedSockets.has(socket)) return;\n this.disconnectedSockets.add(socket);\n\n // A connect that never succeeded is a *failed connect*, not a disconnect —\n // reporting it as a host death would make every poll against a not-yet-started\n // host look like a crash. connect() already surfaces that failure to its caller\n // via the awaited 'error'. Still clear socket state below? No: connect()'s own\n // error path handles a never-established socket, and `this.socket` is\n // overwritten on the next connect() attempt regardless.\n const wasEstablished = establishedSockets.has(socket);\n if (!wasEstablished) {\n if (this.socket === socket) this.socket = null;\n try { socket.destroy(); } catch { /* noop */ }\n // Reject waiters anyway — a request() that raced a dying socket must not hang.\n const failure = error || new Error(`Session host connect failed (${this.endpoint.path})`);\n for (const waiter of this.requestWaiters.values()) waiter.reject(failure);\n this.requestWaiters.clear();\n return;\n }\n\n const pendingRequests = this.requestWaiters.size;\n const failure = error || new Error(`Session host connection ${reason} (${this.endpoint.path})`);\n for (const waiter of this.requestWaiters.values()) {\n waiter.reject(failure);\n }\n this.requestWaiters.clear();\n\n // Clear the dead socket reference so the next connect() creates a fresh\n // connection instead of short-circuiting on the `!this.socket.destroyed`\n // guard at the top of connect().\n if (this.socket === socket) {\n this.socket = null;\n }\n try { socket.destroy(); } catch { /* noop */ }\n\n const info: SessionHostDisconnectInfo = {\n reason,\n endpointPath: this.endpoint.path,\n pendingRequests,\n error,\n };\n for (const listener of this.disconnectListeners) {\n // A throwing observer must not suppress the remaining ones, and must not\n // propagate out of a socket event handler (that would be an uncaught\n // exception in the daemon).\n try { listener(info); } catch { /* noop */ }\n }\n }\n\n async connect(): Promise<void> {\n if (this.socket && !this.socket.destroyed) return;\n // Cleanup stale socket reference left after error/disconnect\n if (this.socket) {\n try { this.socket.destroy(); } catch { /* noop */ }\n this.socket = null;\n }\n\n const socket = net.createConnection(this.endpoint.path);\n this.socket = socket;\n\n socket.on('data', createLineParser((envelope) => {\n if (envelope.kind === 'response') {\n const waiter = this.requestWaiters.get(envelope.requestId);\n if (waiter) {\n this.requestWaiters.delete(envelope.requestId);\n waiter.resolve(envelope.response);\n }\n return;\n }\n\n if (envelope.kind === 'event') {\n for (const listener of this.eventListeners) listener(envelope.event);\n }\n }));\n\n socket.on('error', (error) => {\n this.handleDisconnect(socket, 'error', error);\n });\n\n // 'close'/'end' are the clean-FIN counterparts of 'error'. The host server\n // has always handled both directions (server.ts registers close+end); this\n // side handled neither, so host death was observable only if it happened to\n // surface as a socket error.\n socket.on('close', () => {\n this.handleDisconnect(socket, 'closed');\n });\n socket.on('end', () => {\n this.handleDisconnect(socket, 'ended');\n });\n\n await new Promise<void>((resolve, reject) => {\n socket.once('connect', () => {\n establishedSockets.add(socket);\n resolve();\n });\n socket.once('error', reject);\n });\n }\n\n onEvent(listener: (event: SessionHostEvent) => void): () => void {\n this.eventListeners.add(listener);\n return () => {\n this.eventListeners.delete(listener);\n };\n }\n\n async request<T = unknown>(request: SessionHostRequest): Promise<SessionHostResponse<T>> {\n await this.connect();\n if (!this.socket) throw new Error('Session host socket unavailable');\n\n const requestId = generateUUID();\n const envelope: SessionHostRequestEnvelope = {\n kind: 'request',\n requestId,\n request,\n };\n\n const response = await new Promise<SessionHostResponse>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.requestWaiters.delete(requestId);\n reject(new Error(`Session host request timed out after 30s (${request.type})`));\n }, 30_000);\n this.requestWaiters.set(requestId, {\n resolve: (value) => { clearTimeout(timeout); resolve(value); },\n reject: (error) => { clearTimeout(timeout); reject(error); },\n });\n this.socket?.write(serializeEnvelope(envelope));\n });\n\n return response as SessionHostResponse<T>;\n }\n\n async close(): Promise<void> {\n if (!this.socket) return;\n const socket = this.socket;\n this.socket = null;\n // Deliberate teardown: suppress the 'close' handler's disconnect signal so\n // callers don't see an intentional close reported as a session-host death.\n this.disconnectedSockets.add(socket);\n for (const waiter of this.requestWaiters.values()) {\n waiter.reject(new Error('Session host client closed'));\n }\n this.requestWaiters.clear();\n await new Promise<void>((resolve) => {\n let settled = false;\n const done = () => {\n if (settled) return;\n settled = true;\n resolve();\n };\n socket.once('close', done);\n socket.end();\n socket.destroy();\n setTimeout(done, 50);\n });\n }\n}\n\nexport function createResponseEnvelope(requestId: string, response: SessionHostResponse): SessionHostResponseEnvelope {\n return {\n kind: 'response',\n requestId,\n response,\n };\n}\n\nexport function writeEnvelope(socket: Pick<net.Socket, 'write'>, envelope: SessionHostWireEnvelope): void {\n socket.write(serializeEnvelope(envelope));\n}\n\nexport { createLineParser };\n","import * as crypto from 'crypto';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\n/**\n * Instance identity derivation for the session-host IPC namespace.\n *\n * One machine can host several ADHDev installs (stable `~/.adhdev`, preview\n * `~/.adhdev-preview`, standalone `~/.adhdev-standalone`, or any explicit\n * ADHDEV_CONFIG_DIR). Each install is an *instance*: it owns its config dir\n * and everything mutable under it. The session-host socket/pipe must be\n * namespaced by that identity so two simultaneous instances can never attach\n * to, kill, or rebind each other's session host.\n *\n * This module is the single derivation rule, shared by:\n * - the parent daemons (via @adhdev/daemon-core's InstanceContext),\n * - the session-host-daemon child process (which only sees env vars),\n * - CLI attach/list clients.\n *\n * All three compute the endpoint from the same inputs (appName +\n * ADHDEV_CONFIG_DIR), so they always agree without extra plumbing.\n *\n * Compatibility contract: the DEFAULT instance (config dir canonicalizes to\n * `<home>/.adhdev`) yields an EMPTY ipc key, so its socket/pipe path stays\n * byte-for-byte identical to the pre-instance layout.\n */\n\nexport const DEFAULT_CONFIG_DIR_NAME = '.adhdev';\n\n/**\n * Resolve the instance config dir from the environment: ADHDEV_CONFIG_DIR\n * when set (trimmed), else `<homeDir>/.adhdev`. Mirrors daemon-core's\n * getConfigDir() source rule without the mkdir side effect.\n *\n * NOTE (track identity): the no-env fallback here stays the LEGACY stable\n * default `.adhdev` — session-host-core is a pure leaf package that\n * daemon-core depends on, so it cannot import daemon-core's track-identity\n * (dependency direction) and its separately-built dist never sees the\n * `__ADHDEV_BUILD_CHANNEL__` build stamp. This is safe because every tracked\n * runtime path pins ADHDEV_CONFIG_DIR explicitly: the daemon spawns the\n * session-host child with `env.ADHDEV_CONFIG_DIR = instance().configDir`\n * (managed-host.ts buildEnv), and the installer/service pins it for preview.\n * The track-aware no-env default lives in daemon-core (getConfigDir /\n * resolveInstanceContext), which is the authority for daemon-side paths.\n */\nexport function resolveInstanceConfigDir(\n env: NodeJS.ProcessEnv = process.env,\n homeDir: string = os.homedir(),\n): string {\n const override = typeof env.ADHDEV_CONFIG_DIR === 'string' ? env.ADHDEV_CONFIG_DIR.trim() : '';\n return override || path.join(homeDir, DEFAULT_CONFIG_DIR_NAME);\n}\n\n/**\n * Realpath the longest existing prefix of `resolved` and re-append the\n * non-existing remainder. Unlike a plain realpathSync, this is deterministic\n * whether or not the leaf exists yet (a config dir realpath'd only after its\n * first mkdir must not change the derived ipc key between processes).\n */\nfunction realpathLongestExistingPrefix(resolved: string): string {\n let current = resolved;\n const remainder: string[] = [];\n // Bounded walk: a path can have at most its own segment count of parents.\n for (let depth = 0; depth < 256; depth++) {\n try {\n const real = fs.realpathSync.native(current);\n return remainder.length === 0 ? real : path.join(real, ...remainder);\n } catch {\n const parent = path.dirname(current);\n if (parent === current) return resolved;\n remainder.unshift(path.basename(current));\n current = parent;\n }\n }\n return resolved;\n}\n\n/**\n * Canonical form of an instance path for identity comparison/hashing:\n * absolute, symlink-resolved (longest existing prefix), no trailing\n * separator, case-folded on win32.\n */\nexport function canonicalizeInstancePath(\n dir: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n const resolved = path.resolve(dir);\n const real = realpathLongestExistingPrefix(resolved).replace(/[\\\\/]+$/, '');\n return platform === 'win32' ? real.toLowerCase() : real;\n}\n\n/** True when `configDir` is canonically the default `<homeDir>/.adhdev`. */\nexport function isDefaultInstanceConfigDir(\n configDir: string,\n homeDir: string = os.homedir(),\n platform: NodeJS.Platform = process.platform,\n): boolean {\n return canonicalizeInstancePath(configDir, platform)\n === canonicalizeInstancePath(path.join(homeDir, DEFAULT_CONFIG_DIR_NAME), platform);\n}\n\n/**\n * Derive the session-host IPC namespace key for an instance config dir.\n *\n * '' for the default instance (legacy-compatible endpoint), otherwise a\n * 12-hex-char sha256 prefix of the canonical config dir — stable across\n * processes/machines for the same dir, collision-resistant between dirs,\n * and insensitive to symlink/spelling differences of the same dir.\n */\nexport function resolveSessionHostIpcKey(\n configDir: string,\n homeDir: string = os.homedir(),\n platform: NodeJS.Platform = process.platform,\n): string {\n if (isDefaultInstanceConfigDir(configDir, homeDir, platform)) return '';\n const canonical = canonicalizeInstancePath(configDir, platform);\n return crypto.createHash('sha256').update(canonical, 'utf8').digest('hex').slice(0, 12);\n}\n","/**\n * Shared PTY spawn environment utilities.\n *\n * Centralises npm/pnpm/yarn env variable stripping, terminal colour env\n * injection, and node-pty spawn-helper permission fixing.\n *\n * Used by daemon-core (provider-cli-adapter), session-host-daemon (runtime),\n * and daemon-cloud (session-host).\n */\n\nimport * as os from 'os';\nimport * as path from 'path';\n\n/**\n * Strip package-manager injected environment variables that can interfere\n * with child CLI processes and apply terminal colour defaults.\n */\nexport function sanitizeSpawnEnv(\n baseEnv: NodeJS.ProcessEnv,\n overrides?: Record<string, string>,\n): Record<string, string> {\n const env: Record<string, string> = {};\n const source = { ...baseEnv, ...(overrides || {}) } as NodeJS.ProcessEnv;\n\n for (const [key, value] of Object.entries(source)) {\n if (typeof value !== 'string') continue;\n env[key] = value;\n }\n\n for (const key of Object.keys(env)) {\n if (\n key === 'INIT_CWD'\n || key === 'npm_command'\n || key === 'npm_execpath'\n || key === 'npm_node_execpath'\n || key.startsWith('npm_')\n || key.startsWith('npm_config_')\n || key.startsWith('npm_package_')\n || key.startsWith('npm_lifecycle_')\n || key.startsWith('PNPM_')\n || key.startsWith('YARN_')\n || key.startsWith('BUN_')\n || key.startsWith('VSCODE_')\n || key.startsWith('ELECTRON_')\n ) {\n delete env[key];\n }\n }\n\n // Do not leak parent Codex session controls into child CLIs. Thread identity\n // breaks providerSessionId discovery/reconnect semantics, while the parent's\n // network-disabled sandbox flag prevents coordinator MCP children from\n // connecting back to the local ADHDev daemon.\n delete env.CODEX_THREAD_ID;\n delete env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE;\n delete env.CODEX_SANDBOX_NETWORK_DISABLED;\n delete env.NO_COLOR;\n delete env.COLOR;\n\n // Never forward an inherited terminal geometry. COLUMNS/LINES are advisory\n // overrides that a TUI trusts *in preference to* the real TTY size\n // (measured: with COLUMNS=17 exported, a child on a genuine 80-column PTY\n // reports `tput cols` = 17). The PTY here is sized independently from the\n // adapter's own geometry, so any inherited value is by definition unrelated\n // to the terminal the child was actually given.\n //\n // When the two disagree the child lays out for the phantom width and the\n // VT renders at the real one, so in-place repaints (`\\r`, absolute cursor\n // moves) land on the wrong row: content fragments across the buffer with a\n // blank gap between the stale head and the live tail. Observed on\n // antigravity-cli as a \"Running command\" spinner split into two rows 28\n // blank lines apart, which the spec's from_top/from_bottom sections then\n // sliced into separate body/footer values.\n //\n // Deleting them makes the child measure the TTY via ioctl(TIOCGWINSZ) —\n // the PTY's real size, which is exactly what the VT is sized to. node-pty\n // does not set these itself, so a clean parent env is already the normal\n // case; this only removes a value that leaked in from whatever launched\n // the daemon (interactive shell, tmux, CI runner, nested CLI).\n delete env.COLUMNS;\n delete env.LINES;\n\n // Do not leak a parent Claude Code session identity into spawned claude-cli\n // children. CLAUDE_CODE_CHILD_SESSION makes a spawned claude run as a nested\n // child that does NOT persist its ~/.claude/projects transcript, so the\n // native-source history reader finds no file and the live dashboard renders\n // empty. This bites when the daemon itself was launched from inside a Claude\n // Code session (the markers are inherited and forwarded to every child).\n // Unconditional: originally scoped to win32 (35b462c5) because the bug had\n // only been OBSERVED there at the time — the Windows daemon that surfaced it\n // happened to have been launched from inside a Claude Code session, and\n // macOS/Linux \"looked fine\" only because no one had hit that same launch\n // circumstance yet, not because the platform behaves differently. The\n // marker-inheritance mechanism itself was never platform-specific — nothing\n // about how Claude Code detects a nested child session depends on OS.\n // Confirmed reproduced identically on darwin (live coordinator measurement,\n // this task): a daemon started from inside a Claude Code session inherits\n // and forwards the same markers there too. A daemon-spawned claude should\n // always be a fresh top-level session, on every platform — exactly what the\n // original commit already called out as \"safe to make unconditional later.\"\n delete env.CLAUDECODE;\n delete env.CLAUDE_CODE_CHILD_SESSION;\n delete env.CLAUDE_CODE_ENTRYPOINT;\n delete env.CLAUDE_CODE_SESSION_ID;\n delete env.CLAUDE_CODE_EXECPATH;\n // CLAUDE_CODE_BRIDGE_SESSION_ID is the same kind of parent-identity marker\n // (observed grouped with CLAUDECODE/CLAUDE_CODE_SESSION_ID/\n // CLAUDE_CODE_CHILD_SESSION/CLAUDE_CODE_EXECPATH in the installed `claude`\n // binary's own env-var table, alongside a `buildBridgeReattachEnvFromState`\n // helper) — it identifies the parent's remote bridge/reattach session. Left\n // in place, a spawned child could try to reattach to the PARENT's remote\n // session instead of starting fresh, which is a worse variant of the same\n // \"not actually a fresh top-level session\" bug this block exists to prevent.\n delete env.CLAUDE_CODE_BRIDGE_SESSION_ID;\n\n applyTerminalColorEnv(env);\n return env;\n}\n\n/**\n * Apply preferred terminal colour environment variables.\n * Ensures TERM is set to xterm-256color and enables colour on Windows.\n */\nexport function applyTerminalColorEnv(env: Record<string, string>): void {\n if (env.NO_COLOR) return;\n\n if (!env.TERM || env.TERM === 'xterm-color') {\n env.TERM = 'xterm-256color';\n }\n if (!env.COLORTERM) env.COLORTERM = 'truecolor';\n\n if (process.platform === 'win32') {\n if (!env.FORCE_COLOR) env.FORCE_COLOR = '1';\n if (!env.CLICOLOR) env.CLICOLOR = '1';\n }\n}\n\n/**\n * Ensure node-pty's spawn-helper binary has execute permissions.\n *\n * npm's default umask can strip +x from the prebuilt spawn-helper on macOS/Linux,\n * causing EACCES when node-pty tries to fork. Best-effort fix.\n *\n * @param logFn Optional log callback for reporting the fix.\n */\nexport function ensureNodePtySpawnHelperPermissions(\n logFn?: (msg: string) => void,\n): void {\n if (os.platform() === 'win32') return;\n try {\n const fs = require('fs');\n const ptyDir = path.resolve(path.dirname(require.resolve('node-pty')), '..');\n const platformArch = `${os.platform()}-${os.arch()}`;\n const helper = path.join(ptyDir, 'prebuilds', platformArch, 'spawn-helper');\n if (fs.existsSync(helper)) {\n const stat = fs.statSync(helper);\n if (!(stat.mode & 0o111)) {\n fs.chmodSync(helper, stat.mode | 0o755);\n logFn?.(`Fixed spawn-helper permissions: ${helper}`);\n }\n }\n } catch {\n // best-effort: node-pty still works on most installs without this\n }\n}\n","import type {\n AcquireWritePayload,\n GetHostDiagnosticsPayload,\n PruneDuplicateSessionsPayload,\n ReleaseWritePayload,\n SessionHostDiagnostics,\n SessionHostPruneDuplicatesResult,\n SessionHostRecord,\n SessionHostRequestType,\n} from './types.js';\n\n/**\n * The 11-method session-host control-plane surface shared by the cloud and\n * standalone daemons. Both daemons used to carry a byte-for-byte copy of this\n * dispatch table plus the identical throw strings; this is the single source of\n * truth for that mapping. The wire `type` strings and error text here are the\n * daemon IPC contract and MUST match what the session-host daemon accepts.\n */\nexport interface SessionHostControlPlane {\n getDiagnostics(payload?: GetHostDiagnosticsPayload): Promise<SessionHostDiagnostics>;\n listSessions(): Promise<SessionHostRecord[]>;\n stopSession(sessionId: string): Promise<SessionHostRecord | null>;\n deleteSession(sessionId: string, opts?: { force?: boolean }): Promise<SessionHostRecord | null>;\n resumeSession(sessionId: string): Promise<SessionHostRecord | null>;\n restartSession(sessionId: string): Promise<SessionHostRecord | null>;\n sendSignal(sessionId: string, signal: string): Promise<SessionHostRecord | null>;\n forceDetachClient(sessionId: string, clientId: string): Promise<SessionHostRecord | null>;\n pruneDuplicateSessions(payload?: PruneDuplicateSessionsPayload): Promise<SessionHostPruneDuplicatesResult>;\n acquireWrite(payload: AcquireWritePayload): Promise<SessionHostRecord | null>;\n releaseWrite(payload: ReleaseWritePayload): Promise<SessionHostRecord | null>;\n}\n\n/**\n * Minimal request transport the control-plane factory needs. Each daemon injects\n * its own implementation:\n * - cloud reuses a single persistent SessionHostClient (with reconnect); its\n * `request` calls `ensureConnected()` then the shared client.\n * - standalone opens a fresh client per request and closes it in `finally`.\n *\n * The factory below owns ONLY the type/payload dispatch table; the connection\n * lifecycle and success/error unwrapping stay in each daemon's transport so the\n * observable behavior of both paths is preserved exactly.\n */\nexport interface SessionHostControlTransport {\n request<T>(type: SessionHostRequestType, payload: Record<string, unknown>): Promise<T>;\n}\n\n/**\n * Build the shared 11-method control-plane over an injected request transport.\n * The (type string, payload shape) pairs are verbatim what both daemons emitted\n * previously — do not reword them without matching the session-host daemon's\n * request handlers.\n */\nexport function createSessionHostControlPlane(\n transport: SessionHostControlTransport,\n): SessionHostControlPlane {\n return {\n getDiagnostics(payload: GetHostDiagnosticsPayload = {}): Promise<SessionHostDiagnostics> {\n return transport.request<SessionHostDiagnostics>('get_host_diagnostics', payload as Record<string, unknown>);\n },\n listSessions(): Promise<SessionHostRecord[]> {\n return transport.request<SessionHostRecord[]>('list_sessions', {});\n },\n stopSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('stop_session', { sessionId });\n },\n deleteSession(sessionId: string, opts: { force?: boolean } = {}): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('delete_session', { sessionId, force: opts.force === true });\n },\n resumeSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('resume_session', { sessionId });\n },\n restartSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('restart_session', { sessionId });\n },\n sendSignal(sessionId: string, signal: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('send_signal', { sessionId, signal });\n },\n forceDetachClient(sessionId: string, clientId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('force_detach_client', { sessionId, clientId });\n },\n pruneDuplicateSessions(payload: PruneDuplicateSessionsPayload = {}): Promise<SessionHostPruneDuplicatesResult> {\n return transport.request<SessionHostPruneDuplicatesResult>('prune_duplicate_sessions', payload as Record<string, unknown>);\n },\n acquireWrite(payload: AcquireWritePayload): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('acquire_write', payload as unknown as Record<string, unknown>);\n },\n releaseWrite(payload: ReleaseWritePayload): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('release_write', payload as unknown as Record<string, unknown>);\n },\n };\n}\n"],"mappings":"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","names":["path","generateUUID","platform","failure","resolve","crypto","os","path","platform","os","path","fs"]}
1
+ {"version":3,"sources":["../src/types.ts","../src/termination.ts","../src/buffer.ts","../src/defaults.ts","../src/runtime-labels.ts","../src/registry.ts","../src/ipc.ts","../src/instance-key.ts","../src/spawn-env.ts","../src/control-plane.ts"],"sourcesContent":["export type SessionTransport = 'pty';\n\nexport type SessionHostCategory = 'cli' | 'acp' | 'shell';\n\nexport type SessionLifecycle = 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted';\n\nexport type SessionClientType = 'daemon' | 'web' | 'local-terminal';\n\nexport type SessionOwnerType = 'agent' | 'user';\n\nexport interface SessionLaunchCommand {\n command: string;\n args: string[];\n env?: Record<string, string>;\n}\n\nexport interface SessionWriteOwner {\n clientId: string;\n ownerType: SessionOwnerType;\n acquiredAt: number;\n}\n\nexport interface SessionAttachedClient {\n clientId: string;\n type: SessionClientType;\n readOnly: boolean;\n attachedAt: number;\n lastSeenAt: number;\n}\n\nexport interface SessionBufferSnapshot {\n seq: number;\n text: string;\n truncated: boolean;\n cols?: number;\n rows?: number;\n}\n\nexport interface SessionTerminalState {\n cursor: { row: number; col: number };\n altScreen: boolean;\n pasteMode: boolean;\n rawMode: boolean;\n scrollRegion: { top: number; bot: number };\n cols: number;\n rows: number;\n}\n\nexport interface SessionTerminalSnapshot {\n text: string;\n state: SessionTerminalState;\n}\n\nexport interface SessionBufferState {\n scrollbackBytes: number;\n snapshotSeq: number;\n}\n\nexport type SessionHostSurfaceKind = 'live_runtime' | 'recovery_snapshot' | 'inactive_record';\n\n/**\n * Compact, secret-free record of how a session's PTY process terminated.\n *\n * Preserves the nullable/unknown exitCode and signal exactly as observed from\n * the first node-pty `onExit` — an `exitCode` of `null` means \"unknown\" (the\n * process was terminated by a signal, or the exit code was otherwise not\n * reported) and MUST NOT be collapsed to 0. Persisted as a tombstone so\n * termination diagnostics remain inspectable after the live runtime and its\n * transport adapter are torn down.\n */\nexport interface SessionTermination {\n /** Exit code exactly as observed; `null` = unknown / signal-terminated. Never fabricated to 0. */\n exitCode: number | null;\n /** Signal number that terminated the process, if any; `null` when not signalled. */\n signal: number | null;\n /** Classification derived from (exitCode, signal): clean exit vs. failure vs. unknown. */\n reason: 'exit' | 'signal' | 'failed' | 'unknown';\n /** Lifecycle recorded for this termination (`stopped` | `failed`). */\n lifecycle: Extract<SessionLifecycle, 'stopped' | 'failed'>;\n /** Wall-clock time the termination was observed. */\n terminatedAt: number;\n /** OS pid of the terminated process, if it was known. */\n osPid?: number;\n /** Lifecycle the session was in immediately before termination. */\n previousLifecycle?: SessionLifecycle;\n /** Last time output was observed from the session, if known. */\n lastOutputAt?: number;\n /** Whether the termination followed an explicit stop/delete request, and which. */\n requestedStop?: 'stop' | 'delete' | 'restart' | 'prune';\n}\n\nexport interface SessionHostRecord {\n sessionId: string;\n runtimeKey: string;\n displayName: string;\n workspaceLabel: string;\n transport: SessionTransport;\n providerType: string;\n category: SessionHostCategory;\n workspace: string;\n launchCommand: SessionLaunchCommand;\n osPid?: number;\n createdAt: number;\n startedAt?: number;\n lastActivityAt: number;\n lifecycle: SessionLifecycle;\n surfaceKind?: SessionHostSurfaceKind;\n writeOwner: SessionWriteOwner | null;\n attachedClients: SessionAttachedClient[];\n buffer: SessionBufferState;\n meta: Record<string, unknown>;\n /** Termination tombstone, stamped when the PTY process exits. Retained for post-mortem inspection. */\n termination?: SessionTermination;\n}\n\nexport interface CreateSessionPayload {\n sessionId?: string;\n runtimeKey?: string;\n displayName?: string;\n providerType: string;\n category: SessionHostCategory;\n workspace: string;\n launchCommand: SessionLaunchCommand;\n cols?: number;\n rows?: number;\n clientId?: string;\n clientType?: SessionClientType;\n meta?: Record<string, unknown>;\n}\n\nexport interface AttachSessionPayload {\n sessionId: string;\n clientId: string;\n clientType: SessionClientType;\n readOnly?: boolean;\n}\n\nexport interface DetachSessionPayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface SendInputPayload {\n sessionId: string;\n clientId: string;\n data: string;\n}\n\nexport interface ResizeSessionPayload {\n sessionId: string;\n cols: number;\n rows: number;\n}\n\nexport interface StopSessionPayload {\n sessionId: string;\n}\n\nexport interface DeleteSessionPayload {\n sessionId: string;\n /** Stop a live runtime before deleting the persisted record. */\n force?: boolean;\n}\n\nexport interface ResumeSessionPayload {\n sessionId: string;\n}\n\nexport interface AcquireWritePayload {\n sessionId: string;\n clientId: string;\n ownerType: SessionOwnerType;\n force?: boolean;\n}\n\nexport interface ReleaseWritePayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface GetSnapshotPayload {\n sessionId: string;\n sinceSeq?: number;\n}\n\nexport interface GetTerminalSnapshotPayload {\n sessionId: string;\n}\n\nexport interface ClearSessionBufferPayload {\n sessionId: string;\n}\n\nexport interface UpdateSessionMetaPayload {\n sessionId: string;\n meta: Record<string, unknown>;\n replace?: boolean;\n}\n\nexport interface GetHostDiagnosticsPayload {\n includeSessions?: boolean;\n limit?: number;\n}\n\nexport interface ForceDetachClientPayload {\n sessionId: string;\n clientId: string;\n}\n\nexport interface SendSignalPayload {\n sessionId: string;\n signal: string;\n}\n\nexport interface RestartSessionPayload {\n sessionId: string;\n}\n\nexport interface PruneDuplicateSessionsPayload {\n providerType?: string;\n workspace?: string;\n dryRun?: boolean;\n}\n\nexport interface SessionHostDuplicateSessionGroup {\n bindingKey: string;\n providerType: string;\n workspace: string;\n providerSessionId: string;\n keptSessionId: string;\n prunedSessionIds: string[];\n}\n\nexport interface SessionHostPruneDuplicatesResult {\n duplicateGroupCount: number;\n keptSessionIds: string[];\n prunedSessionIds: string[];\n groups: SessionHostDuplicateSessionGroup[];\n}\n\nexport interface SessionHostLogEntry {\n timestamp: number;\n level: 'debug' | 'info' | 'warn' | 'error';\n message: string;\n sessionId?: string;\n data?: Record<string, unknown>;\n}\n\nexport interface SessionHostRequestTrace {\n timestamp: number;\n requestId: string;\n type: SessionHostRequest['type'];\n sessionId?: string;\n clientId?: string;\n success: boolean;\n durationMs: number;\n error?: string;\n}\n\nexport interface SessionHostRuntimeTransition {\n timestamp: number;\n sessionId: string;\n action: string;\n lifecycle?: SessionLifecycle;\n detail?: string;\n success?: boolean;\n error?: string;\n}\n\nexport interface SessionHostDiagnostics {\n hostStartedAt: number;\n endpoint: string;\n /**\n * Absolute path of the script this host process is running (its own\n * `__filename`), self-reported.\n *\n * This is the only way a client can learn WHICH install a reachable host came\n * from without process inspection. On Windows the daemon's PowerShell/wmic\n * command-line probe is blocked outright on some boxes (AV/EDR, and `wmic` is\n * removed by default in Win11 24H2+), so identity checks that depend on it\n * fail silently — which let a host from a DELETED install prefix keep serving\n * its socket while every `create_session` died loading node-pty's conpty.node\n * from the missing tree. The host answering with its own path needs no\n * privileges and always works.\n *\n * Optional: an older host predating this field simply omits it, and callers\n * must treat `undefined` as \"unknown\", never as a match.\n */\n hostEntryPath?: string;\n runtimeCount: number;\n supportedRequestTypes?: SessionHostRequestType[];\n sessions?: SessionHostRecord[];\n liveRuntimes?: SessionHostRecord[];\n recoverySnapshots?: SessionHostRecord[];\n inactiveRecords?: SessionHostRecord[];\n recentLogs: SessionHostLogEntry[];\n recentRequests: SessionHostRequestTrace[];\n recentTransitions: SessionHostRuntimeTransition[];\n}\n\nexport type SessionHostRequest =\n | { type: 'create_session'; payload: CreateSessionPayload }\n | { type: 'attach_session'; payload: AttachSessionPayload }\n | { type: 'detach_session'; payload: DetachSessionPayload }\n | { type: 'send_input'; payload: SendInputPayload }\n | { type: 'resize_session'; payload: ResizeSessionPayload }\n | { type: 'stop_session'; payload: StopSessionPayload }\n | { type: 'delete_session'; payload: DeleteSessionPayload }\n | { type: 'resume_session'; payload: ResumeSessionPayload }\n | { type: 'acquire_write'; payload: AcquireWritePayload }\n | { type: 'release_write'; payload: ReleaseWritePayload }\n | { type: 'get_snapshot'; payload: GetSnapshotPayload }\n | { type: 'get_terminal_snapshot'; payload: GetTerminalSnapshotPayload }\n | { type: 'clear_session_buffer'; payload: ClearSessionBufferPayload }\n | { type: 'update_session_meta'; payload: UpdateSessionMetaPayload }\n | { type: 'get_host_diagnostics'; payload?: GetHostDiagnosticsPayload }\n | { type: 'force_detach_client'; payload: ForceDetachClientPayload }\n | { type: 'send_signal'; payload: SendSignalPayload }\n | { type: 'restart_session'; payload: RestartSessionPayload }\n | { type: 'prune_duplicate_sessions'; payload?: PruneDuplicateSessionsPayload }\n | { type: 'list_sessions'; payload?: {} };\n\nexport type SessionHostRequestType = SessionHostRequest['type'];\n\nexport const SESSION_HOST_SUPPORTED_REQUEST_TYPES: readonly SessionHostRequestType[] = [\n 'create_session',\n 'attach_session',\n 'detach_session',\n 'send_input',\n 'resize_session',\n 'stop_session',\n 'delete_session',\n 'resume_session',\n 'acquire_write',\n 'release_write',\n 'get_snapshot',\n 'get_terminal_snapshot',\n 'clear_session_buffer',\n 'update_session_meta',\n 'get_host_diagnostics',\n 'force_detach_client',\n 'send_signal',\n 'restart_session',\n 'prune_duplicate_sessions',\n 'list_sessions',\n];\n\nexport interface SessionHostResponse<T = unknown> {\n success: boolean;\n result?: T;\n error?: string;\n}\n\nexport type SessionHostEvent =\n | { type: 'session_created'; sessionId: string; record: SessionHostRecord }\n | { type: 'session_started'; sessionId: string; pid?: number }\n | { type: 'session_resumed'; sessionId: string; pid?: number }\n | { type: 'session_output'; sessionId: string; seq: number; data: string }\n | { type: 'session_cleared'; sessionId: string }\n | { type: 'session_exit'; sessionId: string; exitCode: number | null; signal?: number | null; termination?: SessionTermination }\n | { type: 'session_stopped'; sessionId: string }\n | { type: 'session_deleted'; sessionId: string }\n | { type: 'session_resized'; sessionId: string; cols: number; rows: number }\n | { type: 'write_owner_changed'; sessionId: string; owner: SessionWriteOwner | null }\n | { type: 'client_attached'; sessionId: string; client: SessionAttachedClient }\n | { type: 'client_detached'; sessionId: string; clientId: string }\n | { type: 'host_log'; entry: SessionHostLogEntry }\n | { type: 'request_trace'; trace: SessionHostRequestTrace }\n | { type: 'runtime_transition'; transition: SessionHostRuntimeTransition };\n\nexport interface SessionHostRequestEnvelope {\n kind: 'request';\n requestId: string;\n request: SessionHostRequest;\n}\n\nexport interface SessionHostResponseEnvelope {\n kind: 'response';\n requestId: string;\n response: SessionHostResponse;\n}\n\nexport interface SessionHostEventEnvelope {\n kind: 'event';\n event: SessionHostEvent;\n}\n\nexport type SessionHostWireEnvelope =\n | SessionHostRequestEnvelope\n | SessionHostResponseEnvelope\n | SessionHostEventEnvelope;\n","import type { SessionLifecycle, SessionTermination } from './types.js';\n\nexport interface ClassifyTerminationInput {\n exitCode: number | null;\n signal?: number | null;\n osPid?: number;\n previousLifecycle?: SessionLifecycle;\n lastOutputAt?: number;\n requestedStop?: SessionTermination['requestedStop'];\n terminatedAt: number;\n}\n\n/**\n * Classify a PTY termination from its raw (exitCode, signal) pair.\n *\n * Invariants:\n * - A `null` exitCode is UNKNOWN, never a clean exit — it MUST NOT collapse to\n * exit 0. This is the regression guard for the old `exitCode ?? 0` behavior.\n * - A non-null, non-zero exitCode or any signal is a failure.\n * - Only an explicit exit code of 0 (with no signal) is a clean stop.\n *\n * The returned lifecycle is `stopped` only for a clean exit 0; every other\n * case (nonzero, signalled, or unknown) is `failed` so that an unknown\n * termination stays distinguishable from a successful one.\n */\nexport function classifyTermination(input: ClassifyTerminationInput): SessionTermination {\n const { exitCode, terminatedAt } = input;\n const signal = input.signal ?? null;\n\n let reason: SessionTermination['reason'];\n let lifecycle: SessionTermination['lifecycle'];\n\n if (signal !== null && signal !== 0) {\n reason = 'signal';\n lifecycle = 'failed';\n } else if (exitCode === null) {\n reason = 'unknown';\n lifecycle = 'failed';\n } else if (exitCode === 0) {\n reason = 'exit';\n lifecycle = 'stopped';\n } else {\n reason = 'failed';\n lifecycle = 'failed';\n }\n\n const termination: SessionTermination = {\n exitCode,\n signal,\n reason,\n lifecycle,\n terminatedAt,\n };\n if (typeof input.osPid === 'number') termination.osPid = input.osPid;\n if (input.previousLifecycle) termination.previousLifecycle = input.previousLifecycle;\n if (typeof input.lastOutputAt === 'number') termination.lastOutputAt = input.lastOutputAt;\n if (input.requestedStop) termination.requestedStop = input.requestedStop;\n return termination;\n}\n","import type { SessionBufferSnapshot } from './types.js';\n\nexport interface SessionRingBufferOptions {\n maxBytes?: number;\n}\n\n// Manual \"Load older terminal output\" replays this raw ring buffer into the\n// browser terminal. 512KiB was too small for long Claude/Codex conversations:\n// by the time the user scrolled to the top and clicked the loader, the output\n// they were trying to recover was often already trimmed.\nexport const DEFAULT_SESSION_RING_BUFFER_MAX_BYTES = 4 * 1024 * 1024;\n\nexport class SessionRingBuffer {\n private maxBytes: number;\n private chunks: { seq: number; data: string; bytes: number }[] = [];\n private nextSeq = 1;\n private totalBytes = 0;\n\n constructor(options: SessionRingBufferOptions = {}) {\n this.maxBytes = options.maxBytes ?? DEFAULT_SESSION_RING_BUFFER_MAX_BYTES;\n }\n\n append(data: string): number {\n const normalized = typeof data === 'string' ? data : String(data ?? '');\n const bytes = Buffer.byteLength(normalized, 'utf8');\n const seq = this.nextSeq++;\n\n this.chunks.push({ seq, data: normalized, bytes });\n this.totalBytes += bytes;\n this.trim();\n return seq;\n }\n\n snapshot(sinceSeq?: number): SessionBufferSnapshot {\n const relevant = typeof sinceSeq === 'number'\n ? this.chunks.filter(chunk => chunk.seq > sinceSeq)\n : this.chunks;\n\n const text = relevant.map(chunk => chunk.data).join('');\n const truncated = !!this.chunks[0] && typeof sinceSeq === 'number' && sinceSeq < this.chunks[0].seq - 1;\n\n return {\n seq: this.nextSeq - 1,\n text,\n truncated,\n };\n }\n\n getState(): { scrollbackBytes: number; snapshotSeq: number } {\n return {\n scrollbackBytes: this.totalBytes,\n snapshotSeq: this.nextSeq - 1,\n };\n }\n\n clear(): void {\n this.chunks = [];\n this.totalBytes = 0;\n this.nextSeq = 1;\n }\n\n restore(snapshot: { seq: number; text: string }): void {\n this.clear();\n const text = String(snapshot.text || '');\n if (!text) {\n this.nextSeq = Math.max(1, Number(snapshot.seq || 0) + 1);\n return;\n }\n const bytes = Buffer.byteLength(text, 'utf8');\n const seq = Math.max(1, Number(snapshot.seq || 1));\n this.chunks = [{ seq, data: text, bytes }];\n this.totalBytes = bytes;\n this.nextSeq = seq + 1;\n this.trim();\n }\n\n private trim(): void {\n while (this.totalBytes > this.maxBytes && this.chunks.length > 1) {\n const removed = this.chunks.shift();\n if (!removed) break;\n this.totalBytes -= removed.bytes;\n }\n }\n}\n","export const DEFAULT_SESSION_HOST_COLS = 80;\nexport const DEFAULT_SESSION_HOST_ROWS = 32;\n\nfunction normalizeSessionHostDimension(value: number | undefined, fallback: number): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;\n const rounded = Math.floor(value);\n return rounded > 0 ? rounded : fallback;\n}\n\nexport function resolveSessionHostCols(value: number | undefined): number {\n return normalizeSessionHostDimension(value, DEFAULT_SESSION_HOST_COLS);\n}\n\nexport function resolveSessionHostRows(value: number | undefined): number {\n return normalizeSessionHostDimension(value, DEFAULT_SESSION_HOST_ROWS);\n}\n","import * as path from 'path';\nimport type { CreateSessionPayload, SessionHostRecord, SessionHostSurfaceKind } from './types.js';\n\nfunction normalizeSlug(input: string): string {\n return input\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 48);\n}\n\nfunction normalizeValue(input: string): string {\n return input.trim().toLowerCase();\n}\n\nexport function getWorkspaceLabel(workspace: string): string {\n const trimmed = workspace.trim();\n if (!trimmed) return 'workspace';\n const normalized = trimmed.replace(/[\\\\/]+$/, '');\n const base = path.basename(normalized);\n return base || normalized;\n}\n\nexport function buildRuntimeDisplayName(payload: Pick<CreateSessionPayload, 'displayName' | 'providerType' | 'workspace'>): string {\n const explicit = payload.displayName?.trim();\n if (explicit) return explicit;\n const workspaceLabel = getWorkspaceLabel(payload.workspace);\n const providerLabel = payload.providerType.trim() || 'runtime';\n return `${providerLabel} @ ${workspaceLabel}`;\n}\n\nexport function buildRuntimeKey(\n payload: Pick<CreateSessionPayload, 'runtimeKey' | 'displayName' | 'providerType' | 'workspace'>,\n existingKeys: Iterable<string>,\n): string {\n const requested = payload.runtimeKey?.trim();\n const existing = new Set(Array.from(existingKeys, (key) => key.toLowerCase()));\n const displayName = buildRuntimeDisplayName(payload);\n const baseKey = normalizeSlug(requested || displayName || getWorkspaceLabel(payload.workspace) || payload.providerType || 'runtime') || 'runtime';\n if (!existing.has(baseKey)) return baseKey;\n\n let suffix = 2;\n let candidate = `${baseKey}-${suffix}`;\n while (existing.has(candidate)) {\n suffix += 1;\n candidate = `${baseKey}-${suffix}`;\n }\n return candidate;\n}\n\nexport interface SessionHostSurfaceRecordLike {\n lifecycle?: string | null;\n surfaceKind?: SessionHostSurfaceKind | null;\n meta?: Record<string, unknown> | null;\n termination?: unknown;\n}\n\nconst LIVE_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'interrupted']);\nconst TERMINAL_LIFECYCLES = new Set(['stopped', 'failed']);\n\n/**\n * True when a record has reached a terminal state: either its lifecycle is\n * terminal, or it carries a termination stamp (which the exit handler writes\n * alongside the lifecycle, and which can outlive a stale 'stopping' value).\n *\n * Used both to keep terminated records from being persisted as live files and\n * to keep them from surfacing as attach targets.\n */\nexport function isTerminalRecord(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.termination) return true;\n return TERMINAL_LIFECYCLES.has(String(record.lifecycle || '').trim());\n}\n\nexport function isSessionHostLiveRuntime(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.surfaceKind === 'live_runtime') return true;\n if (record.surfaceKind === 'recovery_snapshot' || record.surfaceKind === 'inactive_record') return false;\n // A record stamped with a termination is terminal even if its lifecycle is\n // still 'stopping' — e.g. a stale file recreated by a late persist that\n // raced the exit/cleanup path. Without this, such records surface as a\n // live runtime (attach target) for a PID that already exited.\n if (record.termination) return false;\n const lifecycle = String(record.lifecycle || '').trim();\n return LIVE_LIFECYCLES.has(lifecycle);\n}\n\nexport function getSessionHostRecoveryLabel(meta: Record<string, unknown> | null | undefined): string | null {\n const recoveryState = typeof meta?.runtimeRecoveryState === 'string'\n ? String(meta.runtimeRecoveryState).trim()\n : '';\n if (!recoveryState) return null;\n if (recoveryState === 'auto_resumed') return 'restored after restart';\n if (recoveryState === 'resume_failed') return 'restore failed';\n if (recoveryState === 'host_restart_interrupted') return 'host restart interrupted';\n if (recoveryState === 'orphan_snapshot') return 'snapshot recovered';\n return recoveryState.replace(/_/g, ' ');\n}\n\nexport function isSessionHostRecoverySnapshot(record: SessionHostSurfaceRecordLike | null | undefined): boolean {\n if (!record) return false;\n if (record.surfaceKind === 'recovery_snapshot') return true;\n if (record.surfaceKind === 'live_runtime' || record.surfaceKind === 'inactive_record') return false;\n if (isSessionHostLiveRuntime(record)) return false;\n\n const lifecycle = String(record.lifecycle || '').trim();\n if (lifecycle && lifecycle !== 'stopped' && lifecycle !== 'failed') {\n return false;\n }\n\n const meta = record.meta || undefined;\n if (meta?.restoredFromStorage === true) return true;\n return getSessionHostRecoveryLabel(meta) !== null;\n}\n\nexport function getSessionHostSurfaceKind(record: SessionHostSurfaceRecordLike | null | undefined): SessionHostSurfaceKind {\n if (record?.surfaceKind === 'live_runtime' || record?.surfaceKind === 'recovery_snapshot' || record?.surfaceKind === 'inactive_record') {\n return record.surfaceKind;\n }\n if (isSessionHostLiveRuntime(record)) return 'live_runtime';\n if (isSessionHostRecoverySnapshot(record)) return 'recovery_snapshot';\n return 'inactive_record';\n}\n\nexport function partitionSessionHostRecords<T extends SessionHostSurfaceRecordLike>(records: T[]): {\n liveRuntimes: T[];\n recoverySnapshots: T[];\n inactiveRecords: T[];\n} {\n const liveRuntimes: T[] = [];\n const recoverySnapshots: T[] = [];\n const inactiveRecords: T[] = [];\n\n for (const record of records) {\n const kind = getSessionHostSurfaceKind(record);\n if (kind === 'live_runtime') {\n liveRuntimes.push(record);\n } else if (kind === 'recovery_snapshot') {\n recoverySnapshots.push(record);\n } else {\n inactiveRecords.push(record);\n }\n }\n\n return {\n liveRuntimes,\n recoverySnapshots,\n inactiveRecords,\n };\n}\n\nexport function partitionSessionHostDiagnosticsSessions(\n records: SessionHostRecord[] | null | undefined,\n): ReturnType<typeof partitionSessionHostRecords<SessionHostRecord>> {\n return partitionSessionHostRecords(records || []);\n}\n\nexport function resolveAttachableRuntimeRecord(records: SessionHostRecord[], identifier: string): SessionHostRecord {\n const record = resolveRuntimeRecord(records, identifier);\n const surfaceKind = getSessionHostSurfaceKind(record);\n if (surfaceKind === 'live_runtime') {\n return record;\n }\n if (surfaceKind === 'recovery_snapshot') {\n throw new Error(`Runtime ${record.runtimeKey} is a recovery snapshot, not a live attach target. Resume or recover it first.`);\n }\n throw new Error(`Runtime ${record.runtimeKey} is ${record.lifecycle}, not a live attach target.`);\n}\n\nfunction uniqueMatch(records: SessionHostRecord[], predicate: (record: SessionHostRecord) => boolean): SessionHostRecord | null {\n const matches = records.filter(predicate);\n if (matches.length === 1) return matches[0] || null;\n if (matches.length === 0) return null;\n const labels = matches.map((record) => `${record.runtimeKey} (${record.sessionId})`).join(', ');\n throw new Error(`Ambiguous runtime target. Matches: ${labels}`);\n}\n\nexport function resolveRuntimeRecord(records: SessionHostRecord[], identifier: string): SessionHostRecord {\n const target = identifier.trim();\n if (!target) {\n throw new Error('Runtime target is required');\n }\n\n const exact = uniqueMatch(records, (record) =>\n record.sessionId === target ||\n normalizeValue(record.runtimeKey) === normalizeValue(target) ||\n normalizeValue(record.displayName) === normalizeValue(target),\n );\n if (exact) return exact;\n\n const prefix = uniqueMatch(records, (record) =>\n record.sessionId.startsWith(target) ||\n normalizeValue(record.runtimeKey).startsWith(normalizeValue(target)),\n );\n if (prefix) return prefix;\n\n throw new Error(`Unknown runtime target: ${target}`);\n}\n\nexport function formatRuntimeOwner(record: Pick<SessionHostRecord, 'writeOwner'>): string {\n if (!record.writeOwner) return 'none';\n return `${record.writeOwner.ownerType}:${record.writeOwner.clientId}`;\n}\n","function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\nimport type {\n AcquireWritePayload,\n AttachSessionPayload,\n CreateSessionPayload,\n DetachSessionPayload,\n ReleaseWritePayload,\n SessionAttachedClient,\n SessionHostRecord,\n SessionTermination,\n} from './types.js';\nimport { SessionRingBuffer } from './buffer.js';\nimport { resolveSessionHostCols, resolveSessionHostRows } from './defaults.js';\nimport { buildRuntimeDisplayName, buildRuntimeKey, getWorkspaceLabel } from './runtime-labels.js';\n\ninterface SessionRuntimeState {\n record: SessionHostRecord;\n buffer: SessionRingBuffer;\n}\n\nexport class SessionHostRegistry {\n private sessions = new Map<string, SessionRuntimeState>();\n\n createSession(payload: CreateSessionPayload): SessionHostRecord {\n const sessionId = payload.sessionId || generateUUID();\n if (this.sessions.has(sessionId)) {\n throw new Error(`Session already exists: ${sessionId}`);\n }\n const now = Date.now();\n const initialClient = payload.clientId\n ? [{\n clientId: payload.clientId,\n type: payload.clientType || 'daemon',\n readOnly: false,\n attachedAt: now,\n lastSeenAt: now,\n } satisfies SessionAttachedClient]\n : [];\n\n const record: SessionHostRecord = {\n sessionId,\n runtimeKey: buildRuntimeKey(\n payload,\n Array.from(this.sessions.values(), (state) => state.record.runtimeKey),\n ),\n displayName: buildRuntimeDisplayName(payload),\n workspaceLabel: getWorkspaceLabel(payload.workspace),\n transport: 'pty',\n providerType: payload.providerType,\n category: payload.category,\n workspace: payload.workspace,\n launchCommand: payload.launchCommand,\n createdAt: now,\n lastActivityAt: now,\n lifecycle: 'starting',\n writeOwner: null,\n attachedClients: initialClient,\n buffer: {\n scrollbackBytes: 0,\n snapshotSeq: 0,\n },\n meta: payload.meta || {},\n };\n\n record.meta = {\n sessionHostCols: resolveSessionHostCols(payload.cols),\n sessionHostRows: resolveSessionHostRows(payload.rows),\n ...record.meta,\n };\n\n this.sessions.set(sessionId, {\n record,\n buffer: new SessionRingBuffer(),\n });\n\n return this.cloneRecord(record);\n }\n\n restoreSession(record: SessionHostRecord, snapshot?: { seq: number; text: string } | null): SessionHostRecord {\n const cloned = this.cloneRecord(record);\n this.sessions.set(cloned.sessionId, {\n record: cloned,\n buffer: (() => {\n const buffer = new SessionRingBuffer();\n if (snapshot) buffer.restore(snapshot);\n return buffer;\n })(),\n });\n return this.cloneRecord(cloned);\n }\n\n listSessions(): SessionHostRecord[] {\n return Array.from(this.sessions.values())\n .map(state => this.cloneRecord(state.record))\n .sort((a, b) => b.lastActivityAt - a.lastActivityAt);\n }\n\n getSession(sessionId: string): SessionHostRecord | null {\n const state = this.sessions.get(sessionId);\n return state ? this.cloneRecord(state.record) : null;\n }\n\n attachClient(payload: AttachSessionPayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n const now = Date.now();\n let removedDaemonOwner = false;\n\n if (payload.clientType === 'daemon') {\n const staleDaemonClientIds = state.record.attachedClients\n .filter(client => client.type === 'daemon' && client.clientId !== payload.clientId)\n .map(client => client.clientId);\n if (staleDaemonClientIds.length > 0) {\n state.record.attachedClients = state.record.attachedClients.filter(\n client => !(client.type === 'daemon' && client.clientId !== payload.clientId),\n );\n if (state.record.writeOwner && staleDaemonClientIds.includes(state.record.writeOwner.clientId)) {\n removedDaemonOwner = true;\n }\n }\n }\n\n const existing = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n\n if (existing) {\n existing.type = payload.clientType;\n existing.readOnly = !!payload.readOnly;\n existing.lastSeenAt = now;\n } else {\n state.record.attachedClients.push({\n clientId: payload.clientId,\n type: payload.clientType,\n readOnly: !!payload.readOnly,\n attachedAt: now,\n lastSeenAt: now,\n });\n }\n\n if (removedDaemonOwner) {\n state.record.writeOwner = null;\n }\n\n state.record.lastActivityAt = now;\n return this.cloneRecord(state.record);\n }\n\n detachClient(payload: DetachSessionPayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n state.record.attachedClients = state.record.attachedClients.filter(client => client.clientId !== payload.clientId);\n if (state.record.writeOwner?.clientId === payload.clientId) {\n state.record.writeOwner = null;\n }\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n acquireWrite(payload: AcquireWritePayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n if (state.record.writeOwner && state.record.writeOwner.clientId !== payload.clientId && !payload.force) {\n throw new Error(`Write owned by ${state.record.writeOwner.clientId}`);\n }\n const attachedClient = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n if (attachedClient) {\n attachedClient.readOnly = false;\n attachedClient.lastSeenAt = Date.now();\n }\n state.record.writeOwner = {\n clientId: payload.clientId,\n ownerType: payload.ownerType,\n acquiredAt: Date.now(),\n };\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n releaseWrite(payload: ReleaseWritePayload): SessionHostRecord {\n const state = this.requireSession(payload.sessionId);\n const attachedClient = state.record.attachedClients.find(client => client.clientId === payload.clientId);\n if (attachedClient) {\n attachedClient.readOnly = false;\n attachedClient.lastSeenAt = Date.now();\n }\n if (state.record.writeOwner?.clientId === payload.clientId) {\n state.record.writeOwner = null;\n }\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n appendOutput(sessionId: string, data: string): { record: SessionHostRecord; seq: number } {\n const state = this.requireSession(sessionId);\n const seq = state.buffer.append(data);\n state.record.buffer = state.buffer.getState();\n state.record.lastActivityAt = Date.now();\n return { record: this.cloneRecord(state.record), seq };\n }\n\n getSnapshot(sessionId: string, sinceSeq?: number) {\n const state = this.requireSession(sessionId);\n state.record.buffer = state.buffer.getState();\n return state.buffer.snapshot(sinceSeq);\n }\n\n clearBuffer(sessionId: string): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.buffer.clear();\n state.record.buffer = state.buffer.getState();\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n updateSessionMeta(sessionId: string, meta: Record<string, unknown>, replace = false): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.meta = replace\n ? { ...meta }\n : {\n ...(state.record.meta || {}),\n ...meta,\n };\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n markStarted(sessionId: string, pid?: number): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = 'running';\n state.record.startedAt = state.record.startedAt || Date.now();\n if (typeof pid === 'number') state.record.osPid = pid;\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n markStopped(sessionId: string, lifecycle: 'stopped' | 'failed' = 'stopped', termination?: SessionTermination): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = lifecycle;\n state.record.lastActivityAt = Date.now();\n if (termination) state.record.termination = termination;\n return this.cloneRecord(state.record);\n }\n\n setLifecycle(sessionId: string, lifecycle: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted'): SessionHostRecord {\n const state = this.requireSession(sessionId);\n state.record.lifecycle = lifecycle;\n state.record.lastActivityAt = Date.now();\n return this.cloneRecord(state.record);\n }\n\n deleteSession(sessionId: string): boolean {\n return this.sessions.delete(sessionId);\n }\n\n private requireSession(sessionId: string): SessionRuntimeState {\n const state = this.sessions.get(sessionId);\n if (!state) throw new Error(`Unknown session: ${sessionId}`);\n return state;\n }\n\n private cloneRecord(record: SessionHostRecord): SessionHostRecord {\n return {\n ...record,\n launchCommand: {\n ...record.launchCommand,\n args: [...record.launchCommand.args],\n env: record.launchCommand.env ? { ...record.launchCommand.env } : undefined,\n },\n writeOwner: record.writeOwner ? { ...record.writeOwner } : null,\n attachedClients: record.attachedClients.map(client => ({ ...client })),\n buffer: { ...record.buffer },\n meta: { ...record.meta },\n };\n }\n}\n","import * as os from 'os';\nimport * as path from 'path';\nimport * as net from 'net';\nimport { StringDecoder } from 'string_decoder';\nimport type {\n SessionHostEvent,\n SessionHostRequest,\n SessionHostRequestEnvelope,\n SessionHostResponse,\n SessionHostResponseEnvelope,\n SessionHostWireEnvelope,\n} from './types.js';\n\nfunction generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = Math.random() * 16 | 0;\n const v = c === 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\nexport interface SessionHostEndpoint {\n kind: 'unix' | 'pipe';\n path: string;\n}\n\nexport interface SessionHostEndpointOptions {\n /**\n * Instance IPC namespace key (see instance-key.ts). '' / omitted yields the\n * legacy default-instance endpoint; a non-empty key suffixes the endpoint so\n * each config-dir instance gets its own socket/pipe.\n */\n ipcKey?: string;\n /** Test seam: override the platform the endpoint shape is derived for. */\n platform?: NodeJS.Platform;\n}\n\nexport function getDefaultSessionHostEndpoint(\n appName = 'adhdev',\n options: SessionHostEndpointOptions = {},\n): SessionHostEndpoint {\n const rawKey = typeof options.ipcKey === 'string' ? options.ipcKey.trim() : '';\n // Fail closed on a malformed key: silently dropping it would collapse the\n // endpoint back onto the default instance's namespace — a cross-instance leak.\n if (rawKey && !/^[0-9a-f]{12}$/.test(rawKey)) {\n throw new Error(`Invalid session-host ipcKey \"${rawKey}\" — expected '' or a 12-hex-char instance key`);\n }\n const suffix = rawKey ? `-${rawKey}` : '';\n const platform = options.platform || process.platform;\n if (platform === 'win32') {\n return {\n kind: 'pipe',\n path: `\\\\\\\\.\\\\pipe\\\\${appName}-session-host${suffix}`,\n };\n }\n\n return {\n kind: 'unix',\n path: path.join(os.tmpdir(), `${appName}-session-host${suffix}.sock`),\n };\n}\n\nfunction serializeEnvelope(envelope: SessionHostWireEnvelope): string {\n return `${JSON.stringify(envelope)}\\n`;\n}\n\n/**\n * UTF8-CHUNK-BOUNDARY: decode the socket stream, never the individual chunk.\n *\n * This used to call `chunk.toString()` on every Buffer independently. A socket\n * hands us arbitrary byte slices — a multi-byte UTF-8 sequence straddling two\n * chunks gets decoded as two truncated sequences, and each half becomes U+FFFD.\n * The damage is silent by construction: the replacement character is valid JSON\n * string content, so the envelope still parses and only the *value* is wrong.\n * A pasted 11,994-char Korean prompt lost exactly one character that way — the\n * `도` beginning at wire offset 8190, split across the 8192-byte chunk boundary,\n * arrived as `�착`.\n *\n * StringDecoder holds an incomplete trailing sequence back until the bytes that\n * finish it arrive, which is the only correct way to turn a byte stream into\n * text. String chunks bypass it: they are already decoded, and feeding them\n * through `Buffer.from` would re-encode text the caller never asked us to touch.\n */\nfunction createLineParser(onEnvelope: (envelope: SessionHostWireEnvelope) => void) {\n let buffer = '';\n const decoder = new StringDecoder('utf8');\n const parser = (chunk: Buffer | string) => {\n buffer += typeof chunk === 'string' ? chunk : decoder.write(chunk);\n let newlineIndex = buffer.indexOf('\\n');\n while (newlineIndex >= 0) {\n const rawLine = buffer.slice(0, newlineIndex).trim();\n buffer = buffer.slice(newlineIndex + 1);\n if (rawLine) {\n onEnvelope(JSON.parse(rawLine) as SessionHostWireEnvelope);\n }\n newlineIndex = buffer.indexOf('\\n');\n }\n };\n /**\n * Flush at EOF, returning whatever text never formed a complete line.\n *\n * `decoder.end()` releases any bytes still held back as an incomplete UTF-8\n * sequence; without it those bytes vanish with the decoder. What is left in\n * the buffer is by definition a partial line — envelopes are newline-framed,\n * so an unterminated tail is a truncated transmission, not a message. It is\n * therefore RETURNED for the caller to log rather than parsed: calling\n * JSON.parse on a half-written envelope inside a socket EOF handler would\n * raise an uncaught exception in the daemon, turning a peer that died\n * mid-write into a crash of the process observing it.\n */\n parser.end = (): string => {\n const remainder = buffer + decoder.end();\n buffer = '';\n return remainder;\n };\n return parser;\n}\n\nexport interface SessionHostClientOptions {\n endpoint?: SessionHostEndpoint;\n appName?: string;\n}\n\n/**\n * Why an established session-host connection went away.\n * - `error` — socket emitted 'error' (includes ECONNRESET on abrupt host death)\n * - `ended` — peer sent FIN ('end'): the host closed the connection cleanly\n * - `closed` — socket fully closed ('close') without a preceding error/end\n */\nexport type SessionHostDisconnectReason = 'error' | 'ended' | 'closed';\n\n/**\n * Sockets that reached 'connect' at least once. Used to distinguish a genuine\n * disconnect (host died under us) from a connect attempt that never landed\n * (host not up yet) — only the former is a session-host death signal.\n */\nconst establishedSockets = new WeakSet<net.Socket>();\n\nexport interface SessionHostDisconnectInfo {\n reason: SessionHostDisconnectReason;\n endpointPath: string;\n /** In-flight requests abandoned by this disconnect (they were rejected). */\n pendingRequests: number;\n error?: Error;\n /**\n * Bytes discarded from a newline-unterminated frame at EOF (0 when the peer\n * hung up on a clean frame boundary, which is the normal case).\n *\n * Non-zero means the peer was cut off MID-WRITE — diagnostic context that\n * distinguishes \"the host exited\" from \"the host died with a frame in\n * flight\". Before the parser's `end()` was reachable this was unknowable:\n * the bytes were dropped with the decoder and no counter existed.\n */\n droppedTailBytes: number;\n}\n\nexport class SessionHostClient {\n readonly endpoint: SessionHostEndpoint;\n\n private socket: net.Socket | null = null;\n private requestWaiters = new Map<string, { resolve: (value: SessionHostResponse) => void; reject: (error: Error) => void }>();\n private eventListeners = new Set<(event: SessionHostEvent) => void>();\n private disconnectListeners = new Set<(info: SessionHostDisconnectInfo) => void>();\n /** Guards against emitting both an 'error'-path and a 'close'-path disconnect for one socket. */\n private disconnectedSockets = new WeakSet<net.Socket>();\n\n constructor(options: SessionHostClientOptions = {}) {\n this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || 'adhdev');\n }\n\n /**\n * Fires when an established connection to the session host goes away.\n *\n * Before this existed the client registered only 'data' and 'error' handlers,\n * so a *clean* FIN — the host process exiting normally, or dying to an\n * uncaught exception that took the socket down without an error event on our\n * end — ran nothing at all: `this.socket` stayed non-null pointing at a dead\n * socket, in-flight waiters hung to their 30s timeout, and no caller was told.\n * That silence is what let a session-host death present downstream as an\n * unrelated application-level fault (a missing completion marker) instead of\n * \"the host is gone\".\n */\n onDisconnect(listener: (info: SessionHostDisconnectInfo) => void): () => void {\n this.disconnectListeners.add(listener);\n return () => {\n this.disconnectListeners.delete(listener);\n };\n }\n\n private handleDisconnect(\n socket: net.Socket,\n reason: SessionHostDisconnectReason,\n error?: Error,\n droppedTailBytes = 0,\n ): void {\n if (this.disconnectedSockets.has(socket)) return;\n this.disconnectedSockets.add(socket);\n\n // A connect that never succeeded is a *failed connect*, not a disconnect —\n // reporting it as a host death would make every poll against a not-yet-started\n // host look like a crash. connect() already surfaces that failure to its caller\n // via the awaited 'error'. Still clear socket state below? No: connect()'s own\n // error path handles a never-established socket, and `this.socket` is\n // overwritten on the next connect() attempt regardless.\n const wasEstablished = establishedSockets.has(socket);\n if (!wasEstablished) {\n if (this.socket === socket) this.socket = null;\n try { socket.destroy(); } catch { /* noop */ }\n // Reject waiters anyway — a request() that raced a dying socket must not hang.\n const failure = error || new Error(`Session host connect failed (${this.endpoint.path})`);\n for (const waiter of this.requestWaiters.values()) waiter.reject(failure);\n this.requestWaiters.clear();\n return;\n }\n\n const pendingRequests = this.requestWaiters.size;\n const failure = error || new Error(`Session host connection ${reason} (${this.endpoint.path})`);\n for (const waiter of this.requestWaiters.values()) {\n waiter.reject(failure);\n }\n this.requestWaiters.clear();\n\n // Clear the dead socket reference so the next connect() creates a fresh\n // connection instead of short-circuiting on the `!this.socket.destroyed`\n // guard at the top of connect().\n if (this.socket === socket) {\n this.socket = null;\n }\n try { socket.destroy(); } catch { /* noop */ }\n\n const info: SessionHostDisconnectInfo = {\n reason,\n endpointPath: this.endpoint.path,\n pendingRequests,\n error,\n droppedTailBytes,\n };\n for (const listener of this.disconnectListeners) {\n // A throwing observer must not suppress the remaining ones, and must not\n // propagate out of a socket event handler (that would be an uncaught\n // exception in the daemon).\n try { listener(info); } catch { /* noop */ }\n }\n }\n\n async connect(): Promise<void> {\n if (this.socket && !this.socket.destroyed) return;\n // Cleanup stale socket reference left after error/disconnect\n if (this.socket) {\n try { this.socket.destroy(); } catch { /* noop */ }\n this.socket = null;\n }\n\n const socket = net.createConnection(this.endpoint.path);\n this.socket = socket;\n\n // PARSER-EOF-FLUSH: keep the parser handle so its EOF flush is reachable.\n // Inlining `createLineParser(...)` into the 'data' registration discarded\n // the only reference to `end()`, so bytes held back by the StringDecoder as\n // an incomplete UTF-8 sequence were lost with the decoder when the host\n // disconnected.\n const parser = createLineParser((envelope) => {\n if (envelope.kind === 'response') {\n const waiter = this.requestWaiters.get(envelope.requestId);\n if (waiter) {\n this.requestWaiters.delete(envelope.requestId);\n waiter.resolve(envelope.response);\n }\n return;\n }\n\n if (envelope.kind === 'event') {\n for (const listener of this.eventListeners) listener(envelope.event);\n }\n });\n socket.on('data', parser);\n\n /**\n * Drain the parser at EOF. The remainder is a partial, newline-unterminated\n * frame — a truncated transmission rather than a message — so it is NOT\n * parsed. Feeding a half-written envelope to JSON.parse here would throw\n * inside a socket event handler with no catch above it, converting a host\n * that died mid-write into an uncaught exception in this process. That is\n * exactly the failure the parser's contract set out to avoid.\n *\n * Reported through the existing disconnect surface rather than a new one:\n * a non-empty tail means the peer was cut off mid-frame, which is\n * diagnostic context for the disconnect that is about to be emitted.\n */\n const flushParser = (): string => {\n try {\n return parser.end();\n } catch {\n return '';\n }\n };\n\n socket.on('error', (error) => {\n const remainder = flushParser();\n this.handleDisconnect(socket, 'error', error, remainder.length);\n });\n\n // 'close'/'end' are the clean-FIN counterparts of 'error'. The host server\n // has always handled both directions (server.ts registers close+end); this\n // side handled neither, so host death was observable only if it happened to\n // surface as a socket error.\n socket.on('close', () => {\n this.handleDisconnect(socket, 'closed', undefined, flushParser().length);\n });\n socket.on('end', () => {\n this.handleDisconnect(socket, 'ended', undefined, flushParser().length);\n });\n\n await new Promise<void>((resolve, reject) => {\n socket.once('connect', () => {\n establishedSockets.add(socket);\n resolve();\n });\n socket.once('error', reject);\n });\n }\n\n onEvent(listener: (event: SessionHostEvent) => void): () => void {\n this.eventListeners.add(listener);\n return () => {\n this.eventListeners.delete(listener);\n };\n }\n\n async request<T = unknown>(request: SessionHostRequest): Promise<SessionHostResponse<T>> {\n await this.connect();\n if (!this.socket) throw new Error('Session host socket unavailable');\n\n const requestId = generateUUID();\n const envelope: SessionHostRequestEnvelope = {\n kind: 'request',\n requestId,\n request,\n };\n\n const response = await new Promise<SessionHostResponse>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.requestWaiters.delete(requestId);\n reject(new Error(`Session host request timed out after 30s (${request.type})`));\n }, 30_000);\n this.requestWaiters.set(requestId, {\n resolve: (value) => { clearTimeout(timeout); resolve(value); },\n reject: (error) => { clearTimeout(timeout); reject(error); },\n });\n this.socket?.write(serializeEnvelope(envelope));\n });\n\n return response as SessionHostResponse<T>;\n }\n\n async close(): Promise<void> {\n if (!this.socket) return;\n const socket = this.socket;\n this.socket = null;\n // Deliberate teardown: suppress the 'close' handler's disconnect signal so\n // callers don't see an intentional close reported as a session-host death.\n this.disconnectedSockets.add(socket);\n for (const waiter of this.requestWaiters.values()) {\n waiter.reject(new Error('Session host client closed'));\n }\n this.requestWaiters.clear();\n await new Promise<void>((resolve) => {\n let settled = false;\n const done = () => {\n if (settled) return;\n settled = true;\n resolve();\n };\n socket.once('close', done);\n socket.end();\n socket.destroy();\n setTimeout(done, 50);\n });\n }\n}\n\nexport function createResponseEnvelope(requestId: string, response: SessionHostResponse): SessionHostResponseEnvelope {\n return {\n kind: 'response',\n requestId,\n response,\n };\n}\n\nexport function writeEnvelope(socket: Pick<net.Socket, 'write'>, envelope: SessionHostWireEnvelope): void {\n socket.write(serializeEnvelope(envelope));\n}\n\nexport { createLineParser };\n","import * as crypto from 'crypto';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\n\n/**\n * Instance identity derivation for the session-host IPC namespace.\n *\n * One machine can host several ADHDev installs (stable `~/.adhdev`, preview\n * `~/.adhdev-preview`, standalone `~/.adhdev-standalone`, or any explicit\n * ADHDEV_CONFIG_DIR). Each install is an *instance*: it owns its config dir\n * and everything mutable under it. The session-host socket/pipe must be\n * namespaced by that identity so two simultaneous instances can never attach\n * to, kill, or rebind each other's session host.\n *\n * This module is the single derivation rule, shared by:\n * - the parent daemons (via @adhdev/daemon-core's InstanceContext),\n * - the session-host-daemon child process (which only sees env vars),\n * - CLI attach/list clients.\n *\n * All three compute the endpoint from the same inputs (appName +\n * ADHDEV_CONFIG_DIR), so they always agree without extra plumbing.\n *\n * Compatibility contract: the DEFAULT instance (config dir canonicalizes to\n * `<home>/.adhdev`) yields an EMPTY ipc key, so its socket/pipe path stays\n * byte-for-byte identical to the pre-instance layout.\n */\n\nexport const DEFAULT_CONFIG_DIR_NAME = '.adhdev';\n\n/**\n * Resolve the instance config dir from the environment: ADHDEV_CONFIG_DIR\n * when set (trimmed), else `<homeDir>/.adhdev`. Mirrors daemon-core's\n * getConfigDir() source rule without the mkdir side effect.\n *\n * NOTE (track identity): the no-env fallback here stays the LEGACY stable\n * default `.adhdev` — session-host-core is a pure leaf package that\n * daemon-core depends on, so it cannot import daemon-core's track-identity\n * (dependency direction) and its separately-built dist never sees the\n * `__ADHDEV_BUILD_CHANNEL__` build stamp. This is safe because every tracked\n * runtime path pins ADHDEV_CONFIG_DIR explicitly: the daemon spawns the\n * session-host child with `env.ADHDEV_CONFIG_DIR = instance().configDir`\n * (managed-host.ts buildEnv), and the installer/service pins it for preview.\n * The track-aware no-env default lives in daemon-core (getConfigDir /\n * resolveInstanceContext), which is the authority for daemon-side paths.\n */\nexport function resolveInstanceConfigDir(\n env: NodeJS.ProcessEnv = process.env,\n homeDir: string = os.homedir(),\n): string {\n const override = typeof env.ADHDEV_CONFIG_DIR === 'string' ? env.ADHDEV_CONFIG_DIR.trim() : '';\n return override || path.join(homeDir, DEFAULT_CONFIG_DIR_NAME);\n}\n\n/**\n * Realpath the longest existing prefix of `resolved` and re-append the\n * non-existing remainder. Unlike a plain realpathSync, this is deterministic\n * whether or not the leaf exists yet (a config dir realpath'd only after its\n * first mkdir must not change the derived ipc key between processes).\n */\nfunction realpathLongestExistingPrefix(resolved: string): string {\n let current = resolved;\n const remainder: string[] = [];\n // Bounded walk: a path can have at most its own segment count of parents.\n for (let depth = 0; depth < 256; depth++) {\n try {\n const real = fs.realpathSync.native(current);\n return remainder.length === 0 ? real : path.join(real, ...remainder);\n } catch {\n const parent = path.dirname(current);\n if (parent === current) return resolved;\n remainder.unshift(path.basename(current));\n current = parent;\n }\n }\n return resolved;\n}\n\n/**\n * Canonical form of an instance path for identity comparison/hashing:\n * absolute, symlink-resolved (longest existing prefix), no trailing\n * separator, case-folded on win32.\n */\nexport function canonicalizeInstancePath(\n dir: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n const resolved = path.resolve(dir);\n const real = realpathLongestExistingPrefix(resolved).replace(/[\\\\/]+$/, '');\n return platform === 'win32' ? real.toLowerCase() : real;\n}\n\n/** True when `configDir` is canonically the default `<homeDir>/.adhdev`. */\nexport function isDefaultInstanceConfigDir(\n configDir: string,\n homeDir: string = os.homedir(),\n platform: NodeJS.Platform = process.platform,\n): boolean {\n return canonicalizeInstancePath(configDir, platform)\n === canonicalizeInstancePath(path.join(homeDir, DEFAULT_CONFIG_DIR_NAME), platform);\n}\n\n/**\n * Derive the session-host IPC namespace key for an instance config dir.\n *\n * '' for the default instance (legacy-compatible endpoint), otherwise a\n * 12-hex-char sha256 prefix of the canonical config dir — stable across\n * processes/machines for the same dir, collision-resistant between dirs,\n * and insensitive to symlink/spelling differences of the same dir.\n */\nexport function resolveSessionHostIpcKey(\n configDir: string,\n homeDir: string = os.homedir(),\n platform: NodeJS.Platform = process.platform,\n): string {\n if (isDefaultInstanceConfigDir(configDir, homeDir, platform)) return '';\n const canonical = canonicalizeInstancePath(configDir, platform);\n return crypto.createHash('sha256').update(canonical, 'utf8').digest('hex').slice(0, 12);\n}\n","/**\n * Shared PTY spawn environment utilities.\n *\n * Centralises npm/pnpm/yarn env variable stripping, terminal colour env\n * injection, and node-pty spawn-helper permission fixing.\n *\n * Used by daemon-core (provider-cli-adapter), session-host-daemon (runtime),\n * and daemon-cloud (session-host).\n */\n\nimport * as os from 'os';\nimport * as path from 'path';\n\n/**\n * Strip package-manager injected environment variables that can interfere\n * with child CLI processes and apply terminal colour defaults.\n */\nexport function sanitizeSpawnEnv(\n baseEnv: NodeJS.ProcessEnv,\n overrides?: Record<string, string>,\n): Record<string, string> {\n const env: Record<string, string> = {};\n const source = { ...baseEnv, ...(overrides || {}) } as NodeJS.ProcessEnv;\n\n for (const [key, value] of Object.entries(source)) {\n if (typeof value !== 'string') continue;\n env[key] = value;\n }\n\n for (const key of Object.keys(env)) {\n if (\n key === 'INIT_CWD'\n || key === 'npm_command'\n || key === 'npm_execpath'\n || key === 'npm_node_execpath'\n || key.startsWith('npm_')\n || key.startsWith('npm_config_')\n || key.startsWith('npm_package_')\n || key.startsWith('npm_lifecycle_')\n || key.startsWith('PNPM_')\n || key.startsWith('YARN_')\n || key.startsWith('BUN_')\n || key.startsWith('VSCODE_')\n || key.startsWith('ELECTRON_')\n ) {\n delete env[key];\n }\n }\n\n // Do not leak parent Codex session controls into child CLIs. Thread identity\n // breaks providerSessionId discovery/reconnect semantics, while the parent's\n // network-disabled sandbox flag prevents coordinator MCP children from\n // connecting back to the local ADHDev daemon.\n delete env.CODEX_THREAD_ID;\n delete env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE;\n delete env.CODEX_SANDBOX_NETWORK_DISABLED;\n delete env.NO_COLOR;\n delete env.COLOR;\n\n // Never forward an inherited terminal geometry. COLUMNS/LINES are advisory\n // overrides that a TUI trusts *in preference to* the real TTY size\n // (measured: with COLUMNS=17 exported, a child on a genuine 80-column PTY\n // reports `tput cols` = 17). The PTY here is sized independently from the\n // adapter's own geometry, so any inherited value is by definition unrelated\n // to the terminal the child was actually given.\n //\n // When the two disagree the child lays out for the phantom width and the\n // VT renders at the real one, so in-place repaints (`\\r`, absolute cursor\n // moves) land on the wrong row: content fragments across the buffer with a\n // blank gap between the stale head and the live tail. Observed on\n // antigravity-cli as a \"Running command\" spinner split into two rows 28\n // blank lines apart, which the spec's from_top/from_bottom sections then\n // sliced into separate body/footer values.\n //\n // Deleting them makes the child measure the TTY via ioctl(TIOCGWINSZ) —\n // the PTY's real size, which is exactly what the VT is sized to. node-pty\n // does not set these itself, so a clean parent env is already the normal\n // case; this only removes a value that leaked in from whatever launched\n // the daemon (interactive shell, tmux, CI runner, nested CLI).\n delete env.COLUMNS;\n delete env.LINES;\n\n // Do not leak a parent Claude Code session identity into spawned claude-cli\n // children. CLAUDE_CODE_CHILD_SESSION makes a spawned claude run as a nested\n // child that does NOT persist its ~/.claude/projects transcript, so the\n // native-source history reader finds no file and the live dashboard renders\n // empty. This bites when the daemon itself was launched from inside a Claude\n // Code session (the markers are inherited and forwarded to every child).\n // Unconditional: originally scoped to win32 (35b462c5) because the bug had\n // only been OBSERVED there at the time — the Windows daemon that surfaced it\n // happened to have been launched from inside a Claude Code session, and\n // macOS/Linux \"looked fine\" only because no one had hit that same launch\n // circumstance yet, not because the platform behaves differently. The\n // marker-inheritance mechanism itself was never platform-specific — nothing\n // about how Claude Code detects a nested child session depends on OS.\n // Confirmed reproduced identically on darwin (live coordinator measurement,\n // this task): a daemon started from inside a Claude Code session inherits\n // and forwards the same markers there too. A daemon-spawned claude should\n // always be a fresh top-level session, on every platform — exactly what the\n // original commit already called out as \"safe to make unconditional later.\"\n delete env.CLAUDECODE;\n delete env.CLAUDE_CODE_CHILD_SESSION;\n delete env.CLAUDE_CODE_ENTRYPOINT;\n delete env.CLAUDE_CODE_SESSION_ID;\n delete env.CLAUDE_CODE_EXECPATH;\n // CLAUDE_CODE_BRIDGE_SESSION_ID is the same kind of parent-identity marker\n // (observed grouped with CLAUDECODE/CLAUDE_CODE_SESSION_ID/\n // CLAUDE_CODE_CHILD_SESSION/CLAUDE_CODE_EXECPATH in the installed `claude`\n // binary's own env-var table, alongside a `buildBridgeReattachEnvFromState`\n // helper) — it identifies the parent's remote bridge/reattach session. Left\n // in place, a spawned child could try to reattach to the PARENT's remote\n // session instead of starting fresh, which is a worse variant of the same\n // \"not actually a fresh top-level session\" bug this block exists to prevent.\n delete env.CLAUDE_CODE_BRIDGE_SESSION_ID;\n\n applyTerminalColorEnv(env);\n return env;\n}\n\n/**\n * Apply preferred terminal colour environment variables.\n * Ensures TERM is set to xterm-256color and enables colour on Windows.\n */\nexport function applyTerminalColorEnv(env: Record<string, string>): void {\n if (env.NO_COLOR) return;\n\n if (!env.TERM || env.TERM === 'xterm-color') {\n env.TERM = 'xterm-256color';\n }\n if (!env.COLORTERM) env.COLORTERM = 'truecolor';\n\n if (process.platform === 'win32') {\n if (!env.FORCE_COLOR) env.FORCE_COLOR = '1';\n if (!env.CLICOLOR) env.CLICOLOR = '1';\n }\n}\n\n/**\n * Ensure node-pty's spawn-helper binary has execute permissions.\n *\n * npm's default umask can strip +x from the prebuilt spawn-helper on macOS/Linux,\n * causing EACCES when node-pty tries to fork. Best-effort fix.\n *\n * @param logFn Optional log callback for reporting the fix.\n */\nexport function ensureNodePtySpawnHelperPermissions(\n logFn?: (msg: string) => void,\n): void {\n if (os.platform() === 'win32') return;\n try {\n const fs = require('fs');\n const ptyDir = path.resolve(path.dirname(require.resolve('node-pty')), '..');\n const platformArch = `${os.platform()}-${os.arch()}`;\n const helper = path.join(ptyDir, 'prebuilds', platformArch, 'spawn-helper');\n if (fs.existsSync(helper)) {\n const stat = fs.statSync(helper);\n if (!(stat.mode & 0o111)) {\n fs.chmodSync(helper, stat.mode | 0o755);\n logFn?.(`Fixed spawn-helper permissions: ${helper}`);\n }\n }\n } catch {\n // best-effort: node-pty still works on most installs without this\n }\n}\n","import type {\n AcquireWritePayload,\n GetHostDiagnosticsPayload,\n PruneDuplicateSessionsPayload,\n ReleaseWritePayload,\n SessionHostDiagnostics,\n SessionHostPruneDuplicatesResult,\n SessionHostRecord,\n SessionHostRequestType,\n} from './types.js';\n\n/**\n * The 11-method session-host control-plane surface shared by the cloud and\n * standalone daemons. Both daemons used to carry a byte-for-byte copy of this\n * dispatch table plus the identical throw strings; this is the single source of\n * truth for that mapping. The wire `type` strings and error text here are the\n * daemon IPC contract and MUST match what the session-host daemon accepts.\n */\nexport interface SessionHostControlPlane {\n getDiagnostics(payload?: GetHostDiagnosticsPayload): Promise<SessionHostDiagnostics>;\n listSessions(): Promise<SessionHostRecord[]>;\n stopSession(sessionId: string): Promise<SessionHostRecord | null>;\n deleteSession(sessionId: string, opts?: { force?: boolean }): Promise<SessionHostRecord | null>;\n resumeSession(sessionId: string): Promise<SessionHostRecord | null>;\n restartSession(sessionId: string): Promise<SessionHostRecord | null>;\n sendSignal(sessionId: string, signal: string): Promise<SessionHostRecord | null>;\n forceDetachClient(sessionId: string, clientId: string): Promise<SessionHostRecord | null>;\n pruneDuplicateSessions(payload?: PruneDuplicateSessionsPayload): Promise<SessionHostPruneDuplicatesResult>;\n acquireWrite(payload: AcquireWritePayload): Promise<SessionHostRecord | null>;\n releaseWrite(payload: ReleaseWritePayload): Promise<SessionHostRecord | null>;\n}\n\n/**\n * Minimal request transport the control-plane factory needs. Each daemon injects\n * its own implementation:\n * - cloud reuses a single persistent SessionHostClient (with reconnect); its\n * `request` calls `ensureConnected()` then the shared client.\n * - standalone opens a fresh client per request and closes it in `finally`.\n *\n * The factory below owns ONLY the type/payload dispatch table; the connection\n * lifecycle and success/error unwrapping stay in each daemon's transport so the\n * observable behavior of both paths is preserved exactly.\n */\nexport interface SessionHostControlTransport {\n request<T>(type: SessionHostRequestType, payload: Record<string, unknown>): Promise<T>;\n}\n\n/**\n * Build the shared 11-method control-plane over an injected request transport.\n * The (type string, payload shape) pairs are verbatim what both daemons emitted\n * previously — do not reword them without matching the session-host daemon's\n * request handlers.\n */\nexport function createSessionHostControlPlane(\n transport: SessionHostControlTransport,\n): SessionHostControlPlane {\n return {\n getDiagnostics(payload: GetHostDiagnosticsPayload = {}): Promise<SessionHostDiagnostics> {\n return transport.request<SessionHostDiagnostics>('get_host_diagnostics', payload as Record<string, unknown>);\n },\n listSessions(): Promise<SessionHostRecord[]> {\n return transport.request<SessionHostRecord[]>('list_sessions', {});\n },\n stopSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('stop_session', { sessionId });\n },\n deleteSession(sessionId: string, opts: { force?: boolean } = {}): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('delete_session', { sessionId, force: opts.force === true });\n },\n resumeSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('resume_session', { sessionId });\n },\n restartSession(sessionId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('restart_session', { sessionId });\n },\n sendSignal(sessionId: string, signal: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('send_signal', { sessionId, signal });\n },\n forceDetachClient(sessionId: string, clientId: string): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('force_detach_client', { sessionId, clientId });\n },\n pruneDuplicateSessions(payload: PruneDuplicateSessionsPayload = {}): Promise<SessionHostPruneDuplicatesResult> {\n return transport.request<SessionHostPruneDuplicatesResult>('prune_duplicate_sessions', payload as Record<string, unknown>);\n },\n acquireWrite(payload: AcquireWritePayload): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('acquire_write', payload as unknown as Record<string, unknown>);\n },\n releaseWrite(payload: ReleaseWritePayload): Promise<SessionHostRecord | null> {\n return transport.request<SessionHostRecord | null>('release_write', payload as unknown as Record<string, unknown>);\n },\n };\n}\n"],"mappings":";;;;;;;;AAoUO,IAAM,uCAA0E;AAAA,EACrF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;AChUO,SAAS,oBAAoB,OAAqD;AACvF,QAAM,EAAE,UAAU,aAAa,IAAI;AACnC,QAAM,SAAS,MAAM,UAAU;AAE/B,MAAI;AACJ,MAAI;AAEJ,MAAI,WAAW,QAAQ,WAAW,GAAG;AACnC,aAAS;AACT,gBAAY;AAAA,EACd,WAAW,aAAa,MAAM;AAC5B,aAAS;AACT,gBAAY;AAAA,EACd,WAAW,aAAa,GAAG;AACzB,aAAS;AACT,gBAAY;AAAA,EACd,OAAO;AACL,aAAS;AACT,gBAAY;AAAA,EACd;AAEA,QAAM,cAAkC;AAAA,IACtC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,OAAO,MAAM,UAAU,SAAU,aAAY,QAAQ,MAAM;AAC/D,MAAI,MAAM,kBAAmB,aAAY,oBAAoB,MAAM;AACnE,MAAI,OAAO,MAAM,iBAAiB,SAAU,aAAY,eAAe,MAAM;AAC7E,MAAI,MAAM,cAAe,aAAY,gBAAgB,MAAM;AAC3D,SAAO;AACT;;;AChDO,IAAM,wCAAwC,IAAI,OAAO;AAEzD,IAAM,oBAAN,MAAwB;AAAA,EACrB;AAAA,EACA,SAAyD,CAAC;AAAA,EAC1D,UAAU;AAAA,EACV,aAAa;AAAA,EAErB,YAAY,UAAoC,CAAC,GAAG;AAClD,SAAK,WAAW,QAAQ,YAAY;AAAA,EACtC;AAAA,EAEA,OAAO,MAAsB;AAC3B,UAAM,aAAa,OAAO,SAAS,WAAW,OAAO,OAAO,QAAQ,EAAE;AACtE,UAAM,QAAQ,OAAO,WAAW,YAAY,MAAM;AAClD,UAAM,MAAM,KAAK;AAEjB,SAAK,OAAO,KAAK,EAAE,KAAK,MAAM,YAAY,MAAM,CAAC;AACjD,SAAK,cAAc;AACnB,SAAK,KAAK;AACV,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,UAA0C;AACjD,UAAM,WAAW,OAAO,aAAa,WACjC,KAAK,OAAO,OAAO,WAAS,MAAM,MAAM,QAAQ,IAChD,KAAK;AAET,UAAM,OAAO,SAAS,IAAI,WAAS,MAAM,IAAI,EAAE,KAAK,EAAE;AACtD,UAAM,YAAY,CAAC,CAAC,KAAK,OAAO,CAAC,KAAK,OAAO,aAAa,YAAY,WAAW,KAAK,OAAO,CAAC,EAAE,MAAM;AAEtG,WAAO;AAAA,MACL,KAAK,KAAK,UAAU;AAAA,MACpB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,WAA6D;AAC3D,WAAO;AAAA,MACL,iBAAiB,KAAK;AAAA,MACtB,aAAa,KAAK,UAAU;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,QAAc;AACZ,SAAK,SAAS,CAAC;AACf,SAAK,aAAa;AAClB,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,QAAQ,UAA+C;AACrD,SAAK,MAAM;AACX,UAAM,OAAO,OAAO,SAAS,QAAQ,EAAE;AACvC,QAAI,CAAC,MAAM;AACT,WAAK,UAAU,KAAK,IAAI,GAAG,OAAO,SAAS,OAAO,CAAC,IAAI,CAAC;AACxD;AAAA,IACF;AACA,UAAM,QAAQ,OAAO,WAAW,MAAM,MAAM;AAC5C,UAAM,MAAM,KAAK,IAAI,GAAG,OAAO,SAAS,OAAO,CAAC,CAAC;AACjD,SAAK,SAAS,CAAC,EAAE,KAAK,MAAM,MAAM,MAAM,CAAC;AACzC,SAAK,aAAa;AAClB,SAAK,UAAU,MAAM;AACrB,SAAK,KAAK;AAAA,EACZ;AAAA,EAEQ,OAAa;AACnB,WAAO,KAAK,aAAa,KAAK,YAAY,KAAK,OAAO,SAAS,GAAG;AAChE,YAAM,UAAU,KAAK,OAAO,MAAM;AAClC,UAAI,CAAC,QAAS;AACd,WAAK,cAAc,QAAQ;AAAA,IAC7B;AAAA,EACF;AACF;;;ACnFO,IAAM,4BAA4B;AAClC,IAAM,4BAA4B;AAEzC,SAAS,8BAA8B,OAA2B,UAA0B;AAC1F,MAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACjE,QAAM,UAAU,KAAK,MAAM,KAAK;AAChC,SAAO,UAAU,IAAI,UAAU;AACjC;AAEO,SAAS,uBAAuB,OAAmC;AACxE,SAAO,8BAA8B,OAAO,yBAAyB;AACvE;AAEO,SAAS,uBAAuB,OAAmC;AACxE,SAAO,8BAA8B,OAAO,yBAAyB;AACvE;;;ACfA,YAAY,UAAU;AAGtB,SAAS,cAAc,OAAuB;AAC5C,SAAO,MACJ,KAAK,EACL,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,YAAY,EAAE,EACtB,MAAM,GAAG,EAAE;AAChB;AAEA,SAAS,eAAe,OAAuB;AAC7C,SAAO,MAAM,KAAK,EAAE,YAAY;AAClC;AAEO,SAAS,kBAAkB,WAA2B;AAC3D,QAAM,UAAU,UAAU,KAAK;AAC/B,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,aAAa,QAAQ,QAAQ,WAAW,EAAE;AAChD,QAAM,OAAY,cAAS,UAAU;AACrC,SAAO,QAAQ;AACjB;AAEO,SAAS,wBAAwB,SAA2F;AACjI,QAAM,WAAW,QAAQ,aAAa,KAAK;AAC3C,MAAI,SAAU,QAAO;AACrB,QAAM,iBAAiB,kBAAkB,QAAQ,SAAS;AAC1D,QAAM,gBAAgB,QAAQ,aAAa,KAAK,KAAK;AACrD,SAAO,GAAG,aAAa,MAAM,cAAc;AAC7C;AAEO,SAAS,gBACd,SACA,cACQ;AACR,QAAM,YAAY,QAAQ,YAAY,KAAK;AAC3C,QAAM,WAAW,IAAI,IAAI,MAAM,KAAK,cAAc,CAAC,QAAQ,IAAI,YAAY,CAAC,CAAC;AAC7E,QAAM,cAAc,wBAAwB,OAAO;AACnD,QAAM,UAAU,cAAc,aAAa,eAAe,kBAAkB,QAAQ,SAAS,KAAK,QAAQ,gBAAgB,SAAS,KAAK;AACxI,MAAI,CAAC,SAAS,IAAI,OAAO,EAAG,QAAO;AAEnC,MAAI,SAAS;AACb,MAAI,YAAY,GAAG,OAAO,IAAI,MAAM;AACpC,SAAO,SAAS,IAAI,SAAS,GAAG;AAC9B,cAAU;AACV,gBAAY,GAAG,OAAO,IAAI,MAAM;AAAA,EAClC;AACA,SAAO;AACT;AASA,IAAM,kBAAkB,oBAAI,IAAI,CAAC,YAAY,WAAW,YAAY,aAAa,CAAC;AAClF,IAAM,sBAAsB,oBAAI,IAAI,CAAC,WAAW,QAAQ,CAAC;AAUlD,SAAS,iBAAiB,QAAkE;AACjG,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,YAAa,QAAO;AAC/B,SAAO,oBAAoB,IAAI,OAAO,OAAO,aAAa,EAAE,EAAE,KAAK,CAAC;AACtE;AAEO,SAAS,yBAAyB,QAAkE;AACzG,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,gBAAgB,eAAgB,QAAO;AAClD,MAAI,OAAO,gBAAgB,uBAAuB,OAAO,gBAAgB,kBAAmB,QAAO;AAKnG,MAAI,OAAO,YAAa,QAAO;AAC/B,QAAM,YAAY,OAAO,OAAO,aAAa,EAAE,EAAE,KAAK;AACtD,SAAO,gBAAgB,IAAI,SAAS;AACtC;AAEO,SAAS,4BAA4B,MAAiE;AAC3G,QAAM,gBAAgB,OAAO,MAAM,yBAAyB,WACxD,OAAO,KAAK,oBAAoB,EAAE,KAAK,IACvC;AACJ,MAAI,CAAC,cAAe,QAAO;AAC3B,MAAI,kBAAkB,eAAgB,QAAO;AAC7C,MAAI,kBAAkB,gBAAiB,QAAO;AAC9C,MAAI,kBAAkB,2BAA4B,QAAO;AACzD,MAAI,kBAAkB,kBAAmB,QAAO;AAChD,SAAO,cAAc,QAAQ,MAAM,GAAG;AACxC;AAEO,SAAS,8BAA8B,QAAkE;AAC9G,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,gBAAgB,oBAAqB,QAAO;AACvD,MAAI,OAAO,gBAAgB,kBAAkB,OAAO,gBAAgB,kBAAmB,QAAO;AAC9F,MAAI,yBAAyB,MAAM,EAAG,QAAO;AAE7C,QAAM,YAAY,OAAO,OAAO,aAAa,EAAE,EAAE,KAAK;AACtD,MAAI,aAAa,cAAc,aAAa,cAAc,UAAU;AAClE,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,OAAO,QAAQ;AAC5B,MAAI,MAAM,wBAAwB,KAAM,QAAO;AAC/C,SAAO,4BAA4B,IAAI,MAAM;AAC/C;AAEO,SAAS,0BAA0B,QAAiF;AACzH,MAAI,QAAQ,gBAAgB,kBAAkB,QAAQ,gBAAgB,uBAAuB,QAAQ,gBAAgB,mBAAmB;AACtI,WAAO,OAAO;AAAA,EAChB;AACA,MAAI,yBAAyB,MAAM,EAAG,QAAO;AAC7C,MAAI,8BAA8B,MAAM,EAAG,QAAO;AAClD,SAAO;AACT;AAEO,SAAS,4BAAoE,SAIlF;AACA,QAAM,eAAoB,CAAC;AAC3B,QAAM,oBAAyB,CAAC;AAChC,QAAM,kBAAuB,CAAC;AAE9B,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,0BAA0B,MAAM;AAC7C,QAAI,SAAS,gBAAgB;AAC3B,mBAAa,KAAK,MAAM;AAAA,IAC1B,WAAW,SAAS,qBAAqB;AACvC,wBAAkB,KAAK,MAAM;AAAA,IAC/B,OAAO;AACL,sBAAgB,KAAK,MAAM;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,wCACd,SACmE;AACnE,SAAO,4BAA4B,WAAW,CAAC,CAAC;AAClD;AAEO,SAAS,+BAA+B,SAA8B,YAAuC;AAClH,QAAM,SAAS,qBAAqB,SAAS,UAAU;AACvD,QAAM,cAAc,0BAA0B,MAAM;AACpD,MAAI,gBAAgB,gBAAgB;AAClC,WAAO;AAAA,EACT;AACA,MAAI,gBAAgB,qBAAqB;AACvC,UAAM,IAAI,MAAM,WAAW,OAAO,UAAU,gFAAgF;AAAA,EAC9H;AACA,QAAM,IAAI,MAAM,WAAW,OAAO,UAAU,OAAO,OAAO,SAAS,6BAA6B;AAClG;AAEA,SAAS,YAAY,SAA8B,WAA6E;AAC9H,QAAM,UAAU,QAAQ,OAAO,SAAS;AACxC,MAAI,QAAQ,WAAW,EAAG,QAAO,QAAQ,CAAC,KAAK;AAC/C,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,SAAS,QAAQ,IAAI,CAAC,WAAW,GAAG,OAAO,UAAU,KAAK,OAAO,SAAS,GAAG,EAAE,KAAK,IAAI;AAC9F,QAAM,IAAI,MAAM,sCAAsC,MAAM,EAAE;AAChE;AAEO,SAAS,qBAAqB,SAA8B,YAAuC;AACxG,QAAM,SAAS,WAAW,KAAK;AAC/B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC9C;AAEA,QAAM,QAAQ;AAAA,IAAY;AAAA,IAAS,CAAC,WAClC,OAAO,cAAc,UACrB,eAAe,OAAO,UAAU,MAAM,eAAe,MAAM,KAC3D,eAAe,OAAO,WAAW,MAAM,eAAe,MAAM;AAAA,EAC9D;AACA,MAAI,MAAO,QAAO;AAElB,QAAM,SAAS;AAAA,IAAY;AAAA,IAAS,CAAC,WACnC,OAAO,UAAU,WAAW,MAAM,KAClC,eAAe,OAAO,UAAU,EAAE,WAAW,eAAe,MAAM,CAAC;AAAA,EACrE;AACA,MAAI,OAAQ,QAAO;AAEnB,QAAM,IAAI,MAAM,2BAA2B,MAAM,EAAE;AACrD;AAEO,SAAS,mBAAmB,QAAuD;AACxF,MAAI,CAAC,OAAO,WAAY,QAAO;AAC/B,SAAO,GAAG,OAAO,WAAW,SAAS,IAAI,OAAO,WAAW,QAAQ;AACrE;;;AC3MA,SAAS,eAAuB;AAC9B,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACtD,WAAO,OAAO,WAAW;AAAA,EAC3B;AACA,SAAO,uCAAuC,QAAQ,SAAS,OAAK;AAClE,UAAM,IAAI,KAAK,OAAO,IAAI,KAAK;AAC/B,UAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAM;AACrC,WAAO,EAAE,SAAS,EAAE;AAAA,EACtB,CAAC;AACH;AAoBO,IAAM,sBAAN,MAA0B;AAAA,EACvB,WAAW,oBAAI,IAAiC;AAAA,EAExD,cAAc,SAAkD;AAC9D,UAAM,YAAY,QAAQ,aAAa,aAAa;AACpD,QAAI,KAAK,SAAS,IAAI,SAAS,GAAG;AAChC,YAAM,IAAI,MAAM,2BAA2B,SAAS,EAAE;AAAA,IACxD;AACA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,gBAAgB,QAAQ,WAC1B,CAAC;AAAA,MACC,UAAU,QAAQ;AAAA,MAClB,MAAM,QAAQ,cAAc;AAAA,MAC5B,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,YAAY;AAAA,IACd,CAAiC,IACjC,CAAC;AAEL,UAAM,SAA4B;AAAA,MAChC;AAAA,MACA,YAAY;AAAA,QACV;AAAA,QACA,MAAM,KAAK,KAAK,SAAS,OAAO,GAAG,CAAC,UAAU,MAAM,OAAO,UAAU;AAAA,MACvE;AAAA,MACA,aAAa,wBAAwB,OAAO;AAAA,MAC5C,gBAAgB,kBAAkB,QAAQ,SAAS;AAAA,MACnD,WAAW;AAAA,MACX,cAAc,QAAQ;AAAA,MACtB,UAAU,QAAQ;AAAA,MAClB,WAAW,QAAQ;AAAA,MACnB,eAAe,QAAQ;AAAA,MACvB,WAAW;AAAA,MACX,gBAAgB;AAAA,MAChB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,QAAQ;AAAA,QACN,iBAAiB;AAAA,QACjB,aAAa;AAAA,MACf;AAAA,MACA,MAAM,QAAQ,QAAQ,CAAC;AAAA,IACzB;AAEA,WAAO,OAAO;AAAA,MACZ,iBAAiB,uBAAuB,QAAQ,IAAI;AAAA,MACpD,iBAAiB,uBAAuB,QAAQ,IAAI;AAAA,MACpD,GAAG,OAAO;AAAA,IACZ;AAEA,SAAK,SAAS,IAAI,WAAW;AAAA,MAC3B;AAAA,MACA,QAAQ,IAAI,kBAAkB;AAAA,IAChC,CAAC;AAED,WAAO,KAAK,YAAY,MAAM;AAAA,EAChC;AAAA,EAEA,eAAe,QAA2B,UAAoE;AAC5G,UAAM,SAAS,KAAK,YAAY,MAAM;AACtC,SAAK,SAAS,IAAI,OAAO,WAAW;AAAA,MAClC,QAAQ;AAAA,MACR,SAAS,MAAM;AACb,cAAM,SAAS,IAAI,kBAAkB;AACrC,YAAI,SAAU,QAAO,QAAQ,QAAQ;AACrC,eAAO;AAAA,MACT,GAAG;AAAA,IACL,CAAC;AACD,WAAO,KAAK,YAAY,MAAM;AAAA,EAChC;AAAA,EAEA,eAAoC;AAClC,WAAO,MAAM,KAAK,KAAK,SAAS,OAAO,CAAC,EACrC,IAAI,WAAS,KAAK,YAAY,MAAM,MAAM,CAAC,EAC3C,KAAK,CAAC,GAAG,MAAM,EAAE,iBAAiB,EAAE,cAAc;AAAA,EACvD;AAAA,EAEA,WAAW,WAA6C;AACtD,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,WAAO,QAAQ,KAAK,YAAY,MAAM,MAAM,IAAI;AAAA,EAClD;AAAA,EAEA,aAAa,SAAkD;AAC7D,UAAM,QAAQ,KAAK,eAAe,QAAQ,SAAS;AACnD,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,qBAAqB;AAEzB,QAAI,QAAQ,eAAe,UAAU;AACnC,YAAM,uBAAuB,MAAM,OAAO,gBACvC,OAAO,YAAU,OAAO,SAAS,YAAY,OAAO,aAAa,QAAQ,QAAQ,EACjF,IAAI,YAAU,OAAO,QAAQ;AAChC,UAAI,qBAAqB,SAAS,GAAG;AACnC,cAAM,OAAO,kBAAkB,MAAM,OAAO,gBAAgB;AAAA,UAC1D,YAAU,EAAE,OAAO,SAAS,YAAY,OAAO,aAAa,QAAQ;AAAA,QACtE;AACA,YAAI,MAAM,OAAO,cAAc,qBAAqB,SAAS,MAAM,OAAO,WAAW,QAAQ,GAAG;AAC9F,+BAAqB;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,OAAO,gBAAgB,KAAK,YAAU,OAAO,aAAa,QAAQ,QAAQ;AAEjG,QAAI,UAAU;AACZ,eAAS,OAAO,QAAQ;AACxB,eAAS,WAAW,CAAC,CAAC,QAAQ;AAC9B,eAAS,aAAa;AAAA,IACxB,OAAO;AACL,YAAM,OAAO,gBAAgB,KAAK;AAAA,QAChC,UAAU,QAAQ;AAAA,QAClB,MAAM,QAAQ;AAAA,QACd,UAAU,CAAC,CAAC,QAAQ;AAAA,QACpB,YAAY;AAAA,QACZ,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAEA,QAAI,oBAAoB;AACtB,YAAM,OAAO,aAAa;AAAA,IAC5B;AAEA,UAAM,OAAO,iBAAiB;AAC9B,WAAO,KAAK,YAAY,MAAM,MAAM;AAAA,EACtC;AAAA,EAEA,aAAa,SAAkD;AAC7D,UAAM,QAAQ,KAAK,eAAe,QAAQ,SAAS;AACnD,UAAM,OAAO,kBAAkB,MAAM,OAAO,gBAAgB,OAAO,YAAU,OAAO,aAAa,QAAQ,QAAQ;AACjH,QAAI,MAAM,OAAO,YAAY,aAAa,QAAQ,UAAU;AAC1D,YAAM,OAAO,aAAa;AAAA,IAC5B;AACA,UAAM,OAAO,iBAAiB,KAAK,IAAI;AACvC,WAAO,KAAK,YAAY,MAAM,MAAM;AAAA,EACtC;AAAA,EAEA,aAAa,SAAiD;AAC5D,UAAM,QAAQ,KAAK,eAAe,QAAQ,SAAS;AACnD,QAAI,MAAM,OAAO,cAAc,MAAM,OAAO,WAAW,aAAa,QAAQ,YAAY,CAAC,QAAQ,OAAO;AACtG,YAAM,IAAI,MAAM,kBAAkB,MAAM,OAAO,WAAW,QAAQ,EAAE;AAAA,IACtE;AACA,UAAM,iBAAiB,MAAM,OAAO,gBAAgB,KAAK,YAAU,OAAO,aAAa,QAAQ,QAAQ;AACvG,QAAI,gBAAgB;AAClB,qBAAe,WAAW;AAC1B,qBAAe,aAAa,KAAK,IAAI;AAAA,IACvC;AACA,UAAM,OAAO,aAAa;AAAA,MACxB,UAAU,QAAQ;AAAA,MAClB,WAAW,QAAQ;AAAA,MACnB,YAAY,KAAK,IAAI;AAAA,IACvB;AACA,UAAM,OAAO,iBAAiB,KAAK,IAAI;AACvC,WAAO,KAAK,YAAY,MAAM,MAAM;AAAA,EACtC;AAAA,EAEA,aAAa,SAAiD;AAC5D,UAAM,QAAQ,KAAK,eAAe,QAAQ,SAAS;AACnD,UAAM,iBAAiB,MAAM,OAAO,gBAAgB,KAAK,YAAU,OAAO,aAAa,QAAQ,QAAQ;AACvG,QAAI,gBAAgB;AAClB,qBAAe,WAAW;AAC1B,qBAAe,aAAa,KAAK,IAAI;AAAA,IACvC;AACA,QAAI,MAAM,OAAO,YAAY,aAAa,QAAQ,UAAU;AAC1D,YAAM,OAAO,aAAa;AAAA,IAC5B;AACA,UAAM,OAAO,iBAAiB,KAAK,IAAI;AACvC,WAAO,KAAK,YAAY,MAAM,MAAM;AAAA,EACtC;AAAA,EAEA,aAAa,WAAmB,MAA0D;AACxF,UAAM,QAAQ,KAAK,eAAe,SAAS;AAC3C,UAAM,MAAM,MAAM,OAAO,OAAO,IAAI;AACpC,UAAM,OAAO,SAAS,MAAM,OAAO,SAAS;AAC5C,UAAM,OAAO,iBAAiB,KAAK,IAAI;AACvC,WAAO,EAAE,QAAQ,KAAK,YAAY,MAAM,MAAM,GAAG,IAAI;AAAA,EACvD;AAAA,EAEA,YAAY,WAAmB,UAAmB;AAChD,UAAM,QAAQ,KAAK,eAAe,SAAS;AAC3C,UAAM,OAAO,SAAS,MAAM,OAAO,SAAS;AAC5C,WAAO,MAAM,OAAO,SAAS,QAAQ;AAAA,EACvC;AAAA,EAEA,YAAY,WAAsC;AAChD,UAAM,QAAQ,KAAK,eAAe,SAAS;AAC3C,UAAM,OAAO,MAAM;AACnB,UAAM,OAAO,SAAS,MAAM,OAAO,SAAS;AAC5C,UAAM,OAAO,iBAAiB,KAAK,IAAI;AACvC,WAAO,KAAK,YAAY,MAAM,MAAM;AAAA,EACtC;AAAA,EAEA,kBAAkB,WAAmB,MAA+B,UAAU,OAA0B;AACtG,UAAM,QAAQ,KAAK,eAAe,SAAS;AAC3C,UAAM,OAAO,OAAO,UAChB,EAAE,GAAG,KAAK,IACV;AAAA,MACE,GAAI,MAAM,OAAO,QAAQ,CAAC;AAAA,MAC1B,GAAG;AAAA,IACL;AACJ,UAAM,OAAO,iBAAiB,KAAK,IAAI;AACvC,WAAO,KAAK,YAAY,MAAM,MAAM;AAAA,EACtC;AAAA,EAEA,YAAY,WAAmB,KAAiC;AAC9D,UAAM,QAAQ,KAAK,eAAe,SAAS;AAC3C,UAAM,OAAO,YAAY;AACzB,UAAM,OAAO,YAAY,MAAM,OAAO,aAAa,KAAK,IAAI;AAC5D,QAAI,OAAO,QAAQ,SAAU,OAAM,OAAO,QAAQ;AAClD,UAAM,OAAO,iBAAiB,KAAK,IAAI;AACvC,WAAO,KAAK,YAAY,MAAM,MAAM;AAAA,EACtC;AAAA,EAEA,YAAY,WAAmB,YAAkC,WAAW,aAAqD;AAC/H,UAAM,QAAQ,KAAK,eAAe,SAAS;AAC3C,UAAM,OAAO,YAAY;AACzB,UAAM,OAAO,iBAAiB,KAAK,IAAI;AACvC,QAAI,YAAa,OAAM,OAAO,cAAc;AAC5C,WAAO,KAAK,YAAY,MAAM,MAAM;AAAA,EACtC;AAAA,EAEA,aAAa,WAAmB,WAA0G;AACxI,UAAM,QAAQ,KAAK,eAAe,SAAS;AAC3C,UAAM,OAAO,YAAY;AACzB,UAAM,OAAO,iBAAiB,KAAK,IAAI;AACvC,WAAO,KAAK,YAAY,MAAM,MAAM;AAAA,EACtC;AAAA,EAEA,cAAc,WAA4B;AACxC,WAAO,KAAK,SAAS,OAAO,SAAS;AAAA,EACvC;AAAA,EAEQ,eAAe,WAAwC;AAC7D,UAAM,QAAQ,KAAK,SAAS,IAAI,SAAS;AACzC,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,oBAAoB,SAAS,EAAE;AAC3D,WAAO;AAAA,EACT;AAAA,EAEQ,YAAY,QAA8C;AAChE,WAAO;AAAA,MACL,GAAG;AAAA,MACH,eAAe;AAAA,QACb,GAAG,OAAO;AAAA,QACV,MAAM,CAAC,GAAG,OAAO,cAAc,IAAI;AAAA,QACnC,KAAK,OAAO,cAAc,MAAM,EAAE,GAAG,OAAO,cAAc,IAAI,IAAI;AAAA,MACpE;AAAA,MACA,YAAY,OAAO,aAAa,EAAE,GAAG,OAAO,WAAW,IAAI;AAAA,MAC3D,iBAAiB,OAAO,gBAAgB,IAAI,aAAW,EAAE,GAAG,OAAO,EAAE;AAAA,MACrE,QAAQ,EAAE,GAAG,OAAO,OAAO;AAAA,MAC3B,MAAM,EAAE,GAAG,OAAO,KAAK;AAAA,IACzB;AAAA,EACF;AACF;;;ACvRA,YAAY,QAAQ;AACpB,YAAYA,WAAU;AACtB,YAAY,SAAS;AACrB,SAAS,qBAAqB;AAU9B,SAASC,gBAAuB;AAC9B,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACtD,WAAO,OAAO,WAAW;AAAA,EAC3B;AACA,SAAO,uCAAuC,QAAQ,SAAS,OAAK;AAClE,UAAM,IAAI,KAAK,OAAO,IAAI,KAAK;AAC/B,UAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAM;AACrC,WAAO,EAAE,SAAS,EAAE;AAAA,EACtB,CAAC;AACH;AAkBO,SAAS,8BACd,UAAU,UACV,UAAsC,CAAC,GAClB;AACrB,QAAM,SAAS,OAAO,QAAQ,WAAW,WAAW,QAAQ,OAAO,KAAK,IAAI;AAG5E,MAAI,UAAU,CAAC,iBAAiB,KAAK,MAAM,GAAG;AAC5C,UAAM,IAAI,MAAM,gCAAgC,MAAM,oDAA+C;AAAA,EACvG;AACA,QAAM,SAAS,SAAS,IAAI,MAAM,KAAK;AACvC,QAAMC,YAAW,QAAQ,YAAY,QAAQ;AAC7C,MAAIA,cAAa,SAAS;AACxB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM,gBAAgB,OAAO,gBAAgB,MAAM;AAAA,IACrD;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAW,WAAQ,UAAO,GAAG,GAAG,OAAO,gBAAgB,MAAM,OAAO;AAAA,EACtE;AACF;AAEA,SAAS,kBAAkB,UAA2C;AACpE,SAAO,GAAG,KAAK,UAAU,QAAQ,CAAC;AAAA;AACpC;AAmBA,SAAS,iBAAiB,YAAyD;AACjF,MAAI,SAAS;AACb,QAAM,UAAU,IAAI,cAAc,MAAM;AACxC,QAAM,SAAS,CAAC,UAA2B;AACzC,cAAU,OAAO,UAAU,WAAW,QAAQ,QAAQ,MAAM,KAAK;AACjE,QAAI,eAAe,OAAO,QAAQ,IAAI;AACtC,WAAO,gBAAgB,GAAG;AACxB,YAAM,UAAU,OAAO,MAAM,GAAG,YAAY,EAAE,KAAK;AACnD,eAAS,OAAO,MAAM,eAAe,CAAC;AACtC,UAAI,SAAS;AACX,mBAAW,KAAK,MAAM,OAAO,CAA4B;AAAA,MAC3D;AACA,qBAAe,OAAO,QAAQ,IAAI;AAAA,IACpC;AAAA,EACF;AAaA,SAAO,MAAM,MAAc;AACzB,UAAM,YAAY,SAAS,QAAQ,IAAI;AACvC,aAAS;AACT,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAoBA,IAAM,qBAAqB,oBAAI,QAAoB;AAoB5C,IAAM,oBAAN,MAAwB;AAAA,EACpB;AAAA,EAED,SAA4B;AAAA,EAC5B,iBAAiB,oBAAI,IAA+F;AAAA,EACpH,iBAAiB,oBAAI,IAAuC;AAAA,EAC5D,sBAAsB,oBAAI,IAA+C;AAAA;AAAA,EAEzE,sBAAsB,oBAAI,QAAoB;AAAA,EAEtD,YAAY,UAAoC,CAAC,GAAG;AAClD,SAAK,WAAW,QAAQ,YAAY,8BAA8B,QAAQ,WAAW,QAAQ;AAAA,EAC/F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,aAAa,UAAiE;AAC5E,SAAK,oBAAoB,IAAI,QAAQ;AACrC,WAAO,MAAM;AACX,WAAK,oBAAoB,OAAO,QAAQ;AAAA,IAC1C;AAAA,EACF;AAAA,EAEQ,iBACN,QACA,QACA,OACA,mBAAmB,GACb;AACN,QAAI,KAAK,oBAAoB,IAAI,MAAM,EAAG;AAC1C,SAAK,oBAAoB,IAAI,MAAM;AAQnC,UAAM,iBAAiB,mBAAmB,IAAI,MAAM;AACpD,QAAI,CAAC,gBAAgB;AACnB,UAAI,KAAK,WAAW,OAAQ,MAAK,SAAS;AAC1C,UAAI;AAAE,eAAO,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAAa;AAE7C,YAAMC,WAAU,SAAS,IAAI,MAAM,gCAAgC,KAAK,SAAS,IAAI,GAAG;AACxF,iBAAW,UAAU,KAAK,eAAe,OAAO,EAAG,QAAO,OAAOA,QAAO;AACxE,WAAK,eAAe,MAAM;AAC1B;AAAA,IACF;AAEA,UAAM,kBAAkB,KAAK,eAAe;AAC5C,UAAM,UAAU,SAAS,IAAI,MAAM,2BAA2B,MAAM,KAAK,KAAK,SAAS,IAAI,GAAG;AAC9F,eAAW,UAAU,KAAK,eAAe,OAAO,GAAG;AACjD,aAAO,OAAO,OAAO;AAAA,IACvB;AACA,SAAK,eAAe,MAAM;AAK1B,QAAI,KAAK,WAAW,QAAQ;AAC1B,WAAK,SAAS;AAAA,IAChB;AACA,QAAI;AAAE,aAAO,QAAQ;AAAA,IAAG,QAAQ;AAAA,IAAa;AAE7C,UAAM,OAAkC;AAAA,MACtC;AAAA,MACA,cAAc,KAAK,SAAS;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,eAAW,YAAY,KAAK,qBAAqB;AAI/C,UAAI;AAAE,iBAAS,IAAI;AAAA,MAAG,QAAQ;AAAA,MAAa;AAAA,IAC7C;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,UAAU,CAAC,KAAK,OAAO,UAAW;AAE3C,QAAI,KAAK,QAAQ;AACf,UAAI;AAAE,aAAK,OAAO,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAAa;AAClD,WAAK,SAAS;AAAA,IAChB;AAEA,UAAM,SAAa,qBAAiB,KAAK,SAAS,IAAI;AACtD,SAAK,SAAS;AAOd,UAAM,SAAS,iBAAiB,CAAC,aAAa;AAC5C,UAAI,SAAS,SAAS,YAAY;AAChC,cAAM,SAAS,KAAK,eAAe,IAAI,SAAS,SAAS;AACzD,YAAI,QAAQ;AACV,eAAK,eAAe,OAAO,SAAS,SAAS;AAC7C,iBAAO,QAAQ,SAAS,QAAQ;AAAA,QAClC;AACA;AAAA,MACF;AAEA,UAAI,SAAS,SAAS,SAAS;AAC7B,mBAAW,YAAY,KAAK,eAAgB,UAAS,SAAS,KAAK;AAAA,MACrE;AAAA,IACF,CAAC;AACD,WAAO,GAAG,QAAQ,MAAM;AAcxB,UAAM,cAAc,MAAc;AAChC,UAAI;AACF,eAAO,OAAO,IAAI;AAAA,MACpB,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO,GAAG,SAAS,CAAC,UAAU;AAC5B,YAAM,YAAY,YAAY;AAC9B,WAAK,iBAAiB,QAAQ,SAAS,OAAO,UAAU,MAAM;AAAA,IAChE,CAAC;AAMD,WAAO,GAAG,SAAS,MAAM;AACvB,WAAK,iBAAiB,QAAQ,UAAU,QAAW,YAAY,EAAE,MAAM;AAAA,IACzE,CAAC;AACD,WAAO,GAAG,OAAO,MAAM;AACrB,WAAK,iBAAiB,QAAQ,SAAS,QAAW,YAAY,EAAE,MAAM;AAAA,IACxE,CAAC;AAED,UAAM,IAAI,QAAc,CAACC,UAAS,WAAW;AAC3C,aAAO,KAAK,WAAW,MAAM;AAC3B,2BAAmB,IAAI,MAAM;AAC7B,QAAAA,SAAQ;AAAA,MACV,CAAC;AACD,aAAO,KAAK,SAAS,MAAM;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA,EAEA,QAAQ,UAAyD;AAC/D,SAAK,eAAe,IAAI,QAAQ;AAChC,WAAO,MAAM;AACX,WAAK,eAAe,OAAO,QAAQ;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,MAAM,QAAqB,SAA8D;AACvF,UAAM,KAAK,QAAQ;AACnB,QAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,iCAAiC;AAEnE,UAAM,YAAYH,cAAa;AAC/B,UAAM,WAAuC;AAAA,MAC3C,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,IAAI,QAA6B,CAACG,UAAS,WAAW;AAC3E,YAAM,UAAU,WAAW,MAAM;AAC/B,aAAK,eAAe,OAAO,SAAS;AACpC,eAAO,IAAI,MAAM,6CAA6C,QAAQ,IAAI,GAAG,CAAC;AAAA,MAChF,GAAG,GAAM;AACT,WAAK,eAAe,IAAI,WAAW;AAAA,QACjC,SAAS,CAAC,UAAU;AAAE,uBAAa,OAAO;AAAG,UAAAA,SAAQ,KAAK;AAAA,QAAG;AAAA,QAC7D,QAAQ,CAAC,UAAU;AAAE,uBAAa,OAAO;AAAG,iBAAO,KAAK;AAAA,QAAG;AAAA,MAC7D,CAAC;AACD,WAAK,QAAQ,MAAM,kBAAkB,QAAQ,CAAC;AAAA,IAChD,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAM,SAAS,KAAK;AACpB,SAAK,SAAS;AAGd,SAAK,oBAAoB,IAAI,MAAM;AACnC,eAAW,UAAU,KAAK,eAAe,OAAO,GAAG;AACjD,aAAO,OAAO,IAAI,MAAM,4BAA4B,CAAC;AAAA,IACvD;AACA,SAAK,eAAe,MAAM;AAC1B,UAAM,IAAI,QAAc,CAACA,aAAY;AACnC,UAAI,UAAU;AACd,YAAM,OAAO,MAAM;AACjB,YAAI,QAAS;AACb,kBAAU;AACV,QAAAA,SAAQ;AAAA,MACV;AACA,aAAO,KAAK,SAAS,IAAI;AACzB,aAAO,IAAI;AACX,aAAO,QAAQ;AACf,iBAAW,MAAM,EAAE;AAAA,IACrB,CAAC;AAAA,EACH;AACF;AAEO,SAAS,uBAAuB,WAAmB,UAA4D;AACpH,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,cAAc,QAAmC,UAAyC;AACxG,SAAO,MAAM,kBAAkB,QAAQ,CAAC;AAC1C;;;AC1YA,YAAYC,aAAY;AACxB,YAAY,QAAQ;AACpB,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AAyBf,IAAM,0BAA0B;AAkBhC,SAAS,yBACZ,MAAyB,QAAQ,KACjC,UAAqB,YAAQ,GACvB;AACN,QAAM,WAAW,OAAO,IAAI,sBAAsB,WAAW,IAAI,kBAAkB,KAAK,IAAI;AAC5F,SAAO,YAAiB,WAAK,SAAS,uBAAuB;AACjE;AAQA,SAAS,8BAA8B,UAA0B;AAC7D,MAAI,UAAU;AACd,QAAM,YAAsB,CAAC;AAE7B,WAAS,QAAQ,GAAG,QAAQ,KAAK,SAAS;AACtC,QAAI;AACA,YAAM,OAAU,gBAAa,OAAO,OAAO;AAC3C,aAAO,UAAU,WAAW,IAAI,OAAY,WAAK,MAAM,GAAG,SAAS;AAAA,IACvE,QAAQ;AACJ,YAAM,SAAc,cAAQ,OAAO;AACnC,UAAI,WAAW,QAAS,QAAO;AAC/B,gBAAU,QAAa,eAAS,OAAO,CAAC;AACxC,gBAAU;AAAA,IACd;AAAA,EACJ;AACA,SAAO;AACX;AAOO,SAAS,yBACZ,KACAC,YAA4B,QAAQ,UAC9B;AACN,QAAM,WAAgB,cAAQ,GAAG;AACjC,QAAM,OAAO,8BAA8B,QAAQ,EAAE,QAAQ,WAAW,EAAE;AAC1E,SAAOA,cAAa,UAAU,KAAK,YAAY,IAAI;AACvD;AAGO,SAAS,2BACZ,WACA,UAAqB,YAAQ,GAC7BA,YAA4B,QAAQ,UAC7B;AACP,SAAO,yBAAyB,WAAWA,SAAQ,MAC3C,yBAA8B,WAAK,SAAS,uBAAuB,GAAGA,SAAQ;AAC1F;AAUO,SAAS,yBACZ,WACA,UAAqB,YAAQ,GAC7BA,YAA4B,QAAQ,UAC9B;AACN,MAAI,2BAA2B,WAAW,SAASA,SAAQ,EAAG,QAAO;AACrE,QAAM,YAAY,yBAAyB,WAAWA,SAAQ;AAC9D,SAAc,mBAAW,QAAQ,EAAE,OAAO,WAAW,MAAM,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC1F;;;AC5GA,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AAMf,SAAS,iBACZ,SACA,WACsB;AACtB,QAAM,MAA8B,CAAC;AACrC,QAAM,SAAS,EAAE,GAAG,SAAS,GAAI,aAAa,CAAC,EAAG;AAElD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,QAAI,OAAO,UAAU,SAAU;AAC/B,QAAI,GAAG,IAAI;AAAA,EACf;AAEA,aAAW,OAAO,OAAO,KAAK,GAAG,GAAG;AAChC,QACI,QAAQ,cACL,QAAQ,iBACR,QAAQ,kBACR,QAAQ,uBACR,IAAI,WAAW,MAAM,KACrB,IAAI,WAAW,aAAa,KAC5B,IAAI,WAAW,cAAc,KAC7B,IAAI,WAAW,gBAAgB,KAC/B,IAAI,WAAW,OAAO,KACtB,IAAI,WAAW,OAAO,KACtB,IAAI,WAAW,MAAM,KACrB,IAAI,WAAW,SAAS,KACxB,IAAI,WAAW,WAAW,GAC/B;AACE,aAAO,IAAI,GAAG;AAAA,IAClB;AAAA,EACJ;AAMA,SAAO,IAAI;AACX,SAAO,IAAI;AACX,SAAO,IAAI;AACX,SAAO,IAAI;AACX,SAAO,IAAI;AAsBX,SAAO,IAAI;AACX,SAAO,IAAI;AAoBX,SAAO,IAAI;AACX,SAAO,IAAI;AACX,SAAO,IAAI;AACX,SAAO,IAAI;AACX,SAAO,IAAI;AASX,SAAO,IAAI;AAEX,wBAAsB,GAAG;AACzB,SAAO;AACX;AAMO,SAAS,sBAAsB,KAAmC;AACrE,MAAI,IAAI,SAAU;AAElB,MAAI,CAAC,IAAI,QAAQ,IAAI,SAAS,eAAe;AACzC,QAAI,OAAO;AAAA,EACf;AACA,MAAI,CAAC,IAAI,UAAW,KAAI,YAAY;AAEpC,MAAI,QAAQ,aAAa,SAAS;AAC9B,QAAI,CAAC,IAAI,YAAa,KAAI,cAAc;AACxC,QAAI,CAAC,IAAI,SAAU,KAAI,WAAW;AAAA,EACtC;AACJ;AAUO,SAAS,oCACZ,OACI;AACJ,MAAO,aAAS,MAAM,QAAS;AAC/B,MAAI;AACA,UAAMC,MAAK,UAAQ,IAAI;AACvB,UAAM,SAAc,cAAa,cAAQ,UAAQ,QAAQ,UAAU,CAAC,GAAG,IAAI;AAC3E,UAAM,eAAe,GAAM,aAAS,CAAC,IAAO,SAAK,CAAC;AAClD,UAAM,SAAc,WAAK,QAAQ,aAAa,cAAc,cAAc;AAC1E,QAAIA,IAAG,WAAW,MAAM,GAAG;AACvB,YAAM,OAAOA,IAAG,SAAS,MAAM;AAC/B,UAAI,EAAE,KAAK,OAAO,KAAQ;AACtB,QAAAA,IAAG,UAAU,QAAQ,KAAK,OAAO,GAAK;AACtC,gBAAQ,mCAAmC,MAAM,EAAE;AAAA,MACvD;AAAA,IACJ;AAAA,EACJ,QAAQ;AAAA,EAER;AACJ;;;AC/GO,SAAS,8BACd,WACyB;AACzB,SAAO;AAAA,IACL,eAAe,UAAqC,CAAC,GAAoC;AACvF,aAAO,UAAU,QAAgC,wBAAwB,OAAkC;AAAA,IAC7G;AAAA,IACA,eAA6C;AAC3C,aAAO,UAAU,QAA6B,iBAAiB,CAAC,CAAC;AAAA,IACnE;AAAA,IACA,YAAY,WAAsD;AAChE,aAAO,UAAU,QAAkC,gBAAgB,EAAE,UAAU,CAAC;AAAA,IAClF;AAAA,IACA,cAAc,WAAmB,OAA4B,CAAC,GAAsC;AAClG,aAAO,UAAU,QAAkC,kBAAkB,EAAE,WAAW,OAAO,KAAK,UAAU,KAAK,CAAC;AAAA,IAChH;AAAA,IACA,cAAc,WAAsD;AAClE,aAAO,UAAU,QAAkC,kBAAkB,EAAE,UAAU,CAAC;AAAA,IACpF;AAAA,IACA,eAAe,WAAsD;AACnE,aAAO,UAAU,QAAkC,mBAAmB,EAAE,UAAU,CAAC;AAAA,IACrF;AAAA,IACA,WAAW,WAAmB,QAAmD;AAC/E,aAAO,UAAU,QAAkC,eAAe,EAAE,WAAW,OAAO,CAAC;AAAA,IACzF;AAAA,IACA,kBAAkB,WAAmB,UAAqD;AACxF,aAAO,UAAU,QAAkC,uBAAuB,EAAE,WAAW,SAAS,CAAC;AAAA,IACnG;AAAA,IACA,uBAAuB,UAAyC,CAAC,GAA8C;AAC7G,aAAO,UAAU,QAA0C,4BAA4B,OAAkC;AAAA,IAC3H;AAAA,IACA,aAAa,SAAiE;AAC5E,aAAO,UAAU,QAAkC,iBAAiB,OAA6C;AAAA,IACnH;AAAA,IACA,aAAa,SAAiE;AAC5E,aAAO,UAAU,QAAkC,iBAAiB,OAA6C;AAAA,IACnH;AAAA,EACF;AACF;","names":["path","generateUUID","platform","failure","resolve","crypto","os","path","platform","os","path","fs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "1.0.58-rc.83",
3
+ "version": "1.0.58-rc.84",
4
4
  "description": "ADHDev local session host core — session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/ipc.ts CHANGED
@@ -145,6 +145,16 @@ export interface SessionHostDisconnectInfo {
145
145
  /** In-flight requests abandoned by this disconnect (they were rejected). */
146
146
  pendingRequests: number;
147
147
  error?: Error;
148
+ /**
149
+ * Bytes discarded from a newline-unterminated frame at EOF (0 when the peer
150
+ * hung up on a clean frame boundary, which is the normal case).
151
+ *
152
+ * Non-zero means the peer was cut off MID-WRITE — diagnostic context that
153
+ * distinguishes "the host exited" from "the host died with a frame in
154
+ * flight". Before the parser's `end()` was reachable this was unknowable:
155
+ * the bytes were dropped with the decoder and no counter existed.
156
+ */
157
+ droppedTailBytes: number;
148
158
  }
149
159
 
150
160
  export class SessionHostClient {
@@ -180,7 +190,12 @@ export class SessionHostClient {
180
190
  };
181
191
  }
182
192
 
183
- private handleDisconnect(socket: net.Socket, reason: SessionHostDisconnectReason, error?: Error): void {
193
+ private handleDisconnect(
194
+ socket: net.Socket,
195
+ reason: SessionHostDisconnectReason,
196
+ error?: Error,
197
+ droppedTailBytes = 0,
198
+ ): void {
184
199
  if (this.disconnectedSockets.has(socket)) return;
185
200
  this.disconnectedSockets.add(socket);
186
201
 
@@ -221,6 +236,7 @@ export class SessionHostClient {
221
236
  endpointPath: this.endpoint.path,
222
237
  pendingRequests,
223
238
  error,
239
+ droppedTailBytes,
224
240
  };
225
241
  for (const listener of this.disconnectListeners) {
226
242
  // A throwing observer must not suppress the remaining ones, and must not
@@ -241,7 +257,12 @@ export class SessionHostClient {
241
257
  const socket = net.createConnection(this.endpoint.path);
242
258
  this.socket = socket;
243
259
 
244
- socket.on('data', createLineParser((envelope) => {
260
+ // PARSER-EOF-FLUSH: keep the parser handle so its EOF flush is reachable.
261
+ // Inlining `createLineParser(...)` into the 'data' registration discarded
262
+ // the only reference to `end()`, so bytes held back by the StringDecoder as
263
+ // an incomplete UTF-8 sequence were lost with the decoder when the host
264
+ // disconnected.
265
+ const parser = createLineParser((envelope) => {
245
266
  if (envelope.kind === 'response') {
246
267
  const waiter = this.requestWaiters.get(envelope.requestId);
247
268
  if (waiter) {
@@ -254,10 +275,32 @@ export class SessionHostClient {
254
275
  if (envelope.kind === 'event') {
255
276
  for (const listener of this.eventListeners) listener(envelope.event);
256
277
  }
257
- }));
278
+ });
279
+ socket.on('data', parser);
280
+
281
+ /**
282
+ * Drain the parser at EOF. The remainder is a partial, newline-unterminated
283
+ * frame — a truncated transmission rather than a message — so it is NOT
284
+ * parsed. Feeding a half-written envelope to JSON.parse here would throw
285
+ * inside a socket event handler with no catch above it, converting a host
286
+ * that died mid-write into an uncaught exception in this process. That is
287
+ * exactly the failure the parser's contract set out to avoid.
288
+ *
289
+ * Reported through the existing disconnect surface rather than a new one:
290
+ * a non-empty tail means the peer was cut off mid-frame, which is
291
+ * diagnostic context for the disconnect that is about to be emitted.
292
+ */
293
+ const flushParser = (): string => {
294
+ try {
295
+ return parser.end();
296
+ } catch {
297
+ return '';
298
+ }
299
+ };
258
300
 
259
301
  socket.on('error', (error) => {
260
- this.handleDisconnect(socket, 'error', error);
302
+ const remainder = flushParser();
303
+ this.handleDisconnect(socket, 'error', error, remainder.length);
261
304
  });
262
305
 
263
306
  // 'close'/'end' are the clean-FIN counterparts of 'error'. The host server
@@ -265,10 +308,10 @@ export class SessionHostClient {
265
308
  // side handled neither, so host death was observable only if it happened to
266
309
  // surface as a socket error.
267
310
  socket.on('close', () => {
268
- this.handleDisconnect(socket, 'closed');
311
+ this.handleDisconnect(socket, 'closed', undefined, flushParser().length);
269
312
  });
270
313
  socket.on('end', () => {
271
- this.handleDisconnect(socket, 'ended');
314
+ this.handleDisconnect(socket, 'ended', undefined, flushParser().length);
272
315
  });
273
316
 
274
317
  await new Promise<void>((resolve, reject) => {