myaiforone 1.0.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 (315) hide show
  1. package/README.md +113 -0
  2. package/agents/_template/CLAUDE.md +18 -0
  3. package/agents/_template/agent.json +7 -0
  4. package/agents/platform/agentcreator/CLAUDE.md +300 -0
  5. package/agents/platform/appcreator/CLAUDE.md +158 -0
  6. package/agents/platform/gym/CLAUDE.md +486 -0
  7. package/agents/platform/gym/agent.json +40 -0
  8. package/agents/platform/gym/programs/agent-building/program.json +160 -0
  9. package/agents/platform/gym/programs/automations-mastery/program.json +129 -0
  10. package/agents/platform/gym/programs/getting-started/program.json +124 -0
  11. package/agents/platform/gym/programs/mcp-integrations/program.json +116 -0
  12. package/agents/platform/gym/programs/multi-model-strategy/program.json +115 -0
  13. package/agents/platform/gym/programs/prompt-engineering/program.json +136 -0
  14. package/agents/platform/gym/souls/alex.md +12 -0
  15. package/agents/platform/gym/souls/jordan.md +12 -0
  16. package/agents/platform/gym/souls/morgan.md +12 -0
  17. package/agents/platform/gym/souls/riley.md +12 -0
  18. package/agents/platform/gym/souls/sam.md +12 -0
  19. package/agents/platform/hub/CLAUDE.md +372 -0
  20. package/agents/platform/promptcreator/CLAUDE.md +130 -0
  21. package/agents/platform/skillcreator/CLAUDE.md +163 -0
  22. package/bin/cli.js +566 -0
  23. package/config.example.json +310 -0
  24. package/dist/agent-registry.d.ts +32 -0
  25. package/dist/agent-registry.d.ts.map +1 -0
  26. package/dist/agent-registry.js +144 -0
  27. package/dist/agent-registry.js.map +1 -0
  28. package/dist/channels/discord.d.ts +17 -0
  29. package/dist/channels/discord.d.ts.map +1 -0
  30. package/dist/channels/discord.js +114 -0
  31. package/dist/channels/discord.js.map +1 -0
  32. package/dist/channels/imessage.d.ts +23 -0
  33. package/dist/channels/imessage.d.ts.map +1 -0
  34. package/dist/channels/imessage.js +214 -0
  35. package/dist/channels/imessage.js.map +1 -0
  36. package/dist/channels/slack.d.ts +19 -0
  37. package/dist/channels/slack.d.ts.map +1 -0
  38. package/dist/channels/slack.js +167 -0
  39. package/dist/channels/slack.js.map +1 -0
  40. package/dist/channels/telegram.d.ts +19 -0
  41. package/dist/channels/telegram.d.ts.map +1 -0
  42. package/dist/channels/telegram.js +274 -0
  43. package/dist/channels/telegram.js.map +1 -0
  44. package/dist/channels/types.d.ts +44 -0
  45. package/dist/channels/types.d.ts.map +1 -0
  46. package/dist/channels/types.js +18 -0
  47. package/dist/channels/types.js.map +1 -0
  48. package/dist/channels/whatsapp.d.ts +23 -0
  49. package/dist/channels/whatsapp.d.ts.map +1 -0
  50. package/dist/channels/whatsapp.js +189 -0
  51. package/dist/channels/whatsapp.js.map +1 -0
  52. package/dist/config.d.ts +134 -0
  53. package/dist/config.d.ts.map +1 -0
  54. package/dist/config.js +127 -0
  55. package/dist/config.js.map +1 -0
  56. package/dist/cron.d.ts +8 -0
  57. package/dist/cron.d.ts.map +1 -0
  58. package/dist/cron.js +35 -0
  59. package/dist/cron.js.map +1 -0
  60. package/dist/decrypt-keys.d.ts +7 -0
  61. package/dist/decrypt-keys.d.ts.map +1 -0
  62. package/dist/decrypt-keys.js +53 -0
  63. package/dist/decrypt-keys.js.map +1 -0
  64. package/dist/encrypt-keys.d.ts +8 -0
  65. package/dist/encrypt-keys.d.ts.map +1 -0
  66. package/dist/encrypt-keys.js +62 -0
  67. package/dist/encrypt-keys.js.map +1 -0
  68. package/dist/executor.d.ts +31 -0
  69. package/dist/executor.d.ts.map +1 -0
  70. package/dist/executor.js +2009 -0
  71. package/dist/executor.js.map +1 -0
  72. package/dist/gemini-executor.d.ts +27 -0
  73. package/dist/gemini-executor.d.ts.map +1 -0
  74. package/dist/gemini-executor.js +160 -0
  75. package/dist/gemini-executor.js.map +1 -0
  76. package/dist/goals.d.ts +24 -0
  77. package/dist/goals.d.ts.map +1 -0
  78. package/dist/goals.js +189 -0
  79. package/dist/goals.js.map +1 -0
  80. package/dist/gym/activity-digest.d.ts +30 -0
  81. package/dist/gym/activity-digest.d.ts.map +1 -0
  82. package/dist/gym/activity-digest.js +506 -0
  83. package/dist/gym/activity-digest.js.map +1 -0
  84. package/dist/gym/dimension-scorer.d.ts +76 -0
  85. package/dist/gym/dimension-scorer.d.ts.map +1 -0
  86. package/dist/gym/dimension-scorer.js +236 -0
  87. package/dist/gym/dimension-scorer.js.map +1 -0
  88. package/dist/gym/gym-router.d.ts +7 -0
  89. package/dist/gym/gym-router.d.ts.map +1 -0
  90. package/dist/gym/gym-router.js +718 -0
  91. package/dist/gym/gym-router.js.map +1 -0
  92. package/dist/gym/index.d.ts +11 -0
  93. package/dist/gym/index.d.ts.map +1 -0
  94. package/dist/gym/index.js +11 -0
  95. package/dist/gym/index.js.map +1 -0
  96. package/dist/heartbeat.d.ts +21 -0
  97. package/dist/heartbeat.d.ts.map +1 -0
  98. package/dist/heartbeat.js +163 -0
  99. package/dist/heartbeat.js.map +1 -0
  100. package/dist/index.d.ts +2 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +254 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/keystore.d.ts +22 -0
  105. package/dist/keystore.d.ts.map +1 -0
  106. package/dist/keystore.js +178 -0
  107. package/dist/keystore.js.map +1 -0
  108. package/dist/logger.d.ts +9 -0
  109. package/dist/logger.d.ts.map +1 -0
  110. package/dist/logger.js +45 -0
  111. package/dist/logger.js.map +1 -0
  112. package/dist/memory/daily.d.ts +22 -0
  113. package/dist/memory/daily.d.ts.map +1 -0
  114. package/dist/memory/daily.js +82 -0
  115. package/dist/memory/daily.js.map +1 -0
  116. package/dist/memory/embeddings.d.ts +15 -0
  117. package/dist/memory/embeddings.d.ts.map +1 -0
  118. package/dist/memory/embeddings.js +154 -0
  119. package/dist/memory/embeddings.js.map +1 -0
  120. package/dist/memory/index.d.ts +32 -0
  121. package/dist/memory/index.d.ts.map +1 -0
  122. package/dist/memory/index.js +159 -0
  123. package/dist/memory/index.js.map +1 -0
  124. package/dist/memory/search.d.ts +21 -0
  125. package/dist/memory/search.d.ts.map +1 -0
  126. package/dist/memory/search.js +77 -0
  127. package/dist/memory/search.js.map +1 -0
  128. package/dist/memory/store.d.ts +23 -0
  129. package/dist/memory/store.d.ts.map +1 -0
  130. package/dist/memory/store.js +144 -0
  131. package/dist/memory/store.js.map +1 -0
  132. package/dist/ollama-executor.d.ts +17 -0
  133. package/dist/ollama-executor.d.ts.map +1 -0
  134. package/dist/ollama-executor.js +112 -0
  135. package/dist/ollama-executor.js.map +1 -0
  136. package/dist/openai-executor.d.ts +38 -0
  137. package/dist/openai-executor.d.ts.map +1 -0
  138. package/dist/openai-executor.js +197 -0
  139. package/dist/openai-executor.js.map +1 -0
  140. package/dist/router.d.ts +11 -0
  141. package/dist/router.d.ts.map +1 -0
  142. package/dist/router.js +185 -0
  143. package/dist/router.js.map +1 -0
  144. package/dist/test-message.d.ts +2 -0
  145. package/dist/test-message.d.ts.map +1 -0
  146. package/dist/test-message.js +60 -0
  147. package/dist/test-message.js.map +1 -0
  148. package/dist/utils/imsg-db-reader.d.ts +24 -0
  149. package/dist/utils/imsg-db-reader.d.ts.map +1 -0
  150. package/dist/utils/imsg-db-reader.js +92 -0
  151. package/dist/utils/imsg-db-reader.js.map +1 -0
  152. package/dist/utils/imsg-rpc.d.ts +25 -0
  153. package/dist/utils/imsg-rpc.d.ts.map +1 -0
  154. package/dist/utils/imsg-rpc.js +149 -0
  155. package/dist/utils/imsg-rpc.js.map +1 -0
  156. package/dist/utils/message-formatter.d.ts +3 -0
  157. package/dist/utils/message-formatter.d.ts.map +1 -0
  158. package/dist/utils/message-formatter.js +69 -0
  159. package/dist/utils/message-formatter.js.map +1 -0
  160. package/dist/web-ui.d.ts +12 -0
  161. package/dist/web-ui.d.ts.map +1 -0
  162. package/dist/web-ui.js +5784 -0
  163. package/dist/web-ui.js.map +1 -0
  164. package/dist/whatsapp-chats.d.ts +2 -0
  165. package/dist/whatsapp-chats.d.ts.map +1 -0
  166. package/dist/whatsapp-chats.js +76 -0
  167. package/dist/whatsapp-chats.js.map +1 -0
  168. package/dist/whatsapp-login.d.ts +2 -0
  169. package/dist/whatsapp-login.d.ts.map +1 -0
  170. package/dist/whatsapp-login.js +90 -0
  171. package/dist/whatsapp-login.js.map +1 -0
  172. package/dist/wiki-sync.d.ts +21 -0
  173. package/dist/wiki-sync.d.ts.map +1 -0
  174. package/dist/wiki-sync.js +147 -0
  175. package/dist/wiki-sync.js.map +1 -0
  176. package/docs/AddNewAgentGuide.md +100 -0
  177. package/docs/AddNewMcpGuide.md +72 -0
  178. package/docs/Architecture.md +795 -0
  179. package/docs/CLAUDE-AI-SETUP.md +166 -0
  180. package/docs/Setup.md +297 -0
  181. package/docs/ai-gym-architecture.md +1040 -0
  182. package/docs/ai-gym-build-plan.md +343 -0
  183. package/docs/ai-gym-onboarding.md +122 -0
  184. package/docs/appcreator_plan.md +348 -0
  185. package/docs/platform-mcp-audit.md +320 -0
  186. package/docs/server-deployment-plan.md +503 -0
  187. package/docs/superpowers/plans/2026-03-25-marketplace.md +1281 -0
  188. package/docs/superpowers/specs/2026-03-25-marketplace-design.md +287 -0
  189. package/docs/user-guide.md +2016 -0
  190. package/mcp-catalog.json +628 -0
  191. package/package.json +63 -0
  192. package/public/MyAIforOne-logomark-512.svg +16 -0
  193. package/public/MyAIforOne-logomark-transparent.svg +15 -0
  194. package/public/activity.html +314 -0
  195. package/public/admin.html +1674 -0
  196. package/public/agent-dashboard.html +670 -0
  197. package/public/api-docs.html +1106 -0
  198. package/public/automations.html +722 -0
  199. package/public/canvas.css +223 -0
  200. package/public/canvas.js +588 -0
  201. package/public/changelog.html +231 -0
  202. package/public/gym.html +2766 -0
  203. package/public/home.html +1930 -0
  204. package/public/index.html +2809 -0
  205. package/public/lab.html +1643 -0
  206. package/public/library.html +1442 -0
  207. package/public/marketplace.html +1101 -0
  208. package/public/mcp-docs.html +441 -0
  209. package/public/mini.html +390 -0
  210. package/public/monitor.html +584 -0
  211. package/public/org.html +4304 -0
  212. package/public/projects.html +734 -0
  213. package/public/settings.html +645 -0
  214. package/public/tasks.html +932 -0
  215. package/public/trainers/alex.svg +12 -0
  216. package/public/trainers/jordan.svg +12 -0
  217. package/public/trainers/morgan.svg +12 -0
  218. package/public/trainers/riley.svg +12 -0
  219. package/public/trainers/sam.svg +12 -0
  220. package/public/user-guide.html +218 -0
  221. package/registry/agents.json +3 -0
  222. package/registry/apps.json +20 -0
  223. package/registry/installed-drafts.json +3 -0
  224. package/registry/mcps.json +1084 -0
  225. package/registry/prompts/personal/mcp-test-prompt.md +6 -0
  226. package/registry/prompts/personal/memory-recall.md +6 -0
  227. package/registry/prompts/platform/brainstorm.md +15 -0
  228. package/registry/prompts/platform/code-review.md +16 -0
  229. package/registry/prompts/platform/explain.md +16 -0
  230. package/registry/prompts.json +58 -0
  231. package/registry/skills/external/brainstorming.md +5 -0
  232. package/registry/skills/external/code-review.md +40 -0
  233. package/registry/skills/external/frontend-patterns.md +642 -0
  234. package/registry/skills/external/frontend-slides.md +184 -0
  235. package/registry/skills/external/systematic-debugging.md +5 -0
  236. package/registry/skills/external/tdd.md +328 -0
  237. package/registry/skills/external/verification-before-completion.md +5 -0
  238. package/registry/skills/external/writing-plans.md +5 -0
  239. package/registry/skills/platform/ai41_app_build.md +930 -0
  240. package/registry/skills/platform/ai41_app_deploy.md +168 -0
  241. package/registry/skills/platform/ai41_app_orchestrator.md +239 -0
  242. package/registry/skills/platform/ai41_app_patterns.md +359 -0
  243. package/registry/skills/platform/ai41_app_register.md +85 -0
  244. package/registry/skills/platform/ai41_app_scaffold.md +421 -0
  245. package/registry/skills/platform/ai41_app_verify.md +107 -0
  246. package/registry/skills/platform/opProjectCreate.md +239 -0
  247. package/registry/skills/platform/op_devbrowser.md +136 -0
  248. package/registry/skills/platform/sop_brandguidelines.md +103 -0
  249. package/registry/skills/platform/sop_docx.md +117 -0
  250. package/registry/skills/platform/sop_frontenddesign.md +44 -0
  251. package/registry/skills/platform/sop_frontenddesign_v2.md +659 -0
  252. package/registry/skills/platform/sop_mcpbuilder.md +133 -0
  253. package/registry/skills/platform/sop_pdf.md +172 -0
  254. package/registry/skills/platform/sop_pptx.md +133 -0
  255. package/registry/skills/platform/sop_skillcreator.md +104 -0
  256. package/registry/skills/platform/sop_themefactory.md +128 -0
  257. package/registry/skills/platform/sop_webapptesting.md +75 -0
  258. package/registry/skills/platform/sop_webartifactsbuilder.md +97 -0
  259. package/registry/skills/platform/sop_xlsx.md +134 -0
  260. package/registry/skills.json +1055 -0
  261. package/scripts/discover-chats.sh +11 -0
  262. package/scripts/install-service-windows.ps1 +87 -0
  263. package/scripts/install-service.sh +52 -0
  264. package/scripts/seed-registry.ts +195 -0
  265. package/scripts/test-send.sh +5 -0
  266. package/scripts/tray-indicator.ps1 +35 -0
  267. package/scripts/uninstall-service-windows.ps1 +23 -0
  268. package/scripts/uninstall-service.sh +15 -0
  269. package/scripts/xbar-myagent.5s.sh +32 -0
  270. package/server/mcp-server/dist/index.d.ts +11 -0
  271. package/server/mcp-server/dist/index.js +1332 -0
  272. package/server/mcp-server/dist/lib/api-client.d.ts +165 -0
  273. package/server/mcp-server/dist/lib/api-client.js +241 -0
  274. package/server/mcp-server/index.ts +1545 -0
  275. package/server/mcp-server/lib/api-client.ts +366 -0
  276. package/server/mcp-server/tsconfig.json +14 -0
  277. package/src/agent-registry.ts +180 -0
  278. package/src/channels/discord.ts +129 -0
  279. package/src/channels/imessage.ts +261 -0
  280. package/src/channels/slack.ts +208 -0
  281. package/src/channels/telegram.ts +307 -0
  282. package/src/channels/types.ts +62 -0
  283. package/src/channels/whatsapp.ts +227 -0
  284. package/src/config.ts +281 -0
  285. package/src/cron.ts +43 -0
  286. package/src/decrypt-keys.ts +60 -0
  287. package/src/encrypt-keys.ts +70 -0
  288. package/src/executor.ts +2190 -0
  289. package/src/gemini-executor.ts +212 -0
  290. package/src/goals.ts +240 -0
  291. package/src/gym/activity-digest.ts +546 -0
  292. package/src/gym/dimension-scorer.ts +297 -0
  293. package/src/gym/gym-router.ts +801 -0
  294. package/src/gym/index.ts +19 -0
  295. package/src/heartbeat.ts +220 -0
  296. package/src/index.ts +275 -0
  297. package/src/keystore.ts +190 -0
  298. package/src/logger.ts +51 -0
  299. package/src/memory/daily.ts +101 -0
  300. package/src/memory/embeddings.ts +185 -0
  301. package/src/memory/index.ts +218 -0
  302. package/src/memory/search.ts +124 -0
  303. package/src/memory/store.ts +189 -0
  304. package/src/ollama-executor.ts +126 -0
  305. package/src/openai-executor.ts +259 -0
  306. package/src/router.ts +230 -0
  307. package/src/test-message.ts +72 -0
  308. package/src/utils/imsg-db-reader.ts +109 -0
  309. package/src/utils/imsg-rpc.ts +178 -0
  310. package/src/utils/message-formatter.ts +90 -0
  311. package/src/web-ui.ts +5778 -0
  312. package/src/whatsapp-chats.ts +91 -0
  313. package/src/whatsapp-login.ts +110 -0
  314. package/src/wiki-sync.ts +199 -0
  315. package/tsconfig.json +19 -0
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Google Gemini executor — uses the Gemini REST API (generativelanguage.googleapis.com).
3
+ *
4
+ * Executor format: "gemini:gemini-2.5-flash", "gemini:gemini-2.5-pro", etc.
5
+ *
6
+ * Gemini's API differs from OpenAI's:
7
+ * - Auth via API key in query string (not Bearer token)
8
+ * - Different request/response schema (contents[], not messages[])
9
+ * - Streaming uses SSE with different event shape
10
+ */
11
+
12
+ import { log } from "./logger.js";
13
+
14
+ const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
15
+
16
+ // ─── Options ────────────────────────────────────────────────────
17
+ export interface GeminiOptions {
18
+ model: string; // "gemini-2.5-flash", "gemini-2.5-pro", etc.
19
+ apiKey: string;
20
+ systemPrompt: string;
21
+ message: string;
22
+ timeout?: number; // default: 300000 (5 min)
23
+ temperature?: number; // default: 0.7
24
+ maxTokens?: number; // default: 4096
25
+ }
26
+
27
+ // ─── Response types ─────────────────────────────────────────────
28
+ interface GeminiResponse {
29
+ candidates?: Array<{
30
+ content: { parts: Array<{ text: string }>; role: string };
31
+ finishReason: string;
32
+ }>;
33
+ usageMetadata?: {
34
+ promptTokenCount: number;
35
+ candidatesTokenCount: number;
36
+ totalTokenCount: number;
37
+ };
38
+ error?: { code: number; message: string; status: string };
39
+ }
40
+
41
+ // ─── Execute (non-streaming) ────────────────────────────────────
42
+ export async function executeGemini(opts: GeminiOptions): Promise<string> {
43
+ const timeout = opts.timeout || 300_000;
44
+
45
+ log.info(`[Gemini] Executing ${opts.model}: ${opts.message.slice(0, 200)}`);
46
+
47
+ const controller = new AbortController();
48
+ const timer = setTimeout(() => controller.abort(), timeout);
49
+
50
+ try {
51
+ const url = `${GEMINI_BASE_URL}/models/${opts.model}:generateContent?key=${opts.apiKey}`;
52
+
53
+ const res = await fetch(url, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify({
57
+ system_instruction: {
58
+ parts: [{ text: opts.systemPrompt }],
59
+ },
60
+ contents: [
61
+ {
62
+ role: "user",
63
+ parts: [{ text: opts.message }],
64
+ },
65
+ ],
66
+ generationConfig: {
67
+ temperature: opts.temperature ?? 0.7,
68
+ maxOutputTokens: opts.maxTokens ?? 4096,
69
+ },
70
+ }),
71
+ signal: controller.signal,
72
+ });
73
+
74
+ if (!res.ok) {
75
+ const errText = await res.text();
76
+ log.error(`[Gemini] Error ${res.status}: ${errText}`);
77
+ try {
78
+ const errJson = JSON.parse(errText);
79
+ throw new Error(`Gemini API error ${res.status}: ${errJson.error?.message || errText}`);
80
+ } catch (e) {
81
+ if (e instanceof Error && e.message.startsWith("Gemini")) throw e;
82
+ throw new Error(`Gemini API error ${res.status}: ${errText}`);
83
+ }
84
+ }
85
+
86
+ const data = await res.json() as GeminiResponse;
87
+
88
+ if (data.error) {
89
+ throw new Error(`Gemini error: ${data.error.message}`);
90
+ }
91
+
92
+ const response = data.candidates?.[0]?.content?.parts
93
+ ?.map(p => p.text)
94
+ .join("") || "";
95
+
96
+ if (data.usageMetadata) {
97
+ log.debug(`[Gemini] ${opts.model} usage: ${data.usageMetadata.promptTokenCount}+${data.usageMetadata.candidatesTokenCount} tokens`);
98
+ }
99
+
100
+ log.info(`[Gemini] Response from ${opts.model}: ${response.slice(0, 200)}`);
101
+ return response;
102
+ } finally {
103
+ clearTimeout(timer);
104
+ }
105
+ }
106
+
107
+ // ─── Stream ─────────────────────────────────────────────────────
108
+ export async function* streamGemini(opts: GeminiOptions): AsyncGenerator<string> {
109
+ const timeout = opts.timeout || 300_000;
110
+
111
+ log.info(`[Gemini] Streaming ${opts.model}: ${opts.message.slice(0, 200)}`);
112
+
113
+ const controller = new AbortController();
114
+ const timer = setTimeout(() => controller.abort(), timeout);
115
+
116
+ try {
117
+ const url = `${GEMINI_BASE_URL}/models/${opts.model}:streamGenerateContent?key=${opts.apiKey}&alt=sse`;
118
+
119
+ const res = await fetch(url, {
120
+ method: "POST",
121
+ headers: { "Content-Type": "application/json" },
122
+ body: JSON.stringify({
123
+ system_instruction: {
124
+ parts: [{ text: opts.systemPrompt }],
125
+ },
126
+ contents: [
127
+ {
128
+ role: "user",
129
+ parts: [{ text: opts.message }],
130
+ },
131
+ ],
132
+ generationConfig: {
133
+ temperature: opts.temperature ?? 0.7,
134
+ maxOutputTokens: opts.maxTokens ?? 4096,
135
+ },
136
+ }),
137
+ signal: controller.signal,
138
+ });
139
+
140
+ if (!res.ok) {
141
+ const errText = await res.text();
142
+ throw new Error(`Gemini API error ${res.status}: ${errText}`);
143
+ }
144
+
145
+ const reader = res.body?.getReader();
146
+ if (!reader) throw new Error("No response body");
147
+
148
+ const decoder = new TextDecoder();
149
+ let buffer = "";
150
+
151
+ while (true) {
152
+ const { done, value } = await reader.read();
153
+ if (done) break;
154
+
155
+ buffer += decoder.decode(value, { stream: true });
156
+ const lines = buffer.split("\n");
157
+ buffer = lines.pop() || "";
158
+
159
+ for (const line of lines) {
160
+ const trimmed = line.trim();
161
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
162
+
163
+ const jsonStr = trimmed.slice(6);
164
+ try {
165
+ const chunk = JSON.parse(jsonStr) as GeminiResponse;
166
+ const text = chunk.candidates?.[0]?.content?.parts
167
+ ?.map(p => p.text)
168
+ .join("");
169
+ if (text) {
170
+ yield text;
171
+ }
172
+ } catch { /* skip unparseable lines */ }
173
+ }
174
+ }
175
+ } finally {
176
+ clearTimeout(timer);
177
+ }
178
+ }
179
+
180
+ // ─── Health check ───────────────────────────────────────────────
181
+ export async function checkGeminiHealth(
182
+ apiKey: string,
183
+ model?: string,
184
+ ): Promise<{ ok: boolean; error?: string; models?: string[] }> {
185
+ try {
186
+ const res = await fetch(
187
+ `${GEMINI_BASE_URL}/models?key=${apiKey}`,
188
+ { signal: AbortSignal.timeout(10_000) },
189
+ );
190
+
191
+ if (res.status === 400 || res.status === 403) {
192
+ return { ok: false, error: "Gemini: Invalid API key" };
193
+ }
194
+
195
+ if (!res.ok) {
196
+ return { ok: false, error: `Gemini: API returned ${res.status}` };
197
+ }
198
+
199
+ const data = await res.json() as { models?: Array<{ name: string; displayName: string }> };
200
+ const models = data.models
201
+ ?.map(m => m.name.replace("models/", ""))
202
+ .filter(m => m.startsWith("gemini")) || [];
203
+
204
+ if (model && !models.some(m => m === model || m.startsWith(model))) {
205
+ return { ok: false, error: `Model "${model}" not found. Available: ${models.slice(0, 10).join(", ")}` };
206
+ }
207
+
208
+ return { ok: true, models: models.slice(0, 20) };
209
+ } catch (err) {
210
+ return { ok: false, error: `Gemini: ${err}` };
211
+ }
212
+ }
package/src/goals.ts ADDED
@@ -0,0 +1,240 @@
1
+ import cron from "node-cron";
2
+ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import type { AppConfig, GoalConfig, McpServerConfig } from "./config.js";
5
+ import type { InboundMessage } from "./channels/types.js";
6
+ import type { ResolvedRoute } from "./router.js";
7
+ import type { ChannelDriver } from "./channels/types.js";
8
+ import { executeAgent } from "./executor.js";
9
+ import { log } from "./logger.js";
10
+
11
+ // ─── Budget tracking ─────────────────────────────────────────────────
12
+
13
+ export interface BudgetState {
14
+ spent: number;
15
+ limit: number;
16
+ executions: number;
17
+ }
18
+
19
+ function todayKey(): string {
20
+ return new Date().toISOString().split("T")[0]; // YYYY-MM-DD
21
+ }
22
+
23
+ export function budgetPath(agentHome: string, date?: string): string {
24
+ const goalsDir = join(agentHome, "goals");
25
+ return join(goalsDir, `budget-${date || todayKey()}.json`);
26
+ }
27
+
28
+ export function readBudget(agentHome: string, dailyLimit: number): BudgetState {
29
+ const path = budgetPath(agentHome);
30
+ if (!existsSync(path)) {
31
+ return { spent: 0, limit: dailyLimit, executions: 0 };
32
+ }
33
+ try {
34
+ const data = JSON.parse(readFileSync(path, "utf-8")) as BudgetState;
35
+ // Always use the configured limit (in case it changed)
36
+ data.limit = dailyLimit;
37
+ return data;
38
+ } catch {
39
+ return { spent: 0, limit: dailyLimit, executions: 0 };
40
+ }
41
+ }
42
+
43
+ export function writeBudget(agentHome: string, budget: BudgetState): void {
44
+ const goalsDir = join(agentHome, "goals");
45
+ mkdirSync(goalsDir, { recursive: true });
46
+ const path = budgetPath(agentHome);
47
+ writeFileSync(path, JSON.stringify(budget, null, 2));
48
+ }
49
+
50
+ export function isBudgetExhausted(budget: BudgetState): boolean {
51
+ return budget.spent >= budget.limit;
52
+ }
53
+
54
+ // ─── Goal log ────────────────────────────────────────────────────────
55
+
56
+ function goalLogPath(agentHome: string): string {
57
+ const goalsDir = join(agentHome, "goals");
58
+ return join(goalsDir, `log-${todayKey()}.jsonl`);
59
+ }
60
+
61
+ function logGoalExecution(
62
+ agentHome: string,
63
+ goalId: string,
64
+ status: "executed" | "skipped-budget" | "error",
65
+ details?: Record<string, unknown>,
66
+ ): void {
67
+ const goalsDir = join(agentHome, "goals");
68
+ mkdirSync(goalsDir, { recursive: true });
69
+ const entry = {
70
+ ts: new Date().toISOString(),
71
+ goalId,
72
+ status,
73
+ ...details,
74
+ };
75
+ try {
76
+ appendFileSync(goalLogPath(agentHome), JSON.stringify(entry) + "\n");
77
+ } catch { /* ignore */ }
78
+ }
79
+
80
+ // ─── Goal prompt builder ─────────────────────────────────────────────
81
+
82
+ export function buildGoalPrompt(goal: GoalConfig, budgetRemaining: number, budgetLimit: number): string {
83
+ const lines: string[] = [
84
+ `[AUTONOMOUS GOAL: ${goal.id}]`,
85
+ `Description: ${goal.description}`,
86
+ ];
87
+ if (goal.successCriteria) lines.push(`Success Criteria: ${goal.successCriteria}`);
88
+ if (goal.instructions) lines.push(`Instructions: ${goal.instructions}`);
89
+ lines.push(`Budget remaining today: $${budgetRemaining.toFixed(2)} of $${budgetLimit.toFixed(2)}`);
90
+ lines.push(`[/AUTONOMOUS GOAL]`);
91
+ lines.push("");
92
+ lines.push("Evaluate this goal. Take action if needed. Report what you did or found.");
93
+ return lines.join("\n");
94
+ }
95
+
96
+ // ─── Execute a single goal (used by cron and trigger endpoint) ───────
97
+
98
+ export async function executeGoal(
99
+ agentId: string,
100
+ agent: AppConfig["agents"][string],
101
+ goal: GoalConfig,
102
+ baseDir: string,
103
+ driverMap: Map<string, ChannelDriver>,
104
+ mcpRegistry?: Record<string, McpServerConfig>,
105
+ claudeAccounts?: Record<string, string>,
106
+ globalDefaults?: { skills?: string[]; mcps?: string[]; prompts?: string[]; promptTrigger?: string },
107
+ ): Promise<{ status: string; response: string }> {
108
+ const agentHome = agent.agentHome || resolve(baseDir, agent.memoryDir, "..");
109
+
110
+ // Check budget
111
+ const dailyLimit = goal.budget?.maxDailyUsd ?? Infinity;
112
+ const budget = readBudget(agentHome, dailyLimit);
113
+
114
+ if (dailyLimit !== Infinity && isBudgetExhausted(budget)) {
115
+ logGoalExecution(agentHome, goal.id, "skipped-budget", {
116
+ spent: budget.spent,
117
+ limit: budget.limit,
118
+ });
119
+ return { status: "skipped-budget", response: `Budget exhausted ($${budget.spent.toFixed(2)}/$${budget.limit.toFixed(2)})` };
120
+ }
121
+
122
+ // Build the goal prompt
123
+ const budgetRemaining = dailyLimit === Infinity
124
+ ? Infinity
125
+ : Math.max(0, dailyLimit - budget.spent);
126
+ const prompt = buildGoalPrompt(goal, budgetRemaining, dailyLimit);
127
+
128
+ // Build synthetic message
129
+ const syntheticMsg: InboundMessage = {
130
+ id: `goal-${goal.id}-${Date.now()}`,
131
+ channel: "goals",
132
+ chatId: `goal-${goal.id}`,
133
+ chatType: "group",
134
+ sender: "goals-runner",
135
+ senderName: "Autonomous Goal",
136
+ text: prompt,
137
+ timestamp: Date.now(),
138
+ isFromMe: false,
139
+ isGroup: true,
140
+ raw: { type: "goal", goalId: goal.id },
141
+ };
142
+
143
+ const route: ResolvedRoute = {
144
+ agentId,
145
+ agentConfig: agent,
146
+ route: agent.routes[0],
147
+ };
148
+
149
+ // Execute the agent
150
+ const response = await executeAgent(route, syntheticMsg, baseDir, mcpRegistry, claudeAccounts, globalDefaults);
151
+
152
+ // Track cost
153
+ let cost = 0;
154
+ try {
155
+ const parsed = JSON.parse(response);
156
+ if (parsed.total_cost_usd) cost = parsed.total_cost_usd;
157
+ } catch { /* not JSON */ }
158
+
159
+ // Update budget
160
+ if (dailyLimit !== Infinity) {
161
+ budget.spent += cost;
162
+ budget.executions += 1;
163
+ writeBudget(agentHome, budget);
164
+ }
165
+
166
+ // Log execution
167
+ logGoalExecution(agentHome, goal.id, "executed", {
168
+ cost,
169
+ responseLength: response.length,
170
+ });
171
+
172
+ // Send report to channel(s) if configured
173
+ if (goal.reportTo) {
174
+ const targets = Array.isArray(goal.reportTo) ? goal.reportTo : [goal.reportTo];
175
+ for (const target of targets) {
176
+ const [channelName, ...chatIdParts] = target.split(":");
177
+ const chatId = chatIdParts.join(":");
178
+ const driver = driverMap.get(channelName);
179
+ if (driver && chatId) {
180
+ try {
181
+ const report = `[Goal: ${goal.id}]\n${response}`;
182
+ await driver.send({ text: report, chatId });
183
+ } catch (err) {
184
+ log.error(`Failed to send goal report for ${agentId}/${goal.id} to ${target}: ${err}`);
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ log.info(`Goal completed: ${agentId}/${goal.id} (${response.length} chars)`);
191
+ return { status: "executed", response };
192
+ }
193
+
194
+ // ─── Goals runner ────────────────────────────────────────────────────
195
+
196
+ const activeTasks: cron.ScheduledTask[] = [];
197
+
198
+ export function startGoals(
199
+ config: AppConfig,
200
+ driverMap: Map<string, ChannelDriver>,
201
+ baseDir: string,
202
+ mcpRegistry?: Record<string, McpServerConfig>,
203
+ ): void {
204
+ for (const [agentId, agent] of Object.entries(config.agents)) {
205
+ // Only agents that are autonomous-capable with enabled goals
206
+ if (agent.autonomousCapable === false) continue;
207
+ if (!agent.goals?.length) continue;
208
+
209
+ const agentHome = agent.agentHome || resolve(baseDir, agent.memoryDir, "..");
210
+
211
+ for (const goal of agent.goals) {
212
+ if (!goal.enabled) continue;
213
+
214
+ if (!cron.validate(goal.heartbeat)) {
215
+ log.error(`Invalid goal heartbeat for ${agentId}/${goal.id}: "${goal.heartbeat}"`);
216
+ continue;
217
+ }
218
+
219
+ const task = cron.schedule(goal.heartbeat, async () => {
220
+ log.info(`Goal heartbeat fired: ${agentId}/${goal.id}`);
221
+ try {
222
+ await executeGoal(agentId, agent, goal, baseDir, driverMap, mcpRegistry, config.service.claudeAccounts, { skills: config.defaultSkills, mcps: config.defaultMcps, prompts: config.defaultPrompts, promptTrigger: config.promptTrigger });
223
+ } catch (err) {
224
+ log.error(`Goal execution failed for ${agentId}/${goal.id}: ${err}`);
225
+ logGoalExecution(agentHome, goal.id, "error", { error: String(err) });
226
+ }
227
+ });
228
+
229
+ activeTasks.push(task);
230
+ log.info(`Goal scheduled: ${agentId}/${goal.id} — "${goal.heartbeat}" — ${goal.description.slice(0, 60)}`);
231
+ }
232
+ }
233
+ }
234
+
235
+ export function stopGoals(): void {
236
+ for (const task of activeTasks) {
237
+ task.stop();
238
+ }
239
+ activeTasks.length = 0;
240
+ }