solana-traderclaw 1.0.144 → 1.0.146

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.
@@ -141,12 +141,19 @@ var AlphaStreamManager = class {
141
141
  return this.subscribed && this.ws !== null && this.ws.readyState === 1;
142
142
  }
143
143
  getStats() {
144
+ const ls = this.config.lifetimeState;
145
+ const lifetimeMessageCount = ls ? ls.lifetimeMessageCount : this.messageCount;
146
+ const firstConnectedAt = ls && ls.firstConnectedAt > 0 ? ls.firstConnectedAt : this.connectedAt;
147
+ const lifetimeLastEventTs = ls && ls.lifetimeLastEventTs > 0 ? ls.lifetimeLastEventTs : this.lastEventTs;
144
148
  return {
145
149
  subscribed: this.isSubscribed(),
146
- messageCount: this.messageCount,
147
- lastEventTs: this.lastEventTs,
150
+ messageCount: lifetimeMessageCount,
151
+ currentWsMessageCount: this.messageCount,
152
+ lastEventTs: lifetimeLastEventTs,
148
153
  connectedAt: this.connectedAt,
154
+ firstConnectedAt,
149
155
  uptimeSeconds: this.connectedAt ? Math.floor((Date.now() - this.connectedAt) / 1e3) : 0,
156
+ lifetimeUptimeSeconds: firstConnectedAt ? Math.floor((Date.now() - firstConnectedAt) / 1e3) : 0,
150
157
  reconnectAttempt: this.reconnectAttempt,
151
158
  unhealthyStreak: this.unhealthyStreak,
152
159
  circuitBackoff: this.unhealthyStreak >= CIRCUIT_UNHEALTHY_THRESHOLD
@@ -200,6 +207,9 @@ var AlphaStreamManager = class {
200
207
  this.ws.on("open", () => {
201
208
  clearTimeout(connectTimeout);
202
209
  this.connectedAt = Date.now();
210
+ if (this.config.lifetimeState && this.config.lifetimeState.firstConnectedAt === 0) {
211
+ this.config.lifetimeState.firstConnectedAt = this.connectedAt;
212
+ }
203
213
  this.reconnectAttempt = 0;
204
214
  this.log("info", "WebSocket connected, waiting for server handshake...");
205
215
  pingInterval = setInterval(() => {
@@ -286,6 +296,10 @@ var AlphaStreamManager = class {
286
296
  case "alpha_signal": {
287
297
  this.messageCount++;
288
298
  this.lastEventTs = Date.now();
299
+ if (this.config.lifetimeState) {
300
+ this.config.lifetimeState.lifetimeMessageCount++;
301
+ this.config.lifetimeState.lifetimeLastEventTs = this.lastEventTs;
302
+ }
289
303
  const data = msg.data;
290
304
  if (data) {
291
305
  const signal = {
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  } from "./chunk-3UQIQJPQ.js";
20
20
  import {
21
21
  AlphaStreamManager
22
- } from "./chunk-ZUY36F4Q.js";
22
+ } from "./chunk-GX4HA22L.js";
23
23
  import {
24
24
  BitqueryStreamManager
25
25
  } from "./chunk-S2DLZKMQ.js";
@@ -777,6 +777,31 @@ var SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY = Symbol.for(
777
777
  "openclaw.solana-trader.lifecycle.v1"
778
778
  );
779
779
  var __solanaTraderGlobalSingletonHolder = globalThis;
780
+ var SOLANA_TRADER_ALPHA_BUFFER_SINGLETON_KEY = Symbol.for(
781
+ "openclaw.solana-trader.alpha-buffer.v1"
782
+ );
783
+ var SOLANA_TRADER_ALPHA_LIFETIME_SINGLETON_KEY = Symbol.for(
784
+ "openclaw.solana-trader.alpha-lifetime.v1"
785
+ );
786
+ var __solanaTraderAlphaSingletonHolder = globalThis;
787
+ function getOrCreateAlphaBuffer() {
788
+ const existing = __solanaTraderAlphaSingletonHolder[SOLANA_TRADER_ALPHA_BUFFER_SINGLETON_KEY];
789
+ if (existing) return existing;
790
+ const fresh = new AlphaBuffer();
791
+ __solanaTraderAlphaSingletonHolder[SOLANA_TRADER_ALPHA_BUFFER_SINGLETON_KEY] = fresh;
792
+ return fresh;
793
+ }
794
+ function getOrCreateAlphaLifetimeState() {
795
+ const existing = __solanaTraderAlphaSingletonHolder[SOLANA_TRADER_ALPHA_LIFETIME_SINGLETON_KEY];
796
+ if (existing) return existing;
797
+ const fresh = {
798
+ lifetimeMessageCount: 0,
799
+ firstConnectedAt: 0,
800
+ lifetimeLastEventTs: 0
801
+ };
802
+ __solanaTraderAlphaSingletonHolder[SOLANA_TRADER_ALPHA_LIFETIME_SINGLETON_KEY] = fresh;
803
+ return fresh;
804
+ }
780
805
  function __solanaTraderDisposePreviousLifecycle(logger) {
781
806
  const prev = __solanaTraderGlobalSingletonHolder[SOLANA_TRADER_LIFECYCLE_SINGLETON_KEY];
782
807
  if (!prev) return;
@@ -2572,11 +2597,13 @@ ${notes}
2572
2597
  async () => get("/api/agents/active")
2573
2598
  )
2574
2599
  });
2575
- const alphaBuffer = new AlphaBuffer();
2600
+ const alphaBuffer = getOrCreateAlphaBuffer();
2601
+ const alphaLifetimeState = getOrCreateAlphaLifetimeState();
2576
2602
  const alphaStreamManager = new AlphaStreamManager({
2577
2603
  wsUrl: orchestratorUrl.replace(/^http/, "ws").replace(/\/$/, "") + "/ws",
2578
2604
  getAccessToken: () => sessionManager.getAccessToken(),
2579
2605
  buffer: alphaBuffer,
2606
+ lifetimeState: alphaLifetimeState,
2580
2607
  agentId: config.agentId,
2581
2608
  logger: {
2582
2609
  info: (msg) => api.logger.info(`[solana-trader] ${msg}`),
@@ -2799,7 +2826,7 @@ ${notes}
2799
2826
  });
2800
2827
  api.registerTool({
2801
2828
  name: "solana_alpha_signals",
2802
- description: "Get buffered alpha signals from the SpyFly stream. By default returns only unseen signals and marks them as seen. Use minScore to filter low-quality signals. Poll this every heartbeat cycle in Step 1.5b. Returns signals sorted by ingestion time (newest last).",
2829
+ description: "Read the live alpha-stream state and buffered signals from the running plugin. **Single source of truth** for any 'how many alpha signals / are we getting alpha / is alpha connected / latest signals' question \u2014 call it on the same turn. Signals are NOT logged per-message, so the journal/heartbeat-history will look empty; only this tool returns the real count.\n\nReturned shape: { signals[], count, bufferSize, subscribed, stats }.\n\nHEADLINE FIELDS for live status answers:\n - stats.messageCount = total alpha_signal messages this gateway process has received (survives plugin re-registers and WS reconnects). Use this for 'how many signals so far' answers.\n - stats.lifetimeUptimeSeconds = seconds since the first WS connect in this gateway process. Divide messageCount/lifetimeUptimeSeconds to get rate.\n - stats.lastEventTs = wall-clock ms of the most recent alpha_signal (lifetime).\n - subscribed = current WS is in subscribed state.\n - stats.reconnectAttempt, stats.unhealthyStreak, stats.circuitBackoff = health signals.\n\nDebug-only fields (do NOT report to users as 'totals'):\n - stats.currentWsMessageCount = messages since the CURRENT WS connect (resets on every reconnect; misleading as a 'total').\n - stats.uptimeSeconds = uptime of the current WS connect.\n - stats.connectedAt = ts of the current WS open.\n\nFor windows beyond what bufferSize covers (\u2248200 signals), or after a gateway restart wiped the lifetime counters, fall through to `solana_alpha_history` (REST, tier=enterprise \u2192 up to 200 results, last 12+ months).\n\nParams: pass unseen:false when the user is asking about overall state (don't mutate _seen). Pass unseen:true (default) only during heartbeat polling to mark and consume new signals. Use minScore to filter low-quality. Returns signals sorted by ingestion time (newest last).",
2803
2830
  parameters: Type.Object({
2804
2831
  minScore: Type.Optional(Type.Number({ description: "Minimum systemScore threshold (0-100). Signals below this are excluded." })),
2805
2832
  chain: Type.Optional(Type.String({ description: "Filter by chain (e.g., 'solana'). BSC is already filtered at ingestion." })),
@@ -2824,7 +2851,7 @@ ${notes}
2824
2851
  });
2825
2852
  api.registerTool({
2826
2853
  name: "solana_alpha_history",
2827
- description: "Query historical alpha signal data via the SpyFly REST API (GET /api/pings). Returns up to 1 year of stored signals for source reputation analysis, post-downtime catch-up, and strategy learning. Tier-gated: starter=10, pro=50, enterprise=200 results. 99.99% of tokens are dead but source patterns are invaluable.",
2854
+ description: "Query historical alpha signal data via the orchestrator REST API (GET /api/pings). Use this whenever the user asks about an ALPHA window that extends beyond what `solana_alpha_signals` can cover, e.g.:\n - 'how many alpha signals in the last hour / today / this week / since Monday'\n - 'any alpha on <token> in the last 24h'\n - 'PAIN/$X signals from yesterday'\n - any time the live buffer is empty or just reconnected but the user is asking about a real time window.\n\nReturns up to 1 year of stored signals. Tier-gated by your account (starter=10, pro=50, enterprise=200 results). Combine with `solana_alpha_signals` (live state) to answer 'we've received N signals lifetime in this gateway, plus M historical in the window you asked about'.\n\nParams: tokenAddress, channelId, limit, days (lookback window). 99.99% of tokens are dead but source patterns are invaluable.",
2828
2855
  parameters: Type.Object({
2829
2856
  tokenAddress: Type.Optional(Type.String({ description: "Filter by token mint address" })),
2830
2857
  channelId: Type.Optional(Type.String({ description: "Filter by source channel ID" })),
@@ -2848,7 +2875,7 @@ ${notes}
2848
2875
  });
2849
2876
  api.registerTool({
2850
2877
  name: "solana_alpha_sources",
2851
- description: "Get per-source statistics from the alpha signal buffer \u2014 signal count, average systemScore, and source type for each channel. Use for quick reputation checks during signal processing and to identify high-quality vs low-quality sources.",
2878
+ description: "Get per-source statistics from the alpha signal buffer \u2014 signal count, average systemScore, and source type for each channel. Call this when the user asks 'which alpha sources / channels are active', 'where are signals coming from', or any breakdown-by-source question. Always live; never answer source breakdowns from memory. Use also for quick reputation checks during signal processing.",
2852
2879
  parameters: Type.Object({}),
2853
2880
  execute: wrapExecute("solana_alpha_sources", async () => ({
2854
2881
  sources: alphaBuffer.getSourceStatsAll(),
@@ -4265,6 +4292,12 @@ ${String(params.summary)}
4265
4292
  content: entitlementMd,
4266
4293
  source: "solana-trader:entitlements-digest"
4267
4294
  });
4295
+ context.bootstrapFiles.push({
4296
+ name: "live-queries.md",
4297
+ path: "live-queries.md",
4298
+ content: "# Live alpha status queries \u2014 always call the tool\n\nWhen the user asks anything about CURRENT alpha activity, call the matching tool **on this turn**. Do NOT answer from memory, heartbeat history, journal logs, or earlier turn context \u2014 alpha signals are not journalled per-message, so 'I don't see any / 0' is wrong by default.\n\n## Routing\n\n| User question shape | Tool(s) to call | Key fields in response |\n|---|---|---|\n| how many alpha signals / are we getting alpha / activity since gateway start | `solana_alpha_signals` (unseen:false) | stats.messageCount, stats.lifetimeUptimeSeconds, stats.lastEventTs, subscribed, bufferSize |\n| is alpha connected / healthy / stream status | `solana_alpha_signals` (unseen:false) | subscribed, stats.reconnectAttempt, stats.unhealthyStreak, stats.circuitBackoff, stats.lastEventTs |\n| how many alpha signals in last hour / today / this week / since <day> | `solana_alpha_signals` (live state) **AND** `solana_alpha_history` (days=\u2026 or compute from window) | live: stats.messageCount + bufferSize; historical: pings[] in window |\n| any alpha on token <X> (recent / historical) | `solana_alpha_signals` for in-buffer signals, then `solana_alpha_history` (tokenAddress=X, days=N) for older | both responses merged by ts |\n| what alpha sources / channels are active | `solana_alpha_sources` | sources[] (name, type, count, avgScore) |\n| latest signals / new signals | `solana_alpha_signals` (unseen:false) | signals[] sorted newest last |\n\n## Field meanings \u2014 IMPORTANT\n\n`solana_alpha_signals` returns `stats` with both lifetime and current-WS fields. Use LIFETIME fields for user-facing answers:\n\n- `stats.messageCount` = **lifetime** total alpha_signal messages received since the gateway process started. Survives plugin re-registers and WS reconnects. **This is the headline number.**\n- `stats.lifetimeUptimeSeconds` = seconds since the first WS open in this process.\n- `stats.lastEventTs` = wall-clock ms of the most recent signal (lifetime).\n- `stats.firstConnectedAt` = wall-clock ms of the first WS open in this process.\n\nDebug-only (do NOT report these as 'totals' \u2014 they reset on every WS reconnect / plugin re-register):\n\n- `stats.currentWsMessageCount` \u2014 messages since the current WS opened.\n- `stats.uptimeSeconds` \u2014 current WS uptime.\n- `stats.connectedAt` \u2014 current WS open ts.\n\n## When to also call `solana_alpha_history`\n\nCall `solana_alpha_history` in addition to `solana_alpha_signals` whenever the user's window pre-dates the current gateway-process lifetime (e.g. `stats.lifetimeUptimeSeconds` is shorter than the asked window, or `stats.messageCount` is 0 because the gateway just restarted). Tier=enterprise returns up to 200 pings, up to ~1 year back.\n\n## Reply template (live-only)\n\n```\nLive alpha state:\n- subscribed: <bool>\n- lifetime: <messageCount> messages over <lifetimeUptimeSeconds>s (\u2248 <rate>/min) since gateway start\n- last signal: <Xs/Xm ago> (lastEventTs)\n- buffer (deduped): <bufferSize>\n- reconnects: <reconnectAttempt>, unhealthy streak: <unhealthyStreak>\n```\n\n## Reply template (with historical window, e.g. 'last hour')\n\n```\nAlpha activity (<window>):\n- live (gateway lifetime): <messageCount> messages over <lifetimeUptimeSeconds>s\n- historical (`solana_alpha_history` days=<N>): <pings.length> pings\n- combined unique within <window>: <merged count>\n- subscribed: <bool>, last signal: <ago>\n```\n\nThis routing overrides any heartbeat-cycle 'minimal calls' cap: ad-hoc user status questions are NOT subject to per-cycle envelopes.\n",
4299
+ source: "solana-trader:live-queries"
4300
+ });
4268
4301
  api.logger.info(`[solana-trader] Bootstrap: injected ${context.bootstrapFiles.length} files for agent ${bootAgentId}`);
4269
4302
  },
4270
4303
  {
@@ -2,7 +2,7 @@ import {
2
2
  ALPHA_INGESTION_STALE_MS,
3
3
  ALPHA_STALE_GRACE_AFTER_CONNECT_MS,
4
4
  AlphaStreamManager
5
- } from "../chunk-ZUY36F4Q.js";
5
+ } from "../chunk-GX4HA22L.js";
6
6
  export {
7
7
  ALPHA_INGESTION_STALE_MS,
8
8
  ALPHA_STALE_GRACE_AFTER_CONNECT_MS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solana-traderclaw",
3
- "version": "1.0.144",
3
+ "version": "1.0.146",
4
4
  "description": "TraderClaw V1-Upgraded — Solana trading for OpenClaw with intelligence lab, tool envelopes, prompt scrubbing, read-only X social intel, and split skill docs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -54,8 +54,6 @@ If ALL four true → exit immediately. Do NOT hold hoping for recovery. A positi
54
54
 
55
55
  ## STEP 1: SCAN
56
56
 
57
- Call `solana_scan_launches` for new launches and `solana_scan_hot_pairs` for hot pairs.
58
-
59
57
  **Bitquery subscription events:** Check `solana_bitquery_subscriptions` for active streams. Process buffered events from real-time subscriptions. If no subscriptions active and first heartbeat of session, call `solana_bitquery_templates` to discover available templates and cache in memory.
60
58
 
61
59
  ## STEP 1.5: ALPHA SIGNALS