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