horizon-code 0.3.2 → 0.5.0
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/package.json +1 -1
- package/src/ai/client.ts +2 -2
- package/src/app.ts +25 -10
- package/src/components/code-panel.ts +2 -2
- package/src/components/footer.ts +0 -1
- package/src/components/input-bar.ts +0 -2
- package/src/components/tab-bar.ts +0 -1
- package/src/platform/supabase.ts +7 -3
- package/src/strategy/dashboard.ts +459 -217
- package/src/strategy/prompts.ts +212 -13
- package/src/strategy/tools.ts +211 -23
- package/src/strategy/validator.ts +25 -1
- package/src/strategy/workspace.ts +175 -0
- package/src/syntax/setup.ts +22 -4
package/package.json
CHANGED
package/src/ai/client.ts
CHANGED
|
@@ -242,7 +242,7 @@ export async function* chat(
|
|
|
242
242
|
const serverMessages = messages.map((m) => ({ role: m.role, content: m.content }));
|
|
243
243
|
|
|
244
244
|
let step = 0;
|
|
245
|
-
const MAX_STEPS =
|
|
245
|
+
const MAX_STEPS = 15;
|
|
246
246
|
|
|
247
247
|
while (step < MAX_STEPS) {
|
|
248
248
|
step++;
|
|
@@ -379,7 +379,7 @@ export async function* chat(
|
|
|
379
379
|
// Add as assistant message — the LLM sees it already ran the tools
|
|
380
380
|
serverMessages.push({ role: "assistant", content: assistantParts.join("\n\n") });
|
|
381
381
|
// Prompt the LLM to continue with its response based on the tool results
|
|
382
|
-
serverMessages.push({ role: "user", content:
|
|
382
|
+
serverMessages.push({ role: "user", content: `Now respond to the user based on the tool results above. Do NOT call the same tools again. [Step ${step}/${MAX_STEPS} used]` });
|
|
383
383
|
|
|
384
384
|
// Reset structured output state for next turn
|
|
385
385
|
if (structuredActive) {
|
package/src/app.ts
CHANGED
|
@@ -456,8 +456,8 @@ export class App {
|
|
|
456
456
|
if (m.proc.exitCode === null) {
|
|
457
457
|
alive++;
|
|
458
458
|
// Filter out __HZ_METRICS__ lines from visible logs
|
|
459
|
-
const stdoutLines = m.stdout.slice(-
|
|
460
|
-
const stderrLines = m.stderr.slice(-
|
|
459
|
+
const stdoutLines = m.stdout.slice(-100);
|
|
460
|
+
const stderrLines = m.stderr.slice(-50).filter((l: string) => !l.startsWith("__HZ_METRICS__"));
|
|
461
461
|
latestLogs = stdoutLines.join("\n") + (stderrLines.length > 0 ? "\n--- stderr ---\n" + stderrLines.join("\n") : "");
|
|
462
462
|
|
|
463
463
|
// Parse latest metrics from this process
|
|
@@ -466,8 +466,22 @@ export class App {
|
|
|
466
466
|
localMetricsData = metrics;
|
|
467
467
|
localProcessStartedAt = m.startedAt;
|
|
468
468
|
}
|
|
469
|
-
} else
|
|
470
|
-
|
|
469
|
+
} else {
|
|
470
|
+
// Process exited — check if it crashed (only notify once)
|
|
471
|
+
const exitCode = m.proc.exitCode;
|
|
472
|
+
if (exitCode !== null && exitCode !== 0 && !(m as any)._crashNotified) {
|
|
473
|
+
(m as any)._crashNotified = true;
|
|
474
|
+
const lastStderr = m.stderr.slice(-5).join("\n");
|
|
475
|
+
const crashMsg = `[CRASH] Process ${pid} exited with code ${exitCode}`;
|
|
476
|
+
this.codePanel.appendLog(crashMsg);
|
|
477
|
+
if (lastStderr) {
|
|
478
|
+
this.codePanel.appendLog(lastStderr);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Clean up dead processes after 5 minutes
|
|
482
|
+
if (now - m.startedAt > 300000) {
|
|
483
|
+
deadPids.push(pid);
|
|
484
|
+
}
|
|
471
485
|
}
|
|
472
486
|
}
|
|
473
487
|
for (const pid of deadPids) runningProcesses.delete(pid);
|
|
@@ -485,13 +499,13 @@ export class App {
|
|
|
485
499
|
total_pnl: localMetricsData.pnl,
|
|
486
500
|
realized_pnl: localMetricsData.rpnl,
|
|
487
501
|
unrealized_pnl: localMetricsData.upnl,
|
|
488
|
-
total_exposure: 0,
|
|
502
|
+
total_exposure: localMetricsData.exposure ?? 0,
|
|
489
503
|
position_count: localMetricsData.positions,
|
|
490
504
|
open_order_count: localMetricsData.orders,
|
|
491
|
-
win_rate: 0,
|
|
492
|
-
total_trades: 0,
|
|
493
|
-
max_drawdown_pct: 0,
|
|
494
|
-
sharpe_ratio: 0,
|
|
505
|
+
win_rate: localMetricsData.win_rate ?? 0,
|
|
506
|
+
total_trades: localMetricsData.trades ?? 0,
|
|
507
|
+
max_drawdown_pct: localMetricsData.max_dd ?? 0,
|
|
508
|
+
sharpe_ratio: localMetricsData.sharpe ?? 0,
|
|
495
509
|
profit_factor: 0,
|
|
496
510
|
avg_return_per_trade: 0,
|
|
497
511
|
gross_profit: 0,
|
|
@@ -907,7 +921,8 @@ export class App {
|
|
|
907
921
|
this.authenticated = loggedIn;
|
|
908
922
|
|
|
909
923
|
const cfg = loadConfig();
|
|
910
|
-
const
|
|
924
|
+
const rawEmail = cfg.user_email || getUser()?.email;
|
|
925
|
+
const email = typeof rawEmail === "string" ? rawEmail : undefined;
|
|
911
926
|
|
|
912
927
|
if (!loggedIn) {
|
|
913
928
|
this.splash.setAuthStatus(false);
|
|
@@ -339,9 +339,9 @@ export class CodePanel {
|
|
|
339
339
|
|
|
340
340
|
appendLog(line: string): void {
|
|
341
341
|
this._logs += (this._logs ? "\n" : "") + line;
|
|
342
|
-
// Keep last
|
|
342
|
+
// Keep last 1000 lines
|
|
343
343
|
const lines = this._logs.split("\n");
|
|
344
|
-
if (lines.length >
|
|
344
|
+
if (lines.length > 1000) this._logs = lines.slice(-1000).join("\n");
|
|
345
345
|
if (this._activeTab === "logs") this.updateLogsContent();
|
|
346
346
|
}
|
|
347
347
|
|
package/src/components/footer.ts
CHANGED
|
@@ -31,7 +31,6 @@ export class InputBar {
|
|
|
31
31
|
width: "100%",
|
|
32
32
|
flexDirection: "column",
|
|
33
33
|
paddingLeft: 4,
|
|
34
|
-
backgroundColor: COLORS.bgSecondary,
|
|
35
34
|
});
|
|
36
35
|
this.acBox.visible = false;
|
|
37
36
|
this.container.add(this.acBox);
|
|
@@ -52,7 +51,6 @@ export class InputBar {
|
|
|
52
51
|
width: "100%",
|
|
53
52
|
flexDirection: "row",
|
|
54
53
|
alignItems: "center",
|
|
55
|
-
backgroundColor: COLORS.bgDarker,
|
|
56
54
|
paddingLeft: 2,
|
|
57
55
|
});
|
|
58
56
|
this.container.add(inputRow);
|
package/src/platform/supabase.ts
CHANGED
|
@@ -214,7 +214,10 @@ export async function loginWithBrowser(): Promise<{ success: boolean; error?: st
|
|
|
214
214
|
// Save everything
|
|
215
215
|
const config = loadConfig();
|
|
216
216
|
saveSessionTokens(config, sd.session.access_token, sd.session.refresh_token);
|
|
217
|
-
|
|
217
|
+
// Defensively extract email — server might return string or object
|
|
218
|
+
const rawEmail = data.email ?? sd.session.user.email ?? sd.session.user?.user_metadata?.email;
|
|
219
|
+
const email = typeof rawEmail === "string" ? rawEmail : typeof rawEmail === "object" && rawEmail !== null ? (rawEmail as any).address ?? (rawEmail as any).email ?? String(rawEmail) : "";
|
|
220
|
+
config.user_email = email;
|
|
218
221
|
config.user_id = sd.session.user.id;
|
|
219
222
|
|
|
220
223
|
if (!config.api_key) {
|
|
@@ -227,7 +230,7 @@ export async function loginWithBrowser(): Promise<{ success: boolean; error?: st
|
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
saveConfig(config);
|
|
230
|
-
return { success: true, email
|
|
233
|
+
return { success: true, email };
|
|
231
234
|
}
|
|
232
235
|
}
|
|
233
236
|
if (res.status === 410) return { success: false, error: "Session expired. Type /login to sign in again." };
|
|
@@ -249,7 +252,8 @@ export async function loginWithPassword(email: string, password: string): Promis
|
|
|
249
252
|
|
|
250
253
|
const config = loadConfig();
|
|
251
254
|
saveSessionTokens(config, data.session.access_token, data.session.refresh_token);
|
|
252
|
-
|
|
255
|
+
const userEmail = data.session.user.email;
|
|
256
|
+
config.user_email = typeof userEmail === "string" ? userEmail : email;
|
|
253
257
|
config.user_id = data.session.user.id;
|
|
254
258
|
|
|
255
259
|
if (!config.api_key) {
|