@bytespell/amux 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/CLAUDE.md +104 -0
  3. package/LICENSE +21 -0
  4. package/README.md +204 -0
  5. package/dist/cli.d.ts +14 -0
  6. package/dist/cli.d.ts.map +1 -0
  7. package/dist/cli.js +118 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/client.d.ts +68 -0
  10. package/dist/client.d.ts.map +1 -0
  11. package/dist/client.js +135 -0
  12. package/dist/client.js.map +1 -0
  13. package/dist/index.d.ts +41 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +44 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/{lib/mentions.d.ts → message-parser.d.ts} +3 -5
  18. package/dist/message-parser.d.ts.map +1 -0
  19. package/dist/message-parser.js +45 -0
  20. package/dist/message-parser.js.map +1 -0
  21. package/dist/server.d.ts +24 -0
  22. package/dist/server.d.ts.map +1 -0
  23. package/dist/server.js +356 -0
  24. package/dist/server.js.map +1 -0
  25. package/dist/session-updates.d.ts +26 -0
  26. package/dist/session-updates.d.ts.map +1 -0
  27. package/dist/session-updates.js +68 -0
  28. package/dist/session-updates.js.map +1 -0
  29. package/dist/session.d.ts +207 -0
  30. package/dist/session.d.ts.map +1 -0
  31. package/dist/session.js +563 -0
  32. package/dist/session.js.map +1 -0
  33. package/dist/state.d.ts +74 -0
  34. package/dist/state.d.ts.map +1 -0
  35. package/dist/state.js +250 -0
  36. package/dist/state.js.map +1 -0
  37. package/dist/terminal.d.ts +47 -0
  38. package/dist/terminal.d.ts.map +1 -0
  39. package/dist/terminal.js +137 -0
  40. package/dist/terminal.js.map +1 -0
  41. package/dist/types.d.ts +64 -2
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +16 -31
  44. package/dist/types.js.map +1 -1
  45. package/dist/ws-adapter.d.ts +39 -0
  46. package/dist/ws-adapter.d.ts.map +1 -0
  47. package/dist/ws-adapter.js +198 -0
  48. package/dist/ws-adapter.js.map +1 -0
  49. package/package.json +47 -24
  50. package/src/client.ts +162 -0
  51. package/src/index.ts +66 -0
  52. package/src/message-parser.ts +54 -0
  53. package/src/session-updates.ts +87 -0
  54. package/src/session.ts +719 -0
  55. package/src/state.ts +287 -0
  56. package/src/terminal.ts +164 -0
  57. package/src/types.ts +88 -0
  58. package/src/ws-adapter.ts +245 -0
  59. package/tsconfig.json +22 -0
  60. package/dist/chunk-5IPYOXBE.js +0 -32
  61. package/dist/chunk-5IPYOXBE.js.map +0 -1
  62. package/dist/chunk-C73RKCTS.js +0 -36
  63. package/dist/chunk-C73RKCTS.js.map +0 -1
  64. package/dist/chunk-VVXT4HQM.js +0 -779
  65. package/dist/chunk-VVXT4HQM.js.map +0 -1
  66. package/dist/lib/logger.d.ts +0 -24
  67. package/dist/lib/logger.js +0 -17
  68. package/dist/lib/logger.js.map +0 -1
  69. package/dist/lib/mentions.js +0 -7
  70. package/dist/lib/mentions.js.map +0 -1
  71. package/dist/streams/backends/index.d.ts +0 -88
  72. package/dist/streams/backends/index.js +0 -13
  73. package/dist/streams/backends/index.js.map +0 -1
  74. package/dist/streams/manager.d.ts +0 -55
  75. package/dist/streams/manager.js +0 -248
  76. package/dist/streams/manager.js.map +0 -1
  77. package/dist/types-DV6-SxsB.d.ts +0 -192
  78. package/scripts/fix-pty.cjs +0 -21
@@ -0,0 +1,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(git config:*)",
5
+ "Bash(gh repo create:*)",
6
+ "Bash(gh org:*)",
7
+ "Bash(gh api:*)",
8
+ "Bash(npm publish:*)"
9
+ ]
10
+ }
11
+ }
package/CLAUDE.md ADDED
@@ -0,0 +1,104 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code when working with this repository.
4
+
5
+ ## What is amux?
6
+
7
+ **amux** (Agent Multiplexer) is a library for building agent-powered HTTP servers. Think of it like tmux, but for AI agents over HTTP. It provides:
8
+
9
+ 1. **Session management**: Spawn, manage, and multiplex multiple ACP agent sessions
10
+ 2. **WebSocket API**: Real-time streaming communication with agents
11
+ 3. **State persistence**: Session history, preferences, and replay
12
+ 4. **System context injection**: Customize agent behavior with injected knowledge
13
+
14
+ ## Project Structure
15
+
16
+ ```
17
+ amux/
18
+ ├── src/
19
+ │ ├── index.ts # Public API exports
20
+ │ ├── cli.ts # CLI entry point
21
+ │ ├── server.ts # Express + WebSocket server factory
22
+ │ ├── session.ts # AgentSession - core session management
23
+ │ ├── client.ts # AmuxClient - ACP client implementation
24
+ │ ├── terminal.ts # TerminalManager - PTY management
25
+ │ ├── state.ts # StateManager - persistence
26
+ │ ├── session-updates.ts # ACP update normalization
27
+ │ └── types.ts # TypeScript types
28
+ ├── package.json
29
+ └── tsconfig.json
30
+ ```
31
+
32
+ ## Key Concepts
33
+
34
+ ### AgentSession
35
+
36
+ The core class that manages an ACP agent lifecycle:
37
+ - Spawns agent processes (claude-code-acp, codex-acp, pi-acp)
38
+ - Handles ACP protocol communication
39
+ - Manages session state and history
40
+ - Supports system context injection
41
+
42
+ ### AmuxClient
43
+
44
+ Implements the ACP Client interface:
45
+ - Filesystem operations (read/write files)
46
+ - Terminal management (spawn commands, get output)
47
+ - Permission request handling
48
+ - Session update broadcasting
49
+
50
+ ### Server Factory
51
+
52
+ `createAmuxServer()` provides a batteries-included server:
53
+ - Express app with API routes
54
+ - WebSocket server on `/ws`
55
+ - Automatic agent spawning
56
+ - History replay on reconnect
57
+
58
+ ## Build Commands
59
+
60
+ ```bash
61
+ npm run build # Compile TypeScript
62
+ npm run dev # Watch mode
63
+ npm run typecheck # Type check only
64
+ npm run test # Run tests
65
+ ```
66
+
67
+ ## API Design
68
+
69
+ The public API has three levels:
70
+
71
+ 1. **High-level**: `createAmuxServer(config)` - Full server in one call
72
+ 2. **Mid-level**: `attachAmux(app, server, config)` - Attach to existing Express app
73
+ 3. **Low-level**: `AgentSession` class - Full control over session management
74
+
75
+ ## State Storage
76
+
77
+ By default, state is stored in `~/.local/state/amux/`:
78
+ - `instance-{id}.json` - Per-instance state (cwd, sessionId, agentType)
79
+ - `session-{id}-history.json` - Session event history for replay
80
+ - `sessions.json` - Session registry with metadata
81
+
82
+ ## WebSocket Protocol
83
+
84
+ The WebSocket API on `/ws` uses JSON messages. Key message types:
85
+
86
+ **Client → Server:**
87
+ - `prompt` - Send user message
88
+ - `cancel` - Cancel current operation
89
+ - `permission_response` - Respond to permission request
90
+ - `change_cwd`, `new_session`, `change_agent`, etc.
91
+
92
+ **Server → Client:**
93
+ - `ready` - Connection established
94
+ - `session_update` - Streaming updates from agent
95
+ - `permission_request` - Agent needs permission
96
+ - `history_replay` - Replay events on reconnect
97
+ - `error` - Error occurred
98
+
99
+ ## Testing
100
+
101
+ When adding features:
102
+ 1. Unit test individual components (StateManager, TerminalManager, etc.)
103
+ 2. Integration test the full server with a mock agent
104
+ 3. Test WebSocket message handling
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ashley Hauck
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # amux
2
+
3
+ **Agent Multiplexer** - EventEmitter-based agent session management.
4
+
5
+ Manage ACP agent sessions (Claude Code, Codex, Pi) with a simple event-driven interface. Transport-agnostic core with an optional WebSocket adapter.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @bytespell/amux
11
+ ```
12
+
13
+ You'll also need at least one ACP agent:
14
+
15
+ ```bash
16
+ npm install @zed-industries/claude-code-acp # Claude Code
17
+ npm install @zed-industries/codex-acp # Codex
18
+ npm install pi-acp # Pi
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### With WebSocket Adapter
24
+
25
+ ```typescript
26
+ import { AgentSession, createWsAdapter } from '@bytespell/amux';
27
+ import { WebSocketServer } from 'ws';
28
+ import { createServer } from 'http';
29
+
30
+ const server = createServer();
31
+ const wss = new WebSocketServer({ server, path: '/ws' });
32
+
33
+ const session = new AgentSession({
34
+ instanceId: process.env.INSTANCE_ID ?? 'default',
35
+ basePath: process.cwd(),
36
+ systemContext: 'You are a helpful assistant...',
37
+ });
38
+
39
+ // Wire up WebSocket
40
+ createWsAdapter(session, wss);
41
+
42
+ // Start the agent
43
+ await session.spawnAgent();
44
+
45
+ server.listen(3000);
46
+ ```
47
+
48
+ ### Events Only (Bring Your Own Transport)
49
+
50
+ ```typescript
51
+ import { AgentSession } from '@bytespell/amux';
52
+
53
+ const session = new AgentSession({
54
+ instanceId: 'my-instance',
55
+ basePath: process.cwd(),
56
+ });
57
+
58
+ // Subscribe to events
59
+ session.on('ready', (data) => {
60
+ console.log('Agent ready:', data.agent.name);
61
+ });
62
+
63
+ session.on('update', (update) => {
64
+ // Handle streaming updates
65
+ if (update.sessionUpdate === 'agent_message_chunk') {
66
+ process.stdout.write(update.content.text);
67
+ }
68
+ });
69
+
70
+ session.on('permission_request', (data) => {
71
+ // Auto-approve for demo
72
+ session.respondToPermission(data.requestId, data.options[0].optionId);
73
+ });
74
+
75
+ session.on('error', (data) => {
76
+ console.error('Error:', data.message);
77
+ });
78
+
79
+ // Start and prompt
80
+ await session.spawnAgent();
81
+ await session.prompt('Hello, what can you do?');
82
+ ```
83
+
84
+ ## API
85
+
86
+ ### `AgentSession`
87
+
88
+ Core class for managing an ACP agent session. Extends `EventEmitter`.
89
+
90
+ ```typescript
91
+ const session = new AgentSession({
92
+ instanceId: string; // Unique ID for state isolation
93
+ basePath: string; // Path to node_modules with agent binaries
94
+ systemContext?: string; // Injected context for the agent
95
+ fixedCwd?: string; // Lock working directory
96
+ agentType?: string; // 'claude-code' | 'codex' | 'pi'
97
+ stateDir?: string; // Custom state directory
98
+ });
99
+ ```
100
+
101
+ #### Methods
102
+
103
+ ```typescript
104
+ await session.spawnAgent() // Start the agent process
105
+ await session.prompt(message) // Send a prompt
106
+ await session.cancel() // Cancel current operation
107
+ session.respondToPermission(id, optId) // Respond to permission request
108
+ await session.newSession() // Create new session
109
+ await session.changeCwd(path) // Change working directory
110
+ await session.changeAgent(type) // Switch agent type
111
+ session.listSessions() // List available sessions
112
+ session.loadHistory() // Get current session history
113
+ session.shutdown() // Cleanup and stop
114
+ ```
115
+
116
+ #### Events
117
+
118
+ ```typescript
119
+ session.on('connecting', () => {})
120
+ session.on('ready', (data) => {}) // Agent initialized
121
+ session.on('update', (update) => {}) // Session update (streaming)
122
+ session.on('turn_start', () => {}) // Prompt started
123
+ session.on('turn_end', () => {}) // Prompt completed
124
+ session.on('permission_request', (data) => {})
125
+ session.on('prompt_complete', (data) => {})
126
+ session.on('session_created', (data) => {})
127
+ session.on('session_switched', (data) => {})
128
+ session.on('history_replay', (data) => {})
129
+ session.on('error', (data) => {})
130
+ session.on('agent_exit', (data) => {})
131
+ ```
132
+
133
+ ### `createWsAdapter(session, wss, options?)`
134
+
135
+ Wire up a WebSocket server to an AgentSession.
136
+
137
+ ```typescript
138
+ import { createWsAdapter } from '@bytespell/amux';
139
+
140
+ const adapter = createWsAdapter(session, wss, {
141
+ sendHistoryOnConnect: true, // Default: true
142
+ });
143
+
144
+ adapter.clientCount() // Get connected clients
145
+ adapter.broadcast(msg) // Send custom message to all
146
+ adapter.close() // Close all connections
147
+ ```
148
+
149
+ #### WebSocket Protocol
150
+
151
+ **Client → Server:**
152
+ ```typescript
153
+ { type: 'prompt', message: 'Hello' }
154
+ { type: 'cancel' }
155
+ { type: 'permission_response', requestId: '...', optionId: '...' }
156
+ { type: 'change_cwd', path: '/new/path' }
157
+ { type: 'new_session' }
158
+ { type: 'change_agent', agentType: 'codex' }
159
+ { type: 'list_sessions' }
160
+ { type: 'switch_session', sessionId: '...' }
161
+ ```
162
+
163
+ **Server → Client:**
164
+ ```typescript
165
+ { type: 'ready', cwd: '...', sessionId: '...', ... }
166
+ { type: 'session_update', update: { sessionUpdate: '...', ... } }
167
+ { type: 'permission_request', requestId: '...', toolCall: {...}, options: [...] }
168
+ { type: 'history_replay', events: [...], eventCount: 42 }
169
+ { type: 'error', message: '...' }
170
+ ```
171
+
172
+ ## System Context
173
+
174
+ Inject context to customize agent behavior:
175
+
176
+ ```typescript
177
+ const session = new AgentSession({
178
+ instanceId: 'docs-helper',
179
+ basePath: __dirname,
180
+ systemContext: `
181
+ # Documentation Assistant
182
+
183
+ You help users write documentation for this project.
184
+
185
+ Key conventions:
186
+ - Use markdown format
187
+ - Include code examples
188
+ - Keep explanations concise
189
+ `,
190
+ });
191
+ ```
192
+
193
+ ## State Persistence
194
+
195
+ Sessions are persisted to `~/.local/state/amux/` by default:
196
+ - Instance state (cwd, sessionId, agentType)
197
+ - Session history (for replay on reconnect)
198
+ - Session registry (list all sessions)
199
+
200
+ Override with `stateDir` option.
201
+
202
+ ## License
203
+
204
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * amux CLI
4
+ *
5
+ * Quick way to start an amux server from the command line.
6
+ *
7
+ * Usage:
8
+ * amux # Start with defaults
9
+ * amux --context ./CONTEXT.md # Start with system context from file
10
+ * amux --cwd /path/to/project # Start with fixed working directory
11
+ * amux --port 3000 # Start on specific port
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;GAUG"}
package/dist/cli.js ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * amux CLI
4
+ *
5
+ * Quick way to start an amux server from the command line.
6
+ *
7
+ * Usage:
8
+ * amux # Start with defaults
9
+ * amux --context ./CONTEXT.md # Start with system context from file
10
+ * amux --cwd /path/to/project # Start with fixed working directory
11
+ * amux --port 3000 # Start on specific port
12
+ */
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import { createAmuxServer } from './server.js';
16
+ function parseArgs() {
17
+ const args = {};
18
+ const argv = process.argv.slice(2);
19
+ for (let i = 0; i < argv.length; i++) {
20
+ const arg = argv[i];
21
+ if (arg === '--help' || arg === '-h') {
22
+ args.help = true;
23
+ }
24
+ else if (arg === '--port' || arg === '-p') {
25
+ args.port = parseInt(argv[++i] ?? '', 10);
26
+ }
27
+ else if (arg === '--context' || arg === '-c') {
28
+ args.context = argv[++i];
29
+ }
30
+ else if (arg === '--cwd' || arg === '-d') {
31
+ args.cwd = argv[++i];
32
+ }
33
+ else if (arg === '--agent' || arg === '-a') {
34
+ args.agent = argv[++i];
35
+ }
36
+ }
37
+ return args;
38
+ }
39
+ function printHelp() {
40
+ console.log(`
41
+ amux - ACP Multiplexer
42
+
43
+ Start an agent-powered HTTP server with multiplexed sessions.
44
+
45
+ USAGE:
46
+ amux [OPTIONS]
47
+
48
+ OPTIONS:
49
+ -p, --port <port> Port to listen on (default: 3000 or PORT env)
50
+ -c, --context <file> Load system context from a markdown file
51
+ -d, --cwd <path> Fixed working directory for all sessions
52
+ -a, --agent <type> Agent type: claude-code, codex, pi (default: claude-code)
53
+ -h, --help Show this help message
54
+
55
+ EXAMPLES:
56
+ # Start with defaults
57
+ amux
58
+
59
+ # Start with system context
60
+ amux --context ./PLUGIN_GUIDE.md
61
+
62
+ # Start for a specific project
63
+ amux --cwd /path/to/project --context ./CONTEXT.md
64
+
65
+ # Use a different agent
66
+ amux --agent codex
67
+
68
+ ENVIRONMENT:
69
+ PORT Server port (overridden by --port)
70
+ INSTANCE_ID Unique instance identifier for state isolation
71
+ `);
72
+ }
73
+ async function main() {
74
+ const args = parseArgs();
75
+ if (args.help) {
76
+ printHelp();
77
+ process.exit(0);
78
+ }
79
+ // Load system context if specified
80
+ let systemContext;
81
+ if (args.context) {
82
+ const contextPath = path.resolve(args.context);
83
+ if (!fs.existsSync(contextPath)) {
84
+ console.error(`Error: Context file not found: ${contextPath}`);
85
+ process.exit(1);
86
+ }
87
+ systemContext = fs.readFileSync(contextPath, 'utf-8');
88
+ console.log(`[amux] Loaded system context from ${contextPath} (${systemContext.length} bytes)`);
89
+ }
90
+ // Resolve fixed cwd if specified
91
+ let fixedCwd;
92
+ if (args.cwd) {
93
+ fixedCwd = path.resolve(args.cwd);
94
+ if (!fs.existsSync(fixedCwd)) {
95
+ console.error(`Error: Working directory not found: ${fixedCwd}`);
96
+ process.exit(1);
97
+ }
98
+ console.log(`[amux] Using fixed working directory: ${fixedCwd}`);
99
+ }
100
+ const { start, shutdown } = createAmuxServer({
101
+ port: args.port,
102
+ systemContext,
103
+ fixedCwd,
104
+ agentType: args.agent,
105
+ });
106
+ // Handle shutdown gracefully
107
+ process.on('SIGINT', () => {
108
+ console.log('\n[amux] Received SIGINT, shutting down...');
109
+ shutdown();
110
+ process.exit(0);
111
+ });
112
+ await start();
113
+ }
114
+ main().catch((err) => {
115
+ console.error('[amux] Fatal error:', err);
116
+ process.exit(1);
117
+ });
118
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type * as acp from '@agentclientprotocol/sdk';
2
+ import type { TerminalManager } from './terminal.js';
3
+ import type { StoredEvent } from './types.js';
4
+ /**
5
+ * Callback for routing events
6
+ */
7
+ type EventCallback = (event: StoredEvent) => void;
8
+ /**
9
+ * Pending permission request tracking
10
+ */
11
+ interface PendingPermission {
12
+ resolve: (value: acp.RequestPermissionResponse) => void;
13
+ reject: (reason: Error) => void;
14
+ timeout: ReturnType<typeof setTimeout>;
15
+ }
16
+ /**
17
+ * ACP Client implementation.
18
+ * Bridges agent notifications to WebSocket clients and handles
19
+ * file operations and terminal management.
20
+ */
21
+ export declare class AmuxClient implements acp.Client {
22
+ private terminalManager;
23
+ private onEvent;
24
+ pendingPermissions: Map<string, PendingPermission>;
25
+ constructor(terminalManager: TerminalManager, onEvent: EventCallback);
26
+ /**
27
+ * Handle permission request from agent
28
+ */
29
+ requestPermission(params: acp.RequestPermissionRequest): Promise<acp.RequestPermissionResponse>;
30
+ /**
31
+ * Handle permission response from UI
32
+ */
33
+ handlePermissionResponse(requestId: string, optionId: string): void;
34
+ /**
35
+ * Handle session update from agent
36
+ */
37
+ sessionUpdate(params: acp.SessionNotification): Promise<void>;
38
+ /**
39
+ * Write text file (ACP fs capability)
40
+ */
41
+ writeTextFile(params: acp.WriteTextFileRequest): Promise<acp.WriteTextFileResponse>;
42
+ /**
43
+ * Read text file (ACP fs capability)
44
+ */
45
+ readTextFile(params: acp.ReadTextFileRequest): Promise<acp.ReadTextFileResponse>;
46
+ /**
47
+ * Create terminal (ACP terminal capability)
48
+ */
49
+ createTerminal(params: acp.CreateTerminalRequest): Promise<acp.CreateTerminalResponse>;
50
+ /**
51
+ * Get terminal output (ACP terminal capability)
52
+ */
53
+ terminalOutput(params: acp.TerminalOutputRequest): Promise<acp.TerminalOutputResponse>;
54
+ /**
55
+ * Wait for terminal exit (ACP terminal capability)
56
+ */
57
+ waitForTerminalExit(params: acp.WaitForTerminalExitRequest): Promise<acp.WaitForTerminalExitResponse>;
58
+ /**
59
+ * Kill terminal (ACP terminal capability)
60
+ */
61
+ killTerminal(params: acp.KillTerminalCommandRequest): Promise<acp.KillTerminalCommandResponse>;
62
+ /**
63
+ * Release terminal (ACP terminal capability)
64
+ */
65
+ releaseTerminal(params: acp.ReleaseTerminalRequest): Promise<acp.ReleaseTerminalResponse>;
66
+ }
67
+ export {};
68
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,GAAG,MAAM,0BAA0B,CAAC;AAGrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;GAEG;AACH,KAAK,aAAa,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;AAElD;;GAEG;AACH,UAAU,iBAAiB;IACzB,OAAO,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,yBAAyB,KAAK,IAAI,CAAC;IACxD,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;IAChC,OAAO,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;CACxC;AAED;;;;GAIG;AACH,qBAAa,UAAW,YAAW,GAAG,CAAC,MAAM;IAIzC,OAAO,CAAC,eAAe;IACvB,OAAO,CAAC,OAAO;IAJjB,kBAAkB,iCAAwC;gBAGhD,eAAe,EAAE,eAAe,EAChC,OAAO,EAAE,aAAa;IAGhC;;OAEG;IACG,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,wBAAwB,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IAuBrG;;OAEG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWnE;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBnE;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,oBAAoB,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAUzF;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,mBAAmB,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAUtF;;OAEG;IACG,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IAI5F;;OAEG;IACG,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IAI5F;;OAEG;IACG,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,0BAA0B,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC;IAI3G;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,0BAA0B,GAAG,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC;IAIpG;;OAEG;IACG,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,sBAAsB,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;CAGhG"}
package/dist/client.js ADDED
@@ -0,0 +1,135 @@
1
+ import fs from 'fs';
2
+ import crypto from 'crypto';
3
+ import { isToolCallUpdate, normalizeSessionUpdate } from './session-updates.js';
4
+ /**
5
+ * ACP Client implementation.
6
+ * Bridges agent notifications to WebSocket clients and handles
7
+ * file operations and terminal management.
8
+ */
9
+ export class AmuxClient {
10
+ terminalManager;
11
+ onEvent;
12
+ pendingPermissions = new Map();
13
+ constructor(terminalManager, onEvent) {
14
+ this.terminalManager = terminalManager;
15
+ this.onEvent = onEvent;
16
+ }
17
+ /**
18
+ * Handle permission request from agent
19
+ */
20
+ async requestPermission(params) {
21
+ console.log('[amux] Permission request:', params.toolCall?.title);
22
+ // Send permission request
23
+ const requestId = crypto.randomUUID();
24
+ this.onEvent({
25
+ type: 'permission_request',
26
+ requestId,
27
+ toolCall: params.toolCall,
28
+ options: params.options,
29
+ });
30
+ // Wait for response from client (with timeout)
31
+ return new Promise((resolve, reject) => {
32
+ const timeout = setTimeout(() => {
33
+ this.pendingPermissions.delete(requestId);
34
+ resolve({ outcome: { outcome: 'cancelled' } });
35
+ }, 300000); // 5 minute timeout
36
+ this.pendingPermissions.set(requestId, { resolve, reject, timeout });
37
+ });
38
+ }
39
+ /**
40
+ * Handle permission response from UI
41
+ */
42
+ handlePermissionResponse(requestId, optionId) {
43
+ const pending = this.pendingPermissions.get(requestId);
44
+ if (pending) {
45
+ clearTimeout(pending.timeout);
46
+ this.pendingPermissions.delete(requestId);
47
+ pending.resolve({
48
+ outcome: { outcome: 'selected', optionId },
49
+ });
50
+ }
51
+ }
52
+ /**
53
+ * Handle session update from agent
54
+ */
55
+ async sessionUpdate(params) {
56
+ const update = params.update;
57
+ // Normalize the update (convert Claude-style diffs to unified format)
58
+ const normalized = normalizeSessionUpdate(update);
59
+ // Route to session
60
+ this.onEvent({ type: 'session_update', update: normalized });
61
+ // Log updates for debugging
62
+ if (update.sessionUpdate === 'agent_message_chunk') {
63
+ // Don't log every chunk
64
+ }
65
+ else if (isToolCallUpdate(update)) {
66
+ console.log(`[amux] Tool call: ${update.title} (${update.status}) id=${update.toolCallId}`);
67
+ }
68
+ else if (update.sessionUpdate === 'tool_call_update') {
69
+ // Don't log every update
70
+ }
71
+ else if (update.sessionUpdate === 'current_mode_update') {
72
+ console.log(`[amux] Mode update: ${update.currentModeId}`);
73
+ }
74
+ else {
75
+ console.log(`[amux] Session update: ${update.sessionUpdate}`);
76
+ }
77
+ }
78
+ /**
79
+ * Write text file (ACP fs capability)
80
+ */
81
+ async writeTextFile(params) {
82
+ console.log('[amux] Write text file:', params.path);
83
+ try {
84
+ fs.writeFileSync(params.path, params.content);
85
+ return {};
86
+ }
87
+ catch (err) {
88
+ throw new Error(`Failed to write file: ${err.message}`);
89
+ }
90
+ }
91
+ /**
92
+ * Read text file (ACP fs capability)
93
+ */
94
+ async readTextFile(params) {
95
+ console.log('[amux] Read text file:', params.path);
96
+ try {
97
+ const content = fs.readFileSync(params.path, 'utf-8');
98
+ return { content };
99
+ }
100
+ catch (err) {
101
+ throw new Error(`Failed to read file: ${err.message}`);
102
+ }
103
+ }
104
+ /**
105
+ * Create terminal (ACP terminal capability)
106
+ */
107
+ async createTerminal(params) {
108
+ return this.terminalManager.create(params);
109
+ }
110
+ /**
111
+ * Get terminal output (ACP terminal capability)
112
+ */
113
+ async terminalOutput(params) {
114
+ return this.terminalManager.getOutput(params.terminalId);
115
+ }
116
+ /**
117
+ * Wait for terminal exit (ACP terminal capability)
118
+ */
119
+ async waitForTerminalExit(params) {
120
+ return this.terminalManager.waitForExit(params.terminalId);
121
+ }
122
+ /**
123
+ * Kill terminal (ACP terminal capability)
124
+ */
125
+ async killTerminal(params) {
126
+ return this.terminalManager.kill(params.terminalId);
127
+ }
128
+ /**
129
+ * Release terminal (ACP terminal capability)
130
+ */
131
+ async releaseTerminal(params) {
132
+ return this.terminalManager.release(params.terminalId);
133
+ }
134
+ }
135
+ //# sourceMappingURL=client.js.map