@tradejs/cli 1.0.6 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +10049 -4860
- package/dist/lib/aiTrainMetrics.js +23 -0
- package/dist/lib/candlesProviderMigration.js +266 -0
- package/dist/lib/cliArgs.js +40 -0
- package/dist/lib/derivativesContextBackfill.js +487 -0
- package/dist/lib/researchAgent.js +195 -0
- package/dist/lib/runtimeParity.js +234 -0
- package/dist/lib/runtimeSignalsLoader.js +153 -0
- package/dist/lib/runtimeSignalsStorage.js +162 -0
- package/dist/lib/telegramReports.js +91 -0
- package/dist/lib/timeWindow.js +101 -0
- package/dist/scripts/agentRun.js +909 -0
- package/dist/scripts/aiTrain.js +113 -31
- package/dist/scripts/backtest.js +794 -93
- package/dist/scripts/candlesMigrateProvider.js +5302 -0
- package/dist/scripts/continuity.js +1 -1
- package/dist/scripts/migration.js +1 -1
- package/dist/scripts/researchAuto.js +847 -0
- package/dist/scripts/results.js +46 -22
- package/dist/scripts/runtimeParity.js +1688 -0
- package/dist/scripts/signals.js +774 -63
- package/dist/scripts/signalsSummary.js +712 -0
- package/dist/scripts/user-add.js +0 -9
- package/package.json +16 -12
|
@@ -0,0 +1,909 @@
|
|
|
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/scripts/agentRun.ts
|
|
31
|
+
var agentRun_exports = {};
|
|
32
|
+
__export(agentRun_exports, {
|
|
33
|
+
main: () => main
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(agentRun_exports);
|
|
36
|
+
var import_args = __toESM(require("args"));
|
|
37
|
+
var import_chalk = __toESM(require("chalk"));
|
|
38
|
+
var import_promises = __toESM(require("fs/promises"));
|
|
39
|
+
var import_node_os = __toESM(require("os"));
|
|
40
|
+
var import_node_path2 = __toESM(require("path"));
|
|
41
|
+
var import_node_child_process = require("child_process");
|
|
42
|
+
var import_redis = require("@tradejs/infra/redis");
|
|
43
|
+
var import_userSettings = require("@tradejs/infra/userSettings");
|
|
44
|
+
|
|
45
|
+
// src/lib/telegramReports.ts
|
|
46
|
+
var import_cli = require("@tradejs/node/cli");
|
|
47
|
+
var TELEGRAM_REPORT_CHUNK_LIMIT = 3900;
|
|
48
|
+
var buildPartHeader = (index, total) => `\u{1F4E9} <b>Part ${index}/${total}</b>`;
|
|
49
|
+
var splitLongLine = (line, limit) => {
|
|
50
|
+
const chunks = [];
|
|
51
|
+
for (let index = 0; index < line.length; index += limit) {
|
|
52
|
+
chunks.push(line.slice(index, index + limit));
|
|
53
|
+
}
|
|
54
|
+
return chunks;
|
|
55
|
+
};
|
|
56
|
+
var splitTelegramReport = (message, limit = TELEGRAM_REPORT_CHUNK_LIMIT) => {
|
|
57
|
+
const normalizedLimit = Math.max(100, Math.floor(limit));
|
|
58
|
+
if (message.length <= normalizedLimit) {
|
|
59
|
+
return [message];
|
|
60
|
+
}
|
|
61
|
+
const chunks = [];
|
|
62
|
+
let current = "";
|
|
63
|
+
const flush = () => {
|
|
64
|
+
if (!current) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
chunks.push(current);
|
|
68
|
+
current = "";
|
|
69
|
+
};
|
|
70
|
+
for (const line of message.split("\n")) {
|
|
71
|
+
const next = current ? `${current}
|
|
72
|
+
${line}` : line;
|
|
73
|
+
if (next.length <= normalizedLimit) {
|
|
74
|
+
current = next;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
flush();
|
|
78
|
+
if (line.length <= normalizedLimit) {
|
|
79
|
+
current = line;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
chunks.push(...splitLongLine(line, normalizedLimit));
|
|
83
|
+
}
|
|
84
|
+
flush();
|
|
85
|
+
return chunks;
|
|
86
|
+
};
|
|
87
|
+
var sendTelegramReport = async (message, options = {}) => {
|
|
88
|
+
const chunks = splitTelegramReport(message);
|
|
89
|
+
if (chunks.length === 1) {
|
|
90
|
+
await (0, import_cli.sendTextToTG)(chunks[0], options);
|
|
91
|
+
return chunks.length;
|
|
92
|
+
}
|
|
93
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
94
|
+
const header = buildPartHeader(index + 1, chunks.length);
|
|
95
|
+
await (0, import_cli.sendTextToTG)(`${header}
|
|
96
|
+
|
|
97
|
+
${chunk}`, {
|
|
98
|
+
userName: options.userName,
|
|
99
|
+
markup: index === chunks.length - 1 ? options.markup : void 0
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return chunks.length;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/lib/researchAgent.ts
|
|
106
|
+
var import_node_path = __toESM(require("path"));
|
|
107
|
+
var RESEARCH_AGENT_MODEL = "openai/gpt-5.4";
|
|
108
|
+
var RESEARCH_AGENT_REASONING_EFFORT = "medium";
|
|
109
|
+
var RESEARCH_AGENT_BRANCH_PREFIX = "codex/research";
|
|
110
|
+
var toUpperSnakeCase = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
111
|
+
var RESEARCH_NOTE_FILE_OVERRIDES = {
|
|
112
|
+
TrendLine: "AI_TRENDLINE_REPLAY_NOTES.md",
|
|
113
|
+
ReverseTrendLine: "AI_REVERSE_TRENDLINE_REPLAY_NOTES.md",
|
|
114
|
+
AdaptiveMomentumRibbon: "AI_ADAPTIVE_MOMENTUM_RIBBON_REPLAY_NOTES.md",
|
|
115
|
+
VolumeDivergence: "AI_VOLUME_DIVERGENCE_REPLAY_NOTES.md"
|
|
116
|
+
};
|
|
117
|
+
var buildResearchAgentBranchName = (strategy, runId) => {
|
|
118
|
+
const normalizedStrategy = strategy.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
119
|
+
const normalizedRunId = runId.trim().replace(/[^a-zA-Z0-9-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
120
|
+
return `${RESEARCH_AGENT_BRANCH_PREFIX}/${normalizedStrategy}-${normalizedRunId}`;
|
|
121
|
+
};
|
|
122
|
+
var buildResearchAgentCommitMessage = (strategy, runId) => `Research agent: ${strategy} follow-up for ${runId}`;
|
|
123
|
+
var buildResearchAgentPrTitle = (strategy, runId) => `Research agent: ${strategy} follow-up for ${runId}`;
|
|
124
|
+
var getResearchAgentNotePath = (strategy) => import_node_path.default.join(
|
|
125
|
+
"notes",
|
|
126
|
+
RESEARCH_NOTE_FILE_OVERRIDES[strategy] || `AI_${toUpperSnakeCase(strategy)}_REPLAY_NOTES.md`
|
|
127
|
+
);
|
|
128
|
+
var getResearchAgentAllowedPathPrefixes = (strategy) => [
|
|
129
|
+
`packages/strategies/src/${strategy}/`,
|
|
130
|
+
getResearchAgentNotePath(strategy)
|
|
131
|
+
];
|
|
132
|
+
var parseGithubRepositoryFromRemote = (value) => {
|
|
133
|
+
const trimmed = value.trim();
|
|
134
|
+
const sshMatch = trimmed.match(/github\.com:([^/]+\/[^/.]+)(?:\.git)?$/i);
|
|
135
|
+
if (sshMatch?.[1]) {
|
|
136
|
+
return sshMatch[1];
|
|
137
|
+
}
|
|
138
|
+
const httpsMatch = trimmed.match(/github\.com\/([^/]+\/[^/.]+)(?:\.git)?$/i);
|
|
139
|
+
if (httpsMatch?.[1]) {
|
|
140
|
+
return httpsMatch[1];
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
};
|
|
144
|
+
var buildResearchAgentPrBody = (params) => {
|
|
145
|
+
const validationSummary = Object.entries(params.validation || {}).map(([key, value]) => `- \`${key}\`: ${value}`).join("\n");
|
|
146
|
+
const aiRun = params.aiTrainLocal?.run;
|
|
147
|
+
const aiOutcome = params.aiTrainLocal?.outcome;
|
|
148
|
+
const lines = [
|
|
149
|
+
"## Why",
|
|
150
|
+
`Automated research follow-up for \`${params.strategy}\` from run \`${params.runId}\`.`,
|
|
151
|
+
`Research config: \`${params.config}\`, connector \`${params.connector}\`, timeframe \`${params.timeframe}\`, window \`${params.days}d\`, recent rows \`${params.recent}\`.`
|
|
152
|
+
];
|
|
153
|
+
if (params.summary) {
|
|
154
|
+
lines.push(params.summary);
|
|
155
|
+
}
|
|
156
|
+
if (aiRun || aiOutcome) {
|
|
157
|
+
lines.push("");
|
|
158
|
+
lines.push("### AI train snapshot");
|
|
159
|
+
if (aiRun) {
|
|
160
|
+
lines.push(
|
|
161
|
+
`- Rows: \`${aiRun.totalRows ?? 0}\`, approved: \`${aiRun.approvedRows ?? 0}\`, minQuality: \`${aiRun.minQuality ?? "n/a"}\``
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (aiOutcome) {
|
|
165
|
+
lines.push(
|
|
166
|
+
`- approvalRate=\`${aiOutcome.approvalRate ?? 0}\`, precisionApproved=\`${aiOutcome.precisionApproved ?? 0}\`, recallWinners=\`${aiOutcome.recallWinners ?? 0}\``
|
|
167
|
+
);
|
|
168
|
+
lines.push(
|
|
169
|
+
`- avgProfitApproved=\`${aiOutcome.avgProfitApproved ?? 0}\`, avgProfitApprovedPerMonth=\`${aiOutcome.avgProfitApprovedPerMonth ?? 0}\`, expectancyDelta=\`${aiOutcome.expectancyDelta ?? 0}\``
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
lines.push("");
|
|
174
|
+
lines.push("## What changed");
|
|
175
|
+
for (const file of params.changedFiles) {
|
|
176
|
+
lines.push(`- \`${file}\``);
|
|
177
|
+
}
|
|
178
|
+
lines.push("");
|
|
179
|
+
lines.push("## Validation");
|
|
180
|
+
lines.push(validationSummary || "- Validation did not run");
|
|
181
|
+
lines.push("");
|
|
182
|
+
lines.push("## Notes");
|
|
183
|
+
lines.push("- Created automatically by the TradeJS research agent.");
|
|
184
|
+
lines.push("- Base branch: `stable`.");
|
|
185
|
+
return lines.join("\n");
|
|
186
|
+
};
|
|
187
|
+
var normalizeDiffOutput = (value) => {
|
|
188
|
+
const trimmed = value.trim();
|
|
189
|
+
if (!trimmed) {
|
|
190
|
+
return "";
|
|
191
|
+
}
|
|
192
|
+
const fenced = trimmed.match(/```(?:diff)?\s*([\s\S]*?)```/i);
|
|
193
|
+
const content = fenced?.[1]?.trim() || trimmed;
|
|
194
|
+
return content.trim();
|
|
195
|
+
};
|
|
196
|
+
var parseChangedFilesFromDiff = (diffText) => {
|
|
197
|
+
const files = /* @__PURE__ */ new Set();
|
|
198
|
+
const lines = diffText.split("\n");
|
|
199
|
+
for (const line of lines) {
|
|
200
|
+
if (!line.startsWith("+++ ")) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const filePath = line.slice(4).trim();
|
|
204
|
+
if (!filePath || filePath === "/dev/null") {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const normalized = filePath.startsWith("b/") ? filePath.slice(2) : filePath;
|
|
208
|
+
if (normalized) {
|
|
209
|
+
files.add(normalized);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return [...files];
|
|
213
|
+
};
|
|
214
|
+
var assertDiffAllowed = (diffText, allowedPrefixes) => {
|
|
215
|
+
if (!diffText || diffText === "NO_CHANGES") {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
if (/\n\+\+\+\s+\/dev\/null\b/.test(`
|
|
219
|
+
${diffText}`)) {
|
|
220
|
+
throw new Error("Research agent diff must not delete files");
|
|
221
|
+
}
|
|
222
|
+
const changedFiles = parseChangedFilesFromDiff(diffText);
|
|
223
|
+
if (!changedFiles.length) {
|
|
224
|
+
throw new Error("Research agent diff did not contain any changed files");
|
|
225
|
+
}
|
|
226
|
+
for (const filePath of changedFiles) {
|
|
227
|
+
const allowed = allowedPrefixes.some(
|
|
228
|
+
(prefix) => prefix.endsWith("/") ? filePath.startsWith(prefix) : filePath === prefix || filePath.startsWith(`${prefix}/`)
|
|
229
|
+
);
|
|
230
|
+
if (!allowed) {
|
|
231
|
+
throw new Error(`Research agent changed disallowed path: ${filePath}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return changedFiles;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/scripts/agentRun.ts
|
|
238
|
+
import_args.default.example(
|
|
239
|
+
"yarn cli:node8g agent-run --runId 1710000000000-trendline --strategy TrendLine",
|
|
240
|
+
"Run the OpenRouter-based research follow-up coding agent in a git worktree"
|
|
241
|
+
);
|
|
242
|
+
import_args.default.option(["U", "user"], "Use user config", "root");
|
|
243
|
+
import_args.default.option(["r", "runId"], "Research run id");
|
|
244
|
+
import_args.default.option(["s", "strategy"], "Strategy name");
|
|
245
|
+
import_args.default.option("json", "Print structured JSON summary", false);
|
|
246
|
+
var flags = import_args.default.parse(process.argv);
|
|
247
|
+
var projectRoot = String(process.env.PROJECT_CWD || process.cwd()).trim() || process.cwd();
|
|
248
|
+
var AGENT_SYSTEM_PROMPT = `You are a senior TypeScript trading research coding agent working inside the TradeJS monorepo.
|
|
249
|
+
|
|
250
|
+
Output ONLY one of:
|
|
251
|
+
1. A unified git diff relative to repo root that can be applied with git apply.
|
|
252
|
+
2. The exact token NO_CHANGES if no safe change should be made.
|
|
253
|
+
|
|
254
|
+
Rules:
|
|
255
|
+
- Keep the patch small and surgical.
|
|
256
|
+
- Prefer strategy-scoped tests and notes updates over broad refactors.
|
|
257
|
+
- If you change strategy logic, also add or update tests in the same strategy directory.
|
|
258
|
+
- Do not touch package boundaries or unrelated files.
|
|
259
|
+
- Do not add markdown fences, commentary, or explanations.
|
|
260
|
+
- Do not modify files outside the allowed paths listed by the user.
|
|
261
|
+
- Deleting files is not allowed.
|
|
262
|
+
`;
|
|
263
|
+
var trimTextTail = (value, limit = 4e3) => value.length <= limit ? value : value.slice(-limit);
|
|
264
|
+
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
265
|
+
var isOpenRouterEndpoint = (value) => {
|
|
266
|
+
try {
|
|
267
|
+
return new URL(value).hostname.toLowerCase().includes("openrouter");
|
|
268
|
+
} catch {
|
|
269
|
+
return value.toLowerCase().includes("openrouter");
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
var resolveChatCompletionsUrl = (endpoint) => {
|
|
273
|
+
const normalized = endpoint.replace(/\/+$/, "");
|
|
274
|
+
if (normalized.endsWith("/chat/completions")) {
|
|
275
|
+
return normalized;
|
|
276
|
+
}
|
|
277
|
+
if (normalized.endsWith("/v1")) {
|
|
278
|
+
return `${normalized}/chat/completions`;
|
|
279
|
+
}
|
|
280
|
+
return `${normalized}/chat/completions`;
|
|
281
|
+
};
|
|
282
|
+
var runCommand = async (command, commandArgs, options = {}) => new Promise((resolve, reject) => {
|
|
283
|
+
const child = (0, import_node_child_process.spawn)(command, commandArgs, {
|
|
284
|
+
cwd: options.cwd || projectRoot,
|
|
285
|
+
env: {
|
|
286
|
+
...process.env,
|
|
287
|
+
...options.env
|
|
288
|
+
},
|
|
289
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
290
|
+
});
|
|
291
|
+
let stdout = "";
|
|
292
|
+
let stderr = "";
|
|
293
|
+
child.stdout.on("data", (chunk) => {
|
|
294
|
+
stdout += chunk.toString();
|
|
295
|
+
});
|
|
296
|
+
child.stderr.on("data", (chunk) => {
|
|
297
|
+
stderr += chunk.toString();
|
|
298
|
+
});
|
|
299
|
+
child.on("error", reject);
|
|
300
|
+
child.on("close", (code) => {
|
|
301
|
+
resolve({
|
|
302
|
+
exitCode: code ?? 1,
|
|
303
|
+
stdout,
|
|
304
|
+
stderr
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
var runCommandOrThrow = async (command, commandArgs, options = {}) => {
|
|
309
|
+
const result = await runCommand(command, commandArgs, options);
|
|
310
|
+
if (result.exitCode !== 0) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
`${command} ${commandArgs.join(" ")} failed: ${trimTextTail(
|
|
313
|
+
result.stderr || result.stdout,
|
|
314
|
+
2e3
|
|
315
|
+
)}`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
};
|
|
320
|
+
var listFilesRecursive = async (targetPath) => {
|
|
321
|
+
try {
|
|
322
|
+
const entries = await import_promises.default.readdir(targetPath, { withFileTypes: true });
|
|
323
|
+
const nested = await Promise.all(
|
|
324
|
+
entries.map(async (entry) => {
|
|
325
|
+
const absolutePath = import_node_path2.default.join(targetPath, entry.name);
|
|
326
|
+
if (entry.isDirectory()) {
|
|
327
|
+
return listFilesRecursive(absolutePath);
|
|
328
|
+
}
|
|
329
|
+
return [absolutePath];
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
return nested.flat().sort((left, right) => left.localeCompare(right));
|
|
333
|
+
} catch {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
var collectStrategyContextFiles = async (worktreePath, strategy) => {
|
|
338
|
+
const strategyRoot = import_node_path2.default.join(
|
|
339
|
+
worktreePath,
|
|
340
|
+
"packages",
|
|
341
|
+
"strategies",
|
|
342
|
+
"src",
|
|
343
|
+
strategy
|
|
344
|
+
);
|
|
345
|
+
const notePath = import_node_path2.default.join(worktreePath, getResearchAgentNotePath(strategy));
|
|
346
|
+
const strategyFiles = await listFilesRecursive(strategyRoot);
|
|
347
|
+
const files = strategyFiles.filter(
|
|
348
|
+
(filePath) => /\.(ts|tsx|md)$/.test(filePath)
|
|
349
|
+
);
|
|
350
|
+
try {
|
|
351
|
+
await import_promises.default.access(notePath);
|
|
352
|
+
files.push(notePath);
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
return [...new Set(files)].sort((left, right) => left.localeCompare(right));
|
|
356
|
+
};
|
|
357
|
+
var renderFileContext = async (worktreePath, filePath) => {
|
|
358
|
+
const relativePath = import_node_path2.default.relative(worktreePath, filePath).replace(/\\/g, "/");
|
|
359
|
+
const content = await import_promises.default.readFile(filePath, "utf8");
|
|
360
|
+
return [
|
|
361
|
+
`=== FILE: ${relativePath} ===`,
|
|
362
|
+
content,
|
|
363
|
+
`=== END FILE: ${relativePath} ===`
|
|
364
|
+
].join("\n");
|
|
365
|
+
};
|
|
366
|
+
var buildPrompt = async (worktreePath, run, allowedPaths) => {
|
|
367
|
+
const files = await collectStrategyContextFiles(worktreePath, run.strategy);
|
|
368
|
+
const contexts = await Promise.all(
|
|
369
|
+
files.map((filePath) => renderFileContext(worktreePath, filePath))
|
|
370
|
+
);
|
|
371
|
+
return [
|
|
372
|
+
`Strategy: ${run.strategy}`,
|
|
373
|
+
`Research run id: ${run.runId}`,
|
|
374
|
+
`Allowed paths: ${allowedPaths.join(", ")}`,
|
|
375
|
+
`Backtest config: ${run.config}`,
|
|
376
|
+
`Window: ${run.days} days, timeframe=${run.timeframe}, connector=${run.connector}`,
|
|
377
|
+
"",
|
|
378
|
+
"Research run summary:",
|
|
379
|
+
JSON.stringify(
|
|
380
|
+
{
|
|
381
|
+
status: run.status,
|
|
382
|
+
finishedAt: run.finishedAt,
|
|
383
|
+
backtestResult: run.artifacts.backtestResult,
|
|
384
|
+
aiTrainLocal: run.artifacts.aiTrainLocal,
|
|
385
|
+
strategyConfig: run.artifacts.strategyConfig
|
|
386
|
+
},
|
|
387
|
+
null,
|
|
388
|
+
2
|
|
389
|
+
),
|
|
390
|
+
"",
|
|
391
|
+
"Task:",
|
|
392
|
+
"- Inspect the research results and current strategy files.",
|
|
393
|
+
"- Produce the smallest safe follow-up patch.",
|
|
394
|
+
"- Prefer notes and tests if the data does not justify a production logic change.",
|
|
395
|
+
"- If you change strategy logic, include matching regression tests.",
|
|
396
|
+
"",
|
|
397
|
+
contexts.join("\n\n")
|
|
398
|
+
].join("\n");
|
|
399
|
+
};
|
|
400
|
+
var extractAssistantContent = (value) => {
|
|
401
|
+
if (typeof value === "string") {
|
|
402
|
+
return value;
|
|
403
|
+
}
|
|
404
|
+
if (Array.isArray(value)) {
|
|
405
|
+
return value.map((item) => {
|
|
406
|
+
if (typeof item === "string") {
|
|
407
|
+
return item;
|
|
408
|
+
}
|
|
409
|
+
if (!item || typeof item !== "object") {
|
|
410
|
+
return "";
|
|
411
|
+
}
|
|
412
|
+
const record = item;
|
|
413
|
+
if (record.type === "text" && typeof record.text === "string") {
|
|
414
|
+
return record.text;
|
|
415
|
+
}
|
|
416
|
+
return "";
|
|
417
|
+
}).filter(Boolean).join("\n");
|
|
418
|
+
}
|
|
419
|
+
return "";
|
|
420
|
+
};
|
|
421
|
+
var requestAgentDiff = async (userName, messages) => {
|
|
422
|
+
const settings = await (0, import_userSettings.getUserSettings)(userName);
|
|
423
|
+
if (!settings.AI_API_KEY || !settings.AI_API_ENDPOINT) {
|
|
424
|
+
throw new Error(`AI settings are incomplete for user ${userName}`);
|
|
425
|
+
}
|
|
426
|
+
if (!isOpenRouterEndpoint(settings.AI_API_ENDPOINT)) {
|
|
427
|
+
throw new Error(
|
|
428
|
+
`Research agent requires OpenRouter endpoint, got: ${settings.AI_API_ENDPOINT}`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
const response = await fetch(
|
|
432
|
+
resolveChatCompletionsUrl(settings.AI_API_ENDPOINT),
|
|
433
|
+
{
|
|
434
|
+
method: "POST",
|
|
435
|
+
headers: {
|
|
436
|
+
Authorization: `Bearer ${settings.AI_API_KEY}`,
|
|
437
|
+
"Content-Type": "application/json",
|
|
438
|
+
"HTTP-Referer": "https://tradejs.dev",
|
|
439
|
+
"X-Title": "TradeJS Research Agent"
|
|
440
|
+
},
|
|
441
|
+
body: JSON.stringify({
|
|
442
|
+
model: RESEARCH_AGENT_MODEL,
|
|
443
|
+
messages,
|
|
444
|
+
reasoning: {
|
|
445
|
+
effort: RESEARCH_AGENT_REASONING_EFFORT,
|
|
446
|
+
exclude: true
|
|
447
|
+
},
|
|
448
|
+
temperature: 0.2
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
);
|
|
452
|
+
if (!response.ok) {
|
|
453
|
+
throw new Error(
|
|
454
|
+
`OpenRouter request failed (${response.status}): ${await response.text()}`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
const payload = await response.json();
|
|
458
|
+
const content = extractAssistantContent(
|
|
459
|
+
payload.choices?.[0]?.message?.content
|
|
460
|
+
);
|
|
461
|
+
const normalized = normalizeDiffOutput(content);
|
|
462
|
+
if (!normalized) {
|
|
463
|
+
throw new Error("Research agent returned empty content");
|
|
464
|
+
}
|
|
465
|
+
return normalized;
|
|
466
|
+
};
|
|
467
|
+
var applyDiff = async (worktreePath, diffText) => {
|
|
468
|
+
const tempDir = await import_promises.default.mkdtemp(import_node_path2.default.join(import_node_os.default.tmpdir(), "tradejs-agent-"));
|
|
469
|
+
const patchPath = import_node_path2.default.join(tempDir, "agent.patch");
|
|
470
|
+
try {
|
|
471
|
+
await import_promises.default.writeFile(patchPath, diffText, "utf8");
|
|
472
|
+
await runCommandOrThrow(
|
|
473
|
+
"git",
|
|
474
|
+
[
|
|
475
|
+
"-C",
|
|
476
|
+
worktreePath,
|
|
477
|
+
"apply",
|
|
478
|
+
"--index",
|
|
479
|
+
"--whitespace=nowarn",
|
|
480
|
+
patchPath
|
|
481
|
+
],
|
|
482
|
+
{ cwd: worktreePath }
|
|
483
|
+
);
|
|
484
|
+
} finally {
|
|
485
|
+
await import_promises.default.rm(tempDir, { recursive: true, force: true });
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
var getChangedFilesFromGit = async (worktreePath) => {
|
|
489
|
+
const result = await runCommandOrThrow(
|
|
490
|
+
"git",
|
|
491
|
+
["-C", worktreePath, "status", "--short"],
|
|
492
|
+
{ cwd: worktreePath }
|
|
493
|
+
);
|
|
494
|
+
return result.stdout.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => line.slice(3).trim());
|
|
495
|
+
};
|
|
496
|
+
var runValidationSuite = async (worktreePath, agentRun) => {
|
|
497
|
+
const commands = [
|
|
498
|
+
["prettify", ["yarn", "prettify"]],
|
|
499
|
+
["typecheck", ["yarn", "typecheck"]],
|
|
500
|
+
["unit", ["yarn", "unit"]]
|
|
501
|
+
];
|
|
502
|
+
agentRun.validation = {
|
|
503
|
+
prettify: "pending",
|
|
504
|
+
typecheck: "pending",
|
|
505
|
+
unit: "pending"
|
|
506
|
+
};
|
|
507
|
+
for (const [key, command] of commands) {
|
|
508
|
+
const result = await runCommand(command[0], command.slice(1), {
|
|
509
|
+
cwd: worktreePath,
|
|
510
|
+
env: {
|
|
511
|
+
PROJECT_CWD: worktreePath,
|
|
512
|
+
DOTENV_CONFIG_PATH: process.env.DOTENV_CONFIG_PATH
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
if (result.exitCode !== 0) {
|
|
516
|
+
agentRun.validation[key] = "failed";
|
|
517
|
+
throw new Error(
|
|
518
|
+
`${key} failed: ${trimTextTail(result.stderr || result.stdout, 2e3)}`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
agentRun.validation[key] = "passed";
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
var buildAgentTelegramReport = (agentRun) => {
|
|
525
|
+
const lines = [
|
|
526
|
+
`<b>Research agent: ${escapeHtml(agentRun.strategy)}</b>`,
|
|
527
|
+
`Status: <b>${escapeHtml(agentRun.status)}</b>`,
|
|
528
|
+
`Run: <code>${escapeHtml(agentRun.runId)}</code>`,
|
|
529
|
+
`Model: <code>${escapeHtml(agentRun.model || RESEARCH_AGENT_MODEL)}</code>`,
|
|
530
|
+
`Reasoning: <code>${escapeHtml(agentRun.reasoningEffort || RESEARCH_AGENT_REASONING_EFFORT)}</code>`
|
|
531
|
+
];
|
|
532
|
+
if (agentRun.branchName) {
|
|
533
|
+
lines.push(`Branch: <code>${escapeHtml(agentRun.branchName)}</code>`);
|
|
534
|
+
}
|
|
535
|
+
if (agentRun.commitHash) {
|
|
536
|
+
lines.push(`Commit: <code>${escapeHtml(agentRun.commitHash)}</code>`);
|
|
537
|
+
}
|
|
538
|
+
if (agentRun.commitMessage) {
|
|
539
|
+
lines.push(
|
|
540
|
+
`Commit message: <code>${escapeHtml(agentRun.commitMessage)}</code>`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
if (typeof agentRun.pullRequestNumber === "number") {
|
|
544
|
+
lines.push(`PR: <code>#${agentRun.pullRequestNumber}</code>`);
|
|
545
|
+
}
|
|
546
|
+
if (agentRun.pullRequestUrl) {
|
|
547
|
+
lines.push(`PR URL: <code>${escapeHtml(agentRun.pullRequestUrl)}</code>`);
|
|
548
|
+
}
|
|
549
|
+
if (agentRun.changedFiles?.length) {
|
|
550
|
+
lines.push(
|
|
551
|
+
`Changed files: <code>${escapeHtml(agentRun.changedFiles.join(", "))}</code>`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
if (agentRun.summary) {
|
|
555
|
+
lines.push(`Summary: <code>${escapeHtml(agentRun.summary)}</code>`);
|
|
556
|
+
}
|
|
557
|
+
if (agentRun.validation) {
|
|
558
|
+
lines.push(
|
|
559
|
+
`Validation: <code>${escapeHtml(
|
|
560
|
+
Object.entries(agentRun.validation).map(([key, value]) => `${key}=${value}`).join(", ")
|
|
561
|
+
)}</code>`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
if (agentRun.error) {
|
|
565
|
+
lines.push("");
|
|
566
|
+
lines.push("<b>Error</b>");
|
|
567
|
+
lines.push(`<code>${escapeHtml(agentRun.error)}</code>`);
|
|
568
|
+
}
|
|
569
|
+
return lines.join("\n");
|
|
570
|
+
};
|
|
571
|
+
var loadRun = async (userName, runId, strategy) => {
|
|
572
|
+
if (runId) {
|
|
573
|
+
const record = await (0, import_redis.getData)(
|
|
574
|
+
import_redis.redisKeys.researchRun(userName, runId),
|
|
575
|
+
null
|
|
576
|
+
);
|
|
577
|
+
if (record) {
|
|
578
|
+
return record;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (strategy) {
|
|
582
|
+
const latest = await (0, import_redis.getData)(
|
|
583
|
+
import_redis.redisKeys.researchLatestRun(userName, strategy),
|
|
584
|
+
null
|
|
585
|
+
);
|
|
586
|
+
if (latest) {
|
|
587
|
+
return latest;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
throw new Error(
|
|
591
|
+
`Research run not found for user=${userName}, runId=${runId || "n/a"}, strategy=${strategy || "n/a"}`
|
|
592
|
+
);
|
|
593
|
+
};
|
|
594
|
+
var saveAgentRun = async (userName, runId, strategy, agentRun) => {
|
|
595
|
+
const run = await (0, import_redis.getData)(
|
|
596
|
+
import_redis.redisKeys.researchRun(userName, runId),
|
|
597
|
+
null
|
|
598
|
+
);
|
|
599
|
+
if (!run) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
run.artifacts = {
|
|
603
|
+
...run.artifacts,
|
|
604
|
+
agentRun
|
|
605
|
+
};
|
|
606
|
+
await (0, import_redis.setData)(import_redis.redisKeys.researchRun(userName, runId), run, { expire: 0 });
|
|
607
|
+
await (0, import_redis.setData)(import_redis.redisKeys.researchLatestRun(userName, strategy), run, {
|
|
608
|
+
expire: 0
|
|
609
|
+
});
|
|
610
|
+
};
|
|
611
|
+
var resolveGithubRepository = async (worktreePath) => {
|
|
612
|
+
if (process.env.AGENT_GITHUB_REPOSITORY?.trim()) {
|
|
613
|
+
return process.env.AGENT_GITHUB_REPOSITORY.trim();
|
|
614
|
+
}
|
|
615
|
+
const remote = await runCommandOrThrow(
|
|
616
|
+
"git",
|
|
617
|
+
["-C", worktreePath, "remote", "get-url", "origin"],
|
|
618
|
+
{ cwd: worktreePath }
|
|
619
|
+
);
|
|
620
|
+
const repository = parseGithubRepositoryFromRemote(remote.stdout.trim());
|
|
621
|
+
if (!repository) {
|
|
622
|
+
throw new Error(`Unable to resolve GitHub repository from origin remote`);
|
|
623
|
+
}
|
|
624
|
+
return repository;
|
|
625
|
+
};
|
|
626
|
+
var createGithubPullRequest = async (params) => {
|
|
627
|
+
const token = process.env.AGENT_GITHUB_TOKEN?.trim() || process.env.GITHUB_TOKEN?.trim();
|
|
628
|
+
if (!token) {
|
|
629
|
+
throw new Error("AGENT_GITHUB_TOKEN is required to create pull requests");
|
|
630
|
+
}
|
|
631
|
+
const repository = await resolveGithubRepository(params.worktreePath);
|
|
632
|
+
const [owner, repo] = repository.split("/");
|
|
633
|
+
if (!owner || !repo) {
|
|
634
|
+
throw new Error(`Invalid GitHub repository value: ${repository}`);
|
|
635
|
+
}
|
|
636
|
+
const baseBranch = process.env.AGENT_GITHUB_BASE_BRANCH?.trim() || "stable";
|
|
637
|
+
const head = `${owner}:${params.branchName}`;
|
|
638
|
+
const headers = {
|
|
639
|
+
Authorization: `Bearer ${token}`,
|
|
640
|
+
Accept: "application/vnd.github+json",
|
|
641
|
+
"Content-Type": "application/json",
|
|
642
|
+
"User-Agent": "TradeJS-Research-Agent"
|
|
643
|
+
};
|
|
644
|
+
const existingResponse = await fetch(
|
|
645
|
+
`https://api.github.com/repos/${owner}/${repo}/pulls?state=open&head=${encodeURIComponent(
|
|
646
|
+
head
|
|
647
|
+
)}&base=${encodeURIComponent(baseBranch)}`,
|
|
648
|
+
{ headers }
|
|
649
|
+
);
|
|
650
|
+
if (!existingResponse.ok) {
|
|
651
|
+
throw new Error(
|
|
652
|
+
`GitHub PR lookup failed (${existingResponse.status}): ${await existingResponse.text()}`
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
const existing = await existingResponse.json();
|
|
656
|
+
if (existing[0]) {
|
|
657
|
+
return {
|
|
658
|
+
number: existing[0].number,
|
|
659
|
+
url: existing[0].html_url,
|
|
660
|
+
title: existing[0].title,
|
|
661
|
+
reused: true
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
const response = await fetch(
|
|
665
|
+
`https://api.github.com/repos/${owner}/${repo}/pulls`,
|
|
666
|
+
{
|
|
667
|
+
method: "POST",
|
|
668
|
+
headers,
|
|
669
|
+
body: JSON.stringify({
|
|
670
|
+
title: params.title,
|
|
671
|
+
head: params.branchName,
|
|
672
|
+
base: baseBranch,
|
|
673
|
+
body: params.body
|
|
674
|
+
})
|
|
675
|
+
}
|
|
676
|
+
);
|
|
677
|
+
if (!response.ok) {
|
|
678
|
+
throw new Error(
|
|
679
|
+
`GitHub PR create failed (${response.status}): ${await response.text()}`
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
const payload = await response.json();
|
|
683
|
+
return {
|
|
684
|
+
number: payload.number,
|
|
685
|
+
url: payload.html_url,
|
|
686
|
+
title: payload.title,
|
|
687
|
+
reused: false
|
|
688
|
+
};
|
|
689
|
+
};
|
|
690
|
+
var createWorktree = async (branchName) => {
|
|
691
|
+
const worktreeToken = branchName.replace(/[/:]+/g, "--");
|
|
692
|
+
const worktreeRoot = import_node_path2.default.join(projectRoot, ".agent-worktrees");
|
|
693
|
+
const worktreePath = import_node_path2.default.join(worktreeRoot, worktreeToken);
|
|
694
|
+
await import_promises.default.mkdir(worktreeRoot, { recursive: true });
|
|
695
|
+
await runCommand("git", ["worktree", "remove", "--force", worktreePath], {
|
|
696
|
+
cwd: projectRoot
|
|
697
|
+
});
|
|
698
|
+
await runCommandOrThrow("git", ["fetch", "origin", "stable"], {
|
|
699
|
+
cwd: projectRoot
|
|
700
|
+
});
|
|
701
|
+
await runCommandOrThrow(
|
|
702
|
+
"git",
|
|
703
|
+
["worktree", "add", "-B", branchName, worktreePath, "origin/stable"],
|
|
704
|
+
{
|
|
705
|
+
cwd: projectRoot
|
|
706
|
+
}
|
|
707
|
+
);
|
|
708
|
+
return worktreePath;
|
|
709
|
+
};
|
|
710
|
+
var cleanupWorktree = async (worktreePath) => {
|
|
711
|
+
await runCommand("git", ["worktree", "remove", "--force", worktreePath], {
|
|
712
|
+
cwd: projectRoot
|
|
713
|
+
});
|
|
714
|
+
};
|
|
715
|
+
var main = async () => {
|
|
716
|
+
const userName = String(flags.user || "root").trim() || "root";
|
|
717
|
+
const requestedRunId = String(flags.runId || "").trim();
|
|
718
|
+
const requestedStrategy = String(flags.strategy || "").trim();
|
|
719
|
+
const jsonOutput = Boolean(flags.json);
|
|
720
|
+
const run = await loadRun(userName, requestedRunId, requestedStrategy);
|
|
721
|
+
const branchName = buildResearchAgentBranchName(run.strategy, run.runId);
|
|
722
|
+
const commitMessage = buildResearchAgentCommitMessage(
|
|
723
|
+
run.strategy,
|
|
724
|
+
run.runId
|
|
725
|
+
);
|
|
726
|
+
const prTitle = buildResearchAgentPrTitle(run.strategy, run.runId);
|
|
727
|
+
const allowedPrefixes = getResearchAgentAllowedPathPrefixes(run.strategy);
|
|
728
|
+
const agentRun = {
|
|
729
|
+
status: "running",
|
|
730
|
+
strategy: run.strategy,
|
|
731
|
+
runId: run.runId,
|
|
732
|
+
branchName,
|
|
733
|
+
commitMessage,
|
|
734
|
+
pullRequestTitle: prTitle,
|
|
735
|
+
model: RESEARCH_AGENT_MODEL,
|
|
736
|
+
reasoningEffort: RESEARCH_AGENT_REASONING_EFFORT,
|
|
737
|
+
summary: "Awaiting model patch",
|
|
738
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
739
|
+
};
|
|
740
|
+
let worktreePath = "";
|
|
741
|
+
await saveAgentRun(userName, run.runId, run.strategy, agentRun);
|
|
742
|
+
try {
|
|
743
|
+
worktreePath = await createWorktree(branchName);
|
|
744
|
+
const userPrompt = await buildPrompt(worktreePath, run, allowedPrefixes);
|
|
745
|
+
const messages = [
|
|
746
|
+
{ role: "system", content: AGENT_SYSTEM_PROMPT },
|
|
747
|
+
{ role: "user", content: userPrompt }
|
|
748
|
+
];
|
|
749
|
+
let diffText = "";
|
|
750
|
+
let changedFiles = [];
|
|
751
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
752
|
+
diffText = await requestAgentDiff(userName, messages);
|
|
753
|
+
if (diffText === "NO_CHANGES") {
|
|
754
|
+
agentRun.status = "no_changes";
|
|
755
|
+
agentRun.summary = "Model decided not to change code or notes";
|
|
756
|
+
agentRun.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
757
|
+
await saveAgentRun(userName, run.runId, run.strategy, agentRun);
|
|
758
|
+
await sendTelegramReport(buildAgentTelegramReport(agentRun), {
|
|
759
|
+
userName
|
|
760
|
+
});
|
|
761
|
+
if (jsonOutput) {
|
|
762
|
+
console.log(JSON.stringify(agentRun));
|
|
763
|
+
} else {
|
|
764
|
+
console.log(import_chalk.default.yellow(agentRun.summary));
|
|
765
|
+
}
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
changedFiles = assertDiffAllowed(diffText, allowedPrefixes);
|
|
769
|
+
try {
|
|
770
|
+
await applyDiff(worktreePath, diffText);
|
|
771
|
+
break;
|
|
772
|
+
} catch (error) {
|
|
773
|
+
if (attempt >= 2) {
|
|
774
|
+
throw error;
|
|
775
|
+
}
|
|
776
|
+
messages.push({ role: "assistant", content: diffText });
|
|
777
|
+
messages.push({
|
|
778
|
+
role: "user",
|
|
779
|
+
content: `The previous unified diff failed to apply.
|
|
780
|
+
Error:
|
|
781
|
+
${String(
|
|
782
|
+
error?.message || error
|
|
783
|
+
)}
|
|
784
|
+
|
|
785
|
+
Return a corrected unified diff only. Keep the same allowed paths.`
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
agentRun.summary = `Applied model diff touching ${changedFiles.length} file(s)`;
|
|
790
|
+
await runValidationSuite(worktreePath, agentRun);
|
|
791
|
+
const effectiveChangedFiles = await getChangedFilesFromGit(worktreePath);
|
|
792
|
+
if (!effectiveChangedFiles.length) {
|
|
793
|
+
agentRun.status = "no_changes";
|
|
794
|
+
agentRun.summary = "Patch applied cleanly but produced no working tree changes";
|
|
795
|
+
agentRun.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
796
|
+
await saveAgentRun(userName, run.runId, run.strategy, agentRun);
|
|
797
|
+
await sendTelegramReport(buildAgentTelegramReport(agentRun), {
|
|
798
|
+
userName
|
|
799
|
+
});
|
|
800
|
+
if (jsonOutput) {
|
|
801
|
+
console.log(JSON.stringify(agentRun));
|
|
802
|
+
} else {
|
|
803
|
+
console.log(import_chalk.default.yellow(agentRun.summary));
|
|
804
|
+
}
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
await runCommandOrThrow("git", ["-C", worktreePath, "add", "."], {
|
|
808
|
+
cwd: worktreePath
|
|
809
|
+
});
|
|
810
|
+
await runCommandOrThrow(
|
|
811
|
+
"git",
|
|
812
|
+
["-C", worktreePath, "commit", "-m", commitMessage],
|
|
813
|
+
{
|
|
814
|
+
cwd: worktreePath,
|
|
815
|
+
env: {
|
|
816
|
+
GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME || "TradeJS Research Agent",
|
|
817
|
+
GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL || "research-agent@tradejs.dev",
|
|
818
|
+
GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME || "TradeJS Research Agent",
|
|
819
|
+
GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL || "research-agent@tradejs.dev"
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
);
|
|
823
|
+
await runCommandOrThrow(
|
|
824
|
+
"git",
|
|
825
|
+
["-C", worktreePath, "push", "-u", "origin", branchName],
|
|
826
|
+
{ cwd: worktreePath }
|
|
827
|
+
);
|
|
828
|
+
const commitHash = (await runCommandOrThrow(
|
|
829
|
+
"git",
|
|
830
|
+
["-C", worktreePath, "rev-parse", "HEAD"],
|
|
831
|
+
{
|
|
832
|
+
cwd: worktreePath
|
|
833
|
+
}
|
|
834
|
+
)).stdout.trim();
|
|
835
|
+
agentRun.status = "completed";
|
|
836
|
+
agentRun.commitHash = commitHash;
|
|
837
|
+
agentRun.changedFiles = effectiveChangedFiles;
|
|
838
|
+
agentRun.summary = `Committed validated patch on branch ${branchName}`;
|
|
839
|
+
const prBody = buildResearchAgentPrBody({
|
|
840
|
+
strategy: run.strategy,
|
|
841
|
+
runId: run.runId,
|
|
842
|
+
config: run.config,
|
|
843
|
+
connector: run.connector,
|
|
844
|
+
timeframe: run.timeframe,
|
|
845
|
+
days: run.days,
|
|
846
|
+
recent: run.recent,
|
|
847
|
+
changedFiles: effectiveChangedFiles,
|
|
848
|
+
validation: agentRun.validation,
|
|
849
|
+
summary: agentRun.summary,
|
|
850
|
+
aiTrainLocal: run.artifacts.aiTrainLocal ?? null
|
|
851
|
+
});
|
|
852
|
+
const pr = await createGithubPullRequest({
|
|
853
|
+
worktreePath,
|
|
854
|
+
branchName,
|
|
855
|
+
title: prTitle,
|
|
856
|
+
body: prBody
|
|
857
|
+
});
|
|
858
|
+
agentRun.pullRequestNumber = pr.number;
|
|
859
|
+
agentRun.pullRequestUrl = pr.url;
|
|
860
|
+
agentRun.pullRequestTitle = pr.title;
|
|
861
|
+
agentRun.summary = pr.reused ? `Committed validated patch on branch ${branchName}; reused PR #${pr.number}` : `Committed validated patch on branch ${branchName}; created PR #${pr.number}`;
|
|
862
|
+
agentRun.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
863
|
+
await saveAgentRun(userName, run.runId, run.strategy, agentRun);
|
|
864
|
+
await sendTelegramReport(buildAgentTelegramReport(agentRun), {
|
|
865
|
+
userName
|
|
866
|
+
});
|
|
867
|
+
if (jsonOutput) {
|
|
868
|
+
console.log(JSON.stringify(agentRun));
|
|
869
|
+
} else {
|
|
870
|
+
console.log(import_chalk.default.green(agentRun.summary));
|
|
871
|
+
console.log(import_chalk.default.gray(`branch=${branchName}`));
|
|
872
|
+
console.log(import_chalk.default.gray(`commit=${commitHash}`));
|
|
873
|
+
if (agentRun.pullRequestUrl) {
|
|
874
|
+
console.log(import_chalk.default.gray(`pullRequest=${agentRun.pullRequestUrl}`));
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
} catch (error) {
|
|
878
|
+
agentRun.status = "failed";
|
|
879
|
+
agentRun.error = error?.message || String(error);
|
|
880
|
+
agentRun.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
881
|
+
await saveAgentRun(userName, run.runId, run.strategy, agentRun);
|
|
882
|
+
try {
|
|
883
|
+
await sendTelegramReport(buildAgentTelegramReport(agentRun), {
|
|
884
|
+
userName
|
|
885
|
+
});
|
|
886
|
+
} catch {
|
|
887
|
+
}
|
|
888
|
+
if (jsonOutput) {
|
|
889
|
+
console.log(JSON.stringify(agentRun));
|
|
890
|
+
} else {
|
|
891
|
+
console.error(import_chalk.default.red(agentRun.error));
|
|
892
|
+
}
|
|
893
|
+
process.exit(1);
|
|
894
|
+
} finally {
|
|
895
|
+
if (worktreePath) {
|
|
896
|
+
await cleanupWorktree(worktreePath);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
if (require.main === module) {
|
|
901
|
+
main().catch((error) => {
|
|
902
|
+
console.error(import_chalk.default.red(error?.message || String(error)));
|
|
903
|
+
process.exit(1);
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
907
|
+
0 && (module.exports = {
|
|
908
|
+
main
|
|
909
|
+
});
|