MCPcopy Create free account
hub / github.com/MemTensor/MemOS / dispatch

Function dispatch

apps/memos-local-plugin/server/http.ts:120–238  ·  view source on GitHub ↗
(
  req: IncomingMessage,
  res: ServerResponse,
  routes: ReturnType<typeof buildRoutes>,
  deps: ServerDeps,
  options: ServerOptions,
  log: ReturnType<typeof rootLogger.child>,
)

Source from the content-addressed store, hash-verified

118}
119
120async function dispatch(
121 req: IncomingMessage,
122 res: ServerResponse,
123 routes: ReturnType<typeof buildRoutes>,
124 deps: ServerDeps,
125 options: ServerOptions,
126 log: ReturnType<typeof rootLogger.child>,
127): Promise<void> {
128 const url = new URL(req.url ?? "/", "http://localhost");
129 const method = (req.method ?? "GET").toUpperCase();
130 let pathname = url.pathname;
131
132 const selfAgent = (options.agent ?? null) as AgentName | null;
133
134 // Backwards-compat for the old single-port "hub/peer" layout, where
135 // every URL was prefixed (`/openclaw/api/v1/...` or
136 // `/hermes/...`). New installs serve the SPA at root, but old
137 // bookmarks (and the old `AGENT_PREFIX` baked into older viewer
138 // bundles) still hit the prefixed paths.
139 //
140 // Two cases:
141 // - Prefix matches THIS agent → drop the prefix and continue
142 // dispatching internally. We must NOT 302 here, because POST /
143 // PATCH / DELETE get downgraded to GET on a 302 in most browsers,
144 // which would silently corrupt mutations.
145 // - Prefix matches the OTHER agent → that agent lives on its own
146 // port now, so 302 the user there. (We accept the request-method
147 // downgrade because cross-port redirects are inherently a "follow
148 // this link" gesture; the SPA bundle on the other port re-issues
149 // mutations from form state, not from the original POST body.)
150 for (const name of AGENT_PREFIXES) {
151 const prefix = `/${name}`;
152 if (pathname === prefix || pathname.startsWith(`${prefix}/`)) {
153 const tail = pathname.slice(prefix.length) || "/";
154 if (name === "memos" || name === selfAgent) {
155 pathname = tail;
156 break;
157 }
158 const peerPort = AGENT_DEFAULT_PORTS[name];
159 const targetHost = req.headers["host"]?.split(":")[0] ?? "127.0.0.1";
160 res.writeHead(302, {
161 Location: `http://${targetHost}:${peerPort}${tail}${url.search}`,
162 });
163 res.end();
164 return;
165 }
166 }
167
168 // Static assets first — cheapest path. Serve on GET/HEAD only.
169 // The root path falls through to the static handler which serves
170 // `index.html` (the SPA). There is no picker page: each agent owns
171 // its own port and is reachable directly. The SPA's header probes
172 // the peer's well-known port and surfaces a small link if it's up.
173 if ((method === "GET" || method === "HEAD") && !pathname.startsWith("/api/")) {
174 const served = await serveStatic(res, pathname, options);
175 if (served) return;
176 }
177

Callers 4

startHttpServerFunction · 0.70
handleLineFunction · 0.50
methods.test.tsFile · 0.50

Calls 14

serveStaticFunction · 0.85
enforceApiKeyFunction · 0.85
requireSessionFunction · 0.85
readBodyFunction · 0.85
bodyLimitForPathFunction · 0.85
writeJsonFunction · 0.85
writeMethodNotAllowedFunction · 0.85
writeNotFoundFunction · 0.85
endMethod · 0.80
getExactMethod · 0.80
matchPatternMethod · 0.80
handlerMethod · 0.80

Tested by

no test coverage detected