import type { CronJob, DebugPlan, DebugRun, DebugTask, DigitalEmployeeDuty, DigitalEmployeeRunDetail, DigitalEmployeeTaskExecutionSummary, DigitalEmployeeWorkEvent, DigitalEmployeeWorkdayActionRequest, DigitalEmployeeWorkdayProjection, FlowResponse, PlanDispatchResponse, SkillSpec, } from '@/types/api'; declare global { interface Window { __QIMINGCLAW_EMBEDDED_DIGITAL__?: boolean; QimingClawBridge?: { digital?: { getSnapshot?: () => Promise; }; }; } } const STATE_KEY = 'qimingclaw:digital-employee-adapter-state'; const DEFAULT_REPORT_TIME = '18:00'; interface AdapterState { selectedDutyIdsByDate: Record; confirmedDates: Record; reportScheduleTime: string; reportGeneratedAtByDate: Record; profile: { name: string; company: string; position: string; currentStatus: string; }; } interface QimingclawServiceStatus { running?: boolean; error?: string; engineType?: string; port?: number; serverCount?: number; serverNames?: string[]; } interface QimingclawSnapshot { generatedAt: string; services: Record; sessions: { total: number; active: number; items: Array<{ id: string; title?: string; status?: string; engineType?: string; projectId?: string }>; }; } const SKILLS: SkillSpec[] = [ { skill_id: 'qimingclaw-acp-session', name: 'ACP 会话执行', version: '1.0.0', enabled: true, description: '通过 qimingclaw 的 ACP 引擎创建会话、执行任务并跟踪结果。', category: '执行', capability_level: 'core', }, { skill_id: 'qimingclaw-mcp-tools', name: 'MCP 工具编排', version: '1.0.0', enabled: true, description: '读取已配置 MCP 服务与工具,作为数字员工的工具能力池。', category: '工具', capability_level: 'core', }, { skill_id: 'qimingclaw-computer-control', name: '客户端电脑控制', version: '1.0.0', enabled: true, description: '承接 /computer/chat 执行入口,面向远程任务和本地自动化操作。', category: '桌面', capability_level: 'specialized', }, { skill_id: 'qimingclaw-artifact-report', name: '产物与日报整理', version: '1.0.0', enabled: true, description: '把执行记录、关键产物和异常汇总成数字员工日报。', category: '汇报', capability_level: 'standard', }, ]; const PLANS: DebugPlan[] = [ { plan_id: 'qimingclaw-client-readiness', title: '客户端运行状态巡检', objective: '确认 qimingclaw 客户端、Agent、MCP 和远程接入能力是否处于可执行状态。', status: 'running', created_at: todayAt(9, 0), steps: [ { step_id: 'agent-status', title: '检查 Agent 引擎状态', depends_on: [], preferred_skills: ['qimingclaw-acp-session'], tasks: [{ task_id: 'task-agent-status', title: '读取 Agent 服务状态', status: 'running' }], }, { step_id: 'mcp-status', title: '检查 MCP 工具能力', depends_on: ['agent-status'], preferred_skills: ['qimingclaw-mcp-tools'], tasks: [{ task_id: 'task-mcp-status', title: '同步 MCP 工具列表', status: 'pending' }], }, ], }, { plan_id: 'qimingclaw-remote-task-ingress', title: '远程任务接入准备', objective: '检查 lanproxy、computer server 和文件服务,为后端下发数字员工任务做准备。', status: 'pending', created_at: todayAt(9, 30), steps: [ { step_id: 'computer-server', title: '确认 /computer/chat 执行入口', depends_on: [], preferred_skills: ['qimingclaw-computer-control'], tasks: [{ task_id: 'task-computer-server', title: '检查电脑控制服务', status: 'pending' }], }, { step_id: 'file-artifact', title: '准备产物回传目录', depends_on: ['computer-server'], preferred_skills: ['qimingclaw-artifact-report'], tasks: [{ task_id: 'task-file-artifact', title: '整理执行产物通道', status: 'pending' }], }, ], }, { plan_id: 'qimingclaw-daily-report', title: '数字员工日报生成', objective: '汇总今日执行记录、服务状态和待介入事项,生成面向业务的日报。', status: 'pending', created_at: todayAt(17, 30), steps: [ { step_id: 'collect-events', title: '汇总运行事件', depends_on: [], preferred_skills: ['qimingclaw-artifact-report'], tasks: [{ task_id: 'task-collect-events', title: '整理今日运行明细', status: 'pending' }], }, { step_id: 'write-report', title: '生成日报摘要', depends_on: ['collect-events'], preferred_skills: ['qimingclaw-artifact-report'], tasks: [{ task_id: 'task-write-report', title: '生成数字员工日报', status: 'pending' }], }, ], }, ]; const PLAN_SCHEDULES: Record = { 'qimingclaw-client-readiness': { expression: '0 9 * * *', nextRun: todayAt(9, 0), scheduleText: '已定时 09:00 执行', }, 'qimingclaw-remote-task-ingress': { expression: '30 9 * * *', nextRun: todayAt(9, 30), scheduleText: '已定时 09:30 执行', }, 'qimingclaw-daily-report': { expression: '0 18 * * *', nextRun: todayAt(18, 0), scheduleText: '已定时 18:00 执行', }, }; export function isQimingclawDigitalApiEnabled(): boolean { return window.__QIMINGCLAW_EMBEDDED_DIGITAL__ === true; } export async function handleQimingclawDigitalApi( path: string, options: RequestInit = {}, ): Promise { if (!isQimingclawDigitalApiEnabled()) return undefined; const url = new URL(path, window.location.origin); const method = (options.method || 'GET').toUpperCase(); const pathname = url.pathname; if (method === 'GET' && pathname === '/api/digital-employee/workday') { return buildWorkday(url.searchParams.get('date') || todayInputDate(), await readQimingclawSnapshot()) as T; } if (method === 'POST' && pathname === '/api/digital-employee/workday/actions') { return handleWorkdayAction(parseJsonBody(options.body), await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname === '/api/scheduler/jobs') { return { jobs: buildSchedulerJobs() } as T; } if (method === 'GET' && pathname === '/api/debug/plans') { return { plans: filterPlans(url.searchParams.get('q'), await readQimingclawSnapshot()) } as T; } if (method === 'GET' && pathname === '/api/debug/tasks') { const planId = url.searchParams.get('plan_id'); return buildTasksResponse(planId, await readQimingclawSnapshot()) as T; } if (method === 'GET' && pathname.startsWith('/api/debug/plan/') && pathname.endsWith('/flow')) { return buildPlanFlow(decodeURIComponent(pathname.split('/')[4] || ''), await readQimingclawSnapshot()) as T; } if (method === 'POST' && pathname.startsWith('/api/debug/plan/') && pathname.endsWith('/dispatch')) { return dispatchPlan(decodeURIComponent(pathname.split('/')[4] || '')) as T; } if (method === 'GET' && pathname.startsWith('/api/plan/') && pathname.endsWith('/messages')) { return { messages: buildPlanMessages(decodeURIComponent(pathname.split('/')[3] || '')) } as T; } if (method === 'GET' && pathname === '/api/skill') { return { skills: SKILLS } as T; } if (method === 'POST' && pathname.startsWith('/api/skill/') && pathname.endsWith('/enabled')) { const skillId = decodeURIComponent(pathname.split('/')[3] || ''); const body = parseJsonBody<{ enabled?: boolean }>(options.body); return { ok: true, skill_id: skillId, enabled: body.enabled ?? true } as T; } return undefined; } function defaultState(): AdapterState { return { selectedDutyIdsByDate: {}, confirmedDates: {}, reportScheduleTime: DEFAULT_REPORT_TIME, reportGeneratedAtByDate: {}, profile: { name: '飞天数字员工', company: 'qimingclaw 客户端', position: '客户端任务执行员', currentStatus: 'working', }, }; } function readState(): AdapterState { try { const raw = window.localStorage.getItem(STATE_KEY); return raw ? { ...defaultState(), ...JSON.parse(raw) as Partial } : defaultState(); } catch { return defaultState(); } } function writeState(state: AdapterState): void { window.localStorage.setItem(STATE_KEY, JSON.stringify(state)); } async function readQimingclawSnapshot(): Promise { try { return await window.QimingClawBridge?.digital?.getSnapshot?.() ?? null; } catch { return null; } } function parseJsonBody(body: BodyInit | null | undefined): T { if (typeof body !== 'string' || !body.trim()) return {} as T; return JSON.parse(body) as T; } function todayInputDate(): string { const date = new Date(); return [ date.getFullYear(), String(date.getMonth() + 1).padStart(2, '0'), String(date.getDate()).padStart(2, '0'), ].join('-'); } function todayAt(hour: number, minute: number): string { const date = new Date(); date.setHours(hour, minute, 0, 0); return date.toISOString(); } function displayDate(date: string): string { return date.split('-').join('/'); } function selectedSetForDate(state: AdapterState, date: string): Set { return new Set(state.selectedDutyIdsByDate[date] ?? []); } function planStatusTone(status: string): string { if (['completed', 'succeeded', 'success'].includes(status)) return 'success'; if (['running', 'active'].includes(status)) return 'running'; if (['failed', 'error'].includes(status)) return 'danger'; return 'waiting'; } function isRunning(snapshot: QimingclawSnapshot | null, key: string): boolean { return snapshot?.services[key]?.running === true; } function serviceError(snapshot: QimingclawSnapshot | null, key: string): string | undefined { return snapshot?.services[key]?.error; } function runtimeStatusForPlan(plan: DebugPlan, snapshot: QimingclawSnapshot | null): string { if (!snapshot) return plan.status; if (plan.plan_id === 'qimingclaw-client-readiness') { if (isRunning(snapshot, 'agent') && isRunning(snapshot, 'mcp')) return 'running'; if (serviceError(snapshot, 'agent') || serviceError(snapshot, 'mcp')) return 'failed'; return 'pending'; } if (plan.plan_id === 'qimingclaw-remote-task-ingress') { if (isRunning(snapshot, 'computerServer') || isRunning(snapshot, 'lanproxy') || isRunning(snapshot, 'fileServer')) return 'running'; if (serviceError(snapshot, 'computerServer') || serviceError(snapshot, 'lanproxy') || serviceError(snapshot, 'fileServer')) return 'failed'; return 'pending'; } return plan.status; } function serviceLine(snapshot: QimingclawSnapshot | null, key: string, label: string): string { const service = snapshot?.services[key]; if (!service) return `${label}:未连接`; if (service.running) { const suffix = service.port ? `(端口 ${service.port})` : ''; return `${label}:运行中${suffix}`; } return `${label}:未运行${service.error ? `,${service.error}` : ''}`; } function withRuntimePlan(plan: DebugPlan, snapshot: QimingclawSnapshot | null): DebugPlan { return { ...plan, status: runtimeStatusForPlan(plan, snapshot) }; } function planStatusLabel(status: string): string { if (status === 'running') return '执行中'; if (status === 'completed' || status === 'succeeded') return '已完成'; if (status === 'failed') return '异常'; return '待执行'; } function buildSchedulerJobs(): CronJob[] { return PLANS.map((plan) => { const schedule = PLAN_SCHEDULES[plan.plan_id] ?? PLAN_SCHEDULES['qimingclaw-client-readiness']!; return { id: `schedule-${plan.plan_id}`, name: plan.title, expression: schedule.expression, command: `digital-employee:${plan.plan_id}`, prompt: plan.objective ?? null, job_type: 'plan_template', schedule: { expression: schedule.expression, template_id: plan.plan_id }, enabled: true, delivery: { mode: 'plan_template', template_id: plan.plan_id }, template_id: plan.plan_id, trigger_kind: 'scheduled', step_count: plan.steps?.length ?? 0, skill_ids: [...new Set((plan.steps ?? []).flatMap((step) => step.preferred_skills ?? []))], delete_after_run: false, created_at: plan.created_at, next_run: schedule.nextRun, last_run: plan.status === 'running' ? todayAt(9, 5) : null, last_status: plan.status === 'running' ? 'running' : null, last_output: null, }; }); } function buildDuties(state: AdapterState, date: string, snapshot: QimingclawSnapshot | null): DigitalEmployeeDuty[] { const selected = selectedSetForDate(state, date); return PLANS.map((rawPlan) => { const plan = withRuntimePlan(rawPlan, snapshot); const schedule = PLAN_SCHEDULES[plan.plan_id] ?? PLAN_SCHEDULES['qimingclaw-client-readiness']!; return { id: plan.plan_id, title: plan.title, summary: plan.objective ?? '等待 qimingclaw 执行链路补充任务说明。', implemented: true, selectable: true, selected: selected.has(plan.plan_id), status: plan.status, status_label: planStatusLabel(plan.status), status_tone: planStatusTone(plan.status), schedule_text: schedule.scheduleText, result_text: planResultText(plan, snapshot), conversation_id: plan.plan_id, }; }); } function planResultText(plan: DebugPlan, snapshot: QimingclawSnapshot | null): string { if (plan.plan_id === 'qimingclaw-client-readiness') { return [ serviceLine(snapshot, 'agent', 'Agent'), serviceLine(snapshot, 'mcp', 'MCP'), `活跃会话:${snapshot?.sessions.active ?? 0}`, ].join(';'); } if (plan.plan_id === 'qimingclaw-remote-task-ingress') { return [ serviceLine(snapshot, 'computerServer', 'Computer Server'), serviceLine(snapshot, 'lanproxy', 'Lanproxy'), serviceLine(snapshot, 'fileServer', 'File Server'), ].join(';'); } return plan.status === 'running' ? '正在同步客户端运行状态' : '等待任务执行'; } function buildTasks(planId?: string | null, snapshot: QimingclawSnapshot | null = null): DebugTask[] { return PLANS .filter((plan) => !planId || plan.plan_id === planId) .map((plan) => withRuntimePlan(plan, snapshot)) .flatMap((plan) => (plan.steps ?? []).flatMap((step) => (step.tasks ?? []).map((task) => ({ task_id: task.task_id, title: task.title, status: plan.status === 'running' && task.status === 'pending' ? 'running' : task.status, plan_id: plan.plan_id, step_id: step.step_id, assigned_skill_id: step.preferred_skills?.[0] ?? '', description: `${plan.title} · ${step.title}`, created_at: plan.created_at, updated_at: plan.status === 'running' ? new Date().toISOString() : plan.created_at, started_at: task.status === 'running' ? todayAt(9, 5) : null, finished_at: task.status === 'completed' ? todayAt(9, 10) : null, })))); } function buildRuns(tasks: DebugTask[]): DebugRun[] { return tasks .filter((task) => task.status === 'running' || task.status === 'completed') .map((task, index) => ({ run_id: `qimingclaw-run-${index + 1}`, task_id: task.task_id, plan_id: task.plan_id, skill_id: task.assigned_skill_id, attempt_no: 1, lifecycle: task.status === 'running' ? 'Running' : 'Succeeded', created_at: task.started_at ?? task.created_at, completed_at: task.finished_at, events: [ { event_id: `${task.task_id}-event-start`, kind: 'RunStarted', occurred_at: task.started_at ?? task.created_at, message: `${task.title} 已进入 qimingclaw 执行链路`, }, ], })); } function buildTasksResponse(planId?: string | null, snapshot: QimingclawSnapshot | null = null): { tasks: DebugTask[]; runs: DebugRun[]; total: number; truncated: boolean } { const tasks = buildTasks(planId, snapshot); return { tasks, runs: buildRuns(tasks), total: tasks.length, truncated: false }; } function filterPlans(query?: string | null, snapshot: QimingclawSnapshot | null = null): DebugPlan[] { const normalized = (query ?? '').trim().toLowerCase(); const plans = PLANS.map((plan) => withRuntimePlan(plan, snapshot)); if (!normalized) return plans; return plans.filter((plan) => ( plan.title.toLowerCase().includes(normalized) || plan.plan_id.toLowerCase().includes(normalized) || (plan.objective ?? '').toLowerCase().includes(normalized) )); } function buildEvents(duties: DigitalEmployeeDuty[], confirmed: boolean, snapshot: QimingclawSnapshot | null): DigitalEmployeeWorkEvent[] { if (!confirmed) { return [{ id: 'event-await-confirmation', time: '待确认', plan_title: '任务设置', task_title: '等待确认今日任务', step_title: '选择职责', detail: '请先确认今天要执行的任务,确认后数字员工会开始执行。', status: 'pending', status_label: '待确认', status_tone: 'waiting', updated_at: new Date().toISOString(), conversation_id: null, }]; } const selectedDuties = duties.filter((duty) => duty.selected); return selectedDuties.map((duty) => ({ id: `event-${duty.id}`, time: duty.status === 'running' ? '实时' : '待执行', plan_title: duty.title, task_title: duty.title, step_title: duty.status === 'running' ? '同步 qimingclaw 服务状态' : '等待调度', detail: duty.status === 'running' ? `${duty.result_text}。快照时间:${snapshot?.generatedAt ?? '未连接 bridge'}` : '任务已确认,将在后续阶段接入 ACP/MCP 真实执行事件。', status: duty.status === 'running' ? 'running' : 'pending', status_label: duty.status === 'running' ? '执行中' : '待执行', status_tone: duty.status === 'running' ? 'running' : 'waiting', updated_at: new Date().toISOString(), conversation_id: duty.conversation_id, })); } function buildExecutionSummaries(duties: DigitalEmployeeDuty[], events: DigitalEmployeeWorkEvent[]): DigitalEmployeeTaskExecutionSummary[] { return duties.filter((duty) => duty.selected).map((duty) => { const related = events.filter((event) => event.conversation_id === duty.conversation_id); const running = related.filter((event) => event.status === 'running').length; const success = related.filter((event) => event.status === 'completed').length; return { id: duty.id, plan_id: duty.id, title: duty.title, description: duty.summary, selected: duty.selected, total_count: Math.max(related.length, 1), success_count: success, failure_count: 0, running_count: running, waiting_count: running > 0 ? 0 : 1, latest_status_label: running > 0 ? '执行中' : '待执行', latest_status_tone: running > 0 ? 'running' : 'waiting', business_summary: duty.result_text, ai_summary: '当前由 qimingclaw adapter 生成,后续接入真实执行日志。', }; }); } function buildRunDetails(events: DigitalEmployeeWorkEvent[]): DigitalEmployeeRunDetail[] { return events.map((event) => ({ id: `run-detail-${event.id}`, time: event.time, task_name: event.task_title, step_name: event.step_title, operation_name: event.plan_title, status_label: event.status_label, status_tone: event.status_tone, business_detail: event.detail, raw_detail: event.detail, updated_at: event.updated_at, conversation_id: event.conversation_id, })); } function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): DigitalEmployeeWorkdayProjection { const state = readState(); const confirmedAt = state.confirmedDates[date] ?? null; const confirmed = Boolean(confirmedAt); const duties = buildDuties(state, date, snapshot); const events = buildEvents(duties, confirmed, snapshot); const executionSummaries = buildExecutionSummaries(duties, events); const generatedAt = state.reportGeneratedAtByDate[date] ?? null; const selectedCount = duties.filter((duty) => duty.selected).length; const runningCount = events.filter((event) => event.status === 'running').length; return { source: 'live', date, employee: { name: state.profile.name, title: '数字员工', major: 'qimingclaw 客户端任务执行', company: state.profile.company, position: state.profile.position, current_status: state.profile.currentStatus, current_status_label: state.profile.currentStatus === 'resting' ? '休息中' : '工作中', motto: '承接客户端任务,调度 ACP 与 MCP 能力完成执行。', }, workday: { confirmed, confirmed_at: confirmedAt, current_status: state.profile.currentStatus, current_status_label: state.profile.currentStatus === 'resting' ? '休息中' : '工作中', status_label: confirmed ? '运行中' : '待确认', status_tone: confirmed ? 'running' : 'waiting', summary: confirmed ? `当前日期 ${displayDate(date)} 已确认 ${selectedCount} 项任务。` : `当前日期 ${displayDate(date)} 待确认今日任务。`, bubble_text: confirmed ? '我已接入 qimingclaw 客户端执行链路,正在等待真实 Run/Event 数据接入。' : '请先确认今天要执行的任务,我会把它们映射到 qimingclaw 执行链路。', }, overview: { title: '今日执行', status_label: confirmed ? '真实数据' : '待确认', status_tone: confirmed ? 'success' : 'waiting', metrics: [ { label: '已选任务', value: String(selectedCount) }, { label: '运行记录', value: String(events.length) }, { label: '执行中', value: String(runningCount) }, { label: '异常', value: '0' }, ], }, duties, events, execution_summaries: executionSummaries, run_details: buildRunDetails(events), decisions: [], delivery: { headline: confirmed ? 'qimingclaw 执行链路已接入' : '等待确认任务设置', summary: '当前阶段已将数字员工页面切换到 qimingclaw adapter,并开始读取客户端服务状态。', lines: [ '已吸收 sgRobot 数字员工 UI 和核心展示模型。', '已建立 qimingclaw workday/plans/tasks/skills API adapter。', serviceLine(snapshot, 'agent', 'Agent'), serviceLine(snapshot, 'mcp', 'MCP'), serviceLine(snapshot, 'computerServer', 'Computer Server'), `活跃会话:${snapshot?.sessions.active ?? 0} / ${snapshot?.sessions.total ?? 0}`, ], artifacts: [ { name: '数字员工运行摘要', status: generatedAt ? 'ready' : 'pending' }, ], }, daily_report: { date, title: `${displayDate(date)} 飞天数字员工日报`, report_schedule_time: state.reportScheduleTime, status: generatedAt ? 'ready' : 'not_generated', generated_at: generatedAt, generated_by: generatedAt ? 'ai' : 'fallback', model: 'qimingclaw-adapter', source: 'qimingclaw', summary: generatedAt ? '今日已完成数字员工页面到 qimingclaw adapter 的接入,并读取了 qimingclaw 服务状态。' : '日报将在生成后汇总任务选择、运行明细和后续接入建议。', totals: { task_count: selectedCount, execution_count: events.length, success_count: 0, failure_count: 0, running_count: runningCount, }, task_sections: executionSummaries, detail_lines: events.map((event) => `${event.time} ${event.task_title}:${event.detail}`), pdf_available: false, pdf_url: null, }, }; } function handleWorkdayAction(body: DigitalEmployeeWorkdayActionRequest, snapshot: QimingclawSnapshot | null): DigitalEmployeeWorkdayProjection { const state = readState(); const date = body.date || todayInputDate(); if (body.action === 'confirm_workday') { state.selectedDutyIdsByDate[date] = body.duty_ids ?? []; state.confirmedDates[date] = new Date().toISOString(); } if (body.action === 'update_profile') { state.profile = { name: body.name || state.profile.name, company: body.company || state.profile.company, position: body.position || state.profile.position, currentStatus: body.current_status || state.profile.currentStatus, }; } if (body.action === 'update_report_schedule') { state.reportScheduleTime = body.report_schedule_time || state.reportScheduleTime; } if (body.action === 'generate_daily_report') { if (body.duty_ids) state.selectedDutyIdsByDate[date] = body.duty_ids; if (body.report_schedule_time) state.reportScheduleTime = body.report_schedule_time; state.reportGeneratedAtByDate[date] = new Date().toISOString(); } writeState(state); return buildWorkday(date, snapshot); } function buildPlanFlow(planId: string, snapshot: QimingclawSnapshot | null = null): FlowResponse { const plan = withRuntimePlan(PLANS.find((candidate) => candidate.plan_id === planId) ?? PLANS[0]!, snapshot); const timestamp = new Date().toISOString(); return { plan_id: plan.plan_id, plan_title: plan.title, plan_status: plan.status, plan_objective: plan.objective, lanes: [ { key: 'intent', label: '目标', color: '#38bdf8' }, { key: 'qimingclaw', label: 'qimingclaw', color: '#22c55e' }, { key: 'execution', label: '执行', color: '#f59e0b' }, ], steps: [ { seq: 1, lane: 'intent', label: plan.title, sub_label: plan.objective, timestamp: plan.created_at, color: '#38bdf8', source_type: 'plan', }, { seq: 2, lane: 'qimingclaw', label: '接入客户端能力', sub_label: 'ACP / MCP / computer server adapter', timestamp, color: '#22c55e', source_type: 'adapter', }, { seq: 3, lane: 'execution', label: plan.status === 'running' ? '已读取 qimingclaw 服务状态' : '等待调度', sub_label: planResultText(plan, snapshot), timestamp, color: '#f59e0b', source_type: 'run', }, ], events: [ { event_id: `${plan.plan_id}-adapter-event`, kind: 'qimingclaw_adapter', occurred_at: timestamp, message: '数字员工页面已通过 qimingclaw adapter 获取数据。', }, ], }; } function dispatchPlan(planId: string): PlanDispatchResponse { const state = readState(); const date = todayInputDate(); const selected = new Set(state.selectedDutyIdsByDate[date] ?? []); selected.add(planId); state.selectedDutyIdsByDate[date] = [...selected]; state.confirmedDates[date] = state.confirmedDates[date] ?? new Date().toISOString(); writeState(state); return { ok: true, plan_id: planId, source_plan_id: planId, dispatched_tasks: buildTasks(planId).map((task) => ({ task_id: task.task_id, title: task.title, status: task.status === 'pending' ? 'running' : task.status, })), }; } function buildPlanMessages(planId: string): Array<{ role: string; content: string; created_at?: string | null }> { const plan = PLANS.find((candidate) => candidate.plan_id === planId) ?? PLANS[0]!; return [ { role: 'user', content: `执行数字员工任务:${plan.title}`, created_at: plan.created_at, }, { role: 'assistant', content: `已接入 qimingclaw adapter。目标:${plan.objective ?? plan.title}`, created_at: new Date().toISOString(), }, ]; }