@zhive/cli 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/README.md +118 -0
- package/dist/agent/analysis.js +160 -0
- package/dist/agent/app.js +122 -0
- package/dist/agent/chat-prompt.js +65 -0
- package/dist/agent/commands/registry.js +12 -0
- package/dist/agent/components/AsciiTicker.js +81 -0
- package/dist/agent/components/CommandInput.js +65 -0
- package/dist/agent/components/HoneycombBoot.js +291 -0
- package/dist/agent/components/Spinner.js +37 -0
- package/dist/agent/config.js +75 -0
- package/dist/agent/edit-section.js +59 -0
- package/dist/agent/fetch-rules.js +21 -0
- package/dist/agent/helpers.js +22 -0
- package/dist/agent/hooks/useAgent.js +480 -0
- package/dist/agent/memory-prompt.js +47 -0
- package/dist/agent/model.js +92 -0
- package/dist/agent/objects.js +1 -0
- package/dist/agent/process-lifecycle.js +18 -0
- package/dist/agent/prompt.js +353 -0
- package/dist/agent/run-headless.js +189 -0
- package/dist/agent/skills/index.js +2 -0
- package/dist/agent/skills/skill-parser.js +149 -0
- package/dist/agent/skills/types.js +1 -0
- package/dist/agent/theme.js +41 -0
- package/dist/agent/tools/index.js +76 -0
- package/dist/agent/tools/market/client.js +41 -0
- package/dist/agent/tools/market/index.js +3 -0
- package/dist/agent/tools/market/tools.js +518 -0
- package/dist/agent/tools/mindshare/client.js +124 -0
- package/dist/agent/tools/mindshare/index.js +3 -0
- package/dist/agent/tools/mindshare/tools.js +563 -0
- package/dist/agent/tools/read-skill-tool.js +30 -0
- package/dist/agent/tools/ta/index.js +1 -0
- package/dist/agent/tools/ta/indicators.js +201 -0
- package/dist/agent/types.js +1 -0
- package/dist/agents.js +110 -0
- package/dist/ai-providers.js +66 -0
- package/dist/avatar.js +34 -0
- package/dist/backtest/default-backtest-data.js +200 -0
- package/dist/backtest/fetch.js +41 -0
- package/dist/backtest/import.js +106 -0
- package/dist/backtest/index.js +10 -0
- package/dist/backtest/results.js +113 -0
- package/dist/backtest/runner.js +134 -0
- package/dist/backtest/storage.js +11 -0
- package/dist/backtest/types.js +1 -0
- package/dist/commands/create/ai-generate.js +126 -0
- package/dist/commands/create/commands/index.js +10 -0
- package/dist/commands/create/generate.js +73 -0
- package/dist/commands/create/presets/data.js +225 -0
- package/dist/commands/create/presets/formatting.js +81 -0
- package/dist/commands/create/presets/index.js +3 -0
- package/dist/commands/create/presets/options.js +307 -0
- package/dist/commands/create/presets/types.js +1 -0
- package/dist/commands/create/presets.js +613 -0
- package/dist/commands/create/ui/CreateApp.js +172 -0
- package/dist/commands/create/ui/steps/ApiKeyStep.js +89 -0
- package/dist/commands/create/ui/steps/AvatarStep.js +16 -0
- package/dist/commands/create/ui/steps/DoneStep.js +14 -0
- package/dist/commands/create/ui/steps/IdentityStep.js +125 -0
- package/dist/commands/create/ui/steps/NameStep.js +148 -0
- package/dist/commands/create/ui/steps/ScaffoldStep.js +59 -0
- package/dist/commands/create/ui/steps/SoulStep.js +21 -0
- package/dist/commands/create/ui/steps/StrategyStep.js +20 -0
- package/dist/commands/create/ui/steps/StreamingGenerationStep.js +56 -0
- package/dist/commands/create/ui/validation.js +34 -0
- package/dist/commands/create/validate-api-key.js +27 -0
- package/dist/commands/install.js +50 -0
- package/dist/commands/list/commands/index.js +7 -0
- package/dist/commands/list/ui/ListApp.js +79 -0
- package/dist/commands/migrate-templates/commands/index.js +9 -0
- package/dist/commands/migrate-templates/migrate.js +87 -0
- package/dist/commands/migrate-templates/ui/MigrateApp.js +132 -0
- package/dist/commands/run/commands/index.js +17 -0
- package/dist/commands/run/run-headless.js +111 -0
- package/dist/commands/shared/theme.js +57 -0
- package/dist/commands/shared/welcome.js +304 -0
- package/dist/commands/start/commands/backtest.js +35 -0
- package/dist/commands/start/commands/index.js +62 -0
- package/dist/commands/start/commands/prediction.js +73 -0
- package/dist/commands/start/commands/skills.js +44 -0
- package/dist/commands/start/commands/skills.test.js +140 -0
- package/dist/commands/start/hooks/types.js +1 -0
- package/dist/commands/start/hooks/useAgent.js +177 -0
- package/dist/commands/start/hooks/useChat.js +266 -0
- package/dist/commands/start/hooks/usePollActivity.js +45 -0
- package/dist/commands/start/hooks/utils.js +152 -0
- package/dist/commands/start/services/backtest/default-backtest-data.js +200 -0
- package/dist/commands/start/services/backtest/fetch.js +42 -0
- package/dist/commands/start/services/backtest/import.js +109 -0
- package/dist/commands/start/services/backtest/index.js +10 -0
- package/dist/commands/start/services/backtest/results.js +113 -0
- package/dist/commands/start/services/backtest/runner.js +103 -0
- package/dist/commands/start/services/backtest/storage.js +11 -0
- package/dist/commands/start/services/backtest/types.js +1 -0
- package/dist/commands/start/services/command-registry.js +13 -0
- package/dist/commands/start/ui/AsciiTicker.js +81 -0
- package/dist/commands/start/ui/CommandInput.js +65 -0
- package/dist/commands/start/ui/HoneycombBoot.js +291 -0
- package/dist/commands/start/ui/PollText.js +23 -0
- package/dist/commands/start/ui/PredictionsPanel.js +88 -0
- package/dist/commands/start/ui/SelectAgentApp.js +93 -0
- package/dist/commands/start/ui/Spinner.js +29 -0
- package/dist/commands/start/ui/SpinnerContext.js +20 -0
- package/dist/commands/start/ui/app.js +36 -0
- package/dist/commands/start-all/AgentProcessManager.js +98 -0
- package/dist/commands/start-all/commands/index.js +24 -0
- package/dist/commands/start-all/ui/Dashboard.js +91 -0
- package/dist/components/AsciiTicker.js +81 -0
- package/dist/components/CharacterSummaryCard.js +33 -0
- package/dist/components/CodeBlock.js +11 -0
- package/dist/components/ColoredStats.js +18 -0
- package/dist/components/Header.js +10 -0
- package/dist/components/HoneycombLoader.js +190 -0
- package/dist/components/InputGuard.js +6 -0
- package/dist/components/MultiSelectPrompt.js +45 -0
- package/dist/components/SelectPrompt.js +20 -0
- package/dist/components/Spinner.js +16 -0
- package/dist/components/StepIndicator.js +31 -0
- package/dist/components/StreamingText.js +50 -0
- package/dist/components/TextPrompt.js +28 -0
- package/dist/components/stdout-spinner.js +48 -0
- package/dist/config.js +28 -0
- package/dist/create/CreateApp.js +153 -0
- package/dist/create/ai-generate.js +147 -0
- package/dist/create/generate.js +73 -0
- package/dist/create/steps/ApiKeyStep.js +97 -0
- package/dist/create/steps/AvatarStep.js +16 -0
- package/dist/create/steps/BioStep.js +14 -0
- package/dist/create/steps/DoneStep.js +14 -0
- package/dist/create/steps/IdentityStep.js +163 -0
- package/dist/create/steps/NameStep.js +71 -0
- package/dist/create/steps/ScaffoldStep.js +58 -0
- package/dist/create/steps/SoulStep.js +58 -0
- package/dist/create/steps/StrategyStep.js +58 -0
- package/dist/create/validate-api-key.js +47 -0
- package/dist/create/welcome.js +304 -0
- package/dist/index.js +60 -0
- package/dist/list/ListApp.js +79 -0
- package/dist/load-agent-env.js +30 -0
- package/dist/migrate-templates/MigrateApp.js +131 -0
- package/dist/migrate-templates/migrate.js +86 -0
- package/dist/presets.js +613 -0
- package/dist/shared/agent/agent-runtime.js +144 -0
- package/dist/shared/agent/analysis.js +171 -0
- package/dist/shared/agent/helpers.js +1 -0
- package/dist/shared/agent/prompts/chat-prompt.js +60 -0
- package/dist/shared/agent/prompts/megathread.js +202 -0
- package/dist/shared/agent/prompts/memory-prompt.js +47 -0
- package/dist/shared/agent/prompts/prompt.js +18 -0
- package/dist/shared/agent/skills/index.js +2 -0
- package/dist/shared/agent/skills/skill-parser.js +167 -0
- package/dist/shared/agent/skills/skill-parser.test.js +190 -0
- package/dist/shared/agent/skills/types.js +1 -0
- package/dist/shared/agent/tools/edit-section.js +60 -0
- package/dist/shared/agent/tools/execute-skill-tool.js +134 -0
- package/dist/shared/agent/tools/fetch-rules.js +22 -0
- package/dist/shared/agent/tools/formatting.js +48 -0
- package/dist/shared/agent/tools/index.js +87 -0
- package/dist/shared/agent/tools/market/client.js +41 -0
- package/dist/shared/agent/tools/market/index.js +3 -0
- package/dist/shared/agent/tools/market/tools.js +497 -0
- package/dist/shared/agent/tools/mindshare/client.js +124 -0
- package/dist/shared/agent/tools/mindshare/index.js +3 -0
- package/dist/shared/agent/tools/mindshare/tools.js +167 -0
- package/dist/shared/agent/tools/read-skill-tool.js +30 -0
- package/dist/shared/agent/tools/ta/index.js +1 -0
- package/dist/shared/agent/tools/ta/indicators.js +201 -0
- package/dist/shared/agent/types.js +1 -0
- package/dist/shared/agent/utils.js +43 -0
- package/dist/shared/config/agent.js +177 -0
- package/dist/shared/config/ai-providers.js +156 -0
- package/dist/shared/config/config.js +22 -0
- package/dist/shared/config/constant.js +8 -0
- package/dist/shared/config/env-loader.js +30 -0
- package/dist/shared/types.js +1 -0
- package/dist/start/AgentProcessManager.js +98 -0
- package/dist/start/Dashboard.js +92 -0
- package/dist/start/SelectAgentApp.js +81 -0
- package/dist/start/StartApp.js +189 -0
- package/dist/start/patch-headless.js +101 -0
- package/dist/start/patch-managed-mode.js +142 -0
- package/dist/start/start-command.js +24 -0
- package/dist/theme.js +54 -0
- package/package.json +68 -0
- package/templates/components/HoneycombBoot.tsx +343 -0
- package/templates/fetch-rules.ts +23 -0
- package/templates/skills/mindshare/SKILL.md +197 -0
- package/templates/skills/ta/SKILL.md +179 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import { HiveAgent, loadMemory } from '@hive-org/sdk';
|
|
2
|
+
import { ToolLoopAgent } from 'ai';
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { HIVE_FRONTEND_URL } from '../../config.js';
|
|
5
|
+
import { buildTextReport, fetchBacktestThreads, loadDefaultBacktest, runBacktest, } from '../../backtest/index.js';
|
|
6
|
+
import { extractAndSaveMemory, processMegathreadRound, processSignalAndSummarize, } from '../analysis.js';
|
|
7
|
+
import { buildChatPrompt } from '../chat-prompt.js';
|
|
8
|
+
import { SLASH_COMMANDS } from '../commands/registry.js';
|
|
9
|
+
import { loadAgentConfig } from '../config.js';
|
|
10
|
+
import { editSectionTool } from '../edit-section.js';
|
|
11
|
+
import { fetchRulesTool } from '../fetch-rules.js';
|
|
12
|
+
import { getModel, resolveModelInfo } from '../model.js';
|
|
13
|
+
import { getAllTools, getReadSkillTool, getSkillMetadataList, initializeSkills, } from '../tools/index.js';
|
|
14
|
+
import { getMarketClient } from '../tools/market/index.js';
|
|
15
|
+
export function useAgent() {
|
|
16
|
+
const [connected, setConnected] = useState(false);
|
|
17
|
+
const [agentName, setAgentName] = useState('agent');
|
|
18
|
+
const [agentBio, setAgentBio] = useState('');
|
|
19
|
+
const [modelInfo, setModelInfo] = useState(null);
|
|
20
|
+
const [pollActivity, setPollActivity] = useState([]);
|
|
21
|
+
const [chatActivity, setChatActivity] = useState([]);
|
|
22
|
+
const [input, setInput] = useState('');
|
|
23
|
+
const [chatStreaming, setChatStreaming] = useState(false);
|
|
24
|
+
const [chatBuffer, setChatBuffer] = useState('');
|
|
25
|
+
const [predictionCount, setPredictionCount] = useState(0);
|
|
26
|
+
const [termWidth, setTermWidth] = useState(process.stdout.columns || 60);
|
|
27
|
+
const agentRef = useRef(null);
|
|
28
|
+
const sessionMessagesRef = useRef([]);
|
|
29
|
+
const memoryRef = useRef('');
|
|
30
|
+
const chatCountSinceExtractRef = useRef(0);
|
|
31
|
+
const extractingRef = useRef(false);
|
|
32
|
+
const recentThreadsRef = useRef([]);
|
|
33
|
+
const recentPredictionsRef = useRef([]);
|
|
34
|
+
const predictionCountRef = useRef(0);
|
|
35
|
+
const soulContentRef = useRef('');
|
|
36
|
+
const strategyContentRef = useRef('');
|
|
37
|
+
const skillToolsRef = useRef({});
|
|
38
|
+
const skillMetadataRef = useRef('');
|
|
39
|
+
// ─── Terminal resize tracking ───────────────────────
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const onResize = () => {
|
|
42
|
+
setTermWidth(process.stdout.columns || 60);
|
|
43
|
+
};
|
|
44
|
+
process.stdout.on('resize', onResize);
|
|
45
|
+
return () => {
|
|
46
|
+
process.stdout.off('resize', onResize);
|
|
47
|
+
};
|
|
48
|
+
}, []);
|
|
49
|
+
// ─── Activity helpers ───────────────────────────────
|
|
50
|
+
const addPollActivity = useCallback((item) => {
|
|
51
|
+
setPollActivity((prev) => {
|
|
52
|
+
const updated = [...prev, { ...item, timestamp: new Date() }];
|
|
53
|
+
const maxItems = 50;
|
|
54
|
+
if (updated.length > maxItems) {
|
|
55
|
+
return updated.slice(updated.length - maxItems);
|
|
56
|
+
}
|
|
57
|
+
return updated;
|
|
58
|
+
});
|
|
59
|
+
}, []);
|
|
60
|
+
const addIdleActivity = useCallback(() => {
|
|
61
|
+
setPollActivity((prev) => {
|
|
62
|
+
const updated = [
|
|
63
|
+
...prev,
|
|
64
|
+
{ type: 'idle', text: 'Polled but no new threads', timestamp: new Date() },
|
|
65
|
+
];
|
|
66
|
+
const maxItems = 50;
|
|
67
|
+
if (updated.length > maxItems) {
|
|
68
|
+
return updated.slice(updated.length - maxItems);
|
|
69
|
+
}
|
|
70
|
+
return updated;
|
|
71
|
+
});
|
|
72
|
+
}, []);
|
|
73
|
+
const updatePollActivity = useCallback((id, updates) => {
|
|
74
|
+
setPollActivity((prev) => {
|
|
75
|
+
let idx = -1;
|
|
76
|
+
for (let i = prev.length - 1; i >= 0; i--) {
|
|
77
|
+
if (prev[i].id === id) {
|
|
78
|
+
idx = i;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (idx === -1)
|
|
83
|
+
return prev;
|
|
84
|
+
const updated = [...prev];
|
|
85
|
+
updated[idx] = { ...updated[idx], ...updates };
|
|
86
|
+
return updated;
|
|
87
|
+
});
|
|
88
|
+
}, []);
|
|
89
|
+
const addChatActivity = useCallback((item) => {
|
|
90
|
+
setChatActivity((prev) => {
|
|
91
|
+
const updated = [...prev, { ...item, timestamp: new Date() }];
|
|
92
|
+
const maxItems = 50;
|
|
93
|
+
if (updated.length > maxItems) {
|
|
94
|
+
return updated.slice(updated.length - maxItems);
|
|
95
|
+
}
|
|
96
|
+
return updated;
|
|
97
|
+
});
|
|
98
|
+
}, []);
|
|
99
|
+
// ─── Agent lifecycle ────────────────────────────────
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const start = async () => {
|
|
102
|
+
const { HIVE_API_URL } = await import('../../config.js');
|
|
103
|
+
const baseUrl = HIVE_API_URL;
|
|
104
|
+
const config = await loadAgentConfig();
|
|
105
|
+
setAgentName(config.name);
|
|
106
|
+
setAgentBio(config.bio ?? '');
|
|
107
|
+
soulContentRef.current = config.soulContent;
|
|
108
|
+
strategyContentRef.current = config.strategyContent;
|
|
109
|
+
const resolvedModelInfo = resolveModelInfo();
|
|
110
|
+
setModelInfo(resolvedModelInfo);
|
|
111
|
+
const skillRegistry = await initializeSkills(process.cwd());
|
|
112
|
+
const allTools = getAllTools();
|
|
113
|
+
const readSkillTool = getReadSkillTool(skillRegistry);
|
|
114
|
+
skillToolsRef.current = { ...allTools, readSkill: readSkillTool };
|
|
115
|
+
skillMetadataRef.current = getSkillMetadataList(skillRegistry);
|
|
116
|
+
const initialMemory = await loadMemory();
|
|
117
|
+
memoryRef.current = initialMemory;
|
|
118
|
+
const agent = new HiveAgent(baseUrl, {
|
|
119
|
+
name: config.name,
|
|
120
|
+
avatarUrl: config.avatarUrl,
|
|
121
|
+
bio: config.bio ?? undefined,
|
|
122
|
+
agentProfile: config.agentProfile,
|
|
123
|
+
pollIntervalMs: 30_000,
|
|
124
|
+
pollLimit: 5,
|
|
125
|
+
onPollEmpty: addIdleActivity,
|
|
126
|
+
onStop: async () => { },
|
|
127
|
+
onNewThread: async (thread) => {
|
|
128
|
+
const threadPreview = thread.text.length > 80 ? thread.text.slice(0, 80) + '\u2026' : thread.text;
|
|
129
|
+
const projectTag = `c/${thread.project_id}`;
|
|
130
|
+
addPollActivity({
|
|
131
|
+
type: 'signal',
|
|
132
|
+
id: thread.id,
|
|
133
|
+
text: projectTag,
|
|
134
|
+
detail: threadPreview,
|
|
135
|
+
status: 'analyzing',
|
|
136
|
+
});
|
|
137
|
+
const summary = `[${thread.project_id}] ${threadPreview}`;
|
|
138
|
+
recentThreadsRef.current = [summary, ...recentThreadsRef.current].slice(0, 5);
|
|
139
|
+
try {
|
|
140
|
+
const result = await processSignalAndSummarize(thread, agent.recentComments, memoryRef.current, soulContentRef.current, strategyContentRef.current, skillToolsRef.current, skillMetadataRef.current);
|
|
141
|
+
if (result.skip) {
|
|
142
|
+
updatePollActivity(thread.id, {
|
|
143
|
+
status: 'skipped',
|
|
144
|
+
tokenUsage: result.usage,
|
|
145
|
+
});
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await agent.postComment(thread.id, {
|
|
149
|
+
thread_id: thread.id,
|
|
150
|
+
text: result.summary,
|
|
151
|
+
conviction: result.conviction,
|
|
152
|
+
}, thread.text);
|
|
153
|
+
const sign = result.conviction >= 0 ? '+' : '';
|
|
154
|
+
const threadUrl = `${HIVE_FRONTEND_URL}/c/${thread.project_id}/${thread.id}`;
|
|
155
|
+
updatePollActivity(thread.id, {
|
|
156
|
+
status: 'posted',
|
|
157
|
+
conviction: result.conviction,
|
|
158
|
+
result: `[${sign}${result.conviction.toFixed(2)}%] "${result.summary}"`,
|
|
159
|
+
url: threadUrl,
|
|
160
|
+
tokenUsage: result.usage,
|
|
161
|
+
});
|
|
162
|
+
predictionCountRef.current += 1;
|
|
163
|
+
setPredictionCount(predictionCountRef.current);
|
|
164
|
+
const predSummary = `[${sign}${result.conviction.toFixed(2)}%] ${result.summary}`;
|
|
165
|
+
recentPredictionsRef.current = [predSummary, ...recentPredictionsRef.current].slice(0, 5);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
169
|
+
const message = raw.length > 120 ? raw.slice(0, 120) + '\u2026' : raw;
|
|
170
|
+
updatePollActivity(thread.id, { status: 'error', result: message });
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
onNewMegathreadRound: async (round) => {
|
|
174
|
+
const hours = Math.round(round.durationMs / 3_600_000);
|
|
175
|
+
const timeframe = hours >= 1 ? `${hours}h` : `${Math.round(round.durationMs / 60_000)}m`;
|
|
176
|
+
const projectTag = `c/${round.projectId}`;
|
|
177
|
+
addPollActivity({
|
|
178
|
+
type: 'megathread',
|
|
179
|
+
id: round.roundId,
|
|
180
|
+
text: `${projectTag} \u00B7 ${timeframe} round`,
|
|
181
|
+
detail: round.roundId.split('@')[0],
|
|
182
|
+
status: 'analyzing',
|
|
183
|
+
});
|
|
184
|
+
let priceAtStart;
|
|
185
|
+
let currentPrice;
|
|
186
|
+
try {
|
|
187
|
+
const client = getMarketClient();
|
|
188
|
+
const roundStartTimestamp = round.roundId.split('@Z')[0];
|
|
189
|
+
const [startResponse, nowResponse] = await Promise.all([
|
|
190
|
+
client.getPrice(round.projectId, roundStartTimestamp),
|
|
191
|
+
client.getPrice(round.projectId, new Date().toISOString()),
|
|
192
|
+
]);
|
|
193
|
+
if (startResponse.price !== null) {
|
|
194
|
+
priceAtStart = startResponse.price;
|
|
195
|
+
}
|
|
196
|
+
if (nowResponse.price !== null) {
|
|
197
|
+
currentPrice = nowResponse.price;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Price fetch failed — both stay undefined, prompt falls back to current behavior
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const result = await processMegathreadRound(round.projectId, round.durationMs, agent.recentComments, memoryRef.current, soulContentRef.current, strategyContentRef.current, skillToolsRef.current, skillMetadataRef.current, priceAtStart, currentPrice);
|
|
205
|
+
if (result.skip) {
|
|
206
|
+
updatePollActivity(round.roundId, {
|
|
207
|
+
status: 'skipped',
|
|
208
|
+
tokenUsage: result.usage,
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
await agent.postMegathreadComment(round.roundId, {
|
|
213
|
+
text: result.summary,
|
|
214
|
+
conviction: result.conviction,
|
|
215
|
+
tokenId: round.projectId,
|
|
216
|
+
roundDuration: round.durationMs,
|
|
217
|
+
});
|
|
218
|
+
const sign = result.conviction >= 0 ? '+' : '';
|
|
219
|
+
const megathreadUrl = `${HIVE_FRONTEND_URL}/c/${round.projectId}/megathread/${timeframe}`;
|
|
220
|
+
updatePollActivity(round.roundId, {
|
|
221
|
+
status: 'posted',
|
|
222
|
+
conviction: result.conviction,
|
|
223
|
+
result: `[${sign}${result.conviction.toFixed(2)}%] "${result.summary}"`,
|
|
224
|
+
url: megathreadUrl,
|
|
225
|
+
tokenUsage: result.usage,
|
|
226
|
+
});
|
|
227
|
+
predictionCountRef.current += 1;
|
|
228
|
+
setPredictionCount(predictionCountRef.current);
|
|
229
|
+
const predSummary = `[${sign}${result.conviction.toFixed(2)}%] ${result.summary}`;
|
|
230
|
+
recentPredictionsRef.current = [predSummary, ...recentPredictionsRef.current].slice(0, 5);
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
234
|
+
const message = raw.length > 120 ? raw.slice(0, 120) + '\u2026' : raw;
|
|
235
|
+
updatePollActivity(round.roundId, { status: 'error', result: message });
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
agentRef.current = agent;
|
|
240
|
+
await agent.start();
|
|
241
|
+
setConnected(true);
|
|
242
|
+
const bio = config.bio ?? '';
|
|
243
|
+
if (bio) {
|
|
244
|
+
addPollActivity({
|
|
245
|
+
type: 'online',
|
|
246
|
+
text: `${config.name} agent online \u2014 "${bio}"`,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
start().catch((err) => {
|
|
251
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
252
|
+
const isNameTaken = raw.includes('409');
|
|
253
|
+
const hint = isNameTaken ? ' Change the name in SOUL.md under "# Agent: <name>".' : '';
|
|
254
|
+
addPollActivity({ type: 'error', text: `Fatal: ${raw.slice(0, 120)}${hint}` });
|
|
255
|
+
});
|
|
256
|
+
return () => {
|
|
257
|
+
agentRef.current?.stop().catch(() => { });
|
|
258
|
+
};
|
|
259
|
+
}, []);
|
|
260
|
+
// ─── Chat submission ────────────────────────────────
|
|
261
|
+
const handleChatSubmit = useCallback(async (message) => {
|
|
262
|
+
if (!message.trim() || chatStreaming) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// Handle slash commands
|
|
266
|
+
if (message.startsWith('/')) {
|
|
267
|
+
const trimmedMessage = message.trim().toLowerCase();
|
|
268
|
+
const parts = trimmedMessage.split(/\s+/);
|
|
269
|
+
const baseCommand = parts[0];
|
|
270
|
+
const commandArg = parts[1];
|
|
271
|
+
const commandHandlers = {
|
|
272
|
+
'/skills': () => {
|
|
273
|
+
const skillsOutput = skillMetadataRef.current || 'No skills loaded.';
|
|
274
|
+
addChatActivity({ type: 'chat-agent', text: skillsOutput });
|
|
275
|
+
},
|
|
276
|
+
'/help': () => {
|
|
277
|
+
const helpText = SLASH_COMMANDS.map((cmd) => `${cmd.name} - ${cmd.description}`).join('\n');
|
|
278
|
+
addChatActivity({ type: 'chat-agent', text: helpText });
|
|
279
|
+
},
|
|
280
|
+
'/clear': () => {
|
|
281
|
+
setChatActivity([]);
|
|
282
|
+
sessionMessagesRef.current = [];
|
|
283
|
+
},
|
|
284
|
+
'/memory': () => {
|
|
285
|
+
const memoryOutput = memoryRef.current || 'No memory stored yet.';
|
|
286
|
+
addChatActivity({ type: 'chat-agent', text: memoryOutput });
|
|
287
|
+
},
|
|
288
|
+
'/backtest': async () => {
|
|
289
|
+
const { HIVE_API_URL } = await import('../../config.js');
|
|
290
|
+
const numThreads = commandArg ? parseInt(commandArg, 10) : null;
|
|
291
|
+
let data;
|
|
292
|
+
if (numThreads !== null && !isNaN(numThreads) && numThreads > 0) {
|
|
293
|
+
// Fetch from API
|
|
294
|
+
addChatActivity({
|
|
295
|
+
type: 'chat-agent',
|
|
296
|
+
text: `Fetching ${numThreads} resolved threads...`,
|
|
297
|
+
});
|
|
298
|
+
const fetchResult = await fetchBacktestThreads(numThreads, HIVE_API_URL);
|
|
299
|
+
if (fetchResult.success) {
|
|
300
|
+
data = fetchResult.data;
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
addChatActivity({
|
|
304
|
+
type: 'chat-agent',
|
|
305
|
+
text: `API fetch failed (${fetchResult.error}), falling back to default dataset...`,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Use default dataset
|
|
310
|
+
if (!data) {
|
|
311
|
+
addChatActivity({
|
|
312
|
+
type: 'chat-agent',
|
|
313
|
+
text: 'Starting backtest against default dataset...',
|
|
314
|
+
});
|
|
315
|
+
data = loadDefaultBacktest();
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const config = {
|
|
319
|
+
agentPath: process.cwd(),
|
|
320
|
+
soulContent: soulContentRef.current,
|
|
321
|
+
strategyContent: strategyContentRef.current,
|
|
322
|
+
agentName: agentName,
|
|
323
|
+
};
|
|
324
|
+
const result = await runBacktest(data, config, {
|
|
325
|
+
onThreadStart: (index, total, thread) => {
|
|
326
|
+
addChatActivity({
|
|
327
|
+
type: 'chat-agent',
|
|
328
|
+
text: `Processing ${index + 1}/${total}: ${thread.project_name}...`,
|
|
329
|
+
});
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
const report = buildTextReport(result);
|
|
333
|
+
addChatActivity({ type: 'chat-agent', text: report });
|
|
334
|
+
sessionMessagesRef.current.push({ role: 'assistant', content: report });
|
|
335
|
+
}
|
|
336
|
+
catch (err) {
|
|
337
|
+
const errMessage = err instanceof Error ? err.message : String(err);
|
|
338
|
+
addChatActivity({ type: 'chat-error', text: `Backtest failed: ${errMessage}` });
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
const handler = commandHandlers[baseCommand];
|
|
343
|
+
if (handler) {
|
|
344
|
+
await handler();
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
// Unknown command
|
|
348
|
+
addChatActivity({ type: 'chat-error', text: `Unknown command: ${message}` });
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
addChatActivity({ type: 'chat-user', text: message });
|
|
352
|
+
sessionMessagesRef.current.push({ role: 'user', content: message });
|
|
353
|
+
chatCountSinceExtractRef.current += 1;
|
|
354
|
+
if (chatCountSinceExtractRef.current >= 3 && !extractingRef.current) {
|
|
355
|
+
extractingRef.current = true;
|
|
356
|
+
const messagesSnapshot = [...sessionMessagesRef.current];
|
|
357
|
+
extractAndSaveMemory(messagesSnapshot)
|
|
358
|
+
.then((newMemory) => {
|
|
359
|
+
if (newMemory !== null) {
|
|
360
|
+
memoryRef.current = newMemory;
|
|
361
|
+
}
|
|
362
|
+
chatCountSinceExtractRef.current = 0;
|
|
363
|
+
})
|
|
364
|
+
.catch(() => { })
|
|
365
|
+
.finally(() => {
|
|
366
|
+
extractingRef.current = false;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
setChatStreaming(true);
|
|
370
|
+
setChatBuffer('');
|
|
371
|
+
try {
|
|
372
|
+
const { system, prompt } = buildChatPrompt(soulContentRef.current, strategyContentRef.current, {
|
|
373
|
+
recentThreadSummaries: recentThreadsRef.current,
|
|
374
|
+
recentPredictions: recentPredictionsRef.current,
|
|
375
|
+
sessionMessages: sessionMessagesRef.current.slice(-20),
|
|
376
|
+
memory: memoryRef.current,
|
|
377
|
+
userMessage: message,
|
|
378
|
+
});
|
|
379
|
+
const model = await getModel();
|
|
380
|
+
const cacheableSystem = {
|
|
381
|
+
role: 'system',
|
|
382
|
+
content: system,
|
|
383
|
+
providerOptions: {
|
|
384
|
+
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
const agent = new ToolLoopAgent({
|
|
388
|
+
model,
|
|
389
|
+
instructions: cacheableSystem,
|
|
390
|
+
tools: {
|
|
391
|
+
editSection: editSectionTool,
|
|
392
|
+
fetchRules: fetchRulesTool,
|
|
393
|
+
...skillToolsRef.current,
|
|
394
|
+
},
|
|
395
|
+
maxOutputTokens: 4096,
|
|
396
|
+
});
|
|
397
|
+
const result = await agent.stream({
|
|
398
|
+
prompt,
|
|
399
|
+
onStepFinish: async ({ toolResults }) => {
|
|
400
|
+
for (const toolResult of toolResults) {
|
|
401
|
+
if (toolResult.toolName === 'editSection') {
|
|
402
|
+
const output = String(toolResult.output);
|
|
403
|
+
// Only reload if update was successful
|
|
404
|
+
if (output.startsWith('Updated')) {
|
|
405
|
+
const config = await loadAgentConfig();
|
|
406
|
+
soulContentRef.current = config.soulContent;
|
|
407
|
+
strategyContentRef.current = config.strategyContent;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
let fullResponse = '';
|
|
414
|
+
let lastFlushTime = 0;
|
|
415
|
+
const THROTTLE_MS = 80;
|
|
416
|
+
const streamErrors = [];
|
|
417
|
+
for await (const part of result.fullStream) {
|
|
418
|
+
if (part.type === 'text-delta') {
|
|
419
|
+
fullResponse += part.text;
|
|
420
|
+
const now = Date.now();
|
|
421
|
+
if (now - lastFlushTime >= THROTTLE_MS) {
|
|
422
|
+
setChatBuffer(fullResponse);
|
|
423
|
+
lastFlushTime = now;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else if (part.type === 'error') {
|
|
427
|
+
const errMsg = typeof part.error === 'string' ? part.error : String(part.error);
|
|
428
|
+
streamErrors.push(errMsg);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Always flush the final value
|
|
432
|
+
setChatBuffer(fullResponse);
|
|
433
|
+
// Surface tool results when the model didn't produce follow-up text
|
|
434
|
+
const steps = await result.steps;
|
|
435
|
+
for (const step of steps) {
|
|
436
|
+
for (const toolResult of step.toolResults) {
|
|
437
|
+
const output = String(toolResult.output);
|
|
438
|
+
if (!fullResponse.includes(output)) {
|
|
439
|
+
const suffix = `\n[${output}]`;
|
|
440
|
+
fullResponse += suffix;
|
|
441
|
+
setChatBuffer(fullResponse);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (fullResponse.trim().length === 0) {
|
|
446
|
+
const errorText = streamErrors.length > 0
|
|
447
|
+
? `Chat error: ${streamErrors.join('; ').slice(0, 120)}`
|
|
448
|
+
: 'No response generated. Try again or rephrase.';
|
|
449
|
+
addChatActivity({ type: 'chat-error', text: errorText });
|
|
450
|
+
setChatBuffer('');
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
sessionMessagesRef.current.push({ role: 'assistant', content: fullResponse });
|
|
454
|
+
addChatActivity({ type: 'chat-agent', text: fullResponse });
|
|
455
|
+
setChatBuffer('');
|
|
456
|
+
}
|
|
457
|
+
catch (err) {
|
|
458
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
459
|
+
addChatActivity({ type: 'chat-error', text: `Chat error: ${raw.slice(0, 120)}` });
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
setChatStreaming(false);
|
|
463
|
+
}
|
|
464
|
+
}, [chatStreaming, addChatActivity]);
|
|
465
|
+
return {
|
|
466
|
+
connected,
|
|
467
|
+
agentName,
|
|
468
|
+
agentBio,
|
|
469
|
+
modelInfo,
|
|
470
|
+
pollActivity,
|
|
471
|
+
chatActivity,
|
|
472
|
+
input,
|
|
473
|
+
chatStreaming,
|
|
474
|
+
chatBuffer,
|
|
475
|
+
predictionCount,
|
|
476
|
+
termWidth,
|
|
477
|
+
setInput,
|
|
478
|
+
handleChatSubmit,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function buildMemoryExtractionPrompt(context) {
|
|
2
|
+
const { currentMemory, sessionMessages, lineCount } = context;
|
|
3
|
+
// ── System (static — cached by providers) ──
|
|
4
|
+
const system = `You are an AI trading agent's memory system. Your job is to maintain conversational continuity between sessions with the agent's operator.
|
|
5
|
+
|
|
6
|
+
Review the session data 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.
|
|
7
|
+
|
|
8
|
+
Focus on extracting:
|
|
9
|
+
1. **Topics discussed** — what subjects came up in conversation (e.g., "we talked about ETH gas fees", "operator asked about macro outlook")
|
|
10
|
+
2. **Operator interests and concerns** — what the operator cares about, recurring themes, questions they've raised
|
|
11
|
+
3. **Ongoing conversational threads** — topics that span multiple sessions or feel unresolved
|
|
12
|
+
4. **Operator preferences** — how they like to interact, what they find useful or annoying
|
|
13
|
+
|
|
14
|
+
Do NOT save:
|
|
15
|
+
- Market predictions, signal analysis, or trading insights — a separate results-based learning system will handle those in the future
|
|
16
|
+
- Raw price data or signal summaries
|
|
17
|
+
- Routine prediction outcomes
|
|
18
|
+
|
|
19
|
+
Follow these rules:
|
|
20
|
+
1. **Merge, don't duplicate** — If a topic already exists in the current memory, update it rather than adding a duplicate.
|
|
21
|
+
2. **Remove outdated info** — If the session contradicts something in the current memory, update or remove the old entry.
|
|
22
|
+
3. **Stay concise** — Each entry should be 1-2 lines. Use bullet points.
|
|
23
|
+
4. **Organize by topic** — Use markdown headers to group related context (e.g., "## Conversations", "## Operator Interests", "## Ongoing Threads").
|
|
24
|
+
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.
|
|
25
|
+
6. **Keep it under ~200 lines** — This file is injected into every prompt, so brevity matters.
|
|
26
|
+
|
|
27
|
+
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.`;
|
|
28
|
+
// ── Prompt (dynamic — changes every call) ──
|
|
29
|
+
let sessionSection = '';
|
|
30
|
+
if (sessionMessages.length > 0) {
|
|
31
|
+
const listed = sessionMessages
|
|
32
|
+
.map((m) => `${m.role === 'user' ? 'Operator' : 'Agent'}: ${m.content}`)
|
|
33
|
+
.join('\n');
|
|
34
|
+
sessionSection = `\n## Session Chat Log\n\n${listed}\n`;
|
|
35
|
+
}
|
|
36
|
+
const currentMemorySection = currentMemory.trim().length > 0
|
|
37
|
+
? `\n## Current MEMORY.md\n\n\`\`\`markdown\n${currentMemory}\n\`\`\`\n`
|
|
38
|
+
: '\n## Current MEMORY.md\n\n(empty - this is a fresh agent)\n';
|
|
39
|
+
const consolidationNote = lineCount > 200
|
|
40
|
+
? `\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`
|
|
41
|
+
: '';
|
|
42
|
+
const prompt = `${currentMemorySection}${consolidationNote}
|
|
43
|
+
## Session Activity
|
|
44
|
+
${sessionSection}
|
|
45
|
+
Update the MEMORY.md based on the session activity above.`;
|
|
46
|
+
return { system, prompt };
|
|
47
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { AI_PROVIDERS } from '../ai-providers.js';
|
|
2
|
+
import { getAgentProviderKeys } from '../load-agent-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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { symbols } from './theme.js';
|
|
3
|
+
const exitImmediately = (exitCode = 0) => {
|
|
4
|
+
process.exit(exitCode);
|
|
5
|
+
};
|
|
6
|
+
export function setupProcessLifecycle() {
|
|
7
|
+
// Unhandled rejection handler
|
|
8
|
+
process.on('unhandledRejection', (reason) => {
|
|
9
|
+
const raw = reason instanceof Error ? reason.message : String(reason);
|
|
10
|
+
const message = raw.length > 200 ? raw.slice(0, 200) + '\u2026' : raw;
|
|
11
|
+
console.error(chalk.red(` ${symbols.cross} Unhandled: ${message}`));
|
|
12
|
+
});
|
|
13
|
+
// No alternate screen buffer — normal buffer allows terminal scrollback
|
|
14
|
+
// so users can scroll up to see historical poll activity.
|
|
15
|
+
// <Static> items from Ink flow into the scrollback naturally.
|
|
16
|
+
process.on('SIGINT', () => exitImmediately(0));
|
|
17
|
+
process.on('SIGTERM', () => exitImmediately(0));
|
|
18
|
+
}
|