@agen-ai/provider-acp 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +515 -12
- package/dist/capabilities.d.ts +3 -1
- package/dist/capabilities.js +5 -4
- package/dist/configuration/catalog.js +0 -2
- package/dist/configuration/errors.js +0 -1
- package/dist/connection/contracts.d.ts +24 -1
- package/dist/connection/errors.js +0 -1
- package/dist/connection/initialize.d.ts +7 -3
- package/dist/connection/initialize.js +64 -19
- package/dist/connection/negotiation.d.ts +3 -1
- package/dist/connection/negotiation.js +22 -1
- package/dist/contracts.d.ts +3 -0
- package/dist/driver.d.ts +1 -1
- package/dist/driver.js +6 -39
- package/dist/driverFactory.d.ts +8 -0
- package/dist/driverFactory.js +48 -0
- package/dist/host/interactions.d.ts +6 -1
- package/dist/host/interactions.js +42 -17
- package/dist/host/leases.js +0 -1
- package/dist/host/router.d.ts +3 -2
- package/dist/host/router.js +23 -21
- package/dist/host/terminals.js +0 -2
- package/dist/index.d.ts +1 -0
- package/dist/instance.d.ts +7 -2
- package/dist/instance.js +113 -28
- package/dist/node/contracts.d.ts +6 -0
- package/dist/node/execution/errors.d.ts +11 -0
- package/dist/node/execution/errors.js +17 -0
- package/dist/node/execution/owner.d.ts +34 -0
- package/dist/node/execution/owner.js +162 -0
- package/dist/node/execution/types.d.ts +20 -0
- package/dist/node/execution/types.js +0 -0
- package/dist/node/index.d.ts +3 -1
- package/dist/node/index.js +16 -6
- package/dist/node/launch.d.ts +5 -8
- package/dist/node/launch.js +13 -1
- package/dist/permissions/contracts.d.ts +22 -0
- package/dist/permissions/contracts.js +0 -0
- package/dist/permissions/mapping.d.ts +13 -0
- package/dist/permissions/mapping.js +99 -0
- package/dist/permissions/selection.d.ts +5 -0
- package/dist/permissions/selection.js +96 -0
- package/dist/session/continuation.d.ts +33 -0
- package/dist/session/continuation.js +185 -0
- package/dist/session/create.d.ts +7 -1
- package/dist/session/create.js +3 -1
- package/dist/session/descendants.d.ts +29 -0
- package/dist/session/descendants.js +97 -0
- package/dist/session/errors.js +0 -1
- package/dist/session/mapping.d.ts +6 -0
- package/dist/session/mapping.js +5 -26
- package/dist/session/native.d.ts +1 -0
- package/dist/session/native.js +8 -0
- package/dist/session/opening.d.ts +1 -0
- package/dist/session/opening.js +3 -1
- package/dist/session/queue.js +0 -1
- package/dist/session/requests/elicitation.d.ts +1 -1
- package/dist/session/requests/elicitation.js +1 -1
- package/dist/session/requests/forms.js +0 -2
- package/dist/session/requests/grokWithdrawals.d.ts +28 -0
- package/dist/session/requests/grokWithdrawals.js +135 -0
- package/dist/session/requests/pending.d.ts +17 -4
- package/dist/session/requests/pending.js +63 -8
- package/dist/session/requests/resolution.js +4 -2
- package/dist/session/resources.js +5 -3
- package/dist/session/session.d.ts +12 -1
- package/dist/session/session.js +197 -47
- package/dist/session/turn.d.ts +6 -1
- package/dist/sessionInteractions/broker.d.ts +50 -0
- package/dist/sessionInteractions/broker.js +193 -0
- package/dist/sessionInteractions/contracts.d.ts +3 -0
- package/dist/sessionInteractions/contracts.js +0 -0
- package/dist/sessionInteractions/extensions.d.ts +4 -0
- package/dist/sessionInteractions/extensions.js +37 -0
- package/dist/sessionInteractions/grokFolderTrust.d.ts +42 -0
- package/dist/sessionInteractions/grokFolderTrust.js +106 -0
- package/dist/sessionInteractions/grokPlanReview.d.ts +16 -0
- package/dist/sessionInteractions/grokPlanReview.js +63 -0
- package/dist/sessionInteractions/request.d.ts +46 -0
- package/dist/sessionInteractions/request.js +173 -0
- package/package.json +4 -4
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { GROK_QUEUE_METHOD, GROK_SESSION_NOTIFICATION_METHOD } from "../sessionInteractions/grokPlanReview.js";
|
|
2
|
+
import { AcpProviderSessionError } from "./errors.js";
|
|
3
|
+
import { nativeIdentity, nativeObject, nativeString } from "./native.js";
|
|
4
|
+
class GrokDescendantObservations {
|
|
5
|
+
constructor(input) {
|
|
6
|
+
this.input = input;
|
|
7
|
+
}
|
|
8
|
+
bySession = /* @__PURE__ */ new Map();
|
|
9
|
+
bySubagent = /* @__PURE__ */ new Map();
|
|
10
|
+
/** Returns true only when this notification is wholly owned by the observation scope. */
|
|
11
|
+
admit(context, historical) {
|
|
12
|
+
const params = nativeObject(context.params, "descendant notification");
|
|
13
|
+
const meta = params._meta === void 0 ? void 0 : nativeObject(params._meta, "descendant metadata");
|
|
14
|
+
if (meta?.isReplay !== void 0 && typeof meta.isReplay !== "boolean") throw new AcpProviderSessionError(
|
|
15
|
+
"acp_descendant_replay_invalid",
|
|
16
|
+
"The selected native replay marker must be a boolean."
|
|
17
|
+
);
|
|
18
|
+
const replay = historical || meta?.isReplay === true;
|
|
19
|
+
const root = this.input.rootSessionId();
|
|
20
|
+
if (params.sessionId === root && root !== void 0) {
|
|
21
|
+
if (context.method !== GROK_SESSION_NOTIFICATION_METHOD) return false;
|
|
22
|
+
const update = nativeObject(params.update, "descendant session update");
|
|
23
|
+
if (update.sessionUpdate === "subagent_spawned") this.spawn(update, root, replay);
|
|
24
|
+
else if (update.sessionUpdate === "subagent_finished") {
|
|
25
|
+
const child = this.bySession.get(nativeIdentity(update.child_session_id, "finished child session identity"));
|
|
26
|
+
if (child?.origin === "live" && !replay) this.input.withdrawals.finish(child.identity, update);
|
|
27
|
+
} else return false;
|
|
28
|
+
this.input.observeHistory(params);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (root === void 0) return false;
|
|
32
|
+
const sessionId = nativeIdentity(params.sessionId, "child session identity");
|
|
33
|
+
const observation = this.bySession.get(sessionId);
|
|
34
|
+
if (!observation || observation.origin === "historical" && !replay) return false;
|
|
35
|
+
if (context.method === GROK_QUEUE_METHOD) {
|
|
36
|
+
this.validateQueue(params);
|
|
37
|
+
} else {
|
|
38
|
+
const update = nativeObject(params.update, "child session update");
|
|
39
|
+
const kind = nativeString(update.sessionUpdate, "child session update kind");
|
|
40
|
+
if (kind === "subagent_spawned") throw new AcpProviderSessionError(
|
|
41
|
+
"acp_descendant_nested_unsupported",
|
|
42
|
+
"The selected native mapping does not admit nested descendant relationships."
|
|
43
|
+
);
|
|
44
|
+
if (context.method === GROK_SESSION_NOTIFICATION_METHOD && !replay) this.input.withdrawals.observe(observation.identity, update);
|
|
45
|
+
}
|
|
46
|
+
this.input.observeHistory(params);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
spawn(update, root, historical) {
|
|
50
|
+
const value = Object.freeze({
|
|
51
|
+
parentSessionId: nativeIdentity(update.parent_session_id, "parent session identity"),
|
|
52
|
+
childSessionId: nativeIdentity(update.child_session_id, "child session identity"),
|
|
53
|
+
subagentId: nativeIdentity(update.subagent_id, "subagent identity"),
|
|
54
|
+
...update.attempt_id === void 0 ? {} : { attemptId: nativeIdentity(update.attempt_id, "attempt identity") },
|
|
55
|
+
...update.parent_prompt_id === void 0 ? {} : { parentPromptId: nativeIdentity(update.parent_prompt_id, "parent prompt identity") }
|
|
56
|
+
});
|
|
57
|
+
if (value.parentSessionId !== root || value.childSessionId === root || value.subagentId === root) throw new AcpProviderSessionError(
|
|
58
|
+
"acp_descendant_identity_invalid",
|
|
59
|
+
"The selected native spawn does not identify a direct child of the bound root."
|
|
60
|
+
);
|
|
61
|
+
const prior = this.bySession.get(value.childSessionId) ?? this.bySubagent.get(value.subagentId);
|
|
62
|
+
if (prior) {
|
|
63
|
+
if (JSON.stringify(prior.identity) !== JSON.stringify(value) || prior.origin === "historical" && !historical) throw new AcpProviderSessionError(
|
|
64
|
+
"acp_descendant_identity_conflict",
|
|
65
|
+
"The selected native descendant identity cannot be rebound or revived."
|
|
66
|
+
);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!historical && !this.input.activeTurn()) throw new AcpProviderSessionError(
|
|
70
|
+
"acp_descendant_without_prompt",
|
|
71
|
+
"A live native descendant requires active owned parent work."
|
|
72
|
+
);
|
|
73
|
+
const observation = {
|
|
74
|
+
identity: value,
|
|
75
|
+
origin: historical ? "historical" : "live",
|
|
76
|
+
release: this.input.reserveState(value)
|
|
77
|
+
};
|
|
78
|
+
this.bySession.set(value.childSessionId, observation);
|
|
79
|
+
this.bySubagent.set(value.subagentId, observation);
|
|
80
|
+
}
|
|
81
|
+
validateQueue(params) {
|
|
82
|
+
if (!Array.isArray(params.entries) || params.entries.length > 256) throw new AcpProviderSessionError(
|
|
83
|
+
"acp_descendant_queue_invalid",
|
|
84
|
+
"The selected native child queue is invalid."
|
|
85
|
+
);
|
|
86
|
+
for (const entry of params.entries) nativeIdentity(nativeObject(entry, "child queue entry").id, "child queue prompt identity");
|
|
87
|
+
if (params.runningPromptId != null) nativeIdentity(params.runningPromptId, "running child prompt identity");
|
|
88
|
+
}
|
|
89
|
+
stop() {
|
|
90
|
+
for (const observation of this.bySession.values()) observation.release();
|
|
91
|
+
this.bySession.clear();
|
|
92
|
+
this.bySubagent.clear();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
GrokDescendantObservations
|
|
97
|
+
};
|
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: 12,
|
|
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) {
|
package/dist/session/native.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AcpJsonObject, AcpJsonValue } from '@agen-ai/acp-transport';
|
|
2
2
|
export declare function nativeObject(value: AcpJsonValue | undefined, field: string): AcpJsonObject;
|
|
3
3
|
export declare function nativeString(value: AcpJsonValue | undefined, field: string): string;
|
|
4
|
+
export declare function nativeIdentity(value: unknown, field: string): string;
|
|
4
5
|
export declare function displayText(value: AcpJsonValue | undefined, fallback: string, limit?: number): string;
|
|
5
6
|
export declare function nativeBytes(value: unknown): number;
|
|
6
7
|
export declare const SESSION_METADATA_UPDATES: Set<string>;
|
package/dist/session/native.js
CHANGED
|
@@ -11,6 +11,13 @@ function nativeString(value, field) {
|
|
|
11
11
|
}
|
|
12
12
|
return value;
|
|
13
13
|
}
|
|
14
|
+
function nativeIdentity(value, field) {
|
|
15
|
+
if (typeof value !== "string" || !value || value !== value.trim() || Buffer.byteLength(value, "utf8") > 256 || /[\p{Cc}\p{Cf}]/u.test(value)) throw new AcpProviderSessionError(
|
|
16
|
+
"acp_descendant_identity_invalid",
|
|
17
|
+
`The selected native ${field} is invalid.`
|
|
18
|
+
);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
14
21
|
function displayText(value, fallback, limit = 120) {
|
|
15
22
|
const text = typeof value === "string" ? value.trim() : "";
|
|
16
23
|
return (text || fallback).slice(0, limit).trimEnd();
|
|
@@ -29,6 +36,7 @@ export {
|
|
|
29
36
|
SESSION_METADATA_UPDATES,
|
|
30
37
|
displayText,
|
|
31
38
|
nativeBytes,
|
|
39
|
+
nativeIdentity,
|
|
32
40
|
nativeObject,
|
|
33
41
|
nativeString
|
|
34
42
|
};
|
|
@@ -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.context.interactions ? input.connection.sessionInteractions : void 0;
|
|
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
|
@@ -9,6 +9,6 @@ export declare function prepareAcpSessionElicitation(input: {
|
|
|
9
9
|
readonly host: AcpInitializedConnection['hostElicitation'];
|
|
10
10
|
}): Readonly<{
|
|
11
11
|
form?: PreparedAcpForm;
|
|
12
|
-
host?: () => Promise<AcpJsonValue>;
|
|
12
|
+
host?: (observeCallback: (work: Promise<void>) => void) => Promise<AcpJsonValue>;
|
|
13
13
|
}>;
|
|
14
14
|
//# sourceMappingURL=elicitation.d.ts.map
|
|
@@ -14,7 +14,7 @@ function prepareAcpSessionElicitation(input) {
|
|
|
14
14
|
const invoke = input.host;
|
|
15
15
|
if (!invoke) throw new Error("This ACP elicitation requires an explicitly configured host UI.");
|
|
16
16
|
const signal = AbortSignal.any([input.context.signal, input.lifetime]);
|
|
17
|
-
return { host: () => invoke({ ...input.context, signal }) };
|
|
17
|
+
return { host: (observeCallback) => invoke({ ...input.context, signal }, observeCallback) };
|
|
18
18
|
} catch (error) {
|
|
19
19
|
throw new AcpRpcHandlerError(-32602, error instanceof Error ? error.message : "Unsupported ACP form.");
|
|
20
20
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AcpJsonObject } from '@agen-ai/acp-transport';
|
|
2
|
+
import type { DescendantIdentity } from '../descendants.js';
|
|
3
|
+
import { type SessionQueue } from '../queue.js';
|
|
4
|
+
import type { NativeTurn } from '../turn.js';
|
|
5
|
+
import type { PendingSessionRequest } from './pending.js';
|
|
6
|
+
/** Only the observed ordered child/tool callback and exact canceled attempt can withdraw a permission. */
|
|
7
|
+
export declare class GrokChildPermissionWithdrawals {
|
|
8
|
+
private readonly input;
|
|
9
|
+
private readonly children;
|
|
10
|
+
private readonly requests;
|
|
11
|
+
constructor(input: Readonly<{
|
|
12
|
+
reserveState: SessionQueue<unknown>['reserveState'];
|
|
13
|
+
currentTurn: () => NativeTurn | undefined;
|
|
14
|
+
timeoutMs: number;
|
|
15
|
+
withdraw: (request: PendingSessionRequest) => void;
|
|
16
|
+
fail: (error: Error) => void;
|
|
17
|
+
}>);
|
|
18
|
+
observe(identity: DescendantIdentity, update: AcpJsonObject): void;
|
|
19
|
+
attach(request: PendingSessionRequest): void;
|
|
20
|
+
finish(identity: DescendantIdentity, update: AcpJsonObject): void;
|
|
21
|
+
answerGate(request: PendingSessionRequest): Promise<void> | undefined;
|
|
22
|
+
settle(request: PendingSessionRequest): void;
|
|
23
|
+
private child;
|
|
24
|
+
private retire;
|
|
25
|
+
private stale;
|
|
26
|
+
stop(error: Error): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=grokWithdrawals.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { AcpProviderSessionError } from "../errors.js";
|
|
2
|
+
import { nativeIdentity, nativeObject } from "../native.js";
|
|
3
|
+
import { deferred } from "../queue.js";
|
|
4
|
+
const PERMISSION_STATE_OVERHEAD_BYTES = 256;
|
|
5
|
+
class GrokChildPermissionWithdrawals {
|
|
6
|
+
constructor(input) {
|
|
7
|
+
this.input = input;
|
|
8
|
+
}
|
|
9
|
+
children = /* @__PURE__ */ new Map();
|
|
10
|
+
requests = /* @__PURE__ */ new Map();
|
|
11
|
+
observe(identity, update) {
|
|
12
|
+
if (identity.attemptId === void 0 || update.sessionUpdate !== "pending_interaction" && update.sessionUpdate !== "interaction_resolved") return;
|
|
13
|
+
if (update.sessionUpdate === "pending_interaction" && update.kind !== "permission") return;
|
|
14
|
+
const toolId = nativeIdentity(update.tool_call_id, "child permission tool identity");
|
|
15
|
+
const child = this.child(identity);
|
|
16
|
+
const prior = child.interactions.get(toolId);
|
|
17
|
+
if (update.sessionUpdate === "pending_interaction") {
|
|
18
|
+
const turn2 = this.input.currentTurn();
|
|
19
|
+
if (child.finished || prior?.phase.kind === "retired" || !turn2 || turn2.nativeSettled || turn2.terminal || prior && prior.phase.turn !== turn2) throw this.stale();
|
|
20
|
+
if (!prior) child.interactions.set(toolId, {
|
|
21
|
+
phase: { kind: "announced", turn: turn2 },
|
|
22
|
+
release: this.input.reserveState({ childSessionId: identity.childSessionId, toolId }, PERMISSION_STATE_OVERHEAD_BYTES)
|
|
23
|
+
});
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!prior) {
|
|
27
|
+
child.interactions.set(toolId, {
|
|
28
|
+
phase: { kind: "retired" },
|
|
29
|
+
release: this.input.reserveState({ childSessionId: identity.childSessionId, toolId }, PERMISSION_STATE_OVERHEAD_BYTES)
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (prior.phase.kind === "announced") {
|
|
34
|
+
prior.phase = { kind: "retired" };
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (prior.phase.kind !== "attached") return;
|
|
38
|
+
const { turn, request } = prior.phase;
|
|
39
|
+
if (turn !== this.input.currentTurn() || turn.nativeSettled || turn.terminal) throw this.stale();
|
|
40
|
+
const hold = request.answered ? void 0 : { ready: deferred(), timer: setTimeout(
|
|
41
|
+
() => this.input.fail(
|
|
42
|
+
new AcpProviderSessionError("acp_native_permission_uncertain", "The resolved native child permission lacks exact cancellation corroboration.")
|
|
43
|
+
),
|
|
44
|
+
this.input.timeoutMs
|
|
45
|
+
) };
|
|
46
|
+
if (hold) void hold.ready.promise.catch(() => {
|
|
47
|
+
});
|
|
48
|
+
prior.phase = { kind: "resolved", turn, request, hold };
|
|
49
|
+
}
|
|
50
|
+
attach(request) {
|
|
51
|
+
if (request.kind !== "permission") return;
|
|
52
|
+
const tool = nativeObject(request.params.toolCall, "permission tool");
|
|
53
|
+
const meta = tool._meta === void 0 ? void 0 : nativeObject(tool._meta, "permission tool metadata");
|
|
54
|
+
if (meta?.["x.ai/requestingSessionId"] === void 0) return;
|
|
55
|
+
const childId = nativeIdentity(meta["x.ai/requestingSessionId"], "requesting child session identity");
|
|
56
|
+
if (childId === request.params.sessionId) return;
|
|
57
|
+
const child = this.children.get(childId);
|
|
58
|
+
if (!child) return;
|
|
59
|
+
const interaction = child.interactions.get(nativeIdentity(tool.toolCallId, "permission tool identity"));
|
|
60
|
+
if (child.finished || interaction && (interaction.phase.kind !== "announced" || interaction.phase.turn !== this.input.currentTurn())) throw this.stale();
|
|
61
|
+
if (!interaction || interaction.phase.kind !== "announced") return;
|
|
62
|
+
interaction.phase = { kind: "attached", turn: interaction.phase.turn, request };
|
|
63
|
+
this.requests.set(request, interaction);
|
|
64
|
+
}
|
|
65
|
+
finish(identity, update) {
|
|
66
|
+
if (identity.attemptId === void 0) return;
|
|
67
|
+
if (nativeIdentity(update.subagent_id, "finished subagent identity") !== identity.subagentId || nativeIdentity(update.attempt_id, "finished attempt identity") !== identity.attemptId) throw new AcpProviderSessionError(
|
|
68
|
+
"acp_native_permission_identity_conflict",
|
|
69
|
+
"The native child finish does not identify its exact announced attempt."
|
|
70
|
+
);
|
|
71
|
+
const child = this.child(identity);
|
|
72
|
+
if (child.finished) return;
|
|
73
|
+
child.finished = true;
|
|
74
|
+
for (const interaction of child.interactions.values()) {
|
|
75
|
+
const phase = interaction.phase;
|
|
76
|
+
if (phase.kind !== "attached" && phase.kind !== "resolved" || phase.turn !== this.input.currentTurn() || phase.turn.nativeSettled || phase.turn.terminal) continue;
|
|
77
|
+
if (phase.kind !== "resolved" || update.status !== "cancelled") {
|
|
78
|
+
if (!phase.request.answered) throw new AcpProviderSessionError(
|
|
79
|
+
"acp_native_permission_uncertain",
|
|
80
|
+
"The native child finish lacks the exact ordered permission cancellation facts."
|
|
81
|
+
);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
this.input.withdraw(phase.request);
|
|
85
|
+
this.retire(interaction);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
answerGate(request) {
|
|
89
|
+
const phase = this.requests.get(request)?.phase;
|
|
90
|
+
return phase?.kind === "resolved" ? phase.hold?.ready.promise : void 0;
|
|
91
|
+
}
|
|
92
|
+
settle(request) {
|
|
93
|
+
const interaction = this.requests.get(request);
|
|
94
|
+
if (interaction) this.retire(interaction);
|
|
95
|
+
this.requests.delete(request);
|
|
96
|
+
}
|
|
97
|
+
child(identity) {
|
|
98
|
+
let child = this.children.get(identity.childSessionId);
|
|
99
|
+
if (!child) {
|
|
100
|
+
child = { interactions: /* @__PURE__ */ new Map(), finished: false, release: this.input.reserveState({
|
|
101
|
+
childSessionId: identity.childSessionId,
|
|
102
|
+
subagentId: identity.subagentId,
|
|
103
|
+
attemptId: identity.attemptId
|
|
104
|
+
}) };
|
|
105
|
+
this.children.set(identity.childSessionId, child);
|
|
106
|
+
}
|
|
107
|
+
return child;
|
|
108
|
+
}
|
|
109
|
+
retire(interaction, error) {
|
|
110
|
+
const phase = interaction.phase;
|
|
111
|
+
if (phase.kind === "resolved" && phase.hold) {
|
|
112
|
+
clearTimeout(phase.hold.timer);
|
|
113
|
+
if (error) phase.hold.ready.reject(error);
|
|
114
|
+
else phase.hold.ready.resolve();
|
|
115
|
+
}
|
|
116
|
+
interaction.phase = { kind: "retired" };
|
|
117
|
+
}
|
|
118
|
+
stale() {
|
|
119
|
+
return new AcpProviderSessionError("acp_native_permission_stale", "The native child permission does not identify a fresh callback in its original turn.");
|
|
120
|
+
}
|
|
121
|
+
stop(error) {
|
|
122
|
+
for (const child of this.children.values()) {
|
|
123
|
+
for (const interaction of child.interactions.values()) {
|
|
124
|
+
this.retire(interaction, error);
|
|
125
|
+
interaction.release();
|
|
126
|
+
}
|
|
127
|
+
child.release();
|
|
128
|
+
}
|
|
129
|
+
this.children.clear();
|
|
130
|
+
this.requests.clear();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
GrokChildPermissionWithdrawals
|
|
135
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { AgentRequest } from '@agen-ai/agent-protocol';
|
|
1
|
+
import { type AcpJsonObject, type AcpJsonValue, type AcpReverseRequestContext } from '@agen-ai/acp-transport';
|
|
2
|
+
import type { AgentRequest, AgentRequestId } from '@agen-ai/agent-protocol';
|
|
3
3
|
import type { MappedPermission } from '../mapping.js';
|
|
4
4
|
import { deferred, type QueueEntry } from '../queue.js';
|
|
5
5
|
import type { AcpFormFlow, PreparedAcpForm } from './forms.js';
|
|
@@ -16,12 +16,16 @@ export interface PendingSessionRequest {
|
|
|
16
16
|
readonly kind: 'permission' | 'form';
|
|
17
17
|
readonly params: AcpJsonObject;
|
|
18
18
|
readonly form?: PreparedAcpForm;
|
|
19
|
-
readonly host?: () => Promise<AcpJsonValue>;
|
|
19
|
+
readonly host?: (observeCallback: (work: Promise<void>) => void) => Promise<AcpJsonValue>;
|
|
20
|
+
hostWork: Promise<void> | undefined;
|
|
20
21
|
readonly response: ReturnType<typeof deferred<AcpJsonValue>>;
|
|
21
22
|
readonly signal: AbortSignal;
|
|
23
|
+
readonly transportSettled: Promise<void>;
|
|
22
24
|
entry: QueueEntry<unknown> | undefined;
|
|
23
25
|
mapped: MappedSessionRequest | undefined;
|
|
24
26
|
answered: boolean;
|
|
27
|
+
publishedRequestId: AgentRequestId | undefined;
|
|
28
|
+
unavailable: boolean;
|
|
25
29
|
readonly onAbort: () => void;
|
|
26
30
|
}
|
|
27
31
|
export declare function canceledRequestReply(kind: PendingSessionRequest['kind']): AcpJsonValue;
|
|
@@ -29,14 +33,23 @@ export declare function canceledRequestReply(kind: PendingSessionRequest['kind']
|
|
|
29
33
|
export declare class PendingSessionRequests {
|
|
30
34
|
private readonly input;
|
|
31
35
|
private readonly pending;
|
|
36
|
+
private readonly retained;
|
|
37
|
+
private retainedBytes;
|
|
32
38
|
constructor(input: {
|
|
33
39
|
readonly maxPendingRequests: number;
|
|
40
|
+
readonly maxRequestMaterialBytes: number;
|
|
34
41
|
readonly admit: (request: PendingSessionRequest, bytes: number) => QueueEntry<unknown>;
|
|
35
42
|
readonly fail: (error: Error) => void;
|
|
43
|
+
readonly unavailable: (request: PendingSessionRequest, requestId: AgentRequestId) => void;
|
|
44
|
+
readonly settled: (request: PendingSessionRequest) => void;
|
|
45
|
+
readonly admitted?: (request: PendingSessionRequest) => void;
|
|
36
46
|
});
|
|
37
47
|
get size(): number;
|
|
38
|
-
admit(context: AcpReverseRequestContext, kind: PendingSessionRequest['kind'], params: AcpJsonObject, form?: PreparedAcpForm, host?:
|
|
48
|
+
admit(context: AcpReverseRequestContext, kind: PendingSessionRequest['kind'], params: AcpJsonObject, form?: PreparedAcpForm, host?: PendingSessionRequest['host']): Promise<AcpJsonValue>;
|
|
39
49
|
canAnswer(request: PendingSessionRequest): boolean;
|
|
50
|
+
withdraw(request: PendingSessionRequest, error: Error): void;
|
|
51
|
+
invokeHost(request: PendingSessionRequest): Promise<AcpJsonValue>;
|
|
52
|
+
join(): Promise<void>;
|
|
40
53
|
answer(request: PendingSessionRequest, result: AcpJsonValue): boolean;
|
|
41
54
|
reject(request: PendingSessionRequest, error: Error): void;
|
|
42
55
|
private release;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AcpRequestError } from "@agen-ai/acp-transport";
|
|
1
2
|
import { AcpProviderSessionError } from "../errors.js";
|
|
2
3
|
import { nativeBytes } from "../native.js";
|
|
3
4
|
import { deferred } from "../queue.js";
|
|
@@ -8,18 +9,25 @@ class PendingSessionRequests {
|
|
|
8
9
|
constructor(input) {
|
|
9
10
|
this.input = input;
|
|
10
11
|
}
|
|
11
|
-
input;
|
|
12
12
|
pending = /* @__PURE__ */ new Set();
|
|
13
|
+
retained = /* @__PURE__ */ new Set();
|
|
14
|
+
retainedBytes = 0;
|
|
13
15
|
get size() {
|
|
14
16
|
return this.pending.size;
|
|
15
17
|
}
|
|
16
18
|
admit(context, kind, params, form, host) {
|
|
17
|
-
if (this.
|
|
19
|
+
if (this.retained.size >= this.input.maxPendingRequests) {
|
|
18
20
|
const error = new AcpProviderSessionError("acp_request_queue_full", "The ACP pending request budget is exhausted.");
|
|
19
21
|
this.input.fail(error);
|
|
20
22
|
throw error;
|
|
21
23
|
}
|
|
22
24
|
const response = deferred();
|
|
25
|
+
const bytes = nativeBytes(params) * 2 + (form ? nativeBytes(form) : 0);
|
|
26
|
+
if (this.retainedBytes + bytes > this.input.maxRequestMaterialBytes) {
|
|
27
|
+
const error = new AcpProviderSessionError("acp_session_queue_full", "The ACP retained request material budget is exhausted.");
|
|
28
|
+
this.input.fail(error);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
23
31
|
const request = {
|
|
24
32
|
kind,
|
|
25
33
|
params,
|
|
@@ -27,18 +35,37 @@ class PendingSessionRequests {
|
|
|
27
35
|
...host === void 0 ? {} : { host },
|
|
28
36
|
response,
|
|
29
37
|
signal: context.signal,
|
|
38
|
+
transportSettled: context.settled,
|
|
30
39
|
entry: void 0,
|
|
31
40
|
mapped: void 0,
|
|
32
41
|
answered: false,
|
|
42
|
+
publishedRequestId: void 0,
|
|
43
|
+
unavailable: false,
|
|
44
|
+
hostWork: void 0,
|
|
33
45
|
onAbort: () => {
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
const cause = context.signal.reason;
|
|
47
|
+
if (cause instanceof AcpRequestError && cause.reasonCode === "acp_reverse_request_canceled" && cause.requestId === context.id) {
|
|
48
|
+
this.withdraw(request, cause);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.input.fail(cause instanceof Error ? cause : new AcpProviderSessionError("acp_request_revoked", "The native callback producer stopped."));
|
|
36
52
|
}
|
|
37
53
|
};
|
|
38
54
|
try {
|
|
39
|
-
request.entry = this.input.admit(request,
|
|
55
|
+
request.entry = this.input.admit(request, bytes);
|
|
40
56
|
this.pending.add(request);
|
|
57
|
+
this.retained.add(request);
|
|
58
|
+
this.retainedBytes += bytes;
|
|
59
|
+
void context.settled.then(async () => {
|
|
60
|
+
await request.hostWork;
|
|
61
|
+
context.signal.removeEventListener("abort", request.onAbort);
|
|
62
|
+
this.retained.delete(request);
|
|
63
|
+
this.retainedBytes -= bytes;
|
|
64
|
+
request.entry?.release();
|
|
65
|
+
this.input.settled(request);
|
|
66
|
+
});
|
|
41
67
|
context.signal.addEventListener("abort", request.onAbort, { once: true });
|
|
68
|
+
this.input.admitted?.(request);
|
|
42
69
|
if (context.signal.aborted) request.onAbort();
|
|
43
70
|
return response.promise;
|
|
44
71
|
} catch (error) {
|
|
@@ -47,7 +74,37 @@ class PendingSessionRequests {
|
|
|
47
74
|
}
|
|
48
75
|
}
|
|
49
76
|
canAnswer(request) {
|
|
50
|
-
return this.pending.has(request) && !request.answered && !request.signal.aborted;
|
|
77
|
+
return this.pending.has(request) && !request.answered && !request.unavailable && !request.signal.aborted;
|
|
78
|
+
}
|
|
79
|
+
withdraw(request, error) {
|
|
80
|
+
if (request.unavailable) return;
|
|
81
|
+
request.unavailable = true;
|
|
82
|
+
if (request.publishedRequestId !== void 0 && request.publishedRequestId === request.mapped?.request.requestId) {
|
|
83
|
+
this.input.unavailable(request, request.publishedRequestId);
|
|
84
|
+
}
|
|
85
|
+
if (!request.answered) this.reject(request, error);
|
|
86
|
+
}
|
|
87
|
+
invokeHost(request) {
|
|
88
|
+
if (!request.host) throw new TypeError("The native request has no host callback.");
|
|
89
|
+
const joined = deferred();
|
|
90
|
+
request.hostWork = joined.promise;
|
|
91
|
+
let callback = Promise.resolve();
|
|
92
|
+
let work;
|
|
93
|
+
try {
|
|
94
|
+
work = request.host((actual) => {
|
|
95
|
+
callback = actual;
|
|
96
|
+
});
|
|
97
|
+
} catch (cause) {
|
|
98
|
+
work = Promise.reject(cause);
|
|
99
|
+
}
|
|
100
|
+
void work.then(() => callback, () => callback).then(joined.resolve, () => joined.resolve());
|
|
101
|
+
return work;
|
|
102
|
+
}
|
|
103
|
+
async join() {
|
|
104
|
+
while (this.retained.size > 0) await Promise.all([...this.retained].map(async (request) => {
|
|
105
|
+
await request.transportSettled;
|
|
106
|
+
await request.hostWork;
|
|
107
|
+
}));
|
|
51
108
|
}
|
|
52
109
|
answer(request, result) {
|
|
53
110
|
if (!this.canAnswer(request) || !this.release(request)) return false;
|
|
@@ -61,9 +118,7 @@ class PendingSessionRequests {
|
|
|
61
118
|
release(request) {
|
|
62
119
|
if (!this.pending.has(request) || request.answered) return false;
|
|
63
120
|
request.answered = true;
|
|
64
|
-
request.signal.removeEventListener("abort", request.onAbort);
|
|
65
121
|
this.pending.delete(request);
|
|
66
|
-
request.entry?.release();
|
|
67
122
|
return true;
|
|
68
123
|
}
|
|
69
124
|
cancelAll() {
|
|
@@ -15,8 +15,10 @@ function resolveAcpRequest(requests, turn, request, mapped, input) {
|
|
|
15
15
|
request.mapped = { ...mapped, request: mapped.flow.request };
|
|
16
16
|
}
|
|
17
17
|
if (reply !== void 0 && !requests.answer(request, reply)) return requestUnavailable();
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
if (reply === void 0) {
|
|
19
|
+
turn.waiting = void 0;
|
|
20
|
+
turn.transition = { nextRequest: request };
|
|
21
|
+
} else turn.transition = {};
|
|
20
22
|
return { status: "delivered" };
|
|
21
23
|
}
|
|
22
24
|
function requestUnavailable() {
|