@zetaloop/chappie 0.2.0 → 0.3.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/README.md +27 -7
- package/docs/tools.md +55 -68
- package/package.json +3 -1
- package/src/broker.ts +240 -50
- package/src/config.ts +21 -0
- package/src/delivery.ts +27 -56
- package/src/index.ts +15 -10
- package/src/instructions.md +8 -4
- package/src/ipc.ts +144 -41
- package/src/provider.ts +3 -5
- package/src/question.html +384 -0
- package/src/questions.ts +95 -0
- package/src/server.ts +250 -69
- package/src/session.ts +131 -67
- package/src/state.ts +104 -9
- package/src/stdio.ts +11 -2
- package/src/tools.ts +6 -23
- package/src/transfer.ts +3 -3
package/src/ipc.ts
CHANGED
|
@@ -20,13 +20,15 @@ import type {
|
|
|
20
20
|
import type { DeliveryRecord } from "./delivery.ts";
|
|
21
21
|
import type { ResourceData } from "./resources.ts";
|
|
22
22
|
|
|
23
|
+
const defaultPort = 24274;
|
|
24
|
+
|
|
23
25
|
export type SessionStatus = "idle" | "ready" | "executing";
|
|
24
26
|
|
|
25
27
|
export interface SessionDescription {
|
|
26
28
|
id: string;
|
|
27
29
|
cwd: string;
|
|
30
|
+
device: string;
|
|
28
31
|
name?: string;
|
|
29
|
-
sessionFile?: string;
|
|
30
32
|
status: SessionStatus;
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -43,10 +45,15 @@ export interface SessionInput {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export type SessionResult =
|
|
46
|
-
| {
|
|
47
|
-
|
|
48
|
+
| {
|
|
49
|
+
inspection: SessionInspection;
|
|
50
|
+
inputs: SessionInput[];
|
|
51
|
+
globalAgents?: string;
|
|
52
|
+
}
|
|
53
|
+
| { message: AssistantMessage; cwd: string; inputs: SessionInput[] }
|
|
48
54
|
| {
|
|
49
55
|
message: AssistantMessage;
|
|
56
|
+
cwd: string;
|
|
50
57
|
toolResults: ToolResultMessage[];
|
|
51
58
|
inputs: SessionInput[];
|
|
52
59
|
}
|
|
@@ -62,6 +69,7 @@ export type SessionMessage =
|
|
|
62
69
|
export type BrokerMessage =
|
|
63
70
|
| { type: "synced"; id: number; sessionId: string }
|
|
64
71
|
| { type: "stored"; id: string }
|
|
72
|
+
| { type: "notice"; sessionId: string; message: string }
|
|
65
73
|
| { type: "inspect"; id: number; sessionId: string }
|
|
66
74
|
| {
|
|
67
75
|
type: "chat";
|
|
@@ -77,7 +85,7 @@ export type BrokerMessage =
|
|
|
77
85
|
sessionId: string;
|
|
78
86
|
calls: ToolCall[];
|
|
79
87
|
}
|
|
80
|
-
| { type: "cancel"; id: number; sessionId: string }
|
|
88
|
+
| { type: "cancel"; id: number; sessionId: string; reason: string }
|
|
81
89
|
| { type: "readResource"; id: number; sessionId: string; uri: string }
|
|
82
90
|
| { type: "ackInputs"; sessionId: string; ids: string[] };
|
|
83
91
|
|
|
@@ -171,7 +179,7 @@ export class IpcServer {
|
|
|
171
179
|
peer: JsonLinePeer<SessionMessage, BrokerMessage>,
|
|
172
180
|
) => void;
|
|
173
181
|
readonly #peers = new Set<JsonLinePeer<SessionMessage, BrokerMessage>>();
|
|
174
|
-
#
|
|
182
|
+
readonly #servers = new Set<Server>();
|
|
175
183
|
|
|
176
184
|
constructor(
|
|
177
185
|
agentDir: string,
|
|
@@ -186,10 +194,34 @@ export class IpcServer {
|
|
|
186
194
|
this.#onClose = onClose;
|
|
187
195
|
}
|
|
188
196
|
|
|
189
|
-
async start(): Promise<void> {
|
|
190
|
-
if (this.#
|
|
197
|
+
async start(network: boolean | number = false): Promise<void> {
|
|
198
|
+
if (this.#servers.size > 0) return;
|
|
191
199
|
if (process.platform !== "win32") await prepareUnixSocket(this.#endpoint);
|
|
192
|
-
|
|
200
|
+
try {
|
|
201
|
+
const local = this.#createServer();
|
|
202
|
+
await listenServer(local, this.#endpoint);
|
|
203
|
+
this.#servers.add(local);
|
|
204
|
+
if (!network) return;
|
|
205
|
+
|
|
206
|
+
const remote = this.#createServer();
|
|
207
|
+
await listenServer(remote, network === true ? defaultPort : network);
|
|
208
|
+
this.#servers.add(remote);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
await this.close();
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async close(): Promise<void> {
|
|
216
|
+
for (const peer of this.#peers) peer.close();
|
|
217
|
+
this.#peers.clear();
|
|
218
|
+
const servers = [...this.#servers];
|
|
219
|
+
this.#servers.clear();
|
|
220
|
+
await Promise.all(servers.map(closeServer));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#createServer(): Server {
|
|
224
|
+
return createServer((socket) => {
|
|
193
225
|
let peer: JsonLinePeer<SessionMessage, BrokerMessage>;
|
|
194
226
|
peer = new JsonLinePeer(
|
|
195
227
|
socket,
|
|
@@ -201,25 +233,6 @@ export class IpcServer {
|
|
|
201
233
|
);
|
|
202
234
|
this.#peers.add(peer);
|
|
203
235
|
});
|
|
204
|
-
await new Promise<void>((resolveListen, rejectListen) => {
|
|
205
|
-
server.once("error", rejectListen);
|
|
206
|
-
server.listen(this.#endpoint, () => {
|
|
207
|
-
server.off("error", rejectListen);
|
|
208
|
-
resolveListen();
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
this.#server = server;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async close(): Promise<void> {
|
|
215
|
-
for (const peer of this.#peers) peer.close();
|
|
216
|
-
this.#peers.clear();
|
|
217
|
-
const server = this.#server;
|
|
218
|
-
this.#server = undefined;
|
|
219
|
-
if (!server) return;
|
|
220
|
-
await new Promise<void>((resolveClose, rejectClose) =>
|
|
221
|
-
server.close((error) => (error ? rejectClose(error) : resolveClose())),
|
|
222
|
-
);
|
|
223
236
|
}
|
|
224
237
|
}
|
|
225
238
|
|
|
@@ -231,14 +244,21 @@ interface ConnectionCallbacks {
|
|
|
231
244
|
|
|
232
245
|
export class IpcClient {
|
|
233
246
|
readonly #endpoint: string;
|
|
247
|
+
readonly #remote: string | undefined;
|
|
234
248
|
readonly #callbacks: ConnectionCallbacks;
|
|
235
249
|
#peer: JsonLinePeer<BrokerMessage, SessionMessage> | undefined;
|
|
236
250
|
#opening: Promise<void> | undefined;
|
|
251
|
+
#controller: AbortController | undefined;
|
|
237
252
|
#retry: NodeJS.Timeout | undefined;
|
|
238
253
|
#closed = false;
|
|
239
254
|
|
|
240
|
-
constructor(
|
|
255
|
+
constructor(
|
|
256
|
+
agentDir: string,
|
|
257
|
+
remote: string | undefined,
|
|
258
|
+
callbacks: ConnectionCallbacks,
|
|
259
|
+
) {
|
|
241
260
|
this.#endpoint = ipcEndpoint(agentDir);
|
|
261
|
+
this.#remote = remote;
|
|
242
262
|
this.#callbacks = callbacks;
|
|
243
263
|
}
|
|
244
264
|
|
|
@@ -257,8 +277,11 @@ export class IpcClient {
|
|
|
257
277
|
if (this.#opening) return this.#opening;
|
|
258
278
|
clearTimeout(this.#retry);
|
|
259
279
|
this.#retry = undefined;
|
|
260
|
-
|
|
280
|
+
const controller = new AbortController();
|
|
281
|
+
this.#controller = controller;
|
|
282
|
+
this.#opening = this.#open(controller.signal).finally(() => {
|
|
261
283
|
this.#opening = undefined;
|
|
284
|
+
if (this.#controller === controller) this.#controller = undefined;
|
|
262
285
|
});
|
|
263
286
|
return this.#opening;
|
|
264
287
|
}
|
|
@@ -274,24 +297,25 @@ export class IpcClient {
|
|
|
274
297
|
this.#closed = true;
|
|
275
298
|
clearTimeout(this.#retry);
|
|
276
299
|
this.#retry = undefined;
|
|
300
|
+
this.#controller?.abort(new Error("Chappie IPC client is closed"));
|
|
301
|
+
this.#controller = undefined;
|
|
277
302
|
this.#peer?.close();
|
|
278
303
|
this.#peer = undefined;
|
|
279
304
|
}
|
|
280
305
|
|
|
281
|
-
async #open(): Promise<void> {
|
|
282
|
-
|
|
283
|
-
let failure = new Error("Chappie
|
|
284
|
-
|
|
285
|
-
socket
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
socket.destroy();
|
|
306
|
+
async #open(signal: AbortSignal): Promise<void> {
|
|
307
|
+
let socket: Socket | undefined;
|
|
308
|
+
let failure = new Error("Chappie disconnected");
|
|
309
|
+
try {
|
|
310
|
+
socket = this.#remote
|
|
311
|
+
? createConnection(networkEndpoint(this.#remote))
|
|
312
|
+
: createConnection(this.#endpoint);
|
|
313
|
+
await connectSocket(socket, signal);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
socket?.destroy();
|
|
292
316
|
this.#scheduleReconnect();
|
|
293
317
|
throw error;
|
|
294
|
-
}
|
|
318
|
+
}
|
|
295
319
|
|
|
296
320
|
let peer: JsonLinePeer<BrokerMessage, SessionMessage>;
|
|
297
321
|
peer = new JsonLinePeer(
|
|
@@ -326,6 +350,85 @@ export class IpcClient {
|
|
|
326
350
|
}
|
|
327
351
|
}
|
|
328
352
|
|
|
353
|
+
function listenServer(
|
|
354
|
+
server: Server,
|
|
355
|
+
endpoint: string | number,
|
|
356
|
+
): Promise<void> {
|
|
357
|
+
return new Promise<void>((resolveListen, rejectListen) => {
|
|
358
|
+
const onError = (error: Error): void => {
|
|
359
|
+
server.off("listening", onListening);
|
|
360
|
+
rejectListen(error);
|
|
361
|
+
};
|
|
362
|
+
const onListening = (): void => {
|
|
363
|
+
server.off("error", onError);
|
|
364
|
+
resolveListen();
|
|
365
|
+
};
|
|
366
|
+
server.once("error", onError);
|
|
367
|
+
server.once("listening", onListening);
|
|
368
|
+
server.listen(endpoint);
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function closeServer(server: Server): Promise<void> {
|
|
373
|
+
return new Promise<void>((resolveClose, rejectClose) =>
|
|
374
|
+
server.close((error) => (error ? rejectClose(error) : resolveClose())),
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
interface NetworkEndpoint {
|
|
379
|
+
host: string;
|
|
380
|
+
port: number;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function networkEndpoint(value: string): NetworkEndpoint {
|
|
384
|
+
const url = new URL(`tcp://${value}`);
|
|
385
|
+
if (url.username || url.password || url.pathname || url.search || url.hash)
|
|
386
|
+
throw new Error(`Invalid Chappie broker address: ${value}`);
|
|
387
|
+
const host = url.hostname.startsWith("[")
|
|
388
|
+
? url.hostname.slice(1, -1)
|
|
389
|
+
: url.hostname;
|
|
390
|
+
if (!host) throw new Error(`Invalid Chappie broker address: ${value}`);
|
|
391
|
+
return { host, port: url.port ? Number(url.port) : defaultPort };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function connectSocket(socket: Socket, signal: AbortSignal): Promise<void> {
|
|
395
|
+
return new Promise<void>((resolveConnect, rejectConnect) => {
|
|
396
|
+
const cleanup = (): void => {
|
|
397
|
+
signal.removeEventListener("abort", onAbort);
|
|
398
|
+
socket.off("connect", onConnect);
|
|
399
|
+
socket.off("error", onError);
|
|
400
|
+
};
|
|
401
|
+
const onConnect = (): void => {
|
|
402
|
+
cleanup();
|
|
403
|
+
resolveConnect();
|
|
404
|
+
};
|
|
405
|
+
const onError = (error: Error): void => {
|
|
406
|
+
cleanup();
|
|
407
|
+
rejectConnect(error);
|
|
408
|
+
};
|
|
409
|
+
const onAbort = (): void => {
|
|
410
|
+
cleanup();
|
|
411
|
+
socket.destroy();
|
|
412
|
+
rejectConnect(abortError(signal));
|
|
413
|
+
};
|
|
414
|
+
if (signal.aborted) {
|
|
415
|
+
onAbort();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
419
|
+
socket.once("connect", onConnect);
|
|
420
|
+
socket.once("error", onError);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function abortError(signal: AbortSignal): Error {
|
|
425
|
+
return signal.reason instanceof Error
|
|
426
|
+
? signal.reason
|
|
427
|
+
: new Error(
|
|
428
|
+
typeof signal.reason === "string" ? signal.reason : "Request cancelled",
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
329
432
|
async function prepareUnixSocket(endpoint: string): Promise<void> {
|
|
330
433
|
try {
|
|
331
434
|
await lstat(endpoint);
|
package/src/provider.ts
CHANGED
|
@@ -147,19 +147,17 @@ export class ProviderOutput {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
export function createChappieProvider(
|
|
150
|
-
start: (output: ProviderOutput
|
|
150
|
+
start: (output: ProviderOutput) => Promise<void>,
|
|
151
151
|
) {
|
|
152
152
|
const stream = (
|
|
153
153
|
model: Model<Api>,
|
|
154
|
-
|
|
154
|
+
_context: Context,
|
|
155
155
|
options?: StreamOptions,
|
|
156
156
|
) => {
|
|
157
157
|
const output = new ProviderOutput(model, options?.signal);
|
|
158
158
|
if (!output.closed) {
|
|
159
159
|
queueMicrotask(() => {
|
|
160
|
-
void start(output
|
|
161
|
-
output.fail(error),
|
|
162
|
-
);
|
|
160
|
+
void start(output).catch((error: unknown) => output.fail(error));
|
|
163
161
|
});
|
|
164
162
|
}
|
|
165
163
|
return output.stream;
|