@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.
- package/dist/index.js +154 -13
- 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
|
|
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
|
|
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
|
|
3601
|
-
- get_market_details(platform, marketId) \u2192
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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",
|