agentix-cli 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -118
- package/apps/mac-helper/Sources/AgentXHelper/AXTree.swift +159 -0
- package/apps/mac-helper/Sources/AgentXHelper/Brand.swift +63 -0
- package/apps/mac-helper/Sources/AgentXHelper/Focus.swift +97 -0
- package/apps/mac-helper/Sources/AgentXHelper/HUD.swift +179 -0
- package/apps/mac-helper/Sources/AgentXHelper/OCR.swift +117 -0
- package/apps/mac-helper/Sources/AgentXHelper/Pointer.swift +309 -0
- package/apps/mac-helper/Sources/AgentXHelper/Screens.swift +67 -0
- package/apps/mac-helper/Sources/AgentXHelper/Typer.swift +185 -0
- package/apps/mac-helper/Sources/AgentXHelper/Vision.swift +201 -0
- package/apps/mac-helper/Sources/AgentXHelper/main.swift +265 -0
- package/apps/mac-helper/build.sh +39 -0
- package/apps/mac-voice/Resources/Info.plist +18 -0
- package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +108 -0
- package/apps/mac-voice/Sources/AgentXVoice/Brand.swift +123 -0
- package/apps/mac-voice/Sources/AgentXVoice/Config.swift +68 -0
- package/apps/mac-voice/Sources/AgentXVoice/Hold.swift +74 -0
- package/apps/mac-voice/Sources/AgentXVoice/Hotkey.swift +76 -0
- package/apps/mac-voice/Sources/AgentXVoice/Markdown.swift +140 -0
- package/apps/mac-voice/Sources/AgentXVoice/Panel.swift +275 -0
- package/apps/mac-voice/Sources/AgentXVoice/Progress.swift +147 -0
- package/apps/mac-voice/Sources/AgentXVoice/Recorder.swift +139 -0
- package/apps/mac-voice/Sources/AgentXVoice/ResultCard.swift +162 -0
- package/apps/mac-voice/Sources/AgentXVoice/SmartPaste.swift +72 -0
- package/apps/mac-voice/Sources/AgentXVoice/Speech.swift +192 -0
- package/apps/mac-voice/Sources/AgentXVoice/StepLabel.swift +159 -0
- package/apps/mac-voice/Sources/AgentXVoice/main.swift +380 -0
- package/apps/mac-voice/build.sh +32 -0
- package/dist/{App-BPNHIGUX.js → App-CKPH3HHF.js} +2 -2
- package/dist/{ChatApp-6XSSZB4U.js → ChatApp-MX7IG4K2.js} +2 -2
- package/dist/agent-2U574KBZ.js +2 -0
- package/dist/article-fields-L4D2BOGY.js +2 -0
- package/dist/article-quality-44ONVGLN.js +3 -0
- package/dist/article-quality-44ONVGLN.js.map +1 -0
- package/dist/{audit-JU3HWISB.js → audit-4245CX6P.js} +2 -2
- package/dist/backfill-MTD2B62I.js +6 -0
- package/dist/backfill-MTD2B62I.js.map +1 -0
- package/dist/{board-dashboard-M3372FLO.js → board-dashboard-QR7CVTKA.js} +1411 -453
- package/dist/board-dashboard-QR7CVTKA.js.map +1 -0
- package/dist/{builtin-FRKAQMFS.js → builtin-B67DMJ3M.js} +2 -2
- package/dist/calibration-ILS5OI44.js +2 -0
- package/dist/{chunk-V3U4OF7L.js → chunk-3LYM22IV.js} +46 -48
- package/dist/chunk-3LYM22IV.js.map +1 -0
- package/dist/chunk-3SCPRYJB.js +5 -0
- package/dist/chunk-3SCPRYJB.js.map +1 -0
- package/dist/chunk-4BR7D2OU.js +36 -0
- package/dist/chunk-4BR7D2OU.js.map +1 -0
- package/dist/{chunk-P4VWIVKN.js → chunk-52ZPELD2.js} +403 -29
- package/dist/chunk-52ZPELD2.js.map +1 -0
- package/dist/chunk-5IA2CYHN.js +5 -0
- package/dist/{chunk-O6YPEOQF.js.map → chunk-5IA2CYHN.js.map} +1 -1
- package/dist/chunk-64CAIB3C.js +9 -0
- package/dist/chunk-64CAIB3C.js.map +1 -0
- package/dist/{chunk-5U35WAZF.js → chunk-77OV7346.js} +6 -6
- package/dist/chunk-77OV7346.js.map +1 -0
- package/dist/chunk-AJ5RMU7X.js +2 -0
- package/dist/chunk-AJ5RMU7X.js.map +1 -0
- package/dist/chunk-AJONZWLT.js +2 -0
- package/dist/chunk-AJONZWLT.js.map +1 -0
- package/dist/chunk-BH3GTCZ6.js +2 -0
- package/dist/chunk-BH3GTCZ6.js.map +1 -0
- package/dist/chunk-E3UJHQGN.js +1189 -0
- package/dist/chunk-E3UJHQGN.js.map +1 -0
- package/dist/chunk-EVWEG3VD.js +3 -0
- package/dist/chunk-EVWEG3VD.js.map +1 -0
- package/dist/chunk-F4HZTZHW.js +2 -0
- package/dist/chunk-F4HZTZHW.js.map +1 -0
- package/dist/chunk-F6CNF7V3.js +25 -0
- package/dist/chunk-F6CNF7V3.js.map +1 -0
- package/dist/chunk-FIOB3MEX.js +2 -0
- package/dist/{chunk-H5D7ATXS.js → chunk-FKBGJYWQ.js} +5 -5
- package/dist/chunk-FKBGJYWQ.js.map +1 -0
- package/dist/{chunk-BVGJSPW4.js → chunk-G2HMRDUQ.js} +7 -7
- package/dist/chunk-G2HMRDUQ.js.map +1 -0
- package/dist/chunk-H2QQU2XV.js +109 -0
- package/dist/chunk-H2QQU2XV.js.map +1 -0
- package/dist/{chunk-LDHPMJ7M.js → chunk-IWMW5432.js} +3 -3
- package/dist/chunk-IWMW5432.js.map +1 -0
- package/dist/chunk-K33GIBSE.js +2 -0
- package/dist/chunk-K33GIBSE.js.map +1 -0
- package/dist/chunk-L2WSPY6S.js +2 -0
- package/dist/chunk-L2WSPY6S.js.map +1 -0
- package/dist/chunk-LCIREYTD.js +54 -0
- package/dist/chunk-LCIREYTD.js.map +1 -0
- package/dist/chunk-MHIMGM4G.js +126 -0
- package/dist/chunk-MHIMGM4G.js.map +1 -0
- package/dist/chunk-P5ZBBEPL.js +13 -0
- package/dist/chunk-P5ZBBEPL.js.map +1 -0
- package/dist/{chunk-JTJIPNXY.js → chunk-PNWNVOEZ.js} +1 -1
- package/dist/chunk-PNWNVOEZ.js.map +1 -0
- package/dist/chunk-QHTQPBHU.js +45 -0
- package/dist/chunk-QHTQPBHU.js.map +1 -0
- package/dist/chunk-S2MNL5V6.js +2 -0
- package/dist/chunk-S2MNL5V6.js.map +1 -0
- package/dist/chunk-SCTIUFL4.js +5 -0
- package/dist/{chunk-K2CAPYES.js.map → chunk-SCTIUFL4.js.map} +1 -1
- package/dist/chunk-ULLAE6LE.js +157 -0
- package/dist/chunk-ULLAE6LE.js.map +1 -0
- package/dist/{chunk-DJEZ2HVU.js → chunk-VDMYBPYB.js} +1 -1
- package/dist/chunk-VDMYBPYB.js.map +1 -0
- package/dist/chunk-VIH7ARPF.js +547 -0
- package/dist/chunk-VIH7ARPF.js.map +1 -0
- package/dist/chunk-W5C2GDNC.js +32 -0
- package/dist/{chunk-JP5IEXR7.js.map → chunk-W5C2GDNC.js.map} +1 -1
- package/dist/chunk-WPMX3VGI.js +3 -0
- package/dist/chunk-WPMX3VGI.js.map +1 -0
- package/dist/chunk-YLBERT7N.js +24 -0
- package/dist/chunk-YLBERT7N.js.map +1 -0
- package/dist/chunk-YLGIZRHV.js +114 -0
- package/dist/chunk-YLGIZRHV.js.map +1 -0
- package/dist/chunk-ZMYGXHPT.js +121 -0
- package/dist/chunk-ZMYGXHPT.js.map +1 -0
- package/dist/chunk-ZTEU5JVQ.js +2 -0
- package/dist/chunk-ZTEU5JVQ.js.map +1 -0
- package/dist/{chunk-2WVX3LEC.js → chunk-ZVP6GB4G.js} +1 -1
- package/dist/chunk-ZVP6GB4G.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-ZLSXGPTV.js +2 -0
- package/dist/{compaction-NWEMOSHA.js → compaction-FD4R23HZ.js} +4 -4
- package/dist/config-HMW3EBZL.js +2 -0
- package/dist/config-mutator-4SZ5YEVD.js +2 -0
- package/dist/{conflict-detector-D7VIYPDI.js → conflict-detector-ZPOT3M2C.js} +2 -2
- package/dist/conflict-detector-ZPOT3M2C.js.map +1 -0
- package/dist/context-planner-AQHJMVY2.js +17 -0
- package/dist/context-planner-AQHJMVY2.js.map +1 -0
- package/dist/debug-3MEUO2P2.js +2 -0
- package/dist/{discover-XJXQXXIO.js → discover-D4VVRNAI.js} +2 -2
- package/dist/discover-D4VVRNAI.js.map +1 -0
- package/dist/doctor-LRYV3QLK.js +2 -0
- package/dist/editor-chat-OB7IQOB5.js +137 -0
- package/dist/editor-chat-OB7IQOB5.js.map +1 -0
- package/dist/entry-triage-LK34TSQD.js +2 -0
- package/dist/entry-triage-LK34TSQD.js.map +1 -0
- package/dist/facts-YVEJOM7O.js +22 -0
- package/dist/facts-YVEJOM7O.js.map +1 -0
- package/dist/heal-FVLBHYP7.js +2 -0
- package/dist/index.d.ts +1040 -64
- package/dist/index.js +12 -19
- package/dist/index.js.map +1 -1
- package/dist/{ingest-whatsapp-LZE62INA.js → ingest-whatsapp-NS4OWDOU.js} +2 -2
- package/dist/loader-4ZGTK7LD.js +2 -0
- package/dist/loader-BOKE5M5S.js +2 -0
- package/dist/memory-extract-F5ZHWY5E.js +2 -0
- package/dist/{migrate-v2-QO7SLKIM.js → migrate-v2-V4WN2VVM.js} +2 -2
- package/dist/ntfy-X7EPB3TG.js +4 -0
- package/dist/ntfy-X7EPB3TG.js.map +1 -0
- package/dist/processes-WU23U2OJ.js +2 -0
- package/dist/program-GSZ5AYAM.js +2 -0
- package/dist/{projects-api-KRME2YKZ.js → projects-api-QE6L65SK.js} +2 -2
- package/dist/{projects-mutate-3NHVG437.js → projects-mutate-MY7JTLSZ.js} +2 -2
- package/dist/providers-KO2DODIO.js +2 -0
- package/dist/query-SQDHE4HV.js +2 -0
- package/dist/questions-4B4KSPXG.js +3 -0
- package/dist/questions-4B4KSPXG.js.map +1 -0
- package/dist/recipes-7S4MC5OJ.js +2 -0
- package/dist/registry-LE6FWYZC.js +2 -0
- package/dist/review-candidates-RQU5KNZM.js +4 -0
- package/dist/review-candidates-RQU5KNZM.js.map +1 -0
- package/dist/review-store-B4YE3EFK.js +4 -0
- package/dist/review-store-B4YE3EFK.js.map +1 -0
- package/dist/{rotation-memo-B3EXXSEH.js → rotation-memo-7T75BLEX.js} +2 -2
- package/dist/routing-ILERJXB6.js +2 -0
- package/dist/runner-YRBVNYTY.js +2 -0
- package/dist/scheduler-BFM3EINS.js +2 -0
- package/dist/seat-7WM7II2B.js +2 -0
- package/dist/select-X4KVMOP5.js +2 -0
- package/dist/select-X4KVMOP5.js.map +1 -0
- package/dist/store-MCOA6N4F.js +2 -0
- package/dist/{subagent-WV7QKQ3L.js → subagent-2SJHSSJS.js} +2 -2
- package/dist/{surface-usage-PRL6Z4U3.js → surface-usage-NS74AXSE.js} +2 -2
- package/dist/timers-PCB4ZIBD.js +2 -0
- package/dist/triage-backtest-JNO7RKX7.js +2 -0
- package/dist/triage-backtest-JNO7RKX7.js.map +1 -0
- package/dist/types-H5ENIUIT.js +2 -0
- package/dist/types-OIFU3KSQ.js +2 -0
- package/dist/types-OIFU3KSQ.js.map +1 -0
- package/dist/web/workflow-editor.global.js +1542 -0
- package/dist/web/workflow-editor.global.js.map +1 -0
- package/dist/{whatsapp-state-XMTCSRXU.js → whatsapp-state-6D6FHHD2.js} +2 -2
- package/dist/wiki-2AUFSIKO.js +2 -0
- package/dist/wiki-2AUFSIKO.js.map +1 -0
- package/dist/workflows/templates/branching.yaml +1 -1
- package/dist/{workspace-setup-TBQAHYHJ.js → workspace-setup-B3F3TUDE.js} +2 -2
- package/dist/workspace-setup-B3F3TUDE.js.map +1 -0
- package/package.json +14 -3
- package/scripts/postinstall.mjs +80 -0
- package/dist/agent-5QMITZT6.js +0 -2
- package/dist/architect-SEPWMWS4.js +0 -2
- package/dist/board-dashboard-M3372FLO.js.map +0 -1
- package/dist/chunk-2WVX3LEC.js.map +0 -1
- package/dist/chunk-3WQFCIB6.js +0 -24
- package/dist/chunk-3WQFCIB6.js.map +0 -1
- package/dist/chunk-4YCH6IZV.js +0 -2
- package/dist/chunk-53NRX2NA.js +0 -2
- package/dist/chunk-53NRX2NA.js.map +0 -1
- package/dist/chunk-5U35WAZF.js.map +0 -1
- package/dist/chunk-BFXONUUN.js +0 -108
- package/dist/chunk-BFXONUUN.js.map +0 -1
- package/dist/chunk-BFZUG66X.js +0 -45
- package/dist/chunk-BFZUG66X.js.map +0 -1
- package/dist/chunk-BTHHSY7D.js +0 -13
- package/dist/chunk-BTHHSY7D.js.map +0 -1
- package/dist/chunk-BVGJSPW4.js.map +0 -1
- package/dist/chunk-D46VZ7K7.js +0 -116
- package/dist/chunk-D46VZ7K7.js.map +0 -1
- package/dist/chunk-DJEZ2HVU.js.map +0 -1
- package/dist/chunk-E5CXCI33.js +0 -155
- package/dist/chunk-E5CXCI33.js.map +0 -1
- package/dist/chunk-FDWOECJY.js +0 -54
- package/dist/chunk-FDWOECJY.js.map +0 -1
- package/dist/chunk-H5D7ATXS.js.map +0 -1
- package/dist/chunk-HR4JSRZS.js +0 -3
- package/dist/chunk-HR4JSRZS.js.map +0 -1
- package/dist/chunk-ISEBLHWX.js +0 -118
- package/dist/chunk-ISEBLHWX.js.map +0 -1
- package/dist/chunk-JP5IEXR7.js +0 -32
- package/dist/chunk-JTJIPNXY.js.map +0 -1
- package/dist/chunk-K2CAPYES.js +0 -5
- package/dist/chunk-LDHPMJ7M.js.map +0 -1
- package/dist/chunk-METYYZKQ.js +0 -1115
- package/dist/chunk-METYYZKQ.js.map +0 -1
- package/dist/chunk-MTAPOCGA.js +0 -47
- package/dist/chunk-MTAPOCGA.js.map +0 -1
- package/dist/chunk-O6YPEOQF.js +0 -5
- package/dist/chunk-P4VWIVKN.js.map +0 -1
- package/dist/chunk-SR3X25TN.js +0 -3
- package/dist/chunk-SR3X25TN.js.map +0 -1
- package/dist/chunk-UHE5FCPB.js +0 -431
- package/dist/chunk-UHE5FCPB.js.map +0 -1
- package/dist/chunk-V3U4OF7L.js.map +0 -1
- package/dist/chunk-VJUTQAPJ.js +0 -2
- package/dist/chunk-VJUTQAPJ.js.map +0 -1
- package/dist/chunk-VT5XYFDD.js +0 -126
- package/dist/chunk-VT5XYFDD.js.map +0 -1
- package/dist/client-HGKAC6VP.js +0 -2
- package/dist/config-A4RWEJHV.js +0 -2
- package/dist/config-mutator-MNSFUN7D.js +0 -2
- package/dist/conflict-detector-D7VIYPDI.js.map +0 -1
- package/dist/context-planner-45GEWKGL.js +0 -17
- package/dist/context-planner-45GEWKGL.js.map +0 -1
- package/dist/debug-N3B5NVJU.js +0 -2
- package/dist/discover-XJXQXXIO.js.map +0 -1
- package/dist/doctor-3Q36ZFE6.js +0 -2
- package/dist/heal-HZ3EQD52.js +0 -2
- package/dist/loader-5CA3O5GM.js +0 -2
- package/dist/loader-6IHUCPVO.js +0 -2
- package/dist/memory-extract-AENFVVO5.js +0 -2
- package/dist/processes-UIC4XXA6.js +0 -2
- package/dist/program-SAUNRCCD.js +0 -2
- package/dist/providers-L27BJ5A6.js +0 -2
- package/dist/query-HZYYRCLU.js +0 -2
- package/dist/recipes-EPLE2SNT.js +0 -2
- package/dist/registry-HK3WQ7BS.js +0 -2
- package/dist/runner-BFYDWPUO.js +0 -2
- package/dist/scheduler-6T2SU5ST.js +0 -2
- package/dist/store-I3HEWVLQ.js +0 -2
- package/dist/timers-5LPZN55J.js +0 -2
- package/dist/types-VECTMYTO.js +0 -2
- package/dist/types-Y6ZCZ52Z.js +0 -2
- package/dist/wiki-M6YNRFC4.js +0 -2
- /package/dist/{App-BPNHIGUX.js.map → App-CKPH3HHF.js.map} +0 -0
- /package/dist/{ChatApp-6XSSZB4U.js.map → ChatApp-MX7IG4K2.js.map} +0 -0
- /package/dist/{agent-5QMITZT6.js.map → agent-2U574KBZ.js.map} +0 -0
- /package/dist/{architect-SEPWMWS4.js.map → article-fields-L4D2BOGY.js.map} +0 -0
- /package/dist/{audit-JU3HWISB.js.map → audit-4245CX6P.js.map} +0 -0
- /package/dist/{builtin-FRKAQMFS.js.map → builtin-B67DMJ3M.js.map} +0 -0
- /package/dist/{chunk-4YCH6IZV.js.map → calibration-ILS5OI44.js.map} +0 -0
- /package/dist/{client-HGKAC6VP.js.map → chunk-FIOB3MEX.js.map} +0 -0
- /package/dist/{config-A4RWEJHV.js.map → client-ZLSXGPTV.js.map} +0 -0
- /package/dist/{compaction-NWEMOSHA.js.map → compaction-FD4R23HZ.js.map} +0 -0
- /package/dist/{config-mutator-MNSFUN7D.js.map → config-HMW3EBZL.js.map} +0 -0
- /package/dist/{debug-N3B5NVJU.js.map → config-mutator-4SZ5YEVD.js.map} +0 -0
- /package/dist/{doctor-3Q36ZFE6.js.map → debug-3MEUO2P2.js.map} +0 -0
- /package/dist/{heal-HZ3EQD52.js.map → doctor-LRYV3QLK.js.map} +0 -0
- /package/dist/{loader-5CA3O5GM.js.map → heal-FVLBHYP7.js.map} +0 -0
- /package/dist/{ingest-whatsapp-LZE62INA.js.map → ingest-whatsapp-NS4OWDOU.js.map} +0 -0
- /package/dist/{loader-6IHUCPVO.js.map → loader-4ZGTK7LD.js.map} +0 -0
- /package/dist/{memory-extract-AENFVVO5.js.map → loader-BOKE5M5S.js.map} +0 -0
- /package/dist/{processes-UIC4XXA6.js.map → memory-extract-F5ZHWY5E.js.map} +0 -0
- /package/dist/{migrate-v2-QO7SLKIM.js.map → migrate-v2-V4WN2VVM.js.map} +0 -0
- /package/dist/{program-SAUNRCCD.js.map → processes-WU23U2OJ.js.map} +0 -0
- /package/dist/{providers-L27BJ5A6.js.map → program-GSZ5AYAM.js.map} +0 -0
- /package/dist/{projects-api-KRME2YKZ.js.map → projects-api-QE6L65SK.js.map} +0 -0
- /package/dist/{projects-mutate-3NHVG437.js.map → projects-mutate-MY7JTLSZ.js.map} +0 -0
- /package/dist/{query-HZYYRCLU.js.map → providers-KO2DODIO.js.map} +0 -0
- /package/dist/{recipes-EPLE2SNT.js.map → query-SQDHE4HV.js.map} +0 -0
- /package/dist/{registry-HK3WQ7BS.js.map → recipes-7S4MC5OJ.js.map} +0 -0
- /package/dist/{runner-BFYDWPUO.js.map → registry-LE6FWYZC.js.map} +0 -0
- /package/dist/{rotation-memo-B3EXXSEH.js.map → rotation-memo-7T75BLEX.js.map} +0 -0
- /package/dist/{scheduler-6T2SU5ST.js.map → routing-ILERJXB6.js.map} +0 -0
- /package/dist/{store-I3HEWVLQ.js.map → runner-YRBVNYTY.js.map} +0 -0
- /package/dist/{surface-usage-PRL6Z4U3.js.map → scheduler-BFM3EINS.js.map} +0 -0
- /package/dist/{timers-5LPZN55J.js.map → seat-7WM7II2B.js.map} +0 -0
- /package/dist/{types-VECTMYTO.js.map → store-MCOA6N4F.js.map} +0 -0
- /package/dist/{subagent-WV7QKQ3L.js.map → subagent-2SJHSSJS.js.map} +0 -0
- /package/dist/{types-Y6ZCZ52Z.js.map → surface-usage-NS74AXSE.js.map} +0 -0
- /package/dist/{wiki-M6YNRFC4.js.map → timers-PCB4ZIBD.js.map} +0 -0
- /package/dist/{workspace-setup-TBQAHYHJ.js.map → types-H5ENIUIT.js.map} +0 -0
- /package/dist/{whatsapp-state-XMTCSRXU.js.map → whatsapp-state-6D6FHHD2.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/setup-wizard.ts","../src/daemon/config-mutate.ts","../src/daemon/ui/pages/setup.ts","../src/daemon/token-store.ts","../src/wiki/serve.ts","../src/wiki/mesh.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\nimport { fileURLToPath } from \"url\"\nimport { hostname } from \"os\"\nimport { spawn } from \"child_process\"\nimport type { IncomingMessage, ServerResponse } from \"http\"\nimport { mutateAgentxConfig, writeAgentxConfig } from \"./config-mutate\"\nimport { renderSetupPage } from \"./ui/pages/setup\"\nimport { loadDaemonConfig } from \"./config\"\n\n/** Shape of the pre-render state snapshot — exported so the page file can\n * type its props without re-importing this whole module. */\nexport interface WizardState {\n configExists: boolean\n agentCount: number\n channelCount: number\n nodeName?: string\n}\n\n// --- /setup wizard: form-driven first-run experience for non-technical operators ---\n//\n// This is the web equivalent of `agentx init` + `agentx agent add` + `agentx\n// channel add`, collapsed into one page. It writes agentx.json and scaffolds\n// the first agent's workspace so a business operator can go from a blank\n// machine to a working AgentX install without editing JSON.\n//\n// The route lives on the board-dashboard server (port 4202) because that's the\n// surface operators actually open. It talks to the loopback daemon for the\n// post-install restart; the wizard itself never requires a running daemon.\n\nexport interface WizardPayload {\n nodeName: string\n agent: {\n id: string\n name: string\n triggerWords: string // comma/space separated; we normalise on the server\n tier: \"claude-code\" | \"codex-cli\" | \"opencode\" | \"sdk\" | \"orchestrator\"\n model?: string\n personality?: string // short free text → CLAUDE.md\n }\n telegram?: {\n accountId: string\n botToken: string\n botUsername?: string\n }\n anthropicApiKey?: string\n}\n\n/**\n * Return the current state of the working directory so the wizard can choose\n * between \"fresh install\" and \"add another\" flows.\n */\nexport function wizardState(baseDir: string = process.cwd()): WizardState {\n const file = resolve(baseDir, \"agentx.json\")\n if (!existsSync(file)) return { configExists: false, agentCount: 0, channelCount: 0 }\n try {\n const cfg = JSON.parse(readFileSync(file, \"utf-8\"))\n const agents = cfg.agents ? Object.keys(cfg.agents).length : 0\n const channels = cfg.channels\n ? Object.values(cfg.channels).filter((c: any) => c && c.enabled).length\n : 0\n return { configExists: true, agentCount: agents, channelCount: channels, nodeName: cfg.node?.name }\n } catch {\n return { configExists: false, agentCount: 0, channelCount: 0 }\n }\n}\n\n/**\n * Execute the wizard submission. Returns a short human-readable summary of\n * what was written. Throws on validation error — the caller surfaces the\n * message to the user.\n */\nexport function runWizard(payload: WizardPayload, baseDir: string = process.cwd()): {\n summary: string\n nextSteps: string[]\n backupPath?: string\n} {\n // --- validate ---\n if (!payload.nodeName?.trim()) throw new Error(\"Team name is required.\")\n if (!payload.agent?.id?.trim()) throw new Error(\"Agent id is required.\")\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(payload.agent.id)) {\n throw new Error(\"Agent id must be lowercase (letters, digits, -, _) and start with a letter or digit.\")\n }\n if (!payload.agent.name?.trim()) throw new Error(\"Agent name is required.\")\n const mentions = splitMentions(payload.agent.triggerWords)\n if (mentions.length === 0) throw new Error(\"At least one trigger word is required.\")\n if (\n payload.agent.tier !== \"claude-code\" &&\n payload.agent.tier !== \"codex-cli\" &&\n payload.agent.tier !== \"opencode\" &&\n payload.agent.tier !== \"sdk\" &&\n payload.agent.tier !== \"orchestrator\"\n ) {\n throw new Error(\"AI engine must be claude-code, codex-cli, opencode, sdk, or orchestrator.\")\n }\n\n const configPath = resolve(baseDir, \"agentx.json\")\n const envPath = resolve(baseDir, \".env\")\n const workspaceDir = resolve(baseDir, \"agents\", payload.agent.id)\n const state = wizardState(baseDir)\n\n // --- scaffold workspace ---\n mkdirSync(workspaceDir, { recursive: true })\n const claudeMd = resolve(workspaceDir, \"CLAUDE.md\")\n if (!existsSync(claudeMd)) {\n const personality = payload.agent.personality?.trim() || `You are ${payload.agent.name}, an assistant on the ${payload.nodeName} team.`\n writeFileSync(claudeMd, `# ${payload.agent.name}\\n\\n${personality}\\n`)\n }\n\n // --- build / mutate agentx.json ---\n const telegramTokenRef = payload.telegram\n ? `TG_${payload.agent.id.toUpperCase()}_BOT_TOKEN`\n : undefined\n\n let backupPath: string | undefined\n if (!state.configExists) {\n const cfg = buildFreshConfig(payload, workspaceDir, mentions, telegramTokenRef)\n writeAgentxConfig(cfg, { configPath })\n } else {\n const result = mutateAgentxConfig((cfg) => {\n cfg.node = cfg.node || {}\n if (!cfg.node.name) cfg.node.name = payload.nodeName\n if (!cfg.node.id) cfg.node.id = slugify(payload.nodeName) || \"team\"\n if (!cfg.node.bind) cfg.node.bind = \"127.0.0.1:18800\"\n cfg.node.defaultAgent = cfg.node.defaultAgent || payload.agent.id\n\n cfg.providers = cfg.providers || {}\n if (!cfg.providers.claude) cfg.providers.claude = { apiKey: \"${ANTHROPIC_API_KEY}\" }\n\n cfg.agents = cfg.agents || {}\n if (cfg.agents[payload.agent.id]) throw new Error(`Agent \"${payload.agent.id}\" already exists.`)\n cfg.agents[payload.agent.id] = agentBlock(payload, workspaceDir, mentions)\n\n if (payload.telegram) {\n cfg.channels = cfg.channels || {}\n cfg.channels.telegram = cfg.channels.telegram || { enabled: true, accounts: {}, policy: { dm: \"pair\", group: \"mention-required\" } }\n cfg.channels.telegram.enabled = true\n cfg.channels.telegram.accounts = cfg.channels.telegram.accounts || {}\n cfg.channels.telegram.accounts[payload.telegram.accountId] = telegramAccountBlock(payload, telegramTokenRef!)\n }\n\n return { summary: `added agent \"${payload.agent.id}\"` + (payload.telegram ? \" + telegram account\" : \"\") }\n }, { configPath })\n backupPath = result.backupPath\n }\n\n // --- write / append .env ---\n ensureEnvEntries(envPath, payload, telegramTokenRef)\n\n const nextSteps: string[] = []\n if (payload.anthropicApiKey || !existsSync(resolve(baseDir, \".env\"))) {\n nextSteps.push(\"Double-check the API keys in .env\")\n }\n if (payload.agent.tier === \"claude-code\") {\n nextSteps.push(\"Run claude --version to confirm Claude Code is installed (needed for the claude-code engine)\")\n }\n if (payload.agent.tier === \"codex-cli\") {\n nextSteps.push(\"Run codex --version to confirm Codex CLI is installed (needed for the codex-cli engine)\")\n }\n if (payload.agent.tier === \"opencode\") {\n nextSteps.push(\"Run opencode --version to confirm OpenCode is installed and authenticated\")\n }\n nextSteps.push(\"Start the daemon: agentx daemon start\")\n nextSteps.push(\"Open the dashboard: http://127.0.0.1:4202\")\n\n return {\n summary: state.configExists\n ? `Added agent \"${payload.agent.id}\" to your team.`\n : `Initialized your team with first agent \"${payload.agent.id}\".`,\n nextSteps,\n backupPath,\n }\n}\n\nfunction splitMentions(s: string): string[] {\n return (s || \"\")\n .split(/[,\\s]+/)\n .map((m) => m.trim())\n .filter(Boolean)\n // Normalise: ensure @-prefix entries stay, plain ones are kept as-is.\n .filter((m, i, a) => a.indexOf(m) === i)\n}\n\nfunction slugify(s: string): string {\n return (s || \"\").toLowerCase().replace(/[^a-z0-9-]+/g, \"-\").replace(/^-+|-+$/g, \"\")\n}\n\nfunction buildFreshConfig(payload: WizardPayload, workspaceDir: string, mentions: string[], telegramTokenRef?: string) {\n const id = slugify(payload.nodeName) || \"team\"\n const cfg: any = {\n node: { id, name: payload.nodeName, bind: \"127.0.0.1:18800\", defaultAgent: payload.agent.id },\n providers: { claude: { apiKey: \"${ANTHROPIC_API_KEY}\" } },\n agents: { [payload.agent.id]: agentBlock(payload, workspaceDir, mentions) },\n channels: {},\n crons: {},\n mesh: { enabled: false, peers: [], discovery: \"static\", healthCheck: { interval: 60, timeout: 10 } },\n dashboard: { enabled: true, port: 4202, bind: \"127.0.0.1\", daemonUrl: \"http://localhost:18800\" },\n boards: [],\n }\n if (payload.telegram && telegramTokenRef) {\n cfg.channels.telegram = {\n enabled: true,\n accounts: { [payload.telegram.accountId]: telegramAccountBlock(payload, telegramTokenRef) },\n policy: { dm: \"pair\", group: \"mention-required\" },\n }\n } else {\n cfg.channels.telegram = { enabled: false, accounts: {}, policy: { dm: \"pair\", group: \"mention-required\" } }\n }\n return cfg\n}\n\nfunction agentBlock(payload: WizardPayload, workspaceDir: string, mentions: string[]) {\n const block: any = {\n name: payload.agent.name,\n workspace: workspaceDir,\n tier: payload.agent.tier,\n mentions,\n maxConcurrent: 2,\n permissionMode: \"default\",\n }\n if (payload.agent.model) block.model = payload.agent.model\n if (payload.agent.personality?.trim()) block.systemPrompt = payload.agent.personality.trim().slice(0, 400)\n return block\n}\n\nfunction telegramAccountBlock(payload: WizardPayload, tokenRef: string) {\n // Field names must match telegramAccountSchema (src/daemon/config.ts):\n // token: z.string() — NOT \"botToken\"\n // agentBinding: z.string()\n // Zod silently strips unknown keys, so writing \"botToken\" produced\n // configs that failed validation with `token: Required` even when the\n // operator had TG_<AGENT>_BOT_TOKEN set in .env.\n void payload.telegram?.botUsername // collected by the form but not in the schema; intentional drop.\n return {\n token: \"${\" + tokenRef + \"}\",\n agentBinding: payload.agent.id,\n }\n}\n\nfunction ensureEnvEntries(envPath: string, payload: WizardPayload, telegramTokenRef?: string): void {\n const existing = existsSync(envPath) ? readFileSync(envPath, \"utf-8\") : \"\"\n const lines: string[] = []\n\n if (payload.anthropicApiKey && !existing.includes(\"ANTHROPIC_API_KEY=\")) {\n lines.push(`ANTHROPIC_API_KEY=${payload.anthropicApiKey}`)\n }\n if (payload.telegram && telegramTokenRef && !existing.includes(`${telegramTokenRef}=`)) {\n lines.push(`${telegramTokenRef}=${payload.telegram.botToken}`)\n }\n\n if (!existsSync(envPath)) {\n const header = [\n \"# AgentX environment variables\",\n \"# (Never commit this file — it contains secrets.)\",\n \"\",\n ]\n writeFileSync(envPath, [...header, ...lines, \"\"].join(\"\\n\"))\n return\n }\n if (lines.length > 0) {\n const sep = existing.endsWith(\"\\n\") ? \"\" : \"\\n\"\n appendFileSync(envPath, sep + lines.join(\"\\n\") + \"\\n\")\n }\n}\n\n// --- HTTP handlers used by board-dashboard.ts ---\n\nexport function handleWizardGet(_req: IncomingMessage, res: ServerResponse): void {\n const state = wizardState()\n const defaultNode = state.nodeName || hostname().replace(/\\.local$/, \"\")\n const html = renderSetupPage(state, defaultNode)\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" })\n res.end(html)\n}\n\nexport async function handleWizardPost(req: IncomingMessage, res: ServerResponse): Promise<void> {\n let raw = \"\"\n await new Promise<void>((ok) => { req.on(\"data\", (c) => { raw += c }); req.on(\"end\", () => ok()) })\n let payload: WizardPayload\n try { payload = JSON.parse(raw) }\n catch { res.writeHead(400, { \"Content-Type\": \"application/json\" }); res.end(JSON.stringify({ error: \"Invalid JSON\" })); return }\n try {\n const result = runWizard(payload)\n res.writeHead(200, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify(result))\n } catch (e: any) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" })\n res.end(JSON.stringify({ error: e.message || \"wizard failed\" }))\n }\n}\n\n/**\n * POST /api/setup/start-daemon — fork `agentx daemon start` from the wizard's\n * success page so non-technical operators don't have to switch to a terminal.\n *\n * Spawns detached so the daemon survives the wizard process exiting, then\n * polls the daemon URL until it answers (or we hit the timeout). Returns the\n * resolved URL on success, or the underlying error + the path the operator\n * should hit manually on failure.\n */\nexport async function handleStartDaemonPost(_req: IncomingMessage, res: ServerResponse): Promise<void> {\n res.setHeader(\"Content-Type\", \"application/json\")\n let bind: string\n try {\n const cfg = loadDaemonConfig()\n bind = cfg.node?.bind || \"127.0.0.1:18800\"\n } catch (e: any) {\n res.writeHead(400)\n res.end(JSON.stringify({ ok: false, error: `Config validation failed: ${e.message}` }))\n return\n }\n\n const url = `http://${bind.replace(/^0\\.0\\.0\\.0/, \"127.0.0.1\")}`\n // Already up? (Idempotent — clicking the button twice is harmless.)\n if (await probe(url)) {\n res.writeHead(200)\n res.end(JSON.stringify({ ok: true, url, alreadyRunning: true }))\n return\n }\n\n // dist/cli.js sits beside this file's compiled chunk. Use process.argv[1]\n // when available — that's the entrypoint the user invoked, which works for\n // both `agentx setup` (resolves to dist/cli.js) and `tsx src/cli.ts` in dev.\n const cli = process.argv[1] || resolve(dirname(fileURLToPath(import.meta.url)), \"cli.js\")\n try {\n const child = spawn(process.execPath, [cli, \"daemon\", \"start\"], {\n detached: true,\n stdio: \"ignore\",\n cwd: process.cwd(),\n })\n child.unref()\n } catch (e: any) {\n res.writeHead(500)\n res.end(JSON.stringify({ ok: false, error: `Failed to spawn daemon: ${e.message}`, manualUrl: url }))\n return\n }\n\n // Poll for up to ~12s. Daemon boot covers .env load, config parse, mesh\n // discovery, and bot registration — usually under 4s on a clean install.\n const deadline = Date.now() + 12_000\n while (Date.now() < deadline) {\n await new Promise((ok) => setTimeout(ok, 400))\n if (await probe(url)) {\n res.writeHead(200)\n res.end(JSON.stringify({ ok: true, url, alreadyRunning: false }))\n return\n }\n }\n\n res.writeHead(504)\n res.end(JSON.stringify({\n ok: false,\n error: \"Daemon spawn succeeded but never bound the port within 12s — check `agentx daemon status` and the dotfile log for errors.\",\n manualUrl: url,\n }))\n}\n\nasync function probe(baseUrl: string): Promise<boolean> {\n try {\n const r = await fetch(baseUrl, { method: \"GET\", signal: AbortSignal.timeout(800) })\n return r.status > 0 && r.status < 600\n } catch { return false }\n}\n","import { existsSync, readFileSync, writeFileSync, copyFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { loadDaemonConfig } from \"./config\"\n\n// --- Shared helpers for safe agentx.json mutation ---\n//\n// Multiple CLI commands and the dashboard admin UI all need to read → tweak →\n// write agentx.json without corrupting it on failure. This module owns the\n// atomic-write + backup + best-effort-reload pattern so every caller gets the\n// same guarantees.\n\nexport interface MutateOptions {\n /** Absolute path to the target agentx.json. Defaults to ./agentx.json. */\n configPath?: string\n /** When true, don't write a timestamped backup. Use for operations that are\n * intrinsically reversible (or when the caller already took a snapshot). */\n skipBackup?: boolean\n /** When true, don't POST /reload after writing. Handy in tests. */\n skipReload?: boolean\n}\n\nexport interface MutateResult<T = void> {\n summary: string\n payload?: T\n}\n\n/**\n * Read agentx.json, let the mutator tweak it in-place, then atomically write\n * the result back to disk. The mutator returns a short human-readable summary\n * of what changed and may optionally return a payload for the caller to relay.\n *\n * If the mutator throws, nothing is written. If writing succeeds but the file\n * doesn't round-trip as JSON, we restore from backup and rethrow.\n */\nexport function mutateAgentxConfig<T = void>(\n mutator: (cfg: any) => MutateResult<T> | string,\n opts: MutateOptions = {},\n): { summary: string; payload?: T; backupPath?: string } {\n const file = opts.configPath || resolve(process.cwd(), \"agentx.json\")\n if (!existsSync(file)) throw new Error(`agentx.json not found at ${file}`)\n\n const cfg = JSON.parse(readFileSync(file, \"utf-8\"))\n const raw = mutator(cfg)\n const result: MutateResult<T> = typeof raw === \"string\" ? { summary: raw } : raw\n\n const json = JSON.stringify(cfg, null, 2) + \"\\n\"\n const tmp = file + \".tmp\"\n writeFileSync(tmp, json, \"utf-8\")\n try {\n JSON.parse(readFileSync(tmp, \"utf-8\"))\n } catch (e: any) {\n throw new Error(`config did not round-trip cleanly: ${e.message}`)\n }\n\n let backupPath: string | undefined\n if (!opts.skipBackup) {\n backupPath = `${file}.bak.${Date.now()}`\n copyFileSync(file, backupPath)\n }\n writeFileSync(file, json, \"utf-8\")\n\n if (!opts.skipReload) triggerLocalReload().catch(() => { /* daemon may be off */ })\n\n return { summary: result.summary, payload: result.payload, backupPath }\n}\n\n/**\n * Write a fresh agentx.json from scratch. Used by the setup wizard on a truly\n * empty working directory. Fails if a file already exists (callers must pass\n * the overwrite flag explicitly — we don't want to silently clobber an\n * operator's hand-written config).\n */\nexport function writeAgentxConfig(\n cfg: unknown,\n opts: { configPath?: string; overwrite?: boolean } = {},\n): { file: string } {\n const file = opts.configPath || resolve(process.cwd(), \"agentx.json\")\n if (existsSync(file) && !opts.overwrite) {\n throw new Error(`agentx.json already exists at ${file}. Pass overwrite=true to replace it.`)\n }\n writeFileSync(file, JSON.stringify(cfg, null, 2) + \"\\n\", \"utf-8\")\n return { file }\n}\n\n/**\n * POST to the primary daemon's /reload endpoint so it picks up config changes\n * without a full restart. Silently no-ops if the daemon isn't running.\n */\nasync function triggerLocalReload(): Promise<void> {\n try {\n const cfg = loadDaemonConfig()\n const url = cfg.dashboard.daemonUrl?.replace(/\\/+$/, \"\") || \"http://127.0.0.1:18800\"\n await fetch(`${url}/reload`, { method: \"POST\" }).catch(() => null)\n } catch { /* config didn't load — leave it */ }\n}\n","// --- Setup wizard page ---\n//\n// First-run flow for non-technical operators: team name, first agent,\n// optional Telegram, optional Anthropic key. Writes agentx.json, scaffolds\n// the agent's workspace, writes .env, and points the operator at the next\n// step.\n//\n// The server-side state machine + form handler live in ../../setup-wizard.ts\n// — this file owns ONLY the HTML. That keeps the data flow inspectable\n// (one place) and the visual surface hot-reloadable (edit → rebuild → view).\n\nimport { renderShell, esc, stepCard, field, row, btn } from \"..\"\nimport type { WizardState } from \"../../setup-wizard\"\n\nexport function renderSetupPage(state: WizardState, defaultNode: string): string {\n const heading = state.configExists\n ? (state.agentCount === 0 ? \"Add your first agent\" : \"Add another agent\")\n : \"Set up your team\"\n\n // Custom topbar: /setup is pre-dashboard, we skip the tab nav and show\n // just brand + subtitle + theme switcher + \"Skip to dashboard\" pill.\n const customHeader = `<header class=\"ax-topbar\">\n <div class=\"ax-topbar__left\">\n <div class=\"ax-brand\">\n <span class=\"ax-brand__mark\">AX</span>\n <span class=\"ax-brand__name\">AgentX</span>\n <span class=\"ax-brand__subtitle\">Setup</span>\n </div>\n </div>\n <div class=\"ax-topbar__right\">\n <a href=\"/glossary\" class=\"ax-skip\">? Glossary</a>\n <a href=\"/live\" class=\"ax-skip\">Skip — open dashboard</a>\n <div class=\"ax-theme-switch\" role=\"tablist\" aria-label=\"Theme\">\n <button data-theme-opt=\"dark\">Dark</button>\n <button data-theme-opt=\"light\">Light</button>\n </div>\n </div>\n</header>`\n\n const step1 = stepCard(1, \"Team basics\",\n field({\n name: \"nodeName\",\n label: \"Team name\",\n hint: \"(what you'd call this AgentX install)\",\n value: defaultNode,\n placeholder: \"My Team\",\n required: true,\n })\n )\n\n const step2 = stepCard(2, \"First agent\",\n row([\n field({ name: \"agentName\", label: \"Agent name\", value: \"Assistant\", placeholder: \"Support Bot\", required: true }),\n field({ name: \"agentId\", label: \"Agent id\", hint: \"(lowercase, no spaces)\", value: \"assistant\", pattern: \"[a-z0-9][a-z0-9_-]*\", required: true }),\n ]) +\n field({\n name: \"triggerWords\",\n label: \"Trigger words\",\n hint: `(comma or space separated — e.g. <code>@support, support</code>)`,\n value: \"@assistant, assistant\",\n required: true,\n }) +\n row([\n field({\n name: \"tier\",\n label: \"AI engine\",\n type: \"select\",\n options: [\n { value: \"claude-code\", label: \"Claude Code (recommended)\", selected: true },\n { value: \"codex-cli\", label: \"Codex CLI\" },\n { value: \"opencode\", label: \"OpenCode CLI\" },\n { value: \"sdk\", label: \"Anthropic API (BYO key)\" },\n { value: \"orchestrator\", label: \"Orchestrator (any provider)\" },\n ],\n }),\n field({\n name: \"model\",\n label: \"Model\",\n hint: \"(optional)\",\n value: \"claude-sonnet-4-6\",\n placeholder: \"claude-sonnet-4-6\",\n }),\n ]) +\n field({\n name: \"personality\",\n label: \"Personality / instructions\",\n hint: \"(optional — what this agent does, in plain English)\",\n type: \"textarea\",\n placeholder: \"You are a support agent for Acme Co. Answer customer questions about our product, be friendly, keep replies short.\",\n })\n )\n\n const step3 = stepCard(3, \"First channel\", `\n <label class=\"ax-toggle\"><input type=\"checkbox\" id=\"enableTelegram\" /> Connect Telegram now</label>\n <div class=\"ax-optional\" id=\"telegramFields\">\n ${field({ name: \"telegramAccountId\", label: \"Account id\", hint: `(a label you choose — e.g. <code>support</code>)`, value: \"default\" })}\n ${field({ name: \"telegramBotToken\", label: \"Bot token\", hint: `(from <a href=\"https://t.me/BotFather\" target=\"_blank\">@BotFather</a>)`, placeholder: \"123456:ABCdef...\" })}\n ${field({ name: \"telegramBotUsername\", label: \"Bot username\", hint: `(optional — e.g. <code>my_support_bot</code>)`, placeholder: \"my_support_bot\" })}\n </div>\n `)\n\n const step4 = stepCard(4, \"Anthropic API key\",\n field({\n name: \"anthropicApiKey\",\n label: \"API key\",\n hint: \"(optional — skip if you're using Claude Code)\",\n type: \"password\",\n placeholder: \"sk-ant-…\",\n extraAttrs: `autocomplete=\"off\"`,\n }) +\n `<div class=\"ax-hint-block\">\n The key is stored in <code>.env</code> as <code>ANTHROPIC_API_KEY</code> and used to authenticate requests to Anthropic. Keep it private.\n </div>`\n )\n\n const banner = state.configExists\n ? `<div class=\"ax-banner\">Existing install: <b>${state.agentCount}</b> agent(s), <b>${state.channelCount}</b> channel(s) already configured. This wizard will add to it.</div>`\n : \"\"\n\n const body = `<div class=\"ax-wizard\">\n <h1 class=\"ax-wizard__h1\">${esc(heading)}</h1>\n <p class=\"ax-wizard__lead\">Fill in the basics below. We'll write <code>agentx.json</code>, set up a folder for your first agent, and point you at the next step.</p>\n\n ${banner}\n\n <div id=\"msg\" class=\"ax-msg\"></div>\n\n <form id=\"wizard\" autocomplete=\"off\">\n ${step1}\n ${step2}\n ${step3}\n ${step4}\n <footer class=\"ax-actions\">\n ${btn(\"Save and continue\", { type: \"submit\", primary: true, id: \"submitBtn\" })}\n ${btn(\"Skip, I'll do it later\", { onclick: \"window.location.href='/live'\" })}\n </footer>\n </form>\n</div>`\n\n return renderShell({\n title: \"AgentX — Setup\",\n activeTab: \"custom\",\n subtitle: \"Setup\",\n customHeader,\n noMain: true,\n body,\n css: SETUP_PAGE_CSS,\n scripts: SETUP_SCRIPT,\n })\n}\n\n/** CSS that's specific to the setup page — wizard layout + optional-fields\n * toggle + result messaging. Everything reusable (form fields, step cards,\n * buttons) comes from AX_COMPONENTS_CSS. */\nconst SETUP_PAGE_CSS = `\n/* Pre-dashboard topbar variant — no tab nav, the pills are the only chrome. */\n.ax-topbar__right a.ax-skip {\n padding: 4px 10px; border: 1px solid var(--ax-border-2); border-radius: 4px;\n color: var(--ax-text-2); font-size: var(--ax-fs-xs); text-decoration: none;\n}\n.ax-topbar__right a.ax-skip:hover { color: var(--ax-accent); border-color: var(--ax-accent); }\n\n/* Wizard frame */\n.ax-wizard { max-width: 680px; margin: 0 auto; padding: 36px 22px 80px; }\n.ax-wizard__h1 { font-size: 22px; font-weight: 600; margin: 0 0 4px; letter-spacing: -0.015em; }\n.ax-wizard__lead { color: var(--ax-muted); margin: 0 0 24px; font-size: var(--ax-fs); line-height: 1.55; }\n.ax-wizard__lead code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-size: var(--ax-fs-sm); }\n\n/* Info banner (\"You already have N agents…\") */\n.ax-banner {\n background: color-mix(in oklch, var(--ax-accent) 10%, var(--ax-bg-elev));\n border: 1px solid color-mix(in oklch, var(--ax-accent) 30%, var(--ax-border-2));\n border-radius: var(--ax-radius);\n padding: 10px 14px; margin-bottom: 18px;\n color: var(--ax-text-2); font-size: var(--ax-fs-sm);\n}\n.ax-banner b { color: var(--ax-text); font-weight: 600; }\n\n/* \"Connect Telegram now\" toggle + collapsible fields */\n.ax-toggle { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; font-size: var(--ax-fs); color: var(--ax-text); cursor: pointer; user-select: none; }\n.ax-toggle input { width: auto; accent-color: var(--ax-accent); }\n.ax-optional { border-left: 2px solid var(--ax-border-2); padding-left: 14px; margin-top: 10px; display: none; }\n.ax-optional.is-show { display: block; }\n.ax-hint-block { font-size: var(--ax-fs-xs); color: var(--ax-muted); margin-top: 8px; line-height: 1.55; }\n.ax-hint-block code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-xs); }\n\n/* Form footer */\n.ax-actions {\n display: flex; gap: 10px; padding: 20px 0 0; align-items: center;\n border-top: 1px solid var(--ax-border); margin-top: 20px;\n}\n\n/* Server-response messaging (success / error blocks) */\n.ax-msg { margin: 14px 0; padding: 12px 16px; border-radius: var(--ax-radius); font-size: var(--ax-fs); display: none; }\n.ax-msg.is-ok {\n display: block;\n background: color-mix(in oklch, var(--ax-accent) 12%, transparent);\n color: var(--ax-accent);\n border: 1px solid color-mix(in oklch, var(--ax-accent) 40%, transparent);\n}\n.ax-msg.is-err {\n display: block;\n background: color-mix(in oklch, var(--ax-err) 12%, transparent);\n color: var(--ax-err);\n border: 1px solid color-mix(in oklch, var(--ax-err) 40%, transparent);\n}\n.ax-next-steps { margin-top: 12px; padding-left: 20px; color: var(--ax-text-2); }\n.ax-next-steps li { margin: 4px 0; }\n.ax-next-steps code { background: var(--ax-surface-2); padding: 1px 6px; border-radius: 3px; color: var(--ax-text); font-family: var(--ax-mono); font-size: var(--ax-fs-sm); }\n.ax-startd { margin-top: 14px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }\n.ax-startd__status { color: var(--ax-text-2); font-size: var(--ax-fs-sm); }\n.ax-startd__status a { color: var(--ax-accent); }`\n\nconst SETUP_SCRIPT = `<script>\ndocument.getElementById('enableTelegram').addEventListener('change', (e) => {\n document.getElementById('telegramFields').classList.toggle('is-show', e.target.checked);\n});\ndocument.getElementById('wizard').addEventListener('submit', async (e) => {\n e.preventDefault();\n const f = e.target;\n const msg = document.getElementById('msg');\n const btn = document.getElementById('submitBtn');\n msg.className = 'ax-msg'; msg.style.display = 'none';\n btn.disabled = true; btn.textContent = 'Saving…';\n const telegramEnabled = document.getElementById('enableTelegram').checked;\n const payload = {\n nodeName: f.nodeName.value.trim(),\n agent: {\n id: f.agentId.value.trim(),\n name: f.agentName.value.trim(),\n triggerWords: f.triggerWords.value.trim(),\n tier: f.tier.value,\n model: f.model.value.trim() || undefined,\n personality: f.personality.value.trim() || undefined,\n },\n anthropicApiKey: f.anthropicApiKey.value.trim() || undefined,\n };\n if (telegramEnabled) {\n payload.telegram = {\n accountId: f.telegramAccountId.value.trim() || 'default',\n botToken: f.telegramBotToken.value.trim(),\n botUsername: f.telegramBotUsername.value.trim() || undefined,\n };\n if (!payload.telegram.botToken) {\n msg.className = 'ax-msg is-err'; msg.textContent = 'Bot token is required when Telegram is enabled.';\n btn.disabled = false; btn.textContent = 'Save and continue'; return;\n }\n }\n try {\n const r = await fetch('/api/setup', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'agentx-wizard' },\n body: JSON.stringify(payload),\n });\n const data = await r.json();\n if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status));\n const steps = data.nextSteps.map((s) => '<li>' + s.replace(/\\\\\\`([^\\\\\\`]+)\\\\\\`/g, '<code>$1</code>') + '</li>').join('');\n msg.className = 'ax-msg is-ok';\n msg.innerHTML = '<b>' + data.summary + '</b><ol class=\"ax-next-steps\">' + steps + '</ol>'\n + '<div class=\"ax-startd\"><button type=\"button\" id=\"startDaemonBtn\" class=\"ax-btn ax-btn--primary\">Start daemon now</button>'\n + ' <span id=\"startDaemonStatus\" class=\"ax-startd__status\"></span></div>';\n btn.textContent = 'Done';\n btn.disabled = true;\n document.getElementById('startDaemonBtn').addEventListener('click', async () => {\n const sb = document.getElementById('startDaemonBtn');\n const ss = document.getElementById('startDaemonStatus');\n sb.disabled = true; sb.textContent = 'Starting…'; ss.textContent = '';\n try {\n const r = await fetch('/api/setup/start-daemon', { method: 'POST' });\n const d = await r.json();\n if (d.ok) {\n sb.textContent = d.alreadyRunning ? 'Already running' : 'Daemon up';\n ss.innerHTML = 'Open <a href=\"' + d.url + '\" target=\"_blank\" rel=\"noopener\">' + d.url + '</a>';\n } else {\n sb.disabled = false; sb.textContent = 'Try again';\n ss.textContent = d.error + (d.manualUrl ? ' Manual: ' + d.manualUrl : '');\n }\n } catch (e) {\n sb.disabled = false; sb.textContent = 'Try again';\n ss.textContent = e.message;\n }\n });\n } catch (err) {\n msg.className = 'ax-msg is-err'; msg.textContent = err.message;\n btn.disabled = false; btn.textContent = 'Save and continue';\n }\n});\n</script>`\n","import { createHash, randomBytes } from \"crypto\"\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { resolve, dirname } from \"path\"\n\n// --- Scoped API tokens ---\n//\n// Tokens let external services (other agentx instances, webhook senders, custom\n// apps) talk to this daemon with explicit scopes instead of sharing a single\n// all-powerful dashboard token.\n//\n// We only ever store the sha256 of each token + its printable prefix. The full\n// token is shown exactly once, at creation time, and the operator copies it.\n// Format: agx_live_<hex(32)> — prefix lets secret-scanners catch leaks.\n//\n// Scopes (string equality; \"agent:*\" grants any agent):\n// dashboard:read — /api/live, /api/agents, /api/task/history\n// dashboard:write — /api/admin/*, config mutation endpoints\n// agent:<id> — /api/public/agents/<id>/messages\n// agent:* — any agent\n// mesh:peer — cross-node mesh (forward-compatible — not enforced yet)\n\nexport const TOKEN_PREFIX = \"agx_live_\"\nconst DEFAULT_FILE = \".agentx/tokens.json\"\n\nexport interface TokenRecord {\n id: string // short public id (shown in lists)\n name: string\n prefix: string // full printable prefix (TOKEN_PREFIX + first-4 chars of body)\n hash: string // sha256 of the full token\n scopes: string[]\n createdAt: string\n expiresAt?: string\n revokedAt?: string\n lastUsedAt?: string\n}\n\nexport class TokenStore {\n private file: string\n\n constructor(baseDir: string = process.cwd(), fileRel: string = DEFAULT_FILE) {\n this.file = resolve(baseDir, fileRel)\n }\n\n private load(): TokenRecord[] {\n if (!existsSync(this.file)) return []\n try { return JSON.parse(readFileSync(this.file, \"utf-8\")) as TokenRecord[] } catch { return [] }\n }\n\n private save(records: TokenRecord[]): void {\n mkdirSync(dirname(this.file), { recursive: true })\n writeFileSync(this.file, JSON.stringify(records, null, 2) + \"\\n\", \"utf-8\")\n }\n\n /**\n * Mint a new token. Returns the full token string AND the record.\n * The full token is the only place the secret is ever exposed — the caller\n * must surface it to the operator immediately; after this call the store only\n * knows the hash.\n */\n create(input: {\n name: string\n scopes: string[]\n expiresInDays?: number\n }): { token: string; record: TokenRecord } {\n if (!input.name?.trim()) throw new Error(\"Token name is required.\")\n if (!Array.isArray(input.scopes) || input.scopes.length === 0) {\n throw new Error(\"At least one scope is required.\")\n }\n for (const s of input.scopes) {\n if (!isValidScope(s)) throw new Error(`Invalid scope: ${s}`)\n }\n const body = randomBytes(32).toString(\"hex\")\n const token = TOKEN_PREFIX + body\n const record: TokenRecord = {\n id: \"tok_\" + randomBytes(4).toString(\"hex\"),\n name: input.name.trim(),\n prefix: TOKEN_PREFIX + body.slice(0, 4),\n hash: hashToken(token),\n scopes: [...input.scopes],\n createdAt: new Date().toISOString(),\n expiresAt: input.expiresInDays\n ? new Date(Date.now() + input.expiresInDays * 86400 * 1000).toISOString()\n : undefined,\n }\n const records = this.load()\n records.push(record)\n this.save(records)\n return { token, record }\n }\n\n list(): TokenRecord[] {\n return this.load()\n }\n\n revoke(id: string): TokenRecord | null {\n const records = this.load()\n const rec = records.find((r) => r.id === id)\n if (!rec) return null\n rec.revokedAt = new Date().toISOString()\n this.save(records)\n return rec\n }\n\n /**\n * Look up a token string. Returns the record on success, null on unknown /\n * revoked / expired. Updates lastUsedAt opportunistically (write-on-success).\n */\n verify(token: string): TokenRecord | null {\n if (!token || !token.startsWith(TOKEN_PREFIX)) return null\n const hash = hashToken(token)\n const records = this.load()\n const rec = records.find((r) => r.hash === hash)\n if (!rec) return null\n if (rec.revokedAt) return null\n if (rec.expiresAt && Date.parse(rec.expiresAt) < Date.now()) return null\n // Throttle lastUsedAt updates to at most once per minute to avoid disk churn.\n const now = Date.now()\n const last = rec.lastUsedAt ? Date.parse(rec.lastUsedAt) : 0\n if (now - last > 60_000) {\n rec.lastUsedAt = new Date(now).toISOString()\n try { this.save(records) } catch { /* best-effort */ }\n }\n return rec\n }\n}\n\n/**\n * Check whether a record has a given required scope. \"agent:<id>\" required\n * matches either itself or \"agent:*\". \"dashboard:write\" implicitly grants\n * \"dashboard:read\" for convenience.\n */\nexport function recordHasScope(record: TokenRecord, required: string): boolean {\n if (record.scopes.includes(required)) return true\n if (required.startsWith(\"agent:\") && record.scopes.includes(\"agent:*\")) return true\n if (required === \"dashboard:read\" && record.scopes.includes(\"dashboard:write\")) return true\n return false\n}\n\nfunction isValidScope(s: string): boolean {\n return (\n s === \"dashboard:read\" ||\n s === \"dashboard:write\" ||\n s === \"agent:*\" ||\n /^agent:[a-z0-9][a-z0-9_-]*$/.test(s) ||\n s === \"mesh:peer\"\n )\n}\n\nexport function hashToken(token: string): string {\n return \"sha256:\" + createHash(\"sha256\").update(token).digest(\"hex\")\n}\n\n/**\n * Extract a bearer token from the request headers. Falls back to the\n * `?token=` query param so EventSource (which can't set headers in most\n * browsers) can still authenticate.\n */\nexport function extractToken(req: { headers: Record<string, string | string[] | undefined>; url?: string }): string | null {\n const auth = req.headers[\"authorization\"]\n const authHeader = Array.isArray(auth) ? auth[0] : auth\n if (authHeader && authHeader.toLowerCase().startsWith(\"bearer \")) {\n return authHeader.slice(7).trim()\n }\n if (req.url) {\n try {\n const u = new URL(req.url, \"http://localhost\")\n const tok = u.searchParams.get(\"token\")\n if (tok) return tok\n } catch { /* */ }\n }\n return null\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"http\"\nimport { markdownToHtml } from \"@/utils/markdown-html\"\nimport { WikiStore } from \"./store\"\nimport { WikiHub } from \"./hub\"\nimport { MeshWikiClient } from \"./mesh\"\nimport type { AgentWikiSummary } from \"./hub\"\n\n// --- Lightweight Markdown → HTML (no deps) ---\n\nfunction escapeHtml(s: string): string {\n return s.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\")\n}\n\n/**\n * Strip legacy section-tag comments from the article body. The old absorb\n * prompt told the LLM to add `<!-- tags: runbook, staging -->` inside\n * sections; in the Farzapedia-aligned design, these are no longer\n * load-bearing and leak through as visible literal text once escaped.\n */\nfunction stripSectionTags(text: string): string {\n return text.replace(/<!--\\s*tags?:[^>]*-->\\s*\\n?/gi, \"\")\n}\n\n/** Render a single tag as a clickable link to the tag-filter route. */\nfunction tagLink(tag: string, agentId?: string): string {\n const href = agentId\n ? `/agent/${encodeURIComponent(agentId)}/tag/${encodeURIComponent(tag)}`\n : `/tag/${encodeURIComponent(tag)}`\n return `<a href=\"${href}\" class=\"tag\">${escapeHtml(tag)}</a>`\n}\n\n/** Render an array of tags as clickable links, space-separated. */\nfunction tagLinks(tags: string[] | undefined, agentId?: string): string {\n return (tags || []).map(t => tagLink(t, agentId)).join(\" \")\n}\n\n/**\n * Resolve the body of a `[[...]]` wikilink to a relative article path.\n *\n * Authors write wikilinks three ways: by title, by path (without `.md`), or\n * by full path (with `.md`). wikiArticles is a title→path map, so we build\n * a path-set on first call and try several normalisations:\n *\n * 1. Exact title lookup (handles [[My Article]])\n * 2. Exact path lookup (handles [[people/alex.md]])\n * 3. Path + \".md\" suffix (handles [[people/alex]])\n * 4. Case-insensitive title scan (forgives minor casing drift)\n *\n * Return the path if any matches, else undefined so the caller can render\n * a broken-link marker.\n */\nfunction resolveWikilink(target: string, wikiArticles: Map<string, string>): string | undefined {\n if (!target) return undefined\n // (1) exact title\n const byTitle = wikiArticles.get(target)\n if (byTitle) return byTitle\n\n // (2) + (3) path lookups\n const pathSet = new Set(wikiArticles.values())\n if (pathSet.has(target)) return target\n const withExt = target.endsWith('.md') ? target : `${target}.md`\n if (pathSet.has(withExt)) return withExt\n\n // (4) case-insensitive title fallback — common when the user writes\n // \"Alex Rivera\" but the canonical title is \"alex rivera\".\n const targetLower = target.toLowerCase()\n for (const [title, path] of wikiArticles) {\n if (title.toLowerCase() === targetLower) return path\n }\n return undefined\n}\n\n/** Wiki-flavoured markdown: the shared renderer plus [[wikilink]] resolution\n * and section-tag stripping, which only articles carry. */\nfunction md(text: string, wikiArticles: Map<string, string>, agentPrefix: string = \"\"): string {\n return markdownToHtml(stripSectionTags(text), {\n wikilink: (target, display) => {\n const path = resolveWikilink(target, wikiArticles)\n if (path) return `<a href=\"${agentPrefix}/article/${encodeURIComponent(path)}\" class=\"wikilink\">${display}</a>`\n return `<a href=\"${agentPrefix}/search?q=${encodeURIComponent(target)}\" class=\"wikilink broken\" title=\"Not found — click to search\">${display}</a>`\n },\n })\n}\n\n// --- CSS ---\n\nconst HEAD_FONTS = `<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,300..600;1,6..72,300..600&family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">`\n\n// Paper/ink editorial palette — warm neutrals, Newsreader display font,\n// Inter Tight for UI, JetBrains Mono for code/meta. Ported from the\n// /Users/you/Downloads/wiki mockup.\nconst CSS = `\n:root {\n --bg: #FAF8F3;\n --bg-raised: #FFFFFF;\n --bg-sunk: #F2EFE7;\n --bg-hover: #EFEBE0;\n --ink: #1A1915;\n --ink-2: #3D3A32;\n --muted: #6B6860;\n --muted-2: #96918A;\n --line: #E4DED0;\n --line-2: #D1CAB8;\n --accent: oklch(55% 0.14 45);\n --accent-soft: oklch(55% 0.14 45 / 0.10);\n --accent-hover: oklch(48% 0.14 45);\n --cool: oklch(55% 0.08 230);\n --cool-soft: oklch(55% 0.08 230 / 0.10);\n --ok: oklch(55% 0.10 145);\n --warn: oklch(60% 0.14 75);\n --err: oklch(55% 0.16 20);\n --font-display: 'Newsreader', Georgia, serif;\n --font-ui: 'Inter Tight', -apple-system, system-ui, sans-serif;\n --font-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;\n --sidebar-w: 260px;\n --topbar-h: 52px;\n --radius: 6px;\n --radius-sm: 4px;\n --radius-lg: 10px;\n --shadow-sm: 0 1px 2px rgba(26,25,21,0.04), 0 1px 1px rgba(26,25,21,0.03);\n --shadow-md: 0 4px 12px rgba(26,25,21,0.06), 0 2px 4px rgba(26,25,21,0.04);\n --shadow-lg: 0 12px 40px rgba(26,25,21,0.12), 0 4px 12px rgba(26,25,21,0.06);\n}\n[data-theme=\"dark\"] {\n --bg: #0F0E0C;\n --bg-raised: #1A1815;\n --bg-sunk: #07060A;\n --bg-hover: #22201C;\n --ink: #EBE7DD;\n --ink-2: #C9C4B7;\n --muted: #8B867B;\n --muted-2: #5E5A52;\n --line: #2A2822;\n --line-2: #3A3830;\n --accent: oklch(70% 0.16 45);\n --accent-soft: oklch(70% 0.16 45 / 0.14);\n --accent-hover: oklch(78% 0.16 45);\n --cool: oklch(70% 0.10 230);\n --cool-soft: oklch(70% 0.10 230 / 0.14);\n --ok: oklch(70% 0.14 145);\n --warn: oklch(75% 0.16 75);\n --err: oklch(68% 0.18 20);\n --shadow-sm: 0 1px 2px rgba(0,0,0,0.4);\n --shadow-md: 0 4px 12px rgba(0,0,0,0.5);\n --shadow-lg: 0 12px 40px rgba(0,0,0,0.7);\n}\n\n* { box-sizing: border-box; margin: 0; padding: 0; }\nhtml, body {\n background: var(--bg); color: var(--ink);\n font-family: var(--font-ui); font-size: 14px; line-height: 1.55;\n font-feature-settings: 'ss01','cv11';\n -webkit-font-smoothing: antialiased; min-height: 100vh;\n}\na { color: var(--accent); text-decoration: none; }\na:hover { color: var(--accent-hover); }\nbutton { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }\ninput, textarea, select { font: inherit; color: inherit; }\ncode, pre, .mono { font-family: var(--font-mono); font-size: 0.92em; }\n.display { font-family: var(--font-display); font-weight: 400; letter-spacing: -0.01em; }\n\n/* ——— App shell ——— */\n.app {\n display: grid;\n grid-template-columns: var(--sidebar-w) 1fr;\n grid-template-rows: var(--topbar-h) 1fr;\n min-height: 100vh;\n}\n.topbar {\n grid-column: 1 / -1;\n display: flex; align-items: center; gap: 16px; padding: 0 20px;\n background: var(--bg); border-bottom: 1px solid var(--line);\n position: sticky; top: 0; z-index: 20; backdrop-filter: blur(8px);\n}\n.topbar-logo {\n display: flex; align-items: center; gap: 10px;\n font-family: var(--font-display); font-size: 18px; color: var(--ink);\n width: calc(var(--sidebar-w) - 20px); padding-right: 12px;\n border-right: 1px solid var(--line); height: 100%;\n text-decoration: none;\n}\n.topbar-logo .mark {\n width: 22px; height: 22px; flex: 0 0 auto;\n display: inline-flex; align-items: center; justify-content: center;\n background: var(--accent); color: var(--bg-raised);\n border-radius: 5px; font-family: var(--font-mono);\n font-size: 11px; font-weight: 700;\n}\n.topbar-search {\n flex: 1; max-width: 520px;\n display: flex; align-items: center; gap: 8px; padding: 6px 12px;\n background: var(--bg-raised); border: 1px solid var(--line);\n border-radius: var(--radius); color: var(--muted);\n transition: border-color 0.15s, background 0.15s;\n}\n.topbar-search:focus-within { border-color: var(--line-2); background: var(--bg-hover); }\n.topbar-search input {\n flex: 1; border: 0; outline: 0; background: transparent;\n color: var(--ink); font: inherit;\n}\n.topbar-search input::placeholder { color: var(--muted); }\n.topbar-search kbd {\n font-family: var(--font-mono); font-size: 11px;\n padding: 2px 6px; border: 1px solid var(--line); border-radius: 3px;\n background: var(--bg); color: var(--muted);\n}\n.topbar-tools { margin-left: auto; display: flex; align-items: center; gap: 4px; }\n.icon-btn {\n width: 32px; height: 32px;\n display: inline-flex; align-items: center; justify-content: center;\n border-radius: var(--radius-sm); color: var(--muted);\n transition: background 0.15s, color 0.15s;\n}\n.icon-btn:hover { background: var(--bg-hover); color: var(--ink); }\n.icon-btn.active { color: var(--accent); background: var(--accent-soft); }\n.icon-btn svg { width: 16px; height: 16px; }\n\n/* ——— Sidebar ——— */\n.sidebar {\n grid-column: 1; grid-row: 2;\n background: var(--bg); border-right: 1px solid var(--line);\n padding: 16px 12px 40px; overflow-y: auto;\n position: sticky; top: var(--topbar-h);\n height: calc(100vh - var(--topbar-h));\n}\n.nav-section { margin-bottom: 20px; }\n.nav-section-title {\n display: flex; align-items: center; justify-content: space-between;\n padding: 4px 10px; font-size: 11px; font-weight: 600;\n text-transform: uppercase; letter-spacing: 0.06em;\n color: var(--muted); margin-bottom: 2px;\n}\n.nav-item {\n display: flex; align-items: center; gap: 8px;\n padding: 5px 10px; color: var(--ink-2);\n font-size: 13px; border-radius: var(--radius-sm);\n line-height: 1.35; cursor: pointer;\n transition: background 0.1s;\n text-decoration: none;\n}\n.nav-item:hover { background: var(--bg-hover); color: var(--ink); }\n.nav-item.active {\n background: var(--accent-soft); color: var(--accent); font-weight: 500;\n}\n.nav-item .count {\n margin-left: auto; font-family: var(--font-mono);\n font-size: 11px; color: var(--muted);\n}\n.nav-item.active .count { color: var(--accent); }\n.nav-agent-header {\n display: flex; align-items: center; gap: 8px;\n padding: 6px 10px; font-size: 13px; color: var(--ink);\n cursor: pointer; border-radius: var(--radius-sm);\n text-decoration: none;\n}\n.nav-agent-header:hover { color: var(--accent); background: var(--bg-hover); }\n.nav-agent-header .name { font-weight: 500; }\n.nav-agent-header .badge {\n margin-left: auto; font-family: var(--font-mono);\n font-size: 10.5px; color: var(--muted);\n}\n.nav-agent-type-label {\n padding: 6px 8px 2px; font-size: 10px;\n text-transform: uppercase; letter-spacing: 0.08em;\n color: var(--muted-2); font-weight: 600;\n}\n.nav-agent-children {\n padding-left: 14px; display: flex; flex-direction: column; gap: 1px;\n margin-top: 2px; border-left: 1px dashed var(--line); margin-left: 10px;\n}\n\n/* ——— Main ——— */\n.main {\n grid-column: 2; grid-row: 2;\n padding: 32px 48px 80px;\n max-width: 1180px; width: 100%;\n}\n.main.article { max-width: 1100px; }\n\n/* ——— Breadcrumb ——— */\n.breadcrumb {\n display: flex; align-items: center; gap: 6px;\n font-size: 12.5px; color: var(--muted);\n margin-bottom: 14px; font-family: var(--font-ui);\n}\n.breadcrumb a { color: var(--muted); }\n.breadcrumb a:hover { color: var(--accent); }\n.breadcrumb .sep { color: var(--muted-2); }\n.breadcrumb .current { color: var(--ink-2); }\n\n/* ——— Display heading ——— */\n.page-head {\n display: flex; align-items: flex-end; justify-content: space-between;\n gap: 24px; padding-bottom: 16px;\n border-bottom: 1px solid var(--line); margin-bottom: 28px;\n}\n.page-head h1 {\n font-family: var(--font-display); font-size: 38px;\n font-weight: 400; letter-spacing: -0.015em; line-height: 1.1;\n}\n.page-head .page-sub {\n font-size: 13.5px; color: var(--muted);\n margin-top: 6px; max-width: 60ch; line-height: 1.5;\n}\n.page-head .page-actions { display: flex; gap: 8px; flex: 0 0 auto; }\n\n/* ——— Buttons ——— */\n.btn {\n display: inline-flex; align-items: center; gap: 6px;\n padding: 6px 12px; font-size: 13px;\n color: var(--ink-2); background: var(--bg-raised);\n border: 1px solid var(--line); border-radius: var(--radius-sm);\n transition: all 0.1s; text-decoration: none;\n}\n.btn:hover { background: var(--bg-hover); border-color: var(--line-2); color: var(--ink); }\n.btn.primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }\n.btn.primary:hover { background: var(--ink-2); color: var(--bg); }\n.btn.ghost { background: transparent; border-color: transparent; }\n.btn.ghost:hover { background: var(--bg-hover); }\n.btn svg { width: 14px; height: 14px; }\n\n/* ——— Stat strip ——— */\n.stats, .stat-strip {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));\n gap: 0;\n background: var(--bg-raised); border: 1px solid var(--line);\n border-radius: var(--radius); overflow: hidden;\n margin-bottom: 28px;\n}\n.stat-card, .stat-cell {\n padding: 16px 20px;\n border-right: 1px solid var(--line);\n display: flex; flex-direction: column; gap: 4px;\n background: transparent; text-align: left;\n}\n.stat-card:last-child, .stat-cell:last-child { border-right: none; }\n.stat-card .label, .stat-cell .label {\n font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;\n color: var(--muted); font-weight: 600;\n}\n.stat-card .number, .stat-cell .number {\n font-family: var(--font-display); font-size: 32px; font-weight: 400;\n line-height: 1; color: var(--ink);\n}\n\n/* ——— Section heading ——— */\n.section-head, h2.section {\n display: flex; align-items: baseline; justify-content: space-between;\n margin: 40px 0 14px;\n}\n.section-head h2 {\n font-family: var(--font-display); font-size: 22px;\n font-weight: 400; letter-spacing: -0.01em; color: var(--ink);\n border: none; padding: 0;\n}\n\n/* ——— Agent grid (hub home) ——— */\n.agent-grid {\n display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 14px;\n}\n.agent-card {\n background: var(--bg-raised); border: 1px solid var(--line);\n border-radius: var(--radius); padding: 18px 20px;\n display: flex; flex-direction: column; gap: 12px;\n transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;\n}\n.agent-card:hover {\n border-color: var(--line-2); box-shadow: var(--shadow-md);\n transform: translateY(-1px);\n}\n.agent-card h3 {\n font-family: var(--font-display); font-size: 20px; font-weight: 400;\n color: var(--ink); margin: 0;\n}\n.agent-card h3 a { color: inherit; text-decoration: none; }\n.agent-card h3 a:hover { color: var(--accent); }\n.agent-card .agent-stats {\n display: flex; gap: 20px; font-size: 13px; color: var(--muted);\n}\n.agent-card .agent-stats strong {\n color: var(--ink); font-weight: 600; font-family: var(--font-mono);\n}\n.agent-card .article-list {\n display: flex; flex-direction: column; gap: 4px; margin-top: 4px; font-size: 13px;\n}\n.agent-card .article-row {\n display: flex; flex-wrap: wrap; align-items: center; gap: 4px;\n padding: 2px 0;\n}\n.agent-card .article-row .article-title {\n color: var(--ink-2); text-decoration: none; margin-left: 4px; flex: 1;\n}\n.agent-card .article-row .article-title:hover { color: var(--accent); }\n\n/* ——— Tags ——— */\n.tag {\n display: inline-flex; align-items: center;\n font-family: var(--font-mono); font-size: 11.5px;\n padding: 1px 7px 2px; background: var(--bg-sunk);\n color: var(--ink-2); border-radius: 3px;\n line-height: 1.4; cursor: pointer;\n transition: background 0.1s, color 0.1s;\n text-decoration: none;\n}\n.tag:hover { background: var(--accent-soft); color: var(--accent); }\n.tag.lg { font-size: 12.5px; padding: 3px 10px; }\n\n/* ——— Article meta — inline dl below h1 ——— */\n.meta {\n background: var(--bg-raised); border: 1px solid var(--line);\n border-radius: var(--radius); padding: 14px 18px;\n margin-bottom: 24px; font-size: 13px;\n display: grid; grid-template-columns: auto 1fr;\n gap: 6px 18px;\n}\n.meta dt {\n font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em;\n color: var(--muted); font-weight: 600;\n align-self: center;\n}\n.meta dd { color: var(--ink-2); font-family: var(--font-mono); font-size: 12.5px; }\n\n/* ——— Prose body ——— */\n.prose {\n font-size: 15.5px; line-height: 1.65; color: var(--ink-2);\n max-width: 68ch;\n}\n.main h1, .prose h1 {\n font-family: var(--font-display); font-size: 40px;\n font-weight: 400; letter-spacing: -0.015em; line-height: 1.1;\n color: var(--ink); margin-bottom: 16px;\n border: none; padding: 0;\n}\n.main h2, .prose h2 {\n font-family: var(--font-display); font-size: 26px;\n font-weight: 400; letter-spacing: -0.01em;\n margin: 36px 0 12px; color: var(--ink);\n padding-bottom: 4px; border-bottom: 1px solid var(--line);\n}\n.main h3, .prose h3 {\n font-family: var(--font-display); font-size: 20px; font-weight: 500;\n margin: 28px 0 8px; color: var(--ink);\n}\n.main h4, .prose h4 {\n font-size: 15px; font-weight: 600; margin: 20px 0 6px; color: var(--ink);\n}\n.main p, .prose p { margin: 10px 0; }\n.main ul, .prose ul, .main ol, .prose ol { margin: 10px 0 10px 22px; }\n.main li, .prose li { margin: 4px 0; }\n.main code, .prose code {\n background: var(--bg-sunk); padding: 1px 5px; border-radius: 3px;\n font-family: var(--font-mono);\n}\n.main pre, .prose pre {\n background: var(--bg-sunk); border: 1px solid var(--line);\n border-radius: var(--radius-sm); padding: 14px 16px;\n overflow-x: auto; margin: 14px 0; font-size: 13px;\n}\n.main pre code, .prose pre code { background: none; padding: 0; }\n.main blockquote, .prose blockquote {\n border-left: 3px solid var(--accent);\n padding: 4px 0 4px 14px; color: var(--muted);\n font-style: italic; margin: 14px 0;\n}\n\n/* Wikilinks in prose */\n.prose a.wikilink, .main a.wikilink {\n color: var(--accent);\n text-decoration: underline;\n text-decoration-color: var(--accent-soft);\n text-underline-offset: 3px;\n text-decoration-thickness: 1.5px;\n}\n.prose a.wikilink:hover, .main a.wikilink:hover { text-decoration-color: var(--accent); }\n.prose a.wikilink.broken, .main a.wikilink.broken {\n color: var(--err); text-decoration-color: var(--err);\n text-decoration-style: dashed;\n}\n\n/* ——— Tables ——— */\ntable {\n width: 100%; border-collapse: collapse;\n margin: 14px 0; font-size: 14px;\n}\nth, td {\n border-bottom: 1px solid var(--line);\n padding: 8px 12px; text-align: left;\n}\nth {\n background: var(--bg-sunk); font-weight: 600;\n color: var(--ink); font-size: 12.5px;\n text-transform: uppercase; letter-spacing: 0.04em;\n}\ntr:hover td { background: var(--bg-hover); }\n\n/* ——— Entry cards ——— */\n.entry-card {\n background: var(--bg-raised); border: 1px solid var(--line);\n border-radius: var(--radius);\n padding: 14px 18px; margin: 10px 0;\n transition: border-color 0.15s, box-shadow 0.15s;\n}\n.entry-card:hover { border-color: var(--line-2); box-shadow: var(--shadow-sm); }\n.entry-card .meta-line {\n font-size: 11.5px; color: var(--muted);\n margin-bottom: 6px; font-family: var(--font-mono);\n}\n.entry-card h3 {\n font-family: var(--font-display); font-size: 19px; font-weight: 400;\n color: var(--ink); margin: 4px 0 6px;\n border: none; padding: 0;\n}\n.entry-card h3 a { color: inherit; }\n.entry-card h3 a:hover { color: var(--accent); }\n.entry-card p {\n color: var(--ink-2); font-size: 13.5px;\n line-height: 1.55; margin: 4px 0;\n}\n\n/* ——— Backlinks ——— */\n.backlinks {\n margin-top: 40px; padding: 16px 18px;\n background: var(--bg-raised); border: 1px solid var(--line);\n border-radius: var(--radius);\n}\n.backlinks h4 {\n font-size: 11px; text-transform: uppercase;\n letter-spacing: 0.08em; color: var(--muted);\n font-weight: 600; margin-bottom: 8px;\n}\n.backlinks ul { list-style: none; padding: 0; margin: 0; }\n.backlinks li { padding: 4px 0; font-size: 13.5px; }\n\n/* ——— Health issues list ——— */\n.health-list { display: flex; flex-direction: column; gap: 6px; }\n.health-row {\n display: grid; grid-template-columns: 96px 1fr 2fr;\n gap: 14px; align-items: center;\n padding: 10px 14px; background: var(--bg-raised);\n border: 1px solid var(--line); border-radius: var(--radius-sm);\n font-size: 13px;\n}\n.health-row .kind {\n font-family: var(--font-mono); font-size: 10.5px;\n text-transform: uppercase; letter-spacing: 0.06em;\n color: var(--muted);\n}\n.health-row .article { color: var(--ink); font-weight: 500; }\n.health-row .msg { color: var(--ink-2); }\n\n@media (max-width: 900px) {\n .app { grid-template-columns: 1fr; }\n .topbar-logo { width: auto; border-right: none; }\n .sidebar { display: none; }\n .main { padding: 20px; grid-column: 1; }\n}\n`\n\n// --- HTML Templates ---\n\nfunction pageLayout(title: string, sidebar: string, content: string, searchQuery: string = \"\"): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <title>${escapeHtml(title)} — AgentX Wiki</title>\n ${HEAD_FONTS}\n <style>${CSS}</style>\n <script>(function(){try{var t=localStorage.getItem('wiki-theme');if(t)document.documentElement.setAttribute('data-theme',t)}catch(e){}})();</script>\n</head>\n<body>\n <div class=\"app\">\n <header class=\"topbar\">\n <a href=\"/\" class=\"topbar-logo\">\n <span class=\"mark\">W</span>\n <span class=\"display\">AgentX Wiki</span>\n </a>\n <form class=\"topbar-search\" action=\"/search\" method=\"get\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/></svg>\n <input type=\"search\" name=\"q\" placeholder=\"Search every wiki…\" value=\"${escapeHtml(searchQuery)}\" autocomplete=\"off\" />\n <kbd>⌘K</kbd>\n </form>\n <div class=\"topbar-tools\">\n <button class=\"icon-btn\" id=\"wiki-theme-toggle\" title=\"Toggle theme\" aria-label=\"Toggle theme\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg>\n </button>\n </div>\n </header>\n <nav class=\"sidebar\">${sidebar}</nav>\n <main class=\"main\">${content}</main>\n </div>\n <script>\n (function(){\n var btn = document.getElementById('wiki-theme-toggle');\n if (!btn) return;\n btn.addEventListener('click', function(){\n var cur = document.documentElement.getAttribute('data-theme') === 'dark' ? '' : 'dark';\n if (cur) document.documentElement.setAttribute('data-theme', 'dark');\n else document.documentElement.removeAttribute('data-theme');\n try { localStorage.setItem('wiki-theme', cur); } catch(e){}\n });\n // Cmd/Ctrl-K focuses search\n document.addEventListener('keydown', function(e){\n if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {\n e.preventDefault();\n var inp = document.querySelector('.topbar-search input');\n if (inp) inp.focus();\n }\n });\n })();\n </script>\n</body>\n</html>`\n}\n\n// --- Hub Mode (all agents) ---\n\nfunction groupByDir(articles: Array<{ title: string; path: string; tags?: string[] }>): Map<string, typeof articles> {\n const byDir = new Map<string, typeof articles>()\n for (const a of articles) {\n const dir = a.path.includes(\"/\") ? a.path.split(\"/\").slice(0, -1).join(\"/\") : \"/\"\n const list = byDir.get(dir) || []\n list.push(a)\n byDir.set(dir, list)\n }\n return new Map([...byDir].sort((a, b) => a[0].localeCompare(b[0])))\n}\n\nfunction dirLabel(dir: string): string {\n if (dir === \"/\") return \"Root\"\n return dir.split(\"/\").pop()!.replace(/-/g, \" \")\n}\n\nfunction hubSidebar(agents: AgentWikiSummary[], activePath?: string): string {\n let html = '<div class=\"nav-section\">'\n html += `<a class=\"nav-item${!activePath ? ' active' : ''}\" href=\"/\">Home</a>`\n html += `<a class=\"nav-item\" href=\"/entries\">All entries</a>`\n html += '</div>'\n\n for (const agent of agents) {\n const agentPrefix = `/agent/${encodeURIComponent(agent.agentId)}`\n html += '<div class=\"nav-section\">'\n html += `<a class=\"nav-agent-header\" href=\"${agentPrefix}\">`\n html += `<span class=\"name\">${escapeHtml(agent.agentId)}</span>`\n html += `<span class=\"badge\">${agent.totalArticles}</span>`\n html += `</a>`\n html += '<div class=\"nav-agent-children\">'\n\n const byType = groupByDir(agent.articles)\n for (const [type, articles] of byType) {\n html += `<div class=\"nav-agent-type-label\">${dirLabel(type)}</div>`\n for (const a of articles) {\n const fullPath = `${agent.agentId}/${a.path}`\n const isActive = fullPath === activePath\n html += `<a class=\"nav-item${isActive ? ' active' : ''}\" href=\"${agentPrefix}/article/${encodeURIComponent(a.path)}\">${escapeHtml(a.title)}</a>`\n }\n }\n html += '</div>'\n html += '</div>'\n }\n\n return html\n}\n\nfunction hubHome(hub: WikiHub, allAgents?: AgentWikiSummary[], remoteAgents?: Array<AgentWikiSummary & { nodeId: string }>): string {\n const agents = allAgents || hub.summary()\n const shared = hub.getSharedStore()\n const totalEntries = shared.listEntries().length\n const totalArticles = agents.reduce((s, a) => s + a.totalArticles, 0)\n const totalUnabsorbed = agents.reduce((s, a) => s + a.unabsorbed, 0)\n const remoteIds = new Set((remoteAgents || []).map(r => r.agentId))\n\n let content = `<div class=\"page-head\">\n <div>\n <h1>AgentX Wiki</h1>\n <p class=\"page-sub\">Agent-owned institutional knowledge, typed and cross-referenced. Everything here compiles from raw conversation entries through the nightly absorb pass.</p>\n </div>\n </div>`\n\n // Stat strip\n content += '<div class=\"stat-strip\">'\n content += `<div class=\"stat-cell\"><div class=\"label\">Agents</div><div class=\"number\">${agents.length}</div></div>`\n content += `<div class=\"stat-cell\"><div class=\"label\">Articles</div><div class=\"number\">${totalArticles}</div></div>`\n content += `<div class=\"stat-cell\"><div class=\"label\">Raw entries</div><div class=\"number\">${totalEntries}</div></div>`\n if (totalUnabsorbed > 0) {\n content += `<div class=\"stat-cell\"><div class=\"label\">Unabsorbed</div><div class=\"number\">${totalUnabsorbed}</div></div>`\n }\n if (remoteAgents && remoteAgents.length > 0) {\n const nodes = new Set(remoteAgents.map(r => r.nodeId))\n content += `<div class=\"stat-cell\"><div class=\"label\">Mesh nodes</div><div class=\"number\">${nodes.size}</div></div>`\n }\n content += '</div>'\n\n // Agent cards grid\n content += '<div class=\"section-head\"><h2>Agent wikis</h2></div>'\n content += '<div class=\"agent-grid\">'\n for (const agent of agents) {\n const statusChip = agent.unabsorbed > 0\n ? `<span class=\"tag\" style=\"color:var(--warn);background:color-mix(in oklch, var(--warn) 12%, transparent)\">${agent.unabsorbed} unabsorbed</span>`\n : `<span class=\"tag\" style=\"color:var(--ok);background:color-mix(in oklch, var(--ok) 12%, transparent)\">up to date</span>`\n\n const isRemote = remoteIds.has(agent.agentId) && !hub.listAgents().includes(agent.agentId)\n const remoteBadge = isRemote\n ? ` <span class=\"tag\" style=\"color:var(--cool);background:var(--cool-soft)\">remote</span>`\n : ''\n\n content += `<div class=\"agent-card\">`\n content += `<h3><a href=\"/agent/${encodeURIComponent(agent.agentId)}\">${escapeHtml(agent.agentId)}</a>${remoteBadge}</h3>`\n content += `<div class=\"agent-stats\">\n <span><strong>${agent.totalArticles}</strong> articles</span>\n <span><strong>${agent.totalEntries}</strong> entries</span>\n ${statusChip}\n </div>`\n\n if (agent.articles.length > 0) {\n content += '<div class=\"article-list\">'\n for (const a of agent.articles.slice(0, 6)) {\n const tagPart = tagLinks((a.tags || []).slice(0, 2), agent.agentId)\n content += `<div class=\"article-row\">${tagPart} <a class=\"article-title\" href=\"/agent/${encodeURIComponent(agent.agentId)}/article/${encodeURIComponent(a.path)}\">${escapeHtml(a.title)}</a></div>`\n }\n if (agent.articles.length > 6) {\n content += `<div class=\"article-row\"><a class=\"article-title\" style=\"color:var(--muted)\" href=\"/agent/${encodeURIComponent(agent.agentId)}\">+ ${agent.articles.length - 6} more →</a></div>`\n }\n content += '</div>'\n }\n content += '</div>'\n }\n content += '</div>'\n\n return pageLayout(\"Hub\", hubSidebar(agents), content)\n}\n\nfunction hubAgentOverview(hub: WikiHub, agentId: string, allAgents?: AgentWikiSummary[]): string {\n const agents = allAgents || hub.summary()\n const agentSummary = agents.find(a => a.agentId === agentId)\n if (!agentSummary) return pageLayout(\"Not Found\", hubSidebar(agents), \"<h1>Agent not found</h1>\")\n\n // Use summary articles (may come from remote peer if it has more)\n const isRemote = (agentSummary as any).peerUrl !== undefined\n const entries = hub.getAgentEntries(agentId)\n const articles = agentSummary.articles || []\n\n let content = `<nav class=\"breadcrumb\"><a href=\"/\">Hub</a> <span class=\"sep\">/</span> <span class=\"current\">${escapeHtml(agentId)}</span></nav>`\n content += `<div class=\"page-head\"><div><h1>${escapeHtml(agentId)}</h1>`\n if (isRemote) {\n content += `<p class=\"page-sub\"><span class=\"tag\" style=\"color:var(--cool);background:var(--cool-soft)\">remote @ ${escapeHtml((agentSummary as any).nodeId)}</span></p>`\n }\n content += `</div></div>`\n\n // Stats\n content += '<div class=\"stat-strip\">'\n content += `<div class=\"stat-cell\"><div class=\"label\">Articles</div><div class=\"number\">${agentSummary.totalArticles}</div></div>`\n content += `<div class=\"stat-cell\"><div class=\"label\">Entries</div><div class=\"number\">${agentSummary.totalEntries}</div></div>`\n content += `<div class=\"stat-cell\"><div class=\"label\">Unabsorbed</div><div class=\"number\">${agentSummary.unabsorbed}</div></div>`\n content += '</div>'\n\n // Articles grouped by type\n if (articles.length > 0) {\n content += '<div class=\"section-head\"><h2>Articles</h2><span class=\"mono\" style=\"font-size:12px;color:var(--muted)\">' + articles.length + ' total</span></div>'\n const byType = groupByDir(articles as Array<{ title: string; path: string; tags?: string[] }>)\n for (const [type, list] of byType) {\n content += `<h3 class=\"display\" style=\"font-size:17px;color:var(--muted);font-weight:500;margin:20px 0 8px\">${dirLabel(type)}</h3>`\n content += '<div class=\"agent-card\" style=\"padding:4px 14px\"><div class=\"article-list\">'\n for (const a of list) {\n content += `<div class=\"article-row\">${tagLinks(((a.tags || []) as string[]).slice(0, 3), agentId)} <a class=\"article-title\" href=\"/agent/${encodeURIComponent(agentId)}/article/${encodeURIComponent(a.path)}\">${escapeHtml(a.title)}</a></div>`\n }\n content += '</div></div>'\n }\n }\n\n // Recent entries\n const recent = entries.slice(-5).reverse()\n if (recent.length > 0) {\n content += '<div class=\"section-head\"><h2>Recent entries</h2><a class=\"btn ghost\" href=\"/entries\">See all →</a></div>'\n for (const e of recent) {\n content += `<div class=\"entry-card\">\n <div class=\"meta-line\">${e.date} · via ${escapeHtml(e.source)} · <code>${escapeHtml(e.id)}</code></div>\n <p>${escapeHtml(e.content.slice(0, 300))}${e.content.length > 300 ? \"…\" : \"\"}</p>\n </div>`\n }\n }\n\n // Lint\n const agentStore = hub.getAgentWiki(agentId)\n const issues = agentStore.lint()\n if (issues.length > 0) {\n content += `<div class=\"section-head\"><h2>Health issues</h2><span class=\"mono\" style=\"font-size:12px;color:var(--warn)\">${issues.length}</span></div>`\n content += '<div class=\"health-list\">'\n for (const issue of issues) {\n content += `<div class=\"health-row\">\n <span class=\"kind\">${escapeHtml(issue.type)}</span>\n <span class=\"article\">${escapeHtml(issue.article)}</span>\n <span class=\"msg\">${escapeHtml(issue.message)}</span>\n </div>`\n }\n content += '</div>'\n }\n\n return pageLayout(agentId, hubSidebar(agents, agentId), content)\n}\n\nfunction hubArticlePage(hub: WikiHub, agentId: string, articlePath: string, allAgents?: AgentWikiSummary[]): string | null {\n const store = hub.getAgentWiki(agentId)\n const article = store.readArticle(articlePath)\n if (!article) return null\n\n const agents = allAgents || hub.summary()\n const index = store.rebuildIndex()\n const titleToPath = new Map<string, string>()\n for (const a of index.articles) titleToPath.set(a.title, a.path)\n\n const prefix = `/agent/${encodeURIComponent(agentId)}`\n const backlinks = store.buildBacklinks()\n const inbound = backlinks[article.meta.title] || []\n\n let content = `<nav class=\"breadcrumb\"><a href=\"/\">Hub</a> <span class=\"sep\">/</span> <a href=\"${prefix}\">${escapeHtml(agentId)}</a> <span class=\"sep\">/</span> <span class=\"current\">${escapeHtml(article.meta.title)}</span></nav>`\n content += `<article class=\"prose\"><h1>${escapeHtml(article.meta.title)}</h1>`\n\n // Meta box\n content += '<dl class=\"meta\">'\n if (article.meta.type) {\n content += `<dt>Type</dt><dd>${tagLink(article.meta.type, agentId)}</dd>`\n }\n if (article.meta.tags?.length) {\n content += `<dt>Tags</dt><dd>${tagLinks(article.meta.tags, agentId)}</dd>`\n }\n if (article.meta.owner) content += `<dt>Owner</dt><dd>${escapeHtml(article.meta.owner)}</dd>`\n content += `<dt>Created</dt><dd>${escapeHtml(article.meta.created || '—')}</dd>`\n content += `<dt>Updated</dt><dd>${escapeHtml(article.meta.lastUpdated || '—')}</dd>`\n if (article.meta.sources?.length) {\n content += `<dt>Sources</dt><dd>${article.meta.sources.length} entries</dd>`\n }\n content += '</dl>'\n\n content += md(article.content, titleToPath, prefix)\n\n if (inbound.length > 0) {\n content += '<div class=\"backlinks\"><h4>Pages that link here</h4><ul>'\n for (const link of inbound) {\n const linked = store.readArticle(link)\n content += `<li><a href=\"${prefix}/article/${encodeURIComponent(link)}\">${escapeHtml(linked?.meta.title || link)}</a></li>`\n }\n content += '</ul></div>'\n }\n content += `</article>`\n\n return pageLayout(article.meta.title, hubSidebar(agents, `${agentId}/${articlePath}`), content)\n}\n\nfunction hubEntries(hub: WikiHub, allAgents?: AgentWikiSummary[]): string {\n const agents = allAgents || hub.summary()\n const shared = hub.getSharedStore()\n const entries = shared.listEntries()\n\n let content = '<nav class=\"breadcrumb\"><a href=\"/\">Hub</a> <span class=\"sep\">/</span> <span class=\"current\">Entries</span></nav>'\n content += `<div class=\"page-head\"><div><h1>All raw entries</h1><p class=\"page-sub\">Every conversation line that the ingest pipeline captured. Absorb compiles these into typed articles nightly.</p></div></div>`\n\n const byDate = new Map<string, typeof entries>()\n for (const e of entries) {\n const list = byDate.get(e.date) || []\n list.push(e)\n byDate.set(e.date, list)\n }\n\n for (const [date, dateEntries] of [...byDate].reverse()) {\n content += `<div class=\"section-head\"><h2>${date}</h2><span class=\"mono\" style=\"font-size:12px;color:var(--muted)\">${dateEntries.length} entries</span></div>`\n for (const e of dateEntries) {\n content += `<div class=\"entry-card\">\n <div class=\"meta-line\">\n <a href=\"/agent/${encodeURIComponent(e.agentId)}\">${escapeHtml(e.agentId)}</a>\n · via ${escapeHtml(e.source)} · <code>${escapeHtml(e.id)}</code>\n </div>\n <p>${escapeHtml(e.content.slice(0, 400))}${e.content.length > 400 ? \"…\" : \"\"}</p>\n </div>`\n }\n }\n\n return pageLayout(\"All Entries\", hubSidebar(agents), content)\n}\n\nfunction hubSearch(hub: WikiHub, query: string, allAgents?: AgentWikiSummary[]): string {\n const agents = allAgents || hub.summary()\n let content = `<nav class=\"breadcrumb\"><a href=\"/\">Hub</a> <span class=\"sep\">/</span> <span class=\"current\">Search</span></nav>`\n content += `<div class=\"page-head\"><div><h1>“${escapeHtml(query)}”</h1><p class=\"page-sub\">Full-text search across every agent's wiki.</p></div></div>`\n\n let totalResults = 0\n const blocks: string[] = []\n\n for (const agentSummary of agents) {\n const store = hub.getAgentWiki(agentSummary.agentId)\n const results = store.findRelevant(query, undefined, 10)\n\n if (results.length > 0) {\n blocks.push(`<div class=\"section-head\"><h2>${escapeHtml(agentSummary.agentId)}</h2><span class=\"mono\" style=\"font-size:12px;color:var(--muted)\">${results.length} match${results.length === 1 ? '' : 'es'}</span></div>`)\n for (const r of results) {\n const prefix = `/agent/${encodeURIComponent(agentSummary.agentId)}`\n blocks.push(`<div class=\"entry-card\">\n <div class=\"meta-line\">${tagLinks((r.meta.tags || []).slice(0, 3), agentSummary.agentId)}</div>\n <h3><a href=\"${prefix}/article/${encodeURIComponent(r.path)}\">${escapeHtml(r.meta.title)}</a></h3>\n <p>${escapeHtml(r.content.slice(0, 240))}…</p>\n </div>`)\n }\n totalResults += results.length\n }\n }\n content += blocks.join('')\n\n if (totalResults === 0) content += '<p style=\"color:var(--muted);margin-top:24px\">No matching articles found.</p>'\n\n return pageLayout(`Search: ${query}`, hubSidebar(agents), content, query)\n}\n\n/**\n * Hub-wide tag filter. Shows every article tagged with `tag` across all\n * local agents, grouped by agent. Exact-tag match (not BM25).\n */\nfunction hubTagPage(hub: WikiHub, tag: string, allAgents?: AgentWikiSummary[]): string {\n const agents = allAgents || hub.summary()\n let content = `<nav class=\"breadcrumb\"><a href=\"/\">Hub</a> <span class=\"sep\">/</span> <span class=\"current\">Tag</span></nav>`\n content += `<div class=\"page-head\"><div><h1>${escapeHtml(tag)}</h1><p class=\"page-sub\">All articles tagged <span class=\"tag\">${escapeHtml(tag)}</span>, across every agent.</p></div></div>`\n let total = 0\n for (const agentSummary of agents) {\n let store\n try { store = hub.getAgentWiki(agentSummary.agentId) } catch { continue }\n const matches = store.findByTags([tag], undefined, 100)\n if (matches.length === 0) continue\n content += `<div class=\"section-head\"><h2>${escapeHtml(agentSummary.agentId)}</h2><span class=\"mono\" style=\"font-size:12px;color:var(--muted)\">${matches.length}</span></div>`\n for (const a of matches) {\n const prefix = `/agent/${encodeURIComponent(agentSummary.agentId)}`\n content += `<div class=\"entry-card\">\n <div class=\"meta-line\">${tagLinks((a.meta.tags || []).slice(0, 6), agentSummary.agentId)}</div>\n <h3><a href=\"${prefix}/article/${encodeURIComponent(a.path)}\">${escapeHtml(a.meta.title)}</a></h3>\n </div>`\n }\n total += matches.length\n }\n if (total === 0) content += `<p style=\"color:var(--muted);margin-top:24px\">No articles tagged <code>${escapeHtml(tag)}</code>.</p>`\n return pageLayout(`Tag: ${tag}`, hubSidebar(agents), content)\n}\n\n/** Per-agent tag filter — narrows to one agent's corpus. */\nfunction hubAgentTagPage(hub: WikiHub, agentId: string, tag: string, allAgents?: AgentWikiSummary[]): string {\n const agents = allAgents || hub.summary()\n let store\n try { store = hub.getAgentWiki(agentId) } catch {\n return pageLayout(`Tag: ${tag}`, hubSidebar(agents), `<h1>Agent not found: ${escapeHtml(agentId)}</h1>`)\n }\n const matches = store.findByTags([tag], undefined, 100)\n const prefix = `/agent/${encodeURIComponent(agentId)}`\n let content = `<nav class=\"breadcrumb\"><a href=\"/\">Hub</a> <span class=\"sep\">/</span> <a href=\"${prefix}\">${escapeHtml(agentId)}</a> <span class=\"sep\">/</span> <span class=\"current\">${escapeHtml(tag)}</span></nav>`\n content += `<div class=\"page-head\"><div><h1>${escapeHtml(tag)}</h1><p class=\"page-sub\">${matches.length} article${matches.length === 1 ? \"\" : \"s\"} tagged <code>${escapeHtml(tag)}</code> in <b>${escapeHtml(agentId)}</b>.</p></div></div>`\n for (const a of matches) {\n content += `<div class=\"entry-card\">\n <div class=\"meta-line\">${tagLinks((a.meta.tags || []).slice(0, 6), agentId)}</div>\n <h3><a href=\"${prefix}/article/${encodeURIComponent(a.path)}\">${escapeHtml(a.meta.title)}</a></h3>\n </div>`\n }\n return pageLayout(`${agentId} · ${tag}`, hubSidebar(agents), content)\n}\n\n// --- Single Agent Mode ---\n\nfunction agentSidebar(store: WikiStore, agentId: string, activePath?: string): string {\n const index = store.rebuildIndex()\n const byType = groupByDir(index.articles.map(a => ({ title: a.title, path: a.path, tags: a.tags })))\n\n let html = '<div class=\"nav-section\">'\n html += `<div class=\"nav-section-title\">${escapeHtml(agentId)}</div>`\n html += `<a class=\"nav-item${!activePath ? ' active' : ''}\" href=\"/\">Home</a>`\n html += `<a class=\"nav-item\" href=\"/lint\">Health check</a>`\n html += '</div>'\n\n for (const [type, articles] of byType) {\n html += '<div class=\"nav-section\">'\n html += `<div class=\"nav-section-title\">${dirLabel(type)}</div>`\n for (const a of articles) {\n const isActive = a.path === activePath\n html += `<a class=\"nav-item${isActive ? ' active' : ''}\" href=\"/article/${encodeURIComponent(a.path)}\">${escapeHtml(a.title)}</a>`\n }\n html += '</div>'\n }\n\n return html\n}\n\n// --- Server ---\n\nexport interface WikiHandlerOpts {\n wikiDir: string\n agentFilter?: string\n peerUrls?: string[]\n mode?: \"flat\" | \"graph\" | \"unified\"\n /** Mount this handler under a URL prefix (e.g. \"/admin/wiki\" when\n * embedded in the dashboard). Routes are matched against the path\n * with the prefix stripped, and hrefs in the rendered HTML get the\n * prefix re-prepended so links round-trip back to the same mount. */\n pathPrefix?: string\n}\n\n/** Build the wiki HTTP request handler. Same logic as `startWikiServer`,\n * but factored out so the dashboard can mount it at `/admin/wiki/*` —\n * one HTTP listener instead of two. */\nexport function createWikiHandler(opts: WikiHandlerOpts): (req: IncomingMessage, res: ServerResponse) => Promise<void> {\n const { wikiDir, agentFilter, peerUrls = [], mode = \"graph\", pathPrefix = \"\" } = opts\n const prefix = pathPrefix.replace(/\\/+$/, \"\")\n const hub = new WikiHub(wikiDir, undefined, mode)\n const mesh = peerUrls.length > 0 ? new MeshWikiClient(peerUrls) : null\n\n return async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || \"/\", `http://localhost`)\n const rawPath = decodeURIComponent(url.pathname)\n const path = prefix && rawPath.startsWith(prefix)\n ? (rawPath.slice(prefix.length) || \"/\")\n : rawPath\n\n let html: string | null = null\n let status = 200\n\n try {\n // Handle remote article fetch: /remote/:peerIdx/:agentId/article/:path\n if (path.startsWith(\"/remote/\") && mesh) {\n const parts = path.slice(\"/remote/\".length).split(\"/article/\")\n if (parts.length === 2) {\n const [peerAgent, articlePath] = parts\n const slashIdx = peerAgent.indexOf(\"/\")\n const peerUrl = decodeURIComponent(peerAgent.slice(0, slashIdx))\n const agentId = peerAgent.slice(slashIdx + 1)\n\n const article = await mesh.getRemoteArticle(peerUrl, agentId, articlePath)\n if (article) {\n const remoteAgents = await mesh.getRemoteAgents()\n const allAgents = [...hub.summary(), ...remoteAgents.map(r => ({ ...r, agentId: `${r.agentId} @${r.nodeId}` }))]\n\n let content = `<div class=\"breadcrumb\"><a href=\"/\">Hub</a> / <span class=\"tag\">remote</span> ${escapeHtml(agentId)} @ ${escapeHtml(peerUrl)}</div>`\n content += `<h1>${escapeHtml(article.meta.title)}</h1>`\n content += '<dl class=\"meta\">'\n content += `<dt>Tags</dt><dd>${(article.meta.tags||[]).map(t => `<span class=\"tag\">${t}</span>`).join(\" \")}</dd>`\n content += `<dt>Owner</dt><dd>${escapeHtml(article.meta.owner)}</dd>`\n content += `<dt>Source</dt><dd><span class=\"tag\">remote</span> ${escapeHtml(peerUrl)}</dd>`\n content += `<dt>Updated</dt><dd>${article.meta.lastUpdated}</dd>`\n content += '</dl>'\n\n const titleToPath = new Map<string, string>()\n content += md(article.content, titleToPath)\n\n html = pageLayout(article.meta.title, hubSidebar(allAgents), content)\n }\n }\n }\n\n if (!html && agentFilter) {\n // Single agent mode\n const store = hub.getAgentWiki(agentFilter)\n\n if (path === \"/\" || path === \"\") {\n const index = store.rebuildIndex()\n const entries = hub.getAgentEntries(agentFilter)\n let content = `<h1>${escapeHtml(agentFilter)}</h1>`\n content += '<div class=\"stats\">'\n content += `<div class=\"stat-card\"><div class=\"number\">${index.articles.length}</div><div class=\"label\">Articles</div></div>`\n content += `<div class=\"stat-card\"><div class=\"number\">${entries.length}</div><div class=\"label\">Entries</div></div>`\n content += '</div>'\n if (index.articles.length > 0) {\n content += '<h2>Articles</h2><table><tr><th>Title</th><th>Type</th><th>Updated</th></tr>'\n for (const a of index.articles) {\n content += `<tr><td><a href=\"/article/${encodeURIComponent(a.path)}\">${escapeHtml(a.title)}</a></td><td><span class=\"tag\">${(a.tags||[]).slice(0,3).join(\", \")}</span></td><td>${a.lastUpdated || \"\"}</td></tr>`\n }\n content += '</table>'\n }\n html = pageLayout(agentFilter, agentSidebar(store, agentFilter), content)\n } else if (path.startsWith(\"/article/\")) {\n const articlePath = path.slice(\"/article/\".length)\n const article = store.readArticle(articlePath)\n if (article) {\n const index = store.rebuildIndex()\n const titleToPath = new Map<string, string>()\n for (const a of index.articles) titleToPath.set(a.title, a.path)\n let content = `<h1>${escapeHtml(article.meta.title)}</h1>`\n content += '<dl class=\"meta\">'\n content += `<dt>Tags</dt><dd>${(article.meta.tags||[]).map(t => `<span class=\"tag\">${t}</span>`).join(\" \")}</dd>`\n content += `<dt>Updated</dt><dd>${article.meta.lastUpdated}</dd>`\n if (article.meta.sources?.length) content += `<dt>Sources</dt><dd>${article.meta.sources.length} entries</dd>`\n content += '</dl>'\n content += md(article.content, titleToPath)\n html = pageLayout(article.meta.title, agentSidebar(store, agentFilter, articlePath), content)\n }\n } else if (path === \"/lint\") {\n const issues = store.lint()\n let content = '<h1>Health Check</h1>'\n if (issues.length === 0) { content += '<p style=\"color:green;\">No issues.</p>' }\n else {\n content += `<table><tr><th>Type</th><th>Article</th><th>Issue</th></tr>`\n for (const i of issues) content += `<tr><td><span class=\"tag\">${i.type}</span></td><td>${escapeHtml(i.article)}</td><td>${escapeHtml(i.message)}</td></tr>`\n content += '</table>'\n }\n html = pageLayout(\"Health Check\", agentSidebar(store, agentFilter), content)\n } else if (path === \"/search\") {\n const q = url.searchParams.get(\"q\") || \"\"\n const results = await store.findRelevantReranked(q, undefined, 20)\n let content = `<h1>Search: \"${escapeHtml(q)}\"</h1>`\n for (const r of results) {\n content += `<div class=\"entry-card\"><h3><a href=\"/article/${encodeURIComponent(r.path)}\">${escapeHtml(r.meta.title)}</a></h3><p>${escapeHtml(r.content.slice(0, 200))}...</p></div>`\n }\n if (results.length === 0) content += '<p>No results.</p>'\n html = pageLayout(`Search: ${q}`, agentSidebar(store, agentFilter), content)\n } else if (path.startsWith(\"/tag/\")) {\n const tag = decodeURIComponent(path.slice(\"/tag/\".length))\n const matches = store.findByTags([tag], undefined, 100)\n let content = `<h1>Tag: <a class=\"tag\" href=\"#\">${escapeHtml(tag)}</a></h1>`\n content += `<p>${matches.length} article${matches.length === 1 ? \"\" : \"s\"} tagged <code>${escapeHtml(tag)}</code></p>`\n for (const a of matches) {\n content += `<div class=\"entry-card\"><h3><a href=\"/article/${encodeURIComponent(a.path)}\">${escapeHtml(a.meta.title)}</a></h3><div class=\"meta-line\">${tagLinks((a.meta.tags || []).slice(0, 6))}</div></div>`\n }\n if (matches.length === 0) content += '<p>No articles with this tag.</p>'\n html = pageLayout(`Tag: ${tag}`, agentSidebar(store, agentFilter), content)\n }\n } else {\n // Hub mode — merge local + remote agents\n const remoteAgents = mesh ? await mesh.getRemoteAgents() : []\n const localAgents = hub.summary()\n // Merge: prefer whichever has more articles (local or remote)\n const localMap = new Map(localAgents.map(a => [a.agentId, a]))\n const allAgents: AgentWikiSummary[] = []\n const seen = new Set<string>()\n\n for (const local of localAgents) {\n const remote = remoteAgents.find(r => r.agentId === local.agentId)\n if (remote && remote.totalArticles > local.totalArticles) {\n // Remote has more articles — merge: keep remote articles, combine entry counts\n allAgents.push({\n ...remote,\n totalEntries: Math.max(local.totalEntries, remote.totalEntries),\n unabsorbed: Math.max(local.unabsorbed, remote.unabsorbed),\n })\n } else {\n allAgents.push(local)\n }\n seen.add(local.agentId)\n }\n // Add remote-only agents\n for (const remote of remoteAgents) {\n if (!seen.has(remote.agentId)) {\n allAgents.push(remote)\n seen.add(remote.agentId)\n }\n }\n\n if (path === \"/\" || path === \"\") {\n html = hubHome(hub, allAgents, remoteAgents)\n } else if (path === \"/entries\") {\n html = hubEntries(hub, allAgents)\n } else if (path === \"/search\") {\n html = hubSearch(hub, url.searchParams.get(\"q\") || \"\", allAgents)\n } else if (path.startsWith(\"/tag/\")) {\n html = hubTagPage(hub, decodeURIComponent(path.slice(\"/tag/\".length)), allAgents)\n } else if (path.match(/^\\/agent\\/[^/]+\\/tag\\/.+/)) {\n const m = path.match(/^\\/agent\\/([^/]+)\\/tag\\/(.+)$/)!\n html = hubAgentTagPage(hub, decodeURIComponent(m[1]), decodeURIComponent(m[2]), allAgents)\n } else if (path.startsWith(\"/agent/\")) {\n const rest = path.slice(\"/agent/\".length)\n const slashIdx = rest.indexOf(\"/article/\")\n if (slashIdx === -1) {\n // Agent overview\n html = hubAgentOverview(hub, rest, allAgents)\n } else {\n // Agent article\n const agentId = rest.slice(0, slashIdx)\n const articlePath = rest.slice(slashIdx + \"/article/\".length)\n html = hubArticlePage(hub, agentId, articlePath, allAgents)\n\n // If not found locally, try remote peers\n if (!html && mesh) {\n const remote = remoteAgents.find(r => r.agentId === agentId)\n if (remote) {\n const article = await mesh.getRemoteArticle(remote.peerUrl, agentId, articlePath)\n if (article) {\n let content = `<div class=\"breadcrumb\"><a href=\"/\">Hub</a> / <span class=\"tag\">remote @ ${escapeHtml(remote.nodeId)}</span> / ${escapeHtml(agentId)}</div>`\n content += `<h1>${escapeHtml(article.meta.title)}</h1>`\n content += '<dl class=\"meta\">'\n content += `<dt>Tags</dt><dd>${(article.meta.tags||[]).map(t => `<span class=\"tag\">${t}</span>`).join(\" \")}</dd>`\n content += `<dt>Source</dt><dd><span class=\"tag\">remote</span> ${escapeHtml(remote.nodeId)}</dd>`\n content += `<dt>Updated</dt><dd>${article.meta.lastUpdated}</dd>`\n content += '</dl>'\n content += md(article.content, new Map())\n html = pageLayout(article.meta.title, hubSidebar(allAgents), content)\n }\n }\n }\n }\n }\n }\n\n if (!html) {\n status = 404\n const sidebar = agentFilter\n ? agentSidebar(hub.getAgentWiki(agentFilter), agentFilter)\n : hubSidebar(hub.summary())\n html = pageLayout(\"Not Found\", sidebar, \"<h1>Page not found</h1>\")\n }\n } catch (err: any) {\n status = 500\n html = pageLayout(\"Error\", \"\", `<h1>Error</h1><pre>${escapeHtml(err.message)}</pre>`)\n }\n\n res.writeHead(status, { \"Content-Type\": \"text/html; charset=utf-8\" })\n res.end(prefix ? prefixHrefs(html, prefix) : html)\n }\n}\n\n/** Re-prepend the mount prefix to every absolute internal href the wiki\n * generates (`/article/...`, `/agent/...`, `/tag/...`, `/`, `/search`,\n * `/lint`, `/entries`, `/remote/...`). Skips `//` (protocol-relative)\n * and external links. Cheap: a few regexes on the rendered HTML so we\n * don't need to thread `pathPrefix` through every layout helper.\n *\n * Also injects a small \"← Dashboard\" pill so embedded users have a way\n * back to the AgentX dashboard tabs (the wiki page renders its own\n * full-screen UI and replaces the dashboard chrome). */\nfunction prefixHrefs(html: string, prefix: string): string {\n // href and form action attributes\n html = html.replace(/(\\s(?:href|action)=\")\\/(?!\\/)/g, `$1${prefix}/`)\n // de-dupe if already prefixed\n html = html.replace(new RegExp(`(${prefix})${prefix}/`, \"g\"), `${prefix}/`)\n // back-pill — fixed top-right, neutral styling so it works in any wiki theme\n const pill = `<a href=\"/admin\" style=\"position:fixed;top:10px;right:14px;z-index:9999;padding:5px 11px;background:rgba(0,0,0,0.55);color:#fff;font:12px/1.2 'IBM Plex Sans',system-ui,sans-serif;text-decoration:none;border-radius:6px;border:1px solid rgba(255,255,255,0.18);backdrop-filter:blur(6px)\">← Dashboard</a>`\n html = html.replace(/<body([^>]*)>/, `<body$1>${pill}`)\n return html\n}\n\n/** Thin wrapper that runs the handler on its own port. Kept for the\n * `agentx wiki serve` CLI; the dashboard uses `createWikiHandler`\n * directly. */\nexport function startWikiServer(wikiDir: string, port: number = 4200, agentFilter?: string, peerUrls: string[] = [], mode: \"flat\" | \"graph\" | \"unified\" = \"graph\"): void {\n const handler = createWikiHandler({ wikiDir, agentFilter, peerUrls, mode })\n const server = createServer((req, res) => { void handler(req, res) })\n server.listen(port)\n}\n","import type { AgentWikiSummary } from \"./hub\"\nimport type { WikiArticle } from \"./types\"\n\n/**\n * Fetches wiki data from mesh peers over HTTP.\n * Each peer exposes /wiki/agents, /wiki/entries, /wiki/articles.\n */\nexport class MeshWikiClient {\n private peers: Array<{ url: string; nodeId?: string }>\n private cache: Map<string, { data: any; ts: number }> = new Map()\n private ttl = 30_000 // 30s cache\n\n constructor(peerUrls: string[]) {\n this.peers = peerUrls.map(url => ({ url: url.replace(/\\/$/, \"\") }))\n }\n\n /**\n * Get agent summaries from all peers.\n * Returns remote summaries with nodeId attached.\n */\n async getRemoteAgents(): Promise<Array<AgentWikiSummary & { nodeId: string; peerUrl: string }>> {\n const results: Array<AgentWikiSummary & { nodeId: string; peerUrl: string }> = []\n\n await Promise.all(this.peers.map(async (peer) => {\n try {\n const data = await this.fetchJson(`${peer.url}/wiki/agents`)\n peer.nodeId = data.nodeId\n for (const agent of (data.agents || [])) {\n if (agent.totalEntries > 0 || agent.totalArticles > 0) {\n results.push({ ...agent, nodeId: data.nodeId, peerUrl: peer.url })\n }\n }\n } catch { /* peer unreachable */ }\n }))\n\n return results\n }\n\n /**\n * Get articles for a specific agent from a specific peer.\n */\n async getRemoteArticles(peerUrl: string, agentId: string): Promise<Array<{\n path: string; title: string; tags: string[]; lastUpdated?: string; sources?: string[]\n }>> {\n try {\n const data = await this.fetchJson(`${peerUrl}/wiki/articles?agent=${encodeURIComponent(agentId)}`)\n return data.articles || []\n } catch {\n return []\n }\n }\n\n /**\n * Fetch a specific article's content from a peer.\n */\n async getRemoteArticle(peerUrl: string, agentId: string, articlePath: string): Promise<WikiArticle | null> {\n try {\n const data = await this.fetchJson(\n `${peerUrl}/wiki/article?agent=${encodeURIComponent(agentId)}&path=${encodeURIComponent(articlePath)}`\n )\n return {\n meta: {\n title: data.title,\n tags: data.tags || [],\n owner: data.owner,\n access: \"public\",\n created: data.created,\n lastUpdated: data.lastUpdated,\n sources: data.sources || [],\n },\n content: data.content,\n path: data.path,\n }\n } catch {\n return null\n }\n }\n\n private async fetchJson(url: string): Promise<any> {\n const cached = this.cache.get(url)\n if (cached && Date.now() - cached.ts < this.ttl) return cached.data\n\n const res = await fetch(url, { signal: AbortSignal.timeout(5000) })\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const data = await res.json()\n this.cache.set(url, { data, ts: Date.now() })\n return data\n }\n}\n"],"mappings":"yKAAA,OAAS,cAAAA,EAAY,gBAAAC,GAAc,iBAAAC,GAAe,kBAAAC,GAAgB,aAAAC,OAAiB,KACnF,OAAS,WAAAC,GAAS,WAAAC,MAAe,OACjC,OAAS,iBAAAC,OAAqB,MAC9B,OAAS,YAAAC,OAAgB,KACzB,OAAS,SAAAC,OAAa,gBCFtBC,IAFA,OAAS,cAAAC,EAAY,gBAAAC,EAAc,iBAAAC,EAAe,gBAAAC,OAAoB,KACtE,OAAS,WAAAC,MAAe,OAiCjB,SAASC,GACdC,EACAC,EAAsB,CAAC,EACgC,CACvD,IAAMC,EAAOD,EAAK,YAAcH,EAAQ,QAAQ,IAAI,EAAG,aAAa,EACpE,GAAI,CAACJ,EAAWQ,CAAI,EAAG,MAAM,IAAI,MAAM,4BAA4BA,GAAM,EAEzE,IAAMC,EAAM,KAAK,MAAMR,EAAaO,EAAM,OAAO,CAAC,EAC5CE,EAAMJ,EAAQG,CAAG,EACjBE,EAA0B,OAAOD,GAAQ,SAAW,CAAE,QAASA,CAAI,EAAIA,EAEvEE,EAAO,KAAK,UAAUH,EAAK,KAAM,CAAC,EAAI;AAAA,EACtCI,EAAML,EAAO,OACnBN,EAAcW,EAAKD,EAAM,OAAO,EAChC,GAAI,CACF,KAAK,MAAMX,EAAaY,EAAK,OAAO,CAAC,CACvC,OAASC,EAAP,CACA,MAAM,IAAI,MAAM,sCAAsCA,EAAE,SAAS,CACnE,CAEA,IAAIC,EACJ,OAAKR,EAAK,aACRQ,EAAa,GAAGP,SAAY,KAAK,IAAI,IACrCL,GAAaK,EAAMO,CAAU,GAE/Bb,EAAcM,EAAMI,EAAM,OAAO,EAE5BL,EAAK,YAAYS,GAAmB,EAAE,MAAM,IAAM,CAA0B,CAAC,EAE3E,CAAE,QAASL,EAAO,QAAS,QAASA,EAAO,QAAS,WAAAI,CAAW,CACxE,CAQO,SAASE,GACdR,EACAF,EAAqD,CAAC,EACpC,CAClB,IAAMC,EAAOD,EAAK,YAAcH,EAAQ,QAAQ,IAAI,EAAG,aAAa,EACpE,GAAIJ,EAAWQ,CAAI,GAAK,CAACD,EAAK,UAC5B,MAAM,IAAI,MAAM,iCAAiCC,uCAA0C,EAE7F,OAAAN,EAAcM,EAAM,KAAK,UAAUC,EAAK,KAAM,CAAC,EAAI;AAAA,EAAM,OAAO,EACzD,CAAE,KAAAD,CAAK,CAChB,CAMA,eAAeQ,IAAoC,CACjD,GAAI,CAEF,IAAME,EADMC,EAAiB,EACb,UAAU,WAAW,QAAQ,OAAQ,EAAE,GAAK,yBAC5D,MAAM,MAAM,GAAGD,WAAc,CAAE,OAAQ,MAAO,CAAC,EAAE,MAAM,IAAM,IAAI,CACnE,MAAE,CAA4C,CAChD,CChFO,SAASE,GAAgBC,EAAoBC,EAA6B,CAC/E,IAAMC,EAAUF,EAAM,aACjBA,EAAM,aAAe,EAAI,uBAAyB,oBACnD,mBAIEG,EAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAkBfC,EAAQC,EAAS,EAAG,cACxBC,EAAM,CACJ,KAAM,WACN,MAAO,YACP,KAAM,wCACN,MAAOL,EACP,YAAa,UACb,SAAU,EACZ,CAAC,CACH,EAEMM,EAAQF,EAAS,EAAG,cACxBG,EAAI,CACFF,EAAM,CAAE,KAAM,YAAa,MAAO,aAAc,MAAO,YAAa,YAAa,cAAe,SAAU,EAAK,CAAC,EAChHA,EAAM,CAAE,KAAM,UAAW,MAAO,WAAY,KAAM,yBAA0B,MAAO,YAAa,QAAS,sBAAuB,SAAU,EAAK,CAAC,CAClJ,CAAC,EACDA,EAAM,CACJ,KAAM,eACN,MAAO,gBACP,KAAM,wEACN,MAAO,wBACP,SAAU,EACZ,CAAC,EACDE,EAAI,CACFF,EAAM,CACJ,KAAM,OACN,MAAO,YACP,KAAM,SACN,QAAS,CACP,CAAE,MAAO,cAAe,MAAO,4BAA6B,SAAU,EAAK,EAC3E,CAAE,MAAO,YAAa,MAAO,WAAY,EACzC,CAAE,MAAO,WAAY,MAAO,cAAe,EAC3C,CAAE,MAAO,MAAO,MAAO,yBAA0B,EACjD,CAAE,MAAO,eAAgB,MAAO,6BAA8B,CAChE,CACF,CAAC,EACDA,EAAM,CACJ,KAAM,QACN,MAAO,QACP,KAAM,aACN,MAAO,oBACP,YAAa,mBACf,CAAC,CACH,CAAC,EACDA,EAAM,CACJ,KAAM,cACN,MAAO,6BACP,KAAM,2DACN,KAAM,WACN,YAAa,oHACf,CAAC,CACH,EAEMG,EAAQJ,EAAS,EAAG,gBAAiB;AAAA;AAAA;AAAA,QAGrCC,EAAM,CAAE,KAAM,oBAAqB,MAAO,aAAc,KAAM,wDAAoD,MAAO,SAAU,CAAC;AAAA,QACpIA,EAAM,CAAE,KAAM,mBAAoB,MAAO,YAAa,KAAM,yEAA0E,YAAa,kBAAmB,CAAC;AAAA,QACvKA,EAAM,CAAE,KAAM,sBAAuB,MAAO,eAAgB,KAAM,qDAAiD,YAAa,gBAAiB,CAAC;AAAA;AAAA,GAEvJ,EAEKI,EAAQL,EAAS,EAAG,oBACxBC,EAAM,CACJ,KAAM,kBACN,MAAO,UACP,KAAM,qDACN,KAAM,WACN,YAAa,gBACb,WAAY,oBACd,CAAC,EACD;AAAA;AAAA,WAGF,EAEMK,EAASX,EAAM,aACjB,+CAA+CA,EAAM,+BAA+BA,EAAM,oFAC1F,GAEEY,EAAO;AAAA,8BACeC,EAAIX,CAAO;AAAA;AAAA;AAAA,IAGrCS;AAAA;AAAA;AAAA;AAAA;AAAA,MAKEP;AAAA,MACAG;AAAA,MACAE;AAAA,MACAC;AAAA;AAAA,QAEEI,EAAI,oBAAqB,CAAE,KAAM,SAAU,QAAS,GAAM,GAAI,WAAY,CAAC;AAAA,QAC3EA,EAAI,yBAA0B,CAAE,QAAS,8BAA+B,CAAC;AAAA;AAAA;AAAA,QAK/E,OAAOC,EAAY,CACjB,MAAO,sBACP,UAAW,SACX,SAAU,QACV,aAAAZ,EACA,OAAQ,GACR,KAAAS,EACA,IAAKI,GACL,QAASC,EACX,CAAC,CACH,CAKA,IAAMD,GAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mDA2DjBC,GAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;WF7MrBC,IA4CO,SAASC,GAAYC,EAAkB,QAAQ,IAAI,EAAgB,CACxE,IAAMC,EAAOC,EAAQF,EAAS,aAAa,EAC3C,GAAI,CAACG,EAAWF,CAAI,EAAG,MAAO,CAAE,aAAc,GAAO,WAAY,EAAG,aAAc,CAAE,EACpF,GAAI,CACF,IAAMG,EAAM,KAAK,MAAMC,GAAaJ,EAAM,OAAO,CAAC,EAC5CK,EAASF,EAAI,OAAS,OAAO,KAAKA,EAAI,MAAM,EAAE,OAAS,EACvDG,EAAWH,EAAI,SACjB,OAAO,OAAOA,EAAI,QAAQ,EAAE,OAAQI,GAAWA,GAAKA,EAAE,OAAO,EAAE,OAC/D,EACJ,MAAO,CAAE,aAAc,GAAM,WAAYF,EAAQ,aAAcC,EAAU,SAAUH,EAAI,MAAM,IAAK,CACpG,MAAE,CACA,MAAO,CAAE,aAAc,GAAO,WAAY,EAAG,aAAc,CAAE,CAC/D,CACF,CAOO,SAASK,GAAUC,EAAwBV,EAAkB,QAAQ,IAAI,EAI9E,CAEA,GAAI,CAACU,EAAQ,UAAU,KAAK,EAAG,MAAM,IAAI,MAAM,wBAAwB,EACvE,GAAI,CAACA,EAAQ,OAAO,IAAI,KAAK,EAAG,MAAM,IAAI,MAAM,uBAAuB,EACvE,GAAI,CAAC,wBAAwB,KAAKA,EAAQ,MAAM,EAAE,EAChD,MAAM,IAAI,MAAM,sFAAsF,EAExG,GAAI,CAACA,EAAQ,MAAM,MAAM,KAAK,EAAG,MAAM,IAAI,MAAM,yBAAyB,EAC1E,IAAMC,EAAWC,GAAcF,EAAQ,MAAM,YAAY,EACzD,GAAIC,EAAS,SAAW,EAAG,MAAM,IAAI,MAAM,wCAAwC,EACnF,GACED,EAAQ,MAAM,OAAS,eACvBA,EAAQ,MAAM,OAAS,aACvBA,EAAQ,MAAM,OAAS,YACvBA,EAAQ,MAAM,OAAS,OACvBA,EAAQ,MAAM,OAAS,eAEvB,MAAM,IAAI,MAAM,2EAA2E,EAG7F,IAAMG,EAAaX,EAAQF,EAAS,aAAa,EAC3Cc,EAAUZ,EAAQF,EAAS,MAAM,EACjCe,EAAeb,EAAQF,EAAS,SAAUU,EAAQ,MAAM,EAAE,EAC1DM,EAAQjB,GAAYC,CAAO,EAGjCiB,GAAUF,EAAc,CAAE,UAAW,EAAK,CAAC,EAC3C,IAAMG,EAAWhB,EAAQa,EAAc,WAAW,EAClD,GAAI,CAACZ,EAAWe,CAAQ,EAAG,CACzB,IAAMC,EAAcT,EAAQ,MAAM,aAAa,KAAK,GAAK,WAAWA,EAAQ,MAAM,6BAA6BA,EAAQ,iBACvHU,GAAcF,EAAU,KAAKR,EAAQ,MAAM;AAAA;AAAA,EAAWS;AAAA,CAAe,EAIvE,IAAME,EAAmBX,EAAQ,SAC7B,MAAMA,EAAQ,MAAM,GAAG,YAAY,cACnC,OAEAY,EACJ,GAAKN,EAAM,aA4BTM,EAxBeC,GAAoBnB,GAAQ,CAWzC,GAVAA,EAAI,KAAOA,EAAI,MAAQ,CAAC,EACnBA,EAAI,KAAK,OAAMA,EAAI,KAAK,KAAOM,EAAQ,UACvCN,EAAI,KAAK,KAAIA,EAAI,KAAK,GAAKoB,GAAQd,EAAQ,QAAQ,GAAK,QACxDN,EAAI,KAAK,OAAMA,EAAI,KAAK,KAAO,mBACpCA,EAAI,KAAK,aAAeA,EAAI,KAAK,cAAgBM,EAAQ,MAAM,GAE/DN,EAAI,UAAYA,EAAI,WAAa,CAAC,EAC7BA,EAAI,UAAU,SAAQA,EAAI,UAAU,OAAS,CAAE,OAAQ,sBAAuB,GAEnFA,EAAI,OAASA,EAAI,QAAU,CAAC,EACxBA,EAAI,OAAOM,EAAQ,MAAM,EAAE,EAAG,MAAM,IAAI,MAAM,UAAUA,EAAQ,MAAM,qBAAqB,EAC/F,OAAAN,EAAI,OAAOM,EAAQ,MAAM,EAAE,EAAIe,GAAWf,EAASK,EAAcJ,CAAQ,EAErED,EAAQ,WACVN,EAAI,SAAWA,EAAI,UAAY,CAAC,EAChCA,EAAI,SAAS,SAAWA,EAAI,SAAS,UAAY,CAAE,QAAS,GAAM,SAAU,CAAC,EAAG,OAAQ,CAAE,GAAI,OAAQ,MAAO,kBAAmB,CAAE,EAClIA,EAAI,SAAS,SAAS,QAAU,GAChCA,EAAI,SAAS,SAAS,SAAWA,EAAI,SAAS,SAAS,UAAY,CAAC,EACpEA,EAAI,SAAS,SAAS,SAASM,EAAQ,SAAS,SAAS,EAAIgB,GAAqBhB,EAASW,CAAiB,GAGvG,CAAE,QAAS,gBAAgBX,EAAQ,MAAM,OAASA,EAAQ,SAAW,sBAAwB,GAAI,CAC1G,EAAG,CAAE,WAAAG,CAAW,CAAC,EACG,eA5BG,CACvB,IAAMT,EAAMuB,GAAiBjB,EAASK,EAAcJ,EAAUU,CAAgB,EAC9EO,GAAkBxB,EAAK,CAAE,WAAAS,CAAW,CAAC,EA8BvCgB,GAAiBf,EAASJ,EAASW,CAAgB,EAEnD,IAAMS,EAAsB,CAAC,EAC7B,OAAIpB,EAAQ,iBAAmB,CAACP,EAAWD,EAAQF,EAAS,MAAM,CAAC,IACjE8B,EAAU,KAAK,mCAAmC,EAEhDpB,EAAQ,MAAM,OAAS,eACzBoB,EAAU,KAAK,gGAAgG,EAE7GpB,EAAQ,MAAM,OAAS,aACzBoB,EAAU,KAAK,2FAA2F,EAExGpB,EAAQ,MAAM,OAAS,YACzBoB,EAAU,KAAK,6EAA6E,EAE9FA,EAAU,KAAK,wCAAwC,EACvDA,EAAU,KAAK,4CAA4C,EAEpD,CACL,QAASd,EAAM,aACX,gBAAgBN,EAAQ,MAAM,oBAC9B,2CAA2CA,EAAQ,MAAM,OAC7D,UAAAoB,EACA,WAAAR,CACF,CACF,CAEA,SAASV,GAAcmB,EAAqB,CAC1C,OAAQA,GAAK,IACV,MAAM,QAAQ,EACd,IAAKC,GAAMA,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO,EAEd,OAAO,CAACA,EAAGC,EAAGC,IAAMA,EAAE,QAAQF,CAAC,IAAMC,CAAC,CAC3C,CAEA,SAAST,GAAQO,EAAmB,CAClC,OAAQA,GAAK,IAAI,YAAY,EAAE,QAAQ,eAAgB,GAAG,EAAE,QAAQ,WAAY,EAAE,CACpF,CAEA,SAASJ,GAAiBjB,EAAwBK,EAAsBJ,EAAoBU,EAA2B,CAErH,IAAMjB,EAAW,CACf,KAAM,CAAE,GAFCoB,GAAQd,EAAQ,QAAQ,GAAK,OAE1B,KAAMA,EAAQ,SAAU,KAAM,kBAAmB,aAAcA,EAAQ,MAAM,EAAG,EAC5F,UAAW,CAAE,OAAQ,CAAE,OAAQ,sBAAuB,CAAE,EACxD,OAAQ,CAAE,CAACA,EAAQ,MAAM,EAAE,EAAGe,GAAWf,EAASK,EAAcJ,CAAQ,CAAE,EAC1E,SAAU,CAAC,EACX,MAAO,CAAC,EACR,KAAM,CAAE,QAAS,GAAO,MAAO,CAAC,EAAG,UAAW,SAAU,YAAa,CAAE,SAAU,GAAI,QAAS,EAAG,CAAE,EACnG,UAAW,CAAE,QAAS,GAAM,KAAM,KAAM,KAAM,YAAa,UAAW,wBAAyB,EAC/F,OAAQ,CAAC,CACX,EACA,OAAID,EAAQ,UAAYW,EACtBjB,EAAI,SAAS,SAAW,CACtB,QAAS,GACT,SAAU,CAAE,CAACM,EAAQ,SAAS,SAAS,EAAGgB,GAAqBhB,EAASW,CAAgB,CAAE,EAC1F,OAAQ,CAAE,GAAI,OAAQ,MAAO,kBAAmB,CAClD,EAEAjB,EAAI,SAAS,SAAW,CAAE,QAAS,GAAO,SAAU,CAAC,EAAG,OAAQ,CAAE,GAAI,OAAQ,MAAO,kBAAmB,CAAE,EAErGA,CACT,CAEA,SAASqB,GAAWf,EAAwBK,EAAsBJ,EAAoB,CACpF,IAAMwB,EAAa,CACjB,KAAMzB,EAAQ,MAAM,KACpB,UAAWK,EACX,KAAML,EAAQ,MAAM,KACpB,SAAAC,EACA,cAAe,EACf,eAAgB,SAClB,EACA,OAAID,EAAQ,MAAM,QAAOyB,EAAM,MAAQzB,EAAQ,MAAM,OACjDA,EAAQ,MAAM,aAAa,KAAK,IAAGyB,EAAM,aAAezB,EAAQ,MAAM,YAAY,KAAK,EAAE,MAAM,EAAG,GAAG,GAClGyB,CACT,CAEA,SAAST,GAAqBhB,EAAwB0B,EAAkB,CAOtE,OAAK1B,EAAQ,UAAU,YAChB,CACL,MAAO,KAAO0B,EAAW,IACzB,aAAc1B,EAAQ,MAAM,EAC9B,CACF,CAEA,SAASmB,GAAiBf,EAAiBJ,EAAwBW,EAAiC,CAClG,IAAMgB,EAAWlC,EAAWW,CAAO,EAAIT,GAAaS,EAAS,OAAO,EAAI,GAClEwB,EAAkB,CAAC,EASzB,GAPI5B,EAAQ,iBAAmB,CAAC2B,EAAS,SAAS,oBAAoB,GACpEC,EAAM,KAAK,qBAAqB5B,EAAQ,iBAAiB,EAEvDA,EAAQ,UAAYW,GAAoB,CAACgB,EAAS,SAAS,GAAGhB,IAAmB,GACnFiB,EAAM,KAAK,GAAGjB,KAAoBX,EAAQ,SAAS,UAAU,EAG3D,CAACP,EAAWW,CAAO,EAAG,CAMxBM,GAAcN,EAAS,CAAC,GALT,CACb,iCACA,yDACA,EACF,EACmC,GAAGwB,EAAO,EAAE,EAAE,KAAK;AAAA,CAAI,CAAC,EAC3D,OAEF,GAAIA,EAAM,OAAS,EAAG,CACpB,IAAMC,EAAMF,EAAS,SAAS;AAAA,CAAI,EAAI,GAAK;AAAA,EAC3CG,GAAe1B,EAASyB,EAAMD,EAAM,KAAK;AAAA,CAAI,EAAI;AAAA,CAAI,EAEzD,CAIO,SAASG,GAAgBC,EAAuBC,EAA2B,CAChF,IAAM3B,EAAQjB,GAAY,EACpB6C,EAAc5B,EAAM,UAAY6B,GAAS,EAAE,QAAQ,WAAY,EAAE,EACjEC,EAAOC,GAAgB/B,EAAO4B,CAAW,EAC/CD,EAAI,UAAU,IAAK,CAAE,eAAgB,0BAA2B,CAAC,EACjEA,EAAI,IAAIG,CAAI,CACd,CAEA,eAAsBE,GAAiBC,EAAsBN,EAAoC,CAC/F,IAAIO,EAAM,GACV,MAAM,IAAI,QAAeC,GAAO,CAAEF,EAAI,GAAG,OAASzC,GAAM,CAAE0C,GAAO1C,CAAE,CAAC,EAAGyC,EAAI,GAAG,MAAO,IAAME,EAAG,CAAC,CAAE,CAAC,EAClG,IAAIzC,EACJ,GAAI,CAAEA,EAAU,KAAK,MAAMwC,CAAG,CAAE,MAChC,CAAQP,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EAAGA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAO,cAAe,CAAC,CAAC,EAAG,MAAO,CAC/H,GAAI,CACF,IAAMS,EAAS3C,GAAUC,CAAO,EAChCiC,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAUS,CAAM,CAAC,CAChC,OAASC,EAAP,CACAV,EAAI,UAAU,IAAK,CAAE,eAAgB,kBAAmB,CAAC,EACzDA,EAAI,IAAI,KAAK,UAAU,CAAE,MAAOU,EAAE,SAAW,eAAgB,CAAC,CAAC,CACjE,CACF,CAWA,eAAsBC,GAAsBZ,EAAuBC,EAAoC,CACrGA,EAAI,UAAU,eAAgB,kBAAkB,EAChD,IAAIY,EACJ,GAAI,CAEFA,EADYC,EAAiB,EAClB,MAAM,MAAQ,iBAC3B,OAASH,EAAP,CACAV,EAAI,UAAU,GAAG,EACjBA,EAAI,IAAI,KAAK,UAAU,CAAE,GAAI,GAAO,MAAO,6BAA6BU,EAAE,SAAU,CAAC,CAAC,EACtF,MACF,CAEA,IAAMI,EAAM,UAAUF,EAAK,QAAQ,cAAe,WAAW,IAE7D,GAAI,MAAMG,GAAMD,CAAG,EAAG,CACpBd,EAAI,UAAU,GAAG,EACjBA,EAAI,IAAI,KAAK,UAAU,CAAE,GAAI,GAAM,IAAAc,EAAK,eAAgB,EAAK,CAAC,CAAC,EAC/D,OAMF,IAAME,EAAM,QAAQ,KAAK,CAAC,GAAKzD,EAAQ0D,GAAQC,GAAc,YAAY,GAAG,CAAC,EAAG,QAAQ,EACxF,GAAI,CACYC,GAAM,QAAQ,SAAU,CAACH,EAAK,SAAU,OAAO,EAAG,CAC9D,SAAU,GACV,MAAO,SACP,IAAK,QAAQ,IAAI,CACnB,CAAC,EACK,MAAM,CACd,OAASN,EAAP,CACAV,EAAI,UAAU,GAAG,EACjBA,EAAI,IAAI,KAAK,UAAU,CAAE,GAAI,GAAO,MAAO,2BAA2BU,EAAE,UAAW,UAAWI,CAAI,CAAC,CAAC,EACpG,MACF,CAIA,IAAMM,EAAW,KAAK,IAAI,EAAI,KAC9B,KAAO,KAAK,IAAI,EAAIA,GAElB,GADA,MAAM,IAAI,QAASZ,GAAO,WAAWA,EAAI,GAAG,CAAC,EACzC,MAAMO,GAAMD,CAAG,EAAG,CACpBd,EAAI,UAAU,GAAG,EACjBA,EAAI,IAAI,KAAK,UAAU,CAAE,GAAI,GAAM,IAAAc,EAAK,eAAgB,EAAM,CAAC,CAAC,EAChE,OAIJd,EAAI,UAAU,GAAG,EACjBA,EAAI,IAAI,KAAK,UAAU,CACrB,GAAI,GACJ,MAAO,iIACP,UAAWc,CACb,CAAC,CAAC,CACJ,CAEA,eAAeC,GAAMM,EAAmC,CACtD,GAAI,CACF,IAAMC,EAAI,MAAM,MAAMD,EAAS,CAAE,OAAQ,MAAO,OAAQ,YAAY,QAAQ,GAAG,CAAE,CAAC,EAClF,OAAOC,EAAE,OAAS,GAAKA,EAAE,OAAS,GACpC,MAAE,CAAQ,MAAO,EAAM,CACzB,CG1WA,OAAS,cAAAC,GAAY,eAAAC,OAAmB,SACxC,OAAS,cAAAC,GAAY,aAAAC,GAAW,gBAAAC,GAAc,iBAAAC,OAAqB,KACnE,OAAS,WAAAC,GAAS,WAAAC,OAAe,OAmB1B,IAAMC,EAAe,YACtBC,GAAe,sBAcRC,GAAN,KAAiB,CACd,KAER,YAAYC,EAAkB,QAAQ,IAAI,EAAGC,EAAkBH,GAAc,CAC3E,KAAK,KAAOH,GAAQK,EAASC,CAAO,CACtC,CAEQ,MAAsB,CAC5B,GAAI,CAACV,GAAW,KAAK,IAAI,EAAG,MAAO,CAAC,EACpC,GAAI,CAAE,OAAO,KAAK,MAAME,GAAa,KAAK,KAAM,OAAO,CAAC,CAAmB,MAAE,CAAQ,MAAO,CAAC,CAAE,CACjG,CAEQ,KAAKS,EAA8B,CACzCV,GAAUI,GAAQ,KAAK,IAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EACjDF,GAAc,KAAK,KAAM,KAAK,UAAUQ,EAAS,KAAM,CAAC,EAAI;AAAA,EAAM,OAAO,CAC3E,CAQA,OAAOC,EAIoC,CACzC,GAAI,CAACA,EAAM,MAAM,KAAK,EAAG,MAAM,IAAI,MAAM,yBAAyB,EAClE,GAAI,CAAC,MAAM,QAAQA,EAAM,MAAM,GAAKA,EAAM,OAAO,SAAW,EAC1D,MAAM,IAAI,MAAM,iCAAiC,EAEnD,QAAWC,KAAKD,EAAM,OACpB,GAAI,CAACE,GAAaD,CAAC,EAAG,MAAM,IAAI,MAAM,kBAAkBA,GAAG,EAE7D,IAAME,EAAOhB,GAAY,EAAE,EAAE,SAAS,KAAK,EACrCiB,EAAQV,EAAeS,EACvBE,EAAsB,CAC1B,GAAI,OAASlB,GAAY,CAAC,EAAE,SAAS,KAAK,EAC1C,KAAMa,EAAM,KAAK,KAAK,EACtB,OAAQN,EAAeS,EAAK,MAAM,EAAG,CAAC,EACtC,KAAMG,GAAUF,CAAK,EACrB,OAAQ,CAAC,GAAGJ,EAAM,MAAM,EACxB,UAAW,IAAI,KAAK,EAAE,YAAY,EAClC,UAAWA,EAAM,cACb,IAAI,KAAK,KAAK,IAAI,EAAIA,EAAM,cAAgB,MAAQ,GAAI,EAAE,YAAY,EACtE,MACN,EACMD,EAAU,KAAK,KAAK,EAC1B,OAAAA,EAAQ,KAAKM,CAAM,EACnB,KAAK,KAAKN,CAAO,EACV,CAAE,MAAAK,EAAO,OAAAC,CAAO,CACzB,CAEA,MAAsB,CACpB,OAAO,KAAK,KAAK,CACnB,CAEA,OAAOE,EAAgC,CACrC,IAAMR,EAAU,KAAK,KAAK,EACpBS,EAAMT,EAAQ,KAAMU,GAAMA,EAAE,KAAOF,CAAE,EAC3C,OAAKC,GACLA,EAAI,UAAY,IAAI,KAAK,EAAE,YAAY,EACvC,KAAK,KAAKT,CAAO,EACVS,GAHU,IAInB,CAMA,OAAOJ,EAAmC,CACxC,GAAI,CAACA,GAAS,CAACA,EAAM,WAAWV,CAAY,EAAG,OAAO,KACtD,IAAMgB,EAAOJ,GAAUF,CAAK,EACtBL,EAAU,KAAK,KAAK,EACpBS,EAAMT,EAAQ,KAAMU,GAAMA,EAAE,OAASC,CAAI,EAG/C,GAFI,CAACF,GACDA,EAAI,WACJA,EAAI,WAAa,KAAK,MAAMA,EAAI,SAAS,EAAI,KAAK,IAAI,EAAG,OAAO,KAEpE,IAAMG,EAAM,KAAK,IAAI,EACfC,EAAOJ,EAAI,WAAa,KAAK,MAAMA,EAAI,UAAU,EAAI,EAC3D,GAAIG,EAAMC,EAAO,IAAQ,CACvBJ,EAAI,WAAa,IAAI,KAAKG,CAAG,EAAE,YAAY,EAC3C,GAAI,CAAE,KAAK,KAAKZ,CAAO,CAAE,MAAE,CAA0B,EAEvD,OAAOS,CACT,CACF,EAOO,SAASK,GAAeR,EAAqBS,EAA2B,CAG7E,MAFI,GAAAT,EAAO,OAAO,SAASS,CAAQ,GAC/BA,EAAS,WAAW,QAAQ,GAAKT,EAAO,OAAO,SAAS,SAAS,GACjES,IAAa,kBAAoBT,EAAO,OAAO,SAAS,iBAAiB,EAE/E,CAEA,SAASH,GAAaD,EAAoB,CACxC,OACEA,IAAM,kBACNA,IAAM,mBACNA,IAAM,WACN,8BAA8B,KAAKA,CAAC,GACpCA,IAAM,WAEV,CAEO,SAASK,GAAUF,EAAuB,CAC/C,MAAO,UAAYlB,GAAW,QAAQ,EAAE,OAAOkB,CAAK,EAAE,OAAO,KAAK,CACpE,CAOO,SAASW,GAAaC,EAA8F,CACzH,IAAMC,EAAOD,EAAI,QAAQ,cACnBE,EAAa,MAAM,QAAQD,CAAI,EAAIA,EAAK,CAAC,EAAIA,EACnD,GAAIC,GAAcA,EAAW,YAAY,EAAE,WAAW,SAAS,EAC7D,OAAOA,EAAW,MAAM,CAAC,EAAE,KAAK,EAElC,GAAIF,EAAI,IACN,GAAI,CAEF,IAAMG,EADI,IAAI,IAAIH,EAAI,IAAK,kBAAkB,EAC/B,aAAa,IAAI,OAAO,EACtC,GAAIG,EAAK,OAAOA,CAClB,MAAE,CAAc,CAElB,OAAO,IACT,CC3KA,OAAS,gBAAAC,OAA+D,OCOjE,IAAMC,EAAN,KAAqB,CAClB,MACA,MAAgD,IAAI,IACpD,IAAM,IAEd,YAAYC,EAAoB,CAC9B,KAAK,MAAQA,EAAS,IAAIC,IAAQ,CAAE,IAAKA,EAAI,QAAQ,MAAO,EAAE,CAAE,EAAE,CACpE,CAMA,MAAM,iBAA0F,CAC9F,IAAMC,EAAyE,CAAC,EAEhF,aAAM,QAAQ,IAAI,KAAK,MAAM,IAAI,MAAOC,GAAS,CAC/C,GAAI,CACF,IAAMC,EAAO,MAAM,KAAK,UAAU,GAAGD,EAAK,iBAAiB,EAC3DA,EAAK,OAASC,EAAK,OACnB,QAAWC,KAAUD,EAAK,QAAU,CAAC,GAC/BC,EAAM,aAAe,GAAKA,EAAM,cAAgB,IAClDH,EAAQ,KAAK,CAAE,GAAGG,EAAO,OAAQD,EAAK,OAAQ,QAASD,EAAK,GAAI,CAAC,CAGvE,MAAE,CAA+B,CACnC,CAAC,CAAC,EAEKD,CACT,CAKA,MAAM,kBAAkBI,EAAiBC,EAErC,CACF,GAAI,CAEF,OADa,MAAM,KAAK,UAAU,GAAGD,yBAA+B,mBAAmBC,CAAO,GAAG,GACrF,UAAY,CAAC,CAC3B,MAAE,CACA,MAAO,CAAC,CACV,CACF,CAKA,MAAM,iBAAiBD,EAAiBC,EAAiBC,EAAkD,CACzG,GAAI,CACF,IAAMJ,EAAO,MAAM,KAAK,UACtB,GAAGE,wBAA8B,mBAAmBC,CAAO,UAAU,mBAAmBC,CAAW,GACrG,EACA,MAAO,CACL,KAAM,CACJ,MAAOJ,EAAK,MACZ,KAAMA,EAAK,MAAQ,CAAC,EACpB,MAAOA,EAAK,MACZ,OAAQ,SACR,QAASA,EAAK,QACd,YAAaA,EAAK,YAClB,QAASA,EAAK,SAAW,CAAC,CAC5B,EACA,QAASA,EAAK,QACd,KAAMA,EAAK,IACb,CACF,MAAE,CACA,OAAO,IACT,CACF,CAEA,MAAc,UAAUH,EAA2B,CACjD,IAAMQ,EAAS,KAAK,MAAM,IAAIR,CAAG,EACjC,GAAIQ,GAAU,KAAK,IAAI,EAAIA,EAAO,GAAK,KAAK,IAAK,OAAOA,EAAO,KAE/D,IAAMC,EAAM,MAAM,MAAMT,EAAK,CAAE,OAAQ,YAAY,QAAQ,GAAI,CAAE,CAAC,EAClE,GAAI,CAACS,EAAI,GAAI,MAAM,IAAI,MAAM,QAAQA,EAAI,QAAQ,EACjD,IAAMN,EAAO,MAAMM,EAAI,KAAK,EAC5B,YAAK,MAAM,IAAIT,EAAK,CAAE,KAAAG,EAAM,GAAI,KAAK,IAAI,CAAE,CAAC,EACrCA,CACT,CACF,ED/EA,SAASO,EAAWC,EAAmB,CACrC,OAAOA,EAAE,QAAQ,KAAM,OAAO,EAAE,QAAQ,KAAM,MAAM,EAAE,QAAQ,KAAM,MAAM,EAAE,QAAQ,KAAM,QAAQ,CACpG,CAQA,SAASC,GAAiBC,EAAsB,CAC9C,OAAOA,EAAK,QAAQ,gCAAiC,EAAE,CACzD,CAGA,SAASC,GAAQC,EAAaC,EAA0B,CAItD,MAAO,YAHMA,EACT,UAAU,mBAAmBA,CAAO,SAAS,mBAAmBD,CAAG,IACnE,QAAQ,mBAAmBA,CAAG,oBACML,EAAWK,CAAG,OACxD,CAGA,SAASE,EAASC,EAA4BF,EAA0B,CACtE,OAAQE,GAAQ,CAAC,GAAG,IAAIC,GAAKL,GAAQK,EAAGH,CAAO,CAAC,EAAE,KAAK,GAAG,CAC5D,CAiBA,SAASI,GAAgBC,EAAgBC,EAAuD,CAC9F,GAAI,CAACD,EAAQ,OAEb,IAAME,EAAUD,EAAa,IAAID,CAAM,EACvC,GAAIE,EAAS,OAAOA,EAGpB,IAAMC,EAAU,IAAI,IAAIF,EAAa,OAAO,CAAC,EAC7C,GAAIE,EAAQ,IAAIH,CAAM,EAAG,OAAOA,EAChC,IAAMI,EAAUJ,EAAO,SAAS,KAAK,EAAIA,EAAS,GAAGA,OACrD,GAAIG,EAAQ,IAAIC,CAAO,EAAG,OAAOA,EAIjC,IAAMC,EAAcL,EAAO,YAAY,EACvC,OAAW,CAACM,EAAOC,CAAI,IAAKN,EAC1B,GAAIK,EAAM,YAAY,IAAMD,EAAa,OAAOE,CAGpD,CAIA,SAASC,EAAGhB,EAAcS,EAAmCQ,EAAsB,GAAY,CAC7F,OAAOC,EAAenB,GAAiBC,CAAI,EAAG,CAC5C,SAAU,CAACQ,EAAQW,IAAY,CAC7B,IAAMJ,EAAOR,GAAgBC,EAAQC,CAAY,EACjD,OAAIM,EAAa,YAAYE,aAAuB,mBAAmBF,CAAI,uBAAuBI,QAC3F,YAAYF,cAAwB,mBAAmBT,CAAM,uEAAkEW,OACxI,CACF,CAAC,CACH,CAIA,IAAMC,GAAa;AAAA;AAAA,6NAObC,GAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":["existsSync","readFileSync","writeFileSync","appendFileSync","mkdirSync","dirname","resolve","fileURLToPath","hostname","spawn","init_config","existsSync","readFileSync","writeFileSync","copyFileSync","resolve","mutateAgentxConfig","mutator","opts","file","cfg","raw","result","json","tmp","e","backupPath","triggerLocalReload","writeAgentxConfig","url","loadDaemonConfig","renderSetupPage","state","defaultNode","heading","customHeader","step1","stepCard","field","step2","row","step3","step4","banner","body","esc","btn","renderShell","SETUP_PAGE_CSS","SETUP_SCRIPT","init_config","wizardState","baseDir","file","resolve","existsSync","cfg","readFileSync","agents","channels","c","runWizard","payload","mentions","splitMentions","configPath","envPath","workspaceDir","state","mkdirSync","claudeMd","personality","writeFileSync","telegramTokenRef","backupPath","mutateAgentxConfig","slugify","agentBlock","telegramAccountBlock","buildFreshConfig","writeAgentxConfig","ensureEnvEntries","nextSteps","s","m","i","a","block","tokenRef","existing","lines","sep","appendFileSync","handleWizardGet","_req","res","defaultNode","hostname","html","renderSetupPage","handleWizardPost","req","raw","ok","result","e","handleStartDaemonPost","bind","loadDaemonConfig","url","probe","cli","dirname","fileURLToPath","spawn","deadline","baseUrl","r","createHash","randomBytes","existsSync","mkdirSync","readFileSync","writeFileSync","resolve","dirname","TOKEN_PREFIX","DEFAULT_FILE","TokenStore","baseDir","fileRel","records","input","s","isValidScope","body","token","record","hashToken","id","rec","r","hash","now","last","recordHasScope","required","extractToken","req","auth","authHeader","tok","createServer","MeshWikiClient","peerUrls","url","results","peer","data","agent","peerUrl","agentId","articlePath","cached","res","escapeHtml","s","stripSectionTags","text","tagLink","tag","agentId","tagLinks","tags","t","resolveWikilink","target","wikiArticles","byTitle","pathSet","withExt","targetLower","title","path","md","agentPrefix","markdownToHtml","display","HEAD_FONTS","CSS","pageLayout","sidebar","content","searchQuery","groupByDir","articles","byDir","dir","list","b","dirLabel","hubSidebar","agents","activePath","html","agent","byType","type","a","isActive","hubHome","hub","allAgents","remoteAgents","totalEntries","totalArticles","totalUnabsorbed","remoteIds","r","nodes","statusChip","remoteBadge","tagPart","hubAgentOverview","agentSummary","isRemote","entries","recent","e","issues","issue","hubArticlePage","articlePath","store","article","index","titleToPath","prefix","inbound","link","linked","hubEntries","byDate","date","dateEntries","hubSearch","query","totalResults","blocks","results","hubTagPage","total","matches","hubAgentTagPage","agentSidebar","createWikiHandler","opts","wikiDir","agentFilter","peerUrls","mode","pathPrefix","WikiHub","mesh","MeshWikiClient","req","res","url","rawPath","status","parts","peerAgent","slashIdx","peerUrl","i","q","localAgents","localMap","seen","local","remote","m","rest","err","prefixHrefs","pill","startWikiServer","port","handler","createServer"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{a as y,f as k}from"./chunk-F4HZTZHW.js";k();var v="article-fields",w=["foundation","pillar","walls","openings","furniture"],c={foundation:0,pillar:1,walls:2,openings:3,furniture:4},R={foundation:"what the thing is \u2014 without it the article is about nothing",pillar:"the facts the article exists to carry \u2014 without them it cannot be acted on",walls:"what it connects to \u2014 people, projects, systems, status",openings:"how to work with it \u2014 access conventions, language, ownership",furniture:"history and colour \u2014 useful, replaceable, last"};function f(e){return e.tier==="foundation"||e.tier==="pillar"}var l=[{key:"whatItIs",tier:"foundation",label:"what it is, in its own terms",question:"Does the article say what the subject is, in its own terms?"},{key:"whyItMatters",tier:"walls",label:"why it matters to our work",question:"Does the article say why the subject matters to our work?"},{key:"relationships",tier:"walls",label:"which named people, projects or systems it connects to",question:"Does the article connect the subject to specific named people, projects or systems?"}],q={person:[...l,{key:"role",tier:"pillar",label:"role or job title",question:"Does the article state this person's role or job title?"},{key:"organisation",tier:"pillar",label:"organisation or team, and their position in it",question:"Does the article name the organisation or team this person belongs to?"},{key:"contactValue",tier:"pillar",label:'contact identifiers VERBATIM \u2014 the actual values, not "reaches us on WhatsApp"',question:"Does the article give an actual contact value \u2014 a number, handle, email or address \u2014 rather than only naming a channel?"},{key:"language",tier:"openings",label:"preferred language",question:"Does the article say what language to use with this person?"},{key:"ourOwner",tier:"openings",label:"who on our side owns the relationship",question:"Does the article say who on our side owns this relationship?"}],place:[...l,{key:"address",tier:"pillar",label:"hostname, IP, URL or path",question:"Does the article give a hostname, IP, URL or filesystem path for this system?"},{key:"access",tier:"pillar",label:"how access is obtained",question:"Does the article say how access is obtained?"},{key:"runsWhat",tier:"walls",label:"what runs on or is stored in it",question:"Does the article say what runs on or is stored in this system?"},{key:"administrator",tier:"walls",label:"who administers it",question:"Does the article say who administers it?"}],project:[...l,{key:"oneLine",tier:"foundation",label:"what it is, in one line",question:"Does the article describe in one line what the project delivers?"},{key:"ownerClient",tier:"pillar",label:"client or internal owner",question:"Does the article name the client or internal owner?"},{key:"locations",tier:"pillar",label:"repository, environment and URLs",question:"Does the article give a repository, environment or URL for it?"},{key:"status",tier:"walls",label:"current status",question:"Does the article state the project's current status?"},{key:"people",tier:"walls",label:"who works on it",question:"Does the article name who works on it?"}],concept:[...l,{key:"definition",tier:"foundation",label:"a one-line definition, first",question:"Does the article open with a one-line definition of the concept?"},{key:"whenApplies",tier:"walls",label:"when it applies",question:"Does the article say when this concept applies?"}],pattern:[...l,{key:"definition",tier:"foundation",label:"a one-line statement of the pattern, first",question:"Does the article open with a one-line statement of the pattern?"},{key:"whenApplies",tier:"pillar",label:"when to apply it",question:"Does the article say when to apply it?"},{key:"whatToDo",tier:"pillar",label:"what to actually do",question:"Does the article say what to actually do?"}],event:[...l,{key:"when",tier:"foundation",label:"the date it happened",question:"Does the article give the date the event happened?"},{key:"outcome",tier:"pillar",label:"what changed as a result",question:"Does the article say what changed as a result?"},{key:"who",tier:"walls",label:"who was involved",question:"Does the article name who was involved?"}],decision:[...l,{key:"what",tier:"foundation",label:"what was decided",question:"Does the article state plainly what was decided?"},{key:"why",tier:"pillar",label:"the reasoning behind it",question:"Does the article give the reasoning behind the decision?"},{key:"when",tier:"walls",label:"when it was decided",question:"Does the article say when it was decided?"},{key:"consequence",tier:"walls",label:"what it changed",question:"Does the article say what the decision changed?"}]},E={contactValue:{heading:"Contact identifiers",match:/contact|reach|phone|whatsapp|e-?mail/i},organisation:{heading:"Organisation and position",match:/organis|organiz|company|employer|team and/i},role:{heading:"Role or job title",match:/role|job title/i},language:{heading:"Preferred language",match:/language/i},ourOwner:{heading:"Relationship owner",match:/relationship owner|owns the relationship|our owner/i},address:{heading:"Address",match:/hostname|address|url|path/i},access:{heading:"Access",match:/access|credential|how to get in/i},administrator:{heading:"Administrator",match:/administrat|maintained by|who runs/i},ownerClient:{heading:"Client or owner",match:/client|owner/i},status:{heading:"Status",match:/status/i},locations:{heading:"Repository and environments",match:/repositor|environment|url/i}},j={contactValue:{heading:"Contact identifiers",match:/contact|reach|phone|whatsapp|e-?mail/i,from:["phone","whatsapp","email","gitlab"]},organisation:{heading:"Organisation and position",match:/organis|organiz|company|employer|team and/i,from:["organisation"]},role:{heading:"Role or job title",match:/role|job title/i,from:["role"]}},A=["contactValue","organisation","role"],D={contactValue:["wacli","gog","gitlab"],organisation:["gog","gitlab","erp"],role:["gog","gitlab"],locations:["gitlab"],address:["gitlab"],ownerClient:["erp"]};function d(e){return q[e??""]??l}function T(e){return[...d(e)].sort((n,o)=>c[n.tier]-c[o.tier])}function I(e){let n={};for(let o of d(e))n[o.key]=y(o.question);return n}function F(e,n=12e3){let o=String(e.body??"").trim();return{title:e.title,type:e.type??null,body:o.length>n?`${o.slice(0,n)}
|
|
2
|
+
\u2026[truncated]`:o}}function C(e,n,o={}){let a=o.low??.3,u=o.high??.7,s=d(e),t=[],r=[],h=[];for(let i of s){let g=n[i.key]?.noul;if(g===void 0){h.push(i.key);continue}g>u?t.push(i.key):g<=a?r.push(i.key):h.push(i.key)}let p=Object.fromEntries(w.map(i=>[i,[]]));for(let i of s)r.includes(i.key)&&p[i.tier].push(i.key);let m=s.filter(i=>f(i)&&r.includes(i.key)).map(i=>i.key),b=w.find(i=>p[i].length>0)??null;return{type:e??"?",present:t,missing:r,unclear:h,missingCritical:m,missingByTier:p,worstTier:b,coverage:s.length?t.length/s.length:0,fit:m.length===0}}function L(e,n={}){let o=new Map(d(e.type==="?"?void 0:e.type).map(t=>[t.key,t])),a=n.maxTier?c[n.maxTier]:c.furniture,u=[...e.missing,...n.includeUnclear?e.unclear:[]],s=[];for(let t of u){let r=o.get(t);if(!r||c[r.tier]>a)continue;let h=D[t]??[];s.push({field:t,label:r.label,tier:r.tier,sources:h,needsHuman:h.length===0,uncertain:e.unclear.includes(t)})}return s.sort((t,r)=>c[t.tier]-c[r.tier]||Number(t.needsHuman)-Number(r.needsHuman)||t.field.localeCompare(r.field))}function S(){let e=["person","project","place","concept","pattern","event","decision"],n=new Set(l.map(a=>a.key)),o=a=>{if(a.includes("**"))throw new Error(`field label must not carry its own emphasis: ${a}`);return`**${a}**`};return e.map(a=>{let s=T(a).filter(t=>!n.has(t.key)).map(t=>f(t)?o(t.label):t.label);return`- **${a}** \u2014 ${s.join("; ")}.`}).join(`
|
|
3
|
+
`)}function O(){return w.map((e,n)=>`${n+1}. **${e}** \u2014 ${R[e]}`).join(`
|
|
4
|
+
`)}export{v as a,w as b,c,R as d,f as e,q as f,E as g,j as h,A as i,D as j,d as k,T as l,I as m,F as n,C as o,L as p,S as q,O as r};
|
|
5
|
+
//# sourceMappingURL=chunk-3SCPRYJB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/decisions/seats/article-fields.ts"],"sourcesContent":["import { noul } from \"../questions\"\nimport type { NoulAnswer, Questions, StateValue } from \"../types\"\n\n// Required fields per article type, checked one Noul at a time, and\n// ordered by how much of the article rests on them.\n//\n// Grading an article on a 0-3 completeness scale tells you it scored\n// 2.17. It does not tell you what to do. Asking instead \"does this state\n// a contact value for this person?\" returns 0.05, and that is a work\n// item.\n//\n// This is the fan-out Jev is built for: one article as shared state, one\n// yes/no per required field, every answer in a single call. Thirteen\n// fields cost the same round trip as one.\n//\n// The field lists mirror the Identity section the absorb prompt now\n// demands, so the same contract is stated once to the writer and once to\n// the checker. When they drift, the checker is right — it is the one\n// measured against real articles.\n\nexport const ARTICLE_FIELDS_SEAT = \"article-fields\"\n\n/**\n * Facts are not equally load-bearing, and absorb treats them as if they\n * were.\n *\n * A building is not finished by adding furniture to a slab, but that is\n * what a single absorb pass does: it reads a batch of conversation and\n * writes down what the conversation emphasised. Conversations are about\n * events, so absorb produces history — two dated invoice exchanges on a\n * page that never states the person's number. The furniture arrived\n * before the pillars, and nothing in the pipeline noticed, because\n * nothing in the pipeline had a notion of order.\n *\n * Naming the order makes the gap reportable, prioritisable, and\n * resolvable in passes. A missing foundation is a broken article; a\n * missing piece of furniture is a Tuesday.\n */\nexport const TIERS = [\"foundation\", \"pillar\", \"walls\", \"openings\", \"furniture\"] as const\nexport type Tier = (typeof TIERS)[number]\n\nexport const TIER_RANK: Record<Tier, number> = {\n foundation: 0, pillar: 1, walls: 2, openings: 3, furniture: 4,\n}\n\nexport const TIER_MEANING: Record<Tier, string> = {\n foundation: \"what the thing is — without it the article is about nothing\",\n pillar: \"the facts the article exists to carry — without them it cannot be acted on\",\n walls: \"what it connects to — people, projects, systems, status\",\n openings: \"how to work with it — access conventions, language, ownership\",\n furniture: \"history and colour — useful, replaceable, last\",\n}\n\nexport interface RequiredField {\n key: string\n /** Imperative form, rendered into the absorb prompt for the writer. */\n label: string\n /** Phrased as a yes/no about the article, never about the subject. */\n question: string\n tier: Tier\n}\n\n/**\n * Foundation and pillar are what \"critical\" used to mean, field for\n * field. Deriving it removes the second concept rather than adding one —\n * there is no way now to mark a field critical and file it under\n * furniture.\n */\nexport function isCritical(f: RequiredField): boolean {\n return f.tier === \"foundation\" || f.tier === \"pillar\"\n}\n\nconst COMMON: RequiredField[] = [\n { key: \"whatItIs\", tier: \"foundation\", label: \"what it is, in its own terms\", question: \"Does the article say what the subject is, in its own terms?\" },\n { key: \"whyItMatters\", tier: \"walls\", label: \"why it matters to our work\", question: \"Does the article say why the subject matters to our work?\" },\n { key: \"relationships\", tier: \"walls\", label: \"which named people, projects or systems it connects to\", question: \"Does the article connect the subject to specific named people, projects or systems?\" },\n]\n\nexport const REQUIRED_FIELDS: Record<string, RequiredField[]> = {\n person: [\n ...COMMON,\n { key: \"role\", tier: \"pillar\", label: \"role or job title\", question: \"Does the article state this person's role or job title?\" },\n { key: \"organisation\", tier: \"pillar\", label: \"organisation or team, and their position in it\", question: \"Does the article name the organisation or team this person belongs to?\" },\n { key: \"contactValue\", tier: \"pillar\", label: \"contact identifiers VERBATIM — the actual values, not \\\"reaches us on WhatsApp\\\"\", question: \"Does the article give an actual contact value — a number, handle, email or address — rather than only naming a channel?\" },\n { key: \"language\", tier: \"openings\", label: \"preferred language\", question: \"Does the article say what language to use with this person?\" },\n { key: \"ourOwner\", tier: \"openings\", label: \"who on our side owns the relationship\", question: \"Does the article say who on our side owns this relationship?\" },\n ],\n place: [\n ...COMMON,\n { key: \"address\", tier: \"pillar\", label: \"hostname, IP, URL or path\", question: \"Does the article give a hostname, IP, URL or filesystem path for this system?\" },\n { key: \"access\", tier: \"pillar\", label: \"how access is obtained\", question: \"Does the article say how access is obtained?\" },\n { key: \"runsWhat\", tier: \"walls\", label: \"what runs on or is stored in it\", question: \"Does the article say what runs on or is stored in this system?\" },\n { key: \"administrator\", tier: \"walls\", label: \"who administers it\", question: \"Does the article say who administers it?\" },\n ],\n project: [\n ...COMMON,\n { key: \"oneLine\", tier: \"foundation\", label: \"what it is, in one line\", question: \"Does the article describe in one line what the project delivers?\" },\n { key: \"ownerClient\", tier: \"pillar\", label: \"client or internal owner\", question: \"Does the article name the client or internal owner?\" },\n { key: \"locations\", tier: \"pillar\", label: \"repository, environment and URLs\", question: \"Does the article give a repository, environment or URL for it?\" },\n { key: \"status\", tier: \"walls\", label: \"current status\", question: \"Does the article state the project's current status?\" },\n { key: \"people\", tier: \"walls\", label: \"who works on it\", question: \"Does the article name who works on it?\" },\n ],\n concept: [\n ...COMMON,\n { key: \"definition\", tier: \"foundation\", label: \"a one-line definition, first\", question: \"Does the article open with a one-line definition of the concept?\" },\n { key: \"whenApplies\", tier: \"walls\", label: \"when it applies\", question: \"Does the article say when this concept applies?\" },\n ],\n pattern: [\n ...COMMON,\n { key: \"definition\", tier: \"foundation\", label: \"a one-line statement of the pattern, first\", question: \"Does the article open with a one-line statement of the pattern?\" },\n { key: \"whenApplies\", tier: \"pillar\", label: \"when to apply it\", question: \"Does the article say when to apply it?\" },\n { key: \"whatToDo\", tier: \"pillar\", label: \"what to actually do\", question: \"Does the article say what to actually do?\" },\n ],\n event: [\n ...COMMON,\n { key: \"when\", tier: \"foundation\", label: \"the date it happened\", question: \"Does the article give the date the event happened?\" },\n { key: \"outcome\", tier: \"pillar\", label: \"what changed as a result\", question: \"Does the article say what changed as a result?\" },\n { key: \"who\", tier: \"walls\", label: \"who was involved\", question: \"Does the article name who was involved?\" },\n ],\n decision: [\n ...COMMON,\n { key: \"what\", tier: \"foundation\", label: \"what was decided\", question: \"Does the article state plainly what was decided?\" },\n { key: \"why\", tier: \"pillar\", label: \"the reasoning behind it\", question: \"Does the article give the reasoning behind the decision?\" },\n { key: \"when\", tier: \"walls\", label: \"when it was decided\", question: \"Does the article say when it was decided?\" },\n { key: \"consequence\", tier: \"walls\", label: \"what it changed\", question: \"Does the article say what the decision changed?\" },\n ],\n}\n\n/**\n * Which fact source can supply a field without asking a person.\n *\n * This is what splits the action list in two. A missing contact value is\n * a lookup — `wacli contacts search` answers it in 300ms and nobody is\n * interrupted. A missing role is a question, because no system of record\n * on this fleet holds job titles. Ranking gaps by tier says what matters\n * most; this says which of them we can close by ourselves.\n */\n/**\n * Fields a backfill may write on its own, and the resolved fact keys\n * that feed each one.\n *\n * Deliberately a short list, and deliberately not the same list as\n * FIELD_SOURCES. That one says which system *might* know something;\n * this one says what we can copy verbatim from a system of record with\n * no judgement involved. A contact value is a string we were handed. A\n * project's status is a reading of the world, and no lookup returns it.\n *\n * `role` and `organisation` are here because GitLab and Google return\n * them as literal profile fields — where those are blank, which is the\n * common case, nothing is written and the gap stays open for a human.\n */\n/**\n * Where a field lives in an article's Identity section.\n *\n * Separate from BACKFILLABLE, which is the narrower question of what a\n * machine may write on its own. A person answering \"what language does\n * she prefer\" has produced a fact that belongs in the article just as\n * much as a number does — it simply had to come from a person. Without\n * a slot for it the answer would be recorded on the queue and never\n * reach the wiki, which is the same dead end the queue exists to fix.\n *\n * A field absent from this map has no defined place, so an answer to it\n * is recorded and left for a person to file. Guessing a location in the\n * source of truth is worse than leaving the answer where it can be seen.\n */\nexport const IDENTITY_SLOTS: Record<string, { heading: string; match: RegExp }> = {\n // Order matters: `organisation` is matched before `role`, because\n // articles write \"Organisation and position\" and a /position/ test\n // for role would claim it.\n contactValue: { heading: \"Contact identifiers\", match: /contact|reach|phone|whatsapp|e-?mail/i },\n organisation: { heading: \"Organisation and position\", match: /organis|organiz|company|employer|team and/i },\n role: { heading: \"Role or job title\", match: /role|job title/i },\n language: { heading: \"Preferred language\", match: /language/i },\n ourOwner: { heading: \"Relationship owner\", match: /relationship owner|owns the relationship|our owner/i },\n address: { heading: \"Address\", match: /hostname|address|url|path/i },\n access: { heading: \"Access\", match: /access|credential|how to get in/i },\n administrator: { heading: \"Administrator\", match: /administrat|maintained by|who runs/i },\n ownerClient: { heading: \"Client or owner\", match: /client|owner/i },\n status: { heading: \"Status\", match: /status/i },\n locations: { heading: \"Repository and environments\", match: /repositor|environment|url/i },\n}\n\nexport const BACKFILLABLE: Record<string, { heading: string; match: RegExp; from: string[] }> = {\n // Ordered by how specific the heading match is: `organisation` is\n // tried before `role` because articles write \"Organisation and\n // position\", and a naive /position/ test for role would claim it.\n contactValue: {\n heading: \"Contact identifiers\",\n match: /contact|reach|phone|whatsapp|e-?mail/i,\n from: [\"phone\", \"whatsapp\", \"email\", \"gitlab\"],\n },\n organisation: {\n heading: \"Organisation and position\",\n match: /organis|organiz|company|employer|team and/i,\n from: [\"organisation\"],\n },\n role: {\n heading: \"Role or job title\",\n match: /role|job title/i,\n from: [\"role\"],\n },\n}\n\nexport const BACKFILL_ORDER = [\"contactValue\", \"organisation\", \"role\"] as const\n\nexport const FIELD_SOURCES: Record<string, string[]> = {\n contactValue: [\"wacli\", \"gog\", \"gitlab\"],\n organisation: [\"gog\", \"gitlab\", \"erp\"],\n role: [\"gog\", \"gitlab\"],\n locations: [\"gitlab\"],\n address: [\"gitlab\"],\n ownerClient: [\"erp\"],\n}\n\nexport function fieldsFor(type?: string): RequiredField[] {\n return REQUIRED_FIELDS[type ?? \"\"] ?? COMMON\n}\n\n/** Required fields in build order — foundation first, furniture last. */\nexport function fieldsByTier(type?: string): RequiredField[] {\n return [...fieldsFor(type)].sort((a, b) => TIER_RANK[a.tier] - TIER_RANK[b.tier])\n}\n\n/** One Noul per required field — all answered in a single call. */\nexport function fieldQuestions(type?: string): Questions {\n const q: Record<string, ReturnType<typeof noul>> = {}\n for (const f of fieldsFor(type)) q[f.key] = noul(f.question)\n return q as Questions\n}\n\nexport function articleFieldsState(a: { title: string; type?: string; body: string }, max = 12_000): StateValue {\n const body = String(a.body ?? \"\").trim()\n return {\n title: a.title,\n type: a.type ?? null,\n body: body.length > max ? `${body.slice(0, max)}\\n…[truncated]` : body,\n }\n}\n\nexport interface FieldReport {\n type: string\n present: string[]\n missing: string[]\n /** Between the thresholds — the model is not sure the field is there,\n * which usually means it is mentioned vaguely. Worth a human glance\n * rather than an automatic rewrite. */\n unclear: string[]\n missingCritical: string[]\n /** Missing field keys grouped by tier, so a gap can be ranked. */\n missingByTier: Record<Tier, string[]>\n /** The most load-bearing tier with a gap — what to fix first. */\n worstTier: Tier | null\n /** Share of required fields present. */\n coverage: number\n fit: boolean\n}\n\n/**\n * TypeSafe's own review band: act below 0.30 or above 0.70, escalate\n * between. classifier.dev — which runs on Jev — re-asks anything under\n * 0.70 for the same reason, and gains 2.5 points of accuracy on AG News\n * doing it. Treating the middle as an answer is where that gain is lost.\n */\nexport function reportFields(\n type: string | undefined,\n answers: Record<string, unknown>,\n opts: { low?: number; high?: number } = {},\n): FieldReport {\n const low = opts.low ?? 0.3\n const high = opts.high ?? 0.7\n const fields = fieldsFor(type)\n const present: string[] = [], missing: string[] = [], unclear: string[] = []\n\n for (const f of fields) {\n const p = (answers[f.key] as NoulAnswer | undefined)?.noul\n if (p === undefined) { unclear.push(f.key); continue }\n if (p > high) present.push(f.key)\n else if (p <= low) missing.push(f.key)\n else unclear.push(f.key)\n }\n\n const missingByTier = Object.fromEntries(TIERS.map((t) => [t, [] as string[]])) as Record<Tier, string[]>\n for (const f of fields) if (missing.includes(f.key)) missingByTier[f.tier].push(f.key)\n\n const missingCritical = fields.filter((f) => isCritical(f) && missing.includes(f.key)).map((f) => f.key)\n const worstTier = TIERS.find((t) => missingByTier[t].length > 0) ?? null\n\n return {\n type: type ?? \"?\",\n present, missing, unclear, missingCritical, missingByTier, worstTier,\n coverage: fields.length ? present.length / fields.length : 0,\n fit: missingCritical.length === 0,\n }\n}\n\nexport interface GapAction {\n field: string\n label: string\n tier: Tier\n /** Fact sources that could answer this without asking anyone. */\n sources: string[]\n /** True when no source holds it — this one needs a person. */\n needsHuman: boolean\n /** The grader was unsure rather than negative; verify before rewriting. */\n uncertain: boolean\n}\n\n/**\n * The post-absorb work list: what to go and find, most load-bearing\n * first.\n *\n * Absorbing an entry once and never revisiting it assumes one pass can\n * see everything worth keeping. It cannot — half of what an article\n * needs was never in the conversation at all. This turns the grader's\n * output into the next pass's input, so the corpus is built up in\n * layers instead of being declared finished at the first draft.\n */\nexport function nextActions(\n report: FieldReport,\n opts: { includeUnclear?: boolean; maxTier?: Tier } = {},\n): GapAction[] {\n const byKey = new Map(fieldsFor(report.type === \"?\" ? undefined : report.type).map((f) => [f.key, f]))\n const limit = opts.maxTier ? TIER_RANK[opts.maxTier] : TIER_RANK.furniture\n\n const keys = [...report.missing, ...(opts.includeUnclear ? report.unclear : [])]\n const actions: GapAction[] = []\n for (const key of keys) {\n const f = byKey.get(key)\n if (!f || TIER_RANK[f.tier] > limit) continue\n const sources = FIELD_SOURCES[key] ?? []\n actions.push({\n field: key,\n label: f.label,\n tier: f.tier,\n sources,\n needsHuman: sources.length === 0,\n uncertain: report.unclear.includes(key),\n })\n }\n\n // Tier first, then resolvable-by-lookup ahead of needs-a-human: within\n // one tier the automatable work should be drained before anyone is\n // interrupted.\n return actions.sort((a, b) =>\n TIER_RANK[a.tier] - TIER_RANK[b.tier] ||\n Number(a.needsHuman) - Number(b.needsHuman) ||\n a.field.localeCompare(b.field))\n}\n\n/**\n * The same field list, rendered for the absorb prompt.\n *\n * The writer and the checker read one definition. When these were two\n * hand-written lists they drifted within a day: the prompt asked for a\n * relationship owner, the rubric never looked for one, and nobody could\n * tell whether a missing owner was the writer's fault or the checker's\n * blind spot. Generating the prompt from the checked fields makes that\n * class of disagreement unrepresentable.\n *\n * Rendered in tier order, so the instruction reads in the order the\n * article should be built rather than the order the record happens to\n * list.\n */\nexport function fieldChecklistMarkdown(): string {\n const order = [\"person\", \"project\", \"place\", \"concept\", \"pattern\", \"event\", \"decision\"]\n const commonKeys = new Set(COMMON.map((f) => f.key))\n const bold = (t: string) => {\n if (t.includes(\"**\")) throw new Error(`field label must not carry its own emphasis: ${t}`)\n return `**${t}**`\n }\n return order\n .map((type) => {\n const own = fieldsByTier(type).filter((f) => !commonKeys.has(f.key))\n const parts = own.map((f) => (isCritical(f) ? bold(f.label) : f.label))\n return `- **${type}** — ${parts.join(\"; \")}.`\n })\n .join(\"\\n\")\n}\n\n/** The tier ladder, for the absorb prompt. */\nexport function tierLadderMarkdown(): string {\n return TIERS.map((t, i) => `${i + 1}. **${t}** — ${TIER_MEANING[t]}`).join(\"\\n\")\n}\n"],"mappings":"+CAAAA,IAoBO,IAAMC,EAAsB,iBAkBtBC,EAAQ,CAAC,aAAc,SAAU,QAAS,WAAY,WAAW,EAGjEC,EAAkC,CAC7C,WAAY,EAAG,OAAQ,EAAG,MAAO,EAAG,SAAU,EAAG,UAAW,CAC9D,EAEaC,EAAqC,CAChD,WAAY,mEACZ,OAAQ,kFACR,MAAO,+DACP,SAAU,qEACV,UAAW,qDACb,EAiBO,SAASC,EAAWC,EAA2B,CACpD,OAAOA,EAAE,OAAS,cAAgBA,EAAE,OAAS,QAC/C,CAEA,IAAMC,EAA0B,CAC9B,CAAE,IAAK,WAAY,KAAM,aAAc,MAAO,+BAAgC,SAAU,6DAA8D,EACtJ,CAAE,IAAK,eAAgB,KAAM,QAAS,MAAO,6BAA8B,SAAU,2DAA4D,EACjJ,CAAE,IAAK,gBAAiB,KAAM,QAAS,MAAO,yDAA0D,SAAU,qFAAsF,CAC1M,EAEaC,EAAmD,CAC9D,OAAQ,CACN,GAAGD,EACH,CAAE,IAAK,OAAQ,KAAM,SAAU,MAAO,oBAAqB,SAAU,yDAA0D,EAC/H,CAAE,IAAK,eAAgB,KAAM,SAAU,MAAO,iDAAkD,SAAU,wEAAyE,EACnL,CAAE,IAAK,eAAgB,KAAM,SAAU,MAAO,sFAAoF,SAAU,mIAA0H,EACtQ,CAAE,IAAK,WAAY,KAAM,WAAY,MAAO,qBAAsB,SAAU,6DAA8D,EAC1I,CAAE,IAAK,WAAY,KAAM,WAAY,MAAO,wCAAyC,SAAU,8DAA+D,CAChK,EACA,MAAO,CACL,GAAGA,EACH,CAAE,IAAK,UAAW,KAAM,SAAU,MAAO,4BAA6B,SAAU,+EAAgF,EAChK,CAAE,IAAK,SAAU,KAAM,SAAU,MAAO,yBAA0B,SAAU,8CAA+C,EAC3H,CAAE,IAAK,WAAY,KAAM,QAAS,MAAO,kCAAmC,SAAU,gEAAiE,EACvJ,CAAE,IAAK,gBAAiB,KAAM,QAAS,MAAO,qBAAsB,SAAU,0CAA2C,CAC3H,EACA,QAAS,CACP,GAAGA,EACH,CAAE,IAAK,UAAW,KAAM,aAAc,MAAO,0BAA2B,SAAU,kEAAmE,EACrJ,CAAE,IAAK,cAAe,KAAM,SAAU,MAAO,2BAA4B,SAAU,qDAAsD,EACzI,CAAE,IAAK,YAAa,KAAM,SAAU,MAAO,mCAAoC,SAAU,gEAAiE,EAC1J,CAAE,IAAK,SAAU,KAAM,QAAS,MAAO,iBAAkB,SAAU,sDAAuD,EAC1H,CAAE,IAAK,SAAU,KAAM,QAAS,MAAO,kBAAmB,SAAU,wCAAyC,CAC/G,EACA,QAAS,CACP,GAAGA,EACH,CAAE,IAAK,aAAc,KAAM,aAAc,MAAO,+BAAgC,SAAU,kEAAmE,EAC7J,CAAE,IAAK,cAAe,KAAM,QAAS,MAAO,kBAAmB,SAAU,iDAAkD,CAC7H,EACA,QAAS,CACP,GAAGA,EACH,CAAE,IAAK,aAAc,KAAM,aAAc,MAAO,6CAA8C,SAAU,iEAAkE,EAC1K,CAAE,IAAK,cAAe,KAAM,SAAU,MAAO,mBAAoB,SAAU,wCAAyC,EACpH,CAAE,IAAK,WAAY,KAAM,SAAU,MAAO,sBAAuB,SAAU,2CAA4C,CACzH,EACA,MAAO,CACL,GAAGA,EACH,CAAE,IAAK,OAAQ,KAAM,aAAc,MAAO,uBAAwB,SAAU,oDAAqD,EACjI,CAAE,IAAK,UAAW,KAAM,SAAU,MAAO,2BAA4B,SAAU,gDAAiD,EAChI,CAAE,IAAK,MAAO,KAAM,QAAS,MAAO,mBAAoB,SAAU,yCAA0C,CAC9G,EACA,SAAU,CACR,GAAGA,EACH,CAAE,IAAK,OAAQ,KAAM,aAAc,MAAO,mBAAoB,SAAU,kDAAmD,EAC3H,CAAE,IAAK,MAAO,KAAM,SAAU,MAAO,0BAA2B,SAAU,0DAA2D,EACrI,CAAE,IAAK,OAAQ,KAAM,QAAS,MAAO,sBAAuB,SAAU,2CAA4C,EAClH,CAAE,IAAK,cAAe,KAAM,QAAS,MAAO,kBAAmB,SAAU,iDAAkD,CAC7H,CACF,EAuCaE,EAAqE,CAIhF,aAAc,CAAE,QAAS,sBAAuB,MAAO,uCAAwC,EAC/F,aAAc,CAAE,QAAS,4BAA6B,MAAO,4CAA6C,EAC1G,KAAM,CAAE,QAAS,oBAAqB,MAAO,iBAAkB,EAC/D,SAAU,CAAE,QAAS,qBAAsB,MAAO,WAAY,EAC9D,SAAU,CAAE,QAAS,qBAAsB,MAAO,qDAAsD,EACxG,QAAS,CAAE,QAAS,UAAW,MAAO,4BAA6B,EACnE,OAAQ,CAAE,QAAS,SAAU,MAAO,kCAAmC,EACvE,cAAe,CAAE,QAAS,gBAAiB,MAAO,qCAAsC,EACxF,YAAa,CAAE,QAAS,kBAAmB,MAAO,eAAgB,EAClE,OAAQ,CAAE,QAAS,SAAU,MAAO,SAAU,EAC9C,UAAW,CAAE,QAAS,8BAA+B,MAAO,4BAA6B,CAC3F,EAEaC,EAAmF,CAI9F,aAAc,CACZ,QAAS,sBACT,MAAO,wCACP,KAAM,CAAC,QAAS,WAAY,QAAS,QAAQ,CAC/C,EACA,aAAc,CACZ,QAAS,4BACT,MAAO,6CACP,KAAM,CAAC,cAAc,CACvB,EACA,KAAM,CACJ,QAAS,oBACT,MAAO,kBACP,KAAM,CAAC,MAAM,CACf,CACF,EAEaC,EAAiB,CAAC,eAAgB,eAAgB,MAAM,EAExDC,EAA0C,CACrD,aAAc,CAAC,QAAS,MAAO,QAAQ,EACvC,aAAc,CAAC,MAAO,SAAU,KAAK,EACrC,KAAM,CAAC,MAAO,QAAQ,EACtB,UAAW,CAAC,QAAQ,EACpB,QAAS,CAAC,QAAQ,EAClB,YAAa,CAAC,KAAK,CACrB,EAEO,SAASC,EAAUC,EAAgC,CACxD,OAAON,EAAgBM,GAAQ,EAAE,GAAKP,CACxC,CAGO,SAASQ,EAAaD,EAAgC,CAC3D,MAAO,CAAC,GAAGD,EAAUC,CAAI,CAAC,EAAE,KAAK,CAACE,EAAGC,IAAMd,EAAUa,EAAE,IAAI,EAAIb,EAAUc,EAAE,IAAI,CAAC,CAClF,CAGO,SAASC,EAAeJ,EAA0B,CACvD,IAAMK,EAA6C,CAAC,EACpD,QAAWb,KAAKO,EAAUC,CAAI,EAAGK,EAAEb,EAAE,GAAG,EAAIc,EAAKd,EAAE,QAAQ,EAC3D,OAAOa,CACT,CAEO,SAASE,EAAmBL,EAAmDM,EAAM,KAAoB,CAC9G,IAAMC,EAAO,OAAOP,EAAE,MAAQ,EAAE,EAAE,KAAK,EACvC,MAAO,CACL,MAAOA,EAAE,MACT,KAAMA,EAAE,MAAQ,KAChB,KAAMO,EAAK,OAASD,EAAM,GAAGC,EAAK,MAAM,EAAGD,CAAG;AAAA,mBAAoBC,CACpE,CACF,CA0BO,SAASC,EACdV,EACAW,EACAC,EAAwC,CAAC,EAC5B,CACb,IAAMC,EAAMD,EAAK,KAAO,GAClBE,EAAOF,EAAK,MAAQ,GACpBG,EAAShB,EAAUC,CAAI,EACvBgB,EAAoB,CAAC,EAAGC,EAAoB,CAAC,EAAGC,EAAoB,CAAC,EAE3E,QAAW1B,KAAKuB,EAAQ,CACtB,IAAMI,EAAKR,EAAQnB,EAAE,GAAG,GAA8B,KACtD,GAAI2B,IAAM,OAAW,CAAED,EAAQ,KAAK1B,EAAE,GAAG,EAAG,SACxC2B,EAAIL,EAAME,EAAQ,KAAKxB,EAAE,GAAG,EACvB2B,GAAKN,EAAKI,EAAQ,KAAKzB,EAAE,GAAG,EAChC0B,EAAQ,KAAK1B,EAAE,GAAG,EAGzB,IAAM4B,EAAgB,OAAO,YAAYhC,EAAM,IAAKiC,GAAM,CAACA,EAAG,CAAC,CAAa,CAAC,CAAC,EAC9E,QAAW7B,KAAKuB,EAAYE,EAAQ,SAASzB,EAAE,GAAG,GAAG4B,EAAc5B,EAAE,IAAI,EAAE,KAAKA,EAAE,GAAG,EAErF,IAAM8B,EAAkBP,EAAO,OAAQvB,GAAMD,EAAWC,CAAC,GAAKyB,EAAQ,SAASzB,EAAE,GAAG,CAAC,EAAE,IAAKA,GAAMA,EAAE,GAAG,EACjG+B,EAAYnC,EAAM,KAAMiC,GAAMD,EAAcC,CAAC,EAAE,OAAS,CAAC,GAAK,KAEpE,MAAO,CACL,KAAMrB,GAAQ,IACd,QAAAgB,EAAS,QAAAC,EAAS,QAAAC,EAAS,gBAAAI,EAAiB,cAAAF,EAAe,UAAAG,EAC3D,SAAUR,EAAO,OAASC,EAAQ,OAASD,EAAO,OAAS,EAC3D,IAAKO,EAAgB,SAAW,CAClC,CACF,CAwBO,SAASE,EACdC,EACAb,EAAqD,CAAC,EACzC,CACb,IAAMc,EAAQ,IAAI,IAAI3B,EAAU0B,EAAO,OAAS,IAAM,OAAYA,EAAO,IAAI,EAAE,IAAKjC,GAAM,CAACA,EAAE,IAAKA,CAAC,CAAC,CAAC,EAC/FmC,EAAQf,EAAK,QAAUvB,EAAUuB,EAAK,OAAO,EAAIvB,EAAU,UAE3DuC,EAAO,CAAC,GAAGH,EAAO,QAAS,GAAIb,EAAK,eAAiBa,EAAO,QAAU,CAAC,CAAE,EACzEI,EAAuB,CAAC,EAC9B,QAAWC,KAAOF,EAAM,CACtB,IAAMpC,EAAIkC,EAAM,IAAII,CAAG,EACvB,GAAI,CAACtC,GAAKH,EAAUG,EAAE,IAAI,EAAImC,EAAO,SACrC,IAAMI,EAAUjC,EAAcgC,CAAG,GAAK,CAAC,EACvCD,EAAQ,KAAK,CACX,MAAOC,EACP,MAAOtC,EAAE,MACT,KAAMA,EAAE,KACR,QAAAuC,EACA,WAAYA,EAAQ,SAAW,EAC/B,UAAWN,EAAO,QAAQ,SAASK,CAAG,CACxC,CAAC,EAMH,OAAOD,EAAQ,KAAK,CAAC3B,EAAGC,IACtBd,EAAUa,EAAE,IAAI,EAAIb,EAAUc,EAAE,IAAI,GACpC,OAAOD,EAAE,UAAU,EAAI,OAAOC,EAAE,UAAU,GAC1CD,EAAE,MAAM,cAAcC,EAAE,KAAK,CAAC,CAClC,CAgBO,SAAS6B,GAAiC,CAC/C,IAAMC,EAAQ,CAAC,SAAU,UAAW,QAAS,UAAW,UAAW,QAAS,UAAU,EAChFC,EAAa,IAAI,IAAIzC,EAAO,IAAKD,GAAMA,EAAE,GAAG,CAAC,EAC7C2C,EAAQd,GAAc,CAC1B,GAAIA,EAAE,SAAS,IAAI,EAAG,MAAM,IAAI,MAAM,gDAAgDA,GAAG,EACzF,MAAO,KAAKA,KACd,EACA,OAAOY,EACJ,IAAKjC,GAAS,CAEb,IAAMoC,EADMnC,EAAaD,CAAI,EAAE,OAAQR,GAAM,CAAC0C,EAAW,IAAI1C,EAAE,GAAG,CAAC,EACjD,IAAKA,GAAOD,EAAWC,CAAC,EAAI2C,EAAK3C,EAAE,KAAK,EAAIA,EAAE,KAAM,EACtE,MAAO,OAAOQ,cAAYoC,EAAM,KAAK,IAAI,IAC3C,CAAC,EACA,KAAK;AAAA,CAAI,CACd,CAGO,SAASC,GAA6B,CAC3C,OAAOjD,EAAM,IAAI,CAACiC,EAAGiB,IAAM,GAAGA,EAAI,QAAQjB,cAAS/B,EAAa+B,CAAC,GAAG,EAAE,KAAK;AAAA,CAAI,CACjF","names":["init_questions","ARTICLE_FIELDS_SEAT","TIERS","TIER_RANK","TIER_MEANING","isCritical","f","COMMON","REQUIRED_FIELDS","IDENTITY_SLOTS","BACKFILLABLE","BACKFILL_ORDER","FIELD_SOURCES","fieldsFor","type","fieldsByTier","a","b","fieldQuestions","q","noul","articleFieldsState","max","body","reportFields","answers","opts","low","high","fields","present","missing","unclear","p","missingByTier","t","missingCritical","worstTier","nextActions","report","byKey","limit","keys","actions","key","sources","fieldChecklistMarkdown","order","commonKeys","bold","parts","tierLadderMarkdown","i"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import{a as u,b as o}from"./chunk-ZTEU5JVQ.js";o();function g(n,e,t=u()){return n.prepare(`
|
|
2
|
+
INSERT INTO task_traces (
|
|
3
|
+
task_id, agent_id, channel, chat_id, workflow_run_id, workflow_id,
|
|
4
|
+
workflow_node_id, intent_event_id, intent_decided_by, resume_session_id,
|
|
5
|
+
model, status, started_at, message_preview, original_message
|
|
6
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'in-flight', ?, ?, ?)
|
|
7
|
+
`).run(t,e.agentId,e.channel??null,e.chatId??null,e.workflowRunId??null,e.workflowId??null,e.workflowNodeId??null,e.intentEventId??null,e.intentDecidedBy??null,e.resumeSessionId??null,e.model??null,Date.now(),e.messagePreview??null,e.originalMessage??null),t}function m(n,e,t){let s=Date.now();n.prepare(`
|
|
8
|
+
UPDATE task_traces SET
|
|
9
|
+
status = ?,
|
|
10
|
+
final_session_id = ?,
|
|
11
|
+
input_tokens = ?,
|
|
12
|
+
output_tokens = ?,
|
|
13
|
+
cache_read_tokens = ?,
|
|
14
|
+
cache_create_tokens = ?,
|
|
15
|
+
error = ?,
|
|
16
|
+
final_response = COALESCE(?, final_response),
|
|
17
|
+
finished_at = ?,
|
|
18
|
+
duration_ms = ? - started_at
|
|
19
|
+
WHERE task_id = ?
|
|
20
|
+
`).run(t.status,t.finalSessionId??null,t.inputTokens??null,t.outputTokens??null,t.cacheReadTokens??null,t.cacheCreateTokens??null,t.error??null,t.finalResponse??null,s,s,e)}function _(n,e,t){let s=Date.now();return n.transaction(()=>{let l=(n.prepare("SELECT MAX(seq) AS max_seq FROM task_trace_steps WHERE task_id = ?").get(e)?.max_seq??-1)+1;return n.prepare(`
|
|
21
|
+
INSERT INTO task_trace_steps (
|
|
22
|
+
task_id, seq, name, action, status, input_summary, output_summary, error, ms, started_at
|
|
23
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
24
|
+
`).run(e,l,t.name,t.action??null,t.status??null,t.inputSummary??null,t.outputSummary??null,t.error??null,t.ms??null,s),l})()}function i(n){return{taskId:n.task_id,agentId:n.agent_id,channel:n.channel??null,chatId:n.chat_id??null,workflowRunId:n.workflow_run_id??null,workflowId:n.workflow_id??null,workflowNodeId:n.workflow_node_id??null,intentEventId:n.intent_event_id??null,intentDecidedBy:n.intent_decided_by??null,resumeSessionId:n.resume_session_id??null,finalSessionId:n.final_session_id??null,model:n.model??null,status:n.status,startedAt:n.started_at,finishedAt:n.finished_at??null,durationMs:n.duration_ms??null,inputTokens:n.input_tokens??null,outputTokens:n.output_tokens??null,cacheReadTokens:n.cache_read_tokens??null,cacheCreateTokens:n.cache_create_tokens??null,error:n.error??null,messagePreview:n.message_preview??null,originalMessage:n.original_message??null,finalResponse:n.final_response??null}}function d(n){return{taskId:n.task_id,seq:n.seq,name:n.name,action:n.action??null,status:n.status??null,inputSummary:n.input_summary??null,outputSummary:n.output_summary??null,error:n.error??null,ms:n.ms??null,startedAt:n.started_at}}function p(n,e){let t=n.prepare("SELECT * FROM task_traces WHERE task_id = ?").get(e);if(!t)return null;let s=n.prepare("SELECT * FROM task_trace_steps WHERE task_id = ? ORDER BY seq").all(e);return{task:i(t),steps:s.map(d)}}function k(n){let e=Date.now();return n.prepare(`
|
|
25
|
+
UPDATE task_traces
|
|
26
|
+
SET status = 'canceled',
|
|
27
|
+
error = 'daemon-restart (orphaned in-flight)',
|
|
28
|
+
finished_at = ?
|
|
29
|
+
WHERE status = 'in-flight'
|
|
30
|
+
`).run(e).changes??0}function h(n,e={}){let t=[],s=[];e.agentId&&(t.push("agent_id = ?"),s.push(e.agentId)),e.channel&&(t.push("channel = ?"),s.push(e.channel)),e.chatId&&(t.push("chat_id = ?"),s.push(e.chatId)),e.workflowRunId&&(t.push("workflow_run_id = ?"),s.push(e.workflowRunId)),e.status&&(t.push("status = ?"),s.push(e.status)),e.since!==void 0&&(t.push("started_at >= ?"),s.push(e.since)),e.until!==void 0&&(t.push("started_at <= ?"),s.push(e.until));let a=Math.max(1,Math.min(e.limit??100,1e3)),r=`
|
|
31
|
+
SELECT * FROM task_traces
|
|
32
|
+
${t.length>0?"WHERE "+t.join(" AND "):""}
|
|
33
|
+
ORDER BY started_at DESC
|
|
34
|
+
LIMIT ?
|
|
35
|
+
`;return s.push(a),n.prepare(r).all(...s).map(i)}export{g as a,m as b,_ as c,p as d,k as e,h as f};
|
|
36
|
+
//# sourceMappingURL=chunk-4BR7D2OU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage/traces.ts"],"sourcesContent":["import type Database from \"better-sqlite3\"\nimport { newEventId } from \"@/intent/ulid\"\n\n// --- Task trace store ---\n//\n// Improvement plan #2 — per-task observability backed by SQLite. One\n// task_traces row per executeTask invocation, an append-only stream of\n// task_trace_steps inside it. The store is intentionally dumb: it stores\n// what callers give it. Callers (capture sites in src/agents/runtime.ts)\n// are responsible for byte-capping large step payloads so the daemon\n// memory stays bounded under tool-heavy turns.\n//\n// Why a separate store layer (vs writing inline in runtime.ts)?\n// - Tests can exercise the trace lifecycle against a temp SQLite without\n// spinning up the agent runtime.\n// - The HTTP endpoints (GET /task/:id/trace) read through the same\n// functions producers write through, so there's a single source of\n// truth on field names + semantics.\n// - Future capture sites (sdk tier, orchestrator tier) reuse this API\n// without copying SQL.\n//\n// Why ULID for task_id?\n// - URL-safe in HTTP routes (GET /task/:id/trace).\n// - Time-sortable, so listTraces ORDER BY started_at and ORDER BY\n// task_id agree without an explicit timestamp column index.\n// - Already in-tree (src/intent/ulid.ts); no new dep.\n//\n// Cross-DB FK note:\n// intent_event_id and intent_decided_by are logical foreign keys into\n// the intent ledger at .agentx/intent/ledger.sqlite. better-sqlite3\n// doesn't enforce cross-database FKs and we don't ATTACH the ledger\n// here on purpose — the rescue plan's append-only contract on the\n// ledger is honoured by routing all writes through src/intent/ledger.ts.\n// Joins happen at the HTTP / CLI layer when needed.\n\nexport interface TraceStartInput {\n agentId: string\n channel?: string | null\n chatId?: string | null\n messagePreview?: string | null\n /** Full untruncated user message — used by `agentx trace replay <taskId>`\n * to re-fire a recorded task against the current agent config. NULL on\n * rows from before migration v8, in which case replay falls back to\n * messagePreview (which is capped at 200 chars). */\n originalMessage?: string | null\n workflowRunId?: string | null\n workflowId?: string | null\n workflowNodeId?: string | null\n intentEventId?: string | null\n intentDecidedBy?: string | null\n resumeSessionId?: string | null\n model?: string | null\n}\n\nexport interface TraceEndInput {\n status: \"ok\" | \"error\" | \"timeout\"\n finalSessionId?: string | null\n inputTokens?: number | null\n outputTokens?: number | null\n cacheReadTokens?: number | null\n cacheCreateTokens?: number | null\n error?: string | null\n /** The agent's final reply text. Stored verbatim so `replay --diff`\n * can compare original output vs the new run's output without\n * reconstructing it from the step ledger. NULL when the response\n * wasn't captured (older rows or non-text outputs). */\n finalResponse?: string | null\n}\n\nexport interface TraceStepInput {\n /** Coarse step kind. Conventional values: \"tool_use\" | \"tool_result\" |\n * \"llm_message\" | \"session_rotation\" | \"error\" | \"preflight\". Free-form\n * strings allowed so future capture sites don't need a schema bump. */\n name: string\n /** Fine-grained name within the kind. For \"tool_use\", this is the tool\n * name (e.g. \"Bash\", \"Edit\"). Optional. */\n action?: string | null\n status?: \"ok\" | \"error\" | \"in-flight\" | null\n /** JSON-stringified input or human-readable summary. Caller-byte-capped. */\n inputSummary?: string | null\n outputSummary?: string | null\n error?: string | null\n /** Step duration when known. Optional — many step kinds are point-in-time. */\n ms?: number | null\n}\n\nexport interface TraceRecord {\n taskId: string\n agentId: string\n channel: string | null\n chatId: string | null\n workflowRunId: string | null\n workflowId: string | null\n workflowNodeId: string | null\n intentEventId: string | null\n intentDecidedBy: string | null\n resumeSessionId: string | null\n finalSessionId: string | null\n model: string | null\n status: string\n startedAt: number\n finishedAt: number | null\n durationMs: number | null\n inputTokens: number | null\n outputTokens: number | null\n cacheReadTokens: number | null\n cacheCreateTokens: number | null\n error: string | null\n messagePreview: string | null\n /** Full untruncated user message — populated for traces from migration v8\n * onward. NULL on older rows; consumers should fall back to messagePreview. */\n originalMessage: string | null\n /** Agent's final reply text — populated for traces from migration v8\n * onward when the executor records it on end. */\n finalResponse: string | null\n}\n\nexport interface TraceStepRecord {\n taskId: string\n seq: number\n name: string\n action: string | null\n status: string | null\n inputSummary: string | null\n outputSummary: string | null\n error: string | null\n ms: number | null\n startedAt: number\n}\n\nexport interface ListTracesFilters {\n agentId?: string\n channel?: string\n chatId?: string\n workflowRunId?: string\n status?: string\n /** ms epoch */\n since?: number\n /** ms epoch */\n until?: number\n /** Default 100, capped at 1000. */\n limit?: number\n}\n\n/**\n * Insert an in-flight trace row and return its ULID. Callers thread the\n * returned id through the executor and call recordTraceEnd at finish time.\n *\n * `taskId` is optional — when supplied, the caller has already allocated\n * a ULID upstream (e.g. registry.execute generating it before the bus\n * event so the runtime can capture per-step rows under the same id).\n * When omitted, a fresh ULID is allocated here.\n */\nexport function recordTraceStart(\n db: Database.Database,\n input: TraceStartInput,\n taskId: string = newEventId(),\n): string {\n db.prepare(`\n INSERT INTO task_traces (\n task_id, agent_id, channel, chat_id, workflow_run_id, workflow_id,\n workflow_node_id, intent_event_id, intent_decided_by, resume_session_id,\n model, status, started_at, message_preview, original_message\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'in-flight', ?, ?, ?)\n `).run(\n taskId,\n input.agentId,\n input.channel ?? null,\n input.chatId ?? null,\n input.workflowRunId ?? null,\n input.workflowId ?? null,\n input.workflowNodeId ?? null,\n input.intentEventId ?? null,\n input.intentDecidedBy ?? null,\n input.resumeSessionId ?? null,\n input.model ?? null,\n Date.now(),\n input.messagePreview ?? null,\n input.originalMessage ?? null,\n )\n return taskId\n}\n\n/**\n * Finalize a trace. duration_ms is computed from finished_at - started_at\n * inside the UPDATE so the two columns can never disagree. Idempotent —\n * calling twice on the same task_id overwrites the prior end values; the\n * second call's status wins. (Soft-overwrite is intentional: timeout\n * handlers may write before a late stream-completion arrives.)\n */\nexport function recordTraceEnd(db: Database.Database, taskId: string, input: TraceEndInput): void {\n const finishedAt = Date.now()\n db.prepare(`\n UPDATE task_traces SET\n status = ?,\n final_session_id = ?,\n input_tokens = ?,\n output_tokens = ?,\n cache_read_tokens = ?,\n cache_create_tokens = ?,\n error = ?,\n final_response = COALESCE(?, final_response),\n finished_at = ?,\n duration_ms = ? - started_at\n WHERE task_id = ?\n `).run(\n input.status,\n input.finalSessionId ?? null,\n input.inputTokens ?? null,\n input.outputTokens ?? null,\n input.cacheReadTokens ?? null,\n input.cacheCreateTokens ?? null,\n input.error ?? null,\n input.finalResponse ?? null,\n finishedAt,\n finishedAt,\n taskId,\n )\n}\n\n/**\n * Append a step. seq is auto-allocated as MAX(seq)+1 within a transaction\n * so concurrent step writes for the same task_id can't collide. Returns\n * the assigned seq, useful for downstream correlation.\n */\nexport function recordTraceStep(db: Database.Database, taskId: string, input: TraceStepInput): number {\n const startedAt = Date.now()\n const allocateAndInsert = db.transaction((): number => {\n const row = db\n .prepare(\"SELECT MAX(seq) AS max_seq FROM task_trace_steps WHERE task_id = ?\")\n .get(taskId) as { max_seq: number | null } | undefined\n const seq = ((row?.max_seq ?? -1) as number) + 1\n db.prepare(`\n INSERT INTO task_trace_steps (\n task_id, seq, name, action, status, input_summary, output_summary, error, ms, started_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n taskId,\n seq,\n input.name,\n input.action ?? null,\n input.status ?? null,\n input.inputSummary ?? null,\n input.outputSummary ?? null,\n input.error ?? null,\n input.ms ?? null,\n startedAt,\n )\n return seq\n })\n return allocateAndInsert()\n}\n\nfunction rowToTrace(row: Record<string, unknown>): TraceRecord {\n return {\n taskId: row.task_id as string,\n agentId: row.agent_id as string,\n channel: (row.channel as string) ?? null,\n chatId: (row.chat_id as string) ?? null,\n workflowRunId: (row.workflow_run_id as string) ?? null,\n workflowId: (row.workflow_id as string) ?? null,\n workflowNodeId: (row.workflow_node_id as string) ?? null,\n intentEventId: (row.intent_event_id as string) ?? null,\n intentDecidedBy: (row.intent_decided_by as string) ?? null,\n resumeSessionId: (row.resume_session_id as string) ?? null,\n finalSessionId: (row.final_session_id as string) ?? null,\n model: (row.model as string) ?? null,\n status: row.status as string,\n startedAt: row.started_at as number,\n finishedAt: (row.finished_at as number) ?? null,\n durationMs: (row.duration_ms as number) ?? null,\n inputTokens: (row.input_tokens as number) ?? null,\n outputTokens: (row.output_tokens as number) ?? null,\n cacheReadTokens: (row.cache_read_tokens as number) ?? null,\n cacheCreateTokens: (row.cache_create_tokens as number) ?? null,\n error: (row.error as string) ?? null,\n messagePreview: (row.message_preview as string) ?? null,\n originalMessage: (row.original_message as string) ?? null,\n finalResponse: (row.final_response as string) ?? null,\n }\n}\n\nfunction rowToStep(row: Record<string, unknown>): TraceStepRecord {\n return {\n taskId: row.task_id as string,\n seq: row.seq as number,\n name: row.name as string,\n action: (row.action as string) ?? null,\n status: (row.status as string) ?? null,\n inputSummary: (row.input_summary as string) ?? null,\n outputSummary: (row.output_summary as string) ?? null,\n error: (row.error as string) ?? null,\n ms: (row.ms as number) ?? null,\n startedAt: row.started_at as number,\n }\n}\n\n/** Fetch a trace + its ordered steps. Returns null if no such task_id. */\nexport function getTrace(\n db: Database.Database,\n taskId: string,\n): { task: TraceRecord; steps: TraceStepRecord[] } | null {\n const row = db.prepare(\"SELECT * FROM task_traces WHERE task_id = ?\").get(taskId) as\n | Record<string, unknown>\n | undefined\n if (!row) return null\n const stepRows = db\n .prepare(\"SELECT * FROM task_trace_steps WHERE task_id = ? ORDER BY seq\")\n .all(taskId) as Record<string, unknown>[]\n return { task: rowToTrace(row), steps: stepRows.map(rowToStep) }\n}\n\n/**\n * Cancel any in-flight trace rows left over from a previous daemon\n * lifetime. Mirrors the intent ledger's startup cleanup (rescue plan\n * Phase 1 commit a8514d9): a hard-killed daemon never gets to fire\n * task:completed, so the row sits \"in-flight\" forever and contaminates\n * triage queries (`agentx trace list --status in-flight`) with stale\n * orphans. Run once at daemon boot before subscribers attach.\n *\n * Returns the number of rows updated. duration_ms is intentionally NOT\n * computed — we don't know when the task actually ended; preserving\n * NULL there is more honest than backdating to \"now\".\n */\nexport function cleanupOrphanedTraces(db: Database.Database): number {\n const now = Date.now()\n const r = db.prepare(`\n UPDATE task_traces\n SET status = 'canceled',\n error = 'daemon-restart (orphaned in-flight)',\n finished_at = ?\n WHERE status = 'in-flight'\n `).run(now)\n return (r.changes ?? 0) as number\n}\n\n/** Triage / dashboard surface — list traces newest-first, filtered. */\nexport function listTraces(db: Database.Database, filters: ListTracesFilters = {}): TraceRecord[] {\n const where: string[] = []\n const params: unknown[] = []\n if (filters.agentId) { where.push(\"agent_id = ?\"); params.push(filters.agentId) }\n if (filters.channel) { where.push(\"channel = ?\"); params.push(filters.channel) }\n if (filters.chatId) { where.push(\"chat_id = ?\"); params.push(filters.chatId) }\n if (filters.workflowRunId) { where.push(\"workflow_run_id = ?\"); params.push(filters.workflowRunId) }\n if (filters.status) { where.push(\"status = ?\"); params.push(filters.status) }\n if (filters.since !== undefined) { where.push(\"started_at >= ?\"); params.push(filters.since) }\n if (filters.until !== undefined) { where.push(\"started_at <= ?\"); params.push(filters.until) }\n\n const limit = Math.max(1, Math.min(filters.limit ?? 100, 1000))\n const sql = `\n SELECT * FROM task_traces\n ${where.length > 0 ? \"WHERE \" + where.join(\" AND \") : \"\"}\n ORDER BY started_at DESC\n LIMIT ?\n `\n params.push(limit)\n return (db.prepare(sql).all(...params) as Record<string, unknown>[]).map(rowToTrace)\n}\n"],"mappings":"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","names":["init_ulid","recordTraceStart","db","input","taskId","newEventId","recordTraceEnd","finishedAt","recordTraceStep","startedAt","seq","rowToTrace","row","rowToStep","getTrace","stepRows","cleanupOrphanedTraces","now","listTraces","filters","where","params","limit","sql"]}
|