@nodetool-ai/cli 0.7.0-rc.11
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/ExecutionTree.d.ts +19 -0
- package/dist/ExecutionTree.d.ts.map +1 -0
- package/dist/ExecutionTree.js +100 -0
- package/dist/ExecutionTree.js.map +1 -0
- package/dist/app.d.ts +50 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +767 -0
- package/dist/app.js.map +1 -0
- package/dist/commands/agent.d.ts +11 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +483 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/deploy-helpers.d.ts +46 -0
- package/dist/commands/deploy-helpers.d.ts.map +1 -0
- package/dist/commands/deploy-helpers.js +448 -0
- package/dist/commands/deploy-helpers.js.map +1 -0
- package/dist/commands/deploy.d.ts +8 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +645 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/models-hf.d.ts +9 -0
- package/dist/commands/models-hf.d.ts.map +1 -0
- package/dist/commands/models-hf.js +235 -0
- package/dist/commands/models-hf.js.map +1 -0
- package/dist/commands/models-recommended.d.ts +10 -0
- package/dist/commands/models-recommended.d.ts.map +1 -0
- package/dist/commands/models-recommended.js +128 -0
- package/dist/commands/models-recommended.js.map +1 -0
- package/dist/commands/package-helpers.d.ts +14 -0
- package/dist/commands/package-helpers.d.ts.map +1 -0
- package/dist/commands/package-helpers.js +76 -0
- package/dist/commands/package-helpers.js.map +1 -0
- package/dist/commands/package.d.ts +3 -0
- package/dist/commands/package.d.ts.map +1 -0
- package/dist/commands/package.js +254 -0
- package/dist/commands/package.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +233 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +9 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +48 -0
- package/dist/markdown.js.map +1 -0
- package/dist/nodetool.d.ts +16 -0
- package/dist/nodetool.d.ts.map +1 -0
- package/dist/nodetool.js +1154 -0
- package/dist/nodetool.js.map +1 -0
- package/dist/providers.d.ts +47 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +173 -0
- package/dist/providers.js.map +1 -0
- package/dist/readline-input.d.ts +29 -0
- package/dist/readline-input.d.ts.map +1 -0
- package/dist/readline-input.js +199 -0
- package/dist/readline-input.js.map +1 -0
- package/dist/run-dsl.d.ts +11 -0
- package/dist/run-dsl.d.ts.map +1 -0
- package/dist/run-dsl.js +31 -0
- package/dist/run-dsl.js.map +1 -0
- package/dist/settings.d.ts +18 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +86 -0
- package/dist/settings.js.map +1 -0
- package/dist/stdin.d.ts +48 -0
- package/dist/stdin.d.ts.map +1 -0
- package/dist/stdin.js +352 -0
- package/dist/stdin.js.map +1 -0
- package/dist/useExecutionState.d.ts +44 -0
- package/dist/useExecutionState.d.ts.map +1 -0
- package/dist/useExecutionState.js +281 -0
- package/dist/useExecutionState.js.map +1 -0
- package/dist/websocket-client.d.ts +100 -0
- package/dist/websocket-client.d.ts.map +1 -0
- package/dist/websocket-client.js +351 -0
- package/dist/websocket-client.js.map +1 -0
- package/package.json +85 -0
package/dist/app.js
ADDED
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* NodeTool Chat CLI — Ink-based terminal UI.
|
|
4
|
+
*
|
|
5
|
+
* Layout:
|
|
6
|
+
* ╭─ nodetool ─────────────────── provider · model ─╮
|
|
7
|
+
* │ │
|
|
8
|
+
* │ You: message │
|
|
9
|
+
* │ │
|
|
10
|
+
* │ response with markdown │
|
|
11
|
+
* │ │
|
|
12
|
+
* │ ◆ tool_name result preview │
|
|
13
|
+
* │ │
|
|
14
|
+
* ├──────────────────────────────────────────────────┤
|
|
15
|
+
* │ › input │
|
|
16
|
+
* ╰──────────────────────────────────────────────────╯
|
|
17
|
+
*/
|
|
18
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
19
|
+
import { Box, Text, Static, useApp, useInput } from "ink";
|
|
20
|
+
import ReadlineInput from "./readline-input.js";
|
|
21
|
+
import Spinner from "ink-spinner";
|
|
22
|
+
import { ExecutionTree } from "./ExecutionTree.js";
|
|
23
|
+
import { useExecutionState } from "./useExecutionState.js";
|
|
24
|
+
import { ProcessingContext } from "@nodetool-ai/runtime";
|
|
25
|
+
import { processChat } from "@nodetool-ai/chat";
|
|
26
|
+
import { MultiModeAgent } from "@nodetool-ai/agents";
|
|
27
|
+
import { ReadFileTool, WriteFileTool, ListDirectoryTool, EditFileTool, GlobTool, GrepTool, DownloadFileTool, HttpRequestTool, GoogleSearchTool, GoogleNewsTool, GoogleImagesTool, BrowserTool, ScreenshotTool, RunCodeTool, CalculatorTool, ExtractPDFTextTool, ConvertPDFToMarkdownTool, ConvertDocumentTool, StatisticsTool, GeometryTool, ConversionTool, OpenAIWebSearchTool, OpenAIImageGenerationTool, OpenAITextToSpeechTool, DataForSEOSearchTool, DataForSEONewsTool, SearchEmailTool, ArchiveEmailTool, getAllMcpTools, } from "@nodetool-ai/agents";
|
|
28
|
+
import { createProvider, DEFAULT_MODELS, KNOWN_PROVIDERS, WebSocketProvider } from "./providers.js";
|
|
29
|
+
import { WebSocketChatClient } from "./websocket-client.js";
|
|
30
|
+
import { renderMarkdown } from "./markdown.js";
|
|
31
|
+
import { saveSettings } from "./settings.js";
|
|
32
|
+
import { getSecret } from "@nodetool-ai/models";
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Command definitions
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
const COMMANDS = {
|
|
37
|
+
"/help": "Show available commands",
|
|
38
|
+
"/clear": "Clear conversation history",
|
|
39
|
+
"/model": "Set model: /model <model-id>",
|
|
40
|
+
"/provider": "Set provider: /provider <name>",
|
|
41
|
+
"/agent": "Toggle agent mode",
|
|
42
|
+
"/planner": "Set agent planner: /planner graph|multi",
|
|
43
|
+
"/tools": "List enabled tools",
|
|
44
|
+
"/exit": "Exit the chat",
|
|
45
|
+
"/quit": "Exit the chat",
|
|
46
|
+
};
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Individual message rendering
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
function UserMessage({ content }) {
|
|
51
|
+
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "magenta", dimColor: true, bold: true, children: "❯ " }), _jsx(Text, { bold: true, inverse: true, children: " " + content + " " })] }));
|
|
52
|
+
}
|
|
53
|
+
function AssistantMessage({ content, rendered }) {
|
|
54
|
+
return (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "green", children: "● " }), _jsx(Text, { children: rendered ?? content })] }));
|
|
55
|
+
}
|
|
56
|
+
function ToolMessage({ toolName, toolArgs, content }) {
|
|
57
|
+
const argsStr = toolArgs
|
|
58
|
+
? Object.entries(toolArgs)
|
|
59
|
+
.map(([k, v]) => `${k}: ${JSON.stringify(v)}`)
|
|
60
|
+
.join(", ")
|
|
61
|
+
: "";
|
|
62
|
+
const preview = content.split("\n").slice(0, 3).join(" ").slice(0, 200);
|
|
63
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "● " }), _jsx(Text, { bold: true, children: toolName }), argsStr ? _jsxs(Text, { color: "gray", dimColor: true, children: ["(", argsStr, ")"] }) : null] }), preview ? (_jsxs(Box, { marginLeft: 2, children: [_jsx(Text, { color: "gray", dimColor: true, children: "└ " }), _jsx(Text, { color: "gray", dimColor: true, children: preview })] })) : null] }));
|
|
64
|
+
}
|
|
65
|
+
function SystemMessage({ content }) {
|
|
66
|
+
return (_jsx(Box, { children: _jsx(Text, { color: "gray", dimColor: true, children: " " + content }) }));
|
|
67
|
+
}
|
|
68
|
+
function ChatMessageItem({ msg }) {
|
|
69
|
+
switch (msg.role) {
|
|
70
|
+
case "user": return _jsx(UserMessage, { content: msg.content });
|
|
71
|
+
case "assistant": return _jsx(AssistantMessage, { content: msg.content, rendered: msg.rendered });
|
|
72
|
+
case "tool": return _jsx(ToolMessage, { toolName: msg.toolName ?? "tool", toolArgs: msg.toolArgs, content: msg.content });
|
|
73
|
+
case "system": return _jsx(SystemMessage, { content: msg.content });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Autocomplete menu
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
const CMD_WIDTH = 14;
|
|
80
|
+
function AutocompleteMenu({ matches, selectedIndex, }) {
|
|
81
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: 2, children: matches.map(({ cmd, desc }, i) => {
|
|
82
|
+
const selected = i === selectedIndex;
|
|
83
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: selected ? "cyan" : "gray", bold: selected, children: [selected ? "› " : " ", cmd.padEnd(CMD_WIDTH)] }), _jsx(Text, { color: "gray", dimColor: true, children: desc })] }, cmd));
|
|
84
|
+
}) }));
|
|
85
|
+
}
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Help panel
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
function HelpPanel() {
|
|
90
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, marginBottom: 1, children: [_jsx(Text, { color: "gray", dimColor: true, bold: true, children: "Commands" }), Object.entries(COMMANDS).map(([cmd, desc]) => (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: (" " + cmd).padEnd(16) }), _jsx(Text, { color: "gray", dimColor: true, children: desc })] }, cmd))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: " \u2191\u2193 history \u00B7 Tab complete \u00B7 Ctrl+C exit" }) })] }));
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Main App
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
export function App({ initialProvider, initialModel, initialAgentMode, initialAgentPlanner = "graph", enabledTools, workspaceDir, wsUrl, extraTools, registry, agentProviders, }) {
|
|
96
|
+
const { exit } = useApp();
|
|
97
|
+
// --- State ---
|
|
98
|
+
const [provider, setProvider] = useState(initialProvider);
|
|
99
|
+
const [model, setModel] = useState(initialModel);
|
|
100
|
+
const [agentMode, setAgentMode] = useState(initialAgentMode);
|
|
101
|
+
const [agentPlanner, setAgentPlanner] = useState(initialAgentPlanner);
|
|
102
|
+
const [messages, setMessages] = useState([]);
|
|
103
|
+
const [chatHistory, setChatHistory] = useState([]);
|
|
104
|
+
const [inputValue, setInputValue] = useState("");
|
|
105
|
+
const [, setInputHistory] = useState([]);
|
|
106
|
+
const [, setHistoryIndex] = useState(-1);
|
|
107
|
+
const [historyDraft, setHistoryDraft] = useState("");
|
|
108
|
+
const [streaming, setStreaming] = useState(false);
|
|
109
|
+
const [streamContent, setStreamContent] = useState("");
|
|
110
|
+
const [streamLabel, setStreamLabel] = useState("");
|
|
111
|
+
// Buffer messages during streaming to avoid Ink <Static> artifacts
|
|
112
|
+
const pendingMessagesRef = useRef([]);
|
|
113
|
+
const streamingRef = useRef(false);
|
|
114
|
+
const [error, setError] = useState(null);
|
|
115
|
+
const [showHelp, setShowHelp] = useState(false);
|
|
116
|
+
const execState = useExecutionState();
|
|
117
|
+
const [acIndex, setAcIndex] = useState(0);
|
|
118
|
+
const [modelList, setModelList] = useState([]);
|
|
119
|
+
// Fetch available models when provider changes
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
let cancelled = false;
|
|
122
|
+
createProvider(provider).then(async (prov) => {
|
|
123
|
+
try {
|
|
124
|
+
const models = await prov.getAvailableLanguageModels();
|
|
125
|
+
if (!cancelled) {
|
|
126
|
+
setModelList(models.map((m) => ({ id: m.id, name: m.name })));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
if (!cancelled)
|
|
131
|
+
setModelList([]);
|
|
132
|
+
}
|
|
133
|
+
}).catch(() => {
|
|
134
|
+
if (!cancelled)
|
|
135
|
+
setModelList([]);
|
|
136
|
+
});
|
|
137
|
+
return () => { cancelled = true; };
|
|
138
|
+
}, [provider]);
|
|
139
|
+
// WebSocket client state (when --url is passed)
|
|
140
|
+
const wsClientRef = useRef(null);
|
|
141
|
+
const [threadId, setThreadId] = useState(() => crypto.randomUUID());
|
|
142
|
+
// Refs to hold latest values without causing re-renders in async callbacks
|
|
143
|
+
const chatHistoryRef = useRef(chatHistory);
|
|
144
|
+
const providerRef = useRef(provider);
|
|
145
|
+
const modelRef = useRef(model);
|
|
146
|
+
const agentModeRef = useRef(agentMode);
|
|
147
|
+
const agentPlannerRef = useRef(agentPlanner);
|
|
148
|
+
const abortRef = useRef(false);
|
|
149
|
+
const abortControllerRef = useRef(null);
|
|
150
|
+
// Guard against double-submit: useInput autocomplete Enter + TextInput onSubmit fire for the same keypress
|
|
151
|
+
const submittingRef = useRef(false);
|
|
152
|
+
useEffect(() => { chatHistoryRef.current = chatHistory; }, [chatHistory]);
|
|
153
|
+
useEffect(() => { providerRef.current = provider; }, [provider]);
|
|
154
|
+
useEffect(() => { modelRef.current = model; }, [model]);
|
|
155
|
+
useEffect(() => { agentModeRef.current = agentMode; }, [agentMode]);
|
|
156
|
+
useEffect(() => { agentPlannerRef.current = agentPlanner; }, [agentPlanner]);
|
|
157
|
+
useEffect(() => { setAcIndex(0); }, [inputValue]);
|
|
158
|
+
// Connect WebSocket when --url is provided
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (!wsUrl)
|
|
161
|
+
return;
|
|
162
|
+
const client = new WebSocketChatClient(wsUrl);
|
|
163
|
+
client.connect().then(() => {
|
|
164
|
+
wsClientRef.current = client;
|
|
165
|
+
}).catch((err) => {
|
|
166
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
167
|
+
addMessage("system", `WebSocket connection failed: ${msg}`);
|
|
168
|
+
});
|
|
169
|
+
return () => {
|
|
170
|
+
client.disconnect();
|
|
171
|
+
wsClientRef.current = null;
|
|
172
|
+
};
|
|
173
|
+
}, [wsUrl]);
|
|
174
|
+
// Unique ID generator
|
|
175
|
+
const nextId = useRef(0);
|
|
176
|
+
const genId = () => `msg-${++nextId.current}`;
|
|
177
|
+
// Add a message to the display history.
|
|
178
|
+
// During streaming, tool/system messages are buffered to avoid Ink <Static> artifacts.
|
|
179
|
+
// User and assistant messages (which mark start/end of a turn) are added immediately.
|
|
180
|
+
const addMessage = useCallback(async (role, content, opts) => {
|
|
181
|
+
let rendered;
|
|
182
|
+
if (role === "assistant") {
|
|
183
|
+
rendered = await renderMarkdown(content);
|
|
184
|
+
}
|
|
185
|
+
const msg = {
|
|
186
|
+
id: genId(),
|
|
187
|
+
role,
|
|
188
|
+
content,
|
|
189
|
+
rendered,
|
|
190
|
+
toolName: opts?.toolName,
|
|
191
|
+
toolArgs: opts?.toolArgs,
|
|
192
|
+
};
|
|
193
|
+
if (streamingRef.current && role !== "user" && role !== "assistant") {
|
|
194
|
+
pendingMessagesRef.current.push(msg);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
setMessages(prev => [...prev, msg]);
|
|
198
|
+
}
|
|
199
|
+
}, []);
|
|
200
|
+
// Flush buffered messages when streaming ends
|
|
201
|
+
const flushPendingMessages = useCallback(() => {
|
|
202
|
+
if (pendingMessagesRef.current.length > 0) {
|
|
203
|
+
const pending = pendingMessagesRef.current;
|
|
204
|
+
pendingMessagesRef.current = [];
|
|
205
|
+
setMessages(prev => [...prev, ...pending]);
|
|
206
|
+
}
|
|
207
|
+
}, []);
|
|
208
|
+
// Create tools from enabled list
|
|
209
|
+
function buildTools() {
|
|
210
|
+
const toolMap = {
|
|
211
|
+
// Filesystem
|
|
212
|
+
read_file: new ReadFileTool(),
|
|
213
|
+
write_file: new WriteFileTool(),
|
|
214
|
+
edit_file: new EditFileTool(),
|
|
215
|
+
list_directory: new ListDirectoryTool(),
|
|
216
|
+
glob: new GlobTool(),
|
|
217
|
+
grep: new GrepTool(),
|
|
218
|
+
// HTTP
|
|
219
|
+
download_file: new DownloadFileTool(),
|
|
220
|
+
http_request: new HttpRequestTool(),
|
|
221
|
+
// Search (SERPAPI)
|
|
222
|
+
google_search: new GoogleSearchTool(),
|
|
223
|
+
google_news: new GoogleNewsTool(),
|
|
224
|
+
google_images: new GoogleImagesTool(),
|
|
225
|
+
// Browser
|
|
226
|
+
browser: new BrowserTool(),
|
|
227
|
+
screenshot: new ScreenshotTool(),
|
|
228
|
+
// Code & math
|
|
229
|
+
run_code: new RunCodeTool(),
|
|
230
|
+
calculator: new CalculatorTool(),
|
|
231
|
+
statistics: new StatisticsTool(),
|
|
232
|
+
geometry: new GeometryTool(),
|
|
233
|
+
conversion: new ConversionTool(),
|
|
234
|
+
// PDF & documents
|
|
235
|
+
extract_pdf_text: new ExtractPDFTextTool(),
|
|
236
|
+
convert_pdf_to_markdown: new ConvertPDFToMarkdownTool(),
|
|
237
|
+
convert_document: new ConvertDocumentTool(),
|
|
238
|
+
// OpenAI tools
|
|
239
|
+
openai_web_search: new OpenAIWebSearchTool(),
|
|
240
|
+
openai_image_generation: new OpenAIImageGenerationTool(),
|
|
241
|
+
openai_text_to_speech: new OpenAITextToSpeechTool(),
|
|
242
|
+
// DataForSEO
|
|
243
|
+
dataseo_search: new DataForSEOSearchTool(),
|
|
244
|
+
dataseo_news: new DataForSEONewsTool(),
|
|
245
|
+
// Email (IMAP)
|
|
246
|
+
search_email: new SearchEmailTool(),
|
|
247
|
+
archive_email: new ArchiveEmailTool(),
|
|
248
|
+
};
|
|
249
|
+
// NodeTool MCP tools (workflows, nodes, jobs, assets, models).
|
|
250
|
+
// When a NodeRegistry is in process, this swaps the REST node-search
|
|
251
|
+
// tools for the local biased versions (and adds `find_model`) so any
|
|
252
|
+
// agent loop gets the same node-selection bias as the GraphPlanner.
|
|
253
|
+
for (const tool of getAllMcpTools({
|
|
254
|
+
registry,
|
|
255
|
+
providers: agentProviders,
|
|
256
|
+
})) {
|
|
257
|
+
toolMap[tool.name] = tool;
|
|
258
|
+
}
|
|
259
|
+
// When sandbox tools are present, exclude host tools that have sandbox
|
|
260
|
+
// equivalents so the agent is forced to execute inside the sandbox.
|
|
261
|
+
const sandboxMode = extraTools && extraTools.length > 0;
|
|
262
|
+
const hostToolsToExclude = sandboxMode
|
|
263
|
+
? new Set([
|
|
264
|
+
"read_file",
|
|
265
|
+
"write_file",
|
|
266
|
+
"edit_file",
|
|
267
|
+
"list_directory",
|
|
268
|
+
"glob",
|
|
269
|
+
"grep",
|
|
270
|
+
"run_code",
|
|
271
|
+
"browser",
|
|
272
|
+
"screenshot",
|
|
273
|
+
"google_search",
|
|
274
|
+
"google_news",
|
|
275
|
+
"google_images",
|
|
276
|
+
"dataseo_search",
|
|
277
|
+
"dataseo_news"
|
|
278
|
+
])
|
|
279
|
+
: null;
|
|
280
|
+
const enabled = enabledTools
|
|
281
|
+
.filter(name => name in toolMap && !(hostToolsToExclude?.has(name)))
|
|
282
|
+
.map(name => toolMap[name]);
|
|
283
|
+
return extraTools && extraTools.length > 0
|
|
284
|
+
? [...enabled, ...extraTools]
|
|
285
|
+
: enabled;
|
|
286
|
+
}
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
// Command handler
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
const handleCommand = useCallback(async (raw) => {
|
|
291
|
+
const parts = raw.trim().split(/\s+/);
|
|
292
|
+
const cmd = parts[0].toLowerCase();
|
|
293
|
+
const args = parts.slice(1);
|
|
294
|
+
switch (cmd) {
|
|
295
|
+
case "/help":
|
|
296
|
+
setShowHelp(prev => !prev);
|
|
297
|
+
return true;
|
|
298
|
+
case "/clear":
|
|
299
|
+
setMessages([{ id: genId(), role: "system", content: "History cleared." }]);
|
|
300
|
+
setChatHistory([]);
|
|
301
|
+
setShowHelp(false);
|
|
302
|
+
if (wsClientRef.current) {
|
|
303
|
+
setThreadId(crypto.randomUUID()); // next message starts a fresh server thread
|
|
304
|
+
}
|
|
305
|
+
return true;
|
|
306
|
+
case "/exit":
|
|
307
|
+
case "/quit":
|
|
308
|
+
await saveSettings({ provider, model, agentMode, agentPlanner });
|
|
309
|
+
exit();
|
|
310
|
+
return true;
|
|
311
|
+
case "/agent":
|
|
312
|
+
setAgentMode(prev => {
|
|
313
|
+
const next = !prev;
|
|
314
|
+
addMessage("system", `Agent mode ${next ? "ON" : "OFF"}.`);
|
|
315
|
+
return next;
|
|
316
|
+
});
|
|
317
|
+
return true;
|
|
318
|
+
case "/planner": {
|
|
319
|
+
const arg = args[0]?.toLowerCase();
|
|
320
|
+
if (arg === "graph" || arg === "multi") {
|
|
321
|
+
setAgentPlanner(arg);
|
|
322
|
+
await saveSettings({ agentPlanner: arg });
|
|
323
|
+
addMessage("system", arg === "graph"
|
|
324
|
+
? "Planner set to graph (workflow builder)."
|
|
325
|
+
: "Planner set to multi (parallel task plan).");
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
addMessage("system", `Current planner: ${agentPlanner}. Usage: /planner graph|multi`);
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
case "/model":
|
|
333
|
+
if (args[0]) {
|
|
334
|
+
setModel(args[0]);
|
|
335
|
+
await saveSettings({ model: args[0] });
|
|
336
|
+
addMessage("system", `Model set to: ${args[0]}`);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
addMessage("system", `Current model: ${model}. Usage: /model <model-id>`);
|
|
340
|
+
}
|
|
341
|
+
return true;
|
|
342
|
+
case "/provider": {
|
|
343
|
+
if (args[0]) {
|
|
344
|
+
const newProvider = args[0].toLowerCase();
|
|
345
|
+
const newModel = DEFAULT_MODELS[newProvider] ?? model;
|
|
346
|
+
setProvider(newProvider);
|
|
347
|
+
setModel(newModel);
|
|
348
|
+
await saveSettings({ provider: newProvider, model: newModel });
|
|
349
|
+
addMessage("system", `Provider: ${newProvider} • Model: ${newModel}`);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
addMessage("system", `Current provider: ${provider}. Usage: /provider <name>`);
|
|
353
|
+
}
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
case "/tools":
|
|
357
|
+
addMessage("system", `Enabled tools: ${enabledTools.join(", ") || "(none)"}`);
|
|
358
|
+
return true;
|
|
359
|
+
default:
|
|
360
|
+
if (cmd.startsWith("/")) {
|
|
361
|
+
addMessage("system", `Unknown command: ${cmd}. Type /help for commands.`);
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}, [provider, model, agentMode, enabledTools, addMessage, exit]);
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
// Chat submission
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
const handleSubmit = useCallback(async (value) => {
|
|
371
|
+
const trimmed = value.trim();
|
|
372
|
+
if (!trimmed)
|
|
373
|
+
return;
|
|
374
|
+
// Deduplicate: useInput autocomplete Enter + TextInput onSubmit both fire for the same keypress
|
|
375
|
+
if (submittingRef.current)
|
|
376
|
+
return;
|
|
377
|
+
submittingRef.current = true;
|
|
378
|
+
// Add to history (deduplicated)
|
|
379
|
+
setInputHistory(prev => {
|
|
380
|
+
const filtered = prev.filter(h => h !== trimmed);
|
|
381
|
+
return [trimmed, ...filtered].slice(0, 100);
|
|
382
|
+
});
|
|
383
|
+
setHistoryIndex(-1);
|
|
384
|
+
setHistoryDraft("");
|
|
385
|
+
setInputValue("");
|
|
386
|
+
setError(null);
|
|
387
|
+
setShowHelp(false);
|
|
388
|
+
// Handle commands
|
|
389
|
+
if (trimmed.startsWith("/")) {
|
|
390
|
+
await handleCommand(trimmed);
|
|
391
|
+
submittingRef.current = false;
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
// Add user message to display
|
|
395
|
+
await addMessage("user", trimmed);
|
|
396
|
+
abortRef.current = false;
|
|
397
|
+
const abortController = new AbortController();
|
|
398
|
+
abortControllerRef.current = abortController;
|
|
399
|
+
streamingRef.current = true;
|
|
400
|
+
setStreaming(true);
|
|
401
|
+
setStreamContent("");
|
|
402
|
+
setStreamLabel("thinking");
|
|
403
|
+
try {
|
|
404
|
+
const ctx = new ProcessingContext({ jobId: crypto.randomUUID(), userId: "1", workspaceDir, secretResolver: getSecret });
|
|
405
|
+
const tools = buildTools();
|
|
406
|
+
if (agentModeRef.current) {
|
|
407
|
+
// --- Agent mode ---
|
|
408
|
+
setStreamLabel("planning");
|
|
409
|
+
execState.reset();
|
|
410
|
+
const prov = wsClientRef.current
|
|
411
|
+
? new WebSocketProvider(wsClientRef.current, modelRef.current, providerRef.current)
|
|
412
|
+
: await createProvider(providerRef.current);
|
|
413
|
+
// Pick the planner. The "graph" planner needs a registry; without
|
|
414
|
+
// one we silently downgrade to the multi-task planner.
|
|
415
|
+
const useGraphPlanner = agentPlannerRef.current === "graph" && !!registry;
|
|
416
|
+
const markdownOutputSchema = {
|
|
417
|
+
type: "object",
|
|
418
|
+
properties: {
|
|
419
|
+
markdown: {
|
|
420
|
+
type: "string",
|
|
421
|
+
description: "The markdown content of the response",
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
required: ["markdown"],
|
|
425
|
+
};
|
|
426
|
+
const agent = new MultiModeAgent({
|
|
427
|
+
name: "chat-agent",
|
|
428
|
+
objective: trimmed,
|
|
429
|
+
provider: prov,
|
|
430
|
+
model: modelRef.current,
|
|
431
|
+
mode: "plan",
|
|
432
|
+
tools,
|
|
433
|
+
useGraphPlanner,
|
|
434
|
+
registry: useGraphPlanner ? registry : undefined,
|
|
435
|
+
providers: useGraphPlanner ? agentProviders : undefined,
|
|
436
|
+
maxStepIterations: 20,
|
|
437
|
+
outputSchema: markdownOutputSchema,
|
|
438
|
+
});
|
|
439
|
+
// Feed all messages into the execution tree state.
|
|
440
|
+
let synthesisContent = "";
|
|
441
|
+
for await (const msg of agent.execute(ctx)) {
|
|
442
|
+
if (abortRef.current)
|
|
443
|
+
break;
|
|
444
|
+
execState.processMessage(msg);
|
|
445
|
+
// Stream the agent's final synthesis so the user sees the answer
|
|
446
|
+
// forming in real time. Other chunks (step thinking, planner
|
|
447
|
+
// commentary) are noise in the chat pane and stay in the tree.
|
|
448
|
+
if (msg.type === "chunk") {
|
|
449
|
+
const ch = msg;
|
|
450
|
+
if (ch.node_id === "agent_synthesizer" && ch.content) {
|
|
451
|
+
synthesisContent += ch.content;
|
|
452
|
+
setStreamContent(synthesisContent);
|
|
453
|
+
setStreamLabel("finalizing");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
execState.markDone();
|
|
458
|
+
setStreamContent("");
|
|
459
|
+
const finalResults = agent.getResults();
|
|
460
|
+
const finalText = typeof finalResults === "string"
|
|
461
|
+
? finalResults
|
|
462
|
+
: finalResults &&
|
|
463
|
+
typeof finalResults === "object" &&
|
|
464
|
+
"markdown" in finalResults &&
|
|
465
|
+
typeof finalResults.markdown ===
|
|
466
|
+
"string"
|
|
467
|
+
? finalResults.markdown
|
|
468
|
+
: finalResults != null
|
|
469
|
+
? JSON.stringify(finalResults, null, 2)
|
|
470
|
+
: "";
|
|
471
|
+
if (finalText) {
|
|
472
|
+
await addMessage("assistant", finalText);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
else if (wsClientRef.current && !extraTools?.length) {
|
|
476
|
+
// --- Regular chat via WebSocket (server handles everything) ---
|
|
477
|
+
const wsClient = wsClientRef.current;
|
|
478
|
+
let assistantContent = "";
|
|
479
|
+
const toolSchemas = tools.map(t => t.toProviderTool());
|
|
480
|
+
const pendingToolArgs = new Map();
|
|
481
|
+
for await (const event of wsClient.chat(trimmed, threadId, modelRef.current, providerRef.current, toolSchemas)) {
|
|
482
|
+
if (abortRef.current)
|
|
483
|
+
break;
|
|
484
|
+
if (event.type === "chunk") {
|
|
485
|
+
assistantContent += event.content;
|
|
486
|
+
setStreamContent(assistantContent);
|
|
487
|
+
setStreamLabel("streaming");
|
|
488
|
+
}
|
|
489
|
+
else if (event.type === "tool_call") {
|
|
490
|
+
pendingToolArgs.set(event.id, event.args);
|
|
491
|
+
setStreamLabel(`tool: ${event.name}`);
|
|
492
|
+
}
|
|
493
|
+
else if (event.type === "tool_result") {
|
|
494
|
+
const preview = event.content.length > 100 ? event.content.slice(0, 100) + "…" : event.content;
|
|
495
|
+
const args = pendingToolArgs.get(event.id);
|
|
496
|
+
pendingToolArgs.delete(event.id);
|
|
497
|
+
await addMessage("tool", preview, { toolName: event.name, toolArgs: args });
|
|
498
|
+
setStreamLabel("thinking");
|
|
499
|
+
}
|
|
500
|
+
else if (event.type === "error") {
|
|
501
|
+
throw new Error(event.message);
|
|
502
|
+
}
|
|
503
|
+
else if (event.type === "done") {
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (assistantContent) {
|
|
508
|
+
await addMessage("assistant", assistantContent);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
else if (wsClientRef.current && extraTools?.length) {
|
|
512
|
+
// --- Regular chat via WebSocket inference + local sandbox tool execution ---
|
|
513
|
+
const prov = new WebSocketProvider(wsClientRef.current, modelRef.current, providerRef.current);
|
|
514
|
+
let assistantContent = "";
|
|
515
|
+
const updatedHistory = [...chatHistoryRef.current];
|
|
516
|
+
await processChat({
|
|
517
|
+
userInput: trimmed,
|
|
518
|
+
messages: updatedHistory,
|
|
519
|
+
model: modelRef.current,
|
|
520
|
+
provider: prov,
|
|
521
|
+
context: ctx,
|
|
522
|
+
tools,
|
|
523
|
+
signal: abortController.signal,
|
|
524
|
+
callbacks: {
|
|
525
|
+
onChunk: (text) => {
|
|
526
|
+
if (abortRef.current)
|
|
527
|
+
throw new Error("aborted");
|
|
528
|
+
assistantContent += text;
|
|
529
|
+
setStreamContent(assistantContent);
|
|
530
|
+
setStreamLabel("streaming");
|
|
531
|
+
},
|
|
532
|
+
onToolCall: (tc) => {
|
|
533
|
+
setStreamLabel(`tool: ${tc.name}`);
|
|
534
|
+
},
|
|
535
|
+
onToolResult: (tc, result) => {
|
|
536
|
+
const preview = typeof result === "string"
|
|
537
|
+
? result
|
|
538
|
+
: JSON.stringify(result).slice(0, 100);
|
|
539
|
+
addMessage("tool", preview, { toolName: tc.name, toolArgs: tc.args });
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
setChatHistory(updatedHistory);
|
|
544
|
+
if (assistantContent) {
|
|
545
|
+
await addMessage("assistant", assistantContent);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
// --- Regular chat mode (direct provider) ---
|
|
550
|
+
const prov = await createProvider(providerRef.current);
|
|
551
|
+
let assistantContent = "";
|
|
552
|
+
const updatedHistory = [...chatHistoryRef.current];
|
|
553
|
+
await processChat({
|
|
554
|
+
userInput: trimmed,
|
|
555
|
+
messages: updatedHistory,
|
|
556
|
+
model: modelRef.current,
|
|
557
|
+
provider: prov,
|
|
558
|
+
context: ctx,
|
|
559
|
+
tools,
|
|
560
|
+
signal: abortController.signal,
|
|
561
|
+
callbacks: {
|
|
562
|
+
onChunk: (text) => {
|
|
563
|
+
if (abortRef.current)
|
|
564
|
+
throw new Error("aborted");
|
|
565
|
+
assistantContent += text;
|
|
566
|
+
setStreamContent(assistantContent);
|
|
567
|
+
setStreamLabel("streaming");
|
|
568
|
+
},
|
|
569
|
+
onToolCall: (tc) => {
|
|
570
|
+
setStreamLabel(`tool: ${tc.name}`);
|
|
571
|
+
},
|
|
572
|
+
onToolResult: (tc, result) => {
|
|
573
|
+
const preview = typeof result === "string"
|
|
574
|
+
? result
|
|
575
|
+
: JSON.stringify(result).slice(0, 100);
|
|
576
|
+
addMessage("tool", preview, { toolName: tc.name, toolArgs: tc.args });
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
});
|
|
580
|
+
setChatHistory(updatedHistory);
|
|
581
|
+
if (assistantContent) {
|
|
582
|
+
await addMessage("assistant", assistantContent);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
catch (err) {
|
|
587
|
+
if (!abortRef.current) {
|
|
588
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
589
|
+
setError(msg);
|
|
590
|
+
await addMessage("system", `Error: ${msg}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
finally {
|
|
594
|
+
streamingRef.current = false;
|
|
595
|
+
setStreaming(false);
|
|
596
|
+
setStreamContent("");
|
|
597
|
+
setStreamLabel("");
|
|
598
|
+
flushPendingMessages();
|
|
599
|
+
submittingRef.current = false;
|
|
600
|
+
abortControllerRef.current = null;
|
|
601
|
+
}
|
|
602
|
+
}, [handleCommand, addMessage, flushPendingMessages, workspaceDir, enabledTools]);
|
|
603
|
+
let acMatches = [];
|
|
604
|
+
if (!streaming && inputValue.startsWith("/")) {
|
|
605
|
+
const lower = inputValue.toLowerCase();
|
|
606
|
+
const spaceIdx = lower.indexOf(" ");
|
|
607
|
+
if (spaceIdx === -1) {
|
|
608
|
+
// Command completion: /pro → /provider
|
|
609
|
+
acMatches = Object.entries(COMMANDS)
|
|
610
|
+
.filter(([cmd]) => cmd.startsWith(lower))
|
|
611
|
+
.map(([cmd, desc]) => ({ cmd, desc }));
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
// Argument completion for specific commands
|
|
615
|
+
const cmd = lower.slice(0, spaceIdx);
|
|
616
|
+
const arg = lower.slice(spaceIdx + 1);
|
|
617
|
+
if (cmd === "/provider") {
|
|
618
|
+
acMatches = KNOWN_PROVIDERS
|
|
619
|
+
.filter((p) => p.startsWith(arg))
|
|
620
|
+
.map((p) => ({
|
|
621
|
+
cmd: p,
|
|
622
|
+
desc: DEFAULT_MODELS[p] ?? "",
|
|
623
|
+
replaceAll: `/provider ${p}`,
|
|
624
|
+
}));
|
|
625
|
+
}
|
|
626
|
+
else if (cmd === "/model") {
|
|
627
|
+
acMatches = modelList
|
|
628
|
+
.filter((m) => m.id.toLowerCase().startsWith(arg))
|
|
629
|
+
.map((m) => ({
|
|
630
|
+
cmd: m.id,
|
|
631
|
+
desc: m.name !== m.id ? m.name : "",
|
|
632
|
+
replaceAll: `/model ${m.id}`,
|
|
633
|
+
}));
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const acOpen = acMatches.length > 0;
|
|
638
|
+
useInput((input, key) => {
|
|
639
|
+
// Escape or Ctrl+C: cancel streaming, or exit when idle
|
|
640
|
+
if (key.escape || (key.ctrl && input === "c")) {
|
|
641
|
+
if (streaming) {
|
|
642
|
+
abortRef.current = true;
|
|
643
|
+
abortControllerRef.current?.abort();
|
|
644
|
+
abortControllerRef.current = null;
|
|
645
|
+
if (wsClientRef.current) {
|
|
646
|
+
wsClientRef.current.stop(agentModeRef.current ? undefined : threadId);
|
|
647
|
+
}
|
|
648
|
+
// Immediately reset UI — don't wait for async cleanup
|
|
649
|
+
setStreaming(false);
|
|
650
|
+
setStreamContent("");
|
|
651
|
+
setStreamLabel("");
|
|
652
|
+
submittingRef.current = false;
|
|
653
|
+
}
|
|
654
|
+
else {
|
|
655
|
+
saveSettings({ provider, model, agentMode }).then(() => exit());
|
|
656
|
+
}
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
// During agent streaming, allow tree navigation
|
|
660
|
+
if (streaming && agentModeRef.current) {
|
|
661
|
+
if (key.upArrow) {
|
|
662
|
+
execState.navigate("up");
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (key.downArrow) {
|
|
666
|
+
execState.navigate("down");
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (key.return || key.rightArrow) {
|
|
670
|
+
execState.toggleExpand();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (key.leftArrow) {
|
|
674
|
+
execState.toggleExpand();
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (streaming)
|
|
680
|
+
return; // block other input while streaming
|
|
681
|
+
// Autocomplete navigation
|
|
682
|
+
if (acOpen) {
|
|
683
|
+
if (key.upArrow) {
|
|
684
|
+
setAcIndex(i => (i <= 0 ? acMatches.length - 1 : i - 1));
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (key.downArrow) {
|
|
688
|
+
setAcIndex(i => (i >= acMatches.length - 1 ? 0 : i + 1));
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (key.tab) {
|
|
692
|
+
const selected = acMatches[acIndex] ?? acMatches[0];
|
|
693
|
+
if (selected)
|
|
694
|
+
setInputValue(selected.replaceAll ?? selected.cmd + " ");
|
|
695
|
+
setAcIndex(0);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
if (key.return) {
|
|
699
|
+
const selected = acMatches[acIndex] ?? acMatches[0];
|
|
700
|
+
if (selected) {
|
|
701
|
+
const completed = selected.replaceAll ?? selected.cmd + " ";
|
|
702
|
+
// Submit complete commands directly; partial ones go into input for editing
|
|
703
|
+
if (completed.trimEnd() in COMMANDS) {
|
|
704
|
+
handleSubmit(completed);
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
setInputValue(completed);
|
|
708
|
+
}
|
|
709
|
+
setAcIndex(0);
|
|
710
|
+
}
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
// History navigation (only when autocomplete is closed)
|
|
715
|
+
if (!acOpen) {
|
|
716
|
+
if (key.upArrow) {
|
|
717
|
+
setInputHistory(hist => {
|
|
718
|
+
if (hist.length === 0)
|
|
719
|
+
return hist;
|
|
720
|
+
setHistoryIndex(prev => {
|
|
721
|
+
if (prev === -1)
|
|
722
|
+
setHistoryDraft(inputValue);
|
|
723
|
+
const next = Math.min(prev + 1, hist.length - 1);
|
|
724
|
+
setInputValue(hist[next] ?? "");
|
|
725
|
+
return next;
|
|
726
|
+
});
|
|
727
|
+
return hist;
|
|
728
|
+
});
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (key.downArrow) {
|
|
732
|
+
setHistoryIndex(prev => {
|
|
733
|
+
if (prev <= 0) {
|
|
734
|
+
setInputValue(historyDraft);
|
|
735
|
+
return -1;
|
|
736
|
+
}
|
|
737
|
+
const next = prev - 1;
|
|
738
|
+
setInputHistory(hist => {
|
|
739
|
+
setInputValue(hist[next] ?? "");
|
|
740
|
+
return hist;
|
|
741
|
+
});
|
|
742
|
+
return next;
|
|
743
|
+
});
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
// ---------------------------------------------------------------------------
|
|
749
|
+
// Render
|
|
750
|
+
// ---------------------------------------------------------------------------
|
|
751
|
+
const statusParts = [
|
|
752
|
+
provider,
|
|
753
|
+
model,
|
|
754
|
+
agentMode ? `agent:${agentPlanner}` : null,
|
|
755
|
+
wsUrl ? "ws" : null,
|
|
756
|
+
].filter(Boolean).join(" ");
|
|
757
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: messages, children: (msg) => (_jsx(Box, { children: _jsx(ChatMessageItem, { msg: msg }) }, msg.id)) }), showHelp && _jsx(HelpPanel, {}), streaming && agentMode && (_jsx(ExecutionTree, { state: execState.state, treeActive: true })), streaming && !agentMode && (() => {
|
|
758
|
+
// Truncate streaming preview to avoid overflowing the terminal's dynamic area.
|
|
759
|
+
const maxPreviewLines = Math.max((process.stdout.rows ?? 24) - 4, 5);
|
|
760
|
+
const lines = streamContent ? streamContent.split("\n") : [];
|
|
761
|
+
const truncated = lines.length > maxPreviewLines
|
|
762
|
+
? lines.slice(-maxPreviewLines).join("\n")
|
|
763
|
+
: streamContent;
|
|
764
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [truncated ? (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "● " }), _jsx(Text, { children: truncated })] })) : null, _jsx(Box, { children: _jsxs(Text, { color: "gray", dimColor: true, children: [" ", _jsx(Spinner, { type: "dots" }), " ", streamLabel || "thinking"] }) })] }));
|
|
765
|
+
})(), error && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "red", children: "● " }), _jsx(Text, { color: "red", children: error })] })), acOpen && (_jsx(AutocompleteMenu, { matches: acMatches, selectedIndex: Math.min(acIndex, acMatches.length - 1) })), !streaming && (_jsxs(_Fragment, { children: [_jsx(Box, { children: _jsx(Text, { color: "gray", dimColor: true, children: "─".repeat(process.stdout.columns ?? 80) }) }), _jsxs(Box, { children: [_jsx(Text, { color: "magenta", dimColor: true, bold: true, children: "❯ " }), _jsx(ReadlineInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit })] }), _jsx(Box, { children: _jsx(Text, { color: "gray", dimColor: true, children: "─".repeat(process.stdout.columns ?? 80) }) }), _jsx(Box, { children: _jsxs(Text, { color: "gray", dimColor: true, children: [" ", statusParts] }) })] }))] }));
|
|
766
|
+
}
|
|
767
|
+
//# sourceMappingURL=app.js.map
|