@xopcai/xopc 0.0.27 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/adapters/onboard-cli.d.ts +7 -0
- package/dist/extensions/weixin/src/adapters/onboard-cli.js +61 -0
- package/dist/extensions/weixin/src/adapters/onboard-cli.js.map +1 -0
- package/dist/extensions/weixin/src/cli/qr-login.d.ts +5 -0
- package/dist/extensions/weixin/src/cli/qr-login.js +1 -1
- package/dist/extensions/weixin/src/cli/qr-login.js.map +1 -1
- package/dist/extensions/weixin/src/index.js +1 -1
- package/dist/extensions/weixin/src/plugin.d.ts +1 -0
- package/dist/extensions/weixin/src/plugin.js +2 -0
- package/dist/extensions/weixin/src/plugin.js.map +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-CBBh_Ww8.js → apps-page-Bmq19MS-.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-CBBh_Ww8.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-08gdQfl9.js → cron-utils-N1PqD2DB.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-utils-08gdQfl9.js.map → cron-utils-N1PqD2DB.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-C1MrygQH.js → dist--p2HQ2QF.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-C1MrygQH.js.map → dist--p2HQ2QF.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js → extension-debug-page-DwHCB_6T.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js.map → extension-debug-page-DwHCB_6T.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js → extension-page-BsYwQIex.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js.map → extension-page-BsYwQIex.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.js → extension-settings-page-nsisEgjB.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.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-PfkB8N37.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-CQwdV_Xw.js.map +1 -0
- 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-BmBDCEbY.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/weixin/index.js +1 -1
- package/dist/src/cli/agent-chat-log-level-preset.d.ts +8 -0
- package/dist/src/cli/agent-chat-log-level-preset.js +25 -0
- package/dist/src/cli/agent-chat-log-level-preset.js.map +1 -0
- package/dist/src/cli/commands/agent/interactive.js +4 -2
- package/dist/src/cli/commands/agent/interactive.js.map +1 -1
- package/dist/src/cli/commands/agent/stream-renderer.d.ts +14 -0
- package/dist/src/cli/commands/agent/stream-renderer.js +99 -0
- package/dist/src/cli/commands/agent/stream-renderer.js.map +1 -0
- package/dist/src/cli/commands/agent.js +2 -2
- package/dist/src/cli/commands/agent.js.map +1 -1
- package/dist/src/cli/commands/onboard.js +77 -93
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/cli/commands/tui.d.ts +1 -0
- package/dist/src/cli/commands/tui.js +40 -0
- package/dist/src/cli/commands/tui.js.map +1 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +7 -3
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/config/schema.d.ts +6 -0
- package/dist/src/config/schema.js +11 -3
- 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/auth.d.ts +17 -3
- package/dist/src/gateway/auth.js +35 -16
- package/dist/src/gateway/auth.js.map +1 -1
- package/dist/src/gateway/hono/app.js +31 -1
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -1
- package/dist/src/gateway/hono/middleware/auth.js +4 -3
- package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
- package/dist/src/gateway/hono/middleware/scopes.d.ts +15 -0
- package/dist/src/gateway/hono/middleware/scopes.js +41 -0
- package/dist/src/gateway/hono/middleware/scopes.js.map +1 -0
- 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/security/audit.d.ts +18 -0
- package/dist/src/gateway/security/audit.js +68 -0
- package/dist/src/gateway/security/audit.js.map +1 -0
- package/dist/src/gateway/security/csp.d.ts +19 -0
- package/dist/src/gateway/security/csp.js +52 -0
- package/dist/src/gateway/security/csp.js.map +1 -0
- package/dist/src/gateway/security/dangerous-tools.d.ts +20 -0
- package/dist/src/gateway/security/dangerous-tools.js +46 -0
- package/dist/src/gateway/security/dangerous-tools.js.map +1 -0
- package/dist/src/gateway/security/flood-guard.d.ts +28 -0
- package/dist/src/gateway/security/flood-guard.js +42 -0
- package/dist/src/gateway/security/flood-guard.js.map +1 -0
- package/dist/src/gateway/security/index.d.ts +9 -0
- package/dist/src/gateway/security/index.js +10 -0
- package/dist/src/gateway/security/known-weak-secrets.d.ts +10 -0
- package/dist/src/gateway/security/known-weak-secrets.js +36 -0
- package/dist/src/gateway/security/known-weak-secrets.js.map +1 -0
- package/dist/src/gateway/security/operator-scopes.d.ts +37 -0
- package/dist/src/gateway/security/operator-scopes.js +137 -0
- package/dist/src/gateway/security/operator-scopes.js.map +1 -0
- package/dist/src/gateway/security/origin-check.d.ts +21 -0
- package/dist/src/gateway/security/origin-check.js +56 -0
- package/dist/src/gateway/security/origin-check.js.map +1 -0
- package/dist/src/gateway/security/preauth-connection-budget.d.ts +17 -0
- package/dist/src/gateway/security/preauth-connection-budget.js +49 -0
- package/dist/src/gateway/security/preauth-connection-budget.js.map +1 -0
- package/dist/src/gateway/security/secret-equal.d.ts +8 -0
- package/dist/src/gateway/security/secret-equal.js +30 -0
- package/dist/src/gateway/security/secret-equal.js.map +1 -0
- package/dist/src/gateway/service.d.ts +3 -1
- package/dist/src/gateway/service.js +40 -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 +42 -0
- package/dist/src/tui/backends/embedded-backend.js +173 -0
- package/dist/src/tui/backends/embedded-backend.js.map +1 -0
- package/dist/src/tui/backends/gateway-sse-backend.d.ts +53 -0
- package/dist/src/tui/backends/gateway-sse-backend.js +256 -0
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -0
- 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/assistant-message.d.ts +6 -0
- package/dist/src/tui/components/assistant-message.js +19 -0
- package/dist/src/tui/components/assistant-message.js.map +1 -0
- package/dist/src/tui/components/chat-log.d.ts +21 -0
- package/dist/src/tui/components/chat-log.js +113 -0
- package/dist/src/tui/components/chat-log.js.map +1 -0
- package/dist/src/tui/components/custom-editor.d.ts +14 -0
- package/dist/src/tui/components/custom-editor.js +50 -0
- package/dist/src/tui/components/custom-editor.js.map +1 -0
- 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/components/tool-execution.d.ts +16 -0
- package/dist/src/tui/components/tool-execution.js +76 -0
- package/dist/src/tui/components/tool-execution.js.map +1 -0
- package/dist/src/tui/components/user-message.d.ts +6 -0
- package/dist/src/tui/components/user-message.js +22 -0
- package/dist/src/tui/components/user-message.js.map +1 -0
- package/dist/src/tui/sse-consumer.d.ts +15 -0
- package/dist/src/tui/sse-consumer.js +75 -0
- package/dist/src/tui/sse-consumer.js.map +1 -0
- package/dist/src/tui/stream-assembler.d.ts +22 -0
- package/dist/src/tui/stream-assembler.js +63 -0
- package/dist/src/tui/stream-assembler.js.map +1 -0
- package/dist/src/tui/theme.d.ts +73 -0
- package/dist/src/tui/theme.js +157 -0
- package/dist/src/tui/theme.js.map +1 -0
- 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 +80 -0
- package/dist/src/tui/tui-backend.js +1 -0
- 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 +86 -0
- package/dist/src/tui/tui-types.js +21 -0
- package/dist/src/tui/tui-types.js.map +1 -0
- package/dist/src/tui/tui.d.ts +5 -0
- package/dist/src/tui/tui.js +389 -0
- package/dist/src/tui/tui.js.map +1 -0
- package/package.json +5 -3
- package/dist/gateway/static/root/assets/agents-w8_jzuiX.js +0 -216
- package/dist/gateway/static/root/assets/agents-w8_jzuiX.js.map +0 -1
- package/dist/gateway/static/root/assets/channels-settings-DUKRPC7C.js +0 -9
- package/dist/gateway/static/root/assets/channels-settings-DUKRPC7C.js.map +0 -1
- package/dist/gateway/static/root/assets/cron-page-S18t1yG-.js +0 -2
- package/dist/gateway/static/root/assets/cron-page-S18t1yG-.js.map +0 -1
- package/dist/gateway/static/root/assets/index-OT4cGzon.css +0 -1
- package/dist/gateway/static/root/assets/index-PfkB8N37.js +0 -4734
- package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js.map +0 -1
- package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js +0 -2
- package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js.map +0 -1
- package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js.map +0 -1
- package/dist/gateway/static/root/assets/skills-page-BmBDCEbY.js +0 -3
|
@@ -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;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//#region src/tui/tui-stdio-filter.ts
|
|
2
|
+
/**
|
|
3
|
+
* While pi-tui owns the screen, strip whole-line pino JSON from stdout/stderr.
|
|
4
|
+
* ANSI chunks must pass through immediately (see runTui).
|
|
5
|
+
*
|
|
6
|
+
* Longer-term (openclaw-style): prefer routing logger transports away from stdout when the TUI
|
|
7
|
+
* is active (e.g. file-only + explicit subsystem tags) instead of patching stdio here.
|
|
8
|
+
*/
|
|
9
|
+
function isLikelyPinoJsonLogLine(line) {
|
|
10
|
+
const t = line.trim();
|
|
11
|
+
if (!t.startsWith("{")) return false;
|
|
12
|
+
try {
|
|
13
|
+
return typeof JSON.parse(t).level === "number";
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Patch process stdout/stderr; call `restore()` before exiting the TUI. */
|
|
19
|
+
function installTuiStdioFilter() {
|
|
20
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
21
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
22
|
+
let suppressLogs = true;
|
|
23
|
+
const stdoutBuf = { s: "" };
|
|
24
|
+
const stderrBuf = { s: "" };
|
|
25
|
+
const takeEmitCompleteLines = (buf) => {
|
|
26
|
+
let emit = "";
|
|
27
|
+
while (true) {
|
|
28
|
+
const idx = buf.s.indexOf("\n");
|
|
29
|
+
if (idx === -1) break;
|
|
30
|
+
const line = buf.s.slice(0, idx);
|
|
31
|
+
buf.s = buf.s.slice(idx + 1);
|
|
32
|
+
if (!isLikelyPinoJsonLogLine(line)) emit += `${line}\n`;
|
|
33
|
+
}
|
|
34
|
+
return emit;
|
|
35
|
+
};
|
|
36
|
+
const installBufferedLogFilter = (original, buf) => function filteredWrite(chunk, ...rest) {
|
|
37
|
+
if (!suppressLogs) {
|
|
38
|
+
const extra = typeof chunk === "string" ? chunk : chunk instanceof Buffer ? chunk.toString() : "";
|
|
39
|
+
const combined = buf.s ? buf.s + extra : extra;
|
|
40
|
+
buf.s = "";
|
|
41
|
+
return combined.length > 0 ? original(combined, ...rest) : true;
|
|
42
|
+
}
|
|
43
|
+
const text = typeof chunk === "string" ? chunk : chunk instanceof Buffer ? chunk.toString() : "";
|
|
44
|
+
if (text.includes("\x1B")) {
|
|
45
|
+
let emit = takeEmitCompleteLines(buf);
|
|
46
|
+
if (buf.s.length > 0) {
|
|
47
|
+
if (!buf.s.trimStart().startsWith("{")) {
|
|
48
|
+
emit += buf.s;
|
|
49
|
+
buf.s = "";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (emit.length > 0) original(emit, ...rest);
|
|
53
|
+
return original(text, ...rest);
|
|
54
|
+
}
|
|
55
|
+
buf.s += text;
|
|
56
|
+
const emit = takeEmitCompleteLines(buf);
|
|
57
|
+
return emit.length > 0 ? original(emit, ...rest) : true;
|
|
58
|
+
};
|
|
59
|
+
const patch = () => {
|
|
60
|
+
process.stdout.write = installBufferedLogFilter(originalStdoutWrite, stdoutBuf);
|
|
61
|
+
process.stderr.write = installBufferedLogFilter(originalStderrWrite, stderrBuf);
|
|
62
|
+
};
|
|
63
|
+
const flushRemainder = (buf, orig) => {
|
|
64
|
+
const rest = buf.s.trimEnd();
|
|
65
|
+
buf.s = "";
|
|
66
|
+
if (!rest.length) return;
|
|
67
|
+
if (!isLikelyPinoJsonLogLine(rest)) orig(`${rest}\n`);
|
|
68
|
+
};
|
|
69
|
+
const unpatch = () => {
|
|
70
|
+
process.stdout.write = originalStdoutWrite;
|
|
71
|
+
process.stderr.write = originalStderrWrite;
|
|
72
|
+
};
|
|
73
|
+
patch();
|
|
74
|
+
return {
|
|
75
|
+
restore: () => {
|
|
76
|
+
suppressLogs = false;
|
|
77
|
+
flushRemainder(stdoutBuf, originalStdoutWrite);
|
|
78
|
+
flushRemainder(stderrBuf, originalStderrWrite);
|
|
79
|
+
unpatch();
|
|
80
|
+
},
|
|
81
|
+
pause: () => {
|
|
82
|
+
suppressLogs = false;
|
|
83
|
+
flushRemainder(stdoutBuf, originalStdoutWrite);
|
|
84
|
+
flushRemainder(stderrBuf, originalStderrWrite);
|
|
85
|
+
unpatch();
|
|
86
|
+
},
|
|
87
|
+
resume: () => {
|
|
88
|
+
suppressLogs = true;
|
|
89
|
+
patch();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { installTuiStdioFilter, isLikelyPinoJsonLogLine };
|
|
95
|
+
|
|
96
|
+
//# sourceMappingURL=tui-stdio-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-stdio-filter.js","names":[],"sources":["../../../src/tui/tui-stdio-filter.ts"],"sourcesContent":["/**\n * While pi-tui owns the screen, strip whole-line pino JSON from stdout/stderr.\n * ANSI chunks must pass through immediately (see runTui).\n *\n * Longer-term (openclaw-style): prefer routing logger transports away from stdout when the TUI\n * is active (e.g. file-only + explicit subsystem tags) instead of patching stdio here.\n */\n\nexport function isLikelyPinoJsonLogLine(line: string): boolean {\n const t = line.trim();\n if (!t.startsWith('{')) return false;\n try {\n const parsed = JSON.parse(t) as { level?: unknown };\n return typeof parsed.level === 'number';\n } catch {\n return false;\n }\n}\n\nexport type TuiStdioFilterHandle = {\n restore: () => void;\n /** Restore native stdio writes temporarily (e.g. inherited child process). */\n pause: () => void;\n /** Re-apply filtering after `pause()`. */\n resume: () => void;\n};\n\n/** Patch process stdout/stderr; call `restore()` before exiting the TUI. */\nexport function installTuiStdioFilter(): TuiStdioFilterHandle {\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 stdoutBuf = { s: '' };\n const stderrBuf = { s: '' };\n\n const takeEmitCompleteLines = (buf: { s: string }): string => {\n let emit = '';\n while (true) {\n const idx = buf.s.indexOf('\\n');\n if (idx === -1) break;\n const line = buf.s.slice(0, idx);\n buf.s = buf.s.slice(idx + 1);\n if (!isLikelyPinoJsonLogLine(line)) {\n emit += `${line}\\n`;\n }\n }\n return emit;\n };\n\n const installBufferedLogFilter = (\n original: typeof process.stdout.write,\n buf: { s: string },\n ): typeof process.stdout.write =>\n function filteredWrite(chunk: unknown, ...rest: unknown[]): boolean {\n if (!suppressLogs) {\n const extra = typeof chunk === 'string' ? chunk : chunk instanceof Buffer ? chunk.toString() : '';\n const combined = buf.s ? buf.s + extra : extra;\n buf.s = '';\n return combined.length > 0\n ? ((original as Function)(combined, ...rest) as boolean)\n : true;\n }\n const text = typeof chunk === 'string' ? chunk : chunk instanceof Buffer ? chunk.toString() : '';\n if (text.includes('\\x1b')) {\n let emit = takeEmitCompleteLines(buf);\n if (buf.s.length > 0) {\n const tail = buf.s.trimStart();\n if (!tail.startsWith('{')) {\n emit += buf.s;\n buf.s = '';\n }\n }\n if (emit.length > 0) {\n (original as Function)(emit, ...rest);\n }\n return (original as Function)(text, ...rest) as boolean;\n }\n buf.s += text;\n const emit = takeEmitCompleteLines(buf);\n return emit.length > 0 ? ((original as Function)(emit, ...rest) as boolean) : true;\n } as typeof process.stdout.write;\n\n const patch = () => {\n process.stdout.write = installBufferedLogFilter(originalStdoutWrite, stdoutBuf);\n process.stderr.write = installBufferedLogFilter(originalStderrWrite, stderrBuf);\n };\n\n const flushRemainder = (buf: { s: string }, orig: typeof process.stdout.write): void => {\n const rest = buf.s.trimEnd();\n buf.s = '';\n if (!rest.length) return;\n if (!isLikelyPinoJsonLogLine(rest)) {\n orig(`${rest}\\n`);\n }\n };\n\n const unpatch = () => {\n process.stdout.write = originalStdoutWrite;\n process.stderr.write = originalStderrWrite;\n };\n\n patch();\n\n return {\n restore: () => {\n suppressLogs = false;\n flushRemainder(stdoutBuf, originalStdoutWrite);\n flushRemainder(stderrBuf, originalStderrWrite);\n unpatch();\n },\n pause: () => {\n suppressLogs = false;\n flushRemainder(stdoutBuf, originalStdoutWrite);\n flushRemainder(stderrBuf, originalStderrWrite);\n unpatch();\n },\n resume: () => {\n suppressLogs = true;\n patch();\n },\n };\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,wBAAwB,MAAuB;CAC7D,MAAM,IAAI,KAAK,MAAM;AACrB,KAAI,CAAC,EAAE,WAAW,IAAI,CAAE,QAAO;AAC/B,KAAI;AAEF,SAAO,OADQ,KAAK,MAAM,EACN,CAAC,UAAU;SACzB;AACN,SAAO;;;;AAaX,SAAgB,wBAA8C;CAC5D,MAAM,sBAAsB,QAAQ,OAAO,MAAM,KAAK,QAAQ,OAAO;CACrE,MAAM,sBAAsB,QAAQ,OAAO,MAAM,KAAK,QAAQ,OAAO;CACrE,IAAI,eAAe;CAEnB,MAAM,YAAY,EAAE,GAAG,IAAI;CAC3B,MAAM,YAAY,EAAE,GAAG,IAAI;CAE3B,MAAM,yBAAyB,QAA+B;EAC5D,IAAI,OAAO;AACX,SAAO,MAAM;GACX,MAAM,MAAM,IAAI,EAAE,QAAQ,KAAK;AAC/B,OAAI,QAAQ,GAAI;GAChB,MAAM,OAAO,IAAI,EAAE,MAAM,GAAG,IAAI;AAChC,OAAI,IAAI,IAAI,EAAE,MAAM,MAAM,EAAE;AAC5B,OAAI,CAAC,wBAAwB,KAAK,CAChC,SAAQ,GAAG,KAAK;;AAGpB,SAAO;;CAGT,MAAM,4BACJ,UACA,QAEA,SAAS,cAAc,OAAgB,GAAG,MAA0B;AAClE,MAAI,CAAC,cAAc;GACjB,MAAM,QAAQ,OAAO,UAAU,WAAW,QAAQ,iBAAiB,SAAS,MAAM,UAAU,GAAG;GAC/F,MAAM,WAAW,IAAI,IAAI,IAAI,IAAI,QAAQ;AACzC,OAAI,IAAI;AACR,UAAO,SAAS,SAAS,IACnB,SAAsB,UAAU,GAAG,KAAK,GAC1C;;EAEN,MAAM,OAAO,OAAO,UAAU,WAAW,QAAQ,iBAAiB,SAAS,MAAM,UAAU,GAAG;AAC9F,MAAI,KAAK,SAAS,OAAO,EAAE;GACzB,IAAI,OAAO,sBAAsB,IAAI;AACrC,OAAI,IAAI,EAAE,SAAS;QAEb,CADS,IAAI,EAAE,WACV,CAAC,WAAW,IAAI,EAAE;AACzB,aAAQ,IAAI;AACZ,SAAI,IAAI;;;AAGZ,OAAI,KAAK,SAAS,EACf,UAAsB,MAAM,GAAG,KAAK;AAEvC,UAAQ,SAAsB,MAAM,GAAG,KAAK;;AAE9C,MAAI,KAAK;EACT,MAAM,OAAO,sBAAsB,IAAI;AACvC,SAAO,KAAK,SAAS,IAAM,SAAsB,MAAM,GAAG,KAAK,GAAe;;CAGlF,MAAM,cAAc;AAClB,UAAQ,OAAO,QAAQ,yBAAyB,qBAAqB,UAAU;AAC/E,UAAQ,OAAO,QAAQ,yBAAyB,qBAAqB,UAAU;;CAGjF,MAAM,kBAAkB,KAAoB,SAA4C;EACtF,MAAM,OAAO,IAAI,EAAE,SAAS;AAC5B,MAAI,IAAI;AACR,MAAI,CAAC,KAAK,OAAQ;AAClB,MAAI,CAAC,wBAAwB,KAAK,CAChC,MAAK,GAAG,KAAK,IAAI;;CAIrB,MAAM,gBAAgB;AACpB,UAAQ,OAAO,QAAQ;AACvB,UAAQ,OAAO,QAAQ;;AAGzB,QAAO;AAEP,QAAO;EACL,eAAe;AACb,kBAAe;AACf,kBAAe,WAAW,oBAAoB;AAC9C,kBAAe,WAAW,oBAAoB;AAC9C,YAAS;;EAEX,aAAa;AACX,kBAAe;AACf,kBAAe,WAAW,oBAAoB;AAC9C,kBAAe,WAAW,oBAAoB;AAC9C,YAAS;;EAEX,cAAc;AACZ,kBAAe;AACf,UAAO;;EAEV"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Submit handling aligned with openclaw: `!` local shell lines + paste burst coalescing
|
|
3
|
+
* for terminals that split multiline paste into rapid single-line submits.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createEditorSubmitHandler(params: {
|
|
6
|
+
editor: {
|
|
7
|
+
setText: (value: string) => void;
|
|
8
|
+
addToHistory: (value: string) => void;
|
|
9
|
+
};
|
|
10
|
+
handleCommand: (value: string) => void | Promise<void>;
|
|
11
|
+
sendMessage: (value: string) => void | Promise<void>;
|
|
12
|
+
handleBangLine: (value: string) => void | Promise<void>;
|
|
13
|
+
}): (text: string) => void;
|
|
14
|
+
export declare function shouldEnableWindowsGitBashPasteFallback(params?: {
|
|
15
|
+
platform?: string;
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
}): boolean;
|
|
18
|
+
export declare function createSubmitBurstCoalescer(params: {
|
|
19
|
+
submit: (value: string) => void;
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
burstWindowMs?: number;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
setTimer?: typeof setTimeout;
|
|
24
|
+
clearTimer?: typeof clearTimeout;
|
|
25
|
+
}): (value: string) => void;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//#region src/tui/tui-submit.ts
|
|
2
|
+
/**
|
|
3
|
+
* Submit handling aligned with openclaw: `!` local shell lines + paste burst coalescing
|
|
4
|
+
* for terminals that split multiline paste into rapid single-line submits.
|
|
5
|
+
*/
|
|
6
|
+
function normalizeLowercaseStringOrEmpty(value) {
|
|
7
|
+
return value.trim().toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
function createEditorSubmitHandler(params) {
|
|
10
|
+
return (text) => {
|
|
11
|
+
const raw = text;
|
|
12
|
+
const value = raw.trim();
|
|
13
|
+
params.editor.setText("");
|
|
14
|
+
if (!value) return;
|
|
15
|
+
if (raw.startsWith("!") && raw !== "!") {
|
|
16
|
+
params.editor.addToHistory(raw);
|
|
17
|
+
params.handleBangLine(raw);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
params.editor.addToHistory(value);
|
|
21
|
+
if (value.startsWith("/")) {
|
|
22
|
+
params.handleCommand(value);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
params.sendMessage(value);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function shouldEnableWindowsGitBashPasteFallback(params) {
|
|
29
|
+
const platform = params?.platform ?? process.platform;
|
|
30
|
+
const env = params?.env ?? process.env;
|
|
31
|
+
const termProgram = normalizeLowercaseStringOrEmpty(env.TERM_PROGRAM ?? "");
|
|
32
|
+
if (platform === "darwin") {
|
|
33
|
+
if (termProgram.includes("iterm") || termProgram.includes("apple_terminal")) return true;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (platform !== "win32") return false;
|
|
37
|
+
const msystem = (env.MSYSTEM ?? "").toUpperCase();
|
|
38
|
+
const shell = env.SHELL ?? "";
|
|
39
|
+
if (msystem.startsWith("MINGW") || msystem.startsWith("MSYS")) return true;
|
|
40
|
+
if (normalizeLowercaseStringOrEmpty(shell).includes("bash")) return true;
|
|
41
|
+
return termProgram.includes("mintty");
|
|
42
|
+
}
|
|
43
|
+
function createSubmitBurstCoalescer(params) {
|
|
44
|
+
const windowMs = Math.max(1, params.burstWindowMs ?? 50);
|
|
45
|
+
const now = params.now ?? (() => Date.now());
|
|
46
|
+
const setTimer = params.setTimer ?? setTimeout;
|
|
47
|
+
const clearTimer = params.clearTimer ?? clearTimeout;
|
|
48
|
+
let pending = null;
|
|
49
|
+
let pendingAt = 0;
|
|
50
|
+
let flushTimer = null;
|
|
51
|
+
const clearFlushTimer = () => {
|
|
52
|
+
if (!flushTimer) return;
|
|
53
|
+
clearTimer(flushTimer);
|
|
54
|
+
flushTimer = null;
|
|
55
|
+
};
|
|
56
|
+
const flushPending = () => {
|
|
57
|
+
if (pending === null) return;
|
|
58
|
+
const flushed = pending;
|
|
59
|
+
pending = null;
|
|
60
|
+
pendingAt = 0;
|
|
61
|
+
clearFlushTimer();
|
|
62
|
+
params.submit(flushed);
|
|
63
|
+
};
|
|
64
|
+
const scheduleFlush = () => {
|
|
65
|
+
clearFlushTimer();
|
|
66
|
+
flushTimer = setTimer(() => {
|
|
67
|
+
flushPending();
|
|
68
|
+
}, windowMs);
|
|
69
|
+
};
|
|
70
|
+
return (value) => {
|
|
71
|
+
if (!params.enabled) {
|
|
72
|
+
params.submit(value);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (value.includes("\n")) {
|
|
76
|
+
flushPending();
|
|
77
|
+
params.submit(value);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const ts = now();
|
|
81
|
+
if (pending === null) {
|
|
82
|
+
pending = value;
|
|
83
|
+
pendingAt = ts;
|
|
84
|
+
scheduleFlush();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (ts - pendingAt <= windowMs) {
|
|
88
|
+
pending = `${pending}\n${value}`;
|
|
89
|
+
pendingAt = ts;
|
|
90
|
+
scheduleFlush();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
flushPending();
|
|
94
|
+
pending = value;
|
|
95
|
+
pendingAt = ts;
|
|
96
|
+
scheduleFlush();
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { createEditorSubmitHandler, createSubmitBurstCoalescer, shouldEnableWindowsGitBashPasteFallback };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=tui-submit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-submit.js","names":[],"sources":["../../../src/tui/tui-submit.ts"],"sourcesContent":["/**\n * Submit handling aligned with openclaw: `!` local shell lines + paste burst coalescing\n * for terminals that split multiline paste into rapid single-line submits.\n */\n\nfunction normalizeLowercaseStringOrEmpty(value: string): string {\n return value.trim().toLowerCase();\n}\n\nexport function createEditorSubmitHandler(params: {\n editor: {\n setText: (value: string) => void;\n addToHistory: (value: string) => void;\n };\n handleCommand: (value: string) => void | Promise<void>;\n sendMessage: (value: string) => void | Promise<void>;\n handleBangLine: (value: string) => void | Promise<void>;\n}) {\n return (text: string) => {\n const raw = text;\n const value = raw.trim();\n params.editor.setText('');\n\n if (!value) {\n return;\n }\n\n if (raw.startsWith('!') && raw !== '!') {\n params.editor.addToHistory(raw);\n void params.handleBangLine(raw);\n return;\n }\n\n params.editor.addToHistory(value);\n\n if (value.startsWith('/')) {\n void params.handleCommand(value);\n return;\n }\n\n void params.sendMessage(value);\n };\n}\n\nexport function shouldEnableWindowsGitBashPasteFallback(params?: {\n platform?: string;\n env?: NodeJS.ProcessEnv;\n}): boolean {\n const platform = params?.platform ?? process.platform;\n const env = params?.env ?? process.env;\n const termProgram = normalizeLowercaseStringOrEmpty(env.TERM_PROGRAM ?? '');\n\n if (platform === 'darwin') {\n if (termProgram.includes('iterm') || termProgram.includes('apple_terminal')) {\n return true;\n }\n return false;\n }\n\n if (platform !== 'win32') {\n return false;\n }\n\n const msystem = (env.MSYSTEM ?? '').toUpperCase();\n const shell = env.SHELL ?? '';\n if (msystem.startsWith('MINGW') || msystem.startsWith('MSYS')) {\n return true;\n }\n if (normalizeLowercaseStringOrEmpty(shell).includes('bash')) {\n return true;\n }\n return termProgram.includes('mintty');\n}\n\nexport function createSubmitBurstCoalescer(params: {\n submit: (value: string) => void;\n enabled: boolean;\n burstWindowMs?: number;\n now?: () => number;\n setTimer?: typeof setTimeout;\n clearTimer?: typeof clearTimeout;\n}) {\n const windowMs = Math.max(1, params.burstWindowMs ?? 50);\n const now = params.now ?? (() => Date.now());\n const setTimer = params.setTimer ?? setTimeout;\n const clearTimer = params.clearTimer ?? clearTimeout;\n let pending: string | null = null;\n let pendingAt = 0;\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n\n const clearFlushTimer = () => {\n if (!flushTimer) {\n return;\n }\n clearTimer(flushTimer);\n flushTimer = null;\n };\n\n const flushPending = () => {\n if (pending === null) {\n return;\n }\n const flushed = pending;\n pending = null;\n pendingAt = 0;\n clearFlushTimer();\n params.submit(flushed);\n };\n\n const scheduleFlush = () => {\n clearFlushTimer();\n flushTimer = setTimer(() => {\n flushPending();\n }, windowMs);\n };\n\n return (value: string) => {\n if (!params.enabled) {\n params.submit(value);\n return;\n }\n if (value.includes('\\n')) {\n flushPending();\n params.submit(value);\n return;\n }\n const ts = now();\n if (pending === null) {\n pending = value;\n pendingAt = ts;\n scheduleFlush();\n return;\n }\n if (ts - pendingAt <= windowMs) {\n pending = `${pending}\\n${value}`;\n pendingAt = ts;\n scheduleFlush();\n return;\n }\n flushPending();\n pending = value;\n pendingAt = ts;\n scheduleFlush();\n };\n}\n"],"mappings":";;;;;AAKA,SAAS,gCAAgC,OAAuB;AAC9D,QAAO,MAAM,MAAM,CAAC,aAAa;;AAGnC,SAAgB,0BAA0B,QAQvC;AACD,SAAQ,SAAiB;EACvB,MAAM,MAAM;EACZ,MAAM,QAAQ,IAAI,MAAM;AACxB,SAAO,OAAO,QAAQ,GAAG;AAEzB,MAAI,CAAC,MACH;AAGF,MAAI,IAAI,WAAW,IAAI,IAAI,QAAQ,KAAK;AACtC,UAAO,OAAO,aAAa,IAAI;AAC1B,UAAO,eAAe,IAAI;AAC/B;;AAGF,SAAO,OAAO,aAAa,MAAM;AAEjC,MAAI,MAAM,WAAW,IAAI,EAAE;AACpB,UAAO,cAAc,MAAM;AAChC;;AAGG,SAAO,YAAY,MAAM;;;AAIlC,SAAgB,wCAAwC,QAG5C;CACV,MAAM,WAAW,QAAQ,YAAY,QAAQ;CAC7C,MAAM,MAAM,QAAQ,OAAO,QAAQ;CACnC,MAAM,cAAc,gCAAgC,IAAI,gBAAgB,GAAG;AAE3E,KAAI,aAAa,UAAU;AACzB,MAAI,YAAY,SAAS,QAAQ,IAAI,YAAY,SAAS,iBAAiB,CACzE,QAAO;AAET,SAAO;;AAGT,KAAI,aAAa,QACf,QAAO;CAGT,MAAM,WAAW,IAAI,WAAW,IAAI,aAAa;CACjD,MAAM,QAAQ,IAAI,SAAS;AAC3B,KAAI,QAAQ,WAAW,QAAQ,IAAI,QAAQ,WAAW,OAAO,CAC3D,QAAO;AAET,KAAI,gCAAgC,MAAM,CAAC,SAAS,OAAO,CACzD,QAAO;AAET,QAAO,YAAY,SAAS,SAAS;;AAGvC,SAAgB,2BAA2B,QAOxC;CACD,MAAM,WAAW,KAAK,IAAI,GAAG,OAAO,iBAAiB,GAAG;CACxD,MAAM,MAAM,OAAO,cAAc,KAAK,KAAK;CAC3C,MAAM,WAAW,OAAO,YAAY;CACpC,MAAM,aAAa,OAAO,cAAc;CACxC,IAAI,UAAyB;CAC7B,IAAI,YAAY;CAChB,IAAI,aAAmD;CAEvD,MAAM,wBAAwB;AAC5B,MAAI,CAAC,WACH;AAEF,aAAW,WAAW;AACtB,eAAa;;CAGf,MAAM,qBAAqB;AACzB,MAAI,YAAY,KACd;EAEF,MAAM,UAAU;AAChB,YAAU;AACV,cAAY;AACZ,mBAAiB;AACjB,SAAO,OAAO,QAAQ;;CAGxB,MAAM,sBAAsB;AAC1B,mBAAiB;AACjB,eAAa,eAAe;AAC1B,iBAAc;KACb,SAAS;;AAGd,SAAQ,UAAkB;AACxB,MAAI,CAAC,OAAO,SAAS;AACnB,UAAO,OAAO,MAAM;AACpB;;AAEF,MAAI,MAAM,SAAS,KAAK,EAAE;AACxB,iBAAc;AACd,UAAO,OAAO,MAAM;AACpB;;EAEF,MAAM,KAAK,KAAK;AAChB,MAAI,YAAY,MAAM;AACpB,aAAU;AACV,eAAY;AACZ,kBAAe;AACf;;AAEF,MAAI,KAAK,aAAa,UAAU;AAC9B,aAAU,GAAG,QAAQ,IAAI;AACzB,eAAY;AACZ,kBAAe;AACf;;AAEF,gBAAc;AACd,YAAU;AACV,cAAY;AACZ,iBAAe"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type DrainableTui } from './tui-lifecycle.js';
|
|
2
|
+
type StartableTui = DrainableTui & {
|
|
3
|
+
start: () => void;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Pause the TUI (drain stdin + stop), run async work with a normal terminal, then restart.
|
|
7
|
+
* Caller should refocus the editor after `start()` if needed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function withTuiSuspended<T>(tui: StartableTui, work: () => Promise<T>): Promise<T>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { drainAndStopTuiSafely } from "./tui-lifecycle.js";
|
|
2
|
+
//#region src/tui/tui-suspend.ts
|
|
3
|
+
/**
|
|
4
|
+
* Pause the TUI (drain stdin + stop), run async work with a normal terminal, then restart.
|
|
5
|
+
* Caller should refocus the editor after `start()` if needed.
|
|
6
|
+
*/
|
|
7
|
+
async function withTuiSuspended(tui, work) {
|
|
8
|
+
await drainAndStopTuiSafely(tui);
|
|
9
|
+
try {
|
|
10
|
+
return await work();
|
|
11
|
+
} finally {
|
|
12
|
+
tui.start();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { withTuiSuspended };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=tui-suspend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui-suspend.js","names":[],"sources":["../../../src/tui/tui-suspend.ts"],"sourcesContent":["import { drainAndStopTuiSafely, type DrainableTui } from './tui-lifecycle.js';\n\ntype StartableTui = DrainableTui & { start: () => void };\n\n/**\n * Pause the TUI (drain stdin + stop), run async work with a normal terminal, then restart.\n * Caller should refocus the editor after `start()` if needed.\n */\nexport async function withTuiSuspended<T>(tui: StartableTui, work: () => Promise<T>): Promise<T> {\n await drainAndStopTuiSafely(tui);\n try {\n return await work();\n } finally {\n tui.start();\n }\n}\n"],"mappings":";;;;;;AAQA,eAAsB,iBAAoB,KAAmB,MAAoC;AAC/F,OAAM,sBAAsB,IAAI;AAChC,KAAI;AACF,SAAO,MAAM,MAAM;WACX;AACR,MAAI,OAAO"}
|