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

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,35 @@ __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
+ const temporary = file + '.request.tmp-' + process.pid;
53
+ fs.writeFileSync(temporary, request, {flag:'wx'});
54
+ try { fs.linkSync(temporary, file + '.request'); }
55
+ catch (error) { if (error.code !== 'EEXIST') throw error; if (fs.readFileSync(file + '.request', 'utf8') !== request) throw new Error('Replay request identity conflict'); }
56
+ finally { fs.unlinkSync(temporary); }
57
+ process.on('SIGTERM', () => { fs.writeFileSync(file + '.cancel', 'cancel'); process.exit(143); });
58
+ const timer = setInterval(() => {
59
+ try {
60
+ const result = JSON.parse(fs.readFileSync(file + '.result', 'utf8'));
61
+ console.log(result.message);
62
+ clearInterval(timer);
63
+ process.exit(result.exitCode === 0 ? 0 : 1);
64
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
65
+ }, 1000);
66
+ `;
48
67
  var buildMcpReplayProcess = async ({
49
68
  evidencePath,
50
69
  outDir,
@@ -52,79 +71,34 @@ var buildMcpReplayProcess = async ({
52
71
  projectRoot,
53
72
  env
54
73
  }) => {
55
- if (!/^[a-zA-Z0-9_-]{1,100}$/.test(runId))
56
- throw new Error("Invalid replay container run id");
74
+ if (!/^[a-f0-9]{64}$/.test(runId))
75
+ throw new Error("Invalid replay request id");
57
76
  const bundle = await (0, runtimeEvidenceArtifacts_exports.verifyRuntimeEvidenceBundle)(import_node_path.default.dirname(evidencePath));
58
77
  if (import_node_path.default.resolve(evidencePath) !== import_node_path.default.resolve(bundle.payloadPath))
59
78
  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
79
+ const relative = import_node_path.default.relative(import_node_path.default.join(projectRoot, "data"), evidencePath);
80
+ if (relative.startsWith("..") || import_node_path.default.isAbsolute(relative))
81
+ throw new Error("Evidence must be inside Project data");
82
+ const root = env.MCP_REPLAY_REQUEST_ROOT || import_node_path.default.join(projectRoot, "data/mcp-replay-requests");
83
+ const heartbeat = JSON.parse(
84
+ await import_promises.default.readFile(import_node_path.default.join(root, "heartbeat.json"), "utf8")
126
85
  );
127
- return { executable: "docker", args };
86
+ if (!Number.isFinite(heartbeat.observedAt) || Date.now() - heartbeat.observedAt < 0 || Date.now() - heartbeat.observedAt > 12e4)
87
+ throw new Error("Host replay runner heartbeat is stale");
88
+ const bytes = await import_promises.default.readFile(evidencePath);
89
+ const request = JSON.stringify({
90
+ schemaVersion: 1,
91
+ id: runId,
92
+ evidence: relative,
93
+ artifactId: bundle.manifest.artifactId,
94
+ deploymentId: bundle.manifest.deploymentId,
95
+ payloadSha256: (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex")
96
+ });
97
+ void outDir;
98
+ return {
99
+ executable: process.execPath,
100
+ args: ["-e", client, root, runId, request]
101
+ };
128
102
  };
129
103
  // Annotate the CommonJS export names for ESM import in node:
130
104
  0 && (module.exports = {
@@ -35,23 +35,42 @@ __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
+ const temporary = file + '.request.tmp-' + process.pid;
60
+ fs.writeFileSync(temporary, request, {flag:'wx'});
61
+ try { fs.linkSync(temporary, file + '.request'); }
62
+ catch (error) { if (error.code !== 'EEXIST') throw error; if (fs.readFileSync(file + '.request', 'utf8') !== request) throw new Error('Replay request identity conflict'); }
63
+ finally { fs.unlinkSync(temporary); }
64
+ process.on('SIGTERM', () => { fs.writeFileSync(file + '.cancel', 'cancel'); process.exit(143); });
65
+ const timer = setInterval(() => {
66
+ try {
67
+ const result = JSON.parse(fs.readFileSync(file + '.result', 'utf8'));
68
+ console.log(result.message);
69
+ clearInterval(timer);
70
+ process.exit(result.exitCode === 0 ? 0 : 1);
71
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
72
+ }, 1000);
73
+ `;
55
74
  var buildMcpReplayProcess = async ({
56
75
  evidencePath,
57
76
  outDir,
@@ -59,79 +78,34 @@ var buildMcpReplayProcess = async ({
59
78
  projectRoot,
60
79
  env
61
80
  }) => {
62
- if (!/^[a-zA-Z0-9_-]{1,100}$/.test(runId))
63
- throw new Error("Invalid replay container run id");
81
+ if (!/^[a-f0-9]{64}$/.test(runId))
82
+ throw new Error("Invalid replay request id");
64
83
  const bundle = await (0, runtimeEvidenceArtifacts_exports.verifyRuntimeEvidenceBundle)(import_node_path.default.dirname(evidencePath));
65
84
  if (import_node_path.default.resolve(evidencePath) !== import_node_path.default.resolve(bundle.payloadPath))
66
85
  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
86
+ const relative = import_node_path.default.relative(import_node_path.default.join(projectRoot, "data"), evidencePath);
87
+ if (relative.startsWith("..") || import_node_path.default.isAbsolute(relative))
88
+ throw new Error("Evidence must be inside Project data");
89
+ const root = env.MCP_REPLAY_REQUEST_ROOT || import_node_path.default.join(projectRoot, "data/mcp-replay-requests");
90
+ const heartbeat = JSON.parse(
91
+ await import_promises.default.readFile(import_node_path.default.join(root, "heartbeat.json"), "utf8")
133
92
  );
134
- return { executable: "docker", args };
93
+ if (!Number.isFinite(heartbeat.observedAt) || Date.now() - heartbeat.observedAt < 0 || Date.now() - heartbeat.observedAt > 12e4)
94
+ throw new Error("Host replay runner heartbeat is stale");
95
+ const bytes = await import_promises.default.readFile(evidencePath);
96
+ const request = JSON.stringify({
97
+ schemaVersion: 1,
98
+ id: runId,
99
+ evidence: relative,
100
+ artifactId: bundle.manifest.artifactId,
101
+ deploymentId: bundle.manifest.deploymentId,
102
+ payloadSha256: (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex")
103
+ });
104
+ void outDir;
105
+ return {
106
+ executable: process.execPath,
107
+ args: ["-e", client, root, runId, request]
108
+ };
135
109
  };
136
110
 
137
111
  // src/scripts/mcpWorker.ts
@@ -140,7 +114,7 @@ var leaseKey = "worker:lease";
140
114
  var LEASE_SECONDS = 15;
141
115
  var JOB_TIMEOUT_MS = 60 * 60 * 1e3;
142
116
  var main = async (options = {}) => {
143
- const owner = (0, import_node_crypto.randomUUID)();
117
+ const owner = (0, import_node_crypto2.randomUUID)();
144
118
  if (!await import_redis.mcpStorage.put(leaseKey, owner, LEASE_SECONDS, true))
145
119
  throw new Error("Another MCP worker owns this storage");
146
120
  let stopping = false;
@@ -148,6 +122,18 @@ var main = async (options = {}) => {
148
122
  let renewing = false;
149
123
  let leaseLost = false;
150
124
  let manifestHash = "";
125
+ let replayReady = false;
126
+ const updateReplayReady = async () => {
127
+ try {
128
+ const root = process.env.MCP_REPLAY_REQUEST_ROOT || `${process.env.PROJECT_CWD || process.cwd()}/data/mcp-replay-requests`;
129
+ const heartbeat2 = JSON.parse(
130
+ await (0, import_promises2.readFile)(`${root}/heartbeat.json`, "utf8")
131
+ );
132
+ replayReady = Number.isFinite(heartbeat2.observedAt) && Date.now() - heartbeat2.observedAt >= 0 && Date.now() - heartbeat2.observedAt < 12e4;
133
+ } catch {
134
+ replayReady = false;
135
+ }
136
+ };
151
137
  const signalChild = (signal) => {
152
138
  if (!child?.pid) return;
153
139
  try {
@@ -161,9 +147,7 @@ var main = async (options = {}) => {
161
147
  const heartbeatValue = () => ({
162
148
  observedAt: Date.now(),
163
149
  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
- )
150
+ replayAvailable: replayReady
167
151
  });
168
152
  const stop = () => {
169
153
  stopping = true;
@@ -179,6 +163,7 @@ var main = async (options = {}) => {
179
163
  try {
180
164
  if (!await import_redis.mcpStorage.compare(leaseKey, owner, owner, LEASE_SECONDS))
181
165
  throw new Error("Worker lease lost");
166
+ await updateReplayReady();
182
167
  await import_redis.mcpStorage.put("worker:heartbeat", heartbeatValue(), LEASE_SECONDS);
183
168
  } catch {
184
169
  leaseLost = true;
@@ -189,6 +174,7 @@ var main = async (options = {}) => {
189
174
  }, 2e3);
190
175
  try {
191
176
  manifestHash = await (0, import_mcp.mcpPackageManifestHash)();
177
+ await updateReplayReady();
192
178
  await import_redis.mcpStorage.put("worker:heartbeat", heartbeatValue(), LEASE_SECONDS);
193
179
  let cursor = "0";
194
180
  do {
@@ -253,7 +239,7 @@ var main = async (options = {}) => {
253
239
  );
254
240
  continue;
255
241
  }
256
- await (0, import_promises.mkdir)((0, import_mcp.mcpJobDirectory)(started), { recursive: true });
242
+ await (0, import_promises2.mkdir)((0, import_mcp.mcpJobDirectory)(started), { recursive: true });
257
243
  if (started.kind === "backtest")
258
244
  await (0, import_redis.setDataStrict)(
259
245
  import_redis.redisKeys.backtestConfig(
@@ -276,7 +262,9 @@ var main = async (options = {}) => {
276
262
  };
277
263
  if (started.kind === "runtime-feedback-replay") {
278
264
  for (const name of Object.keys(jobEnv)) {
279
- if (/secret|token|api.?key|private.?key|cookie|credential/i.test(name))
265
+ if (/secret|password|token|api.?key|private.?key|cookie|credential/i.test(
266
+ name
267
+ ))
280
268
  delete jobEnv[name];
281
269
  }
282
270
  Object.assign(jobEnv, {
@@ -317,8 +305,6 @@ var main = async (options = {}) => {
317
305
  );
318
306
  continue;
319
307
  }
320
- if (started.kind === "runtime-feedback-replay" && process.env.PG_PASSWORD)
321
- jobEnv.PG_PASSWORD = process.env.PG_PASSWORD;
322
308
  child = (0, import_node_child_process.spawn)(command.executable, command.args, {
323
309
  cwd: process.env.PROJECT_CWD || process.cwd(),
324
310
  env: jobEnv,
@@ -373,20 +359,6 @@ var main = async (options = {}) => {
373
359
  );
374
360
  currentChild.once("exit", (code) => resolve({ code }));
375
361
  });
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
362
  clearTimeout(timeout);
391
363
  clearInterval(monitor);
392
364
  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.280",
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.280",
41
+ "@tradejs/core": "^3.1.29-beta.280",
42
+ "@tradejs/indicators": "^3.1.29-beta.280",
43
+ "@tradejs/infra": "^3.1.29-beta.280",
44
+ "@tradejs/node": "^3.1.29-beta.280",
45
45
  "@tradejs/strategy-kit": "3.0.6",
46
- "@tradejs/types": "^3.1.29-beta.277",
46
+ "@tradejs/types": "^3.1.29-beta.280",
47
47
  "args": "^5.0.3",
48
48
  "bcryptjs": "^2.4.3",
49
49
  "chalk": "4.1.2",