agentix-cli 0.146.0 → 0.148.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{App-EU2AJWW5.js → App-2O2UUZGG.js} +2 -2
- package/dist/{ChatApp-ORR3U3AN.js → ChatApp-YU4NF63L.js} +2 -2
- package/dist/{absorb-eval-3VGPDRWS.js → absorb-eval-WHWOJYDX.js} +2 -2
- package/dist/{agent-NVJNOK5L.js → agent-NLCR6VBE.js} +2 -2
- package/dist/{board-dashboard-JV2JNRWD.js → board-dashboard-KXSZIMWV.js} +216 -216
- package/dist/board-dashboard-KXSZIMWV.js.map +1 -0
- package/dist/{chunk-UFX6INNI.js → chunk-2MW32M2D.js} +2 -2
- package/dist/{chunk-5YAUENDI.js → chunk-3HVRWULP.js} +2 -2
- package/dist/{chunk-7O7VDA3J.js → chunk-5IHUZVPU.js} +2 -2
- package/dist/{chunk-USTNT7P7.js → chunk-5QXRPYOY.js} +2 -2
- package/dist/{chunk-3BMIZY6X.js → chunk-6QFDFYI7.js} +2 -2
- package/dist/{chunk-5TFXFLMN.js → chunk-6TJNWUMA.js} +3 -3
- package/dist/{chunk-MOCKFVYE.js → chunk-6YNRLI6I.js} +2 -2
- package/dist/{chunk-OEB6RTRA.js → chunk-AONY6U45.js} +2 -2
- package/dist/{chunk-WCOT35YG.js → chunk-ATCIRFH6.js} +2 -2
- package/dist/{chunk-O7ZJS33Z.js → chunk-BZ6Y264Z.js} +2 -2
- package/dist/{chunk-TYONASHA.js → chunk-CHBZHUIF.js} +41 -41
- package/dist/chunk-CHBZHUIF.js.map +1 -0
- package/dist/{chunk-NBDYBTI6.js → chunk-CJVAIAD7.js} +259 -259
- package/dist/chunk-CJVAIAD7.js.map +1 -0
- package/dist/{chunk-UVJLIHLL.js → chunk-DWOERPBG.js} +2 -2
- package/dist/chunk-E7LPZAXP.js +7 -0
- package/dist/chunk-E7LPZAXP.js.map +1 -0
- package/dist/{chunk-UTYNYG7M.js → chunk-EIY2HRAS.js} +2 -2
- package/dist/{chunk-OT5KLHUT.js → chunk-GMMRBTLQ.js} +2 -2
- package/dist/{chunk-GECGRCYC.js → chunk-GRI5HOPC.js} +2 -2
- package/dist/{chunk-UYLVSS6I.js → chunk-IGA3T3ZT.js} +2 -2
- package/dist/chunk-INT5PIF3.js +533 -0
- package/dist/chunk-INT5PIF3.js.map +1 -0
- package/dist/{chunk-WBG6NA7R.js → chunk-KP7KDLZ2.js} +2 -2
- package/dist/{chunk-QNS3WHGU.js → chunk-NMYC5RW7.js} +2 -2
- package/dist/{chunk-DTWBB7B7.js → chunk-NW4N67TU.js} +2 -2
- package/dist/chunk-OIMJLAWD.js +7 -0
- package/dist/chunk-OIMJLAWD.js.map +1 -0
- package/dist/{chunk-TZVJP4H3.js → chunk-OWMKQ4HZ.js} +2 -2
- package/dist/chunk-PXKIPU27.js +64 -0
- package/dist/chunk-PXKIPU27.js.map +1 -0
- package/dist/{chunk-DG2DC43E.js → chunk-PYHR4HXO.js} +2 -2
- package/dist/{chunk-SK24JFEK.js → chunk-S22IBRY6.js} +2 -2
- package/dist/chunk-S5NGIJDI.js +35 -0
- package/dist/chunk-S5NGIJDI.js.map +1 -0
- package/dist/chunk-X7UQY7CH.js +5 -0
- package/dist/chunk-X7UQY7CH.js.map +1 -0
- package/dist/{chunk-SNCRLEH3.js → chunk-Z7GEW2SR.js} +5 -5
- package/dist/cli.js +1 -1
- package/dist/{client-6KZZKCN2.js → client-A2N6ETK2.js} +2 -2
- package/dist/config-2QM2WTI2.js +2 -0
- package/dist/{config-mutate-57YT7BNB.js → config-mutate-UJS4F7BT.js} +2 -2
- package/dist/config-mutator-VWR3SMD3.js +2 -0
- package/dist/{contributions-Y6B6LG7X.js → contributions-43FWZOGF.js} +2 -2
- package/dist/doctor-5R6X7JH6.js +2 -0
- package/dist/{follow-up-tool-5AMZKVI4.js → follow-up-tool-VB7K6HCL.js} +2 -2
- package/dist/{hub-BZF3ZYQ5.js → hub-JNNNUAZM.js} +2 -2
- package/dist/index.d.ts +119 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/note-tool-EZWSB3J3.js +2 -0
- package/dist/note-tool-EZWSB3J3.js.map +1 -0
- package/dist/{program-MO7HLAP5.js → program-VS6QDZNX.js} +209 -209
- package/dist/program-VS6QDZNX.js.map +1 -0
- package/dist/{promote-IJANZNQK.js → promote-VFYVWVTL.js} +2 -2
- package/dist/{query-DC74NB3R.js → query-RB2MU2HV.js} +2 -2
- package/dist/{query-settings-RXFOATOZ.js → query-settings-5B54WHCS.js} +2 -2
- package/dist/{registry-24IXRDWX.js → registry-6OX7U6FV.js} +2 -2
- package/dist/{request-planner-33CWWCJU.js → request-planner-TUHVEYSF.js} +2 -2
- package/dist/{review-candidates-ENWB6LHD.js → review-candidates-FDYTGG2N.js} +2 -2
- package/dist/{routing-AUFJ4MNP.js → routing-U35SFPEI.js} +2 -2
- package/dist/schedule-tool-EPQKUYJX.js +2 -0
- package/dist/{scheduler-KWSBBZKW.js → scheduler-H2QHWNQ2.js} +2 -2
- package/dist/{seat-YGMLJ6KS.js → seat-EB3KJMIE.js} +2 -2
- package/dist/{select-JJGBM6ZT.js → select-IU4IOVSO.js} +2 -2
- package/dist/{set-enabled-THE7QIGQ.js → set-enabled-64BSJ7LR.js} +2 -2
- package/dist/{tool-4ACTSFKC.js → tool-PLM6Z42O.js} +2 -2
- package/dist/{tool-6LR6VORJ.js → tool-W3CIQZJ7.js} +2 -2
- package/dist/{wiki-YAW7UEOQ.js → wiki-4KMRUD5C.js} +2 -2
- package/dist/wiki-enrich-N4GLW2JX.js +2 -0
- package/package.json +1 -1
- package/dist/board-dashboard-JV2JNRWD.js.map +0 -1
- package/dist/chunk-IBK7AG4S.js +0 -62
- package/dist/chunk-IBK7AG4S.js.map +0 -1
- package/dist/chunk-J723FL3J.js +0 -7
- package/dist/chunk-J723FL3J.js.map +0 -1
- package/dist/chunk-K3QDK4BX.js +0 -5
- package/dist/chunk-K3QDK4BX.js.map +0 -1
- package/dist/chunk-MO7DH5L6.js +0 -535
- package/dist/chunk-MO7DH5L6.js.map +0 -1
- package/dist/chunk-NBDYBTI6.js.map +0 -1
- package/dist/chunk-S2GAEH2F.js +0 -13
- package/dist/chunk-S2GAEH2F.js.map +0 -1
- package/dist/chunk-TYONASHA.js.map +0 -1
- package/dist/config-W6JNVPBY.js +0 -2
- package/dist/config-mutator-6NWQPE7K.js +0 -2
- package/dist/doctor-ZP4KADAD.js +0 -2
- package/dist/program-MO7HLAP5.js.map +0 -1
- package/dist/schedule-tool-NAMOBMZE.js +0 -2
- package/dist/wiki-enrich-6ANFA2VB.js +0 -2
- /package/dist/{App-EU2AJWW5.js.map → App-2O2UUZGG.js.map} +0 -0
- /package/dist/{ChatApp-ORR3U3AN.js.map → ChatApp-YU4NF63L.js.map} +0 -0
- /package/dist/{absorb-eval-3VGPDRWS.js.map → absorb-eval-WHWOJYDX.js.map} +0 -0
- /package/dist/{agent-NVJNOK5L.js.map → agent-NLCR6VBE.js.map} +0 -0
- /package/dist/{chunk-UFX6INNI.js.map → chunk-2MW32M2D.js.map} +0 -0
- /package/dist/{chunk-5YAUENDI.js.map → chunk-3HVRWULP.js.map} +0 -0
- /package/dist/{chunk-7O7VDA3J.js.map → chunk-5IHUZVPU.js.map} +0 -0
- /package/dist/{chunk-USTNT7P7.js.map → chunk-5QXRPYOY.js.map} +0 -0
- /package/dist/{chunk-3BMIZY6X.js.map → chunk-6QFDFYI7.js.map} +0 -0
- /package/dist/{chunk-5TFXFLMN.js.map → chunk-6TJNWUMA.js.map} +0 -0
- /package/dist/{chunk-MOCKFVYE.js.map → chunk-6YNRLI6I.js.map} +0 -0
- /package/dist/{chunk-OEB6RTRA.js.map → chunk-AONY6U45.js.map} +0 -0
- /package/dist/{chunk-WCOT35YG.js.map → chunk-ATCIRFH6.js.map} +0 -0
- /package/dist/{chunk-O7ZJS33Z.js.map → chunk-BZ6Y264Z.js.map} +0 -0
- /package/dist/{chunk-UVJLIHLL.js.map → chunk-DWOERPBG.js.map} +0 -0
- /package/dist/{chunk-UTYNYG7M.js.map → chunk-EIY2HRAS.js.map} +0 -0
- /package/dist/{chunk-OT5KLHUT.js.map → chunk-GMMRBTLQ.js.map} +0 -0
- /package/dist/{chunk-GECGRCYC.js.map → chunk-GRI5HOPC.js.map} +0 -0
- /package/dist/{chunk-UYLVSS6I.js.map → chunk-IGA3T3ZT.js.map} +0 -0
- /package/dist/{chunk-WBG6NA7R.js.map → chunk-KP7KDLZ2.js.map} +0 -0
- /package/dist/{chunk-QNS3WHGU.js.map → chunk-NMYC5RW7.js.map} +0 -0
- /package/dist/{chunk-DTWBB7B7.js.map → chunk-NW4N67TU.js.map} +0 -0
- /package/dist/{chunk-TZVJP4H3.js.map → chunk-OWMKQ4HZ.js.map} +0 -0
- /package/dist/{chunk-DG2DC43E.js.map → chunk-PYHR4HXO.js.map} +0 -0
- /package/dist/{chunk-SK24JFEK.js.map → chunk-S22IBRY6.js.map} +0 -0
- /package/dist/{chunk-SNCRLEH3.js.map → chunk-Z7GEW2SR.js.map} +0 -0
- /package/dist/{client-6KZZKCN2.js.map → client-A2N6ETK2.js.map} +0 -0
- /package/dist/{config-W6JNVPBY.js.map → config-2QM2WTI2.js.map} +0 -0
- /package/dist/{config-mutate-57YT7BNB.js.map → config-mutate-UJS4F7BT.js.map} +0 -0
- /package/dist/{config-mutator-6NWQPE7K.js.map → config-mutator-VWR3SMD3.js.map} +0 -0
- /package/dist/{contributions-Y6B6LG7X.js.map → contributions-43FWZOGF.js.map} +0 -0
- /package/dist/{doctor-ZP4KADAD.js.map → doctor-5R6X7JH6.js.map} +0 -0
- /package/dist/{follow-up-tool-5AMZKVI4.js.map → follow-up-tool-VB7K6HCL.js.map} +0 -0
- /package/dist/{hub-BZF3ZYQ5.js.map → hub-JNNNUAZM.js.map} +0 -0
- /package/dist/{promote-IJANZNQK.js.map → promote-VFYVWVTL.js.map} +0 -0
- /package/dist/{query-DC74NB3R.js.map → query-RB2MU2HV.js.map} +0 -0
- /package/dist/{query-settings-RXFOATOZ.js.map → query-settings-5B54WHCS.js.map} +0 -0
- /package/dist/{registry-24IXRDWX.js.map → registry-6OX7U6FV.js.map} +0 -0
- /package/dist/{request-planner-33CWWCJU.js.map → request-planner-TUHVEYSF.js.map} +0 -0
- /package/dist/{review-candidates-ENWB6LHD.js.map → review-candidates-FDYTGG2N.js.map} +0 -0
- /package/dist/{routing-AUFJ4MNP.js.map → routing-U35SFPEI.js.map} +0 -0
- /package/dist/{schedule-tool-NAMOBMZE.js.map → schedule-tool-EPQKUYJX.js.map} +0 -0
- /package/dist/{scheduler-KWSBBZKW.js.map → scheduler-H2QHWNQ2.js.map} +0 -0
- /package/dist/{seat-YGMLJ6KS.js.map → seat-EB3KJMIE.js.map} +0 -0
- /package/dist/{select-JJGBM6ZT.js.map → select-IU4IOVSO.js.map} +0 -0
- /package/dist/{set-enabled-THE7QIGQ.js.map → set-enabled-64BSJ7LR.js.map} +0 -0
- /package/dist/{tool-4ACTSFKC.js.map → tool-PLM6Z42O.js.map} +0 -0
- /package/dist/{tool-6LR6VORJ.js.map → tool-W3CIQZJ7.js.map} +0 -0
- /package/dist/{wiki-YAW7UEOQ.js.map → wiki-4KMRUD5C.js.map} +0 -0
- /package/dist/{wiki-enrich-6ANFA2VB.js.map → wiki-enrich-N4GLW2JX.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/workflow-health.ts","../src/daemon/routines.ts","../src/approvals/state.ts","../src/approvals/inbox.ts","../src/whatsapp-triage/drafts.ts","../src/whatsapp-triage/wacli.ts","../src/requests/plan-store.ts","../src/requests/settings.ts","../src/daemon/mesh-auth.ts","../src/daemon/browser-origin.ts","../src/voice/voice-health.ts","../src/voice/system-voices.ts","../src/voice/siri.ts","../src/voice/agent-voice.ts","../src/business/work-pool.ts","../src/business/backlog-store.ts","../src/business/backlog-sync.ts","../src/business/clients.ts","../src/channels/ui-directive.ts","../src/channels/webrtc-signal.ts","../src/notify/hold.ts","../src/daemon/app-pair-code.ts","../src/approvals/popup.ts","../src/approvals/card-window.ts","../src/approvals/card-page.ts","../src/approvals/card-page-style.ts","../src/approvals/popup-runner.ts","../src/daemon/app-files-api.ts","../src/voice/speaker.ts","../src/voice/transcribe.ts","../src/daemon/voice-io-api.ts","../src/daemon/file-ops.ts","../src/computer-use/capture-settings.ts"],"sourcesContent":["// --- Is the automation still running? -------------------------------------\n//\n// A workflow that stops firing produces nothing: no error, no run, no page\n// entry. On this fleet 16 workflows produced 759 runs and then went quiet for\n// three days without anyone noticing, which is the failure this module exists\n// to make visible. Health is therefore about ABSENCE first and failure second.\n//\n// Cost note: RunStore.get() replays a whole run file. Polling that for every\n// run on every dashboard refresh is not affordable, so we read each file's\n// first line (the snapshot, which carries workflowId) and last line (the\n// terminal status), and cache per file keyed on mtime. Runs are append-only,\n// so after the first scan almost nothing is re-read.\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"fs\"\nimport { resolve } from \"path\"\n\nexport interface RunSummary { workflowId: string; status: string; at: number }\n\n/** Absence first: a workflow that used to fire and no longer does is the\n * headline, because nothing else in the product will ever mention it. */\nexport type WorkflowState = \"never\" | \"failing\" | \"dormant\" | \"active\" | \"quiet\"\n\nexport interface WorkflowHealth {\n id: string\n name: string\n state: WorkflowState\n /** Last run of any status, ms since epoch. Null when it has never run. */\n lastRunAt: number | null\n recent: number\n prior: number\n failed: number\n /** Runs parked at a checkpoint. These are waiting on a human, not broken. */\n paused: number\n}\n\nexport function workflowHealth(\n workflows: Array<{ id: string; name?: string }>,\n runs: RunSummary[],\n now = Date.now(),\n windowDays = 7,\n): WorkflowHealth[] {\n // One day in ms, inlined: workflowHealth is stringified and shipped to the\n // workflows page, where a module-scope constant would be unbound.\n const span = windowDays * 86_400_000\n const byId = new Map<string, RunSummary[]>()\n for (const r of runs) {\n const list = byId.get(r.workflowId)\n if (list) list.push(r)\n else byId.set(r.workflowId, [r])\n }\n return workflows.map(w => {\n const mine = byId.get(w.id) ?? []\n const recent = mine.filter(r => now - r.at <= span)\n const prior = mine.filter(r => now - r.at > span && now - r.at <= span * 2)\n const failed = recent.filter(r => r.status === \"failed\").length\n const paused = mine.filter(r => r.status === \"paused\").length\n const lastRunAt = mine.length ? Math.max(...mine.map(r => r.at)) : null\n // Order matters: a workflow that has stopped firing is reported as dormant\n // even when its last few runs failed — the silence is the bigger signal.\n const state: WorkflowState =\n !mine.length ? \"never\"\n : prior.length > 0 && recent.length === 0 ? \"dormant\"\n : recent.length > 0 && failed * 2 >= recent.length ? \"failing\"\n : recent.length > 0 ? \"active\"\n : \"quiet\"\n return { id: w.id, name: w.name || w.id, state, lastRunAt, recent: recent.length, prior: prior.length, failed, paused }\n }).sort((a, b) => {\n // Inlined rather than a module constant: workflowHealth is stringified\n // and shipped to the workflows page, where module scope does not travel.\n // A hoisted RANK only failed with two or more workflows, because Array\n // .sort never calls the comparator on a single-element list.\n const rank: Record<string, number> = { dormant: 0, failing: 1, active: 2, quiet: 3, never: 4 }\n return rank[a.state] - rank[b.state] || (b.lastRunAt ?? 0) - (a.lastRunAt ?? 0)\n })\n}\n\n\nconst cache = new Map<string, { mtime: number; summary: RunSummary | null }>()\n\n/** Read workflowId + terminal status per run, re-reading only changed files. */\nexport function scanRuns(runsDir: string, limit = 1000): RunSummary[] {\n if (!existsSync(runsDir)) return []\n const files = readdirSync(runsDir)\n .filter(f => f.endsWith(\".jsonl\"))\n .map(f => { const p = resolve(runsDir, f); return { path: p, mtime: statSync(p).mtimeMs } })\n .sort((a, b) => b.mtime - a.mtime)\n .slice(0, limit)\n const live = new Set(files.map(f => f.path))\n for (const key of cache.keys()) if (!live.has(key)) cache.delete(key)\n const out: RunSummary[] = []\n for (const { path, mtime } of files) {\n const hit = cache.get(path)\n if (hit && hit.mtime === mtime) { if (hit.summary) out.push(hit.summary); continue }\n const summary = readSummary(path, mtime)\n cache.set(path, { mtime, summary })\n if (summary) out.push(summary)\n }\n return out\n}\n\nfunction readSummary(path: string, mtime: number): RunSummary | null {\n try {\n const lines = readFileSync(path, \"utf-8\").split(\"\\n\").filter(Boolean)\n if (!lines.length) return null\n const head = JSON.parse(lines[0]) as { run?: { workflowId?: string; status?: string } }\n const workflowId = head.run?.workflowId\n if (!workflowId) return null\n // The last line is either a status-bearing exec entry or another snapshot;\n // fall back to the snapshot's own status when it carries neither.\n let status = head.run?.status ?? \"unknown\"\n for (let i = lines.length - 1; i >= 0; i--) {\n const tail = JSON.parse(lines[i]) as { status?: string; run?: { status?: string } }\n const s = tail.status ?? tail.run?.status\n if (s) { status = s; break }\n }\n return { workflowId, status, at: mtime }\n } catch { return null }\n}\n","// --- Routines: everything that runs on its own, in one list -----------------\n//\n// Automation lives in two places: time-based `crons.*` jobs and workflows\n// whose trigger node is `trigger.cron` or `trigger.hook`. This module is a\n// read model over both — no new schema — merged into one bounded row shape\n// with a staleness verdict, so obsolete or failing automation surfaces on\n// the mesh overview instead of going unnoticed.\n//\n// Pure: callers pass the job list, the persisted cron attempts (the source\n// of truth; scheduler counters reset on reload), the workflow definitions\n// and the workflow run summaries. Nothing here reads prompts or responses,\n// and every string that leaves it is length-capped.\n//\n// Routine ids are stable and namespaced by source — `cron:<jobId>` and\n// `workflow:<workflowId>` — so later per-routine actions can address a row\n// without knowing which subsystem owns it.\n\nimport type { CronRunHistoryItem } from \"@/crons/run-history\"\nimport { getNextCronDate } from \"@/crons/scheduler\"\nimport { workflowHealth, type RunSummary } from \"./workflow-health\"\n\nexport type RoutineKind = \"schedule\" | \"event\"\nexport type RoutineSource = \"cron\" | \"workflow\"\nexport type RoutineOutcome = \"success\" | \"failed\" | \"timeout\" | \"canceled\" | \"running\" | \"paused\" | \"unknown\"\nexport type RoutineFlagReason = \"failing\" | \"overdue\" | \"dormant\" | \"never-ran\" | \"disabled-long\"\nexport type RoutineAttention = \"critical\" | \"warning\" | \"info\"\n\nexport interface RoutineFlag { reason: RoutineFlagReason; detail: string }\n\nexport interface Routine {\n id: string\n source: RoutineSource\n kind: RoutineKind\n name: string\n trigger: {\n /** Cron expression for schedules. */\n schedule?: string\n timezone?: string\n /** `on:*` event for event routines. */\n event?: string\n /** Compact `key=value` renderings of the trigger filters. */\n filters?: string[]\n }\n agent: string | null\n enabled: boolean\n /** Cron: enabled/disabled. Workflow: active/disabled/quarantined. */\n state: string\n nextRunAt: string | null\n lastRun: { at: string; status: RoutineOutcome; summary?: string } | null\n lastSuccessAt: string | null\n /** Terminal failures in a row, newest first, over the sampled runs. */\n consecutiveFailures: number\n /** Runs inspected to reach the verdict (bounded, not a lifetime total). */\n sampledRuns: number\n flags: RoutineFlag[]\n attention: RoutineAttention | null\n}\n\nexport interface RoutineCronJob {\n id: string\n enabled: boolean\n schedule: string\n timezone?: string\n agent: string\n nextRun?: Date | string\n}\n\nexport interface RoutineWorkflow {\n id: string\n title?: string\n state?: string\n ownerAgent?: string\n project?: string\n updated?: string\n created?: string\n nodes: Array<{ id: string; type: string; config?: Record<string, unknown> }>\n}\n\nexport interface RoutineThresholds {\n /** Consecutive failures that flag a routine as failing. */\n failStreak: number\n /** Days a routine may sit disabled before it reads as obsolete. */\n disabledDays: number\n /** Scheduled fires that may pass without an attempt before it is overdue. */\n missedFires: number\n /** Slack added to the overdue deadline for slow starts and restarts. */\n graceMs: number\n}\n\nexport const DEFAULT_ROUTINE_THRESHOLDS: RoutineThresholds = {\n failStreak: 3,\n disabledDays: 30,\n missedFires: 2,\n graceMs: 15 * 60_000,\n}\n\n/** Hard caps: the list is served on an unauthenticated GET and polled. */\nexport const ROUTINE_LIMITS = { routines: 200, summary: 160, filters: 6, filterValue: 60, text: 120 }\n\nexport type NextFire = (expression: string, after: Date, timezone: string) => Date | null\n\nconst defaultNextFire: NextFire = (expression, after, timezone) => {\n try { return getNextCronDate(expression, after, timezone) } catch { return null }\n}\n\nexport interface BuildRoutinesInput {\n crons: RoutineCronJob[]\n /** Newest-first persisted attempts per cron job id. */\n cronRuns: Map<string, CronRunHistoryItem[]>\n workflows: RoutineWorkflow[]\n workflowRuns: RunSummary[]\n now?: number\n thresholds?: Partial<RoutineThresholds>\n nextFire?: NextFire\n}\n\nconst DAY = 86_400_000\n\nexport function buildRoutines(input: BuildRoutinesInput): Routine[] {\n const now = input.now ?? Date.now()\n const t = { ...DEFAULT_ROUTINE_THRESHOLDS, ...input.thresholds }\n const nextFire = input.nextFire ?? defaultNextFire\n const out: Routine[] = []\n\n for (const job of input.crons) {\n const runs = input.cronRuns.get(job.id) ?? []\n const timezone = job.timezone || \"UTC\"\n const outcomes = runs.map((r) => ({ at: Date.parse(r.startedAt), status: r.status as RoutineOutcome }))\n const latest = runs[0]\n const routine: Routine = {\n id: `cron:${job.id}`,\n source: \"cron\",\n kind: \"schedule\",\n name: cap(job.id, ROUTINE_LIMITS.text),\n trigger: { schedule: cap(job.schedule, ROUTINE_LIMITS.text), timezone },\n agent: job.agent || null,\n enabled: job.enabled,\n state: job.enabled ? \"enabled\" : \"disabled\",\n nextRunAt: job.enabled ? isoOrNull(job.nextRun) ?? isoOrNull(nextFire(job.schedule, new Date(now), timezone)) : null,\n lastRun: latest\n ? { at: latest.startedAt, status: latest.status, summary: capOpt(latest.status === \"success\" ? undefined : latest.errorSummary, ROUTINE_LIMITS.summary) }\n : null,\n lastSuccessAt: runs.find((r) => r.status === \"success\")?.startedAt ?? null,\n consecutiveFailures: failStreak(outcomes),\n sampledRuns: runs.length,\n flags: [],\n attention: null,\n }\n routine.flags = scheduleFlags(routine, outcomes, job.schedule, timezone, null, now, t, nextFire)\n out.push(finish(routine))\n }\n\n const health = new Map(workflowHealth(\n input.workflows.map((w) => ({ id: w.id, name: w.title })), input.workflowRuns, now,\n ).map((h) => [h.id, h] as const))\n const runsByWorkflow = new Map<string, RunSummary[]>()\n for (const r of input.workflowRuns) {\n const list = runsByWorkflow.get(r.workflowId)\n if (list) list.push(r)\n else runsByWorkflow.set(r.workflowId, [r])\n }\n\n for (const wf of input.workflows) {\n const trigger = wf.nodes.find((n) => n.type.startsWith(\"trigger.\"))\n if (!trigger || (trigger.type !== \"trigger.cron\" && trigger.type !== \"trigger.hook\")) continue\n const cfg = (trigger.config ?? {}) as Record<string, unknown>\n const state = wf.state || \"active\"\n const enabled = state === \"active\"\n const runs = (runsByWorkflow.get(wf.id) ?? []).slice().sort((a, b) => b.at - a.at)\n const outcomes = runs.map((r) => ({ at: r.at, status: workflowOutcome(r.status) }))\n const latest = outcomes[0]\n const isCron = trigger.type === \"trigger.cron\"\n const spec = typeof cfg.spec === \"string\" ? cfg.spec : \"\"\n const timezone = typeof cfg.timezone === \"string\" ? cfg.timezone : \"UTC\"\n const routine: Routine = {\n id: `workflow:${wf.id}`,\n source: \"workflow\",\n kind: isCron ? \"schedule\" : \"event\",\n name: cap(wf.title || wf.id, ROUTINE_LIMITS.text),\n trigger: isCron\n ? { schedule: cap(spec, ROUTINE_LIMITS.text), timezone }\n : { event: cap(typeof cfg.event === \"string\" ? cfg.event : \"\", ROUTINE_LIMITS.text), filters: describeFilters(cfg.filter, wf) },\n agent: workflowAgent(wf),\n enabled,\n state,\n nextRunAt: isCron && enabled && spec ? isoOrNull(nextFire(spec, new Date(now), timezone)) : null,\n lastRun: latest ? { at: new Date(latest.at).toISOString(), status: latest.status } : null,\n lastSuccessAt: (() => { const s = outcomes.find((o) => o.status === \"success\"); return s ? new Date(s.at).toISOString() : null })(),\n consecutiveFailures: failStreak(outcomes),\n sampledRuns: runs.length,\n flags: [],\n attention: null,\n }\n const changedAt = Date.parse(wf.updated || wf.created || \"\")\n routine.flags = isCron\n ? scheduleFlags(routine, outcomes, spec, timezone, Number.isFinite(changedAt) ? changedAt : null, now, t, nextFire)\n : eventFlags(routine, health.get(wf.id)?.state === \"dormant\", Number.isFinite(changedAt) ? changedAt : null, now, t)\n out.push(finish(routine))\n }\n\n return out.sort(compareRoutines).slice(0, ROUTINE_LIMITS.routines)\n}\n\n/** Terminal failures in a row from the newest run. In-flight and parked\n * runs are skipped; a success or cancel ends the streak. */\nexport function failStreak(outcomes: Array<{ status: RoutineOutcome }>): number {\n let n = 0\n for (const o of outcomes) {\n if (o.status === \"running\" || o.status === \"paused\" || o.status === \"unknown\") continue\n if (o.status === \"failed\" || o.status === \"timeout\") n++\n else break\n }\n return n\n}\n\nfunction scheduleFlags(\n r: Routine,\n outcomes: Array<{ at: number; status: RoutineOutcome }>,\n spec: string,\n timezone: string,\n changedAt: number | null,\n now: number,\n t: RoutineThresholds,\n nextFire: NextFire,\n): RoutineFlag[] {\n const flags: RoutineFlag[] = []\n const lastAt = outcomes.length ? outcomes[0].at : null\n if (!r.enabled) {\n const disabled = disabledFlag(lastAt, changedAt, now, t)\n if (disabled) flags.push(disabled)\n return flags\n }\n if (r.consecutiveFailures >= t.failStreak) {\n flags.push({ reason: \"failing\", detail: `${r.consecutiveFailures} failed attempts in a row` })\n }\n if (lastAt === null) {\n flags.push({ reason: \"never-ran\", detail: \"No persisted attempt\" })\n return flags\n }\n // Walk the schedule forward from the last attempt: if `missedFires`\n // expected fires have already passed (plus grace), the job has gone quiet.\n let cursor = new Date(lastAt)\n let deadline: Date | null = null\n for (let i = 0; i < t.missedFires; i++) {\n deadline = nextFire(spec, cursor, timezone)\n if (!deadline) break\n cursor = deadline\n }\n if (deadline && now > deadline.getTime() + t.graceMs) {\n flags.push({ reason: \"overdue\", detail: `No attempt since ${new Date(lastAt).toISOString()}; ${t.missedFires} scheduled fires missed` })\n }\n return flags\n}\n\nfunction eventFlags(\n r: Routine,\n dormant: boolean,\n changedAt: number | null,\n now: number,\n t: RoutineThresholds,\n): RoutineFlag[] {\n const flags: RoutineFlag[] = []\n const lastAt = r.lastRun ? Date.parse(r.lastRun.at) : null\n if (!r.enabled) {\n const disabled = disabledFlag(lastAt, changedAt, now, t)\n if (disabled) flags.push(disabled)\n return flags\n }\n if (r.consecutiveFailures >= t.failStreak) {\n flags.push({ reason: \"failing\", detail: `${r.consecutiveFailures} failed runs in a row` })\n }\n if (lastAt === null) flags.push({ reason: \"never-ran\", detail: \"No run recorded for this trigger\" })\n else if (dormant) flags.push({ reason: \"dormant\", detail: \"Fired in the prior week, silent in the last one\" })\n return flags\n}\n\nfunction disabledFlag(lastAt: number | null, changedAt: number | null, now: number, t: RoutineThresholds): RoutineFlag | null {\n // Crons carry no \"disabled since\" timestamp, so the last attempt (or the\n // workflow's last edit) is the best available proxy for how long it sat.\n const since = Math.max(lastAt ?? 0, changedAt ?? 0)\n if (since && now - since < t.disabledDays * DAY) return null\n return {\n reason: \"disabled-long\",\n detail: since ? `Disabled; no activity for ${Math.floor((now - since) / DAY)} days` : \"Disabled and never ran\",\n }\n}\n\nfunction finish(r: Routine): Routine {\n const reasons = new Set(r.flags.map((f) => f.reason))\n r.attention = reasons.has(\"failing\") ? \"critical\"\n : reasons.has(\"overdue\") || reasons.has(\"dormant\") || reasons.has(\"never-ran\") ? \"warning\"\n : reasons.has(\"disabled-long\") ? \"info\"\n : null\n return r\n}\n\nconst ATTENTION_RANK: Record<string, number> = { critical: 0, warning: 1, info: 2 }\n\nfunction compareRoutines(a: Routine, b: Routine): number {\n const ra = a.attention ? ATTENTION_RANK[a.attention] : 3\n const rb = b.attention ? ATTENTION_RANK[b.attention] : 3\n return ra - rb || Number(b.enabled) - Number(a.enabled) || a.name.localeCompare(b.name)\n}\n\n/** Map a workflow run's last recorded status onto the routine vocabulary.\n * The tail line may be a node execution entry (`ok`), not a run status. */\nexport function workflowOutcome(status: string): RoutineOutcome {\n switch (status) {\n case \"completed\": case \"ok\": return \"success\"\n case \"failed\": return \"failed\"\n case \"timeout\": return \"timeout\"\n case \"canceled\": return \"canceled\"\n case \"running\": return \"running\"\n case \"paused\": return \"paused\"\n default: return \"unknown\"\n }\n}\n\nfunction workflowAgent(wf: RoutineWorkflow): string | null {\n if (wf.ownerAgent) return cap(wf.ownerAgent, ROUTINE_LIMITS.text)\n const node = wf.nodes.find((n) => n.type === \"agent\" && typeof n.config?.agentId === \"string\" && n.config.agentId)\n return node ? cap(String(node.config!.agentId), ROUTINE_LIMITS.text) : null\n}\n\n/** `filter.action = [\"open\",\"reopen\"]` → `action=open,reopen`. The workflow's\n * project scope is a filter too: it silently drops other projects' events. */\nexport function describeFilters(filter: unknown, wf?: { project?: string }): string[] {\n const out: string[] = []\n const project = wf?.project\n if (project) out.push(cap(`project=${project}`, ROUTINE_LIMITS.filterValue))\n if (filter && typeof filter === \"object\" && !Array.isArray(filter)) {\n for (const [key, value] of Object.entries(filter as Record<string, unknown>)) {\n const values = Array.isArray(value) ? value.map(String) : value == null ? [] : [String(value)]\n if (!values.length) continue\n out.push(cap(`${key}=${values.join(\",\")}`, ROUTINE_LIMITS.filterValue))\n }\n }\n return out.slice(0, ROUTINE_LIMITS.filters)\n}\n\nfunction isoOrNull(v: Date | string | null | undefined): string | null {\n if (!v) return null\n const d = v instanceof Date ? v : new Date(v)\n return Number.isFinite(d.getTime()) ? d.toISOString() : null\n}\n\nfunction cap(value: string, limit: number): string {\n return value.length <= limit ? value : value.slice(0, limit - 3) + \"...\"\n}\n\nfunction capOpt(value: string | undefined, limit: number): string | undefined {\n return value === undefined ? undefined : cap(value, limit)\n}\n","import { mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { approvalsDir } from \"./cards\"\n\n// --- Inbox view state: \"later\" and the digest date ---\n//\n// Not a decision store. Every decision stays with its source (the card\n// file, the schedule in agentx.json, the memory fact, the wiki proposal).\n// This file only remembers which inbox items the operator put off until\n// when, the last day a digest went out, which cards the Mac popup has\n// already shown, and which ones the operator asked to see again.\n\nexport interface InboxState {\n /** Inbox key → ISO time it comes back. */\n snoozed: Record<string, string>\n /** Local date (YYYY-MM-DD) of the last digest sent. */\n lastDigest?: string\n /** Inbox key → ISO time the Mac popup showed it. Shown once, not every minute. */\n popped?: Record<string, string>\n /** ISO time of the last check-in: every card waiting then may show again. */\n passAt?: string\n /** Inbox keys the operator asked to see again in the Mac popup. */\n wanted?: string[]\n}\n\nfunction stateFile(root: string): string {\n return resolve(approvalsDir(root), \"_state.json\")\n}\n\nexport function readInboxState(root: string): InboxState {\n try {\n const raw = JSON.parse(readFileSync(stateFile(root), \"utf-8\"))\n return {\n snoozed: raw && typeof raw.snoozed === \"object\" && raw.snoozed ? raw.snoozed : {},\n ...(typeof raw?.lastDigest === \"string\" ? { lastDigest: raw.lastDigest } : {}),\n ...(raw?.popped && typeof raw.popped === \"object\" ? { popped: raw.popped } : {}),\n ...(typeof raw?.passAt === \"string\" ? { passAt: raw.passAt } : {}),\n ...(Array.isArray(raw?.wanted) ? { wanted: raw.wanted.filter((k: unknown) => typeof k === \"string\") } : {}),\n }\n } catch {\n return { snoozed: {} }\n }\n}\n\nexport function writeInboxState(root: string, state: InboxState): void {\n mkdirSync(approvalsDir(root), { recursive: true })\n const path = stateFile(root)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(state, null, 2) + \"\\n\")\n renameSync(tmp, path)\n}\n\n/** Put an item off until `until`. Drops snoozes that have run out. */\nexport function snooze(root: string, key: string, until: Date, now: number = Date.now()): void {\n const state = readInboxState(root)\n const snoozed: Record<string, string> = {}\n for (const [k, v] of Object.entries(state.snoozed)) if (Date.parse(v) > now) snoozed[k] = v\n snoozed[key] = until.toISOString()\n writeInboxState(root, { ...state, snoozed })\n}\n\nexport function recordDigest(root: string, localDate: string): void {\n writeInboxState(root, { ...readInboxState(root), lastDigest: localDate })\n}\n\n/** Record that the popup showed `key`. Forgets keys no longer waiting,\n * so the file stays as small as the inbox. */\nexport function recordPopped(root: string, key: string, waiting: string[], now: number = Date.now()): void {\n const state = readInboxState(root)\n const keep = new Set(waiting)\n const popped: Record<string, string> = {}\n for (const [k, v] of Object.entries(state.popped ?? {})) if (keep.has(k)) popped[k] = v\n popped[key] = new Date(now).toISOString()\n const wanted = (state.wanted ?? []).filter((k) => k !== key && keep.has(k))\n writeInboxState(root, { ...state, popped, wanted })\n}\n\n/** The operator asked for `key` in the popup again: it shows on the next\n * minute, even if it was shown, put off, or is more than a day old. */\nexport function recordWanted(root: string, key: string): void {\n const state = readInboxState(root)\n writeInboxState(root, { ...state, wanted: [...(state.wanted ?? []).filter((k) => k !== key), key] })\n}\n\n/** A check-in: every waiting card may show once more, whatever its age. */\nexport function recordPass(root: string, now: number = Date.now()): void {\n writeInboxState(root, { ...readInboxState(root), popped: {}, passAt: new Date(now).toISOString() })\n}\n","import { existsSync, readdirSync, readFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { MemoryStore } from \"@/agents/memory-store\"\nimport { approveSchedule, formatFireTime, humanizeCron, nextFireTime, rejectSchedule } from \"@/crons/schedule-ops\"\nimport { listProposals, readProposal, type PromotionProposal } from \"@/wiki/proposals\"\nimport { approveProposal, rejectProposal } from \"@/wiki/promote\"\nimport { decideDraft, listDrafts, readDraft, type SendReply } from \"@/whatsapp-triage/drafts\"\nimport { decideCard, listCards, readCard, type DecisionCard, type IfSilent } from \"./cards\"\nimport { readInboxState, snooze } from \"./state\"\nimport { openDb } from \"@/storage/sqlite\"\nimport { RequestStore, hasRequestTables } from \"@/requests/store\"\nimport { PlanStore, hasPlanTables } from \"@/requests/plan-store\"\nimport { readRequestSettings } from \"@/requests/settings\"\n\n// --- The Approvals inbox: one list over every pending decision ---\n//\n// A read model, not a store. Each source keeps its own records and its own\n// approve/reject; the inbox lists them in one shape and hands a verdict to\n// the source's existing function:\n//\n// card decision cards agents raise (cards.ts)\n// schedule schedules an agent asked to create or delete (crons/schedule-ops)\n// memory facts from outside sources held before agents may use them\n// wiki lessons proposed for the shared wiki (wiki/proposals, wiki/promote)\n// whatsapp replies an agent drafted for a watched WhatsApp chat\n// (whatsapp-triage/drafts); yes sends it through wacli\n// request something you asked for that failed, timed out, was cut off\n// or went quiet (requests/store); yes hands it back to its\n// agent, no drops it\n//\n// Only operator surfaces call decide(): the `agentx approvals` CLI and the\n// dashboard's /api/admin/approvals. The daemon's agent-facing API and the\n// MCP tool can create cards and read, never decide.\n\nexport type ApprovalKind = \"card\" | \"schedule\" | \"memory\" | \"wiki\" | \"whatsapp\" | \"request\"\nexport const APPROVAL_KINDS: readonly ApprovalKind[] = [\"card\", \"schedule\", \"memory\", \"wiki\", \"whatsapp\", \"request\"]\n\nexport type InboxAction = \"yes\" | \"no\" | \"later\"\n\n/** One pending decision, in bounded summary form. */\nexport interface InboxItem {\n /** `<kind>:<ref>`: what the CLI and the dashboard pass back. */\n key: string\n kind: ApprovalKind\n title: string\n /** The yes/no question. */\n ask: string\n /** What yes and no do, in words. */\n yes: string\n no: string\n recommend?: string\n if_silent?: IfSilent\n expires?: string\n source?: string\n raised_by: string\n /** Cards from an agent on another node of the mesh: that node (#668). */\n node?: string\n created_at: string\n /** A short excerpt of what is being decided. */\n detail?: string\n /** The source's own command for the full record. */\n more?: string\n /** Cards: the ready-made answers on offer; yes must pick one. */\n choices?: string[]\n /** Cards: the suggested message, `{choice}` not yet filled in. The\n * operator may edit it before saying yes. Bounded by CHOICE_LIMITS. */\n draft?: string\n snoozed_until?: string\n}\n\nexport interface InboxContext {\n /** Directory holding agentx.json and .agentx/. */\n root: string\n configPath?: string\n wikiDir?: string\n /** Hot-reload the daemon after a schedule decision. Default true. */\n reload?: boolean\n now?: number\n /** Sends an approved WhatsApp reply. Default: wacli. Tests swap it. */\n sendWhatsApp?: SendReply\n /** Open requests. Default: .agentx/db.sqlite when `root` is the folder\n * this process runs from. Tests pass their own. */\n requests?: RequestStore\n /** Tracked plans (#788), beside the requests. Tests pass their own. */\n plans?: PlanStore\n}\n\nexport const DETAIL_MAX = 280\n\nfunction clip(s: string, n = DETAIL_MAX): string {\n const flat = s.replace(/\\s+/g, \" \").trim()\n return flat.length > n ? `${flat.slice(0, n - 1)}…` : flat\n}\n\nfunction configPathFor(ctx: InboxContext): string {\n if (ctx.configPath) return resolve(ctx.configPath)\n const candidates = [resolve(ctx.root, \"agentx.json\"), resolve(ctx.root, \".agentx\", \"config.json\")]\n return candidates.find((c) => existsSync(c)) ?? candidates[0]\n}\n\nfunction wikiDirFor(ctx: InboxContext): string {\n return ctx.wikiDir ? resolve(ctx.wikiDir) : resolve(ctx.root, \".agentx\", \"wiki\")\n}\n\n// ── Sources ──────────────────────────────────────────────────────────\n\nfunction cardItems(ctx: InboxContext): InboxItem[] {\n return listCards(ctx.root, \"pending\").map(cardItem)\n}\n\nfunction cardItem(c: DecisionCard): InboxItem {\n return {\n key: `card:${c.id}`,\n kind: \"card\",\n title: c.title,\n ask: c.ask,\n yes: `tell ${c.raised_by} yes`,\n no: `tell ${c.raised_by} no`,\n recommend: c.recommend,\n if_silent: c.if_silent,\n expires: c.expires,\n ...(c.source ? { source: c.source } : {}),\n raised_by: c.raised_by,\n ...(c.node ? { node: c.node } : {}),\n created_at: c.created_at,\n ...(c.choices ? { choices: c.choices, more: `agentx approvals popup card:${c.id}` } : {}),\n ...(c.draft ? { draft: c.draft } : {}),\n ...(c.context || c.draft ? { detail: clip([c.context, c.draft ? `Suggested message: ${c.draft}` : \"\"].filter(Boolean).join(\" · \")) } : {}),\n }\n}\n\nfunction scheduleItems(ctx: InboxContext): InboxItem[] {\n const path = configPathFor(ctx)\n if (!existsSync(path)) return []\n let cfg: any\n try { cfg = JSON.parse(readFileSync(path, \"utf-8\")) } catch { return [] }\n const now = new Date(ctx.now ?? Date.now())\n const out: InboxItem[] = []\n for (const [id, job] of Object.entries<any>(cfg?.crons ?? {})) {\n const approval = job?.approval\n if (!approval || (approval.action !== \"create\" && approval.action !== \"delete\")) continue\n const tz = job.timezone || \"UTC\"\n const human = job.schedule ? humanizeCron(job.schedule) : \"\"\n const when = job.schedule ? `${human.charAt(0).toLowerCase()}${human.slice(1)} (${job.schedule})` : \"no schedule\"\n const what = String(job.prompt ?? job.command ?? \"\")\n const creating = approval.action === \"create\"\n out.push({\n key: `schedule:${id}`,\n kind: \"schedule\",\n title: creating ? `New schedule \"${id}\" for ${job.agent ?? \"an agent\"}` : `Delete schedule \"${id}\"`,\n ask: creating ? `Let ${job.agent ?? \"the agent\"} run this ${when}?` : `Remove the schedule \"${id}\" (${when})?`,\n yes: creating ? \"enable it\" : \"remove it\",\n no: creating ? \"drop the request\" : \"keep the schedule\",\n raised_by: String(approval.requestedBy ?? \"unknown\"),\n created_at: String(approval.requestedAt ?? \"\"),\n detail: clip(`${what}${creating && job.schedule ? ` · first run ${formatFireTime(nextFireTime(job.schedule, tz, now), tz)}` : \"\"}`),\n more: \"agentx schedule list\",\n })\n }\n return out\n}\n\nfunction memoryAgents(root: string): string[] {\n const dir = resolve(root, \".agentx\", \"memory\")\n if (!existsSync(dir)) return []\n return readdirSync(dir).filter((f) => f.endsWith(\".jsonl\")).map((f) => f.slice(0, -6)).sort()\n}\n\nfunction memoryItems(ctx: InboxContext): InboxItem[] {\n const agents = memoryAgents(ctx.root)\n if (agents.length === 0) return []\n const store = new MemoryStore(ctx.root)\n const out: InboxItem[] = []\n for (const agent of agents) {\n for (const f of store.held(agent)) {\n out.push({\n key: `memory:${agent}/${f.id}`,\n kind: \"memory\",\n title: `Fact ${agent} learned from ${f.source.channel}`,\n ask: `Let ${agent} use this fact in its prompts?`,\n yes: \"use it\",\n no: \"keep it out for good\",\n raised_by: agent,\n created_at: f.createdAt,\n detail: clip(f.content),\n more: `agentx memory facts held --agent ${agent}`,\n })\n }\n }\n return out\n}\n\n/** One line on what backs a wiki proposal. A lesson drawn from a\n * recurring failure says so, with the failure, before the article text:\n * the reviewer is judging a fix for something that keeps breaking. */\nexport function proposalBacking(e: PromotionProposal[\"evidence\"]): string {\n const failures = e.sources.filter((s) => s.failure)\n if (failures.length) {\n return failures.map((s) =>\n `Recurring failure: ${s.agentId}'s ${s.failure!.tool} fails with \"${s.failure!.errorClass}\" in ${s.occurrences ?? 1} sessions (${s.failure!.runs} runs).`,\n ).join(\" \")\n }\n return `${e.sources.length} source(s)${e.occurrences > 1 ? `, seen in ${e.occurrences} sessions` : \"\"}.`\n}\n\nfunction wikiItems(ctx: InboxContext): InboxItem[] {\n return listProposals(wikiDirFor(ctx), \"pending\").map((p) => {\n const e = p.evidence\n return {\n key: `wiki:${p.id}`,\n kind: \"wiki\" as const,\n title: p.article.title,\n ask: p.replaces ? `Update \"${p.article.path}\" in the shared wiki?` : `Add \"${p.article.path}\" to the shared wiki?`,\n yes: \"write the article\",\n no: \"decline it\",\n raised_by: e.agents.join(\", \") || \"wiki promote\",\n created_at: p.createdAt,\n detail: clip(`${proposalBacking(e)} ${p.article.content}`),\n more: `agentx wiki proposals show ${p.id}`,\n }\n })\n}\n\nfunction whatsappItems(ctx: InboxContext): InboxItem[] {\n return listDrafts(ctx.root, \"pending\").map((d) => ({\n key: `whatsapp:${d.id}`,\n kind: \"whatsapp\" as const,\n title: `WhatsApp reply to ${d.chat_name || d.to}`,\n // The whole draft: the owner approves exactly what will be sent.\n ask: `Send this to ${d.chat_name || d.to}? \"${d.text}\"`,\n yes: \"send it on WhatsApp\",\n no: \"drop the draft\",\n raised_by: d.agent,\n created_at: d.created_at,\n detail: clip(`${d.triage}: ${d.summary}`),\n more: \"agentx whatsapp triage log\",\n }))\n}\n\n/** The requests store, or null when there is no database to read. */\nexport function requestStoreFor(ctx: InboxContext): RequestStore | null {\n if (ctx.requests) return ctx.requests\n // openDb is per process and opens the file under the working directory.\n // A folder with no database yet has no requests: do not create one.\n if (resolve(ctx.root) !== process.cwd() || !existsSync(resolve(ctx.root, \".agentx\", \"db.sqlite\"))) return null\n const db = openDb({ quiet: true })\n if (!db) return null\n let store = requestStores.get(db)\n // The daemon creates the tables. Without them there is nothing to read.\n if (!store && !hasRequestTables(db)) return null\n if (!store) requestStores.set(db, store = new RequestStore(db))\n return store\n}\nconst requestStores = new WeakMap<object, RequestStore>()\n\n/** The plans of the requests, read the same way. Null until the daemon\n * made the tables. */\nexport function planStoreFor(ctx: InboxContext): PlanStore | null {\n if (ctx.plans) return ctx.plans\n if (ctx.requests || resolve(ctx.root) !== process.cwd() || !existsSync(resolve(ctx.root, \".agentx\", \"db.sqlite\"))) return null\n const db = openDb({ quiet: true })\n if (!db || !hasPlanTables(db)) return null\n let store = planStores.get(db)\n if (!store) planStores.set(db, store = new PlanStore(db))\n return store\n}\nconst planStores = new WeakMap<object, PlanStore>()\n\nfunction requestItems(ctx: InboxContext): InboxItem[] {\n const store = requestStoreFor(ctx)\n if (!store) return []\n return store.listByState(\"needs_attention\").map((r) => ({\n key: `request:${r.id}`,\n kind: \"request\" as const,\n title: `Request not finished: ${clip(r.text, 80)}`,\n ask: `Ask ${r.agentId} to pick it up again?`,\n yes: `hand it back to ${r.agentId}`,\n no: \"drop the request\",\n raised_by: r.agentId,\n created_at: new Date(r.createdAt).toISOString(),\n detail: clip(`${r.attentionReason ?? \"It needs attention\"}. Asked on ${r.channel}: ${r.text}`),\n more: `agentx requests show ${r.id}`,\n }))\n}\n\nconst SOURCES: Record<ApprovalKind, (ctx: InboxContext) => InboxItem[]> = {\n card: cardItems,\n schedule: scheduleItems,\n memory: memoryItems,\n wiki: wikiItems,\n whatsapp: whatsappItems,\n request: requestItems,\n}\n\n// ── Read model ───────────────────────────────────────────────────────\n\nexport interface InboxListing {\n items: InboxItem[]\n /** Hidden because the operator said \"later\". */\n snoozed: number\n /** Sources that couldn't be read, so an empty list isn't mistaken for \"nothing waiting\". */\n errors: Array<{ kind: ApprovalKind; error: string }>\n}\n\n/** Soonest expiry first; then oldest first. */\nexport function byUrgency(a: InboxItem, b: InboxItem): number {\n const ea = a.expires ? Date.parse(a.expires) : Infinity\n const eb = b.expires ? Date.parse(b.expires) : Infinity\n if (ea !== eb) return ea - eb\n return (Date.parse(a.created_at) || 0) - (Date.parse(b.created_at) || 0)\n}\n\nexport function listInbox(ctx: InboxContext, opts: { includeSnoozed?: boolean; kinds?: ApprovalKind[] } = {}): InboxListing {\n const now = ctx.now ?? Date.now()\n const { snoozed } = readInboxState(ctx.root)\n const items: InboxItem[] = []\n const errors: InboxListing[\"errors\"] = []\n let hidden = 0\n for (const kind of opts.kinds ?? APPROVAL_KINDS) {\n let found: InboxItem[]\n try { found = SOURCES[kind](ctx) } catch (e: any) {\n errors.push({ kind, error: String(e?.message ?? e).slice(0, 200) })\n continue\n }\n for (const item of found) {\n const until = snoozed[item.key]\n if (until && Date.parse(until) > now) {\n if (!opts.includeSnoozed) { hidden++; continue }\n item.snoozed_until = until\n }\n items.push(item)\n }\n }\n items.sort(byUrgency)\n return { items, snoozed: hidden, errors }\n}\n\n// ── Decide ───────────────────────────────────────────────────────────\n\nexport interface DecideOptions {\n /** Wiki: approve even if the article changed since the proposal. */\n force?: boolean\n /** Kept with the decision where the source records one. */\n note?: string\n /** \"later\": how long to put it off. */\n laterHours?: number\n by?: string\n /** Cards with choices: which one (1-based number or the label). */\n choice?: string | number\n /** Cards with a draft: the message as the operator edited it. */\n text?: string\n}\n\nexport type DecideResult = { ok: true; message: string } | { ok: false; error: string }\n\nexport function parseKey(key: string): { kind: ApprovalKind; ref: string } | null {\n const i = key.indexOf(\":\")\n if (i <= 0) return null\n const kind = key.slice(0, i) as ApprovalKind\n const ref = key.slice(i + 1)\n if (!APPROVAL_KINDS.includes(kind) || !ref) return null\n return { kind, ref }\n}\n\n/** True while the item is still waiting in its source. */\nfunction stillPending(ctx: InboxContext, kind: ApprovalKind, ref: string): boolean {\n switch (kind) {\n case \"card\": return readCard(ctx.root, ref)?.status === \"pending\"\n case \"wiki\": return readProposal(wikiDirFor(ctx), ref)?.status === \"pending\"\n case \"whatsapp\": return readDraft(ctx.root, ref)?.status === \"pending\"\n default: return SOURCES[kind](ctx).some((i) => i.key === `${kind}:${ref}`)\n }\n}\n\n/**\n * Apply the operator's answer through the source's own approve/reject.\n * Operator surfaces only: never expose this to agents.\n */\nexport async function decide(ctx: InboxContext, key: string, action: InboxAction, opts: DecideOptions = {}): Promise<DecideResult> {\n const parsed = parseKey(key)\n if (!parsed) return { ok: false, error: `unknown item \"${key}\": use a key from \\`agentx approvals list\\`` }\n const { kind, ref } = parsed\n const now = ctx.now ?? Date.now()\n const by = opts.by ?? \"operator\"\n\n if (action === \"later\") {\n if (!stillPending(ctx, kind, ref)) return { ok: false, error: `nothing waiting for \"${key}\"` }\n const hours = opts.laterHours && opts.laterHours > 0 ? opts.laterHours : 24\n const until = new Date(now + hours * 3_600_000)\n snooze(ctx.root, key, until, now)\n return { ok: true, message: `${key} put off until ${until.toISOString()}` }\n }\n\n const yes = action === \"yes\"\n switch (kind) {\n case \"card\": {\n const r = decideCard(ctx.root, ref, yes ? \"yes\" : \"no\", { by, note: opts.note, now, choice: opts.choice, text: opts.text })\n if (!r.ok) return r\n return { ok: true, message: `${key}: ${r.card.raised_by} will be told ${yes ? \"yes\" : \"no\"}${r.card.choice ? ` (${r.card.choice})` : \"\"}` }\n }\n case \"schedule\": {\n const r = await (yes ? approveSchedule : rejectSchedule)(ref, { configPath: configPathFor(ctx), reload: ctx.reload })\n return r.success ? { ok: true, message: r.message } : { ok: false, error: r.message }\n }\n case \"memory\": {\n const slash = ref.indexOf(\"/\")\n if (slash <= 0) return { ok: false, error: `memory keys look like memory:<agent>/<fact-id>` }\n const agent = ref.slice(0, slash)\n const id = ref.slice(slash + 1)\n if (!memoryAgents(ctx.root).includes(agent)) return { ok: false, error: `no memory for agent \"${agent}\"` }\n const store = new MemoryStore(ctx.root)\n if (!store.held(agent).some((f) => f.id === id)) return { ok: false, error: `fact \"${id}\" isn't waiting for review` }\n store.review(agent, id, yes ? \"approved\" : \"rejected\", by)\n return { ok: true, message: `${key} ${yes ? \"approved: the agent may use it\" : \"rejected: it stays out of prompts\"}` }\n }\n case \"wiki\": {\n const dir = wikiDirFor(ctx)\n const r = yes\n ? approveProposal(dir, ref, { by, force: opts.force, now })\n : rejectProposal(dir, ref, { by, reason: opts.note, now })\n if (!r.ok) return r\n return { ok: true, message: yes ? `${r.proposal.article.path} written to the shared wiki` : `${key} rejected` }\n }\n case \"whatsapp\": {\n const r = await decideDraft(ctx.root, ref, yes ? \"yes\" : \"no\", { by, now, send: ctx.sendWhatsApp })\n if (!r.ok) return r\n return { ok: true, message: yes ? `${key}: sent to ${r.draft.chat_name || r.draft.to}` : `${key}: dropped, nothing sent` }\n }\n case \"request\": {\n const store = requestStoreFor(ctx)\n const r = store?.get(ref)\n if (!store || !r || r.state !== \"needs_attention\") return { ok: false, error: `nothing waiting for \"${key}\"` }\n if (yes) {\n // The hand-back runs in the daemon's requests check, which is off with the feature.\n if (!readRequestSettings(configPathFor(ctx)).enabled) {\n return { ok: false, error: \"Requests are off, so nothing would hand it back. Turn them on (agentx requests settings --enabled on), or answer no to drop it.\" }\n }\n store.requestPickup(ref, now)\n return { ok: true, message: `${key}: ${r.agentId} will be asked to pick it up again` }\n }\n store.close(ref, \"dropped\", opts.note?.trim() || `dropped by ${by}`, now)\n return { ok: true, message: `${key}: dropped` }\n }\n }\n}\n","import { randomBytes } from \"crypto\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport type { WacliSettings } from \"./config\"\nimport type { TriageClass } from \"./store\"\nimport { sendText } from \"./wacli\"\n\n// --- Draft WhatsApp replies waiting for the owner ---\n//\n// The agent never sends to a watched contact. Its draft is saved here and\n// listed in the Approvals inbox as `whatsapp:<id>`. Only the owner's yes\n// (CLI or dashboard, see approvals/inbox.ts) sends it through wacli.\n//\n// One JSON file per draft under .agentx/whatsapp-triage/drafts/, because\n// the CLI, the dashboard and the daemon are separate processes. Deciding\n// first renames the file away, so two clicks on \"yes\" send once.\n\nexport type DraftStatus = \"pending\" | \"sent\" | \"rejected\" | \"failed\"\n\nexport interface ReplyDraft {\n id: string\n status: DraftStatus\n created_at: string\n agent: string\n rule_id: string\n /** The chat the reply goes to. */\n to: string\n chat_name?: string\n text: string\n triage: TriageClass\n summary: string\n wacli: WacliSettings\n decided_by?: string\n decided_at?: string\n error?: string\n}\n\nexport type SendReply = (draft: ReplyDraft) => Promise<{ ok: true } | { ok: false; error: string }>\n\nexport const DRAFT_MAX = 2000\n\nexport const sendWithWacli: SendReply = (d) => sendText(d.wacli, d.to, d.text)\n\nexport function draftsDir(root: string): string {\n return resolve(root, \".agentx\", \"whatsapp-triage\", \"drafts\")\n}\n\nconst validId = (id: string): boolean => /^[a-z0-9-]{8,64}$/.test(id)\nconst fileFor = (root: string, id: string): string => resolve(draftsDir(root), `${id}.json`)\n\nfunction save(root: string, draft: ReplyDraft): void {\n mkdirSync(draftsDir(root), { recursive: true })\n const path = fileFor(root, draft.id)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(draft, null, 2) + \"\\n\", { mode: 0o600 })\n renameSync(tmp, path)\n}\n\nexport function createDraft(\n root: string,\n input: Omit<ReplyDraft, \"id\" | \"status\" | \"created_at\">,\n now = Date.now(),\n): ReplyDraft {\n const text = input.text.trim().slice(0, DRAFT_MAX)\n if (!text) throw new Error(\"empty draft\")\n const draft: ReplyDraft = {\n ...input,\n text,\n id: `${now.toString(36)}-${randomBytes(4).toString(\"hex\")}`,\n status: \"pending\",\n created_at: new Date(now).toISOString(),\n }\n save(root, draft)\n return draft\n}\n\nexport function readDraft(root: string, id: string): ReplyDraft | null {\n if (!validId(id)) return null\n try { return JSON.parse(readFileSync(fileFor(root, id), \"utf-8\")) as ReplyDraft } catch { return null }\n}\n\nexport function listDrafts(root: string, status?: DraftStatus): ReplyDraft[] {\n const dir = draftsDir(root)\n if (!existsSync(dir)) return []\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".json\"))\n .map((f) => readDraft(root, f.slice(0, -5)))\n .filter((d): d is ReplyDraft => !!d && (!status || d.status === status))\n .sort((a, b) => a.created_at.localeCompare(b.created_at))\n}\n\n/**\n * The owner's answer. Yes sends the draft through wacli, no drops it.\n * Operator surfaces only: never reachable by an agent.\n */\nexport async function decideDraft(\n root: string,\n id: string,\n verdict: \"yes\" | \"no\",\n opts: { by?: string; now?: number; send?: SendReply } = {},\n): Promise<{ ok: true; draft: ReplyDraft } | { ok: false; error: string }> {\n const draft = readDraft(root, id)\n if (!draft || draft.status !== \"pending\") return { ok: false, error: `no reply draft \"${id}\" is waiting` }\n // Claim it: only one caller's rename succeeds.\n const claim = `${fileFor(root, id)}.${process.pid}.${randomBytes(3).toString(\"hex\")}.claim`\n try { renameSync(fileFor(root, id), claim) } catch {\n return { ok: false, error: `reply draft \"${id}\" is already being decided` }\n }\n const decided: ReplyDraft = {\n ...draft,\n decided_by: opts.by ?? \"operator\",\n decided_at: new Date(opts.now ?? Date.now()).toISOString(),\n }\n if (verdict === \"no\") {\n decided.status = \"rejected\"\n } else {\n const r = await (opts.send ?? sendWithWacli)(draft).catch((e: any) => ({ ok: false as const, error: String(e?.message ?? e) }))\n if (r.ok) decided.status = \"sent\"\n else { decided.status = \"failed\"; decided.error = r.error }\n }\n save(root, decided)\n try { unlinkSync(claim) } catch { /* already gone */ }\n return decided.status === \"failed\" ? { ok: false, error: decided.error ?? \"send failed\" } : { ok: true, draft: decided }\n}\n","import { run } from \"@/wiki/facts/exec\"\nimport type { WacliSettings } from \"./config\"\n\n// --- The two wacli commands triage uses ---\n//\n// execFile, never a shell: the reply text and the ids come from chat\n// messages. While `wacli sync --follow` holds the store, `send text` is\n// handed to that process, and `--read-only media download` takes no lock.\n\nexport type Runner = typeof run\n\nfunction base(settings: WacliSettings): string[] {\n return [\n ...(settings.account ? [\"--account\", settings.account] : []),\n ...(settings.store ? [\"--store\", settings.store] : []),\n ]\n}\n\nconst firstLine = (s: string): string => s.trim().split(\"\\n\").pop()?.slice(0, 300) || \"no output\"\n\nexport async function sendText(\n settings: WacliSettings,\n to: string,\n text: string,\n exec: Runner = run,\n): Promise<{ ok: true } | { ok: false; error: string }> {\n const r = await exec(settings.bin, [...base(settings), \"send\", \"text\", \"--to\", to, \"--message\", text, \"--no-preview\"], { timeoutMs: 60_000 })\n return r.code === 0 ? { ok: true } : { ok: false, error: `wacli send failed: ${firstLine(r.stderr || r.stdout)}` }\n}\n\n/** Downloads one message's media to `output`. False when wacli couldn't. */\nexport async function downloadMedia(\n settings: WacliSettings,\n chat: string,\n id: string,\n output: string,\n exec: Runner = run,\n): Promise<boolean> {\n const r = await exec(settings.bin, [...base(settings), \"--read-only\", \"media\", \"download\", \"--chat\", chat, \"--id\", id, \"--output\", output], { timeoutMs: 120_000 })\n return r.code === 0\n}\n","import type Database from \"better-sqlite3\"\nimport { TEXT_MAX } from \"./store\"\n\n// --- Tracked plans: the steps of one open request (#788) ---\n//\n// A request that takes several steps (build, deploy, check it is live,\n// tell the client) gets one plan. Each step has an owner agent and a\n// \"done when\" check; steps run in order. The daemon's plan check\n// (plan-sweep.ts) starts each step, nudges a step that went quiet, sends an\n// approved message itself, and tells the owner once at the end or when a\n// step is blocked.\n//\n// Stored beside the request in .agentx/db.sqlite. A plan is deleted with\n// its request (retention), never on its own.\n\nexport type PlanState = \"active\" | \"done\" | \"closed\"\nexport type StepState = \"pending\" | \"active\" | \"done\" | \"blocked\" | \"skipped\"\nexport type ApprovalState = \"none\" | \"pending\" | \"approved\" | \"declined\" | \"expired\"\n\nexport const FINISHED_STEP_STATES: readonly StepState[] = [\"done\", \"skipped\"]\n\nexport interface PlanRecord {\n requestId: string\n /** The agent that accepted the request and wrote the plan. */\n createdBy: string\n createdAt: number\n state: PlanState\n finishedAt: number | null\n /** When the owner was sent the summary. Sent once. */\n notifiedAt: number | null\n}\n\nexport interface PlanStep {\n requestId: string\n /** 1-based position: steps run in this order. */\n idx: number\n name: string\n /** \"message\": the daemon sends `message` itself. Any other kind is work\n * the owner agent does and reports. */\n kind: string\n agentId: string\n /** What proves the step is done, in words. */\n doneWhen: string\n state: StepState\n /** The owner approved this step when the plan was made (approveKinds). */\n needsApproval: boolean\n approval: ApprovalState\n cardId: string | null\n message: string | null\n toChannel: string | null\n toChat: string | null\n toAccount: string | null\n /** The daemon marks the step done when this address answers (and shows\n * `checkContains`, when set). */\n checkUrl: string | null\n checkContains: string | null\n /** This step's own stall time; null: the setting. */\n stallMinutes: number | null\n evidence: string | null\n /** Why it is blocked or skipped. */\n note: string | null\n /** When its owner agent was given the step. */\n dispatchedAt: number | null\n /** Last progress on the step. */\n updatedAt: number\n nudges: number\n lastNudgeAt: number | null\n /** When the owner was told the step is blocked. */\n notifiedAt: number | null\n}\n\nexport interface PlanEvent {\n requestId: string\n step: number | null\n at: number\n kind: string\n detail: string\n}\n\nexport type NewStep = Pick<PlanStep, \"name\" | \"kind\" | \"agentId\" | \"doneWhen\" | \"needsApproval\">\n & Partial<Pick<PlanStep, \"message\" | \"toChannel\" | \"toChat\" | \"toAccount\" | \"checkUrl\" | \"checkContains\" | \"stallMinutes\">>\n\nconst DETAIL_MAX = 1000\n\nexport function ensurePlanTables(db: Database.Database): void {\n db.exec(`\n CREATE TABLE IF NOT EXISTS request_plans (\n request_id TEXT PRIMARY KEY,\n created_by TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n state TEXT NOT NULL,\n finished_at INTEGER,\n notified_at INTEGER,\n FOREIGN KEY (request_id) REFERENCES requests(id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_request_plans_state ON request_plans(state);\n CREATE TABLE IF NOT EXISTS request_plan_steps (\n request_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n name TEXT NOT NULL,\n kind TEXT NOT NULL,\n agent_id TEXT NOT NULL,\n done_when TEXT NOT NULL,\n state TEXT NOT NULL,\n needs_approval INTEGER NOT NULL DEFAULT 0,\n approval TEXT NOT NULL DEFAULT 'none',\n card_id TEXT,\n message TEXT,\n to_channel TEXT,\n to_chat TEXT,\n to_account TEXT,\n check_url TEXT,\n check_contains TEXT,\n stall_minutes REAL,\n evidence TEXT,\n note TEXT,\n dispatched_at INTEGER,\n updated_at INTEGER NOT NULL,\n nudges INTEGER NOT NULL DEFAULT 0,\n last_nudge_at INTEGER,\n notified_at INTEGER,\n PRIMARY KEY (request_id, idx),\n FOREIGN KEY (request_id) REFERENCES request_plans(request_id) ON DELETE CASCADE\n );\n CREATE TABLE IF NOT EXISTS request_plan_events (\n request_id TEXT NOT NULL,\n step INTEGER,\n at INTEGER NOT NULL,\n kind TEXT NOT NULL,\n detail TEXT NOT NULL,\n FOREIGN KEY (request_id) REFERENCES request_plans(request_id) ON DELETE CASCADE\n );\n CREATE INDEX IF NOT EXISTS idx_request_plan_events ON request_plan_events(request_id, at);\n `)\n}\n\nexport function hasPlanTables(db: Database.Database): boolean {\n return !!db.prepare(\"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'request_plans'\").get()\n}\n\nfunction toPlan(r: any): PlanRecord {\n return {\n requestId: r.request_id, createdBy: r.created_by, createdAt: r.created_at, state: r.state,\n finishedAt: r.finished_at ?? null, notifiedAt: r.notified_at ?? null,\n }\n}\n\nfunction toStep(r: any): PlanStep {\n return {\n requestId: r.request_id, idx: r.idx, name: r.name, kind: r.kind, agentId: r.agent_id, doneWhen: r.done_when,\n state: r.state, needsApproval: !!r.needs_approval, approval: r.approval, cardId: r.card_id ?? null,\n message: r.message ?? null, toChannel: r.to_channel ?? null, toChat: r.to_chat ?? null, toAccount: r.to_account ?? null,\n checkUrl: r.check_url ?? null, checkContains: r.check_contains ?? null, stallMinutes: r.stall_minutes ?? null,\n evidence: r.evidence ?? null, note: r.note ?? null, dispatchedAt: r.dispatched_at ?? null, updatedAt: r.updated_at,\n nudges: r.nudges, lastNudgeAt: r.last_nudge_at ?? null, notifiedAt: r.notified_at ?? null,\n }\n}\n\n/** Fields a step update may change, by their column. */\nconst STEP_COLUMNS: Record<string, string> = {\n state: \"state\", approval: \"approval\", cardId: \"card_id\", message: \"message\", evidence: \"evidence\", note: \"note\",\n dispatchedAt: \"dispatched_at\", updatedAt: \"updated_at\", nudges: \"nudges\", lastNudgeAt: \"last_nudge_at\", notifiedAt: \"notified_at\",\n}\n\nexport type StepPatch = Partial<Pick<PlanStep,\n \"state\" | \"approval\" | \"cardId\" | \"message\" | \"evidence\" | \"note\" | \"dispatchedAt\" | \"updatedAt\" | \"nudges\" | \"lastNudgeAt\" | \"notifiedAt\">>\n\nexport class PlanStore {\n constructor(private db: Database.Database) {\n ensurePlanTables(db)\n }\n\n /** Record a plan for an open request. Step 1 starts active: the agent\n * that wrote the plan is in the turn and, when it owns step 1, already\n * at work on it. */\n create(requestId: string, createdBy: string, steps: NewStep[], now: number): void {\n const insertPlan = this.db.prepare(\n \"INSERT INTO request_plans (request_id, created_by, created_at, state) VALUES (?, ?, ?, 'active')\",\n )\n const insertStep = this.db.prepare(\n `INSERT INTO request_plan_steps (request_id, idx, name, kind, agent_id, done_when, state, needs_approval, message,\n to_channel, to_chat, to_account, check_url, check_contains, stall_minutes, dispatched_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n this.db.transaction(() => {\n insertPlan.run(requestId, createdBy, now)\n steps.forEach((s, i) => {\n const first = i === 0\n // The writer already has step 1 when it is theirs; anyone else's\n // step 1 is handed over by the plan check.\n const own = first && s.agentId === createdBy && s.kind !== \"message\"\n insertStep.run(\n requestId, i + 1, s.name, s.kind, s.agentId, s.doneWhen, first ? \"active\" : \"pending\", s.needsApproval ? 1 : 0,\n s.message ?? null, s.toChannel ?? null, s.toChat ?? null, s.toAccount ?? null, s.checkUrl ?? null,\n s.checkContains ?? null, s.stallMinutes ?? null, own ? now : null, now,\n )\n })\n this.event(requestId, null, \"created\", `${steps.length} steps, written by ${createdBy}`, now)\n })()\n }\n\n get(requestId: string): PlanRecord | null {\n const row = this.db.prepare(\"SELECT * FROM request_plans WHERE request_id = ?\").get(requestId)\n return row ? toPlan(row) : null\n }\n\n steps(requestId: string): PlanStep[] {\n return this.db.prepare(\"SELECT * FROM request_plan_steps WHERE request_id = ? ORDER BY idx\").all(requestId).map(toStep)\n }\n\n step(requestId: string, idx: number): PlanStep | null {\n const row = this.db.prepare(\"SELECT * FROM request_plan_steps WHERE request_id = ? AND idx = ?\").get(requestId, idx)\n return row ? toStep(row) : null\n }\n\n /** Plans the check still follows, oldest first. */\n listActive(): PlanRecord[] {\n return this.db.prepare(\"SELECT * FROM request_plans WHERE state = 'active' ORDER BY created_at, rowid\").all().map(toPlan)\n }\n\n updateStep(requestId: string, idx: number, patch: StepPatch): void {\n const sets: string[] = []\n const values: unknown[] = []\n for (const [key, value] of Object.entries(patch)) {\n const col = STEP_COLUMNS[key]\n if (!col || value === undefined) continue\n sets.push(`${col} = ?`)\n values.push(typeof value === \"boolean\" ? (value ? 1 : 0) : value)\n }\n if (!sets.length) return\n this.db.prepare(`UPDATE request_plan_steps SET ${sets.join(\", \")} WHERE request_id = ? AND idx = ?`).run(...values, requestId, idx)\n }\n\n /** The plan ended: every step finished (\"done\"), or its request was\n * closed first (\"closed\"). */\n finish(requestId: string, state: Exclude<PlanState, \"active\">, now: number): boolean {\n return this.db.prepare(\n \"UPDATE request_plans SET state = ?, finished_at = ? WHERE request_id = ? AND state = 'active'\",\n ).run(state, now, requestId).changes > 0\n }\n\n markNotified(requestId: string, now: number | null): void {\n this.db.prepare(\"UPDATE request_plans SET notified_at = ? WHERE request_id = ?\").run(now, requestId)\n }\n\n /** Finished plans whose summary has not reached the owner yet. */\n awaitingSummary(): PlanRecord[] {\n return this.db.prepare(\"SELECT * FROM request_plans WHERE state = 'done' AND notified_at IS NULL ORDER BY finished_at\").all().map(toPlan)\n }\n\n /** What happened, in order: the plan's log. */\n event(requestId: string, step: number | null, kind: string, detail: string, now: number): void {\n this.db.prepare(\"INSERT INTO request_plan_events (request_id, step, at, kind, detail) VALUES (?, ?, ?, ?, ?)\")\n .run(requestId, step, now, kind, detail.replace(/\\s+/g, \" \").trim().slice(0, DETAIL_MAX))\n }\n\n events(requestId: string, limit = 200): PlanEvent[] {\n return (this.db.prepare(\n \"SELECT request_id, step, at, kind, detail FROM request_plan_events WHERE request_id = ? ORDER BY at, rowid LIMIT ?\",\n ).all(requestId, limit) as any[]).map((r) => ({ requestId: r.request_id, step: r.step ?? null, at: r.at, kind: r.kind, detail: r.detail }))\n }\n}\n\nexport const STEP_TEXT_MAX = TEXT_MAX\n","import { existsSync, readFileSync } from \"fs\"\nimport { applyConfigMutation, findConfigPath, type MutationResult } from \"@/daemon/config-mutator\"\nimport { expandEnvVars, requestsConfigSchema } from \"@/daemon/config\"\nimport { HUMAN_CHANNELS } from \"@/a2a/initiator\"\nimport type { RequestSettings } from \"./tracker\"\nimport type { PlanSettings } from \"./plans\"\nimport type { CheckerSettings } from \"./checkers\"\n\n// --- Requests settings: read and change `requests` in agentx.json ---\n//\n// Shared by `agentx requests settings` and the dashboard, so both validate\n// through the config schema and both hot-reload the daemon.\n\nexport function readRequestSettings(configPath?: string): RequestSettings {\n const path = findConfigPath(configPath)\n let raw: any = {}\n if (existsSync(path)) {\n try { raw = expandEnvVars(JSON.parse(readFileSync(path, \"utf-8\"))) } catch { raw = {} }\n }\n const parsed = requestsConfigSchema.safeParse(raw?.requests)\n return parsed.success ? parsed.data : requestsConfigSchema.parse(undefined)\n}\n\nexport type RequestSettingsPatch = Partial<Omit<RequestSettings, \"plans\" | \"checkers\">> & {\n plans?: Partial<PlanSettings>\n /** The checkers' kinds and pace. Repos and connectors are set in agentx.json. */\n checkers?: Partial<Pick<CheckerSettings, \"enabled\" | \"checkEveryMinutes\">>\n}\n\nexport async function updateRequestSettings(\n patch: RequestSettingsPatch,\n opts: { configPath?: string; reload?: boolean } = {},\n): Promise<MutationResult> {\n // Only a turn a person starts is recorded, so any other name (a typo,\n // say) would record nothing without a word.\n const unknown = (patch.channels ?? []).filter((c) => !HUMAN_CHANNELS.has(c.toLowerCase().split(\"@\")[0]))\n if (unknown.length) {\n return { success: false, error: `unknown channel ${unknown.map((c) => `\"${c}\"`).join(\", \")}: use ${[...HUMAN_CHANNELS].join(\", \")}` }\n }\n return applyConfigMutation((cfg) => {\n const r = (cfg.requests ??= {})\n for (const key of [\"enabled\", \"channels\", \"from\", \"staleAfterHours\", \"retentionDays\"] as const) {\n if (patch[key] !== undefined) r[key] = patch[key]\n }\n if (patch.plans) {\n const p = (r.plans ??= {})\n for (const key of [\"enabled\", \"stallMinutes\", \"maxNudges\", \"approveKinds\", \"disabledAgents\", \"requireForWork\", \"requireOverMinutes\"] as const) {\n if (patch.plans[key] !== undefined) p[key] = patch.plans[key]\n }\n }\n if (patch.checkers) {\n const c = (r.checkers ??= {})\n for (const key of [\"enabled\", \"checkEveryMinutes\"] as const) {\n if (patch.checkers[key] !== undefined) c[key] = patch.checkers[key]\n }\n }\n }, { configPath: opts.configPath, reload: opts.reload })\n}\n","// --- Inbound auth decision for the daemon's mesh surface ---\n//\n// Pure decision logic, kept out of the daemon class so it can be unit\n// tested. The daemon wraps this with logging and the 401 response.\n//\n// Model: mesh peers have always sent `Authorization: Bearer <token>`\n// (a2a/mesh.ts sendTask), so verifying it server-side is compatible with\n// existing paired meshes. Loopback callers (CLI, same-host dashboard,\n// local tools) are exempt. Installs with no token configured anywhere\n// pass with a warning — that grace path is scheduled for removal.\n\nexport interface MeshAuthRequest {\n /** req.socket.remoteAddress */\n remoteAddress: string\n /** Raw Authorization header value (\"\" when absent). */\n authorizationHeader: string\n /** All tokens this node accepts: MESH_TOKEN, peer tokens, dashboard.token. */\n acceptedTokens: ReadonlySet<string>\n /** AGENTX_MESH_AUTH=off escape hatch. */\n enforcementDisabled?: boolean\n /** A browser says the request comes from another origin's page (see\n * browser-origin.ts). Such a page is not the operator, even though the\n * browser that sends it sits on loopback. */\n foreignBrowser?: boolean\n}\n\nexport type MeshAuthDecision =\n | { allowed: true; reason: \"disabled\" | \"loopback\" | \"token\" | \"no-tokens-configured\" }\n | { allowed: false; reason: \"missing-or-invalid-token\" | \"foreign-browser-origin\" }\n\nconst LOOPBACK = new Set([\"127.0.0.1\", \"::1\", \"::ffff:127.0.0.1\"])\n\n/** Routes gated for every method, reads included. Agent memory is\n * injected into every future session of its agent, so an off-box write\n * is a persistent prompt injection and an off-box read can leak what an\n * agent noted down. Agents reach it with `curl localhost` from their own\n * Bash, which the loopback exemption keeps working. */\nexport function isMeshGatedPath(path: string): boolean {\n return path === \"/api/memory\" || path.startsWith(\"/api/memory/\") ||\n // Approvals list held memory facts and draft wiki articles, and a card\n // written from off-box would ask the operator in an agent's name.\n path === \"/approvals\" || path.startsWith(\"/approvals/\") ||\n // A note is written in an agent's name into the owner's notes (#1062).\n path === \"/native/notes\" ||\n path === \"/requests\" || path.startsWith(\"/requests/\") ||\n // Watchers carry each watched task's summary and who handed it over (#1030).\n path === \"/watchers\" || path.startsWith(\"/watchers/\") ||\n // Follow-up runs name people, chats and what was said to them (#788).\n path === \"/follow-up\" || path.startsWith(\"/follow-up/\") ||\n // Recent events name agents, chats and errors across the node.\n path === \"/events/recent\" || /^\\/agents\\/[^/]+\\/events$/.test(path) ||\n // Delegations name which agents are working for which others.\n path === \"/a2a/delegations\" || path.startsWith(\"/a2a/delegations/\") ||\n // Traces carry each task's full prompt and final answer.\n path === \"/traces\" || path.startsWith(\"/traces/\") ||\n // Past voice exchanges: what was asked and answered out loud. A\n // replay under it makes this host speak.\n path === \"/voice/history\" || path.startsWith(\"/voice/history/\") ||\n // The phone app's voice: runs speech to text on this host and spends\n // its ElevenLabs quota (voice-io-api.ts).\n path === \"/voice/transcribe\" || path === \"/voice/speak\" ||\n // Files agents declared for the phone app: whatever an agent saved in\n // its workspace (app-files-api.ts).\n path === \"/app-files\" ||\n // Calls agents place to the owner (calls-api.ts): the reasons are\n // agent-written text, and a call makes this host ring and speak.\n path === \"/calls\" || path.startsWith(\"/calls/\") ||\n // An agent watching the phone camera (camera-api.ts): a look runs the\n // agent's turn, and a snapshot writes a picture of the owner's\n // surroundings to disk.\n path === \"/webrtc/camera\" || path.startsWith(\"/webrtc/camera/\") ||\n // The host's panel of guest meshes (guests/daemon-api.ts): names the\n // other organisations let in, what they may reach and what they did.\n path === \"/mesh/guests\" || path.startsWith(\"/mesh/guests/\") ||\n // The wiki routes peers sync from: every raw entry is a task's text,\n // and articles include ones marked private to their agent.\n path === \"/wiki/agents\" || path === \"/wiki/entries\" ||\n path === \"/wiki/articles\" || path === \"/wiki/article\" ||\n // Notes agents leave for the wiki observe/sweep run (#825): what an\n // agent saw, kept inside the fleet. A peer forwards one with its token.\n path === \"/wiki/notes\" ||\n // The wiki page curator: runs an agent turn and rewrites a page (#818).\n path === \"/api/wiki/curate\" || path === \"/api/wiki/curate/restore\" ||\n // Stopped tasks and their resume plans (#857): each holds the request\n // and what the agent did. Stop and resume are control posts below.\n path === \"/api/signals/stopped\" || path.startsWith(\"/api/signals/stopped/\") ||\n // Pre-merge reviews (#990): PRs, verdicts and what each review cost.\n path === \"/api/review-gate/reviews\"\n}\n\n/** Control POSTs that act as this daemon: reload its config, switch a\n * schedule on or off, stop or steer a running task, kill an agent process,\n * send a message on one of its channels, or announce to the mesh. The CLI, TUI and same-host dashboard reach them over\n * loopback; an off-box caller needs a mesh token. */\nexport function isControlPost(path: string): boolean {\n return path === \"/reload\" || path === \"/api/processes/kill\" ||\n path === \"/send\" || path === \"/send/agent\" || path === \"/send/contact\" ||\n // A note to the whole mesh, published in this node's name.\n path === \"/mesh/announce\" ||\n // AgentX Voice's settings window: rewrites agentx.json, and speaks.\n path === \"/voice/settings\" || path === \"/voice/preview\" ||\n /^\\/api\\/tasks\\/[^/]+\\/(cancel|followup)$/.test(path) ||\n // Pausing, widening, narrowing or ending a guest mesh's grant.\n /^\\/mesh\\/guests\\/[^/]+\\/(pause|resume|end|update)$/.test(path) ||\n /^\\/crons\\/[^/]+\\/enabled$/.test(path) ||\n // Stopping a workflow run (#788).\n /^\\/workflow-runs\\/[^/]+\\/cancel$/.test(path) ||\n // Stop and resume signals (#857).\n path === \"/api/signals/stop\" || path === \"/api/signals/resume\" || path === \"/api/signals/drop\" ||\n // Replaying past PRs through the review gate spends model budget (#990).\n path === \"/api/review-gate/replay\"\n}\n\n/** True when the socket peer is on this host. Used by loopback-only\n * endpoints (e.g. the guard hook) that must never be reachable off-box. */\nexport function isLoopback(remoteAddress: string): boolean {\n return LOOPBACK.has(remoteAddress)\n}\n\nexport function decideMeshAuth(req: MeshAuthRequest): MeshAuthDecision {\n // Checked before every exemption: loopback, the no-token grace path and\n // the off switch all exist for the operator's own tools, not for pages.\n if (req.foreignBrowser) return { allowed: false, reason: \"foreign-browser-origin\" }\n if (req.enforcementDisabled) return { allowed: true, reason: \"disabled\" }\n if (LOOPBACK.has(req.remoteAddress)) return { allowed: true, reason: \"loopback\" }\n if (req.acceptedTokens.size === 0) return { allowed: true, reason: \"no-tokens-configured\" }\n\n const header = req.authorizationHeader || \"\"\n const token = header.startsWith(\"Bearer \") ? header.slice(7).trim() : \"\"\n if (token && req.acceptedTokens.has(token)) return { allowed: true, reason: \"token\" }\n\n return { allowed: false, reason: \"missing-or-invalid-token\" }\n}\n\n/** Config shape this module needs. Structural so callers can pass a full\n * DaemonConfig without this module importing it. */\nexport interface MeshTokenSources {\n mesh?: { peers?: Array<{ token?: string }> }\n dashboard?: { token?: string }\n}\n\n/**\n * Every token accepted on a mesh WRITE path.\n *\n * MESH_TOKEN and per-peer tokens only. `dashboard.token` is deliberately\n * NOT included: it is the most widely handled secret in the config — it\n * sits in dashboard settings and travels alongside peer entries — and\n * accepting it here made it sufficient to POST /task, /channel/send, and\n * the peer-identity forwards that make the daemon act as its own GitLab\n * and GitHub bot.\n *\n * A dashboard on the same host reaches the daemon over loopback, which\n * decideMeshAuth exempts before tokens are consulted. A dashboard on a\n * different host needs a real mesh credential instead.\n */\nexport function collectAcceptedMeshTokens(\n config: MeshTokenSources,\n env: Record<string, string | undefined> = process.env,\n): Set<string> {\n const accepted = new Set<string>()\n if (env.MESH_TOKEN) accepted.add(env.MESH_TOKEN)\n for (const p of config.mesh?.peers || []) if (p.token) accepted.add(p.token)\n return accepted\n}\n\n/**\n * Bearer token the dashboard sends to `target` (a normalized node URL).\n *\n * The primary daemon gets dashboard.token and a configured\n * dashboard.daemons[] entry gets its own token. Without one (a peer the\n * dashboard only learned about through /mesh, or a primary with no\n * dashboard.token) it falls back to MESH_TOKEN, the credential every node\n * in the mesh accepts.\n */\nexport function dashboardTokenForNode(\n dashboard: { daemonUrl: string; token?: string; daemons: Array<{ url: string; token?: string }> },\n target: string,\n env: Record<string, string | undefined> = process.env,\n): string | undefined {\n const norm = (u: string) => u.replace(/\\/+$/, \"\")\n const configured = target === norm(dashboard.daemonUrl)\n ? dashboard.token\n : dashboard.daemons.find((d) => norm(d.url) === target)?.token\n return configured || env.MESH_TOKEN || undefined\n}\n","import type { IncomingHttpHeaders } from \"http\"\n\n// --- Browser-origin guard for the daemon and dashboard HTTP servers ---\n//\n// Both servers trust loopback callers, and a browser on the same machine is\n// a loopback caller. So any web page open in that browser could make it call\n// these servers: send an agent a task, write agent memory, post to a channel.\n// Neither server has a cross-origin browser client (dashboard pages call the\n// dashboard; daemon pages call the daemon; everything else is curl, the CLI,\n// native apps or server-to-server, none of which send Origin), so a request\n// the browser marks as coming from another origin is not trusted.\n//\n// Pure functions, unit tested; the servers add the 403 and the logging.\n\nexport type BrowserOrigin =\n /** No browser origin marker: curl, CLI, native apps, servers. */\n | \"none\"\n /** A page served by this same server. */\n | \"same-origin\"\n /** A page from any other origin, including another port on this host. */\n | \"foreign\"\n\nfunction first(v: string | string[] | undefined): string {\n return (Array.isArray(v) ? v[0] : v)?.trim() ?? \"\"\n}\n\nfunction originHost(origin: string): string | null {\n try {\n return new URL(origin).host.toLowerCase()\n } catch {\n // Opaque origins (\"null\": sandboxed iframes, file://) never match.\n return null\n }\n}\n\n/** Extra trusted origins, for a dashboard served through a reverse proxy\n * under another host name. Comma-separated full origins. */\nexport function allowedOriginsFromEnv(env: NodeJS.ProcessEnv = process.env): Set<string> {\n return new Set(\n (env.AGENTX_ALLOWED_ORIGINS || \"\")\n .split(\",\")\n .map((s) => s.trim().replace(/\\/+$/, \"\").toLowerCase())\n .filter(Boolean),\n )\n}\n\nexport function classifyBrowserRequest(\n headers: IncomingHttpHeaders,\n extraAllowed: ReadonlySet<string> = allowedOriginsFromEnv(),\n): BrowserOrigin {\n const origin = first(headers.origin)\n if (origin) {\n if (extraAllowed.has(origin.replace(/\\/+$/, \"\").toLowerCase())) return \"same-origin\"\n const from = originHost(origin)\n const hosts = [first(headers.host), first(headers[\"x-forwarded-host\"])]\n .filter(Boolean)\n .map((h) => h.toLowerCase())\n return from && hosts.includes(from) ? \"same-origin\" : \"foreign\"\n }\n // Browsers omit Origin on plain GET navigations and image loads, but\n // modern ones still say where the request came from. localhost on\n // another port is \"same-site\", and it is still someone else's page.\n const site = first(headers[\"sec-fetch-site\"]).toLowerCase()\n if (site === \"cross-site\" || site === \"same-site\") return \"foreign\"\n return \"none\"\n}\n\n/** Methods that change state, plus the CORS preflight that precedes a\n * cross-origin write. A foreign page gets none of these. */\nexport function isStateChangingOrPreflight(method: string | undefined): boolean {\n const m = (method || \"GET\").toUpperCase()\n return m !== \"GET\" && m !== \"HEAD\"\n}\n","// --- Configured voices that are not installed, and telling the owner ---\n//\n// macOS can take voices away: it purges the downloaded Siri and Premium\n// voices when the disk runs low. An agent whose voice is gone still\n// speaks, with a stand-in (agent-voice.ts), and only a log line said so.\n// Here the daemon checks every configured voice, tells the owner ONCE per\n// missing voice (at startup, or as soon as a line finds it gone), and says\n// when it is back. The state lives in a small file, so a restart does not\n// repeat the notice, and so the dashboard and `agentx voice list` can show\n// it.\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { dirname, join } from \"path\"\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport { OS_DEFAULT, label, localSystemVoices, type VoiceSettings } from \"./agent-voice\"\nimport { findVoice, forgetWarnings, type SystemVoice } from \"./system-voices\"\nimport { parseSiriId, SIRI_PREFIX } from \"./siri\"\n\n/** Where to get a voice back, as System Settings words it. */\nexport const REINSTALL_HINT = \"System Settings › Accessibility › Spoken Content › System voice › Manage Voices\"\n\nexport interface MissingVoice {\n /** The name as configured, e.g. \"siri:nora\" or a voice id. */\n voice: string\n /** Who names it: agent ids, or \"voice.system\" for the global default. */\n agents: string[]\n /** What each of those agents speaks with instead. */\n speaksWith: Record<string, string>\n /** When it was first found missing (ISO). */\n since: string\n /** When the owner was told (ISO); absent until a notice went out. */\n notifiedAt?: string\n}\n\nexport interface VoiceHealthState {\n checkedAt: string\n missing: MissingVoice[]\n}\n\nexport const voiceHealthPath = (home = homedir()) => join(home, \".agentx\", \"voice\", \"health.json\")\n\n/** Whether a configured name is installed as itself: a Siri voice counts\n * only when that Siri voice is there, not a system voice of its name. */\nexport function isInstalled(name: string, installed: SystemVoice[], locale: string): boolean {\n const q = name.trim()\n if (!q || q.toLowerCase() === OS_DEFAULT) return true\n if (q.toLowerCase().startsWith(SIRI_PREFIX)) {\n const base = q.slice(SIRI_PREFIX.length).trim().toLowerCase()\n return installed.some((v) => v.siri && v.name.toLowerCase() === base)\n }\n return findVoice(q, installed, locale) !== null\n}\n\ntype Config = Pick<DaemonConfig, \"agents\"> & { voice?: VoiceSettings }\n\n/** Every configured voice name, with who names it and in which language. */\nfunction configuredNames(config: Config): Array<{ owner: string; name: string; locale: string; perLanguage: boolean }> {\n const locale = config.voice?.locale ?? \"en\"\n const out: Array<{ owner: string; name: string; locale: string; perLanguage: boolean }> = []\n const add = (owner: string, choice: string | Record<string, string> | undefined) => {\n if (typeof choice === \"string\") out.push({ owner, name: choice, locale, perLanguage: false })\n else for (const [lang, name] of Object.entries(choice ?? {})) out.push({ owner, name, locale: lang, perLanguage: true })\n }\n add(\"voice.system\", config.voice?.system)\n for (const [id, a] of Object.entries(config.agents)) {\n if ((a.voice?.provider ?? config.voice?.provider ?? \"system\") !== \"system\" && config.voice?.fallback === \"none\") continue\n add(id, a.voice?.system)\n }\n return out\n}\n\n/**\n * The configured voices that are not installed, one entry per voice. An\n * empty `installed` means the list could not be read (or not macOS): no\n * verdict, so nothing is reported missing.\n */\nexport function findMissingVoices(config: Config, installed: SystemVoice[]): Array<Omit<MissingVoice, \"since\" | \"notifiedAt\">> {\n if (!installed.length) return []\n const byVoice = new Map<string, Omit<MissingVoice, \"since\" | \"notifiedAt\">>()\n let cast: Map<string, SystemVoice | null> | undefined\n for (const { owner, name, locale, perLanguage } of configuredNames(config)) {\n if (isInstalled(name, installed, locale)) continue\n cast ??= localSystemVoices(config.agents, config.voice, installed)\n const entry = byVoice.get(name) ?? { voice: name, agents: [], speaksWith: {} }\n if (!entry.agents.includes(owner)) entry.agents.push(owner)\n const now = cast.get(owner)\n // A per-language voice's stand-in is not the agent's default voice.\n if (owner !== \"voice.system\" && !perLanguage) entry.speaksWith[owner] = now ? label(now) : \"the OS default voice\"\n byVoice.set(name, entry)\n }\n return [...byVoice.values()]\n}\n\n/** \"Siri Nora (en-US)\" for a Siri id, \"Siri Nora\" for siri:nora, else as is. */\nexport function voiceDisplayName(name: string): string {\n const siri = parseSiriId(name)\n if (siri) return `Siri ${siri.name[0].toUpperCase()}${siri.name.slice(1)} (${siri.locale})`\n if (name.toLowerCase().startsWith(SIRI_PREFIX)) {\n const base = name.slice(SIRI_PREFIX.length).trim()\n return `Siri ${base[0]?.toUpperCase() ?? \"\"}${base.slice(1)}`\n }\n return name\n}\n\n/** The notice: which voices are gone, who they belong to, what speaks\n * now, and how to get them back. */\nexport function missingNotice(missing: MissingVoice[]): { title: string; message: string } {\n const lines = missing.map((m) => {\n const who = m.agents.map((a) => (m.speaksWith[a] ? `${a} (now ${m.speaksWith[a]})` : a)).join(\", \")\n return `• ${voiceDisplayName(m.voice)}: ${who}`\n })\n return {\n title: missing.length === 1 ? \"AgentX: a voice is not installed\" : `AgentX: ${missing.length} voices are not installed`,\n message: `${lines.join(\"\\n\")}\\nReinstall in ${REINSTALL_HINT}. Agents switch back on their own once it is installed.`,\n }\n}\n\nexport function readVoiceHealth(file = voiceHealthPath()): VoiceHealthState | null {\n if (!existsSync(file)) return null\n try {\n const s = JSON.parse(readFileSync(file, \"utf8\"))\n return s && Array.isArray(s.missing) ? s : null\n } catch {\n return null // a torn or hand-edited file is rebuilt by the next check\n }\n}\n\nfunction writeVoiceHealth(file: string, state: VoiceHealthState): void {\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(`${file}.tmp`, JSON.stringify(state, null, 2) + \"\\n\")\n renameSync(`${file}.tmp`, file)\n}\n\nexport interface VoiceHealthDeps {\n /** Tell the owner; resolves once sent (or held for Focus). */\n notify: (title: string, message: string) => Promise<void>\n log: (msg: string) => void\n file?: string\n}\n\n/** Checks configured voices, notifies once per missing voice, and logs\n * when one comes back. One check at a time; calls meanwhile are dropped. */\nexport class VoiceHealth {\n private running = false\n private missingNow = false\n\n constructor(private deps: VoiceHealthDeps) {}\n\n /** Whether the last check found a voice missing. */\n hasMissing(): boolean { return this.missingNow }\n\n async check(config: Config, installed: SystemVoice[], now = new Date()): Promise<VoiceHealthState | null> {\n if (this.running || !installed.length) return null\n this.running = true\n try {\n const file = this.deps.file ?? voiceHealthPath()\n const before = new Map((readVoiceHealth(file)?.missing ?? []).map((m) => [m.voice, m]))\n const found = findMissingVoices(config, installed)\n const missing: MissingVoice[] = found.map((m) => ({ ...m, since: before.get(m.voice)?.since ?? now.toISOString(), ...(before.get(m.voice)?.notifiedAt ? { notifiedAt: before.get(m.voice)!.notifiedAt } : {}) }))\n const back = [...before.keys()].filter((v) => !found.some((m) => m.voice === v))\n for (const v of back) this.deps.log(`[voice] ${voiceDisplayName(v)} is installed again; its agents speak with it again`)\n // Keys are \"<owner>:<name>\", and findVoice's Siri stand-in warns as the name itself.\n if (back.length) forgetWarnings((k) => back.some((v) => k === v || k.endsWith(`:${v}`)))\n const untold = missing.filter((m) => !m.notifiedAt)\n if (untold.length) {\n const { title, message } = missingNotice(untold)\n try {\n await this.deps.notify(title, message)\n for (const m of untold) m.notifiedAt = now.toISOString()\n this.deps.log(`[voice] told the owner: ${untold.map((m) => voiceDisplayName(m.voice)).join(\", \")} not installed`)\n } catch (e: any) {\n // Left untold, so the next check tries again.\n this.deps.log(`[voice] could not send the missing-voice notice: ${e?.message ?? e}`)\n }\n }\n this.missingNow = missing.length > 0\n const state = { checkedAt: now.toISOString(), missing }\n // Only on a change: this runs every minute, often on a nearly full disk.\n if (!existsSync(file) || JSON.stringify(missing) !== JSON.stringify([...before.values()])) writeVoiceHealth(file, state)\n return state\n } finally {\n this.running = false\n }\n }\n}\n","// --- The free voices that ship with macOS ---\n//\n// Every Mac has a few dozen voices installed and can download Premium and\n// Enhanced ones for nothing (System Settings → Accessibility → Spoken\n// Content → Manage Voices). They are the default, so voice works with no\n// account and no key; ElevenLabs is an opt-in upgrade.\n//\n// `say -v '?'` lists names but neither quality nor gender, so the list\n// comes from AVSpeechSynthesisVoice through JXA instead. Two traps shape\n// what is offered:\n// - `say -v <unknown>` exits 0 and quietly uses the default voice, so a\n// configured name is always checked against this list first.\n// - The compact Siri voices in that list cannot be used by `say -v`, and\n// the novelty voices (Bells, Zarvox…) are jokes, not voices for an\n// assistant. Both are left out. The neural Siri voices come from their\n// assets instead (./siri.ts) and are named \"siri:<name>\".\n\nimport { execFile, execFileSync } from \"child_process\"\nimport { listSiriVoices, SIRI_PREFIX } from \"./siri\"\n\nexport type Gender = \"female\" | \"male\" | \"neutral\" | null\n\nexport interface SystemVoice {\n /** What `say -v` takes; unambiguous across languages. */\n id: string\n name: string\n /** BCP 47, e.g. \"en-GB\". */\n locale: string\n quality: \"premium\" | \"enhanced\" | \"standard\"\n gender: Gender\n /** A Siri voice: spoken by switching the OS default, only when named. */\n siri?: boolean\n}\n\nconst JXA = `ObjC.import(\"AVFoundation\")\nconst vs = $.AVSpeechSynthesisVoice.speechVoices, out = []\nfor (let i = 0; i < vs.count; i++) {\n const v = vs.objectAtIndex(i)\n out.push([v.identifier.js, v.name.js, v.language.js, Number(v.quality), Number(v.gender)].join(\"\\\\t\"))\n}\nout.join(\"\\\\n\")`\n\n/** The classic voices worth keeping; the rest of that family are novelties. */\nconst CLASSIC = new Set([\"Fred\", \"Kathy\", \"Ralph\", \"Junior\"])\n/** AVSpeech reports no gender for these families. */\nconst GENDER: Record<string, Gender> = {\n Flo: \"female\", Sandy: \"female\", Shelley: \"female\", Grandma: \"female\", Kathy: \"female\",\n Eddy: \"male\", Reed: \"male\", Rocko: \"male\", Grandpa: \"male\", Fred: \"male\", Ralph: \"male\", Junior: \"male\",\n}\n\n/** Parse the JXA output: id, name, language, quality (1–3), gender (0–2). */\nexport function parseVoiceList(out: string): SystemVoice[] {\n const voices: SystemVoice[] = []\n for (const line of out.split(\"\\n\")) {\n const [id, name, locale, q, g] = line.split(\"\\t\")\n if (!id || !name || !locale) continue\n if (id.includes(\".siri_\")) continue\n if (id.startsWith(\"com.apple.speech.synthesis.voice.\") && !CLASSIC.has(name)) continue\n voices.push({\n id, name, locale,\n quality: q === \"3\" ? \"premium\" : q === \"2\" ? \"enhanced\" : \"standard\",\n gender: g === \"1\" ? \"male\" : g === \"2\" ? \"female\" : GENDER[name] ?? null,\n })\n }\n return voices\n}\n\n// --- Installed voices, cached ---\n//\n// Listing takes a few hundred milliseconds, so it runs once and is then\n// refreshed in the background: a Premium voice downloaded while the daemon\n// runs is picked up within REFRESH_MS without stalling a spoken turn.\n\nconst REFRESH_MS = 10 * 60 * 1000\n/** While a configured voice is missing, look again this often, so a\n * reinstalled voice is picked up without a restart. */\nexport const MISSING_REFRESH_MS = 60 * 1000\nlet cached: { at: number; voices: SystemVoice[] } | null = null\nlet refreshing = false\n\nexport function listSystemVoices(now = Date.now(), maxAgeMs = REFRESH_MS): SystemVoice[] {\n if (process.platform !== \"darwin\") return []\n if (!cached) {\n try {\n cached = { at: now, voices: [...parseVoiceList(execFileSync(\"osascript\", [\"-l\", \"JavaScript\", \"-e\", JXA], { encoding: \"utf8\", timeout: 10_000 })), ...listSiriVoices()] }\n } catch (e: any) {\n voiceLog(`[voice] could not list system voices: ${String(e?.message ?? e).split(\"\\n\")[0]}`)\n cached = { at: now, voices: listSiriVoices() }\n }\n } else if (now - cached.at > maxAgeMs && !refreshing) {\n refreshing = true\n execFile(\"osascript\", [\"-l\", \"JavaScript\", \"-e\", JXA], { timeout: 10_000 }, (err, out) => {\n refreshing = false\n if (!err) cached = { at: Date.now(), voices: [...parseVoiceList(String(out)), ...listSiriVoices()] }\n })\n }\n return cached.voices\n}\n\n// --- Logging, once per problem ---\n\nlet voiceLog: (msg: string) => void = (m) => process.stderr.write(m + \"\\n\")\nconst warned = new Set<string>()\n\n/** The daemon routes voice warnings into its own log. */\nexport function setVoiceLog(fn: (msg: string) => void): void { voiceLog = fn }\n\nexport function warnOnce(key: string, msg: string): void {\n if (warned.has(key)) return\n warned.add(key)\n voiceLog(msg)\n}\n\n/** Warn again about these problems the next time they happen: a voice\n * that came back and then went missing again is news. */\nexport function forgetWarnings(match: (key: string) => boolean): void {\n for (const k of warned) if (match(k)) warned.delete(k)\n}\n\n/** Called when a configured voice is found missing while choosing one to\n * speak with, so the daemon can tell the owner (./voice-health.ts). */\nlet onMissing: (name: string) => void = () => {}\nexport function setMissingVoiceHook(fn: (name: string) => void): void { onMissing = fn }\nexport function reportMissing(name: string): void {\n try { onMissing(name) } catch (e: any) { warnOnce(\"missing-hook\", `[voice] missing-voice check failed: ${e?.message ?? e}`) }\n}\n\n// --- Choosing voices ---\n\nconst QUALITY = { premium: 3, enhanced: 2, standard: 1 } as const\n\n/** Character voices: fine for fun, last choice for an assistant. */\nconst CHARACTER = new Set([\"Grandma\", \"Grandpa\"])\n\n/** Better first: quality tier, then modern over the older families. */\nexport function rank(v: SystemVoice): number {\n const family = v.id.startsWith(\"com.apple.voice.\") ? 2\n : v.id.startsWith(\"com.apple.eloquence.\") && !CHARACTER.has(v.name) ? 1 : 0\n return QUALITY[v.quality] * 10 + family\n}\n\nconst language = (locale: string) => locale.toLowerCase().split(/[-_]/)[0]\n\n/** The current macOS voices (com.apple.voice.*), as opposed to Eloquence,\n * the classic voices and the novelties. */\nconst modern = (v: SystemVoice) => v.id.startsWith(\"com.apple.voice.\")\n\n/**\n * What stands in for a configured voice that is not installed: in its\n * language, of its gender when known, of the best quality there is\n * (premium, then enhanced, then standard). Eloquence and the classic\n * voices only when no current macOS voice speaks the language. Among the\n * best, a voice no one in `taken` uses yet. Null when nothing installed\n * speaks the language.\n */\nexport function fallbackVoice(installed: SystemVoice[], locale: string, gender: Gender | undefined, taken: Set<string> = new Set()): SystemVoice | null {\n let pool = candidates(installed.filter((v) => !v.siri && language(v.locale) === language(locale)), locale)\n if (pool.some(modern)) pool = pool.filter(modern)\n if (gender === \"female\" || gender === \"male\") {\n const same = pool.filter((v) => v.gender === gender)\n if (same.length) pool = same\n }\n if (!pool.length) return null\n const best = pool.filter((v) => v.quality === pool[0].quality)\n const used = new Set([...taken].map((n) => n.toLowerCase()))\n return best.find((v) => !used.has(v.name.toLowerCase())) ?? best[0]\n}\n\n/** Voices in the configured language (e.g. \"en\", \"fr-FR\"), best first; all\n * of them if none match, so a mislabelled locale still speaks. */\nexport function candidates(installed: SystemVoice[], locale = \"en\"): SystemVoice[] {\n const exact = locale.toLowerCase().replace(\"_\", \"-\")\n const lang = language(locale)\n const pool = installed.filter((v) => language(v.locale) === lang)\n return (pool.length ? pool : installed).slice().sort((a, b) =>\n rank(b) - rank(a)\n || Number(b.locale.toLowerCase() === exact) - Number(a.locale.toLowerCase() === exact)\n || a.name.localeCompare(b.name) || a.locale.localeCompare(b.locale))\n}\n\n/**\n * The installed voice a configured name means, or null.\n *\n * Accepts an identifier, a plain name (\"Daniel\"), a tier suffix as System\n * Settings shows it (\"Ava (Premium)\"), or `say`'s language suffix\n * (\"Eddy (English (US))\"). A name several locales share resolves to the\n * best one in the configured language. A Siri voice is named \"siri:aaron\";\n * when its asset is missing, the system voice of that name stands in.\n */\nexport function findVoice(name: string, installed: SystemVoice[], locale = \"en\"): SystemVoice | null {\n const q = name.trim()\n if (!q) return null\n const byId = installed.find((v) => v.id === q)\n if (byId) return byId\n if (q.toLowerCase().startsWith(SIRI_PREFIX)) {\n const base = q.slice(SIRI_PREFIX.length).trim().toLowerCase()\n const siri = candidates(installed.filter((v) => v.siri && v.name.toLowerCase() === base), locale)[0]\n if (siri || !base) return siri ?? null\n // Only a stand-in in the same language: \"siri:nora\" must not become\n // the Norwegian Nora.\n const standIn = findVoice(base, installed.filter((v) => language(v.locale) === language(locale)), locale)\n if (standIn) warnOnce(`siri:${base}`, `[voice] Siri voice \"${base}\" is not installed; speaking with ${standIn.name} (${standIn.locale}) instead.`)\n return standIn\n }\n installed = installed.filter((v) => !v.siri)\n const tier = /^(.+?)\\s*\\((premium|enhanced)\\)$/i.exec(q)\n const base = (tier ? tier[1] : q.replace(/\\s*\\(.*\\)\\s*$/, \"\")).trim().toLowerCase()\n const named = candidates(installed.filter((v) => v.name.toLowerCase() === base), locale)\n const wanted = tier ? named.find((v) => v.quality === tier[2].toLowerCase()) : undefined\n return wanted ?? named[0] ?? null\n}\n\nexport interface CastEntry {\n id: string\n gender?: Gender\n}\n\n/**\n * A distinct voice for each entry, in order: the best remaining voice of\n * the entry's gender, or alternating female and male when it has none, so\n * two agents in a row never sound alike. Distinct means a different NAME:\n * Eddy in en-GB and Eddy in en-US are the same speaker. Names in `taken`\n * (pinned by other agents) are used only once every other voice is; with\n * more agents than voices, voices repeat. A known gender wins over being\n * distinct: once that gender's voices are all used, one is repeated\n * rather than giving the agent a voice of the other gender.\n */\nexport function castVoices(entries: CastEntry[], pool: SystemVoice[], taken: Set<string> = new Set()): Map<string, SystemVoice> {\n const cast = new Map<string, SystemVoice>()\n if (!pool.length) return cast\n const used = new Set([...taken].map((n) => n.toLowerCase()))\n let turn: \"female\" | \"male\" = \"female\"\n for (const e of entries) {\n const known = e.gender === \"female\" || e.gender === \"male\"\n const want = known ? e.gender : turn\n const free = pool.filter((v) => !used.has(v.name.toLowerCase()))\n const pick = free.find((v) => v.gender === want)\n ?? (known ? pool.find((v) => v.gender === want) : undefined)\n ?? free[0] ?? pool[cast.size % pool.length]\n cast.set(e.id, pick)\n used.add(pick.name.toLowerCase())\n if (!known) turn = turn === \"female\" ? \"male\" : \"female\"\n }\n return cast\n}\n","// --- Siri voices, spoken by switching the OS default voice ---\n//\n// `say -v` cannot use the Siri neural voices, but `say` with no -v reads\n// the Spoken Content default (com.apple.Accessibility\n// SpokenContentDefaultVoiceSelectionsByLanguage) afresh on every run. So a\n// Siri line is: take the lock, save the user's selection, write the Siri\n// voice under the system language (the only key `say` reads, whatever\n// language the text is in), run `say`, put the saved selection back.\n//\n// The pref is global, so every `say` that follows it goes through the one\n// script below, from the daemon and from the Mac voice app alike: they\n// share the lock and never race. A run killed mid-line leaves its saved\n// selection behind; the next run restores it before anything else.\n\nimport { execFileSync } from \"child_process\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { join } from \"path\"\nimport type { SystemVoice } from \"./system-voices\"\n\n/** How a config names a Siri voice: \"siri:aaron\". */\nexport const SIRI_PREFIX = \"siri:\"\nconst ID_PREFIX = \"com.apple.ttsbundle.gryphon-neural_\"\n\nexport const isSiriVoice = (id: string | null | undefined): id is string => !!id && id.startsWith(ID_PREFIX)\n\n// --- Can this host switch voices at all? ---\n//\n// Only on macOS, and only when the pieces are really there, not because\n// of a version number: `say`, `defaults` and `plutil`, and the Spoken\n// Content pref domain. Anywhere else (Linux, an older macOS) Siri voices\n// are simply not offered, a configured siri:<name> falls back like any\n// voice that is not installed, and no pref is ever written.\n\nexport interface SiriHost {\n platform?: NodeJS.Platform\n exists?: (path: string) => boolean\n /** True when the com.apple.Accessibility pref domain can be read. */\n prefDomain?: () => boolean\n}\n\nconst TOOLS = [\"/usr/bin/say\", \"/usr/bin/defaults\", \"/usr/bin/plutil\"]\n\nconst readPrefDomain = () => {\n try {\n execFileSync(\"/usr/bin/defaults\", [\"read\", \"com.apple.Accessibility\"], { stdio: \"ignore\", timeout: 5_000 })\n return true\n } catch {\n return false\n }\n}\n\nlet supported: boolean | undefined\n\n/** Whether Siri voices can be spoken here; checked once per process. */\nexport function siriSupported(host: SiriHost = {}): boolean {\n const probe = host.platform !== undefined || host.exists !== undefined || host.prefDomain !== undefined\n if (!probe && supported !== undefined) return supported\n const { platform = process.platform, exists = existsSync, prefDomain = readPrefDomain } = host\n const ok = platform === \"darwin\" && TOOLS.every((t) => exists(t)) && prefDomain()\n if (!probe) supported = ok\n return ok\n}\n\n/** Where MobileAsset keeps the downloaded Siri voices. */\nconst ASSET_DIRS = [\n \"/System/Library/AssetsV2/com_apple_MobileAsset_Trial_Siri_SiriTextToSpeech/purpose_auto\",\n \"/System/Library/AssetsV2/com_apple_MobileAsset_UAF_Siri_TextToSpeech/purpose_auto\",\n]\n\n/** The assets name no gender; these are the voices System Settings offers. */\nconst GENDER: Record<string, SystemVoice[\"gender\"]> = {\n aaron: \"male\", damon: \"male\", daniel: \"male\", pierre: \"male\", samer: \"male\", arthur: \"male\", gordon: \"male\", martin: \"male\",\n nora: \"female\", simone: \"female\", marie: \"female\", soha: \"female\", martha: \"female\", catherine: \"female\", helena: \"female\", nicky: \"female\",\n}\n\n/** A Siri voice's gender, known even when its asset is gone. */\nexport const siriGender = (name: string): SystemVoice[\"gender\"] => GENDER[name.toLowerCase()] ?? null\n\n/** Name and locale from a Siri voice id, e.g. \"nora\" and \"en-US\". */\nexport function parseSiriId(id: string): { name: string; locale: string } | null {\n const m = /^com\\.apple\\.ttsbundle\\.gryphon-neural_([a-z]+)_([a-z]{2}-[A-Z]{2})_premium$/.exec(id)\n return m ? { name: m[1], locale: m[2] } : null\n}\n\n/** A voice asset's specifier, e.g. \"com.apple.siri.tts.voice.en_US.aaron.neural.premium-en_US-iPhone\".\n * Codenames (\"fr-FR-D\") are not voices System Settings offers, so they are skipped. */\nconst SPECIFIER = /com\\.apple\\.siri\\.tts\\.voice\\.([a-z]{2})_([A-Z]{2})\\.([a-z]+)\\.neural\\.premium/g\n\n/** The Siri voices named in asset Info.plists, one per name and locale. */\nexport function parseSiriAssets(plists: string[]): SystemVoice[] {\n const seen = new Map<string, SystemVoice>()\n for (const text of plists) {\n for (const [, lang, region, name] of text.matchAll(SPECIFIER)) {\n const locale = `${lang}-${region}`\n const id = `${ID_PREFIX}${name}_${locale}_premium`\n if (seen.has(id)) continue\n seen.set(id, {\n id, name: name[0].toUpperCase() + name.slice(1), locale,\n quality: \"premium\", gender: GENDER[name] ?? null, siri: true,\n })\n }\n }\n return [...seen.values()]\n}\n\n/** Installed Siri voices. Binary plists keep strings as plain ASCII, so the\n * specifier is found without converting each file. */\nexport function listSiriVoices(dirs = ASSET_DIRS, host: SiriHost = {}): SystemVoice[] {\n if (!siriSupported(host)) return []\n const plists: string[] = []\n for (const dir of dirs) {\n let assets: string[]\n try { assets = readdirSync(dir) } catch { continue }\n for (const a of assets) {\n try { plists.push(readFileSync(join(dir, a, \"Info.plist\"), \"latin1\")) } catch { /* not an asset */ }\n }\n }\n return parseSiriAssets(plists)\n}\n\n// --- Speaking ---\n\n/** Text on stdin; $1 is a voice id: a Siri id switches the OS default for\n * the line, any other id goes to `say -v`, none speaks the OS default as\n * it stands. Every line holds one lock, so none talk over each other.\n * The text is read before the lock, a watchdog bounds each line, lines\n * queued past AGENTX_SAY_STALE_S (30 s) are dropped, and `--stop`\n * silences the line speaking and drops every queued one. */\nexport const SIRI_SAY = `#!/bin/sh\n# Written by agentx; shared by the daemon and AgentX Voice. Do not edit.\n# siri-say.sh [voice] < text speak one line. A Siri voice id switches\n# the OS default for it; any other id is\n# passed to say -v.\n# siri-say.sh --stop silence the line speaking, drop the queue.\n# siri-say.sh --restore put back a voice a killed line left switched.\n# AGENTX_SAY_RATE=<words a minute> sets the speed; unset, say's own.\nD=com.apple.Accessibility K=SpokenContentDefaultVoiceSelectionsByLanguage\ndir=\"$HOME/.agentx/voice\" lock=\"$HOME/.agentx/voice/siri.lock\" saved=\"$HOME/.agentx/voice/siri-saved.plist\"\nstops=\"$dir/stop\"\nread_s=\\${AGENTX_SAY_READ_S:-5} stale_s=\\${AGENTX_SAY_STALE_S:-30} max_s=\\${AGENTX_SAY_MAX_S:-300}\nassets=\\${AGENTX_SIRI_ASSET_DIRS:-\"${ASSET_DIRS.join(\" \")}\"}\nif [ \"$1\" = --stop ]; then\n mkdir -p \"$dir\" || exit 1\n # Queued lines see this change and drop. The owner's trap stops say,\n # restores the voice, and frees the lock.\n echo \"$$ $(date +%s)\" > \"$stops\"\n owner=$(cat \"$lock/pid\" 2>/dev/null)\n [ -n \"$owner\" ] && kill \"$owner\" 2>/dev/null\n exit 0\nfi\nmode=speak\n[ \"$1\" = --restore ] && { mode=restore; set --; }\ncase \"$1\" in com.apple.ttsbundle.gryphon-neural_*) siri=$1 voice= ;; *) siri= voice=$1 ;; esac\nrate=\\${AGENTX_SAY_RATE:-}\ncase \"$rate\" in *[!0-9]*) rate= ;; esac\ncase \"$siri\" in *[!A-Za-z0-9._-]*) echo \"bad voice id\" >&2; exit 2;; esac\n# Not macOS, or no pref tools: speak as is and never touch a pref.\nif [ \"$(uname)\" != Darwin ] || ! command -v defaults >/dev/null || ! command -v plutil >/dev/null; then\n [ \"$mode\" = restore ] && exit 0\n if [ -n \"$voice\" ]; then exec say \\${rate:+-r \"$rate\"} -v \"$voice\"; else exec say \\${rate:+-r \"$rate\"}; fi\nfi\nmkdir -p \"$dir\" || exit 1\nmine=$(cat \"$stops\" 2>/dev/null)\nstopped() { [ \"$(cat \"$stops\" 2>/dev/null)\" != \"$mine\" ]; }\ntxt=$(mktemp \"\\${TMPDIR:-/tmp}/agentx-say.XXXXXX\") || exit 1\nsay_pid= dog=\nrestore() {\n [ -f \"$saved\" ] || return 0\n if [ -s \"$saved\" ]; then defaults write $D $K \"$(cat \"$saved\")\"; else defaults delete $D $K 2>/dev/null; fi\n rm -f \"$saved\"\n}\nfinish() {\n [ -n \"$say_pid\" ] && kill \"$say_pid\" 2>/dev/null\n [ -n \"$dog\" ] && { pkill -P \"$dog\" 2>/dev/null; kill \"$dog\" 2>/dev/null; }\n restore; rm -rf \"$lock\"; rm -f \"$txt\"; exit \"$1\"\n}\ndrop() { rm -f \"$txt\"; exit 75; }\n# A Siri voice whose asset macOS purged: switching to it makes the OS\n# speak a fallback of its own (Eloquence Flo), so leave the pref alone.\nif [ -n \"$siri\" ]; then\n s=\\${siri#com.apple.ttsbundle.gryphon-neural_} ; name=\\${s%%_*} ; s=\\${s#*_} ; loc=$(printf %s \"\\${s%%_*}\" | tr - _)\n found=\n for a in $assets; do\n grep -aqs \"com.apple.siri.tts.voice.$loc.$name.neural.premium\" \"$a\"/*/Info.plist && { found=1; break; }\n done\n [ -z \"$found\" ] && { echo \"Siri voice $siri is not installed; speaking with the OS default\" >&2; siri=; }\nfi\n# The whole text first, before the lock: a writer that never closes stdin\n# must not hold up every other speaker.\n[ \"$mode\" = restore ] && exec 0</dev/null\nexec 3<&0 0</dev/null\ncat <&3 > \"$txt\" & cat_pid=$!\nexec 3<&-\nn=0\nwhile kill -0 \"$cat_pid\" 2>/dev/null; do\n if [ $n -ge $((read_s * 10)) ]; then\n kill \"$cat_pid\" 2>/dev/null; echo \"stdin never closed; nothing said\" >&2; rm -f \"$txt\"; exit 3\n fi\n sleep 0.1; n=$((n + 1))\ndone\n# One speaker at a time. A lock whose owner died is taken over. A line\n# that waits past stale_s, or that a stop overtook, is dropped unsaid.\nn=0\nwhile ! mkdir \"$lock\" 2>/dev/null; do\n stopped && drop\n if [ $n -ge $((stale_s * 10)) ]; then echo \"waited \\${stale_s}s for the speaker; dropped\" >&2; drop; fi\n owner=$(cat \"$lock/pid\" 2>/dev/null)\n if [ -n \"$owner\" ] && ! kill -0 \"$owner\" 2>/dev/null; then rm -rf \"$lock\"\n elif [ -z \"$owner\" ] && [ -n \"$(find \"$lock\" -maxdepth 0 -mmin +1 2>/dev/null)\" ]; then rm -rf \"$lock\"\n else sleep 0.1; n=$((n + 1)); fi\ndone\necho $$ > \"$lock/pid\"\ntrap 'finish 143' TERM INT HUP\nstopped && finish 75\n[ \"$mode\" = restore ] && finish 0\nrestore\nif [ -n \"$siri\" ]; then\n lang=$(defaults read -g AppleLanguages 2>/dev/null | sed -n '2s/^[^A-Za-z]*\\\\([A-Za-z]*\\\\).*/\\\\1/p')\n defaults export $D - 2>/dev/null | plutil -extract $K xml1 -o \"$saved.tmp\" - 2>/dev/null || : > \"$saved.tmp\"\n mv \"$saved.tmp\" \"$saved\"\n defaults write $D $K -array \"\\${lang:-en}\" \"{ _type = \\\\\"Speech.VoiceSelection\\\\\"; _version = 0; voiceId = \\\\\"$siri\\\\\"; }\"\nfi\n# A watchdog: no line runs past its length's worth of speech (a hung say\n# held the lock for minutes), capped at max_s.\nlimit=$((5 + $(wc -w < \"$txt\") * 6 / 10))\n[ $limit -gt $max_s ] && limit=$max_s\nif [ -n \"$voice\" ]; then say \\${rate:+-r \"$rate\"} -v \"$voice\" -f \"$txt\" & else say \\${rate:+-r \"$rate\"} -f \"$txt\" & fi\nsay_pid=$!\n( sleep \"$limit\"; echo \"say ran past \\${limit}s; stopped\" >&2; kill \"$say_pid\" 2>/dev/null ) & dog=$!\nwait \"$say_pid\"\nstatus=$?\nsay_pid=\nfinish $status\n`\n\n/** Where the script lives; AgentX Voice runs the same file. */\nexport const siriSayPath = (home = homedir()) => join(home, \".agentx\", \"voice\", \"siri-say.sh\")\n\n/** Write the script if it is missing or out of date; returns its path. */\nexport function ensureSiriSay(home = homedir()): string {\n const path = siriSayPath(home)\n if (existsSync(path) && readFileSync(path, \"utf8\") === SIRI_SAY) return path\n mkdirSync(join(home, \".agentx\", \"voice\"), { recursive: true })\n writeFileSync(`${path}.tmp`, SIRI_SAY, { mode: 0o755 })\n renameSync(`${path}.tmp`, path)\n return path\n}\n","// --- Per-agent voices, and when an agent should introduce itself ---\n//\n// One global voice made every agent sound like the same person, so a\n// voice conversation could not tell you who was answering. Each agent may\n// carry a `voice` block in agentx.json; anything it leaves out falls back\n// to the global `voice` settings. Out of the box every agent speaks with\n// a free macOS voice of its own; ElevenLabs is opt-in (voice.provider).\n\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport type { TalkSpeaker } from \"./talk\"\nimport type { VoiceRef } from \"./speaker\"\nimport { castVoices, candidates, fallbackVoice, findVoice, listSystemVoices, reportMissing, warnOnce, type CastEntry, type SystemVoice } from \"./system-voices\"\nimport { parseSiriId, siriGender, SIRI_PREFIX } from \"./siri\"\nimport { detectLanguage } from \"./language\"\n\ntype AgentConfig = DaemonConfig[\"agents\"][string]\ntype Agents = DaemonConfig[\"agents\"]\nexport type VoiceSettings = Partial<DaemonConfig[\"voice\"]>\nexport type VoiceProvider = DaemonConfig[\"voice\"][\"provider\"]\n/** A configured system voice: one name, or one per language. */\nexport type SystemChoice = NonNullable<DaemonConfig[\"voice\"][\"system\"]>\n\n/** The name that means the OS default voice: `say` with no -v follows\n * System Settings → Spoken Content, the only way apps get a Siri voice. */\nexport const OS_DEFAULT = \"system\"\nconst isOsDefault = (name: string) => name.trim().toLowerCase() === OS_DEFAULT\n\n/** ElevenLabs \"Rachel\" — the voice every agent used before this existed. */\nexport const DEFAULT_VOICE_ID = \"21m00Tcm4TlvDq8ikWAM\"\n\nexport interface AgentVoice {\n agentId: string\n name: string\n /** Which engine speaks: the free system voices, or ElevenLabs. */\n provider: VoiceProvider\n /** Null when the agent has none: the client applies its own default. */\n elevenlabsVoiceId: string | null\n /** The macOS voice identifier for `say -v`; null for the OS default. */\n systemVoice: string | null\n /** Its readable name, e.g. \"Daniel (en-GB)\". */\n systemVoiceName: string | null\n /** A voice per language (\"fr\", \"ar\"; null for the OS default), used for\n * lines in that language. Empty when one voice speaks every language. */\n systemByLanguage: Record<string, SystemVoice | null>\n /** When ElevenLabs cannot speak, use the system voice instead. */\n fallback: boolean\n gender: \"female\" | \"male\" | \"neutral\" | null\n style: string | null\n intro: string\n /** Speaking speed, 1 = normal; absent when unset. */\n rate?: number\n /** Place in the speaking queue; absent when unset (normal). */\n priority?: \"high\" | \"normal\" | \"low\"\n}\n\n/**\n * How an agent sounds. The provider is the agent's, else the global one\n * (system by default). The system voice is the agent's, else the global\n * default, else one assigned to it alone; a name that is not installed\n * is skipped with one warning.\n */\nexport function resolveAgentVoice(\n agentId: string,\n agents: Agents,\n settings: VoiceSettings = {},\n installed: SystemVoice[] = listSystemVoices(),\n): AgentVoice {\n const agent: Partial<AgentConfig> | undefined = agents[agentId]\n const name = agent?.name || agentId\n const v = agent?.voice\n const sys = localSystemVoices(agents, settings, installed).get(agentId) ?? null\n const systemByLanguage = languageVoices(agentId, v?.system, v?.gender, settings, installed)\n return {\n agentId,\n name,\n provider: v?.provider ?? settings.provider ?? \"system\",\n elevenlabsVoiceId: v?.elevenlabsVoiceId || null,\n systemVoice: sys?.id ?? null,\n systemVoiceName: sys ? label(sys) : null,\n systemByLanguage,\n fallback: (settings.fallback ?? \"system\") === \"system\",\n gender: v?.gender ?? null,\n style: v?.style || null,\n intro: v?.intro || deriveIntro(name, agent?.systemPrompt),\n ...(v?.rate ? { rate: v.rate } : {}),\n ...(v?.priority ? { priority: v.priority } : {}),\n }\n}\n\nexport const label = (v: SystemVoice) =>\n `${v.name}${v.siri ? \" (Siri)\" : v.quality === \"standard\" ? \"\" : ` (${v.quality[0].toUpperCase()}${v.quality.slice(1)})`} ${v.locale}`\n\n/** A configured name, checked: the installed voice, or null and a warning. */\nexport function checkedVoice(owner: string, name: string | undefined, installed: SystemVoice[], locale: string): SystemVoice | null {\n if (!name || !installed.length) return null\n const v = findVoice(name, installed, locale)\n if (!v) {\n warnOnce(`${owner}:${name}`, `[voice] ${owner}: system voice \"${name}\" is not installed; using the next choice. Run \\`agentx voice list\\` to see installed voices.`)\n reportMissing(name)\n }\n return v\n}\n\n/** What a configured name tells about the voice it meant, even when that\n * voice is gone: its language and gender. */\nexport function voiceTraits(name: string, locale: string): { locale: string; gender: SystemVoice[\"gender\"] } {\n const q = name.trim()\n if (q.toLowerCase().startsWith(SIRI_PREFIX)) return { locale, gender: siriGender(q.slice(SIRI_PREFIX.length).trim()) }\n const siri = parseSiriId(q)\n if (siri) return { locale: siri.locale, gender: siriGender(siri.name) }\n const system = /^com\\.apple\\.voice\\.[a-z]+\\.([a-z]{2}-[A-Z]{2})\\./.exec(q)\n return { locale: system?.[1] ?? locale, gender: null }\n}\n\n/** Someone to cast: their configured voice and gender, if any, and what\n * to speak with when that voice is not installed. */\nexport interface VoiceWish { id: string; system?: SystemChoice; gender?: SystemVoice[\"gender\"]; fallbacks?: string[] }\n\nconst language = (locale: string) => locale.toLowerCase().split(/[-_]/)[0]\n\n/** The configured name for one language: a plain name serves them all. */\nconst nameFor = (choice: SystemChoice | undefined, lang: string) =>\n typeof choice === \"string\" ? choice : choice?.[language(lang)]\n\n/** A configured name: the installed voice, null for the OS default, or\n * undefined when unset or not installed (with a warning). */\nfunction configured(owner: string, name: string | undefined, installed: SystemVoice[], locale: string): SystemVoice | null | undefined {\n if (name === undefined) return undefined\n if (isOsDefault(name)) return null\n return checkedVoice(owner, name, installed, locale) ?? undefined\n}\n\n/** The global default suits an agent unless both genders are known and differ. */\nconst fitsGender = (v: SystemVoice | null, gender: SystemVoice[\"gender\"] | undefined) =>\n !v || !v.gender || v.gender === \"neutral\" || !gender || gender === \"neutral\" || v.gender === gender\n\n/**\n * A system voice for each wish, in order: its own if installed, else the\n * global default if it is of the wish's gender, else a distinct one of its\n * own. Null means the OS default voice. Names in `taken` are already\n * spoken for (e.g. by local agents, when casting mesh agents).\n */\nexport function castSystemVoices(wishes: VoiceWish[], settings: VoiceSettings, installed: SystemVoice[], taken: Set<string> = new Set()): Map<string, SystemVoice | null> {\n const locale = settings.locale ?? \"en\"\n const out = new Map<string, SystemVoice | null>()\n const open: CastEntry[] = []\n const fallback = configured(\"voice.system\", nameFor(settings.system, locale), installed, locale)\n const inUse = () => new Set([...taken, ...[...out.values()].flatMap((v) => (v ? [v.name] : []))])\n for (const w of wishes) {\n const name = nameFor(w.system, locale)\n const own = configured(w.id, name, installed, locale)\n const pick = own !== undefined ? own\n : name !== undefined && installed.length ? standIn(w, name, locale, fallback, installed, inUse())\n : fallback !== undefined && fitsGender(fallback, w.gender) ? fallback : undefined\n if (pick !== undefined) out.set(w.id, pick)\n else open.push({ id: w.id, gender: w.gender ?? null })\n }\n const names = inUse()\n // Siri voices switch a pref the user owns, so only an agent that names one gets one.\n for (const [id, v] of castVoices(open, candidates(installed.filter((v) => !v.siri), locale), names)) out.set(id, v)\n return out\n}\n\n/**\n * What speaks for an agent whose configured voice is not installed: the\n * first installed name in its `fallbacks`, else the global default when\n * it suits the agent's gender, else the best installed voice in the\n * missing voice's language and of its gender (fallbackVoice).\n */\nfunction standIn(w: VoiceWish, name: string, locale: string, fallback: SystemVoice | null | undefined, installed: SystemVoice[], taken: Set<string>): SystemVoice | null | undefined {\n for (const f of w.fallbacks ?? []) {\n const v = configured(`${w.id}.fallbacks`, f, installed, locale)\n if (v !== undefined) return v\n }\n if (fallback !== undefined && fitsGender(fallback, w.gender)) return fallback\n const traits = voiceTraits(name, locale)\n return fallbackVoice(installed, traits.locale, w.gender ?? traits.gender, taken) ?? undefined\n}\n\n/**\n * The voice per language for lines not in the default one: the agent's\n * own list over the global one. A plain name on the agent speaks every\n * language, so there is nothing to switch to.\n */\nexport function languageVoices(\n owner: string, own: SystemChoice | undefined, gender: SystemVoice[\"gender\"] | undefined,\n settings: VoiceSettings, installed: SystemVoice[],\n): Record<string, SystemVoice | null> {\n if (typeof own === \"string\") return {}\n const out: Record<string, SystemVoice | null> = {}\n const add = (who: string, list: SystemChoice | undefined, suits: (v: SystemVoice | null) => boolean) => {\n if (!list || typeof list === \"string\") return\n for (const [lang, name] of Object.entries(list)) {\n let v = configured(`${who}.${lang}`, name, installed, lang)\n // Missing: the best installed voice in that language stands in.\n if (v === undefined && installed.length) v = fallbackVoice(installed, lang, gender ?? voiceTraits(name, lang).gender) ?? undefined\n if (v !== undefined && suits(v)) out[language(lang)] = v\n }\n }\n add(\"voice.system\", settings.system, (v) => fitsGender(v, gender))\n add(owner, own, () => true)\n return out\n}\n\n/** The voice for one line: the voice of its language when the agent has\n * one, else its default voice. */\nexport function voiceForText(v: AgentVoice, text?: string | null): AgentVoice {\n const lang = text ? detectLanguage(text) : null\n if (!lang || !(lang in v.systemByLanguage)) return v\n const sys = v.systemByLanguage[lang]\n return { ...v, systemVoice: sys?.id ?? null, systemVoiceName: sys ? label(sys) : null }\n}\n\n/** Every local agent's system voice, assigned in config order, so adding\n * an agent at the end never changes anyone else's voice. */\nexport function localSystemVoices(agents: Agents, settings: VoiceSettings = {}, installed: SystemVoice[] = listSystemVoices()): Map<string, SystemVoice | null> {\n const wishes = Object.entries(agents).map(([id, a]) => ({ id, system: a.voice?.system, gender: a.voice?.gender, fallbacks: a.voice?.fallbacks }))\n return castSystemVoices(wishes, settings, installed)\n}\n\n/** What the speech engine needs; `explicit` is a one-off ElevenLabs id. */\nexport function voiceRef(v: AgentVoice, explicit?: string | null): VoiceRef {\n const ref: VoiceRef = { provider: v.provider, elevenlabs: pickVoiceId(explicit, v.elevenlabsVoiceId), system: v.systemVoice, fallback: v.fallback }\n const langs = Object.entries(v.systemByLanguage ?? {})\n if (langs.length) ref.languages = Object.fromEntries(langs.map(([l, sys]) => [l, sys?.id ?? null]))\n if (v.rate && v.rate !== 1) ref.rate = v.rate\n if (v.priority && v.priority !== \"normal\") ref.priority = v.priority\n return ref\n}\n\n/** The voice id to synthesise with: explicit, then agent, then env, then Rachel. */\nexport function pickVoiceId(explicit?: string | null, agentVoiceId?: string | null): string {\n return explicit || agentVoiceId || process.env.AGENTX_VOICE_ID || DEFAULT_VOICE_ID\n}\n\n/**\n * \"Hello, this is Nadia, the marketing agent for Acme.\" from a system\n * prompt that opens \"You are Nadia, the marketing agent for Acme (…).\".\n *\n * Only the first sentence is used, because that is where every persona\n * says who it is; the rest is instructions, which make terrible speech.\n */\nexport function deriveIntro(name: string, systemPrompt?: string): string {\n const first = (systemPrompt ?? \"\").split(/(?<=\\.)\\s/)[0] ?? \"\"\n const m = /^You are (.+?)\\.?$/i.exec(first.trim())\n if (!m) return `Hello, this is ${name}.`\n // Parentheticals (\"(acme.com)\") read badly aloud.\n const who = m[1].replace(/\\s*\\([^)]*\\)/g, \"\").trim()\n const [lead, ...rest] = who.split(/,\\s*/)\n const role = rest.join(\", \")\n // \"You are Nadia, the marketing agent\" — lead is the name.\n if (role && lead.toLowerCase() === name.toLowerCase()) return `Hello, this is ${name}, ${role}.`\n // \"You are the Accountant agent for Acme\" — the name is already in it.\n // Not for \"a taxonomy/graph agent\": a generic kind still needs the name.\n if (!/^an? /i.test(who) && who.toLowerCase().includes(name.toLowerCase())) return `Hello, this is ${who}.`\n return `Hello, this is ${name}, ${who}.`\n}\n\n// --- Introductions ---\n\n/** Silence longer than this makes the next reply a fresh introduction. */\nexport const INTRO_GAP_MS = 8 * 60 * 60 * 1000\nconst MAX_TRACKED = 500\n\n/**\n * Remembers who has already spoken in which voice session.\n *\n * In memory on purpose: a daemon restart re-introducing everyone once is\n * harmless, and persisting \"who said hello\" is not worth a file.\n */\nexport class VoiceIntroTracker {\n private lastSpoke = new Map<string, number>()\n\n constructor(private gapMs = INTRO_GAP_MS) {}\n\n needsIntro(session: string, agentId: string, now = Date.now()): boolean {\n const last = this.lastSpoke.get(key(session, agentId))\n return last === undefined || now - last > this.gapMs\n }\n\n /** Call only after the agent actually answered, or a failed turn would\n * swallow the introduction. */\n spoke(session: string, agentId: string, now = Date.now()): void {\n const k = key(session, agentId)\n // Re-insert so Map order stays oldest-first for eviction.\n this.lastSpoke.delete(k)\n this.lastSpoke.set(k, now)\n if (this.lastSpoke.size > MAX_TRACKED) {\n this.lastSpoke.delete(this.lastSpoke.keys().next().value as string)\n }\n }\n}\n\nconst key = (session: string, agentId: string) => `${session}\\u0000${agentId}`\n\n/** The system-prompt line that makes the agent introduce itself, or not. */\nexport function introInstruction(voice: AgentVoice, introduce: boolean): string {\n const style = voice.style ? ` Your speaking manner: ${voice.style}.` : \"\"\n if (introduce) {\n return (\n \"[VOICE INTRO] This is your first spoken exchange with this person in a while. \" +\n `Open with one short sentence introducing yourself in your own manner, along the lines of \"${voice.intro}\", ` +\n `then answer.${style}`\n )\n }\n return (\n \"[VOICE CASUAL] You have already introduced yourself in this voice conversation. \" +\n \"Do not introduce yourself or greet again; talk casually, like a colleague in the same office.\" +\n style\n )\n}\n\n/** An agent as a talk participant: who it is, how it sounds, whether it\n * still owes the listener an introduction. */\nexport function talkSpeaker(agentId: string, agents: DaemonConfig[\"agents\"], introduce: boolean, settings: VoiceSettings = {}): TalkSpeaker {\n const agent = agents[agentId]\n const voice = resolveAgentVoice(agentId, agents, settings)\n // The first paragraph is where a persona says who it is; the rest is\n // task instructions, which do not belong in small talk.\n const persona = (agent?.systemPrompt ?? \"\").split(/\\n\\s*\\n/)[0].slice(0, 600).trim()\n return {\n agentId,\n name: voice.name,\n voice: voiceRef(voice),\n persona: persona || `You are ${voice.name}.`,\n introLine: introInstruction(voice, introduce),\n }\n}\n\n/** Rides in the system append of every voice turn, never in the message,\n * or the session would record the scaffolding as the person's words. */\nexport const VOICE_MODE_INSTRUCTION =\n \"[VOICE MODE] This question arrived by voice and your reply will be spoken aloud by a \" +\n \"TTS engine. Answer in two or three short sentences. Plain language, no markdown, no \" +\n \"code blocks, no bullet points, no URLs. Speak conversationally.\"\n\n/** What a mesh peer sends in `context.voice` when a remote Mac speaks for\n * one of this node's agents. Only these fields cross the mesh, never\n * free-form system text. */\nexport interface RemoteVoiceTurn { introduce?: boolean; intro?: string; style?: string }\n\n/** The system append for a voice turn that arrived over the mesh, or\n * undefined when the task is not a voice turn. */\nexport function remoteVoiceAppend(context: unknown): string | undefined {\n const ctx = context as { channel?: unknown; voice?: RemoteVoiceTurn } | undefined\n if (ctx?.channel !== \"voice\" || !ctx.voice || typeof ctx.voice !== \"object\") return undefined\n const clip = (s: unknown) => (typeof s === \"string\" ? s.replace(/\\s+/g, \" \").slice(0, 200) : \"\")\n const intro = clip(ctx.voice.intro)\n const voice: AgentVoice = {\n agentId: \"\", name: \"\", provider: \"system\", elevenlabsVoiceId: null,\n systemVoice: null, systemVoiceName: null, systemByLanguage: {}, fallback: true, gender: null,\n style: clip(ctx.voice.style) || null,\n intro: intro || \"Hello.\",\n }\n return `${VOICE_MODE_INSTRUCTION}\\n${introInstruction(voice, ctx.voice.introduce === true && !!intro)}`\n}\n","import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from \"fs\"\nimport { resolve, join } from \"path\"\nimport type { BusinessConfig, BusinessWorkSource } from \"./config\"\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport { BacklogStore } from \"./backlog-store\"\nimport { syncBacklogItemToSource, resolveAssigneesForSource, type BacklogPatch } from \"./backlog-sync\"\n\n// --- Work pool: pluggable source of tasks for the business layer ---\n\nexport interface WorkItem {\n id: string // stable id for claim/report (e.g. \"backlog:line-12\", \"gitlab:proj:issue:42\")\n title: string\n description?: string\n assignee?: string // agentId\n estimatedSeconds?: number\n url?: string\n priority?: number // lower = higher priority\n /** Board stage / kanban column id. Was a closed enum back when boards\n * had a fixed schema; widened to `string` because per-project boards\n * can declare arbitrary column ids. The well-known names below remain\n * the standard set most boards use. */\n stage?: string // typically: \"triage\" | \"todo\" | \"doing\" | \"onhold\" | \"review\" | \"done\"\n /** Raw labels from the source (GitLab). Empty array if source doesn't support labels. */\n labels?: string[]\n /** Label name+color when the source returns colored labels (GitLab with_labels_details). */\n labelDetails?: Array<{ name: string; color: string; text_color?: string }>\n /** ISO timestamp of last update on the source. */\n updatedAt?: string\n /** Open/closed state from the source. */\n state?: \"opened\" | \"closed\"\n /** Optional milestone title (GitLab: issue.milestone.title). */\n milestone?: string\n /** GitLab assignees (0..n). `assignee` above is the agentId alias for assignees[0]. */\n assignees?: Array<{ username: string; name?: string; avatarUrl?: string }>\n}\n\n/** Member of a source (GitLab project members) — used for assignee pickers. */\nexport interface WorkMember {\n username: string\n name?: string\n avatarUrl?: string\n}\n\n/** Milestone on a source — used for milestone pickers. */\nexport interface WorkMilestone {\n id: number | string\n title: string\n state?: \"active\" | \"closed\"\n dueDate?: string\n}\n\n/** Full issue detail — a superset of WorkItem for the detail view. */\nexport interface WorkDetail extends WorkItem {\n /** Raw body, may be markdown. */\n descriptionHtml?: string\n /** ISO timestamp of creation. */\n createdAt?: string\n /** Author (issue reporter). */\n author?: { username: string; name?: string; avatarUrl?: string }\n /** Full labels list (all of them). */\n allLabels?: string[]\n}\n\nexport interface WorkReport {\n status: \"in-progress\" | \"done\" | \"blocked\"\n note?: string\n timeSeconds?: number\n blocker?: string\n}\n\n/** What a WorkSource can do beyond the minimal listOpen/claim/report triple. */\nexport interface WorkSourceCapabilities {\n /** Enumerate items beyond assignee filter — required for a board view. */\n listAll: boolean\n /** Create new items (e.g. POST GitLab issue). */\n create: boolean\n /** Arbitrary column transitions (label add/remove pair). */\n transition: boolean\n /** Surfaces labels[] on returned items. */\n labels: boolean\n /** Server-side text search. */\n search: boolean\n}\n\nexport interface WorkCreateInput {\n title: string\n description?: string\n /** agentId */\n assignee?: string\n /** Raw GitLab usernames — bypass agentId resolution for UI-driven creates. */\n assigneeUsernames?: string[]\n labels?: string[]\n /** Milestone title (resolved to id). */\n milestoneTitle?: string\n /** For sources that span multiple projects (gitlab): \"group/project\". */\n projectHint?: string\n}\n\nexport interface ListAllOpts {\n sinceDays?: number\n labels?: string[]\n /** Exclude items carrying any of these labels (client-side filter). */\n notLabels?: string[]\n /** Exclude items whose labels carry this scoped prefix (e.g. \"Status\"). */\n withoutScopedPrefix?: string\n search?: string\n assignee?: string\n /** GitLab issue state. Defaults to \"opened\" (boards hide closed issues). */\n state?: \"opened\" | \"closed\" | \"all\"\n /** Milestone filter (GitLab \"milestone\" query param). */\n milestone?: string\n}\n\nexport interface WorkSource {\n type: string\n capabilities: WorkSourceCapabilities\n listOpen(agentId: string): Promise<WorkItem[]>\n claim(agentId: string, itemId: string): Promise<void>\n report(itemId: string, update: WorkReport): Promise<void>\n /** Optional — gated by capabilities.listAll. */\n listAll?(opts: ListAllOpts): Promise<WorkItem[]>\n /** Optional — gated by capabilities.create. */\n create?(input: WorkCreateInput): Promise<WorkItem>\n /**\n * Optional — gated by capabilities.transition. Add `toLabel` and remove\n * `fromLabel` (if provided) from the item. Used by the kanban board on drag.\n */\n transition?(itemId: string, toLabel: string, fromLabel?: string): Promise<void>\n}\n\n// ---------- BacklogWorkSource: GFM checklist file ----------\n// Format in .agentx/backlog.md (or configured path):\n//\n// - [ ] @alice Implement login form [time: 2h]\n// - [x] @alice Write unit tests (done)\n// - [ ] @bob Design pricing page [time: 1h30m]\n//\n// Line number is the stable id.\n\nconst CHECK_RE = /^(\\s*)-\\s*\\[( |x|X)\\]\\s*(.*)$/\nconst MENTION_RE = /@([a-z0-9_-]+)/i\nconst TIME_RE = /\\[time:\\s*([0-9hm\\s]+)\\]/i\n\nfunction parseDuration(s: string): number {\n // \"2h30m\" -> 9000 s. \"1h\" -> 3600. \"45m\" -> 2700.\n const hMatch = s.match(/(\\d+)\\s*h/i)\n const mMatch = s.match(/(\\d+)\\s*m/i)\n return (hMatch ? parseInt(hMatch[1], 10) * 3600 : 0) + (mMatch ? parseInt(mMatch[1], 10) * 60 : 0)\n}\n\nexport class BacklogWorkSource implements WorkSource {\n type = \"backlog\"\n capabilities: WorkSourceCapabilities = {\n listAll: true, create: false, transition: false, labels: true, search: false,\n }\n private store: BacklogStore\n\n constructor(\n private path: string,\n private daemon?: DaemonConfig,\n private log: (...args: unknown[]) => void = () => {},\n ) {\n this.store = new BacklogStore(this.path)\n }\n\n /** Expose the store so the CLI and sync-back can share state. */\n getStore(): BacklogStore {\n return this.store\n }\n\n private readMd(): string[] {\n const full = resolve(process.cwd(), this.path)\n if (!existsSync(full)) return []\n return readFileSync(full, \"utf-8\").split(\"\\n\")\n }\n\n private writeMd(lines: string[]): void {\n const full = resolve(process.cwd(), this.path)\n writeFileSync(full, lines.join(\"\\n\"))\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n if (this.store.exists()) {\n const items = this.store.list().filter((it) =>\n it.status !== \"done\" &&\n (!it.assignee || it.assignee.toLowerCase() === agentId.toLowerCase()),\n )\n return items.map((it) => this.toWorkItem(it))\n }\n // Legacy markdown-checklist fallback (unchanged behavior).\n const lines = this.readMd()\n const items: WorkItem[] = []\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m) continue\n const checked = m[2].toLowerCase() === \"x\"\n if (checked) continue\n const body = m[3]\n const mention = body.match(MENTION_RE)\n if (mention && mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n const timeMatch = body.match(TIME_RE)\n const title = body.replace(MENTION_RE, \"\").replace(TIME_RE, \"\").trim()\n items.push({\n id: `backlog:${i}`,\n title: title || `Task at line ${i + 1}`,\n assignee: mention ? mention[1] : undefined,\n estimatedSeconds: timeMatch ? parseDuration(timeMatch[1]) : undefined,\n priority: i,\n })\n }\n return items\n }\n\n async listAll(_opts: ListAllOpts = {}): Promise<WorkItem[]> {\n if (!this.store.exists()) return []\n return this.store.list().map((it) => this.toWorkItem(it))\n }\n\n async claim(agentId: string, itemId: string): Promise<void> {\n if (!this.store.exists()) return\n const item = this.store.findById(itemId)\n if (!item) return\n const updated = this.store.update(itemId, {\n assignee: item.assignee || agentId,\n status: \"doing\",\n })\n if (updated.source && this.daemon) {\n const usernames = resolveAssigneesForSource([updated.assignee || agentId], updated.source, this.daemon)\n void syncBacklogItemToSource(updated, {\n assigneeUsernames: usernames,\n addLabels: [\"Doing\"],\n removeLabels: [\"To Do\"],\n }, this.daemon, this.log).catch((e) =>\n this.log(`[backlog-sync] claim failed for ${itemId}: ${e?.message ?? e}`),\n )\n }\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n // Structured store path.\n if (this.store.exists() && this.store.findById(itemId)) {\n const patch: Partial<import(\"./backlog-store\").BacklogItem> = {}\n if (update.status === \"done\") patch.status = \"done\"\n else if (update.status === \"blocked\") {\n patch.status = \"blocked\"\n patch.blocker = update.blocker || update.note || undefined\n } else if (update.status === \"in-progress\") patch.status = \"doing\"\n if (update.timeSeconds) patch.estimatedSeconds = update.timeSeconds\n const item = this.store.update(itemId, patch)\n\n if (item.source && this.daemon) {\n const sourcePatch: BacklogPatch = {}\n if (update.status === \"done\") {\n sourcePatch.addLabels = [\"Done\"]\n sourcePatch.removeLabels = [\"Doing\"]\n } else if (update.status === \"blocked\") {\n sourcePatch.addLabels = [\"Blocked\"]\n }\n if (update.note) sourcePatch.note = update.note\n if (Object.keys(sourcePatch).length) {\n void syncBacklogItemToSource(item, sourcePatch, this.daemon, this.log).catch((e) =>\n this.log(`[backlog-sync] report failed for ${itemId}: ${e?.message ?? e}`),\n )\n }\n }\n return\n }\n\n // Legacy markdown path (line-number ids).\n if (!itemId.startsWith(\"backlog:\")) throw new Error(`not a backlog id: ${itemId}`)\n const lineNo = parseInt(itemId.slice(\"backlog:\".length), 10)\n const lines = this.readMd()\n const line = lines[lineNo]\n if (!line) throw new Error(`backlog line ${lineNo} missing`)\n const m = line.match(CHECK_RE)\n if (!m) throw new Error(`backlog line ${lineNo} not a checkbox`)\n\n if (update.status === \"done\") {\n lines[lineNo] = line.replace(/- \\[ \\]/, \"- [x]\")\n const note = update.timeSeconds\n ? ` <!-- done by agent, ${Math.round(update.timeSeconds / 60)}m -->`\n : \" <!-- done by agent -->\"\n lines.splice(lineNo + 1, 0, note)\n } else if (update.status === \"blocked\") {\n lines.splice(lineNo + 1, 0, ` <!-- BLOCKED: ${update.blocker || update.note || \"?\"} -->`)\n } else if (update.note) {\n lines.splice(lineNo + 1, 0, ` <!-- ${update.note.replace(/-->/g, \"— >\")} -->`)\n }\n this.writeMd(lines)\n }\n\n private toWorkItem(it: import(\"./backlog-store\").BacklogItem): WorkItem {\n const stage = ({ todo: \"todo\", doing: \"doing\", blocked: \"onhold\", done: \"done\" } as const)[it.status]\n return {\n id: it.id,\n title: it.title,\n description: it.description,\n assignee: it.assignee,\n estimatedSeconds: it.estimatedSeconds,\n priority: it.priority,\n labels: it.labels,\n stage,\n milestone: it.milestone,\n url: it.source?.url,\n updatedAt: it.updatedAt,\n state: it.status === \"done\" ? \"closed\" : \"opened\",\n }\n }\n}\n\n// ---------- WikiWorkSource: scan markdown files for @mention checkboxes ----------\n\nexport class WikiWorkSource implements WorkSource {\n type = \"wiki\"\n capabilities: WorkSourceCapabilities = {\n listAll: false, create: false, transition: false, labels: false, search: false,\n }\n constructor(private root: string, private glob: string) {}\n\n private walk(dir: string, out: string[]): void {\n if (!existsSync(dir)) return\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n const st = statSync(full)\n if (st.isDirectory()) {\n this.walk(full, out)\n } else if (entry.endsWith(\".md\")) {\n out.push(full)\n }\n }\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const files: string[] = []\n this.walk(resolve(process.cwd(), this.root), files)\n const items: WorkItem[] = []\n for (const file of files) {\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m || m[2].toLowerCase() === \"x\") continue\n const mention = m[3].match(MENTION_RE)\n if (!mention || mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n items.push({\n id: `wiki:${file}:${i}`,\n title: m[3].replace(MENTION_RE, \"\").trim(),\n assignee: mention[1],\n priority: i,\n })\n }\n }\n return items\n }\n\n async claim(): Promise<void> { /* no-op */ }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n // wiki:<file>:<line>\n const parts = itemId.split(\":\")\n const lineNo = parseInt(parts[parts.length - 1], 10)\n const file = parts.slice(1, -1).join(\":\")\n if (!existsSync(file)) return\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n if (update.status === \"done\" && lines[lineNo]) {\n lines[lineNo] = lines[lineNo].replace(/- \\[ \\]/, \"- [x]\")\n writeFileSync(file, lines.join(\"\\n\"))\n }\n }\n}\n\n// ---------- GitLabWorkSource: assignee-filtered issues via GitLab API ----------\n\nexport class GitLabWorkSource implements WorkSource {\n type = \"gitlab\"\n capabilities: WorkSourceCapabilities = {\n listAll: true, create: true, transition: true, labels: true, search: true,\n }\n\n constructor(\n private host: string,\n private token: string,\n private projects: string[],\n /** Map from agentId -> GitLab username(s) */\n private agentUsernames: Record<string, string[]>,\n private log: (...args: unknown[]) => void,\n ) {}\n\n /** Reverse lookup: GitLab username -> agentId. Used when resolving board items\n * back to an agent (for the reconciler and card display). */\n private usernameToAgent(username: string): string | undefined {\n for (const [agentId, names] of Object.entries(this.agentUsernames)) {\n if (names.some((n) => n.toLowerCase() === username.toLowerCase())) return agentId\n }\n return undefined\n }\n\n private mapIssue(issue: any, project: string): WorkItem {\n const ns = issue.references?.full?.split(\"#\")[0] || project\n // `labels` may be a string[] or, with with_labels_details=true, an object[].\n let labels: string[] = []\n let labelDetails: Array<{ name: string; color: string; text_color?: string }> | undefined\n if (Array.isArray(issue.labels)) {\n if (issue.labels.length && typeof issue.labels[0] === \"object\") {\n labelDetails = issue.labels.map((l: any) => ({\n name: l.name, color: l.color, text_color: l.text_color,\n }))\n labels = labelDetails!.map((l) => l.name)\n } else {\n labels = issue.labels\n }\n }\n const rawAssignees: any[] = Array.isArray(issue.assignees) && issue.assignees.length\n ? issue.assignees\n : (issue.assignee ? [issue.assignee] : [])\n const assignees = rawAssignees.map((a) => ({\n username: a.username, name: a.name, avatarUrl: a.avatar_url,\n }))\n const firstUsername = assignees[0]?.username\n return {\n id: `gitlab:${ns}:issue:${issue.iid}`,\n title: issue.title,\n description: issue.description || undefined,\n // Prefer configured agentId mapping; fall back to the raw GitLab username\n // so cards still render an assignee even without agentMappings configured.\n assignee: firstUsername ? (this.usernameToAgent(firstUsername) || firstUsername) : undefined,\n assignees,\n url: issue.web_url,\n priority: issue.weight ?? 99,\n estimatedSeconds: issue.time_stats?.time_estimate || undefined,\n labels,\n labelDetails,\n updatedAt: issue.updated_at,\n state: issue.state === \"closed\" ? \"closed\" : \"opened\",\n milestone: issue.milestone?.title || undefined,\n }\n }\n\n private async api(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetch(`${this.host}/api/v4${path}`, {\n ...init,\n headers: {\n \"PRIVATE-TOKEN\": this.token,\n \"Content-Type\": \"application/json\",\n ...(init.headers || {}),\n },\n })\n if (!res.ok) throw new Error(`GitLab ${res.status}: ${await res.text().catch(() => \"\")}`)\n return res.json()\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const usernames = this.agentUsernames[agentId] || []\n if (!usernames.length) return []\n\n const items: WorkItem[] = []\n const projectList = this.projects.length\n ? this.projects\n : [\"__ALL__\"] // falls back to a global issues query\n\n for (const username of usernames) {\n for (const project of projectList) {\n try {\n const q = `assignee_username=${encodeURIComponent(username)}&state=opened&scope=all&per_page=20`\n const path = project === \"__ALL__\"\n ? `/issues?${q}`\n : `/projects/${encodeURIComponent(project)}/issues?${q}`\n const issues = await this.api(path)\n for (const issue of issues) {\n const item = this.mapIssue(issue, project)\n // Force assignee to the agent we queried for (listOpen is per-agent).\n item.assignee = agentId\n items.push(item)\n }\n } catch (e: any) {\n this.log(`[business] GitLab listOpen failed for ${username} on ${project}: ${e.message}`)\n }\n }\n }\n return items\n }\n\n async claim(agentId: string, itemId: string): Promise<void> {\n // itemId: \"gitlab:<project>:issue:<iid>\"\n const { project, iid, type } = this.parseId(itemId)\n // Transition labels: add \"Doing\", remove \"To Do\"\n const path = `/projects/${encodeURIComponent(project)}/${type === \"issue\" ? \"issues\" : \"merge_requests\"}/${iid}?add_labels=Doing&remove_labels=To Do`\n try {\n await this.api(path, { method: \"PUT\" })\n } catch (e: any) {\n this.log(`[business] claim label update failed: ${e.message}`)\n }\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n\n // Time tracking\n if (update.timeSeconds && update.timeSeconds > 0) {\n const mins = Math.max(1, Math.round(update.timeSeconds / 60))\n const dur = mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? ` ${mins % 60}m` : \"\"}` : `${mins}m`\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/add_spent_time?duration=${encodeURIComponent(dur)}`,\n { method: \"POST\" },\n )\n } catch (e: any) {\n this.log(`[business] add_spent_time failed: ${e.message}`)\n }\n }\n\n // Status → labels\n if (update.status === \"done\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Done&remove_labels=Doing`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] done label update failed: ${e.message}`)\n }\n } else if (update.status === \"blocked\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Blocked`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] blocked label update failed: ${e.message}`)\n }\n }\n\n // Note/comment\n if (update.note) {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/notes`,\n { method: \"POST\", body: JSON.stringify({ body: update.note }) },\n )\n } catch (e: any) {\n this.log(`[business] report note failed: ${e.message}`)\n }\n }\n }\n\n async listAll(opts: ListAllOpts = {}): Promise<WorkItem[]> {\n const sinceDays = opts.sinceDays ?? 30\n const updatedAfter = new Date(Date.now() - sinceDays * 24 * 3600_000).toISOString()\n\n const params = new URLSearchParams()\n const state = opts.state ?? \"opened\"\n params.set(\"state\", state)\n // `updated_after` makes sense for the Closed column (bounded window) and for\n // keeping the open list recent; for \"opened\" we pass it too but GitLab treats\n // the window loosely (any issue updated since counts, regardless of open-age).\n params.set(\"updated_after\", updatedAfter)\n params.set(\"scope\", \"all\")\n params.set(\"per_page\", \"100\")\n params.set(\"with_labels_details\", \"true\")\n params.set(\"order_by\", \"updated_at\")\n params.set(\"sort\", \"desc\")\n if (opts.labels?.length) params.set(\"labels\", opts.labels.join(\",\"))\n if (opts.milestone) params.set(\"milestone\", opts.milestone)\n if (opts.search) params.set(\"search\", opts.search)\n\n const projectList = this.projects.length ? this.projects : [\"__ALL__\"]\n const items: WorkItem[] = []\n for (const project of projectList) {\n try {\n const path = project === \"__ALL__\"\n ? `/issues?${params.toString()}`\n : `/projects/${encodeURIComponent(project)}/issues?${params.toString()}`\n const issues = await this.api(path)\n for (const issue of issues) items.push(this.mapIssue(issue, project))\n } catch (e: any) {\n this.log(`[business] GitLab listAll failed on ${project}: ${e.message}`)\n }\n }\n\n // Client-side negative-label filter (GitLab API supports `not[labels]` only\n // via the `not[]` nested query form, which isn't convenient via URLSearchParams).\n let result = items\n if (opts.notLabels?.length) {\n const deny = new Set(opts.notLabels)\n result = result.filter((i) => !(i.labels || []).some((l) => deny.has(l)))\n }\n if (opts.withoutScopedPrefix) {\n const prefix = opts.withoutScopedPrefix + \"::\"\n result = result.filter((i) => !(i.labels || []).some((l) => l.startsWith(prefix)))\n }\n return result\n }\n\n /** Close or reopen an issue via state_event. */\n async setState(itemId: string, to: \"close\" | \"reopen\"): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?state_event=${to}`,\n { method: \"PUT\" },\n )\n }\n\n /** Fetch a single issue's full detail. */\n async getItem(itemId: string): Promise<WorkDetail | null> {\n const { project, iid } = this.parseId(itemId)\n try {\n const issue = await this.api(\n `/projects/${encodeURIComponent(project)}/issues/${iid}?with_labels_details=true`,\n )\n const base = this.mapIssue(issue, project)\n return {\n ...base,\n descriptionHtml: issue.description_html || undefined,\n createdAt: issue.created_at,\n author: issue.author ? {\n username: issue.author.username, name: issue.author.name, avatarUrl: issue.author.avatar_url,\n } : undefined,\n allLabels: Array.isArray(issue.labels)\n ? (typeof issue.labels[0] === \"object\" ? issue.labels.map((l: any) => l.name) : issue.labels)\n : [],\n }\n } catch (e: any) {\n this.log(`[board] GitLab getItem failed for ${itemId}: ${e.message}`)\n return null\n }\n }\n\n /** Update fields on an issue. Accepts string usernames for assignees. */\n async updateItem(\n itemId: string,\n patch: {\n title?: string\n description?: string\n labels?: string[]\n addLabels?: string[]\n removeLabels?: string[]\n assigneeUsernames?: string[]\n milestoneTitle?: string | null\n },\n ): Promise<WorkItem> {\n const { project, iid } = this.parseId(itemId)\n\n // Resolve assignee usernames → user ids (parallel).\n let assigneeIds: number[] | undefined\n if (patch.assigneeUsernames) {\n const results = await Promise.all(patch.assigneeUsernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch { return null }\n }))\n assigneeIds = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id (null clears the milestone).\n let milestoneId: number | 0 | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneId = 0\n } else {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(patch.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestoneId = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${patch.milestoneTitle}\": ${e.message}`)\n }\n }\n }\n\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.description = patch.description\n if (patch.labels !== undefined) body.labels = patch.labels.join(\",\")\n if (patch.addLabels?.length) body.add_labels = patch.addLabels.join(\",\")\n if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels.join(\",\")\n if (assigneeIds !== undefined) body.assignee_ids = assigneeIds\n if (milestoneId !== undefined) body.milestone_id = milestoneId\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues/${iid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n /** List project members (direct + inherited). Deduped by username across projects. */\n async listMembers(): Promise<WorkMember[]> {\n const byUsername = new Map<string, WorkMember>()\n for (const project of this.projects) {\n try {\n const members = await this.api(\n `/projects/${encodeURIComponent(project)}/members/all?per_page=100`,\n )\n for (const m of members) {\n if (m.username && !byUsername.has(m.username)) {\n byUsername.set(m.username, {\n username: m.username, name: m.name, avatarUrl: m.avatar_url,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMembers failed for ${project}: ${e.message}`)\n }\n }\n return [...byUsername.values()].sort((a, b) => a.username.localeCompare(b.username))\n }\n\n /** List milestones (active first, then closed). Deduped by title across projects. */\n async listMilestones(): Promise<WorkMilestone[]> {\n const byTitle = new Map<string, WorkMilestone>()\n for (const project of this.projects) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?per_page=100`,\n )\n for (const m of ms) {\n if (m.title && !byTitle.has(m.title)) {\n byTitle.set(m.title, {\n id: m.id, title: m.title, state: m.state, dueDate: m.due_date,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMilestones failed for ${project}: ${e.message}`)\n }\n }\n const all = [...byTitle.values()]\n all.sort((a, b) => {\n if (a.state !== b.state) return a.state === \"active\" ? -1 : 1\n return a.title.localeCompare(b.title)\n })\n return all\n }\n\n async create(input: WorkCreateInput): Promise<WorkItem> {\n const project = input.projectHint || this.projects[0]\n if (!project) throw new Error(\"GitLabWorkSource.create requires projectHint or configured projects\")\n\n // Resolve assignee usernames → user ids. Prefer explicit `assigneeUsernames`,\n // fall back to agentId → configured mapping for legacy callers.\n let assignee_ids: number[] | undefined\n const usernames = input.assigneeUsernames?.length\n ? input.assigneeUsernames\n : (input.assignee ? (this.agentUsernames[input.assignee] || []).slice(0, 1) : [])\n if (usernames.length) {\n const results = await Promise.all(usernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch (e: any) {\n this.log(`[board] user lookup failed for ${u}: ${e.message}`)\n return null\n }\n }))\n assignee_ids = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id.\n let milestone_id: number | undefined\n if (input.milestoneTitle) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(input.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestone_id = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${input.milestoneTitle}\": ${e.message}`)\n }\n }\n\n const body: Record<string, unknown> = {\n title: input.title,\n description: input.description,\n labels: (input.labels || []).join(\",\"),\n }\n if (assignee_ids?.length) body.assignee_ids = assignee_ids\n if (milestone_id !== undefined) body.milestone_id = milestone_id\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues`, {\n method: \"POST\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n async transition(itemId: string, toLabel: string, fromLabel?: string): Promise<void> {\n // Legacy single add/remove — boards use transitionMany.\n await this.transitionMany(itemId, toLabel ? [toLabel] : [], fromLabel ? [fromLabel] : [])\n }\n\n /** Multi-label transition; used by board drag-drop (scoped labels produce pairs). */\n async transitionMany(itemId: string, add: string[], remove: string[]): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n const params = new URLSearchParams()\n if (add.length) params.set(\"add_labels\", add.join(\",\"))\n if (remove.length) params.set(\"remove_labels\", remove.join(\",\"))\n if (!params.toString()) return\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?${params.toString()}`,\n { method: \"PUT\" },\n )\n }\n\n private parseId(itemId: string): { project: string; type: \"issue\" | \"mr\"; iid: string } {\n // \"gitlab:<project>:issue:<iid>\" (project may contain a slash → it's joined)\n const body = itemId.replace(/^gitlab:/, \"\")\n const parts = body.split(\":\")\n const iid = parts.pop() as string\n const type = parts.pop() === \"mr\" ? \"mr\" : \"issue\"\n const project = parts.join(\":\")\n return { project, type, iid }\n }\n}\n\n// ---------- Factory ----------\n\nexport function createWorkSource(\n business: BusinessConfig,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): WorkSource {\n const ws: BusinessWorkSource = business.workSource\n switch (ws.type) {\n case \"backlog\":\n return new BacklogWorkSource(ws.path, daemon, log)\n\n case \"wiki\":\n return new WikiWorkSource(ws.path, ws.glob)\n\n case \"gitlab\": {\n const gl = daemon.channels.gitlab\n if (!gl?.token) {\n throw new Error(\"business.workSource.type=gitlab requires channels.gitlab.token\")\n }\n const agentUsernames: Record<string, string[]> = {}\n for (const mapping of gl.agentMappings) {\n if (mapping.gitlabUsernames.length) {\n agentUsernames[mapping.agentId] = mapping.gitlabUsernames\n }\n }\n const projects = ws.projects.length\n ? ws.projects\n : gl.routes.map((r) => r.project)\n return new GitLabWorkSource(gl.host, gl.token, projects, agentUsernames, log)\n }\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\nimport { randomUUID } from \"crypto\"\nimport { z } from \"zod\"\n\n// --- Structured backlog store (Plan B + import) ---\n//\n// Replaces the legacy `.agentx/backlog.md` GFM checklist as the canonical\n// store when `.agentx/backlog.json` is present. The markdown view is\n// regenerated on every save so humans still get a readable diff.\n//\n// Why structured: importing issues from gitlab/github needs a stable\n// linkage back to the source (project + iid + url) so mutations\n// (assignee, labels, status, milestone) can be pushed upstream by the\n// sync-back layer. Encoding that in markdown comments is brittle.\n\nconst sourceSchema = z.object({\n type: z.enum([\"gitlab\", \"github\"]),\n host: z.string().optional(),\n project: z.string(),\n iid: z.number().int().positive(),\n url: z.string(),\n})\n\nconst itemSchema = z.object({\n id: z.string(),\n title: z.string(),\n description: z.string().optional(),\n assignee: z.string().optional(),\n labels: z.array(z.string()).default([]),\n milestone: z.string().optional(),\n status: z.enum([\"todo\", \"doing\", \"blocked\", \"done\"]).default(\"todo\"),\n priority: z.number().int().optional(),\n estimatedSeconds: z.number().int().optional(),\n source: sourceSchema.optional(),\n importedAt: z.string().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n blocker: z.string().optional(),\n doneNote: z.string().optional(),\n})\n\nconst fileSchema = z.object({\n version: z.literal(1),\n items: z.array(itemSchema).default([]),\n})\n\nexport type BacklogSource = z.infer<typeof sourceSchema>\nexport type BacklogItem = z.infer<typeof itemSchema>\nexport type BacklogFile = z.infer<typeof fileSchema>\n\nexport class BacklogStore {\n constructor(private path: string) {}\n\n /** Absolute path of the JSON store. */\n jsonPath(): string {\n return resolve(process.cwd(), this.path).replace(/\\.md$/i, \".json\")\n }\n\n /** Absolute path of the markdown view (alongside the JSON store). */\n mdPath(): string {\n return resolve(process.cwd(), this.path).replace(/\\.json$/i, \".md\")\n }\n\n exists(): boolean {\n return existsSync(this.jsonPath())\n }\n\n load(): BacklogFile {\n const p = this.jsonPath()\n if (!existsSync(p)) return { version: 1, items: [] }\n const raw = readFileSync(p, \"utf-8\")\n try {\n return fileSchema.parse(JSON.parse(raw))\n } catch (e: any) {\n throw new Error(`Backlog file invalid (${p}): ${e.message}`)\n }\n }\n\n save(file: BacklogFile): void {\n const jsonP = this.jsonPath()\n mkdirSync(dirname(jsonP), { recursive: true })\n writeFileSync(jsonP, JSON.stringify(file, null, 2) + \"\\n\")\n writeFileSync(this.mdPath(), this.toMarkdown(file))\n }\n\n list(): BacklogItem[] {\n return this.load().items\n }\n\n findById(id: string): BacklogItem | undefined {\n return this.list().find((i) => i.id === id)\n }\n\n /** Add an item; rejects duplicates by id. Returns the saved item. */\n add(input: Omit<BacklogItem, \"createdAt\" | \"updatedAt\"> & { createdAt?: string }): BacklogItem {\n const file = this.load()\n if (file.items.some((i) => i.id === input.id)) {\n throw new Error(`backlog item ${input.id} already exists`)\n }\n const now = new Date().toISOString()\n const item: BacklogItem = {\n ...input,\n labels: input.labels ?? [],\n status: input.status ?? \"todo\",\n createdAt: input.createdAt ?? now,\n updatedAt: now,\n }\n file.items.push(item)\n this.save(file)\n return item\n }\n\n /** Add many; skips items whose id already exists, returns the inserted set. */\n addMany(inputs: Array<Omit<BacklogItem, \"createdAt\" | \"updatedAt\"> & { createdAt?: string }>): BacklogItem[] {\n const file = this.load()\n const existing = new Set(file.items.map((i) => i.id))\n const now = new Date().toISOString()\n const added: BacklogItem[] = []\n for (const input of inputs) {\n if (existing.has(input.id)) continue\n const item: BacklogItem = {\n ...input,\n labels: input.labels ?? [],\n status: input.status ?? \"todo\",\n createdAt: input.createdAt ?? now,\n updatedAt: now,\n }\n file.items.push(item)\n added.push(item)\n existing.add(item.id)\n }\n if (added.length) this.save(file)\n return added\n }\n\n update(id: string, patch: Partial<BacklogItem>): BacklogItem {\n const file = this.load()\n const idx = file.items.findIndex((i) => i.id === id)\n if (idx < 0) throw new Error(`backlog item ${id} not found`)\n const updated: BacklogItem = {\n ...file.items[idx],\n ...patch,\n id: file.items[idx].id,\n createdAt: file.items[idx].createdAt,\n updatedAt: new Date().toISOString(),\n }\n file.items[idx] = updated\n this.save(file)\n return updated\n }\n\n remove(id: string): boolean {\n const file = this.load()\n const before = file.items.length\n file.items = file.items.filter((i) => i.id !== id)\n if (file.items.length === before) return false\n this.save(file)\n return true\n }\n\n /** Render a human-readable markdown view (generated, not authoritative). */\n toMarkdown(file: BacklogFile): string {\n const lines: string[] = [\n \"# Backlog\",\n \"\",\n \"_Generated from `backlog.json`. Do not edit by hand — use `agentx backlog` commands._\",\n \"\",\n ]\n const buckets: Record<BacklogItem[\"status\"], BacklogItem[]> = {\n doing: [], blocked: [], todo: [], done: [],\n }\n for (const item of file.items) buckets[item.status].push(item)\n const sections: Array<[BacklogItem[\"status\"], string]> = [\n [\"doing\", \"Doing\"], [\"blocked\", \"Blocked\"], [\"todo\", \"To Do\"], [\"done\", \"Done\"],\n ]\n for (const [key, label] of sections) {\n if (!buckets[key].length) continue\n lines.push(`## ${label}`, \"\")\n for (const it of buckets[key]) {\n const check = key === \"done\" ? \"x\" : \" \"\n const mention = it.assignee ? `@${it.assignee} ` : \"\"\n const time = it.estimatedSeconds\n ? ` [time: ${Math.round(it.estimatedSeconds / 60)}m]`\n : \"\"\n const src = it.source ? ` (${it.source.type}: ${it.source.project}#${it.source.iid})` : \"\"\n lines.push(`- [${check}] ${mention}${it.title}${time}${src}`)\n if (it.source?.url) lines.push(` - <${it.source.url}>`)\n if (it.labels.length) lines.push(` - labels: ${it.labels.join(\", \")}`)\n if (it.milestone) lines.push(` - milestone: ${it.milestone}`)\n if (it.blocker) lines.push(` - BLOCKED: ${it.blocker}`)\n }\n lines.push(\"\")\n }\n return lines.join(\"\\n\")\n }\n}\n\n/** Generate a stable BacklogItem.id for a fresh manual item. */\nexport function manualBacklogId(): string {\n return `manual:${randomUUID()}`\n}\n\n/** Build the canonical id for an imported source issue. */\nexport function sourceBacklogId(source: BacklogSource): string {\n return `${source.type}:${source.project}:${source.iid}`\n}\n","import type { BacklogItem, BacklogSource } from \"./backlog-store\"\nimport type { DaemonConfig } from \"@/daemon/config\"\n\n// --- Sync-back layer: backlog item mutations → upstream issue ---\n//\n// When a backlog item carries a `source` ref (gitlab or github), changes\n// applied via the backlog work-source need to flow back to the original\n// issue: assignees, labels, title, description, milestone, open/close.\n// Otherwise the imported view drifts from upstream and humans browsing\n// the source see stale state.\n//\n// This module is intentionally stateless — callers pass the patch they\n// just applied to the local item; we forward the same shape to the\n// matching upstream API.\n\nexport interface BacklogPatch {\n title?: string\n description?: string\n /** Replace labels wholesale. Use addLabels/removeLabels for incremental. */\n labels?: string[]\n addLabels?: string[]\n removeLabels?: string[]\n /** Username on the source (NOT agentId — caller resolves the mapping). */\n assigneeUsernames?: string[]\n /** Title of the milestone, or empty string to clear. */\n milestoneTitle?: string | null\n /** Open/close transition. */\n state?: \"open\" | \"closed\"\n /** Optional comment to post on the upstream issue. */\n note?: string\n}\n\nexport interface SyncBackResult {\n ok: boolean\n url?: string\n error?: string\n /** Each upstream call we attempted, in order. */\n attempts: Array<{ kind: string; ok: boolean; status?: number; error?: string }>\n}\n\n/** Resolve agentIds in a patch to upstream usernames using the daemon mappings. */\nexport function resolveAssigneesForSource(\n agentIds: string[],\n source: BacklogSource,\n daemon: DaemonConfig,\n): string[] {\n const out: string[] = []\n if (source.type === \"gitlab\") {\n const mappings = daemon.channels.gitlab?.agentMappings ?? []\n for (const agentId of agentIds) {\n const m = mappings.find((x) => x.agentId === agentId)\n if (m?.gitlabUsernames?.length) out.push(m.gitlabUsernames[0])\n else out.push(agentId)\n }\n } else if (source.type === \"github\") {\n const mappings = daemon.channels.github?.agentMappings ?? []\n for (const agentId of agentIds) {\n const m = mappings.find((x) => x.agentId === agentId)\n if (m?.githubUsernames?.length) out.push(m.githubUsernames[0])\n else out.push(agentId)\n }\n }\n return out\n}\n\n/** Push a backlog patch to the upstream source referenced by the item. */\nexport async function syncBacklogItemToSource(\n item: BacklogItem,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void = () => {},\n): Promise<SyncBackResult> {\n if (!item.source) return { ok: true, attempts: [] }\n if (item.source.type === \"gitlab\") return syncToGitLab(item.source, patch, daemon, log)\n if (item.source.type === \"github\") return syncToGitHub(item.source, patch, daemon, log)\n return { ok: false, error: `unknown source.type ${(item.source as any).type}`, attempts: [] }\n}\n\n// ---------- GitLab ----------\n\nasync function syncToGitLab(\n source: BacklogSource,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): Promise<SyncBackResult> {\n const gl = daemon.channels.gitlab\n const token = gl?.token\n const host = gl?.host || (source.host ? `https://${source.host}` : undefined)\n if (!token || !host) {\n return { ok: false, error: \"gitlab token/host not configured\", attempts: [] }\n }\n const attempts: SyncBackResult[\"attempts\"] = []\n\n const api = async (path: string, init: RequestInit = {}): Promise<Response> => {\n return fetch(`${host}/api/v4${path}`, {\n ...init,\n headers: {\n \"PRIVATE-TOKEN\": token,\n \"Content-Type\": \"application/json\",\n ...(init.headers || {}),\n },\n })\n }\n\n const projectEnc = encodeURIComponent(source.project)\n\n // Resolve assignee usernames → ids (parallel) — only when we need them.\n let assigneeIds: number[] | undefined\n if (patch.assigneeUsernames !== undefined) {\n const ids = await Promise.all(patch.assigneeUsernames.map(async (u) => {\n try {\n const res = await fetch(`${host}/api/v4/users?username=${encodeURIComponent(u)}`, {\n headers: { \"PRIVATE-TOKEN\": token },\n })\n if (!res.ok) return null\n const arr: any = await res.json()\n return Array.isArray(arr) && arr[0]?.id ? arr[0].id : null\n } catch { return null }\n }))\n assigneeIds = ids.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id.\n let milestoneId: number | 0 | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneId = 0\n } else {\n try {\n const res = await api(`/projects/${projectEnc}/milestones?title=${encodeURIComponent(patch.milestoneTitle)}`)\n if (res.ok) {\n const arr: any = await res.json()\n if (Array.isArray(arr) && arr[0]?.id) milestoneId = arr[0].id\n }\n } catch (e: any) {\n log(`[backlog-sync] milestone lookup failed: ${e.message}`)\n }\n }\n }\n\n // Build the PUT body.\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.description = patch.description\n if (patch.labels !== undefined) body.labels = patch.labels.join(\",\")\n if (patch.addLabels?.length) body.add_labels = patch.addLabels.join(\",\")\n if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels.join(\",\")\n if (assigneeIds !== undefined) body.assignee_ids = assigneeIds\n if (milestoneId !== undefined) body.milestone_id = milestoneId\n if (patch.state) body.state_event = patch.state === \"closed\" ? \"close\" : \"reopen\"\n\n if (Object.keys(body).length) {\n try {\n const res = await api(`/projects/${projectEnc}/issues/${source.iid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n })\n attempts.push({ kind: \"PUT issue\", ok: res.ok, status: res.status })\n if (!res.ok) {\n const text = await res.text().catch(() => \"\")\n return { ok: false, attempts, error: `PUT issue ${res.status}: ${text.slice(0, 200)}`, url: source.url }\n }\n } catch (e: any) {\n attempts.push({ kind: \"PUT issue\", ok: false, error: e.message })\n return { ok: false, attempts, error: e.message, url: source.url }\n }\n }\n\n if (patch.note) {\n try {\n const res = await api(`/projects/${projectEnc}/issues/${source.iid}/notes`, {\n method: \"POST\",\n body: JSON.stringify({ body: patch.note }),\n })\n attempts.push({ kind: \"POST note\", ok: res.ok, status: res.status })\n } catch (e: any) {\n attempts.push({ kind: \"POST note\", ok: false, error: e.message })\n }\n }\n\n return { ok: attempts.every((a) => a.ok), attempts, url: source.url }\n}\n\n// ---------- GitHub ----------\n\nasync function syncToGitHub(\n source: BacklogSource,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): Promise<SyncBackResult> {\n const gh = daemon.channels.github\n const token = gh?.token\n if (!token) {\n return { ok: false, error: \"github token not configured\", attempts: [] }\n }\n const attempts: SyncBackResult[\"attempts\"] = []\n\n const api = async (path: string, init: RequestInit = {}): Promise<Response> => {\n return fetch(`https://api.github.com${path}`, {\n ...init,\n headers: {\n \"Authorization\": `Bearer ${token}`,\n \"Accept\": \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n ...(init.headers || {}),\n },\n })\n }\n\n const repo = source.project // GitHub IDs use \"owner/repo\" verbatim in the URL path.\n\n // Resolve milestone title → number.\n let milestoneNumber: number | null | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneNumber = null\n } else {\n try {\n const res = await api(`/repos/${repo}/milestones?state=all&per_page=100`)\n if (res.ok) {\n const arr: any = await res.json()\n const found = Array.isArray(arr) ? arr.find((m: any) => m.title === patch.milestoneTitle) : null\n if (found?.number) milestoneNumber = found.number\n }\n } catch (e: any) {\n log(`[backlog-sync] github milestone lookup failed: ${e.message}`)\n }\n }\n }\n\n // GitHub doesn't support add/remove labels in the issue PATCH the way\n // GitLab does — labels is a wholesale set. If only addLabels/removeLabels\n // were provided we read+merge.\n let nextLabels: string[] | undefined\n if (patch.labels !== undefined) {\n nextLabels = patch.labels\n } else if (patch.addLabels?.length || patch.removeLabels?.length) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}`)\n if (res.ok) {\n const issue: any = await res.json()\n const current: string[] = Array.isArray(issue.labels)\n ? issue.labels.map((l: any) => (typeof l === \"string\" ? l : l.name))\n : []\n const adds = new Set([...current, ...(patch.addLabels ?? [])])\n for (const r of patch.removeLabels ?? []) adds.delete(r)\n nextLabels = [...adds]\n }\n } catch (e: any) {\n log(`[backlog-sync] github label fetch failed: ${e.message}`)\n }\n }\n\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.body = patch.description\n if (nextLabels !== undefined) body.labels = nextLabels\n if (patch.assigneeUsernames !== undefined) body.assignees = patch.assigneeUsernames\n if (milestoneNumber !== undefined) body.milestone = milestoneNumber\n if (patch.state) body.state = patch.state\n\n if (Object.keys(body).length) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}`, {\n method: \"PATCH\",\n body: JSON.stringify(body),\n })\n attempts.push({ kind: \"PATCH issue\", ok: res.ok, status: res.status })\n if (!res.ok) {\n const text = await res.text().catch(() => \"\")\n return { ok: false, attempts, error: `PATCH issue ${res.status}: ${text.slice(0, 200)}`, url: source.url }\n }\n } catch (e: any) {\n attempts.push({ kind: \"PATCH issue\", ok: false, error: e.message })\n return { ok: false, attempts, error: e.message, url: source.url }\n }\n }\n\n if (patch.note) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}/comments`, {\n method: \"POST\",\n body: JSON.stringify({ body: patch.note }),\n })\n attempts.push({ kind: \"POST comment\", ok: res.ok, status: res.status })\n } catch (e: any) {\n attempts.push({ kind: \"POST comment\", ok: false, error: e.message })\n }\n }\n\n return { ok: attempts.every((a) => a.ok), attempts, url: source.url }\n}\n","// --- Who is the work for? -------------------------------------------------\n//\n// The principal behind a piece of work is a CLIENT. That concept already\n// existed, spread across `business.projects[].client`, `business.contactMap[]`\n// and the `orgChart` reportsTo chain, resolved privately inside the activity\n// graph panel. This module is the single implementation both that panel and\n// the session monitor call, plus the policy layer the monitor needs.\n//\n// Clients are DERIVED, never declared: one appears the moment a project or a\n// contact mentions it. `business.clients` only carries overrides — a display\n// name, whether it is a paying client or your own house, how fast a delay\n// costs you, and what agents may do for it without asking. A client with no\n// entry there still resolves; it just gets defaults.\n\nexport interface ContactRule {\n channel?: string\n chatId?: string\n username?: string\n senderId?: string\n client: string\n project?: string\n displayName?: string\n}\nexport interface ProjectRule { id: string; pm?: string; client?: string }\nexport interface ClientPolicy {\n name?: string\n kind?: \"client\" | \"internal\" | \"own\"\n respondWithin?: string\n standing?: string[]\n}\nexport interface BusinessShape {\n projects?: ProjectRule[]\n contactMap?: ContactRule[]\n orgChart?: Record<string, { reportsTo?: string }>\n clients?: Record<string, ClientPolicy>\n}\nexport interface ResolvedClient {\n id: string\n name: string\n kind: \"client\" | \"internal\" | \"own\"\n /** Minutes before a wait starts costing; undefined = no external clock. */\n respondWithinMinutes?: number\n standing: string[]\n /** True when nothing in config mentions this client — derived only. */\n declared: boolean\n}\n\nexport const UNMAPPED = \"unmapped\"\n\n/** Explicit `business.projects[].client` wins; otherwise the leading segment\n * of the id. Works for \"owner/repo\" on any forge and for bare internal ids. */\nexport function clientFromProject(project: string | null | undefined, projects: ProjectRule[] = []): string {\n if (!project) return UNMAPPED\n const hit = projects.find(p => p.id === project)\n if (hit?.client) return hit.client\n const slash = project.indexOf(\"/\")\n return slash > 0 ? project.slice(0, slash) : project\n}\n\n/** agent id -> client, from project PMs then transitively down reportsTo.\n * A mesh dispatch carries no project, so without this it is unattributable. */\nexport function agentClients(business: BusinessShape): Map<string, string> {\n const out = new Map<string, string>()\n const projects = business.projects ?? []\n const orgChart = business.orgChart ?? {}\n for (const p of projects) {\n if (p.pm) if (!out.has(p.pm)) out.set(p.pm, clientFromProject(p.id, projects))\n }\n const climb = (agentId: string, seen = new Set<string>()): string | undefined => {\n if (out.has(agentId)) return out.get(agentId)\n if (seen.has(agentId)) return undefined\n seen.add(agentId)\n const up = orgChart[agentId]?.reportsTo\n if (!up) return undefined\n const c = climb(up, seen)\n if (c) out.set(agentId, c)\n return c\n }\n for (const agentId of Object.keys(orgChart)) climb(agentId)\n return out\n}\n\n/** chatId > username > senderId > channel-default. First match wins. */\nexport function matchContact(\n channel: string | undefined,\n who: { chatId?: string; username?: string; senderId?: string },\n contactMap: ContactRule[] = [],\n): ContactRule | undefined {\n if (!contactMap.length) return undefined\n const pool = contactMap.filter(m => !m.channel || m.channel === channel)\n return pool.find(m => m.chatId && who.chatId && m.chatId === who.chatId)\n ?? pool.find(m => m.username && who.username && m.username === who.username)\n ?? pool.find(m => m.senderId && who.senderId && m.senderId === who.senderId)\n ?? pool.find(m => m.channel && !m.chatId && !m.username && !m.senderId)\n}\n\nexport interface WorkRef { agentId?: string; channel?: string; project?: string; chatId?: string; username?: string }\n\n/** The one entry point. Conversation rules beat project shape, which beats\n * the agent's place in the org, because a rule someone wrote by hand is\n * always more deliberate than a heuristic. */\nexport function resolveClient(ref: WorkRef, business: BusinessShape): string {\n const contact = matchContact(ref.channel, { chatId: ref.chatId, username: ref.username }, business.contactMap)\n if (contact) return contact.client\n if (ref.project) return clientFromProject(ref.project, business.projects)\n if (ref.agentId) {\n const byAgent = agentClients(business).get(ref.agentId)\n if (byAgent) return byAgent\n }\n return UNMAPPED\n}\n\n/** A monitor session id is \"<agent>:<channel>:<target...>\". Forge targets\n * carry the project, chat targets carry the chat id. */\nexport function parseWorkRef(sessionId: string, agentId?: string): WorkRef {\n const parts = String(sessionId || \"\").split(\":\")\n if (parts.length < 3) return { agentId }\n const [agent, channel, ...rest] = parts\n const target = rest.join(\":\")\n const ref: WorkRef = { agentId: agentId || agent, channel }\n if (channel === \"gitlab\" || channel === \"github\") {\n // \"owner/repo:issue:94\" and \"owner/repo:pipeline:10897\" -> \"owner/repo\"\n ref.project = target.split(\":\")[0]\n } else if (channel === \"cron\" || channel === \"api\") {\n // Nothing addressable: fall through to the agent's own client.\n } else {\n ref.chatId = target.split(\"@\")[0]\n }\n return ref\n}\n\nconst KIND_DEFAULT = (id: string): \"client\" | \"internal\" | \"own\" => (id === UNMAPPED ? \"internal\" : \"client\")\n\nexport function clientPolicy(id: string, business: BusinessShape): ResolvedClient {\n const p = business.clients?.[id]\n return {\n id,\n name: p?.name || id,\n kind: p?.kind ?? KIND_DEFAULT(id),\n respondWithinMinutes: parseDuration(p?.respondWithin),\n standing: p?.standing ?? [],\n declared: Boolean(p),\n }\n}\n\n/** Every client the config can produce: from projects, contacts and overrides. */\nexport function listClients(business: BusinessShape): ResolvedClient[] {\n const ids = new Set<string>()\n for (const p of business.projects ?? []) ids.add(clientFromProject(p.id, business.projects))\n for (const c of business.contactMap ?? []) ids.add(c.client)\n for (const id of Object.keys(business.clients ?? {})) ids.add(id)\n return [...ids].sort().map(id => clientPolicy(id, business))\n}\n\n/** \"4h\", \"90m\", \"2d\" -> minutes. Anything else is treated as no clock, so a\n * typo can never silently become an aggressive deadline. */\nexport function parseDuration(v: string | undefined): number | undefined {\n const m = /^(\\d+)\\s*([mhd])$/.exec(String(v ?? \"\").trim())\n if (!m) return undefined\n const n = Number(m[1])\n return m[2] === \"m\" ? n : m[2] === \"h\" ? n * 60 : n * 1440\n}\n\n/** Whether an agent may act unattended for this client. Deny by default, and\n * never for a paying client regardless of what is listed — an outward-facing\n * mistake on someone else's repo is not ours to make on a timer. */\nexport function mayProceedUnattended(client: ResolvedClient, capability: string): boolean {\n if (client.kind === \"client\") return false\n return client.standing.includes(\"*\") || client.standing.includes(capability)\n}\n","// --- In-band rich-message directive (`agentx:ui`) ---\n//\n// Agents emit rich messages (buttons, polls, media) by appending a fenced\n// block to their reply:\n//\n// ```agentx:ui\n// { \"buttons\": [{ \"label\": \"Docs\", \"url\": \"https://…\" }],\n// \"poll\": { \"question\": \"Deploy now?\", \"options\": [\"Yes\", \"No\"] } }\n// ```\n//\n// A render layer lifts the JSON into an OutgoingMessage's buttons/poll/media\n// and strips the block from the visible text. Fail-safe by design: a missing\n// or malformed block leaves the text untouched and yields no directive, so a\n// half-written or invalid fence just renders as ordinary text.\n//\n// Chat channels render URL buttons. The phone app also renders quick-reply\n// chips (`\"quickReplies\": [\"Yes\", \"No\"]`) and reply buttons\n// (`{ \"label\": …, \"reply\": … }`): a tap sends that text as the user's next\n// message, exactly as if it had been typed. Other channels ignore them. A\n// `{ \"action\": … }` button (a callback that runs without a user message) is\n// parsed but skipped until the inbound callback_query plumbing lands (Phase 2).\n\nexport interface UiButton {\n label: string\n url: string\n}\n\n/** A button whose tap sends `reply` as the user's next message (phone app). */\nexport interface UiReply {\n label: string\n reply: string\n}\n\nexport interface UiPoll {\n question: string\n options: string[]\n multiple?: boolean\n}\n\nexport interface UiMedia {\n type: \"image\" | \"document\" | \"audio\" | \"video\"\n url: string\n caption?: string\n}\n\nexport interface UiDirective {\n buttons?: UiButton[]\n poll?: UiPoll\n media?: UiMedia\n /** Chips whose text is sent as the user's next message when tapped. */\n quickReplies?: string[]\n replies?: UiReply[]\n /** Labels of `action` (callback) buttons dropped because Phase 2 isn't wired\n * yet — surfaced so the caller can log them. */\n skippedActions?: string[]\n}\n\n/** The opening fence, matched case-insensitively at a line start. */\nconst FENCE_OPEN = /(^|\\n)[ \\t]*```[ \\t]*agentx:ui[ \\t]*\\r?\\n/i\n\n/** A complete fenced block: ```agentx:ui\\n<json>\\n``` (capturing the JSON). */\nconst FENCE_FULL = /(^|\\n)[ \\t]*```[ \\t]*agentx:ui[ \\t]*\\r?\\n([\\s\\S]*?)\\r?\\n[ \\t]*```[ \\t]*(?=\\n|$)/gi\n\nexport interface ExtractResult {\n cleanText: string\n ui?: UiDirective\n}\n\n/**\n * Extract the LAST complete `agentx:ui` block from `text`, returning the text\n * with that block removed plus the parsed directive. Never throws: on no\n * block, invalid JSON, or an empty directive, returns the original text and no\n * `ui`.\n */\nexport function extractUiDirective(text: string): ExtractResult {\n if (!text || !text.includes(\"agentx:ui\")) return { cleanText: text }\n\n // Find all complete blocks; the last one wins (agents sometimes narrate an\n // example fence earlier in the reply).\n const matches = [...text.matchAll(FENCE_FULL)]\n if (matches.length === 0) return { cleanText: text }\n const match = matches[matches.length - 1]\n const json = match[2]\n\n const ui = parseDirective(json)\n if (!ui) return { cleanText: text } // malformed → leave text intact\n\n // Remove exactly the matched block, preserving the leading newline group.\n const start = match.index ?? 0\n const leading = match[1] ?? \"\"\n const cleanText = (text.slice(0, start) + leading + text.slice(start + match[0].length)).trim()\n return { cleanText, ui }\n}\n\n/**\n * During streaming, hide a partial or complete directive so a half-written\n * fence never flashes as raw text. Cuts from the first `agentx:ui` opening\n * fence to the end of the string. Cheap and lossy-forward-only — the final\n * `extractUiDirective` does the authoritative parse.\n */\nexport function stripUiDirectiveForPreview(text: string): string {\n if (!text || !text.includes(\"agentx:ui\")) return text\n const m = FENCE_OPEN.exec(text)\n if (!m) return text\n return text.slice(0, m.index).trimEnd()\n}\n\n/** Parse + validate the directive JSON. Returns null on any problem or when\n * nothing renderable survives. */\nfunction parseDirective(json: string): UiDirective | null {\n let raw: any\n try {\n raw = JSON.parse(json)\n } catch {\n return null\n }\n if (!raw || typeof raw !== \"object\") return null\n\n const ui: UiDirective = {}\n\n if (Array.isArray(raw.buttons)) {\n const buttons: UiButton[] = []\n const replies: UiReply[] = []\n const skippedActions: string[] = []\n for (const b of raw.buttons) {\n if (!b || typeof b.label !== \"string\") continue\n if (typeof b.url === \"string\" && /^https?:\\/\\//i.test(b.url)) {\n buttons.push({ label: b.label, url: b.url })\n } else if (typeof b.reply === \"string\" && b.reply.trim()) {\n replies.push({ label: b.label, reply: b.reply.trim() })\n } else if (b.action != null) {\n skippedActions.push(b.label) // Phase 2\n }\n }\n if (buttons.length) ui.buttons = buttons\n if (replies.length) ui.replies = replies\n if (skippedActions.length) ui.skippedActions = skippedActions\n }\n\n if (raw.poll && typeof raw.poll === \"object\") {\n const q = raw.poll.question\n const opts = raw.poll.options\n if (typeof q === \"string\" && q.trim() && Array.isArray(opts) && opts.filter((o: unknown) => typeof o === \"string\").length >= 2) {\n ui.poll = {\n question: q,\n options: opts.filter((o: unknown) => typeof o === \"string\"),\n multiple: raw.poll.multiple === true,\n }\n }\n }\n\n if (raw.media && typeof raw.media === \"object\") {\n const { type, url, caption } = raw.media\n if ([\"image\", \"document\", \"audio\", \"video\"].includes(type) && typeof url === \"string\" && url) {\n ui.media = { type, url, ...(typeof caption === \"string\" ? { caption } : {}) }\n }\n }\n\n if (Array.isArray(raw.quickReplies)) {\n const chips = raw.quickReplies.filter((q: unknown) => typeof q === \"string\" && q.trim()).map((q: string) => q.trim())\n if (chips.length) ui.quickReplies = chips\n }\n\n // Nothing renderable (and no skipped actions worth reporting) → treat as\n // \"no directive\" so the block is left in the text rather than silently\n // vanishing.\n if (!ui.buttons && !ui.poll && !ui.media && !ui.quickReplies && !ui.replies && !ui.skippedActions) return null\n return ui\n}\n","import type { ServerResponse } from \"http\"\n\n// --- WebRTC signal broker ---\n//\n// AgentX itself never carries audio/video frames. This broker only relays the\n// control-plane messages (SDP offer/answer + ICE candidates + hangup) between a\n// local browser and a remote peer's browser — the media connection is negotiated\n// directly between the two browsers via WebRTC (SRTP over UDP).\n//\n// Flow:\n// Alice's browser -- SSE + POST --> agentx-A -- HTTP /webrtc/signal --> agentx-B -- SSE --> Bob's browser\n//\n// Subscribers index by (callId, recipient). A call is two-sided so there are\n// typically two subscribers per callId: one on each daemon.\n\nexport type WebRtcSignalKind = \"offer\" | \"answer\" | \"ice\" | \"hangup\" | \"ring\"\n\nexport interface WebRtcSignal {\n kind: WebRtcSignalKind\n callId: string\n /** Agent/peer name the signal is FROM (which daemon sent it). */\n from: string\n /** Agent/peer name the signal is FOR (which local subscriber should receive). */\n to: string\n /** SDP blob for offer/answer. */\n sdp?: string\n /** ICE candidate payload. */\n candidate?: {\n candidate: string\n sdpMid: string | null\n sdpMLineIndex: number | null\n usernameFragment?: string | null\n }\n /** Optional short reason for hangup/ring. */\n reason?: string\n}\n\n/** A subscriber may be either an SSE-backed browser (we write the raw event\n * stream into its ServerResponse) OR an in-process consumer like the\n * WebRtcBot (we just call its callback). One Set, two delivery shapes. */\ntype SubscriberSink =\n | { kind: \"sse\"; res: ServerResponse }\n | { kind: \"callback\"; deliver: (signal: WebRtcSignal) => void }\n\ninterface Subscriber {\n callId: string\n recipient: string\n sink: SubscriberSink\n}\n\ninterface BufferedSignal {\n signal: WebRtcSignal\n expiresAt: number\n}\n\n/** How long (ms) to hold a signal that arrived with no matching subscriber.\n * 30s comfortably covers the \"caller offered before callee joined\" race\n * without keeping stale ICE around long enough to confuse a later call. */\nconst BUFFER_TTL_MS = 30_000\n\nexport interface WebRtcSignalForwarder {\n /** POST the signal to the named mesh peer's /webrtc/signal endpoint.\n * Returns true on 2xx. */\n (peerName: string, signal: WebRtcSignal): Promise<boolean>\n}\n\nexport interface RingHandler {\n /** Invoked when a ring signal arrives from a remote peer. The broker\n * dedups by callId so this fires at most once per call over a short TTL. */\n (signal: WebRtcSignal): void | Promise<void>\n}\n\n/** The \"someone is calling\" message a ring sends to ringNotify targets. A\n * phone sharing its camera (#325) gets a watch-only link: that page opens\n * no camera or microphone and sends the offer itself. */\nexport function ringNotice(signal: WebRtcSignal, urlBase: string): string {\n const link = `${urlBase}/call?to=${encodeURIComponent(signal.from)}&callId=${encodeURIComponent(signal.callId)}`\n return signal.reason === \"camera\"\n ? `📷 ${signal.from} is sharing a phone camera — tap to watch: ${link}&watch=1`\n : `📞 ${signal.from} is calling — tap to join: ${link}`\n}\n\n/** Dedup window for ring notifications. A single call shouldn't ping the\n * callee's channels twice, but two distinct calls on the same day must\n * each notify. Five minutes comfortably covers retry-after-missed-call\n * without muting legitimate repeat dials. */\nconst RING_DEDUP_TTL_MS = 5 * 60_000\n\n/** Normalize a peer/node name for tolerant matching: lowercase and strip any\n * non-alphanumeric characters. This papers over the fact that a node's\n * self-reported `node.name` and the name other peers use for it in their\n * `mesh.peers[].name` list frequently disagree on case, spaces, or hyphens\n * (\"HQ-Local\" vs \"hq-local\" vs \"hq_local\"). Signaling is\n * looser than agent dispatch because a single call is a tight two-party\n * negotiation where ambiguity is vanishingly unlikely. */\nexport function normalizeName(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]/g, \"\")\n}\n\nexport class WebRtcSignalBroker {\n private subs: Set<Subscriber> = new Set()\n private log: (...args: unknown[]) => void\n private forwarder?: WebRtcSignalForwarder\n /** Local peer/agent name — used when a browser client identifies as \"us\". */\n private localName: string\n /** Peer names allowed to initiate *inbound* calls. Empty means allow all.\n * Stored pre-normalized so every comparison is apples-to-apples. */\n private allowedCallers: Set<string>\n /** Signals that arrived before their matching subscriber showed up.\n * Keyed by `${callId}|${normalizedRecipient}`; flushed on subscribe. */\n private pending: Map<string, BufferedSignal[]> = new Map()\n private cleanupTimer?: ReturnType<typeof setInterval>\n /** Ring notification callback (set by daemon wiring). */\n private ringHandler?: RingHandler\n /** Recent ring callIds, for dedup. Value is the expiry timestamp. */\n private recentRings: Map<string, number> = new Map()\n\n constructor(\n localName: string,\n allowedCallers: string[] = [],\n log: (...args: unknown[]) => void = console.error.bind(console, \"[webrtc]\"),\n ) {\n this.localName = localName\n this.allowedCallers = new Set(allowedCallers.map(normalizeName))\n this.log = log\n // Sweep expired buffered signals every 10s.\n this.cleanupTimer = setInterval(() => this.cleanupExpired(), 10_000)\n }\n\n private pendingKey(callId: string, recipient: string): string {\n return `${callId}|${normalizeName(recipient)}`\n }\n\n private cleanupExpired(): void {\n const now = Date.now()\n for (const [key, bucket] of this.pending) {\n const kept = bucket.filter(b => b.expiresAt > now)\n if (kept.length === 0) this.pending.delete(key)\n else if (kept.length !== bucket.length) this.pending.set(key, kept)\n }\n for (const [callId, expiry] of this.recentRings) {\n if (expiry <= now) this.recentRings.delete(callId)\n }\n }\n\n setForwarder(fn: WebRtcSignalForwarder): void {\n this.forwarder = fn\n }\n\n setRingHandler(fn: RingHandler): void {\n this.ringHandler = fn\n }\n\n /** Register an SSE subscriber (a browser tab). Returns an unsubscribe fn. */\n subscribe(callId: string, recipient: string, res: ServerResponse): () => void {\n return this.addSubscriber(callId, recipient, { kind: \"sse\", res })\n }\n\n /** Register an in-process subscriber (a server-side bot). The callback fires\n * for every signal addressed to `recipient` on `callId`. Returns unsubscribe. */\n subscribeInternal(callId: string, recipient: string, deliver: (signal: WebRtcSignal) => void): () => void {\n return this.addSubscriber(callId, recipient, { kind: \"callback\", deliver })\n }\n\n private addSubscriber(callId: string, recipient: string, sink: SubscriberSink): () => void {\n const sub: Subscriber = { callId, recipient, sink }\n this.subs.add(sub)\n const tag = sink.kind === \"sse\" ? \"sse\" : \"internal\"\n this.log(`subscribe[${tag}] call=${callId} as=${recipient} (${this.subs.size} total)`)\n\n // Flush any signals that arrived before this subscriber. Common race: the\n // caller offers before the callee opens its page, or SSE connects slightly\n // after the POST that sent the offer.\n const key = this.pendingKey(callId, recipient)\n const buffered = this.pending.get(key)\n if (buffered && buffered.length) {\n const now = Date.now()\n const fresh = buffered.filter(b => b.expiresAt > now)\n for (const { signal } of fresh) {\n this.deliverTo(sub, signal)\n }\n this.pending.delete(key)\n if (fresh.length) this.log(`flushed ${fresh.length} buffered signal(s) to new subscriber call=${callId} as=${recipient}`)\n }\n\n return () => {\n this.subs.delete(sub)\n this.log(`unsubscribe[${tag}] call=${callId} as=${recipient} (${this.subs.size} remaining)`)\n }\n }\n\n /** Deliver a single signal to a subscriber, regardless of sink kind.\n * Returns false if the sink looks dead (caller should drop the sub). */\n private deliverTo(sub: Subscriber, signal: WebRtcSignal): boolean {\n try {\n if (sub.sink.kind === \"sse\") {\n sub.sink.res.write(`event: signal\\ndata: ${JSON.stringify(signal)}\\n\\n`)\n } else {\n sub.sink.deliver(signal)\n }\n return true\n } catch {\n return false\n }\n }\n\n /** Called when a browser POSTs a signal. Routes locally if the target is\n * this node OR matches a local in-process subscriber (e.g. the AI bot\n * peer); otherwise forwards through the mesh. */\n async handleOutgoing(signal: WebRtcSignal): Promise<{ ok: boolean; error?: string }> {\n // Local fan-out: addressed to ourselves (loopback) OR addressed to a\n // peer we're hosting in-process (the bot subscribes via callback, not via\n // mesh — its name like \"bot:atlas\" isn't in mesh.peers[]).\n const wantRecipient = normalizeName(signal.to)\n const hasLocalSub = wantRecipient === normalizeName(this.localName) || this.hasSubscriberFor(signal.callId, wantRecipient)\n if (hasLocalSub) {\n this.fanOut(signal)\n return { ok: true }\n }\n if (!this.forwarder) {\n return { ok: false, error: \"mesh forwarder not wired\" }\n }\n const ok = await this.forwarder(signal.to, signal)\n return ok ? { ok } : { ok: false, error: `forward to \"${signal.to}\" failed` }\n }\n\n private hasSubscriberFor(callId: string, normalizedRecipient: string): boolean {\n for (const sub of this.subs) {\n if (sub.callId === callId && normalizeName(sub.recipient) === normalizedRecipient) return true\n }\n return false\n }\n\n /** Called when the daemon receives a signal from a remote peer via /webrtc/signal. */\n handleIncoming(signal: WebRtcSignal): { ok: boolean; error?: string } {\n // Enforce allowedCallers on the *first* signal of a call (offer or ring).\n // Answers/ICE/hangup for an already-established call go through — the\n // authoritative gate is the offer.\n if ((signal.kind === \"offer\" || signal.kind === \"ring\") && this.allowedCallers.size > 0) {\n if (!this.allowedCallers.has(normalizeName(signal.from))) {\n this.log(`reject inbound ${signal.kind} from \"${signal.from}\": not in allowedCallers`)\n return { ok: false, error: \"caller not allowed\" }\n }\n }\n\n // Rings are purely for out-of-band notification — don't fan out over SSE\n // or buffer (they'd be noise on the callee's page). Dedup by callId so\n // the same call only pings the callee's channels once.\n if (signal.kind === \"ring\") {\n const now = Date.now()\n const expiry = this.recentRings.get(signal.callId)\n if (expiry && expiry > now) {\n this.log(`dedup ring for call=${signal.callId} (already notified)`)\n return { ok: true }\n }\n this.recentRings.set(signal.callId, now + RING_DEDUP_TTL_MS)\n if (this.ringHandler) {\n try { void this.ringHandler(signal) }\n catch (e: any) { this.log(`ringHandler threw: ${e.message}`) }\n } else {\n this.log(`ring received for call=${signal.callId} but no handler wired`)\n }\n return { ok: true }\n }\n\n const delivered = this.fanOut(signal)\n if (delivered === 0) {\n // Callee's page isn't up yet. Buffer the signal so the subscribe() call\n // that lands within BUFFER_TTL_MS replays it. Keeps the flow robust to\n // normal race conditions (SSE reconnects, slow first paint) without\n // forcing the caller to implement retry.\n const key = this.pendingKey(signal.callId, signal.to)\n const bucket = this.pending.get(key) || []\n bucket.push({ signal, expiresAt: Date.now() + BUFFER_TTL_MS })\n this.pending.set(key, bucket)\n this.log(`buffered ${signal.kind} for call=${signal.callId} to=${signal.to} (no subscriber yet, ${bucket.length} pending)`)\n }\n return { ok: true }\n }\n\n private fanOut(signal: WebRtcSignal): number {\n let delivered = 0\n const wantRecipient = normalizeName(signal.to)\n for (const sub of this.subs) {\n if (sub.callId !== signal.callId) continue\n if (normalizeName(sub.recipient) !== wantRecipient) continue\n if (this.deliverTo(sub, signal)) delivered++\n else this.subs.delete(sub)\n }\n return delivered\n }\n\n /** Active subscriber count — for /mesh or admin diagnostics. */\n get subscriberCount(): number {\n return this.subs.size\n }\n\n /** Close every open SSE stream (daemon shutdown). Internal subscribers\n * are simply dropped — they're owned by the bot manager which has its own\n * shutdown path. */\n shutdown(): void {\n if (this.cleanupTimer) clearInterval(this.cleanupTimer)\n for (const sub of this.subs) {\n if (sub.sink.kind === \"sse\") {\n try { sub.sink.res.end() } catch { /* already closed */ }\n }\n }\n this.subs.clear()\n this.pending.clear()\n }\n}\n","import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { dirname, join } from \"path\"\nimport { readFocus, type FocusState } from \"./focus\"\n\n// --- Hold while the owner is in a call (#394, the call check) ---\n//\n// Nothing new goes on the screen while the owner is in a call, the same\n// rule as Focus: held, never dropped, delivered when the call ends.\n//\n// The answer is a file, like the widget's Focus hold, so every process\n// reads the same thing: the daemon, and `agentx notify` run by an agent.\n// The daemon writes it from what it knows (an AgentX call answered, the\n// widget busy) and renews it while the call lasts; any other tool that\n// knows of a call (a phone bridge) may write it too, with its own source.\n// A hold past its `until` is over: a writer that died never holds forever.\n//\n// ~/.agentx/call.json {\"active\": true, \"source\": \"daemon\", \"reason\": \"on a call with Sam\", \"until\": \"<ISO>\"}\n//\n// Calls themselves are exempt: they keep their own busy and Focus rule\n// (calls/service.ts), which reads readFocus, not this.\n\nexport const CALL_HOLD = join(homedir(), \".agentx\", \"call.json\")\n\nexport function readCall(path = CALL_HOLD, now = Date.now()): FocusState {\n try {\n const hold = JSON.parse(readFileSync(path, \"utf-8\"))\n if (hold?.active === true && !(hold.until && Date.parse(hold.until) <= now)) {\n return { active: true, mode: \"call\", reason: `in a call${hold.reason ? `: ${hold.reason}` : \"\"}` }\n }\n } catch {\n /* no file, or unreadable: not in a call */\n }\n return { active: false, mode: null, reason: \"not in a call\" }\n}\n\n/** Focus or a call: what notify, the popup and the Focus watcher hold on. */\nexport function readHold(focus: () => FocusState = readFocus, call: () => FocusState = readCall): FocusState {\n const f = focus()\n return f.active ? f : call()\n}\n\n/** The daemon's side: write the hold while `reason` is set, clear its own\n * hold when it is not. Another source's hold is left alone. */\nexport function writeCallHold(reason: string | null, ttlMs: number, path = CALL_HOLD, now = Date.now()): void {\n let current: any = null\n try { current = JSON.parse(readFileSync(path, \"utf-8\")) } catch { /* none */ }\n if (current && current.source && current.source !== \"daemon\") return\n if (!reason) {\n if (current) rmSync(path, { force: true })\n return\n }\n mkdirSync(dirname(path), { recursive: true })\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify({ active: true, source: \"daemon\", reason, until: new Date(now + ttlMs).toISOString() }) + \"\\n\")\n renameSync(tmp, path)\n}\n","import type { IncomingMessage } from \"http\"\nimport { PairCodeStore } from \"./pair-codes\"\nimport { readJson } from \"./app-fleet\"\n\n// --- POST /api/app/pair-code: trade a one-time code for the session ---\n//\n// Public by necessity (the phone isn't paired yet), so it is guarded three\n// ways: an 8-symbol code (31^8 ≈ 850 billion), a per-client and a GLOBAL\n// failure limit with lockout, and one answer for every failure. Behind\n// `tailscale serve` every request arrives from 127.0.0.1, so the per-client\n// limit alone would see one client; the global limit is what really bounds\n// guessing: at most 10 wrong codes in any 10 minutes, whoever sends them.\n//\n// Every attempt is padded to the same minimum duration, so wrong, expired\n// and used codes can't be told apart by timing either.\n\nexport interface PairLimitOptions {\n perClient: number // failures per window before that client is locked out\n global: number // failures per window, all clients together\n windowMs: number\n lockoutMs: number\n}\n\nexport const DEFAULT_PAIR_LIMITS: PairLimitOptions = {\n perClient: 5,\n global: 10,\n windowMs: 10 * 60 * 1000,\n lockoutMs: 5 * 60 * 1000,\n}\n\ninterface Bucket { failures: number[]; lockedUntil: number }\n\n/** In-memory failure counter. A dashboard restart clears it; codes still\n * expire after 10 minutes, so that grants an attacker nothing lasting. */\nexport class PairAttemptLimiter {\n private clients = new Map<string, Bucket>()\n private all: Bucket = { failures: [], lockedUntil: 0 }\n\n constructor(private opts: PairLimitOptions = DEFAULT_PAIR_LIMITS, private now: () => number = Date.now) {}\n\n /** Seconds until this client may try again, or 0 when it may try now. */\n retryAfter(client: string): number {\n const now = this.now()\n const until = Math.max(this.all.lockedUntil, this.clients.get(client)?.lockedUntil ?? 0)\n return until > now ? Math.ceil((until - now) / 1000) : 0\n }\n\n fail(client: string): void {\n let bucket = this.clients.get(client)\n if (!bucket) this.clients.set(client, (bucket = { failures: [], lockedUntil: 0 }))\n this.bump(bucket, this.opts.perClient)\n this.bump(this.all, this.opts.global)\n }\n\n succeed(client: string): void {\n this.clients.delete(client)\n }\n\n private bump(bucket: Bucket, limit: number): void {\n const now = this.now()\n bucket.failures = bucket.failures.filter((t) => now - t < this.opts.windowMs)\n bucket.failures.push(now)\n if (bucket.failures.length >= limit) bucket.lockedUntil = now + this.opts.lockoutMs\n }\n}\n\nexport interface PairCodeDeps {\n codes: PairCodeStore\n limiter: PairAttemptLimiter\n /** Checks the redeemed token is still an active `app` token; returns its device name. */\n verify: (token: string) => string | null\n log?: (line: string) => void\n minMs?: number\n}\n\nexport type PairCodeResult =\n | { status: 200; body: { device: string }; token: string }\n | { status: 401; body: { error: string } }\n | { status: 429; body: { error: string; retryAfter: number }; retryAfter: number }\n\nexport const PAIR_CODE_FAILED = \"That code didn't work. Check it, or run agentx app pair for a new one.\"\nconst MIN_MS = 400\n\nexport async function redeemPairCode(req: IncomingMessage, deps: PairCodeDeps): Promise<PairCodeResult> {\n const started = Date.now()\n const log = deps.log ?? ((line: string) => console.log(line))\n const client = req.socket.remoteAddress || \"unknown\"\n\n const wait = deps.limiter.retryAfter(client)\n if (wait > 0) {\n log(`[app] pair-code refused from ${client}: locked out for ${wait}s more`)\n return { status: 429, body: { error: \"too many attempts\", retryAfter: wait }, retryAfter: wait }\n }\n\n let code: unknown = null\n try { code = (await readJson(req, 1024)).code } catch { /* counts as a wrong code */ }\n const redeemed = deps.codes.redeem(code)\n const device = redeemed ? deps.verify(redeemed.token) : null\n await padTo(started, deps.minMs ?? MIN_MS)\n\n if (!redeemed || !device) {\n deps.limiter.fail(client)\n log(`[app] pair-code failed from ${client}`)\n return { status: 401, body: { error: PAIR_CODE_FAILED } }\n }\n deps.limiter.succeed(client)\n log(`[app] pair-code paired ${redeemed.tokenId} (${device}) from ${client}`)\n return { status: 200, body: { device }, token: redeemed.token }\n}\n\nasync function padTo(started: number, minMs: number): Promise<void> {\n const left = started + minMs - Date.now()\n if (left > 0) await new Promise((r) => setTimeout(r, left))\n}\n","import { execFile } from \"child_process\"\nimport { soundPath } from \"@/native/macos/sound\"\nimport { draftFor } from \"./choices\"\nimport { showCardWindow } from \"./card-window\"\nimport type { DecisionCard } from \"./cards\"\n\n// --- The Mac popup: answer a decision card in one click ---\n//\n// A soft sound, one short spoken line, then the card: a small web window\n// (card-window.ts, style \"card\", the default) or native dialogs (style\n// \"dialog\", and the fallback when the window can't open):\n//\n// card with choices a list to pick from, then (with a draft) the\n// suggested message in an editable box: Send or Cancel\n// card with a draft the message in an editable box: Send, No or Not now\n// plain card the question: Yes, No or Not now\n//\n// The dialogs never call `activate` (#394): they open without taking the\n// keyboard from the app in use.\n//\n// The dialogs are plain osascript, like `agentx notify`'s fallback banner. Every\n// piece of card text travels as argv, never spliced into the script, so a\n// quote in a title cannot turn into AppleScript.\n//\n// Nothing here decides or sends. It returns what the operator clicked; the\n// runner records it through decideCard, and the agent that raised the card\n// does the sending when it hears the result.\n\n/** Why a popup ended without an answer, when the card window can tell. */\nexport const DISMISS_REASONS = [\"not now\", \"timed out\", \"closed\"] as const\n\nexport type PopupAnswer =\n | { action: \"yes\"; choice?: string; text?: string }\n | { action: \"no\" }\n /** Not now, Cancel, closed, or no answer in time: the card keeps waiting. */\n | { action: \"dismiss\"; why?: (typeof DISMISS_REASONS)[number] }\n\nexport interface PopupSettings {\n /** \"card\": the web card window; \"dialog\": native dialogs. Default \"card\". */\n style?: \"card\" | \"dialog\"\n /** The card's colours. Default: follow the system. */\n theme?: \"system\" | \"light\" | \"dark\"\n /** Speak `say` (or the title) aloud. */\n speak: boolean\n /** A macOS voice for `say -v`. Unset: the system voice. */\n voice?: string\n /** \"chime\" (a soft chime, played by the card), a system sound name, or \"\" for none. */\n sound: string\n volume: number\n /** How long the popup stays up before it gives up. */\n timeoutSeconds: number\n}\n\nexport interface RunResult { ok: boolean; stdout: string }\nexport type Run = (file: string, args: string[], timeoutMs: number) => Promise<RunResult>\n\nconst run: Run = (file, args, timeoutMs) =>\n new Promise((done) => {\n try {\n execFile(file, args, { timeout: timeoutMs }, (err, stdout) => done({ ok: !err, stdout: String(stdout ?? \"\") }))\n } catch {\n done({ ok: false, stdout: \"\" })\n }\n })\n\n/** Spoken lines are short; a long one is cut rather than read for a minute. */\nconst SAY_MAX = 160\n/** Sound and speech must not hold the dialog back. */\nconst CUE_TIMEOUT_MS = 15_000\n\nexport function spokenLine(card: Pick<DecisionCard, \"say\" | \"title\">): string {\n const line = (card.say || card.title).replace(/\\s+/g, \" \").trim()\n return line.length > SAY_MAX ? line.slice(0, SAY_MAX) : line\n}\n\n/** The sound, then the spoken line. Never fatal. The card window plays\n * \"chime\" itself; the dialogs have no chime, so they get Glass. */\nasync function cue(card: DecisionCard, s: PopupSettings, exec: Run, inCard: boolean, speak?: ShowOptions[\"speak\"]): Promise<void> {\n const name = s.sound === \"chime\" ? (inCard ? \"\" : \"Glass\") : s.sound\n const path = name ? soundPath(name) : null\n if (path) await exec(\"/usr/bin/afplay\", [\"-v\", String(Math.min(1, Math.max(0, s.volume))), path], CUE_TIMEOUT_MS)\n if (!s.speak) return\n if (speak) {\n // The host's speaking queue (#493): the line waits while the\n // microphone is open. Past the limit the dialog goes on without it.\n let timer: ReturnType<typeof setTimeout> | undefined\n await Promise.race([speak(spokenLine(card)), new Promise((r) => { timer = setTimeout(r, CUE_TIMEOUT_MS) })])\n clearTimeout(timer)\n } else {\n const voice = s.voice && /^[\\w .()-]+$/.test(s.voice) ? [\"-v\", s.voice] : []\n await exec(\"/usr/bin/say\", [...voice, \"--\", spokenLine(card)], CUE_TIMEOUT_MS)\n }\n}\n\nfunction osa(lines: string[], args: string[]): string[] {\n return [...lines.flatMap((l) => [\"-e\", l]), ...args]\n}\n\n/** argv: title, prompt, then the choices. Prints the pick, or nothing. */\nexport function chooseArgs(title: string, prompt: string, choices: string[]): string[] {\n return osa([\n \"on run argv\",\n \"set opts to items 3 thru -1 of argv\",\n \"set pick to choose from list opts with title (item 1 of argv) with prompt (item 2 of argv) OK button name \\\"Next\\\" cancel button name \\\"Not now\\\"\",\n \"if pick is false then return \\\"\\\"\",\n \"return item 1 of pick\",\n \"end run\",\n ], [title, prompt, ...choices])\n}\n\n/**\n * argv: title, prompt, buttons (joined by newlines), default button,\n * seconds, and the editable text (\"\" for none). Prints\n * \"<button>\\n<text>\", or \"\" when it gave up.\n */\nexport function dialogArgs(title: string, prompt: string, buttons: string[], seconds: number, text?: string): string[] {\n return osa([\n \"on run argv\",\n \"set AppleScript's text item delimiters to linefeed\",\n \"set btns to text items of (item 3 of argv)\",\n \"set secs to (item 5 of argv) as integer\",\n \"if (count of argv) > 5 then\",\n \"set r to display dialog (item 2 of argv) with title (item 1 of argv) default answer (item 6 of argv) buttons btns default button (item 4 of argv) giving up after secs\",\n \"else\",\n \"set r to display dialog (item 2 of argv) with title (item 1 of argv) buttons btns default button (item 4 of argv) giving up after secs\",\n \"end if\",\n \"if gave up of r then return \\\"\\\"\",\n \"if (count of argv) > 5 then return (button returned of r) & linefeed & (text returned of r)\",\n \"return button returned of r\",\n \"end run\",\n ], [title, prompt, buttons.join(\"\\n\"), buttons[buttons.length - 1], String(seconds), ...(text !== undefined ? [text] : [])])\n}\n\n/** \"<button>\\n<text…>\" → the button and the text (which may span lines). */\nexport function parseDialog(stdout: string): { button: string; text: string } | null {\n const out = stdout.replace(/\\n$/, \"\")\n if (!out) return null\n const i = out.indexOf(\"\\n\")\n return i < 0 ? { button: out, text: \"\" } : { button: out.slice(0, i), text: out.slice(i + 1) }\n}\n\nfunction promptFor(card: DecisionCard): string {\n return [card.ask, card.recommend ? `Recommended: ${card.recommend}` : \"\", `From ${card.raised_by}`].filter(Boolean).join(\"\\n\\n\")\n}\n\nexport interface ShowOptions {\n run?: Run\n /** The agent's display name, e.g. \"Sam\". */\n from?: string\n /** Docs and tests: save a PNG of the card window. */\n capture?: string\n /** Previews: start with this option (1-based) picked. */\n pick?: number\n /** Says the card's line in place of `say`: the daemon's speaking queue. */\n speak?: (line: string) => Promise<unknown>\n}\n\n/** Show one card and return what the operator did. */\nexport async function showPopup(card: DecisionCard, settings: PopupSettings, deps: ShowOptions = {}): Promise<PopupAnswer> {\n const exec = deps.run ?? run\n const inCard = (settings.style ?? \"card\") === \"card\"\n // The spoken line runs beside the window, so the card is on screen while it speaks.\n const spoken = cue(card, settings, exec, inCard, deps.speak).catch(() => undefined)\n if (inCard) {\n const answer = await showCardWindow(card, {\n timeoutSeconds: settings.timeoutSeconds, sound: settings.sound, volume: settings.volume,\n theme: settings.theme, from: deps.from, capture: deps.capture, pick: deps.pick,\n }, exec).catch(() => null)\n if (answer) return answer\n }\n await spoken\n return showDialogs(card, settings, exec)\n}\n\nasync function showDialogs(card: DecisionCard, settings: PopupSettings, exec: Run): Promise<PopupAnswer> {\n const seconds = Math.max(10, Math.round(settings.timeoutSeconds))\n // A little past giving-up, so osascript can close its own dialog first.\n const limit = (seconds + 5) * 1000\n const title = `${card.raised_by}: ${card.title}`\n\n let choice: string | undefined\n if (card.choices?.length) {\n const r = await exec(\"/usr/bin/osascript\", chooseArgs(title, promptFor(card), card.choices), limit)\n choice = r.stdout.replace(/\\n$/, \"\")\n if (!r.ok || !card.choices.includes(choice)) return { action: \"dismiss\" }\n if (!card.draft) return { action: \"yes\", choice }\n }\n\n if (card.draft) {\n const prompt = choice ? `${choice}\\n\\nMessage (edit it if you like):` : `${promptFor(card)}\\n\\nMessage (edit it if you like):`\n const buttons = choice ? [\"Cancel\", \"Send\"] : [\"Not now\", \"No\", \"Send\"]\n const r = await exec(\"/usr/bin/osascript\", dialogArgs(title, prompt, buttons, seconds, draftFor(card.draft, choice)), limit)\n const d = r.ok ? parseDialog(r.stdout) : null\n if (d?.button === \"Send\") {\n const text = d.text.trim()\n // An emptied box is not an approval of nothing.\n if (!text) return { action: \"dismiss\" }\n return { action: \"yes\", ...(choice ? { choice } : {}), text }\n }\n if (d?.button === \"No\") return { action: \"no\" }\n return { action: \"dismiss\" }\n }\n\n const r = await exec(\"/usr/bin/osascript\", dialogArgs(title, promptFor(card), [\"Not now\", \"No\", \"Yes\"], seconds), limit)\n const d = r.ok ? parseDialog(r.stdout) : null\n if (d?.button === \"Yes\") return { action: \"yes\" }\n if (d?.button === \"No\") return { action: \"no\" }\n return { action: \"dismiss\" }\n}\n","import { mkdtempSync, rmSync, writeFileSync } from \"fs\"\nimport { tmpdir } from \"os\"\nimport { join } from \"path\"\nimport { CHOICE_LIMITS } from \"./choices\"\nimport { renderCardPage, type CardPageOptions } from \"./card-page\"\nimport { CARD_GRIP, CARD_PAD } from \"./card-page-style\"\nimport type { DecisionCard } from \"./cards\"\nimport { DISMISS_REASONS, type PopupAnswer, type Run } from \"./popup\"\n\n// --- The Mac card window ---\n//\n// A small floating panel with a WKWebView in it, opened by osascript's\n// JavaScript (JXA) bridge to AppKit. No app to build or install: the script\n// below ships with the package, like the osascript dialogs it replaces.\n//\n// The page (card-page.ts) is written to a private temp file and loaded from\n// disk; it can't reach the network. The panel is see-through: the page\n// draws the card, its round corners and its shadow, so the panel is wider\n// and taller than the card by CARD_PAD. It has no buttons of its own. The\n// card sits at the top right of the main screen, floats over other windows,\n// fits its height to the page, and closes on an answer or after `seconds`.\n// The web view fills the panel and would take every press, so a press on\n// the card's header (CARD_GRIP) is handed to the window as a drag before\n// the page sees it. A resize keeps the card where it was dragged to. It stays up when another app is clicked\n// (a panel hides then, unless told not to), and it takes a click made while\n// another app is in front, so Send never needs a second click. It never\n// takes focus (#394): a non-activating panel, put in front without\n// activating anything, so what the owner is typing stays where it is.\n//\n// The script prints the page's answer, or why there was none. Nothing here\n// decides: popup-runner.ts records the answer, and only after parseAnswer\n// has checked it against the card once more.\n\nexport const CARD_WIDTH = 440\n\nexport const WINDOW_JXA = `\nObjC.import(\"Cocoa\"); ObjC.import(\"WebKit\")\nfunction run(argv) {\n var path = argv[0], side = ${CARD_PAD.side}, gripTop = ${CARD_PAD.top}, gripEnd = ${CARD_PAD.top + CARD_GRIP.height}, keep = ${CARD_GRIP.keep}, w = Number(argv[1]) + 2 * side, h = 420, secs = Number(argv[2]), capture = argv[3] || \"\"\n var app = $.NSApplication.sharedApplication\n app.setActivationPolicy($.NSApplicationActivationPolicyAccessory)\n ObjC.registerSubclass({ name: \"AgentXCardView\", superclass: \"WKWebView\", methods: {\n \"acceptsFirstMouse:\": { types: [\"bool\", [\"id\"]], implementation: function () { return true } },\n } })\n var vis = $.NSScreen.mainScreen.visibleFrame\n var top = vis.origin.y + vis.size.height - 4, right = vis.origin.x + vis.size.width - 14 + side\n var mask = $.NSWindowStyleMaskTitled | $.NSWindowStyleMaskClosable | $.NSWindowStyleMaskFullSizeContentView | $.NSWindowStyleMaskNonactivatingPanel\n var win = $.NSPanel.alloc.initWithContentRectStyleMaskBackingDefer($.NSMakeRect(right - w, top - h, w, h), mask, $.NSBackingStoreBuffered, false)\n win.titlebarAppearsTransparent = true\n win.titleVisibility = $.NSWindowTitleHidden\n win.movableByWindowBackground = true\n win.level = $.NSFloatingWindowLevel\n win.releasedWhenClosed = false\n win.hidesOnDeactivate = false\n win.collectionBehavior = $.NSWindowCollectionBehaviorCanJoinAllSpaces\n win.opaque = false\n win.backgroundColor = $.NSColor.clearColor\n win.hasShadow = false\n ;[$.NSWindowCloseButton, $.NSWindowMiniaturizeButton, $.NSWindowZoomButton].forEach(function (b) { win.standardWindowButton(b).hidden = true })\n var cfg = $.WKWebViewConfiguration.alloc.init\n cfg.mediaTypesRequiringUserActionForPlayback = 0\n var web = $.AgentXCardView.alloc.initWithFrameConfiguration($.NSMakeRect(0, 0, w, h), cfg)\n web.autoresizingMask = $.NSViewWidthSizable | $.NSViewHeightSizable\n web.setValueForKey(false, \"drawsBackground\")\n win.contentView = web\n var url = $.NSURL.fileURLWithPath(path)\n web.loadFileURLAllowingReadAccessToURL(url, url.URLByDeletingLastPathComponent)\n // No focus taken (#394): shown over the app in use, which keeps the keyboard.\n // A click on the card still lands (acceptsFirstMouse); a click in its text\n // box makes the panel key without bringing anything else forward.\n win.orderFrontRegardless\n var end = Date.now() + secs * 1000, sized = 0, shot = false, seen = \"\", asked = false, answer = null\n // Every kind of event, as a number. JXA hands the AppKit constant for it over as text, and a\n // loop asking with that gets no event at all: no click, no drag, no key.\n var anyEvent = Number.MAX_SAFE_INTEGER\n while (Date.now() < end) {\n var ev = app.nextEventMatchingMaskUntilDateInModeDequeue(anyEvent, $.NSDate.dateWithTimeIntervalSinceNow(0.05), $.NSDefaultRunLoopMode, true)\n if (ev && !ev.isNil()) {\n if (onGrip(ev)) win.performWindowDragWithEvent(ev); else app.sendEvent(ev)\n }\n if (answer !== null) { win.close; return answer }\n if (!win.isVisible) return gone(\"closed\")\n var t = web.title.js || \"\"\n if (t === \"agentx:answer\" && !asked) {\n asked = true\n web.evaluateJavaScriptCompletionHandler(\"window.agentxAnswer\", function (r) { answer = String(ObjC.unwrap(r) || \"\") })\n }\n if (t !== seen && t.indexOf(\"agentx:size:\") === 0) {\n seen = t\n var want = Math.min(Number(t.slice(12)), vis.size.height - 8)\n var f = win.frame\n if (want > 80) { win.setFrameDisplayAnimate($.NSMakeRect(f.origin.x, f.origin.y + f.size.height - want, w, want), true, true); sized = Date.now() }\n }\n if (capture && !shot && sized && Date.now() - sized > 900) {\n shot = true\n var sa = Application.currentApplication(); sa.includeStandardAdditions = true\n sa.doShellScript(\"/usr/sbin/screencapture -x -o -l \" + win.windowNumber + \" \" + quoted(capture))\n end = Math.min(end, Date.now() + 300)\n }\n }\n win.close\n return gone(\"timed out\")\n function onGrip(ev) {\n if (ev.type != $.NSEventTypeLeftMouseDown || ev.windowNumber != win.windowNumber) return false\n var p = ev.locationInWindow, down = win.frame.size.height - p.y\n return down >= gripTop && down <= gripEnd && p.x >= side && p.x <= w - side - keep\n }\n}\nfunction gone(why) { return JSON.stringify({ action: \"dismiss\", why: why }) }\nfunction quoted(s) { return \"'\" + String(s).replace(/'/g, \"'\\\\\\\\''\") + \"'\" }\n`\n\nexport interface CardWindowSettings extends CardPageOptions {\n timeoutSeconds: number\n /** Tests and docs: save a PNG of the window once it has opened. */\n capture?: string\n}\n\n/** The page's answer, checked against the card. Anything odd is a dismiss. */\nexport function parseAnswer(stdout: string, card: DecisionCard): PopupAnswer {\n const line = stdout.trim().split(\"\\n\").pop() ?? \"\"\n let raw: any\n try { raw = JSON.parse(line) } catch { return { action: \"dismiss\" } }\n if (raw?.action === \"no\") return { action: \"no\" }\n if (raw?.action === \"dismiss\" && DISMISS_REASONS.includes(raw.why)) return { action: \"dismiss\", why: raw.why }\n if (raw?.action !== \"yes\") return { action: \"dismiss\" }\n const choices = card.choices ?? []\n let choice: string | undefined\n if (choices.length) {\n if (typeof raw.choice !== \"string\" || !choices.includes(raw.choice)) return { action: \"dismiss\" }\n choice = raw.choice\n }\n let text: string | undefined\n if (card.draft) {\n text = typeof raw.text === \"string\" ? raw.text.replace(/\\r\\n?/g, \"\\n\").trim() : \"\"\n // An emptied box is not an approval of nothing.\n if (!text || text.length > CHOICE_LIMITS.text) return { action: \"dismiss\" }\n }\n return { action: \"yes\", ...(choice ? { choice } : {}), ...(text ? { text } : {}) }\n}\n\n/**\n * Show the card in the window. Returns null when the window couldn't open,\n * so the caller can fall back to the plain dialogs.\n */\nexport async function showCardWindow(card: DecisionCard, settings: CardWindowSettings, exec: Run): Promise<PopupAnswer | null> {\n const seconds = Math.max(10, Math.round(settings.timeoutSeconds))\n const dir = mkdtempSync(join(tmpdir(), \"agentx-card-\"))\n try {\n const page = join(dir, \"card.html\")\n writeFileSync(page, renderCardPage(card, { ...settings, still: !!settings.capture }), { mode: 0o600 })\n const args = [\"-l\", \"JavaScript\", \"-e\", WINDOW_JXA, page, String(CARD_WIDTH), String(seconds), ...(settings.capture ? [settings.capture] : [])]\n const r = await exec(\"/usr/bin/osascript\", args, (seconds + 10) * 1000)\n if (!r.ok) return null\n return parseAnswer(r.stdout, card)\n } finally {\n rmSync(dir, { recursive: true, force: true })\n }\n}\n","import { randomBytes } from \"crypto\"\nimport { esc } from \"@/daemon/ui/util\"\nimport { CARD_CSS } from \"./card-page-style\"\nimport { CHOICE_PLACEHOLDER } from \"./choices\"\nimport type { DecisionCard } from \"./cards\"\n\n// --- The Mac card: one decision card as a small web page ---\n//\n// card-window.ts shows this page in a floating window. It is rendered here,\n// with every piece of card text escaped, and shipped as one file: no\n// network (the Content-Security-Policy forbids it), no dashboard session.\n//\n// The page talks back through its title, which the window reads:\n// agentx:size:<px> the height it needs, so the window fits it\n// (less when shrunk to its title; when the window\n// can't be that tall, the middle of the card scrolls)\n// agentx:answer the operator answered; the window reads\n// window.agentxAnswer (JSON) and closes\n// The answer itself stays out of the title: WebKit cuts a title at 1,000\n// characters, which a message of a few paragraphs passes. It is\n// {action:\"yes\", choice?, text?}, {action:\"no\"} or {action:\"dismiss\"}.\n// card-window.ts checks it against the card again, so a page that\n// misbehaves still can't approve something the card didn't offer.\n\nexport interface CardPageOptions {\n /** Shown instead of the agent id, e.g. \"Sam\". */\n from?: string\n /** \"chime\" plays a soft two-note chime in the page; anything else: silent here. */\n sound?: string\n volume?: number\n theme?: \"system\" | \"light\" | \"dark\"\n now?: number\n /** Previews and screenshots: start with this option (1-based) picked. */\n pick?: number\n /** Screenshots: no slide-in, so the picture never catches the card half faded. */\n still?: boolean\n}\n\nconst time = (d: Date) => d.toLocaleTimeString(\"en-GB\", { hour: \"2-digit\", minute: \"2-digit\" })\nconst day = (d: Date) => `${d.getDate()} ${d.toLocaleDateString(\"en-US\", { month: \"short\" })}`\n\n/** \"since 09:41\" for a card raised today, \"since 28 Sep\" for an older one. */\nfunction since(iso: string, now: number): string {\n const d = new Date(iso)\n if (Number.isNaN(d.getTime())) return \"\"\n return `since ${d.toDateString() === new Date(now).toDateString() ? time(d) : day(d)}`\n}\n\n/** \"Thu 14:00\" inside the coming week, \"Thu 8 Oct 14:00\" past it. */\nfunction when(iso: string, now: number): string {\n const d = new Date(iso)\n const soon = d.getTime() - now < 6 * 86_400_000\n return [d.toLocaleDateString(\"en-US\", { weekday: \"short\" }), ...(soon ? [] : [day(d)]), time(d)].join(\" \")\n}\n\nfunction initials(name: string): string {\n const words = name.trim().split(/[\\s_-]+/).filter(Boolean)\n return (words.length > 1 ? words[0][0] + words[1][0] : name.trim().slice(0, 1)).toUpperCase()\n}\n\n/**\n * The option the recommendation names, and the reason that is left. The\n * card stores its advice as one line (\"Thursday 10:00: your calendar is\n * free\"); when that line starts with an option, the card marks that option\n * and starts with it picked. The option must be the whole start: the line\n * ends there, or a separator and a space follow (\"No: ...\" names No; \"Not\n * yet\", \"No strong view\" and \"No-one\" name nothing, and \"Thu 10\" is not\n * named by \"Thu 10:30\"). An option only mentioned further on is\n * not the advice (\"do not approve\"). No match: nothing is picked, the line\n * shows whole.\n */\nexport function recommended(card: Pick<DecisionCard, \"recommend\" | \"choices\">): { index: number; why: string } {\n const text = (card.recommend ?? \"\").trim()\n const low = text.toLowerCase()\n const choices = card.choices ?? []\n let index = -1\n choices.forEach((c, i) => {\n if (!low.startsWith(c.toLowerCase()) || !/^\\s*($|[:,.;–—-](\\s|$))/.test(text.slice(c.length))) return\n if (index < 0 || c.length > choices[index].length) index = i\n })\n if (index < 0) return { index, why: text }\n const why = text.slice(choices[index].length).replace(/^[\\s:,.;–—-]+/, \"\")\n return { index, why: why.slice(0, 1).toUpperCase() + why.slice(1) }\n}\n\nfunction primaryLabel(card: DecisionCard): string {\n if (card.origin?.kind === \"request\") return \"Hand it back\"\n return card.draft ? \"Send\" : card.choices?.length ? \"Choose\" : \"Yes\"\n}\n\nconst ICON = (path: string) =>\n `<svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${path}</svg>`\nconst CLOCK = ICON('<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7v5l3 2\"/>')\nconst FOLD = ICON('<path d=\"m6 15 6-6 6 6\"/>')\nconst LOCK = ICON('<rect x=\"5\" y=\"11\" width=\"14\" height=\"10\" rx=\"2\"/><path d=\"M8 11V7a4 4 0 0 1 8 0v4\"/>')\n\nexport function renderCardPage(card: DecisionCard, opts: CardPageOptions = {}): string {\n const now = opts.now ?? Date.now()\n const from = opts.from || card.raised_by\n const nonce = randomBytes(12).toString(\"base64\")\n const choices = card.choices ?? []\n const reminder = card.origin?.kind === \"reminder\"\n // An open request that needs attention (popup-runner.ts): no expiry, and no means drop.\n const request = card.origin?.kind === \"request\"\n const rec = recommended(card)\n const options = choices.map((c, i) =>\n `<button class=\"opt\" role=\"radio\" aria-checked=\"false\" data-i=\"${i}\"><span class=\"n\">${i + 1}</span><span class=\"t\">${esc(c)}</span>${i === rec.index ? '<i class=\"dot\"></i>' : \"\"}</button>`,\n ).join(\"\")\n const recLabel = card.recommend ? `<span class=\"rec\"><i class=\"dot\"></i>${esc(from)} recommends</span>` : \"\"\n const data = {\n choices,\n draft: card.draft ?? \"\",\n placeholder: CHOICE_PLACEHOLDER,\n chime: opts.sound === \"chime\",\n volume: Math.min(1, Math.max(0, opts.volume ?? 0.4)),\n theme: opts.theme ?? \"system\",\n // A pick asked for (previews) wins; else the card opens on the recommended option.\n pick: opts.pick && opts.pick <= choices.length ? opts.pick - 1 : rec.index,\n }\n return `<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\">\n<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}'\">\n<title>AgentX</title>\n<style>${CARD_CSS}</style></head>\n<body><div class=\"wrap\"><main class=\"card${opts.still ? \" still\" : \"\"}\">\n<div class=\"head\" title=\"Drag to move\"><span class=\"avatar\">${esc(initials(from))}</span>\n<div class=\"who\"><b>${esc(from)}</b><span>${esc(since(card.created_at, now))}</span></div>\n<span class=\"kind${reminder ? \"\" : \" decision\"}\">${reminder ? \"Reminder\" : request ? \"Request\" : \"Decision\"}</span>\n<button class=\"fold\" id=\"fold\" title=\"Shrink\" aria-label=\"Shrink the card\" aria-expanded=\"true\">${FOLD}</button></div>\n<div class=\"body\"><h1 dir=\"auto\">${esc(card.title)}</h1>\n${card.context ? `<p class=\"context\" dir=\"auto\">${esc(card.context)}</p>` : \"\"}\n<p class=\"ask\" dir=\"auto\">${esc(card.ask)}</p>\n${options || recLabel ? `<div class=\"label-row\">${options ? '<span class=\"label\">Pick one</span>' : \"\"}${recLabel}</div>` : \"\"}\n${options ? `<div class=\"options\" role=\"radiogroup\">${options}</div>` : \"\"}\n${rec.why ? `<p class=\"why\" dir=\"auto\">${esc(rec.why)}</p>` : \"\"}\n${card.draft ? `<div class=\"label-row\" id=\"msg\"><span class=\"label\">Message</span><span class=\"edited\"><i class=\"dot\"></i>Edited</span><button class=\"reset\" id=\"reset\">Reset</button></div>\n<textarea id=\"text\" dir=\"auto\" rows=\"2\" spellcheck=\"true\"></textarea>` : \"\"}\n<div class=\"notes\"><div>${CLOCK}<span>${request ? \"It stays in Open requests on the Approvals page until you close it.\" : `If you don't answer by ${esc(when(card.expires, now))}: ${esc(card.if_silent)}.`}</span></div>\n<div>${LOCK}<span>Nothing goes out until you click.</span></div></div>\n</div><div class=\"foot\"><button class=\"btn later\" id=\"later\">Not now<kbd>esc</kbd></button>\n<button class=\"btn\" id=\"no\">${request ? \"Drop\" : \"No\"}</button>\n<button class=\"btn primary\" id=\"yes\">${primaryLabel(card)}<kbd>⌘↩</kbd></button></div>\n</main></div>\n<script type=\"application/json\" id=\"data\">${JSON.stringify(data).replace(/</g, \"\\\\u003c\")}</script>\n<script nonce=\"${nonce}\">${CARD_SCRIPT}</script>\n</body></html>`\n}\n\n// Plain ES2019, no imports: it runs in the window's WKWebView.\nconst CARD_SCRIPT = `\n(function () {\n var d = JSON.parse(document.getElementById(\"data\").textContent);\n var root = document.documentElement;\n var dark = d.theme === \"dark\" || (d.theme === \"system\" && matchMedia(\"(prefers-color-scheme: dark)\").matches);\n if (dark) root.setAttribute(\"data-theme\", \"dark\");\n var opts = [].slice.call(document.querySelectorAll(\".opt\"));\n var box = document.getElementById(\"text\");\n var msg = document.getElementById(\"msg\");\n var yes = document.getElementById(\"yes\");\n var card = document.querySelector(\".card\");\n var body = document.querySelector(\".body\");\n var fold = document.getElementById(\"fold\");\n var pick = -1, touched = false, done = false;\n\n function fill(i) { return d.draft.split(d.placeholder).join(i >= 0 ? d.choices[i] : \"\").trim(); }\n function send(a) { if (done) return; done = true; window.agentxAnswer = JSON.stringify(a); document.title = \"agentx:answer\"; }\n // What the card needs, not what it has: in a window too short for it, its middle scrolls.\n function fit() {\n // The title carries the answer signal once sent: a late resize must not write over it.\n if (done) return;\n var wrap = document.querySelector(\".wrap\"), head = document.querySelector(\".head\"), foot = document.querySelector(\".foot\");\n document.title = \"agentx:size:\" + Math.ceil(wrap.offsetHeight - card.clientHeight + head.offsetHeight + body.scrollHeight + foot.offsetHeight);\n }\n function mark() { touched = box.value.trim() !== fill(pick); msg.classList.toggle(\"touched\", touched); }\n function update() {\n opts.forEach(function (o, i) { o.setAttribute(\"aria-checked\", String(i === pick)); });\n yes.disabled = (d.choices.length > 0 && pick < 0) || (box && !box.value.trim());\n }\n function grow() { if (!box) return; box.style.height = \"auto\"; box.style.height = box.scrollHeight + 4 + \"px\"; }\n // quiet: the pick made on open. The box is not focused, so the number keys still pick.\n function choose(i, quiet) {\n // An edited message keeps the edits, with the old pick swapped for the new one.\n if (box && touched && pick >= 0) box.value = box.value.split(d.choices[pick]).join(d.choices[i]);\n else if (box) box.value = fill(i);\n pick = i;\n if (box) { grow(); mark(); }\n update();\n if (box && !quiet) box.focus();\n }\n opts.forEach(function (o, i) { o.addEventListener(\"click\", function () { choose(i); }); });\n if (box) {\n box.value = d.choices.length ? \"\" : fill(-1);\n box.placeholder = d.choices.length ? \"Pick an option above\" : \"\";\n grow();\n box.addEventListener(\"input\", function () { grow(); mark(); update(); });\n document.getElementById(\"reset\").addEventListener(\"click\", function () { box.value = fill(pick); grow(); mark(); update(); box.focus(); });\n }\n yes.addEventListener(\"click\", function () {\n if (yes.disabled) return;\n var a = { action: \"yes\" };\n if (pick >= 0) a.choice = d.choices[pick];\n if (box) a.text = box.value.trim();\n send(a);\n });\n document.getElementById(\"no\").addEventListener(\"click\", function () { send({ action: \"no\" }); });\n document.getElementById(\"later\").addEventListener(\"click\", function () { send({ action: \"dismiss\", why: \"not now\" }); });\n fold.addEventListener(\"click\", function () {\n var shrunk = card.classList.toggle(\"folded\");\n fold.setAttribute(\"aria-expanded\", String(!shrunk));\n fold.title = shrunk ? \"Show all\" : \"Shrink\";\n fit();\n });\n document.addEventListener(\"keydown\", function (e) {\n if (e.key === \"Escape\") return send({ action: \"dismiss\", why: \"not now\" });\n // A shrunk card hides its buttons: no answer by key while they can't be seen.\n if (card.classList.contains(\"folded\")) return;\n if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); return yes.click(); }\n // preventDefault: choose() focuses the box, and the digit must not be typed into it.\n if (document.activeElement !== box && /^[1-9]$/.test(e.key) && opts[+e.key - 1]) { e.preventDefault(); choose(+e.key - 1); }\n });\n if (d.pick >= 0) choose(d.pick, true); else update();\n requestAnimationFrame(fit);\n if (box) new ResizeObserver(fit).observe(box);\n\n if (d.chime) try {\n var ac = new AudioContext(), t = ac.currentTime + 0.05;\n [[783.99, 0], [1046.5, 0.14]].forEach(function (n) {\n [1, 2].forEach(function (h) {\n var o = ac.createOscillator(), g = ac.createGain();\n o.type = \"sine\"; o.frequency.value = n[0] * h;\n g.gain.setValueAtTime(0, t + n[1]);\n g.gain.linearRampToValueAtTime(d.volume * (h === 1 ? 0.5 : 0.12), t + n[1] + 0.012);\n g.gain.exponentialRampToValueAtTime(0.0001, t + n[1] + 1.4);\n o.connect(g).connect(ac.destination); o.start(t + n[1]); o.stop(t + n[1] + 1.5);\n });\n });\n } catch (e) {}\n})();\n`\n","// --- The look of the Mac card (card-page.ts) ---\n//\n// The card has its own small set of tokens, apart from the dashboard's: it\n// is a quiet panel on the desktop, not a page of the dashboard. Paper and\n// ink neutrals, hairline borders, navy for the one action that matters,\n// teal for the small marks (recommended, edited, focus). The dark set is\n// the same card remapped, picked by data-theme on <html>.\n//\n// The window is see-through around the card (card-window.ts), so the round\n// corners and the soft shadow are drawn here. CARD_PAD is the room the\n// shadow needs; the window is that much larger than the card.\n\nexport const CARD_PAD = { top: 8, side: 32, bottom: 60 } as const\n\n// The card's header is its handle: a press in the top CARD_GRIP.height of\n// the card drags the window (card-window.ts takes it before the page does).\n// The last CARD_GRIP.keep at the right stays with the page, for the button\n// that shrinks the card.\nexport const CARD_GRIP = { height: 56, keep: 52 } as const\n\nexport const CARD_CSS = `\n:root {\n --ac-paper: #fafaf9; --ac-soft: #f4f4f2; --ac-edge: #e9e9e6;\n --ac-text: #0b1413; --ac-muted: #4a5856; --ac-subtle: #7c8987;\n --ac-primary: #1e3a8a; --ac-primary-deep: #172554;\n --ac-blue: #1e3a8a; --ac-blue-2: #1d4ed8;\n --ac-accent: #14b8a6; --ac-accent-deep: #0f766e;\n --ac-rim: rgba(11,20,19,.10);\n --ac-shadow: 0 0 0 .5px rgba(11,20,19,.05), 0 24px 48px -16px rgba(11,20,19,.30), 0 2px 6px rgba(11,20,19,.06);\n --ac-lift: 0 12px 40px rgba(30,58,138,.20), 0 2px 6px rgba(11,20,19,.06);\n --ac-font: \"Geist\", -apple-system, BlinkMacSystemFont, \"Noto Sans Arabic\", system-ui, sans-serif;\n --ac-mono: \"Geist Mono\", ui-monospace, \"SF Mono\", Menlo, monospace;\n color-scheme: light;\n}\n[data-theme=\"dark\"] {\n --ac-paper: #111c1b; --ac-soft: #172322; --ac-edge: #263433;\n --ac-text: #e8eeed; --ac-muted: #a7b3b1; --ac-subtle: #83918f;\n --ac-primary: #2563eb; --ac-primary-deep: #1d4ed8;\n --ac-blue: #93c5fd; --ac-blue-2: #60a5fa;\n --ac-accent-deep: #5eead4;\n --ac-rim: rgba(255,255,255,.09);\n --ac-shadow: 0 0 0 .5px rgba(0,0,0,.6), 0 28px 56px -16px rgba(0,0,0,.65), 0 2px 8px rgba(0,0,0,.3);\n color-scheme: dark;\n}\n* { box-sizing: border-box; }\nhtml, body { margin: 0; background: transparent; }\nbody {\n font: 13.5px/1.5 var(--ac-font); color: var(--ac-text); font-feature-settings: \"ss01\", \"cv11\";\n -webkit-font-smoothing: antialiased; -webkit-user-select: none; user-select: none; cursor: default;\n}\n.wrap { padding: ${CARD_PAD.top}px ${CARD_PAD.side}px ${CARD_PAD.bottom}px; }\n.card {\n background: var(--ac-paper); border: 1px solid var(--ac-rim); border-radius: 18px;\n box-shadow: var(--ac-shadow); overflow: hidden; animation: ac-in .26s cubic-bezier(.65,0,.35,1);\n position: relative; display: flex; flex-direction: column; max-height: calc(100vh - ${CARD_PAD.top + CARD_PAD.bottom}px);\n}\n.card::before {\n content: \"\"; position: absolute; top: 6px; left: 50%; width: 32px; height: 4px; margin-left: -16px;\n border-radius: 2px; background: var(--ac-edge);\n}\n@keyframes ac-in { from { transform: translateY(14px); opacity: 0; } }\n.card.still { animation: none; }\n@media (prefers-reduced-motion: reduce) { .card { animation: none; } }\n.head { flex: none; display: flex; align-items: center; gap: 10px; padding: 18px 20px 0; cursor: grab; }\n.body { padding: 14px 20px 16px; min-height: 0; overflow-y: auto; }\n.fold {\n flex: none; width: 24px; height: 24px; display: grid; place-items: center; padding: 0; border-radius: 6px;\n border: 1px solid var(--ac-edge); background: var(--ac-soft); color: var(--ac-subtle); cursor: pointer;\n}\n.fold:hover { color: var(--ac-text); border-color: var(--ac-subtle); }\n.folded .fold svg { transform: rotate(180deg); }\n.folded .body > :not(h1), .folded .foot { display: none; }\n.avatar {\n flex: none; width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center;\n font-size: 14px; font-weight: 600; color: #fff;\n background: linear-gradient(135deg, var(--ac-blue-2), var(--ac-accent));\n}\n.who { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }\n.who b { font-size: 13.5px; font-weight: 560; letter-spacing: -.008em; line-height: 1.25; }\n.who span { font-size: 12px; line-height: 1.3; color: var(--ac-subtle); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.label, .kind, .edited { font-family: var(--ac-mono); font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; }\n.kind {\n padding: 3px 8px; border-radius: 999px; white-space: nowrap;\n color: var(--ac-muted); background: var(--ac-soft); border: 1px solid var(--ac-edge);\n}\n.kind.decision {\n color: var(--ac-blue); background: color-mix(in oklab, var(--ac-blue-2) 10%, var(--ac-paper));\n border-color: color-mix(in oklab, var(--ac-blue-2) 22%, var(--ac-edge));\n}\nh1 { margin: 0; font-size: 18.5px; line-height: 1.3; font-weight: 540; letter-spacing: -.018em; text-wrap: balance; }\nh1:dir(rtl) { letter-spacing: 0; }\n.context { margin: 6px 0 0; color: var(--ac-muted); white-space: pre-wrap; unicode-bidi: plaintext; text-wrap: pretty; }\n.ask { margin: 10px 0 0; font-weight: 500; unicode-bidi: plaintext; }\n.label-row { margin-top: 16px; display: flex; align-items: center; gap: 8px; min-height: 18px; }\n.label { color: var(--ac-subtle); margin-inline-end: auto; }\n.dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--ac-accent); }\n.rec { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ac-subtle); }\n.why { margin: 8px 0 0; font-size: 12px; line-height: 1.4; color: var(--ac-subtle); unicode-bidi: plaintext; }\n.options { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 6px; }\n.opt {\n display: inline-flex; align-items: center; gap: 8px; padding: 5px 12px 5px 5px; padding-inline: 5px 12px;\n border-radius: 999px; border: 1px solid var(--ac-edge); background: var(--ac-paper); color: var(--ac-text);\n font: inherit; font-size: 13px; line-height: 1.35; text-align: start; cursor: pointer;\n transition: background .15s ease, border-color .15s ease, transform .15s ease;\n}\n.opt:hover { transform: translateY(-1px); }\n.opt .n {\n flex: none; width: 20px; height: 20px; display: grid; place-items: center; border-radius: 6px;\n border: 1px solid var(--ac-edge); border-bottom-width: 2px; background: var(--ac-soft); color: var(--ac-subtle);\n font-family: var(--ac-mono); font-size: 10.5px;\n}\n.opt .t { unicode-bidi: plaintext; }\n.opt[aria-checked=\"true\"] { background: var(--ac-primary); border-color: var(--ac-primary); color: #fff; }\n.opt[aria-checked=\"true\"] .n { background: rgba(255,255,255,.16); border-color: rgba(255,255,255,.28); color: #fff; }\n.opt[aria-checked=\"true\"] .dot { background: #5eead4; }\n.edited { display: none; align-items: center; gap: 5px; color: var(--ac-accent-deep); }\n.edited .dot { width: 5px; height: 5px; }\n.reset { display: none; border: 0; background: none; padding: 0; font: inherit; font-size: 12px; color: var(--ac-muted); cursor: pointer; }\n.reset:hover { color: var(--ac-text); }\n.touched .edited { display: inline-flex; }\n.touched .reset { display: inline; }\ntextarea {\n margin-top: 6px; display: block; width: 100%; min-height: 60px; max-height: 240px; resize: none; overflow-y: auto;\n padding: 10px 12px; border: 1px solid var(--ac-edge); border-radius: 10px; background: var(--ac-soft);\n color: var(--ac-text); font: inherit; line-height: 1.45; outline: none; unicode-bidi: plaintext;\n -webkit-user-select: text; user-select: text; cursor: text; transition: border-color .15s ease, box-shadow .15s ease;\n}\ntextarea::placeholder { color: var(--ac-subtle); }\ntextarea:focus {\n border-color: var(--ac-accent); background: var(--ac-paper);\n box-shadow: 0 0 0 4px color-mix(in oklab, var(--ac-accent) 18%, transparent);\n}\n.notes { margin-top: 14px; display: flex; flex-direction: column; gap: 5px; font-size: 12px; line-height: 1.4; color: var(--ac-subtle); }\n.notes div { display: flex; gap: 7px; align-items: flex-start; }\n.notes svg { flex: none; margin-top: 1.5px; }\n.foot { flex: none; display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--ac-edge); background: var(--ac-soft); }\n.foot .later { margin-inline-end: auto; }\nbutton.btn {\n display: inline-flex; align-items: center; gap: 8px; padding: 9px 14px; border-radius: 999px;\n font: inherit; font-weight: 500; letter-spacing: -.005em; white-space: nowrap; cursor: pointer;\n background: transparent; color: var(--ac-text); border: 1px solid var(--ac-edge);\n transition: transform .15s ease, background .15s ease, box-shadow .15s ease, border-color .15s ease;\n}\nbutton.btn:hover { background: var(--ac-soft); border-color: var(--ac-subtle); }\nbutton.primary, button.primary:hover { background: var(--ac-primary); border-color: transparent; color: #fff; }\nbutton.primary:hover:not(:disabled) { transform: translateY(-1px); background: var(--ac-primary-deep); box-shadow: var(--ac-lift); }\nbutton.primary:disabled { opacity: .55; cursor: not-allowed; }\nkbd { font: 11px var(--ac-mono); color: var(--ac-subtle); }\nbutton.primary kbd { color: inherit; opacity: .7; }\n`\n","import { focusLabel, type FocusState } from \"@/notify/focus\"\nimport { readHold } from \"@/notify/hold\"\nimport { summarize } from \"@/requests/card-view\"\nimport type { RequestRecord } from \"@/requests/store\"\nimport { CARD_LIMITS, listCards, readCard, type DecisionCard } from \"./cards\"\nimport { mirrorCovers } from \"./mirror\"\nimport { decide, requestStoreFor, type InboxContext } from \"./inbox\"\nimport { showPopup, type PopupAnswer, type PopupSettings } from \"./popup\"\nimport { cardRuleBreak, returnedText } from \"./screen-gate\"\nimport { readInboxState, recordPopped, recordWanted } from \"./state\"\n\n// --- When the Mac popup shows a card ---\n//\n// Called by the daemon every minute, never awaited by the sweep:\n// - macOS only, and only when approvals.popup.enabled is on;\n// - one popup at a time, oldest waiting card first;\n// - each card is shown once. Not now, Cancel or no answer leaves it in\n// the inbox, where the dashboard, the CLI and the digest still have it;\n// - in Focus (system or the widget hold) or in a call (#394) nothing\n// shows: the card is not marked shown, so it pops on the first minute\n// after the hold ends. Same rule as `agentx notify`: held, never dropped;\n// - the screen gate (#394, screen-gate.ts): a card that breaks a rule\n// (one question, title 120, ask 300, at most 3 choices) is not shown;\n// it stays in the inbox and its author is told why, once. A card the\n// operator asks for himself is exempt;\n// - the window takes no focus: it floats over the app in use without\n// taking the keyboard from it (card-window.ts);\n// - cards put off with \"later\" wait until they come back;\n// - cards older than a day don't pop, so switching the popup on doesn't\n// replay a backlog one dialog at a time;\n// - a check-in (checkin.ts) puts every card waiting at that moment back\n// in line, old or not, and each shows once more;\n// - a card the operator asks for (Show on Mac on the Approvals page)\n// goes first and shows again, whatever its age. `agentx approvals\n// popup <key>` shows one from the terminal without waiting for this;\n// - with no card waiting, a request that came to need attention in the\n// last day shows as a card, once (#459): Hand it back, Drop or Not\n// now. The banner that announces it is gone in seconds; the card\n// stays up like any other.\n//\n// Every popup ends with one log line saying how: the answer, \"not now\",\n// \"timed out\" or \"closed\".\n\nexport interface PopupRunnerSettings extends PopupSettings {\n enabled: boolean\n}\n\nexport const POPUP_MAX_AGE_MS = 24 * 3_600_000\n\nexport type PopupOutcome = \"off\" | \"busy\" | \"none\" | \"held\" | \"shown\" | \"returned\"\n\nexport interface PopupRunnerDeps {\n ctx: InboxContext\n settings: PopupRunnerSettings\n log: (msg: string) => void\n focus?: () => FocusState\n show?: (card: DecisionCard, settings: PopupSettings, opts: { from?: string; speak?: (line: string) => Promise<unknown> }) => Promise<PopupAnswer>\n /** Says a card's line through the host's speaking queue, which waits\n * while the microphone is open. Without it the popup runs `say`. */\n speak?: (line: string) => Promise<unknown>\n /** An agent's display name, for the card. */\n agentName?: (agentId: string) => string | undefined\n /** Tell a card's author it was not shown, and why (the screen gate). */\n returnToAuthor?: (card: DecisionCard, text: string) => void\n platform?: NodeJS.Platform\n}\n\n// Shared by every caller in the process: a config reload must not open a\n// second dialog over the first.\nlet showing = false\n// Request cards all say the same sentence: one that follows another\n// within this long shows without it (#493).\nconst REQUEST_SAY_GAP_MS = 10 * 60_000\nlet requestSaidAt = -Infinity\n\n/** The next card to show, or null. */\nexport function nextCardToPop(ctx: InboxContext): DecisionCard | null {\n const now = ctx.now ?? Date.now()\n const { popped = {}, snoozed, passAt, wanted = [] } = readInboxState(ctx.root)\n const pass = passAt ? Date.parse(passAt) : NaN\n const cards = listCards(ctx.root, \"pending\").filter((c) => Date.parse(c.expires) > now)\n const asked = cards.find((c) => wanted.includes(`card:${c.id}`))\n if (asked) return asked\n for (const card of cards) {\n const key = `card:${card.id}`\n if (popped[key]) continue\n // The Reminders mirror alerts for it (#1062): no second alert.\n if (mirrorCovers(ctx.root, card.id, now)) continue\n const until = snoozed[key]\n if (until && Date.parse(until) > now) continue\n const created = Date.parse(card.created_at)\n if (now - created > POPUP_MAX_AGE_MS && !(created <= pass)) continue\n return card\n }\n return null\n}\n\n/** Requests that need attention, oldest first. */\nfunction requestsWaiting(ctx: InboxContext): RequestRecord[] {\n try { return requestStoreFor(ctx)?.listByState(\"needs_attention\") ?? [] } catch { return [] }\n}\n\n/** The next request to show: it came to need attention in the last day,\n * was not shown yet, and was not put off. */\nexport function nextRequestToPop(ctx: InboxContext): RequestRecord | null {\n const now = ctx.now ?? Date.now()\n const { popped = {}, snoozed } = readInboxState(ctx.root)\n return requestsWaiting(ctx).find((r) => {\n const key = `request:${r.id}`\n const until = snoozed[key]\n return !popped[key] && !(until && Date.parse(until) > now) && now - r.updatedAt <= POPUP_MAX_AGE_MS\n }) ?? null\n}\n\n/** A request as the card the window shows. Not stored: the answer goes to\n * the request through the inbox (yes hands it back, no drops it). */\nexport function requestAsCard(r: RequestRecord, now: number): DecisionCard {\n return {\n id: r.id,\n title: summarize(r.text, CARD_LIMITS.title),\n context: r.attentionReason ?? \"It is not finished and nothing is retrying it.\",\n ask: `Hand it back to ${r.agentId}, or drop it?`,\n say: \"A request you made is not finished.\",\n recommend: \"\",\n if_silent: \"keep\",\n expires: new Date(now + POPUP_MAX_AGE_MS).toISOString(),\n raised_by: r.agentId,\n created_at: new Date(r.createdAt).toISOString(),\n status: \"pending\",\n origin: { kind: \"request\", id: r.id },\n }\n}\n\nexport async function popNext(deps: PopupRunnerDeps): Promise<PopupOutcome> {\n const { ctx, settings, log } = deps\n if (!settings.enabled || (deps.platform ?? process.platform) !== \"darwin\") return \"off\"\n if (showing) return \"busy\"\n const request = nextCardToPop(ctx) ? null : nextRequestToPop(ctx)\n const card = request ? requestAsCard(request, ctx.now ?? Date.now()) : nextCardToPop(ctx)\n if (!card) return \"none\"\n const focus = (deps.focus ?? readHold)()\n if (focus.active) return \"held\"\n\n const key = `${request ? \"request\" : \"card\"}:${card.id}`\n const broken = (readInboxState(ctx.root).wanted ?? []).includes(key) ? null : cardRuleBreak(card)\n if (broken) {\n const waiting = [...listCards(ctx.root, \"pending\").map((c) => `card:${c.id}`), ...requestsWaiting(ctx).map((r) => `request:${r.id}`)]\n recordPopped(ctx.root, key, waiting, ctx.now)\n log(`[approvals] popup: ${key} not shown, back to ${card.raised_by}: ${broken}`)\n deps.returnToAuthor?.(card, returnedText(card, broken))\n return \"returned\"\n }\n\n showing = true\n try {\n // Marked first: a dialog that crashes must not come back every minute.\n const waiting = [...listCards(ctx.root, \"pending\").map((c) => `card:${c.id}`), ...requestsWaiting(ctx).map((r) => `request:${r.id}`)]\n recordPopped(ctx.root, key, waiting, ctx.now)\n log(`[approvals] popup: showing ${key} from ${card.raised_by}`)\n const now = ctx.now ?? Date.now()\n const repeat = !!request && now - requestSaidAt < REQUEST_SAY_GAP_MS\n if (request && settings.speak) requestSaidAt = now\n // An agent on another node has no name here: say which node it is on.\n const from = deps.agentName?.(card.raised_by) ?? (card.node ? `${card.raised_by} on ${card.node}` : undefined)\n const answer = await (deps.show ?? showPopup)(card, repeat ? { ...settings, speak: false } : settings,\n { from, speak: deps.speak })\n if (answer.action === \"dismiss\") {\n log(`[approvals] popup: ${key} left waiting: ${answer.why ?? \"no answer\"} (${focusLabel(focus)} when shown)`)\n return \"shown\"\n }\n // Answered somewhere else while the dialog was up: the first answer stands.\n const stillWaiting = request ? requestStoreFor(ctx)?.get(request.id)?.state === \"needs_attention\" : readCard(ctx.root, card.id)?.status === \"pending\"\n if (!stillWaiting) {\n log(`[approvals] popup: ${key} was answered elsewhere; popup answer ignored`)\n return \"shown\"\n }\n const r = await decide(ctx, key, answer.action, {\n by: \"operator (popup)\",\n ...(answer.action === \"yes\" ? { choice: answer.choice, text: answer.text } : {}),\n })\n log(r.ok ? `[approvals] popup: ${r.message}` : `[approvals] popup: ${key} not recorded: ${r.error}`)\n return \"shown\"\n } catch (e: any) {\n log(`[approvals] popup failed for ${key}: ${e?.message ?? e}`)\n return \"shown\"\n } finally {\n showing = false\n }\n}\n\n/** Put a waiting card back in line for the popup. The daemon shows it on its next minute. */\nexport function popAgain(ctx: InboxContext, key: string, enabled: boolean): { ok: true; message: string } | { ok: false; error: string } {\n if (!enabled) return { ok: false, error: \"the Mac popup is off; turn it on with `agentx approvals settings --popup on`\" }\n const card = key.startsWith(\"card:\") ? readCard(ctx.root, key.slice(5)) : null\n if (!card) return { ok: false, error: `no card \"${key}\"; the popup shows decision cards only` }\n if (card.status !== \"pending\") return { ok: false, error: `${key} is already ${card.status}` }\n recordWanted(ctx.root, key)\n return { ok: true, message: `${key} will show on the Mac within a minute` }\n}\n","import type { IncomingMessage, ServerResponse } from \"http\"\nimport { closeSync, createReadStream, fstatSync, openSync, realpathSync } from \"fs\"\nimport { isAbsolute, resolve, sep } from \"path\"\nimport { ARTIFACT_LIMITS, artifactType } from \"@/utils/artifact-sentinel\"\nimport { OUTBOX_DIR } from \"@/utils/app-outbox\"\n\n// --- GET /app-files: one file an agent declared, for the phone app ---\n//\n// The daemon half of /api/app/files/:id (app-files.ts). The dashboard keeps\n// which files each answer declared and which phone may see them; it asks\n// the daemon that ran the agent for the file by agent and declared path.\n// Mesh-gated (isMeshGatedPath): a same-host dashboard comes over loopback,\n// a dashboard on another computer with a mesh token.\n//\n// Whatever the path says, only a regular file whose real location (links\n// followed) is inside the agent's workspace is served, of an allowed type\n// and size, and never as something a browser would run.\n\nexport const APP_FILES_PATH = \"/app-files\"\n\nconst AGENT_RE = /^[A-Za-z0-9_.:-]{1,64}$/\n\nexport interface AppFilesApiDeps {\n /** The agent's workspace as configured, or null for an unknown agent. */\n workspaceOf: (agentId: string) => string | null\n /** Relative workspaces resolve against it (the daemon's cwd). */\n cwd?: string\n /** Refusals are logged, so the agent's owner (and the agent, reading the\n * daemon log) learns why a file never reached the phone. */\n log?: (message: string) => void\n}\n\n/** Where a declared path really is, or why it may not be served. */\nexport function resolveArtifact(\n workspace: string, file: string, cwd = process.cwd(),\n): { ok: true; path: string; mime: string; kind: string } | { ok: false; status: number; error: string } {\n if (!file || file.length > ARTIFACT_LIMITS.pathChars || file.includes(\"\\0\")) return { ok: false, status: 400, error: \"bad file path\" }\n if (file.split(/[\\\\/]/).includes(\"..\")) return { ok: false, status: 403, error: `the path leaves the workspace: copy the file into ${OUTBOX_DIR}/ first` }\n if (!artifactType(file)) return { ok: false, status: 415, error: \"this type of file is not served\" }\n let root: string\n try { root = realpathSync(resolve(cwd, workspace)) } catch { return { ok: false, status: 404, error: \"the agent's workspace is missing\" } }\n let real: string\n try { real = realpathSync(isAbsolute(file) ? file : resolve(root, file)) } catch { return { ok: false, status: 404, error: \"file not found\" } }\n if (!real.startsWith(root + sep)) return { ok: false, status: 403, error: `the file is outside the agent's workspace: copy it into ${OUTBOX_DIR}/ first` }\n // A link inside the workspace may point at a file of another type.\n const type = artifactType(real)\n if (!type) return { ok: false, status: 415, error: \"this type of file is not served\" }\n return { ok: true, path: real, ...type }\n}\n\n/** `inline` for pictures, sound and video; everything else downloads. */\nexport function contentDisposition(name: string, kind: string): string {\n const ascii = name.replace(/[^\\x20-\\x7e]/g, \"_\").replace(/[\"\\\\;]/g, \"_\").slice(0, 120) || \"file\"\n return `${kind === \"file\" ? \"attachment\" : \"inline\"}; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(name.slice(0, 120))}`\n}\n\n/** Headers every served file carries, on the daemon and the dashboard. */\nexport function fileHeaders(mime: string, name: string, kind: string): Record<string, string> {\n return {\n \"Content-Type\": mime,\n \"Content-Disposition\": contentDisposition(name, kind),\n \"X-Content-Type-Options\": \"nosniff\",\n // Opened on its own, the file runs nothing (an SVG or HTML-looking text).\n \"Content-Security-Policy\": \"default-src 'none'; img-src 'self'; media-src 'self'; sandbox\",\n \"Cache-Control\": \"private, max-age=3600\",\n \"Accept-Ranges\": \"bytes\",\n }\n}\n\n/** One `bytes=a-b` range within `size`, or null for the whole file. A range\n * that can't be met is \"unsatisfiable\". */\nexport function parseRange(header: string | undefined, size: number): { start: number; end: number } | null | \"unsatisfiable\" {\n const m = /^bytes=(\\d*)-(\\d*)$/.exec(String(header || \"\").trim())\n if (!m || (!m[1] && !m[2])) return null\n let start: number, end: number\n if (!m[1]) { start = Math.max(0, size - Number(m[2])); end = size - 1 }\n else { start = Number(m[1]); end = m[2] ? Math.min(Number(m[2]), size - 1) : size - 1 }\n if (start > end || start >= size) return \"unsatisfiable\"\n return { start, end }\n}\n\nexport function handleAppFilesApi(req: IncomingMessage, res: ServerResponse, url: URL, deps: AppFilesApiDeps): void {\n const json = (status: number, body: unknown) => {\n res.writeHead(status, { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\", \"X-Content-Type-Options\": \"nosniff\" })\n res.end(JSON.stringify(body))\n }\n if (req.method !== \"GET\" && req.method !== \"HEAD\") return json(405, { error: \"GET only\" })\n const agent = url.searchParams.get(\"agent\") || \"\"\n const file = url.searchParams.get(\"file\") || \"\"\n if (!AGENT_RE.test(agent)) return json(400, { error: \"bad agent id\" })\n const workspace = deps.workspaceOf(agent)\n if (!workspace) return json(404, { error: `no agent \"${agent}\" on this computer` })\n const found = resolveArtifact(workspace, file, deps.cwd)\n if (!found.ok) {\n deps.log?.(`[app-files] refused ${JSON.stringify(file.slice(0, 200))} from ${agent}: ${found.error}`)\n return json(found.status, { error: found.error })\n }\n\n let fd: number\n try { fd = openSync(found.path, \"r\") } catch { return json(404, { error: \"file not found\" }) }\n let size: number\n try {\n // Checked on the open file, so it can't be swapped after the check.\n const st = fstatSync(fd)\n if (!st.isFile()) { closeSync(fd); return json(404, { error: \"not a file\" }) }\n size = st.size\n } catch { closeSync(fd); return json(404, { error: \"file not found\" }) }\n if (size > ARTIFACT_LIMITS.bytes) { closeSync(fd); return json(413, { error: `the file is larger than ${ARTIFACT_LIMITS.bytes / 1024 / 1024} MB` }) }\n\n const name = found.path.split(sep).pop() || \"file\"\n const headers = fileHeaders(found.mime, name, found.kind)\n const range = parseRange(req.headers.range, size)\n if (range === \"unsatisfiable\") {\n closeSync(fd)\n res.writeHead(416, { ...headers, \"Content-Range\": `bytes */${size}` })\n res.end()\n return\n }\n const { start, end } = range ?? { start: 0, end: size - 1 }\n res.writeHead(range ? 206 : 200, {\n ...headers,\n \"Content-Length\": String(size ? end - start + 1 : 0),\n ...(range ? { \"Content-Range\": `bytes ${start}-${end}/${size}` } : {}),\n })\n if (req.method === \"HEAD\" || size === 0) { closeSync(fd); res.end(); return }\n const stream = createReadStream(\"\", { fd, start, end, autoClose: true })\n stream.on(\"error\", () => res.destroy())\n res.on(\"close\", () => stream.destroy())\n stream.pipe(res)\n}\n","// --- Speaking out loud on the daemon's host, one line at a time ---\n//\n// With the system provider (the default) a line is spoken by `say` in the\n// agent's macOS voice and nothing leaves the machine. With ElevenLabs, each\n// line is sent the moment it is known, so its audio is usually ready\n// before the line ahead of it finishes playing. The queue that orders the\n// lines is SpeechOut (speaking-queue.ts): every voice on the daemon shares\n// one, so two voices never talk over each other.\n\nimport { spawn, type ChildProcess } from \"child_process\"\nimport { readFileSync, writeFileSync } from \"fs\"\nimport { tmpdir } from \"os\"\nimport { join } from \"path\"\nimport { warnOnce } from \"./system-voices\"\nimport { ensureSiriSay, isSiriVoice, siriSupported, type SiriHost } from \"./siri\"\nimport { detectLanguage } from \"./language\"\nimport type { SpeechKind } from \"./speaking-queue\"\n\n/** Which engine speaks a line, and in which voice. */\nexport interface VoiceRef {\n provider: \"system\" | \"elevenlabs\"\n /** ElevenLabs voice id; only used by the elevenlabs provider. */\n elevenlabs: string\n /** macOS voice identifier; null for the OS default voice. */\n system: string | null\n /** Identifiers per language (\"fr\", \"ar\"), for lines in that language. */\n languages?: Record<string, string | null>\n /** When ElevenLabs cannot speak, fall back to the system voice. */\n fallback: boolean\n /** Speaking speed, 1 (normal) when unset. */\n rate?: number\n /** Place in the speaking queue; normal when unset. */\n priority?: \"high\" | \"normal\" | \"low\"\n}\n\n/** Words a minute for `say` at this rate; its own default is about 175. */\nexport const sayRate = (rate?: number): number | null =>\n rate && rate !== 1 ? Math.round(175 * rate) : null\n\n/** ElevenLabs' speed setting, which only accepts 0.7 to 1.2. */\nexport const elevenLabsSpeed = (rate?: number): number | null =>\n rate && rate !== 1 ? Math.min(1.2, Math.max(0.7, rate)) : null\n\nexport interface Utterance {\n voice: VoiceRef\n text: string\n /** Called when this line starts playing. */\n onStart?: () => void\n /** Who says it and why; shown in the speaking queue. */\n agentId?: string\n kind?: SpeechKind\n /** The floor this line speaks under: it plays while that floor is held. */\n floor?: string\n}\n\n/** Audio for a line: an mp3 file, or null to speak it with the system voice. */\nexport type Synth = (u: Utterance, signal: AbortSignal) => Promise<string | null>\n/** Start playing; the returned process exits when playback ends. */\nexport type Play = (file: string | null, u: Utterance) => ChildProcess\n\n/** Why a line that had started playing stopped: it played to the end,\n * its player failed, it ran past its bound, the listener spoke (pause),\n * the floor was taken, or it was skipped, cancelled with its kind, or\n * stopped with the queue. */\nexport type SpeechEndReason = \"finished\" | \"failed\" | \"watchdog\" | \"paused\" | \"floor\" | \"skipped\" | \"cancelled\" | \"stopped\"\n\nexport interface SpeechEvents {\n onStart?: (u: Utterance, at: number) => void\n onEnd?: (u: Utterance, at: number, completed: boolean) => void\n /** Every end of a line that had started, with its reason and how long\n * it had been playing. */\n onStopped?: (u: Utterance, reason: SpeechEndReason, playedMs: number) => void\n}\n\nexport function elevenLabsKey(): string | null {\n if (process.env.ELEVENLABS_API_KEY) return process.env.ELEVENLABS_API_KEY\n for (const p of [`${process.env.HOME}/.elevenlabs/key`, `${process.env.HOME}/.agentx/elevenlabs-key.txt`]) {\n try {\n const v = readFileSync(p, \"utf8\").trim()\n if (v) return v\n } catch { /* next */ }\n }\n return null\n}\n\n/** ElevenLabs Flash: the lowest-latency model, fine for short lines. */\nexport function elevenLabsSynth(key: string | null = elevenLabsKey()): Synth {\n let n = 0\n return async (u, signal) => {\n if (u.voice.provider === \"system\") return null\n if (!key) {\n if (u.voice.fallback) return null\n throw new Error(\"ElevenLabs is the voice provider but no key is set\")\n }\n try {\n const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${u.voice.elevenlabs}/stream?output_format=mp3_44100_128`, {\n method: \"POST\",\n headers: { \"xi-api-key\": key, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n text: u.text, model_id: \"eleven_flash_v2_5\",\n ...(elevenLabsSpeed(u.voice.rate) ? { voice_settings: { speed: elevenLabsSpeed(u.voice.rate) } } : {}),\n }),\n signal,\n })\n if (!res.ok) throw new Error(`ElevenLabs ${res.status}: ${(await res.text()).slice(0, 160)}`)\n const file = join(tmpdir(), `agentx-say-${process.pid}-${++n}.mp3`)\n writeFileSync(file, Buffer.from(await res.arrayBuffer()))\n return file\n } catch (e: any) {\n if (signal.aborted || !u.voice.fallback) throw e\n warnOnce(`elevenlabs:${String(e?.message ?? e).slice(0, 40)}`, `[voice] ElevenLabs failed (${String(e?.message ?? e).slice(0, 160)}); speaking with the system voice`)\n return null\n }\n }\n}\n\n/** The system voice for a line: its language's voice, else the default. */\nexport function systemVoiceFor(v: VoiceRef, text: string): string | null {\n const lang = v.languages && detectLanguage(text)\n return lang && v.languages && lang in v.languages ? v.languages[lang] : v.system\n}\n\n/** The `say` arguments for a line. A line that opens with \"-\" must not be\n * read as an option, so the text always comes from stdin. A Siri voice\n * cannot be named to `say`; it speaks as the OS default. */\nexport const sayArgs = (v: VoiceRef, text = \"\"): string[] => {\n const id = systemVoiceFor(v, text)\n const wpm = sayRate(v.rate)\n return [...(id && !isSiriVoice(id) ? [\"-v\", id] : []), ...(wpm ? [\"-r\", String(wpm)] : [])]\n}\n\n/** The command that speaks a line. On macOS every line goes through the\n * shared script: it switches the default for a Siri voice, holds the one\n * speaker lock, bounds the line, and is what `stopAllSpeakers` reaches. */\nexport function sayCommand(v: VoiceRef, text: string, siriSay: string | null): [string, string[]] {\n if (!siriSay) return [\"say\", sayArgs(v, text)]\n const id = systemVoiceFor(v, text)\n return [\"/bin/sh\", [siriSay, ...(id ? [id] : [])]]\n}\n\n/** How long a line may play before it is taken for hung: its length's\n * worth of speech with room to spare, capped. The shared script applies\n * the same bound to its own `say`. */\nexport function speakLimitMs(text: string): number {\n const words = text.split(/\\s+/).filter(Boolean).length\n return Math.min(300, 5 + Math.floor((words * 6) / 10)) * 1000\n}\n\n/** Silence every speaker on this host, AgentX Voice's lines included:\n * the line holding the shared script's lock stops (the voice is\n * restored) and every queued line is dropped. Never throws. */\nexport function stopAllSpeakers(script: string | null = siriSayScript()): void {\n if (!script) return\n try {\n const p = spawn(\"/bin/sh\", [script, \"--stop\"], { stdio: \"ignore\" })\n p.on(\"error\", () => {})\n } catch { /* nothing to stop */ }\n}\n\n/** Put back the Spoken Content voice a line killed outright (SIGKILL, a\n * crash) left switched, instead of waiting for the next line to do it.\n * Takes the shared lock like any line. Never throws. */\nexport function restoreSpokenVoice(script: string | null = siriSayScript()): void {\n if (!script) return\n try {\n const p = spawn(\"/bin/sh\", [script, \"--restore\"], { stdio: \"ignore\" })\n p.on(\"error\", () => {})\n } catch { /* the next line restores it */ }\n}\n\n/** The shared script's path on a Mac that can switch voices, written if\n * needed; null elsewhere, or when it cannot be written (plain `say` still\n * speaks). Nothing is written on a host that cannot switch. */\nexport function siriSayScript(host: SiriHost = {}, home?: string): string | null {\n if (!siriSupported(host)) return null\n try {\n return ensureSiriSay(home)\n } catch (e: any) {\n warnOnce(\"siri-say\", `[voice] could not write the Siri voice script (${String(e?.message ?? e).split(\"\\n\")[0]}); Siri voices will not switch`)\n return null\n }\n}\n\n/** afplay on macOS, mpg123 elsewhere; `say` when there is no audio file. */\nexport const systemPlay: Play = (file, u) => {\n if (!file) {\n const [cmd, args] = sayCommand(u.voice, u.text, siriSayScript())\n // The shared script takes the rate from its environment.\n const wpm = sayRate(u.voice.rate)\n const env = wpm ? { ...process.env, AGENTX_SAY_RATE: String(wpm) } : process.env\n const p = spawn(cmd, args, { stdio: [\"pipe\", \"ignore\", \"ignore\"], env })\n p.stdin?.on(\"error\", () => {})\n p.stdin?.end(u.text)\n return p\n }\n // The file is the queue's to delete: a paused line plays it again.\n return process.platform === \"darwin\"\n ? spawn(\"afplay\", [file], { stdio: \"ignore\" })\n : spawn(\"mpg123\", [\"-q\", file], { stdio: \"ignore\" })\n}\n","import { spawn } from \"child_process\"\nimport { accessSync, constants, readFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { basename, delimiter, dirname, extname, join } from \"path\"\n\n// --- Speech to text for the phone app (and anything else holding a file) ---\n//\n// The same engine order as AgentX Voice on the Mac (Speech.swift): ElevenLabs\n// Scribe when a key is set, then a Whisper that runs on this computer. The\n// phone records whatever its browser can (webm/opus on Android and desktop,\n// mp4/aac on iPhone), so the local engines need ffmpeg to read it: without\n// ffmpeg they are not offered at all.\n//\n// Nothing here logs audio, a transcript or a file path; only which engine ran\n// and why one failed.\n\nexport type SttSetting = \"auto\" | \"elevenlabs\" | \"local\"\nexport type SttEngine = \"elevenlabs\" | \"mlx-whisper\" | \"whisper\"\n\n/** Bounds on one recording, checked by the dashboard and by the daemon.\n * The phone records at `bitsPerSecond`, so 2 minutes is under 1 MB; the\n * byte cap is 2 minutes at about twice that, room for a browser that\n * ignores the requested rate and for the container. The cap bounds bytes,\n * not minutes: a low-bitrate file holds far more than 2 minutes in 2 MB,\n * so only measuring it (with ffmpeg) bounds the length. */\nexport const AUDIO_LIMITS = {\n ms: 2 * 60 * 1000,\n bitsPerSecond: 64_000,\n bytes: 2 * 1024 * 1024,\n} as const\n\n/** The recording's length as the phone states it (X-Audio-Duration-Ms).\n * Required: a missing or malformed value is refused, as is one over the\n * cap (with 2 s of slack for the recorder's own rounding). */\nexport function checkDurationHeader(header: string | string[] | undefined): { status: 400 | 413; error: string } | null {\n const raw = Array.isArray(header) ? header[0] : header\n if (!raw || !/^\\d{1,9}$/.test(raw.trim())) return { status: 400, error: \"Say how long the recording is (X-Audio-Duration-Ms).\" }\n return Number(raw) > AUDIO_LIMITS.ms + 2000 ? { status: 413, error: tooLongMessage() } : null\n}\n\nexport const tooLongMessage = (): string => `The recording is longer than ${AUDIO_LIMITS.ms / 60000} minutes.`\n\nconst AUDIO_TYPES: Record<string, string> = {\n \"audio/webm\": \"webm\", \"video/webm\": \"webm\", \"audio/ogg\": \"ogg\",\n \"audio/mp4\": \"m4a\", \"video/mp4\": \"m4a\", \"audio/x-m4a\": \"m4a\", \"audio/aac\": \"aac\",\n \"audio/mpeg\": \"mp3\", \"audio/wav\": \"wav\", \"audio/x-wav\": \"wav\", \"audio/wave\": \"wav\",\n}\n\n/** The file extension for a recording's Content-Type, or null when it is\n * not audio this route takes. Codec parameters are ignored. */\nexport function audioExt(contentType: string | undefined): string | null {\n const base = String(contentType || \"\").split(\";\")[0].trim().toLowerCase()\n return AUDIO_TYPES[base] ?? null\n}\n\n/** What this computer has for speech to text. */\nexport interface SttHost {\n key: string | null\n mlx: string | null\n whisper: string | null\n ffmpeg: string | null\n}\n\n/** The engines to try, in order. Empty: voice input is not set up here. */\nexport function sttEngines(setting: SttSetting, host: SttHost): SttEngine[] {\n const out: SttEngine[] = []\n if (setting !== \"local\" && host.key) out.push(\"elevenlabs\")\n if (host.ffmpeg) {\n if (host.mlx) out.push(\"mlx-whisper\")\n else if (host.whisper) out.push(\"whisper\")\n }\n return out\n}\n\n/** Why voice input can't work on this computer, in words for the phone. */\nexport function sttSetupHint(setting: SttSetting, host: SttHost): string {\n if (setting === \"local\" && host.key && !host.ffmpeg) return \"Speech to text is set to local, and this computer has no ffmpeg.\"\n if ((host.mlx || host.whisper) && !host.ffmpeg) return \"This computer has Whisper but no ffmpeg to read the recording.\"\n return \"Add an ElevenLabs key on this computer, or install mlx-whisper and ffmpeg.\"\n}\n\n/** Looks for an executable: an explicit path, then the usual install\n * places, then PATH. A launchd daemon's PATH is short, hence the list. */\nexport function findBinary(name: string, explicit?: string | null, pathEnv = process.env.PATH ?? \"\"): string | null {\n const home = homedir()\n const dirs = [join(home, \".local\", \"bin\"), \"/opt/homebrew/bin\", \"/usr/local/bin\", ...pathEnv.split(delimiter).filter(Boolean)]\n for (const p of [explicit, ...dirs.map((d) => join(d, name))]) {\n if (!p) continue\n try { accessSync(p, constants.X_OK); return p } catch { /* next */ }\n }\n return null\n}\n\nexport function detectSttHost(key: string | null): SttHost {\n return {\n key,\n mlx: findBinary(\"mlx_whisper\", process.env.AGENTX_MLX_WHISPER),\n whisper: findBinary(\"whisper\", process.env.AGENTX_WHISPER),\n ffmpeg: findFfmpeg(),\n }\n}\n\n/** The ffmpeg this process would use, from its own PATH (a launchd or\n * systemd service's PATH is not the shell's). */\nexport const findFfmpeg = (): string | null => findBinary(\"ffmpeg\", process.env.AGENTX_FFMPEG)\n\n/** Seconds of audio in a file, measured, not taken from the phone or from\n * the container's stated duration (which the sender writes): ffmpeg\n * decodes it. Decoding stops a little past the cap, so a long file costs\n * no more than a legal one. Null when ffmpeg is not here or can't read it. */\nexport async function measureSeconds(file: string, ffmpeg: string | null): Promise<number | null> {\n return ffmpeg ? decodeSeconds(file, ffmpeg) : null\n}\n\n/** Decodes up to the cap plus 5 s and reads ffmpeg's last \"time=\". */\nexport async function decodeSeconds(file: string, ffmpeg: string): Promise<number | null> {\n const limit = String(AUDIO_LIMITS.ms / 1000 + 5)\n try {\n const err = await runStderr(ffmpeg, [\"-nostdin\", \"-hide_banner\", \"-t\", limit, \"-i\", file, \"-f\", \"null\", \"-\"], 30_000)\n const times = [...err.matchAll(/time=(\\d+):(\\d+):(\\d+(?:\\.\\d+)?)/g)]\n const m = times[times.length - 1]\n return m ? Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3]) : null\n } catch { return null }\n}\n\n/** ElevenLabs Scribe. The recording goes as it is: Scribe reads webm and mp4. */\nexport async function elevenLabsScribe(file: string, mime: string, key: string, signal?: AbortSignal): Promise<string> {\n const fd = new FormData()\n fd.set(\"model_id\", process.env.AGENTX_STT_MODEL || \"scribe_v1\")\n fd.set(\"file\", new Blob([readFileSync(file)], { type: mime }), basename(file))\n const r = await fetch(\"https://api.elevenlabs.io/v1/speech-to-text\", {\n method: \"POST\", headers: { \"xi-api-key\": key }, body: fd, signal: signal ?? AbortSignal.timeout(60_000),\n })\n if (!r.ok) throw new Error(`ElevenLabs ${r.status}: ${(await r.text().catch(() => \"\")).slice(0, 160)}`)\n const j = await r.json() as { text?: unknown }\n if (typeof j.text !== \"string\") throw new Error(\"ElevenLabs returned no text\")\n return j.text.trim()\n}\n\n/** A Whisper CLI on this computer. Both write <name>.txt into `outDir`;\n * stdout is never taken as the words (mlx_whisper prints \"Skipping …\" there\n * and exits 0 when it fails). */\nexport async function localWhisper(engine: \"mlx-whisper\" | \"whisper\", bin: string, file: string, ffmpeg: string, outDir: string): Promise<string> {\n const args = engine === \"mlx-whisper\"\n ? [\"--model\", process.env.AGENTX_MLX_MODEL || \"mlx-community/whisper-large-v3-turbo\", \"--output-format\", \"txt\", \"--output-dir\", outDir, file]\n : [file, \"--model\", process.env.AGENTX_WHISPER_MODEL || \"base\", \"--output_format\", \"txt\", \"--output_dir\", outDir, \"--fp16\", \"False\"]\n // Both read the recording through ffmpeg, found by PATH.\n const env = { ...process.env, PATH: `${dirname(ffmpeg)}${delimiter}${process.env.PATH ?? \"\"}` }\n await run(bin, args, 120_000, env)\n try {\n return readFileSync(join(outDir, basename(file, extname(file)) + \".txt\"), \"utf8\").trim()\n } catch {\n throw new Error(`${engine} wrote no transcript`)\n }\n}\n\n/** Runs to the end and resolves with the tail of stderr (ffmpeg's progress). */\nfunction runStderr(cmd: string, args: string[], timeoutMs: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"ignore\", \"pipe\"] })\n let err = \"\"\n const timer = setTimeout(() => { child.kill(\"SIGKILL\"); reject(new Error(`${basename(cmd)} took too long`)) }, timeoutMs)\n child.stderr.on(\"data\", (d) => { err = (err + d).slice(-8192) })\n child.on(\"error\", (e) => { clearTimeout(timer); reject(e) })\n child.on(\"exit\", (code) => { clearTimeout(timer); code === 0 ? resolve(err) : reject(new Error(`${basename(cmd)} exited ${code}`)) })\n })\n}\n\nfunction run(cmd: string, args: string[], timeoutMs: number, env: NodeJS.ProcessEnv = process.env): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"], env })\n let out = \"\"\n let err = \"\"\n const timer = setTimeout(() => { child.kill(\"SIGKILL\"); reject(new Error(`${basename(cmd)} took longer than ${timeoutMs / 1000}s`)) }, timeoutMs)\n child.stdout.on(\"data\", (d) => { if (out.length < 4096) out += d })\n child.stderr.on(\"data\", (d) => { if (err.length < 4096) err += d })\n child.on(\"error\", (e) => { clearTimeout(timer); reject(e) })\n child.on(\"exit\", (code) => {\n clearTimeout(timer)\n if (code === 0) resolve(out)\n else reject(new Error(`${basename(cmd)} exited ${code}: ${err.trim().split(\"\\n\").pop()?.slice(0, 160) ?? \"\"}`))\n })\n })\n}\n","import type { IncomingMessage, ServerResponse } from \"http\"\nimport { mkdtemp, rm, writeFile } from \"fs/promises\"\nimport { tmpdir } from \"os\"\nimport { join } from \"path\"\nimport { extractUiDirective } from \"@/channels/ui-directive\"\nimport { toSpeakable } from \"@/voice/speakable\"\nimport { extractArtifacts } from \"@/utils/artifact-sentinel\"\nimport { elevenLabsSpeed, type VoiceRef } from \"@/voice/speaker\"\nimport {\n AUDIO_LIMITS, audioExt, checkDurationHeader, elevenLabsScribe, localWhisper, measureSeconds, sttEngines, sttSetupHint,\n tooLongMessage, type SttEngine, type SttHost, type SttSetting,\n} from \"@/voice/transcribe\"\nimport { resolveAgentVoice, voiceRef } from \"@/voice/agent-voice\"\nimport type { MeshVoices } from \"@/voice/mesh-voice\"\nimport type { DaemonConfig } from \"./config\"\n\n// --- Voice in and out for the phone app: POST /voice/transcribe, /voice/speak ---\n//\n// The phone app's dashboard (app-voice.ts) checks the device token and\n// forwards here with the daemon token, because this process holds the\n// ElevenLabs key, the agents' voices and the `voice.stt` setting. Both\n// routes are mesh-gated for every caller off this box (isMeshGatedPath):\n// they spend the ElevenLabs quota and run Whisper on this computer.\n//\n// Audio is never logged and never kept: a recording lives in a private temp\n// folder for the length of the request and is deleted after it.\n\nconst SPEAK_BODY_MAX = 64 * 1024\n/** Transcriptions running at once on this host; more wait for none, they\n * get 429. Whisper is heavy, and one phone rarely needs two. */\nexport const MAX_TRANSCRIBING = 2\nlet transcribing = 0\n\nexport function isVoiceIoPath(path: string): boolean {\n return path === \"/voice/transcribe\" || path === \"/voice/speak\"\n}\n\nexport interface VoiceIoDeps {\n stt: () => SttSetting\n host: () => SttHost\n /** The agent's voice; `peer` names the mesh peer for an agent over there. */\n voiceOf: (agentId: string, peer?: string) => VoiceRef | null\n elevenLabsKey: () => string | null\n log: (line: string) => void\n /** Tests swap the engines and the synthesiser. */\n engines?: Partial<Record<SttEngine, (file: string, mime: string, host: SttHost, dir: string) => Promise<string>>>\n measure?: (file: string, ffmpeg: string | null) => Promise<number | null>\n /** voice.allowUnmeasured: take recordings this host can't measure. */\n allowUnmeasured?: () => boolean\n /** voice.spokenMaxChars: the longest answer said aloud. */\n spokenMaxChars?: () => number\n /** voice.pronunciations: how the voice says a word (#433). */\n pronounce?: (text: string) => string\n /** This node's name, for the phone's setup hint. */\n nodeName?: string\n synth?: (key: string, voice: VoiceRef, text: string) => Promise<Buffer>\n}\n\nexport async function handleVoiceIo(req: IncomingMessage, res: ServerResponse, path: string, deps: VoiceIoDeps): Promise<void> {\n if (req.method !== \"POST\") return json(res, 405, { error: \"POST\" })\n if (path === \"/voice/transcribe\") return transcribe(req, res, deps)\n return speak(req, res, deps)\n}\n\nasync function transcribe(req: IncomingMessage, res: ServerResponse, deps: VoiceIoDeps): Promise<void> {\n const mime = String(req.headers[\"content-type\"] || \"\")\n const ext = audioExt(mime)\n if (!ext) return json(res, 415, { error: \"Send the recording as audio (webm, mp4, ogg, mp3 or wav).\" })\n const stated = checkDurationHeader(req.headers[\"x-audio-duration-ms\"])\n if (stated) { req.resume(); return json(res, stated.status, { error: stated.error }) }\n const audio = await readRaw(req, AUDIO_LIMITS.bytes)\n if (!audio) return json(res, 413, { error: tooBigMessage() })\n if (!audio.length) return json(res, 400, { error: \"The recording is empty.\" })\n if (transcribing >= MAX_TRANSCRIBING) return json(res, 429, { error: \"This computer is already writing down other recordings. Try again in a moment.\" })\n\n const setting = deps.stt()\n const host = deps.host()\n const engines = sttEngines(setting, host)\n // Without ffmpeg the length can't be measured, and the byte cap bounds\n // bytes, not minutes: refuse unless the owner opted out (#233).\n if (!host.ffmpeg && !deps.allowUnmeasured?.()) {\n const where = deps.nodeName ? `on ${deps.nodeName}` : \"on this computer\"\n const more = engines.length ? \"\" : \" \" + sttSetupHint(setting, host)\n return json(res, 503, { error: \"Voice input isn't set up on this computer.\", hint: `Install ffmpeg ${where} for voice input from the phone.${more}`, setup: false })\n }\n if (!engines.length) {\n return json(res, 503, { error: \"Voice input isn't set up on this computer.\", hint: sttSetupHint(setting, host), setup: false })\n }\n // The recording is deleted before the phone gets its answer.\n transcribing++\n let reply: [number, unknown]\n try {\n const dir = await mkdtemp(join(tmpdir(), \"agentx-phone-voice-\"))\n try {\n reply = await transcribeIn(dir, `speech.${ext}`, audio, mime, host, engines, deps)\n } finally {\n await rm(dir, { recursive: true, force: true }).catch(() => {})\n }\n } finally {\n transcribing--\n }\n json(res, reply[0], reply[1])\n}\n\nasync function transcribeIn(dir: string, name: string, audio: Buffer, mime: string, host: SttHost, engines: SttEngine[], deps: VoiceIoDeps): Promise<[number, unknown]> {\n const file = join(dir, name)\n await writeFile(file, audio, { mode: 0o600 })\n // The real length where this host can measure it, whatever the header\n // said. With ffmpeg, a recording it can't read is refused rather than\n // passed on unmeasured. Without ffmpeg (only with voice.allowUnmeasured)\n // just the byte cap applies, which bounds bytes, not minutes.\n if (host.ffmpeg) {\n const secs = await (deps.measure ?? measureSeconds)(file, host.ffmpeg)\n if (secs == null) return [422, { error: \"This computer couldn't read the recording. Try again.\" }]\n if (secs * 1000 > AUDIO_LIMITS.ms + 2000) return [413, { error: tooLongMessage() }]\n }\n const failures: string[] = []\n for (const engine of engines) {\n try {\n const text = await runEngine(engine, file, mime, host, dir, deps)\n deps.log(`[voice] phone recording transcribed with ${engine} (${Math.round(audio.length / 1024)} KB)`)\n if (!text) return [422, { error: \"No words were heard. Hold the orb and speak, then let go.\", engine }]\n return [200, { text, engine }]\n } catch (e: any) {\n const why = String(e?.message ?? e).split(\"\\n\")[0].slice(0, 200)\n failures.push(`${engine}: ${why}`)\n deps.log(`[voice] phone transcription with ${engine} failed: ${why}`)\n }\n }\n return [502, { error: \"Speech to text failed on this computer.\", detail: failures.join(\"; \") }]\n}\n\nfunction runEngine(engine: SttEngine, file: string, mime: string, host: SttHost, dir: string, deps: VoiceIoDeps): Promise<string> {\n const custom = deps.engines?.[engine]\n if (custom) return custom(file, mime, host, dir)\n if (engine === \"elevenlabs\") return elevenLabsScribe(file, mime, host.key!)\n const bin = engine === \"mlx-whisper\" ? host.mlx! : host.whisper!\n return localWhisper(engine, bin, file, host.ffmpeg!, dir)\n}\n\nasync function speak(req: IncomingMessage, res: ServerResponse, deps: VoiceIoDeps): Promise<void> {\n const raw = await readRaw(req, SPEAK_BODY_MAX)\n if (!raw) return json(res, 413, { error: \"request too large\" })\n let body: any\n try { body = JSON.parse(raw.toString(\"utf8\") || \"{}\") } catch { return json(res, 400, { error: \"expected JSON\" }) }\n const agent = typeof body?.agent === \"string\" ? body.agent : \"\"\n const peer = typeof body?.peer === \"string\" && body.peer ? body.peer : undefined\n if (!agent) return json(res, 400, { error: \"agent is required\" })\n const text = speakableAnswer(typeof body?.text === \"string\" ? body.text : \"\", deps.spokenMaxChars?.())\n if (!text) return json(res, 422, { error: \"Nothing in this answer can be said aloud.\" })\n const said = deps.pronounce ? deps.pronounce(text) : text\n\n const voice = deps.voiceOf(agent, peer)\n if (!voice) return json(res, 404, { error: `Unknown agent: ${agent}` })\n // No ElevenLabs voice for this agent: the phone speaks the same cleaned\n // text with its own voice. That is the normal case for system-voice\n // agents, so it is a 200, not an error.\n const browser = (why: string, status = 200) => json(res, status, { fallback: \"browser\", reason: why, text: said })\n if (voice.provider !== \"elevenlabs\") return browser(\"This agent speaks with a system voice, which only plays on the computer.\")\n const key = deps.elevenLabsKey()\n if (!key) return browser(\"No ElevenLabs key is set on this computer.\")\n try {\n const audio = await (deps.synth ?? elevenLabsAudio)(key, voice, said)\n res.writeHead(200, { \"Content-Type\": \"audio/mpeg\", \"Content-Length\": audio.length, \"Cache-Control\": \"no-store\", \"X-Spoken-Chars\": String(said.length) })\n res.end(audio)\n } catch (e: any) {\n deps.log(`[voice] phone answer synthesis failed: ${String(e?.message ?? e).split(\"\\n\")[0].slice(0, 160)}`)\n return browser(\"ElevenLabs could not speak this answer.\", 502)\n }\n}\n\n/** An answer as it is said: no agentx:ui block, no declared files, no\n * pictures, no markdown, capped. Pictures and files are only ever shown. */\nexport function speakableAnswer(text: string, max?: number): string {\n const clean = extractUiDirective(extractArtifacts(text).text).cleanText\n .replace(/!\\[[^\\]\\n]*\\]\\([^)\\n]*\\)/g, \"\")\n return toSpeakable(clean, max)\n}\n\n/** One answer as mp3, in the agent's ElevenLabs voice. */\nasync function elevenLabsAudio(key: string, voice: VoiceRef, text: string): Promise<Buffer> {\n const speed = elevenLabsSpeed(voice.rate)\n const r = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${encodeURIComponent(voice.elevenlabs)}?output_format=mp3_44100_128`, {\n method: \"POST\",\n headers: { \"xi-api-key\": key, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ text, model_id: \"eleven_flash_v2_5\", ...(speed ? { voice_settings: { speed } } : {}) }),\n signal: AbortSignal.timeout(60_000),\n })\n if (!r.ok) throw new Error(`ElevenLabs ${r.status}: ${(await r.text().catch(() => \"\")).slice(0, 160)}`)\n return Buffer.from(await r.arrayBuffer())\n}\n\nexport const tooBigMessage = (): string =>\n `The recording is larger than ${AUDIO_LIMITS.bytes / 1024 / 1024} MB, more than ${AUDIO_LIMITS.ms / 60000} minutes of speech.`\n\n/** The voice for `agentId`: a local agent's when there is no peer, else the\n * agent of that id on that mesh peer exactly. Never another peer's agent\n * that happens to share the id. */\nexport function resolveVoice(\n agentId: string, peer: string | undefined,\n config: Pick<DaemonConfig, \"agents\" | \"voice\">, mesh: Pick<MeshVoices, \"speakerOn\" | \"speaker\">,\n): VoiceRef | null {\n if (peer) return mesh.speakerOn(peer, agentId, false)?.voice ?? null\n const agents = config.agents ?? {}\n return agents[agentId] ? voiceRef(resolveAgentVoice(agentId, agents, config.voice)) : null\n}\n\n/** The whole body, or null past `limit` (a declared length says so at\n * once). Past the limit the rest is read and dropped, not kept, so the\n * answer still reaches the sender. */\nexport function readRaw(req: IncomingMessage, limit: number): Promise<Buffer | null> {\n if (Number(req.headers[\"content-length\"]) > limit) { req.resume(); return Promise.resolve(null) }\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n req.on(\"data\", (chunk: Buffer) => {\n if (size > limit) return\n size += chunk.length\n if (size > limit) { chunks.length = 0; resolve(null); return }\n chunks.push(chunk)\n })\n req.on(\"end\", () => { if (size <= limit) resolve(Buffer.concat(chunks)) })\n req.on(\"error\", reject)\n })\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" })\n res.end(JSON.stringify(body))\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync } from \"fs\"\nimport { resolve, relative, dirname, join, extname, basename } from \"path\"\n\n// --- Scoped file operations for the admin panel ---\n//\n// Lets the dashboard read/write identity files (CLAUDE.md / SOUL.md /\n// IDENTITY.md) and skill files (.claude/skills/<slug>/SKILL.md) WITHIN an\n// agent's workspace without ever touching the rest of the filesystem.\n// Every public function resolves the requested path against the agent's\n// workspace and refuses anything that would escape via .. symlinks, etc.\n\nconst MAX_FILE_BYTES = 200 * 1024 // 200 KB — plenty for CLAUDE.md / skills.\nconst ALLOWED_EXTENSIONS = new Set([\".md\", \".markdown\", \".txt\"])\n/** Extensions the skill-tree viewer is allowed to read/write. Broader than\n * identity editing — skills include shell scripts, lockfiles, configs. Kept\n * as an allow-list so a malicious prompt can't touch an arbitrary binary. */\nconst SKILL_EDITABLE_EXTENSIONS = new Set([\n \".md\", \".markdown\", \".txt\", \".json\", \".jsonc\", \".yaml\", \".yml\", \".toml\",\n \".sh\", \".bash\", \".zsh\", \".py\", \".js\", \".mjs\", \".ts\", \".tsx\", \".jsx\",\n \".css\", \".html\", \".lock\", \".cfg\", \".ini\", \".env\", \".gitignore\",\n])\n/** Depth limit when walking a skill dir tree — defensive against loops. */\nconst SKILL_TREE_MAX_DEPTH = 6\nconst IDENTITY_FILENAMES = new Set([\n \"CLAUDE.md\", \"SOUL.md\", \"IDENTITY.md\", \"PERSONA.md\", \"AGENT.md\", \"BOOTSTRAP.md\",\n])\n\nexport interface FileOverview {\n identity: Array<{\n path: string // relative to workspace, e.g. \"CLAUDE.md\"\n title: string\n size: number\n exists: boolean // canonical files can be listed as templates even if missing\n }>\n skills: Array<{\n slug: string // dir name under .claude/skills/\n title: string // first # heading in SKILL.md or fallback to slug\n size: number\n path: string // relative to workspace, e.g. \".claude/skills/foo/SKILL.md\"\n }>\n workspace: string // absolute, for display only\n}\n\n/**\n * Resolve a user-supplied path to an absolute path strictly within `workspace`.\n * Throws if the result would escape. Returns the normalised absolute path.\n */\nfunction resolveInWorkspace(workspace: string, relPath: string): string {\n if (!workspace) throw new Error(\"Agent workspace is not configured.\")\n const wsAbs = resolve(workspace)\n if (!existsSync(wsAbs)) {\n // Create the workspace lazily — a fresh agent scaffolded via the wizard\n // may have an empty directory created but not yet with file structure.\n mkdirSync(wsAbs, { recursive: true })\n }\n // Reject absolute-path attempts outright.\n if (!relPath || relPath.startsWith(\"/\") || relPath.match(/^[a-zA-Z]:/)) {\n throw new Error(`Path must be relative to the workspace: ${relPath}`)\n }\n const target = resolve(wsAbs, relPath)\n const rel = relative(wsAbs, target)\n // rel === \"\" means target IS the workspace dir — fine for list ops.\n // rel starts with \"..\" iff target is outside the workspace.\n if (rel.startsWith(\"..\")) {\n throw new Error(`Path escapes the workspace: ${relPath}`)\n }\n return target\n}\n\nfunction assertEditable(absPath: string): void {\n const ext = extname(absPath).toLowerCase()\n if (!ALLOWED_EXTENSIONS.has(ext)) {\n throw new Error(`Only text files (.md, .markdown, .txt) can be edited from the admin panel (got ${ext}).`)\n }\n}\n\nfunction extractTitle(markdown: string, fallback: string): string {\n const line = markdown.split(/\\r?\\n/).find((l) => l.trim().startsWith(\"#\"))\n if (!line) return fallback\n return line.replace(/^#+\\s*/, \"\").trim().slice(0, 120) || fallback\n}\n\n/**\n * List the identity files + skills for an agent. Identity \"canonical\" names\n * that don't exist are still reported (exists: false) so the UI can offer to\n * create them with a sensible default.\n */\nexport function listAgentFiles(workspace: string): FileOverview {\n const wsAbs = resolveInWorkspace(workspace, \".\")\n const identity: FileOverview[\"identity\"] = []\n const seen = new Set<string>()\n\n // Every .md in the workspace root is identity material; canonical names\n // sort first.\n let rootEntries: string[] = []\n try { rootEntries = readdirSync(wsAbs) } catch { /* empty */ }\n for (const name of IDENTITY_FILENAMES) {\n const abs = join(wsAbs, name)\n if (existsSync(abs) && statSync(abs).isFile()) {\n const size = statSync(abs).size\n const content = size > 0 && size < MAX_FILE_BYTES ? safeRead(abs) : \"\"\n identity.push({ path: name, title: extractTitle(content, name), size, exists: true })\n seen.add(name)\n } else {\n identity.push({ path: name, title: name, size: 0, exists: false })\n seen.add(name)\n }\n }\n for (const name of rootEntries) {\n if (seen.has(name)) continue\n const abs = join(wsAbs, name)\n try {\n const st = statSync(abs)\n if (!st.isFile()) continue\n if (!ALLOWED_EXTENSIONS.has(extname(name).toLowerCase())) continue\n const content = st.size > 0 && st.size < MAX_FILE_BYTES ? safeRead(abs) : \"\"\n identity.push({ path: name, title: extractTitle(content, name), size: st.size, exists: true })\n } catch { /* skip unreadable */ }\n }\n\n // Skills live under .claude/skills/<slug>/SKILL.md\n const skills: FileOverview[\"skills\"] = []\n const skillsRoot = join(wsAbs, \".claude\", \"skills\")\n if (existsSync(skillsRoot)) {\n try {\n for (const slug of readdirSync(skillsRoot)) {\n const dir = join(skillsRoot, slug)\n let st\n try { st = statSync(dir) } catch { continue }\n if (!st.isDirectory()) continue\n const file = join(dir, \"SKILL.md\")\n if (!existsSync(file)) continue\n const stf = statSync(file)\n const content = stf.size < MAX_FILE_BYTES ? safeRead(file) : \"\"\n skills.push({\n slug,\n title: extractTitle(content, slug),\n size: stf.size,\n path: `.claude/skills/${slug}/SKILL.md`,\n })\n }\n } catch { /* */ }\n }\n skills.sort((a, b) => a.slug.localeCompare(b.slug))\n\n return { identity, skills, workspace: wsAbs }\n}\n\nfunction safeRead(abs: string): string {\n try { return readFileSync(abs, \"utf-8\") } catch { return \"\" }\n}\n\n/**\n * Read a single file within the workspace. Throws if the path is outside the\n * workspace, has a disallowed extension, or doesn't exist.\n */\nexport function readAgentFile(workspace: string, relPath: string): { path: string; content: string } {\n const abs = resolveInWorkspace(workspace, relPath)\n assertEditable(abs)\n if (!existsSync(abs)) return { path: relPath, content: \"\" }\n if (statSync(abs).size > MAX_FILE_BYTES) {\n throw new Error(`File is too large to edit in the admin panel (> ${MAX_FILE_BYTES / 1024} KB).`)\n }\n return { path: relPath, content: readFileSync(abs, \"utf-8\") }\n}\n\n/**\n * Write a file. Creates parent dirs as needed. Rejects extensions / size\n * outside the allow-list the same way readAgentFile does.\n */\nexport function writeAgentFile(workspace: string, relPath: string, content: string): { path: string; bytes: number } {\n const abs = resolveInWorkspace(workspace, relPath)\n assertEditable(abs)\n const buf = Buffer.from(content, \"utf-8\")\n if (buf.length > MAX_FILE_BYTES) {\n throw new Error(`File too large (${buf.length} > ${MAX_FILE_BYTES} bytes).`)\n }\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, buf)\n return { path: relPath, bytes: buf.length }\n}\n\n/**\n * Create a new skill scaffold at .claude/skills/<slug>/SKILL.md.\n */\nexport function createAgentSkill(\n workspace: string,\n slug: string,\n opts: { title?: string; content?: string } = {},\n): { slug: string; path: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) {\n throw new Error(\"Skill slug must be lowercase (letters, digits, -, _) and start with a letter or digit.\")\n }\n const relPath = `.claude/skills/${slug}/SKILL.md`\n const abs = resolveInWorkspace(workspace, relPath)\n if (existsSync(abs)) throw new Error(`Skill \"${slug}\" already exists.`)\n mkdirSync(dirname(abs), { recursive: true })\n const body = opts.content?.trim()\n ? opts.content\n : defaultSkillTemplate(slug, opts.title)\n writeFileSync(abs, body)\n return { slug, path: relPath }\n}\n\n/**\n * Remove a skill directory (.claude/skills/<slug>). Scoped to the workspace\n * so we can never touch anything else.\n */\nexport function deleteAgentSkill(workspace: string, slug: string): { slug: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(abs)) throw new Error(`Skill \"${slug}\" not found.`)\n const st = statSync(abs)\n if (!st.isDirectory()) throw new Error(`Skill path is not a directory: ${slug}`)\n rmSync(abs, { recursive: true, force: true })\n return { slug }\n}\n\n// --- Skill directory tree + per-file I/O ---\n//\n// A skill is `.claude/skills/<slug>/` — usually SKILL.md plus scripts,\n// lockfiles, configs. The admin UI lets the operator browse the directory\n// tree, edit any text file, and dispatch \"install deps\" through the agent.\n\nexport interface SkillTreeEntry {\n /** Path relative to the skill dir root. Directories end with \"/\". */\n path: string\n kind: \"file\" | \"dir\"\n size: number\n editable: boolean // false for binaries or disallowed extensions\n}\n\nexport interface SkillDepsHint {\n /** Which install command the operator should run (or have an agent run). */\n manager: \"npm\" | \"pnpm\" | \"yarn\" | \"pip\" | \"bundle\" | null\n /** Filename that triggered the detection, relative to skill dir. */\n file: string | null\n /** The exact command we'd recommend. */\n command: string | null\n}\n\n/** List every file in `.claude/skills/<slug>/`, recursively. Scoped so the\n * caller can't escape the skill dir via symlinks or \"..\" segments. */\nexport function listSkillTree(workspace: string, slug: string): SkillTreeEntry[] {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const skillRoot = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(skillRoot)) throw new Error(`Skill \"${slug}\" not found.`)\n const out: SkillTreeEntry[] = []\n walk(skillRoot, \"\", 0, out)\n out.sort((a, b) => {\n // dirs first, then alphabetical\n if (a.kind !== b.kind) return a.kind === \"dir\" ? -1 : 1\n return a.path.localeCompare(b.path)\n })\n return out\n\n function walk(absDir: string, relDir: string, depth: number, acc: SkillTreeEntry[]): void {\n if (depth > SKILL_TREE_MAX_DEPTH) return\n let entries: string[] = []\n try { entries = readdirSync(absDir) } catch { return }\n for (const name of entries) {\n if (name === \"node_modules\" || name === \".git\") continue\n const abs = join(absDir, name)\n const rel = relDir ? `${relDir}/${name}` : name\n let st\n try { st = statSync(abs) } catch { continue }\n if (st.isDirectory()) {\n acc.push({ path: `${rel}/`, kind: \"dir\", size: 0, editable: false })\n walk(abs, rel, depth + 1, acc)\n } else if (st.isFile()) {\n const ext = extname(name).toLowerCase()\n // name-only allowlist for dotfiles like .env / .gitignore (no extension)\n const allowedByName = SKILL_EDITABLE_EXTENSIONS.has(ext) ||\n SKILL_EDITABLE_EXTENSIONS.has(name.toLowerCase())\n acc.push({\n path: rel,\n kind: \"file\",\n size: st.size,\n editable: allowedByName && st.size < MAX_FILE_BYTES,\n })\n }\n }\n }\n}\n\n/** Read any file in a skill dir. Respects the skill-editable allowlist. */\nexport function readSkillFile(workspace: string, slug: string, relPath: string): { path: string; content: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n if (!relPath || relPath.includes(\"..\")) throw new Error(`Invalid file path: ${relPath}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}/${relPath}`)\n if (!existsSync(abs) || !statSync(abs).isFile()) {\n throw new Error(`File not found: ${relPath}`)\n }\n const ext = extname(abs).toLowerCase()\n const name = basename(abs).toLowerCase()\n if (!SKILL_EDITABLE_EXTENSIONS.has(ext) && !SKILL_EDITABLE_EXTENSIONS.has(name)) {\n throw new Error(`File type not editable: ${relPath}`)\n }\n if (statSync(abs).size > MAX_FILE_BYTES) {\n throw new Error(`File too large to edit (> ${MAX_FILE_BYTES / 1024} KB): ${relPath}`)\n }\n return { path: relPath, content: readFileSync(abs, \"utf-8\") }\n}\n\n/** Write any allowed file inside a skill dir. Creates parents. */\nexport function writeSkillFile(\n workspace: string,\n slug: string,\n relPath: string,\n content: string,\n): { path: string; bytes: number } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n if (!relPath || relPath.includes(\"..\")) throw new Error(`Invalid file path: ${relPath}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}/${relPath}`)\n const ext = extname(abs).toLowerCase()\n const name = basename(abs).toLowerCase()\n if (!SKILL_EDITABLE_EXTENSIONS.has(ext) && !SKILL_EDITABLE_EXTENSIONS.has(name)) {\n throw new Error(`File type not editable: ${relPath}`)\n }\n const buf = Buffer.from(content, \"utf-8\")\n if (buf.length > MAX_FILE_BYTES) {\n throw new Error(`File too large (${buf.length} > ${MAX_FILE_BYTES} bytes).`)\n }\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, buf)\n return { path: relPath, bytes: buf.length }\n}\n\n/** Detect which dependency manager a skill needs based on the files present. */\nexport function detectSkillDeps(workspace: string, slug: string): SkillDepsHint {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const skillRoot = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(skillRoot)) throw new Error(`Skill \"${slug}\" not found.`)\n const check = (name: string): boolean => existsSync(join(skillRoot, name))\n if (check(\"pnpm-lock.yaml\")) return { manager: \"pnpm\", file: \"pnpm-lock.yaml\", command: \"pnpm install\" }\n if (check(\"yarn.lock\")) return { manager: \"yarn\", file: \"yarn.lock\", command: \"yarn install\" }\n if (check(\"package.json\")) return { manager: \"npm\", file: \"package.json\", command: \"npm install\" }\n if (check(\"requirements.txt\")) return { manager: \"pip\", file: \"requirements.txt\", command: \"pip install -r requirements.txt\" }\n if (check(\"Gemfile\")) return { manager: \"bundle\", file: \"Gemfile\", command: \"bundle install\" }\n return { manager: null, file: null, command: null }\n}\n\nfunction defaultSkillTemplate(slug: string, title?: string): string {\n const displayTitle = title?.trim() || slug\n return `# ${displayTitle}\n\nDescribe what this skill does and when the agent should use it. Keep it under ~5 lines — the agent reads this to decide whether to invoke the skill.\n\n## When to use\n- Trigger condition 1\n- Trigger condition 2\n\n## How to use\nDocument the arguments, commands, or files the skill expects. Example:\n\n\\`\\`\\`bash\n# Example invocation\nclaude-code ${basename(slug)} --arg value\n\\`\\`\\`\n`\n}\n","import { loadDaemonConfig, type DaemonConfig } from \"@/daemon/config\"\n\n// The `screen` block of agentx.json and the one write rule for it, shared\n// by `agentx screen config` and the dashboard so both refuse the same\n// values.\n\nexport type ScreenSettings = DaemonConfig[\"screen\"]\ntype Rect = ScreenSettings[\"regions\"][string]\n\nexport const DEFAULT_SCREEN: ScreenSettings = {\n maxPixels: 1_200_000,\n timeoutMs: 5_000,\n intervalMs: 100,\n changeThreshold: 0.015,\n stableMs: 400,\n regions: {},\n buffer: { enabled: false, seconds: 10, fps: 2, region: \"screen\", maxPixels: 300_000 },\n}\n\n/** Fill gaps in a partial `screen` block with the defaults. */\nexport function screenSettings(cfg: Partial<ScreenSettings> | undefined): ScreenSettings {\n return {\n ...DEFAULT_SCREEN,\n ...(cfg ?? {}),\n regions: { ...(cfg?.regions ?? {}) },\n buffer: { ...DEFAULT_SCREEN.buffer, ...(cfg?.buffer ?? {}) },\n }\n}\n\n/** The `screen` block from agentx.json. A missing or invalid file means\n * the defaults: a capture should never fail over a settings file. */\nexport function readScreenSettings(configPath?: string): ScreenSettings {\n try { return screenSettings(loadDaemonConfig(configPath).screen) } catch { return screenSettings(undefined) }\n}\n\nconst REGION_NAME =/^[a-z][\\w-]*$/\n\n/** Regions the helper knows without configuration; `screen.regions` adds\n * more and may override these. */\nexport const BUILT_IN_REGIONS = [\"window\", \"screen\", \"menubar\", \"notifications\"]\n\nfunction num(v: unknown, what: string, ok: (n: number) => boolean, rule: string): number {\n const n = Number(v)\n if (v === \"\" || v === null || !Number.isFinite(n) || !ok(n)) throw new Error(`${what} must be ${rule}`)\n return n\n}\nconst positiveInt = (v: unknown, what: string) => num(v, what, (n) => Number.isInteger(n) && n > 0, \"a whole number above 0\")\n\n/** \"x,y,w,h\" or {x, y, width, height} → a rect, or an error. */\nexport function parseRect(v: unknown): Rect {\n const r = typeof v === \"string\" ? v.split(\",\").map(Number) : [(v as any)?.x, (v as any)?.y, (v as any)?.width, (v as any)?.height].map(Number)\n if (r.length !== 4 || !r.every(Number.isFinite) || r[2] <= 0 || r[3] <= 0) throw new Error(\"a region is x,y,width,height in screen points, with width and height above 0\")\n return { x: r[0], y: r[1], width: r[2], height: r[3] }\n}\n\n/**\n * Apply a change to a stored `screen` block. Returns the new block;\n * throws on a value that would not load. `regions` entries set to null\n * are removed.\n */\nexport function patchScreen(current: Partial<ScreenSettings> | undefined, patch: Record<string, any>): Partial<ScreenSettings> {\n const next: Record<string, any> = { ...(current ?? {}) }\n if (\"maxPixels\" in patch) next.maxPixels = positiveInt(patch.maxPixels, \"maxPixels\")\n if (\"timeoutMs\" in patch) next.timeoutMs = positiveInt(patch.timeoutMs, \"timeoutMs\")\n if (\"intervalMs\" in patch) next.intervalMs = positiveInt(patch.intervalMs, \"intervalMs\")\n if (\"stableMs\" in patch) next.stableMs = num(patch.stableMs, \"stableMs\", (n) => Number.isInteger(n) && n >= 0, \"a whole number, 0 or more\")\n if (\"changeThreshold\" in patch) next.changeThreshold = num(patch.changeThreshold, \"changeThreshold\", (n) => n >= 0 && n <= 1, \"between 0 and 1\")\n if (patch.regions && typeof patch.regions === \"object\") {\n const regions = { ...(next.regions ?? {}) }\n for (const [name, rect] of Object.entries(patch.regions)) {\n if (!REGION_NAME.test(name)) throw new Error(`region name \"${name}\" must be lowercase letters, digits, - or _`)\n if (rect === null) delete regions[name]\n else regions[name] = parseRect(rect)\n }\n next.regions = regions\n }\n if (patch.buffer && typeof patch.buffer === \"object\") {\n const b = patch.buffer\n const buffer = { ...(next.buffer ?? {}) }\n if (\"enabled\" in b) buffer.enabled = Boolean(b.enabled)\n if (\"seconds\" in b) buffer.seconds = num(b.seconds, \"buffer.seconds\", (n) => n > 0 && n <= 120, \"above 0 and at most 120\")\n if (\"fps\" in b) buffer.fps = num(b.fps, \"buffer.fps\", (n) => n > 0 && n <= 10, \"above 0 and at most 10\")\n if (\"maxPixels\" in b) buffer.maxPixels = positiveInt(b.maxPixels, \"buffer.maxPixels\")\n if (\"region\" in b) buffer.region = String(b.region).trim()\n next.buffer = buffer\n }\n const watched = next.buffer?.region\n if (watched !== undefined && !BUILT_IN_REGIONS.includes(watched) && !next.regions?.[watched]) {\n try { parseRect(watched) } catch {\n throw new Error(`buffer.region \"${watched}\" is not a region — use ${[...BUILT_IN_REGIONS, ...Object.keys(next.regions ?? {})].join(\", \")}, or x,y,w,h`)\n }\n }\n return next\n}\n"],"mappings":"06BAaA,OAAS,cAAAA,GAAY,eAAAC,GAAa,gBAAAC,GAAc,YAAAC,OAAgB,KAChE,OAAS,WAAAC,OAAe,OAqBjB,SAASC,GACdC,EACAC,EACAC,EAAM,KAAK,IAAI,EACfC,EAAa,EACK,CAGlB,IAAMC,EAAOD,EAAa,MACpBE,EAAO,IAAI,IACjB,QAAWC,KAAKL,EAAM,CACpB,IAAMM,EAAOF,EAAK,IAAIC,EAAE,UAAU,EAC9BC,EAAMA,EAAK,KAAKD,CAAC,EAChBD,EAAK,IAAIC,EAAE,WAAY,CAACA,CAAC,CAAC,EAEjC,OAAON,EAAU,IAAIQ,GAAK,CACxB,IAAMC,EAAOJ,EAAK,IAAIG,EAAE,EAAE,GAAK,CAAC,EAC1BE,EAASD,EAAK,OAAOH,GAAKJ,EAAMI,EAAE,IAAMF,CAAI,EAC5CO,EAAQF,EAAK,OAAOH,GAAKJ,EAAMI,EAAE,GAAKF,GAAQF,EAAMI,EAAE,IAAMF,EAAO,CAAC,EACpEQ,EAASF,EAAO,OAAOJ,GAAKA,EAAE,SAAW,QAAQ,EAAE,OACnDO,EAASJ,EAAK,OAAOH,GAAKA,EAAE,SAAW,QAAQ,EAAE,OACjDQ,EAAYL,EAAK,OAAS,KAAK,IAAI,GAAGA,EAAK,IAAIH,GAAKA,EAAE,EAAE,CAAC,EAAI,KAG7DS,EACHN,EAAK,OACJE,EAAM,OAAS,GAAKD,EAAO,SAAW,EAAI,UAC1CA,EAAO,OAAS,GAAKE,EAAS,GAAKF,EAAO,OAAS,UACnDA,EAAO,OAAS,EAAI,SACpB,QAJa,QAKjB,MAAO,CAAE,GAAIF,EAAE,GAAI,KAAMA,EAAE,MAAQA,EAAE,GAAI,MAAAO,EAAO,UAAAD,EAAW,OAAQJ,EAAO,OAAQ,MAAOC,EAAM,OAAQ,OAAAC,EAAQ,OAAAC,CAAO,CACxH,CAAC,EAAE,KAAK,CAAC,EAAGG,IAAM,CAKhB,IAAMC,EAA+B,CAAE,QAAS,EAAG,QAAS,EAAG,OAAQ,EAAG,MAAO,EAAG,MAAO,CAAE,EAC7F,OAAOA,EAAK,EAAE,KAAK,EAAIA,EAAKD,EAAE,KAAK,IAAMA,EAAE,WAAa,IAAM,EAAE,WAAa,EAC/E,CAAC,CACH,CAGA,IAAME,GAAQ,IAAI,IAGX,SAASC,GAASC,EAAiBC,EAAQ,IAAoB,CACpE,GAAI,CAAC3B,GAAW0B,CAAO,EAAG,MAAO,CAAC,EAClC,IAAME,EAAQ3B,GAAYyB,CAAO,EAC9B,OAAOG,GAAKA,EAAE,SAAS,QAAQ,CAAC,EAChC,IAAIA,GAAK,CAAE,IAAMC,EAAI1B,GAAQsB,EAASG,CAAC,EAAG,MAAO,CAAE,KAAMC,EAAG,MAAO3B,GAAS2B,CAAC,EAAE,OAAQ,CAAE,CAAC,EAC1F,KAAK,CAACC,EAAGT,IAAMA,EAAE,MAAQS,EAAE,KAAK,EAChC,MAAM,EAAGJ,CAAK,EACXK,EAAO,IAAI,IAAIJ,EAAM,IAAIC,GAAKA,EAAE,IAAI,CAAC,EAC3C,QAAWI,KAAOT,GAAM,KAAK,EAAQQ,EAAK,IAAIC,CAAG,GAAGT,GAAM,OAAOS,CAAG,EACpE,IAAMC,EAAoB,CAAC,EAC3B,OAAW,CAAE,KAAAC,EAAM,MAAAC,CAAM,IAAKR,EAAO,CACnC,IAAMS,EAAMb,GAAM,IAAIW,CAAI,EAC1B,GAAIE,GAAOA,EAAI,QAAUD,EAAO,CAAMC,EAAI,SAASH,EAAI,KAAKG,EAAI,OAAO,EAAG,SAC1E,IAAMC,EAAUC,GAAYJ,EAAMC,CAAK,EACvCZ,GAAM,IAAIW,EAAM,CAAE,MAAAC,EAAO,QAAAE,CAAQ,CAAC,EAC9BA,GAASJ,EAAI,KAAKI,CAAO,EAE/B,OAAOJ,CACT,CAEA,SAASK,GAAYJ,EAAcC,EAAkC,CACnE,GAAI,CACF,IAAMI,EAAQtC,GAAaiC,EAAM,OAAO,EAAE,MAAM;AAAA,CAAI,EAAE,OAAO,OAAO,EACpE,GAAI,CAACK,EAAM,OAAQ,OAAO,KAC1B,IAAMC,EAAO,KAAK,MAAMD,EAAM,CAAC,CAAC,EAC1BE,EAAaD,EAAK,KAAK,WAC7B,GAAI,CAACC,EAAY,OAAO,KAGxB,IAAIC,EAASF,EAAK,KAAK,QAAU,UACjC,QAASG,EAAIJ,EAAM,OAAS,EAAGI,GAAK,EAAGA,IAAK,CAC1C,IAAMC,EAAO,KAAK,MAAML,EAAMI,CAAC,CAAC,EAC1BE,EAAID,EAAK,QAAUA,EAAK,KAAK,OACnC,GAAIC,EAAG,CAAEH,EAASG,EAAG,OAEvB,MAAO,CAAE,WAAAJ,EAAY,OAAAC,EAAQ,GAAIP,CAAM,CACzC,MAAE,CAAQ,OAAO,IAAK,CACxB,CC5BO,IAAMW,GAAgD,CAC3D,WAAY,EACZ,aAAc,GACd,YAAa,EACb,QAAS,GAAK,GAChB,EAGaC,EAAiB,CAAE,SAAU,IAAK,QAAS,IAAK,QAAS,EAAG,YAAa,GAAI,KAAM,GAAI,EAI9FC,GAA4B,CAACC,EAAYC,EAAOC,IAAa,CACjE,GAAI,CAAE,OAAOC,GAAgBH,EAAYC,EAAOC,CAAQ,CAAE,MAAE,CAAQ,OAAO,IAAK,CAClF,EAaME,GAAM,MAEL,SAASC,GAAcC,EAAsC,CAClE,IAAMC,EAAMD,EAAM,KAAO,KAAK,IAAI,EAC5BE,EAAI,CAAE,GAAGX,GAA4B,GAAGS,EAAM,UAAW,EACzDG,EAAWH,EAAM,UAAYP,GAC7BW,EAAiB,CAAC,EAExB,QAAWC,KAAOL,EAAM,MAAO,CAC7B,IAAMM,EAAON,EAAM,SAAS,IAAIK,EAAI,EAAE,GAAK,CAAC,EACtCT,EAAWS,EAAI,UAAY,MAC3BE,EAAWD,EAAK,IAAKE,IAAO,CAAE,GAAI,KAAK,MAAMA,EAAE,SAAS,EAAG,OAAQA,EAAE,MAAyB,EAAE,EAChGC,EAASH,EAAK,CAAC,EACfI,EAAmB,CACvB,GAAI,QAAQL,EAAI,KAChB,OAAQ,OACR,KAAM,WACN,KAAMM,EAAIN,EAAI,GAAIb,EAAe,IAAI,EACrC,QAAS,CAAE,SAAUmB,EAAIN,EAAI,SAAUb,EAAe,IAAI,EAAG,SAAAI,CAAS,EACtE,MAAOS,EAAI,OAAS,KACpB,QAASA,EAAI,QACb,MAAOA,EAAI,QAAU,UAAY,WACjC,UAAWA,EAAI,QAAUO,GAAUP,EAAI,OAAO,GAAKO,GAAUT,EAASE,EAAI,SAAU,IAAI,KAAKJ,CAAG,EAAGL,CAAQ,CAAC,EAAI,KAChH,QAASa,EACL,CAAE,GAAIA,EAAO,UAAW,OAAQA,EAAO,OAAQ,QAASI,GAAOJ,EAAO,SAAW,UAAY,OAAYA,EAAO,aAAcjB,EAAe,OAAO,CAAE,EACtJ,KACJ,cAAec,EAAK,KAAME,GAAMA,EAAE,SAAW,SAAS,GAAG,WAAa,KACtE,oBAAqBM,GAAWP,CAAQ,EACxC,YAAaD,EAAK,OAClB,MAAO,CAAC,EACR,UAAW,IACb,EACAI,EAAQ,MAAQK,GAAcL,EAASH,EAAUF,EAAI,SAAUT,EAAU,KAAMK,EAAKC,EAAGC,CAAQ,EAC/FC,EAAI,KAAKY,GAAON,CAAO,CAAC,EAG1B,IAAMO,EAAS,IAAI,IAAIC,GACrBlB,EAAM,UAAU,IAAKmB,IAAO,CAAE,GAAIA,EAAE,GAAI,KAAMA,EAAE,KAAM,EAAE,EAAGnB,EAAM,aAAcC,CACjF,EAAE,IAAKmB,GAAM,CAACA,EAAE,GAAIA,CAAC,CAAU,CAAC,EAC1BC,EAAiB,IAAI,IAC3B,QAAWb,KAAKR,EAAM,aAAc,CAClC,IAAMsB,EAAOD,EAAe,IAAIb,EAAE,UAAU,EACxCc,EAAMA,EAAK,KAAKd,CAAC,EAChBa,EAAe,IAAIb,EAAE,WAAY,CAACA,CAAC,CAAC,EAG3C,QAAWe,KAAMvB,EAAM,UAAW,CAChC,IAAMwB,EAAUD,EAAG,MAAM,KAAME,GAAMA,EAAE,KAAK,WAAW,UAAU,CAAC,EAClE,GAAI,CAACD,GAAYA,EAAQ,OAAS,gBAAkBA,EAAQ,OAAS,eAAiB,SACtF,IAAME,EAAOF,EAAQ,QAAU,CAAC,EAC1BG,EAAQJ,EAAG,OAAS,SACpBK,EAAUD,IAAU,SACpBrB,GAAQe,EAAe,IAAIE,EAAG,EAAE,GAAK,CAAC,GAAG,MAAM,EAAE,KAAK,CAACM,EAAGC,KAAMA,GAAE,GAAKD,EAAE,EAAE,EAC3EtB,EAAWD,EAAK,IAAKE,IAAO,CAAE,GAAIA,EAAE,GAAI,OAAQuB,GAAgBvB,EAAE,MAAM,CAAE,EAAE,EAC5EC,EAASF,EAAS,CAAC,EACnByB,EAASR,EAAQ,OAAS,eAC1BS,EAAO,OAAOP,EAAI,MAAS,SAAWA,EAAI,KAAO,GACjD9B,EAAW,OAAO8B,EAAI,UAAa,SAAWA,EAAI,SAAW,MAC7DhB,EAAmB,CACvB,GAAI,YAAYa,EAAG,KACnB,OAAQ,WACR,KAAMS,EAAS,WAAa,QAC5B,KAAMrB,EAAIY,EAAG,OAASA,EAAG,GAAI/B,EAAe,IAAI,EAChD,QAASwC,EACL,CAAE,SAAUrB,EAAIsB,EAAMzC,EAAe,IAAI,EAAG,SAAAI,CAAS,EACrD,CAAE,MAAOe,EAAI,OAAOe,EAAI,OAAU,SAAWA,EAAI,MAAQ,GAAIlC,EAAe,IAAI,EAAG,QAAS0C,GAAgBR,EAAI,OAAQH,CAAE,CAAE,EAChI,MAAOY,GAAcZ,CAAE,EACvB,QAAAK,EACA,MAAAD,EACA,UAAWK,GAAUJ,GAAWK,EAAOrB,GAAUT,EAAS8B,EAAM,IAAI,KAAKhC,CAAG,EAAGL,CAAQ,CAAC,EAAI,KAC5F,QAASa,EAAS,CAAE,GAAI,IAAI,KAAKA,EAAO,EAAE,EAAE,YAAY,EAAG,OAAQA,EAAO,MAAO,EAAI,KACrF,eAAgB,IAAM,CAAE,IAAM2B,EAAI7B,EAAS,KAAM8B,IAAMA,GAAE,SAAW,SAAS,EAAG,OAAOD,EAAI,IAAI,KAAKA,EAAE,EAAE,EAAE,YAAY,EAAI,IAAK,GAAG,EAClI,oBAAqBtB,GAAWP,CAAQ,EACxC,YAAaD,EAAK,OAClB,MAAO,CAAC,EACR,UAAW,IACb,EACMgC,EAAY,KAAK,MAAMf,EAAG,SAAWA,EAAG,SAAW,EAAE,EAC3Db,EAAQ,MAAQsB,EACZjB,GAAcL,EAASH,EAAU0B,EAAMrC,EAAU,OAAO,SAAS0C,CAAS,EAAIA,EAAY,KAAMrC,EAAKC,EAAGC,CAAQ,EAChHoC,GAAW7B,EAASO,EAAO,IAAIM,EAAG,EAAE,GAAG,QAAU,UAAW,OAAO,SAASe,CAAS,EAAIA,EAAY,KAAMrC,EAAKC,CAAC,EACrHE,EAAI,KAAKY,GAAON,CAAO,CAAC,EAG1B,OAAON,EAAI,KAAKoC,EAAe,EAAE,MAAM,EAAGhD,EAAe,QAAQ,CACnE,CAIO,SAASsB,GAAWP,EAAqD,CAC9E,IAAIkB,EAAI,EACR,QAAWY,KAAK9B,EACd,GAAI,EAAA8B,EAAE,SAAW,WAAaA,EAAE,SAAW,UAAYA,EAAE,SAAW,WACpE,GAAIA,EAAE,SAAW,UAAYA,EAAE,SAAW,UAAWZ,QAChD,OAEP,OAAOA,CACT,CAEA,SAASV,GACPP,EACAD,EACA0B,EACArC,EACA0C,EACArC,EACAC,EACAC,EACe,CACf,IAAMsC,EAAuB,CAAC,EACxBC,EAASnC,EAAS,OAASA,EAAS,CAAC,EAAE,GAAK,KAClD,GAAI,CAACC,EAAE,QAAS,CACd,IAAMmC,EAAWC,GAAaF,EAAQJ,EAAWrC,EAAKC,CAAC,EACvD,OAAIyC,GAAUF,EAAM,KAAKE,CAAQ,EAC1BF,EAKT,GAHIjC,EAAE,qBAAuBN,EAAE,YAC7BuC,EAAM,KAAK,CAAE,OAAQ,UAAW,OAAQ,GAAGjC,EAAE,8CAA+C,CAAC,EAE3FkC,IAAW,KACb,OAAAD,EAAM,KAAK,CAAE,OAAQ,YAAa,OAAQ,sBAAuB,CAAC,EAC3DA,EAIT,IAAII,EAAS,IAAI,KAAKH,CAAM,EACxBI,EAAwB,KAC5B,QAASC,EAAI,EAAGA,EAAI7C,EAAE,cACpB4C,EAAW3C,EAAS8B,EAAMY,EAAQjD,CAAQ,EACtC,EAACkD,GAF4BC,IAGjCF,EAASC,EAEX,OAAIA,GAAY7C,EAAM6C,EAAS,QAAQ,EAAI5C,EAAE,SAC3CuC,EAAM,KAAK,CAAE,OAAQ,UAAW,OAAQ,oBAAoB,IAAI,KAAKC,CAAM,EAAE,YAAY,MAAMxC,EAAE,oCAAqC,CAAC,EAElIuC,CACT,CAEA,SAASF,GACP/B,EACAwC,EACAV,EACArC,EACAC,EACe,CACf,IAAMuC,EAAuB,CAAC,EACxBC,EAASlC,EAAE,QAAU,KAAK,MAAMA,EAAE,QAAQ,EAAE,EAAI,KACtD,GAAI,CAACA,EAAE,QAAS,CACd,IAAMmC,EAAWC,GAAaF,EAAQJ,EAAWrC,EAAKC,CAAC,EACvD,OAAIyC,GAAUF,EAAM,KAAKE,CAAQ,EAC1BF,EAET,OAAIjC,EAAE,qBAAuBN,EAAE,YAC7BuC,EAAM,KAAK,CAAE,OAAQ,UAAW,OAAQ,GAAGjC,EAAE,0CAA2C,CAAC,EAEvFkC,IAAW,KAAMD,EAAM,KAAK,CAAE,OAAQ,YAAa,OAAQ,kCAAmC,CAAC,EAC1FO,GAASP,EAAM,KAAK,CAAE,OAAQ,UAAW,OAAQ,iDAAkD,CAAC,EACtGA,CACT,CAEA,SAASG,GAAaF,EAAuBJ,EAA0BrC,EAAaC,EAA0C,CAG5H,IAAM+C,EAAQ,KAAK,IAAIP,GAAU,EAAGJ,GAAa,CAAC,EAClD,OAAIW,GAAShD,EAAMgD,EAAQ/C,EAAE,aAAeJ,GAAY,KACjD,CACL,OAAQ,gBACR,OAAQmD,EAAQ,6BAA6B,KAAK,OAAOhD,EAAMgD,GAASnD,EAAG,SAAW,wBACxF,CACF,CAEA,SAASkB,GAAOR,EAAqB,CACnC,IAAM0C,EAAU,IAAI,IAAI1C,EAAE,MAAM,IAAK2C,GAAMA,EAAE,MAAM,CAAC,EACpD,OAAA3C,EAAE,UAAY0C,EAAQ,IAAI,SAAS,EAAI,WACnCA,EAAQ,IAAI,SAAS,GAAKA,EAAQ,IAAI,SAAS,GAAKA,EAAQ,IAAI,WAAW,EAAI,UAC/EA,EAAQ,IAAI,eAAe,EAAI,OAC/B,KACG1C,CACT,CAEA,IAAM4C,GAAyC,CAAE,SAAU,EAAG,QAAS,EAAG,KAAM,CAAE,EAElF,SAASZ,GAAgBX,EAAYC,EAAoB,CACvD,IAAMuB,EAAKxB,EAAE,UAAYuB,GAAevB,EAAE,SAAS,EAAI,EACjDyB,EAAKxB,EAAE,UAAYsB,GAAetB,EAAE,SAAS,EAAI,EACvD,OAAOuB,EAAKC,GAAM,OAAOxB,EAAE,OAAO,EAAI,OAAOD,EAAE,OAAO,GAAKA,EAAE,KAAK,cAAcC,EAAE,IAAI,CACxF,CAIO,SAASC,GAAgBwB,EAAgC,CAC9D,OAAQA,EAAQ,CACd,IAAK,YAAa,IAAK,KAAM,MAAO,UACpC,IAAK,SAAU,MAAO,SACtB,IAAK,UAAW,MAAO,UACvB,IAAK,WAAY,MAAO,WACxB,IAAK,UAAW,MAAO,UACvB,IAAK,SAAU,MAAO,SACtB,QAAS,MAAO,SAClB,CACF,CAEA,SAASpB,GAAcZ,EAAoC,CACzD,GAAIA,EAAG,WAAY,OAAOZ,EAAIY,EAAG,WAAY/B,EAAe,IAAI,EAChE,IAAMgE,EAAOjC,EAAG,MAAM,KAAM,GAAM,EAAE,OAAS,SAAW,OAAO,EAAE,QAAQ,SAAY,UAAY,EAAE,OAAO,OAAO,EACjH,OAAOiC,EAAO7C,EAAI,OAAO6C,EAAK,OAAQ,OAAO,EAAGhE,EAAe,IAAI,EAAI,IACzE,CAIO,SAAS0C,GAAgBuB,EAAiBlC,EAAqC,CACpF,IAAMnB,EAAgB,CAAC,EACjBsD,EAAUnC,GAAI,QAEpB,GADImC,GAAStD,EAAI,KAAKO,EAAI,WAAW+C,IAAWlE,EAAe,WAAW,CAAC,EACvEiE,GAAU,OAAOA,GAAW,UAAY,CAAC,MAAM,QAAQA,CAAM,EAC/D,OAAW,CAACE,EAAKC,CAAK,IAAK,OAAO,QAAQH,CAAiC,EAAG,CAC5E,IAAMI,EAAS,MAAM,QAAQD,CAAK,EAAIA,EAAM,IAAI,MAAM,EAAIA,GAAS,KAAO,CAAC,EAAI,CAAC,OAAOA,CAAK,CAAC,EACxFC,EAAO,QACZzD,EAAI,KAAKO,EAAI,GAAGgD,KAAOE,EAAO,KAAK,GAAG,IAAKrE,EAAe,WAAW,CAAC,EAG1E,OAAOY,EAAI,MAAM,EAAGZ,EAAe,OAAO,CAC5C,CAEA,SAASoB,GAAUkD,EAAoD,CACrE,GAAI,CAACA,EAAG,OAAO,KACf,IAAMC,EAAID,aAAa,KAAOA,EAAI,IAAI,KAAKA,CAAC,EAC5C,OAAO,OAAO,SAASC,EAAE,QAAQ,CAAC,EAAIA,EAAE,YAAY,EAAI,IAC1D,CAEA,SAASpD,EAAIiD,EAAeI,EAAuB,CACjD,OAAOJ,EAAM,QAAUI,EAAQJ,EAAQA,EAAM,MAAM,EAAGI,EAAQ,CAAC,EAAI,KACrE,CAEA,SAASnD,GAAO+C,EAA2BI,EAAmC,CAC5E,OAAOJ,IAAU,OAAY,OAAYjD,EAAIiD,EAAOI,CAAK,CAC3D,CChWA,OAAS,aAAAC,GAAW,gBAAAC,GAAc,cAAAC,GAAY,iBAAAC,OAAqB,KACnE,OAAS,WAAAC,OAAe,OAwBxB,SAASC,GAAUC,EAAsB,CACvC,OAAOC,GAAQC,GAAaF,CAAI,EAAG,aAAa,CAClD,CAEO,SAASG,EAAeH,EAA0B,CACvD,GAAI,CACF,IAAMI,EAAM,KAAK,MAAMC,GAAaN,GAAUC,CAAI,EAAG,OAAO,CAAC,EAC7D,MAAO,CACL,QAASI,GAAO,OAAOA,EAAI,SAAY,UAAYA,EAAI,QAAUA,EAAI,QAAU,CAAC,EAChF,GAAI,OAAOA,GAAK,YAAe,SAAW,CAAE,WAAYA,EAAI,UAAW,EAAI,CAAC,EAC5E,GAAIA,GAAK,QAAU,OAAOA,EAAI,QAAW,SAAW,CAAE,OAAQA,EAAI,MAAO,EAAI,CAAC,EAC9E,GAAI,OAAOA,GAAK,QAAW,SAAW,CAAE,OAAQA,EAAI,MAAO,EAAI,CAAC,EAChE,GAAI,MAAM,QAAQA,GAAK,MAAM,EAAI,CAAE,OAAQA,EAAI,OAAO,OAAQE,GAAe,OAAOA,GAAM,QAAQ,CAAE,EAAI,CAAC,CAC3G,CACF,MAAE,CACA,MAAO,CAAE,QAAS,CAAC,CAAE,CACvB,CACF,CAEO,SAASC,EAAgBP,EAAcQ,EAAyB,CACrEC,GAAUP,GAAaF,CAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EACjD,IAAMU,EAAOX,GAAUC,CAAI,EACrBW,EAAM,GAAGD,KAAQ,QAAQ,UAC/BE,GAAcD,EAAK,KAAK,UAAUH,EAAO,KAAM,CAAC,EAAI;AAAA,CAAI,EACxDK,GAAWF,EAAKD,CAAI,CACtB,CAGO,SAASI,GAAOd,EAAce,EAAaC,EAAaC,EAAc,KAAK,IAAI,EAAS,CAC7F,IAAMT,EAAQL,EAAeH,CAAI,EAC3BkB,EAAkC,CAAC,EACzC,OAAW,CAACZ,EAAGa,CAAC,IAAK,OAAO,QAAQX,EAAM,OAAO,EAAO,KAAK,MAAMW,CAAC,EAAIF,IAAKC,EAAQZ,CAAC,EAAIa,GAC1FD,EAAQH,CAAG,EAAIC,EAAM,YAAY,EACjCT,EAAgBP,EAAM,CAAE,GAAGQ,EAAO,QAAAU,CAAQ,CAAC,CAC7C,CAEO,SAASE,GAAapB,EAAcqB,EAAyB,CAClEd,EAAgBP,EAAM,CAAE,GAAGG,EAAeH,CAAI,EAAG,WAAYqB,CAAU,CAAC,CAC1E,CAIO,SAASC,GAAatB,EAAce,EAAaQ,EAAmBN,EAAc,KAAK,IAAI,EAAS,CACzG,IAAMT,EAAQL,EAAeH,CAAI,EAC3BwB,EAAO,IAAI,IAAID,CAAO,EACtBE,EAAiC,CAAC,EACxC,OAAW,CAACnB,EAAGa,CAAC,IAAK,OAAO,QAAQX,EAAM,QAAU,CAAC,CAAC,EAAOgB,EAAK,IAAIlB,CAAC,IAAGmB,EAAOnB,CAAC,EAAIa,GACtFM,EAAOV,CAAG,EAAI,IAAI,KAAKE,CAAG,EAAE,YAAY,EACxC,IAAMS,GAAUlB,EAAM,QAAU,CAAC,GAAG,OAAQF,GAAMA,IAAMS,GAAOS,EAAK,IAAIlB,CAAC,CAAC,EAC1EC,EAAgBP,EAAM,CAAE,GAAGQ,EAAO,OAAAiB,EAAQ,OAAAC,CAAO,CAAC,CACpD,CAIO,SAASC,GAAa3B,EAAce,EAAmB,CAC5D,IAAMP,EAAQL,EAAeH,CAAI,EACjCO,EAAgBP,EAAM,CAAE,GAAGQ,EAAO,OAAQ,CAAC,IAAIA,EAAM,QAAU,CAAC,GAAG,OAAQF,GAAMA,IAAMS,CAAG,EAAGA,CAAG,CAAE,CAAC,CACrG,CAGO,SAASa,GAAW5B,EAAciB,EAAc,KAAK,IAAI,EAAS,CACvEV,EAAgBP,EAAM,CAAE,GAAGG,EAAeH,CAAI,EAAG,OAAQ,CAAC,EAAG,OAAQ,IAAI,KAAKiB,CAAG,EAAE,YAAY,CAAE,CAAC,CACpG,CCvFA,OAAS,cAAAY,EAAY,eAAAC,GAAa,gBAAAC,OAAoB,KACtD,OAAS,WAAAC,MAAe,OCDxB,OAAS,eAAAC,OAAmB,SAC5B,OAAS,cAAAC,GAAY,aAAAC,GAAW,eAAAC,GAAa,gBAAAC,GAAc,cAAAC,GAAY,cAAAC,GAAY,iBAAAC,OAAqB,KACxG,OAAS,WAAAC,OAAe,OCSxB,SAASC,GAAKC,EAAmC,CAC/C,MAAO,CACL,GAAIA,EAAS,QAAU,CAAC,YAAaA,EAAS,OAAO,EAAI,CAAC,EAC1D,GAAIA,EAAS,MAAQ,CAAC,UAAWA,EAAS,KAAK,EAAI,CAAC,CACtD,CACF,CAEA,IAAMC,GAAaC,GAAsBA,EAAE,KAAK,EAAE,MAAM;AAAA,CAAI,EAAE,IAAI,GAAG,MAAM,EAAG,GAAG,GAAK,YAEtF,eAAsBC,GACpBH,EACAI,EACAC,EACAC,EAAeC,GACuC,CACtD,IAAM,EAAI,MAAMD,EAAKN,EAAS,IAAK,CAAC,GAAGD,GAAKC,CAAQ,EAAG,OAAQ,OAAQ,OAAQI,EAAI,YAAaC,EAAM,cAAc,EAAG,CAAE,UAAW,GAAO,CAAC,EAC5I,OAAO,EAAE,OAAS,EAAI,CAAE,GAAI,EAAK,EAAI,CAAE,GAAI,GAAO,MAAO,sBAAsBJ,GAAU,EAAE,QAAU,EAAE,MAAM,GAAI,CACnH,CAGA,eAAsBO,GACpBR,EACAS,EACAC,EACAC,EACAL,EAAeC,GACG,CAElB,OADU,MAAMD,EAAKN,EAAS,IAAK,CAAC,GAAGD,GAAKC,CAAQ,EAAG,cAAe,QAAS,WAAY,SAAUS,EAAM,OAAQC,EAAI,WAAYC,CAAM,EAAG,CAAE,UAAW,IAAQ,CAAC,GACzJ,OAAS,CACpB,CDDO,IAAMC,GAAY,IAEZC,GAA4BC,GAAMC,GAASD,EAAE,MAAOA,EAAE,GAAIA,EAAE,IAAI,EAEtE,SAASE,GAAUC,EAAsB,CAC9C,OAAOC,GAAQD,EAAM,UAAW,kBAAmB,QAAQ,CAC7D,CAEA,IAAME,GAAWC,GAAwB,oBAAoB,KAAKA,CAAE,EAC9DC,GAAU,CAACJ,EAAcG,IAAuBF,GAAQF,GAAUC,CAAI,EAAG,GAAGG,QAAS,EAE3F,SAASE,GAAKL,EAAcM,EAAyB,CACnDC,GAAUR,GAAUC,CAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EAC9C,IAAMQ,EAAOJ,GAAQJ,EAAMM,EAAM,EAAE,EAC7BG,EAAM,GAAGD,KAAQ,QAAQ,UAC/BE,GAAcD,EAAK,KAAK,UAAUH,EAAO,KAAM,CAAC,EAAI;AAAA,EAAM,CAAE,KAAM,GAAM,CAAC,EACzEK,GAAWF,EAAKD,CAAI,CACtB,CAEO,SAASI,GACdZ,EACAa,EACAC,EAAM,KAAK,IAAI,EACH,CACZ,IAAMC,EAAOF,EAAM,KAAK,KAAK,EAAE,MAAM,EAAGlB,EAAS,EACjD,GAAI,CAACoB,EAAM,MAAM,IAAI,MAAM,aAAa,EACxC,IAAMT,EAAoB,CACxB,GAAGO,EACH,KAAAE,EACA,GAAI,GAAGD,EAAI,SAAS,EAAE,KAAKE,GAAY,CAAC,EAAE,SAAS,KAAK,IACxD,OAAQ,UACR,WAAY,IAAI,KAAKF,CAAG,EAAE,YAAY,CACxC,EACA,OAAAT,GAAKL,EAAMM,CAAK,EACTA,CACT,CAEO,SAASW,GAAUjB,EAAcG,EAA+B,CACrE,GAAI,CAACD,GAAQC,CAAE,EAAG,OAAO,KACzB,GAAI,CAAE,OAAO,KAAK,MAAMe,GAAad,GAAQJ,EAAMG,CAAE,EAAG,OAAO,CAAC,CAAgB,MAAE,CAAQ,OAAO,IAAK,CACxG,CAEO,SAASgB,GAAWnB,EAAcoB,EAAoC,CAC3E,IAAMC,EAAMtB,GAAUC,CAAI,EAC1B,OAAKsB,GAAWD,CAAG,EACZE,GAAYF,CAAG,EACnB,OAAQG,GAAMA,EAAE,SAAS,OAAO,CAAC,EACjC,IAAKA,GAAMP,GAAUjB,EAAMwB,EAAE,MAAM,EAAG,EAAE,CAAC,CAAC,EAC1C,OAAQ3B,GAAuB,CAAC,CAACA,IAAM,CAACuB,GAAUvB,EAAE,SAAWuB,EAAO,EACtE,KAAK,CAACK,EAAGC,IAAMD,EAAE,WAAW,cAAcC,EAAE,UAAU,CAAC,EAL7B,CAAC,CAMhC,CAMA,eAAsBC,GACpB3B,EACAG,EACAyB,EACAC,EAAwD,CAAC,EACgB,CACzE,IAAMvB,EAAQW,GAAUjB,EAAMG,CAAE,EAChC,GAAI,CAACG,GAASA,EAAM,SAAW,UAAW,MAAO,CAAE,GAAI,GAAO,MAAO,mBAAmBH,eAAiB,EAEzG,IAAM2B,EAAQ,GAAG1B,GAAQJ,EAAMG,CAAE,KAAK,QAAQ,OAAOa,GAAY,CAAC,EAAE,SAAS,KAAK,UAClF,GAAI,CAAEL,GAAWP,GAAQJ,EAAMG,CAAE,EAAG2B,CAAK,CAAE,MAAE,CAC3C,MAAO,CAAE,GAAI,GAAO,MAAO,gBAAgB3B,6BAA+B,CAC5E,CACA,IAAM4B,EAAsB,CAC1B,GAAGzB,EACH,WAAYuB,EAAK,IAAM,WACvB,WAAY,IAAI,KAAKA,EAAK,KAAO,KAAK,IAAI,CAAC,EAAE,YAAY,CAC3D,EACA,GAAID,IAAY,KACdG,EAAQ,OAAS,eACZ,CACL,IAAMC,EAAI,MAAOH,EAAK,MAAQjC,IAAeU,CAAK,EAAE,MAAO2B,IAAY,CAAE,GAAI,GAAgB,MAAO,OAAOA,GAAG,SAAWA,CAAC,CAAE,EAAE,EAC1HD,EAAE,GAAID,EAAQ,OAAS,QACpBA,EAAQ,OAAS,SAAUA,EAAQ,MAAQC,EAAE,OAEtD3B,GAAKL,EAAM+B,CAAO,EAClB,GAAI,CAAEG,GAAWJ,CAAK,CAAE,MAAE,CAA2B,CACrD,OAAOC,EAAQ,SAAW,SAAW,CAAE,GAAI,GAAO,MAAOA,EAAQ,OAAS,aAAc,EAAI,CAAE,GAAI,GAAM,MAAOA,CAAQ,CACzH,CDlHAI,KACAC,KGTAC,KAkBO,IAAMC,GAA6C,CAAC,OAAQ,SAAS,EA+DtEC,GAAa,IAEZ,SAASC,GAAiBC,EAA6B,CAC5DA,EAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAgDP,CACH,CAEO,SAASC,GAAcD,EAAgC,CAC5D,MAAO,CAAC,CAACA,EAAG,QAAQ,6EAA6E,EAAE,IAAI,CACzG,CAEA,SAASE,GAAOC,EAAoB,CAClC,MAAO,CACL,UAAWA,EAAE,WAAY,UAAWA,EAAE,WAAY,UAAWA,EAAE,WAAY,MAAOA,EAAE,MACpF,WAAYA,EAAE,aAAe,KAAM,WAAYA,EAAE,aAAe,IAClE,CACF,CAEA,SAASC,GAAOD,EAAkB,CAChC,MAAO,CACL,UAAWA,EAAE,WAAY,IAAKA,EAAE,IAAK,KAAMA,EAAE,KAAM,KAAMA,EAAE,KAAM,QAASA,EAAE,SAAU,SAAUA,EAAE,UAClG,MAAOA,EAAE,MAAO,cAAe,CAAC,CAACA,EAAE,eAAgB,SAAUA,EAAE,SAAU,OAAQA,EAAE,SAAW,KAC9F,QAASA,EAAE,SAAW,KAAM,UAAWA,EAAE,YAAc,KAAM,OAAQA,EAAE,SAAW,KAAM,UAAWA,EAAE,YAAc,KACnH,SAAUA,EAAE,WAAa,KAAM,cAAeA,EAAE,gBAAkB,KAAM,aAAcA,EAAE,eAAiB,KACzG,SAAUA,EAAE,UAAY,KAAM,KAAMA,EAAE,MAAQ,KAAM,aAAcA,EAAE,eAAiB,KAAM,UAAWA,EAAE,WACxG,OAAQA,EAAE,OAAQ,YAAaA,EAAE,eAAiB,KAAM,WAAYA,EAAE,aAAe,IACvF,CACF,CAGA,IAAME,GAAuC,CAC3C,MAAO,QAAS,SAAU,WAAY,OAAQ,UAAW,QAAS,UAAW,SAAU,WAAY,KAAM,OACzG,aAAc,gBAAiB,UAAW,aAAc,OAAQ,SAAU,YAAa,gBAAiB,WAAY,aACtH,EAKaC,GAAN,KAAgB,CACrB,YAAoBN,EAAuB,CAAvB,QAAAA,EAClBD,GAAiBC,CAAE,CACrB,CAKA,OAAOO,EAAmBC,EAAmBC,EAAkBC,EAAmB,CAChF,IAAMC,EAAa,KAAK,GAAG,QACzB,kGACF,EACMC,EAAa,KAAK,GAAG,QACzB;AAAA;AAAA,kEAGF,EACA,KAAK,GAAG,YAAY,IAAM,CACxBD,EAAW,IAAIJ,EAAWC,EAAWE,CAAG,EACxCD,EAAM,QAAQ,CAACI,EAAGC,IAAM,CACtB,IAAMC,EAAQD,IAAM,EAGdE,EAAMD,GAASF,EAAE,UAAYL,GAAaK,EAAE,OAAS,UAC3DD,EAAW,IACTL,EAAWO,EAAI,EAAGD,EAAE,KAAMA,EAAE,KAAMA,EAAE,QAASA,EAAE,SAAUE,EAAQ,SAAW,UAAWF,EAAE,cAAgB,EAAI,EAC7GA,EAAE,SAAW,KAAMA,EAAE,WAAa,KAAMA,EAAE,QAAU,KAAMA,EAAE,WAAa,KAAMA,EAAE,UAAY,KAC7FA,EAAE,eAAiB,KAAMA,EAAE,cAAgB,KAAMG,EAAMN,EAAM,KAAMA,CACrE,CACF,CAAC,EACD,KAAK,MAAMH,EAAW,KAAM,UAAW,GAAGE,EAAM,4BAA4BD,IAAaE,CAAG,CAC9F,CAAC,EAAE,CACL,CAEA,IAAIH,EAAsC,CACxC,IAAMU,EAAM,KAAK,GAAG,QAAQ,kDAAkD,EAAE,IAAIV,CAAS,EAC7F,OAAOU,EAAMf,GAAOe,CAAG,EAAI,IAC7B,CAEA,MAAMV,EAA+B,CACnC,OAAO,KAAK,GAAG,QAAQ,oEAAoE,EAAE,IAAIA,CAAS,EAAE,IAAIH,EAAM,CACxH,CAEA,KAAKG,EAAmBW,EAA8B,CACpD,IAAMD,EAAM,KAAK,GAAG,QAAQ,mEAAmE,EAAE,IAAIV,EAAWW,CAAG,EACnH,OAAOD,EAAMb,GAAOa,CAAG,EAAI,IAC7B,CAGA,YAA2B,CACzB,OAAO,KAAK,GAAG,QAAQ,+EAA+E,EAAE,IAAI,EAAE,IAAIf,EAAM,CAC1H,CAEA,WAAWK,EAAmBW,EAAaC,EAAwB,CACjE,IAAMC,EAAiB,CAAC,EAClBC,EAAoB,CAAC,EAC3B,OAAW,CAACC,EAAKC,CAAK,IAAK,OAAO,QAAQJ,CAAK,EAAG,CAChD,IAAMK,EAAMnB,GAAaiB,CAAG,EACxB,CAACE,GAAOD,IAAU,SACtBH,EAAK,KAAK,GAAGI,OAAS,EACtBH,EAAO,KAAK,OAAOE,GAAU,UAAaA,EAAQ,EAAI,EAAKA,CAAK,GAE7DH,EAAK,QACV,KAAK,GAAG,QAAQ,iCAAiCA,EAAK,KAAK,IAAI,oCAAoC,EAAE,IAAI,GAAGC,EAAQd,EAAWW,CAAG,CACpI,CAIA,OAAOX,EAAmBkB,EAAqCf,EAAsB,CACnF,OAAO,KAAK,GAAG,QACb,+FACF,EAAE,IAAIe,EAAOf,EAAKH,CAAS,EAAE,QAAU,CACzC,CAEA,aAAaA,EAAmBG,EAA0B,CACxD,KAAK,GAAG,QAAQ,+DAA+D,EAAE,IAAIA,EAAKH,CAAS,CACrG,CAGA,iBAAgC,CAC9B,OAAO,KAAK,GAAG,QAAQ,+FAA+F,EAAE,IAAI,EAAE,IAAIL,EAAM,CAC1I,CAGA,MAAMK,EAAmBmB,EAAqBC,EAAcC,EAAgBlB,EAAmB,CAC7F,KAAK,GAAG,QAAQ,6FAA6F,EAC1G,IAAIH,EAAWmB,EAAMhB,EAAKiB,EAAMC,EAAO,QAAQ,OAAQ,GAAG,EAAE,KAAK,EAAE,MAAM,EAAG9B,EAAU,CAAC,CAC5F,CAEA,OAAOS,EAAmBsB,EAAQ,IAAkB,CAClD,OAAQ,KAAK,GAAG,QACd,oHACF,EAAE,IAAItB,EAAWsB,CAAK,EAAY,IAAK1B,IAAO,CAAE,UAAWA,EAAE,WAAY,KAAMA,EAAE,MAAQ,KAAM,GAAIA,EAAE,GAAI,KAAMA,EAAE,KAAM,OAAQA,EAAE,MAAO,EAAE,CAC5I,CACF,EAEa2B,GAAgBC,GCvQ7B,OAAS,cAAAC,GAAY,gBAAAC,OAAoB,KAEzCC,KACAC,KAUO,SAASC,GAAoBC,EAAsC,CACxE,IAAMC,EAAOC,GAAeF,CAAU,EAClCG,EAAW,CAAC,EAChB,GAAIC,GAAWH,CAAI,EACjB,GAAI,CAAEE,EAAME,GAAc,KAAK,MAAMC,GAAaL,EAAM,OAAO,CAAC,CAAC,CAAE,MAAE,CAAQE,EAAM,CAAC,CAAE,CAExF,IAAMI,EAASC,GAAqB,UAAUL,GAAK,QAAQ,EAC3D,OAAOI,EAAO,QAAUA,EAAO,KAAOC,GAAqB,MAAM,MAAS,CAC5E,CAQA,eAAsBC,GACpBC,EACAC,EAAkD,CAAC,EAC1B,CAGzB,IAAMC,GAAWF,EAAM,UAAY,CAAC,GAAG,OAAQG,GAAM,CAACC,GAAe,IAAID,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,EACvG,OAAID,EAAQ,OACH,CAAE,QAAS,GAAO,MAAO,mBAAmBA,EAAQ,IAAKC,GAAM,IAAIA,IAAI,EAAE,KAAK,IAAI,UAAU,CAAC,GAAGC,EAAc,EAAE,KAAK,IAAI,GAAI,EAE/HC,GAAqBC,GAAQ,CAClC,IAAM,EAAKA,EAAI,WAAa,CAAC,EAC7B,QAAWC,IAAO,CAAC,UAAW,WAAY,OAAQ,kBAAmB,eAAe,EAC9EP,EAAMO,CAAG,IAAM,SAAW,EAAEA,CAAG,EAAIP,EAAMO,CAAG,GAElD,GAAIP,EAAM,MAAO,CACf,IAAMQ,EAAK,EAAE,QAAU,CAAC,EACxB,QAAWD,IAAO,CAAC,UAAW,eAAgB,YAAa,eAAgB,iBAAkB,iBAAkB,oBAAoB,EAC7HP,EAAM,MAAMO,CAAG,IAAM,SAAWC,EAAED,CAAG,EAAIP,EAAM,MAAMO,CAAG,GAGhE,GAAIP,EAAM,SAAU,CAClB,IAAMG,EAAK,EAAE,WAAa,CAAC,EAC3B,QAAWI,IAAO,CAAC,UAAW,mBAAmB,EAC3CP,EAAM,SAASO,CAAG,IAAM,SAAWJ,EAAEI,CAAG,EAAIP,EAAM,SAASO,CAAG,GAGxE,EAAG,CAAE,WAAYN,EAAK,WAAY,OAAQA,EAAK,MAAO,CAAC,CACzD,CJtBO,IAAMQ,GAA0C,CAAC,OAAQ,WAAY,SAAU,OAAQ,WAAY,SAAS,EAoDtGC,GAAa,IAE1B,SAASC,EAAKC,EAAWC,EAAIH,GAAoB,CAC/C,IAAMI,EAAOF,EAAE,QAAQ,OAAQ,GAAG,EAAE,KAAK,EACzC,OAAOE,EAAK,OAASD,EAAI,GAAGC,EAAK,MAAM,EAAGD,EAAI,CAAC,UAAOC,CACxD,CAEA,SAASC,GAAcC,EAA2B,CAChD,GAAIA,EAAI,WAAY,OAAOC,EAAQD,EAAI,UAAU,EACjD,IAAME,EAAa,CAACD,EAAQD,EAAI,KAAM,aAAa,EAAGC,EAAQD,EAAI,KAAM,UAAW,aAAa,CAAC,EACjG,OAAOE,EAAW,KAAMC,GAAMC,EAAWD,CAAC,CAAC,GAAKD,EAAW,CAAC,CAC9D,CAEA,SAASG,GAAWL,EAA2B,CAC7C,OAAOA,EAAI,QAAUC,EAAQD,EAAI,OAAO,EAAIC,EAAQD,EAAI,KAAM,UAAW,MAAM,CACjF,CAIA,SAASM,GAAUN,EAAgC,CACjD,OAAOO,EAAUP,EAAI,KAAM,SAAS,EAAE,IAAIQ,EAAQ,CACpD,CAEA,SAASA,GAASL,EAA4B,CAC5C,MAAO,CACL,IAAK,QAAQA,EAAE,KACf,KAAM,OACN,MAAOA,EAAE,MACT,IAAKA,EAAE,IACP,IAAK,QAAQA,EAAE,gBACf,GAAI,QAAQA,EAAE,eACd,UAAWA,EAAE,UACb,UAAWA,EAAE,UACb,QAASA,EAAE,QACX,GAAIA,EAAE,OAAS,CAAE,OAAQA,EAAE,MAAO,EAAI,CAAC,EACvC,UAAWA,EAAE,UACb,GAAIA,EAAE,KAAO,CAAE,KAAMA,EAAE,IAAK,EAAI,CAAC,EACjC,WAAYA,EAAE,WACd,GAAIA,EAAE,QAAU,CAAE,QAASA,EAAE,QAAS,KAAM,+BAA+BA,EAAE,IAAK,EAAI,CAAC,EACvF,GAAIA,EAAE,MAAQ,CAAE,MAAOA,EAAE,KAAM,EAAI,CAAC,EACpC,GAAIA,EAAE,SAAWA,EAAE,MAAQ,CAAE,OAAQR,EAAK,CAACQ,EAAE,QAASA,EAAE,MAAQ,sBAAsBA,EAAE,QAAU,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,QAAK,CAAC,CAAE,EAAI,CAAC,CAC1I,CACF,CAEA,SAASM,GAAcT,EAAgC,CACrD,IAAMU,EAAOX,GAAcC,CAAG,EAC9B,GAAI,CAACI,EAAWM,CAAI,EAAG,MAAO,CAAC,EAC/B,IAAIC,EACJ,GAAI,CAAEA,EAAM,KAAK,MAAMC,GAAaF,EAAM,OAAO,CAAC,CAAE,MAAE,CAAQ,MAAO,CAAC,CAAE,CACxE,IAAMG,EAAM,IAAI,KAAKb,EAAI,KAAO,KAAK,IAAI,CAAC,EACpCc,EAAmB,CAAC,EAC1B,OAAW,CAACC,EAAIC,CAAG,IAAK,OAAO,QAAaL,GAAK,OAAS,CAAC,CAAC,EAAG,CAC7D,IAAMM,EAAWD,GAAK,SACtB,GAAI,CAACC,GAAaA,EAAS,SAAW,UAAYA,EAAS,SAAW,SAAW,SACjF,IAAMC,EAAKF,EAAI,UAAY,MACrBG,EAAQH,EAAI,SAAWI,GAAaJ,EAAI,QAAQ,EAAI,GACpDK,EAAOL,EAAI,SAAW,GAAGG,EAAM,OAAO,CAAC,EAAE,YAAY,IAAIA,EAAM,MAAM,CAAC,MAAMH,EAAI,YAAc,cAC9FM,EAAO,OAAON,EAAI,QAAUA,EAAI,SAAW,EAAE,EAC7CO,EAAWN,EAAS,SAAW,SACrCH,EAAI,KAAK,CACP,IAAK,YAAYC,IACjB,KAAM,WACN,MAAOQ,EAAW,iBAAiBR,UAAWC,EAAI,OAAS,aAAe,oBAAoBD,KAC9F,IAAKQ,EAAW,OAAOP,EAAI,OAAS,wBAAwBK,KAAU,wBAAwBN,OAAQM,MACtG,IAAKE,EAAW,YAAc,YAC9B,GAAIA,EAAW,mBAAqB,oBACpC,UAAW,OAAON,EAAS,aAAe,SAAS,EACnD,WAAY,OAAOA,EAAS,aAAe,EAAE,EAC7C,OAAQtB,EAAK,GAAG2B,IAAOC,GAAYP,EAAI,SAAW,mBAAgBQ,GAAeC,GAAaT,EAAI,SAAUE,EAAIL,CAAG,EAAGK,CAAE,IAAM,IAAI,EAClI,KAAM,sBACR,CAAC,EAEH,OAAOJ,CACT,CAEA,SAASY,GAAaC,EAAwB,CAC5C,IAAMC,EAAM3B,EAAQ0B,EAAM,UAAW,QAAQ,EAC7C,OAAKvB,EAAWwB,CAAG,EACZC,GAAYD,CAAG,EAAE,OAAQE,GAAMA,EAAE,SAAS,QAAQ,CAAC,EAAE,IAAKA,GAAMA,EAAE,MAAM,EAAG,EAAE,CAAC,EAAE,KAAK,EAD/D,CAAC,CAEhC,CAEA,SAASC,GAAY/B,EAAgC,CACnD,IAAMgC,EAASN,GAAa1B,EAAI,IAAI,EACpC,GAAIgC,EAAO,SAAW,EAAG,MAAO,CAAC,EACjC,IAAMC,EAAQ,IAAIC,GAAYlC,EAAI,IAAI,EAChCc,EAAmB,CAAC,EAC1B,QAAWqB,KAASH,EAClB,QAAWF,KAAKG,EAAM,KAAKE,CAAK,EAC9BrB,EAAI,KAAK,CACP,IAAK,UAAUqB,KAASL,EAAE,KAC1B,KAAM,SACN,MAAO,QAAQK,kBAAsBL,EAAE,OAAO,UAC9C,IAAK,OAAOK,kCACZ,IAAK,SACL,GAAI,uBACJ,UAAWA,EACX,WAAYL,EAAE,UACd,OAAQnC,EAAKmC,EAAE,OAAO,EACtB,KAAM,oCAAoCK,GAC5C,CAAC,EAGL,OAAOrB,CACT,CAKO,SAASsB,GAAgBC,EAA0C,CACxE,IAAMC,EAAWD,EAAE,QAAQ,OAAQzC,GAAMA,EAAE,OAAO,EAClD,OAAI0C,EAAS,OACJA,EAAS,IAAK1C,GACnB,sBAAsBA,EAAE,aAAaA,EAAE,QAAS,oBAAoBA,EAAE,QAAS,kBAAkBA,EAAE,aAAe,eAAeA,EAAE,QAAS,aAC9I,EAAE,KAAK,GAAG,EAEL,GAAGyC,EAAE,QAAQ,mBAAmBA,EAAE,YAAc,EAAI,aAAaA,EAAE,uBAAyB,KACrG,CAEA,SAASE,GAAUvC,EAAgC,CACjD,OAAOwC,GAAcnC,GAAWL,CAAG,EAAG,SAAS,EAAE,IAAKyC,GAAM,CAC1D,IAAMJ,EAAII,EAAE,SACZ,MAAO,CACL,IAAK,QAAQA,EAAE,KACf,KAAM,OACN,MAAOA,EAAE,QAAQ,MACjB,IAAKA,EAAE,SAAW,WAAWA,EAAE,QAAQ,4BAA8B,QAAQA,EAAE,QAAQ,4BACvF,IAAK,oBACL,GAAI,aACJ,UAAWJ,EAAE,OAAO,KAAK,IAAI,GAAK,eAClC,WAAYI,EAAE,UACd,OAAQ9C,EAAK,GAAGyC,GAAgBC,CAAC,KAAKI,EAAE,QAAQ,SAAS,EACzD,KAAM,8BAA8BA,EAAE,IACxC,CACF,CAAC,CACH,CAEA,SAASC,GAAc1C,EAAgC,CACrD,OAAO2C,GAAW3C,EAAI,KAAM,SAAS,EAAE,IAAK4C,IAAO,CACjD,IAAK,YAAYA,EAAE,KACnB,KAAM,WACN,MAAO,qBAAqBA,EAAE,WAAaA,EAAE,KAE7C,IAAK,gBAAgBA,EAAE,WAAaA,EAAE,QAAQA,EAAE,QAChD,IAAK,sBACL,GAAI,iBACJ,UAAWA,EAAE,MACb,WAAYA,EAAE,WACd,OAAQjD,EAAK,GAAGiD,EAAE,WAAWA,EAAE,SAAS,EACxC,KAAM,4BACR,EAAE,CACJ,CAGO,SAASC,EAAgB7C,EAAwC,CACtE,GAAIA,EAAI,SAAU,OAAOA,EAAI,SAG7B,GAAIC,EAAQD,EAAI,IAAI,IAAM,QAAQ,IAAI,GAAK,CAACI,EAAWH,EAAQD,EAAI,KAAM,UAAW,WAAW,CAAC,EAAG,OAAO,KAC1G,IAAM8C,EAAKC,GAAO,CAAE,MAAO,EAAK,CAAC,EACjC,GAAI,CAACD,EAAI,OAAO,KAChB,IAAIb,EAAQe,GAAc,IAAIF,CAAE,EAEhC,MAAI,CAACb,GAAS,CAACgB,GAAiBH,CAAE,EAAU,MACvCb,GAAOe,GAAc,IAAIF,EAAIb,EAAQ,IAAIiB,GAAaJ,CAAE,CAAC,EACvDb,EACT,CACA,IAAMe,GAAgB,IAAI,QAInB,SAASG,GAAanD,EAAqC,CAChE,GAAIA,EAAI,MAAO,OAAOA,EAAI,MAC1B,GAAIA,EAAI,UAAYC,EAAQD,EAAI,IAAI,IAAM,QAAQ,IAAI,GAAK,CAACI,EAAWH,EAAQD,EAAI,KAAM,UAAW,WAAW,CAAC,EAAG,OAAO,KAC1H,IAAM8C,EAAKC,GAAO,CAAE,MAAO,EAAK,CAAC,EACjC,GAAI,CAACD,GAAM,CAACM,GAAcN,CAAE,EAAG,OAAO,KACtC,IAAIb,EAAQoB,GAAW,IAAIP,CAAE,EAC7B,OAAKb,GAAOoB,GAAW,IAAIP,EAAIb,EAAQ,IAAIqB,GAAUR,CAAE,CAAC,EACjDb,CACT,CACA,IAAMoB,GAAa,IAAI,QAEvB,SAASE,GAAavD,EAAgC,CACpD,IAAMiC,EAAQY,EAAgB7C,CAAG,EACjC,OAAKiC,EACEA,EAAM,YAAY,iBAAiB,EAAE,IAAKuB,IAAO,CACtD,IAAK,WAAWA,EAAE,KAClB,KAAM,UACN,MAAO,yBAAyB7D,EAAK6D,EAAE,KAAM,EAAE,IAC/C,IAAK,OAAOA,EAAE,+BACd,IAAK,mBAAmBA,EAAE,UAC1B,GAAI,mBACJ,UAAWA,EAAE,QACb,WAAY,IAAI,KAAKA,EAAE,SAAS,EAAE,YAAY,EAC9C,OAAQ7D,EAAK,GAAG6D,EAAE,iBAAmB,kCAAkCA,EAAE,YAAYA,EAAE,MAAM,EAC7F,KAAM,wBAAwBA,EAAE,IAClC,EAAE,EAZiB,CAAC,CAatB,CAEA,IAAMC,GAAoE,CACxE,KAAMnD,GACN,SAAUG,GACV,OAAQsB,GACR,KAAMQ,GACN,SAAUG,GACV,QAASa,EACX,EAaO,SAASG,GAAUC,EAAcC,EAAsB,CAC5D,IAAMC,EAAKF,EAAE,QAAU,KAAK,MAAMA,EAAE,OAAO,EAAI,IACzCG,EAAKF,EAAE,QAAU,KAAK,MAAMA,EAAE,OAAO,EAAI,IAC/C,OAAIC,IAAOC,EAAWD,EAAKC,GACnB,KAAK,MAAMH,EAAE,UAAU,GAAK,IAAM,KAAK,MAAMC,EAAE,UAAU,GAAK,EACxE,CAEO,SAASG,GAAU/D,EAAmBgE,EAA6D,CAAC,EAAiB,CAC1H,IAAMnD,EAAMb,EAAI,KAAO,KAAK,IAAI,EAC1B,CAAE,QAAAiE,CAAQ,EAAIC,EAAelE,EAAI,IAAI,EACrCmE,EAAqB,CAAC,EACtBC,EAAiC,CAAC,EACpCC,EAAS,EACb,QAAWC,KAAQN,EAAK,OAASvE,GAAgB,CAC/C,IAAI8E,EACJ,GAAI,CAAEA,EAAQd,GAAQa,CAAI,EAAEtE,CAAG,CAAE,OAASqC,EAAP,CACjC+B,EAAO,KAAK,CAAE,KAAAE,EAAM,MAAO,OAAOjC,GAAG,SAAWA,CAAC,EAAE,MAAM,EAAG,GAAG,CAAE,CAAC,EAClE,QACF,CACA,QAAWmC,KAAQD,EAAO,CACxB,IAAME,EAAQR,EAAQO,EAAK,GAAG,EAC9B,GAAIC,GAAS,KAAK,MAAMA,CAAK,EAAI5D,EAAK,CACpC,GAAI,CAACmD,EAAK,eAAgB,CAAEK,IAAU,SACtCG,EAAK,cAAgBC,EAEvBN,EAAM,KAAKK,CAAI,GAGnB,OAAAL,EAAM,KAAKT,EAAS,EACb,CAAE,MAAAS,EAAO,QAASE,EAAQ,OAAAD,CAAO,CAC1C,CAoBO,SAASM,GAASC,EAAyD,CAChF,IAAMC,EAAID,EAAI,QAAQ,GAAG,EACzB,GAAIC,GAAK,EAAG,OAAO,KACnB,IAAMN,EAAOK,EAAI,MAAM,EAAGC,CAAC,EACrBC,EAAMF,EAAI,MAAMC,EAAI,CAAC,EAC3B,MAAI,CAACnF,GAAe,SAAS6E,CAAI,GAAK,CAACO,EAAY,KAC5C,CAAE,KAAAP,EAAM,IAAAO,CAAI,CACrB,CAGA,SAASC,GAAa9E,EAAmBsE,EAAoBO,EAAsB,CACjF,OAAQP,EAAM,CACZ,IAAK,OAAQ,OAAOS,EAAS/E,EAAI,KAAM6E,CAAG,GAAG,SAAW,UACxD,IAAK,OAAQ,OAAOG,GAAa3E,GAAWL,CAAG,EAAG6E,CAAG,GAAG,SAAW,UACnE,IAAK,WAAY,OAAOI,GAAUjF,EAAI,KAAM6E,CAAG,GAAG,SAAW,UAC7D,QAAS,OAAOpB,GAAQa,CAAI,EAAEtE,CAAG,EAAE,KAAM,GAAM,EAAE,MAAQ,GAAGsE,KAAQO,GAAK,CAC3E,CACF,CAMA,eAAsBK,GAAOlF,EAAmB2E,EAAaQ,EAAqBnB,EAAsB,CAAC,EAA0B,CACjI,IAAMoB,EAASV,GAASC,CAAG,EAC3B,GAAI,CAACS,EAAQ,MAAO,CAAE,GAAI,GAAO,MAAO,iBAAiBT,8CAAiD,EAC1G,GAAM,CAAE,KAAAL,EAAM,IAAAO,CAAI,EAAIO,EAChBvE,EAAMb,EAAI,KAAO,KAAK,IAAI,EAC1BqF,EAAKrB,EAAK,IAAM,WAEtB,GAAImB,IAAW,QAAS,CACtB,GAAI,CAACL,GAAa9E,EAAKsE,EAAMO,CAAG,EAAG,MAAO,CAAE,GAAI,GAAO,MAAO,wBAAwBF,IAAO,EAC7F,IAAMW,EAAQtB,EAAK,YAAcA,EAAK,WAAa,EAAIA,EAAK,WAAa,GACnES,EAAQ,IAAI,KAAK5D,EAAMyE,EAAQ,IAAS,EAC9C,OAAAC,GAAOvF,EAAI,KAAM2E,EAAKF,EAAO5D,CAAG,EACzB,CAAE,GAAI,GAAM,QAAS,GAAG8D,mBAAqBF,EAAM,YAAY,GAAI,EAG5E,IAAMe,EAAML,IAAW,MACvB,OAAQb,EAAM,CACZ,IAAK,OAAQ,CACX,IAAMd,EAAIiC,GAAWzF,EAAI,KAAM6E,EAAKW,EAAM,MAAQ,KAAM,CAAE,GAAAH,EAAI,KAAMrB,EAAK,KAAM,IAAAnD,EAAK,OAAQmD,EAAK,OAAQ,KAAMA,EAAK,IAAK,CAAC,EAC1H,OAAKR,EAAE,GACA,CAAE,GAAI,GAAM,QAAS,GAAGmB,MAAQnB,EAAE,KAAK,0BAA0BgC,EAAM,MAAQ,OAAOhC,EAAE,KAAK,OAAS,KAAKA,EAAE,KAAK,UAAY,IAAK,EADxHA,CAEpB,CACA,IAAK,WAAY,CACf,IAAMA,EAAI,MAAOgC,EAAME,GAAkBC,IAAgBd,EAAK,CAAE,WAAY9E,GAAcC,CAAG,EAAG,OAAQA,EAAI,MAAO,CAAC,EACpH,OAAOwD,EAAE,QAAU,CAAE,GAAI,GAAM,QAASA,EAAE,OAAQ,EAAI,CAAE,GAAI,GAAO,MAAOA,EAAE,OAAQ,CACtF,CACA,IAAK,SAAU,CACb,IAAMoC,EAAQf,EAAI,QAAQ,GAAG,EAC7B,GAAIe,GAAS,EAAG,MAAO,CAAE,GAAI,GAAO,MAAO,gDAAiD,EAC5F,IAAMzD,EAAQ0C,EAAI,MAAM,EAAGe,CAAK,EAC1B7E,EAAK8D,EAAI,MAAMe,EAAQ,CAAC,EAC9B,GAAI,CAAClE,GAAa1B,EAAI,IAAI,EAAE,SAASmC,CAAK,EAAG,MAAO,CAAE,GAAI,GAAO,MAAO,wBAAwBA,IAAS,EACzG,IAAMF,EAAQ,IAAIC,GAAYlC,EAAI,IAAI,EACtC,OAAKiC,EAAM,KAAKE,CAAK,EAAE,KAAML,GAAMA,EAAE,KAAOf,CAAE,GAC9CkB,EAAM,OAAOE,EAAOpB,EAAIyE,EAAM,WAAa,WAAYH,CAAE,EAClD,CAAE,GAAI,GAAM,QAAS,GAAGV,KAAOa,EAAM,iCAAmC,qCAAsC,GAF7D,CAAE,GAAI,GAAO,MAAO,SAASzE,6BAA+B,CAGtH,CACA,IAAK,OAAQ,CACX,IAAMa,EAAMvB,GAAWL,CAAG,EACpBwD,EAAIgC,EACNK,GAAgBjE,EAAKiD,EAAK,CAAE,GAAAQ,EAAI,MAAOrB,EAAK,MAAO,IAAAnD,CAAI,CAAC,EACxDiF,GAAelE,EAAKiD,EAAK,CAAE,GAAAQ,EAAI,OAAQrB,EAAK,KAAM,IAAAnD,CAAI,CAAC,EAC3D,OAAK2C,EAAE,GACA,CAAE,GAAI,GAAM,QAASgC,EAAM,GAAGhC,EAAE,SAAS,QAAQ,kCAAoC,GAAGmB,YAAe,EAD5FnB,CAEpB,CACA,IAAK,WAAY,CACf,IAAMA,EAAI,MAAMuC,GAAY/F,EAAI,KAAM6E,EAAKW,EAAM,MAAQ,KAAM,CAAE,GAAAH,EAAI,IAAAxE,EAAK,KAAMb,EAAI,YAAa,CAAC,EAClG,OAAKwD,EAAE,GACA,CAAE,GAAI,GAAM,QAASgC,EAAM,GAAGb,cAAgBnB,EAAE,MAAM,WAAaA,EAAE,MAAM,KAAO,GAAGmB,0BAA6B,EADvGnB,CAEpB,CACA,IAAK,UAAW,CACd,IAAMvB,EAAQY,EAAgB7C,CAAG,EAC3BwD,EAAIvB,GAAO,IAAI4C,CAAG,EACxB,MAAI,CAAC5C,GAAS,CAACuB,GAAKA,EAAE,QAAU,kBAA0B,CAAE,GAAI,GAAO,MAAO,wBAAwBmB,IAAO,EACzGa,EAEGQ,GAAoBjG,GAAcC,CAAG,CAAC,EAAE,SAG7CiC,EAAM,cAAc4C,EAAKhE,CAAG,EACrB,CAAE,GAAI,GAAM,QAAS,GAAG8D,MAAQnB,EAAE,2CAA4C,GAH5E,CAAE,GAAI,GAAO,MAAO,iIAAkI,GAKjKvB,EAAM,MAAM4C,EAAK,UAAWb,EAAK,MAAM,KAAK,GAAK,cAAcqB,IAAMxE,CAAG,EACjE,CAAE,GAAI,GAAM,QAAS,GAAG8D,YAAe,EAChD,CACF,CACF,CK9ZA,IAAMsB,GAAW,IAAI,IAAI,CAAC,YAAa,MAAO,kBAAkB,CAAC,EAO1D,SAASC,GAAgBC,EAAuB,CACrD,OAAOA,IAAS,eAAiBA,EAAK,WAAW,cAAc,GAG7DA,IAAS,cAAgBA,EAAK,WAAW,aAAa,GAEtDA,IAAS,iBACTA,IAAS,aAAeA,EAAK,WAAW,YAAY,GAEpDA,IAAS,aAAeA,EAAK,WAAW,YAAY,GAEpDA,IAAS,cAAgBA,EAAK,WAAW,aAAa,GAEtDA,IAAS,kBAAoB,4BAA4B,KAAKA,CAAI,GAElEA,IAAS,oBAAsBA,EAAK,WAAW,mBAAmB,GAElEA,IAAS,WAAaA,EAAK,WAAW,UAAU,GAGhDA,IAAS,kBAAoBA,EAAK,WAAW,iBAAiB,GAG9DA,IAAS,qBAAuBA,IAAS,gBAGzCA,IAAS,cAGTA,IAAS,UAAYA,EAAK,WAAW,SAAS,GAI9CA,IAAS,kBAAoBA,EAAK,WAAW,iBAAiB,GAG9DA,IAAS,gBAAkBA,EAAK,WAAW,eAAe,GAG1DA,IAAS,gBAAkBA,IAAS,iBACpCA,IAAS,kBAAoBA,IAAS,iBAGtCA,IAAS,eAETA,IAAS,oBAAsBA,IAAS,4BAGxCA,IAAS,wBAA0BA,EAAK,WAAW,uBAAuB,GAE1EA,IAAS,0BACb,CAMO,SAASC,GAAcD,EAAuB,CACnD,OAAOA,IAAS,WAAaA,IAAS,uBACpCA,IAAS,SAAWA,IAAS,eAAiBA,IAAS,iBAEvDA,IAAS,kBAETA,IAAS,mBAAqBA,IAAS,kBACvC,2CAA2C,KAAKA,CAAI,GAEpD,qDAAqD,KAAKA,CAAI,GAC9D,4BAA4B,KAAKA,CAAI,GAErC,mCAAmC,KAAKA,CAAI,GAE5CA,IAAS,qBAAuBA,IAAS,uBAAyBA,IAAS,qBAE3EA,IAAS,yBACb,CAIO,SAASE,GAAWC,EAAgC,CACzD,OAAOL,GAAS,IAAIK,CAAa,CACnC,CAEO,SAASC,GAAeC,EAAwC,CAGrE,GAAIA,EAAI,eAAgB,MAAO,CAAE,QAAS,GAAO,OAAQ,wBAAyB,EAClF,GAAIA,EAAI,oBAAqB,MAAO,CAAE,QAAS,GAAM,OAAQ,UAAW,EACxE,GAAIP,GAAS,IAAIO,EAAI,aAAa,EAAG,MAAO,CAAE,QAAS,GAAM,OAAQ,UAAW,EAChF,GAAIA,EAAI,eAAe,OAAS,EAAG,MAAO,CAAE,QAAS,GAAM,OAAQ,sBAAuB,EAE1F,IAAMC,EAASD,EAAI,qBAAuB,GACpCE,EAAQD,EAAO,WAAW,SAAS,EAAIA,EAAO,MAAM,CAAC,EAAE,KAAK,EAAI,GACtE,OAAIC,GAASF,EAAI,eAAe,IAAIE,CAAK,EAAU,CAAE,QAAS,GAAM,OAAQ,OAAQ,EAE7E,CAAE,QAAS,GAAO,OAAQ,0BAA2B,CAC9D,CAuBO,SAASC,GACdC,EACAC,EAA0C,QAAQ,IACrC,CACb,IAAMC,EAAW,IAAI,IACjBD,EAAI,YAAYC,EAAS,IAAID,EAAI,UAAU,EAC/C,QAAWE,KAAKH,EAAO,MAAM,OAAS,CAAC,EAAOG,EAAE,OAAOD,EAAS,IAAIC,EAAE,KAAK,EAC3E,OAAOD,CACT,CAWO,SAASE,GACdC,EACAC,EACAL,EAA0C,QAAQ,IAC9B,CACpB,IAAMM,EAAQC,GAAcA,EAAE,QAAQ,OAAQ,EAAE,EAIhD,OAHmBF,IAAWC,EAAKF,EAAU,SAAS,EAClDA,EAAU,MACVA,EAAU,QAAQ,KAAMI,GAAMF,EAAKE,EAAE,GAAG,IAAMH,CAAM,GAAG,QACtCL,EAAI,YAAc,MACzC,CClKA,SAASS,GAAMC,EAA0C,CACvD,OAAQ,MAAM,QAAQA,CAAC,EAAIA,EAAE,CAAC,EAAIA,IAAI,KAAK,GAAK,EAClD,CAEA,SAASC,GAAWC,EAA+B,CACjD,GAAI,CACF,OAAO,IAAI,IAAIA,CAAM,EAAE,KAAK,YAAY,CAC1C,MAAE,CAEA,OAAO,IACT,CACF,CAIO,SAASC,GAAsBC,EAAyB,QAAQ,IAAkB,CACvF,OAAO,IAAI,KACRA,EAAI,wBAA0B,IAC5B,MAAM,GAAG,EACT,IAAKC,GAAMA,EAAE,KAAK,EAAE,QAAQ,OAAQ,EAAE,EAAE,YAAY,CAAC,EACrD,OAAO,OAAO,CACnB,CACF,CAEO,SAASC,GACdC,EACAC,EAAoCL,GAAsB,EAC3C,CACf,IAAMD,EAASH,GAAMQ,EAAQ,MAAM,EACnC,GAAIL,EAAQ,CACV,GAAIM,EAAa,IAAIN,EAAO,QAAQ,OAAQ,EAAE,EAAE,YAAY,CAAC,EAAG,MAAO,cACvE,IAAMO,EAAOR,GAAWC,CAAM,EACxBQ,EAAQ,CAACX,GAAMQ,EAAQ,IAAI,EAAGR,GAAMQ,EAAQ,kBAAkB,CAAC,CAAC,EACnE,OAAO,OAAO,EACd,IAAKI,GAAMA,EAAE,YAAY,CAAC,EAC7B,OAAOF,GAAQC,EAAM,SAASD,CAAI,EAAI,cAAgB,UAKxD,IAAMG,EAAOb,GAAMQ,EAAQ,gBAAgB,CAAC,EAAE,YAAY,EAC1D,OAAIK,IAAS,cAAgBA,IAAS,YAAoB,UACnD,MACT,CAIO,SAASC,GAA2BC,EAAqC,CAC9E,IAAMC,GAAKD,GAAU,OAAO,YAAY,EACxC,OAAOC,IAAM,OAASA,IAAM,MAC9B,CC7DA,OAAS,cAAAC,GAAY,aAAAC,GAAW,gBAAAC,GAAc,cAAAC,GAAY,iBAAAC,OAAqB,KAC/E,OAAS,WAAAC,OAAe,KACxB,OAAS,WAAAC,GAAS,QAAAC,OAAY,OCI9B,OAAS,YAAAC,GAAU,gBAAAC,OAAoB,gBCHvC,OAAS,gBAAAC,OAAoB,gBAC7B,OAAS,cAAAC,GAAY,aAAAC,GAAW,eAAAC,GAAa,gBAAAC,GAAc,cAAAC,GAAY,iBAAAC,OAAqB,KAC5F,OAAS,WAAAC,OAAe,KACxB,OAAS,QAAAC,OAAY,OAId,IAAMC,EAAc,QACrBC,GAAY,sCAELC,GAAeC,GAAgD,CAAC,CAACA,GAAMA,EAAG,WAAWF,EAAS,EAiBrGG,GAAQ,CAAC,eAAgB,oBAAqB,iBAAiB,EAE/DC,GAAiB,IAAM,CAC3B,GAAI,CACF,OAAAd,GAAa,oBAAqB,CAAC,OAAQ,yBAAyB,EAAG,CAAE,MAAO,SAAU,QAAS,GAAM,CAAC,EACnG,EACT,MAAE,CACA,MAAO,EACT,CACF,EAEIe,GAGG,SAASC,GAAcC,EAAiB,CAAC,EAAY,CAC1D,IAAMC,EAAQD,EAAK,WAAa,QAAaA,EAAK,SAAW,QAAaA,EAAK,aAAe,OAC9F,GAAI,CAACC,GAASH,KAAc,OAAW,OAAOA,GAC9C,GAAM,CAAE,SAAAI,EAAW,QAAQ,SAAU,OAAAC,EAASnB,GAAY,WAAAoB,EAAaP,EAAe,EAAIG,EACpFK,EAAKH,IAAa,UAAYN,GAAM,MAAOU,GAAMH,EAAOG,CAAC,CAAC,GAAKF,EAAW,EAChF,OAAKH,IAAOH,GAAYO,GACjBA,CACT,CAGA,IAAME,GAAa,CACjB,0FACA,mFACF,EAGMC,GAAgD,CACpD,MAAO,OAAQ,MAAO,OAAQ,OAAQ,OAAQ,OAAQ,OAAQ,MAAO,OAAQ,OAAQ,OAAQ,OAAQ,OAAQ,OAAQ,OACrH,KAAM,SAAU,OAAQ,SAAU,MAAO,SAAU,KAAM,SAAU,OAAQ,SAAU,UAAW,SAAU,OAAQ,SAAU,MAAO,QACrI,EAGaC,GAAcC,GAAwCF,GAAOE,EAAK,YAAY,CAAC,GAAK,KAG1F,SAASC,GAAYhB,EAAqD,CAC/E,IAAMiB,EAAI,+EAA+E,KAAKjB,CAAE,EAChG,OAAOiB,EAAI,CAAE,KAAMA,EAAE,CAAC,EAAG,OAAQA,EAAE,CAAC,CAAE,EAAI,IAC5C,CAIA,IAAMC,GAAY,kFAGX,SAASC,GAAgBC,EAAiC,CAC/D,IAAMC,EAAO,IAAI,IACjB,QAAWC,KAAQF,EACjB,OAAW,CAAC,CAAEG,EAAMC,EAAQT,CAAI,IAAKO,EAAK,SAASJ,EAAS,EAAG,CAC7D,IAAMO,EAAS,GAAGF,KAAQC,IACpBxB,EAAK,GAAGF,KAAYiB,KAAQU,YAC9BJ,EAAK,IAAIrB,CAAE,GACfqB,EAAK,IAAIrB,EAAI,CACX,GAAAA,EAAI,KAAMe,EAAK,CAAC,EAAE,YAAY,EAAIA,EAAK,MAAM,CAAC,EAAG,OAAAU,EACjD,QAAS,UAAW,OAAQZ,GAAOE,CAAI,GAAK,KAAM,KAAM,EAC1D,CAAC,EAGL,MAAO,CAAC,GAAGM,EAAK,OAAO,CAAC,CAC1B,CAIO,SAASK,GAAeC,EAAOf,GAAYP,EAAiB,CAAC,EAAkB,CACpF,GAAI,CAACD,GAAcC,CAAI,EAAG,MAAO,CAAC,EAClC,IAAMe,EAAmB,CAAC,EAC1B,QAAWQ,KAAOD,EAAM,CACtB,IAAIE,EACJ,GAAI,CAAEA,EAAStC,GAAYqC,CAAG,CAAE,MAAE,CAAQ,QAAS,CACnD,QAAWE,KAAKD,EACd,GAAI,CAAET,EAAO,KAAK5B,GAAaI,GAAKgC,EAAKE,EAAG,YAAY,EAAG,QAAQ,CAAC,CAAE,MAAE,CAA2B,EAGvG,OAAOX,GAAgBC,CAAM,CAC/B,CAUO,IAAMW,GAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAYanB,GAAW,KAAK,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgG3CoB,GAAc,CAACC,EAAOtC,GAAQ,IAAMC,GAAKqC,EAAM,UAAW,QAAS,aAAa,EAGtF,SAASC,GAAcD,EAAOtC,GAAQ,EAAW,CACtD,IAAMwC,EAAOH,GAAYC,CAAI,EAC7B,OAAI5C,GAAW8C,CAAI,GAAK3C,GAAa2C,EAAM,MAAM,IAAMJ,KACvDzC,GAAUM,GAAKqC,EAAM,UAAW,OAAO,EAAG,CAAE,UAAW,EAAK,CAAC,EAC7DvC,GAAc,GAAGyC,QAAYJ,GAAU,CAAE,KAAM,GAAM,CAAC,EACtDtC,GAAW,GAAG0C,QAAYA,CAAI,GACvBA,CACT,CDrNA,IAAMC,GAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBASNC,GAAU,IAAI,IAAI,CAAC,OAAQ,QAAS,QAAS,QAAQ,CAAC,EAEtDC,GAAiC,CACrC,IAAK,SAAU,MAAO,SAAU,QAAS,SAAU,QAAS,SAAU,MAAO,SAC7E,KAAM,OAAQ,KAAM,OAAQ,MAAO,OAAQ,QAAS,OAAQ,KAAM,OAAQ,MAAO,OAAQ,OAAQ,MACnG,EAGO,SAASC,GAAeC,EAA4B,CACzD,IAAMC,EAAwB,CAAC,EAC/B,QAAWC,KAAQF,EAAI,MAAM;AAAA,CAAI,EAAG,CAClC,GAAM,CAACG,EAAIC,EAAMC,EAAQC,EAAGC,CAAC,EAAIL,EAAK,MAAM,GAAI,EAC5C,CAACC,GAAM,CAACC,GAAQ,CAACC,GACjBF,EAAG,SAAS,QAAQ,GACpBA,EAAG,WAAW,mCAAmC,GAAK,CAACN,GAAQ,IAAIO,CAAI,GAC3EH,EAAO,KAAK,CACV,GAAAE,EAAI,KAAAC,EAAM,OAAAC,EACV,QAASC,IAAM,IAAM,UAAYA,IAAM,IAAM,WAAa,WAC1D,OAAQC,IAAM,IAAM,OAASA,IAAM,IAAM,SAAWT,GAAOM,CAAI,GAAK,IACtE,CAAC,EAEH,OAAOH,CACT,CAQA,IAAMO,GAAa,GAAK,GAAK,IAGhBC,GAAqB,GAAK,IACnCC,EAAuD,KACvDC,GAAa,GAEV,SAASC,GAAiBC,EAAM,KAAK,IAAI,EAAGC,EAAWN,GAA2B,CACvF,GAAI,QAAQ,WAAa,SAAU,MAAO,CAAC,EAC3C,GAAKE,EAOMG,EAAMH,EAAO,GAAKI,GAAY,CAACH,KACxCA,GAAa,GACbI,GAAS,YAAa,CAAC,KAAM,aAAc,KAAMnB,EAAG,EAAG,CAAE,QAAS,GAAO,EAAG,CAACoB,EAAKhB,IAAQ,CACxFW,GAAa,GACRK,IAAKN,EAAS,CAAE,GAAI,KAAK,IAAI,EAAG,OAAQ,CAAC,GAAGX,GAAe,OAAOC,CAAG,CAAC,EAAG,GAAGiB,GAAe,CAAC,CAAE,EACrG,CAAC,OAXD,IAAI,CACFP,EAAS,CAAE,GAAIG,EAAK,OAAQ,CAAC,GAAGd,GAAemB,GAAa,YAAa,CAAC,KAAM,aAAc,KAAMtB,EAAG,EAAG,CAAE,SAAU,OAAQ,QAAS,GAAO,CAAC,CAAC,EAAG,GAAGqB,GAAe,CAAC,CAAE,CAC1K,OAASE,EAAP,CACAC,GAAS,yCAAyC,OAAOD,GAAG,SAAWA,CAAC,EAAE,MAAM;AAAA,CAAI,EAAE,CAAC,GAAG,EAC1FT,EAAS,CAAE,GAAIG,EAAK,OAAQI,GAAe,CAAE,CAC/C,CAQF,OAAOP,EAAO,MAChB,CAIA,IAAIU,GAAmCC,GAAM,QAAQ,OAAO,MAAMA,EAAI;AAAA,CAAI,EACpEC,GAAS,IAAI,IAGZ,SAASC,GAAYC,EAAiC,CAAEJ,GAAWI,CAAG,CAEtE,SAASC,EAASC,EAAaC,EAAmB,CACnDL,GAAO,IAAII,CAAG,IAClBJ,GAAO,IAAII,CAAG,EACdN,GAASO,CAAG,EACd,CAIO,SAASC,GAAeC,EAAuC,CACpE,QAAWC,KAAKR,GAAYO,EAAMC,CAAC,GAAGR,GAAO,OAAOQ,CAAC,CACvD,CAIA,IAAIC,GAAoC,IAAM,CAAC,EACxC,SAASC,GAAoBR,EAAkC,CAAEO,GAAYP,CAAG,CAChF,SAASS,GAAc7B,EAAoB,CAChD,GAAI,CAAE2B,GAAU3B,CAAI,CAAE,OAAS,EAAP,CAAiBqB,EAAS,eAAgB,uCAAuC,GAAG,SAAW,GAAG,CAAE,CAC9H,CAIA,IAAMS,GAAU,CAAE,QAAS,EAAG,SAAU,EAAG,SAAU,CAAE,EAGjDC,GAAY,IAAI,IAAI,CAAC,UAAW,SAAS,CAAC,EAGzC,SAASC,GAAKC,EAAwB,CAC3C,IAAMC,EAASD,EAAE,GAAG,WAAW,kBAAkB,EAAI,EACjDA,EAAE,GAAG,WAAW,sBAAsB,GAAK,CAACF,GAAU,IAAIE,EAAE,IAAI,EAAI,EAAI,EAC5E,OAAOH,GAAQG,EAAE,OAAO,EAAI,GAAKC,CACnC,CAEA,IAAMC,EAAYlC,GAAmBA,EAAO,YAAY,EAAE,MAAM,MAAM,EAAE,CAAC,EAInEmC,GAAUH,GAAmBA,EAAE,GAAG,WAAW,kBAAkB,EAU9D,SAASI,GAAcC,EAA0BrC,EAAgBsC,EAA4BC,EAAqB,IAAI,IAA2B,CACtJ,IAAIC,EAAOC,EAAWJ,EAAU,OAAQL,GAAM,CAACA,EAAE,MAAQE,EAASF,EAAE,MAAM,IAAME,EAASlC,CAAM,CAAC,EAAGA,CAAM,EAEzG,GADIwC,EAAK,KAAKL,EAAM,IAAGK,EAAOA,EAAK,OAAOL,EAAM,GAC5CG,IAAW,UAAYA,IAAW,OAAQ,CAC5C,IAAMI,EAAOF,EAAK,OAAQR,GAAMA,EAAE,SAAWM,CAAM,EAC/CI,EAAK,SAAQF,EAAOE,GAE1B,GAAI,CAACF,EAAK,OAAQ,OAAO,KACzB,IAAMG,EAAOH,EAAK,OAAQR,GAAMA,EAAE,UAAYQ,EAAK,CAAC,EAAE,OAAO,EACvDI,EAAO,IAAI,IAAI,CAAC,GAAGL,CAAK,EAAE,IAAKM,GAAMA,EAAE,YAAY,CAAC,CAAC,EAC3D,OAAOF,EAAK,KAAMX,GAAM,CAACY,EAAK,IAAIZ,EAAE,KAAK,YAAY,CAAC,CAAC,GAAKW,EAAK,CAAC,CACpE,CAIO,SAASF,EAAWJ,EAA0BrC,EAAS,KAAqB,CACjF,IAAM8C,EAAQ9C,EAAO,YAAY,EAAE,QAAQ,IAAK,GAAG,EAC7C+C,EAAOb,EAASlC,CAAM,EACtBwC,EAAOH,EAAU,OAAQL,GAAME,EAASF,EAAE,MAAM,IAAMe,CAAI,EAChE,OAAQP,EAAK,OAASA,EAAOH,GAAW,MAAM,EAAE,KAAK,CAACW,EAAGC,IACvDlB,GAAKkB,CAAC,EAAIlB,GAAKiB,CAAC,GACb,EAAOC,EAAE,OAAO,YAAY,IAAMH,GAAS,EAAOE,EAAE,OAAO,YAAY,IAAMF,IAC7EE,EAAE,KAAK,cAAcC,EAAE,IAAI,GAAKD,EAAE,OAAO,cAAcC,EAAE,MAAM,CAAC,CACvE,CAWO,SAASC,EAAUnD,EAAcsC,EAA0BrC,EAAS,KAA0B,CACnG,IAAMC,EAAIF,EAAK,KAAK,EACpB,GAAI,CAACE,EAAG,OAAO,KACf,IAAMkD,EAAOd,EAAU,KAAML,GAAMA,EAAE,KAAO/B,CAAC,EAC7C,GAAIkD,EAAM,OAAOA,EACjB,GAAIlD,EAAE,YAAY,EAAE,WAAWmD,CAAW,EAAG,CAC3C,IAAMC,EAAOpD,EAAE,MAAMmD,EAAY,MAAM,EAAE,KAAK,EAAE,YAAY,EACtDE,EAAOb,EAAWJ,EAAU,OAAQL,GAAMA,EAAE,MAAQA,EAAE,KAAK,YAAY,IAAMqB,CAAI,EAAGrD,CAAM,EAAE,CAAC,EACnG,GAAIsD,GAAQ,CAACD,EAAM,OAAOC,GAAQ,KAGlC,IAAMC,EAAUL,EAAUG,EAAMhB,EAAU,OAAQL,GAAME,EAASF,EAAE,MAAM,IAAME,EAASlC,CAAM,CAAC,EAAGA,CAAM,EACxG,OAAIuD,GAASnC,EAAS,QAAQiC,IAAQ,uBAAuBA,sCAAyCE,EAAQ,SAASA,EAAQ,kBAAkB,EAC1IA,EAETlB,EAAYA,EAAU,OAAQL,GAAM,CAACA,EAAE,IAAI,EAC3C,IAAMwB,EAAO,oCAAoC,KAAKvD,CAAC,EACjDoD,GAAQG,EAAOA,EAAK,CAAC,EAAIvD,EAAE,QAAQ,gBAAiB,EAAE,GAAG,KAAK,EAAE,YAAY,EAC5EwD,EAAQhB,EAAWJ,EAAU,OAAQL,GAAMA,EAAE,KAAK,YAAY,IAAMqB,CAAI,EAAGrD,CAAM,EAEvF,OADewD,EAAOC,EAAM,KAAMzB,GAAMA,EAAE,UAAYwB,EAAK,CAAC,EAAE,YAAY,CAAC,EAAI,SAC9DC,EAAM,CAAC,GAAK,IAC/B,CAiBO,SAASC,GAAWC,EAAsBnB,EAAqBD,EAAqB,IAAI,IAAiC,CAC9H,IAAMqB,EAAO,IAAI,IACjB,GAAI,CAACpB,EAAK,OAAQ,OAAOoB,EACzB,IAAMhB,EAAO,IAAI,IAAI,CAAC,GAAGL,CAAK,EAAE,IAAKM,GAAMA,EAAE,YAAY,CAAC,CAAC,EACvDgB,EAA0B,SAC9B,QAAW/C,KAAK6C,EAAS,CACvB,IAAMG,EAAQhD,EAAE,SAAW,UAAYA,EAAE,SAAW,OAC9CiD,EAAOD,EAAQhD,EAAE,OAAS+C,EAC1BG,EAAOxB,EAAK,OAAQR,GAAM,CAACY,EAAK,IAAIZ,EAAE,KAAK,YAAY,CAAC,CAAC,EACzDiC,EAAOD,EAAK,KAAMhC,GAAMA,EAAE,SAAW+B,CAAI,IACzCD,EAAQtB,EAAK,KAAMR,GAAMA,EAAE,SAAW+B,CAAI,EAAI,SAC/CC,EAAK,CAAC,GAAKxB,EAAKoB,EAAK,KAAOpB,EAAK,MAAM,EAC5CoB,EAAK,IAAI9C,EAAE,GAAImD,CAAI,EACnBrB,EAAK,IAAIqB,EAAK,KAAK,YAAY,CAAC,EAC3BH,IAAOD,EAAOA,IAAS,SAAW,OAAS,UAElD,OAAOD,CACT,CEvOAM,KAWO,IAAMC,GAAa,SACpBC,GAAeC,GAAiBA,EAAK,KAAK,EAAE,YAAY,IAAMF,GAGvDG,GAAmB,uBAiCzB,SAASC,GACdC,EACAC,EACAC,EAA0B,CAAC,EAC3BC,EAA2BC,GAAiB,EAChC,CACZ,IAAMC,EAA0CJ,EAAOD,CAAO,EACxDH,EAAOQ,GAAO,MAAQL,EACtBM,EAAID,GAAO,MACXE,EAAMC,GAAkBP,EAAQC,EAAUC,CAAS,EAAE,IAAIH,CAAO,GAAK,KACrES,EAAmBC,GAAeV,EAASM,GAAG,OAAQA,GAAG,OAAQJ,EAAUC,CAAS,EAC1F,MAAO,CACL,QAAAH,EACA,KAAAH,EACA,SAAUS,GAAG,UAAYJ,EAAS,UAAY,SAC9C,kBAAmBI,GAAG,mBAAqB,KAC3C,YAAaC,GAAK,IAAM,KACxB,gBAAiBA,EAAMI,GAAMJ,CAAG,EAAI,KACpC,iBAAAE,EACA,UAAWP,EAAS,UAAY,YAAc,SAC9C,OAAQI,GAAG,QAAU,KACrB,MAAOA,GAAG,OAAS,KACnB,MAAOA,GAAG,OAASM,GAAYf,EAAMQ,GAAO,YAAY,EACxD,GAAIC,GAAG,KAAO,CAAE,KAAMA,EAAE,IAAK,EAAI,CAAC,EAClC,GAAIA,GAAG,SAAW,CAAE,SAAUA,EAAE,QAAS,EAAI,CAAC,CAChD,CACF,CAEO,IAAMK,GAASL,GACpB,GAAGA,EAAE,OAAOA,EAAE,KAAO,UAAYA,EAAE,UAAY,WAAa,GAAK,KAAKA,EAAE,QAAQ,CAAC,EAAE,YAAY,IAAIA,EAAE,QAAQ,MAAM,CAAC,QAAQA,EAAE,SAGzH,SAASO,GAAaC,EAAejB,EAA0BM,EAA0BY,EAAoC,CAClI,GAAI,CAAClB,GAAQ,CAACM,EAAU,OAAQ,OAAO,KACvC,IAAMG,EAAIU,EAAUnB,EAAMM,EAAWY,CAAM,EAC3C,OAAKT,IACHW,EAAS,GAAGH,KAASjB,IAAQ,WAAWiB,oBAAwBjB,gGAAmG,EACnKqB,GAAcrB,CAAI,GAEbS,CACT,CAIO,SAASa,GAAYtB,EAAckB,EAAmE,CAC3G,IAAMK,EAAIvB,EAAK,KAAK,EACpB,GAAIuB,EAAE,YAAY,EAAE,WAAWC,CAAW,EAAG,MAAO,CAAE,OAAAN,EAAQ,OAAQO,GAAWF,EAAE,MAAMC,EAAY,MAAM,EAAE,KAAK,CAAC,CAAE,EACrH,IAAME,EAAOC,GAAYJ,CAAC,EAC1B,OAAIG,EAAa,CAAE,OAAQA,EAAK,OAAQ,OAAQD,GAAWC,EAAK,IAAI,CAAE,EAE/D,CAAE,OADM,oDAAoD,KAAKH,CAAC,IAC/C,CAAC,GAAKL,EAAQ,OAAQ,IAAK,CACvD,CAMA,IAAMU,GAAYV,GAAmBA,EAAO,YAAY,EAAE,MAAM,MAAM,EAAE,CAAC,EAGnEW,GAAU,CAACC,EAAkCC,IACjD,OAAOD,GAAW,SAAWA,EAASA,IAASF,GAASG,CAAI,CAAC,EAI/D,SAASC,GAAWf,EAAejB,EAA0BM,EAA0BY,EAAgD,CACrI,GAAIlB,IAAS,OACb,OAAID,GAAYC,CAAI,EAAU,KACvBgB,GAAaC,EAAOjB,EAAMM,EAAWY,CAAM,GAAK,MACzD,CAGA,IAAMe,GAAa,CAACxB,EAAuByB,IACzC,CAACzB,GAAK,CAACA,EAAE,QAAUA,EAAE,SAAW,WAAa,CAACyB,GAAUA,IAAW,WAAazB,EAAE,SAAWyB,EAQxF,SAASC,GAAiBC,EAAqB/B,EAAyBC,EAA0B+B,EAAqB,IAAI,IAAwC,CACxK,IAAMnB,EAASb,EAAS,QAAU,KAC5BiC,EAAM,IAAI,IACVC,EAAoB,CAAC,EACrBC,EAAWR,GAAW,eAAgBH,GAAQxB,EAAS,OAAQa,CAAM,EAAGZ,EAAWY,CAAM,EACzFuB,EAAQ,IAAM,IAAI,IAAI,CAAC,GAAGJ,EAAO,GAAG,CAAC,GAAGC,EAAI,OAAO,CAAC,EAAE,QAAS7B,GAAOA,EAAI,CAACA,EAAE,IAAI,EAAI,CAAC,CAAE,CAAC,CAAC,EAChG,QAAWiC,KAAKN,EAAQ,CACtB,IAAMpC,EAAO6B,GAAQa,EAAE,OAAQxB,CAAM,EAC/ByB,EAAMX,GAAWU,EAAE,GAAI1C,EAAMM,EAAWY,CAAM,EAC9C0B,EAAOD,IAAQ,OAAYA,EAC7B3C,IAAS,QAAaM,EAAU,OAASuC,GAAQH,EAAG1C,EAAMkB,EAAQsB,EAAUlC,EAAWmC,EAAM,CAAC,EAC9FD,IAAa,QAAaP,GAAWO,EAAUE,EAAE,MAAM,EAAIF,EAAW,OACtEI,IAAS,OAAWN,EAAI,IAAII,EAAE,GAAIE,CAAI,EACrCL,EAAK,KAAK,CAAE,GAAIG,EAAE,GAAI,OAAQA,EAAE,QAAU,IAAK,CAAC,EAEvD,IAAMI,EAAQL,EAAM,EAEpB,OAAW,CAACM,EAAItC,CAAC,IAAKuC,GAAWT,EAAMU,EAAW3C,EAAU,OAAQG,GAAM,CAACA,EAAE,IAAI,EAAGS,CAAM,EAAG4B,CAAK,EAAGR,EAAI,IAAIS,EAAItC,CAAC,EAClH,OAAO6B,CACT,CAQA,SAASO,GAAQH,EAAc1C,EAAckB,EAAgBsB,EAA0ClC,EAA0B+B,EAAoD,CACnL,QAAWa,KAAKR,EAAE,WAAa,CAAC,EAAG,CACjC,IAAMjC,EAAIuB,GAAW,GAAGU,EAAE,eAAgBQ,EAAG5C,EAAWY,CAAM,EAC9D,GAAIT,IAAM,OAAW,OAAOA,EAE9B,GAAI+B,IAAa,QAAaP,GAAWO,EAAUE,EAAE,MAAM,EAAG,OAAOF,EACrE,IAAMW,EAAS7B,GAAYtB,EAAMkB,CAAM,EACvC,OAAOkC,GAAc9C,EAAW6C,EAAO,OAAQT,EAAE,QAAUS,EAAO,OAAQd,CAAK,GAAK,MACtF,CAOO,SAASxB,GACdI,EAAe0B,EAA+BT,EAC9C7B,EAAyBC,EACW,CACpC,GAAI,OAAOqC,GAAQ,SAAU,MAAO,CAAC,EACrC,IAAML,EAA0C,CAAC,EAC3Ce,EAAM,CAACC,EAAaC,EAAgCC,IAA8C,CACtG,GAAI,GAACD,GAAQ,OAAOA,GAAS,UAC7B,OAAW,CAACxB,EAAM/B,CAAI,IAAK,OAAO,QAAQuD,CAAI,EAAG,CAC/C,IAAI9C,EAAIuB,GAAW,GAAGsB,KAAOvB,IAAQ/B,EAAMM,EAAWyB,CAAI,EAEtDtB,IAAM,QAAaH,EAAU,SAAQG,EAAI2C,GAAc9C,EAAWyB,EAAMG,GAAUZ,GAAYtB,EAAM+B,CAAI,EAAE,MAAM,GAAK,QACrHtB,IAAM,QAAa+C,EAAM/C,CAAC,IAAG6B,EAAIV,GAASG,CAAI,CAAC,EAAItB,GAE3D,EACA,OAAA4C,EAAI,eAAgBhD,EAAS,OAASI,GAAMwB,GAAWxB,EAAGyB,CAAM,CAAC,EACjEmB,EAAIpC,EAAO0B,EAAK,IAAM,EAAI,EACnBL,CACT,CAIO,SAASmB,GAAahD,EAAeiD,EAAkC,CAC5E,IAAM3B,EAAO2B,EAAOC,GAAeD,CAAI,EAAI,KAC3C,GAAI,CAAC3B,GAAQ,EAAEA,KAAQtB,EAAE,kBAAmB,OAAOA,EACnD,IAAMC,EAAMD,EAAE,iBAAiBsB,CAAI,EACnC,MAAO,CAAE,GAAGtB,EAAG,YAAaC,GAAK,IAAM,KAAM,gBAAiBA,EAAMI,GAAMJ,CAAG,EAAI,IAAK,CACxF,CAIO,SAASC,GAAkBP,EAAgBC,EAA0B,CAAC,EAAGC,EAA2BC,GAAiB,EAAoC,CAC9J,IAAM6B,EAAS,OAAO,QAAQhC,CAAM,EAAE,IAAI,CAAC,CAAC2C,EAAIa,CAAC,KAAO,CAAE,GAAAb,EAAI,OAAQa,EAAE,OAAO,OAAQ,OAAQA,EAAE,OAAO,OAAQ,UAAWA,EAAE,OAAO,SAAU,EAAE,EAChJ,OAAOzB,GAAiBC,EAAQ/B,EAAUC,CAAS,CACrD,CAGO,SAASuD,GAASpD,EAAeqD,EAAoC,CAC1E,IAAMC,EAAgB,CAAE,SAAUtD,EAAE,SAAU,WAAYuD,GAAYF,EAAUrD,EAAE,iBAAiB,EAAG,OAAQA,EAAE,YAAa,SAAUA,EAAE,QAAS,EAC5IwD,EAAQ,OAAO,QAAQxD,EAAE,kBAAoB,CAAC,CAAC,EACrD,OAAIwD,EAAM,SAAQF,EAAI,UAAY,OAAO,YAAYE,EAAM,IAAI,CAAC,CAACC,EAAGxD,CAAG,IAAM,CAACwD,EAAGxD,GAAK,IAAM,IAAI,CAAC,CAAC,GAC9FD,EAAE,MAAQA,EAAE,OAAS,IAAGsD,EAAI,KAAOtD,EAAE,MACrCA,EAAE,UAAYA,EAAE,WAAa,WAAUsD,EAAI,SAAWtD,EAAE,UACrDsD,CACT,CAGO,SAASC,GAAYF,EAA0BK,EAAsC,CAC1F,OAAOL,GAAYK,GAAgB,QAAQ,IAAI,iBAAmBlE,EACpE,CASO,SAASc,GAAYf,EAAcoE,EAA+B,CACvE,IAAMC,GAASD,GAAgB,IAAI,MAAM,WAAW,EAAE,CAAC,GAAK,GACtDE,EAAI,sBAAsB,KAAKD,EAAM,KAAK,CAAC,EACjD,GAAI,CAACC,EAAG,MAAO,kBAAkBtE,KAEjC,IAAMsD,EAAMgB,EAAE,CAAC,EAAE,QAAQ,gBAAiB,EAAE,EAAE,KAAK,EAC7C,CAACC,EAAM,GAAGC,CAAI,EAAIlB,EAAI,MAAM,MAAM,EAClCmB,EAAOD,EAAK,KAAK,IAAI,EAE3B,OAAIC,GAAQF,EAAK,YAAY,IAAMvE,EAAK,YAAY,EAAU,kBAAkBA,MAASyE,KAGrF,CAAC,SAAS,KAAKnB,CAAG,GAAKA,EAAI,YAAY,EAAE,SAAStD,EAAK,YAAY,CAAC,EAAU,kBAAkBsD,KAC7F,kBAAkBtD,MAASsD,IACpC,CAKO,IAAMoB,GAAe,EAAI,GAAK,GAAK,IACpCC,GAAc,IAQPC,GAAN,KAAwB,CAG7B,YAAoBC,EAAQH,GAAc,CAAtB,WAAAG,CAAuB,CAFnC,UAAY,IAAI,IAIxB,WAAWC,EAAiB3E,EAAiB4E,EAAM,KAAK,IAAI,EAAY,CACtE,IAAMC,EAAO,KAAK,UAAU,IAAIC,GAAIH,EAAS3E,CAAO,CAAC,EACrD,OAAO6E,IAAS,QAAaD,EAAMC,EAAO,KAAK,KACjD,CAIA,MAAMF,EAAiB3E,EAAiB4E,EAAM,KAAK,IAAI,EAAS,CAC9D,IAAMG,EAAID,GAAIH,EAAS3E,CAAO,EAE9B,KAAK,UAAU,OAAO+E,CAAC,EACvB,KAAK,UAAU,IAAIA,EAAGH,CAAG,EACrB,KAAK,UAAU,KAAOJ,IACxB,KAAK,UAAU,OAAO,KAAK,UAAU,KAAK,EAAE,KAAK,EAAE,KAAe,CAEtE,CACF,EAEMM,GAAM,CAACH,EAAiB3E,IAAoB,GAAG2E,MAAgB3E,IAG9D,SAASgF,GAAiBC,EAAmBC,EAA4B,CAC9E,IAAMC,EAAQF,EAAM,MAAQ,0BAA0BA,EAAM,SAAW,GACvE,OAAIC,EAEA,2KAC6FD,EAAM,uBACpFE,IAIjB,gLAEAA,CAEJ,CAIO,SAASC,GAAYpF,EAAiBC,EAAgCiF,EAAoBhF,EAA0B,CAAC,EAAgB,CAC1I,IAAMG,EAAQJ,EAAOD,CAAO,EACtBiF,EAAQlF,GAAkBC,EAASC,EAAQC,CAAQ,EAGnDmF,GAAWhF,GAAO,cAAgB,IAAI,MAAM,SAAS,EAAE,CAAC,EAAE,MAAM,EAAG,GAAG,EAAE,KAAK,EACnF,MAAO,CACL,QAAAL,EACA,KAAMiF,EAAM,KACZ,MAAOvB,GAASuB,CAAK,EACrB,QAASI,GAAW,WAAWJ,EAAM,QACrC,UAAWD,GAAiBC,EAAOC,CAAS,CAC9C,CACF,CAIO,IAAMI,GACX,2OAWK,SAASC,GAAkBC,EAAsC,CACtE,IAAMC,EAAMD,EACZ,GAAIC,GAAK,UAAY,SAAW,CAACA,EAAI,OAAS,OAAOA,EAAI,OAAU,SAAU,OAC7E,IAAMC,EAAQ,GAAgB,OAAO,GAAM,SAAW,EAAE,QAAQ,OAAQ,GAAG,EAAE,MAAM,EAAG,GAAG,EAAI,GACvFC,EAAQD,EAAKD,EAAI,MAAM,KAAK,EAC5BR,EAAoB,CACxB,QAAS,GAAI,KAAM,GAAI,SAAU,SAAU,kBAAmB,KAC9D,YAAa,KAAM,gBAAiB,KAAM,iBAAkB,CAAC,EAAG,SAAU,GAAM,OAAQ,KACxF,MAAOS,EAAKD,EAAI,MAAM,KAAK,GAAK,KAChC,MAAOE,GAAS,QAClB,EACA,MAAO,GAAGL;AAAA,EAA2BN,GAAiBC,EAAOQ,EAAI,MAAM,YAAc,IAAQ,CAAC,CAACE,CAAK,GACtG,CH/UO,IAAMC,GAAiB,sGAoBjBC,GAAkB,CAACC,EAAOC,GAAQ,IAAMC,GAAKF,EAAM,UAAW,QAAS,aAAa,EAI1F,SAASG,GAAYC,EAAcC,EAA0BC,EAAyB,CAC3F,IAAMC,EAAIH,EAAK,KAAK,EACpB,GAAI,CAACG,GAAKA,EAAE,YAAY,IAAMC,GAAY,MAAO,GACjD,GAAID,EAAE,YAAY,EAAE,WAAWE,CAAW,EAAG,CAC3C,IAAMC,EAAOH,EAAE,MAAME,EAAY,MAAM,EAAE,KAAK,EAAE,YAAY,EAC5D,OAAOJ,EAAU,KAAMM,GAAMA,EAAE,MAAQA,EAAE,KAAK,YAAY,IAAMD,CAAI,EAEtE,OAAOE,EAAUL,EAAGF,EAAWC,CAAM,IAAM,IAC7C,CAKA,SAASO,GAAgBC,EAA8F,CACrH,IAAMR,EAASQ,EAAO,OAAO,QAAU,KACjCC,EAAoF,CAAC,EACrFC,EAAM,CAACC,EAAeC,IAAwD,CAClF,GAAI,OAAOA,GAAW,SAAUH,EAAI,KAAK,CAAE,MAAAE,EAAO,KAAMC,EAAQ,OAAAZ,EAAQ,YAAa,EAAM,CAAC,MACvF,QAAW,CAACa,EAAMf,CAAI,IAAK,OAAO,QAAQc,GAAU,CAAC,CAAC,EAAGH,EAAI,KAAK,CAAE,MAAAE,EAAO,KAAAb,EAAM,OAAQe,EAAM,YAAa,EAAK,CAAC,CACzH,EACAH,EAAI,eAAgBF,EAAO,OAAO,MAAM,EACxC,OAAW,CAACM,EAAIC,CAAC,IAAK,OAAO,QAAQP,EAAO,MAAM,GAC3CO,EAAE,OAAO,UAAYP,EAAO,OAAO,UAAY,YAAc,UAAYA,EAAO,OAAO,WAAa,QACzGE,EAAII,EAAIC,EAAE,OAAO,MAAM,EAEzB,OAAON,CACT,CAOO,SAASO,GAAkBR,EAAgBT,EAA6E,CAC7H,GAAI,CAACA,EAAU,OAAQ,MAAO,CAAC,EAC/B,IAAMkB,EAAU,IAAI,IAChBC,EACJ,OAAW,CAAE,MAAAP,EAAO,KAAAb,EAAM,OAAAE,EAAQ,YAAAmB,CAAY,IAAKZ,GAAgBC,CAAM,EAAG,CAC1E,GAAIX,GAAYC,EAAMC,EAAWC,CAAM,EAAG,SAC1CkB,IAASE,GAAkBZ,EAAO,OAAQA,EAAO,MAAOT,CAAS,EACjE,IAAMsB,EAAQJ,EAAQ,IAAInB,CAAI,GAAK,CAAE,MAAOA,EAAM,OAAQ,CAAC,EAAG,WAAY,CAAC,CAAE,EACxEuB,EAAM,OAAO,SAASV,CAAK,GAAGU,EAAM,OAAO,KAAKV,CAAK,EAC1D,IAAMW,EAAMJ,EAAK,IAAIP,CAAK,EAEtBA,IAAU,gBAAkB,CAACQ,IAAaE,EAAM,WAAWV,CAAK,EAAIW,EAAMC,GAAMD,CAAG,EAAI,wBAC3FL,EAAQ,IAAInB,EAAMuB,CAAK,EAEzB,MAAO,CAAC,GAAGJ,EAAQ,OAAO,CAAC,CAC7B,CAGO,SAASO,GAAiB1B,EAAsB,CACrD,IAAM2B,EAAOC,GAAY5B,CAAI,EAC7B,GAAI2B,EAAM,MAAO,QAAQA,EAAK,KAAK,CAAC,EAAE,YAAY,IAAIA,EAAK,KAAK,MAAM,CAAC,MAAMA,EAAK,UAClF,GAAI3B,EAAK,YAAY,EAAE,WAAWK,CAAW,EAAG,CAC9C,IAAMC,EAAON,EAAK,MAAMK,EAAY,MAAM,EAAE,KAAK,EACjD,MAAO,QAAQC,EAAK,CAAC,GAAG,YAAY,GAAK,KAAKA,EAAK,MAAM,CAAC,IAE5D,OAAON,CACT,CAIO,SAAS6B,GAAcC,EAA6D,CACzF,IAAMC,EAAQD,EAAQ,IAAKE,GAAM,CAC/B,IAAMC,EAAMD,EAAE,OAAO,IAAKf,GAAOe,EAAE,WAAWf,CAAC,EAAI,GAAGA,UAAUe,EAAE,WAAWf,CAAC,KAAOA,CAAE,EAAE,KAAK,IAAI,EAClG,MAAO,UAAKS,GAAiBM,EAAE,KAAK,MAAMC,GAC5C,CAAC,EACD,MAAO,CACL,MAAOH,EAAQ,SAAW,EAAI,mCAAqC,WAAWA,EAAQ,kCACtF,QAAS,GAAGC,EAAM,KAAK;AAAA,CAAI;AAAA,eAAmBrC,2DAChD,CACF,CAEO,SAASwC,GAAgBC,EAAOxC,GAAgB,EAA4B,CACjF,GAAI,CAACyC,GAAWD,CAAI,EAAG,OAAO,KAC9B,GAAI,CACF,IAAME,EAAI,KAAK,MAAMC,GAAaH,EAAM,MAAM,CAAC,EAC/C,OAAOE,GAAK,MAAM,QAAQA,EAAE,OAAO,EAAIA,EAAI,IAC7C,MAAE,CACA,OAAO,IACT,CACF,CAEA,SAASE,GAAiBJ,EAAcK,EAA+B,CACrEC,GAAUC,GAAQP,CAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EAC5CQ,GAAc,GAAGR,QAAY,KAAK,UAAUK,EAAO,KAAM,CAAC,EAAI;AAAA,CAAI,EAClEI,GAAW,GAAGT,QAAYA,CAAI,CAChC,CAWO,IAAMU,GAAN,KAAkB,CAIvB,YAAoBC,EAAuB,CAAvB,UAAAA,CAAwB,CAHpC,QAAU,GACV,WAAa,GAKrB,YAAsB,CAAE,OAAO,KAAK,UAAW,CAE/C,MAAM,MAAMpC,EAAgBT,EAA0BuB,EAAM,IAAI,KAA0C,CACxG,GAAI,KAAK,SAAW,CAACvB,EAAU,OAAQ,OAAO,KAC9C,KAAK,QAAU,GACf,GAAI,CACF,IAAMkC,EAAO,KAAK,KAAK,MAAQxC,GAAgB,EACzCoD,EAAS,IAAI,KAAKb,GAAgBC,CAAI,GAAG,SAAW,CAAC,GAAG,IAAKH,GAAM,CAACA,EAAE,MAAOA,CAAC,CAAC,CAAC,EAChFgB,EAAQ9B,GAAkBR,EAAQT,CAAS,EAC3C6B,EAA0BkB,EAAM,IAAKhB,IAAO,CAAE,GAAGA,EAAG,MAAOe,EAAO,IAAIf,EAAE,KAAK,GAAG,OAASR,EAAI,YAAY,EAAG,GAAIuB,EAAO,IAAIf,EAAE,KAAK,GAAG,WAAa,CAAE,WAAYe,EAAO,IAAIf,EAAE,KAAK,EAAG,UAAW,EAAI,CAAC,CAAG,EAAE,EAC1MiB,EAAO,CAAC,GAAGF,EAAO,KAAK,CAAC,EAAE,OAAQxC,GAAM,CAACyC,EAAM,KAAMhB,GAAMA,EAAE,QAAUzB,CAAC,CAAC,EAC/E,QAAWA,KAAK0C,EAAM,KAAK,KAAK,IAAI,WAAWvB,GAAiBnB,CAAC,sDAAsD,EAEnH0C,EAAK,QAAQC,GAAgBC,GAAMF,EAAK,KAAM1C,GAAM4C,IAAM5C,GAAK4C,EAAE,SAAS,IAAI5C,GAAG,CAAC,CAAC,EACvF,IAAM6C,EAAStB,EAAQ,OAAQE,GAAM,CAACA,EAAE,UAAU,EAClD,GAAIoB,EAAO,OAAQ,CACjB,GAAM,CAAE,MAAAC,EAAO,QAAAC,CAAQ,EAAIzB,GAAcuB,CAAM,EAC/C,GAAI,CACF,MAAM,KAAK,KAAK,OAAOC,EAAOC,CAAO,EACrC,QAAWtB,KAAKoB,EAAQpB,EAAE,WAAaR,EAAI,YAAY,EACvD,KAAK,KAAK,IAAI,2BAA2B4B,EAAO,IAAKpB,GAAMN,GAAiBM,EAAE,KAAK,CAAC,EAAE,KAAK,IAAI,iBAAiB,CAClH,OAASuB,EAAP,CAEA,KAAK,KAAK,IAAI,oDAAoDA,GAAG,SAAWA,GAAG,CACrF,EAEF,KAAK,WAAazB,EAAQ,OAAS,EACnC,IAAMU,EAAQ,CAAE,UAAWhB,EAAI,YAAY,EAAG,QAAAM,CAAQ,EAEtD,OAAI,CAACM,GAAWD,CAAI,GAAK,KAAK,UAAUL,CAAO,IAAM,KAAK,UAAU,CAAC,GAAGiB,EAAO,OAAO,CAAC,CAAC,IAAGR,GAAiBJ,EAAMK,CAAK,EAChHA,CACT,QAAE,CACA,KAAK,QAAU,EACjB,CACF,CACF,EIzLA,OAAS,gBAAAgB,GAAc,iBAAAC,GAAe,cAAAC,GAAY,eAAAC,GAAa,YAAAC,OAAgB,KAC/E,OAAS,WAAAC,GAAS,QAAAC,OAAY,OCD9B,OAAS,cAAAC,GAAY,aAAAC,GAAW,gBAAAC,GAAc,iBAAAC,OAAqB,KACnE,OAAS,WAAAC,GAAS,WAAAC,OAAe,OAEjC,OAAS,KAAAC,MAAS,MAalB,IAAMC,GAAeD,EAAE,OAAO,CAC5B,KAAMA,EAAE,KAAK,CAAC,SAAU,QAAQ,CAAC,EACjC,KAAMA,EAAE,OAAO,EAAE,SAAS,EAC1B,QAASA,EAAE,OAAO,EAClB,IAAKA,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAC/B,IAAKA,EAAE,OAAO,CAChB,CAAC,EAEKE,GAAaF,EAAE,OAAO,CAC1B,GAAIA,EAAE,OAAO,EACb,MAAOA,EAAE,OAAO,EAChB,YAAaA,EAAE,OAAO,EAAE,SAAS,EACjC,SAAUA,EAAE,OAAO,EAAE,SAAS,EAC9B,OAAQA,EAAE,MAAMA,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC,EACtC,UAAWA,EAAE,OAAO,EAAE,SAAS,EAC/B,OAAQA,EAAE,KAAK,CAAC,OAAQ,QAAS,UAAW,MAAM,CAAC,EAAE,QAAQ,MAAM,EACnE,SAAUA,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EACpC,iBAAkBA,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAC5C,OAAQC,GAAa,SAAS,EAC9B,WAAYD,EAAE,OAAO,EAAE,SAAS,EAChC,UAAWA,EAAE,OAAO,EACpB,UAAWA,EAAE,OAAO,EACpB,QAASA,EAAE,OAAO,EAAE,SAAS,EAC7B,SAAUA,EAAE,OAAO,EAAE,SAAS,CAChC,CAAC,EAEKG,GAAaH,EAAE,OAAO,CAC1B,QAASA,EAAE,QAAQ,CAAC,EACpB,MAAOA,EAAE,MAAME,EAAU,EAAE,QAAQ,CAAC,CAAC,CACvC,CAAC,EAMYE,GAAN,KAAmB,CACxB,YAAoBC,EAAc,CAAd,UAAAA,CAAe,CAGnC,UAAmB,CACjB,OAAOC,GAAQ,QAAQ,IAAI,EAAG,KAAK,IAAI,EAAE,QAAQ,SAAU,OAAO,CACpE,CAGA,QAAiB,CACf,OAAOA,GAAQ,QAAQ,IAAI,EAAG,KAAK,IAAI,EAAE,QAAQ,WAAY,KAAK,CACpE,CAEA,QAAkB,CAChB,OAAOC,GAAW,KAAK,SAAS,CAAC,CACnC,CAEA,MAAoB,CAClB,IAAMC,EAAI,KAAK,SAAS,EACxB,GAAI,CAACD,GAAWC,CAAC,EAAG,MAAO,CAAE,QAAS,EAAG,MAAO,CAAC,CAAE,EACnD,IAAMC,EAAMC,GAAaF,EAAG,OAAO,EACnC,GAAI,CACF,OAAOL,GAAW,MAAM,KAAK,MAAMM,CAAG,CAAC,CACzC,OAASE,EAAP,CACA,MAAM,IAAI,MAAM,yBAAyBH,OAAOG,EAAE,SAAS,CAC7D,CACF,CAEA,KAAKC,EAAyB,CAC5B,IAAMC,EAAQ,KAAK,SAAS,EAC5BC,GAAUC,GAAQF,CAAK,EAAG,CAAE,UAAW,EAAK,CAAC,EAC7CG,GAAcH,EAAO,KAAK,UAAUD,EAAM,KAAM,CAAC,EAAI;AAAA,CAAI,EACzDI,GAAc,KAAK,OAAO,EAAG,KAAK,WAAWJ,CAAI,CAAC,CACpD,CAEA,MAAsB,CACpB,OAAO,KAAK,KAAK,EAAE,KACrB,CAEA,SAASK,EAAqC,CAC5C,OAAO,KAAK,KAAK,EAAE,KAAMC,GAAMA,EAAE,KAAOD,CAAE,CAC5C,CAGA,IAAIE,EAA2F,CAC7F,IAAMP,EAAO,KAAK,KAAK,EACvB,GAAIA,EAAK,MAAM,KAAMM,GAAMA,EAAE,KAAOC,EAAM,EAAE,EAC1C,MAAM,IAAI,MAAM,gBAAgBA,EAAM,mBAAmB,EAE3D,IAAMC,EAAM,IAAI,KAAK,EAAE,YAAY,EAC7BC,EAAoB,CACxB,GAAGF,EACH,OAAQA,EAAM,QAAU,CAAC,EACzB,OAAQA,EAAM,QAAU,OACxB,UAAWA,EAAM,WAAaC,EAC9B,UAAWA,CACb,EACA,OAAAR,EAAK,MAAM,KAAKS,CAAI,EACpB,KAAK,KAAKT,CAAI,EACPS,CACT,CAGA,QAAQC,EAAqG,CAC3G,IAAMV,EAAO,KAAK,KAAK,EACjBW,EAAW,IAAI,IAAIX,EAAK,MAAM,IAAKM,GAAMA,EAAE,EAAE,CAAC,EAC9CE,EAAM,IAAI,KAAK,EAAE,YAAY,EAC7BI,EAAuB,CAAC,EAC9B,QAAWL,KAASG,EAAQ,CAC1B,GAAIC,EAAS,IAAIJ,EAAM,EAAE,EAAG,SAC5B,IAAME,EAAoB,CACxB,GAAGF,EACH,OAAQA,EAAM,QAAU,CAAC,EACzB,OAAQA,EAAM,QAAU,OACxB,UAAWA,EAAM,WAAaC,EAC9B,UAAWA,CACb,EACAR,EAAK,MAAM,KAAKS,CAAI,EACpBG,EAAM,KAAKH,CAAI,EACfE,EAAS,IAAIF,EAAK,EAAE,EAEtB,OAAIG,EAAM,QAAQ,KAAK,KAAKZ,CAAI,EACzBY,CACT,CAEA,OAAOP,EAAYQ,EAA0C,CAC3D,IAAMb,EAAO,KAAK,KAAK,EACjBc,EAAMd,EAAK,MAAM,UAAWM,GAAMA,EAAE,KAAOD,CAAE,EACnD,GAAIS,EAAM,EAAG,MAAM,IAAI,MAAM,gBAAgBT,aAAc,EAC3D,IAAMU,EAAuB,CAC3B,GAAGf,EAAK,MAAMc,CAAG,EACjB,GAAGD,EACH,GAAIb,EAAK,MAAMc,CAAG,EAAE,GACpB,UAAWd,EAAK,MAAMc,CAAG,EAAE,UAC3B,UAAW,IAAI,KAAK,EAAE,YAAY,CACpC,EACA,OAAAd,EAAK,MAAMc,CAAG,EAAIC,EAClB,KAAK,KAAKf,CAAI,EACPe,CACT,CAEA,OAAOV,EAAqB,CAC1B,IAAML,EAAO,KAAK,KAAK,EACjBgB,EAAShB,EAAK,MAAM,OAE1B,OADAA,EAAK,MAAQA,EAAK,MAAM,OAAQM,GAAMA,EAAE,KAAOD,CAAE,EAC7CL,EAAK,MAAM,SAAWgB,EAAe,IACzC,KAAK,KAAKhB,CAAI,EACP,GACT,CAGA,WAAWA,EAA2B,CACpC,IAAMiB,EAAkB,CACtB,YACA,GACA,6FACA,EACF,EACMC,EAAwD,CAC5D,MAAO,CAAC,EAAG,QAAS,CAAC,EAAG,KAAM,CAAC,EAAG,KAAM,CAAC,CAC3C,EACA,QAAWT,KAAQT,EAAK,MAAOkB,EAAQT,EAAK,MAAM,EAAE,KAAKA,CAAI,EAC7D,IAAMU,EAAmD,CACvD,CAAC,QAAS,OAAO,EAAG,CAAC,UAAW,SAAS,EAAG,CAAC,OAAQ,OAAO,EAAG,CAAC,OAAQ,MAAM,CAChF,EACA,OAAW,CAACC,EAAKC,CAAK,IAAKF,EACzB,GAAKD,EAAQE,CAAG,EAAE,OAClB,CAAAH,EAAM,KAAK,MAAMI,IAAS,EAAE,EAC5B,QAAWC,KAAMJ,EAAQE,CAAG,EAAG,CAC7B,IAAMG,EAAQH,IAAQ,OAAS,IAAM,IAC/BI,EAAUF,EAAG,SAAW,IAAIA,EAAG,YAAc,GAC7CG,EAAOH,EAAG,iBACZ,WAAW,KAAK,MAAMA,EAAG,iBAAmB,EAAE,MAC9C,GACEI,EAAMJ,EAAG,OAAS,KAAKA,EAAG,OAAO,SAASA,EAAG,OAAO,WAAWA,EAAG,OAAO,OAAS,GACxFL,EAAM,KAAK,MAAMM,MAAUC,IAAUF,EAAG,QAAQG,IAAOC,GAAK,EACxDJ,EAAG,QAAQ,KAAKL,EAAM,KAAK,UAAUK,EAAG,OAAO,MAAM,EACrDA,EAAG,OAAO,QAAQL,EAAM,KAAK,iBAAiBK,EAAG,OAAO,KAAK,IAAI,GAAG,EACpEA,EAAG,WAAWL,EAAM,KAAK,oBAAoBK,EAAG,WAAW,EAC3DA,EAAG,SAASL,EAAM,KAAK,kBAAkBK,EAAG,SAAS,EAE3DL,EAAM,KAAK,EAAE,EAEf,OAAOA,EAAM,KAAK;AAAA,CAAI,CACxB,CACF,EAQO,SAASU,GAAgBC,EAA+B,CAC7D,MAAO,GAAGA,EAAO,QAAQA,EAAO,WAAWA,EAAO,KACpD,CCrKO,SAASC,GACdC,EACAC,EACAC,EACU,CACV,IAAMC,EAAgB,CAAC,EACvB,GAAIF,EAAO,OAAS,SAAU,CAC5B,IAAMG,EAAWF,EAAO,SAAS,QAAQ,eAAiB,CAAC,EAC3D,QAAWG,KAAWL,EAAU,CAC9B,IAAMM,EAAIF,EAAS,KAAMG,GAAMA,EAAE,UAAYF,CAAO,EAChDC,GAAG,iBAAiB,OAAQH,EAAI,KAAKG,EAAE,gBAAgB,CAAC,CAAC,EACxDH,EAAI,KAAKE,CAAO,WAEdJ,EAAO,OAAS,SAAU,CACnC,IAAMG,EAAWF,EAAO,SAAS,QAAQ,eAAiB,CAAC,EAC3D,QAAWG,KAAWL,EAAU,CAC9B,IAAMM,EAAIF,EAAS,KAAMG,GAAMA,EAAE,UAAYF,CAAO,EAChDC,GAAG,iBAAiB,OAAQH,EAAI,KAAKG,EAAE,gBAAgB,CAAC,CAAC,EACxDH,EAAI,KAAKE,CAAO,GAGzB,OAAOF,CACT,CAGA,eAAsBK,GACpBC,EACAC,EACAR,EACAS,EAAoC,IAAM,CAAC,EAClB,CACzB,OAAKF,EAAK,OACNA,EAAK,OAAO,OAAS,SAAiBG,GAAaH,EAAK,OAAQC,EAAOR,EAAQS,CAAG,EAClFF,EAAK,OAAO,OAAS,SAAiBI,GAAaJ,EAAK,OAAQC,EAAOR,EAAQS,CAAG,EAC/E,CAAE,GAAI,GAAO,MAAO,uBAAwBF,EAAK,OAAe,OAAQ,SAAU,CAAC,CAAE,EAHnE,CAAE,GAAI,GAAM,SAAU,CAAC,CAAE,CAIpD,CAIA,eAAeG,GACbX,EACAS,EACAR,EACAS,EACyB,CACzB,IAAMG,EAAKZ,EAAO,SAAS,OACrBa,EAAQD,GAAI,MACZE,EAAOF,GAAI,OAASb,EAAO,KAAO,WAAWA,EAAO,OAAS,QACnE,GAAI,CAACc,GAAS,CAACC,EACb,MAAO,CAAE,GAAI,GAAO,MAAO,mCAAoC,SAAU,CAAC,CAAE,EAE9E,IAAMC,EAAuC,CAAC,EAExCC,EAAM,MAAOC,EAAcC,EAAoB,CAAC,IAC7C,MAAM,GAAGJ,WAAcG,IAAQ,CACpC,GAAGC,EACH,QAAS,CACP,gBAAiBL,EACjB,eAAgB,mBAChB,GAAIK,EAAK,SAAW,CAAC,CACvB,CACF,CAAC,EAGGC,EAAa,mBAAmBpB,EAAO,OAAO,EAGhDqB,EACAZ,EAAM,oBAAsB,SAW9BY,GAVY,MAAM,QAAQ,IAAIZ,EAAM,kBAAkB,IAAI,MAAOa,GAAM,CACrE,GAAI,CACF,IAAMC,EAAM,MAAM,MAAM,GAAGR,2BAA8B,mBAAmBO,CAAC,IAAK,CAChF,QAAS,CAAE,gBAAiBR,CAAM,CACpC,CAAC,EACD,GAAI,CAACS,EAAI,GAAI,OAAO,KACpB,IAAMC,EAAW,MAAMD,EAAI,KAAK,EAChC,OAAO,MAAM,QAAQC,CAAG,GAAKA,EAAI,CAAC,GAAG,GAAKA,EAAI,CAAC,EAAE,GAAK,IACxD,MAAE,CAAQ,OAAO,IAAK,CACxB,CAAC,CAAC,GACgB,OAAQlB,GAAmB,OAAOA,GAAM,QAAQ,GAIpE,IAAImB,EACJ,GAAIhB,EAAM,iBAAmB,OAC3B,GAAIA,EAAM,iBAAmB,MAAQA,EAAM,iBAAmB,GAC5DgB,EAAc,MAEd,IAAI,CACF,IAAMF,EAAM,MAAMN,EAAI,aAAaG,sBAA+B,mBAAmBX,EAAM,cAAc,GAAG,EAC5G,GAAIc,EAAI,GAAI,CACV,IAAMC,EAAW,MAAMD,EAAI,KAAK,EAC5B,MAAM,QAAQC,CAAG,GAAKA,EAAI,CAAC,GAAG,KAAIC,EAAcD,EAAI,CAAC,EAAE,IAE/D,OAASE,EAAP,CACAhB,EAAI,2CAA2CgB,EAAE,SAAS,CAC5D,CAKJ,IAAMC,EAAgC,CAAC,EAUvC,GATIlB,EAAM,QAAU,SAAWkB,EAAK,MAAQlB,EAAM,OAC9CA,EAAM,cAAgB,SAAWkB,EAAK,YAAclB,EAAM,aAC1DA,EAAM,SAAW,SAAWkB,EAAK,OAASlB,EAAM,OAAO,KAAK,GAAG,GAC/DA,EAAM,WAAW,SAAQkB,EAAK,WAAalB,EAAM,UAAU,KAAK,GAAG,GACnEA,EAAM,cAAc,SAAQkB,EAAK,cAAgBlB,EAAM,aAAa,KAAK,GAAG,GAC5EY,IAAgB,SAAWM,EAAK,aAAeN,GAC/CI,IAAgB,SAAWE,EAAK,aAAeF,GAC/ChB,EAAM,QAAOkB,EAAK,YAAclB,EAAM,QAAU,SAAW,QAAU,UAErE,OAAO,KAAKkB,CAAI,EAAE,OACpB,GAAI,CACF,IAAMJ,EAAM,MAAMN,EAAI,aAAaG,YAAqBpB,EAAO,MAAO,CACpE,OAAQ,MACR,KAAM,KAAK,UAAU2B,CAAI,CAC3B,CAAC,EAED,GADAX,EAAS,KAAK,CAAE,KAAM,YAAa,GAAIO,EAAI,GAAI,OAAQA,EAAI,MAAO,CAAC,EAC/D,CAACA,EAAI,GAAI,CACX,IAAMK,EAAO,MAAML,EAAI,KAAK,EAAE,MAAM,IAAM,EAAE,EAC5C,MAAO,CAAE,GAAI,GAAO,SAAAP,EAAU,MAAO,aAAaO,EAAI,WAAWK,EAAK,MAAM,EAAG,GAAG,IAAK,IAAK5B,EAAO,GAAI,EAE3G,OAAS0B,EAAP,CACA,OAAAV,EAAS,KAAK,CAAE,KAAM,YAAa,GAAI,GAAO,MAAOU,EAAE,OAAQ,CAAC,EACzD,CAAE,GAAI,GAAO,SAAAV,EAAU,MAAOU,EAAE,QAAS,IAAK1B,EAAO,GAAI,CAClE,CAGF,GAAIS,EAAM,KACR,GAAI,CACF,IAAMc,EAAM,MAAMN,EAAI,aAAaG,YAAqBpB,EAAO,YAAa,CAC1E,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,KAAMS,EAAM,IAAK,CAAC,CAC3C,CAAC,EACDO,EAAS,KAAK,CAAE,KAAM,YAAa,GAAIO,EAAI,GAAI,OAAQA,EAAI,MAAO,CAAC,CACrE,OAASG,EAAP,CACAV,EAAS,KAAK,CAAE,KAAM,YAAa,GAAI,GAAO,MAAOU,EAAE,OAAQ,CAAC,CAClE,CAGF,MAAO,CAAE,GAAIV,EAAS,MAAOa,GAAMA,EAAE,EAAE,EAAG,SAAAb,EAAU,IAAKhB,EAAO,GAAI,CACtE,CAIA,eAAeY,GACbZ,EACAS,EACAR,EACAS,EACyB,CAEzB,IAAMI,EADKb,EAAO,SAAS,QACT,MAClB,GAAI,CAACa,EACH,MAAO,CAAE,GAAI,GAAO,MAAO,8BAA+B,SAAU,CAAC,CAAE,EAEzE,IAAME,EAAuC,CAAC,EAExCC,EAAM,MAAOC,EAAcC,EAAoB,CAAC,IAC7C,MAAM,yBAAyBD,IAAQ,CAC5C,GAAGC,EACH,QAAS,CACP,cAAiB,UAAUL,IAC3B,OAAU,8BACV,uBAAwB,aACxB,GAAIK,EAAK,SAAW,CAAC,CACvB,CACF,CAAC,EAGGW,EAAO9B,EAAO,QAGhB+B,EACJ,GAAItB,EAAM,iBAAmB,OAC3B,GAAIA,EAAM,iBAAmB,MAAQA,EAAM,iBAAmB,GAC5DsB,EAAkB,SAElB,IAAI,CACF,IAAMR,EAAM,MAAMN,EAAI,UAAUa,qCAAwC,EACxE,GAAIP,EAAI,GAAI,CACV,IAAMC,EAAW,MAAMD,EAAI,KAAK,EAC1BS,EAAQ,MAAM,QAAQR,CAAG,EAAIA,EAAI,KAAMnB,GAAWA,EAAE,QAAUI,EAAM,cAAc,EAAI,KACxFuB,GAAO,SAAQD,EAAkBC,EAAM,QAE/C,OAASN,EAAP,CACAhB,EAAI,kDAAkDgB,EAAE,SAAS,CACnE,CAOJ,IAAIO,EACJ,GAAIxB,EAAM,SAAW,OACnBwB,EAAaxB,EAAM,eACVA,EAAM,WAAW,QAAUA,EAAM,cAAc,OACxD,GAAI,CACF,IAAMc,EAAM,MAAMN,EAAI,UAAUa,YAAe9B,EAAO,KAAK,EAC3D,GAAIuB,EAAI,GAAI,CACV,IAAMW,EAAa,MAAMX,EAAI,KAAK,EAC5BY,EAAoB,MAAM,QAAQD,EAAM,MAAM,EAChDA,EAAM,OAAO,IAAKE,GAAY,OAAOA,GAAM,SAAWA,EAAIA,EAAE,IAAK,EACjE,CAAC,EACCC,EAAO,IAAI,IAAI,CAAC,GAAGF,EAAS,GAAI1B,EAAM,WAAa,CAAC,CAAE,CAAC,EAC7D,QAAW6B,KAAK7B,EAAM,cAAgB,CAAC,EAAG4B,EAAK,OAAOC,CAAC,EACvDL,EAAa,CAAC,GAAGI,CAAI,EAEzB,OAASX,EAAP,CACAhB,EAAI,6CAA6CgB,EAAE,SAAS,CAC9D,CAGF,IAAMC,EAAgC,CAAC,EAQvC,GAPIlB,EAAM,QAAU,SAAWkB,EAAK,MAAQlB,EAAM,OAC9CA,EAAM,cAAgB,SAAWkB,EAAK,KAAOlB,EAAM,aACnDwB,IAAe,SAAWN,EAAK,OAASM,GACxCxB,EAAM,oBAAsB,SAAWkB,EAAK,UAAYlB,EAAM,mBAC9DsB,IAAoB,SAAWJ,EAAK,UAAYI,GAChDtB,EAAM,QAAOkB,EAAK,MAAQlB,EAAM,OAEhC,OAAO,KAAKkB,CAAI,EAAE,OACpB,GAAI,CACF,IAAMJ,EAAM,MAAMN,EAAI,UAAUa,YAAe9B,EAAO,MAAO,CAC3D,OAAQ,QACR,KAAM,KAAK,UAAU2B,CAAI,CAC3B,CAAC,EAED,GADAX,EAAS,KAAK,CAAE,KAAM,cAAe,GAAIO,EAAI,GAAI,OAAQA,EAAI,MAAO,CAAC,EACjE,CAACA,EAAI,GAAI,CACX,IAAMK,EAAO,MAAML,EAAI,KAAK,EAAE,MAAM,IAAM,EAAE,EAC5C,MAAO,CAAE,GAAI,GAAO,SAAAP,EAAU,MAAO,eAAeO,EAAI,WAAWK,EAAK,MAAM,EAAG,GAAG,IAAK,IAAK5B,EAAO,GAAI,EAE7G,OAAS0B,EAAP,CACA,OAAAV,EAAS,KAAK,CAAE,KAAM,cAAe,GAAI,GAAO,MAAOU,EAAE,OAAQ,CAAC,EAC3D,CAAE,GAAI,GAAO,SAAAV,EAAU,MAAOU,EAAE,QAAS,IAAK1B,EAAO,GAAI,CAClE,CAGF,GAAIS,EAAM,KACR,GAAI,CACF,IAAMc,EAAM,MAAMN,EAAI,UAAUa,YAAe9B,EAAO,eAAgB,CACpE,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,KAAMS,EAAM,IAAK,CAAC,CAC3C,CAAC,EACDO,EAAS,KAAK,CAAE,KAAM,eAAgB,GAAIO,EAAI,GAAI,OAAQA,EAAI,MAAO,CAAC,CACxE,OAASG,EAAP,CACAV,EAAS,KAAK,CAAE,KAAM,eAAgB,GAAI,GAAO,MAAOU,EAAE,OAAQ,CAAC,CACrE,CAGF,MAAO,CAAE,GAAIV,EAAS,MAAOa,GAAMA,EAAE,EAAE,EAAG,SAAAb,EAAU,IAAKhB,EAAO,GAAI,CACtE,CF1JA,IAAMuC,GAAW,gCACXC,GAAa,kBACbC,GAAU,4BAEhB,SAASC,GAAcC,EAAmB,CAExC,IAAMC,EAASD,EAAE,MAAM,YAAY,EAC7BE,EAASF,EAAE,MAAM,YAAY,EACnC,OAAQC,EAAS,SAASA,EAAO,CAAC,EAAG,EAAE,EAAI,KAAO,IAAMC,EAAS,SAASA,EAAO,CAAC,EAAG,EAAE,EAAI,GAAK,EAClG,CAEO,IAAMC,GAAN,KAA8C,CAOnD,YACUC,EACAC,EACAC,EAAoC,IAAM,CAAC,EACnD,CAHQ,UAAAF,EACA,YAAAC,EACA,SAAAC,EAER,KAAK,MAAQ,IAAIC,GAAa,KAAK,IAAI,CACzC,CAZA,KAAO,UACP,aAAuC,CACrC,QAAS,GAAM,OAAQ,GAAO,WAAY,GAAO,OAAQ,GAAM,OAAQ,EACzE,EACQ,MAWR,UAAyB,CACvB,OAAO,KAAK,KACd,CAEQ,QAAmB,CACzB,IAAMC,EAAOC,GAAQ,QAAQ,IAAI,EAAG,KAAK,IAAI,EAC7C,OAAKC,GAAWF,CAAI,EACbG,GAAaH,EAAM,OAAO,EAAE,MAAM;AAAA,CAAI,EADf,CAAC,CAEjC,CAEQ,QAAQI,EAAuB,CACrC,IAAMJ,EAAOC,GAAQ,QAAQ,IAAI,EAAG,KAAK,IAAI,EAC7CI,GAAcL,EAAMI,EAAM,KAAK;AAAA,CAAI,CAAC,CACtC,CAEA,MAAM,SAASE,EAAsC,CACnD,GAAI,KAAK,MAAM,OAAO,EAKpB,OAJc,KAAK,MAAM,KAAK,EAAE,OAAQC,GACtCA,EAAG,SAAW,SACb,CAACA,EAAG,UAAYA,EAAG,SAAS,YAAY,IAAMD,EAAQ,YAAY,EACrE,EACa,IAAKC,GAAO,KAAK,WAAWA,CAAE,CAAC,EAG9C,IAAMH,EAAQ,KAAK,OAAO,EACpBI,EAAoB,CAAC,EAC3B,QAASC,EAAI,EAAGA,EAAIL,EAAM,OAAQK,IAAK,CACrC,IAAMC,EAAIN,EAAMK,CAAC,EAAE,MAAMrB,EAAQ,EAGjC,GAFI,CAACsB,GACWA,EAAE,CAAC,EAAE,YAAY,IAAM,IAC1B,SACb,IAAMC,EAAOD,EAAE,CAAC,EACVE,EAAUD,EAAK,MAAMtB,EAAU,EACrC,GAAIuB,GAAWA,EAAQ,CAAC,EAAE,YAAY,IAAMN,EAAQ,YAAY,EAAG,SACnE,IAAMO,EAAYF,EAAK,MAAMrB,EAAO,EAC9BwB,EAAQH,EAAK,QAAQtB,GAAY,EAAE,EAAE,QAAQC,GAAS,EAAE,EAAE,KAAK,EACrEkB,EAAM,KAAK,CACT,GAAI,WAAWC,IACf,MAAOK,GAAS,gBAAgBL,EAAI,IACpC,SAAUG,EAAUA,EAAQ,CAAC,EAAI,OACjC,iBAAkBC,EAAYtB,GAAcsB,EAAU,CAAC,CAAC,EAAI,OAC5D,SAAUJ,CACZ,CAAC,EAEH,OAAOD,CACT,CAEA,MAAM,QAAQO,EAAqB,CAAC,EAAwB,CAC1D,OAAK,KAAK,MAAM,OAAO,EAChB,KAAK,MAAM,KAAK,EAAE,IAAKR,GAAO,KAAK,WAAWA,CAAE,CAAC,EADvB,CAAC,CAEpC,CAEA,MAAM,MAAMD,EAAiBU,EAA+B,CAC1D,GAAI,CAAC,KAAK,MAAM,OAAO,EAAG,OAC1B,IAAMC,EAAO,KAAK,MAAM,SAASD,CAAM,EACvC,GAAI,CAACC,EAAM,OACX,IAAMC,EAAU,KAAK,MAAM,OAAOF,EAAQ,CACxC,SAAUC,EAAK,UAAYX,EAC3B,OAAQ,OACV,CAAC,EACD,GAAIY,EAAQ,QAAU,KAAK,OAAQ,CACjC,IAAMC,EAAYC,GAA0B,CAACF,EAAQ,UAAYZ,CAAO,EAAGY,EAAQ,OAAQ,KAAK,MAAM,EACjGG,GAAwBH,EAAS,CACpC,kBAAmBC,EACnB,UAAW,CAAC,OAAO,EACnB,aAAc,CAAC,OAAO,CACxB,EAAG,KAAK,OAAQ,KAAK,GAAG,EAAE,MAAOG,GAC/B,KAAK,IAAI,mCAAmCN,MAAWM,GAAG,SAAWA,GAAG,CAC1E,EAEJ,CAEA,MAAM,OAAON,EAAgBO,EAAmC,CAE9D,GAAI,KAAK,MAAM,OAAO,GAAK,KAAK,MAAM,SAASP,CAAM,EAAG,CACtD,IAAMQ,EAAwD,CAAC,EAC3DD,EAAO,SAAW,OAAQC,EAAM,OAAS,OACpCD,EAAO,SAAW,WACzBC,EAAM,OAAS,UACfA,EAAM,QAAUD,EAAO,SAAWA,EAAO,MAAQ,QACxCA,EAAO,SAAW,gBAAeC,EAAM,OAAS,SACvDD,EAAO,cAAaC,EAAM,iBAAmBD,EAAO,aACxD,IAAMN,EAAO,KAAK,MAAM,OAAOD,EAAQQ,CAAK,EAE5C,GAAIP,EAAK,QAAU,KAAK,OAAQ,CAC9B,IAAMQ,EAA4B,CAAC,EAC/BF,EAAO,SAAW,QACpBE,EAAY,UAAY,CAAC,MAAM,EAC/BA,EAAY,aAAe,CAAC,OAAO,GAC1BF,EAAO,SAAW,YAC3BE,EAAY,UAAY,CAAC,SAAS,GAEhCF,EAAO,OAAME,EAAY,KAAOF,EAAO,MACvC,OAAO,KAAKE,CAAW,EAAE,QACtBJ,GAAwBJ,EAAMQ,EAAa,KAAK,OAAQ,KAAK,GAAG,EAAE,MAAOH,GAC5E,KAAK,IAAI,oCAAoCN,MAAWM,GAAG,SAAWA,GAAG,CAC3E,EAGJ,OAIF,GAAI,CAACN,EAAO,WAAW,UAAU,EAAG,MAAM,IAAI,MAAM,qBAAqBA,GAAQ,EACjF,IAAMU,EAAS,SAASV,EAAO,MAAM,CAAiB,EAAG,EAAE,EACrDZ,EAAQ,KAAK,OAAO,EACpBuB,EAAOvB,EAAMsB,CAAM,EACzB,GAAI,CAACC,EAAM,MAAM,IAAI,MAAM,gBAAgBD,WAAgB,EAE3D,GAAI,CADMC,EAAK,MAAMvC,EAAQ,EACrB,MAAM,IAAI,MAAM,gBAAgBsC,kBAAuB,EAE/D,GAAIH,EAAO,SAAW,OAAQ,CAC5BnB,EAAMsB,CAAM,EAAIC,EAAK,QAAQ,UAAW,OAAO,EAC/C,IAAMC,EAAOL,EAAO,YAChB,2BAA2B,KAAK,MAAMA,EAAO,YAAc,EAAE,SAC7D,6BACJnB,EAAM,OAAOsB,EAAS,EAAG,EAAGE,CAAI,OACvBL,EAAO,SAAW,UAC3BnB,EAAM,OAAOsB,EAAS,EAAG,EAAG,qBAAqBH,EAAO,SAAWA,EAAO,MAAQ,SAAS,EAClFA,EAAO,MAChBnB,EAAM,OAAOsB,EAAS,EAAG,EAAG,YAAYH,EAAO,KAAK,QAAQ,OAAQ,UAAK,OAAO,EAElF,KAAK,QAAQnB,CAAK,CACpB,CAEQ,WAAWG,EAAqD,CACtE,IAAMsB,EAAS,CAAE,KAAM,OAAQ,MAAO,QAAS,QAAS,SAAU,KAAM,MAAO,EAAYtB,EAAG,MAAM,EACpG,MAAO,CACL,GAAIA,EAAG,GACP,MAAOA,EAAG,MACV,YAAaA,EAAG,YAChB,SAAUA,EAAG,SACb,iBAAkBA,EAAG,iBACrB,SAAUA,EAAG,SACb,OAAQA,EAAG,OACX,MAAAsB,EACA,UAAWtB,EAAG,UACd,IAAKA,EAAG,QAAQ,IAChB,UAAWA,EAAG,UACd,MAAOA,EAAG,SAAW,OAAS,SAAW,QAC3C,CACF,CACF,EAIauB,GAAN,KAA2C,CAKhD,YAAoBC,EAAsBC,EAAc,CAApC,UAAAD,EAAsB,UAAAC,CAAe,CAJzD,KAAO,OACP,aAAuC,CACrC,QAAS,GAAO,OAAQ,GAAO,WAAY,GAAO,OAAQ,GAAO,OAAQ,EAC3E,EAGQ,KAAKC,EAAaC,EAAqB,CAC7C,GAAKhC,GAAW+B,CAAG,EACnB,QAAWE,KAASC,GAAYH,CAAG,EAAG,CACpC,IAAMjC,EAAOqC,GAAKJ,EAAKE,CAAK,EACjBG,GAAStC,CAAI,EACjB,YAAY,EACjB,KAAK,KAAKA,EAAMkC,CAAG,EACVC,EAAM,SAAS,KAAK,GAC7BD,EAAI,KAAKlC,CAAI,EAGnB,CAEA,MAAM,SAASM,EAAsC,CACnD,IAAMiC,EAAkB,CAAC,EACzB,KAAK,KAAKtC,GAAQ,QAAQ,IAAI,EAAG,KAAK,IAAI,EAAGsC,CAAK,EAClD,IAAM/B,EAAoB,CAAC,EAC3B,QAAWgC,KAAQD,EAAO,CACxB,IAAMnC,EAAQD,GAAaqC,EAAM,OAAO,EAAE,MAAM;AAAA,CAAI,EACpD,QAAS/B,EAAI,EAAGA,EAAIL,EAAM,OAAQK,IAAK,CACrC,IAAMC,EAAIN,EAAMK,CAAC,EAAE,MAAMrB,EAAQ,EACjC,GAAI,CAACsB,GAAKA,EAAE,CAAC,EAAE,YAAY,IAAM,IAAK,SACtC,IAAME,EAAUF,EAAE,CAAC,EAAE,MAAMrB,EAAU,EACjC,CAACuB,GAAWA,EAAQ,CAAC,EAAE,YAAY,IAAMN,EAAQ,YAAY,GACjEE,EAAM,KAAK,CACT,GAAI,QAAQgC,KAAQ/B,IACpB,MAAOC,EAAE,CAAC,EAAE,QAAQrB,GAAY,EAAE,EAAE,KAAK,EACzC,SAAUuB,EAAQ,CAAC,EACnB,SAAUH,CACZ,CAAC,GAGL,OAAOD,CACT,CAEA,MAAM,OAAuB,CAAc,CAE3C,MAAM,OAAOQ,EAAgBO,EAAmC,CAE9D,IAAMkB,EAAQzB,EAAO,MAAM,GAAG,EACxBU,EAAS,SAASe,EAAMA,EAAM,OAAS,CAAC,EAAG,EAAE,EAC7CD,EAAOC,EAAM,MAAM,EAAG,EAAE,EAAE,KAAK,GAAG,EACxC,GAAI,CAACvC,GAAWsC,CAAI,EAAG,OACvB,IAAMpC,EAAQD,GAAaqC,EAAM,OAAO,EAAE,MAAM;AAAA,CAAI,EAChDjB,EAAO,SAAW,QAAUnB,EAAMsB,CAAM,IAC1CtB,EAAMsB,CAAM,EAAItB,EAAMsB,CAAM,EAAE,QAAQ,UAAW,OAAO,EACxDrB,GAAcmC,EAAMpC,EAAM,KAAK;AAAA,CAAI,CAAC,EAExC,CACF,EAIasC,GAAN,KAA6C,CAMlD,YACUC,EACAC,EACAC,EAEAC,EACAhD,EACR,CANQ,UAAA6C,EACA,WAAAC,EACA,cAAAC,EAEA,oBAAAC,EACA,SAAAhD,CACP,CAZH,KAAO,SACP,aAAuC,CACrC,QAAS,GAAM,OAAQ,GAAM,WAAY,GAAM,OAAQ,GAAM,OAAQ,EACvE,EAaQ,gBAAgBiD,EAAsC,CAC5D,OAAW,CAACzC,EAAS0C,CAAK,IAAK,OAAO,QAAQ,KAAK,cAAc,EAC/D,GAAIA,EAAM,KAAMC,GAAMA,EAAE,YAAY,IAAMF,EAAS,YAAY,CAAC,EAAG,OAAOzC,CAG9E,CAEQ,SAAS4C,EAAYC,EAA2B,CACtD,IAAMC,EAAKF,EAAM,YAAY,MAAM,MAAM,GAAG,EAAE,CAAC,GAAKC,EAEhDE,EAAmB,CAAC,EACpBC,EACA,MAAM,QAAQJ,EAAM,MAAM,IACxBA,EAAM,OAAO,QAAU,OAAOA,EAAM,OAAO,CAAC,GAAM,UACpDI,EAAeJ,EAAM,OAAO,IAAKK,IAAY,CAC3C,KAAMA,EAAE,KAAM,MAAOA,EAAE,MAAO,WAAYA,EAAE,UAC9C,EAAE,EACFF,EAASC,EAAc,IAAKC,GAAMA,EAAE,IAAI,GAExCF,EAASH,EAAM,QAMnB,IAAMM,GAHsB,MAAM,QAAQN,EAAM,SAAS,GAAKA,EAAM,UAAU,OAC1EA,EAAM,UACLA,EAAM,SAAW,CAACA,EAAM,QAAQ,EAAI,CAAC,GACX,IAAKO,IAAO,CACzC,SAAUA,EAAE,SAAU,KAAMA,EAAE,KAAM,UAAWA,EAAE,UACnD,EAAE,EACIC,EAAgBF,EAAU,CAAC,GAAG,SACpC,MAAO,CACL,GAAI,UAAUJ,WAAYF,EAAM,MAChC,MAAOA,EAAM,MACb,YAAaA,EAAM,aAAe,OAGlC,SAAUQ,EAAiB,KAAK,gBAAgBA,CAAa,GAAKA,EAAiB,OACnF,UAAAF,EACA,IAAKN,EAAM,QACX,SAAUA,EAAM,QAAU,GAC1B,iBAAkBA,EAAM,YAAY,eAAiB,OACrD,OAAAG,EACA,aAAAC,EACA,UAAWJ,EAAM,WACjB,MAAOA,EAAM,QAAU,SAAW,SAAW,SAC7C,UAAWA,EAAM,WAAW,OAAS,MACvC,CACF,CAEA,MAAc,IAAItD,EAAc+D,EAAoB,CAAC,EAAiB,CACpE,IAAMC,EAAM,MAAM,MAAM,GAAG,KAAK,cAAchE,IAAQ,CACpD,GAAG+D,EACH,QAAS,CACP,gBAAiB,KAAK,MACtB,eAAgB,mBAChB,GAAIA,EAAK,SAAW,CAAC,CACvB,CACF,CAAC,EACD,GAAI,CAACC,EAAI,GAAI,MAAM,IAAI,MAAM,UAAUA,EAAI,WAAW,MAAMA,EAAI,KAAK,EAAE,MAAM,IAAM,EAAE,GAAG,EACxF,OAAOA,EAAI,KAAK,CAClB,CAEA,MAAM,SAAStD,EAAsC,CACnD,IAAMa,EAAY,KAAK,eAAeb,CAAO,GAAK,CAAC,EACnD,GAAI,CAACa,EAAU,OAAQ,MAAO,CAAC,EAE/B,IAAMX,EAAoB,CAAC,EACrBqD,EAAc,KAAK,SAAS,OAC9B,KAAK,SACL,CAAC,SAAS,EAEd,QAAWd,KAAY5B,EACrB,QAAWgC,KAAWU,EACpB,GAAI,CACF,IAAMC,EAAI,qBAAqB,mBAAmBf,CAAQ,uCACpDnD,EAAOuD,IAAY,UACrB,WAAWW,IACX,aAAa,mBAAmBX,CAAO,YAAYW,IACjDC,EAAS,MAAM,KAAK,IAAInE,CAAI,EAClC,QAAWsD,KAASa,EAAQ,CAC1B,IAAM9C,EAAO,KAAK,SAASiC,EAAOC,CAAO,EAEzClC,EAAK,SAAWX,EAChBE,EAAM,KAAKS,CAAI,EAEnB,OAASK,EAAP,CACA,KAAK,IAAI,yCAAyCyB,QAAeI,MAAY7B,EAAE,SAAS,CAC1F,CAGJ,OAAOd,CACT,CAEA,MAAM,MAAMF,EAAiBU,EAA+B,CAE1D,GAAM,CAAE,QAAAmC,EAAS,IAAAa,EAAK,KAAAC,CAAK,EAAI,KAAK,QAAQjD,CAAM,EAE5CpB,EAAO,aAAa,mBAAmBuD,CAAO,KAAKc,IAAS,QAAU,SAAW,oBAAoBD,yCAC3G,GAAI,CACF,MAAM,KAAK,IAAIpE,EAAM,CAAE,OAAQ,KAAM,CAAC,CACxC,OAAS0B,EAAP,CACA,KAAK,IAAI,yCAAyCA,EAAE,SAAS,CAC/D,CACF,CAEA,MAAM,OAAON,EAAgBO,EAAmC,CAC9D,GAAM,CAAE,QAAA4B,EAAS,IAAAa,EAAK,KAAAC,CAAK,EAAI,KAAK,QAAQjD,CAAM,EAC5CkD,EAAMD,IAAS,QAAU,SAAW,iBAG1C,GAAI1C,EAAO,aAAeA,EAAO,YAAc,EAAG,CAChD,IAAM4C,EAAO,KAAK,IAAI,EAAG,KAAK,MAAM5C,EAAO,YAAc,EAAE,CAAC,EACtD6C,EAAMD,GAAQ,GAAK,GAAG,KAAK,MAAMA,EAAO,EAAE,KAAKA,EAAO,GAAK,IAAIA,EAAO,MAAQ,KAAO,GAAGA,KAC9F,GAAI,CACF,MAAM,KAAK,IACT,aAAa,mBAAmBhB,CAAO,KAAKe,KAAOF,6BAA+B,mBAAmBI,CAAG,IACxG,CAAE,OAAQ,MAAO,CACnB,CACF,OAAS9C,EAAP,CACA,KAAK,IAAI,qCAAqCA,EAAE,SAAS,CAC3D,EAIF,GAAIC,EAAO,SAAW,OACpB,GAAI,CACF,MAAM,KAAK,IACT,aAAa,mBAAmB4B,CAAO,KAAKe,KAAOF,wCACnD,CAAE,OAAQ,KAAM,CAClB,CACF,OAAS1C,EAAP,CACA,KAAK,IAAI,wCAAwCA,EAAE,SAAS,CAC9D,SACSC,EAAO,SAAW,UAC3B,GAAI,CACF,MAAM,KAAK,IACT,aAAa,mBAAmB4B,CAAO,KAAKe,KAAOF,uBACnD,CAAE,OAAQ,KAAM,CAClB,CACF,OAAS1C,EAAP,CACA,KAAK,IAAI,2CAA2CA,EAAE,SAAS,CACjE,CAIF,GAAIC,EAAO,KACT,GAAI,CACF,MAAM,KAAK,IACT,aAAa,mBAAmB4B,CAAO,KAAKe,KAAOF,UACnD,CAAE,OAAQ,OAAQ,KAAM,KAAK,UAAU,CAAE,KAAMzC,EAAO,IAAK,CAAC,CAAE,CAChE,CACF,OAASD,EAAP,CACA,KAAK,IAAI,kCAAkCA,EAAE,SAAS,CACxD,CAEJ,CAEA,MAAM,QAAQ+C,EAAoB,CAAC,EAAwB,CACzD,IAAMC,EAAYD,EAAK,WAAa,GAC9BE,EAAe,IAAI,KAAK,KAAK,IAAI,EAAID,EAAY,GAAK,IAAQ,EAAE,YAAY,EAE5EE,EAAS,IAAI,gBACbC,EAAQJ,EAAK,OAAS,SAC5BG,EAAO,IAAI,QAASC,CAAK,EAIzBD,EAAO,IAAI,gBAAiBD,CAAY,EACxCC,EAAO,IAAI,QAAS,KAAK,EACzBA,EAAO,IAAI,WAAY,KAAK,EAC5BA,EAAO,IAAI,sBAAuB,MAAM,EACxCA,EAAO,IAAI,WAAY,YAAY,EACnCA,EAAO,IAAI,OAAQ,MAAM,EACrBH,EAAK,QAAQ,QAAQG,EAAO,IAAI,SAAUH,EAAK,OAAO,KAAK,GAAG,CAAC,EAC/DA,EAAK,WAAWG,EAAO,IAAI,YAAaH,EAAK,SAAS,EACtDA,EAAK,QAAQG,EAAO,IAAI,SAAUH,EAAK,MAAM,EAEjD,IAAMR,EAAc,KAAK,SAAS,OAAS,KAAK,SAAW,CAAC,SAAS,EAC/DrD,EAAoB,CAAC,EAC3B,QAAW2C,KAAWU,EACpB,GAAI,CACF,IAAMjE,EAAOuD,IAAY,UACrB,WAAWqB,EAAO,SAAS,IAC3B,aAAa,mBAAmBrB,CAAO,YAAYqB,EAAO,SAAS,IACjET,EAAS,MAAM,KAAK,IAAInE,CAAI,EAClC,QAAWsD,KAASa,EAAQvD,EAAM,KAAK,KAAK,SAAS0C,EAAOC,CAAO,CAAC,CACtE,OAAS7B,EAAP,CACA,KAAK,IAAI,uCAAuC6B,MAAY7B,EAAE,SAAS,CACzE,CAKF,IAAIoD,EAASlE,EACb,GAAI6D,EAAK,WAAW,OAAQ,CAC1B,IAAMM,EAAO,IAAI,IAAIN,EAAK,SAAS,EACnCK,EAASA,EAAO,OAAQjE,GAAM,EAAEA,EAAE,QAAU,CAAC,GAAG,KAAM8C,GAAMoB,EAAK,IAAIpB,CAAC,CAAC,CAAC,EAE1E,GAAIc,EAAK,oBAAqB,CAC5B,IAAMO,EAASP,EAAK,oBAAsB,KAC1CK,EAASA,EAAO,OAAQjE,GAAM,EAAEA,EAAE,QAAU,CAAC,GAAG,KAAM8C,GAAMA,EAAE,WAAWqB,CAAM,CAAC,CAAC,EAEnF,OAAOF,CACT,CAGA,MAAM,SAAS1D,EAAgB6D,EAAuC,CACpE,GAAM,CAAE,QAAA1B,EAAS,IAAAa,EAAK,KAAAC,CAAK,EAAI,KAAK,QAAQjD,CAAM,EAC5CkD,EAAMD,IAAS,QAAU,SAAW,iBAC1C,MAAM,KAAK,IACT,aAAa,mBAAmBd,CAAO,KAAKe,KAAOF,iBAAmBa,IACtE,CAAE,OAAQ,KAAM,CAClB,CACF,CAGA,MAAM,QAAQ7D,EAA4C,CACxD,GAAM,CAAE,QAAAmC,EAAS,IAAAa,CAAI,EAAI,KAAK,QAAQhD,CAAM,EAC5C,GAAI,CACF,IAAMkC,EAAQ,MAAM,KAAK,IACvB,aAAa,mBAAmBC,CAAO,YAAYa,4BACrD,EAEA,MAAO,CACL,GAFW,KAAK,SAASd,EAAOC,CAAO,EAGvC,gBAAiBD,EAAM,kBAAoB,OAC3C,UAAWA,EAAM,WACjB,OAAQA,EAAM,OAAS,CACrB,SAAUA,EAAM,OAAO,SAAU,KAAMA,EAAM,OAAO,KAAM,UAAWA,EAAM,OAAO,UACpF,EAAI,OACJ,UAAW,MAAM,QAAQA,EAAM,MAAM,EAChC,OAAOA,EAAM,OAAO,CAAC,GAAM,SAAWA,EAAM,OAAO,IAAKK,GAAWA,EAAE,IAAI,EAAIL,EAAM,OACpF,CAAC,CACP,CACF,OAAS5B,EAAP,CACA,YAAK,IAAI,qCAAqCN,MAAWM,EAAE,SAAS,EAC7D,IACT,CACF,CAGA,MAAM,WACJN,EACAQ,EASmB,CACnB,GAAM,CAAE,QAAA2B,EAAS,IAAAa,CAAI,EAAI,KAAK,QAAQhD,CAAM,EAGxC8D,EACAtD,EAAM,oBAORsD,GANgB,MAAM,QAAQ,IAAItD,EAAM,kBAAkB,IAAI,MAAO,GAAM,CACzE,GAAI,CACF,IAAMuD,EAAQ,MAAM,KAAK,IAAI,mBAAmB,mBAAmB,CAAC,GAAG,EACvE,OAAO,MAAM,QAAQA,CAAK,GAAKA,EAAM,CAAC,GAAG,GAAKA,EAAM,CAAC,EAAE,GAAK,IAC9D,MAAE,CAAQ,OAAO,IAAK,CACxB,CAAC,CAAC,GACoB,OAAQC,GAAmB,OAAOA,GAAM,QAAQ,GAIxE,IAAIC,EACJ,GAAIzD,EAAM,iBAAmB,OAC3B,GAAIA,EAAM,iBAAmB,MAAQA,EAAM,iBAAmB,GAC5DyD,EAAc,MAEd,IAAI,CACF,IAAMC,EAAK,MAAM,KAAK,IACpB,aAAa,mBAAmB/B,CAAO,sBAAsB,mBAAmB3B,EAAM,cAAc,GACtG,EACI,MAAM,QAAQ0D,CAAE,GAAKA,EAAG,CAAC,GAAG,KAAID,EAAcC,EAAG,CAAC,EAAE,GAC1D,OAAS5D,EAAP,CACA,KAAK,IAAI,wCAAwCE,EAAM,oBAAoBF,EAAE,SAAS,CACxF,CAIJ,IAAMX,EAAgC,CAAC,EACnCa,EAAM,QAAU,SAAWb,EAAK,MAAQa,EAAM,OAC9CA,EAAM,cAAgB,SAAWb,EAAK,YAAca,EAAM,aAC1DA,EAAM,SAAW,SAAWb,EAAK,OAASa,EAAM,OAAO,KAAK,GAAG,GAC/DA,EAAM,WAAW,SAAQb,EAAK,WAAaa,EAAM,UAAU,KAAK,GAAG,GACnEA,EAAM,cAAc,SAAQb,EAAK,cAAgBa,EAAM,aAAa,KAAK,GAAG,GAC5EsD,IAAgB,SAAWnE,EAAK,aAAemE,GAC/CG,IAAgB,SAAWtE,EAAK,aAAesE,GAEnD,IAAM/B,EAAQ,MAAM,KAAK,IAAI,aAAa,mBAAmBC,CAAO,YAAYa,IAAO,CACrF,OAAQ,MACR,KAAM,KAAK,UAAUrD,CAAI,CAC3B,CAAC,EACD,OAAO,KAAK,SAASuC,EAAOC,CAAO,CACrC,CAGA,MAAM,aAAqC,CACzC,IAAMgC,EAAa,IAAI,IACvB,QAAWhC,KAAW,KAAK,SACzB,GAAI,CACF,IAAMiC,EAAU,MAAM,KAAK,IACzB,aAAa,mBAAmBjC,CAAO,4BACzC,EACA,QAAWzC,KAAK0E,EACV1E,EAAE,UAAY,CAACyE,EAAW,IAAIzE,EAAE,QAAQ,GAC1CyE,EAAW,IAAIzE,EAAE,SAAU,CACzB,SAAUA,EAAE,SAAU,KAAMA,EAAE,KAAM,UAAWA,EAAE,UACnD,CAAC,CAGP,OAASY,EAAP,CACA,KAAK,IAAI,kCAAkC6B,MAAY7B,EAAE,SAAS,CACpE,CAEF,MAAO,CAAC,GAAG6D,EAAW,OAAO,CAAC,EAAE,KAAK,CAAC1B,EAAG4B,IAAM5B,EAAE,SAAS,cAAc4B,EAAE,QAAQ,CAAC,CACrF,CAGA,MAAM,gBAA2C,CAC/C,IAAMC,EAAU,IAAI,IACpB,QAAWnC,KAAW,KAAK,SACzB,GAAI,CACF,IAAM+B,EAAK,MAAM,KAAK,IACpB,aAAa,mBAAmB/B,CAAO,2BACzC,EACA,QAAWzC,KAAKwE,EACVxE,EAAE,OAAS,CAAC4E,EAAQ,IAAI5E,EAAE,KAAK,GACjC4E,EAAQ,IAAI5E,EAAE,MAAO,CACnB,GAAIA,EAAE,GAAI,MAAOA,EAAE,MAAO,MAAOA,EAAE,MAAO,QAASA,EAAE,QACvD,CAAC,CAGP,OAASY,EAAP,CACA,KAAK,IAAI,qCAAqC6B,MAAY7B,EAAE,SAAS,CACvE,CAEF,IAAMiE,EAAM,CAAC,GAAGD,EAAQ,OAAO,CAAC,EAChC,OAAAC,EAAI,KAAK,CAAC9B,EAAG4B,IACP5B,EAAE,QAAU4B,EAAE,MAAc5B,EAAE,QAAU,SAAW,GAAK,EACrDA,EAAE,MAAM,cAAc4B,EAAE,KAAK,CACrC,EACME,CACT,CAEA,MAAM,OAAOC,EAA2C,CACtD,IAAMrC,EAAUqC,EAAM,aAAe,KAAK,SAAS,CAAC,EACpD,GAAI,CAACrC,EAAS,MAAM,IAAI,MAAM,qEAAqE,EAInG,IAAIsC,EACEtE,EAAYqE,EAAM,mBAAmB,OACvCA,EAAM,kBACLA,EAAM,UAAY,KAAK,eAAeA,EAAM,QAAQ,GAAK,CAAC,GAAG,MAAM,EAAG,CAAC,EAAI,CAAC,EAC7ErE,EAAU,SAUZsE,GATgB,MAAM,QAAQ,IAAItE,EAAU,IAAI,MAAOuE,GAAM,CAC3D,GAAI,CACF,IAAMX,EAAQ,MAAM,KAAK,IAAI,mBAAmB,mBAAmBW,CAAC,GAAG,EACvE,OAAO,MAAM,QAAQX,CAAK,GAAKA,EAAM,CAAC,GAAG,GAAKA,EAAM,CAAC,EAAE,GAAK,IAC9D,OAASzD,EAAP,CACA,YAAK,IAAI,kCAAkCoE,MAAMpE,EAAE,SAAS,EACrD,IACT,CACF,CAAC,CAAC,GACqB,OAAQ0D,GAAmB,OAAOA,GAAM,QAAQ,GAIzE,IAAIW,EACJ,GAAIH,EAAM,eACR,GAAI,CACF,IAAMN,EAAK,MAAM,KAAK,IACpB,aAAa,mBAAmB/B,CAAO,sBAAsB,mBAAmBqC,EAAM,cAAc,GACtG,EACI,MAAM,QAAQN,CAAE,GAAKA,EAAG,CAAC,GAAG,KAAIS,EAAeT,EAAG,CAAC,EAAE,GAC3D,OAAS5D,EAAP,CACA,KAAK,IAAI,wCAAwCkE,EAAM,oBAAoBlE,EAAE,SAAS,CACxF,CAGF,IAAMX,EAAgC,CACpC,MAAO6E,EAAM,MACb,YAAaA,EAAM,YACnB,QAASA,EAAM,QAAU,CAAC,GAAG,KAAK,GAAG,CACvC,EACIC,GAAc,SAAQ9E,EAAK,aAAe8E,GAC1CE,IAAiB,SAAWhF,EAAK,aAAegF,GAEpD,IAAMzC,EAAQ,MAAM,KAAK,IAAI,aAAa,mBAAmBC,CAAO,WAAY,CAC9E,OAAQ,OACR,KAAM,KAAK,UAAUxC,CAAI,CAC3B,CAAC,EACD,OAAO,KAAK,SAASuC,EAAOC,CAAO,CACrC,CAEA,MAAM,WAAWnC,EAAgB4E,EAAiBC,EAAmC,CAEnF,MAAM,KAAK,eAAe7E,EAAQ4E,EAAU,CAACA,CAAO,EAAI,CAAC,EAAGC,EAAY,CAACA,CAAS,EAAI,CAAC,CAAC,CAC1F,CAGA,MAAM,eAAe7E,EAAgB8E,EAAeC,EAAiC,CACnF,GAAM,CAAE,QAAA5C,EAAS,IAAAa,EAAK,KAAAC,CAAK,EAAI,KAAK,QAAQjD,CAAM,EAC5CkD,EAAMD,IAAS,QAAU,SAAW,iBACpCO,EAAS,IAAI,gBACfsB,EAAI,QAAQtB,EAAO,IAAI,aAAcsB,EAAI,KAAK,GAAG,CAAC,EAClDC,EAAO,QAAQvB,EAAO,IAAI,gBAAiBuB,EAAO,KAAK,GAAG,CAAC,EAC1DvB,EAAO,SAAS,GACrB,MAAM,KAAK,IACT,aAAa,mBAAmBrB,CAAO,KAAKe,KAAOF,KAAOQ,EAAO,SAAS,IAC1E,CAAE,OAAQ,KAAM,CAClB,CACF,CAEQ,QAAQxD,EAAwE,CAGtF,IAAMyB,EADOzB,EAAO,QAAQ,WAAY,EAAE,EACvB,MAAM,GAAG,EACtBgD,EAAMvB,EAAM,IAAI,EAChBwB,EAAOxB,EAAM,IAAI,IAAM,KAAO,KAAO,QAE3C,MAAO,CAAE,QADOA,EAAM,KAAK,GAAG,EACZ,KAAAwB,EAAM,IAAAD,CAAI,CAC9B,CACF,EAIO,SAASgC,GACdC,EACApG,EACAC,EACY,CACZ,IAAMoG,EAAyBD,EAAS,WACxC,OAAQC,EAAG,KAAM,CACf,IAAK,UACH,OAAO,IAAIvG,GAAkBuG,EAAG,KAAMrG,EAAQC,CAAG,EAEnD,IAAK,OACH,OAAO,IAAIgC,GAAeoE,EAAG,KAAMA,EAAG,IAAI,EAE5C,IAAK,SAAU,CACb,IAAMC,EAAKtG,EAAO,SAAS,OAC3B,GAAI,CAACsG,GAAI,MACP,MAAM,IAAI,MAAM,gEAAgE,EAElF,IAAMrD,EAA2C,CAAC,EAClD,QAAWsD,KAAWD,EAAG,cACnBC,EAAQ,gBAAgB,SAC1BtD,EAAesD,EAAQ,OAAO,EAAIA,EAAQ,iBAG9C,IAAMvD,EAAWqD,EAAG,SAAS,OACzBA,EAAG,SACHC,EAAG,OAAO,IAAKE,GAAMA,EAAE,OAAO,EAClC,OAAO,IAAI3D,GAAiByD,EAAG,KAAMA,EAAG,MAAOtD,EAAUC,EAAgBhD,CAAG,CAC9E,CACF,CACF,CGnyBO,IAAMwG,GAAW,WAIjB,SAASC,GAAkBC,EAAoCC,EAA0B,CAAC,EAAW,CAC1G,GAAI,CAACD,EAAS,OAAOF,GACrB,IAAMI,EAAMD,EAAS,KAAKE,GAAKA,EAAE,KAAOH,CAAO,EAC/C,GAAIE,GAAK,OAAQ,OAAOA,EAAI,OAC5B,IAAME,EAAQJ,EAAQ,QAAQ,GAAG,EACjC,OAAOI,EAAQ,EAAIJ,EAAQ,MAAM,EAAGI,CAAK,EAAIJ,CAC/C,CAIO,SAASK,GAAaC,EAA8C,CACzE,IAAMC,EAAM,IAAI,IACVN,EAAWK,EAAS,UAAY,CAAC,EACjCE,EAAWF,EAAS,UAAY,CAAC,EACvC,QAAWH,KAAKF,EACVE,EAAE,KAASI,EAAI,IAAIJ,EAAE,EAAE,GAAGI,EAAI,IAAIJ,EAAE,GAAIJ,GAAkBI,EAAE,GAAIF,CAAQ,CAAC,GAE/E,IAAMQ,EAAQ,CAACC,EAAiBC,EAAO,IAAI,MAAsC,CAC/E,GAAIJ,EAAI,IAAIG,CAAO,EAAG,OAAOH,EAAI,IAAIG,CAAO,EAC5C,GAAIC,EAAK,IAAID,CAAO,EAAG,OACvBC,EAAK,IAAID,CAAO,EAChB,IAAME,EAAKJ,EAASE,CAAO,GAAG,UAC9B,GAAI,CAACE,EAAI,OACT,IAAMC,EAAIJ,EAAMG,EAAID,CAAI,EACxB,OAAIE,GAAGN,EAAI,IAAIG,EAASG,CAAC,EAClBA,CACT,EACA,QAAWH,KAAW,OAAO,KAAKF,CAAQ,EAAGC,EAAMC,CAAO,EAC1D,OAAOH,CACT,CAGO,SAASO,GACdC,EACAC,EACAC,EAA4B,CAAC,EACJ,CACzB,GAAI,CAACA,EAAW,OAAQ,OACxB,IAAMC,EAAOD,EAAW,OAAOE,GAAK,CAACA,EAAE,SAAWA,EAAE,UAAYJ,CAAO,EACvE,OAAOG,EAAK,KAAKC,GAAKA,EAAE,QAAUH,EAAI,QAAUG,EAAE,SAAWH,EAAI,MAAM,GAClEE,EAAK,KAAKC,GAAKA,EAAE,UAAYH,EAAI,UAAYG,EAAE,WAAaH,EAAI,QAAQ,GACxEE,EAAK,KAAKC,GAAKA,EAAE,UAAYH,EAAI,UAAYG,EAAE,WAAaH,EAAI,QAAQ,GACxEE,EAAK,KAAKC,GAAKA,EAAE,SAAW,CAACA,EAAE,QAAU,CAACA,EAAE,UAAY,CAACA,EAAE,QAAQ,CAC1E,CAOO,SAASC,GAAcC,EAAcf,EAAiC,CAC3E,IAAMgB,EAAUR,GAAaO,EAAI,QAAS,CAAE,OAAQA,EAAI,OAAQ,SAAUA,EAAI,QAAS,EAAGf,EAAS,UAAU,EAC7G,GAAIgB,EAAS,OAAOA,EAAQ,OAC5B,GAAID,EAAI,QAAS,OAAOtB,GAAkBsB,EAAI,QAASf,EAAS,QAAQ,EACxE,GAAIe,EAAI,QAAS,CACf,IAAME,EAAUlB,GAAaC,CAAQ,EAAE,IAAIe,EAAI,OAAO,EACtD,GAAIE,EAAS,OAAOA,EAEtB,OAAOzB,EACT,CAIO,SAAS0B,GAAaC,EAAmBf,EAA2B,CACzE,IAAMgB,EAAQ,OAAOD,GAAa,EAAE,EAAE,MAAM,GAAG,EAC/C,GAAIC,EAAM,OAAS,EAAG,MAAO,CAAE,QAAAhB,CAAQ,EACvC,GAAM,CAACiB,EAAOZ,EAAS,GAAGa,CAAI,EAAIF,EAC5BG,EAASD,EAAK,KAAK,GAAG,EACtBP,EAAe,CAAE,QAASX,GAAWiB,EAAO,QAAAZ,CAAQ,EAC1D,OAAIA,IAAY,UAAYA,IAAY,SAEtCM,EAAI,QAAUQ,EAAO,MAAM,GAAG,EAAE,CAAC,EACxBd,IAAY,QAAUA,IAAY,QAG3CM,EAAI,OAASQ,EAAO,MAAM,GAAG,EAAE,CAAC,GAE3BR,CACT,CAEA,IAAMS,GAAgBC,GAA+CA,IAAOjC,GAAW,WAAa,SAE7F,SAASkC,GAAaD,EAAYzB,EAAyC,CAChF,IAAMH,EAAIG,EAAS,UAAUyB,CAAE,EAC/B,MAAO,CACL,GAAAA,EACA,KAAM5B,GAAG,MAAQ4B,EACjB,KAAM5B,GAAG,MAAQ2B,GAAaC,CAAE,EAChC,qBAAsBE,GAAc9B,GAAG,aAAa,EACpD,SAAUA,GAAG,UAAY,CAAC,EAC1B,SAAU,EAAQA,CACpB,CACF,CAGO,SAAS+B,GAAY5B,EAA2C,CACrE,IAAM6B,EAAM,IAAI,IAChB,QAAWhC,KAAKG,EAAS,UAAY,CAAC,EAAG6B,EAAI,IAAIpC,GAAkBI,EAAE,GAAIG,EAAS,QAAQ,CAAC,EAC3F,QAAWO,KAAKP,EAAS,YAAc,CAAC,EAAG6B,EAAI,IAAItB,EAAE,MAAM,EAC3D,QAAWkB,KAAM,OAAO,KAAKzB,EAAS,SAAW,CAAC,CAAC,EAAG6B,EAAI,IAAIJ,CAAE,EAChE,MAAO,CAAC,GAAGI,CAAG,EAAE,KAAK,EAAE,IAAIJ,GAAMC,GAAaD,EAAIzB,CAAQ,CAAC,CAC7D,CAIO,SAAS2B,GAAcG,EAA2C,CACvE,IAAMjB,EAAI,oBAAoB,KAAK,OAAOiB,GAAK,EAAE,EAAE,KAAK,CAAC,EACzD,GAAI,CAACjB,EAAG,OACR,IAAM,EAAI,OAAOA,EAAE,CAAC,CAAC,EACrB,OAAOA,EAAE,CAAC,IAAM,IAAM,EAAIA,EAAE,CAAC,IAAM,IAAM,EAAI,GAAK,EAAI,IACxD,CCvGA,IAAMkB,GAAa,6CAGbC,GAAa,oFAaZ,SAASC,GAAmBC,EAA6B,CAC9D,GAAI,CAACA,GAAQ,CAACA,EAAK,SAAS,WAAW,EAAG,MAAO,CAAE,UAAWA,CAAK,EAInE,IAAMC,EAAU,CAAC,GAAGD,EAAK,SAASF,EAAU,CAAC,EAC7C,GAAIG,EAAQ,SAAW,EAAG,MAAO,CAAE,UAAWD,CAAK,EACnD,IAAME,EAAQD,EAAQA,EAAQ,OAAS,CAAC,EAClCE,EAAOD,EAAM,CAAC,EAEdE,EAAKC,GAAeF,CAAI,EAC9B,GAAI,CAACC,EAAI,MAAO,CAAE,UAAWJ,CAAK,EAGlC,IAAMM,EAAQJ,EAAM,OAAS,EACvBK,EAAUL,EAAM,CAAC,GAAK,GAE5B,MAAO,CAAE,WADUF,EAAK,MAAM,EAAGM,CAAK,EAAIC,EAAUP,EAAK,MAAMM,EAAQJ,EAAM,CAAC,EAAE,MAAM,GAAG,KAAK,EAC1E,GAAAE,CAAG,CACzB,CAQO,SAASI,GAA2BR,EAAsB,CAC/D,GAAI,CAACA,GAAQ,CAACA,EAAK,SAAS,WAAW,EAAG,OAAOA,EACjD,IAAMS,EAAIZ,GAAW,KAAKG,CAAI,EAC9B,OAAKS,EACET,EAAK,MAAM,EAAGS,EAAE,KAAK,EAAE,QAAQ,EADvBT,CAEjB,CAIA,SAASK,GAAeF,EAAkC,CACxD,IAAIO,EACJ,GAAI,CACFA,EAAM,KAAK,MAAMP,CAAI,CACvB,MAAE,CACA,OAAO,IACT,CACA,GAAI,CAACO,GAAO,OAAOA,GAAQ,SAAU,OAAO,KAE5C,IAAMN,EAAkB,CAAC,EAEzB,GAAI,MAAM,QAAQM,EAAI,OAAO,EAAG,CAC9B,IAAMC,EAAsB,CAAC,EACvBC,EAAqB,CAAC,EACtBC,EAA2B,CAAC,EAClC,QAAWC,KAAKJ,EAAI,QACd,CAACI,GAAK,OAAOA,EAAE,OAAU,WACzB,OAAOA,EAAE,KAAQ,UAAY,gBAAgB,KAAKA,EAAE,GAAG,EACzDH,EAAQ,KAAK,CAAE,MAAOG,EAAE,MAAO,IAAKA,EAAE,GAAI,CAAC,EAClC,OAAOA,EAAE,OAAU,UAAYA,EAAE,MAAM,KAAK,EACrDF,EAAQ,KAAK,CAAE,MAAOE,EAAE,MAAO,MAAOA,EAAE,MAAM,KAAK,CAAE,CAAC,EAC7CA,EAAE,QAAU,MACrBD,EAAe,KAAKC,EAAE,KAAK,GAG3BH,EAAQ,SAAQP,EAAG,QAAUO,GAC7BC,EAAQ,SAAQR,EAAG,QAAUQ,GAC7BC,EAAe,SAAQT,EAAG,eAAiBS,GAGjD,GAAIH,EAAI,MAAQ,OAAOA,EAAI,MAAS,SAAU,CAC5C,IAAMK,EAAIL,EAAI,KAAK,SACbM,EAAON,EAAI,KAAK,QAClB,OAAOK,GAAM,UAAYA,EAAE,KAAK,GAAK,MAAM,QAAQC,CAAI,GAAKA,EAAK,OAAQC,GAAe,OAAOA,GAAM,QAAQ,EAAE,QAAU,IAC3Hb,EAAG,KAAO,CACR,SAAUW,EACV,QAASC,EAAK,OAAQC,GAAe,OAAOA,GAAM,QAAQ,EAC1D,SAAUP,EAAI,KAAK,WAAa,EAClC,GAIJ,GAAIA,EAAI,OAAS,OAAOA,EAAI,OAAU,SAAU,CAC9C,GAAM,CAAE,KAAAQ,EAAM,IAAAC,EAAK,QAAAC,CAAQ,EAAIV,EAAI,MAC/B,CAAC,QAAS,WAAY,QAAS,OAAO,EAAE,SAASQ,CAAI,GAAK,OAAOC,GAAQ,UAAYA,IACvFf,EAAG,MAAQ,CAAE,KAAAc,EAAM,IAAAC,EAAK,GAAI,OAAOC,GAAY,SAAW,CAAE,QAAAA,CAAQ,EAAI,CAAC,CAAG,GAIhF,GAAI,MAAM,QAAQV,EAAI,YAAY,EAAG,CACnC,IAAMW,EAAQX,EAAI,aAAa,OAAQK,GAAe,OAAOA,GAAM,UAAYA,EAAE,KAAK,CAAC,EAAE,IAAKA,GAAcA,EAAE,KAAK,CAAC,EAChHM,EAAM,SAAQjB,EAAG,aAAeiB,GAMtC,MAAI,CAACjB,EAAG,SAAW,CAACA,EAAG,MAAQ,CAACA,EAAG,OAAS,CAACA,EAAG,cAAgB,CAACA,EAAG,SAAW,CAACA,EAAG,eAAuB,KACnGA,CACT,CC7FO,SAASkB,GAAWC,EAAsBC,EAAyB,CACxE,IAAMC,EAAO,GAAGD,aAAmB,mBAAmBD,EAAO,IAAI,YAAY,mBAAmBA,EAAO,MAAM,IAC7G,OAAOA,EAAO,SAAW,SACrB,aAAMA,EAAO,uDAAkDE,YAC/D,aAAMF,EAAO,uCAAkCE,GACrD,CAMA,IAAMC,GAAoB,EAAI,IASvB,SAASC,EAAcC,EAAsB,CAClD,OAAOA,EAAK,YAAY,EAAE,QAAQ,aAAc,EAAE,CACpD,CAEO,IAAMC,GAAN,KAAyB,CACtB,KAAwB,IAAI,IAC5B,IACA,UAEA,UAGA,eAGA,QAAyC,IAAI,IAC7C,aAEA,YAEA,YAAmC,IAAI,IAE/C,YACEC,EACAC,EAA2B,CAAC,EAC5BC,EAAoC,QAAQ,MAAM,KAAK,QAAS,UAAU,EAC1E,CACA,KAAK,UAAYF,EACjB,KAAK,eAAiB,IAAI,IAAIC,EAAe,IAAIJ,CAAa,CAAC,EAC/D,KAAK,IAAMK,EAEX,KAAK,aAAe,YAAY,IAAM,KAAK,eAAe,EAAG,GAAM,CACrE,CAEQ,WAAWC,EAAgBC,EAA2B,CAC5D,MAAO,GAAGD,KAAUN,EAAcO,CAAS,GAC7C,CAEQ,gBAAuB,CAC7B,IAAMC,EAAM,KAAK,IAAI,EACrB,OAAW,CAACC,EAAKC,CAAM,IAAK,KAAK,QAAS,CACxC,IAAMC,EAAOD,EAAO,OAAOE,GAAKA,EAAE,UAAYJ,CAAG,EAC7CG,EAAK,SAAW,EAAG,KAAK,QAAQ,OAAOF,CAAG,EACrCE,EAAK,SAAWD,EAAO,QAAQ,KAAK,QAAQ,IAAID,EAAKE,CAAI,EAEpE,OAAW,CAACL,EAAQO,CAAM,IAAK,KAAK,YAC9BA,GAAUL,GAAK,KAAK,YAAY,OAAOF,CAAM,CAErD,CAEA,aAAaQ,EAAiC,CAC5C,KAAK,UAAYA,CACnB,CAEA,eAAeA,EAAuB,CACpC,KAAK,YAAcA,CACrB,CAGA,UAAUR,EAAgBC,EAAmBQ,EAAiC,CAC5E,OAAO,KAAK,cAAcT,EAAQC,EAAW,CAAE,KAAM,MAAO,IAAAQ,CAAI,CAAC,CACnE,CAIA,kBAAkBT,EAAgBC,EAAmBS,EAAqD,CACxG,OAAO,KAAK,cAAcV,EAAQC,EAAW,CAAE,KAAM,WAAY,QAAAS,CAAQ,CAAC,CAC5E,CAEQ,cAAcV,EAAgBC,EAAmBU,EAAkC,CACzF,IAAMC,EAAkB,CAAE,OAAAZ,EAAQ,UAAAC,EAAW,KAAAU,CAAK,EAClD,KAAK,KAAK,IAAIC,CAAG,EACjB,IAAMC,EAAMF,EAAK,OAAS,MAAQ,MAAQ,WAC1C,KAAK,IAAI,aAAaE,WAAab,QAAaC,MAAc,KAAK,KAAK,aAAa,EAKrF,IAAME,EAAM,KAAK,WAAWH,EAAQC,CAAS,EACvCa,EAAW,KAAK,QAAQ,IAAIX,CAAG,EACrC,GAAIW,GAAYA,EAAS,OAAQ,CAC/B,IAAMZ,EAAM,KAAK,IAAI,EACfa,EAAQD,EAAS,OAAOR,GAAKA,EAAE,UAAYJ,CAAG,EACpD,OAAW,CAAE,OAAAZ,CAAO,IAAKyB,EACvB,KAAK,UAAUH,EAAKtB,CAAM,EAE5B,KAAK,QAAQ,OAAOa,CAAG,EACnBY,EAAM,QAAQ,KAAK,IAAI,WAAWA,EAAM,oDAAoDf,QAAaC,GAAW,EAG1H,MAAO,IAAM,CACX,KAAK,KAAK,OAAOW,CAAG,EACpB,KAAK,IAAI,eAAeC,WAAab,QAAaC,MAAc,KAAK,KAAK,iBAAiB,CAC7F,CACF,CAIQ,UAAUW,EAAiBtB,EAA+B,CAChE,GAAI,CACF,OAAIsB,EAAI,KAAK,OAAS,MACpBA,EAAI,KAAK,IAAI,MAAM;AAAA,QAAwB,KAAK,UAAUtB,CAAM;AAAA;AAAA,CAAO,EAEvEsB,EAAI,KAAK,QAAQtB,CAAM,EAElB,EACT,MAAE,CACA,MAAO,EACT,CACF,CAKA,MAAM,eAAeA,EAAgE,CAInF,IAAM0B,EAAgBtB,EAAcJ,EAAO,EAAE,EAE7C,GADoB0B,IAAkBtB,EAAc,KAAK,SAAS,GAAK,KAAK,iBAAiBJ,EAAO,OAAQ0B,CAAa,EAEvH,YAAK,OAAO1B,CAAM,EACX,CAAE,GAAI,EAAK,EAEpB,GAAI,CAAC,KAAK,UACR,MAAO,CAAE,GAAI,GAAO,MAAO,0BAA2B,EAExD,IAAM2B,EAAK,MAAM,KAAK,UAAU3B,EAAO,GAAIA,CAAM,EACjD,OAAO2B,EAAK,CAAE,GAAAA,CAAG,EAAI,CAAE,GAAI,GAAO,MAAO,eAAe3B,EAAO,YAAa,CAC9E,CAEQ,iBAAiBU,EAAgBkB,EAAsC,CAC7E,QAAWN,KAAO,KAAK,KACrB,GAAIA,EAAI,SAAWZ,GAAUN,EAAckB,EAAI,SAAS,IAAMM,EAAqB,MAAO,GAE5F,MAAO,EACT,CAGA,eAAe5B,EAAuD,CAIpE,IAAKA,EAAO,OAAS,SAAWA,EAAO,OAAS,SAAW,KAAK,eAAe,KAAO,GAChF,CAAC,KAAK,eAAe,IAAII,EAAcJ,EAAO,IAAI,CAAC,EACrD,YAAK,IAAI,kBAAkBA,EAAO,cAAcA,EAAO,8BAA8B,EAC9E,CAAE,GAAI,GAAO,MAAO,oBAAqB,EAOpD,GAAIA,EAAO,OAAS,OAAQ,CAC1B,IAAMY,EAAM,KAAK,IAAI,EACfK,EAAS,KAAK,YAAY,IAAIjB,EAAO,MAAM,EACjD,GAAIiB,GAAUA,EAASL,EACrB,YAAK,IAAI,uBAAuBZ,EAAO,2BAA2B,EAC3D,CAAE,GAAI,EAAK,EAGpB,GADA,KAAK,YAAY,IAAIA,EAAO,OAAQY,EAAMT,EAAiB,EACvD,KAAK,YACP,GAAI,CAAO,KAAK,YAAYH,CAAM,CAAE,OAC7B6B,EAAP,CAAiB,KAAK,IAAI,sBAAsBA,EAAE,SAAS,CAAE,MAE7D,KAAK,IAAI,0BAA0B7B,EAAO,6BAA6B,EAEzE,MAAO,CAAE,GAAI,EAAK,EAIpB,GADkB,KAAK,OAAOA,CAAM,IAClB,EAAG,CAKnB,IAAMa,EAAM,KAAK,WAAWb,EAAO,OAAQA,EAAO,EAAE,EAC9Cc,EAAS,KAAK,QAAQ,IAAID,CAAG,GAAK,CAAC,EACzCC,EAAO,KAAK,CAAE,OAAAd,EAAQ,UAAW,KAAK,IAAI,EAAI,GAAc,CAAC,EAC7D,KAAK,QAAQ,IAAIa,EAAKC,CAAM,EAC5B,KAAK,IAAI,YAAYd,EAAO,iBAAiBA,EAAO,aAAaA,EAAO,0BAA0Bc,EAAO,iBAAiB,EAE5H,MAAO,CAAE,GAAI,EAAK,CACpB,CAEQ,OAAOd,EAA8B,CAC3C,IAAI8B,EAAY,EACVJ,EAAgBtB,EAAcJ,EAAO,EAAE,EAC7C,QAAWsB,KAAO,KAAK,KACjBA,EAAI,SAAWtB,EAAO,QACtBI,EAAckB,EAAI,SAAS,IAAMI,IACjC,KAAK,UAAUJ,EAAKtB,CAAM,EAAG8B,IAC5B,KAAK,KAAK,OAAOR,CAAG,GAE3B,OAAOQ,CACT,CAGA,IAAI,iBAA0B,CAC5B,OAAO,KAAK,KAAK,IACnB,CAKA,UAAiB,CACX,KAAK,cAAc,cAAc,KAAK,YAAY,EACtD,QAAWR,KAAO,KAAK,KACrB,GAAIA,EAAI,KAAK,OAAS,MACpB,GAAI,CAAEA,EAAI,KAAK,IAAI,IAAI,CAAE,MAAE,CAA6B,CAG5D,KAAK,KAAK,MAAM,EAChB,KAAK,QAAQ,MAAM,CACrB,CACF,ECtTA,OAAS,aAAAS,GAAW,gBAAAC,GAAc,cAAAC,GAAY,UAAAC,GAAQ,iBAAAC,OAAqB,KAC3E,OAAS,WAAAC,OAAe,KACxB,OAAS,WAAAC,GAAS,QAAAC,OAAY,OAoBvB,IAAMC,GAAYC,GAAKC,GAAQ,EAAG,UAAW,WAAW,EAExD,SAASC,GAASC,EAAOJ,GAAWK,EAAM,KAAK,IAAI,EAAe,CACvE,GAAI,CACF,IAAMC,EAAO,KAAK,MAAMC,GAAaH,EAAM,OAAO,CAAC,EACnD,GAAIE,GAAM,SAAW,IAAQ,EAAEA,EAAK,OAAS,KAAK,MAAMA,EAAK,KAAK,GAAKD,GACrE,MAAO,CAAE,OAAQ,GAAM,KAAM,OAAQ,OAAQ,YAAYC,EAAK,OAAS,KAAKA,EAAK,SAAW,IAAK,CAErG,MAAE,CAEF,CACA,MAAO,CAAE,OAAQ,GAAO,KAAM,KAAM,OAAQ,eAAgB,CAC9D,CAGO,SAASE,GAASC,EAA0BC,GAAWC,EAAyBR,GAAsB,CAC3G,IAAMS,EAAIH,EAAM,EAChB,OAAOG,EAAE,OAASA,EAAID,EAAK,CAC7B,CAIO,SAASE,GAAcC,EAAuBC,EAAeX,EAAOJ,GAAWK,EAAM,KAAK,IAAI,EAAS,CAC5G,IAAIW,EAAe,KACnB,GAAI,CAAEA,EAAU,KAAK,MAAMT,GAAaH,EAAM,OAAO,CAAC,CAAE,MAAE,CAAmB,CAC7E,GAAIY,GAAWA,EAAQ,QAAUA,EAAQ,SAAW,SAAU,OAC9D,GAAI,CAACF,EAAQ,CACPE,GAASC,GAAOb,EAAM,CAAE,MAAO,EAAK,CAAC,EACzC,OAEFc,GAAUC,GAAQf,CAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EAC5C,IAAMgB,EAAM,GAAGhB,KAAQ,QAAQ,UAC/BiB,GAAcD,EAAK,KAAK,UAAU,CAAE,OAAQ,GAAM,OAAQ,SAAU,OAAAN,EAAQ,MAAO,IAAI,KAAKT,EAAMU,CAAK,EAAE,YAAY,CAAE,CAAC,EAAI;AAAA,CAAI,EAChIO,GAAWF,EAAKhB,CAAI,CACtB,CCjCO,IAAMmB,GAAwC,CACnD,UAAW,EACX,OAAQ,GACR,SAAU,GAAK,GAAK,IACpB,UAAW,EAAI,GAAK,GACtB,EAMaC,GAAN,KAAyB,CAI9B,YAAoBC,EAAyBF,GAA6BG,EAAoB,KAAK,IAAK,CAApF,UAAAD,EAAsD,SAAAC,CAA+B,CAHjG,QAAU,IAAI,IACd,IAAc,CAAE,SAAU,CAAC,EAAG,YAAa,CAAE,EAKrD,WAAWC,EAAwB,CACjC,IAAMD,EAAM,KAAK,IAAI,EACfE,EAAQ,KAAK,IAAI,KAAK,IAAI,YAAa,KAAK,QAAQ,IAAID,CAAM,GAAG,aAAe,CAAC,EACvF,OAAOC,EAAQF,EAAM,KAAK,MAAME,EAAQF,GAAO,GAAI,EAAI,CACzD,CAEA,KAAKC,EAAsB,CACzB,IAAIE,EAAS,KAAK,QAAQ,IAAIF,CAAM,EAC/BE,GAAQ,KAAK,QAAQ,IAAIF,EAASE,EAAS,CAAE,SAAU,CAAC,EAAG,YAAa,CAAE,CAAE,EACjF,KAAK,KAAKA,EAAQ,KAAK,KAAK,SAAS,EACrC,KAAK,KAAK,KAAK,IAAK,KAAK,KAAK,MAAM,CACtC,CAEA,QAAQF,EAAsB,CAC5B,KAAK,QAAQ,OAAOA,CAAM,CAC5B,CAEQ,KAAKE,EAAgBC,EAAqB,CAChD,IAAMJ,EAAM,KAAK,IAAI,EACrBG,EAAO,SAAWA,EAAO,SAAS,OAAQE,GAAML,EAAMK,EAAI,KAAK,KAAK,QAAQ,EAC5EF,EAAO,SAAS,KAAKH,CAAG,EACpBG,EAAO,SAAS,QAAUC,IAAOD,EAAO,YAAcH,EAAM,KAAK,KAAK,UAC5E,CACF,EAgBaM,GAAmB,yEAC1BC,GAAS,IAEf,eAAsBC,GAAeC,EAAsBC,EAA6C,CACtG,IAAMC,EAAU,KAAK,IAAI,EACnBC,EAAMF,EAAK,MAASG,GAAiB,QAAQ,IAAIA,CAAI,GACrDZ,EAASQ,EAAI,OAAO,eAAiB,UAErCK,EAAOJ,EAAK,QAAQ,WAAWT,CAAM,EAC3C,GAAIa,EAAO,EACT,OAAAF,EAAI,gCAAgCX,qBAA0Ba,SAAY,EACnE,CAAE,OAAQ,IAAK,KAAM,CAAE,MAAO,oBAAqB,WAAYA,CAAK,EAAG,WAAYA,CAAK,EAGjG,IAAIC,EAAgB,KACpB,GAAI,CAAEA,GAAQ,MAAMC,GAASP,EAAK,IAAI,GAAG,IAAK,MAAE,CAAqC,CACrF,IAAMQ,EAAWP,EAAK,MAAM,OAAOK,CAAI,EACjCG,EAASD,EAAWP,EAAK,OAAOO,EAAS,KAAK,EAAI,KAGxD,OAFA,MAAME,GAAMR,EAASD,EAAK,OAASH,EAAM,EAErC,CAACU,GAAY,CAACC,GAChBR,EAAK,QAAQ,KAAKT,CAAM,EACxBW,EAAI,+BAA+BX,GAAQ,EACpC,CAAE,OAAQ,IAAK,KAAM,CAAE,MAAOK,EAAiB,CAAE,IAE1DI,EAAK,QAAQ,QAAQT,CAAM,EAC3BW,EAAI,0BAA0BK,EAAS,YAAYC,WAAgBjB,GAAQ,EACpE,CAAE,OAAQ,IAAK,KAAM,CAAE,OAAAiB,CAAO,EAAG,MAAOD,EAAS,KAAM,EAChE,CAEA,eAAeE,GAAMR,EAAiBS,EAA8B,CAClE,IAAMC,EAAOV,EAAUS,EAAQ,KAAK,IAAI,EACpCC,EAAO,GAAG,MAAM,IAAI,QAASC,GAAM,WAAWA,EAAGD,CAAI,CAAC,CAC5D,CCjHA,OAAS,YAAAE,OAAgB,gBCAzB,OAAS,eAAAC,GAAa,UAAAC,GAAQ,iBAAAC,OAAqB,KACnD,OAAS,UAAAC,OAAc,KACvB,OAAS,QAAAC,OAAY,OCFrB,OAAS,eAAAC,OAAmB,SCYrB,IAAMC,EAAW,CAAE,IAAK,EAAG,KAAM,GAAI,OAAQ,EAAG,EAM1CC,GAAY,CAAE,OAAQ,GAAI,KAAM,EAAG,EAEnCC,GAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBA8BLF,EAAS,SAASA,EAAS,UAAUA,EAAS;AAAA;AAAA;AAAA;AAAA,wFAIuBA,EAAS,IAAMA,EAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EDhBhH,IAAMG,GAAQC,GAAYA,EAAE,mBAAmB,QAAS,CAAE,KAAM,UAAW,OAAQ,SAAU,CAAC,EACxFC,GAAOD,GAAY,GAAGA,EAAE,QAAQ,KAAKA,EAAE,mBAAmB,QAAS,CAAE,MAAO,OAAQ,CAAC,IAG3F,SAASE,GAAMC,EAAaC,EAAqB,CAC/C,IAAMJ,EAAI,IAAI,KAAKG,CAAG,EACtB,OAAI,OAAO,MAAMH,EAAE,QAAQ,CAAC,EAAU,GAC/B,SAASA,EAAE,aAAa,IAAM,IAAI,KAAKI,CAAG,EAAE,aAAa,EAAIL,GAAKC,CAAC,EAAIC,GAAID,CAAC,GACrF,CAGA,SAASK,GAAKF,EAAaC,EAAqB,CAC9C,IAAMJ,EAAI,IAAI,KAAKG,CAAG,EAChBG,EAAON,EAAE,QAAQ,EAAII,EAAM,EAAI,MACrC,MAAO,CAACJ,EAAE,mBAAmB,QAAS,CAAE,QAAS,OAAQ,CAAC,EAAG,GAAIM,EAAO,CAAC,EAAI,CAACL,GAAID,CAAC,CAAC,EAAID,GAAKC,CAAC,CAAC,EAAE,KAAK,GAAG,CAC3G,CAEA,SAASO,GAASC,EAAsB,CACtC,IAAMC,EAAQD,EAAK,KAAK,EAAE,MAAM,SAAS,EAAE,OAAO,OAAO,EACzD,OAAQC,EAAM,OAAS,EAAIA,EAAM,CAAC,EAAE,CAAC,EAAIA,EAAM,CAAC,EAAE,CAAC,EAAID,EAAK,KAAK,EAAE,MAAM,EAAG,CAAC,GAAG,YAAY,CAC9F,CAaO,SAASE,GAAYC,EAAmF,CAC7G,IAAMC,GAAQD,EAAK,WAAa,IAAI,KAAK,EACnCE,EAAMD,EAAK,YAAY,EACvBE,EAAUH,EAAK,SAAW,CAAC,EAC7BI,EAAQ,GAKZ,GAJAD,EAAQ,QAAQ,CAACE,EAAGC,IAAM,CACpB,CAACJ,EAAI,WAAWG,EAAE,YAAY,CAAC,GAAK,CAAC,0BAA0B,KAAKJ,EAAK,MAAMI,EAAE,MAAM,CAAC,IACxFD,EAAQ,GAAKC,EAAE,OAASF,EAAQC,CAAK,EAAE,UAAQA,EAAQE,EAC7D,CAAC,EACGF,EAAQ,EAAG,MAAO,CAAE,MAAAA,EAAO,IAAKH,CAAK,EACzC,IAAMM,EAAMN,EAAK,MAAME,EAAQC,CAAK,EAAE,MAAM,EAAE,QAAQ,gBAAiB,EAAE,EACzE,MAAO,CAAE,MAAAA,EAAO,IAAKG,EAAI,MAAM,EAAG,CAAC,EAAE,YAAY,EAAIA,EAAI,MAAM,CAAC,CAAE,CACpE,CAEA,SAASC,GAAaR,EAA4B,CAChD,OAAIA,EAAK,QAAQ,OAAS,UAAkB,eACrCA,EAAK,MAAQ,OAASA,EAAK,SAAS,OAAS,SAAW,KACjE,CAEA,IAAMS,GAAQC,GACZ,qJAAqJA,UACjJC,GAAQF,GAAK,wDAAwD,EACrEG,GAAOH,GAAK,2BAA2B,EACvCI,GAAOJ,GAAK,uFAAuF,EAElG,SAASK,GAAed,EAAoBe,EAAwB,CAAC,EAAW,CACrF,IAAMtB,EAAMsB,EAAK,KAAO,KAAK,IAAI,EAC3BC,EAAOD,EAAK,MAAQf,EAAK,UACzBiB,EAAQC,GAAY,EAAE,EAAE,SAAS,QAAQ,EACzCf,EAAUH,EAAK,SAAW,CAAC,EAC3BmB,EAAWnB,EAAK,QAAQ,OAAS,WAEjCoB,EAAUpB,EAAK,QAAQ,OAAS,UAChCqB,EAAMtB,GAAYC,CAAI,EACtBsB,EAAUnB,EAAQ,IAAI,CAACE,EAAGC,IAC9B,iEAAiEA,sBAAsBA,EAAI,2BAA2BiB,EAAIlB,CAAC,WAAWC,IAAMe,EAAI,MAAQ,sBAAwB,aAClL,EAAE,KAAK,EAAE,EACHG,EAAWxB,EAAK,UAAY,wCAAwCuB,EAAIP,CAAI,sBAAwB,GACpGS,EAAO,CACX,QAAAtB,EACA,MAAOH,EAAK,OAAS,GACrB,YAAa0B,GACb,MAAOX,EAAK,QAAU,QACtB,OAAQ,KAAK,IAAI,EAAG,KAAK,IAAI,EAAGA,EAAK,QAAU,EAAG,CAAC,EACnD,MAAOA,EAAK,OAAS,SAErB,KAAMA,EAAK,MAAQA,EAAK,MAAQZ,EAAQ,OAASY,EAAK,KAAO,EAAIM,EAAI,KACvE,EACA,MAAO;AAAA;AAAA,uHAE8GJ;AAAA;AAAA,SAE9GU;AAAA,2CACkCZ,EAAK,MAAQ,SAAW;AAAA,8DACLQ,EAAI3B,GAASoB,CAAI,CAAC;AAAA,sBAC1DO,EAAIP,CAAI,cAAcO,EAAIhC,GAAMS,EAAK,WAAYP,CAAG,CAAC;AAAA,mBACxD0B,EAAW,GAAK,gBAAgBA,EAAW,WAAaC,EAAU,UAAY;AAAA,kGACCR;AAAA,mCAC/DW,EAAIvB,EAAK,KAAK;AAAA,EAC/CA,EAAK,QAAU,iCAAiCuB,EAAIvB,EAAK,OAAO,QAAU;AAAA,4BAChDuB,EAAIvB,EAAK,GAAG;AAAA,EACtCsB,GAAWE,EAAW,0BAA0BF,EAAU,sCAAwC,KAAKE,UAAmB;AAAA,EAC1HF,EAAU,0CAA0CA,UAAkB;AAAA,EACtED,EAAI,IAAM,6BAA6BE,EAAIF,EAAI,GAAG,QAAU;AAAA,EAC5DrB,EAAK,MAAQ;AAAA,uEAC0D;AAAA,0BAC/CW,WAAcS,EAAU,sEAAwE,0BAA0BG,EAAI7B,GAAKM,EAAK,QAASP,CAAG,CAAC,MAAM8B,EAAIvB,EAAK,SAAS;AAAA,OAChMa;AAAA;AAAA,8BAEuBO,EAAU,OAAS;AAAA,uCACVZ,GAAaR,CAAI;AAAA;AAAA,4CAEZ,KAAK,UAAUyB,CAAI,EAAE,QAAQ,KAAM,SAAS;AAAA,iBACvER,MAAUW;AAAA,eAE3B,CAGA,IAAMA,GAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EDpHb,IAAMC,GAAa,IAEbC,GAAa;AAAA;AAAA;AAAA,+BAGKC,EAAS,mBAAmBA,EAAS,kBAAkBA,EAAS,IAAMC,GAAU,kBAAkBA,GAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiFpI,SAASC,GAAYC,EAAgBC,EAAiC,CAC3E,IAAMC,EAAOF,EAAO,KAAK,EAAE,MAAM;AAAA,CAAI,EAAE,IAAI,GAAK,GAC5CG,EACJ,GAAI,CAAEA,EAAM,KAAK,MAAMD,CAAI,CAAE,MAAE,CAAQ,MAAO,CAAE,OAAQ,SAAU,CAAE,CACpE,GAAIC,GAAK,SAAW,KAAM,MAAO,CAAE,OAAQ,IAAK,EAChD,GAAIA,GAAK,SAAW,WAAaC,GAAgB,SAASD,EAAI,GAAG,EAAG,MAAO,CAAE,OAAQ,UAAW,IAAKA,EAAI,GAAI,EAC7G,GAAIA,GAAK,SAAW,MAAO,MAAO,CAAE,OAAQ,SAAU,EACtD,IAAME,EAAUJ,EAAK,SAAW,CAAC,EAC7BK,EACJ,GAAID,EAAQ,OAAQ,CAClB,GAAI,OAAOF,EAAI,QAAW,UAAY,CAACE,EAAQ,SAASF,EAAI,MAAM,EAAG,MAAO,CAAE,OAAQ,SAAU,EAChGG,EAASH,EAAI,OAEf,IAAII,EACJ,OAAIN,EAAK,QACPM,EAAO,OAAOJ,EAAI,MAAS,SAAWA,EAAI,KAAK,QAAQ,SAAU;AAAA,CAAI,EAAE,KAAK,EAAI,GAE5E,CAACI,GAAQA,EAAK,OAASC,GAAc,MAAa,CAAE,OAAQ,SAAU,EAErE,CAAE,OAAQ,MAAO,GAAIF,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,EAAI,GAAIC,EAAO,CAAE,KAAAA,CAAK,EAAI,CAAC,CAAG,CACnF,CAMA,eAAsBE,GAAeR,EAAoBS,EAA8BC,EAAwC,CAC7H,IAAMC,EAAU,KAAK,IAAI,GAAI,KAAK,MAAMF,EAAS,cAAc,CAAC,EAC1DG,EAAMC,GAAYC,GAAKC,GAAO,EAAG,cAAc,CAAC,EACtD,GAAI,CACF,IAAMC,EAAOF,GAAKF,EAAK,WAAW,EAClCK,GAAcD,EAAME,GAAelB,EAAM,CAAE,GAAGS,EAAU,MAAO,CAAC,CAACA,EAAS,OAAQ,CAAC,EAAG,CAAE,KAAM,GAAM,CAAC,EACrG,IAAMU,EAAO,CAAC,KAAM,aAAc,KAAMxB,GAAYqB,EAAM,OAAOtB,EAAU,EAAG,OAAOiB,CAAO,EAAG,GAAIF,EAAS,QAAU,CAACA,EAAS,OAAO,EAAI,CAAC,CAAE,EACxIW,EAAI,MAAMV,EAAK,qBAAsBS,GAAOR,EAAU,IAAM,GAAI,EACtE,OAAKS,EAAE,GACAtB,GAAYsB,EAAE,OAAQpB,CAAI,EADf,IAEpB,QAAE,CACAqB,GAAOT,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,CAC9C,CACF,CDjIO,IAAMU,GAAkB,CAAC,UAAW,YAAa,QAAQ,EA2B1DC,GAAW,CAACC,EAAMC,EAAMC,IAC5B,IAAI,QAASC,GAAS,CACpB,GAAI,CACFC,GAASJ,EAAMC,EAAM,CAAE,QAASC,CAAU,EAAG,CAACG,EAAKC,IAAWH,EAAK,CAAE,GAAI,CAACE,EAAK,OAAQ,OAAOC,GAAU,EAAE,CAAE,CAAC,CAAC,CAChH,MAAE,CACAH,EAAK,CAAE,GAAI,GAAO,OAAQ,EAAG,CAAC,CAChC,CACF,CAAC,EAGGI,GAAU,IAEVC,GAAiB,KAEhB,SAASC,GAAWC,EAAmD,CAC5E,IAAMC,GAAQD,EAAK,KAAOA,EAAK,OAAO,QAAQ,OAAQ,GAAG,EAAE,KAAK,EAChE,OAAOC,EAAK,OAASJ,GAAUI,EAAK,MAAM,EAAGJ,EAAO,EAAII,CAC1D,CAIA,eAAeC,GAAIF,EAAoBG,EAAkBC,EAAWC,EAAiBC,EAA6C,CAChI,IAAMC,EAAOJ,EAAE,QAAU,QAAWE,EAAS,GAAK,QAAWF,EAAE,MACzDK,EAAOD,EAAOE,GAAUF,CAAI,EAAI,KAEtC,GADIC,GAAM,MAAMJ,EAAK,kBAAmB,CAAC,KAAM,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,EAAGD,EAAE,MAAM,CAAC,CAAC,EAAGK,CAAI,EAAGV,EAAc,EAC5G,EAACK,EAAE,MACP,GAAIG,EAAO,CAGT,IAAII,EACJ,MAAM,QAAQ,KAAK,CAACJ,EAAMP,GAAWC,CAAI,CAAC,EAAG,IAAI,QAASW,GAAM,CAAED,EAAQ,WAAWC,EAAGb,EAAc,CAAE,CAAC,CAAC,CAAC,EAC3G,aAAaY,CAAK,MACb,CACL,IAAME,EAAQT,EAAE,OAAS,eAAe,KAAKA,EAAE,KAAK,EAAI,CAAC,KAAMA,EAAE,KAAK,EAAI,CAAC,EAC3E,MAAMC,EAAK,eAAgB,CAAC,GAAGQ,EAAO,KAAMb,GAAWC,CAAI,CAAC,EAAGF,EAAc,EAEjF,CAEA,SAASe,GAAIC,EAAiBvB,EAA0B,CACtD,MAAO,CAAC,GAAGuB,EAAM,QAASC,GAAM,CAAC,KAAMA,CAAC,CAAC,EAAG,GAAGxB,CAAI,CACrD,CAGO,SAASyB,GAAWC,EAAeC,EAAgBC,EAA6B,CACrF,OAAON,GAAI,CACT,cACA,sCACA,gJACA,kCACA,wBACA,SACF,EAAG,CAACI,EAAOC,EAAQ,GAAGC,CAAO,CAAC,CAChC,CAOO,SAASC,GAAWH,EAAeC,EAAgBG,EAAmBC,EAAiBC,EAAyB,CACrH,OAAOV,GAAI,CACT,cACA,qDACA,6CACA,0CACA,8BACA,yKACA,OACA,yIACA,SACA,iCACA,8FACA,8BACA,SACF,EAAG,CAACI,EAAOC,EAAQG,EAAQ,KAAK;AAAA,CAAI,EAAGA,EAAQA,EAAQ,OAAS,CAAC,EAAG,OAAOC,CAAO,EAAG,GAAIC,IAAS,OAAY,CAACA,CAAI,EAAI,CAAC,CAAE,CAAC,CAC7H,CAGO,SAASC,GAAY5B,EAAyD,CACnF,IAAM6B,EAAM7B,EAAO,QAAQ,MAAO,EAAE,EACpC,GAAI,CAAC6B,EAAK,OAAO,KACjB,IAAMC,EAAID,EAAI,QAAQ;AAAA,CAAI,EAC1B,OAAOC,EAAI,EAAI,CAAE,OAAQD,EAAK,KAAM,EAAG,EAAI,CAAE,OAAQA,EAAI,MAAM,EAAGC,CAAC,EAAG,KAAMD,EAAI,MAAMC,EAAI,CAAC,CAAE,CAC/F,CAEA,SAASC,GAAU3B,EAA4B,CAC7C,MAAO,CAACA,EAAK,IAAKA,EAAK,UAAY,gBAAgBA,EAAK,YAAc,GAAI,QAAQA,EAAK,WAAW,EAAE,OAAO,OAAO,EAAE,KAAK;AAAA;AAAA,CAAM,CACjI,CAeA,eAAsB4B,GAAU5B,EAAoB6B,EAAyBC,EAAoB,CAAC,EAAyB,CACzH,IAAM1B,EAAO0B,EAAK,KAAOzC,GACnBgB,GAAUwB,EAAS,OAAS,UAAY,OAExCE,EAAS7B,GAAIF,EAAM6B,EAAUzB,EAAMC,EAAQyB,EAAK,KAAK,EAAE,MAAM,IAAG,EAAY,EAClF,GAAIzB,EAAQ,CACV,IAAM2B,EAAS,MAAMC,GAAejC,EAAM,CACxC,eAAgB6B,EAAS,eAAgB,MAAOA,EAAS,MAAO,OAAQA,EAAS,OACjF,MAAOA,EAAS,MAAO,KAAMC,EAAK,KAAM,QAASA,EAAK,QAAS,KAAMA,EAAK,IAC5E,EAAG1B,CAAI,EAAE,MAAM,IAAM,IAAI,EACzB,GAAI4B,EAAQ,OAAOA,EAErB,aAAMD,EACCG,GAAYlC,EAAM6B,EAAUzB,CAAI,CACzC,CAEA,eAAe8B,GAAYlC,EAAoB6B,EAAyBzB,EAAiC,CACvG,IAAMkB,EAAU,KAAK,IAAI,GAAI,KAAK,MAAMO,EAAS,cAAc,CAAC,EAE1DM,GAASb,EAAU,GAAK,IACxBL,EAAQ,GAAGjB,EAAK,cAAcA,EAAK,QAErCoC,EACJ,GAAIpC,EAAK,SAAS,OAAQ,CACxB,IAAMW,EAAI,MAAMP,EAAK,qBAAsBY,GAAWC,EAAOU,GAAU3B,CAAI,EAAGA,EAAK,OAAO,EAAGmC,CAAK,EAElG,GADAC,EAASzB,EAAE,OAAO,QAAQ,MAAO,EAAE,EAC/B,CAACA,EAAE,IAAM,CAACX,EAAK,QAAQ,SAASoC,CAAM,EAAG,MAAO,CAAE,OAAQ,SAAU,EACxE,GAAI,CAACpC,EAAK,MAAO,MAAO,CAAE,OAAQ,MAAO,OAAAoC,CAAO,EAGlD,GAAIpC,EAAK,MAAO,CACd,IAAMkB,EAASkB,EAAS,GAAGA;AAAA;AAAA,gCAA6C,GAAGT,GAAU3B,CAAI;AAAA;AAAA,gCAEnFW,EAAI,MAAMP,EAAK,qBAAsBgB,GAAWH,EAAOC,EAD7CkB,EAAS,CAAC,SAAU,MAAM,EAAI,CAAC,UAAW,KAAM,MAAM,EACQd,EAASe,GAASrC,EAAK,MAAOoC,CAAM,CAAC,EAAGD,CAAK,EACrHG,EAAI3B,EAAE,GAAKa,GAAYb,EAAE,MAAM,EAAI,KACzC,GAAI2B,GAAG,SAAW,OAAQ,CACxB,IAAMf,EAAOe,EAAE,KAAK,KAAK,EAEzB,OAAKf,EACE,CAAE,OAAQ,MAAO,GAAIa,EAAS,CAAE,OAAAA,CAAO,EAAI,CAAC,EAAI,KAAAb,CAAK,EAD1C,CAAE,OAAQ,SAAU,EAGxC,OAAIe,GAAG,SAAW,KAAa,CAAE,OAAQ,IAAK,EACvC,CAAE,OAAQ,SAAU,EAG7B,IAAM3B,EAAI,MAAMP,EAAK,qBAAsBgB,GAAWH,EAAOU,GAAU3B,CAAI,EAAG,CAAC,UAAW,KAAM,KAAK,EAAGsB,CAAO,EAAGa,CAAK,EACjHG,EAAI3B,EAAE,GAAKa,GAAYb,EAAE,MAAM,EAAI,KACzC,OAAI2B,GAAG,SAAW,MAAc,CAAE,OAAQ,KAAM,EAC5CA,GAAG,SAAW,KAAa,CAAE,OAAQ,IAAK,EACvC,CAAE,OAAQ,SAAU,CAC7B,CIjKO,IAAMC,GAAmB,GAAK,KAsBjCC,GAAU,GAGRC,GAAqB,GAAK,IAC5BC,GAAgB,KAGb,SAASC,GAAcC,EAAwC,CACpE,IAAMC,EAAMD,EAAI,KAAO,KAAK,IAAI,EAC1B,CAAE,OAAAE,EAAS,CAAC,EAAG,QAAAC,EAAS,OAAAC,EAAQ,OAAAC,EAAS,CAAC,CAAE,EAAIC,EAAeN,EAAI,IAAI,EACvEO,EAAOH,EAAS,KAAK,MAAMA,CAAM,EAAI,IACrCI,EAAQC,EAAUT,EAAI,KAAM,SAAS,EAAE,OAAQ,GAAM,KAAK,MAAM,EAAE,OAAO,EAAIC,CAAG,EAChFS,EAAQF,EAAM,KAAM,GAAMH,EAAO,SAAS,QAAQ,EAAE,IAAI,CAAC,EAC/D,GAAIK,EAAO,OAAOA,EAClB,QAAWC,KAAQH,EAAO,CACxB,IAAMI,EAAM,QAAQD,EAAK,KAGzB,GAFIT,EAAOU,CAAG,GAEVC,GAAab,EAAI,KAAMW,EAAK,GAAIV,CAAG,EAAG,SAC1C,IAAMa,EAAQX,EAAQS,CAAG,EACzB,GAAIE,GAAS,KAAK,MAAMA,CAAK,EAAIb,EAAK,SACtC,IAAMc,EAAU,KAAK,MAAMJ,EAAK,UAAU,EAC1C,GAAI,EAAAV,EAAMc,EAAUpB,IAAoB,EAAEoB,GAAWR,IACrD,OAAOI,EAET,OAAO,IACT,CAGA,SAASK,GAAgBhB,EAAoC,CAC3D,GAAI,CAAE,OAAOiB,EAAgBjB,CAAG,GAAG,YAAY,iBAAiB,GAAK,CAAC,CAAE,MAAE,CAAQ,MAAO,CAAC,CAAE,CAC9F,CAIO,SAASkB,GAAiBlB,EAAyC,CACxE,IAAMC,EAAMD,EAAI,KAAO,KAAK,IAAI,EAC1B,CAAE,OAAAE,EAAS,CAAC,EAAG,QAAAC,CAAQ,EAAIG,EAAeN,EAAI,IAAI,EACxD,OAAOgB,GAAgBhB,CAAG,EAAE,KAAM,GAAM,CACtC,IAAMY,EAAM,WAAW,EAAE,KACnBE,EAAQX,EAAQS,CAAG,EACzB,MAAO,CAACV,EAAOU,CAAG,GAAK,EAAEE,GAAS,KAAK,MAAMA,CAAK,EAAIb,IAAQA,EAAM,EAAE,WAAaN,EACrF,CAAC,GAAK,IACR,CAIO,SAASwB,GAAcC,EAAkBnB,EAA2B,CACzE,MAAO,CACL,GAAImB,EAAE,GACN,MAAOC,GAAUD,EAAE,KAAME,GAAY,KAAK,EAC1C,QAASF,EAAE,iBAAmB,iDAC9B,IAAK,mBAAmBA,EAAE,uBAC1B,IAAK,sCACL,UAAW,GACX,UAAW,OACX,QAAS,IAAI,KAAKnB,EAAMN,EAAgB,EAAE,YAAY,EACtD,UAAWyB,EAAE,QACb,WAAY,IAAI,KAAKA,EAAE,SAAS,EAAE,YAAY,EAC9C,OAAQ,UACR,OAAQ,CAAE,KAAM,UAAW,GAAIA,EAAE,EAAG,CACtC,CACF,CAEA,eAAsBG,GAAQC,EAA8C,CAC1E,GAAM,CAAE,IAAAxB,EAAK,SAAAyB,EAAU,IAAAC,CAAI,EAAIF,EAC/B,GAAI,CAACC,EAAS,UAAYD,EAAK,UAAY,QAAQ,YAAc,SAAU,MAAO,MAClF,GAAI5B,GAAS,MAAO,OACpB,IAAM+B,EAAU5B,GAAcC,CAAG,EAAI,KAAOkB,GAAiBlB,CAAG,EAC1DW,EAAOgB,EAAUR,GAAcQ,EAAS3B,EAAI,KAAO,KAAK,IAAI,CAAC,EAAID,GAAcC,CAAG,EACxF,GAAI,CAACW,EAAM,MAAO,OAClB,IAAMiB,GAASJ,EAAK,OAASK,IAAU,EACvC,GAAID,EAAM,OAAQ,MAAO,OAEzB,IAAMhB,EAAM,GAAGe,EAAU,UAAY,UAAUhB,EAAK,KAC9CmB,GAAUxB,EAAeN,EAAI,IAAI,EAAE,QAAU,CAAC,GAAG,SAASY,CAAG,EAAI,KAAOmB,GAAcpB,CAAI,EAChG,GAAImB,EAAQ,CACV,IAAME,EAAU,CAAC,GAAGvB,EAAUT,EAAI,KAAM,SAAS,EAAE,IAAKiC,GAAM,QAAQA,EAAE,IAAI,EAAG,GAAGjB,GAAgBhB,CAAG,EAAE,IAAKoB,GAAM,WAAWA,EAAE,IAAI,CAAC,EACpI,OAAAc,GAAalC,EAAI,KAAMY,EAAKoB,EAAShC,EAAI,GAAG,EAC5C0B,EAAI,sBAAsBd,wBAA0BD,EAAK,cAAcmB,GAAQ,EAC/EN,EAAK,iBAAiBb,EAAMwB,GAAaxB,EAAMmB,CAAM,CAAC,EAC/C,WAGTlC,GAAU,GACV,GAAI,CAEF,IAAMoC,EAAU,CAAC,GAAGvB,EAAUT,EAAI,KAAM,SAAS,EAAE,IAAKiC,GAAM,QAAQA,EAAE,IAAI,EAAG,GAAGjB,GAAgBhB,CAAG,EAAE,IAAKoB,GAAM,WAAWA,EAAE,IAAI,CAAC,EACpIc,GAAalC,EAAI,KAAMY,EAAKoB,EAAShC,EAAI,GAAG,EAC5C0B,EAAI,8BAA8Bd,UAAYD,EAAK,WAAW,EAC9D,IAAMV,EAAMD,EAAI,KAAO,KAAK,IAAI,EAC1BoC,EAAS,CAAC,CAACT,GAAW1B,EAAMH,GAAgBD,GAC9C8B,GAAWF,EAAS,QAAO3B,GAAgBG,GAE/C,IAAMoC,EAAOb,EAAK,YAAYb,EAAK,SAAS,IAAMA,EAAK,KAAO,GAAGA,EAAK,gBAAgBA,EAAK,OAAS,QAC9F2B,EAAS,MAAOd,EAAK,MAAQe,IAAW5B,EAAMyB,EAAS,CAAE,GAAGX,EAAU,MAAO,EAAM,EAAIA,EAC3F,CAAE,KAAAY,EAAM,MAAOb,EAAK,KAAM,CAAC,EAC7B,GAAIc,EAAO,SAAW,UACpB,OAAAZ,EAAI,sBAAsBd,mBAAqB0B,EAAO,KAAO,gBAAgBE,GAAWZ,CAAK,eAAe,EACrG,QAIT,GAAI,EADiBD,EAAUV,EAAgBjB,CAAG,GAAG,IAAI2B,EAAQ,EAAE,GAAG,QAAU,kBAAoBc,EAASzC,EAAI,KAAMW,EAAK,EAAE,GAAG,SAAW,WAE1I,OAAAe,EAAI,sBAAsBd,gDAAkD,EACrE,QAET,IAAMQ,EAAI,MAAMsB,GAAO1C,EAAKY,EAAK0B,EAAO,OAAQ,CAC9C,GAAI,mBACJ,GAAIA,EAAO,SAAW,MAAQ,CAAE,OAAQA,EAAO,OAAQ,KAAMA,EAAO,IAAK,EAAI,CAAC,CAChF,CAAC,EACD,OAAAZ,EAAIN,EAAE,GAAK,sBAAsBA,EAAE,UAAY,sBAAsBR,mBAAqBQ,EAAE,OAAO,EAC5F,OACT,OAASuB,EAAP,CACA,OAAAjB,EAAI,gCAAgCd,MAAQ+B,GAAG,SAAWA,GAAG,EACtD,OACT,QAAE,CACA/C,GAAU,EACZ,CACF,CAGO,SAASgD,GAAS5C,EAAmBY,EAAaiC,EAAgF,CACvI,GAAI,CAACA,EAAS,MAAO,CAAE,GAAI,GAAO,MAAO,8EAA+E,EACxH,IAAMlC,EAAOC,EAAI,WAAW,OAAO,EAAI6B,EAASzC,EAAI,KAAMY,EAAI,MAAM,CAAC,CAAC,EAAI,KAC1E,OAAKD,EACDA,EAAK,SAAW,UAAkB,CAAE,GAAI,GAAO,MAAO,GAAGC,gBAAkBD,EAAK,QAAS,GAC7FmC,GAAa9C,EAAI,KAAMY,CAAG,EACnB,CAAE,GAAI,GAAM,QAAS,GAAGA,wCAA2C,GAHxD,CAAE,GAAI,GAAO,MAAO,YAAYA,yCAA4C,CAIhG,CCrMA,OAAS,aAAAmC,EAAW,oBAAAC,GAAkB,aAAAC,GAAW,YAAAC,GAAU,gBAAAC,OAAoB,KAC/E,OAAS,cAAAC,GAAY,WAAAC,GAAS,OAAAC,OAAW,OAgBlC,IAAMC,GAAiB,aAExBC,GAAW,0BAaV,SAASC,GACdC,EAAmBC,EAAcC,EAAM,QAAQ,IAAI,EACoD,CACvG,GAAI,CAACD,GAAQA,EAAK,OAASE,GAAgB,WAAaF,EAAK,SAAS,IAAI,EAAG,MAAO,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,eAAgB,EACrI,GAAIA,EAAK,MAAM,OAAO,EAAE,SAAS,IAAI,EAAG,MAAO,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,qDAAqDG,WAAoB,EACzJ,GAAI,CAACC,GAAaJ,CAAI,EAAG,MAAO,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,iCAAkC,EACnG,IAAIK,EACJ,GAAI,CAAEA,EAAOC,GAAaC,GAAQN,EAAKF,CAAS,CAAC,CAAE,MAAE,CAAQ,MAAO,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,kCAAmC,CAAE,CAC1I,IAAIS,EACJ,GAAI,CAAEA,EAAOF,GAAaG,GAAWT,CAAI,EAAIA,EAAOO,GAAQF,EAAML,CAAI,CAAC,CAAE,MAAE,CAAQ,MAAO,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,gBAAiB,CAAE,CAC9I,GAAI,CAACQ,EAAK,WAAWH,EAAOK,EAAG,EAAG,MAAO,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,2DAA2DP,WAAoB,EAEzJ,IAAMQ,EAAOP,GAAaI,CAAI,EAC9B,OAAKG,EACE,CAAE,GAAI,GAAM,KAAMH,EAAM,GAAGG,CAAK,EADrB,CAAE,GAAI,GAAO,OAAQ,IAAK,MAAO,iCAAkC,CAEvF,CAGO,SAASC,GAAmBC,EAAcC,EAAsB,CACrE,IAAMC,EAAQF,EAAK,QAAQ,gBAAiB,GAAG,EAAE,QAAQ,UAAW,GAAG,EAAE,MAAM,EAAG,GAAG,GAAK,OAC1F,MAAO,GAAGC,IAAS,OAAS,aAAe,uBAAuBC,wBAA4B,mBAAmBF,EAAK,MAAM,EAAG,GAAG,CAAC,GACrI,CAGO,SAASG,GAAYC,EAAcJ,EAAcC,EAAsC,CAC5F,MAAO,CACL,eAAgBG,EAChB,sBAAuBL,GAAmBC,EAAMC,CAAI,EACpD,yBAA0B,UAE1B,0BAA2B,gEAC3B,gBAAiB,wBACjB,gBAAiB,OACnB,CACF,CAIO,SAASI,GAAWC,EAA4BC,EAAuE,CAC5H,IAAMC,EAAI,sBAAsB,KAAK,OAAOF,GAAU,EAAE,EAAE,KAAK,CAAC,EAChE,GAAI,CAACE,GAAM,CAACA,EAAE,CAAC,GAAK,CAACA,EAAE,CAAC,EAAI,OAAO,KACnC,IAAIC,EAAeC,EAGnB,OAFKF,EAAE,CAAC,GACDC,EAAQ,OAAOD,EAAE,CAAC,CAAC,EAAGE,EAAMF,EAAE,CAAC,EAAI,KAAK,IAAI,OAAOA,EAAE,CAAC,CAAC,EAAGD,EAAO,CAAC,EAAIA,EAAO,IADvEE,EAAQ,KAAK,IAAI,EAAGF,EAAO,OAAOC,EAAE,CAAC,CAAC,CAAC,EAAGE,EAAMH,EAAO,GAEhEE,EAAQC,GAAOD,GAASF,EAAa,gBAClC,CAAE,MAAAE,EAAO,IAAAC,CAAI,CACtB,CAEO,SAASC,GAAkBC,EAAsBC,EAAqBC,EAAUC,EAA6B,CAClH,IAAMC,EAAO,CAACC,EAAgBC,IAAkB,CAC9CL,EAAI,UAAUI,EAAQ,CAAE,eAAgB,mBAAoB,gBAAiB,WAAY,yBAA0B,SAAU,CAAC,EAC9HJ,EAAI,IAAI,KAAK,UAAUK,CAAI,CAAC,CAC9B,EACA,GAAIN,EAAI,SAAW,OAASA,EAAI,SAAW,OAAQ,OAAOI,EAAK,IAAK,CAAE,MAAO,UAAW,CAAC,EACzF,IAAMG,EAAQL,EAAI,aAAa,IAAI,OAAO,GAAK,GACzC3B,EAAO2B,EAAI,aAAa,IAAI,MAAM,GAAK,GAC7C,GAAI,CAAC9B,GAAS,KAAKmC,CAAK,EAAG,OAAOH,EAAK,IAAK,CAAE,MAAO,cAAe,CAAC,EACrE,IAAM9B,EAAY6B,EAAK,YAAYI,CAAK,EACxC,GAAI,CAACjC,EAAW,OAAO8B,EAAK,IAAK,CAAE,MAAO,aAAaG,qBAA0B,CAAC,EAClF,IAAMC,EAAQnC,GAAgBC,EAAWC,EAAM4B,EAAK,GAAG,EACvD,GAAI,CAACK,EAAM,GACT,OAAAL,EAAK,MAAM,uBAAuB,KAAK,UAAU5B,EAAK,MAAM,EAAG,GAAG,CAAC,UAAUgC,MAAUC,EAAM,OAAO,EAC7FJ,EAAKI,EAAM,OAAQ,CAAE,MAAOA,EAAM,KAAM,CAAC,EAGlD,IAAIC,EACJ,GAAI,CAAEA,EAAKC,GAASF,EAAM,KAAM,GAAG,CAAE,MAAE,CAAQ,OAAOJ,EAAK,IAAK,CAAE,MAAO,gBAAiB,CAAC,CAAE,CAC7F,IAAIT,EACJ,GAAI,CAEF,IAAMgB,EAAKC,GAAUH,CAAE,EACvB,GAAI,CAACE,EAAG,OAAO,EAAK,OAAAE,EAAUJ,CAAE,EAAUL,EAAK,IAAK,CAAE,MAAO,YAAa,CAAC,EAC3ET,EAAOgB,EAAG,IACZ,MAAE,CAAQ,OAAAE,EAAUJ,CAAE,EAAUL,EAAK,IAAK,CAAE,MAAO,gBAAiB,CAAC,CAAE,CACvE,GAAIT,EAAOlB,GAAgB,MAAS,OAAAoC,EAAUJ,CAAE,EAAUL,EAAK,IAAK,CAAE,MAAO,2BAA2B3B,GAAgB,MAAQ,KAAO,SAAU,CAAC,EAElJ,IAAMW,EAAOoB,EAAM,KAAK,MAAMvB,EAAG,EAAE,IAAI,GAAK,OACtC6B,EAAUvB,GAAYiB,EAAM,KAAMpB,EAAMoB,EAAM,IAAI,EAClDO,EAAQtB,GAAWO,EAAI,QAAQ,MAAOL,CAAI,EAChD,GAAIoB,IAAU,gBAAiB,CAC7BF,EAAUJ,CAAE,EACZR,EAAI,UAAU,IAAK,CAAE,GAAGa,EAAS,gBAAiB,WAAWnB,GAAO,CAAC,EACrEM,EAAI,IAAI,EACR,OAEF,GAAM,CAAE,MAAAJ,EAAO,IAAAC,CAAI,EAAIiB,GAAS,CAAE,MAAO,EAAG,IAAKpB,EAAO,CAAE,EAM1D,GALAM,EAAI,UAAUc,EAAQ,IAAM,IAAK,CAC/B,GAAGD,EACH,iBAAkB,OAAOnB,EAAOG,EAAMD,EAAQ,EAAI,CAAC,EACnD,GAAIkB,EAAQ,CAAE,gBAAiB,SAASlB,KAASC,KAAOH,GAAO,EAAI,CAAC,CACtE,CAAC,EACGK,EAAI,SAAW,QAAUL,IAAS,EAAG,CAAEkB,EAAUJ,CAAE,EAAGR,EAAI,IAAI,EAAG,OACrE,IAAMe,EAASC,GAAiB,GAAI,CAAE,GAAAR,EAAI,MAAAZ,EAAO,IAAAC,EAAK,UAAW,EAAK,CAAC,EACvEkB,EAAO,GAAG,QAAS,IAAMf,EAAI,QAAQ,CAAC,EACtCA,EAAI,GAAG,QAAS,IAAMe,EAAO,QAAQ,CAAC,EACtCA,EAAO,KAAKf,CAAG,CACjB,CCxHA,OAAS,SAAAiB,MAAgC,gBACzC,OAAS,gBAAAC,GAAc,iBAAAC,OAAqB,KAC5C,OAAS,UAAAC,OAAc,KACvB,OAAS,QAAAC,OAAY,OAGrBC,KAqBO,IAAMC,GAAWC,GACtBA,GAAQA,IAAS,EAAI,KAAK,MAAM,IAAMA,CAAI,EAAI,KAGnCC,GAAmBD,GAC9BA,GAAQA,IAAS,EAAI,KAAK,IAAI,IAAK,KAAK,IAAI,GAAKA,CAAI,CAAC,EAAI,KAiCrD,SAASE,IAA+B,CAC7C,GAAI,QAAQ,IAAI,mBAAoB,OAAO,QAAQ,IAAI,mBACvD,QAAWC,IAAK,CAAC,GAAG,QAAQ,IAAI,uBAAwB,GAAG,QAAQ,IAAI,iCAAiC,EACtG,GAAI,CACF,IAAMC,EAAIC,GAAaF,EAAG,MAAM,EAAE,KAAK,EACvC,GAAIC,EAAG,OAAOA,CAChB,MAAE,CAAmB,CAEvB,OAAO,IACT,CAGO,SAASE,GAAgBC,EAAqBL,GAAc,EAAU,CAC3E,IAAIM,EAAI,EACR,MAAO,OAAOC,EAAGC,IAAW,CAC1B,GAAID,EAAE,MAAM,WAAa,SAAU,OAAO,KAC1C,GAAI,CAACF,EAAK,CACR,GAAIE,EAAE,MAAM,SAAU,OAAO,KAC7B,MAAM,IAAI,MAAM,oDAAoD,EAEtE,GAAI,CACF,IAAME,EAAM,MAAM,MAAM,+CAA+CF,EAAE,MAAM,gDAAiD,CAC9H,OAAQ,OACR,QAAS,CAAE,aAAcF,EAAK,eAAgB,kBAAmB,EACjE,KAAM,KAAK,UAAU,CACnB,KAAME,EAAE,KAAM,SAAU,oBACxB,GAAIR,GAAgBQ,EAAE,MAAM,IAAI,EAAI,CAAE,eAAgB,CAAE,MAAOR,GAAgBQ,EAAE,MAAM,IAAI,CAAE,CAAE,EAAI,CAAC,CACtG,CAAC,EACD,OAAAC,CACF,CAAC,EACD,GAAI,CAACC,EAAI,GAAI,MAAM,IAAI,MAAM,cAAcA,EAAI,YAAY,MAAMA,EAAI,KAAK,GAAG,MAAM,EAAG,GAAG,GAAG,EAC5F,IAAMC,EAAOC,GAAKC,GAAO,EAAG,cAAc,QAAQ,OAAO,EAAEN,OAAO,EAClE,OAAAO,GAAcH,EAAM,OAAO,KAAK,MAAMD,EAAI,YAAY,CAAC,CAAC,EACjDC,CACT,OAASI,EAAP,CACA,GAAIN,EAAO,SAAW,CAACD,EAAE,MAAM,SAAU,MAAMO,EAC/C,OAAAC,EAAS,cAAc,OAAOD,GAAG,SAAWA,CAAC,EAAE,MAAM,EAAG,EAAE,IAAK,8BAA8B,OAAOA,GAAG,SAAWA,CAAC,EAAE,MAAM,EAAG,GAAG,oCAAoC,EAC9J,IACT,CACF,CACF,CAGO,SAASE,GAAed,EAAae,EAA6B,CACvE,IAAMC,EAAOhB,EAAE,WAAaiB,GAAeF,CAAI,EAC/C,OAAOC,GAAQhB,EAAE,WAAagB,KAAQhB,EAAE,UAAYA,EAAE,UAAUgB,CAAI,EAAIhB,EAAE,MAC5E,CAKO,IAAMkB,GAAU,CAAClB,EAAae,EAAO,KAAiB,CAC3D,IAAMI,EAAKL,GAAed,EAAGe,CAAI,EAC3BK,EAAMzB,GAAQK,EAAE,IAAI,EAC1B,MAAO,CAAC,GAAImB,GAAM,CAACE,GAAYF,CAAE,EAAI,CAAC,KAAMA,CAAE,EAAI,CAAC,EAAI,GAAIC,EAAM,CAAC,KAAM,OAAOA,CAAG,CAAC,EAAI,CAAC,CAAE,CAC5F,EAKO,SAASE,GAAWtB,EAAae,EAAcQ,EAA4C,CAChG,GAAI,CAACA,EAAS,MAAO,CAAC,MAAOL,GAAQlB,EAAGe,CAAI,CAAC,EAC7C,IAAMI,EAAKL,GAAed,EAAGe,CAAI,EACjC,MAAO,CAAC,UAAW,CAACQ,EAAS,GAAIJ,EAAK,CAACA,CAAE,EAAI,CAAC,CAAE,CAAC,CACnD,CAKO,SAASK,GAAaT,EAAsB,CACjD,IAAMU,EAAQV,EAAK,MAAM,KAAK,EAAE,OAAO,OAAO,EAAE,OAChD,OAAO,KAAK,IAAI,IAAK,EAAI,KAAK,MAAOU,EAAQ,EAAK,EAAE,CAAC,EAAI,GAC3D,CAKO,SAASC,GAAgBC,EAAwBC,GAAc,EAAS,CAC7E,GAAKD,EACL,GAAI,CACQE,EAAM,UAAW,CAACF,EAAQ,QAAQ,EAAG,CAAE,MAAO,QAAS,CAAC,EAChE,GAAG,QAAS,IAAM,CAAC,CAAC,CACxB,MAAE,CAA8B,CAClC,CAKO,SAASG,GAAmBH,EAAwBC,GAAc,EAAS,CAChF,GAAKD,EACL,GAAI,CACQE,EAAM,UAAW,CAACF,EAAQ,WAAW,EAAG,CAAE,MAAO,QAAS,CAAC,EACnE,GAAG,QAAS,IAAM,CAAC,CAAC,CACxB,MAAE,CAAwC,CAC5C,CAKO,SAASC,GAAcG,EAAiB,CAAC,EAAGC,EAA8B,CAC/E,GAAI,CAACC,GAAcF,CAAI,EAAG,OAAO,KACjC,GAAI,CACF,OAAOG,GAAcF,CAAI,CAC3B,OAASpB,EAAP,CACA,OAAAC,EAAS,WAAY,kDAAkD,OAAOD,GAAG,SAAWA,CAAC,EAAE,MAAM;AAAA,CAAI,EAAE,CAAC,iCAAiC,EACtI,IACT,CACF,CAGO,IAAMuB,GAAmB,CAAC3B,EAAMH,IAAM,CAC3C,GAAI,CAACG,EAAM,CACT,GAAM,CAAC4B,EAAKC,CAAI,EAAIf,GAAWjB,EAAE,MAAOA,EAAE,KAAMuB,GAAc,CAAC,EAEzDR,EAAMzB,GAAQU,EAAE,MAAM,IAAI,EAC1BiC,EAAMlB,EAAM,CAAE,GAAG,QAAQ,IAAK,gBAAiB,OAAOA,CAAG,CAAE,EAAI,QAAQ,IACvErB,EAAI8B,EAAMO,EAAKC,EAAM,CAAE,MAAO,CAAC,OAAQ,SAAU,QAAQ,EAAG,IAAAC,CAAI,CAAC,EACvE,OAAAvC,EAAE,OAAO,GAAG,QAAS,IAAM,CAAC,CAAC,EAC7BA,EAAE,OAAO,IAAIM,EAAE,IAAI,EACZN,EAGT,OAAO,QAAQ,WAAa,SACxB8B,EAAM,SAAU,CAACrB,CAAI,EAAG,CAAE,MAAO,QAAS,CAAC,EAC3CqB,EAAM,SAAU,CAAC,KAAMrB,CAAI,EAAG,CAAE,MAAO,QAAS,CAAC,CACvD,ECvMA,OAAS,SAAA+B,OAAa,gBACtB,OAAS,cAAAC,GAAY,aAAAC,GAAW,gBAAAC,OAAoB,KACpD,OAAS,WAAAC,OAAe,KACxB,OAAS,YAAAC,EAAU,aAAAC,GAAW,WAAAC,GAAS,WAAAC,GAAS,QAAAC,OAAY,OAsBrD,IAAMC,EAAe,CAC1B,GAAI,EAAI,GAAK,IACb,cAAe,KACf,MAAO,EAAI,KAAO,IACpB,EAKO,SAASC,GAAoBC,EAAoF,CACtH,IAAMC,EAAM,MAAM,QAAQD,CAAM,EAAIA,EAAO,CAAC,EAAIA,EAChD,MAAI,CAACC,GAAO,CAAC,YAAY,KAAKA,EAAI,KAAK,CAAC,EAAU,CAAE,OAAQ,IAAK,MAAO,sDAAuD,EACxH,OAAOA,CAAG,EAAIH,EAAa,GAAK,IAAO,CAAE,OAAQ,IAAK,MAAOI,GAAe,CAAE,EAAI,IAC3F,CAEO,IAAMA,GAAiB,IAAc,gCAAgCJ,EAAa,GAAK,eAExFK,GAAsC,CAC1C,aAAc,OAAQ,aAAc,OAAQ,YAAa,MACzD,YAAa,MAAO,YAAa,MAAO,cAAe,MAAO,YAAa,MAC3E,aAAc,MAAO,YAAa,MAAO,cAAe,MAAO,aAAc,KAC/E,EAIO,SAASC,GAASC,EAAgD,CACvE,IAAMC,EAAO,OAAOD,GAAe,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EACxE,OAAOF,GAAYG,CAAI,GAAK,IAC9B,CAWO,SAASC,GAAWC,EAAqBC,EAA4B,CAC1E,IAAMC,EAAmB,CAAC,EAC1B,OAAIF,IAAY,SAAWC,EAAK,KAAKC,EAAI,KAAK,YAAY,EACtDD,EAAK,SACHA,EAAK,IAAKC,EAAI,KAAK,aAAa,EAC3BD,EAAK,SAASC,EAAI,KAAK,SAAS,GAEpCA,CACT,CAGO,SAASC,GAAaH,EAAqBC,EAAuB,CACvE,OAAID,IAAY,SAAWC,EAAK,KAAO,CAACA,EAAK,OAAe,oEACvDA,EAAK,KAAOA,EAAK,UAAY,CAACA,EAAK,OAAe,iEAChD,4EACT,CAIO,SAASG,GAAWC,EAAcC,EAA0BC,EAAU,QAAQ,IAAI,MAAQ,GAAmB,CAClH,IAAMC,EAAOxB,GAAQ,EACfyB,EAAO,CAACpB,GAAKmB,EAAM,SAAU,KAAK,EAAG,oBAAqB,iBAAkB,GAAGD,EAAQ,MAAMrB,EAAS,EAAE,OAAO,OAAO,CAAC,EAC7H,QAAWwB,IAAK,CAACJ,EAAU,GAAGG,EAAK,IAAKE,GAAMtB,GAAKsB,EAAGN,CAAI,CAAC,CAAC,EAC1D,GAAKK,EACL,GAAI,CAAE,OAAA7B,GAAW6B,EAAG5B,GAAU,IAAI,EAAU4B,CAAE,MAAE,CAAmB,CAErE,OAAO,IACT,CAEO,SAASE,GAAcC,EAA6B,CACzD,MAAO,CACL,IAAAA,EACA,IAAKT,GAAW,cAAe,QAAQ,IAAI,kBAAkB,EAC7D,QAASA,GAAW,UAAW,QAAQ,IAAI,cAAc,EACzD,OAAQU,GAAW,CACrB,CACF,CAIO,IAAMA,GAAa,IAAqBV,GAAW,SAAU,QAAQ,IAAI,aAAa,EAM7F,eAAsBW,GAAeC,EAAcC,EAA+C,CAChG,OAAOA,EAASC,GAAcF,EAAMC,CAAM,EAAI,IAChD,CAGA,eAAsBC,GAAcF,EAAcC,EAAwC,CACxF,IAAME,EAAQ,OAAO7B,EAAa,GAAK,IAAO,CAAC,EAC/C,GAAI,CAEF,IAAM8B,EAAQ,CAAC,IADH,MAAMC,GAAUJ,EAAQ,CAAC,WAAY,eAAgB,KAAME,EAAO,KAAMH,EAAM,KAAM,OAAQ,GAAG,EAAG,GAAM,GAC9F,SAAS,mCAAmC,CAAC,EAC7DM,EAAIF,EAAMA,EAAM,OAAS,CAAC,EAChC,OAAOE,EAAI,OAAOA,EAAE,CAAC,CAAC,EAAI,KAAO,OAAOA,EAAE,CAAC,CAAC,EAAI,GAAK,OAAOA,EAAE,CAAC,CAAC,EAAI,IACtE,MAAE,CAAQ,OAAO,IAAK,CACxB,CAGA,eAAsBC,GAAiBP,EAAcQ,EAAcX,EAAaY,EAAuC,CACrH,IAAMC,EAAK,IAAI,SACfA,EAAG,IAAI,WAAY,QAAQ,IAAI,kBAAoB,WAAW,EAC9DA,EAAG,IAAI,OAAQ,IAAI,KAAK,CAAC3C,GAAaiC,CAAI,CAAC,EAAG,CAAE,KAAMQ,CAAK,CAAC,EAAGvC,EAAS+B,CAAI,CAAC,EAC7E,IAAMW,EAAI,MAAM,MAAM,8CAA+C,CACnE,OAAQ,OAAQ,QAAS,CAAE,aAAcd,CAAI,EAAG,KAAMa,EAAI,OAAQD,GAAU,YAAY,QAAQ,GAAM,CACxG,CAAC,EACD,GAAI,CAACE,EAAE,GAAI,MAAM,IAAI,MAAM,cAAcA,EAAE,YAAY,MAAMA,EAAE,KAAK,EAAE,MAAM,IAAM,EAAE,GAAG,MAAM,EAAG,GAAG,GAAG,EACtG,IAAMC,EAAI,MAAMD,EAAE,KAAK,EACvB,GAAI,OAAOC,EAAE,MAAS,SAAU,MAAM,IAAI,MAAM,6BAA6B,EAC7E,OAAOA,EAAE,KAAK,KAAK,CACrB,CAKA,eAAsBC,GAAaC,EAAmCC,EAAaf,EAAcC,EAAgBe,EAAiC,CAChJ,IAAMC,EAAOH,IAAW,cACpB,CAAC,UAAW,QAAQ,IAAI,kBAAoB,uCAAwC,kBAAmB,MAAO,eAAgBE,EAAQhB,CAAI,EAC1I,CAACA,EAAM,UAAW,QAAQ,IAAI,sBAAwB,OAAQ,kBAAmB,MAAO,eAAgBgB,EAAQ,SAAU,OAAO,EAE/HE,EAAM,CAAE,GAAG,QAAQ,IAAK,KAAM,GAAG/C,GAAQ8B,CAAM,IAAI/B,KAAY,QAAQ,IAAI,MAAQ,IAAK,EAC9F,MAAMiD,GAAIJ,EAAKE,EAAM,KAASC,CAAG,EACjC,GAAI,CACF,OAAOnD,GAAaM,GAAK2C,EAAQ/C,EAAS+B,EAAM5B,GAAQ4B,CAAI,CAAC,EAAI,MAAM,EAAG,MAAM,EAAE,KAAK,CACzF,MAAE,CACA,MAAM,IAAI,MAAM,GAAGc,uBAA4B,CACjD,CACF,CAGA,SAAST,GAAUe,EAAaH,EAAgBI,EAAoC,CAClF,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtC,IAAMC,EAAQ5D,GAAMwD,EAAKH,EAAM,CAAE,MAAO,CAAC,SAAU,SAAU,MAAM,CAAE,CAAC,EAClEQ,EAAM,GACJC,EAAQ,WAAW,IAAM,CAAEF,EAAM,KAAK,SAAS,EAAGD,EAAO,IAAI,MAAM,GAAGtD,EAASmD,CAAG,iBAAiB,CAAC,CAAE,EAAGC,CAAS,EACxHG,EAAM,OAAO,GAAG,OAAS7B,GAAM,CAAE8B,GAAOA,EAAM9B,GAAG,MAAM,KAAK,CAAE,CAAC,EAC/D6B,EAAM,GAAG,QAAUG,GAAM,CAAE,aAAaD,CAAK,EAAGH,EAAOI,CAAC,CAAE,CAAC,EAC3DH,EAAM,GAAG,OAASI,GAAS,CAAE,aAAaF,CAAK,EAAGE,IAAS,EAAIN,EAAQG,CAAG,EAAIF,EAAO,IAAI,MAAM,GAAGtD,EAASmD,CAAG,YAAYQ,GAAM,CAAC,CAAE,CAAC,CACtI,CAAC,CACH,CAEA,SAAST,GAAIC,EAAaH,EAAgBI,EAAmBH,EAAyB,QAAQ,IAAsB,CAClH,OAAO,IAAI,QAAQ,CAACI,EAASC,IAAW,CACtC,IAAMC,EAAQ5D,GAAMwD,EAAKH,EAAM,CAAE,MAAO,CAAC,SAAU,OAAQ,MAAM,EAAG,IAAAC,CAAI,CAAC,EACrEhC,EAAM,GACNuC,EAAM,GACJC,EAAQ,WAAW,IAAM,CAAEF,EAAM,KAAK,SAAS,EAAGD,EAAO,IAAI,MAAM,GAAGtD,EAASmD,CAAG,sBAAsBC,EAAY,MAAO,CAAC,CAAE,EAAGA,CAAS,EAChJG,EAAM,OAAO,GAAG,OAAS7B,GAAM,CAAMT,EAAI,OAAS,OAAMA,GAAOS,EAAE,CAAC,EAClE6B,EAAM,OAAO,GAAG,OAAS7B,GAAM,CAAM8B,EAAI,OAAS,OAAMA,GAAO9B,EAAE,CAAC,EAClE6B,EAAM,GAAG,QAAUG,GAAM,CAAE,aAAaD,CAAK,EAAGH,EAAOI,CAAC,CAAE,CAAC,EAC3DH,EAAM,GAAG,OAASI,GAAS,CACzB,aAAaF,CAAK,EACdE,IAAS,EAAGN,EAAQpC,CAAG,EACtBqC,EAAO,IAAI,MAAM,GAAGtD,EAASmD,CAAG,YAAYQ,MAASH,EAAI,KAAK,EAAE,MAAM;AAAA,CAAI,EAAE,IAAI,GAAG,MAAM,EAAG,GAAG,GAAK,IAAI,CAAC,CAChH,CAAC,CACH,CAAC,CACH,CCtLA,OAAS,WAAAI,GAAS,MAAAC,GAAI,aAAAC,OAAiB,cACvC,OAAS,UAAAC,OAAc,KACvB,OAAS,QAAAC,OAAY,OAErBC,KAsBA,IAAMC,GAAiB,GAAK,KAGfC,GAAmB,EAC5BC,GAAe,EAEZ,SAASC,GAAcC,EAAuB,CACnD,OAAOA,IAAS,qBAAuBA,IAAS,cAClD,CAuBA,eAAsBC,GAAcC,EAAsBC,EAAqBH,EAAcI,EAAkC,CAC7H,OAAIF,EAAI,SAAW,OAAeG,EAAKF,EAAK,IAAK,CAAE,MAAO,MAAO,CAAC,EAC9DH,IAAS,oBAA4BM,GAAWJ,EAAKC,EAAKC,CAAI,EAC3DG,GAAML,EAAKC,EAAKC,CAAI,CAC7B,CAEA,eAAeE,GAAWJ,EAAsBC,EAAqBC,EAAkC,CACrG,IAAMI,EAAO,OAAON,EAAI,QAAQ,cAAc,GAAK,EAAE,EAC/CO,EAAMC,GAASF,CAAI,EACzB,GAAI,CAACC,EAAK,OAAOJ,EAAKF,EAAK,IAAK,CAAE,MAAO,2DAA4D,CAAC,EACtG,IAAMQ,EAASC,GAAoBV,EAAI,QAAQ,qBAAqB,CAAC,EACrE,GAAIS,EAAU,OAAAT,EAAI,OAAO,EAAUG,EAAKF,EAAKQ,EAAO,OAAQ,CAAE,MAAOA,EAAO,KAAM,CAAC,EACnF,IAAME,EAAQ,MAAMC,GAAQZ,EAAKa,EAAa,KAAK,EACnD,GAAI,CAACF,EAAO,OAAOR,EAAKF,EAAK,IAAK,CAAE,MAAOa,GAAc,CAAE,CAAC,EAC5D,GAAI,CAACH,EAAM,OAAQ,OAAOR,EAAKF,EAAK,IAAK,CAAE,MAAO,yBAA0B,CAAC,EAC7E,GAAIL,IAAgBD,GAAkB,OAAOQ,EAAKF,EAAK,IAAK,CAAE,MAAO,gFAAiF,CAAC,EAEvJ,IAAMc,EAAUb,EAAK,IAAI,EACnBc,EAAOd,EAAK,KAAK,EACjBe,EAAUC,GAAWH,EAASC,CAAI,EAGxC,GAAI,CAACA,EAAK,QAAU,CAACd,EAAK,kBAAkB,EAAG,CAC7C,IAAMiB,EAAQjB,EAAK,SAAW,MAAMA,EAAK,WAAa,mBAChDkB,EAAOH,EAAQ,OAAS,GAAK,IAAMI,GAAaN,EAASC,CAAI,EACnE,OAAOb,EAAKF,EAAK,IAAK,CAAE,MAAO,6CAA8C,KAAM,kBAAkBkB,oCAAwCC,IAAQ,MAAO,EAAM,CAAC,EAErK,GAAI,CAACH,EAAQ,OACX,OAAOd,EAAKF,EAAK,IAAK,CAAE,MAAO,6CAA8C,KAAMoB,GAAaN,EAASC,CAAI,EAAG,MAAO,EAAM,CAAC,EAGhIpB,KACA,IAAI0B,EACJ,GAAI,CACF,IAAMC,EAAM,MAAMC,GAAQC,GAAKC,GAAO,EAAG,qBAAqB,CAAC,EAC/D,GAAI,CACFJ,EAAQ,MAAMK,GAAaJ,EAAK,UAAUhB,IAAOI,EAAOL,EAAMU,EAAMC,EAASf,CAAI,CACnF,QAAE,CACA,MAAM0B,GAAGL,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EAAE,MAAM,IAAM,CAAC,CAAC,CAChE,CACF,QAAE,CACA3B,IACF,CACAO,EAAKF,EAAKqB,EAAM,CAAC,EAAGA,EAAM,CAAC,CAAC,CAC9B,CAEA,eAAeK,GAAaJ,EAAaM,EAAclB,EAAeL,EAAcU,EAAeC,EAAsBf,EAA+C,CACtK,IAAM4B,EAAOL,GAAKF,EAAKM,CAAI,EAM3B,GALA,MAAME,GAAUD,EAAMnB,EAAO,CAAE,KAAM,GAAM,CAAC,EAKxCK,EAAK,OAAQ,CACf,IAAMgB,EAAO,MAAO9B,EAAK,SAAW+B,IAAgBH,EAAMd,EAAK,MAAM,EACrE,GAAIgB,GAAQ,KAAM,MAAO,CAAC,IAAK,CAAE,MAAO,uDAAwD,CAAC,EACjG,GAAIA,EAAO,IAAOnB,EAAa,GAAK,IAAM,MAAO,CAAC,IAAK,CAAE,MAAOqB,GAAe,CAAE,CAAC,EAEpF,IAAMC,EAAqB,CAAC,EAC5B,QAAWC,KAAUnB,EACnB,GAAI,CACF,IAAMoB,EAAO,MAAMC,GAAUF,EAAQN,EAAMxB,EAAMU,EAAMO,EAAKrB,CAAI,EAEhE,OADAA,EAAK,IAAI,4CAA4CkC,MAAW,KAAK,MAAMzB,EAAM,OAAS,IAAI,OAAO,EAChG0B,EACE,CAAC,IAAK,CAAE,KAAAA,EAAM,OAAAD,CAAO,CAAC,EADX,CAAC,IAAK,CAAE,MAAO,4DAA6D,OAAAA,CAAO,CAAC,CAExG,OAASG,EAAP,CACA,IAAMC,EAAM,OAAOD,GAAG,SAAWA,CAAC,EAAE,MAAM;AAAA,CAAI,EAAE,CAAC,EAAE,MAAM,EAAG,GAAG,EAC/DJ,EAAS,KAAK,GAAGC,MAAWI,GAAK,EACjCtC,EAAK,IAAI,oCAAoCkC,aAAkBI,GAAK,CACtE,CAEF,MAAO,CAAC,IAAK,CAAE,MAAO,0CAA2C,OAAQL,EAAS,KAAK,IAAI,CAAE,CAAC,CAChG,CAEA,SAASG,GAAUF,EAAmBN,EAAcxB,EAAcU,EAAeO,EAAarB,EAAoC,CAChI,IAAMuC,EAASvC,EAAK,UAAUkC,CAAM,EACpC,GAAIK,EAAQ,OAAOA,EAAOX,EAAMxB,EAAMU,EAAMO,CAAG,EAC/C,GAAIa,IAAW,aAAc,OAAOM,GAAiBZ,EAAMxB,EAAMU,EAAK,GAAI,EAC1E,IAAM2B,EAAMP,IAAW,cAAgBpB,EAAK,IAAOA,EAAK,QACxD,OAAO4B,GAAaR,EAAQO,EAAKb,EAAMd,EAAK,OAASO,CAAG,CAC1D,CAEA,eAAelB,GAAML,EAAsBC,EAAqBC,EAAkC,CAChG,IAAM2C,EAAM,MAAMjC,GAAQZ,EAAKN,EAAc,EAC7C,GAAI,CAACmD,EAAK,OAAO1C,EAAKF,EAAK,IAAK,CAAE,MAAO,mBAAoB,CAAC,EAC9D,IAAI6C,EACJ,GAAI,CAAEA,EAAO,KAAK,MAAMD,EAAI,SAAS,MAAM,GAAK,IAAI,CAAE,MAAE,CAAQ,OAAO1C,EAAKF,EAAK,IAAK,CAAE,MAAO,eAAgB,CAAC,CAAE,CAClH,IAAM8C,EAAQ,OAAOD,GAAM,OAAU,SAAWA,EAAK,MAAQ,GACvDE,EAAO,OAAOF,GAAM,MAAS,UAAYA,EAAK,KAAOA,EAAK,KAAO,OACvE,GAAI,CAACC,EAAO,OAAO5C,EAAKF,EAAK,IAAK,CAAE,MAAO,mBAAoB,CAAC,EAChE,IAAMoC,EAAOY,GAAgB,OAAOH,GAAM,MAAS,SAAWA,EAAK,KAAO,GAAI5C,EAAK,iBAAiB,CAAC,EACrG,GAAI,CAACmC,EAAM,OAAOlC,EAAKF,EAAK,IAAK,CAAE,MAAO,2CAA4C,CAAC,EACvF,IAAMiD,EAAOhD,EAAK,UAAYA,EAAK,UAAUmC,CAAI,EAAIA,EAE/Cc,EAAQjD,EAAK,QAAQ6C,EAAOC,CAAI,EACtC,GAAI,CAACG,EAAO,OAAOhD,EAAKF,EAAK,IAAK,CAAE,MAAO,kBAAkB8C,GAAQ,CAAC,EAItE,IAAMK,EAAU,CAACZ,EAAaa,EAAS,MAAQlD,EAAKF,EAAKoD,EAAQ,CAAE,SAAU,UAAW,OAAQb,EAAK,KAAMU,CAAK,CAAC,EACjH,GAAIC,EAAM,WAAa,aAAc,OAAOC,EAAQ,0EAA0E,EAC9H,IAAME,EAAMpD,EAAK,cAAc,EAC/B,GAAI,CAACoD,EAAK,OAAOF,EAAQ,4CAA4C,EACrE,GAAI,CACF,IAAMzC,EAAQ,MAAOT,EAAK,OAASqD,IAAiBD,EAAKH,EAAOD,CAAI,EACpEjD,EAAI,UAAU,IAAK,CAAE,eAAgB,aAAc,iBAAkBU,EAAM,OAAQ,gBAAiB,WAAY,iBAAkB,OAAOuC,EAAK,MAAM,CAAE,CAAC,EACvJjD,EAAI,IAAIU,CAAK,CACf,OAAS4B,EAAP,CACA,OAAArC,EAAK,IAAI,0CAA0C,OAAOqC,GAAG,SAAWA,CAAC,EAAE,MAAM;AAAA,CAAI,EAAE,CAAC,EAAE,MAAM,EAAG,GAAG,GAAG,EAClGa,EAAQ,0CAA2C,GAAG,CAC/D,CACF,CAIO,SAASH,GAAgBZ,EAAcmB,EAAsB,CAClE,IAAMC,EAAQC,GAAmBC,GAAiBtB,CAAI,EAAE,IAAI,EAAE,UAC3D,QAAQ,4BAA6B,EAAE,EAC1C,OAAOuB,GAAYH,EAAOD,CAAG,CAC/B,CAGA,eAAeD,GAAgBD,EAAaH,EAAiBd,EAA+B,CAC1F,IAAMwB,EAAQC,GAAgBX,EAAM,IAAI,EAClC,EAAI,MAAM,MAAM,+CAA+C,mBAAmBA,EAAM,UAAU,gCAAiC,CACvI,OAAQ,OACR,QAAS,CAAE,aAAcG,EAAK,eAAgB,kBAAmB,EACjE,KAAM,KAAK,UAAU,CAAE,KAAAjB,EAAM,SAAU,oBAAqB,GAAIwB,EAAQ,CAAE,eAAgB,CAAE,MAAAA,CAAM,CAAE,EAAI,CAAC,CAAG,CAAC,EAC7G,OAAQ,YAAY,QAAQ,GAAM,CACpC,CAAC,EACD,GAAI,CAAC,EAAE,GAAI,MAAM,IAAI,MAAM,cAAc,EAAE,YAAY,MAAM,EAAE,KAAK,EAAE,MAAM,IAAM,EAAE,GAAG,MAAM,EAAG,GAAG,GAAG,EACtG,OAAO,OAAO,KAAK,MAAM,EAAE,YAAY,CAAC,CAC1C,CAEO,IAAM/C,GAAgB,IAC3B,gCAAgCD,EAAa,MAAQ,KAAO,sBAAsBA,EAAa,GAAK,yBAK/F,SAASkD,GACdC,EAAiBhB,EACjBiB,EAAgDC,EAC/B,CACjB,GAAIlB,EAAM,OAAOkB,EAAK,UAAUlB,EAAMgB,EAAS,EAAK,GAAG,OAAS,KAChE,IAAMG,EAASF,EAAO,QAAU,CAAC,EACjC,OAAOE,EAAOH,CAAO,EAAII,GAASC,GAAkBL,EAASG,EAAQF,EAAO,KAAK,CAAC,EAAI,IACxF,CAKO,SAASrD,GAAQZ,EAAsBsE,EAAuC,CACnF,OAAI,OAAOtE,EAAI,QAAQ,gBAAgB,CAAC,EAAIsE,GAAStE,EAAI,OAAO,EAAU,QAAQ,QAAQ,IAAI,GACvF,IAAI,QAAQ,CAACuE,EAASC,IAAW,CACtC,IAAMC,EAAmB,CAAC,EACtBC,EAAO,EACX1E,EAAI,GAAG,OAAS2E,GAAkB,CAChC,GAAI,EAAAD,EAAOJ,GAEX,IADAI,GAAQC,EAAM,OACVD,EAAOJ,EAAO,CAAEG,EAAO,OAAS,EAAGF,EAAQ,IAAI,EAAG,OACtDE,EAAO,KAAKE,CAAK,EACnB,CAAC,EACD3E,EAAI,GAAG,MAAO,IAAM,CAAM0E,GAAQJ,GAAOC,EAAQ,OAAO,OAAOE,CAAM,CAAC,CAAE,CAAC,EACzEzE,EAAI,GAAG,QAASwE,CAAM,CACxB,CAAC,CACH,CAEA,SAASrE,EAAKF,EAAqBoD,EAAgBP,EAAqB,CACtE7C,EAAI,UAAUoD,EAAQ,CAAE,eAAgB,mBAAoB,gBAAiB,UAAW,CAAC,EACzFpD,EAAI,IAAI,KAAK,UAAU6C,CAAI,CAAC,CAC9B,CCrOA,OAAS,cAAA8B,EAAY,gBAAAC,GAAc,iBAAAC,GAAe,aAAAC,GAAW,eAAAC,GAAa,UAAAC,GAAQ,YAAAC,MAAgB,KAClG,OAAS,WAAAC,GAAS,YAAAC,GAAU,WAAAC,GAAS,QAAAC,EAAM,WAAAC,EAAS,YAAAC,OAAgB,OAUpE,IAAMC,EAAiB,IAAM,KACvBC,GAAqB,IAAI,IAAI,CAAC,MAAO,YAAa,MAAM,CAAC,EAIzDC,EAA4B,IAAI,IAAI,CACxC,MAAO,YAAa,OAAQ,QAAS,SAAU,QAAS,OAAQ,QAChE,MAAO,QAAS,OAAQ,MAAO,MAAO,OAAQ,MAAO,OAAQ,OAC7D,OAAQ,QAAS,QAAS,OAAQ,OAAQ,OAAQ,YACpD,CAAC,EAEKC,GAAuB,EACvBC,GAAqB,IAAI,IAAI,CACjC,YAAa,UAAW,cAAe,aAAc,WAAY,cACnE,CAAC,EAsBD,SAASC,EAAmBC,EAAmBC,EAAyB,CACtE,GAAI,CAACD,EAAW,MAAM,IAAI,MAAM,oCAAoC,EACpE,IAAME,EAAQd,GAAQY,CAAS,EAO/B,GANKnB,EAAWqB,CAAK,GAGnBlB,GAAUkB,EAAO,CAAE,UAAW,EAAK,CAAC,EAGlC,CAACD,GAAWA,EAAQ,WAAW,GAAG,GAAKA,EAAQ,MAAM,YAAY,EACnE,MAAM,IAAI,MAAM,2CAA2CA,GAAS,EAEtE,IAAME,EAASf,GAAQc,EAAOD,CAAO,EAIrC,GAHYZ,GAASa,EAAOC,CAAM,EAG1B,WAAW,IAAI,EACrB,MAAM,IAAI,MAAM,+BAA+BF,GAAS,EAE1D,OAAOE,CACT,CAEA,SAASC,GAAeC,EAAuB,CAC7C,IAAMC,EAAMd,EAAQa,CAAO,EAAE,YAAY,EACzC,GAAI,CAACV,GAAmB,IAAIW,CAAG,EAC7B,MAAM,IAAI,MAAM,kFAAkFA,KAAO,CAE7G,CAEA,SAASC,GAAaC,EAAkBC,EAA0B,CAChE,IAAMC,EAAOF,EAAS,MAAM,OAAO,EAAE,KAAMG,GAAMA,EAAE,KAAK,EAAE,WAAW,GAAG,CAAC,EACzE,OAAKD,GACEA,EAAK,QAAQ,SAAU,EAAE,EAAE,KAAK,EAAE,MAAM,EAAG,GAAG,GAAKD,CAC5D,CAOO,SAASG,GAAeZ,EAAiC,CAC9D,IAAME,EAAQH,EAAmBC,EAAW,GAAG,EACzCa,EAAqC,CAAC,EACtCC,EAAO,IAAI,IAIbC,EAAwB,CAAC,EAC7B,GAAI,CAAEA,EAAc9B,GAAYiB,CAAK,CAAE,MAAE,CAAoB,CAC7D,QAAWc,KAAQlB,GAAoB,CACrC,IAAMmB,EAAM1B,EAAKW,EAAOc,CAAI,EAC5B,GAAInC,EAAWoC,CAAG,GAAK9B,EAAS8B,CAAG,EAAE,OAAO,EAAG,CAC7C,IAAMC,EAAO/B,EAAS8B,CAAG,EAAE,KACrBE,EAAUD,EAAO,GAAKA,EAAOxB,EAAiB0B,GAASH,CAAG,EAAI,GACpEJ,EAAS,KAAK,CAAE,KAAMG,EAAM,MAAOT,GAAaY,EAASH,CAAI,EAAG,KAAAE,EAAM,OAAQ,EAAK,CAAC,EACpFJ,EAAK,IAAIE,CAAI,OAEbH,EAAS,KAAK,CAAE,KAAMG,EAAM,MAAOA,EAAM,KAAM,EAAG,OAAQ,EAAM,CAAC,EACjEF,EAAK,IAAIE,CAAI,EAGjB,QAAWA,KAAQD,EAAa,CAC9B,GAAID,EAAK,IAAIE,CAAI,EAAG,SACpB,IAAMC,EAAM1B,EAAKW,EAAOc,CAAI,EAC5B,GAAI,CACF,IAAMK,EAAKlC,EAAS8B,CAAG,EAEvB,GADI,CAACI,EAAG,OAAO,GACX,CAAC1B,GAAmB,IAAIH,EAAQwB,CAAI,EAAE,YAAY,CAAC,EAAG,SAC1D,IAAMG,EAAUE,EAAG,KAAO,GAAKA,EAAG,KAAO3B,EAAiB0B,GAASH,CAAG,EAAI,GAC1EJ,EAAS,KAAK,CAAE,KAAMG,EAAM,MAAOT,GAAaY,EAASH,CAAI,EAAG,KAAMK,EAAG,KAAM,OAAQ,EAAK,CAAC,CAC/F,MAAE,CAA8B,EAIlC,IAAMC,EAAiC,CAAC,EAClCC,EAAahC,EAAKW,EAAO,UAAW,QAAQ,EAClD,GAAIrB,EAAW0C,CAAU,EACvB,GAAI,CACF,QAAWC,KAAQvC,GAAYsC,CAAU,EAAG,CAC1C,IAAME,EAAMlC,EAAKgC,EAAYC,CAAI,EAC7BH,EACJ,GAAI,CAAEA,EAAKlC,EAASsC,CAAG,CAAE,MAAE,CAAQ,QAAS,CAC5C,GAAI,CAACJ,EAAG,YAAY,EAAG,SACvB,IAAMK,EAAOnC,EAAKkC,EAAK,UAAU,EACjC,GAAI,CAAC5C,EAAW6C,CAAI,EAAG,SACvB,IAAMC,EAAMxC,EAASuC,CAAI,EACnBP,EAAUQ,EAAI,KAAOjC,EAAiB0B,GAASM,CAAI,EAAI,GAC7DJ,EAAO,KAAK,CACV,KAAAE,EACA,MAAOjB,GAAaY,EAASK,CAAI,EACjC,KAAMG,EAAI,KACV,KAAM,kBAAkBH,YAC1B,CAAC,EAEL,MAAE,CAAc,CAElB,OAAAF,EAAO,KAAK,CAACM,EAAGC,IAAMD,EAAE,KAAK,cAAcC,EAAE,IAAI,CAAC,EAE3C,CAAE,SAAAhB,EAAU,OAAAS,EAAQ,UAAWpB,CAAM,CAC9C,CAEA,SAASkB,GAASH,EAAqB,CACrC,GAAI,CAAE,OAAOnC,GAAamC,EAAK,OAAO,CAAE,MAAE,CAAQ,MAAO,EAAG,CAC9D,CAMO,SAASa,GAAc9B,EAAmBC,EAAoD,CACnG,IAAMgB,EAAMlB,EAAmBC,EAAWC,CAAO,EAEjD,GADAG,GAAea,CAAG,EACd,CAACpC,EAAWoC,CAAG,EAAG,MAAO,CAAE,KAAMhB,EAAS,QAAS,EAAG,EAC1D,GAAId,EAAS8B,CAAG,EAAE,KAAOvB,EACvB,MAAM,IAAI,MAAM,mDAAmDA,EAAiB,WAAW,EAEjG,MAAO,CAAE,KAAMO,EAAS,QAASnB,GAAamC,EAAK,OAAO,CAAE,CAC9D,CAMO,SAASc,GAAe/B,EAAmBC,EAAiBkB,EAAkD,CACnH,IAAMF,EAAMlB,EAAmBC,EAAWC,CAAO,EACjDG,GAAea,CAAG,EAClB,IAAMe,EAAM,OAAO,KAAKb,EAAS,OAAO,EACxC,GAAIa,EAAI,OAAStC,EACf,MAAM,IAAI,MAAM,mBAAmBsC,EAAI,YAAYtC,WAAwB,EAE7E,OAAAV,GAAUM,GAAQ2B,CAAG,EAAG,CAAE,UAAW,EAAK,CAAC,EAC3ClC,GAAckC,EAAKe,CAAG,EACf,CAAE,KAAM/B,EAAS,MAAO+B,EAAI,MAAO,CAC5C,CAKO,SAASC,GACdjC,EACAwB,EACAU,EAA6C,CAAC,EACd,CAChC,GAAI,CAAC,wBAAwB,KAAKV,CAAI,EACpC,MAAM,IAAI,MAAM,wFAAwF,EAE1G,IAAMvB,EAAU,kBAAkBuB,aAC5BP,EAAMlB,EAAmBC,EAAWC,CAAO,EACjD,GAAIpB,EAAWoC,CAAG,EAAG,MAAM,IAAI,MAAM,UAAUO,oBAAuB,EACtExC,GAAUM,GAAQ2B,CAAG,EAAG,CAAE,UAAW,EAAK,CAAC,EAC3C,IAAMkB,EAAOD,EAAK,SAAS,KAAK,EAC5BA,EAAK,QACLE,GAAqBZ,EAAMU,EAAK,KAAK,EACzC,OAAAnD,GAAckC,EAAKkB,CAAI,EAChB,CAAE,KAAAX,EAAM,KAAMvB,CAAQ,CAC/B,CAMO,SAASoC,GAAiBrC,EAAmBwB,EAAgC,CAClF,GAAI,CAAC,wBAAwB,KAAKA,CAAI,EAAG,MAAM,IAAI,MAAM,uBAAuBA,GAAM,EACtF,IAAMP,EAAMlB,EAAmBC,EAAW,kBAAkBwB,GAAM,EAClE,GAAI,CAAC3C,EAAWoC,CAAG,EAAG,MAAM,IAAI,MAAM,UAAUO,eAAkB,EAElE,GAAI,CADOrC,EAAS8B,CAAG,EACf,YAAY,EAAG,MAAM,IAAI,MAAM,kCAAkCO,GAAM,EAC/E,OAAAtC,GAAO+B,EAAK,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EACrC,CAAE,KAAAO,CAAK,CAChB,CA2BO,SAASc,GAActC,EAAmBwB,EAAgC,CAC/E,GAAI,CAAC,wBAAwB,KAAKA,CAAI,EAAG,MAAM,IAAI,MAAM,uBAAuBA,GAAM,EACtF,IAAMe,EAAYxC,EAAmBC,EAAW,kBAAkBwB,GAAM,EACxE,GAAI,CAAC3C,EAAW0D,CAAS,EAAG,MAAM,IAAI,MAAM,UAAUf,eAAkB,EACxE,IAAMgB,EAAwB,CAAC,EAC/B,OAAAC,EAAKF,EAAW,GAAI,EAAGC,CAAG,EAC1BA,EAAI,KAAK,CAACZ,EAAGC,IAEPD,EAAE,OAASC,EAAE,KAAaD,EAAE,OAAS,MAAQ,GAAK,EAC/CA,EAAE,KAAK,cAAcC,EAAE,IAAI,CACnC,EACMW,EAEP,SAASC,EAAKC,EAAgBC,EAAgBC,EAAeC,EAA6B,CACxF,GAAID,EAAQ/C,GAAsB,OAClC,IAAIiD,EAAoB,CAAC,EACzB,GAAI,CAAEA,EAAU7D,GAAYyD,CAAM,CAAE,MAAE,CAAQ,MAAO,CACrD,QAAW1B,KAAQ8B,EAAS,CAC1B,GAAI9B,IAAS,gBAAkBA,IAAS,OAAQ,SAChD,IAAMC,EAAM1B,EAAKmD,EAAQ1B,CAAI,EACvB+B,EAAMJ,EAAS,GAAGA,KAAU3B,IAASA,EACvCK,EACJ,GAAI,CAAEA,EAAKlC,EAAS8B,CAAG,CAAE,MAAE,CAAQ,QAAS,CAC5C,GAAII,EAAG,YAAY,EACjBwB,EAAI,KAAK,CAAE,KAAM,GAAGE,KAAQ,KAAM,MAAO,KAAM,EAAG,SAAU,EAAM,CAAC,EACnEN,EAAKxB,EAAK8B,EAAKH,EAAQ,EAAGC,CAAG,UACpBxB,EAAG,OAAO,EAAG,CACtB,IAAMf,EAAMd,EAAQwB,CAAI,EAAE,YAAY,EAEhCgC,EAAgBpD,EAA0B,IAAIU,CAAG,GACrDV,EAA0B,IAAIoB,EAAK,YAAY,CAAC,EAClD6B,EAAI,KAAK,CACP,KAAME,EACN,KAAM,OACN,KAAM1B,EAAG,KACT,SAAU2B,GAAiB3B,EAAG,KAAO3B,CACvC,CAAC,GAGP,CACF,CAGO,SAASuD,GAAcjD,EAAmBwB,EAAcvB,EAAoD,CACjH,GAAI,CAAC,wBAAwB,KAAKuB,CAAI,EAAG,MAAM,IAAI,MAAM,uBAAuBA,GAAM,EACtF,GAAI,CAACvB,GAAWA,EAAQ,SAAS,IAAI,EAAG,MAAM,IAAI,MAAM,sBAAsBA,GAAS,EACvF,IAAMgB,EAAMlB,EAAmBC,EAAW,kBAAkBwB,KAAQvB,GAAS,EAC7E,GAAI,CAACpB,EAAWoC,CAAG,GAAK,CAAC9B,EAAS8B,CAAG,EAAE,OAAO,EAC5C,MAAM,IAAI,MAAM,mBAAmBhB,GAAS,EAE9C,IAAMK,EAAMd,EAAQyB,CAAG,EAAE,YAAY,EAC/BD,EAAOvB,GAASwB,CAAG,EAAE,YAAY,EACvC,GAAI,CAACrB,EAA0B,IAAIU,CAAG,GAAK,CAACV,EAA0B,IAAIoB,CAAI,EAC5E,MAAM,IAAI,MAAM,2BAA2Bf,GAAS,EAEtD,GAAId,EAAS8B,CAAG,EAAE,KAAOvB,EACvB,MAAM,IAAI,MAAM,6BAA6BA,EAAiB,aAAaO,GAAS,EAEtF,MAAO,CAAE,KAAMA,EAAS,QAASnB,GAAamC,EAAK,OAAO,CAAE,CAC9D,CAGO,SAASiC,GACdlD,EACAwB,EACAvB,EACAkB,EACiC,CACjC,GAAI,CAAC,wBAAwB,KAAKK,CAAI,EAAG,MAAM,IAAI,MAAM,uBAAuBA,GAAM,EACtF,GAAI,CAACvB,GAAWA,EAAQ,SAAS,IAAI,EAAG,MAAM,IAAI,MAAM,sBAAsBA,GAAS,EACvF,IAAMgB,EAAMlB,EAAmBC,EAAW,kBAAkBwB,KAAQvB,GAAS,EACvEK,EAAMd,EAAQyB,CAAG,EAAE,YAAY,EAC/BD,EAAOvB,GAASwB,CAAG,EAAE,YAAY,EACvC,GAAI,CAACrB,EAA0B,IAAIU,CAAG,GAAK,CAACV,EAA0B,IAAIoB,CAAI,EAC5E,MAAM,IAAI,MAAM,2BAA2Bf,GAAS,EAEtD,IAAM+B,EAAM,OAAO,KAAKb,EAAS,OAAO,EACxC,GAAIa,EAAI,OAAStC,EACf,MAAM,IAAI,MAAM,mBAAmBsC,EAAI,YAAYtC,WAAwB,EAE7E,OAAAV,GAAUM,GAAQ2B,CAAG,EAAG,CAAE,UAAW,EAAK,CAAC,EAC3ClC,GAAckC,EAAKe,CAAG,EACf,CAAE,KAAM/B,EAAS,MAAO+B,EAAI,MAAO,CAC5C,CAGO,SAASmB,GAAgBnD,EAAmBwB,EAA6B,CAC9E,GAAI,CAAC,wBAAwB,KAAKA,CAAI,EAAG,MAAM,IAAI,MAAM,uBAAuBA,GAAM,EACtF,IAAMe,EAAYxC,EAAmBC,EAAW,kBAAkBwB,GAAM,EACxE,GAAI,CAAC3C,EAAW0D,CAAS,EAAG,MAAM,IAAI,MAAM,UAAUf,eAAkB,EACxE,IAAM4B,EAASpC,GAA0BnC,EAAWU,EAAKgD,EAAWvB,CAAI,CAAC,EACzE,OAAIoC,EAAM,gBAAgB,EAAU,CAAE,QAAS,OAAQ,KAAM,iBAAkB,QAAS,cAAe,EACnGA,EAAM,WAAW,EAAU,CAAE,QAAS,OAAQ,KAAM,YAAa,QAAS,cAAe,EACzFA,EAAM,cAAc,EAAU,CAAE,QAAS,MAAO,KAAM,eAAgB,QAAS,aAAc,EAC7FA,EAAM,kBAAkB,EAAU,CAAE,QAAS,MAAO,KAAM,mBAAoB,QAAS,iCAAkC,EACzHA,EAAM,SAAS,EAAU,CAAE,QAAS,SAAU,KAAM,UAAW,QAAS,gBAAiB,EACtF,CAAE,QAAS,KAAM,KAAM,KAAM,QAAS,IAAK,CACpD,CAEA,SAAShB,GAAqBZ,EAAc6B,EAAwB,CAElE,MAAO,KADcA,GAAO,KAAK,GAAK7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,cAc1B/B,GAAS+B,CAAI;AAAA;AAAA,CAG3B,CCxWA8B,KASO,IAAMC,GAAiC,CAC5C,UAAW,KACX,UAAW,IACX,WAAY,IACZ,gBAAiB,KACjB,SAAU,IACV,QAAS,CAAC,EACV,OAAQ,CAAE,QAAS,GAAO,QAAS,GAAI,IAAK,EAAG,OAAQ,SAAU,UAAW,GAAQ,CACtF,EAGO,SAASC,GAAeC,EAA0D,CACvF,MAAO,CACL,GAAGF,GACH,GAAIE,GAAO,CAAC,EACZ,QAAS,CAAE,GAAIA,GAAK,SAAW,CAAC,CAAG,EACnC,OAAQ,CAAE,GAAGF,GAAe,OAAQ,GAAIE,GAAK,QAAU,CAAC,CAAG,CAC7D,CACF,CAIO,SAASC,GAAmBC,EAAqC,CACtE,GAAI,CAAE,OAAOH,GAAeI,GAAiBD,CAAU,EAAE,MAAM,CAAE,MAAE,CAAQ,OAAOH,GAAe,MAAS,CAAE,CAC9G,CAEA,IAAMK,GAAa,gBAINC,GAAmB,CAAC,SAAU,SAAU,UAAW,eAAe,EAE/E,SAASC,EAAIC,EAAYC,EAAcC,EAA4BC,EAAsB,CACvF,IAAMC,EAAI,OAAOJ,CAAC,EAClB,GAAIA,IAAM,IAAMA,IAAM,MAAQ,CAAC,OAAO,SAASI,CAAC,GAAK,CAACF,EAAGE,CAAC,EAAG,MAAM,IAAI,MAAM,GAAGH,aAAgBE,GAAM,EACtG,OAAOC,CACT,CACA,IAAMC,GAAc,CAACL,EAAYC,IAAiBF,EAAIC,EAAGC,EAAO,GAAM,OAAO,UAAU,CAAC,GAAK,EAAI,EAAG,wBAAwB,EAGrH,SAASK,GAAUN,EAAkB,CAC1C,IAAMO,EAAI,OAAOP,GAAM,SAAWA,EAAE,MAAM,GAAG,EAAE,IAAI,MAAM,EAAI,CAAEA,GAAW,EAAIA,GAAW,EAAIA,GAAW,MAAQA,GAAW,MAAM,EAAE,IAAI,MAAM,EAC7I,GAAIO,EAAE,SAAW,GAAK,CAACA,EAAE,MAAM,OAAO,QAAQ,GAAKA,EAAE,CAAC,GAAK,GAAKA,EAAE,CAAC,GAAK,EAAG,MAAM,IAAI,MAAM,8EAA8E,EACzK,MAAO,CAAE,EAAGA,EAAE,CAAC,EAAG,EAAGA,EAAE,CAAC,EAAG,MAAOA,EAAE,CAAC,EAAG,OAAQA,EAAE,CAAC,CAAE,CACvD,CAOO,SAASC,GAAYC,EAA8CC,EAAqD,CAC7H,IAAMC,EAA4B,CAAE,GAAIF,GAAW,CAAC,CAAG,EAMvD,GALI,cAAeC,IAAOC,EAAK,UAAYN,GAAYK,EAAM,UAAW,WAAW,GAC/E,cAAeA,IAAOC,EAAK,UAAYN,GAAYK,EAAM,UAAW,WAAW,GAC/E,eAAgBA,IAAOC,EAAK,WAAaN,GAAYK,EAAM,WAAY,YAAY,GACnF,aAAcA,IAAOC,EAAK,SAAWZ,EAAIW,EAAM,SAAU,WAAaN,GAAM,OAAO,UAAUA,CAAC,GAAKA,GAAK,EAAG,2BAA2B,GACtI,oBAAqBM,IAAOC,EAAK,gBAAkBZ,EAAIW,EAAM,gBAAiB,kBAAoBN,GAAMA,GAAK,GAAKA,GAAK,EAAG,iBAAiB,GAC3IM,EAAM,SAAW,OAAOA,EAAM,SAAY,SAAU,CACtD,IAAME,EAAU,CAAE,GAAID,EAAK,SAAW,CAAC,CAAG,EAC1C,OAAW,CAACE,EAAMC,CAAI,IAAK,OAAO,QAAQJ,EAAM,OAAO,EAAG,CACxD,GAAI,CAACb,GAAY,KAAKgB,CAAI,EAAG,MAAM,IAAI,MAAM,gBAAgBA,8CAAiD,EAC1GC,IAAS,KAAM,OAAOF,EAAQC,CAAI,EACjCD,EAAQC,CAAI,EAAIP,GAAUQ,CAAI,EAErCH,EAAK,QAAUC,EAEjB,GAAIF,EAAM,QAAU,OAAOA,EAAM,QAAW,SAAU,CACpD,IAAMK,EAAIL,EAAM,OACVM,EAAS,CAAE,GAAIL,EAAK,QAAU,CAAC,CAAG,EACpC,YAAaI,IAAGC,EAAO,QAAU,EAAQD,EAAE,SAC3C,YAAaA,IAAGC,EAAO,QAAUjB,EAAIgB,EAAE,QAAS,iBAAmBX,GAAMA,EAAI,GAAKA,GAAK,IAAK,yBAAyB,GACrH,QAASW,IAAGC,EAAO,IAAMjB,EAAIgB,EAAE,IAAK,aAAeX,GAAMA,EAAI,GAAKA,GAAK,GAAI,wBAAwB,GACnG,cAAeW,IAAGC,EAAO,UAAYX,GAAYU,EAAE,UAAW,kBAAkB,GAChF,WAAYA,IAAGC,EAAO,OAAS,OAAOD,EAAE,MAAM,EAAE,KAAK,GACzDJ,EAAK,OAASK,EAEhB,IAAMC,EAAUN,EAAK,QAAQ,OAC7B,GAAIM,IAAY,QAAa,CAACnB,GAAiB,SAASmB,CAAO,GAAK,CAACN,EAAK,UAAUM,CAAO,EACzF,GAAI,CAAEX,GAAUW,CAAO,CAAE,MAAE,CACzB,MAAM,IAAI,MAAM,kBAAkBA,iCAAkC,CAAC,GAAGnB,GAAkB,GAAG,OAAO,KAAKa,EAAK,SAAW,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,eAAe,CACxJ,CAEF,OAAOA,CACT","names":["existsSync","readdirSync","readFileSync","statSync","resolve","workflowHealth","workflows","runs","now","windowDays","span","byId","r","list","w","mine","recent","prior","failed","paused","lastRunAt","state","b","rank","cache","scanRuns","runsDir","limit","files","f","p","a","live","key","out","path","mtime","hit","summary","readSummary","lines","head","workflowId","status","i","tail","s","DEFAULT_ROUTINE_THRESHOLDS","ROUTINE_LIMITS","defaultNextFire","expression","after","timezone","getNextCronDate","DAY","buildRoutines","input","now","t","nextFire","out","job","runs","outcomes","r","latest","routine","cap","isoOrNull","capOpt","failStreak","scheduleFlags","finish","health","workflowHealth","w","h","runsByWorkflow","list","wf","trigger","n","cfg","state","enabled","a","b","workflowOutcome","isCron","spec","describeFilters","workflowAgent","s","o","changedAt","eventFlags","compareRoutines","flags","lastAt","disabled","disabledFlag","cursor","deadline","i","dormant","since","reasons","f","ATTENTION_RANK","ra","rb","status","node","filter","project","key","value","values","v","d","limit","mkdirSync","readFileSync","renameSync","writeFileSync","resolve","stateFile","root","resolve","approvalsDir","readInboxState","raw","readFileSync","k","writeInboxState","state","mkdirSync","path","tmp","writeFileSync","renameSync","snooze","key","until","now","snoozed","v","recordDigest","localDate","recordPopped","waiting","keep","popped","wanted","recordWanted","recordPass","existsSync","readdirSync","readFileSync","resolve","randomBytes","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","unlinkSync","writeFileSync","resolve","base","settings","firstLine","s","sendText","to","text","exec","run","downloadMedia","chat","id","output","DRAFT_MAX","sendWithWacli","d","sendText","draftsDir","root","resolve","validId","id","fileFor","save","draft","mkdirSync","path","tmp","writeFileSync","renameSync","createDraft","input","now","text","randomBytes","readDraft","readFileSync","listDrafts","status","dir","existsSync","readdirSync","f","a","b","decideDraft","verdict","opts","claim","decided","r","e","unlinkSync","init_sqlite","init_store","init_store","FINISHED_STEP_STATES","DETAIL_MAX","ensurePlanTables","db","hasPlanTables","toPlan","r","toStep","STEP_COLUMNS","PlanStore","requestId","createdBy","steps","now","insertPlan","insertStep","s","i","first","own","row","idx","patch","sets","values","key","value","col","state","step","kind","detail","limit","STEP_TEXT_MAX","TEXT_MAX","existsSync","readFileSync","init_config","init_initiator","readRequestSettings","configPath","path","findConfigPath","raw","existsSync","expandEnvVars","readFileSync","parsed","requestsConfigSchema","updateRequestSettings","patch","opts","unknown","c","HUMAN_CHANNELS","applyConfigMutation","cfg","key","p","APPROVAL_KINDS","DETAIL_MAX","clip","s","n","flat","configPathFor","ctx","resolve","candidates","c","existsSync","wikiDirFor","cardItems","listCards","cardItem","scheduleItems","path","cfg","readFileSync","now","out","id","job","approval","tz","human","humanizeCron","when","what","creating","formatFireTime","nextFireTime","memoryAgents","root","dir","readdirSync","f","memoryItems","agents","store","MemoryStore","agent","proposalBacking","e","failures","wikiItems","listProposals","p","whatsappItems","listDrafts","d","requestStoreFor","db","openDb","requestStores","hasRequestTables","RequestStore","planStoreFor","hasPlanTables","planStores","PlanStore","requestItems","r","SOURCES","byUrgency","a","b","ea","eb","listInbox","opts","snoozed","readInboxState","items","errors","hidden","kind","found","item","until","parseKey","key","i","ref","stillPending","readCard","readProposal","readDraft","decide","action","parsed","by","hours","snooze","yes","decideCard","approveSchedule","rejectSchedule","slash","approveProposal","rejectProposal","decideDraft","readRequestSettings","LOOPBACK","isMeshGatedPath","path","isControlPost","isLoopback","remoteAddress","decideMeshAuth","req","header","token","collectAcceptedMeshTokens","config","env","accepted","p","dashboardTokenForNode","dashboard","target","norm","u","d","first","v","originHost","origin","allowedOriginsFromEnv","env","s","classifyBrowserRequest","headers","extraAllowed","from","hosts","h","site","isStateChangingOrPreflight","method","m","existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","homedir","dirname","join","execFile","execFileSync","execFileSync","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","writeFileSync","homedir","join","SIRI_PREFIX","ID_PREFIX","isSiriVoice","id","TOOLS","readPrefDomain","supported","siriSupported","host","probe","platform","exists","prefDomain","ok","t","ASSET_DIRS","GENDER","siriGender","name","parseSiriId","m","SPECIFIER","parseSiriAssets","plists","seen","text","lang","region","locale","listSiriVoices","dirs","dir","assets","a","SIRI_SAY","siriSayPath","home","ensureSiriSay","path","JXA","CLASSIC","GENDER","parseVoiceList","out","voices","line","id","name","locale","q","g","REFRESH_MS","MISSING_REFRESH_MS","cached","refreshing","listSystemVoices","now","maxAgeMs","execFile","err","listSiriVoices","execFileSync","e","voiceLog","m","warned","setVoiceLog","fn","warnOnce","key","msg","forgetWarnings","match","k","onMissing","setMissingVoiceHook","reportMissing","QUALITY","CHARACTER","rank","v","family","language","modern","fallbackVoice","installed","gender","taken","pool","candidates","same","best","used","n","exact","lang","a","b","findVoice","byId","SIRI_PREFIX","base","siri","standIn","tier","named","castVoices","entries","cast","turn","known","want","free","pick","init_language","OS_DEFAULT","isOsDefault","name","DEFAULT_VOICE_ID","resolveAgentVoice","agentId","agents","settings","installed","listSystemVoices","agent","v","sys","localSystemVoices","systemByLanguage","languageVoices","label","deriveIntro","checkedVoice","owner","locale","findVoice","warnOnce","reportMissing","voiceTraits","q","SIRI_PREFIX","siriGender","siri","parseSiriId","language","nameFor","choice","lang","configured","fitsGender","gender","castSystemVoices","wishes","taken","out","open","fallback","inUse","w","own","pick","standIn","names","id","castVoices","candidates","f","traits","fallbackVoice","add","who","list","suits","voiceForText","text","detectLanguage","a","voiceRef","explicit","ref","pickVoiceId","langs","l","agentVoiceId","systemPrompt","first","m","lead","rest","role","INTRO_GAP_MS","MAX_TRACKED","VoiceIntroTracker","gapMs","session","now","last","key","k","introInstruction","voice","introduce","style","talkSpeaker","persona","VOICE_MODE_INSTRUCTION","remoteVoiceAppend","context","ctx","clip","intro","REINSTALL_HINT","voiceHealthPath","home","homedir","join","isInstalled","name","installed","locale","q","OS_DEFAULT","SIRI_PREFIX","base","v","findVoice","configuredNames","config","out","add","owner","choice","lang","id","a","findMissingVoices","byVoice","cast","perLanguage","localSystemVoices","entry","now","label","voiceDisplayName","siri","parseSiriId","missingNotice","missing","lines","m","who","readVoiceHealth","file","existsSync","s","readFileSync","writeVoiceHealth","state","mkdirSync","dirname","writeFileSync","renameSync","VoiceHealth","deps","before","found","back","forgetWarnings","k","untold","title","message","e","readFileSync","writeFileSync","existsSync","readdirSync","statSync","resolve","join","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","resolve","z","sourceSchema","itemSchema","fileSchema","BacklogStore","path","resolve","existsSync","p","raw","readFileSync","e","file","jsonP","mkdirSync","dirname","writeFileSync","id","i","input","now","item","inputs","existing","added","patch","idx","updated","before","lines","buckets","sections","key","label","it","check","mention","time","src","sourceBacklogId","source","resolveAssigneesForSource","agentIds","source","daemon","out","mappings","agentId","m","x","syncBacklogItemToSource","item","patch","log","syncToGitLab","syncToGitHub","gl","token","host","attempts","api","path","init","projectEnc","assigneeIds","u","res","arr","milestoneId","e","body","text","a","repo","milestoneNumber","found","nextLabels","issue","current","l","adds","r","CHECK_RE","MENTION_RE","TIME_RE","parseDuration","s","hMatch","mMatch","BacklogWorkSource","path","daemon","log","BacklogStore","full","resolve","existsSync","readFileSync","lines","writeFileSync","agentId","it","items","i","m","body","mention","timeMatch","title","_opts","itemId","item","updated","usernames","resolveAssigneesForSource","syncBacklogItemToSource","e","update","patch","sourcePatch","lineNo","line","note","stage","WikiWorkSource","root","glob","dir","out","entry","readdirSync","join","statSync","files","file","parts","GitLabWorkSource","host","token","projects","agentUsernames","username","names","n","issue","project","ns","labels","labelDetails","l","assignees","a","firstUsername","init","res","projectList","q","issues","iid","type","seg","mins","dur","opts","sinceDays","updatedAfter","params","state","result","deny","prefix","to","assigneeIds","users","x","milestoneId","ms","byUsername","members","b","byTitle","all","input","assignee_ids","u","milestone_id","toLabel","fromLabel","add","remove","createWorkSource","business","ws","gl","mapping","r","UNMAPPED","clientFromProject","project","projects","hit","p","slash","agentClients","business","out","orgChart","climb","agentId","seen","up","c","matchContact","channel","who","contactMap","pool","m","resolveClient","ref","contact","byAgent","parseWorkRef","sessionId","parts","agent","rest","target","KIND_DEFAULT","id","clientPolicy","parseDuration","listClients","ids","v","FENCE_OPEN","FENCE_FULL","extractUiDirective","text","matches","match","json","ui","parseDirective","start","leading","stripUiDirectiveForPreview","m","raw","buttons","replies","skippedActions","b","q","opts","o","type","url","caption","chips","ringNotice","signal","urlBase","link","RING_DEDUP_TTL_MS","normalizeName","name","WebRtcSignalBroker","localName","allowedCallers","log","callId","recipient","now","key","bucket","kept","b","expiry","fn","res","deliver","sink","sub","tag","buffered","fresh","wantRecipient","ok","normalizedRecipient","e","delivered","mkdirSync","readFileSync","renameSync","rmSync","writeFileSync","homedir","dirname","join","CALL_HOLD","join","homedir","readCall","path","now","hold","readFileSync","readHold","focus","readFocus","call","f","writeCallHold","reason","ttlMs","current","rmSync","mkdirSync","dirname","tmp","writeFileSync","renameSync","DEFAULT_PAIR_LIMITS","PairAttemptLimiter","opts","now","client","until","bucket","limit","t","PAIR_CODE_FAILED","MIN_MS","redeemPairCode","req","deps","started","log","line","wait","code","readJson","redeemed","device","padTo","minMs","left","r","execFile","mkdtempSync","rmSync","writeFileSync","tmpdir","join","randomBytes","CARD_PAD","CARD_GRIP","CARD_CSS","time","d","day","since","iso","now","when","soon","initials","name","words","recommended","card","text","low","choices","index","c","i","why","primaryLabel","ICON","path","CLOCK","FOLD","LOCK","renderCardPage","opts","from","nonce","randomBytes","reminder","request","rec","options","esc","recLabel","data","CHOICE_PLACEHOLDER","CARD_CSS","CARD_SCRIPT","CARD_WIDTH","WINDOW_JXA","CARD_PAD","CARD_GRIP","parseAnswer","stdout","card","line","raw","DISMISS_REASONS","choices","choice","text","CHOICE_LIMITS","showCardWindow","settings","exec","seconds","dir","mkdtempSync","join","tmpdir","page","writeFileSync","renderCardPage","args","r","rmSync","DISMISS_REASONS","run","file","args","timeoutMs","done","execFile","err","stdout","SAY_MAX","CUE_TIMEOUT_MS","spokenLine","card","line","cue","s","exec","inCard","speak","name","path","soundPath","timer","r","voice","osa","lines","l","chooseArgs","title","prompt","choices","dialogArgs","buttons","seconds","text","parseDialog","out","i","promptFor","showPopup","settings","deps","spoken","answer","showCardWindow","showDialogs","limit","choice","draftFor","d","POPUP_MAX_AGE_MS","showing","REQUEST_SAY_GAP_MS","requestSaidAt","nextCardToPop","ctx","now","popped","snoozed","passAt","wanted","readInboxState","pass","cards","listCards","asked","card","key","mirrorCovers","until","created","requestsWaiting","requestStoreFor","nextRequestToPop","requestAsCard","r","summarize","CARD_LIMITS","popNext","deps","settings","log","request","focus","readHold","broken","cardRuleBreak","waiting","c","recordPopped","returnedText","repeat","from","answer","showPopup","focusLabel","readCard","decide","e","popAgain","enabled","recordWanted","closeSync","createReadStream","fstatSync","openSync","realpathSync","isAbsolute","resolve","sep","APP_FILES_PATH","AGENT_RE","resolveArtifact","workspace","file","cwd","ARTIFACT_LIMITS","OUTBOX_DIR","artifactType","root","realpathSync","resolve","real","isAbsolute","sep","type","contentDisposition","name","kind","ascii","fileHeaders","mime","parseRange","header","size","m","start","end","handleAppFilesApi","req","res","url","deps","json","status","body","agent","found","fd","openSync","st","fstatSync","closeSync","headers","range","stream","createReadStream","spawn","readFileSync","writeFileSync","tmpdir","join","init_language","sayRate","rate","elevenLabsSpeed","elevenLabsKey","p","v","readFileSync","elevenLabsSynth","key","n","u","signal","res","file","join","tmpdir","writeFileSync","e","warnOnce","systemVoiceFor","text","lang","detectLanguage","sayArgs","id","wpm","isSiriVoice","sayCommand","siriSay","speakLimitMs","words","stopAllSpeakers","script","siriSayScript","spawn","restoreSpokenVoice","host","home","siriSupported","ensureSiriSay","systemPlay","cmd","args","env","spawn","accessSync","constants","readFileSync","homedir","basename","delimiter","dirname","extname","join","AUDIO_LIMITS","checkDurationHeader","header","raw","tooLongMessage","AUDIO_TYPES","audioExt","contentType","base","sttEngines","setting","host","out","sttSetupHint","findBinary","name","explicit","pathEnv","home","dirs","p","d","detectSttHost","key","findFfmpeg","measureSeconds","file","ffmpeg","decodeSeconds","limit","times","runStderr","m","elevenLabsScribe","mime","signal","fd","r","j","localWhisper","engine","bin","outDir","args","env","run","cmd","timeoutMs","resolve","reject","child","err","timer","e","code","mkdtemp","rm","writeFile","tmpdir","join","init_speakable","SPEAK_BODY_MAX","MAX_TRANSCRIBING","transcribing","isVoiceIoPath","path","handleVoiceIo","req","res","deps","json","transcribe","speak","mime","ext","audioExt","stated","checkDurationHeader","audio","readRaw","AUDIO_LIMITS","tooBigMessage","setting","host","engines","sttEngines","where","more","sttSetupHint","reply","dir","mkdtemp","join","tmpdir","transcribeIn","rm","name","file","writeFile","secs","measureSeconds","tooLongMessage","failures","engine","text","runEngine","e","why","custom","elevenLabsScribe","bin","localWhisper","raw","body","agent","peer","speakableAnswer","said","voice","browser","status","key","elevenLabsAudio","max","clean","extractUiDirective","extractArtifacts","toSpeakable","speed","elevenLabsSpeed","resolveVoice","agentId","config","mesh","agents","voiceRef","resolveAgentVoice","limit","resolve","reject","chunks","size","chunk","existsSync","readFileSync","writeFileSync","mkdirSync","readdirSync","rmSync","statSync","resolve","relative","dirname","join","extname","basename","MAX_FILE_BYTES","ALLOWED_EXTENSIONS","SKILL_EDITABLE_EXTENSIONS","SKILL_TREE_MAX_DEPTH","IDENTITY_FILENAMES","resolveInWorkspace","workspace","relPath","wsAbs","target","assertEditable","absPath","ext","extractTitle","markdown","fallback","line","l","listAgentFiles","identity","seen","rootEntries","name","abs","size","content","safeRead","st","skills","skillsRoot","slug","dir","file","stf","a","b","readAgentFile","writeAgentFile","buf","createAgentSkill","opts","body","defaultSkillTemplate","deleteAgentSkill","listSkillTree","skillRoot","out","walk","absDir","relDir","depth","acc","entries","rel","allowedByName","readSkillFile","writeSkillFile","detectSkillDeps","check","title","init_config","DEFAULT_SCREEN","screenSettings","cfg","readScreenSettings","configPath","loadDaemonConfig","REGION_NAME","BUILT_IN_REGIONS","num","v","what","ok","rule","n","positiveInt","parseRect","r","patchScreen","current","patch","next","regions","name","rect","b","buffer","watched"]}
|