agent-gear 1.1.2 → 1.1.4
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/AGENTS.md +23 -0
- package/dist/acp/detector.d.ts +3 -0
- package/dist/acp/detector.d.ts.map +1 -1
- package/dist/acp/detector.js +41 -2
- package/dist/acp/detector.js.map +1 -1
- package/dist/{port-forward.d.ts → gateway.d.ts} +7 -3
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +158 -0
- package/dist/gateway.js.map +1 -0
- package/dist/gateway.test.d.ts +2 -0
- package/dist/gateway.test.d.ts.map +1 -0
- package/dist/gateway.test.js +241 -0
- package/dist/gateway.test.js.map +1 -0
- package/dist/socketio-client.d.ts.map +1 -1
- package/dist/socketio-client.js +16 -12
- package/dist/socketio-client.js.map +1 -1
- package/package.json +1 -1
- package/src/acp/detector.ts +41 -2
- package/src/gateway.test.ts +301 -0
- package/src/gateway.ts +195 -0
- package/src/socketio-client.ts +18 -12
- package/tests/acp-detector.test.ts +68 -26
- package/dist/port-forward.d.ts.map +0 -1
- package/dist/port-forward.js +0 -68
- package/dist/port-forward.js.map +0 -1
- package/dist/port-forward.test.d.ts +0 -2
- package/dist/port-forward.test.d.ts.map +0 -1
- package/dist/port-forward.test.js +0 -163
- package/dist/port-forward.test.js.map +0 -1
- package/src/port-forward.test.ts +0 -202
- package/src/port-forward.ts +0 -88
package/src/gateway.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
import type { Socket } from 'socket.io-client';
|
|
3
|
+
|
|
4
|
+
interface Tunnel {
|
|
5
|
+
id: string;
|
|
6
|
+
port: number;
|
|
7
|
+
localSocket: net.Socket | null;
|
|
8
|
+
serverSocket: Socket;
|
|
9
|
+
pendingRequestId?: string | null;
|
|
10
|
+
responseBuffer: Buffer[];
|
|
11
|
+
responseTimer?: ReturnType<typeof setTimeout> | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const RECONNECT_INTERVAL_MS = 3000;
|
|
15
|
+
const HTTP_RESPONSE_COLLECT_MS = 100;
|
|
16
|
+
|
|
17
|
+
export class GatewayManager {
|
|
18
|
+
private tunnels: Map<string, Tunnel> = new Map();
|
|
19
|
+
private reconnectTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
|
|
20
|
+
|
|
21
|
+
async createTunnel(tunnelId: string, port: number, serverSocket: Socket): Promise<void> {
|
|
22
|
+
this.tunnels.set(tunnelId, { id: tunnelId, port, localSocket: null, serverSocket, responseBuffer: [] });
|
|
23
|
+
await this.connect(tunnelId, true);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private async connect(tunnelId: string, isInitial: boolean): Promise<void> {
|
|
27
|
+
const tunnel = this.tunnels.get(tunnelId);
|
|
28
|
+
if (!tunnel) return;
|
|
29
|
+
|
|
30
|
+
const localSocket = net.connect({ host: '127.0.0.1', port: tunnel.port });
|
|
31
|
+
localSocket.setKeepAlive(true, 5000);
|
|
32
|
+
|
|
33
|
+
return new Promise<void>((resolve) => {
|
|
34
|
+
const onError = (err: Error) => {
|
|
35
|
+
console.error(`Gateway tunnel error: tunnelId=${tunnelId} error=${err.message}`);
|
|
36
|
+
localSocket.destroy();
|
|
37
|
+
this.scheduleReconnect(tunnelId);
|
|
38
|
+
resolve();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
localSocket.once('error', onError);
|
|
42
|
+
|
|
43
|
+
localSocket.once('connect', () => {
|
|
44
|
+
localSocket.removeListener('error', onError);
|
|
45
|
+
|
|
46
|
+
tunnel.localSocket = localSocket;
|
|
47
|
+
console.log(`Gateway tunnel connected: tunnelId=${tunnelId} port=${tunnel.port}`);
|
|
48
|
+
|
|
49
|
+
if (isInitial) {
|
|
50
|
+
tunnel.serverSocket.emit('port-forward:created', { tunnelId, success: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
localSocket.on('data', (data: Buffer) => {
|
|
54
|
+
if (tunnel.pendingRequestId) {
|
|
55
|
+
tunnel.responseBuffer.push(data);
|
|
56
|
+
if (tunnel.responseTimer) clearTimeout(tunnel.responseTimer);
|
|
57
|
+
tunnel.responseTimer = setTimeout(() => this.flushResponse(tunnel), HTTP_RESPONSE_COLLECT_MS);
|
|
58
|
+
} else {
|
|
59
|
+
tunnel.serverSocket.emit('port-forward:data', {
|
|
60
|
+
tunnelId,
|
|
61
|
+
data: data.toString('base64'),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
localSocket.on('close', () => {
|
|
67
|
+
console.log(`Gateway local socket closed: tunnelId=${tunnelId}`);
|
|
68
|
+
tunnel.localSocket = null;
|
|
69
|
+
this.scheduleReconnect(tunnelId);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
localSocket.on('error', (err: Error) => {
|
|
73
|
+
console.error(`Gateway local socket error: tunnelId=${tunnelId} error=${err.message}`);
|
|
74
|
+
tunnel.serverSocket.emit('port-forward:error', { tunnelId, error: err.message });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
resolve();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private flushResponse(tunnel: Tunnel): void {
|
|
83
|
+
if (!tunnel.pendingRequestId) return;
|
|
84
|
+
const combined = Buffer.concat(tunnel.responseBuffer);
|
|
85
|
+
tunnel.responseBuffer = [];
|
|
86
|
+
const requestId = tunnel.pendingRequestId;
|
|
87
|
+
tunnel.pendingRequestId = null;
|
|
88
|
+
tunnel.responseTimer = null;
|
|
89
|
+
|
|
90
|
+
tunnel.serverSocket.emit('port-forward:data', {
|
|
91
|
+
tunnelId: tunnel.id,
|
|
92
|
+
data: Buffer.from(JSON.stringify({
|
|
93
|
+
type: 'http-response',
|
|
94
|
+
requestId,
|
|
95
|
+
body: combined.toString('base64'),
|
|
96
|
+
})).toString('base64'),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private scheduleReconnect(tunnelId: string): void {
|
|
101
|
+
if (!this.tunnels.has(tunnelId) || this.reconnectTimers.has(tunnelId)) return;
|
|
102
|
+
|
|
103
|
+
console.log(`Gateway scheduling reconnection: tunnelId=${tunnelId}`);
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
this.reconnectTimers.delete(tunnelId);
|
|
106
|
+
this.connect(tunnelId, false);
|
|
107
|
+
}, RECONNECT_INTERVAL_MS);
|
|
108
|
+
|
|
109
|
+
this.reconnectTimers.set(tunnelId, timer);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
handleTunnelData(tunnelId: string, data: string): void {
|
|
113
|
+
const tunnel = this.tunnels.get(tunnelId);
|
|
114
|
+
if (!tunnel) {
|
|
115
|
+
console.warn(`Gateway data for unknown tunnel: tunnelId=${tunnelId}`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const buffer = Buffer.from(data, 'base64');
|
|
120
|
+
let parsed: any;
|
|
121
|
+
try {
|
|
122
|
+
parsed = JSON.parse(buffer.toString('utf8'));
|
|
123
|
+
} catch {
|
|
124
|
+
if (tunnel.localSocket) {
|
|
125
|
+
tunnel.localSocket.write(buffer);
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (parsed.type === 'http-request') {
|
|
131
|
+
const { method, path, headers, body } = parsed;
|
|
132
|
+
let rawRequest = `${method} ${path} HTTP/1.1\r\n`;
|
|
133
|
+
for (const [key, value] of Object.entries(headers || {})) {
|
|
134
|
+
rawRequest += `${key}: ${value}\r\n`;
|
|
135
|
+
}
|
|
136
|
+
rawRequest += `Host: 127.0.0.1:${tunnel.port}\r\n`;
|
|
137
|
+
rawRequest += 'Connection: close\r\n';
|
|
138
|
+
rawRequest += '\r\n';
|
|
139
|
+
if (body) rawRequest += Buffer.from(body, 'base64').toString('utf8');
|
|
140
|
+
|
|
141
|
+
tunnel.pendingRequestId = parsed.requestId;
|
|
142
|
+
tunnel.responseBuffer = [];
|
|
143
|
+
|
|
144
|
+
if (tunnel.localSocket) {
|
|
145
|
+
tunnel.localSocket.write(rawRequest);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (tunnel.localSocket) {
|
|
151
|
+
tunnel.localSocket.write(buffer);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
closeTunnel(tunnelId: string): void {
|
|
156
|
+
const tunnel = this.tunnels.get(tunnelId);
|
|
157
|
+
if (!tunnel) return;
|
|
158
|
+
|
|
159
|
+
console.log(`Gateway closing tunnel: tunnelId=${tunnelId}`);
|
|
160
|
+
|
|
161
|
+
const timer = this.reconnectTimers.get(tunnelId);
|
|
162
|
+
if (timer) {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
this.reconnectTimers.delete(tunnelId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.tunnels.delete(tunnelId);
|
|
168
|
+
|
|
169
|
+
if (tunnel.localSocket) {
|
|
170
|
+
tunnel.localSocket.destroy();
|
|
171
|
+
}
|
|
172
|
+
tunnel.serverSocket.emit('port-forward:close', { tunnelId });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
closeAll(): void {
|
|
176
|
+
console.log(`Gateway closing all tunnels: count=${this.tunnels.size}`);
|
|
177
|
+
|
|
178
|
+
for (const [tunnelId, tunnel] of [...this.tunnels]) {
|
|
179
|
+
const timer = this.reconnectTimers.get(tunnelId);
|
|
180
|
+
if (timer) {
|
|
181
|
+
clearTimeout(timer);
|
|
182
|
+
this.reconnectTimers.delete(tunnelId);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.tunnels.delete(tunnelId);
|
|
186
|
+
|
|
187
|
+
if (tunnel.localSocket) {
|
|
188
|
+
tunnel.localSocket.destroy();
|
|
189
|
+
}
|
|
190
|
+
tunnel.serverSocket.emit('port-forward:close', { tunnelId: tunnel.id });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export const gatewayManager = new GatewayManager();
|
package/src/socketio-client.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { AcpSyncBridge } from './acp/sync-bridge.js';
|
|
|
13
13
|
import { gitHandler } from './git-handler.js';
|
|
14
14
|
import { worktreeHandler } from './worktree-handler.js';
|
|
15
15
|
import { fsHandler } from './fs-handler.js';
|
|
16
|
-
import {
|
|
16
|
+
import { gatewayManager } from './gateway.js';
|
|
17
17
|
|
|
18
18
|
const CONTROLLER_VERSION = '1.1.1';
|
|
19
19
|
const acpBridges = new Map<string, AcpSessionBridge>();
|
|
@@ -83,12 +83,14 @@ interface AcpResumePayload {
|
|
|
83
83
|
|
|
84
84
|
interface AcpListSessionsPayload {
|
|
85
85
|
requestId: string;
|
|
86
|
+
serverType: string;
|
|
86
87
|
cwd: string;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
interface AcpLoadSessionPayload {
|
|
90
91
|
requestId: string;
|
|
91
92
|
sessionId: string;
|
|
93
|
+
serverType: string;
|
|
92
94
|
cwd: string;
|
|
93
95
|
}
|
|
94
96
|
|
|
@@ -343,8 +345,10 @@ export function connectToServer(
|
|
|
343
345
|
});
|
|
344
346
|
|
|
345
347
|
socket.on('acp:list-sessions', async (data: AcpListSessionsPayload) => {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
+
const serverType = data.serverType ?? 'opencode';
|
|
349
|
+
console.log(`[ACP sync] acp:list-sessions received: requestId=${data.requestId} serverType=${serverType} cwd=${data.cwd}`);
|
|
350
|
+
const spawner = createSpawner(serverType, `sync_${Date.now()}`, data.cwd);
|
|
351
|
+
const bridge = new AcpSyncBridge({ cwd: data.cwd }, spawner);
|
|
348
352
|
try {
|
|
349
353
|
await bridge.connect();
|
|
350
354
|
const result = await bridge.listSessions(data.cwd);
|
|
@@ -371,8 +375,10 @@ export function connectToServer(
|
|
|
371
375
|
});
|
|
372
376
|
|
|
373
377
|
socket.on('acp:load-session', async (data: AcpLoadSessionPayload) => {
|
|
374
|
-
|
|
375
|
-
|
|
378
|
+
const serverType = data.serverType ?? 'opencode';
|
|
379
|
+
console.log(`[ACP] load-session received: requestId=${data.requestId} sessionId=${data.sessionId} serverType=${serverType} cwd=${data.cwd}`);
|
|
380
|
+
const spawner = createSpawner(serverType, `sync_${Date.now()}`, data.cwd);
|
|
381
|
+
const bridge = new AcpSyncBridge({ cwd: data.cwd }, spawner);
|
|
376
382
|
try {
|
|
377
383
|
await bridge.connect();
|
|
378
384
|
const { config, updates } = await bridge.loadSession(data.sessionId, data.cwd);
|
|
@@ -585,19 +591,19 @@ export function connectToServer(
|
|
|
585
591
|
socket.emit('git:commit:result', { requestId: data.requestId, ...result });
|
|
586
592
|
});
|
|
587
593
|
|
|
588
|
-
// Port Forward events
|
|
594
|
+
// Port Forward events (event names kept unchanged for backward compatibility)
|
|
589
595
|
socket.on('port-forward:create', async (data: { tunnelId: string; port: number }) => {
|
|
590
|
-
console.log(`
|
|
591
|
-
await
|
|
596
|
+
console.log(`Gateway tunnel create requested: tunnelId=${data.tunnelId} port=${data.port}`);
|
|
597
|
+
await gatewayManager.createTunnel(data.tunnelId, data.port, socket);
|
|
592
598
|
});
|
|
593
599
|
|
|
594
600
|
socket.on('port-forward:data', (data: { tunnelId: string; data: string }) => {
|
|
595
|
-
|
|
601
|
+
gatewayManager.handleTunnelData(data.tunnelId, data.data);
|
|
596
602
|
});
|
|
597
603
|
|
|
598
604
|
socket.on('port-forward:close', (data: { tunnelId: string }) => {
|
|
599
|
-
console.log(`
|
|
600
|
-
|
|
605
|
+
console.log(`Gateway tunnel close: tunnelId=${data.tunnelId}`);
|
|
606
|
+
gatewayManager.closeTunnel(data.tunnelId);
|
|
601
607
|
});
|
|
602
608
|
|
|
603
609
|
socket.on('acp:permission:response', (data: {
|
|
@@ -614,7 +620,7 @@ export function connectToServer(
|
|
|
614
620
|
socket.on('disconnect', (reason) => {
|
|
615
621
|
console.log(`Disconnected: ${reason}`);
|
|
616
622
|
heartbeat.stop();
|
|
617
|
-
|
|
623
|
+
gatewayManager.closeAll();
|
|
618
624
|
permissionRequestBridge.clear();
|
|
619
625
|
});
|
|
620
626
|
|
|
@@ -32,71 +32,98 @@ describe('AcpDetector', () => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
describe('detect', () => {
|
|
35
|
-
it('should detect
|
|
35
|
+
it('should detect all 5 servers when all available', async () => {
|
|
36
36
|
mockExecSuccessOnce('opencode v1.2.3\n');
|
|
37
37
|
mockExecSuccessOnce('copilot 1.0.0\n');
|
|
38
|
+
mockExecSuccessOnce('claude-agent-acp 0.1.0\n');
|
|
39
|
+
mockExecSuccessOnce('gemini 2.0.0\n');
|
|
40
|
+
mockExecSuccessOnce('codex-acp 0.5.0\n');
|
|
38
41
|
|
|
39
42
|
const result = await detector.detect();
|
|
40
43
|
|
|
41
|
-
expect(result).toHaveLength(
|
|
44
|
+
expect(result).toHaveLength(5);
|
|
42
45
|
expect(result[0]).toEqual({ type: 'opencode', available: true, version: '1.2.3' });
|
|
43
46
|
expect(result[1]).toEqual({ type: 'github_copilot', available: true, version: '1.0.0' });
|
|
47
|
+
expect(result[2]).toEqual({ type: 'claude_code', available: true, version: '0.1.0' });
|
|
48
|
+
expect(result[3]).toEqual({ type: 'gemini_cli', available: true, version: '2.0.0' });
|
|
49
|
+
expect(result[4]).toEqual({ type: 'codex', available: true, version: '0.5.0' });
|
|
44
50
|
});
|
|
45
51
|
|
|
46
52
|
it('should detect as unavailable when command fails', async () => {
|
|
47
53
|
mockExecFailureOnce();
|
|
48
54
|
mockExecFailureOnce();
|
|
55
|
+
mockExecFailureOnce();
|
|
56
|
+
mockExecFailureOnce();
|
|
57
|
+
mockExecFailureOnce();
|
|
49
58
|
|
|
50
59
|
const result = await detector.detect();
|
|
51
60
|
|
|
52
|
-
expect(result).toHaveLength(
|
|
61
|
+
expect(result).toHaveLength(5);
|
|
53
62
|
expect(result[0]).toEqual({ type: 'opencode', available: false });
|
|
54
63
|
expect(result[1]).toEqual({ type: 'github_copilot', available: false });
|
|
64
|
+
expect(result[2]).toEqual({ type: 'claude_code', available: false });
|
|
65
|
+
expect(result[3]).toEqual({ type: 'gemini_cli', available: false });
|
|
66
|
+
expect(result[4]).toEqual({ type: 'codex', available: false });
|
|
55
67
|
});
|
|
56
68
|
|
|
57
|
-
it('should
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
it('should detect claude_code only when other servers fail', async () => {
|
|
70
|
+
mockExecFailureOnce();
|
|
71
|
+
mockExecFailureOnce();
|
|
72
|
+
mockExecSuccessOnce('claude-agent-acp 0.1.0\n');
|
|
73
|
+
mockExecFailureOnce();
|
|
74
|
+
mockExecFailureOnce();
|
|
60
75
|
|
|
61
76
|
const result = await detector.detect();
|
|
62
77
|
|
|
63
|
-
expect(result
|
|
64
|
-
expect(result[
|
|
78
|
+
expect(result).toHaveLength(5);
|
|
79
|
+
expect(result[0]).toEqual({ type: 'opencode', available: false });
|
|
80
|
+
expect(result[1]).toEqual({ type: 'github_copilot', available: false });
|
|
81
|
+
expect(result[2]).toEqual({ type: 'claude_code', available: true, version: '0.1.0' });
|
|
82
|
+
expect(result[3]).toEqual({ type: 'gemini_cli', available: false });
|
|
83
|
+
expect(result[4]).toEqual({ type: 'codex', available: false });
|
|
65
84
|
});
|
|
66
85
|
|
|
67
|
-
it('should
|
|
68
|
-
|
|
69
|
-
|
|
86
|
+
it('should detect gemini_cli only when other servers fail', async () => {
|
|
87
|
+
mockExecFailureOnce();
|
|
88
|
+
mockExecFailureOnce();
|
|
89
|
+
mockExecFailureOnce();
|
|
90
|
+
mockExecSuccessOnce('gemini 2.0.0\n');
|
|
91
|
+
mockExecFailureOnce();
|
|
70
92
|
|
|
71
93
|
const result = await detector.detect();
|
|
72
94
|
|
|
73
|
-
expect(result
|
|
74
|
-
expect(result[
|
|
95
|
+
expect(result).toHaveLength(5);
|
|
96
|
+
expect(result[3]).toEqual({ type: 'gemini_cli', available: true, version: '2.0.0' });
|
|
75
97
|
});
|
|
76
98
|
|
|
77
|
-
it('should detect only
|
|
78
|
-
|
|
99
|
+
it('should detect codex only when other servers fail', async () => {
|
|
100
|
+
mockExecFailureOnce();
|
|
101
|
+
mockExecFailureOnce();
|
|
79
102
|
mockExecFailureOnce();
|
|
103
|
+
mockExecFailureOnce();
|
|
104
|
+
mockExecSuccessOnce('codex-acp 0.5.0\n');
|
|
80
105
|
|
|
81
106
|
const result = await detector.detect();
|
|
82
107
|
|
|
83
|
-
expect(result).toHaveLength(
|
|
84
|
-
expect(result[
|
|
85
|
-
expect(result[1]).toEqual({ type: 'github_copilot', available: false });
|
|
108
|
+
expect(result).toHaveLength(5);
|
|
109
|
+
expect(result[4]).toEqual({ type: 'codex', available: true, version: '0.5.0' });
|
|
86
110
|
});
|
|
87
111
|
|
|
88
|
-
it('should
|
|
89
|
-
|
|
90
|
-
mockExecSuccessOnce('copilot
|
|
112
|
+
it('should parse version with different formats', async () => {
|
|
113
|
+
mockExecSuccessOnce('OpenCode version 2.0.1\n');
|
|
114
|
+
mockExecSuccessOnce('copilot 3.0.0\n');
|
|
115
|
+
mockExecSuccessOnce('v0.1.0\n');
|
|
116
|
+
mockExecSuccessOnce('Gemini 2.0.0\n');
|
|
117
|
+
mockExecSuccessOnce('codex-acp version 0.5.0\n');
|
|
91
118
|
|
|
92
119
|
const result = await detector.detect();
|
|
93
120
|
|
|
94
|
-
expect(result).
|
|
95
|
-
expect(result[
|
|
96
|
-
expect(result[
|
|
121
|
+
expect(result[0].version).toBe('2.0.1');
|
|
122
|
+
expect(result[1].version).toBe('3.0.0');
|
|
123
|
+
expect(result[2].version).toBe('0.1.0');
|
|
124
|
+
expect(result[3].version).toBe('2.0.0');
|
|
125
|
+
expect(result[4].version).toBe('0.5.0');
|
|
97
126
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
127
|
});
|
|
101
128
|
|
|
102
129
|
describe('getSpawnerConfig', () => {
|
|
@@ -110,6 +137,21 @@ describe('AcpDetector', () => {
|
|
|
110
137
|
expect(config).toEqual({ command: 'copilot', args: ['--acp', '--stdio'] });
|
|
111
138
|
});
|
|
112
139
|
|
|
140
|
+
it('should return claude_code spawner config', () => {
|
|
141
|
+
const config = detector.getSpawnerConfig('claude_code');
|
|
142
|
+
expect(config).toEqual({ command: 'claude-agent-acp', args: [] });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should return gemini_cli spawner config', () => {
|
|
146
|
+
const config = detector.getSpawnerConfig('gemini_cli');
|
|
147
|
+
expect(config).toEqual({ command: 'gemini', args: ['--acp'] });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should return codex spawner config', () => {
|
|
151
|
+
const config = detector.getSpawnerConfig('codex');
|
|
152
|
+
expect(config).toEqual({ command: 'codex-acp', args: [] });
|
|
153
|
+
});
|
|
154
|
+
|
|
113
155
|
it('should return opencode as default for unknown serverType', () => {
|
|
114
156
|
const config = detector.getSpawnerConfig('unknown_server');
|
|
115
157
|
expect(config).toEqual({ command: 'opencode', args: ['acp'] });
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"port-forward.d.ts","sourceRoot":"","sources":["../src/port-forward.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAkC;IAE3C,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiDvF,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAStD,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQnC,QAAQ,IAAI,IAAI;CAOjB;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
|
package/dist/port-forward.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import net from 'node:net';
|
|
2
|
-
export class PortForwardManager {
|
|
3
|
-
tunnels = new Map();
|
|
4
|
-
async createTunnel(tunnelId, port, serverSocket) {
|
|
5
|
-
const localSocket = net.connect({ host: '127.0.0.1', port });
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
const onError = (err) => {
|
|
8
|
-
console.error(`Port forward tunnel error: tunnelId=${tunnelId} error=${err.message}`);
|
|
9
|
-
serverSocket.emit('port-forward:created', {
|
|
10
|
-
tunnelId,
|
|
11
|
-
success: false,
|
|
12
|
-
error: err.message,
|
|
13
|
-
});
|
|
14
|
-
localSocket.destroy();
|
|
15
|
-
resolve();
|
|
16
|
-
};
|
|
17
|
-
localSocket.once('error', onError);
|
|
18
|
-
localSocket.once('connect', () => {
|
|
19
|
-
localSocket.removeListener('error', onError);
|
|
20
|
-
this.tunnels.set(tunnelId, { id: tunnelId, port, localSocket });
|
|
21
|
-
console.log(`Port forward tunnel created: tunnelId=${tunnelId} port=${port}`);
|
|
22
|
-
serverSocket.emit('port-forward:created', { tunnelId, success: true });
|
|
23
|
-
localSocket.on('data', (data) => {
|
|
24
|
-
serverSocket.emit('port-forward:data', {
|
|
25
|
-
tunnelId,
|
|
26
|
-
data: data.toString('base64'),
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
localSocket.on('close', () => {
|
|
30
|
-
console.log(`Port forward local socket closed: tunnelId=${tunnelId}`);
|
|
31
|
-
this.tunnels.delete(tunnelId);
|
|
32
|
-
serverSocket.emit('port-forward:close', { tunnelId });
|
|
33
|
-
});
|
|
34
|
-
localSocket.on('error', (err) => {
|
|
35
|
-
console.error(`Port forward local socket error: tunnelId=${tunnelId} error=${err.message}`);
|
|
36
|
-
this.tunnels.delete(tunnelId);
|
|
37
|
-
serverSocket.emit('port-forward:error', { tunnelId, error: err.message });
|
|
38
|
-
});
|
|
39
|
-
resolve();
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
handleTunnelData(tunnelId, data) {
|
|
44
|
-
const tunnel = this.tunnels.get(tunnelId);
|
|
45
|
-
if (!tunnel) {
|
|
46
|
-
console.warn(`Port forward data for unknown tunnel: tunnelId=${tunnelId}`);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
tunnel.localSocket.write(Buffer.from(data, 'base64'));
|
|
50
|
-
}
|
|
51
|
-
closeTunnel(tunnelId) {
|
|
52
|
-
const tunnel = this.tunnels.get(tunnelId);
|
|
53
|
-
if (!tunnel)
|
|
54
|
-
return;
|
|
55
|
-
console.log(`Port forward closing tunnel: tunnelId=${tunnelId}`);
|
|
56
|
-
tunnel.localSocket.destroy();
|
|
57
|
-
this.tunnels.delete(tunnelId);
|
|
58
|
-
}
|
|
59
|
-
closeAll() {
|
|
60
|
-
console.log(`Port forward closing all tunnels: count=${this.tunnels.size}`);
|
|
61
|
-
for (const tunnel of this.tunnels.values()) {
|
|
62
|
-
tunnel.localSocket.destroy();
|
|
63
|
-
}
|
|
64
|
-
this.tunnels.clear();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
export const portForwardManager = new PortForwardManager();
|
|
68
|
-
//# sourceMappingURL=port-forward.js.map
|
package/dist/port-forward.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"port-forward.js","sourceRoot":"","sources":["../src/port-forward.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"port-forward.test.d.ts","sourceRoot":"","sources":["../src/port-forward.test.ts"],"names":[],"mappings":""}
|