import type { MindSpaceChatContext, AgentCodeRunClientPolicy } from '../types'; export type AgentRunCreateOptions = { toolMode?: 'chat' | 'code'; taskType?: string | null; forceDeepReasoning?: boolean; validation?: AgentRunValidation | null; validationInstruction?: string | null; selectedAssetIds?: string[]; }; export type AgentRunValidationFile = { path: string; contains?: string; }; export type AgentRunValidation = { expectedFile?: AgentRunValidationFile | string; expectedFiles?: Array; }; function envFlag(value: unknown): boolean { const normalized = String(value ?? '').trim().toLowerCase(); return ['1', 'true', 'yes', 'on'].includes(normalized); } export const agentCodeRunsEnabled = envFlag(import.meta.env.VITE_AGENT_CODE_RUNS_ENABLED); export const agentCodeRunsAutodetectEnabled = envFlag( import.meta.env.VITE_AGENT_CODE_RUNS_AUTODETECT, ); export const agentPageDataDevAutodetectEnabled = envFlag( import.meta.env.VITE_AGENT_PAGE_DATA_DEV_AUTODETECT, ); let runtimeAgentCodeRunPolicy: AgentCodeRunClientPolicy | null = null; export function applyAgentCodeRunClientPolicy(policy: AgentCodeRunClientPolicy | null) { runtimeAgentCodeRunPolicy = policy; } function clientCodeRunsEnabled(): boolean { if (runtimeAgentCodeRunPolicy?.codeRun?.enabled != null) { return runtimeAgentCodeRunPolicy.codeRun.enabled; } return agentCodeRunsEnabled; } function clientCodeRunsAutodetectEnabled(): boolean { if (runtimeAgentCodeRunPolicy?.codeRun?.generalAutodetect != null) { return runtimeAgentCodeRunPolicy.codeRun.generalAutodetect; } return agentCodeRunsAutodetectEnabled; } function clientPageDataDevAutodetectEnabled(): boolean { if (runtimeAgentCodeRunPolicy?.codeRun?.pageDataDevAutodetect != null) { return runtimeAgentCodeRunPolicy.codeRun.pageDataDevAutodetect; } return agentPageDataDevAutodetectEnabled; } export const PAGE_DATA_DEV_TASK_TYPE = 'page_data_dev'; function parseUserIdSet(value: unknown): Set { return new Set( String(value ?? '') .split(',') .map((item) => item.trim()) .filter(Boolean), ); } const agentCodeRunUserIds = parseUserIdSet(import.meta.env.VITE_AGENT_CODE_RUNS_USER_IDS); export function agentCodeRunsEnabledForUser(userId?: string | null): boolean { if (!clientCodeRunsEnabled()) return false; if (agentCodeRunUserIds.size === 0) return true; return Boolean(userId && agentCodeRunUserIds.has(userId)); } const PAGE_DATA_DEV_TASK_PATTERNS = [ /columns_not_allowed/i, /\bpage[\s-]?data[\s-]?dev\b/i, /(?:insert|提交|写入).{0,16}(?:失败|403|报错|error)/iu, /(?:修复|修|排查|debug|fix).{0,24}(?:问卷|page[\s-]?data|数据提交|插入|admin|后台|policy|绑定|bind)/iu, /(?:问卷|page[\s-]?data|数据页|表单页).{0,24}(?:bug|坏了|不通|失败|有问题)/iu, /(?:测试|verify).{0,16}(?:没过|失败|不通过)/iu, /(?:repair|修复).{0,16}(?:bind|绑定|policy|策略)/iu, ]; export function isPageDataDevTaskText(text: string): boolean { const normalized = String(text ?? '').trim(); if (!normalized) return false; return PAGE_DATA_DEV_TASK_PATTERNS.some((pattern) => pattern.test(normalized)); } export function resolvePageDataDevTaskType(text: string): string | null { if (!clientPageDataDevAutodetectEnabled()) return null; return isPageDataDevTaskText(text) ? PAGE_DATA_DEV_TASK_TYPE : null; } const CODE_TASK_PATTERNS = [ /\b(repo|repository|branch|commit|pull request|pr|diff|patch)\b/i, /\b(aider|openhands|codex|codebase|workspace)\b/i, /\b(refactor|debug|fix bug|implement|add test|unit test)\b/i, /(修改|修复|重构|调试|实现|新增|编写|更新).{0,12}(代码|仓库|分支|文件|测试|组件|接口)/, /(代码|仓库|项目|文件|组件|接口).{0,12}(修改|修复|重构|调试|实现|新增|编写|更新)/, ]; const DEEP_REASONING_TASK_PATTERNS = [ /\b(public\/[^\s"'<>]+\.html)\b/i, /\b(html|h5|web\s?page|landing\s?page|microsite|docx|word)\b/i, /\b(write|create|generate|publish|download|export|save|edit)\b.{0,40}\b(file|page|html|docx|word|asset)\b/i, /\b(file|page|html|docx|word|asset)\b.{0,40}\b(write|create|generate|publish|download|export|save|edit)\b/i, /(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑).{0,24}(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页)/u, /(?:页面|网页|HTML|html|H5|h5|文件|文档|Word|word|docx|下载页|公开页|分享页|落地页|活动页).{0,24}(?:生成|创建|制作|做|写|设计|发布|导出|下载|保存|修改|编辑)/u, /MindSpace\/[^/\s]+\/public\/[^\s"'<>]+\.html/i, ]; function sanitizeRequestIdForPath(requestId: string): string { const normalized = String(requestId ?? '').trim().replace(/[^a-zA-Z0-9._-]/g, '-'); return normalized || 'unknown-request'; } function normalizeExpectedFile(value: AgentRunValidationFile | string): AgentRunValidationFile | null { if (typeof value === 'string') { const path = value.trim(); return path ? { path } : null; } const path = String(value?.path ?? '').trim(); if (!path) return null; return { path, ...(value.contains == null ? {} : { contains: String(value.contains) }), }; } function mergeAgentRunValidationFiles( ...items: Array ): AgentRunValidation | null { const byKey = new Map(); const addFile = (file: AgentRunValidationFile | string | null | undefined) => { if (!file) return; const normalized = normalizeExpectedFile(file); if (!normalized) return; const key = `${normalized.path}\u0000${normalized.contains ?? ''}`; byKey.set(key, normalized); }; for (const item of items) { if (!item) continue; if (typeof item === 'string' || 'path' in item) { addFile(item); continue; } addFile(item.expectedFile); for (const file of item.expectedFiles ?? []) addFile(file); } const expectedFiles = [...byKey.values()]; return expectedFiles.length ? { expectedFiles } : null; } export function normalizeAgentRunPublicHtmlPath(candidate: string): string | null { const decoded = decodeURIComponent(String(candidate ?? '')).replace(/\\/g, '/').trim(); const publicMatch = decoded.match(/(?:^|\/)(public\/[^"'<>?#\s]+\.html)\b/i); const raw = publicMatch?.[1] ?? ''; if (!raw) return null; const parts = raw .split('/') .map((part) => part.trim()) .filter(Boolean); if (parts.length < 2 || parts[0] !== 'public') return null; if (parts.some((part) => part === '.' || part === '..')) return null; return parts.join('/'); } export function extractAgentRunPublicHtmlPaths(...values: Array): string[] { const found = new Set(); const pattern = /(?:^|[\s"'([{<])((?:https?:\/\/[^\s"'<>]+|\/?MindSpace\/[^\s"'<>]+|public\/[^\s"'<>]+)\.html)\b/gi; for (const value of values) { const text = String(value ?? ''); let match: RegExpExecArray | null; while ((match = pattern.exec(text))) { const path = normalizeAgentRunPublicHtmlPath(match[1] ?? ''); if (path) found.add(path); } } return [...found]; } export function buildAgentRunTaskValidation({ requestId, taskType, text, mindspaceContext, pageEdit, }: { requestId: string; taskType: string; text: string; mindspaceContext?: MindSpaceChatContext | null; pageEdit?: { pageId?: string | null; pageTitle?: string | null } | null; }): { validation: AgentRunValidation | null; instruction: string } { const safeRequestId = sanitizeRequestIdForPath(requestId); const pageId = String(pageEdit?.pageId ?? mindspaceContext?.page?.id ?? '').trim(); const pageTitle = String(pageEdit?.pageTitle ?? mindspaceContext?.page?.title ?? '').trim(); const publicHtmlPaths = extractAgentRunPublicHtmlPaths( text, mindspaceContext?.page?.publicationUrl ?? null, ); const expectedFiles: AgentRunValidationFile[] = publicHtmlPaths.map((path) => ({ path })); const instructions: string[] = []; if (publicHtmlPaths.length > 0) { instructions.push( '', '[Memind task artifact validation]', 'The task references concrete MindSpace public HTML artifacts.', 'Before finishing, ensure these workspace-relative files exist and contain the intended result:', ...publicHtmlPaths.map((path) => `- ${path}`), ); } if (taskType === 'page_edit_code_task' && pageId) { const taskReceiptPath = `.memind/agent-runs/${safeRequestId}-page-edit.json`; expectedFiles.push({ path: taskReceiptPath, contains: pageId }); instructions.push( '', '[Memind page-edit validation]', `Before finishing this page-edit task, create or update ${taskReceiptPath}.`, 'The file must be valid JSON and include:', `- requestId: ${requestId}`, `- taskType: ${taskType}`, `- pageId: ${pageId}`, ...(pageTitle ? [`- pageTitle: ${pageTitle}`] : []), '- a brief summary of the page changes or the reason no change was needed.', ); } if (taskType === PAGE_DATA_DEV_TASK_TYPE) { const taskReceiptPath = `.memind/agent-runs/${safeRequestId}-page-data-dev.json`; expectedFiles.push({ path: taskReceiptPath, contains: PAGE_DATA_DEV_TASK_TYPE }); instructions.push( '', '[Memind page-data-dev validation]', `Before finishing this Page Data dev repair task, create or update ${taskReceiptPath}.`, 'The file must be valid JSON and include:', `- requestId: ${requestId}`, `- taskType: ${PAGE_DATA_DEV_TASK_TYPE}`, '- a brief summary of HTML/policy fixes or why no file change was needed.', 'Only modify public/*.html and .mindspace/page-data-policies/*.json unless explicitly told otherwise.', 'Do not recreate datasets or call private_data_execute unless the user pasted schema errors.', ); } return { validation: expectedFiles.length ? { expectedFiles } : null, instruction: instructions.join('\n'), }; } 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, { taskType = 'code_task', forceCode = false, allowAutodetect = clientCodeRunsAutodetectEnabled(), allowPageDataDevAutodetect = clientPageDataDevAutodetectEnabled(), userId = null, requestId = null, mindspaceContext = null, pageEdit = null, }: { taskType?: string; forceCode?: boolean; allowAutodetect?: boolean; allowPageDataDevAutodetect?: boolean; userId?: string | null; requestId?: string | null; mindspaceContext?: MindSpaceChatContext | null; pageEdit?: { pageId?: string | null; pageTitle?: string | null } | null; } = {}, ): AgentRunCreateOptions { const normalizedText = String(text ?? '').trim(); const pageDataDevTaskType = allowPageDataDevAutodetect && resolvePageDataDevTaskType(normalizedText); const effectiveTaskType = pageDataDevTaskType ?? taskType; const shouldUseDeepReasoning = forceCode || Boolean(pageDataDevTaskType) || DEEP_REASONING_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText)); if (!agentCodeRunsEnabledForUser(userId)) { return shouldUseDeepReasoning ? { forceDeepReasoning: true, taskType: effectiveTaskType } : {}; } const shouldUseCode = forceCode || Boolean(pageDataDevTaskType) || (allowAutodetect && CODE_TASK_PATTERNS.some((pattern) => pattern.test(normalizedText))); if (!shouldUseCode) { return shouldUseDeepReasoning ? { forceDeepReasoning: true, taskType: effectiveTaskType } : {}; } const normalizedRequestId = requestId ?? crypto.randomUUID(); const receipt = buildAgentRunValidationReceipt(normalizedRequestId); const taskValidation = buildAgentRunTaskValidation({ requestId: normalizedRequestId, taskType: effectiveTaskType, text: normalizedText, mindspaceContext, pageEdit, }); return { toolMode: 'code', taskType: effectiveTaskType, forceDeepReasoning: true, validation: mergeAgentRunValidationFiles(receipt.validation, taskValidation.validation), validationInstruction: `${receipt.instruction}${taskValidation.instruction}`, }; }