@agen-ai/provider-acp 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +355 -12
- package/dist/capabilities.d.ts +3 -1
- package/dist/capabilities.js +5 -4
- package/dist/configuration/catalog.js +0 -2
- package/dist/configuration/errors.js +0 -1
- package/dist/connection/contracts.d.ts +21 -0
- package/dist/connection/errors.js +0 -1
- package/dist/connection/initialize.d.ts +7 -3
- package/dist/connection/initialize.js +46 -17
- package/dist/connection/negotiation.d.ts +2 -1
- package/dist/connection/negotiation.js +15 -1
- package/dist/contracts.d.ts +3 -0
- package/dist/driver.d.ts +1 -1
- package/dist/driver.js +6 -39
- package/dist/driverFactory.d.ts +8 -0
- package/dist/driverFactory.js +48 -0
- package/dist/host/interactions.js +0 -1
- package/dist/host/leases.js +0 -1
- package/dist/host/router.js +0 -1
- package/dist/host/terminals.js +0 -2
- package/dist/index.d.ts +1 -0
- package/dist/instance.d.ts +7 -2
- package/dist/instance.js +102 -27
- package/dist/node/contracts.d.ts +6 -0
- package/dist/node/execution/errors.d.ts +11 -0
- package/dist/node/execution/errors.js +17 -0
- package/dist/node/execution/owner.d.ts +34 -0
- package/dist/node/execution/owner.js +162 -0
- package/dist/node/execution/types.d.ts +20 -0
- package/dist/node/execution/types.js +0 -0
- package/dist/node/index.d.ts +3 -1
- package/dist/node/index.js +16 -6
- package/dist/node/launch.d.ts +5 -8
- package/dist/node/launch.js +13 -1
- package/dist/permissions/contracts.d.ts +22 -0
- package/dist/permissions/contracts.js +0 -0
- package/dist/permissions/mapping.d.ts +13 -0
- package/dist/permissions/mapping.js +99 -0
- package/dist/permissions/selection.d.ts +5 -0
- package/dist/permissions/selection.js +96 -0
- package/dist/session/create.d.ts +7 -1
- package/dist/session/create.js +3 -1
- package/dist/session/errors.js +0 -1
- package/dist/session/mapping.d.ts +6 -0
- package/dist/session/mapping.js +5 -26
- package/dist/session/opening.d.ts +1 -0
- package/dist/session/opening.js +3 -1
- package/dist/session/queue.js +0 -1
- package/dist/session/requests/forms.js +0 -2
- package/dist/session/requests/pending.js +0 -1
- package/dist/session/resources.js +5 -3
- package/dist/session/session.d.ts +4 -1
- package/dist/session/session.js +67 -25
- package/dist/sessionInteractions/broker.d.ts +42 -0
- package/dist/sessionInteractions/broker.js +171 -0
- package/dist/sessionInteractions/contracts.d.ts +3 -0
- package/dist/sessionInteractions/contracts.js +0 -0
- package/dist/sessionInteractions/extensions.d.ts +3 -0
- package/dist/sessionInteractions/extensions.js +17 -0
- package/dist/sessionInteractions/grokFolderTrust.d.ts +43 -0
- package/dist/sessionInteractions/grokFolderTrust.js +107 -0
- package/dist/sessionInteractions/request.d.ts +38 -0
- package/dist/sessionInteractions/request.js +145 -0
- package/package.json +4 -4
|
@@ -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
|
+
};
|
package/dist/session/create.d.ts
CHANGED
|
@@ -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
|
|
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
|
package/dist/session/create.js
CHANGED
|
@@ -6,7 +6,9 @@ async function createAcpProviderSession(input) {
|
|
|
6
6
|
let state;
|
|
7
7
|
try {
|
|
8
8
|
state = new SessionState(input);
|
|
9
|
-
|
|
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 {
|
package/dist/session/errors.js
CHANGED
|
@@ -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;
|
package/dist/session/mapping.js
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
package/dist/session/opening.js
CHANGED
|
@@ -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);
|
package/dist/session/queue.js
CHANGED
|
@@ -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.
|
|
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
|
package/dist/session/session.js
CHANGED
|
@@ -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(() =>
|
|
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)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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 {
|