@zhive/cli 0.6.2 → 0.6.3
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/{services/agent → agent}/analysis.js +5 -5
- package/dist/agent/app.js +122 -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/hooks/useAgent.js +480 -0
- package/dist/{services/agent/prompts → agent}/memory-prompt.js +22 -20
- package/dist/{services/agent/helpers → agent}/model.js +2 -2
- package/dist/agent/process-lifecycle.js +18 -0
- package/dist/{services/agent/prompts → agent}/prompt.js +54 -80
- package/dist/agent/run-headless.js +189 -0
- package/dist/agent/theme.js +41 -0
- package/dist/{services/agent → agent}/tools/market/client.js +1 -1
- package/dist/{services/agent → agent}/tools/mindshare/client.js +1 -1
- package/dist/agent/types.js +1 -0
- package/dist/{services/config/agent.js → agents.js} +2 -2
- 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/install.js +50 -0
- package/dist/commands/megathread/commands/create-comment.js +1 -4
- package/dist/commands/megathread/commands/create-comment.test.js +1 -19
- package/dist/commands/megathread/commands/list.test.js +1 -0
- package/dist/commands/start/commands/prediction.js +1 -2
- package/dist/commands/start/hooks/utils.js +3 -3
- package/dist/commands/start/ui/PollText.js +23 -0
- package/dist/commands/start/ui/PredictionsPanel.js +88 -0
- package/dist/commands/start/ui/SpinnerContext.js +20 -0
- package/dist/components/InputGuard.js +6 -0
- package/dist/components/stdout-spinner.js +48 -0
- package/dist/{services/config/config.js → config.js} +7 -1
- 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/list/ListApp.js +79 -0
- package/dist/{services/agent/env.js → load-agent-env.js} +1 -1
- 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/handler.js +1 -5
- package/dist/shared/config/constant.js +2 -2
- 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 +2 -2
- package/dist/CLAUDE.md +0 -7
- package/dist/backtest/CLAUDE.md +0 -7
- package/dist/cli.js +0 -20
- package/dist/services/config/constant.js +0 -8
- 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/ai-providers.js +0 -66
- /package/dist/{services/agent/prompts → agent}/chat-prompt.js +0 -0
- /package/dist/{services/agent → agent}/config.js +0 -0
- /package/dist/{services/agent/tools → agent}/edit-section.js +0 -0
- /package/dist/{services/agent/tools → agent}/fetch-rules.js +0 -0
- /package/dist/{services/agent → agent}/helpers.js +0 -0
- /package/dist/{services/agent/skills/types.js → agent/objects.js} +0 -0
- /package/dist/{services/agent → agent}/skills/index.js +0 -0
- /package/dist/{services/agent → agent}/skills/skill-parser.js +0 -0
- /package/dist/{services/agent → agent/skills}/types.js +0 -0
- /package/dist/{services/agent → agent}/tools/index.js +0 -0
- /package/dist/{services/agent → agent}/tools/market/index.js +0 -0
- /package/dist/{services/agent → agent}/tools/market/tools.js +0 -0
- /package/dist/{services/agent → agent}/tools/mindshare/index.js +0 -0
- /package/dist/{services/agent → agent}/tools/mindshare/tools.js +0 -0
- /package/dist/{services/agent → agent}/tools/read-skill-tool.js +0 -0
- /package/dist/{services/agent → agent}/tools/ta/index.js +0 -0
- /package/dist/{services/agent → agent}/tools/ta/indicators.js +0 -0
- /package/dist/{services/ai-providers.js → ai-providers.js} +0 -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
|
+
}
|
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
export function buildMemoryExtractionPrompt(context) {
|
|
2
2
|
const { currentMemory, sessionMessages, lineCount } = context;
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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.
|
|
21
5
|
|
|
22
|
-
Review the session
|
|
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.
|
|
23
7
|
|
|
24
8
|
Focus on extracting:
|
|
25
9
|
1. **Topics discussed** — what subjects came up in conversation (e.g., "we talked about ETH gas fees", "operator asked about macro outlook")
|
|
@@ -41,5 +25,23 @@ Follow these rules:
|
|
|
41
25
|
6. **Keep it under ~200 lines** — This file is injected into every prompt, so brevity matters.
|
|
42
26
|
|
|
43
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.`;
|
|
44
|
-
|
|
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 };
|
|
45
47
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AI_PROVIDERS } from '
|
|
2
|
-
import { getAgentProviderKeys } from '../env.js';
|
|
1
|
+
import { AI_PROVIDERS } from '../ai-providers.js';
|
|
2
|
+
import { getAgentProviderKeys } from '../load-agent-env.js';
|
|
3
3
|
const PROVIDERS = [
|
|
4
4
|
{
|
|
5
5
|
label: 'Anthropic',
|
|
@@ -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
|
+
}
|