feat: add external agent run worker entry
This commit is contained in:
@@ -29,6 +29,11 @@ H5_PUBLIC_BASE_URL=http://127.0.0.1:5173
|
|||||||
# Tool Gateway Queue v0(/agent/runs 后台任务队列,默认保守限流)。
|
# Tool Gateway Queue v0(/agent/runs 后台任务队列,默认保守限流)。
|
||||||
# MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=1
|
# MEMIND_AGENT_RUN_QUEUE_CONCURRENCY=1
|
||||||
# MEMIND_AGENT_RUN_TIMEOUT_MS=900000
|
# MEMIND_AGENT_RUN_TIMEOUT_MS=900000
|
||||||
|
# P6.1 外部 Tool Worker 接管入口。默认 Portal 内部 auto-dispatch 保持开启;
|
||||||
|
# 只有外部 worker 验证就绪后,才把 Portal 设为 0 并独立运行 scripts/agent-run-worker.mjs。
|
||||||
|
# MEMIND_AGENT_RUN_AUTODISPATCH=1
|
||||||
|
# MEMIND_AGENT_RUN_WORKER_POLL_MS=1000
|
||||||
|
# MEMIND_AGENT_RUN_WORKER_BATCH_SIZE=1
|
||||||
|
|
||||||
# ===== memind_adm(独立后台服务)=====
|
# ===== memind_adm(独立后台服务)=====
|
||||||
# 平台超管 /admin-api/* 与广场运营 /api/ops/v1/* 已从公网服务拆出,独立进程运行。
|
# 平台超管 /admin-api/* 与广场运营 /api/ops/v1/* 已从公网服务拆出,独立进程运行。
|
||||||
|
|||||||
@@ -47,3 +47,5 @@ Streaming runtime operations:
|
|||||||
node scripts/check-tool-runtime.mjs
|
node scripts/check-tool-runtime.mjs
|
||||||
node scripts/check-agent-code-run-entry.mjs
|
node scripts/check-agent-code-run-entry.mjs
|
||||||
curl -sk https://mm.tkmind.cn/api/runtime/status # includes toolRuntime.queue
|
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
|
||||||
|
|||||||
Executable
+6873
File diff suppressed because it is too large
Load Diff
@@ -6534,6 +6534,11 @@ function timeoutError(ms) {
|
|||||||
err.code = "AGENT_RUN_TIMEOUT";
|
err.code = "AGENT_RUN_TIMEOUT";
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
function envFlag(value, fallback = false) {
|
||||||
|
const raw = String(value ?? "").trim().toLowerCase();
|
||||||
|
if (!raw) return fallback;
|
||||||
|
return ["1", "true", "yes", "on"].includes(raw);
|
||||||
|
}
|
||||||
function normalizeAgentRunToolMode(value) {
|
function normalizeAgentRunToolMode(value) {
|
||||||
const normalized = String(value ?? "chat").trim().toLowerCase();
|
const normalized = String(value ?? "chat").trim().toLowerCase();
|
||||||
if (!normalized || normalized === "chat") return "chat";
|
if (!normalized || normalized === "chat") return "chat";
|
||||||
@@ -6589,7 +6594,7 @@ function createAgentRunGateway({
|
|||||||
userAuth: userAuth2,
|
userAuth: userAuth2,
|
||||||
tkmindProxy: tkmindProxy2,
|
tkmindProxy: tkmindProxy2,
|
||||||
retryDelaysMs = DEFAULT_RUN_RETRY_DELAYS_MS,
|
retryDelaysMs = DEFAULT_RUN_RETRY_DELAYS_MS,
|
||||||
autoDispatch = true,
|
autoDispatch = envFlag(process.env.MEMIND_AGENT_RUN_AUTODISPATCH, true),
|
||||||
maxConcurrentRuns = positiveInteger(
|
maxConcurrentRuns = positiveInteger(
|
||||||
process.env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY,
|
process.env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY,
|
||||||
DEFAULT_MAX_CONCURRENT_RUNS
|
DEFAULT_MAX_CONCURRENT_RUNS
|
||||||
@@ -6810,6 +6815,7 @@ function createAgentRunGateway({
|
|||||||
statusCounts[row.status] = Number(row.count ?? 0);
|
statusCounts[row.status] = Number(row.count ?? 0);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
autoDispatch,
|
||||||
maxConcurrentRuns,
|
maxConcurrentRuns,
|
||||||
runTimeoutMs,
|
runTimeoutMs,
|
||||||
inFlight: inFlight.size,
|
inFlight: inFlight.size,
|
||||||
@@ -6818,22 +6824,51 @@ function createAgentRunGateway({
|
|||||||
terminalStatuses: [...TERMINAL_STATUSES]
|
terminalStatuses: [...TERMINAL_STATUSES]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
async function dispatchQueuedRuns({ limit = maxConcurrentRuns } = {}) {
|
||||||
|
const dispatchLimit = Math.max(1, Number(limit) || maxConcurrentRuns);
|
||||||
|
const available = Math.max(0, maxConcurrentRuns - inFlight.size - queuedDispatches.length);
|
||||||
|
if (available <= 0) {
|
||||||
|
return {
|
||||||
|
considered: 0,
|
||||||
|
dispatched: 0,
|
||||||
|
queue: await getQueueStatus()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const take = Math.min(dispatchLimit, available);
|
||||||
|
const [rows] = await pool2.query(
|
||||||
|
`SELECT id
|
||||||
|
FROM h5_agent_runs
|
||||||
|
WHERE status IN ('queued', 'retryable')
|
||||||
|
ORDER BY updated_at ASC
|
||||||
|
LIMIT ?`,
|
||||||
|
[take]
|
||||||
|
);
|
||||||
|
for (const row of rows) {
|
||||||
|
dispatchRun(row.id);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
considered: rows.length,
|
||||||
|
dispatched: rows.length,
|
||||||
|
queue: await getQueueStatus()
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
createRun,
|
createRun,
|
||||||
getRunForUser,
|
getRunForUser,
|
||||||
dispatchRun,
|
dispatchRun,
|
||||||
getQueueStatus
|
getQueueStatus,
|
||||||
|
dispatchQueuedRuns
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// agent-run-routes.mjs
|
// agent-run-routes.mjs
|
||||||
function envFlag(value) {
|
function envFlag2(value) {
|
||||||
return ["1", "true", "yes", "on"].includes(String(value ?? "").trim().toLowerCase());
|
return ["1", "true", "yes", "on"].includes(String(value ?? "").trim().toLowerCase());
|
||||||
}
|
}
|
||||||
function createPostAgentRunsHandler({
|
function createPostAgentRunsHandler({
|
||||||
userAuth: userAuth2,
|
userAuth: userAuth2,
|
||||||
agentRunGateway: agentRunGateway2,
|
agentRunGateway: agentRunGateway2,
|
||||||
codeRunsEnabled = envFlag(process.env.MEMIND_AGENT_CODE_RUNS_ENABLED)
|
codeRunsEnabled = envFlag2(process.env.MEMIND_AGENT_CODE_RUNS_ENABLED)
|
||||||
}) {
|
}) {
|
||||||
return async function postAgentRuns(request, response) {
|
return async function postAgentRuns(request, response) {
|
||||||
try {
|
try {
|
||||||
@@ -35041,6 +35076,9 @@ async function bootstrapUserAuth() {
|
|||||||
pool: pool2,
|
pool: pool2,
|
||||||
userAuth,
|
userAuth,
|
||||||
tkmindProxy,
|
tkmindProxy,
|
||||||
|
autoDispatch: ["1", "true", "yes", "on"].includes(
|
||||||
|
String(process.env.MEMIND_AGENT_RUN_AUTODISPATCH ?? "1").trim().toLowerCase()
|
||||||
|
),
|
||||||
maxConcurrentRuns: Number(process.env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY ?? 1),
|
maxConcurrentRuns: Number(process.env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY ?? 1),
|
||||||
runTimeoutMs: Number(process.env.MEMIND_AGENT_RUN_TIMEOUT_MS ?? 15 * 60 * 1e3)
|
runTimeoutMs: Number(process.env.MEMIND_AGENT_RUN_TIMEOUT_MS ?? 15 * 60 * 1e3)
|
||||||
});
|
});
|
||||||
|
|||||||
+38
-1
@@ -35,6 +35,12 @@ function timeoutError(ms) {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function envFlag(value, fallback = false) {
|
||||||
|
const raw = String(value ?? '').trim().toLowerCase();
|
||||||
|
if (!raw) return fallback;
|
||||||
|
return ['1', 'true', 'yes', 'on'].includes(raw);
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeAgentRunToolMode(value) {
|
export function normalizeAgentRunToolMode(value) {
|
||||||
const normalized = String(value ?? 'chat').trim().toLowerCase();
|
const normalized = String(value ?? 'chat').trim().toLowerCase();
|
||||||
if (!normalized || normalized === 'chat') return 'chat';
|
if (!normalized || normalized === 'chat') return 'chat';
|
||||||
@@ -101,7 +107,7 @@ export function createAgentRunGateway({
|
|||||||
userAuth,
|
userAuth,
|
||||||
tkmindProxy,
|
tkmindProxy,
|
||||||
retryDelaysMs = DEFAULT_RUN_RETRY_DELAYS_MS,
|
retryDelaysMs = DEFAULT_RUN_RETRY_DELAYS_MS,
|
||||||
autoDispatch = true,
|
autoDispatch = envFlag(process.env.MEMIND_AGENT_RUN_AUTODISPATCH, true),
|
||||||
maxConcurrentRuns = positiveInteger(
|
maxConcurrentRuns = positiveInteger(
|
||||||
process.env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY,
|
process.env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY,
|
||||||
DEFAULT_MAX_CONCURRENT_RUNS,
|
DEFAULT_MAX_CONCURRENT_RUNS,
|
||||||
@@ -340,6 +346,7 @@ export function createAgentRunGateway({
|
|||||||
statusCounts[row.status] = Number(row.count ?? 0);
|
statusCounts[row.status] = Number(row.count ?? 0);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
autoDispatch,
|
||||||
maxConcurrentRuns,
|
maxConcurrentRuns,
|
||||||
runTimeoutMs,
|
runTimeoutMs,
|
||||||
inFlight: inFlight.size,
|
inFlight: inFlight.size,
|
||||||
@@ -349,10 +356,40 @@ export function createAgentRunGateway({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function dispatchQueuedRuns({ limit = maxConcurrentRuns } = {}) {
|
||||||
|
const dispatchLimit = Math.max(1, Number(limit) || maxConcurrentRuns);
|
||||||
|
const available = Math.max(0, maxConcurrentRuns - inFlight.size - queuedDispatches.length);
|
||||||
|
if (available <= 0) {
|
||||||
|
return {
|
||||||
|
considered: 0,
|
||||||
|
dispatched: 0,
|
||||||
|
queue: await getQueueStatus(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const take = Math.min(dispatchLimit, available);
|
||||||
|
const [rows] = await pool.query(
|
||||||
|
`SELECT id
|
||||||
|
FROM h5_agent_runs
|
||||||
|
WHERE status IN ('queued', 'retryable')
|
||||||
|
ORDER BY updated_at ASC
|
||||||
|
LIMIT ?`,
|
||||||
|
[take],
|
||||||
|
);
|
||||||
|
for (const row of rows) {
|
||||||
|
dispatchRun(row.id);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
considered: rows.length,
|
||||||
|
dispatched: rows.length,
|
||||||
|
queue: await getQueueStatus(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createRun,
|
createRun,
|
||||||
getRunForUser,
|
getRunForUser,
|
||||||
dispatchRun,
|
dispatchRun,
|
||||||
getQueueStatus,
|
getQueueStatus,
|
||||||
|
dispatchQueuedRuns,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ function createFakePool() {
|
|||||||
}
|
}
|
||||||
return [[...counts].map(([status, count]) => ({ status, count }))];
|
return [[...counts].map(([status, count]) => ({ status, count }))];
|
||||||
}
|
}
|
||||||
|
if (sql.includes('SELECT id') && sql.includes("status IN ('queued', 'retryable')")) {
|
||||||
|
const limit = Number(params[0] ?? 1);
|
||||||
|
return [[...runs.values()]
|
||||||
|
.filter((row) => ['queued', 'retryable'].includes(row.status))
|
||||||
|
.sort((a, b) => Number(a.updated_at) - Number(b.updated_at))
|
||||||
|
.slice(0, limit)
|
||||||
|
.map((row) => ({ id: row.id }))];
|
||||||
|
}
|
||||||
if (sql.includes('INSERT INTO h5_agent_runs')) {
|
if (sql.includes('INSERT INTO h5_agent_runs')) {
|
||||||
const [
|
const [
|
||||||
id,
|
id,
|
||||||
@@ -338,3 +346,77 @@ test('agent run queue status reports active database and local queue state', asy
|
|||||||
assert.equal(status.pendingDispatches, 0);
|
assert.equal(status.pendingDispatches, 0);
|
||||||
assert.equal(status.statusCounts.queued, 1);
|
assert.equal(status.statusCounts.queued, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('external worker dispatches queued runs through the same queue controls', async () => {
|
||||||
|
const pool = createFakePool();
|
||||||
|
const submitted = [];
|
||||||
|
const gateway = createAgentRunGateway({
|
||||||
|
pool,
|
||||||
|
userAuth: {},
|
||||||
|
tkmindProxy: {
|
||||||
|
async startSessionForUser() {
|
||||||
|
return { id: 'session-worker' };
|
||||||
|
},
|
||||||
|
async submitSessionReplyForUser(_userId, _sessionId, requestId) {
|
||||||
|
submitted.push(requestId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
autoDispatch: false,
|
||||||
|
retryDelaysMs: [],
|
||||||
|
maxConcurrentRuns: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run = await gateway.createRun('user-1', {
|
||||||
|
requestId: 'req-worker',
|
||||||
|
userMessage: { role: 'user', content: [] },
|
||||||
|
});
|
||||||
|
assert.equal(pool.runs.get(run.id).status, 'queued');
|
||||||
|
|
||||||
|
const result = await gateway.dispatchQueuedRuns({ limit: 10 });
|
||||||
|
assert.equal(result.dispatched, 1);
|
||||||
|
await waitFor(() => pool.runs.get(run.id)?.status === 'succeeded');
|
||||||
|
assert.deepEqual(submitted, ['req-worker']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('external worker does not dispatch more runs when local queue is full', async () => {
|
||||||
|
const pool = createFakePool();
|
||||||
|
const release = [];
|
||||||
|
const gateway = createAgentRunGateway({
|
||||||
|
pool,
|
||||||
|
userAuth: {},
|
||||||
|
tkmindProxy: {
|
||||||
|
async startSessionForUser() {
|
||||||
|
return { id: `session-${release.length + 1}` };
|
||||||
|
},
|
||||||
|
async submitSessionReplyForUser() {
|
||||||
|
await new Promise((resolve) => release.push(resolve));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
autoDispatch: false,
|
||||||
|
retryDelaysMs: [],
|
||||||
|
maxConcurrentRuns: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const run1 = await gateway.createRun('user-1', {
|
||||||
|
requestId: 'req-full-1',
|
||||||
|
userMessage: { role: 'user', content: [] },
|
||||||
|
});
|
||||||
|
const run2 = await gateway.createRun('user-1', {
|
||||||
|
requestId: 'req-full-2',
|
||||||
|
userMessage: { role: 'user', content: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const first = await gateway.dispatchQueuedRuns({ limit: 10 });
|
||||||
|
assert.equal(first.dispatched, 1);
|
||||||
|
await waitFor(() => pool.runs.get(run1.id)?.status === 'running');
|
||||||
|
assert.equal((await gateway.getQueueStatus()).inFlight, 1);
|
||||||
|
const second = await gateway.dispatchQueuedRuns({ limit: 10 });
|
||||||
|
assert.equal(second.dispatched, 0);
|
||||||
|
assert.equal(pool.runs.get(run2.id).status, 'queued');
|
||||||
|
} finally {
|
||||||
|
while (release.length > 0) release.shift()();
|
||||||
|
}
|
||||||
|
await waitFor(() => pool.runs.get(run1.id)?.status === 'succeeded');
|
||||||
|
assert.equal(pool.runs.get(run2.id).status, 'queued');
|
||||||
|
});
|
||||||
|
|||||||
@@ -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(() => {});
|
||||||
|
}
|
||||||
@@ -119,6 +119,26 @@ async function bundleSandboxMcp() {
|
|||||||
await run(esbuildBin, args);
|
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() {
|
async function copyRuntimeAssets() {
|
||||||
console.log('==> 拷贝运行时静态资源');
|
console.log('==> 拷贝运行时静态资源');
|
||||||
await remove(runtimeRoot);
|
await remove(runtimeRoot);
|
||||||
@@ -347,6 +367,8 @@ async function writeMetadata() {
|
|||||||
' node scripts/check-tool-runtime.mjs',
|
' node scripts/check-tool-runtime.mjs',
|
||||||
' node scripts/check-agent-code-run-entry.mjs',
|
' node scripts/check-agent-code-run-entry.mjs',
|
||||||
' curl -sk https://mm.tkmind.cn/api/runtime/status # includes toolRuntime.queue',
|
' 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'),
|
].join('\n'),
|
||||||
);
|
);
|
||||||
@@ -357,6 +379,7 @@ async function main() {
|
|||||||
await copyRuntimeAssets();
|
await copyRuntimeAssets();
|
||||||
await bundleServer();
|
await bundleServer();
|
||||||
await bundleSandboxMcp();
|
await bundleSandboxMcp();
|
||||||
|
await bundleAgentRunWorker();
|
||||||
if (runtimeInstallMode === 'bundle-node-modules' && !skipNodeModules) {
|
if (runtimeInstallMode === 'bundle-node-modules' && !skipNodeModules) {
|
||||||
await copyNodeModules();
|
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-drain.mjs'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'runtime-worker-metrics.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', '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', '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', 'check-tool-runtime.mjs'), 0o755);
|
||||||
await fs.chmod(path.join(runtimeRoot, 'scripts', 'memind-portal-tunnel.sh'), 0o755);
|
await fs.chmod(path.join(runtimeRoot, 'scripts', 'memind-portal-tunnel.sh'), 0o755);
|
||||||
|
|||||||
@@ -572,6 +572,9 @@ async function bootstrapUserAuth() {
|
|||||||
pool,
|
pool,
|
||||||
userAuth,
|
userAuth,
|
||||||
tkmindProxy,
|
tkmindProxy,
|
||||||
|
autoDispatch: ['1', 'true', 'yes', 'on'].includes(
|
||||||
|
String(process.env.MEMIND_AGENT_RUN_AUTODISPATCH ?? '1').trim().toLowerCase(),
|
||||||
|
),
|
||||||
maxConcurrentRuns: Number(process.env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY ?? 1),
|
maxConcurrentRuns: Number(process.env.MEMIND_AGENT_RUN_QUEUE_CONCURRENCY ?? 1),
|
||||||
runTimeoutMs: Number(process.env.MEMIND_AGENT_RUN_TIMEOUT_MS ?? 15 * 60 * 1000),
|
runTimeoutMs: Number(process.env.MEMIND_AGENT_RUN_TIMEOUT_MS ?? 15 * 60 * 1000),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user