horizon-code 0.3.3 → 0.5.1
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 +8 -2
- package/src/ai/system-prompt.ts +1 -0
- package/src/app.ts +23 -9
- package/src/components/code-panel.ts +2 -2
- package/src/strategy/dashboard.ts +459 -217
- package/src/strategy/prompts.ts +213 -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++;
|
|
@@ -335,6 +335,12 @@ export async function* chat(
|
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
// If structured output was active but we never extracted a "response" field,
|
|
339
|
+
// the model likely responded with plain text — flush jsonBuf as text
|
|
340
|
+
if (structuredActive && jsonBuf && emittedResponseLen === 0) {
|
|
341
|
+
yield { type: "text-delta", textDelta: jsonBuf };
|
|
342
|
+
}
|
|
343
|
+
|
|
338
344
|
// If no tool calls, we're done
|
|
339
345
|
if (!hasToolCalls || pendingToolCalls.length === 0) {
|
|
340
346
|
if (eventCount === 0) {
|
|
@@ -379,7 +385,7 @@ export async function* chat(
|
|
|
379
385
|
// Add as assistant message — the LLM sees it already ran the tools
|
|
380
386
|
serverMessages.push({ role: "assistant", content: assistantParts.join("\n\n") });
|
|
381
387
|
// Prompt the LLM to continue with its response based on the tool results
|
|
382
|
-
serverMessages.push({ role: "user", content:
|
|
388
|
+
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
389
|
|
|
384
390
|
// Reset structured output state for next turn
|
|
385
391
|
if (structuredActive) {
|
package/src/ai/system-prompt.ts
CHANGED
|
@@ -5,6 +5,7 @@ const BASE = `You are Horizon, an AI trading research assistant running in a CLI
|
|
|
5
5
|
|
|
6
6
|
Rules:
|
|
7
7
|
- Concise and direct. You're a terminal, not a chatbot.
|
|
8
|
+
- NEVER use emojis in your responses. No emoji characters whatsoever. Use plain text only.
|
|
8
9
|
- Tool results render as rich CLI widgets automatically — do NOT reformat the data as tables. Just add 1-2 sentences of insight after the widget.
|
|
9
10
|
- NEVER suggest switching modes.
|
|
10
11
|
- Format: $102,450 not 102450.
|
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,
|
|
@@ -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
|
|