miladyai 2.0.0-alpha.27

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 (241) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +7 -0
  2. package/dist/actions/emote.js +64 -0
  3. package/dist/actions/restart.js +81 -0
  4. package/dist/actions/send-message.js +152 -0
  5. package/dist/agent-admin-routes.js +82 -0
  6. package/dist/agent-lifecycle-routes.js +79 -0
  7. package/dist/agent-transfer-routes.js +102 -0
  8. package/dist/api/agent-admin-routes.js +82 -0
  9. package/dist/api/agent-lifecycle-routes.js +79 -0
  10. package/dist/api/agent-transfer-routes.js +102 -0
  11. package/dist/api/apps-hyperscape-routes.js +58 -0
  12. package/dist/api/apps-routes.js +114 -0
  13. package/dist/api/auth-routes.js +56 -0
  14. package/dist/api/autonomy-routes.js +44 -0
  15. package/dist/api/bug-report-routes.js +111 -0
  16. package/dist/api/character-routes.js +195 -0
  17. package/dist/api/cloud-routes.js +330 -0
  18. package/dist/api/cloud-status-routes.js +155 -0
  19. package/dist/api/compat-utils.js +111 -0
  20. package/dist/api/database.js +735 -0
  21. package/dist/api/diagnostics-routes.js +205 -0
  22. package/dist/api/drop-service.js +134 -0
  23. package/dist/api/early-logs.js +86 -0
  24. package/dist/api/http-helpers.js +131 -0
  25. package/dist/api/knowledge-routes.js +534 -0
  26. package/dist/api/memory-bounds.js +71 -0
  27. package/dist/api/models-routes.js +28 -0
  28. package/dist/api/og-tracker.js +36 -0
  29. package/dist/api/permissions-routes.js +109 -0
  30. package/dist/api/plugin-validation.js +198 -0
  31. package/dist/api/provider-switch-config.js +41 -0
  32. package/dist/api/registry-routes.js +86 -0
  33. package/dist/api/registry-service.js +164 -0
  34. package/dist/api/sandbox-routes.js +1112 -0
  35. package/dist/api/server.js +7949 -0
  36. package/dist/api/subscription-routes.js +172 -0
  37. package/dist/api/terminal-run-limits.js +24 -0
  38. package/dist/api/training-routes.js +158 -0
  39. package/dist/api/trajectory-routes.js +300 -0
  40. package/dist/api/trigger-routes.js +246 -0
  41. package/dist/api/twitter-verify.js +134 -0
  42. package/dist/api/tx-service.js +108 -0
  43. package/dist/api/wallet-routes.js +266 -0
  44. package/dist/api/wallet.js +568 -0
  45. package/dist/api/whatsapp-routes.js +182 -0
  46. package/dist/api/zip-utils.js +109 -0
  47. package/dist/apps-hyperscape-routes.js +58 -0
  48. package/dist/apps-routes.js +114 -0
  49. package/dist/ascii.js +20 -0
  50. package/dist/auth/anthropic.js +44 -0
  51. package/dist/auth/apply-stealth.js +41 -0
  52. package/dist/auth/claude-code-stealth.js +78 -0
  53. package/dist/auth/credentials.js +156 -0
  54. package/dist/auth/index.js +5 -0
  55. package/dist/auth/openai-codex.js +66 -0
  56. package/dist/auth/types.js +9 -0
  57. package/dist/auth-routes.js +56 -0
  58. package/dist/autonomy-routes.js +44 -0
  59. package/dist/bug-report-routes.js +111 -0
  60. package/dist/build-info.json +6 -0
  61. package/dist/character-routes.js +195 -0
  62. package/dist/cli/argv.js +63 -0
  63. package/dist/cli/banner.js +34 -0
  64. package/dist/cli/cli-name.js +21 -0
  65. package/dist/cli/cli-utils.js +16 -0
  66. package/dist/cli/git-commit.js +78 -0
  67. package/dist/cli/parse-duration.js +15 -0
  68. package/dist/cli/plugins-cli.js +590 -0
  69. package/dist/cli/profile-utils.js +9 -0
  70. package/dist/cli/profile.js +95 -0
  71. package/dist/cli/program/build-program.js +17 -0
  72. package/dist/cli/program/command-registry.js +23 -0
  73. package/dist/cli/program/help.js +47 -0
  74. package/dist/cli/program/preaction.js +33 -0
  75. package/dist/cli/program/register.config.js +106 -0
  76. package/dist/cli/program/register.configure.js +20 -0
  77. package/dist/cli/program/register.dashboard.js +124 -0
  78. package/dist/cli/program/register.models.js +23 -0
  79. package/dist/cli/program/register.setup.js +36 -0
  80. package/dist/cli/program/register.start.js +22 -0
  81. package/dist/cli/program/register.subclis.js +70 -0
  82. package/dist/cli/program/register.tui.js +163 -0
  83. package/dist/cli/program/register.update.js +154 -0
  84. package/dist/cli/program.js +3 -0
  85. package/dist/cli/run-main.js +37 -0
  86. package/dist/cli/version.js +7 -0
  87. package/dist/cloud/validate-url.js +93 -0
  88. package/dist/cloud-routes.js +330 -0
  89. package/dist/cloud-status-routes.js +155 -0
  90. package/dist/compat-utils.js +111 -0
  91. package/dist/config/config.js +69 -0
  92. package/dist/config/env-vars.js +19 -0
  93. package/dist/config/includes.js +121 -0
  94. package/dist/config/object-utils.js +7 -0
  95. package/dist/config/paths.js +38 -0
  96. package/dist/config/plugin-auto-enable.js +231 -0
  97. package/dist/config/schema.js +864 -0
  98. package/dist/config/telegram-custom-commands.js +76 -0
  99. package/dist/config/zod-schema.agent-runtime.js +519 -0
  100. package/dist/config/zod-schema.core.js +538 -0
  101. package/dist/config/zod-schema.hooks.js +103 -0
  102. package/dist/config/zod-schema.js +488 -0
  103. package/dist/config/zod-schema.providers-core.js +785 -0
  104. package/dist/config/zod-schema.session.js +73 -0
  105. package/dist/core-plugins.js +37 -0
  106. package/dist/custom-actions.js +250 -0
  107. package/dist/database.js +735 -0
  108. package/dist/diagnostics/integration-observability.js +57 -0
  109. package/dist/diagnostics-routes.js +205 -0
  110. package/dist/drop-service.js +134 -0
  111. package/dist/early-logs.js +24 -0
  112. package/dist/eliza.js +2061 -0
  113. package/dist/emotes/catalog.js +271 -0
  114. package/dist/entry.js +40 -0
  115. package/dist/hooks/discovery.js +167 -0
  116. package/dist/hooks/eligibility.js +64 -0
  117. package/dist/hooks/index.js +4 -0
  118. package/dist/hooks/loader.js +147 -0
  119. package/dist/hooks/registry.js +55 -0
  120. package/dist/http-helpers.js +131 -0
  121. package/dist/index.js +49 -0
  122. package/dist/knowledge-routes.js +534 -0
  123. package/dist/memory-bounds.js +71 -0
  124. package/dist/milady-plugin.js +90 -0
  125. package/dist/models-routes.js +28 -0
  126. package/dist/onboarding-names.js +78 -0
  127. package/dist/onboarding-presets.js +922 -0
  128. package/dist/package.json +1 -0
  129. package/dist/permissions-routes.js +109 -0
  130. package/dist/plugin-validation.js +107 -0
  131. package/dist/plugins/whatsapp/actions.js +91 -0
  132. package/dist/plugins/whatsapp/index.js +16 -0
  133. package/dist/plugins/whatsapp/service.js +270 -0
  134. package/dist/provider-switch-config.js +41 -0
  135. package/dist/providers/admin-trust.js +46 -0
  136. package/dist/providers/autonomous-state.js +101 -0
  137. package/dist/providers/session-bridge.js +86 -0
  138. package/dist/providers/session-utils.js +36 -0
  139. package/dist/providers/simple-mode.js +50 -0
  140. package/dist/providers/ui-catalog.js +15 -0
  141. package/dist/providers/workspace-provider.js +93 -0
  142. package/dist/providers/workspace.js +348 -0
  143. package/dist/registry-routes.js +86 -0
  144. package/dist/registry-service.js +164 -0
  145. package/dist/restart.js +40 -0
  146. package/dist/runtime/core-plugins.js +37 -0
  147. package/dist/runtime/custom-actions.js +250 -0
  148. package/dist/runtime/eliza.js +2061 -0
  149. package/dist/runtime/embedding-manager-support.js +185 -0
  150. package/dist/runtime/embedding-manager.js +193 -0
  151. package/dist/runtime/embedding-presets.js +54 -0
  152. package/dist/runtime/embedding-state.js +8 -0
  153. package/dist/runtime/milady-plugin.js +90 -0
  154. package/dist/runtime/onboarding-names.js +78 -0
  155. package/dist/runtime/restart.js +40 -0
  156. package/dist/runtime/version.js +7 -0
  157. package/dist/sandbox-routes.js +1112 -0
  158. package/dist/security/audit-log.js +149 -0
  159. package/dist/security/network-policy.js +70 -0
  160. package/dist/server.js +7949 -0
  161. package/dist/services/agent-export.js +559 -0
  162. package/dist/services/app-manager.js +389 -0
  163. package/dist/services/browser-capture.js +86 -0
  164. package/dist/services/fallback-training-service.js +128 -0
  165. package/dist/services/mcp-marketplace.js +134 -0
  166. package/dist/services/plugin-installer.js +396 -0
  167. package/dist/services/plugin-manager-types.js +15 -0
  168. package/dist/services/registry-client-app-meta.js +144 -0
  169. package/dist/services/registry-client-endpoints.js +166 -0
  170. package/dist/services/registry-client-local.js +271 -0
  171. package/dist/services/registry-client-network.js +93 -0
  172. package/dist/services/registry-client-queries.js +70 -0
  173. package/dist/services/registry-client.js +157 -0
  174. package/dist/services/sandbox-engine.js +511 -0
  175. package/dist/services/sandbox-manager.js +297 -0
  176. package/dist/services/self-updater.js +175 -0
  177. package/dist/services/skill-catalog-client.js +119 -0
  178. package/dist/services/skill-marketplace.js +521 -0
  179. package/dist/services/stream-manager.js +236 -0
  180. package/dist/services/update-checker.js +121 -0
  181. package/dist/services/update-notifier.js +29 -0
  182. package/dist/services/version-compat.js +78 -0
  183. package/dist/services/whatsapp-pairing.js +196 -0
  184. package/dist/shared/ui-catalog-prompt.js +728 -0
  185. package/dist/subscription-routes.js +172 -0
  186. package/dist/terminal/links.js +19 -0
  187. package/dist/terminal/palette.js +14 -0
  188. package/dist/terminal/theme.js +25 -0
  189. package/dist/terminal-run-limits.js +24 -0
  190. package/dist/training-routes.js +158 -0
  191. package/dist/trajectory-routes.js +300 -0
  192. package/dist/trigger-routes.js +246 -0
  193. package/dist/triggers/action.js +218 -0
  194. package/dist/triggers/runtime.js +281 -0
  195. package/dist/triggers/scheduling.js +295 -0
  196. package/dist/triggers/types.js +5 -0
  197. package/dist/tui/components/assistant-message.js +76 -0
  198. package/dist/tui/components/chat-editor.js +34 -0
  199. package/dist/tui/components/embeddings-overlay.js +46 -0
  200. package/dist/tui/components/footer.js +60 -0
  201. package/dist/tui/components/index.js +15 -0
  202. package/dist/tui/components/modal-frame.js +45 -0
  203. package/dist/tui/components/modal-style.js +15 -0
  204. package/dist/tui/components/model-selector.js +70 -0
  205. package/dist/tui/components/pinned-chat-layout.js +46 -0
  206. package/dist/tui/components/plugins-endpoints-tab.js +196 -0
  207. package/dist/tui/components/plugins-installed-tab-view.js +69 -0
  208. package/dist/tui/components/plugins-installed-tab.js +319 -0
  209. package/dist/tui/components/plugins-overlay-catalog.js +81 -0
  210. package/dist/tui/components/plugins-overlay-data-api.js +21 -0
  211. package/dist/tui/components/plugins-overlay-data-shared.js +20 -0
  212. package/dist/tui/components/plugins-overlay-data.js +323 -0
  213. package/dist/tui/components/plugins-overlay.js +117 -0
  214. package/dist/tui/components/plugins-store-tab.js +148 -0
  215. package/dist/tui/components/settings-overlay.js +61 -0
  216. package/dist/tui/components/status-bar.js +64 -0
  217. package/dist/tui/components/tool-execution.js +68 -0
  218. package/dist/tui/components/user-message.js +22 -0
  219. package/dist/tui/eliza-tui-bridge.js +606 -0
  220. package/dist/tui/index.js +370 -0
  221. package/dist/tui/modal-presets.js +33 -0
  222. package/dist/tui/model-spec.js +46 -0
  223. package/dist/tui/sse-parser.js +78 -0
  224. package/dist/tui/theme.js +110 -0
  225. package/dist/tui/titlebar-spinner.js +62 -0
  226. package/dist/tui/tui-app.js +311 -0
  227. package/dist/tui/ws-client.js +215 -0
  228. package/dist/twitter-verify.js +134 -0
  229. package/dist/tx-service.js +108 -0
  230. package/dist/utils/exec-safety.js +17 -0
  231. package/dist/utils/globals.js +20 -0
  232. package/dist/utils/milady-root.js +61 -0
  233. package/dist/utils/number-parsing.js +37 -0
  234. package/dist/version-resolver.js +37 -0
  235. package/dist/version.js +7 -0
  236. package/dist/wallet-routes.js +266 -0
  237. package/dist/wallet.js +568 -0
  238. package/dist/whatsapp-routes.js +182 -0
  239. package/dist/zip-utils.js +109 -0
  240. package/milady.mjs +14 -0
  241. package/package.json +111 -0
@@ -0,0 +1,236 @@
1
+ import { logger } from "@elizaos/core";
2
+ import { spawn } from "node:child_process";
3
+
4
+ //#region src/services/stream-manager.ts
5
+ /**
6
+ * Stream Manager — macOS-compatible RTMP streaming via FFmpeg.
7
+ *
8
+ * Supports three input modes:
9
+ * - "pipe": Receives JPEG frames via writeFrame() → FFmpeg stdin (image2pipe).
10
+ * Used for streaming Electron window contents captured with capturePage().
11
+ * - "avfoundation": macOS screen capture via avfoundation device.
12
+ * - "testsrc": Solid color test pattern (default fallback).
13
+ *
14
+ * Usage:
15
+ * import { streamManager } from "./services/stream-manager";
16
+ * await streamManager.start({ rtmpUrl, rtmpKey, inputMode: "pipe" });
17
+ * streamManager.writeFrame(jpegBuffer); // called from frame capture
18
+ * await streamManager.stop();
19
+ *
20
+ * @module services/stream-manager
21
+ */
22
+ const TAG = "[StreamManager]";
23
+ var StreamManager = class {
24
+ constructor() {
25
+ this.ffmpeg = null;
26
+ this._running = false;
27
+ this.startedAt = null;
28
+ this._frameCount = 0;
29
+ }
30
+ isRunning() {
31
+ return this._running;
32
+ }
33
+ getUptime() {
34
+ if (!this.startedAt) return 0;
35
+ return Math.floor((Date.now() - this.startedAt) / 1e3);
36
+ }
37
+ getHealth() {
38
+ return {
39
+ running: this._running,
40
+ ffmpegAlive: this.ffmpeg !== null && this.ffmpeg.exitCode === null && !this.ffmpeg.killed,
41
+ uptime: this.getUptime(),
42
+ frameCount: this._frameCount
43
+ };
44
+ }
45
+ /**
46
+ * Write a JPEG frame to FFmpeg's stdin (only works in "pipe" mode).
47
+ * Returns true if the frame was accepted.
48
+ */
49
+ writeFrame(jpegData) {
50
+ if (!this._running || !this.ffmpeg || !this.ffmpeg.stdin) return false;
51
+ if (this.ffmpeg.killed || this.ffmpeg.exitCode !== null) return false;
52
+ try {
53
+ this.ffmpeg.stdin.write(jpegData);
54
+ this._frameCount++;
55
+ if (this._frameCount % 150 === 0) logger.info(`${TAG} Piped ${this._frameCount} frames to FFmpeg`);
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ async start(config) {
62
+ if (this._running) {
63
+ logger.warn(`${TAG} Already running — stop first`);
64
+ return;
65
+ }
66
+ this._frameCount = 0;
67
+ const resolution = config.resolution || "1280x720";
68
+ const bitrate = config.bitrate || "2500k";
69
+ const framerate = config.framerate || 15;
70
+ const rtmpTarget = `${config.rtmpUrl}/${config.rtmpKey}`;
71
+ const bufsize = `${parseInt(bitrate, 10) * 2}k`;
72
+ const mode = config.inputMode || "testsrc";
73
+ const inputArgs = this.buildInputArgs(config, resolution, framerate);
74
+ const isPipe = mode === "pipe";
75
+ const isScreenCapture = mode === "avfoundation" || mode === "screen";
76
+ const ffmpegArgs = [
77
+ "-thread_queue_size",
78
+ "512",
79
+ ...inputArgs,
80
+ "-f",
81
+ "lavfi",
82
+ "-i",
83
+ "anullsrc=channel_layout=stereo:sample_rate=44100",
84
+ ...isScreenCapture ? ["-vf", `scale=${resolution.replace("x", ":")}:flags=fast_bilinear`] : [],
85
+ ...process.platform === "darwin" ? [
86
+ "-c:v",
87
+ "h264_videotoolbox",
88
+ "-realtime",
89
+ "1",
90
+ "-b:v",
91
+ bitrate,
92
+ "-maxrate",
93
+ bitrate,
94
+ "-bufsize",
95
+ bufsize
96
+ ] : [
97
+ "-c:v",
98
+ "libx264",
99
+ "-preset",
100
+ "veryfast",
101
+ "-tune",
102
+ "zerolatency",
103
+ "-b:v",
104
+ bitrate,
105
+ "-maxrate",
106
+ bitrate,
107
+ "-bufsize",
108
+ bufsize
109
+ ],
110
+ "-s",
111
+ resolution,
112
+ "-pix_fmt",
113
+ "yuv420p",
114
+ "-g",
115
+ "60",
116
+ "-c:a",
117
+ "aac",
118
+ "-b:a",
119
+ "128k",
120
+ "-f",
121
+ "flv",
122
+ rtmpTarget
123
+ ];
124
+ logger.info(`${TAG} Starting FFmpeg RTMP stream (mode=${mode}) to ${config.rtmpUrl}`);
125
+ logger.info(`${TAG} Resolution: ${resolution}, Bitrate: ${bitrate}, FPS: ${framerate}`);
126
+ this.ffmpeg = spawn("ffmpeg", ["-y", ...ffmpegArgs], { stdio: [
127
+ isPipe ? "pipe" : "ignore",
128
+ "pipe",
129
+ "pipe"
130
+ ] });
131
+ this.ffmpeg.stderr?.on("data", (chunk) => {
132
+ const line = chunk.toString().trim();
133
+ if (line) console.log(`[FFmpeg] ${line}`);
134
+ });
135
+ this.ffmpeg.on("exit", (code, signal) => {
136
+ if (this._running) {
137
+ logger.warn(`${TAG} FFmpeg exited unexpectedly (code=${code}, signal=${signal})`);
138
+ this._running = false;
139
+ this.startedAt = null;
140
+ }
141
+ });
142
+ if (isPipe && this.ffmpeg.stdin) this.ffmpeg.stdin.on("error", (err) => {
143
+ logger.warn(`${TAG} FFmpeg stdin error: ${err.message}`);
144
+ });
145
+ await new Promise((r) => setTimeout(r, 1500));
146
+ if (this.ffmpeg.exitCode !== null) {
147
+ const exitCode = this.ffmpeg.exitCode;
148
+ this.ffmpeg = null;
149
+ throw new Error(`${TAG} FFmpeg exited immediately with code ${exitCode}`);
150
+ }
151
+ this._running = true;
152
+ this.startedAt = Date.now();
153
+ logger.info(`${TAG} FFmpeg streaming to RTMP — stream should be live`);
154
+ }
155
+ async stop() {
156
+ const uptime = this.getUptime();
157
+ if (this.ffmpeg && !this.ffmpeg.killed && this.ffmpeg.exitCode === null) {
158
+ const ffmpegProc = this.ffmpeg;
159
+ if (ffmpegProc.stdin) try {
160
+ ffmpegProc.stdin.end();
161
+ } catch {}
162
+ ffmpegProc.kill("SIGTERM");
163
+ await Promise.race([new Promise((resolve) => ffmpegProc.on("exit", resolve)), new Promise((resolve) => setTimeout(resolve, 3e3))]);
164
+ if (ffmpegProc.exitCode === null) ffmpegProc.kill("SIGKILL");
165
+ }
166
+ this.ffmpeg = null;
167
+ this._running = false;
168
+ this.startedAt = null;
169
+ this._frameCount = 0;
170
+ logger.info(`${TAG} Stream stopped (uptime: ${uptime}s, frames: ${this._frameCount})`);
171
+ return { uptime };
172
+ }
173
+ buildInputArgs(config, resolution, framerate) {
174
+ switch (config.inputMode || "testsrc") {
175
+ case "pipe": return [
176
+ "-probesize",
177
+ "32",
178
+ "-analyzeduration",
179
+ "0",
180
+ "-f",
181
+ "image2pipe",
182
+ "-c:v",
183
+ "mjpeg",
184
+ "-framerate",
185
+ String(framerate),
186
+ "-i",
187
+ "pipe:0"
188
+ ];
189
+ case "avfoundation":
190
+ case "screen": {
191
+ const videoDevice = config.videoDevice || "3";
192
+ return [
193
+ "-f",
194
+ "avfoundation",
195
+ "-framerate",
196
+ String(framerate),
197
+ "-pixel_format",
198
+ "nv12",
199
+ "-capture_cursor",
200
+ "1",
201
+ "-i",
202
+ `${videoDevice}:none`
203
+ ];
204
+ }
205
+ case "file": {
206
+ const framePath = config.frameFile || "/tmp/milady-stream-frame.jpg";
207
+ return [
208
+ "-probesize",
209
+ "32",
210
+ "-analyzeduration",
211
+ "0",
212
+ "-loop",
213
+ "1",
214
+ "-f",
215
+ "image2",
216
+ "-c:v",
217
+ "mjpeg",
218
+ "-framerate",
219
+ String(framerate),
220
+ "-i",
221
+ framePath
222
+ ];
223
+ }
224
+ default: return [
225
+ "-f",
226
+ "lavfi",
227
+ "-i",
228
+ `color=c=0x1a1a2e:s=${resolution}:r=${framerate}`
229
+ ];
230
+ }
231
+ }
232
+ };
233
+ const streamManager = new StreamManager();
234
+
235
+ //#endregion
236
+ export { streamManager };
@@ -0,0 +1,121 @@
1
+ import { loadMiladyConfig, saveMiladyConfig } from "../config/config.js";
2
+ import { VERSION } from "../runtime/version.js";
3
+ import { compareSemver } from "./version-compat.js";
4
+
5
+ //#region src/services/update-checker.ts
6
+ /**
7
+ * Queries the npm registry for new miladyai versions on the user's
8
+ * configured release channel (stable/beta/nightly).
9
+ */
10
+ const CHECK_INTERVAL_SECONDS = 14400;
11
+ const REGISTRY_TIMEOUT_MS = 8e3;
12
+ const NPM_REGISTRY_PACKUMENT_URL = "https://registry.npmjs.org/miladyai";
13
+ /** npm dist-tag corresponding to each release channel. */
14
+ const CHANNEL_DIST_TAGS = {
15
+ stable: "latest",
16
+ beta: "beta",
17
+ nightly: "nightly"
18
+ };
19
+ /** Fetch dist-tags from the npm registry. Returns null on any failure. */
20
+ async function fetchDistTags() {
21
+ const controller = new AbortController();
22
+ const timer = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS);
23
+ try {
24
+ const res = await fetch(NPM_REGISTRY_PACKUMENT_URL, {
25
+ headers: { Accept: "application/vnd.npm.install-v1+json" },
26
+ signal: controller.signal
27
+ });
28
+ if (!res.ok) return null;
29
+ return (await res.json())["dist-tags"] ?? null;
30
+ } catch {
31
+ return null;
32
+ } finally {
33
+ clearTimeout(timer);
34
+ }
35
+ }
36
+ function shouldSkipCheck(cfg) {
37
+ if (!cfg?.lastCheckAt) return false;
38
+ const interval = cfg.checkIntervalSeconds ?? CHECK_INTERVAL_SECONDS;
39
+ return (Date.now() - new Date(cfg.lastCheckAt).getTime()) / 1e3 < interval;
40
+ }
41
+ /** Resolve the effective release channel from config, env, or default. */
42
+ function resolveChannel(cfg) {
43
+ const env = process.env.MILADY_UPDATE_CHANNEL?.trim().toLowerCase();
44
+ if (env === "stable" || env === "beta" || env === "nightly") return env;
45
+ return cfg?.channel ?? "stable";
46
+ }
47
+ /**
48
+ * Check whether a newer version is available.
49
+ * Respects the check interval; pass `force: true` to bypass.
50
+ */
51
+ async function checkForUpdate(options) {
52
+ const config = loadMiladyConfig();
53
+ const updateCfg = config.update;
54
+ const channel = resolveChannel(updateCfg);
55
+ const distTag = CHANNEL_DIST_TAGS[channel];
56
+ if (!options?.force && shouldSkipCheck(updateCfg)) return {
57
+ updateAvailable: updateCfg?.lastCheckVersion ? (compareSemver(VERSION, updateCfg.lastCheckVersion) ?? 0) < 0 : false,
58
+ currentVersion: VERSION,
59
+ latestVersion: updateCfg?.lastCheckVersion ?? null,
60
+ channel,
61
+ distTag,
62
+ cached: true,
63
+ error: null
64
+ };
65
+ const distTags = await fetchDistTags();
66
+ if (!distTags) return {
67
+ updateAvailable: false,
68
+ currentVersion: VERSION,
69
+ latestVersion: null,
70
+ channel,
71
+ distTag,
72
+ cached: false,
73
+ error: "Unable to reach the npm registry. Check your network connection."
74
+ };
75
+ const latestVersion = distTags[distTag] ?? null;
76
+ if (!latestVersion) return {
77
+ updateAvailable: false,
78
+ currentVersion: VERSION,
79
+ latestVersion: null,
80
+ channel,
81
+ distTag,
82
+ cached: false,
83
+ error: `No version found for dist-tag "${distTag}". The "${channel}" channel may not have any published releases yet.`
84
+ };
85
+ const cmp = compareSemver(VERSION, latestVersion);
86
+ const updateAvailable = cmp !== null && cmp < 0;
87
+ try {
88
+ saveMiladyConfig({
89
+ ...config,
90
+ update: {
91
+ ...config.update,
92
+ lastCheckAt: (/* @__PURE__ */ new Date()).toISOString(),
93
+ lastCheckVersion: latestVersion
94
+ }
95
+ });
96
+ } catch (err) {
97
+ const msg = err instanceof Error ? err.message : String(err);
98
+ process.stderr.write(`[milady] Warning: could not save update-check metadata: ${msg}\n`);
99
+ }
100
+ return {
101
+ updateAvailable,
102
+ currentVersion: VERSION,
103
+ latestVersion,
104
+ channel,
105
+ distTag,
106
+ cached: false,
107
+ error: null
108
+ };
109
+ }
110
+ /** Returns the latest published version for each channel. */
111
+ async function fetchAllChannelVersions() {
112
+ const distTags = await fetchDistTags();
113
+ return {
114
+ stable: distTags?.latest ?? null,
115
+ beta: distTags?.beta ?? null,
116
+ nightly: distTags?.nightly ?? null
117
+ };
118
+ }
119
+
120
+ //#endregion
121
+ export { CHANNEL_DIST_TAGS, checkForUpdate, fetchAllChannelVersions, resolveChannel };
@@ -0,0 +1,29 @@
1
+ import { loadMiladyConfig } from "../config/config.js";
2
+ import { theme } from "../terminal/theme.js";
3
+ import { checkForUpdate, resolveChannel } from "./update-checker.js";
4
+
5
+ //#region src/services/update-notifier.ts
6
+ /**
7
+ * Fire-and-forget background update check. Prints a one-line notice
8
+ * to stderr if a newer version is available (like npm's update-notifier).
9
+ */
10
+ let notified = false;
11
+ function scheduleUpdateNotification() {
12
+ if (notified) return;
13
+ notified = true;
14
+ let config = {};
15
+ try {
16
+ config = loadMiladyConfig();
17
+ } catch {}
18
+ if (config.update?.checkOnStart === false) return;
19
+ if (process.env.CI || !process.stderr.isTTY) return;
20
+ checkForUpdate().then((result) => {
21
+ if (!result.updateAvailable || !result.latestVersion) return;
22
+ const channel = resolveChannel(config.update);
23
+ const suffix = channel !== "stable" ? ` (${channel})` : "";
24
+ process.stderr.write(`\n${theme.accent("Update available:")} ${theme.muted(result.currentVersion)} -> ${theme.success(result.latestVersion)}${theme.muted(suffix)}\n${theme.muted("Run")} ${theme.command("milady update")} ${theme.muted("to install")}\n\n`);
25
+ }).catch(() => {});
26
+ }
27
+
28
+ //#endregion
29
+ export { scheduleUpdateNotification };
@@ -0,0 +1,78 @@
1
+ //#region src/services/version-compat.ts
2
+ /**
3
+ * Plugins that provide AI model capabilities. If ALL of these fail to load
4
+ * the agent is completely non-functional — no responses can be generated.
5
+ */
6
+ const AI_PROVIDER_PLUGINS = [
7
+ "@elizaos/plugin-anthropic",
8
+ "@elizaos/plugin-openai",
9
+ "@elizaos/plugin-openrouter",
10
+ "@elizaos/plugin-ollama",
11
+ "@elizaos/plugin-google-genai",
12
+ "@elizaos/plugin-groq",
13
+ "@elizaos/plugin-xai",
14
+ "@homunculuslabs/plugin-zai",
15
+ "@elizaos/plugin-pi-ai",
16
+ "@elizaos/plugin-elizacloud"
17
+ ];
18
+ /**
19
+ * Parse a semver string (including pre-release tags) into a comparable tuple.
20
+ * Returns null for unparseable versions.
21
+ *
22
+ * Examples:
23
+ * "2.0.0-alpha.3" → [2, 0, 0, 3]
24
+ * "2.0.0-alpha.4" → [2, 0, 0, 4]
25
+ * "2.0.0-nightly.20260208" → [2, 0, 0, 20260208]
26
+ * "2.0.0" → [2, 0, 0, Infinity] (release beats any pre-release)
27
+ *
28
+ * Note: comparisons are only meaningful within the same pre-release tag type
29
+ * (alpha vs alpha, nightly vs nightly). Cross-tag comparisons (alpha.7 vs beta.1)
30
+ * compare only the numeric suffix, which may not reflect the intended ordering.
31
+ * The update checker always compares within the same channel, so this is safe.
32
+ */
33
+ function parseSemver(version) {
34
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(?:alpha|beta|rc|nightly)\.(\d+))?$/);
35
+ if (!match) return null;
36
+ return [
37
+ Number(match[1]),
38
+ Number(match[2]),
39
+ Number(match[3]),
40
+ match[4] !== void 0 ? Number(match[4]) : Number.POSITIVE_INFINITY
41
+ ];
42
+ }
43
+ /**
44
+ * Compare two semver strings. Returns:
45
+ * -1 if a < b
46
+ * 0 if a === b
47
+ * 1 if a > b
48
+ * null if either version is unparseable.
49
+ */
50
+ function compareSemver(a, b) {
51
+ const pa = parseSemver(a);
52
+ const pb = parseSemver(b);
53
+ if (!pa || !pb) return null;
54
+ for (let i = 0; i < 4; i++) {
55
+ if (pa[i] < pb[i]) return -1;
56
+ if (pa[i] > pb[i]) return 1;
57
+ }
58
+ return 0;
59
+ }
60
+ /**
61
+ * After plugin resolution, check whether at least one AI provider plugin
62
+ * loaded successfully. If none loaded, return a diagnostic message explaining
63
+ * whether this is a version-skew issue or a configuration issue.
64
+ *
65
+ * @param loadedPluginNames - Names of plugins that loaded successfully.
66
+ * @param failedPlugins - Names + error strings of plugins that failed to load.
67
+ */
68
+ function diagnoseNoAIProvider(loadedPluginNames, failedPlugins) {
69
+ if (loadedPluginNames.filter((n) => AI_PROVIDER_PLUGINS.includes(n)).length > 0) return null;
70
+ const failedProviders = failedPlugins.filter((f) => AI_PROVIDER_PLUGINS.includes(f.name));
71
+ if (failedProviders.length === 0) return "No AI provider plugin was loaded. Set an API key environment variable (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY, OPENROUTER_API_KEY) or log in to Eliza Cloud (ELIZAOS_CLOUD_API_KEY) to enable at least one model provider.";
72
+ const versionSkewPlugins = failedProviders.filter((f) => f.error.includes("not found in module") || f.error.includes("Export named") || f.error.includes("does not provide an export named"));
73
+ if (versionSkewPlugins.length > 0) return `Version skew detected: ${versionSkewPlugins.map((f) => f.name).join(", ")} failed to import required symbols from @elizaos/core. This usually means the plugin version is ahead of the installed core version. Pin the affected plugins to a version compatible with your installed @elizaos/core, or upgrade core. See: https://github.com/milady-ai/milady/issues/10`;
74
+ return `All AI provider plugins failed to load:\n${failedProviders.map((f) => ` ${f.name}: ${f.error}`).join("\n")}`;
75
+ }
76
+
77
+ //#endregion
78
+ export { compareSemver, diagnoseNoAIProvider };
@@ -0,0 +1,196 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+
4
+ //#region src/services/whatsapp-pairing.ts
5
+ /**
6
+ * WhatsApp pairing service — manages Baileys sessions for QR code authentication.
7
+ *
8
+ * This service is separate from `@elizaos/plugin-whatsapp` because the plugin
9
+ * initializes during runtime startup (too late for interactive QR flow).
10
+ * Once pairing succeeds, the auth state is persisted to disk so the plugin
11
+ * can reconnect automatically on subsequent startups.
12
+ */
13
+ const LOG_PREFIX = "[whatsapp-pairing]";
14
+ /** Validate accountId to prevent path traversal. Only allows alphanumeric, dash, underscore. */
15
+ function sanitizeAccountId(raw) {
16
+ const cleaned = raw.replace(/[^a-zA-Z0-9_-]/g, "");
17
+ if (!cleaned || cleaned !== raw) throw new Error(`Invalid accountId: must only contain alphanumeric characters, dashes, and underscores`);
18
+ return cleaned;
19
+ }
20
+ var WhatsAppPairingSession = class {
21
+ constructor(options) {
22
+ this.socket = null;
23
+ this.status = "idle";
24
+ this.qrAttempts = 0;
25
+ this.MAX_QR_ATTEMPTS = 5;
26
+ this.restartTimer = null;
27
+ this.options = options;
28
+ }
29
+ async start() {
30
+ this.setStatus("initializing");
31
+ const baileys = await import("@whiskeysockets/baileys");
32
+ const makeWASocket = baileys.default;
33
+ const { useMultiFileAuthState, fetchLatestBaileysVersion, DisconnectReason } = baileys;
34
+ const QRCode = (await import("qrcode")).default;
35
+ const { Boom } = await import("@hapi/boom");
36
+ fs.mkdirSync(this.options.authDir, { recursive: true });
37
+ const { state, saveCreds } = await useMultiFileAuthState(this.options.authDir);
38
+ const { version } = await fetchLatestBaileysVersion();
39
+ const pino = (await import("pino")).default;
40
+ this.socket = makeWASocket({
41
+ version,
42
+ auth: state,
43
+ logger: pino({ level: "silent" }),
44
+ printQRInTerminal: false,
45
+ browser: [
46
+ "Milady AI",
47
+ "Desktop",
48
+ "1.0.0"
49
+ ]
50
+ });
51
+ this.socket.ev.on("creds.update", saveCreds);
52
+ this.socket.ev.on("connection.update", async (update) => {
53
+ const { connection, lastDisconnect, qr } = update;
54
+ if (qr) {
55
+ this.qrAttempts++;
56
+ console.info(`${LOG_PREFIX} QR code received (attempt ${this.qrAttempts}/${this.MAX_QR_ATTEMPTS})`);
57
+ if (this.qrAttempts > this.MAX_QR_ATTEMPTS) {
58
+ this.setStatus("timeout");
59
+ this.stop();
60
+ return;
61
+ }
62
+ try {
63
+ const qrDataUrl = await QRCode.toDataURL(qr, {
64
+ width: 256,
65
+ margin: 2,
66
+ color: {
67
+ dark: "#000000",
68
+ light: "#ffffff"
69
+ }
70
+ });
71
+ this.setStatus("waiting_for_qr");
72
+ this.options.onEvent({
73
+ type: "whatsapp-qr",
74
+ accountId: this.options.accountId,
75
+ qrDataUrl,
76
+ expiresInMs: 2e4
77
+ });
78
+ } catch {}
79
+ }
80
+ if (connection === "close") {
81
+ const statusCode = (lastDisconnect?.error)?.output?.statusCode;
82
+ console.info(`${LOG_PREFIX} Connection closed, statusCode=${statusCode}, status=${this.status}`);
83
+ if (statusCode === DisconnectReason.loggedOut) this.setStatus("disconnected");
84
+ else if (statusCode === DisconnectReason.restartRequired || statusCode === DisconnectReason.timedOut || statusCode === DisconnectReason.connectionClosed || statusCode === DisconnectReason.connectionReplaced) {
85
+ console.info(`${LOG_PREFIX} Restarting pairing after transient close...`);
86
+ this.socket = null;
87
+ this.qrAttempts = 0;
88
+ this.restartTimer = setTimeout(() => {
89
+ this.restartTimer = null;
90
+ this.start().catch((err) => {
91
+ console.error(`${LOG_PREFIX} Restart failed:`, err);
92
+ this.setStatus("error");
93
+ this.options.onEvent({
94
+ type: "whatsapp-status",
95
+ accountId: this.options.accountId,
96
+ status: "error",
97
+ error: err instanceof Error ? err.message : String(err)
98
+ });
99
+ });
100
+ }, 3e3);
101
+ }
102
+ } else if (connection === "open") {
103
+ const phoneNumber = this.socket?.user?.id?.split(":")[0] ?? "";
104
+ this.setStatus("connected");
105
+ this.options.onEvent({
106
+ type: "whatsapp-status",
107
+ accountId: this.options.accountId,
108
+ status: "connected",
109
+ phoneNumber
110
+ });
111
+ }
112
+ });
113
+ }
114
+ stop() {
115
+ if (this.restartTimer) {
116
+ clearTimeout(this.restartTimer);
117
+ this.restartTimer = null;
118
+ }
119
+ try {
120
+ this.socket?.end(void 0);
121
+ } catch {}
122
+ this.socket = null;
123
+ }
124
+ getStatus() {
125
+ return this.status;
126
+ }
127
+ setStatus(status) {
128
+ this.status = status;
129
+ this.options.onEvent({
130
+ type: "whatsapp-status",
131
+ accountId: this.options.accountId,
132
+ status
133
+ });
134
+ }
135
+ };
136
+ /**
137
+ * Check if WhatsApp auth credentials exist on disk for an account.
138
+ */
139
+ function whatsappAuthExists(workspaceDir, accountId = "default") {
140
+ const credsPath = path.join(workspaceDir, "whatsapp-auth", accountId, "creds.json");
141
+ return fs.existsSync(credsPath);
142
+ }
143
+ /**
144
+ * Properly logout from WhatsApp (unlinks the device on WhatsApp servers)
145
+ * then delete auth files from disk.
146
+ */
147
+ async function whatsappLogout(workspaceDir, accountId = "default") {
148
+ const authDir = path.join(workspaceDir, "whatsapp-auth", accountId);
149
+ const credsPath = path.join(authDir, "creds.json");
150
+ if (fs.existsSync(credsPath)) try {
151
+ const baileys = await import("@whiskeysockets/baileys");
152
+ const makeWASocket = baileys.default;
153
+ const { useMultiFileAuthState, fetchLatestBaileysVersion } = baileys;
154
+ const pino = (await import("pino")).default;
155
+ const logger = pino({ level: "silent" });
156
+ const { state } = await useMultiFileAuthState(authDir);
157
+ const { version } = await fetchLatestBaileysVersion();
158
+ const sock = makeWASocket({
159
+ version,
160
+ auth: state,
161
+ logger,
162
+ printQRInTerminal: false
163
+ });
164
+ await new Promise((resolve) => {
165
+ let settled = false;
166
+ const finish = () => {
167
+ if (settled) return;
168
+ settled = true;
169
+ clearTimeout(timeout);
170
+ try {
171
+ sock.ev.removeAllListeners("connection.update");
172
+ } catch {}
173
+ try {
174
+ sock.end(void 0);
175
+ } catch {}
176
+ resolve();
177
+ };
178
+ const timeout = setTimeout(finish, 1e4);
179
+ sock.ev.on("connection.update", async (update) => {
180
+ if (update.connection === "open") {
181
+ try {
182
+ await sock.logout();
183
+ } catch {}
184
+ finish();
185
+ } else if (update.connection === "close") finish();
186
+ });
187
+ });
188
+ } catch {}
189
+ fs.rmSync(authDir, {
190
+ recursive: true,
191
+ force: true
192
+ });
193
+ }
194
+
195
+ //#endregion
196
+ export { WhatsAppPairingSession, sanitizeAccountId, whatsappAuthExists, whatsappLogout };