Add page data delivery and publication guards

This commit is contained in:
john
2026-07-08 21:10:47 +08:00
parent 8d629e7a4e
commit eea14c1855
66 changed files with 3035 additions and 413 deletions
+77 -19
View File
@@ -11,6 +11,7 @@ import {
persistSessionTranscriptMessages,
} from './conversation-transcript-persist.mjs';
import { ensureGooseUserMessageMetadata } from './goose-message.mjs';
import { tryRecoverRunFromSessionDeliverables } from './agent-run-deliverable-check.mjs';
const DEFAULT_RUN_RETRY_DELAYS_MS = [1500, 5000, 15000];
const TERMINAL_STATUSES = new Set(['succeeded', 'failed']);
@@ -242,6 +243,7 @@ export function createAgentRunGateway({
chatIntentRouter = null,
sessionSnapshotService = null,
conversationMemoryService = null,
syncUserPagesOnSuccess = null,
retryDelaysMs = DEFAULT_RUN_RETRY_DELAYS_MS,
autoDispatch = envFlag(process.env.MEMIND_AGENT_RUN_AUTODISPATCH, true),
maxConcurrentRuns = positiveInteger(
@@ -389,6 +391,22 @@ export function createAgentRunGateway({
return projectRun(await getRunById(runId));
}
async function recoverRunFromDeliverables({ runId, userId, sessionId, err }) {
const recovered = await tryRecoverRunFromSessionDeliverables({
pool,
userId,
sessionId,
error: err,
});
if (!recovered) return false;
await appendEvent(runId, 'run_recovered_from_deliverables', {
sessionId,
deliverables: recovered.deliverables,
originalError: recovered.originalError,
});
return true;
}
async function markRun(runId, status, fields = {}) {
const updates = ['status = ?', 'updated_at = ?'];
const values = [status, nowMs()];
@@ -675,21 +693,33 @@ export function createAgentRunGateway({
&& runOptions.toolMode === 'chat'
&& typeof tkmindProxy.submitSessionReplyAndAwaitFinishForUser === 'function';
if (awaitSessionFinish) {
const finish = await tkmindProxy.submitSessionReplyAndAwaitFinishForUser(
row.user_id,
sessionId,
row.request_id,
ensureGooseUserMessageMetadata(userMessage),
{
toolMode: runOptions.toolMode,
forceDeepReasoning: runOptions.forceDeepReasoning,
timeoutMs: runTimeoutMs,
},
);
await appendEvent(runId, 'session_finished', {
sessionId,
tokenState: finish.tokenState ?? null,
});
try {
const finish = await tkmindProxy.submitSessionReplyAndAwaitFinishForUser(
row.user_id,
sessionId,
row.request_id,
ensureGooseUserMessageMetadata(userMessage),
{
toolMode: runOptions.toolMode,
forceDeepReasoning: runOptions.forceDeepReasoning,
timeoutMs: runTimeoutMs,
},
);
await appendEvent(runId, 'session_finished', {
sessionId,
tokenState: finish.tokenState ?? null,
});
} catch (err) {
if (await recoverRunFromDeliverables({
runId,
userId: row.user_id,
sessionId,
err,
})) {
return { sessionId };
}
throw err;
}
} else {
await tkmindProxy.submitSessionReplyForUser(
row.user_id,
@@ -705,6 +735,26 @@ export function createAgentRunGateway({
return { sessionId };
}
async function finalizeSuccessfulRun(runId, row, sessionId) {
await markRun(runId, 'succeeded', {
agent_session_id: sessionId,
completed_at: nowMs(),
error_message: null,
});
if (typeof syncUserPagesOnSuccess === 'function') {
await syncUserPagesOnSuccess({
userId: row.user_id,
sessionId,
runId,
}).catch((err) => {
console.warn(
'[AgentRun] workspace page deliver failed:',
err instanceof Error ? err.message : err,
);
});
}
}
async function processRun(runId) {
const row = await getRunById(runId);
if (!row || TERMINAL_STATUSES.has(row.status)) return;
@@ -721,11 +771,19 @@ export function createAgentRunGateway({
try {
const { sessionId } = await runWithTimeout(runId, () => executeRun(row, runId));
await markRun(runId, 'succeeded', {
agent_session_id: sessionId,
completed_at: nowMs(),
});
await finalizeSuccessfulRun(runId, row, sessionId);
} catch (err) {
const latest = await getRunById(runId);
const recoverySessionId = latest?.agent_session_id ?? row.agent_session_id ?? null;
if (await recoverRunFromDeliverables({
runId,
userId: row.user_id,
sessionId: recoverySessionId,
err,
})) {
await finalizeSuccessfulRun(runId, row, recoverySessionId);
return;
}
const message = err instanceof Error ? err.message : String(err);
const timedOut = err?.code === 'AGENT_RUN_TIMEOUT';
if (timedOut) {