@wizdear/atlas-code 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-factory.d.ts +8 -1
- package/dist/agent-factory.d.ts.map +1 -1
- package/dist/agent-factory.js +42 -2
- package/dist/agent-factory.js.map +1 -1
- package/dist/cli.d.ts +7 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/discovery.d.ts +9 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +4 -4
- package/dist/discovery.js.map +1 -1
- package/dist/extension.d.ts +9 -2
- package/dist/extension.d.ts.map +1 -1
- package/dist/extension.js +1096 -333
- package/dist/extension.js.map +1 -1
- package/dist/gate.d.ts +1 -1
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js.map +1 -1
- package/dist/orchestrator.d.ts +0 -2
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +0 -1
- package/dist/orchestrator.js.map +1 -1
- package/dist/pipeline-editor.d.ts +2 -0
- package/dist/pipeline-editor.d.ts.map +1 -1
- package/dist/pipeline-editor.js +36 -5
- package/dist/pipeline-editor.js.map +1 -1
- package/dist/pipeline.d.ts +2 -5
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +4 -3
- package/dist/pipeline.js.map +1 -1
- package/dist/planner.d.ts +9 -0
- package/dist/planner.d.ts.map +1 -1
- package/dist/planner.js +20 -10
- package/dist/planner.js.map +1 -1
- package/dist/roles/architect.d.ts +1 -1
- package/dist/roles/architect.d.ts.map +1 -1
- package/dist/roles/architect.js +1 -1
- package/dist/roles/architect.js.map +1 -1
- package/dist/roles/documenter.d.ts +1 -1
- package/dist/roles/documenter.d.ts.map +1 -1
- package/dist/roles/documenter.js +11 -0
- package/dist/roles/documenter.js.map +1 -1
- package/dist/roles/index.d.ts +1 -0
- package/dist/roles/index.d.ts.map +1 -1
- package/dist/roles/index.js +3 -0
- package/dist/roles/index.js.map +1 -1
- package/dist/roles/recover.d.ts +5 -0
- package/dist/roles/recover.d.ts.map +1 -0
- package/dist/roles/recover.js +82 -0
- package/dist/roles/recover.js.map +1 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +6 -6
- package/dist/router.js.map +1 -1
- package/dist/standards.d.ts.map +1 -1
- package/dist/standards.js +1 -0
- package/dist/standards.js.map +1 -1
- package/dist/step-executor.d.ts +2 -0
- package/dist/step-executor.d.ts.map +1 -1
- package/dist/step-executor.js +16 -4
- package/dist/step-executor.js.map +1 -1
- package/dist/store.d.ts +3 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +48 -19
- package/dist/store.js.map +1 -1
- package/dist/system-architect.d.ts +9 -0
- package/dist/system-architect.d.ts.map +1 -1
- package/dist/system-architect.js +11 -9
- package/dist/system-architect.js.map +1 -1
- package/dist/telegram/bridge.d.ts +39 -0
- package/dist/telegram/bridge.d.ts.map +1 -0
- package/dist/telegram/bridge.js +380 -0
- package/dist/telegram/bridge.js.map +1 -0
- package/dist/telegram/formatter.d.ts +15 -0
- package/dist/telegram/formatter.d.ts.map +1 -0
- package/dist/telegram/formatter.js +86 -0
- package/dist/telegram/formatter.js.map +1 -0
- package/dist/telegram/renderer.d.ts +45 -0
- package/dist/telegram/renderer.d.ts.map +1 -0
- package/dist/telegram/renderer.js +150 -0
- package/dist/telegram/renderer.js.map +1 -0
- package/dist/telegram/telegram-api.d.ts +84 -0
- package/dist/telegram/telegram-api.d.ts.map +1 -0
- package/dist/telegram/telegram-api.js +134 -0
- package/dist/telegram/telegram-api.js.map +1 -0
- package/dist/types.d.ts +10 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +2 -0
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../src/telegram/bridge.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA6BhD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAoC;IAChF,IAAI,SAA6B,CAAC;IAClC,IAAI,UAA8B,CAAC;IAEnC,IAAI,CAAC;QACJ,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAA4B,CAAC;QACrF,MAAM,EAAE,GAAG,GAAG,CAAC,QAA+C,CAAC;QAC/D,IAAI,EAAE,EAAE,CAAC;YACR,SAAS,GAAG,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;YAChE,UAAU,GAAG,OAAO,EAAE,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;QACpE,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,4BAA4B;IAC7B,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,SAAS,CAAC;IAC3D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAE7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,UAAU,CAAC;IAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,SAAS,EAAE,CAAC;AAAA,CAC9C;AAED,oDAAoD;AACpD,SAAS,oBAAoB,CAAC,OAAe,EAAE,MAAc,EAAQ;IACpE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAChD,IAAI,MAAM,GAA4B,EAAE,CAAC;IACzC,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAA4B,CAAC;IACnF,CAAC;IAAC,MAAM,CAAC;QACR,qBAAqB;IACtB,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC7D,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAC;IACtB,CAAC;IACA,MAAM,CAAC,QAAoC,CAAC,MAAM,GAAG,MAAM,CAAC;IAC7D,aAAa,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAAA,CAC9E;AAaD;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CAC1C,EAAgB,EAChB,GAAqB,EACrB,OAAe,EACf,QAAQ,GAAsB,EAAE,EACa;IAC7C,IAAI,OAAiC,CAAC;IACtC,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,SAAS,QAAQ,GAAS;QACzB,IAAI,OAAO;YAAE,OAAO;QACpB,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,OAAO,GAAG,WAAW,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC1D,OAAO,GAAG,IAAI,CAAC;IAAA,CACf;IAED,SAAS,IAAI,GAAS;QACrB,OAAO,EAAE,EAAE,CAAC;QACZ,OAAO,GAAG,SAAS,CAAC;QACpB,OAAO,GAAG,KAAK,CAAC;IAAA,CAChB;IAED,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAAA,CAC1B;AAED,gNAAgF;AAEhF,SAAS,WAAW,CACnB,EAAgB,EAChB,GAAqB,EACrB,MAA4B,EAC5B,OAAe,EACf,QAA2B,EACd;IACb,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,EAAE,CAAC,CAAC;IAC1C,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;IAC9C,MAAM,MAAM,GAAsB,EAAE,CAAC;IAErC,iFAA+E;IAC/E,uDAAuD;IACvD,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,WAAW,GAAG,KAAK,CAAC;IAExB,2BAA2B;IAC3B,EAAE,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,qBAAqB,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACvD,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC;QACxD,CAAC;IAAA,CACD,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,sBAAsB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,IAA+B,CAAC,CAAC;QACrF,QAAQ,CAAC,cAAc,CAAC,UAAU,KAAK,EAAE,CAAC,CAAC;IAAA,CAC3C,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC;QACxB,+FAA6F;QAC7F,4DAA4D;QAC5D,IAAI,CAAC,cAAc,EAAE,CAAC;YACrB,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;QACrC,CAAC;IAAA,CACD,CAAC,CAAC;IAEH,uEAAuE;IACvE,+EAA6E;IAC7E,kEAAkE;IAClE,iEAAiE;IACjE,MAAM,CAAC,IAAI,CACV,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,GAIb,CAAC;QAEF,kEAAkE;QAClE,IAAI,KAAK,CAAC,QAAQ,KAAK,MAAM;YAAE,OAAO;QACtC,IAAI,CAAC,KAAK,CAAC,OAAO;YAAE,OAAO;QAE3B,MAAM,KAAK,GAA2B;YACrC,SAAS,EAAE,QAAQ;YACnB,KAAK,EAAE,QAAQ;YACf,OAAO,EAAE,cAAc;YACvB,OAAO,EAAE,QAAQ;YACjB,KAAK,EAAE,WAAW;YAClB,gBAAgB,EAAE,WAAW;YAC7B,YAAY,EAAE,cAAc;SAC5B,CAAC;QACF,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,WAAW,CAAC;QAElD,yEAAyE;QACzE,4EAA0E;QAC1E,0DAA0D;QAC1D,IAAI,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC;QAChC,IAAI,KAAK,CAAC,QAAQ,KAAK,YAAY,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACvD,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACvD,IAAI,SAAS,EAAE,CAAC;gBACf,WAAW,GAAG,SAAS,CAAC;YACzB,CAAC;QACF,CAAC;QAED,sEAAsE;QACtE,EAAE,CAAC,WAAW,CAAC,GAAG,IAAI,IAAI,WAAW,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;IAAA,CAC/E,CAAC,CACF,CAAC;IAEF,0BAA0B;IAC1B,MAAM,CAAC,IAAI,CACV,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,GAAwB,CAAC;QACvC,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACpC,cAAc,GAAG,IAAI,CAAC;QACvB,CAAC;aAAM,IACN,KAAK,CAAC,IAAI,KAAK,mBAAmB;YAClC,KAAK,CAAC,IAAI,KAAK,kBAAkB;YACjC,KAAK,CAAC,IAAI,KAAK,wBAAwB;YACvC,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAC/B,CAAC;YACF,cAAc,GAAG,KAAK,CAAC;YACvB,WAAW,GAAG,KAAK,CAAC;QACrB,CAAC;QACD,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAAA,CACrC,CAAC,EACF,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,GAAwB,CAAC;QACvC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAAA,CACjC,CAAC,EACF,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,GAAoB,CAAC;QACnC,WAAW,GAAG,IAAI,CAAC;QACnB,MAAM,QAAQ,GAAG;YAChB,eAAe,EAAE;gBAChB;oBACC,EAAE,IAAI,EAAE,gBAAgB,EAAE,aAAa,EAAE,SAAS,EAAE;oBACpD,EAAE,IAAI,EAAE,eAAe,EAAE,aAAa,EAAE,QAAQ,EAAE;iBAClD;gBACD;oBACC,EAAE,IAAI,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,EAAE;oBAC9C,EAAE,IAAI,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE;iBAChD;aACD;SACD,CAAC;QACF,EAAE,CAAC,WAAW,CAAC,gBAAgB,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;IAAA,CAC5F,CAAC,CACF,CAAC;IAEF,4DAA4D;IAC5D,MAAM,CAAC,IAAI,CACV,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,GAAwB,CAAC;QACvC,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACjC,WAAW,GAAG,KAAK,CAAC;QACrB,CAAC;IAAA,CACD,CAAC,CACF,CAAC;IAEF,oCAAoC;IACpC,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE;QAC1D,UAAU,EAAE,GAAG,EAAE,CAAC,cAAc,IAAI,WAAW;QAC/C,aAAa,EAAE,GAAG,EAAE,CAAC,WAAW;QAChC,gBAAgB,EAAE,GAAG,EAAE,CAAC;YACvB,WAAW,GAAG,KAAK,CAAC;QAAA,CACpB;QACD,QAAQ;KACR,CAAC,CAAC;IAEH,4EAA4E;IAC5E,MAAM,YAAY,GAAoD;QACrE,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,wBAAwB,EAAE;KAC1D,CAAC;IACF,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzF,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;gBACnC,8DAA8D;gBAC9D,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;YACzF,CAAC;QACF,CAAC;IACF,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;IAE/C,aAAa;IACb,IAAI,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;QACxB,MAAM,UAAU,GAAG,KAAK;YACvB,CAAC,CAAC,kCAAkC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,EAAE;YAChE,CAAC,CAAC,mCAAmC,CAAC;QACvC,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;SAAM,CAAC;QACP,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,oDAAoD,EAAE,MAAM,CAAC,CAAC;IAC7E,CAAC;IAED,0BAA0B;IAC1B,OAAO,GAAG,EAAE,CAAC;QACZ,eAAe,CAAC,KAAK,EAAE,CAAC;QACxB,KAAK,MAAM,KAAK,IAAI,MAAM;YAAE,KAAK,EAAE,CAAC;IAAA,CACpC,CAAC;AAAA,CACF;AAED,wNAAgF;AAEhF,SAAS,KAAK,CAAC,EAAU,EAAiB;IACzC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAAA,CACzD;AASD,KAAK,UAAU,YAAY,CAC1B,EAAe,EACf,EAAgB,EAChB,GAAqB,EACrB,MAAmB,EACnB,OAAe,EACf,KAAmB,EACH;IAChB,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,UAAU,EAAE,CAAC;YACtC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC9B,IAAI,MAAM,CAAC,OAAO;oBAAE,MAAM;gBAE1B,+CAA+C;gBAC/C,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;oBAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,IAAI,EAAE,CAAC;oBAC9C,EAAE,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;oBACjE,IAAI,KAAK,CAAC,aAAa,EAAE,EAAE,CAAC;wBAC3B,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;wBACvD,KAAK,CAAC,gBAAgB,EAAE,CAAC;oBAC1B,CAAC;oBACD,SAAS;gBACV,CAAC;gBAED,oDAAoD;gBACpD,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC;oBAChD,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBACpD,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;oBAC3B,oBAAoB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;oBAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;oBACxB,MAAM,GAAG,GAAG,KAAK;wBAChB,CAAC,CAAC,qCAAqC,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,EAAE;wBACnE,CAAC,CAAC,4BAA4B,CAAC;oBAChC,MAAM,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;oBAC1C,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAC;oBAC9C,SAAS,CAAC,iDAAiD;gBAC5D,CAAC;gBAED,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC;gBAClC,IAAI,CAAC,IAAI;oBAAE,SAAS;gBAEpB,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;oBACtB,GAAG,CAAC,KAAK,EAAE,CAAC;oBACZ,MAAM,EAAE,CAAC,WAAW,CAAC,iBAAiB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;oBACxD,SAAS;gBACV,CAAC;gBAED,wDAAwD;gBACxD,IAAI,KAAK,CAAC,UAAU,EAAE,EAAE,CAAC;oBACxB,MAAM,EAAE,CAAC,WAAW,CAAC,yCAAyC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;oBAChF,SAAS;gBACV,CAAC;gBAED,IAAI,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;oBAClB,8CAA8C;oBAC9C,2DAA2D;oBAC3D,sEAAsE;oBACtE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC;wBAC/E,SAAS;oBACV,CAAC;oBACD,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;gBAC1B,CAAC;qBAAM,CAAC;oBACP,MAAM,EAAE,CAAC,WAAW,CAAC,6BAA6B,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;gBACrE,CAAC;YACF,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,IAAI,MAAM,CAAC,OAAO;gBAAE,MAAM;YAC1B,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACF,CAAC;AAAA,CACD;AAED,sMAAgF;AAEhF;;;;;;;;GAQG;AACH,SAAS,sBAAsB,CAAC,IAAY,EAAE,QAA2B,EAAE,GAAqB,EAAW;IAC1G,8EAA8E;IAC9E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;IACpE,MAAM,YAAY,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IAExE,kEAAgE;IAChE,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IAC9D,IAAI,WAAW,EAAE,CAAC;QACjB,WAAW,CAAC,OAAO,CAAC,YAAY,EAAE,GAAyC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;QAC7F,OAAO,IAAI,CAAC;IACb,CAAC;IAED,sEAAoE;IACpE,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,WAAW;YAAE,SAAS;QAC/B,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC;QAC9B,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACjC,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC9C,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,EAAE,CAAC;gBACrD,MAAM,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,YAAY,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;gBAC3E,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,GAAyC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;gBACrF,OAAO,IAAI,CAAC;YACb,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,KAAK,CAAC;AAAA,CACb;AAED,uLAA+E;AAE/E,SAAS,mBAAmB,CAAC,KAAwB,EAAE,QAA0B,EAAQ;IACxF,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,eAAe,EAAE,CAAC;YACtB,QAAQ,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,KAA2B,CAAC;YACtD,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAE5E,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,UAAgC,CAAC;YAChE,IAAI,UAAU,EAAE,CAAC;gBAChB,MAAM,KAAK,GAAkB,EAAE,CAAC;gBAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;oBACrC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC3D,CAAC;gBACD,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC1B,CAAC;YACD,MAAM;QACP,CAAC;QAED,KAAK,YAAY,EAAE,CAAC;YACnB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,SAA+B,CAAC;YAC9D,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC7B,QAAQ,CAAC,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;YAC3C,CAAC;YACD,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBAChB,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,SAA+B,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzF,CAAC;YACD,MAAM;QACP,CAAC;QAED,KAAK,eAAe,EAAE,CAAC;YACtB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,SAA+B,CAAC;YAC9D,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC7B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,OAA6B,CAAC;gBAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,KAAsC,CAAC;gBACjE,QAAQ,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YACzG,CAAC;YACD,MAAM;QACP,CAAC;QAED,KAAK,aAAa,EAAE,CAAC;YACpB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,EAAE,SAA+B,CAAC;YAC9D,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC7B,QAAQ,CAAC,UAAU,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC1C,CAAC;YACD,MAAM;QACP,CAAC;QAED,KAAK,mBAAmB,CAAC;QACzB,KAAK,kBAAkB,CAAC;QACxB,KAAK,wBAAwB,CAAC;QAC9B,KAAK,iBAAiB,EAAE,CAAC;YACxB,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAChC,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;YACpC,MAAM;QACP,CAAC;IACF,CAAC;AAAA,CACD","sourcesContent":["/**\n * Telegram bridge — mirrors TUI state to Telegram and accepts simple commands.\n * Activated automatically when token is available (env var or .vibe/config.json).\n * Chat ID is auto-discovered from the first incoming message if not configured.\n */\n\nimport { readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nimport type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from \"@mariozechner/pi-coding-agent\";\n\nimport { formatToolLabel } from \"./formatter.js\";\nimport type { StepDisplay } from \"./renderer.js\";\nimport { ResponseRenderer } from \"./renderer.js\";\nimport { TelegramAPI } from \"./telegram-api.js\";\n\n// ─── Vibe event shapes (local, not exported) ────────────────────────────────\n\ninterface VibePipelineEvent {\n\ttype: string;\n\tfeatureId: string;\n\tstep?: { agent: string; action: string };\n\tdata?: Record<string, unknown>;\n}\n\ninterface VibeActivityEvent {\n\trole: string;\n\ttext: string;\n\ttype: string;\n}\n\ninterface VibeGateEvent {\n\tfeatureId: string;\n\tsummary: string;\n}\n\n// ─── Config resolution ───────────────────────────────────────────────────────\n\ninterface TelegramBridgeConfig {\n\ttoken: string;\n\tchatId?: string;\n}\n\n/**\n * Resolve Telegram config from env vars and .vibe/config.json.\n * Priority: env var > config file.\n */\nexport function resolveConfig(vibeDir: string): TelegramBridgeConfig | undefined {\n\tlet fileToken: string | undefined;\n\tlet fileChatId: string | undefined;\n\n\ttry {\n\t\tconst configPath = join(vibeDir, \"config.json\");\n\t\tconst raw = JSON.parse(readFileSync(configPath, \"utf-8\")) as Record<string, unknown>;\n\t\tconst tg = raw.telegram as Record<string, unknown> | undefined;\n\t\tif (tg) {\n\t\t\tfileToken = typeof tg.token === \"string\" ? tg.token : undefined;\n\t\t\tfileChatId = typeof tg.chatId === \"string\" ? tg.chatId : undefined;\n\t\t}\n\t} catch {\n\t\t// Config file may not exist\n\t}\n\n\tconst token = process.env.VIBE_TELEGRAM_TOKEN ?? fileToken;\n\tif (!token) return undefined;\n\n\tconst chatId = process.env.VIBE_TELEGRAM_CHAT_ID ?? fileChatId;\n\treturn { token, chatId: chatId || undefined };\n}\n\n/** Save discovered chat ID to .vibe/config.json. */\nfunction saveDiscoveredChatId(vibeDir: string, chatId: string): void {\n\tconst configPath = join(vibeDir, \"config.json\");\n\tlet config: Record<string, unknown> = {};\n\ttry {\n\t\tconfig = JSON.parse(readFileSync(configPath, \"utf-8\")) as Record<string, unknown>;\n\t} catch {\n\t\t// File may not exist\n\t}\n\tif (!config.telegram || typeof config.telegram !== \"object\") {\n\t\tconfig.telegram = {};\n\t}\n\t(config.telegram as Record<string, unknown>).chatId = chatId;\n\twriteFileSync(configPath, `${JSON.stringify(config, null, \"\\t\")}\\n`, \"utf-8\");\n}\n\n// ─── Bridge Manager ──────────────────────────────────────────────────────────\n\n/** Registered command entry for Telegram dispatch. */\nexport interface TelegramCommand {\n\tname: string;\n\tdescription?: string;\n\thandler: (args: string, ctx: ExtensionCommandContext) => Promise<void>;\n\t/** Subcommands rendered as separate entries in Telegram bot menu (e.g., vibe_resume). */\n\tsubcommands?: Array<{ name: string; description: string }>;\n}\n\n/**\n * Create a bridge manager that supports dynamic mid-session activation.\n * Call `tryStart()` on session_start and agent_end. Call `stop()` on session_shutdown.\n *\n * @param commands Shared array of commands. Items can be added after creation;\n * the polling loop reads from this array by reference on every message.\n */\nexport function createTelegramBridgeManager(\n\tpi: ExtensionAPI,\n\tctx: ExtensionContext,\n\tvibeDir: string,\n\tcommands: TelegramCommand[] = [],\n): { tryStart: () => void; stop: () => void } {\n\tlet cleanup: (() => void) | undefined;\n\tlet started = false;\n\n\tfunction tryStart(): void {\n\t\tif (started) return;\n\t\tconst config = resolveConfig(vibeDir);\n\t\tif (!config) return;\n\n\t\tcleanup = startBridge(pi, ctx, config, vibeDir, commands);\n\t\tstarted = true;\n\t}\n\n\tfunction stop(): void {\n\t\tcleanup?.();\n\t\tcleanup = undefined;\n\t\tstarted = false;\n\t}\n\n\treturn { tryStart, stop };\n}\n\n// ─── Bridge core ─────────────────────────────────────────────────────────────\n\nfunction startBridge(\n\tpi: ExtensionAPI,\n\tctx: ExtensionContext,\n\tconfig: TelegramBridgeConfig,\n\tvibeDir: string,\n\tcommands: TelegramCommand[],\n): () => void {\n\tconst tg = new TelegramAPI(config.token, config.chatId);\n\tconst renderer = new ResponseRenderer(tg);\n\tconst abortController = new AbortController();\n\tconst unsubs: Array<() => void> = [];\n\n\t// Pipeline/gate state tracking — prevents Telegram input during vibe workflows\n\t// and preserves renderer state between pipeline steps.\n\tlet pipelineActive = false;\n\tlet gateWaiting = false;\n\n\t// 1. Agent event mirroring\n\tpi.on(\"message_update\", (event) => {\n\t\tif (event.assistantMessageEvent.type === \"text_delta\") {\n\t\t\trenderer.appendText(event.assistantMessageEvent.delta);\n\t\t}\n\t});\n\n\tpi.on(\"tool_execution_start\", (event) => {\n\t\tconst label = formatToolLabel(event.toolName, event.args as Record<string, unknown>);\n\t\trenderer.appendToolInfo(`\\u2192 ${label}`);\n\t});\n\n\tpi.on(\"agent_end\", () => {\n\t\t// During pipeline execution, don't finalize — the pipeline events manage renderer lifecycle.\n\t\t// Finalizing here would reset the step table between steps.\n\t\tif (!pipelineActive) {\n\t\t\trenderer.finalize().catch(() => {});\n\t\t}\n\t});\n\n\t// 1b. Vibe custom messages (agent responses, milestones, errors, etc.)\n\t// pi.sendMessage() → sendCustomMessage() only fires _emit() (TUI listeners),\n\t// not _emitExtensionEvent(), so pi.on(\"message_end\") never fires.\n\t// Instead, sendVibeMessage() emits vibe:message on the EventBus.\n\tunsubs.push(\n\t\tpi.events.on(\"vibe:message\", (raw: unknown) => {\n\t\t\tconst event = raw as {\n\t\t\t\tcontent: string;\n\t\t\t\tcategory: string;\n\t\t\t\tfullText?: string;\n\t\t\t};\n\n\t\t\t// Skip categories already handled by pipeline/gate event handlers\n\t\t\tif (event.category === \"gate\") return;\n\t\t\tif (!event.content) return;\n\n\t\t\tconst icons: Record<string, string> = {\n\t\t\t\tmilestone: \"\\u2728\",\n\t\t\t\terror: \"\\u274C\",\n\t\t\t\twarning: \"\\u26A0\\uFE0F\",\n\t\t\t\tcommand: \"\\u25B6\",\n\t\t\t\tevent: \"\\u{1F4AC}\",\n\t\t\t\t\"agent-response\": \"\\u{1F916}\",\n\t\t\t\t\"agent-text\": \"\\u270F\\uFE0F\",\n\t\t\t};\n\t\t\tconst icon = icons[event.category] ?? \"\\u{1F4AC}\";\n\n\t\t\t// For agent-text, show the first line of fullText instead of the generic\n\t\t\t// \"[role] ✎ (N lines)\" summary. This gives meaningful context (e.g., file\n\t\t\t// being worked on) instead of identical-looking messages.\n\t\t\tlet displayText = event.content;\n\t\t\tif (event.category === \"agent-text\" && event.fullText) {\n\t\t\t\tconst firstLine = event.fullText.split(\"\\n\")[0].trim();\n\t\t\t\tif (firstLine) {\n\t\t\t\t\tdisplayText = firstLine;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Send as plain text to avoid Markdown parse failures from LLM output\n\t\t\ttg.sendMessage(`${icon} ${displayText}`, { parse_mode: null }).catch(() => {});\n\t\t}),\n\t);\n\n\t// 2. Vibe event mirroring\n\tunsubs.push(\n\t\tpi.events.on(\"vibe:pipeline\", (raw: unknown) => {\n\t\t\tconst event = raw as VibePipelineEvent;\n\t\t\tif (event.type === \"feature_start\") {\n\t\t\t\tpipelineActive = true;\n\t\t\t} else if (\n\t\t\t\tevent.type === \"pipeline_complete\" ||\n\t\t\t\tevent.type === \"feature_complete\" ||\n\t\t\t\tevent.type === \"orchestration_complete\" ||\n\t\t\t\tevent.type === \"feature_aborted\"\n\t\t\t) {\n\t\t\t\tpipelineActive = false;\n\t\t\t\tgateWaiting = false;\n\t\t\t}\n\t\t\thandlePipelineEvent(event, renderer);\n\t\t}),\n\t\tpi.events.on(\"vibe:activity\", (raw: unknown) => {\n\t\t\tconst event = raw as VibeActivityEvent;\n\t\t\trenderer.addActivity(event.text);\n\t\t}),\n\t\tpi.events.on(\"vibe:gate\", (raw: unknown) => {\n\t\t\tconst event = raw as VibeGateEvent;\n\t\t\tgateWaiting = true;\n\t\t\tconst keyboard = {\n\t\t\t\tinline_keyboard: [\n\t\t\t\t\t[\n\t\t\t\t\t\t{ text: \"\\u2705 Approve\", callback_data: \"Approve\" },\n\t\t\t\t\t\t{ text: \"\\u274C Reject\", callback_data: \"Reject\" },\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t{ text: \"\\u23ED Skip\", callback_data: \"Skip\" },\n\t\t\t\t\t\t{ text: \"\\u26D4 Abort\", callback_data: \"Abort\" },\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t};\n\t\t\ttg.sendMessage(`\\u23F3 Gate: ${event.summary}`, { reply_markup: keyboard }).catch(() => {});\n\t\t}),\n\t);\n\n\t// Reset gate flag when pipeline resumes after gate approval\n\tunsubs.push(\n\t\tpi.events.on(\"vibe:pipeline\", (raw: unknown) => {\n\t\t\tconst event = raw as VibePipelineEvent;\n\t\t\tif (event.type === \"step_start\") {\n\t\t\t\tgateWaiting = false;\n\t\t\t}\n\t\t}),\n\t);\n\n\t// 3. Polling loop (fire-and-forget)\n\tstartPolling(tg, pi, ctx, abortController.signal, vibeDir, {\n\t\tisVibeBusy: () => pipelineActive || gateWaiting,\n\t\tisGateWaiting: () => gateWaiting,\n\t\tclearGateWaiting: () => {\n\t\t\tgateWaiting = false;\n\t\t},\n\t\tcommands,\n\t});\n\n\t// 4. Register bot menu commands (including subcommands as separate entries)\n\tconst menuCommands: Array<{ command: string; description: string }> = [\n\t\t{ command: \"stop\", description: \"Stop current execution\" },\n\t];\n\tfor (const cmd of commands) {\n\t\tmenuCommands.push({ command: cmd.name, description: cmd.description ?? `/${cmd.name}` });\n\t\tif (cmd.subcommands) {\n\t\t\tfor (const sub of cmd.subcommands) {\n\t\t\t\t// Telegram command names: lowercase, digits, underscores only\n\t\t\t\tmenuCommands.push({ command: `${cmd.name}_${sub.name}`, description: sub.description });\n\t\t\t}\n\t\t}\n\t}\n\ttg.setMyCommands(menuCommands).catch(() => {});\n\n\t// 5. Startup\n\tif (tg.hasChatId()) {\n\t\tconst model = ctx.model;\n\t\tconst startupMsg = model\n\t\t\t? `Telegram bridge active. Model: ${model.provider}/${model.id}`\n\t\t\t: \"Telegram bridge active. No model.\";\n\t\ttg.sendMessage(startupMsg).catch(() => {});\n\t} else {\n\t\tctx.ui.notify(\"[Telegram] Waiting for first message from your bot\", \"info\");\n\t}\n\n\t// Return cleanup function\n\treturn () => {\n\t\tabortController.abort();\n\t\tfor (const unsub of unsubs) unsub();\n\t};\n}\n\n// ─── Polling ─────────────────────────────────────────────────────────────────\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\ninterface PollingState {\n\tisVibeBusy: () => boolean;\n\tisGateWaiting: () => boolean;\n\tclearGateWaiting: () => void;\n\tcommands: TelegramCommand[];\n}\n\nasync function startPolling(\n\ttg: TelegramAPI,\n\tpi: ExtensionAPI,\n\tctx: ExtensionContext,\n\tsignal: AbortSignal,\n\tvibeDir: string,\n\tstate: PollingState,\n): Promise<void> {\n\twhile (!signal.aborted) {\n\t\ttry {\n\t\t\tconst updates = await tg.getUpdates();\n\t\t\tfor (const update of updates) {\n\t\t\t\tif (signal.aborted) break;\n\n\t\t\t\t// Handle callback queries (gate button clicks)\n\t\t\t\tif (update.callback_query) {\n\t\t\t\t\tconst data = update.callback_query.data ?? \"\";\n\t\t\t\t\ttg.answerCallbackQuery(update.callback_query.id).catch(() => {});\n\t\t\t\t\tif (state.isGateWaiting()) {\n\t\t\t\t\t\tpi.events.emit(\"vibe:gate_response\", { choice: data });\n\t\t\t\t\t\tstate.clearGateWaiting();\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Auto-discover chat ID from first incoming message\n\t\t\t\tif (!tg.hasChatId() && update.message?.chat.id) {\n\t\t\t\t\tconst discoveredId = String(update.message.chat.id);\n\t\t\t\t\ttg.setChatId(discoveredId);\n\t\t\t\t\tsaveDiscoveredChatId(vibeDir, discoveredId);\n\t\t\t\t\tconst model = ctx.model;\n\t\t\t\t\tconst msg = model\n\t\t\t\t\t\t? `Telegram bridge connected. Model: ${model.provider}/${model.id}`\n\t\t\t\t\t\t: \"Telegram bridge connected.\";\n\t\t\t\t\tawait tg.sendMessage(msg).catch(() => {});\n\t\t\t\t\tctx.ui.notify(\"[Telegram] Connected\", \"info\");\n\t\t\t\t\tcontinue; // Skip processing discovery message as a command\n\t\t\t\t}\n\n\t\t\t\tconst text = update.message?.text;\n\t\t\t\tif (!text) continue;\n\n\t\t\t\tif (text === \"/stop\") {\n\t\t\t\t\tctx.abort();\n\t\t\t\t\tawait tg.sendMessage(\"\\u23F8 Stopped.\").catch(() => {});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Block input during pipeline execution or gate waiting\n\t\t\t\tif (state.isVibeBusy()) {\n\t\t\t\t\tawait tg.sendMessage(\"\\u23F3 Pipeline running. Wait or /stop.\").catch(() => {});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (ctx.isIdle()) {\n\t\t\t\t\t// Try to dispatch as extension command first.\n\t\t\t\t\t// Fire-and-forget: command handlers may block on gates/UI,\n\t\t\t\t\t// and the polling loop must keep running to receive callback queries.\n\t\t\t\t\tif (text.startsWith(\"/\") && tryDispatchCommandSync(text, state.commands, ctx)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tpi.sendUserMessage(text);\n\t\t\t\t} else {\n\t\t\t\t\tawait tg.sendMessage(\"\\u23F3 Busy. Wait or /stop.\").catch(() => {});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\tif (signal.aborted) break;\n\t\t\tawait sleep(5000);\n\t\t}\n\t}\n}\n\n// ─── Command dispatch ────────────────────────────────────────────────────────\n\n/**\n * Try to dispatch a slash command to a registered handler.\n * Handles both direct commands (/vibe resume) and Telegram-style\n * subcommands (/vibe_resume) by mapping underscores back to spaces.\n *\n * Returns true if the command was matched. The handler runs as\n * fire-and-forget so the polling loop is never blocked (handlers\n * may await gates/UI dialogs indefinitely).\n */\nfunction tryDispatchCommandSync(text: string, commands: TelegramCommand[], ctx: ExtensionContext): boolean {\n\t// Strip leading / and optional @BotName suffix (Telegram adds this in groups)\n\tconst raw = text.slice(1).split(\"@\")[0];\n\tconst spaceIndex = raw.indexOf(\" \");\n\tconst fullName = spaceIndex === -1 ? raw : raw.slice(0, spaceIndex);\n\tconst trailingArgs = spaceIndex === -1 ? \"\" : raw.slice(spaceIndex + 1);\n\n\t// 1. Direct match: /vibe resume → command=\"vibe\", args=\"resume\"\n\tconst directMatch = commands.find((c) => c.name === fullName);\n\tif (directMatch) {\n\t\tdirectMatch.handler(trailingArgs, ctx as unknown as ExtensionCommandContext).catch(() => {});\n\t\treturn true;\n\t}\n\n\t// 2. Subcommand match: /vibe_resume → command=\"vibe\", args=\"resume\"\n\tfor (const cmd of commands) {\n\t\tif (!cmd.subcommands) continue;\n\t\tconst prefix = `${cmd.name}_`;\n\t\tif (fullName.startsWith(prefix)) {\n\t\t\tconst subName = fullName.slice(prefix.length);\n\t\t\tif (cmd.subcommands.some((s) => s.name === subName)) {\n\t\t\t\tconst combinedArgs = trailingArgs ? `${subName} ${trailingArgs}` : subName;\n\t\t\t\tcmd.handler(combinedArgs, ctx as unknown as ExtensionCommandContext).catch(() => {});\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn false;\n}\n\n// ─── Pipeline event handler ─────────────────────────────────────────────────\n\nfunction handlePipelineEvent(event: VibePipelineEvent, renderer: ResponseRenderer): void {\n\tswitch (event.type) {\n\t\tcase \"feature_start\": {\n\t\t\trenderer.setPipelineMode(true);\n\t\t\tconst title = event.data?.title as string | undefined;\n\t\t\trenderer.setPhase(title ? `${event.featureId}: ${title}` : event.featureId);\n\n\t\t\tconst totalSteps = event.data?.totalSteps as number | undefined;\n\t\t\tif (totalSteps) {\n\t\t\t\tconst steps: StepDisplay[] = [];\n\t\t\t\tfor (let i = 0; i < totalSteps; i++) {\n\t\t\t\t\tsteps.push({ label: `Step ${i + 1}`, status: \"pending\" });\n\t\t\t\t}\n\t\t\t\trenderer.setSteps(steps);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"step_start\": {\n\t\t\tconst stepIndex = event.data?.stepIndex as number | undefined;\n\t\t\tif (stepIndex !== undefined) {\n\t\t\t\trenderer.updateStep(stepIndex, \"running\");\n\t\t\t}\n\t\t\tif (event.step) {\n\t\t\t\trenderer.updateStepLabel(event.data?.stepIndex as number | undefined, event.step.agent);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"step_complete\": {\n\t\t\tconst stepIndex = event.data?.stepIndex as number | undefined;\n\t\t\tif (stepIndex !== undefined) {\n\t\t\t\tconst elapsed = event.data?.elapsed as number | undefined;\n\t\t\t\tconst usage = event.data?.usage as { cost?: number } | undefined;\n\t\t\t\trenderer.updateStep(stepIndex, \"done\", elapsed !== undefined ? elapsed / 1000 : undefined, usage?.cost);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"step_failed\": {\n\t\t\tconst stepIndex = event.data?.stepIndex as number | undefined;\n\t\t\tif (stepIndex !== undefined) {\n\t\t\t\trenderer.updateStep(stepIndex, \"failed\");\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tcase \"pipeline_complete\":\n\t\tcase \"feature_complete\":\n\t\tcase \"orchestration_complete\":\n\t\tcase \"feature_aborted\": {\n\t\t\trenderer.setPipelineMode(false);\n\t\t\trenderer.finalize().catch(() => {});\n\t\t\tbreak;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram message formatting utilities.
|
|
3
|
+
*/
|
|
4
|
+
/** Escape text for Telegram MarkdownV2 parse mode. */
|
|
5
|
+
export declare function escapeMarkdownV2(text: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Split a long message into chunks that fit Telegram's 4096-char limit.
|
|
8
|
+
* Splits at newline boundaries when possible.
|
|
9
|
+
*/
|
|
10
|
+
export declare function splitMessage(text: string, maxLen?: number): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Format a tool call into a human-readable label.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatToolLabel(toolName: string, args: Record<string, unknown>): string;
|
|
15
|
+
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../src/telegram/formatter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAKD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAqB,GAAG,MAAM,EAAE,CAwBhF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CA6CvF","sourcesContent":["/**\n * Telegram message formatting utilities.\n */\n\n/** Characters that must be escaped in MarkdownV2 parse mode. */\nconst MARKDOWN_V2_SPECIAL = /([_*[\\]()~`>#+\\-=|{}.!\\\\])/g;\n\n/** Escape text for Telegram MarkdownV2 parse mode. */\nexport function escapeMarkdownV2(text: string): string {\n\treturn text.replace(MARKDOWN_V2_SPECIAL, \"\\\\$1\");\n}\n\n/** Maximum Telegram message length. */\nconst MAX_MESSAGE_LENGTH = 4096;\n\n/**\n * Split a long message into chunks that fit Telegram's 4096-char limit.\n * Splits at newline boundaries when possible.\n */\nexport function splitMessage(text: string, maxLen = MAX_MESSAGE_LENGTH): string[] {\n\tif (text.length <= maxLen) return [text];\n\n\tconst parts: string[] = [];\n\tlet remaining = text;\n\n\twhile (remaining.length > 0) {\n\t\tif (remaining.length <= maxLen) {\n\t\t\tparts.push(remaining);\n\t\t\tbreak;\n\t\t}\n\n\t\t// Find last newline within limit\n\t\tlet splitAt = remaining.lastIndexOf(\"\\n\", maxLen);\n\t\tif (splitAt <= 0) {\n\t\t\t// No newline found, force split\n\t\t\tsplitAt = maxLen;\n\t\t}\n\n\t\tparts.push(remaining.slice(0, splitAt));\n\t\tremaining = remaining.slice(splitAt).replace(/^\\n/, \"\");\n\t}\n\n\treturn parts;\n}\n\n/**\n * Format a tool call into a human-readable label.\n */\nexport function formatToolLabel(toolName: string, args: Record<string, unknown>): string {\n\tswitch (toolName) {\n\t\tcase \"read\": {\n\t\t\tconst path = args.path as string | undefined;\n\t\t\tif (path) {\n\t\t\t\tconst offset = args.offset as number | undefined;\n\t\t\t\tconst limit = args.limit as number | undefined;\n\t\t\t\tif (offset !== undefined && limit !== undefined) {\n\t\t\t\t\treturn `Read ${path}:${offset}-${offset + limit}`;\n\t\t\t\t}\n\t\t\t\treturn `Read ${path}`;\n\t\t\t}\n\t\t\treturn \"Read\";\n\t\t}\n\t\tcase \"bash\": {\n\t\t\tconst command = args.command as string | undefined;\n\t\t\tif (command) {\n\t\t\t\tconst short = command.length > 60 ? `${command.slice(0, 57)}...` : command;\n\t\t\t\treturn `Bash ${short}`;\n\t\t\t}\n\t\t\treturn \"Bash\";\n\t\t}\n\t\tcase \"edit\": {\n\t\t\tconst path = args.path as string | undefined;\n\t\t\treturn path ? `Edit ${path}` : \"Edit\";\n\t\t}\n\t\tcase \"write\": {\n\t\t\tconst path = args.path as string | undefined;\n\t\t\treturn path ? `Write ${path}` : \"Write\";\n\t\t}\n\t\tcase \"grep\": {\n\t\t\tconst pattern = args.pattern as string | undefined;\n\t\t\treturn pattern ? `Grep ${pattern}` : \"Grep\";\n\t\t}\n\t\tcase \"find\": {\n\t\t\tconst glob = args.glob as string | undefined;\n\t\t\treturn glob ? `Find ${glob}` : \"Find\";\n\t\t}\n\t\tcase \"ls\": {\n\t\t\tconst path = args.path as string | undefined;\n\t\t\treturn path ? `Ls ${path}` : \"Ls\";\n\t\t}\n\t\tdefault:\n\t\t\treturn toolName;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram message formatting utilities.
|
|
3
|
+
*/
|
|
4
|
+
/** Characters that must be escaped in MarkdownV2 parse mode. */
|
|
5
|
+
const MARKDOWN_V2_SPECIAL = /([_*[\]()~`>#+\-=|{}.!\\])/g;
|
|
6
|
+
/** Escape text for Telegram MarkdownV2 parse mode. */
|
|
7
|
+
export function escapeMarkdownV2(text) {
|
|
8
|
+
return text.replace(MARKDOWN_V2_SPECIAL, "\\$1");
|
|
9
|
+
}
|
|
10
|
+
/** Maximum Telegram message length. */
|
|
11
|
+
const MAX_MESSAGE_LENGTH = 4096;
|
|
12
|
+
/**
|
|
13
|
+
* Split a long message into chunks that fit Telegram's 4096-char limit.
|
|
14
|
+
* Splits at newline boundaries when possible.
|
|
15
|
+
*/
|
|
16
|
+
export function splitMessage(text, maxLen = MAX_MESSAGE_LENGTH) {
|
|
17
|
+
if (text.length <= maxLen)
|
|
18
|
+
return [text];
|
|
19
|
+
const parts = [];
|
|
20
|
+
let remaining = text;
|
|
21
|
+
while (remaining.length > 0) {
|
|
22
|
+
if (remaining.length <= maxLen) {
|
|
23
|
+
parts.push(remaining);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
// Find last newline within limit
|
|
27
|
+
let splitAt = remaining.lastIndexOf("\n", maxLen);
|
|
28
|
+
if (splitAt <= 0) {
|
|
29
|
+
// No newline found, force split
|
|
30
|
+
splitAt = maxLen;
|
|
31
|
+
}
|
|
32
|
+
parts.push(remaining.slice(0, splitAt));
|
|
33
|
+
remaining = remaining.slice(splitAt).replace(/^\n/, "");
|
|
34
|
+
}
|
|
35
|
+
return parts;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Format a tool call into a human-readable label.
|
|
39
|
+
*/
|
|
40
|
+
export function formatToolLabel(toolName, args) {
|
|
41
|
+
switch (toolName) {
|
|
42
|
+
case "read": {
|
|
43
|
+
const path = args.path;
|
|
44
|
+
if (path) {
|
|
45
|
+
const offset = args.offset;
|
|
46
|
+
const limit = args.limit;
|
|
47
|
+
if (offset !== undefined && limit !== undefined) {
|
|
48
|
+
return `Read ${path}:${offset}-${offset + limit}`;
|
|
49
|
+
}
|
|
50
|
+
return `Read ${path}`;
|
|
51
|
+
}
|
|
52
|
+
return "Read";
|
|
53
|
+
}
|
|
54
|
+
case "bash": {
|
|
55
|
+
const command = args.command;
|
|
56
|
+
if (command) {
|
|
57
|
+
const short = command.length > 60 ? `${command.slice(0, 57)}...` : command;
|
|
58
|
+
return `Bash ${short}`;
|
|
59
|
+
}
|
|
60
|
+
return "Bash";
|
|
61
|
+
}
|
|
62
|
+
case "edit": {
|
|
63
|
+
const path = args.path;
|
|
64
|
+
return path ? `Edit ${path}` : "Edit";
|
|
65
|
+
}
|
|
66
|
+
case "write": {
|
|
67
|
+
const path = args.path;
|
|
68
|
+
return path ? `Write ${path}` : "Write";
|
|
69
|
+
}
|
|
70
|
+
case "grep": {
|
|
71
|
+
const pattern = args.pattern;
|
|
72
|
+
return pattern ? `Grep ${pattern}` : "Grep";
|
|
73
|
+
}
|
|
74
|
+
case "find": {
|
|
75
|
+
const glob = args.glob;
|
|
76
|
+
return glob ? `Find ${glob}` : "Find";
|
|
77
|
+
}
|
|
78
|
+
case "ls": {
|
|
79
|
+
const path = args.path;
|
|
80
|
+
return path ? `Ls ${path}` : "Ls";
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
return toolName;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.js","sourceRoot":"","sources":["../../src/telegram/formatter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Telegram message formatting utilities.\n */\n\n/** Characters that must be escaped in MarkdownV2 parse mode. */\nconst MARKDOWN_V2_SPECIAL = /([_*[\\]()~`>#+\\-=|{}.!\\\\])/g;\n\n/** Escape text for Telegram MarkdownV2 parse mode. */\nexport function escapeMarkdownV2(text: string): string {\n\treturn text.replace(MARKDOWN_V2_SPECIAL, \"\\\\$1\");\n}\n\n/** Maximum Telegram message length. */\nconst MAX_MESSAGE_LENGTH = 4096;\n\n/**\n * Split a long message into chunks that fit Telegram's 4096-char limit.\n * Splits at newline boundaries when possible.\n */\nexport function splitMessage(text: string, maxLen = MAX_MESSAGE_LENGTH): string[] {\n\tif (text.length <= maxLen) return [text];\n\n\tconst parts: string[] = [];\n\tlet remaining = text;\n\n\twhile (remaining.length > 0) {\n\t\tif (remaining.length <= maxLen) {\n\t\t\tparts.push(remaining);\n\t\t\tbreak;\n\t\t}\n\n\t\t// Find last newline within limit\n\t\tlet splitAt = remaining.lastIndexOf(\"\\n\", maxLen);\n\t\tif (splitAt <= 0) {\n\t\t\t// No newline found, force split\n\t\t\tsplitAt = maxLen;\n\t\t}\n\n\t\tparts.push(remaining.slice(0, splitAt));\n\t\tremaining = remaining.slice(splitAt).replace(/^\\n/, \"\");\n\t}\n\n\treturn parts;\n}\n\n/**\n * Format a tool call into a human-readable label.\n */\nexport function formatToolLabel(toolName: string, args: Record<string, unknown>): string {\n\tswitch (toolName) {\n\t\tcase \"read\": {\n\t\t\tconst path = args.path as string | undefined;\n\t\t\tif (path) {\n\t\t\t\tconst offset = args.offset as number | undefined;\n\t\t\t\tconst limit = args.limit as number | undefined;\n\t\t\t\tif (offset !== undefined && limit !== undefined) {\n\t\t\t\t\treturn `Read ${path}:${offset}-${offset + limit}`;\n\t\t\t\t}\n\t\t\t\treturn `Read ${path}`;\n\t\t\t}\n\t\t\treturn \"Read\";\n\t\t}\n\t\tcase \"bash\": {\n\t\t\tconst command = args.command as string | undefined;\n\t\t\tif (command) {\n\t\t\t\tconst short = command.length > 60 ? `${command.slice(0, 57)}...` : command;\n\t\t\t\treturn `Bash ${short}`;\n\t\t\t}\n\t\t\treturn \"Bash\";\n\t\t}\n\t\tcase \"edit\": {\n\t\t\tconst path = args.path as string | undefined;\n\t\t\treturn path ? `Edit ${path}` : \"Edit\";\n\t\t}\n\t\tcase \"write\": {\n\t\t\tconst path = args.path as string | undefined;\n\t\t\treturn path ? `Write ${path}` : \"Write\";\n\t\t}\n\t\tcase \"grep\": {\n\t\t\tconst pattern = args.pattern as string | undefined;\n\t\t\treturn pattern ? `Grep ${pattern}` : \"Grep\";\n\t\t}\n\t\tcase \"find\": {\n\t\t\tconst glob = args.glob as string | undefined;\n\t\t\treturn glob ? `Find ${glob}` : \"Find\";\n\t\t}\n\t\tcase \"ls\": {\n\t\t\tconst path = args.path as string | undefined;\n\t\t\treturn path ? `Ls ${path}` : \"Ls\";\n\t\t}\n\t\tdefault:\n\t\t\treturn toolName;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response renderer with debounced Telegram message updates.
|
|
3
|
+
* Handles both normal conversation streaming and pipeline progress display.
|
|
4
|
+
*/
|
|
5
|
+
import type { ITelegramAPI } from "./telegram-api.js";
|
|
6
|
+
/** Pipeline step display state. */
|
|
7
|
+
export interface StepDisplay {
|
|
8
|
+
label: string;
|
|
9
|
+
status: "pending" | "running" | "done" | "failed" | "skipped";
|
|
10
|
+
duration?: number;
|
|
11
|
+
cost?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class ResponseRenderer {
|
|
14
|
+
private messageId;
|
|
15
|
+
private buffer;
|
|
16
|
+
private timer;
|
|
17
|
+
private readonly tg;
|
|
18
|
+
private pipelineMode;
|
|
19
|
+
private steps;
|
|
20
|
+
private activities;
|
|
21
|
+
private currentPhase;
|
|
22
|
+
constructor(tg: ITelegramAPI);
|
|
23
|
+
/** Append a text delta to the response buffer. */
|
|
24
|
+
appendText(delta: string): void;
|
|
25
|
+
/** Append a tool info line (italic). */
|
|
26
|
+
appendToolInfo(text: string): void;
|
|
27
|
+
/** Enable or disable pipeline rendering mode. */
|
|
28
|
+
setPipelineMode(enabled: boolean): void;
|
|
29
|
+
/** Set the pipeline phase label. */
|
|
30
|
+
setPhase(phase: string): void;
|
|
31
|
+
/** Set the full step list. */
|
|
32
|
+
setSteps(steps: StepDisplay[]): void;
|
|
33
|
+
/** Update a step's label. */
|
|
34
|
+
updateStepLabel(index: number | undefined, label: string): void;
|
|
35
|
+
/** Update a single step's status. */
|
|
36
|
+
updateStep(index: number, status: StepDisplay["status"], duration?: number, cost?: number): void;
|
|
37
|
+
/** Add an activity line (capped at MAX_ACTIVITIES). */
|
|
38
|
+
addActivity(text: string): void;
|
|
39
|
+
/** Flush any pending update and reset state for the next message. */
|
|
40
|
+
finalize(): Promise<void>;
|
|
41
|
+
private scheduleUpdate;
|
|
42
|
+
private flush;
|
|
43
|
+
private renderPipeline;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/telegram/renderer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,mCAAmC;AACnC,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAaD,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;IAGlC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,YAAY,CAAM;IAE1B,YAAY,EAAE,EAAE,YAAY,EAE3B;IAID,kDAAkD;IAClD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG9B;IAED,wCAAwC;IACxC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAGjC;IAID,iDAAiD;IACjD,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAQtC;IAED,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAG5B;IAED,8BAA8B;IAC9B,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,IAAI,CAGnC;IAED,6BAA6B;IAC7B,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAK9D;IAED,qCAAqC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAO/F;IAED,uDAAuD;IACvD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAM9B;IAID,qEAAqE;IAC/D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAS9B;IAID,OAAO,CAAC,cAAc;YAQR,KAAK;IA2BnB,OAAO,CAAC,cAAc;CAmBtB","sourcesContent":["/**\n * Response renderer with debounced Telegram message updates.\n * Handles both normal conversation streaming and pipeline progress display.\n */\n\nimport { splitMessage } from \"./formatter.js\";\nimport type { ITelegramAPI } from \"./telegram-api.js\";\n\n/** Pipeline step display state. */\nexport interface StepDisplay {\n\tlabel: string;\n\tstatus: \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\";\n\tduration?: number;\n\tcost?: number;\n}\n\nconst STATUS_ICON: Record<string, string> = {\n\tdone: \"\\u2705\",\n\trunning: \"\\u23F3\",\n\tpending: \"\\u2B1C\",\n\tfailed: \"\\u274C\",\n\tskipped: \"\\u23ED\",\n};\n\nconst DEBOUNCE_MS = 800;\nconst MAX_ACTIVITIES = 5;\n\nexport class ResponseRenderer {\n\tprivate messageId: number | null = null;\n\tprivate buffer = \"\";\n\tprivate timer: ReturnType<typeof setTimeout> | null = null;\n\tprivate readonly tg: ITelegramAPI;\n\n\t// Pipeline mode state\n\tprivate pipelineMode = false;\n\tprivate steps: StepDisplay[] = [];\n\tprivate activities: string[] = [];\n\tprivate currentPhase = \"\";\n\n\tconstructor(tg: ITelegramAPI) {\n\t\tthis.tg = tg;\n\t}\n\n\t// ─── Normal mode ─────────────────────────────────────────────────────\n\n\t/** Append a text delta to the response buffer. */\n\tappendText(delta: string): void {\n\t\tthis.buffer += delta;\n\t\tthis.scheduleUpdate();\n\t}\n\n\t/** Append a tool info line (italic). */\n\tappendToolInfo(text: string): void {\n\t\tthis.buffer += `\\n_${text}_`;\n\t\tthis.scheduleUpdate();\n\t}\n\n\t// ─── Pipeline mode ──────────────────────────────────────────────────\n\n\t/** Enable or disable pipeline rendering mode. */\n\tsetPipelineMode(enabled: boolean): void {\n\t\tthis.pipelineMode = enabled;\n\t\tif (enabled) {\n\t\t\tthis.steps = [];\n\t\t\tthis.activities = [];\n\t\t\tthis.buffer = \"\";\n\t\t\tthis.messageId = null;\n\t\t}\n\t}\n\n\t/** Set the pipeline phase label. */\n\tsetPhase(phase: string): void {\n\t\tthis.currentPhase = phase;\n\t\tthis.scheduleUpdate();\n\t}\n\n\t/** Set the full step list. */\n\tsetSteps(steps: StepDisplay[]): void {\n\t\tthis.steps = steps;\n\t\tthis.scheduleUpdate();\n\t}\n\n\t/** Update a step's label. */\n\tupdateStepLabel(index: number | undefined, label: string): void {\n\t\tif (index !== undefined && this.steps[index]) {\n\t\t\tthis.steps[index].label = label;\n\t\t\tthis.scheduleUpdate();\n\t\t}\n\t}\n\n\t/** Update a single step's status. */\n\tupdateStep(index: number, status: StepDisplay[\"status\"], duration?: number, cost?: number): void {\n\t\tif (this.steps[index]) {\n\t\t\tthis.steps[index].status = status;\n\t\t\tif (duration !== undefined) this.steps[index].duration = duration;\n\t\t\tif (cost !== undefined) this.steps[index].cost = cost;\n\t\t}\n\t\tthis.scheduleUpdate();\n\t}\n\n\t/** Add an activity line (capped at MAX_ACTIVITIES). */\n\taddActivity(text: string): void {\n\t\tthis.activities.push(text);\n\t\tif (this.activities.length > MAX_ACTIVITIES) {\n\t\t\tthis.activities.shift();\n\t\t}\n\t\tthis.scheduleUpdate();\n\t}\n\n\t// ─── Lifecycle ──────────────────────────────────────────────────────\n\n\t/** Flush any pending update and reset state for the next message. */\n\tasync finalize(): Promise<void> {\n\t\tif (this.timer) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = null;\n\t\t}\n\t\tawait this.flush();\n\t\tthis.messageId = null;\n\t\tthis.buffer = \"\";\n\t\tthis.activities = [];\n\t}\n\n\t// ─── Internal ───────────────────────────────────────────────────────\n\n\tprivate scheduleUpdate(): void {\n\t\tif (this.timer) return;\n\t\tthis.timer = setTimeout(async () => {\n\t\t\tthis.timer = null;\n\t\t\tawait this.flush();\n\t\t}, DEBOUNCE_MS);\n\t}\n\n\tprivate async flush(): Promise<void> {\n\t\tconst text = this.pipelineMode ? this.renderPipeline() : this.buffer;\n\t\tif (!text.trim()) return;\n\n\t\t// Telegram 4096-char limit\n\t\tconst parts = splitMessage(text);\n\t\tconst mainText = parts[0];\n\n\t\t// Pipeline mode uses plain text to avoid Markdown parsing failures\n\t\t// (activity text from LLM output may contain unmatched *, _, ` etc.)\n\t\tconst extra = this.pipelineMode ? { parse_mode: null } : undefined;\n\n\t\ttry {\n\t\t\tif (this.messageId) {\n\t\t\t\tawait this.tg.editMessage(this.messageId, mainText, extra);\n\t\t\t} else {\n\t\t\t\tthis.messageId = await this.tg.sendMessage(mainText, extra);\n\t\t\t}\n\t\t\t// Send overflow parts as separate messages\n\t\t\tfor (let i = 1; i < parts.length; i++) {\n\t\t\t\tawait this.tg.sendMessage(parts[i], extra);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore send/edit failures (rate limit, network, etc.)\n\t\t}\n\t}\n\n\tprivate renderPipeline(): string {\n\t\tlet text = `\\uD83D\\uDD27 ${this.currentPhase}\\n\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\n`;\n\n\t\tfor (const step of this.steps) {\n\t\t\tconst icon = STATUS_ICON[step.status] ?? \"\\u2B1C\";\n\t\t\tconst time = step.duration !== undefined ? `${step.duration.toFixed(1)}s` : \"\";\n\t\t\tconst cost = step.cost !== undefined ? `$${step.cost.toFixed(2)}` : \"\";\n\t\t\ttext += `${icon} ${step.label.padEnd(14)} ${time.padStart(7)} ${cost.padStart(6)}\\n`;\n\t\t}\n\n\t\tif (this.activities.length > 0) {\n\t\t\ttext += \"\\n\";\n\t\t\tfor (const activity of this.activities) {\n\t\t\t\ttext += ` ${activity}\\n`;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response renderer with debounced Telegram message updates.
|
|
3
|
+
* Handles both normal conversation streaming and pipeline progress display.
|
|
4
|
+
*/
|
|
5
|
+
import { splitMessage } from "./formatter.js";
|
|
6
|
+
const STATUS_ICON = {
|
|
7
|
+
done: "\u2705",
|
|
8
|
+
running: "\u23F3",
|
|
9
|
+
pending: "\u2B1C",
|
|
10
|
+
failed: "\u274C",
|
|
11
|
+
skipped: "\u23ED",
|
|
12
|
+
};
|
|
13
|
+
const DEBOUNCE_MS = 800;
|
|
14
|
+
const MAX_ACTIVITIES = 5;
|
|
15
|
+
export class ResponseRenderer {
|
|
16
|
+
messageId = null;
|
|
17
|
+
buffer = "";
|
|
18
|
+
timer = null;
|
|
19
|
+
tg;
|
|
20
|
+
// Pipeline mode state
|
|
21
|
+
pipelineMode = false;
|
|
22
|
+
steps = [];
|
|
23
|
+
activities = [];
|
|
24
|
+
currentPhase = "";
|
|
25
|
+
constructor(tg) {
|
|
26
|
+
this.tg = tg;
|
|
27
|
+
}
|
|
28
|
+
// ─── Normal mode ─────────────────────────────────────────────────────
|
|
29
|
+
/** Append a text delta to the response buffer. */
|
|
30
|
+
appendText(delta) {
|
|
31
|
+
this.buffer += delta;
|
|
32
|
+
this.scheduleUpdate();
|
|
33
|
+
}
|
|
34
|
+
/** Append a tool info line (italic). */
|
|
35
|
+
appendToolInfo(text) {
|
|
36
|
+
this.buffer += `\n_${text}_`;
|
|
37
|
+
this.scheduleUpdate();
|
|
38
|
+
}
|
|
39
|
+
// ─── Pipeline mode ──────────────────────────────────────────────────
|
|
40
|
+
/** Enable or disable pipeline rendering mode. */
|
|
41
|
+
setPipelineMode(enabled) {
|
|
42
|
+
this.pipelineMode = enabled;
|
|
43
|
+
if (enabled) {
|
|
44
|
+
this.steps = [];
|
|
45
|
+
this.activities = [];
|
|
46
|
+
this.buffer = "";
|
|
47
|
+
this.messageId = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Set the pipeline phase label. */
|
|
51
|
+
setPhase(phase) {
|
|
52
|
+
this.currentPhase = phase;
|
|
53
|
+
this.scheduleUpdate();
|
|
54
|
+
}
|
|
55
|
+
/** Set the full step list. */
|
|
56
|
+
setSteps(steps) {
|
|
57
|
+
this.steps = steps;
|
|
58
|
+
this.scheduleUpdate();
|
|
59
|
+
}
|
|
60
|
+
/** Update a step's label. */
|
|
61
|
+
updateStepLabel(index, label) {
|
|
62
|
+
if (index !== undefined && this.steps[index]) {
|
|
63
|
+
this.steps[index].label = label;
|
|
64
|
+
this.scheduleUpdate();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Update a single step's status. */
|
|
68
|
+
updateStep(index, status, duration, cost) {
|
|
69
|
+
if (this.steps[index]) {
|
|
70
|
+
this.steps[index].status = status;
|
|
71
|
+
if (duration !== undefined)
|
|
72
|
+
this.steps[index].duration = duration;
|
|
73
|
+
if (cost !== undefined)
|
|
74
|
+
this.steps[index].cost = cost;
|
|
75
|
+
}
|
|
76
|
+
this.scheduleUpdate();
|
|
77
|
+
}
|
|
78
|
+
/** Add an activity line (capped at MAX_ACTIVITIES). */
|
|
79
|
+
addActivity(text) {
|
|
80
|
+
this.activities.push(text);
|
|
81
|
+
if (this.activities.length > MAX_ACTIVITIES) {
|
|
82
|
+
this.activities.shift();
|
|
83
|
+
}
|
|
84
|
+
this.scheduleUpdate();
|
|
85
|
+
}
|
|
86
|
+
// ─── Lifecycle ──────────────────────────────────────────────────────
|
|
87
|
+
/** Flush any pending update and reset state for the next message. */
|
|
88
|
+
async finalize() {
|
|
89
|
+
if (this.timer) {
|
|
90
|
+
clearTimeout(this.timer);
|
|
91
|
+
this.timer = null;
|
|
92
|
+
}
|
|
93
|
+
await this.flush();
|
|
94
|
+
this.messageId = null;
|
|
95
|
+
this.buffer = "";
|
|
96
|
+
this.activities = [];
|
|
97
|
+
}
|
|
98
|
+
// ─── Internal ───────────────────────────────────────────────────────
|
|
99
|
+
scheduleUpdate() {
|
|
100
|
+
if (this.timer)
|
|
101
|
+
return;
|
|
102
|
+
this.timer = setTimeout(async () => {
|
|
103
|
+
this.timer = null;
|
|
104
|
+
await this.flush();
|
|
105
|
+
}, DEBOUNCE_MS);
|
|
106
|
+
}
|
|
107
|
+
async flush() {
|
|
108
|
+
const text = this.pipelineMode ? this.renderPipeline() : this.buffer;
|
|
109
|
+
if (!text.trim())
|
|
110
|
+
return;
|
|
111
|
+
// Telegram 4096-char limit
|
|
112
|
+
const parts = splitMessage(text);
|
|
113
|
+
const mainText = parts[0];
|
|
114
|
+
// Pipeline mode uses plain text to avoid Markdown parsing failures
|
|
115
|
+
// (activity text from LLM output may contain unmatched *, _, ` etc.)
|
|
116
|
+
const extra = this.pipelineMode ? { parse_mode: null } : undefined;
|
|
117
|
+
try {
|
|
118
|
+
if (this.messageId) {
|
|
119
|
+
await this.tg.editMessage(this.messageId, mainText, extra);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
this.messageId = await this.tg.sendMessage(mainText, extra);
|
|
123
|
+
}
|
|
124
|
+
// Send overflow parts as separate messages
|
|
125
|
+
for (let i = 1; i < parts.length; i++) {
|
|
126
|
+
await this.tg.sendMessage(parts[i], extra);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Ignore send/edit failures (rate limit, network, etc.)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
renderPipeline() {
|
|
134
|
+
let text = `\uD83D\uDD27 ${this.currentPhase}\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n`;
|
|
135
|
+
for (const step of this.steps) {
|
|
136
|
+
const icon = STATUS_ICON[step.status] ?? "\u2B1C";
|
|
137
|
+
const time = step.duration !== undefined ? `${step.duration.toFixed(1)}s` : "";
|
|
138
|
+
const cost = step.cost !== undefined ? `$${step.cost.toFixed(2)}` : "";
|
|
139
|
+
text += `${icon} ${step.label.padEnd(14)} ${time.padStart(7)} ${cost.padStart(6)}\n`;
|
|
140
|
+
}
|
|
141
|
+
if (this.activities.length > 0) {
|
|
142
|
+
text += "\n";
|
|
143
|
+
for (const activity of this.activities) {
|
|
144
|
+
text += ` ${activity}\n`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return text;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/telegram/renderer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Response renderer with debounced Telegram message updates.\n * Handles both normal conversation streaming and pipeline progress display.\n */\n\nimport { splitMessage } from \"./formatter.js\";\nimport type { ITelegramAPI } from \"./telegram-api.js\";\n\n/** Pipeline step display state. */\nexport interface StepDisplay {\n\tlabel: string;\n\tstatus: \"pending\" | \"running\" | \"done\" | \"failed\" | \"skipped\";\n\tduration?: number;\n\tcost?: number;\n}\n\nconst STATUS_ICON: Record<string, string> = {\n\tdone: \"\\u2705\",\n\trunning: \"\\u23F3\",\n\tpending: \"\\u2B1C\",\n\tfailed: \"\\u274C\",\n\tskipped: \"\\u23ED\",\n};\n\nconst DEBOUNCE_MS = 800;\nconst MAX_ACTIVITIES = 5;\n\nexport class ResponseRenderer {\n\tprivate messageId: number | null = null;\n\tprivate buffer = \"\";\n\tprivate timer: ReturnType<typeof setTimeout> | null = null;\n\tprivate readonly tg: ITelegramAPI;\n\n\t// Pipeline mode state\n\tprivate pipelineMode = false;\n\tprivate steps: StepDisplay[] = [];\n\tprivate activities: string[] = [];\n\tprivate currentPhase = \"\";\n\n\tconstructor(tg: ITelegramAPI) {\n\t\tthis.tg = tg;\n\t}\n\n\t// ─── Normal mode ─────────────────────────────────────────────────────\n\n\t/** Append a text delta to the response buffer. */\n\tappendText(delta: string): void {\n\t\tthis.buffer += delta;\n\t\tthis.scheduleUpdate();\n\t}\n\n\t/** Append a tool info line (italic). */\n\tappendToolInfo(text: string): void {\n\t\tthis.buffer += `\\n_${text}_`;\n\t\tthis.scheduleUpdate();\n\t}\n\n\t// ─── Pipeline mode ──────────────────────────────────────────────────\n\n\t/** Enable or disable pipeline rendering mode. */\n\tsetPipelineMode(enabled: boolean): void {\n\t\tthis.pipelineMode = enabled;\n\t\tif (enabled) {\n\t\t\tthis.steps = [];\n\t\t\tthis.activities = [];\n\t\t\tthis.buffer = \"\";\n\t\t\tthis.messageId = null;\n\t\t}\n\t}\n\n\t/** Set the pipeline phase label. */\n\tsetPhase(phase: string): void {\n\t\tthis.currentPhase = phase;\n\t\tthis.scheduleUpdate();\n\t}\n\n\t/** Set the full step list. */\n\tsetSteps(steps: StepDisplay[]): void {\n\t\tthis.steps = steps;\n\t\tthis.scheduleUpdate();\n\t}\n\n\t/** Update a step's label. */\n\tupdateStepLabel(index: number | undefined, label: string): void {\n\t\tif (index !== undefined && this.steps[index]) {\n\t\t\tthis.steps[index].label = label;\n\t\t\tthis.scheduleUpdate();\n\t\t}\n\t}\n\n\t/** Update a single step's status. */\n\tupdateStep(index: number, status: StepDisplay[\"status\"], duration?: number, cost?: number): void {\n\t\tif (this.steps[index]) {\n\t\t\tthis.steps[index].status = status;\n\t\t\tif (duration !== undefined) this.steps[index].duration = duration;\n\t\t\tif (cost !== undefined) this.steps[index].cost = cost;\n\t\t}\n\t\tthis.scheduleUpdate();\n\t}\n\n\t/** Add an activity line (capped at MAX_ACTIVITIES). */\n\taddActivity(text: string): void {\n\t\tthis.activities.push(text);\n\t\tif (this.activities.length > MAX_ACTIVITIES) {\n\t\t\tthis.activities.shift();\n\t\t}\n\t\tthis.scheduleUpdate();\n\t}\n\n\t// ─── Lifecycle ──────────────────────────────────────────────────────\n\n\t/** Flush any pending update and reset state for the next message. */\n\tasync finalize(): Promise<void> {\n\t\tif (this.timer) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = null;\n\t\t}\n\t\tawait this.flush();\n\t\tthis.messageId = null;\n\t\tthis.buffer = \"\";\n\t\tthis.activities = [];\n\t}\n\n\t// ─── Internal ───────────────────────────────────────────────────────\n\n\tprivate scheduleUpdate(): void {\n\t\tif (this.timer) return;\n\t\tthis.timer = setTimeout(async () => {\n\t\t\tthis.timer = null;\n\t\t\tawait this.flush();\n\t\t}, DEBOUNCE_MS);\n\t}\n\n\tprivate async flush(): Promise<void> {\n\t\tconst text = this.pipelineMode ? this.renderPipeline() : this.buffer;\n\t\tif (!text.trim()) return;\n\n\t\t// Telegram 4096-char limit\n\t\tconst parts = splitMessage(text);\n\t\tconst mainText = parts[0];\n\n\t\t// Pipeline mode uses plain text to avoid Markdown parsing failures\n\t\t// (activity text from LLM output may contain unmatched *, _, ` etc.)\n\t\tconst extra = this.pipelineMode ? { parse_mode: null } : undefined;\n\n\t\ttry {\n\t\t\tif (this.messageId) {\n\t\t\t\tawait this.tg.editMessage(this.messageId, mainText, extra);\n\t\t\t} else {\n\t\t\t\tthis.messageId = await this.tg.sendMessage(mainText, extra);\n\t\t\t}\n\t\t\t// Send overflow parts as separate messages\n\t\t\tfor (let i = 1; i < parts.length; i++) {\n\t\t\t\tawait this.tg.sendMessage(parts[i], extra);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore send/edit failures (rate limit, network, etc.)\n\t\t}\n\t}\n\n\tprivate renderPipeline(): string {\n\t\tlet text = `\\uD83D\\uDD27 ${this.currentPhase}\\n\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\u2501\\n`;\n\n\t\tfor (const step of this.steps) {\n\t\t\tconst icon = STATUS_ICON[step.status] ?? \"\\u2B1C\";\n\t\t\tconst time = step.duration !== undefined ? `${step.duration.toFixed(1)}s` : \"\";\n\t\t\tconst cost = step.cost !== undefined ? `$${step.cost.toFixed(2)}` : \"\";\n\t\t\ttext += `${icon} ${step.label.padEnd(14)} ${time.padStart(7)} ${cost.padStart(6)}\\n`;\n\t\t}\n\n\t\tif (this.activities.length > 0) {\n\t\t\ttext += \"\\n\";\n\t\t\tfor (const activity of this.activities) {\n\t\t\t\ttext += ` ${activity}\\n`;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Bot API wrapper using fetch (no external SDK).
|
|
3
|
+
*/
|
|
4
|
+
/** Interface for Telegram API operations (enables mocking in tests). */
|
|
5
|
+
export interface ITelegramAPI {
|
|
6
|
+
sendMessage(text: string, extra?: Record<string, unknown>): Promise<number>;
|
|
7
|
+
editMessage(messageId: number, text: string, extra?: Record<string, unknown>): Promise<void>;
|
|
8
|
+
answerCallbackQuery(callbackQueryId: string, text?: string): Promise<void>;
|
|
9
|
+
getUpdates(): Promise<TelegramUpdate[]>;
|
|
10
|
+
waitForCallbackQuery(): Promise<string>;
|
|
11
|
+
waitForTextMessage(): Promise<string>;
|
|
12
|
+
resolveCallback(data: string): boolean;
|
|
13
|
+
resolveText(text: string): boolean;
|
|
14
|
+
hasPending(): boolean;
|
|
15
|
+
hasPendingText(): boolean;
|
|
16
|
+
setChatId(chatId: string): void;
|
|
17
|
+
hasChatId(): boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Subset of Telegram Update object. */
|
|
20
|
+
export interface TelegramUpdate {
|
|
21
|
+
update_id: number;
|
|
22
|
+
message?: {
|
|
23
|
+
message_id: number;
|
|
24
|
+
chat: {
|
|
25
|
+
id: number;
|
|
26
|
+
};
|
|
27
|
+
text?: string;
|
|
28
|
+
from?: {
|
|
29
|
+
id: number;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
callback_query?: {
|
|
33
|
+
id: string;
|
|
34
|
+
data?: string;
|
|
35
|
+
from: {
|
|
36
|
+
id: number;
|
|
37
|
+
};
|
|
38
|
+
message?: {
|
|
39
|
+
message_id: number;
|
|
40
|
+
chat: {
|
|
41
|
+
id: number;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export declare class TelegramAPI {
|
|
47
|
+
private readonly baseUrl;
|
|
48
|
+
private chatId;
|
|
49
|
+
private pollOffset;
|
|
50
|
+
/** Pending promise resolvers for interactive UI. */
|
|
51
|
+
private pendingCallback;
|
|
52
|
+
private pendingText;
|
|
53
|
+
constructor(token: string, chatId?: string);
|
|
54
|
+
/** Set the chat ID (used during auto-discovery). */
|
|
55
|
+
setChatId(chatId: string): void;
|
|
56
|
+
/** Whether a chat ID has been set. */
|
|
57
|
+
hasChatId(): boolean;
|
|
58
|
+
/** Send a text message. Returns the message_id. Set parseMode to null to send plain text. */
|
|
59
|
+
sendMessage(text: string, extra?: Record<string, unknown>): Promise<number>;
|
|
60
|
+
/** Edit an existing message's text. Silently ignores "same text" errors. */
|
|
61
|
+
editMessage(messageId: number, text: string, extra?: Record<string, unknown>): Promise<void>;
|
|
62
|
+
/** Register bot commands in Telegram's menu. */
|
|
63
|
+
setMyCommands(commands: Array<{
|
|
64
|
+
command: string;
|
|
65
|
+
description: string;
|
|
66
|
+
}>): Promise<void>;
|
|
67
|
+
/** Acknowledge a callback query (required by Telegram to dismiss loading state). */
|
|
68
|
+
answerCallbackQuery(callbackQueryId: string, text?: string): Promise<void>;
|
|
69
|
+
/** Long-poll for updates. Returns new updates (30s timeout). */
|
|
70
|
+
getUpdates(): Promise<TelegramUpdate[]>;
|
|
71
|
+
/** Wait for the next callback query data (used by select/confirm UI). */
|
|
72
|
+
waitForCallbackQuery(): Promise<string>;
|
|
73
|
+
/** Wait for the next text message (used by input/editor UI). */
|
|
74
|
+
waitForTextMessage(): Promise<string>;
|
|
75
|
+
/** Resolve a pending callback query promise. Returns true if resolved. */
|
|
76
|
+
resolveCallback(data: string): boolean;
|
|
77
|
+
/** Resolve a pending text message promise. Returns true if resolved. */
|
|
78
|
+
resolveText(text: string): boolean;
|
|
79
|
+
/** Whether there is a pending callback or text promise. */
|
|
80
|
+
hasPending(): boolean;
|
|
81
|
+
/** Whether there is a pending text promise. */
|
|
82
|
+
hasPendingText(): boolean;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=telegram-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram-api.d.ts","sourceRoot":"","sources":["../../src/telegram/telegram-api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Telegram Bot API wrapper using fetch (no external SDK).\n */\n\n/** Interface for Telegram API operations (enables mocking in tests). */\nexport interface ITelegramAPI {\n\tsendMessage(text: string, extra?: Record<string, unknown>): Promise<number>;\n\teditMessage(messageId: number, text: string, extra?: Record<string, unknown>): Promise<void>;\n\tanswerCallbackQuery(callbackQueryId: string, text?: string): Promise<void>;\n\tgetUpdates(): Promise<TelegramUpdate[]>;\n\twaitForCallbackQuery(): Promise<string>;\n\twaitForTextMessage(): Promise<string>;\n\tresolveCallback(data: string): boolean;\n\tresolveText(text: string): boolean;\n\thasPending(): boolean;\n\thasPendingText(): boolean;\n\tsetChatId(chatId: string): void;\n\thasChatId(): boolean;\n}\n\n/** Subset of Telegram Update object. */\nexport interface TelegramUpdate {\n\tupdate_id: number;\n\tmessage?: {\n\t\tmessage_id: number;\n\t\tchat: { id: number };\n\t\ttext?: string;\n\t\tfrom?: { id: number };\n\t};\n\tcallback_query?: {\n\t\tid: string;\n\t\tdata?: string;\n\t\tfrom: { id: number };\n\t\tmessage?: { message_id: number; chat: { id: number } };\n\t};\n}\n\nexport class TelegramAPI {\n\tprivate readonly baseUrl: string;\n\tprivate chatId: string;\n\tprivate pollOffset = 0;\n\n\t/** Pending promise resolvers for interactive UI. */\n\tprivate pendingCallback: ((value: string) => void) | null = null;\n\tprivate pendingText: ((value: string) => void) | null = null;\n\n\tconstructor(token: string, chatId?: string) {\n\t\tthis.baseUrl = `https://api.telegram.org/bot${token}`;\n\t\tthis.chatId = chatId ?? \"\";\n\t}\n\n\t/** Set the chat ID (used during auto-discovery). */\n\tsetChatId(chatId: string): void {\n\t\tthis.chatId = chatId;\n\t}\n\n\t/** Whether a chat ID has been set. */\n\thasChatId(): boolean {\n\t\treturn this.chatId !== \"\";\n\t}\n\n\t/** Send a text message. Returns the message_id. Set parseMode to null to send plain text. */\n\tasync sendMessage(text: string, extra?: Record<string, unknown>): Promise<number> {\n\t\tconst body: Record<string, unknown> = {\n\t\t\tchat_id: this.chatId,\n\t\t\ttext,\n\t\t\tparse_mode: \"Markdown\",\n\t\t\t...extra,\n\t\t};\n\t\t// Remove parse_mode if explicitly set to falsy (plain text mode)\n\t\tif (extra && \"parse_mode\" in extra && !extra.parse_mode) {\n\t\t\tdelete body.parse_mode;\n\t\t}\n\t\tconst res = await fetch(`${this.baseUrl}/sendMessage`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(body),\n\t\t});\n\t\tconst data = (await res.json()) as { ok: boolean; result?: { message_id: number } };\n\t\tif (!data.ok || !data.result) {\n\t\t\tthrow new Error(`sendMessage failed: ${JSON.stringify(data)}`);\n\t\t}\n\t\treturn data.result.message_id;\n\t}\n\n\t/** Edit an existing message's text. Silently ignores \"same text\" errors. */\n\tasync editMessage(messageId: number, text: string, extra?: Record<string, unknown>): Promise<void> {\n\t\tconst body: Record<string, unknown> = {\n\t\t\tchat_id: this.chatId,\n\t\t\tmessage_id: messageId,\n\t\t\ttext,\n\t\t\tparse_mode: \"Markdown\",\n\t\t\t...extra,\n\t\t};\n\t\tif (extra && \"parse_mode\" in extra && !extra.parse_mode) {\n\t\t\tdelete body.parse_mode;\n\t\t}\n\t\ttry {\n\t\t\tawait fetch(`${this.baseUrl}/editMessageText`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify(body),\n\t\t\t});\n\t\t} catch {\n\t\t\t// Ignore network errors on edit (non-critical)\n\t\t}\n\t}\n\n\t/** Register bot commands in Telegram's menu. */\n\tasync setMyCommands(commands: Array<{ command: string; description: string }>): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/setMyCommands`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ commands }),\n\t\t}).catch(() => {});\n\t}\n\n\t/** Acknowledge a callback query (required by Telegram to dismiss loading state). */\n\tasync answerCallbackQuery(callbackQueryId: string, text?: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/answerCallbackQuery`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ callback_query_id: callbackQueryId, text }),\n\t\t});\n\t}\n\n\t/** Long-poll for updates. Returns new updates (30s timeout). */\n\tasync getUpdates(): Promise<TelegramUpdate[]> {\n\t\tconst res = await fetch(`${this.baseUrl}/getUpdates?offset=${this.pollOffset}&timeout=30`);\n\t\tconst data = (await res.json()) as { ok: boolean; result?: TelegramUpdate[] };\n\t\tconst updates = data.result ?? [];\n\t\tfor (const u of updates) {\n\t\t\tthis.pollOffset = u.update_id + 1;\n\t\t}\n\t\treturn updates;\n\t}\n\n\t/** Wait for the next callback query data (used by select/confirm UI). */\n\twaitForCallbackQuery(): Promise<string> {\n\t\treturn new Promise<string>((resolve) => {\n\t\t\tthis.pendingCallback = resolve;\n\t\t});\n\t}\n\n\t/** Wait for the next text message (used by input/editor UI). */\n\twaitForTextMessage(): Promise<string> {\n\t\treturn new Promise<string>((resolve) => {\n\t\t\tthis.pendingText = resolve;\n\t\t});\n\t}\n\n\t/** Resolve a pending callback query promise. Returns true if resolved. */\n\tresolveCallback(data: string): boolean {\n\t\tif (this.pendingCallback) {\n\t\t\tthis.pendingCallback(data);\n\t\t\tthis.pendingCallback = null;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Resolve a pending text message promise. Returns true if resolved. */\n\tresolveText(text: string): boolean {\n\t\tif (this.pendingText) {\n\t\t\tthis.pendingText(text);\n\t\t\tthis.pendingText = null;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/** Whether there is a pending callback or text promise. */\n\thasPending(): boolean {\n\t\treturn this.pendingCallback !== null || this.pendingText !== null;\n\t}\n\n\t/** Whether there is a pending text promise. */\n\thasPendingText(): boolean {\n\t\treturn this.pendingText !== null;\n\t}\n}\n"]}
|