@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
|
@@ -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 {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { selectListTheme } from "./theme.js";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { SelectList } from "@mariozechner/pi-tui";
|
|
4
|
+
//#region src/tui/tui-local-shell.ts
|
|
5
|
+
/** `!command` runs on the local machine (gated by in-session consent). `!!command` uses inherited stdio. */
|
|
6
|
+
function createLocalShellRunner(deps) {
|
|
7
|
+
let localExecAsked = false;
|
|
8
|
+
let localExecAllowed = false;
|
|
9
|
+
const spawnCommand = deps.spawnCommand ?? spawn;
|
|
10
|
+
const getCwd = deps.getCwd ?? (() => process.cwd());
|
|
11
|
+
const env = deps.env ?? process.env;
|
|
12
|
+
const maxChars = deps.maxOutputChars ?? 4e4;
|
|
13
|
+
const ensureLocalExecAllowed = async () => {
|
|
14
|
+
if (localExecAllowed) return true;
|
|
15
|
+
if (localExecAsked) return false;
|
|
16
|
+
localExecAsked = true;
|
|
17
|
+
return await new Promise((resolve) => {
|
|
18
|
+
deps.chatLog.addSystem("Allow local shell commands for this session?");
|
|
19
|
+
deps.chatLog.addSystem("Runs on YOUR machine (not the gateway); may delete files or expose secrets.");
|
|
20
|
+
deps.chatLog.addSystem("↑/↓ + Enter to choose, Esc to cancel.");
|
|
21
|
+
const selector = new SelectList([{
|
|
22
|
+
value: "no",
|
|
23
|
+
label: "No"
|
|
24
|
+
}, {
|
|
25
|
+
value: "yes",
|
|
26
|
+
label: "Yes"
|
|
27
|
+
}], 2, selectListTheme);
|
|
28
|
+
selector.onSelect = (item) => {
|
|
29
|
+
deps.closeOverlay();
|
|
30
|
+
if (item.value === "yes") {
|
|
31
|
+
localExecAllowed = true;
|
|
32
|
+
deps.chatLog.addSystem("local shell: enabled for this session");
|
|
33
|
+
resolve(true);
|
|
34
|
+
} else {
|
|
35
|
+
deps.chatLog.addSystem("local shell: not enabled");
|
|
36
|
+
resolve(false);
|
|
37
|
+
}
|
|
38
|
+
deps.tui.requestRender();
|
|
39
|
+
};
|
|
40
|
+
selector.onCancel = () => {
|
|
41
|
+
deps.closeOverlay();
|
|
42
|
+
deps.chatLog.addSystem("local shell: cancelled");
|
|
43
|
+
deps.tui.requestRender();
|
|
44
|
+
resolve(false);
|
|
45
|
+
};
|
|
46
|
+
deps.openOverlay(selector);
|
|
47
|
+
deps.tui.requestRender();
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const runLocalShellLine = async (line) => {
|
|
51
|
+
let cmd = line.slice(1);
|
|
52
|
+
if (cmd === "") return;
|
|
53
|
+
let inheritStdio = false;
|
|
54
|
+
if (cmd.startsWith("!")) {
|
|
55
|
+
inheritStdio = true;
|
|
56
|
+
cmd = cmd.slice(1);
|
|
57
|
+
if (cmd === "") {
|
|
58
|
+
deps.chatLog.addSystem("[local] !! requires a command (e.g. !!vim file)");
|
|
59
|
+
deps.tui.requestRender();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (localExecAsked && !localExecAllowed) {
|
|
64
|
+
deps.chatLog.addSystem("local shell: not enabled for this session");
|
|
65
|
+
deps.tui.requestRender();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!await ensureLocalExecAllowed()) return;
|
|
69
|
+
deps.chatLog.addSystem(`[local] $ ${cmd}${inheritStdio ? " (inherited stdio)" : ""}`);
|
|
70
|
+
deps.tui.requestRender();
|
|
71
|
+
const appendWithCap = (text, chunk) => {
|
|
72
|
+
const combined = text + chunk;
|
|
73
|
+
return combined.length > maxChars ? combined.slice(-maxChars) : combined;
|
|
74
|
+
};
|
|
75
|
+
if (inheritStdio && deps.runWithInheritedStdio && deps.pauseStdioFilter && deps.resumeStdioFilter) {
|
|
76
|
+
deps.pauseStdioFilter();
|
|
77
|
+
try {
|
|
78
|
+
await deps.runWithInheritedStdio(async () => {
|
|
79
|
+
await new Promise((resolve, reject) => {
|
|
80
|
+
const child = spawnCommand(cmd, {
|
|
81
|
+
shell: true,
|
|
82
|
+
cwd: getCwd(),
|
|
83
|
+
env: {
|
|
84
|
+
...env,
|
|
85
|
+
XOPC_SHELL: "tui-local"
|
|
86
|
+
},
|
|
87
|
+
stdio: "inherit"
|
|
88
|
+
});
|
|
89
|
+
child.on("close", (code, signal) => {
|
|
90
|
+
deps.chatLog.addSystem(`[local] exit ${code ?? "?"}${signal ? ` (signal ${signal})` : ""}`);
|
|
91
|
+
resolve();
|
|
92
|
+
});
|
|
93
|
+
child.on("error", (err) => {
|
|
94
|
+
deps.chatLog.addSystem(`[local] error: ${String(err)}`);
|
|
95
|
+
reject(err);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
} catch {} finally {
|
|
100
|
+
deps.resumeStdioFilter();
|
|
101
|
+
deps.tui.setFocus(deps.editor);
|
|
102
|
+
deps.tui.requestRender();
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (inheritStdio && !deps.runWithInheritedStdio) {
|
|
107
|
+
deps.chatLog.addSystem("[local] inherited stdio requires full TUI wiring; use single ! instead.");
|
|
108
|
+
deps.tui.requestRender();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
await new Promise((resolve) => {
|
|
112
|
+
const child = spawnCommand(cmd, {
|
|
113
|
+
shell: true,
|
|
114
|
+
cwd: getCwd(),
|
|
115
|
+
env: {
|
|
116
|
+
...env,
|
|
117
|
+
XOPC_SHELL: "tui-local"
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
let stdout = "";
|
|
121
|
+
let stderr = "";
|
|
122
|
+
child.stdout?.on("data", (buf) => {
|
|
123
|
+
stdout = appendWithCap(stdout, buf.toString("utf8"));
|
|
124
|
+
});
|
|
125
|
+
child.stderr?.on("data", (buf) => {
|
|
126
|
+
stderr = appendWithCap(stderr, buf.toString("utf8"));
|
|
127
|
+
});
|
|
128
|
+
child.on("close", (code, signal) => {
|
|
129
|
+
const combined = (stdout + (stderr ? (stdout ? "\n" : "") + stderr : "")).slice(0, maxChars).trimEnd();
|
|
130
|
+
if (combined) for (const lineChunk of combined.split("\n")) deps.chatLog.addSystem(`[local] ${lineChunk}`);
|
|
131
|
+
deps.chatLog.addSystem(`[local] exit ${code ?? "?"}${signal ? ` (signal ${signal})` : ""}`);
|
|
132
|
+
deps.tui.requestRender();
|
|
133
|
+
resolve();
|
|
134
|
+
});
|
|
135
|
+
child.on("error", (err) => {
|
|
136
|
+
deps.chatLog.addSystem(`[local] error: ${String(err)}`);
|
|
137
|
+
deps.tui.requestRender();
|
|
138
|
+
resolve();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
return { runLocalShellLine };
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
export { createLocalShellRunner };
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=tui-local-shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-local-shell.js","names":[],"sources":["../../../src/tui/tui-local-shell.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\n\nimport type { Component } from '@mariozechner/pi-tui';\nimport { SelectList } from '@mariozechner/pi-tui';\n\nimport { selectListTheme } from './theme.js';\n\ntype LocalShellDeps = {\n chatLog: { addSystem: (line: string) => void };\n tui: { requestRender: () => void; setFocus: (c: Component) => void };\n editor: Component;\n openOverlay: (component: Component) => void;\n closeOverlay: () => void;\n spawnCommand?: typeof spawn;\n getCwd?: () => string;\n env?: NodeJS.ProcessEnv;\n maxOutputChars?: number;\n /** Pause stdout/stderr filtering before handing the terminal to a child (`stdio: 'inherit'`). */\n pauseStdioFilter?: () => void;\n resumeStdioFilter?: () => void;\n /** Wrap work while the TUI is stopped (full-screen subprocess). */\n runWithInheritedStdio?: (work: () => Promise<void>) => Promise<void>;\n};\n\n/** `!command` runs on the local machine (gated by in-session consent). `!!command` uses inherited stdio. */\nexport function createLocalShellRunner(deps: LocalShellDeps) {\n let localExecAsked = false;\n let localExecAllowed = false;\n const spawnCommand = deps.spawnCommand ?? spawn;\n const getCwd = deps.getCwd ?? (() => process.cwd());\n const env = deps.env ?? process.env;\n const maxChars = deps.maxOutputChars ?? 40_000;\n\n const ensureLocalExecAllowed = async (): Promise<boolean> => {\n if (localExecAllowed) {\n return true;\n }\n if (localExecAsked) {\n return false;\n }\n localExecAsked = true;\n\n return await new Promise<boolean>((resolve) => {\n deps.chatLog.addSystem('Allow local shell commands for this session?');\n deps.chatLog.addSystem(\n 'Runs on YOUR machine (not the gateway); may delete files or expose secrets.',\n );\n deps.chatLog.addSystem('↑/↓ + Enter to choose, Esc to cancel.');\n const selector = new SelectList(\n [\n { value: 'no', label: 'No' },\n { value: 'yes', label: 'Yes' },\n ],\n 2,\n selectListTheme,\n );\n selector.onSelect = (item) => {\n deps.closeOverlay();\n if (item.value === 'yes') {\n localExecAllowed = true;\n deps.chatLog.addSystem('local shell: enabled for this session');\n resolve(true);\n } else {\n deps.chatLog.addSystem('local shell: not enabled');\n resolve(false);\n }\n deps.tui.requestRender();\n };\n selector.onCancel = () => {\n deps.closeOverlay();\n deps.chatLog.addSystem('local shell: cancelled');\n deps.tui.requestRender();\n resolve(false);\n };\n deps.openOverlay(selector);\n deps.tui.requestRender();\n });\n };\n\n const runLocalShellLine = async (line: string) => {\n let cmd = line.slice(1);\n if (cmd === '') {\n return;\n }\n\n let inheritStdio = false;\n if (cmd.startsWith('!')) {\n inheritStdio = true;\n cmd = cmd.slice(1);\n if (cmd === '') {\n deps.chatLog.addSystem('[local] !! requires a command (e.g. !!vim file)');\n deps.tui.requestRender();\n return;\n }\n }\n\n if (localExecAsked && !localExecAllowed) {\n deps.chatLog.addSystem('local shell: not enabled for this session');\n deps.tui.requestRender();\n return;\n }\n\n const allowed = await ensureLocalExecAllowed();\n if (!allowed) {\n return;\n }\n\n deps.chatLog.addSystem(`[local] $ ${cmd}${inheritStdio ? ' (inherited stdio)' : ''}`);\n deps.tui.requestRender();\n\n const appendWithCap = (text: string, chunk: string) => {\n const combined = text + chunk;\n return combined.length > maxChars ? combined.slice(-maxChars) : combined;\n };\n\n if (\n inheritStdio &&\n deps.runWithInheritedStdio &&\n deps.pauseStdioFilter &&\n deps.resumeStdioFilter\n ) {\n deps.pauseStdioFilter();\n try {\n await deps.runWithInheritedStdio(async () => {\n await new Promise<void>((resolve, reject) => {\n const child = spawnCommand(cmd, {\n shell: true,\n cwd: getCwd(),\n env: { ...env, XOPC_SHELL: 'tui-local' },\n stdio: 'inherit',\n });\n child.on('close', (code, signal) => {\n deps.chatLog.addSystem(\n `[local] exit ${code ?? '?'}${signal ? ` (signal ${signal})` : ''}`,\n );\n resolve();\n });\n child.on('error', (err) => {\n deps.chatLog.addSystem(`[local] error: ${String(err)}`);\n reject(err);\n });\n });\n });\n } catch {\n // logged above\n } finally {\n deps.resumeStdioFilter();\n deps.tui.setFocus(deps.editor);\n deps.tui.requestRender();\n }\n return;\n }\n\n if (inheritStdio && !deps.runWithInheritedStdio) {\n deps.chatLog.addSystem('[local] inherited stdio requires full TUI wiring; use single ! instead.');\n deps.tui.requestRender();\n return;\n }\n\n await new Promise<void>((resolve) => {\n const child = spawnCommand(cmd, {\n shell: true,\n cwd: getCwd(),\n env: { ...env, XOPC_SHELL: 'tui-local' },\n });\n\n let stdout = '';\n let stderr = '';\n child.stdout?.on('data', (buf) => {\n stdout = appendWithCap(stdout, buf.toString('utf8'));\n });\n child.stderr?.on('data', (buf) => {\n stderr = appendWithCap(stderr, buf.toString('utf8'));\n });\n\n child.on('close', (code, signal) => {\n const combined = (stdout + (stderr ? (stdout ? '\\n' : '') + stderr : ''))\n .slice(0, maxChars)\n .trimEnd();\n\n if (combined) {\n for (const lineChunk of combined.split('\\n')) {\n deps.chatLog.addSystem(`[local] ${lineChunk}`);\n }\n }\n deps.chatLog.addSystem(\n `[local] exit ${code ?? '?'}${signal ? ` (signal ${signal})` : ''}`,\n );\n deps.tui.requestRender();\n resolve();\n });\n\n child.on('error', (err) => {\n deps.chatLog.addSystem(`[local] error: ${String(err)}`);\n deps.tui.requestRender();\n resolve();\n });\n });\n };\n\n return { runLocalShellLine };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Component, TUI } from '@mariozechner/pi-tui';
|
|
2
|
+
type OverlayHost = Pick<TUI, 'showOverlay' | 'hideOverlay' | 'hasOverlay' | 'setFocus'>;
|
|
3
|
+
/** Focus management for pi-tui overlays (openclaw pattern). */
|
|
4
|
+
export declare function createOverlayHandlers(host: OverlayHost, fallbackFocus: Component): {
|
|
5
|
+
openOverlay: (component: Component) => void;
|
|
6
|
+
closeOverlay: () => void;
|
|
7
|
+
};
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/tui/tui-overlays.ts
|
|
2
|
+
/** Focus management for pi-tui overlays (openclaw pattern). */
|
|
3
|
+
function createOverlayHandlers(host, fallbackFocus) {
|
|
4
|
+
const openOverlay = (component) => {
|
|
5
|
+
host.showOverlay(component);
|
|
6
|
+
};
|
|
7
|
+
const closeOverlay = () => {
|
|
8
|
+
if (host.hasOverlay()) {
|
|
9
|
+
host.hideOverlay();
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
host.setFocus(fallbackFocus);
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
openOverlay,
|
|
16
|
+
closeOverlay
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { createOverlayHandlers };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=tui-overlays.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-overlays.js","names":[],"sources":["../../../src/tui/tui-overlays.ts"],"sourcesContent":["import type { Component, TUI } from '@mariozechner/pi-tui';\n\ntype OverlayHost = Pick<TUI, 'showOverlay' | 'hideOverlay' | 'hasOverlay' | 'setFocus'>;\n\n/** Focus management for pi-tui overlays (openclaw pattern). */\nexport function createOverlayHandlers(host: OverlayHost, fallbackFocus: Component) {\n const openOverlay = (component: Component) => {\n host.showOverlay(component);\n };\n\n const closeOverlay = () => {\n if (host.hasOverlay()) {\n host.hideOverlay();\n return;\n }\n host.setFocus(fallbackFocus);\n };\n\n return { openOverlay, closeOverlay };\n}\n"],"mappings":";;AAKA,SAAgB,sBAAsB,MAAmB,eAA0B;CACjF,MAAM,eAAe,cAAyB;AAC5C,OAAK,YAAY,UAAU;;CAG7B,MAAM,qBAAqB;AACzB,MAAI,KAAK,YAAY,EAAE;AACrB,QAAK,aAAa;AAClB;;AAEF,OAAK,SAAS,cAAc;;AAG9B,QAAO;EAAE;EAAa;EAAc"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Component, TUI } from '@mariozechner/pi-tui';
|
|
2
|
+
import type { TuiBackend } from './tui-backend.js';
|
|
3
|
+
export type PickerServices = {
|
|
4
|
+
tui: TUI;
|
|
5
|
+
editor: Component;
|
|
6
|
+
openOverlay: (c: Component) => void;
|
|
7
|
+
closeOverlay: () => void;
|
|
8
|
+
chatLog: {
|
|
9
|
+
addSystem: (t: string) => void;
|
|
10
|
+
};
|
|
11
|
+
client: TuiBackend;
|
|
12
|
+
sendMessage: (text: string) => void;
|
|
13
|
+
refreshSessionInfo: () => Promise<void>;
|
|
14
|
+
updateHeader: () => void;
|
|
15
|
+
state: {
|
|
16
|
+
currentSessionKey: string;
|
|
17
|
+
};
|
|
18
|
+
setSessionKey: (key: string) => void;
|
|
19
|
+
clearChatForSessionSwitch: () => void;
|
|
20
|
+
/** Load transcript after switching session or on connect. */
|
|
21
|
+
loadSessionHistory: () => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
/** Ctrl+L — pick model, sends `/switch provider/id`. */
|
|
24
|
+
export declare function openModelPickerOverlay(svc: PickerServices): Promise<void>;
|
|
25
|
+
/** Ctrl+P — switch session key and reload transcript when available. */
|
|
26
|
+
export declare function openSessionPickerOverlay(svc: PickerServices): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { searchableSelectListTheme, theme } from "./theme.js";
|
|
2
|
+
import { SearchableSelectList } from "./components/searchable-select-list.js";
|
|
3
|
+
//#region src/tui/tui-picker-overlay.ts
|
|
4
|
+
function openSearchableOverlay(svc, list, title) {
|
|
5
|
+
list.onCancel = () => {
|
|
6
|
+
svc.closeOverlay();
|
|
7
|
+
svc.tui.setFocus(svc.editor);
|
|
8
|
+
svc.tui.requestRender();
|
|
9
|
+
};
|
|
10
|
+
svc.openOverlay(list);
|
|
11
|
+
svc.chatLog.addSystem(theme.dim(`${title} (↑/↓ ctrl+n ctrl+p · type to filter · Esc)`));
|
|
12
|
+
svc.tui.requestRender();
|
|
13
|
+
}
|
|
14
|
+
/** Ctrl+L — pick model, sends `/switch provider/id`. */
|
|
15
|
+
async function openModelPickerOverlay(svc) {
|
|
16
|
+
const models = await svc.client.listModels();
|
|
17
|
+
if (models.length === 0) {
|
|
18
|
+
svc.chatLog.addSystem("No models available from gateway.");
|
|
19
|
+
svc.tui.requestRender();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const items = models.map((m) => ({
|
|
23
|
+
value: `${m.provider}/${m.id}`,
|
|
24
|
+
label: m.name || m.id,
|
|
25
|
+
description: m.provider,
|
|
26
|
+
searchText: `${m.provider} ${m.id} ${m.name ?? ""}`
|
|
27
|
+
}));
|
|
28
|
+
const list = new SearchableSelectList(items, Math.min(10, items.length), searchableSelectListTheme);
|
|
29
|
+
list.onSelect = (item) => {
|
|
30
|
+
svc.closeOverlay();
|
|
31
|
+
svc.tui.setFocus(svc.editor);
|
|
32
|
+
svc.sendMessage(`/switch ${item.value}`);
|
|
33
|
+
svc.tui.requestRender();
|
|
34
|
+
};
|
|
35
|
+
openSearchableOverlay(svc, list, "Select model");
|
|
36
|
+
}
|
|
37
|
+
/** Ctrl+P — switch session key and reload transcript when available. */
|
|
38
|
+
async function openSessionPickerOverlay(svc) {
|
|
39
|
+
const sessions = await svc.client.listSessions();
|
|
40
|
+
if (sessions.length === 0) {
|
|
41
|
+
svc.chatLog.addSystem("No sessions listed.");
|
|
42
|
+
svc.tui.requestRender();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const items = [...sessions].sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)).slice(0, 80).map((s) => ({
|
|
46
|
+
value: s.key,
|
|
47
|
+
label: s.displayName || s.key,
|
|
48
|
+
description: s.model ? String(s.model) : void 0,
|
|
49
|
+
searchText: `${s.key} ${s.displayName ?? ""} ${s.model ?? ""}`
|
|
50
|
+
}));
|
|
51
|
+
const list = new SearchableSelectList(items, Math.min(10, items.length), searchableSelectListTheme);
|
|
52
|
+
list.onSelect = (item) => {
|
|
53
|
+
svc.closeOverlay();
|
|
54
|
+
svc.tui.setFocus(svc.editor);
|
|
55
|
+
svc.setSessionKey(item.value);
|
|
56
|
+
svc.clearChatForSessionSwitch();
|
|
57
|
+
svc.chatLog.addSystem(`Session: ${item.value}`);
|
|
58
|
+
svc.refreshSessionInfo().then(() => svc.loadSessionHistory()).then(() => {
|
|
59
|
+
svc.updateHeader();
|
|
60
|
+
svc.tui.requestRender();
|
|
61
|
+
});
|
|
62
|
+
svc.tui.requestRender();
|
|
63
|
+
};
|
|
64
|
+
openSearchableOverlay(svc, list, "Select session");
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { openModelPickerOverlay, openSessionPickerOverlay };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=tui-picker-overlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-picker-overlay.js","names":[],"sources":["../../../src/tui/tui-picker-overlay.ts"],"sourcesContent":["import type { Component, SelectItem, TUI } from '@mariozechner/pi-tui';\n\nimport type { TuiBackend } from './tui-backend.js';\nimport { SearchableSelectList } from './components/searchable-select-list.js';\nimport { searchableSelectListTheme, theme } from './theme.js';\n\nexport type PickerServices = {\n tui: TUI;\n editor: Component;\n openOverlay: (c: Component) => void;\n closeOverlay: () => void;\n chatLog: { addSystem: (t: string) => void };\n client: TuiBackend;\n sendMessage: (text: string) => void;\n refreshSessionInfo: () => Promise<void>;\n updateHeader: () => void;\n state: { currentSessionKey: string };\n setSessionKey: (key: string) => void;\n clearChatForSessionSwitch: () => void;\n /** Load transcript after switching session or on connect. */\n loadSessionHistory: () => Promise<void>;\n};\n\nfunction openSearchableOverlay(svc: PickerServices, list: SearchableSelectList, title: string) {\n list.onCancel = () => {\n svc.closeOverlay();\n svc.tui.setFocus(svc.editor);\n svc.tui.requestRender();\n };\n svc.openOverlay(list);\n svc.chatLog.addSystem(theme.dim(`${title} (↑/↓ ctrl+n ctrl+p · type to filter · Esc)`));\n svc.tui.requestRender();\n}\n\n/** Ctrl+L — pick model, sends `/switch provider/id`. */\nexport async function openModelPickerOverlay(svc: PickerServices): Promise<void> {\n const models = await svc.client.listModels();\n if (models.length === 0) {\n svc.chatLog.addSystem('No models available from gateway.');\n svc.tui.requestRender();\n return;\n }\n const items: SelectItem[] = models.map((m) => ({\n value: `${m.provider}/${m.id}`,\n label: m.name || m.id,\n description: m.provider,\n searchText: `${m.provider} ${m.id} ${m.name ?? ''}`,\n }));\n const list = new SearchableSelectList(items, Math.min(10, items.length), searchableSelectListTheme);\n list.onSelect = (item) => {\n svc.closeOverlay();\n svc.tui.setFocus(svc.editor);\n svc.sendMessage(`/switch ${item.value}`);\n svc.tui.requestRender();\n };\n openSearchableOverlay(svc, list, 'Select model');\n}\n\n/** Ctrl+P — switch session key and reload transcript when available. */\nexport async function openSessionPickerOverlay(svc: PickerServices): Promise<void> {\n const sessions = await svc.client.listSessions();\n if (sessions.length === 0) {\n svc.chatLog.addSystem('No sessions listed.');\n svc.tui.requestRender();\n return;\n }\n const sorted = [...sessions].sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));\n const items: SelectItem[] = sorted.slice(0, 80).map((s) => ({\n value: s.key,\n label: s.displayName || s.key,\n description: s.model ? String(s.model) : undefined,\n searchText: `${s.key} ${s.displayName ?? ''} ${s.model ?? ''}`,\n }));\n const list = new SearchableSelectList(items, Math.min(10, items.length), searchableSelectListTheme);\n list.onSelect = (item) => {\n svc.closeOverlay();\n svc.tui.setFocus(svc.editor);\n svc.setSessionKey(item.value);\n svc.clearChatForSessionSwitch();\n svc.chatLog.addSystem(`Session: ${item.value}`);\n void svc\n .refreshSessionInfo()\n .then(() => svc.loadSessionHistory())\n .then(() => {\n svc.updateHeader();\n svc.tui.requestRender();\n });\n svc.tui.requestRender();\n };\n openSearchableOverlay(svc, list, 'Select session');\n}\n"],"mappings":";;;AAuBA,SAAS,sBAAsB,KAAqB,MAA4B,OAAe;AAC7F,MAAK,iBAAiB;AACpB,MAAI,cAAc;AAClB,MAAI,IAAI,SAAS,IAAI,OAAO;AAC5B,MAAI,IAAI,eAAe;;AAEzB,KAAI,YAAY,KAAK;AACrB,KAAI,QAAQ,UAAU,MAAM,IAAI,GAAG,MAAM,6CAA6C,CAAC;AACvF,KAAI,IAAI,eAAe;;;AAIzB,eAAsB,uBAAuB,KAAoC;CAC/E,MAAM,SAAS,MAAM,IAAI,OAAO,YAAY;AAC5C,KAAI,OAAO,WAAW,GAAG;AACvB,MAAI,QAAQ,UAAU,oCAAoC;AAC1D,MAAI,IAAI,eAAe;AACvB;;CAEF,MAAM,QAAsB,OAAO,KAAK,OAAO;EAC7C,OAAO,GAAG,EAAE,SAAS,GAAG,EAAE;EAC1B,OAAO,EAAE,QAAQ,EAAE;EACnB,aAAa,EAAE;EACf,YAAY,GAAG,EAAE,SAAS,GAAG,EAAE,GAAG,GAAG,EAAE,QAAQ;EAChD,EAAE;CACH,MAAM,OAAO,IAAI,qBAAqB,OAAO,KAAK,IAAI,IAAI,MAAM,OAAO,EAAE,0BAA0B;AACnG,MAAK,YAAY,SAAS;AACxB,MAAI,cAAc;AAClB,MAAI,IAAI,SAAS,IAAI,OAAO;AAC5B,MAAI,YAAY,WAAW,KAAK,QAAQ;AACxC,MAAI,IAAI,eAAe;;AAEzB,uBAAsB,KAAK,MAAM,eAAe;;;AAIlD,eAAsB,yBAAyB,KAAoC;CACjF,MAAM,WAAW,MAAM,IAAI,OAAO,cAAc;AAChD,KAAI,SAAS,WAAW,GAAG;AACzB,MAAI,QAAQ,UAAU,sBAAsB;AAC5C,MAAI,IAAI,eAAe;AACvB;;CAGF,MAAM,QADS,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,OAAO,EAAE,aAAa,MAAM,EAAE,aAAa,GAC/C,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,OAAO;EAC1D,OAAO,EAAE;EACT,OAAO,EAAE,eAAe,EAAE;EAC1B,aAAa,EAAE,QAAQ,OAAO,EAAE,MAAM,GAAG,KAAA;EACzC,YAAY,GAAG,EAAE,IAAI,GAAG,EAAE,eAAe,GAAG,GAAG,EAAE,SAAS;EAC3D,EAAE;CACH,MAAM,OAAO,IAAI,qBAAqB,OAAO,KAAK,IAAI,IAAI,MAAM,OAAO,EAAE,0BAA0B;AACnG,MAAK,YAAY,SAAS;AACxB,MAAI,cAAc;AAClB,MAAI,IAAI,SAAS,IAAI,OAAO;AAC5B,MAAI,cAAc,KAAK,MAAM;AAC7B,MAAI,2BAA2B;AAC/B,MAAI,QAAQ,UAAU,YAAY,KAAK,QAAQ;AAC1C,MACF,oBAAoB,CACpB,WAAW,IAAI,oBAAoB,CAAC,CACpC,WAAW;AACV,OAAI,cAAc;AAClB,OAAI,IAAI,eAAe;IACvB;AACJ,MAAI,IAAI,eAAe;;AAEzB,uBAAsB,KAAK,MAAM,iBAAiB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* While pi-tui owns the screen, strip whole-line pino JSON from stdout/stderr.
|
|
3
|
+
* ANSI chunks must pass through immediately (see runTui).
|
|
4
|
+
*
|
|
5
|
+
* Longer-term (openclaw-style): prefer routing logger transports away from stdout when the TUI
|
|
6
|
+
* is active (e.g. file-only + explicit subsystem tags) instead of patching stdio here.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isLikelyPinoJsonLogLine(line: string): boolean;
|
|
9
|
+
export type TuiStdioFilterHandle = {
|
|
10
|
+
restore: () => void;
|
|
11
|
+
/** Restore native stdio writes temporarily (e.g. inherited child process). */
|
|
12
|
+
pause: () => void;
|
|
13
|
+
/** Re-apply filtering after `pause()`. */
|
|
14
|
+
resume: () => void;
|
|
15
|
+
};
|
|
16
|
+
/** Patch process stdout/stderr; call `restore()` before exiting the TUI. */
|
|
17
|
+
export declare function installTuiStdioFilter(): TuiStdioFilterHandle;
|