fix: harden agent and WeChat run completion

This commit is contained in:
john
2026-07-23 10:24:21 +08:00
parent d81e798b28
commit a69e2766ed
5 changed files with 216 additions and 116 deletions
+33 -17
View File
@@ -488,7 +488,7 @@ export function createAgentRunGateway({
return true;
}
async function markRun(runId, status, fields = {}) {
async function markRun(runId, status, fields = {}, { expectedStatus = null } = {}) {
const updates = ['status = ?', 'updated_at = ?'];
const values = [status, nowMs()];
for (const [key, value] of Object.entries(fields)) {
@@ -496,12 +496,18 @@ export function createAgentRunGateway({
values.push(value);
}
values.push(runId);
await pool.query(
`UPDATE h5_agent_runs SET ${updates.join(', ')} WHERE id = ?`,
const where = expectedStatus
? 'WHERE id = ? AND status = ?'
: 'WHERE id = ?';
if (expectedStatus) values.push(expectedStatus);
const [result] = await pool.query(
`UPDATE h5_agent_runs SET ${updates.join(', ')} ${where}`,
values,
);
if (Number(result?.affectedRows ?? 0) === 0) return false;
await appendEvent(runId, status, fields);
await appendRunSnapshot(runId);
return true;
}
function startRunHeartbeat(runId, { attempt }) {
@@ -940,11 +946,12 @@ export function createAgentRunGateway({
throw error;
}
}
await markRun(runId, 'succeeded', {
const marked = await markRun(runId, 'succeeded', {
agent_session_id: sessionId,
completed_at: nowMs(),
error_message: null,
});
}, { expectedStatus: 'running' });
if (!marked) return false;
if (typeof observePersonalMemoryOnSuccess === 'function') {
await observePersonalMemoryOnSuccess({
userId: row.user_id,
@@ -999,7 +1006,7 @@ export function createAgentRunGateway({
await markRun(runId, retryable ? 'retryable' : 'failed', {
error_message: message,
completed_at: retryable ? null : nowMs(),
});
}, { expectedStatus: 'running' });
if (retryable && autoDispatch) {
setTimeout(() => dispatchRun(runId), retryDelaysMs[nextAttempt - 1]);
}
@@ -1129,6 +1136,7 @@ export function createAgentRunGateway({
);
const startedCutoff = nowMs() - normalizedStaleMs;
const sessionFinishedCutoff = nowMs() - normalizedSessionFinishedGraceMs;
const heartbeatCutoff = nowMs() - normalizedStaleMs;
const [rows] = await pool.query(
`SELECT
r.id,
@@ -1155,6 +1163,7 @@ export function createAgentRunGateway({
) sf ON sf.run_id = r.id
WHERE r.status = 'running'
AND r.started_at IS NOT NULL
AND (h.latest_heartbeat_at IS NULL OR h.latest_heartbeat_at <= ?)
AND (
r.started_at <= ?
OR (
@@ -1164,7 +1173,7 @@ export function createAgentRunGateway({
)
ORDER BY COALESCE(sf.session_finished_at, r.started_at) ASC
LIMIT ?`,
[startedCutoff, sessionFinishedCutoff, normalizedLimit],
[heartbeatCutoff, startedCutoff, sessionFinishedCutoff, normalizedLimit],
);
const recovered = [];
for (const row of rows) {
@@ -1198,11 +1207,12 @@ export function createAgentRunGateway({
requireRecoverableError: false,
});
if (deliverableRecovered) {
await markRun(row.id, 'succeeded', {
const marked = await markRun(row.id, 'succeeded', {
agent_session_id: row.agent_session_id ?? null,
completed_at: nowMs(),
error_message: null,
});
}, { expectedStatus: 'running' });
if (!marked) continue;
item.status = 'succeeded';
item.recoveredAs = 'deliverables';
recovered.push(item);
@@ -1223,15 +1233,21 @@ export function createAgentRunGateway({
AND started_at IS NOT NULL
AND (
started_at <= ?
OR EXISTS (
SELECT 1
FROM h5_agent_run_events sf
WHERE sf.run_id = h5_agent_runs.id
AND sf.event_type = 'session_finished'
AND sf.created_at <= ?
OR EXISTS (
SELECT 1
FROM h5_agent_run_events sf
WHERE sf.run_id = h5_agent_runs.id
AND sf.event_type = 'session_finished'
AND sf.created_at <= ?
)
)`,
[message, completedAt, completedAt, row.id, startedCutoff, sessionFinishedCutoff],
)
AND (
SELECT COALESCE(MAX(hb.created_at), h5_agent_runs.started_at)
FROM h5_agent_run_events hb
WHERE hb.run_id = h5_agent_runs.id
AND hb.event_type = 'worker_heartbeat'
) <= ?`,
[message, completedAt, completedAt, row.id, startedCutoff, sessionFinishedCutoff, heartbeatCutoff],
);
if (Number(update?.affectedRows ?? 0) === 0) continue;
await appendEvent(row.id, 'stale_recovered', {