@tradejs/node 3.1.29-beta.273 → 3.1.29-beta.276
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/mcp.d.mts +17 -1
- package/dist/mcp.d.ts +17 -1
- package/dist/mcp.js +93 -28
- package/dist/mcp.mjs +91 -28
- package/package.json +5 -5
package/dist/mcp.d.mts
CHANGED
|
@@ -8,6 +8,7 @@ declare const mcpPackageManifestHash: () => Promise<string>;
|
|
|
8
8
|
declare const enqueueMcpJob: (principal: McpPrincipal, kind: McpJob["kind"], request: Record<string, unknown>, idempotencyKey: string) => Promise<McpJob>;
|
|
9
9
|
declare const readMcpJob: (userName: string, id: string) => Promise<McpJob>;
|
|
10
10
|
declare const readAuthorizedMcpJob: (principal: McpPrincipal, id: string) => Promise<McpJob>;
|
|
11
|
+
declare const redactMcpJobLog: (line: string) => string;
|
|
11
12
|
/** Offsets refer to the retained worker tail, which may shift while running. */
|
|
12
13
|
declare const readMcpJobLogs: (principal: McpPrincipal, id: string, offset?: number, limit?: number) => Promise<{
|
|
13
14
|
jobId: string;
|
|
@@ -144,6 +145,10 @@ declare const readMcpStrategies: (userName: string, deploymentId: string) => Pro
|
|
|
144
145
|
}[];
|
|
145
146
|
observedAt: number;
|
|
146
147
|
}>;
|
|
148
|
+
declare class McpCursorError extends Error {
|
|
149
|
+
readonly code: 'CURSOR_EXPIRED' | 'CURSOR_MISMATCH';
|
|
150
|
+
constructor(code: 'CURSOR_EXPIRED' | 'CURSOR_MISMATCH');
|
|
151
|
+
}
|
|
147
152
|
declare const readMcpRuntimePage: (userName: string, kind: "signals" | "evaluations" | "orders", request: {
|
|
148
153
|
deploymentId: string;
|
|
149
154
|
cursor: string;
|
|
@@ -151,6 +156,7 @@ declare const readMcpRuntimePage: (userName: string, kind: "signals" | "evaluati
|
|
|
151
156
|
endTime: number;
|
|
152
157
|
strategy?: string;
|
|
153
158
|
symbol?: string;
|
|
159
|
+
limit?: number;
|
|
154
160
|
}) => Promise<{
|
|
155
161
|
items: Record<string, unknown>[];
|
|
156
162
|
cursor: string;
|
|
@@ -162,6 +168,16 @@ declare const readMcpRuntimePage: (userName: string, kind: "signals" | "evaluati
|
|
|
162
168
|
endTime: number;
|
|
163
169
|
};
|
|
164
170
|
coverage: string;
|
|
171
|
+
pagination: {
|
|
172
|
+
scanComplete: boolean;
|
|
173
|
+
storageReads: number;
|
|
174
|
+
inspected: number;
|
|
175
|
+
cursorTtlSeconds: number;
|
|
176
|
+
consistency: string;
|
|
177
|
+
order: string;
|
|
178
|
+
retentionStartTime: null;
|
|
179
|
+
persistenceCompleteness: string;
|
|
180
|
+
};
|
|
165
181
|
note: string;
|
|
166
182
|
}>;
|
|
167
183
|
declare const readMcpSignal: (userName: string, signalId: string, deploymentId: string, timestamp: number, strategy: string) => Promise<{
|
|
@@ -212,4 +228,4 @@ declare const readMcpBacktestResult: (userName: string, id: string, offset?: num
|
|
|
212
228
|
source: string;
|
|
213
229
|
}>;
|
|
214
230
|
|
|
215
|
-
export { MCP_JOB_TTL, cancelMcpJob, enqueueMcpJob, isMcpRecord, mcpId, mcpJobDirectory, mcpJobStorageId, mcpPackageManifestHash, mcpProjectRoot, publicMcpJob, readAuthorizedMcpJob, readMcpBacktestPage, readMcpBacktestResult, readMcpChart, readMcpDeployments, readMcpEvaluationStats, readMcpJob, readMcpJobLogs, readMcpMarket, readMcpRuntimePage, readMcpSignal, readMcpStrategies, redactMcpData, requireMcpDeployment };
|
|
231
|
+
export { MCP_JOB_TTL, McpCursorError, cancelMcpJob, enqueueMcpJob, isMcpRecord, mcpId, mcpJobDirectory, mcpJobStorageId, mcpPackageManifestHash, mcpProjectRoot, publicMcpJob, readAuthorizedMcpJob, readMcpBacktestPage, readMcpBacktestResult, readMcpChart, readMcpDeployments, readMcpEvaluationStats, readMcpJob, readMcpJobLogs, readMcpMarket, readMcpRuntimePage, readMcpSignal, readMcpStrategies, redactMcpData, redactMcpJobLog, requireMcpDeployment };
|
package/dist/mcp.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ declare const mcpPackageManifestHash: () => Promise<string>;
|
|
|
8
8
|
declare const enqueueMcpJob: (principal: McpPrincipal, kind: McpJob["kind"], request: Record<string, unknown>, idempotencyKey: string) => Promise<McpJob>;
|
|
9
9
|
declare const readMcpJob: (userName: string, id: string) => Promise<McpJob>;
|
|
10
10
|
declare const readAuthorizedMcpJob: (principal: McpPrincipal, id: string) => Promise<McpJob>;
|
|
11
|
+
declare const redactMcpJobLog: (line: string) => string;
|
|
11
12
|
/** Offsets refer to the retained worker tail, which may shift while running. */
|
|
12
13
|
declare const readMcpJobLogs: (principal: McpPrincipal, id: string, offset?: number, limit?: number) => Promise<{
|
|
13
14
|
jobId: string;
|
|
@@ -144,6 +145,10 @@ declare const readMcpStrategies: (userName: string, deploymentId: string) => Pro
|
|
|
144
145
|
}[];
|
|
145
146
|
observedAt: number;
|
|
146
147
|
}>;
|
|
148
|
+
declare class McpCursorError extends Error {
|
|
149
|
+
readonly code: 'CURSOR_EXPIRED' | 'CURSOR_MISMATCH';
|
|
150
|
+
constructor(code: 'CURSOR_EXPIRED' | 'CURSOR_MISMATCH');
|
|
151
|
+
}
|
|
147
152
|
declare const readMcpRuntimePage: (userName: string, kind: "signals" | "evaluations" | "orders", request: {
|
|
148
153
|
deploymentId: string;
|
|
149
154
|
cursor: string;
|
|
@@ -151,6 +156,7 @@ declare const readMcpRuntimePage: (userName: string, kind: "signals" | "evaluati
|
|
|
151
156
|
endTime: number;
|
|
152
157
|
strategy?: string;
|
|
153
158
|
symbol?: string;
|
|
159
|
+
limit?: number;
|
|
154
160
|
}) => Promise<{
|
|
155
161
|
items: Record<string, unknown>[];
|
|
156
162
|
cursor: string;
|
|
@@ -162,6 +168,16 @@ declare const readMcpRuntimePage: (userName: string, kind: "signals" | "evaluati
|
|
|
162
168
|
endTime: number;
|
|
163
169
|
};
|
|
164
170
|
coverage: string;
|
|
171
|
+
pagination: {
|
|
172
|
+
scanComplete: boolean;
|
|
173
|
+
storageReads: number;
|
|
174
|
+
inspected: number;
|
|
175
|
+
cursorTtlSeconds: number;
|
|
176
|
+
consistency: string;
|
|
177
|
+
order: string;
|
|
178
|
+
retentionStartTime: null;
|
|
179
|
+
persistenceCompleteness: string;
|
|
180
|
+
};
|
|
165
181
|
note: string;
|
|
166
182
|
}>;
|
|
167
183
|
declare const readMcpSignal: (userName: string, signalId: string, deploymentId: string, timestamp: number, strategy: string) => Promise<{
|
|
@@ -212,4 +228,4 @@ declare const readMcpBacktestResult: (userName: string, id: string, offset?: num
|
|
|
212
228
|
source: string;
|
|
213
229
|
}>;
|
|
214
230
|
|
|
215
|
-
export { MCP_JOB_TTL, cancelMcpJob, enqueueMcpJob, isMcpRecord, mcpId, mcpJobDirectory, mcpJobStorageId, mcpPackageManifestHash, mcpProjectRoot, publicMcpJob, readAuthorizedMcpJob, readMcpBacktestPage, readMcpBacktestResult, readMcpChart, readMcpDeployments, readMcpEvaluationStats, readMcpJob, readMcpJobLogs, readMcpMarket, readMcpRuntimePage, readMcpSignal, readMcpStrategies, redactMcpData, requireMcpDeployment };
|
|
231
|
+
export { MCP_JOB_TTL, McpCursorError, cancelMcpJob, enqueueMcpJob, isMcpRecord, mcpId, mcpJobDirectory, mcpJobStorageId, mcpPackageManifestHash, mcpProjectRoot, publicMcpJob, readAuthorizedMcpJob, readMcpBacktestPage, readMcpBacktestResult, readMcpChart, readMcpDeployments, readMcpEvaluationStats, readMcpJob, readMcpJobLogs, readMcpMarket, readMcpRuntimePage, readMcpSignal, readMcpStrategies, redactMcpData, redactMcpJobLog, requireMcpDeployment };
|
package/dist/mcp.js
CHANGED
|
@@ -778,6 +778,7 @@ var init_jevTraining = __esm({
|
|
|
778
778
|
var mcp_exports = {};
|
|
779
779
|
__export(mcp_exports, {
|
|
780
780
|
MCP_JOB_TTL: () => MCP_JOB_TTL,
|
|
781
|
+
McpCursorError: () => McpCursorError,
|
|
781
782
|
cancelMcpJob: () => cancelMcpJob,
|
|
782
783
|
enqueueMcpJob: () => enqueueMcpJob,
|
|
783
784
|
isMcpRecord: () => isMcpRecord,
|
|
@@ -800,6 +801,7 @@ __export(mcp_exports, {
|
|
|
800
801
|
readMcpSignal: () => readMcpSignal,
|
|
801
802
|
readMcpStrategies: () => readMcpStrategies,
|
|
802
803
|
redactMcpData: () => redactMcpData,
|
|
804
|
+
redactMcpJobLog: () => redactMcpJobLog,
|
|
803
805
|
requireMcpDeployment: () => requireMcpDeployment
|
|
804
806
|
});
|
|
805
807
|
module.exports = __toCommonJS(mcp_exports);
|
|
@@ -8874,7 +8876,7 @@ var readAuthorizedMcpJob = async (principal, id) => {
|
|
|
8874
8876
|
throw new Error("Insufficient scope");
|
|
8875
8877
|
return job;
|
|
8876
8878
|
};
|
|
8877
|
-
var
|
|
8879
|
+
var redactMcpJobLog = (line) => line.replace(/\u001b\[[0-9;]*m/g, "").replace(/\b(Bearer|Basic)\s+\S+/gi, "$1 [redacted]").replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]*:[^\s/@]+@/gi, "$1[redacted]@").replace(/((?:authorization|cookie)\s*:\s*)[^\r\n]+/gi, "$1[redacted]").replace(
|
|
8878
8880
|
/((?:[\w.-]*(?:secret|password|token|api[_-]?key|private[_-]?key|authorization|cookie|credential)[\w.-]*)["']?\s*[:=]\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;&}]+)/gi,
|
|
8879
8881
|
"$1[redacted]"
|
|
8880
8882
|
).slice(0, 1e3);
|
|
@@ -8885,7 +8887,7 @@ var readMcpJobLogs = async (principal, id, offset = 0, limit = 50) => {
|
|
|
8885
8887
|
const lines = [];
|
|
8886
8888
|
let bytes = 0;
|
|
8887
8889
|
for (const line of job.logs.slice(offset, offset + limit)) {
|
|
8888
|
-
const redacted =
|
|
8890
|
+
const redacted = redactMcpJobLog(line);
|
|
8889
8891
|
const size = Buffer.byteLength(JSON.stringify(redacted)) + 1;
|
|
8890
8892
|
if (bytes + size > 48e3) break;
|
|
8891
8893
|
lines.push(redacted);
|
|
@@ -9081,11 +9083,34 @@ var readMcpStrategies = async (userName, deploymentId) => {
|
|
|
9081
9083
|
observedAt: Date.now()
|
|
9082
9084
|
};
|
|
9083
9085
|
};
|
|
9086
|
+
var McpCursorError = class extends Error {
|
|
9087
|
+
constructor(code) {
|
|
9088
|
+
super(
|
|
9089
|
+
code === "CURSOR_EXPIRED" ? "Invalid storage cursor: expired or unavailable; restart the same window and deduplicate record identities" : "Invalid storage cursor: user, account or filters changed"
|
|
9090
|
+
);
|
|
9091
|
+
this.code = code;
|
|
9092
|
+
this.name = "McpCursorError";
|
|
9093
|
+
}
|
|
9094
|
+
};
|
|
9095
|
+
var RUNTIME_CURSOR_TTL = 86400;
|
|
9084
9096
|
var readMcpRuntimePage = async (userName, kind, request) => {
|
|
9085
9097
|
const deployment = await requireMcpDeployment(userName, request.deploymentId);
|
|
9086
9098
|
const prefix = kind === "signals" ? import_redis4.redisKeys.runtimeSignalBuckets(userName) : kind === "evaluations" ? import_redis4.redisKeys.runtimeSignalEvaluationBuckets(userName) : import_redis4.redisKeys.runtimeTrades(userName);
|
|
9087
|
-
const
|
|
9088
|
-
|
|
9099
|
+
const limit = request.limit ?? 50;
|
|
9100
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 50)
|
|
9101
|
+
throw new Error("Select a page limit of 1\u201350");
|
|
9102
|
+
const binding = mcpId(
|
|
9103
|
+
JSON.stringify({
|
|
9104
|
+
userName,
|
|
9105
|
+
kind,
|
|
9106
|
+
accountId: deployment.accountId,
|
|
9107
|
+
deploymentId: request.deploymentId,
|
|
9108
|
+
startTime: request.startTime,
|
|
9109
|
+
endTime: request.endTime,
|
|
9110
|
+
strategy: request.strategy ?? null,
|
|
9111
|
+
symbol: request.symbol ?? null
|
|
9112
|
+
})
|
|
9113
|
+
);
|
|
9089
9114
|
let state = {
|
|
9090
9115
|
binding,
|
|
9091
9116
|
scan: "0",
|
|
@@ -9098,29 +9123,46 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
9098
9123
|
const saved = await import_redis4.mcpStorage.get(
|
|
9099
9124
|
`cursor:${request.cursor}`
|
|
9100
9125
|
);
|
|
9101
|
-
if (!saved
|
|
9102
|
-
|
|
9103
|
-
state = saved;
|
|
9104
|
-
}
|
|
9105
|
-
if (!state.keys.length && !state.values.length && (!state.scanned || state.scan !== "0")) {
|
|
9106
|
-
const page = await (0, import_redis4.scanDataKeys)(prefix, state.scan);
|
|
9107
|
-
state = { ...state, scan: page.cursor, keys: page.keys, scanned: true };
|
|
9108
|
-
}
|
|
9109
|
-
if (!state.values.length && state.keys.length) {
|
|
9110
|
-
const key = state.keys[0];
|
|
9111
|
-
if (kind === "orders") {
|
|
9112
|
-
state.keys.shift();
|
|
9113
|
-
if (!key.slice(prefix.length).startsWith("days:"))
|
|
9114
|
-
state.values = [await (0, import_redis4.getDataStrict)(key)];
|
|
9115
|
-
} else {
|
|
9116
|
-
const page = await (0, import_redis4.scanHashJson)(key, state.hash);
|
|
9117
|
-
state.values = page.values;
|
|
9118
|
-
state.hash = page.cursor;
|
|
9119
|
-
if (page.cursor === "0") state.keys.shift();
|
|
9120
|
-
}
|
|
9126
|
+
if (!saved) throw new McpCursorError("CURSOR_EXPIRED");
|
|
9127
|
+
if (saved.binding !== binding) throw new McpCursorError("CURSOR_MISMATCH");
|
|
9128
|
+
state = structuredClone(saved);
|
|
9121
9129
|
}
|
|
9122
9130
|
const items = [];
|
|
9123
|
-
|
|
9131
|
+
let storageReads = 0;
|
|
9132
|
+
let inspected = 0;
|
|
9133
|
+
let responseBytes = 0;
|
|
9134
|
+
while (items.length < limit && storageReads < 40 && inspected < 1e3) {
|
|
9135
|
+
if (!state.values.length) {
|
|
9136
|
+
if (!state.keys.length) {
|
|
9137
|
+
if (state.scanned && state.scan === "0") break;
|
|
9138
|
+
const page = await (0, import_redis4.scanDataKeys)(prefix, state.scan);
|
|
9139
|
+
storageReads++;
|
|
9140
|
+
state.scan = page.cursor;
|
|
9141
|
+
state.scanned = true;
|
|
9142
|
+
state.keys = page.keys.filter((key2) => {
|
|
9143
|
+
if (kind === "orders")
|
|
9144
|
+
return !key2.slice(prefix.length).startsWith("days:");
|
|
9145
|
+
const suffix = key2.slice(prefix.length);
|
|
9146
|
+
const day = suffix.slice(0, 10);
|
|
9147
|
+
return day >= (0, import_time3.getRuntimeStorageDayKey)(request.startTime) && day <= (0, import_time3.getRuntimeStorageDayKey)(request.endTime - 1) && (!request.strategy || suffix.slice(11) === request.strategy);
|
|
9148
|
+
});
|
|
9149
|
+
if (!state.keys.length) continue;
|
|
9150
|
+
}
|
|
9151
|
+
const key = state.keys[0];
|
|
9152
|
+
storageReads++;
|
|
9153
|
+
if (kind === "orders") {
|
|
9154
|
+
state.keys.shift();
|
|
9155
|
+
state.values = [await (0, import_redis4.getDataStrict)(key)];
|
|
9156
|
+
} else {
|
|
9157
|
+
const page = await (0, import_redis4.scanHashJson)(key, state.hash);
|
|
9158
|
+
state.values = page.values;
|
|
9159
|
+
state.hash = page.cursor;
|
|
9160
|
+
if (page.cursor === "0") state.keys.shift();
|
|
9161
|
+
}
|
|
9162
|
+
if (!state.values.length) continue;
|
|
9163
|
+
}
|
|
9164
|
+
let row = state.values.shift();
|
|
9165
|
+
inspected++;
|
|
9124
9166
|
if (kind === "signals" && isMcpRecord(row) && typeof row.symbol === "string" && typeof row.signalId === "string")
|
|
9125
9167
|
row = await (0, import_redis4.getDataStrict)(
|
|
9126
9168
|
import_redis4.redisKeys.storeSignal(row.symbol, row.signalId)
|
|
@@ -9138,12 +9180,23 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
9138
9180
|
additionalIndicators: _additional,
|
|
9139
9181
|
...summary
|
|
9140
9182
|
} = row;
|
|
9183
|
+
const bytes = Buffer.byteLength(JSON.stringify(summary));
|
|
9184
|
+
if (bytes > 8e4)
|
|
9185
|
+
throw new Error(
|
|
9186
|
+
"Response too large. Use runtime_get_signal for record details."
|
|
9187
|
+
);
|
|
9188
|
+
if (responseBytes + bytes > 8e4) {
|
|
9189
|
+
state.values.unshift(row);
|
|
9190
|
+
break;
|
|
9191
|
+
}
|
|
9192
|
+
responseBytes += bytes;
|
|
9141
9193
|
items.push(summary);
|
|
9142
9194
|
}
|
|
9143
9195
|
let next = "0";
|
|
9144
9196
|
if (state.values.length || state.keys.length || state.scan !== "0") {
|
|
9145
|
-
next = mcpId(
|
|
9146
|
-
await import_redis4.mcpStorage.put(`cursor:${next}`, state,
|
|
9197
|
+
next = mcpId(JSON.stringify(state));
|
|
9198
|
+
if (!await import_redis4.mcpStorage.put(`cursor:${next}`, state, RUNTIME_CURSOR_TTL))
|
|
9199
|
+
throw new Error("MCP cursor persistence failed");
|
|
9147
9200
|
}
|
|
9148
9201
|
return {
|
|
9149
9202
|
items,
|
|
@@ -9153,7 +9206,17 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
9153
9206
|
source: "runtime_storage",
|
|
9154
9207
|
window: { startTime: request.startTime, endTime: request.endTime },
|
|
9155
9208
|
coverage: "stored_records_only",
|
|
9156
|
-
|
|
9209
|
+
pagination: {
|
|
9210
|
+
scanComplete: next === "0",
|
|
9211
|
+
storageReads,
|
|
9212
|
+
inspected,
|
|
9213
|
+
cursorTtlSeconds: RUNTIME_CURSOR_TTL,
|
|
9214
|
+
consistency: "mutable_storage_scan",
|
|
9215
|
+
order: "storage_scan",
|
|
9216
|
+
retentionStartTime: null,
|
|
9217
|
+
persistenceCompleteness: "unknown"
|
|
9218
|
+
},
|
|
9219
|
+
note: "Continue until cursor=0, including empty pages. Cursors are shared across server processes and retained for 24 hours. Retry identical requests after transport failures. If CURSOR_EXPIRED, restart the same window and deduplicate record identities. Storage scans may repeat rows and are not immutable snapshots; missing records do not prove no activity."
|
|
9157
9220
|
};
|
|
9158
9221
|
};
|
|
9159
9222
|
var readMcpSignal = async (userName, signalId, deploymentId, timestamp, strategy) => {
|
|
@@ -9273,6 +9336,7 @@ var readMcpBacktestResult = async (userName, id, offset = 0, limit = 20) => {
|
|
|
9273
9336
|
// Annotate the CommonJS export names for ESM import in node:
|
|
9274
9337
|
0 && (module.exports = {
|
|
9275
9338
|
MCP_JOB_TTL,
|
|
9339
|
+
McpCursorError,
|
|
9276
9340
|
cancelMcpJob,
|
|
9277
9341
|
enqueueMcpJob,
|
|
9278
9342
|
isMcpRecord,
|
|
@@ -9295,5 +9359,6 @@ var readMcpBacktestResult = async (userName, id, offset = 0, limit = 20) => {
|
|
|
9295
9359
|
readMcpSignal,
|
|
9296
9360
|
readMcpStrategies,
|
|
9297
9361
|
redactMcpData,
|
|
9362
|
+
redactMcpJobLog,
|
|
9298
9363
|
requireMcpDeployment
|
|
9299
9364
|
});
|
package/dist/mcp.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import "./chunk-GHSLOJLE.mjs";
|
|
|
17
17
|
import "./chunk-Y6FXYEAI.mjs";
|
|
18
18
|
|
|
19
19
|
// src/mcp.ts
|
|
20
|
-
import { createHash as createHash2
|
|
20
|
+
import { createHash as createHash2 } from "crypto";
|
|
21
21
|
import { createIndicators } from "@tradejs/core/indicators";
|
|
22
22
|
import { intervalToMs } from "@tradejs/core/data";
|
|
23
23
|
import {
|
|
@@ -200,7 +200,7 @@ var readAuthorizedMcpJob = async (principal, id) => {
|
|
|
200
200
|
throw new Error("Insufficient scope");
|
|
201
201
|
return job;
|
|
202
202
|
};
|
|
203
|
-
var
|
|
203
|
+
var redactMcpJobLog = (line) => line.replace(/\u001b\[[0-9;]*m/g, "").replace(/\b(Bearer|Basic)\s+\S+/gi, "$1 [redacted]").replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]*:[^\s/@]+@/gi, "$1[redacted]@").replace(/((?:authorization|cookie)\s*:\s*)[^\r\n]+/gi, "$1[redacted]").replace(
|
|
204
204
|
/((?:[\w.-]*(?:secret|password|token|api[_-]?key|private[_-]?key|authorization|cookie|credential)[\w.-]*)["']?\s*[:=]\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;&}]+)/gi,
|
|
205
205
|
"$1[redacted]"
|
|
206
206
|
).slice(0, 1e3);
|
|
@@ -211,7 +211,7 @@ var readMcpJobLogs = async (principal, id, offset = 0, limit = 50) => {
|
|
|
211
211
|
const lines = [];
|
|
212
212
|
let bytes = 0;
|
|
213
213
|
for (const line of job.logs.slice(offset, offset + limit)) {
|
|
214
|
-
const redacted =
|
|
214
|
+
const redacted = redactMcpJobLog(line);
|
|
215
215
|
const size = Buffer.byteLength(JSON.stringify(redacted)) + 1;
|
|
216
216
|
if (bytes + size > 48e3) break;
|
|
217
217
|
lines.push(redacted);
|
|
@@ -407,11 +407,34 @@ var readMcpStrategies = async (userName, deploymentId) => {
|
|
|
407
407
|
observedAt: Date.now()
|
|
408
408
|
};
|
|
409
409
|
};
|
|
410
|
+
var McpCursorError = class extends Error {
|
|
411
|
+
constructor(code) {
|
|
412
|
+
super(
|
|
413
|
+
code === "CURSOR_EXPIRED" ? "Invalid storage cursor: expired or unavailable; restart the same window and deduplicate record identities" : "Invalid storage cursor: user, account or filters changed"
|
|
414
|
+
);
|
|
415
|
+
this.code = code;
|
|
416
|
+
this.name = "McpCursorError";
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
var RUNTIME_CURSOR_TTL = 86400;
|
|
410
420
|
var readMcpRuntimePage = async (userName, kind, request) => {
|
|
411
421
|
const deployment = await requireMcpDeployment(userName, request.deploymentId);
|
|
412
422
|
const prefix = kind === "signals" ? redisKeys2.runtimeSignalBuckets(userName) : kind === "evaluations" ? redisKeys2.runtimeSignalEvaluationBuckets(userName) : redisKeys2.runtimeTrades(userName);
|
|
413
|
-
const
|
|
414
|
-
|
|
423
|
+
const limit = request.limit ?? 50;
|
|
424
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 50)
|
|
425
|
+
throw new Error("Select a page limit of 1\u201350");
|
|
426
|
+
const binding = mcpId(
|
|
427
|
+
JSON.stringify({
|
|
428
|
+
userName,
|
|
429
|
+
kind,
|
|
430
|
+
accountId: deployment.accountId,
|
|
431
|
+
deploymentId: request.deploymentId,
|
|
432
|
+
startTime: request.startTime,
|
|
433
|
+
endTime: request.endTime,
|
|
434
|
+
strategy: request.strategy ?? null,
|
|
435
|
+
symbol: request.symbol ?? null
|
|
436
|
+
})
|
|
437
|
+
);
|
|
415
438
|
let state = {
|
|
416
439
|
binding,
|
|
417
440
|
scan: "0",
|
|
@@ -424,29 +447,46 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
424
447
|
const saved = await mcpStorage2.get(
|
|
425
448
|
`cursor:${request.cursor}`
|
|
426
449
|
);
|
|
427
|
-
if (!saved
|
|
428
|
-
|
|
429
|
-
state = saved;
|
|
430
|
-
}
|
|
431
|
-
if (!state.keys.length && !state.values.length && (!state.scanned || state.scan !== "0")) {
|
|
432
|
-
const page = await scanDataKeys(prefix, state.scan);
|
|
433
|
-
state = { ...state, scan: page.cursor, keys: page.keys, scanned: true };
|
|
450
|
+
if (!saved) throw new McpCursorError("CURSOR_EXPIRED");
|
|
451
|
+
if (saved.binding !== binding) throw new McpCursorError("CURSOR_MISMATCH");
|
|
452
|
+
state = structuredClone(saved);
|
|
434
453
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
454
|
+
const items = [];
|
|
455
|
+
let storageReads = 0;
|
|
456
|
+
let inspected = 0;
|
|
457
|
+
let responseBytes = 0;
|
|
458
|
+
while (items.length < limit && storageReads < 40 && inspected < 1e3) {
|
|
459
|
+
if (!state.values.length) {
|
|
460
|
+
if (!state.keys.length) {
|
|
461
|
+
if (state.scanned && state.scan === "0") break;
|
|
462
|
+
const page = await scanDataKeys(prefix, state.scan);
|
|
463
|
+
storageReads++;
|
|
464
|
+
state.scan = page.cursor;
|
|
465
|
+
state.scanned = true;
|
|
466
|
+
state.keys = page.keys.filter((key2) => {
|
|
467
|
+
if (kind === "orders")
|
|
468
|
+
return !key2.slice(prefix.length).startsWith("days:");
|
|
469
|
+
const suffix = key2.slice(prefix.length);
|
|
470
|
+
const day = suffix.slice(0, 10);
|
|
471
|
+
return day >= getRuntimeStorageDayKey(request.startTime) && day <= getRuntimeStorageDayKey(request.endTime - 1) && (!request.strategy || suffix.slice(11) === request.strategy);
|
|
472
|
+
});
|
|
473
|
+
if (!state.keys.length) continue;
|
|
474
|
+
}
|
|
475
|
+
const key = state.keys[0];
|
|
476
|
+
storageReads++;
|
|
477
|
+
if (kind === "orders") {
|
|
478
|
+
state.keys.shift();
|
|
440
479
|
state.values = [await getDataStrict2(key)];
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
480
|
+
} else {
|
|
481
|
+
const page = await scanHashJson(key, state.hash);
|
|
482
|
+
state.values = page.values;
|
|
483
|
+
state.hash = page.cursor;
|
|
484
|
+
if (page.cursor === "0") state.keys.shift();
|
|
485
|
+
}
|
|
486
|
+
if (!state.values.length) continue;
|
|
446
487
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
for (let row of state.values.splice(0, 50)) {
|
|
488
|
+
let row = state.values.shift();
|
|
489
|
+
inspected++;
|
|
450
490
|
if (kind === "signals" && isMcpRecord(row) && typeof row.symbol === "string" && typeof row.signalId === "string")
|
|
451
491
|
row = await getDataStrict2(
|
|
452
492
|
redisKeys2.storeSignal(row.symbol, row.signalId)
|
|
@@ -464,12 +504,23 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
464
504
|
additionalIndicators: _additional,
|
|
465
505
|
...summary
|
|
466
506
|
} = row;
|
|
507
|
+
const bytes = Buffer.byteLength(JSON.stringify(summary));
|
|
508
|
+
if (bytes > 8e4)
|
|
509
|
+
throw new Error(
|
|
510
|
+
"Response too large. Use runtime_get_signal for record details."
|
|
511
|
+
);
|
|
512
|
+
if (responseBytes + bytes > 8e4) {
|
|
513
|
+
state.values.unshift(row);
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
responseBytes += bytes;
|
|
467
517
|
items.push(summary);
|
|
468
518
|
}
|
|
469
519
|
let next = "0";
|
|
470
520
|
if (state.values.length || state.keys.length || state.scan !== "0") {
|
|
471
|
-
next = mcpId(
|
|
472
|
-
await mcpStorage2.put(`cursor:${next}`, state,
|
|
521
|
+
next = mcpId(JSON.stringify(state));
|
|
522
|
+
if (!await mcpStorage2.put(`cursor:${next}`, state, RUNTIME_CURSOR_TTL))
|
|
523
|
+
throw new Error("MCP cursor persistence failed");
|
|
473
524
|
}
|
|
474
525
|
return {
|
|
475
526
|
items,
|
|
@@ -479,7 +530,17 @@ var readMcpRuntimePage = async (userName, kind, request) => {
|
|
|
479
530
|
source: "runtime_storage",
|
|
480
531
|
window: { startTime: request.startTime, endTime: request.endTime },
|
|
481
532
|
coverage: "stored_records_only",
|
|
482
|
-
|
|
533
|
+
pagination: {
|
|
534
|
+
scanComplete: next === "0",
|
|
535
|
+
storageReads,
|
|
536
|
+
inspected,
|
|
537
|
+
cursorTtlSeconds: RUNTIME_CURSOR_TTL,
|
|
538
|
+
consistency: "mutable_storage_scan",
|
|
539
|
+
order: "storage_scan",
|
|
540
|
+
retentionStartTime: null,
|
|
541
|
+
persistenceCompleteness: "unknown"
|
|
542
|
+
},
|
|
543
|
+
note: "Continue until cursor=0, including empty pages. Cursors are shared across server processes and retained for 24 hours. Retry identical requests after transport failures. If CURSOR_EXPIRED, restart the same window and deduplicate record identities. Storage scans may repeat rows and are not immutable snapshots; missing records do not prove no activity."
|
|
483
544
|
};
|
|
484
545
|
};
|
|
485
546
|
var readMcpSignal = async (userName, signalId, deploymentId, timestamp, strategy) => {
|
|
@@ -598,6 +659,7 @@ var readMcpBacktestResult = async (userName, id, offset = 0, limit = 20) => {
|
|
|
598
659
|
};
|
|
599
660
|
export {
|
|
600
661
|
MCP_JOB_TTL,
|
|
662
|
+
McpCursorError,
|
|
601
663
|
cancelMcpJob,
|
|
602
664
|
enqueueMcpJob,
|
|
603
665
|
isMcpRecord,
|
|
@@ -620,5 +682,6 @@ export {
|
|
|
620
682
|
readMcpSignal,
|
|
621
683
|
readMcpStrategies,
|
|
622
684
|
redactMcpData,
|
|
685
|
+
redactMcpJobLog,
|
|
623
686
|
requireMcpDeployment
|
|
624
687
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradejs/node",
|
|
3
|
-
"version": "3.1.29-beta.
|
|
3
|
+
"version": "3.1.29-beta.276",
|
|
4
4
|
"description": "Node-only runtime for the TradeJS TypeScript framework: strategies, backtests, Pine strategy loading, and plugin registries.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tradejs",
|
|
@@ -105,9 +105,9 @@
|
|
|
105
105
|
"dependencies": {
|
|
106
106
|
"@langchain/core": "^1.2.3",
|
|
107
107
|
"@langchain/openai": "^1.5.5",
|
|
108
|
-
"@tradejs/core": "^3.1.29-beta.
|
|
109
|
-
"@tradejs/infra": "^3.1.29-beta.
|
|
110
|
-
"@tradejs/types": "^3.1.29-beta.
|
|
108
|
+
"@tradejs/core": "^3.1.29-beta.276",
|
|
109
|
+
"@tradejs/infra": "^3.1.29-beta.276",
|
|
110
|
+
"@tradejs/types": "^3.1.29-beta.276",
|
|
111
111
|
"@types/node": "^24",
|
|
112
112
|
"chalk": "4.1.2",
|
|
113
113
|
"ioredis": "5.11.1",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"typescript": "^5.9"
|
|
121
121
|
},
|
|
122
122
|
"devDependencies": {
|
|
123
|
-
"@tradejs/indicators": "^3.1.29-beta.
|
|
123
|
+
"@tradejs/indicators": "^3.1.29-beta.276",
|
|
124
124
|
"@tradejs/strategy-adaptive-momentum-ribbon": "3.0.5",
|
|
125
125
|
"@tradejs/strategy-hyperliquid-consensus": "3.0.5",
|
|
126
126
|
"@tradejs/strategy-kit": "3.0.6",
|