@xopcai/xopc 0.0.27 → 0.0.29

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 (239) hide show
  1. package/dist/extensions/telegram/xopc.extension.json +1 -1
  2. package/dist/extensions/weixin/src/adapters/onboard-cli.d.ts +7 -0
  3. package/dist/extensions/weixin/src/adapters/onboard-cli.js +61 -0
  4. package/dist/extensions/weixin/src/adapters/onboard-cli.js.map +1 -0
  5. package/dist/extensions/weixin/src/cli/qr-login.d.ts +5 -0
  6. package/dist/extensions/weixin/src/cli/qr-login.js +1 -1
  7. package/dist/extensions/weixin/src/cli/qr-login.js.map +1 -1
  8. package/dist/extensions/weixin/src/index.js +1 -1
  9. package/dist/extensions/weixin/src/plugin.d.ts +1 -0
  10. package/dist/extensions/weixin/src/plugin.js +2 -0
  11. package/dist/extensions/weixin/src/plugin.js.map +1 -1
  12. package/dist/gateway/static/root/assets/agents-CkgFSiCY.js +216 -0
  13. package/dist/gateway/static/root/assets/agents-CkgFSiCY.js.map +1 -0
  14. package/dist/gateway/static/root/assets/{apps-page-CBBh_Ww8.js → apps-page-Bmq19MS-.js} +2 -2
  15. package/dist/gateway/static/root/assets/{apps-page-CBBh_Ww8.js.map → apps-page-Bmq19MS-.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js +9 -0
  17. package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js.map +1 -0
  18. package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js +2 -0
  19. package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js.map +1 -0
  20. package/dist/gateway/static/root/assets/{cron-utils-08gdQfl9.js → cron-utils-N1PqD2DB.js} +2 -2
  21. package/dist/gateway/static/root/assets/{cron-utils-08gdQfl9.js.map → cron-utils-N1PqD2DB.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/{dist-C1MrygQH.js → dist--p2HQ2QF.js} +2 -2
  23. package/dist/gateway/static/root/assets/{dist-C1MrygQH.js.map → dist--p2HQ2QF.js.map} +1 -1
  24. package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js → extension-debug-page-DwHCB_6T.js} +2 -2
  25. package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js.map → extension-debug-page-DwHCB_6T.js.map} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js → extension-page-BsYwQIex.js} +2 -2
  27. package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js.map → extension-page-BsYwQIex.js.map} +1 -1
  28. package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.js → extension-settings-page-nsisEgjB.js} +2 -2
  29. package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.js.map → extension-settings-page-nsisEgjB.js.map} +1 -1
  30. package/dist/gateway/static/root/assets/index-CR8zUHGR.js +4734 -0
  31. package/dist/gateway/static/root/assets/{index-PfkB8N37.js.map → index-CR8zUHGR.js.map} +1 -1
  32. package/dist/gateway/static/root/assets/index-Dnfha4O2.css +1 -0
  33. package/dist/gateway/static/root/assets/logs-page-CQwdV_Xw.js +2 -0
  34. package/dist/gateway/static/root/assets/logs-page-CQwdV_Xw.js.map +1 -0
  35. package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js +2 -0
  36. package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js.map +1 -0
  37. package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js +2 -0
  38. package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js.map +1 -0
  39. package/dist/gateway/static/root/assets/skills-page-Clg8deH0.js +3 -0
  40. package/dist/gateway/static/root/assets/{skills-page-BmBDCEbY.js.map → skills-page-Clg8deH0.js.map} +1 -1
  41. package/dist/gateway/static/root/index.html +2 -2
  42. package/dist/package.js +1 -1
  43. package/dist/src/agent/lifecycle/hook-handler.d.ts +2 -0
  44. package/dist/src/agent/lifecycle/hook-handler.js +24 -0
  45. package/dist/src/agent/lifecycle/hook-handler.js.map +1 -1
  46. package/dist/src/agent/messaging/command-handler.js +10 -2
  47. package/dist/src/agent/messaging/command-handler.js.map +1 -1
  48. package/dist/src/agent/service/process-direct-streaming.js +77 -20
  49. package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
  50. package/dist/src/agent/service.d.ts +15 -0
  51. package/dist/src/agent/service.js +21 -1
  52. package/dist/src/agent/service.js.map +1 -1
  53. package/dist/src/channels/weixin/index.js +1 -1
  54. package/dist/src/cli/agent-chat-log-level-preset.d.ts +8 -0
  55. package/dist/src/cli/agent-chat-log-level-preset.js +25 -0
  56. package/dist/src/cli/agent-chat-log-level-preset.js.map +1 -0
  57. package/dist/src/cli/commands/agent/interactive.js +4 -2
  58. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  59. package/dist/src/cli/commands/agent/stream-renderer.d.ts +14 -0
  60. package/dist/src/cli/commands/agent/stream-renderer.js +99 -0
  61. package/dist/src/cli/commands/agent/stream-renderer.js.map +1 -0
  62. package/dist/src/cli/commands/agent.js +2 -2
  63. package/dist/src/cli/commands/agent.js.map +1 -1
  64. package/dist/src/cli/commands/onboard.js +77 -93
  65. package/dist/src/cli/commands/onboard.js.map +1 -1
  66. package/dist/src/cli/commands/tui.d.ts +1 -0
  67. package/dist/src/cli/commands/tui.js +40 -0
  68. package/dist/src/cli/commands/tui.js.map +1 -0
  69. package/dist/src/cli/index.d.ts +2 -0
  70. package/dist/src/cli/index.js +7 -3
  71. package/dist/src/cli/index.js.map +1 -1
  72. package/dist/src/config/schema.d.ts +6 -0
  73. package/dist/src/config/schema.js +11 -3
  74. package/dist/src/config/schema.js.map +1 -1
  75. package/dist/src/extensions/hooks.js +5 -1
  76. package/dist/src/extensions/hooks.js.map +1 -1
  77. package/dist/src/extensions/loader.d.ts +1 -0
  78. package/dist/src/extensions/loader.js +3 -1
  79. package/dist/src/extensions/loader.js.map +1 -1
  80. package/dist/src/extensions/sdk/index.d.ts +1 -1
  81. package/dist/src/extensions/sdk/index.js.map +1 -1
  82. package/dist/src/extensions/types/core.d.ts +8 -0
  83. package/dist/src/extensions/types/hooks.d.ts +16 -1
  84. package/dist/src/extensions/types/hooks.js +1 -0
  85. package/dist/src/extensions/types/hooks.js.map +1 -1
  86. package/dist/src/gateway/agents-admin.d.ts +19 -1
  87. package/dist/src/gateway/agents-admin.js +164 -3
  88. package/dist/src/gateway/agents-admin.js.map +1 -1
  89. package/dist/src/gateway/auth.d.ts +17 -3
  90. package/dist/src/gateway/auth.js +35 -16
  91. package/dist/src/gateway/auth.js.map +1 -1
  92. package/dist/src/gateway/hono/app.js +31 -1
  93. package/dist/src/gateway/hono/app.js.map +1 -1
  94. package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -1
  95. package/dist/src/gateway/hono/middleware/auth.js +4 -3
  96. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  97. package/dist/src/gateway/hono/middleware/scopes.d.ts +15 -0
  98. package/dist/src/gateway/hono/middleware/scopes.js +41 -0
  99. package/dist/src/gateway/hono/middleware/scopes.js.map +1 -0
  100. package/dist/src/gateway/hono/routes/agents.js +59 -5
  101. package/dist/src/gateway/hono/routes/agents.js.map +1 -1
  102. package/dist/src/gateway/hono/routes/config.js +2 -2
  103. package/dist/src/gateway/hono/routes/config.js.map +1 -1
  104. package/dist/src/gateway/hono/routes/public-gateway.js +1 -0
  105. package/dist/src/gateway/hono/routes/public-gateway.js.map +1 -1
  106. package/dist/src/gateway/hono/routes/sessions.js +17 -0
  107. package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
  108. package/dist/src/gateway/security/audit.d.ts +18 -0
  109. package/dist/src/gateway/security/audit.js +68 -0
  110. package/dist/src/gateway/security/audit.js.map +1 -0
  111. package/dist/src/gateway/security/csp.d.ts +19 -0
  112. package/dist/src/gateway/security/csp.js +52 -0
  113. package/dist/src/gateway/security/csp.js.map +1 -0
  114. package/dist/src/gateway/security/dangerous-tools.d.ts +20 -0
  115. package/dist/src/gateway/security/dangerous-tools.js +46 -0
  116. package/dist/src/gateway/security/dangerous-tools.js.map +1 -0
  117. package/dist/src/gateway/security/flood-guard.d.ts +28 -0
  118. package/dist/src/gateway/security/flood-guard.js +42 -0
  119. package/dist/src/gateway/security/flood-guard.js.map +1 -0
  120. package/dist/src/gateway/security/index.d.ts +9 -0
  121. package/dist/src/gateway/security/index.js +10 -0
  122. package/dist/src/gateway/security/known-weak-secrets.d.ts +10 -0
  123. package/dist/src/gateway/security/known-weak-secrets.js +36 -0
  124. package/dist/src/gateway/security/known-weak-secrets.js.map +1 -0
  125. package/dist/src/gateway/security/operator-scopes.d.ts +37 -0
  126. package/dist/src/gateway/security/operator-scopes.js +137 -0
  127. package/dist/src/gateway/security/operator-scopes.js.map +1 -0
  128. package/dist/src/gateway/security/origin-check.d.ts +21 -0
  129. package/dist/src/gateway/security/origin-check.js +56 -0
  130. package/dist/src/gateway/security/origin-check.js.map +1 -0
  131. package/dist/src/gateway/security/preauth-connection-budget.d.ts +17 -0
  132. package/dist/src/gateway/security/preauth-connection-budget.js +49 -0
  133. package/dist/src/gateway/security/preauth-connection-budget.js.map +1 -0
  134. package/dist/src/gateway/security/secret-equal.d.ts +8 -0
  135. package/dist/src/gateway/security/secret-equal.js +30 -0
  136. package/dist/src/gateway/security/secret-equal.js.map +1 -0
  137. package/dist/src/gateway/service.d.ts +3 -1
  138. package/dist/src/gateway/service.js +40 -4
  139. package/dist/src/gateway/service.js.map +1 -1
  140. package/dist/src/session/client-history.d.ts +21 -0
  141. package/dist/src/session/client-history.js +89 -0
  142. package/dist/src/session/client-history.js.map +1 -0
  143. package/dist/src/session/index.d.ts +1 -0
  144. package/dist/src/session/index.js +2 -1
  145. package/dist/src/session/manager.d.ts +2 -0
  146. package/dist/src/session/manager.js +5 -0
  147. package/dist/src/session/manager.js.map +1 -1
  148. package/dist/src/session/thinking-resolve.js +1 -1
  149. package/dist/src/session/thinking-resolve.js.map +1 -1
  150. package/dist/src/tui/backends/embedded-backend.d.ts +42 -0
  151. package/dist/src/tui/backends/embedded-backend.js +173 -0
  152. package/dist/src/tui/backends/embedded-backend.js.map +1 -0
  153. package/dist/src/tui/backends/gateway-sse-backend.d.ts +53 -0
  154. package/dist/src/tui/backends/gateway-sse-backend.js +256 -0
  155. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -0
  156. package/dist/src/tui/chat-history.d.ts +4 -0
  157. package/dist/src/tui/chat-history.js +29 -0
  158. package/dist/src/tui/chat-history.js.map +1 -0
  159. package/dist/src/tui/components/assistant-message.d.ts +6 -0
  160. package/dist/src/tui/components/assistant-message.js +19 -0
  161. package/dist/src/tui/components/assistant-message.js.map +1 -0
  162. package/dist/src/tui/components/chat-log.d.ts +21 -0
  163. package/dist/src/tui/components/chat-log.js +113 -0
  164. package/dist/src/tui/components/chat-log.js.map +1 -0
  165. package/dist/src/tui/components/custom-editor.d.ts +14 -0
  166. package/dist/src/tui/components/custom-editor.js +50 -0
  167. package/dist/src/tui/components/custom-editor.js.map +1 -0
  168. package/dist/src/tui/components/fuzzy-filter.d.ts +17 -0
  169. package/dist/src/tui/components/fuzzy-filter.js +85 -0
  170. package/dist/src/tui/components/fuzzy-filter.js.map +1 -0
  171. package/dist/src/tui/components/searchable-select-list.d.ts +39 -0
  172. package/dist/src/tui/components/searchable-select-list.js +257 -0
  173. package/dist/src/tui/components/searchable-select-list.js.map +1 -0
  174. package/dist/src/tui/components/tool-execution.d.ts +16 -0
  175. package/dist/src/tui/components/tool-execution.js +76 -0
  176. package/dist/src/tui/components/tool-execution.js.map +1 -0
  177. package/dist/src/tui/components/user-message.d.ts +6 -0
  178. package/dist/src/tui/components/user-message.js +22 -0
  179. package/dist/src/tui/components/user-message.js.map +1 -0
  180. package/dist/src/tui/sse-consumer.d.ts +15 -0
  181. package/dist/src/tui/sse-consumer.js +75 -0
  182. package/dist/src/tui/sse-consumer.js.map +1 -0
  183. package/dist/src/tui/stream-assembler.d.ts +22 -0
  184. package/dist/src/tui/stream-assembler.js +63 -0
  185. package/dist/src/tui/stream-assembler.js.map +1 -0
  186. package/dist/src/tui/theme.d.ts +73 -0
  187. package/dist/src/tui/theme.js +157 -0
  188. package/dist/src/tui/theme.js.map +1 -0
  189. package/dist/src/tui/tui-agent-events.d.ts +7 -0
  190. package/dist/src/tui/tui-agent-events.js +103 -0
  191. package/dist/src/tui/tui-agent-events.js.map +1 -0
  192. package/dist/src/tui/tui-backend.d.ts +80 -0
  193. package/dist/src/tui/tui-backend.js +1 -0
  194. package/dist/src/tui/tui-commands.d.ts +23 -0
  195. package/dist/src/tui/tui-commands.js +165 -0
  196. package/dist/src/tui/tui-commands.js.map +1 -0
  197. package/dist/src/tui/tui-lifecycle.d.ts +26 -0
  198. package/dist/src/tui/tui-lifecycle.js +57 -0
  199. package/dist/src/tui/tui-lifecycle.js.map +1 -0
  200. package/dist/src/tui/tui-local-shell.d.ts +28 -0
  201. package/dist/src/tui/tui-local-shell.js +147 -0
  202. package/dist/src/tui/tui-local-shell.js.map +1 -0
  203. package/dist/src/tui/tui-overlays.d.ts +8 -0
  204. package/dist/src/tui/tui-overlays.js +22 -0
  205. package/dist/src/tui/tui-overlays.js.map +1 -0
  206. package/dist/src/tui/tui-picker-overlay.d.ts +26 -0
  207. package/dist/src/tui/tui-picker-overlay.js +69 -0
  208. package/dist/src/tui/tui-picker-overlay.js.map +1 -0
  209. package/dist/src/tui/tui-stdio-filter.d.ts +17 -0
  210. package/dist/src/tui/tui-stdio-filter.js +96 -0
  211. package/dist/src/tui/tui-stdio-filter.js.map +1 -0
  212. package/dist/src/tui/tui-submit.d.ts +25 -0
  213. package/dist/src/tui/tui-submit.js +102 -0
  214. package/dist/src/tui/tui-submit.js.map +1 -0
  215. package/dist/src/tui/tui-suspend.d.ts +10 -0
  216. package/dist/src/tui/tui-suspend.js +18 -0
  217. package/dist/src/tui/tui-suspend.js.map +1 -0
  218. package/dist/src/tui/tui-types.d.ts +86 -0
  219. package/dist/src/tui/tui-types.js +21 -0
  220. package/dist/src/tui/tui-types.js.map +1 -0
  221. package/dist/src/tui/tui.d.ts +5 -0
  222. package/dist/src/tui/tui.js +389 -0
  223. package/dist/src/tui/tui.js.map +1 -0
  224. package/package.json +5 -3
  225. package/dist/gateway/static/root/assets/agents-w8_jzuiX.js +0 -216
  226. package/dist/gateway/static/root/assets/agents-w8_jzuiX.js.map +0 -1
  227. package/dist/gateway/static/root/assets/channels-settings-DUKRPC7C.js +0 -9
  228. package/dist/gateway/static/root/assets/channels-settings-DUKRPC7C.js.map +0 -1
  229. package/dist/gateway/static/root/assets/cron-page-S18t1yG-.js +0 -2
  230. package/dist/gateway/static/root/assets/cron-page-S18t1yG-.js.map +0 -1
  231. package/dist/gateway/static/root/assets/index-OT4cGzon.css +0 -1
  232. package/dist/gateway/static/root/assets/index-PfkB8N37.js +0 -4734
  233. package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js +0 -2
  234. package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js.map +0 -1
  235. package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js +0 -2
  236. package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js.map +0 -1
  237. package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js +0 -2
  238. package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js.map +0 -1
  239. package/dist/gateway/static/root/assets/skills-page-BmBDCEbY.js +0 -3
@@ -0,0 +1,19 @@
1
+ import { markdownTheme, theme } from "../theme.js";
2
+ import { Container, Markdown, Spacer } from "@mariozechner/pi-tui";
3
+ //#region src/tui/components/assistant-message.ts
4
+ var AssistantMessageComponent = class extends Container {
5
+ body;
6
+ constructor(text) {
7
+ super();
8
+ this.body = new Markdown(text, 0, 0, markdownTheme, { color: (line) => theme.assistantText(line) });
9
+ this.addChild(new Spacer(1));
10
+ this.addChild(this.body);
11
+ }
12
+ setText(text) {
13
+ this.body.setText(text);
14
+ }
15
+ };
16
+ //#endregion
17
+ export { AssistantMessageComponent };
18
+
19
+ //# sourceMappingURL=assistant-message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assistant-message.js","names":[],"sources":["../../../../src/tui/components/assistant-message.ts"],"sourcesContent":["import { Container, Markdown, Spacer } from '@mariozechner/pi-tui';\n\nimport { markdownTheme, theme } from '../theme.js';\n\nexport class AssistantMessageComponent extends Container {\n private body: Markdown;\n\n constructor(text: string) {\n super();\n this.body = new Markdown(text, 0, 0, markdownTheme, {\n color: (line) => theme.assistantText(line),\n });\n this.addChild(new Spacer(1));\n this.addChild(this.body);\n }\n\n setText(text: string): void {\n this.body.setText(text);\n }\n}\n"],"mappings":";;;AAIA,IAAa,4BAAb,cAA+C,UAAU;CACvD;CAEA,YAAY,MAAc;AACxB,SAAO;AACP,OAAK,OAAO,IAAI,SAAS,MAAM,GAAG,GAAG,eAAe,EAClD,QAAQ,SAAS,MAAM,cAAc,KAAK,EAC3C,CAAC;AACF,OAAK,SAAS,IAAI,OAAO,EAAE,CAAC;AAC5B,OAAK,SAAS,KAAK,KAAK;;CAG1B,QAAQ,MAAoB;AAC1B,OAAK,KAAK,QAAQ,KAAK"}
@@ -0,0 +1,21 @@
1
+ import { Container } from '@mariozechner/pi-tui';
2
+ export declare class ChatLog extends Container {
3
+ private toolById;
4
+ private streamingRuns;
5
+ /** After finalizeAssistant, late tool_start can still arrive; keep the bubble to insert tools above. */
6
+ private assistantAnchorByRunId;
7
+ private toolsExpanded;
8
+ private pruneOverflow;
9
+ private dropReferences;
10
+ private append;
11
+ clearAll(): void;
12
+ addSystem(text: string): void;
13
+ addUser(text: string): void;
14
+ startAssistant(text: string, runId: string): void;
15
+ updateAssistant(text: string, runId: string): void;
16
+ finalizeAssistant(text: string, runId: string): void;
17
+ dropAssistant(runId: string): void;
18
+ startTool(toolCallId: string, toolName: string, args: unknown, runId: string): void;
19
+ updateToolResult(toolCallId: string, result: string, isError: boolean): void;
20
+ setToolsExpanded(expanded: boolean): void;
21
+ }
@@ -0,0 +1,113 @@
1
+ import { theme } from "../theme.js";
2
+ import { AssistantMessageComponent } from "./assistant-message.js";
3
+ import { ToolExecutionComponent } from "./tool-execution.js";
4
+ import { UserMessageComponent } from "./user-message.js";
5
+ import { Container, Spacer, Text } from "@mariozechner/pi-tui";
6
+ //#region src/tui/components/chat-log.ts
7
+ const MAX_COMPONENTS = 180;
8
+ var ChatLog = class extends Container {
9
+ toolById = /* @__PURE__ */ new Map();
10
+ streamingRuns = /* @__PURE__ */ new Map();
11
+ /** After finalizeAssistant, late tool_start can still arrive; keep the bubble to insert tools above. */
12
+ assistantAnchorByRunId = /* @__PURE__ */ new Map();
13
+ toolsExpanded = false;
14
+ pruneOverflow() {
15
+ while (this.children.length > MAX_COMPONENTS) {
16
+ const oldest = this.children[0];
17
+ if (!oldest) return;
18
+ this.removeChild(oldest);
19
+ this.dropReferences(oldest);
20
+ }
21
+ }
22
+ dropReferences(component) {
23
+ for (const [id, tool] of this.toolById.entries()) if (tool === component) this.toolById.delete(id);
24
+ for (const [runId, msg] of this.streamingRuns.entries()) if (msg === component) this.streamingRuns.delete(runId);
25
+ for (const [runId, msg] of this.assistantAnchorByRunId.entries()) if (msg === component) this.assistantAnchorByRunId.delete(runId);
26
+ }
27
+ append(component) {
28
+ this.addChild(component);
29
+ this.pruneOverflow();
30
+ }
31
+ clearAll() {
32
+ this.clear();
33
+ this.toolById.clear();
34
+ this.streamingRuns.clear();
35
+ this.assistantAnchorByRunId.clear();
36
+ }
37
+ addSystem(text) {
38
+ const entry = new Container();
39
+ entry.addChild(new Spacer(1));
40
+ entry.addChild(new Text(theme.system(text), 1, 0));
41
+ this.append(entry);
42
+ }
43
+ addUser(text) {
44
+ this.assistantAnchorByRunId.clear();
45
+ this.append(new UserMessageComponent(text));
46
+ }
47
+ startAssistant(text, runId) {
48
+ const existing = this.streamingRuns.get(runId);
49
+ if (existing) {
50
+ existing.setText(text);
51
+ return;
52
+ }
53
+ const component = new AssistantMessageComponent(text);
54
+ this.streamingRuns.set(runId, component);
55
+ this.append(component);
56
+ }
57
+ updateAssistant(text, runId) {
58
+ const existing = this.streamingRuns.get(runId);
59
+ if (!existing) {
60
+ this.startAssistant(text, runId);
61
+ return;
62
+ }
63
+ existing.setText(text);
64
+ }
65
+ finalizeAssistant(text, runId) {
66
+ const existing = this.streamingRuns.get(runId);
67
+ if (existing) {
68
+ existing.setText(text);
69
+ this.streamingRuns.delete(runId);
70
+ this.assistantAnchorByRunId.set(runId, existing);
71
+ return;
72
+ }
73
+ const legacy = new AssistantMessageComponent(text);
74
+ this.append(legacy);
75
+ if (text.trim()) this.assistantAnchorByRunId.set(runId, legacy);
76
+ }
77
+ dropAssistant(runId) {
78
+ const existing = this.streamingRuns.get(runId);
79
+ if (!existing) return;
80
+ this.removeChild(existing);
81
+ this.streamingRuns.delete(runId);
82
+ }
83
+ startTool(toolCallId, toolName, args, runId) {
84
+ const existing = this.toolById.get(toolCallId);
85
+ if (existing) {
86
+ existing.setArgs(args);
87
+ return;
88
+ }
89
+ const component = new ToolExecutionComponent(toolName, args);
90
+ component.setExpanded(this.toolsExpanded);
91
+ this.toolById.set(toolCallId, component);
92
+ const assistant = this.streamingRuns.get(runId) ?? this.assistantAnchorByRunId.get(runId);
93
+ if (assistant) {
94
+ this.removeChild(assistant);
95
+ this.addChild(component);
96
+ this.addChild(assistant);
97
+ } else this.addChild(component);
98
+ this.pruneOverflow();
99
+ }
100
+ updateToolResult(toolCallId, result, isError) {
101
+ const existing = this.toolById.get(toolCallId);
102
+ if (!existing) return;
103
+ existing.setResult(result, isError);
104
+ }
105
+ setToolsExpanded(expanded) {
106
+ this.toolsExpanded = expanded;
107
+ for (const tool of this.toolById.values()) tool.setExpanded(expanded);
108
+ }
109
+ };
110
+ //#endregion
111
+ export { ChatLog };
112
+
113
+ //# sourceMappingURL=chat-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-log.js","names":[],"sources":["../../../../src/tui/components/chat-log.ts"],"sourcesContent":["import type { Component } from '@mariozechner/pi-tui';\nimport { Container, Spacer, Text } from '@mariozechner/pi-tui';\n\nimport { theme } from '../theme.js';\nimport { AssistantMessageComponent } from './assistant-message.js';\nimport { ToolExecutionComponent } from './tool-execution.js';\nimport { UserMessageComponent } from './user-message.js';\n\nconst MAX_COMPONENTS = 180;\n\nexport class ChatLog extends Container {\n private toolById = new Map<string, ToolExecutionComponent>();\n private streamingRuns = new Map<string, AssistantMessageComponent>();\n /** After finalizeAssistant, late tool_start can still arrive; keep the bubble to insert tools above. */\n private assistantAnchorByRunId = new Map<string, AssistantMessageComponent>();\n private toolsExpanded = false;\n\n private pruneOverflow(): void {\n while (this.children.length > MAX_COMPONENTS) {\n const oldest = this.children[0];\n if (!oldest) return;\n this.removeChild(oldest);\n this.dropReferences(oldest);\n }\n }\n\n private dropReferences(component: Component): void {\n for (const [id, tool] of this.toolById.entries()) {\n if (tool === component) this.toolById.delete(id);\n }\n for (const [runId, msg] of this.streamingRuns.entries()) {\n if (msg === component) this.streamingRuns.delete(runId);\n }\n for (const [runId, msg] of this.assistantAnchorByRunId.entries()) {\n if (msg === component) this.assistantAnchorByRunId.delete(runId);\n }\n }\n\n private append(component: Component): void {\n this.addChild(component);\n this.pruneOverflow();\n }\n\n clearAll(): void {\n this.clear();\n this.toolById.clear();\n this.streamingRuns.clear();\n this.assistantAnchorByRunId.clear();\n }\n\n addSystem(text: string): void {\n const entry = new Container();\n entry.addChild(new Spacer(1));\n entry.addChild(new Text(theme.system(text), 1, 0));\n this.append(entry);\n }\n\n addUser(text: string): void {\n this.assistantAnchorByRunId.clear();\n this.append(new UserMessageComponent(text));\n }\n\n startAssistant(text: string, runId: string): void {\n const existing = this.streamingRuns.get(runId);\n if (existing) {\n existing.setText(text);\n return;\n }\n const component = new AssistantMessageComponent(text);\n this.streamingRuns.set(runId, component);\n this.append(component);\n }\n\n updateAssistant(text: string, runId: string): void {\n const existing = this.streamingRuns.get(runId);\n if (!existing) {\n this.startAssistant(text, runId);\n return;\n }\n existing.setText(text);\n }\n\n finalizeAssistant(text: string, runId: string): void {\n const existing = this.streamingRuns.get(runId);\n if (existing) {\n existing.setText(text);\n this.streamingRuns.delete(runId);\n this.assistantAnchorByRunId.set(runId, existing);\n return;\n }\n const legacy = new AssistantMessageComponent(text);\n this.append(legacy);\n if (text.trim()) {\n this.assistantAnchorByRunId.set(runId, legacy);\n }\n }\n\n dropAssistant(runId: string): void {\n const existing = this.streamingRuns.get(runId);\n if (!existing) return;\n this.removeChild(existing);\n this.streamingRuns.delete(runId);\n }\n\n startTool(toolCallId: string, toolName: string, args: unknown, runId: string): void {\n const existing = this.toolById.get(toolCallId);\n if (existing) {\n existing.setArgs(args);\n return;\n }\n const component = new ToolExecutionComponent(toolName, args);\n component.setExpanded(this.toolsExpanded);\n this.toolById.set(toolCallId, component);\n\n const assistant =\n this.streamingRuns.get(runId) ?? this.assistantAnchorByRunId.get(runId);\n if (assistant) {\n // Streamed assistant text is updated in place from the start of the turn; tools\n // arrive later from SSE but should appear above the conversational reply (like the web UI).\n this.removeChild(assistant);\n this.addChild(component);\n this.addChild(assistant);\n } else {\n this.addChild(component);\n }\n this.pruneOverflow();\n }\n\n updateToolResult(toolCallId: string, result: string, isError: boolean): void {\n const existing = this.toolById.get(toolCallId);\n if (!existing) return;\n existing.setResult(result, isError);\n }\n\n setToolsExpanded(expanded: boolean): void {\n this.toolsExpanded = expanded;\n for (const tool of this.toolById.values()) {\n tool.setExpanded(expanded);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { Editor } from '@mariozechner/pi-tui';
2
+ /**
3
+ * Extended editor with additional key bindings for the TUI.
4
+ */
5
+ export declare class CustomEditor extends Editor {
6
+ onEscape?: () => void;
7
+ onCtrlC?: () => void;
8
+ onCtrlD?: () => void;
9
+ onCtrlL?: () => void;
10
+ onCtrlP?: () => void;
11
+ onCtrlO?: () => void;
12
+ onCtrlT?: () => void;
13
+ handleInput(data: string): void;
14
+ }
@@ -0,0 +1,50 @@
1
+ import { Editor, Key, getKeybindings, matchesKey, parseKey } from "@mariozechner/pi-tui";
2
+ //#region src/tui/components/custom-editor.ts
3
+ /**
4
+ * Extended editor with additional key bindings for the TUI.
5
+ */
6
+ var CustomEditor = class extends Editor {
7
+ onEscape;
8
+ onCtrlC;
9
+ onCtrlD;
10
+ onCtrlL;
11
+ onCtrlP;
12
+ onCtrlO;
13
+ onCtrlT;
14
+ handleInput(data) {
15
+ const kb = getKeybindings();
16
+ if (matchesKey(data, Key.ctrl("l")) && this.onCtrlL) {
17
+ this.onCtrlL();
18
+ return;
19
+ }
20
+ if (matchesKey(data, Key.ctrl("p")) && this.onCtrlP) {
21
+ this.onCtrlP();
22
+ return;
23
+ }
24
+ if (matchesKey(data, Key.ctrl("o")) && this.onCtrlO) {
25
+ this.onCtrlO();
26
+ return;
27
+ }
28
+ if (matchesKey(data, Key.ctrl("t")) && this.onCtrlT) {
29
+ this.onCtrlT();
30
+ return;
31
+ }
32
+ if (matchesKey(data, Key.escape) && this.onEscape && !this.isShowingAutocomplete()) {
33
+ this.onEscape();
34
+ return;
35
+ }
36
+ if (this.onCtrlC && (data === "" || parseKey(data) === "ctrl+c" || matchesKey(data, Key.ctrl("c")) || kb.matches(data, "tui.input.copy"))) {
37
+ this.onCtrlC();
38
+ return;
39
+ }
40
+ if (matchesKey(data, Key.ctrl("d"))) {
41
+ if (this.getText().length === 0 && this.onCtrlD) this.onCtrlD();
42
+ return;
43
+ }
44
+ super.handleInput(data);
45
+ }
46
+ };
47
+ //#endregion
48
+ export { CustomEditor };
49
+
50
+ //# sourceMappingURL=custom-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-editor.js","names":[],"sources":["../../../../src/tui/components/custom-editor.ts"],"sourcesContent":["import { Editor, getKeybindings, Key, matchesKey, parseKey } from '@mariozechner/pi-tui';\n\n/**\n * Extended editor with additional key bindings for the TUI.\n */\nexport class CustomEditor extends Editor {\n onEscape?: () => void;\n onCtrlC?: () => void;\n onCtrlD?: () => void;\n onCtrlL?: () => void;\n onCtrlP?: () => void;\n onCtrlO?: () => void;\n onCtrlT?: () => void;\n\n handleInput(data: string): void {\n const kb = getKeybindings();\n if (matchesKey(data, Key.ctrl('l')) && this.onCtrlL) {\n this.onCtrlL();\n return;\n }\n if (matchesKey(data, Key.ctrl('p')) && this.onCtrlP) {\n this.onCtrlP();\n return;\n }\n if (matchesKey(data, Key.ctrl('o')) && this.onCtrlO) {\n this.onCtrlO();\n return;\n }\n if (matchesKey(data, Key.ctrl('t')) && this.onCtrlT) {\n this.onCtrlT();\n return;\n }\n if (matchesKey(data, Key.escape) && this.onEscape && !this.isShowingAutocomplete()) {\n this.onEscape();\n return;\n }\n // Match all encodings pi-tui uses for Ctrl+C (incl. Kitty / modifyOtherKeys on macOS).\n // Base Editor treats \"tui.input.copy\" as a no-op — falling through swallows the key.\n if (\n this.onCtrlC &&\n (data === '\\x03' ||\n parseKey(data) === 'ctrl+c' ||\n matchesKey(data, Key.ctrl('c')) ||\n kb.matches(data, 'tui.input.copy'))\n ) {\n this.onCtrlC();\n return;\n }\n if (matchesKey(data, Key.ctrl('d'))) {\n if (this.getText().length === 0 && this.onCtrlD) {\n this.onCtrlD();\n }\n return;\n }\n super.handleInput(data);\n }\n}\n"],"mappings":";;;;;AAKA,IAAa,eAAb,cAAkC,OAAO;CACvC;CACA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,MAAoB;EAC9B,MAAM,KAAK,gBAAgB;AAC3B,MAAI,WAAW,MAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,SAAS;AACnD,QAAK,SAAS;AACd;;AAEF,MAAI,WAAW,MAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,SAAS;AACnD,QAAK,SAAS;AACd;;AAEF,MAAI,WAAW,MAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,SAAS;AACnD,QAAK,SAAS;AACd;;AAEF,MAAI,WAAW,MAAM,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,SAAS;AACnD,QAAK,SAAS;AACd;;AAEF,MAAI,WAAW,MAAM,IAAI,OAAO,IAAI,KAAK,YAAY,CAAC,KAAK,uBAAuB,EAAE;AAClF,QAAK,UAAU;AACf;;AAIF,MACE,KAAK,YACJ,SAAS,OACR,SAAS,KAAK,KAAK,YACnB,WAAW,MAAM,IAAI,KAAK,IAAI,CAAC,IAC/B,GAAG,QAAQ,MAAM,iBAAiB,GACpC;AACA,QAAK,SAAS;AACd;;AAEF,MAAI,WAAW,MAAM,IAAI,KAAK,IAAI,CAAC,EAAE;AACnC,OAAI,KAAK,SAAS,CAAC,WAAW,KAAK,KAAK,QACtC,MAAK,SAAS;AAEhB;;AAEF,QAAM,YAAY,KAAK"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Fuzzy filtering for searchable select lists (openclaw-style).
3
+ */
4
+ export declare function normalizeLowercaseStringOrEmpty(value: string): string;
5
+ export declare function isWordBoundary(text: string, index: number): boolean;
6
+ export declare function findWordBoundaryIndex(text: string, query: string): number | null;
7
+ export declare function fuzzyMatchLower(queryLower: string, textLower: string): number | null;
8
+ export declare function fuzzyFilterLower<T extends {
9
+ searchTextLower?: string;
10
+ }>(items: T[], queryLower: string): T[];
11
+ export declare function prepareSearchItems<T extends {
12
+ label?: string;
13
+ description?: string;
14
+ searchText?: string;
15
+ }>(items: T[]): (T & {
16
+ searchTextLower: string;
17
+ })[];
@@ -0,0 +1,85 @@
1
+ //#region src/tui/components/fuzzy-filter.ts
2
+ /**
3
+ * Fuzzy filtering for searchable select lists (openclaw-style).
4
+ */
5
+ function normalizeLowercaseStringOrEmpty(value) {
6
+ return value.trim().toLowerCase().replace(/\s+/g, " ");
7
+ }
8
+ const WORD_BOUNDARY_CHARS = /[\s\-_./:#@]/;
9
+ function isWordBoundary(text, index) {
10
+ return index === 0 || WORD_BOUNDARY_CHARS.test(text[index - 1] ?? "");
11
+ }
12
+ function findWordBoundaryIndex(text, query) {
13
+ if (!query) return null;
14
+ const textLower = normalizeLowercaseStringOrEmpty(text);
15
+ const queryLower = normalizeLowercaseStringOrEmpty(query);
16
+ const maxIndex = textLower.length - queryLower.length;
17
+ if (maxIndex < 0) return null;
18
+ for (let i = 0; i <= maxIndex; i++) if (textLower.startsWith(queryLower, i) && isWordBoundary(textLower, i)) return i;
19
+ return null;
20
+ }
21
+ function fuzzyMatchLower(queryLower, textLower) {
22
+ if (queryLower.length === 0) return 0;
23
+ if (queryLower.length > textLower.length) return null;
24
+ let queryIndex = 0;
25
+ let score = 0;
26
+ let lastMatchIndex = -1;
27
+ let consecutiveMatches = 0;
28
+ for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) if (textLower[i] === queryLower[queryIndex]) {
29
+ const atWordBoundary = isWordBoundary(textLower, i);
30
+ if (lastMatchIndex === i - 1) {
31
+ consecutiveMatches++;
32
+ score -= consecutiveMatches * 5;
33
+ } else {
34
+ consecutiveMatches = 0;
35
+ if (lastMatchIndex >= 0) score += (i - lastMatchIndex - 1) * 2;
36
+ }
37
+ if (atWordBoundary) score -= 10;
38
+ score += i * .1;
39
+ lastMatchIndex = i;
40
+ queryIndex++;
41
+ }
42
+ return queryIndex < queryLower.length ? null : score;
43
+ }
44
+ function fuzzyFilterLower(items, queryLower) {
45
+ const trimmed = queryLower.trim();
46
+ if (!trimmed) return items;
47
+ const tokens = trimmed.split(/\s+/).filter((t) => t.length > 0);
48
+ if (tokens.length === 0) return items;
49
+ const results = [];
50
+ for (const item of items) {
51
+ const text = item.searchTextLower ?? "";
52
+ let totalScore = 0;
53
+ let allMatch = true;
54
+ for (const token of tokens) {
55
+ const score = fuzzyMatchLower(token, text);
56
+ if (score !== null) totalScore += score;
57
+ else {
58
+ allMatch = false;
59
+ break;
60
+ }
61
+ }
62
+ if (allMatch) results.push({
63
+ item,
64
+ score: totalScore
65
+ });
66
+ }
67
+ results.sort((a, b) => a.score - b.score);
68
+ return results.map((r) => r.item);
69
+ }
70
+ function prepareSearchItems(items) {
71
+ return items.map((item) => {
72
+ const parts = [];
73
+ if (item.label) parts.push(item.label);
74
+ if (item.description) parts.push(item.description);
75
+ if (item.searchText) parts.push(item.searchText);
76
+ return {
77
+ ...item,
78
+ searchTextLower: normalizeLowercaseStringOrEmpty(parts.join(" "))
79
+ };
80
+ });
81
+ }
82
+ //#endregion
83
+ export { findWordBoundaryIndex, fuzzyFilterLower, fuzzyMatchLower, isWordBoundary, normalizeLowercaseStringOrEmpty, prepareSearchItems };
84
+
85
+ //# sourceMappingURL=fuzzy-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fuzzy-filter.js","names":[],"sources":["../../../../src/tui/components/fuzzy-filter.ts"],"sourcesContent":["/**\n * Fuzzy filtering for searchable select lists (openclaw-style).\n */\n\nexport function normalizeLowercaseStringOrEmpty(value: string): string {\n return value.trim().toLowerCase().replace(/\\s+/g, ' ');\n}\n\nconst WORD_BOUNDARY_CHARS = /[\\s\\-_./:#@]/;\n\nexport function isWordBoundary(text: string, index: number): boolean {\n return index === 0 || WORD_BOUNDARY_CHARS.test(text[index - 1] ?? '');\n}\n\nexport function findWordBoundaryIndex(text: string, query: string): number | null {\n if (!query) {\n return null;\n }\n const textLower = normalizeLowercaseStringOrEmpty(text);\n const queryLower = normalizeLowercaseStringOrEmpty(query);\n const maxIndex = textLower.length - queryLower.length;\n if (maxIndex < 0) {\n return null;\n }\n for (let i = 0; i <= maxIndex; i++) {\n if (textLower.startsWith(queryLower, i) && isWordBoundary(textLower, i)) {\n return i;\n }\n }\n return null;\n}\n\nexport function fuzzyMatchLower(queryLower: string, textLower: string): number | null {\n if (queryLower.length === 0) {\n return 0;\n }\n if (queryLower.length > textLower.length) {\n return null;\n }\n\n let queryIndex = 0;\n let score = 0;\n let lastMatchIndex = -1;\n let consecutiveMatches = 0;\n\n for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {\n if (textLower[i] === queryLower[queryIndex]) {\n const atWordBoundary = isWordBoundary(textLower, i);\n if (lastMatchIndex === i - 1) {\n consecutiveMatches++;\n score -= consecutiveMatches * 5;\n } else {\n consecutiveMatches = 0;\n if (lastMatchIndex >= 0) {\n score += (i - lastMatchIndex - 1) * 2;\n }\n }\n if (atWordBoundary) {\n score -= 10;\n }\n score += i * 0.1;\n lastMatchIndex = i;\n queryIndex++;\n }\n }\n return queryIndex < queryLower.length ? null : score;\n}\n\nexport function fuzzyFilterLower<T extends { searchTextLower?: string }>(\n items: T[],\n queryLower: string,\n): T[] {\n const trimmed = queryLower.trim();\n if (!trimmed) {\n return items;\n }\n\n const tokens = trimmed.split(/\\s+/).filter((t) => t.length > 0);\n if (tokens.length === 0) {\n return items;\n }\n\n const results: { item: T; score: number }[] = [];\n for (const item of items) {\n const text = item.searchTextLower ?? '';\n let totalScore = 0;\n let allMatch = true;\n for (const token of tokens) {\n const score = fuzzyMatchLower(token, text);\n if (score !== null) {\n totalScore += score;\n } else {\n allMatch = false;\n break;\n }\n }\n if (allMatch) {\n results.push({ item, score: totalScore });\n }\n }\n results.sort((a, b) => a.score - b.score);\n return results.map((r) => r.item);\n}\n\nexport function prepareSearchItems<\n T extends { label?: string; description?: string; searchText?: string },\n>(items: T[]): (T & { searchTextLower: string })[] {\n return items.map((item) => {\n const parts: string[] = [];\n if (item.label) {\n parts.push(item.label);\n }\n if (item.description) {\n parts.push(item.description);\n }\n if (item.searchText) {\n parts.push(item.searchText);\n }\n return { ...item, searchTextLower: normalizeLowercaseStringOrEmpty(parts.join(' ')) };\n });\n}\n"],"mappings":";;;;AAIA,SAAgB,gCAAgC,OAAuB;AACrE,QAAO,MAAM,MAAM,CAAC,aAAa,CAAC,QAAQ,QAAQ,IAAI;;AAGxD,MAAM,sBAAsB;AAE5B,SAAgB,eAAe,MAAc,OAAwB;AACnE,QAAO,UAAU,KAAK,oBAAoB,KAAK,KAAK,QAAQ,MAAM,GAAG;;AAGvE,SAAgB,sBAAsB,MAAc,OAA8B;AAChF,KAAI,CAAC,MACH,QAAO;CAET,MAAM,YAAY,gCAAgC,KAAK;CACvD,MAAM,aAAa,gCAAgC,MAAM;CACzD,MAAM,WAAW,UAAU,SAAS,WAAW;AAC/C,KAAI,WAAW,EACb,QAAO;AAET,MAAK,IAAI,IAAI,GAAG,KAAK,UAAU,IAC7B,KAAI,UAAU,WAAW,YAAY,EAAE,IAAI,eAAe,WAAW,EAAE,CACrE,QAAO;AAGX,QAAO;;AAGT,SAAgB,gBAAgB,YAAoB,WAAkC;AACpF,KAAI,WAAW,WAAW,EACxB,QAAO;AAET,KAAI,WAAW,SAAS,UAAU,OAChC,QAAO;CAGT,IAAI,aAAa;CACjB,IAAI,QAAQ;CACZ,IAAI,iBAAiB;CACrB,IAAI,qBAAqB;AAEzB,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,UAAU,aAAa,WAAW,QAAQ,IACtE,KAAI,UAAU,OAAO,WAAW,aAAa;EAC3C,MAAM,iBAAiB,eAAe,WAAW,EAAE;AACnD,MAAI,mBAAmB,IAAI,GAAG;AAC5B;AACA,YAAS,qBAAqB;SACzB;AACL,wBAAqB;AACrB,OAAI,kBAAkB,EACpB,WAAU,IAAI,iBAAiB,KAAK;;AAGxC,MAAI,eACF,UAAS;AAEX,WAAS,IAAI;AACb,mBAAiB;AACjB;;AAGJ,QAAO,aAAa,WAAW,SAAS,OAAO;;AAGjD,SAAgB,iBACd,OACA,YACK;CACL,MAAM,UAAU,WAAW,MAAM;AACjC,KAAI,CAAC,QACH,QAAO;CAGT,MAAM,SAAS,QAAQ,MAAM,MAAM,CAAC,QAAQ,MAAM,EAAE,SAAS,EAAE;AAC/D,KAAI,OAAO,WAAW,EACpB,QAAO;CAGT,MAAM,UAAwC,EAAE;AAChD,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,OAAO,KAAK,mBAAmB;EACrC,IAAI,aAAa;EACjB,IAAI,WAAW;AACf,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,QAAQ,gBAAgB,OAAO,KAAK;AAC1C,OAAI,UAAU,KACZ,eAAc;QACT;AACL,eAAW;AACX;;;AAGJ,MAAI,SACF,SAAQ,KAAK;GAAE;GAAM,OAAO;GAAY,CAAC;;AAG7C,SAAQ,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AACzC,QAAO,QAAQ,KAAK,MAAM,EAAE,KAAK;;AAGnC,SAAgB,mBAEd,OAAiD;AACjD,QAAO,MAAM,KAAK,SAAS;EACzB,MAAM,QAAkB,EAAE;AAC1B,MAAI,KAAK,MACP,OAAM,KAAK,KAAK,MAAM;AAExB,MAAI,KAAK,YACP,OAAM,KAAK,KAAK,YAAY;AAE9B,MAAI,KAAK,WACP,OAAM,KAAK,KAAK,WAAW;AAE7B,SAAO;GAAE,GAAG;GAAM,iBAAiB,gCAAgC,MAAM,KAAK,IAAI,CAAC;GAAE;GACrF"}
@@ -0,0 +1,39 @@
1
+ import { type Component, type SelectItem, type SelectListTheme } from '@mariozechner/pi-tui';
2
+ export interface SearchableSelectListTheme extends SelectListTheme {
3
+ searchPrompt: (text: string) => string;
4
+ searchInput: (text: string) => string;
5
+ matchHighlight: (text: string) => string;
6
+ }
7
+ export declare class SearchableSelectList implements Component {
8
+ private items;
9
+ private filteredItems;
10
+ private selectedIndex;
11
+ private maxVisible;
12
+ private theme;
13
+ private searchInput;
14
+ private regexCache;
15
+ onSelect?: (item: SelectItem) => void;
16
+ onCancel?: () => void;
17
+ onSelectionChange?: (item: SelectItem) => void;
18
+ private static readonly DESCRIPTION_LAYOUT_MIN_WIDTH;
19
+ private static readonly DESCRIPTION_MIN_WIDTH;
20
+ private static readonly DESCRIPTION_SPACING_WIDTH;
21
+ private static readonly RIGHT_MARGIN_WIDTH;
22
+ constructor(items: SelectItem[], maxVisible: number, theme: SearchableSelectListTheme);
23
+ private getCachedRegex;
24
+ private updateFilter;
25
+ private smartFilter;
26
+ private escapeRegex;
27
+ private compareByScore;
28
+ private getItemLabel;
29
+ private splitAnsiParts;
30
+ private highlightMatch;
31
+ setSelectedIndex(index: number): void;
32
+ invalidate(): void;
33
+ render(width: number): string[];
34
+ private renderItemLine;
35
+ private getDescriptionLayout;
36
+ handleInput(keyData: string): void;
37
+ private notifySelectionChange;
38
+ getSelectedItem(): SelectItem | null;
39
+ }
@@ -0,0 +1,257 @@
1
+ import { findWordBoundaryIndex, fuzzyFilterLower, normalizeLowercaseStringOrEmpty } from "./fuzzy-filter.js";
2
+ import { Input, isKeyRelease, matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
3
+ //#region src/tui/components/searchable-select-list.ts
4
+ const ANSI_SGR_REGEX = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
5
+ function stripAnsi(raw) {
6
+ return raw.replace(/\x1b\[[0-9;]*m/g, "");
7
+ }
8
+ var SearchableSelectList = class SearchableSelectList {
9
+ items;
10
+ filteredItems;
11
+ selectedIndex = 0;
12
+ maxVisible;
13
+ theme;
14
+ searchInput;
15
+ regexCache = /* @__PURE__ */ new Map();
16
+ onSelect;
17
+ onCancel;
18
+ onSelectionChange;
19
+ static DESCRIPTION_LAYOUT_MIN_WIDTH = 40;
20
+ static DESCRIPTION_MIN_WIDTH = 12;
21
+ static DESCRIPTION_SPACING_WIDTH = 2;
22
+ static RIGHT_MARGIN_WIDTH = 2;
23
+ constructor(items, maxVisible, theme) {
24
+ this.items = items;
25
+ this.filteredItems = items;
26
+ this.maxVisible = maxVisible;
27
+ this.theme = theme;
28
+ this.searchInput = new Input();
29
+ }
30
+ getCachedRegex(pattern) {
31
+ let regex = this.regexCache.get(pattern);
32
+ if (!regex) {
33
+ regex = new RegExp(this.escapeRegex(pattern), "gi");
34
+ this.regexCache.set(pattern, regex);
35
+ }
36
+ return regex;
37
+ }
38
+ updateFilter() {
39
+ const query = this.searchInput.getValue().trim();
40
+ if (!query) this.filteredItems = this.items;
41
+ else this.filteredItems = this.smartFilter(query);
42
+ this.selectedIndex = 0;
43
+ this.notifySelectionChange();
44
+ }
45
+ smartFilter(query) {
46
+ const q = normalizeLowercaseStringOrEmpty(query);
47
+ const scoredItems = [];
48
+ const fuzzyCandidates = [];
49
+ for (const item of this.items) {
50
+ const rawLabel = this.getItemLabel(item);
51
+ const rawDesc = item.description ?? "";
52
+ const label = normalizeLowercaseStringOrEmpty(stripAnsi(rawLabel));
53
+ const desc = normalizeLowercaseStringOrEmpty(stripAnsi(rawDesc));
54
+ const labelIndex = label.indexOf(q);
55
+ if (labelIndex !== -1) {
56
+ scoredItems.push({
57
+ item,
58
+ tier: 0,
59
+ score: labelIndex
60
+ });
61
+ continue;
62
+ }
63
+ const wordBoundaryIndex = findWordBoundaryIndex(label, q);
64
+ if (wordBoundaryIndex !== null) {
65
+ scoredItems.push({
66
+ item,
67
+ tier: 1,
68
+ score: wordBoundaryIndex
69
+ });
70
+ continue;
71
+ }
72
+ const descIndex = desc.indexOf(q);
73
+ if (descIndex !== -1) {
74
+ scoredItems.push({
75
+ item,
76
+ tier: 2,
77
+ score: descIndex
78
+ });
79
+ continue;
80
+ }
81
+ const searchText = item.searchText ?? "";
82
+ fuzzyCandidates.push({
83
+ item,
84
+ searchTextLower: normalizeLowercaseStringOrEmpty([
85
+ rawLabel,
86
+ rawDesc,
87
+ searchText
88
+ ].map((value) => stripAnsi(value)).filter(Boolean).join(" "))
89
+ });
90
+ }
91
+ scoredItems.sort(this.compareByScore);
92
+ const fuzzyMatches = fuzzyFilterLower(fuzzyCandidates, q);
93
+ return [...scoredItems.map((s) => s.item), ...fuzzyMatches.map((entry) => entry.item)];
94
+ }
95
+ escapeRegex(str) {
96
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
97
+ }
98
+ compareByScore = (a, b) => {
99
+ if (a.tier !== b.tier) return a.tier - b.tier;
100
+ if (a.score !== b.score) return a.score - b.score;
101
+ return this.getItemLabel(a.item).localeCompare(this.getItemLabel(b.item));
102
+ };
103
+ getItemLabel(item) {
104
+ return item.label || item.value;
105
+ }
106
+ splitAnsiParts(text) {
107
+ const parts = [];
108
+ ANSI_SGR_REGEX.lastIndex = 0;
109
+ let lastIndex = 0;
110
+ let match;
111
+ while ((match = ANSI_SGR_REGEX.exec(text)) !== null) {
112
+ if (match.index > lastIndex) parts.push({
113
+ text: text.slice(lastIndex, match.index),
114
+ isAnsi: false
115
+ });
116
+ parts.push({
117
+ text: match[0],
118
+ isAnsi: true
119
+ });
120
+ lastIndex = match.index + match[0].length;
121
+ }
122
+ if (lastIndex < text.length) parts.push({
123
+ text: text.slice(lastIndex),
124
+ isAnsi: false
125
+ });
126
+ return parts;
127
+ }
128
+ highlightMatch(text, query) {
129
+ const tokens = query.trim().split(/\s+/).map((token) => normalizeLowercaseStringOrEmpty(token)).filter((token) => token.length > 0);
130
+ if (tokens.length === 0) return text;
131
+ const uniqueTokens = Array.from(new Set(tokens)).toSorted((a, b) => b.length - a.length);
132
+ let parts = this.splitAnsiParts(text);
133
+ for (const token of uniqueTokens) {
134
+ const regex = this.getCachedRegex(token);
135
+ const nextParts = [];
136
+ for (const part of parts) {
137
+ if (part.isAnsi) {
138
+ nextParts.push(part);
139
+ continue;
140
+ }
141
+ regex.lastIndex = 0;
142
+ const replaced = part.text.replace(regex, (match) => this.theme.matchHighlight(match));
143
+ if (replaced === part.text) {
144
+ nextParts.push(part);
145
+ continue;
146
+ }
147
+ nextParts.push(...this.splitAnsiParts(replaced));
148
+ }
149
+ parts = nextParts;
150
+ }
151
+ return parts.map((part) => part.text).join("");
152
+ }
153
+ setSelectedIndex(index) {
154
+ this.selectedIndex = Math.max(0, Math.min(index, this.filteredItems.length - 1));
155
+ }
156
+ invalidate() {
157
+ this.searchInput.invalidate();
158
+ }
159
+ render(width) {
160
+ const lines = [];
161
+ const prompt = this.theme.searchPrompt("search: ");
162
+ const inputWidth = Math.max(1, width - visibleWidth(prompt));
163
+ const inputText = this.searchInput.render(inputWidth)[0] ?? "";
164
+ lines.push(`${prompt}${this.theme.searchInput(inputText)}`);
165
+ lines.push("");
166
+ const query = this.searchInput.getValue().trim();
167
+ if (this.filteredItems.length === 0) {
168
+ lines.push(this.theme.noMatch(" No matches"));
169
+ return lines;
170
+ }
171
+ const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible));
172
+ const endIndex = Math.min(startIndex + this.maxVisible, this.filteredItems.length);
173
+ for (let i = startIndex; i < endIndex; i++) {
174
+ const item = this.filteredItems[i];
175
+ if (!item) continue;
176
+ const isSelected = i === this.selectedIndex;
177
+ lines.push(this.renderItemLine(item, isSelected, width, query));
178
+ }
179
+ if (this.filteredItems.length > this.maxVisible) {
180
+ const scrollInfo = `${this.selectedIndex + 1}/${this.filteredItems.length}`;
181
+ lines.push(this.theme.scrollInfo(` ${scrollInfo}`));
182
+ }
183
+ return lines;
184
+ }
185
+ renderItemLine(item, isSelected, width, query) {
186
+ const prefix = isSelected ? "→ " : " ";
187
+ const prefixWidth = prefix.length;
188
+ const displayValue = this.getItemLabel(item);
189
+ const description = item.description;
190
+ if (description) {
191
+ const descriptionLayout = this.getDescriptionLayout(width, prefixWidth);
192
+ if (descriptionLayout) {
193
+ const truncatedValue = truncateToWidth(displayValue, descriptionLayout.maxValueWidth, "");
194
+ const valueText = this.highlightMatch(truncatedValue, query);
195
+ const usedByValue = visibleWidth(valueText);
196
+ const descriptionWidth = descriptionLayout.availableWidth - usedByValue - descriptionLayout.spacingWidth;
197
+ if (descriptionWidth >= SearchableSelectList.DESCRIPTION_MIN_WIDTH) {
198
+ const spacing = " ".repeat(descriptionLayout.spacingWidth);
199
+ const truncatedDesc = truncateToWidth(description, descriptionWidth, "");
200
+ const highlightedDesc = this.highlightMatch(truncatedDesc, query);
201
+ const line = `${prefix}${valueText}${spacing}${isSelected ? highlightedDesc : this.theme.description(highlightedDesc)}`;
202
+ return isSelected ? this.theme.selectedText(line) : line;
203
+ }
204
+ }
205
+ }
206
+ const truncatedValue = truncateToWidth(displayValue, width - prefixWidth - 2, "");
207
+ const line = `${prefix}${this.highlightMatch(truncatedValue, query)}`;
208
+ return isSelected ? this.theme.selectedText(line) : line;
209
+ }
210
+ getDescriptionLayout(width, prefixWidth) {
211
+ if (width <= SearchableSelectList.DESCRIPTION_LAYOUT_MIN_WIDTH) return null;
212
+ const availableWidth = Math.max(1, width - prefixWidth - SearchableSelectList.RIGHT_MARGIN_WIDTH);
213
+ const maxValueWidth = availableWidth - SearchableSelectList.DESCRIPTION_MIN_WIDTH - SearchableSelectList.DESCRIPTION_SPACING_WIDTH;
214
+ if (maxValueWidth < 1) return null;
215
+ return {
216
+ availableWidth,
217
+ maxValueWidth,
218
+ spacingWidth: SearchableSelectList.DESCRIPTION_SPACING_WIDTH
219
+ };
220
+ }
221
+ handleInput(keyData) {
222
+ if (isKeyRelease(keyData)) return;
223
+ if (matchesKey(keyData, "up") || matchesKey(keyData, "ctrl+p")) {
224
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
225
+ this.notifySelectionChange();
226
+ return;
227
+ }
228
+ if (matchesKey(keyData, "down") || matchesKey(keyData, "ctrl+n")) {
229
+ this.selectedIndex = Math.min(this.filteredItems.length - 1, this.selectedIndex + 1);
230
+ this.notifySelectionChange();
231
+ return;
232
+ }
233
+ if (matchesKey(keyData, "enter")) {
234
+ const item = this.filteredItems[this.selectedIndex];
235
+ if (item && this.onSelect) this.onSelect(item);
236
+ return;
237
+ }
238
+ if (matchesKey(keyData, "escape") || keyData === "") {
239
+ if (this.onCancel) this.onCancel();
240
+ return;
241
+ }
242
+ const prevValue = this.searchInput.getValue();
243
+ this.searchInput.handleInput(keyData);
244
+ if (prevValue !== this.searchInput.getValue()) this.updateFilter();
245
+ }
246
+ notifySelectionChange() {
247
+ const item = this.filteredItems[this.selectedIndex];
248
+ if (item && this.onSelectionChange) this.onSelectionChange(item);
249
+ }
250
+ getSelectedItem() {
251
+ return this.filteredItems[this.selectedIndex] ?? null;
252
+ }
253
+ };
254
+ //#endregion
255
+ export { SearchableSelectList };
256
+
257
+ //# sourceMappingURL=searchable-select-list.js.map