@vetala/vetala 0.1.0-dev → 0.2.1-dev
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 +31 -1
- package/dist/src/agent.d.ts +12 -1
- package/dist/src/agent.js +92 -14
- package/dist/src/agent.js.map +1 -1
- package/dist/src/app-meta.d.ts +3 -0
- package/dist/src/app-meta.js +4 -0
- package/dist/src/app-meta.js.map +1 -0
- package/dist/src/cli.js +28 -10
- package/dist/src/cli.js.map +1 -1
- package/dist/src/config.d.ts +10 -1
- package/dist/src/config.js +160 -67
- package/dist/src/config.js.map +1 -1
- package/dist/src/edit-history.d.ts +4 -0
- package/dist/src/edit-history.js +77 -0
- package/dist/src/edit-history.js.map +1 -0
- package/dist/src/edits/diff.d.ts +1 -0
- package/dist/src/edits/diff.js +176 -0
- package/dist/src/edits/diff.js.map +1 -0
- package/dist/src/ink/command-suggestions.js +1 -0
- package/dist/src/ink/command-suggestions.js.map +1 -1
- package/dist/src/ink/ink-terminal-ui.d.ts +2 -2
- package/dist/src/ink/ink-terminal-ui.js +7 -3
- package/dist/src/ink/ink-terminal-ui.js.map +1 -1
- package/dist/src/ink/repl-app.d.ts +3 -2
- package/dist/src/ink/repl-app.js +419 -111
- package/dist/src/ink/repl-app.js.map +1 -1
- package/dist/src/process-utils.d.ts +1 -0
- package/dist/src/process-utils.js +3 -1
- package/dist/src/process-utils.js.map +1 -1
- package/dist/src/providers/catalog.d.ts +29 -0
- package/dist/src/providers/catalog.js +121 -0
- package/dist/src/providers/catalog.js.map +1 -0
- package/dist/src/providers/index.d.ts +9 -0
- package/dist/src/providers/index.js +11 -0
- package/dist/src/providers/index.js.map +1 -0
- package/dist/src/providers/openai-compatible-client.d.ts +23 -0
- package/dist/src/providers/openai-compatible-client.js +183 -0
- package/dist/src/providers/openai-compatible-client.js.map +1 -0
- package/dist/src/repl.d.ts +2 -1
- package/dist/src/repl.js +1 -1
- package/dist/src/repl.js.map +1 -1
- package/dist/src/runtime-profile.d.ts +16 -0
- package/dist/src/runtime-profile.js +112 -0
- package/dist/src/runtime-profile.js.map +1 -0
- package/dist/src/search-provider.d.ts +6 -1
- package/dist/src/search-provider.js +257 -0
- package/dist/src/search-provider.js.map +1 -1
- package/dist/src/session-store.d.ts +5 -3
- package/dist/src/session-store.js +75 -4
- package/dist/src/session-store.js.map +1 -1
- package/dist/src/terminal-ui.d.ts +3 -1
- package/dist/src/terminal-ui.js +13 -3
- package/dist/src/terminal-ui.js.map +1 -1
- package/dist/src/tools/filesystem.js +141 -143
- package/dist/src/tools/filesystem.js.map +1 -1
- package/dist/src/tools/index.js +2 -0
- package/dist/src/tools/index.js.map +1 -1
- package/dist/src/tools/repo-search.d.ts +23 -0
- package/dist/src/tools/repo-search.js +221 -0
- package/dist/src/tools/repo-search.js.map +1 -0
- package/dist/src/tools/shell.js +26 -3
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/timing.d.ts +2 -0
- package/dist/src/tools/timing.js +58 -0
- package/dist/src/tools/timing.js.map +1 -0
- package/dist/src/tools/web.js +70 -5
- package/dist/src/tools/web.js.map +1 -1
- package/dist/src/types.d.ts +89 -17
- package/dist/src/update-notifier.d.ts +41 -0
- package/dist/src/update-notifier.js +145 -0
- package/dist/src/update-notifier.js.map +1 -0
- package/package.json +13 -2
- package/terminal.png +0 -0
package/dist/src/ink/repl-app.js
CHANGED
|
@@ -4,17 +4,29 @@ import { Box, Text, useApp, useInput } from "ink";
|
|
|
4
4
|
import SelectInput from "ink-select-input";
|
|
5
5
|
import Spinner from "ink-spinner";
|
|
6
6
|
import TextInput from "ink-text-input";
|
|
7
|
-
import { Agent } from "../agent.js";
|
|
7
|
+
import { Agent, isAgentInterruptedError } from "../agent.js";
|
|
8
|
+
import { latestUndoableEdit, undoLastEdit } from "../edit-history.js";
|
|
8
9
|
import { ApprovalManager } from "../approvals.js";
|
|
9
|
-
import {
|
|
10
|
+
import { clearProviderSavedAuth, loadConfig, providerConfigFor, saveProviderDefaults, saveProviderPersistentAuth, withProviderSessionAuth, withProviderStoredAuth } from "../config.js";
|
|
10
11
|
import { PathPolicy } from "../path-policy.js";
|
|
11
|
-
import {
|
|
12
|
+
import { getProviderDefinition, listProviders, providerLabel } from "../providers/index.js";
|
|
12
13
|
import { SkillRuntime } from "../skills/runtime.js";
|
|
13
14
|
import { createToolRegistry } from "../tools/index.js";
|
|
15
|
+
import { checkForAppUpdate, installAppUpdate, snoozeAppUpdate } from "../update-notifier.js";
|
|
16
|
+
import { buildTranscriptCards } from "./transcript-cards.js";
|
|
14
17
|
import { InkTerminalUI } from "./ink-terminal-ui.js";
|
|
15
18
|
import { buildSlashSuggestions } from "./command-suggestions.js";
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
const ASSISTANT_FLUSH_INTERVAL_MS = 33;
|
|
20
|
+
const MAX_LIVE_ASSISTANT_LINES = 24;
|
|
21
|
+
const MAX_VISIBLE_TRANSCRIPT_TURNS = 6;
|
|
22
|
+
const UI_COLORS = {
|
|
23
|
+
accent: "blue",
|
|
24
|
+
muted: "gray",
|
|
25
|
+
border: "gray",
|
|
26
|
+
warning: "yellow",
|
|
27
|
+
danger: "red"
|
|
28
|
+
};
|
|
29
|
+
export function ReplApp({ initialConfig, initialSession, runtimeProfile, store }) {
|
|
18
30
|
const { exit } = useApp();
|
|
19
31
|
const [config, setConfig] = useState(initialConfig);
|
|
20
32
|
const [session, setSession] = useState(() => cloneSession(initialSession));
|
|
@@ -26,18 +38,30 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
26
38
|
const [spinnerLabel, setSpinnerLabel] = useState(null);
|
|
27
39
|
const [status, setStatus] = useState("Ready");
|
|
28
40
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
41
|
+
const [pendingBusyPrompt, setPendingBusyPrompt] = useState(null);
|
|
42
|
+
const [pendingUpdatePrompt, setPendingUpdatePrompt] = useState(null);
|
|
29
43
|
const [modelPickerOpen, setModelPickerOpen] = useState(false);
|
|
44
|
+
const [pendingSarvamModelPicker, setPendingSarvamModelPicker] = useState(null);
|
|
45
|
+
const [pendingOpenRouterModelInput, setPendingOpenRouterModelInput] = useState(null);
|
|
30
46
|
const [pendingReasoningSetup, setPendingReasoningSetup] = useState(null);
|
|
31
47
|
const [pendingAuthInput, setPendingAuthInput] = useState(null);
|
|
32
48
|
const [pendingAuthRetention, setPendingAuthRetention] = useState(null);
|
|
33
49
|
const [availableSkills, setAvailableSkills] = useState([]);
|
|
34
50
|
const [paused, setPaused] = useState(false);
|
|
35
51
|
const [pendingExitConfirm, setPendingExitConfirm] = useState(false);
|
|
52
|
+
const [queuedPrompt, setQueuedPrompt] = useState(null);
|
|
53
|
+
const [turnRunning, setTurnRunning] = useState(false);
|
|
54
|
+
const [installingUpdate, setInstallingUpdate] = useState(null);
|
|
36
55
|
const assistantBufferRef = useRef("");
|
|
56
|
+
const assistantFlushTimerRef = useRef(null);
|
|
37
57
|
const nextEntryIdRef = useRef(0);
|
|
58
|
+
const queuedPromptRef = useRef(null);
|
|
38
59
|
const sessionRef = useRef(session);
|
|
60
|
+
const turnRunningRef = useRef(false);
|
|
61
|
+
const updateCheckStartedRef = useRef(false);
|
|
39
62
|
const uiRef = useRef(null);
|
|
40
63
|
const skillRuntimeRef = useRef(null);
|
|
64
|
+
const activeAgentRef = useRef(null);
|
|
41
65
|
sessionRef.current = session;
|
|
42
66
|
const pushEntry = (kind, text) => {
|
|
43
67
|
setEntries((current) => [
|
|
@@ -49,7 +73,24 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
49
73
|
}
|
|
50
74
|
]);
|
|
51
75
|
};
|
|
76
|
+
const flushAssistantBuffer = () => {
|
|
77
|
+
if (assistantFlushTimerRef.current) {
|
|
78
|
+
clearTimeout(assistantFlushTimerRef.current);
|
|
79
|
+
assistantFlushTimerRef.current = null;
|
|
80
|
+
}
|
|
81
|
+
setAssistantBuffer(assistantBufferRef.current);
|
|
82
|
+
};
|
|
83
|
+
const scheduleAssistantFlush = () => {
|
|
84
|
+
if (assistantFlushTimerRef.current) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
assistantFlushTimerRef.current = setTimeout(() => {
|
|
88
|
+
assistantFlushTimerRef.current = null;
|
|
89
|
+
setAssistantBuffer(assistantBufferRef.current);
|
|
90
|
+
}, ASSISTANT_FLUSH_INTERVAL_MS);
|
|
91
|
+
};
|
|
52
92
|
const finalizeAssistant = () => {
|
|
93
|
+
flushAssistantBuffer();
|
|
53
94
|
const buffered = assistantBufferRef.current.trimEnd();
|
|
54
95
|
if (!buffered) {
|
|
55
96
|
assistantBufferRef.current = "";
|
|
@@ -64,7 +105,7 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
64
105
|
uiRef.current = new InkTerminalUI({
|
|
65
106
|
appendAssistant: (text) => {
|
|
66
107
|
assistantBufferRef.current += text;
|
|
67
|
-
|
|
108
|
+
scheduleAssistantFlush();
|
|
68
109
|
},
|
|
69
110
|
finalizeAssistant,
|
|
70
111
|
pushEntry,
|
|
@@ -75,7 +116,7 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
75
116
|
setSpinnerLabel(label);
|
|
76
117
|
setStatus(label ?? "Ready");
|
|
77
118
|
}
|
|
78
|
-
});
|
|
119
|
+
}, runtimeProfile);
|
|
79
120
|
}
|
|
80
121
|
const ui = uiRef.current;
|
|
81
122
|
if (!skillRuntimeRef.current) {
|
|
@@ -85,17 +126,23 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
85
126
|
});
|
|
86
127
|
}
|
|
87
128
|
const skills = skillRuntimeRef.current;
|
|
88
|
-
const busy = spinnerLabel !== null;
|
|
89
|
-
const transcriptCards = buildTranscriptCards(entries).slice(-8);
|
|
90
|
-
const liveCardId = transcriptCards.at(-1)?.id ?? null;
|
|
91
129
|
const visibleStatus = paused ? "Paused" : status;
|
|
130
|
+
const visibleAssistantBuffer = renderLiveAssistantBuffer(assistantBuffer);
|
|
131
|
+
const transcriptCards = buildTranscriptCards(entries);
|
|
132
|
+
const visibleTranscriptCards = transcriptCards.slice(-MAX_VISIBLE_TRANSCRIPT_TURNS);
|
|
133
|
+
const hiddenTranscriptTurnCount = Math.max(0, transcriptCards.length - visibleTranscriptCards.length);
|
|
92
134
|
const slashSuggestions = buildSlashSuggestions(input, availableSkills).slice(0, 8);
|
|
93
135
|
const showSlashSuggestions = Boolean(trusted &&
|
|
94
|
-
!
|
|
136
|
+
!turnRunning &&
|
|
95
137
|
!paused &&
|
|
96
138
|
!pendingExitConfirm &&
|
|
97
139
|
!pendingApproval &&
|
|
140
|
+
!pendingBusyPrompt &&
|
|
141
|
+
!pendingUpdatePrompt &&
|
|
142
|
+
!installingUpdate &&
|
|
98
143
|
!modelPickerOpen &&
|
|
144
|
+
!pendingSarvamModelPicker &&
|
|
145
|
+
!pendingOpenRouterModelInput &&
|
|
99
146
|
!pendingReasoningSetup &&
|
|
100
147
|
!pendingAuthInput &&
|
|
101
148
|
!pendingAuthRetention &&
|
|
@@ -123,6 +170,31 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
123
170
|
cancelled = true;
|
|
124
171
|
};
|
|
125
172
|
}, [skills]);
|
|
173
|
+
useEffect(() => () => {
|
|
174
|
+
if (assistantFlushTimerRef.current) {
|
|
175
|
+
clearTimeout(assistantFlushTimerRef.current);
|
|
176
|
+
}
|
|
177
|
+
}, []);
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (!trusted || updateCheckStartedRef.current) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
updateCheckStartedRef.current = true;
|
|
183
|
+
let cancelled = false;
|
|
184
|
+
void checkForAppUpdate()
|
|
185
|
+
.then((update) => {
|
|
186
|
+
if (!cancelled && update) {
|
|
187
|
+
setPendingUpdatePrompt(update);
|
|
188
|
+
setStatus(`Update available: ${update.latestVersion}`);
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
.catch(() => {
|
|
192
|
+
// Keep startup quiet if the registry cannot be reached.
|
|
193
|
+
});
|
|
194
|
+
return () => {
|
|
195
|
+
cancelled = true;
|
|
196
|
+
};
|
|
197
|
+
}, [trusted]);
|
|
126
198
|
useInput((inputValue, key) => {
|
|
127
199
|
if (showSlashSuggestions && isTabInput(inputValue, key)) {
|
|
128
200
|
const firstSuggestion = slashSuggestions[0];
|
|
@@ -154,41 +226,67 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
154
226
|
return cloned;
|
|
155
227
|
};
|
|
156
228
|
const resetTranscript = () => {
|
|
229
|
+
flushAssistantBuffer();
|
|
157
230
|
assistantBufferRef.current = "";
|
|
158
231
|
setAssistantBuffer("");
|
|
159
232
|
setActivityLabel(null);
|
|
160
233
|
nextEntryIdRef.current = 0;
|
|
161
234
|
setEntries([]);
|
|
162
235
|
};
|
|
163
|
-
const mergeLoadedConfig = (loaded) => {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
236
|
+
const mergeLoadedConfig = (loaded, skipProvider) => {
|
|
237
|
+
let merged = loaded;
|
|
238
|
+
for (const provider of listProviders()) {
|
|
239
|
+
if (provider.name === skipProvider) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const currentProfile = config.providers[provider.name];
|
|
243
|
+
if (currentProfile.authSource === "session" &&
|
|
244
|
+
currentProfile.authValue &&
|
|
245
|
+
currentProfile.authMode !== "missing") {
|
|
246
|
+
merged = withProviderSessionAuth(merged, provider.name, currentProfile.authMode, currentProfile.authValue);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return merged;
|
|
174
250
|
};
|
|
175
251
|
const closeCommandModals = () => {
|
|
176
252
|
setModelPickerOpen(false);
|
|
253
|
+
setPendingSarvamModelPicker(null);
|
|
254
|
+
setPendingOpenRouterModelInput(null);
|
|
177
255
|
setPendingReasoningSetup(null);
|
|
178
256
|
setPendingAuthInput(null);
|
|
179
257
|
setPendingAuthRetention(null);
|
|
180
258
|
};
|
|
181
|
-
const
|
|
259
|
+
const setQueuedPromptState = (prompt) => {
|
|
260
|
+
queuedPromptRef.current = prompt;
|
|
261
|
+
setQueuedPrompt(prompt);
|
|
262
|
+
};
|
|
263
|
+
const setTurnRunningState = (value) => {
|
|
264
|
+
turnRunningRef.current = value;
|
|
265
|
+
setTurnRunning(value);
|
|
266
|
+
};
|
|
267
|
+
const beginQueuedPrompt = (prompt) => {
|
|
268
|
+
setQueuedPromptState(null);
|
|
269
|
+
setPendingBusyPrompt(null);
|
|
270
|
+
void runPrompt(prompt, { forceRun: true });
|
|
271
|
+
};
|
|
272
|
+
const runPrompt = async (prompt, options = {}) => {
|
|
182
273
|
const trimmed = prompt.trim();
|
|
183
274
|
if (!trimmed) {
|
|
184
275
|
return;
|
|
185
276
|
}
|
|
277
|
+
if (turnRunningRef.current && !options.forceRun) {
|
|
278
|
+
setPendingBusyPrompt({ prompt: trimmed });
|
|
279
|
+
setInput("");
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
186
282
|
if (trimmed.startsWith("/")) {
|
|
283
|
+
setInput("");
|
|
187
284
|
await handleCommand(trimmed);
|
|
188
285
|
return;
|
|
189
286
|
}
|
|
190
287
|
setInput("");
|
|
191
288
|
setPendingExitConfirm(false);
|
|
289
|
+
setPendingBusyPrompt(null);
|
|
192
290
|
setActivityLabel(null);
|
|
193
291
|
pushEntry("user", summarizeUserPrompt(trimmed));
|
|
194
292
|
setStatus("Running agent");
|
|
@@ -199,23 +297,40 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
199
297
|
sessionStore: store,
|
|
200
298
|
approvals,
|
|
201
299
|
pathPolicy: new PathPolicy(session.workspaceRoot, approvals),
|
|
300
|
+
runtimeProfile,
|
|
202
301
|
skills,
|
|
203
302
|
tools: createTools(),
|
|
204
303
|
ui
|
|
205
304
|
});
|
|
305
|
+
activeAgentRef.current = agent;
|
|
306
|
+
setTurnRunningState(true);
|
|
206
307
|
try {
|
|
207
308
|
await agent.runTurn(trimmed, true);
|
|
208
309
|
syncSession(session);
|
|
209
310
|
setActivityLabel(null);
|
|
210
|
-
setStatus("Ready");
|
|
311
|
+
setStatus(queuedPromptRef.current ? "Running queued prompt" : "Ready");
|
|
211
312
|
}
|
|
212
313
|
catch (error) {
|
|
314
|
+
if (isAgentInterruptedError(error)) {
|
|
315
|
+
setActivityLabel(null);
|
|
316
|
+
syncSession(session);
|
|
317
|
+
setStatus(queuedPromptRef.current ? "Running queued prompt" : "Interrupted");
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
213
320
|
finalizeAssistant();
|
|
214
321
|
setActivityLabel(null);
|
|
215
322
|
pushEntry("error", error instanceof Error ? error.message : String(error));
|
|
216
323
|
setStatus("Failed");
|
|
217
324
|
syncSession(session);
|
|
218
325
|
}
|
|
326
|
+
finally {
|
|
327
|
+
activeAgentRef.current = null;
|
|
328
|
+
setTurnRunningState(false);
|
|
329
|
+
const nextQueuedPrompt = queuedPromptRef.current;
|
|
330
|
+
if (nextQueuedPrompt) {
|
|
331
|
+
beginQueuedPrompt(nextQueuedPrompt);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
219
334
|
};
|
|
220
335
|
const handleCommand = async (commandLine) => {
|
|
221
336
|
const [command, ...args] = commandLine.slice(1).split(/\s+/);
|
|
@@ -227,6 +342,7 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
227
342
|
pushEntry("info", [
|
|
228
343
|
"/help",
|
|
229
344
|
"/model",
|
|
345
|
+
"/undo",
|
|
230
346
|
"/skill",
|
|
231
347
|
"/tools",
|
|
232
348
|
"/history",
|
|
@@ -245,8 +361,18 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
245
361
|
}
|
|
246
362
|
closeCommandModals();
|
|
247
363
|
setModelPickerOpen(true);
|
|
248
|
-
setStatus("Select a model");
|
|
364
|
+
setStatus("Select a provider and model");
|
|
365
|
+
return;
|
|
366
|
+
case "undo": {
|
|
367
|
+
const result = await undoLastEdit(session, store, (request) => {
|
|
368
|
+
const approvals = new ApprovalManager(session, store, null, requestApprovalDecision);
|
|
369
|
+
return approvals.requestApproval(request);
|
|
370
|
+
});
|
|
371
|
+
syncSession(session);
|
|
372
|
+
pushEntry(result.isError ? "warn" : "info", result.content);
|
|
373
|
+
setStatus(result.isError ? "Undo blocked" : "Ready");
|
|
249
374
|
return;
|
|
375
|
+
}
|
|
250
376
|
case "skill":
|
|
251
377
|
case "skills":
|
|
252
378
|
await handleSkillsCommand(args);
|
|
@@ -282,7 +408,7 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
282
408
|
return;
|
|
283
409
|
}
|
|
284
410
|
case "new": {
|
|
285
|
-
const nextSession = await store.createSession(session.workspaceRoot, session.model);
|
|
411
|
+
const nextSession = await store.createSession(session.workspaceRoot, session.provider, session.model);
|
|
286
412
|
const nextCloned = syncSession(nextSession);
|
|
287
413
|
closeCommandModals();
|
|
288
414
|
resetTranscript();
|
|
@@ -300,22 +426,22 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
300
426
|
ui.printConfig(config);
|
|
301
427
|
return;
|
|
302
428
|
case "logout": {
|
|
303
|
-
const
|
|
304
|
-
await
|
|
429
|
+
const previousProfile = providerConfigFor(config, session.provider);
|
|
430
|
+
await clearProviderSavedAuth(session.provider);
|
|
305
431
|
closeCommandModals();
|
|
306
|
-
const reloaded = await loadConfig();
|
|
432
|
+
const reloaded = mergeLoadedConfig(await loadConfig(), session.provider);
|
|
307
433
|
setConfig(reloaded);
|
|
308
|
-
if (reloaded.authSource === "env") {
|
|
309
|
-
pushEntry("warn",
|
|
434
|
+
if (providerConfigFor(reloaded, session.provider).authSource === "env") {
|
|
435
|
+
pushEntry("warn", `Cleared local ${providerLabel(session.provider)} auth state, but environment credentials are still active for this process.`);
|
|
310
436
|
}
|
|
311
|
-
else if (
|
|
312
|
-
pushEntry("info",
|
|
437
|
+
else if (previousProfile.authSource === "stored" || previousProfile.authSource === "stored_hash") {
|
|
438
|
+
pushEntry("info", `Cleared the saved ${providerLabel(session.provider)} credential for future launches.`);
|
|
313
439
|
}
|
|
314
|
-
else if (
|
|
315
|
-
pushEntry("info",
|
|
440
|
+
else if (previousProfile.authSource === "session") {
|
|
441
|
+
pushEntry("info", `Cleared the ${providerLabel(session.provider)} credential that was only active in this session.`);
|
|
316
442
|
}
|
|
317
443
|
else {
|
|
318
|
-
pushEntry("info",
|
|
444
|
+
pushEntry("info", `No saved ${providerLabel(session.provider)} credential remained after logout.`);
|
|
319
445
|
}
|
|
320
446
|
setStatus("Logged out");
|
|
321
447
|
return;
|
|
@@ -423,6 +549,96 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
423
549
|
setPendingApproval(null);
|
|
424
550
|
promptState.resolve(value);
|
|
425
551
|
};
|
|
552
|
+
const onBusyPromptSelect = async (choice) => {
|
|
553
|
+
const current = pendingBusyPrompt;
|
|
554
|
+
if (!current) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (choice === "cancel") {
|
|
558
|
+
setPendingBusyPrompt(null);
|
|
559
|
+
setInput(current.prompt);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const replacedExistingQueue = queuedPromptRef.current !== null;
|
|
563
|
+
setQueuedPromptState(current.prompt);
|
|
564
|
+
setPendingBusyPrompt(null);
|
|
565
|
+
pushEntry("info", choice === "force"
|
|
566
|
+
? `Stopping the current turn and sending next: ${summarizeUserPrompt(current.prompt)}`
|
|
567
|
+
: `${replacedExistingQueue ? "Replaced" : "Queued"} next prompt: ${summarizeUserPrompt(current.prompt)}`);
|
|
568
|
+
if (choice === "force") {
|
|
569
|
+
setStatus("Stopping current turn");
|
|
570
|
+
activeAgentRef.current?.requestStop();
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
setStatus("Queued next prompt");
|
|
574
|
+
};
|
|
575
|
+
const onUpdatePromptSelect = async (choice) => {
|
|
576
|
+
const current = pendingUpdatePrompt;
|
|
577
|
+
if (!current) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (choice === "update_later") {
|
|
581
|
+
await snoozeAppUpdate(current.latestVersion);
|
|
582
|
+
setPendingUpdatePrompt(null);
|
|
583
|
+
setStatus("Ready");
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
setPendingUpdatePrompt(null);
|
|
587
|
+
setInstallingUpdate(current);
|
|
588
|
+
setStatus(`Installing ${current.latestVersion}`);
|
|
589
|
+
try {
|
|
590
|
+
const result = await installAppUpdate(current.latestVersion);
|
|
591
|
+
const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
592
|
+
if (result.timedOut || (result.exitCode ?? 1) !== 0) {
|
|
593
|
+
pushEntry("warn", [
|
|
594
|
+
`Update failed for ${current.latestVersion}.`,
|
|
595
|
+
current.installCommand,
|
|
596
|
+
output || "(no output)"
|
|
597
|
+
].join("\n"));
|
|
598
|
+
setStatus("Update failed");
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
pushEntry("info", [
|
|
602
|
+
`Updated Vetala to ${current.latestVersion}.`,
|
|
603
|
+
"Restart Vetala to use the new version.",
|
|
604
|
+
current.installCommand
|
|
605
|
+
].join("\n"));
|
|
606
|
+
setStatus("Update installed; restart Vetala");
|
|
607
|
+
}
|
|
608
|
+
catch (error) {
|
|
609
|
+
pushEntry("warn", [
|
|
610
|
+
`Update failed for ${current.latestVersion}.`,
|
|
611
|
+
current.installCommand,
|
|
612
|
+
error instanceof Error ? error.message : String(error)
|
|
613
|
+
].join("\n"));
|
|
614
|
+
setStatus("Update failed");
|
|
615
|
+
}
|
|
616
|
+
finally {
|
|
617
|
+
setInstallingUpdate(null);
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
const applyModelSelection = async (nextSettings) => {
|
|
621
|
+
const definition = getProviderDefinition(nextSettings.provider);
|
|
622
|
+
await store.updateModel(session, nextSettings.provider, nextSettings.model);
|
|
623
|
+
await saveProviderDefaults(nextSettings.provider, nextSettings.model, definition.supportsReasoningEffort
|
|
624
|
+
? { reasoningEffort: nextSettings.reasoningEffort }
|
|
625
|
+
: {});
|
|
626
|
+
syncSession(session);
|
|
627
|
+
const nextConfig = mergeLoadedConfig(await loadConfig());
|
|
628
|
+
setConfig(nextConfig);
|
|
629
|
+
const nextProfile = providerConfigFor(nextConfig, nextSettings.provider);
|
|
630
|
+
if (nextProfile.authSource === "missing" || nextProfile.authSource === "stored_hash") {
|
|
631
|
+
setPendingAuthInput({
|
|
632
|
+
...nextSettings,
|
|
633
|
+
authMode: definition.auth.defaultMode,
|
|
634
|
+
value: ""
|
|
635
|
+
});
|
|
636
|
+
setStatus(`Enter ${definition.auth.inputLabel.toLowerCase()} for ${providerLabel(nextSettings.provider)} / ${nextSettings.model}`);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
pushEntry("info", formatModelSetupSummary(nextSettings, nextConfig));
|
|
640
|
+
setStatus("Ready");
|
|
641
|
+
};
|
|
426
642
|
const onModelSelect = async (value) => {
|
|
427
643
|
if (value === "cancel") {
|
|
428
644
|
setModelPickerOpen(false);
|
|
@@ -430,11 +646,30 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
430
646
|
return;
|
|
431
647
|
}
|
|
432
648
|
setModelPickerOpen(false);
|
|
649
|
+
if (value === "sarvam") {
|
|
650
|
+
setPendingSarvamModelPicker("sarvam");
|
|
651
|
+
setStatus("Select a Sarvam model");
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
setPendingOpenRouterModelInput({
|
|
655
|
+
provider: "openrouter",
|
|
656
|
+
value: session.provider === "openrouter" ? session.model : providerConfigFor(config, "openrouter").defaultModel
|
|
657
|
+
});
|
|
658
|
+
setStatus("Enter an OpenRouter model id");
|
|
659
|
+
};
|
|
660
|
+
const onSarvamModelSelect = async (value) => {
|
|
661
|
+
if (value === "cancel") {
|
|
662
|
+
setPendingSarvamModelPicker(null);
|
|
663
|
+
setStatus("Ready");
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
setPendingSarvamModelPicker(null);
|
|
433
667
|
setPendingReasoningSetup({
|
|
668
|
+
provider: "sarvam",
|
|
434
669
|
model: value,
|
|
435
670
|
reasoningEffort: config.reasoningEffort
|
|
436
671
|
});
|
|
437
|
-
setStatus(`Select reasoning effort for ${value}`);
|
|
672
|
+
setStatus(`Select reasoning effort for Sarvam / ${value}`);
|
|
438
673
|
};
|
|
439
674
|
const onReasoningSelect = async (value) => {
|
|
440
675
|
const current = pendingReasoningSetup;
|
|
@@ -451,23 +686,28 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
451
686
|
...current,
|
|
452
687
|
reasoningEffort: value === "none" ? null : value
|
|
453
688
|
};
|
|
454
|
-
await
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
...nextSettings,
|
|
463
|
-
authMode: "subscription_key",
|
|
464
|
-
value: ""
|
|
465
|
-
});
|
|
466
|
-
setStatus(`Enter API key for ${nextSettings.model}`);
|
|
689
|
+
await applyModelSelection(nextSettings);
|
|
690
|
+
};
|
|
691
|
+
const onOpenRouterModelInputChange = (value) => {
|
|
692
|
+
setPendingOpenRouterModelInput((current) => (current ? { ...current, value } : current));
|
|
693
|
+
};
|
|
694
|
+
const onOpenRouterModelInputSubmit = async (value) => {
|
|
695
|
+
const trimmed = value.trim();
|
|
696
|
+
if (!pendingOpenRouterModelInput) {
|
|
467
697
|
return;
|
|
468
698
|
}
|
|
469
|
-
|
|
470
|
-
|
|
699
|
+
if (!trimmed) {
|
|
700
|
+
setPendingOpenRouterModelInput(null);
|
|
701
|
+
pushEntry("warn", "OpenRouter model selection cancelled.");
|
|
702
|
+
setStatus("Ready");
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
setPendingOpenRouterModelInput(null);
|
|
706
|
+
await applyModelSelection({
|
|
707
|
+
provider: "openrouter",
|
|
708
|
+
model: trimmed,
|
|
709
|
+
reasoningEffort: null
|
|
710
|
+
});
|
|
471
711
|
};
|
|
472
712
|
const onAuthInputChange = (value) => {
|
|
473
713
|
setPendingAuthInput((current) => (current ? { ...current, value } : current));
|
|
@@ -480,7 +720,7 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
480
720
|
const trimmed = value.trim();
|
|
481
721
|
if (!trimmed) {
|
|
482
722
|
setPendingAuthInput(null);
|
|
483
|
-
pushEntry("warn",
|
|
723
|
+
pushEntry("warn", `Credential entry cancelled. Model settings were saved, but no usable ${providerLabel(current.provider)} credential is active.`);
|
|
484
724
|
setStatus("Ready");
|
|
485
725
|
return;
|
|
486
726
|
}
|
|
@@ -489,7 +729,7 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
489
729
|
...current,
|
|
490
730
|
value: trimmed
|
|
491
731
|
});
|
|
492
|
-
setStatus("Choose how long to keep this
|
|
732
|
+
setStatus("Choose how long to keep this credential");
|
|
493
733
|
};
|
|
494
734
|
const onAuthRetentionSelect = async (choice) => {
|
|
495
735
|
const current = pendingAuthRetention;
|
|
@@ -504,15 +744,15 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
504
744
|
}
|
|
505
745
|
const loadedConfig = await loadConfig();
|
|
506
746
|
const nextConfig = choice === "persist"
|
|
507
|
-
?
|
|
508
|
-
:
|
|
747
|
+
? withProviderStoredAuth(loadedConfig, current.provider, current.authMode, current.value)
|
|
748
|
+
: withProviderSessionAuth(loadedConfig, current.provider, current.authMode, current.value);
|
|
509
749
|
if (choice === "persist") {
|
|
510
|
-
await
|
|
750
|
+
await saveProviderPersistentAuth(current.provider, current.authMode, current.value);
|
|
511
751
|
}
|
|
512
752
|
setConfig(nextConfig);
|
|
513
753
|
setPendingAuthRetention(null);
|
|
514
754
|
pushEntry("info", formatModelSetupSummary(current, nextConfig, choice));
|
|
515
|
-
if (choice === "persist" && loadedConfig.authSource === "env") {
|
|
755
|
+
if (choice === "persist" && providerConfigFor(loadedConfig, current.provider).authSource === "env") {
|
|
516
756
|
pushEntry("warn", "Environment credentials are still set in this shell. They may take precedence on future launches.");
|
|
517
757
|
}
|
|
518
758
|
setStatus("Ready");
|
|
@@ -524,51 +764,68 @@ export function ReplApp({ initialConfig, initialSession, store }) {
|
|
|
524
764
|
}
|
|
525
765
|
setPendingExitConfirm(false);
|
|
526
766
|
};
|
|
527
|
-
return (_jsx(Box, { flexDirection: "column", paddingX: 1, children: !trusted ? (_jsxs(_Fragment, { children: [_jsx(TrustScreen, { workspaceRoot: session.workspaceRoot, onSelect: onTrustSelect }), pendingExitConfirm ? _jsx(ExitConfirmBox, { onSelect: onExitConfirmSelect }) : null] })) : (_jsxs(_Fragment, { children: [_jsx(Dashboard, { config: config, session: session, status: visibleStatus }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
|
|
767
|
+
return (_jsx(Box, { flexDirection: "column", paddingX: 1, children: !trusted ? (_jsxs(_Fragment, { children: [_jsx(TrustScreen, { workspaceRoot: session.workspaceRoot, onSelect: onTrustSelect }), pendingExitConfirm ? _jsx(ExitConfirmBox, { onSelect: onExitConfirmSelect }) : null] })) : (_jsxs(_Fragment, { children: [_jsx(Dashboard, { config: config, session: session, status: visibleStatus }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [entries.length === 0 && !assistantBuffer && !spinnerLabel ? (_jsx(Box, { borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, children: _jsx(Text, { color: UI_COLORS.muted, children: "New transcript. Use /history if you want earlier session messages." }) })) : null, hiddenTranscriptTurnCount > 0 ? (_jsx(Box, { marginBottom: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, children: _jsxs(Text, { color: UI_COLORS.muted, children: [hiddenTranscriptTurnCount, " earlier turn", hiddenTranscriptTurnCount === 1 ? "" : "s", " hidden. Use /history to inspect older messages or /clear to reset the visible transcript."] }) })) : null, visibleTranscriptCards.map((card) => (_jsx(TranscriptTurnCard, { card: card }, card.id))), (assistantBuffer || activityLabel || spinnerLabel) ? (_jsx(LiveStatusCard, { assistantBuffer: visibleAssistantBuffer, liveLabel: activityLabel ?? spinnerLabel })) : null] }), pendingExitConfirm ? (_jsx(ExitConfirmBox, { onSelect: onExitConfirmSelect })) : paused ? (_jsx(PauseBox, {})) : pendingApproval ? (_jsx(ApprovalBox, { request: pendingApproval.request, onSelect: onApprovalSelect })) : pendingBusyPrompt ? (_jsx(BusyPromptBox, { prompt: pendingBusyPrompt.prompt, onSelect: onBusyPromptSelect })) : installingUpdate ? (_jsx(UpdateInstallBox, { update: installingUpdate })) : pendingUpdatePrompt ? (_jsx(UpdatePromptBox, { update: pendingUpdatePrompt, onSelect: onUpdatePromptSelect })) : modelPickerOpen ? (_jsx(ModelPicker, { currentProvider: session.provider, onSelect: onModelSelect })) : pendingSarvamModelPicker ? (_jsx(SarvamModelPicker, { currentModel: session.provider === "sarvam" ? session.model : null, onSelect: onSarvamModelSelect })) : pendingOpenRouterModelInput ? (_jsx(OpenRouterModelIdBox, { state: pendingOpenRouterModelInput, onChange: onOpenRouterModelInputChange, onSubmit: onOpenRouterModelInputSubmit })) : pendingReasoningSetup ? (_jsx(ReasoningEffortPicker, { currentValue: pendingReasoningSetup.reasoningEffort, provider: pendingReasoningSetup.provider, model: pendingReasoningSetup.model, onSelect: onReasoningSelect })) : pendingAuthInput ? (_jsx(AuthInputBox, { state: pendingAuthInput, onChange: onAuthInputChange, onSubmit: onAuthInputSubmit })) : pendingAuthRetention ? (_jsx(AuthRetentionBox, { state: pendingAuthRetention, onSelect: onAuthRetentionSelect })) : (_jsxs(_Fragment, { children: [_jsx(InputBox, { busy: turnRunning, value: input, onChange: setInput, onSubmit: runPrompt }), showSlashSuggestions ? _jsx(SlashSuggestionBox, { suggestions: slashSuggestions }) : null] })), _jsx(Footer, { config: config, queuedPrompt: queuedPrompt, status: visibleStatus, session: session })] })) }));
|
|
528
768
|
}
|
|
529
769
|
function Dashboard({ config, session, status }) {
|
|
770
|
+
const activeProvider = providerConfigFor(config, session.provider);
|
|
530
771
|
const infoRows = [
|
|
772
|
+
{ item: "provider", value: providerLabel(session.provider) },
|
|
531
773
|
{ item: "model", value: session.model },
|
|
532
774
|
{ item: "directory", value: session.workspaceRoot },
|
|
533
775
|
{ item: "session", value: session.id.slice(0, 8) },
|
|
534
776
|
{ item: "updated", value: formatTimestamp(session.updatedAt) }
|
|
535
777
|
];
|
|
536
778
|
const stateRows = [
|
|
537
|
-
{ item: "auth", value: describeAuth(
|
|
538
|
-
{ item: "reasoning", value: formatReasoningEffort(config.reasoningEffort) },
|
|
779
|
+
{ item: "auth", value: describeAuth(activeProvider) },
|
|
780
|
+
{ item: "reasoning", value: formatReasoningEffort(config.reasoningEffort, session.provider) },
|
|
539
781
|
{ item: "skills", value: describeSkills(session.pinnedSkills.length) },
|
|
540
|
-
{ item: "
|
|
782
|
+
{ item: "undo", value: latestUndoableEdit(session) ? "ready" : "none" },
|
|
783
|
+
{ item: "sha256", value: activeProvider.authFingerprint?.slice(0, 12) ?? "(none)" },
|
|
541
784
|
{ item: "context", value: describeContext(session.messages.length) }
|
|
542
785
|
];
|
|
543
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Box, { borderStyle: "round", borderColor:
|
|
786
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Box, { borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "row", children: [_jsxs(Box, { flexDirection: "column", width: "60%", paddingRight: 2, children: [_jsx(Text, { color: UI_COLORS.accent, children: "Vetala" }), _jsx(Text, { bold: true, children: "Ready." }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: infoRows.map((row) => (_jsx(InfoRow, { item: row.item, value: row.value }, row.item))) })] }), _jsxs(Box, { flexDirection: "column", width: "40%", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Tips" }), _jsx(Text, { children: "/help for commands" }), _jsx(Text, { children: "/model for provider + model" }), _jsx(Text, { children: "/undo to revert last edit" }), _jsx(Text, { children: "/skill to inspect local skills" }), _jsx(Text, { children: "/logout to clear local auth" }), _jsx(Text, { children: "Ctrl+C to pause" }), _jsx(Text, { children: "Ctrl+D to exit" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: UI_COLORS.muted, children: ["status: ", status] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: UI_COLORS.accent, children: "Context" }) }), stateRows.map((row) => (_jsx(InfoRow, { item: row.item, value: row.value }, row.item)))] })] }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, children: _jsx(Text, { children: "Try \"explain this codebase\" or \"write a test for <filepath>\"" }) })] }));
|
|
544
787
|
}
|
|
545
788
|
function InfoRow({ item, value }) {
|
|
546
|
-
return (_jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { color:
|
|
789
|
+
return (_jsxs(Box, { children: [_jsx(Box, { width: 12, children: _jsx(Text, { color: UI_COLORS.muted, children: item }) }), _jsx(Text, { children: value })] }));
|
|
547
790
|
}
|
|
548
791
|
function TrustScreen({ workspaceRoot, onSelect }) {
|
|
549
|
-
return (_jsxs(Box, { borderStyle: "round", borderColor:
|
|
792
|
+
return (_jsxs(Box, { borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Accessing workspace" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: workspaceRoot }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { children: "Quick safety check: is this a project you created or one you trust? If not, review it before continuing." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Vetala will be able to read, edit, and execute files here." }) })] }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
|
|
550
793
|
{ label: "Yes, I trust this folder", value: "trust" },
|
|
551
794
|
{ label: "No, exit", value: "exit" }
|
|
552
795
|
], onSelect: (item) => void onSelect(item.value) }) })] }));
|
|
553
796
|
}
|
|
554
797
|
function ApprovalBox({ request, onSelect }) {
|
|
555
|
-
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor:
|
|
798
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.warning, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.warning, children: "Approval required" }), request.label.split("\n").map((line) => (_jsx(Text, { children: line }, line))), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
|
|
556
799
|
{ label: "Allow once", value: "once" },
|
|
557
800
|
{ label: "Allow for session", value: "session" },
|
|
558
801
|
{ label: "Deny", value: "deny" }
|
|
559
802
|
], onSelect: (item) => void onSelect(item.value) }) })] }));
|
|
560
803
|
}
|
|
561
|
-
function ModelPicker({
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
804
|
+
function ModelPicker({ currentProvider, onSelect }) {
|
|
805
|
+
const items = [
|
|
806
|
+
...listProviders().map((provider) => ({
|
|
807
|
+
label: provider.name === currentProvider ? `${provider.label} (current)` : provider.label,
|
|
808
|
+
value: provider.name
|
|
809
|
+
})),
|
|
810
|
+
{ label: "Cancel", value: "cancel" }
|
|
811
|
+
];
|
|
812
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Select provider" }), _jsxs(Text, { color: UI_COLORS.muted, children: ["Current: ", providerLabel(currentProvider)] }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: items, onSelect: (item) => void onSelect(item.value) }) })] }));
|
|
569
813
|
}
|
|
570
|
-
function
|
|
571
|
-
|
|
814
|
+
function SarvamModelPicker({ currentModel, onSelect }) {
|
|
815
|
+
const items = [
|
|
816
|
+
...getProviderDefinition("sarvam").suggestedModels.map((model) => ({
|
|
817
|
+
label: model === currentModel ? `${model} (current)` : model,
|
|
818
|
+
value: model
|
|
819
|
+
})),
|
|
820
|
+
{ label: "Cancel", value: "cancel" }
|
|
821
|
+
];
|
|
822
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Select a Sarvam model" }), _jsx(Text, { color: UI_COLORS.muted, children: "After model selection, Vetala will ask for reasoning effort." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: items, onSelect: (item) => void onSelect(item.value) }) })] }));
|
|
823
|
+
}
|
|
824
|
+
function OpenRouterModelIdBox({ state, onChange, onSubmit }) {
|
|
825
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Enter an OpenRouter model id" }), _jsx(Text, { color: UI_COLORS.muted, children: "Examples: `openai/gpt-4o-mini`, `anthropic/claude-3.5-haiku`, `google/gemini-2.0-flash-001`" }), _jsx(Text, { color: UI_COLORS.muted, children: "Reasoning differs by OpenRouter model. Vetala will use the provider default instead of forcing one global reasoning setting." }), _jsx(Text, { color: UI_COLORS.muted, children: "Press Enter on an empty field to cancel." }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: UI_COLORS.accent, children: "model " }), _jsx(TextInput, { highlightPastedText: false, value: state.value, onChange: onChange, onSubmit: (value) => void onSubmit(value) })] })] }));
|
|
826
|
+
}
|
|
827
|
+
function ReasoningEffortPicker({ currentValue, provider, model, onSelect }) {
|
|
828
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Select reasoning effort" }), _jsxs(Text, { color: UI_COLORS.muted, children: ["Provider: ", providerLabel(provider), " \u00B7 Model: ", model, " \u00B7 Current: ", formatReasoningEffort(currentValue, provider)] }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
|
|
572
829
|
{ label: "None (null / let Sarvam decide)", value: "none" },
|
|
573
830
|
{ label: "Low", value: "low" },
|
|
574
831
|
{ label: "Medium", value: "medium" },
|
|
@@ -577,10 +834,11 @@ function ReasoningEffortPicker({ currentValue, model, onSelect }) {
|
|
|
577
834
|
], onSelect: (item) => void onSelect(item.value) }) })] }));
|
|
578
835
|
}
|
|
579
836
|
function AuthInputBox({ state, onChange, onSubmit }) {
|
|
580
|
-
|
|
837
|
+
const definition = getProviderDefinition(state.provider);
|
|
838
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { color: UI_COLORS.accent, children: ["Enter ", definition.auth.inputLabel.toLowerCase(), " for ", providerLabel(state.provider), " / ", state.model] }), _jsxs(Text, { color: UI_COLORS.muted, children: [definition.auth.helpText, " After you press Enter, choose whether Vetala keeps it for all sessions or only for this session."] }), _jsxs(Text, { color: UI_COLORS.muted, children: ["Reasoning: ", formatReasoningEffort(state.reasoningEffort, state.provider)] }), _jsx(Text, { color: UI_COLORS.muted, children: "Press Enter on an empty field to cancel." }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: UI_COLORS.accent, children: "key " }), _jsx(TextInput, { mask: "*", highlightPastedText: false, value: state.value, onChange: onChange, onSubmit: (value) => void onSubmit(value) })] })] }));
|
|
581
839
|
}
|
|
582
840
|
function AuthRetentionBox({ state, onSelect }) {
|
|
583
|
-
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor:
|
|
841
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { color: UI_COLORS.accent, children: ["Keep credential for ", providerLabel(state.provider), " / ", state.model] }), _jsxs(Text, { color: UI_COLORS.muted, children: ["Key preview: ", maskSecretPreview(state.value)] }), _jsxs(Text, { color: UI_COLORS.muted, children: ["Reasoning: ", formatReasoningEffort(state.reasoningEffort, state.provider)] }), _jsx(Text, { color: UI_COLORS.muted, children: "Future-session mode stores the raw key locally until you run /logout." }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(Text, { children: "Choose how long Vetala should keep this key:" }) }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
|
|
584
842
|
{
|
|
585
843
|
label: "Keep for all sessions until /logout",
|
|
586
844
|
value: "persist"
|
|
@@ -596,36 +854,69 @@ function AuthRetentionBox({ state, onSelect }) {
|
|
|
596
854
|
], onSelect: (item) => void onSelect(item.value) }) })] }));
|
|
597
855
|
}
|
|
598
856
|
function InputBox({ busy, value, onChange, onSubmit }) {
|
|
599
|
-
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor:
|
|
857
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, children: [_jsx(Text, { color: UI_COLORS.accent, children: "\u276F " }), _jsx(TextInput, { highlightPastedText: false, value: value, onChange: onChange, onSubmit: (nextValue) => void onSubmit(nextValue) }), busy ? _jsx(Text, { color: UI_COLORS.muted, children: " Enter to queue or force-send." }) : null] }));
|
|
858
|
+
}
|
|
859
|
+
function BusyPromptBox({ prompt, onSelect }) {
|
|
860
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.warning, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.warning, children: "Current turn is still running" }), _jsx(Text, { color: UI_COLORS.muted, children: "Choose what to do with the next prompt:" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: summarizeUserPrompt(prompt).split("\n").map((line) => (_jsx(Text, { children: line }, line))) }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
|
|
861
|
+
{
|
|
862
|
+
label: "Send now (stop current turn)",
|
|
863
|
+
value: "force"
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
label: "Send after current turn",
|
|
867
|
+
value: "queue"
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
label: "Cancel",
|
|
871
|
+
value: "cancel"
|
|
872
|
+
}
|
|
873
|
+
], onSelect: (item) => void onSelect(item.value) }) })] }));
|
|
874
|
+
}
|
|
875
|
+
function UpdatePromptBox({ update, onSelect }) {
|
|
876
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.accent, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Update available" }), _jsxs(Text, { children: [update.currentVersion, " \u2192 ", update.latestVersion] }), _jsx(Text, { color: UI_COLORS.muted, children: update.installCommand }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
|
|
877
|
+
{
|
|
878
|
+
label: "Update now",
|
|
879
|
+
value: "update_now"
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
label: "Update later",
|
|
883
|
+
value: "update_later"
|
|
884
|
+
}
|
|
885
|
+
], onSelect: (item) => void onSelect(item.value) }) })] }));
|
|
886
|
+
}
|
|
887
|
+
function UpdateInstallBox({ update }) {
|
|
888
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.accent, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Installing update" }), _jsxs(Box, { children: [_jsx(Text, { color: UI_COLORS.accent, children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { children: [" Updating to ", update.latestVersion, "..."] })] }), _jsx(Text, { color: UI_COLORS.muted, children: update.installCommand })] }));
|
|
600
889
|
}
|
|
601
890
|
function SlashSuggestionBox({ suggestions }) {
|
|
602
|
-
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor:
|
|
891
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Commands" }), _jsx(Text, { color: UI_COLORS.muted, children: "Tab autocompletes the first match." }), suggestions.map((suggestion, index) => (_jsxs(Box, { children: [_jsx(Box, { width: "45%", children: index === 0 ? (_jsxs(Text, { color: UI_COLORS.accent, children: ["\u276F ", suggestion.label] })) : (_jsxs(Text, { children: [" ", suggestion.label] })) }), _jsx(Text, { color: UI_COLORS.muted, children: suggestion.detail })] }, suggestion.label)))] }));
|
|
603
892
|
}
|
|
604
893
|
function PauseBox() {
|
|
605
|
-
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor:
|
|
894
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: UI_COLORS.border, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.accent, children: "Paused" }), _jsx(Text, { children: "Press Ctrl+C again to resume." }), _jsx(Text, { children: "Press Ctrl+D if you want to exit." })] }));
|
|
606
895
|
}
|
|
607
896
|
function ExitConfirmBox({ onSelect }) {
|
|
608
|
-
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "red", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Exit Vetala?" }), _jsx(Text, { color:
|
|
897
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "red", paddingX: 1, flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Exit Vetala?" }), _jsx(Text, { color: UI_COLORS.muted, children: "Current session state is already written to disk as it changes." }), _jsx(Box, { marginTop: 1, children: _jsx(SelectInput, { items: [
|
|
609
898
|
{ label: "Exit", value: "exit" },
|
|
610
899
|
{ label: "Stay", value: "stay" }
|
|
611
900
|
], onSelect: (item) => void onSelect(item.value) }) })] }));
|
|
612
901
|
}
|
|
613
|
-
function Footer({ config, status, session }) {
|
|
614
|
-
|
|
902
|
+
function Footer({ config, queuedPrompt, status, session }) {
|
|
903
|
+
const activeProvider = providerConfigFor(config, session.provider);
|
|
904
|
+
return (_jsxs(Box, { marginTop: 1, justifyContent: "space-between", children: [_jsx(Text, { color: UI_COLORS.muted, children: "/help for commands \u00B7 /undo reverts last edit \u00B7 Ctrl+C pause \u00B7 Ctrl+D exit" }), _jsxs(Text, { color: UI_COLORS.muted, children: [status, queuedPrompt ? " · queued next prompt" : "", " \u00B7 ", describeAuth(activeProvider), " \u00B7 ", describeContext(session.messages.length)] })] }));
|
|
615
905
|
}
|
|
616
|
-
function
|
|
906
|
+
function TranscriptTurnCard({ card }) {
|
|
617
907
|
const borderColor = transcriptCardBorder(card.entries);
|
|
618
|
-
return (
|
|
908
|
+
return (_jsx(Box, { marginBottom: 1, borderStyle: "round", borderColor: borderColor, paddingX: 1, flexDirection: "column", children: card.entries.map((entry) => (_jsx(TranscriptSection, { entry: entry }, entry.id))) }));
|
|
619
909
|
}
|
|
620
910
|
function LiveStatusCard({ assistantBuffer, liveLabel }) {
|
|
621
|
-
return (_jsxs(Box, { marginBottom: 1, borderStyle: "round", borderColor:
|
|
911
|
+
return (_jsxs(Box, { marginBottom: 1, borderStyle: "round", borderColor: UI_COLORS.accent, paddingX: 1, flexDirection: "column", children: [liveLabel ? _jsx(LiveActivitySection, { label: liveLabel }) : null, assistantBuffer ? (_jsx(TranscriptSection, { entry: { id: "live:assistant", kind: "assistant", text: assistantBuffer } })) : null] }));
|
|
622
912
|
}
|
|
623
913
|
function TranscriptSection({ entry }) {
|
|
624
914
|
const isActivity = entry.kind === "activity";
|
|
625
|
-
|
|
915
|
+
const labelColor = entryColor(entry.kind);
|
|
916
|
+
return (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [labelColor ? _jsx(Text, { color: labelColor, children: entryLabel(entry.kind) }) : _jsx(Text, { children: entryLabel(entry.kind) }), entry.text.split("\n").map((line, index) => isActivity ? (_jsx(Text, { color: UI_COLORS.muted, children: line.length > 0 ? line : " " }, `${entry.id}:${index}`)) : (_jsx(Text, { children: line.length > 0 ? line : " " }, `${entry.id}:${index}`)))] }));
|
|
626
917
|
}
|
|
627
918
|
function LiveActivitySection({ label }) {
|
|
628
|
-
return (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color:
|
|
919
|
+
return (_jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [_jsx(Text, { color: UI_COLORS.muted, children: "doing" }), _jsxs(Box, { children: [_jsx(Text, { color: UI_COLORS.accent, children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { color: UI_COLORS.muted, children: [" ", label] })] })] }));
|
|
629
920
|
}
|
|
630
921
|
function cloneSession(session) {
|
|
631
922
|
return {
|
|
@@ -638,14 +929,15 @@ function cloneSession(session) {
|
|
|
638
929
|
messages: [...session.messages],
|
|
639
930
|
referencedFiles: [...session.referencedFiles],
|
|
640
931
|
readFiles: [...session.readFiles],
|
|
641
|
-
pinnedSkills: [...session.pinnedSkills]
|
|
932
|
+
pinnedSkills: [...session.pinnedSkills],
|
|
933
|
+
edits: session.edits.map((edit) => ({ ...edit }))
|
|
642
934
|
};
|
|
643
935
|
}
|
|
644
936
|
function formatSessionList(sessions) {
|
|
645
937
|
return sessions.length > 0
|
|
646
938
|
? sessions
|
|
647
939
|
.slice(0, 10)
|
|
648
|
-
.map((item) => `${item.id} ${item.updatedAt} ${item.workspaceRoot}`)
|
|
940
|
+
.map((item) => `${item.id} ${item.provider}/${item.model} ${item.updatedAt} ${item.workspaceRoot}`)
|
|
649
941
|
.join("\n")
|
|
650
942
|
: "(no sessions)";
|
|
651
943
|
}
|
|
@@ -667,18 +959,23 @@ function describeAuth(config) {
|
|
|
667
959
|
return "missing";
|
|
668
960
|
}
|
|
669
961
|
}
|
|
670
|
-
function formatReasoningEffort(value) {
|
|
962
|
+
function formatReasoningEffort(value, provider) {
|
|
963
|
+
if (!getProviderDefinition(provider).supportsReasoningEffort) {
|
|
964
|
+
return "provider default (model-specific)";
|
|
965
|
+
}
|
|
671
966
|
return value ?? "(none)";
|
|
672
967
|
}
|
|
673
968
|
function describeSkills(pinnedCount) {
|
|
674
969
|
return pinnedCount > 0 ? `${pinnedCount} pinned` : "none pinned";
|
|
675
970
|
}
|
|
676
971
|
function formatModelSetupSummary(state, config, authRetention) {
|
|
972
|
+
const profile = providerConfigFor(config, state.provider);
|
|
677
973
|
const lines = [
|
|
974
|
+
`Provider: ${providerLabel(state.provider)}`,
|
|
678
975
|
`Model: ${state.model}`,
|
|
679
|
-
`Reasoning effort: ${formatReasoningEffort(state.reasoningEffort)}`,
|
|
680
|
-
`Credential: ${describeAuth(
|
|
681
|
-
`Stored SHA-256: ${
|
|
976
|
+
`Reasoning effort: ${formatReasoningEffort(state.reasoningEffort, state.provider)}`,
|
|
977
|
+
`Credential: ${describeAuth(profile)}`,
|
|
978
|
+
`Stored SHA-256: ${profile.authFingerprint?.slice(0, 16) ?? "(none)"}`
|
|
682
979
|
];
|
|
683
980
|
if (authRetention === "persist") {
|
|
684
981
|
lines.push("Raw key is stored locally for all future sessions until /logout.");
|
|
@@ -706,6 +1003,20 @@ function summarizeUserPrompt(prompt) {
|
|
|
706
1003
|
`Preview: ${preview}${preview.length < prompt.replace(/\s+/g, " ").trim().length ? "..." : ""}`
|
|
707
1004
|
].join("\n");
|
|
708
1005
|
}
|
|
1006
|
+
function renderLiveAssistantBuffer(buffer) {
|
|
1007
|
+
if (!buffer) {
|
|
1008
|
+
return "";
|
|
1009
|
+
}
|
|
1010
|
+
const lines = buffer.split("\n");
|
|
1011
|
+
if (lines.length <= MAX_LIVE_ASSISTANT_LINES) {
|
|
1012
|
+
return buffer;
|
|
1013
|
+
}
|
|
1014
|
+
const hiddenLineCount = lines.length - MAX_LIVE_ASSISTANT_LINES;
|
|
1015
|
+
return [
|
|
1016
|
+
`[${hiddenLineCount} earlier line${hiddenLineCount === 1 ? "" : "s"} hidden while streaming]`,
|
|
1017
|
+
...lines.slice(-MAX_LIVE_ASSISTANT_LINES)
|
|
1018
|
+
].join("\n");
|
|
1019
|
+
}
|
|
709
1020
|
function renderAuthMode(authMode) {
|
|
710
1021
|
switch (authMode) {
|
|
711
1022
|
case "bearer":
|
|
@@ -732,19 +1043,19 @@ function maskSecretPreview(value) {
|
|
|
732
1043
|
function entryColor(kind) {
|
|
733
1044
|
switch (kind) {
|
|
734
1045
|
case "assistant":
|
|
735
|
-
return
|
|
1046
|
+
return UI_COLORS.accent;
|
|
736
1047
|
case "user":
|
|
737
|
-
return
|
|
1048
|
+
return undefined;
|
|
738
1049
|
case "tool":
|
|
739
|
-
return
|
|
1050
|
+
return UI_COLORS.accent;
|
|
740
1051
|
case "activity":
|
|
741
|
-
return
|
|
1052
|
+
return UI_COLORS.muted;
|
|
742
1053
|
case "info":
|
|
743
|
-
return
|
|
1054
|
+
return UI_COLORS.accent;
|
|
744
1055
|
case "warn":
|
|
745
|
-
return
|
|
1056
|
+
return UI_COLORS.warning;
|
|
746
1057
|
case "error":
|
|
747
|
-
return
|
|
1058
|
+
return UI_COLORS.danger;
|
|
748
1059
|
}
|
|
749
1060
|
}
|
|
750
1061
|
function entryLabel(kind) {
|
|
@@ -767,23 +1078,20 @@ function entryLabel(kind) {
|
|
|
767
1078
|
}
|
|
768
1079
|
function transcriptCardBorder(entries) {
|
|
769
1080
|
if (entries.some((entry) => entry.kind === "error")) {
|
|
770
|
-
return
|
|
1081
|
+
return UI_COLORS.danger;
|
|
771
1082
|
}
|
|
772
1083
|
if (entries.some((entry) => entry.kind === "warn")) {
|
|
773
|
-
return
|
|
1084
|
+
return UI_COLORS.warning;
|
|
774
1085
|
}
|
|
775
1086
|
if (entries.some((entry) => entry.kind === "tool")) {
|
|
776
|
-
return
|
|
777
|
-
}
|
|
778
|
-
if (entries.some((entry) => entry.kind === "assistant")) {
|
|
779
|
-
return "white";
|
|
1087
|
+
return UI_COLORS.accent;
|
|
780
1088
|
}
|
|
781
1089
|
if (entries.some((entry) => entry.kind === "info")) {
|
|
782
|
-
return
|
|
1090
|
+
return UI_COLORS.accent;
|
|
783
1091
|
}
|
|
784
1092
|
if (entries.some((entry) => entry.kind === "activity")) {
|
|
785
|
-
return
|
|
1093
|
+
return UI_COLORS.muted;
|
|
786
1094
|
}
|
|
787
|
-
return
|
|
1095
|
+
return UI_COLORS.border;
|
|
788
1096
|
}
|
|
789
1097
|
//# sourceMappingURL=repl-app.js.map
|