diff --git a/CLAUDE.md b/CLAUDE.md index adbd2bfa..8dfab74e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,6 @@ src/ setup/ — First-run setup wizard plugins/ — All plugins (adapters + services) telegram/ — Telegram adapter (grammY) - slack/ — Slack adapter (@slack/bolt) speech/ — TTS/STT (Edge TTS, Groq STT) tunnel/ — Port forwarding (Cloudflare, ngrok, Bore, Tailscale) security/ — Access control, rate limiting @@ -227,3 +226,7 @@ Users who installed and ran older versions will have config, data, and storage i - **CLI flags & commands**: Do not remove or rename existing commands/flags. If deprecating, keep them working and log a warning. - **Plugin API**: When changing interfaces that plugins use, must maintain backward compat or bump major version. - **General rule**: New code must work with old data/config without requiring user action. If migration is needed, run it automatically on startup. + +## Local OpenACP Workspace + +The `.openacp/` directory contains a local OpenACP workspace with secrets (bot tokens, API keys). Do not read, commit, or reference files inside it. diff --git a/docs/gitbook/getting-started/for-contributors.md b/docs/gitbook/getting-started/for-contributors.md index 2f13b9eb..2e1e8046 100644 --- a/docs/gitbook/getting-started/for-contributors.md +++ b/docs/gitbook/getting-started/for-contributors.md @@ -14,7 +14,7 @@ Welcome, and thank you for wanting to contribute! This guide gets your local dev ```bash # Clone the repo -git clone https://github.com/openacp/OpenACP.git +git clone https://github.com/Open-ACP/OpenACP.git cd OpenACP # Install dependencies diff --git a/docs/gitbook/troubleshooting/faq.md b/docs/gitbook/troubleshooting/faq.md index d680cd5b..0187b232 100644 --- a/docs/gitbook/troubleshooting/faq.md +++ b/docs/gitbook/troubleshooting/faq.md @@ -91,4 +91,4 @@ On next startup, OpenACP will create fresh sessions. If a session record in `=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-win32-arm64@0.34.5': resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} @@ -515,36 +525,42 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.10': resolution: {integrity: sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10': resolution: {integrity: sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10': resolution: {integrity: sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.10': resolution: {integrity: sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.10': resolution: {integrity: sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.10': resolution: {integrity: sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==} @@ -606,66 +622,79 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -1207,24 +1236,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 00000000..1a82ecbf --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,31 @@ +import { mkdir, copyFile } from 'node:fs/promises' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { spawn } from 'node:child_process' + +const root = dirname(dirname(fileURLToPath(import.meta.url))) + +async function run(command: string, args: string[]): Promise { + await new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd: root, + stdio: 'inherit', + shell: process.platform === 'win32', + }) + child.on('error', reject) + child.on('exit', (code) => { + if (code === 0) { + resolve() + } else { + reject(new Error(`${command} ${args.join(' ')} exited with code ${code ?? 'unknown'}`)) + } + }) + }) +} + +await run('tsc', []) +await mkdir(join(root, 'dist', 'data'), { recursive: true }) +await copyFile( + join(root, 'src', 'data', 'registry-snapshot.json'), + join(root, 'dist', 'data', 'registry-snapshot.json'), +) diff --git a/src/core/agents/agent-instance.ts b/src/core/agents/agent-instance.ts index 1928db9c..8e349f09 100644 --- a/src/core/agents/agent-instance.ts +++ b/src/core/agents/agent-instance.ts @@ -72,6 +72,14 @@ function findPackageRoot(startDir: string): string { return startDir; } +function commandForWindowsScript(filePath: string): { command: string; args: string[] } { + const ext = path.extname(filePath).toLowerCase(); + if (process.platform === "win32" && (ext === ".cmd" || ext === ".bat")) { + return { command: process.env.ComSpec ?? "cmd.exe", args: ["/d", "/s", "/c", `"${filePath}"`] }; + } + return { command: filePath, args: [] }; +} + /** * Resolve an agent command name to a directly executable form. * @@ -130,32 +138,13 @@ function resolveAgentCommand(cmd: string): { command: string; args: string[] } { } } - // 3. Try resolving from PATH using which - try { - const fullPath = execFileSync("which", [cmd], { encoding: "utf-8" }).trim(); - if (fullPath) { - try { - const content = fs.readFileSync(fullPath, "utf-8"); - if (content.startsWith("#!/usr/bin/env node")) { - return { command: process.execPath, args: [fullPath] }; - } - } catch { - // Binary file (not readable as utf-8) — use full path directly - } - // Found via PATH but not a node script — use the resolved full path - return { command: fullPath, args: [] }; - } - } catch { - // which failed — command not on PATH - } - - // 4. For npx/uvx: derive from the running Node's bin directory. + // 3. For npx/uvx: derive from the running Node's bin directory. // When openacp is installed globally (e.g. via Homebrew or nvm), npx lives // next to the same node binary that is executing this process. The user's // shell PATH may not include that directory (common with nvm in non-interactive - // shells), so resolve it explicitly. + // shells), so resolve it explicitly. On Windows, prefer .cmd/.exe wrappers; + // extensionless shim files cannot be spawned reliably by CreateProcess. if (cmd === "npx" || cmd === "uvx") { - // Collect candidate directories: process.execPath, its realpath, and well-known locations const seen = new Set(); const candidates: string[] = []; const addCandidate = (dir: string) => { @@ -164,18 +153,49 @@ function resolveAgentCommand(cmd: string): { command: string; args: string[] } { addCandidate(path.dirname(process.execPath)); try { addCandidate(path.dirname(fs.realpathSync(process.execPath))); } catch { /* ignore */ } - // Well-known Node.js install locations on macOS/Linux addCandidate("/opt/homebrew/bin"); addCandidate("/usr/local/bin"); + const executableNames = process.platform === "win32" ? [`${cmd}.cmd`, `${cmd}.exe`, cmd] : [cmd]; for (const dir of candidates) { - const candidate = path.join(dir, cmd); - if (fs.existsSync(candidate)) { - log.info({ cmd, resolved: candidate }, "Resolved package runner from fallback search"); - return { command: candidate, args: [] }; + if (cmd === "npx") { + const npxCli = path.join(dir, "node_modules", "npm", "bin", "npx-cli.js"); + if (fs.existsSync(npxCli)) { + log.info({ cmd, resolved: npxCli }, "Resolved npx CLI from Node installation"); + return { command: process.execPath, args: [npxCli] }; + } } + for (const name of executableNames) { + const candidate = path.join(dir, name); + if (fs.existsSync(candidate)) { + const resolved = commandForWindowsScript(candidate); + log.info({ cmd, resolved: candidate }, "Resolved package runner from fallback search"); + return resolved; + } + } + } + } + + // 4. Try resolving from PATH using which + try { + const fullPath = execFileSync("which", [cmd], { encoding: "utf-8" }).trim(); + if (fullPath) { + if (process.platform === "win32" && (cmd === "npx" || cmd === "uvx") && !path.extname(fullPath)) { + throw new Error("Skipping extensionless Windows package runner"); + } + try { + const content = fs.readFileSync(fullPath, "utf-8"); + if (content.startsWith("#!/usr/bin/env node")) { + return { command: process.execPath, args: [fullPath] }; + } + } catch { + // Binary file (not readable as utf-8) — use full path directly + } + // Found via PATH but not a node script — use the resolved full path + return commandForWindowsScript(fullPath); } - log.warn({ cmd, execPath: process.execPath, candidates }, "Could not find package runner"); + } catch { + // which failed — command not on PATH } // 5. Fallback: use command as-is diff --git a/src/core/setup/__tests__/setup-channels.test.ts b/src/core/setup/__tests__/setup-channels.test.ts index 8a4d667b..ad14c54c 100644 --- a/src/core/setup/__tests__/setup-channels.test.ts +++ b/src/core/setup/__tests__/setup-channels.test.ts @@ -76,4 +76,5 @@ describe('getChannelStatuses', () => { const tg = statuses.find(s => s.id === 'telegram') expect(tg?.configured).toBe(true) }) + }) diff --git a/src/core/setup/setup-channels.ts b/src/core/setup/setup-channels.ts index 3b8272de..2916bbbd 100644 --- a/src/core/setup/setup-channels.ts +++ b/src/core/setup/setup-channels.ts @@ -11,7 +11,7 @@ import { CHANNEL_META } from "./types.js"; import { guardCancel, ok, c } from "./helpers.js"; // Maps logical channel ID → plugin name used for settings storage and dynamic import. -// Telegram is built-in so it uses a direct import path instead of this map. +// Telegram is built-in, so it uses a direct import path instead of this map. const CHANNEL_PLUGIN_NAME: Record = { discord: "@openacp/discord-adapter", }; diff --git a/src/core/setup/types.ts b/src/core/setup/types.ts index df52bac5..ccbe15a4 100644 --- a/src/core/setup/types.ts +++ b/src/core/setup/types.ts @@ -41,7 +41,7 @@ export const ONBOARD_SECTION_OPTIONS: Array<{ { value: "integrations", label: "Integrations", hint: "Claude CLI session transfer" }, ]; -/** Display metadata for each built-in channel type. */ +/** Display metadata for each core-managed channel type. */ export const CHANNEL_META: Record = { sse: { label: "Desktop App", method: "SSE" }, telegram: { label: "Telegram", method: "Bot API" }, diff --git a/src/core/setup/wizard.ts b/src/core/setup/wizard.ts index 669b02fc..f1cf3c8d 100644 --- a/src/core/setup/wizard.ts +++ b/src/core/setup/wizard.ts @@ -309,7 +309,6 @@ export async function runSetup( }); } - // Handle official adapter selections (Discord, Slack, etc.) if (channelId.startsWith('official:')) { const npmPackage = channelId.slice('official:'.length); diff --git a/src/data/product-guide.ts b/src/data/product-guide.ts index 943b5b3d..d8928ef9 100644 --- a/src/data/product-guide.ts +++ b/src/data/product-guide.ts @@ -314,7 +314,7 @@ Example: after starting a dev server on port 3000, run \`openacp tunnel add 3000 ## Configuration -Config file: \`~/.openacp/config.json\` +Config file: \`/.openacp/config.json\` (default: \`~/openacp-workspace/.openacp/config.json\`) ### Channels - **channels.telegram.botToken** — Your Telegram bot token @@ -325,7 +325,7 @@ Config file: \`~/.openacp/config.json\` ### Agents - **defaultAgent** — Which agent to use by default - Agents are managed via \`/agents\` (Telegram) or \`openacp agents\` (CLI) -- Installed agents are stored in \`~/.openacp/agents.json\` +- Installed agent definitions are stored in \`/agents.json\`; downloaded agent binaries live in \`~/.openacp/agents/\` - Agent list is fetched from the ACP Registry CDN and cached locally (24h) ### Workspace @@ -346,7 +346,7 @@ Config file: \`~/.openacp/config.json\` ### Logging - **logging.level** — Log level: silent, debug, info, warn, error, fatal (default: info) -- **logging.logDir** — Log directory (default: \`~/.openacp/logs\`) +- **logging.logDir** — Log directory (default: \`/logs\`) - **logging.maxFileSize** — Max log file size before rotation - **logging.maxFiles** — Max number of rotated log files - **logging.sessionLogRetentionDays** — Auto-delete old session logs (default: 30) @@ -404,7 +404,7 @@ Override config with env vars: - If you see messages appearing in the Assistant topic instead of the session topic, try creating a new session ### Viewing logs -- Session-specific logs: \`~/.openacp/logs/sessions/\` +- Session-specific logs: \`/logs/sessions/\` - System logs: \`openacp logs\` to tail live - Set \`OPENACP_DEBUG=true\` for verbose output @@ -412,16 +412,17 @@ Override config with env vars: ## Data & Storage -All data is stored in \`~/.openacp/\`: -- \`config.json\` — Configuration -- \`agents.json\` — Installed agents (managed by AgentCatalog) -- \`registry-cache.json\` — Cached ACP Registry data (refreshes every 24h) -- \`agents/\` — Downloaded binary agents -- \`sessions/\` — Session records and state -- \`topics/\` — Topic-to-session mappings -- \`logs/\` — System and session logs -- \`plugins/\` — Installed adapter plugins -- \`openacp.pid\` — Daemon PID file +OpenACP stores per-instance state in \`/.openacp/\` (the instance root), with shared downloads and caches under \`~/.openacp/\`: +- \`/config.json\` — Configuration +- \`/agents.json\` — Installed agent definitions (managed by AgentCatalog) +- \`/sessions.json\` — Session records and state +- \`/usage.json\` — Token and cost tracking +- \`/history/\` — Per-session conversation history +- \`/logs/\` — System and session logs +- \`/plugins/\` — Installed adapter plugins and plugin data +- \`/openacp.pid\` — Daemon PID file +- \`~/.openacp/cache/registry-cache.json\` — Cached ACP Registry data (refreshes every 24h) +- \`~/.openacp/agents/\` — Downloaded binary agents Session records auto-cleanup: 30 days (configurable via \`sessionStore.ttlDays\`). Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetentionDays\`). diff --git a/src/plugins/core-plugins.ts b/src/plugins/core-plugins.ts index 9199f945..1bc3c096 100644 --- a/src/plugins/core-plugins.ts +++ b/src/plugins/core-plugins.ts @@ -25,7 +25,7 @@ import telegramPlugin from './telegram/index.js' * 1. **Service plugins** — security, file-service, context, speech, notifications. * These provide services that infrastructure and adapter plugins depend on. * 2. **Infrastructure plugins** — tunnel (exposes the local server), api-server (HTTP + SSE). - * 3. **Adapter plugins** — sse-adapter, telegram. Both depend on api-server, security, and + * 3. **Adapter plugins** — sse-adapter and telegram. Both depend on api-server, security, and * notifications, so they must boot after those services are ready. */ export const corePlugins = [