实时同步Agent后台状态
新增 Agent Runtime Tauri 实时更新事件 前端开发窗口和主窗口监听并合并 Runtime 状态 补充后台任务实时更新测试和 Runtime 文档
This commit is contained in:
@@ -1,5 +1,35 @@
|
||||
use super::*;
|
||||
|
||||
static GAME_CREATOR_AGENT_RUNTIME_UPDATE_APP_HANDLE: OnceLock<tauri::AppHandle> = OnceLock::new();
|
||||
|
||||
pub(crate) fn set_game_creator_agent_runtime_update_app_handle(app: tauri::AppHandle) {
|
||||
let _ = GAME_CREATOR_AGENT_RUNTIME_UPDATE_APP_HANDLE.set(app);
|
||||
}
|
||||
|
||||
fn emit_game_creator_agent_runtime_update(root: &Path, agent_id: &str) {
|
||||
let Some(app) = GAME_CREATOR_AGENT_RUNTIME_UPDATE_APP_HANDLE.get() else {
|
||||
return;
|
||||
};
|
||||
let Ok(runtime) = read_game_creator_agent_runtime_at(root, agent_id) else {
|
||||
return;
|
||||
};
|
||||
let agent_id = runtime.state.agent_id.clone();
|
||||
let run_id = runtime.state.run_id.clone();
|
||||
let status = runtime.state.status.clone();
|
||||
let phase = runtime.state.phase.clone();
|
||||
let _ = app.emit(
|
||||
"game-creator-agent-runtime-update",
|
||||
GameCreatorAgentRuntimeUpdateEvent {
|
||||
project_path: root.to_string_lossy().into_owned(),
|
||||
agent_id,
|
||||
run_id,
|
||||
status,
|
||||
phase,
|
||||
runtime,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) async fn generate_local_game_draft_at(
|
||||
root: &Path,
|
||||
prompt: &str,
|
||||
@@ -370,7 +400,9 @@ pub(crate) fn start_game_creator_agent_background_task_at(
|
||||
)?;
|
||||
let Some(runtime_lock) = try_acquire_game_creator_agent_runtime_task_lock(root, &agent_id)?
|
||||
else {
|
||||
return read_game_creator_agent_runtime_at(root, &agent_id);
|
||||
let result = read_game_creator_agent_runtime_at(root, &agent_id)?;
|
||||
emit_game_creator_agent_runtime_update(root, &agent_id);
|
||||
return Ok(result);
|
||||
};
|
||||
let state = start_game_creator_agent_runtime_task_at(
|
||||
root,
|
||||
@@ -3269,7 +3301,9 @@ fn append_game_creator_agent_runtime_event(
|
||||
serde_json::to_writer(&mut file, &event)
|
||||
.map_err(|error| format!("序列化 Agent Runtime 事件失败:{error}"))?;
|
||||
file.write_all(b"\n")
|
||||
.map_err(|error| format!("写入 Agent Runtime 事件失败:{}: {error}", path.display()))
|
||||
.map_err(|error| format!("写入 Agent Runtime 事件失败:{}: {error}", path.display()))?;
|
||||
emit_game_creator_agent_runtime_update(root, &state.agent_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_game_creator_agent_runtime_task(
|
||||
|
||||
@@ -341,7 +341,7 @@ struct AgentRuntimeTaskRecord {
|
||||
updated_at: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AgentRuntimeResult {
|
||||
state: AgentRuntimeState,
|
||||
@@ -353,6 +353,17 @@ struct AgentRuntimeResult {
|
||||
recent_tasks: Vec<AgentRuntimeTaskRecord>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct GameCreatorAgentRuntimeUpdateEvent {
|
||||
project_path: String,
|
||||
agent_id: String,
|
||||
run_id: String,
|
||||
status: String,
|
||||
phase: String,
|
||||
runtime: AgentRuntimeResult,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct GameCreatorAgentProgressEvent {
|
||||
@@ -1154,6 +1165,7 @@ fn main() {
|
||||
.manage(game_creator_preview_registry())
|
||||
.setup(|app| {
|
||||
configure_game_creator_runtime_config_dir(app.handle())?;
|
||||
set_game_creator_agent_runtime_update_app_handle(app.handle().clone());
|
||||
#[cfg(all(debug_assertions, not(test)))]
|
||||
open_developer_window(app.handle())?;
|
||||
Ok(())
|
||||
|
||||
@@ -334,6 +334,15 @@ interface AgentRuntimeResult {
|
||||
recentTasks?: AgentRuntimeTaskRecord[];
|
||||
}
|
||||
|
||||
interface GameCreatorAgentRuntimeUpdateEvent {
|
||||
projectPath: string;
|
||||
agentId: string;
|
||||
runId: string;
|
||||
status: string;
|
||||
phase: string;
|
||||
runtime: AgentRuntimeResult;
|
||||
}
|
||||
|
||||
interface GameCreatorLlmConfigStatus {
|
||||
configured: boolean;
|
||||
apiKeyPresent: boolean;
|
||||
@@ -2676,6 +2685,10 @@ export function WorkspaceLauncher({
|
||||
useState<AgentRuntimeState | null>(null);
|
||||
const [agentChatRuntimeError, setAgentChatRuntimeError] = useState('');
|
||||
const agentChatLoadVersionRef = useRef(0);
|
||||
const agentChatProjectPathRef = useRef(agentChatProjectPath);
|
||||
agentChatProjectPathRef.current = agentChatProjectPath;
|
||||
const agentChatSelectedAgentIdRef = useRef(agentChatSelectedAgentId);
|
||||
agentChatSelectedAgentIdRef.current = agentChatSelectedAgentId;
|
||||
|
||||
useEffect(() => {
|
||||
const invoke = resolveTauriInvoke();
|
||||
@@ -2710,6 +2723,41 @@ export function WorkspaceLauncher({
|
||||
};
|
||||
}, [recentWorkspaces, recentWorkspaceRefreshKey]);
|
||||
|
||||
useEffect(() => {
|
||||
const listen = window.__TAURI__?.event?.listen;
|
||||
if (!listen) {
|
||||
return;
|
||||
}
|
||||
let cleanup: (() => void) | null = null;
|
||||
let disposed = false;
|
||||
void listen<GameCreatorAgentRuntimeUpdateEvent>(
|
||||
'game-creator-agent-runtime-update',
|
||||
(event) => {
|
||||
const payload = event.payload;
|
||||
if (
|
||||
payload.projectPath !== agentChatProjectPathRef.current ||
|
||||
payload.agentId !== agentChatSelectedAgentIdRef.current
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setAgentChatRuntime((current) =>
|
||||
agentRuntimeStateFromResult(payload.runtime, current),
|
||||
);
|
||||
setAgentChatRuntimeError('');
|
||||
},
|
||||
).then((unlisten) => {
|
||||
if (disposed) {
|
||||
unlisten();
|
||||
return;
|
||||
}
|
||||
cleanup = unlisten;
|
||||
});
|
||||
return () => {
|
||||
disposed = true;
|
||||
cleanup?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
void getClientProfileDashboard()
|
||||
@@ -11862,6 +11910,8 @@ export function App() {
|
||||
const [selectedAgent, setSelectedAgent] = useState<AgentStatusCard | null>(
|
||||
null,
|
||||
);
|
||||
const selectedAgentIdRef = useRef<string | null>(null);
|
||||
selectedAgentIdRef.current = selectedAgent?.id ?? null;
|
||||
const [agentConversationInput, setAgentConversationInput] = useState('');
|
||||
const [agentConversationStatus, setAgentConversationStatus] =
|
||||
useState('未选择 agent');
|
||||
@@ -11961,6 +12011,43 @@ export function App() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const listen = window.__TAURI__?.event?.listen;
|
||||
if (!listen) {
|
||||
return;
|
||||
}
|
||||
let cleanup: (() => void) | null = null;
|
||||
let disposed = false;
|
||||
void listen<GameCreatorAgentRuntimeUpdateEvent>(
|
||||
'game-creator-agent-runtime-update',
|
||||
(event) => {
|
||||
const payload = event.payload;
|
||||
if (payload.projectPath !== localProjectPathRef.current) {
|
||||
return;
|
||||
}
|
||||
const nextRuntime = agentRuntimeStateFromResult(payload.runtime);
|
||||
rememberAgentRuntimeState(nextRuntime);
|
||||
if (payload.agentId !== selectedAgentIdRef.current) {
|
||||
return;
|
||||
}
|
||||
setAgentConversationRuntime((current) =>
|
||||
normalizeAgentRuntimeState(nextRuntime, current),
|
||||
);
|
||||
setAgentConversationRuntimeError('');
|
||||
},
|
||||
).then((unlisten) => {
|
||||
if (disposed) {
|
||||
unlisten();
|
||||
return;
|
||||
}
|
||||
cleanup = unlisten;
|
||||
});
|
||||
return () => {
|
||||
disposed = true;
|
||||
cleanup?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
latestMessagesRef.current = messages;
|
||||
const invoke = resolveTauriInvoke();
|
||||
|
||||
@@ -1192,13 +1192,18 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
eventName: string,
|
||||
handler: (event: { payload: Record<string, unknown> }) => void,
|
||||
) => {
|
||||
expect(eventName).toBe('game-creator-role-agent-chat-stream');
|
||||
streamHandler = handler;
|
||||
return () => {
|
||||
if (streamHandler === handler) {
|
||||
streamHandler = null;
|
||||
}
|
||||
};
|
||||
if (eventName === 'game-creator-role-agent-chat-stream') {
|
||||
streamHandler = handler;
|
||||
return () => {
|
||||
if (streamHandler === handler) {
|
||||
streamHandler = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
if (eventName === 'game-creator-agent-runtime-update') {
|
||||
return () => {};
|
||||
}
|
||||
throw new Error(`unexpected listen ${eventName}`);
|
||||
},
|
||||
);
|
||||
const invoke = vi.fn(
|
||||
@@ -1465,6 +1470,32 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
error: null,
|
||||
updatedAt: 4000,
|
||||
};
|
||||
const completedRuntimeState = {
|
||||
...runningRuntimeState,
|
||||
status: 'idle',
|
||||
phase: 'completed',
|
||||
currentAction: '等待下一轮输入',
|
||||
waitingOn: '开发者下一轮输入',
|
||||
nextStep: '等待下一轮输入',
|
||||
taskQueue: {
|
||||
total: 1,
|
||||
pending: 0,
|
||||
running: 0,
|
||||
completed: 1,
|
||||
failed: 0,
|
||||
latestRunId: 'launcher-agent-task-test',
|
||||
updatedAt: 4010,
|
||||
},
|
||||
lastResponse: '角色规范已整理。',
|
||||
updatedAt: 4010,
|
||||
};
|
||||
const completedRuntimeTask = {
|
||||
...runningRuntimeTask,
|
||||
status: 'completed',
|
||||
phase: 'completed',
|
||||
currentAction: '等待下一轮输入',
|
||||
updatedAt: 4010,
|
||||
};
|
||||
const runningRuntimeEvents = [
|
||||
{
|
||||
schemaVersion: 'game-creator-agent-runtime.v1',
|
||||
@@ -1589,7 +1620,26 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
throw new Error(`unexpected invoke ${command}`);
|
||||
},
|
||||
);
|
||||
window.__TAURI__ = { core: { invoke } };
|
||||
let runtimeUpdateHandler:
|
||||
| ((event: { payload: Record<string, unknown> }) => void)
|
||||
| null = null;
|
||||
const listen = vi.fn(
|
||||
async (
|
||||
eventName: string,
|
||||
handler: (event: { payload: Record<string, unknown> }) => void,
|
||||
) => {
|
||||
if (eventName === 'game-creator-agent-runtime-update') {
|
||||
runtimeUpdateHandler = handler;
|
||||
return () => {
|
||||
if (runtimeUpdateHandler === handler) {
|
||||
runtimeUpdateHandler = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected listen ${eventName}`);
|
||||
},
|
||||
);
|
||||
window.__TAURI__ = { core: { invoke }, event: { listen } };
|
||||
renderLauncherAgentChatAt('/?agent-chat');
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Agent 聊天项目目录'), {
|
||||
@@ -1669,6 +1719,54 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
agentId: null,
|
||||
},
|
||||
]);
|
||||
|
||||
await act(async () => {
|
||||
runtimeUpdateHandler?.({
|
||||
payload: {
|
||||
projectPath: '/tmp/authorized-game',
|
||||
agentId: 'design-director',
|
||||
runId: 'launcher-agent-task-test',
|
||||
status: 'idle',
|
||||
phase: 'completed',
|
||||
runtime: {
|
||||
state: completedRuntimeState,
|
||||
sessionPath:
|
||||
'/tmp/authorized-game/.agent/runtime/agents/design-director.json',
|
||||
eventPath:
|
||||
'/tmp/authorized-game/.agent/runtime/events/design-director.jsonl',
|
||||
taskPath:
|
||||
'/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl',
|
||||
taskQueue: completedRuntimeState.taskQueue,
|
||||
recentEvents: [
|
||||
...runningRuntimeEvents,
|
||||
{
|
||||
schemaVersion: 'game-creator-agent-runtime.v1',
|
||||
agentId: 'design-director',
|
||||
taskId: 'design-director',
|
||||
sessionId: 'agent-session-design-director',
|
||||
runId: 'launcher-agent-task-test',
|
||||
source: 'agent-background-task',
|
||||
eventType: 'turn.completed',
|
||||
status: 'idle',
|
||||
phase: 'completed',
|
||||
summary: 'Agent Runtime 完成本轮处理。',
|
||||
detail: '角色规范已整理。',
|
||||
updatedAt: 4010,
|
||||
},
|
||||
],
|
||||
recentTasks: [completedRuntimeTask],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(await screen.findByText('idle / completed')).not.toBeNull();
|
||||
expect(screen.getByText('等待:开发者下一轮输入')).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'turn.completed · idle / completed · Agent Runtime 完成本轮处理。 · 角色规范已整理。',
|
||||
),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it('shows queued developer agent background tasks when the agent is already running', async () => {
|
||||
@@ -13438,6 +13536,32 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
error: null,
|
||||
updatedAt: 10,
|
||||
};
|
||||
const completedRuntimeState = {
|
||||
...runtimeState,
|
||||
status: 'idle',
|
||||
phase: 'completed',
|
||||
currentAction: '等待下一轮输入',
|
||||
waitingOn: '开发者下一轮输入',
|
||||
nextStep: '等待下一轮输入',
|
||||
taskQueue: {
|
||||
total: 2,
|
||||
pending: 1,
|
||||
running: 0,
|
||||
completed: 1,
|
||||
failed: 0,
|
||||
latestRunId: 'runtime-design-director-1',
|
||||
updatedAt: 12,
|
||||
},
|
||||
lastResponse: '已补齐第一关节奏目标。',
|
||||
updatedAt: 12,
|
||||
};
|
||||
const completedRuntimeTask = {
|
||||
...runtimeTask,
|
||||
status: 'completed',
|
||||
phase: 'completed',
|
||||
currentAction: '等待下一轮输入',
|
||||
updatedAt: 12,
|
||||
};
|
||||
const invoke = vi.fn(
|
||||
async (command: string, args?: Record<string, unknown>) => {
|
||||
if (command === 'append_local_permission_log') {
|
||||
@@ -13481,7 +13605,29 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
throw new Error(`unexpected invoke ${command}`);
|
||||
},
|
||||
);
|
||||
window.__TAURI__ = { core: { invoke } };
|
||||
let runtimeUpdateHandler:
|
||||
| ((event: { payload: Record<string, unknown> }) => void)
|
||||
| null = null;
|
||||
const listen = vi.fn(
|
||||
async (
|
||||
eventName: string,
|
||||
handler: (event: { payload: Record<string, unknown> }) => void,
|
||||
) => {
|
||||
if (eventName === 'game-creator-agent-progress') {
|
||||
return () => {};
|
||||
}
|
||||
if (eventName === 'game-creator-agent-runtime-update') {
|
||||
runtimeUpdateHandler = handler;
|
||||
return () => {
|
||||
if (runtimeUpdateHandler === handler) {
|
||||
runtimeUpdateHandler = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected listen ${eventName}`);
|
||||
},
|
||||
);
|
||||
window.__TAURI__ = { core: { invoke }, event: { listen } };
|
||||
|
||||
renderAppAt('/?main&projectPath=%2Ftmp%2Fauthorized-game');
|
||||
|
||||
@@ -13515,6 +13661,45 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
runtimeUpdateHandler?.({
|
||||
payload: {
|
||||
projectPath: '/tmp/authorized-game',
|
||||
agentId: 'design-director',
|
||||
runId: 'runtime-design-director-1',
|
||||
status: 'idle',
|
||||
phase: 'completed',
|
||||
runtime: {
|
||||
state: completedRuntimeState,
|
||||
sessionPath:
|
||||
'/tmp/authorized-game/.agent/runtime/agents/design-director.json',
|
||||
eventPath:
|
||||
'/tmp/authorized-game/.agent/runtime/events/design-director.jsonl',
|
||||
taskPath:
|
||||
'/tmp/authorized-game/.agent/runtime/tasks/design-director.jsonl',
|
||||
taskQueue: completedRuntimeState.taskQueue,
|
||||
recentEvents: [],
|
||||
recentTasks: [completedRuntimeTask],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const designCard = within(agentStatusList).getByRole('button', {
|
||||
name: /拆解创作方向/,
|
||||
});
|
||||
expect(designCard.textContent).toContain(
|
||||
'Runtime:idle / completed · Loop 2/3 · 等待下一轮输入 · 等待 开发者下一轮输入 · 下一步 等待下一轮输入 · run runtime-design-director-1',
|
||||
);
|
||||
expect(designCard.textContent).toContain(
|
||||
'任务队列:pending 1 · running 0 · completed 1 · failed 0 · total 2 · latest runtime-design-director-1',
|
||||
);
|
||||
expect(designCard.textContent).toContain(
|
||||
'最近任务:completed / completed · 排队补齐世界观拆解',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores stale agent conversation reads after switching agents', async () => {
|
||||
@@ -20593,9 +20778,15 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
},
|
||||
);
|
||||
const listen = vi.fn(
|
||||
async (_event: string, handler: typeof progressHandler) => {
|
||||
progressHandler = handler;
|
||||
return vi.fn();
|
||||
async (eventName: string, handler: typeof progressHandler) => {
|
||||
if (eventName === 'game-creator-agent-progress') {
|
||||
progressHandler = handler;
|
||||
return vi.fn();
|
||||
}
|
||||
if (eventName === 'game-creator-agent-runtime-update') {
|
||||
return vi.fn();
|
||||
}
|
||||
throw new Error(`unexpected listen ${eventName}`);
|
||||
},
|
||||
);
|
||||
window.__TAURI__ = { core: { invoke }, event: { listen } };
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
- 背景:开发用单 Agent 聊天已经能真实调用各 Agent 的 LLM 路由并持久化对话,但 Agent 仍主要表现为同步问答,用户无法明确投递一个任务让某个 Agent 独立运行,也无法同时启动多个 Agent 的工作。
|
||||
- 决策:在现有 `.agent/runtime` 和 `.agent/conversations` 基础上新增单 Agent 后台任务入口。Tauri 命令 `start_game_creator_agent_runtime_task` 立即写入该 Agent 的 runtime state/event/task history,追加用户任务到 `.agent/conversations/agents/<agentId>.jsonl`,随后在 App 进程内启动 tokio task 执行最小 Agent loop:Agent 按轮输出 `thinkingSummary / plan / actions / response`,Runtime 按白名单和项目权限策略执行工具并记录 `action / observation` 事件,再把已有 observation 放回下一轮 prompt,让 Agent 修正计划、继续行动或用空 actions + response 收束;当前后台任务最多执行 3 轮 loop,仍未收束时再按最后计划和全部观察生成最终回复。完成或失败后把 assistant 回复或错误追加回对话,并写入 `.agent/agent.db` 审计记录。工具箱包含只读工具 `memory.read`、`conversation.read`、`asset.list`、`project.index`、`project.diff`、`file.list`、`file.read`、`agent.run_status`,以及受策略保护的写/运行工具 `memory.write`、`file.write`、`command.run_limited`、`blackboard.write`、`agent.message` 和 `agent.delegate`;`memory.write` 可追加或覆盖本 Agent 私有记忆、项目长期/短期记忆或黑板,`file.write` 只能写项目内相对路径,`command.run_limited` 只接受 `game.static_smoke` 并复用本地静态自检安全边界,`blackboard.write` 追加共享黑板,`agent.message` 写目标 Agent 对话,`agent.delegate` 把任务投递到目标 Agent 的独立后台队列;策略要求确认或拒绝时不执行写入、运行或委派,只把策略结果作为 observation 回给 Agent。每个 Agent 的任务历史落在 `.agent/runtime/tasks/<agentId>.jsonl`,读 runtime 时按 `runId` 去重返回最近任务,任务视角状态使用 `pending / running / completed / failed`,Runtime state 增加 `nextStep`,UI 在 Runtime 面板和主 Agent 状态卡展示当前任务、动作、下一步与最近任务。不同 Agent 使用独立 `.agent/runtime/locks/<agentId>.lock`,允许并行运行;同一 Agent 已有运行任务时,新任务会先进入该 Agent 的 pending 队列,当前 drain 持锁完成后串行继续下一条 pending。该能力仍不是独立 OS 进程或跨重启离线常驻 worker。
|
||||
- 2026-07-10 补充:后台 Runtime 每次追加 `.agent/runtime/events/<agentId>.jsonl` 后会通过 Tauri `game-creator-agent-runtime-update` 事件广播当前 `AgentRuntimeResult`;开发单 Agent 聊天页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表都只把该事件作为实时 UI 通知并复用前端 runtime 归一化合并,事实源仍是 `.agent/runtime/agents`、`events` 和 `tasks` 文件。
|
||||
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `preview.start`,让 Agent 在完成写盘或静态自检后能按策略自行启动当前项目的 `127.0.0.1` 本地 HTTP 预览。该工具复用 `preview.start` 权限策略、项目写锁、共享 `PreviewRegistry`、manifest 预览状态、`.agent/logs/preview.log` 和 run trace 追加逻辑;写入 `.agent/agent.db` 的审计类型为 `agent.runtime.preview.start`。发给 LLM 的 observation 只包含 localhost URL 和端口,不包含用户项目绝对路径。
|
||||
- 2026-07-10 补充:后台 Agent Runtime 的白名单工具继续扩到 `canvas.asset_generate`,让美术类 Agent 可在 loop 中自行请求生成首版美术素材。该工具读取 AppData / Tauri 配置中的 `editorApi`,复用 `canvas.asset_generate` 权限策略、项目写锁、External Editor API 生成和下载链路、manifest 资产登记以及 `canvas.asset_generate` 本地索引记录;另写 `agent.runtime.canvas.asset_generate` 记录到 `.agent/agent.db`,标明触发的 agent 与本地素材路径。API Key 不进入 prompt observation、manifest、agent.db 或日志;策略要求确认或拒绝时不会调用外部 API。
|
||||
- 补充:规范 Agent ID 统一使用 manifest taskId,例如 `art-asset-plan` 和 `code-prototype`;历史前端曾使用的 `group-role` 别名只在 Tauri command 层兼容并映射到规范 taskId。主窗口 Agent 状态列表通过 `read_game_creator_agent_runtimes` 批量读取 `.agent/runtime/agents/<taskId>.json` 和最近任务,把每个 Agent 的 Runtime 状态、当前动作和最近 task 直接显示在状态卡片和 `/agents` 汇总里。
|
||||
@@ -4064,6 +4065,7 @@
|
||||
- 2026-07-10 调整:Agent Runtime state 新增 `loopIteration / maxLoopIterations / toolActionBudget`。后台 Agent loop 每轮规划前刷新当前轮次、最大轮次和每轮工具动作预算;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示该进度,字段只做运行观测,不改变 loop 上限或权限 gate。
|
||||
- 2026-07-10 调整:Agent Runtime state 新增 `planSteps / activePlanStepIndex`。Runtime 从 Agent 输出的 `plan` 派生结构化计划步骤,并在 action / observation / response / error 生命周期中更新 `pending / active / completed / failed` 和 detail;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示步骤进度,不再只依赖不可定位的 plan 字符串。
|
||||
- 2026-07-10 调整:开发单 Agent 对话页和项目内 Agent 对话弹窗的 Runtime 面板接入 `recentEvents`,展示最近 `thinking_summary / plan / action / observation / response / error` 事件,避免只从当前状态、observation 字符串或最近工具动作里倒推 Agent loop。
|
||||
- 2026-07-10 调整:后台 Runtime 每次追加 `.agent/runtime/events/<agentId>.jsonl` 后会发送 `game-creator-agent-runtime-update` Tauri 事件,payload 带当前 `AgentRuntimeResult`;开发单 Agent 聊天页、项目内 Agent 对话弹窗和主窗口 Agent 状态列表实时合并该结果,但事件不替代 `.agent/runtime/agents`、`events` 和 `tasks` 的落盘事实源。
|
||||
- 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,不得创建新任务。
|
||||
|
||||
@@ -39,6 +39,7 @@ Agent Runtime 负责:
|
||||
- 2026-07-10 补充:Agent Runtime state 新增 `loopIteration / maxLoopIterations / toolActionBudget`,结构化记录后台 Agent loop 当前轮次、最大轮次和每轮工具动作预算;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示该进度,帮助判断 Agent 是刚开始规划、正在 replan,还是接近本轮 loop 上限。该字段只做运行观测,不改变后台 loop 的执行上限或工具权限。
|
||||
- 2026-07-10 补充:Agent Runtime state 新增 `planSteps / activePlanStepIndex`,从 Agent 输出的 `plan` 派生结构化计划步骤,并在工具 action / observation / response / error 生命周期中更新 `pending / active / completed / failed` 和 detail;开发窗口 Runtime 面板、主窗口 Agent 状态列表、`agent.run_status` observation 和下一轮 planning prompt 都展示当前计划步骤与步骤进度,避免只能展示一串不可定位的 plan 文本。
|
||||
- 2026-07-10 补充:`recentEvents` 接入前端归一态和 Runtime 状态面板,事件事实源仍是 `.agent/runtime/events/<agentId>.jsonl`;面板按时间展示最近 `thinking_summary / plan / action / observation / response / error` 事件,现在能同时看到 Agent 的计划、最近观察、最近事件、最近工具动作和任务队列。
|
||||
- 2026-07-10 补充:后台 Runtime 每次追加 `.agent/runtime/events/<agentId>.jsonl` 后会通过 Tauri `game-creator-agent-runtime-update` 事件广播当前 `AgentRuntimeResult`,开发单 Agent 聊天页、项目内 Agent 对话弹窗和主窗口 Agent 状态卡用同一套前端归一化逻辑合并状态;该事件只做实时 UI 通知,`.agent/runtime/agents`、`events` 和 `tasks` 仍是重开项目后的事实源。
|
||||
- 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 与端口,不返回用户项目绝对路径。
|
||||
|
||||
Reference in New Issue
Block a user