Files
memind_adm/server/index.mjs
T
john 731d93843d feat(admin): add Memory V2 personal controls and Skill Runtime page
Wire admin UI and API routes to restored Memind personal memory and skill
runtime modules, including candidate review and runtime status display.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 14:00:20 +08:00

181 lines
4.8 KiB
JavaScript

import fs from 'node:fs';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { ensureArgon2Sync } from './argon2-polyfill.mjs';
import { loadProjectEnv } from './load-env.mjs';
import { parseCookies } from './local-auth.mjs';
import { createPlanSyncService } from './plan-sync.mjs';
ensureArgon2Sync();
import { bootstrapAdminServices } from './bootstrap.mjs';
import { createAdminApp } from './app.mjs';
const projectRoot = loadProjectEnv();
const pidFile = path.join(projectRoot, '.adm-api.pid');
const port = Number(process.env.ADM_API_PORT ?? 8085);
function readPidFile() {
try {
const raw = fs.readFileSync(pidFile, 'utf8').trim();
if (!raw) return null;
const pid = Number(raw);
return Number.isInteger(pid) && pid > 0 ? pid : null;
} catch {
return null;
}
}
function isProcessAlive(pid) {
if (!Number.isInteger(pid) || pid <= 0) return false;
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
function cleanupStalePidFile() {
const existingPid = readPidFile();
if (!existingPid || existingPid === process.pid) return;
if (isProcessAlive(existingPid)) return;
try {
fs.unlinkSync(pidFile);
console.warn(`Removed stale PID file ${pidFile} (pid ${existingPid})`);
} catch {}
}
function writePidFile() {
try {
fs.writeFileSync(pidFile, `${process.pid}\n`, 'utf8');
} catch (err) {
console.warn(`Failed to write PID file ${pidFile}:`, err);
}
}
function removePidFile() {
const existingPid = readPidFile();
if (existingPid !== process.pid) return;
try {
fs.unlinkSync(pidFile);
} catch {}
}
function lookupPortOwner(listenPort) {
try {
const out = execFileSync('lsof', ['-nP', '-iTCP:' + listenPort, '-sTCP:LISTEN', '-Fpc'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
});
let pid = null;
let command = null;
for (const line of out.split('\n')) {
if (line.startsWith('p')) pid = line.slice(1);
if (line.startsWith('c')) command = line.slice(1);
if (pid && command) break;
}
if (!pid) return null;
return command ? `${command} (pid ${pid})` : `pid ${pid}`;
} catch {
return null;
}
}
cleanupStalePidFile();
const ready = bootstrapAdminServices();
const planSyncService = createPlanSyncService(console);
const services = {
ready,
parseCookies,
planSyncService,
};
ready
.then((bootstrapped) => {
const {
pool,
userAuth,
llmProviderService,
assetGatewayConfigService,
plazaOps,
createOpsApi,
wechatAdmin,
loadMindSpaceConfig,
updateMindSpaceConfig,
memoryV2ConfigService,
personalMemoryCandidateStore,
skillRuntimeConfigService,
wechatScheduleLlmConfigService,
adminSystemTestService,
systemTestAccountService,
wordFilterService,
planCatalogService,
subscriptionService,
planSyncService,
USER_COOKIE,
userLoginCookies,
clearUserSessionCookie,
resolveCookieDomainForRequest,
} = bootstrapped;
Object.assign(services, {
pool,
userAuth,
llmProviderService,
assetGatewayConfigService,
plazaOps,
createOpsApi,
wechatAdmin,
loadMindSpaceConfig,
updateMindSpaceConfig,
memoryV2ConfigService,
personalMemoryCandidateStore,
skillRuntimeConfigService,
wechatScheduleLlmConfigService,
adminSystemTestService,
systemTestAccountService,
wordFilterService,
planCatalogService,
subscriptionService,
planSyncService,
USER_COOKIE,
userLoginCookies,
clearUserSessionCookie,
resolveCookieDomainForRequest,
});
const app = createAdminApp(services);
const host = process.env.ADM_API_HOST?.trim() || '127.0.0.1';
const server = app.listen(port, host, () => {
writePidFile();
console.log(`TKMind Admin API @ http://${host}:${port}`);
});
server.on('close', removePidFile);
server.on('error', (err) => {
if (err?.code === 'EADDRINUSE') {
const owner = lookupPortOwner(port);
console.error(
`Admin API 无法启动:${host}:${port} 已被占用${owner ? `,当前占用者是 ${owner}` : ''}。`,
);
console.error(`如需重启,可先停止旧进程,或修改 .env 中的 ADM_API_PORT。`);
} else {
console.error('Admin API listen failed:', err);
}
process.exit(1);
});
process.on('exit', removePidFile);
process.on('SIGINT', () => {
removePidFile();
server.close(() => process.exit(0));
});
process.on('SIGTERM', () => {
removePidFile();
server.close(() => process.exit(0));
});
globalThis.__tkmindAdminServer = server;
})
.catch((err) => {
console.error('Admin server bootstrap failed:', err);
process.exit(1);
});