@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.
Files changed (64) hide show
  1. package/README.md +355 -12
  2. package/dist/capabilities.d.ts +3 -1
  3. package/dist/capabilities.js +5 -4
  4. package/dist/configuration/catalog.js +0 -2
  5. package/dist/configuration/errors.js +0 -1
  6. package/dist/connection/contracts.d.ts +21 -0
  7. package/dist/connection/errors.js +0 -1
  8. package/dist/connection/initialize.d.ts +7 -3
  9. package/dist/connection/initialize.js +46 -17
  10. package/dist/connection/negotiation.d.ts +2 -1
  11. package/dist/connection/negotiation.js +15 -1
  12. package/dist/contracts.d.ts +3 -0
  13. package/dist/driver.d.ts +1 -1
  14. package/dist/driver.js +6 -39
  15. package/dist/driverFactory.d.ts +8 -0
  16. package/dist/driverFactory.js +48 -0
  17. package/dist/host/interactions.js +0 -1
  18. package/dist/host/leases.js +0 -1
  19. package/dist/host/router.js +0 -1
  20. package/dist/host/terminals.js +0 -2
  21. package/dist/index.d.ts +1 -0
  22. package/dist/instance.d.ts +7 -2
  23. package/dist/instance.js +102 -27
  24. package/dist/node/contracts.d.ts +6 -0
  25. package/dist/node/execution/errors.d.ts +11 -0
  26. package/dist/node/execution/errors.js +17 -0
  27. package/dist/node/execution/owner.d.ts +34 -0
  28. package/dist/node/execution/owner.js +162 -0
  29. package/dist/node/execution/types.d.ts +20 -0
  30. package/dist/node/execution/types.js +0 -0
  31. package/dist/node/index.d.ts +3 -1
  32. package/dist/node/index.js +16 -6
  33. package/dist/node/launch.d.ts +5 -8
  34. package/dist/node/launch.js +13 -1
  35. package/dist/permissions/contracts.d.ts +22 -0
  36. package/dist/permissions/contracts.js +0 -0
  37. package/dist/permissions/mapping.d.ts +13 -0
  38. package/dist/permissions/mapping.js +99 -0
  39. package/dist/permissions/selection.d.ts +5 -0
  40. package/dist/permissions/selection.js +96 -0
  41. package/dist/session/create.d.ts +7 -1
  42. package/dist/session/create.js +3 -1
  43. package/dist/session/errors.js +0 -1
  44. package/dist/session/mapping.d.ts +6 -0
  45. package/dist/session/mapping.js +5 -26
  46. package/dist/session/opening.d.ts +1 -0
  47. package/dist/session/opening.js +3 -1
  48. package/dist/session/queue.js +0 -1
  49. package/dist/session/requests/forms.js +0 -2
  50. package/dist/session/requests/pending.js +0 -1
  51. package/dist/session/resources.js +5 -3
  52. package/dist/session/session.d.ts +4 -1
  53. package/dist/session/session.js +67 -25
  54. package/dist/sessionInteractions/broker.d.ts +42 -0
  55. package/dist/sessionInteractions/broker.js +171 -0
  56. package/dist/sessionInteractions/contracts.d.ts +3 -0
  57. package/dist/sessionInteractions/contracts.js +0 -0
  58. package/dist/sessionInteractions/extensions.d.ts +3 -0
  59. package/dist/sessionInteractions/extensions.js +17 -0
  60. package/dist/sessionInteractions/grokFolderTrust.d.ts +43 -0
  61. package/dist/sessionInteractions/grokFolderTrust.js +107 -0
  62. package/dist/sessionInteractions/request.d.ts +38 -0
  63. package/dist/sessionInteractions/request.js +145 -0
  64. 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 connect = (signal) => initializeAcpConnection({
23
- createConnection: input.createConnection,
24
- unknownExtensionPolicy: input.unknownExtensionPolicy,
25
- context: { instanceId: input.instanceId, signal },
26
- limits: input.limits,
27
- usedStreams: input.usedStreams,
28
- configuration: input.configuration,
29
- authentication: input.authentication,
30
- host: input.host
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
- const cleanupPending = () => [...resources].some((resource) => resource.cleanupPending);
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
- const session = await createAcpProviderSession({
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
- sessions.set(parsed.sessionId, session);
169
- return session;
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.close({ reason: "contract_rejected" }) : resource ? resource.close() : retire(connection);
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: Object.freeze({
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((connection) => connection.close())
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((connection) => connection.close()));
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
  });
@@ -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
@@ -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
- /** Launches an owned POSIX process group. Windows hosts must supply an owned connection factory. */
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
@@ -1,20 +1,30 @@
1
- import { createAcpProviderDriver } from "../driver.js";
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
- return createAcpProviderDriver({
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
- createConnection: (context) => spawnAcpProcess(launch, context),
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
- createNodeAcpProviderDriver
27
+ NodeAcpExecutionCleanupError,
28
+ createNodeAcpProviderDriver,
29
+ isNodeAcpExecutionCleanupError
20
30
  };
@@ -1,10 +1,7 @@
1
- import type { NodeAcpLaunchOptions } from './contracts.js';
2
- export interface ResolvedNodeAcpLaunch {
3
- readonly executable: string;
4
- readonly args: readonly string[];
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
@@ -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