@bytespell/amux 0.0.11 → 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.
- package/.claude/settings.local.json +11 -0
- package/CLAUDE.md +104 -0
- package/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +118 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +135 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/{lib/mentions.d.ts → message-parser.d.ts} +3 -5
- package/dist/message-parser.d.ts.map +1 -0
- package/dist/message-parser.js +45 -0
- package/dist/message-parser.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +356 -0
- package/dist/server.js.map +1 -0
- package/dist/session-updates.d.ts +26 -0
- package/dist/session-updates.d.ts.map +1 -0
- package/dist/session-updates.js +68 -0
- package/dist/session-updates.js.map +1 -0
- package/dist/session.d.ts +207 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +563 -0
- package/dist/session.js.map +1 -0
- package/dist/state.d.ts +74 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +250 -0
- package/dist/state.js.map +1 -0
- package/dist/terminal.d.ts +47 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +137 -0
- package/dist/terminal.js.map +1 -0
- package/dist/types.d.ts +64 -2
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -31
- package/dist/types.js.map +1 -1
- package/dist/ws-adapter.d.ts +39 -0
- package/dist/ws-adapter.d.ts.map +1 -0
- package/dist/ws-adapter.js +198 -0
- package/dist/ws-adapter.js.map +1 -0
- package/package.json +47 -24
- package/src/client.ts +162 -0
- package/src/index.ts +66 -0
- package/src/message-parser.ts +54 -0
- package/src/session-updates.ts +87 -0
- package/src/session.ts +719 -0
- package/src/state.ts +287 -0
- package/src/terminal.ts +164 -0
- package/src/types.ts +88 -0
- package/src/ws-adapter.ts +245 -0
- package/tsconfig.json +22 -0
- package/dist/chunk-5IPYOXBE.js +0 -32
- package/dist/chunk-5IPYOXBE.js.map +0 -1
- package/dist/chunk-C73RKCTS.js +0 -36
- package/dist/chunk-C73RKCTS.js.map +0 -1
- package/dist/chunk-VVXT4HQM.js +0 -779
- package/dist/chunk-VVXT4HQM.js.map +0 -1
- package/dist/lib/logger.d.ts +0 -24
- package/dist/lib/logger.js +0 -17
- package/dist/lib/logger.js.map +0 -1
- package/dist/lib/mentions.js +0 -7
- package/dist/lib/mentions.js.map +0 -1
- package/dist/streams/backends/index.d.ts +0 -88
- package/dist/streams/backends/index.js +0 -13
- package/dist/streams/backends/index.js.map +0 -1
- package/dist/streams/manager.d.ts +0 -55
- package/dist/streams/manager.js +0 -248
- package/dist/streams/manager.js.map +0 -1
- package/dist/types-DCRtrjjj.d.ts +0 -192
- package/scripts/fix-pty.cjs +0 -21
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
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -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
|