@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,103 @@
1
+ //#region src/tui/tui-agent-events.ts
2
+ const pendingToolCallIds = /* @__PURE__ */ new Map();
3
+ function clearPendingToolCallIds() {
4
+ pendingToolCallIds.clear();
5
+ }
6
+ const STREAM_TOUCH_EVENTS = new Set([
7
+ "status",
8
+ "token",
9
+ "thinking",
10
+ "tool_start",
11
+ "tool_end",
12
+ "progress"
13
+ ]);
14
+ function dispatchAgentSSE(event, data, state, chatLog, assembler, tui, setActivityStatus, touchStreamingActivity) {
15
+ if (STREAM_TOUCH_EVENTS.has(event)) touchStreamingActivity?.();
16
+ const runId = state.activeRunId ?? "default";
17
+ switch (event) {
18
+ case "status":
19
+ state.activeRunId = typeof data.runId === "string" ? data.runId : runId;
20
+ setActivityStatus("waiting");
21
+ break;
22
+ case "token": {
23
+ const content = typeof data.content === "string" ? data.content : typeof data.delta === "string" ? data.delta : typeof data.text === "string" ? data.text : "";
24
+ if (!content) break;
25
+ setActivityStatus("streaming");
26
+ const display = assembler.ingestToken(runId, content, state.showThinking);
27
+ if (display !== null) {
28
+ chatLog.updateAssistant(display, runId);
29
+ tui.requestRender();
30
+ }
31
+ break;
32
+ }
33
+ case "thinking": {
34
+ const thinkContent = String(data.content ?? "");
35
+ const isDelta = Boolean(data.delta);
36
+ if (data.status === "started") break;
37
+ setActivityStatus("streaming");
38
+ const display = assembler.ingestThinking(runId, thinkContent, isDelta, state.showThinking);
39
+ if (display !== null) {
40
+ chatLog.updateAssistant(display, runId);
41
+ tui.requestRender();
42
+ }
43
+ break;
44
+ }
45
+ case "thinking_end":
46
+ case "message_end": break;
47
+ case "tool_start": {
48
+ const toolName = String(data.toolName ?? "unknown");
49
+ const toolCallId = String(data.toolCallId || crypto.randomUUID());
50
+ const stack = pendingToolCallIds.get(toolName) ?? [];
51
+ stack.push(toolCallId);
52
+ pendingToolCallIds.set(toolName, stack);
53
+ setActivityStatus("running");
54
+ chatLog.startTool(toolCallId, toolName, data.args, runId);
55
+ tui.requestRender();
56
+ break;
57
+ }
58
+ case "tool_end": {
59
+ const toolName = String(data.toolName ?? "");
60
+ let toolCallId = typeof data.toolCallId === "string" && data.toolCallId ? data.toolCallId : "";
61
+ if (!toolCallId && toolName) {
62
+ const stack = pendingToolCallIds.get(toolName);
63
+ if (stack && stack.length > 0) {
64
+ toolCallId = stack.shift();
65
+ if (stack.length === 0) pendingToolCallIds.delete(toolName);
66
+ }
67
+ }
68
+ const resultText = String(data.result ?? "");
69
+ const isError = Boolean(data.isError);
70
+ if (toolCallId) chatLog.updateToolResult(toolCallId, resultText, isError);
71
+ setActivityStatus("streaming");
72
+ tui.requestRender();
73
+ break;
74
+ }
75
+ case "error": {
76
+ const errorContent = String(data.content ?? "Unknown error");
77
+ const finalText = assembler.finalize(runId, state.showThinking);
78
+ if (finalText) chatLog.finalizeAssistant(finalText, runId);
79
+ chatLog.addSystem(`❌ ${errorContent}`);
80
+ state.activeRunId = null;
81
+ setActivityStatus("idle");
82
+ tui.requestRender();
83
+ break;
84
+ }
85
+ case "result": {
86
+ const finalText = assembler.finalize(runId, state.showThinking);
87
+ if (finalText) chatLog.finalizeAssistant(finalText, runId);
88
+ state.activeRunId = null;
89
+ setActivityStatus("idle");
90
+ tui.requestRender();
91
+ break;
92
+ }
93
+ case "progress":
94
+ setActivityStatus("running");
95
+ break;
96
+ default: break;
97
+ }
98
+ }
99
+ const DEFAULT_STREAMING_WATCHDOG_MS = 3e4;
100
+ //#endregion
101
+ export { DEFAULT_STREAMING_WATCHDOG_MS, clearPendingToolCallIds, dispatchAgentSSE };
102
+
103
+ //# sourceMappingURL=tui-agent-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-agent-events.js","names":[],"sources":["../../../src/tui/tui-agent-events.ts"],"sourcesContent":["import type { TUI } from '@mariozechner/pi-tui';\n\nimport type { ChatLog } from './components/chat-log.js';\nimport type { StreamAssembler } from './stream-assembler.js';\nimport type { TuiState } from './tui-types.js';\n\nconst pendingToolCallIds = new Map<string, string[]>();\n\nexport function clearPendingToolCallIds(): void {\n pendingToolCallIds.clear();\n}\n\nconst STREAM_TOUCH_EVENTS = new Set([\n 'status',\n 'token',\n 'thinking',\n 'tool_start',\n 'tool_end',\n 'progress',\n]);\n\nexport function dispatchAgentSSE(\n event: string,\n data: Record<string, unknown>,\n state: TuiState,\n chatLog: ChatLog,\n assembler: StreamAssembler,\n tui: TUI,\n setActivityStatus: (status: string) => void,\n touchStreamingActivity?: () => void,\n): void {\n if (STREAM_TOUCH_EVENTS.has(event)) {\n touchStreamingActivity?.();\n }\n\n const runId = state.activeRunId ?? 'default';\n\n switch (event) {\n case 'status': {\n const newRunId = typeof data.runId === 'string' ? data.runId : runId;\n state.activeRunId = newRunId;\n setActivityStatus('waiting');\n break;\n }\n case 'token': {\n const content =\n typeof data.content === 'string'\n ? data.content\n : typeof data.delta === 'string'\n ? data.delta\n : typeof data.text === 'string'\n ? data.text\n : '';\n if (!content) break;\n setActivityStatus('streaming');\n const display = assembler.ingestToken(runId, content, state.showThinking);\n if (display !== null) {\n chatLog.updateAssistant(display, runId);\n tui.requestRender();\n }\n break;\n }\n case 'thinking': {\n const thinkContent = String(data.content ?? '');\n const isDelta = Boolean(data.delta);\n if (data.status === 'started') break;\n setActivityStatus('streaming');\n const display = assembler.ingestThinking(runId, thinkContent, isDelta, state.showThinking);\n if (display !== null) {\n chatLog.updateAssistant(display, runId);\n tui.requestRender();\n }\n break;\n }\n case 'thinking_end':\n case 'message_end':\n break;\n case 'tool_start': {\n const toolName = String(data.toolName ?? 'unknown');\n const toolCallId = String(data.toolCallId || crypto.randomUUID());\n const stack = pendingToolCallIds.get(toolName) ?? [];\n stack.push(toolCallId);\n pendingToolCallIds.set(toolName, stack);\n setActivityStatus('running');\n chatLog.startTool(toolCallId, toolName, data.args, runId);\n tui.requestRender();\n break;\n }\n case 'tool_end': {\n const toolName = String(data.toolName ?? '');\n let toolCallId = typeof data.toolCallId === 'string' && data.toolCallId ? data.toolCallId : '';\n if (!toolCallId && toolName) {\n const stack = pendingToolCallIds.get(toolName);\n if (stack && stack.length > 0) {\n toolCallId = stack.shift()!;\n if (stack.length === 0) pendingToolCallIds.delete(toolName);\n }\n }\n const resultText = String(data.result ?? '');\n const isError = Boolean(data.isError);\n if (toolCallId) {\n chatLog.updateToolResult(toolCallId, resultText, isError);\n }\n setActivityStatus('streaming');\n tui.requestRender();\n break;\n }\n case 'error': {\n const errorContent = String(data.content ?? 'Unknown error');\n const finalText = assembler.finalize(runId, state.showThinking);\n if (finalText) {\n chatLog.finalizeAssistant(finalText, runId);\n }\n chatLog.addSystem(`❌ ${errorContent}`);\n state.activeRunId = null;\n setActivityStatus('idle');\n tui.requestRender();\n break;\n }\n case 'result': {\n const finalText = assembler.finalize(runId, state.showThinking);\n if (finalText) {\n chatLog.finalizeAssistant(finalText, runId);\n }\n state.activeRunId = null;\n setActivityStatus('idle');\n tui.requestRender();\n break;\n }\n case 'progress': {\n setActivityStatus('running');\n break;\n }\n default:\n break;\n }\n}\n\nexport const DEFAULT_STREAMING_WATCHDOG_MS = 30_000;\n"],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import type { ClientHistoryMessage } from '../session/client-history.js';
2
+ import type { SessionInfo } from './tui-types.js';
3
+ /** Options for sending a chat message. */
4
+ export interface ChatSendOptions {
5
+ sessionKey: string;
6
+ message: string;
7
+ thinking?: string;
8
+ }
9
+ /** SSE event from the agent stream or broadcast channel. */
10
+ export interface TuiEvent {
11
+ event: string;
12
+ data: unknown;
13
+ }
14
+ /** Minimal session list item. */
15
+ export interface TuiSessionItem {
16
+ key: string;
17
+ updatedAt?: number | null;
18
+ model?: string | null;
19
+ totalTokens?: number | null;
20
+ displayName?: string;
21
+ }
22
+ /** Model choice for the selector overlay. */
23
+ export interface TuiModelChoice {
24
+ id: string;
25
+ name: string;
26
+ provider: string;
27
+ }
28
+ /**
29
+ * Abstraction over the gateway (SSE) or embedded agent backend.
30
+ *
31
+ * Both implementations expose the same surface so the TUI core stays
32
+ * transport-agnostic.
33
+ */
34
+ export interface TuiBackend {
35
+ /** Connection metadata (for header display). */
36
+ readonly connectionLabel: string;
37
+ /** Lifecycle callbacks wired by the TUI. */
38
+ onEvent?: (evt: TuiEvent) => void;
39
+ onConnected?: () => void;
40
+ onDisconnected?: (reason: string) => void;
41
+ /** Broadcast SSE sequence gap (if the gateway emits `gap` events). */
42
+ onGap?: (info: {
43
+ expected: number;
44
+ received: number;
45
+ }) => void;
46
+ /** Start the backend (open SSE streams / start agent service). */
47
+ start(): void;
48
+ /** Stop the backend. */
49
+ stop(): void;
50
+ /** Send a chat message, returns the run id. */
51
+ sendChat(opts: ChatSendOptions): Promise<{
52
+ runId: string;
53
+ }>;
54
+ /** Abort an active run. */
55
+ abortChat(opts: {
56
+ sessionKey: string;
57
+ runId: string;
58
+ }): Promise<{
59
+ ok: boolean;
60
+ }>;
61
+ /** Load chat history for a session. */
62
+ loadHistory(opts: {
63
+ sessionKey: string;
64
+ limit?: number;
65
+ }): Promise<{
66
+ messages: HistoryMessage[];
67
+ }>;
68
+ /** List sessions. */
69
+ listSessions(): Promise<TuiSessionItem[]>;
70
+ /** Fetch session info (model, tokens, thinking). */
71
+ getSessionInfo(sessionKey: string): Promise<SessionInfo>;
72
+ /** List available models. */
73
+ listModels(): Promise<TuiModelChoice[]>;
74
+ /** Reset / create new session. */
75
+ resetSession(sessionKey: string): Promise<void>;
76
+ /** Patch session settings (e.g. model). */
77
+ patchSession(sessionKey: string, patch: Record<string, unknown>): Promise<void>;
78
+ }
79
+ /** A single message in chat history (aligned with `ClientHistoryMessage`). */
80
+ export type HistoryMessage = ClientHistoryMessage;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { TUI } from '@mariozechner/pi-tui';
2
+ import type { ChatLog } from './components/chat-log.js';
3
+ import type { StreamAssembler } from './stream-assembler.js';
4
+ import type { TuiState } from './tui-types.js';
5
+ interface SlashCommandDef {
6
+ name: string;
7
+ description: string;
8
+ }
9
+ export declare function getSlashCommands(_isLocal: boolean): SlashCommandDef[];
10
+ export declare function formatTuiHelpText(isLocal: boolean): string;
11
+ export type CommandHandlerDeps = {
12
+ state: TuiState;
13
+ chatLog: ChatLog;
14
+ tui: TUI;
15
+ assembler: StreamAssembler;
16
+ isLocalMode: boolean;
17
+ abortActive: () => Promise<void>;
18
+ sendMessage: (text: string) => void;
19
+ requestExit: () => void;
20
+ updateFooter: () => void;
21
+ };
22
+ export declare function createTuiCommandHandler(deps: CommandHandlerDeps): (input: string) => void;
23
+ export {};
@@ -0,0 +1,165 @@
1
+ //#region src/tui/tui-commands.ts
2
+ function getSlashCommands(_isLocal) {
3
+ return [
4
+ {
5
+ name: "help",
6
+ description: "Show available commands"
7
+ },
8
+ {
9
+ name: "abort",
10
+ description: "Abort active run (or press Escape)"
11
+ },
12
+ {
13
+ name: "tools",
14
+ description: "Toggle tool output expanded/collapsed (or Ctrl+O)"
15
+ },
16
+ {
17
+ name: "thinking",
18
+ description: "Toggle thinking display (or Ctrl+T)"
19
+ },
20
+ {
21
+ name: "exit",
22
+ description: "Exit the TUI"
23
+ },
24
+ {
25
+ name: "models",
26
+ description: "List available models"
27
+ },
28
+ {
29
+ name: "switch",
30
+ description: "Switch model (e.g. /switch openai/gpt-4o)"
31
+ },
32
+ {
33
+ name: "usage",
34
+ description: "Show token usage statistics"
35
+ },
36
+ {
37
+ name: "new",
38
+ description: "Start a new session"
39
+ },
40
+ {
41
+ name: "clear",
42
+ description: "Clear current session"
43
+ },
44
+ {
45
+ name: "list",
46
+ description: "List sessions"
47
+ },
48
+ {
49
+ name: "compact",
50
+ description: "Compact session history"
51
+ },
52
+ {
53
+ name: "think",
54
+ description: "Set thinking level (e.g. /think high)"
55
+ },
56
+ {
57
+ name: "reasoning",
58
+ description: "Set reasoning visibility (e.g. /reasoning stream)"
59
+ },
60
+ {
61
+ name: "verbose",
62
+ description: "Toggle verbose mode"
63
+ },
64
+ {
65
+ name: "status",
66
+ description: "Show agent status"
67
+ },
68
+ {
69
+ name: "config",
70
+ description: "Show or update configuration"
71
+ },
72
+ {
73
+ name: "context",
74
+ description: "Show context budget"
75
+ },
76
+ {
77
+ name: "btw",
78
+ description: "Side question without saving to session"
79
+ },
80
+ {
81
+ name: "export",
82
+ description: "Export session (markdown/html/json)"
83
+ },
84
+ {
85
+ name: "settings",
86
+ description: "Show current settings"
87
+ },
88
+ {
89
+ name: "start",
90
+ description: "Show welcome message"
91
+ }
92
+ ];
93
+ }
94
+ function formatTuiHelpText(isLocal) {
95
+ const commands = getSlashCommands(isLocal);
96
+ const lines = ["Available commands:"];
97
+ for (const c of commands) lines.push(` /${c.name} — ${c.description}`);
98
+ lines.push("", "Keyboard shortcuts:");
99
+ lines.push(" Escape — Abort active run");
100
+ lines.push(" Ctrl+L — Model picker");
101
+ lines.push(" Ctrl+P — Session picker");
102
+ lines.push(" Ctrl+O — Toggle tool output");
103
+ lines.push(" Ctrl+T — Toggle thinking display");
104
+ lines.push(" Ctrl+C — Clear input; empty line: warn, then press again within 1s to exit");
105
+ lines.push(" Ctrl+D — Exit");
106
+ lines.push(" !cmd — Local shell (gated; runs on this machine)");
107
+ return lines.join("\n");
108
+ }
109
+ function createTuiCommandHandler(deps) {
110
+ const { state, chatLog, tui, assembler, isLocalMode, abortActive, sendMessage, requestExit, updateFooter } = deps;
111
+ return (input) => {
112
+ const [commandName] = input.replace(/^\//, "").trim().split(/\s+/);
113
+ const normalizedCommand = (commandName ?? "").toLowerCase();
114
+ switch (normalizedCommand) {
115
+ case "help":
116
+ chatLog.addSystem(formatTuiHelpText(isLocalMode));
117
+ tui.requestRender();
118
+ return;
119
+ case "exit":
120
+ case "quit":
121
+ requestExit();
122
+ return;
123
+ case "abort":
124
+ case "stop":
125
+ case "cancel":
126
+ abortActive().then(() => {
127
+ chatLog.addSystem("Aborted.");
128
+ tui.requestRender();
129
+ });
130
+ return;
131
+ case "tools":
132
+ state.toolsExpanded = !state.toolsExpanded;
133
+ chatLog.setToolsExpanded(state.toolsExpanded);
134
+ chatLog.addSystem(`Tools: ${state.toolsExpanded ? "expanded" : "collapsed"}`);
135
+ tui.requestRender();
136
+ return;
137
+ case "thinking":
138
+ state.showThinking = !state.showThinking;
139
+ chatLog.addSystem(`Thinking display: ${state.showThinking ? "on" : "off"}`);
140
+ updateFooter();
141
+ tui.requestRender();
142
+ return;
143
+ default: break;
144
+ }
145
+ switch (normalizedCommand) {
146
+ case "new":
147
+ case "reset":
148
+ case "restart":
149
+ case "clear":
150
+ abortActive().then(() => {
151
+ assembler.clear();
152
+ chatLog.clearAll();
153
+ tui.requestRender();
154
+ sendMessage(input);
155
+ });
156
+ return;
157
+ default: break;
158
+ }
159
+ sendMessage(input);
160
+ };
161
+ }
162
+ //#endregion
163
+ export { createTuiCommandHandler, formatTuiHelpText, getSlashCommands };
164
+
165
+ //# sourceMappingURL=tui-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-commands.js","names":[],"sources":["../../../src/tui/tui-commands.ts"],"sourcesContent":["import type { TUI } from '@mariozechner/pi-tui';\n\nimport type { ChatLog } from './components/chat-log.js';\nimport type { StreamAssembler } from './stream-assembler.js';\nimport type { TuiState } from './tui-types.js';\n\ninterface SlashCommandDef {\n name: string;\n description: string;\n}\n\nexport function getSlashCommands(_isLocal: boolean): SlashCommandDef[] {\n return [\n { name: 'help', description: 'Show available commands' },\n { name: 'abort', description: 'Abort active run (or press Escape)' },\n { name: 'tools', description: 'Toggle tool output expanded/collapsed (or Ctrl+O)' },\n { name: 'thinking', description: 'Toggle thinking display (or Ctrl+T)' },\n { name: 'exit', description: 'Exit the TUI' },\n { name: 'models', description: 'List available models' },\n { name: 'switch', description: 'Switch model (e.g. /switch openai/gpt-4o)' },\n { name: 'usage', description: 'Show token usage statistics' },\n { name: 'new', description: 'Start a new session' },\n { name: 'clear', description: 'Clear current session' },\n { name: 'list', description: 'List sessions' },\n { name: 'compact', description: 'Compact session history' },\n { name: 'think', description: 'Set thinking level (e.g. /think high)' },\n { name: 'reasoning', description: 'Set reasoning visibility (e.g. /reasoning stream)' },\n { name: 'verbose', description: 'Toggle verbose mode' },\n { name: 'status', description: 'Show agent status' },\n { name: 'config', description: 'Show or update configuration' },\n { name: 'context', description: 'Show context budget' },\n { name: 'btw', description: 'Side question without saving to session' },\n { name: 'export', description: 'Export session (markdown/html/json)' },\n { name: 'settings', description: 'Show current settings' },\n { name: 'start', description: 'Show welcome message' },\n ];\n}\n\nexport function formatTuiHelpText(isLocal: boolean): string {\n const commands = getSlashCommands(isLocal);\n const lines = ['Available commands:'];\n for (const c of commands) {\n lines.push(` /${c.name} — ${c.description}`);\n }\n lines.push('', 'Keyboard shortcuts:');\n lines.push(' Escape — Abort active run');\n lines.push(' Ctrl+L — Model picker');\n lines.push(' Ctrl+P — Session picker');\n lines.push(' Ctrl+O — Toggle tool output');\n lines.push(' Ctrl+T — Toggle thinking display');\n lines.push(' Ctrl+C — Clear input; empty line: warn, then press again within 1s to exit');\n lines.push(' Ctrl+D — Exit');\n lines.push(' !cmd — Local shell (gated; runs on this machine)');\n return lines.join('\\n');\n}\n\nexport type CommandHandlerDeps = {\n state: TuiState;\n chatLog: ChatLog;\n tui: TUI;\n assembler: StreamAssembler;\n isLocalMode: boolean;\n abortActive: () => Promise<void>;\n sendMessage: (text: string) => void;\n requestExit: () => void;\n updateFooter: () => void;\n};\n\nexport function createTuiCommandHandler(deps: CommandHandlerDeps): (input: string) => void {\n const {\n state,\n chatLog,\n tui,\n assembler,\n isLocalMode,\n abortActive,\n sendMessage,\n requestExit,\n updateFooter,\n } = deps;\n\n return (input: string) => {\n const trimmed = input.replace(/^\\//, '').trim();\n const [commandName] = trimmed.split(/\\s+/);\n const normalizedCommand = (commandName ?? '').toLowerCase();\n\n switch (normalizedCommand) {\n case 'help':\n chatLog.addSystem(formatTuiHelpText(isLocalMode));\n tui.requestRender();\n return;\n case 'exit':\n case 'quit':\n requestExit();\n return;\n case 'abort':\n case 'stop':\n case 'cancel':\n void abortActive().then(() => {\n chatLog.addSystem('Aborted.');\n tui.requestRender();\n });\n return;\n case 'tools':\n state.toolsExpanded = !state.toolsExpanded;\n chatLog.setToolsExpanded(state.toolsExpanded);\n chatLog.addSystem(`Tools: ${state.toolsExpanded ? 'expanded' : 'collapsed'}`);\n tui.requestRender();\n return;\n case 'thinking':\n state.showThinking = !state.showThinking;\n chatLog.addSystem(`Thinking display: ${state.showThinking ? 'on' : 'off'}`);\n updateFooter();\n tui.requestRender();\n return;\n default:\n break;\n }\n\n switch (normalizedCommand) {\n case 'new':\n case 'reset':\n case 'restart':\n case 'clear': {\n void abortActive().then(() => {\n assembler.clear();\n chatLog.clearAll();\n tui.requestRender();\n sendMessage(input);\n });\n return;\n }\n default:\n break;\n }\n\n sendMessage(input);\n };\n}\n"],"mappings":";AAWA,SAAgB,iBAAiB,UAAsC;AACrE,QAAO;EACL;GAAE,MAAM;GAAQ,aAAa;GAA2B;EACxD;GAAE,MAAM;GAAS,aAAa;GAAsC;EACpE;GAAE,MAAM;GAAS,aAAa;GAAqD;EACnF;GAAE,MAAM;GAAY,aAAa;GAAuC;EACxE;GAAE,MAAM;GAAQ,aAAa;GAAgB;EAC7C;GAAE,MAAM;GAAU,aAAa;GAAyB;EACxD;GAAE,MAAM;GAAU,aAAa;GAA6C;EAC5E;GAAE,MAAM;GAAS,aAAa;GAA+B;EAC7D;GAAE,MAAM;GAAO,aAAa;GAAuB;EACnD;GAAE,MAAM;GAAS,aAAa;GAAyB;EACvD;GAAE,MAAM;GAAQ,aAAa;GAAiB;EAC9C;GAAE,MAAM;GAAW,aAAa;GAA2B;EAC3D;GAAE,MAAM;GAAS,aAAa;GAAyC;EACvE;GAAE,MAAM;GAAa,aAAa;GAAqD;EACvF;GAAE,MAAM;GAAW,aAAa;GAAuB;EACvD;GAAE,MAAM;GAAU,aAAa;GAAqB;EACpD;GAAE,MAAM;GAAU,aAAa;GAAgC;EAC/D;GAAE,MAAM;GAAW,aAAa;GAAuB;EACvD;GAAE,MAAM;GAAO,aAAa;GAA2C;EACvE;GAAE,MAAM;GAAU,aAAa;GAAuC;EACtE;GAAE,MAAM;GAAY,aAAa;GAAyB;EAC1D;GAAE,MAAM;GAAS,aAAa;GAAwB;EACvD;;AAGH,SAAgB,kBAAkB,SAA0B;CAC1D,MAAM,WAAW,iBAAiB,QAAQ;CAC1C,MAAM,QAAQ,CAAC,sBAAsB;AACrC,MAAK,MAAM,KAAK,SACd,OAAM,KAAK,MAAM,EAAE,KAAK,KAAK,EAAE,cAAc;AAE/C,OAAM,KAAK,IAAI,sBAAsB;AACrC,OAAM,KAAK,8BAA8B;AACzC,OAAM,KAAK,0BAA0B;AACrC,OAAM,KAAK,4BAA4B;AACvC,OAAM,KAAK,gCAAgC;AAC3C,OAAM,KAAK,qCAAqC;AAChD,OAAM,KAAK,+EAA+E;AAC1F,OAAM,KAAK,kBAAkB;AAC7B,OAAM,KAAK,qDAAqD;AAChE,QAAO,MAAM,KAAK,KAAK;;AAezB,SAAgB,wBAAwB,MAAmD;CACzF,MAAM,EACJ,OACA,SACA,KACA,WACA,aACA,aACA,aACA,aACA,iBACE;AAEJ,SAAQ,UAAkB;EAExB,MAAM,CAAC,eADS,MAAM,QAAQ,OAAO,GAAG,CAAC,MACZ,CAAC,MAAM,MAAM;EAC1C,MAAM,qBAAqB,eAAe,IAAI,aAAa;AAE3D,UAAQ,mBAAR;GACE,KAAK;AACH,YAAQ,UAAU,kBAAkB,YAAY,CAAC;AACjD,QAAI,eAAe;AACnB;GACF,KAAK;GACL,KAAK;AACH,iBAAa;AACb;GACF,KAAK;GACL,KAAK;GACL,KAAK;AACE,iBAAa,CAAC,WAAW;AAC5B,aAAQ,UAAU,WAAW;AAC7B,SAAI,eAAe;MACnB;AACF;GACF,KAAK;AACH,UAAM,gBAAgB,CAAC,MAAM;AAC7B,YAAQ,iBAAiB,MAAM,cAAc;AAC7C,YAAQ,UAAU,UAAU,MAAM,gBAAgB,aAAa,cAAc;AAC7E,QAAI,eAAe;AACnB;GACF,KAAK;AACH,UAAM,eAAe,CAAC,MAAM;AAC5B,YAAQ,UAAU,qBAAqB,MAAM,eAAe,OAAO,QAAQ;AAC3E,kBAAc;AACd,QAAI,eAAe;AACnB;GACF,QACE;;AAGJ,UAAQ,mBAAR;GACE,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;AACE,iBAAa,CAAC,WAAW;AAC5B,eAAU,OAAO;AACjB,aAAQ,UAAU;AAClB,SAAI,eAAe;AACnB,iBAAY,MAAM;MAClB;AACF;GAEF,QACE;;AAGJ,cAAY,MAAM"}
@@ -0,0 +1,26 @@
1
+ /** Suppress duplicate backspace bursts from some terminals (openclaw-aligned). */
2
+ export declare function createBackspaceDeduper(params?: {
3
+ dedupeWindowMs?: number;
4
+ now?: () => number;
5
+ }): (data: string) => string;
6
+ export declare function isIgnorableTuiStopError(error: unknown): boolean;
7
+ export declare function stopTuiSafely(stop: () => void): void;
8
+ export type DrainableTui = {
9
+ stop: () => void;
10
+ terminal?: {
11
+ drainInput?: (maxMs?: number, idleMs?: number) => Promise<void>;
12
+ };
13
+ };
14
+ export declare function drainAndStopTuiSafely(tui: DrainableTui): Promise<void>;
15
+ type CtrlCAction = 'clear' | 'warn' | 'exit';
16
+ /** Double Ctrl+C within `exitWindowMs` to exit when the input line is empty (openclaw semantics). */
17
+ export declare function resolveCtrlCAction(params: {
18
+ hasInput: boolean;
19
+ now: number;
20
+ lastCtrlCAt: number;
21
+ exitWindowMs?: number;
22
+ }): {
23
+ action: CtrlCAction;
24
+ nextLastCtrlCAt: number;
25
+ };
26
+ export {};
@@ -0,0 +1,57 @@
1
+ import { Key, matchesKey } from "@mariozechner/pi-tui";
2
+ //#region src/tui/tui-lifecycle.ts
3
+ /** Suppress duplicate backspace bursts from some terminals (openclaw-aligned). */
4
+ function createBackspaceDeduper(params) {
5
+ const dedupeWindowMs = Math.max(0, Math.floor(params?.dedupeWindowMs ?? 8));
6
+ const now = params?.now ?? (() => Date.now());
7
+ let lastBackspaceAt = -1;
8
+ return (data) => {
9
+ if (data !== "\b" && !matchesKey(data, Key.backspace)) return data;
10
+ const ts = now();
11
+ if (lastBackspaceAt >= 0 && ts - lastBackspaceAt <= dedupeWindowMs) return "";
12
+ lastBackspaceAt = ts;
13
+ return data;
14
+ };
15
+ }
16
+ function isIgnorableTuiStopError(error) {
17
+ if (!error || typeof error !== "object") return false;
18
+ const err = error;
19
+ const code = typeof err.code === "string" ? err.code : "";
20
+ const syscall = typeof err.syscall === "string" ? err.syscall : "";
21
+ const message = typeof err.message === "string" ? err.message : "";
22
+ if (code === "EBADF" && syscall === "setRawMode") return true;
23
+ return /setRawMode/i.test(message) && /EBADF/i.test(message);
24
+ }
25
+ function stopTuiSafely(stop) {
26
+ try {
27
+ stop();
28
+ } catch (error) {
29
+ if (!isIgnorableTuiStopError(error)) throw error;
30
+ }
31
+ }
32
+ async function drainAndStopTuiSafely(tui) {
33
+ if (typeof tui.terminal?.drainInput === "function") try {
34
+ await tui.terminal.drainInput();
35
+ } catch {}
36
+ stopTuiSafely(() => tui.stop());
37
+ }
38
+ /** Double Ctrl+C within `exitWindowMs` to exit when the input line is empty (openclaw semantics). */
39
+ function resolveCtrlCAction(params) {
40
+ const exitWindowMs = Math.max(1, Math.floor(params.exitWindowMs ?? 1e3));
41
+ if (params.hasInput) return {
42
+ action: "clear",
43
+ nextLastCtrlCAt: params.now
44
+ };
45
+ if (params.now - params.lastCtrlCAt <= exitWindowMs) return {
46
+ action: "exit",
47
+ nextLastCtrlCAt: params.lastCtrlCAt
48
+ };
49
+ return {
50
+ action: "warn",
51
+ nextLastCtrlCAt: params.now
52
+ };
53
+ }
54
+ //#endregion
55
+ export { createBackspaceDeduper, drainAndStopTuiSafely, isIgnorableTuiStopError, resolveCtrlCAction, stopTuiSafely };
56
+
57
+ //# sourceMappingURL=tui-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-lifecycle.js","names":[],"sources":["../../../src/tui/tui-lifecycle.ts"],"sourcesContent":["import { Key, matchesKey } from '@mariozechner/pi-tui';\n\n/** Suppress duplicate backspace bursts from some terminals (openclaw-aligned). */\nexport function createBackspaceDeduper(params?: { dedupeWindowMs?: number; now?: () => number }) {\n const dedupeWindowMs = Math.max(0, Math.floor(params?.dedupeWindowMs ?? 8));\n const now = params?.now ?? (() => Date.now());\n let lastBackspaceAt = -1;\n\n return (data: string): string => {\n if (data !== '\\x08' && !matchesKey(data, Key.backspace)) {\n return data;\n }\n const ts = now();\n if (lastBackspaceAt >= 0 && ts - lastBackspaceAt <= dedupeWindowMs) {\n return '';\n }\n lastBackspaceAt = ts;\n return data;\n };\n}\n\nexport function isIgnorableTuiStopError(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n const err = error as { code?: unknown; syscall?: unknown; message?: unknown };\n const code = typeof err.code === 'string' ? err.code : '';\n const syscall = typeof err.syscall === 'string' ? err.syscall : '';\n const message = typeof err.message === 'string' ? err.message : '';\n if (code === 'EBADF' && syscall === 'setRawMode') {\n return true;\n }\n return /setRawMode/i.test(message) && /EBADF/i.test(message);\n}\n\nexport function stopTuiSafely(stop: () => void): void {\n try {\n stop();\n } catch (error) {\n if (!isIgnorableTuiStopError(error)) {\n throw error;\n }\n }\n}\n\nexport type DrainableTui = {\n stop: () => void;\n terminal?: {\n drainInput?: (maxMs?: number, idleMs?: number) => Promise<void>;\n };\n};\n\nexport async function drainAndStopTuiSafely(tui: DrainableTui): Promise<void> {\n if (typeof tui.terminal?.drainInput === 'function') {\n try {\n await tui.terminal.drainInput();\n } catch {\n // Best-effort only.\n }\n }\n stopTuiSafely(() => tui.stop());\n}\n\ntype CtrlCAction = 'clear' | 'warn' | 'exit';\n\n/** Double Ctrl+C within `exitWindowMs` to exit when the input line is empty (openclaw semantics). */\nexport function resolveCtrlCAction(params: {\n hasInput: boolean;\n now: number;\n lastCtrlCAt: number;\n exitWindowMs?: number;\n}): { action: CtrlCAction; nextLastCtrlCAt: number } {\n const exitWindowMs = Math.max(1, Math.floor(params.exitWindowMs ?? 1000));\n if (params.hasInput) {\n return { action: 'clear', nextLastCtrlCAt: params.now };\n }\n if (params.now - params.lastCtrlCAt <= exitWindowMs) {\n return { action: 'exit', nextLastCtrlCAt: params.lastCtrlCAt };\n }\n return { action: 'warn', nextLastCtrlCAt: params.now };\n}\n"],"mappings":";;;AAGA,SAAgB,uBAAuB,QAA0D;CAC/F,MAAM,iBAAiB,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,kBAAkB,EAAE,CAAC;CAC3E,MAAM,MAAM,QAAQ,cAAc,KAAK,KAAK;CAC5C,IAAI,kBAAkB;AAEtB,SAAQ,SAAyB;AAC/B,MAAI,SAAS,QAAU,CAAC,WAAW,MAAM,IAAI,UAAU,CACrD,QAAO;EAET,MAAM,KAAK,KAAK;AAChB,MAAI,mBAAmB,KAAK,KAAK,mBAAmB,eAClD,QAAO;AAET,oBAAkB;AAClB,SAAO;;;AAIX,SAAgB,wBAAwB,OAAyB;AAC/D,KAAI,CAAC,SAAS,OAAO,UAAU,SAC7B,QAAO;CAET,MAAM,MAAM;CACZ,MAAM,OAAO,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;CACvD,MAAM,UAAU,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;CAChE,MAAM,UAAU,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;AAChE,KAAI,SAAS,WAAW,YAAY,aAClC,QAAO;AAET,QAAO,cAAc,KAAK,QAAQ,IAAI,SAAS,KAAK,QAAQ;;AAG9D,SAAgB,cAAc,MAAwB;AACpD,KAAI;AACF,QAAM;UACC,OAAO;AACd,MAAI,CAAC,wBAAwB,MAAM,CACjC,OAAM;;;AAYZ,eAAsB,sBAAsB,KAAkC;AAC5E,KAAI,OAAO,IAAI,UAAU,eAAe,WACtC,KAAI;AACF,QAAM,IAAI,SAAS,YAAY;SACzB;AAIV,qBAAoB,IAAI,MAAM,CAAC;;;AAMjC,SAAgB,mBAAmB,QAKkB;CACnD,MAAM,eAAe,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,gBAAgB,IAAK,CAAC;AACzE,KAAI,OAAO,SACT,QAAO;EAAE,QAAQ;EAAS,iBAAiB,OAAO;EAAK;AAEzD,KAAI,OAAO,MAAM,OAAO,eAAe,aACrC,QAAO;EAAE,QAAQ;EAAQ,iBAAiB,OAAO;EAAa;AAEhE,QAAO;EAAE,QAAQ;EAAQ,iBAAiB,OAAO;EAAK"}
@@ -0,0 +1,28 @@
1
+ import { spawn } from 'node:child_process';
2
+ import type { Component } from '@mariozechner/pi-tui';
3
+ type LocalShellDeps = {
4
+ chatLog: {
5
+ addSystem: (line: string) => void;
6
+ };
7
+ tui: {
8
+ requestRender: () => void;
9
+ setFocus: (c: Component) => void;
10
+ };
11
+ editor: Component;
12
+ openOverlay: (component: Component) => void;
13
+ closeOverlay: () => void;
14
+ spawnCommand?: typeof spawn;
15
+ getCwd?: () => string;
16
+ env?: NodeJS.ProcessEnv;
17
+ maxOutputChars?: number;
18
+ /** Pause stdout/stderr filtering before handing the terminal to a child (`stdio: 'inherit'`). */
19
+ pauseStdioFilter?: () => void;
20
+ resumeStdioFilter?: () => void;
21
+ /** Wrap work while the TUI is stopped (full-screen subprocess). */
22
+ runWithInheritedStdio?: (work: () => Promise<void>) => Promise<void>;
23
+ };
24
+ /** `!command` runs on the local machine (gated by in-session consent). `!!command` uses inherited stdio. */
25
+ export declare function createLocalShellRunner(deps: LocalShellDeps): {
26
+ runLocalShellLine: (line: string) => Promise<void>;
27
+ };
28
+ export {};