@xopcai/xopc 0.0.28 → 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.
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/agents-CkgFSiCY.js +216 -0
- package/dist/gateway/static/root/assets/agents-CkgFSiCY.js.map +1 -0
- package/dist/gateway/static/root/assets/{apps-page-Co95hLOJ.js → apps-page-Bmq19MS-.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-Co95hLOJ.js.map → apps-page-Bmq19MS-.js.map} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js +9 -0
- package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js.map +1 -0
- package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js +2 -0
- package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js.map +1 -0
- package/dist/gateway/static/root/assets/{cron-utils-BmzF4m1y.js → cron-utils-N1PqD2DB.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-utils-BmzF4m1y.js.map → cron-utils-N1PqD2DB.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-Dn-ufXyc.js → dist--p2HQ2QF.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-Dn-ufXyc.js.map → dist--p2HQ2QF.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-BZ8xQ74_.js → extension-debug-page-DwHCB_6T.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-BZ8xQ74_.js.map → extension-debug-page-DwHCB_6T.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-BlNgKxwW.js → extension-page-BsYwQIex.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-BlNgKxwW.js.map → extension-page-BsYwQIex.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CWTdW_oY.js → extension-settings-page-nsisEgjB.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-CWTdW_oY.js.map → extension-settings-page-nsisEgjB.js.map} +1 -1
- package/dist/gateway/static/root/assets/index-CR8zUHGR.js +4734 -0
- package/dist/gateway/static/root/assets/{index-lV8FGWlt.js.map → index-CR8zUHGR.js.map} +1 -1
- package/dist/gateway/static/root/assets/index-Dnfha4O2.css +1 -0
- package/dist/gateway/static/root/assets/logs-page-CQwdV_Xw.js +2 -0
- package/dist/gateway/static/root/assets/{logs-page-DG31RpvG.js.map → logs-page-CQwdV_Xw.js.map} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js +2 -0
- package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js.map +1 -0
- package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js +2 -0
- package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js.map +1 -0
- package/dist/gateway/static/root/assets/skills-page-Clg8deH0.js +3 -0
- package/dist/gateway/static/root/assets/{skills-page-lb7vYtlP.js.map → skills-page-Clg8deH0.js.map} +1 -1
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/lifecycle/hook-handler.d.ts +2 -0
- package/dist/src/agent/lifecycle/hook-handler.js +24 -0
- package/dist/src/agent/lifecycle/hook-handler.js.map +1 -1
- package/dist/src/agent/messaging/command-handler.js +10 -2
- package/dist/src/agent/messaging/command-handler.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +77 -20
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.d.ts +15 -0
- package/dist/src/agent/service.js +21 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/channels/index.js +2 -2
- package/dist/src/channels/manager.js +2 -2
- package/dist/src/cli/agent-chat-log-level-preset.d.ts +3 -2
- package/dist/src/cli/agent-chat-log-level-preset.js +6 -3
- package/dist/src/cli/agent-chat-log-level-preset.js.map +1 -1
- package/dist/src/cli/index.js +4 -3
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/config/schema.js +5 -2
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/extensions/hooks.js +5 -1
- package/dist/src/extensions/hooks.js.map +1 -1
- package/dist/src/extensions/loader.d.ts +1 -0
- package/dist/src/extensions/loader.js +3 -1
- package/dist/src/extensions/loader.js.map +1 -1
- package/dist/src/extensions/sdk/index.d.ts +1 -1
- package/dist/src/extensions/sdk/index.js.map +1 -1
- package/dist/src/extensions/types/core.d.ts +8 -0
- package/dist/src/extensions/types/hooks.d.ts +16 -1
- package/dist/src/extensions/types/hooks.js +1 -0
- package/dist/src/extensions/types/hooks.js.map +1 -1
- package/dist/src/gateway/agents-admin.d.ts +19 -1
- package/dist/src/gateway/agents-admin.js +164 -3
- package/dist/src/gateway/agents-admin.js.map +1 -1
- package/dist/src/gateway/hono/app.js +1 -0
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/routes/agents.js +59 -5
- package/dist/src/gateway/hono/routes/agents.js.map +1 -1
- package/dist/src/gateway/hono/routes/config.js +2 -2
- package/dist/src/gateway/hono/routes/config.js.map +1 -1
- package/dist/src/gateway/hono/routes/public-gateway.js +1 -0
- package/dist/src/gateway/hono/routes/public-gateway.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +17 -0
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/service.d.ts +2 -0
- package/dist/src/gateway/service.js +31 -4
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/session/client-history.d.ts +21 -0
- package/dist/src/session/client-history.js +89 -0
- package/dist/src/session/client-history.js.map +1 -0
- package/dist/src/session/index.d.ts +1 -0
- package/dist/src/session/index.js +2 -1
- package/dist/src/session/manager.d.ts +2 -0
- package/dist/src/session/manager.js +5 -0
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/thinking-resolve.js +1 -1
- package/dist/src/session/thinking-resolve.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.d.ts +1 -1
- package/dist/src/tui/backends/embedded-backend.js +15 -2
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.d.ts +4 -0
- package/dist/src/tui/backends/gateway-sse-backend.js +34 -4
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/chat-history.d.ts +4 -0
- package/dist/src/tui/chat-history.js +29 -0
- package/dist/src/tui/chat-history.js.map +1 -0
- package/dist/src/tui/components/chat-log.d.ts +3 -1
- package/dist/src/tui/components/chat-log.js +17 -3
- package/dist/src/tui/components/chat-log.js.map +1 -1
- package/dist/src/tui/components/custom-editor.d.ts +1 -0
- package/dist/src/tui/components/custom-editor.js +8 -2
- package/dist/src/tui/components/custom-editor.js.map +1 -1
- package/dist/src/tui/components/fuzzy-filter.d.ts +17 -0
- package/dist/src/tui/components/fuzzy-filter.js +85 -0
- package/dist/src/tui/components/fuzzy-filter.js.map +1 -0
- package/dist/src/tui/components/searchable-select-list.d.ts +39 -0
- package/dist/src/tui/components/searchable-select-list.js +257 -0
- package/dist/src/tui/components/searchable-select-list.js.map +1 -0
- package/dist/src/tui/theme.d.ts +2 -0
- package/dist/src/tui/theme.js +7 -1
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/tui-agent-events.d.ts +7 -0
- package/dist/src/tui/tui-agent-events.js +103 -0
- package/dist/src/tui/tui-agent-events.js.map +1 -0
- package/dist/src/tui/tui-backend.d.ts +8 -12
- package/dist/src/tui/tui-commands.d.ts +23 -0
- package/dist/src/tui/tui-commands.js +165 -0
- package/dist/src/tui/tui-commands.js.map +1 -0
- package/dist/src/tui/tui-lifecycle.d.ts +26 -0
- package/dist/src/tui/tui-lifecycle.js +57 -0
- package/dist/src/tui/tui-lifecycle.js.map +1 -0
- package/dist/src/tui/tui-local-shell.d.ts +28 -0
- package/dist/src/tui/tui-local-shell.js +147 -0
- package/dist/src/tui/tui-local-shell.js.map +1 -0
- package/dist/src/tui/tui-overlays.d.ts +8 -0
- package/dist/src/tui/tui-overlays.js +22 -0
- package/dist/src/tui/tui-overlays.js.map +1 -0
- package/dist/src/tui/tui-picker-overlay.d.ts +26 -0
- package/dist/src/tui/tui-picker-overlay.js +69 -0
- package/dist/src/tui/tui-picker-overlay.js.map +1 -0
- package/dist/src/tui/tui-stdio-filter.d.ts +17 -0
- package/dist/src/tui/tui-stdio-filter.js +96 -0
- package/dist/src/tui/tui-stdio-filter.js.map +1 -0
- package/dist/src/tui/tui-submit.d.ts +25 -0
- package/dist/src/tui/tui-submit.js +102 -0
- package/dist/src/tui/tui-submit.js.map +1 -0
- package/dist/src/tui/tui-suspend.d.ts +10 -0
- package/dist/src/tui/tui-suspend.js +18 -0
- package/dist/src/tui/tui-suspend.js.map +1 -0
- package/dist/src/tui/tui-types.d.ts +1 -0
- package/dist/src/tui/tui-types.js.map +1 -1
- package/dist/src/tui/tui.d.ts +2 -0
- package/dist/src/tui/tui.js +175 -312
- package/dist/src/tui/tui.js.map +1 -1
- package/package.json +2 -6
- package/dist/gateway/static/root/assets/agents-DplaQYS2.js +0 -216
- package/dist/gateway/static/root/assets/agents-DplaQYS2.js.map +0 -1
- package/dist/gateway/static/root/assets/channels-settings-CkfSST0k.js +0 -9
- package/dist/gateway/static/root/assets/channels-settings-CkfSST0k.js.map +0 -1
- package/dist/gateway/static/root/assets/cron-page-D9q6KqL8.js +0 -2
- package/dist/gateway/static/root/assets/cron-page-D9q6KqL8.js.map +0 -1
- package/dist/gateway/static/root/assets/index-OT4cGzon.css +0 -1
- package/dist/gateway/static/root/assets/index-lV8FGWlt.js +0 -4734
- package/dist/gateway/static/root/assets/logs-page-DG31RpvG.js +0 -2
- package/dist/gateway/static/root/assets/sessions-page-CdmjxDEM.js +0 -2
- package/dist/gateway/static/root/assets/sessions-page-CdmjxDEM.js.map +0 -1
- package/dist/gateway/static/root/assets/settings-page-DU2XLf5s.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-DU2XLf5s.js.map +0 -1
- package/dist/gateway/static/root/assets/skills-page-lb7vYtlP.js +0 -3
package/dist/src/tui/tui.js
CHANGED
|
@@ -1,233 +1,32 @@
|
|
|
1
|
+
import { appendHistoryToChatLog } from "./chat-history.js";
|
|
1
2
|
import { StreamAssembler } from "./stream-assembler.js";
|
|
2
3
|
import { editorTheme, theme } from "./theme.js";
|
|
4
|
+
import { clearPendingToolCallIds, dispatchAgentSSE } from "./tui-agent-events.js";
|
|
5
|
+
import { createTuiCommandHandler, getSlashCommands } from "./tui-commands.js";
|
|
6
|
+
import { createBackspaceDeduper, drainAndStopTuiSafely, isIgnorableTuiStopError, resolveCtrlCAction, stopTuiSafely } from "./tui-lifecycle.js";
|
|
7
|
+
import { createLocalShellRunner } from "./tui-local-shell.js";
|
|
8
|
+
import { createOverlayHandlers } from "./tui-overlays.js";
|
|
9
|
+
import { openModelPickerOverlay, openSessionPickerOverlay } from "./tui-picker-overlay.js";
|
|
10
|
+
import { installTuiStdioFilter } from "./tui-stdio-filter.js";
|
|
11
|
+
import { createEditorSubmitHandler, createSubmitBurstCoalescer, shouldEnableWindowsGitBashPasteFallback } from "./tui-submit.js";
|
|
12
|
+
import { withTuiSuspended } from "./tui-suspend.js";
|
|
3
13
|
import { createInitialState } from "./tui-types.js";
|
|
4
14
|
import { EmbeddedBackend } from "./backends/embedded-backend.js";
|
|
5
15
|
import { GatewaySseBackend } from "./backends/gateway-sse-backend.js";
|
|
6
16
|
import { ChatLog } from "./components/chat-log.js";
|
|
7
17
|
import { CustomEditor } from "./components/custom-editor.js";
|
|
8
|
-
import { CombinedAutocompleteProvider, Container, Loader, ProcessTerminal, TUI, Text } from "@mariozechner/pi-tui";
|
|
18
|
+
import { CombinedAutocompleteProvider, Container, Key, Loader, ProcessTerminal, TUI, Text, getKeybindings, isKeyRelease, matchesKey, parseKey } from "@mariozechner/pi-tui";
|
|
9
19
|
//#region src/tui/tui.ts
|
|
10
|
-
function
|
|
11
|
-
return
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
}
|
|
20
|
+
function matchesCtrlCSequence(data) {
|
|
21
|
+
if (isKeyRelease(data)) return false;
|
|
22
|
+
if (data === "") return true;
|
|
23
|
+
if (parseKey(data) === "ctrl+c") return true;
|
|
24
|
+
const kb = getKeybindings();
|
|
25
|
+
return matchesKey(data, Key.ctrl("c")) || kb.matches(data, "tui.input.copy");
|
|
212
26
|
}
|
|
213
27
|
async function runTui(opts) {
|
|
214
|
-
const
|
|
215
|
-
const
|
|
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
|
-
};
|
|
28
|
+
const stdioFilter = installTuiStdioFilter();
|
|
29
|
+
const restoreStdio = () => stdioFilter.restore();
|
|
231
30
|
const isLocalMode = opts.local === true;
|
|
232
31
|
const state = createInitialState(opts.session ?? "cli:tui");
|
|
233
32
|
const assembler = new StreamAssembler();
|
|
@@ -236,6 +35,12 @@ async function runTui(opts) {
|
|
|
236
35
|
token: opts.token
|
|
237
36
|
});
|
|
238
37
|
const tui = new TUI(new ProcessTerminal());
|
|
38
|
+
const dedupeBackspace = createBackspaceDeduper();
|
|
39
|
+
tui.addInputListener((data) => {
|
|
40
|
+
const next = dedupeBackspace(data);
|
|
41
|
+
if (next.length === 0) return { consume: true };
|
|
42
|
+
return { data: next };
|
|
43
|
+
});
|
|
239
44
|
const header = new Text("", 1, 0);
|
|
240
45
|
const statusContainer = new Container();
|
|
241
46
|
const footer = new Text("", 1, 0);
|
|
@@ -249,6 +54,7 @@ async function runTui(opts) {
|
|
|
249
54
|
root.addChild(editor);
|
|
250
55
|
tui.addChild(root);
|
|
251
56
|
tui.setFocus(editor);
|
|
57
|
+
const { openOverlay, closeOverlay } = createOverlayHandlers(tui, editor);
|
|
252
58
|
const slashCommands = getSlashCommands(isLocalMode);
|
|
253
59
|
editor.setAutocompleteProvider(new CombinedAutocompleteProvider(slashCommands.map((c) => ({
|
|
254
60
|
name: c.name,
|
|
@@ -265,6 +71,11 @@ async function runTui(opts) {
|
|
|
265
71
|
"streaming",
|
|
266
72
|
"running"
|
|
267
73
|
]);
|
|
74
|
+
let lastStreamActivityAt = Date.now();
|
|
75
|
+
let streamWatchdogId = null;
|
|
76
|
+
const touchStreamingActivity = () => {
|
|
77
|
+
lastStreamActivityAt = Date.now();
|
|
78
|
+
};
|
|
268
79
|
const formatElapsed = (startMs) => {
|
|
269
80
|
const totalSeconds = Math.max(0, Math.floor((Date.now() - startMs) / 1e3));
|
|
270
81
|
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
@@ -335,6 +146,38 @@ async function runTui(opts) {
|
|
|
335
146
|
tui.requestRender();
|
|
336
147
|
} catch {}
|
|
337
148
|
};
|
|
149
|
+
let finishTui = null;
|
|
150
|
+
let exitResult = { exitReason: "exit" };
|
|
151
|
+
const requestExit = () => {
|
|
152
|
+
if (state.exitRequested) return;
|
|
153
|
+
state.exitRequested = true;
|
|
154
|
+
if (elapsedTimerId) {
|
|
155
|
+
clearInterval(elapsedTimerId);
|
|
156
|
+
elapsedTimerId = null;
|
|
157
|
+
}
|
|
158
|
+
if (streamWatchdogId) {
|
|
159
|
+
clearInterval(streamWatchdogId);
|
|
160
|
+
streamWatchdogId = null;
|
|
161
|
+
}
|
|
162
|
+
client.stop();
|
|
163
|
+
drainAndStopTuiSafely(tui).then(() => {
|
|
164
|
+
restoreStdio();
|
|
165
|
+
finishTui?.();
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
const abortActive = async () => {
|
|
169
|
+
if (!state.activeRunId) return;
|
|
170
|
+
const runId = state.activeRunId;
|
|
171
|
+
state.activeRunId = null;
|
|
172
|
+
assembler.drop(runId);
|
|
173
|
+
chatLog.dropAssistant(runId);
|
|
174
|
+
setActivityStatus("idle");
|
|
175
|
+
tui.requestRender();
|
|
176
|
+
await client.abortChat({
|
|
177
|
+
sessionKey: state.currentSessionKey,
|
|
178
|
+
runId
|
|
179
|
+
}).catch(() => {});
|
|
180
|
+
};
|
|
338
181
|
const sendMessage = (text) => {
|
|
339
182
|
if (state.activeRunId) {
|
|
340
183
|
chatLog.addSystem("A response is still in progress. Use /abort or press Escape to cancel.");
|
|
@@ -343,6 +186,7 @@ async function runTui(opts) {
|
|
|
343
186
|
}
|
|
344
187
|
chatLog.addUser(text);
|
|
345
188
|
setActivityStatus("sending");
|
|
189
|
+
touchStreamingActivity();
|
|
346
190
|
tui.requestRender();
|
|
347
191
|
client.sendChat({
|
|
348
192
|
sessionKey: state.currentSessionKey,
|
|
@@ -355,79 +199,78 @@ async function runTui(opts) {
|
|
|
355
199
|
tui.requestRender();
|
|
356
200
|
});
|
|
357
201
|
};
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
assembler
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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;
|
|
202
|
+
const handleCommand = createTuiCommandHandler({
|
|
203
|
+
state,
|
|
204
|
+
chatLog,
|
|
205
|
+
tui,
|
|
206
|
+
assembler,
|
|
207
|
+
isLocalMode,
|
|
208
|
+
abortActive,
|
|
209
|
+
sendMessage,
|
|
210
|
+
requestExit,
|
|
211
|
+
updateFooter
|
|
212
|
+
});
|
|
213
|
+
const { runLocalShellLine } = createLocalShellRunner({
|
|
214
|
+
chatLog,
|
|
215
|
+
tui,
|
|
216
|
+
editor,
|
|
217
|
+
openOverlay,
|
|
218
|
+
closeOverlay,
|
|
219
|
+
pauseStdioFilter: () => stdioFilter.pause(),
|
|
220
|
+
resumeStdioFilter: () => stdioFilter.resume(),
|
|
221
|
+
runWithInheritedStdio: async (work) => {
|
|
222
|
+
await withTuiSuspended(tui, work);
|
|
404
223
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
224
|
+
});
|
|
225
|
+
editor.onSubmit = createSubmitBurstCoalescer({
|
|
226
|
+
submit: createEditorSubmitHandler({
|
|
227
|
+
editor,
|
|
228
|
+
handleCommand,
|
|
229
|
+
sendMessage,
|
|
230
|
+
handleBangLine: runLocalShellLine
|
|
231
|
+
}),
|
|
232
|
+
enabled: shouldEnableWindowsGitBashPasteFallback()
|
|
233
|
+
});
|
|
234
|
+
const setSessionKey = (key) => {
|
|
235
|
+
state.currentSessionKey = key;
|
|
236
|
+
};
|
|
237
|
+
const clearChatForSessionSwitch = () => {
|
|
238
|
+
assembler.clear();
|
|
239
|
+
chatLog.clearAll();
|
|
240
|
+
clearPendingToolCallIds();
|
|
241
|
+
state.historyLoaded = false;
|
|
242
|
+
};
|
|
243
|
+
const loadSessionHistory = async () => {
|
|
244
|
+
try {
|
|
245
|
+
const { messages } = await client.loadHistory({
|
|
246
|
+
sessionKey: state.currentSessionKey,
|
|
247
|
+
limit: 200
|
|
248
|
+
});
|
|
249
|
+
appendHistoryToChatLog(chatLog, messages, state.toolsExpanded);
|
|
250
|
+
} catch {} finally {
|
|
251
|
+
state.historyLoaded = true;
|
|
252
|
+
tui.requestRender();
|
|
418
253
|
}
|
|
419
|
-
sendMessage(input);
|
|
420
254
|
};
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
editor
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
255
|
+
const pickerSvc = {
|
|
256
|
+
tui,
|
|
257
|
+
editor,
|
|
258
|
+
openOverlay,
|
|
259
|
+
closeOverlay,
|
|
260
|
+
chatLog,
|
|
261
|
+
client,
|
|
262
|
+
sendMessage,
|
|
263
|
+
refreshSessionInfo,
|
|
264
|
+
updateHeader,
|
|
265
|
+
state,
|
|
266
|
+
setSessionKey,
|
|
267
|
+
clearChatForSessionSwitch,
|
|
268
|
+
loadSessionHistory
|
|
428
269
|
};
|
|
429
270
|
editor.onEscape = () => void abortActive();
|
|
430
271
|
editor.onCtrlD = () => requestExit();
|
|
272
|
+
editor.onCtrlL = () => void openModelPickerOverlay(pickerSvc);
|
|
273
|
+
editor.onCtrlP = () => void openSessionPickerOverlay(pickerSvc);
|
|
431
274
|
editor.onCtrlO = () => {
|
|
432
275
|
state.toolsExpanded = !state.toolsExpanded;
|
|
433
276
|
chatLog.setToolsExpanded(state.toolsExpanded);
|
|
@@ -441,44 +284,54 @@ async function runTui(opts) {
|
|
|
441
284
|
};
|
|
442
285
|
const handleCtrlC = () => {
|
|
443
286
|
const now = Date.now();
|
|
444
|
-
const decision = resolveCtrlCAction(
|
|
287
|
+
const decision = resolveCtrlCAction({
|
|
288
|
+
hasInput: editor.getText().trim().length > 0,
|
|
289
|
+
now,
|
|
290
|
+
lastCtrlCAt: state.lastCtrlCAt
|
|
291
|
+
});
|
|
445
292
|
state.lastCtrlCAt = decision.nextLastCtrlCAt;
|
|
446
293
|
if (decision.action === "clear") {
|
|
447
294
|
editor.setText("");
|
|
448
295
|
setActivityStatus("cleared input; press ctrl+c again to exit");
|
|
449
296
|
tui.requestRender();
|
|
450
|
-
|
|
451
|
-
else {
|
|
452
|
-
setActivityStatus("press ctrl+c again to exit");
|
|
453
|
-
tui.requestRender();
|
|
297
|
+
return;
|
|
454
298
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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;
|
|
299
|
+
if (decision.action === "exit") {
|
|
300
|
+
requestExit();
|
|
301
|
+
return;
|
|
465
302
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
tui.stop();
|
|
469
|
-
} catch {}
|
|
470
|
-
restoreStdio();
|
|
471
|
-
finishTui?.();
|
|
303
|
+
setActivityStatus("press ctrl+c again to exit");
|
|
304
|
+
tui.requestRender();
|
|
472
305
|
};
|
|
306
|
+
editor.onCtrlC = handleCtrlC;
|
|
307
|
+
tui.addInputListener((data) => {
|
|
308
|
+
if (!matchesCtrlCSequence(data)) return void 0;
|
|
309
|
+
handleCtrlC();
|
|
310
|
+
return { consume: true };
|
|
311
|
+
});
|
|
312
|
+
streamWatchdogId = setInterval(() => {
|
|
313
|
+
if (!state.activeRunId) return;
|
|
314
|
+
if (!busyStates.has(state.activityStatus)) return;
|
|
315
|
+
if (Date.now() - lastStreamActivityAt < 3e4) return;
|
|
316
|
+
const rid = state.activeRunId;
|
|
317
|
+
const finalText = assembler.finalize(rid, state.showThinking);
|
|
318
|
+
if (finalText) chatLog.finalizeAssistant(finalText, rid);
|
|
319
|
+
chatLog.addSystem("⚠️ No stream activity for 30s; UI reset (connection may have stalled). Retry or check gateway.");
|
|
320
|
+
state.activeRunId = null;
|
|
321
|
+
setActivityStatus("idle");
|
|
322
|
+
tui.requestRender();
|
|
323
|
+
}, 5e3);
|
|
473
324
|
client.onEvent = (evt) => {
|
|
474
325
|
const data = evt.data ?? {};
|
|
475
|
-
dispatchAgentSSE(evt.event, data, state, chatLog, assembler, tui, setActivityStatus);
|
|
326
|
+
dispatchAgentSSE(evt.event, data, state, chatLog, assembler, tui, setActivityStatus, touchStreamingActivity);
|
|
476
327
|
};
|
|
477
328
|
client.onConnected = () => {
|
|
478
329
|
state.isConnected = true;
|
|
479
330
|
setConnectionStatus(isLocalMode ? "local ready" : "gateway connected");
|
|
331
|
+
touchStreamingActivity();
|
|
480
332
|
(async () => {
|
|
481
333
|
await refreshSessionInfo();
|
|
334
|
+
await loadSessionHistory();
|
|
482
335
|
updateHeader();
|
|
483
336
|
updateFooter();
|
|
484
337
|
tui.requestRender();
|
|
@@ -491,16 +344,22 @@ async function runTui(opts) {
|
|
|
491
344
|
client.onDisconnected = (reason) => {
|
|
492
345
|
const wasConnected = state.isConnected;
|
|
493
346
|
state.isConnected = false;
|
|
347
|
+
touchStreamingActivity();
|
|
494
348
|
if (isLocalMode) setConnectionStatus(`local stopped: ${reason}`);
|
|
495
349
|
else {
|
|
496
|
-
setConnectionStatus(`disconnected
|
|
350
|
+
setConnectionStatus(`disconnected${wasConnected || state.historyLoaded ? ` (${reason}). Reconnecting broadcast stream…` : `. Ensure gateway is running (xopc gateway) or use --local.`}`);
|
|
497
351
|
if (!wasConnected && !state.historyLoaded) {
|
|
498
352
|
const gatewayUrl = opts.url ?? "http://localhost:3120";
|
|
499
|
-
chatLog.addSystem(`Cannot reach gateway at ${gatewayUrl}.\
|
|
353
|
+
chatLog.addSystem(`Cannot reach gateway at ${gatewayUrl}.\nStart the gateway (\`xopc gateway\`) or run \`xopc tui --local\` for embedded mode.`);
|
|
500
354
|
}
|
|
501
355
|
}
|
|
502
356
|
tui.requestRender();
|
|
503
357
|
};
|
|
358
|
+
client.onGap = (info) => {
|
|
359
|
+
chatLog.addSystem(`⚠️ Event gap: expected ${info.expected}, received ${info.received}. Some updates may be missing.`);
|
|
360
|
+
setConnectionStatus(`event gap: expected ${info.expected}, got ${info.received}`);
|
|
361
|
+
tui.requestRender();
|
|
362
|
+
};
|
|
504
363
|
const sigintHandler = () => handleCtrlC();
|
|
505
364
|
const sigtermHandler = () => requestExit();
|
|
506
365
|
process.on("SIGINT", sigintHandler);
|
|
@@ -514,6 +373,10 @@ async function runTui(opts) {
|
|
|
514
373
|
finishTui = () => {
|
|
515
374
|
process.removeListener("SIGINT", sigintHandler);
|
|
516
375
|
process.removeListener("SIGTERM", sigtermHandler);
|
|
376
|
+
if (streamWatchdogId) {
|
|
377
|
+
clearInterval(streamWatchdogId);
|
|
378
|
+
streamWatchdogId = null;
|
|
379
|
+
}
|
|
517
380
|
finishTui = null;
|
|
518
381
|
resolve();
|
|
519
382
|
};
|
|
@@ -521,6 +384,6 @@ async function runTui(opts) {
|
|
|
521
384
|
return exitResult;
|
|
522
385
|
}
|
|
523
386
|
//#endregion
|
|
524
|
-
export { runTui };
|
|
387
|
+
export { createBackspaceDeduper, drainAndStopTuiSafely, isIgnorableTuiStopError, resolveCtrlCAction, runTui, stopTuiSafely, withTuiSuspended };
|
|
525
388
|
|
|
526
389
|
//# sourceMappingURL=tui.js.map
|