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,380 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
import Carbon.HIToolbox
|
|
3
|
+
|
|
4
|
+
/// AgentX Voice — push-to-talk widget over the daemon's /ask endpoint.
|
|
5
|
+
///
|
|
6
|
+
/// Hold ⌥Space, speak, release. Transcribe (ElevenLabs, falling back to
|
|
7
|
+
/// on-device mlx-whisper), POST to /ask, speak the answer (ElevenLabs,
|
|
8
|
+
/// falling back to `say`).
|
|
9
|
+
///
|
|
10
|
+
/// Deliberately has NO computer-use capability in this slice. The point is
|
|
11
|
+
/// to find out whether talking to an agent this way is actually pleasant
|
|
12
|
+
/// before building the part that can click things.
|
|
13
|
+
/// Main-actor isolated in full. An NSApplicationDelegate touches AppKit in
|
|
14
|
+
/// every method, and the crash that prompted this was UI state reached from
|
|
15
|
+
/// a background queue. Isolating the whole class turns that from a runtime
|
|
16
|
+
/// heap corruption into a compile error.
|
|
17
|
+
@MainActor
|
|
18
|
+
final class App: NSObject, NSApplicationDelegate {
|
|
19
|
+
private let panel = Panel()
|
|
20
|
+
private let card = ResultCard()
|
|
21
|
+
private let recorder = Recorder()
|
|
22
|
+
private var hotkey: Hotkey?
|
|
23
|
+
private var pasteHotkey: Hotkey?
|
|
24
|
+
private var busy = false
|
|
25
|
+
private var progress: Progress?
|
|
26
|
+
private var ticker: Timer?
|
|
27
|
+
private var startedAt = Date()
|
|
28
|
+
private var lastStep = "Thinking…"
|
|
29
|
+
private var spokenSteps = Set<String>()
|
|
30
|
+
private var lastSpokeAt = Date.distantPast
|
|
31
|
+
|
|
32
|
+
/// ⌘⌥V. Everything except the hotkey lives in `agentx paste`.
|
|
33
|
+
private func smartPaste() {
|
|
34
|
+
guard !busy else { return }
|
|
35
|
+
busy = true
|
|
36
|
+
panel.render(.working("Pasting…", 0))
|
|
37
|
+
SmartPaste.run { [weak self] result in
|
|
38
|
+
guard let self else { return }
|
|
39
|
+
self.busy = false
|
|
40
|
+
guard let result else {
|
|
41
|
+
self.panel.render(.error("Smart paste unavailable"))
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
// Quiet when nothing changed: "pasted as copied" is the common
|
|
45
|
+
// case and does not deserve an announcement.
|
|
46
|
+
if let what = SmartPaste.summary(result) {
|
|
47
|
+
self.panel.render(.working(what, 0))
|
|
48
|
+
}
|
|
49
|
+
self.panel.render(.idle)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Only one widget, however it was started.
|
|
54
|
+
///
|
|
55
|
+
/// Two pills were sitting on screen at once: the installed copy in
|
|
56
|
+
/// /Applications and a freshly built one from the source tree. Same
|
|
57
|
+
/// bundle identifier, different paths, so macOS is happy to run both —
|
|
58
|
+
/// and the older one keeps rendering an older UI, which looks exactly
|
|
59
|
+
/// like a bug in the new build.
|
|
60
|
+
///
|
|
61
|
+
/// The NEWEST wins. After an upgrade or a rebuild the thing you just
|
|
62
|
+
/// made is the one you want; a stale copy showing yesterday's
|
|
63
|
+
/// behaviour has no claim to the screen.
|
|
64
|
+
private func retireOlderInstances() {
|
|
65
|
+
let me = ProcessInfo.processInfo.processIdentifier
|
|
66
|
+
let mine = Bundle.main.bundleIdentifier
|
|
67
|
+
for app in NSWorkspace.shared.runningApplications {
|
|
68
|
+
guard app.bundleIdentifier == mine,
|
|
69
|
+
app.processIdentifier != me else { continue }
|
|
70
|
+
Log.info("retiring an older instance (pid \(app.processIdentifier))")
|
|
71
|
+
if !app.terminate() { app.forceTerminate() }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func applicationDidFinishLaunching(_ note: Notification) {
|
|
76
|
+
NSApp.setActivationPolicy(.accessory)
|
|
77
|
+
retireOlderInstances()
|
|
78
|
+
panel.orderFrontRegardless()
|
|
79
|
+
|
|
80
|
+
hotkey = Hotkey(
|
|
81
|
+
id: 1,
|
|
82
|
+
onPress: { [weak self] in self?.startListening() },
|
|
83
|
+
onRelease: { [weak self] in self?.stopAndSend() })
|
|
84
|
+
hotkey?.register()
|
|
85
|
+
panel.onClick = { [weak self] in self?.toggleListening() }
|
|
86
|
+
|
|
87
|
+
// Turning the hold OFF is the moment anything that piled up
|
|
88
|
+
// becomes welcome. Without this the queue is a hole rather than a
|
|
89
|
+
// delay — the daemon's watcher only notices SYSTEM Focus ending,
|
|
90
|
+
// and this switch is not that.
|
|
91
|
+
panel.onHoldChanged = { [weak self] nowOn in
|
|
92
|
+
guard let self else { return }
|
|
93
|
+
self.panel.render(.idle)
|
|
94
|
+
if !nowOn { Hold.flushHeld() }
|
|
95
|
+
Log.info(nowOn ? "notifications held" : "notifications delivering")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ⌘⌥V: reshape the clipboard for wherever the caret is, then paste.
|
|
99
|
+
// Fires on RELEASE so the modifiers are up before cmd-V is sent —
|
|
100
|
+
// pressing it while ⌘⌥ are still held produces a different chord
|
|
101
|
+
// in the destination app.
|
|
102
|
+
pasteHotkey = Hotkey(
|
|
103
|
+
id: 2,
|
|
104
|
+
onPress: {},
|
|
105
|
+
onRelease: { [weak self] in self?.smartPaste() })
|
|
106
|
+
pasteHotkey?.register(keyCode: UInt32(kVK_ANSI_V),
|
|
107
|
+
modifiers: UInt32(cmdKey | optionKey))
|
|
108
|
+
|
|
109
|
+
recorder.requestPermission { [weak self] granted in
|
|
110
|
+
Task { @MainActor in
|
|
111
|
+
guard let self else { return }
|
|
112
|
+
if !granted { self.panel.render(.error("Microphone denied")) }
|
|
113
|
+
else { Log.info("ready — hold ⌥Space to talk (agent: \(Config.agentID))") }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// --- Hands-free listening ---
|
|
119
|
+
//
|
|
120
|
+
// Holding a chord is fine for one question and wrong for a
|
|
121
|
+
// conversation: the reply arrives, you want to say one more thing, and
|
|
122
|
+
// you have to find ⌥Space again while the assistant sits idle. So the
|
|
123
|
+
// microphone reopens by itself after every answer, and closes again on
|
|
124
|
+
// its own if you say nothing.
|
|
125
|
+
//
|
|
126
|
+
// Ending on SILENCE rather than a timer is the whole trick. A fixed
|
|
127
|
+
// window either cuts people off mid-sentence or leaves the mic open
|
|
128
|
+
// staring at them; the only honest signal that a sentence has finished
|
|
129
|
+
// is the room going quiet.
|
|
130
|
+
|
|
131
|
+
/// RMS above which the microphone is hearing a voice rather than a room.
|
|
132
|
+
private let speechLevel: Float = 0.02
|
|
133
|
+
/// Quiet this long after speech ends the turn.
|
|
134
|
+
private let endSilence: TimeInterval = 1.2
|
|
135
|
+
/// How long an unprompted follow-up window waits before giving up.
|
|
136
|
+
private let followUpPatience: TimeInterval = 4.0
|
|
137
|
+
/// How long a clicked session waits for you to start talking.
|
|
138
|
+
private let clickPatience: TimeInterval = 8.0
|
|
139
|
+
|
|
140
|
+
private var listenPoll: Timer?
|
|
141
|
+
private var heardSpeech = false
|
|
142
|
+
private var quietSince: Date?
|
|
143
|
+
private var openedAt = Date()
|
|
144
|
+
private var patience: TimeInterval = 8.0
|
|
145
|
+
/// True when nothing was said and the window should close in silence.
|
|
146
|
+
private var silentClose = false
|
|
147
|
+
|
|
148
|
+
/// Open the microphone with no key held.
|
|
149
|
+
private func listenHandsFree(followUp: Bool) {
|
|
150
|
+
guard !busy, !recorder.isRecording else { return }
|
|
151
|
+
do {
|
|
152
|
+
try recorder.start()
|
|
153
|
+
} catch {
|
|
154
|
+
panel.render(.error(error.localizedDescription))
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
heardSpeech = false
|
|
158
|
+
quietSince = nil
|
|
159
|
+
openedAt = Date()
|
|
160
|
+
patience = followUp ? followUpPatience : clickPatience
|
|
161
|
+
silentClose = followUp
|
|
162
|
+
panel.render(.listening)
|
|
163
|
+
|
|
164
|
+
listenPoll?.invalidate()
|
|
165
|
+
// Timer fires on the run loop but its closure is not main-actor
|
|
166
|
+
// isolated, and pollLevel touches UI. Hopping explicitly keeps it
|
|
167
|
+
// correct under Swift 6 rather than relying on the timer happening
|
|
168
|
+
// to run where we want it.
|
|
169
|
+
listenPoll = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
|
|
170
|
+
Task { @MainActor [weak self] in self?.pollLevel() }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private func pollLevel() {
|
|
175
|
+
guard recorder.isRecording else { stopPolling(); return }
|
|
176
|
+
let level = recorder.level
|
|
177
|
+
|
|
178
|
+
if level > speechLevel {
|
|
179
|
+
heardSpeech = true
|
|
180
|
+
quietSince = nil
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
if heardSpeech {
|
|
184
|
+
// Speech has stopped. Give it a beat before deciding the
|
|
185
|
+
// sentence is over — people pause inside sentences.
|
|
186
|
+
let since = quietSince ?? Date()
|
|
187
|
+
quietSince = since
|
|
188
|
+
if Date().timeIntervalSince(since) >= endSilence {
|
|
189
|
+
stopPolling()
|
|
190
|
+
stopAndSend()
|
|
191
|
+
}
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
// Nothing said yet. Close quietly rather than making the person
|
|
195
|
+
// dismiss a window they did not ask for.
|
|
196
|
+
if Date().timeIntervalSince(openedAt) >= patience {
|
|
197
|
+
stopPolling()
|
|
198
|
+
_ = recorder.stop()
|
|
199
|
+
panel.render(silentClose ? .idle : .error("Didn't catch that"))
|
|
200
|
+
if !silentClose { resetSoon() }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private func stopPolling() {
|
|
205
|
+
listenPoll?.invalidate()
|
|
206
|
+
listenPoll = nil
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// The pill was clicked: start if idle, finish early if already listening.
|
|
210
|
+
private func toggleListening() {
|
|
211
|
+
if recorder.isRecording {
|
|
212
|
+
stopPolling()
|
|
213
|
+
stopAndSend()
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
listenHandsFree(followUp: false)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private func startListening() {
|
|
220
|
+
// A held key overrides any hands-free window that is open, so the
|
|
221
|
+
// two ways of talking never fight over the microphone.
|
|
222
|
+
stopPolling()
|
|
223
|
+
// A turn already in flight must not be interrupted by a stray
|
|
224
|
+
// keypress; the answer is still coming and will be spoken.
|
|
225
|
+
guard !busy, !recorder.isRecording else { return }
|
|
226
|
+
do {
|
|
227
|
+
try recorder.start()
|
|
228
|
+
panel.render(.listening)
|
|
229
|
+
} catch {
|
|
230
|
+
panel.render(.error(error.localizedDescription))
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private func stopAndSend() {
|
|
235
|
+
guard recorder.isRecording else { return }
|
|
236
|
+
guard let wav = recorder.stop() else {
|
|
237
|
+
panel.render(.error("Too short — hold while speaking"))
|
|
238
|
+
resetSoon()
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
busy = true
|
|
242
|
+
panel.render(.thinking)
|
|
243
|
+
|
|
244
|
+
Task { @MainActor in
|
|
245
|
+
do {
|
|
246
|
+
let heard = try await Speech.transcribe(wav: wav)
|
|
247
|
+
guard !heard.isEmpty else {
|
|
248
|
+
panel.render(.error("Didn't catch that"))
|
|
249
|
+
busy = false; resetSoon(); return
|
|
250
|
+
}
|
|
251
|
+
Log.info("heard: \(heard)")
|
|
252
|
+
beginNarration()
|
|
253
|
+
|
|
254
|
+
let answer = try await AgentClient.ask(heard)
|
|
255
|
+
endNarration()
|
|
256
|
+
Log.info("answer: \(answer.text)")
|
|
257
|
+
// Show BEFORE speaking, but only when there is something
|
|
258
|
+
// the speech cannot deliver — a link, an image, or more
|
|
259
|
+
// text than was read aloud. A card that opens on every
|
|
260
|
+
// "Ok." teaches you to ignore it.
|
|
261
|
+
if ResultCard.isWorthShowing(spoken: answer.text, written: answer.written,
|
|
262
|
+
buttons: answer.buttons, imageURL: answer.imageURL) {
|
|
263
|
+
card.show(spoken: answer.text, written: answer.written,
|
|
264
|
+
buttons: answer.buttons, imageURL: answer.imageURL)
|
|
265
|
+
} else {
|
|
266
|
+
card.orderOut(nil)
|
|
267
|
+
}
|
|
268
|
+
// Scroll the sentence being spoken, so it can be read as
|
|
269
|
+
// well as heard — and re-read after, which speech cannot do.
|
|
270
|
+
panel.render(.saying(answer.text))
|
|
271
|
+
await Speech.speak(answer.text)
|
|
272
|
+
panel.render(.idle)
|
|
273
|
+
// Leave the microphone open for a moment. Say nothing and
|
|
274
|
+
// it closes itself; start talking and the conversation
|
|
275
|
+
// simply continues.
|
|
276
|
+
busy = false
|
|
277
|
+
listenHandsFree(followUp: true)
|
|
278
|
+
} catch {
|
|
279
|
+
endNarration()
|
|
280
|
+
Log.warn("turn failed: \(error.localizedDescription)")
|
|
281
|
+
panel.render(.error(short(error.localizedDescription)))
|
|
282
|
+
// Say it aloud too — a voice assistant that fails only in
|
|
283
|
+
// a 230px label has failed silently for anyone not looking.
|
|
284
|
+
await Speech.speak("Sorry, that didn't work.")
|
|
285
|
+
resetSoon()
|
|
286
|
+
}
|
|
287
|
+
busy = false
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/// Narrate the wait using what the daemon says it is actually doing.
|
|
292
|
+
///
|
|
293
|
+
/// Two sources, because neither alone is enough: `task:step` events
|
|
294
|
+
/// say what the agent is doing but arrive irregularly, and a 1-second
|
|
295
|
+
/// ticker proves the thing is still alive between them. Together they
|
|
296
|
+
/// answer both "what is it doing" and "is it stuck".
|
|
297
|
+
private func beginNarration() {
|
|
298
|
+
startedAt = Date()
|
|
299
|
+
lastStep = "Thinking…"
|
|
300
|
+
spokenSteps.removeAll()
|
|
301
|
+
lastSpokeAt = Date()
|
|
302
|
+
panel.render(.working(lastStep, 0))
|
|
303
|
+
|
|
304
|
+
// Progress delivers on its own serial queue; hop to main before
|
|
305
|
+
// touching any view.
|
|
306
|
+
progress = Progress(agentID: Config.agentID) { [weak self] step in
|
|
307
|
+
Task { @MainActor in
|
|
308
|
+
guard let self, self.busy else { return }
|
|
309
|
+
self.lastStep = step
|
|
310
|
+
Log.info("step: \(step)")
|
|
311
|
+
self.panel.render(.working(step, Int(Date().timeIntervalSince(self.startedAt))))
|
|
312
|
+
self.speakStepIfDue(step)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
progress?.start()
|
|
316
|
+
|
|
317
|
+
ticker = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
|
318
|
+
Task { @MainActor in
|
|
319
|
+
guard let self, self.busy else { return }
|
|
320
|
+
self.panel.render(.working(self.lastStep, Int(Date().timeIntervalSince(self.startedAt))))
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private func endNarration() {
|
|
326
|
+
ticker?.invalidate(); ticker = nil
|
|
327
|
+
progress?.stop(); progress = nil
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/// Say what it is doing, sparingly.
|
|
331
|
+
///
|
|
332
|
+
/// Silence during a long turn reads as a broken assistant, and a
|
|
333
|
+
/// visible label does not help someone who asked by voice precisely
|
|
334
|
+
/// because they were not looking at the screen. But narrating every
|
|
335
|
+
/// step would talk over itself and be worse than silence — so: nothing
|
|
336
|
+
/// for the first stretch, then at most one short line every 15
|
|
337
|
+
/// seconds, and never the same step twice.
|
|
338
|
+
private func speakStepIfDue(_ step: String) {
|
|
339
|
+
let elapsed = Date().timeIntervalSince(startedAt)
|
|
340
|
+
guard elapsed > 12 else { return }
|
|
341
|
+
guard Date().timeIntervalSince(lastSpokeAt) > 15 else { return }
|
|
342
|
+
|
|
343
|
+
// The step is "Tool: detail". Both halves go to the daemon, which
|
|
344
|
+
// decides whether it is worth saying and in what words. Reading the
|
|
345
|
+
// detail aloud directly is what produced "still working, osascript
|
|
346
|
+
// tell application Calendar".
|
|
347
|
+
let parts = step.split(separator: ":", maxSplits: 1).map(String.init)
|
|
348
|
+
let tool = parts.first?.trimmingCharacters(in: .whitespaces) ?? step
|
|
349
|
+
let detail = parts.count > 1 ? parts[1].trimmingCharacters(in: .whitespaces) : ""
|
|
350
|
+
|
|
351
|
+
// Reserve the slot before awaiting, or two steps arriving together
|
|
352
|
+
// both pass the interval check and talk over each other.
|
|
353
|
+
lastSpokeAt = Date()
|
|
354
|
+
Task { @MainActor in
|
|
355
|
+
guard let phrase = await AgentClient.phrase(
|
|
356
|
+
tool: tool, detail: detail, elapsed: Int(elapsed)) else { return }
|
|
357
|
+
guard !self.spokenSteps.contains(phrase) else { return }
|
|
358
|
+
self.spokenSteps.insert(phrase)
|
|
359
|
+
await Speech.speak(phrase.prefix(1).capitalized + phrase.dropFirst() + ".")
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private func short(_ s: String) -> String {
|
|
364
|
+
s.count > 40 ? String(s.prefix(38)) + "…" : s
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private func resetSoon() {
|
|
368
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
|
|
369
|
+
MainActor.assumeIsolated {
|
|
370
|
+
guard let self, !self.recorder.isRecording, !self.busy else { return }
|
|
371
|
+
self.panel.render(.idle)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let app = NSApplication.shared
|
|
378
|
+
let delegate = MainActor.assumeIsolated { App() }
|
|
379
|
+
app.delegate = delegate
|
|
380
|
+
app.run()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Build AgentX Voice into a .app bundle.
|
|
3
|
+
#
|
|
4
|
+
# swiftc directly, not SwiftPM: a CommandLineTools-only install ships a
|
|
5
|
+
# broken PackageDescription ManifestAPI, and this target has no
|
|
6
|
+
# dependencies, so the package manager buys nothing here.
|
|
7
|
+
#
|
|
8
|
+
# The .app wrapper is not cosmetic — macOS grants microphone access to
|
|
9
|
+
# bundles with an Info.plist, so a bare executable can never get a TCC
|
|
10
|
+
# prompt and will fail with an inscrutable silence instead.
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
cd "$(dirname "$0")"
|
|
13
|
+
|
|
14
|
+
APP="build/AgentX Voice.app"
|
|
15
|
+
BIN="$APP/Contents/MacOS"
|
|
16
|
+
rm -rf build && mkdir -p "$BIN" "$APP/Contents/Resources"
|
|
17
|
+
|
|
18
|
+
swiftc -O \
|
|
19
|
+
-o "$BIN/AgentXVoice" \
|
|
20
|
+
Sources/AgentXVoice/*.swift \
|
|
21
|
+
-framework AppKit -framework AVFoundation -framework Carbon \
|
|
22
|
+
-target arm64-apple-macosx14.0
|
|
23
|
+
|
|
24
|
+
cp Resources/Info.plist "$APP/Contents/Info.plist"
|
|
25
|
+
|
|
26
|
+
# Ad-hoc signature. Unsigned bundles get a fresh TCC identity on every
|
|
27
|
+
# rebuild, so macOS re-asks for the microphone every single launch.
|
|
28
|
+
codesign --force --sign - --identifier tn.acme.agentx.voice "$APP" 2>/dev/null \
|
|
29
|
+
|| echo "warning: codesign failed; expect repeated microphone prompts"
|
|
30
|
+
|
|
31
|
+
echo "built: $APP"
|
|
32
|
+
echo "run: open \"$APP\" (or ./build/AgentX\\ Voice.app/Contents/MacOS/AgentXVoice for logs)"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{a as F,b as L,c as U,d as N,e as O,f as H,g as j}from"./chunk-U2CKL755.js";import{b as B,c as D,d as A,e as P,g as M}from"./chunk-
|
|
1
|
+
import{a as F,b as L,c as U,d as N,e as O,f as H,g as j}from"./chunk-U2CKL755.js";import{b as B,c as D,d as A,e as P,g as M}from"./chunk-5IA2CYHN.js";import"./chunk-P5ZBBEPL.js";import"./chunk-FIOB3MEX.js";import{useEffect as R,useReducer as V,useRef as G}from"react";import{Box as i,Text as o,useApp as W,useInput as J}from"ink";import{randomUUID as z}from"crypto";async function*q(e){let r=[];e.type&&r.push(`type=${encodeURIComponent(e.type)}`);let t=`${e.baseUrl.replace(/\/+$/,"")}/events${r.length?`?${r.join("&")}`:""}`,n={Accept:"text/event-stream"};e.token&&(n.Authorization=`Bearer ${e.token}`);let s=await fetch(t,{headers:n,signal:e.signal});if(!s.ok||!s.body)throw new Error(`SSE connect failed: HTTP ${s.status}`);let u=s.body.getReader(),p=new TextDecoder,y="";try{for(;;){let{done:f,value:l}=await u.read();if(f)return;y+=p.decode(l,{stream:!0});let d;for(;(d=y.indexOf(`
|
|
2
2
|
|
|
3
3
|
`))>=0;){let h=y.slice(0,d);y=y.slice(d+2);let m=X(h);m&&(yield m)}}}finally{try{u.releaseLock()}catch{}}}function X(e){let r="message",t="";for(let s of e.split(`
|
|
4
4
|
`))s.startsWith("event: ")?r=s.slice(7):s.startsWith("data: ")&&(t=s.slice(6));if(!t)return null;let n;try{n=JSON.parse(t)}catch{n=t}return{event:r,data:n}}import{jsx as a,jsxs as c}from"react/jsx-runtime";function $(e,r){return r<=0||e<0?0:e>=r?r-1:e}function E(){return`tui:${z().slice(0,12)}`}function k(e,r){let t=[...e,r];return t.length>50&&t.splice(0,t.length-50),t}function Y(e,r){switch(r.type){case"agents":return{...e,agents:r.data,cursor:{...e.cursor,agents:$(e.cursor.agents,r.data.length)}};case"processes":return{...e,processes:r.data,cursor:{...e.cursor,processes:$(e.cursor.processes,r.data.length)}};case"crons":return{...e,crons:r.data};case"event":{let t=[r.row,...e.events];t.length>200&&(t.length=200);let n=e.channels;return r.channelTally&&(n=new Map(e.channels),n.set(r.channelTally.channel,r.channelTally)),{...e,events:t,channels:n}}case"connError":return{...e,connError:r.error};case"tick":return{...e,lastTick:Date.now()};case"focus":return{...e,focus:r.pane};case"cursor":{let t=r.pane==="agents"?e.agents.length:r.pane==="processes"?e.processes.length:e.events.length;return{...e,cursor:{...e.cursor,[r.pane]:$(e.cursor[r.pane]+r.delta,t)}}}case"bottomRight":return{...e,bottomRight:r.pane};case"chatStart":return{...e,chat:{active:!0,agentId:r.agentId,chatId:E(),history:[],text:"",status:"idle",error:null}};case"chatExit":return{...e,chat:{...e.chat,active:!1}};case"chatClear":return{...e,chat:{...e.chat,chatId:E(),history:[],text:"",status:"idle",error:null}};case"chatText":return{...e,chat:{...e.chat,text:r.text}};case"chatSubmitStart":return{...e,chat:{...e.chat,history:k(e.chat.history,r.you),text:"",status:"sending",error:null}};case"chatSubmitDone":return{...e,chat:{...e.chat,history:k(e.chat.history,r.reply),status:"idle",error:null}};case"chatSubmitError":return{...e,chat:{...e.chat,history:k(e.chat.history,{role:"error",text:r.error,at:Date.now()}),status:"idle",error:r.error}};case"chatStreamStart":{let t=k(e.chat.history,r.you),n=k(t,{role:"agent",text:"",at:Date.now(),streaming:!0,tools:[]});return{...e,chat:{...e.chat,history:n,text:"",status:"sending",error:null}}}case"chatStreamDelta":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,text:n.text+r.text}),{...e,chat:{...e.chat,history:t}}}case"chatStreamThinking":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,thinking:(n.thinking??"")+r.text}),{...e,chat:{...e.chat,history:t}}}case"chatStreamTool":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,tools:[...n.tools??[],{name:r.name,arg:r.arg,error:r.error}]}),{...e,chat:{...e.chat,history:t}}}case"chatStreamEnd":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]=r.error?{...n,streaming:!1,role:"error",text:r.error}:{...n,streaming:!1,elapsedMs:r.elapsedMs,outTokens:r.outTokens}),{...e,chat:{...e.chat,history:t,status:"idle",error:r.error??null}}}case"toast":return{...e,toast:{text:r.text,color:r.color,at:Date.now()}};case"clearToast":return{...e,toast:null}}}var _={agents:[],processes:[],crons:[],events:[],channels:new Map,connError:null,lastTick:0,focus:"agents",cursor:{agents:0,processes:0,events:0},bottomRight:"crons",chat:{active:!1,agentId:null,chatId:E(),history:[],text:"",status:"idle",error:null},toast:null};function be({conn:e,pollMs:r=3e3}){let[t,n]=V(Y,_),{exit:s}=W(),u=G(null),p=G(null),y=t.agents.map(l=>l.id),f=t.chat.active?L(t.chat.text,y,process.cwd()):null;return R(()=>{if(!t.toast)return;let l=setTimeout(()=>n({type:"clearToast"}),3500);return()=>clearTimeout(l)},[t.toast]),J((l,d)=>{if(t.chat.active){if(d.escape){n({type:"chatExit"});return}if(d.ctrl&&l==="l"){n({type:"chatClear"});return}if(d.tab){let m=U(t.chat.text,p,y,process.cwd());m!==null&&n({type:"chatText",text:m});return}if(d.backspace||d.delete){p.current=null,n({type:"chatText",text:t.chat.text.slice(0,-1)});return}if(d.ctrl||d.meta)return;let h=N(l??"",d.return,t.chat.text);if(h.kind==="paste"||h.kind==="type"){p.current=null,n({type:"chatText",text:t.chat.text+h.text});return}if(h.kind==="continue"){n({type:"chatText",text:h.buffer});return}if(h.kind==="submit"){let m=h.text.trim(),g=t.chat.agentId;if(!m||!g||t.chat.status==="sending"){n({type:"chatText",text:""});return}p.current=null;let b=Date.now(),w=t.chat.chatId;n({type:"chatStreamStart",you:{role:"you",text:m,at:b}}),(async()=>{try{let C=await M(e,g,m,{channel:"tui",chatId:w,onText:x=>n({type:"chatStreamDelta",text:x}),onThinking:x=>n({type:"chatStreamThinking",text:x}),onTool:x=>{x.status==="start"&&x.name?n({type:"chatStreamTool",name:x.name,arg:x.arg}):x.status==="result"&&x.error&&n({type:"chatStreamTool",name:x.name??"tool",error:!0})}});n({type:"chatStreamEnd",elapsedMs:Date.now()-b,outTokens:C.usage?.outputTokens,error:C.error})}catch(C){n({type:"chatStreamEnd",elapsedMs:Date.now()-b,error:C?.message||String(C)})}})();return}return}if(l==="q"||d.ctrl&&l==="c"){u.current?.abort(),s();return}if(d.tab){let h=["agents","processes","events"],m=h.indexOf(t.focus);n({type:"focus",pane:h[(m+1)%h.length]});return}if(l==="a"){n({type:"focus",pane:"agents"});return}if(l==="p"){n({type:"focus",pane:"processes"});return}if(l==="e"){n({type:"focus",pane:"events"});return}if(l==="c"){n({type:"bottomRight",pane:t.bottomRight==="crons"?"channels":"crons"});return}if(d.upArrow){n({type:"cursor",pane:t.focus,delta:-1});return}if(d.downArrow){n({type:"cursor",pane:t.focus,delta:1});return}if(d.return&&t.focus==="agents"){let h=t.agents[t.cursor.agents];if(!h){n({type:"toast",text:"no agent selected",color:"yellow"});return}n({type:"chatStart",agentId:h.id});return}if(l==="k"&&t.focus==="processes"){let h=t.processes[t.cursor.processes];if(!h){n({type:"toast",text:"no process selected",color:"yellow"});return}(async()=>{try{await P(e,h.key,"tui"),n({type:"toast",text:`killed ${h.key.agentId}/${h.key.channel}/${h.key.chatId}`,color:"green"})}catch(m){n({type:"toast",text:`kill failed: ${m?.message||m}`,color:"red"})}})();return}}),R(()=>{let l=new AbortController;u.current=l;let d=!1,h=async()=>{try{let[g,b,w]=await Promise.all([B(e,l.signal).catch(()=>[]),D(e,l.signal).catch(()=>[]),A(e,l.signal).catch(()=>[])]);if(d)return;n({type:"agents",data:g}),n({type:"processes",data:b}),n({type:"crons",data:w}),n({type:"tick"}),n({type:"connError",error:null})}catch(g){d||n({type:"connError",error:g?.message||String(g)})}};h();let m=setInterval(h,r);return()=>{d=!0,clearInterval(m),l.abort()}},[e.baseUrl,e.token,r]),R(()=>{let l=!1,d=new AbortController,h=new Map;return(async()=>{let m=500;for(;!l;){try{for await(let g of q({baseUrl:e.baseUrl,token:e.token,signal:d.signal})){let b=ae(g),w;if(g.event==="channel"&&typeof g.data?.channel=="string"){let S=h.get(g.data.channel)??{channel:g.data.channel,inbound:0,outbound:0,lastAt:0,lastPreview:""},C=S.inbound+(g.data.direction==="in"?1:0),x=S.outbound+(g.data.direction==="out"?1:0);w={channel:g.data.channel,inbound:C,outbound:x,lastAt:b.at,lastPreview:(g.data.textPreview||"").slice(0,60)},h.set(g.data.channel,w)}n({type:"event",row:b,channelTally:w}),m=500}}catch(g){if(l)return;n({type:"connError",error:`events: ${g?.message||g}`})}await new Promise(g=>setTimeout(g,m)),m=Math.min(m*2,1e4)}})(),()=>{l=!0,d.abort()}},[e.baseUrl,e.token]),c(i,{flexDirection:"column",width:"100%",children:[a(K,{conn:e,lastTick:t.lastTick,connError:t.connError,chat:t.chat}),c(i,{flexDirection:"row",flexGrow:1,children:[c(i,{flexDirection:"column",width:"50%",children:[a(v,{title:"AGENTS",focused:!t.chat.active&&t.focus==="agents",children:a(Z,{rows:t.agents,cursor:t.cursor.agents,focused:!t.chat.active&&t.focus==="agents",talkingTo:t.chat.active?t.chat.agentId:null})}),a(v,{title:"PROCESSES",focused:!t.chat.active&&t.focus==="processes",children:a(ee,{rows:t.processes,cursor:t.cursor.processes,focused:!t.chat.active&&t.focus==="processes"})})]}),t.chat.active?a(oe,{chat:t.chat,suggest:f,agents:t.agents}):c(i,{flexDirection:"column",width:"50%",children:[a(v,{title:"LIVE EVENTS",focused:t.focus==="events",children:a(ne,{rows:t.events,cursor:t.cursor.events,focused:t.focus==="events"})}),a(v,{title:t.bottomRight==="crons"?"CRONS / SCHEDULES":"CHANNELS",focused:!1,children:t.bottomRight==="crons"?a(te,{rows:t.crons}):a(re,{rows:Array.from(t.channels.values())})})]})]}),a(Q,{focus:t.focus,bottomRight:t.bottomRight,chat:t.chat,toast:t.toast})]})}function K({conn:e,lastTick:r,connError:t,chat:n}){let s=t?c(o,{color:"red",children:["\u25CF ",t]}):r===0?a(o,{color:"yellow",children:"\u25CF connecting\u2026"}):a(o,{color:"green",children:"\u25CF live"}),u=n.active?c(o,{color:"green",bold:!0,children:["CHAT @",n.agentId]}):a(o,{bold:!0,children:"agentx tui"});return c(i,{paddingX:1,justifyContent:"space-between",children:[a(i,{children:u}),a(o,{dimColor:!0,children:e.baseUrl}),a(i,{children:s})]})}function Q({focus:e,bottomRight:r,chat:t,toast:n}){let s=t.active?["[Enter] send","[Esc] exit chat","[Ctrl-L] new session"]:[`[Tab/a/p/e] focus (now: ${e})`,e==="agents"?"[Enter] chat":null,e==="processes"?"[k] kill":null,`[c] ${r==="crons"?"\u2192channels":"\u2192crons"}`,"[\u2191/\u2193] move","[q] quit"].filter(Boolean);return c(i,{paddingX:1,borderStyle:"single",borderTop:!0,borderBottom:!1,borderLeft:!1,borderRight:!1,justifyContent:"space-between",children:[a(o,{dimColor:!0,children:s.join(" \xB7 ")}),n?a(o,{color:n.color,children:n.text}):a(o,{children:" "})]})}function v({title:e,focused:r,children:t}){return c(i,{flexDirection:"column",width:"100%",borderStyle:r?"double":"single",borderColor:r?"cyan":void 0,paddingX:1,children:[c(o,{bold:!0,color:r?"cyan":"white",children:[e,r?" \u25CF":""]}),a(i,{flexDirection:"column",marginTop:1,children:t})]})}function I(e,r){return e&&r?{inverse:!0}:{}}function Z({rows:e,cursor:r,focused:t,talkingTo:n}){return e.length===0?a(o,{dimColor:!0,children:"no agents"}):a(i,{flexDirection:"column",children:e.slice(0,12).map((s,u)=>{let p=I(t,u===r),y=s.errors>0?"red":s.active>0?"yellow":"green",f=s.id===n;return c(i,{children:[a(o,{...p,color:y,children:"\u25CF"}),c(o,{...p,children:[" ",T(s.name,14)]}),c(o,{...p,children:[T(s.tier,6)," "]}),c(o,{...p,children:[s.active,"/",s.total]}),s.errors>0?c(o,{...p,color:"red",children:[" err=",s.errors]}):null,f?a(o,{color:"green",children:" \u2194 chatting"}):null]},s.id)})})}function ee({rows:e,cursor:r,focused:t}){if(e.length===0)return a(o,{dimColor:!0,children:"no live processes"});let n=Date.now();return a(i,{flexDirection:"column",children:e.slice(0,12).map((s,u)=>{let p=I(t,u===r),y=Math.max(0,Math.round((n-(s.lastTurnAt||s.spawnedAt))/1e3)),f=s.state==="dead"?"red":s.state==="warm-hot"?"green":"yellow";return c(i,{children:[a(o,{...p,color:f,children:T(s.state,10)}),a(o,{...p,children:T(s.key.agentId,12)}),a(o,{...p,children:T(s.key.channel,10)}),c(o,{...p,children:[s.turnCount,"t ",y,"s"]})]},`${s.key.agentId}-${s.key.channel}-${s.key.chatId}-${u}`)})})}function te({rows:e}){return e.length===0?a(o,{dimColor:!0,children:"no crons"}):a(i,{flexDirection:"column",children:e.slice(0,12).map(r=>{let t=r.enabled?r.consecutiveErrors>0?a(o,{color:"red",children:"err"}):a(o,{color:"green",children:"ok"}):a(o,{dimColor:!0,children:"off"}),n=r.nextRun?new Date(r.nextRun).toLocaleTimeString():"\u2014";return c(i,{children:[a(o,{children:T(r.id,16)}),a(o,{dimColor:!0,children:T(r.schedule,12)}),a(o,{children:T(n,10)}),a(i,{children:t})]},r.id)})})}function re({rows:e}){if(e.length===0)return a(o,{dimColor:!0,children:"(waiting for channel events\u2026)"});let r=[...e].sort((t,n)=>n.lastAt-t.lastAt);return a(i,{flexDirection:"column",children:r.slice(0,12).map(t=>c(i,{children:[a(o,{children:T(t.channel,12)}),c(o,{color:"cyan",children:["\u2190",T(String(t.inbound),4)]}),c(o,{color:"magenta",children:["\u2192",T(String(t.outbound),4)]}),a(o,{dimColor:!0,children:t.lastPreview})]},t.channel))})}function ne({rows:e,cursor:r,focused:t}){return e.length===0?a(o,{dimColor:!0,children:"(waiting for events\u2026)"}):a(i,{flexDirection:"column",children:e.slice(0,18).map((n,s)=>{let u=I(t,s===r);return c(i,{children:[a(o,{...u,dimColor:!0,children:new Date(n.at).toLocaleTimeString().padEnd(10)}),a(o,{...u,color:ce(n.kind),children:T(n.kind,8)}),a(o,{...u,children:n.line})]},`${n.at}-${s}`)})})}function oe({chat:e,suggest:r,agents:t}){let n=e.history.slice(-14),s=t.find(f=>f.id===e.agentId),u=H(s?.tier??"",s?.model),p=e.status==="sending"?[...e.history].reverse().find(f=>f.role==="agent"&&f.streaming):void 0,y=e.status==="sending"?a(O,{startedAt:p?.at??Date.now(),phase:p?.text?"responding":"thinking",indent:""}):e.error?c(o,{color:"red",children:["\u25CF ",e.error]}):a(o,{dimColor:!0,children:"\u25CF ready"});return c(i,{flexDirection:"column",width:"50%",borderStyle:"double",borderColor:"green",paddingX:1,children:[c(i,{justifyContent:"space-between",children:[c(o,{bold:!0,color:"green",children:["CHAT \xB7 @",e.agentId]}),a(i,{children:y})]}),c(o,{children:[c(o,{color:u.color,bold:!0,children:[u.glyph," ",u.label]}),s?.model?c(o,{dimColor:!0,children:[" ",j(s.model)]}):null,c(o,{dimColor:!0,children:[" \xB7 ",e.chatId]})]}),a(i,{flexDirection:"column",flexGrow:1,marginTop:1,children:n.length===0?a(o,{dimColor:!0,children:"(type a message and press Enter \u2014 Esc exits, Ctrl-L starts fresh)"}):n.map((f,l)=>a(se,{turn:f,agentId:e.agentId},`${f.at}-${l}`))}),r?c(o,{dimColor:!0,children:[a(o,{color:"cyan",children:"\u21B9"})," ",r.items.slice(0,5).map((f,l)=>l===0?a(o,{color:"cyan",children:f},l):c(o,{children:[" ",f]},l))]}):null,c(i,{children:[a(o,{color:"cyan",children:"you \u203A "}),a(o,{children:e.text}),a(o,{inverse:!0,children:" "})]})]})}function se({turn:e,agentId:r}){if(e.role==="you")return c(i,{flexDirection:"column",marginBottom:1,children:[a(o,{color:"cyan",children:"you"}),e.text.split(`
|
|
5
5
|
`).map((s,u)=>c(o,{children:[" ",s]},u))]});if(e.role==="error")return c(i,{flexDirection:"column",marginBottom:1,children:[a(o,{color:"red",children:"error"}),c(o,{color:"red",children:[" ",e.text]})]});let t=e.elapsedMs!=null?` \xB7 ${(e.elapsedMs/1e3).toFixed(1)}s${e.outTokens!=null?`, ${e.outTokens} tok`:""}`:e.streaming?" \xB7 \u2026":"",n=F(e.text,58,{balance:e.streaming});return c(i,{flexDirection:"column",marginBottom:1,children:[c(o,{color:"green",children:["@",r??"agent",a(o,{dimColor:!0,children:t})]}),e.thinking?e.thinking.split(`
|
|
6
6
|
`).filter(s=>s.trim()).slice(-2).map((s,u)=>c(o,{dimColor:!0,italic:!0,children:[" ",s]},`th${u}`)):null,(e.tools??[]).map((s,u)=>c(o,{color:s.error?"red":"green",children:[" \u25CF ",c(o,{bold:!s.error,dimColor:s.error,children:[s.name,s.arg?c(o,{dimColor:!0,children:["(",s.arg,")"]}):null,s.error?" failed":""]})]},`tool${u}`)),n.split(`
|
|
7
7
|
`).slice(0,14).map((s,u)=>c(o,{children:[" ",s]},u))]})}function ae(e){let r=e.data??{},t=typeof r.at=="string"||typeof r.at=="number"?new Date(r.at).getTime():Date.now(),n="";switch(e.event){case"run":n=`${r.phase??""} ${r.workflowId??""} ${(r.runId??"").slice(0,8)} ${r.nodeId??""}`.trim();break;case"task":n=`${r.phase??""} ${r.workflowId??""} ${Array.isArray(r.assignedTo)?r.assignedTo.join(","):""} ${r.title?`"${r.title}"`:""}`.trim();break;case"signal":n=`${r.name??""} (${r.scope??""}) ${r.workflowId??""}`.trim();break;case"mesh":n=`${r.peer??""} ${r.healthy?"healthy":"lost"} ${r.delta??""}`.trim();break;case"channel":{n=`${r.direction==="in"?"\u2190":"\u2192"} ${r.channel??""}:${r.chatId??""} ${r.textPreview??""}`.trim();break}case"status":n=`node=${r.node??""} agents=${r.agents??0} active=${(r.active??[]).length}`;break;default:n=typeof r=="string"?r:JSON.stringify(r).slice(0,80)}return{at:Number.isFinite(t)?t:Date.now(),kind:e.event,line:n}}function ce(e){switch(e){case"run":return"cyan";case"task":return"magenta";case"signal":return"green";case"mesh":return"blue";case"channel":return"yellow";default:return"white"}}function T(e,r){let t=(e??"").toString();return t.length>=r?t.slice(0,r-1)+" ":t+" ".repeat(r-t.length)}export{be as App};
|
|
8
|
-
//# sourceMappingURL=App-
|
|
8
|
+
//# sourceMappingURL=App-CKPH3HHF.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{a as Y,b as Z,c as _,d as ee,e as te,f as ne,g as re}from"./chunk-U2CKL755.js";import{b as Q,g as X}from"./chunk-
|
|
1
|
+
import{a as Y,b as Z,c as _,d as ee,e as te,f as ne,g as re}from"./chunk-U2CKL755.js";import{b as Q,g as X}from"./chunk-5IA2CYHN.js";import"./chunk-P5ZBBEPL.js";import"./chunk-FIOB3MEX.js";import{useCallback as oe,useMemo as H,useRef as P,useState as k}from"react";import{Box as g,Text as t,useApp as he,useInput as xe,useStdout as Te}from"ink";import{randomUUID as se}from"crypto";var m={accent:"cyan",agent:"green",working:"magenta",error:"red",muted:"gray"},q="\u25C7 agentx",F="\u258C";import{jsx as c,jsxs as o}from"react/jsx-runtime";var ie="/help /agent <id> /agents /clear /who /exit",be=[{cmd:"/agent",desc:"switch the target agent (/agent <id>)"},{cmd:"/agents",desc:"list registered agents"},{cmd:"/clear",desc:"start a fresh session"},{cmd:"/who",desc:"show agent + chat id"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit the chat"}];function Pe({conn:n,agentId:p,channel:f,chatId:a,agents:h}){let{exit:w}=he(),{stdout:O}=Te(),V=Math.max(40,Math.min((O?.columns||80)-4,100)),[z,U]=k([]),[$,S]=k(null),[T,le]=k(p),[A,ae]=k(h),[E,ue]=k(a),[y,x]=k(""),[J,de]=k(""),W=P([]),u=P(-1),M=P(null),R=P(null),v=$?.live??!1,j=H(()=>Z(y,A.map(s=>s.id),process.cwd()),[y,A]),N=H(()=>{let s=/^\/(\w*)$/.exec(y);if(!s)return null;let e=be.filter(r=>r.cmd.slice(1).startsWith(s[1].toLowerCase()));return e.length?e:null},[y]),b=s=>de(s),K=oe(s=>{let e=se().slice(0,8),r=Date.now(),i=new AbortController;R.current=i;let d={id:e,you:s,agentId:T,segs:[],live:!0,startedAt:r};S({...d});let L=()=>S({...d,segs:d.segs.map(C=>({...C}))});(async()=>{try{let C=await X(n,T,s,{channel:f,chatId:E,signal:i.signal,onText:l=>{let I=d.segs[d.segs.length-1];I&&I.type==="text"?I.content=(I.content??"")+l:d.segs.push({type:"text",content:l}),L()},onThinking:l=>{d.thinking=(d.thinking??"")+l,L()},onTool:l=>{l.status==="start"&&l.name?(d.segs.push({type:"tool",name:l.name,arg:l.arg}),L()):l.status==="result"&&l.error&&(d.segs.push({type:"tool",name:l.name??"tool",error:!0}),L())}});U(l=>[...l,{...d,live:!1,elapsedMs:Date.now()-r,outTokens:C.usage?.outputTokens,error:C.error}]),S(null)}catch(C){let l=i.signal.aborted||C?.name==="AbortError";U(I=>[...I,{...d,live:!1,elapsedMs:Date.now()-r,interrupted:l,error:l?void 0:C?.message||String(C)}]),S(null)}finally{R.current===i&&(R.current=null)}})()},[n,T,f,E]),me=oe(s=>{let e=s.trim();if(e){if(W.current.push(e),u.current=-1,e==="/exit"||e==="/quit"){w();return}if(e==="/help"){b(ie);return}if(e==="/who"){b(`@${T} \xB7 chatId=${E} \xB7 channel=${f}`);return}if(e==="/clear"){let r=`chat-cli:${se().slice(0,12)}`;ue(r),U([]),b(`fresh chatId=${r} (warm process dropped)`);return}if(e==="/agents"){Q(n).then(r=>{ae(r),b(r.map(i=>i.id===T?`[${i.id}]`:i.id).join(" "))}).catch(r=>b(`failed to list agents: ${r?.message||r}`));return}if(e.startsWith("/agent ")){let r=e.slice(7).replace(/^@/,"").trim();if(!r){b("usage: /agent <id>");return}if(!A.find(i=>i.id===r)){b(`unknown agent: @${r}`);return}le(r),b(`now talking to @${r}`);return}if(e.startsWith("/")){b(`unknown command \xB7 ${ie}`);return}K(e)}},[n,T,A,f,E,w,K]);xe((s,e)=>{if(e.ctrl&&s==="c"){w();return}if(e.escape){if(v&&R.current){R.current.abort();return}w();return}if(e.tab){if(N){x(N[0].cmd+" ");return}let i=_(y,M,A.map(d=>d.id),process.cwd());i!==null&&(x(i),u.current=-1);return}if(e.upArrow){let i=W.current;if(!i.length)return;u.current=u.current<0?i.length-1:Math.max(0,u.current-1),x(i[u.current]??"");return}if(e.downArrow){let i=W.current;if(u.current<0)return;if(u.current=u.current+1,u.current>=i.length){u.current=-1,x("");return}x(i[u.current]??"");return}if(e.backspace||e.delete){M.current=null,x(i=>i.slice(0,-1));return}if(e.ctrl||e.meta)return;let r=ee(s??"",e.return,y);switch(r.kind){case"paste":M.current=null,x(i=>i+r.text),u.current=-1;return;case"type":M.current=null,x(i=>i+r.text),u.current=-1;return;case"continue":x(r.buffer);return;case"submit":if(v)return;M.current=null,x(""),me(r.text);return;case"none":return}});let G=v?m.working:m.accent,ge=y.length?y.split(`
|
|
2
2
|
`):[""],B=A.find(s=>s.id===T),D=ne(B?.tier??"",B?.model),pe=O?.rows||24,fe=z.slice(-40);return o(g,{flexDirection:"column",height:pe,children:[o(g,{flexDirection:"column",flexGrow:1,justifyContent:"flex-end",overflow:"hidden",children:[z.length===0&&!$?o(g,{flexDirection:"column",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),c(t,{dimColor:!0,children:" \u2014 chat with "}),o(t,{color:m.accent,children:["@",T]}),c(t,{dimColor:!0,children:" via "}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" (",B.model,")"]}):null]}),c(t,{dimColor:!0,children:"streaming \xB7 markdown \xB7 tools \xB7 type a message, or / for commands"})]}):null,fe.map(s=>c(ce,{turn:s,width:V},s.id)),$?c(ce,{turn:$,width:V}):null]}),N?c(g,{flexDirection:"column",marginTop:1,children:N.map((s,e)=>o(g,{children:[c(t,{color:e===0?m.accent:m.muted,bold:e===0,children:s.cmd.padEnd(10)}),c(t,{dimColor:!0,children:s.desc})]},s.cmd))}):j?o(g,{marginTop:1,children:[c(t,{color:m.accent,children:"\u21B9 "}),j.items.map((s,e)=>o(t,{color:e===0?m.accent:m.muted,children:[s,e<j.items.length-1?" ":""]},e))]}):J?c(g,{marginTop:1,children:c(t,{dimColor:!0,children:J})}):null,c(g,{borderStyle:"single",borderColor:G,borderLeft:!1,borderRight:!1,borderDimColor:!v,width:"100%",children:c(g,{flexDirection:"column",width:"100%",children:ge.map((s,e,r)=>o(g,{children:[c(t,{color:G,bold:!0,children:e===0?"\u203A ":" "}),c(t,{children:s}),e===r.length-1&&!v?c(t,{color:G,children:"\u258F"}):null]},e))})}),o(g,{justifyContent:"space-between",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),o(t,{dimColor:!0,children:[" \xB7 @",T," \xB7 "]}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" ",re(B.model)]}):null]}),o(t,{dimColor:!0,children:["\u21B5 send / cmds @ mention esc ",v?"stop":"exit"]})]})]})}function ce({turn:n,width:p}){let f=H(()=>ye(n,p),[n,p]),a=n.segs.some(h=>h.type==="text"&&h.content);return o(g,{flexDirection:"column",marginBottom:1,children:[o(t,{color:m.accent,bold:!0,children:[F," you"]}),n.you.split(`
|
|
3
3
|
`).map((h,w)=>o(t,{color:"white",children:[" ",h]},`u${w}`)),o(t,{color:m.agent,bold:!0,children:[F," ",n.agentId??"agent"]}),n.thinking?c(g,{flexDirection:"column",children:we(n.thinking,n.live?3:2).map((h,w)=>o(t,{dimColor:!0,italic:!0,children:[" ",h]},`th${w}`))}):null,f,n.live?c(te,{startedAt:n.startedAt,phase:a?"responding":"thinking",hint:"esc to interrupt"}):n.interrupted?o(t,{dimColor:!0,children:[" \u2298 interrupted \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s"]}):n.error?o(t,{color:"red",children:[" \u2717 ",n.error]}):n.elapsedMs!=null?o(t,{dimColor:!0,children:[" \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s",n.outTokens!=null?`, ${n.outTokens} tok`:""]}):null]})}function we(n,p){return n.split(`
|
|
4
4
|
`).filter(a=>a.trim()).slice(-p)}function ye(n,p){let f=[];return n.segs.forEach((a,h)=>{a.type==="tool"?f.push(o(t,{color:a.error?"red":"green",children:[" ","\u25CF ",o(t,{bold:!a.error,dimColor:a.error,children:[a.name,a.arg?o(t,{dimColor:!0,children:["(",a.arg,")"]}):null,a.error?" failed":""]})]},h)):a.content&&f.push(c(t,{children:Ce(Y(a.content,p,{balance:n.live}))},h))}),f}function Ce(n){return n.split(`
|
|
5
5
|
`).map(p=>p.length?" "+p:p).join(`
|
|
6
6
|
`)}export{Pe as ChatApp};
|
|
7
|
-
//# sourceMappingURL=ChatApp-
|
|
7
|
+
//# sourceMappingURL=ChatApp-MX7IG4K2.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{A as a,B as b,C as c}from"./chunk-MHIMGM4G.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-LCIREYTD.js";import"./chunk-L2WSPY6S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-FIOB3MEX.js";export{a as createAgentContext,b as generate,c as generateStream};
|
|
2
|
+
//# sourceMappingURL=agent-2U574KBZ.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r}from"./chunk-3SCPRYJB.js";import"./chunk-F4HZTZHW.js";import"./chunk-FIOB3MEX.js";export{a as ARTICLE_FIELDS_SEAT,h as BACKFILLABLE,i as BACKFILL_ORDER,j as FIELD_SOURCES,g as IDENTITY_SLOTS,f as REQUIRED_FIELDS,b as TIERS,d as TIER_MEANING,c as TIER_RANK,n as articleFieldsState,q as fieldChecklistMarkdown,m as fieldQuestions,l as fieldsByTier,k as fieldsFor,e as isCritical,p as nextActions,o as reportFields,r as tierLadderMarkdown};
|
|
2
|
+
//# sourceMappingURL=article-fields-L4D2BOGY.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{a as s,b as n,c as a,f as h}from"./chunk-F4HZTZHW.js";import"./chunk-FIOB3MEX.js";h();var b="article-quality",d={identityFirst:s("Does the article state what the subject is before recounting what happened to it?"),hasIdentifiers:s("Does the article contain a concrete identifier \u2014 a phone number, address, URL, path, account or handle?"),standsAlone:s("Could a reader who has never seen the source material act on this article without going back to it?"),completeness:a(["A log of events; the subject itself is barely described","Describes the subject loosely, but a reader would still have to ask basics","Covers the subject, with one or two facts a reader would expect and not find","A reader could act on this without asking anything further"],"How completely does this article describe its subject?")};function p(e){let t={none:"Nothing important is missing",purpose:"Does not say what the subject is for, or why it matters to us",identifiers:"Describes things by name where a concrete value was available",relationships:"Does not connect the subject to the projects or people around it","too-narrative":"Mostly a chronology of events rather than a description"};return(e==="person"||e==="place"||e==="project")&&(t["contact-details"]="No way to reach, locate or access the subject"),(e==="person"||e==="project")&&(t["role-or-org"]="Does not say what the subject's role is, or what it belongs to"),t}function y(e){return{...d,biggestGap:n(p(e),"What is most conspicuously missing from this article?")}}function w(e,t=12e3){let o=String(e.body??"").trim();return{title:e.title,type:e.type??null,tags:e.tags?.length?e.tags.join(", "):null,body:o.length>t?`${o.slice(0,t)}
|
|
2
|
+
\u2026[truncated]`:o,truncated:o.length>t}}function f(e,t={}){let o=e.completeness.score,c=e.identityFirst.noul,l=e.hasIdentifiers.noul,r=e.standsAlone.noul,u=e.biggestGap.choice,i=t.minCompleteness??2;return{completeness:o,identityFirst:c,hasIdentifiers:l,standsAlone:r,biggestGap:u,adequate:o>=i&&r>=.5,needsRework:o<i||r<.5}}export{b as ARTICLE_QUALITY_SEAT,d as articleQualityQuestions,y as articleQualityQuestionsFor,w as articleQualityState,p as gapCriteria,f as gradeArticle};
|
|
3
|
+
//# sourceMappingURL=article-quality-44ONVGLN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/decisions/seats/article-quality.ts"],"sourcesContent":["import { choice, noul, score } from \"../questions\"\nimport type { AnswersFor, ChoiceAnswer, NoulAnswer, ScoreAnswer, StateValue } from \"../types\"\n\n// Grading what absorb wrote.\n//\n// The failure that motivated this is not a bad article, it is a\n// plausible one. A client contact's page recorded the invoice thread in\n// detail — two dated exchanges, the hourly rate, the instalment — and\n// under \"How to reach him\" said \"WhatsApp, in Arabic\". The channel, not\n// the number. It reads well and cannot answer either question a person\n// article exists for: who is this, and how do I reach them.\n//\n// That is invisible to every check absorb already runs. The JSON parsed,\n// the type was valid, the wikilinks resolved, the length was in range.\n// Nothing in the pipeline asks whether the article is USEFUL, because\n// that is a judgement, and judgements were expensive until they weren't.\n//\n// Deliberately type-agnostic. A person needs a phone number and a place\n// needs a hostname, but both fail the same way — recounting history\n// while omitting identity — and one rubric over the whole corpus is\n// comparable in a way seven type-specific ones are not.\n\nexport const ARTICLE_QUALITY_SEAT = \"article-quality\"\n\nexport const articleQualityQuestions = {\n identityFirst: noul(\n \"Does the article state what the subject is before recounting what happened to it?\",\n ),\n hasIdentifiers: noul(\n \"Does the article contain a concrete identifier — a phone number, address, URL, path, account or handle?\",\n ),\n standsAlone: noul(\n \"Could a reader who has never seen the source material act on this article without going back to it?\",\n ),\n completeness: score(\n [\n \"A log of events; the subject itself is barely described\",\n \"Describes the subject loosely, but a reader would still have to ask basics\",\n \"Covers the subject, with one or two facts a reader would expect and not find\",\n \"A reader could act on this without asking anything further\",\n ],\n \"How completely does this article describe its subject?\",\n ),\n}\n\n/**\n * The gap options, narrowed to what can apply to this type.\n *\n * A Choice must return something. The first version offered\n * \"contact-details\" to every article and it won on concept pages about\n * invoice registers and bank downloaders — you do not phone a register.\n * With one strongly-worded attractor in the set, the model picks it\n * rather than the weaker option that actually fits, so the answer said\n * more about the option list than the article.\n *\n * Excluding inapplicable options is not a thumb on the scale. It is\n * declining to ask a question the article cannot answer.\n */\nexport function gapCriteria(type?: string): Record<string, string> {\n const base: Record<string, string> = {\n none: \"Nothing important is missing\",\n purpose: \"Does not say what the subject is for, or why it matters to us\",\n identifiers: \"Describes things by name where a concrete value was available\",\n relationships: \"Does not connect the subject to the projects or people around it\",\n \"too-narrative\": \"Mostly a chronology of events rather than a description\",\n }\n // Only a thing you contact or visit can be missing a way to reach it.\n if (type === \"person\" || type === \"place\" || type === \"project\") {\n base[\"contact-details\"] = \"No way to reach, locate or access the subject\"\n }\n if (type === \"person\" || type === \"project\") {\n base[\"role-or-org\"] = \"Does not say what the subject's role is, or what it belongs to\"\n }\n return base\n}\n\n/** Questions for one article. The gap options depend on its type. */\nexport function articleQualityQuestionsFor(type?: string) {\n return {\n ...articleQualityQuestions,\n biggestGap: choice(gapCriteria(type), \"What is most conspicuously missing from this article?\"),\n }\n}\n\nexport type ArticleQualityAnswers = AnswersFor<ReturnType<typeof articleQualityQuestionsFor>>\n\nexport interface ArticleForReview {\n path: string\n title: string\n type?: string\n tags?: string[]\n body: string\n}\n\n/** The whole article, because the judgement is about what is absent —\n * and a truncated article is missing things for the wrong reason. */\nexport function articleQualityState(a: ArticleForReview, maxChars = 12_000): StateValue {\n const body = String(a.body ?? \"\").trim()\n return {\n title: a.title,\n type: a.type ?? null,\n tags: a.tags?.length ? a.tags.join(\", \") : null,\n body: body.length > maxChars ? `${body.slice(0, maxChars)}\\n…[truncated]` : body,\n truncated: body.length > maxChars,\n }\n}\n\nexport interface QualityVerdict {\n completeness: number\n identityFirst: number\n hasIdentifiers: number\n standsAlone: number\n biggestGap: string\n /** Good enough to leave alone. */\n adequate: boolean\n /** Worth queueing for a rewrite pass. */\n needsRework: boolean\n}\n\nexport function gradeArticle(\n answers: ArticleQualityAnswers,\n opts: { minCompleteness?: number } = {},\n): QualityVerdict {\n const completeness = (answers.completeness as ScoreAnswer).score\n const identityFirst = (answers.identityFirst as NoulAnswer).noul\n const hasIdentifiers = (answers.hasIdentifiers as NoulAnswer).noul\n const standsAlone = (answers.standsAlone as NoulAnswer).noul\n const gap = (answers.biggestGap as ChoiceAnswer).choice\n const floor = opts.minCompleteness ?? 2\n\n return {\n completeness,\n identityFirst,\n hasIdentifiers,\n standsAlone,\n biggestGap: gap,\n // standsAlone is the one that decides it. An article can open with a\n // tidy identity line and still send the reader back to the source.\n adequate: completeness >= floor && standsAlone >= 0.5,\n needsRework: completeness < floor || standsAlone < 0.5,\n }\n}\n"],"mappings":"yFAAAA,IAsBO,IAAMC,EAAuB,kBAEvBC,EAA0B,CACrC,cAAeC,EACb,mFACF,EACA,eAAgBA,EACd,8GACF,EACA,YAAaA,EACX,qGACF,EACA,aAAcC,EACZ,CACE,0DACA,6EACA,+EACA,4DACF,EACA,wDACF,CACF,EAeO,SAASC,EAAYC,EAAuC,CACjE,IAAMC,EAA+B,CACnC,KAAM,+BACN,QAAS,gEACT,YAAa,gEACb,cAAe,mEACf,gBAAiB,yDACnB,EAEA,OAAID,IAAS,UAAYA,IAAS,SAAWA,IAAS,aACpDC,EAAK,iBAAiB,EAAI,kDAExBD,IAAS,UAAYA,IAAS,aAChCC,EAAK,aAAa,EAAI,kEAEjBA,CACT,CAGO,SAASC,EAA2BF,EAAe,CACxD,MAAO,CACL,GAAGJ,EACH,WAAYO,EAAOJ,EAAYC,CAAI,EAAG,uDAAuD,CAC/F,CACF,CAcO,SAASI,EAAoBC,EAAqBC,EAAW,KAAoB,CACtF,IAAMC,EAAO,OAAOF,EAAE,MAAQ,EAAE,EAAE,KAAK,EACvC,MAAO,CACL,MAAOA,EAAE,MACT,KAAMA,EAAE,MAAQ,KAChB,KAAMA,EAAE,MAAM,OAASA,EAAE,KAAK,KAAK,IAAI,EAAI,KAC3C,KAAME,EAAK,OAASD,EAAW,GAAGC,EAAK,MAAM,EAAGD,CAAQ;AAAA,mBAAoBC,EAC5E,UAAWA,EAAK,OAASD,CAC3B,CACF,CAcO,SAASE,EACdC,EACAC,EAAqC,CAAC,EACtB,CAChB,IAAMC,EAAgBF,EAAQ,aAA6B,MACrDG,EAAiBH,EAAQ,cAA6B,KACtDI,EAAkBJ,EAAQ,eAA8B,KACxDK,EAAeL,EAAQ,YAA2B,KAClDM,EAAON,EAAQ,WAA4B,OAC3CO,EAAQN,EAAK,iBAAmB,EAEtC,MAAO,CACL,aAAAC,EACA,cAAAC,EACA,eAAAC,EACA,YAAAC,EACA,WAAYC,EAGZ,SAAUJ,GAAgBK,GAASF,GAAe,GAClD,YAAaH,EAAeK,GAASF,EAAc,EACrD,CACF","names":["init_questions","ARTICLE_QUALITY_SEAT","articleQualityQuestions","noul","score","gapCriteria","type","base","articleQualityQuestionsFor","choice","articleQualityState","a","maxChars","body","gradeArticle","answers","opts","completeness","identityFirst","hasIdentifiers","standsAlone","gap","floor"]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"./chunk-
|
|
1
|
+
import"./chunk-FIOB3MEX.js";function h(e,t,o,n){let r=[],s=e.frontmatter,f=e.instructions??"",l="PASS";if(s.references?.length)for(let i of s.references)m(i,t)||(l="FAILING",r.push(`references: "${i}" does not resolve in the registry`));if(s.delegatesTo?.length)for(let i of s.delegatesTo)n.has(i)||(l="FAILING",r.push(`delegatesTo: "${i}" has no installed SKILL.md`));k(f)&&(l!=="FAILING"&&(l="REVIEW"),r.push("body contains raw `python3 ~/scripts/...` inside a code block \u2014 delegate to the relevant skill instead"));let d=["deploy","ssh","email","gitlab","ops"],p=(s.category||"").toLowerCase(),I=(s.tags||[]).map(i=>i.toLowerCase());(d.includes(p)||I.some(i=>d.includes(i))||/\b(deploy|ssh|gitlab|hotmail|server|host)\b/i.test(s.description||""))&&(!s.references||s.references.length===0)&&(l==="PASS"&&(l="REVIEW"),r.push("infrastructure-flavored skill has no `references:` frontmatter \u2014 facts should be cited from the registry"));let c=f.match(/\b\d{1,3}(?:\.\d{1,3}){3}\b/g);if(c&&c.length>0){let i=new Set([...t.byId.values()].flatMap(a=>Object.values(a.fields).filter(g=>typeof g=="string"))),u=c.filter(a=>!i.has(a));u.length>0&&(l==="PASS"&&(l="REVIEW"),r.push(`body contains raw IP(s) not in the registry: ${[...new Set(u)].slice(0,3).join(", ")}`))}return{skill:e,name:s.name,verdict:l,reasons:r}}function S(e,t){let o=new Set;for(let r of e.recipes)for(let s of r.skills)o.add(s);let n=[];for(let r of o)t.has(r)||n.push({skill:null,name:r,verdict:"FAILING",reasons:["required by a recipe, but no SKILL.md is installed"]});return n}function A(e){let t=new Set(e.skills.map(n=>n.frontmatter.name));return[...e.skills.map(n=>h(n,e.references,e.recipes,t)),...S(e.recipes,t)]}function k(e){let t=!1;for(let o of e.split(`
|
|
2
2
|
`)){if(/^\s*```/.test(o)){t=!t;continue}if(t&&/python3\s+~\/scripts\//.test(o))return!0}return!1}function m(e,t){if(e.endsWith(".*")){let o=e.slice(0,-2);for(let n of t.byId.keys())if(n===o||n.startsWith(`${o}.`))return!0;return!1}return t.byId.has(e)}export{A as auditAll,S as auditMissingSkills,h as auditSkill};
|
|
3
|
-
//# sourceMappingURL=audit-
|
|
3
|
+
//# sourceMappingURL=audit-4245CX6P.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{g as d,h as u,i as g}from"./chunk-3SCPRYJB.js";import"./chunk-F4HZTZHW.js";import"./chunk-FIOB3MEX.js";function h(c,f){let i=[];for(let n of f){let t=c.fields[n];t?.value?.trim()&&i.push(`${t.value} (${n}, via ${t.source})`)}return i.length>0?i.join("; "):null}var p="## Identity";function E(c,f,i){let n=d[f];if(!n)return null;let t=c.split(`
|
|
2
|
+
`),r=`- **${n.heading}:** ${i}`,l=t.findIndex(e=>e.trim().toLowerCase()===p.toLowerCase()),o=t.length;if(l>=0){for(let e=l+1;e<t.length;e++)if(/^##\s/.test(t[e])){o=e;break}}if(l>=0){for(let e=l+1;e<o;e++){let a=t[e].match(/^\s*-\s*\*\*([^*]+)\*\*\s*:?/);if(!(!a||!n.match.test(a[1])))return t[e].includes(i)?null:(t[e]=r,{content:t.join(`
|
|
3
|
+
`),replaced:!0})}return t.splice(o,0,r),{content:t.join(`
|
|
4
|
+
`),replaced:!1}}let s=t.length;for(let e=0;e<t.length;e++)if(/^##\s/.test(t[e])){s=e;break}return t.splice(s,0,p,"",r,""),{content:t.join(`
|
|
5
|
+
`),replaced:!1}}function I(c,f,i){let n=c,t=[];for(let r of g){if(!i.includes(r))continue;let l=u[r],o=h(f,l.from);if(!o)continue;let s=E(n,r,o);s&&(n=s.content,t.push({field:r,heading:l.heading,value:o,replaced:s.replaced}))}return{content:n,edits:t}}export{I as backfillArticle,E as patchIdentityField,h as renderValue};
|
|
6
|
+
//# sourceMappingURL=backfill-MTD2B62I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wiki/backfill.ts"],"sourcesContent":["import { BACKFILLABLE, BACKFILL_ORDER, IDENTITY_SLOTS } from \"@/decisions/seats/article-fields\"\nimport type { MergedEntity } from \"./facts\"\n\n// Writing resolved identifiers back into articles that are missing them.\n//\n// `wiki gaps` reports that a person article has no contact value and\n// that wacli can supply one. Reporting is where it stopped, so the\n// corpus kept a list of things nobody was going to do by hand. This\n// closes that loop for the one class of gap where closing it needs no\n// judgement: a contact value is a string a system of record handed us,\n// and copying it is the whole operation.\n//\n// Everything else stays a report. A project's status, why a decision was\n// made, who owns a relationship — no lookup returns those, and a\n// confident guess written into the source of truth is worse than an\n// acknowledged gap.\n//\n// All edits go through WikiStore.writeArticle, which versions the prior\n// content into `_versions/` first, so every backfill is reversible.\n\nexport interface FieldEdit {\n field: string\n heading: string\n value: string\n /** Replaced an existing bullet rather than adding one. */\n replaced: boolean\n}\n\nexport interface BackfillResult {\n content: string\n edits: FieldEdit[]\n}\n\n/**\n * Render one identity bullet's value from resolved facts.\n *\n * Several facts can feed one field — a person may have a number, an\n * address and a handle — and all of them belong in the article. Each\n * carries the source it came from, because a reader who finds two\n * numbers needs to know which system said what.\n */\nexport function renderValue(\n entity: MergedEntity,\n from: string[],\n): string | null {\n const parts: string[] = []\n for (const key of from) {\n const f = entity.fields[key]\n if (!f?.value?.trim()) continue\n parts.push(`${f.value} (${key}, via ${f.source})`)\n }\n return parts.length > 0 ? parts.join(\"; \") : null\n}\n\nconst IDENTITY_HEADING = \"## Identity\"\n\n/**\n * Replace or insert one identity bullet.\n *\n * Matching is by keyword rather than by exact heading because the\n * headings were written by a model from the prompt's field labels and\n * vary — \"Contact identifiers\", \"How to reach him\", \"Contact details\".\n * Rewriting them all to a canonical form would be a bigger edit than\n * the fact being added, so the patcher meets the article where it is.\n */\nexport function patchIdentityField(\n content: string,\n field: string,\n value: string,\n): { content: string; replaced: boolean } | null {\n // The wider map: a human answer may fill any identity slot, while\n // only BACKFILLABLE fields may be written without being asked.\n const spec = IDENTITY_SLOTS[field]\n if (!spec) return null\n\n const lines = content.split(\"\\n\")\n const bullet = `- **${spec.heading}:** ${value}`\n\n // Find the Identity section's bounds.\n const start = lines.findIndex((l) => l.trim().toLowerCase() === IDENTITY_HEADING.toLowerCase())\n let end = lines.length\n if (start >= 0) {\n for (let i = start + 1; i < lines.length; i++) {\n if (/^##\\s/.test(lines[i])) { end = i; break }\n }\n }\n\n if (start >= 0) {\n for (let i = start + 1; i < end; i++) {\n const m = lines[i].match(/^\\s*-\\s*\\*\\*([^*]+)\\*\\*\\s*:?/)\n if (!m || !spec.match.test(m[1])) continue\n // Already carries this exact value — leave it alone so the\n // backfill is idempotent and re-running writes no new version.\n if (lines[i].includes(value)) return null\n lines[i] = bullet\n return { content: lines.join(\"\\n\"), replaced: true }\n }\n // Section exists, field does not.\n lines.splice(end, 0, bullet)\n return { content: lines.join(\"\\n\"), replaced: false }\n }\n\n // No Identity section at all — articles written before the tier work.\n // It goes after the opening paragraph, which is the definition, so\n // identity still follows \"what this is\" rather than displacing it.\n let insertAt = lines.length\n for (let i = 0; i < lines.length; i++) {\n if (/^##\\s/.test(lines[i])) { insertAt = i; break }\n }\n lines.splice(insertAt, 0, IDENTITY_HEADING, \"\", bullet, \"\")\n return { content: lines.join(\"\\n\"), replaced: false }\n}\n\n/**\n * Apply every backfillable field the facts can answer.\n *\n * Only fields named in `missing` are touched. A field the grader found\n * present is left exactly as written, even when a source could supply\n * a different value — the article may carry a better-sourced fact than\n * the directory does, and overwriting it would make the wiki a mirror\n * of the systems rather than a record that can correct them.\n */\nexport function backfillArticle(\n content: string,\n entity: MergedEntity,\n missing: string[],\n): BackfillResult {\n let out = content\n const edits: FieldEdit[] = []\n\n for (const field of BACKFILL_ORDER) {\n if (!missing.includes(field)) continue\n const spec = BACKFILLABLE[field]\n const value = renderValue(entity, spec.from)\n if (!value) continue\n const patched = patchIdentityField(out, field, value)\n if (!patched) continue\n out = patched.content\n edits.push({ field, heading: spec.heading, value, replaced: patched.replaced })\n }\n\n return { content: out, edits }\n}\n"],"mappings":"8GAyCO,SAASA,EACdC,EACAC,EACe,CACf,IAAMC,EAAkB,CAAC,EACzB,QAAWC,KAAOF,EAAM,CACtB,IAAMG,EAAIJ,EAAO,OAAOG,CAAG,EACtBC,GAAG,OAAO,KAAK,GACpBF,EAAM,KAAK,GAAGE,EAAE,UAAUD,UAAYC,EAAE,SAAS,EAEnD,OAAOF,EAAM,OAAS,EAAIA,EAAM,KAAK,IAAI,EAAI,IAC/C,CAEA,IAAMG,EAAmB,cAWlB,SAASC,EACdC,EACAC,EACAC,EAC+C,CAG/C,IAAMC,EAAOC,EAAeH,CAAK,EACjC,GAAI,CAACE,EAAM,OAAO,KAElB,IAAME,EAAQL,EAAQ,MAAM;AAAA,CAAI,EAC1BM,EAAS,OAAOH,EAAK,cAAcD,IAGnCK,EAAQF,EAAM,UAAWG,GAAMA,EAAE,KAAK,EAAE,YAAY,IAAMV,EAAiB,YAAY,CAAC,EAC1FW,EAAMJ,EAAM,OAChB,GAAIE,GAAS,GACX,QAASG,EAAIH,EAAQ,EAAGG,EAAIL,EAAM,OAAQK,IACxC,GAAI,QAAQ,KAAKL,EAAMK,CAAC,CAAC,EAAG,CAAED,EAAMC,EAAG,OAI3C,GAAIH,GAAS,EAAG,CACd,QAASG,EAAIH,EAAQ,EAAGG,EAAID,EAAKC,IAAK,CACpC,IAAMC,EAAIN,EAAMK,CAAC,EAAE,MAAM,8BAA8B,EACvD,GAAI,GAACC,GAAK,CAACR,EAAK,MAAM,KAAKQ,EAAE,CAAC,CAAC,GAG/B,OAAIN,EAAMK,CAAC,EAAE,SAASR,CAAK,EAAU,MACrCG,EAAMK,CAAC,EAAIJ,EACJ,CAAE,QAASD,EAAM,KAAK;AAAA,CAAI,EAAG,SAAU,EAAK,GAGrD,OAAAA,EAAM,OAAOI,EAAK,EAAGH,CAAM,EACpB,CAAE,QAASD,EAAM,KAAK;AAAA,CAAI,EAAG,SAAU,EAAM,EAMtD,IAAIO,EAAWP,EAAM,OACrB,QAASK,EAAI,EAAGA,EAAIL,EAAM,OAAQK,IAChC,GAAI,QAAQ,KAAKL,EAAMK,CAAC,CAAC,EAAG,CAAEE,EAAWF,EAAG,MAE9C,OAAAL,EAAM,OAAOO,EAAU,EAAGd,EAAkB,GAAIQ,EAAQ,EAAE,EACnD,CAAE,QAASD,EAAM,KAAK;AAAA,CAAI,EAAG,SAAU,EAAM,CACtD,CAWO,SAASQ,EACdb,EACAP,EACAqB,EACgB,CAChB,IAAIC,EAAMf,EACJgB,EAAqB,CAAC,EAE5B,QAAWf,KAASgB,EAAgB,CAClC,GAAI,CAACH,EAAQ,SAASb,CAAK,EAAG,SAC9B,IAAME,EAAOe,EAAajB,CAAK,EACzBC,EAAQV,EAAYC,EAAQU,EAAK,IAAI,EAC3C,GAAI,CAACD,EAAO,SACZ,IAAMiB,EAAUpB,EAAmBgB,EAAKd,EAAOC,CAAK,EAC/CiB,IACLJ,EAAMI,EAAQ,QACdH,EAAM,KAAK,CAAE,MAAAf,EAAO,QAASE,EAAK,QAAS,MAAAD,EAAO,SAAUiB,EAAQ,QAAS,CAAC,GAGhF,MAAO,CAAE,QAASJ,EAAK,MAAAC,CAAM,CAC/B","names":["renderValue","entity","from","parts","key","f","IDENTITY_HEADING","patchIdentityField","content","field","value","spec","IDENTITY_SLOTS","lines","bullet","start","l","end","i","m","insertAt","backfillArticle","missing","out","edits","BACKFILL_ORDER","BACKFILLABLE","patched"]}
|