@quantish/agent 0.1.20 → 0.1.22
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/index.js +482 -31
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3261,17 +3261,38 @@ var OpenRouterProvider = class {
|
|
|
3261
3261
|
const toolCalls = [];
|
|
3262
3262
|
for (const [, tc] of toolCallsInProgress) {
|
|
3263
3263
|
try {
|
|
3264
|
+
if (!tc || !tc.name) {
|
|
3265
|
+
continue;
|
|
3266
|
+
}
|
|
3264
3267
|
let args = tc.arguments?.trim() || "{}";
|
|
3265
|
-
if (args && !args.endsWith("}")) {
|
|
3266
|
-
|
|
3268
|
+
if (args && !args.endsWith("}") && !args.endsWith("]")) {
|
|
3269
|
+
const openBraces = (args.match(/{/g) || []).length;
|
|
3270
|
+
const closeBraces = (args.match(/}/g) || []).length;
|
|
3271
|
+
if (openBraces > closeBraces) {
|
|
3272
|
+
args = args + "}".repeat(openBraces - closeBraces);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
if (!args || args === "" || args === "undefined") {
|
|
3276
|
+
args = "{}";
|
|
3267
3277
|
}
|
|
3268
3278
|
const input = JSON.parse(args);
|
|
3269
|
-
|
|
3270
|
-
|
|
3279
|
+
const toolId = tc.id || `tool_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
3280
|
+
toolCalls.push({ id: toolId, name: tc.name, input });
|
|
3281
|
+
callbacks.onToolCall?.(toolId, tc.name, input);
|
|
3271
3282
|
} catch (e) {
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3283
|
+
const toolName = tc?.name || "unknown_tool";
|
|
3284
|
+
let parsedInput = {};
|
|
3285
|
+
try {
|
|
3286
|
+
const argsStr = tc?.arguments || "";
|
|
3287
|
+
const matches = argsStr.matchAll(/(\w+):\s*"([^"]+)"/g);
|
|
3288
|
+
for (const match of matches) {
|
|
3289
|
+
parsedInput[match[1]] = match[2];
|
|
3290
|
+
}
|
|
3291
|
+
} catch {
|
|
3292
|
+
}
|
|
3293
|
+
const toolId = tc?.id || `tool_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
3294
|
+
toolCalls.push({ id: toolId, name: toolName, input: parsedInput });
|
|
3295
|
+
callbacks.onToolCall?.(toolId, toolName, parsedInput);
|
|
3275
3296
|
}
|
|
3276
3297
|
}
|
|
3277
3298
|
const cost = calculateOpenRouterCost(
|
|
@@ -4166,6 +4187,18 @@ ${userMessage}`;
|
|
|
4166
4187
|
setHistory(history) {
|
|
4167
4188
|
this.conversationHistory = history;
|
|
4168
4189
|
}
|
|
4190
|
+
/**
|
|
4191
|
+
* Get conversation history (alias for getHistory)
|
|
4192
|
+
*/
|
|
4193
|
+
getConversationHistory() {
|
|
4194
|
+
return this.getHistory();
|
|
4195
|
+
}
|
|
4196
|
+
/**
|
|
4197
|
+
* Set conversation history (alias for setHistory)
|
|
4198
|
+
*/
|
|
4199
|
+
setConversationHistory(history) {
|
|
4200
|
+
this.setHistory(history);
|
|
4201
|
+
}
|
|
4169
4202
|
};
|
|
4170
4203
|
function createAgent(config) {
|
|
4171
4204
|
return new Agent(config);
|
|
@@ -4224,10 +4257,204 @@ function tableRow(label, value, width = 20) {
|
|
|
4224
4257
|
}
|
|
4225
4258
|
|
|
4226
4259
|
// src/ui/App.tsx
|
|
4227
|
-
import { useState, useCallback, useRef, useEffect } from "react";
|
|
4260
|
+
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
|
|
4228
4261
|
import { Box, Text, useApp, useInput } from "ink";
|
|
4229
4262
|
import TextInput from "ink-text-input";
|
|
4230
4263
|
import Spinner from "ink-spinner";
|
|
4264
|
+
|
|
4265
|
+
// src/config/sessions.ts
|
|
4266
|
+
import { homedir as homedir2 } from "os";
|
|
4267
|
+
import { join as join3 } from "path";
|
|
4268
|
+
import { existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync, readFileSync } from "fs";
|
|
4269
|
+
var SESSIONS_DIR = join3(homedir2(), ".quantish", "sessions");
|
|
4270
|
+
var INDEX_FILE = join3(SESSIONS_DIR, "index.json");
|
|
4271
|
+
function ensureSessionsDir() {
|
|
4272
|
+
if (!existsSync2(SESSIONS_DIR)) {
|
|
4273
|
+
mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
function generateSessionId() {
|
|
4277
|
+
const timestamp = Date.now().toString(36);
|
|
4278
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
4279
|
+
return `${timestamp}-${random}`;
|
|
4280
|
+
}
|
|
4281
|
+
function loadIndex() {
|
|
4282
|
+
ensureSessionsDir();
|
|
4283
|
+
if (!existsSync2(INDEX_FILE)) {
|
|
4284
|
+
return { lastSessionId: null, sessions: [] };
|
|
4285
|
+
}
|
|
4286
|
+
try {
|
|
4287
|
+
const data = readFileSync(INDEX_FILE, "utf-8");
|
|
4288
|
+
return JSON.parse(data);
|
|
4289
|
+
} catch {
|
|
4290
|
+
return { lastSessionId: null, sessions: [] };
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
function saveIndex(index) {
|
|
4294
|
+
ensureSessionsDir();
|
|
4295
|
+
writeFileSync(INDEX_FILE, JSON.stringify(index, null, 2), "utf-8");
|
|
4296
|
+
}
|
|
4297
|
+
function getSessionPath(id) {
|
|
4298
|
+
return join3(SESSIONS_DIR, `${id}.json`);
|
|
4299
|
+
}
|
|
4300
|
+
var SessionManager = class {
|
|
4301
|
+
/**
|
|
4302
|
+
* Save a new session or update an existing one
|
|
4303
|
+
*/
|
|
4304
|
+
saveSession(messages, model, provider, name, existingId) {
|
|
4305
|
+
ensureSessionsDir();
|
|
4306
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4307
|
+
const id = existingId || generateSessionId();
|
|
4308
|
+
const sessionName = name || `Session ${(/* @__PURE__ */ new Date()).toLocaleDateString()} ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}`;
|
|
4309
|
+
const session = {
|
|
4310
|
+
id,
|
|
4311
|
+
name: sessionName,
|
|
4312
|
+
createdAt: existingId ? this.getSession(existingId)?.createdAt || now : now,
|
|
4313
|
+
updatedAt: now,
|
|
4314
|
+
messages,
|
|
4315
|
+
model,
|
|
4316
|
+
provider,
|
|
4317
|
+
tokenCount: this.estimateTokenCount(messages)
|
|
4318
|
+
};
|
|
4319
|
+
const sessionPath = getSessionPath(id);
|
|
4320
|
+
writeFileSync(sessionPath, JSON.stringify(session, null, 2), "utf-8");
|
|
4321
|
+
const index = loadIndex();
|
|
4322
|
+
index.lastSessionId = id;
|
|
4323
|
+
const existingIndex = index.sessions.findIndex((s) => s.id === id);
|
|
4324
|
+
const sessionMeta = {
|
|
4325
|
+
id,
|
|
4326
|
+
name: sessionName,
|
|
4327
|
+
createdAt: session.createdAt,
|
|
4328
|
+
updatedAt: now,
|
|
4329
|
+
messageCount: messages.length
|
|
4330
|
+
};
|
|
4331
|
+
if (existingIndex >= 0) {
|
|
4332
|
+
index.sessions[existingIndex] = sessionMeta;
|
|
4333
|
+
} else {
|
|
4334
|
+
index.sessions.unshift(sessionMeta);
|
|
4335
|
+
}
|
|
4336
|
+
if (index.sessions.length > 50) {
|
|
4337
|
+
const toRemove = index.sessions.splice(50);
|
|
4338
|
+
for (const s of toRemove) {
|
|
4339
|
+
try {
|
|
4340
|
+
unlinkSync(getSessionPath(s.id));
|
|
4341
|
+
} catch {
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
saveIndex(index);
|
|
4346
|
+
return session;
|
|
4347
|
+
}
|
|
4348
|
+
/**
|
|
4349
|
+
* Get a session by ID
|
|
4350
|
+
*/
|
|
4351
|
+
getSession(id) {
|
|
4352
|
+
const sessionPath = getSessionPath(id);
|
|
4353
|
+
if (!existsSync2(sessionPath)) {
|
|
4354
|
+
return null;
|
|
4355
|
+
}
|
|
4356
|
+
try {
|
|
4357
|
+
const data = readFileSync(sessionPath, "utf-8");
|
|
4358
|
+
return JSON.parse(data);
|
|
4359
|
+
} catch {
|
|
4360
|
+
return null;
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
/**
|
|
4364
|
+
* Get the last session
|
|
4365
|
+
*/
|
|
4366
|
+
getLastSession() {
|
|
4367
|
+
const index = loadIndex();
|
|
4368
|
+
if (!index.lastSessionId) {
|
|
4369
|
+
return null;
|
|
4370
|
+
}
|
|
4371
|
+
return this.getSession(index.lastSessionId);
|
|
4372
|
+
}
|
|
4373
|
+
/**
|
|
4374
|
+
* Get a session by name
|
|
4375
|
+
*/
|
|
4376
|
+
getSessionByName(name) {
|
|
4377
|
+
const index = loadIndex();
|
|
4378
|
+
const session = index.sessions.find(
|
|
4379
|
+
(s) => s.name.toLowerCase() === name.toLowerCase()
|
|
4380
|
+
);
|
|
4381
|
+
if (!session) {
|
|
4382
|
+
return null;
|
|
4383
|
+
}
|
|
4384
|
+
return this.getSession(session.id);
|
|
4385
|
+
}
|
|
4386
|
+
/**
|
|
4387
|
+
* List all sessions
|
|
4388
|
+
*/
|
|
4389
|
+
listSessions() {
|
|
4390
|
+
const index = loadIndex();
|
|
4391
|
+
return index.sessions;
|
|
4392
|
+
}
|
|
4393
|
+
/**
|
|
4394
|
+
* Delete a session
|
|
4395
|
+
*/
|
|
4396
|
+
deleteSession(id) {
|
|
4397
|
+
const sessionPath = getSessionPath(id);
|
|
4398
|
+
if (!existsSync2(sessionPath)) {
|
|
4399
|
+
return false;
|
|
4400
|
+
}
|
|
4401
|
+
try {
|
|
4402
|
+
unlinkSync(sessionPath);
|
|
4403
|
+
const index = loadIndex();
|
|
4404
|
+
index.sessions = index.sessions.filter((s) => s.id !== id);
|
|
4405
|
+
if (index.lastSessionId === id) {
|
|
4406
|
+
index.lastSessionId = index.sessions[0]?.id || null;
|
|
4407
|
+
}
|
|
4408
|
+
saveIndex(index);
|
|
4409
|
+
return true;
|
|
4410
|
+
} catch {
|
|
4411
|
+
return false;
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
/**
|
|
4415
|
+
* Clear all sessions
|
|
4416
|
+
*/
|
|
4417
|
+
clearAllSessions() {
|
|
4418
|
+
ensureSessionsDir();
|
|
4419
|
+
try {
|
|
4420
|
+
const files = readdirSync(SESSIONS_DIR);
|
|
4421
|
+
for (const file of files) {
|
|
4422
|
+
try {
|
|
4423
|
+
unlinkSync(join3(SESSIONS_DIR, file));
|
|
4424
|
+
} catch {
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
} catch {
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
/**
|
|
4431
|
+
* Estimate token count from messages (rough estimate)
|
|
4432
|
+
*/
|
|
4433
|
+
estimateTokenCount(messages) {
|
|
4434
|
+
let totalChars = 0;
|
|
4435
|
+
for (const msg of messages) {
|
|
4436
|
+
if (typeof msg.content === "string") {
|
|
4437
|
+
totalChars += msg.content.length;
|
|
4438
|
+
} else if (Array.isArray(msg.content)) {
|
|
4439
|
+
for (const block of msg.content) {
|
|
4440
|
+
if ("text" in block && typeof block.text === "string") {
|
|
4441
|
+
totalChars += block.text.length;
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
return Math.ceil(totalChars / 4);
|
|
4447
|
+
}
|
|
4448
|
+
};
|
|
4449
|
+
var sessionManager = null;
|
|
4450
|
+
function getSessionManager() {
|
|
4451
|
+
if (!sessionManager) {
|
|
4452
|
+
sessionManager = new SessionManager();
|
|
4453
|
+
}
|
|
4454
|
+
return sessionManager;
|
|
4455
|
+
}
|
|
4456
|
+
|
|
4457
|
+
// src/ui/App.tsx
|
|
4231
4458
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4232
4459
|
function formatTokenCount(count) {
|
|
4233
4460
|
if (count < 1e3) return String(count);
|
|
@@ -4279,6 +4506,10 @@ function formatResult(result, maxLength = 200) {
|
|
|
4279
4506
|
}
|
|
4280
4507
|
return String(result);
|
|
4281
4508
|
}
|
|
4509
|
+
function cleanModelOutput(text) {
|
|
4510
|
+
if (!text) return text;
|
|
4511
|
+
return text.replace(/<tool_call>/g, "").replace(/<\/tool_call>/g, "").replace(/<arg_key>/g, "").replace(/<\/arg_key>/g, "").replace(/<function_call>/g, "").replace(/<\/function_call>/g, "").trim();
|
|
4512
|
+
}
|
|
4282
4513
|
function App({ agent, onExit }) {
|
|
4283
4514
|
const { exit } = useApp();
|
|
4284
4515
|
const [messages, setMessages] = useState([]);
|
|
@@ -4300,6 +4531,10 @@ function App({ agent, onExit }) {
|
|
|
4300
4531
|
});
|
|
4301
4532
|
const completedToolCalls = useRef([]);
|
|
4302
4533
|
const abortController = useRef(null);
|
|
4534
|
+
const [queuedInput, setQueuedInput] = useState("");
|
|
4535
|
+
const [hasQueuedMessage, setHasQueuedMessage] = useState(false);
|
|
4536
|
+
const sessionManager2 = useMemo(() => getSessionManager(), []);
|
|
4537
|
+
const [currentSessionId, setCurrentSessionId] = useState(null);
|
|
4303
4538
|
const handleSlashCommand = useCallback((command) => {
|
|
4304
4539
|
const cmd = command.slice(1).toLowerCase().split(" ")[0];
|
|
4305
4540
|
const args = command.slice(cmd.length + 2).trim();
|
|
@@ -4310,20 +4545,34 @@ function App({ agent, onExit }) {
|
|
|
4310
4545
|
content: `\u{1F4DA} Available Commands:
|
|
4311
4546
|
/clear - Clear conversation history
|
|
4312
4547
|
/compact - Summarize conversation (keeps context, saves tokens)
|
|
4313
|
-
/model - Switch model (opus, sonnet, haiku, minimax,
|
|
4548
|
+
/model - Switch model (opus, sonnet, haiku, minimax, etc.)
|
|
4314
4549
|
/provider - Switch LLM provider (anthropic, openrouter)
|
|
4315
4550
|
/cost - Show session cost breakdown
|
|
4316
4551
|
/help - Show this help message
|
|
4317
4552
|
/tools - List available tools
|
|
4318
4553
|
/config - Show configuration info
|
|
4554
|
+
|
|
4555
|
+
\u{1F5C2}\uFE0F Session Commands:
|
|
4556
|
+
/save [name] - Save current session
|
|
4557
|
+
/resume - Resume last session
|
|
4558
|
+
/sessions - List saved sessions
|
|
4559
|
+
/load <id> - Load a saved session
|
|
4560
|
+
/forget - Delete all saved sessions
|
|
4561
|
+
|
|
4562
|
+
\u{1F4CB} Process Commands:
|
|
4319
4563
|
/processes - List running background processes
|
|
4320
4564
|
/stop <id> - Stop a background process by ID
|
|
4321
4565
|
/stopall - Stop all background processes
|
|
4566
|
+
|
|
4322
4567
|
/exit - Exit the CLI
|
|
4323
4568
|
|
|
4324
4569
|
\u2328\uFE0F Keyboard Shortcuts:
|
|
4325
|
-
Esc - Interrupt current generation
|
|
4326
|
-
|
|
4570
|
+
Esc - Interrupt current generation (or send queued message)
|
|
4571
|
+
Enter - Queue message while agent is working
|
|
4572
|
+
Ctrl+C - Exit (stops all processes)
|
|
4573
|
+
|
|
4574
|
+
\u{1F4A1} Tip: You can type while the agent is working. Press Enter to queue
|
|
4575
|
+
your message. Press Esc to interrupt and send immediately.`
|
|
4327
4576
|
}]);
|
|
4328
4577
|
return true;
|
|
4329
4578
|
case "clear":
|
|
@@ -4573,8 +4822,169 @@ Last API Call Cost:
|
|
|
4573
4822
|
\u{1F4A1} Tip: Use /model haiku for cheaper operations, /compact to reduce context.`
|
|
4574
4823
|
}]);
|
|
4575
4824
|
return true;
|
|
4825
|
+
case "save":
|
|
4826
|
+
try {
|
|
4827
|
+
const conversationHistory = agent.getConversationHistory();
|
|
4828
|
+
if (conversationHistory.length === 0) {
|
|
4829
|
+
setMessages((prev) => [...prev, {
|
|
4830
|
+
role: "system",
|
|
4831
|
+
content: "\u274C Nothing to save - conversation is empty."
|
|
4832
|
+
}]);
|
|
4833
|
+
return true;
|
|
4834
|
+
}
|
|
4835
|
+
const savedSession = sessionManager2.saveSession(
|
|
4836
|
+
conversationHistory,
|
|
4837
|
+
agent.getModel(),
|
|
4838
|
+
agent.getProvider(),
|
|
4839
|
+
args || void 0,
|
|
4840
|
+
currentSessionId || void 0
|
|
4841
|
+
);
|
|
4842
|
+
setCurrentSessionId(savedSession.id);
|
|
4843
|
+
setMessages((prev) => [...prev, {
|
|
4844
|
+
role: "system",
|
|
4845
|
+
content: `\u2705 Session saved: "${savedSession.name}"
|
|
4846
|
+
ID: ${savedSession.id}
|
|
4847
|
+
Messages: ${conversationHistory.length}`
|
|
4848
|
+
}]);
|
|
4849
|
+
} catch (err) {
|
|
4850
|
+
setMessages((prev) => [...prev, {
|
|
4851
|
+
role: "system",
|
|
4852
|
+
content: `\u274C Failed to save session: ${err instanceof Error ? err.message : String(err)}`
|
|
4853
|
+
}]);
|
|
4854
|
+
}
|
|
4855
|
+
return true;
|
|
4856
|
+
case "resume":
|
|
4857
|
+
try {
|
|
4858
|
+
const lastSession = sessionManager2.getLastSession();
|
|
4859
|
+
if (!lastSession) {
|
|
4860
|
+
setMessages((prev) => [...prev, {
|
|
4861
|
+
role: "system",
|
|
4862
|
+
content: "\u274C No previous session to resume."
|
|
4863
|
+
}]);
|
|
4864
|
+
return true;
|
|
4865
|
+
}
|
|
4866
|
+
agent.setConversationHistory(lastSession.messages);
|
|
4867
|
+
agent.setModel(lastSession.model);
|
|
4868
|
+
if (lastSession.provider) {
|
|
4869
|
+
agent.setProvider(lastSession.provider);
|
|
4870
|
+
}
|
|
4871
|
+
setCurrentSessionId(lastSession.id);
|
|
4872
|
+
setMessages([{
|
|
4873
|
+
role: "system",
|
|
4874
|
+
content: `\u2705 Resumed session: "${lastSession.name}"
|
|
4875
|
+
${lastSession.messages.length} messages loaded
|
|
4876
|
+
Model: ${lastSession.model} (${lastSession.provider})`
|
|
4877
|
+
}]);
|
|
4878
|
+
} catch (err) {
|
|
4879
|
+
setMessages((prev) => [...prev, {
|
|
4880
|
+
role: "system",
|
|
4881
|
+
content: `\u274C Failed to resume session: ${err instanceof Error ? err.message : String(err)}`
|
|
4882
|
+
}]);
|
|
4883
|
+
}
|
|
4884
|
+
return true;
|
|
4885
|
+
case "sessions":
|
|
4886
|
+
try {
|
|
4887
|
+
const sessions = sessionManager2.listSessions();
|
|
4888
|
+
if (sessions.length === 0) {
|
|
4889
|
+
setMessages((prev) => [...prev, {
|
|
4890
|
+
role: "system",
|
|
4891
|
+
content: "\u{1F4CB} No saved sessions."
|
|
4892
|
+
}]);
|
|
4893
|
+
return true;
|
|
4894
|
+
}
|
|
4895
|
+
const sessionList = sessions.slice(0, 10).map((s, i) => {
|
|
4896
|
+
const isCurrent = s.id === currentSessionId ? " (current)" : "";
|
|
4897
|
+
const date = new Date(s.updatedAt).toLocaleDateString();
|
|
4898
|
+
return ` ${i + 1}. ${s.name}${isCurrent}
|
|
4899
|
+
ID: ${s.id} | ${s.messageCount} msgs | ${date}`;
|
|
4900
|
+
}).join("\n\n");
|
|
4901
|
+
const moreText = sessions.length > 10 ? `
|
|
4902
|
+
|
|
4903
|
+
... and ${sessions.length - 10} more` : "";
|
|
4904
|
+
setMessages((prev) => [...prev, {
|
|
4905
|
+
role: "system",
|
|
4906
|
+
content: `\u{1F5C2}\uFE0F Saved Sessions:
|
|
4907
|
+
|
|
4908
|
+
${sessionList}${moreText}
|
|
4909
|
+
|
|
4910
|
+
Use /load <id> to load a session.`
|
|
4911
|
+
}]);
|
|
4912
|
+
} catch (err) {
|
|
4913
|
+
setMessages((prev) => [...prev, {
|
|
4914
|
+
role: "system",
|
|
4915
|
+
content: `\u274C Failed to list sessions: ${err instanceof Error ? err.message : String(err)}`
|
|
4916
|
+
}]);
|
|
4917
|
+
}
|
|
4918
|
+
return true;
|
|
4919
|
+
case "load":
|
|
4920
|
+
if (!args) {
|
|
4921
|
+
setMessages((prev) => [...prev, {
|
|
4922
|
+
role: "system",
|
|
4923
|
+
content: "\u2753 Usage: /load <session_id>\n Use /sessions to see saved sessions."
|
|
4924
|
+
}]);
|
|
4925
|
+
return true;
|
|
4926
|
+
}
|
|
4927
|
+
try {
|
|
4928
|
+
let loadSession = sessionManager2.getSession(args);
|
|
4929
|
+
if (!loadSession) {
|
|
4930
|
+
loadSession = sessionManager2.getSessionByName(args);
|
|
4931
|
+
}
|
|
4932
|
+
if (!loadSession) {
|
|
4933
|
+
setMessages((prev) => [...prev, {
|
|
4934
|
+
role: "system",
|
|
4935
|
+
content: `\u274C Session not found: "${args}"`
|
|
4936
|
+
}]);
|
|
4937
|
+
return true;
|
|
4938
|
+
}
|
|
4939
|
+
agent.setConversationHistory(loadSession.messages);
|
|
4940
|
+
agent.setModel(loadSession.model);
|
|
4941
|
+
if (loadSession.provider) {
|
|
4942
|
+
agent.setProvider(loadSession.provider);
|
|
4943
|
+
}
|
|
4944
|
+
setCurrentSessionId(loadSession.id);
|
|
4945
|
+
setMessages([{
|
|
4946
|
+
role: "system",
|
|
4947
|
+
content: `\u2705 Loaded session: "${loadSession.name}"
|
|
4948
|
+
${loadSession.messages.length} messages loaded
|
|
4949
|
+
Model: ${loadSession.model} (${loadSession.provider})`
|
|
4950
|
+
}]);
|
|
4951
|
+
} catch (err) {
|
|
4952
|
+
setMessages((prev) => [...prev, {
|
|
4953
|
+
role: "system",
|
|
4954
|
+
content: `\u274C Failed to load session: ${err instanceof Error ? err.message : String(err)}`
|
|
4955
|
+
}]);
|
|
4956
|
+
}
|
|
4957
|
+
return true;
|
|
4958
|
+
case "forget":
|
|
4959
|
+
try {
|
|
4960
|
+
sessionManager2.clearAllSessions();
|
|
4961
|
+
setCurrentSessionId(null);
|
|
4962
|
+
setMessages((prev) => [...prev, {
|
|
4963
|
+
role: "system",
|
|
4964
|
+
content: "\u2705 All sessions deleted."
|
|
4965
|
+
}]);
|
|
4966
|
+
} catch (err) {
|
|
4967
|
+
setMessages((prev) => [...prev, {
|
|
4968
|
+
role: "system",
|
|
4969
|
+
content: `\u274C Failed to clear sessions: ${err instanceof Error ? err.message : String(err)}`
|
|
4970
|
+
}]);
|
|
4971
|
+
}
|
|
4972
|
+
return true;
|
|
4576
4973
|
case "exit":
|
|
4577
4974
|
case "quit":
|
|
4975
|
+
try {
|
|
4976
|
+
const history = agent.getConversationHistory();
|
|
4977
|
+
if (history.length > 0) {
|
|
4978
|
+
sessionManager2.saveSession(
|
|
4979
|
+
history,
|
|
4980
|
+
agent.getModel(),
|
|
4981
|
+
agent.getProvider(),
|
|
4982
|
+
void 0,
|
|
4983
|
+
currentSessionId || void 0
|
|
4984
|
+
);
|
|
4985
|
+
}
|
|
4986
|
+
} catch {
|
|
4987
|
+
}
|
|
4578
4988
|
if (processManager.hasRunning()) {
|
|
4579
4989
|
processManager.killAll();
|
|
4580
4990
|
}
|
|
@@ -4591,7 +5001,18 @@ Last API Call Cost:
|
|
|
4591
5001
|
}, [agent, onExit, exit]);
|
|
4592
5002
|
const handleSubmit = useCallback(async (value) => {
|
|
4593
5003
|
const trimmed = value.trim();
|
|
4594
|
-
if (!trimmed
|
|
5004
|
+
if (!trimmed) return;
|
|
5005
|
+
if (isProcessing) {
|
|
5006
|
+
setQueuedInput(trimmed);
|
|
5007
|
+
setHasQueuedMessage(true);
|
|
5008
|
+
setInput("");
|
|
5009
|
+
setMessages((prev) => [...prev, {
|
|
5010
|
+
role: "system",
|
|
5011
|
+
content: `\u{1F4E5} Queued: "${trimmed.length > 50 ? trimmed.slice(0, 50) + "..." : trimmed}"
|
|
5012
|
+
Press Esc to interrupt and send now.`
|
|
5013
|
+
}]);
|
|
5014
|
+
return;
|
|
5015
|
+
}
|
|
4595
5016
|
if (trimmed.startsWith("/")) {
|
|
4596
5017
|
setInput("");
|
|
4597
5018
|
handleSlashCommand(trimmed);
|
|
@@ -4630,9 +5051,10 @@ Last API Call Cost:
|
|
|
4630
5051
|
} else {
|
|
4631
5052
|
setMessages((prev) => {
|
|
4632
5053
|
const filtered = prev.filter((m) => !m.isStreaming);
|
|
5054
|
+
const cleanedText = cleanModelOutput(result.text || "");
|
|
4633
5055
|
return [...filtered, {
|
|
4634
5056
|
role: "assistant",
|
|
4635
|
-
content:
|
|
5057
|
+
content: cleanedText || "(completed)",
|
|
4636
5058
|
toolCalls: result.toolCalls.map((tc) => ({
|
|
4637
5059
|
name: tc.name,
|
|
4638
5060
|
args: tc.input,
|
|
@@ -4646,21 +5068,25 @@ Last API Call Cost:
|
|
|
4646
5068
|
setStreamingText("");
|
|
4647
5069
|
setCurrentToolCalls([]);
|
|
4648
5070
|
} catch (err) {
|
|
4649
|
-
const errorMsg = err.message
|
|
5071
|
+
const errorMsg = err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error occurred";
|
|
4650
5072
|
let displayError = errorMsg;
|
|
4651
|
-
|
|
5073
|
+
const msgLower = errorMsg.toLowerCase();
|
|
5074
|
+
if (msgLower.includes("aborted") || msgLower.includes("aborterror")) {
|
|
4652
5075
|
setMessages((prev) => [...prev, {
|
|
4653
5076
|
role: "system",
|
|
4654
5077
|
content: "\u26A1 Generation interrupted by user."
|
|
4655
5078
|
}]);
|
|
4656
|
-
} else if (
|
|
4657
|
-
displayError = "
|
|
5079
|
+
} else if (msgLower.includes("credits exhausted")) {
|
|
5080
|
+
displayError = "API credits exhausted. Please add credits to your provider.";
|
|
5081
|
+
setError(displayError);
|
|
5082
|
+
} else if (msgLower.includes("invalid_api_key") || msgLower.includes("401") || msgLower.includes("unauthorized")) {
|
|
5083
|
+
displayError = 'Invalid API key. Run "quantish init" to reconfigure.';
|
|
4658
5084
|
setError(displayError);
|
|
4659
|
-
} else if (
|
|
4660
|
-
displayError =
|
|
5085
|
+
} else if (msgLower.includes("rate_limit") || msgLower.includes("429")) {
|
|
5086
|
+
displayError = "Rate limited. Please wait a moment and try again.";
|
|
4661
5087
|
setError(displayError);
|
|
4662
|
-
} else if (
|
|
4663
|
-
displayError = "
|
|
5088
|
+
} else if (msgLower.includes("cannot read properties of undefined") || msgLower.includes("undefined")) {
|
|
5089
|
+
displayError = "Tool call parsing error. The model may have sent malformed output.";
|
|
4664
5090
|
setError(displayError);
|
|
4665
5091
|
} else {
|
|
4666
5092
|
setError(displayError);
|
|
@@ -4669,8 +5095,16 @@ Last API Call Cost:
|
|
|
4669
5095
|
setIsProcessing(false);
|
|
4670
5096
|
setThinkingText(null);
|
|
4671
5097
|
abortController.current = null;
|
|
5098
|
+
if (hasQueuedMessage && queuedInput) {
|
|
5099
|
+
const nextMessage = queuedInput;
|
|
5100
|
+
setQueuedInput("");
|
|
5101
|
+
setHasQueuedMessage(false);
|
|
5102
|
+
setTimeout(() => {
|
|
5103
|
+
handleSubmit(nextMessage);
|
|
5104
|
+
}, 100);
|
|
5105
|
+
}
|
|
4672
5106
|
}
|
|
4673
|
-
}, [agent, isProcessing, isInterrupted, exit, onExit, handleSlashCommand]);
|
|
5107
|
+
}, [agent, isProcessing, isInterrupted, exit, onExit, handleSlashCommand, hasQueuedMessage, queuedInput]);
|
|
4674
5108
|
useEffect(() => {
|
|
4675
5109
|
const originalConfig = agent.config;
|
|
4676
5110
|
agent.config = {
|
|
@@ -4678,7 +5112,10 @@ Last API Call Cost:
|
|
|
4678
5112
|
streaming: true,
|
|
4679
5113
|
onText: (text, isComplete) => {
|
|
4680
5114
|
if (!isComplete) {
|
|
4681
|
-
|
|
5115
|
+
const cleanText = text.replace(/<tool_call>/g, "").replace(/<\/tool_call>/g, "").replace(/<arg_key>/g, "").replace(/<\/arg_key>/g, "");
|
|
5116
|
+
if (cleanText) {
|
|
5117
|
+
setStreamingText((prev) => prev + cleanText);
|
|
5118
|
+
}
|
|
4682
5119
|
}
|
|
4683
5120
|
},
|
|
4684
5121
|
onThinking: (text) => {
|
|
@@ -4725,10 +5162,17 @@ Stopped ${count} background process${count > 1 ? "es" : ""}.`);
|
|
|
4725
5162
|
if (key.escape && isProcessing) {
|
|
4726
5163
|
setIsInterrupted(true);
|
|
4727
5164
|
abortController.current?.abort();
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
5165
|
+
if (hasQueuedMessage && queuedInput) {
|
|
5166
|
+
setMessages((prev) => [...prev, {
|
|
5167
|
+
role: "system",
|
|
5168
|
+
content: "\u26A1 Interrupting and sending queued message..."
|
|
5169
|
+
}]);
|
|
5170
|
+
} else {
|
|
5171
|
+
setMessages((prev) => [...prev, {
|
|
5172
|
+
role: "system",
|
|
5173
|
+
content: "\u26A1 Interrupting..."
|
|
5174
|
+
}]);
|
|
5175
|
+
}
|
|
4732
5176
|
}
|
|
4733
5177
|
});
|
|
4734
5178
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
@@ -4805,22 +5249,29 @@ Stopped ${count} background process${count > 1 ? "es" : ""}.`);
|
|
|
4805
5249
|
] })
|
|
4806
5250
|
] }, i))
|
|
4807
5251
|
] }),
|
|
5252
|
+
hasQueuedMessage && isProcessing && /* @__PURE__ */ jsxs(Box, { marginBottom: 1, paddingLeft: 2, children: [
|
|
5253
|
+
/* @__PURE__ */ jsxs(Text, { color: "blue", children: [
|
|
5254
|
+
"\u{1F4E5} Queued: ",
|
|
5255
|
+
queuedInput.length > 40 ? queuedInput.slice(0, 40) + "..." : queuedInput
|
|
5256
|
+
] }),
|
|
5257
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: " (Esc to send now)" })
|
|
5258
|
+
] }),
|
|
4808
5259
|
/* @__PURE__ */ jsx(
|
|
4809
5260
|
Box,
|
|
4810
5261
|
{
|
|
4811
5262
|
borderStyle: "round",
|
|
4812
|
-
borderColor: isProcessing ? "gray" : "yellow",
|
|
5263
|
+
borderColor: hasQueuedMessage ? "blue" : isProcessing ? "gray" : "yellow",
|
|
4813
5264
|
paddingX: 1,
|
|
4814
5265
|
marginTop: 1,
|
|
4815
5266
|
children: /* @__PURE__ */ jsxs(Box, { children: [
|
|
4816
|
-
/* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "\u276F " }),
|
|
5267
|
+
/* @__PURE__ */ jsx(Text, { color: hasQueuedMessage ? "blue" : "yellow", bold: true, children: "\u276F " }),
|
|
4817
5268
|
/* @__PURE__ */ jsx(
|
|
4818
5269
|
TextInput,
|
|
4819
5270
|
{
|
|
4820
5271
|
value: input,
|
|
4821
5272
|
onChange: setInput,
|
|
4822
5273
|
onSubmit: handleSubmit,
|
|
4823
|
-
placeholder: isProcessing ? "
|
|
5274
|
+
placeholder: hasQueuedMessage ? "Message queued. Type more or press Esc to send now." : isProcessing ? "Type to queue a message..." : "Ask anything or type / for commands"
|
|
4824
5275
|
}
|
|
4825
5276
|
)
|
|
4826
5277
|
] })
|
|
@@ -4848,7 +5299,7 @@ Stopped ${count} background process${count > 1 ? "es" : ""}.`);
|
|
|
4848
5299
|
}
|
|
4849
5300
|
|
|
4850
5301
|
// src/index.ts
|
|
4851
|
-
var VERSION = "0.1.
|
|
5302
|
+
var VERSION = "0.1.22";
|
|
4852
5303
|
function cleanup() {
|
|
4853
5304
|
if (processManager.hasRunning()) {
|
|
4854
5305
|
const count = processManager.runningCount();
|