@xopcai/xopc 0.0.26 → 0.0.28

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 (152) 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-Clv9i1Kb.js → agents-DplaQYS2.js} +2 -2
  13. package/dist/gateway/static/root/assets/{agents-Clv9i1Kb.js.map → agents-DplaQYS2.js.map} +1 -1
  14. package/dist/gateway/static/root/assets/{apps-page-DqclV-PP.js → apps-page-Co95hLOJ.js} +2 -2
  15. package/dist/gateway/static/root/assets/{apps-page-DqclV-PP.js.map → apps-page-Co95hLOJ.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/{channels-settings-CLyTYjrz.js → channels-settings-CkfSST0k.js} +2 -2
  17. package/dist/gateway/static/root/assets/{channels-settings-CLyTYjrz.js.map → channels-settings-CkfSST0k.js.map} +1 -1
  18. package/dist/gateway/static/root/assets/{cron-page-CU8lutMt.js → cron-page-D9q6KqL8.js} +2 -2
  19. package/dist/gateway/static/root/assets/{cron-page-CU8lutMt.js.map → cron-page-D9q6KqL8.js.map} +1 -1
  20. package/dist/gateway/static/root/assets/{cron-utils-_UjiWax6.js → cron-utils-BmzF4m1y.js} +2 -2
  21. package/dist/gateway/static/root/assets/{cron-utils-_UjiWax6.js.map → cron-utils-BmzF4m1y.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/{dist-Xqb4IGWC.js → dist-Dn-ufXyc.js} +2 -2
  23. package/dist/gateway/static/root/assets/{dist-Xqb4IGWC.js.map → dist-Dn-ufXyc.js.map} +1 -1
  24. package/dist/gateway/static/root/assets/{extension-debug-page-CtTUkAmw.js → extension-debug-page-BZ8xQ74_.js} +2 -2
  25. package/dist/gateway/static/root/assets/{extension-debug-page-CtTUkAmw.js.map → extension-debug-page-BZ8xQ74_.js.map} +1 -1
  26. package/dist/gateway/static/root/assets/{extension-page-C-aQU8qR.js → extension-page-BlNgKxwW.js} +2 -2
  27. package/dist/gateway/static/root/assets/{extension-page-C-aQU8qR.js.map → extension-page-BlNgKxwW.js.map} +1 -1
  28. package/dist/gateway/static/root/assets/{extension-settings-page-b0y9aY-q.js → extension-settings-page-CWTdW_oY.js} +2 -2
  29. package/dist/gateway/static/root/assets/{extension-settings-page-b0y9aY-q.js.map → extension-settings-page-CWTdW_oY.js.map} +1 -1
  30. package/dist/gateway/static/root/assets/index-OT4cGzon.css +1 -0
  31. package/dist/gateway/static/root/assets/{index-Gr2HWo-G.js → index-lV8FGWlt.js} +94 -94
  32. package/dist/gateway/static/root/assets/{index-Gr2HWo-G.js.map → index-lV8FGWlt.js.map} +1 -1
  33. package/dist/gateway/static/root/assets/logs-page-DG31RpvG.js +2 -0
  34. package/dist/gateway/static/root/assets/logs-page-DG31RpvG.js.map +1 -0
  35. package/dist/gateway/static/root/assets/sessions-page-CdmjxDEM.js +2 -0
  36. package/dist/gateway/static/root/assets/{sessions-page-Cryg-36Z.js.map → sessions-page-CdmjxDEM.js.map} +1 -1
  37. package/dist/gateway/static/root/assets/{settings-page-DFNKT9yg.js → settings-page-DU2XLf5s.js} +2 -2
  38. package/dist/gateway/static/root/assets/{settings-page-DFNKT9yg.js.map → settings-page-DU2XLf5s.js.map} +1 -1
  39. package/dist/gateway/static/root/assets/{skills-page-D4gfh0Ih.js → skills-page-lb7vYtlP.js} +2 -2
  40. package/dist/gateway/static/root/assets/{skills-page-D4gfh0Ih.js.map → skills-page-lb7vYtlP.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/channels/index.js +2 -2
  44. package/dist/src/channels/manager.js +2 -2
  45. package/dist/src/channels/weixin/index.js +1 -1
  46. package/dist/src/cli/agent-chat-log-level-preset.d.ts +7 -0
  47. package/dist/src/cli/agent-chat-log-level-preset.js +22 -0
  48. package/dist/src/cli/agent-chat-log-level-preset.js.map +1 -0
  49. package/dist/src/cli/commands/agent/interactive.js +4 -2
  50. package/dist/src/cli/commands/agent/interactive.js.map +1 -1
  51. package/dist/src/cli/commands/agent/stream-renderer.d.ts +14 -0
  52. package/dist/src/cli/commands/agent/stream-renderer.js +99 -0
  53. package/dist/src/cli/commands/agent/stream-renderer.js.map +1 -0
  54. package/dist/src/cli/commands/agent.js +2 -2
  55. package/dist/src/cli/commands/agent.js.map +1 -1
  56. package/dist/src/cli/commands/onboard.js +77 -93
  57. package/dist/src/cli/commands/onboard.js.map +1 -1
  58. package/dist/src/cli/commands/tui.d.ts +1 -0
  59. package/dist/src/cli/commands/tui.js +40 -0
  60. package/dist/src/cli/commands/tui.js.map +1 -0
  61. package/dist/src/cli/index.d.ts +2 -0
  62. package/dist/src/cli/index.js +3 -0
  63. package/dist/src/cli/index.js.map +1 -1
  64. package/dist/src/config/schema.d.ts +6 -0
  65. package/dist/src/config/schema.js +6 -1
  66. package/dist/src/config/schema.js.map +1 -1
  67. package/dist/src/gateway/auth.d.ts +17 -3
  68. package/dist/src/gateway/auth.js +35 -16
  69. package/dist/src/gateway/auth.js.map +1 -1
  70. package/dist/src/gateway/hono/app.js +30 -1
  71. package/dist/src/gateway/hono/app.js.map +1 -1
  72. package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -1
  73. package/dist/src/gateway/hono/middleware/auth.js +4 -3
  74. package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
  75. package/dist/src/gateway/hono/middleware/scopes.d.ts +15 -0
  76. package/dist/src/gateway/hono/middleware/scopes.js +41 -0
  77. package/dist/src/gateway/hono/middleware/scopes.js.map +1 -0
  78. package/dist/src/gateway/security/audit.d.ts +18 -0
  79. package/dist/src/gateway/security/audit.js +68 -0
  80. package/dist/src/gateway/security/audit.js.map +1 -0
  81. package/dist/src/gateway/security/csp.d.ts +19 -0
  82. package/dist/src/gateway/security/csp.js +52 -0
  83. package/dist/src/gateway/security/csp.js.map +1 -0
  84. package/dist/src/gateway/security/dangerous-tools.d.ts +20 -0
  85. package/dist/src/gateway/security/dangerous-tools.js +46 -0
  86. package/dist/src/gateway/security/dangerous-tools.js.map +1 -0
  87. package/dist/src/gateway/security/flood-guard.d.ts +28 -0
  88. package/dist/src/gateway/security/flood-guard.js +42 -0
  89. package/dist/src/gateway/security/flood-guard.js.map +1 -0
  90. package/dist/src/gateway/security/index.d.ts +9 -0
  91. package/dist/src/gateway/security/index.js +10 -0
  92. package/dist/src/gateway/security/known-weak-secrets.d.ts +10 -0
  93. package/dist/src/gateway/security/known-weak-secrets.js +36 -0
  94. package/dist/src/gateway/security/known-weak-secrets.js.map +1 -0
  95. package/dist/src/gateway/security/operator-scopes.d.ts +37 -0
  96. package/dist/src/gateway/security/operator-scopes.js +137 -0
  97. package/dist/src/gateway/security/operator-scopes.js.map +1 -0
  98. package/dist/src/gateway/security/origin-check.d.ts +21 -0
  99. package/dist/src/gateway/security/origin-check.js +56 -0
  100. package/dist/src/gateway/security/origin-check.js.map +1 -0
  101. package/dist/src/gateway/security/preauth-connection-budget.d.ts +17 -0
  102. package/dist/src/gateway/security/preauth-connection-budget.js +49 -0
  103. package/dist/src/gateway/security/preauth-connection-budget.js.map +1 -0
  104. package/dist/src/gateway/security/secret-equal.d.ts +8 -0
  105. package/dist/src/gateway/security/secret-equal.js +30 -0
  106. package/dist/src/gateway/security/secret-equal.js.map +1 -0
  107. package/dist/src/gateway/service.d.ts +1 -1
  108. package/dist/src/gateway/service.js +11 -2
  109. package/dist/src/gateway/service.js.map +1 -1
  110. package/dist/src/tui/backends/embedded-backend.d.ts +42 -0
  111. package/dist/src/tui/backends/embedded-backend.js +160 -0
  112. package/dist/src/tui/backends/embedded-backend.js.map +1 -0
  113. package/dist/src/tui/backends/gateway-sse-backend.d.ts +49 -0
  114. package/dist/src/tui/backends/gateway-sse-backend.js +226 -0
  115. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -0
  116. package/dist/src/tui/components/assistant-message.d.ts +6 -0
  117. package/dist/src/tui/components/assistant-message.js +19 -0
  118. package/dist/src/tui/components/assistant-message.js.map +1 -0
  119. package/dist/src/tui/components/chat-log.d.ts +19 -0
  120. package/dist/src/tui/components/chat-log.js +99 -0
  121. package/dist/src/tui/components/chat-log.js.map +1 -0
  122. package/dist/src/tui/components/custom-editor.d.ts +13 -0
  123. package/dist/src/tui/components/custom-editor.js +44 -0
  124. package/dist/src/tui/components/custom-editor.js.map +1 -0
  125. package/dist/src/tui/components/tool-execution.d.ts +16 -0
  126. package/dist/src/tui/components/tool-execution.js +76 -0
  127. package/dist/src/tui/components/tool-execution.js.map +1 -0
  128. package/dist/src/tui/components/user-message.d.ts +6 -0
  129. package/dist/src/tui/components/user-message.js +22 -0
  130. package/dist/src/tui/components/user-message.js.map +1 -0
  131. package/dist/src/tui/sse-consumer.d.ts +15 -0
  132. package/dist/src/tui/sse-consumer.js +75 -0
  133. package/dist/src/tui/sse-consumer.js.map +1 -0
  134. package/dist/src/tui/stream-assembler.d.ts +22 -0
  135. package/dist/src/tui/stream-assembler.js +63 -0
  136. package/dist/src/tui/stream-assembler.js.map +1 -0
  137. package/dist/src/tui/theme.d.ts +71 -0
  138. package/dist/src/tui/theme.js +151 -0
  139. package/dist/src/tui/theme.js.map +1 -0
  140. package/dist/src/tui/tui-backend.d.ts +84 -0
  141. package/dist/src/tui/tui-backend.js +1 -0
  142. package/dist/src/tui/tui-types.d.ts +85 -0
  143. package/dist/src/tui/tui-types.js +21 -0
  144. package/dist/src/tui/tui-types.js.map +1 -0
  145. package/dist/src/tui/tui.d.ts +3 -0
  146. package/dist/src/tui/tui.js +526 -0
  147. package/dist/src/tui/tui.js.map +1 -0
  148. package/package.json +9 -3
  149. package/dist/gateway/static/root/assets/index-DhSFfSNN.css +0 -1
  150. package/dist/gateway/static/root/assets/logs-page-DRI33XK4.js +0 -2
  151. package/dist/gateway/static/root/assets/logs-page-DRI33XK4.js.map +0 -1
  152. package/dist/gateway/static/root/assets/sessions-page-Cryg-36Z.js +0 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui-types.js","names":[],"sources":["../../../src/tui/tui-types.ts"],"sourcesContent":["/** TUI configuration options passed from CLI. */\nexport interface TuiOptions {\n /** Connect to a running gateway instead of embedded mode. */\n url?: string;\n /** Gateway bearer token. */\n token?: string;\n /** Session key to resume. */\n session?: string;\n /** Thinking level override. */\n thinking?: string;\n /** Single message to send on start, then stay open. */\n message?: string;\n /** Run in embedded (local) mode — no gateway required. */\n local?: boolean;\n}\n\nexport type TuiExitReason = 'exit' | 'signal';\n\nexport interface TuiResult {\n exitReason: TuiExitReason;\n}\n\n/** SSE events emitted by POST /api/agent. */\nexport interface AgentSSEStatusEvent {\n status: string;\n runId: string;\n}\n\nexport interface AgentSSETokenEvent {\n content: string;\n}\n\nexport interface AgentSSEThinkingEvent {\n content: string;\n isDelta?: boolean;\n}\n\nexport interface AgentSSEToolStartEvent {\n toolName: string;\n toolCallId: string;\n args?: unknown;\n}\n\nexport interface AgentSSEToolEndEvent {\n toolName: string;\n toolCallId: string;\n isError: boolean;\n result?: string;\n}\n\nexport interface AgentSSEErrorEvent {\n content: string;\n}\n\nexport interface AgentSSEResultEvent {\n ok: boolean;\n payload?: { status?: string; summary?: string };\n}\n\n/** Parsed SSE event from the stream. */\nexport interface ParsedSSEEvent {\n event: string;\n data: string;\n id?: string;\n}\n\n/** Activity status for the TUI status bar. */\nexport type ActivityStatus =\n | 'idle'\n | 'sending'\n | 'waiting'\n | 'streaming'\n | 'running';\n\n/** Session metadata shown in the TUI footer. */\nexport interface SessionInfo {\n model?: string;\n modelProvider?: string;\n thinkingLevel?: string;\n contextTokens?: number | null;\n totalTokens?: number | null;\n displayName?: string;\n}\n\n/** Mutable state bag for the TUI runtime. */\nexport interface TuiState {\n currentSessionKey: string;\n activeRunId: string | null;\n isConnected: boolean;\n activityStatus: ActivityStatus;\n connectionStatus: string;\n sessionInfo: SessionInfo;\n autoMessageSent: boolean;\n historyLoaded: boolean;\n toolsExpanded: boolean;\n showThinking: boolean;\n lastCtrlCAt: number;\n exitRequested: boolean;\n}\n\nexport function createInitialState(sessionKey: string): TuiState {\n return {\n currentSessionKey: sessionKey,\n activeRunId: null,\n isConnected: false,\n activityStatus: 'idle',\n connectionStatus: 'connecting',\n sessionInfo: {},\n autoMessageSent: false,\n historyLoaded: false,\n toolsExpanded: false,\n showThinking: false,\n lastCtrlCAt: 0,\n exitRequested: false,\n };\n}\n"],"mappings":";AAoGA,SAAgB,mBAAmB,YAA8B;AAC/D,QAAO;EACL,mBAAmB;EACnB,aAAa;EACb,aAAa;EACb,gBAAgB;EAChB,kBAAkB;EAClB,aAAa,EAAE;EACf,iBAAiB;EACjB,eAAe;EACf,eAAe;EACf,cAAc;EACd,aAAa;EACb,eAAe;EAChB"}
@@ -0,0 +1,3 @@
1
+ import { type TuiOptions, type TuiResult } from './tui-types.js';
2
+ export type { TuiOptions, TuiResult };
3
+ export declare function runTui(opts: TuiOptions): Promise<TuiResult>;
@@ -0,0 +1,526 @@
1
+ import { StreamAssembler } from "./stream-assembler.js";
2
+ import { editorTheme, theme } from "./theme.js";
3
+ import { createInitialState } from "./tui-types.js";
4
+ import { EmbeddedBackend } from "./backends/embedded-backend.js";
5
+ import { GatewaySseBackend } from "./backends/gateway-sse-backend.js";
6
+ import { ChatLog } from "./components/chat-log.js";
7
+ import { CustomEditor } from "./components/custom-editor.js";
8
+ import { CombinedAutocompleteProvider, Container, Loader, ProcessTerminal, TUI, Text } from "@mariozechner/pi-tui";
9
+ //#region src/tui/tui.ts
10
+ function getSlashCommands(_isLocal) {
11
+ return [
12
+ {
13
+ name: "help",
14
+ description: "Show available commands"
15
+ },
16
+ {
17
+ name: "abort",
18
+ description: "Abort active run (or press Escape)"
19
+ },
20
+ {
21
+ name: "tools",
22
+ description: "Toggle tool output expanded/collapsed (or Ctrl+O)"
23
+ },
24
+ {
25
+ name: "thinking",
26
+ description: "Toggle thinking display (or Ctrl+T)"
27
+ },
28
+ {
29
+ name: "exit",
30
+ description: "Exit the TUI"
31
+ },
32
+ {
33
+ name: "models",
34
+ description: "List available models"
35
+ },
36
+ {
37
+ name: "switch",
38
+ description: "Switch model (e.g. /switch openai/gpt-4o)"
39
+ },
40
+ {
41
+ name: "usage",
42
+ description: "Show token usage statistics"
43
+ },
44
+ {
45
+ name: "new",
46
+ description: "Start a new session"
47
+ },
48
+ {
49
+ name: "clear",
50
+ description: "Clear current session"
51
+ },
52
+ {
53
+ name: "list",
54
+ description: "List sessions"
55
+ },
56
+ {
57
+ name: "compact",
58
+ description: "Compact session history"
59
+ },
60
+ {
61
+ name: "think",
62
+ description: "Set thinking level (e.g. /think high)"
63
+ },
64
+ {
65
+ name: "reasoning",
66
+ description: "Set reasoning visibility (e.g. /reasoning stream)"
67
+ },
68
+ {
69
+ name: "verbose",
70
+ description: "Toggle verbose mode"
71
+ },
72
+ {
73
+ name: "status",
74
+ description: "Show agent status"
75
+ },
76
+ {
77
+ name: "config",
78
+ description: "Show or update configuration"
79
+ },
80
+ {
81
+ name: "context",
82
+ description: "Show context budget"
83
+ },
84
+ {
85
+ name: "btw",
86
+ description: "Side question without saving to session"
87
+ },
88
+ {
89
+ name: "export",
90
+ description: "Export session (markdown/html/json)"
91
+ },
92
+ {
93
+ name: "settings",
94
+ description: "Show current settings"
95
+ },
96
+ {
97
+ name: "start",
98
+ description: "Show welcome message"
99
+ }
100
+ ];
101
+ }
102
+ function helpText(isLocal) {
103
+ const commands = getSlashCommands(isLocal);
104
+ const lines = ["Available commands:"];
105
+ for (const c of commands) lines.push(` /${c.name} — ${c.description}`);
106
+ lines.push("", "Keyboard shortcuts:");
107
+ lines.push(" Escape — Abort active run");
108
+ lines.push(" Ctrl+O — Toggle tool output");
109
+ lines.push(" Ctrl+T — Toggle thinking display");
110
+ lines.push(" Ctrl+C — Clear input / exit");
111
+ lines.push(" Ctrl+D — Exit");
112
+ return lines.join("\n");
113
+ }
114
+ function resolveCtrlCAction(hasInput, now, lastCtrlCAt) {
115
+ if (hasInput) return {
116
+ action: "clear",
117
+ nextLastCtrlCAt: now
118
+ };
119
+ if (now - lastCtrlCAt <= 1e3) return {
120
+ action: "exit",
121
+ nextLastCtrlCAt: lastCtrlCAt
122
+ };
123
+ return {
124
+ action: "warn",
125
+ nextLastCtrlCAt: now
126
+ };
127
+ }
128
+ const pendingToolCallIds = /* @__PURE__ */ new Map();
129
+ function dispatchAgentSSE(event, data, state, chatLog, assembler, tui, setActivityStatus) {
130
+ const runId = state.activeRunId ?? "default";
131
+ switch (event) {
132
+ case "status":
133
+ state.activeRunId = typeof data.runId === "string" ? data.runId : runId;
134
+ setActivityStatus("waiting");
135
+ break;
136
+ case "token": {
137
+ const content = typeof data.content === "string" ? data.content : typeof data.delta === "string" ? data.delta : typeof data.text === "string" ? data.text : "";
138
+ if (!content) break;
139
+ setActivityStatus("streaming");
140
+ const display = assembler.ingestToken(runId, content, state.showThinking);
141
+ if (display !== null) {
142
+ chatLog.updateAssistant(display, runId);
143
+ tui.requestRender();
144
+ }
145
+ break;
146
+ }
147
+ case "thinking": {
148
+ const thinkContent = String(data.content ?? "");
149
+ const isDelta = Boolean(data.delta);
150
+ if (data.status === "started") break;
151
+ setActivityStatus("streaming");
152
+ const display = assembler.ingestThinking(runId, thinkContent, isDelta, state.showThinking);
153
+ if (display !== null) {
154
+ chatLog.updateAssistant(display, runId);
155
+ tui.requestRender();
156
+ }
157
+ break;
158
+ }
159
+ case "thinking_end":
160
+ case "message_end": break;
161
+ case "tool_start": {
162
+ const toolName = String(data.toolName ?? "unknown");
163
+ const toolCallId = String(data.toolCallId || crypto.randomUUID());
164
+ const stack = pendingToolCallIds.get(toolName) ?? [];
165
+ stack.push(toolCallId);
166
+ pendingToolCallIds.set(toolName, stack);
167
+ setActivityStatus("running");
168
+ chatLog.startTool(toolCallId, toolName, data.args);
169
+ tui.requestRender();
170
+ break;
171
+ }
172
+ case "tool_end": {
173
+ const toolName = String(data.toolName ?? "");
174
+ let toolCallId = typeof data.toolCallId === "string" && data.toolCallId ? data.toolCallId : "";
175
+ if (!toolCallId && toolName) {
176
+ const stack = pendingToolCallIds.get(toolName);
177
+ if (stack && stack.length > 0) {
178
+ toolCallId = stack.shift();
179
+ if (stack.length === 0) pendingToolCallIds.delete(toolName);
180
+ }
181
+ }
182
+ const resultText = String(data.result ?? "");
183
+ const isError = Boolean(data.isError);
184
+ if (toolCallId) chatLog.updateToolResult(toolCallId, resultText, isError);
185
+ setActivityStatus("streaming");
186
+ tui.requestRender();
187
+ break;
188
+ }
189
+ case "error": {
190
+ const errorContent = String(data.content ?? "Unknown error");
191
+ const finalText = assembler.finalize(runId, state.showThinking);
192
+ if (finalText) chatLog.finalizeAssistant(finalText, runId);
193
+ chatLog.addSystem(`❌ ${errorContent}`);
194
+ state.activeRunId = null;
195
+ setActivityStatus("idle");
196
+ tui.requestRender();
197
+ break;
198
+ }
199
+ case "result": {
200
+ const finalText = assembler.finalize(runId, state.showThinking);
201
+ if (finalText) chatLog.finalizeAssistant(finalText, runId);
202
+ state.activeRunId = null;
203
+ setActivityStatus("idle");
204
+ tui.requestRender();
205
+ break;
206
+ }
207
+ case "progress":
208
+ setActivityStatus("running");
209
+ break;
210
+ default: break;
211
+ }
212
+ }
213
+ async function runTui(opts) {
214
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
215
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
216
+ let suppressLogs = true;
217
+ const logFilter = (original) => {
218
+ return function filteredWrite(chunk, ...rest) {
219
+ if (!suppressLogs) return original(chunk, ...rest);
220
+ if ((typeof chunk === "string" ? chunk : chunk instanceof Buffer ? chunk.toString() : "").startsWith("{\"level\":")) return true;
221
+ return original(chunk, ...rest);
222
+ };
223
+ };
224
+ process.stdout.write = logFilter(originalStdoutWrite);
225
+ process.stderr.write = logFilter(originalStderrWrite);
226
+ const restoreStdio = () => {
227
+ suppressLogs = false;
228
+ process.stdout.write = originalStdoutWrite;
229
+ process.stderr.write = originalStderrWrite;
230
+ };
231
+ const isLocalMode = opts.local === true;
232
+ const state = createInitialState(opts.session ?? "cli:tui");
233
+ const assembler = new StreamAssembler();
234
+ const client = isLocalMode ? new EmbeddedBackend() : new GatewaySseBackend({
235
+ url: opts.url ?? "http://localhost:3120",
236
+ token: opts.token
237
+ });
238
+ const tui = new TUI(new ProcessTerminal());
239
+ const header = new Text("", 1, 0);
240
+ const statusContainer = new Container();
241
+ const footer = new Text("", 1, 0);
242
+ const chatLog = new ChatLog();
243
+ const editor = new CustomEditor(tui, editorTheme);
244
+ const root = new Container();
245
+ root.addChild(header);
246
+ root.addChild(chatLog);
247
+ root.addChild(statusContainer);
248
+ root.addChild(footer);
249
+ root.addChild(editor);
250
+ tui.addChild(root);
251
+ tui.setFocus(editor);
252
+ const slashCommands = getSlashCommands(isLocalMode);
253
+ editor.setAutocompleteProvider(new CombinedAutocompleteProvider(slashCommands.map((c) => ({
254
+ name: c.name,
255
+ description: c.description
256
+ })), process.cwd()));
257
+ let statusText = null;
258
+ let statusLoader = null;
259
+ let statusStartedAt = null;
260
+ let lastActivityStatus = "";
261
+ let elapsedTimerId = null;
262
+ const busyStates = new Set([
263
+ "sending",
264
+ "waiting",
265
+ "streaming",
266
+ "running"
267
+ ]);
268
+ const formatElapsed = (startMs) => {
269
+ const totalSeconds = Math.max(0, Math.floor((Date.now() - startMs) / 1e3));
270
+ if (totalSeconds < 60) return `${totalSeconds}s`;
271
+ return `${Math.floor(totalSeconds / 60)}m ${totalSeconds % 60}s`;
272
+ };
273
+ const renderStatus = () => {
274
+ if (busyStates.has(state.activityStatus)) {
275
+ if (!statusStartedAt || lastActivityStatus !== state.activityStatus) statusStartedAt = Date.now();
276
+ if (!statusLoader) {
277
+ statusContainer.clear();
278
+ statusText = null;
279
+ statusLoader = new Loader(tui, (spinner) => theme.accent(spinner), (text) => theme.bold(theme.accentSoft(text)), "");
280
+ statusContainer.addChild(statusLoader);
281
+ }
282
+ const elapsed = formatElapsed(statusStartedAt);
283
+ statusLoader.setMessage(`${state.activityStatus} • ${elapsed} | ${state.connectionStatus}`);
284
+ if (!elapsedTimerId) elapsedTimerId = setInterval(() => {
285
+ if (statusStartedAt && statusLoader) {
286
+ const el = formatElapsed(statusStartedAt);
287
+ statusLoader.setMessage(`${state.activityStatus} • ${el} | ${state.connectionStatus}`);
288
+ }
289
+ }, 1e3);
290
+ } else {
291
+ statusStartedAt = null;
292
+ if (elapsedTimerId) {
293
+ clearInterval(elapsedTimerId);
294
+ elapsedTimerId = null;
295
+ }
296
+ statusLoader?.stop();
297
+ statusLoader = null;
298
+ if (!statusText) {
299
+ statusContainer.clear();
300
+ statusText = new Text("", 1, 0);
301
+ statusContainer.addChild(statusText);
302
+ }
303
+ const text = state.activityStatus ? `${state.connectionStatus} | ${state.activityStatus}` : state.connectionStatus;
304
+ statusText.setText(theme.dim(text));
305
+ }
306
+ lastActivityStatus = state.activityStatus;
307
+ };
308
+ const setActivityStatus = (status) => {
309
+ state.activityStatus = status;
310
+ renderStatus();
311
+ };
312
+ const setConnectionStatus = (text) => {
313
+ state.connectionStatus = text;
314
+ renderStatus();
315
+ };
316
+ const updateHeader = () => {
317
+ header.setText(theme.header(`xopc tui — ${client.connectionLabel} — session ${state.currentSessionKey}`));
318
+ };
319
+ const updateFooter = () => {
320
+ const modelLabel = state.sessionInfo.model ? state.sessionInfo.modelProvider ? `${state.sessionInfo.modelProvider}/${state.sessionInfo.model}` : state.sessionInfo.model : "unknown";
321
+ const tokens = state.sessionInfo.totalTokens != null ? `${state.sessionInfo.totalTokens} tokens` : "";
322
+ const thinking = state.showThinking ? "thinking:on" : "";
323
+ const parts = [
324
+ `session ${state.currentSessionKey}`,
325
+ modelLabel,
326
+ thinking,
327
+ tokens
328
+ ].filter(Boolean);
329
+ footer.setText(theme.dim(parts.join(" | ")));
330
+ };
331
+ const refreshSessionInfo = async () => {
332
+ try {
333
+ state.sessionInfo = await client.getSessionInfo(state.currentSessionKey);
334
+ updateFooter();
335
+ tui.requestRender();
336
+ } catch {}
337
+ };
338
+ const sendMessage = (text) => {
339
+ if (state.activeRunId) {
340
+ chatLog.addSystem("A response is still in progress. Use /abort or press Escape to cancel.");
341
+ tui.requestRender();
342
+ return;
343
+ }
344
+ chatLog.addUser(text);
345
+ setActivityStatus("sending");
346
+ tui.requestRender();
347
+ client.sendChat({
348
+ sessionKey: state.currentSessionKey,
349
+ message: text,
350
+ thinking: opts.thinking
351
+ }).catch((error) => {
352
+ const errorMessage = error instanceof Error ? error.message : String(error);
353
+ chatLog.addSystem(`❌ Failed to send: ${errorMessage}`);
354
+ setActivityStatus("idle");
355
+ tui.requestRender();
356
+ });
357
+ };
358
+ const abortActive = async () => {
359
+ if (!state.activeRunId) return;
360
+ const runId = state.activeRunId;
361
+ state.activeRunId = null;
362
+ assembler.drop(runId);
363
+ chatLog.dropAssistant(runId);
364
+ setActivityStatus("idle");
365
+ tui.requestRender();
366
+ await client.abortChat({
367
+ sessionKey: state.currentSessionKey,
368
+ runId
369
+ }).catch(() => {});
370
+ };
371
+ const handleCommand = (input) => {
372
+ const [commandName] = input.replace(/^\//, "").trim().split(/\s+/);
373
+ const normalizedCommand = (commandName ?? "").toLowerCase();
374
+ switch (normalizedCommand) {
375
+ case "help":
376
+ chatLog.addSystem(helpText(isLocalMode));
377
+ tui.requestRender();
378
+ return;
379
+ case "exit":
380
+ case "quit":
381
+ requestExit();
382
+ return;
383
+ case "abort":
384
+ case "stop":
385
+ case "cancel":
386
+ abortActive().then(() => {
387
+ chatLog.addSystem("Aborted.");
388
+ tui.requestRender();
389
+ });
390
+ return;
391
+ case "tools":
392
+ state.toolsExpanded = !state.toolsExpanded;
393
+ chatLog.setToolsExpanded(state.toolsExpanded);
394
+ chatLog.addSystem(`Tools: ${state.toolsExpanded ? "expanded" : "collapsed"}`);
395
+ tui.requestRender();
396
+ return;
397
+ case "thinking":
398
+ state.showThinking = !state.showThinking;
399
+ chatLog.addSystem(`Thinking display: ${state.showThinking ? "on" : "off"}`);
400
+ updateFooter();
401
+ tui.requestRender();
402
+ return;
403
+ default: break;
404
+ }
405
+ switch (normalizedCommand) {
406
+ case "new":
407
+ case "reset":
408
+ case "restart":
409
+ case "clear":
410
+ abortActive().then(() => {
411
+ assembler.clear();
412
+ chatLog.clearAll();
413
+ tui.requestRender();
414
+ sendMessage(input);
415
+ });
416
+ return;
417
+ default: break;
418
+ }
419
+ sendMessage(input);
420
+ };
421
+ editor.onSubmit = (text) => {
422
+ const value = text.trim();
423
+ editor.setText("");
424
+ if (!value) return;
425
+ editor.addToHistory(value);
426
+ if (value.startsWith("/")) handleCommand(value);
427
+ else sendMessage(value);
428
+ };
429
+ editor.onEscape = () => void abortActive();
430
+ editor.onCtrlD = () => requestExit();
431
+ editor.onCtrlO = () => {
432
+ state.toolsExpanded = !state.toolsExpanded;
433
+ chatLog.setToolsExpanded(state.toolsExpanded);
434
+ setActivityStatus(state.toolsExpanded ? "tools expanded" : "tools collapsed");
435
+ tui.requestRender();
436
+ };
437
+ editor.onCtrlT = () => {
438
+ state.showThinking = !state.showThinking;
439
+ updateFooter();
440
+ tui.requestRender();
441
+ };
442
+ const handleCtrlC = () => {
443
+ const now = Date.now();
444
+ const decision = resolveCtrlCAction(editor.getText().trim().length > 0, now, state.lastCtrlCAt);
445
+ state.lastCtrlCAt = decision.nextLastCtrlCAt;
446
+ if (decision.action === "clear") {
447
+ editor.setText("");
448
+ setActivityStatus("cleared input; press ctrl+c again to exit");
449
+ tui.requestRender();
450
+ } else if (decision.action === "exit") requestExit();
451
+ else {
452
+ setActivityStatus("press ctrl+c again to exit");
453
+ tui.requestRender();
454
+ }
455
+ };
456
+ editor.onCtrlC = handleCtrlC;
457
+ let finishTui = null;
458
+ let exitResult = { exitReason: "exit" };
459
+ const requestExit = () => {
460
+ if (state.exitRequested) return;
461
+ state.exitRequested = true;
462
+ if (elapsedTimerId) {
463
+ clearInterval(elapsedTimerId);
464
+ elapsedTimerId = null;
465
+ }
466
+ client.stop();
467
+ try {
468
+ tui.stop();
469
+ } catch {}
470
+ restoreStdio();
471
+ finishTui?.();
472
+ };
473
+ client.onEvent = (evt) => {
474
+ const data = evt.data ?? {};
475
+ dispatchAgentSSE(evt.event, data, state, chatLog, assembler, tui, setActivityStatus);
476
+ };
477
+ client.onConnected = () => {
478
+ state.isConnected = true;
479
+ setConnectionStatus(isLocalMode ? "local ready" : "gateway connected");
480
+ (async () => {
481
+ await refreshSessionInfo();
482
+ updateHeader();
483
+ updateFooter();
484
+ tui.requestRender();
485
+ if (!state.autoMessageSent && opts.message) {
486
+ state.autoMessageSent = true;
487
+ sendMessage(opts.message);
488
+ }
489
+ })();
490
+ };
491
+ client.onDisconnected = (reason) => {
492
+ const wasConnected = state.isConnected;
493
+ state.isConnected = false;
494
+ if (isLocalMode) setConnectionStatus(`local stopped: ${reason}`);
495
+ else {
496
+ setConnectionStatus(`disconnected: ${reason}`);
497
+ if (!wasConnected && !state.historyLoaded) {
498
+ const gatewayUrl = opts.url ?? "http://localhost:3120";
499
+ chatLog.addSystem(`Cannot reach gateway at ${gatewayUrl}.\nMake sure the gateway is running (xopc gateway), or use --local for embedded mode.`);
500
+ }
501
+ }
502
+ tui.requestRender();
503
+ };
504
+ const sigintHandler = () => handleCtrlC();
505
+ const sigtermHandler = () => requestExit();
506
+ process.on("SIGINT", sigintHandler);
507
+ process.on("SIGTERM", sigtermHandler);
508
+ updateHeader();
509
+ setConnectionStatus(isLocalMode ? "starting local runtime" : "connecting");
510
+ updateFooter();
511
+ tui.start();
512
+ client.start();
513
+ await new Promise((resolve) => {
514
+ finishTui = () => {
515
+ process.removeListener("SIGINT", sigintHandler);
516
+ process.removeListener("SIGTERM", sigtermHandler);
517
+ finishTui = null;
518
+ resolve();
519
+ };
520
+ });
521
+ return exitResult;
522
+ }
523
+ //#endregion
524
+ export { runTui };
525
+
526
+ //# sourceMappingURL=tui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tui.js","names":[],"sources":["../../../src/tui/tui.ts"],"sourcesContent":["import {\n CombinedAutocompleteProvider,\n Container,\n Loader,\n ProcessTerminal,\n Text,\n TUI,\n} from '@mariozechner/pi-tui';\n\nimport type { TuiBackend, TuiEvent } from './tui-backend.js';\nimport { EmbeddedBackend } from './backends/embedded-backend.js';\nimport { GatewaySseBackend } from './backends/gateway-sse-backend.js';\nimport { ChatLog } from './components/chat-log.js';\nimport { CustomEditor } from './components/custom-editor.js';\nimport { StreamAssembler } from './stream-assembler.js';\nimport { editorTheme, theme } from './theme.js';\nimport { createInitialState, type TuiOptions, type TuiResult, type TuiState } from './tui-types.js';\n\nexport type { TuiOptions, TuiResult };\n\n// ── Slash commands ──\n\ninterface SlashCommandDef {\n name: string;\n description: string;\n}\n\nfunction getSlashCommands(_isLocal: boolean): SlashCommandDef[] {\n return [\n // TUI-local commands\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 // Backend-delegated commands (handled by chat-command system)\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\nfunction helpText(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+O — Toggle tool output');\n lines.push(' Ctrl+T — Toggle thinking display');\n lines.push(' Ctrl+C — Clear input / exit');\n lines.push(' Ctrl+D — Exit');\n return lines.join('\\n');\n}\n\n// ── Ctrl+C handling ──\n\ntype CtrlCAction = 'clear' | 'warn' | 'exit';\n\nfunction resolveCtrlCAction(hasInput: boolean, now: number, lastCtrlCAt: number): { action: CtrlCAction; nextLastCtrlCAt: number } {\n if (hasInput) return { action: 'clear', nextLastCtrlCAt: now };\n if (now - lastCtrlCAt <= 1000) return { action: 'exit', nextLastCtrlCAt: lastCtrlCAt };\n return { action: 'warn', nextLastCtrlCAt: now };\n}\n\n// ── SSE event dispatch ──\n\n// Track tool_start → tool_end matching when backend omits toolCallId.\n// Uses a per-toolName stack so concurrent tools of the same name still pair correctly.\nconst pendingToolCallIds = new Map<string, string[]>();\n\nfunction 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): void {\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 // Push onto the pending stack so tool_end can find this id by toolName\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);\n tui.requestRender();\n break;\n }\n case 'tool_end': {\n const toolName = String(data.toolName ?? '');\n // Resolve toolCallId: prefer explicit value, then pop from pending stack by 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\n// ── Main entry ──\n\nexport async function runTui(opts: TuiOptions): Promise<TuiResult> {\n // Suppress pino JSON logs from polluting the terminal while pi-tui owns the screen.\n // We intercept stdout/stderr writes and swallow anything that looks like a JSON log line.\n const originalStdoutWrite = process.stdout.write.bind(process.stdout);\n const originalStderrWrite = process.stderr.write.bind(process.stderr);\n let suppressLogs = true;\n\n const logFilter = (\n original: typeof process.stdout.write,\n ): typeof process.stdout.write => {\n return function filteredWrite(chunk: unknown, ...rest: unknown[]): boolean {\n if (!suppressLogs) return (original as Function)(chunk, ...rest) as boolean;\n const text = typeof chunk === 'string' ? chunk : chunk instanceof Buffer ? chunk.toString() : '';\n if (text.startsWith('{\"level\":')) return true; // swallow JSON log lines\n return (original as Function)(chunk, ...rest) as boolean;\n } as typeof process.stdout.write;\n };\n\n process.stdout.write = logFilter(originalStdoutWrite);\n process.stderr.write = logFilter(originalStderrWrite);\n\n const restoreStdio = () => {\n suppressLogs = false;\n process.stdout.write = originalStdoutWrite;\n process.stderr.write = originalStderrWrite;\n };\n\n const isLocalMode = opts.local === true;\n const sessionKey = opts.session ?? 'cli:tui';\n const state = createInitialState(sessionKey);\n const assembler = new StreamAssembler();\n\n // Create backend\n const client: TuiBackend = isLocalMode\n ? new EmbeddedBackend()\n : new GatewaySseBackend({ url: opts.url ?? 'http://localhost:3120', token: opts.token });\n\n // Build UI tree\n const tui = new TUI(new ProcessTerminal());\n const header = new Text('', 1, 0);\n const statusContainer = new Container();\n const footer = new Text('', 1, 0);\n const chatLog = new ChatLog();\n const editor = new CustomEditor(tui, editorTheme);\n const root = new Container();\n root.addChild(header);\n root.addChild(chatLog);\n root.addChild(statusContainer);\n root.addChild(footer);\n root.addChild(editor);\n tui.addChild(root);\n tui.setFocus(editor);\n\n // Slash command autocomplete\n const slashCommands = getSlashCommands(isLocalMode);\n editor.setAutocompleteProvider(\n new CombinedAutocompleteProvider(\n slashCommands.map((c) => ({ name: c.name, description: c.description })),\n process.cwd(),\n ),\n );\n\n // Status management\n let statusText: Text | null = null;\n let statusLoader: Loader | null = null;\n let statusStartedAt: number | null = null;\n let lastActivityStatus = '';\n let elapsedTimerId: ReturnType<typeof setInterval> | null = null;\n const busyStates = new Set(['sending', 'waiting', 'streaming', 'running']);\n\n const formatElapsed = (startMs: number) => {\n const totalSeconds = Math.max(0, Math.floor((Date.now() - startMs) / 1000));\n if (totalSeconds < 60) return `${totalSeconds}s`;\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n return `${minutes}m ${seconds}s`;\n };\n\n const renderStatus = () => {\n const isBusy = busyStates.has(state.activityStatus);\n if (isBusy) {\n if (!statusStartedAt || lastActivityStatus !== state.activityStatus) {\n statusStartedAt = Date.now();\n }\n // Show loader\n if (!statusLoader) {\n statusContainer.clear();\n statusText = null;\n statusLoader = new Loader(\n tui,\n (spinner) => theme.accent(spinner),\n (text) => theme.bold(theme.accentSoft(text)),\n '',\n );\n statusContainer.addChild(statusLoader);\n }\n const elapsed = formatElapsed(statusStartedAt);\n statusLoader.setMessage(`${state.activityStatus} • ${elapsed} | ${state.connectionStatus}`);\n // Tick every second to update elapsed time display\n if (!elapsedTimerId) {\n elapsedTimerId = setInterval(() => {\n if (statusStartedAt && statusLoader) {\n const el = formatElapsed(statusStartedAt);\n statusLoader.setMessage(`${state.activityStatus} • ${el} | ${state.connectionStatus}`);\n }\n }, 1000);\n }\n } else {\n statusStartedAt = null;\n if (elapsedTimerId) {\n clearInterval(elapsedTimerId);\n elapsedTimerId = null;\n }\n statusLoader?.stop();\n statusLoader = null;\n if (!statusText) {\n statusContainer.clear();\n statusText = new Text('', 1, 0);\n statusContainer.addChild(statusText);\n }\n const text = state.activityStatus\n ? `${state.connectionStatus} | ${state.activityStatus}`\n : state.connectionStatus;\n statusText.setText(theme.dim(text));\n }\n lastActivityStatus = state.activityStatus;\n };\n\n const setActivityStatus = (status: string) => {\n state.activityStatus = status as TuiState['activityStatus'];\n renderStatus();\n };\n\n const setConnectionStatus = (text: string) => {\n state.connectionStatus = text;\n renderStatus();\n };\n\n const updateHeader = () => {\n const title = 'xopc tui';\n header.setText(\n theme.header(`${title} — ${client.connectionLabel} — session ${state.currentSessionKey}`),\n );\n };\n\n const updateFooter = () => {\n const modelLabel = state.sessionInfo.model\n ? state.sessionInfo.modelProvider\n ? `${state.sessionInfo.modelProvider}/${state.sessionInfo.model}`\n : state.sessionInfo.model\n : 'unknown';\n const tokens = state.sessionInfo.totalTokens != null\n ? `${state.sessionInfo.totalTokens} tokens`\n : '';\n const thinking = state.showThinking ? 'thinking:on' : '';\n const parts = [\n `session ${state.currentSessionKey}`,\n modelLabel,\n thinking,\n tokens,\n ].filter(Boolean);\n footer.setText(theme.dim(parts.join(' | ')));\n };\n\n // Load session info from backend\n const refreshSessionInfo = async () => {\n try {\n state.sessionInfo = await client.getSessionInfo(state.currentSessionKey);\n updateFooter();\n tui.requestRender();\n } catch {\n // Ignore errors silently\n }\n };\n\n // Send message (fire-and-forget so the TUI event loop stays responsive)\n const sendMessage = (text: string) => {\n if (state.activeRunId) {\n chatLog.addSystem('A response is still in progress. Use /abort or press Escape to cancel.');\n tui.requestRender();\n return;\n }\n\n chatLog.addUser(text);\n setActivityStatus('sending');\n tui.requestRender();\n\n // Run in background — events arrive via client.onEvent callback\n void client\n .sendChat({\n sessionKey: state.currentSessionKey,\n message: text,\n thinking: opts.thinking,\n })\n .catch((error: unknown) => {\n const errorMessage = error instanceof Error ? error.message : String(error);\n chatLog.addSystem(`❌ Failed to send: ${errorMessage}`);\n setActivityStatus('idle');\n tui.requestRender();\n });\n };\n\n // Abort active run\n const abortActive = async () => {\n if (!state.activeRunId) return;\n const runId = state.activeRunId;\n state.activeRunId = null;\n assembler.drop(runId);\n chatLog.dropAssistant(runId);\n setActivityStatus('idle');\n tui.requestRender();\n await client.abortChat({ sessionKey: state.currentSessionKey, runId }).catch(() => {});\n };\n\n // Handle slash commands.\n // TUI-local commands are processed here; everything else is delegated to the\n // backend's chat-command system via sendMessage (processDirectStreaming handles\n // the commandRegistry lookup and returns results as SSE token events).\n const handleCommand = (input: string) => {\n const trimmed = input.replace(/^\\//, '').trim();\n const [commandName] = trimmed.split(/\\s+/);\n const normalizedCommand = (commandName ?? '').toLowerCase();\n\n // TUI-local commands (never sent to backend)\n switch (normalizedCommand) {\n case 'help':\n chatLog.addSystem(helpText(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 // Commands that need TUI-side state cleanup before delegating to backend\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 // Delegate to backend so session store is actually cleared/reset\n sendMessage(input);\n });\n return;\n }\n default:\n break;\n }\n\n // Everything else is delegated to the backend chat-command system — send the\n // raw slash command so processDirectStreaming's commandRegistry handles it.\n sendMessage(input);\n };\n\n // Editor submit\n editor.onSubmit = (text: string) => {\n const value = text.trim();\n editor.setText('');\n if (!value) return;\n editor.addToHistory(value);\n if (value.startsWith('/')) {\n void handleCommand(value);\n } else {\n void sendMessage(value);\n }\n };\n\n // Keyboard shortcuts\n editor.onEscape = () => void abortActive();\n editor.onCtrlD = () => requestExit();\n editor.onCtrlO = () => {\n state.toolsExpanded = !state.toolsExpanded;\n chatLog.setToolsExpanded(state.toolsExpanded);\n setActivityStatus(state.toolsExpanded ? 'tools expanded' : 'tools collapsed');\n tui.requestRender();\n };\n editor.onCtrlT = () => {\n state.showThinking = !state.showThinking;\n updateFooter();\n tui.requestRender();\n };\n\n const handleCtrlC = () => {\n const now = Date.now();\n const decision = resolveCtrlCAction(\n editor.getText().trim().length > 0,\n now,\n state.lastCtrlCAt,\n );\n state.lastCtrlCAt = decision.nextLastCtrlCAt;\n if (decision.action === 'clear') {\n editor.setText('');\n setActivityStatus('cleared input; press ctrl+c again to exit');\n tui.requestRender();\n } else if (decision.action === 'exit') {\n requestExit();\n } else {\n setActivityStatus('press ctrl+c again to exit');\n tui.requestRender();\n }\n };\n editor.onCtrlC = handleCtrlC;\n\n // Exit\n let finishTui: (() => void) | null = null;\n let exitResult: TuiResult = { exitReason: 'exit' };\n\n const requestExit = () => {\n if (state.exitRequested) return;\n state.exitRequested = true;\n if (elapsedTimerId) {\n clearInterval(elapsedTimerId);\n elapsedTimerId = null;\n }\n client.stop();\n try {\n tui.stop();\n } catch {\n // Ignore terminal cleanup errors\n }\n restoreStdio();\n finishTui?.();\n };\n\n // Wire backend events\n client.onEvent = (evt: TuiEvent) => {\n const data = (evt.data ?? {}) as Record<string, unknown>;\n dispatchAgentSSE(evt.event, data, state, chatLog, assembler, tui, setActivityStatus);\n };\n\n client.onConnected = () => {\n state.isConnected = true;\n setConnectionStatus(isLocalMode ? 'local ready' : 'gateway connected');\n void (async () => {\n await refreshSessionInfo();\n updateHeader();\n updateFooter();\n tui.requestRender();\n // Auto-send message if provided\n if (!state.autoMessageSent && opts.message) {\n state.autoMessageSent = true;\n sendMessage(opts.message);\n }\n })();\n };\n\n client.onDisconnected = (reason: string) => {\n const wasConnected = state.isConnected;\n state.isConnected = false;\n if (isLocalMode) {\n setConnectionStatus(`local stopped: ${reason}`);\n } else {\n setConnectionStatus(`disconnected: ${reason}`);\n if (!wasConnected && !state.historyLoaded) {\n const gatewayUrl = opts.url ?? 'http://localhost:3120';\n chatLog.addSystem(\n `Cannot reach gateway at ${gatewayUrl}.\\n` +\n 'Make sure the gateway is running (xopc gateway), or use --local for embedded mode.',\n );\n }\n }\n tui.requestRender();\n };\n\n // Signal handlers\n const sigintHandler = () => handleCtrlC();\n const sigtermHandler = () => requestExit();\n process.on('SIGINT', sigintHandler);\n process.on('SIGTERM', sigtermHandler);\n\n // Boot\n updateHeader();\n setConnectionStatus(isLocalMode ? 'starting local runtime' : 'connecting');\n updateFooter();\n tui.start();\n client.start();\n\n // Wait for exit\n await new Promise<void>((resolve) => {\n finishTui = () => {\n process.removeListener('SIGINT', sigintHandler);\n process.removeListener('SIGTERM', sigtermHandler);\n finishTui = null;\n resolve();\n };\n });\n\n return exitResult;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "Personal AI assistant: CLI, gateway (HTTP/WebSocket + React console), Telegram and WeChat (Weixin) channels — TypeScript, 20+ LLM providers via pi-ai, extensions and skills.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -54,17 +54,19 @@
54
54
  "node": ">=22.0.0"
55
55
  },
56
56
  "dependencies": {
57
- "@larksuiteoapi/node-sdk": "^1.39.0",
58
57
  "@grammyjs/runner": "^2.0.3",
59
58
  "@grammyjs/types": "^3.26.0",
60
59
  "@hono/node-server": "^2.0.0",
61
60
  "@inquirer/prompts": "^8.4.2",
61
+ "@larksuiteoapi/node-sdk": "^1.39.0",
62
62
  "@mariozechner/pi-agent-core": "^0.70.2",
63
63
  "@mariozechner/pi-ai": "^0.70.2",
64
+ "@mariozechner/pi-tui": "^0.70.6",
64
65
  "@mozilla/readability": "^0.6.0",
65
66
  "@sinclair/typebox": "^0.34.49",
66
67
  "@vscode/ripgrep": "^1.17.1",
67
68
  "adm-zip": "^0.5.17",
69
+ "chalk": "^5.6.2",
68
70
  "chromium-bidi": "15.0.0",
69
71
  "cli-table3": "^0.6.5",
70
72
  "commander": "^14.0.3",
@@ -127,6 +129,10 @@
127
129
  "typecheck": "tsc --noEmit -p tsconfig.json",
128
130
  "build:web": "pnpm -C web run build",
129
131
  "dev:web": "pnpm -C web run dev",
132
+ "dev:mobile": "pnpm -C mobile run start",
133
+ "mobile:lint": "pnpm -C mobile run lint",
134
+ "mobile:typecheck": "pnpm -C mobile run typecheck",
135
+ "test:gateway-sse-client": "vitest run packages/gateway-sse-client",
130
136
  "dev": "tsx src/cli/index.ts",
131
137
  "start": "node dist/src/cli/index.js",
132
138
  "test": "vitest run src extensions/telegram/src extensions/feishu/src",
@@ -136,7 +142,7 @@
136
142
  "skills:test": "tsx src/cli/index.ts skills test",
137
143
  "skills:validate": "tsx src/cli/index.ts skills test validate",
138
144
  "skills:security": "tsx src/cli/index.ts skills test security",
139
- "lint": "eslint",
145
+ "lint": "eslint && pnpm -C web run lint && pnpm -C mobile run lint",
140
146
  "docs:dev": "vitepress dev docs",
141
147
  "docs:build": "vitepress build docs",
142
148
  "docs:preview": "vitepress preview docs",