p-spore 0.1.0
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/README.md +97 -0
- package/dist/agents/agent-manager.d.ts +57 -0
- package/dist/agents/agent-manager.d.ts.map +1 -0
- package/dist/agents/agent-manager.js +559 -0
- package/dist/agents/agent-manager.js.map +1 -0
- package/dist/agents/agent-tools.d.ts +4 -0
- package/dist/agents/agent-tools.d.ts.map +1 -0
- package/dist/agents/agent-tools.js +111 -0
- package/dist/agents/agent-tools.js.map +1 -0
- package/dist/agents/types.d.ts +52 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/application/agent-application.d.ts +89 -0
- package/dist/application/agent-application.d.ts.map +1 -0
- package/dist/application/agent-application.js +542 -0
- package/dist/application/agent-application.js.map +1 -0
- package/dist/automation/automation-tools.d.ts +4 -0
- package/dist/automation/automation-tools.d.ts.map +1 -0
- package/dist/automation/automation-tools.js +35 -0
- package/dist/automation/automation-tools.js.map +1 -0
- package/dist/automation/cron-adapter.d.ts +11 -0
- package/dist/automation/cron-adapter.d.ts.map +1 -0
- package/dist/automation/cron-adapter.js +48 -0
- package/dist/automation/cron-adapter.js.map +1 -0
- package/dist/automation/daemon-run-executor.d.ts +18 -0
- package/dist/automation/daemon-run-executor.d.ts.map +1 -0
- package/dist/automation/daemon-run-executor.js +69 -0
- package/dist/automation/daemon-run-executor.js.map +1 -0
- package/dist/automation/job-store.d.ts +12 -0
- package/dist/automation/job-store.d.ts.map +1 -0
- package/dist/automation/job-store.js +185 -0
- package/dist/automation/job-store.js.map +1 -0
- package/dist/automation/run-ledger.d.ts +25 -0
- package/dist/automation/run-ledger.d.ts.map +1 -0
- package/dist/automation/run-ledger.js +148 -0
- package/dist/automation/run-ledger.js.map +1 -0
- package/dist/automation/scheduler.d.ts +36 -0
- package/dist/automation/scheduler.d.ts.map +1 -0
- package/dist/automation/scheduler.js +300 -0
- package/dist/automation/scheduler.js.map +1 -0
- package/dist/automation/types.d.ts +81 -0
- package/dist/automation/types.d.ts.map +1 -0
- package/dist/automation/types.js +2 -0
- package/dist/automation/types.js.map +1 -0
- package/dist/cli/main.d.ts +4 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +768 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/config/config.d.ts +52 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +345 -0
- package/dist/config/config.js.map +1 -0
- package/dist/daemon/daemon.d.ts +30 -0
- package/dist/daemon/daemon.d.ts.map +1 -0
- package/dist/daemon/daemon.js +437 -0
- package/dist/daemon/daemon.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +18 -0
- package/dist/daemon/supervisor.d.ts.map +1 -0
- package/dist/daemon/supervisor.js +37 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/desktop/daemon-bridge.d.ts +32 -0
- package/dist/desktop/daemon-bridge.d.ts.map +1 -0
- package/dist/desktop/daemon-bridge.js +204 -0
- package/dist/desktop/daemon-bridge.js.map +1 -0
- package/dist/desktop/daemon-supervisor.d.ts +2 -0
- package/dist/desktop/daemon-supervisor.d.ts.map +1 -0
- package/dist/desktop/daemon-supervisor.js +2 -0
- package/dist/desktop/daemon-supervisor.js.map +1 -0
- package/dist/desktop/ipc-contract.d.ts +153 -0
- package/dist/desktop/ipc-contract.d.ts.map +1 -0
- package/dist/desktop/ipc-contract.js +219 -0
- package/dist/desktop/ipc-contract.js.map +1 -0
- package/dist/desktop/main.d.ts +2 -0
- package/dist/desktop/main.d.ts.map +1 -0
- package/dist/desktop/main.js +594 -0
- package/dist/desktop/main.js.map +1 -0
- package/dist/desktop/preload.cjs +32 -0
- package/dist/desktop/renderer/index.html +284 -0
- package/dist/desktop/renderer/renderer.d.ts +2 -0
- package/dist/desktop/renderer/renderer.d.ts.map +1 -0
- package/dist/desktop/renderer/renderer.js +895 -0
- package/dist/desktop/renderer/renderer.js.map +1 -0
- package/dist/desktop/renderer/store.d.ts +33 -0
- package/dist/desktop/renderer/store.d.ts.map +1 -0
- package/dist/desktop/renderer/store.js +144 -0
- package/dist/desktop/renderer/store.js.map +1 -0
- package/dist/desktop/renderer/styles.css +540 -0
- package/dist/desktop/security.d.ts +5 -0
- package/dist/desktop/security.d.ts.map +1 -0
- package/dist/desktop/security.js +49 -0
- package/dist/desktop/security.js.map +1 -0
- package/dist/desktop/smoke-fixture.d.ts +3 -0
- package/dist/desktop/smoke-fixture.d.ts.map +1 -0
- package/dist/desktop/smoke-fixture.js +223 -0
- package/dist/desktop/smoke-fixture.js.map +1 -0
- package/dist/desktop/validation.d.ts +8 -0
- package/dist/desktop/validation.d.ts.map +1 -0
- package/dist/desktop/validation.js +341 -0
- package/dist/desktop/validation.js.map +1 -0
- package/dist/domain/runtime.d.ts +116 -0
- package/dist/domain/runtime.d.ts.map +1 -0
- package/dist/domain/runtime.js +2 -0
- package/dist/domain/runtime.js.map +1 -0
- package/dist/gateway/local-gateway.d.ts +23 -0
- package/dist/gateway/local-gateway.d.ts.map +1 -0
- package/dist/gateway/local-gateway.js +265 -0
- package/dist/gateway/local-gateway.js.map +1 -0
- package/dist/kernel/loop.d.ts +10 -0
- package/dist/kernel/loop.d.ts.map +1 -0
- package/dist/kernel/loop.js +41 -0
- package/dist/kernel/loop.js.map +1 -0
- package/dist/kernel/reducer.d.ts +3 -0
- package/dist/kernel/reducer.d.ts.map +1 -0
- package/dist/kernel/reducer.js +102 -0
- package/dist/kernel/reducer.js.map +1 -0
- package/dist/kernel/state.d.ts +93 -0
- package/dist/kernel/state.d.ts.map +1 -0
- package/dist/kernel/state.js +15 -0
- package/dist/kernel/state.js.map +1 -0
- package/dist/mcp/mcp-manager.d.ts +55 -0
- package/dist/mcp/mcp-manager.d.ts.map +1 -0
- package/dist/mcp/mcp-manager.js +591 -0
- package/dist/mcp/mcp-manager.js.map +1 -0
- package/dist/mcp/mcp-tools.d.ts +4 -0
- package/dist/mcp/mcp-tools.d.ts.map +1 -0
- package/dist/mcp/mcp-tools.js +53 -0
- package/dist/mcp/mcp-tools.js.map +1 -0
- package/dist/mcp/types.d.ts +73 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/memory/bm25.d.ts +14 -0
- package/dist/memory/bm25.d.ts.map +1 -0
- package/dist/memory/bm25.js +40 -0
- package/dist/memory/bm25.js.map +1 -0
- package/dist/memory/context-loader.d.ts +26 -0
- package/dist/memory/context-loader.d.ts.map +1 -0
- package/dist/memory/context-loader.js +97 -0
- package/dist/memory/context-loader.js.map +1 -0
- package/dist/memory/memory-service.d.ts +30 -0
- package/dist/memory/memory-service.d.ts.map +1 -0
- package/dist/memory/memory-service.js +133 -0
- package/dist/memory/memory-service.js.map +1 -0
- package/dist/memory/memory-tools.d.ts +4 -0
- package/dist/memory/memory-tools.d.ts.map +1 -0
- package/dist/memory/memory-tools.js +49 -0
- package/dist/memory/memory-tools.js.map +1 -0
- package/dist/memory/search-service.d.ts +38 -0
- package/dist/memory/search-service.d.ts.map +1 -0
- package/dist/memory/search-service.js +463 -0
- package/dist/memory/search-service.js.map +1 -0
- package/dist/memory/tokenizer.d.ts +5 -0
- package/dist/memory/tokenizer.d.ts.map +1 -0
- package/dist/memory/tokenizer.js +42 -0
- package/dist/memory/tokenizer.js.map +1 -0
- package/dist/notifications/notification-service.d.ts +47 -0
- package/dist/notifications/notification-service.d.ts.map +1 -0
- package/dist/notifications/notification-service.js +163 -0
- package/dist/notifications/notification-service.js.map +1 -0
- package/dist/notifications/notification-tools.d.ts +4 -0
- package/dist/notifications/notification-tools.d.ts.map +1 -0
- package/dist/notifications/notification-tools.js +31 -0
- package/dist/notifications/notification-tools.js.map +1 -0
- package/dist/pi/pi-runtime-adapter.d.ts +38 -0
- package/dist/pi/pi-runtime-adapter.d.ts.map +1 -0
- package/dist/pi/pi-runtime-adapter.js +483 -0
- package/dist/pi/pi-runtime-adapter.js.map +1 -0
- package/dist/plugins/extension-manager.d.ts +32 -0
- package/dist/plugins/extension-manager.d.ts.map +1 -0
- package/dist/plugins/extension-manager.js +562 -0
- package/dist/plugins/extension-manager.js.map +1 -0
- package/dist/plugins/resource-inspector.d.ts +18 -0
- package/dist/plugins/resource-inspector.d.ts.map +1 -0
- package/dist/plugins/resource-inspector.js +386 -0
- package/dist/plugins/resource-inspector.js.map +1 -0
- package/dist/plugins/types.d.ts +96 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +2 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/policy/approval-service.d.ts +68 -0
- package/dist/policy/approval-service.d.ts.map +1 -0
- package/dist/policy/approval-service.js +349 -0
- package/dist/policy/approval-service.js.map +1 -0
- package/dist/policy/policy.d.ts +26 -0
- package/dist/policy/policy.d.ts.map +1 -0
- package/dist/policy/policy.js +228 -0
- package/dist/policy/policy.js.map +1 -0
- package/dist/protocol/client.d.ts +25 -0
- package/dist/protocol/client.d.ts.map +1 -0
- package/dist/protocol/client.js +188 -0
- package/dist/protocol/client.js.map +1 -0
- package/dist/protocol/errors.d.ts +5 -0
- package/dist/protocol/errors.d.ts.map +1 -0
- package/dist/protocol/errors.js +9 -0
- package/dist/protocol/errors.js.map +1 -0
- package/dist/protocol/schema.d.ts +71 -0
- package/dist/protocol/schema.d.ts.map +1 -0
- package/dist/protocol/schema.js +155 -0
- package/dist/protocol/schema.js.map +1 -0
- package/dist/settings/llm-settings-service.d.ts +9 -0
- package/dist/settings/llm-settings-service.d.ts.map +1 -0
- package/dist/settings/llm-settings-service.js +199 -0
- package/dist/settings/llm-settings-service.js.map +1 -0
- package/dist/settings/llm-settings.d.ts +29 -0
- package/dist/settings/llm-settings.d.ts.map +1 -0
- package/dist/settings/llm-settings.js +113 -0
- package/dist/settings/llm-settings.js.map +1 -0
- package/dist/shell/shell-service.d.ts +88 -0
- package/dist/shell/shell-service.d.ts.map +1 -0
- package/dist/shell/shell-service.js +459 -0
- package/dist/shell/shell-service.js.map +1 -0
- package/dist/storage/atomic.d.ts +4 -0
- package/dist/storage/atomic.d.ts.map +1 -0
- package/dist/storage/atomic.js +49 -0
- package/dist/storage/atomic.js.map +1 -0
- package/dist/storage/daemon-lock.d.ts +17 -0
- package/dist/storage/daemon-lock.d.ts.map +1 -0
- package/dist/storage/daemon-lock.js +97 -0
- package/dist/storage/daemon-lock.js.map +1 -0
- package/dist/storage/home.d.ts +29 -0
- package/dist/storage/home.d.ts.map +1 -0
- package/dist/storage/home.js +108 -0
- package/dist/storage/home.js.map +1 -0
- package/dist/storage/jsonl.d.ts +2 -0
- package/dist/storage/jsonl.d.ts.map +1 -0
- package/dist/storage/jsonl.js +45 -0
- package/dist/storage/jsonl.js.map +1 -0
- package/dist/tools/core-tools.d.ts +16 -0
- package/dist/tools/core-tools.d.ts.map +1 -0
- package/dist/tools/core-tools.js +283 -0
- package/dist/tools/core-tools.js.map +1 -0
- package/dist/ui/shared/presentation.d.ts +46 -0
- package/dist/ui/shared/presentation.d.ts.map +1 -0
- package/dist/ui/shared/presentation.js +81 -0
- package/dist/ui/shared/presentation.js.map +1 -0
- package/dist/ui/shared/protocol-state.d.ts +30 -0
- package/dist/ui/shared/protocol-state.d.ts.map +1 -0
- package/dist/ui/shared/protocol-state.js +165 -0
- package/dist/ui/shared/protocol-state.js.map +1 -0
- package/dist/ui/tui/commands.d.ts +2 -0
- package/dist/ui/tui/commands.d.ts.map +1 -0
- package/dist/ui/tui/commands.js +33 -0
- package/dist/ui/tui/commands.js.map +1 -0
- package/dist/ui/tui/store.d.ts +32 -0
- package/dist/ui/tui/store.d.ts.map +1 -0
- package/dist/ui/tui/store.js +62 -0
- package/dist/ui/tui/store.js.map +1 -0
- package/dist/ui/tui/tui-app.d.ts +21 -0
- package/dist/ui/tui/tui-app.d.ts.map +1 -0
- package/dist/ui/tui/tui-app.js +643 -0
- package/dist/ui/tui/tui-app.js.map +1 -0
- package/dist/ui/tui/view.d.ts +29 -0
- package/dist/ui/tui/view.d.ts.map +1 -0
- package/dist/ui/tui/view.js +255 -0
- package/dist/ui/tui/view.js.map +1 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { ProcessTerminal, TuiAltScreen } from "@earendil-works/pi-tui";
|
|
3
|
+
import { ProtocolClient } from "../../protocol/client.js";
|
|
4
|
+
import { decodeLlmSettingsUpdate, defaultModelFor, MODEL_APIS, PROVIDER_OPTIONS, THINKING_LEVELS, } from "../../settings/llm-settings.js";
|
|
5
|
+
import { TuiStore } from "./store.js";
|
|
6
|
+
import { TUI_HELP_LINES } from "./commands.js";
|
|
7
|
+
import { PsporeTuiView } from "./view.js";
|
|
8
|
+
import { conversationIntent, selectedAgent } from "../shared/presentation.js";
|
|
9
|
+
export class PsporeTuiApp {
|
|
10
|
+
#options;
|
|
11
|
+
#store = new TuiStore();
|
|
12
|
+
#terminal = new ProcessTerminal();
|
|
13
|
+
#tui = new TuiAltScreen(this.#terminal, true);
|
|
14
|
+
#clientId;
|
|
15
|
+
#client;
|
|
16
|
+
#stopping = false;
|
|
17
|
+
#retryTimer;
|
|
18
|
+
#retryMs = 250;
|
|
19
|
+
#lastInterruptAt = 0;
|
|
20
|
+
#resolveExit;
|
|
21
|
+
#view;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.#options = options;
|
|
24
|
+
this.#clientId = options.clientId ?? `tui-${randomUUID()}`;
|
|
25
|
+
}
|
|
26
|
+
async run() {
|
|
27
|
+
const view = new PsporeTuiView({
|
|
28
|
+
state: () => this.#store.snapshot(),
|
|
29
|
+
height: () => this.#terminal.rows,
|
|
30
|
+
requestRender: () => this.#tui.requestRender(),
|
|
31
|
+
onSubmit: (input) => void this.#submit(input),
|
|
32
|
+
onInterrupt: () => void this.#interrupt(),
|
|
33
|
+
onBack: () => this.#store.clearPanel(),
|
|
34
|
+
});
|
|
35
|
+
this.#view = view;
|
|
36
|
+
this.#tui.setLayoutRoot(view);
|
|
37
|
+
this.#tui.setFocus(view);
|
|
38
|
+
this.#tui.start();
|
|
39
|
+
void this.#connect();
|
|
40
|
+
try {
|
|
41
|
+
await new Promise((resolve) => {
|
|
42
|
+
this.#resolveExit = resolve;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
this.#stopping = true;
|
|
47
|
+
if (this.#retryTimer)
|
|
48
|
+
clearTimeout(this.#retryTimer);
|
|
49
|
+
await this.#client?.close().catch(() => undefined);
|
|
50
|
+
this.#tui.stop();
|
|
51
|
+
this.#view = undefined;
|
|
52
|
+
await this.#terminal.drainInput(250, 25);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async #connect() {
|
|
56
|
+
if (this.#stopping)
|
|
57
|
+
return;
|
|
58
|
+
const client = new ProtocolClient({
|
|
59
|
+
socketPath: this.#options.socketPath,
|
|
60
|
+
clientId: this.#clientId,
|
|
61
|
+
capabilities: ["tui", "approval"],
|
|
62
|
+
cursors: { ...this.#store.snapshot().cursors },
|
|
63
|
+
});
|
|
64
|
+
this.#client = client;
|
|
65
|
+
client.subscribe((event) => {
|
|
66
|
+
this.#store.applyEvent(event);
|
|
67
|
+
if (this.#store.snapshot().gap) {
|
|
68
|
+
void client.close();
|
|
69
|
+
}
|
|
70
|
+
this.#tui.requestRender();
|
|
71
|
+
});
|
|
72
|
+
client.subscribeConnection((connected, error) => {
|
|
73
|
+
if (connected)
|
|
74
|
+
return;
|
|
75
|
+
this.#store.disconnected(error?.message ?? "Disconnected");
|
|
76
|
+
this.#tui.requestRender();
|
|
77
|
+
this.#scheduleReconnect();
|
|
78
|
+
});
|
|
79
|
+
try {
|
|
80
|
+
const hello = await client.connect();
|
|
81
|
+
if (this.#client !== client) {
|
|
82
|
+
await client.close();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.#store.applyHello(hello);
|
|
86
|
+
this.#retryMs = 250;
|
|
87
|
+
this.#tui.requestRender(true);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
this.#store.disconnected(`Daemon unavailable: ${toError(error).message}`);
|
|
91
|
+
this.#tui.requestRender();
|
|
92
|
+
this.#scheduleReconnect();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
#scheduleReconnect() {
|
|
96
|
+
if (this.#stopping || this.#retryTimer)
|
|
97
|
+
return;
|
|
98
|
+
const delay = this.#retryMs;
|
|
99
|
+
this.#retryMs = Math.min(this.#retryMs * 2, 5_000);
|
|
100
|
+
this.#retryTimer = setTimeout(() => {
|
|
101
|
+
this.#retryTimer = undefined;
|
|
102
|
+
void this.#connect();
|
|
103
|
+
}, delay);
|
|
104
|
+
this.#retryTimer.unref();
|
|
105
|
+
}
|
|
106
|
+
async #submit(input) {
|
|
107
|
+
try {
|
|
108
|
+
if (input === "/quit") {
|
|
109
|
+
this.#exit();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (input === "/help") {
|
|
113
|
+
this.#store.setPanel("Command palette", TUI_HELP_LINES);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (input === "/back") {
|
|
117
|
+
this.#store.clearPanel();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (input === "/config" || input.startsWith("/config ")) {
|
|
121
|
+
await this.#config(input);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (input === "/abort") {
|
|
125
|
+
const child = this.#selectedAgent();
|
|
126
|
+
if (child)
|
|
127
|
+
await this.#command("agent.cancel", { agentId: child.id, reason: "cancelled from TUI" });
|
|
128
|
+
else
|
|
129
|
+
await this.#command("session.abort", { reason: "TUI user requested abort" }, "main");
|
|
130
|
+
this.#store.setNotice(child ? `Agent ${child.id} cancelled` : "Abort requested");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (input === "/compact") {
|
|
134
|
+
if (this.#selectedAgent())
|
|
135
|
+
throw new Error("Compaction is available only for the main session");
|
|
136
|
+
await this.#command("session.compact", {}, "main");
|
|
137
|
+
this.#store.setNotice("Compaction requested");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (input === "/jobs") {
|
|
141
|
+
const jobs = await this.#command("jobs.list", {});
|
|
142
|
+
this.#store.setPanel("Automation jobs", jobs.map((job) => (`• ${job.id} · ${job.enabled ? "enabled" : "disabled"}${job.schedule?.cron ? ` · ${job.schedule.cron} ${job.schedule.timezone ?? ""}` : ""}`)));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (input === "/runs") {
|
|
146
|
+
const runs = await this.#command("runs.list", {});
|
|
147
|
+
this.#store.setPanel("Automation runs", runs.slice().reverse().map((run) => (`• ${run.id} · ${run.jobId ?? "manual"} · ${run.status}${run.reason ? ` · ${run.reason}` : run.error ? ` · ${run.error}` : ""}`)));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (input === "/notifications") {
|
|
151
|
+
const notifications = await this.#command("notifications.list", {});
|
|
152
|
+
this.#store.setPanel("Notifications", notifications.slice().reverse().map((notification) => (`• ${notification.level.toUpperCase()} · ${notification.title}${notification.body ? ` — ${notification.body}` : ""}`)));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (input === "/agents") {
|
|
156
|
+
const agents = await this.#command("agents.list", {});
|
|
157
|
+
this.#store.setPanel("Agent tree", agents.map((agent) => (`${" ".repeat(agent.depth)}${agent.depth > 0 ? "↳" : "•"} ${agent.id} · ${agent.status}${agent.objective ? ` · ${agent.objective}` : ""}`)));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (input === "/mcp") {
|
|
161
|
+
const servers = await this.#command("mcp.servers", {});
|
|
162
|
+
this.#store.setPanel("MCP servers", servers.map((server) => (`• ${server.serverId} · ${server.status} · ${server.toolCount} tools`)));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (input === "/mcp-tools") {
|
|
166
|
+
const tools = await this.#command("mcp.tools", {});
|
|
167
|
+
this.#store.setPanel("MCP tools", tools.map((tool) => `• ${tool.normalizedName}${tool.description ? ` — ${tool.description}` : ""}`));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (input.startsWith("/mcp-discover ")) {
|
|
171
|
+
const serverId = input.slice(14).trim();
|
|
172
|
+
if (!serverId)
|
|
173
|
+
throw new Error("Usage: /mcp-discover SERVER_ID");
|
|
174
|
+
const tools = await this.#command("mcp.discover", { serverId });
|
|
175
|
+
this.#store.setPanel(`MCP tools · ${serverId}`, tools.map((tool) => `• ${tool.normalizedName}`));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (input.startsWith("/mcp-ping ")) {
|
|
179
|
+
const serverId = input.slice(10).trim();
|
|
180
|
+
if (!serverId)
|
|
181
|
+
throw new Error("Usage: /mcp-ping SERVER_ID");
|
|
182
|
+
const server = await this.#command("mcp.ping", { serverId });
|
|
183
|
+
this.#store.setPanel(`MCP ping · ${serverId}`, [
|
|
184
|
+
`status ${server.status}`,
|
|
185
|
+
`tools ${server.toolCount}`,
|
|
186
|
+
...(server.error ? [`error ${server.error}`] : []),
|
|
187
|
+
]);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (input === "/resources" || input === "/skills" || input === "/plugins") {
|
|
191
|
+
const command = input === "/skills" ? "skills.list" : input === "/plugins" ? "plugins.list" : "resources.list";
|
|
192
|
+
const resources = await this.#command(command, {});
|
|
193
|
+
this.#store.setPanel(input === "/skills" ? "Skills" : input === "/plugins" ? "Host plugins" : "Trusted resources", resources.map((resource) => (`• ${resource.record.kind} · ${resource.record.name} · ${resource.health.status}`)));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (input.startsWith("/resource-inspect ")) {
|
|
197
|
+
const stagingPath = input.slice(18).trim();
|
|
198
|
+
if (!stagingPath)
|
|
199
|
+
throw new Error("Usage: /resource-inspect STAGING_PATH");
|
|
200
|
+
const inspected = await this.#command("resources.inspect", { stagingPath });
|
|
201
|
+
this.#store.setNotice(`${inspected.kind}:${inspected.name} ${inspected.installable ? "installable" : "blocked"} sha256:${inspected.digest}`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (input.startsWith("/resource-install ")) {
|
|
205
|
+
const parts = input.slice(18).trim().split(/\s+/);
|
|
206
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
207
|
+
throw new Error("Usage: /resource-install STAGING_PATH SHA256");
|
|
208
|
+
}
|
|
209
|
+
const installed = await this.#command("resources.install", {
|
|
210
|
+
stagingPath: parts[0],
|
|
211
|
+
expectedDigest: parts[1],
|
|
212
|
+
});
|
|
213
|
+
this.#store.setNotice(`Installed ${installed.record.kind}:${installed.record.name} (${installed.health.status})`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (input.startsWith("/resource-disable ")) {
|
|
217
|
+
const parts = input.slice(18).trim().split(/\s+/);
|
|
218
|
+
if (parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) {
|
|
219
|
+
throw new Error("Usage: /resource-disable KIND NAME SHA256");
|
|
220
|
+
}
|
|
221
|
+
const disabled = await this.#command("resources.disable", {
|
|
222
|
+
kind: parts[0],
|
|
223
|
+
name: parts[1],
|
|
224
|
+
expectedDigest: parts[2],
|
|
225
|
+
});
|
|
226
|
+
this.#store.setNotice(`Disabled ${disabled.record.kind}:${disabled.record.name} (${disabled.health.status})`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (input.startsWith("/select ")) {
|
|
230
|
+
const selected = this.#store.selectSession(input.slice(8).trim());
|
|
231
|
+
this.#store.setNotice(`Selected session ${selected}`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (input.startsWith("/agent-spawn ")) {
|
|
235
|
+
const objective = input.slice(13).trim();
|
|
236
|
+
if (!objective)
|
|
237
|
+
throw new Error("Usage: /agent-spawn OBJECTIVE");
|
|
238
|
+
const agent = await this.#command("agent.spawn", {
|
|
239
|
+
parentId: "main",
|
|
240
|
+
objective,
|
|
241
|
+
workspaceMode: "shared-read",
|
|
242
|
+
budget: {
|
|
243
|
+
maxTurns: 8,
|
|
244
|
+
maxToolCalls: 24,
|
|
245
|
+
maxInputTokens: 50_000,
|
|
246
|
+
maxOutputTokens: 8_000,
|
|
247
|
+
maxChildren: 0,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
this.#store.setNotice(`Agent ${agent.id}: ${agent.status}`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (input.startsWith("/agent-join ")) {
|
|
254
|
+
const agentIds = input.slice(12).trim().split(/\s+/).filter(Boolean);
|
|
255
|
+
if (agentIds.length === 0)
|
|
256
|
+
throw new Error("Usage: /agent-join AGENT_ID...");
|
|
257
|
+
const agents = await this.#command("agent.join", { agentIds, timeoutMs: 300_000 });
|
|
258
|
+
this.#store.setNotice(agents.map((agent) => `${agent.id}:${agent.status}`).join(" "));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (input.startsWith("/agent-cancel ")) {
|
|
262
|
+
const agent = await this.#command("agent.cancel", { agentId: input.slice(14).trim() });
|
|
263
|
+
this.#store.setNotice(`Agent ${agent.id}: ${agent.status}`);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (input.startsWith("/agent ")) {
|
|
267
|
+
const separator = input.indexOf(" ", 7);
|
|
268
|
+
if (separator < 0)
|
|
269
|
+
throw new Error("Usage: /agent AGENT_ID MESSAGE");
|
|
270
|
+
const agentId = input.slice(7, separator).trim();
|
|
271
|
+
const message = input.slice(separator + 1).trim();
|
|
272
|
+
if (!agentId || !message)
|
|
273
|
+
throw new Error("Usage: /agent AGENT_ID MESSAGE");
|
|
274
|
+
await this.#command("agent.send", { agentId, message, mode: "next" });
|
|
275
|
+
this.#store.setNotice(`Message queued for ${agentId}`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (input.startsWith("/run ")) {
|
|
279
|
+
const run = await this.#command("job.run", { jobId: input.slice(5).trim() });
|
|
280
|
+
this.#store.setNotice(`Run ${run.id}: ${run.status}`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (input.startsWith("/cancel ")) {
|
|
284
|
+
const run = await this.#command("run.cancel", { runId: input.slice(8).trim() });
|
|
285
|
+
this.#store.setNotice(`Run ${run.id}: ${run.status}`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (input.startsWith("/steer ")) {
|
|
289
|
+
await this.#prompt(input.slice(7), "steer");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (input.startsWith("/follow ")) {
|
|
293
|
+
await this.#prompt(input.slice(8), "follow_up");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (input.startsWith("/approve") || input.startsWith("/deny")) {
|
|
297
|
+
await this.#approval(input);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const child = this.#selectedAgent();
|
|
301
|
+
const main = this.#store.snapshot().kernel.sessions["main"];
|
|
302
|
+
if (!child && main?.status === "running") {
|
|
303
|
+
this.#store.setNotice("Session is running; use /steer TEXT or /follow TEXT explicitly");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
await this.#prompt(input, "prompt");
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.#store.setNotice(`Error: ${toError(error).message}`);
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
this.#tui.requestRender();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async #prompt(text, mode) {
|
|
316
|
+
const snapshot = this.#store.snapshot();
|
|
317
|
+
const intent = conversationIntent(snapshot.kernel, snapshot.selectedSessionId, text, mode);
|
|
318
|
+
if (intent.target === "agent") {
|
|
319
|
+
await this.#command("agent.send", {
|
|
320
|
+
agentId: intent.agentId,
|
|
321
|
+
message: intent.text,
|
|
322
|
+
mode: intent.mode,
|
|
323
|
+
});
|
|
324
|
+
this.#store.setNotice(`${intent.mode} queued for ${intent.agentId}`);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
await this.#command("session.prompt", { text: intent.text, mode: intent.mode }, intent.sessionId);
|
|
328
|
+
this.#store.setNotice(`${mode} accepted for main`);
|
|
329
|
+
}
|
|
330
|
+
async #approval(input) {
|
|
331
|
+
const parts = input.split(/\s+/);
|
|
332
|
+
const approved = parts[0] === "/approve";
|
|
333
|
+
const pending = Object.values(this.#store.snapshot().kernel.approvals);
|
|
334
|
+
const approvalId = parts[1] ?? (pending.length === 1 ? pending[0]?.id : undefined);
|
|
335
|
+
if (!approvalId)
|
|
336
|
+
throw new Error("Specify an approval ID");
|
|
337
|
+
const scope = (parts[2] ?? "once");
|
|
338
|
+
if (!["once", "session", "job"].includes(scope))
|
|
339
|
+
throw new Error("Approval scope must be once, session, or job");
|
|
340
|
+
const result = await this.#command("approval.resolve", { approvalId, approved, scope });
|
|
341
|
+
if (result.status !== "resolved")
|
|
342
|
+
throw new Error(`Approval was not resolved: ${result.status}`);
|
|
343
|
+
this.#store.setNotice(approved ? "Approval granted" : "Approval denied");
|
|
344
|
+
}
|
|
345
|
+
async #config(input) {
|
|
346
|
+
const settings = this.#options.settings;
|
|
347
|
+
if (!settings)
|
|
348
|
+
throw new Error("Model settings are unavailable in this TUI session");
|
|
349
|
+
const parts = input.trim().split(/\s+/);
|
|
350
|
+
const action = parts[1] ?? "show";
|
|
351
|
+
if (action === "show") {
|
|
352
|
+
if (parts.length > 2)
|
|
353
|
+
throw new Error("Usage: /config show");
|
|
354
|
+
this.#showSettings(await settings.get());
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (action === "wizard") {
|
|
358
|
+
if (parts.length > 2)
|
|
359
|
+
throw new Error("Usage: /config wizard");
|
|
360
|
+
await this.#settingsWizard();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const current = await settings.get();
|
|
364
|
+
if (action === "set") {
|
|
365
|
+
const provider = parseProvider(parts[2]);
|
|
366
|
+
if (provider === "custom-openai") {
|
|
367
|
+
const model = parts[3];
|
|
368
|
+
const baseUrl = parts[4];
|
|
369
|
+
if (!model || !baseUrl) {
|
|
370
|
+
throw new Error("Usage: /config set custom-openai MODEL BASE_URL [chat|responses] [THINKING]");
|
|
371
|
+
}
|
|
372
|
+
const api = parseOpenAiApi(parts[5] ?? "chat");
|
|
373
|
+
const thinking = parseThinking(parts[6] ?? current.thinking);
|
|
374
|
+
await this.#saveSettings(updateFrom(current, { provider, model, baseUrl, api, thinking, customModel: true }));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const model = parts[3] ?? defaultModelFor(provider);
|
|
378
|
+
const thinking = parseThinking(parts[4] ?? current.thinking);
|
|
379
|
+
if (parts.length > 5)
|
|
380
|
+
throw new Error("Usage: /config set PROVIDER [MODEL] [THINKING]");
|
|
381
|
+
await this.#saveSettings(updateFrom(current, {
|
|
382
|
+
provider,
|
|
383
|
+
model,
|
|
384
|
+
thinking,
|
|
385
|
+
baseUrl: "",
|
|
386
|
+
api: "provider-default",
|
|
387
|
+
customModel: false,
|
|
388
|
+
}));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (action === "provider") {
|
|
392
|
+
const provider = parseProvider(parts[2]);
|
|
393
|
+
if (provider === "custom-openai")
|
|
394
|
+
throw new Error("Use /config endpoint BASE_URL [chat|responses] for custom-openai");
|
|
395
|
+
if (parts.length !== 3)
|
|
396
|
+
throw new Error("Usage: /config provider deepseek|openai");
|
|
397
|
+
await this.#saveSettings(updateFrom(current, {
|
|
398
|
+
provider,
|
|
399
|
+
model: defaultModelFor(provider),
|
|
400
|
+
baseUrl: "",
|
|
401
|
+
api: "provider-default",
|
|
402
|
+
customModel: false,
|
|
403
|
+
}));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (action === "model") {
|
|
407
|
+
const model = parts[2];
|
|
408
|
+
if (!model || parts.length !== 3)
|
|
409
|
+
throw new Error("Usage: /config model MODEL");
|
|
410
|
+
await this.#saveSettings(updateFrom(current, { model }));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (action === "endpoint") {
|
|
414
|
+
const baseUrl = parts[2];
|
|
415
|
+
if (!baseUrl || parts.length > 4)
|
|
416
|
+
throw new Error("Usage: /config endpoint BASE_URL [chat|responses]");
|
|
417
|
+
await this.#saveSettings(updateFrom(current, {
|
|
418
|
+
provider: "custom-openai",
|
|
419
|
+
model: current.provider === "custom-openai" ? current.model : defaultModelFor("custom-openai"),
|
|
420
|
+
baseUrl,
|
|
421
|
+
api: parseOpenAiApi(parts[3] ?? "chat"),
|
|
422
|
+
customModel: true,
|
|
423
|
+
}));
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (action === "thinking") {
|
|
427
|
+
if (parts.length !== 3)
|
|
428
|
+
throw new Error(`Usage: /config thinking ${THINKING_LEVELS.join("|")}`);
|
|
429
|
+
await this.#saveSettings(updateFrom(current, { thinking: parseThinking(parts[2]) }));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (action === "key") {
|
|
433
|
+
if (parts.length !== 2)
|
|
434
|
+
throw new Error("API keys are never accepted as command arguments; use /config key");
|
|
435
|
+
const apiKey = await this.#view?.requestValue("API key (hidden; Esc cancels)", { secret: true });
|
|
436
|
+
if (apiKey === undefined) {
|
|
437
|
+
this.#store.setNotice("API key update cancelled");
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (!apiKey)
|
|
441
|
+
throw new Error("API key cannot be empty");
|
|
442
|
+
await this.#saveSettings(updateFrom(current, { apiKey }));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (action === "clear-key") {
|
|
446
|
+
if (parts.length !== 2)
|
|
447
|
+
throw new Error("Usage: /config clear-key");
|
|
448
|
+
await this.#saveSettings(updateFrom(current, { clearApiKey: true }));
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
throw new Error("Unknown config action; use /config, /config wizard, or /help");
|
|
452
|
+
}
|
|
453
|
+
async #settingsWizard() {
|
|
454
|
+
const settingsPort = this.#options.settings;
|
|
455
|
+
const view = this.#view;
|
|
456
|
+
if (!settingsPort || !view)
|
|
457
|
+
throw new Error("Settings wizard is unavailable");
|
|
458
|
+
const current = await settingsPort.get();
|
|
459
|
+
this.#store.setPanel("Model setup wizard", [
|
|
460
|
+
"1 Provider — DeepSeek, OpenAI, or custom OpenAI",
|
|
461
|
+
"2 Model",
|
|
462
|
+
"3 Endpoint/API when custom",
|
|
463
|
+
"4 Thinking level",
|
|
464
|
+
"5 API key action",
|
|
465
|
+
"6 Review and apply",
|
|
466
|
+
"",
|
|
467
|
+
"Press Esc at any prompt to cancel without saving.",
|
|
468
|
+
]);
|
|
469
|
+
const providerValue = await view.requestValue(`1/6 provider [${current.provider}] (1 deepseek · 2 openai · 3 custom)`);
|
|
470
|
+
if (providerValue === undefined)
|
|
471
|
+
return this.#cancelWizard();
|
|
472
|
+
const provider = providerValue.trim() ? parseProvider(providerValue) : current.provider;
|
|
473
|
+
const suggestedModel = provider === current.provider ? current.model : defaultModelFor(provider);
|
|
474
|
+
const modelValue = await view.requestValue(`2/6 model [${suggestedModel}]`);
|
|
475
|
+
if (modelValue === undefined)
|
|
476
|
+
return this.#cancelWizard();
|
|
477
|
+
const model = modelValue.trim() || suggestedModel;
|
|
478
|
+
let baseUrl = "";
|
|
479
|
+
let api = "provider-default";
|
|
480
|
+
if (provider === "custom-openai") {
|
|
481
|
+
const baseUrlValue = await view.requestValue(`3/6 Base URL [${current.provider === provider ? current.baseUrl : "required"}]`);
|
|
482
|
+
if (baseUrlValue === undefined)
|
|
483
|
+
return this.#cancelWizard();
|
|
484
|
+
baseUrl = baseUrlValue.trim() || (current.provider === provider ? current.baseUrl : "");
|
|
485
|
+
if (!baseUrl)
|
|
486
|
+
throw new Error("A Base URL is required for custom OpenAI");
|
|
487
|
+
const apiValue = await view.requestValue("3/6 API [chat] (chat · responses)");
|
|
488
|
+
if (apiValue === undefined)
|
|
489
|
+
return this.#cancelWizard();
|
|
490
|
+
api = parseOpenAiApi(apiValue.trim() || (current.provider === provider ? current.api : "chat"));
|
|
491
|
+
}
|
|
492
|
+
const thinkingValue = await view.requestValue(`4/6 thinking [${current.thinking}] (${THINKING_LEVELS.join(" · ")})`);
|
|
493
|
+
if (thinkingValue === undefined)
|
|
494
|
+
return this.#cancelWizard();
|
|
495
|
+
const thinking = parseThinking(thinkingValue.trim() || current.thinking);
|
|
496
|
+
const keyActionValue = await view.requestValue(`5/6 key [keep] (keep · set · clear)`);
|
|
497
|
+
if (keyActionValue === undefined)
|
|
498
|
+
return this.#cancelWizard();
|
|
499
|
+
const keyAction = keyActionValue.trim().toLowerCase() || "keep";
|
|
500
|
+
if (!["keep", "set", "clear"].includes(keyAction)) {
|
|
501
|
+
throw new Error("Key action must be keep, set, or clear");
|
|
502
|
+
}
|
|
503
|
+
let apiKey;
|
|
504
|
+
if (keyAction === "set") {
|
|
505
|
+
apiKey = await view.requestValue("5/6 API key (hidden; Esc cancels)", { secret: true });
|
|
506
|
+
if (apiKey === undefined)
|
|
507
|
+
return this.#cancelWizard();
|
|
508
|
+
if (!apiKey)
|
|
509
|
+
throw new Error("API key cannot be empty");
|
|
510
|
+
}
|
|
511
|
+
this.#store.setPanel("Review model settings", [
|
|
512
|
+
`Provider ${provider}`,
|
|
513
|
+
`Model ${model}`,
|
|
514
|
+
`Base URL ${baseUrl || "provider default"}`,
|
|
515
|
+
`API ${api}`,
|
|
516
|
+
`Thinking ${thinking}`,
|
|
517
|
+
`API key ${keyAction === "keep" ? current.hasApiKey ? "keep configured key" : "not configured" : keyAction}`,
|
|
518
|
+
"",
|
|
519
|
+
"The key value is never displayed or stored in the TUI state.",
|
|
520
|
+
]);
|
|
521
|
+
const confirmation = await view.requestValue("6/6 save and apply? [y/N]");
|
|
522
|
+
if (confirmation === undefined || !/^(y|yes)$/i.test(confirmation.trim()))
|
|
523
|
+
return this.#cancelWizard();
|
|
524
|
+
await this.#saveSettings(updateFrom(current, {
|
|
525
|
+
provider,
|
|
526
|
+
model,
|
|
527
|
+
baseUrl,
|
|
528
|
+
api,
|
|
529
|
+
thinking,
|
|
530
|
+
customModel: provider === "custom-openai",
|
|
531
|
+
...(apiKey ? { apiKey } : {}),
|
|
532
|
+
clearApiKey: keyAction === "clear",
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
async #saveSettings(update) {
|
|
536
|
+
const settingsPort = this.#options.settings;
|
|
537
|
+
if (!settingsPort)
|
|
538
|
+
throw new Error("Model settings are unavailable");
|
|
539
|
+
this.#store.setNotice("Validating model settings…");
|
|
540
|
+
const saved = await settingsPort.save(decodeLlmSettingsUpdate(update, "TUI LLM settings"));
|
|
541
|
+
const applied = await this.#options.onSettingsSaved?.();
|
|
542
|
+
this.#showSettings(saved, applied?.message ?? "Settings saved; restart the daemon to apply them");
|
|
543
|
+
}
|
|
544
|
+
#showSettings(settings, message = "Model settings") {
|
|
545
|
+
this.#store.setPanel("Model settings", settingsLines(settings));
|
|
546
|
+
this.#store.setNotice(message);
|
|
547
|
+
}
|
|
548
|
+
#cancelWizard() {
|
|
549
|
+
this.#store.clearPanel();
|
|
550
|
+
this.#store.setNotice("Settings wizard cancelled; nothing was changed");
|
|
551
|
+
}
|
|
552
|
+
#selectedAgent() {
|
|
553
|
+
const snapshot = this.#store.snapshot();
|
|
554
|
+
return selectedAgent(snapshot.kernel, snapshot.selectedSessionId);
|
|
555
|
+
}
|
|
556
|
+
async #command(type, payload, sessionId) {
|
|
557
|
+
const client = this.#client;
|
|
558
|
+
if (!client?.hello)
|
|
559
|
+
throw new Error("Daemon is not connected");
|
|
560
|
+
return client.command(type, payload, sessionId === undefined ? {} : { sessionId });
|
|
561
|
+
}
|
|
562
|
+
async #interrupt() {
|
|
563
|
+
const running = this.#store.snapshot().kernel.sessions["main"]?.status === "running";
|
|
564
|
+
if (running) {
|
|
565
|
+
await this.#submit("/abort");
|
|
566
|
+
this.#lastInterruptAt = Date.now();
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const now = Date.now();
|
|
570
|
+
if (now - this.#lastInterruptAt < 1_500) {
|
|
571
|
+
this.#exit();
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
this.#lastInterruptAt = now;
|
|
575
|
+
this.#store.setNotice("Press Ctrl+C again to exit cleanly");
|
|
576
|
+
this.#tui.requestRender();
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
#exit() {
|
|
580
|
+
this.#stopping = true;
|
|
581
|
+
this.#resolveExit?.();
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function toError(error) {
|
|
585
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
586
|
+
}
|
|
587
|
+
function parseProvider(value) {
|
|
588
|
+
const normalized = value?.trim().toLowerCase();
|
|
589
|
+
const aliases = {
|
|
590
|
+
"1": "deepseek",
|
|
591
|
+
deepseek: "deepseek",
|
|
592
|
+
"2": "openai",
|
|
593
|
+
openai: "openai",
|
|
594
|
+
"3": "custom-openai",
|
|
595
|
+
custom: "custom-openai",
|
|
596
|
+
"custom-openai": "custom-openai",
|
|
597
|
+
};
|
|
598
|
+
const provider = normalized ? aliases[normalized] : undefined;
|
|
599
|
+
if (!provider || !PROVIDER_OPTIONS.includes(provider)) {
|
|
600
|
+
throw new Error("Provider must be deepseek, openai, or custom-openai");
|
|
601
|
+
}
|
|
602
|
+
return provider;
|
|
603
|
+
}
|
|
604
|
+
function parseThinking(value) {
|
|
605
|
+
const normalized = value?.trim().toLowerCase();
|
|
606
|
+
if (!normalized || !THINKING_LEVELS.includes(normalized)) {
|
|
607
|
+
throw new Error(`Thinking must be one of: ${THINKING_LEVELS.join(", ")}`);
|
|
608
|
+
}
|
|
609
|
+
return normalized;
|
|
610
|
+
}
|
|
611
|
+
function parseOpenAiApi(value) {
|
|
612
|
+
const normalized = value.trim().toLowerCase();
|
|
613
|
+
const api = normalized === "chat" ? "openai-completions" : normalized === "responses" ? "openai-responses" : normalized;
|
|
614
|
+
if (!MODEL_APIS.includes(api) || (api !== "openai-completions" && api !== "openai-responses")) {
|
|
615
|
+
throw new Error("Custom OpenAI API must be chat/openai-completions or responses/openai-responses");
|
|
616
|
+
}
|
|
617
|
+
return api;
|
|
618
|
+
}
|
|
619
|
+
function updateFrom(settings, changes) {
|
|
620
|
+
return {
|
|
621
|
+
provider: changes.provider ?? settings.provider,
|
|
622
|
+
model: changes.model ?? settings.model,
|
|
623
|
+
baseUrl: changes.baseUrl ?? settings.baseUrl,
|
|
624
|
+
api: changes.api ?? settings.api,
|
|
625
|
+
thinking: changes.thinking ?? settings.thinking,
|
|
626
|
+
customModel: changes.customModel ?? settings.customModel,
|
|
627
|
+
...(changes.apiKey ? { apiKey: changes.apiKey } : {}),
|
|
628
|
+
clearApiKey: changes.clearApiKey ?? false,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
function settingsLines(settings) {
|
|
632
|
+
return [
|
|
633
|
+
`Provider ${settings.provider}`,
|
|
634
|
+
`Model ${settings.model}`,
|
|
635
|
+
`Base URL ${settings.baseUrl || "provider default"}`,
|
|
636
|
+
`API ${settings.api}`,
|
|
637
|
+
`Thinking ${settings.thinking}`,
|
|
638
|
+
`API key ${settings.hasApiKey ? "configured (hidden)" : "not configured"}`,
|
|
639
|
+
"",
|
|
640
|
+
"Use /config wizard for guided setup, or /help for direct commands.",
|
|
641
|
+
];
|
|
642
|
+
}
|
|
643
|
+
//# sourceMappingURL=tui-app.js.map
|