@agen-ai/provider-acp 0.4.1 → 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 +174 -14
- package/dist/capabilities.js +2 -2
- package/dist/connection/contracts.d.ts +3 -1
- package/dist/connection/initialize.js +25 -9
- package/dist/connection/negotiation.d.ts +2 -1
- package/dist/connection/negotiation.js +8 -1
- package/dist/host/interactions.d.ts +6 -1
- package/dist/host/interactions.js +42 -16
- package/dist/host/router.d.ts +3 -2
- package/dist/host/router.js +23 -20
- package/dist/instance.js +19 -9
- package/dist/node/contracts.d.ts +2 -2
- package/dist/node/index.js +1 -1
- package/dist/node/launch.js +3 -3
- package/dist/session/continuation.d.ts +33 -0
- package/dist/session/continuation.js +185 -0
- package/dist/session/descendants.d.ts +29 -0
- package/dist/session/descendants.js +97 -0
- package/dist/session/mapping.js +1 -1
- package/dist/session/native.d.ts +1 -0
- package/dist/session/native.js +8 -0
- package/dist/session/opening.js +1 -1
- package/dist/session/requests/elicitation.d.ts +1 -1
- package/dist/session/requests/elicitation.js +1 -1
- 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 -7
- package/dist/session/requests/resolution.js +4 -2
- package/dist/session/session.d.ts +8 -0
- package/dist/session/session.js +140 -32
- package/dist/session/turn.d.ts +6 -1
- package/dist/sessionInteractions/broker.d.ts +11 -3
- package/dist/sessionInteractions/broker.js +35 -13
- package/dist/sessionInteractions/extensions.d.ts +2 -1
- package/dist/sessionInteractions/extensions.js +21 -1
- package/dist/sessionInteractions/grokFolderTrust.d.ts +0 -1
- package/dist/sessionInteractions/grokFolderTrust.js +0 -1
- package/dist/sessionInteractions/grokPlanReview.d.ts +16 -0
- package/dist/sessionInteractions/grokPlanReview.js +63 -0
- package/dist/sessionInteractions/request.d.ts +15 -7
- package/dist/sessionInteractions/request.js +46 -18
- package/package.json +4 -4
package/dist/host/router.js
CHANGED
|
@@ -15,7 +15,7 @@ class AcpHostRouter {
|
|
|
15
15
|
terminals;
|
|
16
16
|
requests = /* @__PURE__ */ new Map();
|
|
17
17
|
binding;
|
|
18
|
-
pending =
|
|
18
|
+
pending = /* @__PURE__ */ new Map();
|
|
19
19
|
bytes = 0;
|
|
20
20
|
bindSession(binding) {
|
|
21
21
|
if (this.binding && (binding.nativeSessionId !== this.binding.nativeSessionId || binding.sessionId !== this.binding.sessionId || binding.workingDirectory !== this.binding.workingDirectory)) {
|
|
@@ -54,32 +54,27 @@ class AcpHostRouter {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
admit(context) {
|
|
57
|
+
const params = hostObject(context.params);
|
|
58
|
+
if (context.method === "session/request_permission" || context.method === "elicitation/create" && Object.hasOwn(params, "sessionId")) return this.route(context, params);
|
|
57
59
|
const bytes = Buffer.byteLength(JSON.stringify(context.params), "utf8");
|
|
58
|
-
if (this.pending >= this.input.limits.maxPendingRequests || this.bytes + bytes > this.input.limits.maxQueuedBytes) {
|
|
60
|
+
if (this.pending.size >= this.input.limits.maxPendingRequests || this.bytes + bytes > this.input.limits.maxQueuedBytes) {
|
|
59
61
|
throw new AcpRpcHandlerError(-32603, "The host request capacity was exceeded.");
|
|
60
62
|
}
|
|
61
|
-
this.pending += 1;
|
|
62
63
|
this.bytes += bytes;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
result = this.route(context);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
this.pending -= 1;
|
|
64
|
+
const retirement = context.settled.then(() => this.interactions.joinRequest(context.settled)).then(() => {
|
|
65
|
+
this.pending.delete(context.settled);
|
|
68
66
|
this.bytes -= bytes;
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
});
|
|
68
|
+
this.pending.set(context.settled, retirement);
|
|
69
|
+
const result = this.route(context, params);
|
|
71
70
|
return result.then((value) => {
|
|
72
71
|
if (Buffer.byteLength(JSON.stringify(value), "utf8") > Math.min(this.input.limits.maxQueuedBytes, 8 * 1024 * 1024) - 512) {
|
|
73
72
|
throw invalidHostRequest();
|
|
74
73
|
}
|
|
75
74
|
return value;
|
|
76
|
-
}).finally(() => {
|
|
77
|
-
this.pending -= 1;
|
|
78
|
-
this.bytes -= bytes;
|
|
79
75
|
});
|
|
80
76
|
}
|
|
81
|
-
route(context) {
|
|
82
|
-
const params = hostObject(context.params);
|
|
77
|
+
route(context, params) {
|
|
83
78
|
if (context.method === "elicitation/create") {
|
|
84
79
|
const session2 = Object.hasOwn(params, "sessionId");
|
|
85
80
|
const request = Object.hasOwn(params, "requestId");
|
|
@@ -143,20 +138,28 @@ class AcpHostRouter {
|
|
|
143
138
|
const id = hostText(hostObject(value).elicitationId, 256);
|
|
144
139
|
return this.interactions.complete(id);
|
|
145
140
|
}
|
|
146
|
-
sessionElicitation(context) {
|
|
141
|
+
sessionElicitation(context, observeCallback) {
|
|
147
142
|
const bound = this.requireSession(hostObject(context.params), context.signal);
|
|
148
143
|
return this.interactions.request(context, {
|
|
149
144
|
kind: "session",
|
|
150
145
|
sessionId: bound.sessionId,
|
|
151
146
|
workingDirectory: bound.workingDirectory
|
|
152
|
-
}, context.signal);
|
|
147
|
+
}, context.signal, observeCallback);
|
|
153
148
|
}
|
|
154
149
|
async close() {
|
|
155
150
|
for (const request of this.requests.values()) request.abort();
|
|
156
151
|
this.requests.clear();
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
152
|
+
const deadline = hostDeadline(new AbortController().signal, this.input.limits.hostOperationTimeoutMs);
|
|
153
|
+
const join = async () => {
|
|
154
|
+
while (this.pending.size > 0) await Promise.all(this.pending.values());
|
|
155
|
+
};
|
|
156
|
+
try {
|
|
157
|
+
const results = await Promise.allSettled([this.interactions.close(), this.terminals.close(), awaitHost(join(), deadline.signal)]);
|
|
158
|
+
const failures = results.filter((result) => result.status === "rejected");
|
|
159
|
+
if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "ACP host facility cleanup failed.");
|
|
160
|
+
} finally {
|
|
161
|
+
deadline.close();
|
|
162
|
+
}
|
|
160
163
|
}
|
|
161
164
|
}
|
|
162
165
|
export {
|
package/dist/instance.js
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
createAgentProviderSessionCleanupError,
|
|
8
8
|
isAgentProviderSessionCleanupErrorFor,
|
|
9
9
|
throwIfAgentOperationAborted,
|
|
10
|
-
validateAgentProviderAdapter,
|
|
11
10
|
AgentProviderContractError,
|
|
12
11
|
DEFAULT_AGENT_SESSION_INTERACTION_LIMITS
|
|
13
12
|
} from "@agen-ai/agent-runtime";
|
|
@@ -134,12 +133,12 @@ async function createAcpProviderInstance(input) {
|
|
|
134
133
|
const open = async (context, resume) => {
|
|
135
134
|
throwIfAgentOperationAborted(context.signal);
|
|
136
135
|
throwIfAgentOperationAborted(lifetime.signal);
|
|
137
|
-
if (input.sessionInteractions && resume) throw new AgentProviderContractError(
|
|
136
|
+
if (input.sessionInteractions?.mapping === "grok-folder-trust-v1" && resume) throw new AgentProviderContractError(
|
|
138
137
|
input.providerKey,
|
|
139
138
|
"session_interactions_unsupported",
|
|
140
139
|
"The selected ACP session interaction mapping supports creation only."
|
|
141
140
|
);
|
|
142
|
-
if (input.sessionInteractions && context.interactions === void 0) throw new AgentProviderContractError(
|
|
141
|
+
if (input.sessionInteractions && (input.sessionInteractions.mapping === "grok-folder-trust-v1" || resume) && context.interactions === void 0) throw new AgentProviderContractError(
|
|
143
142
|
input.providerKey,
|
|
144
143
|
"invalid_session_interaction",
|
|
145
144
|
"The selected ACP session interaction mapping requires a handler before opening."
|
|
@@ -201,7 +200,7 @@ async function createAcpProviderInstance(input) {
|
|
|
201
200
|
openingInput: context,
|
|
202
201
|
...parsed.operation === "resume" ? { resume: {
|
|
203
202
|
binding: parsed.binding,
|
|
204
|
-
method: sessionCapabilities?.resume != null ? "session/resume" : "session/load"
|
|
203
|
+
method: input.sessionInteractions?.mapping === "grok-build-1.0.60-session-requests-v1" ? "session/load" : sessionCapabilities?.resume != null ? "session/resume" : "session/load"
|
|
205
204
|
} } : {},
|
|
206
205
|
mcpServers,
|
|
207
206
|
connection,
|
|
@@ -216,7 +215,7 @@ async function createAcpProviderInstance(input) {
|
|
|
216
215
|
closeResources
|
|
217
216
|
});
|
|
218
217
|
throwIfAgentOperationAborted(signal);
|
|
219
|
-
connection.sessionInteractions?.adopt();
|
|
218
|
+
if (context.interactions) connection.sessionInteractions?.adopt();
|
|
220
219
|
sessions.set(parsed.sessionId, opened.session);
|
|
221
220
|
return opened.session;
|
|
222
221
|
} catch (cause) {
|
|
@@ -254,10 +253,21 @@ async function createAcpProviderInstance(input) {
|
|
|
254
253
|
const adapter = Object.freeze({
|
|
255
254
|
...input.sessionInteractions ? { sessionInteractions: Object.freeze({
|
|
256
255
|
kind: "supported",
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
256
|
+
mappings: input.sessionInteractions.mapping === "grok-folder-trust-v1" ? Object.freeze([Object.freeze({ openingOperation: "create", requestKind: "decision" })]) : Object.freeze([Object.freeze({ openingOperation: "resume", requestKind: "plan_review" })]),
|
|
257
|
+
producerOwnership: "exclusive",
|
|
258
|
+
humanWait: Object.freeze(["bounded", "until_withdrawn_or_closed"])
|
|
260
259
|
}) } : {},
|
|
260
|
+
...input.sessionInteractions?.mapping === "grok-build-1.0.60-session-requests-v1" ? {
|
|
261
|
+
nativeContinuations: Object.freeze({
|
|
262
|
+
kind: "supported",
|
|
263
|
+
producerOwnership: "exclusive",
|
|
264
|
+
mappings: Object.freeze([Object.freeze({
|
|
265
|
+
openingOperation: "resume",
|
|
266
|
+
requestKind: "plan_review",
|
|
267
|
+
timing: "after_open_return"
|
|
268
|
+
})])
|
|
269
|
+
})
|
|
270
|
+
} : {},
|
|
261
271
|
hostBinding: bindingSupported ? Object.freeze({ kind: "supported", toolTransport: "mcp", instructionPlacements: Object.freeze(["tool_context"]) }) : Object.freeze({ kind: "unsupported" }),
|
|
262
272
|
createSession: (context) => open(context, false),
|
|
263
273
|
resumption: capabilities.sessions.resume ? Object.freeze({ kind: "supported", resumeSession: (context) => open(context, true) }) : Object.freeze({ kind: "unsupported" }),
|
|
@@ -267,7 +277,7 @@ async function createAcpProviderInstance(input) {
|
|
|
267
277
|
return Object.freeze({
|
|
268
278
|
instanceId: input.instanceId,
|
|
269
279
|
capabilities,
|
|
270
|
-
adapter
|
|
280
|
+
adapter,
|
|
271
281
|
accountQuota: { kind: "unsupported" },
|
|
272
282
|
environment: { kind: "unsupported" },
|
|
273
283
|
checkReadiness(check = {}) {
|
package/dist/node/contracts.d.ts
CHANGED
|
@@ -12,9 +12,9 @@ export interface NodeAcpLaunchOptions {
|
|
|
12
12
|
export interface CreateNodeAcpProviderDriverOptions extends Omit<CreateAcpProviderDriverOptions, 'createConnection' | 'limits'> {
|
|
13
13
|
readonly launch: NodeAcpLaunchOptions;
|
|
14
14
|
readonly executionBoundary?: NodeAcpExecutionBoundary;
|
|
15
|
-
/** Requires a
|
|
15
|
+
/** Requires a direct native executable, exact no-leader arguments and a handler for each selected opening tuple. */
|
|
16
16
|
readonly sessionInteractions?: Readonly<{
|
|
17
|
-
readonly mapping: 'grok-folder-trust-v1';
|
|
17
|
+
readonly mapping: 'grok-folder-trust-v1' | 'grok-build-1.0.60-session-requests-v1';
|
|
18
18
|
}>;
|
|
19
19
|
readonly limits?: Partial<AcpProviderLimits>;
|
|
20
20
|
}
|
package/dist/node/index.js
CHANGED
|
@@ -9,7 +9,7 @@ function createNodeAcpProviderDriver(options) {
|
|
|
9
9
|
if (boundary === void 0 && Object.hasOwn(options, "executionBoundary")) throw new TypeError("An explicitly supplied execution boundary must be valid.");
|
|
10
10
|
const reserve = boundary === void 0 ? void 0 : executionReservationSource(boundary, launch);
|
|
11
11
|
const sessionInteractions = resolveNodeAcpSessionInteractions(options, launch);
|
|
12
|
-
if (sessionInteractions && !reserve) throw new TypeError("
|
|
12
|
+
if (sessionInteractions && !reserve) throw new TypeError("Selected Grok session mappings require an exclusive descendant-owning execution boundary.");
|
|
13
13
|
return createAcpProviderDriverInternal({
|
|
14
14
|
providerKey: options.providerKey,
|
|
15
15
|
connectionSource: reserve ? { kind: "reserved", reserve } : { kind: "factory", create: (context) => spawnAcpProcess(launch, context) },
|
package/dist/node/launch.js
CHANGED
|
@@ -34,13 +34,13 @@ function resolveNodeAcpLaunch(launch) {
|
|
|
34
34
|
function resolveNodeAcpSessionInteractions(options, launch) {
|
|
35
35
|
const selection = options.sessionInteractions;
|
|
36
36
|
if (selection === void 0 && !Object.hasOwn(options, "sessionInteractions")) return void 0;
|
|
37
|
-
if (!selection || typeof selection !== "object" || Array.isArray(selection) || Reflect.ownKeys(selection).length !== 1 || !Object.hasOwn(selection, "mapping") ||
|
|
37
|
+
if (!selection || typeof selection !== "object" || Array.isArray(selection) || Reflect.ownKeys(selection).length !== 1 || !Object.hasOwn(selection, "mapping") || !["grok-folder-trust-v1", "grok-build-1.0.60-session-requests-v1"].includes(selection.mapping)) {
|
|
38
38
|
throw new TypeError("ACP session interaction selection must name the supported mapping without additional fields.");
|
|
39
39
|
}
|
|
40
40
|
if (launch.args.length !== 3 || launch.args[0] !== "agent" || launch.args[1] !== "--no-leader" || launch.args[2] !== "stdio") {
|
|
41
|
-
throw new TypeError("
|
|
41
|
+
throw new TypeError("Selected Grok session mappings require exact direct arguments: agent --no-leader stdio.");
|
|
42
42
|
}
|
|
43
|
-
return Object.freeze({ mapping:
|
|
43
|
+
return Object.freeze({ mapping: selection.mapping });
|
|
44
44
|
}
|
|
45
45
|
export {
|
|
46
46
|
resolveNodeAcpLaunch,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AcpJsonObject, AcpNotificationContext } from '@agen-ai/acp-transport';
|
|
2
|
+
import type { AgentTurnCompletedPayload, AgentTurnId } from '@agen-ai/agent-protocol';
|
|
3
|
+
import { type AgentProviderOutput, type AgentProviderTurnExecution } from '@agen-ai/agent-runtime';
|
|
4
|
+
import type { PreparePlanContinuation } from '../sessionInteractions/request.js';
|
|
5
|
+
import { type SessionQueue } from './queue.js';
|
|
6
|
+
import type { NativeTurn, SessionMessage } from './turn.js';
|
|
7
|
+
/** The selected restored-plan continuation shares the session's turn and output owners. */
|
|
8
|
+
export declare class GrokPlanContinuations {
|
|
9
|
+
private readonly input;
|
|
10
|
+
private readonly historicalPrompts;
|
|
11
|
+
private readonly completedPrompts;
|
|
12
|
+
private continuation;
|
|
13
|
+
constructor(input: Readonly<{
|
|
14
|
+
queue: SessionQueue<SessionMessage>;
|
|
15
|
+
selected: boolean;
|
|
16
|
+
requireUsable: () => void;
|
|
17
|
+
assertNativeSession: (params: AcpJsonObject) => void;
|
|
18
|
+
createNativeTurn: (turnId: AgentTurnId) => NativeTurn;
|
|
19
|
+
execution: (turn: NativeTurn, stream: () => AsyncGenerator<AgentProviderOutput>) => AgentProviderTurnExecution;
|
|
20
|
+
stream: (turn: NativeTurn, ready: Promise<boolean>) => AsyncGenerator<AgentProviderOutput>;
|
|
21
|
+
completeNativeTurn: (turn: NativeTurn, payload: AgentTurnCompletedPayload) => void;
|
|
22
|
+
close: () => Promise<void>;
|
|
23
|
+
}>);
|
|
24
|
+
private replayed;
|
|
25
|
+
private promptIds;
|
|
26
|
+
private rememberPrompts;
|
|
27
|
+
admitOutput(params: AcpJsonObject, update: AcpJsonObject, historical: boolean): boolean;
|
|
28
|
+
admitNotification(context: AcpNotificationContext): void;
|
|
29
|
+
prepareResponse(input: Parameters<PreparePlanContinuation>[0]): ReturnType<PreparePlanContinuation>;
|
|
30
|
+
complete(turn: NativeTurn): void;
|
|
31
|
+
stop(error: Error): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=continuation.d.ts.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
throwIfAgentOperationAborted
|
|
3
|
+
} from "@agen-ai/agent-runtime";
|
|
4
|
+
import { GROK_QUEUE_METHOD } from "../sessionInteractions/grokPlanReview.js";
|
|
5
|
+
import { AcpProviderSessionError } from "./errors.js";
|
|
6
|
+
import { acpTurnCompletion } from "./completion.js";
|
|
7
|
+
import { nativeObject, nativeString, SESSION_METADATA_UPDATES } from "./native.js";
|
|
8
|
+
import { deferred } from "./queue.js";
|
|
9
|
+
class GrokPlanContinuations {
|
|
10
|
+
constructor(input) {
|
|
11
|
+
this.input = input;
|
|
12
|
+
}
|
|
13
|
+
historicalPrompts = /* @__PURE__ */ new Map();
|
|
14
|
+
completedPrompts = /* @__PURE__ */ new Set();
|
|
15
|
+
continuation;
|
|
16
|
+
replayed(meta) {
|
|
17
|
+
if (meta?.isReplay !== void 0 && typeof meta.isReplay !== "boolean") throw new AcpProviderSessionError(
|
|
18
|
+
"acp_continuation_replay_invalid",
|
|
19
|
+
"The selected native replay marker must be a boolean."
|
|
20
|
+
);
|
|
21
|
+
return meta?.isReplay === true;
|
|
22
|
+
}
|
|
23
|
+
promptIds(values) {
|
|
24
|
+
return values.flatMap((value) => {
|
|
25
|
+
if (value === void 0 || value === null) return [];
|
|
26
|
+
const id = nativeString(value, "prompt identity");
|
|
27
|
+
if (!id || Buffer.byteLength(id, "utf8") > 256) throw new AcpProviderSessionError(
|
|
28
|
+
"acp_continuation_identity_invalid",
|
|
29
|
+
"Native prompt identity exceeds its bound."
|
|
30
|
+
);
|
|
31
|
+
return [id];
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
rememberPrompts(ids) {
|
|
35
|
+
for (const id of ids) if (!this.historicalPrompts.has(id)) this.historicalPrompts.set(id, this.input.queue.reserveState(id));
|
|
36
|
+
}
|
|
37
|
+
admitOutput(params, update, historical) {
|
|
38
|
+
if (!this.input.selected) return true;
|
|
39
|
+
const meta = params._meta === void 0 ? void 0 : nativeObject(params._meta, "continuation metadata");
|
|
40
|
+
const replay = this.replayed(meta);
|
|
41
|
+
const ids = this.promptIds([meta?.promptId, update.prompt_id]);
|
|
42
|
+
if (historical || replay || !this.continuation?.state.dispatched) this.rememberPrompts(ids);
|
|
43
|
+
if (historical || replay) return false;
|
|
44
|
+
if (SESSION_METADATA_UPDATES.has(nativeString(update.sessionUpdate, "session update kind"))) return true;
|
|
45
|
+
if (this.continuation && !this.continuation.turn.terminal) {
|
|
46
|
+
if (!this.continuation.state.promptId || meta?.promptId !== this.continuation.state.promptId) {
|
|
47
|
+
throw new AcpProviderSessionError("acp_continuation_output_mismatch", "Native continuation output requires its exact promoted prompt identity.");
|
|
48
|
+
}
|
|
49
|
+
this.continuation.state.outputObserved = true;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
admitNotification(context) {
|
|
54
|
+
this.input.requireUsable();
|
|
55
|
+
const params = nativeObject(context.params, "continuation notification");
|
|
56
|
+
this.input.assertNativeSession(params);
|
|
57
|
+
const meta = params._meta === void 0 ? void 0 : nativeObject(params._meta, "continuation metadata");
|
|
58
|
+
const replay = this.replayed(meta);
|
|
59
|
+
const update = context.method === GROK_QUEUE_METHOD ? void 0 : nativeObject(params.update, "native session update");
|
|
60
|
+
const ids = this.promptIds([meta?.promptId, update?.prompt_id, ...context.method === GROK_QUEUE_METHOD ? [params.runningPromptId, ...Array.isArray(params.entries) ? params.entries.map((entry) => nativeObject(entry, "queue entry").id) : []] : []]);
|
|
61
|
+
if (replay || !this.continuation?.state.dispatched) this.rememberPrompts(ids);
|
|
62
|
+
if (replay) return;
|
|
63
|
+
if (context.method === GROK_QUEUE_METHOD) {
|
|
64
|
+
const continuation2 = this.continuation;
|
|
65
|
+
if (!continuation2?.state.dispatched || params.runningPromptId == null) return;
|
|
66
|
+
const id = nativeString(params.runningPromptId, "promoted prompt identity");
|
|
67
|
+
if (params.runningKind !== "prompt" || !id.startsWith("plan-resume-") || this.historicalPrompts.has(id) && continuation2.state.promptId !== id || meta?.promptId != null && meta.promptId !== id || ids.some((value) => value !== id && !this.historicalPrompts.has(value)) || continuation2.state.promptId !== void 0 && continuation2.state.promptId !== id) {
|
|
68
|
+
throw new AcpProviderSessionError("acp_continuation_promotion_invalid", "The native continuation promotion does not identify its fresh owned prompt.");
|
|
69
|
+
}
|
|
70
|
+
if (!continuation2.state.promptId) {
|
|
71
|
+
continuation2.state.promptId = id;
|
|
72
|
+
continuation2.turn.nativeStarted = true;
|
|
73
|
+
continuation2.outcome.resolve(Object.freeze({ kind: "started" }));
|
|
74
|
+
continuation2.ready.resolve(true);
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (update?.sessionUpdate !== "turn_completed") return;
|
|
79
|
+
const continuation = this.continuation;
|
|
80
|
+
if (!continuation?.state.dispatched) {
|
|
81
|
+
if (typeof update.prompt_id === "string" && this.completedPrompts.has(update.prompt_id)) throw new AcpProviderSessionError(
|
|
82
|
+
"acp_continuation_terminal_invalid",
|
|
83
|
+
"The native continuation repeated an already completed terminal."
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (update.prompt_id !== continuation.state.promptId) {
|
|
88
|
+
if (typeof update.prompt_id === "string" && this.historicalPrompts.has(update.prompt_id)) return;
|
|
89
|
+
throw new AcpProviderSessionError("acp_continuation_terminal_mismatch", "The terminal does not identify the owned promoted prompt.");
|
|
90
|
+
}
|
|
91
|
+
if (!continuation.state.promptId || continuation.turn.nativeSettled || update.stop_reason !== "end_turn" && update.stop_reason !== "cancelled" || update.stop_reason === "end_turn" && !continuation.state.outputObserved || update.error != null || params.error != null) throw new AcpProviderSessionError(
|
|
92
|
+
"acp_continuation_terminal_invalid",
|
|
93
|
+
"The native continuation did not produce its exact terminal."
|
|
94
|
+
);
|
|
95
|
+
this.input.completeNativeTurn(continuation.turn, acpTurnCompletion({
|
|
96
|
+
kind: "result",
|
|
97
|
+
result: { stopReason: update.stop_reason }
|
|
98
|
+
}, continuation.turn.cancelReason));
|
|
99
|
+
}
|
|
100
|
+
prepareResponse(input) {
|
|
101
|
+
this.input.requireUsable();
|
|
102
|
+
throwIfAgentOperationAborted(input.signal);
|
|
103
|
+
let canceled = false;
|
|
104
|
+
let dispatched = false;
|
|
105
|
+
let owned;
|
|
106
|
+
const outcome = deferred();
|
|
107
|
+
const ready = deferred();
|
|
108
|
+
void outcome.promise.catch(() => {
|
|
109
|
+
});
|
|
110
|
+
void ready.promise.catch(() => {
|
|
111
|
+
});
|
|
112
|
+
const cancel = async () => {
|
|
113
|
+
if (canceled) return;
|
|
114
|
+
canceled = true;
|
|
115
|
+
if (dispatched) {
|
|
116
|
+
await this.input.close();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
outcome.resolve(Object.freeze({ kind: "not_started" }));
|
|
120
|
+
ready.resolve(false);
|
|
121
|
+
if (owned) {
|
|
122
|
+
owned.turn.terminal = true;
|
|
123
|
+
owned.turn.sourceSettled = true;
|
|
124
|
+
owned.turn.mapping.release();
|
|
125
|
+
owned.outcome.resolve(Object.freeze({ kind: "not_started" }));
|
|
126
|
+
owned.ready.resolve(false);
|
|
127
|
+
this.input.queue.wakeReader();
|
|
128
|
+
if (this.continuation === owned) this.continuation = void 0;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const dispatch = async () => {
|
|
132
|
+
throwIfAgentOperationAborted(input.signal);
|
|
133
|
+
if (canceled || dispatched) throw new AcpProviderSessionError("acp_continuation_dispatch_invalid", "A prepared response can enter only once.");
|
|
134
|
+
if (input.response.action !== "abandon" && !owned) throw new AcpProviderSessionError("acp_continuation_owner_missing", "The native continuation requires its prepared reader.");
|
|
135
|
+
dispatched = true;
|
|
136
|
+
if (owned) owned.state.dispatched = true;
|
|
137
|
+
const result = await input.dispatch();
|
|
138
|
+
if (result.status !== "delivered" && owned) {
|
|
139
|
+
owned.turn.terminal = true;
|
|
140
|
+
owned.turn.mapping.release();
|
|
141
|
+
owned.outcome.resolve(Object.freeze({ kind: "not_started" }));
|
|
142
|
+
owned.ready.resolve(false);
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
};
|
|
146
|
+
if (input.response.action === "abandon") return Object.freeze({ kind: "no_continuation", dispatch, cancel });
|
|
147
|
+
return Object.freeze({
|
|
148
|
+
kind: "native_continuation",
|
|
149
|
+
outcome: outcome.promise,
|
|
150
|
+
dispatch,
|
|
151
|
+
cancel,
|
|
152
|
+
createExecution: ({ turnId, signal }) => {
|
|
153
|
+
if (owned || canceled || dispatched) throw new AcpProviderSessionError("acp_continuation_owner_invalid", "The prepared execution can be acquired only once.");
|
|
154
|
+
throwIfAgentOperationAborted(signal);
|
|
155
|
+
const turn = this.input.createNativeTurn(turnId);
|
|
156
|
+
owned = { turn, outcome, ready, state: { dispatched: false, outputObserved: false } };
|
|
157
|
+
this.continuation = owned;
|
|
158
|
+
const owner = this.input;
|
|
159
|
+
return owner.execution(turn, async function* () {
|
|
160
|
+
yield* owner.stream(turn, ready.promise);
|
|
161
|
+
if (dispatched) await input.nativeSettled;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
complete(turn) {
|
|
167
|
+
if (this.continuation?.turn !== turn) return;
|
|
168
|
+
const promptId = this.continuation.state.promptId;
|
|
169
|
+
if (promptId !== void 0) {
|
|
170
|
+
this.rememberPrompts([promptId]);
|
|
171
|
+
this.completedPrompts.add(promptId);
|
|
172
|
+
}
|
|
173
|
+
this.continuation = void 0;
|
|
174
|
+
}
|
|
175
|
+
stop(error) {
|
|
176
|
+
this.continuation?.ready.reject(error);
|
|
177
|
+
this.continuation?.outcome.reject(error);
|
|
178
|
+
for (const release of this.historicalPrompts.values()) release();
|
|
179
|
+
this.historicalPrompts.clear();
|
|
180
|
+
this.completedPrompts.clear();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export {
|
|
184
|
+
GrokPlanContinuations
|
|
185
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AcpJsonObject, AcpNotificationContext } from '@agen-ai/acp-transport';
|
|
2
|
+
import type { SessionQueue } from './queue.js';
|
|
3
|
+
import type { GrokChildPermissionWithdrawals } from './requests/grokWithdrawals.js';
|
|
4
|
+
export interface DescendantIdentity {
|
|
5
|
+
readonly parentSessionId: string;
|
|
6
|
+
readonly childSessionId: string;
|
|
7
|
+
readonly subagentId: string;
|
|
8
|
+
readonly attemptId?: string;
|
|
9
|
+
readonly parentPromptId?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Selected native descendants are bounded observations, with no parent output or host authority. */
|
|
12
|
+
export declare class GrokDescendantObservations {
|
|
13
|
+
private readonly input;
|
|
14
|
+
private readonly bySession;
|
|
15
|
+
private readonly bySubagent;
|
|
16
|
+
constructor(input: Readonly<{
|
|
17
|
+
reserveState: SessionQueue<unknown>['reserveState'];
|
|
18
|
+
rootSessionId: () => string | undefined;
|
|
19
|
+
activeTurn: () => boolean;
|
|
20
|
+
observeHistory: (params: AcpJsonObject) => void;
|
|
21
|
+
withdrawals: GrokChildPermissionWithdrawals;
|
|
22
|
+
}>);
|
|
23
|
+
/** Returns true only when this notification is wholly owned by the observation scope. */
|
|
24
|
+
admit(context: AcpNotificationContext, historical: boolean): boolean;
|
|
25
|
+
private spawn;
|
|
26
|
+
private validateQueue;
|
|
27
|
+
stop(): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=descendants.d.ts.map
|
|
@@ -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/mapping.js
CHANGED
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
|
};
|
package/dist/session/opening.js
CHANGED
|
@@ -34,7 +34,7 @@ async function openAcpNativeSession(input, hooks) {
|
|
|
34
34
|
const client = acpSessionControlClient(input);
|
|
35
35
|
const operations = createAcpStandardSessionOperations(client);
|
|
36
36
|
const resumed = input.resume;
|
|
37
|
-
const sessionInteractions = input.connection.sessionInteractions;
|
|
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";
|
|
@@ -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
|