feat: require validation receipts for h5 code runs

This commit is contained in:
Your Name
2026-07-02 11:52:40 +08:00
parent 80b762053b
commit c0f922b5f8
6 changed files with 185 additions and 21 deletions
+43 -2
View File
@@ -58,7 +58,7 @@ import type {
} from '../types';
import { CHAT_IMAGE_UPLOAD_MAX_INPUT_BYTES } from '../utils/imageUpload';
import { normalizeConversationMessages, normalizeUserMessageForApi } from '../utils/message';
import type { AgentRunCreateOptions } from '../utils/agentRunMode';
import type { AgentRunCreateOptions, AgentRunValidation } from '../utils/agentRunMode';
const API = '/api';
const DEFAULT_API_TIMEOUT_MS = 20_000;
@@ -79,6 +79,46 @@ export type AgentRun = {
completedAt: number | null;
};
function appendAgentRunValidationInstruction(message: Message, instruction?: string | null): Message {
const normalizedInstruction = String(instruction ?? '').trim();
if (!normalizedInstruction) return message;
return {
...message,
content: [
...message.content,
{ type: 'text', text: normalizedInstruction },
],
};
}
function withAgentRunValidationMetadata(
message: Message,
validation?: AgentRunValidation | null,
): Message {
if (!validation) return message;
const metadata = message.metadata as Message['metadata'] & {
memindRun?: Record<string, unknown>;
};
return {
...message,
metadata: {
...message.metadata,
memindRun: {
...(metadata.memindRun && typeof metadata.memindRun === 'object' ? metadata.memindRun : {}),
validation,
},
} as Message['metadata'],
};
}
function prepareAgentRunUserMessage(message: Message, options: AgentRunCreateOptions): Message {
const normalized = normalizeUserMessageForApi(message);
return appendAgentRunValidationInstruction(
withAgentRunValidationMetadata(normalized, options.validation),
options.toolMode === 'code' ? options.validationInstruction : null,
);
}
export class ApiError extends Error {
readonly status: number;
readonly code?: string;
@@ -2250,6 +2290,7 @@ export async function createAgentRun(
userMessage: Message,
options: AgentRunCreateOptions = {},
): Promise<AgentRun> {
const userMessagePayload = prepareAgentRunUserMessage(userMessage, options);
const result = await apiFetch<{ run: AgentRun }>(
AGENT_RUNS_PATH,
{
@@ -2257,7 +2298,7 @@ export async function createAgentRun(
body: JSON.stringify({
session_id: sessionId,
request_id: requestId,
user_message: normalizeUserMessageForApi(userMessage),
user_message: userMessagePayload,
...(options.toolMode ? { tool_mode: options.toolMode } : {}),
...(options.taskType ? { task_type: options.taskType } : {}),
}),
+1
View File
@@ -348,6 +348,7 @@ export function usePageEditSubChat({
taskType: 'page_edit_code_task',
forceCode: true,
userId: user?.id ?? null,
requestId,
}),
);
const finishedRun =
+1
View File
@@ -1176,6 +1176,7 @@ export function useTKMindChat(
resolveAgentRunOptions(trimmed, {
taskType: 'h5_chat_code_task',
userId: userRef.current?.id ?? null,
requestId,
}),
);
const finishedRun =
+47
View File
@@ -1,6 +1,18 @@
export type AgentRunCreateOptions = {
toolMode?: 'chat' | 'code';
taskType?: string | null;
validation?: AgentRunValidation | null;
validationInstruction?: string | null;
};
export type AgentRunValidationFile = {
path: string;
contains?: string;
};
export type AgentRunValidation = {
expectedFile?: AgentRunValidationFile | string;
expectedFiles?: Array<AgentRunValidationFile | string>;
};
function envFlag(value: unknown): boolean {
@@ -38,6 +50,36 @@ const CODE_TASK_PATTERNS = [
/(代码|仓库|项目|文件|组件|接口).{0,12}(修改|修复|重构|调试|实现|新增|编写|更新)/,
];
function sanitizeRequestIdForPath(requestId: string): string {
const normalized = String(requestId ?? '').trim().replace(/[^a-zA-Z0-9._-]/g, '-');
return normalized || 'unknown-request';
}
export function buildAgentRunValidationReceipt(requestId: string): {
validation: AgentRunValidation;
instruction: string;
} {
const normalizedRequestId = String(requestId ?? '').trim();
const safeRequestId = sanitizeRequestIdForPath(normalizedRequestId);
const receiptPath = `.memind/agent-runs/${safeRequestId}.json`;
const marker = normalizedRequestId || safeRequestId;
return {
validation: {
expectedFile: {
path: receiptPath,
contains: marker,
},
},
instruction: [
'',
'[Memind code-run validation]',
`Before finishing, create or update ${receiptPath}.`,
`The file must be valid JSON and include this requestId value: ${marker}.`,
'Do not skip this receipt even if the user task itself is complete.',
].join('\n'),
};
}
export function resolveAgentRunOptions(
text: string,
{
@@ -45,19 +87,24 @@ export function resolveAgentRunOptions(
forceCode = false,
allowAutodetect = agentCodeRunsAutodetectEnabled,
userId = null,
requestId = null,
}: {
taskType?: string;
forceCode?: boolean;
allowAutodetect?: boolean;
userId?: string | null;
requestId?: string | null;
} = {},
): AgentRunCreateOptions {
if (!agentCodeRunsEnabledForUser(userId)) return {};
const normalizedText = String(text ?? '').trim();
const shouldUseCode = forceCode || (allowAutodetect && CODE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText)));
if (!shouldUseCode) return {};
const receipt = buildAgentRunValidationReceipt(requestId ?? crypto.randomUUID());
return {
toolMode: 'code',
taskType,
validation: receipt.validation,
validationInstruction: receipt.instruction,
};
}