@mariozechner/pi-coding-agent 0.42.5 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +13 -8
  3. package/dist/cli/list-models.d.ts.map +1 -1
  4. package/dist/cli/list-models.js +1 -1
  5. package/dist/cli/list-models.js.map +1 -1
  6. package/dist/cli/session-picker.d.ts +4 -2
  7. package/dist/cli/session-picker.d.ts.map +1 -1
  8. package/dist/cli/session-picker.js +3 -3
  9. package/dist/cli/session-picker.js.map +1 -1
  10. package/dist/core/agent-session.d.ts +14 -8
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +37 -15
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  15. package/dist/core/compaction/branch-summarization.js +3 -1
  16. package/dist/core/compaction/branch-summarization.js.map +1 -1
  17. package/dist/core/extensions/index.d.ts +2 -2
  18. package/dist/core/extensions/index.d.ts.map +1 -1
  19. package/dist/core/extensions/index.js.map +1 -1
  20. package/dist/core/extensions/runner.d.ts +2 -2
  21. package/dist/core/extensions/runner.d.ts.map +1 -1
  22. package/dist/core/extensions/runner.js +9 -5
  23. package/dist/core/extensions/runner.js.map +1 -1
  24. package/dist/core/extensions/types.d.ts +25 -14
  25. package/dist/core/extensions/types.d.ts.map +1 -1
  26. package/dist/core/extensions/types.js.map +1 -1
  27. package/dist/core/footer-data-provider.d.ts.map +1 -1
  28. package/dist/core/footer-data-provider.js +10 -4
  29. package/dist/core/footer-data-provider.js.map +1 -1
  30. package/dist/core/index.d.ts +1 -1
  31. package/dist/core/index.d.ts.map +1 -1
  32. package/dist/core/index.js.map +1 -1
  33. package/dist/core/session-manager.d.ts +11 -2
  34. package/dist/core/session-manager.d.ts.map +1 -1
  35. package/dist/core/session-manager.js +142 -64
  36. package/dist/core/session-manager.js.map +1 -1
  37. package/dist/core/settings-manager.d.ts +7 -3
  38. package/dist/core/settings-manager.d.ts.map +1 -1
  39. package/dist/core/settings-manager.js +15 -0
  40. package/dist/core/settings-manager.js.map +1 -1
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/main.d.ts.map +1 -1
  45. package/dist/main.js +13 -12
  46. package/dist/main.js.map +1 -1
  47. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  48. package/dist/modes/interactive/components/extension-editor.js +8 -8
  49. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  50. package/dist/modes/interactive/components/index.d.ts +1 -0
  51. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  52. package/dist/modes/interactive/components/index.js +1 -0
  53. package/dist/modes/interactive/components/index.js.map +1 -1
  54. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  55. package/dist/modes/interactive/components/model-selector.js +2 -3
  56. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  57. package/dist/modes/interactive/components/scoped-models-selector.d.ts +47 -0
  58. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
  59. package/dist/modes/interactive/components/scoped-models-selector.js +241 -0
  60. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
  61. package/dist/modes/interactive/components/session-selector.d.ts +17 -3
  62. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  63. package/dist/modes/interactive/components/session-selector.js +167 -35
  64. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  65. package/dist/modes/interactive/components/settings-selector.d.ts +4 -2
  66. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  67. package/dist/modes/interactive/components/settings-selector.js +13 -1
  68. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  69. package/dist/modes/interactive/components/tree-selector.d.ts +2 -2
  70. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  71. package/dist/modes/interactive/components/tree-selector.js +8 -7
  72. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  73. package/dist/modes/interactive/interactive-mode.d.ts +6 -0
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.js +222 -28
  76. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  77. package/dist/modes/print-mode.d.ts.map +1 -1
  78. package/dist/modes/print-mode.js +2 -2
  79. package/dist/modes/print-mode.js.map +1 -1
  80. package/dist/modes/rpc/rpc-client.d.ts +4 -4
  81. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  82. package/dist/modes/rpc/rpc-client.js +6 -6
  83. package/dist/modes/rpc/rpc-client.js.map +1 -1
  84. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  85. package/dist/modes/rpc/rpc-mode.js +11 -8
  86. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  87. package/dist/modes/rpc/rpc-types.d.ts +4 -4
  88. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  89. package/dist/modes/rpc/rpc-types.js.map +1 -1
  90. package/docs/extensions.md +43 -10
  91. package/docs/rpc.md +10 -10
  92. package/docs/sdk.md +10 -5
  93. package/docs/session.md +1 -1
  94. package/docs/skills.md +27 -0
  95. package/docs/tree.md +9 -5
  96. package/docs/tui.md +2 -0
  97. package/examples/extensions/README.md +3 -3
  98. package/examples/extensions/confirm-destructive.ts +5 -5
  99. package/examples/extensions/dirty-repo-guard.ts +2 -2
  100. package/examples/extensions/git-checkpoint.ts +3 -3
  101. package/examples/extensions/handoff.ts +1 -1
  102. package/examples/extensions/model-status.ts +31 -0
  103. package/examples/extensions/todo.ts +1 -1
  104. package/examples/extensions/tools.ts +2 -2
  105. package/examples/extensions/with-deps/package-lock.json +2 -2
  106. package/examples/extensions/with-deps/package.json +1 -1
  107. package/examples/sdk/11-sessions.ts +1 -1
  108. package/package.json +4 -4
  109. package/dist/utils/fuzzy.d.ts +0 -7
  110. package/dist/utils/fuzzy.d.ts.map +0 -1
  111. package/dist/utils/fuzzy.js +0 -86
  112. package/dist/utils/fuzzy.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scoped-models-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/scoped-models-selector.ts"],"names":[],"mappings":"AACA,OAAO,EACN,SAAS,EACT,WAAW,EACX,oBAAoB,EACpB,KAAK,EACL,GAAG,EACH,UAAU,EACV,MAAM,EACN,IAAI,GACJ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AA6BpD;;;GAGG;AACH,MAAM,OAAO,6BAA8B,SAAQ,SAAS;IACnD,KAAK,GAAgB,EAAE,CAAC;IACxB,aAAa,GAAgB,EAAE,CAAC;IAChC,aAAa,GAAG,CAAC,CAAC;IAClB,WAAW,CAAQ;IACnB,aAAa,CAAY;IACzB,UAAU,CAAO;IACjB,SAAS,CAAkB;IAC3B,UAAU,GAAG,EAAE,CAAC;IAChB,OAAO,GAAG,KAAK,CAAC;IAExB,YAAY,MAAoB,EAAE,SAA0B,EAAE;QAC7D,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAE3B,iDAAiD;QACjD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAwB,CAAC;QACzD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjB,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC;QAED,kCAAkC;QAClC,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,gBAAgB,EAAE,CAAC;YACnD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAG,GAAG,QAAQ,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;gBACzC,0DAA0D;gBAC1D,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,sBAAsB,IAAI,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACvF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;oBACf,MAAM;oBACN,KAAK;oBACL,OAAO,EAAE,SAAS;iBAClB,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QAE3C,SAAS;QACT,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrF,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,2CAA2C,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9F,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7B,eAAe;QACf,IAAI,CAAC,WAAW,GAAG,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAChC,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7B,iBAAiB;QACjB,IAAI,CAAC,aAAa,GAAG,IAAI,SAAS,EAAE,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAElC,cAAc;QACd,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAE/B,IAAI,CAAC,QAAQ,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;QAEnC,IAAI,CAAC,UAAU,EAAE,CAAC;IAAA,CAClB;IAED,gDAAgD;IACxC,cAAc,GAAgB;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtD,OAAO,CAAC,GAAG,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC;IAAA,CACjC;IAEO,aAAa,GAAW;QAC/B,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QAChE,MAAM,UAAU,GAAG,YAAY,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QACtD,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,YAAY,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,UAAU,CAAC;QAC9F,MAAM,KAAK,GAAG,CAAC,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAC1F,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,IAAI,CAAC,MAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACtF,CAAC;QACD,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,CAAC,IAAI,CAAC,MAAK,CAAC,EAAE,CAAC,CAAC;IAAA,CACjD;IAEO,YAAY,GAAS;QAC5B,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAAA,CAC9C;IAEO,WAAW,CAAC,KAAa,EAAQ;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,IAAI,CAAC,aAAa,GAAG,MAAM,CAAC;QAC7B,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACtG,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QAC9F,IAAI,CAAC,UAAU,EAAE,CAAC;IAAA,CAClB;IAEO,UAAU,GAAS;QAC1B,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;QAE3B,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACvF,OAAO;QACR,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAC1B,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,CAC3G,CAAC;QACF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAEnF,gEAAgE;QAChE,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAEtD,KAAK,IAAI,CAAC,GAAG,UAAU,EAAE,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;YACnC,IAAI,CAAC,IAAI;gBAAE,SAAS;YAEpB,MAAM,UAAU,GAAG,CAAC,KAAK,IAAI,CAAC,aAAa,CAAC;YAC5C,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC5D,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACjF,MAAM,aAAa,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrE,sDAAsD;YACtD,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,MAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAI,CAAC,CAAC;YAElG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC/F,CAAC;QAED,iCAAiC;QACjC,IAAI,UAAU,GAAG,CAAC,IAAI,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;YAC5D,MAAM,UAAU,GAAG,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,aAAa,GAAG,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;YACnG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACzD,CAAC;IAAA,CACD;IAEO,UAAU,CAAC,IAAe,EAAQ;QACzC,iFAAiF;QACjF,+CAA+C;QAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtD,IAAI,UAAU,EAAE,CAAC;YAChB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC5B,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC;YACnB,CAAC;YACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;YAC5B,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACjD,CAAC;aAAM,CAAC;YACP,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;YAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACzD,CAAC;QACD,wDAAwD;QACxD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,YAAY,EAAE,CAAC;IAAA,CACpB;IAED,WAAW,CAAC,IAAY,EAAQ;QAC/B,MAAM,EAAE,GAAG,oBAAoB,EAAE,CAAC;QAElC,aAAa;QACb,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC;YAClC,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAC5C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;YACvG,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO;QACR,CAAC;QACD,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC;YACpC,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAC5C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;YACvG,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO;QACR,CAAC;QAED,kBAAkB;QAClB,IAAI,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACpD,IAAI,IAAI,EAAE,CAAC;gBACV,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACvB,CAAC;YACD,OAAO;QACR,CAAC;QAED,iEAAiE;QACjE,IAAI,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;YAC9E,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACrB,CAAC;YACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YACzD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,OAAO;QACR,CAAC;QAED,gEAAgE;QAChE,IAAI,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;YAC9E,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACtB,CAAC;YACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACpB,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,CAAC;YAC5B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC9C,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,OAAO;QACR,CAAC;QAED,2CAA2C;QAC3C,IAAI,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACrC,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAC3D,IAAI,WAAW,EAAE,CAAC;gBACjB,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC;gBAC5C,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;gBAC9E,MAAM,UAAU,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBACzD,MAAM,QAAQ,GAAG,CAAC,UAAU,CAAC;gBAC7B,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;oBAClC,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC;gBACzB,CAAC;gBACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAC9B,QAAQ,EACR,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAClC,QAAQ,CACR,CAAC;gBACF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAC9C,IAAI,CAAC,YAAY,EAAE,CAAC;YACrB,CAAC;YACD,OAAO;QACR,CAAC;QAED,oCAAoC;QACpC,IAAI,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACrC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAC5E,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;YACrC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;YACrB,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,OAAO;QACR,CAAC;QAED,2CAA2C;QAC3C,IAAI,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACrC,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC;gBACjC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;gBAC9B,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACP,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;YAC3B,CAAC;YACD,OAAO;QACR,CAAC;QAED,kBAAkB;QAClB,IAAI,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QAED,uCAAuC;QACvC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC;IAAA,CAC9C;IAED,cAAc,GAAU;QACvB,OAAO,IAAI,CAAC,WAAW,CAAC;IAAA,CACxB;CACD","sourcesContent":["import type { Model } from \"@mariozechner/pi-ai\";\nimport {\n\tContainer,\n\tfuzzyFilter,\n\tgetEditorKeybindings,\n\tInput,\n\tKey,\n\tmatchesKey,\n\tSpacer,\n\tText,\n} from \"@mariozechner/pi-tui\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\ninterface ModelItem {\n\tfullId: string;\n\tmodel: Model<any>;\n\tenabled: boolean;\n}\n\nexport interface ModelsConfig {\n\tallModels: Model<any>[];\n\tenabledModelIds: Set<string>;\n\t/** true if enabledModels setting is defined (empty = all enabled) */\n\thasEnabledModelsFilter: boolean;\n}\n\nexport interface ModelsCallbacks {\n\t/** Called when a model is toggled (session-only, no persist) */\n\tonModelToggle: (modelId: string, enabled: boolean) => void;\n\t/** Called when user wants to persist current selection to settings */\n\tonPersist: (enabledModelIds: string[]) => void;\n\t/** Called when user enables all models. Returns list of all model IDs. */\n\tonEnableAll: (allModelIds: string[]) => void;\n\t/** Called when user clears all models */\n\tonClearAll: () => void;\n\t/** Called when user toggles all models for a provider. Returns affected model IDs. */\n\tonToggleProvider: (provider: string, modelIds: string[], enabled: boolean) => void;\n\tonCancel: () => void;\n}\n\n/**\n * Component for enabling/disabling models for Ctrl+P cycling.\n * Changes are session-only until explicitly persisted with Ctrl+S.\n */\nexport class ScopedModelsSelectorComponent extends Container {\n\tprivate items: ModelItem[] = [];\n\tprivate filteredItems: ModelItem[] = [];\n\tprivate selectedIndex = 0;\n\tprivate searchInput: Input;\n\tprivate listContainer: Container;\n\tprivate footerText: Text;\n\tprivate callbacks: ModelsCallbacks;\n\tprivate maxVisible = 15;\n\tprivate isDirty = false;\n\n\tconstructor(config: ModelsConfig, callbacks: ModelsCallbacks) {\n\t\tsuper();\n\t\tthis.callbacks = callbacks;\n\n\t\t// Group models by provider for organized display\n\t\tconst modelsByProvider = new Map<string, Model<any>[]>();\n\t\tfor (const model of config.allModels) {\n\t\t\tconst list = modelsByProvider.get(model.provider) ?? [];\n\t\t\tlist.push(model);\n\t\t\tmodelsByProvider.set(model.provider, list);\n\t\t}\n\n\t\t// Build items - group by provider\n\t\tfor (const [provider, models] of modelsByProvider) {\n\t\t\tfor (const model of models) {\n\t\t\t\tconst fullId = `${provider}/${model.id}`;\n\t\t\t\t// If no filter defined, all models are enabled by default\n\t\t\t\tconst isEnabled = !config.hasEnabledModelsFilter || config.enabledModelIds.has(fullId);\n\t\t\t\tthis.items.push({\n\t\t\t\t\tfullId,\n\t\t\t\t\tmodel,\n\t\t\t\t\tenabled: isEnabled,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.filteredItems = this.getSortedItems();\n\n\t\t// Header\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"accent\", theme.bold(\"Model Configuration\")), 0, 0));\n\t\tthis.addChild(new Text(theme.fg(\"muted\", \"Session-only. Ctrl+S to save to settings.\"), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Search input\n\t\tthis.searchInput = new Input();\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// List container\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\n\t\t// Footer hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.footerText = new Text(this.getFooterText(), 0, 0);\n\t\tthis.addChild(this.footerText);\n\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.updateList();\n\t}\n\n\t/** Get items sorted with enabled items first */\n\tprivate getSortedItems(): ModelItem[] {\n\t\tconst enabled = this.items.filter((i) => i.enabled);\n\t\tconst disabled = this.items.filter((i) => !i.enabled);\n\t\treturn [...enabled, ...disabled];\n\t}\n\n\tprivate getFooterText(): string {\n\t\tconst enabledCount = this.items.filter((i) => i.enabled).length;\n\t\tconst allEnabled = enabledCount === this.items.length;\n\t\tconst countText = allEnabled ? \"all enabled\" : `${enabledCount}/${this.items.length} enabled`;\n\t\tconst parts = [\"Enter toggle\", \"^A all\", \"^X clear\", \"^P provider\", \"^S save\", countText];\n\t\tif (this.isDirty) {\n\t\t\treturn theme.fg(\"dim\", ` ${parts.join(\" · \")} `) + theme.fg(\"warning\", \"(unsaved)\");\n\t\t}\n\t\treturn theme.fg(\"dim\", ` ${parts.join(\" · \")}`);\n\t}\n\n\tprivate updateFooter(): void {\n\t\tthis.footerText.setText(this.getFooterText());\n\t}\n\n\tprivate filterItems(query: string): void {\n\t\tconst sorted = this.getSortedItems();\n\t\tif (!query) {\n\t\t\tthis.filteredItems = sorted;\n\t\t} else {\n\t\t\tthis.filteredItems = fuzzyFilter(sorted, query, (item) => `${item.model.id} ${item.model.provider}`);\n\t\t}\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));\n\t\tthis.updateList();\n\t}\n\n\tprivate updateList(): void {\n\t\tthis.listContainer.clear();\n\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(theme.fg(\"muted\", \" No matching models\"), 0, 0));\n\t\t\treturn;\n\t\t}\n\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length);\n\n\t\t// Only show status if there's a filter (not all models enabled)\n\t\tconst allEnabled = this.items.every((i) => i.enabled);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.filteredItems[i];\n\t\t\tif (!item) continue;\n\n\t\t\tconst isSelected = i === this.selectedIndex;\n\t\t\tconst prefix = isSelected ? theme.fg(\"accent\", \"→ \") : \" \";\n\t\t\tconst modelText = isSelected ? theme.fg(\"accent\", item.model.id) : item.model.id;\n\t\t\tconst providerBadge = theme.fg(\"muted\", ` [${item.model.provider}]`);\n\t\t\t// Only show checkmarks when there's actually a filter\n\t\t\tconst status = allEnabled ? \"\" : item.enabled ? theme.fg(\"success\", \" ✓\") : theme.fg(\"dim\", \" ✗\");\n\n\t\t\tthis.listContainer.addChild(new Text(`${prefix}${modelText}${providerBadge}${status}`, 0, 0));\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`);\n\t\t\tthis.listContainer.addChild(new Text(scrollInfo, 0, 0));\n\t\t}\n\t}\n\n\tprivate toggleItem(item: ModelItem): void {\n\t\t// If all models are currently enabled (no scope yet), first toggle starts fresh:\n\t\t// clear all and enable only the selected model\n\t\tconst allEnabled = this.items.every((i) => i.enabled);\n\t\tif (allEnabled) {\n\t\t\tfor (const i of this.items) {\n\t\t\t\ti.enabled = false;\n\t\t\t}\n\t\t\titem.enabled = true;\n\t\t\tthis.isDirty = true;\n\t\t\tthis.callbacks.onClearAll();\n\t\t\tthis.callbacks.onModelToggle(item.fullId, true);\n\t\t} else {\n\t\t\titem.enabled = !item.enabled;\n\t\t\tthis.isDirty = true;\n\t\t\tthis.callbacks.onModelToggle(item.fullId, item.enabled);\n\t\t}\n\t\t// Re-sort and re-filter to move item to correct section\n\t\tthis.filterItems(this.searchInput.getValue());\n\t\tthis.updateFooter();\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getEditorKeybindings();\n\n\t\t// Navigation\n\t\tif (kb.matches(data, \"selectUp\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredItems.length - 1 : this.selectedIndex - 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"selectDown\")) {\n\t\t\tif (this.filteredItems.length === 0) return;\n\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredItems.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\tthis.updateList();\n\t\t\treturn;\n\t\t}\n\n\t\t// Toggle on Enter\n\t\tif (matchesKey(data, Key.enter)) {\n\t\t\tconst item = this.filteredItems[this.selectedIndex];\n\t\t\tif (item) {\n\t\t\t\tthis.toggleItem(item);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+A - Enable all (filtered if search active, otherwise all)\n\t\tif (matchesKey(data, Key.ctrl(\"a\"))) {\n\t\t\tconst targets = this.searchInput.getValue() ? this.filteredItems : this.items;\n\t\t\tfor (const item of targets) {\n\t\t\t\titem.enabled = true;\n\t\t\t}\n\t\t\tthis.isDirty = true;\n\t\t\tthis.callbacks.onEnableAll(targets.map((i) => i.fullId));\n\t\t\tthis.filterItems(this.searchInput.getValue());\n\t\t\tthis.updateFooter();\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+X - Clear all (filtered if search active, otherwise all)\n\t\tif (matchesKey(data, Key.ctrl(\"x\"))) {\n\t\t\tconst targets = this.searchInput.getValue() ? this.filteredItems : this.items;\n\t\t\tfor (const item of targets) {\n\t\t\t\titem.enabled = false;\n\t\t\t}\n\t\t\tthis.isDirty = true;\n\t\t\tthis.callbacks.onClearAll();\n\t\t\tthis.filterItems(this.searchInput.getValue());\n\t\t\tthis.updateFooter();\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+P - Toggle provider of current item\n\t\tif (matchesKey(data, Key.ctrl(\"p\"))) {\n\t\t\tconst currentItem = this.filteredItems[this.selectedIndex];\n\t\t\tif (currentItem) {\n\t\t\t\tconst provider = currentItem.model.provider;\n\t\t\t\tconst providerItems = this.items.filter((i) => i.model.provider === provider);\n\t\t\t\tconst allEnabled = providerItems.every((i) => i.enabled);\n\t\t\t\tconst newState = !allEnabled;\n\t\t\t\tfor (const item of providerItems) {\n\t\t\t\t\titem.enabled = newState;\n\t\t\t\t}\n\t\t\t\tthis.isDirty = true;\n\t\t\t\tthis.callbacks.onToggleProvider(\n\t\t\t\t\tprovider,\n\t\t\t\t\tproviderItems.map((i) => i.fullId),\n\t\t\t\t\tnewState,\n\t\t\t\t);\n\t\t\t\tthis.filterItems(this.searchInput.getValue());\n\t\t\t\tthis.updateFooter();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+S - Save/persist to settings\n\t\tif (matchesKey(data, Key.ctrl(\"s\"))) {\n\t\t\tconst enabledIds = this.items.filter((i) => i.enabled).map((i) => i.fullId);\n\t\t\tthis.callbacks.onPersist(enabledIds);\n\t\t\tthis.isDirty = false;\n\t\t\tthis.updateFooter();\n\t\t\treturn;\n\t\t}\n\n\t\t// Ctrl+C - clear search or cancel if empty\n\t\tif (matchesKey(data, Key.ctrl(\"c\"))) {\n\t\t\tif (this.searchInput.getValue()) {\n\t\t\t\tthis.searchInput.setValue(\"\");\n\t\t\t\tthis.filterItems(\"\");\n\t\t\t} else {\n\t\t\t\tthis.callbacks.onCancel();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Escape - cancel\n\t\tif (matchesKey(data, Key.escape)) {\n\t\t\tthis.callbacks.onCancel();\n\t\t\treturn;\n\t\t}\n\n\t\t// Pass everything else to search input\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.filterItems(this.searchInput.getValue());\n\t}\n\n\tgetSearchInput(): Input {\n\t\treturn this.searchInput;\n\t}\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { type Component, Container } from "@mariozechner/pi-tui";
2
- import type { SessionInfo } from "../../../core/session-manager.js";
2
+ import type { SessionInfo, SessionListProgress } from "../../../core/session-manager.js";
3
3
  /**
4
4
  * Custom session list component with multi-line items and search
5
5
  */
@@ -8,22 +8,36 @@ declare class SessionList implements Component {
8
8
  private filteredSessions;
9
9
  private selectedIndex;
10
10
  private searchInput;
11
+ private showCwd;
11
12
  onSelect?: (sessionPath: string) => void;
12
13
  onCancel?: () => void;
13
14
  onExit: () => void;
15
+ onToggleScope?: () => void;
14
16
  private maxVisible;
15
- constructor(sessions: SessionInfo[]);
17
+ constructor(sessions: SessionInfo[], showCwd: boolean);
18
+ setSessions(sessions: SessionInfo[], showCwd: boolean): void;
16
19
  private filterSessions;
17
20
  invalidate(): void;
18
21
  render(width: number): string[];
19
22
  handleInput(keyData: string): void;
20
23
  }
24
+ type SessionsLoader = (onProgress?: SessionListProgress) => Promise<SessionInfo[]>;
21
25
  /**
22
26
  * Component that renders a session selector
23
27
  */
24
28
  export declare class SessionSelectorComponent extends Container {
25
29
  private sessionList;
26
- constructor(sessions: SessionInfo[], onSelect: (sessionPath: string) => void, onCancel: () => void, onExit: () => void);
30
+ private header;
31
+ private scope;
32
+ private currentSessions;
33
+ private allSessions;
34
+ private currentSessionsLoader;
35
+ private allSessionsLoader;
36
+ private onCancel;
37
+ private requestRender;
38
+ constructor(currentSessionsLoader: SessionsLoader, allSessionsLoader: SessionsLoader, onSelect: (sessionPath: string) => void, onCancel: () => void, onExit: () => void, requestRender: () => void);
39
+ private loadCurrentSessions;
40
+ private toggleScope;
27
41
  getSessionList(): SessionList;
28
42
  }
29
43
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"session-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,SAAS,EACd,SAAS,EAMT,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAKpE;;GAEG;AACH,cAAM,WAAY,YAAW,SAAS;IACrC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,WAAW,CAAQ;IACpB,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,IAAI,CAAY;IACrC,OAAO,CAAC,UAAU,CAAa;IAE/B,YAAY,QAAQ,EAAE,WAAW,EAAE,EAclC;IAED,OAAO,CAAC,cAAc;IAStB,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAqE9B;IAED,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CA4BjC;CACD;AAED;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,SAAS;IACtD,OAAO,CAAC,WAAW,CAAc;IAEjC,YACC,QAAQ,EAAE,WAAW,EAAE,EACvB,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,EACvC,QAAQ,EAAE,MAAM,IAAI,EACpB,MAAM,EAAE,MAAM,IAAI,EA2BlB;IAED,cAAc,IAAI,WAAW,CAE5B;CACD","sourcesContent":["import {\n\ttype Component,\n\tContainer,\n\tgetEditorKeybindings,\n\tInput,\n\tSpacer,\n\tText,\n\ttruncateToWidth,\n} from \"@mariozechner/pi-tui\";\nimport type { SessionInfo } from \"../../../core/session-manager.js\";\nimport { fuzzyFilter } from \"../../../utils/fuzzy.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n/**\n * Custom session list component with multi-line items and search\n */\nclass SessionList implements Component {\n\tprivate allSessions: SessionInfo[] = [];\n\tprivate filteredSessions: SessionInfo[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate searchInput: Input;\n\tpublic onSelect?: (sessionPath: string) => void;\n\tpublic onCancel?: () => void;\n\tpublic onExit: () => void = () => {};\n\tprivate maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)\n\n\tconstructor(sessions: SessionInfo[]) {\n\t\tthis.allSessions = sessions;\n\t\tthis.filteredSessions = sessions;\n\t\tthis.searchInput = new Input();\n\n\t\t// Handle Enter in search input - select current item\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\tif (this.filteredSessions[this.selectedIndex]) {\n\t\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\t\tif (this.onSelect) {\n\t\t\t\t\tthis.onSelect(selected.path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tprivate filterSessions(query: string): void {\n\t\tthis.filteredSessions = fuzzyFilter(\n\t\t\tthis.allSessions,\n\t\t\tquery,\n\t\t\t(session) => `${session.id} ${session.allMessagesText}`,\n\t\t);\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));\n\t}\n\n\tinvalidate(): void {\n\t\t// No cached state to invalidate currently\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Render search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\"); // Blank line after search\n\n\t\tif (this.filteredSessions.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No sessions found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Format dates\n\t\tconst formatDate = (date: Date): string => {\n\t\t\tconst now = new Date();\n\t\t\tconst diffMs = now.getTime() - date.getTime();\n\t\t\tconst diffMins = Math.floor(diffMs / 60000);\n\t\t\tconst diffHours = Math.floor(diffMs / 3600000);\n\t\t\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\t\t\tif (diffMins < 1) return \"just now\";\n\t\t\tif (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n\t\t\tif (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n\t\t\tif (diffDays === 1) return \"1 day ago\";\n\t\t\tif (diffDays < 7) return `${diffDays} days ago`;\n\n\t\t\treturn date.toLocaleDateString();\n\t\t};\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);\n\n\t\t// Render visible sessions (2 lines per session + blank line)\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst session = this.filteredSessions[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Normalize first message to single line\n\t\t\tconst normalizedMessage = session.firstMessage.replace(/\\n/g, \" \").trim();\n\n\t\t\t// First line: cursor + message (truncate to visible width)\n\t\t\tconst cursor = isSelected ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst maxMsgWidth = width - 2; // Account for cursor (2 visible chars)\n\t\t\tconst truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, \"...\");\n\t\t\tconst messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);\n\n\t\t\t// Second line: metadata (dimmed) - also truncate for safety\n\t\t\tconst modified = formatDate(session.modified);\n\t\t\tconst msgCount = `${session.messageCount} message${session.messageCount !== 1 ? \"s\" : \"\"}`;\n\t\t\tconst metadata = ` ${modified} · ${msgCount}`;\n\t\t\tconst metadataLine = theme.fg(\"dim\", truncateToWidth(metadata, width, \"\"));\n\n\t\t\tlines.push(messageLine);\n\t\t\tlines.push(metadataLine);\n\t\t\tlines.push(\"\"); // Blank line between sessions\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredSessions.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;\n\t\t\tconst scrollInfo = theme.fg(\"muted\", truncateToWidth(scrollText, width, \"\"));\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\tif (selected && this.onSelect) {\n\t\t\t\tthis.onSelect(selected.path);\n\t\t\t}\n\t\t}\n\t\t// Escape - cancel\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterSessions(this.searchInput.getValue());\n\t\t}\n\t}\n}\n\n/**\n * Component that renders a session selector\n */\nexport class SessionSelectorComponent extends Container {\n\tprivate sessionList: SessionList;\n\n\tconstructor(\n\t\tsessions: SessionInfo[],\n\t\tonSelect: (sessionPath: string) => void,\n\t\tonCancel: () => void,\n\t\tonExit: () => void,\n\t) {\n\t\tsuper();\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.bold(\"Resume Session\"), 1, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create session list\n\t\tthis.sessionList = new SessionList(sessions);\n\t\tthis.sessionList.onSelect = onSelect;\n\t\tthis.sessionList.onCancel = onCancel;\n\t\tthis.sessionList.onExit = onExit;\n\n\t\tthis.addChild(this.sessionList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Auto-cancel if no sessions\n\t\tif (sessions.length === 0) {\n\t\t\tsetTimeout(() => onCancel(), 100);\n\t\t}\n\t}\n\n\tgetSessionList(): SessionList {\n\t\treturn this.sessionList;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,SAAS,EACd,SAAS,EAOT,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AA+EzF;;GAEG;AACH,cAAM,WAAY,YAAW,SAAS;IACrC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,OAAO,CAAS;IACjB,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,IAAI,CAAY;IAC9B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,OAAO,CAAC,UAAU,CAAa;IAE/B,YAAY,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,EAepD;IAED,WAAW,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAI3D;IAED,OAAO,CAAC,cAAc;IAStB,UAAU,IAAI,IAAI,CAAG;IAErB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAwD9B;IAED,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAkCjC;CACD;AAED,KAAK,cAAc,GAAG,CAAC,UAAU,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;AAEnF;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,SAAS;IACtD,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,qBAAqB,CAAiB;IAC9C,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAa;IAC7B,OAAO,CAAC,aAAa,CAAa;IAElC,YACC,qBAAqB,EAAE,cAAc,EACrC,iBAAiB,EAAE,cAAc,EACjC,QAAQ,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,EACvC,QAAQ,EAAE,MAAM,IAAI,EACpB,MAAM,EAAE,MAAM,IAAI,EAClB,aAAa,EAAE,MAAM,IAAI,EA+BzB;IAED,OAAO,CAAC,mBAAmB;IAkB3B,OAAO,CAAC,WAAW;IAoCnB,cAAc,IAAI,WAAW,CAE5B;CACD","sourcesContent":["import * as os from \"node:os\";\nimport {\n\ttype Component,\n\tContainer,\n\tfuzzyFilter,\n\tgetEditorKeybindings,\n\tInput,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\nimport type { SessionInfo, SessionListProgress } from \"../../../core/session-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\ntype SessionScope = \"current\" | \"all\";\n\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (!path) return path;\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nfunction formatSessionDate(date: Date): string {\n\tconst now = new Date();\n\tconst diffMs = now.getTime() - date.getTime();\n\tconst diffMins = Math.floor(diffMs / 60000);\n\tconst diffHours = Math.floor(diffMs / 3600000);\n\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\tif (diffMins < 1) return \"just now\";\n\tif (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffDays === 1) return \"1 day ago\";\n\tif (diffDays < 7) return `${diffDays} days ago`;\n\n\treturn date.toLocaleDateString();\n}\n\nclass SessionSelectorHeader implements Component {\n\tprivate scope: SessionScope;\n\tprivate loading = false;\n\tprivate loadProgress: { loaded: number; total: number } | null = null;\n\n\tconstructor(scope: SessionScope) {\n\t\tthis.scope = scope;\n\t}\n\n\tsetScope(scope: SessionScope): void {\n\t\tthis.scope = scope;\n\t}\n\n\tsetLoading(loading: boolean): void {\n\t\tthis.loading = loading;\n\t\tif (!loading) {\n\t\t\tthis.loadProgress = null;\n\t\t}\n\t}\n\n\tsetProgress(loaded: number, total: number): void {\n\t\tthis.loadProgress = { loaded, total };\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = this.scope === \"current\" ? \"Resume Session (Current Folder)\" : \"Resume Session (All)\";\n\t\tconst leftText = theme.bold(title);\n\t\tlet scopeText: string;\n\t\tif (this.loading) {\n\t\t\tconst progressText = this.loadProgress ? `${this.loadProgress.loaded}/${this.loadProgress.total}` : \"...\";\n\t\t\tscopeText = `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", `Loading ${progressText}`)}`;\n\t\t} else {\n\t\t\tscopeText =\n\t\t\t\tthis.scope === \"current\"\n\t\t\t\t\t? `${theme.fg(\"accent\", \"◉ Current Folder\")}${theme.fg(\"muted\", \" | ○ All\")}`\n\t\t\t\t\t: `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", \"◉ All\")}`;\n\t\t}\n\t\tconst rightText = truncateToWidth(scopeText, width, \"\");\n\t\tconst availableLeft = Math.max(0, width - visibleWidth(rightText) - 1);\n\t\tconst left = truncateToWidth(leftText, availableLeft, \"\");\n\t\tconst spacing = Math.max(0, width - visibleWidth(left) - visibleWidth(rightText));\n\t\tconst hint = theme.fg(\"muted\", \"Tab to toggle scope\");\n\t\treturn [`${left}${\" \".repeat(spacing)}${rightText}`, hint];\n\t}\n}\n\n/**\n * Custom session list component with multi-line items and search\n */\nclass SessionList implements Component {\n\tprivate allSessions: SessionInfo[] = [];\n\tprivate filteredSessions: SessionInfo[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate searchInput: Input;\n\tprivate showCwd = false;\n\tpublic onSelect?: (sessionPath: string) => void;\n\tpublic onCancel?: () => void;\n\tpublic onExit: () => void = () => {};\n\tpublic onToggleScope?: () => void;\n\tprivate maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)\n\n\tconstructor(sessions: SessionInfo[], showCwd: boolean) {\n\t\tthis.allSessions = sessions;\n\t\tthis.filteredSessions = sessions;\n\t\tthis.searchInput = new Input();\n\t\tthis.showCwd = showCwd;\n\n\t\t// Handle Enter in search input - select current item\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\tif (this.filteredSessions[this.selectedIndex]) {\n\t\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\t\tif (this.onSelect) {\n\t\t\t\t\tthis.onSelect(selected.path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tsetSessions(sessions: SessionInfo[], showCwd: boolean): void {\n\t\tthis.allSessions = sessions;\n\t\tthis.showCwd = showCwd;\n\t\tthis.filterSessions(this.searchInput.getValue());\n\t}\n\n\tprivate filterSessions(query: string): void {\n\t\tthis.filteredSessions = fuzzyFilter(\n\t\t\tthis.allSessions,\n\t\t\tquery,\n\t\t\t(session) => `${session.id} ${session.allMessagesText} ${session.cwd}`,\n\t\t);\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Render search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\"); // Blank line after search\n\n\t\tif (this.filteredSessions.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No sessions found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);\n\n\t\t// Render visible sessions (2 lines per session + blank line)\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst session = this.filteredSessions[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Normalize first message to single line\n\t\t\tconst normalizedMessage = session.firstMessage.replace(/\\n/g, \" \").trim();\n\n\t\t\t// First line: cursor + message (truncate to visible width)\n\t\t\tconst cursor = isSelected ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst maxMsgWidth = width - 2; // Account for cursor (2 visible chars)\n\t\t\tconst truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, \"...\");\n\t\t\tconst messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);\n\n\t\t\t// Second line: metadata (dimmed) - also truncate for safety\n\t\t\tconst modified = formatSessionDate(session.modified);\n\t\t\tconst msgCount = `${session.messageCount} message${session.messageCount !== 1 ? \"s\" : \"\"}`;\n\t\t\tconst metadataParts = [modified, msgCount];\n\t\t\tif (this.showCwd && session.cwd) {\n\t\t\t\tmetadataParts.push(shortenPath(session.cwd));\n\t\t\t}\n\t\t\tconst metadata = ` ${metadataParts.join(\" · \")}`;\n\t\t\tconst metadataLine = theme.fg(\"dim\", truncateToWidth(metadata, width, \"\"));\n\n\t\t\tlines.push(messageLine);\n\t\t\tlines.push(metadataLine);\n\t\t\tlines.push(\"\"); // Blank line between sessions\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredSessions.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;\n\t\t\tconst scrollInfo = theme.fg(\"muted\", truncateToWidth(scrollText, width, \"\"));\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\tif (kb.matches(keyData, \"tab\")) {\n\t\t\tif (this.onToggleScope) {\n\t\t\t\tthis.onToggleScope();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\tif (selected && this.onSelect) {\n\t\t\t\tthis.onSelect(selected.path);\n\t\t\t}\n\t\t}\n\t\t// Escape - cancel\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterSessions(this.searchInput.getValue());\n\t\t}\n\t}\n}\n\ntype SessionsLoader = (onProgress?: SessionListProgress) => Promise<SessionInfo[]>;\n\n/**\n * Component that renders a session selector\n */\nexport class SessionSelectorComponent extends Container {\n\tprivate sessionList: SessionList;\n\tprivate header: SessionSelectorHeader;\n\tprivate scope: SessionScope = \"current\";\n\tprivate currentSessions: SessionInfo[] | null = null;\n\tprivate allSessions: SessionInfo[] | null = null;\n\tprivate currentSessionsLoader: SessionsLoader;\n\tprivate allSessionsLoader: SessionsLoader;\n\tprivate onCancel: () => void;\n\tprivate requestRender: () => void;\n\n\tconstructor(\n\t\tcurrentSessionsLoader: SessionsLoader,\n\t\tallSessionsLoader: SessionsLoader,\n\t\tonSelect: (sessionPath: string) => void,\n\t\tonCancel: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\t\tthis.currentSessionsLoader = currentSessionsLoader;\n\t\tthis.allSessionsLoader = allSessionsLoader;\n\t\tthis.onCancel = onCancel;\n\t\tthis.requestRender = requestRender;\n\t\tthis.header = new SessionSelectorHeader(this.scope);\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.header);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create session list (starts empty, will be populated after load)\n\t\tthis.sessionList = new SessionList([], false);\n\t\tthis.sessionList.onSelect = onSelect;\n\t\tthis.sessionList.onCancel = onCancel;\n\t\tthis.sessionList.onExit = onExit;\n\t\tthis.sessionList.onToggleScope = () => this.toggleScope();\n\n\t\tthis.addChild(this.sessionList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Start loading current sessions immediately\n\t\tthis.loadCurrentSessions();\n\t}\n\n\tprivate loadCurrentSessions(): void {\n\t\tthis.header.setLoading(true);\n\t\tthis.requestRender();\n\t\tthis.currentSessionsLoader((loaded, total) => {\n\t\t\tthis.header.setProgress(loaded, total);\n\t\t\tthis.requestRender();\n\t\t}).then((sessions) => {\n\t\t\tthis.currentSessions = sessions;\n\t\t\tthis.header.setLoading(false);\n\t\t\tthis.sessionList.setSessions(sessions, false);\n\t\t\tthis.requestRender();\n\t\t\t// If no sessions found, cancel\n\t\t\tif (sessions.length === 0) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate toggleScope(): void {\n\t\tif (this.scope === \"current\") {\n\t\t\t// Switching to \"all\" - load if not already loaded\n\t\t\tif (this.allSessions === null) {\n\t\t\t\tthis.header.setLoading(true);\n\t\t\t\tthis.header.setScope(\"all\");\n\t\t\t\tthis.sessionList.setSessions([], true); // Clear list while loading\n\t\t\t\tthis.requestRender();\n\t\t\t\t// Load asynchronously with progress updates\n\t\t\t\tthis.allSessionsLoader((loaded, total) => {\n\t\t\t\t\tthis.header.setProgress(loaded, total);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t}).then((sessions) => {\n\t\t\t\t\tthis.allSessions = sessions;\n\t\t\t\t\tthis.header.setLoading(false);\n\t\t\t\t\tthis.scope = \"all\";\n\t\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t\t// If no sessions in All scope either, cancel\n\t\t\t\t\tif (this.allSessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {\n\t\t\t\t\t\tthis.onCancel();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.scope = \"all\";\n\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\tthis.header.setScope(this.scope);\n\t\t\t}\n\t\t} else {\n\t\t\t// Switching back to \"current\"\n\t\t\tthis.scope = \"current\";\n\t\t\tthis.sessionList.setSessions(this.currentSessions ?? [], false);\n\t\t\tthis.header.setScope(this.scope);\n\t\t}\n\t}\n\n\tgetSessionList(): SessionList {\n\t\treturn this.sessionList;\n\t}\n}\n"]}
@@ -1,7 +1,76 @@
1
- import { Container, getEditorKeybindings, Input, Spacer, Text, truncateToWidth, } from "@mariozechner/pi-tui";
2
- import { fuzzyFilter } from "../../../utils/fuzzy.js";
1
+ import * as os from "node:os";
2
+ import { Container, fuzzyFilter, getEditorKeybindings, Input, Spacer, truncateToWidth, visibleWidth, } from "@mariozechner/pi-tui";
3
3
  import { theme } from "../theme/theme.js";
4
4
  import { DynamicBorder } from "./dynamic-border.js";
5
+ function shortenPath(path) {
6
+ const home = os.homedir();
7
+ if (!path)
8
+ return path;
9
+ if (path.startsWith(home)) {
10
+ return `~${path.slice(home.length)}`;
11
+ }
12
+ return path;
13
+ }
14
+ function formatSessionDate(date) {
15
+ const now = new Date();
16
+ const diffMs = now.getTime() - date.getTime();
17
+ const diffMins = Math.floor(diffMs / 60000);
18
+ const diffHours = Math.floor(diffMs / 3600000);
19
+ const diffDays = Math.floor(diffMs / 86400000);
20
+ if (diffMins < 1)
21
+ return "just now";
22
+ if (diffMins < 60)
23
+ return `${diffMins} minute${diffMins !== 1 ? "s" : ""} ago`;
24
+ if (diffHours < 24)
25
+ return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`;
26
+ if (diffDays === 1)
27
+ return "1 day ago";
28
+ if (diffDays < 7)
29
+ return `${diffDays} days ago`;
30
+ return date.toLocaleDateString();
31
+ }
32
+ class SessionSelectorHeader {
33
+ scope;
34
+ loading = false;
35
+ loadProgress = null;
36
+ constructor(scope) {
37
+ this.scope = scope;
38
+ }
39
+ setScope(scope) {
40
+ this.scope = scope;
41
+ }
42
+ setLoading(loading) {
43
+ this.loading = loading;
44
+ if (!loading) {
45
+ this.loadProgress = null;
46
+ }
47
+ }
48
+ setProgress(loaded, total) {
49
+ this.loadProgress = { loaded, total };
50
+ }
51
+ invalidate() { }
52
+ render(width) {
53
+ const title = this.scope === "current" ? "Resume Session (Current Folder)" : "Resume Session (All)";
54
+ const leftText = theme.bold(title);
55
+ let scopeText;
56
+ if (this.loading) {
57
+ const progressText = this.loadProgress ? `${this.loadProgress.loaded}/${this.loadProgress.total}` : "...";
58
+ scopeText = `${theme.fg("muted", "○ Current Folder | ")}${theme.fg("accent", `Loading ${progressText}`)}`;
59
+ }
60
+ else {
61
+ scopeText =
62
+ this.scope === "current"
63
+ ? `${theme.fg("accent", "◉ Current Folder")}${theme.fg("muted", " | ○ All")}`
64
+ : `${theme.fg("muted", "○ Current Folder | ")}${theme.fg("accent", "◉ All")}`;
65
+ }
66
+ const rightText = truncateToWidth(scopeText, width, "");
67
+ const availableLeft = Math.max(0, width - visibleWidth(rightText) - 1);
68
+ const left = truncateToWidth(leftText, availableLeft, "");
69
+ const spacing = Math.max(0, width - visibleWidth(left) - visibleWidth(rightText));
70
+ const hint = theme.fg("muted", "Tab to toggle scope");
71
+ return [`${left}${" ".repeat(spacing)}${rightText}`, hint];
72
+ }
73
+ }
5
74
  /**
6
75
  * Custom session list component with multi-line items and search
7
76
  */
@@ -10,14 +79,17 @@ class SessionList {
10
79
  filteredSessions = [];
11
80
  selectedIndex = 0;
12
81
  searchInput;
82
+ showCwd = false;
13
83
  onSelect;
14
84
  onCancel;
15
85
  onExit = () => { };
86
+ onToggleScope;
16
87
  maxVisible = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)
17
- constructor(sessions) {
88
+ constructor(sessions, showCwd) {
18
89
  this.allSessions = sessions;
19
90
  this.filteredSessions = sessions;
20
91
  this.searchInput = new Input();
92
+ this.showCwd = showCwd;
21
93
  // Handle Enter in search input - select current item
22
94
  this.searchInput.onSubmit = () => {
23
95
  if (this.filteredSessions[this.selectedIndex]) {
@@ -28,13 +100,16 @@ class SessionList {
28
100
  }
29
101
  };
30
102
  }
103
+ setSessions(sessions, showCwd) {
104
+ this.allSessions = sessions;
105
+ this.showCwd = showCwd;
106
+ this.filterSessions(this.searchInput.getValue());
107
+ }
31
108
  filterSessions(query) {
32
- this.filteredSessions = fuzzyFilter(this.allSessions, query, (session) => `${session.id} ${session.allMessagesText}`);
109
+ this.filteredSessions = fuzzyFilter(this.allSessions, query, (session) => `${session.id} ${session.allMessagesText} ${session.cwd}`);
33
110
  this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));
34
111
  }
35
- invalidate() {
36
- // No cached state to invalidate currently
37
- }
112
+ invalidate() { }
38
113
  render(width) {
39
114
  const lines = [];
40
115
  // Render search input
@@ -44,25 +119,6 @@ class SessionList {
44
119
  lines.push(theme.fg("muted", " No sessions found"));
45
120
  return lines;
46
121
  }
47
- // Format dates
48
- const formatDate = (date) => {
49
- const now = new Date();
50
- const diffMs = now.getTime() - date.getTime();
51
- const diffMins = Math.floor(diffMs / 60000);
52
- const diffHours = Math.floor(diffMs / 3600000);
53
- const diffDays = Math.floor(diffMs / 86400000);
54
- if (diffMins < 1)
55
- return "just now";
56
- if (diffMins < 60)
57
- return `${diffMins} minute${diffMins !== 1 ? "s" : ""} ago`;
58
- if (diffHours < 24)
59
- return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`;
60
- if (diffDays === 1)
61
- return "1 day ago";
62
- if (diffDays < 7)
63
- return `${diffDays} days ago`;
64
- return date.toLocaleDateString();
65
- };
66
122
  // Calculate visible range with scrolling
67
123
  const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible));
68
124
  const endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);
@@ -78,9 +134,13 @@ class SessionList {
78
134
  const truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, "...");
79
135
  const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
80
136
  // Second line: metadata (dimmed) - also truncate for safety
81
- const modified = formatDate(session.modified);
137
+ const modified = formatSessionDate(session.modified);
82
138
  const msgCount = `${session.messageCount} message${session.messageCount !== 1 ? "s" : ""}`;
83
- const metadata = ` ${modified} · ${msgCount}`;
139
+ const metadataParts = [modified, msgCount];
140
+ if (this.showCwd && session.cwd) {
141
+ metadataParts.push(shortenPath(session.cwd));
142
+ }
143
+ const metadata = ` ${metadataParts.join(" · ")}`;
84
144
  const metadataLine = theme.fg("dim", truncateToWidth(metadata, width, ""));
85
145
  lines.push(messageLine);
86
146
  lines.push(metadataLine);
@@ -96,6 +156,12 @@ class SessionList {
96
156
  }
97
157
  handleInput(keyData) {
98
158
  const kb = getEditorKeybindings();
159
+ if (kb.matches(keyData, "tab")) {
160
+ if (this.onToggleScope) {
161
+ this.onToggleScope();
162
+ }
163
+ return;
164
+ }
99
165
  // Up arrow
100
166
  if (kb.matches(keyData, "selectUp")) {
101
167
  this.selectedIndex = Math.max(0, this.selectedIndex - 1);
@@ -129,26 +195,92 @@ class SessionList {
129
195
  */
130
196
  export class SessionSelectorComponent extends Container {
131
197
  sessionList;
132
- constructor(sessions, onSelect, onCancel, onExit) {
198
+ header;
199
+ scope = "current";
200
+ currentSessions = null;
201
+ allSessions = null;
202
+ currentSessionsLoader;
203
+ allSessionsLoader;
204
+ onCancel;
205
+ requestRender;
206
+ constructor(currentSessionsLoader, allSessionsLoader, onSelect, onCancel, onExit, requestRender) {
133
207
  super();
208
+ this.currentSessionsLoader = currentSessionsLoader;
209
+ this.allSessionsLoader = allSessionsLoader;
210
+ this.onCancel = onCancel;
211
+ this.requestRender = requestRender;
212
+ this.header = new SessionSelectorHeader(this.scope);
134
213
  // Add header
135
214
  this.addChild(new Spacer(1));
136
- this.addChild(new Text(theme.bold("Resume Session"), 1, 0));
215
+ this.addChild(this.header);
137
216
  this.addChild(new Spacer(1));
138
217
  this.addChild(new DynamicBorder());
139
218
  this.addChild(new Spacer(1));
140
- // Create session list
141
- this.sessionList = new SessionList(sessions);
219
+ // Create session list (starts empty, will be populated after load)
220
+ this.sessionList = new SessionList([], false);
142
221
  this.sessionList.onSelect = onSelect;
143
222
  this.sessionList.onCancel = onCancel;
144
223
  this.sessionList.onExit = onExit;
224
+ this.sessionList.onToggleScope = () => this.toggleScope();
145
225
  this.addChild(this.sessionList);
146
226
  // Add bottom border
147
227
  this.addChild(new Spacer(1));
148
228
  this.addChild(new DynamicBorder());
149
- // Auto-cancel if no sessions
150
- if (sessions.length === 0) {
151
- setTimeout(() => onCancel(), 100);
229
+ // Start loading current sessions immediately
230
+ this.loadCurrentSessions();
231
+ }
232
+ loadCurrentSessions() {
233
+ this.header.setLoading(true);
234
+ this.requestRender();
235
+ this.currentSessionsLoader((loaded, total) => {
236
+ this.header.setProgress(loaded, total);
237
+ this.requestRender();
238
+ }).then((sessions) => {
239
+ this.currentSessions = sessions;
240
+ this.header.setLoading(false);
241
+ this.sessionList.setSessions(sessions, false);
242
+ this.requestRender();
243
+ // If no sessions found, cancel
244
+ if (sessions.length === 0) {
245
+ this.onCancel();
246
+ }
247
+ });
248
+ }
249
+ toggleScope() {
250
+ if (this.scope === "current") {
251
+ // Switching to "all" - load if not already loaded
252
+ if (this.allSessions === null) {
253
+ this.header.setLoading(true);
254
+ this.header.setScope("all");
255
+ this.sessionList.setSessions([], true); // Clear list while loading
256
+ this.requestRender();
257
+ // Load asynchronously with progress updates
258
+ this.allSessionsLoader((loaded, total) => {
259
+ this.header.setProgress(loaded, total);
260
+ this.requestRender();
261
+ }).then((sessions) => {
262
+ this.allSessions = sessions;
263
+ this.header.setLoading(false);
264
+ this.scope = "all";
265
+ this.sessionList.setSessions(this.allSessions, true);
266
+ this.requestRender();
267
+ // If no sessions in All scope either, cancel
268
+ if (this.allSessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {
269
+ this.onCancel();
270
+ }
271
+ });
272
+ }
273
+ else {
274
+ this.scope = "all";
275
+ this.sessionList.setSessions(this.allSessions, true);
276
+ this.header.setScope(this.scope);
277
+ }
278
+ }
279
+ else {
280
+ // Switching back to "current"
281
+ this.scope = "current";
282
+ this.sessionList.setSessions(this.currentSessions ?? [], false);
283
+ this.header.setScope(this.scope);
152
284
  }
153
285
  }
154
286
  getSessionList() {
@@ -1 +1 @@
1
- {"version":3,"file":"session-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Component,\n\tContainer,\n\tgetEditorKeybindings,\n\tInput,\n\tSpacer,\n\tText,\n\ttruncateToWidth,\n} from \"@mariozechner/pi-tui\";\nimport type { SessionInfo } from \"../../../core/session-manager.js\";\nimport { fuzzyFilter } from \"../../../utils/fuzzy.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\n/**\n * Custom session list component with multi-line items and search\n */\nclass SessionList implements Component {\n\tprivate allSessions: SessionInfo[] = [];\n\tprivate filteredSessions: SessionInfo[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate searchInput: Input;\n\tpublic onSelect?: (sessionPath: string) => void;\n\tpublic onCancel?: () => void;\n\tpublic onExit: () => void = () => {};\n\tprivate maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)\n\n\tconstructor(sessions: SessionInfo[]) {\n\t\tthis.allSessions = sessions;\n\t\tthis.filteredSessions = sessions;\n\t\tthis.searchInput = new Input();\n\n\t\t// Handle Enter in search input - select current item\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\tif (this.filteredSessions[this.selectedIndex]) {\n\t\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\t\tif (this.onSelect) {\n\t\t\t\t\tthis.onSelect(selected.path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tprivate filterSessions(query: string): void {\n\t\tthis.filteredSessions = fuzzyFilter(\n\t\t\tthis.allSessions,\n\t\t\tquery,\n\t\t\t(session) => `${session.id} ${session.allMessagesText}`,\n\t\t);\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));\n\t}\n\n\tinvalidate(): void {\n\t\t// No cached state to invalidate currently\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Render search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\"); // Blank line after search\n\n\t\tif (this.filteredSessions.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No sessions found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Format dates\n\t\tconst formatDate = (date: Date): string => {\n\t\t\tconst now = new Date();\n\t\t\tconst diffMs = now.getTime() - date.getTime();\n\t\t\tconst diffMins = Math.floor(diffMs / 60000);\n\t\t\tconst diffHours = Math.floor(diffMs / 3600000);\n\t\t\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\t\t\tif (diffMins < 1) return \"just now\";\n\t\t\tif (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n\t\t\tif (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n\t\t\tif (diffDays === 1) return \"1 day ago\";\n\t\t\tif (diffDays < 7) return `${diffDays} days ago`;\n\n\t\t\treturn date.toLocaleDateString();\n\t\t};\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);\n\n\t\t// Render visible sessions (2 lines per session + blank line)\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst session = this.filteredSessions[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Normalize first message to single line\n\t\t\tconst normalizedMessage = session.firstMessage.replace(/\\n/g, \" \").trim();\n\n\t\t\t// First line: cursor + message (truncate to visible width)\n\t\t\tconst cursor = isSelected ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst maxMsgWidth = width - 2; // Account for cursor (2 visible chars)\n\t\t\tconst truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, \"...\");\n\t\t\tconst messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);\n\n\t\t\t// Second line: metadata (dimmed) - also truncate for safety\n\t\t\tconst modified = formatDate(session.modified);\n\t\t\tconst msgCount = `${session.messageCount} message${session.messageCount !== 1 ? \"s\" : \"\"}`;\n\t\t\tconst metadata = ` ${modified} · ${msgCount}`;\n\t\t\tconst metadataLine = theme.fg(\"dim\", truncateToWidth(metadata, width, \"\"));\n\n\t\t\tlines.push(messageLine);\n\t\t\tlines.push(metadataLine);\n\t\t\tlines.push(\"\"); // Blank line between sessions\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredSessions.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;\n\t\t\tconst scrollInfo = theme.fg(\"muted\", truncateToWidth(scrollText, width, \"\"));\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\tif (selected && this.onSelect) {\n\t\t\t\tthis.onSelect(selected.path);\n\t\t\t}\n\t\t}\n\t\t// Escape - cancel\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterSessions(this.searchInput.getValue());\n\t\t}\n\t}\n}\n\n/**\n * Component that renders a session selector\n */\nexport class SessionSelectorComponent extends Container {\n\tprivate sessionList: SessionList;\n\n\tconstructor(\n\t\tsessions: SessionInfo[],\n\t\tonSelect: (sessionPath: string) => void,\n\t\tonCancel: () => void,\n\t\tonExit: () => void,\n\t) {\n\t\tsuper();\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.bold(\"Resume Session\"), 1, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create session list\n\t\tthis.sessionList = new SessionList(sessions);\n\t\tthis.sessionList.onSelect = onSelect;\n\t\tthis.sessionList.onCancel = onCancel;\n\t\tthis.sessionList.onExit = onExit;\n\n\t\tthis.addChild(this.sessionList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Auto-cancel if no sessions\n\t\tif (sessions.length === 0) {\n\t\t\tsetTimeout(() => onCancel(), 100);\n\t\t}\n\t}\n\n\tgetSessionList(): SessionList {\n\t\treturn this.sessionList;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/session-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport {\n\ttype Component,\n\tContainer,\n\tfuzzyFilter,\n\tgetEditorKeybindings,\n\tInput,\n\tSpacer,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@mariozechner/pi-tui\";\nimport type { SessionInfo, SessionListProgress } from \"../../../core/session-manager.js\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\ntype SessionScope = \"current\" | \"all\";\n\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (!path) return path;\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nfunction formatSessionDate(date: Date): string {\n\tconst now = new Date();\n\tconst diffMs = now.getTime() - date.getTime();\n\tconst diffMins = Math.floor(diffMs / 60000);\n\tconst diffHours = Math.floor(diffMs / 3600000);\n\tconst diffDays = Math.floor(diffMs / 86400000);\n\n\tif (diffMins < 1) return \"just now\";\n\tif (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? \"s\" : \"\"} ago`;\n\tif (diffDays === 1) return \"1 day ago\";\n\tif (diffDays < 7) return `${diffDays} days ago`;\n\n\treturn date.toLocaleDateString();\n}\n\nclass SessionSelectorHeader implements Component {\n\tprivate scope: SessionScope;\n\tprivate loading = false;\n\tprivate loadProgress: { loaded: number; total: number } | null = null;\n\n\tconstructor(scope: SessionScope) {\n\t\tthis.scope = scope;\n\t}\n\n\tsetScope(scope: SessionScope): void {\n\t\tthis.scope = scope;\n\t}\n\n\tsetLoading(loading: boolean): void {\n\t\tthis.loading = loading;\n\t\tif (!loading) {\n\t\t\tthis.loadProgress = null;\n\t\t}\n\t}\n\n\tsetProgress(loaded: number, total: number): void {\n\t\tthis.loadProgress = { loaded, total };\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst title = this.scope === \"current\" ? \"Resume Session (Current Folder)\" : \"Resume Session (All)\";\n\t\tconst leftText = theme.bold(title);\n\t\tlet scopeText: string;\n\t\tif (this.loading) {\n\t\t\tconst progressText = this.loadProgress ? `${this.loadProgress.loaded}/${this.loadProgress.total}` : \"...\";\n\t\t\tscopeText = `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", `Loading ${progressText}`)}`;\n\t\t} else {\n\t\t\tscopeText =\n\t\t\t\tthis.scope === \"current\"\n\t\t\t\t\t? `${theme.fg(\"accent\", \"◉ Current Folder\")}${theme.fg(\"muted\", \" | ○ All\")}`\n\t\t\t\t\t: `${theme.fg(\"muted\", \"○ Current Folder | \")}${theme.fg(\"accent\", \"◉ All\")}`;\n\t\t}\n\t\tconst rightText = truncateToWidth(scopeText, width, \"\");\n\t\tconst availableLeft = Math.max(0, width - visibleWidth(rightText) - 1);\n\t\tconst left = truncateToWidth(leftText, availableLeft, \"\");\n\t\tconst spacing = Math.max(0, width - visibleWidth(left) - visibleWidth(rightText));\n\t\tconst hint = theme.fg(\"muted\", \"Tab to toggle scope\");\n\t\treturn [`${left}${\" \".repeat(spacing)}${rightText}`, hint];\n\t}\n}\n\n/**\n * Custom session list component with multi-line items and search\n */\nclass SessionList implements Component {\n\tprivate allSessions: SessionInfo[] = [];\n\tprivate filteredSessions: SessionInfo[] = [];\n\tprivate selectedIndex: number = 0;\n\tprivate searchInput: Input;\n\tprivate showCwd = false;\n\tpublic onSelect?: (sessionPath: string) => void;\n\tpublic onCancel?: () => void;\n\tpublic onExit: () => void = () => {};\n\tpublic onToggleScope?: () => void;\n\tprivate maxVisible: number = 5; // Max sessions visible (each session is 3 lines: msg + metadata + blank)\n\n\tconstructor(sessions: SessionInfo[], showCwd: boolean) {\n\t\tthis.allSessions = sessions;\n\t\tthis.filteredSessions = sessions;\n\t\tthis.searchInput = new Input();\n\t\tthis.showCwd = showCwd;\n\n\t\t// Handle Enter in search input - select current item\n\t\tthis.searchInput.onSubmit = () => {\n\t\t\tif (this.filteredSessions[this.selectedIndex]) {\n\t\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\t\tif (this.onSelect) {\n\t\t\t\t\tthis.onSelect(selected.path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\tsetSessions(sessions: SessionInfo[], showCwd: boolean): void {\n\t\tthis.allSessions = sessions;\n\t\tthis.showCwd = showCwd;\n\t\tthis.filterSessions(this.searchInput.getValue());\n\t}\n\n\tprivate filterSessions(query: string): void {\n\t\tthis.filteredSessions = fuzzyFilter(\n\t\t\tthis.allSessions,\n\t\t\tquery,\n\t\t\t(session) => `${session.id} ${session.allMessagesText} ${session.cwd}`,\n\t\t);\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\t// Render search input\n\t\tlines.push(...this.searchInput.render(width));\n\t\tlines.push(\"\"); // Blank line after search\n\n\t\tif (this.filteredSessions.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No sessions found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate visible range with scrolling\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);\n\n\t\t// Render visible sessions (2 lines per session + blank line)\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst session = this.filteredSessions[i];\n\t\t\tconst isSelected = i === this.selectedIndex;\n\n\t\t\t// Normalize first message to single line\n\t\t\tconst normalizedMessage = session.firstMessage.replace(/\\n/g, \" \").trim();\n\n\t\t\t// First line: cursor + message (truncate to visible width)\n\t\t\tconst cursor = isSelected ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst maxMsgWidth = width - 2; // Account for cursor (2 visible chars)\n\t\t\tconst truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, \"...\");\n\t\t\tconst messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);\n\n\t\t\t// Second line: metadata (dimmed) - also truncate for safety\n\t\t\tconst modified = formatSessionDate(session.modified);\n\t\t\tconst msgCount = `${session.messageCount} message${session.messageCount !== 1 ? \"s\" : \"\"}`;\n\t\t\tconst metadataParts = [modified, msgCount];\n\t\t\tif (this.showCwd && session.cwd) {\n\t\t\t\tmetadataParts.push(shortenPath(session.cwd));\n\t\t\t}\n\t\t\tconst metadata = ` ${metadataParts.join(\" · \")}`;\n\t\t\tconst metadataLine = theme.fg(\"dim\", truncateToWidth(metadata, width, \"\"));\n\n\t\t\tlines.push(messageLine);\n\t\t\tlines.push(metadataLine);\n\t\t\tlines.push(\"\"); // Blank line between sessions\n\t\t}\n\n\t\t// Add scroll indicator if needed\n\t\tif (startIndex > 0 || endIndex < this.filteredSessions.length) {\n\t\t\tconst scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;\n\t\t\tconst scrollInfo = theme.fg(\"muted\", truncateToWidth(scrollText, width, \"\"));\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getEditorKeybindings();\n\t\tif (kb.matches(keyData, \"tab\")) {\n\t\t\tif (this.onToggleScope) {\n\t\t\t\tthis.onToggleScope();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\t// Up arrow\n\t\tif (kb.matches(keyData, \"selectUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t}\n\t\t// Down arrow\n\t\telse if (kb.matches(keyData, \"selectDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);\n\t\t}\n\t\t// Enter\n\t\telse if (kb.matches(keyData, \"selectConfirm\")) {\n\t\t\tconst selected = this.filteredSessions[this.selectedIndex];\n\t\t\tif (selected && this.onSelect) {\n\t\t\t\tthis.onSelect(selected.path);\n\t\t\t}\n\t\t}\n\t\t// Escape - cancel\n\t\telse if (kb.matches(keyData, \"selectCancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t\t// Pass everything else to search input\n\t\telse {\n\t\t\tthis.searchInput.handleInput(keyData);\n\t\t\tthis.filterSessions(this.searchInput.getValue());\n\t\t}\n\t}\n}\n\ntype SessionsLoader = (onProgress?: SessionListProgress) => Promise<SessionInfo[]>;\n\n/**\n * Component that renders a session selector\n */\nexport class SessionSelectorComponent extends Container {\n\tprivate sessionList: SessionList;\n\tprivate header: SessionSelectorHeader;\n\tprivate scope: SessionScope = \"current\";\n\tprivate currentSessions: SessionInfo[] | null = null;\n\tprivate allSessions: SessionInfo[] | null = null;\n\tprivate currentSessionsLoader: SessionsLoader;\n\tprivate allSessionsLoader: SessionsLoader;\n\tprivate onCancel: () => void;\n\tprivate requestRender: () => void;\n\n\tconstructor(\n\t\tcurrentSessionsLoader: SessionsLoader,\n\t\tallSessionsLoader: SessionsLoader,\n\t\tonSelect: (sessionPath: string) => void,\n\t\tonCancel: () => void,\n\t\tonExit: () => void,\n\t\trequestRender: () => void,\n\t) {\n\t\tsuper();\n\t\tthis.currentSessionsLoader = currentSessionsLoader;\n\t\tthis.allSessionsLoader = allSessionsLoader;\n\t\tthis.onCancel = onCancel;\n\t\tthis.requestRender = requestRender;\n\t\tthis.header = new SessionSelectorHeader(this.scope);\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.header);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create session list (starts empty, will be populated after load)\n\t\tthis.sessionList = new SessionList([], false);\n\t\tthis.sessionList.onSelect = onSelect;\n\t\tthis.sessionList.onCancel = onCancel;\n\t\tthis.sessionList.onExit = onExit;\n\t\tthis.sessionList.onToggleScope = () => this.toggleScope();\n\n\t\tthis.addChild(this.sessionList);\n\n\t\t// Add bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Start loading current sessions immediately\n\t\tthis.loadCurrentSessions();\n\t}\n\n\tprivate loadCurrentSessions(): void {\n\t\tthis.header.setLoading(true);\n\t\tthis.requestRender();\n\t\tthis.currentSessionsLoader((loaded, total) => {\n\t\t\tthis.header.setProgress(loaded, total);\n\t\t\tthis.requestRender();\n\t\t}).then((sessions) => {\n\t\t\tthis.currentSessions = sessions;\n\t\t\tthis.header.setLoading(false);\n\t\t\tthis.sessionList.setSessions(sessions, false);\n\t\t\tthis.requestRender();\n\t\t\t// If no sessions found, cancel\n\t\t\tif (sessions.length === 0) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate toggleScope(): void {\n\t\tif (this.scope === \"current\") {\n\t\t\t// Switching to \"all\" - load if not already loaded\n\t\t\tif (this.allSessions === null) {\n\t\t\t\tthis.header.setLoading(true);\n\t\t\t\tthis.header.setScope(\"all\");\n\t\t\t\tthis.sessionList.setSessions([], true); // Clear list while loading\n\t\t\t\tthis.requestRender();\n\t\t\t\t// Load asynchronously with progress updates\n\t\t\t\tthis.allSessionsLoader((loaded, total) => {\n\t\t\t\t\tthis.header.setProgress(loaded, total);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t}).then((sessions) => {\n\t\t\t\t\tthis.allSessions = sessions;\n\t\t\t\t\tthis.header.setLoading(false);\n\t\t\t\t\tthis.scope = \"all\";\n\t\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\t\tthis.requestRender();\n\t\t\t\t\t// If no sessions in All scope either, cancel\n\t\t\t\t\tif (this.allSessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {\n\t\t\t\t\t\tthis.onCancel();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.scope = \"all\";\n\t\t\t\tthis.sessionList.setSessions(this.allSessions, true);\n\t\t\t\tthis.header.setScope(this.scope);\n\t\t\t}\n\t\t} else {\n\t\t\t// Switching back to \"current\"\n\t\t\tthis.scope = \"current\";\n\t\t\tthis.sessionList.setSessions(this.currentSessions ?? [], false);\n\t\t\tthis.header.setScope(this.scope);\n\t\t}\n\t}\n\n\tgetSessionList(): SessionList {\n\t\treturn this.sessionList;\n\t}\n}\n"]}
@@ -5,6 +5,7 @@ export interface SettingsConfig {
5
5
  showImages: boolean;
6
6
  autoResizeImages: boolean;
7
7
  blockImages: boolean;
8
+ enableSkillCommands: boolean;
8
9
  steeringMode: "all" | "one-at-a-time";
9
10
  followUpMode: "all" | "one-at-a-time";
10
11
  thinkingLevel: ThinkingLevel;
@@ -13,13 +14,14 @@ export interface SettingsConfig {
13
14
  availableThemes: string[];
14
15
  hideThinkingBlock: boolean;
15
16
  collapseChangelog: boolean;
16
- doubleEscapeAction: "branch" | "tree";
17
+ doubleEscapeAction: "fork" | "tree";
17
18
  }
18
19
  export interface SettingsCallbacks {
19
20
  onAutoCompactChange: (enabled: boolean) => void;
20
21
  onShowImagesChange: (enabled: boolean) => void;
21
22
  onAutoResizeImagesChange: (enabled: boolean) => void;
22
23
  onBlockImagesChange: (blocked: boolean) => void;
24
+ onEnableSkillCommandsChange: (enabled: boolean) => void;
23
25
  onSteeringModeChange: (mode: "all" | "one-at-a-time") => void;
24
26
  onFollowUpModeChange: (mode: "all" | "one-at-a-time") => void;
25
27
  onThinkingLevelChange: (level: ThinkingLevel) => void;
@@ -27,7 +29,7 @@ export interface SettingsCallbacks {
27
29
  onThemePreview?: (theme: string) => void;
28
30
  onHideThinkingBlockChange: (hidden: boolean) => void;
29
31
  onCollapseChangelogChange: (collapsed: boolean) => void;
30
- onDoubleEscapeActionChange: (action: "branch" | "tree") => void;
32
+ onDoubleEscapeActionChange: (action: "fork" | "tree") => void;
31
33
  onCancel: () => void;
32
34
  }
33
35
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"settings-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/settings-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EACN,SAAS,EAKT,YAAY,EAGZ,MAAM,sBAAsB,CAAC;AAa9B,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,KAAK,GAAG,eAAe,CAAC;IACtC,YAAY,EAAE,KAAK,GAAG,eAAe,CAAC;IACtC,aAAa,EAAE,aAAa,CAAC;IAC7B,uBAAuB,EAAE,aAAa,EAAE,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kBAAkB,EAAE,QAAQ,GAAG,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,iBAAiB;IACjC,mBAAmB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,wBAAwB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,mBAAmB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,oBAAoB,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,eAAe,KAAK,IAAI,CAAC;IAC9D,oBAAoB,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,eAAe,KAAK,IAAI,CAAC;IAC9D,qBAAqB,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,yBAAyB,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,yBAAyB,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,0BAA0B,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,KAAK,IAAI,CAAC;IAChE,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;AAgED;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,YAAY,CAAe;IAEnC,YAAY,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,iBAAiB,EAiL/D;IAED,eAAe,IAAI,YAAY,CAE9B;CACD","sourcesContent":["import type { ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport {\n\tContainer,\n\tgetCapabilities,\n\ttype SelectItem,\n\tSelectList,\n\ttype SettingItem,\n\tSettingsList,\n\tSpacer,\n\tText,\n} from \"@mariozechner/pi-tui\";\nimport { getSelectListTheme, getSettingsListTheme, theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\nconst THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\nexport interface SettingsConfig {\n\tautoCompact: boolean;\n\tshowImages: boolean;\n\tautoResizeImages: boolean;\n\tblockImages: boolean;\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\tthinkingLevel: ThinkingLevel;\n\tavailableThinkingLevels: ThinkingLevel[];\n\tcurrentTheme: string;\n\tavailableThemes: string[];\n\thideThinkingBlock: boolean;\n\tcollapseChangelog: boolean;\n\tdoubleEscapeAction: \"branch\" | \"tree\";\n}\n\nexport interface SettingsCallbacks {\n\tonAutoCompactChange: (enabled: boolean) => void;\n\tonShowImagesChange: (enabled: boolean) => void;\n\tonAutoResizeImagesChange: (enabled: boolean) => void;\n\tonBlockImagesChange: (blocked: boolean) => void;\n\tonSteeringModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonFollowUpModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonThinkingLevelChange: (level: ThinkingLevel) => void;\n\tonThemeChange: (theme: string) => void;\n\tonThemePreview?: (theme: string) => void;\n\tonHideThinkingBlockChange: (hidden: boolean) => void;\n\tonCollapseChangelogChange: (collapsed: boolean) => void;\n\tonDoubleEscapeActionChange: (action: \"branch\" | \"tree\") => void;\n\tonCancel: () => void;\n}\n\n/**\n * A submenu component for selecting from a list of options.\n */\nclass SelectSubmenu extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\ttitle: string,\n\t\tdescription: string,\n\t\toptions: SelectItem[],\n\t\tcurrentValue: string,\n\t\tonSelect: (value: string) => void,\n\t\tonCancel: () => void,\n\t\tonSelectionChange?: (value: string) => void,\n\t) {\n\t\tsuper();\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", title)), 0, 0));\n\n\t\t// Description\n\t\tif (description) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(theme.fg(\"muted\", description), 0, 0));\n\t\t}\n\n\t\t// Spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Select list\n\t\tthis.selectList = new SelectList(options, Math.min(options.length, 10), getSelectListTheme());\n\n\t\t// Pre-select current value\n\t\tconst currentIndex = options.findIndex((o) => o.value === currentValue);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value);\n\t\t};\n\n\t\tthis.selectList.onCancel = onCancel;\n\n\t\tif (onSelectionChange) {\n\t\t\tthis.selectList.onSelectionChange = (item) => {\n\t\t\t\tonSelectionChange(item.value);\n\t\t\t};\n\t\t}\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"dim\", \" Enter to select · Esc to go back\"), 0, 0));\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.selectList.handleInput(data);\n\t}\n}\n\n/**\n * Main settings selector component.\n */\nexport class SettingsSelectorComponent extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: SettingsConfig, callbacks: SettingsCallbacks) {\n\t\tsuper();\n\n\t\tconst supportsImages = getCapabilities().images;\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"autocompact\",\n\t\t\t\tlabel: \"Auto-compact\",\n\t\t\t\tdescription: \"Automatically compact context when it gets too large\",\n\t\t\t\tcurrentValue: config.autoCompact ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"steering-mode\",\n\t\t\t\tlabel: \"Steering mode\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enter while streaming queues steering messages. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.\",\n\t\t\t\tcurrentValue: config.steeringMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"follow-up-mode\",\n\t\t\t\tlabel: \"Follow-up mode\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Alt+Enter queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.\",\n\t\t\t\tcurrentValue: config.followUpMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"hide-thinking\",\n\t\t\t\tlabel: \"Hide thinking\",\n\t\t\t\tdescription: \"Hide thinking blocks in assistant responses\",\n\t\t\t\tcurrentValue: config.hideThinkingBlock ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"collapse-changelog\",\n\t\t\t\tlabel: \"Collapse changelog\",\n\t\t\t\tdescription: \"Show condensed changelog after updates\",\n\t\t\t\tcurrentValue: config.collapseChangelog ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"double-escape-action\",\n\t\t\t\tlabel: \"Double-escape action\",\n\t\t\t\tdescription: \"Action when pressing Escape twice with empty editor\",\n\t\t\t\tcurrentValue: config.doubleEscapeAction,\n\t\t\t\tvalues: [\"tree\", \"branch\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"thinking\",\n\t\t\t\tlabel: \"Thinking level\",\n\t\t\t\tdescription: \"Reasoning depth for thinking-capable models\",\n\t\t\t\tcurrentValue: config.thinkingLevel,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Thinking Level\",\n\t\t\t\t\t\t\"Select reasoning depth for thinking-capable models\",\n\t\t\t\t\t\tconfig.availableThinkingLevels.map((level) => ({\n\t\t\t\t\t\t\tvalue: level,\n\t\t\t\t\t\t\tlabel: level,\n\t\t\t\t\t\t\tdescription: THINKING_DESCRIPTIONS[level],\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThinkingLevelChange(value as ThinkingLevel);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"theme\",\n\t\t\t\tlabel: \"Theme\",\n\t\t\t\tdescription: \"Color theme for the interface\",\n\t\t\t\tcurrentValue: config.currentTheme,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Theme\",\n\t\t\t\t\t\t\"Select color theme\",\n\t\t\t\t\t\tconfig.availableThemes.map((t) => ({\n\t\t\t\t\t\t\tvalue: t,\n\t\t\t\t\t\t\tlabel: t,\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThemeChange(value);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t// Restore original theme on cancel\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(currentValue);\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\t// Preview theme on selection change\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\n\t\t// Only show image toggle if terminal supports it\n\t\tif (supportsImages) {\n\t\t\t// Insert after autocompact\n\t\t\titems.splice(1, 0, {\n\t\t\t\tid: \"show-images\",\n\t\t\t\tlabel: \"Show images\",\n\t\t\t\tdescription: \"Render images inline in terminal\",\n\t\t\t\tcurrentValue: config.showImages ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t});\n\t\t}\n\n\t\t// Image auto-resize toggle (always available, affects both attached and read images)\n\t\titems.splice(supportsImages ? 2 : 1, 0, {\n\t\t\tid: \"auto-resize-images\",\n\t\t\tlabel: \"Auto-resize images\",\n\t\t\tdescription: \"Resize large images to 2000x2000 max for better model compatibility\",\n\t\t\tcurrentValue: config.autoResizeImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Block images toggle (always available, insert after auto-resize-images)\n\t\tconst autoResizeIndex = items.findIndex((item) => item.id === \"auto-resize-images\");\n\t\titems.splice(autoResizeIndex + 1, 0, {\n\t\t\tid: \"block-images\",\n\t\t\tlabel: \"Block images\",\n\t\t\tdescription: \"Prevent images from being sent to LLM providers\",\n\t\t\tcurrentValue: config.blockImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Add borders\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\t10,\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"autocompact\":\n\t\t\t\t\t\tcallbacks.onAutoCompactChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"show-images\":\n\t\t\t\t\t\tcallbacks.onShowImagesChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"auto-resize-images\":\n\t\t\t\t\t\tcallbacks.onAutoResizeImagesChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"block-images\":\n\t\t\t\t\t\tcallbacks.onBlockImagesChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"steering-mode\":\n\t\t\t\t\t\tcallbacks.onSteeringModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"follow-up-mode\":\n\t\t\t\t\t\tcallbacks.onFollowUpModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"hide-thinking\":\n\t\t\t\t\t\tcallbacks.onHideThinkingBlockChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"collapse-changelog\":\n\t\t\t\t\t\tcallbacks.onCollapseChangelogChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"double-escape-action\":\n\t\t\t\t\t\tcallbacks.onDoubleEscapeActionChange(newValue as \"branch\" | \"tree\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcallbacks.onCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSettingsList(): SettingsList {\n\t\treturn this.settingsList;\n\t}\n}\n"]}
1
+ {"version":3,"file":"settings-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/settings-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EACN,SAAS,EAKT,YAAY,EAGZ,MAAM,sBAAsB,CAAC;AAa9B,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,YAAY,EAAE,KAAK,GAAG,eAAe,CAAC;IACtC,YAAY,EAAE,KAAK,GAAG,eAAe,CAAC;IACtC,aAAa,EAAE,aAAa,CAAC;IAC7B,uBAAuB,EAAE,aAAa,EAAE,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IACjC,mBAAmB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,wBAAwB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,mBAAmB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,2BAA2B,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,oBAAoB,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,eAAe,KAAK,IAAI,CAAC;IAC9D,oBAAoB,EAAE,CAAC,IAAI,EAAE,KAAK,GAAG,eAAe,KAAK,IAAI,CAAC;IAC9D,qBAAqB,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,yBAAyB,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACrD,yBAAyB,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,0BAA0B,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,KAAK,IAAI,CAAC;IAC9D,QAAQ,EAAE,MAAM,IAAI,CAAC;CACrB;AAgED;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,YAAY,CAAe;IAEnC,YAAY,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,iBAAiB,EA8L/D;IAED,eAAe,IAAI,YAAY,CAE9B;CACD","sourcesContent":["import type { ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport {\n\tContainer,\n\tgetCapabilities,\n\ttype SelectItem,\n\tSelectList,\n\ttype SettingItem,\n\tSettingsList,\n\tSpacer,\n\tText,\n} from \"@mariozechner/pi-tui\";\nimport { getSelectListTheme, getSettingsListTheme, theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\nconst THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\nexport interface SettingsConfig {\n\tautoCompact: boolean;\n\tshowImages: boolean;\n\tautoResizeImages: boolean;\n\tblockImages: boolean;\n\tenableSkillCommands: boolean;\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\tthinkingLevel: ThinkingLevel;\n\tavailableThinkingLevels: ThinkingLevel[];\n\tcurrentTheme: string;\n\tavailableThemes: string[];\n\thideThinkingBlock: boolean;\n\tcollapseChangelog: boolean;\n\tdoubleEscapeAction: \"fork\" | \"tree\";\n}\n\nexport interface SettingsCallbacks {\n\tonAutoCompactChange: (enabled: boolean) => void;\n\tonShowImagesChange: (enabled: boolean) => void;\n\tonAutoResizeImagesChange: (enabled: boolean) => void;\n\tonBlockImagesChange: (blocked: boolean) => void;\n\tonEnableSkillCommandsChange: (enabled: boolean) => void;\n\tonSteeringModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonFollowUpModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonThinkingLevelChange: (level: ThinkingLevel) => void;\n\tonThemeChange: (theme: string) => void;\n\tonThemePreview?: (theme: string) => void;\n\tonHideThinkingBlockChange: (hidden: boolean) => void;\n\tonCollapseChangelogChange: (collapsed: boolean) => void;\n\tonDoubleEscapeActionChange: (action: \"fork\" | \"tree\") => void;\n\tonCancel: () => void;\n}\n\n/**\n * A submenu component for selecting from a list of options.\n */\nclass SelectSubmenu extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\ttitle: string,\n\t\tdescription: string,\n\t\toptions: SelectItem[],\n\t\tcurrentValue: string,\n\t\tonSelect: (value: string) => void,\n\t\tonCancel: () => void,\n\t\tonSelectionChange?: (value: string) => void,\n\t) {\n\t\tsuper();\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", title)), 0, 0));\n\n\t\t// Description\n\t\tif (description) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(theme.fg(\"muted\", description), 0, 0));\n\t\t}\n\n\t\t// Spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Select list\n\t\tthis.selectList = new SelectList(options, Math.min(options.length, 10), getSelectListTheme());\n\n\t\t// Pre-select current value\n\t\tconst currentIndex = options.findIndex((o) => o.value === currentValue);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value);\n\t\t};\n\n\t\tthis.selectList.onCancel = onCancel;\n\n\t\tif (onSelectionChange) {\n\t\t\tthis.selectList.onSelectionChange = (item) => {\n\t\t\t\tonSelectionChange(item.value);\n\t\t\t};\n\t\t}\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"dim\", \" Enter to select · Esc to go back\"), 0, 0));\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.selectList.handleInput(data);\n\t}\n}\n\n/**\n * Main settings selector component.\n */\nexport class SettingsSelectorComponent extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: SettingsConfig, callbacks: SettingsCallbacks) {\n\t\tsuper();\n\n\t\tconst supportsImages = getCapabilities().images;\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"autocompact\",\n\t\t\t\tlabel: \"Auto-compact\",\n\t\t\t\tdescription: \"Automatically compact context when it gets too large\",\n\t\t\t\tcurrentValue: config.autoCompact ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"steering-mode\",\n\t\t\t\tlabel: \"Steering mode\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enter while streaming queues steering messages. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.\",\n\t\t\t\tcurrentValue: config.steeringMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"follow-up-mode\",\n\t\t\t\tlabel: \"Follow-up mode\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Alt+Enter queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.\",\n\t\t\t\tcurrentValue: config.followUpMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"hide-thinking\",\n\t\t\t\tlabel: \"Hide thinking\",\n\t\t\t\tdescription: \"Hide thinking blocks in assistant responses\",\n\t\t\t\tcurrentValue: config.hideThinkingBlock ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"collapse-changelog\",\n\t\t\t\tlabel: \"Collapse changelog\",\n\t\t\t\tdescription: \"Show condensed changelog after updates\",\n\t\t\t\tcurrentValue: config.collapseChangelog ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"double-escape-action\",\n\t\t\t\tlabel: \"Double-escape action\",\n\t\t\t\tdescription: \"Action when pressing Escape twice with empty editor\",\n\t\t\t\tcurrentValue: config.doubleEscapeAction,\n\t\t\t\tvalues: [\"tree\", \"fork\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"thinking\",\n\t\t\t\tlabel: \"Thinking level\",\n\t\t\t\tdescription: \"Reasoning depth for thinking-capable models\",\n\t\t\t\tcurrentValue: config.thinkingLevel,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Thinking Level\",\n\t\t\t\t\t\t\"Select reasoning depth for thinking-capable models\",\n\t\t\t\t\t\tconfig.availableThinkingLevels.map((level) => ({\n\t\t\t\t\t\t\tvalue: level,\n\t\t\t\t\t\t\tlabel: level,\n\t\t\t\t\t\t\tdescription: THINKING_DESCRIPTIONS[level],\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThinkingLevelChange(value as ThinkingLevel);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"theme\",\n\t\t\t\tlabel: \"Theme\",\n\t\t\t\tdescription: \"Color theme for the interface\",\n\t\t\t\tcurrentValue: config.currentTheme,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Theme\",\n\t\t\t\t\t\t\"Select color theme\",\n\t\t\t\t\t\tconfig.availableThemes.map((t) => ({\n\t\t\t\t\t\t\tvalue: t,\n\t\t\t\t\t\t\tlabel: t,\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThemeChange(value);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t// Restore original theme on cancel\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(currentValue);\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\t// Preview theme on selection change\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\n\t\t// Only show image toggle if terminal supports it\n\t\tif (supportsImages) {\n\t\t\t// Insert after autocompact\n\t\t\titems.splice(1, 0, {\n\t\t\t\tid: \"show-images\",\n\t\t\t\tlabel: \"Show images\",\n\t\t\t\tdescription: \"Render images inline in terminal\",\n\t\t\t\tcurrentValue: config.showImages ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t});\n\t\t}\n\n\t\t// Image auto-resize toggle (always available, affects both attached and read images)\n\t\titems.splice(supportsImages ? 2 : 1, 0, {\n\t\t\tid: \"auto-resize-images\",\n\t\t\tlabel: \"Auto-resize images\",\n\t\t\tdescription: \"Resize large images to 2000x2000 max for better model compatibility\",\n\t\t\tcurrentValue: config.autoResizeImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Block images toggle (always available, insert after auto-resize-images)\n\t\tconst autoResizeIndex = items.findIndex((item) => item.id === \"auto-resize-images\");\n\t\titems.splice(autoResizeIndex + 1, 0, {\n\t\t\tid: \"block-images\",\n\t\t\tlabel: \"Block images\",\n\t\t\tdescription: \"Prevent images from being sent to LLM providers\",\n\t\t\tcurrentValue: config.blockImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Skill commands toggle (insert after block-images)\n\t\tconst blockImagesIndex = items.findIndex((item) => item.id === \"block-images\");\n\t\titems.splice(blockImagesIndex + 1, 0, {\n\t\t\tid: \"skill-commands\",\n\t\t\tlabel: \"Skill commands\",\n\t\t\tdescription: \"Register skills as /skill:name commands\",\n\t\t\tcurrentValue: config.enableSkillCommands ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Add borders\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\t10,\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"autocompact\":\n\t\t\t\t\t\tcallbacks.onAutoCompactChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"show-images\":\n\t\t\t\t\t\tcallbacks.onShowImagesChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"auto-resize-images\":\n\t\t\t\t\t\tcallbacks.onAutoResizeImagesChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"block-images\":\n\t\t\t\t\t\tcallbacks.onBlockImagesChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"skill-commands\":\n\t\t\t\t\t\tcallbacks.onEnableSkillCommandsChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"steering-mode\":\n\t\t\t\t\t\tcallbacks.onSteeringModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"follow-up-mode\":\n\t\t\t\t\t\tcallbacks.onFollowUpModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"hide-thinking\":\n\t\t\t\t\t\tcallbacks.onHideThinkingBlockChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"collapse-changelog\":\n\t\t\t\t\t\tcallbacks.onCollapseChangelogChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"double-escape-action\":\n\t\t\t\t\t\tcallbacks.onDoubleEscapeActionChange(newValue as \"fork\" | \"tree\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcallbacks.onCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSettingsList(): SettingsList {\n\t\treturn this.settingsList;\n\t}\n}\n"]}