@bytespell/amux 0.0.7 → 0.0.8

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.
@@ -129,10 +129,9 @@ var AcpDriver = class {
129
129
  return path.join(storageDir, "history.json");
130
130
  }
131
131
  storeEvent(storageDir, update) {
132
- const isSessionUpdate = typeof update === "object" && update !== null && "sessionUpdate" in update;
133
- const isAmuxEvent = typeof update === "object" && update !== null && "amuxEvent" in update;
134
- const isTurnMarker = isAmuxEvent && (update.amuxEvent === "turn_start" || update.amuxEvent === "turn_end");
135
- if (!isSessionUpdate && !isTurnMarker) {
132
+ const isAcpEvent = update.eventType === "acp";
133
+ const isTurnMarker = update.eventType === "acp:turn_start" || update.eventType === "acp:turn_end";
134
+ if (!isAcpEvent && !isTurnMarker) {
136
135
  return;
137
136
  }
138
137
  if (!fsSync.existsSync(storageDir)) {
@@ -185,7 +184,7 @@ var AcpDriver = class {
185
184
  };
186
185
  proc.wait().then(({ exitCode }) => {
187
186
  if (this.instances.has(streamId)) {
188
- emit({ amuxEvent: "error", message: `Agent process exited with code ${exitCode}` });
187
+ emit({ eventType: "error", message: `Agent process exited with code ${exitCode}` });
189
188
  this.instances.delete(streamId);
190
189
  }
191
190
  });
@@ -255,6 +254,7 @@ var AcpDriver = class {
255
254
  const modes = sessionResult?.modes?.availableModes;
256
255
  if (sessionResult?.modes) {
257
256
  emit({
257
+ eventType: "acp",
258
258
  sessionUpdate: "current_mode_update",
259
259
  currentModeId: sessionResult.modes.currentModeId
260
260
  });
@@ -285,12 +285,12 @@ var AcpDriver = class {
285
285
  }))
286
286
  };
287
287
  instance.pendingPermission = permission;
288
- instance.emit({ amuxEvent: "permission_request", permission });
288
+ instance.emit({ eventType: "acp:permission_request", permission });
289
289
  return new Promise((resolve, reject) => {
290
290
  instance.permissionCallbacks.set(requestId, {
291
291
  resolve: (optionId) => {
292
292
  instance.pendingPermission = null;
293
- instance.emit({ amuxEvent: "permission_cleared" });
293
+ instance.emit({ eventType: "acp:permission_cleared" });
294
294
  resolve({ outcome: { outcome: "selected", optionId } });
295
295
  },
296
296
  reject
@@ -300,7 +300,7 @@ var AcpDriver = class {
300
300
  async sessionUpdate(params) {
301
301
  debug("acp", ` sessionUpdate received: ${JSON.stringify(params)}`);
302
302
  const normalized = normalizeSessionUpdate(params.update);
303
- instance.emit(normalized);
303
+ instance.emit({ eventType: "acp", ...normalized });
304
304
  },
305
305
  async readTextFile(params) {
306
306
  const content = await fs.readFile(params.path, "utf-8");
@@ -415,17 +415,23 @@ Error: ${err.message}`;
415
415
  async input(streamId, raw, cwd, _emit) {
416
416
  const instance = this.instances.get(streamId);
417
417
  if (!instance) throw new Error(`No ACP instance for stream ${streamId}`);
418
+ instance.emit({ eventType: "acp:turn_start" });
418
419
  this.storeEvent(instance.storageDir, {
420
+ eventType: "acp",
419
421
  sessionUpdate: "user_message_chunk",
420
422
  content: { type: "text", text: raw }
421
423
  });
422
424
  const content = parseMessageToContentBlocks(raw, cwd);
423
- debug("acp", ` Sending input to session ${instance.acpSessionId} with ${content.length} content block(s)...`);
424
- const result = await instance.connection.prompt({
425
- sessionId: instance.acpSessionId,
426
- prompt: content
427
- });
428
- debug("acp", ` Input complete, stopReason: ${result.stopReason}`);
425
+ debug("acp", `Sending input to session ${instance.acpSessionId} with ${content.length} content block(s)...`);
426
+ try {
427
+ const result = await instance.connection.prompt({
428
+ sessionId: instance.acpSessionId,
429
+ prompt: content
430
+ });
431
+ debug("acp", `Input complete, stopReason: ${result.stopReason}`);
432
+ } finally {
433
+ instance.emit({ eventType: "acp:turn_end" });
434
+ }
429
435
  }
430
436
  async stop(streamId) {
431
437
  const instance = this.instances.get(streamId);
@@ -525,6 +531,7 @@ var MockDriver = class {
525
531
  async start(streamId, _config, _cwd, _backendState, emit, _storageDir) {
526
532
  debug("mock", ` Starting mock agent for stream ${streamId}`);
527
533
  emit({
534
+ eventType: "acp",
528
535
  sessionUpdate: "current_mode_update",
529
536
  currentModeId: "mock"
530
537
  });
@@ -570,6 +577,7 @@ var MockDriver = class {
570
577
  for (let i = 0; i < words.length; i += 3) {
571
578
  const chunk = words.slice(i, i + 3).join(" ") + " ";
572
579
  emit({
580
+ eventType: "acp",
573
581
  sessionUpdate: "agent_message_chunk",
574
582
  content: { type: "text", text: chunk }
575
583
  });
@@ -680,7 +688,7 @@ var PtyDriver = class {
680
688
  instance.scrollback = instance.scrollback.slice(-MAX_SCROLLBACK);
681
689
  }
682
690
  this.debouncedSave(instance);
683
- instance.emit({ amuxEvent: "terminal_output", data });
691
+ instance.emit({ eventType: "terminal:output", data });
684
692
  });
685
693
  p.onExit(({ exitCode, signal }) => {
686
694
  debug("shell", `PTY exited: code=${exitCode}, signal=${signal}`);
@@ -689,7 +697,7 @@ var PtyDriver = class {
689
697
  }
690
698
  this.saveScrollback(instance);
691
699
  instance.emit({
692
- amuxEvent: "terminal_exit",
700
+ eventType: "terminal:exit",
693
701
  exitCode: exitCode ?? null,
694
702
  signal: signal !== void 0 ? String(signal) : null
695
703
  });
@@ -768,4 +776,4 @@ export {
768
776
  MockDriver,
769
777
  PtyDriver
770
778
  };
771
- //# sourceMappingURL=chunk-HJCRMFTD.js.map
779
+ //# sourceMappingURL=chunk-VVXT4HQM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/streams/backends/acp.ts","../src/streams/process.ts","../src/streams/backends/mock.ts","../src/streams/backends/shell.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { spawn as nodeSpawn, type ChildProcess } from 'child_process';\nimport { Readable, Writable } from 'stream';\nimport * as fs from 'fs/promises';\nimport * as fsSync from 'fs';\nimport * as path from 'path';\nimport {\n ClientSideConnection,\n ndJsonStream,\n Client,\n RequestPermissionRequest,\n RequestPermissionResponse,\n SessionNotification,\n ReadTextFileRequest,\n ReadTextFileResponse,\n WriteTextFileRequest,\n WriteTextFileResponse,\n CreateTerminalRequest,\n CreateTerminalResponse,\n TerminalOutputRequest,\n TerminalOutputResponse,\n WaitForTerminalExitRequest,\n WaitForTerminalExitResponse,\n KillTerminalCommandRequest,\n KillTerminalCommandResponse,\n ReleaseTerminalRequest,\n ReleaseTerminalResponse,\n} from '@agentclientprotocol/sdk';\nimport type { StreamDriver, StreamConfig, Stream, EmitFn } from './types.js';\nimport type { PendingPermission, StreamPayload } from '../../types.js';\nimport { spawn, type ManagedProcess } from '../process.js';\nimport { debug } from '../../lib/logger.js';\nimport { parseMessageToContentBlocks } from '../../lib/mentions.js';\n\nconst INIT_TIMEOUT_MS = 90000; // 90s to allow npx download on first run\n\n/** ACP backend state for persistence */\nexport interface AcpBackendState {\n acpSessionId: string;\n}\n\n/**\n * Normalize ACP session updates.\n *\n * Amux passes through events as-is, but normalizes quirks from specific agents:\n * - Claude sends Edit/Write diffs as {newText, oldText, path} instead of unified diff\n * - We convert these to standard unified diff format so the UI doesn't need agent-specific logic\n */\nfunction normalizeSessionUpdate(update: Record<string, unknown>): Record<string, unknown> {\n // Only process tool_call and tool_call_update events with content arrays\n if (update.sessionUpdate !== 'tool_call' && update.sessionUpdate !== 'tool_call_update') {\n return update;\n }\n\n const content = update.content as Array<Record<string, unknown>> | undefined;\n if (!content || !Array.isArray(content)) {\n return update;\n }\n\n // Check for diff items that need normalization\n const normalizedContent = content.map(item => {\n if (item.type !== 'diff') return item;\n\n // Already a string diff - pass through\n if (typeof item.content === 'string') return item;\n\n // Claude-style diff: {type: 'diff', newText, oldText, path}\n // Convert to unified diff format\n const newText = item.newText as string | undefined;\n const oldText = item.oldText as string | null | undefined;\n const path = item.path as string | undefined;\n\n if (newText === undefined) return item;\n\n // Generate unified diff\n const filePath = path ?? 'file';\n const oldLines = oldText ? oldText.split('\\n') : [];\n const newLines = newText.split('\\n');\n\n let unifiedDiff = `Index: ${filePath}\\n`;\n unifiedDiff += '===================================================================\\n';\n unifiedDiff += `--- ${filePath}\\n`;\n unifiedDiff += `+++ ${filePath}\\n`;\n unifiedDiff += `@@ -${oldLines.length > 0 ? 1 : 0},${oldLines.length} +1,${newLines.length} @@\\n`;\n\n for (const line of oldLines) {\n unifiedDiff += `-${line}\\n`;\n }\n for (const line of newLines) {\n unifiedDiff += `+${line}\\n`;\n }\n\n // Return normalized diff item\n return {\n type: 'diff',\n content: unifiedDiff,\n };\n });\n\n return {\n ...update,\n content: normalizedContent,\n };\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {\n return Promise.race([\n promise,\n new Promise<T>((_, reject) =>\n setTimeout(() => reject(new Error(`${operation} timed out after ${ms}ms`)), ms)\n ),\n ]);\n}\n\ninterface Terminal {\n process: ChildProcess;\n output: string;\n exitCode: number | null;\n signal: string | null;\n truncated: boolean;\n outputByteLimit: number;\n}\n\ninterface AcpInstance {\n process: ManagedProcess;\n connection: ClientSideConnection;\n acpSessionId: string; // The ACP protocol session ID\n pendingPermission: PendingPermission | null;\n permissionCallbacks: Map<string, {\n resolve: (optionId: string) => void;\n reject: (err: Error) => void;\n }>;\n emit: EmitFn;\n terminals: Map<string, Terminal>;\n storageDir: string;\n}\n\nexport class AcpDriver implements StreamDriver {\n readonly type = 'acp';\n readonly streamType = 'acp' as const;\n private instances = new Map<string, AcpInstance>();\n\n private getHistoryPath(storageDir: string): string {\n return path.join(storageDir, 'history.json');\n }\n\n private storeEvent(storageDir: string, update: StreamPayload): void {\n // Only store ACP events and turn markers\n const isAcpEvent = update.eventType === 'acp';\n const isTurnMarker = update.eventType === 'acp:turn_start' || update.eventType === 'acp:turn_end';\n\n if (!isAcpEvent && !isTurnMarker) {\n return;\n }\n\n // Ensure directory exists\n if (!fsSync.existsSync(storageDir)) {\n fsSync.mkdirSync(storageDir, { recursive: true });\n }\n\n const historyPath = this.getHistoryPath(storageDir);\n let events: StreamPayload[] = [];\n\n try {\n if (fsSync.existsSync(historyPath)) {\n const data = fsSync.readFileSync(historyPath, 'utf-8');\n events = JSON.parse(data);\n }\n } catch {\n events = [];\n }\n\n events.push(update);\n fsSync.writeFileSync(historyPath, JSON.stringify(events));\n }\n\n async start(\n streamId: string,\n config: StreamConfig,\n cwd: string,\n backendState: unknown | null,\n emit: EmitFn,\n storageDir: string\n ): Promise<Stream> {\n // Extract existing ACP session ID from backend state if resuming\n const existingState = backendState as AcpBackendState | null;\n const existingAcpSessionId = existingState?.acpSessionId ?? null;\n\n // Kill existing instance if any\n if (this.instances.has(streamId)) {\n await this.stop(streamId);\n }\n\n const args = config.args ?? [];\n const env = config.env ?? {};\n\n debug('acp',` Spawning: ${config.command} ${args.join(' ')} in ${cwd}`);\n\n const proc = spawn({\n command: config.command,\n args,\n cwd,\n env,\n });\n\n // Log stderr for debugging startup issues\n proc.stderr.on('data', (data: Buffer) => {\n debug('acp', `[stderr] ${data.toString().trim()}`);\n });\n\n // Wrap emit to store events\n const storingEmit: EmitFn = (update) => {\n this.storeEvent(storageDir, update);\n emit(update);\n };\n\n const instance: AcpInstance = {\n process: proc,\n connection: null!,\n acpSessionId: '',\n pendingPermission: null,\n permissionCallbacks: new Map(),\n emit: storingEmit,\n terminals: new Map(),\n storageDir,\n };\n\n // Handle unexpected exit\n proc.wait().then(({ exitCode }) => {\n if (this.instances.has(streamId)) {\n emit({ eventType: 'error', message: `Agent process exited with code ${exitCode}` });\n this.instances.delete(streamId);\n }\n });\n\n try {\n const input = Writable.toWeb(proc.stdin) as WritableStream<Uint8Array>;\n const output = Readable.toWeb(proc.stdout) as ReadableStream<Uint8Array>;\n const stream = ndJsonStream(input, output);\n\n const client = this.createClient(streamId, instance);\n instance.connection = new ClientSideConnection(() => client, stream);\n\n const initResult = await withTimeout(\n instance.connection.initialize({\n protocolVersion: 1,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: true,\n },\n }),\n INIT_TIMEOUT_MS,\n 'Agent initialization'\n );\n\n debug('acp',` Initialized agent: ${initResult.agentInfo?.name} v${initResult.agentInfo?.version}`);\n\n const canResume = initResult.agentCapabilities?.sessionCapabilities?.resume !== undefined;\n let acpSessionId: string;\n let sessionResult: any;\n\n if (existingAcpSessionId && canResume) {\n let resumeSucceeded = false;\n try {\n debug('acp',` Resuming ACP session ${existingAcpSessionId}...`);\n sessionResult = await withTimeout(\n (instance.connection as any).unstable_resumeSession({\n sessionId: existingAcpSessionId,\n cwd,\n mcpServers: [],\n }),\n INIT_TIMEOUT_MS,\n 'Session resume'\n );\n\n await new Promise(resolve => setTimeout(resolve, 100));\n acpSessionId = existingAcpSessionId;\n debug('acp',` ACP session resumed successfully`);\n resumeSucceeded = true;\n } catch (resumeErr) {\n debug('acp', ` Resume failed, creating new session: ${resumeErr}`);\n }\n\n if (!resumeSucceeded) {\n sessionResult = await withTimeout(\n instance.connection.newSession({ cwd, mcpServers: [] }),\n INIT_TIMEOUT_MS,\n 'New session creation'\n );\n acpSessionId = sessionResult.sessionId;\n debug('acp',` New ACP session created: ${acpSessionId}`);\n }\n } else {\n debug('acp',` Creating new ACP session in ${cwd}...`);\n sessionResult = await withTimeout(\n instance.connection.newSession({ cwd, mcpServers: [] }),\n INIT_TIMEOUT_MS,\n 'New session creation'\n );\n acpSessionId = sessionResult.sessionId!;\n debug('acp',` ACP session created: ${acpSessionId}`);\n }\n\n instance.acpSessionId = acpSessionId!;\n this.instances.set(streamId, instance);\n\n const models = sessionResult?.models?.availableModels;\n const modes = sessionResult?.modes?.availableModes;\n\n if (sessionResult?.modes) {\n emit({\n eventType: 'acp',\n sessionUpdate: 'current_mode_update',\n currentModeId: sessionResult.modes.currentModeId,\n });\n }\n\n return {\n acpSessionId: acpSessionId!,\n models,\n modes,\n };\n } catch (err) {\n console.error(`[acp] Error starting agent for stream ${streamId}:`, err);\n await proc.kill();\n throw err;\n }\n }\n\n private createClient(_streamId: string, instance: AcpInstance): Client {\n return {\n async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {\n const requestId = randomUUID();\n\n const permission = {\n requestId,\n toolCallId: params.toolCall.toolCallId,\n title: params.toolCall.title ?? 'Permission Required',\n options: params.options.map(o => ({\n optionId: o.optionId,\n name: o.name,\n kind: o.kind,\n })),\n };\n instance.pendingPermission = permission;\n instance.emit({ eventType: 'acp:permission_request', permission });\n\n return new Promise((resolve, reject) => {\n instance.permissionCallbacks.set(requestId, {\n resolve: (optionId) => {\n instance.pendingPermission = null;\n instance.emit({ eventType: 'acp:permission_cleared' });\n resolve({ outcome: { outcome: 'selected', optionId } });\n },\n reject,\n });\n });\n },\n\n async sessionUpdate(params: SessionNotification): Promise<void> {\n debug('acp', ` sessionUpdate received: ${JSON.stringify(params)}`);\n // Normalize the update before emitting (ACP normalization layer)\n const normalized = normalizeSessionUpdate(params.update);\n // Wrap with eventType: 'acp' for the new event system\n instance.emit({ eventType: 'acp', ...normalized } as StreamPayload);\n },\n\n async readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {\n const content = await fs.readFile(params.path, 'utf-8');\n return { content };\n },\n\n async writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {\n await fs.writeFile(params.path, params.content);\n return {};\n },\n\n async createTerminal(params: CreateTerminalRequest): Promise<CreateTerminalResponse> {\n debug('acp', ` createTerminal request: ${JSON.stringify(params)}`);\n const terminalId = randomUUID();\n const outputByteLimit = params.outputByteLimit ?? 1024 * 1024; // Default 1MB\n\n const termProc = nodeSpawn(params.command, params.args ?? [], {\n cwd: params.cwd ?? undefined,\n env: params.env\n ? { ...process.env, ...Object.fromEntries(params.env.map(e => [e.name, e.value])) }\n : process.env,\n shell: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n const terminal: Terminal = {\n process: termProc,\n output: '',\n exitCode: null,\n signal: null,\n truncated: false,\n outputByteLimit,\n };\n\n const appendOutput = (data: Buffer) => {\n terminal.output += data.toString();\n // Truncate from beginning if over limit\n if (terminal.output.length > terminal.outputByteLimit) {\n terminal.output = terminal.output.slice(-terminal.outputByteLimit);\n terminal.truncated = true;\n }\n };\n\n termProc.stdout?.on('data', appendOutput);\n termProc.stderr?.on('data', appendOutput);\n\n termProc.on('exit', (code, signal) => {\n debug('acp',` Terminal ${terminalId} exited with code ${code}, signal ${signal}`);\n terminal.exitCode = code ?? null;\n terminal.signal = signal ?? null;\n });\n\n termProc.on('error', (err) => {\n console.error(`[acp] Terminal ${terminalId} error:`, err.message);\n terminal.output += `\\nError: ${err.message}`;\n terminal.exitCode = -1;\n });\n\n instance.terminals.set(terminalId, terminal);\n debug('acp',` Created terminal ${terminalId} for command: ${params.command}`);\n\n return { terminalId };\n },\n\n async terminalOutput(params: TerminalOutputRequest): Promise<TerminalOutputResponse> {\n debug('acp',` terminalOutput request for terminal ${params.terminalId}`);\n const terminal = instance.terminals.get(params.terminalId);\n if (!terminal) {\n throw new Error(`Terminal ${params.terminalId} not found`);\n }\n return {\n output: terminal.output,\n truncated: terminal.truncated,\n exitStatus: terminal.exitCode !== null || terminal.signal !== null ? {\n exitCode: terminal.exitCode,\n signal: terminal.signal,\n } : undefined,\n };\n },\n\n async waitForTerminalExit(params: WaitForTerminalExitRequest): Promise<WaitForTerminalExitResponse> {\n debug('acp',` waitForTerminalExit request for terminal ${params.terminalId}`);\n const terminal = instance.terminals.get(params.terminalId);\n if (!terminal) {\n throw new Error(`Terminal ${params.terminalId} not found`);\n }\n\n // If already exited, return immediately\n if (terminal.exitCode !== null || terminal.signal !== null) {\n return {\n exitCode: terminal.exitCode,\n signal: terminal.signal,\n };\n }\n\n // Wait for exit\n return new Promise((resolve) => {\n terminal.process.on('exit', (code, signal) => {\n resolve({\n exitCode: code ?? null,\n signal: signal ?? null,\n });\n });\n });\n },\n\n // Note: killTerminalCommand not in SDK Client interface yet, but we implement handlers\n // for completeness when the SDK adds support\n async killTerminal(params: KillTerminalCommandRequest): Promise<KillTerminalCommandResponse> {\n debug('acp',` killTerminal request for terminal ${params.terminalId}`);\n const terminal = instance.terminals.get(params.terminalId);\n if (!terminal) {\n throw new Error(`Terminal ${params.terminalId} not found`);\n }\n terminal.process.kill('SIGTERM');\n return {};\n },\n\n async releaseTerminal(params: ReleaseTerminalRequest): Promise<ReleaseTerminalResponse> {\n debug('acp',` releaseTerminal request for terminal ${params.terminalId}`);\n const terminal = instance.terminals.get(params.terminalId);\n if (terminal) {\n if (terminal.exitCode === null) {\n terminal.process.kill('SIGKILL');\n }\n instance.terminals.delete(params.terminalId);\n }\n return {};\n },\n };\n }\n\n async input(streamId: string, raw: string, cwd: string, _emit: EmitFn): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) throw new Error(`No ACP instance for stream ${streamId}`);\n\n instance.emit({ eventType: 'acp:turn_start' });\n\n // Store user message for replay\n this.storeEvent(instance.storageDir, {\n eventType: 'acp',\n sessionUpdate: 'user_message_chunk',\n content: { type: 'text', text: raw },\n } as StreamPayload);\n\n // Parse @mentions into ContentBlocks\n const content = parseMessageToContentBlocks(raw, cwd);\n\n debug('acp', `Sending input to session ${instance.acpSessionId} with ${content.length} content block(s)...`);\n try {\n const result = await instance.connection.prompt({\n sessionId: instance.acpSessionId,\n prompt: content,\n });\n debug('acp', `Input complete, stopReason: ${result.stopReason}`);\n } finally {\n instance.emit({ eventType: 'acp:turn_end' });\n }\n }\n\n async stop(streamId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) return;\n\n // Reject pending permission callbacks\n for (const [, callback] of instance.permissionCallbacks) {\n callback.reject(new Error('Agent stopped'));\n }\n instance.permissionCallbacks.clear();\n instance.pendingPermission = null;\n\n // Remove from instances first to prevent exit handler re-entry\n this.instances.delete(streamId);\n\n // Kill the process tree\n await instance.process.kill();\n }\n\n async stopAll(): Promise<void> {\n const streamIds = [...this.instances.keys()];\n await Promise.all(streamIds.map((id) => this.stop(id)));\n }\n\n isRunning(streamId: string): boolean {\n return this.instances.has(streamId);\n }\n\n /** Get backend state for persistence (ACP session ID for resumption) */\n getState(streamId: string): AcpBackendState | undefined {\n const instance = this.instances.get(streamId);\n if (!instance) return undefined;\n return { acpSessionId: instance.acpSessionId };\n }\n\n /** Get stored events for replay on reconnect */\n getReplayData(streamId: string): StreamPayload[] | undefined {\n const instance = this.instances.get(streamId);\n if (!instance) return undefined;\n\n const historyPath = this.getHistoryPath(instance.storageDir);\n try {\n if (fsSync.existsSync(historyPath)) {\n const data = fsSync.readFileSync(historyPath, 'utf-8');\n return JSON.parse(data);\n }\n } catch {\n // Return empty if file is corrupted\n }\n return [];\n }\n\n /** Clear stored events for a stream */\n clearHistory(streamId: string): void {\n const instance = this.instances.get(streamId);\n if (!instance) return;\n\n const historyPath = this.getHistoryPath(instance.storageDir);\n try {\n if (fsSync.existsSync(historyPath)) {\n fsSync.unlinkSync(historyPath);\n }\n } catch {\n // Ignore errors\n }\n }\n\n respondToPermission(streamId: string, requestId: string, optionId: string): void {\n const instance = this.instances.get(streamId);\n const callback = instance?.permissionCallbacks.get(requestId);\n if (!callback) {\n throw new Error(`No pending permission request ${requestId}`);\n }\n callback.resolve(optionId);\n instance!.permissionCallbacks.delete(requestId);\n }\n\n getPendingPermission(streamId: string): PendingPermission | null {\n const instance = this.instances.get(streamId);\n return instance?.pendingPermission ?? null;\n }\n\n async cancel(streamId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) return;\n\n await instance.connection.cancel({ sessionId: instance.acpSessionId });\n }\n\n async setMode(streamId: string, modeId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) throw new Error(`No ACP instance for stream ${streamId}`);\n\n await instance.connection.setSessionMode({\n sessionId: instance.acpSessionId,\n modeId,\n });\n }\n\n async setModel(streamId: string, modelId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) throw new Error(`No ACP instance for stream ${streamId}`);\n\n await (instance.connection as any).unstable_setSessionModel({\n sessionId: instance.acpSessionId,\n modelId,\n });\n }\n}\n","/**\n * Managed process wrapper with automatic tree cleanup.\n * Uses execa for process spawning and tree-kill for cleanup.\n */\n\nimport { execa } from 'execa';\nimport treeKill from 'tree-kill';\nimport type { Writable, Readable } from 'stream';\n\nexport interface ManagedProcess {\n pid: number;\n stdin: Writable;\n stdout: Readable;\n stderr: Readable;\n kill: (signal?: string) => Promise<void>;\n wait: () => Promise<{ exitCode: number | null }>;\n}\n\nexport interface SpawnOptions {\n command: string;\n args?: string[];\n cwd?: string;\n env?: Record<string, string>;\n timeoutMs?: number;\n}\n\n/**\n * Spawns a managed process with automatic tree cleanup.\n *\n * - Cross-platform process tree killing via tree-kill\n * - Automatic cleanup on parent exit\n * - Promise-based wait/kill\n */\nexport function spawn(options: SpawnOptions): ManagedProcess {\n const { command, args = [], cwd, env, timeoutMs } = options;\n\n const subprocess = execa(command, args, {\n cwd,\n env: { ...process.env, ...env },\n stdin: 'pipe',\n stdout: 'pipe',\n stderr: 'pipe',\n timeout: timeoutMs,\n cleanup: true, // Kill on parent exit\n windowsHide: true, // Hide console window on Windows\n });\n\n const pid = subprocess.pid;\n if (!pid) {\n throw new Error(`Failed to spawn process: ${command}`);\n }\n\n return {\n pid,\n stdin: subprocess.stdin!,\n stdout: subprocess.stdout!,\n stderr: subprocess.stderr!,\n\n kill: (signal = 'SIGTERM') => killTree(pid, signal),\n\n wait: async () => {\n try {\n const result = await subprocess;\n return { exitCode: result.exitCode };\n } catch (err: any) {\n return { exitCode: err.exitCode ?? 1 };\n }\n },\n };\n}\n\nfunction killTree(pid: number, signal: string): Promise<void> {\n return new Promise((resolve) => {\n treeKill(pid, signal, (err?: Error) => {\n if (err && !err.message.includes('No such process')) {\n console.error(`[process] kill error pid=${pid}:`, err.message);\n }\n resolve();\n });\n });\n}\n","import { randomUUID } from 'crypto';\nimport type { StreamDriver, StreamConfig, Stream, EmitFn } from './types.js';\nimport { debug } from '../../lib/logger.js';\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nexport class MockDriver implements StreamDriver {\n readonly type = 'mock';\n readonly streamType = 'acp' as const;\n private running = new Set<string>();\n\n async start(\n streamId: string,\n _config: StreamConfig,\n _cwd: string,\n _backendState: unknown | null,\n emit: EmitFn,\n _storageDir: string\n ): Promise<Stream> {\n debug('mock',` Starting mock agent for stream ${streamId}`);\n\n emit({\n eventType: 'acp',\n sessionUpdate: 'current_mode_update',\n currentModeId: 'mock',\n });\n\n debug('mock',` Mock agent ready for stream ${streamId}`);\n this.running.add(streamId);\n\n return {\n acpSessionId: randomUUID(),\n models: [{ modelId: 'mock-model', name: 'Mock Model' }],\n modes: [{ id: 'mock', name: 'Mock Mode' }],\n };\n }\n\n async input(streamId: string, raw: string, _cwd: string, emit: EmitFn): Promise<void> {\n debug('mock',` Mock input for stream ${streamId}: \"${raw.slice(0, 50)}...\"`);\n\n const words = [\n 'This', 'is', 'a', 'mock', 'response', 'from', 'the', 'mock', 'agent.',\n 'It', 'simulates', 'streaming', 'text', 'chunks', 'for', 'performance', 'testing.',\n 'The', 'response', 'arrives', 'in', 'small', 'pieces', 'to', 'test', 'UI', 'rendering.',\n ];\n\n for (let i = 0; i < words.length; i += 3) {\n const chunk = words.slice(i, i + 3).join(' ') + ' ';\n emit({\n eventType: 'acp',\n sessionUpdate: 'agent_message_chunk',\n content: { type: 'text', text: chunk },\n });\n await delay(50);\n }\n }\n\n async stop(streamId: string): Promise<void> {\n debug('mock',` Stopping mock agent for stream ${streamId}`);\n this.running.delete(streamId);\n }\n\n async stopAll(): Promise<void> {\n const streamIds = [...this.running];\n await Promise.all(streamIds.map((id) => this.stop(id)));\n }\n\n isRunning(streamId: string): boolean {\n return this.running.has(streamId);\n }\n}\n","import type { IPty } from 'node-pty';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { StreamDriver, StreamConfig, Stream, EmitFn } from './types.js';\nimport { debug, warn } from '../../lib/logger.js';\n\n// Dynamic import for node-pty (native module)\nlet pty: typeof import('node-pty') | null = null;\n\nasync function getPty() {\n if (!pty) {\n pty = await import('node-pty');\n }\n return pty;\n}\n\nconst MAX_SCROLLBACK = 100_000; // ~100KB of terminal output\nconst SAVE_DEBOUNCE_MS = 1000; // Debounce scrollback saves\n\ninterface ShellInstance {\n pty: IPty;\n scrollback: string;\n cwd: string;\n emit: EmitFn;\n storageDir: string;\n saveTimeout: ReturnType<typeof setTimeout> | null;\n}\n\n/**\n * PtyDriver - Interactive terminal driver using node-pty.\n *\n * Unlike conversational agents (AcpDriver), this provides a raw PTY\n * for interactive shell access. Key differences:\n * - input() writes text to terminal (also exposes terminalWrite() for raw access)\n * - Emits terminal_output events instead of message chunks\n * - Maintains scrollback buffer for reconnection replay\n */\nexport class PtyDriver implements StreamDriver {\n readonly type = 'pty';\n readonly streamType = 'pty' as const;\n readonly isInteractive = true;\n\n private instances = new Map<string, ShellInstance>();\n\n private getScrollbackPath(storageDir: string): string {\n return path.join(storageDir, 'scrollback.txt');\n }\n\n private saveScrollback(instance: ShellInstance): void {\n // Ensure directory exists\n if (!fs.existsSync(instance.storageDir)) {\n fs.mkdirSync(instance.storageDir, { recursive: true });\n }\n fs.writeFileSync(this.getScrollbackPath(instance.storageDir), instance.scrollback);\n }\n\n private loadScrollback(storageDir: string): string {\n const scrollbackPath = this.getScrollbackPath(storageDir);\n try {\n if (fs.existsSync(scrollbackPath)) {\n return fs.readFileSync(scrollbackPath, 'utf-8');\n }\n } catch {\n // Ignore read errors\n }\n return '';\n }\n\n private debouncedSave(instance: ShellInstance): void {\n if (instance.saveTimeout) {\n clearTimeout(instance.saveTimeout);\n }\n instance.saveTimeout = setTimeout(() => {\n this.saveScrollback(instance);\n instance.saveTimeout = null;\n }, SAVE_DEBOUNCE_MS);\n }\n\n async start(\n streamId: string,\n _config: StreamConfig,\n cwd: string,\n _backendState: unknown | null,\n emit: EmitFn,\n storageDir: string\n ): Promise<Stream> {\n // Check if already running (reconnect case)\n const existing = this.instances.get(streamId);\n if (existing) {\n debug('shell', `Stream ${streamId} already running, reusing`);\n // Update emit function for new subscriber\n existing.emit = emit;\n return {};\n }\n\n // Load persisted scrollback (for after server restart)\n const restoredScrollback = this.loadScrollback(storageDir);\n if (restoredScrollback) {\n debug('shell', `Restored ${restoredScrollback.length} bytes of scrollback for ${streamId}`);\n }\n\n const nodePty = await getPty();\n const shell = process.env.SHELL || '/bin/bash';\n\n debug('shell', `Spawning ${shell} in ${cwd}`);\n\n let p: IPty;\n try {\n p = nodePty.spawn(shell, [], {\n name: 'xterm-256color',\n cols: 80,\n rows: 24,\n cwd,\n env: process.env as Record<string, string>,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n if (message.includes('posix_spawnp failed') && process.platform === 'darwin') {\n throw new Error(\n `Failed to spawn shell on macOS: ${message}. ` +\n `This may be caused by macOS quarantine. Try: ` +\n `1) Install globally: npm install -g @bytespell/shella && shella, or ` +\n `2) Clear quarantine: xattr -dr com.apple.quarantine ~/.npm/_npx`\n );\n }\n throw error;\n }\n\n const instance: ShellInstance = {\n pty: p,\n scrollback: restoredScrollback,\n cwd,\n emit,\n storageDir,\n saveTimeout: null,\n };\n\n p.onData((data: string) => {\n // Accumulate scrollback\n instance.scrollback += data;\n if (instance.scrollback.length > MAX_SCROLLBACK) {\n instance.scrollback = instance.scrollback.slice(-MAX_SCROLLBACK);\n }\n\n // Persist scrollback (debounced)\n this.debouncedSave(instance);\n\n // Emit for live subscribers\n instance.emit({ eventType: 'terminal:output', data });\n });\n\n p.onExit(({ exitCode, signal }) => {\n debug('shell', `PTY exited: code=${exitCode}, signal=${signal}`);\n // Save scrollback immediately on exit\n if (instance.saveTimeout) {\n clearTimeout(instance.saveTimeout);\n }\n this.saveScrollback(instance);\n instance.emit({\n eventType: 'terminal:exit',\n exitCode: exitCode ?? null,\n signal: signal !== undefined ? String(signal) : null,\n });\n this.instances.delete(streamId);\n });\n\n this.instances.set(streamId, instance);\n debug('shell', `Stream ${streamId} started`);\n\n return {};\n }\n\n /**\n * Get accumulated scrollback for replay on reconnect.\n */\n getScrollback(streamId: string): string | undefined {\n return this.instances.get(streamId)?.scrollback;\n }\n\n /**\n * Get replay data (scrollback) for reconnecting clients.\n */\n getReplayData(streamId: string): string | undefined {\n return this.getScrollback(streamId);\n }\n\n /**\n * Write raw input to terminal (keystrokes from client).\n */\n terminalWrite(streamId: string, data: string): void {\n const instance = this.instances.get(streamId);\n if (!instance) {\n warn('shell', `terminalWrite: stream ${streamId} not found`);\n return;\n }\n instance.pty.write(data);\n }\n\n /**\n * Resize terminal dimensions.\n */\n terminalResize(streamId: string, cols: number, rows: number): void {\n const instance = this.instances.get(streamId);\n if (!instance) {\n warn('shell', `terminalResize: stream ${streamId} not found`);\n return;\n }\n instance.pty.resize(cols, rows);\n }\n\n /**\n * Handle user input - writes raw text to terminal.\n */\n async input(streamId: string, raw: string, _cwd: string, _emit: EmitFn): Promise<void> {\n if (raw) {\n this.terminalWrite(streamId, raw);\n }\n }\n\n async stop(streamId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) return;\n\n debug('shell', `Stopping stream ${streamId}`);\n\n // Cancel pending save and save immediately\n if (instance.saveTimeout) {\n clearTimeout(instance.saveTimeout);\n }\n this.saveScrollback(instance);\n\n instance.pty.kill();\n this.instances.delete(streamId);\n }\n\n async stopAll(): Promise<void> {\n debug('shell', `Stopping all streams (${this.instances.size})`);\n for (const streamId of this.instances.keys()) {\n await this.stop(streamId);\n }\n }\n\n isRunning(streamId: string): boolean {\n return this.instances.has(streamId);\n }\n}\n"],"mappings":"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","names":["path","randomUUID","randomUUID","fs","path"]}
@@ -1,4 +1,4 @@
1
- import { c as StreamDriver, e as StreamConfig, f as EmitFn, d as Stream, b as StreamPayload, P as PendingPermission } from '../../types-DoG5bt6C.js';
1
+ import { c as StreamDriver, e as StreamConfig, f as EmitFn, d as Stream, b as StreamPayload, P as PendingPermission } from '../../types-DV6-SxsB.js';
2
2
  import '@agentclientprotocol/sdk';
3
3
 
4
4
  /** ACP backend state for persistence */
@@ -2,7 +2,7 @@ import {
2
2
  AcpDriver,
3
3
  MockDriver,
4
4
  PtyDriver
5
- } from "../../chunk-HJCRMFTD.js";
5
+ } from "../../chunk-VVXT4HQM.js";
6
6
  import "../../chunk-5IPYOXBE.js";
7
7
  import "../../chunk-C73RKCTS.js";
8
8
  export {
@@ -1,4 +1,4 @@
1
- import { S as StartStreamArgs, a as StreamHandle, b as StreamPayload, c as StreamDriver, d as Stream, E as Emit, P as PendingPermission } from '../types-DoG5bt6C.js';
1
+ import { S as StartStreamArgs, a as StreamHandle, b as StreamPayload, c as StreamDriver, d as Stream, E as Emit, P as PendingPermission } from '../types-DV6-SxsB.js';
2
2
  import { EventEmitter } from 'events';
3
3
  import '@agentclientprotocol/sdk';
4
4
 
@@ -43,6 +43,12 @@ declare class MuxManager extends EventEmitter {
43
43
  getForStream(streamId: string): StreamEntry | undefined;
44
44
  getPendingPermission(streamId: string): PendingPermission | null;
45
45
  registerDriver(driver: StreamDriver): void;
46
+ /**
47
+ * Unregister a driver by its type.
48
+ * Stops all streams using this driver before removing it.
49
+ * Used for hot-reload of plugin drivers.
50
+ */
51
+ unregisterDriver(type: string): boolean;
46
52
  }
47
53
  declare const muxManager: MuxManager;
48
54
 
@@ -2,7 +2,7 @@ import {
2
2
  AcpDriver,
3
3
  MockDriver,
4
4
  PtyDriver
5
- } from "../chunk-HJCRMFTD.js";
5
+ } from "../chunk-VVXT4HQM.js";
6
6
  import {
7
7
  debug
8
8
  } from "../chunk-5IPYOXBE.js";
@@ -116,13 +116,8 @@ var MuxManager = class extends EventEmitter {
116
116
  if (!entry || entry.status !== "ready") {
117
117
  throw new Error(`Stream not ready for ${streamId}`);
118
118
  }
119
- this.emitUpdate(streamId, { amuxEvent: "turn_start" });
120
119
  const driverEmit = (payload) => this.emitUpdate(streamId, payload);
121
- try {
122
- await entry.driver.input(streamId, message, entry.cwd, driverEmit);
123
- } finally {
124
- this.emitUpdate(streamId, { amuxEvent: "turn_end" });
125
- }
120
+ await entry.driver.input(streamId, message, entry.cwd, driverEmit);
126
121
  }
127
122
  async setMode(streamId, modeId) {
128
123
  const entry = this.streamEntries.get(streamId);
@@ -228,6 +223,22 @@ var MuxManager = class extends EventEmitter {
228
223
  registerDriver(driver) {
229
224
  this.drivers.unshift(driver);
230
225
  }
226
+ /**
227
+ * Unregister a driver by its type.
228
+ * Stops all streams using this driver before removing it.
229
+ * Used for hot-reload of plugin drivers.
230
+ */
231
+ unregisterDriver(type) {
232
+ const idx = this.drivers.findIndex((d) => d.type === type);
233
+ if (idx === -1) return false;
234
+ const driver = this.drivers[idx];
235
+ const streamsToStop = Array.from(this.streamEntries.entries()).filter(([_, entry]) => entry.driver === driver).map(([streamId]) => streamId);
236
+ for (const streamId of streamsToStop) {
237
+ this.stopForStream(streamId);
238
+ }
239
+ this.drivers.splice(idx, 1);
240
+ return true;
241
+ }
231
242
  };
232
243
  var muxManager = new MuxManager();
233
244
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/streams/manager.ts"],"sourcesContent":["import { EventEmitter } from 'events';\nimport type { StreamPayload, StreamEvent, Emit, StreamHandle, StartStreamArgs } from '../types.js';\nimport { AcpDriver, MockDriver, PtyDriver } from './backends/index.js';\nimport type { StreamDriver, Stream } from './backends/index.js';\nimport { debug } from '../lib/logger.js';\n\ninterface StreamEntry {\n driver: StreamDriver;\n stream: Stream;\n emit: Emit;\n cwd: string;\n storageDir: string;\n status: 'starting' | 'ready' | 'dead';\n}\n\nclass MuxManager extends EventEmitter {\n private drivers: StreamDriver[];\n private streamEntries = new Map<string, StreamEntry>();\n\n constructor() {\n super();\n this.drivers = [\n new AcpDriver(),\n new PtyDriver(),\n new MockDriver(),\n ];\n }\n\n private getDriver(type: string): StreamDriver {\n const driver = this.drivers.find(d => d.type === type);\n if (!driver) {\n throw new Error(`No driver registered for type: ${type}`);\n }\n return driver;\n }\n\n private emitUpdate(streamId: string, payload: StreamPayload): void {\n const entry = this.streamEntries.get(streamId);\n if (!entry) return;\n\n const event: StreamEvent = {\n ...payload,\n streamId,\n timestamp: Date.now(),\n };\n\n // Call the stream's emit callback (for the consumer to persist/handle)\n entry.emit(event);\n\n // Also emit on EventEmitter for internal subscribers\n this.emit('update', event);\n }\n\n /**\n * Start a stream with the given configuration.\n * The caller provides config, cwd, storageDir, and an emit callback for receiving events.\n */\n async startStream(args: StartStreamArgs): Promise<StreamHandle> {\n const { streamId, driverType, config, cwd, restoredState, emit, storageDir } = args;\n\n const existing = this.streamEntries.get(streamId);\n\n if (existing?.status === 'ready') {\n // Already ready - return the existing handle\n return {\n streamId,\n stream: existing.stream,\n driverType: existing.driver.type,\n };\n }\n\n if (existing?.status === 'starting') {\n // Wait for the stream to be ready\n return new Promise((resolve, reject) => {\n const checkReady = () => {\n const entry = this.streamEntries.get(streamId);\n if (entry?.status === 'ready') {\n resolve({\n streamId,\n stream: entry.stream,\n driverType: entry.driver.type,\n });\n } else if (entry?.status === 'dead') {\n reject(new Error('Stream failed to start'));\n } else {\n setTimeout(checkReady, 100);\n }\n };\n checkReady();\n });\n }\n\n const driver = this.getDriver(driverType);\n const driverEmit = (payload: StreamPayload) => this.emitUpdate(streamId, payload);\n\n this.streamEntries.set(streamId, {\n driver,\n stream: {},\n emit,\n cwd,\n storageDir,\n status: 'starting',\n });\n\n try {\n const stream = await driver.start(streamId, config, cwd, restoredState ?? null, driverEmit, storageDir);\n\n this.streamEntries.set(streamId, {\n driver,\n stream,\n emit,\n cwd,\n storageDir,\n status: 'ready',\n });\n\n debug('streams', `Stream ready for ${streamId}`);\n\n return {\n streamId,\n stream,\n driverType: driver.type,\n };\n } catch (err) {\n this.streamEntries.set(streamId, {\n driver,\n stream: {},\n emit,\n cwd,\n storageDir,\n status: 'dead',\n });\n throw err;\n }\n }\n\n async input(streamId: string, message: string): Promise<void> {\n debug('streams', `input() called for stream ${streamId}: \"${message.slice(0, 50)}...\"`);\n\n const entry = this.streamEntries.get(streamId);\n if (!entry || entry.status !== 'ready') {\n throw new Error(`Stream not ready for ${streamId}`);\n }\n\n // Signal turn start for streaming state\n this.emitUpdate(streamId, { amuxEvent: 'turn_start' });\n\n const driverEmit = (payload: StreamPayload) => this.emitUpdate(streamId, payload);\n\n try {\n await entry.driver.input(streamId, message, entry.cwd, driverEmit);\n } finally {\n this.emitUpdate(streamId, { amuxEvent: 'turn_end' });\n }\n }\n\n async setMode(streamId: string, modeId: string): Promise<void> {\n const entry = this.streamEntries.get(streamId);\n if (!entry || entry.status !== 'ready') {\n throw new Error(`Stream not ready for ${streamId}`);\n }\n if (!entry.driver.setMode) {\n throw new Error(`Driver ${entry.driver.type} does not support setMode`);\n }\n await entry.driver.setMode(streamId, modeId);\n }\n\n async setModel(streamId: string, modelId: string): Promise<void> {\n const entry = this.streamEntries.get(streamId);\n if (!entry || entry.status !== 'ready') {\n throw new Error(`Stream not ready for ${streamId}`);\n }\n if (!entry.driver.setModel) {\n throw new Error(`Driver ${entry.driver.type} does not support setModel`);\n }\n await entry.driver.setModel(streamId, modelId);\n }\n\n async cancel(streamId: string): Promise<void> {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.cancel) return;\n await entry.driver.cancel(streamId);\n }\n\n // Terminal-specific methods (for PtyDriver)\n\n terminalWrite(streamId: string, data: string): void {\n const entry = this.streamEntries.get(streamId);\n if (!entry) {\n throw new Error(`No stream running for ${streamId}`);\n }\n if (!entry.driver.terminalWrite) {\n throw new Error(`Driver ${entry.driver.type} does not support terminal input`);\n }\n entry.driver.terminalWrite(streamId, data);\n }\n\n terminalResize(streamId: string, cols: number, rows: number): void {\n const entry = this.streamEntries.get(streamId);\n if (!entry) {\n throw new Error(`No stream running for ${streamId}`);\n }\n if (!entry.driver.terminalResize) {\n throw new Error(`Driver ${entry.driver.type} does not support terminal resize`);\n }\n entry.driver.terminalResize(streamId, cols, rows);\n }\n\n getTerminalScrollback(streamId: string): string | undefined {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.getScrollback) return undefined;\n return entry.driver.getScrollback(streamId);\n }\n\n /**\n * Get replay data from the driver (events for ACP, scrollback for PTY).\n */\n getReplayData(streamId: string): StreamPayload[] | string | undefined {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.getReplayData) return undefined;\n return entry.driver.getReplayData(streamId);\n }\n\n isTerminalStream(streamId: string): boolean {\n const entry = this.streamEntries.get(streamId);\n return entry?.driver.isInteractive ?? false;\n }\n\n respondPermission(streamId: string, requestId: string, optionId: string): void {\n const entry = this.streamEntries.get(streamId);\n if (!entry) {\n // Stream may have stopped - silently ignore stale permission responses\n console.warn(`[MuxManager] No stream running for ${streamId}, ignoring permission response`);\n return;\n }\n if (!entry.driver.respondToPermission) {\n throw new Error(`Driver ${entry.driver.type} does not support permissions`);\n }\n entry.driver.respondToPermission(streamId, requestId, optionId);\n }\n\n /**\n * Get the driver state for a stream (for persistence before stopping).\n */\n getDriverState(streamId: string): unknown | undefined {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.getState) return undefined;\n return entry.driver.getState(streamId);\n }\n\n async stopForStream(streamId: string): Promise<void> {\n const entry = this.streamEntries.get(streamId);\n if (entry) {\n await entry.driver.stop(streamId);\n }\n this.streamEntries.delete(streamId);\n }\n\n async stopAll(): Promise<void> {\n const streamIds = Array.from(this.streamEntries.keys());\n await Promise.all(streamIds.map(id => this.stopForStream(id)));\n }\n\n getForStream(streamId: string): StreamEntry | undefined {\n return this.streamEntries.get(streamId);\n }\n\n getPendingPermission(streamId: string): import('../types.js').PendingPermission | null {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.getPendingPermission) return null;\n return entry.driver.getPendingPermission(streamId);\n }\n\n registerDriver(driver: StreamDriver): void {\n this.drivers.unshift(driver);\n }\n}\n\nexport const muxManager = new MuxManager();\nexport { MuxManager };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/streams/manager.ts"],"sourcesContent":["import { EventEmitter } from 'events';\nimport type { StreamPayload, StreamEvent, Emit, StreamHandle, StartStreamArgs } from '../types.js';\nimport { AcpDriver, MockDriver, PtyDriver } from './backends/index.js';\nimport type { StreamDriver, Stream } from './backends/index.js';\nimport { debug } from '../lib/logger.js';\n\ninterface StreamEntry {\n driver: StreamDriver;\n stream: Stream;\n emit: Emit;\n cwd: string;\n storageDir: string;\n status: 'starting' | 'ready' | 'dead';\n}\n\nclass MuxManager extends EventEmitter {\n private drivers: StreamDriver[];\n private streamEntries = new Map<string, StreamEntry>();\n\n constructor() {\n super();\n this.drivers = [\n new AcpDriver(),\n new PtyDriver(),\n new MockDriver(),\n ];\n }\n\n private getDriver(type: string): StreamDriver {\n const driver = this.drivers.find(d => d.type === type);\n if (!driver) {\n throw new Error(`No driver registered for type: ${type}`);\n }\n return driver;\n }\n\n private emitUpdate(streamId: string, payload: StreamPayload): void {\n const entry = this.streamEntries.get(streamId);\n if (!entry) return;\n\n const event: StreamEvent = {\n ...payload,\n streamId,\n timestamp: Date.now(),\n };\n\n // Call the stream's emit callback (for the consumer to persist/handle)\n entry.emit(event);\n\n // Also emit on EventEmitter for internal subscribers\n this.emit('update', event);\n }\n\n /**\n * Start a stream with the given configuration.\n * The caller provides config, cwd, storageDir, and an emit callback for receiving events.\n */\n async startStream(args: StartStreamArgs): Promise<StreamHandle> {\n const { streamId, driverType, config, cwd, restoredState, emit, storageDir } = args;\n\n const existing = this.streamEntries.get(streamId);\n\n if (existing?.status === 'ready') {\n // Already ready - return the existing handle\n return {\n streamId,\n stream: existing.stream,\n driverType: existing.driver.type,\n };\n }\n\n if (existing?.status === 'starting') {\n // Wait for the stream to be ready\n return new Promise((resolve, reject) => {\n const checkReady = () => {\n const entry = this.streamEntries.get(streamId);\n if (entry?.status === 'ready') {\n resolve({\n streamId,\n stream: entry.stream,\n driverType: entry.driver.type,\n });\n } else if (entry?.status === 'dead') {\n reject(new Error('Stream failed to start'));\n } else {\n setTimeout(checkReady, 100);\n }\n };\n checkReady();\n });\n }\n\n const driver = this.getDriver(driverType);\n const driverEmit = (payload: StreamPayload) => this.emitUpdate(streamId, payload);\n\n this.streamEntries.set(streamId, {\n driver,\n stream: {},\n emit,\n cwd,\n storageDir,\n status: 'starting',\n });\n\n try {\n const stream = await driver.start(streamId, config, cwd, restoredState ?? null, driverEmit, storageDir);\n\n this.streamEntries.set(streamId, {\n driver,\n stream,\n emit,\n cwd,\n storageDir,\n status: 'ready',\n });\n\n debug('streams', `Stream ready for ${streamId}`);\n\n return {\n streamId,\n stream,\n driverType: driver.type,\n };\n } catch (err) {\n this.streamEntries.set(streamId, {\n driver,\n stream: {},\n emit,\n cwd,\n storageDir,\n status: 'dead',\n });\n throw err;\n }\n }\n\n async input(streamId: string, message: string): Promise<void> {\n debug('streams', `input() called for stream ${streamId}: \"${message.slice(0, 50)}...\"`);\n\n const entry = this.streamEntries.get(streamId);\n if (!entry || entry.status !== 'ready') {\n throw new Error(`Stream not ready for ${streamId}`);\n }\n\n const driverEmit = (payload: StreamPayload) => this.emitUpdate(streamId, payload);\n await entry.driver.input(streamId, message, entry.cwd, driverEmit);\n }\n\n async setMode(streamId: string, modeId: string): Promise<void> {\n const entry = this.streamEntries.get(streamId);\n if (!entry || entry.status !== 'ready') {\n throw new Error(`Stream not ready for ${streamId}`);\n }\n if (!entry.driver.setMode) {\n throw new Error(`Driver ${entry.driver.type} does not support setMode`);\n }\n await entry.driver.setMode(streamId, modeId);\n }\n\n async setModel(streamId: string, modelId: string): Promise<void> {\n const entry = this.streamEntries.get(streamId);\n if (!entry || entry.status !== 'ready') {\n throw new Error(`Stream not ready for ${streamId}`);\n }\n if (!entry.driver.setModel) {\n throw new Error(`Driver ${entry.driver.type} does not support setModel`);\n }\n await entry.driver.setModel(streamId, modelId);\n }\n\n async cancel(streamId: string): Promise<void> {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.cancel) return;\n await entry.driver.cancel(streamId);\n }\n\n // Terminal-specific methods (for PtyDriver)\n\n terminalWrite(streamId: string, data: string): void {\n const entry = this.streamEntries.get(streamId);\n if (!entry) {\n throw new Error(`No stream running for ${streamId}`);\n }\n if (!entry.driver.terminalWrite) {\n throw new Error(`Driver ${entry.driver.type} does not support terminal input`);\n }\n entry.driver.terminalWrite(streamId, data);\n }\n\n terminalResize(streamId: string, cols: number, rows: number): void {\n const entry = this.streamEntries.get(streamId);\n if (!entry) {\n throw new Error(`No stream running for ${streamId}`);\n }\n if (!entry.driver.terminalResize) {\n throw new Error(`Driver ${entry.driver.type} does not support terminal resize`);\n }\n entry.driver.terminalResize(streamId, cols, rows);\n }\n\n getTerminalScrollback(streamId: string): string | undefined {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.getScrollback) return undefined;\n return entry.driver.getScrollback(streamId);\n }\n\n /**\n * Get replay data from the driver (events for ACP, scrollback for PTY).\n */\n getReplayData(streamId: string): StreamPayload[] | string | undefined {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.getReplayData) return undefined;\n return entry.driver.getReplayData(streamId);\n }\n\n isTerminalStream(streamId: string): boolean {\n const entry = this.streamEntries.get(streamId);\n return entry?.driver.isInteractive ?? false;\n }\n\n respondPermission(streamId: string, requestId: string, optionId: string): void {\n const entry = this.streamEntries.get(streamId);\n if (!entry) {\n // Stream may have stopped - silently ignore stale permission responses\n console.warn(`[MuxManager] No stream running for ${streamId}, ignoring permission response`);\n return;\n }\n if (!entry.driver.respondToPermission) {\n throw new Error(`Driver ${entry.driver.type} does not support permissions`);\n }\n entry.driver.respondToPermission(streamId, requestId, optionId);\n }\n\n /**\n * Get the driver state for a stream (for persistence before stopping).\n */\n getDriverState(streamId: string): unknown | undefined {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.getState) return undefined;\n return entry.driver.getState(streamId);\n }\n\n async stopForStream(streamId: string): Promise<void> {\n const entry = this.streamEntries.get(streamId);\n if (entry) {\n await entry.driver.stop(streamId);\n }\n this.streamEntries.delete(streamId);\n }\n\n async stopAll(): Promise<void> {\n const streamIds = Array.from(this.streamEntries.keys());\n await Promise.all(streamIds.map(id => this.stopForStream(id)));\n }\n\n getForStream(streamId: string): StreamEntry | undefined {\n return this.streamEntries.get(streamId);\n }\n\n getPendingPermission(streamId: string): import('../types.js').PendingPermission | null {\n const entry = this.streamEntries.get(streamId);\n if (!entry?.driver.getPendingPermission) return null;\n return entry.driver.getPendingPermission(streamId);\n }\n\n registerDriver(driver: StreamDriver): void {\n this.drivers.unshift(driver);\n }\n\n /**\n * Unregister a driver by its type.\n * Stops all streams using this driver before removing it.\n * Used for hot-reload of plugin drivers.\n */\n unregisterDriver(type: string): boolean {\n const idx = this.drivers.findIndex(d => d.type === type);\n if (idx === -1) return false;\n\n const driver = this.drivers[idx];\n\n // Stop all streams using this driver before unregistering\n const streamsToStop = Array.from(this.streamEntries.entries())\n .filter(([_, entry]) => entry.driver === driver)\n .map(([streamId]) => streamId);\n\n for (const streamId of streamsToStop) {\n this.stopForStream(streamId);\n }\n\n this.drivers.splice(idx, 1);\n return true;\n }\n}\n\nexport const muxManager = new MuxManager();\nexport { MuxManager };\n"],"mappings":"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","names":[]}
@@ -102,34 +102,75 @@ interface PendingPermission {
102
102
  kind: string;
103
103
  }>;
104
104
  }
105
- type AmuxEvent = {
106
- amuxEvent: 'error';
107
- message: string;
105
+ /**
106
+ * Universal lifecycle events that ALL drivers should emit.
107
+ */
108
+ type LifecycleEvent = {
109
+ eventType: 'ready';
108
110
  } | {
109
- amuxEvent: 'permission_request';
110
- permission: PendingPermission;
111
+ eventType: 'error';
112
+ message: string;
111
113
  } | {
112
- amuxEvent: 'permission_cleared';
114
+ eventType: 'stopped';
115
+ };
116
+ /**
117
+ * ACP session update events (from ACP driver).
118
+ * Wraps the ACP SessionUpdate with eventType discriminant.
119
+ */
120
+ type AcpEvent = {
121
+ eventType: 'acp';
122
+ } & SessionUpdate;
123
+ /**
124
+ * ACP-specific lifecycle events (turns, permissions).
125
+ * Only the ACP driver emits these.
126
+ */
127
+ type AcpLifecycleEvent = {
128
+ eventType: 'acp:turn_start';
113
129
  } | {
114
- amuxEvent: 'turn_start';
130
+ eventType: 'acp:turn_end';
115
131
  } | {
116
- amuxEvent: 'turn_end';
132
+ eventType: 'acp:turn_cancelled';
117
133
  } | {
118
- amuxEvent: 'turn_cancelled';
134
+ eventType: 'acp:permission_request';
135
+ permission: PendingPermission;
119
136
  } | {
120
- amuxEvent: 'terminal_output';
137
+ eventType: 'acp:permission_cleared';
138
+ };
139
+ /**
140
+ * Terminal events (from PTY/shell driver).
141
+ */
142
+ type TerminalEvent = {
143
+ eventType: 'terminal:output';
121
144
  data: string;
122
145
  } | {
123
- amuxEvent: 'terminal_exit';
146
+ eventType: 'terminal:exit';
124
147
  exitCode: number | null;
125
148
  signal: string | null;
126
149
  };
127
- type StreamPayload = SessionUpdate | AmuxEvent;
150
+ /**
151
+ * All known event types, plus a generic escape hatch for plugins.
152
+ * Plugins can emit { eventType: 'my-plugin:custom', ...data } and renderers
153
+ * can handle them with type narrowing.
154
+ */
155
+ type StreamPayload = LifecycleEvent | AcpEvent | AcpLifecycleEvent | TerminalEvent | {
156
+ eventType: string;
157
+ [key: string]: unknown;
158
+ };
159
+ /**
160
+ * StreamEvent = payload with metadata stamped at emission time.
161
+ */
128
162
  type StreamEvent = StreamPayload & {
129
163
  streamId: string;
130
164
  timestamp: number;
131
165
  };
166
+ /**
167
+ * Emit callback type for library consumers.
168
+ */
132
169
  type Emit = (event: StreamEvent) => void;
170
+ declare function isLifecycleEvent(payload: StreamPayload): payload is LifecycleEvent;
171
+ declare function isAcpEvent(payload: StreamPayload): payload is AcpEvent;
172
+ declare function isAcpLifecycleEvent(payload: StreamPayload): payload is AcpLifecycleEvent;
173
+ declare function isTerminalEvent(payload: StreamPayload): payload is TerminalEvent;
133
174
  interface StreamHandle {
134
175
  streamId: string;
135
176
  stream: Stream;
@@ -147,7 +188,5 @@ interface StartStreamArgs {
147
188
  /** Directory for driver to store persistent data (e.g. history.json) */
148
189
  storageDir: string;
149
190
  }
150
- declare function isSessionUpdate(payload: StreamPayload): payload is SessionUpdate;
151
- declare function isAmuxEvent(payload: StreamPayload): payload is AmuxEvent;
152
191
 
153
- export { type AmuxEvent as A, type Emit as E, type ModelInfo as M, type NormalizedPlanEntry as N, type PendingPermission as P, type StartStreamArgs as S, type StreamHandle as a, type StreamPayload as b, type StreamDriver as c, type Stream as d, type StreamConfig as e, type EmitFn as f, type ModeInfo as g, type StreamEvent as h, isSessionUpdate as i, isAmuxEvent as j, normalizePlanEntry as n };
192
+ export { type AcpEvent as A, type Emit as E, type LifecycleEvent as L, type ModelInfo as M, type NormalizedPlanEntry as N, type PendingPermission as P, type StartStreamArgs as S, type TerminalEvent as T, type StreamHandle as a, type StreamPayload as b, type StreamDriver as c, type Stream as d, type StreamConfig as e, type EmitFn as f, type ModeInfo as g, type AcpLifecycleEvent as h, type StreamEvent as i, isLifecycleEvent as j, isAcpEvent as k, isAcpLifecycleEvent as l, isTerminalEvent as m, normalizePlanEntry as n };
package/dist/types.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { A as AmuxEvent, E as Emit, g as ModeInfo, M as ModelInfo, N as NormalizedPlanEntry, P as PendingPermission, S as StartStreamArgs, h as StreamEvent, a as StreamHandle, b as StreamPayload, j as isAmuxEvent, i as isSessionUpdate, n as normalizePlanEntry } from './types-DoG5bt6C.js';
1
+ export { A as AcpEvent, h as AcpLifecycleEvent, E as Emit, L as LifecycleEvent, g as ModeInfo, M as ModelInfo, N as NormalizedPlanEntry, P as PendingPermission, S as StartStreamArgs, i as StreamEvent, a as StreamHandle, b as StreamPayload, T as TerminalEvent, k as isAcpEvent, l as isAcpLifecycleEvent, j as isLifecycleEvent, m as isTerminalEvent, n as normalizePlanEntry } from './types-DV6-SxsB.js';
2
2
  export { PlanEntry as AcpPlanEntry, AudioContent, AvailableCommand, AvailableCommandsUpdate, ContentBlock, ContentChunk, CurrentModeUpdate, EmbeddedResource, ImageContent, PermissionOption, Plan, PlanEntryPriority, PlanEntryStatus, RequestPermissionRequest, RequestPermissionResponse, ResourceLink, SessionUpdate, TextContent, ToolCall, ToolCallStatus, ToolCallUpdate } from '@agentclientprotocol/sdk';
package/dist/types.js CHANGED
@@ -11,15 +11,23 @@ function normalizePlanEntry(entry) {
11
11
  activeForm: activeForm ?? directActiveForm
12
12
  };
13
13
  }
14
- function isSessionUpdate(payload) {
15
- return "sessionUpdate" in payload;
14
+ function isLifecycleEvent(payload) {
15
+ return payload.eventType === "ready" || payload.eventType === "error" || payload.eventType === "stopped";
16
16
  }
17
- function isAmuxEvent(payload) {
18
- return "amuxEvent" in payload;
17
+ function isAcpEvent(payload) {
18
+ return payload.eventType === "acp";
19
+ }
20
+ function isAcpLifecycleEvent(payload) {
21
+ return typeof payload.eventType === "string" && payload.eventType.startsWith("acp:");
22
+ }
23
+ function isTerminalEvent(payload) {
24
+ return typeof payload.eventType === "string" && payload.eventType.startsWith("terminal:");
19
25
  }
20
26
  export {
21
- isAmuxEvent,
22
- isSessionUpdate,
27
+ isAcpEvent,
28
+ isAcpLifecycleEvent,
29
+ isLifecycleEvent,
30
+ isTerminalEvent,
23
31
  normalizePlanEntry
24
32
  };
25
33
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["// Re-export ACP types from SDK\nexport type {\n SessionUpdate,\n ContentBlock,\n ContentChunk,\n TextContent,\n ImageContent,\n AudioContent,\n EmbeddedResource,\n ResourceLink,\n PlanEntry as AcpPlanEntry,\n PlanEntryPriority,\n PlanEntryStatus,\n Plan,\n AvailableCommand,\n AvailableCommandsUpdate,\n CurrentModeUpdate,\n ToolCall,\n ToolCallUpdate,\n ToolCallStatus,\n PermissionOption,\n RequestPermissionRequest,\n RequestPermissionResponse,\n} from '@agentclientprotocol/sdk';\n\nimport type { PlanEntry as AcpPlanEntry, PlanEntryPriority, PlanEntryStatus, SessionUpdate } from '@agentclientprotocol/sdk';\n\n/**\n * Normalized PlanEntry that handles agent-specific quirks.\n *\n * Standard ACP: content, priority, status\n * Claude-style: content, status, activeForm (priority always \"medium\")\n *\n * The `activeForm` field provides a present-tense description (e.g., \"Analyzing tests\")\n * vs the imperative `content` (e.g., \"Analyze tests\"). When `activeForm` is present\n * and the status is \"in_progress\", UI should prefer displaying `activeForm`.\n */\nexport interface NormalizedPlanEntry {\n content: string;\n status: PlanEntryStatus;\n priority: PlanEntryPriority;\n /** Present-tense form for in_progress display (Claude-style). */\n activeForm?: string;\n}\n\n/**\n * Normalize an incoming ACP plan entry.\n * Extracts `activeForm` from Claude's `_meta.claudeCode` extension if present.\n */\nexport function normalizePlanEntry(entry: AcpPlanEntry): NormalizedPlanEntry {\n // Try to extract activeForm from _meta.claudeCode (if claude-acp sends it there)\n const meta = entry._meta as Record<string, unknown> | undefined;\n const claudeCode = meta?.claudeCode as Record<string, unknown> | undefined;\n const activeForm = claudeCode?.activeForm as string | undefined;\n\n // Also check if activeForm is directly on the entry (non-standard but possible)\n const directActiveForm = (entry as Record<string, unknown>).activeForm as string | undefined;\n\n return {\n content: entry.content,\n status: entry.status,\n priority: entry.priority,\n activeForm: activeForm ?? directActiveForm,\n };\n}\n\n// Model and mode info returned from stream session\nexport interface ModelInfo {\n modelId: string;\n name: string;\n}\n\nexport interface ModeInfo {\n id: string;\n name: string;\n description?: string;\n}\n\n// Permission types for WebSocket push\nexport interface PendingPermission {\n requestId: string;\n toolCallId?: string;\n title: string;\n options: Array<{ optionId: string; name: string; kind: string }>;\n}\n\n// amux events pushed via WebSocket subscription.\n// ACP blocking RPCs (requestPermission, prompt) need WebSocket notifications\n// so the UI can track state independently of RPC callbacks (e.g. after page reload).\n// Pattern: RPC start notification, RPC complete notification\nexport type AmuxEvent =\n | { amuxEvent: 'error'; message: string }\n | { amuxEvent: 'permission_request'; permission: PendingPermission }\n | { amuxEvent: 'permission_cleared' }\n | { amuxEvent: 'turn_start' }\n | { amuxEvent: 'turn_end' }\n | { amuxEvent: 'turn_cancelled' }\n // Terminal-specific events (ShellBackend)\n | { amuxEvent: 'terminal_output'; data: string }\n | { amuxEvent: 'terminal_exit'; exitCode: number | null; signal: string | null };\n\n// Payload types for stream subscriptions\nexport type StreamPayload = SessionUpdate | AmuxEvent;\n\n// Event emitted by amux - payload with metadata stamped at emission time\nexport type StreamEvent = StreamPayload & {\n streamId: string;\n timestamp: number;\n};\n\n// Emit callback type for library consumers\nexport type Emit = (event: StreamEvent) => void;\n\n// Handle returned from startStream for the caller to use\nexport interface StreamHandle {\n streamId: string;\n stream: import('./streams/backends/types.js').Stream;\n /** Driver type that handled this stream */\n driverType: string;\n}\n\n// Arguments for starting a stream\nexport interface StartStreamArgs {\n streamId: string;\n /** Driver type to route to (e.g., 'acp', 'pty', or custom plugin types) */\n driverType: string;\n config: import('./streams/backends/types.js').StreamConfig;\n cwd: string;\n restoredState?: unknown;\n emit: Emit;\n /** Directory for driver to store persistent data (e.g. history.json) */\n storageDir: string;\n}\n\n// Type guard helpers\nexport function isSessionUpdate(payload: StreamPayload): payload is SessionUpdate {\n return 'sessionUpdate' in payload;\n}\n\nexport function isAmuxEvent(payload: StreamPayload): payload is AmuxEvent {\n return 'amuxEvent' in payload;\n}\n\n"],"mappings":";AAiDO,SAAS,mBAAmB,OAA0C;AAE3E,QAAM,OAAO,MAAM;AACnB,QAAM,aAAa,MAAM;AACzB,QAAM,aAAa,YAAY;AAG/B,QAAM,mBAAoB,MAAkC;AAE5D,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,QAAQ,MAAM;AAAA,IACd,UAAU,MAAM;AAAA,IAChB,YAAY,cAAc;AAAA,EAC5B;AACF;AAuEO,SAAS,gBAAgB,SAAkD;AAChF,SAAO,mBAAmB;AAC5B;AAEO,SAAS,YAAY,SAA8C;AACxE,SAAO,eAAe;AACxB;","names":[]}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["// Re-export ACP types from SDK\nexport type {\n SessionUpdate,\n ContentBlock,\n ContentChunk,\n TextContent,\n ImageContent,\n AudioContent,\n EmbeddedResource,\n ResourceLink,\n PlanEntry as AcpPlanEntry,\n PlanEntryPriority,\n PlanEntryStatus,\n Plan,\n AvailableCommand,\n AvailableCommandsUpdate,\n CurrentModeUpdate,\n ToolCall,\n ToolCallUpdate,\n ToolCallStatus,\n PermissionOption,\n RequestPermissionRequest,\n RequestPermissionResponse,\n} from '@agentclientprotocol/sdk';\n\nimport type { PlanEntry as AcpPlanEntry, PlanEntryPriority, PlanEntryStatus, SessionUpdate } from '@agentclientprotocol/sdk';\n\n/**\n * Normalized PlanEntry that handles agent-specific quirks.\n *\n * Standard ACP: content, priority, status\n * Claude-style: content, status, activeForm (priority always \"medium\")\n *\n * The `activeForm` field provides a present-tense description (e.g., \"Analyzing tests\")\n * vs the imperative `content` (e.g., \"Analyze tests\"). When `activeForm` is present\n * and the status is \"in_progress\", UI should prefer displaying `activeForm`.\n */\nexport interface NormalizedPlanEntry {\n content: string;\n status: PlanEntryStatus;\n priority: PlanEntryPriority;\n /** Present-tense form for in_progress display (Claude-style). */\n activeForm?: string;\n}\n\n/**\n * Normalize an incoming ACP plan entry.\n * Extracts `activeForm` from Claude's `_meta.claudeCode` extension if present.\n */\nexport function normalizePlanEntry(entry: AcpPlanEntry): NormalizedPlanEntry {\n // Try to extract activeForm from _meta.claudeCode (if claude-acp sends it there)\n const meta = entry._meta as Record<string, unknown> | undefined;\n const claudeCode = meta?.claudeCode as Record<string, unknown> | undefined;\n const activeForm = claudeCode?.activeForm as string | undefined;\n\n // Also check if activeForm is directly on the entry (non-standard but possible)\n const directActiveForm = (entry as Record<string, unknown>).activeForm as string | undefined;\n\n return {\n content: entry.content,\n status: entry.status,\n priority: entry.priority,\n activeForm: activeForm ?? directActiveForm,\n };\n}\n\n// Model and mode info returned from stream session\nexport interface ModelInfo {\n modelId: string;\n name: string;\n}\n\nexport interface ModeInfo {\n id: string;\n name: string;\n description?: string;\n}\n\n// Permission types for WebSocket push\nexport interface PendingPermission {\n requestId: string;\n toolCallId?: string;\n title: string;\n options: Array<{ optionId: string; name: string; kind: string }>;\n}\n\n// ============================================================================\n// Generic Event System\n//\n// All events have an `eventType` discriminant. Known events are typed precisely;\n// plugins use the generic escape hatch { eventType: string; [key: string]: unknown }.\n// ============================================================================\n\n/**\n * Universal lifecycle events that ALL drivers should emit.\n */\nexport type LifecycleEvent =\n | { eventType: 'ready' }\n | { eventType: 'error'; message: string }\n | { eventType: 'stopped' };\n\n/**\n * ACP session update events (from ACP driver).\n * Wraps the ACP SessionUpdate with eventType discriminant.\n */\nexport type AcpEvent = { eventType: 'acp' } & SessionUpdate;\n\n/**\n * ACP-specific lifecycle events (turns, permissions).\n * Only the ACP driver emits these.\n */\nexport type AcpLifecycleEvent =\n | { eventType: 'acp:turn_start' }\n | { eventType: 'acp:turn_end' }\n | { eventType: 'acp:turn_cancelled' }\n | { eventType: 'acp:permission_request'; permission: PendingPermission }\n | { eventType: 'acp:permission_cleared' };\n\n/**\n * Terminal events (from PTY/shell driver).\n */\nexport type TerminalEvent =\n | { eventType: 'terminal:output'; data: string }\n | { eventType: 'terminal:exit'; exitCode: number | null; signal: string | null };\n\n/**\n * All known event types, plus a generic escape hatch for plugins.\n * Plugins can emit { eventType: 'my-plugin:custom', ...data } and renderers\n * can handle them with type narrowing.\n */\nexport type StreamPayload =\n | LifecycleEvent\n | AcpEvent\n | AcpLifecycleEvent\n | TerminalEvent\n | { eventType: string; [key: string]: unknown };\n\n/**\n * StreamEvent = payload with metadata stamped at emission time.\n */\nexport type StreamEvent = StreamPayload & {\n streamId: string;\n timestamp: number;\n};\n\n/**\n * Emit callback type for library consumers.\n */\nexport type Emit = (event: StreamEvent) => void;\n\n// Type guard helpers\nexport function isLifecycleEvent(payload: StreamPayload): payload is LifecycleEvent {\n return payload.eventType === 'ready' || payload.eventType === 'error' || payload.eventType === 'stopped';\n}\n\nexport function isAcpEvent(payload: StreamPayload): payload is AcpEvent {\n return payload.eventType === 'acp';\n}\n\nexport function isAcpLifecycleEvent(payload: StreamPayload): payload is AcpLifecycleEvent {\n return typeof payload.eventType === 'string' && payload.eventType.startsWith('acp:');\n}\n\nexport function isTerminalEvent(payload: StreamPayload): payload is TerminalEvent {\n return typeof payload.eventType === 'string' && payload.eventType.startsWith('terminal:');\n}\n\n// Handle returned from startStream for the caller to use\nexport interface StreamHandle {\n streamId: string;\n stream: import('./streams/backends/types.js').Stream;\n /** Driver type that handled this stream */\n driverType: string;\n}\n\n// Arguments for starting a stream\nexport interface StartStreamArgs {\n streamId: string;\n /** Driver type to route to (e.g., 'acp', 'pty', or custom plugin types) */\n driverType: string;\n config: import('./streams/backends/types.js').StreamConfig;\n cwd: string;\n restoredState?: unknown;\n emit: Emit;\n /** Directory for driver to store persistent data (e.g. history.json) */\n storageDir: string;\n}\n"],"mappings":";AAiDO,SAAS,mBAAmB,OAA0C;AAE3E,QAAM,OAAO,MAAM;AACnB,QAAM,aAAa,MAAM;AACzB,QAAM,aAAa,YAAY;AAG/B,QAAM,mBAAoB,MAAkC;AAE5D,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,QAAQ,MAAM;AAAA,IACd,UAAU,MAAM;AAAA,IAChB,YAAY,cAAc;AAAA,EAC5B;AACF;AAuFO,SAAS,iBAAiB,SAAmD;AAClF,SAAO,QAAQ,cAAc,WAAW,QAAQ,cAAc,WAAW,QAAQ,cAAc;AACjG;AAEO,SAAS,WAAW,SAA6C;AACtE,SAAO,QAAQ,cAAc;AAC/B;AAEO,SAAS,oBAAoB,SAAsD;AACxF,SAAO,OAAO,QAAQ,cAAc,YAAY,QAAQ,UAAU,WAAW,MAAM;AACrF;AAEO,SAAS,gBAAgB,SAAkD;AAChF,SAAO,OAAO,QAAQ,cAAc,YAAY,QAAQ,UAAU,WAAW,WAAW;AAC1F;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytespell/amux",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Agent Multiplexer - library for managing agent/terminal processes",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/streams/backends/acp.ts","../src/streams/process.ts","../src/streams/backends/mock.ts","../src/streams/backends/shell.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { spawn as nodeSpawn, type ChildProcess } from 'child_process';\nimport { Readable, Writable } from 'stream';\nimport * as fs from 'fs/promises';\nimport * as fsSync from 'fs';\nimport * as path from 'path';\nimport {\n ClientSideConnection,\n ndJsonStream,\n Client,\n RequestPermissionRequest,\n RequestPermissionResponse,\n SessionNotification,\n ReadTextFileRequest,\n ReadTextFileResponse,\n WriteTextFileRequest,\n WriteTextFileResponse,\n CreateTerminalRequest,\n CreateTerminalResponse,\n TerminalOutputRequest,\n TerminalOutputResponse,\n WaitForTerminalExitRequest,\n WaitForTerminalExitResponse,\n KillTerminalCommandRequest,\n KillTerminalCommandResponse,\n ReleaseTerminalRequest,\n ReleaseTerminalResponse,\n} from '@agentclientprotocol/sdk';\nimport type { StreamDriver, StreamConfig, Stream, EmitFn } from './types.js';\nimport type { PendingPermission, StreamPayload } from '../../types.js';\nimport { spawn, type ManagedProcess } from '../process.js';\nimport { debug } from '../../lib/logger.js';\nimport { parseMessageToContentBlocks } from '../../lib/mentions.js';\n\nconst INIT_TIMEOUT_MS = 90000; // 90s to allow npx download on first run\n\n/** ACP backend state for persistence */\nexport interface AcpBackendState {\n acpSessionId: string;\n}\n\n/**\n * Normalize ACP session updates.\n *\n * Amux passes through events as-is, but normalizes quirks from specific agents:\n * - Claude sends Edit/Write diffs as {newText, oldText, path} instead of unified diff\n * - We convert these to standard unified diff format so the UI doesn't need agent-specific logic\n */\nfunction normalizeSessionUpdate(update: Record<string, unknown>): Record<string, unknown> {\n // Only process tool_call and tool_call_update events with content arrays\n if (update.sessionUpdate !== 'tool_call' && update.sessionUpdate !== 'tool_call_update') {\n return update;\n }\n\n const content = update.content as Array<Record<string, unknown>> | undefined;\n if (!content || !Array.isArray(content)) {\n return update;\n }\n\n // Check for diff items that need normalization\n const normalizedContent = content.map(item => {\n if (item.type !== 'diff') return item;\n\n // Already a string diff - pass through\n if (typeof item.content === 'string') return item;\n\n // Claude-style diff: {type: 'diff', newText, oldText, path}\n // Convert to unified diff format\n const newText = item.newText as string | undefined;\n const oldText = item.oldText as string | null | undefined;\n const path = item.path as string | undefined;\n\n if (newText === undefined) return item;\n\n // Generate unified diff\n const filePath = path ?? 'file';\n const oldLines = oldText ? oldText.split('\\n') : [];\n const newLines = newText.split('\\n');\n\n let unifiedDiff = `Index: ${filePath}\\n`;\n unifiedDiff += '===================================================================\\n';\n unifiedDiff += `--- ${filePath}\\n`;\n unifiedDiff += `+++ ${filePath}\\n`;\n unifiedDiff += `@@ -${oldLines.length > 0 ? 1 : 0},${oldLines.length} +1,${newLines.length} @@\\n`;\n\n for (const line of oldLines) {\n unifiedDiff += `-${line}\\n`;\n }\n for (const line of newLines) {\n unifiedDiff += `+${line}\\n`;\n }\n\n // Return normalized diff item\n return {\n type: 'diff',\n content: unifiedDiff,\n };\n });\n\n return {\n ...update,\n content: normalizedContent,\n };\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {\n return Promise.race([\n promise,\n new Promise<T>((_, reject) =>\n setTimeout(() => reject(new Error(`${operation} timed out after ${ms}ms`)), ms)\n ),\n ]);\n}\n\ninterface Terminal {\n process: ChildProcess;\n output: string;\n exitCode: number | null;\n signal: string | null;\n truncated: boolean;\n outputByteLimit: number;\n}\n\ninterface AcpInstance {\n process: ManagedProcess;\n connection: ClientSideConnection;\n acpSessionId: string; // The ACP protocol session ID\n pendingPermission: PendingPermission | null;\n permissionCallbacks: Map<string, {\n resolve: (optionId: string) => void;\n reject: (err: Error) => void;\n }>;\n emit: EmitFn;\n terminals: Map<string, Terminal>;\n storageDir: string;\n}\n\nexport class AcpDriver implements StreamDriver {\n readonly type = 'acp';\n readonly streamType = 'acp' as const;\n private instances = new Map<string, AcpInstance>();\n\n private getHistoryPath(storageDir: string): string {\n return path.join(storageDir, 'history.json');\n }\n\n private storeEvent(storageDir: string, update: StreamPayload): void {\n // Only store SessionUpdate events and turn markers\n const isSessionUpdate = typeof update === 'object' && update !== null && 'sessionUpdate' in update;\n const isAmuxEvent = typeof update === 'object' && update !== null && 'amuxEvent' in update;\n const isTurnMarker = isAmuxEvent && ((update as any).amuxEvent === 'turn_start' || (update as any).amuxEvent === 'turn_end');\n\n if (!isSessionUpdate && !isTurnMarker) {\n return;\n }\n\n // Ensure directory exists\n if (!fsSync.existsSync(storageDir)) {\n fsSync.mkdirSync(storageDir, { recursive: true });\n }\n\n const historyPath = this.getHistoryPath(storageDir);\n let events: StreamPayload[] = [];\n\n try {\n if (fsSync.existsSync(historyPath)) {\n const data = fsSync.readFileSync(historyPath, 'utf-8');\n events = JSON.parse(data);\n }\n } catch {\n events = [];\n }\n\n events.push(update);\n fsSync.writeFileSync(historyPath, JSON.stringify(events));\n }\n\n async start(\n streamId: string,\n config: StreamConfig,\n cwd: string,\n backendState: unknown | null,\n emit: EmitFn,\n storageDir: string\n ): Promise<Stream> {\n // Extract existing ACP session ID from backend state if resuming\n const existingState = backendState as AcpBackendState | null;\n const existingAcpSessionId = existingState?.acpSessionId ?? null;\n\n // Kill existing instance if any\n if (this.instances.has(streamId)) {\n await this.stop(streamId);\n }\n\n const args = config.args ?? [];\n const env = config.env ?? {};\n\n debug('acp',` Spawning: ${config.command} ${args.join(' ')} in ${cwd}`);\n\n const proc = spawn({\n command: config.command,\n args,\n cwd,\n env,\n });\n\n // Log stderr for debugging startup issues\n proc.stderr.on('data', (data: Buffer) => {\n debug('acp', `[stderr] ${data.toString().trim()}`);\n });\n\n // Wrap emit to store events\n const storingEmit: EmitFn = (update) => {\n this.storeEvent(storageDir, update);\n emit(update);\n };\n\n const instance: AcpInstance = {\n process: proc,\n connection: null!,\n acpSessionId: '',\n pendingPermission: null,\n permissionCallbacks: new Map(),\n emit: storingEmit,\n terminals: new Map(),\n storageDir,\n };\n\n // Handle unexpected exit\n proc.wait().then(({ exitCode }) => {\n if (this.instances.has(streamId)) {\n emit({ amuxEvent: 'error', message: `Agent process exited with code ${exitCode}` });\n this.instances.delete(streamId);\n }\n });\n\n try {\n const input = Writable.toWeb(proc.stdin) as WritableStream<Uint8Array>;\n const output = Readable.toWeb(proc.stdout) as ReadableStream<Uint8Array>;\n const stream = ndJsonStream(input, output);\n\n const client = this.createClient(streamId, instance);\n instance.connection = new ClientSideConnection(() => client, stream);\n\n const initResult = await withTimeout(\n instance.connection.initialize({\n protocolVersion: 1,\n clientCapabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: true,\n },\n }),\n INIT_TIMEOUT_MS,\n 'Agent initialization'\n );\n\n debug('acp',` Initialized agent: ${initResult.agentInfo?.name} v${initResult.agentInfo?.version}`);\n\n const canResume = initResult.agentCapabilities?.sessionCapabilities?.resume !== undefined;\n let acpSessionId: string;\n let sessionResult: any;\n\n if (existingAcpSessionId && canResume) {\n let resumeSucceeded = false;\n try {\n debug('acp',` Resuming ACP session ${existingAcpSessionId}...`);\n sessionResult = await withTimeout(\n (instance.connection as any).unstable_resumeSession({\n sessionId: existingAcpSessionId,\n cwd,\n mcpServers: [],\n }),\n INIT_TIMEOUT_MS,\n 'Session resume'\n );\n\n await new Promise(resolve => setTimeout(resolve, 100));\n acpSessionId = existingAcpSessionId;\n debug('acp',` ACP session resumed successfully`);\n resumeSucceeded = true;\n } catch (resumeErr) {\n debug('acp', ` Resume failed, creating new session: ${resumeErr}`);\n }\n\n if (!resumeSucceeded) {\n sessionResult = await withTimeout(\n instance.connection.newSession({ cwd, mcpServers: [] }),\n INIT_TIMEOUT_MS,\n 'New session creation'\n );\n acpSessionId = sessionResult.sessionId;\n debug('acp',` New ACP session created: ${acpSessionId}`);\n }\n } else {\n debug('acp',` Creating new ACP session in ${cwd}...`);\n sessionResult = await withTimeout(\n instance.connection.newSession({ cwd, mcpServers: [] }),\n INIT_TIMEOUT_MS,\n 'New session creation'\n );\n acpSessionId = sessionResult.sessionId!;\n debug('acp',` ACP session created: ${acpSessionId}`);\n }\n\n instance.acpSessionId = acpSessionId!;\n this.instances.set(streamId, instance);\n\n const models = sessionResult?.models?.availableModels;\n const modes = sessionResult?.modes?.availableModes;\n\n if (sessionResult?.modes) {\n emit({\n sessionUpdate: 'current_mode_update',\n currentModeId: sessionResult.modes.currentModeId,\n });\n }\n\n return {\n acpSessionId: acpSessionId!,\n models,\n modes,\n };\n } catch (err) {\n console.error(`[acp] Error starting agent for stream ${streamId}:`, err);\n await proc.kill();\n throw err;\n }\n }\n\n private createClient(_streamId: string, instance: AcpInstance): Client {\n return {\n async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {\n const requestId = randomUUID();\n\n const permission = {\n requestId,\n toolCallId: params.toolCall.toolCallId,\n title: params.toolCall.title ?? 'Permission Required',\n options: params.options.map(o => ({\n optionId: o.optionId,\n name: o.name,\n kind: o.kind,\n })),\n };\n instance.pendingPermission = permission;\n instance.emit({ amuxEvent: 'permission_request', permission });\n\n return new Promise((resolve, reject) => {\n instance.permissionCallbacks.set(requestId, {\n resolve: (optionId) => {\n instance.pendingPermission = null;\n instance.emit({ amuxEvent: 'permission_cleared' });\n resolve({ outcome: { outcome: 'selected', optionId } });\n },\n reject,\n });\n });\n },\n\n async sessionUpdate(params: SessionNotification): Promise<void> {\n debug('acp', ` sessionUpdate received: ${JSON.stringify(params)}`);\n // Normalize the update before emitting (ACP normalization layer)\n const normalized = normalizeSessionUpdate(params.update) as StreamPayload;\n instance.emit(normalized);\n },\n\n async readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse> {\n const content = await fs.readFile(params.path, 'utf-8');\n return { content };\n },\n\n async writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse> {\n await fs.writeFile(params.path, params.content);\n return {};\n },\n\n async createTerminal(params: CreateTerminalRequest): Promise<CreateTerminalResponse> {\n debug('acp', ` createTerminal request: ${JSON.stringify(params)}`);\n const terminalId = randomUUID();\n const outputByteLimit = params.outputByteLimit ?? 1024 * 1024; // Default 1MB\n\n const termProc = nodeSpawn(params.command, params.args ?? [], {\n cwd: params.cwd ?? undefined,\n env: params.env\n ? { ...process.env, ...Object.fromEntries(params.env.map(e => [e.name, e.value])) }\n : process.env,\n shell: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n\n const terminal: Terminal = {\n process: termProc,\n output: '',\n exitCode: null,\n signal: null,\n truncated: false,\n outputByteLimit,\n };\n\n const appendOutput = (data: Buffer) => {\n terminal.output += data.toString();\n // Truncate from beginning if over limit\n if (terminal.output.length > terminal.outputByteLimit) {\n terminal.output = terminal.output.slice(-terminal.outputByteLimit);\n terminal.truncated = true;\n }\n };\n\n termProc.stdout?.on('data', appendOutput);\n termProc.stderr?.on('data', appendOutput);\n\n termProc.on('exit', (code, signal) => {\n debug('acp',` Terminal ${terminalId} exited with code ${code}, signal ${signal}`);\n terminal.exitCode = code ?? null;\n terminal.signal = signal ?? null;\n });\n\n termProc.on('error', (err) => {\n console.error(`[acp] Terminal ${terminalId} error:`, err.message);\n terminal.output += `\\nError: ${err.message}`;\n terminal.exitCode = -1;\n });\n\n instance.terminals.set(terminalId, terminal);\n debug('acp',` Created terminal ${terminalId} for command: ${params.command}`);\n\n return { terminalId };\n },\n\n async terminalOutput(params: TerminalOutputRequest): Promise<TerminalOutputResponse> {\n debug('acp',` terminalOutput request for terminal ${params.terminalId}`);\n const terminal = instance.terminals.get(params.terminalId);\n if (!terminal) {\n throw new Error(`Terminal ${params.terminalId} not found`);\n }\n return {\n output: terminal.output,\n truncated: terminal.truncated,\n exitStatus: terminal.exitCode !== null || terminal.signal !== null ? {\n exitCode: terminal.exitCode,\n signal: terminal.signal,\n } : undefined,\n };\n },\n\n async waitForTerminalExit(params: WaitForTerminalExitRequest): Promise<WaitForTerminalExitResponse> {\n debug('acp',` waitForTerminalExit request for terminal ${params.terminalId}`);\n const terminal = instance.terminals.get(params.terminalId);\n if (!terminal) {\n throw new Error(`Terminal ${params.terminalId} not found`);\n }\n\n // If already exited, return immediately\n if (terminal.exitCode !== null || terminal.signal !== null) {\n return {\n exitCode: terminal.exitCode,\n signal: terminal.signal,\n };\n }\n\n // Wait for exit\n return new Promise((resolve) => {\n terminal.process.on('exit', (code, signal) => {\n resolve({\n exitCode: code ?? null,\n signal: signal ?? null,\n });\n });\n });\n },\n\n // Note: killTerminalCommand not in SDK Client interface yet, but we implement handlers\n // for completeness when the SDK adds support\n async killTerminal(params: KillTerminalCommandRequest): Promise<KillTerminalCommandResponse> {\n debug('acp',` killTerminal request for terminal ${params.terminalId}`);\n const terminal = instance.terminals.get(params.terminalId);\n if (!terminal) {\n throw new Error(`Terminal ${params.terminalId} not found`);\n }\n terminal.process.kill('SIGTERM');\n return {};\n },\n\n async releaseTerminal(params: ReleaseTerminalRequest): Promise<ReleaseTerminalResponse> {\n debug('acp',` releaseTerminal request for terminal ${params.terminalId}`);\n const terminal = instance.terminals.get(params.terminalId);\n if (terminal) {\n if (terminal.exitCode === null) {\n terminal.process.kill('SIGKILL');\n }\n instance.terminals.delete(params.terminalId);\n }\n return {};\n },\n };\n }\n\n async input(streamId: string, raw: string, cwd: string, _emit: EmitFn): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) throw new Error(`No ACP instance for stream ${streamId}`);\n\n // Store user message for replay\n this.storeEvent(instance.storageDir, {\n sessionUpdate: 'user_message_chunk',\n content: { type: 'text', text: raw },\n } as StreamPayload);\n\n // Parse @mentions into ContentBlocks\n const content = parseMessageToContentBlocks(raw, cwd);\n\n debug('acp',` Sending input to session ${instance.acpSessionId} with ${content.length} content block(s)...`);\n const result = await instance.connection.prompt({\n sessionId: instance.acpSessionId,\n prompt: content,\n });\n debug('acp',` Input complete, stopReason: ${result.stopReason}`);\n }\n\n async stop(streamId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) return;\n\n // Reject pending permission callbacks\n for (const [, callback] of instance.permissionCallbacks) {\n callback.reject(new Error('Agent stopped'));\n }\n instance.permissionCallbacks.clear();\n instance.pendingPermission = null;\n\n // Remove from instances first to prevent exit handler re-entry\n this.instances.delete(streamId);\n\n // Kill the process tree\n await instance.process.kill();\n }\n\n async stopAll(): Promise<void> {\n const streamIds = [...this.instances.keys()];\n await Promise.all(streamIds.map((id) => this.stop(id)));\n }\n\n isRunning(streamId: string): boolean {\n return this.instances.has(streamId);\n }\n\n /** Get backend state for persistence (ACP session ID for resumption) */\n getState(streamId: string): AcpBackendState | undefined {\n const instance = this.instances.get(streamId);\n if (!instance) return undefined;\n return { acpSessionId: instance.acpSessionId };\n }\n\n /** Get stored events for replay on reconnect */\n getReplayData(streamId: string): StreamPayload[] | undefined {\n const instance = this.instances.get(streamId);\n if (!instance) return undefined;\n\n const historyPath = this.getHistoryPath(instance.storageDir);\n try {\n if (fsSync.existsSync(historyPath)) {\n const data = fsSync.readFileSync(historyPath, 'utf-8');\n return JSON.parse(data);\n }\n } catch {\n // Return empty if file is corrupted\n }\n return [];\n }\n\n /** Clear stored events for a stream */\n clearHistory(streamId: string): void {\n const instance = this.instances.get(streamId);\n if (!instance) return;\n\n const historyPath = this.getHistoryPath(instance.storageDir);\n try {\n if (fsSync.existsSync(historyPath)) {\n fsSync.unlinkSync(historyPath);\n }\n } catch {\n // Ignore errors\n }\n }\n\n respondToPermission(streamId: string, requestId: string, optionId: string): void {\n const instance = this.instances.get(streamId);\n const callback = instance?.permissionCallbacks.get(requestId);\n if (!callback) {\n throw new Error(`No pending permission request ${requestId}`);\n }\n callback.resolve(optionId);\n instance!.permissionCallbacks.delete(requestId);\n }\n\n getPendingPermission(streamId: string): PendingPermission | null {\n const instance = this.instances.get(streamId);\n return instance?.pendingPermission ?? null;\n }\n\n async cancel(streamId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) return;\n\n await instance.connection.cancel({ sessionId: instance.acpSessionId });\n }\n\n async setMode(streamId: string, modeId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) throw new Error(`No ACP instance for stream ${streamId}`);\n\n await instance.connection.setSessionMode({\n sessionId: instance.acpSessionId,\n modeId,\n });\n }\n\n async setModel(streamId: string, modelId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) throw new Error(`No ACP instance for stream ${streamId}`);\n\n await (instance.connection as any).unstable_setSessionModel({\n sessionId: instance.acpSessionId,\n modelId,\n });\n }\n}\n","/**\n * Managed process wrapper with automatic tree cleanup.\n * Uses execa for process spawning and tree-kill for cleanup.\n */\n\nimport { execa } from 'execa';\nimport treeKill from 'tree-kill';\nimport type { Writable, Readable } from 'stream';\n\nexport interface ManagedProcess {\n pid: number;\n stdin: Writable;\n stdout: Readable;\n stderr: Readable;\n kill: (signal?: string) => Promise<void>;\n wait: () => Promise<{ exitCode: number | null }>;\n}\n\nexport interface SpawnOptions {\n command: string;\n args?: string[];\n cwd?: string;\n env?: Record<string, string>;\n timeoutMs?: number;\n}\n\n/**\n * Spawns a managed process with automatic tree cleanup.\n *\n * - Cross-platform process tree killing via tree-kill\n * - Automatic cleanup on parent exit\n * - Promise-based wait/kill\n */\nexport function spawn(options: SpawnOptions): ManagedProcess {\n const { command, args = [], cwd, env, timeoutMs } = options;\n\n const subprocess = execa(command, args, {\n cwd,\n env: { ...process.env, ...env },\n stdin: 'pipe',\n stdout: 'pipe',\n stderr: 'pipe',\n timeout: timeoutMs,\n cleanup: true, // Kill on parent exit\n windowsHide: true, // Hide console window on Windows\n });\n\n const pid = subprocess.pid;\n if (!pid) {\n throw new Error(`Failed to spawn process: ${command}`);\n }\n\n return {\n pid,\n stdin: subprocess.stdin!,\n stdout: subprocess.stdout!,\n stderr: subprocess.stderr!,\n\n kill: (signal = 'SIGTERM') => killTree(pid, signal),\n\n wait: async () => {\n try {\n const result = await subprocess;\n return { exitCode: result.exitCode };\n } catch (err: any) {\n return { exitCode: err.exitCode ?? 1 };\n }\n },\n };\n}\n\nfunction killTree(pid: number, signal: string): Promise<void> {\n return new Promise((resolve) => {\n treeKill(pid, signal, (err?: Error) => {\n if (err && !err.message.includes('No such process')) {\n console.error(`[process] kill error pid=${pid}:`, err.message);\n }\n resolve();\n });\n });\n}\n","import { randomUUID } from 'crypto';\nimport type { StreamDriver, StreamConfig, Stream, EmitFn } from './types.js';\nimport { debug } from '../../lib/logger.js';\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nexport class MockDriver implements StreamDriver {\n readonly type = 'mock';\n readonly streamType = 'acp' as const;\n private running = new Set<string>();\n\n async start(\n streamId: string,\n _config: StreamConfig,\n _cwd: string,\n _backendState: unknown | null,\n emit: EmitFn,\n _storageDir: string\n ): Promise<Stream> {\n debug('mock',` Starting mock agent for stream ${streamId}`);\n\n emit({\n sessionUpdate: 'current_mode_update',\n currentModeId: 'mock',\n });\n\n debug('mock',` Mock agent ready for stream ${streamId}`);\n this.running.add(streamId);\n\n return {\n acpSessionId: randomUUID(),\n models: [{ modelId: 'mock-model', name: 'Mock Model' }],\n modes: [{ id: 'mock', name: 'Mock Mode' }],\n };\n }\n\n async input(streamId: string, raw: string, _cwd: string, emit: EmitFn): Promise<void> {\n debug('mock',` Mock input for stream ${streamId}: \"${raw.slice(0, 50)}...\"`);\n\n const words = [\n 'This', 'is', 'a', 'mock', 'response', 'from', 'the', 'mock', 'agent.',\n 'It', 'simulates', 'streaming', 'text', 'chunks', 'for', 'performance', 'testing.',\n 'The', 'response', 'arrives', 'in', 'small', 'pieces', 'to', 'test', 'UI', 'rendering.',\n ];\n\n for (let i = 0; i < words.length; i += 3) {\n const chunk = words.slice(i, i + 3).join(' ') + ' ';\n emit({\n sessionUpdate: 'agent_message_chunk',\n content: { type: 'text', text: chunk },\n });\n await delay(50);\n }\n }\n\n async stop(streamId: string): Promise<void> {\n debug('mock',` Stopping mock agent for stream ${streamId}`);\n this.running.delete(streamId);\n }\n\n async stopAll(): Promise<void> {\n const streamIds = [...this.running];\n await Promise.all(streamIds.map((id) => this.stop(id)));\n }\n\n isRunning(streamId: string): boolean {\n return this.running.has(streamId);\n }\n}\n","import type { IPty } from 'node-pty';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { StreamDriver, StreamConfig, Stream, EmitFn } from './types.js';\nimport { debug, warn } from '../../lib/logger.js';\n\n// Dynamic import for node-pty (native module)\nlet pty: typeof import('node-pty') | null = null;\n\nasync function getPty() {\n if (!pty) {\n pty = await import('node-pty');\n }\n return pty;\n}\n\nconst MAX_SCROLLBACK = 100_000; // ~100KB of terminal output\nconst SAVE_DEBOUNCE_MS = 1000; // Debounce scrollback saves\n\ninterface ShellInstance {\n pty: IPty;\n scrollback: string;\n cwd: string;\n emit: EmitFn;\n storageDir: string;\n saveTimeout: ReturnType<typeof setTimeout> | null;\n}\n\n/**\n * PtyDriver - Interactive terminal driver using node-pty.\n *\n * Unlike conversational agents (AcpDriver), this provides a raw PTY\n * for interactive shell access. Key differences:\n * - input() writes text to terminal (also exposes terminalWrite() for raw access)\n * - Emits terminal_output events instead of message chunks\n * - Maintains scrollback buffer for reconnection replay\n */\nexport class PtyDriver implements StreamDriver {\n readonly type = 'pty';\n readonly streamType = 'pty' as const;\n readonly isInteractive = true;\n\n private instances = new Map<string, ShellInstance>();\n\n private getScrollbackPath(storageDir: string): string {\n return path.join(storageDir, 'scrollback.txt');\n }\n\n private saveScrollback(instance: ShellInstance): void {\n // Ensure directory exists\n if (!fs.existsSync(instance.storageDir)) {\n fs.mkdirSync(instance.storageDir, { recursive: true });\n }\n fs.writeFileSync(this.getScrollbackPath(instance.storageDir), instance.scrollback);\n }\n\n private loadScrollback(storageDir: string): string {\n const scrollbackPath = this.getScrollbackPath(storageDir);\n try {\n if (fs.existsSync(scrollbackPath)) {\n return fs.readFileSync(scrollbackPath, 'utf-8');\n }\n } catch {\n // Ignore read errors\n }\n return '';\n }\n\n private debouncedSave(instance: ShellInstance): void {\n if (instance.saveTimeout) {\n clearTimeout(instance.saveTimeout);\n }\n instance.saveTimeout = setTimeout(() => {\n this.saveScrollback(instance);\n instance.saveTimeout = null;\n }, SAVE_DEBOUNCE_MS);\n }\n\n async start(\n streamId: string,\n _config: StreamConfig,\n cwd: string,\n _backendState: unknown | null,\n emit: EmitFn,\n storageDir: string\n ): Promise<Stream> {\n // Check if already running (reconnect case)\n const existing = this.instances.get(streamId);\n if (existing) {\n debug('shell', `Stream ${streamId} already running, reusing`);\n // Update emit function for new subscriber\n existing.emit = emit;\n return {};\n }\n\n // Load persisted scrollback (for after server restart)\n const restoredScrollback = this.loadScrollback(storageDir);\n if (restoredScrollback) {\n debug('shell', `Restored ${restoredScrollback.length} bytes of scrollback for ${streamId}`);\n }\n\n const nodePty = await getPty();\n const shell = process.env.SHELL || '/bin/bash';\n\n debug('shell', `Spawning ${shell} in ${cwd}`);\n\n let p: IPty;\n try {\n p = nodePty.spawn(shell, [], {\n name: 'xterm-256color',\n cols: 80,\n rows: 24,\n cwd,\n env: process.env as Record<string, string>,\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n\n if (message.includes('posix_spawnp failed') && process.platform === 'darwin') {\n throw new Error(\n `Failed to spawn shell on macOS: ${message}. ` +\n `This may be caused by macOS quarantine. Try: ` +\n `1) Install globally: npm install -g @bytespell/shella && shella, or ` +\n `2) Clear quarantine: xattr -dr com.apple.quarantine ~/.npm/_npx`\n );\n }\n throw error;\n }\n\n const instance: ShellInstance = {\n pty: p,\n scrollback: restoredScrollback,\n cwd,\n emit,\n storageDir,\n saveTimeout: null,\n };\n\n p.onData((data: string) => {\n // Accumulate scrollback\n instance.scrollback += data;\n if (instance.scrollback.length > MAX_SCROLLBACK) {\n instance.scrollback = instance.scrollback.slice(-MAX_SCROLLBACK);\n }\n\n // Persist scrollback (debounced)\n this.debouncedSave(instance);\n\n // Emit for live subscribers\n instance.emit({ amuxEvent: 'terminal_output', data });\n });\n\n p.onExit(({ exitCode, signal }) => {\n debug('shell', `PTY exited: code=${exitCode}, signal=${signal}`);\n // Save scrollback immediately on exit\n if (instance.saveTimeout) {\n clearTimeout(instance.saveTimeout);\n }\n this.saveScrollback(instance);\n instance.emit({\n amuxEvent: 'terminal_exit',\n exitCode: exitCode ?? null,\n signal: signal !== undefined ? String(signal) : null,\n });\n this.instances.delete(streamId);\n });\n\n this.instances.set(streamId, instance);\n debug('shell', `Stream ${streamId} started`);\n\n return {};\n }\n\n /**\n * Get accumulated scrollback for replay on reconnect.\n */\n getScrollback(streamId: string): string | undefined {\n return this.instances.get(streamId)?.scrollback;\n }\n\n /**\n * Get replay data (scrollback) for reconnecting clients.\n */\n getReplayData(streamId: string): string | undefined {\n return this.getScrollback(streamId);\n }\n\n /**\n * Write raw input to terminal (keystrokes from client).\n */\n terminalWrite(streamId: string, data: string): void {\n const instance = this.instances.get(streamId);\n if (!instance) {\n warn('shell', `terminalWrite: stream ${streamId} not found`);\n return;\n }\n instance.pty.write(data);\n }\n\n /**\n * Resize terminal dimensions.\n */\n terminalResize(streamId: string, cols: number, rows: number): void {\n const instance = this.instances.get(streamId);\n if (!instance) {\n warn('shell', `terminalResize: stream ${streamId} not found`);\n return;\n }\n instance.pty.resize(cols, rows);\n }\n\n /**\n * Handle user input - writes raw text to terminal.\n */\n async input(streamId: string, raw: string, _cwd: string, _emit: EmitFn): Promise<void> {\n if (raw) {\n this.terminalWrite(streamId, raw);\n }\n }\n\n async stop(streamId: string): Promise<void> {\n const instance = this.instances.get(streamId);\n if (!instance) return;\n\n debug('shell', `Stopping stream ${streamId}`);\n\n // Cancel pending save and save immediately\n if (instance.saveTimeout) {\n clearTimeout(instance.saveTimeout);\n }\n this.saveScrollback(instance);\n\n instance.pty.kill();\n this.instances.delete(streamId);\n }\n\n async stopAll(): Promise<void> {\n debug('shell', `Stopping all streams (${this.instances.size})`);\n for (const streamId of this.instances.keys()) {\n await this.stop(streamId);\n }\n }\n\n isRunning(streamId: string): boolean {\n return this.instances.has(streamId);\n }\n}\n"],"mappings":"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","names":["path","randomUUID","randomUUID","fs","path"]}