@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
package/dist/host/router.js
CHANGED
|
@@ -11,12 +11,11 @@ class AcpHostRouter {
|
|
|
11
11
|
this.interactions = new AcpHostInteractions(input);
|
|
12
12
|
this.terminals = new AcpHostTerminals(input.host, input.limits);
|
|
13
13
|
}
|
|
14
|
-
input;
|
|
15
14
|
interactions;
|
|
16
15
|
terminals;
|
|
17
16
|
requests = /* @__PURE__ */ new Map();
|
|
18
17
|
binding;
|
|
19
|
-
pending =
|
|
18
|
+
pending = /* @__PURE__ */ new Map();
|
|
20
19
|
bytes = 0;
|
|
21
20
|
bindSession(binding) {
|
|
22
21
|
if (this.binding && (binding.nativeSessionId !== this.binding.nativeSessionId || binding.sessionId !== this.binding.sessionId || binding.workingDirectory !== this.binding.workingDirectory)) {
|
|
@@ -55,32 +54,27 @@ class AcpHostRouter {
|
|
|
55
54
|
};
|
|
56
55
|
}
|
|
57
56
|
admit(context) {
|
|
57
|
+
const params = hostObject(context.params);
|
|
58
|
+
if (context.method === "session/request_permission" || context.method === "elicitation/create" && Object.hasOwn(params, "sessionId")) return this.route(context, params);
|
|
58
59
|
const bytes = Buffer.byteLength(JSON.stringify(context.params), "utf8");
|
|
59
|
-
if (this.pending >= this.input.limits.maxPendingRequests || this.bytes + bytes > this.input.limits.maxQueuedBytes) {
|
|
60
|
+
if (this.pending.size >= this.input.limits.maxPendingRequests || this.bytes + bytes > this.input.limits.maxQueuedBytes) {
|
|
60
61
|
throw new AcpRpcHandlerError(-32603, "The host request capacity was exceeded.");
|
|
61
62
|
}
|
|
62
|
-
this.pending += 1;
|
|
63
63
|
this.bytes += bytes;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
result = this.route(context);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
this.pending -= 1;
|
|
64
|
+
const retirement = context.settled.then(() => this.interactions.joinRequest(context.settled)).then(() => {
|
|
65
|
+
this.pending.delete(context.settled);
|
|
69
66
|
this.bytes -= bytes;
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
});
|
|
68
|
+
this.pending.set(context.settled, retirement);
|
|
69
|
+
const result = this.route(context, params);
|
|
72
70
|
return result.then((value) => {
|
|
73
71
|
if (Buffer.byteLength(JSON.stringify(value), "utf8") > Math.min(this.input.limits.maxQueuedBytes, 8 * 1024 * 1024) - 512) {
|
|
74
72
|
throw invalidHostRequest();
|
|
75
73
|
}
|
|
76
74
|
return value;
|
|
77
|
-
}).finally(() => {
|
|
78
|
-
this.pending -= 1;
|
|
79
|
-
this.bytes -= bytes;
|
|
80
75
|
});
|
|
81
76
|
}
|
|
82
|
-
route(context) {
|
|
83
|
-
const params = hostObject(context.params);
|
|
77
|
+
route(context, params) {
|
|
84
78
|
if (context.method === "elicitation/create") {
|
|
85
79
|
const session2 = Object.hasOwn(params, "sessionId");
|
|
86
80
|
const request = Object.hasOwn(params, "requestId");
|
|
@@ -144,20 +138,28 @@ class AcpHostRouter {
|
|
|
144
138
|
const id = hostText(hostObject(value).elicitationId, 256);
|
|
145
139
|
return this.interactions.complete(id);
|
|
146
140
|
}
|
|
147
|
-
sessionElicitation(context) {
|
|
141
|
+
sessionElicitation(context, observeCallback) {
|
|
148
142
|
const bound = this.requireSession(hostObject(context.params), context.signal);
|
|
149
143
|
return this.interactions.request(context, {
|
|
150
144
|
kind: "session",
|
|
151
145
|
sessionId: bound.sessionId,
|
|
152
146
|
workingDirectory: bound.workingDirectory
|
|
153
|
-
}, context.signal);
|
|
147
|
+
}, context.signal, observeCallback);
|
|
154
148
|
}
|
|
155
149
|
async close() {
|
|
156
150
|
for (const request of this.requests.values()) request.abort();
|
|
157
151
|
this.requests.clear();
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
152
|
+
const deadline = hostDeadline(new AbortController().signal, this.input.limits.hostOperationTimeoutMs);
|
|
153
|
+
const join = async () => {
|
|
154
|
+
while (this.pending.size > 0) await Promise.all(this.pending.values());
|
|
155
|
+
};
|
|
156
|
+
try {
|
|
157
|
+
const results = await Promise.allSettled([this.interactions.close(), this.terminals.close(), awaitHost(join(), deadline.signal)]);
|
|
158
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
159
|
+
if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "ACP host facility cleanup failed.");
|
|
160
|
+
} finally {
|
|
161
|
+
deadline.close();
|
|
162
|
+
}
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
export {
|
package/dist/host/terminals.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { createAcpProviderDriver } from './driver.js';
|
|
|
2
2
|
export { DEFAULT_ACP_PROVIDER_LIMITS, type AcpProviderLimits } from './limits.js';
|
|
3
3
|
export { AcpProviderConnectionError } from './connection/errors.js';
|
|
4
4
|
export { AcpProviderSessionError } from './session/errors.js';
|
|
5
|
+
export type { AcpPermissionOptionSignature, AcpPermissionMenuSignature, AcpPermissionMappingSelection, } from './permissions/contracts.js';
|
|
5
6
|
export type { AcpConnectionStreams, AcpConnectionLease, AcpConnectionFactoryContext, AcpConnectionFactory, AcpSessionMcpBindingContext, AcpSessionMcpBindingLease, AcpSessionMcpBindingFactory, AcpSessionMcpBindingOptions, CreateAcpProviderDriverOptions, } from './contracts.js';
|
|
6
7
|
export type { AcpAuthenticationMethods, AcpHostFacilities, AcpHostInteractionScope, AcpHostInteractionInput, AcpHostFormInput, AcpHostFormResponse, AcpHostUrlInput, AcpHostUrlDecision, AcpHostUrlInteractionLease, AcpHostSessionInput, AcpHostFileReadInput, AcpHostFileWriteInput, AcpHostTerminalCreateInput, AcpHostTerminalLease, AcpHostTerminalOutput, AcpHostTerminalExitStatus, AcpHostAuthenticationTerminalInput, AcpHostAuthenticationTerminalLease, } from './host/contracts.js';
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/instance.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { type AgentInstanceId, type AgentProviderKey } from '@agen-ai/agent-protocol';
|
|
2
2
|
import type { AcpStandardMcpServer, AcpUnknownExtensionPolicy } from '@agen-ai/acp-transport';
|
|
3
3
|
import { type MaterializedAgentProviderInstance } from '@agen-ai/agent-runtime';
|
|
4
|
-
import type {
|
|
4
|
+
import type { AcpSessionMcpBindingOptions } from './contracts.js';
|
|
5
5
|
import type { AcpAuthenticationMethods, AcpHostFacilities } from './host/contracts.js';
|
|
6
6
|
import type { AcpProviderLimits } from './limits.js';
|
|
7
|
+
import type { AcpConnectionSource } from './connection/contracts.js';
|
|
8
|
+
import type { AcpSessionInteractionSelection } from './sessionInteractions/contracts.js';
|
|
9
|
+
import type { AcpPermissionMappingSelection } from './permissions/contracts.js';
|
|
7
10
|
export declare function createAcpProviderInstance(input: {
|
|
8
11
|
readonly providerKey: AgentProviderKey;
|
|
9
12
|
readonly instanceId: AgentInstanceId;
|
|
10
|
-
readonly
|
|
13
|
+
readonly connectionSource: AcpConnectionSource;
|
|
11
14
|
readonly unknownExtensionPolicy: AcpUnknownExtensionPolicy;
|
|
12
15
|
readonly limits: AcpProviderLimits;
|
|
13
16
|
readonly usedStreams: WeakSet<object>;
|
|
@@ -15,6 +18,8 @@ export declare function createAcpProviderInstance(input: {
|
|
|
15
18
|
readonly authentication: AcpAuthenticationMethods;
|
|
16
19
|
readonly host: AcpHostFacilities;
|
|
17
20
|
readonly mcpServers: readonly AcpStandardMcpServer[];
|
|
21
|
+
readonly permissionMapping: AcpPermissionMappingSelection;
|
|
18
22
|
readonly sessionMcpBinding?: AcpSessionMcpBindingOptions;
|
|
23
|
+
readonly sessionInteractions?: AcpSessionInteractionSelection;
|
|
19
24
|
}): Promise<MaterializedAgentProviderInstance>;
|
|
20
25
|
//# sourceMappingURL=instance.d.ts.map
|
package/dist/instance.js
CHANGED
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
createAgentProviderReadiness,
|
|
7
7
|
createAgentProviderSessionCleanupError,
|
|
8
8
|
isAgentProviderSessionCleanupErrorFor,
|
|
9
|
-
throwIfAgentOperationAborted
|
|
9
|
+
throwIfAgentOperationAborted,
|
|
10
|
+
AgentProviderContractError,
|
|
11
|
+
DEFAULT_AGENT_SESSION_INTERACTION_LIMITS
|
|
10
12
|
} from "@agen-ai/agent-runtime";
|
|
11
13
|
import { createAcpAuthentication } from "./auth/controller.js";
|
|
12
14
|
import { selectedAuthenticationMethods } from "./auth/methods.js";
|
|
@@ -17,18 +19,33 @@ import { negotiatedEnvelope } from "./connection/negotiation.js";
|
|
|
17
19
|
import { assertAcpMcpSupport, supportsAcpSessionMcpBinding } from "./mcp.js";
|
|
18
20
|
import { createAcpProviderSession } from "./session/create.js";
|
|
19
21
|
import { AcpSessionResources } from "./session/resources.js";
|
|
22
|
+
import { awaitHost, hostDeadline } from "./host/lifecycle.js";
|
|
23
|
+
import { isNodeAcpExecutionCleanupError, NodeAcpExecutionCleanupError } from "./node/execution/errors.js";
|
|
20
24
|
async function createAcpProviderInstance(input) {
|
|
21
25
|
const lifetime = new AbortController();
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
const executions = /* @__PURE__ */ new Set();
|
|
27
|
+
const connect = (signal) => {
|
|
28
|
+
throwIfAgentOperationAborted(signal);
|
|
29
|
+
const source = input.connectionSource.kind === "factory" ? input.connectionSource : { kind: "reserved", owner: input.connectionSource.reserve() };
|
|
30
|
+
if (source.kind === "reserved") {
|
|
31
|
+
executions.add(source.owner);
|
|
32
|
+
source.owner.whenDrained(() => {
|
|
33
|
+
executions.delete(source.owner);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return initializeAcpConnection({
|
|
37
|
+
source,
|
|
38
|
+
unknownExtensionPolicy: input.unknownExtensionPolicy,
|
|
39
|
+
context: { instanceId: input.instanceId, signal },
|
|
40
|
+
limits: input.limits,
|
|
41
|
+
usedStreams: input.usedStreams,
|
|
42
|
+
configuration: input.configuration,
|
|
43
|
+
authentication: input.authentication,
|
|
44
|
+
host: input.host,
|
|
45
|
+
permissionMapping: input.permissionMapping,
|
|
46
|
+
...input.sessionInteractions ? { sessionInteractions: input.sessionInteractions } : {}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
32
49
|
let eager = await connect(lifetime.signal);
|
|
33
50
|
const negotiated = eager.negotiated;
|
|
34
51
|
let methods;
|
|
@@ -36,11 +53,13 @@ async function createAcpProviderInstance(input) {
|
|
|
36
53
|
try {
|
|
37
54
|
assertAcpMcpSupport(input.mcpServers, negotiated);
|
|
38
55
|
methods = selectedAuthenticationMethods(negotiated, input.authentication, input.host);
|
|
39
|
-
capabilities = acpProviderCapabilities(input.providerKey, negotiated, input.configuration, [...methods.keys()]);
|
|
56
|
+
capabilities = acpProviderCapabilities(input.providerKey, negotiated, input.configuration, [...methods.keys()], input.sessionInteractions, input.permissionMapping);
|
|
40
57
|
} catch (cause) {
|
|
58
|
+
if (eager.execution?.cleanupFailure) throw eager.execution.retainedError(cause, eager.execution.cleanupFailure.error);
|
|
41
59
|
try {
|
|
42
60
|
await eager.close();
|
|
43
61
|
} catch (cleanupError) {
|
|
62
|
+
if (eager.execution) throw eager.execution.retainedError(cause, cleanupError);
|
|
44
63
|
throw new AggregateError([cause, cleanupError], "ACP materialization and cleanup failed.");
|
|
45
64
|
}
|
|
46
65
|
throw cause;
|
|
@@ -52,7 +71,8 @@ async function createAcpProviderInstance(input) {
|
|
|
52
71
|
const creatingIds = /* @__PURE__ */ new Set();
|
|
53
72
|
const pending = /* @__PURE__ */ new Set();
|
|
54
73
|
let disposePromise;
|
|
55
|
-
|
|
74
|
+
let disposeDeadline = 0;
|
|
75
|
+
const cleanupPending = () => [...resources].some((resource) => resource.cleanupPending) || [...executions].some((execution) => execution.cleanupPending);
|
|
56
76
|
const bindingSupported = supportsAcpSessionMcpBinding(input.sessionMcpBinding, negotiated);
|
|
57
77
|
const now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
58
78
|
const retire = async (connection) => {
|
|
@@ -60,6 +80,10 @@ async function createAcpProviderInstance(input) {
|
|
|
60
80
|
await connection.close();
|
|
61
81
|
connections.delete(connection);
|
|
62
82
|
};
|
|
83
|
+
const closeConnection = async (connection) => {
|
|
84
|
+
await connection.close();
|
|
85
|
+
if (input.sessionInteractions) connections.delete(connection);
|
|
86
|
+
};
|
|
63
87
|
const acquire = async (signal) => {
|
|
64
88
|
const reserved = eager;
|
|
65
89
|
eager = void 0;
|
|
@@ -67,14 +91,22 @@ async function createAcpProviderInstance(input) {
|
|
|
67
91
|
connections.add(connection);
|
|
68
92
|
try {
|
|
69
93
|
throwIfAgentOperationAborted(signal);
|
|
94
|
+
if (input.sessionInteractions && connection.stopped.aborted) throw connection.stopped.reason;
|
|
70
95
|
if (negotiatedEnvelope(connection.negotiated) !== envelope) {
|
|
71
96
|
throw new AcpProviderConnectionError("acp_provider_negotiation_changed", "The ACP peer changed its initialized behavior envelope.");
|
|
72
97
|
}
|
|
73
98
|
return connection;
|
|
74
99
|
} catch (cause) {
|
|
100
|
+
const retained = (cleanupError) => new NodeAcpExecutionCleanupError({
|
|
101
|
+
cause,
|
|
102
|
+
cleanupError,
|
|
103
|
+
retryCleanup: () => retire(connection)
|
|
104
|
+
});
|
|
105
|
+
if (connection.execution?.cleanupFailure) throw retained(connection.execution.cleanupFailure.error);
|
|
75
106
|
try {
|
|
76
107
|
await retire(connection);
|
|
77
108
|
} catch (cleanupError) {
|
|
109
|
+
if (connection.execution) throw retained(cleanupError);
|
|
78
110
|
throw new AggregateError([cause, cleanupError], "ACP negotiation and cleanup failed.");
|
|
79
111
|
}
|
|
80
112
|
throw cause;
|
|
@@ -101,6 +133,16 @@ async function createAcpProviderInstance(input) {
|
|
|
101
133
|
const open = async (context, resume) => {
|
|
102
134
|
throwIfAgentOperationAborted(context.signal);
|
|
103
135
|
throwIfAgentOperationAborted(lifetime.signal);
|
|
136
|
+
if (input.sessionInteractions?.mapping === "grok-folder-trust-v1" && resume) throw new AgentProviderContractError(
|
|
137
|
+
input.providerKey,
|
|
138
|
+
"session_interactions_unsupported",
|
|
139
|
+
"The selected ACP session interaction mapping supports creation only."
|
|
140
|
+
);
|
|
141
|
+
if (input.sessionInteractions && (input.sessionInteractions.mapping === "grok-folder-trust-v1" || resume) && context.interactions === void 0) throw new AgentProviderContractError(
|
|
142
|
+
input.providerKey,
|
|
143
|
+
"invalid_session_interaction",
|
|
144
|
+
"The selected ACP session interaction mapping requires a handler before opening."
|
|
145
|
+
);
|
|
104
146
|
if (authentication.active) throw new AcpProviderConnectionError("acp_authentication_active", "Authentication owns the ACP instance.");
|
|
105
147
|
if (authentication.cleanupPending) throw new AcpProviderConnectionError("acp_authentication_cleanup_pending", "Authentication terminal cleanup must complete before session mutation.");
|
|
106
148
|
if (cleanupPending()) throw new AcpProviderConnectionError("acp_session_cleanup_pending", "Session resource cleanup must complete before session mutation.");
|
|
@@ -126,6 +168,14 @@ async function createAcpProviderInstance(input) {
|
|
|
126
168
|
let opened;
|
|
127
169
|
try {
|
|
128
170
|
connection = await acquire(signal);
|
|
171
|
+
if (context.interactions) {
|
|
172
|
+
connection.sessionInteractions?.prepareOpening({
|
|
173
|
+
sessionId: parsed.sessionId,
|
|
174
|
+
workingDirectory: context.workingDirectory,
|
|
175
|
+
interactions: context.interactions,
|
|
176
|
+
signal
|
|
177
|
+
});
|
|
178
|
+
}
|
|
129
179
|
const ownedConnection = connection;
|
|
130
180
|
resource = new AcpSessionResources({
|
|
131
181
|
instanceId: input.instanceId,
|
|
@@ -145,32 +195,39 @@ async function createAcpProviderInstance(input) {
|
|
|
145
195
|
throwIfAgentOperationAborted(signal);
|
|
146
196
|
const closeResources = () => resource.close();
|
|
147
197
|
const sessionCapabilities = negotiated.agentCapabilities.sessionCapabilities;
|
|
148
|
-
|
|
198
|
+
opened = await createAcpProviderSession({
|
|
149
199
|
context: { ...context, sessionId: parsed.sessionId, configuration: parsed.configuration, signal },
|
|
150
200
|
openingInput: context,
|
|
151
201
|
...parsed.operation === "resume" ? { resume: {
|
|
152
202
|
binding: parsed.binding,
|
|
153
|
-
method: sessionCapabilities?.resume != null ? "session/resume" : "session/load"
|
|
203
|
+
method: input.sessionInteractions?.mapping === "grok-build-1.0.60-session-requests-v1" ? "session/load" : sessionCapabilities?.resume != null ? "session/resume" : "session/load"
|
|
154
204
|
} } : {},
|
|
155
205
|
mcpServers,
|
|
156
206
|
connection,
|
|
157
207
|
capabilities,
|
|
158
208
|
limits: input.limits,
|
|
159
209
|
now,
|
|
210
|
+
permissionMapping: input.permissionMapping,
|
|
160
211
|
onClosed: () => {
|
|
161
212
|
sessions.delete(parsed.sessionId);
|
|
162
213
|
connections.delete(ownedConnection);
|
|
163
214
|
},
|
|
164
215
|
closeResources
|
|
165
216
|
});
|
|
166
|
-
opened = session;
|
|
167
217
|
throwIfAgentOperationAborted(signal);
|
|
168
|
-
|
|
169
|
-
|
|
218
|
+
if (context.interactions) connection.sessionInteractions?.adopt();
|
|
219
|
+
sessions.set(parsed.sessionId, opened.session);
|
|
220
|
+
return opened.session;
|
|
170
221
|
} catch (cause) {
|
|
171
222
|
if (isAgentProviderSessionCleanupErrorFor(cause, context) && cause.providerKey === input.providerKey) throw cause;
|
|
223
|
+
if (isNodeAcpExecutionCleanupError(cause)) throw createAgentProviderSessionCleanupError({
|
|
224
|
+
providerKey: input.providerKey,
|
|
225
|
+
openingInput: context,
|
|
226
|
+
errors: [cause],
|
|
227
|
+
retryCleanup: cause.retryCleanup
|
|
228
|
+
});
|
|
172
229
|
if (resource || connection) {
|
|
173
|
-
const cleanup = () => opened ? opened.
|
|
230
|
+
const cleanup = () => opened ? opened.cleanup() : resource ? resource.close() : retire(connection);
|
|
174
231
|
try {
|
|
175
232
|
await cleanup();
|
|
176
233
|
} catch (cleanupError) {
|
|
@@ -193,16 +250,34 @@ async function createAcpProviderInstance(input) {
|
|
|
193
250
|
creatingIds.delete(parsed.sessionId);
|
|
194
251
|
}
|
|
195
252
|
};
|
|
253
|
+
const adapter = Object.freeze({
|
|
254
|
+
...input.sessionInteractions ? { sessionInteractions: Object.freeze({
|
|
255
|
+
kind: "supported",
|
|
256
|
+
mappings: input.sessionInteractions.mapping === "grok-folder-trust-v1" ? Object.freeze([Object.freeze({ openingOperation: "create", requestKind: "decision" })]) : Object.freeze([Object.freeze({ openingOperation: "resume", requestKind: "plan_review" })]),
|
|
257
|
+
producerOwnership: "exclusive",
|
|
258
|
+
humanWait: Object.freeze(["bounded", "until_withdrawn_or_closed"])
|
|
259
|
+
}) } : {},
|
|
260
|
+
...input.sessionInteractions?.mapping === "grok-build-1.0.60-session-requests-v1" ? {
|
|
261
|
+
nativeContinuations: Object.freeze({
|
|
262
|
+
kind: "supported",
|
|
263
|
+
producerOwnership: "exclusive",
|
|
264
|
+
mappings: Object.freeze([Object.freeze({
|
|
265
|
+
openingOperation: "resume",
|
|
266
|
+
requestKind: "plan_review",
|
|
267
|
+
timing: "after_open_return"
|
|
268
|
+
})])
|
|
269
|
+
})
|
|
270
|
+
} : {},
|
|
271
|
+
hostBinding: bindingSupported ? Object.freeze({ kind: "supported", toolTransport: "mcp", instructionPlacements: Object.freeze(["tool_context"]) }) : Object.freeze({ kind: "unsupported" }),
|
|
272
|
+
createSession: (context) => open(context, false),
|
|
273
|
+
resumption: capabilities.sessions.resume ? Object.freeze({ kind: "supported", resumeSession: (context) => open(context, true) }) : Object.freeze({ kind: "unsupported" }),
|
|
274
|
+
branching: { kind: "unsupported" },
|
|
275
|
+
authentication: authentication.port
|
|
276
|
+
});
|
|
196
277
|
return Object.freeze({
|
|
197
278
|
instanceId: input.instanceId,
|
|
198
279
|
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
|
-
}),
|
|
280
|
+
adapter,
|
|
206
281
|
accountQuota: { kind: "unsupported" },
|
|
207
282
|
environment: { kind: "unsupported" },
|
|
208
283
|
checkReadiness(check = {}) {
|
|
@@ -217,18 +292,24 @@ async function createAcpProviderInstance(input) {
|
|
|
217
292
|
},
|
|
218
293
|
dispose() {
|
|
219
294
|
if (!disposePromise) {
|
|
295
|
+
disposeDeadline = Date.now() + Math.min(input.limits.hostOperationTimeoutMs, DEFAULT_AGENT_SESSION_INTERACTION_LIMITS.cleanupTimeoutMs);
|
|
220
296
|
disposePromise = Promise.resolve().then(async () => {
|
|
221
297
|
const failures = /* @__PURE__ */ new Set();
|
|
222
298
|
const closingConnections = new Set(connections);
|
|
299
|
+
if (input.sessionInteractions) for (const connection of closingConnections) {
|
|
300
|
+
const reason = connection.stopped.reason;
|
|
301
|
+
if (connection.stopped.aborted && reason instanceof Error && !(reason instanceof AcpProviderConnectionError && reason.reasonCode === "acp_provider_connection_closed")) failures.add(reason);
|
|
302
|
+
}
|
|
223
303
|
const closing = await Promise.allSettled([
|
|
224
304
|
authentication.close(),
|
|
225
305
|
...[...sessions.values()].map((session) => session.close({ reason: "shutdown" })),
|
|
226
306
|
...[...resources].map((resource) => resource.close()),
|
|
227
|
-
...[...closingConnections].map(
|
|
307
|
+
...[...closingConnections].map(closeConnection),
|
|
308
|
+
...[...executions].map((execution) => execution.cleanup())
|
|
228
309
|
]);
|
|
229
310
|
for (const result of closing) if (result.status === "rejected") failures.add(result.reason);
|
|
230
311
|
await Promise.allSettled([...pending]);
|
|
231
|
-
const remaining = await Promise.allSettled([...connections].filter((connection) => !closingConnections.has(connection)).map(
|
|
312
|
+
const remaining = await Promise.allSettled([...connections].filter((connection) => !closingConnections.has(connection)).map(closeConnection));
|
|
232
313
|
for (const result of remaining) if (result.status === "rejected") failures.add(result.reason);
|
|
233
314
|
if (failures.size) throw new AggregateError(failures, "ACP instance cleanup failed.");
|
|
234
315
|
connections.clear();
|
|
@@ -239,6 +320,10 @@ async function createAcpProviderInstance(input) {
|
|
|
239
320
|
});
|
|
240
321
|
lifetime.abort(new AcpProviderConnectionError("acp_provider_instance_disposed", "The ACP instance was disposed."));
|
|
241
322
|
}
|
|
323
|
+
if (input.sessionInteractions || input.connectionSource.kind === "reserved") {
|
|
324
|
+
const deadline = hostDeadline(new AbortController().signal, Math.max(0, disposeDeadline - Date.now()));
|
|
325
|
+
return awaitHost(disposePromise, deadline.signal).finally(() => deadline.close());
|
|
326
|
+
}
|
|
242
327
|
return disposePromise;
|
|
243
328
|
}
|
|
244
329
|
});
|
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 direct native executable, exact no-leader arguments and a handler for each selected opening tuple. */
|
|
16
|
+
readonly sessionInteractions?: Readonly<{
|
|
17
|
+
readonly mapping: 'grok-folder-trust-v1' | 'grok-build-1.0.60-session-requests-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
|
+
};
|