@quantish/agent 0.1.20 → 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 +481 -30
  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(
@@ -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, deepseek, etc.)
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
- 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.`
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 || 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
+ }
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: result.text || "(completed)",
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 || String(err);
5071
+ const errorMsg = err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error occurred";
4650
5072
  let displayError = errorMsg;
4651
- if (errorMsg.includes("aborted") || errorMsg.includes("AbortError")) {
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 (errorMsg.includes("credits exhausted")) {
4657
- 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.';
4658
5084
  setError(displayError);
4659
- } else if (errorMsg.includes("invalid_api_key") || errorMsg.includes("401")) {
4660
- 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.";
4661
5087
  setError(displayError);
4662
- } else if (errorMsg.includes("rate_limit")) {
4663
- 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.";
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
- 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
+ }
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
- setMessages((prev) => [...prev, {
4729
- role: "system",
4730
- content: "\u26A1 Interrupting..."
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 ? "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"
4824
5275
  }
4825
5276
  )
4826
5277
  ] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantish/agent",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "AI-powered agent for building trading bots on Polymarket",
5
5
  "type": "module",
6
6
  "bin": {