@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.
Files changed (81) hide show
  1. package/README.md +515 -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 +24 -1
  7. package/dist/connection/errors.js +0 -1
  8. package/dist/connection/initialize.d.ts +7 -3
  9. package/dist/connection/initialize.js +64 -19
  10. package/dist/connection/negotiation.d.ts +3 -1
  11. package/dist/connection/negotiation.js +22 -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.d.ts +6 -1
  18. package/dist/host/interactions.js +42 -17
  19. package/dist/host/leases.js +0 -1
  20. package/dist/host/router.d.ts +3 -2
  21. package/dist/host/router.js +23 -21
  22. package/dist/host/terminals.js +0 -2
  23. package/dist/index.d.ts +1 -0
  24. package/dist/instance.d.ts +7 -2
  25. package/dist/instance.js +113 -28
  26. package/dist/node/contracts.d.ts +6 -0
  27. package/dist/node/execution/errors.d.ts +11 -0
  28. package/dist/node/execution/errors.js +17 -0
  29. package/dist/node/execution/owner.d.ts +34 -0
  30. package/dist/node/execution/owner.js +162 -0
  31. package/dist/node/execution/types.d.ts +20 -0
  32. package/dist/node/execution/types.js +0 -0
  33. package/dist/node/index.d.ts +3 -1
  34. package/dist/node/index.js +16 -6
  35. package/dist/node/launch.d.ts +5 -8
  36. package/dist/node/launch.js +13 -1
  37. package/dist/permissions/contracts.d.ts +22 -0
  38. package/dist/permissions/contracts.js +0 -0
  39. package/dist/permissions/mapping.d.ts +13 -0
  40. package/dist/permissions/mapping.js +99 -0
  41. package/dist/permissions/selection.d.ts +5 -0
  42. package/dist/permissions/selection.js +96 -0
  43. package/dist/session/continuation.d.ts +33 -0
  44. package/dist/session/continuation.js +185 -0
  45. package/dist/session/create.d.ts +7 -1
  46. package/dist/session/create.js +3 -1
  47. package/dist/session/descendants.d.ts +29 -0
  48. package/dist/session/descendants.js +97 -0
  49. package/dist/session/errors.js +0 -1
  50. package/dist/session/mapping.d.ts +6 -0
  51. package/dist/session/mapping.js +5 -26
  52. package/dist/session/native.d.ts +1 -0
  53. package/dist/session/native.js +8 -0
  54. package/dist/session/opening.d.ts +1 -0
  55. package/dist/session/opening.js +3 -1
  56. package/dist/session/queue.js +0 -1
  57. package/dist/session/requests/elicitation.d.ts +1 -1
  58. package/dist/session/requests/elicitation.js +1 -1
  59. package/dist/session/requests/forms.js +0 -2
  60. package/dist/session/requests/grokWithdrawals.d.ts +28 -0
  61. package/dist/session/requests/grokWithdrawals.js +135 -0
  62. package/dist/session/requests/pending.d.ts +17 -4
  63. package/dist/session/requests/pending.js +63 -8
  64. package/dist/session/requests/resolution.js +4 -2
  65. package/dist/session/resources.js +5 -3
  66. package/dist/session/session.d.ts +12 -1
  67. package/dist/session/session.js +197 -47
  68. package/dist/session/turn.d.ts +6 -1
  69. package/dist/sessionInteractions/broker.d.ts +50 -0
  70. package/dist/sessionInteractions/broker.js +193 -0
  71. package/dist/sessionInteractions/contracts.d.ts +3 -0
  72. package/dist/sessionInteractions/contracts.js +0 -0
  73. package/dist/sessionInteractions/extensions.d.ts +4 -0
  74. package/dist/sessionInteractions/extensions.js +37 -0
  75. package/dist/sessionInteractions/grokFolderTrust.d.ts +42 -0
  76. package/dist/sessionInteractions/grokFolderTrust.js +106 -0
  77. package/dist/sessionInteractions/grokPlanReview.d.ts +16 -0
  78. package/dist/sessionInteractions/grokPlanReview.js +63 -0
  79. package/dist/sessionInteractions/request.d.ts +46 -0
  80. package/dist/sessionInteractions/request.js +173 -0
  81. package/package.json +4 -4
@@ -1,16 +1,37 @@
1
1
  import type { AcpClient, AcpJsonObject, AcpJsonValue, AcpNotificationContext, AcpReverseRequestContext, AcpRequestOptions } from '@agen-ai/acp-transport';
2
2
  import type { AcpHostInteractionScope } from '../host/contracts.js';
3
3
  import type { AcpBoundSession } from '../host/router.js';
4
+ import type { SessionInteractionBroker } from '../sessionInteractions/broker.js';
5
+ import type { AcpConnectionFactory } from '../contracts.js';
6
+ import type { NodeAcpExecutionOwner } from '../node/execution/owner.js';
7
+ export type AcpConnectionSource = Readonly<{
8
+ kind: 'factory';
9
+ create: AcpConnectionFactory;
10
+ }> | Readonly<{
11
+ kind: 'reserved';
12
+ reserve: () => NodeAcpExecutionOwner;
13
+ }>;
14
+ export type AcpConnectionAcquisition = Readonly<{
15
+ kind: 'factory';
16
+ create: AcpConnectionFactory;
17
+ }> | Readonly<{
18
+ kind: 'reserved';
19
+ owner: NodeAcpExecutionOwner;
20
+ }>;
4
21
  export interface AcpNegotiatedAgent {
5
22
  readonly protocolVersion: 1;
6
23
  readonly agentInfo?: Readonly<{
7
24
  name: string;
8
25
  version: string;
9
26
  }>;
27
+ /** Bounded compatibility observation retained only by the selected Grok permission profile. */
28
+ readonly agentVersion?: string;
10
29
  readonly agentCapabilities: AcpJsonObject;
11
30
  readonly authMethods: readonly AcpJsonValue[];
12
31
  }
13
32
  export interface AcpSessionIngress {
33
+ readonly joinCallbacks: () => Promise<void>;
34
+ readonly onExtensionNotification?: (input: AcpNotificationContext) => void | Promise<void>;
14
35
  readonly onNotification: (input: AcpNotificationContext) => void | Promise<void>;
15
36
  readonly onPermissionRequest: (input: AcpReverseRequestContext) => Promise<AcpJsonValue>;
16
37
  readonly onElicitationRequest: (input: AcpReverseRequestContext) => Promise<AcpJsonValue>;
@@ -19,10 +40,12 @@ export interface AcpInitializedConnection {
19
40
  readonly client: AcpClient;
20
41
  readonly negotiated: AcpNegotiatedAgent;
21
42
  readonly stopped: AbortSignal;
43
+ readonly execution?: NodeAcpExecutionOwner;
44
+ readonly sessionInteractions?: SessionInteractionBroker;
22
45
  readonly attachSession: (ingress: AcpSessionIngress) => () => void;
23
46
  readonly bindSession: (binding: AcpBoundSession) => void;
24
47
  readonly requestWithScope: (method: string, params: AcpJsonValue, scope: AcpHostInteractionScope, options?: AcpRequestOptions) => Promise<AcpJsonValue>;
25
- readonly hostElicitation?: (context: AcpReverseRequestContext) => Promise<AcpJsonValue>;
48
+ readonly hostElicitation?: (context: AcpReverseRequestContext, observeCallback?: (work: Promise<void>) => void) => Promise<AcpJsonValue>;
26
49
  readonly close: () => Promise<void>;
27
50
  }
28
51
  //# sourceMappingURL=contracts.d.ts.map
@@ -4,7 +4,6 @@ class AcpProviderConnectionError extends Error {
4
4
  this.reasonCode = reasonCode;
5
5
  this.name = "AcpProviderConnectionError";
6
6
  }
7
- reasonCode;
8
7
  }
9
8
  export {
10
9
  AcpProviderConnectionError
@@ -1,10 +1,12 @@
1
1
  import { type AcpUnknownExtensionPolicy } from '@agen-ai/acp-transport';
2
- import type { AcpConnectionFactory, AcpConnectionFactoryContext } from '../contracts.js';
2
+ import type { AcpConnectionFactoryContext } from '../contracts.js';
3
3
  import type { AcpProviderLimits } from '../limits.js';
4
- import type { AcpInitializedConnection } from './contracts.js';
4
+ import type { AcpConnectionAcquisition, AcpInitializedConnection } from './contracts.js';
5
5
  import type { AcpAuthenticationMethods, AcpHostFacilities } from '../host/contracts.js';
6
+ import type { AcpSessionInteractionSelection } from '../sessionInteractions/contracts.js';
7
+ import type { AcpPermissionMappingSelection } from '../permissions/contracts.js';
6
8
  export declare function initializeAcpConnection(input: {
7
- readonly createConnection: AcpConnectionFactory;
9
+ readonly source: AcpConnectionAcquisition;
8
10
  readonly unknownExtensionPolicy: AcpUnknownExtensionPolicy;
9
11
  readonly context: AcpConnectionFactoryContext;
10
12
  readonly limits: AcpProviderLimits;
@@ -12,5 +14,7 @@ export declare function initializeAcpConnection(input: {
12
14
  readonly host?: AcpHostFacilities;
13
15
  readonly authentication?: AcpAuthenticationMethods;
14
16
  readonly configuration?: 'managed' | 'selectable';
17
+ readonly sessionInteractions?: AcpSessionInteractionSelection;
18
+ readonly permissionMapping?: AcpPermissionMappingSelection;
15
19
  }): Promise<AcpInitializedConnection>;
16
20
  //# sourceMappingURL=initialize.d.ts.map
@@ -8,6 +8,11 @@ import { AcpProviderConnectionError } from "./errors.js";
8
8
  import { acquireAcpLease, releaseOwnedLease } from "./acquire.js";
9
9
  import { readNegotiatedAgent } from "./negotiation.js";
10
10
  import { AcpHostRouter } from "../host/router.js";
11
+ import { SessionInteractionBroker } from "../sessionInteractions/broker.js";
12
+ import { createSessionInteractionExtensionRegistry } from "../sessionInteractions/extensions.js";
13
+ import { GROK_FOLDER_TRUST_CAPABILITY_META, GROK_FOLDER_TRUST_METHOD } from "../sessionInteractions/grokFolderTrust.js";
14
+ import { awaitHost } from "../host/lifecycle.js";
15
+ import { GROK_PLAN_REVIEW_METHOD, GROK_QUEUE_METHOD, GROK_SESSION_NOTIFICATION_METHOD } from "../sessionInteractions/grokPlanReview.js";
11
16
  async function initializeAcpConnection(input) {
12
17
  throwIfAgentOperationAborted(input.context.signal);
13
18
  const initialization = new AbortController();
@@ -18,11 +23,14 @@ async function initializeAcpConnection(input) {
18
23
  "ACP connection acquisition or initialization exceeded its deadline."
19
24
  )), input.limits.initializationTimeoutMs);
20
25
  let lease;
26
+ const execution = input.source.kind === "reserved" ? input.source.owner : void 0;
21
27
  try {
22
- lease = await acquireAcpLease(input.createConnection, { ...input.context, signal: initialization.signal });
28
+ const context = { ...input.context, signal: initialization.signal };
29
+ lease = input.source.kind === "factory" ? await acquireAcpLease(input.source.create, context) : await awaitHost(input.source.owner.start(context), initialization.signal);
23
30
  try {
24
31
  validateLease(lease, input.usedStreams);
25
32
  } catch (cause) {
33
+ if (execution) throw cause;
26
34
  try {
27
35
  await releaseOwnedLease(lease);
28
36
  } catch (cleanupError) {
@@ -33,22 +41,32 @@ async function initializeAcpConnection(input) {
33
41
  } catch (cause) {
34
42
  clearTimeout(timeout);
35
43
  input.context.signal?.removeEventListener("abort", onInitializationAbort);
44
+ if (execution) {
45
+ try {
46
+ await execution.cleanup();
47
+ } catch (cleanupError) {
48
+ throw execution.retainedError(cause, cleanupError);
49
+ }
50
+ }
36
51
  throw cause;
37
52
  }
38
53
  const stopped = new AbortController();
39
54
  let client;
40
55
  let ingress;
56
+ let joinSessionCallbacks;
41
57
  let attached = false;
42
58
  let closePromise;
43
59
  let releasePromise;
44
60
  let released = false;
61
+ let sessionInteractions;
45
62
  const host = input.host ?? {};
46
63
  const router = new AcpHostRouter({
47
64
  instanceId: input.context.instanceId,
48
65
  host,
49
66
  limits: input.limits,
50
67
  stopped: stopped.signal,
51
- ingress: () => ingress
68
+ ingress: () => ingress,
69
+ ...input.sessionInteractions ? { humanTimeoutMs: () => sessionInteractions?.humanTimeoutMs() } : {}
52
70
  });
53
71
  const release = () => {
54
72
  if (released) return Promise.resolve();
@@ -63,11 +81,16 @@ async function initializeAcpConnection(input) {
63
81
  const stop = (reason) => {
64
82
  if (!stopped.signal.aborted) stopped.abort(reason);
65
83
  };
66
- const close = () => {
84
+ const closeActual = () => {
67
85
  if (!closePromise) {
68
86
  closePromise = Promise.resolve().then(async () => {
69
87
  try {
70
- const cleanup = await Promise.allSettled([router.close(), ...client ? [client.dispose()] : [], release()]);
88
+ const stopping = [router.close(), ...client ? [client.dispose()] : [], release()];
89
+ const cleanup = await Promise.allSettled([
90
+ ...stopping,
91
+ ...joinSessionCallbacks ? [joinSessionCallbacks()] : [],
92
+ ...sessionInteractions ? [sessionInteractions.join()] : []
93
+ ]);
71
94
  const failures = cleanup.filter((result) => result.status === "rejected");
72
95
  if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "ACP connection cleanup failed.");
73
96
  } finally {
@@ -82,21 +105,32 @@ async function initializeAcpConnection(input) {
82
105
  closePromise = void 0;
83
106
  throw error;
84
107
  });
108
+ sessionInteractions?.stop({ kind: "session_closed" });
85
109
  stop(new AcpProviderConnectionError("acp_provider_connection_closed", "The ACP connection was closed."));
86
110
  }
87
111
  return closePromise;
88
112
  };
89
- const onPeerEnd = () => {
90
- stop(new AcpProviderConnectionError("acp_provider_peer_closed", "The ACP peer closed its stream."));
113
+ execution?.retainCleanup(closeActual);
114
+ const close = () => execution ? execution.cleanup() : closeActual();
115
+ const fail = (error) => {
116
+ if (stopped.signal.aborted) return;
117
+ stop(error);
91
118
  void close().catch(() => void 0);
92
119
  };
120
+ if (input.sessionInteractions) sessionInteractions = new SessionInteractionBroker({
121
+ stopped: stopped.signal,
122
+ limits: input.limits,
123
+ onFailure: fail,
124
+ selection: input.sessionInteractions
125
+ });
126
+ const onPeerEnd = () => {
127
+ fail(new AcpProviderConnectionError("acp_provider_peer_closed", "The ACP peer closed its stream."));
128
+ };
93
129
  const onPeerError = (error) => {
94
- stop(error);
95
- void close().catch(() => void 0);
130
+ fail(error);
96
131
  };
97
132
  const onAbort = () => {
98
- stop(initialization.signal.reason instanceof Error ? initialization.signal.reason : new DOMException("ACP initialization was aborted.", "AbortError"));
99
- void close().catch(() => void 0);
133
+ fail(initialization.signal.reason instanceof Error ? initialization.signal.reason : new DOMException("ACP initialization was aborted.", "AbortError"));
100
134
  };
101
135
  lease.stdout.once("end", onPeerEnd);
102
136
  lease.stdout.once("close", onPeerEnd);
@@ -114,6 +148,7 @@ async function initializeAcpConnection(input) {
114
148
  schemaChannel: "stable",
115
149
  unknownExtensionPolicy: input.unknownExtensionPolicy,
116
150
  outboundRequestIdPrefix: "agenai-acp-",
151
+ ...input.sessionInteractions ? { extensions: createSessionInteractionExtensionRegistry(input.sessionInteractions) } : {},
117
152
  limits: {
118
153
  maxInboundFrameBytes: Math.min(input.limits.maxQueuedBytes, 1024 * 1024),
119
154
  maxOutboundFrameBytes: Math.min(input.limits.maxQueuedBytes, 8 * 1024 * 1024),
@@ -125,6 +160,13 @@ async function initializeAcpConnection(input) {
125
160
  shutdownDrainMs: 0
126
161
  },
127
162
  notificationHandlers: {
163
+ ...input.sessionInteractions?.mapping === "grok-build-1.0.60-session-requests-v1" ? Object.fromEntries([GROK_QUEUE_METHOD, GROK_SESSION_NOTIFICATION_METHOD].map((method) => [
164
+ method,
165
+ (notification) => {
166
+ if (!ingress?.onExtensionNotification) throw new AcpProviderConnectionError("acp_provider_session_not_attached", "Native continuation output has no bound owner.");
167
+ return ingress.onExtensionNotification(notification);
168
+ }
169
+ ])) : {},
128
170
  "session/update": (notification) => {
129
171
  if (!ingress) throw new AcpProviderConnectionError("acp_provider_session_not_attached", "ACP output has no bound session.");
130
172
  return ingress.onNotification(notification);
@@ -132,6 +174,7 @@ async function initializeAcpConnection(input) {
132
174
  "elicitation/complete": (notification) => router.complete(notification.params ?? null)
133
175
  },
134
176
  reverseRequestHandlers: {
177
+ ...sessionInteractions ? { [input.sessionInteractions?.mapping === "grok-folder-trust-v1" ? GROK_FOLDER_TRUST_METHOD : GROK_PLAN_REVIEW_METHOD]: sessionInteractions.handleRequest } : {},
135
178
  ...Object.fromEntries([
136
179
  "session/request_permission",
137
180
  "elicitation/create",
@@ -144,38 +187,37 @@ async function initializeAcpConnection(input) {
144
187
  "terminal/release"
145
188
  ].map((method) => [method, (request) => router.admit(request)]))
146
189
  },
147
- onFatalError: (error) => {
148
- stop(error);
149
- void close().catch(() => void 0);
150
- }
190
+ onFatalError: fail
151
191
  });
152
192
  void client.closed.then((result) => {
153
- stop(result.error ?? new AcpProviderConnectionError("acp_provider_connection_closed", "The ACP connection stopped."));
154
- void close().catch(() => void 0);
193
+ fail(result.error ?? new AcpProviderConnectionError("acp_provider_connection_closed", "The ACP connection stopped."));
155
194
  });
156
195
  const response = await createAcpStandardSessionOperations(client).initialize({
157
196
  protocolVersion: 1,
158
197
  clientCapabilities: {
198
+ ...input.sessionInteractions?.mapping === "grok-folder-trust-v1" ? { _meta: GROK_FOLDER_TRUST_CAPABILITY_META } : {},
159
199
  fs: { readTextFile: !!host.readTextFile, writeTextFile: !!host.writeTextFile },
160
200
  terminal: !!host.createTerminal,
161
201
  auth: { terminal: !!host.authenticateInTerminal && !!input.authentication?.terminal },
162
202
  elicitation: { form: {}, ...host.url ? { url: {} } : {} },
163
203
  ...input.configuration === "selectable" ? { session: { configOptions: { boolean: {} } } } : {}
164
204
  },
165
- clientInfo: { name: "@agen-ai/provider-acp", version: "0.4.0" }
205
+ clientInfo: { name: "@agen-ai/provider-acp", version: "0.4.2" }
166
206
  }, {
167
207
  timeoutMs: input.limits.initializationTimeoutMs,
168
208
  signal: initialization.signal
169
209
  });
170
210
  if (stopped.signal.aborted) throw stopped.signal.reason;
171
- const negotiated = readNegotiatedAgent(response);
211
+ const negotiated = readNegotiatedAgent(response, input.permissionMapping, input.sessionInteractions);
172
212
  initialization.signal.removeEventListener("abort", onAbort);
173
213
  return Object.freeze({
174
214
  client,
175
215
  negotiated,
176
216
  stopped: stopped.signal,
217
+ ...execution ? { execution } : {},
218
+ ...sessionInteractions ? { sessionInteractions } : {},
177
219
  bindSession: (binding) => router.bindSession(binding),
178
- ...host.form || host.url ? { hostElicitation: (context) => router.sessionElicitation(context) } : {},
220
+ ...host.form || host.url ? { hostElicitation: (context, observeCallback) => router.sessionElicitation(context, observeCallback) } : {},
179
221
  async requestWithScope(method, params, scope, options = {}) {
180
222
  if (!["authenticate", "session/new", "session/load", "session/resume", "session/set_config_option", "session/set_mode"].includes(method)) {
181
223
  throw new TypeError("This ACP method does not grant request-scoped host interaction authority.");
@@ -192,6 +234,7 @@ async function initializeAcpConnection(input) {
192
234
  if (attached) throw new AcpProviderConnectionError("acp_provider_connection_already_attached", "ACP connections accept exactly one session.");
193
235
  attached = true;
194
236
  ingress = next;
237
+ joinSessionCallbacks = next.joinCallbacks;
195
238
  return () => {
196
239
  ingress = void 0;
197
240
  };
@@ -199,9 +242,11 @@ async function initializeAcpConnection(input) {
199
242
  close
200
243
  });
201
244
  } catch (cause) {
245
+ if (execution?.cleanupFailure) throw execution.retainedError(cause, execution.cleanupFailure.error);
202
246
  try {
203
247
  await close();
204
248
  } catch (cleanupError) {
249
+ if (execution) throw execution.retainedError(cause, cleanupError);
205
250
  throw new AggregateError([cause, cleanupError], "ACP initialization and cleanup failed.");
206
251
  }
207
252
  throw cause;
@@ -1,6 +1,8 @@
1
1
  import type { AcpJsonValue } from '@agen-ai/acp-transport';
2
2
  import type { AcpNegotiatedAgent } from './contracts.js';
3
+ import type { AcpPermissionMappingSelection } from '../permissions/contracts.js';
4
+ import type { AcpSessionInteractionSelection } from '../sessionInteractions/contracts.js';
3
5
  /** The transport has already validated the complete pinned initialize response. */
4
- export declare function readNegotiatedAgent(response: AcpJsonValue): AcpNegotiatedAgent;
6
+ export declare function readNegotiatedAgent(response: AcpJsonValue, permissionMapping?: AcpPermissionMappingSelection, sessionInteractions?: AcpSessionInteractionSelection): AcpNegotiatedAgent;
5
7
  export declare function negotiatedEnvelope(negotiated: AcpNegotiatedAgent): string;
6
8
  //# sourceMappingURL=negotiation.d.ts.map
@@ -11,7 +11,7 @@ function immutableProtocolData(value) {
11
11
  }
12
12
  return value;
13
13
  }
14
- function readNegotiatedAgent(response) {
14
+ function readNegotiatedAgent(response, permissionMapping = { mapping: "once-only-v1" }, sessionInteractions) {
15
15
  const value = response;
16
16
  if (value.protocolVersion !== 1) {
17
17
  throw new AcpProviderConnectionError("acp_provider_version_unsupported", "ACP wire version 1 is required.");
@@ -27,9 +27,30 @@ function readNegotiatedAgent(response) {
27
27
  }
28
28
  agentInfo = Object.freeze({ name: info.name, version: info.version });
29
29
  }
30
+ let agentVersion;
31
+ if (sessionInteractions?.mapping === "grok-build-1.0.60-session-requests-v1") {
32
+ const meta = value._meta;
33
+ if (meta === null || typeof meta !== "object" || Array.isArray(meta) || meta.agentVersion !== "1.0.60") {
34
+ throw new AcpProviderConnectionError("acp_session_profile_mismatch", "The selected session mapping requires exact initialized agentVersion 1.0.60.");
35
+ }
36
+ agentVersion = "1.0.60";
37
+ }
38
+ if (permissionMapping.mapping === "declared-complete-consequences-v1") {
39
+ if (agentInfo?.name !== permissionMapping.agentInfo.name || agentInfo.version !== permissionMapping.agentInfo.version) {
40
+ throw new AcpProviderConnectionError("acp_permission_profile_mismatch", "The initialized ACP identity does not match the selected permission profile.");
41
+ }
42
+ } else if (permissionMapping.mapping === "grok-build-1.0.50-edit-remembrance-v1") {
43
+ const meta = value._meta;
44
+ const version = meta !== null && typeof meta === "object" && !Array.isArray(meta) ? meta.agentVersion : void 0;
45
+ if (typeof version !== "string" || version.length > 160 || version !== version.trim() || /[\p{Cc}\p{Cf}]/u.test(version) || version !== "1.0.50") {
46
+ throw new AcpProviderConnectionError("acp_permission_profile_mismatch", "The selected Grok permission profile requires the exact initialized agentVersion 1.0.50.");
47
+ }
48
+ agentVersion = version;
49
+ }
30
50
  return Object.freeze({
31
51
  protocolVersion: 1,
32
52
  ...agentInfo ? { agentInfo } : {},
53
+ ...agentVersion ? { agentVersion } : {},
33
54
  agentCapabilities: immutableProtocolData(value.agentCapabilities ?? {}),
34
55
  authMethods: immutableProtocolData(value.authMethods ?? [])
35
56
  });
@@ -4,6 +4,7 @@ import type { AgentSessionHostBinding, MaybePromise } from '@agen-ai/agent-runti
4
4
  import type { AcpProviderLimits } from './limits.js';
5
5
  import type { AcpStandardMcpServer, AcpUnknownExtensionPolicy } from '@agen-ai/acp-transport';
6
6
  import type { AcpAuthenticationMethods, AcpHostFacilities } from './host/contracts.js';
7
+ import type { AcpPermissionMappingSelection } from './permissions/contracts.js';
7
8
  export type { AcpProviderLimits } from './limits.js';
8
9
  export interface AcpConnectionStreams {
9
10
  readonly stdout: Readable;
@@ -51,6 +52,8 @@ export interface CreateAcpProviderDriverOptions {
51
52
  readonly createConnection: AcpConnectionFactory;
52
53
  /** Defaults to standard handling of unknown optional vendor extensions. */
53
54
  readonly unknownExtensionPolicy?: AcpUnknownExtensionPolicy;
55
+ /** Defaults to once-only choices; complete profiles require independently qualified native semantics. */
56
+ readonly permissionMapping?: AcpPermissionMappingSelection;
54
57
  readonly limits?: Partial<AcpProviderLimits>;
55
58
  readonly configuration?: 'managed' | 'selectable';
56
59
  readonly authentication?: AcpAuthenticationMethods;
package/dist/driver.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type AgentProviderDriver } from '@agen-ai/agent-runtime';
1
+ import type { AgentProviderDriver } from '@agen-ai/agent-runtime';
2
2
  import type { CreateAcpProviderDriverOptions } from './contracts.js';
3
3
  export declare function createAcpProviderDriver(options: CreateAcpProviderDriverOptions): AgentProviderDriver;
4
4
  //# sourceMappingURL=driver.d.ts.map
package/dist/driver.js CHANGED
@@ -1,43 +1,10 @@
1
- import { parseAgentProviderKey } from "@agen-ai/agent-protocol";
2
- import { defineAgentProviderDriver } from "@agen-ai/agent-runtime";
3
- import { parseAcpProviderConfiguration } from "./configuration.js";
4
- import { createAcpProviderInstance } from "./instance.js";
5
- import { resolveAcpProviderLimits } from "./limits.js";
6
- import { resolveAuthenticationMapping } from "./auth/methods.js";
7
- import { resolveHostFacilities } from "./host/validation.js";
8
- import { resolveAcpMcpServers, resolveAcpSessionMcpBinding } from "./mcp.js";
1
+ import { createAcpProviderDriverInternal } from "./driverFactory.js";
9
2
  function createAcpProviderDriver(options) {
10
- const providerKey = parseAgentProviderKey(options.providerKey);
11
- const createConnection = options.createConnection;
12
- if (typeof createConnection !== "function") throw new TypeError("An ACP connection factory is required.");
13
- const unknownExtensionPolicy = options.unknownExtensionPolicy === void 0 ? "standard" : options.unknownExtensionPolicy;
14
- if (unknownExtensionPolicy !== "reject" && unknownExtensionPolicy !== "standard") throw new TypeError("ACP unknown extension policy is invalid.");
15
- const limits = resolveAcpProviderLimits(options.limits);
16
- const configuration = options.configuration ?? "managed";
17
- if (configuration !== "managed" && configuration !== "selectable") throw new TypeError("ACP configuration mode is invalid.");
18
- const authentication = resolveAuthenticationMapping(options.authentication);
19
- const host = resolveHostFacilities(options.host);
20
- const mcpServers = resolveAcpMcpServers(options.mcpServers);
21
- const sessionMcpBinding = resolveAcpSessionMcpBinding(options.sessionMcpBinding);
22
- const usedStreams = /* @__PURE__ */ new WeakSet();
23
- return defineAgentProviderDriver({
24
- providerKey,
25
- supportsMultipleInstances: true,
26
- parseConfiguration: parseAcpProviderConfiguration,
27
- createInstance: ({ instanceId }) => createAcpProviderInstance({
28
- providerKey,
29
- instanceId,
30
- createConnection,
31
- unknownExtensionPolicy,
32
- limits,
33
- usedStreams,
34
- configuration,
35
- authentication,
36
- host,
37
- mcpServers,
38
- ...sessionMcpBinding ? { sessionMcpBinding } : {}
39
- })
40
- });
3
+ if (Object.hasOwn(options, "sessionInteractions")) {
4
+ throw new TypeError("ACP session interaction mappings require the Node provider constructor and its exclusive process ownership.");
5
+ }
6
+ if (Object.hasOwn(options, "executionBoundary")) throw new TypeError("Execution boundaries require the Node provider constructor.");
7
+ return createAcpProviderDriverInternal({ ...options, connectionSource: { kind: "factory", create: options.createConnection } });
41
8
  }
42
9
  export {
43
10
  createAcpProviderDriver
@@ -0,0 +1,8 @@
1
+ import { type AgentProviderDriver } from '@agen-ai/agent-runtime';
2
+ import type { CreateAcpProviderDriverOptions } from './contracts.js';
3
+ import type { AcpSessionInteractionSelection } from './sessionInteractions/contracts.js';
4
+ import type { AcpConnectionSource } from './connection/contracts.js';
5
+ export declare function createAcpProviderDriverInternal(options: Omit<CreateAcpProviderDriverOptions, 'createConnection'> & Readonly<{
6
+ connectionSource: AcpConnectionSource;
7
+ }>, sessionInteractions?: AcpSessionInteractionSelection): AgentProviderDriver;
8
+ //# sourceMappingURL=driverFactory.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { parseAgentProviderKey } from "@agen-ai/agent-protocol";
2
+ import { defineAgentProviderDriver } from "@agen-ai/agent-runtime";
3
+ import { parseAcpProviderConfiguration } from "./configuration.js";
4
+ import { createAcpProviderInstance } from "./instance.js";
5
+ import { resolveAcpProviderLimits } from "./limits.js";
6
+ import { resolveAuthenticationMapping } from "./auth/methods.js";
7
+ import { resolveHostFacilities } from "./host/validation.js";
8
+ import { resolveAcpMcpServers, resolveAcpSessionMcpBinding } from "./mcp.js";
9
+ import { resolveAcpPermissionMapping } from "./permissions/selection.js";
10
+ function createAcpProviderDriverInternal(options, sessionInteractions) {
11
+ const providerKey = parseAgentProviderKey(options.providerKey);
12
+ const connectionSource = options.connectionSource;
13
+ if (connectionSource.kind === "factory" && typeof connectionSource.create !== "function") throw new TypeError("An ACP connection factory is required.");
14
+ const unknownExtensionPolicy = options.unknownExtensionPolicy === void 0 ? "standard" : options.unknownExtensionPolicy;
15
+ if (unknownExtensionPolicy !== "reject" && unknownExtensionPolicy !== "standard") throw new TypeError("ACP unknown extension policy is invalid.");
16
+ const limits = resolveAcpProviderLimits(options.limits);
17
+ const configuration = options.configuration ?? "managed";
18
+ if (configuration !== "managed" && configuration !== "selectable") throw new TypeError("ACP configuration mode is invalid.");
19
+ const authentication = resolveAuthenticationMapping(options.authentication);
20
+ const host = resolveHostFacilities(options.host);
21
+ const mcpServers = resolveAcpMcpServers(options.mcpServers);
22
+ const sessionMcpBinding = resolveAcpSessionMcpBinding(options.sessionMcpBinding);
23
+ const permissionMapping = resolveAcpPermissionMapping(options.permissionMapping);
24
+ const usedStreams = /* @__PURE__ */ new WeakSet();
25
+ return defineAgentProviderDriver({
26
+ providerKey,
27
+ supportsMultipleInstances: true,
28
+ parseConfiguration: parseAcpProviderConfiguration,
29
+ createInstance: ({ instanceId }) => createAcpProviderInstance({
30
+ providerKey,
31
+ instanceId,
32
+ connectionSource,
33
+ unknownExtensionPolicy,
34
+ limits,
35
+ usedStreams,
36
+ configuration,
37
+ authentication,
38
+ host,
39
+ mcpServers,
40
+ permissionMapping,
41
+ ...sessionMcpBinding ? { sessionMcpBinding } : {},
42
+ ...sessionInteractions ? { sessionInteractions } : {}
43
+ })
44
+ });
45
+ }
46
+ export {
47
+ createAcpProviderDriverInternal
48
+ };
@@ -7,15 +7,20 @@ export declare class AcpHostInteractions {
7
7
  private readonly urls;
8
8
  private readonly pendingUrls;
9
9
  private readonly leases;
10
+ private readonly forms;
10
11
  constructor(input: {
11
12
  readonly instanceId: AgentInstanceId;
12
13
  readonly host: AcpHostFacilities;
13
14
  readonly limits: AcpProviderLimits;
14
15
  readonly stopped: AbortSignal;
16
+ readonly humanTimeoutMs?: () => number | undefined;
15
17
  });
16
- request(context: AcpReverseRequestContext, scope: AcpHostInteractionScope, scopeSignal: AbortSignal): Promise<AcpJsonValue>;
18
+ request(context: AcpReverseRequestContext, scope: AcpHostInteractionScope, scopeSignal: AbortSignal, observeCallback?: (work: Promise<void>) => void): Promise<AcpJsonValue>;
19
+ joinRequest(settled: Promise<void>): Promise<void>;
17
20
  complete(id: string): Promise<void>;
18
21
  private releaseUrl;
22
+ private closeForms;
23
+ private closeOwned;
19
24
  close(): Promise<void>;
20
25
  closeScope(signal: AbortSignal): Promise<void>;
21
26
  }
@@ -1,3 +1,4 @@
1
+ import { DEFAULT_AGENT_SESSION_INTERACTION_LIMITS } from "@agen-ai/agent-runtime";
1
2
  import { validateAcpFormContent, validateAcpFormSchema } from "../session/requests/forms.js";
2
3
  import { awaitHost, hostDeadline } from "./lifecycle.js";
3
4
  import { AcpHostLeases } from "./leases.js";
@@ -7,11 +8,11 @@ class AcpHostInteractions {
7
8
  this.input = input;
8
9
  this.leases = new AcpHostLeases(input.limits.hostOperationTimeoutMs);
9
10
  }
10
- input;
11
11
  urls = /* @__PURE__ */ new Map();
12
12
  pendingUrls = /* @__PURE__ */ new Map();
13
13
  leases;
14
- async request(context, scope, scopeSignal) {
14
+ forms = /* @__PURE__ */ new Map();
15
+ async request(context, scope, scopeSignal, observeCallback) {
15
16
  const params = hostObject(context.params);
16
17
  const message = hostText(params.message, 4e3);
17
18
  const signal = AbortSignal.any([context.signal, scopeSignal, this.input.stopped]);
@@ -19,15 +20,23 @@ class AcpHostInteractions {
19
20
  if (!this.input.host.form) throw unsupportedHostRequest();
20
21
  const schema = freezeHostData(structuredClone(hostObject(params.requestedSchema)));
21
22
  validateAcpFormSchema(schema);
22
- const deadline2 = hostDeadline(signal, this.input.limits.authenticationTimeoutMs);
23
+ const timeoutMs = scope.kind === "authentication" ? this.input.limits.authenticationTimeoutMs : this.input.humanTimeoutMs ? this.input.humanTimeoutMs() : DEFAULT_AGENT_SESSION_INTERACTION_LIMITS.requestTimeoutMs;
24
+ const lifetime = new AbortController();
25
+ const formSignal = AbortSignal.any([signal, lifetime.signal]);
26
+ const deadline2 = timeoutMs === void 0 ? { signal: formSignal, close() {
27
+ } } : hostDeadline(formSignal, timeoutMs);
28
+ const callback = Promise.resolve().then(() => {
29
+ if (deadline2.signal.aborted) throw deadline2.signal.reason;
30
+ return this.input.host.form({ instanceId: this.input.instanceId, scope, signal: deadline2.signal, message, requestedSchema: schema });
31
+ });
32
+ const actualCallback = callback.then(() => void 0, () => void 0);
33
+ observeCallback?.(actualCallback);
34
+ const completion = Promise.all([actualCallback, context.settled]).then(() => {
35
+ this.forms.delete(context.settled);
36
+ });
37
+ this.forms.set(context.settled, { scopeSignal, lifetime, completion });
23
38
  try {
24
- const result = await awaitHost(this.input.host.form({
25
- instanceId: this.input.instanceId,
26
- scope,
27
- signal: deadline2.signal,
28
- message,
29
- requestedSchema: schema
30
- }), deadline2.signal);
39
+ const result = await awaitHost(callback, deadline2.signal);
31
40
  if (result.action === "accept") {
32
41
  const content = hostObject(result.content);
33
42
  validateAcpFormContent(schema, content);
@@ -102,6 +111,9 @@ class AcpHostInteractions {
102
111
  if (!this.urls.has(id)) deadline.close();
103
112
  }
104
113
  }
114
+ joinRequest(settled) {
115
+ return this.forms.get(settled)?.completion ?? Promise.resolve();
116
+ }
105
117
  async complete(id) {
106
118
  const interaction = this.urls.get(id);
107
119
  if (!interaction) {
@@ -128,18 +140,31 @@ class AcpHostInteractions {
128
140
  interaction.detach();
129
141
  this.urls.delete(id);
130
142
  }
131
- async close() {
132
- await this.leases.close();
133
- for (const interaction of this.urls.values()) interaction.detach();
134
- this.urls.clear();
143
+ async closeForms(scope) {
144
+ const entries = [...this.forms.values()].filter((entry) => scope === void 0 || entry.scopeSignal === scope);
145
+ for (const entry of entries) entry.lifetime.abort(new DOMException("Host form closed.", "AbortError"));
146
+ const deadline = hostDeadline(new AbortController().signal, this.input.limits.hostOperationTimeoutMs);
147
+ try {
148
+ await awaitHost(Promise.all(entries.map((entry) => entry.completion)), deadline.signal);
149
+ } finally {
150
+ deadline.close();
151
+ }
135
152
  }
136
- async closeScope(signal) {
137
- await this.leases.close(signal);
138
- for (const [id, interaction] of this.urls) if (interaction.scopeSignal === signal) {
153
+ async closeOwned(scope) {
154
+ const results = await Promise.allSettled([this.leases.close(scope), this.closeForms(scope)]);
155
+ const failures = results.filter((result) => result.status === "rejected");
156
+ if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "ACP host interaction cleanup failed.");
157
+ for (const [id, interaction] of this.urls) if (scope === void 0 || interaction.scopeSignal === scope) {
139
158
  interaction.detach();
140
159
  this.urls.delete(id);
141
160
  }
142
161
  }
162
+ close() {
163
+ return this.closeOwned();
164
+ }
165
+ closeScope(signal) {
166
+ return this.closeOwned(signal);
167
+ }
143
168
  }
144
169
  export {
145
170
  AcpHostInteractions
@@ -4,7 +4,6 @@ class AcpHostLeases {
4
4
  constructor(timeoutMs) {
5
5
  this.timeoutMs = timeoutMs;
6
6
  }
7
- timeoutMs;
8
7
  entries = /* @__PURE__ */ new Set();
9
8
  used = /* @__PURE__ */ new WeakSet();
10
9
  closed = false;
@@ -14,13 +14,14 @@ export declare class AcpHostRouter {
14
14
  private readonly terminals;
15
15
  private readonly requests;
16
16
  private binding;
17
- private pending;
17
+ private readonly pending;
18
18
  private bytes;
19
19
  constructor(input: {
20
20
  readonly instanceId: AgentInstanceId;
21
21
  readonly host: AcpHostFacilities;
22
22
  readonly limits: AcpProviderLimits;
23
23
  readonly stopped: AbortSignal;
24
+ readonly humanTimeoutMs?: () => number | undefined;
24
25
  readonly ingress: () => AcpSessionIngress | undefined;
25
26
  });
26
27
  bindSession(binding: AcpBoundSession): void;
@@ -41,7 +42,7 @@ export declare class AcpHostRouter {
41
42
  private requireSession;
42
43
  private hostRequest;
43
44
  complete(value: AcpJsonValue): Promise<void>;
44
- sessionElicitation(context: AcpReverseRequestContext): Promise<AcpJsonValue>;
45
+ sessionElicitation(context: AcpReverseRequestContext, observeCallback?: (work: Promise<void>) => void): Promise<AcpJsonValue>;
45
46
  close(): Promise<void>;
46
47
  }
47
48
  //# sourceMappingURL=router.d.ts.map