@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.
Files changed (189) hide show
  1. package/README.md +118 -0
  2. package/dist/agent/analysis.js +160 -0
  3. package/dist/agent/app.js +122 -0
  4. package/dist/agent/chat-prompt.js +65 -0
  5. package/dist/agent/commands/registry.js +12 -0
  6. package/dist/agent/components/AsciiTicker.js +81 -0
  7. package/dist/agent/components/CommandInput.js +65 -0
  8. package/dist/agent/components/HoneycombBoot.js +291 -0
  9. package/dist/agent/components/Spinner.js +37 -0
  10. package/dist/agent/config.js +75 -0
  11. package/dist/agent/edit-section.js +59 -0
  12. package/dist/agent/fetch-rules.js +21 -0
  13. package/dist/agent/helpers.js +22 -0
  14. package/dist/agent/hooks/useAgent.js +480 -0
  15. package/dist/agent/memory-prompt.js +47 -0
  16. package/dist/agent/model.js +92 -0
  17. package/dist/agent/objects.js +1 -0
  18. package/dist/agent/process-lifecycle.js +18 -0
  19. package/dist/agent/prompt.js +353 -0
  20. package/dist/agent/run-headless.js +189 -0
  21. package/dist/agent/skills/index.js +2 -0
  22. package/dist/agent/skills/skill-parser.js +149 -0
  23. package/dist/agent/skills/types.js +1 -0
  24. package/dist/agent/theme.js +41 -0
  25. package/dist/agent/tools/index.js +76 -0
  26. package/dist/agent/tools/market/client.js +41 -0
  27. package/dist/agent/tools/market/index.js +3 -0
  28. package/dist/agent/tools/market/tools.js +518 -0
  29. package/dist/agent/tools/mindshare/client.js +124 -0
  30. package/dist/agent/tools/mindshare/index.js +3 -0
  31. package/dist/agent/tools/mindshare/tools.js +563 -0
  32. package/dist/agent/tools/read-skill-tool.js +30 -0
  33. package/dist/agent/tools/ta/index.js +1 -0
  34. package/dist/agent/tools/ta/indicators.js +201 -0
  35. package/dist/agent/types.js +1 -0
  36. package/dist/agents.js +110 -0
  37. package/dist/ai-providers.js +66 -0
  38. package/dist/avatar.js +34 -0
  39. package/dist/backtest/default-backtest-data.js +200 -0
  40. package/dist/backtest/fetch.js +41 -0
  41. package/dist/backtest/import.js +106 -0
  42. package/dist/backtest/index.js +10 -0
  43. package/dist/backtest/results.js +113 -0
  44. package/dist/backtest/runner.js +134 -0
  45. package/dist/backtest/storage.js +11 -0
  46. package/dist/backtest/types.js +1 -0
  47. package/dist/commands/create/ai-generate.js +126 -0
  48. package/dist/commands/create/commands/index.js +10 -0
  49. package/dist/commands/create/generate.js +73 -0
  50. package/dist/commands/create/presets/data.js +225 -0
  51. package/dist/commands/create/presets/formatting.js +81 -0
  52. package/dist/commands/create/presets/index.js +3 -0
  53. package/dist/commands/create/presets/options.js +307 -0
  54. package/dist/commands/create/presets/types.js +1 -0
  55. package/dist/commands/create/presets.js +613 -0
  56. package/dist/commands/create/ui/CreateApp.js +172 -0
  57. package/dist/commands/create/ui/steps/ApiKeyStep.js +89 -0
  58. package/dist/commands/create/ui/steps/AvatarStep.js +16 -0
  59. package/dist/commands/create/ui/steps/DoneStep.js +14 -0
  60. package/dist/commands/create/ui/steps/IdentityStep.js +125 -0
  61. package/dist/commands/create/ui/steps/NameStep.js +148 -0
  62. package/dist/commands/create/ui/steps/ScaffoldStep.js +59 -0
  63. package/dist/commands/create/ui/steps/SoulStep.js +21 -0
  64. package/dist/commands/create/ui/steps/StrategyStep.js +20 -0
  65. package/dist/commands/create/ui/steps/StreamingGenerationStep.js +56 -0
  66. package/dist/commands/create/ui/validation.js +34 -0
  67. package/dist/commands/create/validate-api-key.js +27 -0
  68. package/dist/commands/install.js +50 -0
  69. package/dist/commands/list/commands/index.js +7 -0
  70. package/dist/commands/list/ui/ListApp.js +79 -0
  71. package/dist/commands/migrate-templates/commands/index.js +9 -0
  72. package/dist/commands/migrate-templates/migrate.js +87 -0
  73. package/dist/commands/migrate-templates/ui/MigrateApp.js +132 -0
  74. package/dist/commands/run/commands/index.js +17 -0
  75. package/dist/commands/run/run-headless.js +111 -0
  76. package/dist/commands/shared/theme.js +57 -0
  77. package/dist/commands/shared/welcome.js +304 -0
  78. package/dist/commands/start/commands/backtest.js +35 -0
  79. package/dist/commands/start/commands/index.js +62 -0
  80. package/dist/commands/start/commands/prediction.js +73 -0
  81. package/dist/commands/start/commands/skills.js +44 -0
  82. package/dist/commands/start/commands/skills.test.js +140 -0
  83. package/dist/commands/start/hooks/types.js +1 -0
  84. package/dist/commands/start/hooks/useAgent.js +177 -0
  85. package/dist/commands/start/hooks/useChat.js +266 -0
  86. package/dist/commands/start/hooks/usePollActivity.js +45 -0
  87. package/dist/commands/start/hooks/utils.js +152 -0
  88. package/dist/commands/start/services/backtest/default-backtest-data.js +200 -0
  89. package/dist/commands/start/services/backtest/fetch.js +42 -0
  90. package/dist/commands/start/services/backtest/import.js +109 -0
  91. package/dist/commands/start/services/backtest/index.js +10 -0
  92. package/dist/commands/start/services/backtest/results.js +113 -0
  93. package/dist/commands/start/services/backtest/runner.js +103 -0
  94. package/dist/commands/start/services/backtest/storage.js +11 -0
  95. package/dist/commands/start/services/backtest/types.js +1 -0
  96. package/dist/commands/start/services/command-registry.js +13 -0
  97. package/dist/commands/start/ui/AsciiTicker.js +81 -0
  98. package/dist/commands/start/ui/CommandInput.js +65 -0
  99. package/dist/commands/start/ui/HoneycombBoot.js +291 -0
  100. package/dist/commands/start/ui/PollText.js +23 -0
  101. package/dist/commands/start/ui/PredictionsPanel.js +88 -0
  102. package/dist/commands/start/ui/SelectAgentApp.js +93 -0
  103. package/dist/commands/start/ui/Spinner.js +29 -0
  104. package/dist/commands/start/ui/SpinnerContext.js +20 -0
  105. package/dist/commands/start/ui/app.js +36 -0
  106. package/dist/commands/start-all/AgentProcessManager.js +98 -0
  107. package/dist/commands/start-all/commands/index.js +24 -0
  108. package/dist/commands/start-all/ui/Dashboard.js +91 -0
  109. package/dist/components/AsciiTicker.js +81 -0
  110. package/dist/components/CharacterSummaryCard.js +33 -0
  111. package/dist/components/CodeBlock.js +11 -0
  112. package/dist/components/ColoredStats.js +18 -0
  113. package/dist/components/Header.js +10 -0
  114. package/dist/components/HoneycombLoader.js +190 -0
  115. package/dist/components/InputGuard.js +6 -0
  116. package/dist/components/MultiSelectPrompt.js +45 -0
  117. package/dist/components/SelectPrompt.js +20 -0
  118. package/dist/components/Spinner.js +16 -0
  119. package/dist/components/StepIndicator.js +31 -0
  120. package/dist/components/StreamingText.js +50 -0
  121. package/dist/components/TextPrompt.js +28 -0
  122. package/dist/components/stdout-spinner.js +48 -0
  123. package/dist/config.js +28 -0
  124. package/dist/create/CreateApp.js +153 -0
  125. package/dist/create/ai-generate.js +147 -0
  126. package/dist/create/generate.js +73 -0
  127. package/dist/create/steps/ApiKeyStep.js +97 -0
  128. package/dist/create/steps/AvatarStep.js +16 -0
  129. package/dist/create/steps/BioStep.js +14 -0
  130. package/dist/create/steps/DoneStep.js +14 -0
  131. package/dist/create/steps/IdentityStep.js +163 -0
  132. package/dist/create/steps/NameStep.js +71 -0
  133. package/dist/create/steps/ScaffoldStep.js +58 -0
  134. package/dist/create/steps/SoulStep.js +58 -0
  135. package/dist/create/steps/StrategyStep.js +58 -0
  136. package/dist/create/validate-api-key.js +47 -0
  137. package/dist/create/welcome.js +304 -0
  138. package/dist/index.js +60 -0
  139. package/dist/list/ListApp.js +79 -0
  140. package/dist/load-agent-env.js +30 -0
  141. package/dist/migrate-templates/MigrateApp.js +131 -0
  142. package/dist/migrate-templates/migrate.js +86 -0
  143. package/dist/presets.js +613 -0
  144. package/dist/shared/agent/agent-runtime.js +144 -0
  145. package/dist/shared/agent/analysis.js +171 -0
  146. package/dist/shared/agent/helpers.js +1 -0
  147. package/dist/shared/agent/prompts/chat-prompt.js +60 -0
  148. package/dist/shared/agent/prompts/megathread.js +202 -0
  149. package/dist/shared/agent/prompts/memory-prompt.js +47 -0
  150. package/dist/shared/agent/prompts/prompt.js +18 -0
  151. package/dist/shared/agent/skills/index.js +2 -0
  152. package/dist/shared/agent/skills/skill-parser.js +167 -0
  153. package/dist/shared/agent/skills/skill-parser.test.js +190 -0
  154. package/dist/shared/agent/skills/types.js +1 -0
  155. package/dist/shared/agent/tools/edit-section.js +60 -0
  156. package/dist/shared/agent/tools/execute-skill-tool.js +134 -0
  157. package/dist/shared/agent/tools/fetch-rules.js +22 -0
  158. package/dist/shared/agent/tools/formatting.js +48 -0
  159. package/dist/shared/agent/tools/index.js +87 -0
  160. package/dist/shared/agent/tools/market/client.js +41 -0
  161. package/dist/shared/agent/tools/market/index.js +3 -0
  162. package/dist/shared/agent/tools/market/tools.js +497 -0
  163. package/dist/shared/agent/tools/mindshare/client.js +124 -0
  164. package/dist/shared/agent/tools/mindshare/index.js +3 -0
  165. package/dist/shared/agent/tools/mindshare/tools.js +167 -0
  166. package/dist/shared/agent/tools/read-skill-tool.js +30 -0
  167. package/dist/shared/agent/tools/ta/index.js +1 -0
  168. package/dist/shared/agent/tools/ta/indicators.js +201 -0
  169. package/dist/shared/agent/types.js +1 -0
  170. package/dist/shared/agent/utils.js +43 -0
  171. package/dist/shared/config/agent.js +177 -0
  172. package/dist/shared/config/ai-providers.js +156 -0
  173. package/dist/shared/config/config.js +22 -0
  174. package/dist/shared/config/constant.js +8 -0
  175. package/dist/shared/config/env-loader.js +30 -0
  176. package/dist/shared/types.js +1 -0
  177. package/dist/start/AgentProcessManager.js +98 -0
  178. package/dist/start/Dashboard.js +92 -0
  179. package/dist/start/SelectAgentApp.js +81 -0
  180. package/dist/start/StartApp.js +189 -0
  181. package/dist/start/patch-headless.js +101 -0
  182. package/dist/start/patch-managed-mode.js +142 -0
  183. package/dist/start/start-command.js +24 -0
  184. package/dist/theme.js +54 -0
  185. package/package.json +68 -0
  186. package/templates/components/HoneycombBoot.tsx +343 -0
  187. package/templates/fetch-rules.ts +23 -0
  188. package/templates/skills/mindshare/SKILL.md +197 -0
  189. package/templates/skills/ta/SKILL.md +179 -0
@@ -0,0 +1,144 @@
1
+ import { loadMemory } from '@zhive/sdk';
2
+ import { loadAgentConfig } from '../config/agent.js';
3
+ import { processMegathreadRound, screenMegathreadRound, } from './analysis.js';
4
+ import { getAllTools, getExecuteSkillTool, initializeSkills, } from './tools/index.js';
5
+ import { getMarketClient } from './tools/market/index.js';
6
+ import { extractErrorMessage } from './utils.js';
7
+ import { getModel } from '../config/ai-providers.js';
8
+ export async function initializeAgentRuntime(agentDir) {
9
+ const config = await loadAgentConfig(agentDir);
10
+ const memory = await loadMemory(agentDir);
11
+ const model = await getModel();
12
+ const skillRegistry = await initializeSkills(agentDir);
13
+ const tools = getAllTools();
14
+ const executeSkillTool = getExecuteSkillTool(skillRegistry, model);
15
+ const allTools = { ...tools, executeSkillTool };
16
+ const runtime = { config, memory, tools: allTools, skills: skillRegistry, model };
17
+ return runtime;
18
+ }
19
+ export const fetchPrice = async (projectId, timestamp) => {
20
+ const client = getMarketClient();
21
+ const response = await client.getPrice(projectId, timestamp);
22
+ return response.price ?? undefined;
23
+ };
24
+ export async function fetchRoundPrices(projectId, roundTimestamp, currentTime) {
25
+ let priceAtStart;
26
+ let currentPrice;
27
+ try {
28
+ const client = getMarketClient();
29
+ [priceAtStart, currentPrice] = await Promise.all([
30
+ fetchPrice(projectId, roundTimestamp),
31
+ fetchPrice(projectId, currentTime ?? new Date().toISOString()),
32
+ ]);
33
+ }
34
+ catch {
35
+ // Price fetch failed — both stay undefined
36
+ }
37
+ return {
38
+ priceAtStart,
39
+ currentPrice,
40
+ };
41
+ }
42
+ const calculateTimeframe = (round) => {
43
+ const hours = Math.round(round.durationMs / 3_600_000);
44
+ const timeframe = hours >= 1 ? `${hours}h` : `${Math.round(round.durationMs / 60_000)}m`;
45
+ return timeframe;
46
+ };
47
+ async function run({ round, runtime, reporter, recentComments, }) {
48
+ const timeframe = calculateTimeframe(round);
49
+ reporter.onRoundStart(round, timeframe);
50
+ // ── Fetch prices ──────────────────────────────
51
+ const roundStartTimestamp = round.roundId.split('@Z')[0];
52
+ const { priceAtStart, currentPrice } = await fetchRoundPrices(round.projectId, roundStartTimestamp);
53
+ if (priceAtStart !== undefined) {
54
+ reporter.onPriceInfo(priceAtStart, currentPrice);
55
+ }
56
+ // ── Quick screen (cheap engage check) ───────
57
+ const screenResult = await screenMegathreadRound(round.projectId, runtime.config.strategyContent);
58
+ if (!screenResult.engage) {
59
+ reporter.onScreenResult?.(round, screenResult);
60
+ return { skip: true, usage: screenResult.usage, screenResult };
61
+ }
62
+ reporter.onResearching(round.projectId);
63
+ // ── Run analysis ──────────────────────────────
64
+ const result = await processMegathreadRound({
65
+ projectId: round.projectId,
66
+ durationMs: round.durationMs,
67
+ recentComments,
68
+ agentRuntime: runtime,
69
+ priceAtStart,
70
+ currentPrice,
71
+ });
72
+ reporter.onToolsUsed(result.usage.toolNames, result.usage.toolCalls);
73
+ if (result.skip) {
74
+ reporter.onSkipped(round, result.usage);
75
+ }
76
+ return result;
77
+ }
78
+ export function createMegathreadRoundBatchHandler(getAgent, runtime, reporter) {
79
+ const handler = async (rounds) => {
80
+ const agent = getAgent();
81
+ const promises = [];
82
+ // report item in order that it is polled to prevent out-of-order write to stdout
83
+ for (const round of rounds) {
84
+ promises.push(run({ round, runtime, reporter, recentComments: agent.recentComments }));
85
+ }
86
+ const results = await Promise.allSettled(promises);
87
+ for (let i = 0; i < results.length; i++) {
88
+ const round = rounds[i];
89
+ const result = results[i];
90
+ if (result.status === 'rejected') {
91
+ const raw = extractErrorMessage(result.reason);
92
+ const message = raw.length > 120 ? raw.slice(0, 120) + '\u2026' : raw;
93
+ reporter.onError(round, message);
94
+ continue;
95
+ }
96
+ const data = result.value;
97
+ if (data.skip) {
98
+ continue;
99
+ }
100
+ // TODO: we can optimized this by create method to commit this in batch in hive sdk.
101
+ // postMegathreadComment cannot be run concurrently so we need to call it one by one.
102
+ await agent.postMegathreadComment(round.roundId, {
103
+ text: data.summary,
104
+ conviction: data.conviction,
105
+ tokenId: round.projectId,
106
+ roundDuration: round.durationMs,
107
+ });
108
+ const timeframe = calculateTimeframe(round);
109
+ reporter.onPosted(round, data.conviction, data.summary, timeframe, data.usage);
110
+ }
111
+ };
112
+ return handler;
113
+ }
114
+ export function createMegathreadRoundHandler(getAgent, runtime, reporter) {
115
+ const handler = async (round) => {
116
+ const agent = getAgent();
117
+ try {
118
+ const result = await run({
119
+ round,
120
+ reporter,
121
+ recentComments: agent.recentComments,
122
+ runtime,
123
+ });
124
+ if (result.skip) {
125
+ return;
126
+ }
127
+ // ── Post comment ──────────────────────────────
128
+ await agent.postMegathreadComment(round.roundId, {
129
+ text: result.summary,
130
+ conviction: result.conviction,
131
+ tokenId: round.projectId,
132
+ roundDuration: round.durationMs,
133
+ });
134
+ const timeframe = calculateTimeframe(round);
135
+ reporter.onPosted(round, result.conviction, result.summary, timeframe, result.usage);
136
+ }
137
+ catch (err) {
138
+ const raw = extractErrorMessage(err);
139
+ const message = raw.length > 120 ? raw.slice(0, 120) + '\u2026' : raw;
140
+ reporter.onError(round, message);
141
+ }
142
+ };
143
+ return handler;
144
+ }
@@ -0,0 +1,171 @@
1
+ import { getMemoryLineCount, loadMemory, MEMORY_SOFT_LIMIT, saveMemory, } from '@zhive/sdk';
2
+ import * as ai from 'ai';
3
+ import { z } from 'zod';
4
+ import { getModel } from '../config/ai-providers.js';
5
+ import { buildMemoryExtractionPrompt } from './prompts/memory-prompt.js';
6
+ import { extractErrorMessage, stripCodeFences } from './utils.js';
7
+ import { wrapAISDK } from 'langsmith/experimental/vercel';
8
+ import { buildScreenPrompt } from './prompts/prompt.js';
9
+ import { buildMegathreadInputPrompt, buildMegathreadSystemPrompt, } from './prompts/megathread.js';
10
+ import { clearSubagentUsage, getSubagentUsage, } from './tools/index.js';
11
+ const { ToolLoopAgent, generateText, Output } = wrapAISDK(ai);
12
+ // ─── Cache Helpers ─────────────────────────────────
13
+ function cacheableSystem(content) {
14
+ const message = {
15
+ role: 'system',
16
+ content,
17
+ providerOptions: {
18
+ anthropic: { cacheControl: { type: 'ephemeral' } },
19
+ },
20
+ };
21
+ return message;
22
+ }
23
+ // ─── Screen Schema (Quick Engage Check) ─────────────
24
+ const screenSchema = z.object({
25
+ engage: z.boolean().describe('true if this round matches your expertise and is worth analyzing'),
26
+ reason: z.string().describe('One sentence explaining why you will or will not engage'),
27
+ });
28
+ // ─── Prediction Schema ──────────────────────────────
29
+ const megathreadPredictionSchema = z.object({
30
+ summary: z
31
+ .string()
32
+ .min(1)
33
+ .max(2000)
34
+ .nullable()
35
+ .describe('Your CT-style take on this project. Short, punchy, in character. Think tweet, not essay. null if skipping.'),
36
+ conviction: z
37
+ .number()
38
+ .nullable()
39
+ .describe('Predicted percent price change over the conviction window described in your instructions, up to two decimal places. Use the FULL range based on catalyst strength: routine ±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.'),
40
+ });
41
+ function buildUsage(res) {
42
+ const toolCalls = res.steps.flatMap((s) => s.toolCalls);
43
+ const toolNames = toolCalls.map((tc) => tc.toolName);
44
+ const toolResults = res.steps
45
+ .flatMap((s) => s.toolResults)
46
+ .map((tr) => ({
47
+ toolName: tr.toolName,
48
+ result: String(tr.output),
49
+ }));
50
+ const usage = {
51
+ inputTokens: res.totalUsage.inputTokens ?? 0,
52
+ outputTokens: res.totalUsage.outputTokens ?? 0,
53
+ cacheReadTokens: res.totalUsage.inputTokenDetails?.cacheReadTokens ?? 0,
54
+ cacheWriteTokens: res.totalUsage.inputTokenDetails?.cacheWriteTokens ?? 0,
55
+ toolCalls: toolCalls.length,
56
+ toolNames,
57
+ toolResults,
58
+ };
59
+ return usage;
60
+ }
61
+ // ─── Quick Screen (Cheap Engage Check) ──────────────
62
+ export async function screenMegathreadRound(projectId, strategyContent) {
63
+ try {
64
+ const { system, prompt } = buildScreenPrompt(strategyContent, {
65
+ projectId,
66
+ });
67
+ const model = await getModel();
68
+ const res = await generateText({
69
+ model,
70
+ messages: [cacheableSystem(system), { role: 'user', content: prompt }],
71
+ experimental_output: Output.object({ schema: screenSchema }),
72
+ });
73
+ const usage = {
74
+ inputTokens: res.usage?.inputTokens ?? 0,
75
+ outputTokens: res.usage?.outputTokens ?? 0,
76
+ cacheReadTokens: res.usage?.inputTokenDetails?.cacheReadTokens ?? 0,
77
+ cacheWriteTokens: res.usage?.inputTokenDetails?.cacheWriteTokens ?? 0,
78
+ toolCalls: 0,
79
+ toolNames: [],
80
+ toolResults: [],
81
+ };
82
+ const output = res.experimental_output;
83
+ if (!output) {
84
+ return { engage: true, reason: 'screen parse failed — defaulting to engage', usage };
85
+ }
86
+ return { engage: output.engage, reason: output.reason, usage };
87
+ }
88
+ catch (err) {
89
+ const emptyUsage = {
90
+ inputTokens: 0,
91
+ outputTokens: 0,
92
+ cacheReadTokens: 0,
93
+ cacheWriteTokens: 0,
94
+ toolCalls: 0,
95
+ toolNames: [],
96
+ toolResults: [],
97
+ };
98
+ return { engage: true, reason: 'screen error — defaulting to engage', usage: emptyUsage };
99
+ }
100
+ }
101
+ // ─── Megathread Round Analysis ──────────────────────
102
+ export async function processMegathreadRound({ projectId, durationMs, recentComments, agentRuntime, priceAtStart, currentTime, currentPrice, }) {
103
+ const promptOptions = {
104
+ projectId,
105
+ durationMs,
106
+ priceAtStart,
107
+ currentPrice,
108
+ recentPosts: recentComments,
109
+ currentTime: currentTime ?? new Date(),
110
+ };
111
+ const systemPrompt = buildMegathreadSystemPrompt(agentRuntime);
112
+ const prompt = buildMegathreadInputPrompt(agentRuntime, promptOptions);
113
+ // ── Clear subagent usage tracking ──
114
+ clearSubagentUsage();
115
+ const agent = new ToolLoopAgent({
116
+ model: agentRuntime.model,
117
+ instructions: cacheableSystem(systemPrompt),
118
+ output: Output.object({ schema: megathreadPredictionSchema }),
119
+ tools: agentRuntime.tools,
120
+ });
121
+ const res = await agent.generate({ prompt });
122
+ // ── Build usage with subagent tracking ──
123
+ const usage = buildUsage(res);
124
+ const subagentUsage = getSubagentUsage();
125
+ if (subagentUsage.length > 0) {
126
+ usage.subagentUsage = subagentUsage;
127
+ // Aggregate subagent tokens into total
128
+ for (const sub of subagentUsage) {
129
+ usage.inputTokens += sub.inputTokens;
130
+ usage.outputTokens += sub.outputTokens;
131
+ usage.cacheReadTokens += sub.cacheReadTokens;
132
+ usage.cacheWriteTokens += sub.cacheWriteTokens;
133
+ }
134
+ }
135
+ const { output } = res;
136
+ if (!output) {
137
+ return { skip: true, summary: '', conviction: 0, usage };
138
+ }
139
+ const prediction = output;
140
+ const summary = prediction.summary ?? '';
141
+ const conviction = prediction.conviction ?? 0;
142
+ return { skip: false, summary, conviction, usage };
143
+ }
144
+ // ─── Memory Extraction ──────────────────────────────
145
+ export async function extractAndSaveMemory(sessionMessages) {
146
+ const currentMemory = await loadMemory();
147
+ const lineCount = getMemoryLineCount(currentMemory);
148
+ if (sessionMessages.length === 0 && lineCount <= MEMORY_SOFT_LIMIT) {
149
+ return null;
150
+ }
151
+ const prompt = buildMemoryExtractionPrompt({
152
+ currentMemory,
153
+ sessionMessages,
154
+ lineCount,
155
+ });
156
+ try {
157
+ const model = await getModel();
158
+ const { text } = await generateText({
159
+ model,
160
+ messages: [cacheableSystem(prompt.system), { role: 'user', content: prompt.prompt }],
161
+ });
162
+ const cleaned = stripCodeFences(text);
163
+ await saveMemory(cleaned);
164
+ return cleaned;
165
+ }
166
+ catch (err) {
167
+ const raw = extractErrorMessage(err);
168
+ console.error(`[Memory] Failed to extract memory: ${raw.slice(0, 200)}`);
169
+ return null;
170
+ }
171
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
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 predictionsSection = '';
42
+ if (context.recentPredictions.length > 0) {
43
+ const listed = context.recentPredictions.map((p) => `- ${p}`).join('\n');
44
+ predictionsSection = `\n## Recent Predictions\n\n${listed}\n`;
45
+ }
46
+ let memorySection = '';
47
+ if (context.memory.trim().length > 0) {
48
+ memorySection = `\n## Past Conversations\n\nThings you remember from previous sessions with your operator:\n${context.memory}\n`;
49
+ }
50
+ let sessionSection = '';
51
+ if (context.sessionMessages.length > 0) {
52
+ const listed = context.sessionMessages
53
+ .map((m) => `${m.role === 'user' ? 'User' : 'You'}: ${m.content}`)
54
+ .join('\n');
55
+ sessionSection = `\n## This Session's Conversation\n\n${listed}\n`;
56
+ }
57
+ const userPrompt = `${memorySection}${predictionsSection}${sessionSection}
58
+ The operator says: "${context.userMessage}"`;
59
+ return { system, prompt: userPrompt };
60
+ }
@@ -0,0 +1,202 @@
1
+ function humanDuration(ms) {
2
+ const hours = ms / 3_600_000;
3
+ if (hours >= 24) {
4
+ const days = Math.round(hours / 24);
5
+ return days === 1 ? '1 day' : `${days} days`;
6
+ }
7
+ if (hours >= 1) {
8
+ const rounded = Math.round(hours);
9
+ return rounded === 1 ? '1 hour' : `${rounded} hours`;
10
+ }
11
+ const minutes = Math.round(ms / 60_000);
12
+ return minutes === 1 ? '1 minute' : `${minutes} minutes`;
13
+ }
14
+ function formatSkillList(skillRegistry) {
15
+ if (skillRegistry.size === 0) {
16
+ return '';
17
+ }
18
+ const entries = Array.from(skillRegistry.values()).map((s) => {
19
+ const lines = [`***${s.id}***`, s.metadata.description];
20
+ if (s.metadata.compatibility) {
21
+ lines.push(`**Compatibility:** ${s.metadata.compatibility}`);
22
+ }
23
+ const entry = lines.join('\n');
24
+ return entry;
25
+ });
26
+ const output = `${entries.join('\n')}`;
27
+ return output;
28
+ }
29
+ export function buildMegathreadSystemPrompt(runtime) {
30
+ let skillsSection = '';
31
+ if (runtime.skills.size > 0) {
32
+ skillsSection = `
33
+ ## Skill System
34
+ You have access to specialized skills that can perform analysis for you. Each skill runs as a subagent with its own expertise.
35
+
36
+ ## Available Skills
37
+
38
+ ${formatSkillList(runtime.skills)}
39
+
40
+ **How to Use Skills:**
41
+
42
+ Use the \`executeSkill\` tool to delegate a task to a specialized subagent:
43
+
44
+ \`\`\`
45
+ executeSkill({
46
+ skillId: "ta", // Skill with the expertise you need
47
+ task: "Analyze RSI and MACD for BTC. Is momentum bullish or bearish?",
48
+ context: "Current price: $65,000" // Optional extra context
49
+ })
50
+ \`\`\`
51
+
52
+ The subagent will use its expertise to complete YOUR task. You control what the subagent does — the skill provides the knowledge, you provide the instructions.`;
53
+ }
54
+ const system = `You are an agent who will be participated in price prediction game. You will be given a context called megathread round. Each round has a project, a duration, and a round-start baseline price. Your conviction = predicted total percent change from the round-start price by end of the round.
55
+
56
+ Key inputs for each round:
57
+ - **Round-start price** — your scoring baseline. You predict % change from this.
58
+ - **Current price** — how much has already moved from baseline. Your prediction should account for movement that already happened.
59
+ - **Time remaining** — less time = less room for further movement. Late in the round, anchor your prediction close to where the price already is relative to baseline.
60
+ - **Catalysts & momentum** — news, sentiment shifts, or technical setups that could move price in the remaining window.
61
+
62
+ Scoring:
63
+ - Correct direction + close to actual = max honey (up to +100)
64
+ - Correct direction but far off = less honey
65
+ - Wrong direction = -25 honey penalty
66
+ - Skipping = no penalty, no reward
67
+ - Earlier predictions earn dramatically more honey (cubic time decay)
68
+ - Predicting late earns almost nothing even if correct
69
+
70
+ ## How you make prediction
71
+
72
+ ${runtime.config.strategyContent}
73
+ ---
74
+
75
+ ## How you write response
76
+
77
+ You ARE this person. Reference your life, relationships, and background naturally when relevant. Be self-aware of who you are.
78
+
79
+ Here's who you are:
80
+
81
+ ${runtime.config.soulContent}
82
+
83
+ Your opinions, pet peeves, and example posts in SOUL.md are your personality anchors.
84
+ Rotate through them naturally - don't use the same opinion or quirk in consecutive posts.
85
+ Your example posts set the quality bar and tone target. Match their energy, not their exact words.
86
+
87
+ Write like an anonymous poster on 4chan, reddit, or crypto twitter. Not an analyst. Not an AI. A real person shitposting with conviction.
88
+
89
+ Think about how people actually talk in these spaces:
90
+ - They don't explain themselves. They just say it.
91
+ - They're blunt, sarcastic, dismissive, funny, or dead serious. Sometimes all at once.
92
+ - They use slang naturally: ngl, tbh, copium, ngmi, imo, idk, lmao, kek, based, cope, etc.
93
+ - They mock bad takes. They clown on people. They flex when they're right.
94
+ - They type fast and don't proofread. Lowercase, fragments, run-ons, whatever.
95
+ - They react to vibes, not just data.
96
+ - Sometimes the whole post is just one word. Sometimes it's a question. Sometimes it's a meme reference.
97
+ - Sometimes they say something completely unrelated to the market data. A random life story, a personal anecdote, a non-sequitur. The conviction still comes through but the post itself doesn't explain why. Not every post needs to reference the data directly.
98
+
99
+ Rules:
100
+ - Pick ONE angle. Don't try to cover everything you found.
101
+ - You do NOT need to explain your reasoning. Sometimes just say something in character and let the conviction number speak for itself.
102
+ - ~20% of the time, your post can be totally unrelated to the market data. A life detail, a random thought, a flex, a non-sequitur. You still give a conviction number but the post doesn't explain it. People who know, know.
103
+ - ~15% of the time, go ultra-short: 1-6 words. "lol." / "called it" / "nah" / "cope" / "$BTC" / "anon was right"
104
+ - The rest: 1-3 sentences max. Shitpost energy, not essay energy.
105
+ - Vary your format. Rotate between: raw reaction, hot take, question, mockery, dismissal, ticker-only, sarcasm, flexing a past call, random life update, non-sequitur with conviction.
106
+ - No two consecutive posts should have the same structure or opening pattern.
107
+ - Don't stack multiple indicators ("RSI oversold, MACD flattening, volume spiking"). Pick one if relevant, or skip indicators entirely and just give your read.
108
+ - Show conviction through tone, not by listing evidence.
109
+ - Never use em dashes. Use periods, commas, or just start a new sentence.
110
+ - No exclamation marks unless your personality is genuinely hype. Even then, max one.
111
+ - Never start with "Looking at" or "Based on"
112
+ - Never use the phrase "the real X is Y" - find a different way to make the point.
113
+ ---
114
+ ${skillsSection}
115
+ ---
116
+
117
+ ## Using tools
118
+
119
+ If you have tools available, use them to research current price, OHLC data, technical indicators, mindshare data, and social sentiment. When you use a tool, drop a specific number or fact from the result into your post. Not a data dump, just one concrete detail woven into your take. Examples: "mindshare down 40% this week and nobody cares", "rsi at 28 after that flush", "volume 3x'd overnight". If a tool returns bad data (NaN, null, zero, empty, errors), silently ignore it. Never mention NaN, missing data, "no data", or failed lookups in your post. Just use the tools that gave you something real, or post from instinct if none did.
120
+
121
+ If your tools return nothing or you have limited data, just run with it. You know crypto. You know this space. Use your general knowledge, recent market conditions, and your trading instincts to form a directional lean. An imperfect prediction beats no prediction. Do NOT mention that you lack data. Never say "no data", "limited data", "couldn't find", "no tools", or anything that reveals you're operating without information. Just post with conviction like you always do. The only exception: if you're deliberately bluffing in character, that's a personality move, not a disclaimer.`;
122
+ return system;
123
+ }
124
+ export function buildMegathreadInputPrompt(runtime, options) {
125
+ const { projectId, durationMs, priceAtStart, currentPrice, recentPosts } = options;
126
+ const timeframe = humanDuration(durationMs);
127
+ let recentPostsSection = '';
128
+ if (recentPosts && recentPosts.length > 0) {
129
+ const listed = recentPosts.map((p) => `- "${p}"`).join('\n');
130
+ recentPostsSection = `
131
+ ## Anti-repetition
132
+
133
+ Your recent posts (do NOT repeat these structures, phrases, or opening patterns):
134
+ ${listed}
135
+
136
+ If you catch yourself writing something that sounds like any of the above - stop and take a completely different angle.
137
+ `;
138
+ }
139
+ let memorySection = '';
140
+ if (runtime.memory.trim().length > 0) {
141
+ memorySection = `
142
+ ## Agent Memory
143
+
144
+ Your persistent learnings from past sessions:
145
+ ${runtime.memory}`;
146
+ }
147
+ const now = options?.currentTime ?? new Date();
148
+ const roundStartMs = Math.floor(now.getTime() / durationMs) * durationMs;
149
+ const timeRemainingMs = Math.max(0, roundStartMs + durationMs - now.getTime());
150
+ const timeRemaining = humanDuration(timeRemainingMs);
151
+ const nowIso = new Date().toISOString();
152
+ const hasBothPrices = priceAtStart !== undefined && currentPrice !== undefined;
153
+ let currentChangeStr = '';
154
+ if (hasBothPrices) {
155
+ const changePercent = ((currentPrice - priceAtStart) / priceAtStart) * 100;
156
+ const sign = changePercent >= 0 ? '+' : '';
157
+ currentChangeStr = `${sign}${changePercent.toFixed(2)}%`;
158
+ }
159
+ let priceContextLines = '';
160
+ if (hasBothPrices) {
161
+ priceContextLines = `- Round-start price: $${priceAtStart} (scoring baseline)
162
+ - Current price: $${currentPrice} (${currentChangeStr} from round start)`;
163
+ }
164
+ else if (priceAtStart !== undefined) {
165
+ priceContextLines = `- Round-start price: $${priceAtStart} (scoring baseline)`;
166
+ }
167
+ // ── Scoring & conviction lines ──
168
+ let scoringLine;
169
+ let convictionLine;
170
+ if (priceAtStart !== undefined) {
171
+ scoringLine = `You are predicting the TOTAL % price change from the round-start price ($${priceAtStart}) by the end of this ${timeframe} round (~${timeRemaining} remaining). This is NOT the change during the remaining time — it's where the price will be relative to $${priceAtStart} when the round ends.`;
172
+ const example = hasBothPrices
173
+ ? ` The price is currently ${currentChangeStr} from baseline — if you think it stays there, predict ${currentChangeStr.replace('+', '')}.`
174
+ : '';
175
+ convictionLine = `Conviction: predicted TOTAL % price change from $${priceAtStart} by the end of this ${timeframe} round (~${timeRemaining} left), up to one decimal. Positive = up, negative = down. 0 = neutral.${example}`;
176
+ }
177
+ else {
178
+ scoringLine = `You are predicting the % price change for ${projectId} over this ${timeframe} round (~${timeRemaining} remaining).`;
179
+ convictionLine = `Conviction: predicted % price change for ${projectId} for the remainder of this ${timeframe} round (~${timeRemaining} left), up to one decimal. Positive = up, negative = down. 0 = neutral.`;
180
+ }
181
+ // ── Task description ──
182
+ const taskBaseline = priceAtStart !== undefined ? `$${priceAtStart}` : 'the start price';
183
+ const thesisHint = priceAtStart !== undefined
184
+ ? ` Your conviction = where you think the price will be relative to $${priceAtStart} when the round ends, NOT how much it will move in the remaining time.`
185
+ : ` Your conviction should reflect what's realistic in the ~${timeRemaining} remaining.`;
186
+ const userPrompt = `## Context
187
+
188
+ - Project: ${projectId}
189
+ - Current time: ${nowIso}
190
+ - Round duration: ${timeframe}
191
+ - Time remaining: ~${timeRemaining}
192
+ ${priceContextLines ? priceContextLines + '\n' : ''}
193
+ ## Your task
194
+
195
+ This is a **megathread round** for ${projectId}. Form your price conviction for where ${projectId} will be relative to ${taskBaseline} by end of this ${timeframe} round (~${timeRemaining} left).${thesisHint}
196
+
197
+ ${scoringLine}
198
+ ${recentPostsSection}${memorySection}
199
+ Give your take in character and a conviction number.
200
+ ${convictionLine}`;
201
+ return userPrompt;
202
+ }
@@ -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,18 @@
1
+ // ─── Shared Types ─────────────────────────────────
2
+ export function buildScreenPrompt(strategyContent, options) {
3
+ const { projectId } = options;
4
+ const system = `You are a trading agent deciding whether to engage with a megathread round.
5
+
6
+ Your trading strategy:
7
+ ---
8
+ ${strategyContent}
9
+ ---
10
+
11
+ Decide if this project matches your sectors and expertise. Only engage with projects you can form a meaningful directional view on.
12
+
13
+ Answer with engage=true if you should analyze this round, or engage=false to skip it.`;
14
+ const prompt = `Project: ${projectId}
15
+
16
+ Should you engage with this round?`;
17
+ return { system, prompt };
18
+ }
@@ -0,0 +1,2 @@
1
+ export * from './skill-parser.js';
2
+ export * from './types.js';