feat: add external agent run worker entry

This commit is contained in:
John
2026-07-02 08:02:03 +08:00
parent 3ea973968e
commit 05d29ce62d
9 changed files with 7190 additions and 5 deletions
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createAgentRunGateway } from '../agent-run-gateway.mjs';
import { createDbPool } from '../db.mjs';
import { createTkmindProxy } from '../tkmind-proxy.mjs';
import { createUserAuth } from '../user-auth.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
function loadEnvFile(filePath) {
if (!fs.existsSync(filePath)) return;
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq < 0) continue;
const key = trimmed.slice(0, eq).trim();
const value = trimmed.slice(eq + 1).trim();
if (!process.env[key]) process.env[key] = value;
}
}
function parseTargets() {
const csv = String(process.env.TKMIND_API_TARGETS ?? '').trim();
if (csv) {
return csv.split(',').map((item) => item.trim()).filter(Boolean);
}
return [process.env.TKMIND_API_TARGET ?? 'https://127.0.0.1:18006'];
}
function parseArgs(argv) {
const args = {
once: false,
status: false,
pollMs: Number(process.env.MEMIND_AGENT_RUN_WORKER_POLL_MS ?? 1000),
limit: Number(process.env.MEMIND_AGENT_RUN_WORKER_BATCH_SIZE ?? 1),
};
for (let i = 0; i < argv.length; i += 1) {
const item = argv[i];
if (item === '--once') args.once = true;
else if (item === '--status') args.status = true;
else if (item === '--poll-ms') args.pollMs = Number(argv[++i] ?? args.pollMs);
else if (item === '--limit') args.limit = Number(argv[++i] ?? args.limit);
else if (item === '--help' || item === '-h') args.help = true;
}
return args;
}
function printHelp() {
console.log([
'Usage:',
' node scripts/agent-run-worker.mjs [--once] [--status] [--poll-ms 1000] [--limit 1]',
'',
'Notes:',
' - Processes existing h5_agent_runs queued/retryable rows through the Tool Gateway queue.',
' - Does not create agent runs by itself.',
' - Set MEMIND_AGENT_RUN_AUTODISPATCH=0 on Portal only when this worker is ready to take over.',
].join('\n'));
}
loadEnvFile(process.env.MEMIND_ENV_FILE || path.join(root, '.env'));
loadEnvFile(path.join(root, '.env.local'));
const args = parseArgs(process.argv.slice(2));
if (args.help) {
printHelp();
process.exit(0);
}
const pool = createDbPool();
const userAuth = createUserAuth(pool, {
usersRoot: process.env.H5_USERS_ROOT ?? path.join(root, 'users'),
h5Root: root,
});
const apiTargets = parseTargets();
const tkmindProxy = createTkmindProxy({
apiTarget: apiTargets[0],
apiTargets,
apiSecret: process.env.TKMIND_SERVER__SECRET_KEY ?? 'local-dev-secret',
userAuth,
});
const gateway = createAgentRunGateway({
pool,
userAuth,
tkmindProxy,
autoDispatch: false,
maxConcurrentRuns: Number(process.env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY ?? 1),
runTimeoutMs: Number(process.env.MEMIND_AGENT_RUN_TIMEOUT_MS ?? 15 * 60 * 1000),
});
let stopping = false;
process.on('SIGINT', () => { stopping = true; });
process.on('SIGTERM', () => { stopping = true; });
async function runOnce() {
const result = await gateway.dispatchQueuedRuns({ limit: args.limit });
console.log(JSON.stringify({
ok: true,
mode: args.status ? 'status' : 'dispatch',
dispatched: args.status ? 0 : result.dispatched,
queue: result.queue,
}));
}
try {
if (args.status) {
console.log(JSON.stringify({ ok: true, queue: await gateway.getQueueStatus() }, null, 2));
} else if (args.once) {
await runOnce();
} else {
console.log(JSON.stringify({ ok: true, worker: 'agent-run-worker', pollMs: args.pollMs, limit: args.limit }));
while (!stopping) {
await runOnce();
await new Promise((resolve) => setTimeout(resolve, Math.max(200, args.pollMs)));
}
}
} finally {
await pool.end().catch(() => {});
}
+24
View File
@@ -119,6 +119,26 @@ async function bundleSandboxMcp() {
await run(esbuildBin, args);
}
async function bundleAgentRunWorker() {
if (!(await exists(esbuildBin))) {
throw new Error(`未找到 esbuild: ${esbuildBin}`);
}
console.log('==> 打包 agent run worker 为单文件 runtime');
const args = [
'scripts/agent-run-worker.mjs',
'--bundle',
'--platform=node',
'--format=esm',
`--target=${runtimeNodeTarget}`,
'--outfile=.runtime/portal/scripts/agent-run-worker.mjs',
'--banner:js=import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);',
];
for (const pkg of externalPackages) {
args.push(`--external:${pkg}`);
}
await run(esbuildBin, args);
}
async function copyRuntimeAssets() {
console.log('==> 拷贝运行时静态资源');
await remove(runtimeRoot);
@@ -347,6 +367,8 @@ async function writeMetadata() {
' node scripts/check-tool-runtime.mjs',
' node scripts/check-agent-code-run-entry.mjs',
' curl -sk https://mm.tkmind.cn/api/runtime/status # includes toolRuntime.queue',
' node scripts/agent-run-worker.mjs --status',
' node scripts/agent-run-worker.mjs --once',
'',
].join('\n'),
);
@@ -357,6 +379,7 @@ async function main() {
await copyRuntimeAssets();
await bundleServer();
await bundleSandboxMcp();
await bundleAgentRunWorker();
if (runtimeInstallMode === 'bundle-node-modules' && !skipNodeModules) {
await copyNodeModules();
}
@@ -368,6 +391,7 @@ async function main() {
await fs.chmod(path.join(runtimeRoot, 'scripts', 'runtime-worker-drain.mjs'), 0o755);
await fs.chmod(path.join(runtimeRoot, 'scripts', 'runtime-worker-metrics.mjs'), 0o755);
await fs.chmod(path.join(runtimeRoot, 'scripts', 'runtime-slo-report.mjs'), 0o755);
await fs.chmod(path.join(runtimeRoot, 'scripts', 'agent-run-worker.mjs'), 0o755);
await fs.chmod(path.join(runtimeRoot, 'scripts', 'install-runtime-metrics-agent.sh'), 0o755);
await fs.chmod(path.join(runtimeRoot, 'scripts', 'check-tool-runtime.mjs'), 0o755);
await fs.chmod(path.join(runtimeRoot, 'scripts', 'memind-portal-tunnel.sh'), 0o755);