feat(runtime): wire personal memory observation and skill runtime config
Hook shadow pipeline observation into session finish and agent runs, and expose skill runtime settings through auth and runtime status endpoints. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+60
-3
@@ -12,9 +12,11 @@ import {
|
||||
} from './conversation-transcript-persist.mjs';
|
||||
import { ensureGooseUserMessageMetadata } from './goose-message.mjs';
|
||||
import {
|
||||
prepareAndDetectSessionDeliverables,
|
||||
SESSION_FINISHED_STALE_GRACE_MS,
|
||||
tryRecoverRunFromDeliverables,
|
||||
} from './agent-run-deliverable-check.mjs';
|
||||
import { isPageDataIntent } from './chat-skills.mjs';
|
||||
|
||||
const DEFAULT_RUN_RETRY_DELAYS_MS = [1500, 5000, 15000];
|
||||
const TERMINAL_STATUSES = new Set(['succeeded', 'failed']);
|
||||
@@ -53,6 +55,17 @@ function serializeMessage(message) {
|
||||
return JSON.stringify(message ?? {});
|
||||
}
|
||||
|
||||
function extractRunMessageText(row) {
|
||||
const message = parseDbJsonColumn(row?.user_message_json, {}) ?? {};
|
||||
if (typeof message.content === 'string') return message.content;
|
||||
if (!Array.isArray(message.content)) return '';
|
||||
return message.content
|
||||
.filter((item) => item?.type === 'text')
|
||||
.map((item) => String(item.text ?? '').trim())
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
function positiveInteger(value, fallback) {
|
||||
const n = Number(value);
|
||||
if (!Number.isFinite(n) || n <= 0) return fallback;
|
||||
@@ -247,6 +260,8 @@ export function createAgentRunGateway({
|
||||
sessionSnapshotService = null,
|
||||
conversationMemoryService = null,
|
||||
syncUserPagesOnSuccess = null,
|
||||
observePersonalMemoryOnSuccess = null,
|
||||
isSessionExternallyBusy = null,
|
||||
retryDelaysMs = DEFAULT_RUN_RETRY_DELAYS_MS,
|
||||
autoDispatch = envFlag(process.env.MEMIND_AGENT_RUN_AUTODISPATCH, true),
|
||||
maxConcurrentRuns = positiveInteger(
|
||||
@@ -348,6 +363,15 @@ export function createAgentRunGateway({
|
||||
// Prevent multiple concurrent agent runs on the same Goose session, which would
|
||||
// cause replies to arrive out of order and appear garbled in the chat UI.
|
||||
if (sessionId && !isDirectChatSessionId(sessionId)) {
|
||||
if (typeof isSessionExternallyBusy === 'function' && await isSessionExternallyBusy({
|
||||
userId,
|
||||
sessionId,
|
||||
})) {
|
||||
const conflict = new Error('该会话正在完成页面交付或自动修复,请稍候再发送');
|
||||
conflict.code = 'SESSION_RUN_CONFLICT';
|
||||
conflict.status = 409;
|
||||
throw conflict;
|
||||
}
|
||||
const [activeRows] = await pool.query(
|
||||
`SELECT id FROM h5_agent_runs
|
||||
WHERE agent_session_id = ? AND status NOT IN ('succeeded', 'failed')
|
||||
@@ -765,19 +789,52 @@ export function createAgentRunGateway({
|
||||
}
|
||||
|
||||
async function finalizeSuccessfulRun(runId, row, sessionId) {
|
||||
let deliveryResult = null;
|
||||
if (typeof syncUserPagesOnSuccess === 'function') {
|
||||
deliveryResult = await syncUserPagesOnSuccess({
|
||||
userId: row.user_id,
|
||||
sessionId,
|
||||
runId,
|
||||
});
|
||||
}
|
||||
const pageDataErrors = Array.isArray(deliveryResult?.pageDataBind?.errors)
|
||||
? deliveryResult.pageDataBind.errors
|
||||
: [];
|
||||
if (pageDataErrors.length > 0) {
|
||||
const error = new Error(`Page Data 页面绑定失败:${pageDataErrors.map((item) => item?.message ?? item?.code ?? 'unknown').join('; ')}`);
|
||||
error.code = 'PAGE_DATA_DELIVERY_FAILED';
|
||||
error.retryable = false;
|
||||
throw error;
|
||||
}
|
||||
if (isPageDataIntent(extractRunMessageText(row))) {
|
||||
const latest = await getRunById(runId);
|
||||
const deliverables = await prepareAndDetectSessionDeliverables({
|
||||
pool,
|
||||
userId: row.user_id,
|
||||
sessionId,
|
||||
runStartedAtMs: latest?.started_at ?? row.started_at ?? null,
|
||||
});
|
||||
if (deliverables.pageCount < 1) {
|
||||
const error = new Error('Page Data 任务未生成可交付页面,不能标记成功');
|
||||
error.code = 'PAGE_DATA_DELIVERABLE_MISSING';
|
||||
error.retryable = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
await markRun(runId, 'succeeded', {
|
||||
agent_session_id: sessionId,
|
||||
completed_at: nowMs(),
|
||||
error_message: null,
|
||||
});
|
||||
if (typeof syncUserPagesOnSuccess === 'function') {
|
||||
await syncUserPagesOnSuccess({
|
||||
if (typeof observePersonalMemoryOnSuccess === 'function') {
|
||||
await observePersonalMemoryOnSuccess({
|
||||
userId: row.user_id,
|
||||
sessionId,
|
||||
runId,
|
||||
userMessage: parseDbJsonColumn(row.user_message_json, {}),
|
||||
}).catch((err) => {
|
||||
console.warn(
|
||||
'[AgentRun] workspace page deliver failed:',
|
||||
'[AgentRun] personal memory shadow observation failed:',
|
||||
err instanceof Error ? err.message : err,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user