@adhdev/session-host-core 1.0.17 → 1.0.18-rc.10

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
@@ -54,6 +54,36 @@ interface SessionBufferState {
54
54
  snapshotSeq: number;
55
55
  }
56
56
  type SessionHostSurfaceKind = 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
57
+ /**
58
+ * Compact, secret-free record of how a session's PTY process terminated.
59
+ *
60
+ * Preserves the nullable/unknown exitCode and signal exactly as observed from
61
+ * the first node-pty `onExit` — an `exitCode` of `null` means "unknown" (the
62
+ * process was terminated by a signal, or the exit code was otherwise not
63
+ * reported) and MUST NOT be collapsed to 0. Persisted as a tombstone so
64
+ * termination diagnostics remain inspectable after the live runtime and its
65
+ * transport adapter are torn down.
66
+ */
67
+ interface SessionTermination {
68
+ /** Exit code exactly as observed; `null` = unknown / signal-terminated. Never fabricated to 0. */
69
+ exitCode: number | null;
70
+ /** Signal number that terminated the process, if any; `null` when not signalled. */
71
+ signal: number | null;
72
+ /** Classification derived from (exitCode, signal): clean exit vs. failure vs. unknown. */
73
+ reason: 'exit' | 'signal' | 'failed' | 'unknown';
74
+ /** Lifecycle recorded for this termination (`stopped` | `failed`). */
75
+ lifecycle: Extract<SessionLifecycle, 'stopped' | 'failed'>;
76
+ /** Wall-clock time the termination was observed. */
77
+ terminatedAt: number;
78
+ /** OS pid of the terminated process, if it was known. */
79
+ osPid?: number;
80
+ /** Lifecycle the session was in immediately before termination. */
81
+ previousLifecycle?: SessionLifecycle;
82
+ /** Last time output was observed from the session, if known. */
83
+ lastOutputAt?: number;
84
+ /** Whether the termination followed an explicit stop/delete request, and which. */
85
+ requestedStop?: 'stop' | 'delete' | 'restart' | 'prune';
86
+ }
57
87
  interface SessionHostRecord {
58
88
  sessionId: string;
59
89
  runtimeKey: string;
@@ -74,6 +104,8 @@ interface SessionHostRecord {
74
104
  attachedClients: SessionAttachedClient[];
75
105
  buffer: SessionBufferState;
76
106
  meta: Record<string, unknown>;
107
+ /** Termination tombstone, stamped when the PTY process exits. Retained for post-mortem inspection. */
108
+ termination?: SessionTermination;
77
109
  }
78
110
  interface CreateSessionPayload {
79
111
  sessionId?: string;
@@ -310,6 +342,8 @@ type SessionHostEvent = {
310
342
  type: 'session_exit';
311
343
  sessionId: string;
312
344
  exitCode: number | null;
345
+ signal?: number | null;
346
+ termination?: SessionTermination;
313
347
  } | {
314
348
  type: 'session_stopped';
315
349
  sessionId: string;
@@ -359,6 +393,30 @@ interface SessionHostEventEnvelope {
359
393
  }
360
394
  type SessionHostWireEnvelope = SessionHostRequestEnvelope | SessionHostResponseEnvelope | SessionHostEventEnvelope;
361
395
 
396
+ interface ClassifyTerminationInput {
397
+ exitCode: number | null;
398
+ signal?: number | null;
399
+ osPid?: number;
400
+ previousLifecycle?: SessionLifecycle;
401
+ lastOutputAt?: number;
402
+ requestedStop?: SessionTermination['requestedStop'];
403
+ terminatedAt: number;
404
+ }
405
+ /**
406
+ * Classify a PTY termination from its raw (exitCode, signal) pair.
407
+ *
408
+ * Invariants:
409
+ * - A `null` exitCode is UNKNOWN, never a clean exit — it MUST NOT collapse to
410
+ * exit 0. This is the regression guard for the old `exitCode ?? 0` behavior.
411
+ * - A non-null, non-zero exitCode or any signal is a failure.
412
+ * - Only an explicit exit code of 0 (with no signal) is a clean stop.
413
+ *
414
+ * The returned lifecycle is `stopped` only for a clean exit 0; every other
415
+ * case (nonzero, signalled, or unknown) is `failed` so that an unknown
416
+ * termination stays distinguishable from a successful one.
417
+ */
418
+ declare function classifyTermination(input: ClassifyTerminationInput): SessionTermination;
419
+
362
420
  interface SessionRingBufferOptions {
363
421
  maxBytes?: number;
364
422
  }
@@ -404,7 +462,7 @@ declare class SessionHostRegistry {
404
462
  clearBuffer(sessionId: string): SessionHostRecord;
405
463
  updateSessionMeta(sessionId: string, meta: Record<string, unknown>, replace?: boolean): SessionHostRecord;
406
464
  markStarted(sessionId: string, pid?: number): SessionHostRecord;
407
- markStopped(sessionId: string, lifecycle?: 'stopped' | 'failed'): SessionHostRecord;
465
+ markStopped(sessionId: string, lifecycle?: 'stopped' | 'failed', termination?: SessionTermination): SessionHostRecord;
408
466
  setLifecycle(sessionId: string, lifecycle: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted'): SessionHostRecord;
409
467
  deleteSession(sessionId: string): boolean;
410
468
  private requireSession;
@@ -530,4 +588,4 @@ interface SessionHostControlTransport {
530
588
  */
531
589
  declare function createSessionHostControlPlane(transport: SessionHostControlTransport): SessionHostControlPlane;
532
590
 
533
- export { type AcquireWritePayload, type AttachSessionPayload, type ClearSessionBufferPayload, type CreateSessionPayload, DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, type DetachSessionPayload, type ForceDetachClientPayload, type GetHostDiagnosticsPayload, type GetSnapshotPayload, type GetTerminalSnapshotPayload, type PruneDuplicateSessionsPayload, type ReleaseWritePayload, type ResizeSessionPayload, type RestartSessionPayload, type ResumeSessionPayload, SESSION_HOST_SUPPORTED_REQUEST_TYPES, type SendInputPayload, type SendSignalPayload, type SessionAttachedClient, type SessionBufferSnapshot, type SessionClientType, type SessionHostCategory, SessionHostClient, type SessionHostClientOptions, type SessionHostControlPlane, type SessionHostControlTransport, type SessionHostDiagnostics, type SessionHostDuplicateSessionGroup, type SessionHostEndpoint, type SessionHostEvent, type SessionHostEventEnvelope, type SessionHostLogEntry, type SessionHostPruneDuplicatesResult, type SessionHostRecord, SessionHostRegistry, type SessionHostRequest, type SessionHostRequestEnvelope, type SessionHostRequestTrace, type SessionHostRequestType, type SessionHostResponse, type SessionHostResponseEnvelope, type SessionHostRuntimeTransition, type SessionHostSurfaceKind, type SessionHostSurfaceRecordLike, type SessionHostWireEnvelope, type SessionLaunchCommand, type SessionLifecycle, type SessionOwnerType, SessionRingBuffer, type SessionRingBufferOptions, type SessionTerminalSnapshot, type SessionTerminalState, type SessionTransport, type SessionWriteOwner, type StopSessionPayload, type UpdateSessionMetaPayload, applyTerminalColorEnv, buildRuntimeDisplayName, buildRuntimeKey, createLineParser, createResponseEnvelope, createSessionHostControlPlane, ensureNodePtySpawnHelperPermissions, formatRuntimeOwner, getDefaultSessionHostEndpoint, getSessionHostRecoveryLabel, getSessionHostSurfaceKind, getWorkspaceLabel, isSessionHostLiveRuntime, isSessionHostRecoverySnapshot, partitionSessionHostDiagnosticsSessions, partitionSessionHostRecords, resolveAttachableRuntimeRecord, resolveRuntimeRecord, sanitizeSpawnEnv, writeEnvelope };
591
+ export { type AcquireWritePayload, type AttachSessionPayload, type ClearSessionBufferPayload, type CreateSessionPayload, DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, type DetachSessionPayload, type ForceDetachClientPayload, type GetHostDiagnosticsPayload, type GetSnapshotPayload, type GetTerminalSnapshotPayload, type PruneDuplicateSessionsPayload, type ReleaseWritePayload, type ResizeSessionPayload, type RestartSessionPayload, type ResumeSessionPayload, SESSION_HOST_SUPPORTED_REQUEST_TYPES, type SendInputPayload, type SendSignalPayload, type SessionAttachedClient, type SessionBufferSnapshot, type SessionClientType, type SessionHostCategory, SessionHostClient, type SessionHostClientOptions, type SessionHostControlPlane, type SessionHostControlTransport, type SessionHostDiagnostics, type SessionHostDuplicateSessionGroup, type SessionHostEndpoint, type SessionHostEvent, type SessionHostEventEnvelope, type SessionHostLogEntry, type SessionHostPruneDuplicatesResult, type SessionHostRecord, SessionHostRegistry, type SessionHostRequest, type SessionHostRequestEnvelope, type SessionHostRequestTrace, type SessionHostRequestType, type SessionHostResponse, type SessionHostResponseEnvelope, type SessionHostRuntimeTransition, type SessionHostSurfaceKind, type SessionHostSurfaceRecordLike, type SessionHostWireEnvelope, type SessionLaunchCommand, type SessionLifecycle, type SessionOwnerType, SessionRingBuffer, type SessionRingBufferOptions, type SessionTerminalSnapshot, type SessionTerminalState, type SessionTermination, type SessionTransport, type SessionWriteOwner, type StopSessionPayload, type UpdateSessionMetaPayload, applyTerminalColorEnv, buildRuntimeDisplayName, buildRuntimeKey, classifyTermination, createLineParser, createResponseEnvelope, createSessionHostControlPlane, ensureNodePtySpawnHelperPermissions, formatRuntimeOwner, getDefaultSessionHostEndpoint, getSessionHostRecoveryLabel, getSessionHostSurfaceKind, getWorkspaceLabel, isSessionHostLiveRuntime, isSessionHostRecoverySnapshot, partitionSessionHostDiagnosticsSessions, partitionSessionHostRecords, resolveAttachableRuntimeRecord, resolveRuntimeRecord, sanitizeSpawnEnv, writeEnvelope };
package/dist/index.d.ts CHANGED
@@ -54,6 +54,36 @@ interface SessionBufferState {
54
54
  snapshotSeq: number;
55
55
  }
56
56
  type SessionHostSurfaceKind = 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
57
+ /**
58
+ * Compact, secret-free record of how a session's PTY process terminated.
59
+ *
60
+ * Preserves the nullable/unknown exitCode and signal exactly as observed from
61
+ * the first node-pty `onExit` — an `exitCode` of `null` means "unknown" (the
62
+ * process was terminated by a signal, or the exit code was otherwise not
63
+ * reported) and MUST NOT be collapsed to 0. Persisted as a tombstone so
64
+ * termination diagnostics remain inspectable after the live runtime and its
65
+ * transport adapter are torn down.
66
+ */
67
+ interface SessionTermination {
68
+ /** Exit code exactly as observed; `null` = unknown / signal-terminated. Never fabricated to 0. */
69
+ exitCode: number | null;
70
+ /** Signal number that terminated the process, if any; `null` when not signalled. */
71
+ signal: number | null;
72
+ /** Classification derived from (exitCode, signal): clean exit vs. failure vs. unknown. */
73
+ reason: 'exit' | 'signal' | 'failed' | 'unknown';
74
+ /** Lifecycle recorded for this termination (`stopped` | `failed`). */
75
+ lifecycle: Extract<SessionLifecycle, 'stopped' | 'failed'>;
76
+ /** Wall-clock time the termination was observed. */
77
+ terminatedAt: number;
78
+ /** OS pid of the terminated process, if it was known. */
79
+ osPid?: number;
80
+ /** Lifecycle the session was in immediately before termination. */
81
+ previousLifecycle?: SessionLifecycle;
82
+ /** Last time output was observed from the session, if known. */
83
+ lastOutputAt?: number;
84
+ /** Whether the termination followed an explicit stop/delete request, and which. */
85
+ requestedStop?: 'stop' | 'delete' | 'restart' | 'prune';
86
+ }
57
87
  interface SessionHostRecord {
58
88
  sessionId: string;
59
89
  runtimeKey: string;
@@ -74,6 +104,8 @@ interface SessionHostRecord {
74
104
  attachedClients: SessionAttachedClient[];
75
105
  buffer: SessionBufferState;
76
106
  meta: Record<string, unknown>;
107
+ /** Termination tombstone, stamped when the PTY process exits. Retained for post-mortem inspection. */
108
+ termination?: SessionTermination;
77
109
  }
78
110
  interface CreateSessionPayload {
79
111
  sessionId?: string;
@@ -310,6 +342,8 @@ type SessionHostEvent = {
310
342
  type: 'session_exit';
311
343
  sessionId: string;
312
344
  exitCode: number | null;
345
+ signal?: number | null;
346
+ termination?: SessionTermination;
313
347
  } | {
314
348
  type: 'session_stopped';
315
349
  sessionId: string;
@@ -359,6 +393,30 @@ interface SessionHostEventEnvelope {
359
393
  }
360
394
  type SessionHostWireEnvelope = SessionHostRequestEnvelope | SessionHostResponseEnvelope | SessionHostEventEnvelope;
361
395
 
396
+ interface ClassifyTerminationInput {
397
+ exitCode: number | null;
398
+ signal?: number | null;
399
+ osPid?: number;
400
+ previousLifecycle?: SessionLifecycle;
401
+ lastOutputAt?: number;
402
+ requestedStop?: SessionTermination['requestedStop'];
403
+ terminatedAt: number;
404
+ }
405
+ /**
406
+ * Classify a PTY termination from its raw (exitCode, signal) pair.
407
+ *
408
+ * Invariants:
409
+ * - A `null` exitCode is UNKNOWN, never a clean exit — it MUST NOT collapse to
410
+ * exit 0. This is the regression guard for the old `exitCode ?? 0` behavior.
411
+ * - A non-null, non-zero exitCode or any signal is a failure.
412
+ * - Only an explicit exit code of 0 (with no signal) is a clean stop.
413
+ *
414
+ * The returned lifecycle is `stopped` only for a clean exit 0; every other
415
+ * case (nonzero, signalled, or unknown) is `failed` so that an unknown
416
+ * termination stays distinguishable from a successful one.
417
+ */
418
+ declare function classifyTermination(input: ClassifyTerminationInput): SessionTermination;
419
+
362
420
  interface SessionRingBufferOptions {
363
421
  maxBytes?: number;
364
422
  }
@@ -404,7 +462,7 @@ declare class SessionHostRegistry {
404
462
  clearBuffer(sessionId: string): SessionHostRecord;
405
463
  updateSessionMeta(sessionId: string, meta: Record<string, unknown>, replace?: boolean): SessionHostRecord;
406
464
  markStarted(sessionId: string, pid?: number): SessionHostRecord;
407
- markStopped(sessionId: string, lifecycle?: 'stopped' | 'failed'): SessionHostRecord;
465
+ markStopped(sessionId: string, lifecycle?: 'stopped' | 'failed', termination?: SessionTermination): SessionHostRecord;
408
466
  setLifecycle(sessionId: string, lifecycle: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted'): SessionHostRecord;
409
467
  deleteSession(sessionId: string): boolean;
410
468
  private requireSession;
@@ -530,4 +588,4 @@ interface SessionHostControlTransport {
530
588
  */
531
589
  declare function createSessionHostControlPlane(transport: SessionHostControlTransport): SessionHostControlPlane;
532
590
 
533
- export { type AcquireWritePayload, type AttachSessionPayload, type ClearSessionBufferPayload, type CreateSessionPayload, DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, type DetachSessionPayload, type ForceDetachClientPayload, type GetHostDiagnosticsPayload, type GetSnapshotPayload, type GetTerminalSnapshotPayload, type PruneDuplicateSessionsPayload, type ReleaseWritePayload, type ResizeSessionPayload, type RestartSessionPayload, type ResumeSessionPayload, SESSION_HOST_SUPPORTED_REQUEST_TYPES, type SendInputPayload, type SendSignalPayload, type SessionAttachedClient, type SessionBufferSnapshot, type SessionClientType, type SessionHostCategory, SessionHostClient, type SessionHostClientOptions, type SessionHostControlPlane, type SessionHostControlTransport, type SessionHostDiagnostics, type SessionHostDuplicateSessionGroup, type SessionHostEndpoint, type SessionHostEvent, type SessionHostEventEnvelope, type SessionHostLogEntry, type SessionHostPruneDuplicatesResult, type SessionHostRecord, SessionHostRegistry, type SessionHostRequest, type SessionHostRequestEnvelope, type SessionHostRequestTrace, type SessionHostRequestType, type SessionHostResponse, type SessionHostResponseEnvelope, type SessionHostRuntimeTransition, type SessionHostSurfaceKind, type SessionHostSurfaceRecordLike, type SessionHostWireEnvelope, type SessionLaunchCommand, type SessionLifecycle, type SessionOwnerType, SessionRingBuffer, type SessionRingBufferOptions, type SessionTerminalSnapshot, type SessionTerminalState, type SessionTransport, type SessionWriteOwner, type StopSessionPayload, type UpdateSessionMetaPayload, applyTerminalColorEnv, buildRuntimeDisplayName, buildRuntimeKey, createLineParser, createResponseEnvelope, createSessionHostControlPlane, ensureNodePtySpawnHelperPermissions, formatRuntimeOwner, getDefaultSessionHostEndpoint, getSessionHostRecoveryLabel, getSessionHostSurfaceKind, getWorkspaceLabel, isSessionHostLiveRuntime, isSessionHostRecoverySnapshot, partitionSessionHostDiagnosticsSessions, partitionSessionHostRecords, resolveAttachableRuntimeRecord, resolveRuntimeRecord, sanitizeSpawnEnv, writeEnvelope };
591
+ export { type AcquireWritePayload, type AttachSessionPayload, type ClearSessionBufferPayload, type CreateSessionPayload, DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, type DetachSessionPayload, type ForceDetachClientPayload, type GetHostDiagnosticsPayload, type GetSnapshotPayload, type GetTerminalSnapshotPayload, type PruneDuplicateSessionsPayload, type ReleaseWritePayload, type ResizeSessionPayload, type RestartSessionPayload, type ResumeSessionPayload, SESSION_HOST_SUPPORTED_REQUEST_TYPES, type SendInputPayload, type SendSignalPayload, type SessionAttachedClient, type SessionBufferSnapshot, type SessionClientType, type SessionHostCategory, SessionHostClient, type SessionHostClientOptions, type SessionHostControlPlane, type SessionHostControlTransport, type SessionHostDiagnostics, type SessionHostDuplicateSessionGroup, type SessionHostEndpoint, type SessionHostEvent, type SessionHostEventEnvelope, type SessionHostLogEntry, type SessionHostPruneDuplicatesResult, type SessionHostRecord, SessionHostRegistry, type SessionHostRequest, type SessionHostRequestEnvelope, type SessionHostRequestTrace, type SessionHostRequestType, type SessionHostResponse, type SessionHostResponseEnvelope, type SessionHostRuntimeTransition, type SessionHostSurfaceKind, type SessionHostSurfaceRecordLike, type SessionHostWireEnvelope, type SessionLaunchCommand, type SessionLifecycle, type SessionOwnerType, SessionRingBuffer, type SessionRingBufferOptions, type SessionTerminalSnapshot, type SessionTerminalState, type SessionTermination, type SessionTransport, type SessionWriteOwner, type StopSessionPayload, type UpdateSessionMetaPayload, applyTerminalColorEnv, buildRuntimeDisplayName, buildRuntimeKey, classifyTermination, createLineParser, createResponseEnvelope, createSessionHostControlPlane, ensureNodePtySpawnHelperPermissions, formatRuntimeOwner, getDefaultSessionHostEndpoint, getSessionHostRecoveryLabel, getSessionHostSurfaceKind, getWorkspaceLabel, isSessionHostLiveRuntime, isSessionHostRecoverySnapshot, partitionSessionHostDiagnosticsSessions, partitionSessionHostRecords, resolveAttachableRuntimeRecord, resolveRuntimeRecord, sanitizeSpawnEnv, writeEnvelope };
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ __export(index_exports, {
40
40
  applyTerminalColorEnv: () => applyTerminalColorEnv,
41
41
  buildRuntimeDisplayName: () => buildRuntimeDisplayName,
42
42
  buildRuntimeKey: () => buildRuntimeKey,
43
+ classifyTermination: () => classifyTermination,
43
44
  createLineParser: () => createLineParser,
44
45
  createResponseEnvelope: () => createResponseEnvelope,
45
46
  createSessionHostControlPlane: () => createSessionHostControlPlane,
@@ -86,6 +87,39 @@ var SESSION_HOST_SUPPORTED_REQUEST_TYPES = [
86
87
  "list_sessions"
87
88
  ];
88
89
 
90
+ // src/termination.ts
91
+ function classifyTermination(input) {
92
+ const { exitCode, terminatedAt } = input;
93
+ const signal = input.signal ?? null;
94
+ let reason;
95
+ let lifecycle;
96
+ if (signal !== null && signal !== 0) {
97
+ reason = "signal";
98
+ lifecycle = "failed";
99
+ } else if (exitCode === null) {
100
+ reason = "unknown";
101
+ lifecycle = "failed";
102
+ } else if (exitCode === 0) {
103
+ reason = "exit";
104
+ lifecycle = "stopped";
105
+ } else {
106
+ reason = "failed";
107
+ lifecycle = "failed";
108
+ }
109
+ const termination = {
110
+ exitCode,
111
+ signal,
112
+ reason,
113
+ lifecycle,
114
+ terminatedAt
115
+ };
116
+ if (typeof input.osPid === "number") termination.osPid = input.osPid;
117
+ if (input.previousLifecycle) termination.previousLifecycle = input.previousLifecycle;
118
+ if (typeof input.lastOutputAt === "number") termination.lastOutputAt = input.lastOutputAt;
119
+ if (input.requestedStop) termination.requestedStop = input.requestedStop;
120
+ return termination;
121
+ }
122
+
89
123
  // src/buffer.ts
90
124
  var DEFAULT_SESSION_RING_BUFFER_MAX_BYTES = 4 * 1024 * 1024;
91
125
  var SessionRingBuffer = class {
@@ -492,10 +526,11 @@ var SessionHostRegistry = class {
492
526
  state.record.lastActivityAt = Date.now();
493
527
  return this.cloneRecord(state.record);
494
528
  }
495
- markStopped(sessionId, lifecycle = "stopped") {
529
+ markStopped(sessionId, lifecycle = "stopped", termination) {
496
530
  const state = this.requireSession(sessionId);
497
531
  state.record.lifecycle = lifecycle;
498
532
  state.record.lastActivityAt = Date.now();
533
+ if (termination) state.record.termination = termination;
499
534
  return this.cloneRecord(state.record);
500
535
  }
501
536
  setLifecycle(sessionId, lifecycle) {
@@ -799,6 +834,7 @@ function createSessionHostControlPlane(transport) {
799
834
  applyTerminalColorEnv,
800
835
  buildRuntimeDisplayName,
801
836
  buildRuntimeKey,
837
+ classifyTermination,
802
838
  createLineParser,
803
839
  createResponseEnvelope,
804
840
  createSessionHostControlPlane,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/buffer.ts","../src/defaults.ts","../src/runtime-labels.ts","../src/registry.ts","../src/ipc.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 SessionHostWireEnvelope,\n SessionWriteOwner,\n StopSessionPayload,\n} from './types.js';\n\nexport { SESSION_HOST_SUPPORTED_REQUEST_TYPES } from './types.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 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 { SessionHostClientOptions, SessionHostEndpoint } from './ipc.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\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}\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 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 }\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 { 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}\n\nconst LIVE_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'interrupted']);\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 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} 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'): 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 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 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 function getDefaultSessionHostEndpoint(appName = 'adhdev'): SessionHostEndpoint {\n if (process.platform === 'win32') {\n return {\n kind: 'pipe',\n path: `\\\\\\\\.\\\\pipe\\\\${appName}-session-host`,\n };\n }\n\n return {\n kind: 'unix',\n path: path.join(os.tmpdir(), `${appName}-session-host.sock`),\n };\n}\n\nfunction serializeEnvelope(envelope: SessionHostWireEnvelope): string {\n return `${JSON.stringify(envelope)}\\n`;\n}\n\nfunction createLineParser(onEnvelope: (envelope: SessionHostWireEnvelope) => void) {\n let buffer = '';\n return (chunk: Buffer | string) => {\n buffer += chunk.toString();\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\nexport interface SessionHostClientOptions {\n endpoint?: SessionHostEndpoint;\n appName?: string;\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\n constructor(options: SessionHostClientOptions = {}) {\n this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || 'adhdev');\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 for (const waiter of this.requestWaiters.values()) {\n waiter.reject(error);\n }\n this.requestWaiters.clear();\n // Clear the dead socket reference so the next connect() creates a fresh connection\n // instead of skipping reconnection on the `!this.socket.destroyed` guard.\n if (this.socket === socket) {\n this.socket = null;\n }\n try { socket.destroy(); } catch { /* noop */ }\n });\n\n await new Promise<void>((resolve, reject) => {\n socket.once('connect', () => resolve());\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 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","/**\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 // 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 // Scoped to win32 for now: the macOS/Linux path works today and we want to\n // keep the blast radius small. Safe to make unconditional later — a\n // daemon-spawned claude should always be a fresh top-level session.\n if (process.platform === 'win32') {\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 }\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","resolve","os","path"]}
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/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 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 { SessionHostClientOptions, SessionHostEndpoint } from './ipc.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 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}\n\nconst LIVE_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'interrupted']);\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 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 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 function getDefaultSessionHostEndpoint(appName = 'adhdev'): SessionHostEndpoint {\n if (process.platform === 'win32') {\n return {\n kind: 'pipe',\n path: `\\\\\\\\.\\\\pipe\\\\${appName}-session-host`,\n };\n }\n\n return {\n kind: 'unix',\n path: path.join(os.tmpdir(), `${appName}-session-host.sock`),\n };\n}\n\nfunction serializeEnvelope(envelope: SessionHostWireEnvelope): string {\n return `${JSON.stringify(envelope)}\\n`;\n}\n\nfunction createLineParser(onEnvelope: (envelope: SessionHostWireEnvelope) => void) {\n let buffer = '';\n return (chunk: Buffer | string) => {\n buffer += chunk.toString();\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\nexport interface SessionHostClientOptions {\n endpoint?: SessionHostEndpoint;\n appName?: string;\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\n constructor(options: SessionHostClientOptions = {}) {\n this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || 'adhdev');\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 for (const waiter of this.requestWaiters.values()) {\n waiter.reject(error);\n }\n this.requestWaiters.clear();\n // Clear the dead socket reference so the next connect() creates a fresh connection\n // instead of skipping reconnection on the `!this.socket.destroyed` guard.\n if (this.socket === socket) {\n this.socket = null;\n }\n try { socket.destroy(); } catch { /* noop */ }\n });\n\n await new Promise<void>((resolve, reject) => {\n socket.once('connect', () => resolve());\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 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","/**\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 // 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 // Scoped to win32 for now: the macOS/Linux path works today and we want to\n // keep the blast radius small. Safe to make unconditional later — a\n // daemon-spawned claude should always be a fresh top-level session.\n if (process.platform === 'win32') {\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 }\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","resolve","os","path"]}
package/dist/index.mjs CHANGED
@@ -29,6 +29,39 @@ var SESSION_HOST_SUPPORTED_REQUEST_TYPES = [
29
29
  "list_sessions"
30
30
  ];
31
31
 
32
+ // src/termination.ts
33
+ function classifyTermination(input) {
34
+ const { exitCode, terminatedAt } = input;
35
+ const signal = input.signal ?? null;
36
+ let reason;
37
+ let lifecycle;
38
+ if (signal !== null && signal !== 0) {
39
+ reason = "signal";
40
+ lifecycle = "failed";
41
+ } else if (exitCode === null) {
42
+ reason = "unknown";
43
+ lifecycle = "failed";
44
+ } else if (exitCode === 0) {
45
+ reason = "exit";
46
+ lifecycle = "stopped";
47
+ } else {
48
+ reason = "failed";
49
+ lifecycle = "failed";
50
+ }
51
+ const termination = {
52
+ exitCode,
53
+ signal,
54
+ reason,
55
+ lifecycle,
56
+ terminatedAt
57
+ };
58
+ if (typeof input.osPid === "number") termination.osPid = input.osPid;
59
+ if (input.previousLifecycle) termination.previousLifecycle = input.previousLifecycle;
60
+ if (typeof input.lastOutputAt === "number") termination.lastOutputAt = input.lastOutputAt;
61
+ if (input.requestedStop) termination.requestedStop = input.requestedStop;
62
+ return termination;
63
+ }
64
+
32
65
  // src/buffer.ts
33
66
  var DEFAULT_SESSION_RING_BUFFER_MAX_BYTES = 4 * 1024 * 1024;
34
67
  var SessionRingBuffer = class {
@@ -435,10 +468,11 @@ var SessionHostRegistry = class {
435
468
  state.record.lastActivityAt = Date.now();
436
469
  return this.cloneRecord(state.record);
437
470
  }
438
- markStopped(sessionId, lifecycle = "stopped") {
471
+ markStopped(sessionId, lifecycle = "stopped", termination) {
439
472
  const state = this.requireSession(sessionId);
440
473
  state.record.lifecycle = lifecycle;
441
474
  state.record.lastActivityAt = Date.now();
475
+ if (termination) state.record.termination = termination;
442
476
  return this.cloneRecord(state.record);
443
477
  }
444
478
  setLifecycle(sessionId, lifecycle) {
@@ -741,6 +775,7 @@ export {
741
775
  applyTerminalColorEnv,
742
776
  buildRuntimeDisplayName,
743
777
  buildRuntimeKey,
778
+ classifyTermination,
744
779
  createLineParser,
745
780
  createResponseEnvelope,
746
781
  createSessionHostControlPlane,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts","../src/buffer.ts","../src/defaults.ts","../src/runtime-labels.ts","../src/registry.ts","../src/ipc.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\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}\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 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 }\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 { 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}\n\nconst LIVE_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'interrupted']);\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 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} 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'): 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 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 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 function getDefaultSessionHostEndpoint(appName = 'adhdev'): SessionHostEndpoint {\n if (process.platform === 'win32') {\n return {\n kind: 'pipe',\n path: `\\\\\\\\.\\\\pipe\\\\${appName}-session-host`,\n };\n }\n\n return {\n kind: 'unix',\n path: path.join(os.tmpdir(), `${appName}-session-host.sock`),\n };\n}\n\nfunction serializeEnvelope(envelope: SessionHostWireEnvelope): string {\n return `${JSON.stringify(envelope)}\\n`;\n}\n\nfunction createLineParser(onEnvelope: (envelope: SessionHostWireEnvelope) => void) {\n let buffer = '';\n return (chunk: Buffer | string) => {\n buffer += chunk.toString();\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\nexport interface SessionHostClientOptions {\n endpoint?: SessionHostEndpoint;\n appName?: string;\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\n constructor(options: SessionHostClientOptions = {}) {\n this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || 'adhdev');\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 for (const waiter of this.requestWaiters.values()) {\n waiter.reject(error);\n }\n this.requestWaiters.clear();\n // Clear the dead socket reference so the next connect() creates a fresh connection\n // instead of skipping reconnection on the `!this.socket.destroyed` guard.\n if (this.socket === socket) {\n this.socket = null;\n }\n try { socket.destroy(); } catch { /* noop */ }\n });\n\n await new Promise<void>((resolve, reject) => {\n socket.once('connect', () => resolve());\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 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","/**\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 // 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 // Scoped to win32 for now: the macOS/Linux path works today and we want to\n // keep the blast radius small. Safe to make unconditional later — a\n // daemon-spawned claude should always be a fresh top-level session.\n if (process.platform === 'win32') {\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 }\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","resolve","os","path"]}
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/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 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}\n\nconst LIVE_LIFECYCLES = new Set(['starting', 'running', 'stopping', 'interrupted']);\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 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 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 function getDefaultSessionHostEndpoint(appName = 'adhdev'): SessionHostEndpoint {\n if (process.platform === 'win32') {\n return {\n kind: 'pipe',\n path: `\\\\\\\\.\\\\pipe\\\\${appName}-session-host`,\n };\n }\n\n return {\n kind: 'unix',\n path: path.join(os.tmpdir(), `${appName}-session-host.sock`),\n };\n}\n\nfunction serializeEnvelope(envelope: SessionHostWireEnvelope): string {\n return `${JSON.stringify(envelope)}\\n`;\n}\n\nfunction createLineParser(onEnvelope: (envelope: SessionHostWireEnvelope) => void) {\n let buffer = '';\n return (chunk: Buffer | string) => {\n buffer += chunk.toString();\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\nexport interface SessionHostClientOptions {\n endpoint?: SessionHostEndpoint;\n appName?: string;\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\n constructor(options: SessionHostClientOptions = {}) {\n this.endpoint = options.endpoint || getDefaultSessionHostEndpoint(options.appName || 'adhdev');\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 for (const waiter of this.requestWaiters.values()) {\n waiter.reject(error);\n }\n this.requestWaiters.clear();\n // Clear the dead socket reference so the next connect() creates a fresh connection\n // instead of skipping reconnection on the `!this.socket.destroyed` guard.\n if (this.socket === socket) {\n this.socket = null;\n }\n try { socket.destroy(); } catch { /* noop */ }\n });\n\n await new Promise<void>((resolve, reject) => {\n socket.once('connect', () => resolve());\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 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","/**\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 // 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 // Scoped to win32 for now: the macOS/Linux path works today and we want to\n // keep the blast radius small. Safe to make unconditional later — a\n // daemon-spawned claude should always be a fresh top-level session.\n if (process.platform === 'win32') {\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 }\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","resolve","os","path"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adhdev/session-host-core",
3
- "version": "1.0.17",
4
- "description": "ADHDev local session host core \u2014 session registry, protocol, buffers",
3
+ "version": "1.0.18-rc.10",
4
+ "description": "ADHDev local session host core session registry, protocol, buffers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
package/src/index.ts CHANGED
@@ -41,12 +41,14 @@ export type {
41
41
  SessionLifecycle,
42
42
  SessionOwnerType,
43
43
  SessionTransport,
44
+ SessionTermination,
44
45
  SessionHostWireEnvelope,
45
46
  SessionWriteOwner,
46
47
  StopSessionPayload,
47
48
  } from './types.js';
48
49
 
49
50
  export { SESSION_HOST_SUPPORTED_REQUEST_TYPES } from './types.js';
51
+ export { classifyTermination } from './termination.js';
50
52
 
51
53
  export { DEFAULT_SESSION_RING_BUFFER_MAX_BYTES, SessionRingBuffer } from './buffer.js';
52
54
  export type { SessionRingBufferOptions } from './buffer.js';
package/src/registry.ts CHANGED
@@ -16,6 +16,7 @@ import type {
16
16
  ReleaseWritePayload,
17
17
  SessionAttachedClient,
18
18
  SessionHostRecord,
19
+ SessionTermination,
19
20
  } from './types.js';
20
21
  import { SessionRingBuffer } from './buffer.js';
21
22
  import { resolveSessionHostCols, resolveSessionHostRows } from './defaults.js';
@@ -237,10 +238,11 @@ export class SessionHostRegistry {
237
238
  return this.cloneRecord(state.record);
238
239
  }
239
240
 
240
- markStopped(sessionId: string, lifecycle: 'stopped' | 'failed' = 'stopped'): SessionHostRecord {
241
+ markStopped(sessionId: string, lifecycle: 'stopped' | 'failed' = 'stopped', termination?: SessionTermination): SessionHostRecord {
241
242
  const state = this.requireSession(sessionId);
242
243
  state.record.lifecycle = lifecycle;
243
244
  state.record.lastActivityAt = Date.now();
245
+ if (termination) state.record.termination = termination;
244
246
  return this.cloneRecord(state.record);
245
247
  }
246
248
 
@@ -0,0 +1,59 @@
1
+ import type { SessionLifecycle, SessionTermination } from './types.js';
2
+
3
+ export interface ClassifyTerminationInput {
4
+ exitCode: number | null;
5
+ signal?: number | null;
6
+ osPid?: number;
7
+ previousLifecycle?: SessionLifecycle;
8
+ lastOutputAt?: number;
9
+ requestedStop?: SessionTermination['requestedStop'];
10
+ terminatedAt: number;
11
+ }
12
+
13
+ /**
14
+ * Classify a PTY termination from its raw (exitCode, signal) pair.
15
+ *
16
+ * Invariants:
17
+ * - A `null` exitCode is UNKNOWN, never a clean exit — it MUST NOT collapse to
18
+ * exit 0. This is the regression guard for the old `exitCode ?? 0` behavior.
19
+ * - A non-null, non-zero exitCode or any signal is a failure.
20
+ * - Only an explicit exit code of 0 (with no signal) is a clean stop.
21
+ *
22
+ * The returned lifecycle is `stopped` only for a clean exit 0; every other
23
+ * case (nonzero, signalled, or unknown) is `failed` so that an unknown
24
+ * termination stays distinguishable from a successful one.
25
+ */
26
+ export function classifyTermination(input: ClassifyTerminationInput): SessionTermination {
27
+ const { exitCode, terminatedAt } = input;
28
+ const signal = input.signal ?? null;
29
+
30
+ let reason: SessionTermination['reason'];
31
+ let lifecycle: SessionTermination['lifecycle'];
32
+
33
+ if (signal !== null && signal !== 0) {
34
+ reason = 'signal';
35
+ lifecycle = 'failed';
36
+ } else if (exitCode === null) {
37
+ reason = 'unknown';
38
+ lifecycle = 'failed';
39
+ } else if (exitCode === 0) {
40
+ reason = 'exit';
41
+ lifecycle = 'stopped';
42
+ } else {
43
+ reason = 'failed';
44
+ lifecycle = 'failed';
45
+ }
46
+
47
+ const termination: SessionTermination = {
48
+ exitCode,
49
+ signal,
50
+ reason,
51
+ lifecycle,
52
+ terminatedAt,
53
+ };
54
+ if (typeof input.osPid === 'number') termination.osPid = input.osPid;
55
+ if (input.previousLifecycle) termination.previousLifecycle = input.previousLifecycle;
56
+ if (typeof input.lastOutputAt === 'number') termination.lastOutputAt = input.lastOutputAt;
57
+ if (input.requestedStop) termination.requestedStop = input.requestedStop;
58
+ return termination;
59
+ }
package/src/types.ts CHANGED
@@ -58,6 +58,37 @@ export interface SessionBufferState {
58
58
 
59
59
  export type SessionHostSurfaceKind = 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
60
60
 
61
+ /**
62
+ * Compact, secret-free record of how a session's PTY process terminated.
63
+ *
64
+ * Preserves the nullable/unknown exitCode and signal exactly as observed from
65
+ * the first node-pty `onExit` — an `exitCode` of `null` means "unknown" (the
66
+ * process was terminated by a signal, or the exit code was otherwise not
67
+ * reported) and MUST NOT be collapsed to 0. Persisted as a tombstone so
68
+ * termination diagnostics remain inspectable after the live runtime and its
69
+ * transport adapter are torn down.
70
+ */
71
+ export interface SessionTermination {
72
+ /** Exit code exactly as observed; `null` = unknown / signal-terminated. Never fabricated to 0. */
73
+ exitCode: number | null;
74
+ /** Signal number that terminated the process, if any; `null` when not signalled. */
75
+ signal: number | null;
76
+ /** Classification derived from (exitCode, signal): clean exit vs. failure vs. unknown. */
77
+ reason: 'exit' | 'signal' | 'failed' | 'unknown';
78
+ /** Lifecycle recorded for this termination (`stopped` | `failed`). */
79
+ lifecycle: Extract<SessionLifecycle, 'stopped' | 'failed'>;
80
+ /** Wall-clock time the termination was observed. */
81
+ terminatedAt: number;
82
+ /** OS pid of the terminated process, if it was known. */
83
+ osPid?: number;
84
+ /** Lifecycle the session was in immediately before termination. */
85
+ previousLifecycle?: SessionLifecycle;
86
+ /** Last time output was observed from the session, if known. */
87
+ lastOutputAt?: number;
88
+ /** Whether the termination followed an explicit stop/delete request, and which. */
89
+ requestedStop?: 'stop' | 'delete' | 'restart' | 'prune';
90
+ }
91
+
61
92
  export interface SessionHostRecord {
62
93
  sessionId: string;
63
94
  runtimeKey: string;
@@ -78,6 +109,8 @@ export interface SessionHostRecord {
78
109
  attachedClients: SessionAttachedClient[];
79
110
  buffer: SessionBufferState;
80
111
  meta: Record<string, unknown>;
112
+ /** Termination tombstone, stamped when the PTY process exits. Retained for post-mortem inspection. */
113
+ termination?: SessionTermination;
81
114
  }
82
115
 
83
116
  export interface CreateSessionPayload {
@@ -307,7 +340,7 @@ export type SessionHostEvent =
307
340
  | { type: 'session_resumed'; sessionId: string; pid?: number }
308
341
  | { type: 'session_output'; sessionId: string; seq: number; data: string }
309
342
  | { type: 'session_cleared'; sessionId: string }
310
- | { type: 'session_exit'; sessionId: string; exitCode: number | null }
343
+ | { type: 'session_exit'; sessionId: string; exitCode: number | null; signal?: number | null; termination?: SessionTermination }
311
344
  | { type: 'session_stopped'; sessionId: string }
312
345
  | { type: 'session_deleted'; sessionId: string }
313
346
  | { type: 'session_resized'; sessionId: string; cols: number; rows: number }