@kolisachint/hoocode-agent 0.5.33 → 0.5.35

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/core/tools/read.d.ts.map +1 -1
  3. package/dist/core/tools/read.js +2 -2
  4. package/dist/core/tools/read.js.map +1 -1
  5. package/dist/modes/interactive/components/assistant-message.d.ts +16 -3
  6. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  7. package/dist/modes/interactive/components/assistant-message.js +18 -10
  8. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  9. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  10. package/dist/modes/interactive/components/branch-summary-message.js +2 -2
  11. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  12. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  13. package/dist/modes/interactive/components/compaction-summary-message.js +2 -2
  14. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  15. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  16. package/dist/modes/interactive/components/custom-message.js +3 -2
  17. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  18. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  19. package/dist/modes/interactive/components/footer.js +5 -1
  20. package/dist/modes/interactive/components/footer.js.map +1 -1
  21. package/dist/modes/interactive/components/progress-bar.d.ts.map +1 -1
  22. package/dist/modes/interactive/components/progress-bar.js +1 -1
  23. package/dist/modes/interactive/components/progress-bar.js.map +1 -1
  24. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  25. package/dist/modes/interactive/components/skill-invocation-message.js +3 -3
  26. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  27. package/dist/modes/interactive/components/tool-chain-summary.d.ts.map +1 -1
  28. package/dist/modes/interactive/components/tool-chain-summary.js +28 -10
  29. package/dist/modes/interactive/components/tool-chain-summary.js.map +1 -1
  30. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  31. package/dist/modes/interactive/components/user-message.js +2 -1
  32. package/dist/modes/interactive/components/user-message.js.map +1 -1
  33. package/dist/modes/interactive/interactive-mode.d.ts +13 -9
  34. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  35. package/dist/modes/interactive/interactive-mode.js +30 -23
  36. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  37. package/dist/modes/interactive/theme/theme-schema.json +24 -0
  38. package/dist/modes/interactive/theme/theme.d.ts +19 -2
  39. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  40. package/dist/modes/interactive/theme/theme.js +66 -1
  41. package/dist/modes/interactive/theme/theme.js.map +1 -1
  42. package/dist/modes/interactive/theme/vox-cutout-dark.json +116 -0
  43. package/dist/modes/interactive/theme/vox-cutout-light.json +115 -0
  44. package/docs/themes.md +101 -1
  45. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  46. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  47. package/examples/extensions/sandbox/package.json +1 -1
  48. package/examples/extensions/with-deps/package.json +1 -1
  49. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"assistant-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAyB,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AACxH,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE5D,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAC3C,MAAM,eAAe,GAAG,gBAAgB,CAAC;AACzC,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAE3C,+EAA+E;AAC/E,+EAA+E;AAC/E,kEAAkE;AAClE,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B,MAAM,YAAY,GAAG,gCAAgC,CAAC;AACtD,MAAM,QAAQ,GAAG,oBAAoB,CAAC;AACtC,MAAM,WAAW,GAAG,sBAAsB,CAAC;AAC3C,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AAEnD;yEACyE;AACzE,SAAS,cAAc,CAAC,IAAY,EAAE,IAAY,EAAW;IAC5D,2EAA2E;IAC3E,uBAAuB;IACvB,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACnC,sEAAsE;IACtE,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,sEAAsE;IACtE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3E,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3E,wEAAwE;IACxE,4DAA4D;IAC5D,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACjD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY,EAAY;IAChE,qEAAqE;IACrE,8DAA8D;IAC9D,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS,GAAG,CAAC,SAAS,CAAC;QAChD,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE;gBAAE,CAAC,EAAE,CAAC;YACvD,oEAAoE;YACpE,gEAAgE;YAChE,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,UAAU,GAAG,CAAC,IAAI,cAAc,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClF,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBACnD,UAAU,GAAG,CAAC,CAAC;YAChB,CAAC;YACD,CAAC,GAAG,CAAC,CAAC;YACN,SAAS;QACV,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,YAAY,GAAG,IAAI,CAAC;QAC5C,CAAC,EAAE,CAAC;IACL,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAAA,CAC3C;AAED;;GAEG;AACH,MAAM,OAAO,yBAA0B,SAAQ,SAAS;IAC/C,gBAAgB,CAAY;IAC5B,iBAAiB,CAAU;IAC3B,aAAa,CAAgB;IAC7B,mBAAmB,CAAS;IAC5B,WAAW,CAAoB;IAC/B,YAAY,GAAG,KAAK,CAAC;IAC7B,8EAA8E;IAC9E,4EAA4E;IAC5E,6EAA6E;IAC7E,2EAA2E;IAC3E,yEAAyE;IACjE,aAAa,GAAG,IAAI,GAAG,EAA0C,CAAC;IAE1E,YACC,OAA0B,EAC1B,iBAAiB,GAAG,KAAK,EACzB,aAAa,GAAkB,gBAAgB,EAAE,EACjD,mBAAmB,GAAG,aAAa,EAClC;QACD,KAAK,EAAE,CAAC;QAER,IAAI,CAAC,iBAAiB,GAAG,iBAAiB,CAAC;QAC3C,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;QAE/C,sCAAsC;QACtC,IAAI,CAAC,gBAAgB,GAAG,IAAI,SAAS,EAAE,CAAC;QACxC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAErC,IAAI,OAAO,EAAE,CAAC;YACb,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;IAAA,CACD;IAEQ,UAAU,GAAS;QAC3B,2EAA2E;QAC3E,uEAAuE;QACvE,qCAAqC;QACrC,KAAK,MAAM,EAAE,EAAE,EAAE,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC;YAClD,EAAE,CAAC,UAAU,EAAE,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,CAAC;IAAA,CACD;IAEO,aAAa,CAAC,GAAW,EAAE,IAAY,EAAE,KAAwB,EAAY;QACpF,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,KAAK,EAAE,CAAC;YACX,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBACzB,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBACvB,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;YACnB,CAAC;YACD,OAAO,KAAK,CAAC,EAAE,CAAC;QACjB,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;QAC/D,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,OAAO,EAAE,CAAC;IAAA,CACV;IAED;0EACsE;IAC9D,kBAAkB,GAAG,KAAK,CAAC;IAEnC;;;;;;OAMG;IACK,gBAAgB,CAAC,OAAe,EAAE,IAAY,EAAE,SAAkB,EAAE,KAAwB,EAAQ;QAC3G,IAAI,SAAS,IAAI,IAAI,CAAC,MAAM,IAAI,iBAAiB,EAAE,CAAC;YACnD,MAAM,MAAM,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC;YAC9C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACxC,IAAI,CAAC,GAAG,CAAC;wBAAE,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;oBACzD,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,OAAO,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;gBAC7F,CAAC;gBACD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;gBAC/B,OAAO;YACR,CAAC;QACF,CAAC;QACD,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAAA,CACzE;IAED,oBAAoB,CAAC,IAAa,EAAQ;QACzC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC9B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,CAAC;IAAA,CACD;IAED,sBAAsB,CAAC,KAAa,EAAQ;QAC3C,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC;QACjC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,CAAC;IAAA,CACD;IAED;;8DAE0D;IAClD,QAAQ,CAAoC;IAE3C,MAAM,CAAC,KAAa,EAAY;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7C,OAAO,KAAK,CAAC;QACd,CAAC;QAED,IAAI,IAAI,CAAC,QAAQ,EAAE,GAAG,KAAK,KAAK,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAC1B,CAAC;QACD,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC1B,GAAG,CAAC,CAAC,CAAC,GAAG,iBAAiB,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACpC,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,eAAe,GAAG,iBAAiB,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,QAAQ,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QACpC,OAAO,GAAG,CAAC;IAAA,CACX;IAED,aAAa,CAAC,OAAyB,EAAE,SAAS,GAAG,KAAK,EAAQ;QACjE,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;QAE3B,wEAAwE;QACxE,kEAAkE;QAClE,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC3C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC7C,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;oBAAE,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3D,CAAC;YACD,IAAI,CAAC,kBAAkB,GAAG,KAAK,CAAC;QACjC,CAAC;QAED,0BAA0B;QAC1B,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAE9B,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAC7C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAC3F,CAAC;QAEF,IAAI,iBAAiB,EAAE,CAAC;YACvB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;QAED,0BAA0B;QAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACjD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YACnC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBACpD,6DAA6D;gBAC7D,+DAA+D;gBAC/D,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,SAAS,CAAC,CAAC;YACpE,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;gBACnE,yEAAyE;gBACzE,yFAAyF;gBACzF,MAAM,sBAAsB,GAAG,OAAO,CAAC,OAAO;qBAC5C,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;qBACZ,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAEpG,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBAC5B,yCAAyC;oBACzC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAC7B,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAChF,CAAC;oBACF,IAAI,sBAAsB,EAAE,CAAC;wBAC5B,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC/C,CAAC;gBACF,CAAC;qBAAM,CAAC;oBACP,iEAA+D;oBAC/D,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,WAAW,EAAE,OAAK,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE;wBACjF,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,cAAc,EAAE,IAAI,CAAC;wBACvD,MAAM,EAAE,IAAI;qBACZ,CAAC,CAAC;oBACH,IAAI,sBAAsB,EAAE,CAAC;wBAC5B,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC/C,CAAC;gBACF,CAAC;YACF,CAAC;QACF,CAAC;QAED,gDAAgD;QAChD,sFAAsF;QACtF,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;QACxE,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACtC,MAAM,YAAY,GACjB,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,KAAK,qBAAqB;oBACrE,CAAC,CAAC,OAAO,CAAC,YAAY;oBACtB,CAAC,CAAC,mBAAmB,CAAC;gBACxB,IAAI,iBAAiB,EAAE,CAAC;oBACvB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/C,CAAC;qBAAM,CAAC;oBACP,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/C,CAAC;gBACD,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACjF,CAAC;iBAAM,IAAI,OAAO,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;gBAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,YAAY,IAAI,eAAe,CAAC;gBACzD,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC9C,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACzF,CAAC;QACF,CAAC;IAAA,CACD;CACD","sourcesContent":["import type { AssistantMessage } from \"@kolisachint/hoocode-ai\";\nimport { Container, type DefaultTextStyle, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\n\nconst OSC133_ZONE_START = \"\\x1b]133;A\\x07\";\nconst OSC133_ZONE_END = \"\\x1b]133;B\\x07\";\nconst OSC133_ZONE_FINAL = \"\\x1b]133;C\\x07\";\n\n// Streaming messages below this size render as a single Markdown; above it the\n// text is segmented at stable block boundaries so each throttle tick re-parses\n// only the growing tail instead of the whole accumulated message.\nconst SEGMENT_MIN_CHARS = 2048;\n\nconst LIST_ITEM_RE = /^ {0,3}(?:[-*+] |\\d{1,9}[.)] )/;\nconst FENCE_RE = /^ {0,3}(?:```|~~~)/;\nconst LINK_DEF_RE = /^ {0,3}\\[[^\\]]+\\]: /m;\nconst SETEXT_UNDERLINE_RE = /^ {0,3}(?:=+|-+)\\s*$/;\n\n/** True when a blank-line gap between `prev` and `next` (both non-blank) is a\n * safe place to cut the markdown into independently-parseable chunks. */\nfunction isSafeBoundary(prev: string, next: string): boolean {\n\t// Indented continuation (loose list item body, indented code) binds to the\n\t// block above the gap.\n\tif (/^\\s/.test(next)) return false;\n\t// A blank line between two list items is a loose list, not two lists.\n\tif (LIST_ITEM_RE.test(prev) && LIST_ITEM_RE.test(next)) return false;\n\t// Tables and raw HTML blocks can span blank lines in surprising ways.\n\tif (next.startsWith(\"|\") || prev.trimStart().startsWith(\"|\")) return false;\n\tif (next.startsWith(\"<\") || prev.trimStart().startsWith(\"<\")) return false;\n\t// A bare ===/--- line after the gap could lex as setext underline or hr\n\t// differently without its preceding text; keep it attached.\n\tif (SETEXT_UNDERLINE_RE.test(next)) return false;\n\treturn true;\n}\n\n/**\n * Split markdown into chunks at blank-line boundaries where each chunk lexes\n * independently to the same blocks the whole text would. Prefix-stable:\n * appending text never changes earlier boundaries (decisions depend only on\n * preceding fence parity and the lines adjacent to each gap), so during\n * streaming every chunk except the last is byte-identical across updates and\n * its Markdown render cache keeps hitting. Exported for tests.\n */\nexport function segmentStreamingMarkdown(text: string): string[] {\n\t// Reference-style link/footnote definitions resolve across the whole\n\t// document; segmenting would break lookups from other chunks.\n\tif (LINK_DEF_RE.test(text)) return [text];\n\tconst lines = text.split(\"\\n\");\n\tconst chunks: string[] = [];\n\tlet chunkStart = 0;\n\tlet fenceOpen = false;\n\tlet prevNonblank = \"\";\n\tlet i = 0;\n\twhile (i < lines.length) {\n\t\tconst line = lines[i];\n\t\tif (FENCE_RE.test(line)) fenceOpen = !fenceOpen;\n\t\tif (line.trim() === \"\" && !fenceOpen) {\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < lines.length && lines[j].trim() === \"\") j++;\n\t\t\t// Only cut when the gap has content on both sides; a trailing blank\n\t\t\t// run stays attached so the decision never has to be revisited.\n\t\t\tif (j < lines.length && chunkStart < i && isSafeBoundary(prevNonblank, lines[j])) {\n\t\t\t\tchunks.push(lines.slice(chunkStart, i).join(\"\\n\"));\n\t\t\t\tchunkStart = j;\n\t\t\t}\n\t\t\ti = j;\n\t\t\tcontinue;\n\t\t}\n\t\tif (line.trim() !== \"\") prevNonblank = line;\n\t\ti++;\n\t}\n\tconst last = lines.slice(chunkStart).join(\"\\n\");\n\tif (last.trim() !== \"\") {\n\t\tchunks.push(last);\n\t}\n\treturn chunks.length > 0 ? chunks : [text];\n}\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate hideThinkingBlock: boolean;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate hiddenThinkingLabel: string;\n\tprivate lastMessage?: AssistantMessage;\n\tprivate hasToolCalls = false;\n\t// Markdown children reused across streaming updates, keyed by content index +\n\t// kind. Markdown caches its rendered lines by (text, width); recreating the\n\t// instances on every streamed delta discarded those caches and re-parsed the\n\t// entire message (thinking trace included) per frame. Reuse keeps finished\n\t// blocks cached so only the block whose text actually changed re-parses.\n\tprivate markdownCache = new Map<string, { md: Markdown; text: string }>();\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\thideThinkingBlock = false,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t\thiddenThinkingLabel = \"Thinking...\",\n\t) {\n\t\tsuper();\n\n\t\tthis.hideThinkingBlock = hideThinkingBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.hiddenThinkingLabel = hiddenThinkingLabel;\n\n\t\t// Container for text/thinking content\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\t// Cached Markdown blocks may be detached right now (e.g. hidden thinking);\n\t\t// drop their render caches too so a theme/width change can't resurface\n\t\t// stale styling when they re-attach.\n\t\tfor (const { md } of this.markdownCache.values()) {\n\t\t\tmd.invalidate();\n\t\t}\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tprivate reuseMarkdown(key: string, text: string, style?: DefaultTextStyle): Markdown {\n\t\tconst entry = this.markdownCache.get(key);\n\t\tif (entry) {\n\t\t\tif (entry.text !== text) {\n\t\t\t\tentry.md.setText(text);\n\t\t\t\tentry.text = text;\n\t\t\t}\n\t\t\treturn entry.md;\n\t\t}\n\t\tconst md = new Markdown(text, 1, 0, this.markdownTheme, style);\n\t\tthis.markdownCache.set(key, { md, text });\n\t\treturn md;\n\t}\n\n\t/** Set when the current children include streaming segments; the next\n\t * non-streaming (final/rebuild) render purges their cache entries. */\n\tprivate hasSegmentedBlocks = false;\n\n\t/**\n\t * Add one markdown content block. Large blocks still being streamed are\n\t * segmented at stable boundaries so only the tail chunk re-parses per\n\t * update; every earlier chunk is byte-stable and stays cached. The final\n\t * (non-streaming) render collapses back to one canonical Markdown, so any\n\t * segmentation artifact is transient by construction.\n\t */\n\tprivate addMarkdownBlock(keyBase: string, text: string, streaming: boolean, style?: DefaultTextStyle): void {\n\t\tif (streaming && text.length >= SEGMENT_MIN_CHARS) {\n\t\t\tconst chunks = segmentStreamingMarkdown(text);\n\t\t\tif (chunks.length > 1) {\n\t\t\t\tfor (let k = 0; k < chunks.length; k++) {\n\t\t\t\t\tif (k > 0) this.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\tthis.contentContainer.addChild(this.reuseMarkdown(`${keyBase}:seg:${k}`, chunks[k], style));\n\t\t\t\t}\n\t\t\t\tthis.hasSegmentedBlocks = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tthis.contentContainer.addChild(this.reuseMarkdown(keyBase, text, style));\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.hideThinkingBlock = hide;\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHiddenThinkingLabel(label: string): void {\n\t\tthis.hiddenThinkingLabel = label;\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\t/** OSC-zone wrap memo: Container.render returns the same array across\n\t * frames when nothing changed, so it must not be mutated — the wrapped\n\t * copy is cached keyed on the source array's identity. */\n\tprivate zoneMemo?: { src: string[]; out: string[] };\n\n\toverride render(width: number): string[] {\n\t\tconst lines = super.render(width);\n\t\tif (this.hasToolCalls || lines.length === 0) {\n\t\t\treturn lines;\n\t\t}\n\n\t\tif (this.zoneMemo?.src === lines) {\n\t\t\treturn this.zoneMemo.out;\n\t\t}\n\t\tconst out = lines.slice();\n\t\tout[0] = OSC133_ZONE_START + out[0];\n\t\tout[out.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + out[out.length - 1];\n\t\tthis.zoneMemo = { src: lines, out };\n\t\treturn out;\n\t}\n\n\tupdateContent(message: AssistantMessage, streaming = false): void {\n\t\tthis.lastMessage = message;\n\n\t\t// A final/rebuild render replaces streaming segments with the canonical\n\t\t// single-Markdown form; drop the segment cache entries they used.\n\t\tif (!streaming && this.hasSegmentedBlocks) {\n\t\t\tfor (const key of this.markdownCache.keys()) {\n\t\t\t\tif (key.includes(\":seg:\")) this.markdownCache.delete(key);\n\t\t\t}\n\t\t\tthis.hasSegmentedBlocks = false;\n\t\t}\n\n\t\t// Clear content container\n\t\tthis.contentContainer.clear();\n\n\t\tconst hasVisibleContent = message.content.some(\n\t\t\t(c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()),\n\t\t);\n\n\t\tif (hasVisibleContent) {\n\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.addMarkdownBlock(`${i}:text`, content.text.trim(), streaming);\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.some((c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()));\n\n\t\t\t\tif (this.hideThinkingBlock) {\n\t\t\t\t\t// Show static thinking label when hidden\n\t\t\t\t\tthis.contentContainer.addChild(\n\t\t\t\t\t\tnew Text(theme.italic(theme.fg(\"thinkingText\", this.hiddenThinkingLabel)), 1, 0),\n\t\t\t\t\t);\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Thinking traces in thinkingText color, italic, with ✻ prefix\n\t\t\t\t\tthis.addMarkdownBlock(`${i}:thinking`, `✻ ${content.thinking.trim()}`, streaming, {\n\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\titalic: true,\n\t\t\t\t\t});\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check if aborted - show after partial content\n\t\t// But only if there are no tool calls (tool execution components will show the error)\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tthis.hasToolCalls = hasToolCalls;\n\t\tif (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\tif (hasVisibleContent) {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t} else {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t}\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", abortMessage), 1, 0));\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", `Error: ${errorMsg}`), 1, 0));\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"assistant-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@kolisachint/hoocode-ai\";\nimport { Container, type DefaultTextStyle, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\n\n/**\n * How a thinking block renders.\n *\n * `omit` exists for radar. Folding a trace to its one-line label is right when\n * the label sits between things you can see; under a chain summary it is not,\n * because a message whose only content is thinking plus tool calls has nothing\n * left on screen once its calls collapse into the chain's row. A stack of\n * `Thinking...` lines under a one-line summary is precisely the noise radar\n * exists to remove.\n */\nexport type ThinkingDisplay = \"full\" | \"label\" | \"omit\";\n\nconst OSC133_ZONE_START = \"\\x1b]133;A\\x07\";\nconst OSC133_ZONE_END = \"\\x1b]133;B\\x07\";\nconst OSC133_ZONE_FINAL = \"\\x1b]133;C\\x07\";\n\n// Streaming messages below this size render as a single Markdown; above it the\n// text is segmented at stable block boundaries so each throttle tick re-parses\n// only the growing tail instead of the whole accumulated message.\nconst SEGMENT_MIN_CHARS = 2048;\n\nconst LIST_ITEM_RE = /^ {0,3}(?:[-*+] |\\d{1,9}[.)] )/;\nconst FENCE_RE = /^ {0,3}(?:```|~~~)/;\nconst LINK_DEF_RE = /^ {0,3}\\[[^\\]]+\\]: /m;\nconst SETEXT_UNDERLINE_RE = /^ {0,3}(?:=+|-+)\\s*$/;\n\n/** True when a blank-line gap between `prev` and `next` (both non-blank) is a\n * safe place to cut the markdown into independently-parseable chunks. */\nfunction isSafeBoundary(prev: string, next: string): boolean {\n\t// Indented continuation (loose list item body, indented code) binds to the\n\t// block above the gap.\n\tif (/^\\s/.test(next)) return false;\n\t// A blank line between two list items is a loose list, not two lists.\n\tif (LIST_ITEM_RE.test(prev) && LIST_ITEM_RE.test(next)) return false;\n\t// Tables and raw HTML blocks can span blank lines in surprising ways.\n\tif (next.startsWith(\"|\") || prev.trimStart().startsWith(\"|\")) return false;\n\tif (next.startsWith(\"<\") || prev.trimStart().startsWith(\"<\")) return false;\n\t// A bare ===/--- line after the gap could lex as setext underline or hr\n\t// differently without its preceding text; keep it attached.\n\tif (SETEXT_UNDERLINE_RE.test(next)) return false;\n\treturn true;\n}\n\n/**\n * Split markdown into chunks at blank-line boundaries where each chunk lexes\n * independently to the same blocks the whole text would. Prefix-stable:\n * appending text never changes earlier boundaries (decisions depend only on\n * preceding fence parity and the lines adjacent to each gap), so during\n * streaming every chunk except the last is byte-identical across updates and\n * its Markdown render cache keeps hitting. Exported for tests.\n */\nexport function segmentStreamingMarkdown(text: string): string[] {\n\t// Reference-style link/footnote definitions resolve across the whole\n\t// document; segmenting would break lookups from other chunks.\n\tif (LINK_DEF_RE.test(text)) return [text];\n\tconst lines = text.split(\"\\n\");\n\tconst chunks: string[] = [];\n\tlet chunkStart = 0;\n\tlet fenceOpen = false;\n\tlet prevNonblank = \"\";\n\tlet i = 0;\n\twhile (i < lines.length) {\n\t\tconst line = lines[i];\n\t\tif (FENCE_RE.test(line)) fenceOpen = !fenceOpen;\n\t\tif (line.trim() === \"\" && !fenceOpen) {\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < lines.length && lines[j].trim() === \"\") j++;\n\t\t\t// Only cut when the gap has content on both sides; a trailing blank\n\t\t\t// run stays attached so the decision never has to be revisited.\n\t\t\tif (j < lines.length && chunkStart < i && isSafeBoundary(prevNonblank, lines[j])) {\n\t\t\t\tchunks.push(lines.slice(chunkStart, i).join(\"\\n\"));\n\t\t\t\tchunkStart = j;\n\t\t\t}\n\t\t\ti = j;\n\t\t\tcontinue;\n\t\t}\n\t\tif (line.trim() !== \"\") prevNonblank = line;\n\t\ti++;\n\t}\n\tconst last = lines.slice(chunkStart).join(\"\\n\");\n\tif (last.trim() !== \"\") {\n\t\tchunks.push(last);\n\t}\n\treturn chunks.length > 0 ? chunks : [text];\n}\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate thinkingDisplay: ThinkingDisplay;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate hiddenThinkingLabel: string;\n\tprivate lastMessage?: AssistantMessage;\n\tprivate hasToolCalls = false;\n\t// Markdown children reused across streaming updates, keyed by content index +\n\t// kind. Markdown caches its rendered lines by (text, width); recreating the\n\t// instances on every streamed delta discarded those caches and re-parsed the\n\t// entire message (thinking trace included) per frame. Reuse keeps finished\n\t// blocks cached so only the block whose text actually changed re-parses.\n\tprivate markdownCache = new Map<string, { md: Markdown; text: string }>();\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\tthinkingDisplay: ThinkingDisplay = \"full\",\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t\thiddenThinkingLabel = \"Thinking...\",\n\t) {\n\t\tsuper();\n\n\t\tthis.thinkingDisplay = thinkingDisplay;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.hiddenThinkingLabel = hiddenThinkingLabel;\n\n\t\t// Container for text/thinking content\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\t// Cached Markdown blocks may be detached right now (e.g. hidden thinking);\n\t\t// drop their render caches too so a theme/width change can't resurface\n\t\t// stale styling when they re-attach.\n\t\tfor (const { md } of this.markdownCache.values()) {\n\t\t\tmd.invalidate();\n\t\t}\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tprivate reuseMarkdown(key: string, text: string, style?: DefaultTextStyle): Markdown {\n\t\tconst entry = this.markdownCache.get(key);\n\t\tif (entry) {\n\t\t\tif (entry.text !== text) {\n\t\t\t\tentry.md.setText(text);\n\t\t\t\tentry.text = text;\n\t\t\t}\n\t\t\treturn entry.md;\n\t\t}\n\t\tconst md = new Markdown(text, 1, 0, this.markdownTheme, style);\n\t\tthis.markdownCache.set(key, { md, text });\n\t\treturn md;\n\t}\n\n\t/** Set when the current children include streaming segments; the next\n\t * non-streaming (final/rebuild) render purges their cache entries. */\n\tprivate hasSegmentedBlocks = false;\n\n\t/**\n\t * Add one markdown content block. Large blocks still being streamed are\n\t * segmented at stable boundaries so only the tail chunk re-parses per\n\t * update; every earlier chunk is byte-stable and stays cached. The final\n\t * (non-streaming) render collapses back to one canonical Markdown, so any\n\t * segmentation artifact is transient by construction.\n\t */\n\tprivate addMarkdownBlock(keyBase: string, text: string, streaming: boolean, style?: DefaultTextStyle): void {\n\t\tif (streaming && text.length >= SEGMENT_MIN_CHARS) {\n\t\t\tconst chunks = segmentStreamingMarkdown(text);\n\t\t\tif (chunks.length > 1) {\n\t\t\t\tfor (let k = 0; k < chunks.length; k++) {\n\t\t\t\t\tif (k > 0) this.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\tthis.contentContainer.addChild(this.reuseMarkdown(`${keyBase}:seg:${k}`, chunks[k], style));\n\t\t\t\t}\n\t\t\t\tthis.hasSegmentedBlocks = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tthis.contentContainer.addChild(this.reuseMarkdown(keyBase, text, style));\n\t}\n\n\tsetThinkingDisplay(display: ThinkingDisplay): void {\n\t\tthis.thinkingDisplay = display;\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\t/** Whether a content block puts anything on screen under the current settings. */\n\tprivate isVisibleBlock(content: AssistantMessage[\"content\"][number]): boolean {\n\t\tif (content.type === \"text\") return content.text.trim() !== \"\";\n\t\tif (content.type === \"thinking\") return this.thinkingDisplay !== \"omit\" && content.thinking.trim() !== \"\";\n\t\treturn false;\n\t}\n\n\tsetHiddenThinkingLabel(label: string): void {\n\t\tthis.hiddenThinkingLabel = label;\n\t\tif (this.lastMessage) {\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\t/** OSC-zone wrap memo: Container.render returns the same array across\n\t * frames when nothing changed, so it must not be mutated — the wrapped\n\t * copy is cached keyed on the source array's identity. */\n\tprivate zoneMemo?: { src: string[]; out: string[] };\n\n\toverride render(width: number): string[] {\n\t\tconst lines = super.render(width);\n\t\tif (this.hasToolCalls || lines.length === 0) {\n\t\t\treturn lines;\n\t\t}\n\n\t\tif (this.zoneMemo?.src === lines) {\n\t\t\treturn this.zoneMemo.out;\n\t\t}\n\t\tconst out = lines.slice();\n\t\tout[0] = OSC133_ZONE_START + out[0];\n\t\tout[out.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + out[out.length - 1];\n\t\tthis.zoneMemo = { src: lines, out };\n\t\treturn out;\n\t}\n\n\tupdateContent(message: AssistantMessage, streaming = false): void {\n\t\tthis.lastMessage = message;\n\n\t\t// A final/rebuild render replaces streaming segments with the canonical\n\t\t// single-Markdown form; drop the segment cache entries they used.\n\t\tif (!streaming && this.hasSegmentedBlocks) {\n\t\t\tfor (const key of this.markdownCache.keys()) {\n\t\t\t\tif (key.includes(\":seg:\")) this.markdownCache.delete(key);\n\t\t\t}\n\t\t\tthis.hasSegmentedBlocks = false;\n\t\t}\n\n\t\t// Clear content container\n\t\tthis.contentContainer.clear();\n\n\t\tconst hasVisibleContent = message.content.some((c) => this.isVisibleBlock(c));\n\n\t\tif (hasVisibleContent) {\n\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t}\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.addMarkdownBlock(`${i}:text`, content.text.trim(), streaming);\n\t\t\t} else if (content.type === \"thinking\" && content.thinking.trim()) {\n\t\t\t\tif (this.thinkingDisplay === \"omit\") continue;\n\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content.slice(i + 1).some((c) => this.isVisibleBlock(c));\n\n\t\t\t\tif (this.thinkingDisplay === \"label\") {\n\t\t\t\t\t// Show static thinking label when hidden\n\t\t\t\t\tthis.contentContainer.addChild(\n\t\t\t\t\t\tnew Text(theme.italic(theme.fg(\"thinkingText\", this.hiddenThinkingLabel)), 1, 0),\n\t\t\t\t\t);\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Thinking traces in thinkingText color, italic, with ✻ prefix\n\t\t\t\t\tthis.addMarkdownBlock(`${i}:thinking`, `✻ ${content.thinking.trim()}`, streaming, {\n\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\titalic: true,\n\t\t\t\t\t});\n\t\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check if aborted - show after partial content\n\t\t// But only if there are no tool calls (tool execution components will show the error)\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tthis.hasToolCalls = hasToolCalls;\n\t\tif (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\tif (hasVisibleContent) {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t} else {\n\t\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\t}\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", abortMessage), 1, 0));\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\t\t\t\tthis.contentContainer.addChild(new Spacer(1));\n\t\t\t\tthis.contentContainer.addChild(new Text(theme.fg(\"error\", `Error: ${errorMsg}`), 1, 0));\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"branch-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/branch-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAgB,MAAM,0BAA0B,CAAC;AAI3F;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,GAAG;IACrD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,OAAO,EAAE,oBAAoB,EAAE,aAAa,GAAE,aAAkC,EAK3F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CA0BrB","sourcesContent":["import type { BranchSummaryMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a branch summary message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class BranchSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: BranchSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[branch]\\x1b[22m`);\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = \"**Branch Summary**\\n\\n\";\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", \"Branch summary (\") +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"branch-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/branch-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAgB,MAAM,0BAA0B,CAAC;AAI3F;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,GAAG;IACrD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,OAAO,EAAE,oBAAoB,EAAE,aAAa,GAAE,aAAkC,EAK3F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CA0BrB","sourcesContent":["import type { BranchSummaryMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, messageLabel, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a branch summary message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class BranchSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: BranchSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst label = messageLabel(\"branch\");\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = \"**Branch Summary**\\n\\n\";\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", \"Branch summary (\") +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { Box, Markdown, Spacer, Text } from "@kolisachint/hoocode-tui";
2
- import { getMarkdownTheme, theme } from "../theme/theme.js";
2
+ import { getMarkdownTheme, messageLabel, theme } from "../theme/theme.js";
3
3
  import { keyText } from "./keybinding-hints.js";
4
4
  /**
5
5
  * Component that renders a branch summary message with collapsed/expanded state.
@@ -25,7 +25,7 @@ export class BranchSummaryMessageComponent extends Box {
25
25
  }
26
26
  updateDisplay() {
27
27
  this.clear();
28
- const label = theme.fg("customMessageLabel", `\x1b[1m[branch]\x1b[22m`);
28
+ const label = messageLabel("branch");
29
29
  this.addChild(new Text(label, 0, 0));
30
30
  this.addChild(new Spacer(1));
31
31
  if (this.expanded) {
@@ -1 +1 @@
1
- {"version":3,"file":"branch-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/branch-summary-message.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD;;;GAGG;AACH,MAAM,OAAO,6BAA8B,SAAQ,GAAG;IAC7C,QAAQ,GAAG,KAAK,CAAC;IACjB,OAAO,CAAuB;IAC9B,aAAa,CAAgB;IAErC,YAAY,OAA6B,EAAE,aAAa,GAAkB,gBAAgB,EAAE,EAAE;QAC7F,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAED,WAAW,CAAC,QAAiB,EAAQ;QACpC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEQ,UAAU,GAAS;QAC3B,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEO,aAAa,GAAS;QAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC,oBAAoB,EAAE,yBAAyB,CAAC,CAAC;QACxE,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,wBAAwB,CAAC;YACxC,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACrE,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,CACZ,IAAI,IAAI,CACP,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,kBAAkB,CAAC;gBAChD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;gBAC5C,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,aAAa,CAAC,EAC7C,CAAC,EACD,CAAC,CACD,CACD,CAAC;QACH,CAAC;IAAA,CACD;CACD","sourcesContent":["import type { BranchSummaryMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a branch summary message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class BranchSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: BranchSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[branch]\\x1b[22m`);\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = \"**Branch Summary**\\n\\n\";\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", \"Branch summary (\") +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"branch-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/branch-summary-message.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD;;;GAGG;AACH,MAAM,OAAO,6BAA8B,SAAQ,GAAG;IAC7C,QAAQ,GAAG,KAAK,CAAC;IACjB,OAAO,CAAuB;IAC9B,aAAa,CAAgB;IAErC,YAAY,OAA6B,EAAE,aAAa,GAAkB,gBAAgB,EAAE,EAAE;QAC7F,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAED,WAAW,CAAC,QAAiB,EAAQ;QACpC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEQ,UAAU,GAAS;QAC3B,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEO,aAAa,GAAS;QAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,wBAAwB,CAAC;YACxC,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACrE,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,CACZ,IAAI,IAAI,CACP,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,kBAAkB,CAAC;gBAChD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;gBAC5C,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,aAAa,CAAC,EAC7C,CAAC,EACD,CAAC,CACD,CACD,CAAC;QACH,CAAC;IAAA,CACD;CACD","sourcesContent":["import type { BranchSummaryMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, messageLabel, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a branch summary message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class BranchSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: BranchSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: BranchSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst label = messageLabel(\"branch\");\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = \"**Branch Summary**\\n\\n\";\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", \"Branch summary (\") +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"compaction-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAgB,MAAM,0BAA0B,CAAC;AAI3F;;;GAGG;AACH,qBAAa,iCAAkC,SAAQ,GAAG;IACzD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,OAAO,EAAE,wBAAwB,EAAE,aAAa,GAAE,aAAkC,EAK/F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CAgCrB","sourcesContent":["import type { CompactionSummaryMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a compaction message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class CompactionSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst beforeStr = this.message.tokensBefore.toLocaleString();\n\t\tconst after = this.message.tokensAfter;\n\t\tconst summaryText =\n\t\t\tafter !== undefined && this.message.tokensBefore > 0\n\t\t\t\t? `Compacted ${beforeStr} → ${after.toLocaleString()} tokens (saved ${formatSavings(this.message.tokensBefore, after)})`\n\t\t\t\t: `Compacted from ${beforeStr} tokens`;\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[compaction]\\x1b[22m`);\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**${summaryText}**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", `${summaryText} (`) +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction formatSavings(before: number, after: number): string {\n\tconst saved = Math.max(0, before - after);\n\tconst pct = Math.round((saved / before) * 100);\n\treturn `${pct}%`;\n}\n"]}
1
+ {"version":3,"file":"compaction-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAChF,OAAO,EAAE,GAAG,EAAY,KAAK,aAAa,EAAgB,MAAM,0BAA0B,CAAC;AAI3F;;;GAGG;AACH,qBAAa,iCAAkC,SAAQ,GAAG;IACzD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,OAAO,EAAE,wBAAwB,EAAE,aAAa,GAAE,aAAkC,EAK/F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CAgCrB","sourcesContent":["import type { CompactionSummaryMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, messageLabel, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a compaction message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class CompactionSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst beforeStr = this.message.tokensBefore.toLocaleString();\n\t\tconst after = this.message.tokensAfter;\n\t\tconst summaryText =\n\t\t\tafter !== undefined && this.message.tokensBefore > 0\n\t\t\t\t? `Compacted ${beforeStr} → ${after.toLocaleString()} tokens (saved ${formatSavings(this.message.tokensBefore, after)})`\n\t\t\t\t: `Compacted from ${beforeStr} tokens`;\n\t\tconst label = messageLabel(\"compaction\");\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**${summaryText}**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", `${summaryText} (`) +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction formatSavings(before: number, after: number): string {\n\tconst saved = Math.max(0, before - after);\n\tconst pct = Math.round((saved / before) * 100);\n\treturn `${pct}%`;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { Box, Markdown, Spacer, Text } from "@kolisachint/hoocode-tui";
2
- import { getMarkdownTheme, theme } from "../theme/theme.js";
2
+ import { getMarkdownTheme, messageLabel, theme } from "../theme/theme.js";
3
3
  import { keyText } from "./keybinding-hints.js";
4
4
  /**
5
5
  * Component that renders a compaction message with collapsed/expanded state.
@@ -30,7 +30,7 @@ export class CompactionSummaryMessageComponent extends Box {
30
30
  const summaryText = after !== undefined && this.message.tokensBefore > 0
31
31
  ? `Compacted ${beforeStr} → ${after.toLocaleString()} tokens (saved ${formatSavings(this.message.tokensBefore, after)})`
32
32
  : `Compacted from ${beforeStr} tokens`;
33
- const label = theme.fg("customMessageLabel", `\x1b[1m[compaction]\x1b[22m`);
33
+ const label = messageLabel("compaction");
34
34
  this.addChild(new Text(label, 0, 0));
35
35
  this.addChild(new Spacer(1));
36
36
  if (this.expanded) {
@@ -1 +1 @@
1
- {"version":3,"file":"compaction-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CompactionSummaryMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a compaction message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class CompactionSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst beforeStr = this.message.tokensBefore.toLocaleString();\n\t\tconst after = this.message.tokensAfter;\n\t\tconst summaryText =\n\t\t\tafter !== undefined && this.message.tokensBefore > 0\n\t\t\t\t? `Compacted ${beforeStr} → ${after.toLocaleString()} tokens (saved ${formatSavings(this.message.tokensBefore, after)})`\n\t\t\t\t: `Compacted from ${beforeStr} tokens`;\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[compaction]\\x1b[22m`);\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**${summaryText}**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", `${summaryText} (`) +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction formatSavings(before: number, after: number): string {\n\tconst saved = Math.max(0, before - after);\n\tconst pct = Math.round((saved / before) * 100);\n\treturn `${pct}%`;\n}\n"]}
1
+ {"version":3,"file":"compaction-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAC3F,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD;;;GAGG;AACH,MAAM,OAAO,iCAAkC,SAAQ,GAAG;IACjD,QAAQ,GAAG,KAAK,CAAC;IACjB,OAAO,CAA2B;IAClC,aAAa,CAAgB;IAErC,YAAY,OAAiC,EAAE,aAAa,GAAkB,gBAAgB,EAAE,EAAE;QACjG,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAED,WAAW,CAAC,QAAiB,EAAQ;QACpC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEQ,UAAU,GAAS;QAC3B,KAAK,CAAC,UAAU,EAAE,CAAC;QACnB,IAAI,CAAC,aAAa,EAAE,CAAC;IAAA,CACrB;IAEO,aAAa,GAAS;QAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;QAEb,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC;QAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;QACvC,MAAM,WAAW,GAChB,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY,GAAG,CAAC;YACnD,CAAC,CAAC,aAAa,SAAS,QAAM,KAAK,CAAC,cAAc,EAAE,kBAAkB,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,KAAK,CAAC,GAAG;YACxH,CAAC,CAAC,kBAAkB,SAAS,SAAS,CAAC;QACzC,MAAM,KAAK,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QACzC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,KAAK,WAAW,QAAQ,CAAC;YACxC,IAAI,CAAC,QAAQ,CACZ,IAAI,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE;gBACrE,KAAK,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,IAAI,CAAC;aAC5D,CAAC,CACF,CAAC;QACH,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,QAAQ,CACZ,IAAI,IAAI,CACP,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,GAAG,WAAW,IAAI,CAAC;gBAChD,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;gBAC5C,KAAK,CAAC,EAAE,CAAC,mBAAmB,EAAE,aAAa,CAAC,EAC7C,CAAC,EACD,CAAC,CACD,CACD,CAAC;QACH,CAAC;IAAA,CACD;CACD;AAED,SAAS,aAAa,CAAC,MAAc,EAAE,KAAa,EAAU;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC;IAC/C,OAAO,GAAG,GAAG,GAAG,CAAC;AAAA,CACjB","sourcesContent":["import type { CompactionSummaryMessage } from \"@kolisachint/hoocode-agent-core\";\nimport { Box, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport { getMarkdownTheme, messageLabel, theme } from \"../theme/theme.js\";\nimport { keyText } from \"./keybinding-hints.js\";\n\n/**\n * Component that renders a compaction message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class CompactionSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\n\t\tconst beforeStr = this.message.tokensBefore.toLocaleString();\n\t\tconst after = this.message.tokensAfter;\n\t\tconst summaryText =\n\t\t\tafter !== undefined && this.message.tokensBefore > 0\n\t\t\t\t? `Compacted ${beforeStr} → ${after.toLocaleString()} tokens (saved ${formatSavings(this.message.tokensBefore, after)})`\n\t\t\t\t: `Compacted from ${beforeStr} tokens`;\n\t\tconst label = messageLabel(\"compaction\");\n\t\tthis.addChild(new Text(label, 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**${summaryText}**\\n\\n`;\n\t\t\tthis.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tthis.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", `${summaryText} (`) +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n}\n\nfunction formatSavings(before: number, after: number): string {\n\tconst saved = Math.max(0, before - after);\n\tconst pct = Math.round((saved / before) * 100);\n\treturn `${pct}%`;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"custom-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,EAAO,SAAS,EAAY,KAAK,aAAa,EAAgB,MAAM,0BAA0B,CAAC;AACtG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAGzE;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,cAAc,CAAC,CAAkB;IACzC,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,eAAe,CAAC,CAAY;IACpC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,SAAS,CAAS;IAE1B,YACC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,EAC/B,cAAc,CAAC,EAAE,eAAe,EAChC,aAAa,GAAE,aAAkC,EAajD;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAKnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,OAAO;CAiDf","sourcesContent":["import type { CustomMessage } from \"@kolisachint/hoocode-agent-core\";\nimport type { TextContent } from \"@kolisachint/hoocode-ai\";\nimport type { Component } from \"@kolisachint/hoocode-tui\";\nimport { Box, Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport type { MessageRenderer } from \"../../../core/extensions/types.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\n\n/**\n * Component that renders a custom message entry from extensions.\n * Uses distinct styling to differentiate from user messages.\n */\nexport class CustomMessageComponent extends Container {\n\tprivate message: CustomMessage<unknown>;\n\tprivate customRenderer?: MessageRenderer;\n\tprivate box: Box;\n\tprivate customComponent?: Component;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate _expanded = false;\n\n\tconstructor(\n\t\tmessage: CustomMessage<unknown>,\n\t\tcustomRenderer?: MessageRenderer,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t) {\n\t\tsuper();\n\t\tthis.message = message;\n\t\tthis.customRenderer = customRenderer;\n\t\tthis.markdownTheme = markdownTheme;\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create box with purple background (used for default rendering)\n\t\tthis.box = new Box(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\n\t\tthis.rebuild();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tif (this._expanded !== expanded) {\n\t\t\tthis._expanded = expanded;\n\t\t\tthis.rebuild();\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.rebuild();\n\t}\n\n\tprivate rebuild(): void {\n\t\t// Remove previous content component\n\t\tif (this.customComponent) {\n\t\t\tthis.removeChild(this.customComponent);\n\t\t\tthis.customComponent = undefined;\n\t\t}\n\t\tthis.removeChild(this.box);\n\n\t\t// Try custom renderer first - it handles its own styling\n\t\tif (this.customRenderer) {\n\t\t\ttry {\n\t\t\t\tconst component = this.customRenderer(this.message, { expanded: this._expanded }, theme);\n\t\t\t\tif (component) {\n\t\t\t\t\t// Custom renderer provides its own styled component\n\t\t\t\t\tthis.customComponent = component;\n\t\t\t\t\tthis.addChild(component);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Fall through to default rendering\n\t\t\t}\n\t\t}\n\n\t\t// Default rendering uses our box\n\t\tthis.addChild(this.box);\n\t\tthis.box.clear();\n\n\t\t// Default rendering: label + content\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[${this.message.customType}]\\x1b[22m`);\n\t\tthis.box.addChild(new Text(label, 0, 0));\n\t\tthis.box.addChild(new Spacer(1));\n\n\t\t// Extract text content\n\t\tlet text: string;\n\t\tif (typeof this.message.content === \"string\") {\n\t\t\ttext = this.message.content;\n\t\t} else {\n\t\t\ttext = this.message.content\n\t\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\\n\");\n\t\t}\n\n\t\tthis.box.addChild(\n\t\t\tnew Markdown(text, 0, 0, this.markdownTheme, {\n\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t}),\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"custom-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,EAAO,SAAS,EAAY,KAAK,aAAa,EAAgB,MAAM,0BAA0B,CAAC;AACtG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAGzE;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,cAAc,CAAC,CAAkB;IACzC,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,eAAe,CAAC,CAAY;IACpC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,SAAS,CAAS;IAE1B,YACC,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,EAC/B,cAAc,CAAC,EAAE,eAAe,EAChC,aAAa,GAAE,aAAkC,EAcjD;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAKnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,OAAO;CAiDf","sourcesContent":["import type { CustomMessage } from \"@kolisachint/hoocode-agent-core\";\nimport type { TextContent } from \"@kolisachint/hoocode-ai\";\nimport type { Component } from \"@kolisachint/hoocode-tui\";\nimport { Box, Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport type { MessageRenderer } from \"../../../core/extensions/types.js\";\nimport { getMarkdownTheme, getPaperShadowFn, messageLabel, theme } from \"../theme/theme.js\";\n\n/**\n * Component that renders a custom message entry from extensions.\n * Uses distinct styling to differentiate from user messages.\n */\nexport class CustomMessageComponent extends Container {\n\tprivate message: CustomMessage<unknown>;\n\tprivate customRenderer?: MessageRenderer;\n\tprivate box: Box;\n\tprivate customComponent?: Component;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate _expanded = false;\n\n\tconstructor(\n\t\tmessage: CustomMessage<unknown>,\n\t\tcustomRenderer?: MessageRenderer,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t) {\n\t\tsuper();\n\t\tthis.message = message;\n\t\tthis.customRenderer = customRenderer;\n\t\tthis.markdownTheme = markdownTheme;\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create box with purple background (used for default rendering)\n\t\tthis.box = new Box(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.box.setShadowFn(getPaperShadowFn());\n\n\t\tthis.rebuild();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tif (this._expanded !== expanded) {\n\t\t\tthis._expanded = expanded;\n\t\t\tthis.rebuild();\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.rebuild();\n\t}\n\n\tprivate rebuild(): void {\n\t\t// Remove previous content component\n\t\tif (this.customComponent) {\n\t\t\tthis.removeChild(this.customComponent);\n\t\t\tthis.customComponent = undefined;\n\t\t}\n\t\tthis.removeChild(this.box);\n\n\t\t// Try custom renderer first - it handles its own styling\n\t\tif (this.customRenderer) {\n\t\t\ttry {\n\t\t\t\tconst component = this.customRenderer(this.message, { expanded: this._expanded }, theme);\n\t\t\t\tif (component) {\n\t\t\t\t\t// Custom renderer provides its own styled component\n\t\t\t\t\tthis.customComponent = component;\n\t\t\t\t\tthis.addChild(component);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Fall through to default rendering\n\t\t\t}\n\t\t}\n\n\t\t// Default rendering uses our box\n\t\tthis.addChild(this.box);\n\t\tthis.box.clear();\n\n\t\t// Default rendering: label + content\n\t\tconst label = messageLabel(this.message.customType);\n\t\tthis.box.addChild(new Text(label, 0, 0));\n\t\tthis.box.addChild(new Spacer(1));\n\n\t\t// Extract text content\n\t\tlet text: string;\n\t\tif (typeof this.message.content === \"string\") {\n\t\t\ttext = this.message.content;\n\t\t} else {\n\t\t\ttext = this.message.content\n\t\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\\n\");\n\t\t}\n\n\t\tthis.box.addChild(\n\t\t\tnew Markdown(text, 0, 0, this.markdownTheme, {\n\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t}),\n\t\t);\n\t}\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { Box, Container, Markdown, Spacer, Text } from "@kolisachint/hoocode-tui";
2
- import { getMarkdownTheme, theme } from "../theme/theme.js";
2
+ import { getMarkdownTheme, getPaperShadowFn, messageLabel, theme } from "../theme/theme.js";
3
3
  /**
4
4
  * Component that renders a custom message entry from extensions.
5
5
  * Uses distinct styling to differentiate from user messages.
@@ -19,6 +19,7 @@ export class CustomMessageComponent extends Container {
19
19
  this.addChild(new Spacer(1));
20
20
  // Create box with purple background (used for default rendering)
21
21
  this.box = new Box(1, 1, (t) => theme.bg("customMessageBg", t));
22
+ this.box.setShadowFn(getPaperShadowFn());
22
23
  this.rebuild();
23
24
  }
24
25
  setExpanded(expanded) {
@@ -57,7 +58,7 @@ export class CustomMessageComponent extends Container {
57
58
  this.addChild(this.box);
58
59
  this.box.clear();
59
60
  // Default rendering: label + content
60
- const label = theme.fg("customMessageLabel", `\x1b[1m[${this.message.customType}]\x1b[22m`);
61
+ const label = messageLabel(this.message.customType);
61
62
  this.box.addChild(new Text(label, 0, 0));
62
63
  this.box.addChild(new Spacer(1));
63
64
  // Extract text content
@@ -1 +1 @@
1
- {"version":3,"file":"custom-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CustomMessage } from \"@kolisachint/hoocode-agent-core\";\nimport type { TextContent } from \"@kolisachint/hoocode-ai\";\nimport type { Component } from \"@kolisachint/hoocode-tui\";\nimport { Box, Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport type { MessageRenderer } from \"../../../core/extensions/types.js\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.js\";\n\n/**\n * Component that renders a custom message entry from extensions.\n * Uses distinct styling to differentiate from user messages.\n */\nexport class CustomMessageComponent extends Container {\n\tprivate message: CustomMessage<unknown>;\n\tprivate customRenderer?: MessageRenderer;\n\tprivate box: Box;\n\tprivate customComponent?: Component;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate _expanded = false;\n\n\tconstructor(\n\t\tmessage: CustomMessage<unknown>,\n\t\tcustomRenderer?: MessageRenderer,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t) {\n\t\tsuper();\n\t\tthis.message = message;\n\t\tthis.customRenderer = customRenderer;\n\t\tthis.markdownTheme = markdownTheme;\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create box with purple background (used for default rendering)\n\t\tthis.box = new Box(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\n\t\tthis.rebuild();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tif (this._expanded !== expanded) {\n\t\t\tthis._expanded = expanded;\n\t\t\tthis.rebuild();\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.rebuild();\n\t}\n\n\tprivate rebuild(): void {\n\t\t// Remove previous content component\n\t\tif (this.customComponent) {\n\t\t\tthis.removeChild(this.customComponent);\n\t\t\tthis.customComponent = undefined;\n\t\t}\n\t\tthis.removeChild(this.box);\n\n\t\t// Try custom renderer first - it handles its own styling\n\t\tif (this.customRenderer) {\n\t\t\ttry {\n\t\t\t\tconst component = this.customRenderer(this.message, { expanded: this._expanded }, theme);\n\t\t\t\tif (component) {\n\t\t\t\t\t// Custom renderer provides its own styled component\n\t\t\t\t\tthis.customComponent = component;\n\t\t\t\t\tthis.addChild(component);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Fall through to default rendering\n\t\t\t}\n\t\t}\n\n\t\t// Default rendering uses our box\n\t\tthis.addChild(this.box);\n\t\tthis.box.clear();\n\n\t\t// Default rendering: label + content\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[${this.message.customType}]\\x1b[22m`);\n\t\tthis.box.addChild(new Text(label, 0, 0));\n\t\tthis.box.addChild(new Spacer(1));\n\n\t\t// Extract text content\n\t\tlet text: string;\n\t\tif (typeof this.message.content === \"string\") {\n\t\t\ttext = this.message.content;\n\t\t} else {\n\t\t\ttext = this.message.content\n\t\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\\n\");\n\t\t}\n\n\t\tthis.box.addChild(\n\t\t\tnew Markdown(text, 0, 0, this.markdownTheme, {\n\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t}),\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"custom-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CustomMessage } from \"@kolisachint/hoocode-agent-core\";\nimport type { TextContent } from \"@kolisachint/hoocode-ai\";\nimport type { Component } from \"@kolisachint/hoocode-tui\";\nimport { Box, Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@kolisachint/hoocode-tui\";\nimport type { MessageRenderer } from \"../../../core/extensions/types.js\";\nimport { getMarkdownTheme, getPaperShadowFn, messageLabel, theme } from \"../theme/theme.js\";\n\n/**\n * Component that renders a custom message entry from extensions.\n * Uses distinct styling to differentiate from user messages.\n */\nexport class CustomMessageComponent extends Container {\n\tprivate message: CustomMessage<unknown>;\n\tprivate customRenderer?: MessageRenderer;\n\tprivate box: Box;\n\tprivate customComponent?: Component;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate _expanded = false;\n\n\tconstructor(\n\t\tmessage: CustomMessage<unknown>,\n\t\tcustomRenderer?: MessageRenderer,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t) {\n\t\tsuper();\n\t\tthis.message = message;\n\t\tthis.customRenderer = customRenderer;\n\t\tthis.markdownTheme = markdownTheme;\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create box with purple background (used for default rendering)\n\t\tthis.box = new Box(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.box.setShadowFn(getPaperShadowFn());\n\n\t\tthis.rebuild();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tif (this._expanded !== expanded) {\n\t\t\tthis._expanded = expanded;\n\t\t\tthis.rebuild();\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.rebuild();\n\t}\n\n\tprivate rebuild(): void {\n\t\t// Remove previous content component\n\t\tif (this.customComponent) {\n\t\t\tthis.removeChild(this.customComponent);\n\t\t\tthis.customComponent = undefined;\n\t\t}\n\t\tthis.removeChild(this.box);\n\n\t\t// Try custom renderer first - it handles its own styling\n\t\tif (this.customRenderer) {\n\t\t\ttry {\n\t\t\t\tconst component = this.customRenderer(this.message, { expanded: this._expanded }, theme);\n\t\t\t\tif (component) {\n\t\t\t\t\t// Custom renderer provides its own styled component\n\t\t\t\t\tthis.customComponent = component;\n\t\t\t\t\tthis.addChild(component);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Fall through to default rendering\n\t\t\t}\n\t\t}\n\n\t\t// Default rendering uses our box\n\t\tthis.addChild(this.box);\n\t\tthis.box.clear();\n\n\t\t// Default rendering: label + content\n\t\tconst label = messageLabel(this.message.customType);\n\t\tthis.box.addChild(new Text(label, 0, 0));\n\t\tthis.box.addChild(new Spacer(1));\n\n\t\t// Extract text content\n\t\tlet text: string;\n\t\tif (typeof this.message.content === \"string\") {\n\t\t\ttext = this.message.content;\n\t\t} else {\n\t\t\ttext = this.message.content\n\t\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t\t.map((c) => c.text)\n\t\t\t\t.join(\"\\n\");\n\t\t}\n\n\t\tthis.box.addChild(\n\t\t\tnew Markdown(text, 0, 0, this.markdownTheme, {\n\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t}),\n\t\t);\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,0BAA0B,CAAC;AACzF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAIxF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AA4ExE;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAK/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IALnB,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,cAAc,CAA4B;IAElD,YACS,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC3C;IAEJ,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED,4EAA4E;IAC5E,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAE5C;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAqK9B;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport { sumAssistantUsage } from \"../../../core/agent-session-stats.js\";\nimport { BRAND_MARK, GIT_BRANCH_GLYPH } from \"../../../core/brand.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { formatTokens } from \"../../../core/format-tokens.js\";\nimport { type StartupProgress, startupProgress } from \"../../../core/startup-progress.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport type { ToolOutputView } from \"../../../core/tool-output-view.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { renderDownloadProgress, renderProgressBar } from \"./progress-bar.js\";\n\n/**\n * Assemble one footer line: `left` flush left, `right` flush right when it fits\n * (≥2 cols between), padded to the full width. When it doesn't fit, drop `right`\n * and pad; when even `left` overflows, truncate it. Width math runs on the plain\n * strings; the styled strings carry the colour. Every returned line is exactly\n * `width` cells or fewer — the invariant the footer-width tests hold us to.\n */\nfunction assembleLine(width: number, leftPlain: string, leftStyled: string, rightPlain = \"\", rightStyled = \"\"): string {\n\tconst lw = visibleWidth(leftPlain);\n\tif (rightPlain && lw + 2 + visibleWidth(rightPlain) <= width) {\n\t\treturn leftStyled + \" \".repeat(width - lw - visibleWidth(rightPlain)) + rightStyled;\n\t}\n\tif (lw <= width) return leftStyled + \" \".repeat(width - lw);\n\treturn truncateToWidth(leftStyled, width, theme.fg(\"dim\", \"…\"));\n}\n\n/** A compact context-fill gauge, coloured by proximity to the auto-compact trip point. */\nfunction contextGauge(percent: number, errorLevel: number, warnLevel: number): { plain: string; styled: string } {\n\tconst CELLS = 8;\n\tconst filled = Math.max(0, Math.min(CELLS, Math.round((percent / 100) * CELLS)));\n\tconst fill = \"▰\".repeat(filled);\n\tconst track = \"▱\".repeat(CELLS - filled);\n\tconst color = percent >= errorLevel ? \"error\" : percent >= warnLevel ? \"warning\" : \"accent\";\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"dim\", track) };\n}\n\n/**\n * The view dial's glyph, filling up as the view widens — so the footer shows\n * where the dial sits on its three-stop scale, not just its name.\n */\nconst TOOL_OUTPUT_VIEW_GLYPHS: Record<ToolOutputView, string> = {\n\tradar: \"◌\",\n\tglance: \"◍\",\n\tfull: \"◉\",\n};\n\n/** Count subagent runs currently in flight, for the footer's live delegation cue. */\nfunction activeSubagentCount(): number {\n\treturn taskStore.list().filter((t) => t.source === \"subagent\" && t.status === \"in_progress\").length;\n}\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * One footer line for a transient progress entry (tool download, index build,\n * `/learn` reading transcripts). The bar comes from the shared progress-bar\n * component, so this line and the voice panel's cannot drift apart again. An\n * error entry renders as a dim message instead. Returns a styled string; the\n * caller width-clamps it.\n */\nfunction renderStartupLine(entry: StartupProgress): string {\n\tif (entry.kind === \"error\") {\n\t\treturn theme.fg(\"dim\", `${entry.label}: ${entry.message}`);\n\t}\n\tconst label = theme.fg(\"text\", entry.label);\n\tif (entry.kind === \"download\") {\n\t\treturn `${label} ${renderDownloadProgress(entry.receivedBytes, entry.totalBytes)}`;\n\t}\n\tconst ratio = entry.total > 0 ? entry.done / entry.total : 0;\n\treturn `${label} ${renderProgressBar(ratio, `${entry.done}/${entry.total} ${entry.unit}`)}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate toolOutputView: ToolOutputView = \"glance\";\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/** The view dial's current stop, shown so `alt+o` has somewhere to land. */\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.toolOutputView = view;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Cumulative usage across ALL session entries (not just post-compaction\n\t\t// messages). Shares sumAssistantUsage with the per-request cost line the\n\t\t// transcript prints at agent_end, so the footer total and that line are always\n\t\t// derived from the same accounting.\n\t\tconst {\n\t\t\tinput: totalInput,\n\t\t\toutput: totalOutput,\n\t\t\tcacheRead: totalCacheRead,\n\t\t\tcacheWrite: totalCacheWrite,\n\t\t\tcost: totalCost,\n\t\t} = sumAssistantUsage(this.session.sessionManager.getEntries());\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tconst modeLabel = this.footerData.getActiveMode();\n\n\t\t// ── Line 1 — identity & location ────────────────────────────────────────\n\t\t// Lead with the brand mark + MODE (the agent's guardrail: Ask/Plan/Build/\n\t\t// Debug) in bold accent so it is the first thing the eye lands on, then the\n\t\t// path, git branch, and session name in descending emphasis. The live\n\t\t// subagent count sits flush right — present only while work is delegated.\n\t\tconst modeUp = modeLabel.toUpperCase();\n\t\t// Themes that define the brandBg/brandText pair paint the mark as a filled\n\t\t// chip; the padding lives in the plain string too so width math and\n\t\t// truncation stay honest. Themes without the pair keep accent-coloured text.\n\t\tconst chip = theme.hasBg(\"brandBg\") && theme.has(\"brandText\");\n\t\tconst brand = chip ? ` ${BRAND_MARK} ${modeUp} ` : `${BRAND_MARK} ${modeUp}`;\n\t\tlet l1Plain = `${brand} ${pwd}`;\n\t\tconst brandStyled = chip\n\t\t\t? theme.bg(\"brandBg\", theme.bold(theme.fg(\"brandText\", brand)))\n\t\t\t: theme.bold(theme.fg(\"accent\", brand));\n\t\tlet l1Styled = `${brandStyled} ${theme.fg(\"muted\", pwd)}`;\n\t\tif (branch) {\n\t\t\tl1Plain += ` ${GIT_BRANCH_GLYPH} ${branch}`;\n\t\t\tl1Styled += ` ${theme.fg(\"dim\", GIT_BRANCH_GLYPH)} ${theme.fg(\"muted\", branch)}`;\n\t\t}\n\t\tif (sessionName) {\n\t\t\tl1Plain += ` • ${sessionName}`;\n\t\t\tl1Styled += theme.fg(\"dim\", ` • ${sessionName}`);\n\t\t}\n\t\tconst nSub = activeSubagentCount();\n\t\t// Right cluster: the view dial always, the live subagent count when there\n\t\t// is one. The dial's glyph fills up as the view widens, so its position on\n\t\t// the three-stop scale reads without parsing the word.\n\t\tconst viewGlyph = TOOL_OUTPUT_VIEW_GLYPHS[this.toolOutputView];\n\t\tconst rightParts: Array<{ plain: string; styled: string }> = [];\n\t\tif (nSub > 0) {\n\t\t\trightParts.push({\n\t\t\t\tplain: `◇${nSub} running`,\n\t\t\t\tstyled: theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\"),\n\t\t\t});\n\t\t}\n\t\trightParts.push({\n\t\t\tplain: `${viewGlyph} ${this.toolOutputView}`,\n\t\t\tstyled: theme.fg(\"dim\", `${viewGlyph} ${this.toolOutputView}`),\n\t\t});\n\t\tconst l1RightPlain = rightParts.map((p) => p.plain).join(\" \");\n\t\tconst l1RightStyled = rightParts.map((p) => p.styled).join(\" \");\n\t\tconst line1 = assembleLine(width, l1Plain, l1Styled, l1RightPlain, l1RightStyled);\n\n\t\t// ── Line 2 — session vitals ─────────────────────────────────────────────\n\t\t// A context-fill gauge (coloured by proximity to the auto-compact trip\n\t\t// point) leads, then token/cost deltas, with the model + thinking level\n\t\t// flush right. Numbers read in muted, labels/arrows in dim — a legible\n\t\t// hierarchy in place of the old uniform grey.\n\t\tlet thresholdPercent: number | undefined;\n\t\tif (this.autoCompactEnabled && contextWindow > 0) {\n\t\t\tconst reserveTokens = this.session.settingsManager.getCompactionSettings().reserveTokens;\n\t\t\tconst effective = contextWindow - reserveTokens;\n\t\t\tif (effective > 0) thresholdPercent = (effective / contextWindow) * 100;\n\t\t}\n\t\tconst errorLevel = thresholdPercent !== undefined ? thresholdPercent - 3 : 90;\n\t\tconst warnLevel = thresholdPercent !== undefined ? thresholdPercent - 10 : 70;\n\t\tconst autoIndicator =\n\t\t\tthresholdPercent !== undefined\n\t\t\t\t? ` auto@${thresholdPercent.toFixed(0)}%`\n\t\t\t\t: this.autoCompactEnabled\n\t\t\t\t\t? \" auto\"\n\t\t\t\t\t: \"\";\n\n\t\tconst gauge = contextGauge(contextPercentValue, errorLevel, warnLevel);\n\t\tconst pctText = contextPercent === \"?\" ? \"?\" : `${contextPercent}%`;\n\t\tconst pctColor =\n\t\t\tcontextPercentValue >= errorLevel ? \"error\" : contextPercentValue >= warnLevel ? \"warning\" : \"muted\";\n\t\tconst winText = `${formatTokens(contextWindow)}${autoIndicator}`;\n\n\t\tconst segs: Array<{ plain: string; styled: string }> = [\n\t\t\t{\n\t\t\t\tplain: `${gauge.plain} ${pctText} ${winText}`,\n\t\t\t\tstyled: `${gauge.styled} ${theme.fg(pctColor, pctText)} ${theme.fg(\"dim\", winText)}`,\n\t\t\t},\n\t\t];\n\t\tconst arrow = (a: string, n: number) => ({\n\t\t\tplain: `${a}${formatTokens(n)}`,\n\t\t\tstyled: theme.fg(\"dim\", a) + theme.fg(\"muted\", formatTokens(n)),\n\t\t});\n\t\tif (totalInput) segs.push(arrow(\"↑\", totalInput));\n\t\tif (totalOutput) segs.push(arrow(\"↓\", totalOutput));\n\t\tif (totalCacheRead) segs.push(arrow(\"R\", totalCacheRead));\n\t\tif (totalCacheWrite) segs.push(arrow(\"W\", totalCacheWrite));\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tsegs.push({ plain: costStr, styled: theme.fg(\"muted\", costStr) });\n\t\t}\n\t\tconst l2Plain = segs.map((s) => s.plain).join(\" \");\n\t\tconst l2Styled = segs.map((s) => s.styled).join(\" \");\n\n\t\t// Right: model, thinking level, and provider (when several are configured).\n\t\tconst modelName = state.model?.id || \"no-model\";\n\t\tlet r2Plain = modelName;\n\t\tlet r2Styled = theme.fg(\"muted\", modelName);\n\t\tif (state.model?.reasoning) {\n\t\t\tconst tl = state.thinkingLevel || \"off\";\n\t\t\tconst tstr = tl === \"off\" ? \"thinking off\" : tl;\n\t\t\tr2Plain += ` • ${tstr}`;\n\t\t\tr2Styled += theme.fg(\"dim\", ` • ${tstr}`);\n\t\t}\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\t// Prepend the provider only when the whole right cluster still fits.\n\t\t\tconst withProv = `(${state.model.provider}) ${r2Plain}`;\n\t\t\tif (visibleWidth(l2Plain) + 2 + visibleWidth(withProv) <= width) {\n\t\t\t\tr2Plain = withProv;\n\t\t\t\tr2Styled = theme.fg(\"dim\", `(${state.model.provider}) `) + r2Styled;\n\t\t\t}\n\t\t}\n\t\tconst line2 = assembleLine(width, l2Plain, l2Styled, r2Plain, r2Styled);\n\n\t\tconst lines = [line1, line2];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\t// Transient startup progress (first-run tool downloads, index build): one\n\t\t// determinate bar per entry, cleared as each settles. Width-clamped like the\n\t\t// status line so the footer never overflows.\n\t\tfor (const entry of startupProgress.list()) {\n\t\t\tlines.push(truncateToWidth(renderStartupLine(entry), width, theme.fg(\"dim\", \"…\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
1
+ {"version":3,"file":"footer.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,0BAA0B,CAAC;AACzF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,uCAAuC,CAAC;AAIxF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAgFxE;;;GAGG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAK/C,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,UAAU;IALnB,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,cAAc,CAA4B;IAElD,YACS,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,0BAA0B,EAC3C;IAEJ,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAEtC;IAED,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE5C;IAED,4EAA4E;IAC5E,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAE5C;IAED;;;OAGG;IACH,UAAU,IAAI,IAAI,CAEjB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI,CAEd;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAqK9B;CACD","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport { sumAssistantUsage } from \"../../../core/agent-session-stats.js\";\nimport { BRAND_MARK, GIT_BRANCH_GLYPH } from \"../../../core/brand.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { formatTokens } from \"../../../core/format-tokens.js\";\nimport { type StartupProgress, startupProgress } from \"../../../core/startup-progress.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport type { ToolOutputView } from \"../../../core/tool-output-view.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { renderDownloadProgress, renderProgressBar } from \"./progress-bar.js\";\n\n/**\n * Assemble one footer line: `left` flush left, `right` flush right when it fits\n * (≥2 cols between), padded to the full width. When it doesn't fit, drop `right`\n * and pad; when even `left` overflows, truncate it. Width math runs on the plain\n * strings; the styled strings carry the colour. Every returned line is exactly\n * `width` cells or fewer — the invariant the footer-width tests hold us to.\n */\nfunction assembleLine(width: number, leftPlain: string, leftStyled: string, rightPlain = \"\", rightStyled = \"\"): string {\n\tconst lw = visibleWidth(leftPlain);\n\tif (rightPlain && lw + 2 + visibleWidth(rightPlain) <= width) {\n\t\treturn leftStyled + \" \".repeat(width - lw - visibleWidth(rightPlain)) + rightStyled;\n\t}\n\tif (lw <= width) return leftStyled + \" \".repeat(width - lw);\n\treturn truncateToWidth(leftStyled, width, theme.fg(\"dim\", \"…\"));\n}\n\n/** A compact context-fill gauge, coloured by proximity to the auto-compact trip point. */\nfunction contextGauge(percent: number, errorLevel: number, warnLevel: number): { plain: string; styled: string } {\n\tconst CELLS = 8;\n\tconst filled = Math.max(0, Math.min(CELLS, Math.round((percent / 100) * CELLS)));\n\tconst fill = \"▰\".repeat(filled);\n\tconst track = \"▱\".repeat(CELLS - filled);\n\tconst color = percent >= errorLevel ? \"error\" : percent >= warnLevel ? \"warning\" : \"accent\";\n\t// The unfilled track takes `halftone`, which falls back to `dim` — the tone it\n\t// used before the token existed. The point of separating them is that `dim` is\n\t// body-weight text: a track drawn in it reads as writing rather than as the\n\t// space the fill has yet to reach.\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"halftone\", track) };\n}\n\n/**\n * The view dial's glyph, filling up as the view widens — so the footer shows\n * where the dial sits on its three-stop scale, not just its name.\n */\nconst TOOL_OUTPUT_VIEW_GLYPHS: Record<ToolOutputView, string> = {\n\tradar: \"◌\",\n\tglance: \"◍\",\n\tfull: \"◉\",\n};\n\n/** Count subagent runs currently in flight, for the footer's live delegation cue. */\nfunction activeSubagentCount(): number {\n\treturn taskStore.list().filter((t) => t.source === \"subagent\" && t.status === \"in_progress\").length;\n}\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * One footer line for a transient progress entry (tool download, index build,\n * `/learn` reading transcripts). The bar comes from the shared progress-bar\n * component, so this line and the voice panel's cannot drift apart again. An\n * error entry renders as a dim message instead. Returns a styled string; the\n * caller width-clamps it.\n */\nfunction renderStartupLine(entry: StartupProgress): string {\n\tif (entry.kind === \"error\") {\n\t\treturn theme.fg(\"dim\", `${entry.label}: ${entry.message}`);\n\t}\n\tconst label = theme.fg(\"text\", entry.label);\n\tif (entry.kind === \"download\") {\n\t\treturn `${label} ${renderDownloadProgress(entry.receivedBytes, entry.totalBytes)}`;\n\t}\n\tconst ratio = entry.total > 0 ? entry.done / entry.total : 0;\n\treturn `${label} ${renderProgressBar(ratio, `${entry.done}/${entry.total} ${entry.unit}`)}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate toolOutputView: ToolOutputView = \"glance\";\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/** The view dial's current stop, shown so `alt+o` has somewhere to land. */\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.toolOutputView = view;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Cumulative usage across ALL session entries (not just post-compaction\n\t\t// messages). Shares sumAssistantUsage with the per-request cost line the\n\t\t// transcript prints at agent_end, so the footer total and that line are always\n\t\t// derived from the same accounting.\n\t\tconst {\n\t\t\tinput: totalInput,\n\t\t\toutput: totalOutput,\n\t\t\tcacheRead: totalCacheRead,\n\t\t\tcacheWrite: totalCacheWrite,\n\t\t\tcost: totalCost,\n\t\t} = sumAssistantUsage(this.session.sessionManager.getEntries());\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tconst modeLabel = this.footerData.getActiveMode();\n\n\t\t// ── Line 1 — identity & location ────────────────────────────────────────\n\t\t// Lead with the brand mark + MODE (the agent's guardrail: Ask/Plan/Build/\n\t\t// Debug) in bold accent so it is the first thing the eye lands on, then the\n\t\t// path, git branch, and session name in descending emphasis. The live\n\t\t// subagent count sits flush right — present only while work is delegated.\n\t\tconst modeUp = modeLabel.toUpperCase();\n\t\t// Themes that define the brandBg/brandText pair paint the mark as a filled\n\t\t// chip; the padding lives in the plain string too so width math and\n\t\t// truncation stay honest. Themes without the pair keep accent-coloured text.\n\t\tconst chip = theme.hasBg(\"brandBg\") && theme.has(\"brandText\");\n\t\tconst brand = chip ? ` ${BRAND_MARK} ${modeUp} ` : `${BRAND_MARK} ${modeUp}`;\n\t\tlet l1Plain = `${brand} ${pwd}`;\n\t\tconst brandStyled = chip\n\t\t\t? theme.bg(\"brandBg\", theme.bold(theme.fg(\"brandText\", brand)))\n\t\t\t: theme.bold(theme.fg(\"accent\", brand));\n\t\tlet l1Styled = `${brandStyled} ${theme.fg(\"muted\", pwd)}`;\n\t\tif (branch) {\n\t\t\tl1Plain += ` ${GIT_BRANCH_GLYPH} ${branch}`;\n\t\t\tl1Styled += ` ${theme.fg(\"dim\", GIT_BRANCH_GLYPH)} ${theme.fg(\"muted\", branch)}`;\n\t\t}\n\t\tif (sessionName) {\n\t\t\tl1Plain += ` • ${sessionName}`;\n\t\t\tl1Styled += theme.fg(\"dim\", ` • ${sessionName}`);\n\t\t}\n\t\tconst nSub = activeSubagentCount();\n\t\t// Right cluster: the view dial always, the live subagent count when there\n\t\t// is one. The dial's glyph fills up as the view widens, so its position on\n\t\t// the three-stop scale reads without parsing the word.\n\t\tconst viewGlyph = TOOL_OUTPUT_VIEW_GLYPHS[this.toolOutputView];\n\t\tconst rightParts: Array<{ plain: string; styled: string }> = [];\n\t\tif (nSub > 0) {\n\t\t\trightParts.push({\n\t\t\t\tplain: `◇${nSub} running`,\n\t\t\t\tstyled: theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\"),\n\t\t\t});\n\t\t}\n\t\trightParts.push({\n\t\t\tplain: `${viewGlyph} ${this.toolOutputView}`,\n\t\t\tstyled: theme.fg(\"dim\", `${viewGlyph} ${this.toolOutputView}`),\n\t\t});\n\t\tconst l1RightPlain = rightParts.map((p) => p.plain).join(\" \");\n\t\tconst l1RightStyled = rightParts.map((p) => p.styled).join(\" \");\n\t\tconst line1 = assembleLine(width, l1Plain, l1Styled, l1RightPlain, l1RightStyled);\n\n\t\t// ── Line 2 — session vitals ─────────────────────────────────────────────\n\t\t// A context-fill gauge (coloured by proximity to the auto-compact trip\n\t\t// point) leads, then token/cost deltas, with the model + thinking level\n\t\t// flush right. Numbers read in muted, labels/arrows in dim — a legible\n\t\t// hierarchy in place of the old uniform grey.\n\t\tlet thresholdPercent: number | undefined;\n\t\tif (this.autoCompactEnabled && contextWindow > 0) {\n\t\t\tconst reserveTokens = this.session.settingsManager.getCompactionSettings().reserveTokens;\n\t\t\tconst effective = contextWindow - reserveTokens;\n\t\t\tif (effective > 0) thresholdPercent = (effective / contextWindow) * 100;\n\t\t}\n\t\tconst errorLevel = thresholdPercent !== undefined ? thresholdPercent - 3 : 90;\n\t\tconst warnLevel = thresholdPercent !== undefined ? thresholdPercent - 10 : 70;\n\t\tconst autoIndicator =\n\t\t\tthresholdPercent !== undefined\n\t\t\t\t? ` auto@${thresholdPercent.toFixed(0)}%`\n\t\t\t\t: this.autoCompactEnabled\n\t\t\t\t\t? \" auto\"\n\t\t\t\t\t: \"\";\n\n\t\tconst gauge = contextGauge(contextPercentValue, errorLevel, warnLevel);\n\t\tconst pctText = contextPercent === \"?\" ? \"?\" : `${contextPercent}%`;\n\t\tconst pctColor =\n\t\t\tcontextPercentValue >= errorLevel ? \"error\" : contextPercentValue >= warnLevel ? \"warning\" : \"muted\";\n\t\tconst winText = `${formatTokens(contextWindow)}${autoIndicator}`;\n\n\t\tconst segs: Array<{ plain: string; styled: string }> = [\n\t\t\t{\n\t\t\t\tplain: `${gauge.plain} ${pctText} ${winText}`,\n\t\t\t\tstyled: `${gauge.styled} ${theme.fg(pctColor, pctText)} ${theme.fg(\"dim\", winText)}`,\n\t\t\t},\n\t\t];\n\t\tconst arrow = (a: string, n: number) => ({\n\t\t\tplain: `${a}${formatTokens(n)}`,\n\t\t\tstyled: theme.fg(\"dim\", a) + theme.fg(\"muted\", formatTokens(n)),\n\t\t});\n\t\tif (totalInput) segs.push(arrow(\"↑\", totalInput));\n\t\tif (totalOutput) segs.push(arrow(\"↓\", totalOutput));\n\t\tif (totalCacheRead) segs.push(arrow(\"R\", totalCacheRead));\n\t\tif (totalCacheWrite) segs.push(arrow(\"W\", totalCacheWrite));\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tsegs.push({ plain: costStr, styled: theme.fg(\"muted\", costStr) });\n\t\t}\n\t\tconst l2Plain = segs.map((s) => s.plain).join(\" \");\n\t\tconst l2Styled = segs.map((s) => s.styled).join(\" \");\n\n\t\t// Right: model, thinking level, and provider (when several are configured).\n\t\tconst modelName = state.model?.id || \"no-model\";\n\t\tlet r2Plain = modelName;\n\t\tlet r2Styled = theme.fg(\"muted\", modelName);\n\t\tif (state.model?.reasoning) {\n\t\t\tconst tl = state.thinkingLevel || \"off\";\n\t\t\tconst tstr = tl === \"off\" ? \"thinking off\" : tl;\n\t\t\tr2Plain += ` • ${tstr}`;\n\t\t\tr2Styled += theme.fg(\"dim\", ` • ${tstr}`);\n\t\t}\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\t// Prepend the provider only when the whole right cluster still fits.\n\t\t\tconst withProv = `(${state.model.provider}) ${r2Plain}`;\n\t\t\tif (visibleWidth(l2Plain) + 2 + visibleWidth(withProv) <= width) {\n\t\t\t\tr2Plain = withProv;\n\t\t\t\tr2Styled = theme.fg(\"dim\", `(${state.model.provider}) `) + r2Styled;\n\t\t\t}\n\t\t}\n\t\tconst line2 = assembleLine(width, l2Plain, l2Styled, r2Plain, r2Styled);\n\n\t\tconst lines = [line1, line2];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\t// Transient startup progress (first-run tool downloads, index build): one\n\t\t// determinate bar per entry, cleared as each settles. Width-clamped like the\n\t\t// status line so the footer never overflows.\n\t\tfor (const entry of startupProgress.list()) {\n\t\t\tlines.push(truncateToWidth(renderStartupLine(entry), width, theme.fg(\"dim\", \"…\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
@@ -29,7 +29,11 @@ function contextGauge(percent, errorLevel, warnLevel) {
29
29
  const fill = "▰".repeat(filled);
30
30
  const track = "▱".repeat(CELLS - filled);
31
31
  const color = percent >= errorLevel ? "error" : percent >= warnLevel ? "warning" : "accent";
32
- return { plain: fill + track, styled: theme.fg(color, fill) + theme.fg("dim", track) };
32
+ // The unfilled track takes `halftone`, which falls back to `dim` the tone it
33
+ // used before the token existed. The point of separating them is that `dim` is
34
+ // body-weight text: a track drawn in it reads as writing rather than as the
35
+ // space the fill has yet to reach.
36
+ return { plain: fill + track, styled: theme.fg(color, fill) + theme.fg("halftone", track) };
33
37
  }
34
38
  /**
35
39
  * The view dial's glyph, filling up as the view widens — so the footer shows
@@ -1 +1 @@
1
- {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport { sumAssistantUsage } from \"../../../core/agent-session-stats.js\";\nimport { BRAND_MARK, GIT_BRANCH_GLYPH } from \"../../../core/brand.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { formatTokens } from \"../../../core/format-tokens.js\";\nimport { type StartupProgress, startupProgress } from \"../../../core/startup-progress.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport type { ToolOutputView } from \"../../../core/tool-output-view.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { renderDownloadProgress, renderProgressBar } from \"./progress-bar.js\";\n\n/**\n * Assemble one footer line: `left` flush left, `right` flush right when it fits\n * (≥2 cols between), padded to the full width. When it doesn't fit, drop `right`\n * and pad; when even `left` overflows, truncate it. Width math runs on the plain\n * strings; the styled strings carry the colour. Every returned line is exactly\n * `width` cells or fewer — the invariant the footer-width tests hold us to.\n */\nfunction assembleLine(width: number, leftPlain: string, leftStyled: string, rightPlain = \"\", rightStyled = \"\"): string {\n\tconst lw = visibleWidth(leftPlain);\n\tif (rightPlain && lw + 2 + visibleWidth(rightPlain) <= width) {\n\t\treturn leftStyled + \" \".repeat(width - lw - visibleWidth(rightPlain)) + rightStyled;\n\t}\n\tif (lw <= width) return leftStyled + \" \".repeat(width - lw);\n\treturn truncateToWidth(leftStyled, width, theme.fg(\"dim\", \"…\"));\n}\n\n/** A compact context-fill gauge, coloured by proximity to the auto-compact trip point. */\nfunction contextGauge(percent: number, errorLevel: number, warnLevel: number): { plain: string; styled: string } {\n\tconst CELLS = 8;\n\tconst filled = Math.max(0, Math.min(CELLS, Math.round((percent / 100) * CELLS)));\n\tconst fill = \"▰\".repeat(filled);\n\tconst track = \"▱\".repeat(CELLS - filled);\n\tconst color = percent >= errorLevel ? \"error\" : percent >= warnLevel ? \"warning\" : \"accent\";\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"dim\", track) };\n}\n\n/**\n * The view dial's glyph, filling up as the view widens — so the footer shows\n * where the dial sits on its three-stop scale, not just its name.\n */\nconst TOOL_OUTPUT_VIEW_GLYPHS: Record<ToolOutputView, string> = {\n\tradar: \"◌\",\n\tglance: \"◍\",\n\tfull: \"◉\",\n};\n\n/** Count subagent runs currently in flight, for the footer's live delegation cue. */\nfunction activeSubagentCount(): number {\n\treturn taskStore.list().filter((t) => t.source === \"subagent\" && t.status === \"in_progress\").length;\n}\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * One footer line for a transient progress entry (tool download, index build,\n * `/learn` reading transcripts). The bar comes from the shared progress-bar\n * component, so this line and the voice panel's cannot drift apart again. An\n * error entry renders as a dim message instead. Returns a styled string; the\n * caller width-clamps it.\n */\nfunction renderStartupLine(entry: StartupProgress): string {\n\tif (entry.kind === \"error\") {\n\t\treturn theme.fg(\"dim\", `${entry.label}: ${entry.message}`);\n\t}\n\tconst label = theme.fg(\"text\", entry.label);\n\tif (entry.kind === \"download\") {\n\t\treturn `${label} ${renderDownloadProgress(entry.receivedBytes, entry.totalBytes)}`;\n\t}\n\tconst ratio = entry.total > 0 ? entry.done / entry.total : 0;\n\treturn `${label} ${renderProgressBar(ratio, `${entry.done}/${entry.total} ${entry.unit}`)}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate toolOutputView: ToolOutputView = \"glance\";\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/** The view dial's current stop, shown so `alt+o` has somewhere to land. */\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.toolOutputView = view;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Cumulative usage across ALL session entries (not just post-compaction\n\t\t// messages). Shares sumAssistantUsage with the per-request cost line the\n\t\t// transcript prints at agent_end, so the footer total and that line are always\n\t\t// derived from the same accounting.\n\t\tconst {\n\t\t\tinput: totalInput,\n\t\t\toutput: totalOutput,\n\t\t\tcacheRead: totalCacheRead,\n\t\t\tcacheWrite: totalCacheWrite,\n\t\t\tcost: totalCost,\n\t\t} = sumAssistantUsage(this.session.sessionManager.getEntries());\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tconst modeLabel = this.footerData.getActiveMode();\n\n\t\t// ── Line 1 — identity & location ────────────────────────────────────────\n\t\t// Lead with the brand mark + MODE (the agent's guardrail: Ask/Plan/Build/\n\t\t// Debug) in bold accent so it is the first thing the eye lands on, then the\n\t\t// path, git branch, and session name in descending emphasis. The live\n\t\t// subagent count sits flush right — present only while work is delegated.\n\t\tconst modeUp = modeLabel.toUpperCase();\n\t\t// Themes that define the brandBg/brandText pair paint the mark as a filled\n\t\t// chip; the padding lives in the plain string too so width math and\n\t\t// truncation stay honest. Themes without the pair keep accent-coloured text.\n\t\tconst chip = theme.hasBg(\"brandBg\") && theme.has(\"brandText\");\n\t\tconst brand = chip ? ` ${BRAND_MARK} ${modeUp} ` : `${BRAND_MARK} ${modeUp}`;\n\t\tlet l1Plain = `${brand} ${pwd}`;\n\t\tconst brandStyled = chip\n\t\t\t? theme.bg(\"brandBg\", theme.bold(theme.fg(\"brandText\", brand)))\n\t\t\t: theme.bold(theme.fg(\"accent\", brand));\n\t\tlet l1Styled = `${brandStyled} ${theme.fg(\"muted\", pwd)}`;\n\t\tif (branch) {\n\t\t\tl1Plain += ` ${GIT_BRANCH_GLYPH} ${branch}`;\n\t\t\tl1Styled += ` ${theme.fg(\"dim\", GIT_BRANCH_GLYPH)} ${theme.fg(\"muted\", branch)}`;\n\t\t}\n\t\tif (sessionName) {\n\t\t\tl1Plain += ` • ${sessionName}`;\n\t\t\tl1Styled += theme.fg(\"dim\", ` • ${sessionName}`);\n\t\t}\n\t\tconst nSub = activeSubagentCount();\n\t\t// Right cluster: the view dial always, the live subagent count when there\n\t\t// is one. The dial's glyph fills up as the view widens, so its position on\n\t\t// the three-stop scale reads without parsing the word.\n\t\tconst viewGlyph = TOOL_OUTPUT_VIEW_GLYPHS[this.toolOutputView];\n\t\tconst rightParts: Array<{ plain: string; styled: string }> = [];\n\t\tif (nSub > 0) {\n\t\t\trightParts.push({\n\t\t\t\tplain: `◇${nSub} running`,\n\t\t\t\tstyled: theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\"),\n\t\t\t});\n\t\t}\n\t\trightParts.push({\n\t\t\tplain: `${viewGlyph} ${this.toolOutputView}`,\n\t\t\tstyled: theme.fg(\"dim\", `${viewGlyph} ${this.toolOutputView}`),\n\t\t});\n\t\tconst l1RightPlain = rightParts.map((p) => p.plain).join(\" \");\n\t\tconst l1RightStyled = rightParts.map((p) => p.styled).join(\" \");\n\t\tconst line1 = assembleLine(width, l1Plain, l1Styled, l1RightPlain, l1RightStyled);\n\n\t\t// ── Line 2 — session vitals ─────────────────────────────────────────────\n\t\t// A context-fill gauge (coloured by proximity to the auto-compact trip\n\t\t// point) leads, then token/cost deltas, with the model + thinking level\n\t\t// flush right. Numbers read in muted, labels/arrows in dim — a legible\n\t\t// hierarchy in place of the old uniform grey.\n\t\tlet thresholdPercent: number | undefined;\n\t\tif (this.autoCompactEnabled && contextWindow > 0) {\n\t\t\tconst reserveTokens = this.session.settingsManager.getCompactionSettings().reserveTokens;\n\t\t\tconst effective = contextWindow - reserveTokens;\n\t\t\tif (effective > 0) thresholdPercent = (effective / contextWindow) * 100;\n\t\t}\n\t\tconst errorLevel = thresholdPercent !== undefined ? thresholdPercent - 3 : 90;\n\t\tconst warnLevel = thresholdPercent !== undefined ? thresholdPercent - 10 : 70;\n\t\tconst autoIndicator =\n\t\t\tthresholdPercent !== undefined\n\t\t\t\t? ` auto@${thresholdPercent.toFixed(0)}%`\n\t\t\t\t: this.autoCompactEnabled\n\t\t\t\t\t? \" auto\"\n\t\t\t\t\t: \"\";\n\n\t\tconst gauge = contextGauge(contextPercentValue, errorLevel, warnLevel);\n\t\tconst pctText = contextPercent === \"?\" ? \"?\" : `${contextPercent}%`;\n\t\tconst pctColor =\n\t\t\tcontextPercentValue >= errorLevel ? \"error\" : contextPercentValue >= warnLevel ? \"warning\" : \"muted\";\n\t\tconst winText = `${formatTokens(contextWindow)}${autoIndicator}`;\n\n\t\tconst segs: Array<{ plain: string; styled: string }> = [\n\t\t\t{\n\t\t\t\tplain: `${gauge.plain} ${pctText} ${winText}`,\n\t\t\t\tstyled: `${gauge.styled} ${theme.fg(pctColor, pctText)} ${theme.fg(\"dim\", winText)}`,\n\t\t\t},\n\t\t];\n\t\tconst arrow = (a: string, n: number) => ({\n\t\t\tplain: `${a}${formatTokens(n)}`,\n\t\t\tstyled: theme.fg(\"dim\", a) + theme.fg(\"muted\", formatTokens(n)),\n\t\t});\n\t\tif (totalInput) segs.push(arrow(\"↑\", totalInput));\n\t\tif (totalOutput) segs.push(arrow(\"↓\", totalOutput));\n\t\tif (totalCacheRead) segs.push(arrow(\"R\", totalCacheRead));\n\t\tif (totalCacheWrite) segs.push(arrow(\"W\", totalCacheWrite));\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tsegs.push({ plain: costStr, styled: theme.fg(\"muted\", costStr) });\n\t\t}\n\t\tconst l2Plain = segs.map((s) => s.plain).join(\" \");\n\t\tconst l2Styled = segs.map((s) => s.styled).join(\" \");\n\n\t\t// Right: model, thinking level, and provider (when several are configured).\n\t\tconst modelName = state.model?.id || \"no-model\";\n\t\tlet r2Plain = modelName;\n\t\tlet r2Styled = theme.fg(\"muted\", modelName);\n\t\tif (state.model?.reasoning) {\n\t\t\tconst tl = state.thinkingLevel || \"off\";\n\t\t\tconst tstr = tl === \"off\" ? \"thinking off\" : tl;\n\t\t\tr2Plain += ` • ${tstr}`;\n\t\t\tr2Styled += theme.fg(\"dim\", ` • ${tstr}`);\n\t\t}\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\t// Prepend the provider only when the whole right cluster still fits.\n\t\t\tconst withProv = `(${state.model.provider}) ${r2Plain}`;\n\t\t\tif (visibleWidth(l2Plain) + 2 + visibleWidth(withProv) <= width) {\n\t\t\t\tr2Plain = withProv;\n\t\t\t\tr2Styled = theme.fg(\"dim\", `(${state.model.provider}) `) + r2Styled;\n\t\t\t}\n\t\t}\n\t\tconst line2 = assembleLine(width, l2Plain, l2Styled, r2Plain, r2Styled);\n\n\t\tconst lines = [line1, line2];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\t// Transient startup progress (first-run tool downloads, index build): one\n\t\t// determinate bar per entry, cleared as each settles. Width-clamped like the\n\t\t// status line so the footer never overflows.\n\t\tfor (const entry of startupProgress.list()) {\n\t\t\tlines.push(truncateToWidth(renderStartupLine(entry), width, theme.fg(\"dim\", \"…\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/footer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { AgentSession } from \"../../../core/agent-session.js\";\nimport { sumAssistantUsage } from \"../../../core/agent-session-stats.js\";\nimport { BRAND_MARK, GIT_BRANCH_GLYPH } from \"../../../core/brand.js\";\nimport type { ReadonlyFooterDataProvider } from \"../../../core/footer-data-provider.js\";\nimport { formatTokens } from \"../../../core/format-tokens.js\";\nimport { type StartupProgress, startupProgress } from \"../../../core/startup-progress.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport type { ToolOutputView } from \"../../../core/tool-output-view.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { renderDownloadProgress, renderProgressBar } from \"./progress-bar.js\";\n\n/**\n * Assemble one footer line: `left` flush left, `right` flush right when it fits\n * (≥2 cols between), padded to the full width. When it doesn't fit, drop `right`\n * and pad; when even `left` overflows, truncate it. Width math runs on the plain\n * strings; the styled strings carry the colour. Every returned line is exactly\n * `width` cells or fewer — the invariant the footer-width tests hold us to.\n */\nfunction assembleLine(width: number, leftPlain: string, leftStyled: string, rightPlain = \"\", rightStyled = \"\"): string {\n\tconst lw = visibleWidth(leftPlain);\n\tif (rightPlain && lw + 2 + visibleWidth(rightPlain) <= width) {\n\t\treturn leftStyled + \" \".repeat(width - lw - visibleWidth(rightPlain)) + rightStyled;\n\t}\n\tif (lw <= width) return leftStyled + \" \".repeat(width - lw);\n\treturn truncateToWidth(leftStyled, width, theme.fg(\"dim\", \"…\"));\n}\n\n/** A compact context-fill gauge, coloured by proximity to the auto-compact trip point. */\nfunction contextGauge(percent: number, errorLevel: number, warnLevel: number): { plain: string; styled: string } {\n\tconst CELLS = 8;\n\tconst filled = Math.max(0, Math.min(CELLS, Math.round((percent / 100) * CELLS)));\n\tconst fill = \"▰\".repeat(filled);\n\tconst track = \"▱\".repeat(CELLS - filled);\n\tconst color = percent >= errorLevel ? \"error\" : percent >= warnLevel ? \"warning\" : \"accent\";\n\t// The unfilled track takes `halftone`, which falls back to `dim` — the tone it\n\t// used before the token existed. The point of separating them is that `dim` is\n\t// body-weight text: a track drawn in it reads as writing rather than as the\n\t// space the fill has yet to reach.\n\treturn { plain: fill + track, styled: theme.fg(color, fill) + theme.fg(\"halftone\", track) };\n}\n\n/**\n * The view dial's glyph, filling up as the view widens — so the footer shows\n * where the dial sits on its three-stop scale, not just its name.\n */\nconst TOOL_OUTPUT_VIEW_GLYPHS: Record<ToolOutputView, string> = {\n\tradar: \"◌\",\n\tglance: \"◍\",\n\tfull: \"◉\",\n};\n\n/** Count subagent runs currently in flight, for the footer's live delegation cue. */\nfunction activeSubagentCount(): number {\n\treturn taskStore.list().filter((t) => t.source === \"subagent\" && t.status === \"in_progress\").length;\n}\n\n/**\n * Sanitize text for display in a single-line status.\n * Removes newlines, tabs, carriage returns, and other control characters.\n */\nfunction sanitizeStatusText(text: string): string {\n\t// Replace newlines, tabs, carriage returns with space, then collapse multiple spaces\n\treturn text\n\t\t.replace(/[\\r\\n\\t]/g, \" \")\n\t\t.replace(/ +/g, \" \")\n\t\t.trim();\n}\n\n/**\n * One footer line for a transient progress entry (tool download, index build,\n * `/learn` reading transcripts). The bar comes from the shared progress-bar\n * component, so this line and the voice panel's cannot drift apart again. An\n * error entry renders as a dim message instead. Returns a styled string; the\n * caller width-clamps it.\n */\nfunction renderStartupLine(entry: StartupProgress): string {\n\tif (entry.kind === \"error\") {\n\t\treturn theme.fg(\"dim\", `${entry.label}: ${entry.message}`);\n\t}\n\tconst label = theme.fg(\"text\", entry.label);\n\tif (entry.kind === \"download\") {\n\t\treturn `${label} ${renderDownloadProgress(entry.receivedBytes, entry.totalBytes)}`;\n\t}\n\tconst ratio = entry.total > 0 ? entry.done / entry.total : 0;\n\treturn `${label} ${renderProgressBar(ratio, `${entry.done}/${entry.total} ${entry.unit}`)}`;\n}\n\n/**\n * Footer component that shows pwd, token stats, and context usage.\n * Computes token/context stats from session, gets git branch and extension statuses from provider.\n */\nexport class FooterComponent implements Component {\n\tprivate autoCompactEnabled = true;\n\tprivate toolOutputView: ToolOutputView = \"glance\";\n\n\tconstructor(\n\t\tprivate session: AgentSession,\n\t\tprivate footerData: ReadonlyFooterDataProvider,\n\t) {}\n\n\tsetSession(session: AgentSession): void {\n\t\tthis.session = session;\n\t}\n\n\tsetAutoCompactEnabled(enabled: boolean): void {\n\t\tthis.autoCompactEnabled = enabled;\n\t}\n\n\t/** The view dial's current stop, shown so `alt+o` has somewhere to land. */\n\tsetToolOutputView(view: ToolOutputView): void {\n\t\tthis.toolOutputView = view;\n\t}\n\n\t/**\n\t * No-op: git branch caching now handled by provider.\n\t * Kept for compatibility with existing call sites in interactive-mode.\n\t */\n\tinvalidate(): void {\n\t\t// No-op: git branch is cached/invalidated by provider\n\t}\n\n\t/**\n\t * Clean up resources.\n\t * Git watcher cleanup now handled by provider.\n\t */\n\tdispose(): void {\n\t\t// Git watcher cleanup handled by provider\n\t}\n\n\trender(width: number): string[] {\n\t\tconst state = this.session.state;\n\n\t\t// Cumulative usage across ALL session entries (not just post-compaction\n\t\t// messages). Shares sumAssistantUsage with the per-request cost line the\n\t\t// transcript prints at agent_end, so the footer total and that line are always\n\t\t// derived from the same accounting.\n\t\tconst {\n\t\t\tinput: totalInput,\n\t\t\toutput: totalOutput,\n\t\t\tcacheRead: totalCacheRead,\n\t\t\tcacheWrite: totalCacheWrite,\n\t\t\tcost: totalCost,\n\t\t} = sumAssistantUsage(this.session.sessionManager.getEntries());\n\n\t\t// Calculate context usage from session (handles compaction correctly).\n\t\t// After compaction, tokens are unknown until the next LLM response.\n\t\tconst contextUsage = this.session.getContextUsage();\n\t\tconst contextWindow = contextUsage?.contextWindow ?? state.model?.contextWindow ?? 0;\n\t\tconst contextPercentValue = contextUsage?.percent ?? 0;\n\t\tconst contextPercent = contextUsage?.percent !== null ? contextPercentValue.toFixed(1) : \"?\";\n\n\t\t// Replace home directory with ~\n\t\tlet pwd = this.session.sessionManager.getCwd();\n\t\tconst home = process.env.HOME || process.env.USERPROFILE;\n\t\tif (home && pwd.startsWith(home)) {\n\t\t\tpwd = `~${pwd.slice(home.length)}`;\n\t\t}\n\t\tconst branch = this.footerData.getGitBranch();\n\t\tconst sessionName = this.session.sessionManager.getSessionName();\n\t\tconst modeLabel = this.footerData.getActiveMode();\n\n\t\t// ── Line 1 — identity & location ────────────────────────────────────────\n\t\t// Lead with the brand mark + MODE (the agent's guardrail: Ask/Plan/Build/\n\t\t// Debug) in bold accent so it is the first thing the eye lands on, then the\n\t\t// path, git branch, and session name in descending emphasis. The live\n\t\t// subagent count sits flush right — present only while work is delegated.\n\t\tconst modeUp = modeLabel.toUpperCase();\n\t\t// Themes that define the brandBg/brandText pair paint the mark as a filled\n\t\t// chip; the padding lives in the plain string too so width math and\n\t\t// truncation stay honest. Themes without the pair keep accent-coloured text.\n\t\tconst chip = theme.hasBg(\"brandBg\") && theme.has(\"brandText\");\n\t\tconst brand = chip ? ` ${BRAND_MARK} ${modeUp} ` : `${BRAND_MARK} ${modeUp}`;\n\t\tlet l1Plain = `${brand} ${pwd}`;\n\t\tconst brandStyled = chip\n\t\t\t? theme.bg(\"brandBg\", theme.bold(theme.fg(\"brandText\", brand)))\n\t\t\t: theme.bold(theme.fg(\"accent\", brand));\n\t\tlet l1Styled = `${brandStyled} ${theme.fg(\"muted\", pwd)}`;\n\t\tif (branch) {\n\t\t\tl1Plain += ` ${GIT_BRANCH_GLYPH} ${branch}`;\n\t\t\tl1Styled += ` ${theme.fg(\"dim\", GIT_BRANCH_GLYPH)} ${theme.fg(\"muted\", branch)}`;\n\t\t}\n\t\tif (sessionName) {\n\t\t\tl1Plain += ` • ${sessionName}`;\n\t\t\tl1Styled += theme.fg(\"dim\", ` • ${sessionName}`);\n\t\t}\n\t\tconst nSub = activeSubagentCount();\n\t\t// Right cluster: the view dial always, the live subagent count when there\n\t\t// is one. The dial's glyph fills up as the view widens, so its position on\n\t\t// the three-stop scale reads without parsing the word.\n\t\tconst viewGlyph = TOOL_OUTPUT_VIEW_GLYPHS[this.toolOutputView];\n\t\tconst rightParts: Array<{ plain: string; styled: string }> = [];\n\t\tif (nSub > 0) {\n\t\t\trightParts.push({\n\t\t\t\tplain: `◇${nSub} running`,\n\t\t\t\tstyled: theme.fg(\"accent\", `◇${nSub}`) + theme.fg(\"dim\", \" running\"),\n\t\t\t});\n\t\t}\n\t\trightParts.push({\n\t\t\tplain: `${viewGlyph} ${this.toolOutputView}`,\n\t\t\tstyled: theme.fg(\"dim\", `${viewGlyph} ${this.toolOutputView}`),\n\t\t});\n\t\tconst l1RightPlain = rightParts.map((p) => p.plain).join(\" \");\n\t\tconst l1RightStyled = rightParts.map((p) => p.styled).join(\" \");\n\t\tconst line1 = assembleLine(width, l1Plain, l1Styled, l1RightPlain, l1RightStyled);\n\n\t\t// ── Line 2 — session vitals ─────────────────────────────────────────────\n\t\t// A context-fill gauge (coloured by proximity to the auto-compact trip\n\t\t// point) leads, then token/cost deltas, with the model + thinking level\n\t\t// flush right. Numbers read in muted, labels/arrows in dim — a legible\n\t\t// hierarchy in place of the old uniform grey.\n\t\tlet thresholdPercent: number | undefined;\n\t\tif (this.autoCompactEnabled && contextWindow > 0) {\n\t\t\tconst reserveTokens = this.session.settingsManager.getCompactionSettings().reserveTokens;\n\t\t\tconst effective = contextWindow - reserveTokens;\n\t\t\tif (effective > 0) thresholdPercent = (effective / contextWindow) * 100;\n\t\t}\n\t\tconst errorLevel = thresholdPercent !== undefined ? thresholdPercent - 3 : 90;\n\t\tconst warnLevel = thresholdPercent !== undefined ? thresholdPercent - 10 : 70;\n\t\tconst autoIndicator =\n\t\t\tthresholdPercent !== undefined\n\t\t\t\t? ` auto@${thresholdPercent.toFixed(0)}%`\n\t\t\t\t: this.autoCompactEnabled\n\t\t\t\t\t? \" auto\"\n\t\t\t\t\t: \"\";\n\n\t\tconst gauge = contextGauge(contextPercentValue, errorLevel, warnLevel);\n\t\tconst pctText = contextPercent === \"?\" ? \"?\" : `${contextPercent}%`;\n\t\tconst pctColor =\n\t\t\tcontextPercentValue >= errorLevel ? \"error\" : contextPercentValue >= warnLevel ? \"warning\" : \"muted\";\n\t\tconst winText = `${formatTokens(contextWindow)}${autoIndicator}`;\n\n\t\tconst segs: Array<{ plain: string; styled: string }> = [\n\t\t\t{\n\t\t\t\tplain: `${gauge.plain} ${pctText} ${winText}`,\n\t\t\t\tstyled: `${gauge.styled} ${theme.fg(pctColor, pctText)} ${theme.fg(\"dim\", winText)}`,\n\t\t\t},\n\t\t];\n\t\tconst arrow = (a: string, n: number) => ({\n\t\t\tplain: `${a}${formatTokens(n)}`,\n\t\t\tstyled: theme.fg(\"dim\", a) + theme.fg(\"muted\", formatTokens(n)),\n\t\t});\n\t\tif (totalInput) segs.push(arrow(\"↑\", totalInput));\n\t\tif (totalOutput) segs.push(arrow(\"↓\", totalOutput));\n\t\tif (totalCacheRead) segs.push(arrow(\"R\", totalCacheRead));\n\t\tif (totalCacheWrite) segs.push(arrow(\"W\", totalCacheWrite));\n\t\tconst usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;\n\t\tif (totalCost || usingSubscription) {\n\t\t\tconst costStr = `$${totalCost.toFixed(3)}${usingSubscription ? \" (sub)\" : \"\"}`;\n\t\t\tsegs.push({ plain: costStr, styled: theme.fg(\"muted\", costStr) });\n\t\t}\n\t\tconst l2Plain = segs.map((s) => s.plain).join(\" \");\n\t\tconst l2Styled = segs.map((s) => s.styled).join(\" \");\n\n\t\t// Right: model, thinking level, and provider (when several are configured).\n\t\tconst modelName = state.model?.id || \"no-model\";\n\t\tlet r2Plain = modelName;\n\t\tlet r2Styled = theme.fg(\"muted\", modelName);\n\t\tif (state.model?.reasoning) {\n\t\t\tconst tl = state.thinkingLevel || \"off\";\n\t\t\tconst tstr = tl === \"off\" ? \"thinking off\" : tl;\n\t\t\tr2Plain += ` • ${tstr}`;\n\t\t\tr2Styled += theme.fg(\"dim\", ` • ${tstr}`);\n\t\t}\n\t\tif (this.footerData.getAvailableProviderCount() > 1 && state.model) {\n\t\t\t// Prepend the provider only when the whole right cluster still fits.\n\t\t\tconst withProv = `(${state.model.provider}) ${r2Plain}`;\n\t\t\tif (visibleWidth(l2Plain) + 2 + visibleWidth(withProv) <= width) {\n\t\t\t\tr2Plain = withProv;\n\t\t\t\tr2Styled = theme.fg(\"dim\", `(${state.model.provider}) `) + r2Styled;\n\t\t\t}\n\t\t}\n\t\tconst line2 = assembleLine(width, l2Plain, l2Styled, r2Plain, r2Styled);\n\n\t\tconst lines = [line1, line2];\n\n\t\t// Add extension statuses on a single line, sorted by key alphabetically\n\t\tconst extensionStatuses = this.footerData.getExtensionStatuses();\n\t\tif (extensionStatuses.size > 0) {\n\t\t\tconst sortedStatuses = Array.from(extensionStatuses.entries())\n\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t.map(([, text]) => sanitizeStatusText(text));\n\t\t\tconst statusLine = sortedStatuses.join(\" \");\n\t\t\t// Truncate to terminal width with dim ellipsis for consistency with footer style\n\t\t\tlines.push(truncateToWidth(statusLine, width, theme.fg(\"dim\", \"...\")));\n\t\t}\n\n\t\t// Transient startup progress (first-run tool downloads, index build): one\n\t\t// determinate bar per entry, cleared as each settles. Width-clamped like the\n\t\t// status line so the footer never overflows.\n\t\tfor (const entry of startupProgress.list()) {\n\t\t\tlines.push(truncateToWidth(renderStartupLine(entry), width, theme.fg(\"dim\", \"…\")));\n\t\t}\n\n\t\treturn lines;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"progress-bar.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/progress-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAoBD,MAAM,WAAW,kBAAkB;IAClC,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAKzG;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACrC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,OAAO,GAAE,kBAAuB,GAC9B,MAAM,CAMR","sourcesContent":["/**\n * The one progress bar. Every surface that makes the user wait on measurable\n * work renders it through here.\n *\n * There used to be two, written months apart, and they had drifted in three\n * separate ways: the footer drew `▰▱` while the voice panel drew `·` over `·`,\n * the footer said `2.0 MB` while the voice panel said `2 MB`, and each carried\n * its own copy of the percent-and-detail layout. None of that is a decision\n * anyone made — it is just what happens when the same widget is written twice.\n *\n * Bar *width* is still the caller's, because that is a genuine property of the\n * surface: a footer line shares its row with other status, a panel has the room\n * to be finer-grained. Everything else — glyphs, colours, percent, separator,\n * byte formatting — is fixed here, so two bars on screen at once always agree.\n */\n\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Default cells in a bar. Compact enough that several stacked footer lines\n * still fit a narrow terminal.\n */\nexport const PROGRESS_BAR_CELLS = 12;\n\n/**\n * Megabytes, to one decimal.\n *\n * The decimal is the point: whole megabytes make a slow download look frozen\n * for tens of seconds at a time, which is precisely when the user is deciding\n * whether it has hung.\n */\nexport function formatMegabytes(bytes: number): string {\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * The filled/empty track.\n *\n * The glyphs differ in shape, not only in colour, matching the context gauge in\n * the footer. An earlier version drew both halves as `·` and left the whole\n * reading to the colour, so at a glance — or on a low-contrast theme, or piped\n * somewhere that drops styling — a bar at 10% and one at 90% were identical.\n *\n * A started-but-tiny ratio keeps one filled cell rather than rounding to an\n * empty bar: \"nothing has happened yet\" and \"the first of forty is done\" are\n * different things to be told.\n */\nfunction track(ratio: number, cells: number): string {\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst filled = Math.max(clamped > 0 ? 1 : 0, Math.min(cells, Math.round(clamped * cells)));\n\treturn theme.fg(\"accent\", \"▰\".repeat(filled)) + theme.fg(\"dim\", \"▱\".repeat(cells - filled));\n}\n\nexport interface ProgressBarOptions {\n\t/** Track width. Defaults to {@link PROGRESS_BAR_CELLS}. */\n\tcells?: number;\n}\n\n/**\n * A determinate bar: track, percent, and a trailing detail such as\n * `3/12 sessions` or `2.0 MB / 8.0 MB`.\n *\n * Returns a styled string with no leading or trailing space, so callers can\n * place it after their own label. Callers width-clamp.\n */\nexport function renderProgressBar(ratio: number, detail: string, options: ProgressBarOptions = {}): string {\n\tconst cells = options.cells ?? PROGRESS_BAR_CELLS;\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst percent = `${Math.round(clamped * 100)}%`;\n\treturn `${track(clamped, cells)} ${theme.fg(\"muted\", percent)} ${theme.fg(\"dim\", `· ${detail}`)}`;\n}\n\n/**\n * A download's progress, determinate when the server sent a length and a\n * running byte count when it did not.\n *\n * The indeterminate case deliberately drops the bar rather than animating a\n * fake one: a moving byte count already says \"still going\", and a bar that\n * cannot reach the end is worse than no bar.\n */\nexport function renderDownloadProgress(\n\treceivedBytes: number,\n\ttotalBytes: number | null,\n\toptions: ProgressBarOptions = {},\n): string {\n\tif (totalBytes === null || totalBytes <= 0) {\n\t\treturn theme.fg(\"dim\", `${formatMegabytes(receivedBytes)}…`);\n\t}\n\tconst detail = `${formatMegabytes(receivedBytes)} / ${formatMegabytes(totalBytes)}`;\n\treturn renderProgressBar(receivedBytes / totalBytes, detail, options);\n}\n"]}
1
+ {"version":3,"file":"progress-bar.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/progress-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAErD;AAoBD,MAAM,WAAW,kBAAkB;IAClC,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAKzG;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACrC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,OAAO,GAAE,kBAAuB,GAC9B,MAAM,CAMR","sourcesContent":["/**\n * The one progress bar. Every surface that makes the user wait on measurable\n * work renders it through here.\n *\n * There used to be two, written months apart, and they had drifted in three\n * separate ways: the footer drew `▰▱` while the voice panel drew `·` over `·`,\n * the footer said `2.0 MB` while the voice panel said `2 MB`, and each carried\n * its own copy of the percent-and-detail layout. None of that is a decision\n * anyone made — it is just what happens when the same widget is written twice.\n *\n * Bar *width* is still the caller's, because that is a genuine property of the\n * surface: a footer line shares its row with other status, a panel has the room\n * to be finer-grained. Everything else — glyphs, colours, percent, separator,\n * byte formatting — is fixed here, so two bars on screen at once always agree.\n */\n\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Default cells in a bar. Compact enough that several stacked footer lines\n * still fit a narrow terminal.\n */\nexport const PROGRESS_BAR_CELLS = 12;\n\n/**\n * Megabytes, to one decimal.\n *\n * The decimal is the point: whole megabytes make a slow download look frozen\n * for tens of seconds at a time, which is precisely when the user is deciding\n * whether it has hung.\n */\nexport function formatMegabytes(bytes: number): string {\n\treturn `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * The filled/empty track.\n *\n * The glyphs differ in shape, not only in colour, matching the context gauge in\n * the footer. An earlier version drew both halves as `·` and left the whole\n * reading to the colour, so at a glance — or on a low-contrast theme, or piped\n * somewhere that drops styling — a bar at 10% and one at 90% were identical.\n *\n * A started-but-tiny ratio keeps one filled cell rather than rounding to an\n * empty bar: \"nothing has happened yet\" and \"the first of forty is done\" are\n * different things to be told.\n */\nfunction track(ratio: number, cells: number): string {\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst filled = Math.max(clamped > 0 ? 1 : 0, Math.min(cells, Math.round(clamped * cells)));\n\treturn theme.fg(\"accent\", \"▰\".repeat(filled)) + theme.fg(\"halftone\", \"▱\".repeat(cells - filled));\n}\n\nexport interface ProgressBarOptions {\n\t/** Track width. Defaults to {@link PROGRESS_BAR_CELLS}. */\n\tcells?: number;\n}\n\n/**\n * A determinate bar: track, percent, and a trailing detail such as\n * `3/12 sessions` or `2.0 MB / 8.0 MB`.\n *\n * Returns a styled string with no leading or trailing space, so callers can\n * place it after their own label. Callers width-clamp.\n */\nexport function renderProgressBar(ratio: number, detail: string, options: ProgressBarOptions = {}): string {\n\tconst cells = options.cells ?? PROGRESS_BAR_CELLS;\n\tconst clamped = Math.max(0, Math.min(1, ratio));\n\tconst percent = `${Math.round(clamped * 100)}%`;\n\treturn `${track(clamped, cells)} ${theme.fg(\"muted\", percent)} ${theme.fg(\"dim\", `· ${detail}`)}`;\n}\n\n/**\n * A download's progress, determinate when the server sent a length and a\n * running byte count when it did not.\n *\n * The indeterminate case deliberately drops the bar rather than animating a\n * fake one: a moving byte count already says \"still going\", and a bar that\n * cannot reach the end is worse than no bar.\n */\nexport function renderDownloadProgress(\n\treceivedBytes: number,\n\ttotalBytes: number | null,\n\toptions: ProgressBarOptions = {},\n): string {\n\tif (totalBytes === null || totalBytes <= 0) {\n\t\treturn theme.fg(\"dim\", `${formatMegabytes(receivedBytes)}…`);\n\t}\n\tconst detail = `${formatMegabytes(receivedBytes)} / ${formatMegabytes(totalBytes)}`;\n\treturn renderProgressBar(receivedBytes / totalBytes, detail, options);\n}\n"]}
@@ -44,7 +44,7 @@ export function formatMegabytes(bytes) {
44
44
  function track(ratio, cells) {
45
45
  const clamped = Math.max(0, Math.min(1, ratio));
46
46
  const filled = Math.max(clamped > 0 ? 1 : 0, Math.min(cells, Math.round(clamped * cells)));
47
- return theme.fg("accent", "▰".repeat(filled)) + theme.fg("dim", "▱".repeat(cells - filled));
47
+ return theme.fg("accent", "▰".repeat(filled)) + theme.fg("halftone", "▱".repeat(cells - filled));
48
48
  }
49
49
  /**
50
50
  * A determinate bar: track, percent, and a trailing detail such as