@zhive/cli 0.6.6 → 0.6.7
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/commands/agent/commands/profile.js +0 -6
- package/dist/commands/agent/commands/profile.test.js +2 -23
- package/dist/commands/create/presets/index.js +1 -1
- package/dist/commands/create/presets/options.js +18 -15
- package/dist/commands/create/ui/steps/IdentityStep.js +3 -2
- package/dist/commands/doctor/commands/index.js +5 -11
- package/dist/commands/megathread/commands/create-comment.js +2 -7
- package/dist/commands/megathread/commands/create-comment.test.js +3 -30
- package/dist/commands/megathread/commands/create-comments.js +2 -7
- package/dist/commands/megathread/commands/list.js +5 -10
- package/dist/commands/megathread/commands/list.test.js +3 -21
- package/dist/commands/migrate-templates/ui/MigrateApp.js +1 -1
- package/dist/commands/start/commands/prediction.js +1 -1
- package/dist/commands/start/commands/skills.test.js +1 -2
- package/dist/components/MultiSelectPrompt.js +3 -3
- package/dist/shared/config/agent.js +4 -0
- package/dist/shared/config/agent.test.js +0 -5
- package/package.json +1 -1
- package/dist/CLAUDE.md +0 -7
- package/dist/backtest/CLAUDE.md +0 -7
- package/dist/cli.js +0 -20
- package/dist/commands/create/presets.js +0 -613
- package/dist/commands/start/ui/AsciiTicker.js +0 -81
- package/dist/services/agent/analysis.js +0 -160
- package/dist/services/agent/config.js +0 -75
- package/dist/services/agent/env.js +0 -30
- package/dist/services/agent/helpers/model.js +0 -92
- package/dist/services/agent/helpers.js +0 -22
- package/dist/services/agent/prompts/chat-prompt.js +0 -65
- package/dist/services/agent/prompts/memory-prompt.js +0 -45
- package/dist/services/agent/prompts/prompt.js +0 -379
- package/dist/services/agent/skills/index.js +0 -2
- package/dist/services/agent/skills/skill-parser.js +0 -149
- package/dist/services/agent/skills/types.js +0 -1
- package/dist/services/agent/tools/edit-section.js +0 -59
- package/dist/services/agent/tools/fetch-rules.js +0 -21
- package/dist/services/agent/tools/index.js +0 -76
- package/dist/services/agent/tools/market/client.js +0 -41
- package/dist/services/agent/tools/market/index.js +0 -3
- package/dist/services/agent/tools/market/tools.js +0 -518
- package/dist/services/agent/tools/mindshare/client.js +0 -124
- package/dist/services/agent/tools/mindshare/index.js +0 -3
- package/dist/services/agent/tools/mindshare/tools.js +0 -563
- package/dist/services/agent/tools/read-skill-tool.js +0 -30
- package/dist/services/agent/tools/ta/index.js +0 -1
- package/dist/services/agent/tools/ta/indicators.js +0 -201
- package/dist/services/agent/types.js +0 -1
- package/dist/services/ai-providers.js +0 -66
- package/dist/services/config/agent.js +0 -110
- package/dist/services/config/config.js +0 -22
- package/dist/services/config/constant.js +0 -8
- package/dist/shared/agent/agent-runtime.js +0 -144
- package/dist/shared/agent/config.js +0 -75
- package/dist/shared/agent/env.js +0 -30
- package/dist/shared/agent/helpers/model.js +0 -92
- package/dist/shared/agent/types.js +0 -1
- package/dist/shared/ai-providers.js +0 -66
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
import { animation, colors } from '../../shared/theme.js';
|
|
5
|
-
function buildTickerChars(step) {
|
|
6
|
-
const stepStr = String(step).padStart(2, '0');
|
|
7
|
-
const digits = stepStr.split('');
|
|
8
|
-
return animation.HEX_CHARS + digits.join('') + '\u25AA\u25AB\u2591\u2592';
|
|
9
|
-
}
|
|
10
|
-
function buildRow(cols, frame, rowIndex, tickerChars) {
|
|
11
|
-
const segments = [];
|
|
12
|
-
const isSecondRow = rowIndex === 1;
|
|
13
|
-
const scrollSpeed = isSecondRow ? 3 : 2;
|
|
14
|
-
const direction = isSecondRow ? -1 : 1;
|
|
15
|
-
const sinFreq = isSecondRow ? 0.4 : 0.3;
|
|
16
|
-
const sinPhase = isSecondRow ? -0.4 : 0.6;
|
|
17
|
-
const wrapLen = cols * 2;
|
|
18
|
-
for (let c = 0; c < cols; c++) {
|
|
19
|
-
const scrolledC = direction === 1
|
|
20
|
-
? (c + frame * scrollSpeed) % wrapLen
|
|
21
|
-
: (cols - c + frame * scrollSpeed) % wrapLen;
|
|
22
|
-
const charIdx = scrolledC % tickerChars.length;
|
|
23
|
-
const char = tickerChars[charIdx];
|
|
24
|
-
const isHex = char === '\u2B21' || char === '\u2B22';
|
|
25
|
-
const pulseHit = Math.sin((c + frame * sinPhase) * sinFreq) > 0.5;
|
|
26
|
-
// Edge fade: dim the outermost 4 columns
|
|
27
|
-
const edgeDist = Math.min(c, cols - 1 - c);
|
|
28
|
-
if (edgeDist < 2) {
|
|
29
|
-
segments.push({ char: '\u00B7', color: colors.grayDim });
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (edgeDist < 4) {
|
|
33
|
-
segments.push({ char, color: colors.grayDim });
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (pulseHit && isHex) {
|
|
37
|
-
segments.push({ char, color: colors.honey });
|
|
38
|
-
}
|
|
39
|
-
else if (pulseHit) {
|
|
40
|
-
segments.push({ char, color: colors.green });
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
segments.push({ char, color: colors.grayDim });
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return segments;
|
|
47
|
-
}
|
|
48
|
-
function renderSegments(segments) {
|
|
49
|
-
const elements = [];
|
|
50
|
-
let runColor = segments[0]?.color ?? colors.grayDim;
|
|
51
|
-
let runChars = '';
|
|
52
|
-
for (let i = 0; i < segments.length; i++) {
|
|
53
|
-
const seg = segments[i];
|
|
54
|
-
if (seg.color === runColor) {
|
|
55
|
-
runChars += seg.char;
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
elements.push(_jsx(Text, { color: runColor, children: runChars }, `${elements.length}`));
|
|
59
|
-
runColor = seg.color;
|
|
60
|
-
runChars = seg.char;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (runChars.length > 0) {
|
|
64
|
-
elements.push(_jsx(Text, { color: runColor, children: runChars }, `${elements.length}`));
|
|
65
|
-
}
|
|
66
|
-
return elements;
|
|
67
|
-
}
|
|
68
|
-
export function AsciiTicker({ rows = 1, step = 1 }) {
|
|
69
|
-
const [frame, setFrame] = useState(0);
|
|
70
|
-
const cols = process.stdout.columns || 60;
|
|
71
|
-
const tickerChars = buildTickerChars(step);
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
const timer = setInterval(() => {
|
|
74
|
-
setFrame((prev) => prev + 1);
|
|
75
|
-
}, animation.TICK_MS);
|
|
76
|
-
return () => {
|
|
77
|
-
clearInterval(timer);
|
|
78
|
-
};
|
|
79
|
-
}, []);
|
|
80
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: renderSegments(buildRow(cols, frame, 0, tickerChars)) }), rows === 2 && _jsx(Text, { children: renderSegments(buildRow(cols, frame, 1, tickerChars)) })] }));
|
|
81
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { generateText, Output, ToolLoopAgent } from 'ai';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { buildAnalystPrompt, buildMegathreadPrompt } from './prompts/prompt.js';
|
|
4
|
-
import { loadMemory, saveMemory, getMemoryLineCount, MEMORY_SOFT_LIMIT, } from '@hive-org/sdk';
|
|
5
|
-
import { buildMemoryExtractionPrompt } from './prompts/memory-prompt.js';
|
|
6
|
-
import { stripCodeFences } from './helpers.js';
|
|
7
|
-
import { getModel } from './helpers/model.js';
|
|
8
|
-
// ─── Cache Helpers ─────────────────────────────────
|
|
9
|
-
function cacheableSystem(content) {
|
|
10
|
-
const message = {
|
|
11
|
-
role: 'system',
|
|
12
|
-
content,
|
|
13
|
-
providerOptions: {
|
|
14
|
-
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
return message;
|
|
18
|
-
}
|
|
19
|
-
// ─── Prediction Schema ──────────────────────────────
|
|
20
|
-
export const predictionSchema = z.object({
|
|
21
|
-
skip: z
|
|
22
|
-
.boolean()
|
|
23
|
-
.describe('true if this signal is outside your expertise or you have no strong take. false if you want to comment.'),
|
|
24
|
-
summary: z
|
|
25
|
-
.string()
|
|
26
|
-
.min(1)
|
|
27
|
-
.max(2000)
|
|
28
|
-
.nullable()
|
|
29
|
-
.describe('Your CT-style take on this signal. Short, punchy, in character. Think tweet, not essay. null if skipping.'),
|
|
30
|
-
conviction: z
|
|
31
|
-
.number()
|
|
32
|
-
.nullable()
|
|
33
|
-
.describe('Predicted percent price change over the next 3 hours, up to two decimal place. Use the FULL range based on signal strength: tiny signals ±0.1-1.0, moderate ±1.5-5.0, strong ±5.0-12.0, extreme ±12.0-25.0. Negative for bearish. NEVER use 0 — always commit to a directional lean, even if small (e.g. ±0.1). null if skipping. VARY your predictions — do NOT default to the same number repeatedly.'),
|
|
34
|
-
});
|
|
35
|
-
const megathreadPredictionSchema = z.object({
|
|
36
|
-
skip: z
|
|
37
|
-
.boolean()
|
|
38
|
-
.describe('Only true if this project is outside the expertise list defined in your STRATEGY.md.'),
|
|
39
|
-
summary: z
|
|
40
|
-
.string()
|
|
41
|
-
.min(1)
|
|
42
|
-
.max(2000)
|
|
43
|
-
.nullable()
|
|
44
|
-
.describe('Your CT-style take on this project. Short, punchy, in character. Think tweet, not essay. null if skipping.'),
|
|
45
|
-
conviction: z
|
|
46
|
-
.number()
|
|
47
|
-
.nullable()
|
|
48
|
-
.describe('Predicted percent price change over the conviction window described in your instructions, up to two decimal places. Use the FULL range based on signal strength: tiny signals ±0.1-1.0, moderate ±1.5-5.0, strong ±5.0-12.0, extreme ±12.0-25.0. Negative for bearish. NEVER use 0 — always commit to a directional lean, even if small (e.g. ±0.1). null if skipping. VARY your predictions — do NOT default to the same number repeatedly.'),
|
|
49
|
-
});
|
|
50
|
-
function buildUsage(res) {
|
|
51
|
-
const toolCalls = res.steps.flatMap((s) => s.toolCalls);
|
|
52
|
-
const toolNames = toolCalls.map((tc) => tc.toolName);
|
|
53
|
-
const toolResults = res.steps
|
|
54
|
-
.flatMap((s) => s.toolResults)
|
|
55
|
-
.map((tr) => ({
|
|
56
|
-
toolName: tr.toolName,
|
|
57
|
-
result: String(tr.output),
|
|
58
|
-
}));
|
|
59
|
-
const usage = {
|
|
60
|
-
inputTokens: res.totalUsage.inputTokens ?? 0,
|
|
61
|
-
outputTokens: res.totalUsage.outputTokens ?? 0,
|
|
62
|
-
cacheReadTokens: res.totalUsage.inputTokenDetails?.cacheReadTokens ?? 0,
|
|
63
|
-
cacheWriteTokens: res.totalUsage.inputTokenDetails?.cacheWriteTokens ?? 0,
|
|
64
|
-
toolCalls: toolCalls.length,
|
|
65
|
-
toolNames,
|
|
66
|
-
toolResults,
|
|
67
|
-
};
|
|
68
|
-
return usage;
|
|
69
|
-
}
|
|
70
|
-
// ─── Signal Analysis ────────────────────────────────
|
|
71
|
-
export async function processSignalAndSummarize(thread, recentComments, memory, soulContent, strategyContent, tools = {}, availableSkills) {
|
|
72
|
-
const promptOptions = {
|
|
73
|
-
threadText: thread.text,
|
|
74
|
-
projectId: thread.project_id,
|
|
75
|
-
timestamp: thread.timestamp,
|
|
76
|
-
priceOnFetch: thread.price_on_fetch,
|
|
77
|
-
citations: thread.citations,
|
|
78
|
-
recentPosts: recentComments,
|
|
79
|
-
memory,
|
|
80
|
-
availableSkills,
|
|
81
|
-
};
|
|
82
|
-
const { system, prompt } = buildAnalystPrompt(soulContent, strategyContent, promptOptions);
|
|
83
|
-
const model = await getModel();
|
|
84
|
-
const agent = new ToolLoopAgent({
|
|
85
|
-
model,
|
|
86
|
-
instructions: cacheableSystem(system),
|
|
87
|
-
output: Output.object({ schema: predictionSchema }),
|
|
88
|
-
tools,
|
|
89
|
-
});
|
|
90
|
-
const res = await agent.generate({ prompt });
|
|
91
|
-
const usage = buildUsage(res);
|
|
92
|
-
const { output } = res;
|
|
93
|
-
if (!output) {
|
|
94
|
-
return { skip: true, summary: '', conviction: 0, usage };
|
|
95
|
-
}
|
|
96
|
-
const prediction = output;
|
|
97
|
-
const skip = prediction.skip ?? false;
|
|
98
|
-
const summary = prediction.summary ?? '';
|
|
99
|
-
const conviction = prediction.conviction ?? 0;
|
|
100
|
-
return { skip, summary, conviction, usage };
|
|
101
|
-
}
|
|
102
|
-
// ─── Megathread Round Analysis ──────────────────────
|
|
103
|
-
export async function processMegathreadRound(projectId, durationMs, recentComments, memory, soulContent, strategyContent, tools = {}, availableSkills, priceAtStart, currentPrice) {
|
|
104
|
-
const promptOptions = {
|
|
105
|
-
projectId,
|
|
106
|
-
durationMs,
|
|
107
|
-
priceAtStart,
|
|
108
|
-
currentPrice,
|
|
109
|
-
recentPosts: recentComments,
|
|
110
|
-
memory,
|
|
111
|
-
availableSkills,
|
|
112
|
-
};
|
|
113
|
-
const { system, prompt } = buildMegathreadPrompt(soulContent, strategyContent, promptOptions);
|
|
114
|
-
const model = await getModel();
|
|
115
|
-
const agent = new ToolLoopAgent({
|
|
116
|
-
model,
|
|
117
|
-
instructions: cacheableSystem(system),
|
|
118
|
-
output: Output.object({ schema: megathreadPredictionSchema }),
|
|
119
|
-
tools,
|
|
120
|
-
});
|
|
121
|
-
const res = await agent.generate({ prompt });
|
|
122
|
-
const usage = buildUsage(res);
|
|
123
|
-
const { output } = res;
|
|
124
|
-
if (!output) {
|
|
125
|
-
return { skip: true, summary: '', conviction: 0, usage };
|
|
126
|
-
}
|
|
127
|
-
const prediction = output;
|
|
128
|
-
const skip = prediction.skip ?? false;
|
|
129
|
-
const summary = prediction.summary ?? '';
|
|
130
|
-
const conviction = prediction.conviction ?? 0;
|
|
131
|
-
return { skip, summary, conviction, usage };
|
|
132
|
-
}
|
|
133
|
-
// ─── Memory Extraction ──────────────────────────────
|
|
134
|
-
export async function extractAndSaveMemory(sessionMessages) {
|
|
135
|
-
const currentMemory = await loadMemory();
|
|
136
|
-
const lineCount = getMemoryLineCount(currentMemory);
|
|
137
|
-
if (sessionMessages.length === 0 && lineCount <= MEMORY_SOFT_LIMIT) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
const prompt = buildMemoryExtractionPrompt({
|
|
141
|
-
currentMemory,
|
|
142
|
-
sessionMessages,
|
|
143
|
-
lineCount,
|
|
144
|
-
});
|
|
145
|
-
try {
|
|
146
|
-
const model = await getModel();
|
|
147
|
-
const { text } = await generateText({
|
|
148
|
-
model,
|
|
149
|
-
prompt,
|
|
150
|
-
});
|
|
151
|
-
const cleaned = stripCodeFences(text);
|
|
152
|
-
await saveMemory(cleaned);
|
|
153
|
-
return cleaned;
|
|
154
|
-
}
|
|
155
|
-
catch (err) {
|
|
156
|
-
const raw = err instanceof Error ? err.message : String(err);
|
|
157
|
-
console.error(`[Memory] Failed to extract memory: ${raw.slice(0, 200)}`);
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs/promises';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
async function loadMarkdownFile(filename) {
|
|
4
|
-
const filePath = path.join(process.cwd(), filename);
|
|
5
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
6
|
-
return content;
|
|
7
|
-
}
|
|
8
|
-
function extractField(content, pattern) {
|
|
9
|
-
const match = content.match(pattern);
|
|
10
|
-
if (match === null) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
const value = match[1].trim();
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
const VALID_SENTIMENTS = [
|
|
17
|
-
'very-bullish',
|
|
18
|
-
'bullish',
|
|
19
|
-
'neutral',
|
|
20
|
-
'bearish',
|
|
21
|
-
'very-bearish',
|
|
22
|
-
];
|
|
23
|
-
const VALID_TIMEFRAMES = ['1h', '4h', '24h'];
|
|
24
|
-
function parseSentiment(raw) {
|
|
25
|
-
if (raw !== null && VALID_SENTIMENTS.includes(raw)) {
|
|
26
|
-
return raw;
|
|
27
|
-
}
|
|
28
|
-
return 'neutral';
|
|
29
|
-
}
|
|
30
|
-
function parseSectors(raw) {
|
|
31
|
-
if (raw === null || raw.trim() === '') {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
34
|
-
const sectors = raw
|
|
35
|
-
.split(',')
|
|
36
|
-
.map((s) => s.trim())
|
|
37
|
-
.filter((s) => s.length > 0);
|
|
38
|
-
return sectors;
|
|
39
|
-
}
|
|
40
|
-
function parseTimeframes(raw) {
|
|
41
|
-
if (raw === null || raw.trim() === '') {
|
|
42
|
-
return ['1h', '4h', '24h'];
|
|
43
|
-
}
|
|
44
|
-
const parsed = raw
|
|
45
|
-
.split(',')
|
|
46
|
-
.map((t) => t.trim())
|
|
47
|
-
.filter((t) => VALID_TIMEFRAMES.includes(t));
|
|
48
|
-
if (parsed.length === 0) {
|
|
49
|
-
return ['1h', '4h', '24h'];
|
|
50
|
-
}
|
|
51
|
-
return parsed;
|
|
52
|
-
}
|
|
53
|
-
export async function loadAgentConfig() {
|
|
54
|
-
const soulContent = await loadMarkdownFile('SOUL.md');
|
|
55
|
-
const strategyContent = await loadMarkdownFile('STRATEGY.md');
|
|
56
|
-
const name = extractField(soulContent, /^#\s+Agent:\s+(.+)$/m);
|
|
57
|
-
if (name === null) {
|
|
58
|
-
throw new Error('Could not parse agent name from SOUL.md. Expected "# Agent: <name>" as the first heading.');
|
|
59
|
-
}
|
|
60
|
-
const avatarUrl = extractField(soulContent, /^## Avatar\s*\n+(https?:\/\/.+)$/m);
|
|
61
|
-
if (avatarUrl === null) {
|
|
62
|
-
throw new Error('Could not parse avatar URL from SOUL.md. Expected a valid URL under "## Avatar".');
|
|
63
|
-
}
|
|
64
|
-
const bioRaw = extractField(soulContent, /^## Bio\s*\n+(.+)$/m);
|
|
65
|
-
const bio = bioRaw ?? null;
|
|
66
|
-
const sentimentRaw = extractField(strategyContent, /^-\s+Bias:\s+(.+)$/m);
|
|
67
|
-
const sectorsRaw = extractField(strategyContent, /^-\s+Sectors:\s+(.+)$/m);
|
|
68
|
-
const timeframesRaw = extractField(strategyContent, /^-\s+Active timeframes:\s+(.+)$/m);
|
|
69
|
-
const agentProfile = {
|
|
70
|
-
sentiment: parseSentiment(sentimentRaw),
|
|
71
|
-
sectors: parseSectors(sectorsRaw),
|
|
72
|
-
timeframes: parseTimeframes(timeframesRaw),
|
|
73
|
-
};
|
|
74
|
-
return { name, bio, avatarUrl, soulContent, strategyContent, agentProfile };
|
|
75
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
2
|
-
import { AI_PROVIDER_ENV_VARS } from '../ai-providers.js';
|
|
3
|
-
let _agentProviderKeys = new Set();
|
|
4
|
-
/**
|
|
5
|
-
* Provider env-var names declared in the agent's .env file.
|
|
6
|
-
* Used by getModel() to prioritize the agent's chosen provider
|
|
7
|
-
* over keys inherited from the shell.
|
|
8
|
-
*/
|
|
9
|
-
export function getAgentProviderKeys() {
|
|
10
|
-
return _agentProviderKeys;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Load the agent's .env with provider-key priority.
|
|
14
|
-
*
|
|
15
|
-
* 1. Parse .env to discover which provider keys the agent declared.
|
|
16
|
-
* 2. Load .env with override so the agent's values win for the same key.
|
|
17
|
-
* 3. getModel() uses getAgentProviderKeys() to check those providers first,
|
|
18
|
-
* falling back to shell-inherited keys if the agent has none.
|
|
19
|
-
*/
|
|
20
|
-
export async function loadAgentEnv() {
|
|
21
|
-
try {
|
|
22
|
-
const content = readFileSync('.env', 'utf-8');
|
|
23
|
-
_agentProviderKeys = new Set(AI_PROVIDER_ENV_VARS.filter((key) => new RegExp(`^${key}=`, 'm').test(content)));
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
_agentProviderKeys = new Set();
|
|
27
|
-
}
|
|
28
|
-
const { config } = await import('dotenv');
|
|
29
|
-
config({ override: true });
|
|
30
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { AI_PROVIDERS } from '../../ai-providers.js';
|
|
2
|
-
import { getAgentProviderKeys } from '../env.js';
|
|
3
|
-
const PROVIDERS = [
|
|
4
|
-
{
|
|
5
|
-
label: 'Anthropic',
|
|
6
|
-
envVar: 'ANTHROPIC_API_KEY',
|
|
7
|
-
load: async (modelId) => {
|
|
8
|
-
const { anthropic } = await import('@ai-sdk/anthropic');
|
|
9
|
-
return anthropic(modelId);
|
|
10
|
-
},
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
label: 'OpenAI',
|
|
14
|
-
envVar: 'OPENAI_API_KEY',
|
|
15
|
-
load: async (modelId) => {
|
|
16
|
-
const { openai } = await import('@ai-sdk/openai');
|
|
17
|
-
return openai(modelId);
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
label: 'Google',
|
|
22
|
-
envVar: 'GOOGLE_GENERATIVE_AI_API_KEY',
|
|
23
|
-
load: async (modelId) => {
|
|
24
|
-
const { google } = await import('@ai-sdk/google');
|
|
25
|
-
return google(modelId);
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
label: 'xAI',
|
|
30
|
-
envVar: 'XAI_API_KEY',
|
|
31
|
-
load: async (modelId) => {
|
|
32
|
-
const { xai } = await import('@ai-sdk/xai');
|
|
33
|
-
return xai(modelId);
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
label: 'OpenRouter',
|
|
38
|
-
envVar: 'OPENROUTER_API_KEY',
|
|
39
|
-
load: async (modelId) => {
|
|
40
|
-
const { createOpenRouter } = await import('@openrouter/ai-sdk-provider');
|
|
41
|
-
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY });
|
|
42
|
-
return openrouter.chat(modelId);
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
];
|
|
46
|
-
export function resolveModelInfo() {
|
|
47
|
-
const overrideModel = process.env.HIVE_MODEL;
|
|
48
|
-
const agentKeys = getAgentProviderKeys();
|
|
49
|
-
const sortedProviders = [
|
|
50
|
-
...PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
|
|
51
|
-
...PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
|
|
52
|
-
];
|
|
53
|
-
for (const provider of sortedProviders) {
|
|
54
|
-
const keyValue = process.env[provider.envVar];
|
|
55
|
-
if (keyValue && keyValue.trim().length > 0) {
|
|
56
|
-
const centralProvider = AI_PROVIDERS.find((p) => p.envVar === provider.envVar);
|
|
57
|
-
const runtimeModel = centralProvider?.models.runtime ?? 'unknown';
|
|
58
|
-
const modelId = overrideModel ?? runtimeModel;
|
|
59
|
-
const source = agentKeys.has(provider.envVar) ? '.env' : 'shell';
|
|
60
|
-
return { provider: provider.label, modelId, source };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return { provider: 'unknown', modelId: 'unknown', source: 'unknown' };
|
|
64
|
-
}
|
|
65
|
-
let _modelPromise = null;
|
|
66
|
-
export function getModel() {
|
|
67
|
-
if (_modelPromise) {
|
|
68
|
-
return _modelPromise;
|
|
69
|
-
}
|
|
70
|
-
_modelPromise = (async () => {
|
|
71
|
-
const info = resolveModelInfo();
|
|
72
|
-
if (info.provider === 'unknown') {
|
|
73
|
-
throw new Error('No AI provider API key found in environment. ' +
|
|
74
|
-
'Set one of: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY');
|
|
75
|
-
}
|
|
76
|
-
const agentKeys = getAgentProviderKeys();
|
|
77
|
-
const sortedProviders = [
|
|
78
|
-
...PROVIDERS.filter((p) => agentKeys.has(p.envVar)),
|
|
79
|
-
...PROVIDERS.filter((p) => !agentKeys.has(p.envVar)),
|
|
80
|
-
];
|
|
81
|
-
for (const provider of sortedProviders) {
|
|
82
|
-
const keyValue = process.env[provider.envVar];
|
|
83
|
-
if (keyValue && keyValue.trim().length > 0) {
|
|
84
|
-
const modelId = info.modelId;
|
|
85
|
-
const model = await provider.load(modelId);
|
|
86
|
-
return model;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
throw new Error('Unreachable: resolveModelInfo succeeded but no provider found');
|
|
90
|
-
})();
|
|
91
|
-
return _modelPromise;
|
|
92
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export function formatTime(date) {
|
|
2
|
-
const hours = String(date.getHours()).padStart(2, '0');
|
|
3
|
-
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
4
|
-
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
5
|
-
return `${hours}:${minutes}:${seconds}`;
|
|
6
|
-
}
|
|
7
|
-
export function convictionColor(conviction) {
|
|
8
|
-
if (conviction > 0)
|
|
9
|
-
return 'green';
|
|
10
|
-
if (conviction < 0)
|
|
11
|
-
return 'red';
|
|
12
|
-
return 'gray';
|
|
13
|
-
}
|
|
14
|
-
export function stripCodeFences(text) {
|
|
15
|
-
const trimmed = text.trim();
|
|
16
|
-
const fencePattern = /^```(?:markdown|md)?\s*\n([\s\S]*?)\n```$/;
|
|
17
|
-
const match = trimmed.match(fencePattern);
|
|
18
|
-
if (match) {
|
|
19
|
-
return match[1].trim();
|
|
20
|
-
}
|
|
21
|
-
return trimmed;
|
|
22
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
function extractSections(content) {
|
|
2
|
-
const sections = content
|
|
3
|
-
.split('\n')
|
|
4
|
-
.filter((line) => line.trim().startsWith('## '))
|
|
5
|
-
.map((line) => line.trim().replace(/^## /, ''));
|
|
6
|
-
return sections;
|
|
7
|
-
}
|
|
8
|
-
export function buildChatPrompt(soulContent, strategyContent, context) {
|
|
9
|
-
// ── System (static per agent session — cached by providers) ──
|
|
10
|
-
const system = `You are an AI trading agent having a conversation with your operator. Stay in character.
|
|
11
|
-
|
|
12
|
-
Your personality:
|
|
13
|
-
---
|
|
14
|
-
${soulContent}
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
Your trading strategy:
|
|
18
|
-
---
|
|
19
|
-
${strategyContent}
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Editing Your Files
|
|
23
|
-
|
|
24
|
-
You have a tool called "editSection" that can update sections of your SOUL.md and STRATEGY.md.
|
|
25
|
-
|
|
26
|
-
Rules:
|
|
27
|
-
1. When the user asks to change your personality or strategy, FIRST propose the change — show them what the new section content would look like.
|
|
28
|
-
2. Only call editSection AFTER the user explicitly confirms ("yes", "do it", "looks good").
|
|
29
|
-
3. Never call the tool speculatively.
|
|
30
|
-
4. After applying, confirm briefly in character.
|
|
31
|
-
|
|
32
|
-
SOUL.md sections: ${extractSections(soulContent).join(', ')}
|
|
33
|
-
STRATEGY.md sections: ${extractSections(strategyContent).join(', ')}
|
|
34
|
-
|
|
35
|
-
## Game Rules
|
|
36
|
-
|
|
37
|
-
You have a tool called "fetchRules" that fetches the official Hive game rules. Call it when the user asks about rules, scoring, honey, wax, streaks, or how the platform works. Summarize the rules in your own voice — don't dump the raw markdown.
|
|
38
|
-
|
|
39
|
-
Respond in character. Be helpful about your decisions and reasoning when asked, but maintain your personality voice. Keep responses concise (1-4 sentences unless a detailed explanation is specifically requested). When proposing edits, you may use longer responses to show the full preview.`;
|
|
40
|
-
// ── Prompt (dynamic per chat message) ──
|
|
41
|
-
let threadsSection = '';
|
|
42
|
-
if (context.recentThreadSummaries.length > 0) {
|
|
43
|
-
const listed = context.recentThreadSummaries.map((t) => `- ${t}`).join('\n');
|
|
44
|
-
threadsSection = `\n## Recent Signals\n\n${listed}\n`;
|
|
45
|
-
}
|
|
46
|
-
let predictionsSection = '';
|
|
47
|
-
if (context.recentPredictions.length > 0) {
|
|
48
|
-
const listed = context.recentPredictions.map((p) => `- ${p}`).join('\n');
|
|
49
|
-
predictionsSection = `\n## Recent Predictions\n\n${listed}\n`;
|
|
50
|
-
}
|
|
51
|
-
let memorySection = '';
|
|
52
|
-
if (context.memory.trim().length > 0) {
|
|
53
|
-
memorySection = `\n## Past Conversations\n\nThings you remember from previous sessions with your operator:\n${context.memory}\n`;
|
|
54
|
-
}
|
|
55
|
-
let sessionSection = '';
|
|
56
|
-
if (context.sessionMessages.length > 0) {
|
|
57
|
-
const listed = context.sessionMessages
|
|
58
|
-
.map((m) => `${m.role === 'user' ? 'User' : 'You'}: ${m.content}`)
|
|
59
|
-
.join('\n');
|
|
60
|
-
sessionSection = `\n## This Session's Conversation\n\n${listed}\n`;
|
|
61
|
-
}
|
|
62
|
-
const userPrompt = `${memorySection}${threadsSection}${predictionsSection}${sessionSection}
|
|
63
|
-
The operator says: "${context.userMessage}"`;
|
|
64
|
-
return { system, prompt: userPrompt };
|
|
65
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export function buildMemoryExtractionPrompt(context) {
|
|
2
|
-
const { currentMemory, sessionMessages, lineCount } = context;
|
|
3
|
-
let sessionSection = '';
|
|
4
|
-
if (sessionMessages.length > 0) {
|
|
5
|
-
const listed = sessionMessages
|
|
6
|
-
.map((m) => `${m.role === 'user' ? 'Operator' : 'Agent'}: ${m.content}`)
|
|
7
|
-
.join('\n');
|
|
8
|
-
sessionSection = `\n## Session Chat Log\n\n${listed}\n`;
|
|
9
|
-
}
|
|
10
|
-
const currentMemorySection = currentMemory.trim().length > 0
|
|
11
|
-
? `\n## Current MEMORY.md\n\n\`\`\`markdown\n${currentMemory}\n\`\`\`\n`
|
|
12
|
-
: '\n## Current MEMORY.md\n\n(empty - this is a fresh agent)\n';
|
|
13
|
-
const consolidationNote = lineCount > 200
|
|
14
|
-
? `\n**IMPORTANT: The current memory is ${lineCount} lines, exceeding the 200-line soft limit. Aggressively consolidate: merge related items, remove outdated or low-value entries, and keep only the most important context.**\n`
|
|
15
|
-
: '';
|
|
16
|
-
const prompt = `You are an AI trading agent's memory system. Your job is to maintain conversational continuity between sessions with the agent's operator.
|
|
17
|
-
${currentMemorySection}${consolidationNote}
|
|
18
|
-
## Session Activity
|
|
19
|
-
${sessionSection}
|
|
20
|
-
## Instructions
|
|
21
|
-
|
|
22
|
-
Review the session chat log above and update the agent's MEMORY.md file. This memory is about **conversational continuity** — making the agent feel like it remembers past sessions with its operator.
|
|
23
|
-
|
|
24
|
-
Focus on extracting:
|
|
25
|
-
1. **Topics discussed** — what subjects came up in conversation (e.g., "we talked about ETH gas fees", "operator asked about macro outlook")
|
|
26
|
-
2. **Operator interests and concerns** — what the operator cares about, recurring themes, questions they've raised
|
|
27
|
-
3. **Ongoing conversational threads** — topics that span multiple sessions or feel unresolved
|
|
28
|
-
4. **Operator preferences** — how they like to interact, what they find useful or annoying
|
|
29
|
-
|
|
30
|
-
Do NOT save:
|
|
31
|
-
- Market predictions, signal analysis, or trading insights — a separate results-based learning system will handle those in the future
|
|
32
|
-
- Raw price data or signal summaries
|
|
33
|
-
- Routine prediction outcomes
|
|
34
|
-
|
|
35
|
-
Follow these rules:
|
|
36
|
-
1. **Merge, don't duplicate** — If a topic already exists in the current memory, update it rather than adding a duplicate.
|
|
37
|
-
2. **Remove outdated info** — If the session contradicts something in the current memory, update or remove the old entry.
|
|
38
|
-
3. **Stay concise** — Each entry should be 1-2 lines. Use bullet points.
|
|
39
|
-
4. **Organize by topic** — Use markdown headers to group related context (e.g., "## Conversations", "## Operator Interests", "## Ongoing Threads").
|
|
40
|
-
5. **Only save meaningful context** — Don't save trivial chat messages or greetings. Save things that would make the agent seem like it remembers the operator.
|
|
41
|
-
6. **Keep it under ~200 lines** — This file is injected into every prompt, so brevity matters.
|
|
42
|
-
|
|
43
|
-
Output the complete updated MEMORY.md content. Start with \`# Memory\` as the top-level header. Output ONLY the markdown content, no code fences or explanation.`;
|
|
44
|
-
return prompt;
|
|
45
|
-
}
|