@parall/codex-agent 1.43.0 → 1.45.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-process.d.ts +9 -0
- package/dist/app-server-process.d.ts.map +1 -0
- package/dist/app-server-process.js +58 -0
- package/dist/app-server-protocol.d.ts +19 -0
- package/dist/app-server-protocol.d.ts.map +1 -0
- package/dist/app-server-protocol.js +42 -0
- package/dist/dispatch.d.ts +43 -5
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +28 -136
- package/dist/index.js +24 -10
- package/dist/legacy-workspace-config-migration.d.ts +112 -0
- package/dist/legacy-workspace-config-migration.d.ts.map +1 -0
- package/dist/legacy-workspace-config-migration.js +229 -0
- package/dist/turn-sink.d.ts +23 -0
- package/dist/turn-sink.d.ts.map +1 -0
- package/dist/turn-sink.js +40 -0
- package/dist/workspace.d.ts +23 -25
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +139 -138
- package/package.json +5 -5
- package/src/app-server-process.ts +59 -0
- package/src/app-server-protocol.ts +46 -0
- package/src/dispatch.ts +57 -140
- package/src/index.ts +32 -10
- package/src/legacy-workspace-config-migration.ts +296 -0
- package/src/turn-sink.ts +48 -0
- package/src/workspace.ts +156 -155
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS-level plumbing for the `codex app-server` child process: the cwd invariant
|
|
3
|
+
* it requires (a git repo), and the Windows spawn/kill quirks.
|
|
4
|
+
*/
|
|
5
|
+
export declare const IS_WIN32: boolean;
|
|
6
|
+
export declare function quoteWin32Arg(arg: string): string;
|
|
7
|
+
export declare function killWin32Tree(pid: number): boolean;
|
|
8
|
+
export declare function ensureGitRepo(workingDirectory: string): void;
|
|
9
|
+
//# sourceMappingURL=app-server-process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-server-process.d.ts","sourceRoot":"","sources":["../src/app-server-process.ts"],"names":[],"mappings":"AAIA;;;GAGG;AAEH,eAAO,MAAM,QAAQ,SAA+B,CAAC;AAErD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,wBAAgB,aAAa,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAiC5D"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { ensureLocalAttachmentGitExclude } from '@parall/agent-core/internal/attachment-input';
|
|
4
|
+
/**
|
|
5
|
+
* OS-level plumbing for the `codex app-server` child process: the cwd invariant
|
|
6
|
+
* it requires (a git repo), and the Windows spawn/kill quirks.
|
|
7
|
+
*/
|
|
8
|
+
export const IS_WIN32 = process.platform === 'win32';
|
|
9
|
+
export function quoteWin32Arg(arg) {
|
|
10
|
+
if (!/[\s"&|^<>()]/.test(arg))
|
|
11
|
+
return arg;
|
|
12
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
13
|
+
}
|
|
14
|
+
export function killWin32Tree(pid) {
|
|
15
|
+
try {
|
|
16
|
+
execSync(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: 'ignore' });
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function ensureGitRepo(workingDirectory) {
|
|
24
|
+
fs.mkdirSync(workingDirectory, { recursive: true });
|
|
25
|
+
// Only `git init` if the workspace isn't already inside any git repo. A
|
|
26
|
+
// bare existsSync(.git) check would miss the common case of a user pointing
|
|
27
|
+
// PRLL_WORKSPACE_DIR at a subdirectory of their existing project,
|
|
28
|
+
// and silently creating a nested repo there would mangle their layout.
|
|
29
|
+
//
|
|
30
|
+
// The exclude write sits inside this try, so in principle its failure would
|
|
31
|
+
// read as "not a repo" and fall through to `git init` in the user's existing
|
|
32
|
+
// repository. It cannot: the helper swallows its own errors. Deliberately left
|
|
33
|
+
// as-is rather than fixed under review — latent, not live, and out of this
|
|
34
|
+
// PR's scope: docs/tech-debt/codex-ensure-git-repo-exclude-coupling.md.
|
|
35
|
+
try {
|
|
36
|
+
execSync('git rev-parse --is-inside-work-tree', { cwd: workingDirectory, stdio: 'pipe' });
|
|
37
|
+
ensureLocalAttachmentGitExclude(workingDirectory);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Not inside a repo — fall through to init.
|
|
42
|
+
}
|
|
43
|
+
const env = {
|
|
44
|
+
...process.env,
|
|
45
|
+
GIT_AUTHOR_NAME: 'parall-codex-agent',
|
|
46
|
+
GIT_AUTHOR_EMAIL: 'agent@parall.local',
|
|
47
|
+
GIT_COMMITTER_NAME: 'parall-codex-agent',
|
|
48
|
+
GIT_COMMITTER_EMAIL: 'agent@parall.local',
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
execSync('git init', { cwd: workingDirectory, stdio: 'pipe', env });
|
|
52
|
+
execSync('git commit --allow-empty -m init', { cwd: workingDirectory, stdio: 'pipe', env });
|
|
53
|
+
ensureLocalAttachmentGitExclude(workingDirectory);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Non-fatal: codex app-server may still accept a bare directory. Let it raise at turn time.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PreparedLocalImage } from '@parall/agent-core/internal/attachment-input';
|
|
2
|
+
/**
|
|
3
|
+
* Wire shapes of the `codex app-server` JSON-RPC payloads: what we send as turn
|
|
4
|
+
* input, and how we read ids back out of responses and notifications. The
|
|
5
|
+
* protocol has shifted between CLI versions, so the tolerant field probing lives
|
|
6
|
+
* here rather than being spread through the adapter.
|
|
7
|
+
*/
|
|
8
|
+
export type CodexTurnInput = {
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'localImage';
|
|
13
|
+
path: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function buildTurnInput(body: string, images: PreparedLocalImage[]): CodexTurnInput[];
|
|
16
|
+
export declare function extractThreadId(result: unknown): string | undefined;
|
|
17
|
+
export declare function extractTurnId(result: unknown): string | undefined;
|
|
18
|
+
export declare function extractThreadIdFromNotification(params: unknown): string | undefined;
|
|
19
|
+
//# sourceMappingURL=app-server-protocol.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function buildTurnInput(body, images) {
|
|
2
|
+
return [
|
|
3
|
+
{ type: 'text', text: body },
|
|
4
|
+
...images.map((image) => ({ type: 'localImage', path: image.localPath })),
|
|
5
|
+
];
|
|
6
|
+
}
|
|
7
|
+
export function extractThreadId(result) {
|
|
8
|
+
if (!result || typeof result !== 'object')
|
|
9
|
+
return undefined;
|
|
10
|
+
const r = result;
|
|
11
|
+
if (typeof r.threadId === 'string')
|
|
12
|
+
return r.threadId;
|
|
13
|
+
const thread = r.thread;
|
|
14
|
+
if (thread && typeof thread.id === 'string')
|
|
15
|
+
return thread.id;
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
export function extractTurnId(result) {
|
|
19
|
+
if (!result || typeof result !== 'object')
|
|
20
|
+
return undefined;
|
|
21
|
+
const r = result;
|
|
22
|
+
if (typeof r.turnId === 'string')
|
|
23
|
+
return r.turnId;
|
|
24
|
+
const turn = r.turn;
|
|
25
|
+
if (turn && typeof turn.id === 'string')
|
|
26
|
+
return turn.id;
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
export function extractThreadIdFromNotification(params) {
|
|
30
|
+
if (!params || typeof params !== 'object')
|
|
31
|
+
return undefined;
|
|
32
|
+
const p = params;
|
|
33
|
+
if (typeof p.threadId === 'string')
|
|
34
|
+
return p.threadId;
|
|
35
|
+
const thread = p.thread;
|
|
36
|
+
if (thread && typeof thread.id === 'string')
|
|
37
|
+
return thread.id;
|
|
38
|
+
const meta = (p._meta ?? p.meta);
|
|
39
|
+
if (meta && typeof meta.threadId === 'string')
|
|
40
|
+
return meta.threadId;
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -15,7 +15,41 @@ type CodexAppServerAdapterOptions = Pick<CodexAgentConfig, 'approvalPolicy' | 'c
|
|
|
15
15
|
* on its next shell command — no respawn needed.
|
|
16
16
|
*/
|
|
17
17
|
capabilityBinDir?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Platform system prompt, delivered per-thread via the app-server's typed
|
|
20
|
+
* top-level `developerInstructions` param — the ONLY delivery channel. It
|
|
21
|
+
* replaces the legacy workspace `.codex/config.toml` + global trust entry,
|
|
22
|
+
* which coupled the prompt to codex's interactive workspace-trust concept
|
|
23
|
+
* and made the bridge write into the operator's own config on shared homes.
|
|
24
|
+
*
|
|
25
|
+
* Where it lands, live-probed on 0.144.1 — the CLI accepts the param on all
|
|
26
|
+
* three entrypoints but only `thread/start` APPLIES it (instructions are
|
|
27
|
+
* baked into the thread there). `thread/resume` keeps the thread's own copy;
|
|
28
|
+
* `thread/fork` inherits the parent's. The bridge sends it on all three
|
|
29
|
+
* anyway: a failed resume falls back to thread/start on the same params, and
|
|
30
|
+
* a future CLI that honors it then needs no bridge change.
|
|
31
|
+
*
|
|
32
|
+
* Consequence: updateConfig() changes what the NEXT thread/start sends; it
|
|
33
|
+
* cannot re-instruct an already-persisted thread. Inherited from the retired
|
|
34
|
+
* channel, not introduced —
|
|
35
|
+
* docs/tech-debt/codex-persisted-thread-prompt-refresh.md.
|
|
36
|
+
*/
|
|
37
|
+
developerInstructions?: string;
|
|
18
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Bridge driver backed by `codex app-server --listen stdio://`.
|
|
41
|
+
*
|
|
42
|
+
* Lifecycle:
|
|
43
|
+
* - bridge startup: spawn one `codex app-server` subprocess, do the
|
|
44
|
+
* `initialize` / `initialized` handshake once, keep the pipe open
|
|
45
|
+
* - per Parall dispatch: `thread/start` (first time) or `thread/resume`,
|
|
46
|
+
* then `turn/start`; forward server notifications until `turn/completed`
|
|
47
|
+
* - fork-on-busy: `thread/fork` creates a disposable sibling thread
|
|
48
|
+
*
|
|
49
|
+
* Concurrency: agent-core routes one dispatch per sessionKey at a time, so
|
|
50
|
+
* main + fork can interleave turns on the same stdio pipe. We route
|
|
51
|
+
* notifications by threadId the server stamps on every item/turn event.
|
|
52
|
+
*/
|
|
19
53
|
export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
20
54
|
private readonly opts;
|
|
21
55
|
private client;
|
|
@@ -40,6 +74,7 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
40
74
|
updateConfig(config: {
|
|
41
75
|
model?: string | null;
|
|
42
76
|
reasoningEffort?: string | null;
|
|
77
|
+
developerInstructions?: string | null;
|
|
43
78
|
}): void;
|
|
44
79
|
enqueueDuringDispatch(sessionKey: string, body: string): Promise<boolean>;
|
|
45
80
|
abortDispatch(sessionKey: string): void;
|
|
@@ -50,11 +85,14 @@ export declare class CodexAppServerAdapter implements DispatchAdapter {
|
|
|
50
85
|
cleanupFork({ fork }: CleanupForkOpts): void;
|
|
51
86
|
private logForkFailure;
|
|
52
87
|
/**
|
|
53
|
-
* Lazily restart the app-server before the NEXT turn
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
88
|
+
* Lazily restart the app-server before the NEXT turn, so a subprocess that
|
|
89
|
+
* has been running since before a capability change starts clean. Deferred
|
|
90
|
+
* to the next dispatch with no active turns — never kills an in-flight turn;
|
|
91
|
+
* thread state survives via thread/resume.
|
|
92
|
+
*
|
|
93
|
+
* A restart does NOT re-instruct an already-persisted thread — see the
|
|
94
|
+
* `developerInstructions` option doc. The capability shim dir on PATH is what
|
|
95
|
+
* makes a grant/revocation effective immediately.
|
|
58
96
|
*/
|
|
59
97
|
requestProcessRestart(): void;
|
|
60
98
|
private restartRequested;
|
package/dist/dispatch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,YAAY,EACb,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAGhE,KAAK,4BAA4B,GAAG,IAAI,CACtC,gBAAgB,EACd,gBAAgB,GAChB,UAAU,GACV,WAAW,GACX,OAAO,GACP,iBAAiB,GACjB,SAAS,GACT,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,mBAAmB,CAAC;IACpC,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;;;;;;;OAkBG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,qBAAa,qBAAsB,YAAW,eAAe;IA+B/C,OAAO,CAAC,QAAQ,CAAC,IAAI;IA9BjC,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,IAAI,CAA+C;IAC3D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAC3D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;IACtD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;gBAYQ,IAAI,EAAE,4BAA4B;IAE/D,YAAY,CAAC,MAAM,EAAE;QACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAChC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvC,GAAG,IAAI;IAQF,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqB/E,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAUvC,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI1C,QAAQ,CAAC,EACd,KAAK,EACL,YAAY,EACZ,UAAU,EACV,OAAO,GACR,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IA8P7C,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAMjD,WAAW,CAAC,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAqChG,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAIrC,OAAO,CAAC,cAAc;IAKtB;;;;;;;;;OASG;IACH,qBAAqB,IAAI,IAAI;IAI7B,OAAO,CAAC,gBAAgB,CAAS;YAEnB,mBAAmB;IAS3B,IAAI;YAuBI,aAAa;YA2Bb,OAAO;IAuGrB,OAAO,CAAC,qBAAqB;YAmCf,UAAU;IAoDxB,OAAO,CAAC,iBAAiB;CA2B1B"}
|
package/dist/dispatch.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
3
|
import * as path from 'node:path';
|
|
5
|
-
import { appendPreparedLocalAttachmentRefs,
|
|
4
|
+
import { appendPreparedLocalAttachmentRefs, pinLocalAttachmentPaths, } from '@parall/agent-core/internal/attachment-input';
|
|
5
|
+
import { IS_WIN32, ensureGitRepo, killWin32Tree, quoteWin32Arg } from './app-server-process.js';
|
|
6
|
+
import { buildTurnInput, extractThreadId, extractThreadIdFromNotification, extractTurnId, } from './app-server-protocol.js';
|
|
6
7
|
import { normalizeApprovalPolicy, normalizeSandbox } from './config.js';
|
|
7
|
-
import { EventMapper } from './event-mapping.js';
|
|
8
8
|
import { JsonRpcStdioClient } from './jsonrpc-client.js';
|
|
9
|
+
import { TurnSink } from './turn-sink.js';
|
|
9
10
|
/**
|
|
10
11
|
* Bridge driver backed by `codex app-server --listen stdio://`.
|
|
11
12
|
*
|
|
@@ -20,21 +21,6 @@ import { JsonRpcStdioClient } from './jsonrpc-client.js';
|
|
|
20
21
|
* main + fork can interleave turns on the same stdio pipe. We route
|
|
21
22
|
* notifications by threadId the server stamps on every item/turn event.
|
|
22
23
|
*/
|
|
23
|
-
const IS_WIN32 = process.platform === 'win32';
|
|
24
|
-
function quoteWin32Arg(arg) {
|
|
25
|
-
if (!/[\s"&|^<>()]/.test(arg))
|
|
26
|
-
return arg;
|
|
27
|
-
return `"${arg.replace(/"/g, '""')}"`;
|
|
28
|
-
}
|
|
29
|
-
function killWin32Tree(pid) {
|
|
30
|
-
try {
|
|
31
|
-
execSync(`taskkill /T /F /PID ${pid}`, { windowsHide: true, stdio: 'ignore' });
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
24
|
export class CodexAppServerAdapter {
|
|
39
25
|
opts;
|
|
40
26
|
client = null;
|
|
@@ -71,6 +57,8 @@ export class CodexAppServerAdapter {
|
|
|
71
57
|
this.opts.model = config.model ?? undefined;
|
|
72
58
|
if (config.reasoningEffort !== undefined)
|
|
73
59
|
this.opts.reasoningEffort = config.reasoningEffort ?? undefined;
|
|
60
|
+
if (config.developerInstructions !== undefined)
|
|
61
|
+
this.opts.developerInstructions = config.developerInstructions ?? undefined;
|
|
74
62
|
}
|
|
75
63
|
async enqueueDuringDispatch(sessionKey, body) {
|
|
76
64
|
const client = this.client;
|
|
@@ -381,6 +369,11 @@ export class CodexAppServerAdapter {
|
|
|
381
369
|
if (this.opts.useParallProvider) {
|
|
382
370
|
forkParams.modelProvider = 'parall';
|
|
383
371
|
}
|
|
372
|
+
if (this.opts.developerInstructions) {
|
|
373
|
+
// Accepted, but the fork inherits the parent's instructions instead —
|
|
374
|
+
// so it carries the platform prompt either way. See the option doc.
|
|
375
|
+
forkParams.developerInstructions = this.opts.developerInstructions;
|
|
376
|
+
}
|
|
384
377
|
if (this.opts.model)
|
|
385
378
|
forkParams.model = this.opts.model;
|
|
386
379
|
if (this.opts.reasoningEffort) {
|
|
@@ -408,11 +401,14 @@ export class CodexAppServerAdapter {
|
|
|
408
401
|
return null;
|
|
409
402
|
}
|
|
410
403
|
/**
|
|
411
|
-
* Lazily restart the app-server before the NEXT turn
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
404
|
+
* Lazily restart the app-server before the NEXT turn, so a subprocess that
|
|
405
|
+
* has been running since before a capability change starts clean. Deferred
|
|
406
|
+
* to the next dispatch with no active turns — never kills an in-flight turn;
|
|
407
|
+
* thread state survives via thread/resume.
|
|
408
|
+
*
|
|
409
|
+
* A restart does NOT re-instruct an already-persisted thread — see the
|
|
410
|
+
* `developerInstructions` option doc. The capability shim dir on PATH is what
|
|
411
|
+
* makes a grant/revocation effective immediately.
|
|
416
412
|
*/
|
|
417
413
|
requestProcessRestart() {
|
|
418
414
|
this.restartRequested = true;
|
|
@@ -422,7 +418,7 @@ export class CodexAppServerAdapter {
|
|
|
422
418
|
if (!this.restartRequested || this.activeTurns.size > 0)
|
|
423
419
|
return;
|
|
424
420
|
this.restartRequested = false;
|
|
425
|
-
(log ?? this.opts.log)?.info?.('restarting codex app-server
|
|
421
|
+
(log ?? this.opts.log)?.info?.('restarting codex app-server after a capability change (new threads pick up the refreshed developerInstructions; an already-persisted thread keeps its own)');
|
|
426
422
|
await this.stop();
|
|
427
423
|
}
|
|
428
424
|
async stop() {
|
|
@@ -617,6 +613,13 @@ export class CodexAppServerAdapter {
|
|
|
617
613
|
commonParams.modelProvider = 'parall';
|
|
618
614
|
}
|
|
619
615
|
commonParams.sandbox = normalizeSandbox(this.opts.sandbox);
|
|
616
|
+
if (this.opts.developerInstructions) {
|
|
617
|
+
// Typed top-level param (camelCase), NOT a raw config.toml override —
|
|
618
|
+
// trust-independent, so the platform prompt loads regardless of any codex
|
|
619
|
+
// workspace-trust state. thread/start applies it; thread/resume keeps the
|
|
620
|
+
// thread's own copy. Sent on both — see the option doc.
|
|
621
|
+
commonParams.developerInstructions = this.opts.developerInstructions;
|
|
622
|
+
}
|
|
620
623
|
if (this.opts.model)
|
|
621
624
|
commonParams.model = this.opts.model;
|
|
622
625
|
if (this.opts.reasoningEffort) {
|
|
@@ -667,117 +670,6 @@ export class CodexAppServerAdapter {
|
|
|
667
670
|
}
|
|
668
671
|
}
|
|
669
672
|
}
|
|
670
|
-
/** Per-turn buffered sink backed by an unbounded promise queue. */
|
|
671
|
-
class TurnSink {
|
|
672
|
-
mapper = new EventMapper();
|
|
673
|
-
queue = [];
|
|
674
|
-
resolver = null;
|
|
675
|
-
closed = false;
|
|
676
|
-
push(envelope) {
|
|
677
|
-
if (this.closed)
|
|
678
|
-
return;
|
|
679
|
-
if (this.resolver) {
|
|
680
|
-
const r = this.resolver;
|
|
681
|
-
this.resolver = null;
|
|
682
|
-
r(envelope);
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
this.queue.push(envelope);
|
|
686
|
-
}
|
|
687
|
-
next() {
|
|
688
|
-
// Drain any queued envelopes first, even after close(). Otherwise a final
|
|
689
|
-
// error envelope enqueued right before close() (e.g. by
|
|
690
|
-
// handleSubprocessClose) is silently dropped because the consumer would
|
|
691
|
-
// see turn_end before it.
|
|
692
|
-
const pending = this.queue.shift();
|
|
693
|
-
if (pending)
|
|
694
|
-
return Promise.resolve(pending);
|
|
695
|
-
if (this.closed) {
|
|
696
|
-
return Promise.resolve({ kind: 'turn_end' });
|
|
697
|
-
}
|
|
698
|
-
return new Promise((resolve) => {
|
|
699
|
-
this.resolver = resolve;
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
close() {
|
|
703
|
-
this.closed = true;
|
|
704
|
-
const r = this.resolver;
|
|
705
|
-
this.resolver = null;
|
|
706
|
-
r?.({ kind: 'turn_end' });
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
function ensureGitRepo(workingDirectory) {
|
|
710
|
-
fs.mkdirSync(workingDirectory, { recursive: true });
|
|
711
|
-
// Only `git init` if the workspace isn't already inside any git repo. A
|
|
712
|
-
// bare existsSync(.git) check would miss the common case of a user pointing
|
|
713
|
-
// PRLL_WORKSPACE_DIR at a subdirectory of their existing project,
|
|
714
|
-
// and silently creating a nested repo there would mangle their layout.
|
|
715
|
-
try {
|
|
716
|
-
execSync('git rev-parse --is-inside-work-tree', { cwd: workingDirectory, stdio: 'pipe' });
|
|
717
|
-
ensureLocalAttachmentGitExclude(workingDirectory);
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
catch {
|
|
721
|
-
// Not inside a repo — fall through to init.
|
|
722
|
-
}
|
|
723
|
-
const env = {
|
|
724
|
-
...process.env,
|
|
725
|
-
GIT_AUTHOR_NAME: 'parall-codex-agent',
|
|
726
|
-
GIT_AUTHOR_EMAIL: 'agent@parall.local',
|
|
727
|
-
GIT_COMMITTER_NAME: 'parall-codex-agent',
|
|
728
|
-
GIT_COMMITTER_EMAIL: 'agent@parall.local',
|
|
729
|
-
};
|
|
730
|
-
try {
|
|
731
|
-
execSync('git init', { cwd: workingDirectory, stdio: 'pipe', env });
|
|
732
|
-
execSync('git commit --allow-empty -m init', { cwd: workingDirectory, stdio: 'pipe', env });
|
|
733
|
-
ensureLocalAttachmentGitExclude(workingDirectory);
|
|
734
|
-
}
|
|
735
|
-
catch {
|
|
736
|
-
// Non-fatal: codex app-server may still accept a bare directory. Let it raise at turn time.
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
function buildTurnInput(body, images) {
|
|
740
|
-
return [
|
|
741
|
-
{ type: 'text', text: body },
|
|
742
|
-
...images.map((image) => ({ type: 'localImage', path: image.localPath })),
|
|
743
|
-
];
|
|
744
|
-
}
|
|
745
|
-
function extractThreadId(result) {
|
|
746
|
-
if (!result || typeof result !== 'object')
|
|
747
|
-
return undefined;
|
|
748
|
-
const r = result;
|
|
749
|
-
if (typeof r.threadId === 'string')
|
|
750
|
-
return r.threadId;
|
|
751
|
-
const thread = r.thread;
|
|
752
|
-
if (thread && typeof thread.id === 'string')
|
|
753
|
-
return thread.id;
|
|
754
|
-
return undefined;
|
|
755
|
-
}
|
|
756
|
-
function extractTurnId(result) {
|
|
757
|
-
if (!result || typeof result !== 'object')
|
|
758
|
-
return undefined;
|
|
759
|
-
const r = result;
|
|
760
|
-
if (typeof r.turnId === 'string')
|
|
761
|
-
return r.turnId;
|
|
762
|
-
const turn = r.turn;
|
|
763
|
-
if (turn && typeof turn.id === 'string')
|
|
764
|
-
return turn.id;
|
|
765
|
-
return undefined;
|
|
766
|
-
}
|
|
767
|
-
function extractThreadIdFromNotification(params) {
|
|
768
|
-
if (!params || typeof params !== 'object')
|
|
769
|
-
return undefined;
|
|
770
|
-
const p = params;
|
|
771
|
-
if (typeof p.threadId === 'string')
|
|
772
|
-
return p.threadId;
|
|
773
|
-
const thread = p.thread;
|
|
774
|
-
if (thread && typeof thread.id === 'string')
|
|
775
|
-
return thread.id;
|
|
776
|
-
const meta = (p._meta ?? p.meta);
|
|
777
|
-
if (meta && typeof meta.threadId === 'string')
|
|
778
|
-
return meta.threadId;
|
|
779
|
-
return undefined;
|
|
780
|
-
}
|
|
781
673
|
function errToString(err) {
|
|
782
674
|
if (err instanceof Error)
|
|
783
675
|
return err.message;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
|
5
5
|
import { buildCodexRuntimeKey, contextFilePathForSession, dispatchContextDirPath, resolveCodexAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from './config.js';
|
|
6
6
|
import { CodexAppServerAdapter } from './dispatch.js';
|
|
7
7
|
import { CodexSessionManager } from './session-manager.js';
|
|
8
|
-
import { ensureCodexWorkspace, ensureParallProvider,
|
|
8
|
+
import { ensureCodexWorkspace, ensureParallProvider, isParallProxyMode, writeCodexSystemPrompt, } from './workspace.js';
|
|
9
9
|
const log = createLogger('codex-agent');
|
|
10
10
|
let activeLog = log;
|
|
11
11
|
async function getAgentMeWithLegacyFallback(client, orgId) {
|
|
@@ -52,7 +52,6 @@ async function main() {
|
|
|
52
52
|
const agentUserId = me.id;
|
|
53
53
|
const agentLog = childLogger(activeLog, agentUserId);
|
|
54
54
|
activeLog = agentLog;
|
|
55
|
-
ensureWorkspaceTrusted(config.codexHome, config.workspaceDir, agentLog);
|
|
56
55
|
const useParallProvider = isParallProxyMode();
|
|
57
56
|
if (useParallProvider) {
|
|
58
57
|
ensureParallProvider(config.codexHome, config.apiUrl, agentLog);
|
|
@@ -89,11 +88,14 @@ async function main() {
|
|
|
89
88
|
materializeChannelCapabilities(config.stateDir, caps, agentLog);
|
|
90
89
|
return caps.map((c) => c.fragment);
|
|
91
90
|
};
|
|
92
|
-
// Assemble the workspace AFTER the first config fetch so
|
|
93
|
-
//
|
|
91
|
+
// Assemble the workspace AFTER the first config fetch so the platform
|
|
92
|
+
// prompt carries the capability declarations from boot.
|
|
94
93
|
const bootCapabilityFragments = applyChannelCapabilities();
|
|
95
94
|
let lastCapabilityFragments = bootCapabilityFragments.join('\n\n');
|
|
96
|
-
|
|
95
|
+
// Platform instructions are delivered per-thread via the app-server's
|
|
96
|
+
// developerInstructions param — no workspace config file, no dependence
|
|
97
|
+
// on codex's workspace-trust state, no writes to the operator's config.
|
|
98
|
+
const developerInstructions = ensureCodexWorkspace(config.workspaceDir, agentLog, agentIdentity, bootCapabilityFragments);
|
|
97
99
|
// Model precedence: operator PIN (override) > env > server FLOOR. The server
|
|
98
100
|
// now says which it is via model_is_pin (deriveModelIsPin presence-gates the
|
|
99
101
|
// dual-read: old servers omit it → fall back to model_management). A PIN beats
|
|
@@ -120,6 +122,7 @@ async function main() {
|
|
|
120
122
|
contextDirPath: dispatchContextDirPath(config.stateDir),
|
|
121
123
|
useParallProvider,
|
|
122
124
|
capabilityBinDir: capabilityBinDir(config.stateDir),
|
|
125
|
+
developerInstructions,
|
|
123
126
|
});
|
|
124
127
|
// Shared by onConfigUpdate + onSessionReady. /agents/me is only consumed as
|
|
125
128
|
// deriveModelIsPin's legacy fallback when the server omits model_is_pin
|
|
@@ -145,15 +148,26 @@ async function main() {
|
|
|
145
148
|
model: resolveRuntimeModel(isPin, updated.model, config.model) ?? null,
|
|
146
149
|
reasoningEffort: updated.thinkingEffort ?? config.reasoningEffort ?? null,
|
|
147
150
|
});
|
|
148
|
-
// Capability heat-update: re-materialize shims +
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
151
|
+
// Capability heat-update: re-materialize shims + rebuild the prompt.
|
|
152
|
+
// The write is refresh-tolerant (warn + retry next refresh); only a
|
|
153
|
+
// SUCCESSFUL rebuild with a changed fragment set schedules the lazy
|
|
154
|
+
// app-server restart.
|
|
155
|
+
//
|
|
156
|
+
// What each half of the refresh actually reaches: the shim dir on PATH
|
|
157
|
+
// makes the granted TOOL work on the agent's next shell command, live,
|
|
158
|
+
// no restart needed. The refreshed PROMPT reaches the next thread that
|
|
159
|
+
// gets STARTED — codex bakes developerInstructions into a thread at
|
|
160
|
+
// thread/start and neither resume nor fork replaces them (live-probed on
|
|
161
|
+
// 0.144.1; the retired workspace-config channel had the same limitation).
|
|
162
|
+
// So an agent with a long-lived persisted thread keeps the older fragment
|
|
163
|
+
// text in its prompt until that thread is replaced —
|
|
164
|
+
// docs/tech-debt/codex-persisted-thread-prompt-refresh.md.
|
|
152
165
|
const fragments = applyChannelCapabilities();
|
|
153
166
|
const joinedFragments = fragments.join('\n\n');
|
|
154
167
|
let promptWritten = true;
|
|
155
168
|
try {
|
|
156
|
-
writeCodexSystemPrompt(config.workspaceDir, agentIdentity, fragments);
|
|
169
|
+
const refreshedPrompt = writeCodexSystemPrompt(config.workspaceDir, agentIdentity, fragments);
|
|
170
|
+
adapter.updateConfig({ developerInstructions: refreshedPrompt });
|
|
157
171
|
}
|
|
158
172
|
catch (err) {
|
|
159
173
|
promptWritten = false;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export declare function legacyWorkspaceConfigPath(workspaceDir: string): string;
|
|
2
|
+
export declare function migrationSentinelPath(workspaceDir: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Byte-exact reconstruction of the retired serializer. Verified identical across
|
|
5
|
+
* every released bridge that wrote this file (v1.37.0 … v1.44.0 — the line is
|
|
6
|
+
* byte-for-byte the same in all of them), so a single reconstruction covers the
|
|
7
|
+
* whole legacy fleet. The deletion gate compares raw bytes against this: it must
|
|
8
|
+
* never drift.
|
|
9
|
+
*/
|
|
10
|
+
export declare function legacyWorkspaceConfigToml(prompt: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* The PRE-OVERWRITE `.parall/system-prompt.md`. The retired bridge wrote the
|
|
13
|
+
* reference copy and the config from the SAME string in the same call, so a byte
|
|
14
|
+
* match against `legacyWorkspaceConfigToml(prompt)` is what identifies our own
|
|
15
|
+
* artifact.
|
|
16
|
+
*
|
|
17
|
+
* `absent` (ENOENT — the bridge never bootstrapped this workspace) and
|
|
18
|
+
* `unreadable` (it is there, but an I/O error hid it) both mean "cannot prove
|
|
19
|
+
* ownership, so do not delete" — but they are different facts, and reporting the
|
|
20
|
+
* second as the first tells the operator the wrong reason for a preserved file.
|
|
21
|
+
*/
|
|
22
|
+
export type AuthorshipProof = {
|
|
23
|
+
kind: 'present';
|
|
24
|
+
prompt: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'absent';
|
|
27
|
+
} | {
|
|
28
|
+
kind: 'unreadable';
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* What the filesystem says about the legacy config. Gathered by the executor;
|
|
32
|
+
* the classifier below sees nothing else — no fs, no clock, no env.
|
|
33
|
+
*/
|
|
34
|
+
export type LegacyConfigFacts = {
|
|
35
|
+
/** lstat of the legacy path — never a stat: a symlink must not be followed. `null` = ENOENT. */
|
|
36
|
+
entry: {
|
|
37
|
+
isPlainFile: boolean;
|
|
38
|
+
hardLinks: number;
|
|
39
|
+
} | null;
|
|
40
|
+
/** Raw bytes of the legacy file. `null` = not a plain file, or unreadable. */
|
|
41
|
+
rawContent: string | null;
|
|
42
|
+
proof: AuthorshipProof;
|
|
43
|
+
};
|
|
44
|
+
export type LegacyConfigVerdict = {
|
|
45
|
+
action: 'none';
|
|
46
|
+
} | {
|
|
47
|
+
action: 'remove';
|
|
48
|
+
} | {
|
|
49
|
+
action: 'preserve';
|
|
50
|
+
reason: PreserveReason;
|
|
51
|
+
};
|
|
52
|
+
export type PreserveReason =
|
|
53
|
+
/** Symlink (a dotfiles arrangement) or a directory — unlink-by-path would sever the operator's link. */
|
|
54
|
+
'not-a-plain-file'
|
|
55
|
+
/** Extra hard links: the same inode is reachable from a path we know nothing about. */
|
|
56
|
+
| 'extra-hard-links'
|
|
57
|
+
/** No `.parall/system-prompt.md` from a previous boot — no proof can exist. */
|
|
58
|
+
| 'no-authorship-proof'
|
|
59
|
+
/** The proof copy exists but could not be read, so ownership cannot be evaluated. */
|
|
60
|
+
| 'authorship-proof-unreadable'
|
|
61
|
+
/** Could not read the legacy config's own bytes, so the proof cannot be applied. */
|
|
62
|
+
| 'config-unreadable'
|
|
63
|
+
/** Bytes differ from what the retired bridge would have written (operator-authored, comments, hand-edited). */
|
|
64
|
+
| 'content-mismatch';
|
|
65
|
+
/**
|
|
66
|
+
* Pure. Decides the fate of the legacy config from filesystem facts alone.
|
|
67
|
+
*
|
|
68
|
+
* The only path to `remove` is: plain regular file + exactly one hard link + a
|
|
69
|
+
* pre-overwrite reference copy exists + raw bytes are EXACTLY the retired
|
|
70
|
+
* serializer's output for that reference prompt. Every other combination
|
|
71
|
+
* preserves — including value-level near-misses (an operator file carrying the
|
|
72
|
+
* same `developer_instructions` value plus a comment would be destroyed together
|
|
73
|
+
* with the comment by a value-level compare).
|
|
74
|
+
*/
|
|
75
|
+
export declare function classifyLegacyWorkspaceConfig(facts: LegacyConfigFacts): LegacyConfigVerdict;
|
|
76
|
+
export type MigrationClaim = 'claimed' | 'already-claimed';
|
|
77
|
+
/**
|
|
78
|
+
* Atomically take the one-way claim. `wx` makes this a single filesystem
|
|
79
|
+
* operation, so concurrent boots cannot both win.
|
|
80
|
+
*
|
|
81
|
+
* THROWS if the claim cannot be persisted (EACCES, EIO, EROFS …). That is
|
|
82
|
+
* deliberate and load-bearing: the caller runs this BEFORE overwriting
|
|
83
|
+
* `.parall/system-prompt.md`, so a boot that cannot record its claim must die
|
|
84
|
+
* with the legacy evidence still intact rather than proceed to manufacture a
|
|
85
|
+
* prompt that a later boot would mistake for that evidence.
|
|
86
|
+
*/
|
|
87
|
+
export declare function claimLegacyWorkspaceConfigMigration(workspaceDir: string): MigrationClaim;
|
|
88
|
+
/**
|
|
89
|
+
* The migration lifecycle: claim, then — only if we won the claim — read the
|
|
90
|
+
* proof and clean up.
|
|
91
|
+
*
|
|
92
|
+
* `readProof` is a THUNK, not a value, and that is the whole point. Reading the
|
|
93
|
+
* proof is not free of side effects: an unreadable `.parall/system-prompt.md`
|
|
94
|
+
* warns. Passed as an already-evaluated argument, that warning fires before the
|
|
95
|
+
* claim is even checked — so an already-claimed later boot, or a concurrent
|
|
96
|
+
* loser, would emit a migration warning about a decision it is not making. The
|
|
97
|
+
* thunk makes "only the winner touches the proof" a property of the type rather
|
|
98
|
+
* than of the caller's evaluation order.
|
|
99
|
+
*
|
|
100
|
+
* The caller still owns `.parall/system-prompt.md` and must hand over the
|
|
101
|
+
* PRE-OVERWRITE copy: the winner evaluates the thunk here, before anything
|
|
102
|
+
* writes a new prompt.
|
|
103
|
+
*
|
|
104
|
+
* A boot that does not win the claim returns silently — it must not delete, it
|
|
105
|
+
* must not read, and it must not warn: the winner may be deleting the very file
|
|
106
|
+
* it would warn about, and a stale warning about a file that is already gone is
|
|
107
|
+
* exactly the misleading diagnostic this module exists to avoid.
|
|
108
|
+
*/
|
|
109
|
+
export declare function runLegacyWorkspaceConfigMigration(workspaceDir: string, readProof: () => AuthorshipProof, log?: {
|
|
110
|
+
warn: (msg: string) => void;
|
|
111
|
+
}): void;
|
|
112
|
+
//# sourceMappingURL=legacy-workspace-config-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy-workspace-config-migration.d.ts","sourceRoot":"","sources":["../src/legacy-workspace-config-migration.ts"],"names":[],"mappings":"AAqDA,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gGAAgG;IAChG,KAAK,EAAE;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1D,8EAA8E;IAC9E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,CAAC;AAEnD,MAAM,MAAM,cAAc;AACxB,wGAAwG;AACtG,kBAAkB;AACpB,uFAAuF;GACrF,kBAAkB;AACpB,+EAA+E;GAC7E,qBAAqB;AACvB,qFAAqF;GACnF,6BAA6B;AAC/B,oFAAoF;GAClF,mBAAmB;AACrB,+GAA+G;GAC7G,kBAAkB,CAAC;AAEvB;;;;;;;;;GASG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,iBAAiB,GAAG,mBAAmB,CAc3F;AA6BD,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,iBAAiB,CAAC;AAE3D;;;;;;;;;GASG;AACH,wBAAgB,mCAAmC,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,CAYxF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iCAAiC,CAC/C,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,eAAe,EAChC,GAAG,CAAC,EAAE;IAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACpC,IAAI,CAGN"}
|