import { desktopAssert, desktopId, desktopProjectBody } from './contract.mjs' export const taskUuid = value => typeof value === 'string' && /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(value) export const taskDigest = value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value) export const taskBusy = ['queued', 'running', 'received', 'unknown'] const text = (value, max) => typeof value === 'string' && value.length <= max const taskId = value => value === null || typeof value === 'string' && /^[A-Za-z0-9_-]{1,160}$/.test(value) export function validateCreationTask(value, projectId) { desktopAssert(value && taskUuid(value.id) && ['facts','points','plan','image'].includes(value.stage), '任务编号或类型无效') const document = desktopProjectBody({ document: structuredClone(value.document), status: 'draft', revision: 0 }).document desktopAssert(document.version === 2 && document.id === desktopId(projectId), '任务必须属于当前创作项目') desktopAssert(value.input && taskDigest(value.input.hash) && text(value.input.prompt, 40000) && taskDigest(value.keyHash), '任务检查点无效') desktopAssert(value.model && value.model.baseUrl === 'https://api.vip98k.com/v1' && taskDigest(value.model.fingerprint) && text(value.model.id, 200) && text(value.model.key, 200) && value.model.protocol, '模型执行契约无效') // Project/task snapshots must never become a credential transport. const encoded = JSON.stringify(value) desktopAssert(encoded.length < 650000 && !/"(?:api_?key|access_?token|authorization|password|secret)"\s*:/i.test(encoded), '任务包含敏感字段或体积过大') if (value.stage === 'image') desktopAssert(document.plan.status === 'confirmed' && document.plan.screens.some(screen => screen.id === value.screenId) && taskDigest(value.input.screenHash) && taskDigest(value.input.planHash), '先确认当前页面方案') return { id: value.id, stage: value.stage, model: value.model, document, input: value.input, keyHash: value.keyHash, screenId: value.screenId || null, batchId: value.batchId || null, status: 'queued', dispatched: false, requestId: null, remoteTaskId: null, text: null, artifact: null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), message: '已登记客户端任务', applied: false } } export function validateTaskPatch(task, patch, recovery) { desktopAssert(patch && typeof patch === 'object' && Object.keys(patch).every(key => ['status','dispatched','requestId','remoteTaskId','text','artifact','summary','result','applied','message'].includes(key)), '不允许改写原任务输入') const result = { ...task, ...patch } desktopAssert(['queued','running','received','succeeded','failed','unknown','cancelled'].includes(result.status) && text(result.message, 500), '任务状态无效') desktopAssert(!task.dispatched || result.dispatched === true, '已发送的请求不能退回未发送') desktopAssert(!recovery || result.dispatched === task.dispatched, '恢复任务只允许读取原结果') desktopAssert(taskId(result.requestId) && taskId(result.remoteTaskId) && (!task.requestId || task.requestId === result.requestId) && (!task.remoteTaskId || task.remoteTaskId === result.remoteTaskId), '不能替换原请求编号') desktopAssert(!result.dispatched || result.status !== 'queued' && result.status !== 'cancelled', '已发送请求不能标记为取消执行') desktopAssert(!['succeeded','failed','cancelled'].includes(task.status)||recovery, '任务已结束') if (result.text !== null && result.text !== undefined) desktopAssert(text(result.text,120000), '原响应过大') if (result.artifact) desktopAssert(/^local:[a-zA-Z0-9-]{1,100}$/.test(result.artifact.source || '') && taskDigest(result.artifact.sha256) && Number.isInteger(result.artifact.width) && Number.isInteger(result.artifact.height) && result.artifact.width > 0 && result.artifact.height > 0 && result.artifact.width * result.artifact.height <= 24000000, '图片检查点无效') const encoded = JSON.stringify(result) desktopAssert(encoded.length < 850000 && !/"(?:api_?key|access_?token|authorization|password|secret)"\s*:/i.test(encoded), '任务结果包含敏感字段或体积过大') return result }