@kb-labs/host-agent-transport 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +107 -0
- package/dist/index.js +241 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ILocalTransport — abstraction over the IPC channel between
|
|
3
|
+
* CLI/Studio (client) and Host Agent daemon (server).
|
|
4
|
+
*
|
|
5
|
+
* Both sides use the same interface:
|
|
6
|
+
* - Server: listen() + onMessage()
|
|
7
|
+
* - Client: connect() + send()
|
|
8
|
+
*/
|
|
9
|
+
interface ILocalTransport {
|
|
10
|
+
/** Server side: start accepting connections */
|
|
11
|
+
listen(): Promise<void>;
|
|
12
|
+
/** Client side: connect to the server */
|
|
13
|
+
connect(): Promise<void>;
|
|
14
|
+
/** Send a message (both sides) */
|
|
15
|
+
send(msg: unknown): void;
|
|
16
|
+
/** Register a handler for incoming messages */
|
|
17
|
+
onMessage(handler: (msg: unknown) => void): void;
|
|
18
|
+
/** Close the connection / server */
|
|
19
|
+
close(): void;
|
|
20
|
+
}
|
|
21
|
+
type TransportMode = 'unix' | 'named-pipe' | 'tcp' | 'auto';
|
|
22
|
+
interface TransportConfig {
|
|
23
|
+
mode: TransportMode;
|
|
24
|
+
/** Unix socket path (unix mode). Default: ~/.kb/agent.sock */
|
|
25
|
+
socketPath?: string;
|
|
26
|
+
/** Named pipe name (named-pipe mode). Default: kb-agent */
|
|
27
|
+
pipeName?: string;
|
|
28
|
+
/** TCP port (tcp mode). Default: 7779 */
|
|
29
|
+
port?: number;
|
|
30
|
+
/** TCP host (tcp mode). Default: 127.0.0.1 */
|
|
31
|
+
host?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* UnixSocketTransport — ILocalTransport over Unix domain socket.
|
|
36
|
+
* Default on Linux/macOS.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
declare class UnixSocketTransport implements ILocalTransport {
|
|
40
|
+
private readonly socketPath;
|
|
41
|
+
private server;
|
|
42
|
+
private socket;
|
|
43
|
+
private messageHandler;
|
|
44
|
+
constructor(socketPath: string);
|
|
45
|
+
listen(): Promise<void>;
|
|
46
|
+
connect(): Promise<void>;
|
|
47
|
+
send(msg: unknown): void;
|
|
48
|
+
onMessage(handler: (msg: unknown) => void): void;
|
|
49
|
+
close(): void;
|
|
50
|
+
private handleConnection;
|
|
51
|
+
private setupSocket;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* NamedPipeTransport — ILocalTransport over Windows named pipe.
|
|
56
|
+
* Default on Windows. Uses the same net.createServer API — Node.js
|
|
57
|
+
* treats \\.\pipe\<name> paths as named pipes automatically.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
declare class NamedPipeTransport implements ILocalTransport {
|
|
61
|
+
private server;
|
|
62
|
+
private socket;
|
|
63
|
+
private messageHandler;
|
|
64
|
+
private readonly path;
|
|
65
|
+
constructor(pipeName: string);
|
|
66
|
+
listen(): Promise<void>;
|
|
67
|
+
connect(): Promise<void>;
|
|
68
|
+
send(msg: unknown): void;
|
|
69
|
+
onMessage(handler: (msg: unknown) => void): void;
|
|
70
|
+
close(): void;
|
|
71
|
+
private handleConnection;
|
|
72
|
+
private setupSocket;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* TcpTransport — ILocalTransport over TCP localhost.
|
|
77
|
+
* Universal fallback: works on Linux, macOS, Windows without special paths.
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
declare class TcpTransport implements ILocalTransport {
|
|
81
|
+
private readonly port;
|
|
82
|
+
private readonly host;
|
|
83
|
+
private server;
|
|
84
|
+
private socket;
|
|
85
|
+
private messageHandler;
|
|
86
|
+
constructor(port: number, host?: string);
|
|
87
|
+
listen(): Promise<void>;
|
|
88
|
+
connect(): Promise<void>;
|
|
89
|
+
send(msg: unknown): void;
|
|
90
|
+
onMessage(handler: (msg: unknown) => void): void;
|
|
91
|
+
close(): void;
|
|
92
|
+
private handleConnection;
|
|
93
|
+
private setupSocket;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* createTransport — factory that picks the right ILocalTransport
|
|
98
|
+
* based on config.mode (or platform when mode === 'auto').
|
|
99
|
+
*
|
|
100
|
+
* auto resolution:
|
|
101
|
+
* Windows → NamedPipeTransport
|
|
102
|
+
* Linux/Mac → UnixSocketTransport
|
|
103
|
+
*/
|
|
104
|
+
|
|
105
|
+
declare function createTransport(config: TransportConfig): ILocalTransport;
|
|
106
|
+
|
|
107
|
+
export { type ILocalTransport, NamedPipeTransport, TcpTransport, type TransportConfig, type TransportMode, UnixSocketTransport, createTransport };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
// src/unix-socket.ts
|
|
6
|
+
|
|
7
|
+
// src/ndjson-framer.ts
|
|
8
|
+
var MAX_BUFFER_BYTES = 1024 * 1024;
|
|
9
|
+
var NdjsonFramer = class {
|
|
10
|
+
constructor(onMessage) {
|
|
11
|
+
this.onMessage = onMessage;
|
|
12
|
+
}
|
|
13
|
+
buffer = "";
|
|
14
|
+
/** Feed raw data from socket. Returns false if buffer limit exceeded (socket is closed). */
|
|
15
|
+
feed(socket, chunk) {
|
|
16
|
+
if (Buffer.byteLength(this.buffer) + Buffer.byteLength(chunk) > MAX_BUFFER_BYTES) {
|
|
17
|
+
console.warn("[transport] Buffer limit exceeded \u2014 closing socket to prevent DoS");
|
|
18
|
+
socket.destroy(new Error("Buffer limit exceeded"));
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
this.buffer += chunk.toString();
|
|
22
|
+
const lines = this.buffer.split("\n");
|
|
23
|
+
this.buffer = lines.pop() ?? "";
|
|
24
|
+
for (const line of lines) {
|
|
25
|
+
if (!line.trim()) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(line);
|
|
31
|
+
} catch {
|
|
32
|
+
console.warn("[transport] Malformed JSON, ignoring line");
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
this.onMessage(parsed);
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
reset() {
|
|
40
|
+
this.buffer = "";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
function writeNdjson(socket, msg) {
|
|
44
|
+
if (!socket.destroyed) {
|
|
45
|
+
socket.write(JSON.stringify(msg) + "\n");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/unix-socket.ts
|
|
50
|
+
var UnixSocketTransport = class {
|
|
51
|
+
constructor(socketPath) {
|
|
52
|
+
this.socketPath = socketPath;
|
|
53
|
+
}
|
|
54
|
+
server = null;
|
|
55
|
+
socket = null;
|
|
56
|
+
messageHandler = null;
|
|
57
|
+
async listen() {
|
|
58
|
+
try {
|
|
59
|
+
const { unlink } = await import('fs/promises');
|
|
60
|
+
await unlink(this.socketPath);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
this.server = net.createServer((conn) => this.handleConnection(conn));
|
|
64
|
+
await new Promise((resolve, reject) => {
|
|
65
|
+
this.server.listen(this.socketPath, resolve);
|
|
66
|
+
this.server.once("error", reject);
|
|
67
|
+
});
|
|
68
|
+
const { chmod } = await import('fs/promises');
|
|
69
|
+
await chmod(this.socketPath, 384);
|
|
70
|
+
}
|
|
71
|
+
async connect() {
|
|
72
|
+
await new Promise((resolve, reject) => {
|
|
73
|
+
this.socket = net.createConnection(this.socketPath, resolve);
|
|
74
|
+
this.socket.once("error", reject);
|
|
75
|
+
this.setupSocket(this.socket);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
send(msg) {
|
|
79
|
+
if (this.socket) {
|
|
80
|
+
writeNdjson(this.socket, msg);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
onMessage(handler) {
|
|
84
|
+
this.messageHandler = handler;
|
|
85
|
+
}
|
|
86
|
+
close() {
|
|
87
|
+
this.socket?.destroy();
|
|
88
|
+
this.socket = null;
|
|
89
|
+
this.server?.close();
|
|
90
|
+
this.server = null;
|
|
91
|
+
}
|
|
92
|
+
handleConnection(conn) {
|
|
93
|
+
this.socket = conn;
|
|
94
|
+
this.setupSocket(conn);
|
|
95
|
+
}
|
|
96
|
+
setupSocket(socket) {
|
|
97
|
+
const framer = new NdjsonFramer((msg) => this.messageHandler?.(msg));
|
|
98
|
+
socket.on("data", (chunk) => {
|
|
99
|
+
if (!framer.feed(socket, chunk)) {
|
|
100
|
+
socket.destroy();
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
socket.on("error", () => socket.destroy());
|
|
104
|
+
socket.on("close", () => {
|
|
105
|
+
framer.reset();
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
function pipePath(name) {
|
|
110
|
+
return `\\\\.\\pipe\\${name}`;
|
|
111
|
+
}
|
|
112
|
+
var NamedPipeTransport = class {
|
|
113
|
+
server = null;
|
|
114
|
+
socket = null;
|
|
115
|
+
messageHandler = null;
|
|
116
|
+
path;
|
|
117
|
+
constructor(pipeName) {
|
|
118
|
+
this.path = pipePath(pipeName);
|
|
119
|
+
}
|
|
120
|
+
async listen() {
|
|
121
|
+
this.server = net.createServer((conn) => this.handleConnection(conn));
|
|
122
|
+
await new Promise((resolve, reject) => {
|
|
123
|
+
this.server.listen(this.path, resolve);
|
|
124
|
+
this.server.once("error", reject);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async connect() {
|
|
128
|
+
await new Promise((resolve, reject) => {
|
|
129
|
+
this.socket = net.createConnection(this.path, resolve);
|
|
130
|
+
this.socket.once("error", reject);
|
|
131
|
+
this.setupSocket(this.socket);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
send(msg) {
|
|
135
|
+
if (this.socket) {
|
|
136
|
+
writeNdjson(this.socket, msg);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
onMessage(handler) {
|
|
140
|
+
this.messageHandler = handler;
|
|
141
|
+
}
|
|
142
|
+
close() {
|
|
143
|
+
this.socket?.destroy();
|
|
144
|
+
this.socket = null;
|
|
145
|
+
this.server?.close();
|
|
146
|
+
this.server = null;
|
|
147
|
+
}
|
|
148
|
+
handleConnection(conn) {
|
|
149
|
+
this.socket = conn;
|
|
150
|
+
this.setupSocket(conn);
|
|
151
|
+
}
|
|
152
|
+
setupSocket(socket) {
|
|
153
|
+
const framer = new NdjsonFramer((msg) => this.messageHandler?.(msg));
|
|
154
|
+
socket.on("data", (chunk) => {
|
|
155
|
+
if (!framer.feed(socket, chunk)) {
|
|
156
|
+
socket.destroy();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
socket.on("error", () => socket.destroy());
|
|
160
|
+
socket.on("close", () => {
|
|
161
|
+
framer.reset();
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var TcpTransport = class {
|
|
166
|
+
constructor(port, host = "127.0.0.1") {
|
|
167
|
+
this.port = port;
|
|
168
|
+
this.host = host;
|
|
169
|
+
}
|
|
170
|
+
server = null;
|
|
171
|
+
socket = null;
|
|
172
|
+
messageHandler = null;
|
|
173
|
+
async listen() {
|
|
174
|
+
this.server = net.createServer((conn) => this.handleConnection(conn));
|
|
175
|
+
await new Promise((resolve, reject) => {
|
|
176
|
+
this.server.listen(this.port, this.host, resolve);
|
|
177
|
+
this.server.once("error", reject);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async connect() {
|
|
181
|
+
await new Promise((resolve, reject) => {
|
|
182
|
+
this.socket = net.createConnection(this.port, this.host, resolve);
|
|
183
|
+
this.socket.once("error", reject);
|
|
184
|
+
this.setupSocket(this.socket);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
send(msg) {
|
|
188
|
+
if (this.socket) {
|
|
189
|
+
writeNdjson(this.socket, msg);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
onMessage(handler) {
|
|
193
|
+
this.messageHandler = handler;
|
|
194
|
+
}
|
|
195
|
+
close() {
|
|
196
|
+
this.socket?.destroy();
|
|
197
|
+
this.socket = null;
|
|
198
|
+
this.server?.close();
|
|
199
|
+
this.server = null;
|
|
200
|
+
}
|
|
201
|
+
handleConnection(conn) {
|
|
202
|
+
this.socket = conn;
|
|
203
|
+
this.setupSocket(conn);
|
|
204
|
+
}
|
|
205
|
+
setupSocket(socket) {
|
|
206
|
+
const framer = new NdjsonFramer((msg) => this.messageHandler?.(msg));
|
|
207
|
+
socket.on("data", (chunk) => {
|
|
208
|
+
if (!framer.feed(socket, chunk)) {
|
|
209
|
+
socket.destroy();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
socket.on("error", () => socket.destroy());
|
|
213
|
+
socket.on("close", () => {
|
|
214
|
+
framer.reset();
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var DEFAULT_SOCKET_PATH = join(homedir(), ".kb", "agent.sock");
|
|
219
|
+
var DEFAULT_PIPE_NAME = "kb-agent";
|
|
220
|
+
var DEFAULT_TCP_PORT = 7779;
|
|
221
|
+
var DEFAULT_TCP_HOST = "127.0.0.1";
|
|
222
|
+
function createTransport(config) {
|
|
223
|
+
const mode = config.mode === "auto" ? process.platform === "win32" ? "named-pipe" : "unix" : config.mode;
|
|
224
|
+
switch (mode) {
|
|
225
|
+
case "unix":
|
|
226
|
+
return new UnixSocketTransport(config.socketPath ?? DEFAULT_SOCKET_PATH);
|
|
227
|
+
case "named-pipe":
|
|
228
|
+
return new NamedPipeTransport(config.pipeName ?? DEFAULT_PIPE_NAME);
|
|
229
|
+
case "tcp":
|
|
230
|
+
return new TcpTransport(
|
|
231
|
+
config.port ?? DEFAULT_TCP_PORT,
|
|
232
|
+
config.host ?? DEFAULT_TCP_HOST
|
|
233
|
+
);
|
|
234
|
+
default:
|
|
235
|
+
throw new Error(`Unknown transport mode: ${String(mode)}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export { NamedPipeTransport, TcpTransport, UnixSocketTransport, createTransport };
|
|
240
|
+
//# sourceMappingURL=index.js.map
|
|
241
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ndjson-framer.ts","../src/unix-socket.ts","../src/named-pipe.ts","../src/tcp.ts","../src/auto.ts"],"names":["net"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * NdjsonFramer — shared NDJSON framing logic for all transport implementations.\n *\n * Accumulates chunks, splits on newlines, emits complete JSON messages.\n * Guards against oversized buffers (DoS protection).\n */\n\nimport type net from 'node:net';\n\nconst MAX_BUFFER_BYTES = 1024 * 1024; // 1 MB\n\nexport class NdjsonFramer {\n private buffer = '';\n\n constructor(private readonly onMessage: (msg: unknown) => void) {}\n\n /** Feed raw data from socket. Returns false if buffer limit exceeded (socket is closed). */\n feed(socket: net.Socket, chunk: Buffer | string): boolean {\n if (Buffer.byteLength(this.buffer) + Buffer.byteLength(chunk as string) > MAX_BUFFER_BYTES) {\n console.warn('[transport] Buffer limit exceeded — closing socket to prevent DoS');\n socket.destroy(new Error('Buffer limit exceeded'));\n return false;\n }\n this.buffer += chunk.toString();\n\n const lines = this.buffer.split('\\n');\n this.buffer = lines.pop() ?? '';\n\n for (const line of lines) {\n if (!line.trim()) { continue; }\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n console.warn('[transport] Malformed JSON, ignoring line');\n continue;\n }\n this.onMessage(parsed);\n }\n return true;\n }\n\n reset(): void {\n this.buffer = '';\n }\n}\n\nexport function writeNdjson(socket: net.Socket, msg: unknown): void {\n if (!socket.destroyed) {\n socket.write(JSON.stringify(msg) + '\\n');\n }\n}\n","/**\n * UnixSocketTransport — ILocalTransport over Unix domain socket.\n * Default on Linux/macOS.\n */\n\nimport net from 'node:net';\nimport { NdjsonFramer, writeNdjson } from './ndjson-framer.js';\nimport type { ILocalTransport } from './transport.js';\n\nexport class UnixSocketTransport implements ILocalTransport {\n private server: net.Server | null = null;\n private socket: net.Socket | null = null;\n private messageHandler: ((msg: unknown) => void) | null = null;\n\n constructor(private readonly socketPath: string) {}\n\n async listen(): Promise<void> {\n try {\n const { unlink } = await import('node:fs/promises');\n await unlink(this.socketPath);\n } catch { /* stale socket — fine */ }\n\n this.server = net.createServer((conn) => this.handleConnection(conn));\n await new Promise<void>((resolve, reject) => {\n this.server!.listen(this.socketPath, resolve);\n this.server!.once('error', reject);\n });\n\n const { chmod } = await import('node:fs/promises');\n await chmod(this.socketPath, 0o600);\n }\n\n async connect(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.socket = net.createConnection(this.socketPath, resolve);\n this.socket.once('error', reject);\n this.setupSocket(this.socket);\n });\n }\n\n send(msg: unknown): void {\n if (this.socket) { writeNdjson(this.socket, msg); }\n }\n\n onMessage(handler: (msg: unknown) => void): void {\n this.messageHandler = handler;\n }\n\n close(): void {\n this.socket?.destroy();\n this.socket = null;\n this.server?.close();\n this.server = null;\n }\n\n private handleConnection(conn: net.Socket): void {\n this.socket = conn;\n this.setupSocket(conn);\n }\n\n private setupSocket(socket: net.Socket): void {\n const framer = new NdjsonFramer((msg) => this.messageHandler?.(msg));\n socket.on('data', (chunk) => {\n if (!framer.feed(socket, chunk)) { socket.destroy(); }\n });\n socket.on('error', () => socket.destroy());\n socket.on('close', () => { framer.reset(); });\n }\n}\n","/**\n * NamedPipeTransport — ILocalTransport over Windows named pipe.\n * Default on Windows. Uses the same net.createServer API — Node.js\n * treats \\\\.\\pipe\\<name> paths as named pipes automatically.\n */\n\nimport net from 'node:net';\nimport { NdjsonFramer, writeNdjson } from './ndjson-framer.js';\nimport type { ILocalTransport } from './transport.js';\n\nfunction pipePath(name: string): string {\n return `\\\\\\\\.\\\\pipe\\\\${name}`;\n}\n\nexport class NamedPipeTransport implements ILocalTransport {\n private server: net.Server | null = null;\n private socket: net.Socket | null = null;\n private messageHandler: ((msg: unknown) => void) | null = null;\n private readonly path: string;\n\n constructor(pipeName: string) {\n this.path = pipePath(pipeName);\n }\n\n async listen(): Promise<void> {\n this.server = net.createServer((conn) => this.handleConnection(conn));\n await new Promise<void>((resolve, reject) => {\n this.server!.listen(this.path, resolve);\n this.server!.once('error', reject);\n });\n }\n\n async connect(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.socket = net.createConnection(this.path, resolve);\n this.socket.once('error', reject);\n this.setupSocket(this.socket);\n });\n }\n\n send(msg: unknown): void {\n if (this.socket) { writeNdjson(this.socket, msg); }\n }\n\n onMessage(handler: (msg: unknown) => void): void {\n this.messageHandler = handler;\n }\n\n close(): void {\n this.socket?.destroy();\n this.socket = null;\n this.server?.close();\n this.server = null;\n }\n\n private handleConnection(conn: net.Socket): void {\n this.socket = conn;\n this.setupSocket(conn);\n }\n\n private setupSocket(socket: net.Socket): void {\n const framer = new NdjsonFramer((msg) => this.messageHandler?.(msg));\n socket.on('data', (chunk) => {\n if (!framer.feed(socket, chunk)) { socket.destroy(); }\n });\n socket.on('error', () => socket.destroy());\n socket.on('close', () => { framer.reset(); });\n }\n}\n","/**\n * TcpTransport — ILocalTransport over TCP localhost.\n * Universal fallback: works on Linux, macOS, Windows without special paths.\n */\n\nimport net from 'node:net';\nimport { NdjsonFramer, writeNdjson } from './ndjson-framer.js';\nimport type { ILocalTransport } from './transport.js';\n\nexport class TcpTransport implements ILocalTransport {\n private server: net.Server | null = null;\n private socket: net.Socket | null = null;\n private messageHandler: ((msg: unknown) => void) | null = null;\n\n constructor(\n private readonly port: number,\n private readonly host = '127.0.0.1',\n ) {}\n\n async listen(): Promise<void> {\n this.server = net.createServer((conn) => this.handleConnection(conn));\n await new Promise<void>((resolve, reject) => {\n this.server!.listen(this.port, this.host, resolve);\n this.server!.once('error', reject);\n });\n }\n\n async connect(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.socket = net.createConnection(this.port, this.host, resolve);\n this.socket.once('error', reject);\n this.setupSocket(this.socket);\n });\n }\n\n send(msg: unknown): void {\n if (this.socket) { writeNdjson(this.socket, msg); }\n }\n\n onMessage(handler: (msg: unknown) => void): void {\n this.messageHandler = handler;\n }\n\n close(): void {\n this.socket?.destroy();\n this.socket = null;\n this.server?.close();\n this.server = null;\n }\n\n private handleConnection(conn: net.Socket): void {\n this.socket = conn;\n this.setupSocket(conn);\n }\n\n private setupSocket(socket: net.Socket): void {\n const framer = new NdjsonFramer((msg) => this.messageHandler?.(msg));\n socket.on('data', (chunk) => {\n if (!framer.feed(socket, chunk)) { socket.destroy(); }\n });\n socket.on('error', () => socket.destroy());\n socket.on('close', () => { framer.reset(); });\n }\n}\n","/**\n * createTransport — factory that picks the right ILocalTransport\n * based on config.mode (or platform when mode === 'auto').\n *\n * auto resolution:\n * Windows → NamedPipeTransport\n * Linux/Mac → UnixSocketTransport\n */\n\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ILocalTransport, TransportConfig } from './transport.js';\nimport { UnixSocketTransport } from './unix-socket.js';\nimport { NamedPipeTransport } from './named-pipe.js';\nimport { TcpTransport } from './tcp.js';\n\nconst DEFAULT_SOCKET_PATH = join(homedir(), '.kb', 'agent.sock');\nconst DEFAULT_PIPE_NAME = 'kb-agent';\nconst DEFAULT_TCP_PORT = 7779;\nconst DEFAULT_TCP_HOST = '127.0.0.1';\n\nexport function createTransport(config: TransportConfig): ILocalTransport {\n const mode = config.mode === 'auto'\n ? (process.platform === 'win32' ? 'named-pipe' : 'unix')\n : config.mode;\n\n switch (mode) {\n case 'unix':\n return new UnixSocketTransport(config.socketPath ?? DEFAULT_SOCKET_PATH);\n case 'named-pipe':\n return new NamedPipeTransport(config.pipeName ?? DEFAULT_PIPE_NAME);\n case 'tcp':\n return new TcpTransport(\n config.port ?? DEFAULT_TCP_PORT,\n config.host ?? DEFAULT_TCP_HOST,\n );\n default:\n throw new Error(`Unknown transport mode: ${String(mode)}`);\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/host-agent-transport",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"scripts": {
|
|
19
|
+
"clean": "rimraf dist",
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"type-check": "tsc --noEmit",
|
|
23
|
+
"lint": "eslint .",
|
|
24
|
+
"lint:fix": "eslint . --fix",
|
|
25
|
+
"test": "vitest run -c ../../vitest.config.ts",
|
|
26
|
+
"test:watch": "vitest -c ../../vitest.config.ts"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@kb-labs/devkit": "link:../../../kb-labs-devkit",
|
|
31
|
+
"@types/node": "^20",
|
|
32
|
+
"rimraf": "^6",
|
|
33
|
+
"tsup": "^8.5.0",
|
|
34
|
+
"vitest": "^3.2.4"
|
|
35
|
+
}
|
|
36
|
+
}
|