@agen-ai/provider-acp 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +174 -14
  2. package/dist/capabilities.js +2 -2
  3. package/dist/connection/contracts.d.ts +3 -1
  4. package/dist/connection/initialize.js +25 -9
  5. package/dist/connection/negotiation.d.ts +2 -1
  6. package/dist/connection/negotiation.js +8 -1
  7. package/dist/host/interactions.d.ts +6 -1
  8. package/dist/host/interactions.js +42 -16
  9. package/dist/host/router.d.ts +3 -2
  10. package/dist/host/router.js +23 -20
  11. package/dist/instance.js +19 -9
  12. package/dist/node/contracts.d.ts +2 -2
  13. package/dist/node/index.js +1 -1
  14. package/dist/node/launch.js +3 -3
  15. package/dist/session/continuation.d.ts +33 -0
  16. package/dist/session/continuation.js +185 -0
  17. package/dist/session/descendants.d.ts +29 -0
  18. package/dist/session/descendants.js +97 -0
  19. package/dist/session/mapping.js +1 -1
  20. package/dist/session/native.d.ts +1 -0
  21. package/dist/session/native.js +8 -0
  22. package/dist/session/opening.js +1 -1
  23. package/dist/session/requests/elicitation.d.ts +1 -1
  24. package/dist/session/requests/elicitation.js +1 -1
  25. package/dist/session/requests/grokWithdrawals.d.ts +28 -0
  26. package/dist/session/requests/grokWithdrawals.js +135 -0
  27. package/dist/session/requests/pending.d.ts +17 -4
  28. package/dist/session/requests/pending.js +63 -7
  29. package/dist/session/requests/resolution.js +4 -2
  30. package/dist/session/session.d.ts +8 -0
  31. package/dist/session/session.js +140 -32
  32. package/dist/session/turn.d.ts +6 -1
  33. package/dist/sessionInteractions/broker.d.ts +11 -3
  34. package/dist/sessionInteractions/broker.js +35 -13
  35. package/dist/sessionInteractions/extensions.d.ts +2 -1
  36. package/dist/sessionInteractions/extensions.js +21 -1
  37. package/dist/sessionInteractions/grokFolderTrust.d.ts +0 -1
  38. package/dist/sessionInteractions/grokFolderTrust.js +0 -1
  39. package/dist/sessionInteractions/grokPlanReview.d.ts +16 -0
  40. package/dist/sessionInteractions/grokPlanReview.js +63 -0
  41. package/dist/sessionInteractions/request.d.ts +15 -7
  42. package/dist/sessionInteractions/request.js +46 -18
  43. package/package.json +4 -4
@@ -15,7 +15,7 @@ class AcpHostRouter {
15
15
  terminals;
16
16
  requests = /* @__PURE__ */ new Map();
17
17
  binding;
18
- pending = 0;
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
- let result;
64
- try {
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
- throw error;
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 results = await Promise.allSettled([this.interactions.close(), this.terminals.close()]);
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.");
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
- openingOperations: Object.freeze(["create"]),
258
- requestKinds: Object.freeze(["decision"]),
259
- producerOwnership: "exclusive"
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: input.sessionInteractions ? validateAgentProviderAdapter(capabilities, adapter) : adapter,
280
+ adapter,
271
281
  accountQuota: { kind: "unsupported" },
272
282
  environment: { kind: "unsupported" },
273
283
  checkReadiness(check = {}) {
@@ -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 genuine direct native executable, exact no-leader arguments and a handler for every created session. */
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
  }
@@ -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("The Grok folder-trust mapping requires an exclusive descendant-owning execution boundary.");
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) },
@@ -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") || selection.mapping !== "grok-folder-trust-v1") {
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("The Grok folder-trust mapping requires exact direct arguments: agent --no-leader stdio.");
41
+ throw new TypeError("Selected Grok session mappings require exact direct arguments: agent --no-leader stdio.");
42
42
  }
43
- return Object.freeze({ mapping: "grok-folder-trust-v1" });
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
+ };
@@ -20,7 +20,7 @@ class TurnMapping {
20
20
  planRevision = 0;
21
21
  event(type, payload) {
22
22
  return createAgentEventOutput({
23
- protocolVersion: 11,
23
+ protocolVersion: 12,
24
24
  sessionId: this.context.sessionId,
25
25
  turnId: this.context.turnId,
26
26
  occurredAt: this.context.now(),
@@ -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>;
@@ -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
  };
@@ -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