@sapiom/harness 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +44 -43
- package/dist/cli/auth.d.ts +26 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +39 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +163 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/consent.d.ts +15 -0
- package/dist/cli/consent.d.ts.map +1 -0
- package/dist/cli/consent.js +81 -0
- package/dist/cli/consent.js.map +1 -0
- package/dist/cli/doctor.d.ts +27 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +74 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/machine-id.d.ts +19 -0
- package/dist/cli/machine-id.d.ts.map +1 -0
- package/dist/cli/machine-id.js +75 -0
- package/dist/cli/machine-id.js.map +1 -0
- package/dist/cli/paths.d.ts +3 -0
- package/dist/cli/paths.d.ts.map +1 -0
- package/dist/cli/paths.js +11 -0
- package/dist/cli/paths.js.map +1 -0
- package/dist/cli/settings.d.ts +26 -0
- package/dist/cli/settings.d.ts.map +1 -0
- package/dist/cli/settings.js +123 -0
- package/dist/cli/settings.js.map +1 -0
- package/dist/core/adapters/claude-code.d.ts +41 -0
- package/dist/core/adapters/claude-code.d.ts.map +1 -0
- package/dist/core/adapters/claude-code.js +238 -0
- package/dist/core/adapters/claude-code.js.map +1 -0
- package/dist/core/adapters/codex.d.ts +59 -0
- package/dist/core/adapters/codex.d.ts.map +1 -0
- package/dist/core/adapters/codex.js +319 -0
- package/dist/core/adapters/codex.js.map +1 -0
- package/dist/core/canvas-body.d.ts +45 -0
- package/dist/core/canvas-body.d.ts.map +1 -0
- package/dist/core/canvas-body.js +94 -0
- package/dist/core/canvas-body.js.map +1 -0
- package/dist/core/canvas-cache.d.ts +23 -0
- package/dist/core/canvas-cache.d.ts.map +1 -0
- package/dist/core/canvas-cache.js +64 -0
- package/dist/core/canvas-cache.js.map +1 -0
- package/dist/core/canvas-enrich.d.ts +106 -0
- package/dist/core/canvas-enrich.d.ts.map +1 -0
- package/dist/core/canvas-enrich.js +178 -0
- package/dist/core/canvas-enrich.js.map +1 -0
- package/dist/core/canvas-enrichment.d.ts +144 -0
- package/dist/core/canvas-enrichment.d.ts.map +1 -0
- package/dist/core/canvas-enrichment.js +192 -0
- package/dist/core/canvas-enrichment.js.map +1 -0
- package/dist/core/canvas-graph.d.ts +71 -0
- package/dist/core/canvas-graph.d.ts.map +1 -0
- package/dist/core/canvas-graph.js +111 -0
- package/dist/core/canvas-graph.js.map +1 -0
- package/dist/core/canvas-index-classify.d.ts +41 -0
- package/dist/core/canvas-index-classify.d.ts.map +1 -0
- package/dist/core/canvas-index-classify.js +63 -0
- package/dist/core/canvas-index-classify.js.map +1 -0
- package/dist/core/canvas-interconnections.d.ts +24 -0
- package/dist/core/canvas-interconnections.d.ts.map +1 -0
- package/dist/core/canvas-interconnections.js +96 -0
- package/dist/core/canvas-interconnections.js.map +1 -0
- package/dist/core/canvas-manifest-check.d.ts +15 -0
- package/dist/core/canvas-manifest-check.d.ts.map +1 -0
- package/dist/core/canvas-manifest-check.js +90 -0
- package/dist/core/canvas-manifest-check.js.map +1 -0
- package/dist/core/canvas-render.d.ts +73 -0
- package/dist/core/canvas-render.d.ts.map +1 -0
- package/dist/core/canvas-render.js +145 -0
- package/dist/core/canvas-render.js.map +1 -0
- package/dist/core/canvas-svg.d.ts +50 -0
- package/dist/core/canvas-svg.d.ts.map +1 -0
- package/dist/core/canvas-svg.js +240 -0
- package/dist/core/canvas-svg.js.map +1 -0
- package/dist/core/canvas-template.d.ts +43 -0
- package/dist/core/canvas-template.d.ts.map +1 -0
- package/dist/core/canvas-template.js +365 -0
- package/dist/core/canvas-template.js.map +1 -0
- package/dist/core/canvas-watcher.d.ts +25 -0
- package/dist/core/canvas-watcher.d.ts.map +1 -0
- package/dist/core/canvas-watcher.js +144 -0
- package/dist/core/canvas-watcher.js.map +1 -0
- package/dist/core/collector/batcher.d.ts +57 -0
- package/dist/core/collector/batcher.d.ts.map +1 -0
- package/dist/core/collector/batcher.js +143 -0
- package/dist/core/collector/batcher.js.map +1 -0
- package/dist/core/collector/codex-tailer.d.ts +85 -0
- package/dist/core/collector/codex-tailer.d.ts.map +1 -0
- package/dist/core/collector/codex-tailer.js +270 -0
- package/dist/core/collector/codex-tailer.js.map +1 -0
- package/dist/core/collector/normalizer.d.ts +33 -0
- package/dist/core/collector/normalizer.d.ts.map +1 -0
- package/dist/core/collector/normalizer.js +94 -0
- package/dist/core/collector/normalizer.js.map +1 -0
- package/dist/core/collector/seq.d.ts +14 -0
- package/dist/core/collector/seq.d.ts.map +1 -0
- package/dist/core/collector/seq.js +20 -0
- package/dist/core/collector/seq.js.map +1 -0
- package/dist/core/collector/store.d.ts +16 -0
- package/dist/core/collector/store.d.ts.map +1 -0
- package/dist/core/collector/store.js +30 -0
- package/dist/core/collector/store.js.map +1 -0
- package/dist/core/collector/transcript.d.ts +38 -0
- package/dist/core/collector/transcript.d.ts.map +1 -0
- package/dist/core/collector/transcript.js +110 -0
- package/dist/core/collector/transcript.js.map +1 -0
- package/dist/core/event-bus.d.ts +9 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +25 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/example-seed.d.ts +34 -0
- package/dist/core/example-seed.d.ts.map +1 -0
- package/dist/core/example-seed.js +277 -0
- package/dist/core/example-seed.js.map +1 -0
- package/dist/core/inject/claude-settings.d.ts +34 -0
- package/dist/core/inject/claude-settings.d.ts.map +1 -0
- package/dist/core/inject/claude-settings.js +152 -0
- package/dist/core/inject/claude-settings.js.map +1 -0
- package/dist/core/inject/mcp-config.d.ts +26 -0
- package/dist/core/inject/mcp-config.d.ts.map +1 -0
- package/dist/core/inject/mcp-config.js +39 -0
- package/dist/core/inject/mcp-config.js.map +1 -0
- package/dist/core/inject/retention.d.ts +66 -0
- package/dist/core/inject/retention.d.ts.map +1 -0
- package/dist/core/inject/retention.js +119 -0
- package/dist/core/inject/retention.js.map +1 -0
- package/dist/core/inject/system-prompt.d.ts +11 -0
- package/dist/core/inject/system-prompt.d.ts.map +1 -0
- package/dist/core/inject/system-prompt.js +22 -0
- package/dist/core/inject/system-prompt.js.map +1 -0
- package/dist/core/macro-runner.d.ts +36 -0
- package/dist/core/macro-runner.d.ts.map +1 -0
- package/dist/core/macro-runner.js +66 -0
- package/dist/core/macro-runner.js.map +1 -0
- package/dist/core/macros.d.ts +10 -0
- package/dist/core/macros.d.ts.map +1 -0
- package/dist/core/macros.js +59 -0
- package/dist/core/macros.js.map +1 -0
- package/dist/core/path-safety.d.ts +33 -0
- package/dist/core/path-safety.d.ts.map +1 -0
- package/dist/core/path-safety.js +63 -0
- package/dist/core/path-safety.js.map +1 -0
- package/dist/core/paths.d.ts +27 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +43 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/port-detector.d.ts +69 -0
- package/dist/core/port-detector.d.ts.map +1 -0
- package/dist/core/port-detector.js +121 -0
- package/dist/core/port-detector.js.map +1 -0
- package/dist/core/session-manager.d.ts +275 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/core/session-manager.js +729 -0
- package/dist/core/session-manager.js.map +1 -0
- package/dist/core/task-manager.d.ts +138 -0
- package/dist/core/task-manager.d.ts.map +1 -0
- package/dist/core/task-manager.js +286 -0
- package/dist/core/task-manager.js.map +1 -0
- package/dist/core/task-stream.d.ts +32 -0
- package/dist/core/task-stream.d.ts.map +1 -0
- package/dist/core/task-stream.js +87 -0
- package/dist/core/task-stream.js.map +1 -0
- package/dist/core/workflow-registry.d.ts +36 -0
- package/dist/core/workflow-registry.d.ts.map +1 -0
- package/dist/core/workflow-registry.js +220 -0
- package/dist/core/workflow-registry.js.map +1 -0
- package/dist/core/workspace-context.d.ts +43 -0
- package/dist/core/workspace-context.d.ts.map +1 -0
- package/dist/core/workspace-context.js +108 -0
- package/dist/core/workspace-context.js.map +1 -0
- package/dist/core/workspace-watcher.d.ts +26 -0
- package/dist/core/workspace-watcher.d.ts.map +1 -0
- package/dist/core/workspace-watcher.js +174 -0
- package/dist/core/workspace-watcher.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/profiles/canvas-guidelines.d.ts +20 -0
- package/dist/profiles/canvas-guidelines.d.ts.map +1 -0
- package/dist/profiles/canvas-guidelines.js +48 -0
- package/dist/profiles/canvas-guidelines.js.map +1 -0
- package/dist/profiles/default.d.ts +10 -0
- package/dist/profiles/default.d.ts.map +1 -0
- package/dist/profiles/default.js +70 -0
- package/dist/profiles/default.js.map +1 -0
- package/dist/server/auth.d.ts +9 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +28 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/canvas-render.d.ts +18 -0
- package/dist/server/canvas-render.d.ts.map +1 -0
- package/dist/server/canvas-render.js +24 -0
- package/dist/server/canvas-render.js.map +1 -0
- package/dist/server/canvas.d.ts +12 -0
- package/dist/server/canvas.d.ts.map +1 -0
- package/dist/server/canvas.js +174 -0
- package/dist/server/canvas.js.map +1 -0
- package/dist/server/events-ws.d.ts +11 -0
- package/dist/server/events-ws.d.ts.map +1 -0
- package/dist/server/events-ws.js +24 -0
- package/dist/server/events-ws.js.map +1 -0
- package/dist/server/fs.d.ts +4 -0
- package/dist/server/fs.d.ts.map +1 -0
- package/dist/server/fs.js +88 -0
- package/dist/server/fs.js.map +1 -0
- package/dist/server/index.d.ts +83 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +0 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/ingest.d.ts +74 -0
- package/dist/server/ingest.d.ts.map +1 -0
- package/dist/server/ingest.js +74 -0
- package/dist/server/ingest.js.map +1 -0
- package/dist/server/macros.d.ts +32 -0
- package/dist/server/macros.d.ts.map +1 -0
- package/dist/server/macros.js +76 -0
- package/dist/server/macros.js.map +1 -0
- package/dist/server/rest.d.ts +75 -0
- package/dist/server/rest.d.ts.map +1 -0
- package/dist/server/rest.js +235 -0
- package/dist/server/rest.js.map +1 -0
- package/dist/server/static.d.ts +8 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +36 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/terminal-ws.d.ts +9 -0
- package/dist/server/terminal-ws.d.ts.map +1 -0
- package/dist/server/terminal-ws.js +59 -0
- package/dist/server/terminal-ws.js.map +1 -0
- package/dist/server/ws-router.d.ts +16 -0
- package/dist/server/ws-router.d.ts.map +1 -0
- package/dist/server/ws-router.js +26 -0
- package/dist/server/ws-router.js.map +1 -0
- package/dist/shared/types.d.ts +485 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +80 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/web/assets/index-B9R4TzH7.css +32 -0
- package/dist/web/assets/index-Dh97QAgq.js +192 -0
- package/dist/web/index.html +27 -0
- package/package.json +84 -7
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codex adapter — launches/resumes the `codex` CLI and scans its rollout
|
|
3
|
+
* store for resumable history. Codex has no hook system (see
|
|
4
|
+
* core/collector/codex-tailer.ts for how its analytics eventSource works
|
|
5
|
+
* instead); this file only covers process launch/resume/doctor/history.
|
|
6
|
+
*
|
|
7
|
+
* Verified against a locally installed `codex-cli 0.134.0`: `codex resume
|
|
8
|
+
* [SESSION_ID] [PROMPT]` (positional UUID or thread name) via `codex resume
|
|
9
|
+
* --help`; the generic `-c key=value` config-override mechanism via `codex
|
|
10
|
+
* --help`; and, via real spawns with `--strict-config`, that both
|
|
11
|
+
* `developer_instructions` and `model_instructions_file` are real,
|
|
12
|
+
* recognized keys (an earlier version of this file used the latter and
|
|
13
|
+
* flagged both as unconfirmed — see buildConfigArgs below for why the
|
|
14
|
+
* adapter now uses `developer_instructions` instead: `model_instructions_file`
|
|
15
|
+
* makes codex's own startup depend on re-reading a file we already have the
|
|
16
|
+
* content of, and an unreadable path there kills the process instantly with
|
|
17
|
+
* no trust prompt, no TUI — precisely the "session has no live pty" a user
|
|
18
|
+
* sees with zero indication why).
|
|
19
|
+
*/
|
|
20
|
+
import { execFile } from "node:child_process";
|
|
21
|
+
import { readFileSync } from "node:fs";
|
|
22
|
+
import { open, readdir, stat } from "node:fs/promises";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { basename, join } from "node:path";
|
|
25
|
+
import { promisify } from "node:util";
|
|
26
|
+
const execFileAsync = promisify(execFile);
|
|
27
|
+
/** Bytes read from the head of a rollout file to find its session_meta line
|
|
28
|
+
* and an early title candidate. Unlike Claude's transcripts (where a summary
|
|
29
|
+
* lands near the end), Codex has no end-of-session summary line, so the
|
|
30
|
+
* earliest user message is the best available title — and it's always near
|
|
31
|
+
* the start of the file. */
|
|
32
|
+
const ROLLOUT_HEAD_BYTES = 65536;
|
|
33
|
+
const MAX_SCAN_DEPTH = 4; // ~/.codex/sessions/YYYY/MM/DD/*.jsonl
|
|
34
|
+
/**
|
|
35
|
+
* Matches Codex's directory-trust confirmation prompt ("Do you trust the
|
|
36
|
+
* contents of this directory? ... 1. Yes, continue 2. No, quit"), after
|
|
37
|
+
* `stripAnsi` — confirmed against a real capture: Codex's TUI positions
|
|
38
|
+
* each *word* with its own cursor-addressing escape sequence instead of
|
|
39
|
+
* emitting literal spaces between them (`trust\x1b[3;16Hthe\x1b[3;20H...`),
|
|
40
|
+
* so even a stripped buffer runs the words together with no separator at
|
|
41
|
+
* all; `\s*` between them tolerates that (and ordinary whitespace, in case
|
|
42
|
+
* a future TUI revision renders differently). See `detectBlockingPrompt`
|
|
43
|
+
* for why this exists at all (Codex's own structured readiness signal can't
|
|
44
|
+
* be relied on standalone, unlike Claude Code's).
|
|
45
|
+
*/
|
|
46
|
+
const TRUST_PROMPT_PATTERN = /trust\s*the\s*contents\s*of\s*this\s*directory/i;
|
|
47
|
+
/** Strips ANSI/CSI/OSC control sequences a pty stream is otherwise full of,
|
|
48
|
+
* so text pattern-matching (e.g. `TRUST_PROMPT_PATTERN`) sees the words a
|
|
49
|
+
* human would actually read rather than raw terminal-control bytes. Not
|
|
50
|
+
* exhaustive of every escape sequence a TUI could emit, but covers what
|
|
51
|
+
* Codex's TUI is confirmed (via real captures) to use: CSI (cursor moves,
|
|
52
|
+
* SGR color/style) and OSC (window title) sequences. */
|
|
53
|
+
export function stripAnsi(text) {
|
|
54
|
+
/* eslint-disable no-control-regex -- matching literal ESC (\x1b) / BEL
|
|
55
|
+
* (\x07) bytes is the entire point of an ANSI-sequence stripper; there's
|
|
56
|
+
* no non-control-character way to express "a real escape sequence". */
|
|
57
|
+
return text
|
|
58
|
+
// Lazy (`*?`), not greedy: a greedy `[^\x07]*` doesn't exclude `\x1b\\`
|
|
59
|
+
// (ST) from what it can consume, so it backtracks from the END of the
|
|
60
|
+
// whole string looking for the last reachable terminator instead of the
|
|
61
|
+
// next one — silently swallowing everything (including real prompt
|
|
62
|
+
// text) between an OSC sequence and some unrelated, much-later BEL/ST.
|
|
63
|
+
// Confirmed against a real capture: this exact bug made the trust-
|
|
64
|
+
// prompt regex below never match a full multi-KB scrollback buffer.
|
|
65
|
+
.replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, "") // OSC ... BEL or ST
|
|
66
|
+
.replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, "") // CSI sequences
|
|
67
|
+
.replace(/\x1b[()>=][A-Za-z0-9]?/g, ""); // charset/keypad-mode selection
|
|
68
|
+
/* eslint-enable no-control-regex */
|
|
69
|
+
}
|
|
70
|
+
/** Read only the head of a (possibly huge) rollout file and extract its
|
|
71
|
+
* `session_meta` entry. Codex always writes `session_meta` as the first
|
|
72
|
+
* line, but this tolerates a few leading blank/malformed lines defensively. */
|
|
73
|
+
async function readSessionMeta(filePath, maxBytes = ROLLOUT_HEAD_BYTES) {
|
|
74
|
+
let content;
|
|
75
|
+
try {
|
|
76
|
+
const handle = await open(filePath, "r");
|
|
77
|
+
try {
|
|
78
|
+
const { size } = await handle.stat();
|
|
79
|
+
const length = Math.min(size, maxBytes);
|
|
80
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
81
|
+
await handle.read(buffer, 0, length, 0);
|
|
82
|
+
content = buffer.toString("utf8");
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await handle.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
for (const line of content.split("\n").slice(0, 20)) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
continue;
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(trimmed);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (parsed.type !== "session_meta")
|
|
103
|
+
continue;
|
|
104
|
+
const payload = parsed.payload;
|
|
105
|
+
const id = typeof payload?.id === "string" ? payload.id : undefined;
|
|
106
|
+
const cwd = typeof payload?.cwd === "string" ? payload.cwd : undefined;
|
|
107
|
+
if (!id || !cwd)
|
|
108
|
+
return null;
|
|
109
|
+
const timestamp = typeof payload?.timestamp === "string" ? Date.parse(payload.timestamp) : NaN;
|
|
110
|
+
return { id, cwd, timestampMs: Number.isNaN(timestamp) ? null : timestamp };
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/** First `event_msg`/`user_message` found in the head of the file, truncated
|
|
115
|
+
* for use as a session title. Falls back to `fallback` when none is found
|
|
116
|
+
* within the head window (long system-prompt-only sessions, mid-tail-cut). */
|
|
117
|
+
function extractTitleFromHead(content, fallback) {
|
|
118
|
+
for (const line of content.split("\n")) {
|
|
119
|
+
const trimmed = line.trim();
|
|
120
|
+
if (!trimmed)
|
|
121
|
+
continue;
|
|
122
|
+
let parsed;
|
|
123
|
+
try {
|
|
124
|
+
parsed = JSON.parse(trimmed);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (parsed.type !== "event_msg" || parsed.payload?.type !== "user_message")
|
|
130
|
+
continue;
|
|
131
|
+
const message = parsed.payload.message;
|
|
132
|
+
if (typeof message === "string" && message.trim()) {
|
|
133
|
+
const text = message.trim();
|
|
134
|
+
return text.length > 120 ? `${text.slice(0, 120)}...` : text;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return fallback;
|
|
138
|
+
}
|
|
139
|
+
/** Recursively collect `.jsonl` files under Codex's date-sharded sessions
|
|
140
|
+
* root (`YYYY/MM/DD/rollout-*.jsonl`). Bounded depth as a safety guard
|
|
141
|
+
* against unexpectedly deep/cyclical directory structures. */
|
|
142
|
+
async function collectRolloutFiles(dir, depth = 0) {
|
|
143
|
+
if (depth > MAX_SCAN_DEPTH)
|
|
144
|
+
return [];
|
|
145
|
+
let entries;
|
|
146
|
+
try {
|
|
147
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
const results = [];
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
const fullPath = join(dir, entry.name);
|
|
155
|
+
if (entry.isDirectory()) {
|
|
156
|
+
results.push(...(await collectRolloutFiles(fullPath, depth + 1)));
|
|
157
|
+
}
|
|
158
|
+
else if (entry.name.endsWith(".jsonl")) {
|
|
159
|
+
results.push(fullPath);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return results;
|
|
163
|
+
}
|
|
164
|
+
export class CodexAdapter {
|
|
165
|
+
constructor(options = {}) {
|
|
166
|
+
this.id = "codex";
|
|
167
|
+
this.eventSource = "transcript-tail";
|
|
168
|
+
this.binary = options.binary ?? "codex";
|
|
169
|
+
this.homeDir = options.homeDir ?? homedir();
|
|
170
|
+
}
|
|
171
|
+
async doctor() {
|
|
172
|
+
try {
|
|
173
|
+
const { stdout } = await execFileAsync(this.binary, ["--version"], { timeout: 5000 });
|
|
174
|
+
return [{ name: "codex", ok: true, detail: stdout.trim() || "installed" }];
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return [
|
|
178
|
+
{
|
|
179
|
+
name: "codex",
|
|
180
|
+
ok: false,
|
|
181
|
+
detail: `\`${this.binary}\` not found on PATH. Install: https://github.com/openai/codex`,
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
launch(opts) {
|
|
187
|
+
return {
|
|
188
|
+
command: this.binary,
|
|
189
|
+
args: buildConfigArgs(opts),
|
|
190
|
+
// Codex has no analog to Claude's CLAUDECODE nested-agent guard; no env
|
|
191
|
+
// overrides are needed for a fresh launch.
|
|
192
|
+
env: {},
|
|
193
|
+
cwd: opts.cwd,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
resume(agentSessionId, opts) {
|
|
197
|
+
return {
|
|
198
|
+
command: this.binary,
|
|
199
|
+
args: ["resume", agentSessionId, ...buildConfigArgs(opts)],
|
|
200
|
+
env: {},
|
|
201
|
+
cwd: opts.cwd,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* See `HarnessAdapter.detectBlockingPrompt`. Codex needs this (Claude Code
|
|
206
|
+
* doesn't) because its rollout file — and therefore the SessionStart-
|
|
207
|
+
* equivalent event `SessionManager` otherwise waits on — isn't written
|
|
208
|
+
* until the *first* turn is actually submitted, confirmed empirically
|
|
209
|
+
* against a real `codex-cli 0.134.0`: an idle, fully-interactive session
|
|
210
|
+
* (trust prompt already accepted, composer visibly ready) produces zero
|
|
211
|
+
* files under `~/.codex/sessions` for as long as nothing is submitted.
|
|
212
|
+
* That real signal therefore can't arrive before the very first
|
|
213
|
+
* injection that needs it — a scrollback check is the only thing that
|
|
214
|
+
* can stand in for it up to that point.
|
|
215
|
+
*/
|
|
216
|
+
detectBlockingPrompt(scrollback) {
|
|
217
|
+
return TRUST_PROMPT_PATTERN.test(stripAnsi(scrollback));
|
|
218
|
+
}
|
|
219
|
+
async listPastSessions(cwd) {
|
|
220
|
+
const root = join(this.homeDir, ".codex", "sessions");
|
|
221
|
+
const files = await collectRolloutFiles(root);
|
|
222
|
+
const summaries = [];
|
|
223
|
+
for (const filePath of files) {
|
|
224
|
+
const meta = await readSessionMeta(filePath);
|
|
225
|
+
if (!meta || meta.cwd !== cwd)
|
|
226
|
+
continue;
|
|
227
|
+
const fileStat = await stat(filePath).catch(() => undefined);
|
|
228
|
+
const lastActiveAt = fileStat ? fileStat.mtime.toISOString() : new Date(0).toISOString();
|
|
229
|
+
let title = basename(filePath, ".jsonl");
|
|
230
|
+
try {
|
|
231
|
+
const handle = await open(filePath, "r");
|
|
232
|
+
try {
|
|
233
|
+
const { size } = await handle.stat();
|
|
234
|
+
const length = Math.min(size, ROLLOUT_HEAD_BYTES);
|
|
235
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
236
|
+
await handle.read(buffer, 0, length, 0);
|
|
237
|
+
title = extractTitleFromHead(buffer.toString("utf8"), meta.id);
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
await handle.close();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// Fall back to the rollout id as the title.
|
|
245
|
+
}
|
|
246
|
+
summaries.push({
|
|
247
|
+
agentSessionId: meta.id,
|
|
248
|
+
harness: "codex",
|
|
249
|
+
cwd,
|
|
250
|
+
title,
|
|
251
|
+
lastActiveAt,
|
|
252
|
+
source: "transcript",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return summaries.sort((a, b) => (a.lastActiveAt < b.lastActiveAt ? 1 : -1));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Codex has no single-flag equivalent to Claude's `--append-system-prompt` /
|
|
260
|
+
* `--mcp-config` — MCP servers are registered globally via `codex mcp add`
|
|
261
|
+
* (a persistent config.toml mutation, which the harness's "zero config
|
|
262
|
+
* mutation" design deliberately avoids), so `opts.mcpConfigFile` /
|
|
263
|
+
* `opts.settingsFile` are intentionally unused here. System-prompt injection
|
|
264
|
+
* uses the generic `-c key=value` override mechanism instead.
|
|
265
|
+
*
|
|
266
|
+
* Confirmed against a locally installed codex-cli 0.134.0: `-c
|
|
267
|
+
* model_instructions_file=<path>` is a real, recognized key — but if that
|
|
268
|
+
* path is missing/unreadable at codex's own startup (a moment we don't
|
|
269
|
+
* control, in a separate process), codex exits immediately with a config
|
|
270
|
+
* error, no trust prompt, no TUI — which reads to a user as the session
|
|
271
|
+
* dying instantly. Since we already have the prompt's content in hand
|
|
272
|
+
* (we're the ones who generated the file), embedding it inline via
|
|
273
|
+
* `developer_instructions=<value>` instead removes that dependency
|
|
274
|
+
* entirely: nothing for codex to fail to (re)read. `-c` values parse as
|
|
275
|
+
* TOML, and TOML basic strings share JSON's escaping rules for control
|
|
276
|
+
* characters/quotes/backslashes, so `JSON.stringify` produces a valid TOML
|
|
277
|
+
* string literal here — confirmed with a real multiline prompt.
|
|
278
|
+
*
|
|
279
|
+
* If even this read fails (the file we just generated is somehow gone by
|
|
280
|
+
* the time we get here — a narrow race, but still no reason to crash the
|
|
281
|
+
* whole session over an optional prompt), fall back to launching without
|
|
282
|
+
* one rather than passing a broken reference that's guaranteed to kill the
|
|
283
|
+
* process on startup.
|
|
284
|
+
*/
|
|
285
|
+
function buildConfigArgs(opts) {
|
|
286
|
+
const args = [
|
|
287
|
+
"-c",
|
|
288
|
+
"check_for_update_on_startup=false",
|
|
289
|
+
// Codex's default approval policy interrupts the session with "Would you
|
|
290
|
+
// like to run the following command?" prompts (even for read-only
|
|
291
|
+
// commands like `ps`). Harness sessions are expected to run without
|
|
292
|
+
// approval interruptions — the analog of Claude Code sessions, which the
|
|
293
|
+
// harness runs in auto mode — so pin codex's non-interactive pairing:
|
|
294
|
+
// never ask for approval, and confine writes to the workspace via the OS
|
|
295
|
+
// sandbox instead of via per-command human review. Both keys and their
|
|
296
|
+
// values confirmed against a locally installed codex-cli 0.134.0 (its
|
|
297
|
+
// config deserializer enumerates `never` / `workspace-write` among the
|
|
298
|
+
// accepted variants when probed with a bogus value). `-c` values parse
|
|
299
|
+
// as TOML, so the string values need the embedded quotes.
|
|
300
|
+
"-c",
|
|
301
|
+
'approval_policy="never"',
|
|
302
|
+
"-c",
|
|
303
|
+
'sandbox_mode="workspace-write"',
|
|
304
|
+
];
|
|
305
|
+
if (opts.systemPromptFile) {
|
|
306
|
+
try {
|
|
307
|
+
const prompt = readFileSync(opts.systemPromptFile, "utf8");
|
|
308
|
+
args.push("-c", `developer_instructions=${JSON.stringify(prompt)}`);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
console.error(`[codex adapter] could not read systemPromptFile "${opts.systemPromptFile}" — launching without an injected system prompt: ${err.message}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return args;
|
|
315
|
+
}
|
|
316
|
+
export function createCodexAdapter(options) {
|
|
317
|
+
return new CodexAdapter(options);
|
|
318
|
+
}
|
|
319
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../../src/core/adapters/codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAUtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C;;;;4BAI4B;AAC5B,MAAM,kBAAkB,GAAG,KAAM,CAAC;AAClC,MAAM,cAAc,GAAG,CAAC,CAAC,CAAC,uCAAuC;AAEjE;;;;;;;;;;;GAWG;AACH,MAAM,oBAAoB,GAAG,iDAAiD,CAAC;AAE/E;;;;;yDAKyD;AACzD,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC;;2EAEuE;IACvE,OAAO,IAAI;QACT,wEAAwE;QACxE,sEAAsE;QACtE,wEAAwE;QACxE,mEAAmE;QACnE,uEAAuE;QACvE,mEAAmE;QACnE,oEAAoE;SACnE,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC,oBAAoB;SAClE,OAAO,CAAC,yBAAyB,EAAE,EAAE,CAAC,CAAC,gBAAgB;SACvD,OAAO,CAAC,yBAAyB,EAAE,EAAE,CAAC,CAAC,CAAC,gCAAgC;IAC3E,oCAAoC;AACtC,CAAC;AAoBD;;+EAE+E;AAC/E,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,QAAQ,GAAG,kBAAkB;IAC5E,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACxC,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;YACxC,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;QACpD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,IAAI,MAAmB,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc;YAAE,SAAS;QAC7C,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC/B,MAAM,EAAE,GAAG,OAAO,OAAO,EAAE,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACpE,MAAM,GAAG,GAAG,OAAO,OAAO,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QACvE,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,SAAS,GAAG,OAAO,OAAO,EAAE,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAC/F,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IAC9E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;8EAE8E;AAC9E,SAAS,oBAAoB,CAAC,OAAe,EAAE,QAAgB;IAC7D,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,IAAI,MAAmB,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,IAAI,MAAM,CAAC,OAAO,EAAE,IAAI,KAAK,cAAc;YAAE,SAAS;QACrF,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QACvC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAClD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;8DAE8D;AAC9D,KAAK,UAAU,mBAAmB,CAAC,GAAW,EAAE,KAAK,GAAG,CAAC;IACvD,IAAI,KAAK,GAAG,cAAc;QAAE,OAAO,EAAE,CAAC;IACtC,IAAI,OAAmC,CAAC;IACxC,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,mBAAmB,CAAC,QAAQ,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,CAAC;aAAM,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,OAAO,YAAY;IAMvB,YAAY,UAA+B,EAAE;QALpC,OAAE,GAAG,OAAgB,CAAC;QACtB,gBAAW,GAAG,iBAA0B,CAAC;QAKhD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC;QACxC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,EAAE,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,MAAM;QACV,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,OAAO,EAAE,IAAK,EAAE,CAAC,CAAC;YACvF,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,CAAC;QAC7E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL;oBACE,IAAI,EAAE,OAAO;oBACb,EAAE,EAAE,KAAK;oBACT,MAAM,EAAE,KAAK,IAAI,CAAC,MAAM,gEAAgE;iBACzF;aACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAgB;QACrB,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,MAAM;YACpB,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC;YAC3B,wEAAwE;YACxE,2CAA2C;YAC3C,GAAG,EAAE,EAAE;YACP,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,cAAsB,EAAE,IAAgB;QAC7C,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,MAAM;YACpB,IAAI,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;YAC1D,GAAG,EAAE,EAAE;YACP,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;OAWG;IACH,oBAAoB,CAAC,UAAkB;QACrC,OAAO,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,GAAW;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAE9C,MAAM,SAAS,GAAqB,EAAE,CAAC;QACvC,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;YAC7C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,GAAG;gBAAE,SAAS;YAExC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC7D,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAEzF,IAAI,KAAK,GAAG,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACzC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;gBACzC,IAAI,CAAC;oBACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;oBACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;oBAClD,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;oBAC1C,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;oBACxC,KAAK,GAAG,oBAAoB,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;gBACjE,CAAC;wBAAS,CAAC;oBACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;gBACvB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,4CAA4C;YAC9C,CAAC;YAED,SAAS,CAAC,IAAI,CAAC;gBACb,cAAc,EAAE,IAAI,CAAC,EAAE;gBACvB,OAAO,EAAE,OAAO;gBAChB,GAAG;gBACH,KAAK;gBACL,YAAY;gBACZ,MAAM,EAAE,YAAY;aACrB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,CAAC;CACF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAS,eAAe,CAAC,IAAgB;IACvC,MAAM,IAAI,GAAG;QACX,IAAI;QACJ,mCAAmC;QACnC,yEAAyE;QACzE,kEAAkE;QAClE,oEAAoE;QACpE,yEAAyE;QACzE,sEAAsE;QACtE,yEAAyE;QACzE,uEAAuE;QACvE,sEAAsE;QACtE,uEAAuE;QACvE,uEAAuE;QACvE,0DAA0D;QAC1D,IAAI;QACJ,yBAAyB;QACzB,IAAI;QACJ,gCAAgC;KACjC,CAAC;IACF,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;YAC3D,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,0BAA0B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CACX,oDAAoD,IAAI,CAAC,gBAAgB,oDAAqD,GAAa,CAAC,OAAO,EAAE,CACtJ,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAA6B;IAC9D,OAAO,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assembles the `<div id="canvas-root">` body — the bound workflow's
|
|
3
|
+
* `<section class="canvas-panel">` (header + stats + SVG diagram) and a
|
|
4
|
+
* legend footer — entirely from the classes `core/canvas-template.ts`'s shell
|
|
5
|
+
* already defines. This is the HTML half of the deterministic render;
|
|
6
|
+
* `core/canvas-render.ts` wraps the result through `renderCanvasDocument()`
|
|
7
|
+
* and writes it to the workflow's render file.
|
|
8
|
+
*/
|
|
9
|
+
import type { CanvasEdgeKind, CanvasGraph, CanvasNodeKind } from "./canvas-graph.js";
|
|
10
|
+
import type { CanvasEnrichment } from "./canvas-enrichment.js";
|
|
11
|
+
export interface WorkflowPanelMeta {
|
|
12
|
+
title: string;
|
|
13
|
+
badges?: string[];
|
|
14
|
+
}
|
|
15
|
+
/** How enrichment content reaches the panel/footer builders: the validated
|
|
16
|
+
* content itself plus whether its source fingerprint no longer matches the
|
|
17
|
+
* workflow's current sources (kept displayed, marked stale). */
|
|
18
|
+
export interface PanelEnrichment {
|
|
19
|
+
enrichment: CanvasEnrichment;
|
|
20
|
+
stale: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** One workflow's full panel: title/badges header, step/entry stats, SVG
|
|
23
|
+
* diagram — with any enrichment merged in (summary line, node/edge
|
|
24
|
+
* annotations, a "stale" chip when its fingerprint no longer matches). */
|
|
25
|
+
export declare function buildWorkflowPanelHtml(graph: CanvasGraph, meta: WorkflowPanelMeta, panelEnrichment?: PanelEnrichment | null): string;
|
|
26
|
+
/** A degraded panel for a workflow whose graph couldn't be extracted — never
|
|
27
|
+
* a crash, never a silent fallback to the LLM path, just an honest reason
|
|
28
|
+
* styled through the same shell. */
|
|
29
|
+
export declare function buildErrorPanelHtml(title: string, reason: string): string;
|
|
30
|
+
/** Legend footer covering every node/edge kind actually used across every
|
|
31
|
+
* rendered panel (aggregate, not per-panel — matches the template's shape). */
|
|
32
|
+
export declare function buildLegendHtml(nodeKinds: Set<CanvasNodeKind>, edgeKinds: Set<CanvasEdgeKind>): string;
|
|
33
|
+
/** Joins panels + a footer (enrichment notes/cross-workflow tie, legend,
|
|
34
|
+
* note) into the final `#canvas-root` body — the string
|
|
35
|
+
* `renderCanvasDocument()` wraps. */
|
|
36
|
+
export declare function assembleCanvasBody(input: {
|
|
37
|
+
panels: string[];
|
|
38
|
+
legend: string;
|
|
39
|
+
note: string;
|
|
40
|
+
/** Enrichment footer facts (already schema-bounded), rendered as a list. */
|
|
41
|
+
notes?: string[];
|
|
42
|
+
/** Enrichment's "how this ties into the other workflows" line. */
|
|
43
|
+
crossWorkflow?: string;
|
|
44
|
+
}): string;
|
|
45
|
+
//# sourceMappingURL=canvas-body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-body.d.ts","sourceRoot":"","sources":["../../src/core/canvas-body.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAO/D,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;iEAEiE;AACjE,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,gBAAgB,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;2EAE2E;AAC3E,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,iBAAiB,EACvB,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,GACvC,MAAM,CA4BR;AAED;;qCAEqC;AACrC,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAYzE;AAmBD;gFACgF;AAChF,wBAAgB,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,GAAG,MAAM,CAWtG;AAED;;sCAEsC;AACtC,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,MAAM,CAcT"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { renderGraphSvg } from "./canvas-svg.js";
|
|
2
|
+
function esc(s) {
|
|
3
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4
|
+
}
|
|
5
|
+
/** One workflow's full panel: title/badges header, step/entry stats, SVG
|
|
6
|
+
* diagram — with any enrichment merged in (summary line, node/edge
|
|
7
|
+
* annotations, a "stale" chip when its fingerprint no longer matches). */
|
|
8
|
+
export function buildWorkflowPanelHtml(graph, meta, panelEnrichment) {
|
|
9
|
+
const badges = (meta.badges ?? [])
|
|
10
|
+
.map((b) => `<span class="canvas-badge">${esc(b)}</span>`)
|
|
11
|
+
.join("");
|
|
12
|
+
const warningBadge = graph.warnings.length > 0
|
|
13
|
+
? `<span class="canvas-badge">${graph.warnings.length} warning${graph.warnings.length === 1 ? "" : "s"}</span>`
|
|
14
|
+
: "";
|
|
15
|
+
const enrichment = panelEnrichment?.enrichment ?? null;
|
|
16
|
+
const staleBadge = panelEnrichment?.stale
|
|
17
|
+
? `<span class="canvas-badge canvas-badge--stale">stale — Refresh</span>`
|
|
18
|
+
: "";
|
|
19
|
+
const summary = enrichment?.summary ? `\n <p class="canvas-subtitle">${esc(enrichment.summary)}</p>` : "";
|
|
20
|
+
return `<section class="canvas-panel">
|
|
21
|
+
<header class="canvas-header">
|
|
22
|
+
<div class="canvas-title-row">
|
|
23
|
+
<h1 class="canvas-title">${esc(meta.title)}</h1>
|
|
24
|
+
${badges}${warningBadge}${staleBadge}
|
|
25
|
+
</div>${summary}
|
|
26
|
+
<div class="canvas-stats">
|
|
27
|
+
<div class="canvas-stat"><span class="canvas-stat-value">${graph.nodes.length}</span><span class="canvas-stat-label">steps</span></div>
|
|
28
|
+
<div class="canvas-stat"><span class="canvas-stat-value">${esc(graph.entry)}</span><span class="canvas-stat-label">entry</span></div>
|
|
29
|
+
</div>
|
|
30
|
+
</header>
|
|
31
|
+
<div class="canvas-diagram-panel">
|
|
32
|
+
${renderGraphSvg(graph, enrichment)}
|
|
33
|
+
</div>
|
|
34
|
+
</section>`;
|
|
35
|
+
}
|
|
36
|
+
/** A degraded panel for a workflow whose graph couldn't be extracted — never
|
|
37
|
+
* a crash, never a silent fallback to the LLM path, just an honest reason
|
|
38
|
+
* styled through the same shell. */
|
|
39
|
+
export function buildErrorPanelHtml(title, reason) {
|
|
40
|
+
return `<section class="canvas-panel">
|
|
41
|
+
<header class="canvas-header">
|
|
42
|
+
<div class="canvas-title-row">
|
|
43
|
+
<h1 class="canvas-title">${esc(title)}</h1>
|
|
44
|
+
<span class="canvas-badge">render failed</span>
|
|
45
|
+
</div>
|
|
46
|
+
</header>
|
|
47
|
+
<div class="canvas-diagram-panel">
|
|
48
|
+
<p class="canvas-empty-note">Could not extract this workflow's step graph: ${esc(reason)}. Ask your agent to fix the issue (see the terminal for details) — this pane updates automatically once it builds cleanly.</p>
|
|
49
|
+
</div>
|
|
50
|
+
</section>`;
|
|
51
|
+
}
|
|
52
|
+
const NODE_KIND_LABEL = {
|
|
53
|
+
entry: "entry / active step",
|
|
54
|
+
step: "step",
|
|
55
|
+
pause: "pause / waits for input",
|
|
56
|
+
"terminal-success": "terminal · success",
|
|
57
|
+
"terminal-warn": "terminal · escalation",
|
|
58
|
+
"launched-workflow": "launches another workflow",
|
|
59
|
+
};
|
|
60
|
+
const NODE_KIND_ORDER = [
|
|
61
|
+
"entry",
|
|
62
|
+
"step",
|
|
63
|
+
"pause",
|
|
64
|
+
"terminal-success",
|
|
65
|
+
"terminal-warn",
|
|
66
|
+
"launched-workflow",
|
|
67
|
+
];
|
|
68
|
+
/** Legend footer covering every node/edge kind actually used across every
|
|
69
|
+
* rendered panel (aggregate, not per-panel — matches the template's shape). */
|
|
70
|
+
export function buildLegendHtml(nodeKinds, edgeKinds) {
|
|
71
|
+
const items = NODE_KIND_ORDER.filter((k) => nodeKinds.has(k)).map((k) => `<span class="canvas-legend-item"><span class="canvas-legend-marker canvas-legend-marker--${k}"></span>${NODE_KIND_LABEL[k]}</span>`);
|
|
72
|
+
if (edgeKinds.has("cross")) {
|
|
73
|
+
items.push(`<span class="canvas-legend-item"><span class="canvas-legend-marker canvas-legend-marker--cross"></span>cross-workflow signal/handoff</span>`);
|
|
74
|
+
}
|
|
75
|
+
return `<div class="canvas-legend">${items.join("")}</div>`;
|
|
76
|
+
}
|
|
77
|
+
/** Joins panels + a footer (enrichment notes/cross-workflow tie, legend,
|
|
78
|
+
* note) into the final `#canvas-root` body — the string
|
|
79
|
+
* `renderCanvasDocument()` wraps. */
|
|
80
|
+
export function assembleCanvasBody(input) {
|
|
81
|
+
const parts = [...input.panels];
|
|
82
|
+
const notesHtml = input.notes && input.notes.length > 0
|
|
83
|
+
? `\n <ul class="canvas-notes">${input.notes.map((n) => `<li>${esc(n)}</li>`).join("")}</ul>`
|
|
84
|
+
: "";
|
|
85
|
+
const crossHtml = input.crossWorkflow
|
|
86
|
+
? `\n <p class="canvas-cross-workflow">${esc(input.crossWorkflow)}</p>`
|
|
87
|
+
: "";
|
|
88
|
+
parts.push(`<footer class="canvas-footer">${notesHtml}${crossHtml}
|
|
89
|
+
${input.legend}
|
|
90
|
+
<p class="canvas-note">${esc(input.note)}</p>
|
|
91
|
+
</footer>`);
|
|
92
|
+
return parts.join("\n\n");
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=canvas-body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-body.js","sourceRoot":"","sources":["../../src/core/canvas-body.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ExtractionResult } from "./canvas-graph.js";
|
|
2
|
+
/** `<file count>:<newest mtimeMs>` over the workflow's own sources. */
|
|
3
|
+
export declare function fingerprintWorkflowSources(root: string): Promise<string>;
|
|
4
|
+
export interface CachedExtraction {
|
|
5
|
+
result: ExtractionResult;
|
|
6
|
+
/** True when the result came from cache — no child process ran. */
|
|
7
|
+
cached: boolean;
|
|
8
|
+
/** The source fingerprint the result corresponds to — the same value the
|
|
9
|
+
* enrichment cache stores as `sourceFingerprint`, so freshness checks
|
|
10
|
+
* compare like with like. */
|
|
11
|
+
fingerprint: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `extractWorkflowGraph` behind the fingerprint cache. The `extract`
|
|
15
|
+
* parameter exists for tests only (inject a spy to prove hit/miss behavior).
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractWorkflowGraphCached(sourceDir: string, extract?: (dir: string) => Promise<ExtractionResult>): Promise<CachedExtraction>;
|
|
18
|
+
/** Drops one workflow's cached extraction — the visualize macro's force
|
|
19
|
+
* refresh, which must re-run the child process even for unchanged sources. */
|
|
20
|
+
export declare function invalidateExtractionCache(sourceDir: string): void;
|
|
21
|
+
/** Test hook — the cache is module-level state shared across a process. */
|
|
22
|
+
export declare function clearExtractionCache(): void;
|
|
23
|
+
//# sourceMappingURL=canvas-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-cache.d.ts","sourceRoot":"","sources":["../../src/core/canvas-cache.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAwB,KAAK,gBAAgB,EAA0B,MAAM,mBAAmB,CAAC;AAGxG,uEAAuE;AACvE,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAa9E;AASD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,gBAAgB,CAAC;IACzB,mEAAmE;IACnE,MAAM,EAAE,OAAO,CAAC;IAChB;;kCAE8B;IAC9B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAwB,GACzE,OAAO,CAAC,gBAAgB,CAAC,CAU3B;AAED;+EAC+E;AAC/E,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEjE;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,IAAI,IAAI,CAE3C"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory extraction cache: binding an unchanged workflow must never pay
|
|
3
|
+
* for a child `check()` process twice. Keyed by workflow directory, guarded
|
|
4
|
+
* by a cheap source fingerprint (file count + newest mtime over the
|
|
5
|
+
* project's own `.ts`/`.tsx` sources — the same file walk the launch grep
|
|
6
|
+
* uses, see core/canvas-interconnections.ts's `listSourceFiles`). Any source
|
|
7
|
+
* edit bumps an mtime, any add/remove changes the count; either invalidates.
|
|
8
|
+
*
|
|
9
|
+
* Only SUCCESSFUL extractions are cached: a failure like "run npm install
|
|
10
|
+
* first" gets fixed without touching any `.ts` file, so a cached failure
|
|
11
|
+
* would never self-invalidate — re-running the (already fast-failing)
|
|
12
|
+
* extraction is the honest choice there.
|
|
13
|
+
*
|
|
14
|
+
* Process-lifetime only; a server restart re-extracts once per workflow.
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from "node:fs/promises";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
import { extractWorkflowGraph } from "./canvas-graph.js";
|
|
19
|
+
import { listSourceFiles } from "./canvas-interconnections.js";
|
|
20
|
+
/** `<file count>:<newest mtimeMs>` over the workflow's own sources. */
|
|
21
|
+
export async function fingerprintWorkflowSources(root) {
|
|
22
|
+
const files = await listSourceFiles(root);
|
|
23
|
+
let maxMtimeMs = 0;
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
try {
|
|
26
|
+
const stat = await fs.stat(file);
|
|
27
|
+
if (stat.mtimeMs > maxMtimeMs)
|
|
28
|
+
maxMtimeMs = stat.mtimeMs;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// A file deleted mid-walk still counts toward the file count; the next
|
|
32
|
+
// fingerprint won't include it, which is invalidation working as intended.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return `${files.length}:${maxMtimeMs}`;
|
|
36
|
+
}
|
|
37
|
+
const cache = new Map();
|
|
38
|
+
/**
|
|
39
|
+
* `extractWorkflowGraph` behind the fingerprint cache. The `extract`
|
|
40
|
+
* parameter exists for tests only (inject a spy to prove hit/miss behavior).
|
|
41
|
+
*/
|
|
42
|
+
export async function extractWorkflowGraphCached(sourceDir, extract = extractWorkflowGraph) {
|
|
43
|
+
const key = path.resolve(sourceDir);
|
|
44
|
+
const fingerprint = await fingerprintWorkflowSources(key);
|
|
45
|
+
const hit = cache.get(key);
|
|
46
|
+
if (hit && hit.fingerprint === fingerprint)
|
|
47
|
+
return { result: hit.result, cached: true, fingerprint };
|
|
48
|
+
const result = await extract(key);
|
|
49
|
+
if (result.ok)
|
|
50
|
+
cache.set(key, { fingerprint, result });
|
|
51
|
+
else
|
|
52
|
+
cache.delete(key);
|
|
53
|
+
return { result, cached: false, fingerprint };
|
|
54
|
+
}
|
|
55
|
+
/** Drops one workflow's cached extraction — the visualize macro's force
|
|
56
|
+
* refresh, which must re-run the child process even for unchanged sources. */
|
|
57
|
+
export function invalidateExtractionCache(sourceDir) {
|
|
58
|
+
cache.delete(path.resolve(sourceDir));
|
|
59
|
+
}
|
|
60
|
+
/** Test hook — the cache is module-level state shared across a process. */
|
|
61
|
+
export function clearExtractionCache() {
|
|
62
|
+
cache.clear();
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=canvas-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-cache.js","sourceRoot":"","sources":["../../src/core/canvas-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAiD,MAAM,mBAAmB,CAAC;AACxG,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,IAAY;IAC3D,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,IAAI,IAAI,CAAC,OAAO,GAAG,UAAU;gBAAE,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;YACvE,2EAA2E;QAC7E,CAAC;IACH,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,MAAM,IAAI,UAAU,EAAE,CAAC;AACzC,CAAC;AAOD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;AAY5C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,SAAiB,EACjB,UAAsD,oBAAoB;IAE1E,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,MAAM,0BAA0B,CAAC,GAAG,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,GAAG,IAAI,GAAG,CAAC,WAAW,KAAK,WAAW;QAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAErG,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;;QAClD,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;AAChD,CAAC;AAED;+EAC+E;AAC/E,MAAM,UAAU,yBAAyB,CAAC,SAAiB;IACzD,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;AACxC,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,oBAAoB;IAClC,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
|