zelari-code 2.15.0 → 2.16.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.
Files changed (46) hide show
  1. package/dist/cli/companion/cors.js +52 -0
  2. package/dist/cli/companion/cors.js.map +1 -0
  3. package/dist/cli/companion/runManager.js +233 -1
  4. package/dist/cli/companion/runManager.js.map +1 -1
  5. package/dist/cli/companion/serve.js +21 -4
  6. package/dist/cli/companion/serve.js.map +1 -1
  7. package/dist/cli/extensions/extensionToolWiring.js +57 -0
  8. package/dist/cli/extensions/extensionToolWiring.js.map +1 -0
  9. package/dist/cli/extensions/loader.js +213 -0
  10. package/dist/cli/extensions/loader.js.map +1 -0
  11. package/dist/cli/extensions/sandboxedFs.js +69 -0
  12. package/dist/cli/extensions/sandboxedFs.js.map +1 -0
  13. package/dist/cli/headless/runOneTurn.js +783 -0
  14. package/dist/cli/headless/runOneTurn.js.map +1 -0
  15. package/dist/cli/kraken/completionGate.js +22 -5
  16. package/dist/cli/kraken/completionGate.js.map +1 -1
  17. package/dist/cli/lsp/manager.js +16 -0
  18. package/dist/cli/lsp/manager.js.map +1 -1
  19. package/dist/cli/main.bundled.js +17714 -15752
  20. package/dist/cli/main.bundled.js.map +4 -4
  21. package/dist/cli/main.js +20 -0
  22. package/dist/cli/main.js.map +1 -1
  23. package/dist/cli/runHeadless.js +5 -695
  24. package/dist/cli/runHeadless.js.map +1 -1
  25. package/dist/cli/safety/jails/darwin.js +87 -0
  26. package/dist/cli/safety/jails/darwin.js.map +1 -0
  27. package/dist/cli/safety/jails/linux.js +80 -0
  28. package/dist/cli/safety/jails/linux.js.map +1 -0
  29. package/dist/cli/safety/jails/win32.js +19 -0
  30. package/dist/cli/safety/jails/win32.js.map +1 -0
  31. package/dist/cli/safety/lifecycleHooks.js +33 -3
  32. package/dist/cli/safety/lifecycleHooks.js.map +1 -1
  33. package/dist/cli/safety/osJail.js +289 -0
  34. package/dist/cli/safety/osJail.js.map +1 -0
  35. package/dist/cli/safety/policyLoadMode.js.map +1 -1
  36. package/dist/cli/serve/harnessClient.js +150 -0
  37. package/dist/cli/serve/harnessClient.js.map +1 -0
  38. package/dist/cli/serve/harnessServer.js +333 -0
  39. package/dist/cli/serve/harnessServer.js.map +1 -0
  40. package/dist/cli/serve/sessionControl.js +67 -0
  41. package/dist/cli/serve/sessionControl.js.map +1 -0
  42. package/dist/cli/toolRegistry.js +338 -33
  43. package/dist/cli/toolRegistry.js.map +1 -1
  44. package/dist/cli/tools/execProcess.js +36 -15
  45. package/dist/cli/tools/execProcess.js.map +1 -1
  46. package/package.json +2 -2
@@ -0,0 +1,333 @@
1
+ /**
2
+ * harnessServer — `--serve-harness` (t29, Pilastro B).
3
+ *
4
+ * A long-lived host for the HarnessAppServer kernel
5
+ * (@zelari/core/harness) speaking line-delimited JSON (NDJSON) over
6
+ * stdio, on top of the EXISTING headless protocol v2: the boot line is
7
+ * the same `protocol_info` event `--headless` emits (version 2), and
8
+ * while a turn runs the same BrainEvent stream is forwarded verbatim.
9
+ *
10
+ * Wire format (client → server requests carry `id`; server → client
11
+ * unsolicited events carry `type` — clients demultiplex on that):
12
+ *
13
+ * → {"id":1,"method":"session.create","params":{"workspaceRoot":"…"}}
14
+ * ← {"id":1,"ok":true,"result":{"sessionId":"…"}}
15
+ * → {"id":2,"method":"run.turn","params":{"sessionId":"…", …turn input
16
+ * same shape as a headless single turn (HeadlessOptions)}}
17
+ * ← {"id":2,"ok":true,"result":{"exitCode":0}}
18
+ * → {"id":3,"method":"session.dispose","params":{"sessionId":"…"}}
19
+ * ← {"id":3,"ok":true,"result":{"disposed":true}}
20
+ * → {"id":4,"method":"session.steer","params":{"sessionId":"…","text":"…",
21
+ * "controlId":"s1"}} (t32, protocol v2 unchanged)
22
+ * ← {"id":4,"ok":true,"result":{"accepted":true,"controlId":"s1",…}}
23
+ * ← {"type":"control_accepted","controlId":"s1","controlType":"steer",…}
24
+ * (unsolicited §24 acks ride the same stdout; `control_applied`
25
+ * arrives later, when the turn's SteeringObserver drains the queue)
26
+ * → {"id":5,"method":"session.cancel","params":{"sessionId":"…","reason":"…"}}
27
+ * ← {"id":5,"ok":true,"result":{"accepted":true,"delivered":true,…}}
28
+ * steer/cancel on a session with no running turn → ok:true with
29
+ * {"accepted":false,"outcome":"already_finished"} (explicit noop, no crash)
30
+ * unknown method → {"id":n,"ok":false,"error":{"code":"unknown_method",…}}
31
+ * unknown session → {"id":n,"ok":false,"error":{"code":"unknown_session",…}}
32
+ * malformed line → {"id":null,"ok":false,"error":{"code":"bad_json",…}}
33
+ * (the server process NEVER exits on bad input)
34
+ *
35
+ * Error codes: bad_json · bad_request · unknown_method · unknown_session ·
36
+ * method_failed. Malformed JSON and handler failures are contained — the
37
+ * process keeps serving the next line.
38
+ *
39
+ * Ownership (the whole point of Pilastro B): the SERVER owns per-workspace
40
+ * services (one LspManager + policy cache per resolved workspace root —
41
+ * a second session on the same workspace does NOT respawn them) and the
42
+ * completion-proof persistence (a client disconnect never cancels an
43
+ * in-flight/queued proof write). The in-process stdin control plane is
44
+ * suppressed in this mode (ZELARI_SERVE_HARNESS=1) because the transport
45
+ * owns stdin. `--headless` remains the in-process CI client of the same
46
+ * kernel via runOneTurn — no fork, same code path.
47
+ */
48
+ import { createInterface } from 'node:readline';
49
+ import { randomUUID } from 'node:crypto';
50
+ import { HarnessAppServer } from '@zelari/core/harness';
51
+ import { controlAcceptedEvent, controlAppliedEvent, protocolInfoEvent } from '../headless/protocol.js';
52
+ import { runOneTurn } from '../headless/runOneTurn.js';
53
+ import { clearSessionTurnControl, getLiveTurnControl, runWithSession, } from './sessionControl.js';
54
+ import { resolveHeadlessKey, resolveHeadlessProvider } from '../headless.js';
55
+ import { LspManager } from '../lsp/manager.js';
56
+ import { writeCompletionProofDetailed } from '../kraken/completionProof.js';
57
+ import { setActiveProofPersistenceSurface } from '../kraken/completionProofPersist.js';
58
+ import { checkStrictPolicyLoad } from '../headless/policyGate.js';
59
+ import { activePolicyLoadMode, setActivePolicyLoadSurface } from '../safety/policyLoadMode.js';
60
+ /**
61
+ * Real per-workspace services: one LspManager anchored to the workspace,
62
+ * the strict policy-load snapshot (cached per root by the kernel), and the
63
+ * durable completion-proof writer (atomic tmp→fsync→rename, CLI-owned).
64
+ */
65
+ export function createCliWorkspaceServices(workspaceRoot) {
66
+ const mode = activePolicyLoadMode();
67
+ const policyLoad = checkStrictPolicyLoad(workspaceRoot, { mode });
68
+ // Extra host fields ride along; the kernel sees the PolicyCacheLike shape.
69
+ const policyCache = {
70
+ workspaceRoot,
71
+ loadedAt: Date.now(),
72
+ mode,
73
+ blocked: policyLoad.blocked,
74
+ warnings: policyLoad.warnings,
75
+ };
76
+ return {
77
+ lspManager: new LspManager({ cwd: workspaceRoot }),
78
+ policyCache,
79
+ completionProofWriter: async (request) => {
80
+ await writeCompletionProofDetailed(request.payload, {
81
+ baseDir: request.baseDir,
82
+ meta: { surface: request.surface, sessionId: request.sessionId },
83
+ });
84
+ },
85
+ };
86
+ }
87
+ /**
88
+ * Real turn implementation: provider/key/stream resolution happens ONCE
89
+ * per server (mirroring runHeadless: one process, one key), then each
90
+ * run.turn is a runOneTurn call — the exact `--headless` code path.
91
+ */
92
+ export function createCliRunTurn() {
93
+ let streamPromise = null;
94
+ const ensureStream = () => {
95
+ if (!streamPromise) {
96
+ streamPromise = (async () => {
97
+ const { provider, model } = resolveHeadlessProvider({});
98
+ const key = await resolveHeadlessKey(provider);
99
+ if ('error' in key)
100
+ throw new Error(key.error);
101
+ const { buildProviderStream } = await import('../provider/resolveStream.js');
102
+ const stream = buildProviderStream({
103
+ providerId: provider,
104
+ apiKey: key.apiKey,
105
+ baseUrl: key.baseUrl,
106
+ model,
107
+ });
108
+ return { provider, model, stream };
109
+ })();
110
+ streamPromise.catch(() => {
111
+ streamPromise = null; // allow a retry after the key materializes
112
+ });
113
+ }
114
+ return streamPromise;
115
+ };
116
+ return async (input, deps) => {
117
+ const { provider, model, stream } = await ensureStream();
118
+ // Transport-boundary validation: a headless single turn needs a task.
119
+ // Everything else arrives as untrusted JSON, so the payload is cast
120
+ // once (unknown → Omit) and the required/forced fields are set here.
121
+ const turnInput = input;
122
+ if (typeof turnInput.task !== 'string' || turnInput.task.length === 0) {
123
+ throw new Error('run.turn requires a non-empty string `task`');
124
+ }
125
+ const opts = {
126
+ ...input,
127
+ task: turnInput.task,
128
+ mode: (typeof turnInput.mode === 'string'
129
+ ? turnInput.mode
130
+ : 'kraken'),
131
+ output: 'json', // NDJSON transport: BrainEvents ride the same stdout
132
+ };
133
+ const exitCode = await runOneTurn(opts, provider, model, stream);
134
+ void deps.services.policyCache.workspaceRoot;
135
+ return { exitCode };
136
+ };
137
+ }
138
+ /** Typed unknown_session envelope (shared by the session-scoped methods). */
139
+ function unknownSessionEnvelope(id, rawSessionId) {
140
+ return {
141
+ id,
142
+ ok: false,
143
+ error: { code: 'unknown_session', message: `no session '${String(rawSessionId ?? '')}'` },
144
+ };
145
+ }
146
+ /**
147
+ * Boot the kernel + NDJSON transport. Injectable I/O keeps this testable
148
+ * without spawning a process; `runHarnessServer()` below is the stdio entry.
149
+ */
150
+ export function startHarnessServer(options = {}) {
151
+ const io = options.io ?? { input: process.stdin, output: process.stdout };
152
+ const server = new HarnessAppServer({
153
+ createWorkspaceServices: options.createWorkspaceServices ?? createCliWorkspaceServices,
154
+ });
155
+ const write = (line) => {
156
+ try {
157
+ io.output.write(line + '\n');
158
+ }
159
+ catch {
160
+ /* transport is gone (EPIPE) — the kernel outlives the client */
161
+ }
162
+ };
163
+ const respond = (envelope) => {
164
+ write(JSON.stringify(envelope));
165
+ };
166
+ const dispatch = async (req) => {
167
+ if (typeof req.method !== 'string') {
168
+ return { id: req.id ?? null, ok: false, error: { code: 'bad_request', message: 'missing method' } };
169
+ }
170
+ const params = req.params ?? {};
171
+ switch (req.method) {
172
+ case 'session.create': {
173
+ const root = typeof params.workspaceRoot === 'string' ? params.workspaceRoot : process.cwd();
174
+ const session = server.createSession({ workspaceRoot: root, runTurn: options.runTurn ?? createCliRunTurn() });
175
+ return { id: req.id ?? null, ok: true, result: { sessionId: session.id, workspaceRoot: session.workspaceRoot } };
176
+ }
177
+ case 'run.turn': {
178
+ const session = server.getSession(String(params.sessionId ?? ''));
179
+ if (!session) {
180
+ return { id: req.id ?? null, ok: false, error: { code: 'unknown_session', message: `no session '${String(params.sessionId ?? '')}'` } };
181
+ }
182
+ const { sessionId: _ignored, ...turnInput } = params;
183
+ // t32: carry the session id into runOneTurn's control-plane
184
+ // registration via async context (same call-chain, no new
185
+ // HeadlessOptions field). Settlement cleanup drops the registration
186
+ // even on a crash path, so later steers get the honest
187
+ // already_finished noop instead of targeting a dead queue.
188
+ const result = await runWithSession(session.id, async () => {
189
+ try {
190
+ return await session.runTurn(turnInput);
191
+ }
192
+ finally {
193
+ clearSessionTurnControl(session.id);
194
+ }
195
+ });
196
+ return { id: req.id ?? null, ok: true, result };
197
+ }
198
+ case 'session.dispose': {
199
+ const session = server.getSession(String(params.sessionId ?? ''));
200
+ if (!session) {
201
+ return { id: req.id ?? null, ok: false, error: { code: 'unknown_session', message: `no session '${String(params.sessionId ?? '')}'` } };
202
+ }
203
+ await session.dispose();
204
+ return { id: req.id ?? null, ok: true, result: { disposed: true } };
205
+ }
206
+ // t32: session-scoped steer/cancel over the SAME protocol v2 — no new
207
+ // protocol version, same §24 ack events the stdin bridge emits.
208
+ case 'session.steer':
209
+ case 'session.cancel': {
210
+ const isCancel = req.method === 'session.cancel';
211
+ const session = server.getSession(String(params.sessionId ?? ''));
212
+ if (!session) {
213
+ return unknownSessionEnvelope(req.id ?? null, params.sessionId);
214
+ }
215
+ const controlId = typeof params.controlId === 'string' && params.controlId.length > 0
216
+ ? params.controlId
217
+ : `ctrl-${randomUUID()}`;
218
+ const controlType = isCancel ? 'cancel' : 'steer';
219
+ // Shape validation BEFORE state checks: a malformed control is a
220
+ // bad_request regardless of session/turn state (same shape rule as
221
+ // the stdin control reader, controlReader.ts).
222
+ const text = params.text;
223
+ if (!isCancel && (typeof text !== 'string' || text.trim().length === 0)) {
224
+ return {
225
+ id: req.id ?? null,
226
+ ok: false,
227
+ error: { code: 'bad_request', message: 'session.steer requires a non-empty string `text`' },
228
+ };
229
+ }
230
+ const live = getLiveTurnControl(session.id);
231
+ if (!live) {
232
+ // Known session, but no turn is running (never started, already
233
+ // completed or cancelled): EXPLICIT noop result — never a crash,
234
+ // never a fake acceptance (§24: never pretend a control applied).
235
+ return {
236
+ id: req.id ?? null,
237
+ ok: true,
238
+ result: { accepted: false, outcome: 'already_finished', controlId, controlType },
239
+ };
240
+ }
241
+ if (isCancel) {
242
+ // Nothing drains cancel events mid-run, so application goes
243
+ // through the cooperative harness cancel hook directly; the queue
244
+ // never holds an undrainable cancel.
245
+ const delivered = live.cancel();
246
+ if (delivered) {
247
+ write(JSON.stringify(controlAppliedEvent(controlId, 'cancel', 'cancel')));
248
+ }
249
+ return {
250
+ id: req.id ?? null,
251
+ ok: true,
252
+ result: { accepted: true, delivered, controlId, controlType },
253
+ };
254
+ }
255
+ // §24: enqueue = accepted; the control_applied ack (boundary
256
+ // turn-end) is emitted by the turn when SteeringObserver drains.
257
+ live.queue.enqueue({ type: 'steer', id: controlId, text: text, ts: Date.now() });
258
+ write(JSON.stringify(controlAcceptedEvent(controlId, 'steer')));
259
+ return {
260
+ id: req.id ?? null,
261
+ ok: true,
262
+ result: { accepted: true, controlId, controlType, boundary: 'turn-end' },
263
+ };
264
+ }
265
+ default:
266
+ return {
267
+ id: req.id ?? null,
268
+ ok: false,
269
+ error: { code: 'unknown_method', message: `unsupported method '${req.method}'` },
270
+ };
271
+ }
272
+ };
273
+ const rl = createInterface({ input: io.input });
274
+ // Boot handshake — same protocol_info envelope/version as `--headless`.
275
+ write(JSON.stringify(protocolInfoEvent()));
276
+ rl.on('line', (line) => {
277
+ const trimmed = line.trim();
278
+ if (trimmed === '')
279
+ return;
280
+ let req;
281
+ try {
282
+ req = JSON.parse(trimmed);
283
+ if (req === null || typeof req !== 'object')
284
+ throw new Error('not an object');
285
+ }
286
+ catch (err) {
287
+ respond({
288
+ id: null,
289
+ ok: false,
290
+ error: {
291
+ code: 'bad_json',
292
+ message: `malformed JSON line skipped: ${err instanceof Error ? err.message : String(err)}`,
293
+ },
294
+ });
295
+ return; // server keeps running (DoD e)
296
+ }
297
+ void dispatch(req).then((envelope) => respond(envelope), (err) => respond({
298
+ id: (req.id ?? null),
299
+ ok: false,
300
+ error: {
301
+ code: 'method_failed',
302
+ message: err instanceof Error ? err.message : String(err),
303
+ },
304
+ }));
305
+ });
306
+ return {
307
+ server,
308
+ close() {
309
+ // Kernel teardown FIRST-class: pending completion-proof writes are
310
+ // awaited (never cancelled) before services die.
311
+ rl.close();
312
+ io.input.pause();
313
+ return server.dispose();
314
+ },
315
+ };
316
+ }
317
+ /** Process entry for `--serve-harness`: stdio transport + host pre-flight. */
318
+ export async function runHarnessServer() {
319
+ // Same host registration runHeadless performs before any registry exists.
320
+ setActivePolicyLoadSurface('headless');
321
+ setActiveProofPersistenceSurface('headless');
322
+ // runOneTurn checks this to keep its paws off stdin (transport-owned).
323
+ process.env.ZELARI_SERVE_HARNESS = '1';
324
+ const started = startHarnessServer();
325
+ await new Promise((resolvePromise) => {
326
+ const finish = () => void started.close().then(() => resolvePromise());
327
+ process.stdin.once('end', finish);
328
+ process.stdin.once('close', finish);
329
+ process.once('SIGINT', finish);
330
+ process.once('SIGTERM', finish);
331
+ });
332
+ }
333
+ //# sourceMappingURL=harnessServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harnessServer.js","sourceRoot":"","sources":["../../../src/cli/serve/harnessServer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Session-scoped control plane for the harness App Server (t32, Pilastro B
3
+ * residuo).
4
+ *
5
+ * In `--serve-harness` mode the NDJSON transport owns stdin, so the headless
6
+ * stdin control plane (controlBridge.ts) cannot serve per-session
7
+ * steer/cancel. Instead:
8
+ * - the server's `run.turn` dispatch carries the harness sessionId down the
9
+ * turn's async call-chain via AsyncLocalStorage (no new HeadlessOptions
10
+ * field, CLI/protocol surface unchanged, concurrent sessions isolated);
11
+ * - runOneTurn (serve mode ONLY) registers its per-turn RuntimeControlQueue
12
+ * + cooperative cancel hook under that session id;
13
+ * - the server answers `session.steer` / `session.cancel` NDJSON methods by
14
+ * targeting the live registration, with protocol-v2 acks (§24:
15
+ * accepted ≠ applied — `control_applied` still fires from the queue drain
16
+ * / the cancel hook, never fabricated by this module).
17
+ *
18
+ * Plain `--headless` never dispatches through runWithSession, so
19
+ * registerLiveTurnControl returns undefined there and behavior is unchanged.
20
+ */
21
+ import { AsyncLocalStorage } from 'node:async_hooks';
22
+ const dispatchContext = new AsyncLocalStorage();
23
+ /** Live registry: harness sessionId → its running turn control (if any). */
24
+ const liveTurns = new Map();
25
+ /** Run `fn` inside a session dispatch context (server → runOneTurn). */
26
+ export function runWithSession(sessionId, fn) {
27
+ return dispatchContext.run({ sessionId, token: {} }, fn);
28
+ }
29
+ /**
30
+ * runOneTurn (serve mode) registers the per-turn control surface. Returns
31
+ * the unregister fn (identity-guarded, safe across back-to-back turns on
32
+ * the same session), or undefined when NOT dispatched by the harness server
33
+ * — plain `--headless` keeps its stdin control plane, zero behavior change.
34
+ */
35
+ export function registerLiveTurnControl(control) {
36
+ const store = dispatchContext.getStore();
37
+ if (!store)
38
+ return undefined;
39
+ const registered = { ...control, token: store.token };
40
+ liveTurns.set(store.sessionId, registered);
41
+ return () => {
42
+ if (liveTurns.get(store.sessionId) === registered) {
43
+ liveTurns.delete(store.sessionId);
44
+ }
45
+ };
46
+ }
47
+ /** The live turn control for a session, when one is running. */
48
+ export function getLiveTurnControl(sessionId) {
49
+ return liveTurns.get(sessionId);
50
+ }
51
+ /**
52
+ * Server-side settlement cleanup (called inside runWithSession, after the
53
+ * turn promise settles). Drops the registration of THIS dispatch only —
54
+ * token-matched, so a concurrent newer turn on the same session survives.
55
+ * Covers the crash path where the turn impl never unregistered, keeping the
56
+ * `already_finished` noop honest instead of steering a dead queue.
57
+ */
58
+ export function clearSessionTurnControl(sessionId) {
59
+ const store = dispatchContext.getStore();
60
+ if (!store || store.sessionId !== sessionId)
61
+ return;
62
+ const registered = liveTurns.get(sessionId);
63
+ if (registered && registered.token === store.token) {
64
+ liveTurns.delete(sessionId);
65
+ }
66
+ }
67
+ //# sourceMappingURL=sessionControl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionControl.js","sourceRoot":"","sources":["../../../src/cli/serve/sessionControl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAoBrD,MAAM,eAAe,GAAG,IAAI,iBAAiB,EAAwC,CAAC;AAEtF,4EAA4E;AAC5E,MAAM,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;AAEpD,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAAI,SAAiB,EAAE,EAAW;IAC9D,OAAO,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,EAAY,EAAE,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAwB;IAExB,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,UAAU,GAAmB,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IACtE,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IAC3C,OAAO,GAAG,EAAE;QACV,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,UAAU,EAAE,CAAC;YAClD,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,kBAAkB,CAChC,SAAiB;IAEjB,OAAO,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,SAAiB;IACvD,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,CAAC;IACzC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO;IACpD,MAAM,UAAU,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,UAAU,IAAI,UAAU,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,EAAE,CAAC;QACnD,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC"}