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,546 @@
1
+ /**
2
+ * AI Gym — Activity Digest
3
+ *
4
+ * Scheduled daily cron (6am) that:
5
+ * 1. Reads all agent activity summaries via local HTTP API
6
+ * 2. Scores dimensions using dimension-scorer
7
+ * 3. Snapshots dimension history weekly
8
+ * 4. Writes digest to daily journal
9
+ * 5. Updates learner profile
10
+ * 6. Generates gym cards with insights
11
+ *
12
+ * Runs only when gymEnabled: true.
13
+ */
14
+
15
+ import cron from "node-cron";
16
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import {
19
+ scoreAllDimensions,
20
+ computeTrends,
21
+ type ActivitySummary,
22
+ type AgentInfo,
23
+ type DimensionScores,
24
+ } from "./dimension-scorer.js";
25
+ import { log } from "../logger.js";
26
+
27
+ const API_BASE = "http://localhost";
28
+ let scheduledTask: cron.ScheduledTask | null = null;
29
+
30
+ interface DigestConfig {
31
+ baseDir: string;
32
+ port: number;
33
+ memoryDir?: string; // Override gym memory dir (defaults to agents/platform/gym/memory)
34
+ }
35
+
36
+ async function apiGet(port: number, path: string): Promise<any> {
37
+ const res = await fetch(`${API_BASE}:${port}${path}`);
38
+ if (!res.ok) throw new Error(`API ${path} returned ${res.status}`);
39
+ return res.json();
40
+ }
41
+
42
+ async function apiPost(port: number, path: string, body: any): Promise<any> {
43
+ const res = await fetch(`${API_BASE}:${port}${path}`, {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/json" },
46
+ body: JSON.stringify(body),
47
+ });
48
+ if (!res.ok) throw new Error(`API POST ${path} returned ${res.status}`);
49
+ return res.json();
50
+ }
51
+
52
+ async function apiPut(port: number, path: string, body: any): Promise<any> {
53
+ const res = await fetch(`${API_BASE}:${port}${path}`, {
54
+ method: "PUT",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify(body),
57
+ });
58
+ if (!res.ok) throw new Error(`API PUT ${path} returned ${res.status}`);
59
+ return res.json();
60
+ }
61
+
62
+ function readJson(path: string, fallback: any = {}): any {
63
+ try {
64
+ if (!existsSync(path)) return fallback;
65
+ const raw = readFileSync(path, "utf-8").trim();
66
+ if (!raw) return fallback;
67
+ return JSON.parse(raw);
68
+ } catch {
69
+ return fallback;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Run the activity digest. Called by cron or manually.
75
+ */
76
+ export async function runActivityDigest(config: DigestConfig): Promise<void> {
77
+ const { baseDir, port } = config;
78
+ const memoryDir = config.memoryDir || join(baseDir, "agents", "platform", "gym", "memory");
79
+ const dailyDir = join(memoryDir, "daily");
80
+
81
+ log.info("[Gym Digest] Starting activity digest...");
82
+
83
+ try {
84
+ // ── Step 1: Get agent roster from config.json ──
85
+ const agentsDir = join(baseDir, "agents");
86
+ const agentDirs: string[] = [];
87
+
88
+ // Primary source: config.json (contains all agents including those without agent.json)
89
+ const configPath = join(baseDir, "config.json");
90
+ if (existsSync(configPath)) {
91
+ try {
92
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
93
+ const agentsMap = config.agents || {};
94
+ for (const id of Object.keys(agentsMap)) {
95
+ agentDirs.push(id);
96
+ }
97
+ } catch { /* fall back to directory scan */ }
98
+ }
99
+
100
+ // Fallback: scan agent directories if config.json didn't yield results
101
+ if (agentDirs.length === 0 && existsSync(agentsDir)) {
102
+ for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
103
+ if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
104
+ const ajPath = join(agentsDir, entry.name, "agent.json");
105
+ if (existsSync(ajPath)) {
106
+ agentDirs.push(entry.name);
107
+ }
108
+ if (entry.name === "platform") {
109
+ const platformDir = join(agentsDir, "platform");
110
+ for (const sub of readdirSync(platformDir, { withFileTypes: true })) {
111
+ if (!sub.isDirectory()) continue;
112
+ const subAjPath = join(platformDir, sub.name, "agent.json");
113
+ if (existsSync(subAjPath)) {
114
+ agentDirs.push(`platform/${sub.name}`);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ // ── Step 2: Get activity summaries for each agent ──
122
+ const summaries: ActivitySummary[] = [];
123
+ const agentInfos: AgentInfo[] = [];
124
+
125
+ // Read config.json for agent metadata (most agents are defined here)
126
+ let configAgents: Record<string, any> = {};
127
+ const configPath2 = join(baseDir, "config.json");
128
+ try {
129
+ if (existsSync(configPath2)) {
130
+ const config = JSON.parse(readFileSync(configPath2, "utf-8"));
131
+ configAgents = config.agents || {};
132
+ }
133
+ } catch { /* ignore */ }
134
+
135
+ for (const agentPath of agentDirs) {
136
+ const agentId = agentPath.includes("/") ? agentPath.split("/").pop()! : agentPath;
137
+ try {
138
+ const summary = await apiGet(port, `/api/agents/${agentId}/activity-summary`);
139
+ summaries.push(summary);
140
+ } catch {
141
+ // Agent may not have logs — skip
142
+ }
143
+
144
+ // Read agent config from agent.json or config.json
145
+ let aj: any = null;
146
+ const ajPath = join(agentsDir, agentPath, "agent.json");
147
+ if (existsSync(ajPath)) {
148
+ try { aj = JSON.parse(readFileSync(ajPath, "utf-8")); } catch { /* skip */ }
149
+ }
150
+ // Fall back to config.json entry
151
+ if (!aj && configAgents[agentId]) {
152
+ aj = { id: agentId, ...configAgents[agentId] };
153
+ }
154
+
155
+ if (aj) {
156
+ try {
157
+ const claudeMdPath = aj.claudeMd
158
+ ? aj.claudeMd.replace(/^~/, process.env.HOME || "~")
159
+ : join(agentsDir, agentPath, "CLAUDE.md");
160
+ let promptLength = 0;
161
+ if (existsSync(claudeMdPath)) {
162
+ promptLength = readFileSync(claudeMdPath, "utf-8").length;
163
+ }
164
+ agentInfos.push({
165
+ id: aj.id || agentId,
166
+ name: aj.name || agentId,
167
+ description: aj.description,
168
+ agentClass: aj.agentClass,
169
+ workspace: aj.workspace,
170
+ allowedTools: aj.allowedTools,
171
+ mcps: aj.mcps,
172
+ goals: aj.goals,
173
+ cron: aj.cron,
174
+ systemPromptLength: promptLength,
175
+ });
176
+ } catch { /* skip */ }
177
+ }
178
+ }
179
+
180
+ log.info(`[Gym Digest] Analyzed ${summaries.length} agents, ${agentInfos.length} agent configs`);
181
+
182
+ // ── Step 3: Read learner profile ──
183
+ const profilePath = join(memoryDir, "learner-profile.json");
184
+ const profile = readJson(profilePath, {});
185
+ const programsCompleted: string[] = profile.programs?.completed || [];
186
+
187
+ // Count total programs
188
+ const programsDir = join(baseDir, "agents", "platform", "gym", "programs");
189
+ let totalPrograms = 0;
190
+ if (existsSync(programsDir)) {
191
+ totalPrograms = readdirSync(programsDir, { withFileTypes: true }).filter(
192
+ (d) => d.isDirectory()
193
+ ).length;
194
+ }
195
+
196
+ // ── Step 4: Score dimensions ──
197
+ const scores = scoreAllDimensions(summaries, agentInfos, programsCompleted, totalPrograms);
198
+
199
+ // Read previous scores for trends
200
+ const prevDimensions = profile.dimensions || {};
201
+ const previousScores: DimensionScores | null = prevDimensions.application
202
+ ? {
203
+ application: prevDimensions.application?.score || 0,
204
+ communication: prevDimensions.communication?.score || 0,
205
+ knowledge: prevDimensions.knowledge?.score || 0,
206
+ orchestration: prevDimensions.orchestration?.score || 0,
207
+ craft: prevDimensions.craft?.score || 0,
208
+ }
209
+ : null;
210
+
211
+ const trends = computeTrends(scores, previousScores);
212
+ const today = new Date().toISOString().slice(0, 10);
213
+
214
+ const scoreLabels: Record<number, string> = {
215
+ 0: "Not assessed",
216
+ 1: "Beginner",
217
+ 2: "Developing",
218
+ 3: "Proficient",
219
+ 4: "Advanced",
220
+ 5: "Expert",
221
+ };
222
+
223
+ // Build dimensions object for profile
224
+ const dimensions: Record<string, any> = {};
225
+ for (const [key, score] of Object.entries(scores)) {
226
+ dimensions[key] = {
227
+ score,
228
+ label: scoreLabels[score] || "Unknown",
229
+ trend: trends[key as keyof DimensionScores],
230
+ lastUpdated: today,
231
+ };
232
+ }
233
+
234
+ // ── Step 5: Update streak ──
235
+ const lastActiveDate = profile.streak?.lastActiveDate;
236
+ let streakCurrent = profile.streak?.current || 0;
237
+ const streakLongest = profile.streak?.longest || 0;
238
+
239
+ // Check if user was active today (any agent had activity)
240
+ const wasActiveToday = summaries.some((s) => {
241
+ if (!s.lastActive) return false;
242
+ return s.lastActive.slice(0, 10) === today;
243
+ });
244
+
245
+ if (wasActiveToday) {
246
+ if (lastActiveDate === today) {
247
+ // Already counted today
248
+ } else {
249
+ const yesterday = new Date();
250
+ yesterday.setDate(yesterday.getDate() - 1);
251
+ const yesterdayStr = yesterday.toISOString().slice(0, 10);
252
+ if (lastActiveDate === yesterdayStr) {
253
+ streakCurrent += 1;
254
+ } else {
255
+ streakCurrent = 1; // Reset streak
256
+ }
257
+ }
258
+ }
259
+
260
+ // ── Step 6: Compute active agent stats ──
261
+ const activeAgents = summaries
262
+ .filter((s) => s.messageCount > 0 && !["hub", "gym", "agentcreator"].includes(s.agentId))
263
+ .map((s) => s.agentId);
264
+
265
+ const dormantAgents = summaries
266
+ .filter((s) => {
267
+ if (["hub", "gym", "agentcreator"].includes(s.agentId)) return false;
268
+ if (s.messageCount === 0) return true;
269
+ if (!s.lastActive) return true;
270
+ const daysSinceActive =
271
+ (Date.now() - new Date(s.lastActive).getTime()) / (1000 * 60 * 60 * 24);
272
+ return daysSinceActive > 14;
273
+ })
274
+ .map((s) => s.agentId);
275
+
276
+ const totalMessages = summaries.reduce((sum, s) => sum + s.messageCount, 0);
277
+ const messagesThisWeek = summaries.reduce((sum, s) => {
278
+ const weekAgo = new Date();
279
+ weekAgo.setDate(weekAgo.getDate() - 7);
280
+ const recentDates = s.uniqueDates.filter(
281
+ (d) => new Date(d) >= weekAgo
282
+ );
283
+ // Rough approximation: distribute messages evenly across dates
284
+ const avgPerDay = s.messageCount / Math.max(s.activeDays, 1);
285
+ return sum + Math.round(avgPerDay * recentDates.length);
286
+ }, 0);
287
+
288
+ // Compute features used across all agents
289
+ const featuresUsed = new Set<string>();
290
+ for (const s of summaries) {
291
+ if (s.messageCount > 0) featuresUsed.add("chat");
292
+ for (const tool of Object.keys(s.toolUseCounts)) {
293
+ if (tool === "Read" || tool === "Write" || tool === "Edit") featuresUsed.add("file-ops");
294
+ if (tool === "Bash") featuresUsed.add("bash");
295
+ if (tool === "WebFetch" || tool === "WebSearch") featuresUsed.add("web");
296
+ }
297
+ }
298
+ for (const a of agentInfos) {
299
+ if (a.mcps && a.mcps.length > 0) featuresUsed.add("mcps");
300
+ if (a.goals && a.goals.length > 0) featuresUsed.add("goals");
301
+ if (a.cron && a.cron.length > 0) featuresUsed.add("cron");
302
+ }
303
+
304
+ const allFeatures = ["chat", "file-ops", "bash", "web", "mcps", "goals", "cron", "delegation", "webhooks", "multi-model"];
305
+ const neverUsed = allFeatures.filter((f) => !featuresUsed.has(f));
306
+
307
+ // ── Step 5: Snapshot dimensions weekly ──
308
+ const historyPath = join(memoryDir, "dimension-history.json");
309
+ const history = readJson(historyPath, []);
310
+ const lastSnapshot = history.length > 0 ? history[history.length - 1].date : null;
311
+ const daysSinceSnapshot = lastSnapshot
312
+ ? (Date.now() - new Date(lastSnapshot).getTime()) / (1000 * 60 * 60 * 24)
313
+ : 999;
314
+
315
+ if (daysSinceSnapshot >= 7) {
316
+ try {
317
+ await apiPost(port, "/api/gym/dimensions/snapshot", {
318
+ date: today,
319
+ dimensions: scores,
320
+ });
321
+ log.info("[Gym Digest] Weekly dimension snapshot saved");
322
+ } catch (err) {
323
+ log.warn(`[Gym Digest] Failed to snapshot dimensions: ${err}`);
324
+ }
325
+ }
326
+
327
+ // ── Step 6: Write daily digest journal ──
328
+ mkdirSync(dailyDir, { recursive: true });
329
+ const digestPath = join(dailyDir, `${today}.md`);
330
+
331
+ const digestLines: string[] = [
332
+ `# Activity Digest — ${today}`,
333
+ "",
334
+ `## Dimensions`,
335
+ ...Object.entries(dimensions).map(
336
+ ([k, v]: [string, any]) => `- **${k}**: ${v.score}/5 (${v.label}, ${v.trend})`
337
+ ),
338
+ "",
339
+ `## Activity`,
340
+ `- Active agents: ${activeAgents.join(", ") || "none"}`,
341
+ `- Dormant agents: ${dormantAgents.join(", ") || "none"}`,
342
+ `- Total messages: ${totalMessages}`,
343
+ `- Messages this week: ~${messagesThisWeek}`,
344
+ `- Streak: ${streakCurrent} days`,
345
+ "",
346
+ `## Features`,
347
+ `- Used: ${Array.from(featuresUsed).join(", ") || "none"}`,
348
+ `- Never used: ${neverUsed.join(", ") || "all used!"}`,
349
+ "",
350
+ ];
351
+
352
+ writeFileSync(digestPath, digestLines.join("\n"), "utf-8");
353
+ log.info(`[Gym Digest] Daily digest written to ${digestPath}`);
354
+
355
+ // ── Step 7: Update learner profile ──
356
+ const profileUpdate: any = {
357
+ dimensions,
358
+ activity: {
359
+ activeAgents,
360
+ dormantAgents,
361
+ totalMessages,
362
+ messagesThisWeek,
363
+ lastActivity: new Date().toISOString(),
364
+ },
365
+ features: {
366
+ used: Array.from(featuresUsed),
367
+ neverUsed,
368
+ },
369
+ streak: {
370
+ current: streakCurrent,
371
+ longest: Math.max(streakCurrent, streakLongest),
372
+ lastActiveDate: wasActiveToday ? today : lastActiveDate || today,
373
+ },
374
+ };
375
+
376
+ try {
377
+ await apiPut(port, "/api/gym/learner-profile", profileUpdate);
378
+ log.info("[Gym Digest] Learner profile updated");
379
+ } catch (err) {
380
+ log.warn(`[Gym Digest] Failed to update profile: ${err}`);
381
+ }
382
+
383
+ // ── Step 8: Struggle Detection (saved to profile for deep evaluation rubric) ──
384
+ const struggles: Array<{agentId: string; pattern: string; detail: string}> = [];
385
+
386
+ for (const s of summaries) {
387
+ if (s.messageCount < 5) continue;
388
+ const agentId = s.agentId;
389
+ if (["hub", "gym", "agentcreator"].includes(agentId)) continue;
390
+
391
+ try {
392
+ const logsRes = await apiGet(port, `/api/agents/${agentId}/logs?limit=50`);
393
+ const entries = logsRes.entries || [];
394
+
395
+ const gaveUpPatterns = /never\s*mind|forget\s*it|i('|')ll\s+do\s+it\s+(myself|manually)|this\s+isn('|')t\s+working|let('|')s\s+stop/i;
396
+ const gaveUpCount = entries.filter((e: any) => e.role === "user" && gaveUpPatterns.test(e.content || "")).length;
397
+ if (gaveUpCount >= 2) {
398
+ struggles.push({ agentId, pattern: "gave-up", detail: `User gave up ${gaveUpCount} times with @${agentId}` });
399
+ }
400
+
401
+ let corrections = 0;
402
+ for (let i = 1; i < entries.length - 1; i++) {
403
+ const prev = entries[i - 1];
404
+ const curr = entries[i];
405
+ if (prev?.role === "assistant" && curr?.role === "user") {
406
+ const msg = (curr.content || "").toLowerCase();
407
+ if (msg.startsWith("no") || msg.startsWith("that's not") || msg.startsWith("wrong") || msg.startsWith("try again") || /not what i/i.test(msg)) {
408
+ corrections++;
409
+ }
410
+ }
411
+ }
412
+ if (corrections >= 4) {
413
+ struggles.push({ agentId, pattern: "high-correction", detail: `High correction rate (${corrections}x) with @${agentId}` });
414
+ }
415
+ } catch { /* skip */ }
416
+ }
417
+
418
+ if (struggles.length > 0) {
419
+ profileUpdate.patterns = {
420
+ ...profileUpdate.patterns,
421
+ struggles: struggles.map((s) => ({ ...s, detectedAt: today })),
422
+ };
423
+ try {
424
+ await apiPut(port, "/api/gym/learner-profile", profileUpdate);
425
+ log.info("[Gym Digest] Learner profile updated with struggle patterns");
426
+ } catch (err) {
427
+ log.warn(`[Gym Digest] Failed to update profile with struggles: ${err}`);
428
+ }
429
+ }
430
+
431
+ // ── Step 9: Generate insights for "You tell me" mode ──
432
+ const insights: Array<{ category: string; text: string; priority: number }> = [];
433
+
434
+ // Insight from weakest dimension
435
+ const weakest = (Object.entries(scores) as [string, number][]).sort((a, b) => a[1] - b[1])[0];
436
+ if (weakest && weakest[1] < 3) {
437
+ const dimTips: Record<string, string> = {
438
+ application: "You're not yet using agents for real work tasks regularly. Bringing a real task to an agent — even a small one — builds the strongest intuition.",
439
+ communication: "Your prompts could be more detailed. Try giving agents more context upfront: what you want, why, and what good looks like.",
440
+ knowledge: "You haven't explored the learning programs yet. Structured programs are the fastest way to level up.",
441
+ orchestration: "You're doing everything manually. Goals, cron jobs, and delegation can automate the repetitive parts.",
442
+ craft: "Your agents are still generic. Specialized agents with focused system prompts and curated tools outperform generalists.",
443
+ };
444
+ insights.push({
445
+ category: "dimension",
446
+ text: dimTips[weakest[0]] || `Your ${weakest[0]} score is ${weakest[1]}/5 — room to grow.`,
447
+ priority: 5 - weakest[1],
448
+ });
449
+ }
450
+
451
+ // Insight from struggles
452
+ if (struggles.length > 0) {
453
+ const s = struggles[0];
454
+ insights.push({
455
+ category: "struggle",
456
+ text: s.pattern === "gave-up"
457
+ ? `You've been hitting walls with @${s.agentId}. This usually means the prompt needs restructuring — not that the agent can't do it.`
458
+ : `Lots of corrections with @${s.agentId}. Front-loading context in your initial prompt can cut the back-and-forth in half.`,
459
+ priority: 4,
460
+ });
461
+ }
462
+
463
+ // Insight from dormant agents
464
+ if (dormantAgents.length > 0) {
465
+ insights.push({
466
+ category: "dormant",
467
+ text: `You have ${dormantAgents.length} dormant agent${dormantAgents.length > 1 ? "s" : ""} (${dormantAgents.slice(0, 3).map(a => "@" + a).join(", ")}). Worth reviewing — maybe reconfigure or retire them.`,
468
+ priority: 2,
469
+ });
470
+ }
471
+
472
+ // Insight from unused high-value feature
473
+ const featureValue: Record<string, { value: number; reason: string }> = {
474
+ "goals": { value: 5, reason: "Automate recurring tasks — your agents can work while you sleep." },
475
+ "mcps": { value: 5, reason: "Connect to external services (Slack, GitHub, APIs) for powerful workflows." },
476
+ "projects": { value: 4, reason: "Organize multi-agent initiatives with tasks and tracking." },
477
+ "cron": { value: 4, reason: "Schedule regular agent check-ins, reports, or maintenance." },
478
+ "wiki": { value: 3, reason: "Let agents learn and remember facts across sessions." },
479
+ "advanced-memory": { value: 3, reason: "Semantic search across past conversations for deeper context." },
480
+ "multi-model": { value: 2, reason: "Use different AI models for different tasks." },
481
+ "delegation": { value: 2, reason: "Let agents hand off work to each other for complex workflows." },
482
+ };
483
+ const topGap = neverUsed
484
+ .filter((f) => featureValue[f])
485
+ .sort((a, b) => (featureValue[b]?.value || 0) - (featureValue[a]?.value || 0))[0];
486
+
487
+ if (topGap) {
488
+ insights.push({
489
+ category: "feature-gap",
490
+ text: `You haven't tried ${topGap} yet. ${featureValue[topGap].reason}`,
491
+ priority: featureValue[topGap].value >= 4 ? 3 : 1,
492
+ });
493
+ }
494
+
495
+ // Sort by priority and pick top recommendation
496
+ insights.sort((a, b) => b.priority - a.priority);
497
+ const topRec = insights[0];
498
+
499
+ // Build summary
500
+ const summaryParts: string[] = [];
501
+ const overallLevel = Math.round(Object.values(scores).reduce((a, b) => a + b, 0) / 5);
502
+ summaryParts.push(`Overall level: ${scoreLabels[overallLevel] || "Developing"} (avg ${(Object.values(scores).reduce((a, b) => a + b, 0) / 5).toFixed(1)}/5).`);
503
+ summaryParts.push(`${activeAgents.length} active agent${activeAgents.length !== 1 ? "s" : ""}, ~${messagesThisWeek} messages this week, ${streakCurrent}-day streak.`);
504
+ if (weakest && weakest[1] < 3) summaryParts.push(`Weakest area: ${weakest[0]} (${weakest[1]}/5).`);
505
+
506
+ try {
507
+ await apiPost(port, "/api/gym/insights", {
508
+ insights: insights.map(({ category, text }) => ({ category, text })),
509
+ topRecommendation: topRec ? topRec.text : null,
510
+ summary: summaryParts.join(" "),
511
+ });
512
+ log.info(`[Gym Digest] Insights updated (${insights.length} insights, top: ${topRec?.category || "none"})`);
513
+ } catch (err) {
514
+ log.warn(`[Gym Digest] Failed to update insights: ${err}`);
515
+ }
516
+
517
+ log.info("[Gym Digest] Complete.");
518
+ } catch (err) {
519
+ log.error(`[Gym Digest] Failed: ${err}`);
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Start the activity digest cron job.
525
+ * Runs daily at 6am when gymEnabled is true.
526
+ */
527
+ export function startActivityDigest(config: DigestConfig): void {
528
+ if (scheduledTask) {
529
+ scheduledTask.stop();
530
+ scheduledTask = null;
531
+ }
532
+
533
+ scheduledTask = cron.schedule("0 6 * * *", async () => {
534
+ await runActivityDigest(config);
535
+ });
536
+
537
+ log.info("[Gym Digest] Scheduled: daily at 6am");
538
+ }
539
+
540
+ export function stopActivityDigest(): void {
541
+ if (scheduledTask) {
542
+ scheduledTask.stop();
543
+ scheduledTask = null;
544
+ log.info("[Gym Digest] Stopped");
545
+ }
546
+ }