milaidy 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 (253) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +538 -0
  3. package/dist/argv-CfSowvEA.js +63 -0
  4. package/dist/config-B-mboG4v.js +4 -0
  5. package/dist/eliza-CPJjgw-e.js +1491 -0
  6. package/dist/eliza.js +2192 -0
  7. package/dist/entry.js +232 -0
  8. package/dist/index.js +209 -0
  9. package/dist/links-BFKlWqSe.js +15 -0
  10. package/dist/paths-D_yh1DEJ.js +69 -0
  11. package/dist/plugins-cli-B7kSre2c.js +134 -0
  12. package/dist/program-6KwWwKKh.js +510 -0
  13. package/dist/register.agents-CPVmSjMG.js +17 -0
  14. package/dist/register.browser-B2ooXxNx.js +15 -0
  15. package/dist/register.channels-CMYQ6K6Y.js +42 -0
  16. package/dist/register.cron-D91lY1_Y.js +9 -0
  17. package/dist/register.devices-rU5I5L_y.js +13 -0
  18. package/dist/register.gateway-82SLAvw3.js +22 -0
  19. package/dist/register.hooks-B_XTBEkt.js +9 -0
  20. package/dist/register.logs-BgEGcPd8.js +10 -0
  21. package/dist/register.models-BJt9eVgZ.js +26 -0
  22. package/dist/register.nodes-B5xY1s8a.js +9 -0
  23. package/dist/register.skills-SFQqYIhg.js +10 -0
  24. package/dist/register.subclis-uF_AsbWR.js +187 -0
  25. package/dist/run-main-XODklzS-.js +56 -0
  26. package/dist/theme-DBvtuGeq.js +36 -0
  27. package/dist/utils-C1AUpp_V.js +42 -0
  28. package/dist/version-Cpn3yr5D.js +26 -0
  29. package/dist/workspace-Co3Wul2D.js +206 -0
  30. package/dist/workspace-DCA6MNVK.js +350 -0
  31. package/docs/.i18n/README.md +31 -0
  32. package/docs/.i18n/glossary.zh-CN.json +210 -0
  33. package/docs/.i18n/zh-CN.tm.jsonl +1329 -0
  34. package/docs/CNAME +1 -0
  35. package/docs/automation/cron-jobs.md +468 -0
  36. package/docs/automation/cron-vs-heartbeat.md +254 -0
  37. package/docs/automation/gmail-pubsub.md +256 -0
  38. package/docs/automation/poll.md +69 -0
  39. package/docs/automation/webhook.md +163 -0
  40. package/docs/bedrock.md +176 -0
  41. package/docs/brave-search.md +41 -0
  42. package/docs/broadcast-groups.md +442 -0
  43. package/docs/cli/acp.md +170 -0
  44. package/docs/cli/agent.md +24 -0
  45. package/docs/cli/agents.md +75 -0
  46. package/docs/cli/approvals.md +50 -0
  47. package/docs/cli/browser.md +107 -0
  48. package/docs/cli/channels.md +79 -0
  49. package/docs/cli/config.md +50 -0
  50. package/docs/cli/configure.md +33 -0
  51. package/docs/cli/cron.md +42 -0
  52. package/docs/cli/dashboard.md +16 -0
  53. package/docs/cli/devices.md +67 -0
  54. package/docs/cli/directory.md +63 -0
  55. package/docs/cli/dns.md +23 -0
  56. package/docs/cli/docs.md +15 -0
  57. package/docs/cli/doctor.md +41 -0
  58. package/docs/cli/gateway.md +199 -0
  59. package/docs/cli/health.md +21 -0
  60. package/docs/cli/hooks.md +291 -0
  61. package/docs/cli/index.md +1029 -0
  62. package/docs/cli/logs.md +24 -0
  63. package/docs/cli/memory.md +45 -0
  64. package/docs/cli/message.md +239 -0
  65. package/docs/cli/models.md +79 -0
  66. package/docs/cli/node.md +112 -0
  67. package/docs/cli/nodes.md +73 -0
  68. package/docs/cli/onboard.md +29 -0
  69. package/docs/cli/pairing.md +21 -0
  70. package/docs/cli/plugins.md +62 -0
  71. package/docs/cli/reset.md +17 -0
  72. package/docs/cli/sandbox.md +152 -0
  73. package/docs/cli/security.md +26 -0
  74. package/docs/cli/sessions.md +16 -0
  75. package/docs/cli/setup.md +29 -0
  76. package/docs/cli/skills.md +26 -0
  77. package/docs/cli/status.md +26 -0
  78. package/docs/cli/system.md +60 -0
  79. package/docs/cli/tui.md +23 -0
  80. package/docs/cli/uninstall.md +17 -0
  81. package/docs/cli/update.md +98 -0
  82. package/docs/cli/voicecall.md +34 -0
  83. package/docs/cli/webhooks.md +25 -0
  84. package/docs/concepts/agent-loop.md +146 -0
  85. package/docs/concepts/agent-workspace.md +229 -0
  86. package/docs/concepts/agent.md +122 -0
  87. package/docs/concepts/architecture.md +129 -0
  88. package/docs/concepts/channel-routing.md +114 -0
  89. package/docs/concepts/compaction.md +61 -0
  90. package/docs/concepts/context.md +159 -0
  91. package/docs/concepts/features.md +53 -0
  92. package/docs/concepts/group-messages.md +84 -0
  93. package/docs/concepts/groups.md +373 -0
  94. package/docs/concepts/markdown-formatting.md +130 -0
  95. package/docs/concepts/memory.md +546 -0
  96. package/docs/concepts/messages.md +154 -0
  97. package/docs/concepts/model-failover.md +149 -0
  98. package/docs/concepts/model-providers.md +315 -0
  99. package/docs/concepts/models.md +208 -0
  100. package/docs/concepts/multi-agent.md +376 -0
  101. package/docs/concepts/oauth.md +145 -0
  102. package/docs/concepts/plugins.md +454 -0
  103. package/docs/concepts/presence.md +102 -0
  104. package/docs/concepts/queue.md +89 -0
  105. package/docs/concepts/retry.md +69 -0
  106. package/docs/concepts/secrets.md +300 -0
  107. package/docs/concepts/session-pruning.md +122 -0
  108. package/docs/concepts/session-tool.md +193 -0
  109. package/docs/concepts/session.md +188 -0
  110. package/docs/concepts/sessions.md +10 -0
  111. package/docs/concepts/skills.md +392 -0
  112. package/docs/concepts/streaming.md +135 -0
  113. package/docs/concepts/system-prompt.md +114 -0
  114. package/docs/concepts/timezone.md +91 -0
  115. package/docs/concepts/typebox.md +289 -0
  116. package/docs/concepts/typing-indicators.md +68 -0
  117. package/docs/concepts/usage-tracking.md +35 -0
  118. package/docs/custom.css +4 -0
  119. package/docs/date-time.md +128 -0
  120. package/docs/debugging.md +162 -0
  121. package/docs/docs.json +1599 -0
  122. package/docs/environment.md +81 -0
  123. package/docs/hooks.md +876 -0
  124. package/docs/index.md +179 -0
  125. package/docs/install/ansible.md +208 -0
  126. package/docs/install/bun.md +59 -0
  127. package/docs/install/development-channels.md +75 -0
  128. package/docs/install/docker.md +567 -0
  129. package/docs/install/index.md +185 -0
  130. package/docs/install/installer.md +123 -0
  131. package/docs/install/migrating.md +192 -0
  132. package/docs/install/nix.md +96 -0
  133. package/docs/install/node.md +78 -0
  134. package/docs/install/uninstall.md +128 -0
  135. package/docs/install/updating.md +228 -0
  136. package/docs/logging.md +350 -0
  137. package/docs/multi-agent-sandbox-tools.md +395 -0
  138. package/docs/network.md +54 -0
  139. package/docs/nodes/audio.md +114 -0
  140. package/docs/nodes/camera.md +156 -0
  141. package/docs/nodes/images.md +72 -0
  142. package/docs/nodes/index.md +341 -0
  143. package/docs/nodes/location-command.md +113 -0
  144. package/docs/nodes/media-understanding.md +379 -0
  145. package/docs/nodes/talk.md +90 -0
  146. package/docs/nodes/voicewake.md +65 -0
  147. package/docs/northflank.mdx +53 -0
  148. package/docs/perplexity.md +80 -0
  149. package/docs/platforms/android.md +129 -0
  150. package/docs/platforms/digitalocean.md +262 -0
  151. package/docs/platforms/exe-dev.md +125 -0
  152. package/docs/platforms/fly.md +486 -0
  153. package/docs/platforms/gcp.md +503 -0
  154. package/docs/platforms/hetzner.md +330 -0
  155. package/docs/platforms/index.md +53 -0
  156. package/docs/platforms/ios.md +106 -0
  157. package/docs/platforms/linux.md +94 -0
  158. package/docs/platforms/mac/bundled-gateway.md +73 -0
  159. package/docs/platforms/mac/canvas.md +125 -0
  160. package/docs/platforms/mac/child-process.md +69 -0
  161. package/docs/platforms/mac/dev-setup.md +102 -0
  162. package/docs/platforms/mac/health.md +34 -0
  163. package/docs/platforms/mac/icon.md +31 -0
  164. package/docs/platforms/mac/logging.md +57 -0
  165. package/docs/platforms/mac/menu-bar.md +81 -0
  166. package/docs/platforms/mac/peekaboo.md +65 -0
  167. package/docs/platforms/mac/permissions.md +44 -0
  168. package/docs/platforms/mac/release.md +85 -0
  169. package/docs/platforms/mac/remote.md +83 -0
  170. package/docs/platforms/mac/signing.md +47 -0
  171. package/docs/platforms/mac/skills.md +33 -0
  172. package/docs/platforms/mac/voice-overlay.md +60 -0
  173. package/docs/platforms/mac/voicewake.md +67 -0
  174. package/docs/platforms/mac/webchat.md +41 -0
  175. package/docs/platforms/mac/xpc.md +61 -0
  176. package/docs/platforms/macos-vm.md +281 -0
  177. package/docs/platforms/macos.md +203 -0
  178. package/docs/platforms/oracle.md +303 -0
  179. package/docs/platforms/raspberry-pi.md +358 -0
  180. package/docs/platforms/windows.md +159 -0
  181. package/docs/plugin.md +651 -0
  182. package/docs/plugins/agent-tools.md +99 -0
  183. package/docs/plugins/manifest.md +71 -0
  184. package/docs/plugins/voice-call.md +273 -0
  185. package/docs/plugins/zalouser.md +70 -0
  186. package/docs/providers/anthropic.md +152 -0
  187. package/docs/providers/claude-max-api-proxy.md +148 -0
  188. package/docs/providers/cloudflare-ai-gateway.md +71 -0
  189. package/docs/providers/deepgram.md +93 -0
  190. package/docs/providers/glm.md +33 -0
  191. package/docs/providers/index.md +63 -0
  192. package/docs/providers/minimax.md +208 -0
  193. package/docs/providers/models.md +51 -0
  194. package/docs/providers/moonshot.md +142 -0
  195. package/docs/providers/ollama.md +223 -0
  196. package/docs/providers/openai.md +62 -0
  197. package/docs/providers/opencode.md +36 -0
  198. package/docs/providers/openrouter.md +37 -0
  199. package/docs/providers/qwen.md +53 -0
  200. package/docs/providers/synthetic.md +99 -0
  201. package/docs/providers/venice.md +267 -0
  202. package/docs/providers/vercel-ai-gateway.md +50 -0
  203. package/docs/providers/xiaomi.md +64 -0
  204. package/docs/providers/zai.md +36 -0
  205. package/docs/railway.mdx +99 -0
  206. package/docs/reference/templates/AGENTS.md +9 -0
  207. package/docs/reference/templates/BOOTSTRAP.md +3 -0
  208. package/docs/reference/templates/HEARTBEAT.md +3 -0
  209. package/docs/reference/templates/IDENTITY.md +3 -0
  210. package/docs/reference/templates/TOOLS.md +3 -0
  211. package/docs/reference/templates/USER.md +3 -0
  212. package/docs/render.mdx +165 -0
  213. package/docs/start/docs-directory.md +63 -0
  214. package/docs/start/getting-started.md +212 -0
  215. package/docs/start/milaidy.md +247 -0
  216. package/docs/start/onboarding.md +258 -0
  217. package/docs/start/pairing.md +86 -0
  218. package/docs/start/quickstart.md +81 -0
  219. package/docs/start/setup.md +149 -0
  220. package/docs/start/showcase.md +416 -0
  221. package/docs/start/wizard.md +418 -0
  222. package/docs/testing.md +368 -0
  223. package/docs/token-use.md +112 -0
  224. package/docs/tools/agent-send.md +53 -0
  225. package/docs/tools/apply-patch.md +50 -0
  226. package/docs/tools/browser-linux-troubleshooting.md +139 -0
  227. package/docs/tools/browser-login.md +68 -0
  228. package/docs/tools/browser.md +576 -0
  229. package/docs/tools/chrome-extension.md +178 -0
  230. package/docs/tools/clawhub.md +257 -0
  231. package/docs/tools/creating-skills.md +54 -0
  232. package/docs/tools/elevated.md +57 -0
  233. package/docs/tools/exec-approvals.md +246 -0
  234. package/docs/tools/exec.md +179 -0
  235. package/docs/tools/firecrawl.md +61 -0
  236. package/docs/tools/index.md +508 -0
  237. package/docs/tools/llm-task.md +115 -0
  238. package/docs/tools/reactions.md +22 -0
  239. package/docs/tools/skills-config.md +76 -0
  240. package/docs/tools/skills.md +300 -0
  241. package/docs/tools/slash-commands.md +196 -0
  242. package/docs/tools/subagents.md +151 -0
  243. package/docs/tools/thinking.md +73 -0
  244. package/docs/tools/web.md +261 -0
  245. package/docs/tui.md +159 -0
  246. package/docs/vps.md +43 -0
  247. package/docs/web/control-ui.md +221 -0
  248. package/docs/web/dashboard.md +46 -0
  249. package/docs/web/index.md +116 -0
  250. package/docs/web/webchat.md +49 -0
  251. package/milaidy.mjs +14 -0
  252. package/package.json +271 -0
  253. package/skills/.cache/catalog.json +88519 -0
@@ -0,0 +1,1491 @@
1
+ import { d as filterBootstrapFilesForSession, f as loadWorkspaceBootstrapFiles, n as DEFAULT_AGENT_WORKSPACE_DIR, p as resolveDefaultAgentWorkspaceDir, u as ensureAgentWorkspace } from "./workspace-DCA6MNVK.js";
2
+ import "./paths-D_yh1DEJ.js";
3
+ import "./theme-DBvtuGeq.js";
4
+ import "./utils-C1AUpp_V.js";
5
+ import { n as saveMilaidyConfig, t as loadMilaidyConfig } from "./workspace-Co3Wul2D.js";
6
+ import { t as VERSION } from "./version-Cpn3yr5D.js";
7
+ import process$1 from "node:process";
8
+ import { homedir, platform } from "node:os";
9
+ import { basename, extname, join, resolve } from "node:path";
10
+ import { AgentRuntime, ChannelType, MemoryType, ModelType, buildAgentMainSessionKey, createCharacter, createMessageMemory, getSessionProviders, logger, parseAgentSessionKey, resolveDefaultSessionStorePath, stringToUuid } from "@elizaos/core";
11
+ import { readFile, readdir, stat } from "node:fs/promises";
12
+ import { existsSync } from "node:fs";
13
+ import { pathToFileURL } from "node:url";
14
+ import crypto from "node:crypto";
15
+ import * as readline from "node:readline";
16
+
17
+ //#region src/config/plugin-auto-enable.ts
18
+ /**
19
+ * Channel to plugin mappings.
20
+ */
21
+ const CHANNEL_PLUGINS = {
22
+ telegram: "@elizaos/plugin-telegram",
23
+ discord: "@elizaos/plugin-discord",
24
+ slack: "@elizaos/plugin-slack",
25
+ twitter: "@elizaos/plugin-twitter",
26
+ whatsapp: "@elizaos/plugin-whatsapp",
27
+ signal: "@elizaos/plugin-signal",
28
+ bluebubbles: "@elizaos/plugin-bluebubbles",
29
+ imessage: "@elizaos/plugin-imessage",
30
+ farcaster: "@elizaos/plugin-farcaster",
31
+ lens: "@elizaos/plugin-lens",
32
+ msteams: "@elizaos/plugin-msteams",
33
+ mattermost: "@elizaos/plugin-mattermost",
34
+ googlechat: "@elizaos/plugin-google-chat",
35
+ feishu: "@elizaos/plugin-feishu",
36
+ matrix: "@elizaos/plugin-matrix",
37
+ nostr: "@elizaos/plugin-nostr"
38
+ };
39
+ /**
40
+ * Provider to plugin mappings.
41
+ */
42
+ const PROVIDER_PLUGINS = {
43
+ "google-antigravity": "@elizaos/plugin-google-antigravity",
44
+ "google-gemini": "@elizaos/plugin-google-gemini",
45
+ openai: "@elizaos/plugin-openai",
46
+ anthropic: "@elizaos/plugin-anthropic",
47
+ qwen: "@elizaos/plugin-qwen",
48
+ minimax: "@elizaos/plugin-minimax",
49
+ groq: "@elizaos/plugin-groq",
50
+ xai: "@elizaos/plugin-xai",
51
+ openrouter: "@elizaos/plugin-openrouter",
52
+ ollama: "@elizaos/plugin-ollama",
53
+ deepseek: "@elizaos/plugin-deepseek",
54
+ together: "@elizaos/plugin-together",
55
+ mistral: "@elizaos/plugin-mistral",
56
+ cohere: "@elizaos/plugin-cohere",
57
+ perplexity: "@elizaos/plugin-perplexity"
58
+ };
59
+ /**
60
+ * Auth provider secret key to plugin mappings.
61
+ * Used to auto-enable plugins when API keys are present in environment or secrets.
62
+ */
63
+ const AUTH_PROVIDER_PLUGINS = {
64
+ ANTHROPIC_API_KEY: "@elizaos/plugin-anthropic",
65
+ CLAUDE_API_KEY: "@elizaos/plugin-anthropic",
66
+ OPENAI_API_KEY: "@elizaos/plugin-openai",
67
+ GOOGLE_API_KEY: "@elizaos/plugin-google-gemini",
68
+ GOOGLE_GENERATIVE_AI_API_KEY: "@elizaos/plugin-google-gemini",
69
+ GOOGLE_CLOUD_API_KEY: "@elizaos/plugin-google-antigravity",
70
+ GROQ_API_KEY: "@elizaos/plugin-groq",
71
+ XAI_API_KEY: "@elizaos/plugin-xai",
72
+ GROK_API_KEY: "@elizaos/plugin-xai",
73
+ OPENROUTER_API_KEY: "@elizaos/plugin-openrouter",
74
+ OLLAMA_BASE_URL: "@elizaos/plugin-ollama",
75
+ DEEPSEEK_API_KEY: "@elizaos/plugin-deepseek",
76
+ TOGETHER_API_KEY: "@elizaos/plugin-together",
77
+ MISTRAL_API_KEY: "@elizaos/plugin-mistral",
78
+ COHERE_API_KEY: "@elizaos/plugin-cohere",
79
+ PERPLEXITY_API_KEY: "@elizaos/plugin-perplexity"
80
+ };
81
+ /**
82
+ * Feature to plugin mappings for optional features.
83
+ */
84
+ const FEATURE_PLUGINS = {
85
+ browser: "@elizaos/plugin-browser",
86
+ cron: "@elizaos/plugin-cron",
87
+ shell: "@elizaos/plugin-shell",
88
+ imageGen: "@elizaos/plugin-image-generation",
89
+ tts: "@elizaos/plugin-tts",
90
+ stt: "@elizaos/plugin-stt",
91
+ agentSkills: "@elizaos/plugin-agent-skills",
92
+ directives: "@elizaos/plugin-directives",
93
+ commands: "@elizaos/plugin-commands",
94
+ diagnosticsOtel: "@elizaos/plugin-diagnostics-otel",
95
+ webhooks: "@elizaos/plugin-webhooks",
96
+ gmailWatch: "@elizaos/plugin-gmail-watch",
97
+ personality: "@elizaos/plugin-personality",
98
+ experience: "@elizaos/plugin-experience",
99
+ form: "@elizaos/plugin-form"
100
+ };
101
+ /**
102
+ * Check if a channel is configured with credentials.
103
+ */
104
+ function isChannelConfigured(channelName, channelConfig) {
105
+ if (!channelConfig || typeof channelConfig !== "object") return false;
106
+ const config = channelConfig;
107
+ if (config.botToken || config.token || config.apiKey) return true;
108
+ switch (channelName) {
109
+ case "bluebubbles": return Boolean(config.serverUrl && config.password);
110
+ case "imessage": return Boolean(config.cliPath);
111
+ case "whatsapp": return Boolean(config.authState || config.sessionPath);
112
+ default: return Object.keys(config).length > 0;
113
+ }
114
+ }
115
+ /**
116
+ * Apply plugin auto-enable based on configuration.
117
+ *
118
+ * This function analyzes the configuration and automatically enables
119
+ * plugins that are required based on configured channels and providers.
120
+ *
121
+ * @param params - Parameters
122
+ * @returns Result with updated config and changes
123
+ */
124
+ function applyPluginAutoEnable(params) {
125
+ const { config, env } = params;
126
+ const changes = [];
127
+ const updatedConfig = JSON.parse(JSON.stringify(config));
128
+ if (updatedConfig.plugins && typeof updatedConfig.plugins === "object" && updatedConfig.plugins.enabled === false) return {
129
+ config: updatedConfig,
130
+ changes
131
+ };
132
+ updatedConfig.plugins = updatedConfig.plugins ?? {};
133
+ const pluginsConfig = updatedConfig.plugins;
134
+ pluginsConfig.allow = pluginsConfig.allow ?? [];
135
+ pluginsConfig.entries = pluginsConfig.entries ?? {};
136
+ if (updatedConfig.channels && typeof updatedConfig.channels === "object") for (const [channelName, channelConfig] of Object.entries(updatedConfig.channels)) {
137
+ const pluginName = CHANNEL_PLUGINS[channelName];
138
+ if (!pluginName) continue;
139
+ if (!isChannelConfigured(channelName, channelConfig)) continue;
140
+ const entryConfig = pluginsConfig.entries[channelName];
141
+ if (entryConfig && entryConfig.enabled === false) continue;
142
+ if (!pluginsConfig.allow.includes(pluginName) && !pluginsConfig.allow.includes(channelName)) {
143
+ pluginsConfig.allow.push(channelName);
144
+ changes.push(`Auto-enabled plugin: ${pluginName} (channel: ${channelName})`);
145
+ }
146
+ }
147
+ if (updatedConfig.auth && typeof updatedConfig.auth === "object" && updatedConfig.auth.profiles) {
148
+ const profiles = updatedConfig.auth.profiles;
149
+ for (const [profileKey, profile] of Object.entries(profiles)) {
150
+ const provider = profile.provider;
151
+ if (!provider) continue;
152
+ const pluginName = PROVIDER_PLUGINS[provider];
153
+ if (!pluginName) continue;
154
+ if (!pluginsConfig.allow.includes(pluginName) && !pluginsConfig.allow.includes(provider)) {
155
+ pluginsConfig.allow.push(provider);
156
+ changes.push(`Auto-enabled plugin: ${pluginName} (auth profile: ${profileKey})`);
157
+ }
158
+ }
159
+ }
160
+ for (const [envKey, pluginName] of Object.entries(AUTH_PROVIDER_PLUGINS)) {
161
+ const envValue = env[envKey];
162
+ if (!envValue || typeof envValue !== "string" || envValue.trim() === "") continue;
163
+ const pluginId = pluginName.replace("@elizaos/plugin-", "");
164
+ const entryConfig = pluginsConfig.entries[pluginId];
165
+ if (entryConfig && entryConfig.enabled === false) continue;
166
+ if (!pluginsConfig.allow.includes(pluginName) && !pluginsConfig.allow.includes(pluginId)) {
167
+ pluginsConfig.allow.push(pluginId);
168
+ changes.push(`Auto-enabled plugin: ${pluginName} (env: ${envKey})`);
169
+ }
170
+ }
171
+ if (updatedConfig.features && typeof updatedConfig.features === "object") {
172
+ const features = updatedConfig.features;
173
+ for (const [featureName, featureConfig] of Object.entries(features)) {
174
+ const pluginName = FEATURE_PLUGINS[featureName];
175
+ if (!pluginName) continue;
176
+ if (!(featureConfig === true || featureConfig && typeof featureConfig === "object" && featureConfig.enabled !== false)) continue;
177
+ const pluginId = pluginName.replace("@elizaos/plugin-", "");
178
+ const entryConfig = pluginsConfig.entries[pluginId];
179
+ if (entryConfig && entryConfig.enabled === false) continue;
180
+ if (!pluginsConfig.allow.includes(pluginName) && !pluginsConfig.allow.includes(pluginId)) {
181
+ pluginsConfig.allow.push(pluginId);
182
+ changes.push(`Auto-enabled plugin: ${pluginName} (feature: ${featureName})`);
183
+ }
184
+ }
185
+ }
186
+ const hooksConfig = updatedConfig.hooks;
187
+ if (hooksConfig && hooksConfig.enabled !== false && hooksConfig.token) {
188
+ const webhooksPlugin = FEATURE_PLUGINS.webhooks;
189
+ if (webhooksPlugin) {
190
+ const pluginId = webhooksPlugin.replace("@elizaos/plugin-", "");
191
+ if (!pluginsConfig.allow.includes(webhooksPlugin) && !pluginsConfig.allow.includes(pluginId)) {
192
+ pluginsConfig.allow.push(pluginId);
193
+ changes.push(`Auto-enabled plugin: ${webhooksPlugin} (hooks.enabled + hooks.token)`);
194
+ }
195
+ }
196
+ }
197
+ if (hooksConfig) {
198
+ const gmailConfig = hooksConfig.gmail ?? {};
199
+ if (typeof gmailConfig.account === "string" && gmailConfig.account.trim()) {
200
+ const gmailPlugin = FEATURE_PLUGINS.gmailWatch;
201
+ if (gmailPlugin) {
202
+ const pluginId = gmailPlugin.replace("@elizaos/plugin-", "");
203
+ if (!pluginsConfig.allow.includes(gmailPlugin) && !pluginsConfig.allow.includes(pluginId)) {
204
+ pluginsConfig.allow.push(pluginId);
205
+ changes.push(`Auto-enabled plugin: ${gmailPlugin} (hooks.gmail.account)`);
206
+ }
207
+ }
208
+ }
209
+ }
210
+ return {
211
+ config: updatedConfig,
212
+ changes
213
+ };
214
+ }
215
+
216
+ //#endregion
217
+ //#region src/hooks/registry.ts
218
+ /**
219
+ * Hook Registry — event registration and dispatch.
220
+ *
221
+ * Maintains a registry of hook handlers keyed by event strings.
222
+ * Handlers are dispatched sequentially; errors are isolated.
223
+ *
224
+ * @module hooks/registry
225
+ */
226
+ /** Internal registry: event key -> handler list. */
227
+ const registry = /* @__PURE__ */ new Map();
228
+ /**
229
+ * Register a handler for an event key.
230
+ *
231
+ * Event keys can be:
232
+ * - General: "command" (matches all command events)
233
+ * - Specific: "command:new" (matches only /new)
234
+ */
235
+ function registerHook(eventKey, handler) {
236
+ const handlers = registry.get(eventKey) ?? [];
237
+ handlers.push(handler);
238
+ registry.set(eventKey, handlers);
239
+ }
240
+ /**
241
+ * Clear all registered hooks (useful for testing).
242
+ */
243
+ function clearHooks() {
244
+ registry.clear();
245
+ }
246
+ /**
247
+ * Trigger a hook event. Dispatches to all matching handlers.
248
+ *
249
+ * Matching order:
250
+ * 1. Specific key: "command:new"
251
+ * 2. General key: "command"
252
+ *
253
+ * Handlers run sequentially. Errors are caught and logged
254
+ * but don't prevent other handlers from running.
255
+ */
256
+ async function triggerHook(event) {
257
+ const specificKey = `${event.type}:${event.action}`;
258
+ const generalKey = event.type;
259
+ const handlers = [];
260
+ const specificHandlers = registry.get(specificKey);
261
+ if (specificHandlers) for (const handler of specificHandlers) handlers.push({
262
+ key: specificKey,
263
+ handler
264
+ });
265
+ const generalHandlers = registry.get(generalKey);
266
+ if (generalHandlers) for (const handler of generalHandlers) handlers.push({
267
+ key: generalKey,
268
+ handler
269
+ });
270
+ if (handlers.length === 0) return;
271
+ for (const { key, handler } of handlers) try {
272
+ await handler(event);
273
+ } catch (err) {
274
+ const msg = err instanceof Error ? err.message : String(err);
275
+ logger.error(`[hooks] Handler error for "${key}": ${msg}`);
276
+ }
277
+ }
278
+ /**
279
+ * Create a hook event with sensible defaults.
280
+ */
281
+ function createHookEvent(type, action, sessionKey, context = {}) {
282
+ return {
283
+ type,
284
+ action,
285
+ sessionKey,
286
+ timestamp: /* @__PURE__ */ new Date(),
287
+ messages: [],
288
+ context
289
+ };
290
+ }
291
+
292
+ //#endregion
293
+ //#region src/hooks/discovery.ts
294
+ /**
295
+ * Hook Discovery — scan directories for hooks.
296
+ *
297
+ * Discovers hooks from three locations (in order of precedence):
298
+ * 1. Workspace hooks: <workspace>/hooks/ (highest)
299
+ * 2. Managed hooks: ~/.milaidy/hooks/
300
+ * 3. Bundled hooks: <milaidy>/dist/hooks/bundled/ (lowest)
301
+ *
302
+ * Each hook is a directory containing HOOK.md + handler.ts/js.
303
+ *
304
+ * @module hooks/discovery
305
+ */
306
+ const HOOK_MD = "HOOK.md";
307
+ const HANDLER_NAMES = [
308
+ "handler.ts",
309
+ "handler.js",
310
+ "index.ts",
311
+ "index.js"
312
+ ];
313
+ /**
314
+ * Parse YAML-like frontmatter from HOOK.md content.
315
+ * Supports name, description, homepage, and metadata (JSON).
316
+ */
317
+ function parseFrontmatter(content) {
318
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
319
+ if (!fmMatch) return null;
320
+ const fmBlock = fmMatch[1];
321
+ const result = {
322
+ name: "",
323
+ description: ""
324
+ };
325
+ for (const line of fmBlock.split("\n")) {
326
+ const kvMatch = line.match(/^(\w+):\s*(.+)/);
327
+ if (!kvMatch) continue;
328
+ const [, key, rawValue] = kvMatch;
329
+ const value = rawValue.replace(/^["']|["']$/g, "").trim();
330
+ switch (key) {
331
+ case "name":
332
+ result.name = value;
333
+ break;
334
+ case "description":
335
+ result.description = value;
336
+ break;
337
+ case "homepage":
338
+ result.homepage = value;
339
+ break;
340
+ case "metadata":
341
+ try {
342
+ const metaStart = fmBlock.indexOf("metadata:");
343
+ if (metaStart !== -1) {
344
+ const jsonMatch = fmBlock.slice(metaStart + 9).trim().match(/\{[\s\S]*\}/);
345
+ if (jsonMatch) result.metadata = JSON.parse(jsonMatch[0]);
346
+ }
347
+ } catch {
348
+ try {
349
+ result.metadata = JSON.parse(value);
350
+ } catch {
351
+ logger.warn(`[hooks] Failed to parse metadata in HOOK.md`);
352
+ }
353
+ }
354
+ break;
355
+ }
356
+ }
357
+ return result.name ? result : null;
358
+ }
359
+ /**
360
+ * Extract MilaidyHookMetadata from parsed frontmatter.
361
+ */
362
+ function extractMetadata(frontmatter) {
363
+ const milaidy = frontmatter.metadata?.milaidy;
364
+ if (!milaidy) return void 0;
365
+ return {
366
+ always: milaidy.always,
367
+ hookKey: milaidy.hookKey,
368
+ emoji: milaidy.emoji,
369
+ homepage: milaidy.homepage ?? frontmatter.homepage,
370
+ events: Array.isArray(milaidy.events) ? milaidy.events : [],
371
+ export: milaidy.export,
372
+ os: milaidy.os,
373
+ requires: milaidy.requires,
374
+ install: milaidy.install
375
+ };
376
+ }
377
+ /**
378
+ * Check if a path exists and is a directory.
379
+ */
380
+ async function isDirectory(path) {
381
+ try {
382
+ return (await stat(path)).isDirectory();
383
+ } catch {
384
+ return false;
385
+ }
386
+ }
387
+ /**
388
+ * Check if a file exists.
389
+ */
390
+ async function fileExists(path) {
391
+ try {
392
+ return (await stat(path)).isFile();
393
+ } catch {
394
+ return false;
395
+ }
396
+ }
397
+ /**
398
+ * Find the handler file in a hook directory.
399
+ */
400
+ async function findHandlerPath(dir) {
401
+ for (const name of HANDLER_NAMES) {
402
+ const p = join(dir, name);
403
+ if (await fileExists(p)) return p;
404
+ }
405
+ return null;
406
+ }
407
+ /**
408
+ * Load a single hook from a directory.
409
+ */
410
+ async function loadHookFromDir(dir, source, pluginId) {
411
+ const hookMdPath = join(dir, HOOK_MD);
412
+ if (!await fileExists(hookMdPath)) return null;
413
+ const handlerPath = await findHandlerPath(dir);
414
+ if (!handlerPath) {
415
+ logger.warn(`[hooks] Hook at ${dir} has HOOK.md but no handler`);
416
+ return null;
417
+ }
418
+ try {
419
+ const frontmatter = parseFrontmatter(await readFile(hookMdPath, "utf-8"));
420
+ if (!frontmatter) {
421
+ logger.warn(`[hooks] Invalid frontmatter in ${hookMdPath}`);
422
+ return null;
423
+ }
424
+ const metadata = extractMetadata(frontmatter);
425
+ return {
426
+ hook: {
427
+ name: frontmatter.name,
428
+ description: frontmatter.description,
429
+ source,
430
+ pluginId,
431
+ filePath: hookMdPath,
432
+ baseDir: dir,
433
+ handlerPath
434
+ },
435
+ frontmatter,
436
+ metadata
437
+ };
438
+ } catch (err) {
439
+ const msg = err instanceof Error ? err.message : String(err);
440
+ logger.warn(`[hooks] Error loading hook from ${dir}: ${msg}`);
441
+ return null;
442
+ }
443
+ }
444
+ /**
445
+ * Scan a directory for hook subdirectories.
446
+ */
447
+ async function scanHooksDir(dir, source) {
448
+ if (!await isDirectory(dir)) return [];
449
+ const entries = [];
450
+ try {
451
+ const items = await readdir(dir);
452
+ for (const item of items) {
453
+ const itemPath = join(dir, item);
454
+ if (!await isDirectory(itemPath)) continue;
455
+ const entry = await loadHookFromDir(itemPath, source);
456
+ if (entry) entries.push(entry);
457
+ }
458
+ } catch (err) {
459
+ const msg = err instanceof Error ? err.message : String(err);
460
+ logger.warn(`[hooks] Error scanning ${dir}: ${msg}`);
461
+ }
462
+ return entries;
463
+ }
464
+ /**
465
+ * Discover all hooks from configured directories.
466
+ *
467
+ * Precedence (later wins on name conflicts):
468
+ * 1. Extra dirs (lowest)
469
+ * 2. Bundled hooks
470
+ * 3. Managed hooks (~/.milaidy/hooks/)
471
+ * 4. Workspace hooks (<workspace>/hooks/) (highest)
472
+ */
473
+ async function discoverHooks(options = {}) {
474
+ const seen = /* @__PURE__ */ new Map();
475
+ if (options.extraDirs) for (const dir of options.extraDirs) {
476
+ const entries = await scanHooksDir(resolve(dir.replace(/^~/, homedir())), "milaidy-managed");
477
+ for (const entry of entries) seen.set(entry.hook.name, entry);
478
+ }
479
+ if (options.bundledDir) {
480
+ const entries = await scanHooksDir(options.bundledDir, "milaidy-bundled");
481
+ for (const entry of entries) seen.set(entry.hook.name, entry);
482
+ }
483
+ const managedEntries = await scanHooksDir(join(homedir(), ".milaidy", "hooks"), "milaidy-managed");
484
+ for (const entry of managedEntries) seen.set(entry.hook.name, entry);
485
+ if (options.workspacePath) {
486
+ const wsEntries = await scanHooksDir(join(options.workspacePath.replace(/^~/, homedir()), "hooks"), "milaidy-workspace");
487
+ for (const entry of wsEntries) seen.set(entry.hook.name, entry);
488
+ }
489
+ const all = Array.from(seen.values());
490
+ logger.info(`[hooks] Discovered ${all.length} hooks`);
491
+ return all;
492
+ }
493
+
494
+ //#endregion
495
+ //#region src/hooks/eligibility.ts
496
+ /**
497
+ * Hook Eligibility — determine if a hook should be loaded.
498
+ *
499
+ * Checks OS, binary, environment, and config requirements.
500
+ *
501
+ * @module hooks/eligibility
502
+ */
503
+ /**
504
+ * Check if a binary exists on PATH.
505
+ */
506
+ function binaryExists(name) {
507
+ const pathDirs = (process.env.PATH ?? "").split(":");
508
+ for (const dir of pathDirs) if (existsSync(`${dir}/${name}`)) return true;
509
+ return false;
510
+ }
511
+ /**
512
+ * Resolve a dot-separated config path to a value.
513
+ */
514
+ function resolveConfigPath(config, pathStr) {
515
+ const parts = pathStr.split(".");
516
+ let current = config;
517
+ for (const part of parts) {
518
+ if (current === null || current === void 0 || typeof current !== "object") return;
519
+ current = current[part];
520
+ }
521
+ return current;
522
+ }
523
+ /**
524
+ * Check if a config path is truthy.
525
+ */
526
+ function isConfigPathTruthy(config, pathStr) {
527
+ const value = resolveConfigPath(config, pathStr);
528
+ return value !== void 0 && value !== null && value !== false && value !== "" && value !== 0;
529
+ }
530
+ /**
531
+ * Check if a hook meets all eligibility requirements.
532
+ */
533
+ function checkEligibility(metadata, hookConfig, milaidyConfig = {}) {
534
+ const missing = [];
535
+ if (!metadata) return {
536
+ eligible: true,
537
+ missing: []
538
+ };
539
+ if (hookConfig?.enabled === false) return {
540
+ eligible: false,
541
+ missing: ["Disabled in config"]
542
+ };
543
+ if (metadata.os && metadata.os.length > 0) {
544
+ if (!metadata.os.includes(platform())) missing.push(`OS: requires ${metadata.os.join("|")}, current: ${platform()}`);
545
+ }
546
+ if (metadata.always) return {
547
+ eligible: missing.length === 0,
548
+ missing
549
+ };
550
+ if (metadata.requires?.bins) {
551
+ for (const bin of metadata.requires.bins) if (!binaryExists(bin)) missing.push(`Binary missing: ${bin}`);
552
+ }
553
+ if (metadata.requires?.anyBins && metadata.requires.anyBins.length > 0) {
554
+ if (!metadata.requires.anyBins.some(binaryExists)) missing.push(`None of: ${metadata.requires.anyBins.join(", ")}`);
555
+ }
556
+ if (metadata.requires?.env) for (const envVar of metadata.requires.env) {
557
+ const hasInProcess = Boolean(process.env[envVar]);
558
+ const hasInHookConfig = Boolean(hookConfig?.env?.[envVar]);
559
+ if (!hasInProcess && !hasInHookConfig) missing.push(`Env missing: ${envVar}`);
560
+ }
561
+ if (metadata.requires?.config) {
562
+ for (const configPath of metadata.requires.config) if (!isConfigPathTruthy(milaidyConfig, configPath)) missing.push(`Config missing: ${configPath}`);
563
+ }
564
+ return {
565
+ eligible: missing.length === 0,
566
+ missing
567
+ };
568
+ }
569
+ /**
570
+ * Resolve per-hook config from the internal hooks config.
571
+ */
572
+ function resolveHookConfig(internalConfig, hookKey) {
573
+ return internalConfig?.entries?.[hookKey];
574
+ }
575
+
576
+ //#endregion
577
+ //#region src/hooks/loader.ts
578
+ /**
579
+ * Hook Loader — load and register hooks into the event system.
580
+ *
581
+ * Orchestrates discovery -> eligibility -> loading -> registration.
582
+ *
583
+ * @module hooks/loader
584
+ */
585
+ /**
586
+ * Dynamically import a hook handler module.
587
+ * Uses cache-busting query parameter for dev mode hot reload.
588
+ */
589
+ async function loadHandlerModule(handlerPath, exportName = "default") {
590
+ try {
591
+ const handler = (await import(`${pathToFileURL(handlerPath).href}?t=${Date.now()}`))[exportName];
592
+ if (typeof handler !== "function") {
593
+ logger.warn(`[hooks] Handler at ${handlerPath} does not export a function as "${exportName}"`);
594
+ return null;
595
+ }
596
+ return handler;
597
+ } catch (err) {
598
+ const msg = err instanceof Error ? err.message : String(err);
599
+ logger.error(`[hooks] Failed to load handler ${handlerPath}: ${msg}`);
600
+ return null;
601
+ }
602
+ }
603
+ /**
604
+ * Discover, filter, load, and register all hooks.
605
+ *
606
+ * This is the main entry point called during gateway startup.
607
+ */
608
+ async function loadHooks(options = {}) {
609
+ const { internalConfig, milaidyConfig = {} } = options;
610
+ if (internalConfig?.enabled === false) {
611
+ logger.info("[hooks] Internal hooks disabled");
612
+ return {
613
+ discovered: 0,
614
+ eligible: 0,
615
+ registered: 0,
616
+ skipped: [],
617
+ failed: []
618
+ };
619
+ }
620
+ clearHooks();
621
+ const entries = await discoverHooks({
622
+ workspacePath: options.workspacePath,
623
+ bundledDir: options.bundledDir,
624
+ extraDirs: [...options.extraDirs ?? [], ...internalConfig?.load?.extraDirs ?? []]
625
+ });
626
+ const result = {
627
+ discovered: entries.length,
628
+ eligible: 0,
629
+ registered: 0,
630
+ skipped: [],
631
+ failed: []
632
+ };
633
+ for (const entry of entries) {
634
+ const hookConfig = resolveHookConfig(internalConfig, entry.metadata?.hookKey ?? entry.hook.name);
635
+ const eligibility = checkEligibility(entry.metadata, hookConfig, milaidyConfig);
636
+ if (!eligibility.eligible) {
637
+ result.skipped.push(`${entry.hook.name}: ${eligibility.missing.join(", ")}`);
638
+ continue;
639
+ }
640
+ result.eligible++;
641
+ if (hookConfig?.enabled === false) {
642
+ result.skipped.push(`${entry.hook.name}: disabled in config`);
643
+ continue;
644
+ }
645
+ const exportName = entry.metadata?.export ?? "default";
646
+ const handler = await loadHandlerModule(entry.hook.handlerPath, exportName);
647
+ if (!handler) {
648
+ result.failed.push(entry.hook.name);
649
+ continue;
650
+ }
651
+ const events = entry.metadata?.events ?? [];
652
+ if (events.length === 0) {
653
+ logger.warn(`[hooks] Hook "${entry.hook.name}" has no events configured`);
654
+ result.skipped.push(`${entry.hook.name}: no events`);
655
+ continue;
656
+ }
657
+ for (const eventKey of events) registerHook(eventKey, handler);
658
+ const emoji = entry.metadata?.emoji ?? "šŸ”—";
659
+ logger.info(`[hooks] ${emoji} Registered: ${entry.hook.name} -> ${events.join(", ")}`);
660
+ result.registered++;
661
+ }
662
+ if (internalConfig?.handlers) for (const legacyHandler of internalConfig.handlers) try {
663
+ const handler = await loadHandlerModule(legacyHandler.module, legacyHandler.export ?? "default");
664
+ if (handler) {
665
+ registerHook(legacyHandler.event, handler);
666
+ logger.info(`[hooks] Registered legacy handler: ${legacyHandler.event} -> ${legacyHandler.module}`);
667
+ result.registered++;
668
+ }
669
+ } catch (err) {
670
+ const msg = err instanceof Error ? err.message : String(err);
671
+ logger.warn(`[hooks] Failed to load legacy handler: ${msg}`);
672
+ result.failed.push(legacyHandler.module);
673
+ }
674
+ logger.info(`[hooks] Load complete: ${result.registered}/${result.discovered} registered, ${result.skipped.length} skipped, ${result.failed.length} failed`);
675
+ return result;
676
+ }
677
+
678
+ //#endregion
679
+ //#region src/agents/workspace-provider.ts
680
+ const DEFAULT_MAX_CHARS = 2e4;
681
+ const CACHE_TTL_MS = 6e4;
682
+ const cache = /* @__PURE__ */ new Map();
683
+ async function getFiles(dir) {
684
+ const entry = cache.get(dir);
685
+ if (entry && Date.now() - entry.at < CACHE_TTL_MS) return entry.files;
686
+ const files = await loadWorkspaceBootstrapFiles(dir);
687
+ cache.set(dir, {
688
+ files,
689
+ at: Date.now()
690
+ });
691
+ return files;
692
+ }
693
+ function truncate(content, max) {
694
+ if (content.length <= max) return content;
695
+ return `${content.slice(0, max)}\n\n[... truncated at ${max.toLocaleString()} chars]`;
696
+ }
697
+ function buildContext(files, maxChars) {
698
+ const sections = [];
699
+ for (const f of files) {
700
+ if (f.missing || !f.content?.trim()) continue;
701
+ const text = truncate(f.content.trim(), maxChars);
702
+ const tag = text.length > f.content.trim().length ? " [TRUNCATED]" : "";
703
+ sections.push(`### ${f.name}${tag}\n\n${text}`);
704
+ }
705
+ if (sections.length === 0) return "";
706
+ return `## Project Context (Workspace)\n\n${sections.join("\n\n---\n\n")}`;
707
+ }
708
+ function createWorkspaceProvider(options) {
709
+ const dir = options?.workspaceDir ?? DEFAULT_AGENT_WORKSPACE_DIR;
710
+ const maxChars = options?.maxCharsPerFile ?? DEFAULT_MAX_CHARS;
711
+ return {
712
+ name: "workspaceContext",
713
+ description: "Workspace bootstrap files (AGENTS.md, TOOLS.md, IDENTITY.md, etc.)",
714
+ position: 10,
715
+ async get(_runtime, message, _state) {
716
+ try {
717
+ const allFiles = await getFiles(dir);
718
+ const sessionKey = message.metadata?.sessionKey;
719
+ return {
720
+ text: buildContext(filterBootstrapFilesForSession(allFiles, sessionKey), maxChars),
721
+ data: { workspaceDir: dir }
722
+ };
723
+ } catch (err) {
724
+ return {
725
+ text: `[Workspace context unavailable: ${err instanceof Error ? err.message : err}]`,
726
+ data: {}
727
+ };
728
+ }
729
+ }
730
+ };
731
+ }
732
+
733
+ //#endregion
734
+ //#region src/agents/session-bridge.ts
735
+ /**
736
+ * Resolve an Milaidy session key from an ElizaOS room.
737
+ *
738
+ * DMs -> agent:{agentId}:main
739
+ * Groups -> agent:{agentId}:{channel}:group:{groupId}
740
+ * Channels -> agent:{agentId}:{channel}:channel:{channelId}
741
+ * Threads append :thread:{threadId}
742
+ */
743
+ function resolveSessionKeyFromRoom(agentId, room, meta) {
744
+ const channel = meta?.channel ?? room.source ?? "unknown";
745
+ if (room.type === ChannelType.DM || room.type === ChannelType.SELF) return buildAgentMainSessionKey({
746
+ agentId,
747
+ mainKey: "main"
748
+ });
749
+ const id = meta?.groupId ?? room.channelId ?? room.id;
750
+ const base = `agent:${agentId}:${channel}:${room.type === ChannelType.GROUP ? "group" : "channel"}:${id}`;
751
+ return meta?.threadId ? `${base}:thread:${meta.threadId}` : base;
752
+ }
753
+ function createSessionKeyProvider(options) {
754
+ const agentId = options?.defaultAgentId ?? "main";
755
+ return {
756
+ name: "milaidySessionKey",
757
+ description: "Milaidy session key (DM/group/thread isolation)",
758
+ dynamic: true,
759
+ position: 5,
760
+ async get(runtime, message, _state) {
761
+ const meta = message.metadata ?? {};
762
+ const existing = meta.sessionKey;
763
+ if (existing) {
764
+ const parsed = parseAgentSessionKey(existing);
765
+ return {
766
+ text: `Session: ${existing}`,
767
+ values: {
768
+ sessionKey: existing,
769
+ agentId: parsed?.agentId ?? agentId
770
+ },
771
+ data: { sessionKey: existing }
772
+ };
773
+ }
774
+ const room = await runtime.getRoom(message.roomId);
775
+ if (!room) {
776
+ const key = buildAgentMainSessionKey({
777
+ agentId,
778
+ mainKey: "main"
779
+ });
780
+ return {
781
+ text: `Session: ${key}`,
782
+ values: { sessionKey: key },
783
+ data: { sessionKey: key }
784
+ };
785
+ }
786
+ const key = resolveSessionKeyFromRoom(agentId, room, {
787
+ threadId: meta.threadId,
788
+ groupId: meta.groupId,
789
+ channel: meta.channel ?? room.source
790
+ });
791
+ return {
792
+ text: `Session: ${key}`,
793
+ values: {
794
+ sessionKey: key,
795
+ isGroup: room.type === ChannelType.GROUP
796
+ },
797
+ data: { sessionKey: key }
798
+ };
799
+ }
800
+ };
801
+ }
802
+
803
+ //#endregion
804
+ //#region src/agents/compaction-action.ts
805
+ /**
806
+ * Compaction: summarize conversation history, store summary, set compaction point.
807
+ *
808
+ * After compaction, recentMessages provider only loads messages after lastCompactionAt.
809
+ * The summary becomes the effective "first message" in the room.
810
+ */
811
+ function buildPrompt(messages, instructions) {
812
+ let prompt = "Summarize this conversation for context preservation. Focus on decisions, facts learned, open questions, action items, and key context needed to continue.\n\nConversation:\n" + messages;
813
+ if (instructions) prompt += `\n\nAdditional instructions: ${instructions}`;
814
+ prompt += "\n\nSummary:";
815
+ return prompt;
816
+ }
817
+ async function summarize(runtime, roomId, instructions) {
818
+ const messages = await runtime.getMemories({
819
+ tableName: "messages",
820
+ roomId,
821
+ count: 200
822
+ });
823
+ if (!messages?.length) return "No conversation history to compact.";
824
+ const formatted = messages.sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0)).map((m) => {
825
+ return `${m.entityId === runtime.agentId ? "Assistant" : "User"}: ${m.content?.text ?? ""}`;
826
+ }).join("\n");
827
+ return runtime.useModel(ModelType.TEXT_LARGE, { prompt: buildPrompt(formatted, instructions) });
828
+ }
829
+ const compactAction = {
830
+ name: "COMPACT_SESSION",
831
+ similes: [
832
+ "COMPACT",
833
+ "COMPRESS",
834
+ "SUMMARIZE_SESSION"
835
+ ],
836
+ description: "Summarize conversation history and set a compaction point.",
837
+ validate: async () => true,
838
+ handler: async (runtime, message, _state, _options, callback) => {
839
+ const { roomId } = message;
840
+ const instructions = (message.content?.text ?? "").replace(/^\/?compact\s*/i, "").trim() || void 0;
841
+ try {
842
+ const summary = await summarize(runtime, roomId, instructions);
843
+ const now = Date.now();
844
+ await runtime.createMemory({
845
+ id: crypto.randomUUID(),
846
+ entityId: runtime.agentId,
847
+ roomId,
848
+ content: {
849
+ text: `[Compaction Summary]\n\n${summary}`,
850
+ source: "compaction"
851
+ },
852
+ createdAt: now,
853
+ metadata: { type: MemoryType.CUSTOM }
854
+ }, "messages");
855
+ const room = await runtime.getRoom(roomId);
856
+ if (room) {
857
+ const prev = Array.isArray(room.metadata?.compactionHistory) ? room.metadata.compactionHistory : [];
858
+ const entry = {
859
+ timestamp: now,
860
+ triggeredBy: message.entityId
861
+ };
862
+ const compactionHistory = [...prev, entry].slice(-10);
863
+ await runtime.updateRoom({
864
+ ...room,
865
+ metadata: {
866
+ ...room.metadata,
867
+ lastCompactionAt: now,
868
+ compactionHistory
869
+ }
870
+ });
871
+ }
872
+ if (callback) await callback({ text: "Session compacted." });
873
+ return {
874
+ success: true,
875
+ data: { compactedAt: now }
876
+ };
877
+ } catch (err) {
878
+ const msg = err instanceof Error ? err.message : String(err);
879
+ if (callback) await callback({ text: `Compaction failed: ${msg}` });
880
+ return {
881
+ success: false,
882
+ error: msg
883
+ };
884
+ }
885
+ },
886
+ examples: [[{
887
+ name: "{{name1}}",
888
+ content: { text: "/compact" }
889
+ }, {
890
+ name: "{{agentName}}",
891
+ content: {
892
+ text: "Session compacted.",
893
+ actions: ["COMPACT_SESSION"]
894
+ }
895
+ }], [{
896
+ name: "{{name1}}",
897
+ content: { text: "/compact Focus on decisions" }
898
+ }, {
899
+ name: "{{agentName}}",
900
+ content: {
901
+ text: "Session compacted.",
902
+ actions: ["COMPACT_SESSION"]
903
+ }
904
+ }]]
905
+ };
906
+
907
+ //#endregion
908
+ //#region src/agents/tools/memory-tools.ts
909
+ /**
910
+ * memory_search and memory_get actions for workspace memory files.
911
+ */
912
+ const SUPPORTED_EXT = new Set([
913
+ ".md",
914
+ ".txt",
915
+ ".json"
916
+ ]);
917
+ function resolveMemoryDir(workspacePath) {
918
+ return join(workspacePath?.replace(/^~/, homedir()) ?? join(homedir(), ".milaidy", "workspace"), "memory");
919
+ }
920
+ function score(content, query) {
921
+ const lower = content.toLowerCase();
922
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
923
+ if (terms.length === 0) return 0;
924
+ let matched = 0;
925
+ for (const t of terms) if (lower.includes(t)) matched++;
926
+ return matched / terms.length;
927
+ }
928
+ const memorySearchAction = {
929
+ name: "memory_search",
930
+ description: "Search workspace memory files for relevant context.",
931
+ similes: ["MEMORY_SEARCH", "SEARCH_MEMORY"],
932
+ examples: [],
933
+ validate: async (_runtime, message) => {
934
+ const text = message.content?.text;
935
+ return typeof text === "string" && text.length > 0;
936
+ },
937
+ handler: async (_runtime, message, _state, options, callback) => {
938
+ const params = options ?? {};
939
+ const query = params.query ?? message.content?.text ?? "";
940
+ const limit = Math.min(Number(params.limit ?? 10), 50);
941
+ const memDir = resolveMemoryDir(params.workspacePath);
942
+ let files;
943
+ try {
944
+ files = await readdir(memDir);
945
+ } catch {
946
+ const msg = `Memory directory not found: ${memDir}`;
947
+ if (callback) await callback({ text: msg });
948
+ return {
949
+ success: false,
950
+ error: msg
951
+ };
952
+ }
953
+ const results = [];
954
+ for (const file of files) {
955
+ if (!SUPPORTED_EXT.has(extname(file))) continue;
956
+ const filePath = join(memDir, file);
957
+ let content;
958
+ try {
959
+ content = await readFile(filePath, "utf-8");
960
+ } catch {
961
+ continue;
962
+ }
963
+ const relevance = score(content, query);
964
+ if (relevance > 0) {
965
+ const snippet = content.slice(0, 200).replace(/\n/g, " ").trim();
966
+ results.push({
967
+ name: basename(file, extname(file)),
968
+ path: filePath,
969
+ relevance,
970
+ snippet: snippet.length < content.length ? `${snippet}...` : snippet
971
+ });
972
+ }
973
+ }
974
+ results.sort((a, b) => b.relevance - a.relevance);
975
+ const top = results.slice(0, limit);
976
+ if (top.length === 0) {
977
+ const msg = `No memory entries found matching "${query}"`;
978
+ if (callback) await callback({ text: msg });
979
+ return {
980
+ success: true,
981
+ text: msg
982
+ };
983
+ }
984
+ const output = top.map((r, i) => `${i + 1}. **${r.name}** (${(r.relevance * 100).toFixed(0)}%)\n ${r.snippet}`).join("\n\n");
985
+ const text = `Found ${top.length} memory entries:\n\n${output}`;
986
+ if (callback) await callback({ text });
987
+ return {
988
+ success: true,
989
+ text,
990
+ data: { results: top }
991
+ };
992
+ }
993
+ };
994
+ const memoryGetAction = {
995
+ name: "memory_get",
996
+ description: "Retrieve a specific memory file by name.",
997
+ similes: [
998
+ "MEMORY_GET",
999
+ "GET_MEMORY",
1000
+ "READ_MEMORY"
1001
+ ],
1002
+ examples: [],
1003
+ validate: async (_runtime, message) => {
1004
+ const text = message.content?.text;
1005
+ return typeof text === "string" && text.length > 0;
1006
+ },
1007
+ handler: async (_runtime, message, _state, options, callback) => {
1008
+ const params = options ?? {};
1009
+ const name = params.name ?? message.content?.text ?? "";
1010
+ const memDir = resolveMemoryDir(params.workspacePath);
1011
+ const candidates = [
1012
+ name,
1013
+ `${name}.md`,
1014
+ `${name}.txt`,
1015
+ `${name}.json`
1016
+ ];
1017
+ for (const candidate of candidates) {
1018
+ const filePath = candidate.startsWith("/") ? candidate : join(memDir, candidate);
1019
+ let content;
1020
+ try {
1021
+ content = await readFile(filePath, "utf-8");
1022
+ } catch {
1023
+ continue;
1024
+ }
1025
+ const text = `# Memory: ${basename(filePath)}\n\n${content}`;
1026
+ if (callback) await callback({ text });
1027
+ return {
1028
+ success: true,
1029
+ text,
1030
+ data: {
1031
+ name: basename(filePath),
1032
+ content,
1033
+ path: filePath
1034
+ }
1035
+ };
1036
+ }
1037
+ let hint = "";
1038
+ try {
1039
+ const available = (await readdir(memDir)).filter((f) => SUPPORTED_EXT.has(extname(f)));
1040
+ if (available.length > 0) hint = `\n\nAvailable: ${available.slice(0, 10).join(", ")}`;
1041
+ } catch {}
1042
+ const msg = `Memory not found: "${name}"${hint}`;
1043
+ if (callback) await callback({ text: msg });
1044
+ return {
1045
+ success: false,
1046
+ error: msg,
1047
+ text: msg
1048
+ };
1049
+ }
1050
+ };
1051
+
1052
+ //#endregion
1053
+ //#region src/milaidy-plugin.ts
1054
+ function createMilaidyPlugin(config) {
1055
+ const workspaceDir = config?.workspaceDir ?? DEFAULT_AGENT_WORKSPACE_DIR;
1056
+ const agentId = config?.agentId ?? "main";
1057
+ const sessionStorePath = config?.sessionStorePath ?? resolveDefaultSessionStorePath(agentId);
1058
+ return {
1059
+ name: "milaidy",
1060
+ description: "Milaidy workspace context, session keys, and compaction",
1061
+ providers: [
1062
+ createWorkspaceProvider({
1063
+ workspaceDir,
1064
+ maxCharsPerFile: config?.bootstrapMaxChars
1065
+ }),
1066
+ createSessionKeyProvider({ defaultAgentId: agentId }),
1067
+ ...getSessionProviders({ storePath: sessionStorePath })
1068
+ ],
1069
+ actions: [
1070
+ compactAction,
1071
+ memorySearchAction,
1072
+ memoryGetAction
1073
+ ],
1074
+ events: { MESSAGE_RECEIVED: [async (payload) => {
1075
+ const { runtime, message } = payload;
1076
+ if (!message || !runtime) return;
1077
+ const meta = message.metadata ?? {};
1078
+ if (meta.sessionKey) return;
1079
+ const room = await runtime.getRoom(message.roomId);
1080
+ if (!room) return;
1081
+ const key = resolveSessionKeyFromRoom(agentId, room, {
1082
+ threadId: meta.threadId,
1083
+ groupId: meta.groupId,
1084
+ channel: meta.channel ?? room.source
1085
+ });
1086
+ message.metadata.sessionKey = key;
1087
+ }] }
1088
+ };
1089
+ }
1090
+ const milaidyPlugin = createMilaidyPlugin();
1091
+
1092
+ //#endregion
1093
+ //#region src/eliza.ts
1094
+ /**
1095
+ * ElizaOS runtime entry point for Milaidy.
1096
+ *
1097
+ * Starts the ElizaOS agent runtime with Milaidy's plugin configuration.
1098
+ * Can be run directly via: node --import tsx src/eliza.ts
1099
+ * Or via the CLI: milaidy start
1100
+ *
1101
+ * @module eliza
1102
+ */
1103
+ /**
1104
+ * Maps Milaidy channel config fields to the environment variable names
1105
+ * that ElizaOS plugins expect.
1106
+ *
1107
+ * Milaidy stores channel credentials under `config.channels.<name>.<field>`,
1108
+ * while ElizaOS plugins read them from process.env.
1109
+ */
1110
+ const CHANNEL_ENV_MAP = {
1111
+ discord: { token: "DISCORD_BOT_TOKEN" },
1112
+ telegram: { botToken: "TELEGRAM_BOT_TOKEN" },
1113
+ slack: {
1114
+ botToken: "SLACK_BOT_TOKEN",
1115
+ appToken: "SLACK_APP_TOKEN",
1116
+ userToken: "SLACK_USER_TOKEN"
1117
+ },
1118
+ signal: { account: "SIGNAL_ACCOUNT" },
1119
+ msteams: {
1120
+ appId: "MSTEAMS_APP_ID",
1121
+ appPassword: "MSTEAMS_APP_PASSWORD"
1122
+ },
1123
+ mattermost: {
1124
+ botToken: "MATTERMOST_BOT_TOKEN",
1125
+ baseUrl: "MATTERMOST_BASE_URL"
1126
+ },
1127
+ googlechat: { serviceAccountKey: "GOOGLE_CHAT_SERVICE_ACCOUNT_KEY" }
1128
+ };
1129
+ /** Core plugins that should always be loaded. */
1130
+ const CORE_PLUGINS = [
1131
+ "@elizaos/plugin-sql",
1132
+ "@elizaos/plugin-agent-skills",
1133
+ "@elizaos/plugin-directives",
1134
+ "@elizaos/plugin-commands",
1135
+ "@elizaos/plugin-shell",
1136
+ "@elizaos/plugin-personality",
1137
+ "@elizaos/plugin-experience",
1138
+ "@elizaos/plugin-form"
1139
+ ];
1140
+ /** Maps Milaidy channel names to ElizaOS plugin package names. */
1141
+ const CHANNEL_PLUGIN_MAP = {
1142
+ discord: "@elizaos/plugin-discord",
1143
+ telegram: "@elizaos/plugin-telegram",
1144
+ slack: "@elizaos/plugin-slack",
1145
+ whatsapp: "@elizaos/plugin-whatsapp",
1146
+ signal: "@elizaos/plugin-signal",
1147
+ imessage: "@elizaos/plugin-imessage",
1148
+ bluebubbles: "@elizaos/plugin-bluebubbles",
1149
+ msteams: "@elizaos/plugin-msteams",
1150
+ mattermost: "@elizaos/plugin-mattermost",
1151
+ googlechat: "@elizaos/plugin-google-chat"
1152
+ };
1153
+ /** Maps environment variable names to model-provider plugin packages. */
1154
+ const PROVIDER_PLUGIN_MAP = {
1155
+ ANTHROPIC_API_KEY: "@elizaos/plugin-anthropic",
1156
+ OPENAI_API_KEY: "@elizaos/plugin-openai",
1157
+ GOOGLE_API_KEY: "@elizaos/plugin-google-genai",
1158
+ GOOGLE_GENERATIVE_AI_API_KEY: "@elizaos/plugin-google-genai",
1159
+ GROQ_API_KEY: "@elizaos/plugin-groq",
1160
+ XAI_API_KEY: "@elizaos/plugin-xai",
1161
+ OPENROUTER_API_KEY: "@elizaos/plugin-openrouter",
1162
+ OLLAMA_BASE_URL: "@elizaos/plugin-ollama"
1163
+ };
1164
+ /** Optional feature plugins keyed by feature name. */
1165
+ const OPTIONAL_PLUGIN_MAP = {};
1166
+ function looksLikePlugin(value) {
1167
+ if (!value || typeof value !== "object") return false;
1168
+ const obj = value;
1169
+ return typeof obj.name === "string" && typeof obj.description === "string";
1170
+ }
1171
+ function extractPlugin(mod) {
1172
+ if (looksLikePlugin(mod.default)) return mod.default;
1173
+ if (looksLikePlugin(mod.plugin)) return mod.plugin;
1174
+ if (looksLikePlugin(mod)) return mod;
1175
+ return null;
1176
+ }
1177
+ /**
1178
+ * Collect the set of plugin package names that should be loaded
1179
+ * based on config, environment variables, and feature flags.
1180
+ */
1181
+ function collectPluginNames(config) {
1182
+ const pluginsToLoad = new Set(CORE_PLUGINS);
1183
+ const channels = config.channels ?? {};
1184
+ for (const [channelName, channelConfig] of Object.entries(channels)) if (channelConfig && typeof channelConfig === "object") {
1185
+ const pluginName = CHANNEL_PLUGIN_MAP[channelName];
1186
+ if (pluginName) pluginsToLoad.add(pluginName);
1187
+ }
1188
+ for (const [envKey, pluginName] of Object.entries(PROVIDER_PLUGIN_MAP)) if (process$1.env[envKey]) pluginsToLoad.add(pluginName);
1189
+ const pluginsConfig = config.plugins;
1190
+ if (pluginsConfig?.entries) {
1191
+ for (const [key, entry] of Object.entries(pluginsConfig.entries)) if (entry && typeof entry === "object" && entry.enabled !== false) {
1192
+ const pluginName = OPTIONAL_PLUGIN_MAP[key];
1193
+ if (pluginName) pluginsToLoad.add(pluginName);
1194
+ }
1195
+ }
1196
+ const features = config.features;
1197
+ if (features && typeof features === "object") {
1198
+ for (const [featureName, featureValue] of Object.entries(features)) if (featureValue === true || typeof featureValue === "object" && featureValue !== null && featureValue.enabled !== false) {
1199
+ const pluginName = OPTIONAL_PLUGIN_MAP[featureName];
1200
+ if (pluginName) pluginsToLoad.add(pluginName);
1201
+ }
1202
+ }
1203
+ return pluginsToLoad;
1204
+ }
1205
+ /**
1206
+ * Resolve Milaidy plugins from config and auto-enable logic.
1207
+ * Returns an array of ElizaOS Plugin instances ready for AgentRuntime.
1208
+ */
1209
+ async function resolvePlugins(config) {
1210
+ const plugins = [];
1211
+ const autoEnableResult = applyPluginAutoEnable({
1212
+ config,
1213
+ env: process$1.env
1214
+ });
1215
+ for (const change of autoEnableResult.changes) logger.info(`[milaidy] ${change}`);
1216
+ const pluginsToLoad = collectPluginNames(config);
1217
+ for (const pluginName of pluginsToLoad) try {
1218
+ const pluginInstance = extractPlugin(await import(pluginName));
1219
+ if (pluginInstance) {
1220
+ plugins.push({
1221
+ name: pluginName,
1222
+ plugin: pluginInstance
1223
+ });
1224
+ logger.info(`[milaidy] Loaded plugin: ${pluginName}`);
1225
+ } else logger.warn(`[milaidy] Plugin ${pluginName} did not export a valid Plugin object`);
1226
+ } catch (err) {
1227
+ const msg = err instanceof Error ? err.message : String(err);
1228
+ logger.warn(`[milaidy] Could not load plugin ${pluginName}: ${msg}`);
1229
+ }
1230
+ return plugins;
1231
+ }
1232
+ /**
1233
+ * Propagate channel credentials from Milaidy config into process.env so
1234
+ * that ElizaOS plugins can find them.
1235
+ */
1236
+ function applyChannelSecretsToEnv(config) {
1237
+ const channels = config.channels ?? {};
1238
+ for (const [channelName, channelConfig] of Object.entries(channels)) {
1239
+ if (!channelConfig || typeof channelConfig !== "object") continue;
1240
+ const envMap = CHANNEL_ENV_MAP[channelName];
1241
+ if (!envMap) continue;
1242
+ const configObj = channelConfig;
1243
+ for (const [configField, envKey] of Object.entries(envMap)) {
1244
+ const value = configObj[configField];
1245
+ if (typeof value === "string" && value.trim() && !process$1.env[envKey]) process$1.env[envKey] = value;
1246
+ }
1247
+ }
1248
+ }
1249
+ /**
1250
+ * Build an ElizaOS Character from the Milaidy config.
1251
+ *
1252
+ * Merges the deprecated `config.agent` object and the newer
1253
+ * `config.agents.defaults` into a single Character, collecting
1254
+ * secrets from environment variables along the way.
1255
+ */
1256
+ function buildCharacterFromConfig(config) {
1257
+ const legacyAgent = config.agent;
1258
+ const name = legacyAgent?.name ?? config.ui?.assistant?.name ?? "Milaidy";
1259
+ const bio = legacyAgent?.bio ?? "An AI assistant powered by Milaidy and ElizaOS.";
1260
+ const systemPrompt = legacyAgent?.system_prompt;
1261
+ const secretKeys = [
1262
+ "ANTHROPIC_API_KEY",
1263
+ "OPENAI_API_KEY",
1264
+ "GOOGLE_API_KEY",
1265
+ "GOOGLE_GENERATIVE_AI_API_KEY",
1266
+ "GROQ_API_KEY",
1267
+ "XAI_API_KEY",
1268
+ "OPENROUTER_API_KEY",
1269
+ "OLLAMA_BASE_URL",
1270
+ "DISCORD_BOT_TOKEN",
1271
+ "TELEGRAM_BOT_TOKEN",
1272
+ "SLACK_BOT_TOKEN",
1273
+ "SLACK_APP_TOKEN",
1274
+ "SLACK_USER_TOKEN",
1275
+ "SIGNAL_ACCOUNT",
1276
+ "MSTEAMS_APP_ID",
1277
+ "MSTEAMS_APP_PASSWORD",
1278
+ "MATTERMOST_BOT_TOKEN",
1279
+ "MATTERMOST_BASE_URL"
1280
+ ];
1281
+ const secrets = {};
1282
+ for (const key of secretKeys) {
1283
+ const value = process$1.env[key];
1284
+ if (value && value.trim()) secrets[key] = value;
1285
+ }
1286
+ return createCharacter({
1287
+ name,
1288
+ bio,
1289
+ system: systemPrompt,
1290
+ secrets
1291
+ });
1292
+ }
1293
+ /**
1294
+ * Resolve the primary model identifier from Milaidy config.
1295
+ *
1296
+ * Milaidy stores the model under `agents.defaults.model.primary` as an
1297
+ * AgentModelListConfig object. Returns undefined when no model is
1298
+ * explicitly configured (ElizaOS falls back to whichever model
1299
+ * plugin is loaded).
1300
+ */
1301
+ function resolvePrimaryModel(config) {
1302
+ const modelConfig = config.agents?.defaults?.model;
1303
+ if (!modelConfig) return void 0;
1304
+ return modelConfig.primary;
1305
+ }
1306
+ /**
1307
+ * Detect whether this is the first run (no agent name configured)
1308
+ * and prompt the user to pick a name for their agent.
1309
+ *
1310
+ * Saves the chosen name, a default bio, and a basic system prompt
1311
+ * back to the Milaidy config so subsequent runs skip this step.
1312
+ */
1313
+ async function runFirstTimeSetup(config) {
1314
+ if (Boolean(config.agent?.name || config.ui?.assistant?.name)) return config;
1315
+ if (!process$1.stdin.isTTY) return config;
1316
+ const rl = readline.createInterface({
1317
+ input: process$1.stdin,
1318
+ output: process$1.stdout
1319
+ });
1320
+ const ask = (question) => new Promise((resolve) => {
1321
+ rl.question(question, (answer) => resolve(answer));
1322
+ });
1323
+ console.log("");
1324
+ const answer = await ask(" What should your agent be called? (Milaidy) ");
1325
+ rl.close();
1326
+ const name = answer.trim() || "Milaidy";
1327
+ const updated = {
1328
+ ...config,
1329
+ agent: {
1330
+ ...config.agent,
1331
+ name,
1332
+ bio: `An autonomous agent`,
1333
+ system_prompt: `You are ${name}, an autonomous AI agent powered by ElizaOS. You are helpful, concise, and proactive.`
1334
+ }
1335
+ };
1336
+ saveMilaidyConfig(updated);
1337
+ console.log(` Agent "${name}" created.\n`);
1338
+ return updated;
1339
+ }
1340
+ /**
1341
+ * Start the ElizaOS runtime with Milaidy's configuration.
1342
+ */
1343
+ async function startEliza() {
1344
+ logger.info(`Milaidy v${VERSION} — starting ElizaOS runtime`);
1345
+ let config;
1346
+ try {
1347
+ config = await loadMilaidyConfig();
1348
+ } catch {
1349
+ logger.warn("[milaidy] No config found, using defaults");
1350
+ config = {};
1351
+ }
1352
+ config = await runFirstTimeSetup(config);
1353
+ applyChannelSecretsToEnv(config);
1354
+ const character = buildCharacterFromConfig(config);
1355
+ logger.info(`[milaidy] Agent character: ${character.name ?? "Milaidy"}`);
1356
+ const primaryModel = resolvePrimaryModel(config);
1357
+ if (primaryModel) logger.info(`[milaidy] Primary model: ${primaryModel}`);
1358
+ const workspaceDir = config.agents?.defaults?.workspace ?? resolveDefaultAgentWorkspaceDir();
1359
+ logger.info(`[milaidy] Agent workspace: ${workspaceDir}`);
1360
+ await ensureAgentWorkspace({
1361
+ dir: workspaceDir,
1362
+ ensureBootstrapFiles: true
1363
+ });
1364
+ const agentId = character.name?.toLowerCase().replace(/\s+/g, "-") ?? "main";
1365
+ const milaidyPlugin = createMilaidyPlugin({
1366
+ workspaceDir,
1367
+ bootstrapMaxChars: config.agents?.defaults?.bootstrapMaxChars,
1368
+ agentId
1369
+ });
1370
+ const resolvedPlugins = await resolvePlugins(config);
1371
+ logger.info(`[milaidy] Resolved ${resolvedPlugins.length} plugins`);
1372
+ logger.info(`[milaidy] Plugins: ${resolvedPlugins.map((p) => p.name.replace("@elizaos/", "")).join(", ")}`);
1373
+ if (resolvedPlugins.length === 0) {
1374
+ logger.error("[milaidy] No plugins loaded — at least one model provider plugin is required");
1375
+ logger.error("[milaidy] Set an API key (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY) in your environment");
1376
+ throw new Error("No plugins loaded");
1377
+ }
1378
+ const sqlPlugin = resolvedPlugins.find((p) => p.name === "@elizaos/plugin-sql");
1379
+ const runtime = new AgentRuntime({
1380
+ character,
1381
+ plugins: [milaidyPlugin, ...resolvedPlugins.filter((p) => p.name !== "@elizaos/plugin-sql").map((p) => p.plugin)],
1382
+ settings: { ...primaryModel ? { MODEL_PROVIDER: primaryModel } : {} }
1383
+ });
1384
+ if (sqlPlugin) await runtime.registerPlugin(sqlPlugin.plugin);
1385
+ logger.info("[milaidy] Initializing AgentRuntime...");
1386
+ await runtime.initialize();
1387
+ logger.info("[milaidy] AgentRuntime initialized successfully");
1388
+ let isShuttingDown = false;
1389
+ const shutdown = async () => {
1390
+ if (isShuttingDown) return;
1391
+ isShuttingDown = true;
1392
+ logger.info("[milaidy] Shutting down...");
1393
+ try {
1394
+ await runtime.stop();
1395
+ } catch (err) {
1396
+ const msg = err instanceof Error ? err.message : String(err);
1397
+ logger.warn(`[milaidy] Error during shutdown: ${msg}`);
1398
+ }
1399
+ process$1.exit(0);
1400
+ };
1401
+ process$1.on("SIGINT", () => void shutdown());
1402
+ process$1.on("SIGTERM", () => void shutdown());
1403
+ try {
1404
+ const internalHooksConfig = config.hooks?.internal;
1405
+ const hooksResult = await loadHooks({
1406
+ workspacePath: workspaceDir,
1407
+ internalConfig: internalHooksConfig,
1408
+ milaidyConfig: config
1409
+ });
1410
+ if (hooksResult.registered > 0) logger.info(`[milaidy] Hooks: ${hooksResult.registered} registered`);
1411
+ await triggerHook(createHookEvent("gateway", "startup", "system", { cfg: config }));
1412
+ } catch (err) {
1413
+ const msg = err instanceof Error ? err.message : String(err);
1414
+ logger.warn(`[milaidy] Hooks system could not load: ${msg}`);
1415
+ }
1416
+ logger.info("[milaidy] Runtime is ready.");
1417
+ const agentName = character.name ?? "Milaidy";
1418
+ const userId = crypto.randomUUID();
1419
+ const roomId = stringToUuid(`${agentName}-chat-room`);
1420
+ const worldId = stringToUuid(`${agentName}-chat-world`);
1421
+ try {
1422
+ await runtime.ensureConnection({
1423
+ entityId: userId,
1424
+ roomId,
1425
+ worldId,
1426
+ userName: "User",
1427
+ source: "cli",
1428
+ channelId: `${agentName}-chat`,
1429
+ type: ChannelType.DM
1430
+ });
1431
+ } catch (err) {
1432
+ const msg = err instanceof Error ? err.message : String(err);
1433
+ logger.warn(`[milaidy] Could not establish chat room, retrying with fresh IDs: ${msg}`);
1434
+ const freshRoomId = crypto.randomUUID();
1435
+ const freshWorldId = crypto.randomUUID();
1436
+ await runtime.ensureConnection({
1437
+ entityId: userId,
1438
+ roomId: freshRoomId,
1439
+ worldId: freshWorldId,
1440
+ userName: "User",
1441
+ source: "cli",
1442
+ channelId: `${agentName}-chat`,
1443
+ type: ChannelType.DM
1444
+ });
1445
+ }
1446
+ const rl = readline.createInterface({
1447
+ input: process$1.stdin,
1448
+ output: process$1.stdout
1449
+ });
1450
+ console.log(`\nšŸ’¬ Chat with ${agentName} (type 'exit' to quit)\n`);
1451
+ const prompt = () => {
1452
+ rl.question("You: ", async (input) => {
1453
+ const text = input.trim();
1454
+ if (text.toLowerCase() === "exit" || text.toLowerCase() === "quit") {
1455
+ console.log("\nGoodbye!");
1456
+ rl.close();
1457
+ await runtime.stop();
1458
+ process$1.exit(0);
1459
+ }
1460
+ if (!text) {
1461
+ prompt();
1462
+ return;
1463
+ }
1464
+ const message = createMessageMemory({
1465
+ id: crypto.randomUUID(),
1466
+ entityId: userId,
1467
+ roomId,
1468
+ content: {
1469
+ text,
1470
+ source: "client_chat",
1471
+ channelType: ChannelType.DM
1472
+ }
1473
+ });
1474
+ process$1.stdout.write(`${agentName}: `);
1475
+ await runtime?.messageService?.handleMessage(runtime, message, async (content) => {
1476
+ if (content?.text) process$1.stdout.write(content.text);
1477
+ return [];
1478
+ });
1479
+ console.log("\n");
1480
+ prompt();
1481
+ });
1482
+ };
1483
+ prompt();
1484
+ }
1485
+ if (import.meta.url === `file://${process$1.argv[1]}` || process$1.argv[1]?.endsWith("/eliza.ts") || process$1.argv[1]?.endsWith("/eliza.js")) startEliza().catch((err) => {
1486
+ console.error("[milaidy] Fatal error:", err instanceof Error ? err.stack ?? err.message : err);
1487
+ process$1.exit(1);
1488
+ });
1489
+
1490
+ //#endregion
1491
+ export { startEliza };