@parall/codex-agent 1.58.2 → 1.60.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/app-server-protocol.d.ts +13 -0
- package/dist/app-server-protocol.d.ts.map +1 -1
- package/dist/app-server-protocol.js +21 -0
- package/dist/compact.d.ts +33 -0
- package/dist/compact.d.ts.map +1 -0
- package/dist/compact.js +92 -0
- package/dist/dispatch.d.ts +37 -5
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +124 -61
- package/dist/foreign-threads.d.ts +49 -0
- package/dist/foreign-threads.d.ts.map +1 -0
- package/dist/foreign-threads.js +143 -0
- package/dist/index.js +12 -2
- package/dist/injection-registry.d.ts +39 -0
- package/dist/injection-registry.d.ts.map +1 -0
- package/dist/injection-registry.js +103 -0
- package/dist/instructions-refresh.d.ts +25 -0
- package/dist/instructions-refresh.d.ts.map +1 -1
- package/dist/instructions-refresh.js +79 -19
- package/dist/runtime-turn.d.ts +23 -0
- package/dist/runtime-turn.d.ts.map +1 -0
- package/dist/runtime-turn.js +56 -0
- package/dist/session-manager.d.ts +2 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +7 -0
- package/dist/turn-sink.d.ts +5 -3
- package/dist/turn-sink.d.ts.map +1 -1
- package/dist/turn-sink.js +12 -31
- package/package.json +4 -4
- package/src/app-server-protocol.ts +37 -0
- package/src/compact.ts +132 -0
- package/src/dispatch.ts +149 -64
- package/src/foreign-threads.ts +187 -0
- package/src/index.ts +12 -1
- package/src/injection-registry.ts +100 -0
- package/src/instructions-refresh.ts +90 -18
- package/src/runtime-turn.ts +64 -0
- package/src/session-manager.ts +6 -0
- package/src/turn-sink.ts +12 -29
|
@@ -15,5 +15,18 @@ export type CodexTurnInput = {
|
|
|
15
15
|
export declare function buildTurnInput(body: string, images: PreparedLocalImage[]): CodexTurnInput[];
|
|
16
16
|
export declare function extractThreadId(result: unknown): string | undefined;
|
|
17
17
|
export declare function extractTurnId(result: unknown): string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* The `thread` object a `thread/started` notification carries. Subagent
|
|
20
|
+
* threads (`spawn_agent`) report `threadSource: "subAgent"` plus the parent
|
|
21
|
+
* thread and the agent's nickname/role; other fields are tolerated absent.
|
|
22
|
+
*/
|
|
23
|
+
export type CodexThreadInfo = {
|
|
24
|
+
id: string;
|
|
25
|
+
parentThreadId?: string;
|
|
26
|
+
source?: string;
|
|
27
|
+
nickname?: string;
|
|
28
|
+
role?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function extractThreadInfo(params: unknown): CodexThreadInfo | undefined;
|
|
18
31
|
export declare function extractThreadIdFromNotification(params: unknown): string | undefined;
|
|
19
32
|
//# sourceMappingURL=app-server-protocol.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-server-protocol.d.ts","sourceRoot":"","sources":["../src/app-server-protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAEvF;;;;;GAKG;AAEH,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnG,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,cAAc,EAAE,CAK3F;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOnE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOjE;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CASnF"}
|
|
1
|
+
{"version":3,"file":"app-server-protocol.d.ts","sourceRoot":"","sources":["../src/app-server-protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8CAA8C,CAAC;AAEvF;;;;;GAKG;AAEH,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnG,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,cAAc,EAAE,CAK3F;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOnE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOjE;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAsB9E;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CASnF"}
|
|
@@ -26,6 +26,27 @@ export function extractTurnId(result) {
|
|
|
26
26
|
return turn.id;
|
|
27
27
|
return undefined;
|
|
28
28
|
}
|
|
29
|
+
export function extractThreadInfo(params) {
|
|
30
|
+
if (!params || typeof params !== 'object')
|
|
31
|
+
return undefined;
|
|
32
|
+
const p = params;
|
|
33
|
+
const thread = (p.thread && typeof p.thread === 'object' ? p.thread : p);
|
|
34
|
+
const id = typeof thread.id === 'string'
|
|
35
|
+
? thread.id
|
|
36
|
+
: typeof p.threadId === 'string'
|
|
37
|
+
? p.threadId
|
|
38
|
+
: undefined;
|
|
39
|
+
if (!id)
|
|
40
|
+
return undefined;
|
|
41
|
+
const str = (value) => (typeof value === 'string' && value.trim() ? value : undefined);
|
|
42
|
+
return {
|
|
43
|
+
id,
|
|
44
|
+
parentThreadId: str(thread.parentThreadId),
|
|
45
|
+
source: str(thread.threadSource) ?? str(thread.source),
|
|
46
|
+
nickname: str(thread.agentNickname),
|
|
47
|
+
role: str(thread.agentRole),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
29
50
|
export function extractThreadIdFromNotification(params) {
|
|
30
51
|
if (!params || typeof params !== 'object')
|
|
31
52
|
return undefined;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CompactOpts, CompactResult, GatewayLogger } from '@parall/agent-core';
|
|
2
|
+
import type { OpenDispatchOutcome } from './dispatch.js';
|
|
3
|
+
import { type MainThreadInstructionsRefresher, type NotificationTapSource } from './instructions-refresh.js';
|
|
4
|
+
/**
|
|
5
|
+
* Idle auto-compact on the Codex bridge (idle-auto-compact-design.md §3.4):
|
|
6
|
+
* open the MAIN thread exactly the way a dispatch would (start/resume + the
|
|
7
|
+
* instructions reconcile), then run the unconditional compaction primitive
|
|
8
|
+
* as a tap-only turn — no TurnSink, no activeTurnIds entry, so it is neither
|
|
9
|
+
* a user turn nor a steer target (PFB-84 bookkeeping untouched). Outcomes
|
|
10
|
+
* follow the refresh path's posture: a stalled compaction quarantines the
|
|
11
|
+
* main lane and bounces the subprocess; -32601 latches unsupported for the
|
|
12
|
+
* subprocess lifetime.
|
|
13
|
+
*
|
|
14
|
+
* The host is the narrow slice of CodexAppServerAdapter this needs; the
|
|
15
|
+
* adapter's `compact()` is a thin delegate.
|
|
16
|
+
*/
|
|
17
|
+
export interface CodexCompactHost {
|
|
18
|
+
log?: GatewayLogger;
|
|
19
|
+
taps: NotificationTapSource;
|
|
20
|
+
refresher: Pick<MainThreadInstructionsRefresher, 'isCompactUnsupported' | 'runCompaction' | 'recordCompacted'>;
|
|
21
|
+
isMainSession(sessionKey: string): boolean;
|
|
22
|
+
isMainLaneQuarantined(): boolean;
|
|
23
|
+
/** Quarantine the main lane and request a subprocess restart (applied when free). */
|
|
24
|
+
quarantineAndBounce(log?: GatewayLogger): Promise<void>;
|
|
25
|
+
applyPendingRestart(log?: GatewayLogger): Promise<void>;
|
|
26
|
+
/** Open the main thread with restart gating (openingDispatches) held. */
|
|
27
|
+
openMainThread(sessionKey: string, log?: GatewayLogger): Promise<OpenDispatchOutcome>;
|
|
28
|
+
hasActiveTurn(threadId: string): boolean;
|
|
29
|
+
/** Run `fn` as tap-only work on the thread: restart-gated, no unrouted-notification warnings. */
|
|
30
|
+
withTapOnlyTurn<T>(threadId: string, fn: () => Promise<T>): Promise<T>;
|
|
31
|
+
}
|
|
32
|
+
export declare function runCodexCompact(host: CodexCompactHost, { sessionKey, signal, log }: CompactOpts): Promise<CompactResult>;
|
|
33
|
+
//# sourceMappingURL=compact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compact.d.ts","sourceRoot":"","sources":["../src/compact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAEL,KAAK,+BAA+B,EACpC,KAAK,qBAAqB,EAC3B,MAAM,2BAA2B,CAAC;AAGnC;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,SAAS,EAAE,IAAI,CACb,+BAA+B,EAC/B,sBAAsB,GAAG,eAAe,GAAG,iBAAiB,CAC7D,CAAC;IACF,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3C,qBAAqB,IAAI,OAAO,CAAC;IACjC,qFAAqF;IACrF,mBAAmB,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,mBAAmB,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,yEAAyE;IACzE,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACtF,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACzC,iGAAiG;IACjG,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACxE;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,gBAAgB,EACtB,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE,WAAW,GACvC,OAAO,CAAC,aAAa,CAAC,CAkFxB"}
|
package/dist/compact.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { CompactionStalledError, } from './instructions-refresh.js';
|
|
2
|
+
import { JSON_RPC_METHOD_NOT_FOUND, JsonRpcError } from './jsonrpc-client.js';
|
|
3
|
+
export async function runCodexCompact(host, { sessionKey, signal, log }) {
|
|
4
|
+
const logger = log ?? host.log;
|
|
5
|
+
if (!host.isMainSession(sessionKey)) {
|
|
6
|
+
return { status: 'unsupported', detail: 'compact is only supported on the main session' };
|
|
7
|
+
}
|
|
8
|
+
if (signal.aborted)
|
|
9
|
+
return { status: 'timeout' };
|
|
10
|
+
if (host.isMainLaneQuarantined()) {
|
|
11
|
+
await host.applyPendingRestart(logger);
|
|
12
|
+
if (host.isMainLaneQuarantined()) {
|
|
13
|
+
return {
|
|
14
|
+
status: 'failed',
|
|
15
|
+
detail: 'main lane quarantined after a stalled compaction; the subprocess restart is still deferred behind an active turn',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (host.refresher.isCompactUnsupported()) {
|
|
20
|
+
return {
|
|
21
|
+
status: 'unsupported',
|
|
22
|
+
detail: 'thread/compact/start not supported by this codex CLI',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
await host.applyPendingRestart(logger);
|
|
26
|
+
const opened = await host.openMainThread(sessionKey, logger);
|
|
27
|
+
if (!opened.ok)
|
|
28
|
+
return { status: 'failed', detail: opened.message };
|
|
29
|
+
if (opened.reconcile === 'stalled') {
|
|
30
|
+
// Same posture as dispatch(): the refresh compaction may still be
|
|
31
|
+
// running on the thread — never rotate it; quarantine and bounce.
|
|
32
|
+
await host.quarantineAndBounce(logger);
|
|
33
|
+
return {
|
|
34
|
+
status: 'timeout',
|
|
35
|
+
detail: 'instructions-refresh compaction stalled while opening the thread; subprocess bounced',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (opened.reconcile === 'unsupported') {
|
|
39
|
+
return {
|
|
40
|
+
status: 'unsupported',
|
|
41
|
+
detail: 'thread/compact/start not supported by this codex CLI',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (opened.reconcile === 'refreshed') {
|
|
45
|
+
// The open just ran a compaction of this very thread (instructions
|
|
46
|
+
// refresh) — the history is already folded; a second one is waste.
|
|
47
|
+
return { status: 'done', detail: 'compacted by the instructions refresh during open' };
|
|
48
|
+
}
|
|
49
|
+
if (signal.aborted) {
|
|
50
|
+
return { status: 'timeout', detail: 'budget elapsed while opening the thread' };
|
|
51
|
+
}
|
|
52
|
+
const { client, threadId } = opened;
|
|
53
|
+
if (host.hasActiveTurn(threadId)) {
|
|
54
|
+
return { status: 'failed', detail: 'a turn is active on the main thread' };
|
|
55
|
+
}
|
|
56
|
+
let stalled = false;
|
|
57
|
+
try {
|
|
58
|
+
return await host.withTapOnlyTurn(threadId, async () => {
|
|
59
|
+
try {
|
|
60
|
+
await host.refresher.runCompaction({ client, taps: host.taps, threadId, signal });
|
|
61
|
+
host.refresher.recordCompacted(sessionKey, threadId);
|
|
62
|
+
return { status: 'done' };
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err instanceof CompactionStalledError) {
|
|
66
|
+
// Budget elapsed (the gateway's abort or the refresher's own
|
|
67
|
+
// ceiling) and the turn ignored the interrupt: the compaction is
|
|
68
|
+
// torn down with the subprocess below.
|
|
69
|
+
stalled = true;
|
|
70
|
+
return { status: 'timeout', detail: errToString(err) };
|
|
71
|
+
}
|
|
72
|
+
if (err instanceof JsonRpcError && err.code === JSON_RPC_METHOD_NOT_FOUND) {
|
|
73
|
+
return { status: 'unsupported', detail: errToString(err) };
|
|
74
|
+
}
|
|
75
|
+
return { status: 'failed', detail: errToString(err) };
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
if (stalled) {
|
|
81
|
+
// The compaction turn may still be running: quarantine the main lane
|
|
82
|
+
// and bounce the subprocess (deferred behind an active fork turn,
|
|
83
|
+
// retried by the next main dispatch) — exactly the dispatch posture.
|
|
84
|
+
await host.quarantineAndBounce(logger);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function errToString(err) {
|
|
89
|
+
if (err instanceof Error)
|
|
90
|
+
return err.message;
|
|
91
|
+
return String(err);
|
|
92
|
+
}
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { CleanupForkOpts, DispatchAdapter, DispatchOpts, ForkOpts, ForkSessionHandle, GatewayLogger, RuntimeEvent } from '@parall/agent-core';
|
|
1
|
+
import type { CleanupForkOpts, CompactOpts, CompactResult, DispatchAdapter, DispatchInputLifecycle, DispatchOpts, ForkOpts, ForkSessionHandle, GatewayLogger, RuntimeActivityEvent, RuntimeBusyState, RuntimeEvent } from '@parall/agent-core';
|
|
2
2
|
import type { CodexAgentConfig } from './config.js';
|
|
3
|
-
import { type NotificationTap } from './instructions-refresh.js';
|
|
3
|
+
import { type NotificationTap, type RefreshOutcome } from './instructions-refresh.js';
|
|
4
|
+
import { JsonRpcStdioClient } from './jsonrpc-client.js';
|
|
4
5
|
import type { CodexSessionManager } from './session-manager.js';
|
|
5
6
|
type CodexAppServerAdapterOptions = Pick<CodexAgentConfig, 'approvalPolicy' | 'codexBin' | 'codexHome' | 'model' | 'reasoningEffort' | 'sandbox' | 'workspaceDir'> & {
|
|
6
7
|
sessionManager: CodexSessionManager;
|
|
@@ -62,7 +63,7 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
62
63
|
private startPromise;
|
|
63
64
|
private readonly activeTurns;
|
|
64
65
|
private readonly activeTurnIds;
|
|
65
|
-
private readonly
|
|
66
|
+
private readonly injections;
|
|
66
67
|
private readonly resumedThreadIds;
|
|
67
68
|
/** Threads whose reconcile compaction is in flight — tap-only traffic. */
|
|
68
69
|
private readonly reconcilingThreadIds;
|
|
@@ -80,6 +81,9 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
80
81
|
private readonly instructionsRefresher;
|
|
81
82
|
private stopping;
|
|
82
83
|
private lastUnroutedNotificationWarnAt;
|
|
84
|
+
/** Subagent threads and their runtime-initiated turns (foreign-threads.ts). */
|
|
85
|
+
private readonly foreignThreads;
|
|
86
|
+
private readonly activity;
|
|
83
87
|
/**
|
|
84
88
|
* Store an active turn sink keyed by threadId. If a sink already exists for
|
|
85
89
|
* the same threadId, log a warning and fail the existing sink — this
|
|
@@ -105,10 +109,29 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
105
109
|
developerInstructions?: string | null;
|
|
106
110
|
}): void;
|
|
107
111
|
private threadConfigOverrides;
|
|
108
|
-
enqueueDuringDispatch(sessionKey: string, body: string): Promise<boolean>;
|
|
112
|
+
enqueueDuringDispatch(sessionKey: string, body: string, inputLifecycle?: DispatchInputLifecycle): Promise<boolean>;
|
|
109
113
|
abortDispatch(sessionKey: string): void;
|
|
114
|
+
/** Idle auto-compact (compact.ts): the compaction primitive as a tap-only main-thread turn. */
|
|
115
|
+
compact(opts: CompactOpts): Promise<CompactResult>;
|
|
116
|
+
/**
|
|
117
|
+
* Real work on the subprocess that predates any TurnSink (thread open,
|
|
118
|
+
* a tap-only compaction turn): counted so applyPendingRestart cannot
|
|
119
|
+
* stop() the subprocess out from under it.
|
|
120
|
+
*/
|
|
121
|
+
private withOpening;
|
|
110
122
|
hasPendingInjections(sessionKey: string): boolean;
|
|
111
|
-
|
|
123
|
+
hasUnsettledInjections(sessionKey: string): boolean;
|
|
124
|
+
acknowledgeDiscardedInjection(sessionKey: string, deliveryKey: string): void;
|
|
125
|
+
subscribeRuntimeActivity(handler: (event: RuntimeActivityEvent) => void): () => void;
|
|
126
|
+
/**
|
|
127
|
+
* Busy = a dispatch turn, a dispatch opening its thread (possibly running
|
|
128
|
+
* the instructions-refresh compaction turn), or a subagent thread's turn
|
|
129
|
+
* is executing. Live subagent threads between turns are reported as
|
|
130
|
+
* background work, not busy.
|
|
131
|
+
*/
|
|
132
|
+
isBusy(): boolean;
|
|
133
|
+
busyState(): RuntimeBusyState;
|
|
134
|
+
dispatch({ event, bodyForAgent, sessionKey, context, inputLifecycle, noteActivity, }: DispatchOpts): AsyncIterable<RuntimeEvent>;
|
|
112
135
|
/**
|
|
113
136
|
* Bring the app-server up and open (start or resume) the thread for this
|
|
114
137
|
* dispatch, then reconcile the main thread's EFFECTIVE instructions plane.
|
|
@@ -150,5 +173,14 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
150
173
|
private openThread;
|
|
151
174
|
private routeNotification;
|
|
152
175
|
}
|
|
176
|
+
export type OpenDispatchOutcome = {
|
|
177
|
+
ok: true;
|
|
178
|
+
client: JsonRpcStdioClient;
|
|
179
|
+
threadId: string;
|
|
180
|
+
reconcile?: RefreshOutcome;
|
|
181
|
+
} | {
|
|
182
|
+
ok: false;
|
|
183
|
+
message: string;
|
|
184
|
+
};
|
|
153
185
|
export {};
|
|
154
186
|
//# sourceMappingURL=dispatch.d.ts.map
|
package/dist/dispatch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,YAAY,EACb,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
|
package/dist/dispatch.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
import { isRuntimeBusy, projectRuntimeEvent, RuntimeActivityPort } from '@parall/agent-core';
|
|
4
5
|
import { appendPreparedLocalAttachmentRefs, pinLocalAttachmentPaths, } from '@parall/agent-core/internal/attachment-input';
|
|
5
6
|
import { ensureGitRepo, IS_WIN32, killWin32Tree, quoteWin32Arg } from './app-server-process.js';
|
|
6
7
|
import { buildTurnInput, extractThreadId, extractThreadIdFromNotification, extractTurnId, } from './app-server-protocol.js';
|
|
7
8
|
import { normalizeApprovalPolicy, normalizeSandbox } from './config.js';
|
|
9
|
+
import { runCodexCompact } from './compact.js';
|
|
10
|
+
import { ForeignThreadRegistry } from './foreign-threads.js';
|
|
11
|
+
import { CodexInjectionRegistry } from './injection-registry.js';
|
|
8
12
|
import { MainThreadInstructionsRefresher, } from './instructions-refresh.js';
|
|
9
13
|
import { JsonRpcStdioClient } from './jsonrpc-client.js';
|
|
10
14
|
import { answerServerRequest } from './server-requests.js';
|
|
@@ -32,7 +36,7 @@ export class CodexAppServerAdapter {
|
|
|
32
36
|
startPromise = null;
|
|
33
37
|
activeTurns = new Map();
|
|
34
38
|
activeTurnIds = new Map();
|
|
35
|
-
|
|
39
|
+
injections = new CodexInjectionRegistry();
|
|
36
40
|
resumedThreadIds = new Set();
|
|
37
41
|
/** Threads whose reconcile compaction is in flight — tap-only traffic. */
|
|
38
42
|
reconcilingThreadIds = new Set();
|
|
@@ -50,6 +54,9 @@ export class CodexAppServerAdapter {
|
|
|
50
54
|
instructionsRefresher;
|
|
51
55
|
stopping = false;
|
|
52
56
|
lastUnroutedNotificationWarnAt = 0;
|
|
57
|
+
/** Subagent threads and their runtime-initiated turns (foreign-threads.ts). */
|
|
58
|
+
foreignThreads;
|
|
59
|
+
activity;
|
|
53
60
|
/**
|
|
54
61
|
* Store an active turn sink keyed by threadId. If a sink already exists for
|
|
55
62
|
* the same threadId, log a warning and fail the existing sink — this
|
|
@@ -75,6 +82,18 @@ export class CodexAppServerAdapter {
|
|
|
75
82
|
compactTimeoutMs: opts.instructionsCompactTimeoutMs,
|
|
76
83
|
interruptGraceMs: opts.instructionsInterruptGraceMs,
|
|
77
84
|
});
|
|
85
|
+
this.activity = new RuntimeActivityPort('CodexAppServerAdapter', opts.log);
|
|
86
|
+
this.foreignThreads = new ForeignThreadRegistry({
|
|
87
|
+
sessionManager: opts.sessionManager,
|
|
88
|
+
log: opts.log,
|
|
89
|
+
activity: this.activity,
|
|
90
|
+
// A restart deferred behind subagent work must not starve once it ends.
|
|
91
|
+
afterTurnClosed: () => {
|
|
92
|
+
if (!this.restartRequested)
|
|
93
|
+
return;
|
|
94
|
+
void this.applyPendingRestart().catch((err) => this.opts.log?.warn?.(`deferred restart failed: ${errToString(err)}`));
|
|
95
|
+
},
|
|
96
|
+
});
|
|
78
97
|
}
|
|
79
98
|
/** Register a listener for every server notification; returns unregister. */
|
|
80
99
|
addNotificationTap(tap) {
|
|
@@ -112,7 +131,10 @@ export class CodexAppServerAdapter {
|
|
|
112
131
|
inheritedPath: process.env.PATH,
|
|
113
132
|
});
|
|
114
133
|
}
|
|
115
|
-
async enqueueDuringDispatch(sessionKey, body) {
|
|
134
|
+
async enqueueDuringDispatch(sessionKey, body, inputLifecycle) {
|
|
135
|
+
// Identity is mandatory — the drain releases the injection by this key (injection-registry.ts).
|
|
136
|
+
if (!inputLifecycle)
|
|
137
|
+
return false;
|
|
116
138
|
const client = this.client;
|
|
117
139
|
if (!client || client.isDisposed())
|
|
118
140
|
return false;
|
|
@@ -128,7 +150,7 @@ export class CodexAppServerAdapter {
|
|
|
128
150
|
expectedTurnId: turnId,
|
|
129
151
|
input: buildTurnInput(body, []),
|
|
130
152
|
});
|
|
131
|
-
this.
|
|
153
|
+
this.injections.register(sessionKey, inputLifecycle.deliveryKey);
|
|
132
154
|
return true;
|
|
133
155
|
}
|
|
134
156
|
catch (err) {
|
|
@@ -137,7 +159,8 @@ export class CodexAppServerAdapter {
|
|
|
137
159
|
}
|
|
138
160
|
}
|
|
139
161
|
abortDispatch(sessionKey) {
|
|
140
|
-
|
|
162
|
+
// A torn-down turn owes nothing: the error complete releases its members for fresh turns.
|
|
163
|
+
this.injections.clear(sessionKey);
|
|
141
164
|
const threadId = this.opts.sessionManager.getThreadId(sessionKey);
|
|
142
165
|
if (!threadId)
|
|
143
166
|
return;
|
|
@@ -162,25 +185,82 @@ export class CodexAppServerAdapter {
|
|
|
162
185
|
sink.push({ kind: 'error', message: 'dispatch inactivity deadline exceeded' });
|
|
163
186
|
sink.close();
|
|
164
187
|
}
|
|
165
|
-
|
|
166
|
-
|
|
188
|
+
/** Idle auto-compact (compact.ts): the compaction primitive as a tap-only main-thread turn. */
|
|
189
|
+
compact(opts) {
|
|
190
|
+
return runCodexCompact({
|
|
191
|
+
log: this.opts.log,
|
|
192
|
+
taps: this,
|
|
193
|
+
refresher: this.instructionsRefresher,
|
|
194
|
+
isMainSession: (sessionKey) => this.opts.sessionManager.isMain(sessionKey),
|
|
195
|
+
isMainLaneQuarantined: () => this.mainLaneQuarantined,
|
|
196
|
+
quarantineAndBounce: async (log) => {
|
|
197
|
+
this.mainLaneQuarantined = true;
|
|
198
|
+
this.requestProcessRestart();
|
|
199
|
+
await this.applyPendingRestart(log);
|
|
200
|
+
},
|
|
201
|
+
applyPendingRestart: (log) => this.applyPendingRestart(log),
|
|
202
|
+
openMainThread: (sessionKey, log) => this.withOpening(() => this.openDispatchTarget(sessionKey, true, log)),
|
|
203
|
+
hasActiveTurn: (threadId) => this.activeTurns.has(threadId),
|
|
204
|
+
withTapOnlyTurn: async (threadId, fn) => {
|
|
205
|
+
this.reconcilingThreadIds.add(threadId);
|
|
206
|
+
try {
|
|
207
|
+
return await this.withOpening(fn);
|
|
208
|
+
}
|
|
209
|
+
finally {
|
|
210
|
+
this.reconcilingThreadIds.delete(threadId);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
}, opts);
|
|
167
214
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
runtimeSessionId: threadId,
|
|
178
|
-
runtimeLaneKey: sessionKey,
|
|
179
|
-
};
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
(this.opts.log ?? context.log)?.warn?.(`pending steer invalidated (subprocess died); falling through to normal dispatch`);
|
|
215
|
+
/**
|
|
216
|
+
* Real work on the subprocess that predates any TurnSink (thread open,
|
|
217
|
+
* a tap-only compaction turn): counted so applyPendingRestart cannot
|
|
218
|
+
* stop() the subprocess out from under it.
|
|
219
|
+
*/
|
|
220
|
+
async withOpening(fn) {
|
|
221
|
+
this.openingDispatches += 1;
|
|
222
|
+
try {
|
|
223
|
+
return await fn();
|
|
183
224
|
}
|
|
225
|
+
finally {
|
|
226
|
+
this.openingDispatches -= 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
hasPendingInjections(sessionKey) {
|
|
230
|
+
return this.injections.hasPending(sessionKey);
|
|
231
|
+
}
|
|
232
|
+
hasUnsettledInjections(sessionKey) {
|
|
233
|
+
return this.injections.hasUnsettled(sessionKey);
|
|
234
|
+
}
|
|
235
|
+
acknowledgeDiscardedInjection(sessionKey, deliveryKey) {
|
|
236
|
+
this.injections.markDrained(sessionKey, deliveryKey);
|
|
237
|
+
}
|
|
238
|
+
// --- runtime-initiated work -------------------------------------------------
|
|
239
|
+
subscribeRuntimeActivity(handler) {
|
|
240
|
+
return this.activity.subscribe(handler);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Busy = a dispatch turn, a dispatch opening its thread (possibly running
|
|
244
|
+
* the instructions-refresh compaction turn), or a subagent thread's turn
|
|
245
|
+
* is executing. Live subagent threads between turns are reported as
|
|
246
|
+
* background work, not busy.
|
|
247
|
+
*/
|
|
248
|
+
isBusy() {
|
|
249
|
+
return isRuntimeBusy(this.busyState());
|
|
250
|
+
}
|
|
251
|
+
busyState() {
|
|
252
|
+
return {
|
|
253
|
+
activeTurns: this.activeTurns.size +
|
|
254
|
+
(this.openingDispatches > 0 || this.reconcilingThreadIds.size > 0 ? 1 : 0) +
|
|
255
|
+
this.foreignThreads.openTurns(),
|
|
256
|
+
backgroundWork: this.foreignThreads.size,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async *dispatch({ event, bodyForAgent, sessionKey, context, inputLifecycle, noteActivity, }) {
|
|
260
|
+
// This dispatch drains the injected copies it lists; it ALWAYS runs its own
|
|
261
|
+
// turn — never skipped on outstanding injections (PFB-84, injection-registry.ts).
|
|
262
|
+
if (inputLifecycle)
|
|
263
|
+
this.injections.markDrained(sessionKey, inputLifecycle.deliveryKey);
|
|
184
264
|
const isMainSession = this.opts.sessionManager.isMain(sessionKey);
|
|
185
265
|
if (isMainSession && this.mainLaneQuarantined) {
|
|
186
266
|
// Try the pending restart now — it may have been deferred behind an
|
|
@@ -228,14 +308,7 @@ export class CodexAppServerAdapter {
|
|
|
228
308
|
// compaction turn BEFORE any TurnSink exists, and a concurrent fork
|
|
229
309
|
// dispatch hitting a pending restart in that window would otherwise
|
|
230
310
|
// stop() the subprocess out from under it.
|
|
231
|
-
this.
|
|
232
|
-
let opened;
|
|
233
|
-
try {
|
|
234
|
-
opened = await this.openDispatchTarget(sessionKey, isMainSession, log);
|
|
235
|
-
}
|
|
236
|
-
finally {
|
|
237
|
-
this.openingDispatches -= 1;
|
|
238
|
-
}
|
|
311
|
+
const opened = await this.withOpening(() => this.openDispatchTarget(sessionKey, isMainSession, log));
|
|
239
312
|
if (!opened.ok) {
|
|
240
313
|
yield { type: 'error', message: opened.message };
|
|
241
314
|
return;
|
|
@@ -422,40 +495,18 @@ export class CodexAppServerAdapter {
|
|
|
422
495
|
sawTurnEnd = true;
|
|
423
496
|
break;
|
|
424
497
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (runtimeEvent.type === 'error') {
|
|
431
|
-
yield runtimeEvent;
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
if (runtimeEvent.type === 'runtime_session') {
|
|
435
|
-
yield runtimeEvent;
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
if (runtimeEvent.type === 'text') {
|
|
439
|
-
// Layer 0 symmetric output contract: Codex's plain text is never
|
|
440
|
-
// projected as a chat message. Outbound messages must come from the
|
|
441
|
-
// agent explicitly invoking `@parall/cli messages send` / `dm` via
|
|
442
|
-
// the shell/exec tool. Text events are still yielded so agent-core
|
|
443
|
-
// records them as suppressed session steps for audit.
|
|
444
|
-
yield { ...runtimeEvent, project: false, groupKey };
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
if (runtimeEvent.type === 'turn_outcome') {
|
|
448
|
-
// Turn-boundary summary — no groupKey, passed through as-is.
|
|
449
|
-
yield runtimeEvent;
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
yield { ...runtimeEvent, groupKey };
|
|
498
|
+
// Layer 0 symmetric output contract (projectRuntimeEvent): Codex's
|
|
499
|
+
// plain text is never projected as a chat message — outbound
|
|
500
|
+
// messages come from the agent invoking `parall messages send` /
|
|
501
|
+
// `dm`; text is still yielded as a suppressed audit step.
|
|
502
|
+
yield projectRuntimeEvent(envelope.kind === 'error' ? { type: 'error', message: envelope.message } : envelope.event, groupKey);
|
|
453
503
|
}
|
|
454
504
|
}
|
|
455
505
|
finally {
|
|
456
506
|
releasePreparedAttachments();
|
|
457
507
|
this.activeTurns.delete(threadId);
|
|
458
508
|
this.activeTurnIds.delete(threadId);
|
|
509
|
+
this.injections.settleAll(sessionKey); // the turn consumed (or lost) everything steered into it
|
|
459
510
|
if (!sawTurnEnd) {
|
|
460
511
|
sink.close();
|
|
461
512
|
}
|
|
@@ -651,7 +702,9 @@ export class CodexAppServerAdapter {
|
|
|
651
702
|
*/
|
|
652
703
|
openingDispatches = 0;
|
|
653
704
|
async applyPendingRestart(log) {
|
|
654
|
-
|
|
705
|
+
// isBusy folds in subagent turns: a capability change must not SIGTERM
|
|
706
|
+
// the app-server under a running spawn_agent thread.
|
|
707
|
+
if (!this.restartRequested || this.isBusy())
|
|
655
708
|
return;
|
|
656
709
|
this.restartRequested = false;
|
|
657
710
|
(log ?? this.opts.log)?.info?.('restarting codex app-server after a capability change (the fresh-process thread/resume applies the refreshed developerInstructions to the persisted thread configuration; the model-visible context is reconciled before the next turn)');
|
|
@@ -665,7 +718,7 @@ export class CodexAppServerAdapter {
|
|
|
665
718
|
this.client = null;
|
|
666
719
|
this.initialized = false;
|
|
667
720
|
this.activeTurnIds.clear();
|
|
668
|
-
this.
|
|
721
|
+
this.injections.clear();
|
|
669
722
|
// Resume markers are process-local facts. Clear them HERE, synchronously:
|
|
670
723
|
// the old child's close callback sees the replaced refs and skips its
|
|
671
724
|
// cleanup as stale, so a marker surviving stop() would make the next
|
|
@@ -675,6 +728,9 @@ export class CodexAppServerAdapter {
|
|
|
675
728
|
this.instructionsRefresher.clearThreadState();
|
|
676
729
|
// The (possible) zombie compaction turn dies with the subprocess.
|
|
677
730
|
this.mainLaneQuarantined = false;
|
|
731
|
+
// Subagent threads die with the app-server: end their open turns
|
|
732
|
+
// (runtime_crash) and close their child sessions before the client goes.
|
|
733
|
+
this.foreignThreads.dropAll('lost: Codex app-server stopped');
|
|
678
734
|
this.notifyTapsDisposed('Codex app-server stopped');
|
|
679
735
|
if (client)
|
|
680
736
|
client.dispose(new Error('adapter stopped'));
|
|
@@ -692,7 +748,8 @@ export class CodexAppServerAdapter {
|
|
|
692
748
|
}
|
|
693
749
|
this.activeTurns.clear();
|
|
694
750
|
this.activeTurnIds.clear();
|
|
695
|
-
this.
|
|
751
|
+
this.injections.clear();
|
|
752
|
+
this.foreignThreads.dropAll('lost: Codex app-server disposed');
|
|
696
753
|
this.client = null;
|
|
697
754
|
this.proc = null;
|
|
698
755
|
this.initialized = false;
|
|
@@ -856,10 +913,11 @@ export class CodexAppServerAdapter {
|
|
|
856
913
|
}
|
|
857
914
|
this.activeTurns.clear();
|
|
858
915
|
this.activeTurnIds.clear();
|
|
859
|
-
this.
|
|
916
|
+
this.injections.clear();
|
|
860
917
|
this.resumedThreadIds.clear();
|
|
861
918
|
this.instructionsRefresher.clearThreadState();
|
|
862
919
|
this.mainLaneQuarantined = false;
|
|
920
|
+
this.foreignThreads.dropAll(`lost: Codex app-server ${reason}`);
|
|
863
921
|
this.notifyTapsDisposed(`Codex app-server ${reason}`);
|
|
864
922
|
this.client = null;
|
|
865
923
|
this.proc = null;
|
|
@@ -948,6 +1006,11 @@ export class CodexAppServerAdapter {
|
|
|
948
1006
|
// window that exists to surface REAL orphaned turns.
|
|
949
1007
|
if (this.reconcilingThreadIds.has(threadId))
|
|
950
1008
|
return;
|
|
1009
|
+
// Subagent threads and their turns are runtime-initiated work: routed
|
|
1010
|
+
// into their own sinks and surfaced to the gateway as child-session
|
|
1011
|
+
// turns instead of being dropped.
|
|
1012
|
+
if (this.foreignThreads.route(method, params, threadId))
|
|
1013
|
+
return;
|
|
951
1014
|
// Notifications for a thread with no registered sink are dropped. A
|
|
952
1015
|
// sustained stream of these means a turn is running that nothing is
|
|
953
1016
|
// consuming (threadId mismatch, sink torn down early) — the dispatch
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { GatewayLogger, RuntimeActivityPort } from '@parall/agent-core';
|
|
2
|
+
import { CodexRuntimeTurn } from './runtime-turn.js';
|
|
3
|
+
import type { CodexSessionManager } from './session-manager.js';
|
|
4
|
+
/**
|
|
5
|
+
* Threads the app-server runs that no dispatch opened — `spawn_agent`
|
|
6
|
+
* subagent threads (`thread/started` with `threadSource: "subAgent"`), or a
|
|
7
|
+
* thread whose turn showed up with no `thread/started` (bridge restarted
|
|
8
|
+
* mid-subagent). Each is a child session of its parent thread's session;
|
|
9
|
+
* every `turn/started`…`turn/completed` on it is a RuntimeInitiatedTurn
|
|
10
|
+
* (runtime-initiated-turns-design.md §5).
|
|
11
|
+
*/
|
|
12
|
+
export type ForeignThread = {
|
|
13
|
+
threadId: string;
|
|
14
|
+
sessionKey: string;
|
|
15
|
+
parentThreadId?: string;
|
|
16
|
+
parentSessionKey: string;
|
|
17
|
+
nickname?: string;
|
|
18
|
+
role?: string;
|
|
19
|
+
turn?: CodexRuntimeTurn;
|
|
20
|
+
};
|
|
21
|
+
export type ForeignThreadHost = {
|
|
22
|
+
sessionManager: CodexSessionManager;
|
|
23
|
+
log?: GatewayLogger;
|
|
24
|
+
/** Turns opened and child sessions closed go to the gateway through it. */
|
|
25
|
+
activity: RuntimeActivityPort;
|
|
26
|
+
/** A subagent turn ended — a deferred restart may apply now. */
|
|
27
|
+
afterTurnClosed(): void;
|
|
28
|
+
};
|
|
29
|
+
export declare class ForeignThreadRegistry {
|
|
30
|
+
private readonly host;
|
|
31
|
+
private readonly threads;
|
|
32
|
+
constructor(host: ForeignThreadHost);
|
|
33
|
+
/** Live (registered, not closed) subagent threads. */
|
|
34
|
+
get size(): number;
|
|
35
|
+
openTurns(): number;
|
|
36
|
+
/**
|
|
37
|
+
* Notifications for a thread no dispatch owns. Returns false when the
|
|
38
|
+
* caller should keep treating the frame as unroutable.
|
|
39
|
+
*/
|
|
40
|
+
route(method: string, params: unknown, threadId: string): boolean;
|
|
41
|
+
/** Subagent threads die with the app-server: end open turns, close child sessions. */
|
|
42
|
+
dropAll(reason: string): void;
|
|
43
|
+
private register;
|
|
44
|
+
private openTurn;
|
|
45
|
+
/** The thread's open turn is over: last envelope, sink closed, restart re-driven. */
|
|
46
|
+
private endTurn;
|
|
47
|
+
private close;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=foreign-threads.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foreign-threads.d.ts","sourceRoot":"","sources":["../src/foreign-threads.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,cAAc,EAAE,mBAAmB,CAAC;IACpC,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,2EAA2E;IAC3E,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,gEAAgE;IAChE,eAAe,IAAI,IAAI,CAAC;CACzB,CAAC;AAEF,qBAAa,qBAAqB;IAGpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;gBAE/B,IAAI,EAAE,iBAAiB;IAEpD,sDAAsD;IACtD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,SAAS,IAAI,MAAM;IAMnB;;;OAGG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAmDjE,sFAAsF;IACtF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAM7B,OAAO,CAAC,QAAQ;IA4BhB,OAAO,CAAC,QAAQ;IAyBhB,qFAAqF;IACrF,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,KAAK;CAYd"}
|