@quantish/agent 0.1.19 → 0.1.21

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.
Files changed (2) hide show
  1. package/dist/index.js +507 -47
  2. 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
- args = args + "}";
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
- toolCalls.push({ id: tc.id, name: tc.name, input });
3270
- callbacks.onToolCall?.(tc.id, tc.name, input);
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
- console.error(`[OpenRouter] Failed to parse tool call "${tc.name}": ${tc.arguments}`);
3273
- toolCalls.push({ id: tc.id, name: tc.name, input: {} });
3274
- callbacks.onToolCall?.(tc.id, tc.name, {});
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(
@@ -3486,31 +3507,40 @@ function extractTokenInfo(token) {
3486
3507
  }
3487
3508
  var DEFAULT_SYSTEM_PROMPT = `You are Quantish, an AI coding and trading agent.
3488
3509
 
3489
- You have two sets of capabilities:
3490
-
3491
3510
  ## Trading Tools (via MCP)
3492
- You can interact with Polymarket prediction markets:
3493
3511
  - Check wallet balances and positions
3494
3512
  - Place, cancel, and manage orders
3495
- - Transfer funds and claim winnings
3496
3513
  - Get market prices and orderbook data
3514
+ - Search markets on Polymarket, Kalshi, Limitless
3497
3515
 
3498
3516
  ## Coding Tools (local)
3499
- You can work with the local filesystem:
3500
- - Read and write files
3501
- - List directories and search with grep
3502
- - Run shell commands
3503
- - Use git for version control
3517
+ - read_file, write_file, edit_file, list_dir
3518
+ - grep (search), find_files
3519
+ - run_command (blocking), start_background_process (non-blocking)
3520
+ - git operations
3504
3521
 
3505
- ## Guidelines
3506
- - Be concise and helpful
3507
- - When making trades, always confirm details before proceeding
3508
- - Prices on Polymarket are between 0.01 and 0.99 (probabilities)
3509
- - Minimum order value is $1
3510
- - When writing code, follow existing patterns and conventions
3511
- - For dangerous operations (rm, sudo), explain what you're doing
3522
+ ## IMPORTANT: Background vs Blocking Commands
3523
+
3524
+ Use \`start_background_process\` for:
3525
+ - Dev servers: npm start, npm run dev, yarn dev, vite, next dev
3526
+ - Watch mode: npm run watch, tsc --watch
3527
+ - Any server or long-running process
3528
+ - Returns immediately with a process ID
3512
3529
 
3513
- You help users build trading bots and agents by combining coding skills with trading capabilities.`;
3530
+ Use \`run_command\` for:
3531
+ - Quick commands: ls, cat, npm install, npm run build
3532
+ - One-time operations that complete quickly
3533
+ - Blocks until command finishes
3534
+
3535
+ After starting a background process:
3536
+ 1. Use \`get_process_output(process_id)\` to check if it started correctly
3537
+ 2. Use \`list_processes()\` to see all running processes
3538
+ 3. Use \`stop_process(process_id)\` to stop when done
3539
+
3540
+ ## Guidelines
3541
+ - Be concise
3542
+ - Prices on Polymarket are 0.01-0.99 (probabilities)
3543
+ - For dangerous operations (rm, sudo), explain first`;
3514
3544
  var Agent = class {
3515
3545
  anthropic;
3516
3546
  llmProvider;
@@ -4157,6 +4187,18 @@ ${userMessage}`;
4157
4187
  setHistory(history) {
4158
4188
  this.conversationHistory = history;
4159
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
+ }
4160
4202
  };
4161
4203
  function createAgent(config) {
4162
4204
  return new Agent(config);
@@ -4215,10 +4257,204 @@ function tableRow(label, value, width = 20) {
4215
4257
  }
4216
4258
 
4217
4259
  // src/ui/App.tsx
4218
- import { useState, useCallback, useRef, useEffect } from "react";
4260
+ import { useState, useCallback, useRef, useEffect, useMemo } from "react";
4219
4261
  import { Box, Text, useApp, useInput } from "ink";
4220
4262
  import TextInput from "ink-text-input";
4221
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
4222
4458
  import { jsx, jsxs } from "react/jsx-runtime";
4223
4459
  function formatTokenCount(count) {
4224
4460
  if (count < 1e3) return String(count);
@@ -4270,6 +4506,10 @@ function formatResult(result, maxLength = 200) {
4270
4506
  }
4271
4507
  return String(result);
4272
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
+ }
4273
4513
  function App({ agent, onExit }) {
4274
4514
  const { exit } = useApp();
4275
4515
  const [messages, setMessages] = useState([]);
@@ -4291,6 +4531,10 @@ function App({ agent, onExit }) {
4291
4531
  });
4292
4532
  const completedToolCalls = useRef([]);
4293
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);
4294
4538
  const handleSlashCommand = useCallback((command) => {
4295
4539
  const cmd = command.slice(1).toLowerCase().split(" ")[0];
4296
4540
  const args = command.slice(cmd.length + 2).trim();
@@ -4301,20 +4545,34 @@ function App({ agent, onExit }) {
4301
4545
  content: `\u{1F4DA} Available Commands:
4302
4546
  /clear - Clear conversation history
4303
4547
  /compact - Summarize conversation (keeps context, saves tokens)
4304
- /model - Switch model (opus, sonnet, haiku, minimax, deepseek, etc.)
4548
+ /model - Switch model (opus, sonnet, haiku, minimax, etc.)
4305
4549
  /provider - Switch LLM provider (anthropic, openrouter)
4306
4550
  /cost - Show session cost breakdown
4307
4551
  /help - Show this help message
4308
4552
  /tools - List available tools
4309
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:
4310
4563
  /processes - List running background processes
4311
4564
  /stop <id> - Stop a background process by ID
4312
4565
  /stopall - Stop all background processes
4566
+
4313
4567
  /exit - Exit the CLI
4314
4568
 
4315
4569
  \u2328\uFE0F Keyboard Shortcuts:
4316
- Esc - Interrupt current generation
4317
- Ctrl+C - Exit (stops all processes)`
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.`
4318
4576
  }]);
4319
4577
  return true;
4320
4578
  case "clear":
@@ -4564,8 +4822,169 @@ Last API Call Cost:
4564
4822
  \u{1F4A1} Tip: Use /model haiku for cheaper operations, /compact to reduce context.`
4565
4823
  }]);
4566
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;
4567
4973
  case "exit":
4568
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
+ }
4569
4988
  if (processManager.hasRunning()) {
4570
4989
  processManager.killAll();
4571
4990
  }
@@ -4582,7 +5001,18 @@ Last API Call Cost:
4582
5001
  }, [agent, onExit, exit]);
4583
5002
  const handleSubmit = useCallback(async (value) => {
4584
5003
  const trimmed = value.trim();
4585
- if (!trimmed || isProcessing) return;
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
+ }
4586
5016
  if (trimmed.startsWith("/")) {
4587
5017
  setInput("");
4588
5018
  handleSlashCommand(trimmed);
@@ -4621,9 +5051,10 @@ Last API Call Cost:
4621
5051
  } else {
4622
5052
  setMessages((prev) => {
4623
5053
  const filtered = prev.filter((m) => !m.isStreaming);
5054
+ const cleanedText = cleanModelOutput(result.text || "");
4624
5055
  return [...filtered, {
4625
5056
  role: "assistant",
4626
- content: result.text || "(completed)",
5057
+ content: cleanedText || "(completed)",
4627
5058
  toolCalls: result.toolCalls.map((tc) => ({
4628
5059
  name: tc.name,
4629
5060
  args: tc.input,
@@ -4637,21 +5068,25 @@ Last API Call Cost:
4637
5068
  setStreamingText("");
4638
5069
  setCurrentToolCalls([]);
4639
5070
  } catch (err) {
4640
- const errorMsg = err.message || String(err);
5071
+ const errorMsg = err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error occurred";
4641
5072
  let displayError = errorMsg;
4642
- if (errorMsg.includes("aborted") || errorMsg.includes("AbortError")) {
5073
+ const msgLower = errorMsg.toLowerCase();
5074
+ if (msgLower.includes("aborted") || msgLower.includes("aborterror")) {
4643
5075
  setMessages((prev) => [...prev, {
4644
5076
  role: "system",
4645
5077
  content: "\u26A1 Generation interrupted by user."
4646
5078
  }]);
4647
- } else if (errorMsg.includes("credits exhausted")) {
4648
- displayError = "Anthropic API credits exhausted. Please add credits at console.anthropic.com";
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.';
4649
5084
  setError(displayError);
4650
- } else if (errorMsg.includes("invalid_api_key") || errorMsg.includes("401")) {
4651
- displayError = 'Invalid Anthropic API key. Run "quantish init" to reconfigure.';
5085
+ } else if (msgLower.includes("rate_limit") || msgLower.includes("429")) {
5086
+ displayError = "Rate limited. Please wait a moment and try again.";
4652
5087
  setError(displayError);
4653
- } else if (errorMsg.includes("rate_limit")) {
4654
- displayError = "Rate limited by Anthropic API. Please wait a moment and try again.";
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.";
4655
5090
  setError(displayError);
4656
5091
  } else {
4657
5092
  setError(displayError);
@@ -4660,8 +5095,16 @@ Last API Call Cost:
4660
5095
  setIsProcessing(false);
4661
5096
  setThinkingText(null);
4662
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
+ }
4663
5106
  }
4664
- }, [agent, isProcessing, isInterrupted, exit, onExit, handleSlashCommand]);
5107
+ }, [agent, isProcessing, isInterrupted, exit, onExit, handleSlashCommand, hasQueuedMessage, queuedInput]);
4665
5108
  useEffect(() => {
4666
5109
  const originalConfig = agent.config;
4667
5110
  agent.config = {
@@ -4669,7 +5112,10 @@ Last API Call Cost:
4669
5112
  streaming: true,
4670
5113
  onText: (text, isComplete) => {
4671
5114
  if (!isComplete) {
4672
- setStreamingText((prev) => prev + text);
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
+ }
4673
5119
  }
4674
5120
  },
4675
5121
  onThinking: (text) => {
@@ -4716,10 +5162,17 @@ Stopped ${count} background process${count > 1 ? "es" : ""}.`);
4716
5162
  if (key.escape && isProcessing) {
4717
5163
  setIsInterrupted(true);
4718
5164
  abortController.current?.abort();
4719
- setMessages((prev) => [...prev, {
4720
- role: "system",
4721
- content: "\u26A1 Interrupting..."
4722
- }]);
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
+ }
4723
5176
  }
4724
5177
  });
4725
5178
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
@@ -4796,22 +5249,29 @@ Stopped ${count} background process${count > 1 ? "es" : ""}.`);
4796
5249
  ] })
4797
5250
  ] }, i))
4798
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
+ ] }),
4799
5259
  /* @__PURE__ */ jsx(
4800
5260
  Box,
4801
5261
  {
4802
5262
  borderStyle: "round",
4803
- borderColor: isProcessing ? "gray" : "yellow",
5263
+ borderColor: hasQueuedMessage ? "blue" : isProcessing ? "gray" : "yellow",
4804
5264
  paddingX: 1,
4805
5265
  marginTop: 1,
4806
5266
  children: /* @__PURE__ */ jsxs(Box, { children: [
4807
- /* @__PURE__ */ jsx(Text, { color: "yellow", bold: true, children: "\u276F " }),
5267
+ /* @__PURE__ */ jsx(Text, { color: hasQueuedMessage ? "blue" : "yellow", bold: true, children: "\u276F " }),
4808
5268
  /* @__PURE__ */ jsx(
4809
5269
  TextInput,
4810
5270
  {
4811
5271
  value: input,
4812
5272
  onChange: setInput,
4813
5273
  onSubmit: handleSubmit,
4814
- placeholder: isProcessing ? "Processing..." : "Ask anything or type / for commands"
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"
4815
5275
  }
4816
5276
  )
4817
5277
  ] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantish/agent",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "AI-powered agent for building trading bots on Polymarket",
5
5
  "type": "module",
6
6
  "bin": {