@automatalabs/pi-acp 0.1.3 → 0.2.1
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 +24 -3
- package/dist/agent.d.ts +11 -7
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +374 -60
- package/dist/child-process-registry.d.ts +82 -0
- package/dist/child-process-registry.d.ts.map +1 -0
- package/dist/child-process-registry.js +408 -0
- package/dist/config.d.ts +14 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +54 -10
- package/dist/deps.d.ts +3 -3
- package/dist/deps.d.ts.map +1 -1
- package/dist/deps.js +2 -2
- package/dist/errors.d.ts +17 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +10 -5
- package/dist/index.js +5 -6
- package/dist/mcp-bridge.d.ts +235 -17
- package/dist/mcp-bridge.d.ts.map +1 -1
- package/dist/mcp-bridge.js +1331 -144
- package/dist/mcp-sampling-payload.d.ts +25 -0
- package/dist/mcp-sampling-payload.d.ts.map +1 -0
- package/dist/mcp-sampling-payload.js +263 -0
- package/dist/session.d.ts +27 -9
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +301 -145
- package/dist/translate.d.ts.map +1 -1
- package/dist/translate.js +10 -6
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/package.json +3 -3
- package/dist/structured-output.d.ts +0 -14
- package/dist/structured-output.d.ts.map +0 -1
- package/dist/structured-output.js +0 -71
package/dist/session.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import { methods, } from "@agentclientprotocol/sdk";
|
|
2
2
|
import { adapterError, classifyPreflight, isRequestError, unexpectedError } from "./errors.js";
|
|
3
|
-
import { applyConfig, thinkingLevelOption } from "./config.js";
|
|
3
|
+
import { applyConfig, modelOption, thinkingLevelOption } from "./config.js";
|
|
4
4
|
import { convertPromptContent } from "./prompt-content.js";
|
|
5
5
|
import { replayEntry } from "./replay.js";
|
|
6
6
|
import { stopReasonFor } from "./stop-reason.js";
|
|
7
7
|
import { translateEvent } from "./translate.js";
|
|
8
8
|
import { agentMessages, promptUsage, terminalAssistant, usageUpdate, } from "./usage.js";
|
|
9
9
|
import { installPermissionWrapper } from "./permissions.js";
|
|
10
|
-
import {
|
|
10
|
+
import { ChildCleanupFailure } from "./child-process-registry.js";
|
|
11
11
|
export class PiSession {
|
|
12
12
|
sessionId;
|
|
13
13
|
pi;
|
|
14
14
|
manager;
|
|
15
15
|
client;
|
|
16
16
|
deps;
|
|
17
|
-
|
|
17
|
+
mcpBridge;
|
|
18
18
|
failedMcpResults;
|
|
19
|
-
|
|
19
|
+
availableModels;
|
|
20
|
+
childRegistry;
|
|
21
|
+
lifecycleController;
|
|
20
22
|
onWedged;
|
|
21
23
|
pending = [];
|
|
22
24
|
pump;
|
|
@@ -26,15 +28,22 @@ export class PiSession {
|
|
|
26
28
|
disposed = false;
|
|
27
29
|
unsubscribe;
|
|
28
30
|
activeTurn;
|
|
31
|
+
configReserved = false;
|
|
32
|
+
cleanupDirty = false;
|
|
33
|
+
cleanupGeneration;
|
|
34
|
+
resourceDisposePromise;
|
|
35
|
+
bridgeClosePromise;
|
|
29
36
|
constructor(options) {
|
|
30
37
|
this.sessionId = options.sessionId;
|
|
31
38
|
this.pi = options.session;
|
|
32
39
|
this.manager = options.manager;
|
|
33
40
|
this.client = options.client;
|
|
34
41
|
this.deps = options.deps;
|
|
35
|
-
this.
|
|
42
|
+
this.mcpBridge = options.mcpBridge;
|
|
36
43
|
this.failedMcpResults = options.failedMcpResults;
|
|
37
|
-
this.
|
|
44
|
+
this.availableModels = options.availableModels;
|
|
45
|
+
this.childRegistry = options.childRegistry;
|
|
46
|
+
this.lifecycleController = options.lifecycleController;
|
|
38
47
|
this.onWedged = options.onWedged;
|
|
39
48
|
installPermissionWrapper(this.pi, {
|
|
40
49
|
sessionId: this.sessionId,
|
|
@@ -66,10 +75,24 @@ export class PiSession {
|
|
|
66
75
|
});
|
|
67
76
|
}
|
|
68
77
|
get busy() {
|
|
69
|
-
return this.activeTurn !== undefined || this.closing;
|
|
78
|
+
return this.activeTurn !== undefined || this.configReserved || this.closing;
|
|
70
79
|
}
|
|
71
80
|
configOptions() {
|
|
72
|
-
return [thinkingLevelOption(this.pi)];
|
|
81
|
+
return [thinkingLevelOption(this.pi), modelOption(this.pi, this.availableModels)];
|
|
82
|
+
}
|
|
83
|
+
publishAvailableModels(models) {
|
|
84
|
+
this.availableModels = [...models];
|
|
85
|
+
}
|
|
86
|
+
activeTurnSignal() { return this.activeTurn?.controller.signal; }
|
|
87
|
+
emitMcpDiagnostic(text) {
|
|
88
|
+
if (this.disposed)
|
|
89
|
+
return;
|
|
90
|
+
if (this.activeTurn && !this.activeTurn.completed && this.activeTurn.diagnosticOpen) {
|
|
91
|
+
this.enqueue({ sessionUpdate: "agent_thought_chunk", content: { type: "text", text } });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.error(text);
|
|
95
|
+
}
|
|
73
96
|
}
|
|
74
97
|
enqueue(update) {
|
|
75
98
|
if (this.stopped)
|
|
@@ -125,34 +148,44 @@ export class PiSession {
|
|
|
125
148
|
async setConfig(configId, value) {
|
|
126
149
|
if (this.busy)
|
|
127
150
|
throw adapterError("session_busy");
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return;
|
|
151
|
+
// Reserve synchronously before the corrective catalog refresh. Prompt,
|
|
152
|
+
// config, fork, and refresh commit all share the same admission boundary.
|
|
153
|
+
this.configReserved = true;
|
|
154
|
+
let release;
|
|
133
155
|
try {
|
|
134
|
-
this.
|
|
156
|
+
release = await this.mcpBridge.acquireTurnBoundary();
|
|
157
|
+
if (this.closing)
|
|
158
|
+
throw adapterError("session_busy");
|
|
159
|
+
const result = await applyConfig(this.pi, this.deps.modelRuntime, this.availableModels, configId, value);
|
|
160
|
+
this.availableModels = result.availableModels;
|
|
161
|
+
return result.configOptions;
|
|
135
162
|
}
|
|
136
|
-
|
|
137
|
-
|
|
163
|
+
finally {
|
|
164
|
+
release?.();
|
|
165
|
+
this.configReserved = false;
|
|
138
166
|
}
|
|
139
167
|
}
|
|
140
|
-
finish(turn, outcome
|
|
168
|
+
finish(turn, outcome) {
|
|
141
169
|
if (turn.completed)
|
|
142
170
|
return;
|
|
171
|
+
turn.diagnosticOpen = false;
|
|
143
172
|
turn.completed = true;
|
|
144
173
|
turn.removeRequestAbort?.();
|
|
145
174
|
turn.removeRequestAbort = undefined;
|
|
146
|
-
this.disarm(turn);
|
|
147
|
-
if (!keepBackstop)
|
|
148
|
-
turn.settledController.abort();
|
|
149
|
-
if (this.activeTurn === turn)
|
|
150
|
-
this.activeTurn = undefined;
|
|
151
175
|
if ("response" in outcome)
|
|
152
176
|
turn.resolve(outcome.response);
|
|
153
177
|
else
|
|
154
178
|
turn.reject(outcome.error);
|
|
155
179
|
turn.resolveSettlement();
|
|
180
|
+
turn.releaseBoundary?.();
|
|
181
|
+
turn.releaseBoundary = undefined;
|
|
182
|
+
if (this.activeTurn === turn)
|
|
183
|
+
this.activeTurn = undefined;
|
|
184
|
+
const generation = this.cleanupGeneration;
|
|
185
|
+
if (generation?.resumeRefreshesOnSettlement && generation.mode === "cancel-only") {
|
|
186
|
+
this.cleanupGeneration = undefined;
|
|
187
|
+
this.mcpBridge.resumeRefreshes();
|
|
188
|
+
}
|
|
156
189
|
}
|
|
157
190
|
notificationFailure() {
|
|
158
191
|
const turn = this.activeTurn;
|
|
@@ -160,17 +193,135 @@ export class PiSession {
|
|
|
160
193
|
return;
|
|
161
194
|
turn.notificationFailed = true;
|
|
162
195
|
this.abortTurn(turn);
|
|
163
|
-
this.finish(turn, { error: adapterError("notification_error") },
|
|
196
|
+
void turn.cleanup?.then(() => this.finish(turn, { error: adapterError("notification_error") }), (error) => this.finish(turn, { error }));
|
|
164
197
|
}
|
|
165
198
|
abortTurn(turn) {
|
|
166
199
|
if (turn.completed)
|
|
167
200
|
return;
|
|
168
|
-
|
|
201
|
+
turn.cleanup ??= this.cleanupTurn("cancel-only");
|
|
202
|
+
turn.cleanup.catch((error) => {
|
|
203
|
+
if (!turn.completed)
|
|
204
|
+
this.turnError(turn, error);
|
|
205
|
+
void this.cleanupWedged();
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
startDisposal() {
|
|
209
|
+
this.closing = true;
|
|
210
|
+
// The contract's disposal prefix is intentionally split: transport close
|
|
211
|
+
// admission starts first, then incoming handlers observe session disposal,
|
|
212
|
+
// and only then are refresh/turn signals aborted.
|
|
213
|
+
this.mcpBridge.startDisposal();
|
|
214
|
+
if (!this.lifecycleController.signal.aborted) {
|
|
215
|
+
this.lifecycleController.abort(new Error("session disposed"));
|
|
216
|
+
}
|
|
217
|
+
this.mcpBridge.abortRefreshes();
|
|
218
|
+
this.bridgeClosePromise ??= this.mcpBridge.close();
|
|
219
|
+
this.bridgeClosePromise.catch(() => undefined);
|
|
220
|
+
}
|
|
221
|
+
cleanupTurn(mode) {
|
|
222
|
+
const current = this.cleanupGeneration;
|
|
223
|
+
if (current?.status === "pending") {
|
|
224
|
+
if (mode === "disposal" && current.mode === "cancel-only") {
|
|
225
|
+
current.mode = "disposal";
|
|
226
|
+
this.startDisposal();
|
|
227
|
+
}
|
|
228
|
+
return current.promise;
|
|
229
|
+
}
|
|
230
|
+
if (current?.status === "succeeded" && current.mode === "disposal") {
|
|
231
|
+
return current.promise;
|
|
232
|
+
}
|
|
233
|
+
const deadlineController = new AbortController();
|
|
234
|
+
const timerController = new AbortController();
|
|
235
|
+
const generation = {
|
|
236
|
+
mode,
|
|
237
|
+
status: "pending",
|
|
238
|
+
promise: Promise.resolve(),
|
|
239
|
+
deadlineController,
|
|
240
|
+
timerController,
|
|
241
|
+
};
|
|
242
|
+
this.cleanupGeneration = generation;
|
|
243
|
+
const expiry = this.deps.sleep(this.deps.graceMs, timerController.signal).then(() => {
|
|
244
|
+
const failure = new ChildCleanupFailure(this.childRegistry.remainingChildren);
|
|
245
|
+
deadlineController.abort(failure);
|
|
246
|
+
throw failure;
|
|
247
|
+
});
|
|
248
|
+
expiry.catch(() => undefined);
|
|
249
|
+
// Closing the captured epoch is the synchronous admission barrier. It
|
|
250
|
+
// must precede Pi abort, because abort can yield while a bash spawn is
|
|
251
|
+
// between its filesystem check and lease acquisition.
|
|
252
|
+
const captured = this.childRegistry.closeEpoch(deadlineController.signal);
|
|
253
|
+
captured.drain.catch(() => undefined);
|
|
254
|
+
if (mode === "disposal")
|
|
255
|
+
this.startDisposal();
|
|
256
|
+
else
|
|
257
|
+
this.mcpBridge.abortRefreshes();
|
|
258
|
+
const turn = this.activeTurn;
|
|
259
|
+
if (turn && !turn.controller.signal.aborted)
|
|
169
260
|
turn.controller.abort();
|
|
261
|
+
let abortPi;
|
|
262
|
+
try {
|
|
263
|
+
abortPi = this.pi.abort();
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
abortPi = Promise.reject(error);
|
|
267
|
+
}
|
|
268
|
+
abortPi.catch(() => undefined);
|
|
269
|
+
const operations = Promise.allSettled([abortPi, captured.drain]);
|
|
270
|
+
generation.promise = new Promise((resolve, reject) => {
|
|
271
|
+
let claimed = false;
|
|
272
|
+
const fail = (error) => {
|
|
273
|
+
if (claimed)
|
|
274
|
+
return;
|
|
275
|
+
claimed = true;
|
|
276
|
+
generation.status = "failed";
|
|
277
|
+
this.cleanupDirty = true;
|
|
278
|
+
generation.mode = "disposal";
|
|
279
|
+
this.startDisposal();
|
|
280
|
+
const remaining = error instanceof ChildCleanupFailure
|
|
281
|
+
? error.remainingChildren
|
|
282
|
+
: this.childRegistry.remainingChildren;
|
|
283
|
+
generation.error = adapterError("child_cleanup_error", { details: { remainingChildren: remaining } });
|
|
284
|
+
timerController.abort();
|
|
285
|
+
reject(generation.error);
|
|
286
|
+
};
|
|
287
|
+
expiry.then(() => undefined, (error) => {
|
|
288
|
+
if (timerController.signal.aborted && !deadlineController.signal.aborted)
|
|
289
|
+
return;
|
|
290
|
+
fail(error);
|
|
291
|
+
});
|
|
292
|
+
operations.then((results) => {
|
|
293
|
+
if (claimed)
|
|
294
|
+
return;
|
|
295
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
296
|
+
if (failure) {
|
|
297
|
+
fail(failure.reason);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
claimed = true;
|
|
301
|
+
generation.status = "succeeded";
|
|
302
|
+
this.cleanupDirty = false;
|
|
303
|
+
timerController.abort();
|
|
304
|
+
if (generation.mode === "cancel-only" && this.cleanupGeneration === generation) {
|
|
305
|
+
this.childRegistry.commitRotation(captured.epoch);
|
|
306
|
+
if (turn?.completed) {
|
|
307
|
+
this.cleanupGeneration = undefined;
|
|
308
|
+
this.mcpBridge.resumeRefreshes();
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
generation.resumeRefreshesOnSettlement = true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
resolve();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
generation.promise.catch(() => undefined);
|
|
318
|
+
return generation.promise;
|
|
170
319
|
}
|
|
171
320
|
turnError(turn, error) {
|
|
172
|
-
if (turn.completed)
|
|
321
|
+
if (turn.completed || turn.errorSettlementStarted)
|
|
173
322
|
return;
|
|
323
|
+
turn.errorSettlementStarted = true;
|
|
324
|
+
turn.diagnosticOpen = false;
|
|
174
325
|
let terminal;
|
|
175
326
|
try {
|
|
176
327
|
terminal = terminalAssistant(agentMessages(this.pi).slice(turn.startMessageIndex));
|
|
@@ -178,7 +329,8 @@ export class PiSession {
|
|
|
178
329
|
catch {
|
|
179
330
|
terminal = undefined;
|
|
180
331
|
}
|
|
181
|
-
|
|
332
|
+
const mapped = isRequestError(error) ? error : unexpectedError(error, terminal);
|
|
333
|
+
void this.drain().then(() => this.finish(turn, { error: mapped }), () => this.notificationFailure());
|
|
182
334
|
}
|
|
183
335
|
runTurnTask(turn, task) {
|
|
184
336
|
void task.catch((error) => {
|
|
@@ -190,61 +342,22 @@ export class PiSession {
|
|
|
190
342
|
}
|
|
191
343
|
async cleanupWedged() {
|
|
192
344
|
try {
|
|
193
|
-
await this.onWedged(this.sessionId, this);
|
|
345
|
+
await this.onWedged(this.sessionId, this, true);
|
|
194
346
|
}
|
|
195
347
|
catch (error) {
|
|
196
348
|
console.error("pi-acp wedged-session cleanup error:", error);
|
|
197
349
|
}
|
|
198
350
|
}
|
|
199
|
-
startBackstop(turn) {
|
|
200
|
-
if (turn.backstopStarted)
|
|
201
|
-
return;
|
|
202
|
-
turn.backstopStarted = true;
|
|
203
|
-
const backstop = this.deps.sleep(this.deps.graceMs, turn.settledController.signal).then(async () => {
|
|
204
|
-
try {
|
|
205
|
-
if (!turn.completed) {
|
|
206
|
-
const messages = agentMessages(this.pi).slice(turn.startMessageIndex);
|
|
207
|
-
if (!this.pumpFailure) {
|
|
208
|
-
this.enqueue(usageUpdate(this.pi));
|
|
209
|
-
await this.drain();
|
|
210
|
-
}
|
|
211
|
-
if (!turn.completed) {
|
|
212
|
-
this.finish(turn, {
|
|
213
|
-
response: { stopReason: "cancelled", usage: promptUsage(messages) },
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
catch (error) {
|
|
219
|
-
if (this.pumpFailure !== undefined) {
|
|
220
|
-
turn.notificationFailed = true;
|
|
221
|
-
this.finish(turn, { error: adapterError("notification_error") }, true);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
this.turnError(turn, error);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (turn.completed)
|
|
228
|
-
await this.cleanupWedged();
|
|
229
|
-
}, async (error) => {
|
|
230
|
-
if (turn.settledController.signal.aborted || turn.completed)
|
|
231
|
-
return;
|
|
232
|
-
this.turnError(turn, error);
|
|
233
|
-
await this.cleanupWedged();
|
|
234
|
-
});
|
|
235
|
-
this.runTurnTask(turn, backstop);
|
|
236
|
-
}
|
|
237
351
|
async handlePiResolved(turn) {
|
|
238
352
|
if (turn.completed)
|
|
239
353
|
return;
|
|
240
354
|
try {
|
|
355
|
+
if (this.childRegistry.childCleanupFailed)
|
|
356
|
+
turn.cleanup ??= this.cleanupTurn("disposal");
|
|
241
357
|
const messages = agentMessages(this.pi).slice(turn.startMessageIndex);
|
|
242
|
-
if (turn.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.enqueue({ sessionUpdate: "agent_message_chunk", content: { type: "text", text: json } });
|
|
246
|
-
}
|
|
247
|
-
}
|
|
358
|
+
if (turn.cleanup)
|
|
359
|
+
await turn.cleanup;
|
|
360
|
+
turn.diagnosticOpen = false;
|
|
248
361
|
this.enqueue(usageUpdate(this.pi));
|
|
249
362
|
await this.drain();
|
|
250
363
|
if (turn.completed)
|
|
@@ -264,11 +377,21 @@ export class PiSession {
|
|
|
264
377
|
if (turn.completed)
|
|
265
378
|
return;
|
|
266
379
|
if (!turn.controller.signal.aborted) {
|
|
267
|
-
|
|
380
|
+
turn.diagnosticOpen = false;
|
|
381
|
+
try {
|
|
382
|
+
await this.drain();
|
|
383
|
+
this.finish(turn, { error: classifyPreflight(error) });
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
this.notificationFailure();
|
|
387
|
+
}
|
|
268
388
|
return;
|
|
269
389
|
}
|
|
270
390
|
try {
|
|
391
|
+
if (turn.cleanup)
|
|
392
|
+
await turn.cleanup;
|
|
271
393
|
const messages = agentMessages(this.pi).slice(turn.startMessageIndex);
|
|
394
|
+
turn.diagnosticOpen = false;
|
|
272
395
|
this.enqueue(usageUpdate(this.pi));
|
|
273
396
|
await this.drain();
|
|
274
397
|
this.finish(turn, { response: { stopReason: "cancelled", usage: promptUsage(messages) } });
|
|
@@ -284,35 +407,12 @@ export class PiSession {
|
|
|
284
407
|
if (this.busy)
|
|
285
408
|
throw adapterError("session_busy");
|
|
286
409
|
const converted = convertPromptContent(params.prompt);
|
|
287
|
-
const
|
|
288
|
-
let text = converted.text;
|
|
289
|
-
let structured = false;
|
|
290
|
-
if (schema !== undefined) {
|
|
291
|
-
let instruction;
|
|
292
|
-
try {
|
|
293
|
-
instruction = this.structured.arm(this.pi, schema);
|
|
294
|
-
}
|
|
295
|
-
catch (error) {
|
|
296
|
-
if (isRequestError(error))
|
|
297
|
-
throw error;
|
|
298
|
-
throw unexpectedError(error);
|
|
299
|
-
}
|
|
300
|
-
text = text ? `${instruction}\n\n${text}` : instruction;
|
|
301
|
-
structured = true;
|
|
302
|
-
}
|
|
410
|
+
const text = converted.text;
|
|
303
411
|
let startMessageIndex;
|
|
304
412
|
try {
|
|
305
413
|
startMessageIndex = agentMessages(this.pi).length;
|
|
306
414
|
}
|
|
307
415
|
catch (error) {
|
|
308
|
-
if (structured) {
|
|
309
|
-
try {
|
|
310
|
-
this.structured.disarm(this.pi);
|
|
311
|
-
}
|
|
312
|
-
catch (disarmError) {
|
|
313
|
-
console.error("pi-acp structured-output disarm error:", disarmError);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
416
|
throw unexpectedError(error);
|
|
317
417
|
}
|
|
318
418
|
let resolve;
|
|
@@ -327,29 +427,17 @@ export class PiSession {
|
|
|
327
427
|
});
|
|
328
428
|
const turn = {
|
|
329
429
|
controller: new AbortController(),
|
|
330
|
-
settledController: new AbortController(),
|
|
331
430
|
settlement,
|
|
332
431
|
resolveSettlement,
|
|
333
432
|
completed: false,
|
|
433
|
+
diagnosticOpen: true,
|
|
434
|
+
errorSettlementStarted: false,
|
|
334
435
|
notificationFailed: false,
|
|
335
|
-
backstopStarted: false,
|
|
336
436
|
resolve,
|
|
337
437
|
reject,
|
|
338
438
|
startMessageIndex,
|
|
339
|
-
structured,
|
|
340
439
|
};
|
|
341
440
|
this.activeTurn = turn;
|
|
342
|
-
turn.controller.signal.addEventListener("abort", () => {
|
|
343
|
-
try {
|
|
344
|
-
this.pi.agent.abort();
|
|
345
|
-
}
|
|
346
|
-
catch (error) {
|
|
347
|
-
console.error("pi-acp abort error:", error);
|
|
348
|
-
}
|
|
349
|
-
finally {
|
|
350
|
-
this.startBackstop(turn);
|
|
351
|
-
}
|
|
352
|
-
}, { once: true });
|
|
353
441
|
const abortFromRequest = () => this.abortTurn(turn);
|
|
354
442
|
if (requestSignal.aborted)
|
|
355
443
|
abortFromRequest();
|
|
@@ -357,58 +445,126 @@ export class PiSession {
|
|
|
357
445
|
requestSignal.addEventListener("abort", abortFromRequest, { once: true });
|
|
358
446
|
turn.removeRequestAbort = () => requestSignal.removeEventListener("abort", abortFromRequest);
|
|
359
447
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
448
|
+
this.runTurnTask(turn, (async () => {
|
|
449
|
+
turn.releaseBoundary = await this.mcpBridge.acquireTurnBoundary();
|
|
450
|
+
if (turn.controller.signal.aborted) {
|
|
451
|
+
try {
|
|
452
|
+
if (turn.cleanup)
|
|
453
|
+
await turn.cleanup;
|
|
454
|
+
turn.diagnosticOpen = false;
|
|
455
|
+
this.enqueue(usageUpdate(this.pi));
|
|
456
|
+
await this.drain();
|
|
457
|
+
this.finish(turn, {
|
|
458
|
+
response: {
|
|
459
|
+
stopReason: "cancelled",
|
|
460
|
+
usage: { inputTokens: 0, outputTokens: 0, cachedReadTokens: 0, cachedWriteTokens: 0, totalTokens: 0 },
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
this.turnError(turn, error);
|
|
466
|
+
}
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
let piPromise;
|
|
470
|
+
try {
|
|
471
|
+
piPromise = this.pi.prompt(text, { images: converted.images });
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
await this.handlePiRejected(turn, error);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
await piPromise.then(() => this.handlePiResolved(turn), (error) => this.handlePiRejected(turn, error));
|
|
478
|
+
})());
|
|
369
479
|
return result;
|
|
370
480
|
}
|
|
371
481
|
cancel() {
|
|
372
482
|
if (this.activeTurn)
|
|
373
483
|
this.abortTurn(this.activeTurn);
|
|
374
484
|
}
|
|
485
|
+
childCleanupFailure() {
|
|
486
|
+
const turn = this.activeTurn;
|
|
487
|
+
if (turn && !turn.completed) {
|
|
488
|
+
this.abortTurn(turn);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
void this.cleanupWedged();
|
|
492
|
+
}
|
|
375
493
|
async dispose() {
|
|
376
|
-
if (this.disposed)
|
|
494
|
+
if (this.disposed && !this.cleanupDirty && this.childRegistry.remainingChildren === 0 && !this.childRegistry.childCleanupFailed)
|
|
377
495
|
return;
|
|
378
|
-
|
|
496
|
+
const cleanup = this.cleanupTurn("disposal");
|
|
379
497
|
const turn = this.activeTurn;
|
|
498
|
+
let cleanupError;
|
|
380
499
|
if (turn) {
|
|
381
500
|
this.abortTurn(turn);
|
|
382
501
|
await turn.settlement;
|
|
502
|
+
try {
|
|
503
|
+
await cleanup;
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
cleanupError = error;
|
|
507
|
+
}
|
|
383
508
|
}
|
|
509
|
+
else {
|
|
510
|
+
try {
|
|
511
|
+
await cleanup;
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
cleanupError = error;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
await this.disposeResources();
|
|
518
|
+
if (cleanupError)
|
|
519
|
+
throw cleanupError;
|
|
520
|
+
}
|
|
521
|
+
async disposeAfterCleanupFailure() {
|
|
522
|
+
this.startDisposal();
|
|
384
523
|
await this.disposeResources();
|
|
385
524
|
}
|
|
525
|
+
get remainingChildren() { return this.childRegistry.remainingChildren; }
|
|
526
|
+
get cleanupRetryRequired() {
|
|
527
|
+
return this.cleanupDirty || this.childRegistry.remainingChildren > 0 || this.childRegistry.childCleanupFailed;
|
|
528
|
+
}
|
|
386
529
|
async disposeResources() {
|
|
387
|
-
|
|
530
|
+
this.resourceDisposePromise ??= (async () => {
|
|
531
|
+
if (this.disposed)
|
|
532
|
+
return;
|
|
533
|
+
this.closing = true;
|
|
534
|
+
this.disposed = true;
|
|
535
|
+
this.stopped = true;
|
|
536
|
+
this.pending.length = 0;
|
|
537
|
+
this.failedMcpResults.clear();
|
|
538
|
+
try {
|
|
539
|
+
this.unsubscribe?.();
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
console.error("pi-acp unsubscribe error:", error);
|
|
543
|
+
}
|
|
544
|
+
this.unsubscribe = undefined;
|
|
545
|
+
this.startDisposal();
|
|
546
|
+
await this.mcpBridge.drainRefreshes().catch((error) => {
|
|
547
|
+
console.error("pi-acp MCP refresh drain error:", error);
|
|
548
|
+
});
|
|
549
|
+
const results = await Promise.allSettled([
|
|
550
|
+
Promise.resolve().then(() => this.pi.dispose()),
|
|
551
|
+
this.bridgeClosePromise ?? Promise.resolve(),
|
|
552
|
+
]);
|
|
553
|
+
if (results[0]?.status === "rejected") {
|
|
554
|
+
console.error("pi-acp session dispose error:", results[0].reason);
|
|
555
|
+
}
|
|
556
|
+
if (results[1]?.status === "rejected") {
|
|
557
|
+
console.error("pi-acp MCP disposal error:", results[1].reason);
|
|
558
|
+
}
|
|
559
|
+
})();
|
|
560
|
+
return this.resourceDisposePromise;
|
|
561
|
+
}
|
|
562
|
+
poison() {
|
|
563
|
+
if (this.closing || this.disposed)
|
|
388
564
|
return;
|
|
389
565
|
this.closing = true;
|
|
390
|
-
this.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
this.failedMcpResults.clear();
|
|
394
|
-
try {
|
|
395
|
-
this.unsubscribe?.();
|
|
396
|
-
}
|
|
397
|
-
catch (error) {
|
|
398
|
-
console.error("pi-acp unsubscribe error:", error);
|
|
399
|
-
}
|
|
400
|
-
this.unsubscribe = undefined;
|
|
401
|
-
try {
|
|
402
|
-
await this.pi.dispose();
|
|
403
|
-
}
|
|
404
|
-
catch (error) {
|
|
405
|
-
console.error("pi-acp session dispose error:", error);
|
|
406
|
-
}
|
|
407
|
-
try {
|
|
408
|
-
await disposeMcpBridge(this.mcpClients, this.deps);
|
|
409
|
-
}
|
|
410
|
-
catch (error) {
|
|
411
|
-
console.error("pi-acp MCP disposal error:", error);
|
|
412
|
-
}
|
|
566
|
+
void this.onWedged(this.sessionId, this, false).catch((error) => {
|
|
567
|
+
console.error("pi-acp poisoned-session cleanup error:", error);
|
|
568
|
+
});
|
|
413
569
|
}
|
|
414
570
|
}
|
package/dist/translate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE,UAAU,QAAQ;IAChB,OAAO,CAAC,EAAE,KAAK,CACX;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC9B;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CACpD,CAAC;IACF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAgBlD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,EAAE,GAAG,SAAS,CAI3E;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,GAAG,YAAY,EAAE,CAM7D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,EAAE,CAE7D;AA4BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,EAAE,YAAY,CAAC,EAAE,QAAQ,GAAG,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE,UAAU,QAAQ;IAChB,OAAO,CAAC,EAAE,KAAK,CACX;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAC9B;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CACpD,CAAC;IACF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAgBlD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,EAAE,GAAG,SAAS,CAI3E;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,GAAG,YAAY,EAAE,CAM7D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,GAAG,eAAe,EAAE,CAE7D;AA4BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,EAAE,YAAY,CAAC,EAAE,QAAQ,GAAG,aAAa,EAAE,CAyDjG"}
|
package/dist/translate.js
CHANGED
|
@@ -68,12 +68,16 @@ export function translateEvent(event, failedResult) {
|
|
|
68
68
|
_meta: { toolName: event.toolName },
|
|
69
69
|
}];
|
|
70
70
|
case "tool_execution_update":
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
const update = {
|
|
72
|
+
sessionUpdate: "tool_call_update",
|
|
73
|
+
toolCallId: event.toolCallId,
|
|
74
|
+
status: "in_progress",
|
|
75
|
+
content: toContent(event.partialResult),
|
|
76
|
+
};
|
|
77
|
+
const partial = event.partialResult;
|
|
78
|
+
if (partial.details !== undefined)
|
|
79
|
+
update.rawOutput = partial.details;
|
|
80
|
+
return [update];
|
|
77
81
|
case "tool_execution_end": {
|
|
78
82
|
const result = failedResult ?? event.result;
|
|
79
83
|
const update = {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,WAAW,QAEvB,CAAC"}
|
package/dist/version.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automatalabs/pi-acp",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.19.0"
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
34
|
+
"@earendil-works/pi-ai": "0.80.10",
|
|
34
35
|
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
35
36
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
36
37
|
"typebox": "1.3.2"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@earendil-works/pi-agent-core": "0.80.10"
|
|
40
|
-
"@earendil-works/pi-ai": "0.80.10"
|
|
40
|
+
"@earendil-works/pi-agent-core": "0.80.10"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsc -b",
|