@zltquant/zlt-mcp 0.6.1-beta.0 → 0.6.1-beta.2

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/config.json CHANGED
@@ -1143,22 +1143,21 @@
1143
1143
  },
1144
1144
  {
1145
1145
  "name": "mcp-wait",
1146
- "description": "阻塞等待一个异步任务结束,直到需要 agent 行动的状态才返回。kind=install 等 runtime 安装,id 传 zlt-runtime-install 返回的 taskId;kind=backtest 等回测,id 传 mcp-create-and-run-backtest / mcp-re-backtest 返回的 runId;kind=download 等数据下载,id 传 mcp-download-data 返回的 uuid(传 \"0\" 等本机当前最新下载任务)。拿到任务 id 后应立即调用本工具,等待期间不要做任何其他事、不要轮询状态。\n\nkind=install 返回安装阶段和终态;completed 后再调用 zlt-runtime-doctor。\n\nkind=backtest 返回与 mcp-get-backtest-state 相同的终态结果(status 2 失败/3 取消/4 终止/5 完成;status=5 才能查收益)。\n\nkind=download 返回与 mcp-get-download-progress 相同的结果,按其中 nextAction 行动。除下载中以外的任何状态都会立即返回:resume-required / await-credential / report-stopped 表示任务不会自己结束,需要用户决定。\n\n任一 kind 带 waitStopped=no-progress 表示约 3 分钟毫无进展、先交回现状(回测可能仍在准备数据);可用对应状态工具复查一次,再调用本工具继续等。",
1146
+ "description": "阻塞等待一个异步任务结束,直到需要 agent 行动的状态才返回。kind=backtest 等回测,id 传 mcp-create-and-run-backtest / mcp-re-backtest 返回的 runId;kind=download 等数据下载,id 传 mcp-download-data 返回的 uuid(传 \"0\" 等本机当前最新下载任务)。拿到任务 id 后应立即调用本工具,等待期间不要做任何其他事、不要轮询状态。\n\nkind=backtest 返回与 mcp-get-backtest-state 相同的终态结果(status 2 失败/3 取消/4 终止/5 完成;status=5 才能查收益)。\n\nkind=download 返回与 mcp-get-download-progress 相同的结果,按其中 nextAction 行动。除下载中以外的任何状态都会立即返回:resume-required / await-credential / report-stopped 表示任务不会自己结束,需要用户决定。\n\n任一 kind 带 waitStopped=no-progress 表示约 3 分钟毫无进展、先交回现状(回测可能仍在准备数据);可用对应状态工具复查一次,再调用本工具继续等。",
1147
1147
  "inputSchema": {
1148
1148
  "type": "object",
1149
1149
  "properties": {
1150
1150
  "kind": {
1151
1151
  "type": "string",
1152
1152
  "enum": [
1153
- "install",
1154
1153
  "backtest",
1155
1154
  "download"
1156
1155
  ],
1157
- "description": "任务类别:install runtime 安装,backtest 回测,download 数据下载。"
1156
+ "description": "任务类别:backtest 回测,download 数据下载。"
1158
1157
  },
1159
1158
  "id": {
1160
1159
  "type": "string",
1161
- "description": "任务标识:kind=install 传 zlt-runtime-install 返回的 taskId;kind=backtest 传 runId;kind=download 传 uuid(\"0\" 表示本机当前最新下载任务)。"
1160
+ "description": "任务标识:kind=backtest 传 runId;kind=download 传 uuid(\"0\" 表示本机当前最新下载任务)。"
1162
1161
  },
1163
1162
  "codePath": {
1164
1163
  "type": "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zltquant/zlt-mcp",
3
- "version": "0.6.1-beta.0",
3
+ "version": "0.6.1-beta.2",
4
4
  "description": "智量通 ZLT Quant MCP server(Windows x64;量化策略创建与本地回测)",
5
5
  "bin": {
6
6
  "zlt-mcp": "./zlt-mcp-proxy.bundle.cjs"
@@ -6274,7 +6274,7 @@ var require_compile = __commonJS({
6274
6274
  const schOrFunc = root.refs[ref];
6275
6275
  if (schOrFunc)
6276
6276
  return schOrFunc;
6277
- let _sch = resolve8.call(this, root, ref);
6277
+ let _sch = resolve9.call(this, root, ref);
6278
6278
  if (_sch === void 0) {
6279
6279
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
6280
6280
  const { schemaId } = this.opts;
@@ -6301,7 +6301,7 @@ var require_compile = __commonJS({
6301
6301
  function sameSchemaEnv(s1, s2) {
6302
6302
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
6303
6303
  }
6304
- function resolve8(root, ref) {
6304
+ function resolve9(root, ref) {
6305
6305
  let sch;
6306
6306
  while (typeof (sch = this.refs[ref]) == "string")
6307
6307
  ref = sch;
@@ -6876,7 +6876,7 @@ var require_fast_uri = __commonJS({
6876
6876
  }
6877
6877
  return uri;
6878
6878
  }
6879
- function resolve8(baseURI, relativeURI, options) {
6879
+ function resolve9(baseURI, relativeURI, options) {
6880
6880
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
6881
6881
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
6882
6882
  schemelessOptions.skipEscape = true;
@@ -7103,7 +7103,7 @@ var require_fast_uri = __commonJS({
7103
7103
  var fastUri = {
7104
7104
  SCHEMES,
7105
7105
  normalize: normalize2,
7106
- resolve: resolve8,
7106
+ resolve: resolve9,
7107
7107
  resolveComponent,
7108
7108
  equal,
7109
7109
  serialize,
@@ -10186,12 +10186,12 @@ var require_isexe = __commonJS({
10186
10186
  if (typeof Promise !== "function") {
10187
10187
  throw new TypeError("callback not provided");
10188
10188
  }
10189
- return new Promise(function(resolve8, reject) {
10189
+ return new Promise(function(resolve9, reject) {
10190
10190
  isexe(path17, options || {}, function(er, is) {
10191
10191
  if (er) {
10192
10192
  reject(er);
10193
10193
  } else {
10194
- resolve8(is);
10194
+ resolve9(is);
10195
10195
  }
10196
10196
  });
10197
10197
  });
@@ -10257,27 +10257,27 @@ var require_which = __commonJS({
10257
10257
  opt = {};
10258
10258
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
10259
10259
  const found = [];
10260
- const step = (i) => new Promise((resolve8, reject) => {
10260
+ const step = (i) => new Promise((resolve9, reject) => {
10261
10261
  if (i === pathEnv.length)
10262
- return opt.all && found.length ? resolve8(found) : reject(getNotFoundError(cmd));
10262
+ return opt.all && found.length ? resolve9(found) : reject(getNotFoundError(cmd));
10263
10263
  const ppRaw = pathEnv[i];
10264
10264
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
10265
10265
  const pCmd = path17.join(pathPart, cmd);
10266
10266
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
10267
- resolve8(subStep(p, i, 0));
10267
+ resolve9(subStep(p, i, 0));
10268
10268
  });
10269
- const subStep = (p, i, ii) => new Promise((resolve8, reject) => {
10269
+ const subStep = (p, i, ii) => new Promise((resolve9, reject) => {
10270
10270
  if (ii === pathExt.length)
10271
- return resolve8(step(i + 1));
10271
+ return resolve9(step(i + 1));
10272
10272
  const ext = pathExt[ii];
10273
10273
  isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
10274
10274
  if (!er && is) {
10275
10275
  if (opt.all)
10276
10276
  found.push(p + ext);
10277
10277
  else
10278
- return resolve8(p + ext);
10278
+ return resolve9(p + ext);
10279
10279
  }
10280
- return resolve8(subStep(p, i, ii + 1));
10280
+ return resolve9(subStep(p, i, ii + 1));
10281
10281
  });
10282
10282
  });
10283
10283
  return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
@@ -16457,7 +16457,7 @@ var Protocol = class {
16457
16457
  return;
16458
16458
  }
16459
16459
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
16460
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
16460
+ await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
16461
16461
  options?.signal?.throwIfAborted();
16462
16462
  }
16463
16463
  } catch (error2) {
@@ -16474,7 +16474,7 @@ var Protocol = class {
16474
16474
  */
16475
16475
  request(request, resultSchema, options) {
16476
16476
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
16477
- return new Promise((resolve8, reject) => {
16477
+ return new Promise((resolve9, reject) => {
16478
16478
  const earlyReject = (error2) => {
16479
16479
  reject(error2);
16480
16480
  };
@@ -16552,7 +16552,7 @@ var Protocol = class {
16552
16552
  if (!parseResult.success) {
16553
16553
  reject(parseResult.error);
16554
16554
  } else {
16555
- resolve8(parseResult.data);
16555
+ resolve9(parseResult.data);
16556
16556
  }
16557
16557
  } catch (error2) {
16558
16558
  reject(error2);
@@ -16813,12 +16813,12 @@ var Protocol = class {
16813
16813
  }
16814
16814
  } catch {
16815
16815
  }
16816
- return new Promise((resolve8, reject) => {
16816
+ return new Promise((resolve9, reject) => {
16817
16817
  if (signal.aborted) {
16818
16818
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
16819
16819
  return;
16820
16820
  }
16821
- const timeoutId = setTimeout(resolve8, interval);
16821
+ const timeoutId = setTimeout(resolve9, interval);
16822
16822
  signal.addEventListener("abort", () => {
16823
16823
  clearTimeout(timeoutId);
16824
16824
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -17769,7 +17769,7 @@ var StdioClientTransport = class {
17769
17769
  if (this._process) {
17770
17770
  throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
17771
17771
  }
17772
- return new Promise((resolve8, reject) => {
17772
+ return new Promise((resolve9, reject) => {
17773
17773
  this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
17774
17774
  // merge default env with server env because mcp server needs some env vars
17775
17775
  env: {
@@ -17786,7 +17786,7 @@ var StdioClientTransport = class {
17786
17786
  this.onerror?.(error2);
17787
17787
  });
17788
17788
  this._process.on("spawn", () => {
17789
- resolve8();
17789
+ resolve9();
17790
17790
  });
17791
17791
  this._process.on("close", (_code) => {
17792
17792
  this._process = void 0;
@@ -17845,22 +17845,22 @@ var StdioClientTransport = class {
17845
17845
  if (this._process) {
17846
17846
  const processToClose = this._process;
17847
17847
  this._process = void 0;
17848
- const closePromise = new Promise((resolve8) => {
17848
+ const closePromise = new Promise((resolve9) => {
17849
17849
  processToClose.once("close", () => {
17850
- resolve8();
17850
+ resolve9();
17851
17851
  });
17852
17852
  });
17853
17853
  try {
17854
17854
  processToClose.stdin?.end();
17855
17855
  } catch {
17856
17856
  }
17857
- await Promise.race([closePromise, new Promise((resolve8) => setTimeout(resolve8, 2e3).unref())]);
17857
+ await Promise.race([closePromise, new Promise((resolve9) => setTimeout(resolve9, 2e3).unref())]);
17858
17858
  if (processToClose.exitCode === null) {
17859
17859
  try {
17860
17860
  processToClose.kill("SIGTERM");
17861
17861
  } catch {
17862
17862
  }
17863
- await Promise.race([closePromise, new Promise((resolve8) => setTimeout(resolve8, 2e3).unref())]);
17863
+ await Promise.race([closePromise, new Promise((resolve9) => setTimeout(resolve9, 2e3).unref())]);
17864
17864
  }
17865
17865
  if (processToClose.exitCode === null) {
17866
17866
  try {
@@ -17872,15 +17872,15 @@ var StdioClientTransport = class {
17872
17872
  this._readBuffer.clear();
17873
17873
  }
17874
17874
  send(message) {
17875
- return new Promise((resolve8) => {
17875
+ return new Promise((resolve9) => {
17876
17876
  if (!this._process?.stdin) {
17877
17877
  throw new Error("Not connected");
17878
17878
  }
17879
17879
  const json = serializeMessage(message);
17880
17880
  if (this._process.stdin.write(json)) {
17881
- resolve8();
17881
+ resolve9();
17882
17882
  } else {
17883
- this._process.stdin.once("drain", resolve8);
17883
+ this._process.stdin.once("drain", resolve9);
17884
17884
  }
17885
17885
  });
17886
17886
  }
@@ -18418,7 +18418,7 @@ function buildPowerShellRegistryArgs() {
18418
18418
  return ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", READ_REGISTRY_VALUE_SCRIPT];
18419
18419
  }
18420
18420
  function readRegistryValueWithPowerShell(registryKey, valueName) {
18421
- return new Promise((resolve8, reject) => {
18421
+ return new Promise((resolve9, reject) => {
18422
18422
  (0, import_child_process.execFile)(POWERSHELL_EXE, buildPowerShellRegistryArgs(), {
18423
18423
  encoding: "utf-8",
18424
18424
  env: buildPowerShellRegistryEnvironment(registryKey, valueName),
@@ -18427,7 +18427,7 @@ function readRegistryValueWithPowerShell(registryKey, valueName) {
18427
18427
  if (error2)
18428
18428
  reject(error2);
18429
18429
  else
18430
- resolve8(stdout.trim());
18430
+ resolve9(stdout.trim());
18431
18431
  });
18432
18432
  });
18433
18433
  }
@@ -18843,7 +18843,7 @@ var AssistantMasterIpcClient = class {
18843
18843
  this.buffer = Buffer.alloc(0);
18844
18844
  }
18845
18845
  connectMainPipe(pipePath, timeout) {
18846
- return new Promise((resolve8, reject) => {
18846
+ return new Promise((resolve9, reject) => {
18847
18847
  const socket = net.connect(pipePath);
18848
18848
  const timer = setTimeout(() => {
18849
18849
  socket.destroy();
@@ -18852,7 +18852,7 @@ var AssistantMasterIpcClient = class {
18852
18852
  socket.once("connect", () => {
18853
18853
  clearTimeout(timer);
18854
18854
  this.mainSocket = socket;
18855
- resolve8();
18855
+ resolve9();
18856
18856
  });
18857
18857
  socket.once("error", (error2) => {
18858
18858
  clearTimeout(timer);
@@ -18861,7 +18861,7 @@ var AssistantMasterIpcClient = class {
18861
18861
  });
18862
18862
  }
18863
18863
  sendHandshake(timeout) {
18864
- return new Promise((resolve8, reject) => {
18864
+ return new Promise((resolve9, reject) => {
18865
18865
  if (!this.mainSocket) {
18866
18866
  reject(new Error("Assistant master pipe is not connected"));
18867
18867
  return;
@@ -18890,7 +18890,7 @@ var AssistantMasterIpcClient = class {
18890
18890
  reject(new Error(`Assistant master pipe handshake failed: ${buffer.slice(0, 200)}`));
18891
18891
  return;
18892
18892
  }
18893
- resolve8();
18893
+ resolve9();
18894
18894
  };
18895
18895
  this.mainSocket.on("data", onData);
18896
18896
  this.mainSocket.on("error", onError);
@@ -18906,7 +18906,7 @@ var AssistantMasterIpcClient = class {
18906
18906
  });
18907
18907
  }
18908
18908
  connectBusinessPipe(pipePath, timeout) {
18909
- return new Promise((resolve8, reject) => {
18909
+ return new Promise((resolve9, reject) => {
18910
18910
  const socket = net.connect(pipePath);
18911
18911
  const timer = setTimeout(() => {
18912
18912
  socket.destroy();
@@ -18917,7 +18917,7 @@ var AssistantMasterIpcClient = class {
18917
18917
  this.businessSocket = socket;
18918
18918
  socket.on("data", (chunk) => this.onBusinessData(chunk));
18919
18919
  socket.on("close", () => this.rejectAll(new Error("Assistant business pipe closed")));
18920
- resolve8();
18920
+ resolve9();
18921
18921
  });
18922
18922
  socket.once("error", (error2) => {
18923
18923
  clearTimeout(timer);
@@ -18926,7 +18926,7 @@ var AssistantMasterIpcClient = class {
18926
18926
  });
18927
18927
  }
18928
18928
  sendRequest(moduleName, funcName, data, timeout) {
18929
- return new Promise((resolve8, reject) => {
18929
+ return new Promise((resolve9, reject) => {
18930
18930
  if (!this.businessSocket || !this.connected) {
18931
18931
  reject(new Error("Assistant business pipe is not connected"));
18932
18932
  return;
@@ -18949,7 +18949,7 @@ var AssistantMasterIpcClient = class {
18949
18949
  this.pending.delete(seq);
18950
18950
  reject(new Error(`Assistant IPC request timeout: ${moduleName}/${funcName}`));
18951
18951
  }, timeout);
18952
- this.pending.set(seq, { resolve: resolve8, reject, timer });
18952
+ this.pending.set(seq, { resolve: resolve9, reject, timer });
18953
18953
  this.businessSocket.write(request);
18954
18954
  });
18955
18955
  }
@@ -19299,7 +19299,7 @@ function formatModuleInstallError(message, moduleIndex, progress) {
19299
19299
  return `${message}: moduleIndex=${moduleIndex}, status=${progress.status}, phase=${progress.phase}, process=${progress.process}, response=${JSON.stringify(progress.response)}`;
19300
19300
  }
19301
19301
  function sleep(ms) {
19302
- return new Promise((resolve8) => setTimeout(resolve8, ms));
19302
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
19303
19303
  }
19304
19304
 
19305
19305
  // ../zlt-runtime-core/dist/runtime-lifecycle.js
@@ -19343,7 +19343,7 @@ async function acquireRuntimeLease(workRoot, role, options = {}) {
19343
19343
  return handle;
19344
19344
  });
19345
19345
  }
19346
- async function updateRuntimeLeaseActiveJob(handle, runId, active, options = {}) {
19346
+ async function updateRuntimeLeaseActiveJob(handle, runId, active2, options = {}) {
19347
19347
  if (typeof runId !== "string" || runId.trim().length === 0) {
19348
19348
  throw new Error("runtime active job runId \u4E0D\u80FD\u4E3A\u7A7A");
19349
19349
  }
@@ -19360,7 +19360,7 @@ async function updateRuntimeLeaseActiveJob(handle, runId, active, options = {})
19360
19360
  const legacyBoundJobIds = new Set(store.owner.legacyBoundJobIds);
19361
19361
  const legacyResolvedJobIds = new Set(store.owner.legacyResolvedJobIds);
19362
19362
  let legacyActiveJobs = store.owner.legacyActiveJobs;
19363
- if (active) {
19363
+ if (active2) {
19364
19364
  if (!activeJobIds.has(normalizedRunId) && !legacyResolvedJobIds.has(normalizedRunId) && legacyActiveJobs > 0) {
19365
19365
  legacyActiveJobs -= 1;
19366
19366
  legacyBoundJobIds.add(normalizedRunId);
@@ -19471,7 +19471,7 @@ var defaultRuntimeLifecycleDeps = {
19471
19471
  },
19472
19472
  randomToken: import_crypto2.randomUUID,
19473
19473
  now: Date.now,
19474
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms))
19474
+ sleep: (ms) => new Promise((resolve9) => setTimeout(resolve9, ms))
19475
19475
  };
19476
19476
  async function withLifecycleLock(workRoot, deps, action) {
19477
19477
  const stateDir = stateDirectory(workRoot);
@@ -20788,7 +20788,7 @@ function describeInstallerExitCode(exitCode) {
20788
20788
  }
20789
20789
  }
20790
20790
  function executeInstallerProcess(installerPath, installDir, logPath) {
20791
- return new Promise((resolve8, reject) => {
20791
+ return new Promise((resolve9, reject) => {
20792
20792
  const child = (0, import_child_process3.spawn)(installerPath, [
20793
20793
  "/VERYSILENT",
20794
20794
  "/SUPPRESSMSGBOXES",
@@ -20810,14 +20810,14 @@ function executeInstallerProcess(installerPath, installDir, logPath) {
20810
20810
  } else if (code === null) {
20811
20811
  reject(new Error("\u5B89\u88C5\u7A0B\u5E8F\u6267\u884C\u5931\u8D25\uFF0C\u672A\u8FD4\u56DE\u9000\u51FA\u7801"));
20812
20812
  } else {
20813
- resolve8(code);
20813
+ resolve9(code);
20814
20814
  }
20815
20815
  });
20816
20816
  child.on("error", (error2) => reject(new Error(`\u5B89\u88C5\u7A0B\u5E8F\u542F\u52A8\u5931\u8D25: ${error2.message}`)));
20817
20817
  });
20818
20818
  }
20819
20819
  function runPythonSdkInstall(pythonPath, args, timeoutMs, bootstrapPipWheelPath, environment = process.env) {
20820
- return new Promise((resolve8, reject) => {
20820
+ return new Promise((resolve9, reject) => {
20821
20821
  const commandArgs = bootstrapPipWheelPath ? buildPipBootstrapCommand(args, bootstrapPipWheelPath) : args;
20822
20822
  const child = (0, import_child_process3.spawn)(pythonPath, commandArgs, {
20823
20823
  env: environment,
@@ -20838,7 +20838,7 @@ function runPythonSdkInstall(pythonPath, args, timeoutMs, bootstrapPipWheelPath,
20838
20838
  child.on("close", (code) => {
20839
20839
  clearTimeout(timer);
20840
20840
  if (code === 0) {
20841
- resolve8();
20841
+ resolve9();
20842
20842
  } else {
20843
20843
  reject(new Error(`Python SDK \u5B89\u88C5\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${code ?? "unknown"}, output=${truncateOutput(output)}`));
20844
20844
  }
@@ -20898,14 +20898,14 @@ function downloadFile(url, destination, redirectCount = 0, timeoutMs = 0, initia
20898
20898
  }
20899
20899
  fs5.mkdirSync(path5.dirname(destination), { recursive: true });
20900
20900
  const partialPath = `${destination}.${process.pid}.${(0, import_crypto3.randomUUID)()}.part`;
20901
- return new Promise((resolve8, reject) => {
20901
+ return new Promise((resolve9, reject) => {
20902
20902
  let settled = false;
20903
20903
  let file;
20904
20904
  const cleanupPartial = () => {
20905
20905
  file?.destroy();
20906
20906
  fs5.rmSync(partialPath, { force: true });
20907
20907
  };
20908
- const fail = (error2) => {
20908
+ const fail2 = (error2) => {
20909
20909
  if (settled)
20910
20910
  return;
20911
20911
  settled = true;
@@ -20919,9 +20919,9 @@ function downloadFile(url, destination, redirectCount = 0, timeoutMs = 0, initia
20919
20919
  fs5.rmSync(destination, { force: true });
20920
20920
  fs5.renameSync(partialPath, destination);
20921
20921
  settled = true;
20922
- resolve8();
20922
+ resolve9();
20923
20923
  } catch (error2) {
20924
- fail(error2);
20924
+ fail2(error2);
20925
20925
  }
20926
20926
  };
20927
20927
  const client = url.startsWith("https:") ? https : http;
@@ -20937,49 +20937,49 @@ function downloadFile(url, destination, redirectCount = 0, timeoutMs = 0, initia
20937
20937
  response.resume();
20938
20938
  const nextUrl = new URL(location, url).toString();
20939
20939
  if (initialProtocol === "https:" && new URL(nextUrl).protocol !== "https:") {
20940
- fail(new Error(`\u62D2\u7EDD\u5B89\u88C5\u5305 HTTPS \u964D\u7EA7\u91CD\u5B9A\u5411: ${nextUrl}`));
20940
+ fail2(new Error(`\u62D2\u7EDD\u5B89\u88C5\u5305 HTTPS \u964D\u7EA7\u91CD\u5B9A\u5411: ${nextUrl}`));
20941
20941
  return;
20942
20942
  }
20943
20943
  downloadFile(nextUrl, destination, redirectCount + 1, timeoutMs, initialProtocol).then(() => {
20944
20944
  if (settled)
20945
20945
  return;
20946
20946
  settled = true;
20947
- resolve8();
20948
- }, fail);
20947
+ resolve9();
20948
+ }, fail2);
20949
20949
  return;
20950
20950
  }
20951
20951
  if (statusCode < 200 || statusCode >= 300) {
20952
20952
  response.resume();
20953
- fail(new Error(`\u4E0B\u8F7D\u5931\u8D25: HTTP ${statusCode}`));
20953
+ fail2(new Error(`\u4E0B\u8F7D\u5931\u8D25: HTTP ${statusCode}`));
20954
20954
  return;
20955
20955
  }
20956
20956
  file = fs5.createWriteStream(partialPath, { flags: "wx" });
20957
- response.on("aborted", () => fail(new Error(`\u4E0B\u8F7D\u54CD\u5E94\u4E2D\u65AD: ${url}`)));
20958
- response.on("error", fail);
20957
+ response.on("aborted", () => fail2(new Error(`\u4E0B\u8F7D\u54CD\u5E94\u4E2D\u65AD: ${url}`)));
20958
+ response.on("error", fail2);
20959
20959
  response.on("close", () => {
20960
20960
  if (!response.complete)
20961
- fail(new Error(`\u4E0B\u8F7D\u54CD\u5E94\u672A\u5B8C\u6574\u7ED3\u675F: ${url}`));
20961
+ fail2(new Error(`\u4E0B\u8F7D\u54CD\u5E94\u672A\u5B8C\u6574\u7ED3\u675F: ${url}`));
20962
20962
  });
20963
20963
  response.pipe(file);
20964
20964
  file.on("finish", () => {
20965
20965
  if (!response.complete) {
20966
- fail(new Error(`\u4E0B\u8F7D\u5185\u5BB9\u4E0D\u5B8C\u6574: ${url}`));
20966
+ fail2(new Error(`\u4E0B\u8F7D\u5185\u5BB9\u4E0D\u5B8C\u6574: ${url}`));
20967
20967
  return;
20968
20968
  }
20969
- file?.close((error2) => error2 ? fail(error2) : succeed());
20969
+ file?.close((error2) => error2 ? fail2(error2) : succeed());
20970
20970
  });
20971
- file.on("error", fail);
20971
+ file.on("error", fail2);
20972
20972
  });
20973
20973
  if (timeoutMs > 0) {
20974
20974
  request.setTimeout(timeoutMs, () => {
20975
20975
  request.destroy(new Error(`\u4E0B\u8F7D\u8D85\u65F6: ${url}`));
20976
20976
  });
20977
20977
  }
20978
- request.on("error", fail);
20978
+ request.on("error", fail2);
20979
20979
  });
20980
20980
  }
20981
20981
  function delay(ms) {
20982
- return new Promise((resolve8) => setTimeout(resolve8, ms));
20982
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
20983
20983
  }
20984
20984
  function getZltafExePath(workRoot) {
20985
20985
  return path5.join(workRoot, "zltaf", "zltaf.exe");
@@ -21469,7 +21469,7 @@ var ZltafIpcClient = class {
21469
21469
  log("CONNECT", "IPC \u8FDE\u63A5\u5EFA\u7ACB\u5B8C\u6210");
21470
21470
  }
21471
21471
  invoke(uri, params, body = "{}", timeout = 5e3, customHeaders = []) {
21472
- return new Promise((resolve8, reject) => {
21472
+ return new Promise((resolve9, reject) => {
21473
21473
  if (!this.workSocket || !this.connected) {
21474
21474
  log("INVOKE", `\u8C03\u7528\u5931\u8D25: \u672A\u8FDE\u63A5 (hasSocket=${Boolean(this.workSocket)}, connected=${this.connected})`);
21475
21475
  reject(new Error("Not connected"));
@@ -21496,7 +21496,7 @@ var ZltafIpcClient = class {
21496
21496
  log("REQ", `seq=${seq} \u8D85\u65F6 (timeout=${timeout}ms)`);
21497
21497
  reject(new Error(`RPC timeout for ${uri} (seq=${seq})`));
21498
21498
  }, timeout);
21499
- this.pendingRequests.set(seq, { resolve: resolve8, reject, timer });
21499
+ this.pendingRequests.set(seq, { resolve: resolve9, reject, timer });
21500
21500
  this.workSocket.write(request.generatePacket());
21501
21501
  });
21502
21502
  }
@@ -21518,11 +21518,11 @@ var ZltafIpcClient = class {
21518
21518
  log("DISCONNECT", "\u4E3B\u52A8\u65AD\u5F00\u8FDE\u63A5");
21519
21519
  }
21520
21520
  connectWorkPipe(pipeName, timeout) {
21521
- return new Promise((resolve8, reject) => {
21521
+ return new Promise((resolve9, reject) => {
21522
21522
  const timer = setTimeout(() => reject(new Error(`Work pipe connect timeout: ${pipeName}`)), timeout);
21523
21523
  this.workSocket = net2.connect(pipeName, () => {
21524
21524
  clearTimeout(timer);
21525
- resolve8();
21525
+ resolve9();
21526
21526
  });
21527
21527
  this.workSocket.on("error", (error2) => {
21528
21528
  clearTimeout(timer);
@@ -21547,13 +21547,13 @@ var ZltafIpcClient = class {
21547
21547
  this.onDisconnect?.();
21548
21548
  }
21549
21549
  connectAndSend(pipePath, data, timeout) {
21550
- return new Promise((resolve8, reject) => {
21550
+ return new Promise((resolve9, reject) => {
21551
21551
  const timer = setTimeout(() => {
21552
21552
  if (this.mainSocket) {
21553
21553
  this.mainSocket.destroy();
21554
21554
  this.mainSocket = null;
21555
21555
  }
21556
- resolve8(null);
21556
+ resolve9(null);
21557
21557
  }, timeout);
21558
21558
  let responseBuffer = Buffer.alloc(0);
21559
21559
  this.mainSocket = net2.connect(pipePath, () => this.mainSocket.write(data));
@@ -21564,7 +21564,7 @@ var ZltafIpcClient = class {
21564
21564
  clearTimeout(timer);
21565
21565
  this.mainSocket.destroy();
21566
21566
  this.mainSocket = null;
21567
- resolve8(packet);
21567
+ resolve9(packet);
21568
21568
  }
21569
21569
  });
21570
21570
  this.mainSocket.on("error", (error2) => {
@@ -22081,7 +22081,7 @@ async function claimKeepaliveLock(runId) {
22081
22081
  if (existing?.record && keepaliveLeaseIsFresh(existing.record) && keepaliveProcessIsCurrent(existing.record)) return null;
22082
22082
  if ((!existing || !existing.record) && incompleteReads < KEEPALIVE_LOCK_INIT_RETRIES) {
22083
22083
  incompleteReads += 1;
22084
- await new Promise((resolve8) => setTimeout(resolve8, KEEPALIVE_LOCK_INIT_RETRY_MS));
22084
+ await new Promise((resolve9) => setTimeout(resolve9, KEEPALIVE_LOCK_INIT_RETRY_MS));
22085
22085
  continue;
22086
22086
  }
22087
22087
  if (!existing || !existing.record && keepaliveTimestampIsFresh(existing.identity.mtimeMs)) return null;
@@ -22105,16 +22105,16 @@ async function waitForKeepaliveProcessIdentity(pid) {
22105
22105
  while (Date.now() < deadline) {
22106
22106
  const identity = inspectRuntimeProcess(pid);
22107
22107
  if (identity) return identity;
22108
- await new Promise((resolve8) => setTimeout(resolve8, KEEPALIVE_READY_POLL_MS));
22108
+ await new Promise((resolve9) => setTimeout(resolve9, KEEPALIVE_READY_POLL_MS));
22109
22109
  }
22110
22110
  return null;
22111
22111
  }
22112
22112
  function waitForKeepaliveReady(readyFile, child) {
22113
22113
  const startedAt = Date.now();
22114
- return new Promise((resolve8, reject) => {
22114
+ return new Promise((resolve9, reject) => {
22115
22115
  const poll = () => {
22116
22116
  if (fs9.existsSync(readyFile)) {
22117
- resolve8();
22117
+ resolve9();
22118
22118
  return;
22119
22119
  }
22120
22120
  if (child.exitCode !== null) {
@@ -22180,7 +22180,7 @@ function mcpErrorCode(result) {
22180
22180
  return Number.isFinite(code) ? code : null;
22181
22181
  }
22182
22182
  async function keepaliveDelay() {
22183
- await new Promise((resolve8) => setTimeout(resolve8, KEEPALIVE_RETRY_MS));
22183
+ await new Promise((resolve9) => setTimeout(resolve9, KEEPALIVE_RETRY_MS));
22184
22184
  }
22185
22185
  function parseAttemptStartedAt(raw) {
22186
22186
  if (!raw) return void 0;
@@ -23111,12 +23111,12 @@ var StdioServerTransport = class {
23111
23111
  this.onclose?.();
23112
23112
  }
23113
23113
  send(message) {
23114
- return new Promise((resolve8) => {
23114
+ return new Promise((resolve9) => {
23115
23115
  const json = serializeMessage(message);
23116
23116
  if (this._stdout.write(json)) {
23117
- resolve8();
23117
+ resolve9();
23118
23118
  } else {
23119
- this._stdout.once("drain", resolve8);
23119
+ this._stdout.once("drain", resolve9);
23120
23120
  }
23121
23121
  });
23122
23122
  }
@@ -23132,71 +23132,129 @@ var fs11 = __toESM(require("node:fs"), 1);
23132
23132
  var os5 = __toESM(require("node:os"), 1);
23133
23133
  var path11 = __toESM(require("node:path"), 1);
23134
23134
  var import_node_child_process = require("node:child_process");
23135
- var TASK_ROOT = path11.join(os5.tmpdir(), "zlt-mcp-install-tasks");
23135
+ var DEFAULT_TASK_ROOT = path11.join(os5.tmpdir(), "zlt-mcp-install-tasks");
23136
+ var STARTUP_GRACE_MS = 3e4;
23137
+ function taskRoot() {
23138
+ return process.env.ZLT_MCP_INSTALL_TASK_ROOT || DEFAULT_TASK_ROOT;
23139
+ }
23136
23140
  function taskPath(taskId) {
23137
- return path11.join(TASK_ROOT, `${taskId}.json`);
23141
+ return path11.join(taskRoot(), `${taskId}.json`);
23138
23142
  }
23139
23143
  function write(record3) {
23140
- fs11.mkdirSync(TASK_ROOT, { recursive: true });
23144
+ fs11.mkdirSync(taskRoot(), { recursive: true });
23141
23145
  const target = taskPath(record3.taskId);
23142
23146
  const temporary = `${target}.${process.pid}.tmp`;
23143
23147
  fs11.writeFileSync(temporary, `${JSON.stringify(record3)}
23144
23148
  `, "utf8");
23145
23149
  fs11.renameSync(temporary, target);
23146
23150
  }
23147
- function readInstallTask(taskId) {
23151
+ function readRaw(taskId) {
23148
23152
  if (!/^[A-Za-z0-9_-]+$/.test(taskId)) return void 0;
23149
23153
  try {
23150
- return JSON.parse(fs11.readFileSync(taskPath(taskId), "utf8"));
23154
+ const record3 = JSON.parse(fs11.readFileSync(taskPath(taskId), "utf8"));
23155
+ return record3.taskId === taskId ? record3 : void 0;
23151
23156
  } catch {
23152
23157
  return void 0;
23153
23158
  }
23154
23159
  }
23155
- function startInstallTask(entrypoint, argsJson, spawnProcess = import_node_child_process.spawn) {
23160
+ function active(record3) {
23161
+ return record3.status === "queued" || record3.status === "running";
23162
+ }
23163
+ function alive(pid) {
23164
+ if (!Number.isInteger(pid) || pid <= 0) return false;
23156
23165
  try {
23157
- for (const file of fs11.readdirSync(TASK_ROOT)) {
23158
- if (!file.endsWith(".json")) continue;
23159
- const existing = readInstallTask(file.slice(0, -5));
23160
- if (existing && (existing.status === "queued" || existing.status === "running")) return existing;
23166
+ process.kill(pid, 0);
23167
+ return true;
23168
+ } catch (error2) {
23169
+ return error2.code !== "ESRCH";
23170
+ }
23171
+ }
23172
+ function fail(taskId, error2) {
23173
+ const current = readRaw(taskId);
23174
+ if (!current || !active(current)) return current;
23175
+ const failed = { ...current, status: "failed", endedAt: (/* @__PURE__ */ new Date()).toISOString(), error: error2 };
23176
+ write(failed);
23177
+ return failed;
23178
+ }
23179
+ var INTERRUPTED = "\u5B89\u88C5\u5DE5\u4F5C\u8FDB\u7A0B\u5DF2\u9000\u51FA\u6216\u65E0\u6CD5\u786E\u8BA4\uFF0C\u5B89\u88C5\u7ED3\u679C\u672A\u77E5\uFF1B\u5916\u90E8\u5B89\u88C5\u5668\u53EF\u80FD\u4ECD\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u68C0\u67E5\u5B9E\u9645\u5B89\u88C5\u72B6\u6001\u518D\u51B3\u5B9A\u662F\u5426\u91CD\u8BD5\u3002";
23180
+ function readInstallTask(taskId) {
23181
+ const current = readRaw(taskId);
23182
+ if (!current || !active(current)) return current;
23183
+ let pid = current.workerPid;
23184
+ if (!pid) {
23185
+ try {
23186
+ pid = Number(fs11.readFileSync(`${taskPath(taskId)}.pid`, "utf8"));
23187
+ } catch {
23161
23188
  }
23162
- } catch {
23189
+ }
23190
+ if (pid && alive(pid)) return { ...current, workerPid: pid };
23191
+ const age = Date.now() - Date.parse(current.createdAt);
23192
+ if (!pid && current.status === "queued" && age >= 0 && age < STARTUP_GRACE_MS && (!current.launcherPid || alive(current.launcherPid))) return current;
23193
+ return fail(taskId, INTERRUPTED);
23194
+ }
23195
+ function startInstallTask(entrypoint, argsJson, spawnProcess = import_node_child_process.spawn) {
23196
+ let files = [];
23197
+ try {
23198
+ files = fs11.readdirSync(taskRoot());
23199
+ } catch (error2) {
23200
+ if (error2.code !== "ENOENT") throw error2;
23201
+ }
23202
+ for (const file of files) {
23203
+ if (!file.endsWith(".json")) continue;
23204
+ const taskId2 = file.slice(0, -5);
23205
+ const existing = readRaw(taskId2);
23206
+ if (existing && active(existing)) return readInstallTask(taskId2);
23163
23207
  }
23164
23208
  const taskId = `install-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
23165
- const record3 = { taskId, status: "queued", createdAt: (/* @__PURE__ */ new Date()).toISOString() };
23209
+ const record3 = {
23210
+ taskId,
23211
+ status: "queued",
23212
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
23213
+ launcherPid: process.pid,
23214
+ logPath: path11.join(taskRoot(), `${taskId}.log`)
23215
+ };
23166
23216
  write(record3);
23167
- const child = spawnProcess(process.execPath, [entrypoint, "__install-worker", taskId, argsJson], {
23168
- detached: true,
23169
- stdio: "ignore",
23170
- windowsHide: true,
23171
- env: process.env
23172
- });
23173
- child.unref();
23174
- return record3;
23217
+ let logFd;
23218
+ try {
23219
+ logFd = fs11.openSync(record3.logPath, "a");
23220
+ const child = spawnProcess(process.execPath, [entrypoint, "__install-worker", taskId, argsJson], {
23221
+ detached: true,
23222
+ stdio: ["ignore", logFd, logFd],
23223
+ windowsHide: true,
23224
+ env: process.env
23225
+ });
23226
+ child.once("error", (error2) => {
23227
+ fail(taskId, `\u5B89\u88C5\u5DE5\u4F5C\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25: ${error2.message}`);
23228
+ });
23229
+ child.once("exit", (code, signal) => {
23230
+ fail(taskId, `${INTERRUPTED} (exit=${code}, signal=${signal})`);
23231
+ });
23232
+ if (child.pid) fs11.writeFileSync(`${taskPath(taskId)}.pid`, String(child.pid), "utf8");
23233
+ child.unref();
23234
+ } catch (error2) {
23235
+ fail(taskId, `\u5B89\u88C5\u5DE5\u4F5C\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25: ${error2 instanceof Error ? error2.message : String(error2)}`);
23236
+ } finally {
23237
+ if (logFd !== void 0) fs11.closeSync(logFd);
23238
+ }
23239
+ return readRaw(taskId);
23175
23240
  }
23176
- function runInstallWorker(taskId, argsJson, install) {
23177
- const current = readInstallTask(taskId);
23178
- if (!current || current.status !== "queued") return Promise.resolve();
23179
- write({ ...current, status: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
23180
- let args;
23241
+ async function runInstallWorker(taskId, argsJson, install) {
23242
+ const current = readRaw(taskId);
23243
+ if (!current || current.status !== "queued") return;
23244
+ write({ ...current, status: "running", workerPid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
23181
23245
  try {
23182
23246
  const parsed = JSON.parse(argsJson);
23183
23247
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("\u5B89\u88C5\u4EFB\u52A1\u53C2\u6570\u4E0D\u662F\u5BF9\u8C61");
23184
- args = parsed;
23248
+ const workRoot = await install(parsed, (message) => updateInstallTask(taskId, { message }));
23249
+ const latest = readRaw(taskId);
23250
+ if (latest?.status === "running") write({ ...latest, status: "completed", endedAt: (/* @__PURE__ */ new Date()).toISOString(), workRoot });
23185
23251
  } catch (error2) {
23186
- write({ ...current, status: "failed", startedAt: (/* @__PURE__ */ new Date()).toISOString(), endedAt: (/* @__PURE__ */ new Date()).toISOString(), error: String(error2) });
23187
- return Promise.resolve();
23188
- }
23189
- return install(args, (message) => updateInstallTask(taskId, { message })).then((workRoot) => {
23190
- const latest = readInstallTask(taskId) ?? current;
23191
- write({ ...latest, status: "completed", endedAt: (/* @__PURE__ */ new Date()).toISOString(), workRoot });
23192
- }).catch((error2) => {
23193
- const latest = readInstallTask(taskId) ?? current;
23194
- write({ ...latest, status: "failed", endedAt: (/* @__PURE__ */ new Date()).toISOString(), error: error2 instanceof Error ? error2.message : String(error2) });
23195
- });
23252
+ fail(taskId, error2 instanceof Error ? error2.message : String(error2));
23253
+ }
23196
23254
  }
23197
23255
  function updateInstallTask(taskId, update) {
23198
- const current = readInstallTask(taskId);
23199
- if (!current || current.status === "completed" || current.status === "failed") return;
23256
+ const current = readRaw(taskId);
23257
+ if (!current || !active(current)) return;
23200
23258
  write({ ...current, ...update, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
23201
23259
  }
23202
23260
 
@@ -24667,14 +24725,14 @@ var BACKTEST_WAIT_POLL_IDENTITY = {
24667
24725
  };
24668
24726
  function sleepUnlessAborted(ms, abortSignal) {
24669
24727
  if (abortSignal?.aborted) return Promise.resolve();
24670
- return new Promise((resolve8) => {
24728
+ return new Promise((resolve9) => {
24671
24729
  const onAbort = () => {
24672
24730
  clearTimeout(timer);
24673
- resolve8();
24731
+ resolve9();
24674
24732
  };
24675
24733
  const timer = setTimeout(() => {
24676
24734
  abortSignal?.removeEventListener("abort", onAbort);
24677
- resolve8();
24735
+ resolve9();
24678
24736
  }, ms);
24679
24737
  abortSignal?.addEventListener("abort", onAbort, { once: true });
24680
24738
  });
@@ -27046,9 +27104,9 @@ async function startMcpServer() {
27046
27104
  signalCliKeepaliveReady();
27047
27105
  return runtimeLease.workRoot;
27048
27106
  };
27049
- const recordActiveBacktest = (runId, active) => {
27107
+ const recordActiveBacktest = (runId, active2) => {
27050
27108
  if (!runtimeLease) return;
27051
- leaseStateUpdate = leaseStateUpdate.then(() => updateRuntimeLeaseActiveJob(runtimeLease, runId, active)).catch((error2) => {
27109
+ leaseStateUpdate = leaseStateUpdate.then(() => updateRuntimeLeaseActiveJob(runtimeLease, runId, active2)).catch((error2) => {
27052
27110
  process.stderr.write(`[zlt-mcp-proxy] runtime lease active-job update failed: ${error2 instanceof Error ? error2.message : String(error2)}
27053
27111
  `);
27054
27112
  });
@@ -27480,6 +27538,13 @@ async function handleRuntimeTool(handler, args, runtimeConfig, acquireRuntime, i
27480
27538
  );
27481
27539
  }
27482
27540
  const task = startInstallTask(process.argv[1], JSON.stringify(args));
27541
+ if (task.status === "failed") {
27542
+ return jsonToolResult(10012, task.error || "runtime \u5B89\u88C5\u5931\u8D25\u3002", {
27543
+ ...task,
27544
+ isTerminal: true,
27545
+ nextAction: "inspect-error"
27546
+ }, true);
27547
+ }
27483
27548
  return jsonToolResult(0, "", {
27484
27549
  ...task,
27485
27550
  isTerminal: false,
@@ -27526,8 +27591,8 @@ async function waitForInstallTask(taskId, control, doctor) {
27526
27591
  }, !ready);
27527
27592
  }
27528
27593
  if (task.status === "failed") return jsonToolResult(10012, task.error || "runtime \u5B89\u88C5\u5931\u8D25\u3002", { ...task, isTerminal: true, nextAction: "inspect-error" }, true);
27529
- await new Promise((resolve8, reject) => {
27530
- const timer = setTimeout(resolve8, 2e3);
27594
+ await new Promise((resolve9, reject) => {
27595
+ const timer = setTimeout(resolve9, 2e3);
27531
27596
  control.abortSignal?.addEventListener("abort", () => {
27532
27597
  clearTimeout(timer);
27533
27598
  reject(new Error("aborted"));
@@ -27843,9 +27908,8 @@ if (process.argv.length <= 2) {
27843
27908
  } else {
27844
27909
  const program2 = new Command();
27845
27910
  program2.command("__install-worker <taskId> <argsJson>", { hidden: true }).action(async (taskId, argsJson) => {
27846
- const config2 = loadAssistantRuntimeConfig(resolveDefaultRuntimeConfigPath());
27847
27911
  await runInstallWorker(taskId, argsJson, async (args, onProgress) => installRuntime({
27848
- config: config2,
27912
+ config: loadAssistantRuntimeConfig(resolveDefaultRuntimeConfigPath?.()),
27849
27913
  installType: "mini",
27850
27914
  installDir: typeof args.installDir === "string" && args.installDir ? args.installDir : void 0,
27851
27915
  onProgress: (message) => {
@@ -28059,7 +28123,6 @@ function compactCliReceipt(tool, result, resultFile) {
28059
28123
  if (source && typeof source === "object" && !Array.isArray(source)) {
28060
28124
  const fields = source;
28061
28125
  for (const key of [
28062
- "taskId",
28063
28126
  "runId",
28064
28127
  "backtestId",
28065
28128
  "strategyId",