@tradejs/infra 3.1.29-beta.261 → 3.1.29-beta.263

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.
@@ -83,6 +83,76 @@ var closeRedisConnection = async () => {
83
83
  redisConnectionWarningShown = false;
84
84
  redis.disconnect();
85
85
  };
86
+ var mcpStorage = {
87
+ async get(id) {
88
+ const value = await (await requireReadyRedis()).get(`tradejs:mcp:v1:${id}`);
89
+ return value === null ? null : JSON.parse(value);
90
+ },
91
+ async put(id, value, ttlSeconds, onlyAbsent = false) {
92
+ const redis = await requireReadyRedis();
93
+ const key = `tradejs:mcp:v1:${id}`;
94
+ const result = onlyAbsent ? await redis.set(key, JSON.stringify(value), "EX", ttlSeconds, "NX") : await redis.set(key, JSON.stringify(value), "EX", ttlSeconds);
95
+ return result === "OK";
96
+ },
97
+ async take(id) {
98
+ const value = await (await requireReadyRedis()).getdel(`tradejs:mcp:v1:${id}`);
99
+ return value === null ? null : JSON.parse(value);
100
+ },
101
+ async remove(id) {
102
+ return await (await requireReadyRedis()).del(`tradejs:mcp:v1:${id}`) > 0;
103
+ },
104
+ async compare(id, previous, next, ttlSeconds) {
105
+ const result = await (await requireReadyRedis()).eval(
106
+ "if redis.call('GET',KEYS[1]) ~= ARGV[1] then return 0 end; redis.call('SET',KEYS[1],ARGV[2],'EX',ARGV[3]); return 1",
107
+ 1,
108
+ `tradejs:mcp:v1:${id}`,
109
+ JSON.stringify(previous),
110
+ JSON.stringify(next),
111
+ ttlSeconds
112
+ );
113
+ return result === 1;
114
+ },
115
+ async release(id, previous) {
116
+ return await (await requireReadyRedis()).eval(
117
+ "if redis.call('GET',KEYS[1]) ~= ARGV[1] then return 0 end; return redis.call('DEL',KEYS[1])",
118
+ 1,
119
+ `tradejs:mcp:v1:${id}`,
120
+ JSON.stringify(previous)
121
+ ) === 1;
122
+ },
123
+ async rateLimit(id, maximum, seconds) {
124
+ const count = await (await requireReadyRedis()).eval(
125
+ "local n=redis.call('INCR',KEYS[1]); if n==1 then redis.call('EXPIRE',KEYS[1],ARGV[1]) end; return n",
126
+ 1,
127
+ `tradejs:mcp:v1:rate:${id}`,
128
+ seconds
129
+ );
130
+ return Number(count) <= maximum;
131
+ },
132
+ async scan(prefix, cursor = "0", count = 100) {
133
+ const [nextCursor, keys] = await (await requireReadyRedis()).scan(cursor, "MATCH", `tradejs:mcp:v1:${prefix}*`, "COUNT", count);
134
+ return {
135
+ cursor: nextCursor,
136
+ ids: keys.map((key) => key.slice("tradejs:mcp:v1:".length))
137
+ };
138
+ }
139
+ };
140
+ var scanDataKeys = async (prefix, cursor = "0") => {
141
+ if (!/^\d+$/.test(cursor)) throw new Error("Invalid storage cursor");
142
+ const literalPrefix = [...prefix].map(
143
+ (character) => ["*", "?", "[", "]", "\\"].includes(character) ? `\\${character}` : character
144
+ ).join("");
145
+ const [nextCursor, keys] = await (await requireReadyRedis()).scan(cursor, "MATCH", `${literalPrefix}*`, "COUNT", 100);
146
+ return { cursor: nextCursor, keys };
147
+ };
148
+ var scanHashJson = async (key, cursor = "0") => {
149
+ if (!/^\d+$/.test(cursor)) throw new Error("Invalid storage cursor");
150
+ const [next, pairs] = await (await requireReadyRedis()).hscan(key, cursor, "COUNT", 50);
151
+ const values = [];
152
+ for (let index = 1; index < pairs.length; index += 2)
153
+ values.push(JSON.parse(pairs[index]));
154
+ return { cursor: next, values };
155
+ };
86
156
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
87
157
  var getRedisStatus = (redis) => String(redis.status ?? "ready");
88
158
  var waitForRedisReady = async (redis) => {
@@ -649,7 +719,6 @@ var redisKeys = {
649
719
  runtimeClosedTradeBucket: (userName, dayKey) => `users:${userName}:runtime:closed-trade-records:days:${dayKey}`,
650
720
  runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
651
721
  runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
652
- aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
653
722
  analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
654
723
  screenshotSessionToken: (token) => `auth:screenshot:${token}`,
655
724
  backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
@@ -672,6 +741,9 @@ var redisKeys = {
672
741
 
673
742
  export {
674
743
  closeRedisConnection,
744
+ mcpStorage,
745
+ scanDataKeys,
746
+ scanHashJson,
675
747
  publishData,
676
748
  getKeys,
677
749
  getData,
package/dist/jev.d.mts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { JevProviderConfig, JevResponse } from '@tradejs/types';
2
2
 
3
+ /** Provider criteria are zero-based: ten strength levels use raw scores 0–9. */
4
+ declare const jevScoreMaximum: (dimension: string) => 4 | 9;
3
5
  declare const jevStableJson: (value: unknown) => string;
4
6
  declare const jevHash: (value: unknown) => string;
5
7
  declare const jevFileHash: (contents: string) => string;
@@ -13,4 +15,4 @@ declare const requestJev: (provider: JevProviderConfig, apiKey: string, state: u
13
15
  }) => Promise<JevResponse>;
14
16
  declare const validateJevResponse: (body: unknown, expected: string[]) => JevResponse;
15
17
 
16
- export { jevFileHash, jevHash, jevStableJson, readJevArtifact, requestJev, validateJevResponse, writeJevArtifact };
18
+ export { jevFileHash, jevHash, jevScoreMaximum, jevStableJson, readJevArtifact, requestJev, validateJevResponse, writeJevArtifact };
package/dist/jev.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { JevProviderConfig, JevResponse } from '@tradejs/types';
2
2
 
3
+ /** Provider criteria are zero-based: ten strength levels use raw scores 0–9. */
4
+ declare const jevScoreMaximum: (dimension: string) => 4 | 9;
3
5
  declare const jevStableJson: (value: unknown) => string;
4
6
  declare const jevHash: (value: unknown) => string;
5
7
  declare const jevFileHash: (contents: string) => string;
@@ -13,4 +15,4 @@ declare const requestJev: (provider: JevProviderConfig, apiKey: string, state: u
13
15
  }) => Promise<JevResponse>;
14
16
  declare const validateJevResponse: (body: unknown, expected: string[]) => JevResponse;
15
17
 
16
- export { jevFileHash, jevHash, jevStableJson, readJevArtifact, requestJev, validateJevResponse, writeJevArtifact };
18
+ export { jevFileHash, jevHash, jevScoreMaximum, jevStableJson, readJevArtifact, requestJev, validateJevResponse, writeJevArtifact };
package/dist/jev.js CHANGED
@@ -32,6 +32,7 @@ var jev_exports = {};
32
32
  __export(jev_exports, {
33
33
  jevFileHash: () => jevFileHash,
34
34
  jevHash: () => jevHash,
35
+ jevScoreMaximum: () => jevScoreMaximum,
35
36
  jevStableJson: () => jevStableJson,
36
37
  readJevArtifact: () => readJevArtifact,
37
38
  requestJev: () => requestJev,
@@ -42,6 +43,7 @@ module.exports = __toCommonJS(jev_exports);
42
43
  var import_node_crypto = require("crypto");
43
44
  var import_promises = __toESM(require("fs/promises"));
44
45
  var import_node_path = __toESM(require("path"));
46
+ var jevScoreMaximum = (dimension) => dimension === "signalStrength" ? 9 : 4;
45
47
  var jevStableJson = (value) => {
46
48
  if (Array.isArray(value)) return `[${value.map(jevStableJson).join(",")}]`;
47
49
  if (value && typeof value === "object")
@@ -154,26 +156,28 @@ var validateJevResponse = (body, expected) => {
154
156
  const result = body;
155
157
  if (!result || typeof result.model !== "string" || !result.model || !result.answers)
156
158
  throw new Error("Invalid Jev response");
157
- if (!expected.length || expected.length > 8 || Object.keys(result.answers).sort().join(",") !== [...expected].sort().join(","))
159
+ if (!expected.length || expected.length > 9 || Object.keys(result.answers).sort().join(",") !== [...expected].sort().join(","))
158
160
  throw new Error("Invalid Jev answer set");
159
161
  for (const key of expected) {
160
162
  const answer = result.answers[key];
161
- if (!answer || answer.type !== "score" || !Number.isFinite(answer.score) || answer.score < 0 || answer.score > 4 || !Number.isFinite(answer.confidence) || answer.confidence < 0 || answer.confidence > 1)
163
+ const maximum = jevScoreMaximum(key);
164
+ const levels = Array.from({ length: maximum + 1 }, (_, index) => index);
165
+ if (!answer || answer.type !== "score" || !Number.isFinite(answer.score) || answer.score < 0 || answer.score > maximum || !Number.isFinite(answer.confidence) || answer.confidence < 0 || answer.confidence > 1)
162
166
  throw new Error(`Invalid Jev ${key} answer`);
163
167
  const probabilities = answer.probabilities;
164
- if (!probabilities || Object.keys(probabilities).length !== 5 || [0, 1, 2, 3, 4].some(
168
+ if (!probabilities || Object.keys(probabilities).length !== levels.length || levels.some(
165
169
  (level) => !Number.isFinite(probabilities[level]) || probabilities[level] < 0 || probabilities[level] > 1
166
170
  ))
167
171
  throw new Error(`Invalid Jev ${key} probabilities`);
168
172
  const roundingHalfUnit = 5e-3;
169
173
  const epsilon = 1e-9;
170
- if (Math.abs(Object.values(probabilities).reduce((a, b) => a + b, 0) - 1) > 5 * roundingHalfUnit + epsilon)
174
+ if (Math.abs(Object.values(probabilities).reduce((a, b) => a + b, 0) - 1) > levels.length * roundingHalfUnit + epsilon)
171
175
  throw new Error("Jev probabilities must sum to one");
172
- const mean = [0, 1, 2, 3, 4].reduce(
176
+ const mean = levels.reduce(
173
177
  (sum, level) => sum + level * probabilities[level],
174
178
  0
175
179
  );
176
- if (Math.abs(mean - answer.score) > (0 + 1 + 2 + 3 + 4 + 1) * roundingHalfUnit + epsilon)
180
+ if (Math.abs(mean - answer.score) > levels.reduce((sum, level) => sum + level, 1) * roundingHalfUnit + epsilon)
177
181
  throw new Error("Jev score disagrees with probabilities");
178
182
  }
179
183
  return result;
@@ -182,6 +186,7 @@ var validateJevResponse = (body, expected) => {
182
186
  0 && (module.exports = {
183
187
  jevFileHash,
184
188
  jevHash,
189
+ jevScoreMaximum,
185
190
  jevStableJson,
186
191
  readJevArtifact,
187
192
  requestJev,
package/dist/jev.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  import { createHash, randomUUID } from "crypto";
3
3
  import fs from "fs/promises";
4
4
  import path from "path";
5
+ var jevScoreMaximum = (dimension) => dimension === "signalStrength" ? 9 : 4;
5
6
  var jevStableJson = (value) => {
6
7
  if (Array.isArray(value)) return `[${value.map(jevStableJson).join(",")}]`;
7
8
  if (value && typeof value === "object")
@@ -114,26 +115,28 @@ var validateJevResponse = (body, expected) => {
114
115
  const result = body;
115
116
  if (!result || typeof result.model !== "string" || !result.model || !result.answers)
116
117
  throw new Error("Invalid Jev response");
117
- if (!expected.length || expected.length > 8 || Object.keys(result.answers).sort().join(",") !== [...expected].sort().join(","))
118
+ if (!expected.length || expected.length > 9 || Object.keys(result.answers).sort().join(",") !== [...expected].sort().join(","))
118
119
  throw new Error("Invalid Jev answer set");
119
120
  for (const key of expected) {
120
121
  const answer = result.answers[key];
121
- if (!answer || answer.type !== "score" || !Number.isFinite(answer.score) || answer.score < 0 || answer.score > 4 || !Number.isFinite(answer.confidence) || answer.confidence < 0 || answer.confidence > 1)
122
+ const maximum = jevScoreMaximum(key);
123
+ const levels = Array.from({ length: maximum + 1 }, (_, index) => index);
124
+ if (!answer || answer.type !== "score" || !Number.isFinite(answer.score) || answer.score < 0 || answer.score > maximum || !Number.isFinite(answer.confidence) || answer.confidence < 0 || answer.confidence > 1)
122
125
  throw new Error(`Invalid Jev ${key} answer`);
123
126
  const probabilities = answer.probabilities;
124
- if (!probabilities || Object.keys(probabilities).length !== 5 || [0, 1, 2, 3, 4].some(
127
+ if (!probabilities || Object.keys(probabilities).length !== levels.length || levels.some(
125
128
  (level) => !Number.isFinite(probabilities[level]) || probabilities[level] < 0 || probabilities[level] > 1
126
129
  ))
127
130
  throw new Error(`Invalid Jev ${key} probabilities`);
128
131
  const roundingHalfUnit = 5e-3;
129
132
  const epsilon = 1e-9;
130
- if (Math.abs(Object.values(probabilities).reduce((a, b) => a + b, 0) - 1) > 5 * roundingHalfUnit + epsilon)
133
+ if (Math.abs(Object.values(probabilities).reduce((a, b) => a + b, 0) - 1) > levels.length * roundingHalfUnit + epsilon)
131
134
  throw new Error("Jev probabilities must sum to one");
132
- const mean = [0, 1, 2, 3, 4].reduce(
135
+ const mean = levels.reduce(
133
136
  (sum, level) => sum + level * probabilities[level],
134
137
  0
135
138
  );
136
- if (Math.abs(mean - answer.score) > (0 + 1 + 2 + 3 + 4 + 1) * roundingHalfUnit + epsilon)
139
+ if (Math.abs(mean - answer.score) > levels.reduce((sum, level) => sum + level, 1) * roundingHalfUnit + epsilon)
137
140
  throw new Error("Jev score disagrees with probabilities");
138
141
  }
139
142
  return result;
@@ -141,6 +144,7 @@ var validateJevResponse = (body, expected) => {
141
144
  export {
142
145
  jevFileHash,
143
146
  jevHash,
147
+ jevScoreMaximum,
144
148
  jevStableJson,
145
149
  readJevArtifact,
146
150
  requestJev,
package/dist/redis.d.mts CHANGED
@@ -4,6 +4,33 @@ declare global {
4
4
  var __redis__: Redis | undefined;
5
5
  }
6
6
  declare const closeRedisConnection: () => Promise<void>;
7
+ /** Fail-closed storage for MCP credentials, grants, jobs and worker leases.
8
+ * Values use native Redis strings so consumption and compare-and-set are atomic.
9
+ * Never pass a caller-supplied Redis key to this adapter.
10
+ */
11
+ declare const mcpStorage: {
12
+ get<T>(id: string): Promise<T | null>;
13
+ put(id: string, value: unknown, ttlSeconds: number, onlyAbsent?: boolean): Promise<boolean>;
14
+ take<T>(id: string): Promise<T | null>;
15
+ remove(id: string): Promise<boolean>;
16
+ compare(id: string, previous: unknown, next: unknown, ttlSeconds: number): Promise<boolean>;
17
+ release(id: string, previous: unknown): Promise<boolean>;
18
+ rateLimit(id: string, maximum: number, seconds: number): Promise<boolean>;
19
+ scan(prefix: string, cursor?: string, count?: number): Promise<{
20
+ cursor: string;
21
+ ids: string[];
22
+ }>;
23
+ };
24
+ /** One bounded SCAN page. Cursor is opaque; an empty page is not end-of-data. */
25
+ declare const scanDataKeys: (prefix: string, cursor?: string) => Promise<{
26
+ cursor: string;
27
+ keys: string[];
28
+ }>;
29
+ /** HSCAN preserves large runtime buckets without materializing the complete hash. */
30
+ declare const scanHashJson: (key: string, cursor?: string) => Promise<{
31
+ cursor: string;
32
+ values: unknown[];
33
+ }>;
7
34
  interface Options {
8
35
  expire?: number;
9
36
  }
@@ -94,7 +121,6 @@ declare const redisKeys: {
94
121
  runtimeClosedTradeBucket: (userName: string, dayKey: string) => string;
95
122
  runtimeActiveTrades: (userName: string) => string;
96
123
  runtimeActiveTrade: (userName: string, symbol: string, scopeId?: string) => string;
97
- aiChatHistory: (userName: string, symbolKey: string) => string;
98
124
  analysis: (symbol: string, signalId: string) => string;
99
125
  screenshotSessionToken: (token: string) => string;
100
126
  backtestResults: (userName: string, config: string, timestamp: string) => string;
@@ -115,4 +141,4 @@ declare const redisKeys: {
115
141
  mlResult: (strategyName: string, signalId: string) => string;
116
142
  };
117
143
 
118
- export { RedisWriteBlockedError, closeRedisConnection, consumeScreenshotSessionToken, createScreenshotSessionToken, delKey, delKeyStrict, delKeyWithOptions, getData, getDataStrict, getHashData, getHashJsonField, getHashJsonValues, getKeys, incrHashFields, incrementKey, publishData, redisKeys, setData, setDataIfAbsent, setDataStrict, setHashJsonField, setHashJsonFields };
144
+ export { RedisWriteBlockedError, closeRedisConnection, consumeScreenshotSessionToken, createScreenshotSessionToken, delKey, delKeyStrict, delKeyWithOptions, getData, getDataStrict, getHashData, getHashJsonField, getHashJsonValues, getKeys, incrHashFields, incrementKey, mcpStorage, publishData, redisKeys, scanDataKeys, scanHashJson, setData, setDataIfAbsent, setDataStrict, setHashJsonField, setHashJsonFields };
package/dist/redis.d.ts CHANGED
@@ -4,6 +4,33 @@ declare global {
4
4
  var __redis__: Redis | undefined;
5
5
  }
6
6
  declare const closeRedisConnection: () => Promise<void>;
7
+ /** Fail-closed storage for MCP credentials, grants, jobs and worker leases.
8
+ * Values use native Redis strings so consumption and compare-and-set are atomic.
9
+ * Never pass a caller-supplied Redis key to this adapter.
10
+ */
11
+ declare const mcpStorage: {
12
+ get<T>(id: string): Promise<T | null>;
13
+ put(id: string, value: unknown, ttlSeconds: number, onlyAbsent?: boolean): Promise<boolean>;
14
+ take<T>(id: string): Promise<T | null>;
15
+ remove(id: string): Promise<boolean>;
16
+ compare(id: string, previous: unknown, next: unknown, ttlSeconds: number): Promise<boolean>;
17
+ release(id: string, previous: unknown): Promise<boolean>;
18
+ rateLimit(id: string, maximum: number, seconds: number): Promise<boolean>;
19
+ scan(prefix: string, cursor?: string, count?: number): Promise<{
20
+ cursor: string;
21
+ ids: string[];
22
+ }>;
23
+ };
24
+ /** One bounded SCAN page. Cursor is opaque; an empty page is not end-of-data. */
25
+ declare const scanDataKeys: (prefix: string, cursor?: string) => Promise<{
26
+ cursor: string;
27
+ keys: string[];
28
+ }>;
29
+ /** HSCAN preserves large runtime buckets without materializing the complete hash. */
30
+ declare const scanHashJson: (key: string, cursor?: string) => Promise<{
31
+ cursor: string;
32
+ values: unknown[];
33
+ }>;
7
34
  interface Options {
8
35
  expire?: number;
9
36
  }
@@ -94,7 +121,6 @@ declare const redisKeys: {
94
121
  runtimeClosedTradeBucket: (userName: string, dayKey: string) => string;
95
122
  runtimeActiveTrades: (userName: string) => string;
96
123
  runtimeActiveTrade: (userName: string, symbol: string, scopeId?: string) => string;
97
- aiChatHistory: (userName: string, symbolKey: string) => string;
98
124
  analysis: (symbol: string, signalId: string) => string;
99
125
  screenshotSessionToken: (token: string) => string;
100
126
  backtestResults: (userName: string, config: string, timestamp: string) => string;
@@ -115,4 +141,4 @@ declare const redisKeys: {
115
141
  mlResult: (strategyName: string, signalId: string) => string;
116
142
  };
117
143
 
118
- export { RedisWriteBlockedError, closeRedisConnection, consumeScreenshotSessionToken, createScreenshotSessionToken, delKey, delKeyStrict, delKeyWithOptions, getData, getDataStrict, getHashData, getHashJsonField, getHashJsonValues, getKeys, incrHashFields, incrementKey, publishData, redisKeys, setData, setDataIfAbsent, setDataStrict, setHashJsonField, setHashJsonFields };
144
+ export { RedisWriteBlockedError, closeRedisConnection, consumeScreenshotSessionToken, createScreenshotSessionToken, delKey, delKeyStrict, delKeyWithOptions, getData, getDataStrict, getHashData, getHashJsonField, getHashJsonValues, getKeys, incrHashFields, incrementKey, mcpStorage, publishData, redisKeys, scanDataKeys, scanHashJson, setData, setDataIfAbsent, setDataStrict, setHashJsonField, setHashJsonFields };
package/dist/redis.js CHANGED
@@ -45,8 +45,11 @@ __export(redis_exports, {
45
45
  getKeys: () => getKeys,
46
46
  incrHashFields: () => incrHashFields,
47
47
  incrementKey: () => incrementKey,
48
+ mcpStorage: () => mcpStorage,
48
49
  publishData: () => publishData,
49
50
  redisKeys: () => redisKeys,
51
+ scanDataKeys: () => scanDataKeys,
52
+ scanHashJson: () => scanHashJson,
50
53
  setData: () => setData,
51
54
  setDataIfAbsent: () => setDataIfAbsent,
52
55
  setDataStrict: () => setDataStrict,
@@ -138,6 +141,76 @@ var closeRedisConnection = async () => {
138
141
  redisConnectionWarningShown = false;
139
142
  redis.disconnect();
140
143
  };
144
+ var mcpStorage = {
145
+ async get(id) {
146
+ const value = await (await requireReadyRedis()).get(`tradejs:mcp:v1:${id}`);
147
+ return value === null ? null : JSON.parse(value);
148
+ },
149
+ async put(id, value, ttlSeconds, onlyAbsent = false) {
150
+ const redis = await requireReadyRedis();
151
+ const key = `tradejs:mcp:v1:${id}`;
152
+ const result = onlyAbsent ? await redis.set(key, JSON.stringify(value), "EX", ttlSeconds, "NX") : await redis.set(key, JSON.stringify(value), "EX", ttlSeconds);
153
+ return result === "OK";
154
+ },
155
+ async take(id) {
156
+ const value = await (await requireReadyRedis()).getdel(`tradejs:mcp:v1:${id}`);
157
+ return value === null ? null : JSON.parse(value);
158
+ },
159
+ async remove(id) {
160
+ return await (await requireReadyRedis()).del(`tradejs:mcp:v1:${id}`) > 0;
161
+ },
162
+ async compare(id, previous, next, ttlSeconds) {
163
+ const result = await (await requireReadyRedis()).eval(
164
+ "if redis.call('GET',KEYS[1]) ~= ARGV[1] then return 0 end; redis.call('SET',KEYS[1],ARGV[2],'EX',ARGV[3]); return 1",
165
+ 1,
166
+ `tradejs:mcp:v1:${id}`,
167
+ JSON.stringify(previous),
168
+ JSON.stringify(next),
169
+ ttlSeconds
170
+ );
171
+ return result === 1;
172
+ },
173
+ async release(id, previous) {
174
+ return await (await requireReadyRedis()).eval(
175
+ "if redis.call('GET',KEYS[1]) ~= ARGV[1] then return 0 end; return redis.call('DEL',KEYS[1])",
176
+ 1,
177
+ `tradejs:mcp:v1:${id}`,
178
+ JSON.stringify(previous)
179
+ ) === 1;
180
+ },
181
+ async rateLimit(id, maximum, seconds) {
182
+ const count = await (await requireReadyRedis()).eval(
183
+ "local n=redis.call('INCR',KEYS[1]); if n==1 then redis.call('EXPIRE',KEYS[1],ARGV[1]) end; return n",
184
+ 1,
185
+ `tradejs:mcp:v1:rate:${id}`,
186
+ seconds
187
+ );
188
+ return Number(count) <= maximum;
189
+ },
190
+ async scan(prefix, cursor = "0", count = 100) {
191
+ const [nextCursor, keys] = await (await requireReadyRedis()).scan(cursor, "MATCH", `tradejs:mcp:v1:${prefix}*`, "COUNT", count);
192
+ return {
193
+ cursor: nextCursor,
194
+ ids: keys.map((key) => key.slice("tradejs:mcp:v1:".length))
195
+ };
196
+ }
197
+ };
198
+ var scanDataKeys = async (prefix, cursor = "0") => {
199
+ if (!/^\d+$/.test(cursor)) throw new Error("Invalid storage cursor");
200
+ const literalPrefix = [...prefix].map(
201
+ (character) => ["*", "?", "[", "]", "\\"].includes(character) ? `\\${character}` : character
202
+ ).join("");
203
+ const [nextCursor, keys] = await (await requireReadyRedis()).scan(cursor, "MATCH", `${literalPrefix}*`, "COUNT", 100);
204
+ return { cursor: nextCursor, keys };
205
+ };
206
+ var scanHashJson = async (key, cursor = "0") => {
207
+ if (!/^\d+$/.test(cursor)) throw new Error("Invalid storage cursor");
208
+ const [next, pairs] = await (await requireReadyRedis()).hscan(key, cursor, "COUNT", 50);
209
+ const values = [];
210
+ for (let index = 1; index < pairs.length; index += 2)
211
+ values.push(JSON.parse(pairs[index]));
212
+ return { cursor: next, values };
213
+ };
141
214
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
142
215
  var getRedisStatus = (redis) => String(redis.status ?? "ready");
143
216
  var waitForRedisReady = async (redis) => {
@@ -704,7 +777,6 @@ var redisKeys = {
704
777
  runtimeClosedTradeBucket: (userName, dayKey) => `users:${userName}:runtime:closed-trade-records:days:${dayKey}`,
705
778
  runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
706
779
  runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
707
- aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
708
780
  analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
709
781
  screenshotSessionToken: (token) => `auth:screenshot:${token}`,
710
782
  backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
@@ -741,8 +813,11 @@ var redisKeys = {
741
813
  getKeys,
742
814
  incrHashFields,
743
815
  incrementKey,
816
+ mcpStorage,
744
817
  publishData,
745
818
  redisKeys,
819
+ scanDataKeys,
820
+ scanHashJson,
746
821
  setData,
747
822
  setDataIfAbsent,
748
823
  setDataStrict,
package/dist/redis.mjs CHANGED
@@ -14,14 +14,17 @@ import {
14
14
  getKeys,
15
15
  incrHashFields,
16
16
  incrementKey,
17
+ mcpStorage,
17
18
  publishData,
18
19
  redisKeys,
20
+ scanDataKeys,
21
+ scanHashJson,
19
22
  setData,
20
23
  setDataIfAbsent,
21
24
  setDataStrict,
22
25
  setHashJsonField,
23
26
  setHashJsonFields
24
- } from "./chunk-GTZOMAFK.mjs";
27
+ } from "./chunk-BACJCS7N.mjs";
25
28
  export {
26
29
  RedisWriteBlockedError,
27
30
  closeRedisConnection,
@@ -38,8 +41,11 @@ export {
38
41
  getKeys,
39
42
  incrHashFields,
40
43
  incrementKey,
44
+ mcpStorage,
41
45
  publishData,
42
46
  redisKeys,
47
+ scanDataKeys,
48
+ scanHashJson,
43
49
  setData,
44
50
  setDataIfAbsent,
45
51
  setDataStrict,
@@ -304,7 +304,6 @@ var redisKeys = {
304
304
  runtimeClosedTradeBucket: (userName, dayKey) => `users:${userName}:runtime:closed-trade-records:days:${dayKey}`,
305
305
  runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
306
306
  runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
307
- aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
308
307
  analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
309
308
  screenshotSessionToken: (token) => `auth:screenshot:${token}`,
310
309
  backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
@@ -3,7 +3,7 @@ import {
3
3
  getDataStrict,
4
4
  redisKeys,
5
5
  setDataStrict
6
- } from "./chunk-GTZOMAFK.mjs";
6
+ } from "./chunk-BACJCS7N.mjs";
7
7
 
8
8
  // src/runtimeControls.ts
9
9
  import { randomUUID } from "crypto";
@@ -328,7 +328,6 @@ var redisKeys = {
328
328
  runtimeClosedTradeBucket: (userName, dayKey) => `users:${userName}:runtime:closed-trade-records:days:${dayKey}`,
329
329
  runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
330
330
  runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
331
- aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
332
331
  analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
333
332
  screenshotSessionToken: (token) => `auth:screenshot:${token}`,
334
333
  backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
@@ -3,7 +3,7 @@ import {
3
3
  getKeys,
4
4
  redisKeys,
5
5
  setDataStrict
6
- } from "./chunk-GTZOMAFK.mjs";
6
+ } from "./chunk-BACJCS7N.mjs";
7
7
 
8
8
  // src/runtimeDeploymentEvents.ts
9
9
  import { randomUUID } from "crypto";
@@ -285,7 +285,6 @@ var redisKeys = {
285
285
  runtimeClosedTradeBucket: (userName, dayKey) => `users:${userName}:runtime:closed-trade-records:days:${dayKey}`,
286
286
  runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
287
287
  runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
288
- aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
289
288
  analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
290
289
  screenshotSessionToken: (token) => `auth:screenshot:${token}`,
291
290
  backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
@@ -2,7 +2,7 @@ import {
2
2
  getData,
3
3
  redisKeys,
4
4
  setData
5
- } from "./chunk-GTZOMAFK.mjs";
5
+ } from "./chunk-BACJCS7N.mjs";
6
6
 
7
7
  // src/runtimeHeartbeats.ts
8
8
  var normalizeDeploymentId = (value) => {
@@ -323,7 +323,6 @@ var redisKeys = {
323
323
  runtimeClosedTradeBucket: (userName, dayKey) => `users:${userName}:runtime:closed-trade-records:days:${dayKey}`,
324
324
  runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
325
325
  runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
326
- aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
327
326
  analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
328
327
  screenshotSessionToken: (token) => `auth:screenshot:${token}`,
329
328
  backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
@@ -3,7 +3,7 @@ import {
3
3
  getKeys,
4
4
  redisKeys,
5
5
  setData
6
- } from "./chunk-GTZOMAFK.mjs";
6
+ } from "./chunk-BACJCS7N.mjs";
7
7
 
8
8
  // src/runtimeStrategyConfigs.ts
9
9
  var RESERVED_STRATEGY_NAMES = /* @__PURE__ */ new Set(["charts"]);
@@ -353,7 +353,6 @@ var redisKeys = {
353
353
  runtimeClosedTradeBucket: (userName, dayKey) => `users:${userName}:runtime:closed-trade-records:days:${dayKey}`,
354
354
  runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
355
355
  runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
356
- aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
357
356
  analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
358
357
  screenshotSessionToken: (token) => `auth:screenshot:${token}`,
359
358
  backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
@@ -4,7 +4,7 @@ import {
4
4
  getKeys,
5
5
  redisKeys,
6
6
  setData
7
- } from "./chunk-GTZOMAFK.mjs";
7
+ } from "./chunk-BACJCS7N.mjs";
8
8
 
9
9
  // src/tradingAccounts.ts
10
10
  var normalizeId = (value, label) => {
@@ -286,7 +286,6 @@ var redisKeys = {
286
286
  runtimeClosedTradeBucket: (userName, dayKey) => `users:${userName}:runtime:closed-trade-records:days:${dayKey}`,
287
287
  runtimeActiveTrades: (userName) => `users:${userName}:runtime:active-trades:`,
288
288
  runtimeActiveTrade: (userName, symbol, scopeId) => scopeId ? `users:${userName}:runtime:active-trades:${scopeId}:${symbol}` : `users:${userName}:runtime:active-trades:${symbol}`,
289
- aiChatHistory: (userName, symbolKey) => `users:${userName}:ai:chats:${symbolKey}`,
290
289
  analysis: (symbol, signalId) => `analysis:${symbol}:${signalId}`,
291
290
  screenshotSessionToken: (token) => `auth:screenshot:${token}`,
292
291
  backtestResults: (userName, config, timestamp) => `users:${userName}:backtests:results:${config}:${timestamp}`,
@@ -2,7 +2,7 @@ import {
2
2
  getData,
3
3
  redisKeys,
4
4
  setData
5
- } from "./chunk-GTZOMAFK.mjs";
5
+ } from "./chunk-BACJCS7N.mjs";
6
6
 
7
7
  // src/userSettings.ts
8
8
  var readString = (value) => typeof value === "string" ? value.trim() : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/infra",
3
- "version": "3.1.29-beta.261",
3
+ "version": "3.1.29-beta.263",
4
4
  "description": "MIT-licensed server infrastructure adapters for TradeJS: Redis, Timescale, ML, logging, and IO.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -16,7 +16,8 @@
16
16
  "coinbase",
17
17
  "ai",
18
18
  "claude",
19
- "codex"
19
+ "codex",
20
+ "jev"
20
21
  ],
21
22
  "repository": {
22
23
  "type": "git",
@@ -140,7 +141,7 @@
140
141
  "dependencies": {
141
142
  "@grpc/grpc-js": "^1.14.4",
142
143
  "@grpc/proto-loader": "^0.8.1",
143
- "@tradejs/types": "^3.1.29-beta.261",
144
+ "@tradejs/types": "^3.1.29-beta.263",
144
145
  "chalk": "4.1.2",
145
146
  "date-fns": "^3.6.0",
146
147
  "ioredis": "5.11.1",