feat(client): control task agents from digital deck
This commit is contained in:
@@ -1939,6 +1939,65 @@
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
.de-task-agent-control-strip {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.de-task-agent-control {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 7px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(79,172,254,0.14);
|
||||||
|
background: rgba(247,251,255,0.9);
|
||||||
|
}
|
||||||
|
.de-task-agent-control-main {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.de-task-agent-control-main span {
|
||||||
|
color: #0c5a85;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.de-task-agent-control-main strong {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #143450;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.25;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.de-task-agent-control-main small {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #58728e;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1.25;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.de-task-agent-control-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.de-task-agent-control-actions .de-btn-sm {
|
||||||
|
min-width: 58px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.de-task-agent-cancel-btn {
|
||||||
|
color: #b42318;
|
||||||
|
border-color: rgba(180,35,24,0.22);
|
||||||
|
}
|
||||||
.de-run-detail-list-scroll {
|
.de-run-detail-list-scroll {
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import type { KeyboardEvent as ReactKeyboardEvent, MouseEvent as ReactMouseEvent } from 'react';
|
import type { KeyboardEvent as ReactKeyboardEvent, MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import { Clipboard, Clock3, Download, Eye, PlayCircle, RefreshCw } from 'lucide-react';
|
import { Clipboard, Clock3, Download, Eye, PlayCircle, RefreshCw, SkipForward, Square } from 'lucide-react';
|
||||||
import Avatar3D from '@/components/digital/Avatar3D';
|
import Avatar3D from '@/components/digital/Avatar3D';
|
||||||
import { basePath } from '@/lib/basePath';
|
import { basePath } from '@/lib/basePath';
|
||||||
import {
|
import {
|
||||||
@@ -22,6 +22,7 @@ import type {
|
|||||||
DigitalEmployeeTaskExecutionSummary,
|
DigitalEmployeeTaskExecutionSummary,
|
||||||
DigitalEmployeeWorkEvent,
|
DigitalEmployeeWorkEvent,
|
||||||
DigitalEmployeeWorkdayProjection,
|
DigitalEmployeeWorkdayProjection,
|
||||||
|
GovernanceCommandKind,
|
||||||
} from '@/types/api';
|
} from '@/types/api';
|
||||||
import type { DigitalNavigationTarget } from './navigation';
|
import type { DigitalNavigationTarget } from './navigation';
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ const DIGITAL_EMPLOYEE_TIME_ZONE = 'Asia/Shanghai';
|
|||||||
type EmployeeVisualState = 'skill' | 'browser' | 'call' | 'redial' | 'evidence' | 'decision' | 'reporting';
|
type EmployeeVisualState = 'skill' | 'browser' | 'call' | 'redial' | 'evidence' | 'decision' | 'reporting';
|
||||||
type EventStatusFilter = 'all' | 'running' | 'success' | 'danger';
|
type EventStatusFilter = 'all' | 'running' | 'success' | 'danger';
|
||||||
type TaskListFilter = 'all' | 'selected' | 'pending';
|
type TaskListFilter = 'all' | 'selected' | 'pending';
|
||||||
|
type TaskAgentControlCommand = Extract<GovernanceCommandKind, 'retry_task' | 'skip_task' | 'cancel_task'>;
|
||||||
|
|
||||||
interface DigitalPlanItem {
|
interface DigitalPlanItem {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -723,6 +725,15 @@ function taskAgentSummary(agent: DigitalEmployeeTaskAgentState): string {
|
|||||||
return `${agent.title} ${agent.status_label},下一步 ${next}`;
|
return `${agent.title} ${agent.status_label},下一步 ${next}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function taskAgentControlLabel(command: TaskAgentControlCommand): string {
|
||||||
|
switch (command) {
|
||||||
|
case 'retry_task': return '重试';
|
||||||
|
case 'skip_task': return '跳过';
|
||||||
|
case 'cancel_task': return '取消';
|
||||||
|
default: return command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isDecisionOpen(status?: string): boolean {
|
function isDecisionOpen(status?: string): boolean {
|
||||||
return ['pending', 'requested'].includes(String(status || '').toLowerCase());
|
return ['pending', 'requested'].includes(String(status || '').toLowerCase());
|
||||||
}
|
}
|
||||||
@@ -1330,6 +1341,50 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
|||||||
}
|
}
|
||||||
}, [selectedDate]);
|
}, [selectedDate]);
|
||||||
|
|
||||||
|
const controlTaskAgent = useCallback(async (
|
||||||
|
agent: DigitalEmployeeTaskAgentState,
|
||||||
|
command: TaskAgentControlCommand,
|
||||||
|
) => {
|
||||||
|
const actionKey = `task-agent:${agent.task_id}:${command}`;
|
||||||
|
setActionBusy(actionKey);
|
||||||
|
setActionError(null);
|
||||||
|
setActionNotice(null);
|
||||||
|
try {
|
||||||
|
const response = await governanceCommand({
|
||||||
|
command_kind: command,
|
||||||
|
context_refs: {
|
||||||
|
plan_id: agent.plan_id,
|
||||||
|
task_id: agent.task_id,
|
||||||
|
run_id: agent.latest_run_id,
|
||||||
|
task_agent_id: agent.agent_id,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
source: 'digital-home-task-agent',
|
||||||
|
task_agent_id: agent.agent_id,
|
||||||
|
task_id: agent.task_id,
|
||||||
|
plan_id: agent.plan_id,
|
||||||
|
latest_run_id: agent.latest_run_id,
|
||||||
|
title: agent.title,
|
||||||
|
requested_action: command,
|
||||||
|
previous_state: agent.execution_state,
|
||||||
|
next_action: agent.next_action,
|
||||||
|
},
|
||||||
|
correlation_id: `digital-home-task-agent-${agent.task_id}-${command}-${Date.now()}`,
|
||||||
|
});
|
||||||
|
if (!response.accepted) {
|
||||||
|
throw new Error(response.message || `Task Agent 命令未被接受:${response.command_id || command}`);
|
||||||
|
}
|
||||||
|
setActionNotice(`Task Agent 已${taskAgentControlLabel(command)}:${agent.title}。`);
|
||||||
|
await fetchProjection();
|
||||||
|
} catch (error) {
|
||||||
|
if (mountedRef.current) {
|
||||||
|
setActionError(error instanceof Error ? error.message : 'Task Agent 控制失败');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mountedRef.current) setActionBusy(null);
|
||||||
|
}
|
||||||
|
}, [fetchProjection]);
|
||||||
|
|
||||||
const openDuty = useCallback((planItem: DigitalPlanItem) => {
|
const openDuty = useCallback((planItem: DigitalPlanItem) => {
|
||||||
onNavigate({ tab: 'missions', date: selectedDate, missionId: planItem.conversationId || planItem.id });
|
onNavigate({ tab: 'missions', date: selectedDate, missionId: planItem.conversationId || planItem.id });
|
||||||
}, [onNavigate, selectedDate]);
|
}, [onNavigate, selectedDate]);
|
||||||
@@ -1781,6 +1836,63 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{actionableTaskAgents.length > 0 && (
|
||||||
|
<div className="de-task-agent-control-strip" aria-label="Task Agent 控制台">
|
||||||
|
{actionableTaskAgents.slice(0, 2).map((agent) => {
|
||||||
|
const retryBusy = actionBusy === `task-agent:${agent.task_id}:retry_task`;
|
||||||
|
const skipBusy = actionBusy === `task-agent:${agent.task_id}:skip_task`;
|
||||||
|
const cancelBusy = actionBusy === `task-agent:${agent.task_id}:cancel_task`;
|
||||||
|
return (
|
||||||
|
<div key={agent.agent_id} className={`de-task-agent-control de-live-feed-row--${agent.status_tone || 'info'}`}>
|
||||||
|
<div className="de-task-agent-control-main">
|
||||||
|
<span>Task Agent</span>
|
||||||
|
<strong>{agent.title}</strong>
|
||||||
|
<small>{agent.status_label} · {agent.next_action}</small>
|
||||||
|
</div>
|
||||||
|
<div className="de-task-agent-control-actions">
|
||||||
|
{agent.can_retry && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="de-btn-secondary de-btn-sm"
|
||||||
|
disabled={Boolean(actionBusy)}
|
||||||
|
title="记录重试任务命令"
|
||||||
|
onClick={() => { void controlTaskAgent(agent, 'retry_task'); }}
|
||||||
|
>
|
||||||
|
<RefreshCw size={13} aria-hidden="true" />
|
||||||
|
<span>{retryBusy ? '重试中' : '重试'}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{agent.can_skip && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="de-btn-secondary de-btn-sm"
|
||||||
|
disabled={Boolean(actionBusy)}
|
||||||
|
title="记录跳过任务命令"
|
||||||
|
onClick={() => { void controlTaskAgent(agent, 'skip_task'); }}
|
||||||
|
>
|
||||||
|
<SkipForward size={13} aria-hidden="true" />
|
||||||
|
<span>{skipBusy ? '跳过中' : '跳过'}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{agent.can_cancel && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="de-btn-secondary de-btn-sm de-task-agent-cancel-btn"
|
||||||
|
disabled={Boolean(actionBusy)}
|
||||||
|
title="记录取消任务命令"
|
||||||
|
onClick={() => { void controlTaskAgent(agent, 'cancel_task'); }}
|
||||||
|
>
|
||||||
|
<Square size={12} aria-hidden="true" />
|
||||||
|
<span>{cancelBusy ? '取消中' : '取消'}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="de-stage-feed-tools">
|
<div className="de-stage-feed-tools">
|
||||||
<span>
|
<span>
|
||||||
展示 {visibleEvents.length} / 共 {allEvents.length} 条
|
展示 {visibleEvents.length} / 共 {allEvents.length} 条
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,8 +16,8 @@
|
|||||||
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="./assets/index-C9vE8PTK.js"></script>
|
<script type="module" crossorigin src="./assets/index-BBxsxEp7.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-BgRGGB2I.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-CKIQdvco.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -1125,12 +1125,14 @@ User
|
|||||||
- Plan Agent 包含计划生命周期、托管任务数、失败任务数、下一步动作和是否可重规划。
|
- Plan Agent 包含计划生命周期、托管任务数、失败任务数、下一步动作和是否可重规划。
|
||||||
- Task Agent 包含执行状态、绑定 Skill、最新 Run、是否可 retry / skip / cancel,以及下一步动作。
|
- Task Agent 包含执行状态、绑定 Skill、最新 Run、是否可 retry / skip / cancel,以及下一步动作。
|
||||||
- 首页 overview 展示 Plan Agent、Task Agent 和可治理任务数量;播报区域优先提示可治理 Task Agent。
|
- 首页 overview 展示 Plan Agent、Task Agent 和可治理任务数量;播报区域优先提示可治理 Task Agent。
|
||||||
|
- 首页“今日执行”面板已提供 Task Agent 控制条,可对可治理任务发起重试、跳过、取消,并复用 governance command 写入本地运行状态和 outbox。
|
||||||
- `retry_task`、`skip_task`、`pause_task`、`resume_task`、`provide_task_input`、`cancel_task`、`start_run`、`cancel_run` governance command 会被 qimingclaw adapter 接受并写入正式本地 governance 记录。
|
- `retry_task`、`skip_task`、`pause_task`、`resume_task`、`provide_task_input`、`cancel_task`、`start_run`、`cancel_run` governance command 会被 qimingclaw adapter 接受并写入正式本地 governance 记录。
|
||||||
|
|
||||||
### 产出
|
### 产出
|
||||||
|
|
||||||
- qimingclaw 从“Agent 客户端”升级为“数字员工任务操作系统”。
|
- qimingclaw 从“Agent 客户端”升级为“数字员工任务操作系统”。
|
||||||
- 计划/任务执行开始具备 Plan Agent / Task Agent 控制平面投影。
|
- 计划/任务执行开始具备 Plan Agent / Task Agent 控制平面投影。
|
||||||
|
- Task Agent 的 retry / skip / cancel 不再只是提示,已经具备用户可触发的本地治理入口。
|
||||||
|
|
||||||
## 推荐实施顺序
|
## 推荐实施顺序
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user