Files
qiming/qimingclaw/crates/agent-electron-client/embedded/sgrobot-digital/src/lib/qimingclawAdapter.ts
2026-06-05 17:11:10 +08:00

785 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<QimingclawSnapshot>;
getSyncStatus?: () => Promise<QimingclawSyncStatus>;
flushSync?: () => Promise<QimingclawSyncStatus>;
};
};
}
}
const STATE_KEY = 'qimingclaw:digital-employee-adapter-state';
const DEFAULT_REPORT_TIME = '18:00';
interface AdapterState {
selectedDutyIdsByDate: Record<string, string[]>;
confirmedDates: Record<string, string>;
reportScheduleTime: string;
reportGeneratedAtByDate: Record<string, string>;
profile: {
name: string;
company: string;
position: string;
currentStatus: string;
};
}
interface QimingclawServiceStatus {
running?: boolean;
error?: string;
engineType?: string;
port?: number;
serverCount?: number;
serverNames?: string[];
}
interface QimingclawSyncStatus {
running: boolean;
syncing: boolean;
enabled: boolean;
endpoint: string | null;
pending: number;
failed: number;
lastSyncAt: string | null;
lastSyncError: string | null;
}
interface QimingclawSnapshot {
generatedAt: string;
services: Record<string, QimingclawServiceStatus | null>;
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<string, { expression: string; nextRun: string; scheduleText: string }> = {
'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<T>(
path: string,
options: RequestInit = {},
): Promise<T | undefined> {
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<DigitalEmployeeWorkdayActionRequest>(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<AdapterState> } : defaultState();
} catch {
return defaultState();
}
}
function writeState(state: AdapterState): void {
window.localStorage.setItem(STATE_KEY, JSON.stringify(state));
}
async function readQimingclawSnapshot(): Promise<QimingclawSnapshot | null> {
try {
return await window.QimingClawBridge?.digital?.getSnapshot?.() ?? null;
} catch {
return null;
}
}
function parseJsonBody<T>(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<string> {
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(),
},
];
}