@tekmidian/pai 0.42.4 → 0.42.6
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/ARCHITECTURE.md +9 -9
- package/README.md +26 -8
- package/dist/{aibroker-client-BP74gGwD.mjs → aibroker-client-Bn9SnGMa.mjs} +3 -3
- package/dist/{aibroker-client-BP74gGwD.mjs.map → aibroker-client-Bn9SnGMa.mjs.map} +1 -1
- package/dist/{aibroker-client-C6m71p6O.mjs → aibroker-client-D5NT0S1N.mjs} +1 -1
- package/dist/{auto-route-_5zzJz5q.mjs → auto-route-CcD0OWWB.mjs} +4 -4
- package/dist/{auto-route-_5zzJz5q.mjs.map → auto-route-CcD0OWWB.mjs.map} +1 -1
- package/dist/{chain-DTII7xeL.mjs → chain-BbcfP77K.mjs} +5 -4
- package/dist/{chain-DTII7xeL.mjs.map → chain-BbcfP77K.mjs.map} +1 -1
- package/dist/cli/index.mjs +23 -21
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.d.mts.map +1 -1
- package/dist/cli/program.mjs +23 -21
- package/dist/{clusters-j0ZuNYp9.mjs → clusters-Cw_P6lO-.mjs} +2 -2
- package/dist/{clusters-j0ZuNYp9.mjs.map → clusters-Cw_P6lO-.mjs.map} +1 -1
- package/dist/{config-B64vFg14.mjs → config-DCu8THKi.mjs} +41 -9
- package/dist/{config-B64vFg14.mjs.map → config-DCu8THKi.mjs.map} +1 -1
- package/dist/config-DeqFqa1N.mjs +3 -0
- package/dist/config-DxMZjpvY.mjs +1289 -0
- package/dist/config-DxMZjpvY.mjs.map +1 -0
- package/dist/context-handover-cache-B9NKSjwR.mjs +3242 -0
- package/dist/context-handover-cache-B9NKSjwR.mjs.map +1 -0
- package/dist/daemon/index.mjs +23 -22
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-BNjbVsQU.mjs +24 -0
- package/dist/{daemon-B8N1egl0.mjs → daemon-BppziPi6.mjs} +52 -38
- package/dist/daemon-BppziPi6.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +144 -117
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/db-BOBfewFH.mjs +3 -0
- package/dist/{db-a1ixZQjr.mjs → db-DJ8rk0VM.mjs} +15 -6
- package/dist/{db-a1ixZQjr.mjs.map → db-DJ8rk0VM.mjs.map} +1 -1
- package/dist/db-DbY2O8QO.mjs +3 -0
- package/dist/{db-Ca5qfsMC.mjs → db-Ddns80yf.mjs} +15 -6
- package/dist/{db-Ca5qfsMC.mjs.map → db-Ddns80yf.mjs.map} +1 -1
- package/dist/{detect-CdaA48EI.mjs → detect-Bf2z-oKB.mjs} +1 -1
- package/dist/{detect-CdaA48EI.mjs.map → detect-Bf2z-oKB.mjs.map} +1 -1
- package/dist/detector-CkwkXuGh.mjs +5 -0
- package/dist/{detector-C73Nxm94.mjs → detector-DWXkeTlk.mjs} +2 -2
- package/dist/{detector-C73Nxm94.mjs.map → detector-DWXkeTlk.mjs.map} +1 -1
- package/dist/{embeddings-CEBGrzwu.mjs → embeddings-ClqKtLZ8.mjs} +1 -1
- package/dist/{embeddings-DOLZnT1X.mjs → embeddings-DvRKcQyt.mjs} +1 -1
- package/dist/{embeddings-DOLZnT1X.mjs.map → embeddings-DvRKcQyt.mjs.map} +1 -1
- package/dist/factory-47Qf_df7.mjs +3 -0
- package/dist/{factory-DEb9NmHJ.mjs → factory-DNYAqjH0.mjs} +8 -8
- package/dist/{factory-DEb9NmHJ.mjs.map → factory-DNYAqjH0.mjs.map} +1 -1
- package/dist/{fallback-oJl3BaIN.mjs → fallback-Cwt4hmh5.mjs} +182 -122
- package/dist/fallback-Cwt4hmh5.mjs.map +1 -0
- package/dist/{helpers-IjZkXBhj.mjs → helpers-crDEr6S2.mjs} +1 -1
- package/dist/{helpers-IjZkXBhj.mjs.map → helpers-crDEr6S2.mjs.map} +1 -1
- package/dist/hooks/capture-all-events.mjs +117 -34
- package/dist/hooks/capture-all-events.mjs.map +4 -4
- package/dist/hooks/capture-session-summary.mjs +110 -33
- package/dist/hooks/capture-session-summary.mjs.map +4 -4
- package/dist/hooks/capture-tool-output.mjs +105 -28
- package/dist/hooks/capture-tool-output.mjs.map +4 -4
- package/dist/hooks/cleanup-session-files.mjs +54 -13
- package/dist/hooks/cleanup-session-files.mjs.map +2 -2
- package/dist/hooks/context-compression-hook.mjs +7565 -122
- package/dist/hooks/context-compression-hook.mjs.map +4 -4
- package/dist/hooks/initialize-session.mjs +55 -14
- package/dist/hooks/initialize-session.mjs.map +2 -2
- package/dist/hooks/inject-observations.mjs +79 -18
- package/dist/hooks/inject-observations.mjs.map +3 -3
- package/dist/hooks/load-core-context.mjs +55 -14
- package/dist/hooks/load-core-context.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +185 -103
- package/dist/hooks/load-project-context.mjs.map +4 -4
- package/dist/hooks/mcp-deferred-gate.mjs +54 -13
- package/dist/hooks/mcp-deferred-gate.mjs.map +2 -2
- package/dist/hooks/mcp-deferred-reminder.mjs +9 -0
- package/dist/hooks/mcp-deferred-reminder.mjs.map +3 -3
- package/dist/hooks/observe.mjs +54 -13
- package/dist/hooks/observe.mjs.map +2 -2
- package/dist/hooks/post-compact-inject.mjs +7419 -16
- package/dist/hooks/post-compact-inject.mjs.map +4 -4
- package/dist/hooks/route-agents-to-worker.mjs +7778 -171
- package/dist/hooks/route-agents-to-worker.mjs.map +4 -4
- package/dist/hooks/route-edits-to-worker.mjs +90 -0
- package/dist/hooks/route-edits-to-worker.mjs.map +7 -0
- package/dist/hooks/security-validator.mjs +147 -6
- package/dist/hooks/security-validator.mjs.map +4 -4
- package/dist/hooks/session-commands.mjs +9 -0
- package/dist/hooks/session-commands.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +182 -105
- package/dist/hooks/stop-hook.mjs.map +4 -4
- package/dist/hooks/subagent-stop-hook.mjs +3 -0
- package/dist/hooks/subagent-stop-hook.mjs.map +1 -1
- package/dist/hooks/sync-todo-to-md.mjs +54 -13
- package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
- package/dist/hooks/update-tab-on-action.mjs +3 -0
- package/dist/hooks/update-tab-on-action.mjs.map +1 -1
- package/dist/hooks/update-tab-titles.mjs +3 -0
- package/dist/hooks/update-tab-titles.mjs.map +1 -1
- package/dist/hooks/whisper-reinject.mjs +3 -0
- package/dist/hooks/whisper-reinject.mjs.map +1 -1
- package/dist/hooks/whisper-rules.mjs +134 -29
- package/dist/hooks/whisper-rules.mjs.map +4 -4
- package/dist/hooks/worker-proxy.mjs +7737 -117
- package/dist/hooks/worker-proxy.mjs.map +4 -4
- package/dist/hooks/worker-status-line.mjs +7791 -144
- package/dist/hooks/worker-status-line.mjs.map +4 -4
- package/dist/hooks/worker-supervision.mjs +7721 -115
- package/dist/hooks/worker-supervision.mjs.map +4 -4
- package/dist/index.d.mts +4 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +10 -10
- package/dist/{indexer-backend-BPTzKFZU.mjs → indexer-backend-CdvCJCkD.mjs} +3 -3
- package/dist/{indexer-backend-BPTzKFZU.mjs.map → indexer-backend-CdvCJCkD.mjs.map} +1 -1
- package/dist/{ipc-client-BmypMNYk.mjs → ipc-client-C6NryMMb.mjs} +2 -2
- package/dist/{ipc-client-BmypMNYk.mjs.map → ipc-client-C6NryMMb.mjs.map} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs → kg-entity-BSjM5xZh.mjs} +1 -1
- package/dist/{kg-entity-r8duqhi9.mjs.map → kg-entity-BSjM5xZh.mjs.map} +1 -1
- package/dist/{latent-ideas-CtSca4qG.mjs → latent-ideas-BfL4nCQ3.mjs} +4 -4
- package/dist/{latent-ideas-CtSca4qG.mjs.map → latent-ideas-BfL4nCQ3.mjs.map} +1 -1
- package/dist/{link-boost-Bucnz3n8.mjs → link-boost-C5PaNdzm.mjs} +1 -1
- package/dist/{link-boost-Bucnz3n8.mjs.map → link-boost-C5PaNdzm.mjs.map} +1 -1
- package/dist/main-resolver-CnfnWa8C.mjs +7 -0
- package/dist/{main-resolver-DnkxPYkr.mjs → main-resolver-DvYu9jbl.mjs} +21 -11
- package/dist/main-resolver-DvYu9jbl.mjs.map +1 -0
- package/dist/{migrate-Cjzeefn9.mjs → migrate-CfERwXhY.mjs} +2 -2
- package/dist/{migrate-Cjzeefn9.mjs.map → migrate-CfERwXhY.mjs.map} +1 -1
- package/dist/{neighborhood-CM7Jgb51.mjs → neighborhood-CETR_P4y.mjs} +2 -2
- package/dist/{neighborhood-CM7Jgb51.mjs.map → neighborhood-CETR_P4y.mjs.map} +1 -1
- package/dist/{note-context-DCwE2A-M.mjs → note-context-DCcRWBoN.mjs} +1 -1
- package/dist/{note-context-DCwE2A-M.mjs.map → note-context-DCcRWBoN.mjs.map} +1 -1
- package/dist/pai-home-ChAOupZc.mjs +164 -0
- package/dist/pai-home-ChAOupZc.mjs.map +1 -0
- package/dist/{pai-marker-CHtbJMwJ.mjs → pai-marker-B20KqhA8.mjs} +1 -1
- package/dist/{pai-marker-CHtbJMwJ.mjs.map → pai-marker-B20KqhA8.mjs.map} +1 -1
- package/dist/{planner-D5Ui7DAm.mjs → planner-pXRc-8yt.mjs} +7 -6
- package/dist/{planner-D5Ui7DAm.mjs.map → planner-pXRc-8yt.mjs.map} +1 -1
- package/dist/{postgres-CtOzfoQU.mjs → postgres-CUSXWgTG.mjs} +2 -2
- package/dist/{postgres-CtOzfoQU.mjs.map → postgres-CUSXWgTG.mjs.map} +1 -1
- package/dist/{program-zpoYnJGz.mjs → program-LLNPpxlj.mjs} +8308 -6065
- package/dist/program-LLNPpxlj.mjs.map +1 -0
- package/dist/query-feedback-C7rSfiXU.mjs +3 -0
- package/dist/{query-feedback-Dqz-HC__.mjs → query-feedback-CYyOGi3y.mjs} +20 -6
- package/dist/query-feedback-CYyOGi3y.mjs.map +1 -0
- package/dist/{reranker-CwTCNsgA.mjs → reranker-B5qUTamW.mjs} +1 -1
- package/dist/{reranker-xPm04PXx.mjs → reranker-HHmhZk6v.mjs} +1 -1
- package/dist/{reranker-xPm04PXx.mjs.map → reranker-HHmhZk6v.mjs.map} +1 -1
- package/dist/router-CremHKTI.mjs +3 -0
- package/dist/{router-DD6D4j31.mjs → router-DBAZfE7X.mjs} +2 -2
- package/dist/{router-DD6D4j31.mjs.map → router-DBAZfE7X.mjs.map} +1 -1
- package/dist/{run-D7K5UALn.mjs → run-DDdT9teG.mjs} +530 -210
- package/dist/run-DDdT9teG.mjs.map +1 -0
- package/dist/{runtime-paths-rni52zHX.mjs → runtime-paths-B0P1TvUr.mjs} +1 -1
- package/dist/{runtime-paths-rni52zHX.mjs.map → runtime-paths-B0P1TvUr.mjs.map} +1 -1
- package/dist/{search-Rpk1cSBC.mjs → search-B69cQwDB.mjs} +3 -3
- package/dist/{search-Rpk1cSBC.mjs.map → search-B69cQwDB.mjs.map} +1 -1
- package/dist/{search-CfPpJAWQ.mjs → search-fb-5ovj4.mjs} +2 -2
- package/dist/{server-D4vAieF4.mjs → server-Ch99xt5K.mjs} +15 -14
- package/dist/server-Ch99xt5K.mjs.map +1 -0
- package/dist/skills/Advisor/SKILL.md +3 -3
- package/dist/skills/Providers/SKILL.md +30 -0
- package/dist/skills/Reconstruct/SKILL.md +1 -1
- package/dist/skills/VaultContext/SKILL.md +1 -1
- package/dist/skills/Whisper/SKILL.md +3 -3
- package/dist/skills/Worker/SKILL.md +1 -1
- package/dist/{sources-CS43CpuS.mjs → sources-KBGEA_E6.mjs} +2 -2
- package/dist/{sources-CS43CpuS.mjs.map → sources-KBGEA_E6.mjs.map} +1 -1
- package/dist/{sqlite-Cq3gJcq9.mjs → sqlite-By6r2YhZ.mjs} +3 -3
- package/dist/{sqlite-Cq3gJcq9.mjs.map → sqlite-By6r2YhZ.mjs.map} +1 -1
- package/dist/{state-CY6I-Hnc.mjs → state-DnYGRmS7.mjs} +1 -1
- package/dist/{state-BRdB-Bf1.mjs → state-PJrgfDLW.mjs} +1 -1
- package/dist/{state-BRdB-Bf1.mjs.map → state-PJrgfDLW.mjs.map} +1 -1
- package/dist/{stop-words-Hfu8u22w.mjs → stop-words-BaMEGVeY.mjs} +1 -1
- package/dist/{stop-words-Hfu8u22w.mjs.map → stop-words-BaMEGVeY.mjs.map} +1 -1
- package/dist/{sync-BWbe8JTg.mjs → sync-Btcb6xA4.mjs} +3 -3
- package/dist/{sync-BWbe8JTg.mjs.map → sync-Btcb6xA4.mjs.map} +1 -1
- package/dist/{themes-B7h9YDiy.mjs → themes-BVoAnLZj.mjs} +3 -3
- package/dist/{themes-B7h9YDiy.mjs.map → themes-BVoAnLZj.mjs.map} +1 -1
- package/dist/{tools-BW3NLMPk.mjs → tools-BradfQk7.mjs} +4 -3
- package/dist/{tools-LlzcV_eK.mjs → tools-UrSHIe29.mjs} +30 -233
- package/dist/tools-UrSHIe29.mjs.map +1 -0
- package/dist/{trace-u4hT-pLD.mjs → trace-Cy8quQTm.mjs} +1 -1
- package/dist/{trace-u4hT-pLD.mjs.map → trace-Cy8quQTm.mjs.map} +1 -1
- package/dist/{utils-DhMex3Ox.mjs → utils-BD8g8oSf.mjs} +1 -1
- package/dist/{utils-9Err2RBW.mjs → utils-CJymHkYm.mjs} +1 -1
- package/dist/{utils-9Err2RBW.mjs.map → utils-CJymHkYm.mjs.map} +1 -1
- package/dist/{vault-indexer-mata9S7b.mjs → vault-indexer-C5i-OAyr.mjs} +2 -2
- package/dist/{vault-indexer-mata9S7b.mjs.map → vault-indexer-C5i-OAyr.mjs.map} +1 -1
- package/dist/wakeup-TnGjJY3I.mjs +218 -0
- package/dist/wakeup-TnGjJY3I.mjs.map +1 -0
- package/dist/work-queue-worker-BA5IDBwQ.mjs +567 -0
- package/dist/work-queue-worker-BA5IDBwQ.mjs.map +1 -0
- package/dist/work-queue-worker-DLM2wlqM.mjs +14 -0
- package/dist/{zettelkasten-F0D_XfQ4.mjs → zettelkasten-BBjA7GcP.mjs} +4 -4
- package/dist/{zettelkasten-F0D_XfQ4.mjs.map → zettelkasten-BBjA7GcP.mjs.map} +1 -1
- package/docs/commands/README.md +30 -3
- package/docs/commands/audit.md +88 -0
- package/docs/commands/backup.md +2 -2
- package/docs/commands/clear-names.md +1 -1
- package/docs/commands/config.md +59 -0
- package/docs/commands/daemon.md +1 -1
- package/docs/commands/db.md +1 -1
- package/docs/commands/end.md +1 -1
- package/docs/commands/help.md +1 -1
- package/docs/commands/identity.md +1 -1
- package/docs/commands/kg.md +1 -1
- package/docs/commands/launch.md +45 -0
- package/docs/commands/mcp.md +1 -1
- package/docs/commands/memory.md +3 -3
- package/docs/commands/notify.md +1 -1
- package/docs/commands/observation.md +1 -1
- package/docs/commands/obsidian.md +1 -1
- package/docs/commands/pause.md +1 -1
- package/docs/commands/project.md +46 -1
- package/docs/commands/projects.md +46 -1
- package/docs/commands/registry.md +1 -1
- package/docs/commands/restore.md +1 -1
- package/docs/commands/session.md +1 -1
- package/docs/commands/sessions.md +1 -1
- package/docs/commands/setup.md +1 -1
- package/docs/commands/shell-init.md +1 -1
- package/docs/commands/skill.md +1 -1
- package/docs/commands/task.md +1 -1
- package/docs/commands/topic.md +1 -1
- package/docs/commands/update.md +1 -1
- package/docs/commands/worker.md +95 -6
- package/docs/commands/zettel.md +1 -1
- package/docs/provider-abstraction.md +1 -1
- package/docs/provider-independence-details.md +8 -5
- package/docs/task-bus.md +1 -1
- package/docs/worker.md +136 -83
- package/docs/workers-config.md +446 -0
- package/package.json +4 -2
- package/scripts/build-hooks.mjs +56 -3
- package/src/hooks/pre-compact.sh +5 -2
- package/src/hooks/session-stop.sh +29 -4
- package/src/hooks/ts/capture-all-events.ts +3 -3
- package/src/hooks/ts/lib/advisor-budget.test.ts +98 -0
- package/src/hooks/ts/lib/advisor-budget.ts +90 -0
- package/src/hooks/ts/lib/agent-gate.test.ts +101 -0
- package/src/hooks/ts/lib/agent-gate.ts +112 -0
- package/src/hooks/ts/lib/context-fill.ts +2 -2
- package/src/hooks/ts/lib/edit-gate.test.ts +52 -0
- package/src/hooks/ts/lib/edit-gate.ts +51 -0
- package/src/hooks/ts/lib/mcp-deferred-reminder.test.ts +8 -0
- package/src/hooks/ts/lib/pai-paths.test.ts +93 -0
- package/src/hooks/ts/lib/pai-paths.ts +168 -24
- package/src/hooks/ts/lib/project-utils/index.ts +1 -0
- package/src/hooks/ts/lib/project-utils/paths.ts +5 -4
- package/src/hooks/ts/lib/project-utils.ts +1 -0
- package/src/hooks/ts/post-tool-use/capture-tool-output.ts +2 -2
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
- package/src/hooks/ts/pre-tool-use/route-agents-to-worker.ts +46 -99
- package/src/hooks/ts/pre-tool-use/route-edits-to-worker.test.ts +47 -0
- package/src/hooks/ts/pre-tool-use/route-edits-to-worker.ts +96 -0
- package/src/hooks/ts/pre-tool-use/security-validator.ts +2 -1
- package/src/hooks/ts/session-end/capture-session-summary.ts +4 -4
- package/src/hooks/ts/session-start/initialize-session.ts +3 -3
- package/src/hooks/ts/session-start/inject-observations.test.ts +56 -0
- package/src/hooks/ts/session-start/inject-observations.ts +28 -8
- package/src/hooks/ts/session-start/load-core-context.ts +4 -4
- package/src/hooks/ts/session-start/load-project-context.ts +2 -2
- package/src/hooks/ts/session-start/mcp-deferred-reminder.ts +2 -0
- package/src/hooks/ts/session-start/session-commands.ts +4 -0
- package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +36 -0
- package/src/hooks/ts/stop/stop-hook.ts +6 -11
- package/src/hooks/ts/user-prompt/whisper-rules.test.ts +49 -0
- package/src/hooks/ts/user-prompt/whisper-rules.ts +88 -47
- package/statusline-command.sh +277 -116
- package/templates/agent-prefs.example.md +8 -8
- package/templates/claude-md.template.md +1 -1
- package/templates/voices.example.json +3 -3
- package/dist/config-C_ErGddD.mjs +0 -3
- package/dist/config-_lQI_W08.mjs +0 -640
- package/dist/config-_lQI_W08.mjs.map +0 -1
- package/dist/context-handover-cache-pNFdBNrU.mjs +0 -1548
- package/dist/context-handover-cache-pNFdBNrU.mjs.map +0 -1
- package/dist/daemon-B8N1egl0.mjs.map +0 -1
- package/dist/daemon-DHfyoTlk.mjs +0 -23
- package/dist/db-O-cyAPfS.mjs +0 -3
- package/dist/db-XEwJbuGO.mjs +0 -3
- package/dist/detector-C-7KRi42.mjs +0 -5
- package/dist/factory-Dbo8yNPi.mjs +0 -3
- package/dist/fallback-oJl3BaIN.mjs.map +0 -1
- package/dist/main-resolver-BAfOMppW.mjs +0 -7
- package/dist/main-resolver-DnkxPYkr.mjs.map +0 -1
- package/dist/program-zpoYnJGz.mjs.map +0 -1
- package/dist/query-feedback-DGoVRaWD.mjs +0 -3
- package/dist/query-feedback-Dqz-HC__.mjs.map +0 -1
- package/dist/router-BTIGZR2_.mjs +0 -3
- package/dist/run-D7K5UALn.mjs.map +0 -1
- package/dist/server-D4vAieF4.mjs.map +0 -1
- package/dist/tools-LlzcV_eK.mjs.map +0 -1
- package/dist/work-queue-worker-AEmfVE0F.mjs +0 -14
- package/dist/work-queue-worker-DIT-VLSh.mjs +0 -2136
- package/dist/work-queue-worker-DIT-VLSh.mjs.map +0 -1
- /package/dist/{indexer-D7MvSQPY.mjs → indexer-C5Le7XE7.mjs} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"query-feedback-Dqz-HC__.mjs","names":[],"sources":["../src/zettelkasten/query-feedback.ts"],"sourcesContent":["/**\n * Query feedback loop — persist search queries and results as markdown files.\n *\n * When memory_search or zettel_converse returns results, the query + result\n * metadata is saved to ~/.config/pai/queries/ as a markdown file with YAML\n * frontmatter. The daemon indexer picks these up on the next cycle and indexes\n * them into federation.db, creating a self-reinforcing feedback loop: past\n * queries become searchable context for future queries.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nexport interface QueryRecord {\n /** The original query string. */\n query: string;\n /** Timestamp of the query. */\n timestamp: number;\n /** Tool that produced the result: 'memory_search' | 'zettel_converse'. */\n source: string;\n /** Slugs of the top result paths (for linking back). */\n sourceSlugs: string[];\n /** Preview of the answer/result (first 500 chars). */\n answerPreview: string;\n /** Number of results returned. */\n resultCount: number;\n}\n\nconst QUERIES_DIR = join(homedir(), \".config\", \"pai\", \"queries\");\n\n/**\n * Ensure the queries directory exists.\n */\nfunction ensureQueriesDir(): void {\n if (!existsSync(QUERIES_DIR)) {\n mkdirSync(QUERIES_DIR, { recursive: true });\n }\n}\n\n/**\n * Generate a filename-safe slug from a query string.\n */\nfunction querySlug(query: string, timestamp: number): string {\n const slug = query\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .slice(0, 60);\n const ts = new Date(timestamp).toISOString().slice(0, 10);\n const shortHash = timestamp.toString(36).slice(-4);\n return `${ts}-${slug}-${shortHash}`;\n}\n\n/**\n * Save a query + result record as a markdown file with YAML frontmatter.\n *\n * The file is written to ~/.config/pai/queries/ and will be picked up by\n * the daemon indexer on the next cycle.\n */\nexport function saveQueryResult(record: QueryRecord): string | null {\n try {\n ensureQueriesDir();\n\n const filename = querySlug(record.query, record.timestamp) + \".md\";\n const filepath = join(QUERIES_DIR, filename);\n\n // Don't overwrite if the exact file already exists\n if (existsSync(filepath)) return filepath;\n\n const frontmatter = [\n \"---\",\n `query: \"${record.query.replace(/\"/g, '\\\\\"')}\"`,\n `timestamp: ${new Date(record.timestamp).toISOString()}`,\n `source: ${record.source}`,\n `result_count: ${record.resultCount}`,\n `source_slugs:`,\n ...record.sourceSlugs.map((s) => ` - \"${s}\"`),\n \"---\",\n ].join(\"\\n\");\n\n const body = [\n \"\",\n `# Query: ${record.query}`,\n \"\",\n `**Source:** ${record.source} `,\n `**Date:** ${new Date(record.timestamp).toISOString().slice(0, 19).replace(\"T\", \" \")} `,\n `**Results:** ${record.resultCount}`,\n \"\",\n \"## Answer Preview\",\n \"\",\n record.answerPreview,\n \"\",\n \"## Source Paths\",\n \"\",\n ...record.sourceSlugs.map((s) => `- \\`${s}\\``),\n \"\",\n ].join(\"\\n\");\n\n writeFileSync(filepath, frontmatter + body, \"utf8\");\n return filepath;\n } catch {\n // Non-critical — don't crash the parent tool if query logging fails\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA6BA,MAAM,cAAc,KAAK,SAAS,EAAE,WAAW,OAAO,UAAU;;;;AAKhE,SAAS,mBAAyB;AAChC,KAAI,CAAC,WAAW,YAAY,CAC1B,WAAU,aAAa,EAAE,WAAW,MAAM,CAAC;;;;;AAO/C,SAAS,UAAU,OAAe,WAA2B;CAC3D,MAAM,OAAO,MACV,aAAa,CACb,QAAQ,gBAAgB,GAAG,CAC3B,QAAQ,QAAQ,IAAI,CACpB,MAAM,GAAG,GAAG;AAGf,QAAO,GAFI,IAAI,KAAK,UAAU,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAE5C,GAAG,KAAK,GADH,UAAU,SAAS,GAAG,CAAC,MAAM,GAAG;;;;;;;;AAUpD,SAAgB,gBAAgB,QAAoC;AAClE,KAAI;AACF,oBAAkB;EAGlB,MAAM,WAAW,KAAK,aADL,UAAU,OAAO,OAAO,OAAO,UAAU,GAAG,MACjB;AAG5C,MAAI,WAAW,SAAS,CAAE,QAAO;AA+BjC,gBAAc,UA7BM;GAClB;GACA,WAAW,OAAO,MAAM,QAAQ,MAAM,OAAM,CAAC;GAC7C,cAAc,IAAI,KAAK,OAAO,UAAU,CAAC,aAAa;GACtD,WAAW,OAAO;GAClB,iBAAiB,OAAO;GACxB;GACA,GAAG,OAAO,YAAY,KAAK,MAAM,QAAQ,EAAE,GAAG;GAC9C;GACD,CAAC,KAAK,KAAK,GAEC;GACX;GACA,YAAY,OAAO;GACnB;GACA,eAAe,OAAO,OAAO;GAC7B,aAAa,IAAI,KAAK,OAAO,UAAU,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC;GACrF,gBAAgB,OAAO;GACvB;GACA;GACA;GACA,OAAO;GACP;GACA;GACA;GACA,GAAG,OAAO,YAAY,KAAK,MAAM,OAAO,EAAE,IAAI;GAC9C;GACD,CAAC,KAAK,KAAK,EAEgC,OAAO;AACnD,SAAO;SACD;AAEN,SAAO"}
|
package/dist/router-BTIGZR2_.mjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-D7K5UALn.mjs","names":["readKey"],"sources":["../src/workers/args.ts","../src/workers/ledger.ts","../src/workers/status.ts","../src/workers/scope.ts","../src/workers/pane.ts","../src/workers/tree.ts","../src/workers/operator.ts","../src/workers/handoff.ts","../src/workers/worktree.ts","../src/workers/report.ts","../src/workers/mcp.ts","../src/workers/codex.ts","../src/workers/run.ts"],"sourcesContent":["/**\n * args.ts — parse the claude-args tail that `pai worker run` receives.\n *\n * The runner needs a few things out of the caller's argument vector: the\n * prompt (for the default label), the requested --output-format (so the final\n * print matches what plain `claude -p` would have produced), whether the\n * caller already chose a --model or an --mcp-config (both suppress the\n * defaults the runner would otherwise force), any --mcp allowlist names, any\n * --allowedTools grants (their mcp__server__… entries decide which servers\n * load), and whether they appended their own system prompt (the worker\n * contract is then added alongside, not instead). Everything is passed\n * through untouched — the runner never rewrites the caller's task.\n */\n\nexport interface ParsedRunnerArgs {\n /** Prompt string after -p/--print, when the run is headless. */\n prompt: string | null;\n /** Caller's --output-format: text (default), json, or stream-json. */\n outputFormat: \"text\" | \"json\" | \"stream-json\";\n /** Args to hand to claude (minus --output-format/--verbose, which we add). */\n rest: string[];\n /** Headless: a -p/--print flag is present. */\n headless: boolean;\n /** Caller passed --model (or --model=…): do not force the provider model. */\n callerModel: boolean;\n /** Caller passed --mcp-config (or --mcp-config=…): keep their MCP setup. */\n callerMcpConfig: boolean;\n /** Caller passed --append-system-prompt: the contract is added alongside. */\n callerSystemPrompt: boolean;\n /** --mcp values (repeatable, comma-separated inside one flag). */\n mcp: string[];\n /** --allowedTools values (repeatable, comma-separated inside one flag). */\n allowedTools: string[];\n}\n\nexport function parseRunnerArgs(argv: string[]): ParsedRunnerArgs {\n let prompt: string | null = null;\n let outputFormat: ParsedRunnerArgs[\"outputFormat\"] = \"text\";\n const rest: string[] = [];\n let headless = false;\n let callerModel = false;\n let callerMcpConfig = false;\n let callerSystemPrompt = false;\n const mcp: string[] = [];\n const allowedTools: string[] = [];\n\n let i = 0;\n while (i < argv.length) {\n const a = argv[i];\n if (a === \"-p\" || a === \"--print\") {\n headless = true;\n rest.push(a);\n if (i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) {\n prompt = argv[i + 1];\n rest.push(prompt);\n i += 1;\n }\n } else if (a === \"--output-format\") {\n const v = argv[i + 1];\n if (v === \"json\" || v === \"stream-json\") outputFormat = v;\n i += 1; // dropped; the runner prints the result in this format itself\n } else if (a.startsWith(\"--output-format=\")) {\n const v = a.slice(\"--output-format=\".length);\n if (v === \"json\" || v === \"stream-json\") outputFormat = v;\n } else if (a === \"--verbose\") {\n // dropped; re-added by the runner\n } else if (a === \"--mcp\") {\n const v = argv[i + 1];\n if (v !== undefined && !v.startsWith(\"-\")) {\n mcp.push(v);\n i += 1;\n }\n } else if (a.startsWith(\"--mcp=\")) {\n mcp.push(a.slice(\"--mcp=\".length));\n } else if (a === \"--allowedTools\") {\n // captured for the MCP derivation, but still claude's flag: it passes through\n rest.push(a);\n const v = argv[i + 1];\n if (v !== undefined && !v.startsWith(\"-\")) {\n allowedTools.push(v);\n rest.push(v);\n i += 1;\n }\n } else if (a.startsWith(\"--allowedTools=\")) {\n allowedTools.push(a.slice(\"--allowedTools=\".length));\n rest.push(a);\n } else {\n if (a === \"--model\") callerModel = true;\n if (a.startsWith(\"--model=\")) callerModel = true;\n if (a === \"--mcp-config\") callerMcpConfig = true;\n if (a.startsWith(\"--mcp-config=\")) callerMcpConfig = true;\n if (a === \"--append-system-prompt\") callerSystemPrompt = true;\n if (a.startsWith(\"--append-system-prompt=\")) callerSystemPrompt = true;\n if (\n prompt === null && !a.startsWith(\"-\") && rest.length > 0 &&\n (rest[rest.length - 1] === \"-p\" || rest[rest.length - 1] === \"--print\")\n ) {\n prompt = a;\n }\n rest.push(a);\n }\n i += 1;\n }\n\n return {\n prompt,\n outputFormat,\n rest,\n headless,\n callerModel,\n callerMcpConfig,\n callerSystemPrompt,\n mcp,\n allowedTools,\n };\n}\n\n/**\n * Turn `-p \"<prompt>\"` into bare `-p` (same for --print): for stream-json\n * stdin runs the prompt moves to the first user message on stdin, so the\n * value must not stay on the command line. Only prompt values directly\n * following the flag are touched; everything else passes through.\n */\nexport function stripPromptValues(argv: string[]): string[] {\n const out: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n const isPrint = a === \"-p\" || a === \"--print\";\n out.push(a);\n if (isPrint && i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) {\n i += 1; // drop the prompt value; the flag itself stays\n }\n }\n return out;\n}\n\n/** Collapse whitespace and cut to n chars with an ellipsis (label rendering). */\nexport function shortText(s: unknown, n: number): string {\n const t = String(s ?? \"\").split(/\\s+/).filter(Boolean).join(\" \");\n return t.length <= n ? t : t.slice(0, n - 1) + \"…\";\n}\n","/**\n * ledger.ts — the routing ledger, one append-only log for every worker event.\n *\n * Line shapes (whitespace-aligned so `tail` reads as a table):\n *\n * 2026-09-17 12:00:00 WORKER-START id=<id> provider=<p> mode=headless model=<m> cwd=<cwd> label=<label>\n * 2026-09-17 12:01:00 WORKER-END id=<id> provider=<p> mode=headless model=<m> rc=0 secs=60 turns=3 tools=8 label=<label>\n * 2026-09-17 12:00:20 WORKER-REROUTE from=<a> to=<b> reason=quota\n * 2026-09-17 12:00:00 DENIED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n * 2026-09-17 12:00:00 ALLOWED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n *\n * `pai worker log` (and glm-log before it) counts these; keep the tags stable.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function ledgerStamp(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` +\n `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`\n );\n}\n\nexport interface LedgerLine {\n stamp: string;\n event: string;\n fields: Record<string, string>;\n}\n\n/** Parse `2026-09-17 12:00:00 TAG key=value …` into its parts. */\nexport function parseLedgerLine(line: string): LedgerLine | null {\n const m = line.match(/^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) (\\S+)(?: (.*))?$/);\n if (!m) return null;\n const fields: Record<string, string> = {};\n // values are whitespace-collapsed by the writers, so spaces safely delimit\n for (const part of (m[3] ?? \"\").split(\" \")) {\n if (!part) continue;\n const eq = part.indexOf(\"=\");\n if (eq <= 0) continue;\n fields[part.slice(0, eq)] = part.slice(eq + 1);\n }\n return { stamp: m[1], event: m[2], fields };\n}\n\nexport function parseLedger(text: string): LedgerLine[] {\n const out: LedgerLine[] = [];\n for (const line of text.split(\"\\n\")) {\n if (!line.trim()) continue;\n const parsed = parseLedgerLine(line);\n if (parsed) out.push(parsed);\n }\n return out;\n}\n\n/** Append one ledger line. `kv` order is the caller's; values are flattened. */\nexport function appendLedger(\n path: string,\n event: string,\n kv: Record<string, string | number | null | undefined>,\n now: Date = new Date()\n): void {\n const parts: string[] = [];\n for (const [k, v] of Object.entries(kv)) {\n if (v === undefined || v === null) continue;\n parts.push(`${k}=${String(v).replace(/\\s+/g, \" \")}`);\n }\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tail = parts.length ? \" \" + parts.join(\" \") : \"\";\n appendFileSync(path, `${ledgerStamp(now)} ${event}${tail}\\n`, \"utf8\");\n}\n\nexport interface LedgerSummary {\n scope: string;\n started: number;\n endedOk: number;\n endedFailed: number;\n denied: number;\n allowed: number;\n reroutes: number;\n lastLines: string[];\n}\n\n/** The counts `pai worker log` prints, over today's lines or the whole file. */\nexport function ledgerSummary(\n path: string,\n scope: \"today\" | \"all\",\n lastN = 15,\n now: Date = new Date()\n): LedgerSummary | null {\n if (!existsSync(path)) return null;\n const text = readFileSync(path, \"utf8\");\n const today = ledgerStamp(now).slice(0, 10);\n const lines = text.split(\"\\n\").filter((l) => l.trim());\n const scoped =\n scope === \"all\" ? lines : lines.filter((l) => l.startsWith(today));\n const parsed = scoped.map(parseLedgerLine).filter((x): x is LedgerLine => x !== null);\n const count = (ev: string) => parsed.filter((p) => p.event === ev).length;\n const ends = parsed.filter((p) => p.event === \"WORKER-END\");\n const ok = ends.filter((p) => p.fields.rc === \"0\").length;\n return {\n scope: scope === \"all\" ? \"all time\" : `today ${today}`,\n started: count(\"WORKER-START\"),\n endedOk: ok,\n endedFailed: ends.length - ok,\n denied: count(\"DENIED-ANTHROPIC-AGENT\"),\n allowed: count(\"ALLOWED-ANTHROPIC-AGENT\"),\n reroutes: count(\"WORKER-REROUTE\"),\n lastLines: lines.slice(-lastN),\n };\n}\n","/**\n * status.ts — the live per-worker status file.\n *\n * One JSON file per run, rewritten atomically on every turn, read by `ps`,\n * `follow`, the status line and the pane logic. Same field set the Python\n * runner wrote, plus `provider` (routing is multi-provider now) and `session`\n * (the AIBroker identity of the launching session, see scope.ts).\n */\n\nimport { existsSync, readFileSync, renameSync, writeFileSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { shortText } from \"./args.js\";\nimport { statusPath } from \"./paths.js\";\n\nexport interface WorkerSessionRef {\n /** AIBroker/iTerm session id (the iTerm UUID). */\n id: string;\n /** AIBroker name of that session, when it has one. */\n name: string;\n}\n\nexport interface WorkerStatus {\n id: string;\n pid: number;\n label: string;\n cwd: string;\n /** iTerm session id of the launching terminal (\"\" outside iTerm). */\n term: string;\n provider: string;\n model: string;\n state: \"running\" | \"done\" | \"failed\" | \"killed\" | \"lost\";\n started: string;\n updated: string;\n turns: number;\n tools: number;\n last: string;\n rc: number | null;\n secs: number | null;\n /** Launching session resolved through AIBroker, when it was. */\n session?: WorkerSessionRef | null;\n /** Claude Code session id (system/init) — what `resume` continues. */\n claudeSession?: string | null;\n /**\n * Claude Code session id of the orchestrator whose Bash launched this run,\n * when the status line's session map knew it (see scope.ts). Distinct from\n * claudeSession, which is the run's own session.\n */\n spawnerSession?: string | null;\n /** Context meter: tokens of the last assistant turn (input+cache+output). */\n contextTokens?: number | null;\n /** Context meter: window size (init model info or provider default). */\n contextWindow?: number | null;\n /** Chain this stage belongs to (the chain id), when it is a chain stage. */\n parent?: string;\n /** Class name of the chain stage (\"draft\", \"implement\", …). */\n stage?: string;\n /** Worktree this run executed in, when it got one (see worktree.ts). */\n worktreeDir?: string | null;\n /** Branch the worker committed on (worker/<id>), set on worktree runs. */\n branch?: string | null;\n /** Commit the branch started from (worktree base) for the commits count. */\n worktreeBase?: string | null;\n /** Commits the worker made on its branch; set with `branch` on success. */\n commits?: number | null;\n /** Set by `pai worker merge` once the branch landed in the original checkout. */\n merged?: boolean;\n /**\n * How the run came to be: \"spawn\" (a `pai worker run` subagent) or \"chat\"\n * (the terminal's interactive pane itself, tracked like a worker). Absent\n * on statuses written before the flag existed — read as a spawn.\n */\n origin?: \"spawn\" | \"chat\";\n}\n\n/** Label a worker gets when launched with neither --label nor a prompt. */\nexport const UNLABELED = \"unlabeled\";\n\n/**\n * Whether a status is the terminal's interactive chat pane, not a spawned\n * task worker. `origin` is the real discriminator; the fallback catches\n * running entries from before the flag existed (unlabeled, no turns yet).\n * Removable once every pane runs code that writes `origin`.\n */\nexport function isChatPane(s: Pick<WorkerStatus, \"origin\" | \"label\" | \"turns\">): boolean {\n return (\n s.origin === \"chat\" ||\n (!s.origin && (s.label === UNLABELED || s.label === \"(no prompt)\") && s.turns === 0)\n );\n}\n\n/** Context-meter percentage 0–100, null when the numbers are missing. */\nexport function contextPercent(s: Pick<WorkerStatus, \"contextTokens\" | \"contextWindow\">): number | null {\n if (!s.contextTokens || !s.contextWindow) return null;\n return Math.round((s.contextTokens / s.contextWindow) * 100);\n}\n\n/** Compact token count: 84k, 200k, 900. */\nexport function fmtContextK(n: number): string {\n return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);\n}\n\n/**\n * The one context-meter label, shared by every renderer so identical\n * numbers always render identically: `ctx 84k/200k (42%)` — used-style\n * percent. Empty when the numbers are missing (callers drop the part).\n */\nexport function contextLabel(\n s: Pick<WorkerStatus, \"contextTokens\" | \"contextWindow\">\n): string {\n const pct = contextPercent(s);\n if (pct === null) return \"\";\n return `ctx ${fmtContextK(s.contextTokens ?? 0)}/${fmtContextK(s.contextWindow ?? 0)} (${pct}%)`;\n}\n\n/** Timestamp format shared by status files and the ledger. */\nexport function nowStamp(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` +\n `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`\n );\n}\n\n// second-resolution ids repeat when two workers/chains start together in one\n// process; a repeat gets a monotonic suffix so files never collide\nlet lastId = \"\";\nlet idSeq = 0;\n\nexport function newWorkerId(d: Date = new Date(), pid = process.pid): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n const base = `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${pid}`;\n if (base === lastId) {\n idSeq += 1;\n return `${base}-${String(idSeq).padStart(2, \"0\")}`;\n }\n lastId = base;\n idSeq = 0;\n return base;\n}\n\n/** Write status atomically (temp + rename) and stamp `updated`. */\nexport function saveStatus(logDir: string, status: WorkerStatus, d: Date = new Date()): void {\n status.updated = nowStamp(d);\n const path = statusPath(logDir, status.id);\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(status), \"utf8\");\n renameSync(tmp, path);\n}\n\n/** Load every status file in the logDir, oldest id first, skipping damage. */\nexport function loadStatuses(logDir: string): WorkerStatus[] {\n if (!existsSync(logDir)) return [];\n const out: WorkerStatus[] = [];\n for (const name of readdirSync(logDir).sort()) {\n if (!name.endsWith(\".status\")) continue;\n try {\n out.push(JSON.parse(readFileSync(join(logDir, name), \"utf8\")) as WorkerStatus);\n } catch {\n // a half-written or damaged status file is not worth a crash\n }\n }\n return out;\n}\n\nexport function loadStatus(logDir: string, id: string): WorkerStatus | null {\n const path = statusPath(logDir, id);\n if (!existsSync(path)) return null;\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as WorkerStatus;\n } catch {\n return null;\n }\n}\n\nexport function alive(pid: number | null | undefined): boolean {\n if (!pid || pid <= 0) return false;\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** \"42s\" under 90s, \"7m\" above — the coarse age the table and bar show. */\nexport function ageOf(ts: string, now: Date = new Date()): string {\n const t = Date.parse(ts.replace(\" \", \"T\"));\n if (Number.isNaN(t)) return \"?\";\n const s = Math.max(0, Math.floor((now.getTime() - t) / 1000));\n return s < 90 ? `${s}s` : `${Math.floor(s / 60)}m`;\n}\n\n/** One-line description of a tool call, e.g. \"Bash: npm test\". */\nexport function describeTool(name: string, inp: unknown): string {\n if (typeof inp !== \"object\" || inp === null) return name;\n const i = inp as Record<string, unknown>;\n const get = (k: string) => (typeof i[k] === \"string\" ? (i[k] as string) : \"\");\n if (name === \"Bash\") return `Bash: ${shortText(get(\"command\"), 70)}`;\n if (name === \"Read\" || name === \"Edit\" || name === \"Write\" || name === \"MultiEdit\") {\n const file = get(\"file_path\").split(\"/\").pop() ?? \"\";\n return `${name}: ${file}`;\n }\n if (name === \"Grep\" || name === \"Glob\") return `${name}: ${shortText(get(\"pattern\"), 50)}`;\n if (name === \"WebSearch\") return `${name}: ${shortText(get(\"query\"), 50)}`;\n if (name === \"WebFetch\") return `${name}: ${shortText(get(\"url\"), 60)}`;\n return name;\n}\n","/**\n * scope.ts — which workers \"belong\" to the terminal asking about them.\n *\n * Two identities, in priority order:\n *\n * 1. AIBroker's session registry (~/.aibroker/session-names.json): iTerm\n * session UUID → session name. The launching session's UUID is stored in\n * each worker's status file as `session.id`, so every pane of a named\n * session sees its workers — regardless of tab layout.\n * 2. The iTerm tab key (`w<window>t<tab>` prefix of ITERM_SESSION_ID): the\n * original heuristic, kept as the fallback when no registry entry\n * matches (AIBroker absent, unnamed session, non-iTerm terminal).\n *\n * If neither is available, viewers fall back to \"all workers\".\n *\n * A third case: workers spawned from a Claude Code Bash tool (the\n * orchestrator pattern) have neither — Claude Code exports no terminal or\n * session identity to its Bash children. Those record `spawnerSession`, the\n * orchestrator's claude session id bridged through the status line's\n * session map (see below), so their tab still claims them.\n */\n\nimport { existsSync, readFileSync, renameSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { loadStatus, type WorkerStatus } from \"./status.js\";\n\nexport const AIBROKER_REGISTRY = join(homedir(), \".aibroker\", \"session-names.json\");\n\n/**\n * `w<window>t<tab>` prefix of an iTerm session id, \"\" if empty or malformed.\n * Panes split from the same tab share this prefix and differ only in `p<n>`.\n */\nexport function tabKey(term: string): string {\n if (!term) return \"\";\n const head = term.split(\"p\", 1)[0];\n const parts = head.slice(1).split(\"t\");\n if (head.startsWith(\"w\") && parts.length === 2 && parts.every((p) => /^\\d+$/.test(p))) {\n return head;\n }\n return \"\";\n}\n\n/** Tab key of the iTerm tab this process runs in, \"\" if not inside iTerm2. */\nexport function currentTabKey(env: NodeJS.ProcessEnv = process.env): string {\n return tabKey(env.ITERM_SESSION_ID ?? \"\");\n}\n\n/** The iTerm UUID part of an ITERM_SESSION_ID (after the last colon). */\nexport function itermUuid(term: string): string {\n if (!term) return \"\";\n return term.split(\":\").pop() ?? \"\";\n}\n\nexport interface SessionIdentity {\n id: string;\n name: string;\n}\n\n/**\n * Resolve the AIBroker session for an ITERM_SESSION_ID. Reads the persistent\n * name registry (the same store `aibroker_rename` writes); returns null when\n * AIBroker is absent or the session is not in it — callers then use the tab\n * key, which is the pre-AIBroker behaviour.\n */\nexport function resolveSession(\n term: string,\n registryPath: string = AIBROKER_REGISTRY\n): SessionIdentity | null {\n const uuid = itermUuid(term);\n if (!uuid) return null;\n let names: Record<string, unknown>;\n try {\n if (!existsSync(registryPath)) return null;\n names = JSON.parse(readFileSync(registryPath, \"utf8\")) as Record<string, unknown>;\n } catch {\n return null;\n }\n const name = names[uuid];\n if (typeof name !== \"string\" || !name) return null;\n return { id: uuid, name };\n}\n\n/**\n * True when `worker` was launched from `term` (the terminal asking about it).\n * Session-id match first; the tab key only when no registry entry matched —\n * a session without a name keeps the tab-scoped behaviour it always had.\n */\nexport function workerInScope(worker: WorkerStatus, term: string): boolean {\n if (!term) return false;\n const uuid = itermUuid(term);\n if (worker.session?.id && uuid) return worker.session.id === uuid;\n return tabKey(worker.term) === tabKey(term) && tabKey(term) !== \"\";\n}\n\n/**\n * Registry key for pane files: the session id when AIBroker knows this\n * terminal, else the tab key, else the raw iTerm UUID.\n */\nexport function scopeKey(term: string): string {\n const session = resolveSession(term);\n if (session) return session.id;\n return currentTabKey() || itermUuid(term);\n}\n\n/** `[Name]` when the worker has an AIBroker session name, else \"\". */\nexport function sessionTag(worker: { session?: { name?: string } | null }): string {\n return worker.session?.name ? `[${worker.session.name}]` : \"\";\n}\n\n// ---------------------------------------------------------------------------\n// spawner sessions — attribution for workers launched from a Claude Code Bash\n// ---------------------------------------------------------------------------\n\n/**\n * Claude Code exports neither ITERM_SESSION_ID nor its own session id to the\n * Bash tool, so a `pai worker run` from an orchestrator has no terminal to\n * record and its status would be unattributable. The status line is the one\n * process that sees both identities at once — the payload's session_id and\n * the tab's ITERM_SESSION_ID — so it bridges them: every refresh writes this\n * cwd-keyed map, and the runner reads it back at spawn time.\n */\nexport interface SessionMapEntry {\n session: string;\n ts: number;\n /**\n * The tab's ITERM_SESSION_ID, when the status line could see one — the\n * bridge back from a claude session id to the terminal it runs in\n * (supervision uses it to push worker events into that terminal).\n * Absent on entries written before the field existed.\n */\n term?: string;\n}\n\n/** How old a map entry may be for a spawn to adopt it (status lines refresh constantly while a session lives). */\nexport const SPAWNER_SESSION_TTL_MS = 10 * 60_000;\n\n/** Map entries not refreshed within this window are pruned on write. */\nconst SESSION_MAP_PRUNE_MS = 60 * 60_000;\n\nexport function sessionMapPath(logDir: string): string {\n return join(logDir, \"claude-session-map.json\");\n}\n\n/**\n * Record that the claude session `session` renders its status line in `cwd`\n * (and, when known, in iTerm session `term`). Never throws — a broken map\n * must not break the bar. Prunes stale entries; skips the write when the\n * entry is unchanged and fresh.\n */\nexport function recordSessionMapEntry(\n logDir: string,\n cwd: string,\n session: string,\n term?: string,\n now: number = Date.now()\n): void {\n if (!cwd || !session) return;\n const path = sessionMapPath(logDir);\n let map: Record<string, SessionMapEntry> = {};\n try {\n if (existsSync(path)) {\n map = JSON.parse(readFileSync(path, \"utf8\")) as Record<string, SessionMapEntry>;\n }\n } catch {\n map = {}; // a damaged map is rewritten, never fatal\n }\n const prev = map[cwd];\n if (\n prev &&\n prev.session === session &&\n (prev.term ?? \"\") === (term ?? \"\") &&\n now - prev.ts < 60_000\n ) {\n return;\n }\n const pruned: Record<string, SessionMapEntry> = {};\n for (const [dir, e] of Object.entries(map)) {\n if (now - e.ts < SESSION_MAP_PRUNE_MS) pruned[dir] = e;\n }\n pruned[cwd] = { session, ts: now, ...(term ? { term } : {}) };\n try {\n mkdirSync(logDir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(pruned), \"utf8\");\n renameSync(tmp, path);\n } catch {\n // unwritable log dir: no attribution this round, nothing else breaks\n }\n}\n\n/**\n * The claude session a new run was spawned by, when it can be known: a run\n * inside another worker inherits its spawner (chain stages keep the\n * orchestrator's tab that way); otherwise a fresh map entry for `cwd`.\n */\nexport function resolveSpawnerSession(\n logDir: string,\n cwd: string,\n env: NodeJS.ProcessEnv = process.env,\n now: number = Date.now()\n): string | null {\n const parentId = env.PAI_WORKER_ID;\n if (parentId) {\n const inherited = loadStatus(logDir, parentId)?.spawnerSession;\n if (inherited) return inherited;\n }\n const path = sessionMapPath(logDir);\n if (!existsSync(path) || !cwd) return null;\n try {\n const entry = (JSON.parse(readFileSync(path, \"utf8\")) as Record<string, SessionMapEntry>)[cwd];\n if (entry && entry.session && now - entry.ts < SPAWNER_SESSION_TTL_MS) return entry.session;\n } catch {\n // a damaged map simply attributes nothing\n }\n return null;\n}\n\n/**\n * The iTerm UUID a claude session last rendered its status line in, when the\n * map knows one: the freshest entry naming that session. This is the reverse\n * of resolveSpawnerSession — worker → orchestrator there, orchestrator →\n * terminal here — and it exists so daemon-side pushes (supervision) can reach\n * a session that has no iTerm identity of its own in any worker status.\n */\nexport function itermForClaudeSession(\n logDir: string,\n claudeSession: string,\n now: number = Date.now()\n): string | null {\n const path = sessionMapPath(logDir);\n if (!existsSync(path) || !claudeSession) return null;\n let map: Record<string, SessionMapEntry>;\n try {\n map = JSON.parse(readFileSync(path, \"utf8\")) as Record<string, SessionMapEntry>;\n } catch {\n return null;\n }\n let best: SessionMapEntry | null = null;\n for (const e of Object.values(map)) {\n if (e.session !== claudeSession || !e.term) continue;\n // a stale entry names a tab the session left; freshness orders them\n if (now - e.ts >= SESSION_MAP_PRUNE_MS) continue;\n if (!best || e.ts > best.ts) best = e;\n }\n const term = best?.term;\n return term ? itermUuid(term) : null;\n}\n","/**\n * pane.ts — the per-worker follow pane in iTerm2.\n *\n * One small-font pane per worker, stacked in a right-hand column: the first\n * worker of a scope splits the launching session vertically, every further\n * one splits the lowest live worker pane horizontally, so panes stack top to\n * bottom. The split never sizes the new session — that would grow the whole\n * window; instead the window's bounds are read before the split and restored\n * right after, so the panes share the space the window already had. The follow\n * command is part of the split itself — iTerm creates the new session already\n * running it — so no text is ever typed into any session afterwards; a separate\n * typing step raced the operator's keystrokes (focus briefly sits on the new\n * pane after a split, and in one observed failure the command landed in the\n * operator's own shell). A split still makes the new session the tab's active\n * one — the scripts re-select the launching session right after, so a pane\n * opening never steals the keystrokes the operator is typing. Each\n * pane runs `pai worker follow <id> --auto-exit <n>` under the `pai-worker`\n * dynamic profile (Close Sessions On End), so panes disappear by themselves.\n *\n * Panes are tracked per scope (AIBroker session id, else tab key) in\n * <logDir>/panes/<key>.json, keyed by iTerm session unique id, newest first —\n * the split lands below the lowest live worker pane.\n *\n * The dynamic profile's font is the family of iTerm's DEFAULT profile (the\n * `Default Bookmark Guid` entry in New Bookmarks) at workers.pane.fontSize —\n * never the launching session's profile, which the Python version did and\n * which produced iTerm's \"unknown parent name\" dialog whenever the two\n * differed. `Dynamic Profile Parent Name` is only written when that name\n * actually exists in New Bookmarks; when iTerm's preferences cannot be read\n * the profile is still written, with font Menlo-Regular and no parent.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { execFileSync } from \"node:child_process\";\nimport {\n existsSync,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { WorkersConfig } from \"./config.js\";\nimport { panesDir } from \"./paths.js\";\nimport { itermUuid, scopeKey } from \"./scope.js\";\n\nexport const PROFILE_NAME = \"pai-worker\";\n\n/** Where the dynamic profile lives (env override keeps tests off the real one). */\nexport function dynamicProfilePath(): string {\n return process.env.PAI_WORKER_PROFILE ?? join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"iTerm2\",\n \"DynamicProfiles\",\n \"pai-worker.json\"\n );\n}\n\n// ---------------------------------------------------------------------------\n// AppleScripts (arguments are passed as argv items, never interpolated)\n// ---------------------------------------------------------------------------\n\n// One pane per worker: split the launching session vertically, or the lowest\n// live candidate horizontally. Returns \"<live ids>,|<new session id>\".\n// Exported for the script-content tests (window size, argv-only arguments).\nexport const WORKER_SPLIT_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n set candList to item 2 of argv\n set followCmd to item 3 of argv\n set profileName to item 4 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n -- sizing the new session would grow the whole window:\n -- pin the window's bounds now and restore them after\n -- the split, so the panes share the existing space.\n -- copy, never set: set stores the property\n -- reference lazily, so restoring it would re-read the\n -- post-split bounds instead of these — observed as the\n -- window jumping to the main display\n copy bounds of w to winBounds\n set sessIDs to {}\n repeat with other in sessions of t\n set end of sessIDs to (id of other as text)\n end repeat\n set lived to {}\n set splitS to missing value\n repeat with cid in my splitIds(candList)\n set cidText to (cid as text)\n if sessIDs contains cidText then\n set end of lived to cidText\n if splitS is missing value then\n set splitS to first session of t whose id is cidText\n end if\n end if\n end repeat\n -- the follow command is part of the split itself: the\n -- pane is born already running it, so no text is ever\n -- typed into any session — a typing step raced the\n -- operator's keystrokes and could even land in the\n -- operator's own session\n if profileName is \"\" then\n if splitS is missing value then\n tell s\n set newS to split vertically with default profile command followCmd\n end tell\n else\n tell splitS\n set newS to split horizontally with default profile command followCmd\n end tell\n end if\n else\n if splitS is missing value then\n tell s\n set newS to split vertically with profile profileName command followCmd\n end tell\n else\n tell splitS\n set newS to split horizontally with profile profileName command followCmd\n end tell\n end if\n end if\n -- a split makes the new session the tab's active one:\n -- re-select the launching session so focus returns to\n -- where the operator was typing, never the new pane\n try\n select s\n end try\n try\n set bounds of w to winBounds\n end try\n set out to \"\"\n repeat with lid in lived\n set out to out & lid & \",\"\n end repeat\n return out & \"|\" & (id of newS as text)\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run\n\non splitIds(s)\n set out to {}\n if s is \"\" then return out\n set prevDels to AppleScript's text item delimiters\n set AppleScript's text item delimiters to \",\"\n repeat with part in text items of s\n set end of out to (part as text)\n end repeat\n set AppleScript's text item delimiters to prevDels\n return out\nend splitIds`;\n\n// TTys of every session in the launching session's tab (no-worker pane variant).\nconst TAB_TTYS_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n set ttys to {}\n repeat with other in sessions of t\n copy (tty of other) to end of ttys\n end repeat\n return ttys\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run`;\n\n// Bounds (x1, y1, x2, y2, comma-joined) of the window hosting one iTerm\n// session — read-only, for `pai worker pane <id> --check`. Exported for the\n// script-content tests (reads bounds, never sets them).\nexport const WINDOW_BOUNDS_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n copy bounds of w to winBounds\n set prevDels to AppleScript's text item delimiters\n set AppleScript's text item delimiters to \", \"\n set out to winBounds as text\n set AppleScript's text item delimiters to prevDels\n return out\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run`;\n\n// Split the launching session vertically and run followCmd in the new pane.\n// Exported for the script-content tests (focus stays on the launching session).\nexport const SPLIT_SCRIPT = `on run(argv)\n set targetID to item 1 of argv\n set followCmd to item 2 of argv\n tell application id \"com.googlecode.iterm2\"\n if not running then return \"notrunning\"\n repeat with w in windows\n repeat with t in tabs of w\n repeat with s in sessions of t\n if id of s is targetID then\n -- the follow command is part of the split itself (see\n -- WORKER_SPLIT_SCRIPT): nothing is ever typed into a\n -- session afterwards\n tell s\n set newS to split vertically with default profile command followCmd\n end tell\n -- keep the tab's active session where it was (see\n -- WORKER_SPLIT_SCRIPT)\n try\n select s\n end try\n return \"opened\"\n end if\n end repeat\n end repeat\n end repeat\n end tell\n return \"notfound\"\nend run`;\n\n// ---------------------------------------------------------------------------\n// osascript / ps / defaults helpers\n// ---------------------------------------------------------------------------\n\n/** Run an AppleScript with argv. Rejects when osascript itself cannot run. */\nfunction osascript(script: string, args: string[]): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n const proc = spawn(\"osascript\", [\"-\", ...args], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n let out = \"\";\n let err = \"\";\n proc.stdout.on(\"data\", (c: Buffer) => (out += c.toString(\"utf8\")));\n proc.stderr.on(\"data\", (c: Buffer) => (err += c.toString(\"utf8\")));\n proc.on(\"error\", reject);\n proc.on(\"close\", () => resolve({ stdout: out, stderr: err }));\n proc.stdin.write(script);\n proc.stdin.end();\n });\n}\n\nfunction psOutput(format: string): string {\n try {\n return execFileSync(\"ps\", [\"-axo\", format], { encoding: \"utf8\" });\n } catch {\n return \"\";\n }\n}\n\n/** TTys of running `worker follow` processes, normalised to /dev/ttysNNN. */\nfunction followTtys(): Set<string> {\n const ttys = new Set<string>();\n for (const line of psOutput(\"tty=,command=\").split(\"\\n\")) {\n const parts = line.trim().split(/\\s+/, 2);\n if (parts.length < 2 || parts[0] === \"ps\") continue;\n if (!/(^|\\/)(worker-follow|pai worker follow|glm-ps follow)\\b/.test(parts[1])) continue;\n if (parts[0].startsWith(\"ttys\")) ttys.add(`/dev/${parts[0]}`);\n }\n return ttys;\n}\n\n/** True while a `follow <wid>` process runs (its pane shows that worker). */\nexport function workerPaneOpen(wid: string): boolean {\n const pat = new RegExp(`(?:worker follow|worker-follow|glm-ps follow) ${wid.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\\\\b`);\n return psOutput(\"command=\").split(\"\\n\").some((ln) => pat.test(ln));\n}\n\n/** One entry of iTerm's New Bookmarks (the fields the pane cares about). */\nexport interface Bookmark {\n Name?: string;\n Guid?: string;\n \"Normal Font\"?: string;\n}\n\n/** What reading iTerm's preferences yielded — `error` carries the reason. */\nexport interface PrefsRead {\n bookmarks: Bookmark[];\n defaultGuid: string | null;\n error: string | null;\n}\n\n/** plutil's message for an ExecFileSync failure, else the exception text. */\nfunction toolErr(e: unknown): string {\n const err = e as { stderr?: string | Buffer; message?: string };\n const stderr = typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\");\n return (stderr || err.message || String(e)).trim();\n}\n\n/**\n * Read New Bookmarks and Default Bookmark Guid from an iTerm preferences\n * plist. Key-scoped `plutil -extract`, never a whole-file JSON conversion:\n * real iTerm preferences carry `<date>` objects (SULastCheckTime & friends)\n * which `plutil -convert json` rejects outright — \"Invalid object in plist\n * for JSON format\" — so the whole-file read always failed and the dynamic\n * profile was never written.\n */\nexport function readItermPlist(plistPath: string): PrefsRead {\n let bookmarks: Bookmark[] = [];\n try {\n const out = execFileSync(\n \"plutil\",\n [\"-extract\", \"New Bookmarks\", \"json\", \"-o\", \"-\", plistPath],\n { encoding: \"utf8\", timeout: 10_000 }\n );\n const parsed = JSON.parse(out) as unknown;\n if (Array.isArray(parsed)) bookmarks = parsed as Bookmark[];\n } catch (e) {\n return { bookmarks: [], defaultGuid: null, error: `extracting New Bookmarks: ${toolErr(e)}` };\n }\n let defaultGuid: string | null = null;\n try {\n const out = execFileSync(\n \"plutil\",\n [\"-extract\", \"Default Bookmark Guid\", \"raw\", \"-o\", \"-\", plistPath],\n { encoding: \"utf8\", timeout: 10_000 }\n );\n defaultGuid = out.trim() || null;\n } catch {\n // no default guid set — defaultBookmarkFrom() yields null, fine\n }\n return { bookmarks, defaultGuid, error: null };\n}\n\n/** iTerm's exported preferences, via a temp plist (no shell pipe involved). */\nexport function itermPrefs(): PrefsRead {\n let dir: string | null = null;\n try {\n dir = mkdtempSync(join(tmpdir(), \"pai-iterm-\"));\n const plist = join(dir, \"iterm2.plist\");\n execFileSync(\"defaults\", [\"export\", \"com.googlecode.iterm2\", plist], {\n encoding: \"utf8\",\n timeout: 10_000,\n });\n return readItermPlist(plist);\n } catch (e) {\n return { bookmarks: [], defaultGuid: null, error: `defaults export: ${toolErr(e)}` };\n } finally {\n if (dir) {\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // best effort cleanup of a temp dir\n }\n }\n }\n}\n\n/** The New Bookmarks entry of that profile name, null if absent. */\nfunction bookmarkFrom(read: PrefsRead, named: string): Bookmark | null {\n return read.bookmarks.find((b) => b.Name === named) ?? null;\n}\n\n/** The bookmark iTerm marks default, null when it is missing or unreadable. */\nfunction defaultBookmarkFrom(read: PrefsRead): Bookmark | null {\n return read.defaultGuid\n ? read.bookmarks.find((b) => b.Guid === read.defaultGuid) ?? null\n : null;\n}\n\n/** The default profile, read fresh from iTerm's preferences. */\nexport function defaultBookmark(): Bookmark | null {\n return defaultBookmarkFrom(itermPrefs());\n}\n\n/**\n * The pane profile's font: the family of iTerm's default profile at\n * `fontSize` points (\"MesloLGLNFM-Regular 18\" + 13 → \"MesloLGLNFM-Regular 13\"),\n * or \"Menlo-Regular <fontSize>\" when the default font cannot be read.\n */\nexport function paneFont(font: string | undefined, fontSize: number): string {\n const idx = (font ?? \"\").lastIndexOf(\" \");\n if (idx > 0) {\n const family = font!.slice(0, idx);\n if (!Number.isNaN(parseFloat(font!.slice(idx + 1)))) return `${family} ${fontSize}`;\n }\n return `Menlo-Regular ${fontSize}`;\n}\n\n/** Write the pai-worker dynamic profile; iTerm2 loads that directory itself. */\nexport function writeDynamicProfile(\n parent: Bookmark | null,\n fontSize: number,\n read: () => PrefsRead = itermPrefs\n): void {\n const profile: Record<string, unknown> = {\n Name: PROFILE_NAME,\n Guid: \"pai-worker-dynamic-profile\",\n \"Normal Font\": paneFont(parent?.[\"Normal Font\"], fontSize),\n \"Close Sessions On End\": true,\n };\n // Parent name only when iTerm actually has that profile loaded — a name it\n // does not know makes every split open an error dialog instead of a pane.\n if (parent?.Name && bookmarkFrom(read(), parent.Name)) {\n profile[\"Dynamic Profile Parent Name\"] = parent.Name;\n }\n const path = dynamicProfilePath();\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify({ Profiles: [profile] }, null, 2) + \"\\n\", \"utf8\");\n renameSync(tmp, path);\n}\n\nlet warnedPrefs = false;\n\n/**\n * Profile for new worker panes, creating the small-font one on demand.\n * \"\" means \"split with the default profile\" (also the fallback when the\n * dynamic profile does not show up in time).\n *\n * The profile file is written whenever it is missing — even when iTerm's\n * preferences cannot be read; then it gets font \"Menlo-Regular <fontSize>\"\n * and no parent, and the reason is logged to stderr once.\n */\nexport async function followProfile(\n fontSize: number,\n read: () => PrefsRead = itermPrefs\n): Promise<string> {\n const first = read();\n const parent = defaultBookmarkFrom(first);\n const path = dynamicProfilePath();\n const existed = existsSync(path);\n if (!existed) writeDynamicProfile(parent, fontSize, () => first);\n if (!parent) {\n if (!warnedPrefs) {\n warnedPrefs = true;\n const reason = first.error ?? \"no profile is marked default\";\n const what = existed\n ? `keeping ${path} as-is`\n : `wrote ${path} with Menlo-Regular ${fontSize} and no parent`;\n process.stderr.write(\n `pai worker pane: cannot read iTerm's default profile (${reason}) — ${what}\\n`\n );\n }\n if (first.error) return \"\"; // polling cannot succeed against unreadable prefs\n }\n for (let i = 0; i < 10; i++) {\n // iTerm2 picks DynamicProfiles up quickly; allow it 2 s\n if (bookmarkFrom(read(), PROFILE_NAME)) return PROFILE_NAME;\n await new Promise((r) => setTimeout(r, 200));\n }\n process.stderr.write(`pai worker pane: profile ${PROFILE_NAME} not visible, splitting with default\\n`);\n return \"\";\n}\n\n// ---------------------------------------------------------------------------\n// Pane registry\n// ---------------------------------------------------------------------------\n\ninterface PaneEntry {\n session: string;\n worker: string;\n opened: string;\n}\n\nfunction loadRegistry(path: string): PaneEntry[] {\n try {\n const reg = JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n return Array.isArray(reg) ? (reg as PaneEntry[]) : [];\n } catch {\n return [];\n }\n}\n\nfunction saveRegistry(path: string, reg: PaneEntry[]): void {\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(reg, null, 1), \"utf8\");\n renameSync(tmp, path);\n}\n\n// ---------------------------------------------------------------------------\n// Public entry points\n// ---------------------------------------------------------------------------\n\n/**\n * The command a worker pane runs: one exec-able line with no shell syntax —\n * iTerm's split command parameter execs without a shell (proven live: panes\n * survive `sleep 30` and a single-quoted `/bin/sh -c`, die instantly on\n * `export …; …` or a redirect), so the command must be one exec-able line.\n * Absolute node on the CLI entry file also sidesteps the panes' bare launchd\n * PATH (no pai, no node) and the `#!/usr/bin/env node` shebang.\n */\nexport function followCommand(wid: string, autoExitSecs: number): string {\n return `${process.execPath} ${absoluteCliPath()} worker follow ${wid} --auto-exit ${autoExitSecs}`;\n}\n\n/** Absolute path of the running pai CLI; falls back to PATH lookup at spawn. */\nfunction absoluteCliPath(): string {\n const argv1 = process.argv[1] ?? \"\";\n if (argv1.endsWith(\"pai\")) return argv1;\n return \"pai\";\n}\n\n/** Open (or only report on) the stacked follow pane of one worker. */\nexport async function openPaneForWorker(\n logDir: string,\n config: WorkersConfig,\n wid: string,\n term: string\n): Promise<string> {\n if (workerPaneOpen(wid)) return `pane for ${wid} already open`;\n const uid = itermUuid(term);\n const regPath = join(panesDir(logDir), `${scopeKey(term)}.json`);\n const reg = loadRegistry(regPath);\n const profile = await followProfile(config.pane.fontSize);\n const cmd = followCommand(wid, config.pane.autoExitSecs);\n // registry newest first: the split lands below the lowest live worker pane\n const cands = [...reg].reverse().map((e) => e.session).join(\",\");\n const p = await osascript(WORKER_SPLIT_SCRIPT, [uid, cands, cmd, profile]);\n const out = p.stdout.trim();\n if (out === \"notfound\" || out === \"notrunning\") {\n throw new Error(\n out === \"notfound\"\n ? \"pai worker pane: no open iTerm2 session matches ITERM_SESSION_ID\"\n : \"pai worker pane: iTerm2 is not running\"\n );\n }\n const bar = out.indexOf(\"|\");\n if (bar < 0) {\n throw new Error(\n `pai worker pane: osascript failed: ${(p.stderr || out).slice(0, 200)}`\n );\n }\n const live = new Set(out.slice(0, bar).split(\",\").filter(Boolean));\n const pruned = reg.filter((e) => live.has(e.session));\n pruned.push({\n session: out.slice(bar + 1),\n worker: wid,\n opened: new Date().toISOString().replace(\"T\", \" \").slice(0, 19),\n });\n saveRegistry(regPath, pruned);\n return `pane opened for ${wid}`;\n}\n\n/**\n * One `--check` line with the bounds of the window hosting `term`'s iTerm\n * session — the before/after pair that shows whether a split moved it.\n * Read-only; never touches the window.\n */\nasync function windowBoundsLine(term: string): Promise<string> {\n const uid = itermUuid(term);\n if (!uid) return \"window bounds: (not in iTerm2)\";\n try {\n const p = await osascript(WINDOW_BOUNDS_SCRIPT, [uid]);\n const out = p.stdout.trim();\n if (out && out !== \"notfound\" && out !== \"notrunning\") return `window bounds: ${out}`;\n const why =\n out === \"notfound\" ? \"iTerm2 session not found\"\n : out === \"notrunning\" ? \"iTerm2 not running\"\n : (p.stderr.trim() || \"no output\").slice(0, 120);\n return `window bounds: (${why})`;\n } catch (e) {\n return `window bounds: (osascript: ${String((e as Error).message ?? e).slice(0, 120)})`;\n }\n}\n\n/**\n * Report-only variant used by `pai worker pane <id> --check`: whether a pane\n * runs for the worker, the bounds of the window hosting the asking session,\n * plus the dynamic profile's path, its existence, and the font it contains\n * (or, when missing, would write).\n */\nexport async function checkPaneForWorker(wid: string, fontSize: number, term: string): Promise<string> {\n const lines = [workerPaneOpen(wid) ? `pane for ${wid} open` : `no pane for ${wid}`];\n lines.push(await windowBoundsLine(term));\n const path = dynamicProfilePath();\n if (existsSync(path)) {\n let font = \"(unreadable)\";\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as {\n Profiles?: Array<{ \"Normal Font\"?: string }>;\n };\n font = parsed.Profiles?.[0]?.[\"Normal Font\"] ?? \"(none)\";\n } catch {\n // font stays \"(unreadable)\"\n }\n lines.push(`profile file: ${path} (exists)`);\n lines.push(`profile font: ${font}`);\n } else {\n const parent = defaultBookmark();\n lines.push(`profile file: ${path} (missing)`);\n lines.push(`profile font (would write): ${paneFont(parent?.[\"Normal Font\"], fontSize)}`);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Split this iTerm tab and run `pai worker follow` in the new pane, unless\n * one already runs here (detected by TTY — iTerm overwrites session names\n * with the running command, so names cannot serve as idempotence).\n */\nexport async function openFollowPane(\n logDir: string,\n _config: WorkersConfig,\n term: string,\n checkOnly: boolean\n): Promise<string> {\n void logDir;\n const uid = itermUuid(term);\n const p = await osascript(TAB_TTYS_SCRIPT, [uid]);\n const out = p.stdout.trim();\n if (out === \"notfound\") throw new Error(\"pai worker pane: no open iTerm2 session matches ITERM_SESSION_ID\");\n if (out === \"notrunning\") throw new Error(\"pai worker pane: iTerm2 is not running\");\n const tabTtys = new Set(out.split(\",\").map((t) => t.trim()).filter(Boolean));\n const overlap = [...tabTtys].filter((t) => followTtys().has(t));\n if (overlap.length > 0) return \"follow pane already open\";\n if (checkOnly) return \"no follow pane\";\n // one exec-able line, no shell syntax — see followCommand()\n const cmd = `${process.execPath} ${absoluteCliPath()} worker follow`;\n const s = await osascript(SPLIT_SCRIPT, [uid, cmd]);\n const sout = s.stdout.trim();\n if (sout === \"opened\") return \"follow pane opened\";\n if (sout === \"notfound\") throw new Error(\"pai worker pane: no open iTerm2 session matches ITERM_SESSION_ID\");\n throw new Error(`pai worker pane: osascript failed: ${(s.stderr || sout).slice(0, 200)}`);\n}\n","/**\n * tree.ts — sub-workers: parent detection and the tree caps.\n *\n * A worker launched by another worker (the runner exports PAI_WORKER_ID in\n * every worker's environment) carries `parent` in its status, so the worker\n * forest is visible in `ps`, the status line and the ledger. Two caps keep\n * the tree from growing without bound, both under `workers.tree`:\n *\n * - maxDepth (default 2): how deep sub-workers may nest. A top-level worker\n * sits at depth 0; its children at 1; grandchildren at 2 — one level past\n * the cap refuses with a clear message.\n * - maxChildren (default 4): how many children of one parent may run at the\n * same time. Finished children do not count; a planner works through its\n * sub-tasks in waves of this size.\n *\n * Chain stages also carry `parent` (the chain id), but chains are not workers\n * and never get a status file — a parent without a status is not capped.\n */\n\nimport type { WorkersTreeConfig } from \"./config.js\";\nimport { alive, loadStatus, loadStatuses, type WorkerStatus } from \"./status.js\";\n\n/** The env var the runner sets in every worker's environment. */\nexport const WORKER_ID_ENV = \"PAI_WORKER_ID\";\n\n/** The worker this process runs inside, when it runs inside one. */\nexport function parentFromEnv(env: NodeJS.ProcessEnv = process.env): string | null {\n const id = env[WORKER_ID_ENV];\n return typeof id === \"string\" && id.trim() ? id.trim() : null;\n}\n\n/**\n * Depth of `id` in the worker forest: 0 for a top-level worker, 1 + the\n * parent's depth for a sub-worker. Parents without a status file (chain ids,\n * unknown ids) count as roots; a cycle reads as its own depth and is cut off\n * after the statuses it walked. `chatIsRoot` stops one level short of the\n * chat-pane tracker (origin \"chat\"): the status line treats the pane as the\n * bar itself, so the workers it spawned are its top level.\n */\nexport function workerDepth(\n statuses: WorkerStatus[],\n id: string,\n opts?: { chatIsRoot?: boolean }\n): number {\n const byId = new Map(statuses.map((s) => [s.id, s]));\n let depth = 0;\n let cur = byId.get(id);\n const seen = new Set<string>([id]);\n while (cur?.parent && !seen.has(cur.parent)) {\n seen.add(cur.parent);\n const next = byId.get(cur.parent);\n if (!next) break; // chain id or stale parent: a root, not a level\n if (!(opts?.chatIsRoot && next.origin === \"chat\")) depth += 1;\n cur = next;\n }\n return depth;\n}\n\n/** Children of `parent` that are still running, oldest first. */\nexport function runningChildren(statuses: WorkerStatus[], parent: string): WorkerStatus[] {\n return statuses.filter((s) => s.parent === parent && s.state === \"running\" && alive(s.pid));\n}\n\n/**\n * Would starting a child of `parent` stay within the caps? Throws with a\n * clear message when it would not; returns silently when `parent` has no\n * status file (a chain id or an unknown id — not a worker, not capped).\n */\nexport function assertChildAllowed(\n logDir: string,\n parent: string,\n caps: WorkersTreeConfig,\n statuses: WorkerStatus[] = loadStatuses(logDir)\n): void {\n if (!statuses.some((s) => s.id === parent)) return;\n const depth = workerDepth(statuses, parent);\n if (depth + 1 > caps.maxDepth) {\n throw new Error(\n `worker tree: ${parent} sits at depth ${depth} and ` +\n `workers.tree.maxDepth is ${caps.maxDepth} — it cannot start another level of sub-workers. ` +\n `Hand the task up instead: pai worker handoff '{\"kind\":\"proposal\",\"text\":\"…\"}'`\n );\n }\n const kids = runningChildren(statuses, parent);\n if (kids.length >= caps.maxChildren) {\n throw new Error(\n `worker tree: ${parent} already has ${kids.length} running sub-workers ` +\n `(${kids.map((k) => k.id).join(\", \")}) and workers.tree.maxChildren is ${caps.maxChildren} — ` +\n `wait for one to finish, or raise the cap in the workers config`\n );\n }\n}\n\n/**\n * The parent a launch should record: an explicit one (chain stage) wins, else\n * the worker this process runs inside. Returns null for top-level runs.\n */\nexport function launchParent(explicit: string | undefined, env: NodeJS.ProcessEnv = process.env): string | null {\n return explicit ?? parentFromEnv(env);\n}\n\n/** Does a status file exist for `id` (i.e. is it a worker rather than a chain)? */\nexport function isWorkerId(logDir: string, id: string): boolean {\n return loadStatus(logDir, id) !== null;\n}\n","/**\n * operator.ts — talking to a running worker.\n *\n * Headless workers run with `--input-format stream-json` and their stdin held\n * open, so a run is a conversation: every line sent to the per-worker Unix\n * socket `<logDir>/<id>.sock` is forwarded to the child as a user message and\n * mirrored into the transcript as an `operator` event (rendered with a »\n * marker). Once the worker has finished its turn, stdin closes 2 s later\n * unless a new message arrives — after that, `say` refuses and `resume`\n * continues the same Claude session with the worker's context intact.\n */\n\nimport { createServer, connect, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { loadStatus, alive } from \"./status.js\";\n\nexport function operatorSocketPath(logDir: string, id: string): string {\n return join(logDir, `${id}.sock`);\n}\n\n/**\n * The runner's side: listen on the worker socket, hand every received line to\n * `onLine`. Returns the server (close it when the run ends; the socket file is\n * unlinked on close, best effort).\n */\nexport function createOperatorServer(\n logDir: string,\n id: string,\n onLine: (text: string) => void\n): import(\"node:net\").Server {\n const path = operatorSocketPath(logDir, id);\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // a stale socket from a crashed run must not block the new one\n }\n const server = createServer((socket: Socket) => {\n let buf = \"\";\n socket.on(\"data\", (chunk: Buffer) => {\n buf += chunk.toString(\"utf8\");\n let nl: number;\n while ((nl = buf.indexOf(\"\\n\")) >= 0) {\n const line = buf.slice(0, nl).replace(/\\r$/, \"\");\n buf = buf.slice(nl + 1);\n if (line.trim()) onLine(line);\n socket.write(\"ok\\n\");\n }\n });\n });\n server.listen(path);\n server.on(\"close\", () => {\n try {\n if (existsSync(path)) unlinkSync(path);\n } catch {\n // already gone\n }\n });\n return server;\n}\n\n/**\n * `pai worker say <id> \"<text>\"`: forward one line to a running worker.\n * Resolves \"ok\", rejects with a clear message when the worker is not running.\n */\nexport function sayToWorker(logDir: string, id: string, text: string, timeoutMs = 4000): Promise<string> {\n const status = loadStatus(logDir, id);\n if (!status) {\n return Promise.reject(new Error(`no worker named \"${id}\"`));\n }\n if (status.state !== \"running\" || !alive(status.pid)) {\n return Promise.reject(\n new Error(\n `worker ${id} is not running (state: ${status.state}) — ` +\n `continue it instead with: pai worker resume ${id} \"<text>\"`\n )\n );\n }\n const path = operatorSocketPath(logDir, id);\n if (!existsSync(path)) {\n return Promise.reject(\n new Error(`worker ${id} has no operator socket (${path}) — it may predate this PAI version`)\n );\n }\n return new Promise((resolve, reject) => {\n const sock = connect(path);\n const fail = (e: Error) => {\n sock.destroy();\n reject(new Error(`cannot talk to worker ${id}: ${e.message}`));\n };\n sock.setTimeout(timeoutMs, () => fail(new Error(\"timeout\")));\n sock.once(\"error\", (e: Error) => fail(e));\n sock.once(\"connect\", () => {\n sock.write(text.replace(/\\n/g, \" \") + \"\\n\");\n });\n sock.once(\"data\", () => {\n sock.end();\n resolve(\"ok\");\n });\n });\n}\n","/**\n * handoff.ts — upward messages between workers.\n *\n * Coordination in the worker tree is upward only: a child appends a handoff\n * to `<logDir>/<parent>.inbox.jsonl` and (when the parent is a running\n * worker) the same text is said to it, so it lands in the parent's\n * conversation as `[handoff from <child>] …`. The parent's pane renders the\n * inbox as `◆` lines, `ps`/`worker_status` show an inbox count, and a child\n * that finishes delivers its structured report automatically as a\n * `kind: \"result\"` handoff. There is no downward or sideways path — telling\n * a worker something is the operator's job (`pai worker say`).\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { alive, loadStatus } from \"./status.js\";\nimport { sayToWorker } from \"./operator.js\";\n\nexport const HANDOFF_KINDS = [\"proposal\", \"result\", \"question\", \"blocker\"] as const;\n\nexport type HandoffKind = (typeof HANDOFF_KINDS)[number];\n\nexport interface Handoff {\n /** Sending worker id. */\n from: string;\n /** Receiving worker id (the parent). */\n to: string;\n kind: HandoffKind;\n text: string;\n /** Structured payload (a report, a proposal's fields, …). */\n data?: Record<string, unknown>;\n /** ISO stamp, attached on append. */\n _ts?: string;\n}\n\n/** Where a worker's handoffs land: <logDir>/<id>.inbox.jsonl. */\nexport function inboxPath(logDir: string, id: string): string {\n return join(logDir, `${id}.inbox.jsonl`);\n}\n\nexport function isHandoffKind(v: unknown): v is HandoffKind {\n return typeof v === \"string\" && (HANDOFF_KINDS as readonly string[]).includes(v);\n}\n\n/**\n * Normalize a raw parsed value into a Handoff, or explain what is missing.\n * `from`/`to` may be preset by the caller (the CLI fills them from the\n * environment); everything else must be present and well-formed.\n */\nexport function parseHandoff(\n v: unknown,\n preset: Partial<Pick<Handoff, \"from\" | \"to\">> = {}\n): Handoff {\n if (typeof v !== \"object\" || v === null || Array.isArray(v)) {\n throw new Error(\"handoff payload must be a JSON object\");\n }\n const o = v as Record<string, unknown>;\n const from = typeof o.from === \"string\" && o.from ? o.from : preset.from;\n const to = typeof o.to === \"string\" && o.to ? o.to : preset.to;\n if (!from) throw new Error(`handoff needs \"from\" (the sending worker id)`);\n if (!to) throw new Error(`handoff needs \"to\" (the parent worker id)`);\n if (!isHandoffKind(o.kind)) {\n throw new Error(`handoff \"kind\" must be one of: ${HANDOFF_KINDS.join(\", \")}`);\n }\n const text = typeof o.text === \"string\" ? o.text : \"\";\n if (!text.trim()) throw new Error(`handoff needs a non-empty \"text\"`);\n const data =\n typeof o.data === \"object\" && o.data !== null && !Array.isArray(o.data)\n ? (o.data as Record<string, unknown>)\n : undefined;\n return { from, to, kind: o.kind, text, ...(data ? { data } : {}) };\n}\n\n/** Append one handoff to its recipient's inbox, stamped now. */\nexport function appendHandoff(logDir: string, h: Handoff, now: Date = new Date()): void {\n const path = inboxPath(logDir, h.to);\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(path, JSON.stringify({ ...h, _ts: now.toISOString() }) + \"\\n\", \"utf8\");\n}\n\n/** Every handoff in a worker's inbox, oldest first; damaged lines skip. */\nexport function readInbox(logDir: string, id: string): Handoff[] {\n const path = inboxPath(logDir, id);\n if (!existsSync(path)) return [];\n const out: Handoff[] = [];\n for (const line of readFileSync(path, \"utf8\").split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n out.push(JSON.parse(line) as Handoff);\n } catch {\n // a half-written line is not worth a crash; the rest still reads\n }\n }\n return out;\n}\n\n/** How the say path prefixes a handoff so the parent model knows its source. */\nexport function handoffMessage(h: Pick<Handoff, \"from\" | \"kind\" | \"text\">): string {\n return `[handoff from ${h.from}] (${h.kind}) ${h.text.replace(/\\s+/g, \" \").trim()}`;\n}\n\n/** True for the exact shape of handoffMessage() — the runner marks the mirror. */\nexport function isHandoffMessage(text: string): boolean {\n return new RegExp(`^\\\\[handoff from \\\\S+\\\\] \\\\((${HANDOFF_KINDS.join(\"|\")})\\\\) `).test(text);\n}\n\n/** Test seam for deliverHandoff: the say path, overridable with a mock. */\nexport interface HandoffDeps {\n say?: (id: string, text: string) => Promise<string>;\n}\n\n/**\n * Deliver one handoff: append it to the parent's inbox, then — when the\n * parent is a running worker with a live operator socket — say it so it\n * enters the parent's conversation. The say is best effort: a parent that is\n * busy, finished or gone still has the inbox line, and nothing here may make\n * a finishing child's exit path fail.\n */\nexport async function deliverHandoff(\n logDir: string,\n h: Handoff,\n deps: HandoffDeps = {},\n now: Date = new Date()\n): Promise<Handoff> {\n appendHandoff(logDir, h, now);\n const say = deps.say ?? ((id: string, text: string) => sayToWorker(logDir, id, text));\n const parent = loadStatus(logDir, h.to);\n if (parent && parent.state === \"running\" && alive(parent.pid)) {\n try {\n await say(h.to, handoffMessage(h));\n } catch {\n // the inbox line is the durable record; a failed say is not an error\n }\n }\n return h;\n}\n\n/**\n * Send a handoff from inside a worker (the `pai worker handoff` CLI and the\n * MCP tool both land here): the sender comes from PAI_WORKER_ID, the\n * recipient from its status file's `parent`. Rejects with a clear message\n * outside a worker or under a parentless worker.\n */\nexport async function handoffFromInside(\n logDir: string,\n env: NodeJS.ProcessEnv,\n payload: unknown,\n deps: HandoffDeps = {}\n): Promise<Handoff> {\n const mine = env.PAI_WORKER_ID;\n if (!mine) {\n throw new Error(\n \"not inside a worker — handoffs are sent by workers (PAI_WORKER_ID unset). \" +\n \"Talk to a running worker with: pai worker say <id> \\\"<text>\\\"\"\n );\n }\n const st = loadStatus(logDir, mine);\n if (!st) throw new Error(`no status for this worker (${mine}) — cannot find its parent`);\n if (!st.parent || !loadStatus(logDir, st.parent)) {\n throw new Error(\n `worker ${mine} has no worker parent to hand off to ` +\n `(parent: ${st.parent ?? \"(none)\"}) — handoffs go up the worker tree only`\n );\n }\n const h = parseHandoff(payload, { from: mine, to: st.parent });\n return deliverHandoff(logDir, h, deps);\n}\n","/**\n * worktree.ts — one git worktree per writing worker.\n *\n * A run whose class edits files (implement, complex, plan) and whose cwd is a\n * git repository gets its own worktree by default: `git worktree add\n * <logDir>/worktrees/<id> -b worker/<id>` from the current HEAD. The worker\n * commits its own work on that branch — the no-commit rule applies to the\n * main branch only — and the parent (or the operator) merges the result back:\n *\n * pai worker merge <id> git merge --no-ff worker/<id> + remove worktree + delete branch\n * pai worker discard <id> remove worktree and branch, keep nothing\n *\n * `ps` marks a worker with an unmerged branch `⎇`. Draft and review run in\n * place; `--no-worktree` opts out, `--worktree` forces one on.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, readdirSync, rmSync, statSync } from \"node:fs\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { loadStatus, saveStatus, UNLABELED, type WorkerStatus } from \"./status.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, statusPath } from \"./paths.js\";\n\nexport function worktreesDir(logDir: string): string {\n return join(logDir, \"worktrees\");\n}\n\nexport function worktreeBranch(id: string): string {\n return `worker/${id}`;\n}\n\nexport function worktreePath(logDir: string, id: string): string {\n return join(worktreesDir(logDir), id);\n}\n\n/** Run git in `cwd`, returning trimmed stdout; throws with stderr on failure. */\nexport function git(cwd: string, args: string[]): string {\n try {\n return execFileSync(\"git\", [\"-C\", cwd, ...args], {\n encoding: \"utf8\",\n timeout: 30_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n }).trim();\n } catch (e) {\n const err = e as { stderr?: Buffer | string; message?: string };\n const why =\n (typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\")) ||\n err.message ||\n String(e);\n throw new Error(`git ${args.join(\" \")} in ${cwd}: ${why.trim()}`);\n }\n}\n\n/** Is `cwd` inside a git repository (a .git dir — worktrees: a .git file)? */\nexport function isGitRepo(cwd: string): boolean {\n try {\n return git(cwd, [\"rev-parse\", \"--git-dir\"]) !== \"\";\n } catch {\n return false;\n }\n}\n\n/**\n * Does the prompt read as a read-only task? Writing classes default to a\n * worktree; a prompt that only asks to look at things should not pay for one.\n * First-word verbs plus the explicit markers people actually write.\n */\nexport function promptLooksReadonly(prompt: string): boolean {\n const p = prompt.trim();\n if (!p) return true;\n if (/\\b(read[- ]only|do not (modify|change|edit|write)|don'?t (modify|change|edit|write)|no changes)\\b/i.test(p)) {\n return true;\n }\n return /^(review|read|analy[sz]e|research|summar[iy]|inspect|investigate|spotcheck|report|find|list|check|verify|describe|explain|show)\\b/i.test(\n p\n );\n}\n\n/** The classes whose runs write files and therefore default to a worktree. */\nexport const WORKTREE_CLASSES = [\"implement\", \"complex\", \"plan\"] as const;\n\n/** How the run flags decide the worktree question; undefined = decide by default. */\nexport type WorktreeFlag = boolean | undefined;\n\n/** Should this run get a worktree? Explicit flag first, then the default rule. */\nexport function worktreeWanted(\n flag: WorktreeFlag,\n opts: { cwd: string; className?: string; prompt: string | null }\n): boolean {\n if (flag !== undefined) return flag;\n if (!opts.className || !(WORKTREE_CLASSES as readonly string[]).includes(opts.className)) {\n return false;\n }\n if (!isGitRepo(opts.cwd)) return false;\n return !promptLooksReadonly(opts.prompt ?? \"\");\n}\n\nexport interface WorktreeInfo {\n dir: string;\n branch: string;\n base: string;\n}\n\n/**\n * Create the worktree and branch for `id` from `cwd`'s HEAD. Throws when git\n * refuses (no commits yet, branch exists, …) — the caller decides whether to\n * degrade to an in-place run.\n */\nexport function addWorktree(logDir: string, id: string, cwd: string): WorktreeInfo {\n const swept = sweepOrphanWorktrees(logDir);\n if (swept.length) {\n appendLedger(ledgerPath(logDir), \"WORKER-NOTE\", {\n id,\n note: `swept orphan worktree(s): ${swept.join(\", \")}`,\n });\n }\n const dir = worktreePath(logDir, id);\n const branch = worktreeBranch(id);\n const base = git(cwd, [\"rev-parse\", \"HEAD\"]);\n git(cwd, [\"worktree\", \"add\", dir, \"-b\", branch]);\n return { dir, branch, base };\n}\n\n/**\n * Remove worktrees (and their `worker/<id>` branches) whose worker no longer\n * exists — no status file left in the log dir. Killed and failed runs clean\n * up after themselves, but a `kill -9` or a crash strands a directory and a\n * branch; every worktree creation sweeps first so they cannot accumulate.\n * Directories younger than `minAgeMin` minutes are left alone: a run that is\n * just starting owns its worktree a moment before its status file exists.\n */\nexport function sweepOrphanWorktrees(logDir: string, minAgeMin = 10): string[] {\n const root = worktreesDir(logDir);\n if (!existsSync(root)) return [];\n const swept: string[] = [];\n for (const ent of readdirSync(root, { withFileTypes: true })) {\n if (!ent.isDirectory()) continue;\n const id = ent.name;\n const dir = join(root, id);\n if (existsSync(statusPath(logDir, id))) continue; // a known worker owns it\n try {\n const ageMin = (Date.now() - statSync(dir).mtimeMs) / 60_000;\n if (ageMin < minAgeMin) continue;\n } catch {\n /* vanished mid-sweep; nothing to do */\n }\n let gitDir: string | null = null;\n try {\n const raw = git(dir, [\"rev-parse\", \"--git-common-dir\"]);\n gitDir = isAbsolute(raw) ? raw : resolve(dir, raw);\n } catch {\n gitDir = null; // not a worktree anymore; just drop the directory\n }\n removeWorktree(gitDir ?? dir, dir, true);\n if (gitDir) {\n try {\n git(gitDir, [\"worktree\", \"prune\"]);\n git(gitDir, [\"branch\", \"-D\", worktreeBranch(id)]);\n } catch {\n // branch already gone or kept by git for a reason; the directory is\n }\n }\n swept.push(id);\n }\n return swept;\n}\n\n/** Commits the branch collected on top of its base. */\nexport function commitsSince(dir: string, base: string): number {\n try {\n return parseInt(git(dir, [\"rev-list\", \"--count\", `${base}..HEAD`]), 10) || 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * Record the worktree result in the status file: branch and commit count on\n * success, the worktree cleaned up on failure. Returns the updated status.\n */\nexport function recordWorktree(\n logDir: string,\n status: WorkerStatus,\n info: WorktreeInfo,\n ok: boolean\n): WorkerStatus {\n const s = { ...status };\n if (ok) {\n s.branch = info.branch;\n s.commits = commitsSince(info.dir, info.base);\n s.worktreeDir = info.dir;\n s.worktreeBase = info.base;\n } else {\n // a failed run leaves nothing to merge; the branch dies with the worktree\n removeWorktree(s.cwd, info.dir, true);\n try {\n git(s.cwd, [\"branch\", \"-D\", info.branch]);\n } catch {\n // already gone or never created\n }\n s.branch = null;\n s.worktreeDir = null;\n s.worktreeBase = null;\n s.commits = null;\n }\n saveStatus(logDir, s);\n return s;\n}\n\n/** Remove a worktree directory from git's books and the filesystem. */\nfunction removeWorktree(cwd: string, dir: string, force: boolean): void {\n try {\n git(cwd, [\"worktree\", \"remove\", ...(force ? [\"--force\"] : []), dir]);\n return;\n } catch {\n // fall through to the manual cleanup\n }\n if (existsSync(dir)) {\n try {\n rmSync(dir, { recursive: true, force: true });\n git(cwd, [\"worktree\", \"prune\"]);\n } catch {\n // best effort: a leftover directory is visible in the logDir\n }\n }\n}\n\n/**\n * Tracked changes (staged or not) plus untracked files — everything a\n * `git add -A` in `wtDir` would commit. The same two calls the old patch\n * carry used; name-only, not porcelain: a worktree-only change renders as\n * \" M path\" and the shared git() helper trims that leading space away.\n */\nexport function uncommittedPaths(wtDir: string): string[] {\n const tracked = git(wtDir, [\"diff\", \"--name-only\", \"HEAD\"]).split(\"\\n\").filter(Boolean);\n const untracked = git(wtDir, [\"ls-files\", \"--others\", \"--exclude-standard\"])\n .split(\"\\n\")\n .filter(Boolean);\n return [...tracked, ...untracked];\n}\n\n/**\n * Commit a worktree's uncommitted changes to its branch so the merge carries\n * them. `git merge` only moves committed work, so a worker that stopped\n * without committing would lose its edits to `worktree remove` — exactly\n * what happened live on 2026-09-17. Returns the salvaged paths, [] when the\n * worktree is clean. A failed commit throws with the worktree untouched:\n * its edits are still on disk, so nothing is lost.\n */\nexport function salvageUncommitted(wtDir: string, label: string): string[] {\n const paths = uncommittedPaths(wtDir);\n if (!paths.length) return [];\n try {\n git(wtDir, [\"add\", \"-A\"]);\n git(wtDir, [\"commit\", \"-m\", `salvaged: ${label}`]);\n } catch (e) {\n throw new Error(\n `cannot salvage the uncommitted changes in ${wtDir} — ${(e as Error).message}; ` +\n `nothing was lost: commit them there by hand, then re-run merge`\n );\n }\n return paths;\n}\n\n/**\n * The dirty paths of a checkout, parsed from `git status --porcelain -z`:\n * NUL-separated (a path with a newline in it cannot corrupt the parse),\n * rename entries contribute both sides, and any quoting is stripped.\n */\nfunction dirtyPaths(cwd: string): string[] {\n // raw execFileSync, not the shared git(): it trims stdout, which eats the\n // leading space of a worktree-only \" M path\" record and breaks the parse\n const raw = execFileSync(\"git\", [\"-C\", cwd, \"status\", \"--porcelain\", \"-z\"], {\n encoding: \"utf8\",\n timeout: 30_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const fields = raw.split(\"\\0\");\n const strip = (p: string) => (p.startsWith('\"') && p.endsWith('\"') ? p.slice(1, -1) : p);\n const out: string[] = [];\n for (let i = 0; i < fields.length; i++) {\n const f = fields[i];\n if (!f || f.length < 4 || f.charAt(2) !== \" \") continue; // not an \"XY path\" record\n out.push(strip(f.slice(3)));\n const xy = f.slice(0, 2);\n if (xy.includes(\"R\") || xy.includes(\"C\")) {\n const orig = fields[i + 1]; // rename/copy records carry the source path next\n if (orig && orig.charAt(2) !== \" \") {\n out.push(strip(orig));\n i += 1;\n }\n }\n }\n return out;\n}\n\n/**\n * Refuse the merge when the original checkout is dirty in paths the branch\n * touches: the merge would overwrite those edits or fail on them, either way\n * leaving a half-state. No merge is made, the worktree stays.\n */\nfunction assertNoDirtyOverlap(\n cwd: string,\n incoming: string[],\n id: string,\n branch: string\n): void {\n const dirty = new Set(dirtyPaths(cwd));\n const overlap = [...new Set(incoming)].filter((p) => dirty.has(p)).sort();\n if (!overlap.length) return;\n throw new Error(\n `worker ${id}: the checkout ${cwd} has uncommitted changes in paths ${branch} touches: ` +\n `${overlap.join(\", \")}. Commit or stash them in the checkout, then re-run: pai worker merge ${id}. ` +\n `No merge was made; the worktree was kept.`\n );\n}\n\n/** Never remove a worktree that still holds uncommitted changes. */\nexport function assertWorktreeClean(wtDir: string, id: string): void {\n const leftover = uncommittedPaths(wtDir);\n if (leftover.length) {\n throw new Error(\n `worker ${id}: the worktree ${wtDir} still holds uncommitted changes ` +\n `(${leftover.join(\", \")}) — it was NOT removed; commit or copy them by hand, then re-run merge`\n );\n }\n}\n\n/**\n * `pai worker merge <id>`: salvage whatever the worker left uncommitted onto\n * its branch, refuse when the original checkout is dirty in paths the branch\n * touches, merge with --no-ff (the merge commit names the worker), then\n * remove the worktree and delete the branch; the status gains `merged: true`.\n * A branch with nothing to merge is refused loudly — after salvage that\n * genuinely means it holds nothing, and reporting success there would\n * destroy the worktree for no gain.\n */\nexport function mergeWorker(logDir: string, id: string): string {\n const st = mustHaveBranch(logDir, id);\n if (st.merged) return `worker ${id}: branch ${st.branch} already merged`;\n // salvage first: only a commit can carry uncommitted work through the merge\n const salvaged = existsSync(st.worktreeDir!)\n ? salvageUncommitted(st.worktreeDir!, st.label || UNLABELED)\n : [];\n const incoming = parseInt(git(st.cwd, [\"rev-list\", \"--count\", `HEAD..${st.branch}`]), 10) || 0;\n if (incoming <= 0) {\n throw new Error(\n `worker ${id}: branch ${st.branch} has no commits to merge. ` +\n `The worktree ${st.worktreeDir} was NOT removed — uncommitted work there would be destroyed. ` +\n `Commit it yourself, or drop everything with: pai worker discard ${id}`\n );\n }\n const mergeBase = git(st.cwd, [\"merge-base\", \"HEAD\", st.branch!]);\n const incomingPaths = git(st.cwd, [\"diff\", \"--name-only\", mergeBase, st.branch!])\n .split(\"\\n\")\n .filter(Boolean);\n assertNoDirtyOverlap(st.cwd, incomingPaths, id, st.branch!);\n try {\n git(st.cwd, [\"merge\", \"--no-ff\", st.branch!, \"-m\", `merge worker ${id} (${st.label})`]);\n } catch (e) {\n throw new Error(\n `worker ${id}: git refused the merge of ${st.branch} — ${(e as Error).message}. ` +\n `The worktree ${st.worktreeDir} was kept: resolve the conflict, then re-run merge`\n );\n }\n if (existsSync(st.worktreeDir!)) assertWorktreeClean(st.worktreeDir!, id);\n removeWorktree(st.cwd, st.worktreeDir!, false);\n let branchGone = true;\n try {\n git(st.cwd, [\"branch\", \"-d\", st.branch!]);\n } catch {\n branchGone = false; // -d refuses anything not fully merged; keep the branch, say so\n }\n const s = { ...st, merged: true };\n saveStatus(logDir, s);\n const base = `merged ${st.branch} into ${st.cwd} (worktree removed${branchGone ? \", branch deleted\" : \"; branch kept: git refused -d\"})`;\n return salvaged.length\n ? `${base}; salvaged ${salvaged.length} uncommitted change(s): ${salvaged.join(\", \")}`\n : base;\n}\n\n/** `pai worker discard <id>`: drop worktree and branch, keep nothing. */\nexport function discardWorker(logDir: string, id: string): string {\n const st = mustHaveBranch(logDir, id);\n removeWorktree(st.cwd, st.worktreeDir!, true);\n let branchGone = false;\n try {\n git(st.cwd, [\"branch\", \"-D\", st.branch!]);\n branchGone = true;\n } catch {\n branchGone = false;\n }\n const s = { ...st, branch: null, worktreeDir: null, worktreeBase: null, commits: null, merged: false };\n saveStatus(logDir, s);\n return `discarded worker ${id}: worktree removed${branchGone ? `, branch ${st.branch} deleted` : \"\"}`;\n}\n\nfunction mustHaveBranch(logDir: string, id: string): WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">> {\n const st = loadStatus(logDir, id);\n if (!st) throw new Error(`no worker named \"${id}\"`);\n if (!st.branch || !st.worktreeDir) {\n throw new Error(\n `worker ${id} has no worktree branch to ${st.branch ? \"clean up\" : \"merge\"} — ` +\n `it ran in place or its branch was already handled`\n );\n }\n return st as WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">>;\n}\n\n/**\n * The paragraph a worktree run's system prompt gains: it may commit on its\n * own branch (the no-commit rule holds for the main branch only), it must not\n * merge or push itself, and the parent or operator merges.\n */\nexport function worktreeSystemPrompt(id: string, branch: string, dir: string): string {\n return [\n \"You are running in your own git worktree:\",\n ` ${dir} on branch ${branch} (worker id ${id}).`,\n \"Commit your work on that branch as you go (git add / git commit) — committing here is expected;\",\n \"the no-commit rule applies to the main branch only, and this is not it.\",\n \"Do not merge, rebase or push; the operator merges your branch back with `pai worker merge`.\",\n \"Use ONLY relative paths inside the worktree, never absolute worktree paths — absolute paths break after merge and leak machine layout.\",\n ].join(\"\\n\");\n}\n","/**\n * report.ts — the worker contract: terse, structured final reports.\n *\n * Headless workers run with an appended system prompt that fixes their output\n * shape: act, verify, then finish with ONE final message that is a JSON object\n * and nothing else. The runner parses it out of the result; the viewer renders\n * it as a compact block instead of a wall of text.\n */\n\nimport { relative } from \"node:path\";\nimport { shortText } from \"./args.js\";\nimport type { Paint } from \"./render.js\";\n\n/** The marker the runner puts on operator messages the worker must answer. */\nexport const OPERATOR_MARK = \"[operator]\";\n\n/** Appended to the caller's system prompt on every headless run (2h). */\nexport const WORKER_CONTRACT_PROMPT = [\n \"You are a headless implementation worker, run non-interactively by an orchestrating session.\",\n \"No narration, no timestamps, no greetings, no summaries of what you read; act, verify, then stop.\",\n \"Your ONE final message is a JSON object and nothing else — no prose before or after, no code fence:\",\n '{\"changed\":[{\"path\":\"…\",\"summary\":\"…\"}],\"commands\":[\"…\"],\"checks\":[{\"name\":\"…\",\"ok\":true,\"detail\":\"…\"}],\"open\":[\"…\"],\"notes\":\"one line\"}',\n \"changed: files you touched (path + one-line summary). commands: the commands that verify the work.\",\n \"checks: each with ok true/false and the evidence in detail. open: what you could not finish, if anything.\",\n \"notes: one line, the headline a reviewer reads first.\",\n \"Never write shell heredocs — write the script to a file with the Write tool, then run it\",\n \"(heredocs fail silently or get swallowed).\",\n \"Never inline code one-liners (python3 -c, node -e, ruby -e and friends) — the permission layer\",\n \"denies them (\\\"ztk: command denied by permission rules\\\"); write the script to a file with the\",\n \"Write tool and run it (python3 script.py), or use jq for JSON inspection.\",\n \"\",\n `Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,\n \"run (the prompt of a resumed run is the operator's too). Answer it FIRST, in one or two plain lines —\",\n \"a question gets its answer, an instruction gets one line naming what will change — then continue the\",\n \"task you were on.\",\n \"\",\n \"Sub-workers: you may start your own workers with `pai worker run --class <class> -p '<prompt>'`\",\n \"(Bash tool). Your children run on their own provider and report back to you automatically when\",\n \"they finish. Send a handoff UP instead of doing work yourself when a cheaper provider would\",\n \"suffice, the task is out of your scope, or a decision is needed:\",\n \"`pai worker handoff '{\\\"kind\\\":\\\"proposal\\\",\\\"text\\\":\\\"…\\\",\\\"data\\\":{…}}'` (kinds: proposal,\",\n \"question, blocker; results are sent for you when you finish). Sibling workers are not a\",\n \"coordination path — there is no sideways channel; everything goes up to your parent.\",\n].join(\"\\n\");\n\nexport interface WorkerReport {\n changed?: Array<{ path?: string; summary?: string }>;\n commands?: string[];\n checks?: Array<{ name?: string; ok?: boolean; detail?: string }>;\n open?: string[];\n notes?: string;\n}\n\n/**\n * Extract the JSON report from a worker's final message. Accepts a bare\n * object, a ```json fenced block, or an object embedded in surrounding text.\n * Only objects that look like the contract count (at least one of changed /\n * checks / notes) — any other JSON falls through to null so the raw text is\n * kept as-is.\n */\nexport function parseWorkerReport(text: string): WorkerReport | null {\n const trimmed = (text ?? \"\").trim();\n if (!trimmed) return null;\n const candidates: string[] = [];\n const fence = trimmed.match(/```(?:json)?\\s*(\\{[\\s\\S]*?\\})\\s*```/);\n if (fence) candidates.push(fence[1]);\n if (trimmed.startsWith(\"{\")) {\n // a bare object may still carry trailing punctuation/newlines\n candidates.push(trimmed);\n }\n // object embedded in prose: first \"{\" to the matching last \"}\"\n const first = trimmed.indexOf(\"{\");\n const last = trimmed.lastIndexOf(\"}\");\n if (first >= 0 && last > first) candidates.push(trimmed.slice(first, last + 1));\n for (const cand of candidates) {\n try {\n const v = JSON.parse(cand) as unknown;\n if (typeof v === \"object\" && v !== null && !Array.isArray(v) && looksLikeReport(v)) {\n return v as WorkerReport;\n }\n } catch {\n // try the next candidate\n }\n }\n return null;\n}\n\n/** The contract's fingerprint — guards against unrelated JSON in a reply. */\nfunction looksLikeReport(v: object): boolean {\n const o = v as WorkerReport;\n return Array.isArray(o.changed) || Array.isArray(o.checks) || typeof o.notes === \"string\";\n}\n\n/**\n * Render a parsed report as the compact block the viewer shows: changed paths\n * with summaries, checks with ✓/✗, open items, notes. Empty report → [].\n */\nexport function renderReport(c: Paint, prefix: string, r: WorkerReport, cwd = \"\"): string[] {\n const out: string[] = [];\n const has = (xs: unknown[] | undefined) => (xs ?? []).length > 0;\n if (has(r.changed)) {\n out.push(`${prefix}${c(\"bold\", \"changed\")}`);\n for (const ch of r.changed!) {\n out.push(`${prefix} ${relShort(ch.path ?? \"?\", cwd)} — ${shortText(ch.summary ?? \"\", 90)}`);\n }\n }\n if (has(r.checks)) {\n out.push(`${prefix}${c(\"bold\", \"checks\")}`);\n for (const ck of r.checks!) {\n const mark = ck.ok === false ? c(\"red\", \"✗\") : c(\"green\", \"✓\");\n const detail = ck.detail ? c(\"dim\", ` · ${shortText(ck.detail, 80)}`) : \"\";\n out.push(`${prefix} ${mark} ${ck.name ?? \"?\"}${detail}`);\n }\n }\n if (has(r.commands)) {\n out.push(`${prefix}${c(\"bold\", \"commands\")}`);\n for (const cmd of r.commands!) out.push(`${prefix} ${c(\"dim\", shortText(cmd, 100))}`);\n }\n if (has(r.open)) {\n out.push(`${prefix}${c(\"bold\", \"open\")}`);\n for (const o of r.open!) out.push(`${prefix} ${c(\"yellow\", shortText(o, 100))}`);\n }\n if (r.notes) out.push(`${prefix}${c(\"bold\", \"notes\")} ${shortText(r.notes, 120)}`);\n return out;\n}\n\n/** repo-relative display of a changed path when it lies under cwd. */\nfunction relShort(p: string, cwd: string): string {\n if (!cwd) return p;\n const r = relative(cwd, p);\n return r && !r.startsWith(\"..\") ? r : p;\n}\n","/**\n * mcp.ts — the MCP allowlist for headless workers.\n *\n * Headless workers start with NO MCP servers: every server definition is a\n * prompt-time tool inventory the model pays to know, and a worker that only\n * reads and edits files needs none of it. A run may opt in with `--mcp\n * name[,name…]`, a role carrying `\"mcp\": [...]`, or implicitly by naming\n * `mcp__server__tool` in --allowedTools (a grant without its server loaded is\n * a dead letter). Names may be single servers from ~/.claude.json's\n * `mcpServers` or `workers.mcpSets` set names, which expand to their member\n * list. The filtered config lands in\n * `<logDir>/<id>.mcp.json` and is passed with `--strict-mcp-config\n * --mcp-config` so exactly those servers load. MCP servers are chosen at\n * launch only — a mid-run `say` cannot add any.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { WorkersConfigError, type WorkersConfig } from \"./config.js\";\n\n/** ~/.claude.json — the user's MCP server definitions (top-level mcpServers). */\nexport const CLAUDE_JSON = join(homedir(), \".claude.json\");\n\nexport function readMcpServers(claudeJson = CLAUDE_JSON): Record<string, unknown> {\n try {\n if (!existsSync(claudeJson)) return {};\n const parsed = JSON.parse(readFileSync(claudeJson, \"utf8\")) as Record<string, unknown>;\n const servers = parsed.mcpServers;\n if (typeof servers !== \"object\" || servers === null || Array.isArray(servers)) return {};\n return servers as Record<string, unknown>;\n } catch {\n // a damaged ~/.claude.json must not take workers down with it\n return {};\n }\n}\n\n/**\n * Split a `--mcp a,b,c` flag value (also accepts repeated flags already split\n * by the caller) and expand set names from workers.mcpSets.\n */\nexport function expandMcpNames(\n names: string[],\n config: Pick<WorkersConfig, \"mcpSets\">,\n claudeJson = CLAUDE_JSON\n): string[] {\n const available = readMcpServers(claudeJson);\n const out: string[] = [];\n for (const raw of names) {\n for (const name of raw.split(\",\").map((s) => s.trim()).filter(Boolean)) {\n if (name in config.mcpSets) {\n for (const member of config.mcpSets[name]) {\n if (!out.includes(member)) out.push(member);\n }\n continue;\n }\n if (!(name in available)) {\n const sets = Object.keys(config.mcpSets);\n throw new WorkersConfigError(\n `unknown MCP server \"${name}\". Available servers: ` +\n `${Object.keys(available).join(\", \") || \"(none in ~/.claude.json)\"}` +\n `${sets.length ? `; sets: ${sets.join(\", \")}` : \"\"}`\n );\n }\n if (!out.includes(name)) out.push(name);\n }\n }\n return out;\n}\n\n/**\n * Derive server names from tool grants: every `mcp__<server>__<tool>` (or bare\n * `mcp__<server>`, or `mcp__<server>__*`) in an --allowedTools list names a\n * server the run expects to be loaded. A grant is a dead letter unless its\n * server is in the filtered config, so the runner treats these as implicit\n * --mcp names — and only these; non-mcp grants load nothing.\n */\nexport function mcpServersFromToolGrants(tools: string[]): string[] {\n const out: string[] = [];\n for (const entry of tools) {\n for (const name of entry.split(\",\").map((s) => s.trim()).filter(Boolean)) {\n if (!name.startsWith(\"mcp__\")) continue;\n const server = name.slice(\"mcp__\".length).split(\"__\")[0];\n if (server && !out.includes(server)) out.push(server);\n }\n }\n return out;\n}\n\n/** Path of a run's filtered MCP config. */\nexport function runMcpConfigPath(logDir: string, id: string): string {\n return join(logDir, `${id}.mcp.json`);\n}\n\n/**\n * Write a config containing only `names` (already expanded) and return its\n * path. Unknown names fail fast with the available list.\n */\nexport function writeMcpConfig(\n logDir: string,\n id: string,\n names: string[],\n claudeJson = CLAUDE_JSON\n): string {\n const available = readMcpServers(claudeJson);\n const unknown = names.filter((n) => !(n in available));\n if (unknown.length) {\n throw new WorkersConfigError(\n `unknown MCP server(s): ${unknown.join(\", \")}. Available: ` +\n `${Object.keys(available).join(\", \") || \"(none in ~/.claude.json)\"}`\n );\n }\n const servers: Record<string, unknown> = {};\n for (const n of names) servers[n] = available[n];\n const path = runMcpConfigPath(logDir, id);\n if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });\n writeFileSync(path, JSON.stringify({ mcpServers: servers }, null, 2) + \"\\n\", \"utf8\");\n return path;\n}\n\n/** `pai worker mcp list`: the servers and sets a run could name. */\nexport function describeMcp(config: Pick<WorkersConfig, \"mcpSets\">, claudeJson = CLAUDE_JSON): string[] {\n const servers = Object.keys(readMcpServers(claudeJson));\n const lines: string[] = [];\n if (servers.length) {\n lines.push(`servers (~/.claude.json):`);\n for (const s of servers) lines.push(` ${s}`);\n } else {\n lines.push(`no MCP servers defined in ~/.claude.json`);\n }\n const sets = Object.entries(config.mcpSets);\n if (sets.length) {\n lines.push(`sets (workers.mcpSets):`);\n for (const [name, members] of sets) lines.push(` ${name} = ${members.join(\", \")}`);\n }\n lines.push(`usage: pai worker run --mcp <name>[,<name>…] -p '<task>'`);\n return lines;\n}\n","/**\n * codex.ts — the \"codex\" runner engine.\n *\n * A ChatGPT plan gives no API key, only Codex CLI access, so a provider may\n * set `engine: \"codex\"`: `run` then shells out to `codex exec --json <prompt>`\n * (non-interactive) instead of Claude Code. The JSONL event stream is parsed\n * into the same status-file fields (turns, tools, last) and ledger lines as a\n * Claude run, and is normalised into claude-code-shaped events in the\n * worker's .jsonl so follow/replay/the pane render it unchanged.\n *\n * Implemented against the documented `codex exec --json` interface\n * (thread.started / item.completed / turn.completed / turn.failed lines);\n * verify against the installed CLI when one is present.\n * `--allowedTools` has no Codex equivalent and is dropped (ledgered).\n */\n\nimport { spawn, execFileSync } from \"node:child_process\";\nimport { existsSync, readFileSync as readKey } from \"node:fs\";\nimport { providerKeyPath, type WorkerProvider } from \"./config.js\";\n\n/** Build the codex exec argument vector (without the binary itself). */\nexport function buildCodexArgs(prompt: string, model: string | undefined): string[] {\n return [\n \"exec\",\n \"--json\",\n \"--skip-git-repo-check\",\n ...(model ? [\"-m\", model] : []),\n \"--\",\n prompt,\n ];\n}\n\n/** Env for a codex run: caller's env, Anthropic vars stripped, key applied. */\nexport function buildCodexEnv(provider: WorkerProvider): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = { ...process.env };\n delete env.ANTHROPIC_API_KEY;\n delete env.ANTHROPIC_BASE_URL;\n delete env.ANTHROPIC_AUTH_TOKEN;\n delete env.ANTHROPIC_DEFAULT_HAIKU_MODEL;\n delete env.ANTHROPIC_DEFAULT_SONNET_MODEL;\n delete env.ANTHROPIC_DEFAULT_OPUS_MODEL;\n // API-key providers: OpenAI env from keyFile/baseUrl; ChatGPT-login codex\n // (no keyFile) keeps its own auth from ~/.codex.\n const keyPath = providerKeyPath(provider);\n if (keyPath && existsSync(keyPath)) {\n env.OPENAI_API_KEY = readKey(keyPath, \"utf8\").trim();\n }\n if (provider.upstreamUrl) env.OPENAI_BASE_URL = provider.upstreamUrl;\n for (const [k, v] of Object.entries(provider.env)) env[k] = v;\n env.PAI_WORKER = \"1\";\n return env;\n}\n\n/** Codex \"not applicable\" flags the runner drops with a ledger note. */\nexport function codexDroppedFlags(argv: string[]): string[] {\n const dropped: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a === \"--allowedTools\" || a === \"--disallowedTools\" || a === \"--mcp-config\" || a === \"--mcp\") {\n dropped.push(a);\n if (i + 1 < argv.length && !argv[i + 1].startsWith(\"-\")) i++; // and its value\n } else if (a.startsWith(\"--allowedTools=\") || a.startsWith(\"--mcp-config=\")) {\n dropped.push(a.split(\"=\")[0]);\n }\n }\n return dropped;\n}\n\n// ---------------------------------------------------------------------------\n// Event stream parsing (pure — tested with recorded JSONL lines)\n// ---------------------------------------------------------------------------\n\n/** One normalised claude-code-shaped event + its effect on the status. */\nexport interface CodexEventResult {\n events: Array<Record<string, unknown>>;\n turns: number;\n tools: number;\n last: string | null;\n isError: boolean;\n contextTokens: number | null;\n finalText: string | null;\n threadId: string | null;\n}\n\nexport const emptyCodexResult = (): CodexEventResult => ({\n events: [],\n turns: 0,\n tools: 0,\n last: null,\n isError: false,\n contextTokens: null,\n finalText: null,\n threadId: null,\n});\n\n/**\n * Fold one parsed `codex exec --json` line into the running result: appends\n * normalised transcript events and updates the counters in place.\n */\nexport function foldCodexLine(line: unknown, r: CodexEventResult): void {\n if (typeof line !== \"object\" || line === null) return;\n const e = line as Record<string, unknown>;\n const type = typeof e.type === \"string\" ? e.type : \"\";\n\n if (type === \"thread.started\" && typeof e.thread_id === \"string\") {\n r.threadId = e.thread_id;\n return;\n }\n if (type === \"item.completed\" && typeof e.item === \"object\" && e.item !== null) {\n const item = e.item as Record<string, unknown>;\n const kind = typeof item.type === \"string\" ? item.type : \"\";\n if (kind === \"agent_message\" && typeof item.text === \"string\") {\n r.turns += 1;\n r.last = `says: ${item.text.slice(0, 70)}`;\n r.finalText = item.text;\n r.events.push({\n type: \"assistant\",\n message: { content: [{ type: \"text\", text: item.text }] },\n });\n } else if (kind === \"command_execution\") {\n r.tools += 1;\n const cmd = typeof item.command === \"string\" ? item.command : \"?\";\n const rc = typeof item.exit_code === \"number\" ? item.exit_code : 0;\n r.last = `Bash: ${cmd.slice(0, 70)}`;\n r.events.push({\n type: \"assistant\",\n message: { content: [{ type: \"tool_use\", name: \"Bash\", input: { command: cmd } }] },\n });\n r.events.push({\n type: \"user\",\n message: {\n content: [\n {\n type: \"tool_result\",\n tool_use_id: \"\",\n is_error: rc !== 0,\n content: item.aggregated_output ?? \"\",\n },\n ],\n },\n });\n } else if (kind === \"file_change\") {\n r.tools += 1;\n const changes = Array.isArray(item.changes) ? item.changes : [];\n const files = changes\n .map((c) => (typeof c === \"object\" && c !== null && typeof (c as { path?: unknown }).path === \"string\" ? (c as { path: string }).path : \"?\"))\n .join(\", \");\n r.last = `files: ${files.slice(0, 70)}`;\n r.events.push({\n type: \"assistant\",\n message: { content: [{ type: \"tool_use\", name: \"Write\", input: { file_path: files } }] },\n });\n } else if (kind === \"mcp_tool_call\") {\n r.tools += 1;\n r.last = `mcp: ${String(item.tool ?? \"?\")}`;\n }\n return;\n }\n if (type === \"turn.completed\") {\n const usage = (typeof e.usage === \"object\" && e.usage !== null ? e.usage : {}) as {\n input_tokens?: number;\n output_tokens?: number;\n cached_input_tokens?: number;\n };\n const tokens =\n (usage.input_tokens ?? 0) + (usage.cached_input_tokens ?? 0) + (usage.output_tokens ?? 0);\n if (tokens > 0) r.contextTokens = tokens;\n return;\n }\n if (type === \"turn.failed\" || type === \"error\") {\n r.isError = true;\n const err = typeof e.error === \"object\" && e.error !== null ? e.error : {};\n r.last = String((err as { message?: unknown }).message ?? e.message ?? \"codex turn failed\");\n }\n}\n\n/** Parse one JSONL line; null for blanks and non-JSON noise. */\nexport function parseCodexLine(line: string): unknown | null {\n const t = line.trim();\n if (!t.startsWith(\"{\")) return null;\n try {\n return JSON.parse(t) as unknown;\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// The engine probe used by `providers test`\n// ---------------------------------------------------------------------------\n\n/** True when the Codex CLI is on PATH (test reports \"not installed\" else). */\nexport function codexInstalled(): boolean {\n try {\n execFileSync(\"codex\", [\"--version\"], { timeout: 5000, stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Spawn helper shared by run and test; caller wires stdout. */\nexport function spawnCodex(args: string[], env: NodeJS.ProcessEnv, cwd: string) {\n return spawn(\"codex\", args, { env, cwd, stdio: [\"ignore\", \"pipe\", \"inherit\"] });\n}\n","/**\n * run.ts — the worker runner (port of the glm / glm-run pair, provider-neutral).\n *\n * One claude-code process per call, pointed at the chosen provider:\n *\n * - env: ANTHROPIC_BASE_URL/AUTH_TOKEN from the provider (token from its\n * key file, never from the environment), the three DEFAULT_*_MODEL vars,\n * the provider's extra env, nonessential traffic off, and ANTHROPIC_API_KEY\n * stripped so nothing can fall back to Anthropic billing. Headless runs\n * also drop the spawner's session identity (messaging socket, session id,\n * nesting markers) — inherited, a worktree child starts tool-blind.\n * OpenAI-protocol providers point at the PAI proxy instead (started on\n * demand, the provider name in the URL path); codex-engine providers run\n * the Codex CLI.\n * - headless (-p): strict empty MCP config unless the caller brings one or\n * names servers via --mcp / a role (then a filtered <id>.mcp.json), the\n * core tool grants the caller did not bring (a headless run cannot\n * approve a permission-gated tool mid-flight — with no grant at all,\n * claude drops the file/shell tools entirely and the worker is\n * tool-blind), PAI_WORKER=1 so PAI's per-session hooks leave it alone,\n * the worker\n * contract appended to the system prompt, `--input-format stream-json`\n * with the prompt as the first stdin user message (the operator socket\n * can add more mid-run), stream-json mirroring (every line stamped `_ts`)\n * into <logDir>/<id>.jsonl, a live <id>.status for ps/follow/status line\n * (context meter included), ledger lines, result printed in the caller's\n * --output-format (json adds the parsed `report`), and the follow pane\n * (unless --no-pane).\n * - interactive: no MCP restriction, no pane, ENABLE_TOOL_SEARCH=true.\n *\n * Auto-routed runs that die of a quota error before the first tool call are\n * restarted on the next provider in routing order (WORKER-REROUTE ledger line).\n */\n\nimport { spawn } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\nimport {\n existsSync,\n mkdirSync,\n openSync,\n writeFileSync,\n closeSync,\n writeSync,\n} from \"node:fs\";\nimport { parseRunnerArgs, shortText, stripPromptValues } from \"./args.js\";\nimport {\n assertProviderRunnable,\n classModelCapability,\n isModelCapability,\n readWorkersSection,\n resolveModelCapability,\n type WorkerProvider,\n} from \"./config.js\";\nimport { buildRunEnv } from \"./run-env.js\";\n\nexport { buildRunEnv } from \"./run-env.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport {\n ensureNoMcpConfig,\n eventsPath,\n ledgerPath,\n noMcpConfigPath,\n workersLogDir,\n} from \"./paths.js\";\nimport {\n type WorkerStatus,\n newWorkerId,\n saveStatus,\n describeTool,\n nowStamp,\n UNLABELED,\n} from \"./status.js\";\nimport { resolveSession, resolveSpawnerSession } from \"./scope.js\";\nimport { isQuotaFailure, nextAutoProvider, resolveTarget, setCooldown } from \"./routing.js\";\nimport { openPaneForWorker } from \"./pane.js\";\nimport { assertChildAllowed, isWorkerId, launchParent } from \"./tree.js\";\nimport { deliverHandoff, isHandoffMessage } from \"./handoff.js\";\nimport {\n addWorktree,\n recordWorktree,\n worktreeSystemPrompt,\n worktreeWanted,\n type WorktreeInfo,\n} from \"./worktree.js\";\nimport { OPERATOR_MARK, WORKER_CONTRACT_PROMPT, parseWorkerReport, type WorkerReport } from \"./report.js\";\nimport { expandMcpNames, mcpServersFromToolGrants, writeMcpConfig } from \"./mcp.js\";\nimport { createOperatorServer } from \"./operator.js\";\nimport { DEFAULT_PROXY_PORT, ensureProxyRunning } from \"./proxy/server.js\";\nimport {\n buildCodexArgs,\n buildCodexEnv,\n codexDroppedFlags,\n codexInstalled,\n emptyCodexResult,\n foldCodexLine,\n parseCodexLine,\n} from \"./codex.js\";\n\nexport interface RunOptions {\n providerFlag?: string;\n /** --class value (the old --role): a task class from workers.classes. */\n className?: string;\n modelFlag?: string;\n label?: string;\n noPane?: boolean;\n /** --mcp value: server/set names, comma-separated. */\n mcpFlag?: string;\n /** Everything after `--` (the claude args). */\n claudeArgs: string[];\n /** Working directory for the run (default: this process's cwd). */\n cwd?: string;\n /** Chain stage bookkeeping: the chain id this stage belongs to. */\n parent?: string;\n /** Chain stage bookkeeping: the class name of this stage. */\n stage?: string;\n /** Suppress result printing (MCP worker_run: its stdout is the RPC channel). */\n quiet?: boolean;\n /** Internal: notified with the worker id once it exists (resume uses it). */\n onWorkerStart?: (wid: string) => void;\n /** Internal: preset worker id (the planner mints its id before phase 1). */\n id?: string;\n /** --worktree/--no-worktree; undefined lets the class default decide. */\n worktreeFlag?: boolean;\n /** Internal: suppress recursion depth on reroute. */\n _reroutes?: number;\n /** Internal: this run is the planner's phase-1 worker, not a new orchestration. */\n _planner?: boolean;\n}\n\n/** The strict empty MCP config for headless workers, written on demand. */\nexport { ensureNoMcpConfig } from \"./paths.js\";\n\n/** A stream-json user message for the child's stdin. */\nexport function stdinUserMessage(text: string): string {\n return JSON.stringify({ type: \"user\", message: { role: \"user\", content: text } });\n}\n\n/** An operator line as the worker sees it: carrying the contract's marker. */\nexport function operatorUserText(text: string): string {\n return `${OPERATOR_MARK} ${text}`;\n}\n\n/**\n * ISO stamp with seconds, attached to every mirrored event (2g): local time\n * with its offset (`2026-09-17T14:19:23+02:00`), so the viewer can render the\n * wall clock the operator lives in. `offMin` is east-positive minutes —\n * injectable so tests do not depend on the machine's zone.\n */\nexport function isoStamp(d = new Date(), offMin = -d.getTimezoneOffset()): string {\n const t = new Date(d.getTime() + offMin * 60_000);\n const sign = offMin < 0 ? \"-\" : \"+\";\n const abs = Math.abs(offMin);\n const hh = String(Math.floor(abs / 60)).padStart(2, \"0\");\n const mm = String(abs % 60).padStart(2, \"0\");\n return `${t.toISOString().slice(0, 19)}${sign}${hh}:${mm}`;\n}\n\nexport interface UsageBlock {\n input_tokens?: number;\n output_tokens?: number;\n cache_read_input_tokens?: number;\n cache_creation_input_tokens?: number;\n}\n\n// a type alias (not an interface): it must stay assignable to\n// Record<string, unknown> when written into the event transcript\nexport type StreamEvent = {\n type?: string;\n subtype?: string;\n session_id?: string;\n model?: string;\n cwd?: string;\n context_window?: number;\n model_info?: { context_window?: number } | null;\n message?: {\n content?: Array<{ type?: string; text?: string; name?: string; id?: string; input?: unknown }>;\n usage?: UsageBlock;\n };\n usage?: UsageBlock;\n result?: string;\n is_error?: boolean;\n is_compact?: boolean;\n num_turns?: number;\n duration_ms?: number;\n /** API time of the final turn — the TTFT proxy on zeroed per-turn usage. */\n duration_api_ms?: number;\n};\n\n/** Context tokens of an assistant/result usage block (input+cache+output). */\nexport function usageContextTokens(u: UsageBlock | undefined): number | null {\n if (!u) return null;\n const t =\n (u.input_tokens ?? 0) +\n (u.cache_read_input_tokens ?? 0) +\n (u.cache_creation_input_tokens ?? 0) +\n (u.output_tokens ?? 0);\n return t > 0 ? t : null;\n}\n\n/**\n * Context never shrinks mid-segment, so the status keeps the high-water\n * mark of the usage it has seen: a smaller later reading (short reply,\n * sidechain answer) must not drag the meter down. Compaction is the one\n * legitimate drop — see `resetContextTokensOnCompact`.\n */\nexport function bumpContextTokens(status: Pick<WorkerStatus, \"contextTokens\">, tokens: number | null): void {\n if (tokens === null || tokens <= 0) return;\n status.contextTokens = Math.max(status.contextTokens ?? 0, tokens);\n}\n\n/**\n * A compact boundary (`system`/`compact_boundary`, the shape Claude Code\n * writes with `compactMetadata.preTokens`; `compact` kept as the older\n * spelling) legitimately restarts the context at a lower size: the floor\n * drops to the event's own usage — usually none — so the next usage\n * reading re-seeds the meter at the fresh, smaller context.\n */\nexport function resetContextTokensOnCompact(status: Pick<WorkerStatus, \"contextTokens\">, e: StreamEvent): void {\n status.contextTokens = usageContextTokens(e.usage) ?? null;\n}\n\n/** A compact boundary in a worker's stream, in either event spelling. */\nexport function isCompactBoundary(e: StreamEvent): boolean {\n return e.type === \"system\" && (e.subtype === \"compact_boundary\" || e.subtype === \"compact\");\n}\n\n/** Context window announced by the init event, when the endpoint sends one. */\nexport function initContextWindow(e: StreamEvent): number | null {\n if (typeof e.context_window === \"number\" && e.context_window > 0) return e.context_window;\n if (e.model_info && typeof e.model_info.context_window === \"number\" && e.model_info.context_window > 0) {\n return e.model_info.context_window;\n }\n // the `[1m]` model variant announces a 1M-token window by suffix\n if (/\\[1m\\]$/.test((e.model ?? \"\").trim())) return 1_000_000;\n return null;\n}\n\n/**\n * Run one worker. Returns the process exit code to pass through.\n * Throws WorkersConfigError-shaped Errors for configuration problems.\n */\nexport async function runWorker(opts: RunOptions): Promise<number> {\n const { raw: _raw, workers: config } = readWorkersSection();\n void _raw;\n if (!config.enabled) {\n throw new Error(\n `workers are off. Turn them on with: pai worker on` +\n `\\n(then the Agent-tool hook stops denying Anthropic subagents only when you do)`\n );\n }\n const logDir = workersLogDir(config);\n mkdirSync(logDir, { recursive: true });\n\n // class plan is not one worker but the planner orchestration (planner.ts)\n if (opts.className === \"plan\" && !opts._planner) {\n const { runPlanner } = await import(\"./planner.js\");\n return runPlanner(opts);\n }\n\n // Sub-worker bookkeeping: an explicit parent (chain stage, planner child)\n // wins, else the worker this process runs inside (PAI_WORKER_ID). Both\n // caps from workers.tree apply to parents that are workers themselves.\n const parent = launchParent(opts.parent);\n if (parent) assertChildAllowed(logDir, parent, config.tree);\n\n const target = resolveTarget(config, logDir, {\n flagProvider: opts.providerFlag,\n className: opts.className,\n });\n assertProviderRunnable(target.providerName, target.provider);\n\n const parsed = parseRunnerArgs(opts.claudeArgs);\n const label =\n opts.label ??\n shortText(parsed.prompt ?? UNLABELED, 70);\n\n // model from the class target's alias (\"glm/fast\"), else the capability the\n // class implies (image class → image model, everything else → default)\n const alias = target.modelAlias;\n const capability =\n alias && isModelCapability(alias) ? alias : classModelCapability(opts.className);\n const model = opts.modelFlag ?? resolveModelCapability(target.provider, capability);\n\n try {\n if (target.provider.engine === \"codex\") {\n return await executeCodexRun({\n config,\n logDir,\n target,\n model,\n label,\n parsed,\n claudeArgs: opts.claudeArgs,\n noPane: opts.noPane ?? false,\n cwd: opts.cwd,\n parent: parent ?? undefined,\n stage: opts.stage,\n quiet: opts.quiet,\n onWorkerStart: opts.onWorkerStart,\n id: opts.id,\n worktreeFlag: opts.worktreeFlag,\n className: opts.className,\n });\n }\n return await executeRun({\n config,\n logDir,\n target,\n model,\n label,\n parsed,\n claudeArgs: opts.claudeArgs,\n noPane: opts.noPane ?? false,\n mcpFlag: opts.mcpFlag,\n cwd: opts.cwd,\n parent: parent ?? undefined,\n stage: opts.stage,\n quiet: opts.quiet,\n onWorkerStart: opts.onWorkerStart,\n id: opts.id,\n worktreeFlag: opts.worktreeFlag,\n className: opts.className,\n reroutes: opts._reroutes ?? 0,\n });\n } catch (e) {\n if (e instanceof Error && e.message.startsWith(\"key file\")) {\n throw new Error(\n `provider \"${target.providerName}\": ${e.message}` +\n `\\nPut the token in that file (chmod 600) or point keyFile elsewhere.`\n );\n }\n throw e;\n }\n}\n\n/**\n * The core tools a headless run grants when the caller brings none of its\n * own. Permission-gated tools (Bash, Read, Write, …) are never offered to a\n * headless session that has no allow rule for them — the child starts with\n * only the tools that never ask (tool search, web, cron, tasks) and cannot\n * touch a file or shell (2026-09-18). A caller's own --allowedTools is a\n * deliberate restriction and passes through untouched.\n */\nconst DEFAULT_WORKER_TOOLS = \"Read,Edit,Write,Bash,Grep,Glob\";\n\n/** The --allowedTools args a headless run needs; [] when the caller granted. */\nexport function headlessToolGrants(allowedTools: string[]): string[] {\n return allowedTools.length ? [] : [\"--allowedTools\", DEFAULT_WORKER_TOOLS];\n}\n\ninterface ExecuteArgs {\n config: ReturnType<typeof readWorkersSection>[\"workers\"];\n logDir: string;\n target: ReturnType<typeof resolveTarget>;\n model: string;\n label: string;\n parsed: ReturnType<typeof parseRunnerArgs>;\n claudeArgs: string[];\n noPane: boolean;\n mcpFlag?: string;\n cwd?: string;\n parent?: string;\n stage?: string;\n quiet?: boolean;\n onWorkerStart?: (wid: string) => void;\n id?: string;\n worktreeFlag?: boolean;\n className?: string;\n reroutes: number;\n}\n\nasync function executeRun(a: ExecuteArgs): Promise<number> {\n const { config, logDir, target, model, label, parsed, noPane } = a;\n const headless = parsed.headless;\n\n // openai-protocol providers run through the local proxy (started on demand)\n let proxyUrl: string | undefined;\n if (target.provider.protocol === \"openai\") {\n const base = await ensureProxyRunning(DEFAULT_PROXY_PORT, logDir);\n proxyUrl = `${base}/${target.providerName}`;\n }\n const env = buildRunEnv(target.provider, headless, proxyUrl);\n\n const wid = a.id ?? newWorkerId();\n const cwd = a.cwd ?? process.cwd();\n const term = process.env.ITERM_SESSION_ID ?? \"\";\n const session = resolveSession(term);\n // orchestrator Bash children have no terminal identity (see scope.ts) — the\n // session map supplies the claude session they were spawned by instead\n const spawnerSession = resolveSpawnerSession(logDir, cwd);\n\n // One worktree per writing run (implement/complex/plan, a git cwd, a prompt\n // that is not read-only; --worktree/--no-worktree override). A git refusal\n // degrades to an in-place run — the worker itself must still run.\n let worktree: WorktreeInfo | null = null;\n if (headless && worktreeWanted(a.worktreeFlag, { cwd, className: a.className, prompt: parsed.prompt })) {\n try {\n worktree = addWorktree(logDir, wid, cwd);\n } catch (e) {\n const why = (e as Error).message;\n process.stderr.write(`pai worker: no worktree (${why}) — running in place\\n`);\n appendLedger(ledgerPath(logDir), \"WORKER-NOTE\", { id: wid, note: `no worktree: ${why}` });\n }\n }\n // sub-workers detect themselves (and their parent) through this variable\n env.PAI_WORKER_ID = wid;\n\n const status: WorkerStatus = {\n id: wid,\n pid: process.pid,\n label,\n cwd,\n term,\n provider: target.providerName,\n model,\n state: \"running\",\n started: nowStamp(),\n updated: nowStamp(),\n turns: 0,\n tools: 0,\n last: headless ? \"starting\" : \"interactive\",\n rc: null,\n secs: null,\n // interactive runs ARE the chat pane, not a spawned subagent of it\n origin: headless ? \"spawn\" : \"chat\",\n ...(session ? { session } : {}),\n ...(spawnerSession ? { spawnerSession } : {}),\n // no window seed: contextWindow comes from the init event only, and the\n // meter stays hidden until one is announced (never a guessed default)\n ...(a.parent ? { parent: a.parent, stage: a.stage } : {}),\n ...(worktree ? { worktreeDir: worktree.dir, branch: worktree.branch, worktreeBase: worktree.base } : {}),\n };\n saveStatus(logDir, status);\n a.onWorkerStart?.(wid);\n const ledger = ledgerPath(logDir);\n appendLedger(ledger, \"WORKER-START\", {\n id: wid,\n provider: target.providerName,\n mode: headless ? \"headless\" : \"interactive\",\n model,\n cwd,\n label,\n });\n\n // Follow pane: headless only, best effort, never blocking the worker.\n if (headless && !noPane && config.pane.enabled && term && process.env.PAI_WORKER_AUTOPANE !== \"0\") {\n void openPaneForWorker(logDir, config, wid, term).catch(() => {});\n }\n\n // MCP: caller config > allowlist (--mcp flag / --mcp args / role / mcp__\n // grants in --allowedTools) > the strict empty set.\n let mcpArgs: string[] = [];\n if (headless && !parsed.callerMcpConfig) {\n const wanted = [\n ...(a.mcpFlag ? [a.mcpFlag] : []),\n ...parsed.mcp,\n ...(target.classMcp ?? []),\n ...mcpServersFromToolGrants(parsed.allowedTools),\n ];\n if (wanted.length) {\n const names = expandMcpNames(wanted, config); // unknown names fail fast\n mcpArgs = [\"--strict-mcp-config\", \"--mcp-config\", writeMcpConfig(logDir, wid, names)];\n } else {\n mcpArgs = [\"--strict-mcp-config\", \"--mcp-config\", ensureNoMcpConfig(logDir)];\n }\n }\n\n // In stdin mode the prompt moves to the first user message on stdin, so it\n // must come off the command line (bare -p stays: stream-json needs --print).\n const restArgs = headless ? stripPromptValues(parsed.rest) : parsed.rest;\n const toolArgs = headless ? headlessToolGrants(parsed.allowedTools) : [];\n const cmd: string[] = [\"claude\"];\n // a native-Anthropic run with no explicit --model resolves to \"\" (let\n // Claude Code pick its own default) — passing --model \"\" would break the\n // spawn, so skip the flag entirely in that case.\n if (!parsed.callerModel && model) cmd.push(\"--model\", model);\n cmd.push(...mcpArgs, ...toolArgs, ...restArgs);\n if (headless) {\n cmd.push(\"--output-format\", \"stream-json\", \"--verbose\", \"--input-format\", \"stream-json\");\n if (!parsed.callerSystemPrompt) cmd.push(\"--append-system-prompt\", WORKER_CONTRACT_PROMPT);\n if (worktree) {\n cmd.push(\n \"--append-system-prompt\",\n worktreeSystemPrompt(wid, worktree.branch, worktree.dir)\n );\n }\n }\n\n const t0 = Date.now();\n const proc = spawn(cmd[0], cmd.slice(1), {\n env,\n cwd: worktree?.dir ?? cwd,\n stdio: headless ? [\"pipe\", \"pipe\", \"inherit\"] : \"inherit\",\n });\n\n // --- stdin lifecycle (2i): prompt in, socket forwards, close 2 s after result\n let operatorInFlight = 0;\n let closeTimer: NodeJS.Timeout | null = null;\n const armStdinClose = () => {\n if (closeTimer) clearTimeout(closeTimer);\n closeTimer = setTimeout(() => {\n if (operatorInFlight === 0) {\n try {\n proc.stdin?.end();\n } catch {\n /* already closed */\n }\n }\n }, 2_000);\n };\n\n let eventsFd: number | null = null;\n const writeEvent = (obj: Record<string, unknown>): void => {\n if (eventsFd === null) return;\n try {\n writeSync(eventsFd, JSON.stringify({ ...obj, _ts: isoStamp() }) + \"\\n\");\n } catch {\n // a full disk must not take the worker transcript's process down\n }\n };\n\n const operatorServer = headless\n ? createOperatorServer(logDir, wid, (text) => {\n operatorInFlight += 1;\n if (closeTimer) {\n clearTimeout(closeTimer);\n closeTimer = null;\n }\n // a handoff delivery's mirror carries a flag: the viewer shows the\n // inbox ◆ line instead, never both\n writeEvent({ type: \"operator\", text, handoff: isHandoffMessage(text) });\n try {\n proc.stdin?.write(stdinUserMessage(operatorUserText(text)) + \"\\n\");\n } catch {\n /* child gone; the socket is closed by the run's cleanup */\n }\n })\n : null;\n\n let killed = false;\n const cleanup = () => {\n if (closeTimer) clearTimeout(closeTimer);\n operatorServer?.close();\n };\n const onSignal = (sig: string) => {\n killed = true;\n status.state = \"killed\";\n status.rc = 143;\n status.secs = Math.floor((Date.now() - t0) / 1000);\n status.last = `killed by signal ${sig}`;\n saveStatus(logDir, status);\n appendLedger(ledger, \"WORKER-END\", {\n id: wid,\n provider: target.providerName,\n mode: \"headless\",\n model,\n rc: 143,\n secs: status.secs,\n killed: 1,\n label,\n });\n // a killed worktree run leaves nothing to merge — drop its worktree and\n // branch too, or every kill strands them for hand-pruning (2026-09-18)\n if (worktree) {\n try {\n recordWorktree(logDir, status, worktree, false);\n } catch {\n /* best effort: the status already says killed */\n }\n }\n cleanup();\n try {\n proc.kill();\n } catch {\n /* already gone */\n }\n process.exit(143);\n };\n process.once(\"SIGTERM\", () => onSignal(\"SIGTERM\"));\n process.once(\"SIGINT\", () => onSignal(\"SIGINT\"));\n process.once(\"SIGHUP\", () => onSignal(\"SIGHUP\"));\n\n // Holder for the last result event + its parsed report: assigned inside the\n // readline callback below, read after the await.\n const ctx: { resultEvent: StreamEvent | null; resultReport: WorkerReport | null } = {\n resultEvent: null,\n resultReport: null,\n };\n\n if (headless) {\n // the first user message carries the prompt (the -p value was stripped)\n if (parsed.prompt !== null) {\n try {\n proc.stdin!.write(stdinUserMessage(parsed.prompt) + \"\\n\");\n } catch {\n /* child died instantly; the close handler reports it */\n }\n }\n eventsFd = openSync(eventsPath(logDir, wid), \"a\");\n const rl = createInterface({ input: proc.stdout! });\n rl.on(\"line\", (line) => {\n if (parsed.outputFormat === \"stream-json\") {\n process.stdout.write(line + \"\\n\");\n }\n if (!line.startsWith(\"{\")) return;\n let e: StreamEvent;\n try {\n e = JSON.parse(line) as StreamEvent;\n } catch {\n return;\n }\n writeEvent(e as Record<string, unknown>);\n if (e.type === \"system\" && e.subtype === \"init\") {\n if (e.session_id) status.claudeSession = e.session_id;\n const cw = initContextWindow(e);\n if (cw) status.contextWindow = cw;\n saveStatus(logDir, status);\n } else if (isCompactBoundary(e)) {\n resetContextTokensOnCompact(status, e);\n saveStatus(logDir, status);\n } else if (e.type === \"assistant\") {\n status.turns += 1;\n bumpContextTokens(status, usageContextTokens(e.message?.usage));\n for (const block of e.message?.content ?? []) {\n if (block.type === \"tool_use\") {\n status.tools += 1;\n status.last = describeTool(block.name ?? \"?\", block.input);\n } else if (block.type === \"text\" && (block.text ?? \"\").trim()) {\n status.last = \"says: \" + shortText(block.text, 70);\n }\n }\n saveStatus(logDir, status);\n } else if (e.type === \"result\") {\n const tokens = usageContextTokens(e.usage);\n // a compact result legitimately restarts the context lower\n if (e.is_compact) status.contextTokens = tokens ?? status.contextTokens;\n else bumpContextTokens(status, tokens);\n const report = parseWorkerReport(e.result ?? \"\");\n if (report?.notes) status.last = shortText(report.notes, 90);\n else if (e.result) status.last = shortText(e.result, 90);\n saveStatus(logDir, status);\n operatorInFlight = 0;\n armStdinClose();\n ctx.resultEvent = e;\n ctx.resultReport = report;\n }\n });\n }\n\n const rc = await new Promise<number>((resolve, reject) => {\n proc.on(\"error\", reject);\n proc.on(\"close\", (code) => resolve(code ?? (killed ? 143 : 1)));\n });\n if (eventsFd !== null) closeSync(eventsFd);\n cleanup();\n\n const secs = Math.floor((Date.now() - t0) / 1000);\n const resultEvent = ctx.resultEvent;\n const ok = rc === 0 && resultEvent !== null && !resultEvent.is_error;\n status.state = ok ? \"done\" : \"failed\";\n status.rc = rc;\n status.secs = secs;\n if (resultEvent) status.last = shortText(resultEvent.result ?? \"\", 90);\n if (ctx.resultReport?.notes) status.last = shortText(ctx.resultReport.notes, 90);\n saveStatus(logDir, status);\n appendLedger(ledger, \"WORKER-END\", {\n id: wid,\n provider: target.providerName,\n mode: headless ? \"headless\" : \"interactive\",\n model,\n rc,\n secs,\n turns: status.turns,\n tools: status.tools,\n label,\n });\n\n // worktree outcome: keep branch + commit count on success, clean up on failure\n if (worktree) recordWorktree(logDir, status, worktree, ok);\n\n if (headless && !a.quiet) {\n printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, ctx.resultReport, worktreeExtras(status));\n }\n\n // Quota reroute: only auto-routed runs, dead before the first tool call.\n const resultText = resultEvent?.result ?? \"\";\n if (\n !ok &&\n headless &&\n a.target.via === \"auto\" &&\n config.routing.retryOnQuota &&\n status.turns <= 1 &&\n status.tools === 0 &&\n isQuotaFailure(resultText)\n ) {\n setCooldown(logDir, target.providerName, config.routing.cooldownMinutes);\n const next = nextAutoProvider(config, logDir, target.providerName);\n if (next && a.reroutes < config.routing.order.length) {\n appendLedger(ledger, \"WORKER-REROUTE\", {\n from: target.providerName,\n to: next,\n reason: \"quota\",\n });\n return runWorker({\n providerFlag: next,\n label,\n noPane: a.noPane,\n mcpFlag: a.mcpFlag,\n cwd: a.cwd,\n parent: a.parent,\n stage: a.stage,\n claudeArgs: a.claudeArgs,\n onWorkerStart: a.onWorkerStart,\n worktreeFlag: a.worktreeFlag,\n className: a.className,\n _reroutes: a.reroutes + 1,\n });\n }\n }\n\n // A finishing child reports to its worker parent automatically: the report\n // lands in the parent's inbox and (when the parent still runs) is said to\n // it so it enters the parent's conversation.\n const finalRc = rc !== 0 ? rc : ok ? 0 : 1;\n if (status.parent && isWorkerId(logDir, status.parent)) {\n try {\n await deliverHandoff(logDir, {\n from: wid,\n to: status.parent,\n kind: \"result\",\n text: shortText(\n ctx.resultReport?.notes ?? resultEvent?.result ?? (ok ? \"done\" : \"failed\"),\n 400\n ),\n data: {\n rc: finalRc,\n ok,\n ...(status.branch ? { branch: status.branch, commits: status.commits ?? 0 } : {}),\n ...(ctx.resultReport ? { report: ctx.resultReport } : {}),\n },\n });\n } catch {\n // the inbox line is best effort; it must never fail the exit path\n }\n }\n\n return finalRc;\n}\n\n/** The worktree fields printResult adds to a json payload, when there is one. */\nfunction worktreeExtras(s: WorkerStatus): Record<string, unknown> | undefined {\n return s.branch ? { branch: s.branch, commits: s.commits ?? 0 } : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// codex engine (2d)\n// ---------------------------------------------------------------------------\n\ninterface CodexArgs extends Omit<ExecuteArgs, \"reroutes\" | \"mcpFlag\"> {}\n\nasync function executeCodexRun(a: CodexArgs): Promise<number> {\n const { config, logDir, target, model, label, parsed, noPane } = a;\n if (!parsed.headless || parsed.prompt === null) {\n throw new Error(\n `provider \"${target.providerName}\" (engine codex) supports headless runs only: ` +\n `pass the task with -p '<prompt>'`\n );\n }\n const env = buildCodexEnv(target.provider);\n const wid = a.id ?? newWorkerId();\n const cwd = a.cwd ?? process.cwd();\n const term = process.env.ITERM_SESSION_ID ?? \"\";\n const session = resolveSession(term);\n const spawnerSession = resolveSpawnerSession(logDir, cwd);\n\n let worktree: WorktreeInfo | null = null;\n if (worktreeWanted(a.worktreeFlag, { cwd, className: a.className, prompt: parsed.prompt })) {\n try {\n worktree = addWorktree(logDir, wid, cwd);\n } catch (e) {\n const why = (e as Error).message;\n process.stderr.write(`pai worker: no worktree (${why}) — running in place\\n`);\n appendLedger(ledgerPath(logDir), \"WORKER-NOTE\", { id: wid, note: `no worktree: ${why}` });\n }\n }\n env.PAI_WORKER_ID = wid;\n // codex takes instructions through the prompt, not a system prompt flag\n const prompt = (worktree ? worktreeSystemPrompt(wid, worktree.branch, worktree.dir) + \"\\n\\n\" : \"\") + parsed.prompt;\n\n const status: WorkerStatus = {\n id: wid,\n pid: process.pid,\n label,\n cwd,\n term,\n provider: target.providerName,\n model,\n state: \"running\",\n started: nowStamp(),\n updated: nowStamp(),\n turns: 0,\n tools: 0,\n last: \"starting\",\n rc: null,\n secs: null,\n origin: \"spawn\",\n ...(session ? { session } : {}),\n ...(spawnerSession ? { spawnerSession } : {}),\n // codex has no init event of its own: the synthetic one below announces\n // an explicitly configured window (never a guessed default)\n ...(target.provider.contextWindow ? { contextWindow: target.provider.contextWindow } : {}),\n ...(a.parent ? { parent: a.parent, stage: a.stage } : {}),\n ...(worktree ? { worktreeDir: worktree.dir, branch: worktree.branch, worktreeBase: worktree.base } : {}),\n };\n saveStatus(logDir, status);\n a.onWorkerStart?.(wid);\n const ledger = ledgerPath(logDir);\n appendLedger(ledger, \"WORKER-START\", {\n id: wid,\n provider: target.providerName,\n mode: \"headless\",\n engine: \"codex\",\n model,\n cwd,\n label,\n });\n const dropped = codexDroppedFlags(a.claudeArgs);\n if (dropped.length) {\n appendLedger(ledger, \"WORKER-NOTE\", {\n id: wid,\n note: `dropped for codex: ${dropped.join(\", \")}`,\n });\n }\n\n if (!noPane && config.pane.enabled && term && process.env.PAI_WORKER_AUTOPANE !== \"0\") {\n void openPaneForWorker(logDir, config, wid, term).catch(() => {});\n }\n\n const t0 = Date.now();\n const proc = spawn(\"codex\", buildCodexArgs(prompt, parsed.callerModel ? undefined : model), {\n env,\n cwd: worktree?.dir ?? cwd,\n stdio: [\"ignore\", \"pipe\", \"inherit\"],\n });\n\n const fold = emptyCodexResult();\n const eventsFd = openSync(eventsPath(logDir, wid), \"a\");\n const writeEvent = (obj: Record<string, unknown>) => {\n try {\n writeSync(eventsFd, JSON.stringify({ ...obj, _ts: isoStamp() }) + \"\\n\");\n } catch {\n // best effort transcript\n }\n };\n writeEvent({\n type: \"system\",\n subtype: \"init\",\n model,\n cwd,\n ...(target.provider.contextWindow ? { context_window: target.provider.contextWindow } : {}),\n });\n\n let killed = false;\n process.once(\"SIGTERM\", onCodexSignal(\"SIGTERM\"));\n process.once(\"SIGINT\", onCodexSignal(\"SIGINT\"));\n process.once(\"SIGHUP\", onCodexSignal(\"SIGHUP\"));\n function onCodexSignal(sig: string) {\n return () => {\n killed = true;\n status.state = \"killed\";\n status.rc = 143;\n status.secs = Math.floor((Date.now() - t0) / 1000);\n status.last = `killed by signal ${sig}`;\n saveStatus(logDir, status);\n // same as the claude path: a killed run's worktree and branch go now\n if (worktree) {\n try {\n recordWorktree(logDir, status, worktree, false);\n } catch {\n /* best effort: the status already says killed */\n }\n }\n try {\n proc.kill();\n } catch {\n /* already gone */\n }\n process.exit(143);\n };\n }\n\n const rl = createInterface({ input: proc.stdout! });\n rl.on(\"line\", (line) => {\n if (parsed.outputFormat === \"stream-json\") process.stdout.write(line + \"\\n\");\n const parsedLine = parseCodexLine(line);\n if (parsedLine === null) return;\n foldCodexLine(parsedLine, fold);\n if (fold.threadId && !status.claudeSession) status.claudeSession = fold.threadId;\n status.turns = fold.turns;\n status.tools = fold.tools;\n if (fold.last) status.last = shortText(fold.last, 90);\n bumpContextTokens(status, fold.contextTokens);\n saveStatus(logDir, status);\n for (const ev of fold.events.splice(0)) writeEvent(ev);\n });\n\n const rc = await new Promise<number>((resolve, reject) => {\n proc.on(\"error\", reject);\n proc.on(\"close\", (code) => resolve(code ?? (killed ? 143 : 1)));\n });\n closeSync(eventsFd);\n\n const secs = Math.floor((Date.now() - t0) / 1000);\n const finalText = fold.finalText ?? \"\";\n const report = parseWorkerReport(finalText);\n const resultEvent: StreamEvent = {\n type: \"result\",\n result: finalText,\n is_error: fold.isError || rc !== 0,\n num_turns: fold.turns,\n duration_ms: secs * 1000,\n };\n writeEvent(resultEvent);\n\n const ok = rc === 0 && !fold.isError;\n status.state = ok ? \"done\" : \"failed\";\n status.rc = rc;\n status.secs = secs;\n status.last = shortText(report?.notes ?? finalText, 90) || (ok ? \"done\" : \"failed\");\n saveStatus(logDir, status);\n appendLedger(ledger, \"WORKER-END\", {\n id: wid,\n provider: target.providerName,\n mode: \"headless\",\n engine: \"codex\",\n model,\n rc,\n secs,\n turns: status.turns,\n tools: status.tools,\n label,\n });\n\n if (worktree) recordWorktree(logDir, status, worktree, ok);\n\n if (!a.quiet) printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, report, worktreeExtras(status));\n\n // the codex engine reports to its worker parent the same way (handoff.ts)\n const finalRc = rc !== 0 ? rc : ok ? 0 : 1;\n if (status.parent && isWorkerId(logDir, status.parent)) {\n try {\n await deliverHandoff(logDir, {\n from: wid,\n to: status.parent,\n kind: \"result\",\n text: shortText(report?.notes ?? finalText ?? (ok ? \"done\" : \"failed\"), 400),\n data: {\n rc: finalRc,\n ok,\n ...(status.branch ? { branch: status.branch, commits: status.commits ?? 0 } : {}),\n ...(report ? { report } : {}),\n },\n });\n } catch {\n // best effort; never fail the exit path\n }\n }\n return finalRc;\n}\n\n// ---------------------------------------------------------------------------\n// result printing\n// ---------------------------------------------------------------------------\n\nexport function printResult(\n fmt: \"text\" | \"json\" | \"stream-json\",\n resultEvent: StreamEvent | null,\n rc: number,\n logDir: string,\n wid: string,\n report?: WorkerReport | null,\n extras?: Record<string, unknown>\n): void {\n if (fmt === \"stream-json\") return; // already mirrored live\n if (fmt === \"json\") {\n const payload = report\n ? { ...(resultEvent ?? { is_error: true, result: \"no result event\", rc }), report, ...extras }\n : { ...(resultEvent ?? { is_error: true, result: \"no result event\", rc }), ...extras };\n console.log(JSON.stringify(payload));\n return;\n }\n if (resultEvent) {\n console.log(resultEvent.result ?? \"\");\n } else {\n process.stderr.write(\n `pai worker: run produced no result (rc=${rc}); see ${eventsPath(logDir, wid)}\\n`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// providers test: the 90-second pong probe\n// ---------------------------------------------------------------------------\n\nexport interface ProviderTestResult {\n provider: string;\n model: string;\n latencyMs: number;\n result: string;\n ok: boolean;\n /** Set when the probe could not run (e.g. \"codex not installed\"). */\n skipped?: string;\n}\n\n/**\n * Run a one-word pong probe through the provider (headless, no pane) and\n * report provider, model, latency and the reply. ok is false unless the reply\n * was exactly \"pong\" (case-insensitive, whitespace-trimmed).\n */\nexport async function testProvider(\n providerName: string,\n provider: WorkerProvider,\n logDir: string,\n timeoutMs = 90_000\n): Promise<ProviderTestResult> {\n assertProviderRunnable(providerName, provider);\n const model = provider.models.default;\n\n if (provider.engine === \"codex\") {\n const skipped = codexInstalled() ? undefined : \"codex not installed\";\n return {\n provider: providerName,\n model,\n latencyMs: 0,\n result: skipped ?? \"codex engine: pong probe through codex exec not implemented\",\n ok: false,\n ...(skipped ? { skipped } : {}),\n };\n }\n\n let proxyUrl: string | undefined;\n if (provider.protocol === \"openai\") {\n const base = await ensureProxyRunning(DEFAULT_PROXY_PORT, logDir);\n proxyUrl = `${base}/${providerName}`;\n }\n const env = buildRunEnv(provider, true, proxyUrl);\n const t0 = Date.now();\n const proc = spawn(\n \"claude\",\n [\n \"--model\", model,\n \"--strict-mcp-config\", \"--mcp-config\", ensureNoMcpConfig(logDir),\n \"-p\", \"Reply with exactly one word: pong\",\n \"--output-format\", \"json\",\n ],\n { env, stdio: [\"ignore\", \"pipe\", \"inherit\"] }\n );\n const timer = setTimeout(() => {\n try { proc.kill(\"SIGKILL\"); } catch { /* already gone */ }\n }, timeoutMs);\n\n let out = \"\";\n proc.stdout.on(\"data\", (chunk: Buffer) => {\n out += chunk.toString(\"utf8\");\n });\n const rc = await new Promise<number>((resolve) => {\n proc.on(\"error\", () => resolve(1));\n proc.on(\"close\", (code) => resolve(code ?? 1));\n });\n clearTimeout(timer);\n\n return {\n provider: providerName,\n model,\n latencyMs: Date.now() - t0,\n result: resultFromOutput(out),\n ok: rc === 0 && resultFromOutput(out).trim().toLowerCase() === \"pong\",\n };\n}\n\n/**\n * Pull the reply out of claude's stdout. With --verbose, `--output-format\n * json` dumps a JSON array of stream events and the reply sits in the last\n * \"result\" event; without it, stdout is the single result object. Accept\n * either shape, plus a bare-text fallback for error output.\n */\nexport function resultFromOutput(out: string): string {\n const trimmed = out.trim();\n if (!trimmed) return \"\";\n const parse = (s: string): unknown => {\n try {\n return JSON.parse(s);\n } catch {\n return undefined;\n }\n };\n const fromValue = (v: unknown): string | null => {\n if (Array.isArray(v)) {\n for (let i = v.length - 1; i >= 0; i--) {\n const r = fromValue(v[i]);\n if (r !== null) return r;\n }\n return null;\n }\n if (typeof v === \"object\" && v !== null) {\n const o = v as StreamEvent;\n if (o.type === \"result\" && typeof o.result === \"string\") return o.result;\n }\n return null;\n };\n const direct = fromValue(parse(trimmed));\n if (direct !== null) return direct;\n const lines = trimmed.split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n const r = fromValue(parse(lines[i]));\n if (r !== null) return r;\n }\n return trimmed.slice(0, 200);\n}\n"],"mappings":";;;;;;;;;;AAmCA,SAAgB,gBAAgB,MAAkC;CAChE,IAAI,SAAwB;CAC5B,IAAI,eAAiD;CACrD,MAAM,OAAiB,EAAE;CACzB,IAAI,WAAW;CACf,IAAI,cAAc;CAClB,IAAI,kBAAkB;CACtB,IAAI,qBAAqB;CACzB,MAAM,MAAgB,EAAE;CACxB,MAAM,eAAyB,EAAE;CAEjC,IAAI,IAAI;AACR,QAAO,IAAI,KAAK,QAAQ;EACtB,MAAM,IAAI,KAAK;AACf,MAAI,MAAM,QAAQ,MAAM,WAAW;AACjC,cAAW;AACX,QAAK,KAAK,EAAE;AACZ,OAAI,IAAI,IAAI,KAAK,UAAU,CAAC,KAAK,IAAI,GAAG,WAAW,IAAI,EAAE;AACvD,aAAS,KAAK,IAAI;AAClB,SAAK,KAAK,OAAO;AACjB,SAAK;;aAEE,MAAM,mBAAmB;GAClC,MAAM,IAAI,KAAK,IAAI;AACnB,OAAI,MAAM,UAAU,MAAM,cAAe,gBAAe;AACxD,QAAK;aACI,EAAE,WAAW,mBAAmB,EAAE;GAC3C,MAAM,IAAI,EAAE,MAAM,GAA0B;AAC5C,OAAI,MAAM,UAAU,MAAM,cAAe,gBAAe;aAC/C,MAAM,aAAa,YAEnB,MAAM,SAAS;GACxB,MAAM,IAAI,KAAK,IAAI;AACnB,OAAI,MAAM,UAAa,CAAC,EAAE,WAAW,IAAI,EAAE;AACzC,QAAI,KAAK,EAAE;AACX,SAAK;;aAEE,EAAE,WAAW,SAAS,CAC/B,KAAI,KAAK,EAAE,MAAM,EAAgB,CAAC;WACzB,MAAM,kBAAkB;AAEjC,QAAK,KAAK,EAAE;GACZ,MAAM,IAAI,KAAK,IAAI;AACnB,OAAI,MAAM,UAAa,CAAC,EAAE,WAAW,IAAI,EAAE;AACzC,iBAAa,KAAK,EAAE;AACpB,SAAK,KAAK,EAAE;AACZ,SAAK;;aAEE,EAAE,WAAW,kBAAkB,EAAE;AAC1C,gBAAa,KAAK,EAAE,MAAM,GAAyB,CAAC;AACpD,QAAK,KAAK,EAAE;SACP;AACL,OAAI,MAAM,UAAW,eAAc;AACnC,OAAI,EAAE,WAAW,WAAW,CAAE,eAAc;AAC5C,OAAI,MAAM,eAAgB,mBAAkB;AAC5C,OAAI,EAAE,WAAW,gBAAgB,CAAE,mBAAkB;AACrD,OAAI,MAAM,yBAA0B,sBAAqB;AACzD,OAAI,EAAE,WAAW,0BAA0B,CAAE,sBAAqB;AAClE,OACE,WAAW,QAAQ,CAAC,EAAE,WAAW,IAAI,IAAI,KAAK,SAAS,MACtD,KAAK,KAAK,SAAS,OAAO,QAAQ,KAAK,KAAK,SAAS,OAAO,WAE7D,UAAS;AAEX,QAAK,KAAK,EAAE;;AAEd,OAAK;;AAGP,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;;;;;;;;AASH,SAAgB,kBAAkB,MAA0B;CAC1D,MAAM,MAAgB,EAAE;AACxB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,IAAI,KAAK;EACf,MAAM,UAAU,MAAM,QAAQ,MAAM;AACpC,MAAI,KAAK,EAAE;AACX,MAAI,WAAW,IAAI,IAAI,KAAK,UAAU,CAAC,KAAK,IAAI,GAAG,WAAW,IAAI,CAChE,MAAK;;AAGT,QAAO;;;AAIT,SAAgB,UAAU,GAAY,GAAmB;CACvD,MAAM,IAAI,OAAO,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAChE,QAAO,EAAE,UAAU,IAAI,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG;;;;;;;;;;;;;;;;;;AC1HjD,SAAgB,YAAY,oBAAU,IAAI,MAAM,EAAU;CACxD,MAAM,KAAK,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACnD,QACE,GAAG,EAAE,aAAa,CAAC,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,GACzD,EAAE,EAAE,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;;;AAWhE,SAAgB,gBAAgB,MAAiC;CAC/D,MAAM,IAAI,KAAK,MAAM,0DAA0D;AAC/E,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,SAAiC,EAAE;AAEzC,MAAK,MAAM,SAAS,EAAE,MAAM,IAAI,MAAM,IAAI,EAAE;AAC1C,MAAI,CAAC,KAAM;EACX,MAAM,KAAK,KAAK,QAAQ,IAAI;AAC5B,MAAI,MAAM,EAAG;AACb,SAAO,KAAK,MAAM,GAAG,GAAG,IAAI,KAAK,MAAM,KAAK,EAAE;;AAEhD,QAAO;EAAE,OAAO,EAAE;EAAI,OAAO,EAAE;EAAI;EAAQ;;;AAc7C,SAAgB,aACd,MACA,OACA,IACA,sBAAY,IAAI,MAAM,EAChB;CACN,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,GAAG,EAAE;AACvC,MAAI,MAAM,UAAa,MAAM,KAAM;AACnC,QAAM,KAAK,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC,QAAQ,QAAQ,IAAI,GAAG;;CAEtD,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACzD,MAAM,OAAO,MAAM,SAAS,MAAM,MAAM,KAAK,IAAI,GAAG;AACpD,gBAAe,MAAM,GAAG,YAAY,IAAI,CAAC,GAAG,QAAQ,KAAK,KAAK,OAAO;;;AAevE,SAAgB,cACd,MACA,OACA,QAAQ,IACR,sBAAY,IAAI,MAAM,EACA;AACtB,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;CAC9B,MAAM,OAAO,aAAa,MAAM,OAAO;CACvC,MAAM,QAAQ,YAAY,IAAI,CAAC,MAAM,GAAG,GAAG;CAC3C,MAAM,QAAQ,KAAK,MAAM,KAAK,CAAC,QAAQ,MAAM,EAAE,MAAM,CAAC;CAGtD,MAAM,UADJ,UAAU,QAAQ,QAAQ,MAAM,QAAQ,MAAM,EAAE,WAAW,MAAM,CAAC,EAC9C,IAAI,gBAAgB,CAAC,QAAQ,MAAuB,MAAM,KAAK;CACrF,MAAM,SAAS,OAAe,OAAO,QAAQ,MAAM,EAAE,UAAU,GAAG,CAAC;CACnE,MAAM,OAAO,OAAO,QAAQ,MAAM,EAAE,UAAU,aAAa;CAC3D,MAAM,KAAK,KAAK,QAAQ,MAAM,EAAE,OAAO,OAAO,IAAI,CAAC;AACnD,QAAO;EACL,OAAO,UAAU,QAAQ,aAAa,SAAS;EAC/C,SAAS,MAAM,eAAe;EAC9B,SAAS;EACT,aAAa,KAAK,SAAS;EAC3B,QAAQ,MAAM,yBAAyB;EACvC,SAAS,MAAM,0BAA0B;EACzC,UAAU,MAAM,iBAAiB;EACjC,WAAW,MAAM,MAAM,CAAC,MAAM;EAC/B;;;;;;;;;;;;;;ACpCH,MAAa,YAAY;;;;;;;AAQzB,SAAgB,WAAW,GAA8D;AACvF,QACE,EAAE,WAAW,UACZ,CAAC,EAAE,WAAW,EAAE,UAAU,aAAa,EAAE,UAAU,kBAAkB,EAAE,UAAU;;;AAKtF,SAAgB,eAAe,GAAyE;AACtG,KAAI,CAAC,EAAE,iBAAiB,CAAC,EAAE,cAAe,QAAO;AACjD,QAAO,KAAK,MAAO,EAAE,gBAAgB,EAAE,gBAAiB,IAAI;;;AAI9D,SAAgB,YAAY,GAAmB;AAC7C,QAAO,KAAK,MAAO,GAAG,KAAK,MAAM,IAAI,IAAK,CAAC,KAAK,OAAO,EAAE;;;;;;;AAQ3D,SAAgB,aACd,GACQ;CACR,MAAM,MAAM,eAAe,EAAE;AAC7B,KAAI,QAAQ,KAAM,QAAO;AACzB,QAAO,OAAO,YAAY,EAAE,iBAAiB,EAAE,CAAC,GAAG,YAAY,EAAE,iBAAiB,EAAE,CAAC,IAAI,IAAI;;;AAI/F,SAAgB,SAAS,oBAAU,IAAI,MAAM,EAAU;CACrD,MAAM,KAAK,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACnD,QACE,GAAG,EAAE,aAAa,CAAC,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,GACzD,EAAE,EAAE,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;;AAMhE,IAAI,SAAS;AACb,IAAI,QAAQ;AAEZ,SAAgB,YAAY,oBAAU,IAAI,MAAM,EAAE,MAAM,QAAQ,KAAa;CAC3E,MAAM,KAAK,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;CACnD,MAAM,OAAO,GAAG,EAAE,aAAa,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC,GAAG;AACrI,KAAI,SAAS,QAAQ;AACnB,WAAS;AACT,SAAO,GAAG,KAAK,GAAG,OAAO,MAAM,CAAC,SAAS,GAAG,IAAI;;AAElD,UAAS;AACT,SAAQ;AACR,QAAO;;;AAIT,SAAgB,WAAW,QAAgB,QAAsB,oBAAU,IAAI,MAAM,EAAQ;AAC3F,QAAO,UAAU,SAAS,EAAE;CAC5B,MAAM,OAAO,WAAW,QAAQ,OAAO,GAAG;CAC1C,MAAM,MAAM,GAAG,KAAK;AACpB,eAAc,KAAK,KAAK,UAAU,OAAO,EAAE,OAAO;AAClD,YAAW,KAAK,KAAK;;;AAIvB,SAAgB,aAAa,QAAgC;AAC3D,KAAI,CAAC,WAAW,OAAO,CAAE,QAAO,EAAE;CAClC,MAAM,MAAsB,EAAE;AAC9B,MAAK,MAAM,QAAQ,YAAY,OAAO,CAAC,MAAM,EAAE;AAC7C,MAAI,CAAC,KAAK,SAAS,UAAU,CAAE;AAC/B,MAAI;AACF,OAAI,KAAK,KAAK,MAAM,aAAa,KAAK,QAAQ,KAAK,EAAE,OAAO,CAAC,CAAiB;UACxE;;AAIV,QAAO;;AAGT,SAAgB,WAAW,QAAgB,IAAiC;CAC1E,MAAM,OAAO,WAAW,QAAQ,GAAG;AACnC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;AAC9B,KAAI;AACF,SAAO,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;SACvC;AACN,SAAO;;;AAIX,SAAgB,MAAM,KAAyC;AAC7D,KAAI,CAAC,OAAO,OAAO,EAAG,QAAO;AAC7B,KAAI;AACF,UAAQ,KAAK,KAAK,EAAE;AACpB,SAAO;SACD;AACN,SAAO;;;;AAKX,SAAgB,MAAM,IAAY,sBAAY,IAAI,MAAM,EAAU;CAChE,MAAM,IAAI,KAAK,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAC;AAC1C,KAAI,OAAO,MAAM,EAAE,CAAE,QAAO;CAC5B,MAAM,IAAI,KAAK,IAAI,GAAG,KAAK,OAAO,IAAI,SAAS,GAAG,KAAK,IAAK,CAAC;AAC7D,QAAO,IAAI,KAAK,GAAG,EAAE,KAAK,GAAG,KAAK,MAAM,IAAI,GAAG,CAAC;;;AAIlD,SAAgB,aAAa,MAAc,KAAsB;AAC/D,KAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO;CACpD,MAAM,IAAI;CACV,MAAM,OAAO,MAAe,OAAO,EAAE,OAAO,WAAY,EAAE,KAAgB;AAC1E,KAAI,SAAS,OAAQ,QAAO,SAAS,UAAU,IAAI,UAAU,EAAE,GAAG;AAClE,KAAI,SAAS,UAAU,SAAS,UAAU,SAAS,WAAW,SAAS,YAErE,QAAO,GAAG,KAAK,IADF,IAAI,YAAY,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI;AAGpD,KAAI,SAAS,UAAU,SAAS,OAAQ,QAAO,GAAG,KAAK,IAAI,UAAU,IAAI,UAAU,EAAE,GAAG;AACxF,KAAI,SAAS,YAAa,QAAO,GAAG,KAAK,IAAI,UAAU,IAAI,QAAQ,EAAE,GAAG;AACxE,KAAI,SAAS,WAAY,QAAO,GAAG,KAAK,IAAI,UAAU,IAAI,MAAM,EAAE,GAAG;AACrE,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AClLT,MAAa,oBAAoB,KAAK,SAAS,EAAE,aAAa,qBAAqB;;;;;AAMnF,SAAgB,OAAO,MAAsB;AAC3C,KAAI,CAAC,KAAM,QAAO;CAClB,MAAM,OAAO,KAAK,MAAM,KAAK,EAAE,CAAC;CAChC,MAAM,QAAQ,KAAK,MAAM,EAAE,CAAC,MAAM,IAAI;AACtC,KAAI,KAAK,WAAW,IAAI,IAAI,MAAM,WAAW,KAAK,MAAM,OAAO,MAAM,QAAQ,KAAK,EAAE,CAAC,CACnF,QAAO;AAET,QAAO;;;AAIT,SAAgB,cAAc,MAAyB,QAAQ,KAAa;AAC1E,QAAO,OAAO,IAAI,oBAAoB,GAAG;;;AAI3C,SAAgB,UAAU,MAAsB;AAC9C,KAAI,CAAC,KAAM,QAAO;AAClB,QAAO,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI;;;;;;;;AAclC,SAAgB,eACd,MACA,eAAuB,mBACC;CACxB,MAAM,OAAO,UAAU,KAAK;AAC5B,KAAI,CAAC,KAAM,QAAO;CAClB,IAAI;AACJ,KAAI;AACF,MAAI,CAAC,WAAW,aAAa,CAAE,QAAO;AACtC,UAAQ,KAAK,MAAM,aAAa,cAAc,OAAO,CAAC;SAChD;AACN,SAAO;;CAET,MAAM,OAAO,MAAM;AACnB,KAAI,OAAO,SAAS,YAAY,CAAC,KAAM,QAAO;AAC9C,QAAO;EAAE,IAAI;EAAM;EAAM;;;;;;;AAQ3B,SAAgB,cAAc,QAAsB,MAAuB;AACzE,KAAI,CAAC,KAAM,QAAO;CAClB,MAAM,OAAO,UAAU,KAAK;AAC5B,KAAI,OAAO,SAAS,MAAM,KAAM,QAAO,OAAO,QAAQ,OAAO;AAC7D,QAAO,OAAO,OAAO,KAAK,KAAK,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK;;;;;;AAOlE,SAAgB,SAAS,MAAsB;CAC7C,MAAM,UAAU,eAAe,KAAK;AACpC,KAAI,QAAS,QAAO,QAAQ;AAC5B,QAAO,eAAe,IAAI,UAAU,KAAK;;;AAI3C,SAAgB,WAAW,QAAwD;AACjF,QAAO,OAAO,SAAS,OAAO,IAAI,OAAO,QAAQ,KAAK,KAAK;;;AA4B7D,MAAa,yBAAyB,KAAK;;AAG3C,MAAM,uBAAuB,KAAK;AAElC,SAAgB,eAAe,QAAwB;AACrD,QAAO,KAAK,QAAQ,0BAA0B;;;;;;;AAuDhD,SAAgB,sBACd,QACA,KACA,MAAyB,QAAQ,KACjC,MAAc,KAAK,KAAK,EACT;CACf,MAAM,WAAW,IAAI;AACrB,KAAI,UAAU;EACZ,MAAM,YAAY,WAAW,QAAQ,SAAS,EAAE;AAChD,MAAI,UAAW,QAAO;;CAExB,MAAM,OAAO,eAAe,OAAO;AACnC,KAAI,CAAC,WAAW,KAAK,IAAI,CAAC,IAAK,QAAO;AACtC,KAAI;EACF,MAAM,QAAS,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC,CAAqC;AAC1F,MAAI,SAAS,MAAM,WAAW,MAAM,MAAM,KAAK,uBAAwB,QAAO,MAAM;SAC9E;AAGR,QAAO;;;;;;;;;AAUT,SAAgB,sBACd,QACA,eACA,MAAc,KAAK,KAAK,EACT;CACf,MAAM,OAAO,eAAe,OAAO;AACnC,KAAI,CAAC,WAAW,KAAK,IAAI,CAAC,cAAe,QAAO;CAChD,IAAI;AACJ,KAAI;AACF,QAAM,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;SACtC;AACN,SAAO;;CAET,IAAI,OAA+B;AACnC,MAAK,MAAM,KAAK,OAAO,OAAO,IAAI,EAAE;AAClC,MAAI,EAAE,YAAY,iBAAiB,CAAC,EAAE,KAAM;AAE5C,MAAI,MAAM,EAAE,MAAM,qBAAsB;AACxC,MAAI,CAAC,QAAQ,EAAE,KAAK,KAAK,GAAI,QAAO;;CAEtC,MAAM,OAAO,MAAM;AACnB,QAAO,OAAO,UAAU,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACrMlC,MAAa,eAAe;;AAG5B,SAAgB,qBAA6B;AAC3C,QAAO,QAAQ,IAAI,sBAAsB,KACvC,SAAS,EACT,WACA,uBACA,UACA,mBACA,kBACD;;AAUH,MAAa,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FnC,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;AAwBxB,MAAa,uBAAuB;;;;;;;;;;;;;;;;;;;;;AAwBpC,MAAa,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkC5B,SAAS,UAAU,QAAgB,MAA6D;AAC9F,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,OAAO,MAAM,aAAa,CAAC,KAAK,GAAG,KAAK,EAAE,EAAE,OAAO;GAAC;GAAQ;GAAQ;GAAO,EAAE,CAAC;EACpF,IAAI,MAAM;EACV,IAAI,MAAM;AACV,OAAK,OAAO,GAAG,SAAS,MAAe,OAAO,EAAE,SAAS,OAAO,CAAE;AAClE,OAAK,OAAO,GAAG,SAAS,MAAe,OAAO,EAAE,SAAS,OAAO,CAAE;AAClE,OAAK,GAAG,SAAS,OAAO;AACxB,OAAK,GAAG,eAAe,QAAQ;GAAE,QAAQ;GAAK,QAAQ;GAAK,CAAC,CAAC;AAC7D,OAAK,MAAM,MAAM,OAAO;AACxB,OAAK,MAAM,KAAK;GAChB;;AAGJ,SAAS,SAAS,QAAwB;AACxC,KAAI;AACF,SAAO,aAAa,MAAM,CAAC,QAAQ,OAAO,EAAE,EAAE,UAAU,QAAQ,CAAC;SAC3D;AACN,SAAO;;;;AAKX,SAAS,aAA0B;CACjC,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,QAAQ,SAAS,gBAAgB,CAAC,MAAM,KAAK,EAAE;EACxD,MAAM,QAAQ,KAAK,MAAM,CAAC,MAAM,OAAO,EAAE;AACzC,MAAI,MAAM,SAAS,KAAK,MAAM,OAAO,KAAM;AAC3C,MAAI,CAAC,0DAA0D,KAAK,MAAM,GAAG,CAAE;AAC/E,MAAI,MAAM,GAAG,WAAW,OAAO,CAAE,MAAK,IAAI,QAAQ,MAAM,KAAK;;AAE/D,QAAO;;;AAIT,SAAgB,eAAe,KAAsB;CACnD,MAAM,MAAM,IAAI,OAAO,iDAAiD,IAAI,QAAQ,uBAAuB,OAAO,CAAC,KAAK;AACxH,QAAO,SAAS,WAAW,CAAC,MAAM,KAAK,CAAC,MAAM,OAAO,IAAI,KAAK,GAAG,CAAC;;;AAkBpE,SAAS,QAAQ,GAAoB;CACnC,MAAM,MAAM;AAEZ,UADe,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,IAAI,QAAQ,SAAS,OAAO,KACvE,IAAI,WAAW,OAAO,EAAE,EAAE,MAAM;;;;;;;;;;AAWpD,SAAgB,eAAe,WAA8B;CAC3D,IAAI,YAAwB,EAAE;AAC9B,KAAI;EACF,MAAM,MAAM,aACV,UACA;GAAC;GAAY;GAAiB;GAAQ;GAAM;GAAK;GAAU,EAC3D;GAAE,UAAU;GAAQ,SAAS;GAAQ,CACtC;EACD,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,MAAM,QAAQ,OAAO,CAAE,aAAY;UAChC,GAAG;AACV,SAAO;GAAE,WAAW,EAAE;GAAE,aAAa;GAAM,OAAO,6BAA6B,QAAQ,EAAE;GAAI;;CAE/F,IAAI,cAA6B;AACjC,KAAI;AAMF,gBALY,aACV,UACA;GAAC;GAAY;GAAyB;GAAO;GAAM;GAAK;GAAU,EAClE;GAAE,UAAU;GAAQ,SAAS;GAAQ,CACtC,CACiB,MAAM,IAAI;SACtB;AAGR,QAAO;EAAE;EAAW;EAAa,OAAO;EAAM;;;AAIhD,SAAgB,aAAwB;CACtC,IAAI,MAAqB;AACzB,KAAI;AACF,QAAM,YAAY,KAAK,QAAQ,EAAE,aAAa,CAAC;EAC/C,MAAM,QAAQ,KAAK,KAAK,eAAe;AACvC,eAAa,YAAY;GAAC;GAAU;GAAyB;GAAM,EAAE;GACnE,UAAU;GACV,SAAS;GACV,CAAC;AACF,SAAO,eAAe,MAAM;UACrB,GAAG;AACV,SAAO;GAAE,WAAW,EAAE;GAAE,aAAa;GAAM,OAAO,oBAAoB,QAAQ,EAAE;GAAI;WAC5E;AACR,MAAI,IACF,KAAI;AACF,UAAO,KAAK;IAAE,WAAW;IAAM,OAAO;IAAM,CAAC;UACvC;;;;AAQd,SAAS,aAAa,MAAiB,OAAgC;AACrE,QAAO,KAAK,UAAU,MAAM,MAAM,EAAE,SAAS,MAAM,IAAI;;;AAIzD,SAAS,oBAAoB,MAAkC;AAC7D,QAAO,KAAK,cACR,KAAK,UAAU,MAAM,MAAM,EAAE,SAAS,KAAK,YAAY,IAAI,OAC3D;;;AAIN,SAAgB,kBAAmC;AACjD,QAAO,oBAAoB,YAAY,CAAC;;;;;;;AAQ1C,SAAgB,SAAS,MAA0B,UAA0B;CAC3E,MAAM,OAAO,QAAQ,IAAI,YAAY,IAAI;AACzC,KAAI,MAAM,GAAG;EACX,MAAM,SAAS,KAAM,MAAM,GAAG,IAAI;AAClC,MAAI,CAAC,OAAO,MAAM,WAAW,KAAM,MAAM,MAAM,EAAE,CAAC,CAAC,CAAE,QAAO,GAAG,OAAO,GAAG;;AAE3E,QAAO,iBAAiB;;;AAI1B,SAAgB,oBACd,QACA,UACA,OAAwB,YAClB;CACN,MAAM,UAAmC;EACvC,MAAM;EACN,MAAM;EACN,eAAe,SAAS,SAAS,gBAAgB,SAAS;EAC1D,yBAAyB;EAC1B;AAGD,KAAI,QAAQ,QAAQ,aAAa,MAAM,EAAE,OAAO,KAAK,CACnD,SAAQ,iCAAiC,OAAO;CAElD,MAAM,OAAO,oBAAoB;CACjC,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACzD,MAAM,MAAM,GAAG,KAAK;AACpB,eAAc,KAAK,KAAK,UAAU,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO;AACnF,YAAW,KAAK,KAAK;;AAGvB,IAAI,cAAc;;;;;;;;;;AAWlB,eAAsB,cACpB,UACA,OAAwB,YACP;CACjB,MAAM,QAAQ,MAAM;CACpB,MAAM,SAAS,oBAAoB,MAAM;CACzC,MAAM,OAAO,oBAAoB;CACjC,MAAM,UAAU,WAAW,KAAK;AAChC,KAAI,CAAC,QAAS,qBAAoB,QAAQ,gBAAgB,MAAM;AAChE,KAAI,CAAC,QAAQ;AACX,MAAI,CAAC,aAAa;AAChB,iBAAc;GACd,MAAM,SAAS,MAAM,SAAS;GAC9B,MAAM,OAAO,UACT,WAAW,KAAK,UAChB,SAAS,KAAK,sBAAsB,SAAS;AACjD,WAAQ,OAAO,MACb,yDAAyD,OAAO,MAAM,KAAK,IAC5E;;AAEH,MAAI,MAAM,MAAO,QAAO;;AAE1B,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;AAE3B,MAAI,aAAa,MAAM,EAAE,aAAa,CAAE,QAAO;AAC/C,QAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAI,CAAC;;AAE9C,SAAQ,OAAO,MAAM,4BAA4B,aAAa,wCAAwC;AACtG,QAAO;;AAaT,SAAS,aAAa,MAA2B;AAC/C,KAAI;EACF,MAAM,MAAM,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAClD,SAAO,MAAM,QAAQ,IAAI,GAAI,MAAsB,EAAE;SAC/C;AACN,SAAO,EAAE;;;AAIb,SAAS,aAAa,MAAc,KAAwB;CAC1D,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACzD,MAAM,MAAM,GAAG,KAAK;AACpB,eAAc,KAAK,KAAK,UAAU,KAAK,MAAM,EAAE,EAAE,OAAO;AACxD,YAAW,KAAK,KAAK;;;;;;;;;;AAevB,SAAgB,cAAc,KAAa,cAA8B;AACvE,QAAO,GAAG,QAAQ,SAAS,GAAG,iBAAiB,CAAC,iBAAiB,IAAI,eAAe;;;AAItF,SAAS,kBAA0B;CACjC,MAAM,QAAQ,QAAQ,KAAK,MAAM;AACjC,KAAI,MAAM,SAAS,MAAM,CAAE,QAAO;AAClC,QAAO;;;AAIT,eAAsB,kBACpB,QACA,QACA,KACA,MACiB;AACjB,KAAI,eAAe,IAAI,CAAE,QAAO,YAAY,IAAI;CAChD,MAAM,MAAM,UAAU,KAAK;CAC3B,MAAM,UAAU,KAAK,SAAS,OAAO,EAAE,GAAG,SAAS,KAAK,CAAC,OAAO;CAChE,MAAM,MAAM,aAAa,QAAQ;CACjC,MAAM,UAAU,MAAM,cAAc,OAAO,KAAK,SAAS;CACzD,MAAM,MAAM,cAAc,KAAK,OAAO,KAAK,aAAa;CAGxD,MAAM,IAAI,MAAM,UAAU,qBAAqB;EAAC;EADlC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,EAAE,QAAQ,CAAC,KAAK,IAAI;EACJ;EAAK;EAAQ,CAAC;CAC1E,MAAM,MAAM,EAAE,OAAO,MAAM;AAC3B,KAAI,QAAQ,cAAc,QAAQ,aAChC,OAAM,IAAI,MACR,QAAQ,aACJ,qEACA,yCACL;CAEH,MAAM,MAAM,IAAI,QAAQ,IAAI;AAC5B,KAAI,MAAM,EACR,OAAM,IAAI,MACR,uCAAuC,EAAE,UAAU,KAAK,MAAM,GAAG,IAAI,GACtE;CAEH,MAAM,OAAO,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,QAAQ,CAAC;CAClE,MAAM,SAAS,IAAI,QAAQ,MAAM,KAAK,IAAI,EAAE,QAAQ,CAAC;AACrD,QAAO,KAAK;EACV,SAAS,IAAI,MAAM,MAAM,EAAE;EAC3B,QAAQ;EACR,yBAAQ,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,KAAK,IAAI,CAAC,MAAM,GAAG,GAAG;EAChE,CAAC;AACF,cAAa,SAAS,OAAO;AAC7B,QAAO,mBAAmB;;;;;;;AAQ5B,eAAe,iBAAiB,MAA+B;CAC7D,MAAM,MAAM,UAAU,KAAK;AAC3B,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI;EACF,MAAM,IAAI,MAAM,UAAU,sBAAsB,CAAC,IAAI,CAAC;EACtD,MAAM,MAAM,EAAE,OAAO,MAAM;AAC3B,MAAI,OAAO,QAAQ,cAAc,QAAQ,aAAc,QAAO,kBAAkB;AAKhF,SAAO,mBAHL,QAAQ,aAAa,6BACnB,QAAQ,eAAe,wBACtB,EAAE,OAAO,MAAM,IAAI,aAAa,MAAM,GAAG,IAAI,CACpB;UACvB,GAAG;AACV,SAAO,8BAA8B,OAAQ,EAAY,WAAW,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC;;;;;;;;;AAUzF,eAAsB,mBAAmB,KAAa,UAAkB,MAA+B;CACrG,MAAM,QAAQ,CAAC,eAAe,IAAI,GAAG,YAAY,IAAI,SAAS,eAAe,MAAM;AACnF,OAAM,KAAK,MAAM,iBAAiB,KAAK,CAAC;CACxC,MAAM,OAAO,oBAAoB;AACjC,KAAI,WAAW,KAAK,EAAE;EACpB,IAAI,OAAO;AACX,MAAI;AAIF,UAHe,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC,CAGvC,WAAW,KAAK,kBAAkB;UAC1C;AAGR,QAAM,KAAK,iBAAiB,KAAK,WAAW;AAC5C,QAAM,KAAK,iBAAiB,OAAO;QAC9B;EACL,MAAM,SAAS,iBAAiB;AAChC,QAAM,KAAK,iBAAiB,KAAK,YAAY;AAC7C,QAAM,KAAK,+BAA+B,SAAS,SAAS,gBAAgB,SAAS,GAAG;;AAE1F,QAAO,MAAM,KAAK,KAAK;;;;;;;AAQzB,eAAsB,eACpB,QACA,SACA,MACA,WACiB;CAEjB,MAAM,MAAM,UAAU,KAAK;CAE3B,MAAM,OADI,MAAM,UAAU,iBAAiB,CAAC,IAAI,CAAC,EACnC,OAAO,MAAM;AAC3B,KAAI,QAAQ,WAAY,OAAM,IAAI,MAAM,mEAAmE;AAC3G,KAAI,QAAQ,aAAc,OAAM,IAAI,MAAM,yCAAyC;AAGnF,KADgB,CAAC,GADD,IAAI,IAAI,IAAI,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,CAAC,CAChD,CAAC,QAAQ,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC,CACnD,SAAS,EAAG,QAAO;AAC/B,KAAI,UAAW,QAAO;CAGtB,MAAM,IAAI,MAAM,UAAU,cAAc,CAAC,KAD7B,GAAG,QAAQ,SAAS,GAAG,iBAAiB,CAAC,gBACH,CAAC;CACnD,MAAM,OAAO,EAAE,OAAO,MAAM;AAC5B,KAAI,SAAS,SAAU,QAAO;AAC9B,KAAI,SAAS,WAAY,OAAM,IAAI,MAAM,mEAAmE;AAC5G,OAAM,IAAI,MAAM,uCAAuC,EAAE,UAAU,MAAM,MAAM,GAAG,IAAI,GAAG;;;;;;ACpmB3F,MAAa,gBAAgB;;AAG7B,SAAgB,cAAc,MAAyB,QAAQ,KAAoB;CACjF,MAAM,KAAK,IAAI;AACf,QAAO,OAAO,OAAO,YAAY,GAAG,MAAM,GAAG,GAAG,MAAM,GAAG;;;;;;;;;;AAW3D,SAAgB,YACd,UACA,IACA,MACQ;CACR,MAAM,OAAO,IAAI,IAAI,SAAS,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;CACpD,IAAI,QAAQ;CACZ,IAAI,MAAM,KAAK,IAAI,GAAG;CACtB,MAAM,OAAO,IAAI,IAAY,CAAC,GAAG,CAAC;AAClC,QAAO,KAAK,UAAU,CAAC,KAAK,IAAI,IAAI,OAAO,EAAE;AAC3C,OAAK,IAAI,IAAI,OAAO;EACpB,MAAM,OAAO,KAAK,IAAI,IAAI,OAAO;AACjC,MAAI,CAAC,KAAM;AACX,MAAI,EAAE,MAAM,cAAc,KAAK,WAAW,QAAS,UAAS;AAC5D,QAAM;;AAER,QAAO;;;AAIT,SAAgB,gBAAgB,UAA0B,QAAgC;AACxF,QAAO,SAAS,QAAQ,MAAM,EAAE,WAAW,UAAU,EAAE,UAAU,aAAa,MAAM,EAAE,IAAI,CAAC;;;;;;;AAQ7F,SAAgB,mBACd,QACA,QACA,MACA,WAA2B,aAAa,OAAO,EACzC;AACN,KAAI,CAAC,SAAS,MAAM,MAAM,EAAE,OAAO,OAAO,CAAE;CAC5C,MAAM,QAAQ,YAAY,UAAU,OAAO;AAC3C,KAAI,QAAQ,IAAI,KAAK,SACnB,OAAM,IAAI,MACR,gBAAgB,OAAO,iBAAiB,MAAM,gCAChB,KAAK,SAAS,gIAE7C;CAEH,MAAM,OAAO,gBAAgB,UAAU,OAAO;AAC9C,KAAI,KAAK,UAAU,KAAK,YACtB,OAAM,IAAI,MACR,gBAAgB,OAAO,eAAe,KAAK,OAAO,wBAC5C,KAAK,KAAK,MAAM,EAAE,GAAG,CAAC,KAAK,KAAK,CAAC,oCAAoC,KAAK,YAAY,mEAE7F;;;;;;AAQL,SAAgB,aAAa,UAA8B,MAAyB,QAAQ,KAAoB;AAC9G,QAAO,YAAY,cAAc,IAAI;;;AAIvC,SAAgB,WAAW,QAAgB,IAAqB;AAC9D,QAAO,WAAW,QAAQ,GAAG,KAAK;;;;;;;;;;;;;;;;ACtFpC,SAAgB,mBAAmB,QAAgB,IAAoB;AACrE,QAAO,KAAK,QAAQ,GAAG,GAAG,OAAO;;;;;;;AAQnC,SAAgB,qBACd,QACA,IACA,QAC2B;CAC3B,MAAM,OAAO,mBAAmB,QAAQ,GAAG;AAC3C,KAAI;AACF,MAAI,WAAW,KAAK,CAAE,YAAW,KAAK;SAChC;CAGR,MAAM,SAAS,cAAc,WAAmB;EAC9C,IAAI,MAAM;AACV,SAAO,GAAG,SAAS,UAAkB;AACnC,UAAO,MAAM,SAAS,OAAO;GAC7B,IAAI;AACJ,WAAQ,KAAK,IAAI,QAAQ,KAAK,KAAK,GAAG;IACpC,MAAM,OAAO,IAAI,MAAM,GAAG,GAAG,CAAC,QAAQ,OAAO,GAAG;AAChD,UAAM,IAAI,MAAM,KAAK,EAAE;AACvB,QAAI,KAAK,MAAM,CAAE,QAAO,KAAK;AAC7B,WAAO,MAAM,OAAO;;IAEtB;GACF;AACF,QAAO,OAAO,KAAK;AACnB,QAAO,GAAG,eAAe;AACvB,MAAI;AACF,OAAI,WAAW,KAAK,CAAE,YAAW,KAAK;UAChC;GAGR;AACF,QAAO;;;;;;AAOT,SAAgB,YAAY,QAAgB,IAAY,MAAc,YAAY,KAAuB;CACvG,MAAM,SAAS,WAAW,QAAQ,GAAG;AACrC,KAAI,CAAC,OACH,QAAO,QAAQ,uBAAO,IAAI,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAE7D,KAAI,OAAO,UAAU,aAAa,CAAC,MAAM,OAAO,IAAI,CAClD,QAAO,QAAQ,uBACb,IAAI,MACF,UAAU,GAAG,0BAA0B,OAAO,MAAM,kDACH,GAAG,WACrD,CACF;CAEH,MAAM,OAAO,mBAAmB,QAAQ,GAAG;AAC3C,KAAI,CAAC,WAAW,KAAK,CACnB,QAAO,QAAQ,uBACb,IAAI,MAAM,UAAU,GAAG,2BAA2B,KAAK,qCAAqC,CAC7F;AAEH,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,OAAO,QAAQ,KAAK;EAC1B,MAAM,QAAQ,MAAa;AACzB,QAAK,SAAS;AACd,0BAAO,IAAI,MAAM,yBAAyB,GAAG,IAAI,EAAE,UAAU,CAAC;;AAEhE,OAAK,WAAW,iBAAiB,qBAAK,IAAI,MAAM,UAAU,CAAC,CAAC;AAC5D,OAAK,KAAK,UAAU,MAAa,KAAK,EAAE,CAAC;AACzC,OAAK,KAAK,iBAAiB;AACzB,QAAK,MAAM,KAAK,QAAQ,OAAO,IAAI,GAAG,KAAK;IAC3C;AACF,OAAK,KAAK,cAAc;AACtB,QAAK,KAAK;AACV,WAAQ,KAAK;IACb;GACF;;;;;;;;;;;;;;;;;ACjFJ,MAAa,gBAAgB;CAAC;CAAY;CAAU;CAAY;CAAU;;AAkB1E,SAAgB,UAAU,QAAgB,IAAoB;AAC5D,QAAO,KAAK,QAAQ,GAAG,GAAG,cAAc;;AAG1C,SAAgB,cAAc,GAA8B;AAC1D,QAAO,OAAO,MAAM,YAAa,cAAoC,SAAS,EAAE;;;;;;;AAQlF,SAAgB,aACd,GACA,SAAgD,EAAE,EACzC;AACT,KAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,MAAM,QAAQ,EAAE,CACzD,OAAM,IAAI,MAAM,wCAAwC;CAE1D,MAAM,IAAI;CACV,MAAM,OAAO,OAAO,EAAE,SAAS,YAAY,EAAE,OAAO,EAAE,OAAO,OAAO;CACpE,MAAM,KAAK,OAAO,EAAE,OAAO,YAAY,EAAE,KAAK,EAAE,KAAK,OAAO;AAC5D,KAAI,CAAC,KAAM,OAAM,IAAI,MAAM,+CAA+C;AAC1E,KAAI,CAAC,GAAI,OAAM,IAAI,MAAM,4CAA4C;AACrE,KAAI,CAAC,cAAc,EAAE,KAAK,CACxB,OAAM,IAAI,MAAM,kCAAkC,cAAc,KAAK,KAAK,GAAG;CAE/E,MAAM,OAAO,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AACnD,KAAI,CAAC,KAAK,MAAM,CAAE,OAAM,IAAI,MAAM,mCAAmC;CACrE,MAAM,OACJ,OAAO,EAAE,SAAS,YAAY,EAAE,SAAS,QAAQ,CAAC,MAAM,QAAQ,EAAE,KAAK,GAClE,EAAE,OACH;AACN,QAAO;EAAE;EAAM;EAAI,MAAM,EAAE;EAAM;EAAM,GAAI,OAAO,EAAE,MAAM,GAAG,EAAE;EAAG;;;AAIpE,SAAgB,cAAc,QAAgB,GAAY,sBAAY,IAAI,MAAM,EAAQ;CACtF,MAAM,OAAO,UAAU,QAAQ,EAAE,GAAG;CACpC,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AACzD,gBAAe,MAAM,KAAK,UAAU;EAAE,GAAG;EAAG,KAAK,IAAI,aAAa;EAAE,CAAC,GAAG,MAAM,OAAO;;;AAIvF,SAAgB,UAAU,QAAgB,IAAuB;CAC/D,MAAM,OAAO,UAAU,QAAQ,GAAG;AAClC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO,EAAE;CAChC,MAAM,MAAiB,EAAE;AACzB,MAAK,MAAM,QAAQ,aAAa,MAAM,OAAO,CAAC,MAAM,KAAK,EAAE;AACzD,MAAI,CAAC,KAAK,MAAM,CAAE;AAClB,MAAI;AACF,OAAI,KAAK,KAAK,MAAM,KAAK,CAAY;UAC/B;;AAIV,QAAO;;;AAIT,SAAgB,eAAe,GAAoD;AACjF,QAAO,iBAAiB,EAAE,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,QAAQ,IAAI,CAAC,MAAM;;;AAInF,SAAgB,iBAAiB,MAAuB;AACtD,QAAO,IAAI,OAAO,gCAAgC,cAAc,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,KAAK;;;;;;;;;AAe9F,eAAsB,eACpB,QACA,GACA,OAAoB,EAAE,EACtB,sBAAY,IAAI,MAAM,EACJ;AAClB,eAAc,QAAQ,GAAG,IAAI;CAC7B,MAAM,MAAM,KAAK,SAAS,IAAY,SAAiB,YAAY,QAAQ,IAAI,KAAK;CACpF,MAAM,SAAS,WAAW,QAAQ,EAAE,GAAG;AACvC,KAAI,UAAU,OAAO,UAAU,aAAa,MAAM,OAAO,IAAI,CAC3D,KAAI;AACF,QAAM,IAAI,EAAE,IAAI,eAAe,EAAE,CAAC;SAC5B;AAIV,QAAO;;;;;;;;AAST,eAAsB,kBACpB,QACA,KACA,SACA,OAAoB,EAAE,EACJ;CAClB,MAAM,OAAO,IAAI;AACjB,KAAI,CAAC,KACH,OAAM,IAAI,MACR,0IAED;CAEH,MAAM,KAAK,WAAW,QAAQ,KAAK;AACnC,KAAI,CAAC,GAAI,OAAM,IAAI,MAAM,8BAA8B,KAAK,4BAA4B;AACxF,KAAI,CAAC,GAAG,UAAU,CAAC,WAAW,QAAQ,GAAG,OAAO,CAC9C,OAAM,IAAI,MACR,UAAU,KAAK,gDACD,GAAG,UAAU,SAAS,yCACrC;AAGH,QAAO,eAAe,QADZ,aAAa,SAAS;EAAE,MAAM;EAAM,IAAI,GAAG;EAAQ,CAAC,EAC7B,KAAK;;;;;;;;;;;;;;;;;;;;AC/IxC,SAAgB,aAAa,QAAwB;AACnD,QAAO,KAAK,QAAQ,YAAY;;AAGlC,SAAgB,eAAe,IAAoB;AACjD,QAAO,UAAU;;AAGnB,SAAgB,aAAa,QAAgB,IAAoB;AAC/D,QAAO,KAAK,aAAa,OAAO,EAAE,GAAG;;;AAIvC,SAAgB,IAAI,KAAa,MAAwB;AACvD,KAAI;AACF,SAAO,aAAa,OAAO;GAAC;GAAM;GAAK,GAAG;GAAK,EAAE;GAC/C,UAAU;GACV,SAAS;GACT,OAAO;IAAC;IAAU;IAAQ;IAAO;GAClC,CAAC,CAAC,MAAM;UACF,GAAG;EACV,MAAM,MAAM;EACZ,MAAM,OACH,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS,IAAI,QAAQ,SAAS,OAAO,KAC3E,IAAI,WACJ,OAAO,EAAE;AACX,QAAM,IAAI,MAAM,OAAO,KAAK,KAAK,IAAI,CAAC,MAAM,IAAI,IAAI,IAAI,MAAM,GAAG;;;;AAKrE,SAAgB,UAAU,KAAsB;AAC9C,KAAI;AACF,SAAO,IAAI,KAAK,CAAC,aAAa,YAAY,CAAC,KAAK;SAC1C;AACN,SAAO;;;;;;;;AASX,SAAgB,oBAAoB,QAAyB;CAC3D,MAAM,IAAI,OAAO,MAAM;AACvB,KAAI,CAAC,EAAG,QAAO;AACf,KAAI,qGAAqG,KAAK,EAAE,CAC9G,QAAO;AAET,QAAO,qIAAqI,KAC1I,EACD;;;AAIH,MAAa,mBAAmB;CAAC;CAAa;CAAW;CAAO;;AAMhE,SAAgB,eACd,MACA,MACS;AACT,KAAI,SAAS,OAAW,QAAO;AAC/B,KAAI,CAAC,KAAK,aAAa,CAAE,iBAAuC,SAAS,KAAK,UAAU,CACtF,QAAO;AAET,KAAI,CAAC,UAAU,KAAK,IAAI,CAAE,QAAO;AACjC,QAAO,CAAC,oBAAoB,KAAK,UAAU,GAAG;;;;;;;AAchD,SAAgB,YAAY,QAAgB,IAAY,KAA2B;CACjF,MAAM,QAAQ,qBAAqB,OAAO;AAC1C,KAAI,MAAM,OACR,cAAa,WAAW,OAAO,EAAE,eAAe;EAC9C;EACA,MAAM,6BAA6B,MAAM,KAAK,KAAK;EACpD,CAAC;CAEJ,MAAM,MAAM,aAAa,QAAQ,GAAG;CACpC,MAAM,SAAS,eAAe,GAAG;CACjC,MAAM,OAAO,IAAI,KAAK,CAAC,aAAa,OAAO,CAAC;AAC5C,KAAI,KAAK;EAAC;EAAY;EAAO;EAAK;EAAM;EAAO,CAAC;AAChD,QAAO;EAAE;EAAK;EAAQ;EAAM;;;;;;;;;;AAW9B,SAAgB,qBAAqB,QAAgB,YAAY,IAAc;CAC7E,MAAM,OAAO,aAAa,OAAO;AACjC,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO,EAAE;CAChC,MAAM,QAAkB,EAAE;AAC1B,MAAK,MAAM,OAAO,YAAY,MAAM,EAAE,eAAe,MAAM,CAAC,EAAE;AAC5D,MAAI,CAAC,IAAI,aAAa,CAAE;EACxB,MAAM,KAAK,IAAI;EACf,MAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,MAAI,WAAW,WAAW,QAAQ,GAAG,CAAC,CAAE;AACxC,MAAI;AAEF,QADgB,KAAK,KAAK,GAAG,SAAS,IAAI,CAAC,WAAW,MACzC,UAAW;UAClB;EAGR,IAAI,SAAwB;AAC5B,MAAI;GACF,MAAM,MAAM,IAAI,KAAK,CAAC,aAAa,mBAAmB,CAAC;AACvD,YAAS,WAAW,IAAI,GAAG,MAAM,QAAQ,KAAK,IAAI;UAC5C;AACN,YAAS;;AAEX,iBAAe,UAAU,KAAK,KAAK,KAAK;AACxC,MAAI,OACF,KAAI;AACF,OAAI,QAAQ,CAAC,YAAY,QAAQ,CAAC;AAClC,OAAI,QAAQ;IAAC;IAAU;IAAM,eAAe,GAAG;IAAC,CAAC;UAC3C;AAIV,QAAM,KAAK,GAAG;;AAEhB,QAAO;;;AAIT,SAAgB,aAAa,KAAa,MAAsB;AAC9D,KAAI;AACF,SAAO,SAAS,IAAI,KAAK;GAAC;GAAY;GAAW,GAAG,KAAK;GAAQ,CAAC,EAAE,GAAG,IAAI;SACrE;AACN,SAAO;;;;;;;AAQX,SAAgB,eACd,QACA,QACA,MACA,IACc;CACd,MAAM,IAAI,EAAE,GAAG,QAAQ;AACvB,KAAI,IAAI;AACN,IAAE,SAAS,KAAK;AAChB,IAAE,UAAU,aAAa,KAAK,KAAK,KAAK,KAAK;AAC7C,IAAE,cAAc,KAAK;AACrB,IAAE,eAAe,KAAK;QACjB;AAEL,iBAAe,EAAE,KAAK,KAAK,KAAK,KAAK;AACrC,MAAI;AACF,OAAI,EAAE,KAAK;IAAC;IAAU;IAAM,KAAK;IAAO,CAAC;UACnC;AAGR,IAAE,SAAS;AACX,IAAE,cAAc;AAChB,IAAE,eAAe;AACjB,IAAE,UAAU;;AAEd,YAAW,QAAQ,EAAE;AACrB,QAAO;;;AAIT,SAAS,eAAe,KAAa,KAAa,OAAsB;AACtE,KAAI;AACF,MAAI,KAAK;GAAC;GAAY;GAAU,GAAI,QAAQ,CAAC,UAAU,GAAG,EAAE;GAAG;GAAI,CAAC;AACpE;SACM;AAGR,KAAI,WAAW,IAAI,CACjB,KAAI;AACF,SAAO,KAAK;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AAC7C,MAAI,KAAK,CAAC,YAAY,QAAQ,CAAC;SACzB;;;;;;;;AAYZ,SAAgB,iBAAiB,OAAyB;CACxD,MAAM,UAAU,IAAI,OAAO;EAAC;EAAQ;EAAe;EAAO,CAAC,CAAC,MAAM,KAAK,CAAC,OAAO,QAAQ;CACvF,MAAM,YAAY,IAAI,OAAO;EAAC;EAAY;EAAY;EAAqB,CAAC,CACzE,MAAM,KAAK,CACX,OAAO,QAAQ;AAClB,QAAO,CAAC,GAAG,SAAS,GAAG,UAAU;;;;;;;;;;AAWnC,SAAgB,mBAAmB,OAAe,OAAyB;CACzE,MAAM,QAAQ,iBAAiB,MAAM;AACrC,KAAI,CAAC,MAAM,OAAQ,QAAO,EAAE;AAC5B,KAAI;AACF,MAAI,OAAO,CAAC,OAAO,KAAK,CAAC;AACzB,MAAI,OAAO;GAAC;GAAU;GAAM,aAAa;GAAQ,CAAC;UAC3C,GAAG;AACV,QAAM,IAAI,MACR,6CAA6C,MAAM,KAAM,EAAY,QAAQ,kEAE9E;;AAEH,QAAO;;;;;;;AAQT,SAAS,WAAW,KAAuB;CAQzC,MAAM,SALM,aAAa,OAAO;EAAC;EAAM;EAAK;EAAU;EAAe;EAAK,EAAE;EAC1E,UAAU;EACV,SAAS;EACT,OAAO;GAAC;GAAU;GAAQ;GAAO;EAClC,CAAC,CACiB,MAAM,KAAK;CAC9B,MAAM,SAAS,MAAe,EAAE,WAAW,KAAI,IAAI,EAAE,SAAS,KAAI,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG;CACtF,MAAM,MAAgB,EAAE;AACxB,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,IAAI,OAAO;AACjB,MAAI,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,OAAO,EAAE,KAAK,IAAK;AAC/C,MAAI,KAAK,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;EAC3B,MAAM,KAAK,EAAE,MAAM,GAAG,EAAE;AACxB,MAAI,GAAG,SAAS,IAAI,IAAI,GAAG,SAAS,IAAI,EAAE;GACxC,MAAM,OAAO,OAAO,IAAI;AACxB,OAAI,QAAQ,KAAK,OAAO,EAAE,KAAK,KAAK;AAClC,QAAI,KAAK,MAAM,KAAK,CAAC;AACrB,SAAK;;;;AAIX,QAAO;;;;;;;AAQT,SAAS,qBACP,KACA,UACA,IACA,QACM;CACN,MAAM,QAAQ,IAAI,IAAI,WAAW,IAAI,CAAC;CACtC,MAAM,UAAU,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC,CAAC,QAAQ,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM;AACzE,KAAI,CAAC,QAAQ,OAAQ;AACrB,OAAM,IAAI,MACR,UAAU,GAAG,iBAAiB,IAAI,oCAAoC,OAAO,YACxE,QAAQ,KAAK,KAAK,CAAC,wEAAwE,GAAG,6CAEpG;;;AAIH,SAAgB,oBAAoB,OAAe,IAAkB;CACnE,MAAM,WAAW,iBAAiB,MAAM;AACxC,KAAI,SAAS,OACX,OAAM,IAAI,MACR,UAAU,GAAG,iBAAiB,MAAM,oCAC9B,SAAS,KAAK,KAAK,CAAC,wEAC3B;;;;;;;;;;;AAaL,SAAgB,YAAY,QAAgB,IAAoB;CAC9D,MAAM,KAAK,eAAe,QAAQ,GAAG;AACrC,KAAI,GAAG,OAAQ,QAAO,UAAU,GAAG,WAAW,GAAG,OAAO;CAExD,MAAM,WAAW,WAAW,GAAG,YAAa,GACxC,mBAAmB,GAAG,aAAc,GAAG,SAAS,UAAU,GAC1D,EAAE;AAEN,MADiB,SAAS,IAAI,GAAG,KAAK;EAAC;EAAY;EAAW,SAAS,GAAG;EAAS,CAAC,EAAE,GAAG,IAAI,MAC7E,EACd,OAAM,IAAI,MACR,UAAU,GAAG,WAAW,GAAG,OAAO,yCAChB,GAAG,YAAY,gIACoC,KACtE;CAEH,MAAM,YAAY,IAAI,GAAG,KAAK;EAAC;EAAc;EAAQ,GAAG;EAAQ,CAAC;CACjE,MAAM,gBAAgB,IAAI,GAAG,KAAK;EAAC;EAAQ;EAAe;EAAW,GAAG;EAAQ,CAAC,CAC9E,MAAM,KAAK,CACX,OAAO,QAAQ;AAClB,sBAAqB,GAAG,KAAK,eAAe,IAAI,GAAG,OAAQ;AAC3D,KAAI;AACF,MAAI,GAAG,KAAK;GAAC;GAAS;GAAW,GAAG;GAAS;GAAM,gBAAgB,GAAG,IAAI,GAAG,MAAM;GAAG,CAAC;UAChF,GAAG;AACV,QAAM,IAAI,MACR,UAAU,GAAG,6BAA6B,GAAG,OAAO,KAAM,EAAY,QAAQ,iBAC5D,GAAG,YAAY,oDAClC;;AAEH,KAAI,WAAW,GAAG,YAAa,CAAE,qBAAoB,GAAG,aAAc,GAAG;AACzE,gBAAe,GAAG,KAAK,GAAG,aAAc,MAAM;CAC9C,IAAI,aAAa;AACjB,KAAI;AACF,MAAI,GAAG,KAAK;GAAC;GAAU;GAAM,GAAG;GAAQ,CAAC;SACnC;AACN,eAAa;;AAGf,YAAW,QADD;EAAE,GAAG;EAAI,QAAQ;EAAM,CACZ;CACrB,MAAM,OAAO,UAAU,GAAG,OAAO,QAAQ,GAAG,IAAI,oBAAoB,aAAa,qBAAqB,gCAAgC;AACtI,QAAO,SAAS,SACZ,GAAG,KAAK,aAAa,SAAS,OAAO,0BAA0B,SAAS,KAAK,KAAK,KAClF;;;AAIN,SAAgB,cAAc,QAAgB,IAAoB;CAChE,MAAM,KAAK,eAAe,QAAQ,GAAG;AACrC,gBAAe,GAAG,KAAK,GAAG,aAAc,KAAK;CAC7C,IAAI,aAAa;AACjB,KAAI;AACF,MAAI,GAAG,KAAK;GAAC;GAAU;GAAM,GAAG;GAAQ,CAAC;AACzC,eAAa;SACP;AACN,eAAa;;AAGf,YAAW,QADD;EAAE,GAAG;EAAI,QAAQ;EAAM,aAAa;EAAM,cAAc;EAAM,SAAS;EAAM,QAAQ;EAAO,CACjF;AACrB,QAAO,oBAAoB,GAAG,oBAAoB,aAAa,YAAY,GAAG,OAAO,YAAY;;AAGnG,SAAS,eAAe,QAAgB,IAAmF;CACzH,MAAM,KAAK,WAAW,QAAQ,GAAG;AACjC,KAAI,CAAC,GAAI,OAAM,IAAI,MAAM,oBAAoB,GAAG,GAAG;AACnD,KAAI,CAAC,GAAG,UAAU,CAAC,GAAG,YACpB,OAAM,IAAI,MACR,UAAU,GAAG,6BAA6B,GAAG,SAAS,aAAa,QAAQ,sDAE5E;AAEH,QAAO;;;;;;;AAQT,SAAgB,qBAAqB,IAAY,QAAgB,KAAqB;AACpF,QAAO;EACL;EACA,KAAK,IAAI,aAAa,OAAO,cAAc,GAAG;EAC9C;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;;;;;;;;;;;;;ACxZd,MAAa,gBAAgB;;AAG7B,MAAa,yBAAyB;CACpC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,gDAAgD,cAAc;CAC9D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,KAAK,KAAK;;;;;;;;AAiBZ,SAAgB,kBAAkB,MAAmC;CACnE,MAAM,WAAW,QAAQ,IAAI,MAAM;AACnC,KAAI,CAAC,QAAS,QAAO;CACrB,MAAM,aAAuB,EAAE;CAC/B,MAAM,QAAQ,QAAQ,MAAM,sCAAsC;AAClE,KAAI,MAAO,YAAW,KAAK,MAAM,GAAG;AACpC,KAAI,QAAQ,WAAW,IAAI,CAEzB,YAAW,KAAK,QAAQ;CAG1B,MAAM,QAAQ,QAAQ,QAAQ,IAAI;CAClC,MAAM,OAAO,QAAQ,YAAY,IAAI;AACrC,KAAI,SAAS,KAAK,OAAO,MAAO,YAAW,KAAK,QAAQ,MAAM,OAAO,OAAO,EAAE,CAAC;AAC/E,MAAK,MAAM,QAAQ,WACjB,KAAI;EACF,MAAM,IAAI,KAAK,MAAM,KAAK;AAC1B,MAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,EAAE,IAAI,gBAAgB,EAAE,CAChF,QAAO;SAEH;AAIV,QAAO;;;AAIT,SAAS,gBAAgB,GAAoB;CAC3C,MAAM,IAAI;AACV,QAAO,MAAM,QAAQ,EAAE,QAAQ,IAAI,MAAM,QAAQ,EAAE,OAAO,IAAI,OAAO,EAAE,UAAU;;;;;;AAOnF,SAAgB,aAAa,GAAU,QAAgB,GAAiB,MAAM,IAAc;CAC1F,MAAM,MAAgB,EAAE;CACxB,MAAM,OAAO,QAA+B,MAAM,EAAE,EAAE,SAAS;AAC/D,KAAI,IAAI,EAAE,QAAQ,EAAE;AAClB,MAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,UAAU,GAAG;AAC5C,OAAK,MAAM,MAAM,EAAE,QACjB,KAAI,KAAK,GAAG,OAAO,IAAI,SAAS,GAAG,QAAQ,KAAK,IAAI,CAAC,KAAK,UAAU,GAAG,WAAW,IAAI,GAAG,GAAG;;AAGhG,KAAI,IAAI,EAAE,OAAO,EAAE;AACjB,MAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,SAAS,GAAG;AAC3C,OAAK,MAAM,MAAM,EAAE,QAAS;GAC1B,MAAM,OAAO,GAAG,OAAO,QAAQ,EAAE,OAAO,IAAI,GAAG,EAAE,SAAS,IAAI;GAC9D,MAAM,SAAS,GAAG,SAAS,EAAE,OAAO,MAAM,UAAU,GAAG,QAAQ,GAAG,GAAG,GAAG;AACxE,OAAI,KAAK,GAAG,OAAO,IAAI,KAAK,GAAG,GAAG,QAAQ,MAAM,SAAS;;;AAG7D,KAAI,IAAI,EAAE,SAAS,EAAE;AACnB,MAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,WAAW,GAAG;AAC7C,OAAK,MAAM,OAAO,EAAE,SAAW,KAAI,KAAK,GAAG,OAAO,IAAI,EAAE,OAAO,UAAU,KAAK,IAAI,CAAC,GAAG;;AAExF,KAAI,IAAI,EAAE,KAAK,EAAE;AACf,MAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,OAAO,GAAG;AACzC,OAAK,MAAM,KAAK,EAAE,KAAO,KAAI,KAAK,GAAG,OAAO,IAAI,EAAE,UAAU,UAAU,GAAG,IAAI,CAAC,GAAG;;AAEnF,KAAI,EAAE,MAAO,KAAI,KAAK,GAAG,SAAS,EAAE,QAAQ,QAAQ,CAAC,IAAI,UAAU,EAAE,OAAO,IAAI,GAAG;AACnF,QAAO;;;AAIT,SAAS,SAAS,GAAW,KAAqB;AAChD,KAAI,CAAC,IAAK,QAAO;CACjB,MAAM,IAAI,SAAS,KAAK,EAAE;AAC1B,QAAO,KAAK,CAAC,EAAE,WAAW,KAAK,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;AC5GxC,MAAa,cAAc,KAAK,SAAS,EAAE,eAAe;AAE1D,SAAgB,eAAe,aAAa,aAAsC;AAChF,KAAI;AACF,MAAI,CAAC,WAAW,WAAW,CAAE,QAAO,EAAE;EAEtC,MAAM,UADS,KAAK,MAAM,aAAa,YAAY,OAAO,CAAC,CACpC;AACvB,MAAI,OAAO,YAAY,YAAY,YAAY,QAAQ,MAAM,QAAQ,QAAQ,CAAE,QAAO,EAAE;AACxF,SAAO;SACD;AAEN,SAAO,EAAE;;;;;;;AAQb,SAAgB,eACd,OACA,QACA,aAAa,aACH;CACV,MAAM,YAAY,eAAe,WAAW;CAC5C,MAAM,MAAgB,EAAE;AACxB,MAAK,MAAM,OAAO,MAChB,MAAK,MAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,EAAE;AACtE,MAAI,QAAQ,OAAO,SAAS;AAC1B,QAAK,MAAM,UAAU,OAAO,QAAQ,MAClC,KAAI,CAAC,IAAI,SAAS,OAAO,CAAE,KAAI,KAAK,OAAO;AAE7C;;AAEF,MAAI,EAAE,QAAQ,YAAY;GACxB,MAAM,OAAO,OAAO,KAAK,OAAO,QAAQ;AACxC,SAAM,IAAI,mBACR,uBAAuB,KAAK,wBACvB,OAAO,KAAK,UAAU,CAAC,KAAK,KAAK,IAAI,6BACrC,KAAK,SAAS,WAAW,KAAK,KAAK,KAAK,KAAK,KACnD;;AAEH,MAAI,CAAC,IAAI,SAAS,KAAK,CAAE,KAAI,KAAK,KAAK;;AAG3C,QAAO;;;;;;;;;AAUT,SAAgB,yBAAyB,OAA2B;CAClE,MAAM,MAAgB,EAAE;AACxB,MAAK,MAAM,SAAS,MAClB,MAAK,MAAM,QAAQ,MAAM,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ,EAAE;AACxE,MAAI,CAAC,KAAK,WAAW,QAAQ,CAAE;EAC/B,MAAM,SAAS,KAAK,MAAM,EAAe,CAAC,MAAM,KAAK,CAAC;AACtD,MAAI,UAAU,CAAC,IAAI,SAAS,OAAO,CAAE,KAAI,KAAK,OAAO;;AAGzD,QAAO;;;AAIT,SAAgB,iBAAiB,QAAgB,IAAoB;AACnE,QAAO,KAAK,QAAQ,GAAG,GAAG,WAAW;;;;;;AAOvC,SAAgB,eACd,QACA,IACA,OACA,aAAa,aACL;CACR,MAAM,YAAY,eAAe,WAAW;CAC5C,MAAM,UAAU,MAAM,QAAQ,MAAM,EAAE,KAAK,WAAW;AACtD,KAAI,QAAQ,OACV,OAAM,IAAI,mBACR,0BAA0B,QAAQ,KAAK,KAAK,CAAC,eACxC,OAAO,KAAK,UAAU,CAAC,KAAK,KAAK,IAAI,6BAC3C;CAEH,MAAM,UAAmC,EAAE;AAC3C,MAAK,MAAM,KAAK,MAAO,SAAQ,KAAK,UAAU;CAC9C,MAAM,OAAO,iBAAiB,QAAQ,GAAG;AACzC,KAAI,CAAC,WAAW,OAAO,CAAE,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;AAC/D,eAAc,MAAM,KAAK,UAAU,EAAE,YAAY,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO;AACpF,QAAO;;;AAIT,SAAgB,YAAY,QAAwC,aAAa,aAAuB;CACtG,MAAM,UAAU,OAAO,KAAK,eAAe,WAAW,CAAC;CACvD,MAAM,QAAkB,EAAE;AAC1B,KAAI,QAAQ,QAAQ;AAClB,QAAM,KAAK,4BAA4B;AACvC,OAAK,MAAM,KAAK,QAAS,OAAM,KAAK,KAAK,IAAI;OAE7C,OAAM,KAAK,2CAA2C;CAExD,MAAM,OAAO,OAAO,QAAQ,OAAO,QAAQ;AAC3C,KAAI,KAAK,QAAQ;AACf,QAAM,KAAK,0BAA0B;AACrC,OAAK,MAAM,CAAC,MAAM,YAAY,KAAM,OAAM,KAAK,KAAK,KAAK,KAAK,QAAQ,KAAK,KAAK,GAAG;;AAErF,OAAM,KAAK,2DAA2D;AACtE,QAAO;;;;;;;;;;;;;;;;;;;;;ACnHT,SAAgB,eAAe,QAAgB,OAAqC;AAClF,QAAO;EACL;EACA;EACA;EACA,GAAI,QAAQ,CAAC,MAAM,MAAM,GAAG,EAAE;EAC9B;EACA;EACD;;;AAIH,SAAgB,cAAc,UAA6C;CACzE,MAAM,MAAyB,EAAE,GAAG,QAAQ,KAAK;AACjD,QAAO,IAAI;AACX,QAAO,IAAI;AACX,QAAO,IAAI;AACX,QAAO,IAAI;AACX,QAAO,IAAI;AACX,QAAO,IAAI;CAGX,MAAM,UAAU,gBAAgB,SAAS;AACzC,KAAI,WAAW,WAAW,QAAQ,CAChC,KAAI,iBAAiBA,aAAQ,SAAS,OAAO,CAAC,MAAM;AAEtD,KAAI,SAAS,YAAa,KAAI,kBAAkB,SAAS;AACzD,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,SAAS,IAAI,CAAE,KAAI,KAAK;AAC5D,KAAI,aAAa;AACjB,QAAO;;;AAIT,SAAgB,kBAAkB,MAA0B;CAC1D,MAAM,UAAoB,EAAE;AAC5B,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,IAAI,KAAK;AACf,MAAI,MAAM,oBAAoB,MAAM,uBAAuB,MAAM,kBAAkB,MAAM,SAAS;AAChG,WAAQ,KAAK,EAAE;AACf,OAAI,IAAI,IAAI,KAAK,UAAU,CAAC,KAAK,IAAI,GAAG,WAAW,IAAI,CAAE;aAChD,EAAE,WAAW,kBAAkB,IAAI,EAAE,WAAW,gBAAgB,CACzE,SAAQ,KAAK,EAAE,MAAM,IAAI,CAAC,GAAG;;AAGjC,QAAO;;AAmBT,MAAa,0BAA4C;CACvD,QAAQ,EAAE;CACV,OAAO;CACP,OAAO;CACP,MAAM;CACN,SAAS;CACT,eAAe;CACf,WAAW;CACX,UAAU;CACX;;;;;AAMD,SAAgB,cAAc,MAAe,GAA2B;AACtE,KAAI,OAAO,SAAS,YAAY,SAAS,KAAM;CAC/C,MAAM,IAAI;CACV,MAAM,OAAO,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AAEnD,KAAI,SAAS,oBAAoB,OAAO,EAAE,cAAc,UAAU;AAChE,IAAE,WAAW,EAAE;AACf;;AAEF,KAAI,SAAS,oBAAoB,OAAO,EAAE,SAAS,YAAY,EAAE,SAAS,MAAM;EAC9E,MAAM,OAAO,EAAE;EACf,MAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AACzD,MAAI,SAAS,mBAAmB,OAAO,KAAK,SAAS,UAAU;AAC7D,KAAE,SAAS;AACX,KAAE,OAAO,SAAS,KAAK,KAAK,MAAM,GAAG,GAAG;AACxC,KAAE,YAAY,KAAK;AACnB,KAAE,OAAO,KAAK;IACZ,MAAM;IACN,SAAS,EAAE,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,KAAK;KAAM,CAAC,EAAE;IAC1D,CAAC;aACO,SAAS,qBAAqB;AACvC,KAAE,SAAS;GACX,MAAM,MAAM,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;GAC9D,MAAM,KAAK,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AACjE,KAAE,OAAO,SAAS,IAAI,MAAM,GAAG,GAAG;AAClC,KAAE,OAAO,KAAK;IACZ,MAAM;IACN,SAAS,EAAE,SAAS,CAAC;KAAE,MAAM;KAAY,MAAM;KAAQ,OAAO,EAAE,SAAS,KAAK;KAAE,CAAC,EAAE;IACpF,CAAC;AACF,KAAE,OAAO,KAAK;IACZ,MAAM;IACN,SAAS,EACP,SAAS,CACP;KACE,MAAM;KACN,aAAa;KACb,UAAU,OAAO;KACjB,SAAS,KAAK,qBAAqB;KACpC,CACF,EACF;IACF,CAAC;aACO,SAAS,eAAe;AACjC,KAAE,SAAS;GAEX,MAAM,SADU,MAAM,QAAQ,KAAK,QAAQ,GAAG,KAAK,UAAU,EAAE,EAE5D,KAAK,MAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,OAAQ,EAAyB,SAAS,WAAY,EAAuB,OAAO,IAAK,CAC5I,KAAK,KAAK;AACb,KAAE,OAAO,UAAU,MAAM,MAAM,GAAG,GAAG;AACrC,KAAE,OAAO,KAAK;IACZ,MAAM;IACN,SAAS,EAAE,SAAS,CAAC;KAAE,MAAM;KAAY,MAAM;KAAS,OAAO,EAAE,WAAW,OAAO;KAAE,CAAC,EAAE;IACzF,CAAC;aACO,SAAS,iBAAiB;AACnC,KAAE,SAAS;AACX,KAAE,OAAO,QAAQ,OAAO,KAAK,QAAQ,IAAI;;AAE3C;;AAEF,KAAI,SAAS,kBAAkB;EAC7B,MAAM,QAAS,OAAO,EAAE,UAAU,YAAY,EAAE,UAAU,OAAO,EAAE,QAAQ,EAAE;EAK7E,MAAM,UACH,MAAM,gBAAgB,MAAM,MAAM,uBAAuB,MAAM,MAAM,iBAAiB;AACzF,MAAI,SAAS,EAAG,GAAE,gBAAgB;AAClC;;AAEF,KAAI,SAAS,iBAAiB,SAAS,SAAS;AAC9C,IAAE,UAAU;EACZ,MAAM,MAAM,OAAO,EAAE,UAAU,YAAY,EAAE,UAAU,OAAO,EAAE,QAAQ,EAAE;AAC1E,IAAE,OAAO,OAAQ,IAA8B,WAAW,EAAE,WAAW,oBAAoB;;;;AAK/F,SAAgB,eAAe,MAA8B;CAC3D,MAAM,IAAI,KAAK,MAAM;AACrB,KAAI,CAAC,EAAE,WAAW,IAAI,CAAE,QAAO;AAC/B,KAAI;AACF,SAAO,KAAK,MAAM,EAAE;SACd;AACN,SAAO;;;;AASX,SAAgB,iBAA0B;AACxC,KAAI;AACF,eAAa,SAAS,CAAC,YAAY,EAAE;GAAE,SAAS;GAAM,OAAO;GAAU,CAAC;AACxE,SAAO;SACD;AACN,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChEX,SAAgB,iBAAiB,MAAsB;AACrD,QAAO,KAAK,UAAU;EAAE,MAAM;EAAQ,SAAS;GAAE,MAAM;GAAQ,SAAS;GAAM;EAAE,CAAC;;;AAInF,SAAgB,iBAAiB,MAAsB;AACrD,QAAO,GAAG,cAAc,GAAG;;;;;;;;AAS7B,SAAgB,SAAS,oBAAI,IAAI,MAAM,EAAE,SAAS,CAAC,EAAE,mBAAmB,EAAU;CAChF,MAAM,IAAI,IAAI,KAAK,EAAE,SAAS,GAAG,SAAS,IAAO;CACjD,MAAM,OAAO,SAAS,IAAI,MAAM;CAChC,MAAM,MAAM,KAAK,IAAI,OAAO;CAC5B,MAAM,KAAK,OAAO,KAAK,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,GAAG,IAAI;CACxD,MAAM,KAAK,OAAO,MAAM,GAAG,CAAC,SAAS,GAAG,IAAI;AAC5C,QAAO,GAAG,EAAE,aAAa,CAAC,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,GAAG;;;AAmCxD,SAAgB,mBAAmB,GAA0C;AAC3E,KAAI,CAAC,EAAG,QAAO;CACf,MAAM,KACH,EAAE,gBAAgB,MAClB,EAAE,2BAA2B,MAC7B,EAAE,+BAA+B,MACjC,EAAE,iBAAiB;AACtB,QAAO,IAAI,IAAI,IAAI;;;;;;;;AASrB,SAAgB,kBAAkB,QAA6C,QAA6B;AAC1G,KAAI,WAAW,QAAQ,UAAU,EAAG;AACpC,QAAO,gBAAgB,KAAK,IAAI,OAAO,iBAAiB,GAAG,OAAO;;;;;;;;;AAUpE,SAAgB,4BAA4B,QAA6C,GAAsB;AAC7G,QAAO,gBAAgB,mBAAmB,EAAE,MAAM,IAAI;;;AAIxD,SAAgB,kBAAkB,GAAyB;AACzD,QAAO,EAAE,SAAS,aAAa,EAAE,YAAY,sBAAsB,EAAE,YAAY;;;AAInF,SAAgB,kBAAkB,GAA+B;AAC/D,KAAI,OAAO,EAAE,mBAAmB,YAAY,EAAE,iBAAiB,EAAG,QAAO,EAAE;AAC3E,KAAI,EAAE,cAAc,OAAO,EAAE,WAAW,mBAAmB,YAAY,EAAE,WAAW,iBAAiB,EACnG,QAAO,EAAE,WAAW;AAGtB,KAAI,UAAU,MAAM,EAAE,SAAS,IAAI,MAAM,CAAC,CAAE,QAAO;AACnD,QAAO;;;;;;AAOT,eAAsB,UAAU,MAAmC;CACjE,MAAM,EAAE,KAAK,MAAM,SAAS,WAAW,oBAAoB;AAE3D,KAAI,CAAC,OAAO,QACV,OAAM,IAAI,MACR,mIAED;CAEH,MAAM,SAAS,cAAc,OAAO;AACpC,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;AAGtC,KAAI,KAAK,cAAc,UAAU,CAAC,KAAK,UAAU;EAC/C,MAAM,EAAE,eAAe,MAAM,OAAO;AACpC,SAAO,WAAW,KAAK;;CAMzB,MAAM,SAAS,aAAa,KAAK,OAAO;AACxC,KAAI,OAAQ,oBAAmB,QAAQ,QAAQ,OAAO,KAAK;CAE3D,MAAM,SAAS,cAAc,QAAQ,QAAQ;EAC3C,cAAc,KAAK;EACnB,WAAW,KAAK;EACjB,CAAC;AACF,wBAAuB,OAAO,cAAc,OAAO,SAAS;CAE5D,MAAM,SAAS,gBAAgB,KAAK,WAAW;CAC/C,MAAM,QACJ,KAAK,SACL,UAAU,OAAO,UAAU,WAAW,GAAG;CAI3C,MAAM,QAAQ,OAAO;CACrB,MAAM,aACJ,SAAS,kBAAkB,MAAM,GAAG,QAAQ,qBAAqB,KAAK,UAAU;CAClF,MAAM,QAAQ,KAAK,aAAa,uBAAuB,OAAO,UAAU,WAAW;AAEnF,KAAI;AACF,MAAI,OAAO,SAAS,WAAW,QAC7B,QAAO,MAAM,gBAAgB;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA,YAAY,KAAK;GACjB,QAAQ,KAAK,UAAU;GACvB,KAAK,KAAK;GACV,QAAQ,UAAU;GAClB,OAAO,KAAK;GACZ,OAAO,KAAK;GACZ,eAAe,KAAK;GACpB,IAAI,KAAK;GACT,cAAc,KAAK;GACnB,WAAW,KAAK;GACjB,CAAC;AAEJ,SAAO,MAAM,WAAW;GACtB;GACA;GACA;GACA;GACA;GACA;GACA,YAAY,KAAK;GACjB,QAAQ,KAAK,UAAU;GACvB,SAAS,KAAK;GACd,KAAK,KAAK;GACV,QAAQ,UAAU;GAClB,OAAO,KAAK;GACZ,OAAO,KAAK;GACZ,eAAe,KAAK;GACpB,IAAI,KAAK;GACT,cAAc,KAAK;GACnB,WAAW,KAAK;GAChB,UAAU,KAAK,aAAa;GAC7B,CAAC;UACK,GAAG;AACV,MAAI,aAAa,SAAS,EAAE,QAAQ,WAAW,WAAW,CACxD,OAAM,IAAI,MACR,aAAa,OAAO,aAAa,KAAK,EAAE,8EAEzC;AAEH,QAAM;;;;;;;;;;;AAYV,MAAM,uBAAuB;;AAG7B,SAAgB,mBAAmB,cAAkC;AACnE,QAAO,aAAa,SAAS,EAAE,GAAG,CAAC,kBAAkB,qBAAqB;;AAwB5E,eAAe,WAAW,GAAiC;CACzD,MAAM,EAAE,QAAQ,QAAQ,QAAQ,OAAO,OAAO,QAAQ,WAAW;CACjE,MAAM,WAAW,OAAO;CAGxB,IAAI;AACJ,KAAI,OAAO,SAAS,aAAa,SAE/B,YAAW,GADE,MAAM,mBAAmB,oBAAoB,OAAO,CAC9C,GAAG,OAAO;CAE/B,MAAM,MAAM,YAAY,OAAO,UAAU,UAAU,SAAS;CAE5D,MAAM,MAAM,EAAE,MAAM,aAAa;CACjC,MAAM,MAAM,EAAE,OAAO,QAAQ,KAAK;CAClC,MAAM,OAAO,QAAQ,IAAI,oBAAoB;CAC7C,MAAM,UAAU,eAAe,KAAK;CAGpC,MAAM,iBAAiB,sBAAsB,QAAQ,IAAI;CAKzD,IAAI,WAAgC;AACpC,KAAI,YAAY,eAAe,EAAE,cAAc;EAAE;EAAK,WAAW,EAAE;EAAW,QAAQ,OAAO;EAAQ,CAAC,CACpG,KAAI;AACF,aAAW,YAAY,QAAQ,KAAK,IAAI;UACjC,GAAG;EACV,MAAM,MAAO,EAAY;AACzB,UAAQ,OAAO,MAAM,4BAA4B,IAAI,wBAAwB;AAC7E,eAAa,WAAW,OAAO,EAAE,eAAe;GAAE,IAAI;GAAK,MAAM,gBAAgB;GAAO,CAAC;;AAI7F,KAAI,gBAAgB;CAEpB,MAAM,SAAuB;EAC3B,IAAI;EACJ,KAAK,QAAQ;EACb;EACA;EACA;EACA,UAAU,OAAO;EACjB;EACA,OAAO;EACP,SAAS,UAAU;EACnB,SAAS,UAAU;EACnB,OAAO;EACP,OAAO;EACP,MAAM,WAAW,aAAa;EAC9B,IAAI;EACJ,MAAM;EAEN,QAAQ,WAAW,UAAU;EAC7B,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;EAC9B,GAAI,iBAAiB,EAAE,gBAAgB,GAAG,EAAE;EAG5C,GAAI,EAAE,SAAS;GAAE,QAAQ,EAAE;GAAQ,OAAO,EAAE;GAAO,GAAG,EAAE;EACxD,GAAI,WAAW;GAAE,aAAa,SAAS;GAAK,QAAQ,SAAS;GAAQ,cAAc,SAAS;GAAM,GAAG,EAAE;EACxG;AACD,YAAW,QAAQ,OAAO;AAC1B,GAAE,gBAAgB,IAAI;CACtB,MAAM,SAAS,WAAW,OAAO;AACjC,cAAa,QAAQ,gBAAgB;EACnC,IAAI;EACJ,UAAU,OAAO;EACjB,MAAM,WAAW,aAAa;EAC9B;EACA;EACA;EACD,CAAC;AAGF,KAAI,YAAY,CAAC,UAAU,OAAO,KAAK,WAAW,QAAQ,QAAQ,IAAI,wBAAwB,IAC5F,CAAK,kBAAkB,QAAQ,QAAQ,KAAK,KAAK,CAAC,YAAY,GAAG;CAKnE,IAAI,UAAoB,EAAE;AAC1B,KAAI,YAAY,CAAC,OAAO,iBAAiB;EACvC,MAAM,SAAS;GACb,GAAI,EAAE,UAAU,CAAC,EAAE,QAAQ,GAAG,EAAE;GAChC,GAAG,OAAO;GACV,GAAI,OAAO,YAAY,EAAE;GACzB,GAAG,yBAAyB,OAAO,aAAa;GACjD;AACD,MAAI,OAAO,OAET,WAAU;GAAC;GAAuB;GAAgB,eAAe,QAAQ,KAD3D,eAAe,QAAQ,OAAO,CACwC;GAAC;MAErF,WAAU;GAAC;GAAuB;GAAgB,kBAAkB,OAAO;GAAC;;CAMhF,MAAM,WAAW,WAAW,kBAAkB,OAAO,KAAK,GAAG,OAAO;CACpE,MAAM,WAAW,WAAW,mBAAmB,OAAO,aAAa,GAAG,EAAE;CACxE,MAAM,MAAgB,CAAC,SAAS;AAIhC,KAAI,CAAC,OAAO,eAAe,MAAO,KAAI,KAAK,WAAW,MAAM;AAC5D,KAAI,KAAK,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS;AAC9C,KAAI,UAAU;AACZ,MAAI,KAAK,mBAAmB,eAAe,aAAa,kBAAkB,cAAc;AACxF,MAAI,CAAC,OAAO,mBAAoB,KAAI,KAAK,0BAA0B,uBAAuB;AAC1F,MAAI,SACF,KAAI,KACF,0BACA,qBAAqB,KAAK,SAAS,QAAQ,SAAS,IAAI,CACzD;;CAIL,MAAM,KAAK,KAAK,KAAK;CACrB,MAAM,OAAO,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,EAAE;EACvC;EACA,KAAK,UAAU,OAAO;EACtB,OAAO,WAAW;GAAC;GAAQ;GAAQ;GAAU,GAAG;EACjD,CAAC;CAGF,IAAI,mBAAmB;CACvB,IAAI,aAAoC;CACxC,MAAM,sBAAsB;AAC1B,MAAI,WAAY,cAAa,WAAW;AACxC,eAAa,iBAAiB;AAC5B,OAAI,qBAAqB,EACvB,KAAI;AACF,SAAK,OAAO,KAAK;WACX;KAIT,IAAM;;CAGX,IAAI,WAA0B;CAC9B,MAAM,cAAc,QAAuC;AACzD,MAAI,aAAa,KAAM;AACvB,MAAI;AACF,aAAU,UAAU,KAAK,UAAU;IAAE,GAAG;IAAK,KAAK,UAAU;IAAE,CAAC,GAAG,KAAK;UACjE;;CAKV,MAAM,iBAAiB,WACnB,qBAAqB,QAAQ,MAAM,SAAS;AAC1C,sBAAoB;AACpB,MAAI,YAAY;AACd,gBAAa,WAAW;AACxB,gBAAa;;AAIf,aAAW;GAAE,MAAM;GAAY;GAAM,SAAS,iBAAiB,KAAK;GAAE,CAAC;AACvE,MAAI;AACF,QAAK,OAAO,MAAM,iBAAiB,iBAAiB,KAAK,CAAC,GAAG,KAAK;UAC5D;GAGR,GACF;CAEJ,IAAI,SAAS;CACb,MAAM,gBAAgB;AACpB,MAAI,WAAY,cAAa,WAAW;AACxC,kBAAgB,OAAO;;CAEzB,MAAM,YAAY,QAAgB;AAChC,WAAS;AACT,SAAO,QAAQ;AACf,SAAO,KAAK;AACZ,SAAO,OAAO,KAAK,OAAO,KAAK,KAAK,GAAG,MAAM,IAAK;AAClD,SAAO,OAAO,oBAAoB;AAClC,aAAW,QAAQ,OAAO;AAC1B,eAAa,QAAQ,cAAc;GACjC,IAAI;GACJ,UAAU,OAAO;GACjB,MAAM;GACN;GACA,IAAI;GACJ,MAAM,OAAO;GACb,QAAQ;GACR;GACD,CAAC;AAGF,MAAI,SACF,KAAI;AACF,kBAAe,QAAQ,QAAQ,UAAU,MAAM;UACzC;AAIV,WAAS;AACT,MAAI;AACF,QAAK,MAAM;UACL;AAGR,UAAQ,KAAK,IAAI;;AAEnB,SAAQ,KAAK,iBAAiB,SAAS,UAAU,CAAC;AAClD,SAAQ,KAAK,gBAAgB,SAAS,SAAS,CAAC;AAChD,SAAQ,KAAK,gBAAgB,SAAS,SAAS,CAAC;CAIhD,MAAM,MAA8E;EAClF,aAAa;EACb,cAAc;EACf;AAED,KAAI,UAAU;AAEZ,MAAI,OAAO,WAAW,KACpB,KAAI;AACF,QAAK,MAAO,MAAM,iBAAiB,OAAO,OAAO,GAAG,KAAK;UACnD;AAIV,aAAW,SAAS,WAAW,QAAQ,IAAI,EAAE,IAAI;AAEjD,EADW,gBAAgB,EAAE,OAAO,KAAK,QAAS,CAAC,CAChD,GAAG,SAAS,SAAS;AACtB,OAAI,OAAO,iBAAiB,cAC1B,SAAQ,OAAO,MAAM,OAAO,KAAK;AAEnC,OAAI,CAAC,KAAK,WAAW,IAAI,CAAE;GAC3B,IAAI;AACJ,OAAI;AACF,QAAI,KAAK,MAAM,KAAK;WACd;AACN;;AAEF,cAAW,EAA6B;AACxC,OAAI,EAAE,SAAS,YAAY,EAAE,YAAY,QAAQ;AAC/C,QAAI,EAAE,WAAY,QAAO,gBAAgB,EAAE;IAC3C,MAAM,KAAK,kBAAkB,EAAE;AAC/B,QAAI,GAAI,QAAO,gBAAgB;AAC/B,eAAW,QAAQ,OAAO;cACjB,kBAAkB,EAAE,EAAE;AAC/B,gCAA4B,QAAQ,EAAE;AACtC,eAAW,QAAQ,OAAO;cACjB,EAAE,SAAS,aAAa;AACjC,WAAO,SAAS;AAChB,sBAAkB,QAAQ,mBAAmB,EAAE,SAAS,MAAM,CAAC;AAC/D,SAAK,MAAM,SAAS,EAAE,SAAS,WAAW,EAAE,CAC1C,KAAI,MAAM,SAAS,YAAY;AAC7B,YAAO,SAAS;AAChB,YAAO,OAAO,aAAa,MAAM,QAAQ,KAAK,MAAM,MAAM;eACjD,MAAM,SAAS,WAAW,MAAM,QAAQ,IAAI,MAAM,CAC3D,QAAO,OAAO,WAAW,UAAU,MAAM,MAAM,GAAG;AAGtD,eAAW,QAAQ,OAAO;cACjB,EAAE,SAAS,UAAU;IAC9B,MAAM,SAAS,mBAAmB,EAAE,MAAM;AAE1C,QAAI,EAAE,WAAY,QAAO,gBAAgB,UAAU,OAAO;QACrD,mBAAkB,QAAQ,OAAO;IACtC,MAAM,SAAS,kBAAkB,EAAE,UAAU,GAAG;AAChD,QAAI,QAAQ,MAAO,QAAO,OAAO,UAAU,OAAO,OAAO,GAAG;aACnD,EAAE,OAAQ,QAAO,OAAO,UAAU,EAAE,QAAQ,GAAG;AACxD,eAAW,QAAQ,OAAO;AAC1B,uBAAmB;AACnB,mBAAe;AACf,QAAI,cAAc;AAClB,QAAI,eAAe;;IAErB;;CAGJ,MAAM,KAAK,MAAM,IAAI,SAAiB,SAAS,WAAW;AACxD,OAAK,GAAG,SAAS,OAAO;AACxB,OAAK,GAAG,UAAU,SAAS,QAAQ,SAAS,SAAS,MAAM,GAAG,CAAC;GAC/D;AACF,KAAI,aAAa,KAAM,WAAU,SAAS;AAC1C,UAAS;CAET,MAAM,OAAO,KAAK,OAAO,KAAK,KAAK,GAAG,MAAM,IAAK;CACjD,MAAM,cAAc,IAAI;CACxB,MAAM,KAAK,OAAO,KAAK,gBAAgB,QAAQ,CAAC,YAAY;AAC5D,QAAO,QAAQ,KAAK,SAAS;AAC7B,QAAO,KAAK;AACZ,QAAO,OAAO;AACd,KAAI,YAAa,QAAO,OAAO,UAAU,YAAY,UAAU,IAAI,GAAG;AACtE,KAAI,IAAI,cAAc,MAAO,QAAO,OAAO,UAAU,IAAI,aAAa,OAAO,GAAG;AAChF,YAAW,QAAQ,OAAO;AAC1B,cAAa,QAAQ,cAAc;EACjC,IAAI;EACJ,UAAU,OAAO;EACjB,MAAM,WAAW,aAAa;EAC9B;EACA;EACA;EACA,OAAO,OAAO;EACd,OAAO,OAAO;EACd;EACD,CAAC;AAGF,KAAI,SAAU,gBAAe,QAAQ,QAAQ,UAAU,GAAG;AAE1D,KAAI,YAAY,CAAC,EAAE,MACjB,aAAY,OAAO,cAAc,aAAa,IAAI,QAAQ,KAAK,IAAI,cAAc,eAAe,OAAO,CAAC;CAI1G,MAAM,aAAa,aAAa,UAAU;AAC1C,KACE,CAAC,MACD,YACA,EAAE,OAAO,QAAQ,UACjB,OAAO,QAAQ,gBACf,OAAO,SAAS,KAChB,OAAO,UAAU,KACjB,eAAe,WAAW,EAC1B;AACA,cAAY,QAAQ,OAAO,cAAc,OAAO,QAAQ,gBAAgB;EACxE,MAAM,OAAO,iBAAiB,QAAQ,QAAQ,OAAO,aAAa;AAClE,MAAI,QAAQ,EAAE,WAAW,OAAO,QAAQ,MAAM,QAAQ;AACpD,gBAAa,QAAQ,kBAAkB;IACrC,MAAM,OAAO;IACb,IAAI;IACJ,QAAQ;IACT,CAAC;AACF,UAAO,UAAU;IACf,cAAc;IACd;IACA,QAAQ,EAAE;IACV,SAAS,EAAE;IACX,KAAK,EAAE;IACP,QAAQ,EAAE;IACV,OAAO,EAAE;IACT,YAAY,EAAE;IACd,eAAe,EAAE;IACjB,cAAc,EAAE;IAChB,WAAW,EAAE;IACb,WAAW,EAAE,WAAW;IACzB,CAAC;;;CAON,MAAM,UAAU,OAAO,IAAI,KAAK,KAAK,IAAI;AACzC,KAAI,OAAO,UAAU,WAAW,QAAQ,OAAO,OAAO,CACpD,KAAI;AACF,QAAM,eAAe,QAAQ;GAC3B,MAAM;GACN,IAAI,OAAO;GACX,MAAM;GACN,MAAM,UACJ,IAAI,cAAc,SAAS,aAAa,WAAW,KAAK,SAAS,WACjE,IACD;GACD,MAAM;IACJ,IAAI;IACJ;IACA,GAAI,OAAO,SAAS;KAAE,QAAQ,OAAO;KAAQ,SAAS,OAAO,WAAW;KAAG,GAAG,EAAE;IAChF,GAAI,IAAI,eAAe,EAAE,QAAQ,IAAI,cAAc,GAAG,EAAE;IACzD;GACF,CAAC;SACI;AAKV,QAAO;;;AAIT,SAAS,eAAe,GAAsD;AAC5E,QAAO,EAAE,SAAS;EAAE,QAAQ,EAAE;EAAQ,SAAS,EAAE,WAAW;EAAG,GAAG;;AASpE,eAAe,gBAAgB,GAA+B;CAC5D,MAAM,EAAE,QAAQ,QAAQ,QAAQ,OAAO,OAAO,QAAQ,WAAW;AACjE,KAAI,CAAC,OAAO,YAAY,OAAO,WAAW,KACxC,OAAM,IAAI,MACR,aAAa,OAAO,aAAa,gFAElC;CAEH,MAAM,MAAM,cAAc,OAAO,SAAS;CAC1C,MAAM,MAAM,EAAE,MAAM,aAAa;CACjC,MAAM,MAAM,EAAE,OAAO,QAAQ,KAAK;CAClC,MAAM,OAAO,QAAQ,IAAI,oBAAoB;CAC7C,MAAM,UAAU,eAAe,KAAK;CACpC,MAAM,iBAAiB,sBAAsB,QAAQ,IAAI;CAEzD,IAAI,WAAgC;AACpC,KAAI,eAAe,EAAE,cAAc;EAAE;EAAK,WAAW,EAAE;EAAW,QAAQ,OAAO;EAAQ,CAAC,CACxF,KAAI;AACF,aAAW,YAAY,QAAQ,KAAK,IAAI;UACjC,GAAG;EACV,MAAM,MAAO,EAAY;AACzB,UAAQ,OAAO,MAAM,4BAA4B,IAAI,wBAAwB;AAC7E,eAAa,WAAW,OAAO,EAAE,eAAe;GAAE,IAAI;GAAK,MAAM,gBAAgB;GAAO,CAAC;;AAG7F,KAAI,gBAAgB;CAEpB,MAAM,UAAU,WAAW,qBAAqB,KAAK,SAAS,QAAQ,SAAS,IAAI,GAAG,SAAS,MAAM,OAAO;CAE5G,MAAM,SAAuB;EAC3B,IAAI;EACJ,KAAK,QAAQ;EACb;EACA;EACA;EACA,UAAU,OAAO;EACjB;EACA,OAAO;EACP,SAAS,UAAU;EACnB,SAAS,UAAU;EACnB,OAAO;EACP,OAAO;EACP,MAAM;EACN,IAAI;EACJ,MAAM;EACN,QAAQ;EACR,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;EAC9B,GAAI,iBAAiB,EAAE,gBAAgB,GAAG,EAAE;EAG5C,GAAI,OAAO,SAAS,gBAAgB,EAAE,eAAe,OAAO,SAAS,eAAe,GAAG,EAAE;EACzF,GAAI,EAAE,SAAS;GAAE,QAAQ,EAAE;GAAQ,OAAO,EAAE;GAAO,GAAG,EAAE;EACxD,GAAI,WAAW;GAAE,aAAa,SAAS;GAAK,QAAQ,SAAS;GAAQ,cAAc,SAAS;GAAM,GAAG,EAAE;EACxG;AACD,YAAW,QAAQ,OAAO;AAC1B,GAAE,gBAAgB,IAAI;CACtB,MAAM,SAAS,WAAW,OAAO;AACjC,cAAa,QAAQ,gBAAgB;EACnC,IAAI;EACJ,UAAU,OAAO;EACjB,MAAM;EACN,QAAQ;EACR;EACA;EACA;EACD,CAAC;CACF,MAAM,UAAU,kBAAkB,EAAE,WAAW;AAC/C,KAAI,QAAQ,OACV,cAAa,QAAQ,eAAe;EAClC,IAAI;EACJ,MAAM,sBAAsB,QAAQ,KAAK,KAAK;EAC/C,CAAC;AAGJ,KAAI,CAAC,UAAU,OAAO,KAAK,WAAW,QAAQ,QAAQ,IAAI,wBAAwB,IAChF,CAAK,kBAAkB,QAAQ,QAAQ,KAAK,KAAK,CAAC,YAAY,GAAG;CAGnE,MAAM,KAAK,KAAK,KAAK;CACrB,MAAM,OAAO,MAAM,SAAS,eAAe,QAAQ,OAAO,cAAc,SAAY,MAAM,EAAE;EAC1F;EACA,KAAK,UAAU,OAAO;EACtB,OAAO;GAAC;GAAU;GAAQ;GAAU;EACrC,CAAC;CAEF,MAAM,OAAO,kBAAkB;CAC/B,MAAM,WAAW,SAAS,WAAW,QAAQ,IAAI,EAAE,IAAI;CACvD,MAAM,cAAc,QAAiC;AACnD,MAAI;AACF,aAAU,UAAU,KAAK,UAAU;IAAE,GAAG;IAAK,KAAK,UAAU;IAAE,CAAC,GAAG,KAAK;UACjE;;AAIV,YAAW;EACT,MAAM;EACN,SAAS;EACT;EACA;EACA,GAAI,OAAO,SAAS,gBAAgB,EAAE,gBAAgB,OAAO,SAAS,eAAe,GAAG,EAAE;EAC3F,CAAC;CAEF,IAAI,SAAS;AACb,SAAQ,KAAK,WAAW,cAAc,UAAU,CAAC;AACjD,SAAQ,KAAK,UAAU,cAAc,SAAS,CAAC;AAC/C,SAAQ,KAAK,UAAU,cAAc,SAAS,CAAC;CAC/C,SAAS,cAAc,KAAa;AAClC,eAAa;AACX,YAAS;AACT,UAAO,QAAQ;AACf,UAAO,KAAK;AACZ,UAAO,OAAO,KAAK,OAAO,KAAK,KAAK,GAAG,MAAM,IAAK;AAClD,UAAO,OAAO,oBAAoB;AAClC,cAAW,QAAQ,OAAO;AAE1B,OAAI,SACF,KAAI;AACF,mBAAe,QAAQ,QAAQ,UAAU,MAAM;WACzC;AAIV,OAAI;AACF,SAAK,MAAM;WACL;AAGR,WAAQ,KAAK,IAAI;;;AAKrB,CADW,gBAAgB,EAAE,OAAO,KAAK,QAAS,CAAC,CAChD,GAAG,SAAS,SAAS;AACtB,MAAI,OAAO,iBAAiB,cAAe,SAAQ,OAAO,MAAM,OAAO,KAAK;EAC5E,MAAM,aAAa,eAAe,KAAK;AACvC,MAAI,eAAe,KAAM;AACzB,gBAAc,YAAY,KAAK;AAC/B,MAAI,KAAK,YAAY,CAAC,OAAO,cAAe,QAAO,gBAAgB,KAAK;AACxE,SAAO,QAAQ,KAAK;AACpB,SAAO,QAAQ,KAAK;AACpB,MAAI,KAAK,KAAM,QAAO,OAAO,UAAU,KAAK,MAAM,GAAG;AACrD,oBAAkB,QAAQ,KAAK,cAAc;AAC7C,aAAW,QAAQ,OAAO;AAC1B,OAAK,MAAM,MAAM,KAAK,OAAO,OAAO,EAAE,CAAE,YAAW,GAAG;GACtD;CAEF,MAAM,KAAK,MAAM,IAAI,SAAiB,SAAS,WAAW;AACxD,OAAK,GAAG,SAAS,OAAO;AACxB,OAAK,GAAG,UAAU,SAAS,QAAQ,SAAS,SAAS,MAAM,GAAG,CAAC;GAC/D;AACF,WAAU,SAAS;CAEnB,MAAM,OAAO,KAAK,OAAO,KAAK,KAAK,GAAG,MAAM,IAAK;CACjD,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,SAAS,kBAAkB,UAAU;CAC3C,MAAM,cAA2B;EAC/B,MAAM;EACN,QAAQ;EACR,UAAU,KAAK,WAAW,OAAO;EACjC,WAAW,KAAK;EAChB,aAAa,OAAO;EACrB;AACD,YAAW,YAAY;CAEvB,MAAM,KAAK,OAAO,KAAK,CAAC,KAAK;AAC7B,QAAO,QAAQ,KAAK,SAAS;AAC7B,QAAO,KAAK;AACZ,QAAO,OAAO;AACd,QAAO,OAAO,UAAU,QAAQ,SAAS,WAAW,GAAG,KAAK,KAAK,SAAS;AAC1E,YAAW,QAAQ,OAAO;AAC1B,cAAa,QAAQ,cAAc;EACjC,IAAI;EACJ,UAAU,OAAO;EACjB,MAAM;EACN,QAAQ;EACR;EACA;EACA;EACA,OAAO,OAAO;EACd,OAAO,OAAO;EACd;EACD,CAAC;AAEF,KAAI,SAAU,gBAAe,QAAQ,QAAQ,UAAU,GAAG;AAE1D,KAAI,CAAC,EAAE,MAAO,aAAY,OAAO,cAAc,aAAa,IAAI,QAAQ,KAAK,QAAQ,eAAe,OAAO,CAAC;CAG5G,MAAM,UAAU,OAAO,IAAI,KAAK,KAAK,IAAI;AACzC,KAAI,OAAO,UAAU,WAAW,QAAQ,OAAO,OAAO,CACpD,KAAI;AACF,QAAM,eAAe,QAAQ;GAC3B,MAAM;GACN,IAAI,OAAO;GACX,MAAM;GACN,MAAM,UAAU,QAAQ,SAAS,cAAc,KAAK,SAAS,WAAW,IAAI;GAC5E,MAAM;IACJ,IAAI;IACJ;IACA,GAAI,OAAO,SAAS;KAAE,QAAQ,OAAO;KAAQ,SAAS,OAAO,WAAW;KAAG,GAAG,EAAE;IAChF,GAAI,SAAS,EAAE,QAAQ,GAAG,EAAE;IAC7B;GACF,CAAC;SACI;AAIV,QAAO;;AAOT,SAAgB,YACd,KACA,aACA,IACA,QACA,KACA,QACA,QACM;AACN,KAAI,QAAQ,cAAe;AAC3B,KAAI,QAAQ,QAAQ;EAClB,MAAM,UAAU,SACZ;GAAE,GAAI,eAAe;IAAE,UAAU;IAAM,QAAQ;IAAmB;IAAI;GAAG;GAAQ,GAAG;GAAQ,GAC5F;GAAE,GAAI,eAAe;IAAE,UAAU;IAAM,QAAQ;IAAmB;IAAI;GAAG,GAAG;GAAQ;AACxF,UAAQ,IAAI,KAAK,UAAU,QAAQ,CAAC;AACpC;;AAEF,KAAI,YACF,SAAQ,IAAI,YAAY,UAAU,GAAG;KAErC,SAAQ,OAAO,MACb,0CAA0C,GAAG,SAAS,WAAW,QAAQ,IAAI,CAAC,IAC/E;;;;;;;AAuBL,eAAsB,aACpB,cACA,UACA,QACA,YAAY,KACiB;AAC7B,wBAAuB,cAAc,SAAS;CAC9C,MAAM,QAAQ,SAAS,OAAO;AAE9B,KAAI,SAAS,WAAW,SAAS;EAC/B,MAAM,UAAU,gBAAgB,GAAG,SAAY;AAC/C,SAAO;GACL,UAAU;GACV;GACA,WAAW;GACX,QAAQ,WAAW;GACnB,IAAI;GACJ,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;GAC/B;;CAGH,IAAI;AACJ,KAAI,SAAS,aAAa,SAExB,YAAW,GADE,MAAM,mBAAmB,oBAAoB,OAAO,CAC9C,GAAG;CAExB,MAAM,MAAM,YAAY,UAAU,MAAM,SAAS;CACjD,MAAM,KAAK,KAAK,KAAK;CACrB,MAAM,OAAO,MACX,UACA;EACE;EAAW;EACX;EAAuB;EAAgB,kBAAkB,OAAO;EAChE;EAAM;EACN;EAAmB;EACpB,EACD;EAAE;EAAK,OAAO;GAAC;GAAU;GAAQ;GAAU;EAAE,CAC9C;CACD,MAAM,QAAQ,iBAAiB;AAC7B,MAAI;AAAE,QAAK,KAAK,UAAU;UAAU;IACnC,UAAU;CAEb,IAAI,MAAM;AACV,MAAK,OAAO,GAAG,SAAS,UAAkB;AACxC,SAAO,MAAM,SAAS,OAAO;GAC7B;CACF,MAAM,KAAK,MAAM,IAAI,SAAiB,YAAY;AAChD,OAAK,GAAG,eAAe,QAAQ,EAAE,CAAC;AAClC,OAAK,GAAG,UAAU,SAAS,QAAQ,QAAQ,EAAE,CAAC;GAC9C;AACF,cAAa,MAAM;AAEnB,QAAO;EACL,UAAU;EACV;EACA,WAAW,KAAK,KAAK,GAAG;EACxB,QAAQ,iBAAiB,IAAI;EAC7B,IAAI,OAAO,KAAK,iBAAiB,IAAI,CAAC,MAAM,CAAC,aAAa,KAAK;EAChE;;;;;;;;AASH,SAAgB,iBAAiB,KAAqB;CACpD,MAAM,UAAU,IAAI,MAAM;AAC1B,KAAI,CAAC,QAAS,QAAO;CACrB,MAAM,SAAS,MAAuB;AACpC,MAAI;AACF,UAAO,KAAK,MAAM,EAAE;UACd;AACN;;;CAGJ,MAAM,aAAa,MAA8B;AAC/C,MAAI,MAAM,QAAQ,EAAE,EAAE;AACpB,QAAK,IAAI,IAAI,EAAE,SAAS,GAAG,KAAK,GAAG,KAAK;IACtC,MAAM,IAAI,UAAU,EAAE,GAAG;AACzB,QAAI,MAAM,KAAM,QAAO;;AAEzB,UAAO;;AAET,MAAI,OAAO,MAAM,YAAY,MAAM,MAAM;GACvC,MAAM,IAAI;AACV,OAAI,EAAE,SAAS,YAAY,OAAO,EAAE,WAAW,SAAU,QAAO,EAAE;;AAEpE,SAAO;;CAET,MAAM,SAAS,UAAU,MAAM,QAAQ,CAAC;AACxC,KAAI,WAAW,KAAM,QAAO;CAC5B,MAAM,QAAQ,QAAQ,MAAM,KAAK;AACjC,MAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK;EAC1C,MAAM,IAAI,UAAU,MAAM,MAAM,GAAG,CAAC;AACpC,MAAI,MAAM,KAAM,QAAO;;AAEzB,QAAO,QAAQ,MAAM,GAAG,IAAI"}
|