@tekmidian/pai 0.37.0 → 0.38.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers-DUshcB-d.mjs","names":["psOutput","readKey","readKey"],"sources":["../src/config/json-store.ts","../src/workers/config.ts","../src/workers/paths.ts","../src/workers/args.ts","../src/workers/ledger.ts","../src/workers/status.ts","../src/workers/scope.ts","../src/workers/routing.ts","../src/workers/pane.ts","../src/workers/report.ts","../src/workers/mcp.ts","../src/workers/operator.ts","../src/workers/proxy/server.ts","../src/workers/codex.ts","../src/workers/run.ts","../src/workers/chain.ts","../src/workers/chatui.ts","../src/workers/render.ts","../src/workers/viewer.ts","../src/workers/providers.ts"],"sourcesContent":["/**\n * json-store.ts — read/write JSON config files without destroying them\n *\n * The failure this exists to prevent:\n *\n * try { return JSON.parse(read(path)); } catch { return {}; }\n * ... later ...\n * write(path, JSON.stringify(ourData));\n *\n * An unreadable file becomes an empty object, and the next write makes that\n * permanent. Silently, exit code 0. This shape appeared three times in this\n * repo against three different files, and twice in AIBroker.\n *\n * The distinction that matters is between *missing* and *unreadable*:\n *\n * missing — legitimate first run. Start fresh; writing is safe.\n * unreadable — the file exists and we could not parse it. Those bytes are\n * the only copy of something. Never overwrite them.\n *\n * Collapsing the second into the first is the bug.\n *\n * NOT everything deserves this guard. For a transient buffer — an undelivered\n * message queue, a cache — starting fresh IS the correct recovery, and\n * refusing to write would disable the feature permanently. Use `writeJsonAtomic`\n * alone there: it still prevents a crash from truncating a good file, without\n * blocking recovery. Reserve `readJsonStrict` for data a user cannot rebuild.\n */\n\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n copyFileSync,\n renameSync,\n unlinkSync,\n mkdirSync,\n} from \"node:fs\";\nimport { dirname } from \"node:path\";\n\n/**\n * Read a JSON file, distinguishing \"absent\" from \"damaged\".\n *\n * @param path file to read\n * @param label how to name it to the user, e.g. \"~/.claude.json\"\n * @throws if the file exists but cannot be read or parsed\n */\nexport function readJsonStrict(path: string, label = path): Record<string, unknown> {\n if (!existsSync(path)) return {};\n\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch (e) {\n throw new Error(\n `Could not read ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — writing now would replace its contents with ours alone.`\n );\n }\n\n try {\n return JSON.parse(raw) as Record<string, unknown>;\n } catch (e) {\n throw new Error(\n `${label} exists but is not valid JSON: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to continue — overwriting it would destroy whatever it holds.\\n` +\n `Repair the file, or move it aside and re-run this command.`\n );\n }\n}\n\n/**\n * Write JSON without risking the existing file.\n *\n * Keeps a `.bak-pai` copy of the previous contents, then writes to a temp file\n * and renames. Rename is atomic within a filesystem, so a crash mid-write\n * leaves the original intact rather than truncated — which is how these files\n * become corrupt in the first place.\n */\nexport function writeJsonAtomic(\n path: string,\n data: Record<string, unknown>,\n opts: { backup?: boolean; label?: string } = {}\n): void {\n const { backup = true, label = path } = opts;\n const serialized = JSON.stringify(data, null, 2) + \"\\n\";\n\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\n if (backup && existsSync(path)) {\n try {\n copyFileSync(path, `${path}.bak-pai`);\n } catch (e) {\n throw new Error(\n `Could not back up ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `Refusing to write without a backup.`\n );\n }\n }\n\n const tmp = `${path}.tmp-pai-${process.pid}`;\n try {\n writeFileSync(tmp, serialized, \"utf8\");\n renameSync(tmp, path);\n } catch (e) {\n try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best effort */ }\n throw new Error(\n `Failed to write ${label}: ${e instanceof Error ? e.message : String(e)}\\n` +\n `The original is unchanged.`\n );\n }\n}\n","/**\n * config.ts — the `workers` section of ~/.config/pai/config.json\n *\n * Everything that knows about worker providers reads this module: the CLI\n * (`pai worker …`), the MCP tools (worker_*), and the Agent-routing hook.\n * Keep it free of commander/MCP imports so all three layers stay thin over it.\n *\n * Keys are never stored here — only paths to 0600 files. A provider without a\n * keyFile is a local server and gets the placeholder token \"local\".\n *\n * `protocol: \"openai\"` providers run through the PAI proxy (src/workers/proxy):\n * `upstreamUrl` is their Chat Completions base, and `run` points\n * ANTHROPIC_BASE_URL at the local proxy with the provider name in the path.\n * `engine: \"codex\"` providers run through the Codex CLI instead of Claude\n * Code.\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readJsonStrict, writeJsonAtomic } from \"../config/json-store.js\";\nimport { CONFIG_FILE } from \"../daemon/config.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Wire protocol a provider speaks: \"anthropic\" natively, \"openai\" through the\n * PAI proxy (which translates the Anthropic Messages API to Chat Completions).\n */\nexport type WorkerProtocol = \"anthropic\" | \"openai\";\n\n/** Runner executable behind a provider: Claude Code or the Codex CLI. */\nexport type WorkerEngine = \"claude\" | \"codex\";\n\nexport interface WorkerProvider {\n enabled: boolean;\n /** Wire protocol the baseUrl speaks. */\n protocol: WorkerProtocol;\n /** Anthropic-compatible Messages API base. */\n baseUrl: string;\n /** 0600 file holding the auth token; null for local servers (\"local\"). */\n keyFile: string | null;\n models: {\n default: string;\n /** Alias used for cheap/fast work (spotcheck, research). */\n fast?: string;\n };\n /** Extra environment variables for runs through this provider (string values). */\n env: Record<string, string>;\n note?: string;\n /**\n * OpenAI Chat Completions base (e.g. \"https://api.openai.com/v1\"). Required\n * for protocol \"openai\"; read by the PAI proxy, never by the runner.\n */\n upstreamUrl?: string;\n /** Runner for this provider; \"codex\" goes through the Codex CLI. */\n engine?: WorkerEngine;\n /** Optional shell command printing 0–100 (percent of quota used). */\n quotaProbe?: string;\n /** Auto-routing skips the provider at or above this percentage. Default 95. */\n quotaSkipAt?: number;\n /** Context window of the provider's model, for the context meter. Default 200000. */\n contextWindow?: number;\n /** Cost tier 1 (cheapest) … 5 (most expensive); classes cap it via maxCostTier. */\n costTier?: number;\n /** Capability tags; classes filter auto-routing via requireTags. */\n tags?: ProviderTag[];\n}\n\nexport interface WorkersPaneConfig {\n enabled: boolean;\n /** Font size (points) of the follow-pane profile's font. */\n fontSize: number;\n /** Seconds a pane lingers after its worker goes quiet. */\n autoExitSecs: number;\n}\n\nexport interface WorkersRoutingConfig {\n /** Provider names in preference order; first usable one wins. */\n order: string[];\n /** Minutes a provider sits in cooldown after a quota/rate failure. */\n cooldownMinutes: number;\n /** Reroute a failed-before-first-tool run to the next provider. */\n retryOnQuota: boolean;\n}\n\n/** Cost/quality tier of a provider's model, 1 (cheapest) … 5 (most expensive). */\nexport type CostTier = 1 | 2 | 3 | 4 | 5;\n\nexport const DEFAULT_COST_TIER = 3;\n\n/** Tags a provider may carry; classes filter auto-routing on them. */\nexport const PROVIDER_TAGS = [\n \"code\",\n \"vision\",\n \"image-gen\",\n \"long-context\",\n \"fast\",\n \"reasoning\",\n] as const;\n\nexport type ProviderTag = (typeof PROVIDER_TAGS)[number];\n\n/** The standard task classes; `workers.classes` maps each to a target. */\nexport const WORKER_CLASSES = [\n \"draft\",\n \"plan\",\n \"implement\",\n \"review\",\n \"research\",\n \"spotcheck\",\n \"simple\",\n \"complex\",\n \"image\",\n] as const;\n\nexport type WorkerClassName = (typeof WORKER_CLASSES)[number];\n\n/**\n * A class target: \"<provider>\", \"<provider>/<modelAlias>\", or an object. The\n * object either pins a `provider` (plus optional `mcp` allowlist) or only\n * constrains auto-routing (`maxCostTier`, `requireTags`, per-class `order`).\n */\nexport type ClassTarget =\n | string\n | {\n provider?: string;\n mcp?: string[];\n /** Auto-routing may only use providers at or below this cost tier. */\n maxCostTier?: number;\n /** Auto-routing may only use providers carrying all these tags. */\n requireTags?: string[];\n /** Provider order for this class; defaults to routing.order. */\n order?: string[];\n };\n\nexport interface WorkersConfig {\n enabled: boolean;\n /** Provider name, or \"auto\" for routing.order resolution. */\n active: string | null;\n providers: Record<string, WorkerProvider>;\n /** Class → target; see ClassTarget. (Reads the legacy `roles` key.) */\n classes: Record<string, ClassTarget>;\n /** MCP set name → server names; `--mcp <set>` and class `mcp` expand these. */\n mcpSets: Record<string, string[]>;\n pane: WorkersPaneConfig;\n logDir: string;\n routing: WorkersRoutingConfig;\n}\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_LOG_DIR = \"~/.claude/logs/workers\";\n\nexport const DEFAULT_PANE: WorkersPaneConfig = {\n enabled: true,\n fontSize: 13,\n autoExitSecs: 60,\n};\n\nexport const DEFAULT_ROUTING: WorkersRoutingConfig = {\n order: [],\n cooldownMinutes: 30,\n retryOnQuota: true,\n};\n\nexport function defaultWorkersConfig(): WorkersConfig {\n return {\n enabled: false,\n active: null,\n providers: {},\n classes: {},\n mcpSets: {},\n pane: { ...DEFAULT_PANE },\n logDir: DEFAULT_LOG_DIR,\n routing: { ...DEFAULT_ROUTING, order: [] },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nexport class WorkersConfigError extends Error {}\n\nfunction bad(path: string, why: string): never {\n throw new WorkersConfigError(`workers${path}: ${why}`);\n}\n\nfunction str(v: unknown): string {\n return typeof v === \"string\" ? v : \"\";\n}\n\nfunction parseProvider(name: string, raw: unknown): WorkerProvider {\n if (typeof raw !== \"object\" || raw === null) bad(`.providers.${name}`, \"must be an object\");\n const p = raw as Record<string, unknown>;\n\n const protocol = p.protocol === undefined ? \"anthropic\" : str(p.protocol);\n if (protocol !== \"anthropic\" && protocol !== \"openai\") {\n bad(`.providers.${name}.protocol`, `\"${str(p.protocol)}\" is neither \"anthropic\" nor \"openai\"`);\n }\n const engine = p.engine === undefined ? \"claude\" : str(p.engine);\n if (engine !== \"claude\" && engine !== \"codex\") {\n bad(`.providers.${name}.engine`, `\"${str(p.engine)}\" is neither \"claude\" nor \"codex\"`);\n }\n\n const baseUrl = str(p.baseUrl);\n // openai providers reach the model through the PAI proxy; their baseUrl is\n // the proxy URL, filled in by the runner — only anthropic needs one here.\n if (!baseUrl && protocol !== \"openai\") bad(`.providers.${name}.baseUrl`, \"is required\");\n\n const keyFile =\n p.keyFile === undefined || p.keyFile === null || str(p.keyFile) === \"\"\n ? null\n : str(p.keyFile);\n\n const modelsRaw = p.models === undefined ? {} : p.models;\n if (typeof modelsRaw !== \"object\" || modelsRaw === null) {\n bad(`.providers.${name}.models`, \"must be an object\");\n }\n const m = modelsRaw as Record<string, unknown>;\n const defaultModel = str(m.default);\n if (!defaultModel) bad(`.providers.${name}.models.default`, \"is required\");\n const fast = m.fast === undefined ? undefined : str(m.fast);\n\n const env: Record<string, string> = {};\n if (p.env !== undefined) {\n if (typeof p.env !== \"object\" || p.env === null || Array.isArray(p.env)) {\n bad(`.providers.${name}.env`, \"must be an object of string values\");\n }\n for (const [k, v] of Object.entries(p.env as Record<string, unknown>)) {\n if (typeof v !== \"string\") bad(`.providers.${name}.env.${k}`, \"must be a string\");\n env[k] = v;\n }\n }\n\n const quotaSkipAt = p.quotaSkipAt === undefined ? undefined : p.quotaSkipAt;\n if (quotaSkipAt !== undefined) {\n if (typeof quotaSkipAt !== \"number\" || quotaSkipAt < 0 || quotaSkipAt > 100) {\n bad(`.providers.${name}.quotaSkipAt`, \"must be a number between 0 and 100\");\n }\n }\n\n const contextWindow = p.contextWindow === undefined ? undefined : p.contextWindow;\n if (contextWindow !== undefined) {\n if (typeof contextWindow !== \"number\" || contextWindow <= 0) {\n bad(`.providers.${name}.contextWindow`, \"must be a positive number of tokens\");\n }\n }\n\n const upstreamUrl = str(p.upstreamUrl);\n if (protocol === \"openai\" && !upstreamUrl) {\n bad(`.providers.${name}.upstreamUrl`, `is required for protocol \"openai\" (the Chat Completions base, e.g. \"https://api.openai.com/v1\")`);\n }\n\n const costTier = p.costTier === undefined ? undefined : p.costTier;\n if (costTier !== undefined) {\n if (typeof costTier !== \"number\" || !Number.isInteger(costTier) || costTier < 1 || costTier > 5) {\n bad(`.providers.${name}.costTier`, \"must be an integer 1 (cheapest) … 5 (most expensive)\");\n }\n }\n\n let tags: ProviderTag[] | undefined;\n if (p.tags !== undefined) {\n if (!Array.isArray(p.tags) || p.tags.some((x) => typeof x !== \"string\")) {\n bad(`.providers.${name}.tags`, `must be an array of tags from: ${PROVIDER_TAGS.join(\", \")}`);\n }\n for (const t of p.tags as string[]) {\n if (!(PROVIDER_TAGS as readonly string[]).includes(t)) {\n bad(`.providers.${name}.tags`, `\"${t}\" is not a tag (from: ${PROVIDER_TAGS.join(\", \")})`);\n }\n }\n tags = p.tags as ProviderTag[];\n }\n\n return {\n enabled: p.enabled === undefined ? true : p.enabled === true,\n protocol,\n baseUrl,\n keyFile,\n models: fast ? { default: defaultModel, fast } : { default: defaultModel },\n env,\n ...(str(p.note) ? { note: str(p.note) } : {}),\n ...(upstreamUrl ? { upstreamUrl } : {}),\n ...(engine !== \"claude\" ? { engine } : {}),\n ...(str(p.quotaProbe) ? { quotaProbe: str(p.quotaProbe) } : {}),\n ...(quotaSkipAt !== undefined ? { quotaSkipAt } : {}),\n ...(contextWindow !== undefined ? { contextWindow } : {}),\n ...(costTier !== undefined ? { costTier } : {}),\n ...(tags ? { tags } : {}),\n };\n}\n\n/**\n * Parse and validate a raw `workers` value. Missing section → defaults.\n * Unknown-but-typed garbage → WorkersConfigError naming the offending field.\n */\nexport function parseWorkersConfig(raw: unknown): WorkersConfig {\n const d = defaultWorkersConfig();\n if (raw === undefined || raw === null) return d;\n if (typeof raw !== \"object\" || Array.isArray(raw)) {\n bad(\"\", \"section must be an object\");\n }\n const w = raw as Record<string, unknown>;\n\n const providers: Record<string, WorkerProvider> = {};\n if (w.providers !== undefined) {\n if (typeof w.providers !== \"object\" || w.providers === null || Array.isArray(w.providers)) {\n bad(\".providers\", \"must be an object keyed by provider name\");\n }\n for (const [name, p] of Object.entries(w.providers)) {\n providers[name] = parseProvider(name, p);\n }\n }\n\n // classes is canonical; a config that still carries the pre-classes `roles`\n // key is migrated by reading it here — the next write stores only `classes`.\n const classes: Record<string, ClassTarget> = {};\n const classesRaw = w.classes !== undefined ? w.classes : w.roles;\n if (classesRaw !== undefined) {\n if (typeof classesRaw !== \"object\" || classesRaw === null || Array.isArray(classesRaw)) {\n bad(w.classes !== undefined ? \".classes\" : \".roles\", \"must be an object of class → provider[/alias] or {provider, mcp, maxCostTier, requireTags, order}\");\n }\n for (const [cls, target] of Object.entries(classesRaw)) {\n if (typeof target === \"object\" && target !== null && !Array.isArray(target)) {\n const o = target as Record<string, unknown>;\n const provider = str(o.provider);\n if (o.provider !== undefined && (!provider || provider.includes(\" \"))) {\n bad(`.classes.${cls}.provider`, `invalid provider \"${provider}\"`);\n }\n let mcp: string[] | undefined;\n if (o.mcp !== undefined) {\n if (!Array.isArray(o.mcp) || o.mcp.some((x) => typeof x !== \"string\")) {\n bad(`.classes.${cls}.mcp`, \"must be an array of MCP server or set names\");\n }\n mcp = o.mcp as string[];\n }\n let maxCostTier: number | undefined;\n if (o.maxCostTier !== undefined) {\n if (\n typeof o.maxCostTier !== \"number\" ||\n !Number.isInteger(o.maxCostTier) ||\n o.maxCostTier < 1 ||\n o.maxCostTier > 5\n ) {\n bad(`.classes.${cls}.maxCostTier`, \"must be an integer 1 … 5\");\n }\n maxCostTier = o.maxCostTier;\n }\n let requireTags: string[] | undefined;\n if (o.requireTags !== undefined) {\n if (!Array.isArray(o.requireTags) || o.requireTags.some((x) => typeof x !== \"string\")) {\n bad(`.classes.${cls}.requireTags`, `must be an array of tags from: ${PROVIDER_TAGS.join(\", \")}`);\n }\n for (const t of o.requireTags as string[]) {\n if (!(PROVIDER_TAGS as readonly string[]).includes(t)) {\n bad(`.classes.${cls}.requireTags`, `\"${t}\" is not a tag (from: ${PROVIDER_TAGS.join(\", \")})`);\n }\n }\n requireTags = o.requireTags as string[];\n }\n let order: string[] | undefined;\n if (o.order !== undefined) {\n if (!Array.isArray(o.order) || o.order.some((x) => typeof x !== \"string\")) {\n bad(`.classes.${cls}.order`, \"must be an array of provider names\");\n }\n order = o.order as string[];\n }\n const obj: ClassTarget = {\n ...(provider ? { provider } : {}),\n ...(mcp ? { mcp } : {}),\n ...(maxCostTier !== undefined ? { maxCostTier } : {}),\n ...(requireTags ? { requireTags } : {}),\n ...(order ? { order } : {}),\n };\n classes[cls] = Object.keys(obj).length ? obj : {};\n } else {\n const t = str(target);\n if (!t || t.includes(\" \")) bad(`.classes.${cls}`, `invalid target \"${t}\"`);\n classes[cls] = t;\n }\n }\n }\n\n const mcpSets: Record<string, string[]> = {};\n if (w.mcpSets !== undefined) {\n if (typeof w.mcpSets !== \"object\" || w.mcpSets === null || Array.isArray(w.mcpSets)) {\n bad(\".mcpSets\", \"must be an object of set name → [server names]\");\n }\n for (const [setName, servers] of Object.entries(w.mcpSets)) {\n if (!Array.isArray(servers) || servers.some((x) => typeof x !== \"string\")) {\n bad(`.mcpSets.${setName}`, \"must be an array of MCP server names\");\n }\n mcpSets[setName] = servers as string[];\n }\n }\n\n let pane = { ...DEFAULT_PANE };\n if (w.pane !== undefined) {\n if (typeof w.pane !== \"object\" || w.pane === null) bad(\".pane\", \"must be an object\");\n const pc = w.pane as Record<string, unknown>;\n if (pc.enabled !== undefined && typeof pc.enabled !== \"boolean\") bad(\".pane.enabled\", \"must be boolean\");\n if (pc.fontSize !== undefined && (typeof pc.fontSize !== \"number\" || pc.fontSize <= 0)) {\n bad(\".pane.fontSize\", \"must be a positive number of points\");\n }\n if (pc.autoExitSecs !== undefined && typeof pc.autoExitSecs !== \"number\") {\n bad(\".pane.autoExitSecs\", \"must be a number\");\n }\n // legacy fontScale (a relative scale, superseded by fontSize) is tolerated and ignored\n pane = {\n enabled: pc.enabled === undefined ? DEFAULT_PANE.enabled : pc.enabled === true,\n fontSize: pc.fontSize === undefined ? DEFAULT_PANE.fontSize : pc.fontSize,\n autoExitSecs: pc.autoExitSecs === undefined ? DEFAULT_PANE.autoExitSecs : pc.autoExitSecs,\n };\n }\n\n let routing = { ...DEFAULT_ROUTING, order: [] as string[] };\n if (w.routing !== undefined) {\n if (typeof w.routing !== \"object\" || w.routing === null) bad(\".routing\", \"must be an object\");\n const r = w.routing as Record<string, unknown>;\n if (r.order !== undefined) {\n if (!Array.isArray(r.order) || r.order.some((x) => typeof x !== \"string\")) {\n bad(\".routing.order\", \"must be an array of provider names\");\n }\n routing.order = r.order as string[];\n }\n if (r.cooldownMinutes !== undefined && typeof r.cooldownMinutes !== \"number\") {\n bad(\".routing.cooldownMinutes\", \"must be a number\");\n }\n if (r.retryOnQuota !== undefined && typeof r.retryOnQuota !== \"boolean\") {\n bad(\".routing.retryOnQuota\", \"must be boolean\");\n }\n routing = {\n order: routing.order,\n cooldownMinutes: r.cooldownMinutes === undefined ? DEFAULT_ROUTING.cooldownMinutes : r.cooldownMinutes,\n retryOnQuota: r.retryOnQuota === undefined ? DEFAULT_ROUTING.retryOnQuota : r.retryOnQuota === true,\n };\n }\n\n const active = w.active === undefined || w.active === null ? null : str(w.active);\n if (active !== null && active !== \"auto\" && !(active in providers)) {\n // Tolerated at parse time (a provider may have been removed while active\n // still names it) but every consumer resolves it to a clear error.\n }\n\n return {\n enabled: w.enabled === undefined ? d.enabled : w.enabled === true,\n active,\n providers,\n classes,\n mcpSets,\n pane,\n logDir: str(w.logDir) || d.logDir,\n routing,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Read / write\n// ---------------------------------------------------------------------------\n\n/**\n * Read the whole config file and return (raw, workers) — the raw record so\n * callers can rewrite it preserving every other section, the parsed+validated\n * workers section. Unreadable config throws (readJsonStrict), missing is fine.\n */\nexport function readWorkersSection(): {\n raw: Record<string, unknown>;\n workers: WorkersConfig;\n} {\n const raw = readJsonStrict(CONFIG_FILE, \"~/.config/pai/config.json\");\n return { raw, workers: parseWorkersConfig(raw.workers) };\n}\n\n/** Write the workers section back into the config file, atomically. */\nexport function writeWorkersSection(\n raw: Record<string, unknown>,\n workers: WorkersConfig\n): void {\n raw.workers = workers;\n writeJsonAtomic(CONFIG_FILE, raw, { label: \"~/.config/pai/config.json\" });\n}\n\n/** Expand a leading ~ (config values are written with `~` to stay portable). */\nexport function expandHome(p: string): string {\n if (p === \"~\" || p.startsWith(\"~/\")) return join(homedir(), p.slice(1));\n return p;\n}\n\n// ---------------------------------------------------------------------------\n// Runnability checks (proxy / codex providers included)\n// ---------------------------------------------------------------------------\n\nexport function assertProviderRunnable(name: string, p: WorkerProvider): void {\n if (p.protocol === \"openai\" && !p.upstreamUrl) {\n throw new WorkersConfigError(\n `provider \"${name}\" uses protocol \"openai\" but has no upstreamUrl — ` +\n `set its Chat Completions base (e.g. \"https://api.openai.com/v1\") ` +\n `so the PAI proxy knows where to translate to.`\n );\n }\n}\n\n/** Where a provider's key file lives, or null. Shared by run + test + add. */\nexport function providerKeyPath(p: WorkerProvider): string | null {\n return p.keyFile ? expandHome(p.keyFile) : null;\n}\n\nexport const DEFAULT_CONTEXT_WINDOW = 200_000;\n\n/** Cost tier of a provider for class filtering (unset = 3, the middle). */\nexport function providerCostTier(p: WorkerProvider): number {\n return p.costTier ?? DEFAULT_COST_TIER;\n}\n\n/** Context window used by the meter when the init event carries none. */\nexport function providerContextWindow(p: WorkerProvider): number {\n return p.contextWindow ?? DEFAULT_CONTEXT_WINDOW;\n}\n\n/** Directory under which inline keys (MCP `key` field) are stored, 0600. */\nexport function keysDir(): string {\n return join(homedir(), \".config\", \"pai\", \"keys\");\n}\n","/**\n * paths.ts — where worker run artefacts live.\n *\n * Everything a run writes (event mirror, status file, pane registry, routing\n * state, the empty MCP config) sits under one logDir so the whole tree is\n * disposable and configurable: `workers.logDir`, default ~/.claude/logs/workers.\n */\n\nimport { join } from \"node:path\";\nimport { expandHome, type WorkersConfig } from \"./config.js\";\n\n/** Absolute logDir for the given config. */\nexport function workersLogDir(config: WorkersConfig): string {\n return expandHome(config.logDir);\n}\n\nexport function statusPath(logDir: string, id: string): string {\n return join(logDir, `${id}.status`);\n}\n\nexport function eventsPath(logDir: string, id: string): string {\n return join(logDir, `${id}.jsonl`);\n}\n\nexport function ledgerPath(logDir: string): string {\n return join(logDir, \"ledger.log\");\n}\n\nexport function routingStatePath(logDir: string): string {\n return join(logDir, \"routing-state.json\");\n}\n\nexport function panesDir(logDir: string): string {\n return join(logDir, \"panes\");\n}\n\n/**\n * The strict empty MCP config handed to headless workers. Written into the\n * logDir on demand (never into the user's vendor config directory — this is\n * PAI state, not vendor state).\n */\nexport function noMcpConfigPath(logDir: string): string {\n return join(logDir, \"no-mcp.json\");\n}\n","/**\n * args.ts — parse the claude-args tail that `pai worker run` receives.\n *\n * The runner needs a few things out of the caller's argument vector: the\n * prompt (for the default label), the requested --output-format (so the final\n * print matches what plain `claude -p` would have produced), whether the\n * caller already chose a --model or an --mcp-config (both suppress the\n * defaults the runner would otherwise force), any --mcp allowlist names, and\n * whether they appended their own system prompt (the worker contract is then\n * added alongside, not instead). Everything is passed through untouched — the\n * runner never rewrites the caller's task.\n */\n\nexport interface ParsedRunnerArgs {\n /** Prompt string after -p/--print, when the run is headless. */\n prompt: string | null;\n /** Caller's --output-format: text (default), json, or stream-json. */\n outputFormat: \"text\" | \"json\" | \"stream-json\";\n /** Args to hand to claude (minus --output-format/--verbose, which we add). */\n rest: string[];\n /** Headless: a -p/--print flag is present. */\n headless: boolean;\n /** Caller passed --model (or --model=…): do not force the provider model. */\n callerModel: boolean;\n /** Caller passed --mcp-config (or --mcp-config=…): keep their MCP setup. */\n callerMcpConfig: boolean;\n /** Caller passed --append-system-prompt: the contract is added alongside. */\n callerSystemPrompt: boolean;\n /** --mcp values (repeatable, comma-separated inside one flag). */\n mcp: string[];\n}\n\nexport function parseRunnerArgs(argv: string[]): ParsedRunnerArgs {\n let prompt: string | null = null;\n let outputFormat: ParsedRunnerArgs[\"outputFormat\"] = \"text\";\n const rest: string[] = [];\n let headless = false;\n let callerModel = false;\n let callerMcpConfig = false;\n let callerSystemPrompt = false;\n const mcp: string[] = [];\n\n let i = 0;\n while (i < argv.length) {\n const a = argv[i];\n if (a === \"-p\" || a === \"--print\") {\n headless = true;\n rest.push(a);\n if (i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) {\n prompt = argv[i + 1];\n rest.push(prompt);\n i += 1;\n }\n } else if (a === \"--output-format\") {\n const v = argv[i + 1];\n if (v === \"json\" || v === \"stream-json\") outputFormat = v;\n i += 1; // dropped; the runner prints the result in this format itself\n } else if (a.startsWith(\"--output-format=\")) {\n const v = a.slice(\"--output-format=\".length);\n if (v === \"json\" || v === \"stream-json\") outputFormat = v;\n } else if (a === \"--verbose\") {\n // dropped; re-added by the runner\n } else if (a === \"--mcp\") {\n const v = argv[i + 1];\n if (v !== undefined && !v.startsWith(\"-\")) {\n mcp.push(v);\n i += 1;\n }\n } else if (a.startsWith(\"--mcp=\")) {\n mcp.push(a.slice(\"--mcp=\".length));\n } else {\n if (a === \"--model\") callerModel = true;\n if (a.startsWith(\"--model=\")) callerModel = true;\n if (a === \"--mcp-config\") callerMcpConfig = true;\n if (a.startsWith(\"--mcp-config=\")) callerMcpConfig = true;\n if (a === \"--append-system-prompt\") callerSystemPrompt = true;\n if (a.startsWith(\"--append-system-prompt=\")) callerSystemPrompt = true;\n if (\n prompt === null && !a.startsWith(\"-\") && rest.length > 0 &&\n (rest[rest.length - 1] === \"-p\" || rest[rest.length - 1] === \"--print\")\n ) {\n prompt = a;\n }\n rest.push(a);\n }\n i += 1;\n }\n\n return { prompt, outputFormat, rest, headless, callerModel, callerMcpConfig, callerSystemPrompt, mcp };\n}\n\n/**\n * Turn `-p \"<prompt>\"` into bare `-p` (same for --print): for stream-json\n * stdin runs the prompt moves to the first user message on stdin, so the\n * value must not stay on the command line. Only prompt values directly\n * following the flag are touched; everything else passes through.\n */\nexport function stripPromptValues(argv: string[]): string[] {\n const out: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n const isPrint = a === \"-p\" || a === \"--print\";\n out.push(a);\n if (isPrint && i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) {\n i += 1; // drop the prompt value; the flag itself stays\n }\n }\n return out;\n}\n\n/** Collapse whitespace and cut to n chars with an ellipsis (label rendering). */\nexport function shortText(s: unknown, n: number): string {\n const t = String(s ?? \"\").split(/\\s+/).filter(Boolean).join(\" \");\n return t.length <= n ? t : t.slice(0, n - 1) + \"…\";\n}\n","/**\n * ledger.ts — the routing ledger, one append-only log for every worker event.\n *\n * Line shapes (whitespace-aligned so `tail` reads as a table):\n *\n * 2026-09-17 12:00:00 WORKER-START id=<id> provider=<p> mode=headless model=<m> cwd=<cwd> label=<label>\n * 2026-09-17 12:01:00 WORKER-END id=<id> provider=<p> mode=headless model=<m> rc=0 secs=60 turns=3 tools=8 label=<label>\n * 2026-09-17 12:00:20 WORKER-REROUTE from=<a> to=<b> reason=quota\n * 2026-09-17 12:00:00 DENIED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n * 2026-09-17 12:00:00 ALLOWED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n *\n * `pai worker log` (and glm-log before it) counts these; keep the tags stable.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function ledgerStamp(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` +\n `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`\n );\n}\n\nexport interface LedgerLine {\n stamp: string;\n event: string;\n fields: Record<string, string>;\n}\n\n/** Parse `2026-09-17 12:00:00 TAG key=value …` into its parts. */\nexport function parseLedgerLine(line: string): LedgerLine | null {\n const m = line.match(/^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) (\\S+)(?: (.*))?$/);\n if (!m) return null;\n const fields: Record<string, string> = {};\n // values are whitespace-collapsed by the writers, so spaces safely delimit\n for (const part of (m[3] ?? \"\").split(\" \")) {\n if (!part) continue;\n const eq = part.indexOf(\"=\");\n if (eq <= 0) continue;\n fields[part.slice(0, eq)] = part.slice(eq + 1);\n }\n return { stamp: m[1], event: m[2], fields };\n}\n\nexport function parseLedger(text: string): LedgerLine[] {\n const out: LedgerLine[] = [];\n for (const line of text.split(\"\\n\")) {\n if (!line.trim()) continue;\n const parsed = parseLedgerLine(line);\n if (parsed) out.push(parsed);\n }\n return out;\n}\n\n/** Append one ledger line. `kv` order is the caller's; values are flattened. */\nexport function appendLedger(\n path: string,\n event: string,\n kv: Record<string, string | number | null | undefined>,\n now: Date = new Date()\n): void {\n const parts: string[] = [];\n for (const [k, v] of Object.entries(kv)) {\n if (v === undefined || v === null) continue;\n parts.push(`${k}=${String(v).replace(/\\s+/g, \" \")}`);\n }\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tail = parts.length ? \" \" + parts.join(\" \") : \"\";\n appendFileSync(path, `${ledgerStamp(now)} ${event}${tail}\\n`, \"utf8\");\n}\n\nexport interface LedgerSummary {\n scope: string;\n started: number;\n endedOk: number;\n endedFailed: number;\n denied: number;\n allowed: number;\n reroutes: number;\n lastLines: string[];\n}\n\n/** The counts `pai worker log` prints, over today's lines or the whole file. */\nexport function ledgerSummary(\n path: string,\n scope: \"today\" | \"all\",\n lastN = 15,\n now: Date = new Date()\n): LedgerSummary | null {\n if (!existsSync(path)) return null;\n const text = readFileSync(path, \"utf8\");\n const today = ledgerStamp(now).slice(0, 10);\n const lines = text.split(\"\\n\").filter((l) => l.trim());\n const scoped =\n scope === \"all\" ? lines : lines.filter((l) => l.startsWith(today));\n const parsed = scoped.map(parseLedgerLine).filter((x): x is LedgerLine => x !== null);\n const count = (ev: string) => parsed.filter((p) => p.event === ev).length;\n const ends = parsed.filter((p) => p.event === \"WORKER-END\");\n const ok = ends.filter((p) => p.fields.rc === \"0\").length;\n return {\n scope: scope === \"all\" ? \"all time\" : `today ${today}`,\n started: count(\"WORKER-START\"),\n endedOk: ok,\n endedFailed: ends.length - ok,\n denied: count(\"DENIED-ANTHROPIC-AGENT\"),\n allowed: count(\"ALLOWED-ANTHROPIC-AGENT\"),\n reroutes: count(\"WORKER-REROUTE\"),\n lastLines: lines.slice(-lastN),\n };\n}\n","/**\n * status.ts — the live per-worker status file.\n *\n * One JSON file per run, rewritten atomically on every turn, read by `ps`,\n * `follow`, the status line and the pane logic. Same field set the Python\n * runner wrote, plus `provider` (routing is multi-provider now) and `session`\n * (the AIBroker identity of the launching session, see scope.ts).\n */\n\nimport { existsSync, readFileSync, renameSync, writeFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { shortText } from \"./args.js\";\nimport { statusPath } from \"./paths.js\";\n\nexport interface WorkerSessionRef {\n /** AIBroker/iTerm session id (the iTerm UUID). */\n id: string;\n /** AIBroker name of that session, when it has one. */\n name: string;\n}\n\nexport interface WorkerStatus {\n id: string;\n pid: number;\n label: string;\n cwd: string;\n /** iTerm session id of the launching terminal (\"\" outside iTerm). */\n term: string;\n provider: string;\n model: string;\n state: \"running\" | \"done\" | \"failed\" | \"killed\" | \"lost\";\n started: string;\n updated: string;\n turns: number;\n tools: number;\n last: string;\n rc: number | null;\n secs: number | null;\n /** Launching session resolved through AIBroker, when it was. */\n session?: WorkerSessionRef | null;\n /** Claude Code session id (system/init) — what `resume` continues. */\n claudeSession?: string | null;\n /** Context meter: tokens of the last assistant turn (input+cache+output). */\n contextTokens?: number | null;\n /** Context meter: window size (init model info or provider default). */\n contextWindow?: number | null;\n /** Chain this stage belongs to (the chain id), when it is a chain stage. */\n parent?: string;\n /** Class name of the chain stage (\"draft\", \"implement\", …). */\n stage?: string;\n}\n\n/** Context-meter percentage 0–100, null when the numbers are missing. */\nexport function contextPercent(s: Pick<WorkerStatus, \"contextTokens\" | \"contextWindow\">): number | null {\n if (!s.contextTokens || !s.contextWindow) return null;\n return Math.round((s.contextTokens / s.contextWindow) * 100);\n}\n\n/** Timestamp format shared by status files and the ledger. */\nexport function nowStamp(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` +\n `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`\n );\n}\n\n// second-resolution ids repeat when two workers/chains start together in one\n// process; a repeat gets a monotonic suffix so files never collide\nlet lastId = \"\";\nlet idSeq = 0;\n\nexport function newWorkerId(d: Date = new Date(), pid = process.pid): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n const base = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${pid}`;\n if (base === lastId) {\n idSeq += 1;\n return `${base}-${String(idSeq).padStart(2, \"0\")}`;\n }\n lastId = base;\n idSeq = 0;\n return base;\n}\n\n/** Write status atomically (temp + rename) and stamp `updated`. */\nexport function saveStatus(logDir: string, status: WorkerStatus, d: Date = new Date()): void {\n status.updated = nowStamp(d);\n const path = statusPath(logDir, status.id);\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(status), \"utf8\");\n renameSync(tmp, path);\n}\n\n/** Load every status file in the logDir, oldest id first, skipping damage. */\nexport function loadStatuses(logDir: string): WorkerStatus[] {\n if (!existsSync(logDir)) return [];\n const out: WorkerStatus[] = [];\n for (const name of readdirSync(logDir).sort()) {\n if (!name.endsWith(\".status\")) continue;\n try {\n out.push(JSON.parse(readFileSync(join(logDir, name), \"utf8\")) as WorkerStatus);\n } catch {\n // a half-written or damaged status file is not worth a crash\n }\n }\n return out;\n}\n\nexport function loadStatus(logDir: string, id: string): WorkerStatus | null {\n const path = statusPath(logDir, id);\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as WorkerStatus;\n } catch {\n return null;\n }\n}\n\nexport function alive(pid: number | null | undefined): boolean {\n if (!pid || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** \"42s\" under 90s, \"7m\" above — the coarse age the table and bar show. */\nexport function ageOf(ts: string, now: Date = new Date()): string {\n const t = Date.parse(ts.replace(\" \", \"T\"));\n if (Number.isNaN(t)) return \"?\";\n const s = Math.max(0, Math.floor((now.getTime() - t) / 1000));\n return s < 90 ? `${s}s` : `${Math.floor(s / 60)}m`;\n}\n\n/** One-line description of a tool call, e.g. \"Bash: npm test\". */\nexport function describeTool(name: string, inp: unknown): string {\n if (typeof inp !== \"object\" || inp === null) return name;\n const i = inp as Record<string, unknown>;\n const get = (k: string) => (typeof i[k] === \"string\" ? (i[k] as string) : \"\");\n if (name === \"Bash\") return `Bash: ${shortText(get(\"command\"), 70)}`;\n if (name === \"Read\" || name === \"Edit\" || name === \"Write\" || name === \"MultiEdit\") {\n const file = get(\"file_path\").split(\"/\").pop() ?? \"\";\n return `${name}: ${file}`;\n }\n if (name === \"Grep\" || name === \"Glob\") return `${name}: ${shortText(get(\"pattern\"), 50)}`;\n if (name === \"WebSearch\") return `${name}: ${shortText(get(\"query\"), 50)}`;\n if (name === \"WebFetch\") return `${name}: ${shortText(get(\"url\"), 60)}`;\n return name;\n}\n","/**\n * scope.ts — which workers \"belong\" to the terminal asking about them.\n *\n * Two identities, in priority order:\n *\n * 1. AIBroker's session registry (~/.aibroker/session-names.json): iTerm\n * session UUID → session name. The launching session's UUID is stored in\n * each worker's status file as `session.id`, so every pane of a named\n * session sees its workers — regardless of tab layout.\n * 2. The iTerm tab key (`w<window>t<tab>` prefix of ITERM_SESSION_ID): the\n * original heuristic, kept as the fallback when no registry entry\n * matches (AIBroker absent, unnamed session, non-iTerm terminal).\n *\n * If neither is available, viewers fall back to \"all workers\".\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { WorkerStatus } from \"./status.js\";\n\nexport const AIBROKER_REGISTRY = join(homedir(), \".aibroker\", \"session-names.json\");\n\n/**\n * `w<window>t<tab>` prefix of an iTerm session id, \"\" if empty or malformed.\n * Panes split from the same tab share this prefix and differ only in `p<n>`.\n */\nexport function tabKey(term: string): string {\n if (!term) return \"\";\n const head = term.split(\"p\", 1)[0];\n const parts = head.slice(1).split(\"t\");\n if (head.startsWith(\"w\") && parts.length === 2 && parts.every((p) => /^\\d+$/.test(p))) {\n return head;\n }\n return \"\";\n}\n\n/** Tab key of the iTerm tab this process runs in, \"\" if not inside iTerm2. */\nexport function currentTabKey(env: NodeJS.ProcessEnv = process.env): string {\n return tabKey(env.ITERM_SESSION_ID ?? \"\");\n}\n\n/** The iTerm UUID part of an ITERM_SESSION_ID (after the last colon). */\nexport function itermUuid(term: string): string {\n if (!term) return \"\";\n return term.split(\":\").pop() ?? \"\";\n}\n\nexport interface SessionIdentity {\n id: string;\n name: string;\n}\n\n/**\n * Resolve the AIBroker session for an ITERM_SESSION_ID. Reads the persistent\n * name registry (the same store `aibroker_rename` writes); returns null when\n * AIBroker is absent or the session is not in it — callers then use the tab\n * key, which is the pre-AIBroker behaviour.\n */\nexport function resolveSession(\n term: string,\n registryPath: string = AIBROKER_REGISTRY\n): SessionIdentity | null {\n const uuid = itermUuid(term);\n if (!uuid) return null;\n let names: Record<string, unknown>;\n try {\n if (!existsSync(registryPath)) return null;\n names = JSON.parse(readFileSync(registryPath, \"utf8\")) as Record<string, unknown>;\n } catch {\n return null;\n }\n const name = names[uuid];\n if (typeof name !== \"string\" || !name) return null;\n return { id: uuid, name };\n}\n\n/**\n * True when `worker` was launched from `term` (the terminal asking about it).\n * Session-id match first; the tab key only when no registry entry matched —\n * a session without a name keeps the tab-scoped behaviour it always had.\n */\nexport function workerInScope(worker: WorkerStatus, term: string): boolean {\n if (!term) return false;\n const uuid = itermUuid(term);\n if (worker.session?.id && uuid) return worker.session.id === uuid;\n return tabKey(worker.term) === tabKey(term) && tabKey(term) !== \"\";\n}\n\n/**\n * Registry key for pane files: the session id when AIBroker knows this\n * terminal, else the tab key, else the raw iTerm UUID.\n */\nexport function scopeKey(term: string): string {\n const session = resolveSession(term);\n if (session) return session.id;\n return currentTabKey() || itermUuid(term);\n}\n\n/** `[Name]` when the worker has an AIBroker session name, else \"\". */\nexport function sessionTag(worker: { session?: { name?: string } | null }): string {\n return worker.session?.name ? `[${worker.session.name}]` : \"\";\n}\n","/**\n * routing.ts — provider selection: flag > class > active (possibly \"auto\").\n *\n * Auto-routing walks `workers.routing.order` — or the class's own `order` —\n * and takes the first provider that is enabled, out of cooldown, (when it\n * defines a quotaProbe) under its quotaSkipAt threshold, and — when the class\n * constrains it — within `maxCostTier` and carrying all `requireTags`. A run\n * that dies of a quota/rate error puts its provider in cooldown for\n * cooldownMinutes; when that happens before the first tool call and\n * retryOnQuota is set, the runner restarts the same task on the next provider\n * (ledger: WORKER-REROUTE).\n *\n * An explicit --provider or a class mapping that pins a provider always\n * bypasses all of this.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport {\n type WorkerProvider,\n type WorkersConfig,\n type ProviderTag,\n WORKER_CLASSES,\n WorkersConfigError,\n providerCostTier,\n} from \"./config.js\";\nimport { routingStatePath } from \"./paths.js\";\n\nconst QUOTA_SKIP_DEFAULT = 95;\nconst PROBE_TIMEOUT_MS = 10_000;\n\nexport interface RoutingState {\n /** provider name → ISO timestamp when its cooldown ends */\n cooldowns: Record<string, string>;\n}\n\nexport function readRoutingState(logDir: string): RoutingState {\n const path = routingStatePath(logDir);\n if (!existsSync(path)) return { cooldowns: {} };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as RoutingState;\n return { cooldowns: parsed.cooldowns ?? {} };\n } catch {\n return { cooldowns: {} };\n }\n}\n\nexport function writeRoutingState(logDir: string, state: RoutingState): void {\n const path = routingStatePath(logDir);\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(state, null, 2) + \"\\n\", \"utf8\");\n renameSync(tmp, path);\n}\n\nexport function cooldownRemaining(\n state: RoutingState,\n provider: string,\n now: Date = new Date()\n): number {\n const end = state.cooldowns[provider];\n if (!end) return 0;\n const ms = Date.parse(end) - now.getTime();\n return ms > 0 ? ms : 0;\n}\n\nexport function setCooldown(\n logDir: string,\n provider: string,\n minutes: number,\n now: Date = new Date()\n): void {\n const state = readRoutingState(logDir);\n state.cooldowns[provider] = new Date(now.getTime() + minutes * 60_000).toISOString();\n writeRoutingState(logDir, state);\n}\n\nexport function clearCooldown(logDir: string, provider: string): void {\n const state = readRoutingState(logDir);\n if (!(provider in state.cooldowns)) return;\n delete state.cooldowns[provider];\n writeRoutingState(logDir, state);\n}\n\n/**\n * Run a provider's quotaProbe and return its percentage (0–100), or null when\n * there is no probe or it printed nothing usable. A probe must never break\n * routing: failures read as \"unknown\", not as \"full\".\n */\nexport function probeQuota(provider: WorkerProvider): number | null {\n if (!provider.quotaProbe) return null;\n try {\n const out = execFileSync(\"/bin/sh\", [\"-c\", provider.quotaProbe], {\n timeout: PROBE_TIMEOUT_MS,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n const m = out.match(/(\\d+(?:\\.\\d+)?)/);\n if (!m) return null;\n return Math.min(100, Math.max(0, Math.round(parseFloat(m[1]))));\n } catch {\n return null;\n }\n}\n\nexport function quotaSkipThreshold(provider: WorkerProvider): number {\n return provider.quotaSkipAt ?? QUOTA_SKIP_DEFAULT;\n}\n\n/** Does this provider exceed its quota threshold right now? */\nexport function quotaExceeded(provider: WorkerProvider): boolean {\n const used = probeQuota(provider);\n return used !== null && used >= quotaSkipThreshold(provider);\n}\n\nexport interface ResolvedTarget {\n providerName: string;\n provider: WorkerProvider;\n /** Model alias from the class (\"glm/fast\" → \"fast\"), null = provider default. */\n modelAlias: string | null;\n /** MCP servers (or set names) from the class target, null when it sets none. */\n classMcp: string[] | null;\n /** How the provider was chosen — names the bypass rule for rerouting. */\n via: \"flag\" | \"class\" | \"active\" | \"auto\";\n}\n\nexport class NoProviderError extends WorkersConfigError {}\n\nfunction mustExist(config: WorkersConfig, name: string): WorkerProvider {\n const p = config.providers[name];\n if (!p) {\n throw new NoProviderError(\n `no worker provider named \"${name}\". Configured: ` +\n `${Object.keys(config.providers).join(\", \") || \"(none)\"}.` +\n `\\nAdd one with: pai worker providers add <name> --base-url <url> --key-file <path> --model <model>`\n );\n }\n return p;\n}\n\nfunction mustBeRunnable(name: string, p: WorkerProvider): WorkerProvider {\n if (!p.enabled) {\n throw new NoProviderError(\n `provider \"${name}\" is disabled. Enable it with: pai worker providers enable ${name}`\n );\n }\n return p;\n}\n\n/** Why one provider of a routing order did not qualify (message fragment). */\nfunction exclusionReason(\n config: WorkersConfig,\n state: RoutingState,\n name: string,\n cls?: { maxCostTier?: number; requireTags?: string[] }\n): string | null {\n const p = config.providers[name];\n if (!p) return \"not configured\";\n if (!p.enabled) return \"disabled\";\n if (cooldownRemaining(state, name) > 0) return \"cooldown\";\n if (quotaExceeded(p)) return \"quota\";\n const tier = providerCostTier(p);\n if (cls?.maxCostTier !== undefined && tier > cls.maxCostTier) {\n return `cost tier ${tier} > max ${cls.maxCostTier}`;\n }\n if (cls?.requireTags?.length) {\n const have = p.tags ?? [];\n const missing = cls.requireTags.filter((t) => !have.includes(t as ProviderTag));\n if (missing.length) return `missing tags: ${missing.join(\", \")}`;\n }\n return null;\n}\n\n/**\n * Resolve which provider (and model alias) a run uses.\n *\n * @param flagProvider --provider value, highest precedence\n * @param className --class value (the old --role), looked up in workers.classes\n */\nexport function resolveTarget(\n config: WorkersConfig,\n logDir: string,\n opts: { flagProvider?: string; className?: string } = {}\n): ResolvedTarget {\n if (opts.flagProvider) {\n return {\n providerName: opts.flagProvider,\n provider: mustBeRunnable(opts.flagProvider, mustExist(config, opts.flagProvider)),\n modelAlias: null,\n classMcp: null,\n via: \"flag\",\n };\n }\n\n // class constraints (present whether or not the target pins a provider)\n const clsTarget = opts.className ? config.classes[opts.className] : undefined;\n const cls =\n typeof clsTarget === \"object\" && clsTarget !== null ? clsTarget : undefined;\n\n if (opts.className && clsTarget !== undefined && typeof clsTarget !== \"object\") {\n const [name, alias] = clsTarget.split(\"/\");\n return {\n providerName: name,\n provider: mustBeRunnable(name, mustExist(config, name)),\n modelAlias: alias ?? null,\n classMcp: null,\n via: \"class\",\n };\n }\n if (opts.className && cls?.provider) {\n const provider = mustBeRunnable(cls.provider, mustExist(config, cls.provider));\n return {\n providerName: cls.provider,\n provider,\n modelAlias: null,\n classMcp: cls.mcp ?? null,\n via: \"class\",\n };\n }\n if (opts.className && clsTarget === undefined) {\n // a standard class may simply be unconfigured (it then routes like a run\n // without a class); anything else is a typo and must not pass silently\n if (!(WORKER_CLASSES as readonly string[]).includes(opts.className)) {\n throw new NoProviderError(\n `no class named \"${opts.className}\". Standard classes: ${WORKER_CLASSES.join(\", \")}.` +\n `Defined: ${Object.keys(config.classes).join(\", \") || \"(none)\"}.` +\n `\\nSet one with: pai worker classes set ${opts.className}=<provider[/alias]>`\n );\n }\n }\n\n if (config.active !== \"auto\") {\n const name = config.active;\n if (!name) {\n throw new NoProviderError(\n `no active worker provider. Add one with: ` +\n `pai worker providers add <name> --base-url <url> --key-file <path> --model <model>` +\n `\\n(or point one that exists at it: pai worker providers use <name>)`\n );\n }\n return {\n providerName: name,\n provider: mustBeRunnable(name, mustExist(config, name)),\n modelAlias: null,\n classMcp: null,\n via: \"active\",\n };\n }\n\n // auto: first provider in (class or global) order that is enabled,\n // cooled-down-free, under quota and within the class constraints\n const state = readRoutingState(logDir);\n const order = cls?.order ?? config.routing.order;\n const excluded: string[] = [];\n for (const name of order) {\n const p = config.providers[name];\n const why = exclusionReason(config, state, name, cls);\n if (why) {\n excluded.push(`${name}: ${why}`);\n continue;\n }\n return { providerName: name, provider: p, modelAlias: null, classMcp: cls?.mcp ?? null, via: \"auto\" };\n }\n throw new NoProviderError(\n `auto-routing found no usable provider${opts.className ? ` for class \"${opts.className}\"` : \"\"} ` +\n `(order: [${order.join(\", \")}]).` +\n `${excluded.length ? `\\nExcluded: ${excluded.join(\"; \")}.` : \"\"}` +\n `\\nClear a cooldown with: pai worker providers enable <name>; widen the class with: pai worker classes set ${opts.className ?? \"<class>\"}=<target>`\n );\n}\n\n/**\n * Next provider after `from` in auto order, applying the same filters.\n * Used by rerouting; null when the order is exhausted.\n */\nexport function nextAutoProvider(\n config: WorkersConfig,\n logDir: string,\n from: string,\n now: Date = new Date()\n): string | null {\n const state = readRoutingState(logDir);\n const order = config.routing.order;\n const start = order.indexOf(from);\n for (let i = start + 1; i < order.length; i++) {\n const name = order[i];\n const p = config.providers[name];\n if (!p || !p.enabled) continue;\n const end = state.cooldowns[name];\n if (end && Date.parse(end) > now.getTime()) continue;\n if (quotaExceeded(p)) continue;\n return name;\n }\n return null;\n}\n\n/**\n * Was this failure a quota/rate failure? Detected from the result text the\n * endpoint produced (HTTP status is not visible in the stream events).\n */\nexport function isQuotaFailure(resultText: string): boolean {\n return /usage limit reached|rate limit|quota/i.test(resultText);\n}\n","/**\n * pane.ts — the per-worker follow pane in iTerm2.\n *\n * One small-font pane per worker, stacked in a right-hand column: the first\n * worker of a scope splits the launching session vertically, every further\n * one splits the lowest live worker pane horizontally, so panes stack top to\n * bottom. The split never sizes the new session — that would grow the whole\n * window; instead the window's bounds are read before the split and restored\n * right after, so the panes share the space the window already had. Each\n * pane runs `pai worker follow <id> --auto-exit <n>` under the `pai-worker`\n * dynamic profile (Close Sessions On End), so panes disappear by themselves.\n *\n * Panes are tracked per scope (AIBroker session id, else tab key) in\n * <logDir>/panes/<key>.json, keyed by iTerm session unique id, newest first —\n * the split lands below the lowest live worker pane.\n *\n * The dynamic profile's font is the family of iTerm's DEFAULT profile (the\n * `Default Bookmark Guid` entry in New Bookmarks) at workers.pane.fontSize —\n * never the launching session's profile, which the Python version did and\n * which produced iTerm's \"unknown parent name\" dialog whenever the two\n * differed. `Dynamic Profile Parent Name` is only written when that name\n * actually exists in New Bookmarks; when iTerm's preferences cannot be read\n * the profile is still written, with font Menlo-Regular and no parent.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { execFileSync } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { WorkersConfig } from \"./config.js\";\nimport { panesDir } from \"./paths.js\";\nimport { itermUuid, scopeKey } from \"./scope.js\";\n\nexport const PROFILE_NAME = \"pai-worker\";\n\n/** Where the dynamic profile lives (env override keeps tests off the real one). */\nexport function dynamicProfilePath(): string {\n return process.env.PAI_WORKER_PROFILE ?? join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"iTerm2\",\n \"DynamicProfiles\",\n \"pai-worker.json\"\n );\n}\n\n// ---------------------------------------------------------------------------\n// AppleScripts (arguments are passed as argv items, never interpolated)\n// ---------------------------------------------------------------------------\n\n// One pane per worker: split the launching session vertically, or the lowest\n// live candidate horizontally. Returns \"<live ids>,|<new session id>\".\n// Exported for the script-content tests (window size, argv-only arguments).\nexport const WORKER_SPLIT_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n set candList to item 2 of argv\n set followCmd to item 3 of argv\n set profileName to item 4 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n -- sizing the new session would grow the whole window:\n -- pin the window's bounds now and restore them after\n -- the split, so the panes share the existing space.\n -- copy, never set: set stores the property\n -- reference lazily, so restoring it would re-read the\n -- post-split bounds instead of these — observed as the\n -- window jumping to the main display\n copy bounds of w to winBounds\n set sessIDs to {}\n repeat with other in sessions of t\n set end of sessIDs to (id of other as text)\n end repeat\n set lived to {}\n set splitS to missing value\n repeat with cid in my splitIds(candList)\n set cidText to (cid as text)\n if sessIDs contains cidText then\n set end of lived to cidText\n if splitS is missing value then\n set splitS to first session of t whose id is cidText\n end if\n end if\n end repeat\n if profileName is \"\" then\n if splitS is missing value then\n tell s\n set newS to split vertically with default profile\n end tell\n else\n tell splitS\n set newS to split horizontally with default profile\n end tell\n end if\n else\n if splitS is missing value then\n tell s\n set newS to split vertically with profile profileName\n end tell\n else\n tell splitS\n set newS to split horizontally with profile profileName\n end tell\n end if\n end if\n tell newS\n write text followCmd\n end tell\n try\n set bounds of w to winBounds\n end try\n set out to \"\"\n repeat with lid in lived\n set out to out & lid & \",\"\n end repeat\n return out & \"|\" & (id of newS as text)\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run\n\non splitIds(s)\n set out to {}\n if s is \"\" then return out\n set prevDels to AppleScript's text item delimiters\n set AppleScript's text item delimiters to \",\"\n repeat with part in text items of s\n set end of out to (part as text)\n end repeat\n set AppleScript's text item delimiters to prevDels\n return out\nend splitIds`;\n\n// TTys of every session in the launching session's tab (no-worker pane variant).\nconst TAB_TTYS_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n set ttys to {}\n repeat with other in sessions of t\n copy (tty of other) to end of ttys\n end repeat\n return ttys\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run`;\n\n// Bounds (x1, y1, x2, y2, comma-joined) of the window hosting one iTerm\n// session — read-only, for `pai worker pane <id> --check`. Exported for the\n// script-content tests (reads bounds, never sets them).\nexport const WINDOW_BOUNDS_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n copy bounds of w to winBounds\n set prevDels to AppleScript's text item delimiters\n set AppleScript's text item delimiters to \", \"\n set out to winBounds as text\n set AppleScript's text item delimiters to prevDels\n return out\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run`;\n\n// Split the launching session vertically and run followCmd in the new pane.\nconst SPLIT_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n set followCmd to item 2 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n tell s\n set newS to split vertically with default profile\n end tell\n tell newS\n write text followCmd\n end tell\n return \"opened\"\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run`;\n\n// ---------------------------------------------------------------------------\n// osascript / ps / defaults helpers\n// ---------------------------------------------------------------------------\n\n/** Run an AppleScript with argv. Rejects when osascript itself cannot run. */\nfunction osascript(script: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"osascript\", [\"-\", ...args], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n let out = \"\";\n let err = \"\";\n proc.stdout.on(\"data\", (c: Buffer) => (out += c.toString(\"utf8\")));\n proc.stderr.on(\"data\", (c: Buffer) => (err += c.toString(\"utf8\")));\n proc.on(\"error\", reject);\n proc.on(\"close\", () => resolve({ stdout: out, stderr: err }));\n proc.stdin.write(script);\n proc.stdin.end();\n });\n}\n\nfunction psOutput(format: string): string {\n try {\n return execFileSync(\"ps\", [\"-axo\", format], { encoding: \"utf8\" });\n } catch {\n return \"\";\n }\n}\n\n/** TTys of running `worker follow` processes, normalised to /dev/ttysNNN. */\nfunction followTtys(): Set<string> {\n const ttys = new Set<string>();\n for (const line of psOutput(\"tty=,command=\").split(\"\\n\")) {\n const parts = line.trim().split(/\\s+/, 2);\n if (parts.length < 2 || parts[0] === \"ps\") continue;\n if (!/(^|\\/)(worker-follow|pai worker follow|glm-ps follow)\\b/.test(parts[1])) continue;\n if (parts[0].startsWith(\"ttys\")) ttys.add(`/dev/${parts[0]}`);\n }\n return ttys;\n}\n\n/** True while a `follow <wid>` process runs (its pane shows that worker). */\nexport function workerPaneOpen(wid: string): boolean {\n const pat = new RegExp(`(?:worker follow|worker-follow|glm-ps follow) ${wid.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\\\\b`);\n return psOutput(\"command=\").split(\"\\n\").some((ln) => pat.test(ln));\n}\n\n/** One entry of iTerm's New Bookmarks (the fields the pane cares about). */\nexport interface Bookmark {\n Name?: string;\n Guid?: string;\n \"Normal Font\"?: string;\n}\n\n/** What reading iTerm's preferences yielded — `error` carries the reason. */\nexport interface PrefsRead {\n bookmarks: Bookmark[];\n defaultGuid: string | null;\n error: string | null;\n}\n\n/** plutil's message for an ExecFileSync failure, else the exception text. */\nfunction toolErr(e: unknown): string {\n const err = e as { stderr?: string | Buffer; message?: string };\n const stderr = typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\");\n return (stderr || err.message || String(e)).trim();\n}\n\n/**\n * Read New Bookmarks and Default Bookmark Guid from an iTerm preferences\n * plist. Key-scoped `plutil -extract`, never a whole-file JSON conversion:\n * real iTerm preferences carry `<date>` objects (SULastCheckTime & friends)\n * which `plutil -convert json` rejects outright — \"Invalid object in plist\n * for JSON format\" — so the whole-file read always failed and the dynamic\n * profile was never written.\n */\nexport function readItermPlist(plistPath: string): PrefsRead {\n let bookmarks: Bookmark[] = [];\n try {\n const out = execFileSync(\n \"plutil\",\n [\"-extract\", \"New Bookmarks\", \"json\", \"-o\", \"-\", plistPath],\n { encoding: \"utf8\", timeout: 10_000 }\n );\n const parsed = JSON.parse(out) as unknown;\n if (Array.isArray(parsed)) bookmarks = parsed as Bookmark[];\n } catch (e) {\n return { bookmarks: [], defaultGuid: null, error: `extracting New Bookmarks: ${toolErr(e)}` };\n }\n let defaultGuid: string | null = null;\n try {\n const out = execFileSync(\n \"plutil\",\n [\"-extract\", \"Default Bookmark Guid\", \"raw\", \"-o\", \"-\", plistPath],\n { encoding: \"utf8\", timeout: 10_000 }\n );\n defaultGuid = out.trim() || null;\n } catch {\n // no default guid set — defaultBookmarkFrom() yields null, fine\n }\n return { bookmarks, defaultGuid, error: null };\n}\n\n/** iTerm's exported preferences, via a temp plist (no shell pipe involved). */\nexport function itermPrefs(): PrefsRead {\n let dir: string | null = null;\n try {\n dir = mkdtempSync(join(tmpdir(), \"pai-iterm-\"));\n const plist = join(dir, \"iterm2.plist\");\n execFileSync(\"defaults\", [\"export\", \"com.googlecode.iterm2\", plist], {\n encoding: \"utf8\",\n timeout: 10_000,\n });\n return readItermPlist(plist);\n } catch (e) {\n return { bookmarks: [], defaultGuid: null, error: `defaults export: ${toolErr(e)}` };\n } finally {\n if (dir) {\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // best effort cleanup of a temp dir\n }\n }\n }\n}\n\n/** The New Bookmarks entry of that profile name, null if absent. */\nfunction bookmarkFrom(read: PrefsRead, named: string): Bookmark | null {\n return read.bookmarks.find((b) => b.Name === named) ?? null;\n}\n\n/** The bookmark iTerm marks default, null when it is missing or unreadable. */\nfunction defaultBookmarkFrom(read: PrefsRead): Bookmark | null {\n return read.defaultGuid\n ? read.bookmarks.find((b) => b.Guid === read.defaultGuid) ?? null\n : null;\n}\n\n/** The default profile, read fresh from iTerm's preferences. */\nexport function defaultBookmark(): Bookmark | null {\n return defaultBookmarkFrom(itermPrefs());\n}\n\n/**\n * The pane profile's font: the family of iTerm's default profile at\n * `fontSize` points (\"MesloLGLNFM-Regular 18\" + 13 → \"MesloLGLNFM-Regular 13\"),\n * or \"Menlo-Regular <fontSize>\" when the default font cannot be read.\n */\nexport function paneFont(font: string | undefined, fontSize: number): string {\n const idx = (font ?? \"\").lastIndexOf(\" \");\n if (idx > 0) {\n const family = font!.slice(0, idx);\n if (!Number.isNaN(parseFloat(font!.slice(idx + 1)))) return `${family} ${fontSize}`;\n }\n return `Menlo-Regular ${fontSize}`;\n}\n\n/** Write the pai-worker dynamic profile; iTerm2 loads that directory itself. */\nexport function writeDynamicProfile(\n parent: Bookmark | null,\n fontSize: number,\n read: () => PrefsRead = itermPrefs\n): void {\n const profile: Record<string, unknown> = {\n Name: PROFILE_NAME,\n Guid: \"pai-worker-dynamic-profile\",\n \"Normal Font\": paneFont(parent?.[\"Normal Font\"], fontSize),\n \"Close Sessions On End\": true,\n };\n // Parent name only when iTerm actually has that profile loaded — a name it\n // does not know makes every split open an error dialog instead of a pane.\n if (parent?.Name && bookmarkFrom(read(), parent.Name)) {\n profile[\"Dynamic Profile Parent Name\"] = parent.Name;\n }\n const path = dynamicProfilePath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify({ Profiles: [profile] }, null, 2) + \"\\n\", \"utf8\");\n renameSync(tmp, path);\n}\n\nlet warnedPrefs = false;\n\n/**\n * Profile for new worker panes, creating the small-font one on demand.\n * \"\" means \"split with the default profile\" (also the fallback when the\n * dynamic profile does not show up in time).\n *\n * The profile file is written whenever it is missing — even when iTerm's\n * preferences cannot be read; then it gets font \"Menlo-Regular <fontSize>\"\n * and no parent, and the reason is logged to stderr once.\n */\nexport async function followProfile(\n fontSize: number,\n read: () => PrefsRead = itermPrefs\n): Promise<string> {\n const first = read();\n const parent = defaultBookmarkFrom(first);\n const path = dynamicProfilePath();\n const existed = existsSync(path);\n if (!existed) writeDynamicProfile(parent, fontSize, () => first);\n if (!parent) {\n if (!warnedPrefs) {\n warnedPrefs = true;\n const reason = first.error ?? \"no profile is marked default\";\n const what = existed\n ? `keeping ${path} as-is`\n : `wrote ${path} with Menlo-Regular ${fontSize} and no parent`;\n process.stderr.write(\n `pai worker pane: cannot read iTerm's default profile (${reason}) — ${what}\\n`\n );\n }\n if (first.error) return \"\"; // polling cannot succeed against unreadable prefs\n }\n for (let i = 0; i < 10; i++) {\n // iTerm2 picks DynamicProfiles up quickly; allow it 2 s\n if (bookmarkFrom(read(), PROFILE_NAME)) return PROFILE_NAME;\n await new Promise((r) => setTimeout(r, 200));\n }\n process.stderr.write(`pai worker pane: profile ${PROFILE_NAME} not visible, splitting with default\\n`);\n return \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Pane registry\n// ---------------------------------------------------------------------------\n\ninterface PaneEntry {\n session: string;\n worker: string;\n opened: string;\n}\n\nfunction loadRegistry(path: string): PaneEntry[] {\n try {\n const reg = JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n return Array.isArray(reg) ? (reg as PaneEntry[]) : [];\n } catch {\n return [];\n }\n}\n\nfunction saveRegistry(path: string, reg: PaneEntry[]): void {\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(reg, null, 1), \"utf8\");\n renameSync(tmp, path);\n}\n\n// ---------------------------------------------------------------------------\n// Public entry points\n// ---------------------------------------------------------------------------\n\n/** Open (or only report on) the stacked follow pane of one worker. */\nexport async function openPaneForWorker(\n logDir: string,\n config: WorkersConfig,\n wid: string,\n term: string\n): Promise<string> {\n if (workerPaneOpen(wid)) return `pane for ${wid} already open`;\n const uid = itermUuid(term);\n const regPath = join(panesDir(logDir), `${scopeKey(term)}.json`);\n const reg = loadRegistry(regPath);\n const profile = await followProfile(config.pane.fontSize);\n // leading space keeps the command out of shell history; `exec` replaces the\n // pane's shell with follow itself, so signals reach it directly and no shell\n // lingers once the pane closes\n const cmd = ` exec pai worker follow ${JSON.stringify(wid)} --auto-exit ${config.pane.autoExitSecs}`;\n // registry newest first: the split lands below the lowest live worker pane\n const cands = [...reg].reverse().map((e) => e.session).join(\",\");\n const p = await osascript(WORKER_SPLIT_SCRIPT, [uid, cands, cmd, profile]);\n const out = p.stdout.trim();\n if (out === \"notfound\" || out === \"notrunning\") {\n throw new Error(\n out === \"notfound\"\n ? \"pai worker pane: no open iTerm2 session matches ITERM_SESSION_ID\"\n : \"pai worker pane: iTerm2 is not running\"\n );\n }\n const bar = out.indexOf(\"|\");\n if (bar < 0) {\n throw new Error(\n `pai worker pane: osascript failed: ${(p.stderr || out).slice(0, 200)}`\n );\n }\n const live = new Set(out.slice(0, bar).split(\",\").filter(Boolean));\n const pruned = reg.filter((e) => live.has(e.session));\n pruned.push({\n session: out.slice(bar + 1),\n worker: wid,\n opened: new Date().toISOString().replace(\"T\", \" \").slice(0, 19),\n });\n saveRegistry(regPath, pruned);\n return `pane opened for ${wid}`;\n}\n\n/**\n * One `--check` line with the bounds of the window hosting `term`'s iTerm\n * session — the before/after pair that shows whether a split moved it.\n * Read-only; never touches the window.\n */\nasync function windowBoundsLine(term: string): Promise<string> {\n const uid = itermUuid(term);\n if (!uid) return \"window bounds: (not in iTerm2)\";\n try {\n const p = await osascript(WINDOW_BOUNDS_SCRIPT, [uid]);\n const out = p.stdout.trim();\n if (out && out !== \"notfound\" && out !== \"notrunning\") return `window bounds: ${out}`;\n const why =\n out === \"notfound\" ? \"iTerm2 session not found\"\n : out === \"notrunning\" ? \"iTerm2 not running\"\n : (p.stderr.trim() || \"no output\").slice(0, 120);\n return `window bounds: (${why})`;\n } catch (e) {\n return `window bounds: (osascript: ${String((e as Error).message ?? e).slice(0, 120)})`;\n }\n}\n\n/**\n * Report-only variant used by `pai worker pane <id> --check`: whether a pane\n * runs for the worker, the bounds of the window hosting the asking session,\n * plus the dynamic profile's path, its existence, and the font it contains\n * (or, when missing, would write).\n */\nexport async function checkPaneForWorker(wid: string, fontSize: number, term: string): Promise<string> {\n const lines = [workerPaneOpen(wid) ? `pane for ${wid} open` : `no pane for ${wid}`];\n lines.push(await windowBoundsLine(term));\n const path = dynamicProfilePath();\n if (existsSync(path)) {\n let font = \"(unreadable)\";\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as {\n Profiles?: Array<{ \"Normal Font\"?: string }>;\n };\n font = parsed.Profiles?.[0]?.[\"Normal Font\"] ?? \"(none)\";\n } catch {\n // font stays \"(unreadable)\"\n }\n lines.push(`profile file: ${path} (exists)`);\n lines.push(`profile font: ${font}`);\n } else {\n const parent = defaultBookmark();\n lines.push(`profile file: ${path} (missing)`);\n lines.push(`profile font (would write): ${paneFont(parent?.[\"Normal Font\"], fontSize)}`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Split this iTerm tab and run `pai worker follow` in the new pane, unless\n * one already runs here (detected by TTY — iTerm overwrites session names\n * with the running command, so names cannot serve as idempotence).\n */\nexport async function openFollowPane(\n logDir: string,\n _config: WorkersConfig,\n term: string,\n checkOnly: boolean\n): Promise<string> {\n void logDir;\n const uid = itermUuid(term);\n const p = await osascript(TAB_TTYS_SCRIPT, [uid]);\n const out = p.stdout.trim();\n if (out === \"notfound\") throw new Error(\"pai worker pane: no open iTerm2 session matches ITERM_SESSION_ID\");\n if (out === \"notrunning\") throw new Error(\"pai worker pane: iTerm2 is not running\");\n const tabTtys = new Set(out.split(\",\").map((t) => t.trim()).filter(Boolean));\n const overlap = [...tabTtys].filter((t) => followTtys().has(t));\n if (overlap.length > 0) return \"follow pane already open\";\n if (checkOnly) return \"no follow pane\";\n const cmd = \" exec pai worker follow\";\n const s = await osascript(SPLIT_SCRIPT, [uid, cmd]);\n const sout = s.stdout.trim();\n if (sout === \"opened\") return \"follow pane opened\";\n if (sout === \"notfound\") throw new Error(\"pai worker pane: no open iTerm2 session matches ITERM_SESSION_ID\");\n throw new Error(`pai worker pane: osascript failed: ${(s.stderr || sout).slice(0, 200)}`);\n}\n","/**\n * report.ts — the worker contract: terse, structured final reports.\n *\n * Headless workers run with an appended system prompt that fixes their output\n * shape: act, verify, then finish with ONE final message that is a JSON object\n * and nothing else. The runner parses it out of the result; the viewer renders\n * it as a compact block instead of a wall of text.\n */\n\nimport { relative } from \"node:path\";\nimport { shortText } from \"./args.js\";\nimport type { Paint } from \"./render.js\";\n\n/** Appended to the caller's system prompt on every headless run (2h). */\nexport const WORKER_CONTRACT_PROMPT = [\n \"You are a headless implementation worker, run non-interactively by an orchestrating session.\",\n \"No narration, no timestamps, no greetings, no summaries of what you read; act, verify, then stop.\",\n \"Your ONE final message is a JSON object and nothing else — no prose before or after, no code fence:\",\n '{\"changed\":[{\"path\":\"…\",\"summary\":\"…\"}],\"commands\":[\"…\"],\"checks\":[{\"name\":\"…\",\"ok\":true,\"detail\":\"…\"}],\"open\":[\"…\"],\"notes\":\"one line\"}',\n \"changed: files you touched (path + one-line summary). commands: the commands that verify the work.\",\n \"checks: each with ok true/false and the evidence in detail. open: what you could not finish, if anything.\",\n \"notes: one line, the headline a reviewer reads first.\",\n].join(\"\\n\");\n\nexport interface WorkerReport {\n changed?: Array<{ path?: string; summary?: string }>;\n commands?: string[];\n checks?: Array<{ name?: string; ok?: boolean; detail?: string }>;\n open?: string[];\n notes?: string;\n}\n\n/**\n * Extract the JSON report from a worker's final message. Accepts a bare\n * object, a ```json fenced block, or an object embedded in surrounding text.\n * Only objects that look like the contract count (at least one of changed /\n * checks / notes) — any other JSON falls through to null so the raw text is\n * kept as-is.\n */\nexport function parseWorkerReport(text: string): WorkerReport | null {\n const trimmed = (text ?? \"\").trim();\n if (!trimmed) return null;\n const candidates: string[] = [];\n const fence = trimmed.match(/```(?:json)?\\s*(\\{[\\s\\S]*?\\})\\s*```/);\n if (fence) candidates.push(fence[1]);\n if (trimmed.startsWith(\"{\")) {\n // a bare object may still carry trailing punctuation/newlines\n candidates.push(trimmed);\n }\n // object embedded in prose: first \"{\" to the matching last \"}\"\n const first = trimmed.indexOf(\"{\");\n const last = trimmed.lastIndexOf(\"}\");\n if (first >= 0 && last > first) candidates.push(trimmed.slice(first, last + 1));\n for (const cand of candidates) {\n try {\n const v = JSON.parse(cand) as unknown;\n if (typeof v === \"object\" && v !== null && !Array.isArray(v) && looksLikeReport(v)) {\n return v as WorkerReport;\n }\n } catch {\n // try the next candidate\n }\n }\n return null;\n}\n\n/** The contract's fingerprint — guards against unrelated JSON in a reply. */\nfunction looksLikeReport(v: object): boolean {\n const o = v as WorkerReport;\n return Array.isArray(o.changed) || Array.isArray(o.checks) || typeof o.notes === \"string\";\n}\n\n/**\n * Render a parsed report as the compact block the viewer shows: changed paths\n * with summaries, checks with ✓/✗, open items, notes. Empty report → [].\n */\nexport function renderReport(c: Paint, prefix: string, r: WorkerReport, cwd = \"\"): string[] {\n const out: string[] = [];\n const has = (xs: unknown[] | undefined) => (xs ?? []).length > 0;\n if (has(r.changed)) {\n out.push(`${prefix}${c(\"bold\", \"changed\")}`);\n for (const ch of r.changed!) {\n out.push(`${prefix} ${relShort(ch.path ?? \"?\", cwd)} — ${shortText(ch.summary ?? \"\", 90)}`);\n }\n }\n if (has(r.checks)) {\n out.push(`${prefix}${c(\"bold\", \"checks\")}`);\n for (const ck of r.checks!) {\n const mark = ck.ok === false ? c(\"red\", \"✗\") : c(\"green\", \"✓\");\n const detail = ck.detail ? c(\"dim\", ` · ${shortText(ck.detail, 80)}`) : \"\";\n out.push(`${prefix} ${mark} ${ck.name ?? \"?\"}${detail}`);\n }\n }\n if (has(r.commands)) {\n out.push(`${prefix}${c(\"bold\", \"commands\")}`);\n for (const cmd of r.commands!) out.push(`${prefix} ${c(\"dim\", shortText(cmd, 100))}`);\n }\n if (has(r.open)) {\n out.push(`${prefix}${c(\"bold\", \"open\")}`);\n for (const o of r.open!) out.push(`${prefix} ${c(\"yellow\", shortText(o, 100))}`);\n }\n if (r.notes) out.push(`${prefix}${c(\"bold\", \"notes\")} ${shortText(r.notes, 120)}`);\n return out;\n}\n\n/** repo-relative display of a changed path when it lies under cwd. */\nfunction relShort(p: string, cwd: string): string {\n if (!cwd) return p;\n const r = relative(cwd, p);\n return r && !r.startsWith(\"..\") ? r : p;\n}\n","/**\n * mcp.ts — the MCP allowlist for headless workers.\n *\n * Headless workers start with NO MCP servers: every server definition is a\n * prompt-time tool inventory the model pays to know, and a worker that only\n * reads and edits files needs none of it. A run may opt in with `--mcp\n * name[,name…]` (or a role carrying `\"mcp\": [...]`); names may be single\n * servers from ~/.claude.json's `mcpServers` or `workers.mcpSets` set names,\n * which expand to their member list. The filtered config lands in\n * `<logDir>/<id>.mcp.json` and is passed with `--strict-mcp-config\n * --mcp-config` so exactly those servers load. MCP servers are chosen at\n * launch only — a mid-run `say` cannot add any.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { WorkersConfigError, type WorkersConfig } from \"./config.js\";\n\n/** ~/.claude.json — the user's MCP server definitions (top-level mcpServers). */\nexport const CLAUDE_JSON = join(homedir(), \".claude.json\");\n\nexport function readMcpServers(claudeJson = CLAUDE_JSON): Record<string, unknown> {\n try {\n if (!existsSync(claudeJson)) return {};\n const parsed = JSON.parse(readFileSync(claudeJson, \"utf8\")) as Record<string, unknown>;\n const servers = parsed.mcpServers;\n if (typeof servers !== \"object\" || servers === null || Array.isArray(servers)) return {};\n return servers as Record<string, unknown>;\n } catch {\n // a damaged ~/.claude.json must not take workers down with it\n return {};\n }\n}\n\n/**\n * Split a `--mcp a,b,c` flag value (also accepts repeated flags already split\n * by the caller) and expand set names from workers.mcpSets.\n */\nexport function expandMcpNames(\n names: string[],\n config: Pick<WorkersConfig, \"mcpSets\">,\n claudeJson = CLAUDE_JSON\n): string[] {\n const available = readMcpServers(claudeJson);\n const out: string[] = [];\n for (const raw of names) {\n for (const name of raw.split(\",\").map((s) => s.trim()).filter(Boolean)) {\n if (name in config.mcpSets) {\n for (const member of config.mcpSets[name]) {\n if (!out.includes(member)) out.push(member);\n }\n continue;\n }\n if (!(name in available)) {\n const sets = Object.keys(config.mcpSets);\n throw new WorkersConfigError(\n `unknown MCP server \"${name}\". Available servers: ` +\n `${Object.keys(available).join(\", \") || \"(none in ~/.claude.json)\"}` +\n `${sets.length ? `; sets: ${sets.join(\", \")}` : \"\"}`\n );\n }\n if (!out.includes(name)) out.push(name);\n }\n }\n return out;\n}\n\n/** Path of a run's filtered MCP config. */\nexport function runMcpConfigPath(logDir: string, id: string): string {\n return join(logDir, `${id}.mcp.json`);\n}\n\n/**\n * Write a config containing only `names` (already expanded) and return its\n * path. Unknown names fail fast with the available list.\n */\nexport function writeMcpConfig(\n logDir: string,\n id: string,\n names: string[],\n claudeJson = CLAUDE_JSON\n): string {\n const available = readMcpServers(claudeJson);\n const unknown = names.filter((n) => !(n in available));\n if (unknown.length) {\n throw new WorkersConfigError(\n `unknown MCP server(s): ${unknown.join(\", \")}. Available: ` +\n `${Object.keys(available).join(\", \") || \"(none in ~/.claude.json)\"}`\n );\n }\n const servers: Record<string, unknown> = {};\n for (const n of names) servers[n] = available[n];\n const path = runMcpConfigPath(logDir, id);\n if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });\n writeFileSync(path, JSON.stringify({ mcpServers: servers }, null, 2) + \"\\n\", \"utf8\");\n return path;\n}\n\n/** `pai worker mcp list`: the servers and sets a run could name. */\nexport function describeMcp(config: Pick<WorkersConfig, \"mcpSets\">, claudeJson = CLAUDE_JSON): string[] {\n const servers = Object.keys(readMcpServers(claudeJson));\n const lines: string[] = [];\n if (servers.length) {\n lines.push(`servers (~/.claude.json):`);\n for (const s of servers) lines.push(` ${s}`);\n } else {\n lines.push(`no MCP servers defined in ~/.claude.json`);\n }\n const sets = Object.entries(config.mcpSets);\n if (sets.length) {\n lines.push(`sets (workers.mcpSets):`);\n for (const [name, members] of sets) lines.push(` ${name} = ${members.join(\", \")}`);\n }\n lines.push(`usage: pai worker run --mcp <name>[,<name>…] -p '<task>'`);\n return lines;\n}\n","/**\n * operator.ts — talking to a running worker.\n *\n * Headless workers run with `--input-format stream-json` and their stdin held\n * open, so a run is a conversation: every line sent to the per-worker Unix\n * socket `<logDir>/<id>.sock` is forwarded to the child as a user message and\n * mirrored into the transcript as an `operator` event (rendered with a »\n * marker). Once the worker has finished its turn, stdin closes 2 s later\n * unless a new message arrives — after that, `say` refuses and `resume`\n * continues the same Claude session with the worker's context intact.\n */\n\nimport { createServer, connect, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { loadStatus, alive } from \"./status.js\";\n\nexport function operatorSocketPath(logDir: string, id: string): string {\n return join(logDir, `${id}.sock`);\n}\n\n/**\n * The runner's side: listen on the worker socket, hand every received line to\n * `onLine`. Returns the server (close it when the run ends; the socket file is\n * unlinked on close, best effort).\n */\nexport function createOperatorServer(\n logDir: string,\n id: string,\n onLine: (text: string) => void\n): import(\"node:net\").Server {\n const path = operatorSocketPath(logDir, id);\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // a stale socket from a crashed run must not block the new one\n }\n const server = createServer((socket: Socket) => {\n let buf = \"\";\n socket.on(\"data\", (chunk: Buffer) => {\n buf += chunk.toString(\"utf8\");\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, nl).replace(/\\r$/, \"\");\n buf = buf.slice(nl + 1);\n if (line.trim()) onLine(line);\n socket.write(\"ok\\n\");\n }\n });\n });\n server.listen(path);\n server.on(\"close\", () => {\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // already gone\n }\n });\n return server;\n}\n\n/**\n * `pai worker say <id> \"<text>\"`: forward one line to a running worker.\n * Resolves \"ok\", rejects with a clear message when the worker is not running.\n */\nexport function sayToWorker(logDir: string, id: string, text: string, timeoutMs = 4000): Promise<string> {\n const status = loadStatus(logDir, id);\n if (!status) {\n return Promise.reject(new Error(`no worker named \"${id}\"`));\n }\n if (status.state !== \"running\" || !alive(status.pid)) {\n return Promise.reject(\n new Error(\n `worker ${id} is not running (state: ${status.state}) — ` +\n `continue it instead with: pai worker resume ${id} \"<text>\"`\n )\n );\n }\n const path = operatorSocketPath(logDir, id);\n if (!existsSync(path)) {\n return Promise.reject(\n new Error(`worker ${id} has no operator socket (${path}) — it may predate this PAI version`)\n );\n }\n return new Promise((resolve, reject) => {\n const sock = connect(path);\n const fail = (e: Error) => {\n sock.destroy();\n reject(new Error(`cannot talk to worker ${id}: ${e.message}`));\n };\n sock.setTimeout(timeoutMs, () => fail(new Error(\"timeout\")));\n sock.once(\"error\", (e: Error) => fail(e));\n sock.once(\"connect\", () => {\n sock.write(text.replace(/\\n/g, \" \") + \"\\n\");\n });\n sock.once(\"data\", () => {\n sock.end();\n resolve(\"ok\");\n });\n });\n}\n","/**\n * server.ts — the PAI worker proxy: Anthropic Messages API on the front,\n * OpenAI Chat Completions on the back, loopback only.\n *\n * One proxy serves every OpenAI-protocol provider: Claude Code points\n * ANTHROPIC_BASE_URL at `http://127.0.0.1:8797/<provider>` and the provider\n * name in the path selects the upstream (`upstreamUrl` + `keyFile` from the\n * workers config, re-read per request so config edits apply without a\n * restart). `run` starts the proxy on demand (detached, pid file under the\n * logDir) and `pai worker proxy stop` stops it again.\n *\n * All translation lives in translate.ts; this file is only HTTP plumbing.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createServer, type Server } from \"node:http\";\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { connect } from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { readWorkersSection, providerKeyPath, type WorkerProvider } from \"../config.js\";\nimport { workersLogDir } from \"../paths.js\";\nimport {\n anthropicError,\n anthropicToOpenAi,\n openAiToAnthropic,\n OpenAiStreamTranslator,\n type AnthropicRequest,\n} from \"./translate.js\";\n\nexport const DEFAULT_PROXY_PORT = 8797;\nconst HOST = \"127.0.0.1\";\n\n/** How providers are looked up — swapped out by the tests. */\nexport type ProviderResolver = () => Record<string, WorkerProvider>;\n\nconst configProviders: ProviderResolver = () => readWorkersSection().workers.providers;\n\nexport function proxyPidPath(logDir: string): string {\n return join(logDir, \"proxy.pid\");\n}\n\n// ---------------------------------------------------------------------------\n// HTTP server\n// ---------------------------------------------------------------------------\n\ninterface ProxyServerOptions {\n port?: number;\n resolveProvider?: ProviderResolver;\n /** Test hook: called with every (translated) upstream request body. */\n onUpstreamRequest?: (url: string, body: unknown) => void;\n}\n\n/**\n * Create (but not start) the proxy server. Routes:\n * GET /healthz → 200 ok\n * POST /<provider>/v1/messages (and /<provider>/messages) → translated call\n */\nexport function createProxyServer(opts: ProxyServerOptions = {}): Server {\n const resolveProvider = opts.resolveProvider ?? configProviders;\n return createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", `http://${HOST}`);\n if (req.method === \"GET\" && url.pathname === \"/healthz\") {\n res.writeHead(200, { \"content-type\": \"text/plain\" });\n res.end(\"ok\");\n return;\n }\n const m = url.pathname.match(/^\\/([a-zA-Z0-9_-]+)\\/(v1\\/)?messages$/);\n if (req.method !== \"POST\" || !m) {\n res.writeHead(404, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(404, `no such route: ${req.method} ${url.pathname}`)));\n return;\n }\n const providerName = m[1];\n const chunks: Buffer[] = [];\n req.on(\"data\", (c: Buffer) => chunks.push(c));\n req.on(\"end\", () => {\n void handleMessages(providerName, Buffer.concat(chunks).toString(\"utf8\"), resolveProvider, res, opts);\n });\n req.on(\"error\", () => {\n res.writeHead(400, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(400, \"request body read failed\")));\n });\n });\n}\n\nasync function handleMessages(\n providerName: string,\n bodyText: string,\n resolveProvider: ProviderResolver,\n res: import(\"node:http\").ServerResponse,\n opts: ProxyServerOptions\n): Promise<void> {\n const fail = (status: number, message: string) => {\n res.writeHead(status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(status, JSON.stringify({ message }))));\n };\n let reqBody: AnthropicRequest;\n try {\n reqBody = JSON.parse(bodyText) as AnthropicRequest;\n } catch {\n fail(400, \"request body is not valid JSON\");\n return;\n }\n const provider = resolveProvider()[providerName];\n if (!provider) {\n fail(404, `no worker provider named \"${providerName}\"`);\n return;\n }\n if (provider.protocol !== \"openai\" || !provider.upstreamUrl) {\n fail(400, `provider \"${providerName}\" is not an openai-protocol provider`);\n return;\n }\n\n const model = provider.models.default;\n const openaiBody = anthropicToOpenAi(reqBody, model);\n opts.onUpstreamRequest?.(`${provider.upstreamUrl}/chat/completions`, openaiBody);\n\n let upstream: Response;\n try {\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n const keyPath = providerKeyPath(provider);\n if (keyPath) {\n try {\n headers.authorization = `Bearer ${readFileSync(keyPath, \"utf8\").trim()}`;\n } catch {\n fail(500, `key file not readable: ${keyPath}`);\n return;\n }\n }\n upstream = await fetch(`${provider.upstreamUrl}/chat/completions`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(openaiBody),\n });\n } catch (e) {\n fail(502, `upstream unreachable: ${e instanceof Error ? e.message : String(e)}`);\n return;\n }\n\n if (!upstream.ok) {\n const text = await upstream.text().catch(() => \"\");\n res.writeHead(upstream.status, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(anthropicError(upstream.status, text)));\n return;\n }\n\n if (reqBody.stream) {\n res.writeHead(200, {\n \"content-type\": \"text/event-stream\",\n \"cache-control\": \"no-cache\",\n connection: \"keep-alive\",\n });\n const translator = new OpenAiStreamTranslator(model);\n let buf = \"\";\n if (!upstream.body) {\n res.end(translator.finish());\n return;\n }\n const reader = upstream.body.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buf += new TextDecoder().decode(value, { stream: true });\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line.startsWith(\"data:\")) continue;\n const payload = line.slice(5).trim();\n if (payload === \"[DONE]\") {\n res.write(translator.finish());\n } else {\n try {\n res.write(translator.feed(JSON.parse(payload) as Parameters<typeof translator.feed>[0]));\n } catch {\n // skip a malformed chunk rather than kill the stream\n }\n }\n }\n }\n } finally {\n res.write(translator.finish()); // no-op when [DONE] already finished it\n res.end();\n }\n return;\n }\n\n const json = (await upstream.json().catch(() => null)) as unknown;\n if (!json) {\n fail(502, \"upstream returned a non-JSON body\");\n return;\n }\n res.writeHead(200, { \"content-type\": \"application/json\" });\n res.end(JSON.stringify(openAiToAnthropic(json as Parameters<typeof openAiToAnthropic>[0], model)));\n}\n\n// ---------------------------------------------------------------------------\n// Start / stop\n// ---------------------------------------------------------------------------\n\n/** Listen on host:port (loopback). Resolves with the bound port. */\nexport function listenProxy(server: Server, port = DEFAULT_PROXY_PORT): Promise<number> {\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, HOST, () => {\n const addr = server.address();\n resolve(typeof addr === \"object\" && addr ? addr.port : port);\n });\n });\n}\n\n/** True when something already accepts connections on the port. */\nexport function proxyListening(port = DEFAULT_PROXY_PORT, timeoutMs = 400): Promise<boolean> {\n return new Promise((resolve) => {\n const sock = connect({ port, host: HOST });\n const done = (ok: boolean) => {\n sock.removeAllListeners();\n sock.destroy();\n resolve(ok);\n };\n sock.setTimeout(timeoutMs, () => done(false));\n sock.once(\"connect\", () => done(true));\n sock.once(\"error\", () => done(false));\n });\n}\n\n/**\n * Where the standalone proxy artefact lives, found from this module's own\n * location (dist/cli bundle → ../hooks/, dev checkout → <repo>/dist/hooks).\n */\nexport function standaloneProxyPath(): string | null {\n let dir = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 6; i++) {\n for (const cand of [\n join(dir, \"worker-proxy.mjs\"),\n join(dir, \"hooks\", \"worker-proxy.mjs\"),\n join(dir, \"dist\", \"hooks\", \"worker-proxy.mjs\"),\n ]) {\n if (existsSync(cand)) return cand;\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n\n/**\n * Ensure a proxy is listening on the port; start the detached standalone when\n * nothing answers. Returns the base URL (`http://127.0.0.1:<port>`).\n */\nexport async function ensureProxyRunning(port: number, logDir: string): Promise<string> {\n if (await proxyListening(port)) return `http://${HOST}:${port}`;\n const script = standaloneProxyPath();\n if (!script) {\n throw new Error(\n `the PAI worker proxy is not running and its build was not found — ` +\n `run \\`bun run build\\` (or start one with: pai worker proxy --port ${port})`\n );\n }\n if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });\n const child = spawn(process.execPath, [script, \"--port\", String(port)], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n writeFileSync(proxyPidPath(logDir), `${child.pid}\\n`, \"utf8\");\n for (let i = 0; i < 40; i++) {\n if (await proxyListening(port)) return `http://${HOST}:${port}`;\n await new Promise((r) => setTimeout(r, 250));\n }\n throw new Error(`the PAI worker proxy did not come up on port ${port} (see ${proxyPidPath(logDir)})`);\n}\n\n/** `pai worker proxy stop`: SIGTERM the pid from the pid file. */\nexport function stopProxy(logDir: string): string {\n const path = proxyPidPath(logDir);\n if (!existsSync(path)) return \"no proxy pid file — nothing to stop\";\n const pid = parseInt(readFileSync(path, \"utf8\").trim(), 10);\n unlinkSync(path);\n if (!Number.isFinite(pid)) return \"stale proxy pid file removed\";\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n return `proxy pid ${pid} was not running (pid file removed)`;\n }\n return `proxy pid ${pid} stopped`;\n}\n","/**\n * codex.ts — the \"codex\" runner engine.\n *\n * A ChatGPT plan gives no API key, only Codex CLI access, so a provider may\n * set `engine: \"codex\"`: `run` then shells out to `codex exec --json <prompt>`\n * (non-interactive) instead of Claude Code. The JSONL event stream is parsed\n * into the same status-file fields (turns, tools, last) and ledger lines as a\n * Claude run, and is normalised into claude-code-shaped events in the\n * worker's .jsonl so follow/replay/the pane render it unchanged.\n *\n * Implemented against the documented `codex exec --json` interface\n * (thread.started / item.completed / turn.completed / turn.failed lines);\n * verify against the installed CLI when one is present.\n * `--allowedTools` has no Codex equivalent and is dropped (ledgered).\n */\n\nimport { spawn, execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync as readKey } from \"node:fs\";\nimport { providerKeyPath, type WorkerProvider } from \"./config.js\";\n\n/** Build the codex exec argument vector (without the binary itself). */\nexport function buildCodexArgs(prompt: string, model: string | undefined): string[] {\n return [\n \"exec\",\n \"--json\",\n \"--skip-git-repo-check\",\n ...(model ? [\"-m\", model] : []),\n \"--\",\n prompt,\n ];\n}\n\n/** Env for a codex run: caller's env, Anthropic vars stripped, key applied. */\nexport function buildCodexEnv(provider: WorkerProvider): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n delete env.ANTHROPIC_API_KEY;\n delete env.ANTHROPIC_BASE_URL;\n delete env.ANTHROPIC_AUTH_TOKEN;\n delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;\n delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;\n delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;\n // API-key providers: OpenAI env from keyFile/baseUrl; ChatGPT-login codex\n // (no keyFile) keeps its own auth from ~/.codex.\n const keyPath = providerKeyPath(provider);\n if (keyPath && existsSync(keyPath)) {\n env.OPENAI_API_KEY = readKey(keyPath, \"utf8\").trim();\n }\n if (provider.upstreamUrl) env.OPENAI_BASE_URL = provider.upstreamUrl;\n for (const [k, v] of Object.entries(provider.env)) env[k] = v;\n env.PAI_WORKER = \"1\";\n return env;\n}\n\n/** Codex \"not applicable\" flags the runner drops with a ledger note. */\nexport function codexDroppedFlags(argv: string[]): string[] {\n const dropped: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--allowedTools\" || a === \"--disallowedTools\" || a === \"--mcp-config\" || a === \"--mcp\") {\n dropped.push(a);\n if (i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) i++; // and its value\n } else if (a.startsWith(\"--allowedTools=\") || a.startsWith(\"--mcp-config=\")) {\n dropped.push(a.split(\"=\")[0]);\n }\n }\n return dropped;\n}\n\n// ---------------------------------------------------------------------------\n// Event stream parsing (pure — tested with recorded JSONL lines)\n// ---------------------------------------------------------------------------\n\n/** One normalised claude-code-shaped event + its effect on the status. */\nexport interface CodexEventResult {\n events: Array<Record<string, unknown>>;\n turns: number;\n tools: number;\n last: string | null;\n isError: boolean;\n contextTokens: number | null;\n finalText: string | null;\n threadId: string | null;\n}\n\nexport const emptyCodexResult = (): CodexEventResult => ({\n events: [],\n turns: 0,\n tools: 0,\n last: null,\n isError: false,\n contextTokens: null,\n finalText: null,\n threadId: null,\n});\n\n/**\n * Fold one parsed `codex exec --json` line into the running result: appends\n * normalised transcript events and updates the counters in place.\n */\nexport function foldCodexLine(line: unknown, r: CodexEventResult): void {\n if (typeof line !== \"object\" || line === null) return;\n const e = line as Record<string, unknown>;\n const type = typeof e.type === \"string\" ? e.type : \"\";\n\n if (type === \"thread.started\" && typeof e.thread_id === \"string\") {\n r.threadId = e.thread_id;\n return;\n }\n if (type === \"item.completed\" && typeof e.item === \"object\" && e.item !== null) {\n const item = e.item as Record<string, unknown>;\n const kind = typeof item.type === \"string\" ? item.type : \"\";\n if (kind === \"agent_message\" && typeof item.text === \"string\") {\n r.turns += 1;\n r.last = `says: ${item.text.slice(0, 70)}`;\n r.finalText = item.text;\n r.events.push({\n type: \"assistant\",\n message: { content: [{ type: \"text\", text: item.text }] },\n });\n } else if (kind === \"command_execution\") {\n r.tools += 1;\n const cmd = typeof item.command === \"string\" ? item.command : \"?\";\n const rc = typeof item.exit_code === \"number\" ? item.exit_code : 0;\n r.last = `Bash: ${cmd.slice(0, 70)}`;\n r.events.push({\n type: \"assistant\",\n message: { content: [{ type: \"tool_use\", name: \"Bash\", input: { command: cmd } }] },\n });\n r.events.push({\n type: \"user\",\n message: {\n content: [\n {\n type: \"tool_result\",\n tool_use_id: \"\",\n is_error: rc !== 0,\n content: item.aggregated_output ?? \"\",\n },\n ],\n },\n });\n } else if (kind === \"file_change\") {\n r.tools += 1;\n const changes = Array.isArray(item.changes) ? item.changes : [];\n const files = changes\n .map((c) => (typeof c === \"object\" && c !== null && typeof (c as { path?: unknown }).path === \"string\" ? (c as { path: string }).path : \"?\"))\n .join(\", \");\n r.last = `files: ${files.slice(0, 70)}`;\n r.events.push({\n type: \"assistant\",\n message: { content: [{ type: \"tool_use\", name: \"Write\", input: { file_path: files } }] },\n });\n } else if (kind === \"mcp_tool_call\") {\n r.tools += 1;\n r.last = `mcp: ${String(item.tool ?? \"?\")}`;\n }\n return;\n }\n if (type === \"turn.completed\") {\n const usage = (typeof e.usage === \"object\" && e.usage !== null ? e.usage : {}) as {\n input_tokens?: number;\n output_tokens?: number;\n cached_input_tokens?: number;\n };\n const tokens =\n (usage.input_tokens ?? 0) + (usage.cached_input_tokens ?? 0) + (usage.output_tokens ?? 0);\n if (tokens > 0) r.contextTokens = tokens;\n return;\n }\n if (type === \"turn.failed\" || type === \"error\") {\n r.isError = true;\n const err = typeof e.error === \"object\" && e.error !== null ? e.error : {};\n r.last = String((err as { message?: unknown }).message ?? e.message ?? \"codex turn failed\");\n }\n}\n\n/** Parse one JSONL line; null for blanks and non-JSON noise. */\nexport function parseCodexLine(line: string): unknown | null {\n const t = line.trim();\n if (!t.startsWith(\"{\")) return null;\n try {\n return JSON.parse(t) as unknown;\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// The engine probe used by `providers test`\n// ---------------------------------------------------------------------------\n\n/** True when the Codex CLI is on PATH (test reports \"not installed\" else). */\nexport function codexInstalled(): boolean {\n try {\n execFileSync(\"codex\", [\"--version\"], { timeout: 5000, stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Spawn helper shared by run and test; caller wires stdout. */\nexport function spawnCodex(args: string[], env: NodeJS.ProcessEnv, cwd: string) {\n return spawn(\"codex\", args, { env, cwd, stdio: [\"ignore\", \"pipe\", \"inherit\"] });\n}\n","/**\n * run.ts — the worker runner (port of the glm / glm-run pair, provider-neutral).\n *\n * One claude-code process per call, pointed at the chosen provider:\n *\n * - env: ANTHROPIC_BASE_URL/AUTH_TOKEN from the provider (token from its\n * key file, never from the environment), the three DEFAULT_*_MODEL vars,\n * the provider's extra env, nonessential traffic off, and ANTHROPIC_API_KEY\n * stripped so nothing can fall back to Anthropic billing. OpenAI-protocol\n * providers point at the PAI proxy instead (started on demand, the\n * provider name in the URL path); codex-engine providers run the Codex CLI.\n * - headless (-p): strict empty MCP config unless the caller brings one or\n * names servers via --mcp / a role (then a filtered <id>.mcp.json),\n * PAI_WORKER=1 so PAI's per-session hooks leave it alone, the worker\n * contract appended to the system prompt, `--input-format stream-json`\n * with the prompt as the first stdin user message (the operator socket\n * can add more mid-run), stream-json mirroring (every line stamped `_ts`)\n * into <logDir>/<id>.jsonl, a live <id>.status for ps/follow/status line\n * (context meter included), ledger lines, result printed in the caller's\n * --output-format (json adds the parsed `report`), and the follow pane\n * (unless --no-pane).\n * - interactive: no MCP restriction, no pane, ENABLE_TOOL_SEARCH=true.\n *\n * Auto-routed runs that die of a quota error before the first tool call are\n * restarted on the next provider in routing order (WORKER-REROUTE ledger line).\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport {\n existsSync,\n mkdirSync,\n openSync,\n readFileSync as readKey,\n writeFileSync,\n closeSync,\n writeSync,\n} from \"node:fs\";\nimport { parseRunnerArgs, shortText, stripPromptValues } from \"./args.js\";\nimport {\n assertProviderRunnable,\n providerContextWindow,\n providerKeyPath,\n readWorkersSection,\n type WorkerProvider,\n} from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { eventsPath, ledgerPath, noMcpConfigPath, workersLogDir } from \"./paths.js\";\nimport {\n type WorkerStatus,\n newWorkerId,\n saveStatus,\n describeTool,\n nowStamp,\n} from \"./status.js\";\nimport { resolveSession } from \"./scope.js\";\nimport { isQuotaFailure, nextAutoProvider, resolveTarget, setCooldown } from \"./routing.js\";\nimport { openPaneForWorker } from \"./pane.js\";\nimport { WORKER_CONTRACT_PROMPT, parseWorkerReport, type WorkerReport } from \"./report.js\";\nimport { expandMcpNames, writeMcpConfig } from \"./mcp.js\";\nimport { createOperatorServer } from \"./operator.js\";\nimport { DEFAULT_PROXY_PORT, ensureProxyRunning } from \"./proxy/server.js\";\nimport {\n buildCodexArgs,\n buildCodexEnv,\n codexDroppedFlags,\n codexInstalled,\n emptyCodexResult,\n foldCodexLine,\n parseCodexLine,\n} from \"./codex.js\";\n\nexport interface RunOptions {\n providerFlag?: string;\n /** --class value (the old --role): a task class from workers.classes. */\n className?: string;\n modelFlag?: string;\n label?: string;\n noPane?: boolean;\n /** --mcp value: server/set names, comma-separated. */\n mcpFlag?: string;\n /** Everything after `--` (the claude args). */\n claudeArgs: string[];\n /** Working directory for the run (default: this process's cwd). */\n cwd?: string;\n /** Chain stage bookkeeping: the chain id this stage belongs to. */\n parent?: string;\n /** Chain stage bookkeeping: the class name of this stage. */\n stage?: string;\n /** Suppress result printing (MCP worker_run: its stdout is the RPC channel). */\n quiet?: boolean;\n /** Internal: notified with the worker id once it exists (resume uses it). */\n onWorkerStart?: (wid: string) => void;\n /** Internal: suppress recursion depth on reroute. */\n _reroutes?: number;\n}\n\n/** Environment for a run through `provider`. Caller's env minus the Anthropic key. */\nexport function buildRunEnv(\n provider: WorkerProvider,\n headless: boolean,\n proxyUrl?: string\n): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n delete env.ANTHROPIC_API_KEY;\n\n let token = \"local\";\n if (proxyUrl) {\n // openai-protocol provider: the proxy holds the real key; the runner only\n // needs a placeholder so Claude Code sends an auth header at all\n env.ANTHROPIC_BASE_URL = proxyUrl;\n } else {\n const keyPath = providerKeyPath(provider);\n if (keyPath) {\n try {\n token = readKey(keyPath, \"utf8\").trim();\n } catch {\n // the caller turns this into a clear error before spawning\n throw new Error(`key file not readable: ${keyPath}`);\n }\n if (!token) throw new Error(`key file is empty: ${keyPath}`);\n }\n env.ANTHROPIC_BASE_URL = provider.baseUrl;\n }\n\n env.ANTHROPIC_AUTH_TOKEN = token;\n env.ANTHROPIC_DEFAULT_HAIKU_MODEL = provider.models.fast ?? provider.models.default;\n env.ANTHROPIC_DEFAULT_SONNET_MODEL = provider.models.default;\n env.ANTHROPIC_DEFAULT_OPUS_MODEL = provider.models.default;\n for (const [k, v] of Object.entries(provider.env)) env[k] = v;\n env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = \"1\";\n if (headless) {\n env.PAI_WORKER = \"1\";\n } else {\n env.ENABLE_TOOL_SEARCH = \"true\";\n }\n return env;\n}\n\n/** The strict empty MCP config for headless workers, written on demand. */\nexport function ensureNoMcpConfig(logDir: string): string {\n const path = noMcpConfigPath(logDir);\n if (!existsSync(path)) {\n mkdirSync(logDir, { recursive: true });\n writeFileSync(path, '{ \"mcpServers\": {} }\\n', \"utf8\");\n }\n return path;\n}\n\n/** A stream-json user message for the child's stdin. */\nexport function stdinUserMessage(text: string): string {\n return JSON.stringify({ type: \"user\", message: { role: \"user\", content: text } });\n}\n\n/**\n * ISO stamp with seconds, attached to every mirrored event (2g): local time\n * with its offset (`2026-09-17T14:19:23+02:00`), so the viewer can render the\n * wall clock the operator lives in. `offMin` is east-positive minutes —\n * injectable so tests do not depend on the machine's zone.\n */\nexport function isoStamp(d = new Date(), offMin = -d.getTimezoneOffset()): string {\n const t = new Date(d.getTime() + offMin * 60_000);\n const sign = offMin < 0 ? \"-\" : \"+\";\n const abs = Math.abs(offMin);\n const hh = String(Math.floor(abs / 60)).padStart(2, \"0\");\n const mm = String(abs % 60).padStart(2, \"0\");\n return `${t.toISOString().slice(0, 19)}${sign}${hh}:${mm}`;\n}\n\nexport interface UsageBlock {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n}\n\n// a type alias (not an interface): it must stay assignable to\n// Record<string, unknown> when written into the event transcript\nexport type StreamEvent = {\n type?: string;\n subtype?: string;\n session_id?: string;\n model?: string;\n cwd?: string;\n context_window?: number;\n model_info?: { context_window?: number } | null;\n message?: {\n content?: Array<{ type?: string; text?: string; name?: string; id?: string; input?: unknown }>;\n usage?: UsageBlock;\n };\n usage?: UsageBlock;\n result?: string;\n is_error?: boolean;\n num_turns?: number;\n duration_ms?: number;\n};\n\n/** Context tokens of an assistant/result usage block (input+cache+output). */\nexport function usageContextTokens(u: UsageBlock | undefined): number | null {\n if (!u) return null;\n const t =\n (u.input_tokens ?? 0) +\n (u.cache_read_input_tokens ?? 0) +\n (u.cache_creation_input_tokens ?? 0) +\n (u.output_tokens ?? 0);\n return t > 0 ? t : null;\n}\n\n/** Context window announced by the init event, when the endpoint sends one. */\nexport function initContextWindow(e: StreamEvent): number | null {\n if (typeof e.context_window === \"number\" && e.context_window > 0) return e.context_window;\n if (e.model_info && typeof e.model_info.context_window === \"number\" && e.model_info.context_window > 0) {\n return e.model_info.context_window;\n }\n return null;\n}\n\n/**\n * Run one worker. Returns the process exit code to pass through.\n * Throws WorkersConfigError-shaped Errors for configuration problems.\n */\nexport async function runWorker(opts: RunOptions): Promise<number> {\n const { raw: _raw, workers: config } = readWorkersSection();\n void _raw;\n if (!config.enabled) {\n throw new Error(\n `workers are off. Turn them on with: pai worker on` +\n `\\n(then the Agent-tool hook stops denying Anthropic subagents only when you do)`\n );\n }\n const logDir = workersLogDir(config);\n mkdirSync(logDir, { recursive: true });\n\n const target = resolveTarget(config, logDir, {\n flagProvider: opts.providerFlag,\n className: opts.className,\n });\n assertProviderRunnable(target.providerName, target.provider);\n\n const parsed = parseRunnerArgs(opts.claudeArgs);\n const label =\n opts.label ??\n shortText(parsed.prompt ?? \"(no prompt)\", 70);\n\n const model =\n opts.modelFlag ??\n (target.modelAlias === \"fast\"\n ? target.provider.models.fast ?? target.provider.models.default\n : target.provider.models.default);\n\n try {\n if (target.provider.engine === \"codex\") {\n return await executeCodexRun({\n config,\n logDir,\n target,\n model,\n label,\n parsed,\n claudeArgs: opts.claudeArgs,\n noPane: opts.noPane ?? false,\n cwd: opts.cwd,\n parent: opts.parent,\n stage: opts.stage,\n quiet: opts.quiet,\n onWorkerStart: opts.onWorkerStart,\n });\n }\n return await executeRun({\n config,\n logDir,\n target,\n model,\n label,\n parsed,\n claudeArgs: opts.claudeArgs,\n noPane: opts.noPane ?? false,\n mcpFlag: opts.mcpFlag,\n cwd: opts.cwd,\n parent: opts.parent,\n stage: opts.stage,\n quiet: opts.quiet,\n onWorkerStart: opts.onWorkerStart,\n reroutes: opts._reroutes ?? 0,\n });\n } catch (e) {\n if (e instanceof Error && e.message.startsWith(\"key file\")) {\n throw new Error(\n `provider \"${target.providerName}\": ${e.message}` +\n `\\nPut the token in that file (chmod 600) or point keyFile elsewhere.`\n );\n }\n throw e;\n }\n}\n\ninterface ExecuteArgs {\n config: ReturnType<typeof readWorkersSection>[\"workers\"];\n logDir: string;\n target: ReturnType<typeof resolveTarget>;\n model: string;\n label: string;\n parsed: ReturnType<typeof parseRunnerArgs>;\n claudeArgs: string[];\n noPane: boolean;\n mcpFlag?: string;\n cwd?: string;\n parent?: string;\n stage?: string;\n quiet?: boolean;\n onWorkerStart?: (wid: string) => void;\n reroutes: number;\n}\n\nasync function executeRun(a: ExecuteArgs): Promise<number> {\n const { config, logDir, target, model, label, parsed, noPane } = a;\n const headless = parsed.headless;\n\n // openai-protocol providers run through the local proxy (started on demand)\n let proxyUrl: string | undefined;\n if (target.provider.protocol === \"openai\") {\n const base = await ensureProxyRunning(DEFAULT_PROXY_PORT, logDir);\n proxyUrl = `${base}/${target.providerName}`;\n }\n const env = buildRunEnv(target.provider, headless, proxyUrl);\n\n const wid = newWorkerId();\n const cwd = a.cwd ?? process.cwd();\n const term = process.env.ITERM_SESSION_ID ?? \"\";\n const session = resolveSession(term);\n\n const status: WorkerStatus = {\n id: wid,\n pid: process.pid,\n label,\n cwd,\n term,\n provider: target.providerName,\n model,\n state: \"running\",\n started: nowStamp(),\n updated: nowStamp(),\n turns: 0,\n tools: 0,\n last: headless ? \"starting\" : \"interactive\",\n rc: null,\n secs: null,\n ...(session ? { session } : {}),\n contextWindow: providerContextWindow(target.provider),\n ...(a.parent ? { parent: a.parent, stage: a.stage } : {}),\n };\n saveStatus(logDir, status);\n a.onWorkerStart?.(wid);\n const ledger = ledgerPath(logDir);\n appendLedger(ledger, \"WORKER-START\", {\n id: wid,\n provider: target.providerName,\n mode: headless ? \"headless\" : \"interactive\",\n model,\n cwd,\n label,\n });\n\n // Follow pane: headless only, best effort, never blocking the worker.\n if (headless && !noPane && config.pane.enabled && term && process.env.PAI_WORKER_AUTOPANE !== \"0\") {\n void openPaneForWorker(logDir, config, wid, term).catch(() => {});\n }\n\n // MCP: caller config > allowlist (--mcp flag / --mcp args / role) > the strict empty set.\n let mcpArgs: string[] = [];\n if (headless && !parsed.callerMcpConfig) {\n const wanted = [\n ...(a.mcpFlag ? [a.mcpFlag] : []),\n ...parsed.mcp,\n ...(target.classMcp ?? []),\n ];\n if (wanted.length) {\n const names = expandMcpNames(wanted, config); // unknown names fail fast\n mcpArgs = [\"--strict-mcp-config\", \"--mcp-config\", writeMcpConfig(logDir, wid, names)];\n } else {\n mcpArgs = [\"--strict-mcp-config\", \"--mcp-config\", ensureNoMcpConfig(logDir)];\n }\n }\n\n // In stdin mode the prompt moves to the first user message on stdin, so it\n // must come off the command line (bare -p stays: stream-json needs --print).\n const restArgs = headless ? stripPromptValues(parsed.rest) : parsed.rest;\n const cmd: string[] = [\"claude\"];\n if (!parsed.callerModel) cmd.push(\"--model\", model);\n cmd.push(...mcpArgs, ...restArgs);\n if (headless) {\n cmd.push(\"--output-format\", \"stream-json\", \"--verbose\", \"--input-format\", \"stream-json\");\n if (!parsed.callerSystemPrompt) cmd.push(\"--append-system-prompt\", WORKER_CONTRACT_PROMPT);\n }\n\n const t0 = Date.now();\n const proc = spawn(cmd[0], cmd.slice(1), {\n env,\n cwd,\n stdio: headless ? [\"pipe\", \"pipe\", \"inherit\"] : \"inherit\",\n });\n\n // --- stdin lifecycle (2i): prompt in, socket forwards, close 2 s after result\n let operatorInFlight = 0;\n let closeTimer: NodeJS.Timeout | null = null;\n const armStdinClose = () => {\n if (closeTimer) clearTimeout(closeTimer);\n closeTimer = setTimeout(() => {\n if (operatorInFlight === 0) {\n try {\n proc.stdin?.end();\n } catch {\n /* already closed */\n }\n }\n }, 2_000);\n };\n\n let eventsFd: number | null = null;\n const writeEvent = (obj: Record<string, unknown>): void => {\n if (eventsFd === null) return;\n try {\n writeSync(eventsFd, JSON.stringify({ ...obj, _ts: isoStamp() }) + \"\\n\");\n } catch {\n // a full disk must not take the worker transcript's process down\n }\n };\n\n const operatorServer = headless\n ? createOperatorServer(logDir, wid, (text) => {\n operatorInFlight += 1;\n if (closeTimer) {\n clearTimeout(closeTimer);\n closeTimer = null;\n }\n writeEvent({ type: \"operator\", text });\n try {\n proc.stdin?.write(stdinUserMessage(text) + \"\\n\");\n } catch {\n /* child gone; the socket is closed by the run's cleanup */\n }\n })\n : null;\n\n let killed = false;\n const cleanup = () => {\n if (closeTimer) clearTimeout(closeTimer);\n operatorServer?.close();\n };\n const onSignal = (sig: string) => {\n killed = true;\n status.state = \"killed\";\n status.rc = 143;\n status.secs = Math.floor((Date.now() - t0) / 1000);\n status.last = `killed by signal ${sig}`;\n saveStatus(logDir, status);\n appendLedger(ledger, \"WORKER-END\", {\n id: wid,\n provider: target.providerName,\n mode: \"headless\",\n model,\n rc: 143,\n secs: status.secs,\n killed: 1,\n label,\n });\n cleanup();\n try {\n proc.kill();\n } catch {\n /* already gone */\n }\n process.exit(143);\n };\n process.once(\"SIGTERM\", () => onSignal(\"SIGTERM\"));\n process.once(\"SIGINT\", () => onSignal(\"SIGINT\"));\n\n // Holder for the last result event + its parsed report: assigned inside the\n // readline callback below, read after the await.\n const ctx: { resultEvent: StreamEvent | null; resultReport: WorkerReport | null } = {\n resultEvent: null,\n resultReport: null,\n };\n\n if (headless) {\n // the first user message carries the prompt (the -p value was stripped)\n if (parsed.prompt !== null) {\n try {\n proc.stdin!.write(stdinUserMessage(parsed.prompt) + \"\\n\");\n } catch {\n /* child died instantly; the close handler reports it */\n }\n }\n eventsFd = openSync(eventsPath(logDir, wid), \"a\");\n const rl = createInterface({ input: proc.stdout! });\n rl.on(\"line\", (line) => {\n if (parsed.outputFormat === \"stream-json\") {\n process.stdout.write(line + \"\\n\");\n }\n if (!line.startsWith(\"{\")) return;\n let e: StreamEvent;\n try {\n e = JSON.parse(line) as StreamEvent;\n } catch {\n return;\n }\n writeEvent(e as Record<string, unknown>);\n if (e.type === \"system\" && e.subtype === \"init\") {\n if (e.session_id) status.claudeSession = e.session_id;\n const cw = initContextWindow(e);\n if (cw) status.contextWindow = cw;\n saveStatus(logDir, status);\n } else if (e.type === \"assistant\") {\n status.turns += 1;\n const tokens = usageContextTokens(e.message?.usage);\n if (tokens) status.contextTokens = tokens;\n for (const block of e.message?.content ?? []) {\n if (block.type === \"tool_use\") {\n status.tools += 1;\n status.last = describeTool(block.name ?? \"?\", block.input);\n } else if (block.type === \"text\" && (block.text ?? \"\").trim()) {\n status.last = \"says: \" + shortText(block.text, 70);\n }\n }\n saveStatus(logDir, status);\n } else if (e.type === \"result\") {\n const tokens = usageContextTokens(e.usage);\n if (tokens) status.contextTokens = tokens;\n const report = parseWorkerReport(e.result ?? \"\");\n if (report?.notes) status.last = shortText(report.notes, 90);\n else if (e.result) status.last = shortText(e.result, 90);\n saveStatus(logDir, status);\n operatorInFlight = 0;\n armStdinClose();\n ctx.resultEvent = e;\n ctx.resultReport = report;\n }\n });\n }\n\n const rc = await new Promise<number>((resolve, reject) => {\n proc.on(\"error\", reject);\n proc.on(\"close\", (code) => resolve(code ?? (killed ? 143 : 1)));\n });\n if (eventsFd !== null) closeSync(eventsFd);\n cleanup();\n\n const secs = Math.floor((Date.now() - t0) / 1000);\n const resultEvent = ctx.resultEvent;\n const ok = rc === 0 && resultEvent !== null && !resultEvent.is_error;\n status.state = ok ? \"done\" : \"failed\";\n status.rc = rc;\n status.secs = secs;\n if (resultEvent) status.last = shortText(resultEvent.result ?? \"\", 90);\n if (ctx.resultReport?.notes) status.last = shortText(ctx.resultReport.notes, 90);\n saveStatus(logDir, status);\n appendLedger(ledger, \"WORKER-END\", {\n id: wid,\n provider: target.providerName,\n mode: headless ? \"headless\" : \"interactive\",\n model,\n rc,\n secs,\n turns: status.turns,\n tools: status.tools,\n label,\n });\n\n if (headless && !a.quiet) printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, ctx.resultReport);\n\n // Quota reroute: only auto-routed runs, dead before the first tool call.\n const resultText = resultEvent?.result ?? \"\";\n if (\n !ok &&\n headless &&\n a.target.via === \"auto\" &&\n config.routing.retryOnQuota &&\n status.turns <= 1 &&\n status.tools === 0 &&\n isQuotaFailure(resultText)\n ) {\n setCooldown(logDir, target.providerName, config.routing.cooldownMinutes);\n const next = nextAutoProvider(config, logDir, target.providerName);\n if (next && a.reroutes < config.routing.order.length) {\n appendLedger(ledger, \"WORKER-REROUTE\", {\n from: target.providerName,\n to: next,\n reason: \"quota\",\n });\n return runWorker({\n providerFlag: next,\n label,\n noPane: a.noPane,\n mcpFlag: a.mcpFlag,\n cwd: a.cwd,\n parent: a.parent,\n stage: a.stage,\n claudeArgs: a.claudeArgs,\n onWorkerStart: a.onWorkerStart,\n _reroutes: a.reroutes + 1,\n });\n }\n }\n\n return rc !== 0 ? rc : ok ? 0 : 1;\n}\n\n// ---------------------------------------------------------------------------\n// codex engine (2d)\n// ---------------------------------------------------------------------------\n\ninterface CodexArgs extends Omit<ExecuteArgs, \"reroutes\" | \"mcpFlag\"> {}\n\nasync function executeCodexRun(a: CodexArgs): Promise<number> {\n const { config, logDir, target, model, label, parsed, noPane } = a;\n if (!parsed.headless || parsed.prompt === null) {\n throw new Error(\n `provider \"${target.providerName}\" (engine codex) supports headless runs only: ` +\n `pass the task with -p '<prompt>'`\n );\n }\n const env = buildCodexEnv(target.provider);\n const wid = newWorkerId();\n const cwd = a.cwd ?? process.cwd();\n const term = process.env.ITERM_SESSION_ID ?? \"\";\n const session = resolveSession(term);\n\n const status: WorkerStatus = {\n id: wid,\n pid: process.pid,\n label,\n cwd,\n term,\n provider: target.providerName,\n model,\n state: \"running\",\n started: nowStamp(),\n updated: nowStamp(),\n turns: 0,\n tools: 0,\n last: \"starting\",\n rc: null,\n secs: null,\n ...(session ? { session } : {}),\n contextWindow: providerContextWindow(target.provider),\n ...(a.parent ? { parent: a.parent, stage: a.stage } : {}),\n };\n saveStatus(logDir, status);\n a.onWorkerStart?.(wid);\n const ledger = ledgerPath(logDir);\n appendLedger(ledger, \"WORKER-START\", {\n id: wid,\n provider: target.providerName,\n mode: \"headless\",\n engine: \"codex\",\n model,\n cwd,\n label,\n });\n const dropped = codexDroppedFlags(a.claudeArgs);\n if (dropped.length) {\n appendLedger(ledger, \"WORKER-NOTE\", {\n id: wid,\n note: `dropped for codex: ${dropped.join(\", \")}`,\n });\n }\n\n if (!noPane && config.pane.enabled && term && process.env.PAI_WORKER_AUTOPANE !== \"0\") {\n void openPaneForWorker(logDir, config, wid, term).catch(() => {});\n }\n\n const t0 = Date.now();\n const proc = spawn(\"codex\", buildCodexArgs(parsed.prompt, parsed.callerModel ? undefined : model), {\n env,\n cwd,\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n });\n\n const fold = emptyCodexResult();\n const eventsFd = openSync(eventsPath(logDir, wid), \"a\");\n const writeEvent = (obj: Record<string, unknown>) => {\n try {\n writeSync(eventsFd, JSON.stringify({ ...obj, _ts: isoStamp() }) + \"\\n\");\n } catch {\n // best effort transcript\n }\n };\n writeEvent({ type: \"system\", subtype: \"init\", model, cwd });\n\n let killed = false;\n process.once(\"SIGTERM\", onCodexSignal(\"SIGTERM\"));\n process.once(\"SIGINT\", onCodexSignal(\"SIGINT\"));\n function onCodexSignal(sig: string) {\n return () => {\n killed = true;\n status.state = \"killed\";\n status.rc = 143;\n status.secs = Math.floor((Date.now() - t0) / 1000);\n status.last = `killed by signal ${sig}`;\n saveStatus(logDir, status);\n try {\n proc.kill();\n } catch {\n /* already gone */\n }\n process.exit(143);\n };\n }\n\n const rl = createInterface({ input: proc.stdout! });\n rl.on(\"line\", (line) => {\n if (parsed.outputFormat === \"stream-json\") process.stdout.write(line + \"\\n\");\n const parsedLine = parseCodexLine(line);\n if (parsedLine === null) return;\n foldCodexLine(parsedLine, fold);\n if (fold.threadId && !status.claudeSession) status.claudeSession = fold.threadId;\n status.turns = fold.turns;\n status.tools = fold.tools;\n if (fold.last) status.last = shortText(fold.last, 90);\n if (fold.contextTokens) status.contextTokens = fold.contextTokens;\n saveStatus(logDir, status);\n for (const ev of fold.events.splice(0)) writeEvent(ev);\n });\n\n const rc = await new Promise<number>((resolve, reject) => {\n proc.on(\"error\", reject);\n proc.on(\"close\", (code) => resolve(code ?? (killed ? 143 : 1)));\n });\n closeSync(eventsFd);\n\n const secs = Math.floor((Date.now() - t0) / 1000);\n const finalText = fold.finalText ?? \"\";\n const report = parseWorkerReport(finalText);\n const resultEvent: StreamEvent = {\n type: \"result\",\n result: finalText,\n is_error: fold.isError || rc !== 0,\n num_turns: fold.turns,\n duration_ms: secs * 1000,\n };\n writeEvent(resultEvent);\n\n const ok = rc === 0 && !fold.isError;\n status.state = ok ? \"done\" : \"failed\";\n status.rc = rc;\n status.secs = secs;\n status.last = shortText(report?.notes ?? finalText, 90) || (ok ? \"done\" : \"failed\");\n saveStatus(logDir, status);\n appendLedger(ledger, \"WORKER-END\", {\n id: wid,\n provider: target.providerName,\n mode: \"headless\",\n engine: \"codex\",\n model,\n rc,\n secs,\n turns: status.turns,\n tools: status.tools,\n label,\n });\n\n if (!a.quiet) printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, report);\n return rc !== 0 ? rc : ok ? 0 : 1;\n}\n\n// ---------------------------------------------------------------------------\n// result printing\n// ---------------------------------------------------------------------------\n\nfunction printResult(\n fmt: \"text\" | \"json\" | \"stream-json\",\n resultEvent: StreamEvent | null,\n rc: number,\n logDir: string,\n wid: string,\n report?: WorkerReport | null\n): void {\n if (fmt === \"stream-json\") return; // already mirrored live\n if (fmt === \"json\") {\n const payload = report\n ? { ...(resultEvent ?? { is_error: true, result: \"no result event\", rc }), report }\n : resultEvent ?? { is_error: true, result: \"no result event\", rc };\n console.log(JSON.stringify(payload));\n return;\n }\n if (resultEvent) {\n console.log(resultEvent.result ?? \"\");\n } else {\n process.stderr.write(\n `pai worker: run produced no result (rc=${rc}); see ${eventsPath(logDir, wid)}\\n`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// providers test: the 90-second pong probe\n// ---------------------------------------------------------------------------\n\nexport interface ProviderTestResult {\n provider: string;\n model: string;\n latencyMs: number;\n result: string;\n ok: boolean;\n /** Set when the probe could not run (e.g. \"codex not installed\"). */\n skipped?: string;\n}\n\n/**\n * Run a one-word pong probe through the provider (headless, no pane) and\n * report provider, model, latency and the reply. ok is false unless the reply\n * was exactly \"pong\" (case-insensitive, whitespace-trimmed).\n */\nexport async function testProvider(\n providerName: string,\n provider: WorkerProvider,\n logDir: string,\n timeoutMs = 90_000\n): Promise<ProviderTestResult> {\n assertProviderRunnable(providerName, provider);\n const model = provider.models.default;\n\n if (provider.engine === \"codex\") {\n const skipped = codexInstalled() ? undefined : \"codex not installed\";\n return {\n provider: providerName,\n model,\n latencyMs: 0,\n result: skipped ?? \"codex engine: pong probe through codex exec not implemented\",\n ok: false,\n ...(skipped ? { skipped } : {}),\n };\n }\n\n let proxyUrl: string | undefined;\n if (provider.protocol === \"openai\") {\n const base = await ensureProxyRunning(DEFAULT_PROXY_PORT, logDir);\n proxyUrl = `${base}/${providerName}`;\n }\n const env = buildRunEnv(provider, true, proxyUrl);\n const t0 = Date.now();\n const proc = spawn(\n \"claude\",\n [\n \"--model\", model,\n \"--strict-mcp-config\", \"--mcp-config\", ensureNoMcpConfig(logDir),\n \"-p\", \"Reply with exactly one word: pong\",\n \"--output-format\", \"json\",\n ],\n { env, stdio: [\"ignore\", \"pipe\", \"inherit\"] }\n );\n const timer = setTimeout(() => {\n try { proc.kill(\"SIGKILL\"); } catch { /* already gone */ }\n }, timeoutMs);\n\n let out = \"\";\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n out += chunk.toString(\"utf8\");\n });\n const rc = await new Promise<number>((resolve) => {\n proc.on(\"error\", () => resolve(1));\n proc.on(\"close\", (code) => resolve(code ?? 1));\n });\n clearTimeout(timer);\n\n return {\n provider: providerName,\n model,\n latencyMs: Date.now() - t0,\n result: resultFromOutput(out),\n ok: rc === 0 && resultFromOutput(out).trim().toLowerCase() === \"pong\",\n };\n}\n\n/**\n * Pull the reply out of claude's stdout. With --verbose, `--output-format\n * json` dumps a JSON array of stream events and the reply sits in the last\n * \"result\" event; without it, stdout is the single result object. Accept\n * either shape, plus a bare-text fallback for error output.\n */\nexport function resultFromOutput(out: string): string {\n const trimmed = out.trim();\n if (!trimmed) return \"\";\n const parse = (s: string): unknown => {\n try {\n return JSON.parse(s);\n } catch {\n return undefined;\n }\n };\n const fromValue = (v: unknown): string | null => {\n if (Array.isArray(v)) {\n for (let i = v.length - 1; i >= 0; i--) {\n const r = fromValue(v[i]);\n if (r !== null) return r;\n }\n return null;\n }\n if (typeof v === \"object\" && v !== null) {\n const o = v as StreamEvent;\n if (o.type === \"result\" && typeof o.result === \"string\") return o.result;\n }\n return null;\n };\n const direct = fromValue(parse(trimmed));\n if (direct !== null) return direct;\n const lines = trimmed.split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n const r = fromValue(parse(lines[i]));\n if (r !== null) return r;\n }\n return trimmed.slice(0, 200);\n}\n","/**\n * chain.ts — draft-then-implement chains: `--chain draft,implement[,review]`.\n *\n * The chain runs each stage as its own worker (own id, own pane, `parent` set\n * to the chain id), so `ps` shows the chain as a tree and every stage can be\n * followed, replayed and said to like any other worker:\n *\n * - draft turns the operator's brief into a full spec file under\n * <logDir>/specs/<chain id>.md (goal, constraints, files likely\n * touched, acceptance checks, verification commands);\n * - any other stage (implement, plan, …) runs with that spec as its prompt\n * and the original brief attached;\n * - review reads the spec and the working-tree diff and produces the\n * structured report.\n *\n * A stage that fails (or a draft that produces no spec file) stops the chain;\n * the caller then writes the spec itself and re-runs without the draft stage.\n */\n\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, workersLogDir } from \"./paths.js\";\nimport { newWorkerId } from \"./status.js\";\nimport { shortText } from \"./args.js\";\nimport { runWorker, type RunOptions } from \"./run.js\";\n\n/** Where a chain's spec file lives: <logDir>/specs/<chain id>.md. */\nexport function specPathFor(logDir: string, chainId: string): string {\n return join(logDir, \"specs\", `${chainId}.md`);\n}\n\n/**\n * Replace the caller's -p value with `prompt`, dropping every existing\n * -p/--print pair first (two -p flags on one claude command line are an error,\n * so the chain must never leave the brief in place when swapping prompts).\n */\nexport function swapPromptArg(claudeArgs: string[], prompt: string): string[] {\n const out: string[] = [];\n for (let i = 0; i < claudeArgs.length; i++) {\n const a = claudeArgs[i];\n if (a === \"-p\" || a === \"--print\") {\n if (i + 1 < claudeArgs.length && !claudeArgs[i + 1].startsWith(\"-\")) i += 1;\n continue;\n }\n out.push(a);\n }\n out.push(\"-p\", prompt);\n return out;\n}\n\nexport function draftPrompt(brief: string, specPath: string): string {\n return [\n \"You are the DRAFT stage of a worker chain. Turn the operator's brief below into a full implementation spec and write it to\",\n specPath,\n \"with the Write tool.\",\n \"\",\n \"Use exactly these five sections as Markdown headings:\",\n \"# Goal\",\n \"# Constraints\",\n \"# Files likely touched\",\n \"# Acceptance checks\",\n \"# Verification commands\",\n \"\",\n \"Read the repository first (Glob/Grep/Read) so the spec names real files and real commands. Do not implement anything.\",\n \"\",\n \"## Operator brief\",\n \"\",\n brief,\n ].join(\"\\n\");\n}\n\nexport function implementPrompt(brief: string, specPath: string | null, spec: string | null): string {\n const head = spec\n ? [\n \"You are the IMPLEMENT stage of a worker chain. The draft stage wrote the spec below (also at \" +\n specPath +\n \"). Implement it exactly, then run the spec's verification commands before finishing.\",\n \"\",\n \"## Spec\",\n \"\",\n spec,\n ]\n : [\"Implement the operator's brief below.\"];\n return [...head, \"\", \"## Operator brief\", \"\", brief].join(\"\\n\");\n}\n\nexport function reviewPrompt(brief: string, specPath: string | null, spec: string | null): string {\n const specPart = spec\n ? [\n \"\",\n \"## Spec (also at \" + specPath + \")\",\n \"\",\n spec,\n ]\n : [];\n return [\n \"You are the REVIEW stage of a worker chain. The implement stage just ran. Read the repository's diff (run `git diff` and `git status`; use `git diff --stat` for the overview) and check it against the spec's acceptance checks and verification commands — run the checks when they are cheap. Do not fix anything you find; report it.\",\n ...specPart,\n \"\",\n \"## Operator brief\",\n \"\",\n brief,\n ].join(\"\\n\");\n}\n\nexport interface ChainOptions {\n /** Class names, run in order: e.g. [\"draft\", \"implement\", \"review\"]. */\n stages: string[];\n /** Overrides the class of every stage when given (--class with --chain). */\n className?: string;\n providerFlag?: string;\n modelFlag?: string;\n label?: string;\n noPane?: boolean;\n mcpFlag?: string;\n /** The operator's brief — the -p value of the run. */\n brief: string;\n /** The caller's claude args (allowedTools etc.); the -p value is swapped. */\n claudeArgs: string[];\n cwd?: string;\n /** Internal: notified with the chain id once it exists (worker_run uses it). */\n onChainStart?: (chainId: string) => void;\n /** Internal: suppress result printing (the MCP shim's stdout is the RPC channel). */\n quiet?: boolean;\n}\n\nexport interface ChainDeps {\n /** Stage runner; tests inject a mock, production uses runWorker. */\n runStage?: (opts: RunOptions) => Promise<number>;\n /** logDir override for tests; default: the configured workers logDir. */\n logDir?: string;\n}\n\n/** Run a chain of stages; returns the exit code of the first failed stage, 0 when all pass. */\nexport async function runChain(opts: ChainOptions, deps: ChainDeps = {}): Promise<number> {\n const stages = opts.stages.map((s) => s.trim()).filter(Boolean);\n if (!stages.length) throw new Error(\"--chain needs at least one class, e.g. --chain draft,implement\");\n const runStage = deps.runStage ?? runWorker;\n const logDir = deps.logDir ?? workersLogDir(readWorkersSection().workers);\n const chainId = newWorkerId();\n const specPath = specPathFor(logDir, chainId);\n mkdirSync(join(logDir, \"specs\"), { recursive: true }); // the draft stage writes into it\n const baseLabel = opts.label ?? shortText(opts.brief, 40);\n\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN\", {\n chain: chainId,\n stages: stages.join(\",\"),\n label: baseLabel,\n });\n opts.onChainStart?.(chainId);\n\n let spec: string | null = null;\n for (let i = 0; i < stages.length; i++) {\n const stage = stages[i];\n if (stage === \"draft\") {\n spec = null; // a chain may legally start over; the draft rewrites it\n } else if (spec === null && existsSync(specPath)) {\n spec = readFileSync(specPath, \"utf8\");\n }\n const prompt =\n stage === \"draft\"\n ? draftPrompt(opts.brief, specPath)\n : stage === \"review\"\n ? reviewPrompt(opts.brief, spec ? specPath : null, spec)\n : implementPrompt(opts.brief, spec ? specPath : null, spec);\n process.stderr.write(\n `chain ${chainId}: stage ${i + 1}/${stages.length} ${stage} (spec: ${specPath})\\n`\n );\n const rc = await runStage({\n className: opts.className ?? stage,\n providerFlag: opts.providerFlag,\n modelFlag: opts.modelFlag,\n label: `${baseLabel} · ${stage}`,\n noPane: opts.noPane,\n mcpFlag: opts.mcpFlag,\n claudeArgs: swapPromptArg(opts.claudeArgs, prompt),\n cwd: opts.cwd,\n parent: chainId,\n stage,\n quiet: opts.quiet,\n });\n if (stage === \"draft\") {\n if (!existsSync(specPath)) {\n process.stderr.write(\n `chain ${chainId}: draft stage produced no spec at ${specPath} — stopping. ` +\n `Write the spec yourself and re-run without the draft stage.\\n`\n );\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", {\n chain: chainId,\n rc: rc !== 0 ? rc : 1,\n failed: \"draft\",\n });\n return rc !== 0 ? rc : 1;\n }\n spec = readFileSync(specPath, \"utf8\");\n }\n if (rc !== 0) {\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", {\n chain: chainId,\n rc,\n failed: stage,\n });\n return rc;\n }\n }\n appendLedger(ledgerPath(logDir), \"WORKER-CHAIN-END\", { chain: chainId, rc: 0 });\n return 0;\n}\n","/**\n * chatui.ts — the chat line of a `follow` pane.\n *\n * A follow pane with a target behaves like a small chat, Claude Code style:\n * the transcript lives in a terminal scroll region that ends two rows above\n * the pane's bottom; the last two rows are fixed — the prompt row (`› `,\n * readline line editing) and the ticker row. A transcript line is inserted\n * above the fixed rows with a save-cursor / scroll-region / restore-cursor\n * write that never touches them; the scroll region makes the transcript roll\n * inside itself. Everything here builds strings (or parses one line), so the\n * tests assert exact byte sequences — no terminal needed.\n */\n\n/** The prompt marker of the chat row. */\nexport const CHAT_PROMPT = \"› \";\n\n/** Dim hint shown once behind the cursor until the first line is typed. */\nexport const CHAT_HINT = \"type here and press Enter · /help for commands\";\n\n/** What `/help` prints (one command per line, dim). */\nexport const CHAT_HELP = [\n \"/quit close this pane\",\n \"/resume <text> continue the finished worker with <text>\",\n \"/status one-line worker status\",\n \"anything else is sent to the worker — said while it runs, resumed after\",\n];\n\n// ---------------------------------------------------------------------------\n// visible width & wrapping (the gutter must stay the leftmost column)\n// ---------------------------------------------------------------------------\n\n/** Index just past the escape starting at `i` (CSI, OSC or a two-char one). */\nfunction endOfEscape(s: string, i: number): number {\n const n = s[i + 1];\n if (n === \"[\") {\n let j = i + 2;\n while (j < s.length && !(s[j]! >= \"@\" && s[j]! <= \"~\")) j++;\n return Math.min(s.length, j + 1);\n }\n if (n === \"]\") {\n let j = i + 2;\n while (j < s.length && s[j] !== \"\\x07\") j++;\n return Math.min(s.length, j + 1);\n }\n return i + 2;\n}\n\n/** Printable columns of `s` — ANSI escape sequences measure zero. */\nexport function visibleWidth(s: string): number {\n let w = 0;\n let i = 0;\n while (i < s.length) {\n if (s[i] === \"\\x1b\") {\n i = endOfEscape(s, i);\n continue;\n }\n w += 1;\n i += 1;\n }\n return w;\n}\n\n/**\n * The SGR sequences in effect at `upto`: everything opened since the last\n * reset, in order. Anything that is not an SGR escape is ignored (it does\n * not change colour state).\n */\nfunction sgrStateAt(text: string, upto: number): string[] {\n const open: string[] = [];\n let i = 0;\n while (i < Math.min(upto, text.length)) {\n if (text[i] === \"\\x1b\") {\n const end = endOfEscape(text, i);\n const esc = text.slice(i, end);\n if (/^\\x1b\\[[0-9;]*m$/.test(esc)) {\n const params = esc.slice(2, -1);\n const resets = params === \"\" || params.split(\";\").includes(\"0\");\n if (resets) open.length = 0;\n if (!(params === \"\" || params === \"0\")) open.push(esc);\n }\n i = end;\n continue;\n }\n i += 1;\n }\n return open;\n}\n\n/**\n * Wrap one rendered row to `width` printable columns. Breaks on whitespace\n * where possible, hard-wraps words longer than the width, never splits an\n * ANSI escape, and re-opens the colours it wraps inside of, so a diff row\n * keeps its `-`/`+` colour on every continuation row.\n */\nexport function wrapText(text: string, width: number): string[] {\n if (width < 1 || visibleWidth(text) <= width) return [text];\n\n // visible characters by their index in `text`\n const chars: number[] = [];\n for (let i = 0; i < text.length; i++) {\n if (text[i] === \"\\x1b\") {\n i = endOfEscape(text, i) - 1;\n continue;\n }\n chars.push(i);\n }\n\n // words as [first, last] indexes into `chars` (escapes ride along later)\n const words: Array<{ s: number; e: number; w: number }> = [];\n {\n let s = -1;\n let w = 0;\n for (let k = 0; k <= chars.length; k++) {\n const ch = k === chars.length ? \" \" : text[chars[k]!]!;\n if (ch === \" \") {\n if (s >= 0) {\n words.push({ s, e: k - 1, w });\n s = -1;\n w = 0;\n }\n } else {\n if (s < 0) s = k;\n w++;\n }\n }\n }\n // a leading indent (diff rows) belongs to the first word, width counted\n if (words.length && words[0]!.s > 0) {\n words[0] = { s: 0, e: words[0]!.e, w: words[0]!.w + words[0]!.s };\n }\n\n // chunk spans [start, end] over `chars`, greedy on visible width — the\n // width of a chunk is simply its span in `chars`, inner spaces included\n const spans: Array<[number, number]> = [];\n let cs = -1;\n let ce = -1;\n for (const word of words) {\n if (word.w > width) {\n // oversized word: fill the rest of the line, then full-width rows.\n // `take` is the word's chars that still fit — the gap before the word\n // eats into the room, and when it eats all of it the word starts fresh\n const room = cs < 0 ? 0 : width - (ce - cs + 1);\n const take = room > 0 ? ce + room - word.s + 1 : 0;\n if (take > 0) spans.push([cs, ce + room]);\n else if (cs >= 0) spans.push([cs, ce]);\n let pos = word.s + Math.max(0, take);\n let remaining = word.w - Math.max(0, take);\n while (remaining > width) {\n spans.push([pos, pos + width - 1]);\n pos += width;\n remaining -= width;\n }\n cs = pos;\n ce = pos + remaining - 1;\n continue;\n }\n if (cs < 0) {\n cs = word.s;\n ce = word.e;\n continue;\n }\n if (word.e - cs + 1 <= width) {\n ce = word.e;\n continue;\n }\n spans.push([cs, ce]);\n cs = word.s;\n ce = word.e;\n }\n if (cs >= 0) spans.push([cs, ce]);\n\n const out: string[] = [];\n for (const [a, b] of spans) {\n const from = chars[a]!;\n // take trailing escapes up to the next visible char with the chunk\n let end = chars[b]! + 1;\n while (end < text.length && text[end] === \"\\x1b\") end = endOfEscape(text, end);\n let piece = text.slice(from, end);\n const reopen = sgrStateAt(text, from);\n if (reopen.length) piece = reopen.join(\"\") + piece;\n if (sgrStateAt(text, end).length) piece += \"\\x1b[0m\";\n out.push(piece);\n }\n return out.length ? out : [\"\"];\n}\n\n// ---------------------------------------------------------------------------\n// the layout: scroll region + two fixed rows\n// ---------------------------------------------------------------------------\n\n/** Restrict scrolling to the transcript region (rows 1 … rows-2). */\nexport function chatScrollRegion(rows: number): string {\n return `\\x1b[1;${Math.max(1, rows - 2)}r`;\n}\n\n/**\n * Enter the chat layout: clear the pane, set the scroll region, park the\n * cursor at column 1 of the prompt row (rows-1). The ticker owns row `rows`.\n */\nexport function chatEnter(rows: number): string {\n return \"\\x1b[2J\" + chatScrollRegion(rows) + `\\x1b[${Math.max(1, rows - 1)};1H`;\n}\n\n/** Leave it: reset the scroll region, show the cursor, drop to the last row. */\nexport function chatLeave(rows: number): string {\n return \"\\x1b[r\\x1b[?25h\" + `\\x1b[${Math.max(1, rows)};1H`;\n}\n\n/** Redraw the ticker on its own row without moving the user's cursor. */\nexport function chatTickerRow(text: string, rows: number): string {\n return \"\\x1b7\" + `\\x1b[${Math.max(1, rows)};1H\\x1b[K` + text + \"\\x1b8\";\n}\n\n/** Move to column 1 of the prompt row and draw prompt (and hint). */\nexport function chatPromptRow(rows: number, prompt = CHAT_PROMPT, hint?: string): string {\n return `\\x1b[${Math.max(1, rows - 1)};1H` + prompt + (hint ?? \"\");\n}\n\nexport interface ChatInsert {\n seq: string;\n /** rows filled after this one (caps at regionRows, then it always scrolls). */\n fill: number;\n}\n\n/**\n * Insert one transcript row above the fixed prompt/ticker rows. While the\n * region is still filling (`fill < regionRows`) the row is placed top-down;\n * once full, the cursor moves to the region's bottom row and a newline\n * scrolls the region up by one — the two fixed rows are never touched. The\n * user's cursor is saved before and restored after, so readline keeps its\n * position on the prompt row.\n */\nexport function chatInsertLine(line: string, fill: number, regionRows: number): ChatInsert {\n const growing = fill < regionRows;\n const seq =\n \"\\x1b7\" +\n (growing\n ? `\\x1b[${fill + 1};1H${line}\\x1b[K`\n : `\\x1b[${regionRows};1H\\n${line}\\x1b[K`) +\n \"\\x1b8\";\n return { seq, fill: Math.min(fill + 1, regionRows) };\n}\n\n// ---------------------------------------------------------------------------\n// prompt command parsing\n// ---------------------------------------------------------------------------\n\nexport type ChatAction =\n | { kind: \"message\"; text: string }\n | { kind: \"help\" }\n | { kind: \"quit\" }\n | { kind: \"status\" }\n | { kind: \"resume\"; text: string };\n\n/**\n * One submitted prompt line → what to do with it. `/help`, `/quit`,\n * `/status` and `/resume <text>` are commands (a bare `/resume` comes back\n * with empty text so the caller can print its usage); anything else,\n * including any other `/word`, is a message for the worker.\n */\nexport function parseChatLine(raw: string): ChatAction {\n const text = raw.trim();\n if (text === \"/help\") return { kind: \"help\" };\n if (text === \"/quit\") return { kind: \"quit\" };\n if (text === \"/status\") return { kind: \"status\" };\n if (text.startsWith(\"/resume\")) return { kind: \"resume\", text: text.slice(\"/resume\".length).trim() };\n return { kind: \"message\", text };\n}\n\n/**\n * The auto-exit countdown must not fire while the prompt holds unsent text:\n * true while it does. null/undefined (no prompt wired) never holds.\n */\nexport function holdAutoExit(promptText: string | null | undefined): boolean {\n return typeof promptText === \"string\" && promptText.trim() !== \"\";\n}\n","/**\n * render.ts — turn worker events into the lines a human reads.\n *\n * Ports the glm-ps transcript rendering: the gutter rules (dim `>` for reads\n * and searches, `$` for shell, magenta `~` + red/green diff for edits, green\n * `+` for writes), Read-result trimming (first 3 lines then a count), and the\n * result footer. Colors are applied only when the output is a TTY, so MCP\n * tool output stays plain text.\n */\n\nimport { relative, basename } from \"node:path\";\nimport { shortText } from \"./args.js\";\nimport { ageOf, contextPercent, type WorkerStatus, alive } from \"./status.js\";\nimport { sessionTag } from \"./scope.js\";\nimport { parseWorkerReport, renderReport } from \"./report.js\";\n\nexport type ColorEnabled = boolean;\n\nconst CODES = {\n dim: \"2\",\n bold: \"1\",\n red: \"31\",\n green: \"32\",\n yellow: \"33\",\n blue: \"34\",\n mag: \"35\",\n cyan: \"36\",\n} as const;\n\nexport type ColorName = keyof typeof CODES;\n\nexport function makeColor(enabled: ColorEnabled) {\n return (name: ColorName, s: string): string =>\n enabled ? `\\x1b[${CODES[name]}m${s}\\x1b[0m` : s;\n}\n\nexport type Paint = ReturnType<typeof makeColor>;\n\n/** Path relative to cwd when it lies inside it, otherwise unchanged. */\nexport function relPath(path: string, cwd: string): string {\n if (!path || !cwd) return path;\n let r: string;\n try {\n r = relative(cwd, path);\n } catch {\n return path;\n }\n return r.startsWith(\"..\") ? path : r;\n}\n\n/** Minimal line diff for Edit previews: common prefix/suffix, one hunk. */\nexport function unifiedDiffLines(oldStr: string, newStr: string): string[] {\n const oldL = oldStr.split(\"\\n\");\n const newL = newStr.split(\"\\n\");\n let start = 0;\n while (start < oldL.length && start < newL.length && oldL[start] === newL[start]) start++;\n let endOld = oldL.length;\n let endNew = newL.length;\n while (endOld > start && endNew > start && oldL[endOld - 1] === newL[endNew - 1]) {\n endOld--;\n endNew--;\n }\n const out: string[] = [];\n for (let i = start; i < endOld; i++) out.push(\"-\" + oldL[i]);\n for (let i = start; i < endNew; i++) out.push(\"+\" + newL[i]);\n return out;\n}\n\nexport interface ToolUseBlock {\n type?: string;\n text?: string;\n name?: string;\n id?: string;\n input?: unknown;\n /** tool_result blocks only: the call this answers. */\n tool_use_id?: string;\n is_error?: boolean;\n content?: unknown;\n}\n\nexport interface StreamEventLike {\n type?: string;\n subtype?: string;\n model?: string;\n cwd?: string;\n message?: { content?: ToolUseBlock[] };\n result?: string;\n is_error?: boolean;\n num_turns?: number;\n duration_ms?: number;\n /** ISO stamp the runner attaches to every mirrored event (2g). */\n _ts?: string;\n /** Operator text (type: \"operator\"). */\n text?: string;\n}\n\n/**\n * `HH:MM:SS` at an offset east of UTC in minutes (Date.getTimezoneOffset()\n * negated), from any ISO stamp the runner wrote — `Z` or a local `+HH:MM`.\n * null when the stamp cannot be parsed. Offsets make this testable without\n * depending on the machine's zone; the default is this machine's.\n */\nexport function clockOf(ts: string, offMin = -new Date().getTimezoneOffset()): string | null {\n const t = Date.parse(ts);\n if (Number.isNaN(t)) return null;\n return new Date(t + offMin * 60_000).toISOString().slice(11, 19);\n}\n\n/** `YYYY-MM-DD` at the same offset — the local day a date separator shows. */\nexport function dayOf(ts: string, offMin = -new Date().getTimezoneOffset()): string | null {\n const t = Date.parse(ts);\n if (Number.isNaN(t)) return null;\n return new Date(t + offMin * 60_000).toISOString().slice(0, 10);\n}\n\n/** The gutter of one rendered event: its three shapes and its width. */\nexport interface Gutter {\n /** first row: `HH:MM:SS │ ` (dim). */\n first: string;\n /** later rows of one event, no wrapping: blanks of the same width. */\n cont: string;\n /** wrapped continuation rows: blank time, the `│` bar kept (dim). */\n barCont: string;\n /** printable columns first/cont/barCont occupy. */\n width: number;\n}\n\n/**\n * The transcript gutter (2g): `HH:MM:SS │ ` from the event's `_ts`, dim, with\n * the worker tag in front when several run at once. Continuation lines get\n * blanks of the same width so wrapped text stays aligned; when the viewer\n * wraps lines itself, continuation rows carry the `│` bar instead (barCont)\n * so the bar runs unbroken down the pane. null when the event carries no\n * stamp (logs from before 2g render with the plain prefix). The time is the\n * stamp's wall clock at `offMin` — the default renders local time, whatever\n * zone stamped the log (old logs were stamped in UTC).\n */\nexport function gutterFor(\n c: Paint,\n e: { _ts?: string },\n tag?: string,\n offMin?: number\n): Gutter | null {\n if (!e._ts) return null;\n const time = clockOf(e._ts, offMin) ?? (e._ts.length >= 19 ? e._ts.slice(11, 19) : e._ts);\n const head = tag ? `${tag} ${time}` : time;\n const width = head.length + 3; // + \" │ \"\n return {\n first: c(\"dim\", `${head} │ `),\n cont: \" \".repeat(width),\n barCont: c(\"dim\", `${\" \".repeat(head.length)} │ `),\n width,\n };\n}\n\n/**\n * The ticker's tool part: `$ <command>` for Bash (first 60 chars), the file\n * basename for the file tools, the bare name for everything else.\n */\nexport function tickerTool(name: string, inp: unknown): string {\n const i = (typeof inp === \"object\" && inp !== null ? inp : {}) as Record<string, unknown>;\n const get = (k: string) => (typeof i[k] === \"string\" ? (i[k] as string) : \"\");\n if (name === \"Bash\") return `$ ${shortText(get(\"command\").replace(/\\s+/g, \" \").trim(), 60)}`;\n if (name === \"Read\" || name === \"Edit\" || name === \"Write\" || name === \"MultiEdit\") {\n const base = get(\"file_path\").split(\"/\").pop() ?? \"\";\n return base || name;\n }\n return name;\n}\n\n/**\n * The liveness line: `⋯ 12s · run tests before the fix · $ bun run test` —\n * seconds since the last *rendered* event, the worker's last stated intent\n * (its last assistant text, ≤60 chars) and the tool it is currently running.\n * Parts that are empty drop out.\n */\nexport function tickerText(secs: number, intent: string, tool: string, meter?: string | null): string {\n const head = `⋯ ${secs}s`;\n const parts = [intent, tool].map((p) => p.trim()).filter(Boolean);\n const tail = parts.join(\" · \");\n const line = tail ? `${head} · ${tail}` : head;\n return meter ? `${line} · ${meter}` : line;\n}\n\n/**\n * One blank line between turns, none inside one: a blank goes before an\n * assistant message that follows a tool result or an operator message (the\n * worker starting to speak again after its tools were answered / it was told\n * something), not between the text, tool calls and results of one turn.\n */\nexport function blankBetween(prev: { type?: string } | null, e: { type?: string }): boolean {\n if (!prev) return false;\n if (e.type !== \"assistant\") return false;\n return prev.type === \"user\" || prev.type === \"operator\";\n}\n\n/** The worker's last stated intent: the first line of its last text, ≤60. */\nexport function intentOf(text: string): string {\n const first = text.trim().split(\"\\n\").find((l) => l.trim()) ?? \"\";\n return shortText(first.trim().replace(/\\s+/g, \" \"), 60);\n}\n\n/** Compact token count: 84k, 200k, 900. */\nfunction fmtK(n: number): string {\n return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);\n}\n\n/**\n * The context meter `ctx 84k/200k (42%)`, yellow from 70 %, red from 85 %.\n * null when the numbers are missing or below `minPct` (the table only shows\n * it past 60; the pane liveness line always shows it).\n */\nexport function contextMeter(\n c: Paint,\n s: Pick<WorkerStatus, \"contextTokens\" | \"contextWindow\">,\n minPct = 0\n): string | null {\n const pct = contextPercent(s);\n if (pct === null || pct <= minPct) return null;\n const label = `ctx ${fmtK(s.contextTokens ?? 0)}/${fmtK(s.contextWindow ?? 0)} (${pct}%)`;\n if (pct > 85) return c(\"red\", label);\n if (pct > 70) return c(\"yellow\", label);\n return label;\n}\n\nfunction renderToolUse(\n c: Paint,\n prefix: string,\n name: string,\n inp: unknown,\n cwd: string\n): string[] {\n const i = (typeof inp === \"object\" && inp !== null ? inp : {}) as Record<string, unknown>;\n const get = (k: string) => (typeof i[k] === \"string\" ? (i[k] as string) : \"\");\n if (name === \"Read\") {\n return [`${prefix}${c(\"dim\", \">\")} Reading ${relPath(get(\"file_path\"), cwd)}`];\n }\n if (name === \"Grep\" || name === \"Glob\") {\n return [\n `${prefix}${c(\"dim\", \">\")} Searching ${c(\"cyan\", get(\"pattern\"))} in ${relPath(get(\"path\") || \".\", cwd)}`,\n ];\n }\n if (name === \"Bash\") {\n return [`${prefix}${c(\"dim\", \"$\")} ${shortText(get(\"command\"), 160)}`];\n }\n if (name === \"Edit\") {\n const out = [`${prefix}${c(\"mag\", \"~\")} Editing ${relPath(get(\"file_path\"), cwd)}`];\n for (const line of unifiedDiffLines(get(\"old_string\"), get(\"new_string\"))) {\n if (line.startsWith(\"-\")) out.push(`${prefix} ${c(\"red\", line)}`);\n else if (line.startsWith(\"+\")) out.push(`${prefix} ${c(\"green\", line)}`);\n else out.push(`${prefix} ${c(\"dim\", line)}`);\n }\n return out;\n }\n if (name === \"Write\") {\n const n = get(\"content\").split(\"\\n\").length;\n return [`${prefix}${c(\"green\", \"+\")} Writing ${relPath(get(\"file_path\"), cwd)} (${n} lines)`];\n }\n if (name === \"WebSearch\" || name === \"WebFetch\") {\n return [`${prefix}${c(\"dim\", \">\")} ${name} ${shortText(get(\"query\") || get(\"url\"), 100)}`];\n }\n return [`${prefix}${c(\"dim\", \">\")} ${name} ${shortText(JSON.stringify(i), 120)}`];\n}\n\n/** Render one stream-json event; `tools` maps tool_use_id → tool name. */\nexport function renderEvent(\n c: Paint,\n prefix: string,\n e: StreamEventLike,\n cwd: string,\n tools: Record<string, string>,\n gutter?: { first: string; cont: string } | null\n): string[] {\n const out: string[] = [];\n if (e.type === \"system\" && e.subtype === \"init\") {\n out.push(\n `${prefix}${c(\"dim\", `worker started · model ${e.model ?? \"?\"} · cwd ${basename(e.cwd || cwd || \"\")}`)}`\n );\n } else if (e.type === \"operator\") {\n // split per line so the gutter continuation pads wrapped text\n for (const ln of String(e.text ?? \"\").split(\"\\n\")) {\n out.push(`${prefix}${c(\"cyan\", \"» \" + ln)}`);\n }\n } else if (e.type === \"assistant\") {\n for (const b of e.message?.content ?? []) {\n if (b.type === \"text\" && (b.text ?? \"\").trim()) {\n for (const ln of (b.text ?? \"\").trim().split(\"\\n\")) out.push(`${prefix}${ln}`);\n } else if (b.type === \"tool_use\") {\n out.push(...renderToolUse(c, prefix, b.name ?? \"?\", b.input, cwd));\n }\n }\n } else if (e.type === \"user\") {\n for (const b of e.message?.content ?? []) {\n if (b.type !== \"tool_result\") continue;\n let content: unknown = (b as { content?: unknown }).content ?? \"\";\n if (Array.isArray(content)) {\n content = content\n .map((x) => (typeof x === \"object\" && x !== null && \"text\" in x ? String((x as { text?: string }).text ?? \"\") : \"\"))\n .join(\"\\n\");\n }\n const text = String(content);\n const isError = (b as { is_error?: boolean }).is_error === true;\n if (!isError && tools[(b as { tool_use_id?: string }).tool_use_id ?? \"\"] === \"Read\") {\n // keep Read results as the tool returned them (`<lineno>\\t<code>`),\n // only their count is summarised\n const lines = text.split(\"\\n\");\n for (const ln of lines.slice(0, 3)) out.push(`${prefix}${c(\"dim\", ln)}`);\n if (lines.length > 3) {\n out.push(`${prefix}${c(\"dim\", ` … ${lines.length} lines`)}`);\n }\n } else if (isError) {\n out.push(`${prefix} ${c(\"red\", \"! \" + shortText(text, 200))}`);\n } else if (text.trim()) {\n out.push(`${prefix} ${c(\"dim\", shortText(text, 120))}`);\n }\n }\n } else if (e.type === \"result\") {\n const ok = !e.is_error;\n const mark = ok ? c(\"green\", \"✓ done\") : c(\"red\", \"✗ failed\");\n out.push(`${prefix}${mark} · ${e.num_turns ?? \"?\"} turns · ${Math.floor((e.duration_ms ?? 0) / 1000)}s`);\n // a contract-compliant final message renders as the compact report block\n const report = parseWorkerReport(String(e.result ?? \"\"));\n if (report) {\n out.push(...renderReport(c, prefix, report, cwd));\n } else {\n for (const ln of String(e.result ?? \"\").trim().split(\"\\n\")) {\n out.push(`${prefix} ${ln}`);\n }\n }\n }\n if (!gutter) return out;\n return out.map((ln, i) => (i === 0 ? gutter.first : gutter.cont) + ln);\n}\n\n/** Transcript header: id, provider, label, session name, project dir. */\nexport function headerLine(\n c: Paint,\n s: { id: string; label: string; cwd: string; provider?: string; session?: { name?: string } | null }\n): string {\n const bits = [s.provider ? `[${s.provider}]` : \"\", sessionTag(s)].filter(Boolean).join(\" \");\n const sep = bits ? ` ${bits}` : \"\";\n return c(\"bold\", `━━ ${s.id}${sep} ${s.label} (${basename(s.cwd)})`);\n}\n\n/** The chain label behind a stage label: strip the trailing \" · <stage>\". */\nfunction chainLabelOf(stages: WorkerStatus[]): string {\n const first = stages[0];\n if (!first) return \"\";\n const suffix = first.stage ? ` · ${first.stage}` : \"\";\n return first.label.endsWith(suffix) && suffix\n ? first.label.slice(0, first.label.length - suffix.length)\n : first.label;\n}\n\n/**\n * The ps table (RUNNING + FINISHED last 8). Chain stages carry `parent` and\n * render as a tree under one `chain <id>` header; plain workers render as\n * before.\n */\nexport function renderTable(\n c: Paint,\n statuses: WorkerStatus[],\n scopeLabel: string,\n now: Date = new Date()\n): string {\n const running: WorkerStatus[] = [];\n const done: WorkerStatus[] = [];\n for (const s of statuses) {\n if (s.state === \"running\" && alive(s.pid)) running.push(s);\n else {\n if (s.state === \"running\") s.state = \"lost\";\n done.push(s);\n }\n }\n // group stages by their chain id, keeping first-seen order\n const group = (list: WorkerStatus[]): { s: WorkerStatus; chain: string | null }[] => {\n const out: { s: WorkerStatus; chain: string | null }[] = [];\n for (const s of list) {\n out.push({ s, chain: s.parent ?? null });\n }\n return out;\n };\n const treeLine = (line: string, chain: string | null, last: boolean): string => {\n if (chain === null) return line;\n const mark = last ? \"└\" : \"├\";\n const bar = last ? \" \" : \"│\";\n return line.startsWith(\" \")\n ? ` ${bar} ${line.slice(6)}`\n : ` ${mark} ${line.slice(2)}`;\n };\n\n const clock = `${String(now.getHours()).padStart(2, \"0\")}:${String(now.getMinutes()).padStart(2, \"0\")}:${String(now.getSeconds()).padStart(2, \"0\")}`;\n const lines: string[] = [c(\"bold\", `Workers ${clock}`), \"\"];\n lines.push(c(\"bold\", `RUNNING (${running.length})`));\n if (!running.length) lines.push(\" none\");\n const runEntries = group(running);\n for (let i = 0; i < runEntries.length; i++) {\n const { s, chain } = runEntries[i];\n if (chain) {\n const prev = runEntries[i - 1];\n if (!prev || prev.chain !== chain) {\n const stages = runEntries.filter((e) => e.chain === chain).map((e) => e.s);\n lines.push(` ${c(\"bold\", `chain ${chain}`)} ${chainLabelOf(stages)}`);\n }\n }\n const last = !chain || !runEntries[i + 1] || runEntries[i + 1].chain !== chain;\n const meter = contextMeter(c, s, 60);\n lines.push(\n treeLine(\n ` ${c(\"cyan\", s.id)} [${s.provider}] ${ageOf(s.started, now).padStart(4)} old turns ${String(s.turns).padStart(2)} tools ${String(s.tools).padStart(2)} ${basename(s.cwd)}${meter ? \" \" + meter : \"\"}`,\n chain,\n last\n )\n );\n lines.push(treeLine(` task: ${s.label}`, chain, last));\n lines.push(\n treeLine(` now: ${c(\"yellow\", s.last)} (${ageOf(s.updated, now)} ago)`, chain, last)\n );\n }\n lines.push(\"\");\n lines.push(c(\"bold\", \"FINISHED (last 8)\"));\n const doneEntries = group(done.slice(-8));\n for (let i = 0; i < doneEntries.length; i++) {\n const { s, chain } = doneEntries[i];\n if (chain) {\n const prev = doneEntries[i - 1];\n if (!prev || prev.chain !== chain) {\n const stages = doneEntries.filter((e) => e.chain === chain).map((e) => e.s);\n lines.push(` ${c(\"bold\", `chain ${chain}`)} ${chainLabelOf(stages)}`);\n }\n }\n const last = !chain || !doneEntries[i + 1] || doneEntries[i + 1].chain !== chain;\n const col = s.state === \"done\" ? \"green\" : \"red\";\n const tag = sessionTag(s);\n lines.push(\n treeLine(\n ` ${s.id} [${s.provider}]${tag ? \" \" + tag : \"\"} ${c(col, s.state.padEnd(6))} rc=${s.rc} ${String(s.secs ?? \"?\").padStart(4)}s turns ${String(s.turns).padStart(2)} tools ${String(s.tools).padStart(2)} ${basename(s.cwd)} ${s.label}`,\n chain,\n last\n )\n );\n }\n lines.push(\"\");\n lines.push(c(\"dim\", \"worker follow live transcript of running workers\"));\n lines.push(c(\"dim\", \"worker <id> replay one worker\"));\n lines.push(c(\"dim\", scopeLabel));\n return lines.join(\"\\n\");\n}\n\n/** One-line status-bar summary (same shape glm-ps --status printed). */\nexport function renderStatusLine(\n mine: WorkerStatus[],\n now: Date = new Date(),\n c: Paint = makeColor(true)\n): string {\n if (!mine.length) return \"\";\n const running = mine.filter((s) => s.state === \"running\" && alive(s.pid));\n const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, \"0\")}-${String(now.getDate()).padStart(2, \"0\")}`;\n const doneToday = mine.filter((s) => s.state !== \"running\" && s.started.startsWith(today));\n const ok = doneToday.filter((s) => s.state === \"done\").length;\n const bad = doneToday.length - ok;\n const providers = new Set(running.map((s) => s.provider));\n const providerTag = providers.size === 1 ? [...providers][0] : \"workers\";\n let head = `${providerTag} ▶${running.length}`;\n const parts = running.slice(0, 3).map((s) => {\n // context load joins the summary once it passes 60 % (yellow >70, red >85)\n const meter = contextMeter(c, s, 60);\n return (\n `${s.id.slice(-4)} ${s.label.slice(0, 26)} ${ageOf(s.started, now)} · ${s.last.slice(0, 30)}` +\n (meter ? ` ${meter}` : \"\")\n );\n });\n if (parts.length) head += \" \" + parts.join(\" | \");\n if (doneToday.length) head += ` ✓${ok} ✗${bad} today`;\n return head;\n}\n","/**\n * viewer.ts — ps / follow / replay / status line over the worker logDir.\n *\n * Scoping: workers launched from this terminal's AIBroker session (or, as the\n * fallback, its iTerm tab) unless --all or an explicit worker id is given.\n * Outside iTerm, everything degrades to \"all workers\" — the Python behaviour.\n *\n * Transcripts render with a `HH:MM:SS │ ` gutter (2g): dim, taken from the\n * `_ts` stamp on every mirrored event (local wall clock — stamps carry a local\n * offset and old UTC stamps are converted), the worker tag in front when\n * several run at once, a date separator when the day changes, and — on a TTY —\n * a liveness line (`⋯ 12s · run tests before the fix · $ bun run test`) that\n * is rewritten in place between events. Attaching to a worker that is already\n * running first replays its last events (backfill), then continues live.\n *\n * Following one worker turns the pane into a small chat (see chatui.ts): the\n * transcript scrolls in a region that ends two rows above the bottom, the\n * prompt row (`› `, readline editing) and the ticker row stay fixed, and every\n * submitted line is said to the worker while it runs and resumes it (same\n * Claude session) once it has finished. Lines the pane wraps itself keep the\n * `│` bar on continuation rows, so no content ever lands left of the bar.\n * Non-TTY output keeps the plain scrolling behaviour.\n */\n\nimport { existsSync, openSync, readSync, closeSync, readFileSync } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport { spawn, type SpawnOptions } from \"node:child_process\";\nimport { eventsPath } from \"./paths.js\";\nimport { alive, loadStatuses, type WorkerStatus } from \"./status.js\";\nimport { currentTabKey, resolveSession, workerInScope } from \"./scope.js\";\nimport { sayToWorker } from \"./operator.js\";\nimport {\n CHAT_HELP,\n CHAT_HINT,\n CHAT_PROMPT,\n chatEnter,\n chatInsertLine,\n chatLeave,\n chatPromptRow,\n chatScrollRegion,\n chatTickerRow,\n holdAutoExit,\n parseChatLine,\n wrapText,\n} from \"./chatui.js\";\nimport {\n blankBetween,\n contextMeter,\n dayOf,\n gutterFor,\n headerLine,\n intentOf,\n makeColor,\n renderEvent,\n renderStatusLine,\n renderTable,\n tickerText,\n tickerTool,\n type Gutter,\n type Paint,\n type StreamEventLike,\n} from \"./render.js\";\n\n/** How many existing events a fresh follow pane replays before going live. */\nexport const BACKFILL_EVENTS = 200;\n\n// ---------------------------------------------------------------------------\n// ps\n// ---------------------------------------------------------------------------\n\nexport function psOutput(\n logDir: string,\n showAll: boolean,\n env: NodeJS.ProcessEnv = process.env,\n color = process.stdout.isTTY === true\n): string {\n const c = makeColor(color);\n const term = env.ITERM_SESSION_ID ?? \"\";\n const statuses = loadStatuses(logDir);\n const scoped = showAll || !term ? statuses : statuses.filter((s) => workerInScope(s, term));\n const scopeLabel =\n showAll || !term\n ? \"scope: all workers\"\n : resolveSession(term)\n ? `scope: session ${resolveSession(term)!.name}`\n : `scope: tab ${currentTabKey(env)} (this iTerm tab)`;\n return renderTable(c, scoped, scopeLabel);\n}\n\n// ---------------------------------------------------------------------------\n// one event → rendered lines (shared by replay, backfill and the live tail)\n// ---------------------------------------------------------------------------\n\n/** What applyEvent() carries between the events of one transcript. */\nexport interface FollowState {\n /** day separator already printed (\"\" before the first stamped event). */\n lastDay: string;\n /** tool_use id → tool name (Edit previews and Read trimming need it). */\n tools: Record<string, string>;\n /** the worker's last stated intent — its last assistant text, ≤60 chars. */\n intent: string;\n /** the ticker's tool part of the last tool_use (\"$ bun run test\"). */\n tool: string;\n /** the last event seen, for the blank line between turns. */\n prev: StreamEventLike | null;\n}\n\nexport function initialFollowState(intent = \"waiting for first event\"): FollowState {\n return { lastDay: \"\", tools: {}, intent, tool: \"\", prev: null };\n}\n\n/** One applied event: what to print, and how the ticker changes. */\nexport interface FollowStep {\n /** rendered lines (\"\" among them marks the blank line between turns). */\n lines: string[];\n /** day separator to print first, when the stamp's local day changed. */\n day: string | null;\n /** the event produced visible output — the ticker clock restarts. */\n activity: boolean;\n state: FollowState;\n}\n\n/**\n * Gutter the rendered body of one event, wrapping when the pane width is\n * known. Unwrapped (null `wrapWidth`, e.g. piped output): the first row gets\n * the stamped gutter, later rows of the event blanks — exactly the pre-chat\n * rendering. Wrapped: every row is folded at `wrapWidth` columns and each\n * continuation row carries the blank gutter with the `│` bar, so the bar runs\n * unbroken down the pane and no content ever lands left of it.\n */\nexport function gutterBody(\n body: string[],\n gutter: Gutter | null,\n wrapWidth: number | null\n): string[] {\n if (!gutter) return body;\n if (wrapWidth === null || wrapWidth <= gutter.width) {\n return body.map((ln, i) => (i === 0 ? gutter.first : gutter.cont) + ln);\n }\n const out: string[] = [];\n for (const ln of body) {\n for (const piece of wrapText(ln, wrapWidth - gutter.width)) {\n out.push((out.length === 0 ? gutter.first : gutter.barCont) + piece);\n }\n }\n return out;\n}\n\n/**\n * Render one event and advance the follow state. Everything the viewer shows\n * between events of one worker comes from here: replay, the backfill on\n * attach and the live tail all use it, so they space identically — events\n * back to back, one blank line between turns. `activity` is false for events\n * that render nothing (stream noise, empty tool results): they leave the\n * ticker's \"since last event\" clock running. `wrapWidth` (the pane's column\n * count, re-read on resize) makes the viewer wrap rows itself; null keeps\n * the terminal's own wrapping.\n */\nexport function applyEvent(\n c: Paint,\n s: FollowState,\n e: StreamEventLike,\n cwd: string,\n tag?: string,\n offMin?: number,\n wrapWidth?: number | null\n): FollowStep {\n const tools = { ...s.tools };\n if (e.type === \"assistant\") {\n for (const b of e.message?.content ?? []) {\n if (b.type === \"tool_use\" && b.id) tools[b.id] = b.name ?? \"?\";\n }\n }\n const day = typeof e._ts === \"string\" ? (dayOf(e._ts, offMin) ?? rawDay(e._ts)) : \"\";\n const state: FollowState = {\n lastDay: day && day !== s.lastDay ? day : s.lastDay,\n tools,\n intent: s.intent,\n tool: s.tool,\n prev: e,\n };\n if (e.type === \"assistant\") {\n for (const b of e.message?.content ?? []) {\n if (b.type === \"text\" && (b.text ?? \"\").trim()) state.intent = intentOf(b.text ?? \"\");\n else if (b.type === \"tool_use\") state.tool = tickerTool(b.name ?? \"?\", b.input);\n }\n }\n const gutter = gutterFor(c, e, tag, offMin);\n const prefix = \"\";\n const body = gutterBody(renderEvent(c, prefix, e, cwd, tools), gutter, wrapWidth ?? null);\n const lines = blankBetween(s.prev, e) ? [\"\", ...body] : body;\n return {\n lines,\n day: day && day !== s.lastDay ? day : null,\n activity: lines.some((ln) => ln !== \"\"),\n state,\n };\n}\n\n/** `YYYY-MM-DD` straight out of an unparsable stamp, \"\" when it has none. */\nfunction rawDay(ts: string): string {\n return /^\\d{4}-\\d{2}-\\d{2}/.test(ts) ? ts.slice(0, 10) : \"\";\n}\n\n/**\n * The events a fresh follow replays before going live: the last `cap`\n * non-empty log lines, oldest first.\n */\nexport function backfillLines(raw: string, cap = BACKFILL_EVENTS): string[] {\n return raw.split(\"\\n\").filter((l) => l.trim()).slice(-cap);\n}\n\n// ---------------------------------------------------------------------------\n// replay\n// ---------------------------------------------------------------------------\n\n/** The rendered transcript of one worker, from the start, as one string. */\nexport function replayOutput(\n logDir: string,\n wid: string,\n color = process.stdout.isTTY === true,\n tailLines?: number\n): string {\n const path = eventsPath(logDir, wid);\n if (!existsSync(path)) {\n throw new Error(`no event log for ${wid}`);\n }\n const c = makeColor(color);\n const st =\n loadStatuses(logDir).find((s) => s.id === wid) ??\n ({ id: wid, label: \"\", cwd: \"\", provider: \"\" } as WorkerStatus);\n const wrapWidth = typeof process.stdout.columns === \"number\" ? process.stdout.columns : null;\n const out: string[] = [headerLine(c, st)];\n const raw = readFileSync(path, \"utf8\");\n const lines = tailLines !== undefined ? raw.split(\"\\n\").slice(-tailLines) : raw.split(\"\\n\");\n let state = initialFollowState(\"\");\n for (const line of lines) {\n if (!line.trim()) continue;\n let e: StreamEventLike;\n try {\n e = JSON.parse(line) as StreamEventLike;\n } catch {\n continue;\n }\n const step = applyEvent(c, state, e, st.cwd ?? \"\", undefined, undefined, wrapWidth);\n if (step.day) out.push(c(\"dim\", `── ${step.day} ──`));\n out.push(...step.lines);\n state = step.state;\n }\n return out.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// follow\n// ---------------------------------------------------------------------------\n\ninterface FollowHandle {\n fd: number;\n buf: string;\n}\n\n/**\n * The follow exit decision for one worker: it is over once its result event\n * was rendered, or once its status left \"running\" while its pid is gone — a\n * worker killed without writing a result still ends. A status never read\n * (undefined) means \"not over\": follow keeps waiting for the first event.\n */\nexport function workerEnded(\n resultRendered: boolean,\n state: WorkerStatus[\"state\"] | undefined,\n pidAlive: boolean\n): boolean {\n return resultRendered || (state !== undefined && state !== \"running\" && !pidAlive);\n}\n\n// ---------------------------------------------------------------------------\n// operator input (2i): this pane's stdin drives say / resume\n// ---------------------------------------------------------------------------\n\n/** What makeOperatorInput() needs from the follow around it. */\nexport interface OperatorInputDeps {\n /** the worker typed lines go to right now (null: none, lines are ignored). */\n target: () => string | null;\n /** forwards one message to a running worker. */\n say: (id: string, text: string) => Promise<string>;\n /** whether a status file exists for the id (say failed → resume, or note). */\n workerKnown: (id: string) => boolean;\n /** continues a finished worker (same Claude session). */\n resume: (text: string, id: string) => void;\n /** one line of feedback in the pane. */\n note: (s: string) => void;\n paint: Paint;\n /** chat mode only: the echoed line replaces the \"» sent\" note. */\n sent?: (id: string) => void;\n}\n\n/**\n * One typed stdin line → say (worker running) or resume (worker finished):\n * the operator channel of a `follow <id>` pane. Trimmed; empty lines and a\n * missing target are ignored.\n */\nexport function makeOperatorInput(d: OperatorInputDeps): (raw: string) => void {\n return (raw: string) => {\n const text = raw.trim();\n if (!text) return;\n const id = d.target();\n if (id === null) return;\n d.say(id, text).then(\n () => (d.sent ? d.sent(id) : d.note(d.paint(\"dim\", `» sent to ${id}`))),\n (e: Error) => {\n if (d.workerKnown(id)) d.resume(text, id);\n else d.note(d.paint(\"red\", `» ${e.message}`));\n }\n );\n };\n}\n\n/** What followWorkers writes to (process.stdout, or a fake in tests). */\nexport interface FollowStream {\n write(s: string): boolean;\n isTTY?: boolean;\n columns?: number;\n rows?: number;\n on?(event: \"resize\", fn: () => void): unknown;\n removeListener?(event: \"resize\", fn: () => void): unknown;\n}\n\n/** The child of a `pai worker resume` spawn (tests inject a fake). */\nexport interface ResumeChild {\n stdout?: { on(event: \"data\", cb: (chunk: Buffer) => void): unknown };\n on(event: \"close\", cb: (code: number | null) => void): unknown;\n}\n\n/** Test seams for followWorkers: the streams, the resume spawn, the prompt. */\nexport interface FollowIO {\n stdin?: NodeJS.ReadableStream;\n stdout?: FollowStream;\n /** replaces the `pai worker resume` spawn (tests record instead of run). */\n spawnResume?: (id: string, text: string) => ResumeChild;\n /** current unsent prompt text (tests force a draft to hold the countdown). */\n promptLine?: () => string;\n}\n\n/**\n * Tail one worker (target) or the running workers of this scope, live.\n * Workers whose event log already exists are first replayed (last\n * BACKFILL_EVENTS events), then tailed. Auto-exit: with a target, wait\n * `autoExit` seconds after its end; without one, exit once no worker in\n * scope has run for that many seconds in a row, never within the first 30 s.\n *\n * With a target on a TTY the pane becomes a chat (chatui.ts): transcript in\n * a scroll region, fixed prompt and ticker rows, submitted lines said to the\n * worker (or resuming it after it finished) and echoed as `»` rows. A draft\n * in the prompt holds the auto-exit countdown. Non-TTY output keeps the\n * plain scrolling behaviour; FORCE_TTY=1 emits the chat layout over a pipe.\n */\nexport async function followWorkers(\n logDir: string,\n target: string | null,\n showAll: boolean,\n autoExit: number,\n env: NodeJS.ProcessEnv = process.env,\n color = process.stdout.isTTY === true,\n io?: FollowIO\n): Promise<void> {\n const c = makeColor(color);\n const out_ = io?.stdout ?? process.stdout;\n const in_ = io?.stdin ?? process.stdin;\n // FORCE_TTY=1: the TTY layout over a pipe (tests, recorded panes)\n const tty = out_.isTTY === true || env.FORCE_TTY === \"1\";\n const term = env.ITERM_SESSION_ID ?? \"\";\n const scopeTab = target || showAll ? \"\" : currentTabKey(env);\n const handles = new Map<string, FollowHandle>();\n const seenHeader = new Set<string>();\n const finished = new Set<string>();\n const states = new Map<string, FollowState>();\n const started = Date.now();\n let idleSince: number | null = null;\n let aborted = false;\n // liveness state: rewritten in place between events, TTY only\n let lastEventAt = Date.now();\n let meterStatus: WorkerStatus | null = null;\n const onInt = () => {\n aborted = true;\n };\n process.once(\"SIGINT\", onInt);\n\n // --- the chat layout (target + TTY): transcript region + two fixed rows\n const chat = tty && target !== null;\n let rows = out_.rows ?? 24;\n const columns = (): number | null => (typeof out_.columns === \"number\" ? out_.columns : null);\n let fill = 0; // transcript rows filled since the region was (re)set\n const regionRows = () => Math.max(1, rows - 2);\n /** Every line the pane shows goes through here: plain newline, or a row\n * inserted above the fixed prompt/ticker rows (chatui.chatInsertLine). */\n const out = (line: string) => {\n if (!chat) {\n out_.write(line + \"\\n\");\n return;\n }\n const r = chatInsertLine(line, fill, regionRows());\n out_.write(r.seq);\n fill = r.fill;\n };\n\n // The ticker redraws its line in place: in the chat layout that is its own\n // bottom row (save-cursor, draw, restore-cursor); the plain mode writes\n // CR + erase-to-end-of-line, never a newline. The control bytes live in\n // their own string literals — bundling them onto a template literal makes\n // the bundler fold them in as raw chars, and a raw CR inside a template\n // literal is normalised to LF by the language, which is how the ticker\n // once scrolled a blank line per tick.\n const eraseLiveness = () => {\n if (chat) return;\n if (tty) out_.write(\"\\r\\x1b[K\");\n };\n let ticker = initialFollowState();\n const writeLiveness = () => {\n if (!tty) return;\n const secs = Math.max(0, Math.floor((Date.now() - lastEventAt) / 1000));\n const meter = meterStatus ? contextMeter(c, meterStatus) : null;\n const text = tickerText(secs, ticker.intent, ticker.tool, meter);\n if (chat) out_.write(chatTickerRow(text, rows));\n else {\n out_.write(\"\\r\\x1b[K\");\n out_.write(text);\n }\n };\n\n const runningIds = (): string[] =>\n loadStatuses(logDir)\n .filter(\n (s) =>\n s.state === \"running\" &&\n alive(s.pid) &&\n (target !== null || showAll || (scopeTab ? workerInScope(s, term) : true))\n )\n .map((s) => s.id);\n\n // --- one rendered event, shared by the backfill and the live tail\n const emitEvent = (e: StreamEventLike, wid: string, st: WorkerStatus, multi: boolean) => {\n if (e.type === \"operator\" && chat && suppressMirror(String(e.text ?? \"\"))) return;\n if (!seenHeader.has(wid)) {\n eraseLiveness();\n out(headerLine(c, st));\n seenHeader.add(wid);\n }\n const state = states.get(wid) ?? initialFollowState();\n const step = applyEvent(\n c,\n state,\n e,\n st.cwd ?? \"\",\n multi ? c(\"cyan\", wid.slice(-4)) : undefined,\n undefined,\n columns()\n );\n if (step.day) {\n out(c(\"dim\", `── ${step.day} ──`));\n }\n states.set(wid, step.state);\n if (wid === target) ticker = step.state;\n eraseLiveness();\n for (const ln of step.lines) {\n out(ln);\n }\n if (step.activity) lastEventAt = Date.now();\n if (e.type === \"result\") {\n meterStatus = st;\n finished.add(wid);\n } else if (wid === target || target === null) {\n meterStatus = st;\n }\n };\n\n // --- attach: replay what is already in the log, then tail from its end\n const attachHandle = (wid: string, path: string, st: WorkerStatus, multi: boolean): FollowHandle => {\n const handle = { fd: openSync(path, \"r\"), buf: \"\" };\n try {\n const existing = readFileSync(path, \"utf8\");\n if (existing.trim()) {\n for (const line of backfillLines(existing)) {\n let e: StreamEventLike;\n try {\n e = JSON.parse(line) as StreamEventLike;\n } catch {\n continue;\n }\n emitEvent(e, wid, st, multi);\n }\n } else {\n // no event yet: show the header now, not at the first event\n if (!seenHeader.has(wid)) {\n out(headerLine(c, st));\n seenHeader.add(wid);\n }\n }\n // the backfill already rendered the file; the live tail starts at EOF\n const sink = Buffer.alloc(65536);\n for (;;) {\n let n: number;\n try {\n n = readSync(handle.fd, sink, 0, sink.length, null);\n } catch {\n break;\n }\n if (n <= 0) break;\n }\n } catch {\n // unreadable log: tail from wherever the fd happens to be\n }\n return handle;\n };\n\n // --- say / resume from this pane's stdin\n const noteLine = (s: string) => {\n eraseLiveness();\n out(s);\n };\n // SpawnOptions (not the stdio-tuple overload): we only read stdout and\n // want the plain ChildProcess shape; tests inject a fake that records\n const spawnResume =\n io?.spawnResume ??\n ((id: string, text: string): ResumeChild =>\n spawn(\"pai\", [\"worker\", \"resume\", id, text, \"--print-id\", \"--no-pane\"], {\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n } as SpawnOptions) as unknown as ResumeChild);\n const resumeTarget = (text: string, id: string) => {\n noteLine(c(\"dim\", `» resuming ${id} …`));\n const child = spawnResume(id, text);\n let idOut = \"\";\n child.stdout?.on(\"data\", (chunk: Buffer) => {\n idOut += chunk.toString(\"utf8\");\n });\n child.on(\"close\", (rc: number | null) => {\n const newId = idOut.trim().split(\"\\n\").pop() ?? \"\";\n if (rc === 0 && /^\\d{8}-\\d{6}-\\d+$/.test(newId)) {\n noteLine(c(\"dim\", `» resumed as ${newId}`));\n target = newId;\n finished.delete(newId);\n seenHeader.delete(newId);\n states.delete(newId);\n ticker = initialFollowState(\"resumed\");\n lastEventAt = Date.now();\n } else {\n noteLine(c(\"red\", `» resume failed (rc=${rc})`));\n }\n });\n };\n const handleOperatorLine = makeOperatorInput({\n target: () => target,\n say: (id, text) => sayToWorker(logDir, id, text),\n workerKnown: (id) => loadStatuses(logDir).some((s) => s.id === id),\n resume: resumeTarget,\n note: noteLine,\n paint: c,\n ...(chat ? { sent: () => undefined } : {}), // the echo replaces the note\n });\n\n // --- the chat line: readline editing on the prompt row\n // the readline interface keeps stdin flowing — without a close, the process\n // outlives follow itself (a pane then never closes, however long ago the\n // worker ended), so it is closed in the finally block below\n const terminalIn = (in_ as { isTTY?: boolean }).isTTY === true;\n let hintUp = true;\n let rlIn: ReturnType<typeof createInterface> | null = null;\n let onResize: (() => void) | null = null;\n // an echoed line comes back as a mirrored operator event within moments —\n // remember what was echoed and swallow the twin, so the pane shows what\n // was typed exactly once\n const echoed = new Map<string, { n: number; until: number }>();\n const suppressMirror = (text: string): boolean => {\n const g = echoed.get(text);\n if (!g || Date.now() > g.until) return false;\n g.n -= 1;\n if (g.n <= 0) echoed.delete(text);\n return true;\n };\n const drawPrompt = () => {\n if (!chat || terminalIn) return;\n out_.write(chatPromptRow(rows, CHAT_PROMPT, hintUp ? c(\"dim\", CHAT_HINT) : undefined));\n };\n if (chat) {\n const echoOperator = (text: string) => {\n const id = target;\n if (id === null) return;\n echoed.set(text, { n: 1, until: Date.now() + 10_000 });\n const st = states.get(id) ?? initialFollowState();\n const step = applyEvent(\n c,\n st,\n { type: \"operator\", _ts: new Date().toISOString(), text },\n \"\",\n undefined,\n undefined,\n columns()\n );\n if (step.day) out(c(\"dim\", `── ${step.day} ──`));\n for (const ln of step.lines) out(ln);\n states.set(id, step.state);\n };\n const handleChatLine = (raw: string) => {\n hintUp = false;\n const act = parseChatLine(raw);\n switch (act.kind) {\n case \"message\":\n if (!act.text) break;\n echoOperator(act.text);\n handleOperatorLine(act.text);\n break;\n case \"resume\":\n if (!act.text) {\n out(c(\"dim\", \"usage: /resume <text>\"));\n break;\n }\n echoOperator(act.text);\n if (target !== null) resumeTarget(act.text, target);\n break;\n case \"help\":\n for (const ln of CHAT_HELP) out(c(\"dim\", ln));\n break;\n case \"status\": {\n const s =\n target !== null ? loadStatuses(logDir).find((x) => x.id === target) : undefined;\n out(c(\"dim\", s ? `${s.id} · ${s.state} · ${s.last}` : `${target ?? \"?\"} · no status`));\n break;\n }\n case \"quit\":\n aborted = true;\n break;\n }\n drawPrompt(); // on a pipe readline does not repaint the prompt itself\n };\n rlIn = createInterface({\n input: in_,\n output: out_ as unknown as NodeJS.WriteStream,\n terminal: terminalIn,\n });\n rlIn.on(\"line\", handleChatLine);\n // Ctrl-C: an empty prompt leaves, a draft clears; Ctrl-D (close) leaves\n rlIn.on(\"SIGINT\", () => {\n if ((rlIn?.line ?? \"\").trim() === \"\") aborted = true;\n else rlIn?.write(null, { ctrl: true, name: \"u\" });\n });\n rlIn.on(\"close\", () => {\n aborted = true;\n });\n out_.write(chatEnter(rows));\n if (terminalIn) {\n rlIn.setPrompt(CHAT_PROMPT);\n rlIn.prompt();\n out_.write(c(\"dim\", CHAT_HINT));\n } else {\n drawPrompt();\n }\n // resize: re-read the geometry, rebuild the region, fill it afresh\n onResize = () => {\n if (typeof out_.rows === \"number\") rows = out_.rows;\n fill = 0;\n out_.write(chatScrollRegion(rows));\n drawPrompt();\n };\n out_.on?.(\"resize\", onResize);\n } else if (target !== null && (in_ as { isTTY?: boolean }).isTTY) {\n // plain operator channel: TTY stdin, non-TTY stdout\n rlIn = createInterface({ input: in_ });\n rlIn.on(\"line\", handleOperatorLine);\n }\n\n /** The prompt's unsent text — a draft holds the auto-exit countdown. */\n const promptText = () => (io?.promptLine ? io.promptLine() : (rlIn?.line ?? \"\"));\n\n try {\n for (;;) {\n if (aborted) return;\n const statuses = new Map(loadStatuses(logDir).map((s) => [s.id, s]));\n const wanted = target ? [target] : runningIds();\n for (const wid of wanted) {\n if (handles.has(wid) || finished.has(wid)) continue;\n const path = eventsPath(logDir, wid);\n const st = statuses.get(wid) ?? ({ id: wid, label: \"\", cwd: \"\", provider: \"\" } as WorkerStatus);\n if (existsSync(path)) {\n const multi = target === null || handles.size > 0;\n handles.set(wid, attachHandle(wid, path, st, multi));\n states.set(wid, states.get(wid) ?? initialFollowState());\n } else if (!seenHeader.has(wid)) {\n // worker just started, no event yet: name it instead of a blank pane\n out(headerLine(c, st));\n seenHeader.add(wid);\n }\n }\n const multi = handles.size > 1 || target === null;\n let progressed = false;\n\n for (const [wid, h] of [...handles.entries()]) {\n const st = statuses.get(wid) ?? ({ id: wid, label: \"\", cwd: \"\", provider: \"\" } as WorkerStatus);\n // read everything appended since the last poll (fd position advances)\n const buffer = Buffer.alloc(65536);\n for (;;) {\n let n: number;\n try {\n n = readSync(h.fd, buffer, 0, buffer.length, null);\n } catch {\n n = 0;\n }\n if (n <= 0) break;\n h.buf += buffer.toString(\"utf8\", 0, n);\n }\n const lines = h.buf.split(\"\\n\");\n h.buf = lines.pop() ?? \"\";\n for (const line of lines) {\n if (!line.trim()) continue;\n progressed = true;\n let e: StreamEventLike;\n try {\n e = JSON.parse(line) as StreamEventLike;\n } catch {\n continue;\n }\n emitEvent(e, wid, st, multi);\n }\n const ended = workerEnded(finished.has(wid), st.state, alive(st.pid));\n if (ended) {\n if (!finished.has(wid)) {\n eraseLiveness();\n out(\n `${multi ? c(\"cyan\", wid.slice(-4)) + c(\"dim\", \" ┃ \") : \" \"}${c(\"red\", \"✗ \" + (st.state || \"ended\"))} · ${st.last ?? \"\"}`\n );\n finished.add(wid);\n }\n closeSync(h.fd);\n handles.delete(wid);\n }\n }\n\n const lingerOn = target; // resume swaps `target` under us (see above)\n if (lingerOn !== null && finished.has(lingerOn)) {\n if (autoExit) {\n // a draft in the prompt holds the countdown: the operator may be\n // about to say or resume something\n if (holdAutoExit(promptText())) {\n writeLiveness();\n await sleep(250);\n continue;\n }\n const until = Date.now() + autoExit * 1000;\n while (\n Date.now() < until &&\n !aborted &&\n target === lingerOn &&\n !holdAutoExit(promptText())\n ) {\n writeLiveness();\n await sleep(250);\n }\n // interrupted, resumed inside the window, or a draft appeared: follow on\n if (aborted || target !== lingerOn || holdAutoExit(promptText())) continue;\n eraseLiveness();\n out(c(\"dim\", \"closing\"));\n return;\n }\n // no auto-exit: only a terminal follow with no wired stdin is done —\n // an interactive one stays up for say / resume input\n if (rlIn === null) return;\n }\n if (autoExit && !target) {\n if (runningIds().length) {\n idleSince = null;\n } else if (idleSince === null) {\n idleSince = Date.now();\n } else if (Date.now() - started >= 30_000 && Date.now() - idleSince >= autoExit * 1000) {\n eraseLiveness();\n out(c(\"dim\", \"closing\"));\n return;\n }\n }\n if (!progressed) {\n writeLiveness();\n await sleep(500);\n }\n }\n } finally {\n // close before anything else: an open readline keeps the process (and the\n // iTerm pane running it) alive long after follow has decided to end\n rlIn?.close();\n process.removeListener(\"SIGINT\", onInt);\n if (onResize) out_.removeListener?.(\"resize\", onResize);\n if (chat) out_.write(chatLeave(rows));\n for (const h of handles.values()) {\n try {\n closeSync(h.fd);\n } catch {\n /* already closed */\n }\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n// ---------------------------------------------------------------------------\n// status line\n// ---------------------------------------------------------------------------\n\n/** Workers of this terminal for the status bar; \"\" when there are none. */\nexport function statusLineOutput(\n logDir: string,\n term: string,\n cwd: string,\n now: Date = new Date()\n): string {\n const statuses = loadStatuses(logDir);\n const mine = statuses.filter((s) => {\n const sameScope = term && workerInScope(s, term);\n const sameDir = cwd && s.cwd.startsWith(cwd);\n return sameScope || (!term && sameDir);\n });\n return renderStatusLine(mine, now);\n}\n","/**\n * providers.ts — provider, class and switch management over the workers config.\n *\n * One layer under both `pai worker providers …` and the MCP worker_providers\n * tool. Every mutation re-reads the config file, changes only the workers\n * section, and writes it back atomically — the file is shared with everything\n * else PAI runs, so a torn write is not an option.\n *\n * Keys are never stored in the config and never accepted inline from the CLI:\n * only file paths. (The MCP `add` tool additionally accepts a raw `key`, which\n * it immediately parks in ~/.config/pai/keys/<name>, mode 0600, storing only\n * the path — a chat is not a place to leave a credential lying around.)\n */\n\nimport { existsSync, writeFileSync, chmodSync, mkdirSync } from \"node:fs\";\nimport {\n DEFAULT_LOG_DIR,\n PROVIDER_TAGS,\n WorkersConfigError,\n expandHome,\n keysDir,\n parseWorkersConfig,\n providerCostTier,\n readWorkersSection,\n writeWorkersSection,\n type ClassTarget,\n type WorkerProvider,\n type WorkersConfig,\n} from \"./config.js\";\nimport { clearCooldown, probeQuota, quotaSkipThreshold } from \"./routing.js\";\nimport { workersLogDir } from \"./paths.js\";\n\nexport interface AddProviderInput {\n name: string;\n baseUrl: string;\n keyFile?: string | null;\n key?: string;\n model: string;\n fastModel?: string;\n env?: Record<string, string>;\n note?: string;\n protocol?: \"anthropic\" | \"openai\";\n upstreamUrl?: string;\n engine?: \"claude\" | \"codex\";\n quotaProbe?: string;\n contextWindow?: number;\n costTier?: number;\n tags?: string[];\n}\n\nexport function addProvider(input: AddProviderInput): WorkersConfig {\n if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(input.name)) {\n throw new WorkersConfigError(\n `provider name \"${input.name}\" may only contain letters, digits, - and _`\n );\n }\n if (input.protocol === \"openai\" && !input.upstreamUrl) {\n throw new WorkersConfigError(\n `protocol \"openai\" needs the upstreamUrl (the Chat Completions base, ` +\n `e.g. \"https://api.openai.com/v1\") — the PAI proxy translates to it.`\n );\n }\n if (!input.baseUrl && input.protocol !== \"openai\") {\n throw new WorkersConfigError(\"baseUrl is required\");\n }\n\n let keyFile = input.keyFile ?? null;\n if (input.key !== undefined) {\n const dir = keysDir();\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const path = `${dir}/${input.name}`;\n writeFileSync(path, input.key.trim() + \"\\n\", { encoding: \"utf8\", mode: 0o600 });\n try {\n chmodSync(path, 0o600);\n } catch {\n /* mode above already applied on create where supported */\n }\n keyFile = path;\n }\n\n const { raw, workers } = readWorkersSection();\n const provider: WorkerProvider = {\n enabled: true,\n protocol: input.protocol ?? \"anthropic\",\n baseUrl: input.baseUrl,\n keyFile,\n models: input.fastModel\n ? { default: input.model, fast: input.fastModel }\n : { default: input.model },\n env: input.env ?? {},\n ...(input.note ? { note: input.note } : {}),\n ...(input.upstreamUrl ? { upstreamUrl: input.upstreamUrl } : {}),\n ...(input.engine && input.engine !== \"claude\" ? { engine: input.engine } : {}),\n ...(input.quotaProbe ? { quotaProbe: input.quotaProbe } : {}),\n ...(input.contextWindow ? { contextWindow: input.contextWindow } : {}),\n ...(input.costTier ? { costTier: input.costTier } : {}),\n ...(input.tags?.length ? { tags: input.tags as WorkerProvider[\"tags\"] } : {}),\n };\n workers.providers[input.name] = provider;\n\n // First provider takes over the whole section: active, default classes, pane.\n const first = Object.keys(workers.providers).length === 1;\n if (first) {\n workers.enabled = true;\n workers.active = input.name;\n workers.logDir = workers.logDir || DEFAULT_LOG_DIR;\n const fast = input.fastModel ? `${input.name}/fast` : input.name;\n workers.classes = {\n draft: fast,\n plan: input.name,\n implement: input.name,\n review: input.name,\n research: input.name,\n spotcheck: fast,\n simple: fast,\n complex: input.name,\n image: input.name,\n };\n }\n\n writeWorkersSection(raw, workers);\n return workers;\n}\n\n/** Change costTier / tags on an existing provider (MCP action \"update\"). */\nexport function updateProvider(\n name: string,\n changes: { costTier?: number; tags?: string[] }\n): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n const p = workers.providers[name];\n if (!p) {\n throw new WorkersConfigError(\n `no provider named \"${name}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n }\n if (changes.costTier !== undefined) {\n if (!Number.isInteger(changes.costTier) || changes.costTier < 1 || changes.costTier > 5) {\n throw new WorkersConfigError(\"costTier must be an integer 1 (cheapest) … 5 (most expensive)\");\n }\n p.costTier = changes.costTier;\n }\n if (changes.tags !== undefined) {\n for (const t of changes.tags) {\n if (!(PROVIDER_TAGS as readonly string[]).includes(t)) {\n throw new WorkersConfigError(`\"${t}\" is not a tag (from: ${PROVIDER_TAGS.join(\", \")})`);\n }\n }\n p.tags = changes.tags as WorkerProvider[\"tags\"];\n }\n writeWorkersSection(raw, workers);\n return workers;\n}\n\nexport function removeProvider(name: string): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n if (!workers.providers[name]) {\n throw new WorkersConfigError(`no provider named \"${name}\"`);\n }\n delete workers.providers[name];\n for (const [cls, target] of Object.entries(workers.classes)) {\n const targetProvider = typeof target === \"string\" ? target.split(\"/\")[0] : target.provider;\n if (targetProvider === name) delete workers.classes[cls];\n }\n if (workers.active === name) workers.active = null;\n writeWorkersSection(raw, workers);\n return workers;\n}\n\nexport function useProvider(name: string): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n if (!workers.providers[name]) {\n throw new WorkersConfigError(\n `no provider named \"${name}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n }\n workers.active = name;\n writeWorkersSection(raw, workers);\n return workers;\n}\n\nexport function setProviderEnabled(name: string, enabled: boolean): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n const p = workers.providers[name];\n if (!p) throw new WorkersConfigError(`no provider named \"${name}\"`);\n p.enabled = enabled;\n writeWorkersSection(raw, workers);\n // `enable` is also the manual cooldown-clear (spec: routing)\n if (enabled) clearCooldown(workersLogDir(parseWorkersConfig(raw.workers)), name);\n return workers;\n}\n\n/**\n * Point a class at a target (\"provider\", \"provider/fast\", or an object with\n * provider + optional mcp/maxCostTier/requireTags/order). An object without\n * a provider only constrains auto-routing for that class.\n */\nexport function setClass(name: string, target: ClassTarget): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n if (typeof target === \"string\") {\n const [provider, alias] = target.split(\"/\");\n const p = workers.providers[provider];\n if (!p) {\n throw new WorkersConfigError(\n `no provider named \"${provider}\" in \"${target}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n }\n if (alias && alias !== \"default\" && alias !== \"fast\") {\n throw new WorkersConfigError(\n `unknown model alias \"${alias}\" — providers expose \"default\" and \"fast\"`\n );\n }\n if (alias === \"fast\" && !p.models.fast) {\n throw new WorkersConfigError(`provider \"${provider}\" has no fast model configured`);\n }\n } else if (target.provider) {\n if (!workers.providers[target.provider]) {\n throw new WorkersConfigError(\n `no provider named \"${target.provider}\". Configured: ${Object.keys(workers.providers).join(\", \") || \"(none)\"}`\n );\n }\n }\n workers.classes[name] = target;\n writeWorkersSection(raw, workers);\n return workers;\n}\n\nexport function unsetClass(name: string): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n if (!(name in workers.classes)) {\n throw new WorkersConfigError(`no class named \"${name}\"`);\n }\n delete workers.classes[name];\n writeWorkersSection(raw, workers);\n return workers;\n}\n\nexport function setWorkersEnabled(enabled: boolean): WorkersConfig {\n const { raw, workers } = readWorkersSection();\n workers.enabled = enabled;\n writeWorkersSection(raw, workers);\n return workers;\n}\n\n/** `glm/fast` | `{provider, mcp, …}` → one printable target line. */\nexport function classTargetText(target: ClassTarget): string {\n if (typeof target === \"string\") return target;\n const bits = [\n target.provider ?? \"(routing)\",\n ...(target.mcp?.length ? [`mcp(${target.mcp.join(\",\")})`] : []),\n ...(target.maxCostTier !== undefined ? [`max tier ${target.maxCostTier}`] : []),\n ...(target.requireTags?.length ? [`needs ${target.requireTags.join(\",\")}`] : []),\n ...(target.order?.length ? [`order [${target.order.join(\",\")}]`] : []),\n ];\n return bits.join(\" \");\n}\n\n/** Human-readable provider listing (quota probe included when configured). */\nexport function describeProviders(workers: WorkersConfig): string[] {\n const lines: string[] = [];\n const names = Object.keys(workers.providers);\n if (!names.length) {\n lines.push(`no providers configured. Add one with:`);\n lines.push(\n ` pai worker providers add <name> --base-url <url> --key-file <path> --model <model>`\n );\n return lines;\n }\n for (const name of names) {\n const p = workers.providers[name];\n const flags = [\n p.enabled ? \"enabled\" : \"disabled\",\n workers.active === name ? \"active\" : null,\n ].filter(Boolean);\n const quota = p.quotaProbe ? probeQuota(p) : null;\n const quotaNote = quota === null ? \"\" : ` quota ${quota}% (skip at ${quotaSkipThreshold(p)})`;\n const tierTags = [`tier ${providerCostTier(p)}`, ...(p.tags ?? [])].join(\", \");\n lines.push(`${name} [${flags.join(\", \")}] ${p.baseUrl}`);\n lines.push(\n ` model ${p.models.default}${p.models.fast ? ` (fast: ${p.models.fast})` : \"\"}${quotaNote}`\n );\n lines.push(` ${tierTags}`);\n if (p.keyFile) lines.push(` key file ${expandHome(p.keyFile)}`);\n else lines.push(` no key file (token \"local\")`);\n if (p.note) lines.push(` ${p.note}`);\n if (p.protocol === \"openai\") {\n lines.push(` via PAI proxy ← ${p.upstreamUrl ?? \"(upstreamUrl missing)\"}`);\n }\n if (p.engine === \"codex\") {\n lines.push(` engine codex (runs through the Codex CLI)`);\n }\n }\n const setNames = Object.keys(workers.mcpSets);\n if (setNames.length) {\n lines.push(`mcp sets: ${setNames.map((s) => `${s}=[${workers.mcpSets[s].join(\",\")}]`).join(\" \")}`);\n }\n const classNames = Object.keys(workers.classes);\n if (classNames.length) {\n lines.push(`classes: ${classNames.map((cl) => `${cl}=${classTargetText(workers.classes[cl])}`).join(\" \")}`);\n }\n if (workers.active === \"auto\") {\n lines.push(`routing: auto — order [${workers.routing.order.join(\", \")}], cooldown ${workers.routing.cooldownMinutes}m`);\n }\n return lines;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,eAAe,MAAc,QAAQ,MAA+B;AAClF,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO,EAAE;CAEhC,IAAI;AACJ,KAAI;AACF,QAAM,aAAa,MAAM,OAAO;UACzB,GAAG;AACV,QAAM,IAAI,MACR,kBAAkB,MAAM,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,kFAExE;;AAGH,KAAI;AACF,SAAO,KAAK,MAAM,IAAI;UACf,GAAG;AACV,QAAM,IAAI,MACR,GAAG,MAAM,iCAAiC,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,sIAGtF;;;;;;;;;;;AAYL,SAAgB,gBACd,MACA,MACA,OAA6C,EAAE,EACzC;CACN,MAAM,EAAE,SAAS,MAAM,QAAQ,SAAS;CACxC,MAAM,aAAa,KAAK,UAAU,MAAM,MAAM,EAAE,GAAG;CAEnD,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AAEzD,KAAI,UAAU,WAAW,KAAK,CAC5B,KAAI;AACF,eAAa,MAAM,GAAG,KAAK,UAAU;UAC9B,GAAG;AACV,QAAM,IAAI,MACR,qBAAqB,MAAM,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,uCAE3E;;CAIL,MAAM,MAAM,GAAG,KAAK,WAAW,QAAQ;AACvC,KAAI;AACF,gBAAc,KAAK,YAAY,OAAO;AACtC,aAAW,KAAK,KAAK;UACd,GAAG;AACV,MAAI;AAAE,OAAI,WAAW,IAAI,CAAE,YAAW,IAAI;UAAU;AACpD,QAAM,IAAI,MACR,mBAAmB,MAAM,IAAI,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,8BAEzE;;;;;;;;;;;;;;;;;;;;;;ACnBL,MAAa,oBAAoB;;AAGjC,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACD;;AAKD,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAwCD,MAAa,kBAAkB;AAE/B,MAAa,eAAkC;CAC7C,SAAS;CACT,UAAU;CACV,cAAc;CACf;AAED,MAAa,kBAAwC;CACnD,OAAO,EAAE;CACT,iBAAiB;CACjB,cAAc;CACf;AAED,SAAgB,uBAAsC;AACpD,QAAO;EACL,SAAS;EACT,QAAQ;EACR,WAAW,EAAE;EACb,SAAS,EAAE;EACX,SAAS,EAAE;EACX,MAAM,EAAE,GAAG,cAAc;EACzB,QAAQ;EACR,SAAS;GAAE,GAAG;GAAiB,OAAO,EAAE;GAAE;EAC3C;;AAOH,IAAa,qBAAb,cAAwC,MAAM;AAE9C,SAAS,IAAI,MAAc,KAAoB;AAC7C,OAAM,IAAI,mBAAmB,UAAU,KAAK,IAAI,MAAM;;AAGxD,SAAS,IAAI,GAAoB;AAC/B,QAAO,OAAO,MAAM,WAAW,IAAI;;AAGrC,SAAS,cAAc,MAAc,KAA8B;AACjE,KAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,KAAI,cAAc,QAAQ,oBAAoB;CAC3F,MAAM,IAAI;CAEV,MAAM,WAAW,EAAE,aAAa,SAAY,cAAc,IAAI,EAAE,SAAS;AACzE,KAAI,aAAa,eAAe,aAAa,SAC3C,KAAI,cAAc,KAAK,YAAY,IAAI,IAAI,EAAE,SAAS,CAAC,uCAAuC;CAEhG,MAAM,SAAS,EAAE,WAAW,SAAY,WAAW,IAAI,EAAE,OAAO;AAChE,KAAI,WAAW,YAAY,WAAW,QACpC,KAAI,cAAc,KAAK,UAAU,IAAI,IAAI,EAAE,OAAO,CAAC,mCAAmC;CAGxF,MAAM,UAAU,IAAI,EAAE,QAAQ;AAG9B,KAAI,CAAC,WAAW,aAAa,SAAU,KAAI,cAAc,KAAK,WAAW,cAAc;CAEvF,MAAM,UACJ,EAAE,YAAY,UAAa,EAAE,YAAY,QAAQ,IAAI,EAAE,QAAQ,KAAK,KAChE,OACA,IAAI,EAAE,QAAQ;CAEpB,MAAM,YAAY,EAAE,WAAW,SAAY,EAAE,GAAG,EAAE;AAClD,KAAI,OAAO,cAAc,YAAY,cAAc,KACjD,KAAI,cAAc,KAAK,UAAU,oBAAoB;CAEvD,MAAM,IAAI;CACV,MAAM,eAAe,IAAI,EAAE,QAAQ;AACnC,KAAI,CAAC,aAAc,KAAI,cAAc,KAAK,kBAAkB,cAAc;CAC1E,MAAM,OAAO,EAAE,SAAS,SAAY,SAAY,IAAI,EAAE,KAAK;CAE3D,MAAM,MAA8B,EAAE;AACtC,KAAI,EAAE,QAAQ,QAAW;AACvB,MAAI,OAAO,EAAE,QAAQ,YAAY,EAAE,QAAQ,QAAQ,MAAM,QAAQ,EAAE,IAAI,CACrE,KAAI,cAAc,KAAK,OAAO,qCAAqC;AAErE,OAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,EAAE,IAA+B,EAAE;AACrE,OAAI,OAAO,MAAM,SAAU,KAAI,cAAc,KAAK,OAAO,KAAK,mBAAmB;AACjF,OAAI,KAAK;;;CAIb,MAAM,cAAc,EAAE,gBAAgB,SAAY,SAAY,EAAE;AAChE,KAAI,gBAAgB,QAClB;MAAI,OAAO,gBAAgB,YAAY,cAAc,KAAK,cAAc,IACtE,KAAI,cAAc,KAAK,eAAe,qCAAqC;;CAI/E,MAAM,gBAAgB,EAAE,kBAAkB,SAAY,SAAY,EAAE;AACpE,KAAI,kBAAkB,QACpB;MAAI,OAAO,kBAAkB,YAAY,iBAAiB,EACxD,KAAI,cAAc,KAAK,iBAAiB,sCAAsC;;CAIlF,MAAM,cAAc,IAAI,EAAE,YAAY;AACtC,KAAI,aAAa,YAAY,CAAC,YAC5B,KAAI,cAAc,KAAK,eAAe,kGAAkG;CAG1I,MAAM,WAAW,EAAE,aAAa,SAAY,SAAY,EAAE;AAC1D,KAAI,aAAa,QACf;MAAI,OAAO,aAAa,YAAY,CAAC,OAAO,UAAU,SAAS,IAAI,WAAW,KAAK,WAAW,EAC5F,KAAI,cAAc,KAAK,YAAY,uDAAuD;;CAI9F,IAAI;AACJ,KAAI,EAAE,SAAS,QAAW;AACxB,MAAI,CAAC,MAAM,QAAQ,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,MAAM,OAAO,MAAM,SAAS,CACrE,KAAI,cAAc,KAAK,QAAQ,kCAAkC,cAAc,KAAK,KAAK,GAAG;AAE9F,OAAK,MAAM,KAAK,EAAE,KAChB,KAAI,CAAE,cAAoC,SAAS,EAAE,CACnD,KAAI,cAAc,KAAK,QAAQ,IAAI,EAAE,wBAAwB,cAAc,KAAK,KAAK,CAAC,GAAG;AAG7F,SAAO,EAAE;;AAGX,QAAO;EACL,SAAS,EAAE,YAAY,SAAY,OAAO,EAAE,YAAY;EACxD;EACA;EACA;EACA,QAAQ,OAAO;GAAE,SAAS;GAAc;GAAM,GAAG,EAAE,SAAS,cAAc;EAC1E;EACA,GAAI,IAAI,EAAE,KAAK,GAAG,EAAE,MAAM,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;EAC5C,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;EACtC,GAAI,WAAW,WAAW,EAAE,QAAQ,GAAG,EAAE;EACzC,GAAI,IAAI,EAAE,WAAW,GAAG,EAAE,YAAY,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;EAC9D,GAAI,gBAAgB,SAAY,EAAE,aAAa,GAAG,EAAE;EACpD,GAAI,kBAAkB,SAAY,EAAE,eAAe,GAAG,EAAE;EACxD,GAAI,aAAa,SAAY,EAAE,UAAU,GAAG,EAAE;EAC9C,GAAI,OAAO,EAAE,MAAM,GAAG,EAAE;EACzB;;;;;;AAOH,SAAgB,mBAAmB,KAA6B;CAC9D,MAAM,IAAI,sBAAsB;AAChC,KAAI,QAAQ,UAAa,QAAQ,KAAM,QAAO;AAC9C,KAAI,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAAI,CAC/C,KAAI,IAAI,4BAA4B;CAEtC,MAAM,IAAI;CAEV,MAAM,YAA4C,EAAE;AACpD,KAAI,EAAE,cAAc,QAAW;AAC7B,MAAI,OAAO,EAAE,cAAc,YAAY,EAAE,cAAc,QAAQ,MAAM,QAAQ,EAAE,UAAU,CACvF,KAAI,cAAc,2CAA2C;AAE/D,OAAK,MAAM,CAAC,MAAM,MAAM,OAAO,QAAQ,EAAE,UAAU,CACjD,WAAU,QAAQ,cAAc,MAAM,EAAE;;CAM5C,MAAM,UAAuC,EAAE;CAC/C,MAAM,aAAa,EAAE,YAAY,SAAY,EAAE,UAAU,EAAE;AAC3D,KAAI,eAAe,QAAW;AAC5B,MAAI,OAAO,eAAe,YAAY,eAAe,QAAQ,MAAM,QAAQ,WAAW,CACpF,KAAI,EAAE,YAAY,SAAY,aAAa,UAAU,oGAAoG;AAE3J,OAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,WAAW,CACpD,KAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,CAAC,MAAM,QAAQ,OAAO,EAAE;GAC3E,MAAM,IAAI;GACV,MAAM,WAAW,IAAI,EAAE,SAAS;AAChC,OAAI,EAAE,aAAa,WAAc,CAAC,YAAY,SAAS,SAAS,IAAI,EAClE,KAAI,YAAY,IAAI,YAAY,qBAAqB,SAAS,GAAG;GAEnE,IAAI;AACJ,OAAI,EAAE,QAAQ,QAAW;AACvB,QAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,IAAI,EAAE,IAAI,MAAM,MAAM,OAAO,MAAM,SAAS,CACnE,KAAI,YAAY,IAAI,OAAO,8CAA8C;AAE3E,UAAM,EAAE;;GAEV,IAAI;AACJ,OAAI,EAAE,gBAAgB,QAAW;AAC/B,QACE,OAAO,EAAE,gBAAgB,YACzB,CAAC,OAAO,UAAU,EAAE,YAAY,IAChC,EAAE,cAAc,KAChB,EAAE,cAAc,EAEhB,KAAI,YAAY,IAAI,eAAe,2BAA2B;AAEhE,kBAAc,EAAE;;GAElB,IAAI;AACJ,OAAI,EAAE,gBAAgB,QAAW;AAC/B,QAAI,CAAC,MAAM,QAAQ,EAAE,YAAY,IAAI,EAAE,YAAY,MAAM,MAAM,OAAO,MAAM,SAAS,CACnF,KAAI,YAAY,IAAI,eAAe,kCAAkC,cAAc,KAAK,KAAK,GAAG;AAElG,SAAK,MAAM,KAAK,EAAE,YAChB,KAAI,CAAE,cAAoC,SAAS,EAAE,CACnD,KAAI,YAAY,IAAI,eAAe,IAAI,EAAE,wBAAwB,cAAc,KAAK,KAAK,CAAC,GAAG;AAGjG,kBAAc,EAAE;;GAElB,IAAI;AACJ,OAAI,EAAE,UAAU,QAAW;AACzB,QAAI,CAAC,MAAM,QAAQ,EAAE,MAAM,IAAI,EAAE,MAAM,MAAM,MAAM,OAAO,MAAM,SAAS,CACvE,KAAI,YAAY,IAAI,SAAS,qCAAqC;AAEpE,YAAQ,EAAE;;GAEZ,MAAM,MAAmB;IACvB,GAAI,WAAW,EAAE,UAAU,GAAG,EAAE;IAChC,GAAI,MAAM,EAAE,KAAK,GAAG,EAAE;IACtB,GAAI,gBAAgB,SAAY,EAAE,aAAa,GAAG,EAAE;IACpD,GAAI,cAAc,EAAE,aAAa,GAAG,EAAE;IACtC,GAAI,QAAQ,EAAE,OAAO,GAAG,EAAE;IAC3B;AACD,WAAQ,OAAO,OAAO,KAAK,IAAI,CAAC,SAAS,MAAM,EAAE;SAC5C;GACL,MAAM,IAAI,IAAI,OAAO;AACrB,OAAI,CAAC,KAAK,EAAE,SAAS,IAAI,CAAE,KAAI,YAAY,OAAO,mBAAmB,EAAE,GAAG;AAC1E,WAAQ,OAAO;;;CAKrB,MAAM,UAAoC,EAAE;AAC5C,KAAI,EAAE,YAAY,QAAW;AAC3B,MAAI,OAAO,EAAE,YAAY,YAAY,EAAE,YAAY,QAAQ,MAAM,QAAQ,EAAE,QAAQ,CACjF,KAAI,YAAY,iDAAiD;AAEnE,OAAK,MAAM,CAAC,SAAS,YAAY,OAAO,QAAQ,EAAE,QAAQ,EAAE;AAC1D,OAAI,CAAC,MAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,MAAM,OAAO,MAAM,SAAS,CACvE,KAAI,YAAY,WAAW,uCAAuC;AAEpE,WAAQ,WAAW;;;CAIvB,IAAI,OAAO,EAAE,GAAG,cAAc;AAC9B,KAAI,EAAE,SAAS,QAAW;AACxB,MAAI,OAAO,EAAE,SAAS,YAAY,EAAE,SAAS,KAAM,KAAI,SAAS,oBAAoB;EACpF,MAAM,KAAK,EAAE;AACb,MAAI,GAAG,YAAY,UAAa,OAAO,GAAG,YAAY,UAAW,KAAI,iBAAiB,kBAAkB;AACxG,MAAI,GAAG,aAAa,WAAc,OAAO,GAAG,aAAa,YAAY,GAAG,YAAY,GAClF,KAAI,kBAAkB,sCAAsC;AAE9D,MAAI,GAAG,iBAAiB,UAAa,OAAO,GAAG,iBAAiB,SAC9D,KAAI,sBAAsB,mBAAmB;AAG/C,SAAO;GACL,SAAS,GAAG,YAAY,SAAY,aAAa,UAAU,GAAG,YAAY;GAC1E,UAAU,GAAG,aAAa,SAAY,aAAa,WAAW,GAAG;GACjE,cAAc,GAAG,iBAAiB,SAAY,aAAa,eAAe,GAAG;GAC9E;;CAGH,IAAI,UAAU;EAAE,GAAG;EAAiB,OAAO,EAAE;EAAc;AAC3D,KAAI,EAAE,YAAY,QAAW;AAC3B,MAAI,OAAO,EAAE,YAAY,YAAY,EAAE,YAAY,KAAM,KAAI,YAAY,oBAAoB;EAC7F,MAAM,IAAI,EAAE;AACZ,MAAI,EAAE,UAAU,QAAW;AACzB,OAAI,CAAC,MAAM,QAAQ,EAAE,MAAM,IAAI,EAAE,MAAM,MAAM,MAAM,OAAO,MAAM,SAAS,CACvE,KAAI,kBAAkB,qCAAqC;AAE7D,WAAQ,QAAQ,EAAE;;AAEpB,MAAI,EAAE,oBAAoB,UAAa,OAAO,EAAE,oBAAoB,SAClE,KAAI,4BAA4B,mBAAmB;AAErD,MAAI,EAAE,iBAAiB,UAAa,OAAO,EAAE,iBAAiB,UAC5D,KAAI,yBAAyB,kBAAkB;AAEjD,YAAU;GACR,OAAO,QAAQ;GACf,iBAAiB,EAAE,oBAAoB,SAAY,gBAAgB,kBAAkB,EAAE;GACvF,cAAc,EAAE,iBAAiB,SAAY,gBAAgB,eAAe,EAAE,iBAAiB;GAChG;;CAGH,MAAM,SAAS,EAAE,WAAW,UAAa,EAAE,WAAW,OAAO,OAAO,IAAI,EAAE,OAAO;AACjF,KAAI,WAAW,QAAQ,WAAW,UAAU,EAAE,UAAU,YAAY;AAKpE,QAAO;EACL,SAAS,EAAE,YAAY,SAAY,EAAE,UAAU,EAAE,YAAY;EAC7D;EACA;EACA;EACA;EACA;EACA,QAAQ,IAAI,EAAE,OAAO,IAAI,EAAE;EAC3B;EACD;;;;;;;AAYH,SAAgB,qBAGd;CACA,MAAM,MAAM,eAAe,aAAa,4BAA4B;AACpE,QAAO;EAAE;EAAK,SAAS,mBAAmB,IAAI,QAAQ;EAAE;;;AAI1D,SAAgB,oBACd,KACA,SACM;AACN,KAAI,UAAU;AACd,iBAAgB,aAAa,KAAK,EAAE,OAAO,6BAA6B,CAAC;;;AAI3E,SAAgB,WAAW,GAAmB;AAC5C,KAAI,MAAM,OAAO,EAAE,WAAW,KAAK,CAAE,QAAO,KAAK,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC;AACvE,QAAO;;AAOT,SAAgB,uBAAuB,MAAc,GAAyB;AAC5E,KAAI,EAAE,aAAa,YAAY,CAAC,EAAE,YAChC,OAAM,IAAI,mBACR,aAAa,KAAK,kKAGnB;;;AAKL,SAAgB,gBAAgB,GAAkC;AAChE,QAAO,EAAE,UAAU,WAAW,EAAE,QAAQ,GAAG;;AAG7C,MAAa,yBAAyB;;AAGtC,SAAgB,iBAAiB,GAA2B;AAC1D,QAAO,EAAE,YAAY;;;AAIvB,SAAgB,sBAAsB,GAA2B;AAC/D,QAAO,EAAE,iBAAiB;;;AAI5B,SAAgB,UAAkB;AAChC,QAAO,KAAK,SAAS,EAAE,WAAW,OAAO,OAAO;;;;;;;;;;;;;ACjgBlD,SAAgB,cAAc,QAA+B;AAC3D,QAAO,WAAW,OAAO,OAAO;;AAGlC,SAAgB,WAAW,QAAgB,IAAoB;AAC7D,QAAO,KAAK,QAAQ,GAAG,GAAG,SAAS;;AAGrC,SAAgB,WAAW,QAAgB,IAAoB;AAC7D,QAAO,KAAK,QAAQ,GAAG,GAAG,QAAQ;;AAGpC,SAAgB,WAAW,QAAwB;AACjD,QAAO,KAAK,QAAQ,aAAa;;AAGnC,SAAgB,iBAAiB,QAAwB;AACvD,QAAO,KAAK,QAAQ,qBAAqB;;AAG3C,SAAgB,SAAS,QAAwB;AAC/C,QAAO,KAAK,QAAQ,QAAQ;;;;;;;AAQ9B,SAAgB,gBAAgB,QAAwB;AACtD,QAAO,KAAK,QAAQ,cAAc;;;;;ACVpC,SAAgB,gBAAgB,MAAkC;CAChE,IAAI,SAAwB;CAC5B,IAAI,eAAiD;CACrD,MAAM,OAAiB,EAAE;CACzB,IAAI,WAAW;CACf,IAAI,cAAc;CAClB,IAAI,kBAAkB;CACtB,IAAI,qBAAqB;CACzB,MAAM,MAAgB,EAAE;CAExB,IAAI,IAAI;AACR,QAAO,IAAI,KAAK,QAAQ;EACtB,MAAM,IAAI,KAAK;AACf,MAAI,MAAM,QAAQ,MAAM,WAAW;AACjC,cAAW;AACX,QAAK,KAAK,EAAE;AACZ,OAAI,IAAI,IAAI,KAAK,UAAU,CAAC,KAAK,IAAI,GAAG,WAAW,IAAI,EAAE;AACvD,aAAS,KAAK,IAAI;AAClB,SAAK,KAAK,OAAO;AACjB,SAAK;;aAEE,MAAM,mBAAmB;GAClC,MAAM,IAAI,KAAK,IAAI;AACnB,OAAI,MAAM,UAAU,MAAM,cAAe,gBAAe;AACxD,QAAK;aACI,EAAE,WAAW,mBAAmB,EAAE;GAC3C,MAAM,IAAI,EAAE,MAAM,GAA0B;AAC5C,OAAI,MAAM,UAAU,MAAM,cAAe,gBAAe;aAC/C,MAAM,aAAa,YAEnB,MAAM,SAAS;GACxB,MAAM,IAAI,KAAK,IAAI;AACnB,OAAI,MAAM,UAAa,CAAC,EAAE,WAAW,IAAI,EAAE;AACzC,QAAI,KAAK,EAAE;AACX,SAAK;;aAEE,EAAE,WAAW,SAAS,CAC/B,KAAI,KAAK,EAAE,MAAM,EAAgB,CAAC;OAC7B;AACL,OAAI,MAAM,UAAW,eAAc;AACnC,OAAI,EAAE,WAAW,WAAW,CAAE,eAAc;AAC5C,OAAI,MAAM,eAAgB,mBAAkB;AAC5C,OAAI,EAAE,WAAW,gBAAgB,CAAE,mBAAkB;AACrD,OAAI,MAAM,yBAA0B,sBAAqB;AACzD,OAAI,EAAE,WAAW,0BAA0B,CAAE,sBAAqB;AAClE,OACE,WAAW,QAAQ,CAAC,EAAE,WAAW,IAAI,IAAI,KAAK,SAAS,MACtD,KAAK,KAAK,SAAS,OAAO,QAAQ,KAAK,KAAK,SAAS,OAAO,WAE7D,UAAS;AAEX,QAAK,KAAK,EAAE;;AAEd,OAAK;;AAGP,QAAO;EAAE;EAAQ;EAAc;EAAM;EAAU;EAAa;EAAiB;EAAoB;EAAK;;;;;;;;AASxG,SAAgB,kBAAkB,MAA0B;CAC1D,MAAM,MAAgB,EAAE;AACxB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,IAAI,KAAK;EACf,MAAM,UAAU,MAAM,QAAQ,MAAM;AACpC,MAAI,KAAK,EAAE;AACX,MAAI,WAAW,IAAI,IAAI,KAAK,UAAU,CAAC,KAAK,IAAI,GAAG,WAAW,IAAI,CAChE,MAAK;;AAGT,QAAO;;;AAIT,SAAgB,UAAU,GAAY,GAAmB;CACvD,MAAM,IAAI,OAAO,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAChE,QAAO,EAAE,UAAU,IAAI,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG;;;;;;;;;;;;;;;;;;AChGjD,SAAgB,YAAY,oBAAU,IAAI,MAAM,EAAU;CACxD,MAAM,KAAK,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACnD,QACE,GAAG,EAAE,aAAa,CAAC,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,GACzD,EAAE,EAAE,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;;;AAWhE,SAAgB,gBAAgB,MAAiC;CAC/D,MAAM,IAAI,KAAK,MAAM,0DAA0D;AAC/E,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,SAAiC,EAAE;AAEzC,MAAK,MAAM,SAAS,EAAE,MAAM,IAAI,MAAM,IAAI,EAAE;AAC1C,MAAI,CAAC,KAAM;EACX,MAAM,KAAK,KAAK,QAAQ,IAAI;AAC5B,MAAI,MAAM,EAAG;AACb,SAAO,KAAK,MAAM,GAAG,GAAG,IAAI,KAAK,MAAM,KAAK,EAAE;;AAEhD,QAAO;EAAE,OAAO,EAAE;EAAI,OAAO,EAAE;EAAI;EAAQ;;;AAc7C,SAAgB,aACd,MACA,OACA,IACA,sBAAY,IAAI,MAAM,EAChB;CACN,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,GAAG,EAAE;AACvC,MAAI,MAAM,UAAa,MAAM,KAAM;AACnC,QAAM,KAAK,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,QAAQ,QAAQ,IAAI,GAAG;;CAEtD,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACzD,MAAM,OAAO,MAAM,SAAS,MAAM,MAAM,KAAK,IAAI,GAAG;AACpD,gBAAe,MAAM,GAAG,YAAY,IAAI,CAAC,GAAG,QAAQ,KAAK,KAAK,OAAO;;;AAevE,SAAgB,cACd,MACA,OACA,QAAQ,IACR,sBAAY,IAAI,MAAM,EACA;AACtB,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;CAC9B,MAAM,OAAO,aAAa,MAAM,OAAO;CACvC,MAAM,QAAQ,YAAY,IAAI,CAAC,MAAM,GAAG,GAAG;CAC3C,MAAM,QAAQ,KAAK,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,MAAM,CAAC;CAGtD,MAAM,UADJ,UAAU,QAAQ,QAAQ,MAAM,QAAQ,MAAM,EAAE,WAAW,MAAM,CAAC,EAC9C,IAAI,gBAAgB,CAAC,QAAQ,MAAuB,MAAM,KAAK;CACrF,MAAM,SAAS,OAAe,OAAO,QAAQ,MAAM,EAAE,UAAU,GAAG,CAAC;CACnE,MAAM,OAAO,OAAO,QAAQ,MAAM,EAAE,UAAU,aAAa;CAC3D,MAAM,KAAK,KAAK,QAAQ,MAAM,EAAE,OAAO,OAAO,IAAI,CAAC;AACnD,QAAO;EACL,OAAO,UAAU,QAAQ,aAAa,SAAS;EAC/C,SAAS,MAAM,eAAe;EAC9B,SAAS;EACT,aAAa,KAAK,SAAS;EAC3B,QAAQ,MAAM,yBAAyB;EACvC,SAAS,MAAM,0BAA0B;EACzC,UAAU,MAAM,iBAAiB;EACjC,WAAW,MAAM,MAAM,CAAC,MAAM;EAC/B;;;;;;;;;;;;;;AC1DH,SAAgB,eAAe,GAAyE;AACtG,KAAI,CAAC,EAAE,iBAAiB,CAAC,EAAE,cAAe,QAAO;AACjD,QAAO,KAAK,MAAO,EAAE,gBAAgB,EAAE,gBAAiB,IAAI;;;AAI9D,SAAgB,SAAS,oBAAU,IAAI,MAAM,EAAU;CACrD,MAAM,KAAK,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACnD,QACE,GAAG,EAAE,aAAa,CAAC,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,GACzD,EAAE,EAAE,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;;AAMhE,IAAI,SAAS;AACb,IAAI,QAAQ;AAEZ,SAAgB,YAAY,oBAAU,IAAI,MAAM,EAAE,MAAM,QAAQ,KAAa;CAC3E,MAAM,KAAK,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;CACnD,MAAM,OAAO,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC,GAAG;AACrI,KAAI,SAAS,QAAQ;AACnB,WAAS;AACT,SAAO,GAAG,KAAK,GAAG,OAAO,MAAM,CAAC,SAAS,GAAG,IAAI;;AAElD,UAAS;AACT,SAAQ;AACR,QAAO;;;AAIT,SAAgB,WAAW,QAAgB,QAAsB,oBAAU,IAAI,MAAM,EAAQ;AAC3F,QAAO,UAAU,SAAS,EAAE;CAC5B,MAAM,OAAO,WAAW,QAAQ,OAAO,GAAG;CAC1C,MAAM,MAAM,GAAG,KAAK;AACpB,eAAc,KAAK,KAAK,UAAU,OAAO,EAAE,OAAO;AAClD,YAAW,KAAK,KAAK;;;AAIvB,SAAgB,aAAa,QAAgC;AAC3D,KAAI,CAAC,WAAW,OAAO,CAAE,QAAO,EAAE;CAClC,MAAM,MAAsB,EAAE;AAC9B,MAAK,MAAM,QAAQ,YAAY,OAAO,CAAC,MAAM,EAAE;AAC7C,MAAI,CAAC,KAAK,SAAS,UAAU,CAAE;AAC/B,MAAI;AACF,OAAI,KAAK,KAAK,MAAM,aAAa,KAAK,QAAQ,KAAK,EAAE,OAAO,CAAC,CAAiB;UACxE;;AAIV,QAAO;;AAGT,SAAgB,WAAW,QAAgB,IAAiC;CAC1E,MAAM,OAAO,WAAW,QAAQ,GAAG;AACnC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;AAC9B,KAAI;AACF,SAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;SACvC;AACN,SAAO;;;AAIX,SAAgB,MAAM,KAAyC;AAC7D,KAAI,CAAC,OAAO,OAAO,EAAG,QAAO;AAC7B,KAAI;AACF,UAAQ,KAAK,KAAK,EAAE;AACpB,SAAO;SACD;AACN,SAAO;;;;AAKX,SAAgB,MAAM,IAAY,sBAAY,IAAI,MAAM,EAAU;CAChE,MAAM,IAAI,KAAK,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAC;AAC1C,KAAI,OAAO,MAAM,EAAE,CAAE,QAAO;CAC5B,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,SAAS,GAAG,KAAK,IAAK,CAAC;AAC7D,QAAO,IAAI,KAAK,GAAG,EAAE,KAAK,GAAG,KAAK,MAAM,IAAI,GAAG,CAAC;;;AAIlD,SAAgB,aAAa,MAAc,KAAsB;AAC/D,KAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;CACpD,MAAM,IAAI;CACV,MAAM,OAAO,MAAe,OAAO,EAAE,OAAO,WAAY,EAAE,KAAgB;AAC1E,KAAI,SAAS,OAAQ,QAAO,SAAS,UAAU,IAAI,UAAU,EAAE,GAAG;AAClE,KAAI,SAAS,UAAU,SAAS,UAAU,SAAS,WAAW,SAAS,YAErE,QAAO,GAAG,KAAK,IADF,IAAI,YAAY,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI;AAGpD,KAAI,SAAS,UAAU,SAAS,OAAQ,QAAO,GAAG,KAAK,IAAI,UAAU,IAAI,UAAU,EAAE,GAAG;AACxF,KAAI,SAAS,YAAa,QAAO,GAAG,KAAK,IAAI,UAAU,IAAI,QAAQ,EAAE,GAAG;AACxE,KAAI,SAAS,WAAY,QAAO,GAAG,KAAK,IAAI,UAAU,IAAI,MAAM,EAAE,GAAG;AACrE,QAAO;;;;;;;;;;;;;;;;;;;;AChIT,MAAa,oBAAoB,KAAK,SAAS,EAAE,aAAa,qBAAqB;;;;;AAMnF,SAAgB,OAAO,MAAsB;AAC3C,KAAI,CAAC,KAAM,QAAO;CAClB,MAAM,OAAO,KAAK,MAAM,KAAK,EAAE,CAAC;CAChC,MAAM,QAAQ,KAAK,MAAM,EAAE,CAAC,MAAM,IAAI;AACtC,KAAI,KAAK,WAAW,IAAI,IAAI,MAAM,WAAW,KAAK,MAAM,OAAO,MAAM,QAAQ,KAAK,EAAE,CAAC,CACnF,QAAO;AAET,QAAO;;;AAIT,SAAgB,cAAc,MAAyB,QAAQ,KAAa;AAC1E,QAAO,OAAO,IAAI,oBAAoB,GAAG;;;AAI3C,SAAgB,UAAU,MAAsB;AAC9C,KAAI,CAAC,KAAM,QAAO;AAClB,QAAO,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI;;;;;;;;AAclC,SAAgB,eACd,MACA,eAAuB,mBACC;CACxB,MAAM,OAAO,UAAU,KAAK;AAC5B,KAAI,CAAC,KAAM,QAAO;CAClB,IAAI;AACJ,KAAI;AACF,MAAI,CAAC,WAAW,aAAa,CAAE,QAAO;AACtC,UAAQ,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;SAChD;AACN,SAAO;;CAET,MAAM,OAAO,MAAM;AACnB,KAAI,OAAO,SAAS,YAAY,CAAC,KAAM,QAAO;AAC9C,QAAO;EAAE,IAAI;EAAM;EAAM;;;;;;;AAQ3B,SAAgB,cAAc,QAAsB,MAAuB;AACzE,KAAI,CAAC,KAAM,QAAO;CAClB,MAAM,OAAO,UAAU,KAAK;AAC5B,KAAI,OAAO,SAAS,MAAM,KAAM,QAAO,OAAO,QAAQ,OAAO;AAC7D,QAAO,OAAO,OAAO,KAAK,KAAK,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK;;;;;;AAOlE,SAAgB,SAAS,MAAsB;CAC7C,MAAM,UAAU,eAAe,KAAK;AACpC,KAAI,QAAS,QAAO,QAAQ;AAC5B,QAAO,eAAe,IAAI,UAAU,KAAK;;;AAI3C,SAAgB,WAAW,QAAwD;AACjF,QAAO,OAAO,SAAS,OAAO,IAAI,OAAO,QAAQ,KAAK,KAAK;;;;;;;;;;;;;;;;;;;;ACxE7D,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AAOzB,SAAgB,iBAAiB,QAA8B;CAC7D,MAAM,OAAO,iBAAiB,OAAO;AACrC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO,EAAE,WAAW,EAAE,EAAE;AAC/C,KAAI;AAEF,SAAO,EAAE,WADM,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC,CAC1B,aAAa,EAAE,EAAE;SACtC;AACN,SAAO,EAAE,WAAW,EAAE,EAAE;;;AAI5B,SAAgB,kBAAkB,QAAgB,OAA2B;CAC3E,MAAM,OAAO,iBAAiB,OAAO;CACrC,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACzD,MAAM,MAAM,GAAG,KAAK;AACpB,eAAc,KAAK,KAAK,UAAU,OAAO,MAAM,EAAE,GAAG,MAAM,OAAO;AACjE,YAAW,KAAK,KAAK;;AAGvB,SAAgB,kBACd,OACA,UACA,sBAAY,IAAI,MAAM,EACd;CACR,MAAM,MAAM,MAAM,UAAU;AAC5B,KAAI,CAAC,IAAK,QAAO;CACjB,MAAM,KAAK,KAAK,MAAM,IAAI,GAAG,IAAI,SAAS;AAC1C,QAAO,KAAK,IAAI,KAAK;;AAGvB,SAAgB,YACd,QACA,UACA,SACA,sBAAY,IAAI,MAAM,EAChB;CACN,MAAM,QAAQ,iBAAiB,OAAO;AACtC,OAAM,UAAU,YAAY,IAAI,KAAK,IAAI,SAAS,GAAG,UAAU,IAAO,CAAC,aAAa;AACpF,mBAAkB,QAAQ,MAAM;;AAGlC,SAAgB,cAAc,QAAgB,UAAwB;CACpE,MAAM,QAAQ,iBAAiB,OAAO;AACtC,KAAI,EAAE,YAAY,MAAM,WAAY;AACpC,QAAO,MAAM,UAAU;AACvB,mBAAkB,QAAQ,MAAM;;;;;;;AAQlC,SAAgB,WAAW,UAAyC;AAClE,KAAI,CAAC,SAAS,WAAY,QAAO;AACjC,KAAI;EAMF,MAAM,IALM,aAAa,WAAW,CAAC,MAAM,SAAS,WAAW,EAAE;GAC/D,SAAS;GACT,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;IAAS;GACpC,CAAC,CACY,MAAM,kBAAkB;AACtC,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;SACzD;AACN,SAAO;;;AAIX,SAAgB,mBAAmB,UAAkC;AACnE,QAAO,SAAS,eAAe;;;AAIjC,SAAgB,cAAc,UAAmC;CAC/D,MAAM,OAAO,WAAW,SAAS;AACjC,QAAO,SAAS,QAAQ,QAAQ,mBAAmB,SAAS;;AAc9D,IAAa,kBAAb,cAAqC,mBAAmB;AAExD,SAAS,UAAU,QAAuB,MAA8B;CACtE,MAAM,IAAI,OAAO,UAAU;AAC3B,KAAI,CAAC,EACH,OAAM,IAAI,gBACR,6BAA6B,KAAK,iBAC7B,OAAO,KAAK,OAAO,UAAU,CAAC,KAAK,KAAK,IAAI,SAAS,qGAE3D;AAEH,QAAO;;AAGT,SAAS,eAAe,MAAc,GAAmC;AACvE,KAAI,CAAC,EAAE,QACL,OAAM,IAAI,gBACR,aAAa,KAAK,6DAA6D,OAChF;AAEH,QAAO;;;AAIT,SAAS,gBACP,QACA,OACA,MACA,KACe;CACf,MAAM,IAAI,OAAO,UAAU;AAC3B,KAAI,CAAC,EAAG,QAAO;AACf,KAAI,CAAC,EAAE,QAAS,QAAO;AACvB,KAAI,kBAAkB,OAAO,KAAK,GAAG,EAAG,QAAO;AAC/C,KAAI,cAAc,EAAE,CAAE,QAAO;CAC7B,MAAM,OAAO,iBAAiB,EAAE;AAChC,KAAI,KAAK,gBAAgB,UAAa,OAAO,IAAI,YAC/C,QAAO,aAAa,KAAK,SAAS,IAAI;AAExC,KAAI,KAAK,aAAa,QAAQ;EAC5B,MAAM,OAAO,EAAE,QAAQ,EAAE;EACzB,MAAM,UAAU,IAAI,YAAY,QAAQ,MAAM,CAAC,KAAK,SAAS,EAAiB,CAAC;AAC/E,MAAI,QAAQ,OAAQ,QAAO,iBAAiB,QAAQ,KAAK,KAAK;;AAEhE,QAAO;;;;;;;;AAST,SAAgB,cACd,QACA,QACA,OAAsD,EAAE,EACxC;AAChB,KAAI,KAAK,aACP,QAAO;EACL,cAAc,KAAK;EACnB,UAAU,eAAe,KAAK,cAAc,UAAU,QAAQ,KAAK,aAAa,CAAC;EACjF,YAAY;EACZ,UAAU;EACV,KAAK;EACN;CAIH,MAAM,YAAY,KAAK,YAAY,OAAO,QAAQ,KAAK,aAAa;CACpE,MAAM,MACJ,OAAO,cAAc,YAAY,cAAc,OAAO,YAAY;AAEpE,KAAI,KAAK,aAAa,cAAc,UAAa,OAAO,cAAc,UAAU;EAC9E,MAAM,CAAC,MAAM,SAAS,UAAU,MAAM,IAAI;AAC1C,SAAO;GACL,cAAc;GACd,UAAU,eAAe,MAAM,UAAU,QAAQ,KAAK,CAAC;GACvD,YAAY,SAAS;GACrB,UAAU;GACV,KAAK;GACN;;AAEH,KAAI,KAAK,aAAa,KAAK,UAAU;EACnC,MAAM,WAAW,eAAe,IAAI,UAAU,UAAU,QAAQ,IAAI,SAAS,CAAC;AAC9E,SAAO;GACL,cAAc,IAAI;GAClB;GACA,YAAY;GACZ,UAAU,IAAI,OAAO;GACrB,KAAK;GACN;;AAEH,KAAI,KAAK,aAAa,cAAc,QAGlC;MAAI,CAAE,eAAqC,SAAS,KAAK,UAAU,CACjE,OAAM,IAAI,gBACR,mBAAmB,KAAK,UAAU,uBAAuB,eAAe,KAAK,KAAK,CAAC,YACrE,OAAO,KAAK,OAAO,QAAQ,CAAC,KAAK,KAAK,IAAI,SAAS,0CACrB,KAAK,UAAU,qBAC5D;;AAIL,KAAI,OAAO,WAAW,QAAQ;EAC5B,MAAM,OAAO,OAAO;AACpB,MAAI,CAAC,KACH,OAAM,IAAI,gBACR,iMAGD;AAEH,SAAO;GACL,cAAc;GACd,UAAU,eAAe,MAAM,UAAU,QAAQ,KAAK,CAAC;GACvD,YAAY;GACZ,UAAU;GACV,KAAK;GACN;;CAKH,MAAM,QAAQ,iBAAiB,OAAO;CACtC,MAAM,QAAQ,KAAK,SAAS,OAAO,QAAQ;CAC3C,MAAM,WAAqB,EAAE;AAC7B,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,IAAI,OAAO,UAAU;EAC3B,MAAM,MAAM,gBAAgB,QAAQ,OAAO,MAAM,IAAI;AACrD,MAAI,KAAK;AACP,YAAS,KAAK,GAAG,KAAK,IAAI,MAAM;AAChC;;AAEF,SAAO;GAAE,cAAc;GAAM,UAAU;GAAG,YAAY;GAAM,UAAU,KAAK,OAAO;GAAM,KAAK;GAAQ;;AAEvG,OAAM,IAAI,gBACR,wCAAwC,KAAK,YAAY,eAAe,KAAK,UAAU,KAAK,GAAG,YACjF,MAAM,KAAK,KAAK,CAAC,KAC1B,SAAS,SAAS,eAAe,SAAS,KAAK,KAAK,CAAC,KAAK,+GACgD,KAAK,aAAa,UAAU,WAC5I;;;;;;AAOH,SAAgB,iBACd,QACA,QACA,MACA,sBAAY,IAAI,MAAM,EACP;CACf,MAAM,QAAQ,iBAAiB,OAAO;CACtC,MAAM,QAAQ,OAAO,QAAQ;CAC7B,MAAM,QAAQ,MAAM,QAAQ,KAAK;AACjC,MAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,MAAM,QAAQ,KAAK;EAC7C,MAAM,OAAO,MAAM;EACnB,MAAM,IAAI,OAAO,UAAU;AAC3B,MAAI,CAAC,KAAK,CAAC,EAAE,QAAS;EACtB,MAAM,MAAM,MAAM,UAAU;AAC5B,MAAI,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI,SAAS,CAAE;AAC5C,MAAI,cAAc,EAAE,CAAE;AACtB,SAAO;;AAET,QAAO;;;;;;AAOT,SAAgB,eAAe,YAA6B;AAC1D,QAAO,wCAAwC,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrQjE,MAAa,eAAe;;AAG5B,SAAgB,qBAA6B;AAC3C,QAAO,QAAQ,IAAI,sBAAsB,KACvC,SAAS,EACT,WACA,uBACA,UACA,mBACA,kBACD;;AAUH,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuFnC,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;AAwBxB,MAAa,uBAAuB;;;;;;;;;;;;;;;;;;;;;AAuBpC,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;AA6BrB,SAAS,UAAU,QAAgB,MAA6D;AAC9F,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,OAAO,MAAM,aAAa,CAAC,KAAK,GAAG,KAAK,EAAE,EAAE,OAAO;GAAC;GAAQ;GAAQ;GAAO,EAAE,CAAC;EACpF,IAAI,MAAM;EACV,IAAI,MAAM;AACV,OAAK,OAAO,GAAG,SAAS,MAAe,OAAO,EAAE,SAAS,OAAO,CAAE;AAClE,OAAK,OAAO,GAAG,SAAS,MAAe,OAAO,EAAE,SAAS,OAAO,CAAE;AAClE,OAAK,GAAG,SAAS,OAAO;AACxB,OAAK,GAAG,eAAe,QAAQ;GAAE,QAAQ;GAAK,QAAQ;GAAK,CAAC,CAAC;AAC7D,OAAK,MAAM,MAAM,OAAO;AACxB,OAAK,MAAM,KAAK;GAChB;;AAGJ,SAASA,WAAS,QAAwB;AACxC,KAAI;AACF,SAAO,aAAa,MAAM,CAAC,QAAQ,OAAO,EAAE,EAAE,UAAU,QAAQ,CAAC;SAC3D;AACN,SAAO;;;;AAKX,SAAS,aAA0B;CACjC,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,QAAQA,WAAS,gBAAgB,CAAC,MAAM,KAAK,EAAE;EACxD,MAAM,QAAQ,KAAK,MAAM,CAAC,MAAM,OAAO,EAAE;AACzC,MAAI,MAAM,SAAS,KAAK,MAAM,OAAO,KAAM;AAC3C,MAAI,CAAC,0DAA0D,KAAK,MAAM,GAAG,CAAE;AAC/E,MAAI,MAAM,GAAG,WAAW,OAAO,CAAE,MAAK,IAAI,QAAQ,MAAM,KAAK;;AAE/D,QAAO;;;AAIT,SAAgB,eAAe,KAAsB;CACnD,MAAM,MAAM,IAAI,OAAO,iDAAiD,IAAI,QAAQ,uBAAuB,OAAO,CAAC,KAAK;AACxH,QAAOA,WAAS,WAAW,CAAC,MAAM,KAAK,CAAC,MAAM,OAAO,IAAI,KAAK,GAAG,CAAC;;;AAkBpE,SAAS,QAAQ,GAAoB;CACnC,MAAM,MAAM;AAEZ,UADe,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,IAAI,QAAQ,SAAS,OAAO,KACvE,IAAI,WAAW,OAAO,EAAE,EAAE,MAAM;;;;;;;;;;AAWpD,SAAgB,eAAe,WAA8B;CAC3D,IAAI,YAAwB,EAAE;AAC9B,KAAI;EACF,MAAM,MAAM,aACV,UACA;GAAC;GAAY;GAAiB;GAAQ;GAAM;GAAK;GAAU,EAC3D;GAAE,UAAU;GAAQ,SAAS;GAAQ,CACtC;EACD,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,MAAM,QAAQ,OAAO,CAAE,aAAY;UAChC,GAAG;AACV,SAAO;GAAE,WAAW,EAAE;GAAE,aAAa;GAAM,OAAO,6BAA6B,QAAQ,EAAE;GAAI;;CAE/F,IAAI,cAA6B;AACjC,KAAI;AAMF,gBALY,aACV,UACA;GAAC;GAAY;GAAyB;GAAO;GAAM;GAAK;GAAU,EAClE;GAAE,UAAU;GAAQ,SAAS;GAAQ,CACtC,CACiB,MAAM,IAAI;SACtB;AAGR,QAAO;EAAE;EAAW;EAAa,OAAO;EAAM;;;AAIhD,SAAgB,aAAwB;CACtC,IAAI,MAAqB;AACzB,KAAI;AACF,QAAM,YAAY,KAAK,QAAQ,EAAE,aAAa,CAAC;EAC/C,MAAM,QAAQ,KAAK,KAAK,eAAe;AACvC,eAAa,YAAY;GAAC;GAAU;GAAyB;GAAM,EAAE;GACnE,UAAU;GACV,SAAS;GACV,CAAC;AACF,SAAO,eAAe,MAAM;UACrB,GAAG;AACV,SAAO;GAAE,WAAW,EAAE;GAAE,aAAa;GAAM,OAAO,oBAAoB,QAAQ,EAAE;GAAI;WAC5E;AACR,MAAI,IACF,KAAI;AACF,UAAO,KAAK;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;UACvC;;;;AAQd,SAAS,aAAa,MAAiB,OAAgC;AACrE,QAAO,KAAK,UAAU,MAAM,MAAM,EAAE,SAAS,MAAM,IAAI;;;AAIzD,SAAS,oBAAoB,MAAkC;AAC7D,QAAO,KAAK,cACR,KAAK,UAAU,MAAM,MAAM,EAAE,SAAS,KAAK,YAAY,IAAI,OAC3D;;;AAIN,SAAgB,kBAAmC;AACjD,QAAO,oBAAoB,YAAY,CAAC;;;;;;;AAQ1C,SAAgB,SAAS,MAA0B,UAA0B;CAC3E,MAAM,OAAO,QAAQ,IAAI,YAAY,IAAI;AACzC,KAAI,MAAM,GAAG;EACX,MAAM,SAAS,KAAM,MAAM,GAAG,IAAI;AAClC,MAAI,CAAC,OAAO,MAAM,WAAW,KAAM,MAAM,MAAM,EAAE,CAAC,CAAC,CAAE,QAAO,GAAG,OAAO,GAAG;;AAE3E,QAAO,iBAAiB;;;AAI1B,SAAgB,oBACd,QACA,UACA,OAAwB,YAClB;CACN,MAAM,UAAmC;EACvC,MAAM;EACN,MAAM;EACN,eAAe,SAAS,SAAS,gBAAgB,SAAS;EAC1D,yBAAyB;EAC1B;AAGD,KAAI,QAAQ,QAAQ,aAAa,MAAM,EAAE,OAAO,KAAK,CACnD,SAAQ,iCAAiC,OAAO;CAElD,MAAM,OAAO,oBAAoB;CACjC,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACzD,MAAM,MAAM,GAAG,KAAK;AACpB,eAAc,KAAK,KAAK,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO;AACnF,YAAW,KAAK,KAAK;;AAGvB,IAAI,cAAc;;;;;;;;;;AAWlB,eAAsB,cACpB,UACA,OAAwB,YACP;CACjB,MAAM,QAAQ,MAAM;CACpB,MAAM,SAAS,oBAAoB,MAAM;CACzC,MAAM,OAAO,oBAAoB;CACjC,MAAM,UAAU,WAAW,KAAK;AAChC,KAAI,CAAC,QAAS,qBAAoB,QAAQ,gBAAgB,MAAM;AAChE,KAAI,CAAC,QAAQ;AACX,MAAI,CAAC,aAAa;AAChB,iBAAc;GACd,MAAM,SAAS,MAAM,SAAS;GAC9B,MAAM,OAAO,UACT,WAAW,KAAK,UAChB,SAAS,KAAK,sBAAsB,SAAS;AACjD,WAAQ,OAAO,MACb,yDAAyD,OAAO,MAAM,KAAK,IAC5E;;AAEH,MAAI,MAAM,MAAO,QAAO;;AAE1B,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;AAE3B,MAAI,aAAa,MAAM,EAAE,aAAa,CAAE,QAAO;AAC/C,QAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAI,CAAC;;AAE9C,SAAQ,OAAO,MAAM,4BAA4B,aAAa,wCAAwC;AACtG,QAAO;;AAaT,SAAS,aAAa,MAA2B;AAC/C,KAAI;EACF,MAAM,MAAM,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAClD,SAAO,MAAM,QAAQ,IAAI,GAAI,MAAsB,EAAE;SAC/C;AACN,SAAO,EAAE;;;AAIb,SAAS,aAAa,MAAc,KAAwB;CAC1D,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACzD,MAAM,MAAM,GAAG,KAAK;AACpB,eAAc,KAAK,KAAK,UAAU,KAAK,MAAM,EAAE,EAAE,OAAO;AACxD,YAAW,KAAK,KAAK;;;AAQvB,eAAsB,kBACpB,QACA,QACA,KACA,MACiB;AACjB,KAAI,eAAe,IAAI,CAAE,QAAO,YAAY,IAAI;CAChD,MAAM,MAAM,UAAU,KAAK;CAC3B,MAAM,UAAU,KAAK,SAAS,OAAO,EAAE,GAAG,SAAS,KAAK,CAAC,OAAO;CAChE,MAAM,MAAM,aAAa,QAAQ;CACjC,MAAM,UAAU,MAAM,cAAc,OAAO,KAAK,SAAS;CAIzD,MAAM,MAAM,2BAA2B,KAAK,UAAU,IAAI,CAAC,eAAe,OAAO,KAAK;CAGtF,MAAM,IAAI,MAAM,UAAU,qBAAqB;EAAC;EADlC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,EAAE,QAAQ,CAAC,KAAK,IAAI;EACJ;EAAK;EAAQ,CAAC;CAC1E,MAAM,MAAM,EAAE,OAAO,MAAM;AAC3B,KAAI,QAAQ,cAAc,QAAQ,aAChC,OAAM,IAAI,MACR,QAAQ,aACJ,qEACA,yCACL;CAEH,MAAM,MAAM,IAAI,QAAQ,IAAI;AAC5B,KAAI,MAAM,EACR,OAAM,IAAI,MACR,uCAAuC,EAAE,UAAU,KAAK,MAAM,GAAG,IAAI,GACtE;CAEH,MAAM,OAAO,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,QAAQ,CAAC;CAClE,MAAM,SAAS,IAAI,QAAQ,MAAM,KAAK,IAAI,EAAE,QAAQ,CAAC;AACrD,QAAO,KAAK;EACV,SAAS,IAAI,MAAM,MAAM,EAAE;EAC3B,QAAQ;EACR,yBAAQ,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,KAAK,IAAI,CAAC,MAAM,GAAG,GAAG;EAChE,CAAC;AACF,cAAa,SAAS,OAAO;AAC7B,QAAO,mBAAmB;;;;;;;AAQ5B,eAAe,iBAAiB,MAA+B;CAC7D,MAAM,MAAM,UAAU,KAAK;AAC3B,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI;EACF,MAAM,IAAI,MAAM,UAAU,sBAAsB,CAAC,IAAI,CAAC;EACtD,MAAM,MAAM,EAAE,OAAO,MAAM;AAC3B,MAAI,OAAO,QAAQ,cAAc,QAAQ,aAAc,QAAO,kBAAkB;AAKhF,SAAO,mBAHL,QAAQ,aAAa,6BACnB,QAAQ,eAAe,wBACtB,EAAE,OAAO,MAAM,IAAI,aAAa,MAAM,GAAG,IAAI,CACpB;UACvB,GAAG;AACV,SAAO,8BAA8B,OAAQ,EAAY,WAAW,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC;;;;;;;;;AAUzF,eAAsB,mBAAmB,KAAa,UAAkB,MAA+B;CACrG,MAAM,QAAQ,CAAC,eAAe,IAAI,GAAG,YAAY,IAAI,SAAS,eAAe,MAAM;AACnF,OAAM,KAAK,MAAM,iBAAiB,KAAK,CAAC;CACxC,MAAM,OAAO,oBAAoB;AACjC,KAAI,WAAW,KAAK,EAAE;EACpB,IAAI,OAAO;AACX,MAAI;AAIF,UAHe,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC,CAGvC,WAAW,KAAK,kBAAkB;UAC1C;AAGR,QAAM,KAAK,iBAAiB,KAAK,WAAW;AAC5C,QAAM,KAAK,iBAAiB,OAAO;QAC9B;EACL,MAAM,SAAS,iBAAiB;AAChC,QAAM,KAAK,iBAAiB,KAAK,YAAY;AAC7C,QAAM,KAAK,+BAA+B,SAAS,SAAS,gBAAgB,SAAS,GAAG;;AAE1F,QAAO,MAAM,KAAK,KAAK;;;;;;;AAQzB,eAAsB,eACpB,QACA,SACA,MACA,WACiB;CAEjB,MAAM,MAAM,UAAU,KAAK;CAE3B,MAAM,OADI,MAAM,UAAU,iBAAiB,CAAC,IAAI,CAAC,EACnC,OAAO,MAAM;AAC3B,KAAI,QAAQ,WAAY,OAAM,IAAI,MAAM,mEAAmE;AAC3G,KAAI,QAAQ,aAAc,OAAM,IAAI,MAAM,yCAAyC;AAGnF,KADgB,CAAC,GADD,IAAI,IAAI,IAAI,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,CAAC,CAChD,CAAC,QAAQ,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC,CACnD,SAAS,EAAG,QAAO;AAC/B,KAAI,UAAW,QAAO;CAEtB,MAAM,IAAI,MAAM,UAAU,cAAc,CAAC,KAD7B,0BACsC,CAAC;CACnD,MAAM,OAAO,EAAE,OAAO,MAAM;AAC5B,KAAI,SAAS,SAAU,QAAO;AAC9B,KAAI,SAAS,WAAY,OAAM,IAAI,MAAM,mEAAmE;AAC5G,OAAM,IAAI,MAAM,uCAAuC,EAAE,UAAU,MAAM,MAAM,GAAG,IAAI,GAAG;;;;;;;;;;;;;;ACvkB3F,MAAa,yBAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,KAAK,KAAK;;;;;;;;AAiBZ,SAAgB,kBAAkB,MAAmC;CACnE,MAAM,WAAW,QAAQ,IAAI,MAAM;AACnC,KAAI,CAAC,QAAS,QAAO;CACrB,MAAM,aAAuB,EAAE;CAC/B,MAAM,QAAQ,QAAQ,MAAM,sCAAsC;AAClE,KAAI,MAAO,YAAW,KAAK,MAAM,GAAG;AACpC,KAAI,QAAQ,WAAW,IAAI,CAEzB,YAAW,KAAK,QAAQ;CAG1B,MAAM,QAAQ,QAAQ,QAAQ,IAAI;CAClC,MAAM,OAAO,QAAQ,YAAY,IAAI;AACrC,KAAI,SAAS,KAAK,OAAO,MAAO,YAAW,KAAK,QAAQ,MAAM,OAAO,OAAO,EAAE,CAAC;AAC/E,MAAK,MAAM,QAAQ,WACjB,KAAI;EACF,MAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,MAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,EAAE,IAAI,gBAAgB,EAAE,CAChF,QAAO;SAEH;AAIV,QAAO;;;AAIT,SAAS,gBAAgB,GAAoB;CAC3C,MAAM,IAAI;AACV,QAAO,MAAM,QAAQ,EAAE,QAAQ,IAAI,MAAM,QAAQ,EAAE,OAAO,IAAI,OAAO,EAAE,UAAU;;;;;;AAOnF,SAAgB,aAAa,GAAU,QAAgB,GAAiB,MAAM,IAAc;CAC1F,MAAM,MAAgB,EAAE;CACxB,MAAM,OAAO,QAA+B,MAAM,EAAE,EAAE,SAAS;AAC/D,KAAI,IAAI,EAAE,QAAQ,EAAE;AAClB,MAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,UAAU,GAAG;AAC5C,OAAK,MAAM,MAAM,EAAE,QACjB,KAAI,KAAK,GAAG,OAAO,IAAI,SAAS,GAAG,QAAQ,KAAK,IAAI,CAAC,KAAK,UAAU,GAAG,WAAW,IAAI,GAAG,GAAG;;AAGhG,KAAI,IAAI,EAAE,OAAO,EAAE;AACjB,MAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,SAAS,GAAG;AAC3C,OAAK,MAAM,MAAM,EAAE,QAAS;GAC1B,MAAM,OAAO,GAAG,OAAO,QAAQ,EAAE,OAAO,IAAI,GAAG,EAAE,SAAS,IAAI;GAC9D,MAAM,SAAS,GAAG,SAAS,EAAE,OAAO,MAAM,UAAU,GAAG,QAAQ,GAAG,GAAG,GAAG;AACxE,OAAI,KAAK,GAAG,OAAO,IAAI,KAAK,GAAG,GAAG,QAAQ,MAAM,SAAS;;;AAG7D,KAAI,IAAI,EAAE,SAAS,EAAE;AACnB,MAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,WAAW,GAAG;AAC7C,OAAK,MAAM,OAAO,EAAE,SAAW,KAAI,KAAK,GAAG,OAAO,IAAI,EAAE,OAAO,UAAU,KAAK,IAAI,CAAC,GAAG;;AAExF,KAAI,IAAI,EAAE,KAAK,EAAE;AACf,MAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,OAAO,GAAG;AACzC,OAAK,MAAM,KAAK,EAAE,KAAO,KAAI,KAAK,GAAG,OAAO,IAAI,EAAE,UAAU,UAAU,GAAG,IAAI,CAAC,GAAG;;AAEnF,KAAI,EAAE,MAAO,KAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,QAAQ,CAAC,IAAI,UAAU,EAAE,OAAO,IAAI,GAAG;AACnF,QAAO;;;AAIT,SAAS,SAAS,GAAW,KAAqB;AAChD,KAAI,CAAC,IAAK,QAAO;CACjB,MAAM,IAAI,SAAS,KAAK,EAAE;AAC1B,QAAO,KAAK,CAAC,EAAE,WAAW,KAAK,GAAG,IAAI;;;;;;;;;;;;;;;;;;;ACzFxC,MAAa,cAAc,KAAK,SAAS,EAAE,eAAe;AAE1D,SAAgB,eAAe,aAAa,aAAsC;AAChF,KAAI;AACF,MAAI,CAAC,WAAW,WAAW,CAAE,QAAO,EAAE;EAEtC,MAAM,UADS,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC,CACpC;AACvB,MAAI,OAAO,YAAY,YAAY,YAAY,QAAQ,MAAM,QAAQ,QAAQ,CAAE,QAAO,EAAE;AACxF,SAAO;SACD;AAEN,SAAO,EAAE;;;;;;;AAQb,SAAgB,eACd,OACA,QACA,aAAa,aACH;CACV,MAAM,YAAY,eAAe,WAAW;CAC5C,MAAM,MAAgB,EAAE;AACxB,MAAK,MAAM,OAAO,MAChB,MAAK,MAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,EAAE;AACtE,MAAI,QAAQ,OAAO,SAAS;AAC1B,QAAK,MAAM,UAAU,OAAO,QAAQ,MAClC,KAAI,CAAC,IAAI,SAAS,OAAO,CAAE,KAAI,KAAK,OAAO;AAE7C;;AAEF,MAAI,EAAE,QAAQ,YAAY;GACxB,MAAM,OAAO,OAAO,KAAK,OAAO,QAAQ;AACxC,SAAM,IAAI,mBACR,uBAAuB,KAAK,wBACvB,OAAO,KAAK,UAAU,CAAC,KAAK,KAAK,IAAI,6BACrC,KAAK,SAAS,WAAW,KAAK,KAAK,KAAK,KAAK,KACnD;;AAEH,MAAI,CAAC,IAAI,SAAS,KAAK,CAAE,KAAI,KAAK,KAAK;;AAG3C,QAAO;;;AAIT,SAAgB,iBAAiB,QAAgB,IAAoB;AACnE,QAAO,KAAK,QAAQ,GAAG,GAAG,WAAW;;;;;;AAOvC,SAAgB,eACd,QACA,IACA,OACA,aAAa,aACL;CACR,MAAM,YAAY,eAAe,WAAW;CAC5C,MAAM,UAAU,MAAM,QAAQ,MAAM,EAAE,KAAK,WAAW;AACtD,KAAI,QAAQ,OACV,OAAM,IAAI,mBACR,0BAA0B,QAAQ,KAAK,KAAK,CAAC,eACxC,OAAO,KAAK,UAAU,CAAC,KAAK,KAAK,IAAI,6BAC3C;CAEH,MAAM,UAAmC,EAAE;AAC3C,MAAK,MAAM,KAAK,MAAO,SAAQ,KAAK,UAAU;CAC9C,MAAM,OAAO,iBAAiB,QAAQ,GAAG;AACzC,KAAI,CAAC,WAAW,OAAO,CAAE,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;AAC/D,eAAc,MAAM,KAAK,UAAU,EAAE,YAAY,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO;AACpF,QAAO;;;AAIT,SAAgB,YAAY,QAAwC,aAAa,aAAuB;CACtG,MAAM,UAAU,OAAO,KAAK,eAAe,WAAW,CAAC;CACvD,MAAM,QAAkB,EAAE;AAC1B,KAAI,QAAQ,QAAQ;AAClB,QAAM,KAAK,4BAA4B;AACvC,OAAK,MAAM,KAAK,QAAS,OAAM,KAAK,KAAK,IAAI;OAE7C,OAAM,KAAK,2CAA2C;CAExD,MAAM,OAAO,OAAO,QAAQ,OAAO,QAAQ;AAC3C,KAAI,KAAK,QAAQ;AACf,QAAM,KAAK,0BAA0B;AACrC,OAAK,MAAM,CAAC,MAAM,YAAY,KAAM,OAAM,KAAK,KAAK,KAAK,KAAK,QAAQ,KAAK,KAAK,GAAG;;AAErF,OAAM,KAAK,2DAA2D;AACtE,QAAO;;;;;;;;;;;;;;;;AClGT,SAAgB,mBAAmB,QAAgB,IAAoB;AACrE,QAAO,KAAK,QAAQ,GAAG,GAAG,OAAO;;;;;;;AAQnC,SAAgB,qBACd,QACA,IACA,QAC2B;CAC3B,MAAM,OAAO,mBAAmB,QAAQ,GAAG;AAC3C,KAAI;AACF,MAAI,WAAW,KAAK,CAAE,YAAW,KAAK;SAChC;CAGR,MAAM,SAAS,cAAc,WAAmB;EAC9C,IAAI,MAAM;AACV,SAAO,GAAG,SAAS,UAAkB;AACnC,UAAO,MAAM,SAAS,OAAO;GAC7B,IAAI;AACJ,WAAQ,KAAK,IAAI,QAAQ,KAAK,KAAK,GAAG;IACpC,MAAM,OAAO,IAAI,MAAM,GAAG,GAAG,CAAC,QAAQ,OAAO,GAAG;AAChD,UAAM,IAAI,MAAM,KAAK,EAAE;AACvB,QAAI,KAAK,MAAM,CAAE,QAAO,KAAK;AAC7B,WAAO,MAAM,OAAO;;IAEtB;GACF;AACF,QAAO,OAAO,KAAK;AACnB,QAAO,GAAG,eAAe;AACvB,MAAI;AACF,OAAI,WAAW,KAAK,CAAE,YAAW,KAAK;UAChC;GAGR;AACF,QAAO;;;;;;AAOT,SAAgB,YAAY,QAAgB,IAAY,MAAc,YAAY,KAAuB;CACvG,MAAM,SAAS,WAAW,QAAQ,GAAG;AACrC,KAAI,CAAC,OACH,QAAO,QAAQ,uBAAO,IAAI,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAE7D,KAAI,OAAO,UAAU,aAAa,CAAC,MAAM,OAAO,IAAI,CAClD,QAAO,QAAQ,uBACb,IAAI,MACF,UAAU,GAAG,0BAA0B,OAAO,MAAM,kDACH,GAAG,WACrD,CACF;CAEH,MAAM,OAAO,mBAAmB,QAAQ,GAAG;AAC3C,KAAI,CAAC,WAAW,KAAK,CACnB,QAAO,QAAQ,uBACb,IAAI,MAAM,UAAU,GAAG,2BAA2B,KAAK,qCAAqC,CAC7F;AAEH,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,OAAO,QAAQ,KAAK;EAC1B,MAAM,QAAQ,MAAa;AACzB,QAAK,SAAS;AACd,0BAAO,IAAI,MAAM,yBAAyB,GAAG,IAAI,EAAE,UAAU,CAAC;;AAEhE,OAAK,WAAW,iBAAiB,qBAAK,IAAI,MAAM,UAAU,CAAC,CAAC;AAC5D,OAAK,KAAK,UAAU,MAAa,KAAK,EAAE,CAAC;AACzC,OAAK,KAAK,iBAAiB;AACzB,QAAK,MAAM,KAAK,QAAQ,OAAO,IAAI,GAAG,KAAK;IAC3C;AACF,OAAK,KAAK,cAAc;AACtB,QAAK,KAAK;AACV,WAAQ,KAAK;IACb;GACF;;;;;;;;;;;;;;;;;;ACrEJ,MAAa,qBAAqB;AAClC,MAAM,OAAO;AAOb,SAAgB,aAAa,QAAwB;AACnD,QAAO,KAAK,QAAQ,YAAY;;;AA+KlC,SAAgB,eAAe,OAAO,oBAAoB,YAAY,KAAuB;AAC3F,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,OAAO,QAAQ;GAAE;GAAM,MAAM;GAAM,CAAC;EAC1C,MAAM,QAAQ,OAAgB;AAC5B,QAAK,oBAAoB;AACzB,QAAK,SAAS;AACd,WAAQ,GAAG;;AAEb,OAAK,WAAW,iBAAiB,KAAK,MAAM,CAAC;AAC7C,OAAK,KAAK,iBAAiB,KAAK,KAAK,CAAC;AACtC,OAAK,KAAK,eAAe,KAAK,MAAM,CAAC;GACrC;;;;;;AAOJ,SAAgB,sBAAqC;CACnD,IAAI,MAAM,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AACjD,MAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,OAAK,MAAM,QAAQ;GACjB,KAAK,KAAK,mBAAmB;GAC7B,KAAK,KAAK,SAAS,mBAAmB;GACtC,KAAK,KAAK,QAAQ,SAAS,mBAAmB;GAC/C,CACC,KAAI,WAAW,KAAK,CAAE,QAAO;EAE/B,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAER,QAAO;;;;;;AAOT,eAAsB,mBAAmB,MAAc,QAAiC;AACtF,KAAI,MAAM,eAAe,KAAK,CAAE,QAAO,UAAU,KAAK,GAAG;CACzD,MAAM,SAAS,qBAAqB;AACpC,KAAI,CAAC,OACH,OAAM,IAAI,MACR,uIACuE,KAAK,GAC7E;AAEH,KAAI,CAAC,WAAW,OAAO,CAAE,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;CAC/D,MAAM,QAAQ,MAAM,QAAQ,UAAU;EAAC;EAAQ;EAAU,OAAO,KAAK;EAAC,EAAE;EACtE,UAAU;EACV,OAAO;EACR,CAAC;AACF,OAAM,OAAO;AACb,eAAc,aAAa,OAAO,EAAE,GAAG,MAAM,IAAI,KAAK,OAAO;AAC7D,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,MAAI,MAAM,eAAe,KAAK,CAAE,QAAO,UAAU,KAAK,GAAG;AACzD,QAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAI,CAAC;;AAE9C,OAAM,IAAI,MAAM,gDAAgD,KAAK,QAAQ,aAAa,OAAO,CAAC,GAAG;;;AAIvG,SAAgB,UAAU,QAAwB;CAChD,MAAM,OAAO,aAAa,OAAO;AACjC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;CAC9B,MAAM,MAAM,SAAS,aAAa,MAAM,OAAO,CAAC,MAAM,EAAE,GAAG;AAC3D,YAAW,KAAK;AAChB,KAAI,CAAC,OAAO,SAAS,IAAI,CAAE,QAAO;AAClC,KAAI;AACF,UAAQ,KAAK,KAAK,UAAU;SACtB;AACN,SAAO,aAAa,IAAI;;AAE1B,QAAO,aAAa,IAAI;;;;;;;;;;;;;;;;;;;;;AC3Q1B,SAAgB,eAAe,QAAgB,OAAqC;AAClF,QAAO;EACL;EACA;EACA;EACA,GAAI,QAAQ,CAAC,MAAM,MAAM,GAAG,EAAE;EAC9B;EACA;EACD;;;AAIH,SAAgB,cAAc,UAA6C;CACzE,MAAM,MAAyB,EAAE,GAAG,QAAQ,KAAK;AACjD,QAAO,IAAI;AACX,QAAO,IAAI;AACX,QAAO,IAAI;AACX,QAAO,IAAI;AACX,QAAO,IAAI;AACX,QAAO,IAAI;CAGX,MAAM,UAAU,gBAAgB,SAAS;AACzC,KAAI,WAAW,WAAW,QAAQ,CAChC,KAAI,iBAAiBC,aAAQ,SAAS,OAAO,CAAC,MAAM;AAEtD,KAAI,SAAS,YAAa,KAAI,kBAAkB,SAAS;AACzD,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,SAAS,IAAI,CAAE,KAAI,KAAK;AAC5D,KAAI,aAAa;AACjB,QAAO;;;AAIT,SAAgB,kBAAkB,MAA0B;CAC1D,MAAM,UAAoB,EAAE;AAC5B,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,IAAI,KAAK;AACf,MAAI,MAAM,oBAAoB,MAAM,uBAAuB,MAAM,kBAAkB,MAAM,SAAS;AAChG,WAAQ,KAAK,EAAE;AACf,OAAI,IAAI,IAAI,KAAK,UAAU,CAAC,KAAK,IAAI,GAAG,WAAW,IAAI,CAAE;aAChD,EAAE,WAAW,kBAAkB,IAAI,EAAE,WAAW,gBAAgB,CACzE,SAAQ,KAAK,EAAE,MAAM,IAAI,CAAC,GAAG;;AAGjC,QAAO;;AAmBT,MAAa,0BAA4C;CACvD,QAAQ,EAAE;CACV,OAAO;CACP,OAAO;CACP,MAAM;CACN,SAAS;CACT,eAAe;CACf,WAAW;CACX,UAAU;CACX;;;;;AAMD,SAAgB,cAAc,MAAe,GAA2B;AACtE,KAAI,OAAO,SAAS,YAAY,SAAS,KAAM;CAC/C,MAAM,IAAI;CACV,MAAM,OAAO,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AAEnD,KAAI,SAAS,oBAAoB,OAAO,EAAE,cAAc,UAAU;AAChE,IAAE,WAAW,EAAE;AACf;;AAEF,KAAI,SAAS,oBAAoB,OAAO,EAAE,SAAS,YAAY,EAAE,SAAS,MAAM;EAC9E,MAAM,OAAO,EAAE;EACf,MAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AACzD,MAAI,SAAS,mBAAmB,OAAO,KAAK,SAAS,UAAU;AAC7D,KAAE,SAAS;AACX,KAAE,OAAO,SAAS,KAAK,KAAK,MAAM,GAAG,GAAG;AACxC,KAAE,YAAY,KAAK;AACnB,KAAE,OAAO,KAAK;IACZ,MAAM;IACN,SAAS,EAAE,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,KAAK;KAAM,CAAC,EAAE;IAC1D,CAAC;aACO,SAAS,qBAAqB;AACvC,KAAE,SAAS;GACX,MAAM,MAAM,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;GAC9D,MAAM,KAAK,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AACjE,KAAE,OAAO,SAAS,IAAI,MAAM,GAAG,GAAG;AAClC,KAAE,OAAO,KAAK;IACZ,MAAM;IACN,SAAS,EAAE,SAAS,CAAC;KAAE,MAAM;KAAY,MAAM;KAAQ,OAAO,EAAE,SAAS,KAAK;KAAE,CAAC,EAAE;IACpF,CAAC;AACF,KAAE,OAAO,KAAK;IACZ,MAAM;IACN,SAAS,EACP,SAAS,CACP;KACE,MAAM;KACN,aAAa;KACb,UAAU,OAAO;KACjB,SAAS,KAAK,qBAAqB;KACpC,CACF,EACF;IACF,CAAC;aACO,SAAS,eAAe;AACjC,KAAE,SAAS;GAEX,MAAM,SADU,MAAM,QAAQ,KAAK,QAAQ,GAAG,KAAK,UAAU,EAAE,EAE5D,KAAK,MAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,OAAQ,EAAyB,SAAS,WAAY,EAAuB,OAAO,IAAK,CAC5I,KAAK,KAAK;AACb,KAAE,OAAO,UAAU,MAAM,MAAM,GAAG,GAAG;AACrC,KAAE,OAAO,KAAK;IACZ,MAAM;IACN,SAAS,EAAE,SAAS,CAAC;KAAE,MAAM;KAAY,MAAM;KAAS,OAAO,EAAE,WAAW,OAAO;KAAE,CAAC,EAAE;IACzF,CAAC;aACO,SAAS,iBAAiB;AACnC,KAAE,SAAS;AACX,KAAE,OAAO,QAAQ,OAAO,KAAK,QAAQ,IAAI;;AAE3C;;AAEF,KAAI,SAAS,kBAAkB;EAC7B,MAAM,QAAS,OAAO,EAAE,UAAU,YAAY,EAAE,UAAU,OAAO,EAAE,QAAQ,EAAE;EAK7E,MAAM,UACH,MAAM,gBAAgB,MAAM,MAAM,uBAAuB,MAAM,MAAM,iBAAiB;AACzF,MAAI,SAAS,EAAG,GAAE,gBAAgB;AAClC;;AAEF,KAAI,SAAS,iBAAiB,SAAS,SAAS;AAC9C,IAAE,UAAU;EACZ,MAAM,MAAM,OAAO,EAAE,UAAU,YAAY,EAAE,UAAU,OAAO,EAAE,QAAQ,EAAE;AAC1E,IAAE,OAAO,OAAQ,IAA8B,WAAW,EAAE,WAAW,oBAAoB;;;;AAK/F,SAAgB,eAAe,MAA8B;CAC3D,MAAM,IAAI,KAAK,MAAM;AACrB,KAAI,CAAC,EAAE,WAAW,IAAI,CAAE,QAAO;AAC/B,KAAI;AACF,SAAO,KAAK,MAAM,EAAE;SACd;AACN,SAAO;;;;AASX,SAAgB,iBAA0B;AACxC,KAAI;AACF,eAAa,SAAS,CAAC,YAAY,EAAE;GAAE,SAAS;GAAM,OAAO;GAAU,CAAC;AACxE,SAAO;SACD;AACN,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACnGX,SAAgB,YACd,UACA,UACA,UACmB;CACnB,MAAM,MAAyB,EAAE,GAAG,QAAQ,KAAK;AACjD,QAAO,IAAI;CAEX,IAAI,QAAQ;AACZ,KAAI,SAGF,KAAI,qBAAqB;MACpB;EACL,MAAM,UAAU,gBAAgB,SAAS;AACzC,MAAI,SAAS;AACX,OAAI;AACF,YAAQC,aAAQ,SAAS,OAAO,CAAC,MAAM;WACjC;AAEN,UAAM,IAAI,MAAM,0BAA0B,UAAU;;AAEtD,OAAI,CAAC,MAAO,OAAM,IAAI,MAAM,sBAAsB,UAAU;;AAE9D,MAAI,qBAAqB,SAAS;;AAGpC,KAAI,uBAAuB;AAC3B,KAAI,gCAAgC,SAAS,OAAO,QAAQ,SAAS,OAAO;AAC5E,KAAI,iCAAiC,SAAS,OAAO;AACrD,KAAI,+BAA+B,SAAS,OAAO;AACnD,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,SAAS,IAAI,CAAE,KAAI,KAAK;AAC5D,KAAI,2CAA2C;AAC/C,KAAI,SACF,KAAI,aAAa;KAEjB,KAAI,qBAAqB;AAE3B,QAAO;;;AAIT,SAAgB,kBAAkB,QAAwB;CACxD,MAAM,OAAO,gBAAgB,OAAO;AACpC,KAAI,CAAC,WAAW,KAAK,EAAE;AACrB,YAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;AACtC,gBAAc,MAAM,4BAA0B,OAAO;;AAEvD,QAAO;;;AAIT,SAAgB,iBAAiB,MAAsB;AACrD,QAAO,KAAK,UAAU;EAAE,MAAM;EAAQ,SAAS;GAAE,MAAM;GAAQ,SAAS;GAAM;EAAE,CAAC;;;;;;;;AASnF,SAAgB,SAAS,oBAAI,IAAI,MAAM,EAAE,SAAS,CAAC,EAAE,mBAAmB,EAAU;CAChF,MAAM,IAAI,IAAI,KAAK,EAAE,SAAS,GAAG,SAAS,IAAO;CACjD,MAAM,OAAO,SAAS,IAAI,MAAM;CAChC,MAAM,MAAM,KAAK,IAAI,OAAO;CAC5B,MAAM,KAAK,OAAO,KAAK,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,GAAG,IAAI;CACxD,MAAM,KAAK,OAAO,MAAM,GAAG,CAAC,SAAS,GAAG,IAAI;AAC5C,QAAO,GAAG,EAAE,aAAa,CAAC,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,GAAG;;;AAgCxD,SAAgB,mBAAmB,GAA0C;AAC3E,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,KACH,EAAE,gBAAgB,MAClB,EAAE,2BAA2B,MAC7B,EAAE,+BAA+B,MACjC,EAAE,iBAAiB;AACtB,QAAO,IAAI,IAAI,IAAI;;;AAIrB,SAAgB,kBAAkB,GAA+B;AAC/D,KAAI,OAAO,EAAE,mBAAmB,YAAY,EAAE,iBAAiB,EAAG,QAAO,EAAE;AAC3E,KAAI,EAAE,cAAc,OAAO,EAAE,WAAW,mBAAmB,YAAY,EAAE,WAAW,iBAAiB,EACnG,QAAO,EAAE,WAAW;AAEtB,QAAO;;;;;;AAOT,eAAsB,UAAU,MAAmC;CACjE,MAAM,EAAE,KAAK,MAAM,SAAS,WAAW,oBAAoB;AAE3D,KAAI,CAAC,OAAO,QACV,OAAM,IAAI,MACR,mIAED;CAEH,MAAM,SAAS,cAAc,OAAO;AACpC,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;CAEtC,MAAM,SAAS,cAAc,QAAQ,QAAQ;EAC3C,cAAc,KAAK;EACnB,WAAW,KAAK;EACjB,CAAC;AACF,wBAAuB,OAAO,cAAc,OAAO,SAAS;CAE5D,MAAM,SAAS,gBAAgB,KAAK,WAAW;CAC/C,MAAM,QACJ,KAAK,SACL,UAAU,OAAO,UAAU,eAAe,GAAG;CAE/C,MAAM,QACJ,KAAK,cACJ,OAAO,eAAe,SACnB,OAAO,SAAS,OAAO,QAAQ,OAAO,SAAS,OAAO,UACtD,OAAO,SAAS,OAAO;AAE7B,KAAI;AACF,MAAI,OAAO,SAAS,WAAW,QAC7B,QAAO,MAAM,gBAAgB;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA,YAAY,KAAK;GACjB,QAAQ,KAAK,UAAU;GACvB,KAAK,KAAK;GACV,QAAQ,KAAK;GACb,OAAO,KAAK;GACZ,OAAO,KAAK;GACZ,eAAe,KAAK;GACrB,CAAC;AAEJ,SAAO,MAAM,WAAW;GACtB;GACA;GACA;GACA;GACA;GACA;GACA,YAAY,KAAK;GACjB,QAAQ,KAAK,UAAU;GACvB,SAAS,KAAK;GACd,KAAK,KAAK;GACV,QAAQ,KAAK;GACb,OAAO,KAAK;GACZ,OAAO,KAAK;GACZ,eAAe,KAAK;GACpB,UAAU,KAAK,aAAa;GAC7B,CAAC;UACK,GAAG;AACV,MAAI,aAAa,SAAS,EAAE,QAAQ,WAAW,WAAW,CACxD,OAAM,IAAI,MACR,aAAa,OAAO,aAAa,KAAK,EAAE,8EAEzC;AAEH,QAAM;;;AAsBV,eAAe,WAAW,GAAiC;CACzD,MAAM,EAAE,QAAQ,QAAQ,QAAQ,OAAO,OAAO,QAAQ,WAAW;CACjE,MAAM,WAAW,OAAO;CAGxB,IAAI;AACJ,KAAI,OAAO,SAAS,aAAa,SAE/B,YAAW,GADE,MAAM,mBAAmB,oBAAoB,OAAO,CAC9C,GAAG,OAAO;CAE/B,MAAM,MAAM,YAAY,OAAO,UAAU,UAAU,SAAS;CAE5D,MAAM,MAAM,aAAa;CACzB,MAAM,MAAM,EAAE,OAAO,QAAQ,KAAK;CAClC,MAAM,OAAO,QAAQ,IAAI,oBAAoB;CAC7C,MAAM,UAAU,eAAe,KAAK;CAEpC,MAAM,SAAuB;EAC3B,IAAI;EACJ,KAAK,QAAQ;EACb;EACA;EACA;EACA,UAAU,OAAO;EACjB;EACA,OAAO;EACP,SAAS,UAAU;EACnB,SAAS,UAAU;EACnB,OAAO;EACP,OAAO;EACP,MAAM,WAAW,aAAa;EAC9B,IAAI;EACJ,MAAM;EACN,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;EAC9B,eAAe,sBAAsB,OAAO,SAAS;EACrD,GAAI,EAAE,SAAS;GAAE,QAAQ,EAAE;GAAQ,OAAO,EAAE;GAAO,GAAG,EAAE;EACzD;AACD,YAAW,QAAQ,OAAO;AAC1B,GAAE,gBAAgB,IAAI;CACtB,MAAM,SAAS,WAAW,OAAO;AACjC,cAAa,QAAQ,gBAAgB;EACnC,IAAI;EACJ,UAAU,OAAO;EACjB,MAAM,WAAW,aAAa;EAC9B;EACA;EACA;EACD,CAAC;AAGF,KAAI,YAAY,CAAC,UAAU,OAAO,KAAK,WAAW,QAAQ,QAAQ,IAAI,wBAAwB,IAC5F,CAAK,kBAAkB,QAAQ,QAAQ,KAAK,KAAK,CAAC,YAAY,GAAG;CAInE,IAAI,UAAoB,EAAE;AAC1B,KAAI,YAAY,CAAC,OAAO,iBAAiB;EACvC,MAAM,SAAS;GACb,GAAI,EAAE,UAAU,CAAC,EAAE,QAAQ,GAAG,EAAE;GAChC,GAAG,OAAO;GACV,GAAI,OAAO,YAAY,EAAE;GAC1B;AACD,MAAI,OAAO,OAET,WAAU;GAAC;GAAuB;GAAgB,eAAe,QAAQ,KAD3D,eAAe,QAAQ,OAAO,CACwC;GAAC;MAErF,WAAU;GAAC;GAAuB;GAAgB,kBAAkB,OAAO;GAAC;;CAMhF,MAAM,WAAW,WAAW,kBAAkB,OAAO,KAAK,GAAG,OAAO;CACpE,MAAM,MAAgB,CAAC,SAAS;AAChC,KAAI,CAAC,OAAO,YAAa,KAAI,KAAK,WAAW,MAAM;AACnD,KAAI,KAAK,GAAG,SAAS,GAAG,SAAS;AACjC,KAAI,UAAU;AACZ,MAAI,KAAK,mBAAmB,eAAe,aAAa,kBAAkB,cAAc;AACxF,MAAI,CAAC,OAAO,mBAAoB,KAAI,KAAK,0BAA0B,uBAAuB;;CAG5F,MAAM,KAAK,KAAK,KAAK;CACrB,MAAM,OAAO,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,EAAE;EACvC;EACA;EACA,OAAO,WAAW;GAAC;GAAQ;GAAQ;GAAU,GAAG;EACjD,CAAC;CAGF,IAAI,mBAAmB;CACvB,IAAI,aAAoC;CACxC,MAAM,sBAAsB;AAC1B,MAAI,WAAY,cAAa,WAAW;AACxC,eAAa,iBAAiB;AAC5B,OAAI,qBAAqB,EACvB,KAAI;AACF,SAAK,OAAO,KAAK;WACX;KAIT,IAAM;;CAGX,IAAI,WAA0B;CAC9B,MAAM,cAAc,QAAuC;AACzD,MAAI,aAAa,KAAM;AACvB,MAAI;AACF,aAAU,UAAU,KAAK,UAAU;IAAE,GAAG;IAAK,KAAK,UAAU;IAAE,CAAC,GAAG,KAAK;UACjE;;CAKV,MAAM,iBAAiB,WACnB,qBAAqB,QAAQ,MAAM,SAAS;AAC1C,sBAAoB;AACpB,MAAI,YAAY;AACd,gBAAa,WAAW;AACxB,gBAAa;;AAEf,aAAW;GAAE,MAAM;GAAY;GAAM,CAAC;AACtC,MAAI;AACF,QAAK,OAAO,MAAM,iBAAiB,KAAK,GAAG,KAAK;UAC1C;GAGR,GACF;CAEJ,IAAI,SAAS;CACb,MAAM,gBAAgB;AACpB,MAAI,WAAY,cAAa,WAAW;AACxC,kBAAgB,OAAO;;CAEzB,MAAM,YAAY,QAAgB;AAChC,WAAS;AACT,SAAO,QAAQ;AACf,SAAO,KAAK;AACZ,SAAO,OAAO,KAAK,OAAO,KAAK,KAAK,GAAG,MAAM,IAAK;AAClD,SAAO,OAAO,oBAAoB;AAClC,aAAW,QAAQ,OAAO;AAC1B,eAAa,QAAQ,cAAc;GACjC,IAAI;GACJ,UAAU,OAAO;GACjB,MAAM;GACN;GACA,IAAI;GACJ,MAAM,OAAO;GACb,QAAQ;GACR;GACD,CAAC;AACF,WAAS;AACT,MAAI;AACF,QAAK,MAAM;UACL;AAGR,UAAQ,KAAK,IAAI;;AAEnB,SAAQ,KAAK,iBAAiB,SAAS,UAAU,CAAC;AAClD,SAAQ,KAAK,gBAAgB,SAAS,SAAS,CAAC;CAIhD,MAAM,MAA8E;EAClF,aAAa;EACb,cAAc;EACf;AAED,KAAI,UAAU;AAEZ,MAAI,OAAO,WAAW,KACpB,KAAI;AACF,QAAK,MAAO,MAAM,iBAAiB,OAAO,OAAO,GAAG,KAAK;UACnD;AAIV,aAAW,SAAS,WAAW,QAAQ,IAAI,EAAE,IAAI;AAEjD,EADW,gBAAgB,EAAE,OAAO,KAAK,QAAS,CAAC,CAChD,GAAG,SAAS,SAAS;AACtB,OAAI,OAAO,iBAAiB,cAC1B,SAAQ,OAAO,MAAM,OAAO,KAAK;AAEnC,OAAI,CAAC,KAAK,WAAW,IAAI,CAAE;GAC3B,IAAI;AACJ,OAAI;AACF,QAAI,KAAK,MAAM,KAAK;WACd;AACN;;AAEF,cAAW,EAA6B;AACxC,OAAI,EAAE,SAAS,YAAY,EAAE,YAAY,QAAQ;AAC/C,QAAI,EAAE,WAAY,QAAO,gBAAgB,EAAE;IAC3C,MAAM,KAAK,kBAAkB,EAAE;AAC/B,QAAI,GAAI,QAAO,gBAAgB;AAC/B,eAAW,QAAQ,OAAO;cACjB,EAAE,SAAS,aAAa;AACjC,WAAO,SAAS;IAChB,MAAM,SAAS,mBAAmB,EAAE,SAAS,MAAM;AACnD,QAAI,OAAQ,QAAO,gBAAgB;AACnC,SAAK,MAAM,SAAS,EAAE,SAAS,WAAW,EAAE,CAC1C,KAAI,MAAM,SAAS,YAAY;AAC7B,YAAO,SAAS;AAChB,YAAO,OAAO,aAAa,MAAM,QAAQ,KAAK,MAAM,MAAM;eACjD,MAAM,SAAS,WAAW,MAAM,QAAQ,IAAI,MAAM,CAC3D,QAAO,OAAO,WAAW,UAAU,MAAM,MAAM,GAAG;AAGtD,eAAW,QAAQ,OAAO;cACjB,EAAE,SAAS,UAAU;IAC9B,MAAM,SAAS,mBAAmB,EAAE,MAAM;AAC1C,QAAI,OAAQ,QAAO,gBAAgB;IACnC,MAAM,SAAS,kBAAkB,EAAE,UAAU,GAAG;AAChD,QAAI,QAAQ,MAAO,QAAO,OAAO,UAAU,OAAO,OAAO,GAAG;aACnD,EAAE,OAAQ,QAAO,OAAO,UAAU,EAAE,QAAQ,GAAG;AACxD,eAAW,QAAQ,OAAO;AAC1B,uBAAmB;AACnB,mBAAe;AACf,QAAI,cAAc;AAClB,QAAI,eAAe;;IAErB;;CAGJ,MAAM,KAAK,MAAM,IAAI,SAAiB,SAAS,WAAW;AACxD,OAAK,GAAG,SAAS,OAAO;AACxB,OAAK,GAAG,UAAU,SAAS,QAAQ,SAAS,SAAS,MAAM,GAAG,CAAC;GAC/D;AACF,KAAI,aAAa,KAAM,WAAU,SAAS;AAC1C,UAAS;CAET,MAAM,OAAO,KAAK,OAAO,KAAK,KAAK,GAAG,MAAM,IAAK;CACjD,MAAM,cAAc,IAAI;CACxB,MAAM,KAAK,OAAO,KAAK,gBAAgB,QAAQ,CAAC,YAAY;AAC5D,QAAO,QAAQ,KAAK,SAAS;AAC7B,QAAO,KAAK;AACZ,QAAO,OAAO;AACd,KAAI,YAAa,QAAO,OAAO,UAAU,YAAY,UAAU,IAAI,GAAG;AACtE,KAAI,IAAI,cAAc,MAAO,QAAO,OAAO,UAAU,IAAI,aAAa,OAAO,GAAG;AAChF,YAAW,QAAQ,OAAO;AAC1B,cAAa,QAAQ,cAAc;EACjC,IAAI;EACJ,UAAU,OAAO;EACjB,MAAM,WAAW,aAAa;EAC9B;EACA;EACA;EACA,OAAO,OAAO;EACd,OAAO,OAAO;EACd;EACD,CAAC;AAEF,KAAI,YAAY,CAAC,EAAE,MAAO,aAAY,OAAO,cAAc,aAAa,IAAI,QAAQ,KAAK,IAAI,aAAa;CAG1G,MAAM,aAAa,aAAa,UAAU;AAC1C,KACE,CAAC,MACD,YACA,EAAE,OAAO,QAAQ,UACjB,OAAO,QAAQ,gBACf,OAAO,SAAS,KAChB,OAAO,UAAU,KACjB,eAAe,WAAW,EAC1B;AACA,cAAY,QAAQ,OAAO,cAAc,OAAO,QAAQ,gBAAgB;EACxE,MAAM,OAAO,iBAAiB,QAAQ,QAAQ,OAAO,aAAa;AAClE,MAAI,QAAQ,EAAE,WAAW,OAAO,QAAQ,MAAM,QAAQ;AACpD,gBAAa,QAAQ,kBAAkB;IACrC,MAAM,OAAO;IACb,IAAI;IACJ,QAAQ;IACT,CAAC;AACF,UAAO,UAAU;IACf,cAAc;IACd;IACA,QAAQ,EAAE;IACV,SAAS,EAAE;IACX,KAAK,EAAE;IACP,QAAQ,EAAE;IACV,OAAO,EAAE;IACT,YAAY,EAAE;IACd,eAAe,EAAE;IACjB,WAAW,EAAE,WAAW;IACzB,CAAC;;;AAIN,QAAO,OAAO,IAAI,KAAK,KAAK,IAAI;;AASlC,eAAe,gBAAgB,GAA+B;CAC5D,MAAM,EAAE,QAAQ,QAAQ,QAAQ,OAAO,OAAO,QAAQ,WAAW;AACjE,KAAI,CAAC,OAAO,YAAY,OAAO,WAAW,KACxC,OAAM,IAAI,MACR,aAAa,OAAO,aAAa,gFAElC;CAEH,MAAM,MAAM,cAAc,OAAO,SAAS;CAC1C,MAAM,MAAM,aAAa;CACzB,MAAM,MAAM,EAAE,OAAO,QAAQ,KAAK;CAClC,MAAM,OAAO,QAAQ,IAAI,oBAAoB;CAC7C,MAAM,UAAU,eAAe,KAAK;CAEpC,MAAM,SAAuB;EAC3B,IAAI;EACJ,KAAK,QAAQ;EACb;EACA;EACA;EACA,UAAU,OAAO;EACjB;EACA,OAAO;EACP,SAAS,UAAU;EACnB,SAAS,UAAU;EACnB,OAAO;EACP,OAAO;EACP,MAAM;EACN,IAAI;EACJ,MAAM;EACN,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;EAC9B,eAAe,sBAAsB,OAAO,SAAS;EACrD,GAAI,EAAE,SAAS;GAAE,QAAQ,EAAE;GAAQ,OAAO,EAAE;GAAO,GAAG,EAAE;EACzD;AACD,YAAW,QAAQ,OAAO;AAC1B,GAAE,gBAAgB,IAAI;CACtB,MAAM,SAAS,WAAW,OAAO;AACjC,cAAa,QAAQ,gBAAgB;EACnC,IAAI;EACJ,UAAU,OAAO;EACjB,MAAM;EACN,QAAQ;EACR;EACA;EACA;EACD,CAAC;CACF,MAAM,UAAU,kBAAkB,EAAE,WAAW;AAC/C,KAAI,QAAQ,OACV,cAAa,QAAQ,eAAe;EAClC,IAAI;EACJ,MAAM,sBAAsB,QAAQ,KAAK,KAAK;EAC/C,CAAC;AAGJ,KAAI,CAAC,UAAU,OAAO,KAAK,WAAW,QAAQ,QAAQ,IAAI,wBAAwB,IAChF,CAAK,kBAAkB,QAAQ,QAAQ,KAAK,KAAK,CAAC,YAAY,GAAG;CAGnE,MAAM,KAAK,KAAK,KAAK;CACrB,MAAM,OAAO,MAAM,SAAS,eAAe,OAAO,QAAQ,OAAO,cAAc,SAAY,MAAM,EAAE;EACjG;EACA;EACA,OAAO;GAAC;GAAU;GAAQ;GAAU;EACrC,CAAC;CAEF,MAAM,OAAO,kBAAkB;CAC/B,MAAM,WAAW,SAAS,WAAW,QAAQ,IAAI,EAAE,IAAI;CACvD,MAAM,cAAc,QAAiC;AACnD,MAAI;AACF,aAAU,UAAU,KAAK,UAAU;IAAE,GAAG;IAAK,KAAK,UAAU;IAAE,CAAC,GAAG,KAAK;UACjE;;AAIV,YAAW;EAAE,MAAM;EAAU,SAAS;EAAQ;EAAO;EAAK,CAAC;CAE3D,IAAI,SAAS;AACb,SAAQ,KAAK,WAAW,cAAc,UAAU,CAAC;AACjD,SAAQ,KAAK,UAAU,cAAc,SAAS,CAAC;CAC/C,SAAS,cAAc,KAAa;AAClC,eAAa;AACX,YAAS;AACT,UAAO,QAAQ;AACf,UAAO,KAAK;AACZ,UAAO,OAAO,KAAK,OAAO,KAAK,KAAK,GAAG,MAAM,IAAK;AAClD,UAAO,OAAO,oBAAoB;AAClC,cAAW,QAAQ,OAAO;AAC1B,OAAI;AACF,SAAK,MAAM;WACL;AAGR,WAAQ,KAAK,IAAI;;;AAKrB,CADW,gBAAgB,EAAE,OAAO,KAAK,QAAS,CAAC,CAChD,GAAG,SAAS,SAAS;AACtB,MAAI,OAAO,iBAAiB,cAAe,SAAQ,OAAO,MAAM,OAAO,KAAK;EAC5E,MAAM,aAAa,eAAe,KAAK;AACvC,MAAI,eAAe,KAAM;AACzB,gBAAc,YAAY,KAAK;AAC/B,MAAI,KAAK,YAAY,CAAC,OAAO,cAAe,QAAO,gBAAgB,KAAK;AACxE,SAAO,QAAQ,KAAK;AACpB,SAAO,QAAQ,KAAK;AACpB,MAAI,KAAK,KAAM,QAAO,OAAO,UAAU,KAAK,MAAM,GAAG;AACrD,MAAI,KAAK,cAAe,QAAO,gBAAgB,KAAK;AACpD,aAAW,QAAQ,OAAO;AAC1B,OAAK,MAAM,MAAM,KAAK,OAAO,OAAO,EAAE,CAAE,YAAW,GAAG;GACtD;CAEF,MAAM,KAAK,MAAM,IAAI,SAAiB,SAAS,WAAW;AACxD,OAAK,GAAG,SAAS,OAAO;AACxB,OAAK,GAAG,UAAU,SAAS,QAAQ,SAAS,SAAS,MAAM,GAAG,CAAC;GAC/D;AACF,WAAU,SAAS;CAEnB,MAAM,OAAO,KAAK,OAAO,KAAK,KAAK,GAAG,MAAM,IAAK;CACjD,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,SAAS,kBAAkB,UAAU;CAC3C,MAAM,cAA2B;EAC/B,MAAM;EACN,QAAQ;EACR,UAAU,KAAK,WAAW,OAAO;EACjC,WAAW,KAAK;EAChB,aAAa,OAAO;EACrB;AACD,YAAW,YAAY;CAEvB,MAAM,KAAK,OAAO,KAAK,CAAC,KAAK;AAC7B,QAAO,QAAQ,KAAK,SAAS;AAC7B,QAAO,KAAK;AACZ,QAAO,OAAO;AACd,QAAO,OAAO,UAAU,QAAQ,SAAS,WAAW,GAAG,KAAK,KAAK,SAAS;AAC1E,YAAW,QAAQ,OAAO;AAC1B,cAAa,QAAQ,cAAc;EACjC,IAAI;EACJ,UAAU,OAAO;EACjB,MAAM;EACN,QAAQ;EACR;EACA;EACA;EACA,OAAO,OAAO;EACd,OAAO,OAAO;EACd;EACD,CAAC;AAEF,KAAI,CAAC,EAAE,MAAO,aAAY,OAAO,cAAc,aAAa,IAAI,QAAQ,KAAK,OAAO;AACpF,QAAO,OAAO,IAAI,KAAK,KAAK,IAAI;;AAOlC,SAAS,YACP,KACA,aACA,IACA,QACA,KACA,QACM;AACN,KAAI,QAAQ,cAAe;AAC3B,KAAI,QAAQ,QAAQ;EAClB,MAAM,UAAU,SACZ;GAAE,GAAI,eAAe;IAAE,UAAU;IAAM,QAAQ;IAAmB;IAAI;GAAG;GAAQ,GACjF,eAAe;GAAE,UAAU;GAAM,QAAQ;GAAmB;GAAI;AACpE,UAAQ,IAAI,KAAK,UAAU,QAAQ,CAAC;AACpC;;AAEF,KAAI,YACF,SAAQ,IAAI,YAAY,UAAU,GAAG;KAErC,SAAQ,OAAO,MACb,0CAA0C,GAAG,SAAS,WAAW,QAAQ,IAAI,CAAC,IAC/E;;;;;;;AAuBL,eAAsB,aACpB,cACA,UACA,QACA,YAAY,KACiB;AAC7B,wBAAuB,cAAc,SAAS;CAC9C,MAAM,QAAQ,SAAS,OAAO;AAE9B,KAAI,SAAS,WAAW,SAAS;EAC/B,MAAM,UAAU,gBAAgB,GAAG,SAAY;AAC/C,SAAO;GACL,UAAU;GACV;GACA,WAAW;GACX,QAAQ,WAAW;GACnB,IAAI;GACJ,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;GAC/B;;CAGH,IAAI;AACJ,KAAI,SAAS,aAAa,SAExB,YAAW,GADE,MAAM,mBAAmB,oBAAoB,OAAO,CAC9C,GAAG;CAExB,MAAM,MAAM,YAAY,UAAU,MAAM,SAAS;CACjD,MAAM,KAAK,KAAK,KAAK;CACrB,MAAM,OAAO,MACX,UACA;EACE;EAAW;EACX;EAAuB;EAAgB,kBAAkB,OAAO;EAChE;EAAM;EACN;EAAmB;EACpB,EACD;EAAE;EAAK,OAAO;GAAC;GAAU;GAAQ;GAAU;EAAE,CAC9C;CACD,MAAM,QAAQ,iBAAiB;AAC7B,MAAI;AAAE,QAAK,KAAK,UAAU;UAAU;IACnC,UAAU;CAEb,IAAI,MAAM;AACV,MAAK,OAAO,GAAG,SAAS,UAAkB;AACxC,SAAO,MAAM,SAAS,OAAO;GAC7B;CACF,MAAM,KAAK,MAAM,IAAI,SAAiB,YAAY;AAChD,OAAK,GAAG,eAAe,QAAQ,EAAE,CAAC;AAClC,OAAK,GAAG,UAAU,SAAS,QAAQ,QAAQ,EAAE,CAAC;GAC9C;AACF,cAAa,MAAM;AAEnB,QAAO;EACL,UAAU;EACV;EACA,WAAW,KAAK,KAAK,GAAG;EACxB,QAAQ,iBAAiB,IAAI;EAC7B,IAAI,OAAO,KAAK,iBAAiB,IAAI,CAAC,MAAM,CAAC,aAAa,KAAK;EAChE;;;;;;;;AASH,SAAgB,iBAAiB,KAAqB;CACpD,MAAM,UAAU,IAAI,MAAM;AAC1B,KAAI,CAAC,QAAS,QAAO;CACrB,MAAM,SAAS,MAAuB;AACpC,MAAI;AACF,UAAO,KAAK,MAAM,EAAE;UACd;AACN;;;CAGJ,MAAM,aAAa,MAA8B;AAC/C,MAAI,MAAM,QAAQ,EAAE,EAAE;AACpB,QAAK,IAAI,IAAI,EAAE,SAAS,GAAG,KAAK,GAAG,KAAK;IACtC,MAAM,IAAI,UAAU,EAAE,GAAG;AACzB,QAAI,MAAM,KAAM,QAAO;;AAEzB,UAAO;;AAET,MAAI,OAAO,MAAM,YAAY,MAAM,MAAM;GACvC,MAAM,IAAI;AACV,OAAI,EAAE,SAAS,YAAY,OAAO,EAAE,WAAW,SAAU,QAAO,EAAE;;AAEpE,SAAO;;CAET,MAAM,SAAS,UAAU,MAAM,QAAQ,CAAC;AACxC,KAAI,WAAW,KAAM,QAAO;CAC5B,MAAM,QAAQ,QAAQ,MAAM,KAAK;AACjC,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,IAAI,UAAU,MAAM,MAAM,GAAG,CAAC;AACpC,MAAI,MAAM,KAAM,QAAO;;AAEzB,QAAO,QAAQ,MAAM,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;;ACl3B9B,SAAgB,YAAY,QAAgB,SAAyB;AACnE,QAAO,KAAK,QAAQ,SAAS,GAAG,QAAQ,KAAK;;;;;;;AAQ/C,SAAgB,cAAc,YAAsB,QAA0B;CAC5E,MAAM,MAAgB,EAAE;AACxB,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;EAC1C,MAAM,IAAI,WAAW;AACrB,MAAI,MAAM,QAAQ,MAAM,WAAW;AACjC,OAAI,IAAI,IAAI,WAAW,UAAU,CAAC,WAAW,IAAI,GAAG,WAAW,IAAI,CAAE,MAAK;AAC1E;;AAEF,MAAI,KAAK,EAAE;;AAEb,KAAI,KAAK,MAAM,OAAO;AACtB,QAAO;;AAGT,SAAgB,YAAY,OAAe,UAA0B;AACnE,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;AAGd,SAAgB,gBAAgB,OAAe,UAAyB,MAA6B;AAYnG,QAAO;EAAC,GAXK,OACT;GACE,kGACE,WACA;GACF;GACA;GACA;GACA;GACD,GACD,CAAC,wCAAwC;EAC5B;EAAI;EAAqB;EAAI;EAAM,CAAC,KAAK,KAAK;;AAGjE,SAAgB,aAAa,OAAe,UAAyB,MAA6B;AAShG,QAAO;EACL;EACA,GAVe,OACb;GACE;GACA,sBAAsB,WAAW;GACjC;GACA;GACD,GACD,EAAE;EAIJ;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;;AAgCd,eAAsB,SAAS,MAAoB,OAAkB,EAAE,EAAmB;CACxF,MAAM,SAAS,KAAK,OAAO,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;AAC/D,KAAI,CAAC,OAAO,OAAQ,OAAM,IAAI,MAAM,iEAAiE;CACrG,MAAM,WAAW,KAAK,YAAY;CAClC,MAAM,SAAS,KAAK,UAAU,cAAc,oBAAoB,CAAC,QAAQ;CACzE,MAAM,UAAU,aAAa;CAC7B,MAAM,WAAW,YAAY,QAAQ,QAAQ;AAC7C,WAAU,KAAK,QAAQ,QAAQ,EAAE,EAAE,WAAW,MAAM,CAAC;CACrD,MAAM,YAAY,KAAK,SAAS,UAAU,KAAK,OAAO,GAAG;AAEzD,cAAa,WAAW,OAAO,EAAE,gBAAgB;EAC/C,OAAO;EACP,QAAQ,OAAO,KAAK,IAAI;EACxB,OAAO;EACR,CAAC;AACF,MAAK,eAAe,QAAQ;CAE5B,IAAI,OAAsB;AAC1B,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,QAAQ,OAAO;AACrB,MAAI,UAAU,QACZ,QAAO;WACE,SAAS,QAAQ,WAAW,SAAS,CAC9C,QAAO,aAAa,UAAU,OAAO;EAEvC,MAAM,SACJ,UAAU,UACN,YAAY,KAAK,OAAO,SAAS,GACjC,UAAU,WACR,aAAa,KAAK,OAAO,OAAO,WAAW,MAAM,KAAK,GACtD,gBAAgB,KAAK,OAAO,OAAO,WAAW,MAAM,KAAK;AACjE,UAAQ,OAAO,MACb,SAAS,QAAQ,UAAU,IAAI,EAAE,GAAG,OAAO,OAAO,GAAG,MAAM,UAAU,SAAS,KAC/E;EACD,MAAM,KAAK,MAAM,SAAS;GACxB,WAAW,KAAK,aAAa;GAC7B,cAAc,KAAK;GACnB,WAAW,KAAK;GAChB,OAAO,GAAG,UAAU,KAAK;GACzB,QAAQ,KAAK;GACb,SAAS,KAAK;GACd,YAAY,cAAc,KAAK,YAAY,OAAO;GAClD,KAAK,KAAK;GACV,QAAQ;GACR;GACA,OAAO,KAAK;GACb,CAAC;AACF,MAAI,UAAU,SAAS;AACrB,OAAI,CAAC,WAAW,SAAS,EAAE;AACzB,YAAQ,OAAO,MACb,SAAS,QAAQ,oCAAoC,SAAS,4EAE/D;AACD,iBAAa,WAAW,OAAO,EAAE,oBAAoB;KACnD,OAAO;KACP,IAAI,OAAO,IAAI,KAAK;KACpB,QAAQ;KACT,CAAC;AACF,WAAO,OAAO,IAAI,KAAK;;AAEzB,UAAO,aAAa,UAAU,OAAO;;AAEvC,MAAI,OAAO,GAAG;AACZ,gBAAa,WAAW,OAAO,EAAE,oBAAoB;IACnD,OAAO;IACP;IACA,QAAQ;IACT,CAAC;AACF,UAAO;;;AAGX,cAAa,WAAW,OAAO,EAAE,oBAAoB;EAAE,OAAO;EAAS,IAAI;EAAG,CAAC;AAC/E,QAAO;;;;;;;;;;;;;;;;;;AClMT,MAAa,cAAc;;AAG3B,MAAa,YAAY;;AAGzB,MAAa,YAAY;CACvB;CACA;CACA;CACA;CACD;;AAOD,SAAS,YAAY,GAAW,GAAmB;CACjD,MAAM,IAAI,EAAE,IAAI;AAChB,KAAI,MAAM,KAAK;EACb,IAAI,IAAI,IAAI;AACZ,SAAO,IAAI,EAAE,UAAU,EAAE,EAAE,MAAO,OAAO,EAAE,MAAO,KAAM;AACxD,SAAO,KAAK,IAAI,EAAE,QAAQ,IAAI,EAAE;;AAElC,KAAI,MAAM,KAAK;EACb,IAAI,IAAI,IAAI;AACZ,SAAO,IAAI,EAAE,UAAU,EAAE,OAAO,OAAQ;AACxC,SAAO,KAAK,IAAI,EAAE,QAAQ,IAAI,EAAE;;AAElC,QAAO,IAAI;;;AAIb,SAAgB,aAAa,GAAmB;CAC9C,IAAI,IAAI;CACR,IAAI,IAAI;AACR,QAAO,IAAI,EAAE,QAAQ;AACnB,MAAI,EAAE,OAAO,QAAQ;AACnB,OAAI,YAAY,GAAG,EAAE;AACrB;;AAEF,OAAK;AACL,OAAK;;AAEP,QAAO;;;;;;;AAQT,SAAS,WAAW,MAAc,MAAwB;CACxD,MAAM,OAAiB,EAAE;CACzB,IAAI,IAAI;AACR,QAAO,IAAI,KAAK,IAAI,MAAM,KAAK,OAAO,EAAE;AACtC,MAAI,KAAK,OAAO,QAAQ;GACtB,MAAM,MAAM,YAAY,MAAM,EAAE;GAChC,MAAM,MAAM,KAAK,MAAM,GAAG,IAAI;AAC9B,OAAI,mBAAmB,KAAK,IAAI,EAAE;IAChC,MAAM,SAAS,IAAI,MAAM,GAAG,GAAG;AAE/B,QADe,WAAW,MAAM,OAAO,MAAM,IAAI,CAAC,SAAS,IAAI,CACnD,MAAK,SAAS;AAC1B,QAAI,EAAE,WAAW,MAAM,WAAW,KAAM,MAAK,KAAK,IAAI;;AAExD,OAAI;AACJ;;AAEF,OAAK;;AAEP,QAAO;;;;;;;;AAST,SAAgB,SAAS,MAAc,OAAyB;AAC9D,KAAI,QAAQ,KAAK,aAAa,KAAK,IAAI,MAAO,QAAO,CAAC,KAAK;CAG3D,MAAM,QAAkB,EAAE;AAC1B,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,MAAI,KAAK,OAAO,QAAQ;AACtB,OAAI,YAAY,MAAM,EAAE,GAAG;AAC3B;;AAEF,QAAM,KAAK,EAAE;;CAIf,MAAM,QAAoD,EAAE;CAC5D;EACE,IAAI,IAAI;EACR,IAAI,IAAI;AACR,OAAK,IAAI,IAAI,GAAG,KAAK,MAAM,QAAQ,IAEjC,MADW,MAAM,MAAM,SAAS,MAAM,KAAK,MAAM,SACtC,KACT;OAAI,KAAK,GAAG;AACV,UAAM,KAAK;KAAE;KAAG,GAAG,IAAI;KAAG;KAAG,CAAC;AAC9B,QAAI;AACJ,QAAI;;SAED;AACL,OAAI,IAAI,EAAG,KAAI;AACf;;;AAKN,KAAI,MAAM,UAAU,MAAM,GAAI,IAAI,EAChC,OAAM,KAAK;EAAE,GAAG;EAAG,GAAG,MAAM,GAAI;EAAG,GAAG,MAAM,GAAI,IAAI,MAAM,GAAI;EAAG;CAKnE,MAAM,QAAiC,EAAE;CACzC,IAAI,KAAK;CACT,IAAI,KAAK;AACT,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,KAAK,IAAI,OAAO;GAIlB,MAAM,OAAO,KAAK,IAAI,IAAI,SAAS,KAAK,KAAK;GAC7C,MAAM,OAAO,OAAO,IAAI,KAAK,OAAO,KAAK,IAAI,IAAI;AACjD,OAAI,OAAO,EAAG,OAAM,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC;YAChC,MAAM,EAAG,OAAM,KAAK,CAAC,IAAI,GAAG,CAAC;GACtC,IAAI,MAAM,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK;GACpC,IAAI,YAAY,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK;AAC1C,UAAO,YAAY,OAAO;AACxB,UAAM,KAAK,CAAC,KAAK,MAAM,QAAQ,EAAE,CAAC;AAClC,WAAO;AACP,iBAAa;;AAEf,QAAK;AACL,QAAK,MAAM,YAAY;AACvB;;AAEF,MAAI,KAAK,GAAG;AACV,QAAK,KAAK;AACV,QAAK,KAAK;AACV;;AAEF,MAAI,KAAK,IAAI,KAAK,KAAK,OAAO;AAC5B,QAAK,KAAK;AACV;;AAEF,QAAM,KAAK,CAAC,IAAI,GAAG,CAAC;AACpB,OAAK,KAAK;AACV,OAAK,KAAK;;AAEZ,KAAI,MAAM,EAAG,OAAM,KAAK,CAAC,IAAI,GAAG,CAAC;CAEjC,MAAM,MAAgB,EAAE;AACxB,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO;EAC1B,MAAM,OAAO,MAAM;EAEnB,IAAI,MAAM,MAAM,KAAM;AACtB,SAAO,MAAM,KAAK,UAAU,KAAK,SAAS,OAAQ,OAAM,YAAY,MAAM,IAAI;EAC9E,IAAI,QAAQ,KAAK,MAAM,MAAM,IAAI;EACjC,MAAM,SAAS,WAAW,MAAM,KAAK;AACrC,MAAI,OAAO,OAAQ,SAAQ,OAAO,KAAK,GAAG,GAAG;AAC7C,MAAI,WAAW,MAAM,IAAI,CAAC,OAAQ,UAAS;AAC3C,MAAI,KAAK,MAAM;;AAEjB,QAAO,IAAI,SAAS,MAAM,CAAC,GAAG;;;AAQhC,SAAgB,iBAAiB,MAAsB;AACrD,QAAO,UAAU,KAAK,IAAI,GAAG,OAAO,EAAE,CAAC;;;;;;AAOzC,SAAgB,UAAU,MAAsB;AAC9C,QAAO,YAAY,iBAAiB,KAAK,GAAG,QAAQ,KAAK,IAAI,GAAG,OAAO,EAAE,CAAC;;;AAI5E,SAAgB,UAAU,MAAsB;AAC9C,QAAO,iBAA4B,KAAK,IAAI,GAAG,KAAK,CAAC;;;AAIvD,SAAgB,cAAc,MAAc,MAAsB;AAChE,QAAO,UAAkB,KAAK,IAAI,GAAG,KAAK,CAAC,aAAa,OAAO;;;AAIjE,SAAgB,cAAc,MAAc,SAAS,aAAa,MAAuB;AACvF,QAAO,QAAQ,KAAK,IAAI,GAAG,OAAO,EAAE,CAAC,OAAO,UAAU,QAAQ;;;;;;;;;;AAiBhE,SAAgB,eAAe,MAAc,MAAc,YAAgC;AAQzF,QAAO;EAAE,KALP,WAFc,OAAO,aAIjB,QAAQ,OAAO,EAAE,KAAK,KAAK,UAC3B,QAAQ,WAAW,OAAO,KAAK,WACnC;EACY,MAAM,KAAK,IAAI,OAAO,GAAG,WAAW;EAAE;;;;;;;;AAoBtD,SAAgB,cAAc,KAAyB;CACrD,MAAM,OAAO,IAAI,MAAM;AACvB,KAAI,SAAS,QAAS,QAAO,EAAE,MAAM,QAAQ;AAC7C,KAAI,SAAS,QAAS,QAAO,EAAE,MAAM,QAAQ;AAC7C,KAAI,SAAS,UAAW,QAAO,EAAE,MAAM,UAAU;AACjD,KAAI,KAAK,WAAW,UAAU,CAAE,QAAO;EAAE,MAAM;EAAU,MAAM,KAAK,MAAM,EAAiB,CAAC,MAAM;EAAE;AACpG,QAAO;EAAE,MAAM;EAAW;EAAM;;;;;;AAOlC,SAAgB,aAAa,YAAgD;AAC3E,QAAO,OAAO,eAAe,YAAY,WAAW,MAAM,KAAK;;;;;;;;;;;;;;AChQjE,MAAM,QAAQ;CACZ,KAAK;CACL,MAAM;CACN,KAAK;CACL,OAAO;CACP,QAAQ;CACR,MAAM;CACN,KAAK;CACL,MAAM;CACP;AAID,SAAgB,UAAU,SAAuB;AAC/C,SAAQ,MAAiB,MACvB,UAAU,QAAQ,MAAM,MAAM,GAAG,EAAE,WAAW;;;AAMlD,SAAgB,QAAQ,MAAc,KAAqB;AACzD,KAAI,CAAC,QAAQ,CAAC,IAAK,QAAO;CAC1B,IAAI;AACJ,KAAI;AACF,MAAI,SAAS,KAAK,KAAK;SACjB;AACN,SAAO;;AAET,QAAO,EAAE,WAAW,KAAK,GAAG,OAAO;;;AAIrC,SAAgB,iBAAiB,QAAgB,QAA0B;CACzE,MAAM,OAAO,OAAO,MAAM,KAAK;CAC/B,MAAM,OAAO,OAAO,MAAM,KAAK;CAC/B,IAAI,QAAQ;AACZ,QAAO,QAAQ,KAAK,UAAU,QAAQ,KAAK,UAAU,KAAK,WAAW,KAAK,OAAQ;CAClF,IAAI,SAAS,KAAK;CAClB,IAAI,SAAS,KAAK;AAClB,QAAO,SAAS,SAAS,SAAS,SAAS,KAAK,SAAS,OAAO,KAAK,SAAS,IAAI;AAChF;AACA;;CAEF,MAAM,MAAgB,EAAE;AACxB,MAAK,IAAI,IAAI,OAAO,IAAI,QAAQ,IAAK,KAAI,KAAK,MAAM,KAAK,GAAG;AAC5D,MAAK,IAAI,IAAI,OAAO,IAAI,QAAQ,IAAK,KAAI,KAAK,MAAM,KAAK,GAAG;AAC5D,QAAO;;;;;;;;AAqCT,SAAgB,QAAQ,IAAY,SAAS,kBAAC,IAAI,MAAM,EAAC,mBAAmB,EAAiB;CAC3F,MAAM,IAAI,KAAK,MAAM,GAAG;AACxB,KAAI,OAAO,MAAM,EAAE,CAAE,QAAO;AAC5B,QAAO,IAAI,KAAK,IAAI,SAAS,IAAO,CAAC,aAAa,CAAC,MAAM,IAAI,GAAG;;;AAIlE,SAAgB,MAAM,IAAY,SAAS,kBAAC,IAAI,MAAM,EAAC,mBAAmB,EAAiB;CACzF,MAAM,IAAI,KAAK,MAAM,GAAG;AACxB,KAAI,OAAO,MAAM,EAAE,CAAE,QAAO;AAC5B,QAAO,IAAI,KAAK,IAAI,SAAS,IAAO,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG;;;;;;;;;;;;AAyBjE,SAAgB,UACd,GACA,GACA,KACA,QACe;AACf,KAAI,CAAC,EAAE,IAAK,QAAO;CACnB,MAAM,OAAO,QAAQ,EAAE,KAAK,OAAO,KAAK,EAAE,IAAI,UAAU,KAAK,EAAE,IAAI,MAAM,IAAI,GAAG,GAAG,EAAE;CACrF,MAAM,OAAO,MAAM,GAAG,IAAI,GAAG,SAAS;CACtC,MAAM,QAAQ,KAAK,SAAS;AAC5B,QAAO;EACL,OAAO,EAAE,OAAO,GAAG,KAAK,KAAK;EAC7B,MAAM,IAAI,OAAO,MAAM;EACvB,SAAS,EAAE,OAAO,GAAG,IAAI,OAAO,KAAK,OAAO,CAAC,KAAK;EAClD;EACD;;;;;;AAOH,SAAgB,WAAW,MAAc,KAAsB;CAC7D,MAAM,IAAK,OAAO,QAAQ,YAAY,QAAQ,OAAO,MAAM,EAAE;CAC7D,MAAM,OAAO,MAAe,OAAO,EAAE,OAAO,WAAY,EAAE,KAAgB;AAC1E,KAAI,SAAS,OAAQ,QAAO,KAAK,UAAU,IAAI,UAAU,CAAC,QAAQ,QAAQ,IAAI,CAAC,MAAM,EAAE,GAAG;AAC1F,KAAI,SAAS,UAAU,SAAS,UAAU,SAAS,WAAW,SAAS,YAErE,SADa,IAAI,YAAY,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI,OACnC;AAEjB,QAAO;;;;;;;;AAST,SAAgB,WAAW,MAAc,QAAgB,MAAc,OAA+B;CACpG,MAAM,OAAO,KAAK,KAAK;CAEvB,MAAM,OADQ,CAAC,QAAQ,KAAK,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,CAC9C,KAAK,MAAM;CAC9B,MAAM,OAAO,OAAO,GAAG,KAAK,KAAK,SAAS;AAC1C,QAAO,QAAQ,GAAG,KAAK,KAAK,UAAU;;;;;;;;AASxC,SAAgB,aAAa,MAAgC,GAA+B;AAC1F,KAAI,CAAC,KAAM,QAAO;AAClB,KAAI,EAAE,SAAS,YAAa,QAAO;AACnC,QAAO,KAAK,SAAS,UAAU,KAAK,SAAS;;;AAI/C,SAAgB,SAAS,MAAsB;AAE7C,QAAO,WADO,KAAK,MAAM,CAAC,MAAM,KAAK,CAAC,MAAM,MAAM,EAAE,MAAM,CAAC,IAAI,IACxC,MAAM,CAAC,QAAQ,QAAQ,IAAI,EAAE,GAAG;;;AAIzD,SAAS,KAAK,GAAmB;AAC/B,QAAO,KAAK,MAAO,GAAG,KAAK,MAAM,IAAI,IAAK,CAAC,KAAK,OAAO,EAAE;;;;;;;AAQ3D,SAAgB,aACd,GACA,GACA,SAAS,GACM;CACf,MAAM,MAAM,eAAe,EAAE;AAC7B,KAAI,QAAQ,QAAQ,OAAO,OAAQ,QAAO;CAC1C,MAAM,QAAQ,OAAO,KAAK,EAAE,iBAAiB,EAAE,CAAC,GAAG,KAAK,EAAE,iBAAiB,EAAE,CAAC,IAAI,IAAI;AACtF,KAAI,MAAM,GAAI,QAAO,EAAE,OAAO,MAAM;AACpC,KAAI,MAAM,GAAI,QAAO,EAAE,UAAU,MAAM;AACvC,QAAO;;AAGT,SAAS,cACP,GACA,QACA,MACA,KACA,KACU;CACV,MAAM,IAAK,OAAO,QAAQ,YAAY,QAAQ,OAAO,MAAM,EAAE;CAC7D,MAAM,OAAO,MAAe,OAAO,EAAE,OAAO,WAAY,EAAE,KAAgB;AAC1E,KAAI,SAAS,OACX,QAAO,CAAC,GAAG,SAAS,EAAE,OAAO,IAAI,CAAC,WAAW,QAAQ,IAAI,YAAY,EAAE,IAAI,GAAG;AAEhF,KAAI,SAAS,UAAU,SAAS,OAC9B,QAAO,CACL,GAAG,SAAS,EAAE,OAAO,IAAI,CAAC,aAAa,EAAE,QAAQ,IAAI,UAAU,CAAC,CAAC,MAAM,QAAQ,IAAI,OAAO,IAAI,KAAK,IAAI,GACxG;AAEH,KAAI,SAAS,OACX,QAAO,CAAC,GAAG,SAAS,EAAE,OAAO,IAAI,CAAC,GAAG,UAAU,IAAI,UAAU,EAAE,IAAI,GAAG;AAExE,KAAI,SAAS,QAAQ;EACnB,MAAM,MAAM,CAAC,GAAG,SAAS,EAAE,OAAO,IAAI,CAAC,WAAW,QAAQ,IAAI,YAAY,EAAE,IAAI,GAAG;AACnF,OAAK,MAAM,QAAQ,iBAAiB,IAAI,aAAa,EAAE,IAAI,aAAa,CAAC,CACvE,KAAI,KAAK,WAAW,IAAI,CAAE,KAAI,KAAK,GAAG,OAAO,MAAM,EAAE,OAAO,KAAK,GAAG;WAC3D,KAAK,WAAW,IAAI,CAAE,KAAI,KAAK,GAAG,OAAO,MAAM,EAAE,SAAS,KAAK,GAAG;MACtE,KAAI,KAAK,GAAG,OAAO,MAAM,EAAE,OAAO,KAAK,GAAG;AAEjD,SAAO;;AAET,KAAI,SAAS,SAAS;EACpB,MAAM,IAAI,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;AACrC,SAAO,CAAC,GAAG,SAAS,EAAE,SAAS,IAAI,CAAC,WAAW,QAAQ,IAAI,YAAY,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS;;AAE/F,KAAI,SAAS,eAAe,SAAS,WACnC,QAAO,CAAC,GAAG,SAAS,EAAE,OAAO,IAAI,CAAC,GAAG,KAAK,GAAG,UAAU,IAAI,QAAQ,IAAI,IAAI,MAAM,EAAE,IAAI,GAAG;AAE5F,QAAO,CAAC,GAAG,SAAS,EAAE,OAAO,IAAI,CAAC,GAAG,KAAK,GAAG,UAAU,KAAK,UAAU,EAAE,EAAE,IAAI,GAAG;;;AAInF,SAAgB,YACd,GACA,QACA,GACA,KACA,OACA,QACU;CACV,MAAM,MAAgB,EAAE;AACxB,KAAI,EAAE,SAAS,YAAY,EAAE,YAAY,OACvC,KAAI,KACF,GAAG,SAAS,EAAE,OAAO,0BAA0B,EAAE,SAAS,IAAI,SAAS,SAAS,EAAE,OAAO,OAAO,GAAG,GAAG,GACvG;UACQ,EAAE,SAAS,WAEpB,MAAK,MAAM,MAAM,OAAO,EAAE,QAAQ,GAAG,CAAC,MAAM,KAAK,CAC/C,KAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,OAAO,GAAG,GAAG;UAErC,EAAE,SAAS,aACpB;OAAK,MAAM,KAAK,EAAE,SAAS,WAAW,EAAE,CACtC,KAAI,EAAE,SAAS,WAAW,EAAE,QAAQ,IAAI,MAAM,CAC5C,MAAK,MAAM,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAE,KAAI,KAAK,GAAG,SAAS,KAAK;WACrE,EAAE,SAAS,WACpB,KAAI,KAAK,GAAG,cAAc,GAAG,QAAQ,EAAE,QAAQ,KAAK,EAAE,OAAO,IAAI,CAAC;YAG7D,EAAE,SAAS,OACpB,MAAK,MAAM,KAAK,EAAE,SAAS,WAAW,EAAE,EAAE;AACxC,MAAI,EAAE,SAAS,cAAe;EAC9B,IAAI,UAAoB,EAA4B,WAAW;AAC/D,MAAI,MAAM,QAAQ,QAAQ,CACxB,WAAU,QACP,KAAK,MAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,UAAU,IAAI,OAAQ,EAAwB,QAAQ,GAAG,GAAG,GAAI,CACnH,KAAK,KAAK;EAEf,MAAM,OAAO,OAAO,QAAQ;EAC5B,MAAM,UAAW,EAA6B,aAAa;AAC3D,MAAI,CAAC,WAAW,MAAO,EAA+B,eAAe,QAAQ,QAAQ;GAGnF,MAAM,QAAQ,KAAK,MAAM,KAAK;AAC9B,QAAK,MAAM,MAAM,MAAM,MAAM,GAAG,EAAE,CAAE,KAAI,KAAK,GAAG,SAAS,EAAE,OAAO,GAAG,GAAG;AACxE,OAAI,MAAM,SAAS,EACjB,KAAI,KAAK,GAAG,SAAS,EAAE,OAAO,SAAS,MAAM,OAAO,QAAQ,GAAG;aAExD,QACT,KAAI,KAAK,GAAG,OAAO,MAAM,EAAE,OAAO,OAAO,UAAU,MAAM,IAAI,CAAC,GAAG;WACxD,KAAK,MAAM,CACpB,KAAI,KAAK,GAAG,OAAO,MAAM,EAAE,OAAO,UAAU,MAAM,IAAI,CAAC,GAAG;;UAGrD,EAAE,SAAS,UAAU;EAE9B,MAAM,OADK,CAAC,EAAE,WACI,EAAE,SAAS,SAAS,GAAG,EAAE,OAAO,WAAW;AAC7D,MAAI,KAAK,GAAG,SAAS,KAAK,KAAK,EAAE,aAAa,IAAI,WAAW,KAAK,OAAO,EAAE,eAAe,KAAK,IAAK,CAAC,GAAG;EAExG,MAAM,SAAS,kBAAkB,OAAO,EAAE,UAAU,GAAG,CAAC;AACxD,MAAI,OACF,KAAI,KAAK,GAAG,aAAa,GAAG,QAAQ,QAAQ,IAAI,CAAC;MAEjD,MAAK,MAAM,MAAM,OAAO,EAAE,UAAU,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,CACxD,KAAI,KAAK,GAAG,OAAO,IAAI,KAAK;;AAIlC,KAAI,CAAC,OAAQ,QAAO;AACpB,QAAO,IAAI,KAAK,IAAI,OAAO,MAAM,IAAI,OAAO,QAAQ,OAAO,QAAQ,GAAG;;;AAIxE,SAAgB,WACd,GACA,GACQ;CACR,MAAM,OAAO,CAAC,EAAE,WAAW,IAAI,EAAE,SAAS,KAAK,IAAI,WAAW,EAAE,CAAC,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;CAC3F,MAAM,MAAM,OAAO,KAAK,SAAS;AACjC,QAAO,EAAE,QAAQ,MAAM,EAAE,KAAK,IAAI,IAAI,EAAE,MAAM,KAAK,SAAS,EAAE,IAAI,CAAC,GAAG;;;AAIxE,SAAS,aAAa,QAAgC;CACpD,MAAM,QAAQ,OAAO;AACrB,KAAI,CAAC,MAAO,QAAO;CACnB,MAAM,SAAS,MAAM,QAAQ,MAAM,MAAM,UAAU;AACnD,QAAO,MAAM,MAAM,SAAS,OAAO,IAAI,SACnC,MAAM,MAAM,MAAM,GAAG,MAAM,MAAM,SAAS,OAAO,OAAO,GACxD,MAAM;;;;;;;AAQZ,SAAgB,YACd,GACA,UACA,YACA,sBAAY,IAAI,MAAM,EACd;CACR,MAAM,UAA0B,EAAE;CAClC,MAAM,OAAuB,EAAE;AAC/B,MAAK,MAAM,KAAK,SACd,KAAI,EAAE,UAAU,aAAa,MAAM,EAAE,IAAI,CAAE,SAAQ,KAAK,EAAE;MACrD;AACH,MAAI,EAAE,UAAU,UAAW,GAAE,QAAQ;AACrC,OAAK,KAAK,EAAE;;CAIhB,MAAM,SAAS,SAAsE;EACnF,MAAM,MAAmD,EAAE;AAC3D,OAAK,MAAM,KAAK,KACd,KAAI,KAAK;GAAE;GAAG,OAAO,EAAE,UAAU;GAAM,CAAC;AAE1C,SAAO;;CAET,MAAM,YAAY,MAAc,OAAsB,SAA0B;AAC9E,MAAI,UAAU,KAAM,QAAO;EAC3B,MAAM,OAAO,OAAO,MAAM;EAC1B,MAAM,MAAM,OAAO,MAAM;AACzB,SAAO,KAAK,WAAW,SAAS,GAC5B,KAAK,IAAI,KAAK,KAAK,MAAM,EAAE,KAC3B,KAAK,KAAK,GAAG,KAAK,MAAM,EAAE;;CAIhC,MAAM,QAAkB,CAAC,EAAE,QAAQ,YADrB,GAAG,OAAO,IAAI,UAAU,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,IAAI,YAAY,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,IAAI,YAAY,CAAC,CAAC,SAAS,GAAG,IAAI,KAC3F,EAAE,GAAG;AAC5D,OAAM,KAAK,EAAE,QAAQ,YAAY,QAAQ,OAAO,GAAG,CAAC;AACpD,KAAI,CAAC,QAAQ,OAAQ,OAAM,KAAK,SAAS;CACzC,MAAM,aAAa,MAAM,QAAQ;AACjC,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;EAC1C,MAAM,EAAE,GAAG,UAAU,WAAW;AAChC,MAAI,OAAO;GACT,MAAM,OAAO,WAAW,IAAI;AAC5B,OAAI,CAAC,QAAQ,KAAK,UAAU,OAAO;IACjC,MAAM,SAAS,WAAW,QAAQ,MAAM,EAAE,UAAU,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE;AAC1E,UAAM,KAAK,KAAK,EAAE,QAAQ,SAAS,QAAQ,CAAC,IAAI,aAAa,OAAO,GAAG;;;EAG3E,MAAM,OAAO,CAAC,SAAS,CAAC,WAAW,IAAI,MAAM,WAAW,IAAI,GAAG,UAAU;EACzE,MAAM,QAAQ,aAAa,GAAG,GAAG,GAAG;AACpC,QAAM,KACJ,SACE,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,EAAE,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC,cAAc,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,UAAU,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,IAAI,SAAS,EAAE,IAAI,GAAG,QAAQ,OAAO,QAAQ,MACxM,OACA,KACD,CACF;AACD,QAAM,KAAK,SAAS,eAAe,EAAE,SAAS,OAAO,KAAK,CAAC;AAC3D,QAAM,KACJ,SAAS,eAAe,EAAE,UAAU,EAAE,KAAK,CAAC,KAAK,MAAM,EAAE,SAAS,IAAI,CAAC,QAAQ,OAAO,KAAK,CAC5F;;AAEH,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,EAAE,QAAQ,oBAAoB,CAAC;CAC1C,MAAM,cAAc,MAAM,KAAK,MAAM,GAAG,CAAC;AACzC,MAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK;EAC3C,MAAM,EAAE,GAAG,UAAU,YAAY;AACjC,MAAI,OAAO;GACT,MAAM,OAAO,YAAY,IAAI;AAC7B,OAAI,CAAC,QAAQ,KAAK,UAAU,OAAO;IACjC,MAAM,SAAS,YAAY,QAAQ,MAAM,EAAE,UAAU,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE;AAC3E,UAAM,KAAK,KAAK,EAAE,QAAQ,SAAS,QAAQ,CAAC,IAAI,aAAa,OAAO,GAAG;;;EAG3E,MAAM,OAAO,CAAC,SAAS,CAAC,YAAY,IAAI,MAAM,YAAY,IAAI,GAAG,UAAU;EAC3E,MAAM,MAAM,EAAE,UAAU,SAAS,UAAU;EAC3C,MAAM,MAAM,WAAW,EAAE;AACzB,QAAM,KACJ,SACE,KAAK,EAAE,GAAG,IAAI,EAAE,SAAS,GAAG,MAAM,MAAM,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,QAAQ,IAAI,CAAC,SAAS,EAAE,CAAC,WAAW,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,UAAU,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,IAAI,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,SACvO,OACA,KACD,CACF;;AAEH,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,EAAE,OAAO,0DAA0D,CAAC;AAC/E,OAAM,KAAK,EAAE,OAAO,yCAAyC,CAAC;AAC9D,OAAM,KAAK,EAAE,OAAO,WAAW,CAAC;AAChC,QAAO,MAAM,KAAK,KAAK;;;AAIzB,SAAgB,iBACd,MACA,sBAAY,IAAI,MAAM,EACtB,IAAW,UAAU,KAAK,EAClB;AACR,KAAI,CAAC,KAAK,OAAQ,QAAO;CACzB,MAAM,UAAU,KAAK,QAAQ,MAAM,EAAE,UAAU,aAAa,MAAM,EAAE,IAAI,CAAC;CACzE,MAAM,QAAQ,GAAG,IAAI,aAAa,CAAC,GAAG,OAAO,IAAI,UAAU,GAAG,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,OAAO,IAAI,SAAS,CAAC,CAAC,SAAS,GAAG,IAAI;CAC3H,MAAM,YAAY,KAAK,QAAQ,MAAM,EAAE,UAAU,aAAa,EAAE,QAAQ,WAAW,MAAM,CAAC;CAC1F,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE,UAAU,OAAO,CAAC;CACvD,MAAM,MAAM,UAAU,SAAS;CAC/B,MAAM,YAAY,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,SAAS,CAAC;CAEzD,IAAI,OAAO,GADS,UAAU,SAAS,IAAI,CAAC,GAAG,UAAU,CAAC,KAAK,UACrC,IAAI,QAAQ;CACtC,MAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM;EAE3C,MAAM,QAAQ,aAAa,GAAG,GAAG,GAAG;AACpC,SACE,GAAG,EAAE,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,EAAE,SAAS,IAAI,CAAC,KAAK,EAAE,KAAK,MAAM,GAAG,GAAG,MAC1F,QAAQ,IAAI,UAAU;GAEzB;AACF,KAAI,MAAM,OAAQ,SAAQ,OAAO,MAAM,KAAK,MAAM;AAClD,KAAI,UAAU,OAAQ,SAAQ,OAAO,GAAG,IAAI,IAAI;AAChD,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC1ZT,MAAa,kBAAkB;AAM/B,SAAgB,SACd,QACA,SACA,MAAyB,QAAQ,KACjC,QAAQ,QAAQ,OAAO,UAAU,MACzB;CACR,MAAM,IAAI,UAAU,MAAM;CAC1B,MAAM,OAAO,IAAI,oBAAoB;CACrC,MAAM,WAAW,aAAa,OAAO;AAQrC,QAAO,YAAY,GAPJ,WAAW,CAAC,OAAO,WAAW,SAAS,QAAQ,MAAM,cAAc,GAAG,KAAK,CAAC,EAEzF,WAAW,CAAC,OACR,uBACA,eAAe,KAAK,GAClB,kBAAkB,eAAe,KAAK,CAAE,SACxC,cAAc,cAAc,IAAI,CAAC,mBACA;;AAqB3C,SAAgB,mBAAmB,SAAS,2BAAwC;AAClF,QAAO;EAAE,SAAS;EAAI,OAAO,EAAE;EAAE;EAAQ,MAAM;EAAI,MAAM;EAAM;;;;;;;;;;AAsBjE,SAAgB,WACd,MACA,QACA,WACU;AACV,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,cAAc,QAAQ,aAAa,OAAO,MAC5C,QAAO,KAAK,KAAK,IAAI,OAAO,MAAM,IAAI,OAAO,QAAQ,OAAO,QAAQ,GAAG;CAEzE,MAAM,MAAgB,EAAE;AACxB,MAAK,MAAM,MAAM,KACf,MAAK,MAAM,SAAS,SAAS,IAAI,YAAY,OAAO,MAAM,CACxD,KAAI,MAAM,IAAI,WAAW,IAAI,OAAO,QAAQ,OAAO,WAAW,MAAM;AAGxE,QAAO;;;;;;;;;;;;AAaT,SAAgB,WACd,GACA,GACA,GACA,KACA,KACA,QACA,WACY;CACZ,MAAM,QAAQ,EAAE,GAAG,EAAE,OAAO;AAC5B,KAAI,EAAE,SAAS,aACb;OAAK,MAAM,KAAK,EAAE,SAAS,WAAW,EAAE,CACtC,KAAI,EAAE,SAAS,cAAc,EAAE,GAAI,OAAM,EAAE,MAAM,EAAE,QAAQ;;CAG/D,MAAM,MAAM,OAAO,EAAE,QAAQ,WAAY,MAAM,EAAE,KAAK,OAAO,IAAI,OAAO,EAAE,IAAI,GAAI;CAClF,MAAM,QAAqB;EACzB,SAAS,OAAO,QAAQ,EAAE,UAAU,MAAM,EAAE;EAC5C;EACA,QAAQ,EAAE;EACV,MAAM,EAAE;EACR,MAAM;EACP;AACD,KAAI,EAAE,SAAS,aACb;OAAK,MAAM,KAAK,EAAE,SAAS,WAAW,EAAE,CACtC,KAAI,EAAE,SAAS,WAAW,EAAE,QAAQ,IAAI,MAAM,CAAE,OAAM,SAAS,SAAS,EAAE,QAAQ,GAAG;WAC5E,EAAE,SAAS,WAAY,OAAM,OAAO,WAAW,EAAE,QAAQ,KAAK,EAAE,MAAM;;CAGnF,MAAM,SAAS,UAAU,GAAG,GAAG,KAAK,OAAO;CAE3C,MAAM,OAAO,WAAW,YAAY,GADrB,IACgC,GAAG,KAAK,MAAM,EAAE,QAAQ,aAAa,KAAK;CACzF,MAAM,QAAQ,aAAa,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,GAAG,KAAK,GAAG;AACxD,QAAO;EACL;EACA,KAAK,OAAO,QAAQ,EAAE,UAAU,MAAM;EACtC,UAAU,MAAM,MAAM,OAAO,OAAO,GAAG;EACvC;EACD;;;AAIH,SAAS,OAAO,IAAoB;AAClC,QAAO,qBAAqB,KAAK,GAAG,GAAG,GAAG,MAAM,GAAG,GAAG,GAAG;;;;;;AAO3D,SAAgB,cAAc,KAAa,MAAM,iBAA2B;AAC1E,QAAO,IAAI,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI;;;AAQ5D,SAAgB,aACd,QACA,KACA,QAAQ,QAAQ,OAAO,UAAU,MACjC,WACQ;CACR,MAAM,OAAO,WAAW,QAAQ,IAAI;AACpC,KAAI,CAAC,WAAW,KAAK,CACnB,OAAM,IAAI,MAAM,oBAAoB,MAAM;CAE5C,MAAM,IAAI,UAAU,MAAM;CAC1B,MAAM,KACJ,aAAa,OAAO,CAAC,MAAM,MAAM,EAAE,OAAO,IAAI,IAC7C;EAAE,IAAI;EAAK,OAAO;EAAI,KAAK;EAAI,UAAU;EAAI;CAChD,MAAM,YAAY,OAAO,QAAQ,OAAO,YAAY,WAAW,QAAQ,OAAO,UAAU;CACxF,MAAM,MAAgB,CAAC,WAAW,GAAG,GAAG,CAAC;CACzC,MAAM,MAAM,aAAa,MAAM,OAAO;CACtC,MAAM,QAAQ,cAAc,SAAY,IAAI,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,MAAM,KAAK;CAC3F,IAAI,QAAQ,mBAAmB,GAAG;AAClC,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,CAAC,KAAK,MAAM,CAAE;EAClB,IAAI;AACJ,MAAI;AACF,OAAI,KAAK,MAAM,KAAK;UACd;AACN;;EAEF,MAAM,OAAO,WAAW,GAAG,OAAO,GAAG,GAAG,OAAO,IAAI,QAAW,QAAW,UAAU;AACnF,MAAI,KAAK,IAAK,KAAI,KAAK,EAAE,OAAO,MAAM,KAAK,IAAI,KAAK,CAAC;AACrD,MAAI,KAAK,GAAG,KAAK,MAAM;AACvB,UAAQ,KAAK;;AAEf,QAAO,IAAI,KAAK,KAAK;;;;;;;;AAkBvB,SAAgB,YACd,gBACA,OACA,UACS;AACT,QAAO,kBAAmB,UAAU,UAAa,UAAU,aAAa,CAAC;;;;;;;AA6B3E,SAAgB,kBAAkB,GAA6C;AAC7E,SAAQ,QAAgB;EACtB,MAAM,OAAO,IAAI,MAAM;AACvB,MAAI,CAAC,KAAM;EACX,MAAM,KAAK,EAAE,QAAQ;AACrB,MAAI,OAAO,KAAM;AACjB,IAAE,IAAI,IAAI,KAAK,CAAC,WACP,EAAE,OAAO,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,OAAO,aAAa,KAAK,CAAC,GACrE,MAAa;AACZ,OAAI,EAAE,YAAY,GAAG,CAAE,GAAE,OAAO,MAAM,GAAG;OACpC,GAAE,KAAK,EAAE,MAAM,OAAO,KAAK,EAAE,UAAU,CAAC;IAEhD;;;;;;;;;;;;;;;;AA2CL,eAAsB,cACpB,QACA,QACA,SACA,UACA,MAAyB,QAAQ,KACjC,QAAQ,QAAQ,OAAO,UAAU,MACjC,IACe;CACf,MAAM,IAAI,UAAU,MAAM;CAC1B,MAAM,OAAO,IAAI,UAAU,QAAQ;CACnC,MAAM,MAAM,IAAI,SAAS,QAAQ;CAEjC,MAAM,MAAM,KAAK,UAAU,QAAQ,IAAI,cAAc;CACrD,MAAM,OAAO,IAAI,oBAAoB;CACrC,MAAM,WAAW,UAAU,UAAU,KAAK,cAAc,IAAI;CAC5D,MAAM,0BAAU,IAAI,KAA2B;CAC/C,MAAM,6BAAa,IAAI,KAAa;CACpC,MAAM,2BAAW,IAAI,KAAa;CAClC,MAAM,yBAAS,IAAI,KAA0B;CAC7C,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,YAA2B;CAC/B,IAAI,UAAU;CAEd,IAAI,cAAc,KAAK,KAAK;CAC5B,IAAI,cAAmC;CACvC,MAAM,cAAc;AAClB,YAAU;;AAEZ,SAAQ,KAAK,UAAU,MAAM;CAG7B,MAAM,OAAO,OAAO,WAAW;CAC/B,IAAI,OAAO,KAAK,QAAQ;CACxB,MAAM,gBAAgC,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;CACxF,IAAI,OAAO;CACX,MAAM,mBAAmB,KAAK,IAAI,GAAG,OAAO,EAAE;;;CAG9C,MAAM,OAAO,SAAiB;AAC5B,MAAI,CAAC,MAAM;AACT,QAAK,MAAM,OAAO,KAAK;AACvB;;EAEF,MAAM,IAAI,eAAe,MAAM,MAAM,YAAY,CAAC;AAClD,OAAK,MAAM,EAAE,IAAI;AACjB,SAAO,EAAE;;CAUX,MAAM,sBAAsB;AAC1B,MAAI,KAAM;AACV,MAAI,IAAK,MAAK,MAAM,WAAW;;CAEjC,IAAI,SAAS,oBAAoB;CACjC,MAAM,sBAAsB;AAC1B,MAAI,CAAC,IAAK;EACV,MAAM,OAAO,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,KAAK,GAAG,eAAe,IAAK,CAAC;EACvE,MAAM,QAAQ,cAAc,aAAa,GAAG,YAAY,GAAG;EAC3D,MAAM,OAAO,WAAW,MAAM,OAAO,QAAQ,OAAO,MAAM,MAAM;AAChE,MAAI,KAAM,MAAK,MAAM,cAAc,MAAM,KAAK,CAAC;OAC1C;AACH,QAAK,MAAM,WAAW;AACtB,QAAK,MAAM,KAAK;;;CAIpB,MAAM,mBACJ,aAAa,OAAO,CACjB,QACE,MACC,EAAE,UAAU,aACZ,MAAM,EAAE,IAAI,KACX,WAAW,QAAQ,YAAY,WAAW,cAAc,GAAG,KAAK,GAAG,OACvE,CACA,KAAK,MAAM,EAAE,GAAG;CAGrB,MAAM,aAAa,GAAoB,KAAa,IAAkB,UAAmB;AACvF,MAAI,EAAE,SAAS,cAAc,QAAQ,eAAe,OAAO,EAAE,QAAQ,GAAG,CAAC,CAAE;AAC3E,MAAI,CAAC,WAAW,IAAI,IAAI,EAAE;AACxB,kBAAe;AACf,OAAI,WAAW,GAAG,GAAG,CAAC;AACtB,cAAW,IAAI,IAAI;;EAGrB,MAAM,OAAO,WACX,GAFY,OAAO,IAAI,IAAI,IAAI,oBAAoB,EAInD,GACA,GAAG,OAAO,IACV,QAAQ,EAAE,QAAQ,IAAI,MAAM,GAAG,CAAC,GAAG,QACnC,QACA,SAAS,CACV;AACD,MAAI,KAAK,IACP,KAAI,EAAE,OAAO,MAAM,KAAK,IAAI,KAAK,CAAC;AAEpC,SAAO,IAAI,KAAK,KAAK,MAAM;AAC3B,MAAI,QAAQ,OAAQ,UAAS,KAAK;AAClC,iBAAe;AACf,OAAK,MAAM,MAAM,KAAK,MACpB,KAAI,GAAG;AAET,MAAI,KAAK,SAAU,eAAc,KAAK,KAAK;AAC3C,MAAI,EAAE,SAAS,UAAU;AACvB,iBAAc;AACd,YAAS,IAAI,IAAI;aACR,QAAQ,UAAU,WAAW,KACtC,eAAc;;CAKlB,MAAM,gBAAgB,KAAa,MAAc,IAAkB,UAAiC;EAClG,MAAM,SAAS;GAAE,IAAI,SAAS,MAAM,IAAI;GAAE,KAAK;GAAI;AACnD,MAAI;GACF,MAAM,WAAW,aAAa,MAAM,OAAO;AAC3C,OAAI,SAAS,MAAM,CACjB,MAAK,MAAM,QAAQ,cAAc,SAAS,EAAE;IAC1C,IAAI;AACJ,QAAI;AACF,SAAI,KAAK,MAAM,KAAK;YACd;AACN;;AAEF,cAAU,GAAG,KAAK,IAAI,MAAM;;YAI1B,CAAC,WAAW,IAAI,IAAI,EAAE;AACxB,QAAI,WAAW,GAAG,GAAG,CAAC;AACtB,eAAW,IAAI,IAAI;;GAIvB,MAAM,OAAO,OAAO,MAAM,MAAM;AAChC,YAAS;IACP,IAAI;AACJ,QAAI;AACF,SAAI,SAAS,OAAO,IAAI,MAAM,GAAG,KAAK,QAAQ,KAAK;YAC7C;AACN;;AAEF,QAAI,KAAK,EAAG;;UAER;AAGR,SAAO;;CAIT,MAAM,YAAY,MAAc;AAC9B,iBAAe;AACf,MAAI,EAAE;;CAIR,MAAM,cACJ,IAAI,iBACF,IAAY,SACZ,MAAM,OAAO;EAAC;EAAU;EAAU;EAAI;EAAM;EAAc;EAAY,EAAE,EACtE,OAAO;EAAC;EAAU;EAAQ;EAAU,EACrC,CAAiB;CACtB,MAAM,gBAAgB,MAAc,OAAe;AACjD,WAAS,EAAE,OAAO,cAAc,GAAG,IAAI,CAAC;EACxC,MAAM,QAAQ,YAAY,IAAI,KAAK;EACnC,IAAI,QAAQ;AACZ,QAAM,QAAQ,GAAG,SAAS,UAAkB;AAC1C,YAAS,MAAM,SAAS,OAAO;IAC/B;AACF,QAAM,GAAG,UAAU,OAAsB;GACvC,MAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,IAAI;AAChD,OAAI,OAAO,KAAK,oBAAoB,KAAK,MAAM,EAAE;AAC/C,aAAS,EAAE,OAAO,gBAAgB,QAAQ,CAAC;AAC3C,aAAS;AACT,aAAS,OAAO,MAAM;AACtB,eAAW,OAAO,MAAM;AACxB,WAAO,OAAO,MAAM;AACpB,aAAS,mBAAmB,UAAU;AACtC,kBAAc,KAAK,KAAK;SAExB,UAAS,EAAE,OAAO,uBAAuB,GAAG,GAAG,CAAC;IAElD;;CAEJ,MAAM,qBAAqB,kBAAkB;EAC3C,cAAc;EACd,MAAM,IAAI,SAAS,YAAY,QAAQ,IAAI,KAAK;EAChD,cAAc,OAAO,aAAa,OAAO,CAAC,MAAM,MAAM,EAAE,OAAO,GAAG;EAClE,QAAQ;EACR,MAAM;EACN,OAAO;EACP,GAAI,OAAO,EAAE,YAAY,QAAW,GAAG,EAAE;EAC1C,CAAC;CAMF,MAAM,aAAc,IAA4B,UAAU;CAC1D,IAAI,SAAS;CACb,IAAI,OAAkD;CACtD,IAAI,WAAgC;CAIpC,MAAM,yBAAS,IAAI,KAA2C;CAC9D,MAAM,kBAAkB,SAA0B;EAChD,MAAM,IAAI,OAAO,IAAI,KAAK;AAC1B,MAAI,CAAC,KAAK,KAAK,KAAK,GAAG,EAAE,MAAO,QAAO;AACvC,IAAE,KAAK;AACP,MAAI,EAAE,KAAK,EAAG,QAAO,OAAO,KAAK;AACjC,SAAO;;CAET,MAAM,mBAAmB;AACvB,MAAI,CAAC,QAAQ,WAAY;AACzB,OAAK,MAAM,cAAc,MAAM,aAAa,SAAS,EAAE,OAAO,UAAU,GAAG,OAAU,CAAC;;AAExF,KAAI,MAAM;EACR,MAAM,gBAAgB,SAAiB;GACrC,MAAM,KAAK;AACX,OAAI,OAAO,KAAM;AACjB,UAAO,IAAI,MAAM;IAAE,GAAG;IAAG,OAAO,KAAK,KAAK,GAAG;IAAQ,CAAC;GAEtD,MAAM,OAAO,WACX,GAFS,OAAO,IAAI,GAAG,IAAI,oBAAoB,EAI/C;IAAE,MAAM;IAAY,sBAAK,IAAI,MAAM,EAAC,aAAa;IAAE;IAAM,EACzD,IACA,QACA,QACA,SAAS,CACV;AACD,OAAI,KAAK,IAAK,KAAI,EAAE,OAAO,MAAM,KAAK,IAAI,KAAK,CAAC;AAChD,QAAK,MAAM,MAAM,KAAK,MAAO,KAAI,GAAG;AACpC,UAAO,IAAI,IAAI,KAAK,MAAM;;EAE5B,MAAM,kBAAkB,QAAgB;AACtC,YAAS;GACT,MAAM,MAAM,cAAc,IAAI;AAC9B,WAAQ,IAAI,MAAZ;IACE,KAAK;AACH,SAAI,CAAC,IAAI,KAAM;AACf,kBAAa,IAAI,KAAK;AACtB,wBAAmB,IAAI,KAAK;AAC5B;IACF,KAAK;AACH,SAAI,CAAC,IAAI,MAAM;AACb,UAAI,EAAE,OAAO,wBAAwB,CAAC;AACtC;;AAEF,kBAAa,IAAI,KAAK;AACtB,SAAI,WAAW,KAAM,cAAa,IAAI,MAAM,OAAO;AACnD;IACF,KAAK;AACH,UAAK,MAAM,MAAM,UAAW,KAAI,EAAE,OAAO,GAAG,CAAC;AAC7C;IACF,KAAK,UAAU;KACb,MAAM,IACJ,WAAW,OAAO,aAAa,OAAO,CAAC,MAAM,MAAM,EAAE,OAAO,OAAO,GAAG;AACxE,SAAI,EAAE,OAAO,IAAI,GAAG,EAAE,GAAG,KAAK,EAAE,MAAM,KAAK,EAAE,SAAS,GAAG,UAAU,IAAI,cAAc,CAAC;AACtF;;IAEF,KAAK;AACH,eAAU;AACV;;AAEJ,eAAY;;AAEd,SAAO,gBAAgB;GACrB,OAAO;GACP,QAAQ;GACR,UAAU;GACX,CAAC;AACF,OAAK,GAAG,QAAQ,eAAe;AAE/B,OAAK,GAAG,gBAAgB;AACtB,QAAK,MAAM,QAAQ,IAAI,MAAM,KAAK,GAAI,WAAU;OAC3C,OAAM,MAAM,MAAM;IAAE,MAAM;IAAM,MAAM;IAAK,CAAC;IACjD;AACF,OAAK,GAAG,eAAe;AACrB,aAAU;IACV;AACF,OAAK,MAAM,UAAU,KAAK,CAAC;AAC3B,MAAI,YAAY;AACd,QAAK,UAAU,YAAY;AAC3B,QAAK,QAAQ;AACb,QAAK,MAAM,EAAE,OAAO,UAAU,CAAC;QAE/B,aAAY;AAGd,mBAAiB;AACf,OAAI,OAAO,KAAK,SAAS,SAAU,QAAO,KAAK;AAC/C,UAAO;AACP,QAAK,MAAM,iBAAiB,KAAK,CAAC;AAClC,eAAY;;AAEd,OAAK,KAAK,UAAU,SAAS;YACpB,WAAW,QAAS,IAA4B,OAAO;AAEhE,SAAO,gBAAgB,EAAE,OAAO,KAAK,CAAC;AACtC,OAAK,GAAG,QAAQ,mBAAmB;;;CAIrC,MAAM,mBAAoB,IAAI,aAAa,GAAG,YAAY,GAAI,MAAM,QAAQ;AAE5E,KAAI;AACF,WAAS;AACP,OAAI,QAAS;GACb,MAAM,WAAW,IAAI,IAAI,aAAa,OAAO,CAAC,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;GACpE,MAAM,SAAS,SAAS,CAAC,OAAO,GAAG,YAAY;AAC/C,QAAK,MAAM,OAAO,QAAQ;AACxB,QAAI,QAAQ,IAAI,IAAI,IAAI,SAAS,IAAI,IAAI,CAAE;IAC3C,MAAM,OAAO,WAAW,QAAQ,IAAI;IACpC,MAAM,KAAK,SAAS,IAAI,IAAI,IAAK;KAAE,IAAI;KAAK,OAAO;KAAI,KAAK;KAAI,UAAU;KAAI;AAC9E,QAAI,WAAW,KAAK,EAAE;KACpB,MAAM,QAAQ,WAAW,QAAQ,QAAQ,OAAO;AAChD,aAAQ,IAAI,KAAK,aAAa,KAAK,MAAM,IAAI,MAAM,CAAC;AACpD,YAAO,IAAI,KAAK,OAAO,IAAI,IAAI,IAAI,oBAAoB,CAAC;eAC/C,CAAC,WAAW,IAAI,IAAI,EAAE;AAE/B,SAAI,WAAW,GAAG,GAAG,CAAC;AACtB,gBAAW,IAAI,IAAI;;;GAGvB,MAAM,QAAQ,QAAQ,OAAO,KAAK,WAAW;GAC7C,IAAI,aAAa;AAEjB,QAAK,MAAM,CAAC,KAAK,MAAM,CAAC,GAAG,QAAQ,SAAS,CAAC,EAAE;IAC7C,MAAM,KAAK,SAAS,IAAI,IAAI,IAAK;KAAE,IAAI;KAAK,OAAO;KAAI,KAAK;KAAI,UAAU;KAAI;IAE9E,MAAM,SAAS,OAAO,MAAM,MAAM;AAClC,aAAS;KACP,IAAI;AACJ,SAAI;AACF,UAAI,SAAS,EAAE,IAAI,QAAQ,GAAG,OAAO,QAAQ,KAAK;aAC5C;AACN,UAAI;;AAEN,SAAI,KAAK,EAAG;AACZ,OAAE,OAAO,OAAO,SAAS,QAAQ,GAAG,EAAE;;IAExC,MAAM,QAAQ,EAAE,IAAI,MAAM,KAAK;AAC/B,MAAE,MAAM,MAAM,KAAK,IAAI;AACvB,SAAK,MAAM,QAAQ,OAAO;AACxB,SAAI,CAAC,KAAK,MAAM,CAAE;AAClB,kBAAa;KACb,IAAI;AACJ,SAAI;AACF,UAAI,KAAK,MAAM,KAAK;aACd;AACN;;AAEF,eAAU,GAAG,KAAK,IAAI,MAAM;;AAG9B,QADc,YAAY,SAAS,IAAI,IAAI,EAAE,GAAG,OAAO,MAAM,GAAG,IAAI,CAAC,EAC1D;AACT,SAAI,CAAC,SAAS,IAAI,IAAI,EAAE;AACtB,qBAAe;AACf,UACE,GAAG,QAAQ,EAAE,QAAQ,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE,OAAO,MAAM,GAAG,OAAO,EAAE,OAAO,QAAQ,GAAG,SAAS,SAAS,CAAC,KAAK,GAAG,QAAQ,KACvH;AACD,eAAS,IAAI,IAAI;;AAEnB,eAAU,EAAE,GAAG;AACf,aAAQ,OAAO,IAAI;;;GAIvB,MAAM,WAAW;AACjB,OAAI,aAAa,QAAQ,SAAS,IAAI,SAAS,EAAE;AAC/C,QAAI,UAAU;AAGZ,SAAI,aAAa,YAAY,CAAC,EAAE;AAC9B,qBAAe;AACf,YAAM,MAAM,IAAI;AAChB;;KAEF,MAAM,QAAQ,KAAK,KAAK,GAAG,WAAW;AACtC,YACE,KAAK,KAAK,GAAG,SACb,CAAC,WACD,WAAW,YACX,CAAC,aAAa,YAAY,CAAC,EAC3B;AACA,qBAAe;AACf,YAAM,MAAM,IAAI;;AAGlB,SAAI,WAAW,WAAW,YAAY,aAAa,YAAY,CAAC,CAAE;AAClE,oBAAe;AACf,SAAI,EAAE,OAAO,UAAU,CAAC;AACxB;;AAIF,QAAI,SAAS,KAAM;;AAErB,OAAI,YAAY,CAAC,QACf;QAAI,YAAY,CAAC,OACf,aAAY;aACH,cAAc,KACvB,aAAY,KAAK,KAAK;aACb,KAAK,KAAK,GAAG,WAAW,OAAU,KAAK,KAAK,GAAG,aAAa,WAAW,KAAM;AACtF,oBAAe;AACf,SAAI,EAAE,OAAO,UAAU,CAAC;AACxB;;;AAGJ,OAAI,CAAC,YAAY;AACf,mBAAe;AACf,UAAM,MAAM,IAAI;;;WAGZ;AAGR,QAAM,OAAO;AACb,UAAQ,eAAe,UAAU,MAAM;AACvC,MAAI,SAAU,MAAK,iBAAiB,UAAU,SAAS;AACvD,MAAI,KAAM,MAAK,MAAM,UAAU,KAAK,CAAC;AACrC,OAAK,MAAM,KAAK,QAAQ,QAAQ,CAC9B,KAAI;AACF,aAAU,EAAE,GAAG;UACT;;;AAOd,SAAS,MAAM,IAA2B;AACxC,QAAO,IAAI,SAAS,MAAM,WAAW,GAAG,GAAG,CAAC;;;AAQ9C,SAAgB,iBACd,QACA,MACA,KACA,sBAAY,IAAI,MAAM,EACd;AAOR,QAAO,iBANU,aAAa,OAAO,CACf,QAAQ,MAAM;EAClC,MAAM,YAAY,QAAQ,cAAc,GAAG,KAAK;EAChD,MAAM,UAAU,OAAO,EAAE,IAAI,WAAW,IAAI;AAC5C,SAAO,aAAc,CAAC,QAAQ;GAC9B,EAC4B,IAAI;;;;;;;;;;;;;;;;;;ACjwBpC,SAAgB,YAAY,OAAwC;AAClE,KAAI,CAAC,8BAA8B,KAAK,MAAM,KAAK,CACjD,OAAM,IAAI,mBACR,kBAAkB,MAAM,KAAK,6CAC9B;AAEH,KAAI,MAAM,aAAa,YAAY,CAAC,MAAM,YACxC,OAAM,IAAI,mBACR,8IAED;AAEH,KAAI,CAAC,MAAM,WAAW,MAAM,aAAa,SACvC,OAAM,IAAI,mBAAmB,sBAAsB;CAGrD,IAAI,UAAU,MAAM,WAAW;AAC/B,KAAI,MAAM,QAAQ,QAAW;EAC3B,MAAM,MAAM,SAAS;AACrB,MAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;EACzD,MAAM,OAAO,GAAG,IAAI,GAAG,MAAM;AAC7B,gBAAc,MAAM,MAAM,IAAI,MAAM,GAAG,MAAM;GAAE,UAAU;GAAQ,MAAM;GAAO,CAAC;AAC/E,MAAI;AACF,aAAU,MAAM,IAAM;UAChB;AAGR,YAAU;;CAGZ,MAAM,EAAE,KAAK,YAAY,oBAAoB;CAC7C,MAAM,WAA2B;EAC/B,SAAS;EACT,UAAU,MAAM,YAAY;EAC5B,SAAS,MAAM;EACf;EACA,QAAQ,MAAM,YACV;GAAE,SAAS,MAAM;GAAO,MAAM,MAAM;GAAW,GAC/C,EAAE,SAAS,MAAM,OAAO;EAC5B,KAAK,MAAM,OAAO,EAAE;EACpB,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE;EAC1C,GAAI,MAAM,cAAc,EAAE,aAAa,MAAM,aAAa,GAAG,EAAE;EAC/D,GAAI,MAAM,UAAU,MAAM,WAAW,WAAW,EAAE,QAAQ,MAAM,QAAQ,GAAG,EAAE;EAC7E,GAAI,MAAM,aAAa,EAAE,YAAY,MAAM,YAAY,GAAG,EAAE;EAC5D,GAAI,MAAM,gBAAgB,EAAE,eAAe,MAAM,eAAe,GAAG,EAAE;EACrE,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,UAAU,GAAG,EAAE;EACtD,GAAI,MAAM,MAAM,SAAS,EAAE,MAAM,MAAM,MAAgC,GAAG,EAAE;EAC7E;AACD,SAAQ,UAAU,MAAM,QAAQ;AAIhC,KADc,OAAO,KAAK,QAAQ,UAAU,CAAC,WAAW,GAC7C;AACT,UAAQ,UAAU;AAClB,UAAQ,SAAS,MAAM;AACvB,UAAQ,SAAS,QAAQ,UAAU;EACnC,MAAM,OAAO,MAAM,YAAY,GAAG,MAAM,KAAK,SAAS,MAAM;AAC5D,UAAQ,UAAU;GAChB,OAAO;GACP,MAAM,MAAM;GACZ,WAAW,MAAM;GACjB,QAAQ,MAAM;GACd,UAAU,MAAM;GAChB,WAAW;GACX,QAAQ;GACR,SAAS,MAAM;GACf,OAAO,MAAM;GACd;;AAGH,qBAAoB,KAAK,QAAQ;AACjC,QAAO;;;AAIT,SAAgB,eACd,MACA,SACe;CACf,MAAM,EAAE,KAAK,YAAY,oBAAoB;CAC7C,MAAM,IAAI,QAAQ,UAAU;AAC5B,KAAI,CAAC,EACH,OAAM,IAAI,mBACR,sBAAsB,KAAK,iBAAiB,OAAO,KAAK,QAAQ,UAAU,CAAC,KAAK,KAAK,IAAI,WAC1F;AAEH,KAAI,QAAQ,aAAa,QAAW;AAClC,MAAI,CAAC,OAAO,UAAU,QAAQ,SAAS,IAAI,QAAQ,WAAW,KAAK,QAAQ,WAAW,EACpF,OAAM,IAAI,mBAAmB,gEAAgE;AAE/F,IAAE,WAAW,QAAQ;;AAEvB,KAAI,QAAQ,SAAS,QAAW;AAC9B,OAAK,MAAM,KAAK,QAAQ,KACtB,KAAI,CAAE,cAAoC,SAAS,EAAE,CACnD,OAAM,IAAI,mBAAmB,IAAI,EAAE,wBAAwB,cAAc,KAAK,KAAK,CAAC,GAAG;AAG3F,IAAE,OAAO,QAAQ;;AAEnB,qBAAoB,KAAK,QAAQ;AACjC,QAAO;;AAGT,SAAgB,eAAe,MAA6B;CAC1D,MAAM,EAAE,KAAK,YAAY,oBAAoB;AAC7C,KAAI,CAAC,QAAQ,UAAU,MACrB,OAAM,IAAI,mBAAmB,sBAAsB,KAAK,GAAG;AAE7D,QAAO,QAAQ,UAAU;AACzB,MAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,QAAQ,QAAQ,CAEzD,MADuB,OAAO,WAAW,WAAW,OAAO,MAAM,IAAI,CAAC,KAAK,OAAO,cAC3D,KAAM,QAAO,QAAQ,QAAQ;AAEtD,KAAI,QAAQ,WAAW,KAAM,SAAQ,SAAS;AAC9C,qBAAoB,KAAK,QAAQ;AACjC,QAAO;;AAGT,SAAgB,YAAY,MAA6B;CACvD,MAAM,EAAE,KAAK,YAAY,oBAAoB;AAC7C,KAAI,CAAC,QAAQ,UAAU,MACrB,OAAM,IAAI,mBACR,sBAAsB,KAAK,iBAAiB,OAAO,KAAK,QAAQ,UAAU,CAAC,KAAK,KAAK,IAAI,WAC1F;AAEH,SAAQ,SAAS;AACjB,qBAAoB,KAAK,QAAQ;AACjC,QAAO;;AAGT,SAAgB,mBAAmB,MAAc,SAAiC;CAChF,MAAM,EAAE,KAAK,YAAY,oBAAoB;CAC7C,MAAM,IAAI,QAAQ,UAAU;AAC5B,KAAI,CAAC,EAAG,OAAM,IAAI,mBAAmB,sBAAsB,KAAK,GAAG;AACnE,GAAE,UAAU;AACZ,qBAAoB,KAAK,QAAQ;AAEjC,KAAI,QAAS,eAAc,cAAc,mBAAmB,IAAI,QAAQ,CAAC,EAAE,KAAK;AAChF,QAAO;;;;;;;AAQT,SAAgB,SAAS,MAAc,QAAoC;CACzE,MAAM,EAAE,KAAK,YAAY,oBAAoB;AAC7C,KAAI,OAAO,WAAW,UAAU;EAC9B,MAAM,CAAC,UAAU,SAAS,OAAO,MAAM,IAAI;EAC3C,MAAM,IAAI,QAAQ,UAAU;AAC5B,MAAI,CAAC,EACH,OAAM,IAAI,mBACR,sBAAsB,SAAS,QAAQ,OAAO,iBAAiB,OAAO,KAAK,QAAQ,UAAU,CAAC,KAAK,KAAK,IAAI,WAC7G;AAEH,MAAI,SAAS,UAAU,aAAa,UAAU,OAC5C,OAAM,IAAI,mBACR,wBAAwB,MAAM,2CAC/B;AAEH,MAAI,UAAU,UAAU,CAAC,EAAE,OAAO,KAChC,OAAM,IAAI,mBAAmB,aAAa,SAAS,gCAAgC;YAE5E,OAAO,UAChB;MAAI,CAAC,QAAQ,UAAU,OAAO,UAC5B,OAAM,IAAI,mBACR,sBAAsB,OAAO,SAAS,iBAAiB,OAAO,KAAK,QAAQ,UAAU,CAAC,KAAK,KAAK,IAAI,WACrG;;AAGL,SAAQ,QAAQ,QAAQ;AACxB,qBAAoB,KAAK,QAAQ;AACjC,QAAO;;AAGT,SAAgB,WAAW,MAA6B;CACtD,MAAM,EAAE,KAAK,YAAY,oBAAoB;AAC7C,KAAI,EAAE,QAAQ,QAAQ,SACpB,OAAM,IAAI,mBAAmB,mBAAmB,KAAK,GAAG;AAE1D,QAAO,QAAQ,QAAQ;AACvB,qBAAoB,KAAK,QAAQ;AACjC,QAAO;;AAGT,SAAgB,kBAAkB,SAAiC;CACjE,MAAM,EAAE,KAAK,YAAY,oBAAoB;AAC7C,SAAQ,UAAU;AAClB,qBAAoB,KAAK,QAAQ;AACjC,QAAO;;;AAIT,SAAgB,gBAAgB,QAA6B;AAC3D,KAAI,OAAO,WAAW,SAAU,QAAO;AAQvC,QAPa;EACX,OAAO,YAAY;EACnB,GAAI,OAAO,KAAK,SAAS,CAAC,OAAO,OAAO,IAAI,KAAK,IAAI,CAAC,GAAG,GAAG,EAAE;EAC9D,GAAI,OAAO,gBAAgB,SAAY,CAAC,YAAY,OAAO,cAAc,GAAG,EAAE;EAC9E,GAAI,OAAO,aAAa,SAAS,CAAC,SAAS,OAAO,YAAY,KAAK,IAAI,GAAG,GAAG,EAAE;EAC/E,GAAI,OAAO,OAAO,SAAS,CAAC,UAAU,OAAO,MAAM,KAAK,IAAI,CAAC,GAAG,GAAG,EAAE;EACtE,CACW,KAAK,IAAI;;;AAIvB,SAAgB,kBAAkB,SAAkC;CAClE,MAAM,QAAkB,EAAE;CAC1B,MAAM,QAAQ,OAAO,KAAK,QAAQ,UAAU;AAC5C,KAAI,CAAC,MAAM,QAAQ;AACjB,QAAM,KAAK,yCAAyC;AACpD,QAAM,KACJ,uFACD;AACD,SAAO;;AAET,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,IAAI,QAAQ,UAAU;EAC5B,MAAM,QAAQ,CACZ,EAAE,UAAU,YAAY,YACxB,QAAQ,WAAW,OAAO,WAAW,KACtC,CAAC,OAAO,QAAQ;EACjB,MAAM,QAAQ,EAAE,aAAa,WAAW,EAAE,GAAG;EAC7C,MAAM,YAAY,UAAU,OAAO,KAAK,WAAW,MAAM,aAAa,mBAAmB,EAAE,CAAC;EAC5F,MAAM,WAAW,CAAC,QAAQ,iBAAiB,EAAE,IAAI,GAAI,EAAE,QAAQ,EAAE,CAAE,CAAC,KAAK,KAAK;AAC9E,QAAM,KAAK,GAAG,KAAK,KAAK,MAAM,KAAK,KAAK,CAAC,KAAK,EAAE,UAAU;AAC1D,QAAM,KACJ,aAAa,EAAE,OAAO,UAAU,EAAE,OAAO,OAAO,WAAW,EAAE,OAAO,KAAK,KAAK,KAAK,YACpF;AACD,QAAM,KAAK,OAAO,WAAW;AAC7B,MAAI,EAAE,QAAS,OAAM,KAAK,gBAAgB,WAAW,EAAE,QAAQ,GAAG;MAC7D,OAAM,KAAK,kCAAkC;AAClD,MAAI,EAAE,KAAM,OAAM,KAAK,OAAO,EAAE,OAAO;AACvC,MAAI,EAAE,aAAa,SACjB,OAAM,KAAK,uBAAuB,EAAE,eAAe,0BAA0B;AAE/E,MAAI,EAAE,WAAW,QACf,OAAM,KAAK,gDAAgD;;CAG/D,MAAM,WAAW,OAAO,KAAK,QAAQ,QAAQ;AAC7C,KAAI,SAAS,OACX,OAAM,KAAK,aAAa,SAAS,KAAK,MAAM,GAAG,EAAE,IAAI,QAAQ,QAAQ,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,GAAG;CAErG,MAAM,aAAa,OAAO,KAAK,QAAQ,QAAQ;AAC/C,KAAI,WAAW,OACb,OAAM,KAAK,YAAY,WAAW,KAAK,OAAO,GAAG,GAAG,GAAG,gBAAgB,QAAQ,QAAQ,IAAI,GAAG,CAAC,KAAK,KAAK,GAAG;AAE9G,KAAI,QAAQ,WAAW,OACrB,OAAM,KAAK,0BAA0B,QAAQ,QAAQ,MAAM,KAAK,KAAK,CAAC,cAAc,QAAQ,QAAQ,gBAAgB,GAAG;AAEzH,QAAO"}