@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
@@ -0,0 +1,99 @@
1
+ import { parseAgentApprovalOptionId } from "@agen-ai/agent-protocol";
2
+ import { AcpProviderSessionError } from "../session/errors.js";
3
+ import { displayText, nativeObject, nativeString } from "../session/native.js";
4
+ const GROK_EDIT_MENU = {
5
+ toolKind: "edit",
6
+ options: [
7
+ { optionId: "allow-edits-session", kind: "allow_always" },
8
+ { optionId: "allow-once", kind: "allow_once" },
9
+ { optionId: "reject-once", kind: "reject_once" }
10
+ ]
11
+ };
12
+ function unsupportedConsequence() {
13
+ throw new AcpProviderSessionError("acp_permission_consequence_unsupported", "The selected ACP permission mapping does not represent this complete native menu.");
14
+ }
15
+ function readNativeOptions(params) {
16
+ if (!Array.isArray(params.options) || params.options.length < 1 || params.options.length > 16) {
17
+ throw new AcpProviderSessionError("acp_permission_options_invalid", "ACP permission menus require between one and sixteen options.");
18
+ }
19
+ const ids = /* @__PURE__ */ new Set();
20
+ return params.options.map((value) => {
21
+ const option = nativeObject(value, "permission option");
22
+ const optionId = nativeString(option.optionId, "permission option identity");
23
+ if (!optionId || optionId.length > 256 || optionId !== optionId.trim() || /[\u0000-\u001f\u007f-\u009f]/u.test(optionId)) {
24
+ throw new AcpProviderSessionError("acp_permission_options_invalid", "ACP permission option identities must be bounded canonical strings.");
25
+ }
26
+ if (ids.has(optionId)) throw new AcpProviderSessionError("acp_permission_option_duplicate", "ACP repeated a permission option identity.");
27
+ ids.add(optionId);
28
+ const kind = option.kind;
29
+ if (kind !== "allow_once" && kind !== "reject_once" && kind !== "allow_always" && kind !== "reject_always") {
30
+ throw new AcpProviderSessionError("acp_permission_options_invalid", "ACP permission option kind is unsupported.");
31
+ }
32
+ return { optionId, kind, source: option };
33
+ });
34
+ }
35
+ function matchesMenu(menu, tool, options) {
36
+ return menu.toolKind === tool.kind && menu.options.length === options.length && menu.options.every((option, index) => option.optionId === options[index]?.optionId && option.kind === options[index]?.kind);
37
+ }
38
+ function assertCompleteChoiceFields(params, options) {
39
+ if (Object.keys(params).some((key) => !["sessionId", "toolCall", "options"].includes(key)) || options.some((option) => Object.keys(option.source).some((key) => !["optionId", "name", "kind"].includes(key)))) {
40
+ unsupportedConsequence();
41
+ }
42
+ }
43
+ function qualifiedGrokEdit(tool, options) {
44
+ const input = tool.rawInput;
45
+ if (input === null || typeof input !== "object" || Array.isArray(input)) return false;
46
+ return matchesMenu(GROK_EDIT_MENU, tool, options) && Object.keys(input).length === 5 && typeof input.file_path === "string" && input.file_path.length > 0 && typeof input.old_string === "string" && typeof input.new_string === "string" && input.variant === "SearchReplace" && input.replace_all === false;
47
+ }
48
+ function mapAcpPermissionOptions(selection, params) {
49
+ const native = readNativeOptions(params);
50
+ const tool = nativeObject(params.toolCall, "permission tool");
51
+ let complete = false;
52
+ let fallback = false;
53
+ if (selection.mapping === "declared-complete-consequences-v1") {
54
+ assertCompleteChoiceFields(params, native);
55
+ if (!selection.menus.some((menu) => matchesMenu(menu, tool, native))) unsupportedConsequence();
56
+ complete = true;
57
+ } else if (selection.mapping === "grok-build-1.0.50-edit-remembrance-v1") {
58
+ if (tool.kind === "edit") {
59
+ assertCompleteChoiceFields(params, native);
60
+ if (!qualifiedGrokEdit(tool, native)) unsupportedConsequence();
61
+ complete = true;
62
+ } else fallback = true;
63
+ }
64
+ const nativeOptions = /* @__PURE__ */ new Map();
65
+ const options = [];
66
+ for (const [index, option] of native.entries()) {
67
+ const remembered = option.kind === "allow_always" || option.kind === "reject_always";
68
+ if (remembered && !complete) continue;
69
+ const approved = option.kind === "allow_once" || option.kind === "allow_always";
70
+ const optionId = parseAgentApprovalOptionId(`acp:option:${index + 1}`);
71
+ nativeOptions.set(optionId, option.optionId);
72
+ options.push({
73
+ optionId,
74
+ label: remembered ? approved ? "Allow and remember" : "Deny and remember" : displayText(option.source.name, approved ? "Allow once" : "Reject once"),
75
+ decision: approved ? "approved" : "denied",
76
+ consequence: remembered ? {
77
+ kind: "provider_managed_remembrance",
78
+ duration: { kind: "unknown" },
79
+ applicability: { kind: "unknown" },
80
+ explanation: "The provider is asked to remember this decision. Duration and future matching are unspecified. Delivery does not establish successful persistence or revocation."
81
+ } : { kind: "scoped", persistence: "once", scope: { kind: "exact_action" } }
82
+ });
83
+ }
84
+ if (options.length === 0) {
85
+ if (fallback) unsupportedConsequence();
86
+ throw new AcpProviderSessionError("acp_permission_persistence_unsupported", "ACP offered no supported once-only permission choice.");
87
+ }
88
+ return {
89
+ options,
90
+ nativeOptions,
91
+ ...fallback ? { warning: {
92
+ code: "acp_permission_remembrance_unmapped",
93
+ message: "The selected ACP profile does not map remembered choices for this action. Only once-only choices are available."
94
+ } } : {}
95
+ };
96
+ }
97
+ export {
98
+ mapAcpPermissionOptions
99
+ };
@@ -0,0 +1,5 @@
1
+ import type { AgentApprovalCapability } from '@agen-ai/agent-protocol';
2
+ import type { AcpPermissionMappingSelection } from './contracts.js';
3
+ export declare function resolveAcpPermissionMapping(value: unknown): AcpPermissionMappingSelection;
4
+ export declare function acpPermissionCapability(selection: AcpPermissionMappingSelection): AgentApprovalCapability;
5
+ //# sourceMappingURL=selection.d.ts.map
@@ -0,0 +1,96 @@
1
+ function selectionObject(value, keys) {
2
+ if (value === null || typeof value !== "object" || Array.isArray(value) || Reflect.ownKeys(value).some((key) => typeof key !== "string" || !keys.includes(key))) {
3
+ throw new TypeError("ACP permission mapping selections must contain only their declared fields.");
4
+ }
5
+ return value;
6
+ }
7
+ function canonicalText(value, maximum, identity = false) {
8
+ if (typeof value !== "string" || !value || value !== value.trim() || value.length > maximum || (identity ? /[\p{Cc}\p{Cf}]/u : /[\u0000-\u001f\u007f-\u009f]/u).test(value)) {
9
+ throw new TypeError("ACP permission mapping identities must be bounded canonical strings.");
10
+ }
11
+ return value;
12
+ }
13
+ function optionKind(value) {
14
+ switch (value) {
15
+ case "allow_once":
16
+ case "reject_once":
17
+ case "allow_always":
18
+ case "reject_always":
19
+ return value;
20
+ default:
21
+ throw new TypeError("ACP permission option kind is invalid.");
22
+ }
23
+ }
24
+ function toolKind(value) {
25
+ switch (value) {
26
+ case "read":
27
+ case "edit":
28
+ case "delete":
29
+ case "move":
30
+ case "search":
31
+ case "execute":
32
+ case "think":
33
+ case "fetch":
34
+ case "switch_mode":
35
+ case "other":
36
+ return value;
37
+ default:
38
+ throw new TypeError("ACP permission tool kind is invalid.");
39
+ }
40
+ }
41
+ function boundedNonempty(value, parse) {
42
+ if (!Array.isArray(value) || value.length < 1 || value.length > 16) {
43
+ throw new TypeError("ACP permission declarations require between one and sixteen entries.");
44
+ }
45
+ const first = parse(value[0]);
46
+ return Object.freeze([first, ...Array.from(value.slice(1), parse)]);
47
+ }
48
+ function menuSignature(value) {
49
+ const menu = selectionObject(value, ["toolKind", "options"]);
50
+ const ids = /* @__PURE__ */ new Set();
51
+ const options = boundedNonempty(menu.options, (entry) => {
52
+ const option = selectionObject(entry, ["optionId", "kind"]);
53
+ const optionId = canonicalText(option.optionId, 256);
54
+ if (ids.has(optionId)) throw new TypeError("ACP declared permission option identities must be unique.");
55
+ ids.add(optionId);
56
+ return Object.freeze({ optionId, kind: optionKind(option.kind) });
57
+ });
58
+ return Object.freeze({ toolKind: toolKind(menu.toolKind), options });
59
+ }
60
+ function resolveAcpPermissionMapping(value) {
61
+ if (value === void 0) return Object.freeze({ mapping: "once-only-v1" });
62
+ const selection = selectionObject(value, ["mapping", "agentInfo", "menus"]);
63
+ if (selection.mapping === "once-only-v1" || selection.mapping === "grok-build-1.0.50-edit-remembrance-v1") {
64
+ selectionObject(value, ["mapping"]);
65
+ return Object.freeze({ mapping: selection.mapping });
66
+ }
67
+ if (selection.mapping !== "declared-complete-consequences-v1") throw new TypeError("ACP permission mapping is invalid.");
68
+ const info = selectionObject(selection.agentInfo, ["name", "version"]);
69
+ const agentInfo = Object.freeze({ name: canonicalText(info.name, 256, true), version: canonicalText(info.version, 160, true) });
70
+ const signatures = /* @__PURE__ */ new Set();
71
+ const menus = boundedNonempty(selection.menus, (entry) => {
72
+ const menu = menuSignature(entry);
73
+ const signature = JSON.stringify(menu);
74
+ if (signatures.has(signature)) throw new TypeError("ACP declared permission menus must be unique.");
75
+ signatures.add(signature);
76
+ return menu;
77
+ });
78
+ return Object.freeze({ mapping: selection.mapping, agentInfo, menus });
79
+ }
80
+ function acpPermissionCapability(selection) {
81
+ const once = { persistence: "once", scopeKinds: ["exact_action"] };
82
+ if (selection.mapping === "once-only-v1") {
83
+ return { kind: "supported", modes: [once], providerManagedRemembrance: false };
84
+ }
85
+ if (selection.mapping === "grok-build-1.0.50-edit-remembrance-v1") {
86
+ return { kind: "supported", modes: [once], providerManagedRemembrance: true };
87
+ }
88
+ const options = selection.menus.flatMap((menu) => menu.options);
89
+ const precise = options.some((option) => option.kind === "allow_once" || option.kind === "reject_once");
90
+ const remembered = options.some((option) => option.kind === "allow_always" || option.kind === "reject_always");
91
+ return remembered ? { kind: "supported", modes: precise ? [once] : [], providerManagedRemembrance: true } : { kind: "supported", modes: [once], providerManagedRemembrance: false };
92
+ }
93
+ export {
94
+ acpPermissionCapability,
95
+ resolveAcpPermissionMapping
96
+ };
@@ -2,6 +2,7 @@ import type { AgentCapabilities, AgentSessionBinding } from '@agen-ai/agent-prot
2
2
  import { type AgentProviderCreateSessionInput, type AgentProviderResumeSessionInput, type AgentProviderSession, type AgentProviderSessionContext } from '@agen-ai/agent-runtime';
3
3
  import type { AcpInitializedConnection } from '../connection/contracts.js';
4
4
  import type { AcpProviderLimits } from '../limits.js';
5
+ import type { AcpPermissionMappingSelection } from '../permissions/contracts.js';
5
6
  export interface CreateAcpProviderSessionInput {
6
7
  readonly context: AgentProviderCreateSessionInput | AgentProviderResumeSessionInput;
7
8
  readonly openingInput: AgentProviderSessionContext;
@@ -13,9 +14,14 @@ export interface CreateAcpProviderSessionInput {
13
14
  readonly connection: AcpInitializedConnection;
14
15
  readonly capabilities: AgentCapabilities;
15
16
  readonly limits: AcpProviderLimits;
17
+ readonly permissionMapping: AcpPermissionMappingSelection;
16
18
  readonly now: () => string;
17
19
  readonly onClosed: () => void;
18
20
  readonly closeResources: () => Promise<void>;
19
21
  }
20
- export declare function createAcpProviderSession(input: CreateAcpProviderSessionInput): Promise<AgentProviderSession>;
22
+ export interface AcpOpenedProviderSession {
23
+ readonly session: AgentProviderSession;
24
+ readonly cleanup: () => Promise<void>;
25
+ }
26
+ export declare function createAcpProviderSession(input: CreateAcpProviderSessionInput): Promise<AcpOpenedProviderSession>;
21
27
  //# sourceMappingURL=create.d.ts.map
@@ -6,7 +6,9 @@ async function createAcpProviderSession(input) {
6
6
  let state;
7
7
  try {
8
8
  state = new SessionState(input);
9
- return await state.open();
9
+ const session = await state.open();
10
+ const owner = state;
11
+ return Object.freeze({ session, cleanup: () => owner.close() });
10
12
  } catch (error) {
11
13
  const cleanup = () => state ? state.close() : input.closeResources();
12
14
  try {
@@ -4,7 +4,6 @@ class AcpProviderSessionError extends Error {
4
4
  this.reasonCode = reasonCode;
5
5
  this.name = "AcpProviderSessionError";
6
6
  }
7
- reasonCode;
8
7
  }
9
8
  function sessionError(error) {
10
9
  return error instanceof Error ? error : new AcpProviderSessionError("acp_session_failed", "The ACP session failed.");
@@ -2,9 +2,14 @@ import type { AcpJsonObject } from '@agen-ai/acp-transport';
2
2
  import { type AgentApprovalRequest, type AgentItemId, type AgentSessionId, type AgentTurnCompletedPayload, type AgentTurnId } from '@agen-ai/agent-protocol';
3
3
  import { type AgentProviderOutput } from '@agen-ai/agent-runtime';
4
4
  import type { SessionQueue } from './queue.js';
5
+ import type { AcpPermissionMappingSelection } from '../permissions/contracts.js';
5
6
  export interface MappedPermission {
6
7
  readonly request: AgentApprovalRequest;
7
8
  readonly nativeOptions: ReadonlyMap<string, string>;
9
+ readonly warning?: Readonly<{
10
+ code: string;
11
+ message: string;
12
+ }>;
8
13
  }
9
14
  export declare class TurnMapping {
10
15
  private readonly context;
@@ -17,6 +22,7 @@ export declare class TurnMapping {
17
22
  readonly sessionId: AgentSessionId;
18
23
  readonly turnId: AgentTurnId;
19
24
  readonly workingDirectory: string;
25
+ readonly permissionMapping: AcpPermissionMappingSelection;
20
26
  readonly now: () => string;
21
27
  readonly nextItemId: () => number;
22
28
  readonly nextRequestId: () => number;
@@ -1,5 +1,4 @@
1
1
  import {
2
- parseAgentApprovalOptionId,
3
2
  parseAgentApprovalRequest,
4
3
  parseAgentItemId,
5
4
  parseAgentRequestId
@@ -9,11 +8,11 @@ import { AcpProviderSessionError } from "./errors.js";
9
8
  import { displayText, nativeObject, nativeString } from "./native.js";
10
9
  import { acpPlanSteps } from "./plans.js";
11
10
  import { acpToolDiff } from "./files.js";
11
+ import { mapAcpPermissionOptions } from "../permissions/mapping.js";
12
12
  class TurnMapping {
13
13
  constructor(context) {
14
14
  this.context = context;
15
15
  }
16
- context;
17
16
  tools = /* @__PURE__ */ new Map();
18
17
  messages = /* @__PURE__ */ new Map();
19
18
  files = /* @__PURE__ */ new Map();
@@ -21,7 +20,7 @@ class TurnMapping {
21
20
  planRevision = 0;
22
21
  event(type, payload) {
23
22
  return createAgentEventOutput({
24
- protocolVersion: 10,
23
+ protocolVersion: 11,
25
24
  sessionId: this.context.sessionId,
26
25
  turnId: this.context.turnId,
27
26
  occurredAt: this.context.now(),
@@ -168,28 +167,7 @@ class TurnMapping {
168
167
  permission(params, itemId) {
169
168
  const tool = nativeObject(params.toolCall, "permission tool");
170
169
  const title = displayText(tool.title, "Agent tool");
171
- if (!Array.isArray(params.options)) throw new AcpProviderSessionError("acp_permission_options_invalid", "ACP permission options must be an array.");
172
- const nativeOptions = /* @__PURE__ */ new Map();
173
- const options = params.options.flatMap((value, index) => {
174
- const option = nativeObject(value, "permission option");
175
- if (option.kind !== "allow_once" && option.kind !== "reject_once") return [];
176
- const optionId = parseAgentApprovalOptionId(`acp:option:${index + 1}`);
177
- const nativeId = nativeString(option.optionId, "permission option identity");
178
- if ([...nativeOptions.values()].includes(nativeId)) {
179
- throw new AcpProviderSessionError("acp_permission_option_duplicate", "ACP repeated a permission option identity.");
180
- }
181
- nativeOptions.set(optionId, nativeId);
182
- return [{
183
- optionId,
184
- label: displayText(option.name, option.kind === "allow_once" ? "Allow once" : "Reject once"),
185
- decision: option.kind === "allow_once" ? "approved" : "denied",
186
- persistence: "once",
187
- scope: { kind: "exact_action" }
188
- }];
189
- });
190
- if (options.length === 0) {
191
- throw new AcpProviderSessionError("acp_permission_persistence_unsupported", "ACP offered no supported once-only permission choice.");
192
- }
170
+ const { options, nativeOptions, warning } = mapAcpPermissionOptions(this.context.permissionMapping, params);
193
171
  return {
194
172
  request: parseAgentApprovalRequest({
195
173
  requestKind: "approval",
@@ -198,7 +176,8 @@ class TurnMapping {
198
176
  subject: { kind: "tool", title, itemId },
199
177
  options
200
178
  }),
201
- nativeOptions
179
+ nativeOptions,
180
+ ...warning ? { warning } : {}
202
181
  };
203
182
  }
204
183
  *complete(payload) {
@@ -18,5 +18,6 @@ export declare function openAcpNativeSession(input: CreateAcpProviderSessionInpu
18
18
  readonly replay: AcpHistoryReplay;
19
19
  readonly bind: (binding: AgentSessionBinding) => void;
20
20
  readonly observeSetup: (result: AcpJsonObject) => void;
21
+ readonly completeOpeningResponse?: () => void;
21
22
  }): Promise<void>;
22
23
  //# sourceMappingURL=opening.d.ts.map
@@ -18,7 +18,6 @@ class AcpHistoryReplay {
18
18
  constructor(limits) {
19
19
  this.limits = limits;
20
20
  }
21
- limits;
22
21
  active = false;
23
22
  messages = 0;
24
23
  bytes = 0;
@@ -35,6 +34,7 @@ async function openAcpNativeSession(input, hooks) {
35
34
  const client = acpSessionControlClient(input);
36
35
  const operations = createAcpStandardSessionOperations(client);
37
36
  const resumed = input.resume;
37
+ const sessionInteractions = input.connection.sessionInteractions;
38
38
  if (resumed) {
39
39
  hooks.bind(resumed.binding);
40
40
  hooks.replay.active = resumed.method === "session/load";
@@ -43,6 +43,7 @@ async function openAcpNativeSession(input, hooks) {
43
43
  const options = {
44
44
  ...input.context.signal === void 0 ? {} : { signal: input.context.signal },
45
45
  timeoutMs: input.limits.initializationTimeoutMs,
46
+ ...sessionInteractions ? { onRequestDispatch: () => sessionInteractions.onOpeningDispatched() } : {},
46
47
  onResponseReceived: (response) => {
47
48
  hooks.replay.active = false;
48
49
  if (response.kind === "error") return;
@@ -54,6 +55,7 @@ async function openAcpNativeSession(input, hooks) {
54
55
  input.context.onBindingCreated(binding);
55
56
  }
56
57
  hooks.observeSetup(result);
58
+ hooks.completeOpeningResponse?.();
57
59
  }
58
60
  };
59
61
  if (resumed?.method === "session/resume") await client.request("session/resume", { ...params, sessionId: resumed.binding.conversationId }, options);
@@ -13,7 +13,6 @@ class SessionQueue {
13
13
  constructor(limits) {
14
14
  this.limits = limits;
15
15
  }
16
- limits;
17
16
  entries = [];
18
17
  retained = /* @__PURE__ */ new Map();
19
18
  wake;
@@ -193,8 +193,6 @@ class AcpFormFlow {
193
193
  }]
194
194
  });
195
195
  }
196
- form;
197
- nextRequestId;
198
196
  phase = "intent";
199
197
  request;
200
198
  resolve(resolution) {
@@ -8,7 +8,6 @@ class PendingSessionRequests {
8
8
  constructor(input) {
9
9
  this.input = input;
10
10
  }
11
- input;
12
11
  pending = /* @__PURE__ */ new Set();
13
12
  get size() {
14
13
  return this.pending.size;
@@ -8,7 +8,6 @@ class AcpSessionResources {
8
8
  constructor(input) {
9
9
  this.input = input;
10
10
  }
11
- input;
12
11
  lifetime = new AbortController();
13
12
  acquisition;
14
13
  releaseMcp;
@@ -64,9 +63,12 @@ class AcpSessionResources {
64
63
  close() {
65
64
  if (this.closed) return Promise.resolve();
66
65
  this.lifetime.abort(new DOMException("ACP session resources were closed.", "AbortError"));
66
+ const nativeCleanup = this.nativeClosed ? Promise.resolve() : this.input.connection.close();
67
+ void nativeCleanup.catch(() => {
68
+ });
67
69
  this.cleanup ??= Promise.resolve().then(async () => {
68
70
  if (!this.nativeClosed) {
69
- await this.input.connection.close();
71
+ await (this.input.connection.execution?.joinCleanup() ?? nativeCleanup);
70
72
  this.nativeClosed = true;
71
73
  }
72
74
  if (this.acquisition) {
@@ -82,7 +84,7 @@ class AcpSessionResources {
82
84
  this.cleanup = void 0;
83
85
  });
84
86
  const deadline = hostDeadline(new AbortController().signal, this.input.limits.hostOperationTimeoutMs);
85
- return awaitHost(this.cleanup, deadline.signal).finally(() => {
87
+ return awaitHost(Promise.all([nativeCleanup, this.cleanup]).then(() => void 0), deadline.signal).finally(() => {
86
88
  deadline.close();
87
89
  });
88
90
  }
@@ -15,6 +15,8 @@ export declare class SessionState {
15
15
  private nativeSessionId;
16
16
  private turn;
17
17
  private failure;
18
+ private failureObserved;
19
+ private cleanupFailure;
18
20
  private closed;
19
21
  private closePromise;
20
22
  private itemSequence;
@@ -38,6 +40,7 @@ export declare class SessionState {
38
40
  private resolveRequest;
39
41
  private cancelNative;
40
42
  interruptTurn(input: AgentProviderInterruptTurnInput): Promise<AgentProviderTurnInterruptionResult>;
41
- close(): Promise<void>;
43
+ close(observeFailure?: boolean): Promise<void>;
44
+ private observeFailure;
42
45
  }
43
46
  //# sourceMappingURL=session.d.ts.map
@@ -51,7 +51,6 @@ class SessionState {
51
51
  input.connection.stopped.addEventListener("abort", this.onStopped, { once: true });
52
52
  if (input.connection.stopped.aborted) this.onStopped();
53
53
  }
54
- input;
55
54
  queue;
56
55
  operations;
57
56
  requests;
@@ -65,6 +64,8 @@ class SessionState {
65
64
  nativeSessionId;
66
65
  turn;
67
66
  failure;
67
+ failureObserved = false;
68
+ cleanupFailure;
68
69
  closed = false;
69
70
  closePromise;
70
71
  itemSequence = 0;
@@ -72,11 +73,13 @@ class SessionState {
72
73
  async open() {
73
74
  this.requireUsable();
74
75
  throwIfAgentOperationAborted(this.input.context.signal);
76
+ const sessionInteractions = this.input.connection.sessionInteractions;
75
77
  await openAcpNativeSession(this.input, {
76
78
  replay: this.replay,
77
79
  bind: (binding2) => {
78
80
  this.binding = binding2;
79
81
  this.nativeSessionId = binding2.conversationId;
82
+ this.input.connection.sessionInteractions?.bindNativeSession(binding2.conversationId);
80
83
  this.bindHostSession();
81
84
  },
82
85
  observeSetup: (result) => {
@@ -91,7 +94,10 @@ class SessionState {
91
94
  }
92
95
  }
93
96
  this.configuration.observeSetup(result);
94
- }
97
+ },
98
+ ...sessionInteractions ? {
99
+ completeOpeningResponse: () => sessionInteractions.completeOpeningResponse()
100
+ } : {}
95
101
  });
96
102
  this.requireUsable();
97
103
  if (!this.binding) throw new AcpProviderSessionError("acp_binding_missing", "ACP did not return a session binding.");
@@ -108,7 +114,7 @@ class SessionState {
108
114
  environment: this.environment.port,
109
115
  collaboration: Object.freeze({ kind: "unsupported" }),
110
116
  generatedResources: Object.freeze({ kind: "unsupported" }),
111
- close: async () => this.close()
117
+ close: async () => this.close(true)
112
118
  });
113
119
  }
114
120
  requireUsable() {
@@ -123,13 +129,16 @@ class SessionState {
123
129
  fail(error) {
124
130
  if (this.failure) return;
125
131
  this.failure = error;
132
+ this.input.connection.sessionInteractions?.stop({ kind: "failed", cause: error });
126
133
  this.turn?.hostInteractions.abort(error);
127
134
  this.queue.fail(error);
128
135
  this.requests.cancelAll();
129
136
  this.environment.close();
130
137
  this.turn?.settled.reject(error);
131
138
  this.clearTurnTimers();
132
- void this.input.connection.close().catch(() => void 0);
139
+ void this.input.connection.close().catch((cause) => {
140
+ this.cleanupFailure = cause;
141
+ });
133
142
  }
134
143
  admitNotification(context) {
135
144
  this.requireUsable();
@@ -204,6 +213,7 @@ class SessionState {
204
213
  sessionId: this.input.context.sessionId,
205
214
  turnId: input.turnId,
206
215
  workingDirectory: this.input.context.workingDirectory,
216
+ permissionMapping: this.input.permissionMapping,
207
217
  now: this.input.now,
208
218
  nextItemId: () => ++this.itemSequence,
209
219
  nextRequestId: () => ++this.requestSequence,
@@ -330,10 +340,19 @@ class SessionState {
330
340
  if (turn.terminal) return "terminal";
331
341
  yield* this.emit(next.value);
332
342
  if (turn.terminal) return "terminal";
343
+ if (request.answered || turn.cancelReason) break;
333
344
  next = mappedTool.next();
334
345
  }
346
+ if (!next.done) {
347
+ entry.release();
348
+ continue;
349
+ }
335
350
  const permission = turn.mapping.permission(request.params, next.value);
336
351
  request.mapped = { kind: "permission", permission, request: permission.request };
352
+ if (permission.warning) {
353
+ yield* this.emit(turn.mapping.event("runtime.warning", permission.warning));
354
+ if (turn.terminal) return "terminal";
355
+ }
337
356
  } else {
338
357
  if (request.params.toolCallId != null && !turn.mapping.hasTool(nativeString(request.params.toolCallId, "elicitation tool identity"))) {
339
358
  this.requests.reject(request, new AcpRpcHandlerError(-32602, "ACP form tool identity was not observed in this turn."));
@@ -346,6 +365,10 @@ class SessionState {
346
365
  const flow = new AcpFormFlow(request.form, () => parseAgentRequestId(`acp:request:${++this.requestSequence}`));
347
366
  request.mapped = { kind: "form", flow, request: flow.request };
348
367
  }
368
+ if (request.answered || turn.cancelReason) {
369
+ entry.release();
370
+ continue;
371
+ }
349
372
  turn.waiting = request;
350
373
  yield* this.offerRequest(turn, request);
351
374
  if (turn.terminal) return "terminal";
@@ -451,27 +474,46 @@ class SessionState {
451
474
  throw error;
452
475
  }
453
476
  }
454
- async close() {
455
- if (this.closePromise) return this.closePromise;
456
- this.closed = true;
457
- this.closePromise = Promise.resolve().then(async () => {
458
- await this.input.closeResources();
459
- this.input.onClosed();
460
- }).catch((error2) => {
461
- this.closePromise = void 0;
462
- throw error2;
463
- });
464
- const error = new AcpProviderSessionError("acp_session_closed", "The ACP session is closed.");
465
- this.turn?.hostInteractions.abort(error);
466
- this.queue.fail(error);
467
- this.requests.cancelAll();
468
- this.turn?.settled.reject(error);
469
- this.clearTurnTimers();
470
- this.environment.close();
471
- this.turn?.mapping.release();
472
- this.detach();
473
- this.input.connection.stopped.removeEventListener("abort", this.onStopped);
474
- return this.closePromise;
477
+ async close(observeFailure = false) {
478
+ if (!this.closePromise) {
479
+ this.closed = true;
480
+ this.closePromise = Promise.resolve().then(async () => {
481
+ await this.input.closeResources();
482
+ this.input.onClosed();
483
+ }).catch((error2) => {
484
+ this.cleanupFailure = error2;
485
+ this.closePromise = void 0;
486
+ throw error2;
487
+ });
488
+ this.input.connection.sessionInteractions?.stop({ kind: "session_closed" });
489
+ const error = new AcpProviderSessionError("acp_session_closed", "The ACP session is closed.");
490
+ this.turn?.hostInteractions.abort(error);
491
+ this.queue.fail(error);
492
+ this.requests.cancelAll();
493
+ this.turn?.settled.reject(error);
494
+ this.clearTurnTimers();
495
+ this.environment.close();
496
+ this.turn?.mapping.release();
497
+ this.detach();
498
+ this.input.connection.stopped.removeEventListener("abort", this.onStopped);
499
+ }
500
+ try {
501
+ await this.closePromise;
502
+ } catch (cause) {
503
+ const failure2 = this.observeFailure(observeFailure);
504
+ if (failure2) throw new AggregateError([failure2, cause], "ACP session failure and cleanup remain owned.");
505
+ throw cause;
506
+ }
507
+ const failure = this.observeFailure(observeFailure);
508
+ if (failure) {
509
+ if (this.cleanupFailure !== void 0) throw new AggregateError([failure, this.cleanupFailure], "ACP session failed before cleanup completed.");
510
+ throw failure;
511
+ }
512
+ }
513
+ observeFailure(explicit) {
514
+ if (!explicit || !this.input.connection.sessionInteractions || !this.failure || this.failureObserved) return void 0;
515
+ this.failureObserved = true;
516
+ return this.failure;
475
517
  }
476
518
  }
477
519
  export {