@quantish/agent 0.1.43 → 0.1.44

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 +154 -13
  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;
@@ -3596,13 +3657,17 @@ search_markets already returns prices, volume, and liquidity.
3596
3657
 
3597
3658
  ## Tools Available
3598
3659
 
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
3660
+ **Discovery MCP** (market data - prices included):
3661
+ - search_markets(query, limit=10) \u2192 Markets WITH prices from Polymarket/Kalshi/Limitless
3662
+ - get_market_details(platform, marketId) \u2192 Full details WITH prices for ONE market
3602
3663
  - get_trending_markets(limit=10) \u2192 Hot markets by volume
3603
3664
 
3604
- **Polymarket Trading**:
3605
- - place_order, cancel_order, get_orders, get_positions, get_balances, get_price, get_orderbook
3665
+ **Polymarket Trading** (requires conditionId from Discovery results):
3666
+ - place_order, cancel_order, get_orders, get_positions, get_balances
3667
+ - get_price(tokenId) \u2192 Live price (only if you need real-time, Discovery already has prices)
3668
+ - get_orderbook(tokenId) \u2192 Bid/ask depth
3669
+
3670
+ NOTE: Don't use get_market - use get_market_details from Discovery instead.
3606
3671
 
3607
3672
  **Kalshi Trading** (via DFlow):
3608
3673
  - kalshi_buy_yes, kalshi_buy_no, kalshi_get_positions, kalshi_get_balances
@@ -3614,10 +3679,22 @@ search_markets already returns prices, volume, and liquidity.
3614
3679
  - get_process_output, list_processes, stop_process
3615
3680
  - git operations: status, diff, add, commit
3616
3681
 
3682
+ ## CRITICAL: File Operations
3683
+
3684
+ **NEVER repeat the same operation.** If write_file or edit_file fails:
3685
+ 1. Stop and tell the user what went wrong
3686
+ 2. Do NOT retry with the same content
3687
+ 3. Do NOT delete and rewrite - use edit_file to fix specific issues
3688
+
3689
+ When writing code files:
3690
+ - Write complete, valid code (not JSON-escaped strings)
3691
+ - Create one file at a time, verify it works
3692
+ - If you get stuck, ask the user for help
3693
+
3617
3694
  ## Building Trading Bots
3618
3695
 
3619
3696
  When user wants to build an app or bot:
3620
- 1. Use coding tools to create files (write_file, edit_file)
3697
+ 1. Create files one at a time with write_file
3621
3698
  2. The MCP servers are HTTP APIs - apps can call them directly
3622
3699
  3. Use start_background_process for dev servers
3623
3700
  4. API endpoints:
@@ -3629,7 +3706,7 @@ When user wants to build an app or bot:
3629
3706
  - Kalshi: percentages like 5% YES
3630
3707
 
3631
3708
  Be concise. Present results clearly. Wait for user input.`;
3632
- var Agent = class {
3709
+ var Agent = class _Agent {
3633
3710
  anthropic;
3634
3711
  llmProvider;
3635
3712
  mcpClient;
@@ -3639,6 +3716,11 @@ var Agent = class {
3639
3716
  workingDirectory;
3640
3717
  sessionCost = 0;
3641
3718
  // Cumulative cost for this session
3719
+ // Loop detection: track last N tool calls to detect loops
3720
+ recentToolCalls = [];
3721
+ static MAX_RECENT_TOOL_CALLS = 5;
3722
+ static LOOP_THRESHOLD = 2;
3723
+ // Abort if same call appears this many times
3642
3724
  cumulativeTokenUsage = {
3643
3725
  inputTokens: 0,
3644
3726
  outputTokens: 0,
@@ -3742,10 +3824,16 @@ ${userMessage}`;
3742
3824
  role: "user",
3743
3825
  content: contextMessage
3744
3826
  });
3827
+ this.clearToolCallLoopTracking();
3745
3828
  const toolCalls = [];
3746
3829
  let iterations = 0;
3747
3830
  let finalText = "";
3748
- while (iterations < maxIterations) {
3831
+ const maxTurns = this.config.maxTurns ?? maxIterations;
3832
+ while (iterations < maxTurns) {
3833
+ if (this.config.abortSignal?.aborted) {
3834
+ finalText += "\n\n[Operation cancelled by user]";
3835
+ break;
3836
+ }
3749
3837
  iterations++;
3750
3838
  this.config.onStreamStart?.();
3751
3839
  let response;
@@ -3862,6 +3950,18 @@ ${userMessage}`;
3862
3950
  * Execute a tool (local or MCP)
3863
3951
  */
3864
3952
  async executeTool(name, args) {
3953
+ if (this.config.abortSignal?.aborted) {
3954
+ return {
3955
+ result: { error: "Operation cancelled by user" },
3956
+ source: "local"
3957
+ };
3958
+ }
3959
+ if (this.checkToolCallLoop(name, args)) {
3960
+ return {
3961
+ result: { error: `Loop detected: "${name}" was called multiple times with the same input. Please try a different approach.` },
3962
+ source: "local"
3963
+ };
3964
+ }
3865
3965
  if (isLocalTool(name)) {
3866
3966
  const result = await executeLocalTool(name, args);
3867
3967
  return {
@@ -3889,10 +3989,19 @@ ${userMessage}`;
3889
3989
  source: "local"
3890
3990
  };
3891
3991
  }
3992
+ /**
3993
+ * Set the abort signal for the current request (call before run())
3994
+ */
3995
+ setAbortSignal(signal) {
3996
+ this.config.abortSignal = signal;
3997
+ }
3892
3998
  /**
3893
3999
  * Run the agent with a user message (supports streaming)
3894
4000
  */
3895
- async run(userMessage) {
4001
+ async run(userMessage, options) {
4002
+ if (options?.abortSignal) {
4003
+ this.config.abortSignal = options.abortSignal;
4004
+ }
3896
4005
  if (this.config.provider === "openrouter") {
3897
4006
  return this.runWithProvider(userMessage);
3898
4007
  }
@@ -3910,10 +4019,16 @@ ${userMessage}`;
3910
4019
  role: "user",
3911
4020
  content: contextMessage
3912
4021
  });
4022
+ this.clearToolCallLoopTracking();
3913
4023
  const toolCalls = [];
3914
4024
  let iterations = 0;
3915
4025
  let finalText = "";
3916
- while (iterations < maxIterations) {
4026
+ const maxTurns = this.config.maxTurns ?? maxIterations;
4027
+ while (iterations < maxTurns) {
4028
+ if (this.config.abortSignal?.aborted) {
4029
+ finalText += "\n\n[Operation cancelled by user]";
4030
+ break;
4031
+ }
3917
4032
  iterations++;
3918
4033
  this.config.onStreamStart?.();
3919
4034
  let response;
@@ -4118,6 +4233,32 @@ ${userMessage}`;
4118
4233
  * @param usage - Token counts from the API response
4119
4234
  * @param preCalculatedCost - Optional pre-calculated cost (from OpenRouter provider)
4120
4235
  */
4236
+ /**
4237
+ * Check if a tool call would create a loop (same call repeated too many times).
4238
+ * Returns true if this call is part of a loop and should be stopped.
4239
+ */
4240
+ checkToolCallLoop(toolName, input) {
4241
+ const inputStr = JSON.stringify(input);
4242
+ const callSignature = `${toolName}:${inputStr}`;
4243
+ this.recentToolCalls.push({ name: toolName, input: inputStr });
4244
+ if (this.recentToolCalls.length > _Agent.MAX_RECENT_TOOL_CALLS) {
4245
+ this.recentToolCalls.shift();
4246
+ }
4247
+ const duplicateCount = this.recentToolCalls.filter(
4248
+ (call) => call.name === toolName && call.input === inputStr
4249
+ ).length;
4250
+ if (duplicateCount >= _Agent.LOOP_THRESHOLD) {
4251
+ console.warn(`[Loop Detection] Tool "${toolName}" called ${duplicateCount} times with identical input. Stopping loop.`);
4252
+ return true;
4253
+ }
4254
+ return false;
4255
+ }
4256
+ /**
4257
+ * Clear the tool call loop tracking (call when starting a new user message)
4258
+ */
4259
+ clearToolCallLoopTracking() {
4260
+ this.recentToolCalls = [];
4261
+ }
4121
4262
  updateTokenUsage(usage, preCalculatedCost) {
4122
4263
  const model = this.config.model ?? DEFAULT_MODEL;
4123
4264
  this.cumulativeTokenUsage.inputTokens = usage.input_tokens;
@@ -5161,7 +5302,7 @@ Use /load <id> to load a session.`
5161
5302
  completedToolCalls.current = [];
5162
5303
  abortController.current = new AbortController();
5163
5304
  try {
5164
- const result = await agent.run(trimmed);
5305
+ const result = await agent.run(trimmed, { abortSignal: abortController.current.signal });
5165
5306
  if (isInterrupted) {
5166
5307
  setMessages((prev) => [...prev, {
5167
5308
  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.44",
4
4
  "description": "AI-powered agent for trading on Polymarket and Kalshi",
5
5
  "type": "module",
6
6
  "bin": {