@tradejs/cli 3.1.29-beta.277 → 3.1.29-beta.278

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.
@@ -35,16 +35,32 @@ __export(mcpReplayProcess_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(mcpReplayProcess_exports);
37
37
  var import_node_path = __toESM(require("path"));
38
-
39
- // src/lib/runtimeEvidenceProducer.ts
40
- var runtimeEvidenceProducer_exports = {};
41
- __reExport(runtimeEvidenceProducer_exports, require("@tradejs/node/diagnostics"));
38
+ var import_node_crypto = require("crypto");
39
+ var import_promises = __toESM(require("fs/promises"));
42
40
 
43
41
  // src/lib/runtimeEvidenceArtifacts.ts
44
42
  var runtimeEvidenceArtifacts_exports = {};
45
43
  __reExport(runtimeEvidenceArtifacts_exports, require("@tradejs/node/diagnostics"));
46
44
 
47
45
  // src/lib/mcpReplayProcess.ts
46
+ var client = `
47
+ const fs = require('node:fs');
48
+ const path = require('node:path');
49
+ const [root, id, request] = process.argv.slice(1);
50
+ const file = path.join(root, id);
51
+ fs.mkdirSync(root, {recursive:true});
52
+ try { fs.writeFileSync(file + '.request', request, {flag:'wx'}); }
53
+ catch (error) { if (error.code !== 'EEXIST') throw error; if (fs.readFileSync(file + '.request', 'utf8') !== request) throw new Error('Replay request identity conflict'); }
54
+ process.on('SIGTERM', () => { fs.writeFileSync(file + '.cancel', 'cancel'); process.exit(143); });
55
+ const timer = setInterval(() => {
56
+ try {
57
+ const result = JSON.parse(fs.readFileSync(file + '.result', 'utf8'));
58
+ console.log(result.message);
59
+ clearInterval(timer);
60
+ process.exit(result.exitCode === 0 ? 0 : 1);
61
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
62
+ }, 1000);
63
+ `;
48
64
  var buildMcpReplayProcess = async ({
49
65
  evidencePath,
50
66
  outDir,
@@ -52,79 +68,34 @@ var buildMcpReplayProcess = async ({
52
68
  projectRoot,
53
69
  env
54
70
  }) => {
55
- if (!/^[a-zA-Z0-9_-]{1,100}$/.test(runId))
56
- throw new Error("Invalid replay container run id");
71
+ if (!/^[a-f0-9]{64}$/.test(runId))
72
+ throw new Error("Invalid replay request id");
57
73
  const bundle = await (0, runtimeEvidenceArtifacts_exports.verifyRuntimeEvidenceBundle)(import_node_path.default.dirname(evidencePath));
58
74
  if (import_node_path.default.resolve(evidencePath) !== import_node_path.default.resolve(bundle.payloadPath))
59
75
  throw new Error("Replay requires the verified bundle payload");
60
- const producer = (0, runtimeEvidenceProducer_exports.parseRuntimeEvidenceProducer)(bundle.artifact.producer);
61
- if (!env.MCP_REPLAY_HOST_PROJECT_ROOT || !env.MCP_REPLAY_NETWORK || !env.MCP_REPLAY_IMAGE_REPOSITORY)
62
- throw new Error(
63
- "Image-bound replay requires MCP_REPLAY_HOST_PROJECT_ROOT MCP_REPLAY_NETWORK and MCP_REPLAY_IMAGE_REPOSITORY"
64
- );
65
- const hostPath = (file) => {
66
- const relative = import_node_path.default.relative(projectRoot, file);
67
- if (relative.startsWith("..") || import_node_path.default.isAbsolute(relative))
68
- throw new Error("Replay mounts must be inside the Project");
69
- return import_node_path.default.join(env.MCP_REPLAY_HOST_PROJECT_ROOT, relative);
70
- };
71
- const args = [
72
- "run",
73
- "--rm",
74
- "--name",
75
- `tradejs-replay-${runId}`,
76
- "--memory=1536m",
77
- "--cpus=1",
78
- "--pids-limit=512",
79
- "--read-only",
80
- "--cap-drop=ALL",
81
- "--security-opt=no-new-privileges",
82
- "--network",
83
- env.MCP_REPLAY_NETWORK,
84
- "--tmpfs",
85
- "/tmp",
86
- "--tmpfs",
87
- "/app/data",
88
- "--entrypoint",
89
- "/app/node_modules/.bin/tradejs",
90
- "--mount",
91
- `type=bind,src=${hostPath(import_node_path.default.dirname(evidencePath))},dst=/evidence,readonly`,
92
- "--mount",
93
- `type=bind,src=${hostPath(outDir)},dst=/replay-output`
94
- ];
95
- const safeEnv = {
96
- PROJECT_CWD: "/app",
97
- DOTENV_CONFIG_PATH: "/dev/null",
98
- TRADEJS_PROJECT_SHA: producer.projectSha,
99
- TRADEJS_PROJECT_IMAGE_DIGEST: producer.imageDigest,
100
- NODE_OPTIONS: "--max-old-space-size=1024",
101
- MAKE_ORDERS: "false",
102
- TRADEJS_EXTERNAL_ORDER_PLACEMENT: "false",
103
- AI_ENABLED: "false",
104
- ML_ENABLED: "false",
105
- RUNTIME_FEEDBACK_ISOLATED_REDIS: "true",
106
- REDIS_HOST: env.MCP_REPLAY_REDIS_HOST,
107
- REDIS_PORT: "6379",
108
- REDIS_DB: "0",
109
- PGOPTIONS: "-c default_transaction_read_only=on",
110
- TRADEJS_TIMESCALE_READ_ONLY: "true"
111
- };
112
- for (const name of ["PG_HOST", "PG_PORT", "PG_USER", "PG_DATABASE", "PG_DB"])
113
- if (env[name]) safeEnv[name] = env[name];
114
- for (const [name, value] of Object.entries(safeEnv))
115
- if (value !== void 0) args.push("--env", `${name}=${value}`);
116
- if (env.PG_PASSWORD) args.push("--env", "PG_PASSWORD");
117
- args.push(
118
- `${env.MCP_REPLAY_IMAGE_REPOSITORY}@${producer.imageDigest}`,
119
- "runtime-feedback-replay",
120
- "--runtimeEvidence",
121
- `/evidence/${import_node_path.default.basename(evidencePath)}`,
122
- "--outDir",
123
- "/replay-output",
124
- "--runId",
125
- runId
76
+ const relative = import_node_path.default.relative(import_node_path.default.join(projectRoot, "data"), evidencePath);
77
+ if (relative.startsWith("..") || import_node_path.default.isAbsolute(relative))
78
+ throw new Error("Evidence must be inside Project data");
79
+ const root = env.MCP_REPLAY_REQUEST_ROOT || import_node_path.default.join(projectRoot, "data/mcp-replay-requests");
80
+ const heartbeat = JSON.parse(
81
+ await import_promises.default.readFile(import_node_path.default.join(root, "heartbeat.json"), "utf8")
126
82
  );
127
- return { executable: "docker", args };
83
+ if (!Number.isFinite(heartbeat.observedAt) || Date.now() - heartbeat.observedAt < 0 || Date.now() - heartbeat.observedAt > 12e4)
84
+ throw new Error("Host replay runner heartbeat is stale");
85
+ const bytes = await import_promises.default.readFile(evidencePath);
86
+ const request = JSON.stringify({
87
+ schemaVersion: 1,
88
+ id: runId,
89
+ evidence: relative,
90
+ artifactId: bundle.manifest.artifactId,
91
+ deploymentId: bundle.manifest.deploymentId,
92
+ payloadSha256: (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex")
93
+ });
94
+ void outDir;
95
+ return {
96
+ executable: process.execPath,
97
+ args: ["-e", client, root, runId, request]
98
+ };
128
99
  };
129
100
  // Annotate the CommonJS export names for ESM import in node:
130
101
  0 && (module.exports = {
@@ -35,23 +35,39 @@ __export(mcpWorker_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(mcpWorker_exports);
37
37
  var import_node_child_process = require("child_process");
38
- var import_node_crypto = require("crypto");
39
- var import_promises = require("fs/promises");
38
+ var import_node_crypto2 = require("crypto");
39
+ var import_promises2 = require("fs/promises");
40
40
  var import_redis = require("@tradejs/infra/redis");
41
41
  var import_mcp = require("@tradejs/node/mcp");
42
42
 
43
43
  // src/lib/mcpReplayProcess.ts
44
44
  var import_node_path = __toESM(require("path"));
45
-
46
- // src/lib/runtimeEvidenceProducer.ts
47
- var runtimeEvidenceProducer_exports = {};
48
- __reExport(runtimeEvidenceProducer_exports, require("@tradejs/node/diagnostics"));
45
+ var import_node_crypto = require("crypto");
46
+ var import_promises = __toESM(require("fs/promises"));
49
47
 
50
48
  // src/lib/runtimeEvidenceArtifacts.ts
51
49
  var runtimeEvidenceArtifacts_exports = {};
52
50
  __reExport(runtimeEvidenceArtifacts_exports, require("@tradejs/node/diagnostics"));
53
51
 
54
52
  // src/lib/mcpReplayProcess.ts
53
+ var client = `
54
+ const fs = require('node:fs');
55
+ const path = require('node:path');
56
+ const [root, id, request] = process.argv.slice(1);
57
+ const file = path.join(root, id);
58
+ fs.mkdirSync(root, {recursive:true});
59
+ try { fs.writeFileSync(file + '.request', request, {flag:'wx'}); }
60
+ catch (error) { if (error.code !== 'EEXIST') throw error; if (fs.readFileSync(file + '.request', 'utf8') !== request) throw new Error('Replay request identity conflict'); }
61
+ process.on('SIGTERM', () => { fs.writeFileSync(file + '.cancel', 'cancel'); process.exit(143); });
62
+ const timer = setInterval(() => {
63
+ try {
64
+ const result = JSON.parse(fs.readFileSync(file + '.result', 'utf8'));
65
+ console.log(result.message);
66
+ clearInterval(timer);
67
+ process.exit(result.exitCode === 0 ? 0 : 1);
68
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
69
+ }, 1000);
70
+ `;
55
71
  var buildMcpReplayProcess = async ({
56
72
  evidencePath,
57
73
  outDir,
@@ -59,79 +75,34 @@ var buildMcpReplayProcess = async ({
59
75
  projectRoot,
60
76
  env
61
77
  }) => {
62
- if (!/^[a-zA-Z0-9_-]{1,100}$/.test(runId))
63
- throw new Error("Invalid replay container run id");
78
+ if (!/^[a-f0-9]{64}$/.test(runId))
79
+ throw new Error("Invalid replay request id");
64
80
  const bundle = await (0, runtimeEvidenceArtifacts_exports.verifyRuntimeEvidenceBundle)(import_node_path.default.dirname(evidencePath));
65
81
  if (import_node_path.default.resolve(evidencePath) !== import_node_path.default.resolve(bundle.payloadPath))
66
82
  throw new Error("Replay requires the verified bundle payload");
67
- const producer = (0, runtimeEvidenceProducer_exports.parseRuntimeEvidenceProducer)(bundle.artifact.producer);
68
- if (!env.MCP_REPLAY_HOST_PROJECT_ROOT || !env.MCP_REPLAY_NETWORK || !env.MCP_REPLAY_IMAGE_REPOSITORY)
69
- throw new Error(
70
- "Image-bound replay requires MCP_REPLAY_HOST_PROJECT_ROOT MCP_REPLAY_NETWORK and MCP_REPLAY_IMAGE_REPOSITORY"
71
- );
72
- const hostPath = (file) => {
73
- const relative = import_node_path.default.relative(projectRoot, file);
74
- if (relative.startsWith("..") || import_node_path.default.isAbsolute(relative))
75
- throw new Error("Replay mounts must be inside the Project");
76
- return import_node_path.default.join(env.MCP_REPLAY_HOST_PROJECT_ROOT, relative);
77
- };
78
- const args = [
79
- "run",
80
- "--rm",
81
- "--name",
82
- `tradejs-replay-${runId}`,
83
- "--memory=1536m",
84
- "--cpus=1",
85
- "--pids-limit=512",
86
- "--read-only",
87
- "--cap-drop=ALL",
88
- "--security-opt=no-new-privileges",
89
- "--network",
90
- env.MCP_REPLAY_NETWORK,
91
- "--tmpfs",
92
- "/tmp",
93
- "--tmpfs",
94
- "/app/data",
95
- "--entrypoint",
96
- "/app/node_modules/.bin/tradejs",
97
- "--mount",
98
- `type=bind,src=${hostPath(import_node_path.default.dirname(evidencePath))},dst=/evidence,readonly`,
99
- "--mount",
100
- `type=bind,src=${hostPath(outDir)},dst=/replay-output`
101
- ];
102
- const safeEnv = {
103
- PROJECT_CWD: "/app",
104
- DOTENV_CONFIG_PATH: "/dev/null",
105
- TRADEJS_PROJECT_SHA: producer.projectSha,
106
- TRADEJS_PROJECT_IMAGE_DIGEST: producer.imageDigest,
107
- NODE_OPTIONS: "--max-old-space-size=1024",
108
- MAKE_ORDERS: "false",
109
- TRADEJS_EXTERNAL_ORDER_PLACEMENT: "false",
110
- AI_ENABLED: "false",
111
- ML_ENABLED: "false",
112
- RUNTIME_FEEDBACK_ISOLATED_REDIS: "true",
113
- REDIS_HOST: env.MCP_REPLAY_REDIS_HOST,
114
- REDIS_PORT: "6379",
115
- REDIS_DB: "0",
116
- PGOPTIONS: "-c default_transaction_read_only=on",
117
- TRADEJS_TIMESCALE_READ_ONLY: "true"
118
- };
119
- for (const name of ["PG_HOST", "PG_PORT", "PG_USER", "PG_DATABASE", "PG_DB"])
120
- if (env[name]) safeEnv[name] = env[name];
121
- for (const [name, value] of Object.entries(safeEnv))
122
- if (value !== void 0) args.push("--env", `${name}=${value}`);
123
- if (env.PG_PASSWORD) args.push("--env", "PG_PASSWORD");
124
- args.push(
125
- `${env.MCP_REPLAY_IMAGE_REPOSITORY}@${producer.imageDigest}`,
126
- "runtime-feedback-replay",
127
- "--runtimeEvidence",
128
- `/evidence/${import_node_path.default.basename(evidencePath)}`,
129
- "--outDir",
130
- "/replay-output",
131
- "--runId",
132
- runId
83
+ const relative = import_node_path.default.relative(import_node_path.default.join(projectRoot, "data"), evidencePath);
84
+ if (relative.startsWith("..") || import_node_path.default.isAbsolute(relative))
85
+ throw new Error("Evidence must be inside Project data");
86
+ const root = env.MCP_REPLAY_REQUEST_ROOT || import_node_path.default.join(projectRoot, "data/mcp-replay-requests");
87
+ const heartbeat = JSON.parse(
88
+ await import_promises.default.readFile(import_node_path.default.join(root, "heartbeat.json"), "utf8")
133
89
  );
134
- return { executable: "docker", args };
90
+ if (!Number.isFinite(heartbeat.observedAt) || Date.now() - heartbeat.observedAt < 0 || Date.now() - heartbeat.observedAt > 12e4)
91
+ throw new Error("Host replay runner heartbeat is stale");
92
+ const bytes = await import_promises.default.readFile(evidencePath);
93
+ const request = JSON.stringify({
94
+ schemaVersion: 1,
95
+ id: runId,
96
+ evidence: relative,
97
+ artifactId: bundle.manifest.artifactId,
98
+ deploymentId: bundle.manifest.deploymentId,
99
+ payloadSha256: (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex")
100
+ });
101
+ void outDir;
102
+ return {
103
+ executable: process.execPath,
104
+ args: ["-e", client, root, runId, request]
105
+ };
135
106
  };
136
107
 
137
108
  // src/scripts/mcpWorker.ts
@@ -140,7 +111,7 @@ var leaseKey = "worker:lease";
140
111
  var LEASE_SECONDS = 15;
141
112
  var JOB_TIMEOUT_MS = 60 * 60 * 1e3;
142
113
  var main = async (options = {}) => {
143
- const owner = (0, import_node_crypto.randomUUID)();
114
+ const owner = (0, import_node_crypto2.randomUUID)();
144
115
  if (!await import_redis.mcpStorage.put(leaseKey, owner, LEASE_SECONDS, true))
145
116
  throw new Error("Another MCP worker owns this storage");
146
117
  let stopping = false;
@@ -148,6 +119,18 @@ var main = async (options = {}) => {
148
119
  let renewing = false;
149
120
  let leaseLost = false;
150
121
  let manifestHash = "";
122
+ let replayReady = false;
123
+ const updateReplayReady = async () => {
124
+ try {
125
+ const root = process.env.MCP_REPLAY_REQUEST_ROOT || `${process.env.PROJECT_CWD || process.cwd()}/data/mcp-replay-requests`;
126
+ const heartbeat2 = JSON.parse(
127
+ await (0, import_promises2.readFile)(`${root}/heartbeat.json`, "utf8")
128
+ );
129
+ replayReady = Number.isFinite(heartbeat2.observedAt) && Date.now() - heartbeat2.observedAt >= 0 && Date.now() - heartbeat2.observedAt < 12e4;
130
+ } catch {
131
+ replayReady = false;
132
+ }
133
+ };
151
134
  const signalChild = (signal) => {
152
135
  if (!child?.pid) return;
153
136
  try {
@@ -161,9 +144,7 @@ var main = async (options = {}) => {
161
144
  const heartbeatValue = () => ({
162
145
  observedAt: Date.now(),
163
146
  packageManifestSha256: manifestHash,
164
- replayAvailable: Boolean(
165
- process.env.MCP_REPLAY_REDIS_HOST && process.env.MCP_REPLAY_HOST_PROJECT_ROOT && process.env.MCP_REPLAY_NETWORK && process.env.MCP_REPLAY_IMAGE_REPOSITORY
166
- )
147
+ replayAvailable: replayReady
167
148
  });
168
149
  const stop = () => {
169
150
  stopping = true;
@@ -179,6 +160,7 @@ var main = async (options = {}) => {
179
160
  try {
180
161
  if (!await import_redis.mcpStorage.compare(leaseKey, owner, owner, LEASE_SECONDS))
181
162
  throw new Error("Worker lease lost");
163
+ await updateReplayReady();
182
164
  await import_redis.mcpStorage.put("worker:heartbeat", heartbeatValue(), LEASE_SECONDS);
183
165
  } catch {
184
166
  leaseLost = true;
@@ -189,6 +171,7 @@ var main = async (options = {}) => {
189
171
  }, 2e3);
190
172
  try {
191
173
  manifestHash = await (0, import_mcp.mcpPackageManifestHash)();
174
+ await updateReplayReady();
192
175
  await import_redis.mcpStorage.put("worker:heartbeat", heartbeatValue(), LEASE_SECONDS);
193
176
  let cursor = "0";
194
177
  do {
@@ -253,7 +236,7 @@ var main = async (options = {}) => {
253
236
  );
254
237
  continue;
255
238
  }
256
- await (0, import_promises.mkdir)((0, import_mcp.mcpJobDirectory)(started), { recursive: true });
239
+ await (0, import_promises2.mkdir)((0, import_mcp.mcpJobDirectory)(started), { recursive: true });
257
240
  if (started.kind === "backtest")
258
241
  await (0, import_redis.setDataStrict)(
259
242
  import_redis.redisKeys.backtestConfig(
@@ -276,7 +259,9 @@ var main = async (options = {}) => {
276
259
  };
277
260
  if (started.kind === "runtime-feedback-replay") {
278
261
  for (const name of Object.keys(jobEnv)) {
279
- if (/secret|token|api.?key|private.?key|cookie|credential/i.test(name))
262
+ if (/secret|password|token|api.?key|private.?key|cookie|credential/i.test(
263
+ name
264
+ ))
280
265
  delete jobEnv[name];
281
266
  }
282
267
  Object.assign(jobEnv, {
@@ -317,8 +302,6 @@ var main = async (options = {}) => {
317
302
  );
318
303
  continue;
319
304
  }
320
- if (started.kind === "runtime-feedback-replay" && process.env.PG_PASSWORD)
321
- jobEnv.PG_PASSWORD = process.env.PG_PASSWORD;
322
305
  child = (0, import_node_child_process.spawn)(command.executable, command.args, {
323
306
  cwd: process.env.PROJECT_CWD || process.cwd(),
324
307
  env: jobEnv,
@@ -373,20 +356,6 @@ var main = async (options = {}) => {
373
356
  );
374
357
  currentChild.once("exit", (code) => resolve({ code }));
375
358
  });
376
- if (started.kind === "runtime-feedback-replay") {
377
- await new Promise((resolve) => {
378
- const cleanup = (0, import_node_child_process.spawn)(
379
- "docker",
380
- ["rm", "--force", `tradejs-replay-${started.id}`],
381
- {
382
- stdio: "ignore",
383
- timeout: 1e4
384
- }
385
- );
386
- cleanup.once("error", () => resolve());
387
- cleanup.once("close", () => resolve());
388
- });
389
- }
390
359
  clearTimeout(timeout);
391
360
  clearInterval(monitor);
392
361
  child = void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tradejs/cli",
3
- "version": "3.1.29-beta.277",
3
+ "version": "3.1.29-beta.278",
4
4
  "description": "Official CLI for the TradeJS TypeScript framework: infra setup, backtests, signals, bots, and ML workflows.",
5
5
  "keywords": [
6
6
  "tradejs",
@@ -37,13 +37,13 @@
37
37
  "tradejs": "dist/cli.js"
38
38
  },
39
39
  "dependencies": {
40
- "@tradejs/connectors": "^3.1.29-beta.277",
41
- "@tradejs/core": "^3.1.29-beta.277",
42
- "@tradejs/indicators": "^3.1.29-beta.277",
43
- "@tradejs/infra": "^3.1.29-beta.277",
44
- "@tradejs/node": "^3.1.29-beta.277",
40
+ "@tradejs/connectors": "^3.1.29-beta.278",
41
+ "@tradejs/core": "^3.1.29-beta.278",
42
+ "@tradejs/indicators": "^3.1.29-beta.278",
43
+ "@tradejs/infra": "^3.1.29-beta.278",
44
+ "@tradejs/node": "^3.1.29-beta.278",
45
45
  "@tradejs/strategy-kit": "3.0.6",
46
- "@tradejs/types": "^3.1.29-beta.277",
46
+ "@tradejs/types": "^3.1.29-beta.278",
47
47
  "args": "^5.0.3",
48
48
  "bcryptjs": "^2.4.3",
49
49
  "chalk": "4.1.2",