补齐Agent任务队列观测
新增 Agent Runtime taskQueue 汇总 pending/running/completed/failed 队列状态 在 Runtime 面板、主 Agent 卡片、agent.run_status 和下一轮 prompt 展示队列摘要 补充最近动作与任务队列前端测试、Rust JSON 与连续上下文测试 同步 AI 游戏创作 App 技术方案和项目决策记录
This commit is contained in:
@@ -216,14 +216,16 @@ pub(crate) fn read_game_creator_agent_runtime_at(
|
||||
normalize_game_creator_agent_runtime_state(&mut state, &agent_id);
|
||||
let _ = refresh_game_creator_agent_runtime_tool_policy(root, &mut state);
|
||||
let recent_events = read_recent_game_creator_agent_runtime_events(&event_path)?;
|
||||
let recent_tasks = read_recent_game_creator_agent_runtime_tasks(&task_path)?;
|
||||
let task_snapshot = read_game_creator_agent_runtime_task_snapshot(&task_path)?;
|
||||
state.task_queue = task_snapshot.task_queue.clone();
|
||||
Ok(AgentRuntimeResult {
|
||||
state,
|
||||
session_path: session_path.to_string_lossy().into_owned(),
|
||||
event_path: event_path.to_string_lossy().into_owned(),
|
||||
task_path: task_path.to_string_lossy().into_owned(),
|
||||
task_queue: task_snapshot.task_queue,
|
||||
recent_events,
|
||||
recent_tasks,
|
||||
recent_tasks: task_snapshot.recent_tasks,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2142,6 +2144,7 @@ fn format_agent_runtime_status_observation(result: &AgentRuntimeResult) -> Strin
|
||||
.last()
|
||||
.map(format_agent_runtime_task_observation)
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
let task_queue = format_agent_runtime_task_queue_observation(&result.task_queue);
|
||||
let recent_tool = state
|
||||
.recent_tool_calls
|
||||
.last()
|
||||
@@ -2154,7 +2157,7 @@ fn format_agent_runtime_status_observation(result: &AgentRuntimeResult) -> Strin
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
format!(
|
||||
"agentId: {}\nstatus: {}\nphase: {}\nrunId: {}\n当前目标: {}\n当前任务: {}\n当前动作: {}\n等待: {}\n下一步: {}\n计划: {}\n最近任务: {}\n最近工具: {}\n错误: {}",
|
||||
"agentId: {}\nstatus: {}\nphase: {}\nrunId: {}\n当前目标: {}\n当前任务: {}\n当前动作: {}\n等待: {}\n下一步: {}\n计划: {}\n任务队列: {}\n最近任务: {}\n最近工具: {}\n错误: {}",
|
||||
state.agent_id,
|
||||
state.status,
|
||||
state.phase,
|
||||
@@ -2165,6 +2168,7 @@ fn format_agent_runtime_status_observation(result: &AgentRuntimeResult) -> Strin
|
||||
waiting_on,
|
||||
next_step,
|
||||
plan,
|
||||
task_queue,
|
||||
recent_task,
|
||||
recent_tool,
|
||||
error
|
||||
@@ -2180,6 +2184,18 @@ fn agent_runtime_status_text(value: &str, max_chars: usize) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn format_agent_runtime_task_queue_observation(queue: &AgentRuntimeTaskQueueSummary) -> String {
|
||||
format!(
|
||||
"total={} pending={} running={} completed={} failed={} latest={}",
|
||||
queue.total,
|
||||
queue.pending,
|
||||
queue.running,
|
||||
queue.completed,
|
||||
queue.failed,
|
||||
queue.latest_run_id.as_deref().unwrap_or("-")
|
||||
)
|
||||
}
|
||||
|
||||
fn format_agent_runtime_task_observation(task: &AgentRuntimeTaskRecord) -> String {
|
||||
format!(
|
||||
"{} / {} / {} / {}",
|
||||
@@ -2321,8 +2337,9 @@ pub(crate) fn start_game_creator_agent_runtime_task_at(
|
||||
}
|
||||
refresh_game_creator_agent_runtime_tool_policy(root, &mut state)?;
|
||||
state.updated_at = unix_timestamp();
|
||||
write_game_creator_agent_runtime_state(root, &state)?;
|
||||
append_game_creator_agent_runtime_task(root, &state)?;
|
||||
refresh_game_creator_agent_runtime_task_queue(root, &mut state)?;
|
||||
write_game_creator_agent_runtime_state(root, &state)?;
|
||||
append_game_creator_agent_runtime_event(
|
||||
root,
|
||||
&state,
|
||||
@@ -2365,8 +2382,9 @@ pub(crate) fn advance_game_creator_agent_runtime_turn_at(
|
||||
}
|
||||
refresh_game_creator_agent_runtime_tool_policy(root, &mut state)?;
|
||||
state.updated_at = unix_timestamp();
|
||||
write_game_creator_agent_runtime_state(root, &state)?;
|
||||
append_game_creator_agent_runtime_task(root, &state)?;
|
||||
refresh_game_creator_agent_runtime_task_queue(root, &mut state)?;
|
||||
write_game_creator_agent_runtime_state(root, &state)?;
|
||||
append_game_creator_agent_runtime_event(
|
||||
root,
|
||||
&state,
|
||||
@@ -2396,8 +2414,9 @@ pub(crate) fn finish_game_creator_agent_runtime_turn_at(
|
||||
.push("Agent 已完成回复,assistant 消息等待或已经由前端落盘。".to_string());
|
||||
refresh_game_creator_agent_runtime_tool_policy(root, &mut state)?;
|
||||
state.updated_at = unix_timestamp();
|
||||
write_game_creator_agent_runtime_state(root, &state)?;
|
||||
append_game_creator_agent_runtime_task(root, &state)?;
|
||||
refresh_game_creator_agent_runtime_task_queue(root, &mut state)?;
|
||||
write_game_creator_agent_runtime_state(root, &state)?;
|
||||
append_game_creator_agent_runtime_event(
|
||||
root,
|
||||
&state,
|
||||
@@ -2435,8 +2454,9 @@ pub(crate) fn fail_game_creator_agent_runtime_turn_at(
|
||||
state.error = Some(sanitize_agent_runtime_text(error, 500));
|
||||
let _ = refresh_game_creator_agent_runtime_tool_policy(root, &mut state);
|
||||
state.updated_at = unix_timestamp();
|
||||
write_game_creator_agent_runtime_state(root, &state)?;
|
||||
append_game_creator_agent_runtime_task(root, &state)?;
|
||||
refresh_game_creator_agent_runtime_task_queue(root, &mut state)?;
|
||||
write_game_creator_agent_runtime_state(root, &state)?;
|
||||
append_game_creator_agent_runtime_event(
|
||||
root,
|
||||
&state,
|
||||
@@ -2512,6 +2532,7 @@ fn default_game_creator_agent_runtime_state(agent_id: &str, run_id: &str) -> Age
|
||||
],
|
||||
observations: Vec::new(),
|
||||
recent_tool_calls: Vec::new(),
|
||||
task_queue: AgentRuntimeTaskQueueSummary::default(),
|
||||
allowed_tools: default_game_creator_agent_runtime_allowed_tools(),
|
||||
tool_policy: AgentRuntimeToolPolicySnapshot::default(),
|
||||
last_response: None,
|
||||
@@ -2991,6 +3012,15 @@ fn append_game_creator_agent_runtime_task_record(
|
||||
.map_err(|error| format!("写入 Agent Runtime 任务失败:{}: {error}", path.display()))
|
||||
}
|
||||
|
||||
fn refresh_game_creator_agent_runtime_task_queue(
|
||||
root: &Path,
|
||||
state: &mut AgentRuntimeState,
|
||||
) -> Result<(), String> {
|
||||
let task_path = game_creator_agent_runtime_task_path(root, &state.agent_id);
|
||||
state.task_queue = read_game_creator_agent_runtime_task_snapshot(&task_path)?.task_queue;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn game_creator_agent_runtime_task_status(state: &AgentRuntimeState) -> String {
|
||||
if state.error.is_some() || state.status == "failed" || state.phase == "failed" {
|
||||
"failed".to_string()
|
||||
@@ -3041,15 +3071,25 @@ fn read_recent_game_creator_agent_runtime_events(
|
||||
}
|
||||
}
|
||||
|
||||
fn read_recent_game_creator_agent_runtime_tasks(
|
||||
struct AgentRuntimeTaskSnapshot {
|
||||
task_queue: AgentRuntimeTaskQueueSummary,
|
||||
recent_tasks: Vec<AgentRuntimeTaskRecord>,
|
||||
}
|
||||
|
||||
fn read_game_creator_agent_runtime_task_snapshot(
|
||||
path: &Path,
|
||||
) -> Result<Vec<AgentRuntimeTaskRecord>, String> {
|
||||
) -> Result<AgentRuntimeTaskSnapshot, String> {
|
||||
let records = read_all_game_creator_agent_runtime_tasks(path)?;
|
||||
let mut recent = latest_game_creator_agent_runtime_tasks(records);
|
||||
let latest = latest_game_creator_agent_runtime_tasks(records);
|
||||
let task_queue = summarize_game_creator_agent_runtime_task_queue(&latest);
|
||||
let mut recent = latest;
|
||||
if recent.len() > AGENT_RUNTIME_RECENT_TASK_LIMIT {
|
||||
recent = recent[recent.len() - AGENT_RUNTIME_RECENT_TASK_LIMIT..].to_vec();
|
||||
}
|
||||
Ok(recent)
|
||||
Ok(AgentRuntimeTaskSnapshot {
|
||||
task_queue,
|
||||
recent_tasks: recent,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_next_pending_game_creator_agent_runtime_task(
|
||||
@@ -3114,6 +3154,29 @@ fn latest_game_creator_agent_runtime_tasks(
|
||||
latest
|
||||
}
|
||||
|
||||
fn summarize_game_creator_agent_runtime_task_queue(
|
||||
records: &[AgentRuntimeTaskRecord],
|
||||
) -> AgentRuntimeTaskQueueSummary {
|
||||
let mut summary = AgentRuntimeTaskQueueSummary {
|
||||
total: records.len() as u32,
|
||||
..AgentRuntimeTaskQueueSummary::default()
|
||||
};
|
||||
for record in records {
|
||||
match record.status.as_str() {
|
||||
"pending" => summary.pending += 1,
|
||||
"running" => summary.running += 1,
|
||||
"completed" => summary.completed += 1,
|
||||
"failed" => summary.failed += 1,
|
||||
_ => {}
|
||||
}
|
||||
if record.updated_at >= summary.updated_at {
|
||||
summary.updated_at = record.updated_at;
|
||||
summary.latest_run_id = Some(record.run_id.clone());
|
||||
}
|
||||
}
|
||||
summary
|
||||
}
|
||||
|
||||
fn truncate_agent_runtime_text(value: &str, max_chars: usize) -> String {
|
||||
let value = value.trim();
|
||||
let mut output = String::new();
|
||||
@@ -3227,6 +3290,12 @@ fn render_agent_runtime_prompt_context(root: &Path, agent_id: &str) -> Result<St
|
||||
redact_agent_runtime_project_paths(root, &state.next_step, 160)
|
||||
));
|
||||
}
|
||||
if runtime.task_queue.total > 0 {
|
||||
lines.push(format!(
|
||||
"任务队列:{}",
|
||||
format_agent_runtime_task_queue_observation(&runtime.task_queue)
|
||||
));
|
||||
}
|
||||
if let Some(last_response) = state
|
||||
.last_response
|
||||
.as_deref()
|
||||
|
||||
@@ -168,6 +168,8 @@ struct AgentRuntimeState {
|
||||
#[serde(default)]
|
||||
recent_tool_calls: Vec<AgentRuntimeToolCallRecord>,
|
||||
#[serde(default)]
|
||||
task_queue: AgentRuntimeTaskQueueSummary,
|
||||
#[serde(default)]
|
||||
allowed_tools: Vec<String>,
|
||||
#[serde(default)]
|
||||
tool_policy: AgentRuntimeToolPolicySnapshot,
|
||||
@@ -223,6 +225,39 @@ struct AgentRuntimeToolCallRecord {
|
||||
updated_at: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AgentRuntimeTaskQueueSummary {
|
||||
#[serde(default)]
|
||||
total: u32,
|
||||
#[serde(default)]
|
||||
pending: u32,
|
||||
#[serde(default)]
|
||||
running: u32,
|
||||
#[serde(default)]
|
||||
completed: u32,
|
||||
#[serde(default)]
|
||||
failed: u32,
|
||||
#[serde(default)]
|
||||
latest_run_id: Option<String>,
|
||||
#[serde(default)]
|
||||
updated_at: u64,
|
||||
}
|
||||
|
||||
impl Default for AgentRuntimeTaskQueueSummary {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
running: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latest_run_id: None,
|
||||
updated_at: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AgentRuntimeEvent {
|
||||
@@ -288,6 +323,7 @@ struct AgentRuntimeResult {
|
||||
session_path: String,
|
||||
event_path: String,
|
||||
task_path: String,
|
||||
task_queue: AgentRuntimeTaskQueueSummary,
|
||||
recent_events: Vec<AgentRuntimeEvent>,
|
||||
recent_tasks: Vec<AgentRuntimeTaskRecord>,
|
||||
}
|
||||
|
||||
@@ -1142,11 +1142,19 @@ async fn role_agent_legacy_alias_maps_to_canonical_task_runtime_and_route() {
|
||||
.expect("parse runtime wire json");
|
||||
assert_eq!(runtime_wire["currentGoal"], "排队规划美术资产");
|
||||
assert_eq!(runtime_wire["waitingOn"], "Agent 输出计划或回复");
|
||||
assert_eq!(runtime_wire["taskQueue"]["total"], 1);
|
||||
assert_eq!(runtime_wire["taskQueue"]["running"], 1);
|
||||
assert_eq!(
|
||||
runtime_wire["taskQueue"]["latestRunId"],
|
||||
"alias-runtime-run"
|
||||
);
|
||||
let alias_read =
|
||||
read_game_creator_agent_runtime_at(&root, "art-asset").expect("read alias runtime");
|
||||
assert_eq!(alias_read.state.agent_id, "art-asset-plan");
|
||||
assert_eq!(alias_read.state.current_goal, "排队规划美术资产");
|
||||
assert_eq!(alias_read.state.waiting_on, "Agent 输出计划或回复");
|
||||
assert_eq!(alias_read.task_queue.total, 1);
|
||||
assert_eq!(alias_read.task_queue.running, 1);
|
||||
assert!(alias_read
|
||||
.session_path
|
||||
.ends_with(".agent/runtime/agents/art-asset-plan.json"));
|
||||
@@ -1963,6 +1971,9 @@ async fn background_agent_runtime_plan_request_includes_same_agent_continuity_co
|
||||
assert!(second_design_request.contains("# Agent Runtime 连续上下文"));
|
||||
assert!(second_design_request.contains("当前状态:status=running"));
|
||||
assert!(second_design_request.contains("runId=design-continuity-second"));
|
||||
assert!(second_design_request.contains(
|
||||
"任务队列:total=2 pending=0 running=1 completed=1 failed=0 latest=design-continuity-second"
|
||||
));
|
||||
assert!(second_design_request.contains("最近回复:首轮完成:已经读取连续上下文笔记。"));
|
||||
assert!(second_design_request.contains("最近工具动作"));
|
||||
assert!(second_design_request.contains("file.read [ok]"));
|
||||
|
||||
@@ -244,6 +244,7 @@ interface AgentRuntimeState {
|
||||
plan: string[];
|
||||
observations: string[];
|
||||
recentToolCalls?: AgentRuntimeToolCallRecord[];
|
||||
taskQueue?: AgentRuntimeTaskQueueSummary;
|
||||
allowedTools: string[];
|
||||
toolPolicy?: AgentRuntimeToolPolicySnapshot;
|
||||
lastResponse: string | null;
|
||||
@@ -270,6 +271,16 @@ interface AgentRuntimeToolCallRecord {
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
interface AgentRuntimeTaskQueueSummary {
|
||||
total: number;
|
||||
pending: number;
|
||||
running: number;
|
||||
completed: number;
|
||||
failed: number;
|
||||
latestRunId: string | null;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
interface AgentRuntimeEventRecord {
|
||||
schemaVersion: string;
|
||||
agentId: string;
|
||||
@@ -305,6 +316,7 @@ interface AgentRuntimeResult {
|
||||
sessionPath: string;
|
||||
eventPath: string;
|
||||
taskPath?: string;
|
||||
taskQueue?: AgentRuntimeTaskQueueSummary;
|
||||
recentEvents?: AgentRuntimeEventRecord[];
|
||||
recentTasks?: AgentRuntimeTaskRecord[];
|
||||
}
|
||||
@@ -533,6 +545,15 @@ function normalizeAgentRuntimeState(
|
||||
deniedTools: [],
|
||||
updatedAt: 0,
|
||||
},
|
||||
taskQueue: state.taskQueue ?? previous?.taskQueue ?? {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
running: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latestRunId: null,
|
||||
updatedAt: 0,
|
||||
},
|
||||
recentEvents: state.recentEvents ?? previous?.recentEvents ?? [],
|
||||
recentTasks: state.recentTasks ?? previous?.recentTasks ?? [],
|
||||
};
|
||||
@@ -545,6 +566,7 @@ function agentRuntimeStateFromResult(
|
||||
return normalizeAgentRuntimeState(
|
||||
{
|
||||
...result.state,
|
||||
taskQueue: result.taskQueue ?? result.state.taskQueue,
|
||||
recentEvents: result.recentEvents ?? result.state.recentEvents,
|
||||
recentTasks: result.recentTasks ?? result.state.recentTasks,
|
||||
},
|
||||
@@ -605,6 +627,25 @@ function formatAgentRuntimeEvent(event: AgentRuntimeEventRecord) {
|
||||
return `${event.eventType} · ${event.status} / ${event.phase} · ${summary}${detail}`;
|
||||
}
|
||||
|
||||
function formatAgentRuntimeTaskQueue(
|
||||
queue: AgentRuntimeTaskQueueSummary | null | undefined,
|
||||
) {
|
||||
if (!queue || queue.total <= 0) {
|
||||
return null;
|
||||
}
|
||||
const parts = [
|
||||
`pending ${queue.pending}`,
|
||||
`running ${queue.running}`,
|
||||
`completed ${queue.completed}`,
|
||||
`failed ${queue.failed}`,
|
||||
`total ${queue.total}`,
|
||||
];
|
||||
if (queue.latestRunId) {
|
||||
parts.push(`latest ${queue.latestRunId}`);
|
||||
}
|
||||
return `任务队列:${parts.join(' · ')}`;
|
||||
}
|
||||
|
||||
function AgentRuntimeStatusPanel({
|
||||
runtime,
|
||||
error,
|
||||
@@ -631,6 +672,7 @@ function AgentRuntimeStatusPanel({
|
||||
const recentToolCalls = (runtime.recentToolCalls ?? []).slice(-3).reverse();
|
||||
const recentTasks = (runtime.recentTasks ?? []).slice(-3).reverse();
|
||||
const toolPolicy = runtime.toolPolicy;
|
||||
const taskQueueSummary = formatAgentRuntimeTaskQueue(runtime.taskQueue);
|
||||
const nextStep = runtime.nextStep ?? agentRuntimeNextStepFromPhase(runtime.phase);
|
||||
const currentGoal = runtime.currentGoal ?? runtime.currentTask;
|
||||
const waitingOn = runtime.waitingOn ?? agentRuntimeWaitingOnFromPhase(runtime.phase);
|
||||
@@ -647,6 +689,7 @@ function AgentRuntimeStatusPanel({
|
||||
<small>{runtime.currentAction}</small>
|
||||
{waitingOn ? <small>{`等待:${waitingOn}`}</small> : null}
|
||||
{nextStep ? <small>{`下一步:${nextStep}`}</small> : null}
|
||||
{taskQueueSummary ? <small>{taskQueueSummary}</small> : null}
|
||||
{toolPolicy ? (
|
||||
<small>
|
||||
{`工具策略:auto ${toolPolicy.autoTools.length} · confirm ${toolPolicy.confirmTools.length} · deny ${toolPolicy.deniedTools.length}`}
|
||||
@@ -833,6 +876,7 @@ interface AgentStatusCard {
|
||||
runtimeNextStep: string | null;
|
||||
runtimeTask: string | null;
|
||||
runtimeRunId: string | null;
|
||||
runtimeTaskQueue: AgentRuntimeTaskQueueSummary | null;
|
||||
runtimeRecentTasks: AgentRuntimeTaskRecord[];
|
||||
}
|
||||
|
||||
@@ -10153,6 +10197,7 @@ export function deriveAgentStatusCards(
|
||||
: null,
|
||||
runtimeTask: runtime?.currentTask ?? null,
|
||||
runtimeRunId: runtime?.runId ?? null,
|
||||
runtimeTaskQueue: runtime?.taskQueue ?? null,
|
||||
runtimeRecentTasks: runtime?.recentTasks ?? [],
|
||||
};
|
||||
});
|
||||
@@ -10185,6 +10230,7 @@ function sameAgentStatusCard(left: AgentStatusCard, right: AgentStatusCard) {
|
||||
left.runtimeNextStep === right.runtimeNextStep &&
|
||||
left.runtimeTask === right.runtimeTask &&
|
||||
left.runtimeRunId === right.runtimeRunId &&
|
||||
sameAgentRuntimeTaskQueue(left.runtimeTaskQueue, right.runtimeTaskQueue) &&
|
||||
left.hasRecentEvidence === right.hasRecentEvidence &&
|
||||
left.taskGraphState === right.taskGraphState &&
|
||||
sameStringArray(left.inputPaths, right.inputPaths) &&
|
||||
@@ -10205,6 +10251,24 @@ function sameAgentStatusCard(left: AgentStatusCard, right: AgentStatusCard) {
|
||||
);
|
||||
}
|
||||
|
||||
function sameAgentRuntimeTaskQueue(
|
||||
left: AgentRuntimeTaskQueueSummary | null,
|
||||
right: AgentRuntimeTaskQueueSummary | null,
|
||||
) {
|
||||
if (!left || !right) {
|
||||
return left === right;
|
||||
}
|
||||
return (
|
||||
left.total === right.total &&
|
||||
left.pending === right.pending &&
|
||||
left.running === right.running &&
|
||||
left.completed === right.completed &&
|
||||
left.failed === right.failed &&
|
||||
left.latestRunId === right.latestRunId &&
|
||||
left.updatedAt === right.updatedAt
|
||||
);
|
||||
}
|
||||
|
||||
function sameAgentRuntimeTasks(
|
||||
left: AgentRuntimeTaskRecord[],
|
||||
right: AgentRuntimeTaskRecord[],
|
||||
@@ -11028,10 +11092,14 @@ function summarizeAgentStatusCardsForChat(
|
||||
formatAgentCardRuntimeStatus(agent),
|
||||
].filter(Boolean);
|
||||
const latestRuntimeTask = agent.runtimeRecentTasks.at(-1);
|
||||
const runtimeTaskQueue = formatAgentRuntimeTaskQueue(
|
||||
agent.runtimeTaskQueue,
|
||||
);
|
||||
return [
|
||||
`- ${parts.join(' · ')}`,
|
||||
` ${agent.summary}`,
|
||||
agent.runtimeGoal ? ` 当前目标:${agent.runtimeGoal}` : null,
|
||||
runtimeTaskQueue ? ` ${runtimeTaskQueue}` : null,
|
||||
latestRuntimeTask
|
||||
? ` 最近任务:${formatAgentRecentRuntimeTask(latestRuntimeTask)}`
|
||||
: null,
|
||||
@@ -20262,6 +20330,9 @@ export function App() {
|
||||
agent,
|
||||
);
|
||||
const agentRuntimeStatus = formatAgentCardRuntimeStatus(agent);
|
||||
const agentRuntimeTaskQueue = formatAgentRuntimeTaskQueue(
|
||||
agent.runtimeTaskQueue,
|
||||
);
|
||||
const recentRuntimeTasks = agent.runtimeRecentTasks
|
||||
.slice(-2)
|
||||
.reverse();
|
||||
@@ -20284,6 +20355,9 @@ export function App() {
|
||||
{agent.runtimeTask ? (
|
||||
<small>{`当前任务:${agent.runtimeTask}`}</small>
|
||||
) : null}
|
||||
{agentRuntimeTaskQueue ? (
|
||||
<small>{agentRuntimeTaskQueue}</small>
|
||||
) : null}
|
||||
{recentRuntimeTasks.map((task, index) => (
|
||||
<small
|
||||
key={`${agent.id}-runtime-task-${task.runId}-${task.updatedAt}`}
|
||||
|
||||
@@ -494,6 +494,15 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
error: null,
|
||||
updatedAt: 1234,
|
||||
};
|
||||
const runtimeTaskQueue = {
|
||||
total: 1,
|
||||
pending: 1,
|
||||
running: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latestRunId: 'runtime-art-asset-plan-1',
|
||||
updatedAt: 1234,
|
||||
};
|
||||
const cards = deriveAgentStatusCards(manifest, trace, {
|
||||
'art-asset-plan': {
|
||||
schemaVersion: 'game-creator-agent-runtime.v1',
|
||||
@@ -512,6 +521,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
lastResponse: null,
|
||||
error: null,
|
||||
updatedAt: 1234,
|
||||
taskQueue: runtimeTaskQueue,
|
||||
recentTasks: [runtimeTask],
|
||||
},
|
||||
});
|
||||
@@ -545,6 +555,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
runtimeAction: '拆解素材规格',
|
||||
runtimeTask: '补齐主角规范图资产列表',
|
||||
runtimeRunId: 'runtime-art-asset-plan-1',
|
||||
runtimeTaskQueue,
|
||||
runtimeRecentTasks: [runtimeTask],
|
||||
});
|
||||
expect(cards.find((card) => card.id === 'code-prototype')).toMatchObject({
|
||||
@@ -1372,6 +1383,25 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
waitingOn: '工具观察结果',
|
||||
plan: ['读取项目笔记', '结合观察修正建议', '回复开发者'],
|
||||
observations: ['思考摘要:需要先看项目笔记', 'file.read:ok · 已读取 game/notes.txt'],
|
||||
recentToolCalls: [
|
||||
{
|
||||
tool: 'file.read',
|
||||
status: 'ok',
|
||||
reason: '读取笔记',
|
||||
summary: '已读取 game/notes.txt',
|
||||
detail: 'game/notes.txt: 连续上下文笔记',
|
||||
updatedAt: 4004,
|
||||
},
|
||||
],
|
||||
taskQueue: {
|
||||
total: 1,
|
||||
pending: 0,
|
||||
running: 1,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latestRunId: 'launcher-agent-task-test',
|
||||
updatedAt: 4000,
|
||||
},
|
||||
allowedTools: ['conversation.read', 'conversation.write'],
|
||||
lastResponse: null,
|
||||
error: null,
|
||||
@@ -1481,6 +1511,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
'/tmp/authorized-game/.agent/runtime/events/design-director.jsonl',
|
||||
taskPath:
|
||||
'/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl',
|
||||
taskQueue: runningRuntimeState.taskQueue,
|
||||
recentEvents: runningRuntimeEvents,
|
||||
recentTasks: [runningRuntimeTask],
|
||||
};
|
||||
@@ -1503,6 +1534,10 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
'/tmp/authorized-game/.agent/runtime/events/design-director.jsonl',
|
||||
taskPath:
|
||||
'/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl',
|
||||
taskQueue: {
|
||||
...runningRuntimeState.taskQueue,
|
||||
latestRunId: runId,
|
||||
},
|
||||
recentEvents: runningRuntimeEvents,
|
||||
recentTasks: [{ ...runningRuntimeTask, runId }],
|
||||
};
|
||||
@@ -1530,11 +1565,20 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
expect(screen.getByText('当前目标:完成角色规范阶段')).not.toBeNull();
|
||||
expect(screen.getByText('调用工具 file.read')).not.toBeNull();
|
||||
expect(screen.getByText('等待:工具观察结果')).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/任务队列:pending 0 · running 1 · completed 0 · failed 0 · total 1 · latest launcher-agent-task-/,
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(screen.getByText('读取项目笔记')).not.toBeNull();
|
||||
expect(screen.getByText('结合观察修正建议')).not.toBeNull();
|
||||
expect(screen.getByText('回复开发者')).not.toBeNull();
|
||||
expect(screen.getByText('思考摘要:需要先看项目笔记')).not.toBeNull();
|
||||
expect(screen.getByText('file.read:ok · 已读取 game/notes.txt')).not.toBeNull();
|
||||
expect(screen.getByText('最近动作')).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText('file.read · ok · 已读取 game/notes.txt · 读取笔记'),
|
||||
).not.toBeNull();
|
||||
expect(screen.getByText('最近事件')).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(
|
||||
@@ -1589,6 +1633,15 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
currentAction: '生成 Agent 工具计划',
|
||||
plan: ['读取上下文', '回复开发者'],
|
||||
observations: ['上一条任务正在运行。'],
|
||||
taskQueue: {
|
||||
total: 1,
|
||||
pending: 0,
|
||||
running: 1,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latestRunId: 'launcher-agent-task-running',
|
||||
updatedAt: 5000,
|
||||
},
|
||||
allowedTools: ['conversation.read', 'conversation.write'],
|
||||
lastResponse: null,
|
||||
error: null,
|
||||
@@ -1650,6 +1703,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
'/tmp/authorized-game/.agent/runtime/events/design-director.jsonl',
|
||||
taskPath:
|
||||
'/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl',
|
||||
taskQueue: runningRuntimeState.taskQueue,
|
||||
recentEvents: [],
|
||||
recentTasks: [runningTask],
|
||||
};
|
||||
@@ -1663,6 +1717,15 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
'/tmp/authorized-game/.agent/runtime/events/design-director.jsonl',
|
||||
taskPath:
|
||||
'/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl',
|
||||
taskQueue: {
|
||||
total: 2,
|
||||
pending: 1,
|
||||
running: 1,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latestRunId: String(args?.runId ?? 'launcher-agent-task-pending'),
|
||||
updatedAt: 5001,
|
||||
},
|
||||
recentEvents: [],
|
||||
recentTasks: [
|
||||
runningTask,
|
||||
@@ -1702,6 +1765,11 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
expect(
|
||||
screen.getByText(/pending \/ queued · 排队整理第二个需求/),
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/任务队列:pending 1 · running 1 · completed 0 · failed 0 · total 2 · latest launcher-agent-task-/,
|
||||
),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it('shows developer agent runtime read failures', async () => {
|
||||
@@ -13281,6 +13349,15 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
waitingOn: 'Agent 输出计划或回复',
|
||||
plan: ['读取项目上下文'],
|
||||
observations: ['已创建本轮 Agent Runtime run。'],
|
||||
taskQueue: {
|
||||
total: 2,
|
||||
pending: 1,
|
||||
running: 1,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
latestRunId: 'runtime-design-director-1',
|
||||
updatedAt: 10,
|
||||
},
|
||||
allowedTools: ['conversation.read'],
|
||||
lastResponse: null,
|
||||
error: null,
|
||||
@@ -13309,6 +13386,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
'/tmp/authorized-game/.agent/runtime/events/design-director.jsonl',
|
||||
taskPath:
|
||||
'/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl',
|
||||
taskQueue: runtimeState.taskQueue,
|
||||
recentEvents: [],
|
||||
recentTasks: [runtimeTask],
|
||||
},
|
||||
@@ -13342,6 +13420,9 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
'当前目标:补齐第一关节奏目标',
|
||||
);
|
||||
expect(designCard.textContent).toContain('当前任务:拆解关卡节奏');
|
||||
expect(designCard.textContent).toContain(
|
||||
'任务队列:pending 1 · running 1 · completed 0 · failed 0 · total 2 · latest runtime-design-director-1',
|
||||
);
|
||||
expect(designCard.textContent).toContain(
|
||||
'最近任务:pending / queued · 排队补齐世界观拆解',
|
||||
);
|
||||
|
||||
@@ -4062,6 +4062,7 @@
|
||||
- 2026-07-10 调整:Agent Runtime state 新增 `recentToolCalls`,后台 loop 每次执行白名单工具后记录最近 20 条结构化动作,包含 tool、status、reason、summary、detail 和 updatedAt。状态面板展示最近动作时使用该字段,不解析 observation 文本;写入前继续过滤敏感上下文,不保存原始密钥或任意未过滤输入。
|
||||
- 2026-07-10 调整:Agent Runtime state 新增 `currentGoal` 和 `waitingOn`。`currentGoal` 固定表达本轮任务目标,`waitingOn` 表达当前等待 LLM、工具观察、开发者输入或失败处理;后台任务生命周期、`agent.run_status` observation、下一轮 planning prompt、开发单 Agent 对话页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表都必须展示同一份目标 / 等待状态。
|
||||
- 2026-07-10 调整:开发单 Agent 对话页和项目内 Agent 对话弹窗的 Runtime 面板接入 `recentEvents`,展示最近 `thinking_summary / plan / action / observation / response / error` 事件,避免只从当前状态、observation 字符串或最近工具动作里倒推 Agent loop。
|
||||
- 2026-07-10 调整:Agent Runtime state / result 新增 `taskQueue` 观测摘要,从 `.agent/runtime/tasks/<agentId>.jsonl` 中每个 `runId` 的最新记录汇总 `total / pending / running / completed / failed / latestRunId`。开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都使用该字段判断同一 Agent 是否仍有排队任务;它不是新的调度器、SQLite 或跨重启独立 worker。
|
||||
- 2026-07-10 调整:Agent Runtime V1 后台工具箱新增只读 `task.list`。Agent 可自行读取 `.agent/manifest.json` 的 seed task 状态、依赖、产物交接和按依赖计算的 `readyTaskIds`,用于判断下一步任务;该工具必须受 `task.list` 项目权限策略保护,策略要求确认或拒绝时不得把任务图细节放进 observation。
|
||||
- 2026-07-10 调整:Agent Runtime V1 后台工具箱新增受策略保护的 `task.update`。Agent 只能把 `.agent/manifest.json` 中已有 seed task 的状态更新为 `pending`、`running`、`waiting-for-confirmation`、`completed` 或 `failed`,Runtime 必须复用项目写锁、`task.update` 权限策略和 `.agent/agent.db` 审计记录;策略要求确认或拒绝时不得修改 manifest,不得创建新任务。
|
||||
- 2026-07-10 调整:Agent Runtime V1 后台工具箱新增只读 `file.list`。Agent 可自行列出项目文件摘要或相对路径范围内的条目,先观察项目结构再决定是否读取具体文件;该工具必须受 `file.list` 项目权限策略保护,observation 只返回项目相对路径、类型和大小,不读取文件内容、不返回本机绝对路径。
|
||||
|
||||
@@ -37,6 +37,7 @@ Agent Runtime 负责:
|
||||
- 2026-07-10 补充:Agent Runtime state 新增 `recentToolCalls`,每次后台工具执行后记录最近 20 条结构化工具动作,包含 tool、status、reason、summary、detail 和 updatedAt;开发窗口、项目内 Agent 对话弹窗和主窗口 Agent 状态列表可直接展示“最近动作”,不再只能从 observation 字符串里猜测 action / observation 对应关系。字段只保存过滤后的摘要和观察细节,不保存原始 API Key 或任意未过滤输入。
|
||||
- 2026-07-10 补充:Agent Runtime state 新增 `currentGoal` 和 `waitingOn`,把本轮目标与当前等待对象从 `currentTask / currentAction / nextStep` 中显式拆出来;后台任务启动、工具 observation、完成和失败都会刷新该状态,开发窗口、项目内 Agent 对话弹窗、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示同一份目标 / 等待信息,避免开发者只能从动作文本里猜 Agent 卡在 LLM、工具、同伴还是人工输入。
|
||||
- 2026-07-10 补充:`recentEvents` 接入前端归一态和 Runtime 状态面板,事件事实源仍是 `.agent/runtime/events/<agentId>.jsonl`;面板按时间展示最近 `thinking_summary / plan / action / observation / response / error` 事件,现在能同时看到 Agent 的计划、最近观察、最近事件、最近工具动作和任务队列。
|
||||
- 2026-07-10 补充:Agent Runtime state / result 新增 `taskQueue`,从 `.agent/runtime/tasks/<agentId>.jsonl` 中每个 `runId` 的最新记录汇总 `total / pending / running / completed / failed / latestRunId`;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都读取该摘要,用于判断同一 Agent 是否仍有排队任务。该字段是运行观测摘要,不新增调度器、SQLite 或独立 worker。
|
||||
- 2026-07-10 补充:单 Agent 聊天和后台 planning prompt 会读取同一个 Agent 的 Runtime 连续上下文,把本 Agent 最近 status / phase / runId / 当前任务 / 下一步、最近回复、计划、观察、最近 3 条工具动作、最近事件、最近 3 条任务记录和工具策略摘要带入下一轮推理;上下文按规范 taskId 隔离,不读取其他 Agent 的 runtime 文件,并在进入 prompt 前过滤密钥和本机绝对路径。新后台 run 启动时会继承本 Agent 上次 `recentToolCalls` 和 `lastResponse`,让多轮任务不丢失结构化行动证据。
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `preview.start`。Agent 可在 loop 中自行请求启动当前项目的本地 HTTP 预览;Runtime 会复用 `preview.start` 策略、项目写锁、共享 `PreviewRegistry`、manifest 预览状态、`.agent/logs/preview.log` 和 run trace 追加逻辑,并把 `agent.runtime.preview.start` 写入 `.agent/agent.db`。该 observation 只向 LLM 返回 localhost URL 与端口,不返回用户项目绝对路径。
|
||||
- 2026-07-10 补充:后台任务工具箱已加入 `canvas.asset_generate`。Agent 可在 loop 中自行给出素材 prompt,通过 AppData / Tauri 配置里的 `editorApi` 调用 External Editor API 生成首版美术素材、下载到 `assets/canvas-generated/` 并登记 manifest;Runtime 复用 `canvas.asset_generate` 策略和项目写锁,并写入 `agent.runtime.canvas.asset_generate` 审计记录。API Key 不进入 observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。
|
||||
|
||||
Reference in New Issue
Block a user