@automatalabs/acp-agents 0.13.0 → 0.15.0
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/dist/acp-client.d.ts +33 -3
- package/dist/acp-client.d.ts.map +1 -1
- package/dist/acp-client.js +242 -18
- package/dist/capabilities.d.ts +11 -0
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +28 -1
- package/dist/client-handlers.d.ts +7 -1
- package/dist/client-handlers.d.ts.map +1 -1
- package/dist/client-handlers.js +3 -1
- package/dist/events.d.ts +24 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +3 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/interactive.d.ts +14 -6
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +10 -2
- package/dist/permissions.d.ts +5 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/pool.d.ts +3 -1
- package/dist/pool.d.ts.map +1 -1
- package/dist/pool.js +2 -0
- package/dist/protocol-coverage.d.ts +3 -1
- package/dist/protocol-coverage.d.ts.map +1 -1
- package/dist/protocol-coverage.js +7 -7
- package/dist/runner.d.ts +52 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +142 -56
- package/package.json +1 -1
package/dist/acp-client.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { type ContentBlock, type AgentNotificationMethod, type AgentNotificationParamsByMethod, type AgentRequestMethod, type AgentRequestParamsByMethod, type AgentRequestResponsesByMethod, type PromptRequest, type PromptResponse, type RequestPermissionResponse, type SendRequestOptions, type SessionConfigOption, type SessionModeState, type SessionNotification, type SetSessionConfigOptionRequest, type SetSessionConfigOptionResponse, type SetSessionModeRequest, type SetSessionModeResponse } from "@agentclientprotocol/sdk";
|
|
1
|
+
import { type CreateElicitationResponse, type ContentBlock, type AgentNotificationMethod, type AgentNotificationParamsByMethod, type AgentRequestMethod, type AgentRequestParamsByMethod, type AgentRequestResponsesByMethod, type DeleteSessionRequest, type ListSessionsRequest, type ListSessionsResponse, type PromptRequest, type PromptResponse, type RequestPermissionResponse, type SendRequestOptions, type SessionConfigOption, type SessionModeState, type SessionNotification, type SetSessionConfigOptionRequest, type SetSessionConfigOptionResponse, type SetSessionModeRequest, type SetSessionModeResponse } from "@agentclientprotocol/sdk";
|
|
2
2
|
import type { TSchema } from "typebox";
|
|
3
3
|
import { type AgentHistoryEntry, type McpServerConfig } from "@automatalabs/shared-types";
|
|
4
4
|
import type { Backend, BackendId, StructuredSource } from "./backend.js";
|
|
5
5
|
import { type NegotiatedCapabilities } from "./capabilities.js";
|
|
6
6
|
import { type AcpEventSink } from "./events.js";
|
|
7
|
-
import { type PermissionResolver, type ToolPolicy } from "./permissions.js";
|
|
7
|
+
import { type ElicitationResolver, type PermissionResolver, type ToolPolicy } from "./permissions.js";
|
|
8
8
|
import { UsageAccumulator } from "./usage.js";
|
|
9
9
|
import { type ClientHandlers } from "./client-handlers.js";
|
|
10
10
|
interface RawResultSuccess {
|
|
@@ -18,6 +18,7 @@ declare class SessionState {
|
|
|
18
18
|
readonly cwd: string;
|
|
19
19
|
readonly policy: ToolPolicy;
|
|
20
20
|
readonly permissionResolver?: PermissionResolver | undefined;
|
|
21
|
+
readonly elicitationResolver?: ElicitationResolver | undefined;
|
|
21
22
|
readonly label?: string | undefined;
|
|
22
23
|
readonly runId?: string | undefined;
|
|
23
24
|
private readonly retainSessionLog;
|
|
@@ -25,12 +26,14 @@ declare class SessionState {
|
|
|
25
26
|
readonly history: AgentHistoryEntry[];
|
|
26
27
|
readonly usage: UsageAccumulator;
|
|
27
28
|
readonly pendingPermissions: Set<(outcome: RequestPermissionResponse) => void>;
|
|
29
|
+
readonly pendingElicitations: Set<(outcome: CreateElicitationResponse) => void>;
|
|
30
|
+
readonly urlElicitationIds: Set<string>;
|
|
28
31
|
rawResultSuccess: RawResultSuccess | undefined;
|
|
29
32
|
modes: SessionModeState | null | undefined;
|
|
30
33
|
private turnStartIndex;
|
|
31
34
|
/** `label`/`runId` are carried here ONLY so the MultiplexClient can stamp them onto emitted
|
|
32
35
|
* events as context — they never affect routing or the wire request. */
|
|
33
|
-
constructor(cwd: string, policy: ToolPolicy, permissionResolver?: PermissionResolver | undefined, label?: string | undefined, runId?: string | undefined, modes?: SessionModeState | null, retainSessionLog?: boolean);
|
|
36
|
+
constructor(cwd: string, policy: ToolPolicy, permissionResolver?: PermissionResolver | undefined, elicitationResolver?: ElicitationResolver | undefined, label?: string | undefined, runId?: string | undefined, modes?: SessionModeState | null, retainSessionLog?: boolean);
|
|
34
37
|
/** Mark the start of a new turn so currentTurnText()/structured_output read only this turn.
|
|
35
38
|
* Long-lived interactive sessions can opt out of retaining old text/history because hosts
|
|
36
39
|
* stream live events and keep their own transcript; clearing here prevents dead logs from
|
|
@@ -42,6 +45,9 @@ declare class SessionState {
|
|
|
42
45
|
/** Settle every deferred permission still parked on this session. Used by release/cancel/death
|
|
43
46
|
* teardown so an interactive resolver can never strand an ACP prompt turn. */
|
|
44
47
|
settlePendingPermissions(): void;
|
|
48
|
+
/** Same teardown guarantee for elicitation/create: a parked human prompt must not survive
|
|
49
|
+
* release/cancel/death after its ACP session is gone. */
|
|
50
|
+
settlePendingElicitations(): void;
|
|
45
51
|
}
|
|
46
52
|
export interface AcpSessionOptions {
|
|
47
53
|
/** Absolute working directory for the ACP session (worktree isolation). */
|
|
@@ -52,6 +58,9 @@ export interface AcpSessionOptions {
|
|
|
52
58
|
/** Session-scoped permission resolver. When present it wins over the runner default and
|
|
53
59
|
* replaces the synchronous ToolPolicy auto-response path for this session. */
|
|
54
60
|
permissionResolver?: PermissionResolver;
|
|
61
|
+
/** Session-scoped elicitation resolver. When present it wins over the runner default for this
|
|
62
|
+
* session; initialize-time advertisement still depends on the runner-wide resolver. */
|
|
63
|
+
elicitationResolver?: ElicitationResolver;
|
|
55
64
|
signal?: AbortSignal;
|
|
56
65
|
/** Client-provided MCP servers to attach at session/new. Omitted => `[]` (the default). */
|
|
57
66
|
mcpServers?: McpServerConfig[];
|
|
@@ -83,6 +92,11 @@ export interface PooledConnectionDeps {
|
|
|
83
92
|
onEvent?: AcpEventSink;
|
|
84
93
|
/** Runner-wide permission resolver default. SessionState.permissionResolver overrides it. */
|
|
85
94
|
permissionResolver?: PermissionResolver;
|
|
95
|
+
/** Runner-wide elicitation resolver default. SessionState.elicitationResolver overrides it. */
|
|
96
|
+
elicitationResolver?: ElicitationResolver;
|
|
97
|
+
/** Initialize-time elicitation advertisement; fixed per connection, so it is driven by the
|
|
98
|
+
* runner-wide resolver rather than session-scoped responders attached later. */
|
|
99
|
+
advertiseElicitation?: boolean;
|
|
86
100
|
/** Client-side ACP fs/terminal handlers advertised once and routed by sessionId. */
|
|
87
101
|
clientHandlers?: ClientHandlers;
|
|
88
102
|
}
|
|
@@ -102,6 +116,7 @@ export declare class PooledConnection {
|
|
|
102
116
|
private readonly onDead;
|
|
103
117
|
private readonly onEvent;
|
|
104
118
|
private readonly clientHandlers;
|
|
119
|
+
private readonly advertiseElicitation;
|
|
105
120
|
/** Set true at the start of dispose() so the graceful-shutdown death is NOT reported as a crash. */
|
|
106
121
|
private disposing;
|
|
107
122
|
/** Resolves once `initialize` completed (or rejects if the process died first). */
|
|
@@ -127,6 +142,9 @@ export declare class PooledConnection {
|
|
|
127
142
|
/** Drop the backend-declared bare `_meta` keys the connected agent did not advertise support
|
|
128
143
|
* for (see gateCustomMeta). Applied to BOTH session/new and session/prompt `_meta`. */
|
|
129
144
|
gateCustomMeta(meta: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
|
145
|
+
private assertSupportedMcpServers;
|
|
146
|
+
private sessionRequestMeta;
|
|
147
|
+
private assertLifecycleSupported;
|
|
130
148
|
/** Mark this connection dead exactly once, then ask the pool to evict it. Idempotent. */
|
|
131
149
|
private die;
|
|
132
150
|
private stderrSuffix;
|
|
@@ -140,6 +158,16 @@ export declare class PooledConnection {
|
|
|
140
158
|
* synchronously (before the first await) so the pool's load accounting is race-free.
|
|
141
159
|
*/
|
|
142
160
|
openSession(opts: AcpSessionOptions): Promise<SessionHandle>;
|
|
161
|
+
/** Reopen an existing session and replay its transcript through the router before resolving. */
|
|
162
|
+
loadSession(sessionId: string, opts: AcpSessionOptions): Promise<SessionHandle>;
|
|
163
|
+
/** Reopen an existing session without transcript replay. */
|
|
164
|
+
resumeSession(sessionId: string, opts: AcpSessionOptions): Promise<SessionHandle>;
|
|
165
|
+
private rawAgentRequest;
|
|
166
|
+
private reattachSession;
|
|
167
|
+
/** session/list on a dedicated connection, gated on the initialize advertisement. */
|
|
168
|
+
listSessions(request: ListSessionsRequest, label?: string): Promise<ListSessionsResponse>;
|
|
169
|
+
/** session/delete on a dedicated connection, gated on the initialize advertisement. */
|
|
170
|
+
deleteSession(request: DeleteSessionRequest, label?: string): Promise<void>;
|
|
143
171
|
/** session/prompt on this connection, raced against process death. */
|
|
144
172
|
prompt(request: PromptRequest): Promise<PromptResponse>;
|
|
145
173
|
/** session/set_config_option on this connection, raced against process death. */
|
|
@@ -187,6 +215,8 @@ export declare class SessionHandle implements StructuredSource {
|
|
|
187
215
|
get usage(): UsageAccumulator;
|
|
188
216
|
/** Diagnostic message/tool history accumulated across this session's run. */
|
|
189
217
|
get history(): AgentHistoryEntry[];
|
|
218
|
+
/** Assistant text accumulated across the retained session log. */
|
|
219
|
+
get text(): string;
|
|
190
220
|
/** Agent-advertised session mode catalog plus the currently active mode, if supported. */
|
|
191
221
|
get modes(): SessionModeState | null | undefined;
|
|
192
222
|
/**
|
package/dist/acp-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"acp-client.d.ts","sourceRoot":"","sources":["../src/acp-client.ts"],"names":[],"mappings":"AAwBA,OAAO,
|
|
1
|
+
{"version":3,"file":"acp-client.d.ts","sourceRoot":"","sources":["../src/acp-client.ts"],"names":[],"mappings":"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"}
|
package/dist/acp-client.js
CHANGED
|
@@ -24,9 +24,9 @@ import { spawn } from "node:child_process";
|
|
|
24
24
|
import { Readable, Writable } from "node:stream";
|
|
25
25
|
import { AGENT_METHODS, client, CLIENT_METHODS, ndJsonStream, PROTOCOL_VERSION, RequestError, } from "@agentclientprotocol/sdk";
|
|
26
26
|
import { META_KEYS, WorkflowError, WorkflowErrorCode, } from "@automatalabs/shared-types";
|
|
27
|
-
import { adaptPromptContent, gateCustomMeta, isSupportedProtocolVersion, negotiateCapabilities, unsupportedMcpServer, } from "./capabilities.js";
|
|
27
|
+
import { adaptPromptContent, describeLifecycleAdvertisement, gateCustomMeta, isSupportedProtocolVersion, negotiateCapabilities, unsupportedMcpServer, } from "./capabilities.js";
|
|
28
28
|
import { emitSessionUpdate } from "./events.js";
|
|
29
|
-
import { decidePermission } from "./permissions.js";
|
|
29
|
+
import { decidePermission, } from "./permissions.js";
|
|
30
30
|
import { UsageAccumulator } from "./usage.js";
|
|
31
31
|
import { clientCapabilitiesFor, } from "./client-handlers.js";
|
|
32
32
|
/** A benign client identity. NOT JetBrains/IntelliJ 2026.1 — that exact identity makes
|
|
@@ -42,12 +42,22 @@ const CLOSE_SESSION_TIMEOUT_MS = 5_000;
|
|
|
42
42
|
/** Bound the graceful SIGTERM shutdown before escalating to SIGKILL. */
|
|
43
43
|
const DISPOSE_SIGKILL_GRACE_MS = 2_000;
|
|
44
44
|
const TOMBSTONE_SESSION_CAP = 64;
|
|
45
|
+
const GUARDED_STATEFUL_REQUESTS = new Map([
|
|
46
|
+
[AGENT_METHODS.session_new, "use openSession()"],
|
|
47
|
+
[AGENT_METHODS.session_load, "use loadSession()"],
|
|
48
|
+
[AGENT_METHODS.session_resume, "use resumeSession()"],
|
|
49
|
+
[
|
|
50
|
+
AGENT_METHODS.session_fork,
|
|
51
|
+
"no driven wrapper yet; raw forked sessions cannot be routed (permissions auto-cancel)",
|
|
52
|
+
],
|
|
53
|
+
]);
|
|
45
54
|
/** Per-session accumulator: assistant text, tool history, usage, the Claude raw structured_output,
|
|
46
55
|
* and the permission policy/resolver used to answer permission requests for THIS session. */
|
|
47
56
|
class SessionState {
|
|
48
57
|
cwd;
|
|
49
58
|
policy;
|
|
50
59
|
permissionResolver;
|
|
60
|
+
elicitationResolver;
|
|
51
61
|
label;
|
|
52
62
|
runId;
|
|
53
63
|
retainSessionLog;
|
|
@@ -55,15 +65,18 @@ class SessionState {
|
|
|
55
65
|
history = [];
|
|
56
66
|
usage = new UsageAccumulator();
|
|
57
67
|
pendingPermissions = new Set();
|
|
68
|
+
pendingElicitations = new Set();
|
|
69
|
+
urlElicitationIds = new Set();
|
|
58
70
|
rawResultSuccess;
|
|
59
71
|
modes;
|
|
60
72
|
turnStartIndex = 0;
|
|
61
73
|
/** `label`/`runId` are carried here ONLY so the MultiplexClient can stamp them onto emitted
|
|
62
74
|
* events as context — they never affect routing or the wire request. */
|
|
63
|
-
constructor(cwd, policy, permissionResolver, label, runId, modes, retainSessionLog = true) {
|
|
75
|
+
constructor(cwd, policy, permissionResolver, elicitationResolver, label, runId, modes, retainSessionLog = true) {
|
|
64
76
|
this.cwd = cwd;
|
|
65
77
|
this.policy = policy;
|
|
66
78
|
this.permissionResolver = permissionResolver;
|
|
79
|
+
this.elicitationResolver = elicitationResolver;
|
|
67
80
|
this.label = label;
|
|
68
81
|
this.runId = runId;
|
|
69
82
|
this.retainSessionLog = retainSessionLog;
|
|
@@ -140,6 +153,12 @@ class SessionState {
|
|
|
140
153
|
for (const settle of [...this.pendingPermissions])
|
|
141
154
|
settle(cancelledPermissionResponse());
|
|
142
155
|
}
|
|
156
|
+
/** Same teardown guarantee for elicitation/create: a parked human prompt must not survive
|
|
157
|
+
* release/cancel/death after its ACP session is gone. */
|
|
158
|
+
settlePendingElicitations() {
|
|
159
|
+
for (const settle of [...this.pendingElicitations])
|
|
160
|
+
settle(cancelledElicitationResponse());
|
|
161
|
+
}
|
|
143
162
|
}
|
|
144
163
|
/** The single client-side handler set for one pooled connection (registered on the SDK's fluent
|
|
145
164
|
* client() app). It ROUTES every notification and permission request to the per-session
|
|
@@ -150,7 +169,9 @@ class MultiplexClient {
|
|
|
150
169
|
onEvent;
|
|
151
170
|
handlers;
|
|
152
171
|
permissionResolver;
|
|
172
|
+
elicitationResolver;
|
|
153
173
|
sessions = new Map();
|
|
174
|
+
urlElicitationContexts = new Map();
|
|
154
175
|
/** Recently unregistered sessions kept ONLY for the teardown window: ACP agents may
|
|
155
176
|
* legitimately release terminals (or finish fs/terminal cleanup) after this client releases
|
|
156
177
|
* the session because session/close is cancel + free resources and the Agent owns terminal
|
|
@@ -159,11 +180,12 @@ class MultiplexClient {
|
|
|
159
180
|
/** `backendId` stamps event context; `onEvent` (optional) bubbles every notification, permission
|
|
160
181
|
* request and session lifecycle change up to the runner's typed bus. `permissionResolver` is
|
|
161
182
|
* the runner-wide default; a SessionState resolver wins when present. */
|
|
162
|
-
constructor(backendId, onEvent, handlers, permissionResolver) {
|
|
183
|
+
constructor(backendId, onEvent, handlers, permissionResolver, elicitationResolver) {
|
|
163
184
|
this.backendId = backendId;
|
|
164
185
|
this.onEvent = onEvent;
|
|
165
186
|
this.handlers = handlers;
|
|
166
187
|
this.permissionResolver = permissionResolver;
|
|
188
|
+
this.elicitationResolver = elicitationResolver;
|
|
167
189
|
}
|
|
168
190
|
contextFor(sessionId, state) {
|
|
169
191
|
return { sessionId, backendId: this.backendId, label: state?.label, runId: state?.runId };
|
|
@@ -191,8 +213,11 @@ class MultiplexClient {
|
|
|
191
213
|
unregister(sessionId) {
|
|
192
214
|
const state = this.sessions.get(sessionId);
|
|
193
215
|
state?.settlePendingPermissions();
|
|
216
|
+
state?.settlePendingElicitations();
|
|
194
217
|
this.sessions.delete(sessionId);
|
|
195
218
|
if (state) {
|
|
219
|
+
for (const elicitationId of state.urlElicitationIds)
|
|
220
|
+
this.urlElicitationContexts.delete(elicitationId);
|
|
196
221
|
this.tombstones.set(sessionId, {
|
|
197
222
|
cwd: state.cwd,
|
|
198
223
|
label: state.label,
|
|
@@ -210,11 +235,19 @@ class MultiplexClient {
|
|
|
210
235
|
settlePendingPermissions(sessionId) {
|
|
211
236
|
this.sessions.get(sessionId)?.settlePendingPermissions();
|
|
212
237
|
}
|
|
238
|
+
settlePendingElicitations(sessionId) {
|
|
239
|
+
this.sessions.get(sessionId)?.settlePendingElicitations();
|
|
240
|
+
}
|
|
213
241
|
settleAllPendingPermissions() {
|
|
214
242
|
for (const state of this.sessions.values()) {
|
|
215
243
|
state.settlePendingPermissions();
|
|
216
244
|
}
|
|
217
245
|
}
|
|
246
|
+
settleAllPendingElicitations() {
|
|
247
|
+
for (const state of this.sessions.values()) {
|
|
248
|
+
state.settlePendingElicitations();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
218
251
|
requestPermission(params) {
|
|
219
252
|
const state = this.sessions.get(params.sessionId);
|
|
220
253
|
// Unknown/closed session: refuse rather than silently allow a tool we can't attribute.
|
|
@@ -262,6 +295,63 @@ class MultiplexClient {
|
|
|
262
295
|
response.catch(() => { });
|
|
263
296
|
return response;
|
|
264
297
|
}
|
|
298
|
+
requestElicitation(params) {
|
|
299
|
+
const sessionId = sessionIdFromElicitationRequest(params);
|
|
300
|
+
if (!sessionId)
|
|
301
|
+
return declinedElicitationResponse();
|
|
302
|
+
const state = this.sessions.get(sessionId);
|
|
303
|
+
const ctx = this.contextFor(sessionId, state);
|
|
304
|
+
// Unknown/closed session: decline rather than asking a human for a prompt we cannot route.
|
|
305
|
+
if (!state) {
|
|
306
|
+
const outcome = declinedElicitationResponse();
|
|
307
|
+
this.onEvent?.("elicitation_request", { ...ctx, request: params, outcome });
|
|
308
|
+
return outcome;
|
|
309
|
+
}
|
|
310
|
+
this.trackUrlElicitation(params, state, ctx);
|
|
311
|
+
const resolver = state.elicitationResolver ?? this.elicitationResolver;
|
|
312
|
+
if (resolver)
|
|
313
|
+
return this.requestElicitationViaResolver(params, state, ctx, resolver);
|
|
314
|
+
const outcome = declinedElicitationResponse();
|
|
315
|
+
this.onEvent?.("elicitation_request", { ...ctx, request: params, outcome });
|
|
316
|
+
return outcome;
|
|
317
|
+
}
|
|
318
|
+
requestElicitationViaResolver(params, state, ctx, resolver) {
|
|
319
|
+
let settled = false;
|
|
320
|
+
let settle;
|
|
321
|
+
const response = new Promise((resolve) => {
|
|
322
|
+
settle = (outcome) => {
|
|
323
|
+
if (settled)
|
|
324
|
+
return;
|
|
325
|
+
settled = true;
|
|
326
|
+
state.pendingElicitations.delete(settle);
|
|
327
|
+
this.onEvent?.("elicitation_request", { ...ctx, request: params, outcome });
|
|
328
|
+
resolve(outcome);
|
|
329
|
+
};
|
|
330
|
+
state.pendingElicitations.add(settle);
|
|
331
|
+
this.onEvent?.("elicitation_pending", { ...ctx, request: params });
|
|
332
|
+
try {
|
|
333
|
+
Promise.resolve(resolver(params, ctx)).then((outcome) => {
|
|
334
|
+
settle(outcome);
|
|
335
|
+
}, () => {
|
|
336
|
+
// Resolver failure is observable as the FINAL cancel outcome; no extra error event is
|
|
337
|
+
// needed, and the prompt turn continues instead of hanging behind a rejected promise.
|
|
338
|
+
settle(cancelledElicitationResponse());
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
settle(cancelledElicitationResponse());
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
response.catch(() => { });
|
|
346
|
+
return response;
|
|
347
|
+
}
|
|
348
|
+
trackUrlElicitation(params, state, ctx) {
|
|
349
|
+
const elicitationId = urlElicitationId(params);
|
|
350
|
+
if (!elicitationId)
|
|
351
|
+
return;
|
|
352
|
+
state.urlElicitationIds.add(elicitationId);
|
|
353
|
+
this.urlElicitationContexts.set(elicitationId, ctx);
|
|
354
|
+
}
|
|
265
355
|
readTextFile(params) {
|
|
266
356
|
return this.dispatch(params, CLIENT_METHODS.fs_read_text_file, this.handlers?.fs?.readTextFile);
|
|
267
357
|
}
|
|
@@ -308,6 +398,14 @@ class MultiplexClient {
|
|
|
308
398
|
message: rawMessage,
|
|
309
399
|
});
|
|
310
400
|
}
|
|
401
|
+
elicitationComplete(params) {
|
|
402
|
+
const ctx = this.urlElicitationContexts.get(params.elicitationId);
|
|
403
|
+
if (!ctx)
|
|
404
|
+
return;
|
|
405
|
+
this.urlElicitationContexts.delete(params.elicitationId);
|
|
406
|
+
this.sessions.get(ctx.sessionId)?.urlElicitationIds.delete(params.elicitationId);
|
|
407
|
+
this.onEvent?.("elicitation_complete", { ...ctx, notification: params });
|
|
408
|
+
}
|
|
311
409
|
}
|
|
312
410
|
function unknownSession(sessionId) {
|
|
313
411
|
return RequestError.invalidParams({ sessionId }, `unknown session: ${sessionId}`);
|
|
@@ -315,9 +413,31 @@ function unknownSession(sessionId) {
|
|
|
315
413
|
function cancelledPermissionResponse() {
|
|
316
414
|
return { outcome: { outcome: "cancelled" } };
|
|
317
415
|
}
|
|
416
|
+
function declinedElicitationResponse() {
|
|
417
|
+
return { action: "decline" };
|
|
418
|
+
}
|
|
419
|
+
function cancelledElicitationResponse() {
|
|
420
|
+
return { action: "cancel" };
|
|
421
|
+
}
|
|
422
|
+
function sessionIdFromElicitationRequest(params) {
|
|
423
|
+
return "sessionId" in params && typeof params.sessionId === "string" ? params.sessionId : undefined;
|
|
424
|
+
}
|
|
425
|
+
function urlElicitationId(params) {
|
|
426
|
+
return params.mode === "url" && "elicitationId" in params && typeof params.elicitationId === "string"
|
|
427
|
+
? params.elicitationId
|
|
428
|
+
: undefined;
|
|
429
|
+
}
|
|
318
430
|
function methodNotAdvertised(method) {
|
|
319
431
|
return new RequestError(-32601, `${method} was not advertised by this client`, { method });
|
|
320
432
|
}
|
|
433
|
+
function assertSafeRawRequest(method) {
|
|
434
|
+
const guidance = GUARDED_STATEFUL_REQUESTS.get(method);
|
|
435
|
+
if (!guidance)
|
|
436
|
+
return;
|
|
437
|
+
throw new Error(`Raw ACP request "${method}" is guarded: ${guidance}. Sessions created, reopened, or forked ` +
|
|
438
|
+
"outside the router are unregistered: session/update notifications do not fold into an " +
|
|
439
|
+
"accumulator, permission requests auto-cancel, and fs/terminal dispatch fails for unknown sessions.");
|
|
440
|
+
}
|
|
321
441
|
function modeIds(modes) {
|
|
322
442
|
return modes?.availableModes.map((mode) => mode.id) ?? [];
|
|
323
443
|
}
|
|
@@ -326,6 +446,12 @@ function modeSelectionError(backendId, requested, advertisedIds, label, cause) {
|
|
|
326
446
|
const suffix = cause ? `: ${thrownMessage(cause)}` : "";
|
|
327
447
|
return new WorkflowError(`ACP agent (${backendId}) cannot apply session mode "${requested}" (advertised modes: ${advertised})${suffix}`, WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, { recoverable: false, agentLabel: label, details: cause });
|
|
328
448
|
}
|
|
449
|
+
function lifecycleCapabilityError(backendId, method, capabilities, label) {
|
|
450
|
+
const advertised = capabilities
|
|
451
|
+
? describeLifecycleAdvertisement(capabilities.agent)
|
|
452
|
+
: "initialize did not complete";
|
|
453
|
+
return new WorkflowError(`ACP agent (${backendId}) does not advertise ${method}; advertised lifecycle capabilities: ${advertised}`, WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, { recoverable: false, agentLabel: label });
|
|
454
|
+
}
|
|
329
455
|
function thrownMessage(error) {
|
|
330
456
|
if (error instanceof Error)
|
|
331
457
|
return error.message;
|
|
@@ -388,6 +514,7 @@ export class PooledConnection {
|
|
|
388
514
|
onDead;
|
|
389
515
|
onEvent;
|
|
390
516
|
clientHandlers;
|
|
517
|
+
advertiseElicitation;
|
|
391
518
|
/** Set true at the start of dispose() so the graceful-shutdown death is NOT reported as a crash. */
|
|
392
519
|
disposing = false;
|
|
393
520
|
/** Resolves once `initialize` completed (or rejects if the process died first). */
|
|
@@ -407,7 +534,8 @@ export class PooledConnection {
|
|
|
407
534
|
this.onDead = deps.onDead;
|
|
408
535
|
this.onEvent = deps.onEvent;
|
|
409
536
|
this.clientHandlers = deps.clientHandlers;
|
|
410
|
-
this.
|
|
537
|
+
this.advertiseElicitation = deps.advertiseElicitation ?? Boolean(deps.elicitationResolver);
|
|
538
|
+
this.client = new MultiplexClient(this.backendId, this.onEvent, deps.clientHandlers, deps.permissionResolver, deps.elicitationResolver);
|
|
411
539
|
const { command, args, env } = backend.spawnConfig();
|
|
412
540
|
// NOTE: deliberately NO `cwd` here. cwd is per-SESSION (session/new), so one pooled process
|
|
413
541
|
// serves runs in different worktrees without losing isolation.
|
|
@@ -445,7 +573,9 @@ export class PooledConnection {
|
|
|
445
573
|
this.connection = client({ name: CLIENT_INFO.name })
|
|
446
574
|
.onNotification(CLIENT_METHODS.session_update, ({ params }) => this.client.sessionUpdate(params))
|
|
447
575
|
.onNotification(CLAUDE_RAW_MESSAGE_METHOD, (params) => (params ?? {}), ({ params }) => this.client.extNotification(CLAUDE_RAW_MESSAGE_METHOD, params))
|
|
576
|
+
.onNotification(CLIENT_METHODS.elicitation_complete, ({ params }) => this.client.elicitationComplete(params))
|
|
448
577
|
.onRequest(CLIENT_METHODS.session_request_permission, ({ params }) => this.client.requestPermission(params))
|
|
578
|
+
.onRequest(CLIENT_METHODS.elicitation_create, ({ params }) => this.client.requestElicitation(params))
|
|
449
579
|
.onRequest(CLIENT_METHODS.fs_read_text_file, ({ params }) => this.client.readTextFile(params))
|
|
450
580
|
.onRequest(CLIENT_METHODS.fs_write_text_file, ({ params }) => this.client.writeTextFile(params))
|
|
451
581
|
.onRequest(CLIENT_METHODS.terminal_create, ({ params }) => this.client.createTerminal(params))
|
|
@@ -490,6 +620,35 @@ export class PooledConnection {
|
|
|
490
620
|
gateCustomMeta(meta) {
|
|
491
621
|
return gateCustomMeta(meta, this.negotiated?.customMetaSupport, this.negotiated?.gatedKeys);
|
|
492
622
|
}
|
|
623
|
+
assertSupportedMcpServers(opts) {
|
|
624
|
+
const unsupported = this.negotiated
|
|
625
|
+
? unsupportedMcpServer(opts.mcpServers, this.negotiated.agent)
|
|
626
|
+
: undefined;
|
|
627
|
+
if (!unsupported)
|
|
628
|
+
return;
|
|
629
|
+
throw new WorkflowError(`MCP server "${unsupported.name}" uses the "${unsupported.transport}" transport, which the ` +
|
|
630
|
+
`${this.backendId} agent does not support`, WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, { recoverable: false, agentLabel: opts.label });
|
|
631
|
+
}
|
|
632
|
+
sessionRequestMeta(opts) {
|
|
633
|
+
return this.gateCustomMeta(stampRunId(layerMeta(this.backend.sessionMetaDefaults?.(), opts.meta, this.backend.sessionMeta(opts.schema, {
|
|
634
|
+
baseInstructions: opts.baseInstructions,
|
|
635
|
+
developerInstructions: opts.developerInstructions,
|
|
636
|
+
})), opts.runId));
|
|
637
|
+
}
|
|
638
|
+
assertLifecycleSupported(method, label) {
|
|
639
|
+
const supported = method === AGENT_METHODS.session_load
|
|
640
|
+
? this.negotiated?.supportsLoadSession
|
|
641
|
+
: method === AGENT_METHODS.session_list
|
|
642
|
+
? this.negotiated?.supportsListSessions
|
|
643
|
+
: method === AGENT_METHODS.session_delete
|
|
644
|
+
? this.negotiated?.supportsDeleteSession
|
|
645
|
+
: method === AGENT_METHODS.session_resume
|
|
646
|
+
? this.negotiated?.supportsResumeSession
|
|
647
|
+
: false;
|
|
648
|
+
if (supported)
|
|
649
|
+
return;
|
|
650
|
+
throw lifecycleCapabilityError(this.backendId, method, this.negotiated, label);
|
|
651
|
+
}
|
|
493
652
|
/** Mark this connection dead exactly once, then ask the pool to evict it. Idempotent. */
|
|
494
653
|
die(error) {
|
|
495
654
|
if (!this._alive)
|
|
@@ -498,6 +657,7 @@ export class PooledConnection {
|
|
|
498
657
|
this.deathError = error;
|
|
499
658
|
this.resolveDead();
|
|
500
659
|
this.client.settleAllPendingPermissions();
|
|
660
|
+
this.client.settleAllPendingElicitations();
|
|
501
661
|
// A crash (not a graceful dispose) is worth surfacing for observability; the engine still
|
|
502
662
|
// handles it by retrying the run on a fresh process. Best-effort, after death is recorded.
|
|
503
663
|
if (this.onEvent && !this.disposing) {
|
|
@@ -541,7 +701,9 @@ export class PooledConnection {
|
|
|
541
701
|
protocolVersion: PROTOCOL_VERSION,
|
|
542
702
|
// Truthful advertisement: computed from the consumer-provided handlers registered on
|
|
543
703
|
// this runner. Omitted flags are unsupported; false flags are never sent deliberately.
|
|
544
|
-
clientCapabilities: clientCapabilitiesFor(this.clientHandlers
|
|
704
|
+
clientCapabilities: clientCapabilitiesFor(this.clientHandlers, {
|
|
705
|
+
elicitation: this.advertiseElicitation,
|
|
706
|
+
}),
|
|
545
707
|
clientInfo: { ...CLIENT_INFO },
|
|
546
708
|
})),
|
|
547
709
|
deadline,
|
|
@@ -577,23 +739,14 @@ export class PooledConnection {
|
|
|
577
739
|
// does not advertise (http/sse gated on mcpCapabilities; stdio is always serviceable).
|
|
578
740
|
// Fail-fast and non-recoverable — re-running the same incompatible transport can never
|
|
579
741
|
// succeed. Lenient for agents that advertise no mcpCapabilities (the legacy passthrough).
|
|
580
|
-
|
|
581
|
-
? unsupportedMcpServer(opts.mcpServers, this.negotiated.agent)
|
|
582
|
-
: undefined;
|
|
583
|
-
if (unsupported) {
|
|
584
|
-
throw new WorkflowError(`MCP server "${unsupported.name}" uses the "${unsupported.transport}" transport, which the ` +
|
|
585
|
-
`${this.backendId} agent does not support`, WorkflowErrorCode.SCRIPT_VALIDATION_ERROR, { recoverable: false, agentLabel: opts.label });
|
|
586
|
-
}
|
|
742
|
+
this.assertSupportedMcpServers(opts);
|
|
587
743
|
// session/new `_meta`, layered lowest-to-highest precedence: the backend's static
|
|
588
744
|
// defaults (a custom registry entry's `sessionMeta`), then the generic user passthrough
|
|
589
745
|
// (opts.meta), then the backend's protocol-critical `_meta` (Claude schema channel;
|
|
590
746
|
// Codex base/developer instructions), then the engine runId correlation stamp. The result
|
|
591
747
|
// is gated against the agent's advertised custom capabilities (a declared key the agent
|
|
592
748
|
// said it does not honor is dropped). When no layer survives, no `_meta` is sent.
|
|
593
|
-
const meta = this.
|
|
594
|
-
baseInstructions: opts.baseInstructions,
|
|
595
|
-
developerInstructions: opts.developerInstructions,
|
|
596
|
-
})), opts.runId));
|
|
749
|
+
const meta = this.sessionRequestMeta(opts);
|
|
597
750
|
const request = {
|
|
598
751
|
cwd: opts.cwd,
|
|
599
752
|
// Client-provided MCP servers (additive run input), else the default empty list.
|
|
@@ -601,7 +754,7 @@ export class PooledConnection {
|
|
|
601
754
|
...(meta ? { _meta: meta } : {}),
|
|
602
755
|
};
|
|
603
756
|
const response = await this.race(this.connection.agent.request(AGENT_METHODS.session_new, request));
|
|
604
|
-
const state = new SessionState(opts.cwd, opts.policy, opts.permissionResolver, opts.label, opts.runId, response.modes, opts.retainSessionLog ?? true);
|
|
757
|
+
const state = new SessionState(opts.cwd, opts.policy, opts.permissionResolver, opts.elicitationResolver, opts.label, opts.runId, response.modes, opts.retainSessionLog ?? true);
|
|
605
758
|
this.client.register(response.sessionId, state);
|
|
606
759
|
return new SessionHandle(this, response.sessionId, state, response.configOptions ?? [], opts);
|
|
607
760
|
}
|
|
@@ -610,6 +763,71 @@ export class PooledConnection {
|
|
|
610
763
|
throw error;
|
|
611
764
|
}
|
|
612
765
|
}
|
|
766
|
+
/** Reopen an existing session and replay its transcript through the router before resolving. */
|
|
767
|
+
loadSession(sessionId, opts) {
|
|
768
|
+
return this.reattachSession(AGENT_METHODS.session_load, sessionId, opts);
|
|
769
|
+
}
|
|
770
|
+
/** Reopen an existing session without transcript replay. */
|
|
771
|
+
resumeSession(sessionId, opts) {
|
|
772
|
+
return this.reattachSession(AGENT_METHODS.session_resume, sessionId, opts);
|
|
773
|
+
}
|
|
774
|
+
rawAgentRequest(method, params) {
|
|
775
|
+
// SDK 1.2.0 still maps session/load through emptyObjectResponse in ClientContext.request(),
|
|
776
|
+
// but both supported adapters return configOptions/modes there and the driven wrapper must
|
|
777
|
+
// adopt them. This bypasses only the SDK response mapper, not JSON-RPC validation/racing.
|
|
778
|
+
const agent = this.connection.agent;
|
|
779
|
+
return this.race(agent.sendRequest(method, params));
|
|
780
|
+
}
|
|
781
|
+
async reattachSession(method, sessionId, opts) {
|
|
782
|
+
this._activeSessions += 1;
|
|
783
|
+
let registered = false;
|
|
784
|
+
const state = new SessionState(opts.cwd, opts.policy, opts.permissionResolver, opts.elicitationResolver, opts.label, opts.runId, undefined, opts.retainSessionLog ?? true);
|
|
785
|
+
try {
|
|
786
|
+
await this.ready;
|
|
787
|
+
this.assertLifecycleSupported(method, opts.label);
|
|
788
|
+
this.assertSupportedMcpServers(opts);
|
|
789
|
+
const meta = this.sessionRequestMeta(opts);
|
|
790
|
+
const request = {
|
|
791
|
+
sessionId,
|
|
792
|
+
cwd: opts.cwd,
|
|
793
|
+
mcpServers: opts.mcpServers ?? [],
|
|
794
|
+
...(meta ? { _meta: meta } : {}),
|
|
795
|
+
};
|
|
796
|
+
// Replayed updates and permission requests can arrive before the response, so the
|
|
797
|
+
// caller-provided id must be routable before the wire call leaves this process.
|
|
798
|
+
this.client.register(sessionId, state);
|
|
799
|
+
registered = true;
|
|
800
|
+
let response;
|
|
801
|
+
if (method === AGENT_METHODS.session_load) {
|
|
802
|
+
const loadRequest = request;
|
|
803
|
+
response = await this.rawAgentRequest(AGENT_METHODS.session_load, loadRequest);
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
const resumeRequest = request;
|
|
807
|
+
response = await this.rawAgentRequest(AGENT_METHODS.session_resume, resumeRequest);
|
|
808
|
+
}
|
|
809
|
+
state.modes = response.modes;
|
|
810
|
+
return new SessionHandle(this, sessionId, state, response.configOptions ?? [], opts);
|
|
811
|
+
}
|
|
812
|
+
catch (error) {
|
|
813
|
+
if (registered)
|
|
814
|
+
this.client.unregister(sessionId);
|
|
815
|
+
this._activeSessions -= 1;
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/** session/list on a dedicated connection, gated on the initialize advertisement. */
|
|
820
|
+
async listSessions(request, label) {
|
|
821
|
+
await this.ready;
|
|
822
|
+
this.assertLifecycleSupported(AGENT_METHODS.session_list, label);
|
|
823
|
+
return this.race(this.connection.agent.request(AGENT_METHODS.session_list, request));
|
|
824
|
+
}
|
|
825
|
+
/** session/delete on a dedicated connection, gated on the initialize advertisement. */
|
|
826
|
+
async deleteSession(request, label) {
|
|
827
|
+
await this.ready;
|
|
828
|
+
this.assertLifecycleSupported(AGENT_METHODS.session_delete, label);
|
|
829
|
+
await this.race(this.connection.agent.request(AGENT_METHODS.session_delete, request));
|
|
830
|
+
}
|
|
613
831
|
/** session/prompt on this connection, raced against process death. */
|
|
614
832
|
prompt(request) {
|
|
615
833
|
return this.race(this.connection.agent.request(AGENT_METHODS.session_prompt, request));
|
|
@@ -623,6 +841,7 @@ export class PooledConnection {
|
|
|
623
841
|
return this.race(this.connection.agent.request(AGENT_METHODS.session_set_mode, request));
|
|
624
842
|
}
|
|
625
843
|
request(method, params, options) {
|
|
844
|
+
assertSafeRawRequest(method);
|
|
626
845
|
return this.race(this.connection.agent.request(method, params, options));
|
|
627
846
|
}
|
|
628
847
|
notify(method, params) {
|
|
@@ -631,6 +850,7 @@ export class PooledConnection {
|
|
|
631
850
|
/** Best-effort ACP cancel for one session (wired to opts.signal). The PROCESS stays pooled. */
|
|
632
851
|
async cancelSession(sessionId) {
|
|
633
852
|
this.client.settlePendingPermissions(sessionId);
|
|
853
|
+
this.client.settlePendingElicitations(sessionId);
|
|
634
854
|
if (!this._alive)
|
|
635
855
|
return;
|
|
636
856
|
try {
|
|
@@ -743,6 +963,10 @@ export class SessionHandle {
|
|
|
743
963
|
get history() {
|
|
744
964
|
return this.state.history;
|
|
745
965
|
}
|
|
966
|
+
/** Assistant text accumulated across the retained session log. */
|
|
967
|
+
get text() {
|
|
968
|
+
return this.state.textChunks.join("");
|
|
969
|
+
}
|
|
746
970
|
/** Agent-advertised session mode catalog plus the currently active mode, if supported. */
|
|
747
971
|
get modes() {
|
|
748
972
|
return this.state.modes;
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -18,6 +18,15 @@ export interface NegotiatedCapabilities {
|
|
|
18
18
|
agentInfo: Implementation | undefined;
|
|
19
19
|
/** Whether session/close is advertised (gates the best-effort release-time close). */
|
|
20
20
|
supportsClose: boolean;
|
|
21
|
+
/** Whether session/load is advertised. The current SDK keeps this as the legacy top-level
|
|
22
|
+
* `loadSession` flag; tolerate a future sessionCapabilities.load shape for forward compat. */
|
|
23
|
+
supportsLoadSession: boolean;
|
|
24
|
+
/** Whether session/list is advertised. */
|
|
25
|
+
supportsListSessions: boolean;
|
|
26
|
+
/** Whether session/delete is advertised. */
|
|
27
|
+
supportsDeleteSession: boolean;
|
|
28
|
+
/** Whether session/resume is advertised. */
|
|
29
|
+
supportsResumeSession: boolean;
|
|
21
30
|
/** The parsed backend-declared custom-capability block (the namespaced `_meta` object), or
|
|
22
31
|
* undefined when the backend declared none or the agent did not advertise it — passthrough. */
|
|
23
32
|
customMetaSupport: Record<string, unknown> | undefined;
|
|
@@ -27,6 +36,8 @@ export interface NegotiatedCapabilities {
|
|
|
27
36
|
}
|
|
28
37
|
/** Parse an initialize response into the connection's derived capability state. */
|
|
29
38
|
export declare function negotiateCapabilities(response: InitializeResponse, customCapabilities?: Backend["customCapabilities"]): NegotiatedCapabilities;
|
|
39
|
+
/** Human-readable lifecycle advertisement summary for strict wrapper gate errors. */
|
|
40
|
+
export declare function describeLifecycleAdvertisement(agent: AgentCapabilities): string;
|
|
30
41
|
/** True only when the agent selected EXACTLY PROTOCOL_VERSION. This client implements that one wire
|
|
31
42
|
* version and adapts its behavior to no other, so any other selected version — older or newer —
|
|
32
43
|
* means close the connection per the ACP spec's SHOULD-close rule. Per the spec the agent echoes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAkBA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAQ5C;;;iGAGiG;AACjG,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAInD,CAAC;AAEF,qFAAqF;AACrF,MAAM,WAAW,sBAAsB;IACrC;0FACsF;IACtF,eAAe,EAAE,MAAM,CAAC;IACxB;4DACwD;IACxD,KAAK,EAAE,iBAAiB,CAAC;IACzB,+DAA+D;IAC/D,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,sFAAsF;IACtF,aAAa,EAAE,OAAO,CAAC;IACvB;oGACgG;IAChG,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACvD;+EAC2E;IAC3E,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAC1C;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,kBAAkB,EAC5B,kBAAkB,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GACjD,sBAAsB,
|
|
1
|
+
{"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAkBA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAQ5C;;;iGAGiG;AACjG,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAInD,CAAC;AAEF,qFAAqF;AACrF,MAAM,WAAW,sBAAsB;IACrC;0FACsF;IACtF,eAAe,EAAE,MAAM,CAAC;IACxB;4DACwD;IACxD,KAAK,EAAE,iBAAiB,CAAC;IACzB,+DAA+D;IAC/D,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,sFAAsF;IACtF,aAAa,EAAE,OAAO,CAAC;IACvB;mGAC+F;IAC/F,mBAAmB,EAAE,OAAO,CAAC;IAC7B,0CAA0C;IAC1C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,4CAA4C;IAC5C,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4CAA4C;IAC5C,qBAAqB,EAAE,OAAO,CAAC;IAC/B;oGACgG;IAChG,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACvD;+EAC2E;IAC3E,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAC1C;AAED,mFAAmF;AACnF,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,kBAAkB,EAC5B,kBAAkB,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GACjD,sBAAsB,CAkBxB;AAMD,qFAAqF;AACrF,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAiB/E;AAaD;;;;+DAI+D;AAC/D,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED;;;gEAGgE;AAChE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC5C,SAAS,GAAE,SAAS,MAAM,EAA2B,GACpD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAWrC;AAED;;;;;2EAK2E;AAC3E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,EAAE,EACtB,KAAK,EAAE,iBAAiB,EACxB,SAAS,EAAE,MAAM,GAChB,YAAY,EAAE,CAchB;AAkCD;;;;0EAI0E;AAC1E,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,eAAe,EAAE,GAAG,SAAS,EACtC,KAAK,EAAE,iBAAiB,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAA;CAAE,GAAG,SAAS,CAYzD"}
|