@agen-ai/provider-acp 0.4.0 → 0.4.2
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 +515 -12
- package/dist/capabilities.d.ts +3 -1
- package/dist/capabilities.js +5 -4
- package/dist/configuration/catalog.js +0 -2
- package/dist/configuration/errors.js +0 -1
- package/dist/connection/contracts.d.ts +24 -1
- package/dist/connection/errors.js +0 -1
- package/dist/connection/initialize.d.ts +7 -3
- package/dist/connection/initialize.js +64 -19
- package/dist/connection/negotiation.d.ts +3 -1
- package/dist/connection/negotiation.js +22 -1
- package/dist/contracts.d.ts +3 -0
- package/dist/driver.d.ts +1 -1
- package/dist/driver.js +6 -39
- package/dist/driverFactory.d.ts +8 -0
- package/dist/driverFactory.js +48 -0
- package/dist/host/interactions.d.ts +6 -1
- package/dist/host/interactions.js +42 -17
- package/dist/host/leases.js +0 -1
- package/dist/host/router.d.ts +3 -2
- package/dist/host/router.js +23 -21
- package/dist/host/terminals.js +0 -2
- package/dist/index.d.ts +1 -0
- package/dist/instance.d.ts +7 -2
- package/dist/instance.js +113 -28
- package/dist/node/contracts.d.ts +6 -0
- package/dist/node/execution/errors.d.ts +11 -0
- package/dist/node/execution/errors.js +17 -0
- package/dist/node/execution/owner.d.ts +34 -0
- package/dist/node/execution/owner.js +162 -0
- package/dist/node/execution/types.d.ts +20 -0
- package/dist/node/execution/types.js +0 -0
- package/dist/node/index.d.ts +3 -1
- package/dist/node/index.js +16 -6
- package/dist/node/launch.d.ts +5 -8
- package/dist/node/launch.js +13 -1
- package/dist/permissions/contracts.d.ts +22 -0
- package/dist/permissions/contracts.js +0 -0
- package/dist/permissions/mapping.d.ts +13 -0
- package/dist/permissions/mapping.js +99 -0
- package/dist/permissions/selection.d.ts +5 -0
- package/dist/permissions/selection.js +96 -0
- package/dist/session/continuation.d.ts +33 -0
- package/dist/session/continuation.js +185 -0
- package/dist/session/create.d.ts +7 -1
- package/dist/session/create.js +3 -1
- package/dist/session/descendants.d.ts +29 -0
- package/dist/session/descendants.js +97 -0
- package/dist/session/errors.js +0 -1
- package/dist/session/mapping.d.ts +6 -0
- package/dist/session/mapping.js +5 -26
- package/dist/session/native.d.ts +1 -0
- package/dist/session/native.js +8 -0
- package/dist/session/opening.d.ts +1 -0
- package/dist/session/opening.js +3 -1
- package/dist/session/queue.js +0 -1
- package/dist/session/requests/elicitation.d.ts +1 -1
- package/dist/session/requests/elicitation.js +1 -1
- package/dist/session/requests/forms.js +0 -2
- package/dist/session/requests/grokWithdrawals.d.ts +28 -0
- package/dist/session/requests/grokWithdrawals.js +135 -0
- package/dist/session/requests/pending.d.ts +17 -4
- package/dist/session/requests/pending.js +63 -8
- package/dist/session/requests/resolution.js +4 -2
- package/dist/session/resources.js +5 -3
- package/dist/session/session.d.ts +12 -1
- package/dist/session/session.js +197 -47
- package/dist/session/turn.d.ts +6 -1
- package/dist/sessionInteractions/broker.d.ts +50 -0
- package/dist/sessionInteractions/broker.js +193 -0
- package/dist/sessionInteractions/contracts.d.ts +3 -0
- package/dist/sessionInteractions/contracts.js +0 -0
- package/dist/sessionInteractions/extensions.d.ts +4 -0
- package/dist/sessionInteractions/extensions.js +37 -0
- package/dist/sessionInteractions/grokFolderTrust.d.ts +42 -0
- package/dist/sessionInteractions/grokFolderTrust.js +106 -0
- package/dist/sessionInteractions/grokPlanReview.d.ts +16 -0
- package/dist/sessionInteractions/grokPlanReview.js +63 -0
- package/dist/sessionInteractions/request.d.ts +46 -0
- package/dist/sessionInteractions/request.js +173 -0
- package/package.json +4 -4
|
@@ -8,7 +8,6 @@ class AcpSessionResources {
|
|
|
8
8
|
constructor(input) {
|
|
9
9
|
this.input = input;
|
|
10
10
|
}
|
|
11
|
-
input;
|
|
12
11
|
lifetime = new AbortController();
|
|
13
12
|
acquisition;
|
|
14
13
|
releaseMcp;
|
|
@@ -64,9 +63,12 @@ class AcpSessionResources {
|
|
|
64
63
|
close() {
|
|
65
64
|
if (this.closed) return Promise.resolve();
|
|
66
65
|
this.lifetime.abort(new DOMException("ACP session resources were closed.", "AbortError"));
|
|
66
|
+
const nativeCleanup = this.nativeClosed ? Promise.resolve() : this.input.connection.close();
|
|
67
|
+
void nativeCleanup.catch(() => {
|
|
68
|
+
});
|
|
67
69
|
this.cleanup ??= Promise.resolve().then(async () => {
|
|
68
70
|
if (!this.nativeClosed) {
|
|
69
|
-
await this.input.connection.
|
|
71
|
+
await (this.input.connection.execution?.joinCleanup() ?? nativeCleanup);
|
|
70
72
|
this.nativeClosed = true;
|
|
71
73
|
}
|
|
72
74
|
if (this.acquisition) {
|
|
@@ -82,7 +84,7 @@ class AcpSessionResources {
|
|
|
82
84
|
this.cleanup = void 0;
|
|
83
85
|
});
|
|
84
86
|
const deadline = hostDeadline(new AbortController().signal, this.input.limits.hostOperationTimeoutMs);
|
|
85
|
-
return awaitHost(this.cleanup, deadline.signal).finally(() => {
|
|
87
|
+
return awaitHost(Promise.all([nativeCleanup, this.cleanup]).then(() => void 0), deadline.signal).finally(() => {
|
|
86
88
|
deadline.close();
|
|
87
89
|
});
|
|
88
90
|
}
|
|
@@ -15,10 +15,15 @@ export declare class SessionState {
|
|
|
15
15
|
private nativeSessionId;
|
|
16
16
|
private turn;
|
|
17
17
|
private failure;
|
|
18
|
+
private failureObserved;
|
|
19
|
+
private cleanupFailure;
|
|
18
20
|
private closed;
|
|
19
21
|
private closePromise;
|
|
20
22
|
private itemSequence;
|
|
21
23
|
private requestSequence;
|
|
24
|
+
private readonly continuations;
|
|
25
|
+
private readonly descendants;
|
|
26
|
+
private readonly childWithdrawals;
|
|
22
27
|
constructor(input: CreateAcpProviderSessionInput);
|
|
23
28
|
open(): Promise<AgentProviderSession>;
|
|
24
29
|
private requireUsable;
|
|
@@ -29,7 +34,12 @@ export declare class SessionState {
|
|
|
29
34
|
private observeMetadata;
|
|
30
35
|
private admitRequest;
|
|
31
36
|
private clearTurnTimers;
|
|
37
|
+
private streamContinuation;
|
|
32
38
|
startTurn(input: AgentProviderStartTurnInput): AgentProviderTurnExecution;
|
|
39
|
+
private createNativeTurn;
|
|
40
|
+
private execution;
|
|
41
|
+
private ownedStream;
|
|
42
|
+
private completeNativeTurn;
|
|
33
43
|
private launchTurn;
|
|
34
44
|
private emit;
|
|
35
45
|
private drain;
|
|
@@ -38,6 +48,7 @@ export declare class SessionState {
|
|
|
38
48
|
private resolveRequest;
|
|
39
49
|
private cancelNative;
|
|
40
50
|
interruptTurn(input: AgentProviderInterruptTurnInput): Promise<AgentProviderTurnInterruptionResult>;
|
|
41
|
-
close(): Promise<void>;
|
|
51
|
+
close(observeFailure?: boolean): Promise<void>;
|
|
52
|
+
private observeFailure;
|
|
42
53
|
}
|
|
43
54
|
//# sourceMappingURL=session.d.ts.map
|
package/dist/session/session.js
CHANGED
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
throwIfAgentOperationAborted
|
|
10
10
|
} from "@agen-ai/agent-runtime";
|
|
11
|
+
import { GrokPlanContinuations } from "./continuation.js";
|
|
12
|
+
import { GrokDescendantObservations } from "./descendants.js";
|
|
13
|
+
import { GrokChildPermissionWithdrawals } from "./requests/grokWithdrawals.js";
|
|
14
|
+
import { awaitHost } from "../host/lifecycle.js";
|
|
11
15
|
import { AcpProviderSessionError, sessionError } from "./errors.js";
|
|
12
16
|
import { TurnMapping } from "./mapping.js";
|
|
13
17
|
import { createAcpSessionConfiguration } from "../configuration/control.js";
|
|
@@ -26,11 +30,55 @@ class SessionState {
|
|
|
26
30
|
this.input = input;
|
|
27
31
|
this.queue = new SessionQueue(input.limits);
|
|
28
32
|
this.replay = new AcpHistoryReplay(input.limits);
|
|
33
|
+
if (input.connection.sessionInteractions?.selectedLifecycle) {
|
|
34
|
+
this.childWithdrawals = new GrokChildPermissionWithdrawals({
|
|
35
|
+
reserveState: (value, extraBytes) => this.queue.reserveState(value, extraBytes),
|
|
36
|
+
currentTurn: () => this.turn,
|
|
37
|
+
timeoutMs: input.limits.cancellationTimeoutMs,
|
|
38
|
+
withdraw: (request) => this.requests.withdraw(request, new AcpRpcHandlerError(-32800, "Request cancelled")),
|
|
39
|
+
fail: (error) => this.fail(error)
|
|
40
|
+
});
|
|
41
|
+
this.descendants = new GrokDescendantObservations({
|
|
42
|
+
reserveState: (value, extraBytes) => this.queue.reserveState(value, extraBytes),
|
|
43
|
+
rootSessionId: () => this.nativeSessionId,
|
|
44
|
+
activeTurn: () => this.turn !== void 0 && !this.turn.nativeSettled && !this.turn.terminal,
|
|
45
|
+
observeHistory: (params) => this.replay.observe(params),
|
|
46
|
+
withdrawals: this.childWithdrawals
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
this.continuations = new GrokPlanContinuations({
|
|
50
|
+
queue: this.queue,
|
|
51
|
+
selected: input.connection.sessionInteractions?.selectedLifecycle === true,
|
|
52
|
+
requireUsable: () => this.requireUsable(),
|
|
53
|
+
assertNativeSession: (params) => this.assertNativeSession(params),
|
|
54
|
+
createNativeTurn: (turnId) => this.createNativeTurn(turnId),
|
|
55
|
+
execution: (turn, stream) => this.execution(turn, stream),
|
|
56
|
+
stream: (turn, ready) => this.streamContinuation(turn, ready),
|
|
57
|
+
completeNativeTurn: (turn, payload) => this.completeNativeTurn(turn, payload),
|
|
58
|
+
close: () => this.close()
|
|
59
|
+
});
|
|
29
60
|
this.operations = createAcpStandardSessionOperations(input.connection.client);
|
|
30
61
|
this.requests = new PendingSessionRequests({
|
|
31
|
-
maxPendingRequests: input.limits.maxPendingRequests,
|
|
62
|
+
maxPendingRequests: input.connection.sessionInteractions?.selectedLifecycle ? Math.min(input.limits.maxPendingRequests, 16) : input.limits.maxPendingRequests,
|
|
63
|
+
maxRequestMaterialBytes: input.connection.sessionInteractions?.selectedLifecycle ? Math.min(input.limits.maxQueuedBytes, 1048576) : input.limits.maxQueuedBytes,
|
|
32
64
|
admit: (request, bytes) => this.queue.admit({ kind: "request", request }, bytes),
|
|
33
|
-
fail: (error) => this.fail(error)
|
|
65
|
+
fail: (error) => this.fail(error),
|
|
66
|
+
unavailable: (request, requestId) => {
|
|
67
|
+
try {
|
|
68
|
+
this.queue.admit({ kind: "unavailable", request, requestId }, nativeBytes(requestId) + 64, true);
|
|
69
|
+
} catch (cause) {
|
|
70
|
+
this.fail(sessionError(cause));
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
settled: (request) => {
|
|
74
|
+
this.childWithdrawals?.settle(request);
|
|
75
|
+
const turn = this.turn;
|
|
76
|
+
if (turn?.waiting === request && !request.unavailable) {
|
|
77
|
+
turn.waiting = void 0;
|
|
78
|
+
this.queue.wakeReader();
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
admitted: (request) => this.childWithdrawals?.attach(request)
|
|
34
82
|
});
|
|
35
83
|
this.configuration = createAcpSessionConfiguration({
|
|
36
84
|
capability: input.capabilities.configuration,
|
|
@@ -44,14 +92,20 @@ class SessionState {
|
|
|
44
92
|
this.environment = createAcpSessionEnvironment({ limits: input.limits, now: input.now, signal: input.connection.stopped });
|
|
45
93
|
this.onStopped = () => this.fail(sessionError(input.connection.stopped.reason));
|
|
46
94
|
this.detach = input.connection.attachSession({
|
|
95
|
+
joinCallbacks: () => this.requests.join(),
|
|
96
|
+
onExtensionNotification: (context) => {
|
|
97
|
+
this.requireUsable();
|
|
98
|
+
if (this.descendants?.admit(context, this.replay.active)) return;
|
|
99
|
+
this.continuations.admitNotification(context);
|
|
100
|
+
},
|
|
47
101
|
onNotification: (context) => this.admitNotification(context),
|
|
48
102
|
onPermissionRequest: (context) => this.admitRequest(context, "permission"),
|
|
49
103
|
onElicitationRequest: (context) => this.admitRequest(context, "form")
|
|
50
104
|
});
|
|
105
|
+
input.connection.sessionInteractions?.attachContinuationOwner((prepared) => this.continuations.prepareResponse(prepared));
|
|
51
106
|
input.connection.stopped.addEventListener("abort", this.onStopped, { once: true });
|
|
52
107
|
if (input.connection.stopped.aborted) this.onStopped();
|
|
53
108
|
}
|
|
54
|
-
input;
|
|
55
109
|
queue;
|
|
56
110
|
operations;
|
|
57
111
|
requests;
|
|
@@ -65,18 +119,25 @@ class SessionState {
|
|
|
65
119
|
nativeSessionId;
|
|
66
120
|
turn;
|
|
67
121
|
failure;
|
|
122
|
+
failureObserved = false;
|
|
123
|
+
cleanupFailure;
|
|
68
124
|
closed = false;
|
|
69
125
|
closePromise;
|
|
70
126
|
itemSequence = 0;
|
|
71
127
|
requestSequence = 0;
|
|
128
|
+
continuations;
|
|
129
|
+
descendants;
|
|
130
|
+
childWithdrawals;
|
|
72
131
|
async open() {
|
|
73
132
|
this.requireUsable();
|
|
74
133
|
throwIfAgentOperationAborted(this.input.context.signal);
|
|
134
|
+
const sessionInteractions = this.input.context.interactions ? this.input.connection.sessionInteractions : void 0;
|
|
75
135
|
await openAcpNativeSession(this.input, {
|
|
76
136
|
replay: this.replay,
|
|
77
137
|
bind: (binding2) => {
|
|
78
138
|
this.binding = binding2;
|
|
79
139
|
this.nativeSessionId = binding2.conversationId;
|
|
140
|
+
sessionInteractions?.bindNativeSession(binding2.conversationId);
|
|
80
141
|
this.bindHostSession();
|
|
81
142
|
},
|
|
82
143
|
observeSetup: (result) => {
|
|
@@ -91,7 +152,10 @@ class SessionState {
|
|
|
91
152
|
}
|
|
92
153
|
}
|
|
93
154
|
this.configuration.observeSetup(result);
|
|
94
|
-
}
|
|
155
|
+
},
|
|
156
|
+
...sessionInteractions ? {
|
|
157
|
+
completeOpeningResponse: () => sessionInteractions.completeOpeningResponse()
|
|
158
|
+
} : {}
|
|
95
159
|
});
|
|
96
160
|
this.requireUsable();
|
|
97
161
|
if (!this.binding) throw new AcpProviderSessionError("acp_binding_missing", "ACP did not return a session binding.");
|
|
@@ -108,7 +172,7 @@ class SessionState {
|
|
|
108
172
|
environment: this.environment.port,
|
|
109
173
|
collaboration: Object.freeze({ kind: "unsupported" }),
|
|
110
174
|
generatedResources: Object.freeze({ kind: "unsupported" }),
|
|
111
|
-
close: async () => this.close()
|
|
175
|
+
close: async () => this.close(true)
|
|
112
176
|
});
|
|
113
177
|
}
|
|
114
178
|
requireUsable() {
|
|
@@ -121,23 +185,31 @@ class SessionState {
|
|
|
121
185
|
}
|
|
122
186
|
}
|
|
123
187
|
fail(error) {
|
|
124
|
-
if (this.failure) return;
|
|
188
|
+
if (this.failure || this.closed) return;
|
|
125
189
|
this.failure = error;
|
|
190
|
+
this.input.connection.sessionInteractions?.stop({ kind: "failed", cause: error });
|
|
126
191
|
this.turn?.hostInteractions.abort(error);
|
|
127
192
|
this.queue.fail(error);
|
|
128
193
|
this.requests.cancelAll();
|
|
129
194
|
this.environment.close();
|
|
130
195
|
this.turn?.settled.reject(error);
|
|
196
|
+
this.continuations.stop(error);
|
|
197
|
+
this.descendants?.stop();
|
|
198
|
+
this.childWithdrawals?.stop(error);
|
|
131
199
|
this.clearTurnTimers();
|
|
132
|
-
void this.input.connection.close().catch(() =>
|
|
200
|
+
void this.input.connection.close().catch((cause) => {
|
|
201
|
+
this.cleanupFailure = cause;
|
|
202
|
+
});
|
|
133
203
|
}
|
|
134
204
|
admitNotification(context) {
|
|
135
205
|
this.requireUsable();
|
|
206
|
+
if (this.descendants?.admit(context, this.replay.active)) return;
|
|
136
207
|
const params = nativeObject(context.params, "session notification");
|
|
137
208
|
this.assertNativeSession(params);
|
|
138
209
|
const update = nativeObject(params.update, "session update");
|
|
139
210
|
const kind = nativeString(update.sessionUpdate, "session update kind");
|
|
140
211
|
this.replay.observe(params);
|
|
212
|
+
const liveOutput = this.continuations.admitOutput(params, update, this.replay.active);
|
|
141
213
|
if (SESSION_METADATA_UPDATES.has(kind)) {
|
|
142
214
|
if (this.nativeSessionId === void 0) {
|
|
143
215
|
const entry = this.queue.admit({ kind: "update", params }, nativeBytes(params) * 2);
|
|
@@ -147,7 +219,7 @@ class SessionState {
|
|
|
147
219
|
this.observeMetadata(update);
|
|
148
220
|
return;
|
|
149
221
|
}
|
|
150
|
-
if (this.replay.active) return;
|
|
222
|
+
if (this.replay.active || !liveOutput) return;
|
|
151
223
|
if (!this.turn || this.turn.nativeSettled || this.turn.terminal) {
|
|
152
224
|
throw new AcpProviderSessionError("acp_update_without_prompt", "ACP emitted turn content without an active native prompt.");
|
|
153
225
|
}
|
|
@@ -188,22 +260,47 @@ class SessionState {
|
|
|
188
260
|
clearTurnTimers() {
|
|
189
261
|
for (const cleanup of this.turn?.cleanup.splice(0) ?? []) cleanup();
|
|
190
262
|
}
|
|
263
|
+
async *streamContinuation(turn, ready) {
|
|
264
|
+
let stable = false;
|
|
265
|
+
try {
|
|
266
|
+
if (!await ready) {
|
|
267
|
+
stable = true;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
yield* this.emit(turn.mapping.event("turn.started", {}));
|
|
271
|
+
yield* this.drain(turn);
|
|
272
|
+
await this.requests.join();
|
|
273
|
+
stable = true;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
this.fail(sessionError(error));
|
|
276
|
+
throw error;
|
|
277
|
+
} finally {
|
|
278
|
+
if (!stable && !this.closed) this.fail(new AcpProviderSessionError("acp_turn_abandoned", "The native continuation reader was abandoned."));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
191
281
|
startTurn(input) {
|
|
192
282
|
this.requireUsable();
|
|
193
283
|
throwIfAgentOperationAborted(input.signal);
|
|
194
|
-
if (this.turn && !this.turn.
|
|
284
|
+
if (this.turn && !this.turn.sourceSettled) throw new AcpProviderSessionError("acp_turn_active", "The ACP session already has an active or waiting turn.");
|
|
195
285
|
if (input.interactionMode !== "default") {
|
|
196
286
|
throw new AcpProviderSessionError("acp_input_unsupported", "ACP supports only the default interaction mode.");
|
|
197
287
|
}
|
|
198
288
|
const prompt = prepareAcpPrompt(input.parts, this.input.capabilities);
|
|
289
|
+
const turn = this.createNativeTurn(input.turnId);
|
|
290
|
+
return this.execution(turn, () => this.streamTurn(turn, input, prompt));
|
|
291
|
+
}
|
|
292
|
+
createNativeTurn(turnId) {
|
|
293
|
+
this.requireUsable();
|
|
294
|
+
if (this.turn && !this.turn.sourceSettled) throw new AcpProviderSessionError("acp_turn_active", "The ACP session already has an active or waiting turn.");
|
|
199
295
|
const settled = deferred();
|
|
200
296
|
void settled.promise.catch(() => void 0);
|
|
201
297
|
const turn = {
|
|
202
|
-
turnId
|
|
298
|
+
turnId,
|
|
203
299
|
mapping: new TurnMapping({
|
|
204
300
|
sessionId: this.input.context.sessionId,
|
|
205
|
-
turnId
|
|
301
|
+
turnId,
|
|
206
302
|
workingDirectory: this.input.context.workingDirectory,
|
|
303
|
+
permissionMapping: this.input.permissionMapping,
|
|
207
304
|
now: this.input.now,
|
|
208
305
|
nextItemId: () => ++this.itemSequence,
|
|
209
306
|
nextRequestId: () => ++this.requestSequence,
|
|
@@ -216,22 +313,41 @@ class SessionState {
|
|
|
216
313
|
nativeStarted: false,
|
|
217
314
|
nativeSettled: false,
|
|
218
315
|
terminal: false,
|
|
316
|
+
sourceSettled: false,
|
|
219
317
|
waiting: void 0,
|
|
220
318
|
transition: void 0,
|
|
221
319
|
cancelReason: void 0,
|
|
222
320
|
cancellation: void 0
|
|
223
321
|
};
|
|
224
322
|
this.turn = turn;
|
|
323
|
+
return turn;
|
|
324
|
+
}
|
|
325
|
+
execution(turn, stream) {
|
|
225
326
|
let acquired = false;
|
|
226
327
|
const outputs = {
|
|
227
328
|
[Symbol.asyncIterator]: () => {
|
|
228
329
|
if (acquired) throw new AcpProviderSessionError("acp_turn_stream_owned", "The ACP turn output already has a consumer.");
|
|
229
330
|
acquired = true;
|
|
230
|
-
return this.
|
|
331
|
+
return this.ownedStream(turn, stream);
|
|
231
332
|
}
|
|
232
333
|
};
|
|
233
334
|
return Object.freeze({ turnId: turn.turnId, outputs, resolveRequest: (resolution) => this.resolveRequest(turn, resolution) });
|
|
234
335
|
}
|
|
336
|
+
async *ownedStream(turn, stream) {
|
|
337
|
+
yield* stream();
|
|
338
|
+
turn.sourceSettled = true;
|
|
339
|
+
}
|
|
340
|
+
completeNativeTurn(turn, payload) {
|
|
341
|
+
turn.nativeSettled = true;
|
|
342
|
+
if (this.requests.size > 0) {
|
|
343
|
+
this.fail(new AcpProviderSessionError("acp_terminal_with_request", "ACP completed native work with unanswered interaction requests."));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
turn.hostInteractions.abort(new DOMException("The ACP native execution settled.", "AbortError"));
|
|
347
|
+
this.clearTurnTimers();
|
|
348
|
+
this.queue.admit({ kind: "terminal", payload }, nativeBytes(payload), true);
|
|
349
|
+
turn.settled.resolve(payload);
|
|
350
|
+
}
|
|
235
351
|
launchTurn(turn, input, prompt) {
|
|
236
352
|
this.requireUsable();
|
|
237
353
|
throwIfAgentOperationAborted(input.signal);
|
|
@@ -244,16 +360,8 @@ class SessionState {
|
|
|
244
360
|
}, {
|
|
245
361
|
timeoutMs: this.input.limits.promptTimeoutMs,
|
|
246
362
|
onResponseReceived: (response) => {
|
|
247
|
-
turn.nativeSettled = true;
|
|
248
|
-
if (this.requests.size > 0) {
|
|
249
|
-
this.fail(new AcpProviderSessionError("acp_terminal_with_request", "ACP completed a prompt with unanswered interaction requests."));
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
turn.hostInteractions.abort(new DOMException("The ACP native prompt settled.", "AbortError"));
|
|
253
|
-
this.clearTurnTimers();
|
|
254
363
|
const payload = acpTurnCompletion(response, turn.cancelReason);
|
|
255
|
-
this.
|
|
256
|
-
turn.settled.resolve(payload);
|
|
364
|
+
this.completeNativeTurn(turn, payload);
|
|
257
365
|
}
|
|
258
366
|
}).then(() => void 0, (error) => {
|
|
259
367
|
if (!turn.nativeSettled) this.fail(sessionError(error));
|
|
@@ -319,10 +427,7 @@ class SessionState {
|
|
|
319
427
|
}
|
|
320
428
|
} else if (message.kind === "request") {
|
|
321
429
|
const request = message.request;
|
|
322
|
-
if (request.answered || turn.cancelReason)
|
|
323
|
-
entry.release();
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
430
|
+
if (request.answered || turn.cancelReason) continue;
|
|
326
431
|
if (request.kind === "permission") {
|
|
327
432
|
const mappedTool = turn.mapping.tool(nativeObject(request.params.toolCall, "permission tool"), true);
|
|
328
433
|
let next = mappedTool.next();
|
|
@@ -330,26 +435,42 @@ class SessionState {
|
|
|
330
435
|
if (turn.terminal) return "terminal";
|
|
331
436
|
yield* this.emit(next.value);
|
|
332
437
|
if (turn.terminal) return "terminal";
|
|
438
|
+
if (request.answered || turn.cancelReason) break;
|
|
333
439
|
next = mappedTool.next();
|
|
334
440
|
}
|
|
441
|
+
if (!next.done) continue;
|
|
335
442
|
const permission = turn.mapping.permission(request.params, next.value);
|
|
336
443
|
request.mapped = { kind: "permission", permission, request: permission.request };
|
|
444
|
+
if (permission.warning) {
|
|
445
|
+
yield* this.emit(turn.mapping.event("runtime.warning", permission.warning));
|
|
446
|
+
if (turn.terminal) return "terminal";
|
|
447
|
+
}
|
|
337
448
|
} else {
|
|
338
449
|
if (request.params.toolCallId != null && !turn.mapping.hasTool(nativeString(request.params.toolCallId, "elicitation tool identity"))) {
|
|
339
450
|
this.requests.reject(request, new AcpRpcHandlerError(-32602, "ACP form tool identity was not observed in this turn."));
|
|
340
451
|
continue;
|
|
341
452
|
}
|
|
342
453
|
if (request.host) {
|
|
343
|
-
void (
|
|
454
|
+
void this.requests.invokeHost(request).then((result) => this.requests.answer(request, result), (error) => this.requests.reject(request, sessionError(error)));
|
|
344
455
|
continue;
|
|
345
456
|
}
|
|
346
457
|
const flow = new AcpFormFlow(request.form, () => parseAgentRequestId(`acp:request:${++this.requestSequence}`));
|
|
347
458
|
request.mapped = { kind: "form", flow, request: flow.request };
|
|
348
459
|
}
|
|
460
|
+
if (request.answered || turn.cancelReason) continue;
|
|
349
461
|
turn.waiting = request;
|
|
350
462
|
yield* this.offerRequest(turn, request);
|
|
351
463
|
if (turn.terminal) return "terminal";
|
|
352
464
|
continue;
|
|
465
|
+
} else if (message.kind === "unavailable") {
|
|
466
|
+
try {
|
|
467
|
+
const request = message.request;
|
|
468
|
+
yield* this.emit(turn.mapping.event("request.unavailable", { requestId: message.requestId, reason: "withdrawn" }));
|
|
469
|
+
if (turn.waiting === request) turn.waiting = void 0;
|
|
470
|
+
yield* this.emit(turn.mapping.event("turn.state_changed", { state: "running" }));
|
|
471
|
+
} finally {
|
|
472
|
+
entry.release();
|
|
473
|
+
}
|
|
353
474
|
} else {
|
|
354
475
|
try {
|
|
355
476
|
if (this.requests.size > 0) throw new AcpProviderSessionError("acp_terminal_with_request", "ACP completed a prompt with unanswered interaction requests.");
|
|
@@ -361,6 +482,7 @@ class SessionState {
|
|
|
361
482
|
turn.terminal = true;
|
|
362
483
|
turn.waiting = void 0;
|
|
363
484
|
turn.mapping.release();
|
|
485
|
+
this.continuations.complete(turn);
|
|
364
486
|
this.clearTurnTimers();
|
|
365
487
|
return "terminal";
|
|
366
488
|
} finally {
|
|
@@ -371,6 +493,8 @@ class SessionState {
|
|
|
371
493
|
}
|
|
372
494
|
async *offerRequest(turn, request) {
|
|
373
495
|
const canonical = request.mapped.request;
|
|
496
|
+
if (!this.requests.canAnswer(request)) return;
|
|
497
|
+
request.publishedRequestId = canonical.requestId;
|
|
374
498
|
yield* this.emit(turn.mapping.event("request.opened", { request: canonical }));
|
|
375
499
|
if (turn.terminal) return;
|
|
376
500
|
yield* this.emit(turn.mapping.event("turn.state_changed", { state: "waiting_for_request", requestId: canonical.requestId }));
|
|
@@ -381,6 +505,7 @@ class SessionState {
|
|
|
381
505
|
this.launchTurn(turn, input, prompt);
|
|
382
506
|
yield* this.emit(turn.mapping.event("turn.started", {}));
|
|
383
507
|
yield* this.drain(turn);
|
|
508
|
+
await this.requests.join();
|
|
384
509
|
stable = true;
|
|
385
510
|
} catch (error) {
|
|
386
511
|
this.fail(sessionError(error));
|
|
@@ -395,6 +520,9 @@ class SessionState {
|
|
|
395
520
|
if (this.turn !== turn || turn.terminal || !request?.mapped) {
|
|
396
521
|
throw new AcpProviderSessionError("acp_request_not_pending", "The ACP resolution does not identify an available pending request.");
|
|
397
522
|
}
|
|
523
|
+
const gate = this.childWithdrawals?.answerGate(request);
|
|
524
|
+
if (gate) await awaitHost(gate, input.signal ?? this.input.connection.stopped);
|
|
525
|
+
throwIfAgentOperationAborted(input.signal);
|
|
398
526
|
const result = resolveAcpRequest(this.requests, turn, request, request.mapped, input);
|
|
399
527
|
if (result.status === "delivered") this.queue.wakeReader();
|
|
400
528
|
return result;
|
|
@@ -451,27 +579,49 @@ class SessionState {
|
|
|
451
579
|
throw error;
|
|
452
580
|
}
|
|
453
581
|
}
|
|
454
|
-
async close() {
|
|
455
|
-
if (this.closePromise)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
582
|
+
async close(observeFailure = false) {
|
|
583
|
+
if (!this.closePromise) {
|
|
584
|
+
this.closed = true;
|
|
585
|
+
this.closePromise = Promise.resolve().then(async () => {
|
|
586
|
+
await this.input.closeResources();
|
|
587
|
+
this.input.onClosed();
|
|
588
|
+
}).catch((error2) => {
|
|
589
|
+
this.cleanupFailure = error2;
|
|
590
|
+
this.closePromise = void 0;
|
|
591
|
+
throw error2;
|
|
592
|
+
});
|
|
593
|
+
this.input.connection.sessionInteractions?.stop({ kind: "session_closed" });
|
|
594
|
+
const error = new AcpProviderSessionError("acp_session_closed", "The ACP session is closed.");
|
|
595
|
+
this.turn?.hostInteractions.abort(error);
|
|
596
|
+
this.queue.fail(error);
|
|
597
|
+
this.requests.cancelAll();
|
|
598
|
+
this.turn?.settled.reject(error);
|
|
599
|
+
this.continuations.stop(error);
|
|
600
|
+
this.descendants?.stop();
|
|
601
|
+
this.childWithdrawals?.stop(error);
|
|
602
|
+
this.clearTurnTimers();
|
|
603
|
+
this.environment.close();
|
|
604
|
+
this.turn?.mapping.release();
|
|
605
|
+
this.detach();
|
|
606
|
+
this.input.connection.stopped.removeEventListener("abort", this.onStopped);
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
await this.closePromise;
|
|
610
|
+
} catch (cause) {
|
|
611
|
+
const failure2 = this.observeFailure(observeFailure);
|
|
612
|
+
if (failure2) throw new AggregateError([failure2, cause], "ACP session failure and cleanup remain owned.");
|
|
613
|
+
throw cause;
|
|
614
|
+
}
|
|
615
|
+
const failure = this.observeFailure(observeFailure);
|
|
616
|
+
if (failure) {
|
|
617
|
+
if (this.cleanupFailure !== void 0) throw new AggregateError([failure, this.cleanupFailure], "ACP session failed before cleanup completed.");
|
|
618
|
+
throw failure;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
observeFailure(explicit) {
|
|
622
|
+
if (!explicit || !this.input.connection.sessionInteractions || !this.failure || this.failureObserved) return void 0;
|
|
623
|
+
this.failureObserved = true;
|
|
624
|
+
return this.failure;
|
|
475
625
|
}
|
|
476
626
|
}
|
|
477
627
|
export {
|
package/dist/session/turn.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AcpJsonObject } from '@agen-ai/acp-transport';
|
|
2
|
-
import type { AgentTurnCompletedPayload, AgentTurnId } from '@agen-ai/agent-protocol';
|
|
2
|
+
import type { AgentRequestId, AgentTurnCompletedPayload, AgentTurnId } from '@agen-ai/agent-protocol';
|
|
3
3
|
import type { TurnMapping } from './mapping.js';
|
|
4
4
|
import type { deferred } from './queue.js';
|
|
5
5
|
import type { PendingSessionRequest } from './requests/pending.js';
|
|
@@ -9,6 +9,10 @@ export type SessionMessage = Readonly<{
|
|
|
9
9
|
}> | Readonly<{
|
|
10
10
|
kind: 'request';
|
|
11
11
|
request: PendingSessionRequest;
|
|
12
|
+
}> | Readonly<{
|
|
13
|
+
kind: 'unavailable';
|
|
14
|
+
request: PendingSessionRequest;
|
|
15
|
+
requestId: AgentRequestId;
|
|
12
16
|
}> | Readonly<{
|
|
13
17
|
kind: 'terminal';
|
|
14
18
|
payload: AgentTurnCompletedPayload;
|
|
@@ -23,6 +27,7 @@ export interface NativeTurn {
|
|
|
23
27
|
nativeStarted: boolean;
|
|
24
28
|
nativeSettled: boolean;
|
|
25
29
|
terminal: boolean;
|
|
30
|
+
sourceSettled: boolean;
|
|
26
31
|
waiting: PendingSessionRequest | undefined;
|
|
27
32
|
transition: Readonly<{
|
|
28
33
|
nextRequest?: PendingSessionRequest;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { AcpJsonValue, AcpReverseRequestContext } from '@agen-ai/acp-transport';
|
|
2
|
+
import type { AgentProviderConversationId, AgentSessionId } from '@agen-ai/agent-protocol';
|
|
3
|
+
import { type AgentProviderSessionInteractionContext } from '@agen-ai/agent-runtime';
|
|
4
|
+
import type { AcpProviderLimits } from '../limits.js';
|
|
5
|
+
import { type PreparePlanContinuation } from './request.js';
|
|
6
|
+
import type { AcpSessionInteractionSelection } from './contracts.js';
|
|
7
|
+
export type SessionInteractionStop = Readonly<{
|
|
8
|
+
kind: 'session_closed';
|
|
9
|
+
}> | Readonly<{
|
|
10
|
+
kind: 'failed';
|
|
11
|
+
cause: Error;
|
|
12
|
+
}>;
|
|
13
|
+
/** One connection owns one opening generation and one retained native session request. */
|
|
14
|
+
export declare class SessionInteractionBroker {
|
|
15
|
+
private readonly input;
|
|
16
|
+
private state;
|
|
17
|
+
private requestNumber;
|
|
18
|
+
private expectedNativeId;
|
|
19
|
+
private prepareContinuation;
|
|
20
|
+
private retained;
|
|
21
|
+
private readonly entered;
|
|
22
|
+
private openingTimer;
|
|
23
|
+
private readonly onStopped;
|
|
24
|
+
readonly handleRequest: (context: AcpReverseRequestContext) => Promise<AcpJsonValue>;
|
|
25
|
+
constructor(input: Readonly<{
|
|
26
|
+
stopped: AbortSignal;
|
|
27
|
+
limits: AcpProviderLimits;
|
|
28
|
+
selection: AcpSessionInteractionSelection;
|
|
29
|
+
onFailure: (cause: Error) => void;
|
|
30
|
+
}>);
|
|
31
|
+
attachContinuationOwner(prepare: PreparePlanContinuation): void;
|
|
32
|
+
humanTimeoutMs(): number | undefined;
|
|
33
|
+
get selectedLifecycle(): boolean;
|
|
34
|
+
prepareOpening(input: Readonly<{
|
|
35
|
+
sessionId: AgentSessionId;
|
|
36
|
+
workingDirectory: string;
|
|
37
|
+
interactions: AgentProviderSessionInteractionContext;
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
}>): void;
|
|
40
|
+
onOpeningDispatched(): void;
|
|
41
|
+
bindNativeSession(nativeId: AgentProviderConversationId): void;
|
|
42
|
+
completeOpeningResponse(): void;
|
|
43
|
+
adopt(): void;
|
|
44
|
+
stop(reason: SessionInteractionStop): void;
|
|
45
|
+
private clearOpeningTimer;
|
|
46
|
+
private stateError;
|
|
47
|
+
private throwIfStopped;
|
|
48
|
+
join(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=broker.d.ts.map
|