@elpapi42/pi-fleet-sdk 0.2.0-beta.14 → 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/README.md +14 -256
- package/dist/agent.d.ts +13 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +20 -0
- package/dist/agent.js.map +1 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +163 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +5 -174
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -0
- package/dist/internal/paths.d.ts +6 -0
- package/dist/internal/paths.d.ts.map +1 -0
- package/dist/internal/paths.js +26 -0
- package/dist/internal/paths.js.map +1 -0
- package/dist/internal/pi-rpc.d.ts +13 -0
- package/dist/internal/pi-rpc.d.ts.map +1 -0
- package/dist/internal/pi-rpc.js +105 -0
- package/dist/internal/pi-rpc.js.map +1 -0
- package/dist/internal/protocol.d.ts +23 -0
- package/dist/internal/protocol.d.ts.map +1 -0
- package/dist/internal/protocol.js +7 -0
- package/dist/internal/protocol.js.map +1 -0
- package/dist/internal/registry.d.ts +46 -0
- package/dist/internal/registry.d.ts.map +1 -0
- package/dist/internal/registry.js +118 -0
- package/dist/internal/registry.js.map +1 -0
- package/dist/internal/worker-client.d.ts +4 -0
- package/dist/internal/worker-client.d.ts.map +1 -0
- package/dist/internal/worker-client.js +60 -0
- package/dist/internal/worker-client.js.map +1 -0
- package/dist/internal/worker-launcher.d.ts +3 -0
- package/dist/internal/worker-launcher.d.ts.map +1 -0
- package/dist/internal/worker-launcher.js +13 -0
- package/dist/internal/worker-launcher.js.map +1 -0
- package/dist/internal/worker.d.ts +2 -0
- package/dist/internal/worker.d.ts.map +1 -0
- package/dist/internal/worker.js +114 -0
- package/dist/internal/worker.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/package.json +16 -32
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -1,269 +1,27 @@
|
|
|
1
1
|
# @elpapi42/pi-fleet-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Create and discover durable, host-local Pi agents.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Requirements
|
|
8
|
-
|
|
9
|
-
- Node.js `^22.19.0 || ^24.0.0`
|
|
10
|
-
- A compatible `pi-fleet-daemon` installed and active for the current OS user
|
|
11
|
-
|
|
12
|
-
Install the SDK separately from the daemon:
|
|
13
|
-
|
|
14
|
-
```sh
|
|
5
|
+
```bash
|
|
15
6
|
npm install @elpapi42/pi-fleet-sdk
|
|
16
7
|
```
|
|
17
8
|
|
|
18
|
-
## Connect and create an agent
|
|
19
|
-
|
|
20
|
-
```ts
|
|
21
|
-
import { connectPiFleet, PiFleetError } from "@elpapi42/pi-fleet-sdk";
|
|
22
|
-
|
|
23
|
-
let client;
|
|
24
|
-
try {
|
|
25
|
-
client = await connectPiFleet();
|
|
26
|
-
} catch (error) {
|
|
27
|
-
if (error instanceof PiFleetError) {
|
|
28
|
-
switch (error.code) {
|
|
29
|
-
case "daemon_unavailable":
|
|
30
|
-
// The daemon endpoint is absent or unavailable.
|
|
31
|
-
break;
|
|
32
|
-
case "endpoint_unsafe":
|
|
33
|
-
// The endpoint failed ownership, mode, type, or identity checks.
|
|
34
|
-
break;
|
|
35
|
-
case "daemon_rejected":
|
|
36
|
-
case "protocol_incompatible":
|
|
37
|
-
// The installed daemon cannot accept this SDK connection.
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
throw error;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const reviewer = await client.create({
|
|
46
|
-
name: "reviewer",
|
|
47
|
-
cwd: "/workspace/project",
|
|
48
|
-
instructions: "Review the current changes.",
|
|
49
|
-
piArgs: ["--model", "provider/model"],
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
console.log(reviewer.name, reviewer.id);
|
|
53
|
-
} finally {
|
|
54
|
-
await client.close();
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
`connectPiFleet()` performs a passive daemon handshake. It sends no agent operation and never starts a missing daemon.
|
|
59
|
-
|
|
60
|
-
`piArgs` are exact Pi passthrough tokens. They can contain secrets. The SDK sends them unchanged and does not log them. The daemon stores them in private local state for session restoration. Do not copy them into application logs or diagnostics.
|
|
61
|
-
|
|
62
|
-
## Get and list agents
|
|
63
|
-
|
|
64
9
|
```ts
|
|
65
|
-
|
|
66
|
-
const summaries = await client.list();
|
|
67
|
-
|
|
68
|
-
for (const summary of summaries) {
|
|
69
|
-
console.log(summary.name, summary.id, summary.state, summary.process.state);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const current = await reviewer.status();
|
|
73
|
-
console.log(current.state);
|
|
74
|
-
```
|
|
10
|
+
import { connectPiFleet } from "@elpapi42/pi-fleet-sdk"
|
|
75
11
|
|
|
76
|
-
|
|
12
|
+
const client = await connectPiFleet()
|
|
13
|
+
const agent = await client.create({
|
|
14
|
+
name: "researcher",
|
|
15
|
+
cwd: process.cwd(),
|
|
16
|
+
instructions: "Give concise answers.",
|
|
17
|
+
})
|
|
77
18
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`get()` throws `PiFleetError` with code `agent_not_found` when the name does not exist.
|
|
81
|
-
|
|
82
|
-
## Send instructions
|
|
83
|
-
|
|
84
|
-
Steering is the default delivery mode:
|
|
85
|
-
|
|
86
|
-
```ts
|
|
87
|
-
const receipt = await reviewer.send("Check the public error boundary.");
|
|
88
|
-
console.log(receipt.acceptedAt);
|
|
19
|
+
console.log(await agent.status())
|
|
20
|
+
await client.close()
|
|
89
21
|
```
|
|
90
22
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
await reviewer.send("Run the focused tests next.", {
|
|
95
|
-
delivery: "followUp",
|
|
96
|
-
});
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`send()` reports acceptance only. It does not return an assistant response or correlate a later response with one send. Use `receive()` to observe agent activity.
|
|
100
|
-
|
|
101
|
-
## Receive semantic events
|
|
102
|
-
|
|
103
|
-
`receive()` is a passive continuous stream. It does not start or restore Pi. The default stream begins at its live attachment boundary. The stream remains open across idle periods, Pi restoration, and recoverable daemon restarts.
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
const stream = await reviewer.receive();
|
|
107
|
-
|
|
108
|
-
// The initial cursor is available before the first event.
|
|
109
|
-
await saveCursor(stream.cursor);
|
|
110
|
-
|
|
111
|
-
for await (const event of stream) {
|
|
112
|
-
console.log(event.observedAt, event.type, event.id);
|
|
113
|
-
|
|
114
|
-
if (event.type === "assistant.message.finished") {
|
|
115
|
-
console.log(event.text);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
The SDK emits these six event types:
|
|
121
|
-
|
|
122
|
-
- `assistant.thinking.started`
|
|
123
|
-
- `assistant.thinking.finished`
|
|
124
|
-
- `assistant.message.started`
|
|
125
|
-
- `assistant.message.finished`
|
|
126
|
-
- `tool.execution.started`
|
|
127
|
-
- `tool.execution.finished`
|
|
128
|
-
|
|
129
|
-
The stream emits semantic lifecycle start and finish events, not raw Pi RPC records or text deltas. An observed start can remain unmatched after interruption. Each event includes an event ID, activity ID, agent ID, cursor, observation epoch, raw-record position, and observation time.
|
|
130
|
-
|
|
131
|
-
Each stream permits one consumer. Call `receive()` again to create an independent subscription.
|
|
132
|
-
|
|
133
|
-
### Start modes
|
|
134
|
-
|
|
135
|
-
```ts
|
|
136
|
-
// Live events from this attachment boundary.
|
|
137
|
-
const live = await reviewer.receive();
|
|
138
|
-
|
|
139
|
-
// All retained events from the start.
|
|
140
|
-
const history = await reviewer.receive({ fromStart: true });
|
|
141
|
-
|
|
142
|
-
// Events after an opaque cursor previously returned by this agent's stream.
|
|
143
|
-
const resumed = await reviewer.receive({ after: savedCursor });
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
`after` and `fromStart` are mutually exclusive. Treat cursors as opaque values. Store and restore only exact cursors returned by the SDK for the same agent generation.
|
|
147
|
-
|
|
148
|
-
### Checkpoint and deduplicate
|
|
149
|
-
|
|
150
|
-
Receive reconnection is at least once. A resumed stream can emit an event ID that the application already processed.
|
|
151
|
-
|
|
152
|
-
For durable consumers:
|
|
153
|
-
|
|
154
|
-
1. Save `stream.cursor` after attachment and before reading the first event.
|
|
155
|
-
2. Deduplicate events by `event.id`.
|
|
156
|
-
3. Process an event and save `event.cursor` in one application transaction when possible.
|
|
157
|
-
4. Resume with `receive({ after: savedCursor })`.
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
for await (const event of stream) {
|
|
161
|
-
if (await hasProcessedEvent(event.id)) {
|
|
162
|
-
await saveCursor(event.cursor);
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
await processAndCheckpoint(event, event.cursor);
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## Handle observation uncertainty
|
|
171
|
-
|
|
172
|
-
The SDK reconnects after recoverable daemon loss from the last safe cursor. It never crosses a known observation gap automatically.
|
|
173
|
-
|
|
174
|
-
If continuity is uncertain, the stream throws `PiFleetError` with code `observation_uncertain`. The error includes `lastSafeCursor` and can include `continuationCursor`. Continue only after the application explicitly accepts the gap.
|
|
175
|
-
|
|
176
|
-
```ts
|
|
177
|
-
import { PiFleetError, type Agent, type ReceiveStream } from "@elpapi42/pi-fleet-sdk";
|
|
178
|
-
|
|
179
|
-
async function consume(agent: Agent, initial: ReceiveStream): Promise<void> {
|
|
180
|
-
let stream = initial;
|
|
181
|
-
|
|
182
|
-
while (true) {
|
|
183
|
-
try {
|
|
184
|
-
await saveCursor(stream.cursor);
|
|
185
|
-
for await (const event of stream) {
|
|
186
|
-
await processAndCheckpoint(event, event.cursor);
|
|
187
|
-
}
|
|
188
|
-
return;
|
|
189
|
-
} catch (error) {
|
|
190
|
-
if (!(error instanceof PiFleetError) || error.code !== "observation_uncertain") {
|
|
191
|
-
throw error;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const continuation = error.details?.continuationCursor;
|
|
195
|
-
if (continuation === undefined) throw error;
|
|
196
|
-
|
|
197
|
-
await acceptObservationGap({
|
|
198
|
-
lastSafeCursor: error.details?.lastSafeCursor,
|
|
199
|
-
continuationCursor: continuation,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
stream = await agent.receive({ after: continuation });
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
The application owns the gap decision. Do not use `continuationCursor` as an automatic retry cursor.
|
|
209
|
-
|
|
210
|
-
## Cancellation and close
|
|
211
|
-
|
|
212
|
-
Pass an `AbortSignal` to connection, finite operations, sends, or receive streams:
|
|
213
|
-
|
|
214
|
-
```ts
|
|
215
|
-
const controller = new AbortController();
|
|
216
|
-
|
|
217
|
-
const stream = await reviewer.receive({ signal: controller.signal });
|
|
218
|
-
const consuming = (async () => {
|
|
219
|
-
for await (const event of stream) {
|
|
220
|
-
await processEvent(event);
|
|
221
|
-
}
|
|
222
|
-
})();
|
|
223
|
-
|
|
224
|
-
controller.abort();
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
await consuming;
|
|
228
|
-
} catch (error) {
|
|
229
|
-
if (!(error instanceof PiFleetError) || error.code !== "cancelled") throw error;
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
Caller cancellation uses the `cancelled` code. Work cancelled by `client.close()` uses `client_closed`.
|
|
234
|
-
|
|
235
|
-
`client.close()` is idempotent and local. It cancels this client's pending requests, receive streams, connection attempts, and reconnect delays. It does not stop the daemon, destroy agents, stop Pi, or change shared state.
|
|
236
|
-
|
|
237
|
-
```ts
|
|
238
|
-
try {
|
|
239
|
-
// Use the client.
|
|
240
|
-
} finally {
|
|
241
|
-
await client.close();
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Error boundary
|
|
246
|
-
|
|
247
|
-
All public SDK failures are `PiFleetError` instances with a closed `code` union and fixed content-safe messages. The SDK does not expose daemon messages, filesystem paths, Pi arguments, prompts, raw protocol records, or nested internal errors.
|
|
248
|
-
|
|
249
|
-
Useful connection and lifecycle codes include:
|
|
250
|
-
|
|
251
|
-
- `daemon_unavailable`: the canonical daemon endpoint is unavailable
|
|
252
|
-
- `endpoint_unsafe`: endpoint trust checks failed
|
|
253
|
-
- `daemon_rejected`: the daemon rejected the SDK handshake
|
|
254
|
-
- `protocol_incompatible`: protocol or required capabilities are incompatible
|
|
255
|
-
- `timeout`: connection or handshake timed out
|
|
256
|
-
- `runtime_unavailable`: an established daemon connection was lost
|
|
257
|
-
- `cancelled`: the caller cancelled the operation
|
|
258
|
-
- `client_closed`: `client.close()` cancelled local work
|
|
259
|
-
- `observation_uncertain`: receive continuity has a known gap
|
|
260
|
-
|
|
261
|
-
## SDK boundary
|
|
262
|
-
|
|
263
|
-
The SDK has no public daemon-management API. Install and manage `pi-fleet-daemon` separately.
|
|
264
|
-
|
|
265
|
-
Neither the current SDK nor the proposed CLI exposes `untilIdle`. The SDK uses continuous receive streams with explicit cursors. Callers decide when to stop consuming events without treating that choice as agent task completion.
|
|
23
|
+
`instructions` adds durable system instructions. It does not send initial work.
|
|
266
24
|
|
|
267
|
-
|
|
25
|
+
`client.get(name)` and `client.list()` discover agents created by another local client. `client.close()` only closes this SDK client. It does not stop an agent worker.
|
|
268
26
|
|
|
269
|
-
|
|
27
|
+
Slice 1 supports Unix-like hosts with ZeroMQ `ipc://` support. It provides create, get, list, and status. Sending work, event streaming, recovery, retirement, compact, and destroy come in later slices.
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Agent, AgentStatus } from "./types.js";
|
|
2
|
+
type AgentClient = {
|
|
3
|
+
status(id: string, name: string): Promise<AgentStatus>;
|
|
4
|
+
};
|
|
5
|
+
export declare class AgentHandle implements Agent {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(client: AgentClient, id: string, name: string);
|
|
8
|
+
get id(): string;
|
|
9
|
+
get name(): string;
|
|
10
|
+
status(): Promise<AgentStatus>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAEpD,KAAK,WAAW,GAAG;IACjB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CACvD,CAAA;AAED,qBAAa,WAAY,YAAW,KAAK;;gBAK3B,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAMzD,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,MAAM,IAAI,OAAO,CAAC,WAAW,CAAC;CAG/B"}
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class AgentHandle {
|
|
2
|
+
#client;
|
|
3
|
+
#id;
|
|
4
|
+
#name;
|
|
5
|
+
constructor(client, id, name) {
|
|
6
|
+
this.#client = client;
|
|
7
|
+
this.#id = id;
|
|
8
|
+
this.#name = name;
|
|
9
|
+
}
|
|
10
|
+
get id() {
|
|
11
|
+
return this.#id;
|
|
12
|
+
}
|
|
13
|
+
get name() {
|
|
14
|
+
return this.#name;
|
|
15
|
+
}
|
|
16
|
+
status() {
|
|
17
|
+
return this.#client.status(this.#id, this.#name);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,WAAW;IACb,OAAO,CAAa;IACpB,GAAG,CAAQ;IACX,KAAK,CAAQ;IAEtB,YAAY,MAAmB,EAAE,EAAU,EAAE,IAAY;QACvD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAA;QACrB,IAAI,CAAC,GAAG,GAAG,EAAE,CAAA;QACb,IAAI,CAAC,KAAK,GAAG,IAAI,CAAA;IACnB,CAAC;IAED,IAAI,EAAE;QACJ,OAAO,IAAI,CAAC,GAAG,CAAA;IACjB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;IAClD,CAAC;CACF"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAMA,OAAO,EAAuE,KAAK,cAAc,EAA2B,KAAK,aAAa,EAAE,MAAM,YAAY,CAAA;AAsFlK,wBAAsB,cAAc,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAIzF"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { capability } from "zeromq";
|
|
3
|
+
import { stat } from "node:fs/promises";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { AgentHandle } from "./agent.js";
|
|
6
|
+
import { AgentNotFoundError } from "./types.js";
|
|
7
|
+
import { resolveStateDir, workerEndpoint } from "./internal/paths.js";
|
|
8
|
+
import { openRegistry } from "./internal/registry.js";
|
|
9
|
+
import { launchWorker } from "./internal/worker-launcher.js";
|
|
10
|
+
import { requestStatus } from "./internal/worker-client.js";
|
|
11
|
+
const STARTUP_TIMEOUT_MS = 10_000;
|
|
12
|
+
const OWNED_PI_OPTIONS = new Set(["--mode", "--session", "--session-id", "--no-session"]);
|
|
13
|
+
class PiFleetClientImpl {
|
|
14
|
+
#registry;
|
|
15
|
+
#stateDir;
|
|
16
|
+
#closed = false;
|
|
17
|
+
constructor(registry, stateDir) {
|
|
18
|
+
this.#registry = registry;
|
|
19
|
+
this.#stateDir = stateDir;
|
|
20
|
+
}
|
|
21
|
+
async create(options) {
|
|
22
|
+
this.assertOpen();
|
|
23
|
+
const input = await validateCreateOptions(options);
|
|
24
|
+
const id = randomUUID();
|
|
25
|
+
const generation = randomUUID();
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
const record = {
|
|
28
|
+
id,
|
|
29
|
+
name: input.name,
|
|
30
|
+
cwd: input.cwd,
|
|
31
|
+
instructions: input.instructions,
|
|
32
|
+
piArgs: input.piArgs,
|
|
33
|
+
state: "starting",
|
|
34
|
+
runtime: {
|
|
35
|
+
generation,
|
|
36
|
+
state: "starting",
|
|
37
|
+
endpoint: workerEndpoint(this.#stateDir, id, generation),
|
|
38
|
+
},
|
|
39
|
+
lastEventSeq: 0,
|
|
40
|
+
createdAt: now,
|
|
41
|
+
updatedAt: now,
|
|
42
|
+
};
|
|
43
|
+
await this.#registry.create(record);
|
|
44
|
+
let worker;
|
|
45
|
+
try {
|
|
46
|
+
worker = launchWorker(this.#stateDir, id, generation);
|
|
47
|
+
await waitForWorkerReady(record, worker, STARTUP_TIMEOUT_MS);
|
|
48
|
+
return new AgentHandle(this, id, input.name);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
await stopWorker(worker);
|
|
52
|
+
await this.#registry.rollbackCreation(id, input.name, generation);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async get(name) {
|
|
57
|
+
this.assertOpen();
|
|
58
|
+
const record = this.#registry.getByName(name);
|
|
59
|
+
if (!record)
|
|
60
|
+
throw new AgentNotFoundError(name);
|
|
61
|
+
return new AgentHandle(this, record.id, record.name);
|
|
62
|
+
}
|
|
63
|
+
async list() {
|
|
64
|
+
this.assertOpen();
|
|
65
|
+
return this.#registry.list();
|
|
66
|
+
}
|
|
67
|
+
async status(id, name) {
|
|
68
|
+
this.assertOpen();
|
|
69
|
+
const record = this.#registry.getById(id);
|
|
70
|
+
if (!record || record.name !== name)
|
|
71
|
+
throw new AgentNotFoundError(name);
|
|
72
|
+
const status = await requestStatus(record);
|
|
73
|
+
return { id: status.id, name: status.name, state: status.state };
|
|
74
|
+
}
|
|
75
|
+
close() {
|
|
76
|
+
if (this.#closed)
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
this.#closed = true;
|
|
79
|
+
return this.#registry.close();
|
|
80
|
+
}
|
|
81
|
+
assertOpen() {
|
|
82
|
+
if (this.#closed)
|
|
83
|
+
throw new Error("The pi-fleet client is closed");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export async function connectPiFleet(options = {}) {
|
|
87
|
+
if (!capability.ipc)
|
|
88
|
+
throw new Error("pi-fleet requires ZeroMQ ipc:// support on this host");
|
|
89
|
+
const stateDir = resolveStateDir(options);
|
|
90
|
+
return new PiFleetClientImpl(await openRegistry(stateDir), stateDir);
|
|
91
|
+
}
|
|
92
|
+
async function validateCreateOptions(options) {
|
|
93
|
+
const name = options.name?.trim();
|
|
94
|
+
if (!name)
|
|
95
|
+
throw new TypeError("Agent name must not be empty");
|
|
96
|
+
if (name.includes("\0"))
|
|
97
|
+
throw new TypeError("Agent name must not contain a null byte");
|
|
98
|
+
if (!options.cwd)
|
|
99
|
+
throw new TypeError("Agent cwd is required");
|
|
100
|
+
const cwd = resolve(options.cwd);
|
|
101
|
+
if (!(await stat(cwd)).isDirectory())
|
|
102
|
+
throw new TypeError(`Agent cwd is not a directory: ${cwd}`);
|
|
103
|
+
const piArgs = options.piArgs ? [...options.piArgs] : [];
|
|
104
|
+
for (const arg of piArgs) {
|
|
105
|
+
if (OWNED_PI_OPTIONS.has(arg) || [...OWNED_PI_OPTIONS].some((option) => arg.startsWith(`${option}=`))) {
|
|
106
|
+
throw new TypeError(`Pi argument ${JSON.stringify(arg)} is managed by pi-fleet`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { name, cwd, instructions: options.instructions, piArgs };
|
|
110
|
+
}
|
|
111
|
+
async function waitForWorkerReady(record, worker, timeoutMs) {
|
|
112
|
+
let onExit = () => { };
|
|
113
|
+
const exited = new Promise((_, reject) => {
|
|
114
|
+
onExit = (code, signal) => reject(new Error(`Worker exited before readiness (${signal ?? code ?? "unknown"})`));
|
|
115
|
+
worker.once("exit", onExit);
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
await Promise.race([waitForStatus(record, timeoutMs), exited]);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
worker.off("exit", onExit);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function stopWorker(worker) {
|
|
125
|
+
if (!worker || worker.exitCode !== null || worker.signalCode !== null)
|
|
126
|
+
return;
|
|
127
|
+
worker.kill("SIGTERM");
|
|
128
|
+
if (await waitForExit(worker, 1_000))
|
|
129
|
+
return;
|
|
130
|
+
worker.kill("SIGKILL");
|
|
131
|
+
await waitForExit(worker, 1_000);
|
|
132
|
+
}
|
|
133
|
+
async function waitForExit(worker, timeoutMs) {
|
|
134
|
+
if (worker.exitCode !== null || worker.signalCode !== null)
|
|
135
|
+
return true;
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
const timeout = setTimeout(() => {
|
|
138
|
+
worker.off("exit", onExit);
|
|
139
|
+
resolve(false);
|
|
140
|
+
}, timeoutMs);
|
|
141
|
+
const onExit = () => {
|
|
142
|
+
clearTimeout(timeout);
|
|
143
|
+
resolve(true);
|
|
144
|
+
};
|
|
145
|
+
worker.once("exit", onExit);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async function waitForStatus(record, timeoutMs) {
|
|
149
|
+
const end = Date.now() + timeoutMs;
|
|
150
|
+
let lastError;
|
|
151
|
+
while (Date.now() < end) {
|
|
152
|
+
try {
|
|
153
|
+
await requestStatus(record, Math.min(500, end - Date.now()));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
lastError = error;
|
|
158
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
throw lastError instanceof Error ? lastError : new Error("Worker did not become ready");
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|