@agen-ai/provider-acp 0.4.0 → 0.4.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 +355 -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 +21 -0
- package/dist/connection/errors.js +0 -1
- package/dist/connection/initialize.d.ts +7 -3
- package/dist/connection/initialize.js +46 -17
- package/dist/connection/negotiation.d.ts +2 -1
- package/dist/connection/negotiation.js +15 -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.js +0 -1
- package/dist/host/leases.js +0 -1
- package/dist/host/router.js +0 -1
- 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 +102 -27
- 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/create.d.ts +7 -1
- package/dist/session/create.js +3 -1
- 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/opening.d.ts +1 -0
- package/dist/session/opening.js +3 -1
- package/dist/session/queue.js +0 -1
- package/dist/session/requests/forms.js +0 -2
- package/dist/session/requests/pending.js +0 -1
- package/dist/session/resources.js +5 -3
- package/dist/session/session.d.ts +4 -1
- package/dist/session/session.js +67 -25
- package/dist/sessionInteractions/broker.d.ts +42 -0
- package/dist/sessionInteractions/broker.js +171 -0
- package/dist/sessionInteractions/contracts.d.ts +3 -0
- package/dist/sessionInteractions/contracts.js +0 -0
- package/dist/sessionInteractions/extensions.d.ts +3 -0
- package/dist/sessionInteractions/extensions.js +17 -0
- package/dist/sessionInteractions/grokFolderTrust.d.ts +43 -0
- package/dist/sessionInteractions/grokFolderTrust.js +107 -0
- package/dist/sessionInteractions/request.d.ts +38 -0
- package/dist/sessionInteractions/request.js +145 -0
- package/package.json +4 -4
package/dist/instance.js
CHANGED
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
createAgentProviderReadiness,
|
|
7
7
|
createAgentProviderSessionCleanupError,
|
|
8
8
|
isAgentProviderSessionCleanupErrorFor,
|
|
9
|
-
throwIfAgentOperationAborted
|
|
9
|
+
throwIfAgentOperationAborted,
|
|
10
|
+
validateAgentProviderAdapter,
|
|
11
|
+
AgentProviderContractError,
|
|
12
|
+
DEFAULT_AGENT_SESSION_INTERACTION_LIMITS
|
|
10
13
|
} from "@agen-ai/agent-runtime";
|
|
11
14
|
import { createAcpAuthentication } from "./auth/controller.js";
|
|
12
15
|
import { selectedAuthenticationMethods } from "./auth/methods.js";
|
|
@@ -17,18 +20,33 @@ import { negotiatedEnvelope } from "./connection/negotiation.js";
|
|
|
17
20
|
import { assertAcpMcpSupport, supportsAcpSessionMcpBinding } from "./mcp.js";
|
|
18
21
|
import { createAcpProviderSession } from "./session/create.js";
|
|
19
22
|
import { AcpSessionResources } from "./session/resources.js";
|
|
23
|
+
import { awaitHost, hostDeadline } from "./host/lifecycle.js";
|
|
24
|
+
import { isNodeAcpExecutionCleanupError, NodeAcpExecutionCleanupError } from "./node/execution/errors.js";
|
|
20
25
|
async function createAcpProviderInstance(input) {
|
|
21
26
|
const lifetime = new AbortController();
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
const executions = /* @__PURE__ */ new Set();
|
|
28
|
+
const connect = (signal) => {
|
|
29
|
+
throwIfAgentOperationAborted(signal);
|
|
30
|
+
const source = input.connectionSource.kind === "factory" ? input.connectionSource : { kind: "reserved", owner: input.connectionSource.reserve() };
|
|
31
|
+
if (source.kind === "reserved") {
|
|
32
|
+
executions.add(source.owner);
|
|
33
|
+
source.owner.whenDrained(() => {
|
|
34
|
+
executions.delete(source.owner);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return initializeAcpConnection({
|
|
38
|
+
source,
|
|
39
|
+
unknownExtensionPolicy: input.unknownExtensionPolicy,
|
|
40
|
+
context: { instanceId: input.instanceId, signal },
|
|
41
|
+
limits: input.limits,
|
|
42
|
+
usedStreams: input.usedStreams,
|
|
43
|
+
configuration: input.configuration,
|
|
44
|
+
authentication: input.authentication,
|
|
45
|
+
host: input.host,
|
|
46
|
+
permissionMapping: input.permissionMapping,
|
|
47
|
+
...input.sessionInteractions ? { sessionInteractions: input.sessionInteractions } : {}
|
|
48
|
+
});
|
|
49
|
+
};
|
|
32
50
|
let eager = await connect(lifetime.signal);
|
|
33
51
|
const negotiated = eager.negotiated;
|
|
34
52
|
let methods;
|
|
@@ -36,11 +54,13 @@ async function createAcpProviderInstance(input) {
|
|
|
36
54
|
try {
|
|
37
55
|
assertAcpMcpSupport(input.mcpServers, negotiated);
|
|
38
56
|
methods = selectedAuthenticationMethods(negotiated, input.authentication, input.host);
|
|
39
|
-
capabilities = acpProviderCapabilities(input.providerKey, negotiated, input.configuration, [...methods.keys()]);
|
|
57
|
+
capabilities = acpProviderCapabilities(input.providerKey, negotiated, input.configuration, [...methods.keys()], input.sessionInteractions, input.permissionMapping);
|
|
40
58
|
} catch (cause) {
|
|
59
|
+
if (eager.execution?.cleanupFailure) throw eager.execution.retainedError(cause, eager.execution.cleanupFailure.error);
|
|
41
60
|
try {
|
|
42
61
|
await eager.close();
|
|
43
62
|
} catch (cleanupError) {
|
|
63
|
+
if (eager.execution) throw eager.execution.retainedError(cause, cleanupError);
|
|
44
64
|
throw new AggregateError([cause, cleanupError], "ACP materialization and cleanup failed.");
|
|
45
65
|
}
|
|
46
66
|
throw cause;
|
|
@@ -52,7 +72,8 @@ async function createAcpProviderInstance(input) {
|
|
|
52
72
|
const creatingIds = /* @__PURE__ */ new Set();
|
|
53
73
|
const pending = /* @__PURE__ */ new Set();
|
|
54
74
|
let disposePromise;
|
|
55
|
-
|
|
75
|
+
let disposeDeadline = 0;
|
|
76
|
+
const cleanupPending = () => [...resources].some((resource) => resource.cleanupPending) || [...executions].some((execution) => execution.cleanupPending);
|
|
56
77
|
const bindingSupported = supportsAcpSessionMcpBinding(input.sessionMcpBinding, negotiated);
|
|
57
78
|
const now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
58
79
|
const retire = async (connection) => {
|
|
@@ -60,6 +81,10 @@ async function createAcpProviderInstance(input) {
|
|
|
60
81
|
await connection.close();
|
|
61
82
|
connections.delete(connection);
|
|
62
83
|
};
|
|
84
|
+
const closeConnection = async (connection) => {
|
|
85
|
+
await connection.close();
|
|
86
|
+
if (input.sessionInteractions) connections.delete(connection);
|
|
87
|
+
};
|
|
63
88
|
const acquire = async (signal) => {
|
|
64
89
|
const reserved = eager;
|
|
65
90
|
eager = void 0;
|
|
@@ -67,14 +92,22 @@ async function createAcpProviderInstance(input) {
|
|
|
67
92
|
connections.add(connection);
|
|
68
93
|
try {
|
|
69
94
|
throwIfAgentOperationAborted(signal);
|
|
95
|
+
if (input.sessionInteractions && connection.stopped.aborted) throw connection.stopped.reason;
|
|
70
96
|
if (negotiatedEnvelope(connection.negotiated) !== envelope) {
|
|
71
97
|
throw new AcpProviderConnectionError("acp_provider_negotiation_changed", "The ACP peer changed its initialized behavior envelope.");
|
|
72
98
|
}
|
|
73
99
|
return connection;
|
|
74
100
|
} catch (cause) {
|
|
101
|
+
const retained = (cleanupError) => new NodeAcpExecutionCleanupError({
|
|
102
|
+
cause,
|
|
103
|
+
cleanupError,
|
|
104
|
+
retryCleanup: () => retire(connection)
|
|
105
|
+
});
|
|
106
|
+
if (connection.execution?.cleanupFailure) throw retained(connection.execution.cleanupFailure.error);
|
|
75
107
|
try {
|
|
76
108
|
await retire(connection);
|
|
77
109
|
} catch (cleanupError) {
|
|
110
|
+
if (connection.execution) throw retained(cleanupError);
|
|
78
111
|
throw new AggregateError([cause, cleanupError], "ACP negotiation and cleanup failed.");
|
|
79
112
|
}
|
|
80
113
|
throw cause;
|
|
@@ -101,6 +134,16 @@ async function createAcpProviderInstance(input) {
|
|
|
101
134
|
const open = async (context, resume) => {
|
|
102
135
|
throwIfAgentOperationAborted(context.signal);
|
|
103
136
|
throwIfAgentOperationAborted(lifetime.signal);
|
|
137
|
+
if (input.sessionInteractions && resume) throw new AgentProviderContractError(
|
|
138
|
+
input.providerKey,
|
|
139
|
+
"session_interactions_unsupported",
|
|
140
|
+
"The selected ACP session interaction mapping supports creation only."
|
|
141
|
+
);
|
|
142
|
+
if (input.sessionInteractions && context.interactions === void 0) throw new AgentProviderContractError(
|
|
143
|
+
input.providerKey,
|
|
144
|
+
"invalid_session_interaction",
|
|
145
|
+
"The selected ACP session interaction mapping requires a handler before opening."
|
|
146
|
+
);
|
|
104
147
|
if (authentication.active) throw new AcpProviderConnectionError("acp_authentication_active", "Authentication owns the ACP instance.");
|
|
105
148
|
if (authentication.cleanupPending) throw new AcpProviderConnectionError("acp_authentication_cleanup_pending", "Authentication terminal cleanup must complete before session mutation.");
|
|
106
149
|
if (cleanupPending()) throw new AcpProviderConnectionError("acp_session_cleanup_pending", "Session resource cleanup must complete before session mutation.");
|
|
@@ -126,6 +169,14 @@ async function createAcpProviderInstance(input) {
|
|
|
126
169
|
let opened;
|
|
127
170
|
try {
|
|
128
171
|
connection = await acquire(signal);
|
|
172
|
+
if (context.interactions) {
|
|
173
|
+
connection.sessionInteractions?.prepareOpening({
|
|
174
|
+
sessionId: parsed.sessionId,
|
|
175
|
+
workingDirectory: context.workingDirectory,
|
|
176
|
+
interactions: context.interactions,
|
|
177
|
+
signal
|
|
178
|
+
});
|
|
179
|
+
}
|
|
129
180
|
const ownedConnection = connection;
|
|
130
181
|
resource = new AcpSessionResources({
|
|
131
182
|
instanceId: input.instanceId,
|
|
@@ -145,7 +196,7 @@ async function createAcpProviderInstance(input) {
|
|
|
145
196
|
throwIfAgentOperationAborted(signal);
|
|
146
197
|
const closeResources = () => resource.close();
|
|
147
198
|
const sessionCapabilities = negotiated.agentCapabilities.sessionCapabilities;
|
|
148
|
-
|
|
199
|
+
opened = await createAcpProviderSession({
|
|
149
200
|
context: { ...context, sessionId: parsed.sessionId, configuration: parsed.configuration, signal },
|
|
150
201
|
openingInput: context,
|
|
151
202
|
...parsed.operation === "resume" ? { resume: {
|
|
@@ -157,20 +208,27 @@ async function createAcpProviderInstance(input) {
|
|
|
157
208
|
capabilities,
|
|
158
209
|
limits: input.limits,
|
|
159
210
|
now,
|
|
211
|
+
permissionMapping: input.permissionMapping,
|
|
160
212
|
onClosed: () => {
|
|
161
213
|
sessions.delete(parsed.sessionId);
|
|
162
214
|
connections.delete(ownedConnection);
|
|
163
215
|
},
|
|
164
216
|
closeResources
|
|
165
217
|
});
|
|
166
|
-
opened = session;
|
|
167
218
|
throwIfAgentOperationAborted(signal);
|
|
168
|
-
|
|
169
|
-
|
|
219
|
+
connection.sessionInteractions?.adopt();
|
|
220
|
+
sessions.set(parsed.sessionId, opened.session);
|
|
221
|
+
return opened.session;
|
|
170
222
|
} catch (cause) {
|
|
171
223
|
if (isAgentProviderSessionCleanupErrorFor(cause, context) && cause.providerKey === input.providerKey) throw cause;
|
|
224
|
+
if (isNodeAcpExecutionCleanupError(cause)) throw createAgentProviderSessionCleanupError({
|
|
225
|
+
providerKey: input.providerKey,
|
|
226
|
+
openingInput: context,
|
|
227
|
+
errors: [cause],
|
|
228
|
+
retryCleanup: cause.retryCleanup
|
|
229
|
+
});
|
|
172
230
|
if (resource || connection) {
|
|
173
|
-
const cleanup = () => opened ? opened.
|
|
231
|
+
const cleanup = () => opened ? opened.cleanup() : resource ? resource.close() : retire(connection);
|
|
174
232
|
try {
|
|
175
233
|
await cleanup();
|
|
176
234
|
} catch (cleanupError) {
|
|
@@ -193,16 +251,23 @@ async function createAcpProviderInstance(input) {
|
|
|
193
251
|
creatingIds.delete(parsed.sessionId);
|
|
194
252
|
}
|
|
195
253
|
};
|
|
254
|
+
const adapter = Object.freeze({
|
|
255
|
+
...input.sessionInteractions ? { sessionInteractions: Object.freeze({
|
|
256
|
+
kind: "supported",
|
|
257
|
+
openingOperations: Object.freeze(["create"]),
|
|
258
|
+
requestKinds: Object.freeze(["decision"]),
|
|
259
|
+
producerOwnership: "exclusive"
|
|
260
|
+
}) } : {},
|
|
261
|
+
hostBinding: bindingSupported ? Object.freeze({ kind: "supported", toolTransport: "mcp", instructionPlacements: Object.freeze(["tool_context"]) }) : Object.freeze({ kind: "unsupported" }),
|
|
262
|
+
createSession: (context) => open(context, false),
|
|
263
|
+
resumption: capabilities.sessions.resume ? Object.freeze({ kind: "supported", resumeSession: (context) => open(context, true) }) : Object.freeze({ kind: "unsupported" }),
|
|
264
|
+
branching: { kind: "unsupported" },
|
|
265
|
+
authentication: authentication.port
|
|
266
|
+
});
|
|
196
267
|
return Object.freeze({
|
|
197
268
|
instanceId: input.instanceId,
|
|
198
269
|
capabilities,
|
|
199
|
-
adapter:
|
|
200
|
-
hostBinding: bindingSupported ? Object.freeze({ kind: "supported", toolTransport: "mcp", instructionPlacements: Object.freeze(["tool_context"]) }) : Object.freeze({ kind: "unsupported" }),
|
|
201
|
-
createSession: (context) => open(context, false),
|
|
202
|
-
resumption: capabilities.sessions.resume ? Object.freeze({ kind: "supported", resumeSession: (context) => open(context, true) }) : Object.freeze({ kind: "unsupported" }),
|
|
203
|
-
branching: { kind: "unsupported" },
|
|
204
|
-
authentication: authentication.port
|
|
205
|
-
}),
|
|
270
|
+
adapter: input.sessionInteractions ? validateAgentProviderAdapter(capabilities, adapter) : adapter,
|
|
206
271
|
accountQuota: { kind: "unsupported" },
|
|
207
272
|
environment: { kind: "unsupported" },
|
|
208
273
|
checkReadiness(check = {}) {
|
|
@@ -217,18 +282,24 @@ async function createAcpProviderInstance(input) {
|
|
|
217
282
|
},
|
|
218
283
|
dispose() {
|
|
219
284
|
if (!disposePromise) {
|
|
285
|
+
disposeDeadline = Date.now() + Math.min(input.limits.hostOperationTimeoutMs, DEFAULT_AGENT_SESSION_INTERACTION_LIMITS.cleanupTimeoutMs);
|
|
220
286
|
disposePromise = Promise.resolve().then(async () => {
|
|
221
287
|
const failures = /* @__PURE__ */ new Set();
|
|
222
288
|
const closingConnections = new Set(connections);
|
|
289
|
+
if (input.sessionInteractions) for (const connection of closingConnections) {
|
|
290
|
+
const reason = connection.stopped.reason;
|
|
291
|
+
if (connection.stopped.aborted && reason instanceof Error && !(reason instanceof AcpProviderConnectionError && reason.reasonCode === "acp_provider_connection_closed")) failures.add(reason);
|
|
292
|
+
}
|
|
223
293
|
const closing = await Promise.allSettled([
|
|
224
294
|
authentication.close(),
|
|
225
295
|
...[...sessions.values()].map((session) => session.close({ reason: "shutdown" })),
|
|
226
296
|
...[...resources].map((resource) => resource.close()),
|
|
227
|
-
...[...closingConnections].map(
|
|
297
|
+
...[...closingConnections].map(closeConnection),
|
|
298
|
+
...[...executions].map((execution) => execution.cleanup())
|
|
228
299
|
]);
|
|
229
300
|
for (const result of closing) if (result.status === "rejected") failures.add(result.reason);
|
|
230
301
|
await Promise.allSettled([...pending]);
|
|
231
|
-
const remaining = await Promise.allSettled([...connections].filter((connection) => !closingConnections.has(connection)).map(
|
|
302
|
+
const remaining = await Promise.allSettled([...connections].filter((connection) => !closingConnections.has(connection)).map(closeConnection));
|
|
232
303
|
for (const result of remaining) if (result.status === "rejected") failures.add(result.reason);
|
|
233
304
|
if (failures.size) throw new AggregateError(failures, "ACP instance cleanup failed.");
|
|
234
305
|
connections.clear();
|
|
@@ -239,6 +310,10 @@ async function createAcpProviderInstance(input) {
|
|
|
239
310
|
});
|
|
240
311
|
lifetime.abort(new AcpProviderConnectionError("acp_provider_instance_disposed", "The ACP instance was disposed."));
|
|
241
312
|
}
|
|
313
|
+
if (input.sessionInteractions || input.connectionSource.kind === "reserved") {
|
|
314
|
+
const deadline = hostDeadline(new AbortController().signal, Math.max(0, disposeDeadline - Date.now()));
|
|
315
|
+
return awaitHost(disposePromise, deadline.signal).finally(() => deadline.close());
|
|
316
|
+
}
|
|
242
317
|
return disposePromise;
|
|
243
318
|
}
|
|
244
319
|
});
|
package/dist/node/contracts.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AcpProviderLimits } from '../limits.js';
|
|
2
2
|
import type { CreateAcpProviderDriverOptions } from '../contracts.js';
|
|
3
|
+
import type { NodeAcpExecutionBoundary } from './execution/types.js';
|
|
3
4
|
export interface NodeAcpLaunchOptions {
|
|
4
5
|
readonly executable: string;
|
|
5
6
|
readonly args?: readonly string[];
|
|
@@ -10,6 +11,11 @@ export interface NodeAcpLaunchOptions {
|
|
|
10
11
|
}
|
|
11
12
|
export interface CreateNodeAcpProviderDriverOptions extends Omit<CreateAcpProviderDriverOptions, 'createConnection' | 'limits'> {
|
|
12
13
|
readonly launch: NodeAcpLaunchOptions;
|
|
14
|
+
readonly executionBoundary?: NodeAcpExecutionBoundary;
|
|
15
|
+
/** Requires a genuine direct native executable, exact no-leader arguments and a handler for every created session. */
|
|
16
|
+
readonly sessionInteractions?: Readonly<{
|
|
17
|
+
readonly mapping: 'grok-folder-trust-v1';
|
|
18
|
+
}>;
|
|
13
19
|
readonly limits?: Partial<AcpProviderLimits>;
|
|
14
20
|
}
|
|
15
21
|
//# sourceMappingURL=contracts.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class NodeAcpExecutionCleanupError extends Error {
|
|
2
|
+
readonly cleanupError: unknown;
|
|
3
|
+
readonly retryCleanup: () => Promise<void>;
|
|
4
|
+
constructor(input: Readonly<{
|
|
5
|
+
cause: unknown;
|
|
6
|
+
cleanupError: unknown;
|
|
7
|
+
retryCleanup: () => Promise<void>;
|
|
8
|
+
}>);
|
|
9
|
+
}
|
|
10
|
+
export declare function isNodeAcpExecutionCleanupError(error: unknown): error is NodeAcpExecutionCleanupError;
|
|
11
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class NodeAcpExecutionCleanupError extends Error {
|
|
2
|
+
cleanupError;
|
|
3
|
+
retryCleanup;
|
|
4
|
+
constructor(input) {
|
|
5
|
+
super("The owned Node ACP execution cleanup remains incomplete.", { cause: input.cause });
|
|
6
|
+
this.name = "NodeAcpExecutionCleanupError";
|
|
7
|
+
this.cleanupError = input.cleanupError;
|
|
8
|
+
this.retryCleanup = input.retryCleanup;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function isNodeAcpExecutionCleanupError(error) {
|
|
12
|
+
return error instanceof NodeAcpExecutionCleanupError;
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
NodeAcpExecutionCleanupError,
|
|
16
|
+
isNodeAcpExecutionCleanupError
|
|
17
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AcpConnectionFactoryContext, AcpConnectionLease } from '../../contracts.js';
|
|
2
|
+
import { NodeAcpExecutionCleanupError } from './errors.js';
|
|
3
|
+
import type { NodeAcpExecutionBoundary, NodeAcpExecutionInput, NodeAcpExecutionReservation } from './types.js';
|
|
4
|
+
export declare class NodeAcpExecutionOwner {
|
|
5
|
+
private readonly cleanupTimeoutMs;
|
|
6
|
+
private state;
|
|
7
|
+
private startup;
|
|
8
|
+
private releaseState;
|
|
9
|
+
private readonly releaseComplete;
|
|
10
|
+
private failureNotice;
|
|
11
|
+
private cleanupAttempt;
|
|
12
|
+
private observation;
|
|
13
|
+
private cleanupWork;
|
|
14
|
+
private onDrained;
|
|
15
|
+
private failure;
|
|
16
|
+
private readonly startReservation;
|
|
17
|
+
private readonly releaseReservation;
|
|
18
|
+
constructor(reservation: NodeAcpExecutionReservation, cleanupTimeoutMs: number);
|
|
19
|
+
get cleanupPending(): boolean;
|
|
20
|
+
get cleanupFailure(): Readonly<{
|
|
21
|
+
error: unknown;
|
|
22
|
+
}> | undefined;
|
|
23
|
+
retainCleanup(work: () => Promise<void>): void;
|
|
24
|
+
whenDrained(callback: () => void): void;
|
|
25
|
+
start(context: AcpConnectionFactoryContext): Promise<AcpConnectionLease>;
|
|
26
|
+
/** Actual release is unbounded here; one outer observation owns the full connection deadline. */
|
|
27
|
+
release(): Promise<void>;
|
|
28
|
+
private beginRelease;
|
|
29
|
+
cleanup(): Promise<void>;
|
|
30
|
+
retainedError(cause: unknown, cleanupError: unknown): NodeAcpExecutionCleanupError;
|
|
31
|
+
joinCleanup(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare function executionReservationSource(boundary: NodeAcpExecutionBoundary, input: NodeAcpExecutionInput): () => NodeAcpExecutionOwner;
|
|
34
|
+
//# sourceMappingURL=owner.d.ts.map
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { AcpProviderConnectionError } from "../../connection/errors.js";
|
|
2
|
+
import { NodeAcpExecutionCleanupError } from "./errors.js";
|
|
3
|
+
class NodeAcpExecutionOwner {
|
|
4
|
+
constructor(reservation, cleanupTimeoutMs) {
|
|
5
|
+
this.cleanupTimeoutMs = cleanupTimeoutMs;
|
|
6
|
+
const release = reservation.release;
|
|
7
|
+
const start = reservation.start;
|
|
8
|
+
if (typeof release !== "function" || typeof start !== "function") {
|
|
9
|
+
throw new TypeError("An execution reservation must provide callable start and release methods.");
|
|
10
|
+
}
|
|
11
|
+
this.startReservation = (context) => Reflect.apply(start, reservation, [context]);
|
|
12
|
+
this.releaseReservation = () => Reflect.apply(release, reservation, []);
|
|
13
|
+
}
|
|
14
|
+
state = "reserved";
|
|
15
|
+
startup;
|
|
16
|
+
releaseState = { kind: "idle" };
|
|
17
|
+
releaseComplete = Promise.withResolvers();
|
|
18
|
+
failureNotice = Promise.withResolvers();
|
|
19
|
+
cleanupAttempt;
|
|
20
|
+
observation;
|
|
21
|
+
cleanupWork = () => this.release();
|
|
22
|
+
onDrained = () => {
|
|
23
|
+
};
|
|
24
|
+
failure;
|
|
25
|
+
startReservation;
|
|
26
|
+
releaseReservation;
|
|
27
|
+
get cleanupPending() {
|
|
28
|
+
return this.state === "stopping";
|
|
29
|
+
}
|
|
30
|
+
get cleanupFailure() {
|
|
31
|
+
return this.failure;
|
|
32
|
+
}
|
|
33
|
+
retainCleanup(work) {
|
|
34
|
+
this.cleanupWork = work;
|
|
35
|
+
}
|
|
36
|
+
whenDrained(callback) {
|
|
37
|
+
this.onDrained = callback;
|
|
38
|
+
}
|
|
39
|
+
start(context) {
|
|
40
|
+
if (this.state !== "reserved") return Promise.reject(new TypeError("Execution startup is once-only and cannot follow release."));
|
|
41
|
+
this.state = "active";
|
|
42
|
+
this.startup = Promise.resolve().then(() => {
|
|
43
|
+
if (this.state !== "active") throw new DOMException("Execution startup was stopped.", "AbortError");
|
|
44
|
+
context.signal?.throwIfAborted();
|
|
45
|
+
return this.startReservation(Object.freeze({ ...context }));
|
|
46
|
+
});
|
|
47
|
+
void this.startup.catch(() => {
|
|
48
|
+
});
|
|
49
|
+
return this.startup.then((streams) => {
|
|
50
|
+
const stdout = streams.stdout;
|
|
51
|
+
const stdin = streams.stdin;
|
|
52
|
+
const stderr = streams.stderr;
|
|
53
|
+
return { ownership: "owned", stdout, stdin, ...stderr === void 0 ? {} : { stderr }, release: () => this.release() };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** Actual release is unbounded here; one outer observation owns the full connection deadline. */
|
|
57
|
+
release() {
|
|
58
|
+
if (this.state === "released") return Promise.resolve();
|
|
59
|
+
this.state = "stopping";
|
|
60
|
+
if (this.releaseState.kind === "idle") this.beginRelease();
|
|
61
|
+
return this.releaseComplete.promise;
|
|
62
|
+
}
|
|
63
|
+
beginRelease() {
|
|
64
|
+
if (this.releaseState.kind === "running" || this.releaseState.kind === "complete") return;
|
|
65
|
+
const operation = Promise.withResolvers();
|
|
66
|
+
const actual = operation.promise.then(async () => {
|
|
67
|
+
await this.startup?.catch(() => void 0);
|
|
68
|
+
this.releaseState = { kind: "complete" };
|
|
69
|
+
this.releaseComplete.resolve();
|
|
70
|
+
}).catch((error) => {
|
|
71
|
+
this.releaseState = { kind: "failed", error };
|
|
72
|
+
this.failure = { error };
|
|
73
|
+
this.failureNotice.resolve(error);
|
|
74
|
+
});
|
|
75
|
+
this.releaseState = { kind: "running", actual };
|
|
76
|
+
try {
|
|
77
|
+
Promise.resolve(this.releaseReservation()).then(operation.resolve, operation.reject);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
operation.reject(error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
cleanup() {
|
|
83
|
+
if (this.state === "released") return Promise.resolve();
|
|
84
|
+
this.state = "stopping";
|
|
85
|
+
if (this.observation) return this.observation;
|
|
86
|
+
const deadline = performance.now() + this.cleanupTimeoutMs;
|
|
87
|
+
this.failureNotice = Promise.withResolvers();
|
|
88
|
+
let initiate;
|
|
89
|
+
let actual = this.cleanupAttempt;
|
|
90
|
+
if (!actual) {
|
|
91
|
+
const operation = Promise.withResolvers();
|
|
92
|
+
actual = operation.promise.then(() => {
|
|
93
|
+
this.state = "released";
|
|
94
|
+
this.failure = void 0;
|
|
95
|
+
this.onDrained();
|
|
96
|
+
}).catch((error) => {
|
|
97
|
+
this.failure = { error };
|
|
98
|
+
this.cleanupAttempt = void 0;
|
|
99
|
+
throw error;
|
|
100
|
+
});
|
|
101
|
+
this.cleanupAttempt = actual;
|
|
102
|
+
initiate = () => {
|
|
103
|
+
try {
|
|
104
|
+
Promise.resolve(this.cleanupWork()).then(operation.resolve, operation.reject);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
operation.reject(error);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const cleanup = actual;
|
|
111
|
+
this.observation = new Promise((resolve, reject) => {
|
|
112
|
+
const timeout = setTimeout(() => reject(new AcpProviderConnectionError(
|
|
113
|
+
"acp_node_execution_cleanup_timeout",
|
|
114
|
+
"The owned Node ACP execution cleanup exceeded its observation deadline."
|
|
115
|
+
)), Math.max(0, deadline - performance.now()));
|
|
116
|
+
cleanup.then(() => {
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
resolve();
|
|
119
|
+
}, (error) => {
|
|
120
|
+
clearTimeout(timeout);
|
|
121
|
+
reject(error);
|
|
122
|
+
});
|
|
123
|
+
this.failureNotice.promise.then((error) => {
|
|
124
|
+
clearTimeout(timeout);
|
|
125
|
+
reject(error);
|
|
126
|
+
});
|
|
127
|
+
}).catch((error) => {
|
|
128
|
+
this.failure ??= { error };
|
|
129
|
+
throw error;
|
|
130
|
+
}).finally(() => {
|
|
131
|
+
this.observation = void 0;
|
|
132
|
+
});
|
|
133
|
+
if (this.releaseState.kind === "failed") this.beginRelease();
|
|
134
|
+
initiate?.();
|
|
135
|
+
return this.observation;
|
|
136
|
+
}
|
|
137
|
+
retainedError(cause, cleanupError) {
|
|
138
|
+
return new NodeAcpExecutionCleanupError({ cause, cleanupError, retryCleanup: () => this.cleanup() });
|
|
139
|
+
}
|
|
140
|
+
joinCleanup() {
|
|
141
|
+
if (this.cleanupAttempt) return this.cleanupAttempt;
|
|
142
|
+
if (this.state === "released") return Promise.resolve();
|
|
143
|
+
return Promise.reject(this.failure?.error ?? new Error("Execution cleanup has not started."));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function executionReservationSource(boundary, input) {
|
|
147
|
+
const reserve = boundary?.reserve;
|
|
148
|
+
if (typeof reserve !== "function") throw new TypeError("A Node ACP execution boundary must provide a synchronous reserve method.");
|
|
149
|
+
const used = /* @__PURE__ */ new WeakSet();
|
|
150
|
+
return () => {
|
|
151
|
+
const reservation = Reflect.apply(reserve, boundary, [input]);
|
|
152
|
+
if (!reservation || typeof reservation !== "object" || "then" in reservation || used.has(reservation)) {
|
|
153
|
+
throw new TypeError("Execution reserve must return a fresh synchronous inert reservation.");
|
|
154
|
+
}
|
|
155
|
+
used.add(reservation);
|
|
156
|
+
return new NodeAcpExecutionOwner(reservation, 2 * input.shutdownTimeoutMs);
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
NodeAcpExecutionOwner,
|
|
161
|
+
executionReservationSource
|
|
162
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MaybePromise } from '@agen-ai/agent-runtime';
|
|
2
|
+
import type { AcpConnectionFactoryContext, AcpConnectionStreams } from '../../contracts.js';
|
|
3
|
+
export interface NodeAcpExecutionInput {
|
|
4
|
+
readonly executable: string;
|
|
5
|
+
readonly args: readonly string[];
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
readonly env: Readonly<Record<string, string>>;
|
|
8
|
+
readonly shutdownTimeoutMs: number;
|
|
9
|
+
}
|
|
10
|
+
/** Reserve fresh inert state; resource acquisition and execution belong to start. */
|
|
11
|
+
export interface NodeAcpExecutionBoundary {
|
|
12
|
+
readonly reserve: (input: NodeAcpExecutionInput) => NodeAcpExecutionReservation;
|
|
13
|
+
}
|
|
14
|
+
/** Own the workload before execution, including descendants in other groups or sessions. */
|
|
15
|
+
export interface NodeAcpExecutionReservation {
|
|
16
|
+
readonly start: (context: AcpConnectionFactoryContext) => MaybePromise<AcpConnectionStreams>;
|
|
17
|
+
/** Fence startup, terminate and join the workload and streams; failed release remains retryable. */
|
|
18
|
+
readonly release: () => MaybePromise<void>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
|
File without changes
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AgentProviderDriver } from '@agen-ai/agent-runtime';
|
|
2
2
|
import type { CreateNodeAcpProviderDriverOptions } from './contracts.js';
|
|
3
3
|
export type { NodeAcpLaunchOptions, CreateNodeAcpProviderDriverOptions } from './contracts.js';
|
|
4
|
-
|
|
4
|
+
export type { NodeAcpExecutionInput, NodeAcpExecutionBoundary, NodeAcpExecutionReservation } from './execution/types.js';
|
|
5
|
+
export { NodeAcpExecutionCleanupError, isNodeAcpExecutionCleanupError } from './execution/errors.js';
|
|
6
|
+
/** Uses the supplied inert execution owner, or the default owned POSIX process-group launcher. */
|
|
5
7
|
export declare function createNodeAcpProviderDriver(options: CreateNodeAcpProviderDriverOptions): AgentProviderDriver;
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/node/index.js
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolveNodeAcpLaunch } from "./launch.js";
|
|
1
|
+
import { createAcpProviderDriverInternal } from "../driverFactory.js";
|
|
2
|
+
import { resolveNodeAcpLaunch, resolveNodeAcpSessionInteractions } from "./launch.js";
|
|
3
3
|
import { spawnAcpProcess } from "./process.js";
|
|
4
|
+
import { executionReservationSource } from "./execution/owner.js";
|
|
5
|
+
import { NodeAcpExecutionCleanupError, isNodeAcpExecutionCleanupError } from "./execution/errors.js";
|
|
4
6
|
function createNodeAcpProviderDriver(options) {
|
|
5
7
|
const launch = resolveNodeAcpLaunch(options.launch);
|
|
6
|
-
|
|
8
|
+
const boundary = options.executionBoundary;
|
|
9
|
+
if (boundary === void 0 && Object.hasOwn(options, "executionBoundary")) throw new TypeError("An explicitly supplied execution boundary must be valid.");
|
|
10
|
+
const reserve = boundary === void 0 ? void 0 : executionReservationSource(boundary, launch);
|
|
11
|
+
const sessionInteractions = resolveNodeAcpSessionInteractions(options, launch);
|
|
12
|
+
if (sessionInteractions && !reserve) throw new TypeError("The Grok folder-trust mapping requires an exclusive descendant-owning execution boundary.");
|
|
13
|
+
return createAcpProviderDriverInternal({
|
|
7
14
|
providerKey: options.providerKey,
|
|
8
|
-
|
|
15
|
+
connectionSource: reserve ? { kind: "reserved", reserve } : { kind: "factory", create: (context) => spawnAcpProcess(launch, context) },
|
|
9
16
|
...options.unknownExtensionPolicy !== void 0 ? { unknownExtensionPolicy: options.unknownExtensionPolicy } : {},
|
|
17
|
+
...options.permissionMapping !== void 0 ? { permissionMapping: options.permissionMapping } : {},
|
|
10
18
|
...options.limits ? { limits: options.limits } : {},
|
|
11
19
|
...options.configuration ? { configuration: options.configuration } : {},
|
|
12
20
|
...options.authentication ? { authentication: options.authentication } : {},
|
|
13
21
|
...options.host ? { host: options.host } : {},
|
|
14
22
|
...options.mcpServers ? { mcpServers: options.mcpServers } : {},
|
|
15
23
|
...options.sessionMcpBinding ? { sessionMcpBinding: options.sessionMcpBinding } : {}
|
|
16
|
-
});
|
|
24
|
+
}, sessionInteractions);
|
|
17
25
|
}
|
|
18
26
|
export {
|
|
19
|
-
|
|
27
|
+
NodeAcpExecutionCleanupError,
|
|
28
|
+
createNodeAcpProviderDriver,
|
|
29
|
+
isNodeAcpExecutionCleanupError
|
|
20
30
|
};
|
package/dist/node/launch.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import type { NodeAcpLaunchOptions } from './contracts.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
readonly cwd: string;
|
|
6
|
-
readonly env: Readonly<Record<string, string>>;
|
|
7
|
-
readonly shutdownTimeoutMs: number;
|
|
8
|
-
}
|
|
1
|
+
import type { CreateNodeAcpProviderDriverOptions, NodeAcpLaunchOptions } from './contracts.js';
|
|
2
|
+
import type { AcpSessionInteractionSelection } from '../sessionInteractions/contracts.js';
|
|
3
|
+
import type { NodeAcpExecutionInput } from './execution/types.js';
|
|
4
|
+
export type ResolvedNodeAcpLaunch = NodeAcpExecutionInput;
|
|
9
5
|
export declare function resolveNodeAcpLaunch(launch: NodeAcpLaunchOptions): ResolvedNodeAcpLaunch;
|
|
6
|
+
export declare function resolveNodeAcpSessionInteractions(options: CreateNodeAcpProviderDriverOptions, launch: ResolvedNodeAcpLaunch): AcpSessionInteractionSelection | undefined;
|
|
10
7
|
//# sourceMappingURL=launch.d.ts.map
|
package/dist/node/launch.js
CHANGED
|
@@ -31,6 +31,18 @@ function resolveNodeAcpLaunch(launch) {
|
|
|
31
31
|
shutdownTimeoutMs
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
+
function resolveNodeAcpSessionInteractions(options, launch) {
|
|
35
|
+
const selection = options.sessionInteractions;
|
|
36
|
+
if (selection === void 0 && !Object.hasOwn(options, "sessionInteractions")) return void 0;
|
|
37
|
+
if (!selection || typeof selection !== "object" || Array.isArray(selection) || Reflect.ownKeys(selection).length !== 1 || !Object.hasOwn(selection, "mapping") || selection.mapping !== "grok-folder-trust-v1") {
|
|
38
|
+
throw new TypeError("ACP session interaction selection must name the supported mapping without additional fields.");
|
|
39
|
+
}
|
|
40
|
+
if (launch.args.length !== 3 || launch.args[0] !== "agent" || launch.args[1] !== "--no-leader" || launch.args[2] !== "stdio") {
|
|
41
|
+
throw new TypeError("The Grok folder-trust mapping requires exact direct arguments: agent --no-leader stdio.");
|
|
42
|
+
}
|
|
43
|
+
return Object.freeze({ mapping: "grok-folder-trust-v1" });
|
|
44
|
+
}
|
|
34
45
|
export {
|
|
35
|
-
resolveNodeAcpLaunch
|
|
46
|
+
resolveNodeAcpLaunch,
|
|
47
|
+
resolveNodeAcpSessionInteractions
|
|
36
48
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface AcpPermissionOptionSignature {
|
|
2
|
+
readonly optionId: string;
|
|
3
|
+
readonly kind: 'allow_once' | 'reject_once' | 'allow_always' | 'reject_always';
|
|
4
|
+
}
|
|
5
|
+
export interface AcpPermissionMenuSignature {
|
|
6
|
+
readonly toolKind: 'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'think' | 'fetch' | 'switch_mode' | 'other';
|
|
7
|
+
readonly options: readonly [AcpPermissionOptionSignature, ...AcpPermissionOptionSignature[]];
|
|
8
|
+
}
|
|
9
|
+
/** Complete declarations attest that these exact menus add no unrepresented choice effects. */
|
|
10
|
+
export type AcpPermissionMappingSelection = Readonly<{
|
|
11
|
+
mapping: 'once-only-v1';
|
|
12
|
+
}> | Readonly<{
|
|
13
|
+
mapping: 'grok-build-1.0.50-edit-remembrance-v1';
|
|
14
|
+
}> | Readonly<{
|
|
15
|
+
mapping: 'declared-complete-consequences-v1';
|
|
16
|
+
agentInfo: Readonly<{
|
|
17
|
+
name: string;
|
|
18
|
+
version: string;
|
|
19
|
+
}>;
|
|
20
|
+
menus: readonly [AcpPermissionMenuSignature, ...AcpPermissionMenuSignature[]];
|
|
21
|
+
}>;
|
|
22
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AcpJsonObject } from '@agen-ai/acp-transport';
|
|
2
|
+
import { type AgentApprovalOption } from '@agen-ai/agent-protocol';
|
|
3
|
+
import type { AcpPermissionMappingSelection } from './contracts.js';
|
|
4
|
+
export interface MappedPermissionOptions {
|
|
5
|
+
readonly options: readonly AgentApprovalOption[];
|
|
6
|
+
readonly nativeOptions: ReadonlyMap<string, string>;
|
|
7
|
+
readonly warning?: Readonly<{
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export declare function mapAcpPermissionOptions(selection: AcpPermissionMappingSelection, params: AcpJsonObject): MappedPermissionOptions;
|
|
13
|
+
//# sourceMappingURL=mapping.d.ts.map
|