feat(mindspace): enforce PostgreSQL user data delivery

This commit is contained in:
john
2026-07-13 15:29:29 +08:00
parent b33c943b69
commit a6620fb719
57 changed files with 2779 additions and 164 deletions
+47 -14
View File
@@ -16,7 +16,7 @@ import {
SESSION_FINISHED_STALE_GRACE_MS,
tryRecoverRunFromDeliverables,
} from './agent-run-deliverable-check.mjs';
import { isPageDataIntent } from './chat-skills.mjs';
import { isPageDataIntent, isPageGenerationIntent } from './chat-skills.mjs';
const DEFAULT_RUN_RETRY_DELAYS_MS = [1500, 5000, 15000];
const TERMINAL_STATUSES = new Set(['succeeded', 'failed']);
@@ -262,6 +262,7 @@ export function createAgentRunGateway({
syncUserPagesOnSuccess = null,
observePersonalMemoryOnSuccess = null,
isSessionExternallyBusy = null,
validateRunDeliverables = null,
retryDelaysMs = DEFAULT_RUN_RETRY_DELAYS_MS,
autoDispatch = envFlag(process.env.MEMIND_AGENT_RUN_AUTODISPATCH, true),
maxConcurrentRuns = positiveInteger(
@@ -420,7 +421,7 @@ export function createAgentRunGateway({
async function prepareRunDeliverables({ userId, sessionId, runId, runStartedAtMs = null }) {
if (typeof syncUserPagesOnSuccess !== 'function') return;
await syncUserPagesOnSuccess({
return await syncUserPagesOnSuccess({
userId,
sessionId,
runId,
@@ -619,7 +620,7 @@ export function createAgentRunGateway({
billed: Boolean(result.billing?.ok),
});
await appendRunSnapshot(runId);
return { sessionId: result.sessionId };
return { sessionId: result.sessionId, routing };
} catch (err) {
await appendEvent(runId, 'direct_chat_failed', {
code: err?.code ?? null,
@@ -673,7 +674,7 @@ export function createAgentRunGateway({
});
throw err;
}
return { sessionId: row.agent_session_id ?? null };
return { sessionId: row.agent_session_id ?? null, routing };
}
let sessionId = resolveGatewayAgentSessionId({
@@ -769,7 +770,7 @@ export function createAgentRunGateway({
sessionId,
err,
})) {
return { sessionId };
return { sessionId, routing };
}
throw err;
}
@@ -785,16 +786,17 @@ export function createAgentRunGateway({
},
);
}
return { sessionId };
return { sessionId, routing };
}
async function finalizeSuccessfulRun(runId, row, sessionId) {
async function finalizeSuccessfulRun(runId, row, sessionId, { routing = null } = {}) {
let deliveryResult = null;
if (typeof syncUserPagesOnSuccess === 'function') {
deliveryResult = await syncUserPagesOnSuccess({
userId: row.user_id,
sessionId,
runId,
runStartedAtMs: row.started_at ?? null,
});
}
const pageDataErrors = Array.isArray(deliveryResult?.pageDataBind?.errors)
@@ -806,17 +808,48 @@ export function createAgentRunGateway({
error.retryable = false;
throw error;
}
if (isPageDataIntent(extractRunMessageText(row))) {
const runMessageText = extractRunMessageText(row);
const pageDataIntent = isPageDataIntent(runMessageText)
|| routing?.suggestedSkill === 'page-data-collect';
const pageGenerationIntent = isPageGenerationIntent(runMessageText)
|| routing?.suggestedSkill === 'static-page-publish';
const requiresPageDeliverable = pageDataIntent || pageGenerationIntent;
let deliverables = null;
if (requiresPageDeliverable || typeof validateRunDeliverables === 'function') {
const latest = await getRunById(runId);
const deliverables = await prepareAndDetectSessionDeliverables({
deliverables = await prepareAndDetectSessionDeliverables({
pool,
userId: row.user_id,
sessionId,
runStartedAtMs: latest?.started_at ?? row.started_at ?? null,
workspaceRelativePaths: deliveryResult?.pageDataRelativePaths ?? [],
});
if (deliverables.pageCount < 1) {
const error = new Error('Page Data 任务未生成可交付页面,不能标记成功');
error.code = 'PAGE_DATA_DELIVERABLE_MISSING';
if (requiresPageDeliverable && deliverables.pageCount < 1) {
const error = new Error(
pageDataIntent
? 'Page Data 任务未生成可交付页面,不能标记成功'
: '页面任务未生成 public HTML 交付物,不能标记成功',
);
error.code = pageDataIntent
? 'PAGE_DATA_DELIVERABLE_MISSING'
: 'PUBLIC_PAGE_DELIVERABLE_MISSING';
error.retryable = false;
throw error;
}
}
if (typeof validateRunDeliverables === 'function') {
const validation = await validateRunDeliverables({
userId: row.user_id,
sessionId,
runId,
deliverables: deliverables ?? { pageCount: 0, publicationCount: 0, pages: [] },
});
const errors = Array.isArray(validation?.errors) ? validation.errors : [];
if (errors.length > 0) {
const error = new Error(
`页面交付违反数据存储策略:${errors.map((item) => item?.message ?? item?.code ?? 'unknown').join('; ')}`,
);
error.code = 'DELIVERABLE_DATA_STORAGE_FORBIDDEN';
error.retryable = false;
throw error;
}
@@ -856,8 +889,8 @@ export function createAgentRunGateway({
const stopHeartbeat = startRunHeartbeat(runId, { attempt: nextAttempt });
try {
const { sessionId } = await runWithTimeout(runId, () => executeRun(row, runId));
await finalizeSuccessfulRun(runId, row, sessionId);
const { sessionId, routing } = await runWithTimeout(runId, () => executeRun(row, runId));
await finalizeSuccessfulRun(runId, row, sessionId, { routing });
} catch (err) {
const latest = await getRunById(runId);
const recoverySessionId = latest?.agent_session_id ?? row.agent_session_id ?? null;