@quantish/agent 0.1.43 → 0.1.45

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 +158 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -674,9 +674,40 @@ async function runSetup() {
674
674
  let skipTrading = false;
675
675
  if (quantishKey) {
676
676
  console.log(chalk.dim(`Current trading key: ${quantishKey.slice(0, 12)}...`));
677
- const action = await prompt("Keep current key (Enter), enter new key (n), or disable trading (d): ");
677
+ const action = await prompt("Keep (Enter), new key (n), create wallet (c), or disable (d): ");
678
678
  if (action.toLowerCase() === "n") {
679
679
  quantishKey = await prompt("Enter your Quantish Trading API key: ", true);
680
+ } else if (action.toLowerCase() === "c") {
681
+ console.log(chalk.dim("\nCreating a new wallet on Quantish Signing Server..."));
682
+ const externalId = await prompt("Enter a unique identifier (e.g., email or username): ");
683
+ if (!externalId) {
684
+ console.log(chalk.red("Identifier is required to create an account."));
685
+ console.log(chalk.dim("Keeping current key.\n"));
686
+ } else {
687
+ try {
688
+ const mcpClient = createMCPClient(config.getTradingMcpUrl(), "");
689
+ const result = await mcpClient.callTool("request_api_key", { externalId });
690
+ if (result.success && typeof result.data === "object" && result.data !== null) {
691
+ const data = result.data;
692
+ quantishKey = data.apiKey;
693
+ console.log(chalk.green("\n\u2713 New wallet created!"));
694
+ console.log(chalk.dim(` EOA Address: ${data.eoaAddress}`));
695
+ console.log(chalk.dim(" (Your Safe wallet will deploy on first trade)\n"));
696
+ if (data.apiSecret) {
697
+ console.log(chalk.yellow("\u26A0\uFE0F Save your API secret (shown only once):"));
698
+ console.log(chalk.bold.yellow(` ${String(data.apiSecret)}`));
699
+ console.log();
700
+ }
701
+ } else {
702
+ console.log(chalk.red("Failed to create wallet: " + (result.error || "Unknown error")));
703
+ console.log(chalk.dim("Keeping current key.\n"));
704
+ }
705
+ } catch (error2) {
706
+ console.log(chalk.red("Failed to connect to Quantish Trading Server."));
707
+ console.log(chalk.dim(String(error2)));
708
+ console.log(chalk.dim("Keeping current key.\n"));
709
+ }
710
+ }
680
711
  } else if (action.toLowerCase() === "d") {
681
712
  quantishKey = void 0;
682
713
  skipTrading = true;
@@ -740,9 +771,39 @@ async function runSetup() {
740
771
  let skipKalshi = false;
741
772
  if (kalshiKey) {
742
773
  console.log(chalk.dim(`Current Kalshi key: ${kalshiKey.slice(0, 12)}...`));
743
- const action = await prompt("Keep current key (Enter), enter new key (n), or disable Kalshi (d): ");
774
+ const action = await prompt("Keep (Enter), new key (n), create wallet (c), or disable (d): ");
744
775
  if (action.toLowerCase() === "n") {
745
776
  kalshiKey = await prompt("Enter your Kalshi API key: ", true);
777
+ } else if (action.toLowerCase() === "c") {
778
+ console.log(chalk.dim("\nCreating a new Solana wallet on Kalshi MCP..."));
779
+ const externalId = await prompt("Enter a unique identifier (e.g., email or username): ");
780
+ if (!externalId) {
781
+ console.log(chalk.red("Identifier is required to create an account."));
782
+ console.log(chalk.dim("Keeping current key.\n"));
783
+ } else {
784
+ try {
785
+ const kalshiClient = createMCPClient(KALSHI_MCP_URL, "", "kalshi");
786
+ const result = await kalshiClient.callTool("kalshi_signup", { externalId });
787
+ if (result.success && typeof result.data === "object" && result.data !== null) {
788
+ const data = result.data;
789
+ kalshiKey = data.apiKey;
790
+ console.log(chalk.green("\n\u2713 New Kalshi wallet created!"));
791
+ console.log(chalk.dim(` Solana Address: ${data.walletAddress}`));
792
+ if (data.apiSecret) {
793
+ console.log(chalk.yellow("\n\u26A0\uFE0F Save your API secret (shown only once):"));
794
+ console.log(chalk.bold.yellow(` ${String(data.apiSecret)}`));
795
+ console.log();
796
+ }
797
+ } else {
798
+ console.log(chalk.red("Failed to create wallet: " + (result.error || "Unknown error")));
799
+ console.log(chalk.dim("Keeping current key.\n"));
800
+ }
801
+ } catch (error2) {
802
+ console.log(chalk.red("Failed to connect to Kalshi MCP."));
803
+ console.log(chalk.dim(String(error2)));
804
+ console.log(chalk.dim("Keeping current key.\n"));
805
+ }
806
+ }
746
807
  } else if (action.toLowerCase() === "d") {
747
808
  kalshiKey = void 0;
748
809
  skipKalshi = true;
@@ -3588,21 +3649,27 @@ var DEFAULT_SYSTEM_PROMPT = `You are Quantish, an AI trading agent for predictio
3588
3649
 
3589
3650
  When user asks to find markets:
3590
3651
  1. Call search_markets ONCE with a good query
3591
- 2. Present results in a clean table
3652
+ 2. Present results in a table that INCLUDES PRICES from the response:
3653
+ | Market | Yes Price | No Price | Volume | End Date |
3654
+ The response has: markets[].markets[].outcomes[].price (0.05 = 5% probability)
3592
3655
  3. STOP. Wait for user to ask for more.
3593
3656
 
3594
3657
  **DO NOT** make multiple searches or call get_market_details on every result.
3595
- search_markets already returns prices, volume, and liquidity.
3658
+ search_markets already returns prices, volume, resolution criteria, and outcome probabilities.
3596
3659
 
3597
3660
  ## Tools Available
3598
3661
 
3599
- **Discovery MCP** (market data):
3600
- - search_markets(query, limit=10) \u2192 Markets with prices from Polymarket/Kalshi/Limitless
3601
- - get_market_details(platform, marketId) \u2192 Only when user asks about ONE specific market
3662
+ **Discovery MCP** (market data - prices included):
3663
+ - search_markets(query, limit=10) \u2192 Markets WITH prices from Polymarket/Kalshi/Limitless
3664
+ - get_market_details(platform, marketId) \u2192 Full details WITH prices for ONE market
3602
3665
  - get_trending_markets(limit=10) \u2192 Hot markets by volume
3603
3666
 
3604
- **Polymarket Trading**:
3605
- - place_order, cancel_order, get_orders, get_positions, get_balances, get_price, get_orderbook
3667
+ **Polymarket Trading** (requires conditionId from Discovery results):
3668
+ - place_order, cancel_order, get_orders, get_positions, get_balances
3669
+ - get_price(tokenId) \u2192 Live price (only if you need real-time, Discovery already has prices)
3670
+ - get_orderbook(tokenId) \u2192 Bid/ask depth
3671
+
3672
+ NOTE: Don't use get_market - use get_market_details from Discovery instead.
3606
3673
 
3607
3674
  **Kalshi Trading** (via DFlow):
3608
3675
  - kalshi_buy_yes, kalshi_buy_no, kalshi_get_positions, kalshi_get_balances
@@ -3614,10 +3681,22 @@ search_markets already returns prices, volume, and liquidity.
3614
3681
  - get_process_output, list_processes, stop_process
3615
3682
  - git operations: status, diff, add, commit
3616
3683
 
3684
+ ## CRITICAL: File Operations
3685
+
3686
+ **NEVER repeat the same operation.** If write_file or edit_file fails:
3687
+ 1. Stop and tell the user what went wrong
3688
+ 2. Do NOT retry with the same content
3689
+ 3. Do NOT delete and rewrite - use edit_file to fix specific issues
3690
+
3691
+ When writing code files:
3692
+ - Write complete, valid code (not JSON-escaped strings)
3693
+ - Create one file at a time, verify it works
3694
+ - If you get stuck, ask the user for help
3695
+
3617
3696
  ## Building Trading Bots
3618
3697
 
3619
3698
  When user wants to build an app or bot:
3620
- 1. Use coding tools to create files (write_file, edit_file)
3699
+ 1. Create files one at a time with write_file
3621
3700
  2. The MCP servers are HTTP APIs - apps can call them directly
3622
3701
  3. Use start_background_process for dev servers
3623
3702
  4. API endpoints:
@@ -3629,7 +3708,7 @@ When user wants to build an app or bot:
3629
3708
  - Kalshi: percentages like 5% YES
3630
3709
 
3631
3710
  Be concise. Present results clearly. Wait for user input.`;
3632
- var Agent = class {
3711
+ var Agent = class _Agent {
3633
3712
  anthropic;
3634
3713
  llmProvider;
3635
3714
  mcpClient;
@@ -3639,6 +3718,11 @@ var Agent = class {
3639
3718
  workingDirectory;
3640
3719
  sessionCost = 0;
3641
3720
  // Cumulative cost for this session
3721
+ // Loop detection: track last N tool calls to detect loops
3722
+ recentToolCalls = [];
3723
+ static MAX_RECENT_TOOL_CALLS = 5;
3724
+ static LOOP_THRESHOLD = 2;
3725
+ // Abort if same call appears this many times
3642
3726
  cumulativeTokenUsage = {
3643
3727
  inputTokens: 0,
3644
3728
  outputTokens: 0,
@@ -3742,10 +3826,16 @@ ${userMessage}`;
3742
3826
  role: "user",
3743
3827
  content: contextMessage
3744
3828
  });
3829
+ this.clearToolCallLoopTracking();
3745
3830
  const toolCalls = [];
3746
3831
  let iterations = 0;
3747
3832
  let finalText = "";
3748
- while (iterations < maxIterations) {
3833
+ const maxTurns = this.config.maxTurns ?? maxIterations;
3834
+ while (iterations < maxTurns) {
3835
+ if (this.config.abortSignal?.aborted) {
3836
+ finalText += "\n\n[Operation cancelled by user]";
3837
+ break;
3838
+ }
3749
3839
  iterations++;
3750
3840
  this.config.onStreamStart?.();
3751
3841
  let response;
@@ -3862,6 +3952,18 @@ ${userMessage}`;
3862
3952
  * Execute a tool (local or MCP)
3863
3953
  */
3864
3954
  async executeTool(name, args) {
3955
+ if (this.config.abortSignal?.aborted) {
3956
+ return {
3957
+ result: { error: "Operation cancelled by user" },
3958
+ source: "local"
3959
+ };
3960
+ }
3961
+ if (this.checkToolCallLoop(name, args)) {
3962
+ return {
3963
+ result: { error: `Loop detected: "${name}" was called multiple times with the same input. Please try a different approach.` },
3964
+ source: "local"
3965
+ };
3966
+ }
3865
3967
  if (isLocalTool(name)) {
3866
3968
  const result = await executeLocalTool(name, args);
3867
3969
  return {
@@ -3889,10 +3991,19 @@ ${userMessage}`;
3889
3991
  source: "local"
3890
3992
  };
3891
3993
  }
3994
+ /**
3995
+ * Set the abort signal for the current request (call before run())
3996
+ */
3997
+ setAbortSignal(signal) {
3998
+ this.config.abortSignal = signal;
3999
+ }
3892
4000
  /**
3893
4001
  * Run the agent with a user message (supports streaming)
3894
4002
  */
3895
- async run(userMessage) {
4003
+ async run(userMessage, options) {
4004
+ if (options?.abortSignal) {
4005
+ this.config.abortSignal = options.abortSignal;
4006
+ }
3896
4007
  if (this.config.provider === "openrouter") {
3897
4008
  return this.runWithProvider(userMessage);
3898
4009
  }
@@ -3910,10 +4021,16 @@ ${userMessage}`;
3910
4021
  role: "user",
3911
4022
  content: contextMessage
3912
4023
  });
4024
+ this.clearToolCallLoopTracking();
3913
4025
  const toolCalls = [];
3914
4026
  let iterations = 0;
3915
4027
  let finalText = "";
3916
- while (iterations < maxIterations) {
4028
+ const maxTurns = this.config.maxTurns ?? maxIterations;
4029
+ while (iterations < maxTurns) {
4030
+ if (this.config.abortSignal?.aborted) {
4031
+ finalText += "\n\n[Operation cancelled by user]";
4032
+ break;
4033
+ }
3917
4034
  iterations++;
3918
4035
  this.config.onStreamStart?.();
3919
4036
  let response;
@@ -4118,6 +4235,32 @@ ${userMessage}`;
4118
4235
  * @param usage - Token counts from the API response
4119
4236
  * @param preCalculatedCost - Optional pre-calculated cost (from OpenRouter provider)
4120
4237
  */
4238
+ /**
4239
+ * Check if a tool call would create a loop (same call repeated too many times).
4240
+ * Returns true if this call is part of a loop and should be stopped.
4241
+ */
4242
+ checkToolCallLoop(toolName, input) {
4243
+ const inputStr = JSON.stringify(input);
4244
+ const callSignature = `${toolName}:${inputStr}`;
4245
+ this.recentToolCalls.push({ name: toolName, input: inputStr });
4246
+ if (this.recentToolCalls.length > _Agent.MAX_RECENT_TOOL_CALLS) {
4247
+ this.recentToolCalls.shift();
4248
+ }
4249
+ const duplicateCount = this.recentToolCalls.filter(
4250
+ (call) => call.name === toolName && call.input === inputStr
4251
+ ).length;
4252
+ if (duplicateCount >= _Agent.LOOP_THRESHOLD) {
4253
+ console.warn(`[Loop Detection] Tool "${toolName}" called ${duplicateCount} times with identical input. Stopping loop.`);
4254
+ return true;
4255
+ }
4256
+ return false;
4257
+ }
4258
+ /**
4259
+ * Clear the tool call loop tracking (call when starting a new user message)
4260
+ */
4261
+ clearToolCallLoopTracking() {
4262
+ this.recentToolCalls = [];
4263
+ }
4121
4264
  updateTokenUsage(usage, preCalculatedCost) {
4122
4265
  const model = this.config.model ?? DEFAULT_MODEL;
4123
4266
  this.cumulativeTokenUsage.inputTokens = usage.input_tokens;
@@ -5161,7 +5304,7 @@ Use /load <id> to load a session.`
5161
5304
  completedToolCalls.current = [];
5162
5305
  abortController.current = new AbortController();
5163
5306
  try {
5164
- const result = await agent.run(trimmed);
5307
+ const result = await agent.run(trimmed, { abortSignal: abortController.current.signal });
5165
5308
  if (isInterrupted) {
5166
5309
  setMessages((prev) => [...prev, {
5167
5310
  role: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantish/agent",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "description": "AI-powered agent for trading on Polymarket and Kalshi",
5
5
  "type": "module",
6
6
  "bin": {