Files: 88. Audited: 88 / 88. Refactored: 0 / 88.
The HTTP surface of the app-core package — request entry point, auth gates,
route handlers, "compat" routes (legacy compatibility shims for the
upstream agent API), and the typed in-process client (client-*.ts) the
renderer uses to call those routes.
Two distinct concerns share this folder:
- Server-side route handlers (
*-routes.ts,server.ts,server-*.ts,*-compat-routes.ts,auth.ts,auth-*.ts,auth/*.ts,dev-*.ts,wallet-*.ts,secrets-*.ts,compat-route-shared.ts,response.ts,trusted-local-request.ts,spa-fallback-guard.ts,cloud-secrets.ts,cloud-connection.ts,credential-resolver.ts). - Client-side typed API client (
client.ts,client-base.ts,client-agent.ts,client-chat.ts,client-skills.ts,client-cloud.ts,client-wallet.ts,client-types*.ts,csrf-client.ts,auth-client.ts,transport.ts,streaming-text.ts,android-native-agent-transport.ts,native-cloud-http-transport.ts).
These should arguably be in two folders. They share types (and that is
the only reason they cohabit) but client-side code that runs in WKWebView
sits next to Node-only node:http route handlers — a mixture that
breaks tree-shaking guarantees and forces every consumer to reason about
the bundling boundary.
- Layer 3 starts the API server (
startApiServer); Layer 4 is the API server. Every route handler under Layer 3'sdev-server.tsandruntime/eliza.tsultimately resolves to a function defined here. - Layer 0/1/2/3 have all been audited (or partially audited). Auth, CSRF, rate-limiting, and route-validation patterns set in this layer are the contract every higher layer (UI, plugins) consumes.
- MASTER.md §0's bug ("provider issue") is partly a Layer 4 issue:
the chat fallback constant lives in
eliza/packages/agent/src/api/chat-routes.ts(which is Layer 6, not Layer 4 — see §"Out-of-layer note" below), but the symptom is observed via the API client this layer ships (client-chat.ts) and via the route mounting inserver.ts.
MASTER.md §0 + §3 Phase 4 reference eliza/packages/agent/src/api/chat-routes.ts
— this file is not in scope for Layer 4. It belongs to
@elizaos/agent (Layer 6 — agent runtime). The line numbers MASTER.md
cites (1918, 1999, 2181, 2273) all still match the current file —
verified during this audit. See §"Chat-fallback paths" in the summary
below for confirmation. The Layer 4 implications are limited to
server.ts:705-950 (handleCompatRoute) which mounts these routes via
the upstream agent package.
- Route handler bloat — biggest in-scope file is
client-agent.ts(2800 LOC, the typed renderer client) andserver.ts(1194 LOC, the request-mux glue). Several*-compat-routes.tsfiles are 600–1700 LOC. - Validation pattern uniformity. Commandment 7 says: route schemas
validate + transform; use cases trust pre-validated input. This layer
uses zero zod schemas. Validation is hand-rolled
typeof,Array.isArray, regex literals, and ad-hocis*()predicates. - Auth gates. Two sister functions exist:
ensureRouteAuthorized(canonical) andensureCompatApiAuthorized(sync, bearer-only). PlusensureCompatSensitiveRouteAuthorized,ensureCompatApiAuthorizedAsync,ensureAuthSessionOrBootstrap,ensureCompatSensitiveRouteAuthorized. Six entry points for "is this caller allowed to do this?" — at least three is one too many. - CSRF coverage.
auth.tsenforces CSRF onPOST/PUT/PATCH/DELETEfor cookie sessions. Bearer-auth requests are exempt. Verify every state-mutating handler routes throughensureRouteAuthorizedand not the olderensureCompatApiAuthorized. - Cloud routes.
cloud-secrets.ts,cloud-connection.ts,server-cloud-tts.tsare all 13–22 LOC re-export shims to@elizaos/plugin-elizacloud/lib/*. Pattern: every cloud route lives in the plugin; app-core just re-exports for compat. - Dev-only route safety.
dev-compat-routes.ts:37has the onlyNODE_ENV === "production"guard.dev-stack.ts,dev-console-log.tsare pure helpers — safe in production but never invoked in production because their handler refuses.
(See AUDIT.md for the canonical legend.)
[ ] pending · [~] reading · [!] findings · [*] refactor ·
[x] clean · [-] delete · [?] blocked
- [!]
eliza/packages/app-core/src/api/server.ts— 1194 LOC glue file. dedup:re-exports from 6 split-out modules (server-cloud-tts,server-config-filter,server-cors,server-html,server-security,server-startup,server-wallet-trade) plus 14 named upstream re-exports from@elizaos/agentplus 1 import from@elizaos/agent/config— this is a barrel masquerading as an entry. boundaries:handleCompatRouteat line 705 is 246 LOC of compat router that fans out to 14 sub-handlers (handleAuthBootstrapRoutes,handleAuthSessionRoutes,handleCatalogRoutes,handleDatabaseRowsCompatRoute,handleDevCompatRoutes,handleLocalInferenceCompatRoutes,handleOnboardingCompatRoute,handlePluginsCompatRoutes,handleSecretsInventoryRoute,handleSecretsManagerRoute,handleWorkbenchCompatRoutes,handleAutomationsCompatRoutes, plus inline status patches and reset). errors:hydrateWalletOsStoreFlagFromConfig(line 188),clearCompatRuntimeStateViaApi(363, 386, 406) all log-and-continue on every catch — exactly the swallowing pattern AGENTS.md axis 5 forbids. types:15} catchblocks;parsed = JSON.parse(bodyText) as unknown(line 488) widens. legacy:lines 138-143 admit "Wallet market overview route extracted to @elizaos/plugin-wallet/routes" + "Steward compat routes → app-steward/src/plugin.ts" — comments narrate prior refactor instead of being removed. dead:_PACKAGE_ROOT_NAMES(line 176) underscore-prefixed const,_getTableColumnNames(579) underscore-prefixed function — both signal "kept for future use." dedup:compatLoopbackFetchJson+compatLoopbackRequest+buildCompatLoopbackHeaders+resolveCompatLoopbackApiBaseare an inline mini-client that duplicatesclient-base.ts's fetch wrapper. - [!]
eliza/packages/app-core/src/api/compat-route-shared.ts— 373 LOC. dedup:isLoopbackRemoteAddressdefined here AND identical copy intrusted-local-request.ts:5-17(5-line literal duplicate).firstHeaderValuedefined here AND intrusted-local-request.tsANDextractHeaderValueis the same shape inauth.ts:26-31. TheCLIENT_IP_PROXY_HEADERSset +isClientIpProxyHeaderName+extractForwardedForCandidates+extractProxyClientAddressCandidatesblock (lines 66-130+) is also fully duplicated intrusted-local-request.ts. types:1as unknowncast (line 289). errors:catch aroundfor await (chunk of req)body reader uses generic message "request body too large" without preserving the underlying error. - [!]
eliza/packages/app-core/src/api/trusted-local-request.ts— 211 LOC. dedup:near-100% overlap withcompat-route-shared.tsforisLoopbackRemoteAddress,CLIENT_IP_PROXY_HEADERS,firstHeaderValue,headerValues,isClientIpProxyHeaderName,extractForwardedForCandidates,extractProxyClientAddressCandidates. Should either delete this file or move shared helpers to a singlerequest-context.ts. -
eliza/packages/app-core/src/api/response.ts— 47 LOC. Clean.scrubStackFieldsstripsstack/stackTracefrom any nested object — good defence-in-depth for accidental error leakage. -
eliza/packages/app-core/src/api/spa-fallback-guard.ts— 12 LOC. Clean. -
eliza/packages/app-core/src/api/server-cors.ts— 141 LOC. Clean.isAllowedLocalOrigin = isAllowedOrigin(line 141) is a@deprecated retained for API compatibilityshim — should plan a removal pass on the consumers, then delete. -
eliza/packages/app-core/src/api/server-html.ts— 8 LOC. Pure passthrough re-export wrapper aroundinjectApiBaseIntoHtmlfrom@elizaos/agent. Zero value beyond changing the import path; consumers could import from@elizaos/agentdirectly. -
eliza/packages/app-core/src/api/server-config-filter.ts— 44 LOC. Clean. -
eliza/packages/app-core/src/api/server-startup.ts— 95 LOC. Clean. legacy:line 13 has aSet<string>with "eliza" listed twice —["eliza", "elizaai", "elizaos", "eliza"]— bug, dedup at construction makes it harmless, but signals a copy-paste. boundaries:syncElizaEnvAliases+syncAppEnvToElizacalled twice each inresolveCorsOrigin— env is mutated to call into upstream then mutated back. Suggests the upstream signature is wrong for the boundary. -
eliza/packages/app-core/src/api/server-security.ts— 63 LOC. 5 wrapper functions, each a ~7-line re-shape around an upstream@elizaos/agentresolver, just to callrunWithCompatAuthContext(env mutation +mirrorCompatHeaders) before/after. dedup:every wrapper has the same shape; could be a single higher-order helper. boundaries:env mutation around an upstream call is a leaky abstraction — fix the upstream signature.
-
eliza/packages/app-core/src/api/server-cloud-tts.ts— 21 LOC. Re-export shim for@elizaos/plugin-elizacloud/lib/server-cloud-tts. legacy:exists only becauseserver.tsandserver-wallet-trade.tsuse the relative path; once those import from the plugin directly this file is deletable. -
eliza/packages/app-core/src/api/server-onboarding-helpers.ts— 383 LOC. Helpers used byonboarding-routes.ts(replay body, extract+persist API key, etc). Clean — no audit issues other than the standard} catch { /* non-fatal */ }pattern (3 sites) that should be reviewed during the error-handling pass. (Renamed 2026-05-11 fromserver-onboarding-compat.ts.) -
eliza/packages/app-core/src/api/server-wallet-trade.ts— 115 LOC. Hardened wallet-export guard composition + per-op env mutation around the upstream rejection resolver. errors:runWithCompatAuthContextmutates env in finally — leaky boundary, same disease asserver-security.ts. dedup:normalizeCompatRejection+normalizeCompatReasonare no-ops onreason— leftover from a real normalisation that no longer happens? Or pre-emptive infrastructure? Either delete the no-op chain or document the kept-for-future-use intent. -
eliza/packages/app-core/src/api/cloud-secrets.ts— 13 LOC. Re-export shim for@elizaos/plugin-elizacloud/lib/cloud-secrets. Same legacy:bin pattern asserver-cloud-tts.ts; deletable when consumers migrate. -
eliza/packages/app-core/src/api/cloud-connection.ts— 22 LOC. Re-export shim for@elizaos/plugin-elizacloud/lib/cloud-connection. Same pattern.
- [!]
eliza/packages/app-core/src/api/auth.ts— 443 LOC. 6 auth gate variants are exported / declared here:ensureCompatApiAuthorized(sync bearer-only),ensureCompatApiAuthorizedAsync(cookie+CSRF+legacy-bearer),ensureCompatSensitiveRouteAuthorized(sync sensitive),ensureAuthSessionOrBootstrap(cookie OR bearer-as-bootstrap), plus the canonicalensureRouteAuthorized(lines 425-443) which delegates. dedup:in-process auth rate limiter (lines 71-113) is a near-duplicate ofauth-session-routes.ts:78-111— samecount/resetAtshape, same 20/min cap, same sweep timer. The two limiters share purpose (auth rate limit per IP) but live in different modules with separate state. Should be one limiter with a named bucket key. types:AuthSessionOrBootstrapResultdiscriminated union is fine; oneas unknown as AuthStorecast (line 437) is unavoidable due to dynamic import. dead:isDevEnvironment(line 285-288) is exported but only one caller (verify with knip during refactor pass). slop:lines 5-6// extractHeaderValue, getCompatApiToken — now imported from ./authstyle narration about removed code; should be deleted. -
eliza/packages/app-core/src/api/auth/index.ts— 89 LOC barrel. Clean. dedup-positive — explicitly re-exports the named surface so internal modules can shape themselves freely. -
eliza/packages/app-core/src/api/auth/audit.ts— 171 LOC. Clean. Good error pattern:appendAuditEventdeliberately runs DB + file writes in parallel and rethrows the first error — does not swallow. -
eliza/packages/app-core/src/api/auth/auth-context.ts— 168 LOC. Clean. Hard-coded fail-closed throughout (3 explicit.catch(() => null)returns null, never widens). errors:console.error("[auth] legacy bearer audit failed:", err)(lines 135, 149) — should uselogger.errorper AGENTS.md commandment 9 (logger only, never console). - [!]
eliza/packages/app-core/src/api/auth/legacy-bearer.ts— 188 LOC. legacy:this entire module is the legacy bearer 14-day grace window. It's a bridge that exists to letELIZA_API_TOKENcallers migrate before the bearer is rejected. After the grace window everywhere has expired (the deploy pipeline setsELIZA_LEGACY_GRACE_UNTIL), this module should be deleted. Audit recommendation: track when the earliest production deploy crossed the 14-day mark; once all current deploys are post-grace, delete this module + the call sites inauth.tsandauth-context.ts. -
eliza/packages/app-core/src/api/auth/passwords.ts— 97 LOC. Clean. argon2id with OWASP params; well-commented. -
eliza/packages/app-core/src/api/auth/sensitive-rate-limit.ts— 113 LOC. Clean. -
eliza/packages/app-core/src/api/auth/sessions.ts— 421 LOC. Clean. Owns session lifecycle, CSRF derive/verify, cookie serialize/parse — exactly the responsibility split AGENTS.md commandment 6 (CQRS) wants. -
eliza/packages/app-core/src/api/auth/tokens.ts— 14 LOC. Clean. -
eliza/packages/app-core/src/api/auth/bootstrap-token.ts— 223 LOC. Clean. Hard fail-closed contract enforced. - [!]
eliza/packages/app-core/src/api/auth/cloud-sso.ts— 545 LOC. The largest auth/* file. boundaries:owns OAuth state, pending-state TTL sweep, JWKS verify, identity link/create — multiple lifecycles in one file. Could split:cloud-sso/state.ts(pending state map),cloud-sso/token-exchange.ts(POST /oauth/token + JWKS verify),cloud-sso/identity-link.ts(Identity row management). Defer split — current file is internally cohesive.
-
eliza/packages/app-core/src/api/auth-bootstrap-routes.ts— 234 LOC. Clean. Hard fail-closed at every error path; 3 audit catches useconsole.errornotlogger.error(commandment 9 violation, same asauth-context.ts). -
eliza/packages/app-core/src/api/auth-pairing-routes.ts— 290 LOC. Clean. (Renamed 2026-05-11 fromauth-pairing-compat-routes.ts.) - [!]
eliza/packages/app-core/src/api/auth-session-routes.ts— 771 LOC. dedup:consumeAuthBucket(lines 82-97) is a copy of theauthAttempts/recordFailedAuth/isAuthRateLimitedtriad inauth.ts:71-113. Two parallel limiters with same name, same window, same cap. Should be one. boundaries:6 routes (setup/login/logout/me/sessions list/sessions revoke) all in one file; each is 60–120 LOC. legacy:SESSION_COOKIE_NAMEre-imported from./auth/indexthen shadowed byparseSessionCookie— confirm one module owns the cookie name. - [!]
eliza/packages/app-core/src/api/auth-client.ts— 448 LOC. Renderer-side, not server-side. boundaries:lives in the auth folder family but is consumed only by the SPA. Should move toclient-auth.tsnext to otherclient-*.tsmodules so the file naming reflects the runtime.
- [!]
eliza/packages/app-core/src/api/automations-compat-routes.ts— 907 LOC. boundaries:huge staticSTATIC_AUTOMATION_NODE_SPECSlist (~250 LOC of literals starting line 87) belongs in a data file, not a route handler. types:as unknown as Pick<Room, "metadata">(line 334) andas unknown as Record<string, unknown>(line 359). legacy:BLOCKED_AUTOMATION_PROVIDER_NODESset hides what would be a per-nodeenabledflag — should be metadata not a deny-list. - [!]
eliza/packages/app-core/src/api/plugins-routes.ts— 1651 LOC — the second-largest file in the layer. boundaries:14+ exported helpers (maskValue,normalizePluginCategory,resolveCompatPluginEnabledForList,analyzePluginStateDrift,buildPluginListResponse,validateCompatPluginConfig,persistCompatPluginMutation,resolvePluginManifestPath,resolveAdvancedCapabilityCompatStatus, etc.) plus 30+ private helpers — this is a plugin-management module not a route handler. Should split:plugins/registry.ts(manifest + drift),plugins/mutations.ts(validate + persist),plugins-routes.ts(the actual route mux). types:1as unknown as CompatPluginRecord[](line 566). errors:6} catchblocks. dead:shortPluginIdFromNpmNameis internal but exported. (Renamed 2026-05-11 fromplugins-compat-routes.ts.) - [!]
eliza/packages/app-core/src/api/onboarding-routes.ts— 242 LOC. errors:line 92-94 catches and silently returns when re-savingcloud.apiKeyto the config file fails — exactly the "best effort" pattern AGENTS.md axis 5 calls out. legacy:scheduleCloudApiKeyResave(line 77) is a 3-secondsetTimeoutworkaround "after upstream handler clobbered it" — a workaround for a bug in the upstream handler, not a fix. Should fix the upstream handler and delete this. dedup:loopback fetch on line 65 duplicatescompatLoopbackFetchJsoninserver.ts— should call the shared helper. (Renamed 2026-05-11 fromonboarding-compat-routes.ts.) -
eliza/packages/app-core/src/api/dev-compat-routes.ts— 169 LOC. Clean. Only file withNODE_ENV === "production"guard for full-route disable (line 37). Loopback gate + auth gate on every dev endpoint. SSRF guard on the screenshot proxy (lines 79-94) is correct. - [!]
eliza/packages/app-core/src/api/local-inference-compat-routes.ts— 606 LOC. types:1as unknown as CatalogModelcast (line 233). dedup:duplicates download-job, hardware-probe, model-management surface that exists in the local-inference plugin runtime — confirm the boundary; if these routes are strictly compat shims they should be very thin. - [!]
eliza/packages/app-core/src/api/database-rows-compat-routes.ts— 174 LOC. Database read/write through the API is a sharp tool; needs a careful read in a future pass to confirm column-name + identifier sanitization is correct. - [!]
eliza/packages/app-core/src/api/workbench-compat-routes.ts— 451 LOC. Owns workbench todos/notes; tightly coupled to the agent task table. Defer detailed audit. - [!]
eliza/packages/app-core/src/api/secrets-manager-routes.ts— 579 LOC. Module-level_managersingleton with explicit per-process rationale comment. boundaries:install routes (POST /api/secrets/manager/install) start a job + stream SSE — long-running ops in a route handler. Should consider extracting the job orchestration to a service with the route as a thin facade. - [!]
eliza/packages/app-core/src/api/secrets-inventory-routes.ts— 573 LOC. Similar pattern to secrets-manager. Defer detailed audit.
These are delegation wrappers to @elizaos/agent route handlers,
adding only context shape conversion. Each is 17–55 LOC and they all
follow an identical structural pattern.
-
eliza/packages/app-core/src/api/agent-admin-routes.ts— 25 LOC. -
eliza/packages/app-core/src/api/agent-lifecycle-routes.ts— 20 LOC. -
eliza/packages/app-core/src/api/agent-transfer-routes.ts— 34 LOC. -
eliza/packages/app-core/src/api/character-routes.ts— 36 LOC. Sole zod-adjacent surface: line 28validateCharacter: (body) => CharacterSchema.safeParse(body) as never— and the cast hides the type. Fix the upstream signature so the cast goes away. -
eliza/packages/app-core/src/api/permissions-routes.ts— 39 LOC. -
eliza/packages/app-core/src/api/training-routes.ts— 24 LOC. -
eliza/packages/app-core/src/api/diagnostics-routes.ts— 33 LOC. -
eliza/packages/app-core/src/api/registry-routes.ts— 33 LOC. -
eliza/packages/app-core/src/api/subscription-routes.ts— 29 LOC. types:2as nevercasts (lines 25, 27) hiding upstream signature mismatch. legacy:dynamicawait import("@elizaos/agent/auth")mid-handler (line 27) — should be a top-level import. -
eliza/packages/app-core/src/api/memory-routes.ts— 17 LOC. -
eliza/packages/app-core/src/api/accounts-routes.ts— 33 LOC. -
eliza/packages/app-core/src/api/trigger-routes.ts— 55 LOC. dedup:re-passes 13 named upstream functions throughtoAutonomousContext— the wrapper exists only to pass these through. IfhandleAutonomousTriggerRoutes's context could accept adependenciesobject, this could be a 5-line file. -
eliza/packages/app-core/src/api/catalog-routes.ts— 75 LOC. The only "real" route in this group (mounts/api/catalog/appswith its own logic, not a delegation wrapper). Clean.
Pattern observation: these 12 files are 290 LOC combined of repeating
glue. They could be a single route-wrappers.ts with one helper that
generates the wrapper from a config object. Risk: the per-route state
shape divergence is real (character-routes has pickRandomNames,
trigger-routes has 13 dependencies, etc), so a generator may not save
much. Action: keep separate, but eliminate the as never casts at
the call boundary.
- [!]
eliza/packages/app-core/src/api/wallet-market-overview-route.ts— 772 LOC. boundaries:owns CoinGecko + Polymarket data shape, response cache, refresh rate-limit buckets, fetch retry, source-availability flags. This is a service in a route file. Should beservices/wallet-market-overview.ts(the service) +wallet-market-overview-route.ts(a 30-line handler). dedup:cache + in-flight + refresh-buckets pattern is implemented from scratch — the same pattern exists insecrets-manager-routes.ts(job stream) andauth-session-routes.ts(auth attempts). Worth a sharedtiny-cache+tiny-rate-limitutility (Layer 5). -
eliza/packages/app-core/src/api/wallet-export-guard.ts— 328 LOC. Clean.console.warnat line 90 should belogger.warnper commandment 9. -
eliza/packages/app-core/src/api/credential-resolver.ts— 343 LOC.readJsonSafe+extractOauthAccessToken+ Keychainsecurityinvocation. errors:readJsonSafeswallows all errors and returns null — acceptable for "best effort credential probe" but should document that contract. -
eliza/packages/app-core/src/api/dev-stack.ts— 100 LOC. Clean. -
eliza/packages/app-core/src/api/dev-console-log.ts— 79 LOC. Clean. Allow-list path validation is correct (basename +.elizaparent requirement).
-
eliza/packages/app-core/src/api/index.ts— 1 LOC barrelexport * from "./client". Clean. - [!]
eliza/packages/app-core/src/api/client.ts— 245 LOC. boundaries:imports 12 siblingclient-*files for declaration merging side effects (lines 227-238). The singleexport const client = new ElizaClient()(line 245) is a process-global singleton. Most of the file (200+ LOC) is pass-through type re-exports from@elizaos/sharedand split-outclient-types-*files. The barrel itself is fine; the type-fan-out duplication should resolve when theclient-types-*split completes. - [!]
eliza/packages/app-core/src/api/client-base.ts— 914 LOC. The actual ElizaClient class. boundaries:owns_baseUrl/_userSetBase→ this is the class MASTER.md §0 cites — oncesetBaseUrl()is called,_userSetBase = trueand the client stops re-reading boot config. types:GENERIC_NO_RESPONSE_TEXTconstant (line 38) is a renderer-side duplicate of the agent-sidePROVIDER_ISSUE_CHAT_REPLYandGENERIC_NO_RESPONSE_CHAT_REPLYstrings — three places now hold "no-response" copy. dead:9} catchblocks. errors:fetchWithCsrf-style behaviour is reimplemented inline rather than usingcsrf-client.ts'sfetchWithCsrf— confirm this isn't a divergent path. - [!]
eliza/packages/app-core/src/api/client-agent.ts— 2800 LOC, the largest file in scope. dead:only 10 named exports for 2800 LOC — most of the file is method augmentation viaElizaClient.prototype(declaration merging). 16} catchblocks. boundaries:single file owns lifecycle, auth, config, connectors, triggers, training, plugins, streaming/PTY, logs, character, permissions, updates, app-blocker, website-blocker — at least 14 distinct concerns. Should be split per concern (client-agent-lifecycle.ts,client-agent-config.ts,client-agent-plugins.ts, etc). - [!]
eliza/packages/app-core/src/api/client-cloud.ts— 1790 LOC. types:as unknown(line 205) on__ELIZA_CLOUD_AUTH_TOKEN__window-global read. boundaries:owns billing, compat agents, sandbox, export/import, direct cloud auth, bug reports — should split by concern. legacy:DirectCloudAgentandDirectCloudJobtypes (lines 59-99) have parallelcamelCase+snake_casefield pairs — adapter shape for "cloud might return either case." Should pick one shape at the boundary and never speak the other inside the client. - [!]
eliza/packages/app-core/src/api/client-skills.ts— 1658 LOC. 0 try/catch (good — actual error propagation). boundaries:owns skills, catalog, marketplace, apps, Babylon, custom actions, WhatsApp, agent events. Same split-by-concern recommendation asclient-agent.ts. - [!]
eliza/packages/app-core/src/api/client-chat.ts— 1421 LOC. 2 try/catch. dead:probable overlap withclient-agent.tsfor chat-related methods — confirm. - [!]
eliza/packages/app-core/src/api/client-wallet.ts— 553 LOC. Clean shape but huge — splits same as siblings. -
eliza/packages/app-core/src/api/client-automations.ts— 24 LOC. -
eliza/packages/app-core/src/api/client-browser-workspace.ts— 183 LOC. -
eliza/packages/app-core/src/api/client-computeruse.ts— 73 LOC. -
eliza/packages/app-core/src/api/client-imessage.ts— 203 LOC. -
eliza/packages/app-core/src/api/client-local-inference.ts— 249 LOC. -
eliza/packages/app-core/src/api/client-n8n.ts— 174 LOC. -
eliza/packages/app-core/src/api/client-vault.ts— 131 LOC.
-
eliza/packages/app-core/src/api/client-types.ts— 13 LOC barrel. - [!]
eliza/packages/app-core/src/api/client-types-cloud.ts— 1015 LOC. The largest type module. boundaries:cloud billing, agent provisioning, OAuth connections, sandbox, login/persist, credits — multiple subdomains. Should split per cloud feature. - [!]
eliza/packages/app-core/src/api/client-types-config.ts— 737 LOC. Same observation; multiple config subdomains. - [!]
eliza/packages/app-core/src/api/client-types-chat.ts— 594 LOC. Should split (n8n, conversation, message-event sub-types live together). -
eliza/packages/app-core/src/api/client-types-core.ts— 445 LOC. -
eliza/packages/app-core/src/api/client-types-babylon.ts— 294 LOC. -
eliza/packages/app-core/src/api/client-types-relationships.ts— 200 LOC. -
eliza/packages/app-core/src/api/client-types-steward.ts— 118 LOC. -
eliza/packages/app-core/src/api/client-types-experience.ts— 103 LOC. -
eliza/packages/app-core/src/api/client-types-character.ts— 47 LOC.
-
eliza/packages/app-core/src/api/transport.ts— 13 LOC. Clean. -
eliza/packages/app-core/src/api/streaming-text.ts— 5 LOC re-export. -
eliza/packages/app-core/src/api/csrf-client.ts— 63 LOC. Clean.fetchWithCsrfis the canonical CSRF-aware fetch helper. -
eliza/packages/app-core/src/api/android-native-agent-transport.ts— 120 LOC. -
eliza/packages/app-core/src/api/native-cloud-http-transport.ts— 85 LOC. -
eliza/packages/app-core/src/api/automation-node-contributors.ts— 35 LOC.
- [!]
eliza/packages/app-core/src/api/__tests__/sandbox-test-helpers.ts— test helper. boundaries:lives undersrc/(nottests/). Confirm test-only.
| LOC | File | Concerns | Recommendation |
|---|---|---|---|
| 1651 | plugins-routes.ts |
14+ | Split (registry + mutations + routes) |
| 1194 | server.ts |
5+ | Split mux from compat router |
| 907 | automations-compat-routes.ts |
4 | Move static specs to data file |
| 771 | auth-session-routes.ts |
6 routes | Dedup limiter; split routes |
| 772 | wallet-market-overview-route.ts |
6+ | Service in a route file — extract service |
| 606 | local-inference-compat-routes.ts |
4 | Confirm vs plugin runtime |
| 579 | secrets-manager-routes.ts |
8 routes | Extract install-job orchestrator |
| 573 | secrets-inventory-routes.ts |
? | Defer detailed audit |
| 545 | auth/cloud-sso.ts |
4 | Split per lifecycle (state, token, identity) |
| 451 | workbench-compat-routes.ts |
? | Defer detailed audit |
| 443 | auth.ts |
6 gates | Reduce to 1–2 gates |
| 421 | auth/sessions.ts |
clean | — |
| 383 | server-onboarding-helpers.ts |
clean | — |
| 373 | compat-route-shared.ts |
dup | Merge with trusted-local-request.ts |
| 343 | credential-resolver.ts |
clean | — |
| 328 | wallet-export-guard.ts |
clean | logger.warn over console.warn |
| 290 | auth-pairing-routes.ts |
clean | — |
| 242 | onboarding-routes.ts |
1 | Delete scheduleCloudApiKeyResave workaround |
| 234 | auth-bootstrap-routes.ts |
clean | — |
| 223 | auth/bootstrap-token.ts |
clean | — |
| 211 | trusted-local-request.ts |
dup | Delete or merge |
| 188 | auth/legacy-bearer.ts |
legacy | Delete after grace window |
| 174 | database-rows-compat-routes.ts |
? | Defer detailed audit |
| 171 | auth/audit.ts |
clean | — |
| 169 | dev-compat-routes.ts |
clean | Only NODE_ENV guard in layer |
| 168 | auth/auth-context.ts |
clean | console.error → logger.error |
| 141 | server-cors.ts |
clean | — |
| 131 | client-vault.ts |
client | — |
| 120 | android-native-agent-transport.ts |
client | — |
| 115 | server-wallet-trade.ts |
leaky | Fix env-mutation boundary |
| 113 | auth/sensitive-rate-limit.ts |
clean | — |
| 100 | dev-stack.ts |
clean | — |
| 97 | auth/passwords.ts |
clean | — |
| 95 | server-startup.ts |
clean | — |
| 89 | auth/index.ts |
barrel | — |
| 85 | native-cloud-http-transport.ts |
client | — |
| 79 | dev-console-log.ts |
clean | — |
| 75 | catalog-routes.ts |
clean | — |
| 63 | server-security.ts |
leaky | 5 wrappers around upstream |
| 63 | csrf-client.ts |
clean | — |
| 55 | trigger-routes.ts |
wrapper | 13-dep passthrough |
| 47 | response.ts |
clean | — |
| 44 | server-config-filter.ts |
clean | — |
| 39 | permissions-routes.ts |
wrapper | — |
| 36 | character-routes.ts |
wrapper | as never cast hides type |
| 35 | automation-node-contributors.ts |
clean | — |
| 34 | agent-transfer-routes.ts |
wrapper | — |
| 33 | registry-routes.ts |
wrapper | — |
| 33 | diagnostics-routes.ts |
wrapper | — |
| 33 | accounts-routes.ts |
wrapper | — |
| 29 | subscription-routes.ts |
wrapper | dynamic import + as never |
| 25 | agent-admin-routes.ts |
wrapper | — |
| 24 | training-routes.ts |
wrapper | — |
| 22 | cloud-connection.ts |
shim | Re-export only |
| 21 | server-cloud-tts.ts |
shim | Re-export only |
| 20 | agent-lifecycle-routes.ts |
wrapper | — |
| 17 | memory-routes.ts |
wrapper | — |
| 13 | cloud-secrets.ts |
shim | Re-export only |
| 12 | spa-fallback-guard.ts |
clean | — |
| 8 | server-html.ts |
shim | Re-export only |
No server-side route file > 2000 LOC. The biggest single-file
"route handler" is plugins-routes.ts at 1651 LOC, and most of
that is plugin manifest/state-management helpers that should live in a
service. Files > 1000 LOC: 2 (plugins-routes.ts,
server.ts).
| LOC | File | Notes |
|---|---|---|
| 2800 | client-agent.ts |
14+ concerns; needs split |
| 1790 | client-cloud.ts |
6+ concerns; needs split |
| 1658 | client-skills.ts |
8 concerns; needs split |
| 1421 | client-chat.ts |
overlap risk with client-agent |
| 1015 | client-types-cloud.ts |
type fan-out |
| 914 | client-base.ts |
the ElizaClient class |
| 737 | client-types-config.ts |
type fan-out |
| 594 | client-types-chat.ts |
type fan-out |
| 553 | client-wallet.ts |
clean shape |
| 448 | auth-client.ts |
misnamed (renderer-side, should be client-auth) |
Files > 1000 LOC on the client side: 5. Total client-side surface is ~13,400 LOC across 29 files — comparable to the server-side route surface. The two halves of this folder really are two folders.
MASTER.md §3 Phase 4 cites four trigger paths in
eliza/packages/agent/src/api/chat-routes.ts (Layer 6, NOT Layer 4):
| MASTER.md line | Current line | Path | Status |
|---|---|---|---|
| 1918 | 1918 | OpenAI /v1/chat/completions stream — resolveNoResponseFallback |
✅ unchanged |
| 1999 | 1999 | OpenAI /v1/chat/completions non-stream — resolveNoResponseFallback |
✅ unchanged |
| 2181 | 2181 | Anthropic /v1/messages stream — resolveNoResponseFallback |
✅ unchanged |
| 2273 | 2273 | Anthropic /v1/messages non-stream — resolveNoResponseFallback |
✅ unchanged |
| 314 (constant) | 314 | const PROVIDER_ISSUE_CHAT_REPLY = "Sorry, I'm having a provider issue" |
✅ unchanged |
| 317 (alias) | 317 | const GENERIC_NO_RESPONSE_CHAT_REPLY = PROVIDER_ISSUE_CHAT_REPLY |
✅ unchanged |
Conclusion: Phase 4 rename is safe to execute. All four trigger
paths and the constant location MASTER.md identified still exist
verbatim. The rename PROVIDER_ISSUE_CHAT_REPLY → NO_RESPONSE_FALLBACK_REPLY
should land at lines 314, 317, 441 (getProviderIssueChatReply), 445
(return PROVIDER_ISSUE_CHAT_REPLY), and 515 (text comparison) — 5
literal occurrences in chat-routes.ts.
Layer-4 implication: client-base.ts:38's GENERIC_NO_RESPONSE_TEXT
is a parallel renderer-side fallback string that currently reads
"Sorry, I couldn't generate a response right now. Please try again."
— different copy, same purpose. Phase 4 should align both: one
constant per concept, named for what it is (no-response), used by both
the agent fallback and the renderer's "I got nothing back" branch.
Zero zod schemas exist in this layer. validate-zod-style
validation is hand-rolled per route:
auth-session-routes.tsdefinesDISPLAY_NAME_REregex inline (line 66) andisValidDisplayNamepredicate (line 68).secrets-manager-routes.tsdefinesisInstallableBackendpredicate (line 111).automations-compat-routes.tsvalidates by checkingArray.isArray+typeofper field.wallet-export-guard.tsvalidates the body shape via the upstreamWalletExportRequestBodytype but never validates at runtime — relies on the guard rejection function.character-routes.tsis the only route that uses a schema (CharacterSchema.safeParse(body) as never), and even that schema lives in@elizaos/agentnot in this layer.
Violation of commandment 7: validation lives inside the use case
(after readCompatJsonBody returns), not at the route boundary. The
correct shape per commandment 7 is:
route handler: validate body via zod schema → typed input
use case: trust pre-validated input → produce DTO
The current shape is:
route handler: read raw body → pass typeof-checked-but-not-typed body to inline logic
inline logic: reach into body fields with typeof guards
Recommendation: before splitting any route file, introduce a
schemas/ folder under src/api/ with one zod schema per route,
shared between client (validation pre-send) and server (validation
post-receive). This matches the agent runtime's CharacterSchema
pattern.
auth.ts:151 defines CSRF_REQUIRED_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"])
and ensureCompatApiAuthorizedAsync enforces a CSRF header for
cookie-bound state-changing requests. Bearer-auth requests are
exempt (lines 219-271).
Verified gates per state-mutating handler:
auth-session-routes.ts— usesensureSessionForRequest. CSRF enforced for cookie auth viaensureRouteAuthorizedfor non-cookie-mint routes; explicitskipCsrf: truefor setup/login (correct — no session exists yet to mint a CSRF token from).auth-bootstrap-routes.ts—POST /api/auth/bootstrap/exchangedoes NOT require CSRF (correct — token exchange happens before any session exists).secrets-manager-routes.ts— every state-mutating route routes throughensureRouteAuthorized(verify in detailed pass).dev-compat-routes.ts— only GETs; CSRF not applicable.wallet-market-overview-route.ts— only GETs; CSRF not applicable.onboarding-routes.ts—POST /api/onboardingusesensureRouteAuthorized(line 109) — CSRF enforced via the gate's default behaviour.
Confirmed gap: the route wrappers (agent-admin-routes.ts,
character-routes.ts, permissions-routes.ts, etc) delegate to
upstream @elizaos/agent handlers. CSRF enforcement depends on the
upstream's gate choice — needs a Layer 6 audit trace.
api/server-html.ts(8 LOC) — pass-through wrapper aroundinjectApiBaseIntoHtmlfrom@elizaos/agent. Delete and have consumers import from@elizaos/agentdirectly.api/cloud-secrets.ts(13 LOC) — re-export shim for@elizaos/plugin-elizacloud/lib/cloud-secrets. Delete after consumer migration.api/cloud-connection.ts(22 LOC) — re-export shim for@elizaos/plugin-elizacloud/lib/cloud-connection. Delete after consumer migration.api/server-cloud-tts.ts(21 LOC) — re-export shim. Same as above.api/trusted-local-request.ts(211 LOC) — duplicate helpers fromcompat-route-shared.ts. Merge or delete.api/auth/legacy-bearer.ts(188 LOC) — entire module is the 14-day grace window forELIZA_API_TOKEN. Delete once all current deploys are post-grace, plus the call sites inauth.ts:233-269,auth-context.ts:127-152.onboarding-routes.ts:77-96scheduleCloudApiKeyResave—setTimeout(... 3000)workaround "after upstream handler clobbered it". Fix the upstream handler in Layer 6, then delete this. Also delete the swallowedtry { ... } catch { /* Non-fatal */ }.server.ts:138-143— narrative comments about prior refactor (Wallet market overview → plugin-wallet, Steward compat → app-steward). These comments add nothing readers couldn't get fromgit log.server.ts:176_PACKAGE_ROOT_NAMES— underscore-prefixed, unused inside the file.server-startup.ts:13has the same set. Delete the duplicate in server.ts (and fix the duplicated"eliza"entry in server-startup.ts).server.ts:579-636_getTableColumnNames— 57-LOC underscore- prefixed function that suggests "kept for future use." Verify withknipit's truly unused, then delete.
-
Collapse the 6 auth gate variants in
auth.tsto 2. The canonical async gateensureRouteAuthorizedis the right interface;ensureCompatApiAuthorized(sync bearer-only) should remain only as the boot-path fallback.ensureCompatSensitiveRouteAuthorized,ensureCompatApiAuthorizedAsync(now reachable only viaensureRouteAuthorized), andensureAuthSessionOrBootstrapare internal — un-export them. Target: 2 exported gate functions, with the rest as private helpers. Why: every "is this caller allowed" decision should hit one or two functions; six entry points means six audit surfaces. -
Introduce a
schemas/folder + zod validation at the route boundary to satisfy commandment 7. Start with the highest-traffic routes (auth-session-routes,secrets-manager-routes,automations-compat-routes). Each schema becomes the single source of truth for the route's input shape and feeds both runtime validation and the typed client. Why: removes hand-rolledtypeof/isArrayper-field validation; makes the boundary contract visible; makes the client / server impossible to drift. -
Split
plugins-routes.ts(1651 LOC) into 3 modules.plugins/registry.ts(manifest + drift analysis),plugins/mutations.ts(validate + persist + reconcile),plugins-routes.ts(the actual route mux ≤ 200 LOC). Same pattern forclient-agent.ts(2800 LOC, 14+ concerns) andclient-cloud.ts(1790 LOC). Why: single-concern modules are auditable; god-files are not. -
Extract
wallet-market-overview-route.ts(772 LOC) to a service. The cache, in-flight dedupe, refresh rate-limit buckets, source availability, and CoinGecko + Polymarket adapters all belong toservices/wallet-market-overview.ts. The route handler should be ~30 LOC: parse query → call service → respond. Same pattern forsecrets-manager-routes.ts's install-job orchestration. Why: commandment 4 (BFF is auth + proxy, nothing else) — these route files currently ARE the business logic. -
Merge
trusted-local-request.tsintocompat-route-shared.ts(or vice versa) and delete the duplicate. The 5+ shared helpers (isLoopbackRemoteAddress,firstHeaderValue,headerValues,isClientIpProxyHeaderName,extractForwardedForCandidates,extractProxyClientAddressCandidates,CLIENT_IP_PROXY_HEADERSset, etc) are byte-identical between the two files. Why: AGENTS.md axis 1 — "true duplication that should be unified."
-
console.error/console.warnin 5+ places wherelogger.error/logger.warnis the project standard (commandment 9):auth-context.ts:135,149,auth-bootstrap-routes.ts(3 sites),wallet-export-guard.ts:90,compat-route-shared.tsbody-reader. -
In-process rate limiters reimplemented per file —
auth.ts:71-113,auth-session-routes.ts:78-111,auth/sensitive-rate-limit.ts(single-class registry — best of the three),wallet-export-guard.ts:34-52,auth-pairing-routes.ts:36-51,wallet-market-overview-route.ts:88-91. 6 different limiter implementations; one shared limiter (Layer 5 utility) would replace all six. -
5 module-level singletons via
setInterval(...).unref()sweep pattern. Same pattern:Map<key, {count|expiresAt, ...}>+ 5-min sweep timer +.unref(). Should be one sharedtiny-cache.ts. -
7
as unknown/as nevercasts that hide an upstream signature mismatch. Each one signals "the upstream context shape doesn't match the wrapper's needed shape" — fix the upstream signature, not the caller. -
Hand-rolled fetch+headers+token logic appears in
server.ts(compat loopback),onboarding-routes.ts:65,client-base.ts,csrf-client.ts:fetchWithCsrf,auth-client.ts. Should be one request helper used everywhere. -
server.ts:705 handleCompatRouteis a 246-LOC monolithic router that fans out to 14 handlers. Could be aMap<pathPrefix, handler>with a single dispatch loop — would convert 246 LOC ofif (path === ...)chains into a 30-LOC dispatch.
| File | Violating concern | Belongs in |
|---|---|---|
wallet-market-overview-route.ts |
Cache + in-flight dedupe + rate buckets + CoinGecko/Polymarket adapters | services/wallet-market-overview.ts |
plugins-routes.ts |
Plugin manifest analysis + drift detection + mutation persistence | services/plugin-manifest.ts |
automations-compat-routes.ts |
250-LOC STATIC_AUTOMATION_NODE_SPECS literal list |
data/automation-nodes.ts |
secrets-manager-routes.ts |
Install-job orchestration + SSE stream | services/secrets-installer.ts |
auth/cloud-sso.ts |
OAuth state map + sweep + JWKS verify + identity-link logic | split into 3 modules |
client-agent.ts |
14 unrelated client concerns (lifecycle/auth/config/connectors/triggers/training/plugins/streaming/PTY/logs/character/permissions/updates/blockers) | client-agent-{lifecycle,config,plugins,...}.ts |
server.ts:188-205 hydrateWalletOsStoreFlagFromConfig |
Config hydration in the API entry file | startup hook in Layer 3 (runtime/eliza.ts or dev-server.ts) |
server.ts:363-418 clearCompatRuntimeStateViaApi |
Calls own /api endpoints via loopback to clear state during reset | services/runtime-reset.ts |
client-base.ts:38 GENERIC_NO_RESPONSE_TEXT |
Renderer-side parallel of agent's PROVIDER_ISSUE_CHAT_REPLY |
one constant in @elizaos/shared |
| File:line | Literal | Status |
|---|---|---|
server.ts:304 |
"31337" (loopback default) |
OK — matches Layer 1 default |
server-cors.ts:14 |
"31337" |
OK — same default |
server-cors.ts:15 |
"2138" |
OK — UI port default |
server-cors.ts:16 |
"18789" |
OK — gateway default |
server-cors.ts:17 |
"2142" |
OK — home default |
server-cors.ts:22 |
5174..5200 range |
OK — Electrobun static server range (matches Layer 1's note) |
client-base.ts:42-47 |
ELIZA_CLOUD_CONTROL_PLANE_HOSTS set |
OK — production cloud hosts |
cloud.ts (DEFAULT_DIRECT_CLOUD_BASE_URL) |
"https://www.elizacloud.ai", "https://api.elizacloud.ai" |
OK — production cloud endpoints |
No port mis-defaults found in this layer.