补齐Agent任务队列观测

新增 Agent Runtime taskQueue 汇总 pending/running/completed/failed 队列状态

在 Runtime 面板、主 Agent 卡片、agent.run_status 和下一轮 prompt 展示队列摘要

补充最近动作与任务队列前端测试、Rust JSON 与连续上下文测试

同步 AI 游戏创作 App 技术方案和项目决策记录
This commit is contained in:
AIGameCreator App
2026-07-10 03:36:46 +08:00
parent 4e19f68bf8
commit f72deb9477
7 changed files with 284 additions and 11 deletions
@@ -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]"));
+74
View File
@@ -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.readok · 已读取 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.readok · 已读取 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/` 并登记 manifestRuntime 复用 `canvas.asset_generate` 策略和项目写锁,并写入 `agent.runtime.canvas.asset_generate` 审计记录。API Key 不进入 observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。