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
package/dist/eliza.js ADDED
@@ -0,0 +1,2192 @@
1
+ import { createRequire } from "node:module";
2
+ import crypto from "node:crypto";
3
+ import process$1 from "node:process";
4
+ import * as readline from "node:readline";
5
+ import { AgentRuntime, ChannelType, MemoryType, ModelType, buildAgentMainSessionKey, createCharacter, createMessageMemory, getSessionProviders, isSubagentSessionKey, logger, parseAgentSessionKey, resolveDefaultSessionStorePath, stringToUuid } from "@elizaos/core";
6
+ import fs, { existsSync } from "node:fs";
7
+ import path, { basename, extname, join, resolve } from "node:path";
8
+ import JSON5 from "json5";
9
+ import os, { homedir, platform } from "node:os";
10
+ import fs$1, { readFile, readdir, stat } from "node:fs/promises";
11
+ import { fileURLToPath, pathToFileURL } from "node:url";
12
+ import { spawn } from "node:child_process";
13
+ import chalk, { Chalk } from "chalk";
14
+
15
+ //#region src/version.ts
16
+ function readVersionFromPackageJson() {
17
+ try {
18
+ return createRequire(import.meta.url)("../package.json").version ?? null;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ function readVersionFromBuildInfo() {
24
+ try {
25
+ const require = createRequire(import.meta.url);
26
+ for (const candidate of ["../build-info.json", "./build-info.json"]) try {
27
+ const info = require(candidate);
28
+ if (info.version) return info.version;
29
+ } catch {}
30
+ return null;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ const VERSION = typeof __MILAIDY_VERSION__ === "string" && __MILAIDY_VERSION__ || process.env.MILAIDY_BUNDLED_VERSION || readVersionFromPackageJson() || readVersionFromBuildInfo() || "0.0.0";
36
+
37
+ //#endregion
38
+ //#region src/config/plugin-auto-enable.ts
39
+ /**
40
+ * Channel to plugin mappings.
41
+ */
42
+ const CHANNEL_PLUGINS = {
43
+ telegram: "@elizaos/plugin-telegram",
44
+ discord: "@elizaos/plugin-discord",
45
+ slack: "@elizaos/plugin-slack",
46
+ twitter: "@elizaos/plugin-twitter",
47
+ whatsapp: "@elizaos/plugin-whatsapp",
48
+ signal: "@elizaos/plugin-signal",
49
+ bluebubbles: "@elizaos/plugin-bluebubbles",
50
+ imessage: "@elizaos/plugin-imessage",
51
+ farcaster: "@elizaos/plugin-farcaster",
52
+ lens: "@elizaos/plugin-lens",
53
+ msteams: "@elizaos/plugin-msteams",
54
+ mattermost: "@elizaos/plugin-mattermost",
55
+ googlechat: "@elizaos/plugin-google-chat",
56
+ feishu: "@elizaos/plugin-feishu",
57
+ matrix: "@elizaos/plugin-matrix",
58
+ nostr: "@elizaos/plugin-nostr"
59
+ };
60
+ /**
61
+ * Provider to plugin mappings.
62
+ */
63
+ const PROVIDER_PLUGINS = {
64
+ "google-antigravity": "@elizaos/plugin-google-antigravity",
65
+ "google-gemini": "@elizaos/plugin-google-gemini",
66
+ openai: "@elizaos/plugin-openai",
67
+ anthropic: "@elizaos/plugin-anthropic",
68
+ qwen: "@elizaos/plugin-qwen",
69
+ minimax: "@elizaos/plugin-minimax",
70
+ groq: "@elizaos/plugin-groq",
71
+ xai: "@elizaos/plugin-xai",
72
+ openrouter: "@elizaos/plugin-openrouter",
73
+ ollama: "@elizaos/plugin-ollama",
74
+ deepseek: "@elizaos/plugin-deepseek",
75
+ together: "@elizaos/plugin-together",
76
+ mistral: "@elizaos/plugin-mistral",
77
+ cohere: "@elizaos/plugin-cohere",
78
+ perplexity: "@elizaos/plugin-perplexity"
79
+ };
80
+ /**
81
+ * Auth provider secret key to plugin mappings.
82
+ * Used to auto-enable plugins when API keys are present in environment or secrets.
83
+ */
84
+ const AUTH_PROVIDER_PLUGINS = {
85
+ ANTHROPIC_API_KEY: "@elizaos/plugin-anthropic",
86
+ CLAUDE_API_KEY: "@elizaos/plugin-anthropic",
87
+ OPENAI_API_KEY: "@elizaos/plugin-openai",
88
+ GOOGLE_API_KEY: "@elizaos/plugin-google-gemini",
89
+ GOOGLE_GENERATIVE_AI_API_KEY: "@elizaos/plugin-google-gemini",
90
+ GOOGLE_CLOUD_API_KEY: "@elizaos/plugin-google-antigravity",
91
+ GROQ_API_KEY: "@elizaos/plugin-groq",
92
+ XAI_API_KEY: "@elizaos/plugin-xai",
93
+ GROK_API_KEY: "@elizaos/plugin-xai",
94
+ OPENROUTER_API_KEY: "@elizaos/plugin-openrouter",
95
+ OLLAMA_BASE_URL: "@elizaos/plugin-ollama",
96
+ DEEPSEEK_API_KEY: "@elizaos/plugin-deepseek",
97
+ TOGETHER_API_KEY: "@elizaos/plugin-together",
98
+ MISTRAL_API_KEY: "@elizaos/plugin-mistral",
99
+ COHERE_API_KEY: "@elizaos/plugin-cohere",
100
+ PERPLEXITY_API_KEY: "@elizaos/plugin-perplexity"
101
+ };
102
+ /**
103
+ * Feature to plugin mappings for optional features.
104
+ */
105
+ const FEATURE_PLUGINS = {
106
+ browser: "@elizaos/plugin-browser",
107
+ cron: "@elizaos/plugin-cron",
108
+ shell: "@elizaos/plugin-shell",
109
+ imageGen: "@elizaos/plugin-image-generation",
110
+ tts: "@elizaos/plugin-tts",
111
+ stt: "@elizaos/plugin-stt",
112
+ agentSkills: "@elizaos/plugin-agent-skills",
113
+ directives: "@elizaos/plugin-directives",
114
+ commands: "@elizaos/plugin-commands",
115
+ diagnosticsOtel: "@elizaos/plugin-diagnostics-otel",
116
+ webhooks: "@elizaos/plugin-webhooks",
117
+ gmailWatch: "@elizaos/plugin-gmail-watch",
118
+ personality: "@elizaos/plugin-personality",
119
+ experience: "@elizaos/plugin-experience",
120
+ form: "@elizaos/plugin-form"
121
+ };
122
+ /**
123
+ * Check if a channel is configured with credentials.
124
+ */
125
+ function isChannelConfigured(channelName, channelConfig) {
126
+ if (!channelConfig || typeof channelConfig !== "object") return false;
127
+ const config = channelConfig;
128
+ if (config.botToken || config.token || config.apiKey) return true;
129
+ switch (channelName) {
130
+ case "bluebubbles": return Boolean(config.serverUrl && config.password);
131
+ case "imessage": return Boolean(config.cliPath);
132
+ case "whatsapp": return Boolean(config.authState || config.sessionPath);
133
+ default: return Object.keys(config).length > 0;
134
+ }
135
+ }
136
+ /**
137
+ * Apply plugin auto-enable based on configuration.
138
+ *
139
+ * This function analyzes the configuration and automatically enables
140
+ * plugins that are required based on configured channels and providers.
141
+ *
142
+ * @param params - Parameters
143
+ * @returns Result with updated config and changes
144
+ */
145
+ function applyPluginAutoEnable(params) {
146
+ const { config, env } = params;
147
+ const changes = [];
148
+ const updatedConfig = JSON.parse(JSON.stringify(config));
149
+ if (updatedConfig.plugins && typeof updatedConfig.plugins === "object" && updatedConfig.plugins.enabled === false) return {
150
+ config: updatedConfig,
151
+ changes
152
+ };
153
+ updatedConfig.plugins = updatedConfig.plugins ?? {};
154
+ const pluginsConfig = updatedConfig.plugins;
155
+ pluginsConfig.allow = pluginsConfig.allow ?? [];
156
+ pluginsConfig.entries = pluginsConfig.entries ?? {};
157
+ if (updatedConfig.channels && typeof updatedConfig.channels === "object") for (const [channelName, channelConfig] of Object.entries(updatedConfig.channels)) {
158
+ const pluginName = CHANNEL_PLUGINS[channelName];
159
+ if (!pluginName) continue;
160
+ if (!isChannelConfigured(channelName, channelConfig)) continue;
161
+ const entryConfig = pluginsConfig.entries[channelName];
162
+ if (entryConfig && entryConfig.enabled === false) continue;
163
+ if (!pluginsConfig.allow.includes(pluginName) && !pluginsConfig.allow.includes(channelName)) {
164
+ pluginsConfig.allow.push(channelName);
165
+ changes.push(`Auto-enabled plugin: ${pluginName} (channel: ${channelName})`);
166
+ }
167
+ }
168
+ if (updatedConfig.auth && typeof updatedConfig.auth === "object" && updatedConfig.auth.profiles) {
169
+ const profiles = updatedConfig.auth.profiles;
170
+ for (const [profileKey, profile] of Object.entries(profiles)) {
171
+ const provider = profile.provider;
172
+ if (!provider) continue;
173
+ const pluginName = PROVIDER_PLUGINS[provider];
174
+ if (!pluginName) continue;
175
+ if (!pluginsConfig.allow.includes(pluginName) && !pluginsConfig.allow.includes(provider)) {
176
+ pluginsConfig.allow.push(provider);
177
+ changes.push(`Auto-enabled plugin: ${pluginName} (auth profile: ${profileKey})`);
178
+ }
179
+ }
180
+ }
181
+ for (const [envKey, pluginName] of Object.entries(AUTH_PROVIDER_PLUGINS)) {
182
+ const envValue = env[envKey];
183
+ if (!envValue || typeof envValue !== "string" || envValue.trim() === "") continue;
184
+ const pluginId = pluginName.replace("@elizaos/plugin-", "");
185
+ const entryConfig = pluginsConfig.entries[pluginId];
186
+ if (entryConfig && entryConfig.enabled === false) continue;
187
+ if (!pluginsConfig.allow.includes(pluginName) && !pluginsConfig.allow.includes(pluginId)) {
188
+ pluginsConfig.allow.push(pluginId);
189
+ changes.push(`Auto-enabled plugin: ${pluginName} (env: ${envKey})`);
190
+ }
191
+ }
192
+ if (updatedConfig.features && typeof updatedConfig.features === "object") {
193
+ const features = updatedConfig.features;
194
+ for (const [featureName, featureConfig] of Object.entries(features)) {
195
+ const pluginName = FEATURE_PLUGINS[featureName];
196
+ if (!pluginName) continue;
197
+ if (!(featureConfig === true || featureConfig && typeof featureConfig === "object" && featureConfig.enabled !== false)) continue;
198
+ const pluginId = pluginName.replace("@elizaos/plugin-", "");
199
+ const entryConfig = pluginsConfig.entries[pluginId];
200
+ if (entryConfig && entryConfig.enabled === false) continue;
201
+ if (!pluginsConfig.allow.includes(pluginName) && !pluginsConfig.allow.includes(pluginId)) {
202
+ pluginsConfig.allow.push(pluginId);
203
+ changes.push(`Auto-enabled plugin: ${pluginName} (feature: ${featureName})`);
204
+ }
205
+ }
206
+ }
207
+ const hooksConfig = updatedConfig.hooks;
208
+ if (hooksConfig && hooksConfig.enabled !== false && hooksConfig.token) {
209
+ const webhooksPlugin = FEATURE_PLUGINS.webhooks;
210
+ if (webhooksPlugin) {
211
+ const pluginId = webhooksPlugin.replace("@elizaos/plugin-", "");
212
+ if (!pluginsConfig.allow.includes(webhooksPlugin) && !pluginsConfig.allow.includes(pluginId)) {
213
+ pluginsConfig.allow.push(pluginId);
214
+ changes.push(`Auto-enabled plugin: ${webhooksPlugin} (hooks.enabled + hooks.token)`);
215
+ }
216
+ }
217
+ }
218
+ if (hooksConfig) {
219
+ const gmailConfig = hooksConfig.gmail ?? {};
220
+ if (typeof gmailConfig.account === "string" && gmailConfig.account.trim()) {
221
+ const gmailPlugin = FEATURE_PLUGINS.gmailWatch;
222
+ if (gmailPlugin) {
223
+ const pluginId = gmailPlugin.replace("@elizaos/plugin-", "");
224
+ if (!pluginsConfig.allow.includes(gmailPlugin) && !pluginsConfig.allow.includes(pluginId)) {
225
+ pluginsConfig.allow.push(pluginId);
226
+ changes.push(`Auto-enabled plugin: ${gmailPlugin} (hooks.gmail.account)`);
227
+ }
228
+ }
229
+ }
230
+ }
231
+ return {
232
+ config: updatedConfig,
233
+ changes
234
+ };
235
+ }
236
+
237
+ //#endregion
238
+ //#region src/config/paths.ts
239
+ /**
240
+ * Nix mode detection: When MILAIDY_NIX_MODE=1, the gateway is running under Nix.
241
+ * In this mode:
242
+ * - No auto-install flows should be attempted
243
+ * - Missing dependencies should produce actionable Nix-specific error messages
244
+ * - Config is managed externally (read-only from Nix perspective)
245
+ */
246
+ function resolveIsNixMode(env = process.env) {
247
+ return env.MILAIDY_NIX_MODE === "1";
248
+ }
249
+ const isNixMode = resolveIsNixMode();
250
+ const STATE_DIRNAME = ".milaidy";
251
+ const CONFIG_FILENAME = "milaidy.json";
252
+ function stateDir(homedir = os.homedir) {
253
+ return path.join(homedir(), STATE_DIRNAME);
254
+ }
255
+ /**
256
+ * State directory for mutable data (sessions, logs, caches).
257
+ * Can be overridden via MILAIDY_STATE_DIR.
258
+ * Default: ~/.milaidy
259
+ */
260
+ function resolveStateDir(env = process.env, homedir = os.homedir) {
261
+ const override = env.MILAIDY_STATE_DIR?.trim();
262
+ if (override) return resolveUserPath$1(override);
263
+ return stateDir(homedir);
264
+ }
265
+ function resolveUserPath$1(input) {
266
+ const trimmed = input.trim();
267
+ if (!trimmed) return trimmed;
268
+ if (trimmed.startsWith("~")) {
269
+ const expanded = trimmed.replace(/^~(?=$|[\\/])/, os.homedir());
270
+ return path.resolve(expanded);
271
+ }
272
+ return path.resolve(trimmed);
273
+ }
274
+ const STATE_DIR = resolveStateDir();
275
+ /**
276
+ * Config file path (JSON5).
277
+ * Can be overridden via MILAIDY_CONFIG_PATH.
278
+ * Default: ~/.milaidy/milaidy.json (or $MILAIDY_STATE_DIR/milaidy.json)
279
+ */
280
+ function resolveCanonicalConfigPath(env = process.env, stateDirPath = resolveStateDir(env, os.homedir)) {
281
+ const override = env.MILAIDY_CONFIG_PATH?.trim();
282
+ if (override) return resolveUserPath$1(override);
283
+ return path.join(stateDirPath, CONFIG_FILENAME);
284
+ }
285
+ /**
286
+ * Resolve the active config path by preferring existing config candidates
287
+ * before falling back to the canonical path.
288
+ */
289
+ function resolveConfigPathCandidate(env = process.env, homedir = os.homedir) {
290
+ return resolveCanonicalConfigPath(env, resolveStateDir(env, homedir));
291
+ }
292
+ /**
293
+ * Active config path (prefers existing config files).
294
+ */
295
+ function resolveConfigPath$1(env = process.env, stateDirPath = resolveStateDir(env, os.homedir), _homedir = os.homedir) {
296
+ const override = env.MILAIDY_CONFIG_PATH?.trim();
297
+ if (override) return resolveUserPath$1(override);
298
+ return path.join(stateDirPath, CONFIG_FILENAME);
299
+ }
300
+ const CONFIG_PATH = resolveConfigPathCandidate();
301
+
302
+ //#endregion
303
+ //#region src/config/includes.ts
304
+ /**
305
+ * Config includes: $include directive for modular configs
306
+ *
307
+ * @example
308
+ * ```json5
309
+ * {
310
+ * "$include": "./base.json5", // single file
311
+ * "$include": ["./a.json5", "./b.json5"] // merge multiple
312
+ * }
313
+ * ```
314
+ */
315
+ const INCLUDE_KEY = "$include";
316
+ const MAX_INCLUDE_DEPTH = 10;
317
+ var ConfigIncludeError = class extends Error {
318
+ constructor(message, includePath, cause) {
319
+ super(message);
320
+ this.includePath = includePath;
321
+ this.cause = cause;
322
+ this.name = "ConfigIncludeError";
323
+ }
324
+ };
325
+ var CircularIncludeError = class extends ConfigIncludeError {
326
+ constructor(chain) {
327
+ super(`Circular include detected: ${chain.join(" -> ")}`, chain[chain.length - 1]);
328
+ this.chain = chain;
329
+ this.name = "CircularIncludeError";
330
+ }
331
+ };
332
+ function isPlainObject(value) {
333
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
334
+ }
335
+ /** Deep merge: arrays concatenate, objects merge recursively, primitives: source wins */
336
+ function deepMerge(target, source) {
337
+ if (Array.isArray(target) && Array.isArray(source)) return [...target, ...source];
338
+ if (isPlainObject(target) && isPlainObject(source)) {
339
+ const result = { ...target };
340
+ for (const key of Object.keys(source)) result[key] = key in result ? deepMerge(result[key], source[key]) : source[key];
341
+ return result;
342
+ }
343
+ return source;
344
+ }
345
+ var IncludeProcessor = class IncludeProcessor {
346
+ constructor(basePath, resolver) {
347
+ this.basePath = basePath;
348
+ this.resolver = resolver;
349
+ this.visited = /* @__PURE__ */ new Set();
350
+ this.depth = 0;
351
+ this.visited.add(path.normalize(basePath));
352
+ }
353
+ process(obj) {
354
+ if (Array.isArray(obj)) return obj.map((item) => this.process(item));
355
+ if (!isPlainObject(obj)) return obj;
356
+ if (!(INCLUDE_KEY in obj)) return this.processObject(obj);
357
+ return this.processInclude(obj);
358
+ }
359
+ processObject(obj) {
360
+ const result = {};
361
+ for (const [key, value] of Object.entries(obj)) result[key] = this.process(value);
362
+ return result;
363
+ }
364
+ processInclude(obj) {
365
+ const includeValue = obj[INCLUDE_KEY];
366
+ const otherKeys = Object.keys(obj).filter((k) => k !== INCLUDE_KEY);
367
+ const included = this.resolveInclude(includeValue);
368
+ if (otherKeys.length === 0) return included;
369
+ if (!isPlainObject(included)) throw new ConfigIncludeError("Sibling keys require included content to be an object", typeof includeValue === "string" ? includeValue : INCLUDE_KEY);
370
+ const rest = {};
371
+ for (const key of otherKeys) rest[key] = this.process(obj[key]);
372
+ return deepMerge(included, rest);
373
+ }
374
+ resolveInclude(value) {
375
+ if (typeof value === "string") return this.loadFile(value);
376
+ if (Array.isArray(value)) return value.reduce((merged, item) => {
377
+ if (typeof item !== "string") throw new ConfigIncludeError(`Invalid $include array item: expected string, got ${typeof item}`, String(item));
378
+ return deepMerge(merged, this.loadFile(item));
379
+ }, {});
380
+ throw new ConfigIncludeError(`Invalid $include value: expected string or array of strings, got ${typeof value}`, String(value));
381
+ }
382
+ loadFile(includePath) {
383
+ const resolvedPath = this.resolvePath(includePath);
384
+ this.checkCircular(resolvedPath);
385
+ this.checkDepth(includePath);
386
+ const raw = this.readFile(includePath, resolvedPath);
387
+ const parsed = this.parseFile(includePath, resolvedPath, raw);
388
+ return this.processNested(resolvedPath, parsed);
389
+ }
390
+ resolvePath(includePath) {
391
+ const resolved = path.isAbsolute(includePath) ? includePath : path.resolve(path.dirname(this.basePath), includePath);
392
+ return path.normalize(resolved);
393
+ }
394
+ checkCircular(resolvedPath) {
395
+ if (this.visited.has(resolvedPath)) throw new CircularIncludeError([...this.visited, resolvedPath]);
396
+ }
397
+ checkDepth(includePath) {
398
+ if (this.depth >= MAX_INCLUDE_DEPTH) throw new ConfigIncludeError(`Maximum include depth (${MAX_INCLUDE_DEPTH}) exceeded at: ${includePath}`, includePath);
399
+ }
400
+ readFile(includePath, resolvedPath) {
401
+ try {
402
+ return this.resolver.readFile(resolvedPath);
403
+ } catch (err) {
404
+ throw new ConfigIncludeError(`Failed to read include file: ${includePath} (resolved: ${resolvedPath})`, includePath, err instanceof Error ? err : void 0);
405
+ }
406
+ }
407
+ parseFile(includePath, resolvedPath, raw) {
408
+ try {
409
+ return this.resolver.parseJson(raw);
410
+ } catch (err) {
411
+ throw new ConfigIncludeError(`Failed to parse include file: ${includePath} (resolved: ${resolvedPath})`, includePath, err instanceof Error ? err : void 0);
412
+ }
413
+ }
414
+ processNested(resolvedPath, parsed) {
415
+ const nested = new IncludeProcessor(resolvedPath, this.resolver);
416
+ nested.visited = new Set([...this.visited, resolvedPath]);
417
+ nested.depth = this.depth + 1;
418
+ return nested.process(parsed);
419
+ }
420
+ };
421
+ const defaultResolver = {
422
+ readFile: (p) => fs.readFileSync(p, "utf-8"),
423
+ parseJson: (raw) => JSON5.parse(raw)
424
+ };
425
+ /**
426
+ * Resolves all $include directives in a parsed config object.
427
+ */
428
+ function resolveConfigIncludes(obj, configPath, resolver = defaultResolver) {
429
+ return new IncludeProcessor(configPath, resolver).process(obj);
430
+ }
431
+
432
+ //#endregion
433
+ //#region src/config/env-vars.ts
434
+ function collectConfigEnvVars(cfg) {
435
+ const envConfig = cfg?.env;
436
+ if (!envConfig) return {};
437
+ const entries = {};
438
+ if (envConfig.vars) for (const [key, value] of Object.entries(envConfig.vars)) {
439
+ if (!value) continue;
440
+ entries[key] = value;
441
+ }
442
+ for (const [key, value] of Object.entries(envConfig)) {
443
+ if (key === "shellEnv" || key === "vars") continue;
444
+ if (typeof value !== "string" || !value.trim()) continue;
445
+ entries[key] = value;
446
+ }
447
+ return entries;
448
+ }
449
+
450
+ //#endregion
451
+ //#region src/config/config.ts
452
+ /**
453
+ * Config loading and types.
454
+ *
455
+ * Reads ~/.milaidy/milaidy.json (or $MILAIDY_CONFIG_PATH), resolves $include
456
+ * directives, and applies inline env vars.
457
+ *
458
+ * @module config/config
459
+ */
460
+ /**
461
+ * Load the Milaidy config from disk.
462
+ *
463
+ * 1. Resolves the config path (~/.milaidy/milaidy.json or MILAIDY_CONFIG_PATH).
464
+ * 2. Parses the file as JSON5.
465
+ * 3. Resolves any $include directives.
466
+ * 4. Applies inline env vars (config.env.vars) to process.env.
467
+ *
468
+ * Returns an empty MilaidyConfig when the file is missing.
469
+ */
470
+ async function loadMilaidyConfig() {
471
+ const configPath = resolveConfigPath$1();
472
+ let raw;
473
+ try {
474
+ raw = fs.readFileSync(configPath, "utf-8");
475
+ } catch {
476
+ return {};
477
+ }
478
+ const resolved = resolveConfigIncludes(JSON5.parse(raw), configPath);
479
+ const envVars = collectConfigEnvVars(resolved);
480
+ for (const [key, value] of Object.entries(envVars)) if (!process.env[key]) process.env[key] = value;
481
+ return resolved;
482
+ }
483
+ /**
484
+ * Save the Milaidy config to disk.
485
+ *
486
+ * Writes the config as pretty-printed JSON to the resolved config path.
487
+ * Creates the parent directory if it doesn't exist.
488
+ */
489
+ function saveMilaidyConfig(config) {
490
+ const configPath = resolveConfigPath$1();
491
+ const dir = path.dirname(configPath);
492
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, {
493
+ recursive: true,
494
+ mode: 448
495
+ });
496
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
497
+ }
498
+
499
+ //#endregion
500
+ //#region src/hooks/registry.ts
501
+ /**
502
+ * Hook Registry — event registration and dispatch.
503
+ *
504
+ * Maintains a registry of hook handlers keyed by event strings.
505
+ * Handlers are dispatched sequentially; errors are isolated.
506
+ *
507
+ * @module hooks/registry
508
+ */
509
+ /** Internal registry: event key -> handler list. */
510
+ const registry = /* @__PURE__ */ new Map();
511
+ /**
512
+ * Register a handler for an event key.
513
+ *
514
+ * Event keys can be:
515
+ * - General: "command" (matches all command events)
516
+ * - Specific: "command:new" (matches only /new)
517
+ */
518
+ function registerHook(eventKey, handler) {
519
+ const handlers = registry.get(eventKey) ?? [];
520
+ handlers.push(handler);
521
+ registry.set(eventKey, handlers);
522
+ }
523
+ /**
524
+ * Clear all registered hooks (useful for testing).
525
+ */
526
+ function clearHooks() {
527
+ registry.clear();
528
+ }
529
+ /**
530
+ * Trigger a hook event. Dispatches to all matching handlers.
531
+ *
532
+ * Matching order:
533
+ * 1. Specific key: "command:new"
534
+ * 2. General key: "command"
535
+ *
536
+ * Handlers run sequentially. Errors are caught and logged
537
+ * but don't prevent other handlers from running.
538
+ */
539
+ async function triggerHook(event) {
540
+ const specificKey = `${event.type}:${event.action}`;
541
+ const generalKey = event.type;
542
+ const handlers = [];
543
+ const specificHandlers = registry.get(specificKey);
544
+ if (specificHandlers) for (const handler of specificHandlers) handlers.push({
545
+ key: specificKey,
546
+ handler
547
+ });
548
+ const generalHandlers = registry.get(generalKey);
549
+ if (generalHandlers) for (const handler of generalHandlers) handlers.push({
550
+ key: generalKey,
551
+ handler
552
+ });
553
+ if (handlers.length === 0) return;
554
+ for (const { key, handler } of handlers) try {
555
+ await handler(event);
556
+ } catch (err) {
557
+ const msg = err instanceof Error ? err.message : String(err);
558
+ logger.error(`[hooks] Handler error for "${key}": ${msg}`);
559
+ }
560
+ }
561
+ /**
562
+ * Create a hook event with sensible defaults.
563
+ */
564
+ function createHookEvent(type, action, sessionKey, context = {}) {
565
+ return {
566
+ type,
567
+ action,
568
+ sessionKey,
569
+ timestamp: /* @__PURE__ */ new Date(),
570
+ messages: [],
571
+ context
572
+ };
573
+ }
574
+
575
+ //#endregion
576
+ //#region src/hooks/discovery.ts
577
+ /**
578
+ * Hook Discovery — scan directories for hooks.
579
+ *
580
+ * Discovers hooks from three locations (in order of precedence):
581
+ * 1. Workspace hooks: <workspace>/hooks/ (highest)
582
+ * 2. Managed hooks: ~/.milaidy/hooks/
583
+ * 3. Bundled hooks: <milaidy>/dist/hooks/bundled/ (lowest)
584
+ *
585
+ * Each hook is a directory containing HOOK.md + handler.ts/js.
586
+ *
587
+ * @module hooks/discovery
588
+ */
589
+ const HOOK_MD = "HOOK.md";
590
+ const HANDLER_NAMES = [
591
+ "handler.ts",
592
+ "handler.js",
593
+ "index.ts",
594
+ "index.js"
595
+ ];
596
+ /**
597
+ * Parse YAML-like frontmatter from HOOK.md content.
598
+ * Supports name, description, homepage, and metadata (JSON).
599
+ */
600
+ function parseFrontmatter(content) {
601
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
602
+ if (!fmMatch) return null;
603
+ const fmBlock = fmMatch[1];
604
+ const result = {
605
+ name: "",
606
+ description: ""
607
+ };
608
+ for (const line of fmBlock.split("\n")) {
609
+ const kvMatch = line.match(/^(\w+):\s*(.+)/);
610
+ if (!kvMatch) continue;
611
+ const [, key, rawValue] = kvMatch;
612
+ const value = rawValue.replace(/^["']|["']$/g, "").trim();
613
+ switch (key) {
614
+ case "name":
615
+ result.name = value;
616
+ break;
617
+ case "description":
618
+ result.description = value;
619
+ break;
620
+ case "homepage":
621
+ result.homepage = value;
622
+ break;
623
+ case "metadata":
624
+ try {
625
+ const metaStart = fmBlock.indexOf("metadata:");
626
+ if (metaStart !== -1) {
627
+ const jsonMatch = fmBlock.slice(metaStart + 9).trim().match(/\{[\s\S]*\}/);
628
+ if (jsonMatch) result.metadata = JSON.parse(jsonMatch[0]);
629
+ }
630
+ } catch {
631
+ try {
632
+ result.metadata = JSON.parse(value);
633
+ } catch {
634
+ logger.warn(`[hooks] Failed to parse metadata in HOOK.md`);
635
+ }
636
+ }
637
+ break;
638
+ }
639
+ }
640
+ return result.name ? result : null;
641
+ }
642
+ /**
643
+ * Extract MilaidyHookMetadata from parsed frontmatter.
644
+ */
645
+ function extractMetadata(frontmatter) {
646
+ const milaidy = frontmatter.metadata?.milaidy;
647
+ if (!milaidy) return void 0;
648
+ return {
649
+ always: milaidy.always,
650
+ hookKey: milaidy.hookKey,
651
+ emoji: milaidy.emoji,
652
+ homepage: milaidy.homepage ?? frontmatter.homepage,
653
+ events: Array.isArray(milaidy.events) ? milaidy.events : [],
654
+ export: milaidy.export,
655
+ os: milaidy.os,
656
+ requires: milaidy.requires,
657
+ install: milaidy.install
658
+ };
659
+ }
660
+ /**
661
+ * Check if a path exists and is a directory.
662
+ */
663
+ async function isDirectory(path) {
664
+ try {
665
+ return (await stat(path)).isDirectory();
666
+ } catch {
667
+ return false;
668
+ }
669
+ }
670
+ /**
671
+ * Check if a file exists.
672
+ */
673
+ async function fileExists(path) {
674
+ try {
675
+ return (await stat(path)).isFile();
676
+ } catch {
677
+ return false;
678
+ }
679
+ }
680
+ /**
681
+ * Find the handler file in a hook directory.
682
+ */
683
+ async function findHandlerPath(dir) {
684
+ for (const name of HANDLER_NAMES) {
685
+ const p = join(dir, name);
686
+ if (await fileExists(p)) return p;
687
+ }
688
+ return null;
689
+ }
690
+ /**
691
+ * Load a single hook from a directory.
692
+ */
693
+ async function loadHookFromDir(dir, source, pluginId) {
694
+ const hookMdPath = join(dir, HOOK_MD);
695
+ if (!await fileExists(hookMdPath)) return null;
696
+ const handlerPath = await findHandlerPath(dir);
697
+ if (!handlerPath) {
698
+ logger.warn(`[hooks] Hook at ${dir} has HOOK.md but no handler`);
699
+ return null;
700
+ }
701
+ try {
702
+ const frontmatter = parseFrontmatter(await readFile(hookMdPath, "utf-8"));
703
+ if (!frontmatter) {
704
+ logger.warn(`[hooks] Invalid frontmatter in ${hookMdPath}`);
705
+ return null;
706
+ }
707
+ const metadata = extractMetadata(frontmatter);
708
+ return {
709
+ hook: {
710
+ name: frontmatter.name,
711
+ description: frontmatter.description,
712
+ source,
713
+ pluginId,
714
+ filePath: hookMdPath,
715
+ baseDir: dir,
716
+ handlerPath
717
+ },
718
+ frontmatter,
719
+ metadata
720
+ };
721
+ } catch (err) {
722
+ const msg = err instanceof Error ? err.message : String(err);
723
+ logger.warn(`[hooks] Error loading hook from ${dir}: ${msg}`);
724
+ return null;
725
+ }
726
+ }
727
+ /**
728
+ * Scan a directory for hook subdirectories.
729
+ */
730
+ async function scanHooksDir(dir, source) {
731
+ if (!await isDirectory(dir)) return [];
732
+ const entries = [];
733
+ try {
734
+ const items = await readdir(dir);
735
+ for (const item of items) {
736
+ const itemPath = join(dir, item);
737
+ if (!await isDirectory(itemPath)) continue;
738
+ const entry = await loadHookFromDir(itemPath, source);
739
+ if (entry) entries.push(entry);
740
+ }
741
+ } catch (err) {
742
+ const msg = err instanceof Error ? err.message : String(err);
743
+ logger.warn(`[hooks] Error scanning ${dir}: ${msg}`);
744
+ }
745
+ return entries;
746
+ }
747
+ /**
748
+ * Discover all hooks from configured directories.
749
+ *
750
+ * Precedence (later wins on name conflicts):
751
+ * 1. Extra dirs (lowest)
752
+ * 2. Bundled hooks
753
+ * 3. Managed hooks (~/.milaidy/hooks/)
754
+ * 4. Workspace hooks (<workspace>/hooks/) (highest)
755
+ */
756
+ async function discoverHooks(options = {}) {
757
+ const seen = /* @__PURE__ */ new Map();
758
+ if (options.extraDirs) for (const dir of options.extraDirs) {
759
+ const entries = await scanHooksDir(resolve(dir.replace(/^~/, homedir())), "milaidy-managed");
760
+ for (const entry of entries) seen.set(entry.hook.name, entry);
761
+ }
762
+ if (options.bundledDir) {
763
+ const entries = await scanHooksDir(options.bundledDir, "milaidy-bundled");
764
+ for (const entry of entries) seen.set(entry.hook.name, entry);
765
+ }
766
+ const managedEntries = await scanHooksDir(join(homedir(), ".milaidy", "hooks"), "milaidy-managed");
767
+ for (const entry of managedEntries) seen.set(entry.hook.name, entry);
768
+ if (options.workspacePath) {
769
+ const wsEntries = await scanHooksDir(join(options.workspacePath.replace(/^~/, homedir()), "hooks"), "milaidy-workspace");
770
+ for (const entry of wsEntries) seen.set(entry.hook.name, entry);
771
+ }
772
+ const all = Array.from(seen.values());
773
+ logger.info(`[hooks] Discovered ${all.length} hooks`);
774
+ return all;
775
+ }
776
+
777
+ //#endregion
778
+ //#region src/hooks/eligibility.ts
779
+ /**
780
+ * Hook Eligibility — determine if a hook should be loaded.
781
+ *
782
+ * Checks OS, binary, environment, and config requirements.
783
+ *
784
+ * @module hooks/eligibility
785
+ */
786
+ /**
787
+ * Check if a binary exists on PATH.
788
+ */
789
+ function binaryExists(name) {
790
+ const pathDirs = (process.env.PATH ?? "").split(":");
791
+ for (const dir of pathDirs) if (existsSync(`${dir}/${name}`)) return true;
792
+ return false;
793
+ }
794
+ /**
795
+ * Resolve a dot-separated config path to a value.
796
+ */
797
+ function resolveConfigPath(config, pathStr) {
798
+ const parts = pathStr.split(".");
799
+ let current = config;
800
+ for (const part of parts) {
801
+ if (current === null || current === void 0 || typeof current !== "object") return;
802
+ current = current[part];
803
+ }
804
+ return current;
805
+ }
806
+ /**
807
+ * Check if a config path is truthy.
808
+ */
809
+ function isConfigPathTruthy(config, pathStr) {
810
+ const value = resolveConfigPath(config, pathStr);
811
+ return value !== void 0 && value !== null && value !== false && value !== "" && value !== 0;
812
+ }
813
+ /**
814
+ * Check if a hook meets all eligibility requirements.
815
+ */
816
+ function checkEligibility(metadata, hookConfig, milaidyConfig = {}) {
817
+ const missing = [];
818
+ if (!metadata) return {
819
+ eligible: true,
820
+ missing: []
821
+ };
822
+ if (hookConfig?.enabled === false) return {
823
+ eligible: false,
824
+ missing: ["Disabled in config"]
825
+ };
826
+ if (metadata.os && metadata.os.length > 0) {
827
+ if (!metadata.os.includes(platform())) missing.push(`OS: requires ${metadata.os.join("|")}, current: ${platform()}`);
828
+ }
829
+ if (metadata.always) return {
830
+ eligible: missing.length === 0,
831
+ missing
832
+ };
833
+ if (metadata.requires?.bins) {
834
+ for (const bin of metadata.requires.bins) if (!binaryExists(bin)) missing.push(`Binary missing: ${bin}`);
835
+ }
836
+ if (metadata.requires?.anyBins && metadata.requires.anyBins.length > 0) {
837
+ if (!metadata.requires.anyBins.some(binaryExists)) missing.push(`None of: ${metadata.requires.anyBins.join(", ")}`);
838
+ }
839
+ if (metadata.requires?.env) for (const envVar of metadata.requires.env) {
840
+ const hasInProcess = Boolean(process.env[envVar]);
841
+ const hasInHookConfig = Boolean(hookConfig?.env?.[envVar]);
842
+ if (!hasInProcess && !hasInHookConfig) missing.push(`Env missing: ${envVar}`);
843
+ }
844
+ if (metadata.requires?.config) {
845
+ for (const configPath of metadata.requires.config) if (!isConfigPathTruthy(milaidyConfig, configPath)) missing.push(`Config missing: ${configPath}`);
846
+ }
847
+ return {
848
+ eligible: missing.length === 0,
849
+ missing
850
+ };
851
+ }
852
+ /**
853
+ * Resolve per-hook config from the internal hooks config.
854
+ */
855
+ function resolveHookConfig(internalConfig, hookKey) {
856
+ return internalConfig?.entries?.[hookKey];
857
+ }
858
+
859
+ //#endregion
860
+ //#region src/hooks/loader.ts
861
+ /**
862
+ * Hook Loader — load and register hooks into the event system.
863
+ *
864
+ * Orchestrates discovery -> eligibility -> loading -> registration.
865
+ *
866
+ * @module hooks/loader
867
+ */
868
+ /**
869
+ * Dynamically import a hook handler module.
870
+ * Uses cache-busting query parameter for dev mode hot reload.
871
+ */
872
+ async function loadHandlerModule(handlerPath, exportName = "default") {
873
+ try {
874
+ const handler = (await import(`${pathToFileURL(handlerPath).href}?t=${Date.now()}`))[exportName];
875
+ if (typeof handler !== "function") {
876
+ logger.warn(`[hooks] Handler at ${handlerPath} does not export a function as "${exportName}"`);
877
+ return null;
878
+ }
879
+ return handler;
880
+ } catch (err) {
881
+ const msg = err instanceof Error ? err.message : String(err);
882
+ logger.error(`[hooks] Failed to load handler ${handlerPath}: ${msg}`);
883
+ return null;
884
+ }
885
+ }
886
+ /**
887
+ * Discover, filter, load, and register all hooks.
888
+ *
889
+ * This is the main entry point called during gateway startup.
890
+ */
891
+ async function loadHooks(options = {}) {
892
+ const { internalConfig, milaidyConfig = {} } = options;
893
+ if (internalConfig?.enabled === false) {
894
+ logger.info("[hooks] Internal hooks disabled");
895
+ return {
896
+ discovered: 0,
897
+ eligible: 0,
898
+ registered: 0,
899
+ skipped: [],
900
+ failed: []
901
+ };
902
+ }
903
+ clearHooks();
904
+ const entries = await discoverHooks({
905
+ workspacePath: options.workspacePath,
906
+ bundledDir: options.bundledDir,
907
+ extraDirs: [...options.extraDirs ?? [], ...internalConfig?.load?.extraDirs ?? []]
908
+ });
909
+ const result = {
910
+ discovered: entries.length,
911
+ eligible: 0,
912
+ registered: 0,
913
+ skipped: [],
914
+ failed: []
915
+ };
916
+ for (const entry of entries) {
917
+ const hookConfig = resolveHookConfig(internalConfig, entry.metadata?.hookKey ?? entry.hook.name);
918
+ const eligibility = checkEligibility(entry.metadata, hookConfig, milaidyConfig);
919
+ if (!eligibility.eligible) {
920
+ result.skipped.push(`${entry.hook.name}: ${eligibility.missing.join(", ")}`);
921
+ continue;
922
+ }
923
+ result.eligible++;
924
+ if (hookConfig?.enabled === false) {
925
+ result.skipped.push(`${entry.hook.name}: disabled in config`);
926
+ continue;
927
+ }
928
+ const exportName = entry.metadata?.export ?? "default";
929
+ const handler = await loadHandlerModule(entry.hook.handlerPath, exportName);
930
+ if (!handler) {
931
+ result.failed.push(entry.hook.name);
932
+ continue;
933
+ }
934
+ const events = entry.metadata?.events ?? [];
935
+ if (events.length === 0) {
936
+ logger.warn(`[hooks] Hook "${entry.hook.name}" has no events configured`);
937
+ result.skipped.push(`${entry.hook.name}: no events`);
938
+ continue;
939
+ }
940
+ for (const eventKey of events) registerHook(eventKey, handler);
941
+ const emoji = entry.metadata?.emoji ?? "šŸ”—";
942
+ logger.info(`[hooks] ${emoji} Registered: ${entry.hook.name} -> ${events.join(", ")}`);
943
+ result.registered++;
944
+ }
945
+ if (internalConfig?.handlers) for (const legacyHandler of internalConfig.handlers) try {
946
+ const handler = await loadHandlerModule(legacyHandler.module, legacyHandler.export ?? "default");
947
+ if (handler) {
948
+ registerHook(legacyHandler.event, handler);
949
+ logger.info(`[hooks] Registered legacy handler: ${legacyHandler.event} -> ${legacyHandler.module}`);
950
+ result.registered++;
951
+ }
952
+ } catch (err) {
953
+ const msg = err instanceof Error ? err.message : String(err);
954
+ logger.warn(`[hooks] Failed to load legacy handler: ${msg}`);
955
+ result.failed.push(legacyHandler.module);
956
+ }
957
+ logger.info(`[hooks] Load complete: ${result.registered}/${result.discovered} registered, ${result.skipped.length} skipped, ${result.failed.length} failed`);
958
+ return result;
959
+ }
960
+
961
+ //#endregion
962
+ //#region src/process/exec.ts
963
+ /**
964
+ * Runs a command with an optional timeout.
965
+ * Returns { code, stdout, stderr }.
966
+ * Rejects if the process cannot be spawned or the timeout fires.
967
+ */
968
+ function runCommandWithTimeout(argv, opts = {}) {
969
+ const [cmd, ...args] = argv;
970
+ if (!cmd) return Promise.reject(/* @__PURE__ */ new Error("runCommandWithTimeout: empty argv"));
971
+ return new Promise((resolve, reject) => {
972
+ const child = spawn(cmd, args, {
973
+ cwd: opts.cwd,
974
+ env: opts.env ?? process.env,
975
+ stdio: [
976
+ "ignore",
977
+ "pipe",
978
+ "pipe"
979
+ ]
980
+ });
981
+ const stdoutChunks = [];
982
+ const stderrChunks = [];
983
+ child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
984
+ child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
985
+ let timedOut = false;
986
+ let timer;
987
+ if (opts.timeoutMs && opts.timeoutMs > 0) timer = setTimeout(() => {
988
+ timedOut = true;
989
+ child.kill("SIGKILL");
990
+ }, opts.timeoutMs);
991
+ child.on("error", (err) => {
992
+ if (timer) clearTimeout(timer);
993
+ reject(err);
994
+ });
995
+ child.on("close", (exitCode) => {
996
+ if (timer) clearTimeout(timer);
997
+ if (timedOut) {
998
+ reject(/* @__PURE__ */ new Error(`Command timed out after ${opts.timeoutMs}ms: ${argv.join(" ")}`));
999
+ return;
1000
+ }
1001
+ resolve({
1002
+ code: exitCode ?? 1,
1003
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
1004
+ stderr: Buffer.concat(stderrChunks).toString("utf-8")
1005
+ });
1006
+ });
1007
+ });
1008
+ }
1009
+
1010
+ //#endregion
1011
+ //#region src/logging.ts
1012
+ const ALLOWED_LOG_LEVELS = [
1013
+ "trace",
1014
+ "debug",
1015
+ "info",
1016
+ "warn",
1017
+ "error",
1018
+ "fatal",
1019
+ "silent"
1020
+ ];
1021
+ function normalizeLogLevel(level, fallback = "info") {
1022
+ if (!level) return fallback;
1023
+ const normalized = level.toLowerCase();
1024
+ return ALLOWED_LOG_LEVELS.includes(normalized) ? normalized : fallback;
1025
+ }
1026
+ let consoleSettings = {
1027
+ level: normalizeLogLevel(process.env.LOG_LEVEL, "info"),
1028
+ style: process.env.LOG_JSON_FORMAT === "true" ? "json" : "pretty"
1029
+ };
1030
+
1031
+ //#endregion
1032
+ //#region src/terminal/palette.ts
1033
+ const CLI_PALETTE = {
1034
+ accent: "#FF5A2D",
1035
+ accentBright: "#FF7A3D",
1036
+ accentDim: "#D14A22",
1037
+ info: "#FF8A5B",
1038
+ success: "#2FBF71",
1039
+ warn: "#FFB020",
1040
+ error: "#E23D2D",
1041
+ muted: "#8B7F77"
1042
+ };
1043
+
1044
+ //#endregion
1045
+ //#region src/terminal/theme.ts
1046
+ const hasForceColor = typeof process.env.FORCE_COLOR === "string" && process.env.FORCE_COLOR.trim().length > 0 && process.env.FORCE_COLOR.trim() !== "0";
1047
+ const baseChalk = process.env.NO_COLOR && !hasForceColor ? new Chalk({ level: 0 }) : chalk;
1048
+ const hex = (value) => baseChalk.hex(value);
1049
+ const theme = {
1050
+ accent: hex(CLI_PALETTE.accent),
1051
+ accentBright: hex(CLI_PALETTE.accentBright),
1052
+ accentDim: hex(CLI_PALETTE.accentDim),
1053
+ info: hex(CLI_PALETTE.info),
1054
+ success: hex(CLI_PALETTE.success),
1055
+ warn: hex(CLI_PALETTE.warn),
1056
+ error: hex(CLI_PALETTE.error),
1057
+ muted: hex(CLI_PALETTE.muted),
1058
+ heading: baseChalk.bold.hex(CLI_PALETTE.accent),
1059
+ command: hex(CLI_PALETTE.accentBright),
1060
+ option: hex(CLI_PALETTE.warn)
1061
+ };
1062
+
1063
+ //#endregion
1064
+ //#region src/globals.ts
1065
+ const success = theme.success;
1066
+ const warn = theme.warn;
1067
+ const info = theme.info;
1068
+ const danger = theme.error;
1069
+
1070
+ //#endregion
1071
+ //#region src/utils.ts
1072
+ function resolveUserPath(input) {
1073
+ const trimmed = input.trim();
1074
+ if (!trimmed) return trimmed;
1075
+ if (trimmed.startsWith("~")) {
1076
+ const expanded = trimmed.replace(/^~(?=$|[\\/])/, os.homedir());
1077
+ return path.resolve(expanded);
1078
+ }
1079
+ return path.resolve(trimmed);
1080
+ }
1081
+ function resolveConfigDir(env = process.env, homedir = os.homedir) {
1082
+ const override = env.MILAIDY_STATE_DIR?.trim();
1083
+ if (override) return resolveUserPath(override);
1084
+ return path.join(homedir(), ".milaidy");
1085
+ }
1086
+ const CONFIG_DIR = resolveConfigDir();
1087
+
1088
+ //#endregion
1089
+ //#region src/infra/milaidy-root.ts
1090
+ const CORE_PACKAGE_NAMES = new Set(["milaidy"]);
1091
+ async function readPackageName(dir) {
1092
+ try {
1093
+ const raw = await fs$1.readFile(path.join(dir, "package.json"), "utf-8");
1094
+ const parsed = JSON.parse(raw);
1095
+ return typeof parsed.name === "string" ? parsed.name : null;
1096
+ } catch {
1097
+ return null;
1098
+ }
1099
+ }
1100
+ async function findPackageRoot(startDir, maxDepth = 12) {
1101
+ let current = path.resolve(startDir);
1102
+ for (let i = 0; i < maxDepth; i += 1) {
1103
+ const name = await readPackageName(current);
1104
+ if (name && CORE_PACKAGE_NAMES.has(name)) return current;
1105
+ const parent = path.dirname(current);
1106
+ if (parent === current) break;
1107
+ current = parent;
1108
+ }
1109
+ return null;
1110
+ }
1111
+ function candidateDirsFromArgv1(argv1) {
1112
+ const normalized = path.resolve(argv1);
1113
+ const candidates = [path.dirname(normalized)];
1114
+ const parts = normalized.split(path.sep);
1115
+ const binIndex = parts.lastIndexOf(".bin");
1116
+ if (binIndex > 0 && parts[binIndex - 1] === "node_modules") {
1117
+ const binName = path.basename(normalized);
1118
+ const nodeModulesDir = parts.slice(0, binIndex).join(path.sep);
1119
+ candidates.push(path.join(nodeModulesDir, binName));
1120
+ }
1121
+ return candidates;
1122
+ }
1123
+ async function resolveMilaidyPackageRoot(opts) {
1124
+ const candidates = [];
1125
+ if (opts.moduleUrl) candidates.push(path.dirname(fileURLToPath(opts.moduleUrl)));
1126
+ if (opts.argv1) candidates.push(...candidateDirsFromArgv1(opts.argv1));
1127
+ if (opts.cwd) candidates.push(opts.cwd);
1128
+ for (const candidate of candidates) {
1129
+ const found = await findPackageRoot(candidate);
1130
+ if (found) return found;
1131
+ }
1132
+ return null;
1133
+ }
1134
+
1135
+ //#endregion
1136
+ //#region src/agents/workspace-templates.ts
1137
+ const FALLBACK_TEMPLATE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../docs/reference/templates");
1138
+ let cachedTemplateDir;
1139
+ let resolvingTemplateDir;
1140
+ async function pathExists(candidate) {
1141
+ try {
1142
+ await fs$1.access(candidate);
1143
+ return true;
1144
+ } catch {
1145
+ return false;
1146
+ }
1147
+ }
1148
+ async function resolveWorkspaceTemplateDir(opts) {
1149
+ if (cachedTemplateDir) return cachedTemplateDir;
1150
+ if (resolvingTemplateDir) return resolvingTemplateDir;
1151
+ resolvingTemplateDir = (async () => {
1152
+ const moduleUrl = opts?.moduleUrl ?? import.meta.url;
1153
+ const argv1 = opts?.argv1 ?? process.argv[1];
1154
+ const cwd = opts?.cwd ?? process.cwd();
1155
+ const packageRoot = await resolveMilaidyPackageRoot({
1156
+ moduleUrl,
1157
+ argv1,
1158
+ cwd
1159
+ });
1160
+ const candidates = [
1161
+ packageRoot ? path.join(packageRoot, "docs", "reference", "templates") : null,
1162
+ cwd ? path.resolve(cwd, "docs", "reference", "templates") : null,
1163
+ FALLBACK_TEMPLATE_DIR
1164
+ ].filter(Boolean);
1165
+ for (const candidate of candidates) if (await pathExists(candidate)) {
1166
+ cachedTemplateDir = candidate;
1167
+ return candidate;
1168
+ }
1169
+ cachedTemplateDir = candidates[0] ?? FALLBACK_TEMPLATE_DIR;
1170
+ return cachedTemplateDir;
1171
+ })();
1172
+ try {
1173
+ return await resolvingTemplateDir;
1174
+ } finally {
1175
+ resolvingTemplateDir = void 0;
1176
+ }
1177
+ }
1178
+
1179
+ //#endregion
1180
+ //#region src/agents/workspace.ts
1181
+ function resolveDefaultAgentWorkspaceDir(env = process.env, homedir = os.homedir) {
1182
+ const profile = env.MILAIDY_PROFILE?.trim();
1183
+ if (profile && profile.toLowerCase() !== "default") return path.join(homedir(), ".milaidy", `workspace-${profile}`);
1184
+ return path.join(homedir(), ".milaidy", "workspace");
1185
+ }
1186
+ const DEFAULT_AGENT_WORKSPACE_DIR = resolveDefaultAgentWorkspaceDir();
1187
+ const DEFAULT_AGENTS_FILENAME = "AGENTS.md";
1188
+ const DEFAULT_TOOLS_FILENAME = "TOOLS.md";
1189
+ const DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
1190
+ const DEFAULT_USER_FILENAME = "USER.md";
1191
+ const DEFAULT_HEARTBEAT_FILENAME = "HEARTBEAT.md";
1192
+ const DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
1193
+ const DEFAULT_MEMORY_FILENAME = "MEMORY.md";
1194
+ const DEFAULT_MEMORY_ALT_FILENAME = "memory.md";
1195
+ function stripFrontMatter(content) {
1196
+ if (!content.startsWith("---")) return content;
1197
+ const endIndex = content.indexOf("\n---", 3);
1198
+ if (endIndex === -1) return content;
1199
+ const start = endIndex + 4;
1200
+ let trimmed = content.slice(start);
1201
+ trimmed = trimmed.replace(/^\s+/, "");
1202
+ return trimmed;
1203
+ }
1204
+ async function loadTemplate(name) {
1205
+ const templateDir = await resolveWorkspaceTemplateDir();
1206
+ const templatePath = path.join(templateDir, name);
1207
+ try {
1208
+ return stripFrontMatter(await fs$1.readFile(templatePath, "utf-8"));
1209
+ } catch {
1210
+ throw new Error(`Missing workspace template: ${name} (${templatePath}). Ensure docs/reference/templates are packaged.`);
1211
+ }
1212
+ }
1213
+ async function writeFileIfMissing(filePath, content) {
1214
+ try {
1215
+ await fs$1.writeFile(filePath, content, {
1216
+ encoding: "utf-8",
1217
+ flag: "wx"
1218
+ });
1219
+ } catch (err) {
1220
+ if (err.code !== "EEXIST") throw err;
1221
+ }
1222
+ }
1223
+ async function hasGitRepo(dir) {
1224
+ try {
1225
+ await fs$1.stat(path.join(dir, ".git"));
1226
+ return true;
1227
+ } catch {
1228
+ return false;
1229
+ }
1230
+ }
1231
+ async function isGitAvailable() {
1232
+ try {
1233
+ return (await runCommandWithTimeout(["git", "--version"], { timeoutMs: 2e3 })).code === 0;
1234
+ } catch {
1235
+ return false;
1236
+ }
1237
+ }
1238
+ async function ensureGitRepo(dir, isBrandNewWorkspace) {
1239
+ if (!isBrandNewWorkspace) return;
1240
+ if (await hasGitRepo(dir)) return;
1241
+ if (!await isGitAvailable()) return;
1242
+ try {
1243
+ await runCommandWithTimeout(["git", "init"], {
1244
+ cwd: dir,
1245
+ timeoutMs: 1e4
1246
+ });
1247
+ } catch {}
1248
+ }
1249
+ async function ensureAgentWorkspace(params) {
1250
+ const dir = resolveUserPath(params?.dir?.trim() ? params.dir.trim() : DEFAULT_AGENT_WORKSPACE_DIR);
1251
+ await fs$1.mkdir(dir, { recursive: true });
1252
+ if (!params?.ensureBootstrapFiles) return { dir };
1253
+ const agentsPath = path.join(dir, DEFAULT_AGENTS_FILENAME);
1254
+ const toolsPath = path.join(dir, DEFAULT_TOOLS_FILENAME);
1255
+ const identityPath = path.join(dir, DEFAULT_IDENTITY_FILENAME);
1256
+ const userPath = path.join(dir, DEFAULT_USER_FILENAME);
1257
+ const heartbeatPath = path.join(dir, DEFAULT_HEARTBEAT_FILENAME);
1258
+ const bootstrapPath = path.join(dir, DEFAULT_BOOTSTRAP_FILENAME);
1259
+ const isBrandNewWorkspace = await (async () => {
1260
+ const paths = [
1261
+ agentsPath,
1262
+ toolsPath,
1263
+ identityPath,
1264
+ userPath,
1265
+ heartbeatPath
1266
+ ];
1267
+ return (await Promise.all(paths.map(async (p) => {
1268
+ try {
1269
+ await fs$1.access(p);
1270
+ return true;
1271
+ } catch {
1272
+ return false;
1273
+ }
1274
+ }))).every((v) => !v);
1275
+ })();
1276
+ const agentsTemplate = await loadTemplate(DEFAULT_AGENTS_FILENAME);
1277
+ const toolsTemplate = await loadTemplate(DEFAULT_TOOLS_FILENAME);
1278
+ const identityTemplate = await loadTemplate(DEFAULT_IDENTITY_FILENAME);
1279
+ const userTemplate = await loadTemplate(DEFAULT_USER_FILENAME);
1280
+ const heartbeatTemplate = await loadTemplate(DEFAULT_HEARTBEAT_FILENAME);
1281
+ const bootstrapTemplate = await loadTemplate(DEFAULT_BOOTSTRAP_FILENAME);
1282
+ await writeFileIfMissing(agentsPath, agentsTemplate);
1283
+ await writeFileIfMissing(toolsPath, toolsTemplate);
1284
+ await writeFileIfMissing(identityPath, identityTemplate);
1285
+ await writeFileIfMissing(userPath, userTemplate);
1286
+ await writeFileIfMissing(heartbeatPath, heartbeatTemplate);
1287
+ if (isBrandNewWorkspace) await writeFileIfMissing(bootstrapPath, bootstrapTemplate);
1288
+ await ensureGitRepo(dir, isBrandNewWorkspace);
1289
+ return {
1290
+ dir,
1291
+ agentsPath,
1292
+ toolsPath,
1293
+ identityPath,
1294
+ userPath,
1295
+ heartbeatPath,
1296
+ bootstrapPath
1297
+ };
1298
+ }
1299
+ async function resolveMemoryBootstrapEntries(resolvedDir) {
1300
+ const candidates = [DEFAULT_MEMORY_FILENAME, DEFAULT_MEMORY_ALT_FILENAME];
1301
+ const entries = [];
1302
+ for (const name of candidates) {
1303
+ const filePath = path.join(resolvedDir, name);
1304
+ try {
1305
+ await fs$1.access(filePath);
1306
+ entries.push({
1307
+ name,
1308
+ filePath
1309
+ });
1310
+ } catch {}
1311
+ }
1312
+ if (entries.length <= 1) return entries;
1313
+ const seen = /* @__PURE__ */ new Set();
1314
+ const deduped = [];
1315
+ for (const entry of entries) {
1316
+ let key = entry.filePath;
1317
+ try {
1318
+ key = await fs$1.realpath(entry.filePath);
1319
+ } catch {}
1320
+ if (seen.has(key)) continue;
1321
+ seen.add(key);
1322
+ deduped.push(entry);
1323
+ }
1324
+ return deduped;
1325
+ }
1326
+ async function loadWorkspaceBootstrapFiles(dir) {
1327
+ const resolvedDir = resolveUserPath(dir);
1328
+ const entries = [
1329
+ {
1330
+ name: DEFAULT_AGENTS_FILENAME,
1331
+ filePath: path.join(resolvedDir, DEFAULT_AGENTS_FILENAME)
1332
+ },
1333
+ {
1334
+ name: DEFAULT_TOOLS_FILENAME,
1335
+ filePath: path.join(resolvedDir, DEFAULT_TOOLS_FILENAME)
1336
+ },
1337
+ {
1338
+ name: DEFAULT_IDENTITY_FILENAME,
1339
+ filePath: path.join(resolvedDir, DEFAULT_IDENTITY_FILENAME)
1340
+ },
1341
+ {
1342
+ name: DEFAULT_USER_FILENAME,
1343
+ filePath: path.join(resolvedDir, DEFAULT_USER_FILENAME)
1344
+ },
1345
+ {
1346
+ name: DEFAULT_HEARTBEAT_FILENAME,
1347
+ filePath: path.join(resolvedDir, DEFAULT_HEARTBEAT_FILENAME)
1348
+ },
1349
+ {
1350
+ name: DEFAULT_BOOTSTRAP_FILENAME,
1351
+ filePath: path.join(resolvedDir, DEFAULT_BOOTSTRAP_FILENAME)
1352
+ }
1353
+ ];
1354
+ entries.push(...await resolveMemoryBootstrapEntries(resolvedDir));
1355
+ const result = [];
1356
+ for (const entry of entries) try {
1357
+ const content = await fs$1.readFile(entry.filePath, "utf-8");
1358
+ result.push({
1359
+ name: entry.name,
1360
+ path: entry.filePath,
1361
+ content,
1362
+ missing: false
1363
+ });
1364
+ } catch {
1365
+ result.push({
1366
+ name: entry.name,
1367
+ path: entry.filePath,
1368
+ missing: true
1369
+ });
1370
+ }
1371
+ return result;
1372
+ }
1373
+ const SUBAGENT_BOOTSTRAP_ALLOWLIST = new Set([DEFAULT_AGENTS_FILENAME, DEFAULT_TOOLS_FILENAME]);
1374
+ function filterBootstrapFilesForSession(files, sessionKey) {
1375
+ if (!sessionKey || !isSubagentSessionKey(sessionKey)) return files;
1376
+ return files.filter((file) => SUBAGENT_BOOTSTRAP_ALLOWLIST.has(file.name));
1377
+ }
1378
+
1379
+ //#endregion
1380
+ //#region src/agents/workspace-provider.ts
1381
+ const DEFAULT_MAX_CHARS = 2e4;
1382
+ const CACHE_TTL_MS = 6e4;
1383
+ const cache = /* @__PURE__ */ new Map();
1384
+ async function getFiles(dir) {
1385
+ const entry = cache.get(dir);
1386
+ if (entry && Date.now() - entry.at < CACHE_TTL_MS) return entry.files;
1387
+ const files = await loadWorkspaceBootstrapFiles(dir);
1388
+ cache.set(dir, {
1389
+ files,
1390
+ at: Date.now()
1391
+ });
1392
+ return files;
1393
+ }
1394
+ function truncate(content, max) {
1395
+ if (content.length <= max) return content;
1396
+ return `${content.slice(0, max)}\n\n[... truncated at ${max.toLocaleString()} chars]`;
1397
+ }
1398
+ function buildContext(files, maxChars) {
1399
+ const sections = [];
1400
+ for (const f of files) {
1401
+ if (f.missing || !f.content?.trim()) continue;
1402
+ const text = truncate(f.content.trim(), maxChars);
1403
+ const tag = text.length > f.content.trim().length ? " [TRUNCATED]" : "";
1404
+ sections.push(`### ${f.name}${tag}\n\n${text}`);
1405
+ }
1406
+ if (sections.length === 0) return "";
1407
+ return `## Project Context (Workspace)\n\n${sections.join("\n\n---\n\n")}`;
1408
+ }
1409
+ function createWorkspaceProvider(options) {
1410
+ const dir = options?.workspaceDir ?? DEFAULT_AGENT_WORKSPACE_DIR;
1411
+ const maxChars = options?.maxCharsPerFile ?? DEFAULT_MAX_CHARS;
1412
+ return {
1413
+ name: "workspaceContext",
1414
+ description: "Workspace bootstrap files (AGENTS.md, TOOLS.md, IDENTITY.md, etc.)",
1415
+ position: 10,
1416
+ async get(_runtime, message, _state) {
1417
+ try {
1418
+ const allFiles = await getFiles(dir);
1419
+ const sessionKey = message.metadata?.sessionKey;
1420
+ return {
1421
+ text: buildContext(filterBootstrapFilesForSession(allFiles, sessionKey), maxChars),
1422
+ data: { workspaceDir: dir }
1423
+ };
1424
+ } catch (err) {
1425
+ return {
1426
+ text: `[Workspace context unavailable: ${err instanceof Error ? err.message : err}]`,
1427
+ data: {}
1428
+ };
1429
+ }
1430
+ }
1431
+ };
1432
+ }
1433
+
1434
+ //#endregion
1435
+ //#region src/agents/session-bridge.ts
1436
+ /**
1437
+ * Resolve an Milaidy session key from an ElizaOS room.
1438
+ *
1439
+ * DMs -> agent:{agentId}:main
1440
+ * Groups -> agent:{agentId}:{channel}:group:{groupId}
1441
+ * Channels -> agent:{agentId}:{channel}:channel:{channelId}
1442
+ * Threads append :thread:{threadId}
1443
+ */
1444
+ function resolveSessionKeyFromRoom(agentId, room, meta) {
1445
+ const channel = meta?.channel ?? room.source ?? "unknown";
1446
+ if (room.type === ChannelType.DM || room.type === ChannelType.SELF) return buildAgentMainSessionKey({
1447
+ agentId,
1448
+ mainKey: "main"
1449
+ });
1450
+ const id = meta?.groupId ?? room.channelId ?? room.id;
1451
+ const base = `agent:${agentId}:${channel}:${room.type === ChannelType.GROUP ? "group" : "channel"}:${id}`;
1452
+ return meta?.threadId ? `${base}:thread:${meta.threadId}` : base;
1453
+ }
1454
+ function createSessionKeyProvider(options) {
1455
+ const agentId = options?.defaultAgentId ?? "main";
1456
+ return {
1457
+ name: "milaidySessionKey",
1458
+ description: "Milaidy session key (DM/group/thread isolation)",
1459
+ dynamic: true,
1460
+ position: 5,
1461
+ async get(runtime, message, _state) {
1462
+ const meta = message.metadata ?? {};
1463
+ const existing = meta.sessionKey;
1464
+ if (existing) {
1465
+ const parsed = parseAgentSessionKey(existing);
1466
+ return {
1467
+ text: `Session: ${existing}`,
1468
+ values: {
1469
+ sessionKey: existing,
1470
+ agentId: parsed?.agentId ?? agentId
1471
+ },
1472
+ data: { sessionKey: existing }
1473
+ };
1474
+ }
1475
+ const room = await runtime.getRoom(message.roomId);
1476
+ if (!room) {
1477
+ const key = buildAgentMainSessionKey({
1478
+ agentId,
1479
+ mainKey: "main"
1480
+ });
1481
+ return {
1482
+ text: `Session: ${key}`,
1483
+ values: { sessionKey: key },
1484
+ data: { sessionKey: key }
1485
+ };
1486
+ }
1487
+ const key = resolveSessionKeyFromRoom(agentId, room, {
1488
+ threadId: meta.threadId,
1489
+ groupId: meta.groupId,
1490
+ channel: meta.channel ?? room.source
1491
+ });
1492
+ return {
1493
+ text: `Session: ${key}`,
1494
+ values: {
1495
+ sessionKey: key,
1496
+ isGroup: room.type === ChannelType.GROUP
1497
+ },
1498
+ data: { sessionKey: key }
1499
+ };
1500
+ }
1501
+ };
1502
+ }
1503
+
1504
+ //#endregion
1505
+ //#region src/agents/compaction-action.ts
1506
+ /**
1507
+ * Compaction: summarize conversation history, store summary, set compaction point.
1508
+ *
1509
+ * After compaction, recentMessages provider only loads messages after lastCompactionAt.
1510
+ * The summary becomes the effective "first message" in the room.
1511
+ */
1512
+ function buildPrompt(messages, instructions) {
1513
+ 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;
1514
+ if (instructions) prompt += `\n\nAdditional instructions: ${instructions}`;
1515
+ prompt += "\n\nSummary:";
1516
+ return prompt;
1517
+ }
1518
+ async function summarize(runtime, roomId, instructions) {
1519
+ const messages = await runtime.getMemories({
1520
+ tableName: "messages",
1521
+ roomId,
1522
+ count: 200
1523
+ });
1524
+ if (!messages?.length) return "No conversation history to compact.";
1525
+ const formatted = messages.sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0)).map((m) => {
1526
+ return `${m.entityId === runtime.agentId ? "Assistant" : "User"}: ${m.content?.text ?? ""}`;
1527
+ }).join("\n");
1528
+ return runtime.useModel(ModelType.TEXT_LARGE, { prompt: buildPrompt(formatted, instructions) });
1529
+ }
1530
+ const compactAction = {
1531
+ name: "COMPACT_SESSION",
1532
+ similes: [
1533
+ "COMPACT",
1534
+ "COMPRESS",
1535
+ "SUMMARIZE_SESSION"
1536
+ ],
1537
+ description: "Summarize conversation history and set a compaction point.",
1538
+ validate: async () => true,
1539
+ handler: async (runtime, message, _state, _options, callback) => {
1540
+ const { roomId } = message;
1541
+ const instructions = (message.content?.text ?? "").replace(/^\/?compact\s*/i, "").trim() || void 0;
1542
+ try {
1543
+ const summary = await summarize(runtime, roomId, instructions);
1544
+ const now = Date.now();
1545
+ await runtime.createMemory({
1546
+ id: crypto.randomUUID(),
1547
+ entityId: runtime.agentId,
1548
+ roomId,
1549
+ content: {
1550
+ text: `[Compaction Summary]\n\n${summary}`,
1551
+ source: "compaction"
1552
+ },
1553
+ createdAt: now,
1554
+ metadata: { type: MemoryType.CUSTOM }
1555
+ }, "messages");
1556
+ const room = await runtime.getRoom(roomId);
1557
+ if (room) {
1558
+ const prev = Array.isArray(room.metadata?.compactionHistory) ? room.metadata.compactionHistory : [];
1559
+ const entry = {
1560
+ timestamp: now,
1561
+ triggeredBy: message.entityId
1562
+ };
1563
+ const compactionHistory = [...prev, entry].slice(-10);
1564
+ await runtime.updateRoom({
1565
+ ...room,
1566
+ metadata: {
1567
+ ...room.metadata,
1568
+ lastCompactionAt: now,
1569
+ compactionHistory
1570
+ }
1571
+ });
1572
+ }
1573
+ if (callback) await callback({ text: "Session compacted." });
1574
+ return {
1575
+ success: true,
1576
+ data: { compactedAt: now }
1577
+ };
1578
+ } catch (err) {
1579
+ const msg = err instanceof Error ? err.message : String(err);
1580
+ if (callback) await callback({ text: `Compaction failed: ${msg}` });
1581
+ return {
1582
+ success: false,
1583
+ error: msg
1584
+ };
1585
+ }
1586
+ },
1587
+ examples: [[{
1588
+ name: "{{name1}}",
1589
+ content: { text: "/compact" }
1590
+ }, {
1591
+ name: "{{agentName}}",
1592
+ content: {
1593
+ text: "Session compacted.",
1594
+ actions: ["COMPACT_SESSION"]
1595
+ }
1596
+ }], [{
1597
+ name: "{{name1}}",
1598
+ content: { text: "/compact Focus on decisions" }
1599
+ }, {
1600
+ name: "{{agentName}}",
1601
+ content: {
1602
+ text: "Session compacted.",
1603
+ actions: ["COMPACT_SESSION"]
1604
+ }
1605
+ }]]
1606
+ };
1607
+
1608
+ //#endregion
1609
+ //#region src/agents/tools/memory-tools.ts
1610
+ /**
1611
+ * memory_search and memory_get actions for workspace memory files.
1612
+ */
1613
+ const SUPPORTED_EXT = new Set([
1614
+ ".md",
1615
+ ".txt",
1616
+ ".json"
1617
+ ]);
1618
+ function resolveMemoryDir(workspacePath) {
1619
+ return join(workspacePath?.replace(/^~/, homedir()) ?? join(homedir(), ".milaidy", "workspace"), "memory");
1620
+ }
1621
+ function score(content, query) {
1622
+ const lower = content.toLowerCase();
1623
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
1624
+ if (terms.length === 0) return 0;
1625
+ let matched = 0;
1626
+ for (const t of terms) if (lower.includes(t)) matched++;
1627
+ return matched / terms.length;
1628
+ }
1629
+ const memorySearchAction = {
1630
+ name: "memory_search",
1631
+ description: "Search workspace memory files for relevant context.",
1632
+ similes: ["MEMORY_SEARCH", "SEARCH_MEMORY"],
1633
+ examples: [],
1634
+ validate: async (_runtime, message) => {
1635
+ const text = message.content?.text;
1636
+ return typeof text === "string" && text.length > 0;
1637
+ },
1638
+ handler: async (_runtime, message, _state, options, callback) => {
1639
+ const params = options ?? {};
1640
+ const query = params.query ?? message.content?.text ?? "";
1641
+ const limit = Math.min(Number(params.limit ?? 10), 50);
1642
+ const memDir = resolveMemoryDir(params.workspacePath);
1643
+ let files;
1644
+ try {
1645
+ files = await readdir(memDir);
1646
+ } catch {
1647
+ const msg = `Memory directory not found: ${memDir}`;
1648
+ if (callback) await callback({ text: msg });
1649
+ return {
1650
+ success: false,
1651
+ error: msg
1652
+ };
1653
+ }
1654
+ const results = [];
1655
+ for (const file of files) {
1656
+ if (!SUPPORTED_EXT.has(extname(file))) continue;
1657
+ const filePath = join(memDir, file);
1658
+ let content;
1659
+ try {
1660
+ content = await readFile(filePath, "utf-8");
1661
+ } catch {
1662
+ continue;
1663
+ }
1664
+ const relevance = score(content, query);
1665
+ if (relevance > 0) {
1666
+ const snippet = content.slice(0, 200).replace(/\n/g, " ").trim();
1667
+ results.push({
1668
+ name: basename(file, extname(file)),
1669
+ path: filePath,
1670
+ relevance,
1671
+ snippet: snippet.length < content.length ? `${snippet}...` : snippet
1672
+ });
1673
+ }
1674
+ }
1675
+ results.sort((a, b) => b.relevance - a.relevance);
1676
+ const top = results.slice(0, limit);
1677
+ if (top.length === 0) {
1678
+ const msg = `No memory entries found matching "${query}"`;
1679
+ if (callback) await callback({ text: msg });
1680
+ return {
1681
+ success: true,
1682
+ text: msg
1683
+ };
1684
+ }
1685
+ const output = top.map((r, i) => `${i + 1}. **${r.name}** (${(r.relevance * 100).toFixed(0)}%)\n ${r.snippet}`).join("\n\n");
1686
+ const text = `Found ${top.length} memory entries:\n\n${output}`;
1687
+ if (callback) await callback({ text });
1688
+ return {
1689
+ success: true,
1690
+ text,
1691
+ data: { results: top }
1692
+ };
1693
+ }
1694
+ };
1695
+ const memoryGetAction = {
1696
+ name: "memory_get",
1697
+ description: "Retrieve a specific memory file by name.",
1698
+ similes: [
1699
+ "MEMORY_GET",
1700
+ "GET_MEMORY",
1701
+ "READ_MEMORY"
1702
+ ],
1703
+ examples: [],
1704
+ validate: async (_runtime, message) => {
1705
+ const text = message.content?.text;
1706
+ return typeof text === "string" && text.length > 0;
1707
+ },
1708
+ handler: async (_runtime, message, _state, options, callback) => {
1709
+ const params = options ?? {};
1710
+ const name = params.name ?? message.content?.text ?? "";
1711
+ const memDir = resolveMemoryDir(params.workspacePath);
1712
+ const candidates = [
1713
+ name,
1714
+ `${name}.md`,
1715
+ `${name}.txt`,
1716
+ `${name}.json`
1717
+ ];
1718
+ for (const candidate of candidates) {
1719
+ const filePath = candidate.startsWith("/") ? candidate : join(memDir, candidate);
1720
+ let content;
1721
+ try {
1722
+ content = await readFile(filePath, "utf-8");
1723
+ } catch {
1724
+ continue;
1725
+ }
1726
+ const text = `# Memory: ${basename(filePath)}\n\n${content}`;
1727
+ if (callback) await callback({ text });
1728
+ return {
1729
+ success: true,
1730
+ text,
1731
+ data: {
1732
+ name: basename(filePath),
1733
+ content,
1734
+ path: filePath
1735
+ }
1736
+ };
1737
+ }
1738
+ let hint = "";
1739
+ try {
1740
+ const available = (await readdir(memDir)).filter((f) => SUPPORTED_EXT.has(extname(f)));
1741
+ if (available.length > 0) hint = `\n\nAvailable: ${available.slice(0, 10).join(", ")}`;
1742
+ } catch {}
1743
+ const msg = `Memory not found: "${name}"${hint}`;
1744
+ if (callback) await callback({ text: msg });
1745
+ return {
1746
+ success: false,
1747
+ error: msg,
1748
+ text: msg
1749
+ };
1750
+ }
1751
+ };
1752
+
1753
+ //#endregion
1754
+ //#region src/milaidy-plugin.ts
1755
+ function createMilaidyPlugin(config) {
1756
+ const workspaceDir = config?.workspaceDir ?? DEFAULT_AGENT_WORKSPACE_DIR;
1757
+ const agentId = config?.agentId ?? "main";
1758
+ const sessionStorePath = config?.sessionStorePath ?? resolveDefaultSessionStorePath(agentId);
1759
+ return {
1760
+ name: "milaidy",
1761
+ description: "Milaidy workspace context, session keys, and compaction",
1762
+ providers: [
1763
+ createWorkspaceProvider({
1764
+ workspaceDir,
1765
+ maxCharsPerFile: config?.bootstrapMaxChars
1766
+ }),
1767
+ createSessionKeyProvider({ defaultAgentId: agentId }),
1768
+ ...getSessionProviders({ storePath: sessionStorePath })
1769
+ ],
1770
+ actions: [
1771
+ compactAction,
1772
+ memorySearchAction,
1773
+ memoryGetAction
1774
+ ],
1775
+ events: { MESSAGE_RECEIVED: [async (payload) => {
1776
+ const { runtime, message } = payload;
1777
+ if (!message || !runtime) return;
1778
+ const meta = message.metadata ?? {};
1779
+ if (meta.sessionKey) return;
1780
+ const room = await runtime.getRoom(message.roomId);
1781
+ if (!room) return;
1782
+ const key = resolveSessionKeyFromRoom(agentId, room, {
1783
+ threadId: meta.threadId,
1784
+ groupId: meta.groupId,
1785
+ channel: meta.channel ?? room.source
1786
+ });
1787
+ message.metadata.sessionKey = key;
1788
+ }] }
1789
+ };
1790
+ }
1791
+ const milaidyPlugin = createMilaidyPlugin();
1792
+
1793
+ //#endregion
1794
+ //#region src/eliza.ts
1795
+ /**
1796
+ * ElizaOS runtime entry point for Milaidy.
1797
+ *
1798
+ * Starts the ElizaOS agent runtime with Milaidy's plugin configuration.
1799
+ * Can be run directly via: node --import tsx src/eliza.ts
1800
+ * Or via the CLI: milaidy start
1801
+ *
1802
+ * @module eliza
1803
+ */
1804
+ /**
1805
+ * Maps Milaidy channel config fields to the environment variable names
1806
+ * that ElizaOS plugins expect.
1807
+ *
1808
+ * Milaidy stores channel credentials under `config.channels.<name>.<field>`,
1809
+ * while ElizaOS plugins read them from process.env.
1810
+ */
1811
+ const CHANNEL_ENV_MAP = {
1812
+ discord: { token: "DISCORD_BOT_TOKEN" },
1813
+ telegram: { botToken: "TELEGRAM_BOT_TOKEN" },
1814
+ slack: {
1815
+ botToken: "SLACK_BOT_TOKEN",
1816
+ appToken: "SLACK_APP_TOKEN",
1817
+ userToken: "SLACK_USER_TOKEN"
1818
+ },
1819
+ signal: { account: "SIGNAL_ACCOUNT" },
1820
+ msteams: {
1821
+ appId: "MSTEAMS_APP_ID",
1822
+ appPassword: "MSTEAMS_APP_PASSWORD"
1823
+ },
1824
+ mattermost: {
1825
+ botToken: "MATTERMOST_BOT_TOKEN",
1826
+ baseUrl: "MATTERMOST_BASE_URL"
1827
+ },
1828
+ googlechat: { serviceAccountKey: "GOOGLE_CHAT_SERVICE_ACCOUNT_KEY" }
1829
+ };
1830
+ /** Core plugins that should always be loaded. */
1831
+ const CORE_PLUGINS = [
1832
+ "@elizaos/plugin-sql",
1833
+ "@elizaos/plugin-agent-skills",
1834
+ "@elizaos/plugin-directives",
1835
+ "@elizaos/plugin-commands",
1836
+ "@elizaos/plugin-shell",
1837
+ "@elizaos/plugin-personality",
1838
+ "@elizaos/plugin-experience",
1839
+ "@elizaos/plugin-form"
1840
+ ];
1841
+ /** Maps Milaidy channel names to ElizaOS plugin package names. */
1842
+ const CHANNEL_PLUGIN_MAP = {
1843
+ discord: "@elizaos/plugin-discord",
1844
+ telegram: "@elizaos/plugin-telegram",
1845
+ slack: "@elizaos/plugin-slack",
1846
+ whatsapp: "@elizaos/plugin-whatsapp",
1847
+ signal: "@elizaos/plugin-signal",
1848
+ imessage: "@elizaos/plugin-imessage",
1849
+ bluebubbles: "@elizaos/plugin-bluebubbles",
1850
+ msteams: "@elizaos/plugin-msteams",
1851
+ mattermost: "@elizaos/plugin-mattermost",
1852
+ googlechat: "@elizaos/plugin-google-chat"
1853
+ };
1854
+ /** Maps environment variable names to model-provider plugin packages. */
1855
+ const PROVIDER_PLUGIN_MAP = {
1856
+ ANTHROPIC_API_KEY: "@elizaos/plugin-anthropic",
1857
+ OPENAI_API_KEY: "@elizaos/plugin-openai",
1858
+ GOOGLE_API_KEY: "@elizaos/plugin-google-genai",
1859
+ GOOGLE_GENERATIVE_AI_API_KEY: "@elizaos/plugin-google-genai",
1860
+ GROQ_API_KEY: "@elizaos/plugin-groq",
1861
+ XAI_API_KEY: "@elizaos/plugin-xai",
1862
+ OPENROUTER_API_KEY: "@elizaos/plugin-openrouter",
1863
+ OLLAMA_BASE_URL: "@elizaos/plugin-ollama"
1864
+ };
1865
+ /** Optional feature plugins keyed by feature name. */
1866
+ const OPTIONAL_PLUGIN_MAP = {};
1867
+ function looksLikePlugin(value) {
1868
+ if (!value || typeof value !== "object") return false;
1869
+ const obj = value;
1870
+ return typeof obj.name === "string" && typeof obj.description === "string";
1871
+ }
1872
+ function extractPlugin(mod) {
1873
+ if (looksLikePlugin(mod.default)) return mod.default;
1874
+ if (looksLikePlugin(mod.plugin)) return mod.plugin;
1875
+ if (looksLikePlugin(mod)) return mod;
1876
+ return null;
1877
+ }
1878
+ /**
1879
+ * Collect the set of plugin package names that should be loaded
1880
+ * based on config, environment variables, and feature flags.
1881
+ */
1882
+ function collectPluginNames(config) {
1883
+ const pluginsToLoad = new Set(CORE_PLUGINS);
1884
+ const channels = config.channels ?? {};
1885
+ for (const [channelName, channelConfig] of Object.entries(channels)) if (channelConfig && typeof channelConfig === "object") {
1886
+ const pluginName = CHANNEL_PLUGIN_MAP[channelName];
1887
+ if (pluginName) pluginsToLoad.add(pluginName);
1888
+ }
1889
+ for (const [envKey, pluginName] of Object.entries(PROVIDER_PLUGIN_MAP)) if (process$1.env[envKey]) pluginsToLoad.add(pluginName);
1890
+ const pluginsConfig = config.plugins;
1891
+ if (pluginsConfig?.entries) {
1892
+ for (const [key, entry] of Object.entries(pluginsConfig.entries)) if (entry && typeof entry === "object" && entry.enabled !== false) {
1893
+ const pluginName = OPTIONAL_PLUGIN_MAP[key];
1894
+ if (pluginName) pluginsToLoad.add(pluginName);
1895
+ }
1896
+ }
1897
+ const features = config.features;
1898
+ if (features && typeof features === "object") {
1899
+ for (const [featureName, featureValue] of Object.entries(features)) if (featureValue === true || typeof featureValue === "object" && featureValue !== null && featureValue.enabled !== false) {
1900
+ const pluginName = OPTIONAL_PLUGIN_MAP[featureName];
1901
+ if (pluginName) pluginsToLoad.add(pluginName);
1902
+ }
1903
+ }
1904
+ return pluginsToLoad;
1905
+ }
1906
+ /**
1907
+ * Resolve Milaidy plugins from config and auto-enable logic.
1908
+ * Returns an array of ElizaOS Plugin instances ready for AgentRuntime.
1909
+ */
1910
+ async function resolvePlugins(config) {
1911
+ const plugins = [];
1912
+ const autoEnableResult = applyPluginAutoEnable({
1913
+ config,
1914
+ env: process$1.env
1915
+ });
1916
+ for (const change of autoEnableResult.changes) logger.info(`[milaidy] ${change}`);
1917
+ const pluginsToLoad = collectPluginNames(config);
1918
+ for (const pluginName of pluginsToLoad) try {
1919
+ const pluginInstance = extractPlugin(await import(pluginName));
1920
+ if (pluginInstance) {
1921
+ plugins.push({
1922
+ name: pluginName,
1923
+ plugin: pluginInstance
1924
+ });
1925
+ logger.info(`[milaidy] Loaded plugin: ${pluginName}`);
1926
+ } else logger.warn(`[milaidy] Plugin ${pluginName} did not export a valid Plugin object`);
1927
+ } catch (err) {
1928
+ const msg = err instanceof Error ? err.message : String(err);
1929
+ logger.warn(`[milaidy] Could not load plugin ${pluginName}: ${msg}`);
1930
+ }
1931
+ return plugins;
1932
+ }
1933
+ /**
1934
+ * Propagate channel credentials from Milaidy config into process.env so
1935
+ * that ElizaOS plugins can find them.
1936
+ */
1937
+ function applyChannelSecretsToEnv(config) {
1938
+ const channels = config.channels ?? {};
1939
+ for (const [channelName, channelConfig] of Object.entries(channels)) {
1940
+ if (!channelConfig || typeof channelConfig !== "object") continue;
1941
+ const envMap = CHANNEL_ENV_MAP[channelName];
1942
+ if (!envMap) continue;
1943
+ const configObj = channelConfig;
1944
+ for (const [configField, envKey] of Object.entries(envMap)) {
1945
+ const value = configObj[configField];
1946
+ if (typeof value === "string" && value.trim() && !process$1.env[envKey]) process$1.env[envKey] = value;
1947
+ }
1948
+ }
1949
+ }
1950
+ /**
1951
+ * Build an ElizaOS Character from the Milaidy config.
1952
+ *
1953
+ * Merges the deprecated `config.agent` object and the newer
1954
+ * `config.agents.defaults` into a single Character, collecting
1955
+ * secrets from environment variables along the way.
1956
+ */
1957
+ function buildCharacterFromConfig(config) {
1958
+ const legacyAgent = config.agent;
1959
+ const name = legacyAgent?.name ?? config.ui?.assistant?.name ?? "Milaidy";
1960
+ const bio = legacyAgent?.bio ?? "An AI assistant powered by Milaidy and ElizaOS.";
1961
+ const systemPrompt = legacyAgent?.system_prompt;
1962
+ const secretKeys = [
1963
+ "ANTHROPIC_API_KEY",
1964
+ "OPENAI_API_KEY",
1965
+ "GOOGLE_API_KEY",
1966
+ "GOOGLE_GENERATIVE_AI_API_KEY",
1967
+ "GROQ_API_KEY",
1968
+ "XAI_API_KEY",
1969
+ "OPENROUTER_API_KEY",
1970
+ "OLLAMA_BASE_URL",
1971
+ "DISCORD_BOT_TOKEN",
1972
+ "TELEGRAM_BOT_TOKEN",
1973
+ "SLACK_BOT_TOKEN",
1974
+ "SLACK_APP_TOKEN",
1975
+ "SLACK_USER_TOKEN",
1976
+ "SIGNAL_ACCOUNT",
1977
+ "MSTEAMS_APP_ID",
1978
+ "MSTEAMS_APP_PASSWORD",
1979
+ "MATTERMOST_BOT_TOKEN",
1980
+ "MATTERMOST_BASE_URL"
1981
+ ];
1982
+ const secrets = {};
1983
+ for (const key of secretKeys) {
1984
+ const value = process$1.env[key];
1985
+ if (value && value.trim()) secrets[key] = value;
1986
+ }
1987
+ return createCharacter({
1988
+ name,
1989
+ bio,
1990
+ system: systemPrompt,
1991
+ secrets
1992
+ });
1993
+ }
1994
+ /**
1995
+ * Resolve the primary model identifier from Milaidy config.
1996
+ *
1997
+ * Milaidy stores the model under `agents.defaults.model.primary` as an
1998
+ * AgentModelListConfig object. Returns undefined when no model is
1999
+ * explicitly configured (ElizaOS falls back to whichever model
2000
+ * plugin is loaded).
2001
+ */
2002
+ function resolvePrimaryModel(config) {
2003
+ const modelConfig = config.agents?.defaults?.model;
2004
+ if (!modelConfig) return void 0;
2005
+ return modelConfig.primary;
2006
+ }
2007
+ /**
2008
+ * Detect whether this is the first run (no agent name configured)
2009
+ * and prompt the user to pick a name for their agent.
2010
+ *
2011
+ * Saves the chosen name, a default bio, and a basic system prompt
2012
+ * back to the Milaidy config so subsequent runs skip this step.
2013
+ */
2014
+ async function runFirstTimeSetup(config) {
2015
+ if (Boolean(config.agent?.name || config.ui?.assistant?.name)) return config;
2016
+ if (!process$1.stdin.isTTY) return config;
2017
+ const rl = readline.createInterface({
2018
+ input: process$1.stdin,
2019
+ output: process$1.stdout
2020
+ });
2021
+ const ask = (question) => new Promise((resolve) => {
2022
+ rl.question(question, (answer) => resolve(answer));
2023
+ });
2024
+ console.log("");
2025
+ const answer = await ask(" What should your agent be called? (Milaidy) ");
2026
+ rl.close();
2027
+ const name = answer.trim() || "Milaidy";
2028
+ const updated = {
2029
+ ...config,
2030
+ agent: {
2031
+ ...config.agent,
2032
+ name,
2033
+ bio: `An autonomous agent`,
2034
+ system_prompt: `You are ${name}, an autonomous AI agent powered by ElizaOS. You are helpful, concise, and proactive.`
2035
+ }
2036
+ };
2037
+ saveMilaidyConfig(updated);
2038
+ console.log(` Agent "${name}" created.\n`);
2039
+ return updated;
2040
+ }
2041
+ /**
2042
+ * Start the ElizaOS runtime with Milaidy's configuration.
2043
+ */
2044
+ async function startEliza() {
2045
+ logger.info(`Milaidy v${VERSION} — starting ElizaOS runtime`);
2046
+ let config;
2047
+ try {
2048
+ config = await loadMilaidyConfig();
2049
+ } catch {
2050
+ logger.warn("[milaidy] No config found, using defaults");
2051
+ config = {};
2052
+ }
2053
+ config = await runFirstTimeSetup(config);
2054
+ applyChannelSecretsToEnv(config);
2055
+ const character = buildCharacterFromConfig(config);
2056
+ logger.info(`[milaidy] Agent character: ${character.name ?? "Milaidy"}`);
2057
+ const primaryModel = resolvePrimaryModel(config);
2058
+ if (primaryModel) logger.info(`[milaidy] Primary model: ${primaryModel}`);
2059
+ const workspaceDir = config.agents?.defaults?.workspace ?? resolveDefaultAgentWorkspaceDir();
2060
+ logger.info(`[milaidy] Agent workspace: ${workspaceDir}`);
2061
+ await ensureAgentWorkspace({
2062
+ dir: workspaceDir,
2063
+ ensureBootstrapFiles: true
2064
+ });
2065
+ const agentId = character.name?.toLowerCase().replace(/\s+/g, "-") ?? "main";
2066
+ const milaidyPlugin = createMilaidyPlugin({
2067
+ workspaceDir,
2068
+ bootstrapMaxChars: config.agents?.defaults?.bootstrapMaxChars,
2069
+ agentId
2070
+ });
2071
+ const resolvedPlugins = await resolvePlugins(config);
2072
+ logger.info(`[milaidy] Resolved ${resolvedPlugins.length} plugins`);
2073
+ logger.info(`[milaidy] Plugins: ${resolvedPlugins.map((p) => p.name.replace("@elizaos/", "")).join(", ")}`);
2074
+ if (resolvedPlugins.length === 0) {
2075
+ logger.error("[milaidy] No plugins loaded — at least one model provider plugin is required");
2076
+ logger.error("[milaidy] Set an API key (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY) in your environment");
2077
+ throw new Error("No plugins loaded");
2078
+ }
2079
+ const sqlPlugin = resolvedPlugins.find((p) => p.name === "@elizaos/plugin-sql");
2080
+ const runtime = new AgentRuntime({
2081
+ character,
2082
+ plugins: [milaidyPlugin, ...resolvedPlugins.filter((p) => p.name !== "@elizaos/plugin-sql").map((p) => p.plugin)],
2083
+ settings: { ...primaryModel ? { MODEL_PROVIDER: primaryModel } : {} }
2084
+ });
2085
+ if (sqlPlugin) await runtime.registerPlugin(sqlPlugin.plugin);
2086
+ logger.info("[milaidy] Initializing AgentRuntime...");
2087
+ await runtime.initialize();
2088
+ logger.info("[milaidy] AgentRuntime initialized successfully");
2089
+ let isShuttingDown = false;
2090
+ const shutdown = async () => {
2091
+ if (isShuttingDown) return;
2092
+ isShuttingDown = true;
2093
+ logger.info("[milaidy] Shutting down...");
2094
+ try {
2095
+ await runtime.stop();
2096
+ } catch (err) {
2097
+ const msg = err instanceof Error ? err.message : String(err);
2098
+ logger.warn(`[milaidy] Error during shutdown: ${msg}`);
2099
+ }
2100
+ process$1.exit(0);
2101
+ };
2102
+ process$1.on("SIGINT", () => void shutdown());
2103
+ process$1.on("SIGTERM", () => void shutdown());
2104
+ try {
2105
+ const internalHooksConfig = config.hooks?.internal;
2106
+ const hooksResult = await loadHooks({
2107
+ workspacePath: workspaceDir,
2108
+ internalConfig: internalHooksConfig,
2109
+ milaidyConfig: config
2110
+ });
2111
+ if (hooksResult.registered > 0) logger.info(`[milaidy] Hooks: ${hooksResult.registered} registered`);
2112
+ await triggerHook(createHookEvent("gateway", "startup", "system", { cfg: config }));
2113
+ } catch (err) {
2114
+ const msg = err instanceof Error ? err.message : String(err);
2115
+ logger.warn(`[milaidy] Hooks system could not load: ${msg}`);
2116
+ }
2117
+ logger.info("[milaidy] Runtime is ready.");
2118
+ const agentName = character.name ?? "Milaidy";
2119
+ const userId = crypto.randomUUID();
2120
+ const roomId = stringToUuid(`${agentName}-chat-room`);
2121
+ const worldId = stringToUuid(`${agentName}-chat-world`);
2122
+ try {
2123
+ await runtime.ensureConnection({
2124
+ entityId: userId,
2125
+ roomId,
2126
+ worldId,
2127
+ userName: "User",
2128
+ source: "cli",
2129
+ channelId: `${agentName}-chat`,
2130
+ type: ChannelType.DM
2131
+ });
2132
+ } catch (err) {
2133
+ const msg = err instanceof Error ? err.message : String(err);
2134
+ logger.warn(`[milaidy] Could not establish chat room, retrying with fresh IDs: ${msg}`);
2135
+ const freshRoomId = crypto.randomUUID();
2136
+ const freshWorldId = crypto.randomUUID();
2137
+ await runtime.ensureConnection({
2138
+ entityId: userId,
2139
+ roomId: freshRoomId,
2140
+ worldId: freshWorldId,
2141
+ userName: "User",
2142
+ source: "cli",
2143
+ channelId: `${agentName}-chat`,
2144
+ type: ChannelType.DM
2145
+ });
2146
+ }
2147
+ const rl = readline.createInterface({
2148
+ input: process$1.stdin,
2149
+ output: process$1.stdout
2150
+ });
2151
+ console.log(`\nšŸ’¬ Chat with ${agentName} (type 'exit' to quit)\n`);
2152
+ const prompt = () => {
2153
+ rl.question("You: ", async (input) => {
2154
+ const text = input.trim();
2155
+ if (text.toLowerCase() === "exit" || text.toLowerCase() === "quit") {
2156
+ console.log("\nGoodbye!");
2157
+ rl.close();
2158
+ await runtime.stop();
2159
+ process$1.exit(0);
2160
+ }
2161
+ if (!text) {
2162
+ prompt();
2163
+ return;
2164
+ }
2165
+ const message = createMessageMemory({
2166
+ id: crypto.randomUUID(),
2167
+ entityId: userId,
2168
+ roomId,
2169
+ content: {
2170
+ text,
2171
+ source: "client_chat",
2172
+ channelType: ChannelType.DM
2173
+ }
2174
+ });
2175
+ process$1.stdout.write(`${agentName}: `);
2176
+ await runtime?.messageService?.handleMessage(runtime, message, async (content) => {
2177
+ if (content?.text) process$1.stdout.write(content.text);
2178
+ return [];
2179
+ });
2180
+ console.log("\n");
2181
+ prompt();
2182
+ });
2183
+ };
2184
+ prompt();
2185
+ }
2186
+ 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) => {
2187
+ console.error("[milaidy] Fatal error:", err instanceof Error ? err.stack ?? err.message : err);
2188
+ process$1.exit(1);
2189
+ });
2190
+
2191
+ //#endregion
2192
+ export { startEliza };