@tradejs/cli 3.1.29-beta.298 → 3.1.29-beta.299

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.
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/lib/mcpJobOutputs.ts
31
+ var mcpJobOutputs_exports = {};
32
+ __export(mcpJobOutputs_exports, {
33
+ collectMcpJobOutputs: () => collectMcpJobOutputs,
34
+ mcpBacktestResultTimestamp: () => mcpBacktestResultTimestamp
35
+ });
36
+ module.exports = __toCommonJS(mcpJobOutputs_exports);
37
+ var import_promises = require("fs/promises");
38
+ var import_node_path = __toESM(require("path"));
39
+ var import_redis = require("@tradejs/infra/redis");
40
+ var import_diagnostics = require("@tradejs/node/diagnostics");
41
+ var import_mcp = require("@tradejs/node/mcp");
42
+
43
+ // src/lib/runFormatting.ts
44
+ var import_date_fns = require("date-fns");
45
+ var ListIt = require("list-it");
46
+ var createTimestamp = (date) => (0, import_date_fns.format)(date, "yyyyMMddHHmm");
47
+
48
+ // src/lib/mcpJobOutputs.ts
49
+ var mcpBacktestResultTimestamp = (job) => createTimestamp(new Date(job.createdAt));
50
+ var collectEvidence = async (job) => Promise.all(
51
+ (await (0, import_diagnostics.discoverRuntimeEvidenceBundles)((0, import_mcp.mcpJobDirectory)(job))).map(
52
+ async (bundleDir) => {
53
+ const { manifest } = await (0, import_diagnostics.verifyRuntimeEvidenceBundle)(bundleDir);
54
+ if (manifest.userName !== job.userName || manifest.deploymentId !== job.request.deploymentId || manifest.window.startTime !== job.request.startTime || manifest.window.endTime !== job.request.endTime)
55
+ throw new Error("Runtime evidence does not match the job");
56
+ return {
57
+ bundleDir,
58
+ id: (0, import_mcp.mcpId)(`${manifest.artifactId}:${manifest.payload.sha256}`)
59
+ };
60
+ }
61
+ )
62
+ );
63
+ var collectReplay = async (job) => {
64
+ const sourceDir = import_node_path.default.dirname(String(job.request.verifiedEvidencePath));
65
+ const source = await (0, import_diagnostics.verifyRuntimeEvidenceBundle)(sourceDir);
66
+ const { artifactId, deploymentId, userName, window } = source.manifest;
67
+ if (userName !== job.userName || deploymentId !== job.request.deploymentId || job.request.verifiedEvidenceSha256 !== void 0 && job.request.verifiedEvidenceSha256 !== source.manifest.payload.sha256 || !/^[A-Za-z0-9._-]+$/.test(artifactId) || !/^[A-Za-z0-9._-]+$/.test(deploymentId) || [".", ".."].includes(artifactId) || [".", ".."].includes(deploymentId))
68
+ throw new Error("Runtime evidence does not match the replay job");
69
+ const root = import_node_path.default.join((0, import_mcp.mcpProjectRoot)(), "data/runtime-feedback");
70
+ const receipt = JSON.parse(
71
+ await (0, import_promises.readFile)(
72
+ import_node_path.default.join(root, "completed", deploymentId, `${artifactId}.json`),
73
+ "utf8"
74
+ )
75
+ );
76
+ if (!(0, import_mcp.isMcpRecord)(receipt) || receipt.schemaVersion !== 1 || receipt.runtimeEvidenceArtifactId !== artifactId || typeof receipt.runtimeFeedbackBundle !== "string")
77
+ throw new Error("Invalid replay completion receipt");
78
+ const runId = import_node_path.default.basename(receipt.runtimeFeedbackBundle);
79
+ if (!/^[A-Za-z0-9_-]+$/.test(runId))
80
+ throw new Error("Invalid replay bundle name");
81
+ const day = new Date(window.endTime).toISOString().slice(0, 10).split("-");
82
+ const bundleDir = import_node_path.default.join(
83
+ root,
84
+ "ready",
85
+ deploymentId,
86
+ ...day,
87
+ artifactId,
88
+ runId
89
+ );
90
+ const { feedback } = await (0, import_diagnostics.verifyRuntimeFeedbackReplaySource)({
91
+ bundleDir,
92
+ runtimeEvidenceBundleDir: sourceDir
93
+ });
94
+ const { manifest } = feedback;
95
+ return [
96
+ {
97
+ bundleDir,
98
+ id: (0, import_mcp.mcpId)(
99
+ `${manifest.artifactId}:${manifest.payloads.replayEvidence.sha256}`
100
+ )
101
+ }
102
+ ];
103
+ };
104
+ var collectMcpJobOutputs = async (job) => {
105
+ if (job.kind === "backtest") {
106
+ const config = job.request.frozenConfigId;
107
+ if (typeof config !== "string")
108
+ throw new Error("Invalid frozen backtest config");
109
+ const timestamp = mcpBacktestResultTimestamp(job);
110
+ if (!await (0, import_redis.getDataStrict)(
111
+ import_redis.redisKeys.backtestResults(job.userName, config, timestamp)
112
+ ))
113
+ throw new Error("Backtest result was not saved");
114
+ return { resultId: `${config}:${timestamp}` };
115
+ }
116
+ const bundles = job.kind === "runtime-evidence" ? await collectEvidence(job) : await collectReplay(job);
117
+ if (!bundles.length)
118
+ throw new Error("No verified diagnostic report was saved");
119
+ for (const { id, bundleDir } of bundles) {
120
+ if (!await import_redis.mcpStorage.put(
121
+ (0, import_mcp.mcpArtifactLocationId)(job.userName, id),
122
+ bundleDir,
123
+ import_mcp.MCP_JOB_TTL
124
+ ))
125
+ throw new Error("Diagnostic report location could not be saved");
126
+ }
127
+ return { artifactIds: bundles.map(({ id }) => id) };
128
+ };
129
+ // Annotate the CommonJS export names for ESM import in node:
130
+ 0 && (module.exports = {
131
+ collectMcpJobOutputs,
132
+ mcpBacktestResultTimestamp
133
+ });
@@ -35,7 +35,6 @@ __export(mcpReplayProcess_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(mcpReplayProcess_exports);
37
37
  var import_node_path = __toESM(require("path"));
38
- var import_node_crypto = require("crypto");
39
38
  var import_promises = __toESM(require("fs/promises"));
40
39
 
41
40
  // src/lib/runtimeEvidenceArtifacts.ts
@@ -81,6 +80,7 @@ const timer = setInterval(() => {
81
80
  `;
82
81
  var buildMcpReplayProcess = async ({
83
82
  evidencePath,
83
+ expectedEvidenceSha256,
84
84
  outDir,
85
85
  runId,
86
86
  projectRoot,
@@ -91,6 +91,9 @@ var buildMcpReplayProcess = async ({
91
91
  const bundle = await (0, runtimeEvidenceArtifacts_exports.verifyRuntimeEvidenceBundle)(import_node_path.default.dirname(evidencePath));
92
92
  if (import_node_path.default.resolve(evidencePath) !== import_node_path.default.resolve(bundle.payloadPath))
93
93
  throw new Error("Replay requires the verified bundle payload");
94
+ const payloadSha256 = bundle.manifest.payload.sha256;
95
+ if (expectedEvidenceSha256 && expectedEvidenceSha256 !== payloadSha256)
96
+ throw new Error("Runtime evidence changed since job submission");
94
97
  const relative = import_node_path.default.relative(import_node_path.default.join(projectRoot, "data"), evidencePath);
95
98
  if (relative.startsWith("..") || import_node_path.default.isAbsolute(relative))
96
99
  throw new Error("Evidence must be inside Project data");
@@ -100,14 +103,13 @@ var buildMcpReplayProcess = async ({
100
103
  );
101
104
  if (heartbeat.protocolVersion !== 2 || !Number.isFinite(heartbeat.observedAt) || Date.now() - heartbeat.observedAt < 0 || Date.now() - heartbeat.observedAt > 12e4)
102
105
  throw new Error("Host replay runner heartbeat is stale");
103
- const bytes = await import_promises.default.readFile(evidencePath);
104
106
  const request = JSON.stringify({
105
107
  schemaVersion: 1,
106
108
  id: runId,
107
109
  evidence: relative,
108
110
  artifactId: bundle.manifest.artifactId,
109
111
  deploymentId: bundle.manifest.deploymentId,
110
- payloadSha256: (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex")
112
+ payloadSha256
111
113
  });
112
114
  void outDir;
113
115
  return {
@@ -5779,8 +5779,14 @@ var finishBacktest = async (testSuite) => {
5779
5779
  console.log(import_chalk10.default.gray(`full report: ${markdownReportPath}`));
5780
5780
  console.log("");
5781
5781
  if (!isFastMode || flags.persistResults) {
5782
- await (0, import_redis6.setData)(
5783
- import_redis6.redisKeys.backtestResults(userName, flags.config, timestamp),
5782
+ const mcpTimestamp = process.env.MCP_WORKER === "true" ? process.env.MCP_BACKTEST_RESULT_TIMESTAMP : void 0;
5783
+ const saveResult = mcpTimestamp ? import_redis6.setDataStrict : import_redis6.setData;
5784
+ await saveResult(
5785
+ import_redis6.redisKeys.backtestResults(
5786
+ userName,
5787
+ flags.config,
5788
+ mcpTimestamp ?? timestamp
5789
+ ),
5784
5790
  {
5785
5791
  config: flags.config,
5786
5792
  mode: "config",
@@ -1578,13 +1578,16 @@ async function fetchTarget(userName, request, target, loaded) {
1578
1578
  const interval = dataset.interval;
1579
1579
  const missing = (time) => time >= day && time < end && !loaded.has(time);
1580
1580
  if (dataset.provider === "CoinMarketCap") {
1581
- await backfillCoinMarketCapContext({
1581
+ const params = {
1582
1582
  userName,
1583
1583
  startMs: day,
1584
1584
  preloadStartMs: day,
1585
1585
  endMs: end,
1586
1586
  repairDataset: dataset.id
1587
- });
1587
+ };
1588
+ if (resolveCoinMarketCapBackfillWindow(params).fromMs > day)
1589
+ return "outside_configured_history";
1590
+ await backfillCoinMarketCapContext(params);
1588
1591
  return;
1589
1592
  }
1590
1593
  if (dataset.provider === "Coinalyze") {
@@ -1593,7 +1596,7 @@ async function fetchTarget(userName, request, target, loaded) {
1593
1596
  userName,
1594
1597
  series
1595
1598
  );
1596
- if (!marketSymbol) return;
1599
+ if (!marketSymbol) return "no_matching_market";
1597
1600
  const { derivativesRows = [] } = await import_connectors.marketDataProviders.coinalyze.fetchWindow({
1598
1601
  symbol: series,
1599
1602
  marketSymbol,
@@ -1685,8 +1688,7 @@ async function fetchTarget(userName, request, target, loaded) {
1685
1688
  const universe = (0, import_strategies.getBinanceBreadthUniverses)().find(
1686
1689
  (item) => item.universe === series
1687
1690
  );
1688
- if (!universe)
1689
- throw new Error("The historical Binance basket is unavailable.");
1691
+ if (!universe) return "historical_basket_unavailable";
1690
1692
  const candlesBySymbol = {};
1691
1693
  const warmup = day - 2 * DAY_MS3;
1692
1694
  for (let offset = 0; offset < universe.symbols.length; offset += 4) {
@@ -1715,14 +1717,16 @@ async function main() {
1715
1717
  );
1716
1718
  await (0, import_client3.waitForDbReady)();
1717
1719
  const before = await (0, import_dataCoverage.readDataBackfillSlots)(request, target.end);
1718
- if (before.loaded.size < before.expected)
1719
- await fetchTarget(userName, request, target, before.loaded);
1720
+ const outcome = before.loaded.size < before.expected ? await fetchTarget(userName, request, target, before.loaded) : void 0;
1720
1721
  const after = await (0, import_dataCoverage.readDataBackfillSlots)(request, target.end);
1721
1722
  const result = {
1722
1723
  loaded: after.loaded.size,
1723
1724
  added: Math.max(0, after.loaded.size - before.loaded.size),
1724
1725
  missing: Math.max(0, after.expected - after.loaded.size)
1725
1726
  };
1727
+ if (result.missing) {
1728
+ result.outcome = outcome ?? (result.added ? "some_missing_slots_filled" : "no_missing_slots_filled");
1729
+ }
1726
1730
  console.log(`TRADEJS_DATA_BACKFILL ${JSON.stringify(result)}`);
1727
1731
  }
1728
1732
  // Annotate the CommonJS export names for ESM import in node:
@@ -35,14 +35,13 @@ __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_crypto2 = require("crypto");
39
- var import_promises2 = require("fs/promises");
40
- var import_redis = require("@tradejs/infra/redis");
41
- var import_mcp = require("@tradejs/node/mcp");
38
+ var import_node_crypto = require("crypto");
39
+ var import_promises3 = require("fs/promises");
40
+ var import_redis2 = require("@tradejs/infra/redis");
41
+ var import_mcp2 = require("@tradejs/node/mcp");
42
42
 
43
43
  // src/lib/mcpReplayProcess.ts
44
44
  var import_node_path = __toESM(require("path"));
45
- var import_node_crypto = require("crypto");
46
45
  var import_promises = __toESM(require("fs/promises"));
47
46
 
48
47
  // src/lib/runtimeEvidenceArtifacts.ts
@@ -88,6 +87,7 @@ const timer = setInterval(() => {
88
87
  `;
89
88
  var buildMcpReplayProcess = async ({
90
89
  evidencePath,
90
+ expectedEvidenceSha256,
91
91
  outDir,
92
92
  runId,
93
93
  projectRoot,
@@ -98,6 +98,9 @@ var buildMcpReplayProcess = async ({
98
98
  const bundle = await (0, runtimeEvidenceArtifacts_exports.verifyRuntimeEvidenceBundle)(import_node_path.default.dirname(evidencePath));
99
99
  if (import_node_path.default.resolve(evidencePath) !== import_node_path.default.resolve(bundle.payloadPath))
100
100
  throw new Error("Replay requires the verified bundle payload");
101
+ const payloadSha256 = bundle.manifest.payload.sha256;
102
+ if (expectedEvidenceSha256 && expectedEvidenceSha256 !== payloadSha256)
103
+ throw new Error("Runtime evidence changed since job submission");
101
104
  const relative = import_node_path.default.relative(import_node_path.default.join(projectRoot, "data"), evidencePath);
102
105
  if (relative.startsWith("..") || import_node_path.default.isAbsolute(relative))
103
106
  throw new Error("Evidence must be inside Project data");
@@ -107,14 +110,13 @@ var buildMcpReplayProcess = async ({
107
110
  );
108
111
  if (heartbeat.protocolVersion !== 2 || !Number.isFinite(heartbeat.observedAt) || Date.now() - heartbeat.observedAt < 0 || Date.now() - heartbeat.observedAt > 12e4)
109
112
  throw new Error("Host replay runner heartbeat is stale");
110
- const bytes = await import_promises.default.readFile(evidencePath);
111
113
  const request = JSON.stringify({
112
114
  schemaVersion: 1,
113
115
  id: runId,
114
116
  evidence: relative,
115
117
  artifactId: bundle.manifest.artifactId,
116
118
  deploymentId: bundle.manifest.deploymentId,
117
- payloadSha256: (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex")
119
+ payloadSha256
118
120
  });
119
121
  void outDir;
120
122
  return {
@@ -123,14 +125,108 @@ var buildMcpReplayProcess = async ({
123
125
  };
124
126
  };
125
127
 
128
+ // src/lib/mcpJobOutputs.ts
129
+ var import_promises2 = require("fs/promises");
130
+ var import_node_path2 = __toESM(require("path"));
131
+ var import_redis = require("@tradejs/infra/redis");
132
+ var import_diagnostics = require("@tradejs/node/diagnostics");
133
+ var import_mcp = require("@tradejs/node/mcp");
134
+
135
+ // src/lib/runFormatting.ts
136
+ var import_date_fns = require("date-fns");
137
+ var ListIt = require("list-it");
138
+ var createTimestamp = (date) => (0, import_date_fns.format)(date, "yyyyMMddHHmm");
139
+
140
+ // src/lib/mcpJobOutputs.ts
141
+ var mcpBacktestResultTimestamp = (job) => createTimestamp(new Date(job.createdAt));
142
+ var collectEvidence = async (job) => Promise.all(
143
+ (await (0, import_diagnostics.discoverRuntimeEvidenceBundles)((0, import_mcp.mcpJobDirectory)(job))).map(
144
+ async (bundleDir) => {
145
+ const { manifest } = await (0, import_diagnostics.verifyRuntimeEvidenceBundle)(bundleDir);
146
+ if (manifest.userName !== job.userName || manifest.deploymentId !== job.request.deploymentId || manifest.window.startTime !== job.request.startTime || manifest.window.endTime !== job.request.endTime)
147
+ throw new Error("Runtime evidence does not match the job");
148
+ return {
149
+ bundleDir,
150
+ id: (0, import_mcp.mcpId)(`${manifest.artifactId}:${manifest.payload.sha256}`)
151
+ };
152
+ }
153
+ )
154
+ );
155
+ var collectReplay = async (job) => {
156
+ const sourceDir = import_node_path2.default.dirname(String(job.request.verifiedEvidencePath));
157
+ const source = await (0, import_diagnostics.verifyRuntimeEvidenceBundle)(sourceDir);
158
+ const { artifactId, deploymentId, userName, window } = source.manifest;
159
+ if (userName !== job.userName || deploymentId !== job.request.deploymentId || job.request.verifiedEvidenceSha256 !== void 0 && job.request.verifiedEvidenceSha256 !== source.manifest.payload.sha256 || !/^[A-Za-z0-9._-]+$/.test(artifactId) || !/^[A-Za-z0-9._-]+$/.test(deploymentId) || [".", ".."].includes(artifactId) || [".", ".."].includes(deploymentId))
160
+ throw new Error("Runtime evidence does not match the replay job");
161
+ const root = import_node_path2.default.join((0, import_mcp.mcpProjectRoot)(), "data/runtime-feedback");
162
+ const receipt = JSON.parse(
163
+ await (0, import_promises2.readFile)(
164
+ import_node_path2.default.join(root, "completed", deploymentId, `${artifactId}.json`),
165
+ "utf8"
166
+ )
167
+ );
168
+ if (!(0, import_mcp.isMcpRecord)(receipt) || receipt.schemaVersion !== 1 || receipt.runtimeEvidenceArtifactId !== artifactId || typeof receipt.runtimeFeedbackBundle !== "string")
169
+ throw new Error("Invalid replay completion receipt");
170
+ const runId = import_node_path2.default.basename(receipt.runtimeFeedbackBundle);
171
+ if (!/^[A-Za-z0-9_-]+$/.test(runId))
172
+ throw new Error("Invalid replay bundle name");
173
+ const day = new Date(window.endTime).toISOString().slice(0, 10).split("-");
174
+ const bundleDir = import_node_path2.default.join(
175
+ root,
176
+ "ready",
177
+ deploymentId,
178
+ ...day,
179
+ artifactId,
180
+ runId
181
+ );
182
+ const { feedback } = await (0, import_diagnostics.verifyRuntimeFeedbackReplaySource)({
183
+ bundleDir,
184
+ runtimeEvidenceBundleDir: sourceDir
185
+ });
186
+ const { manifest } = feedback;
187
+ return [
188
+ {
189
+ bundleDir,
190
+ id: (0, import_mcp.mcpId)(
191
+ `${manifest.artifactId}:${manifest.payloads.replayEvidence.sha256}`
192
+ )
193
+ }
194
+ ];
195
+ };
196
+ var collectMcpJobOutputs = async (job) => {
197
+ if (job.kind === "backtest") {
198
+ const config = job.request.frozenConfigId;
199
+ if (typeof config !== "string")
200
+ throw new Error("Invalid frozen backtest config");
201
+ const timestamp = mcpBacktestResultTimestamp(job);
202
+ if (!await (0, import_redis.getDataStrict)(
203
+ import_redis.redisKeys.backtestResults(job.userName, config, timestamp)
204
+ ))
205
+ throw new Error("Backtest result was not saved");
206
+ return { resultId: `${config}:${timestamp}` };
207
+ }
208
+ const bundles = job.kind === "runtime-evidence" ? await collectEvidence(job) : await collectReplay(job);
209
+ if (!bundles.length)
210
+ throw new Error("No verified diagnostic report was saved");
211
+ for (const { id, bundleDir } of bundles) {
212
+ if (!await import_redis.mcpStorage.put(
213
+ (0, import_mcp.mcpArtifactLocationId)(job.userName, id),
214
+ bundleDir,
215
+ import_mcp.MCP_JOB_TTL
216
+ ))
217
+ throw new Error("Diagnostic report location could not be saved");
218
+ }
219
+ return { artifactIds: bundles.map(({ id }) => id) };
220
+ };
221
+
126
222
  // src/scripts/mcpWorker.ts
127
223
  var pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
128
224
  var leaseKey = "worker:lease";
129
225
  var LEASE_SECONDS = 15;
130
226
  var JOB_TIMEOUT_MS = 60 * 60 * 1e3;
131
227
  var main = async (options = {}) => {
132
- const owner = (0, import_node_crypto2.randomUUID)();
133
- if (!await import_redis.mcpStorage.put(leaseKey, owner, LEASE_SECONDS, true))
228
+ const owner = (0, import_node_crypto.randomUUID)();
229
+ if (!await import_redis2.mcpStorage.put(leaseKey, owner, LEASE_SECONDS, true))
134
230
  throw new Error("Another MCP worker owns this storage");
135
231
  let stopping = false;
136
232
  let child;
@@ -145,7 +241,7 @@ var main = async (options = {}) => {
145
241
  try {
146
242
  const root = process.env.MCP_REPLAY_REQUEST_ROOT || `${process.env.PROJECT_CWD || process.cwd()}/data/mcp-replay-requests`;
147
243
  const heartbeat2 = JSON.parse(
148
- await (0, import_promises2.readFile)(`${root}/heartbeat.json`, "utf8")
244
+ await (0, import_promises3.readFile)(`${root}/heartbeat.json`, "utf8")
149
245
  );
150
246
  replayReady = heartbeat2.protocolVersion === 2 && Number.isFinite(heartbeat2.observedAt) && Date.now() - heartbeat2.observedAt >= 0 && Date.now() - heartbeat2.observedAt < 12e4;
151
247
  replayReadiness = replayReady && (heartbeat2.readiness === "ready" || heartbeat2.readiness === "waiting_resources") ? heartbeat2.readiness : "unknown";
@@ -194,10 +290,10 @@ var main = async (options = {}) => {
194
290
  if (renewing || leaseLost) return;
195
291
  renewing = true;
196
292
  try {
197
- if (!await import_redis.mcpStorage.compare(leaseKey, owner, owner, LEASE_SECONDS))
293
+ if (!await import_redis2.mcpStorage.compare(leaseKey, owner, owner, LEASE_SECONDS))
198
294
  throw new Error("Worker lease lost");
199
295
  await updateReplayReady();
200
- await import_redis.mcpStorage.put("worker:heartbeat", heartbeatValue(), LEASE_SECONDS);
296
+ await import_redis2.mcpStorage.put("worker:heartbeat", heartbeatValue(), LEASE_SECONDS);
201
297
  } catch {
202
298
  leaseLost = true;
203
299
  stop();
@@ -206,17 +302,17 @@ var main = async (options = {}) => {
206
302
  }
207
303
  }, 2e3);
208
304
  try {
209
- manifestHash = await (0, import_mcp.mcpPackageManifestHash)();
305
+ manifestHash = await (0, import_mcp2.mcpPackageManifestHash)();
210
306
  await updateReplayReady();
211
- await import_redis.mcpStorage.put("worker:heartbeat", heartbeatValue(), LEASE_SECONDS);
307
+ await import_redis2.mcpStorage.put("worker:heartbeat", heartbeatValue(), LEASE_SECONDS);
212
308
  let cursor = "0";
213
309
  do {
214
- const page = await import_redis.mcpStorage.scan("jobs:", cursor);
310
+ const page = await import_redis2.mcpStorage.scan("jobs:", cursor);
215
311
  cursor = page.cursor;
216
312
  for (const key of page.ids) {
217
- const job = await import_redis.mcpStorage.get(key);
313
+ const job = await import_redis2.mcpStorage.get(key);
218
314
  if (job?.status === "running")
219
- await import_redis.mcpStorage.compare(
315
+ await import_redis2.mcpStorage.compare(
220
316
  key,
221
317
  job,
222
318
  {
@@ -226,7 +322,7 @@ var main = async (options = {}) => {
226
322
  finishedAt: Date.now(),
227
323
  updatedAt: Date.now()
228
324
  },
229
- import_mcp.MCP_JOB_TTL
325
+ import_mcp2.MCP_JOB_TTL
230
326
  );
231
327
  }
232
328
  } while (cursor !== "0" && !stopping);
@@ -237,10 +333,10 @@ var main = async (options = {}) => {
237
333
  let queued;
238
334
  cursor = "0";
239
335
  do {
240
- const page = await import_redis.mcpStorage.scan("jobs:", cursor);
336
+ const page = await import_redis2.mcpStorage.scan("jobs:", cursor);
241
337
  cursor = page.cursor;
242
338
  for (const key of page.ids) {
243
- const job = await import_redis.mcpStorage.get(key);
339
+ const job = await import_redis2.mcpStorage.get(key);
244
340
  if (job && ["queued", "waiting_resources", "waiting_executor"].includes(
245
341
  job.status
246
342
  ) && (!queued || job.createdAt < queued.createdAt))
@@ -251,17 +347,17 @@ var main = async (options = {}) => {
251
347
  await pause(1e3);
252
348
  continue;
253
349
  }
254
- const jobKey = (0, import_mcp.mcpJobStorageId)(queued.userName, queued.id);
350
+ const jobKey = (0, import_mcp2.mcpJobStorageId)(queued.userName, queued.id);
255
351
  const started = {
256
352
  ...queued,
257
353
  status: queued.kind === "runtime-feedback-replay" ? "waiting_executor" : "running",
258
354
  ...queued.kind === "runtime-feedback-replay" ? {} : { startedAt: Date.now() },
259
355
  updatedAt: Date.now()
260
356
  };
261
- if (!await import_redis.mcpStorage.compare(jobKey, queued, started, import_mcp.MCP_JOB_TTL))
357
+ if (!await import_redis2.mcpStorage.compare(jobKey, queued, started, import_mcp2.MCP_JOB_TTL))
262
358
  continue;
263
359
  if (started.kind !== "runtime-feedback-replay" && started.packageManifestSha256 !== manifestHash) {
264
- await import_redis.mcpStorage.compare(
360
+ await import_redis2.mcpStorage.compare(
265
361
  jobKey,
266
362
  started,
267
363
  {
@@ -270,19 +366,19 @@ var main = async (options = {}) => {
270
366
  error: "Package composition changed since submission",
271
367
  finishedAt: Date.now()
272
368
  },
273
- import_mcp.MCP_JOB_TTL
369
+ import_mcp2.MCP_JOB_TTL
274
370
  );
275
371
  continue;
276
372
  }
277
- await (0, import_promises2.mkdir)((0, import_mcp.mcpJobDirectory)(started), { recursive: true });
373
+ await (0, import_promises3.mkdir)((0, import_mcp2.mcpJobDirectory)(started), { recursive: true });
278
374
  if (started.kind === "backtest")
279
- await (0, import_redis.setDataStrict)(
280
- import_redis.redisKeys.backtestConfig(
375
+ await (0, import_redis2.setDataStrict)(
376
+ import_redis2.redisKeys.backtestConfig(
281
377
  started.userName,
282
378
  String(started.request.frozenConfigId)
283
379
  ),
284
380
  started.request.resolvedConfig,
285
- { expire: import_mcp.MCP_JOB_TTL }
381
+ { expire: import_mcp2.MCP_JOB_TTL }
286
382
  );
287
383
  const jobEnv = {
288
384
  ...process.env,
@@ -295,6 +391,8 @@ var main = async (options = {}) => {
295
391
  MAKE_ORDERS: "false",
296
392
  TRADEJS_EXTERNAL_ORDER_PLACEMENT: "false"
297
393
  };
394
+ if (started.kind === "backtest")
395
+ jobEnv.MCP_BACKTEST_RESULT_TIMESTAMP = mcpBacktestResultTimestamp(started);
298
396
  if (started.kind === "runtime-feedback-replay") {
299
397
  for (const name of Object.keys(jobEnv)) {
300
398
  if (/secret|password|token|api.?key|private.?key|cookie|credential/i.test(
@@ -320,13 +418,14 @@ var main = async (options = {}) => {
320
418
  if (started.kind === "runtime-feedback-replay")
321
419
  command = await buildMcpReplayProcess({
322
420
  evidencePath: String(started.request.verifiedEvidencePath),
323
- outDir: (0, import_mcp.mcpJobDirectory)(started),
421
+ expectedEvidenceSha256: typeof started.request.verifiedEvidenceSha256 === "string" ? started.request.verifiedEvidenceSha256 : void 0,
422
+ outDir: (0, import_mcp2.mcpJobDirectory)(started),
324
423
  runId: started.id,
325
424
  projectRoot: process.env.PROJECT_CWD || process.cwd(),
326
425
  env: process.env
327
426
  });
328
427
  } catch (error) {
329
- await import_redis.mcpStorage.compare(
428
+ await import_redis2.mcpStorage.compare(
330
429
  jobKey,
331
430
  started,
332
431
  {
@@ -334,9 +433,9 @@ var main = async (options = {}) => {
334
433
  status: "failed",
335
434
  finishedAt: Date.now(),
336
435
  updatedAt: Date.now(),
337
- error: error instanceof Error ? (0, import_mcp.redactMcpJobLog)(error.message) : "Replay preparation failed"
436
+ error: error instanceof Error ? (0, import_mcp2.redactMcpJobLog)(error.message) : "Replay preparation failed"
338
437
  },
339
- import_mcp.MCP_JOB_TTL
438
+ import_mcp2.MCP_JOB_TTL
340
439
  );
341
440
  continue;
342
441
  }
@@ -356,7 +455,7 @@ var main = async (options = {}) => {
356
455
  const logs = [];
357
456
  const log = (chunk) => {
358
457
  const lines = chunk.toString().replace(/\u001b\[[0-9;]*m/g, "").split(/[\r\n]+/).filter(Boolean);
359
- logs.push(...lines.map(import_mcp.redactMcpJobLog));
458
+ logs.push(...lines.map(import_mcp2.redactMcpJobLog));
360
459
  if (logs.length > 150) logs.splice(0, logs.length - 150);
361
460
  };
362
461
  currentChild.stdout?.on("data", log);
@@ -364,7 +463,7 @@ var main = async (options = {}) => {
364
463
  let killAt;
365
464
  const monitor = setInterval(async () => {
366
465
  try {
367
- const latest = await import_redis.mcpStorage.get(jobKey);
466
+ const latest = await import_redis2.mcpStorage.get(jobKey);
368
467
  if (!latest || latest.cancelRequested || stopping || timedOut) {
369
468
  killAt ??= Date.now();
370
469
  signalChild(Date.now() - killAt > 8e3 ? "SIGKILL" : "SIGTERM");
@@ -374,7 +473,7 @@ var main = async (options = {}) => {
374
473
  try {
375
474
  const root = process.env.MCP_REPLAY_REQUEST_ROOT || `${process.env.PROJECT_CWD || process.cwd()}/data/mcp-replay-requests`;
376
475
  const state = JSON.parse(
377
- await (0, import_promises2.readFile)(`${root}/${latest.id}.state`, "utf8")
476
+ await (0, import_promises3.readFile)(`${root}/${latest.id}.state`, "utf8")
378
477
  );
379
478
  if (state && typeof state === "object" && "status" in state && (state.status === "waiting_resources" || state.status === "waiting_executor" || state.status === "running"))
380
479
  replayStatus = state.status;
@@ -389,7 +488,7 @@ var main = async (options = {}) => {
389
488
  }
390
489
  }
391
490
  if (latest && !leaseLost)
392
- await import_redis.mcpStorage.compare(
491
+ await import_redis2.mcpStorage.compare(
393
492
  jobKey,
394
493
  latest,
395
494
  {
@@ -401,7 +500,7 @@ var main = async (options = {}) => {
401
500
  logs: [...logs],
402
501
  updatedAt: Date.now()
403
502
  },
404
- import_mcp.MCP_JOB_TTL
503
+ import_mcp2.MCP_JOB_TTL
405
504
  );
406
505
  } catch {
407
506
  leaseLost = true;
@@ -418,25 +517,34 @@ var main = async (options = {}) => {
418
517
  clearTimeout(timeout);
419
518
  clearInterval(monitor);
420
519
  child = void 0;
520
+ let outputs = {};
521
+ if (!leaseLost && outcome.code === 0 && !outcome.error && !timedOut && !stopping) {
522
+ try {
523
+ outputs = await collectMcpJobOutputs(started);
524
+ } catch {
525
+ outcome.error = "Job finished but its saved output could not be verified";
526
+ }
527
+ }
421
528
  if (!leaseLost) {
422
529
  for (let attempt = 0; attempt < 4; attempt++) {
423
- const latest = await import_redis.mcpStorage.get(jobKey);
530
+ const latest = await import_redis2.mcpStorage.get(jobKey);
424
531
  if (!latest) break;
425
532
  const cancelled = latest.cancelRequested;
426
533
  const next = {
427
534
  ...latest,
428
535
  logs,
429
- status: cancelled ? "cancelled" : outcome.code === 0 && !timedOut && !stopping ? "completed" : "failed",
536
+ status: cancelled ? "cancelled" : outcome.code === 0 && !outcome.error && !timedOut && !stopping ? "completed" : "failed",
537
+ ...cancelled || stopping ? {} : outputs,
430
538
  exitCode: outcome.code,
431
539
  updatedAt: Date.now(),
432
540
  finishedAt: Date.now(),
433
- ...cancelled ? {} : timedOut ? { error: "Job exceeded one hour" } : stopping ? { error: "Worker stopped; this job was not replayed" } : outcome.code !== 0 ? {
541
+ ...cancelled ? {} : timedOut ? { error: "Job exceeded one hour" } : stopping ? { error: "Worker stopped; this job was not replayed" } : outcome.code !== 0 || outcome.error ? {
434
542
  error: outcome.error || (started.kind === "runtime-feedback-replay" ? logs.find(
435
543
  (line) => line.startsWith("Host replay: ")
436
544
  ) || logs.at(-1) : void 0) || "Research process failed"
437
545
  } : {}
438
546
  };
439
- if (await import_redis.mcpStorage.compare(jobKey, latest, next, import_mcp.MCP_JOB_TTL))
547
+ if (await import_redis2.mcpStorage.compare(jobKey, latest, next, import_mcp2.MCP_JOB_TTL))
440
548
  break;
441
549
  }
442
550
  }
@@ -446,8 +554,8 @@ var main = async (options = {}) => {
446
554
  options.signal?.removeEventListener("abort", stop);
447
555
  process.removeListener("SIGINT", stop);
448
556
  process.removeListener("SIGTERM", stop);
449
- if (await import_redis.mcpStorage.release(leaseKey, owner))
450
- await import_redis.mcpStorage.remove("worker:heartbeat");
557
+ if (await import_redis2.mcpStorage.release(leaseKey, owner))
558
+ await import_redis2.mcpStorage.remove("worker:heartbeat");
451
559
  }
452
560
  if (leaseLost)
453
561
  throw new Error("Worker lease lost; active computation was stopped");