补充Agent LLM状态展示
回填LLM状态到Agent列表和单Agent对话 补充Agent级Provider状态的用户面测试 同步AI游戏创作App技术方案和共享决策记录
This commit is contained in:
@@ -3095,6 +3095,58 @@ function formatLlmAgentStatusLine(agent: GameCreatorAgentLlmConfigStatus) {
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
function llmStatusForAgentCard(
|
||||
status: GameCreatorLlmConfigStatus | null,
|
||||
agent: AgentStatusCard,
|
||||
) {
|
||||
return (
|
||||
status?.agents?.find(
|
||||
(candidate) =>
|
||||
candidate.agentId === agent.taskId || candidate.agentId === agent.id,
|
||||
) ?? null
|
||||
);
|
||||
}
|
||||
|
||||
function formatAgentCardLlmStatus(
|
||||
status: GameCreatorLlmConfigStatus | null,
|
||||
agent: AgentStatusCard,
|
||||
) {
|
||||
const agentStatus = llmStatusForAgentCard(status, agent);
|
||||
if (!agentStatus) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
`LLM:${agentStatus.configured ? '已配置' : '未就绪'}`,
|
||||
agentStatus.model ?? '未命名模型',
|
||||
agentStatus.apiKind,
|
||||
`流式${agentStatus.stream ? '开' : '关'}`,
|
||||
`Key${agentStatus.apiKeyPresent ? '已读' : '未读'}`,
|
||||
].join(' · ');
|
||||
}
|
||||
|
||||
function formatAgentDialogLlmStatus(
|
||||
status: GameCreatorLlmConfigStatus | null,
|
||||
agent: AgentStatusCard,
|
||||
) {
|
||||
const agentStatus = llmStatusForAgentCard(status, agent);
|
||||
if (!agentStatus) {
|
||||
return null;
|
||||
}
|
||||
const parts = [
|
||||
`LLM:${agentStatus.configured ? '已配置' : '未就绪'}`,
|
||||
`${agentStatus.model ?? '未命名模型'} @ ${
|
||||
agentStatus.baseUrl ?? '未设置 base_url'
|
||||
}`,
|
||||
agentStatus.apiKind,
|
||||
`流式 ${agentStatus.stream ? '开启' : '关闭'}`,
|
||||
`API Key ${agentStatus.apiKeyPresent ? '已读取' : '未读取'}`,
|
||||
];
|
||||
if (!agentStatus.configured && agentStatus.error) {
|
||||
parts.push(`错误:${agentStatus.error}`);
|
||||
}
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
function formatTraceTaskId(taskId: string, tasks: GameCreationAppTaskState[]) {
|
||||
const task = tasks.find((candidate) => candidate.id === taskId);
|
||||
if (!task) {
|
||||
@@ -3546,6 +3598,8 @@ export function App() {
|
||||
useState(false);
|
||||
const [agentRunStatus, setAgentRunStatus] = useState('未运行');
|
||||
const [runtimeConfigOpen, setRuntimeConfigOpen] = useState(false);
|
||||
const [llmConfigStatus, setLlmConfigStatus] =
|
||||
useState<GameCreatorLlmConfigStatus | null>(null);
|
||||
const [workspaceStatus, setWorkspaceStatus] = useState('请选择工作区');
|
||||
const [selectedAgent, setSelectedAgent] = useState<AgentStatusCard | null>(
|
||||
null,
|
||||
@@ -5547,6 +5601,7 @@ export function App() {
|
||||
async function executeLlmConfigStatus() {
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!invoke) {
|
||||
setLlmConfigStatus(null);
|
||||
setMessages((current) => [
|
||||
...current,
|
||||
{ role: 'assistant', text: '需要在 Tauri App 内运行。' },
|
||||
@@ -5558,6 +5613,7 @@ export function App() {
|
||||
const status = await invoke<GameCreatorLlmConfigStatus>(
|
||||
'check_game_creator_llm_config',
|
||||
);
|
||||
setLlmConfigStatus(status);
|
||||
setCommandLog((current) => [...current, 'llm.config_check']);
|
||||
const agentLines = (status.agents ?? []).map(formatLlmAgentStatusLine);
|
||||
const summary = status.configured
|
||||
@@ -5579,6 +5635,7 @@ export function App() {
|
||||
},
|
||||
]);
|
||||
} catch (error) {
|
||||
setLlmConfigStatus(null);
|
||||
setMessages((current) => [
|
||||
...current,
|
||||
{
|
||||
@@ -9333,6 +9390,9 @@ export function App() {
|
||||
0,
|
||||
agentConversationMessages.length - visibleAgentConversationMessages.length,
|
||||
);
|
||||
const selectedAgentLlmStatus = selectedAgent
|
||||
? formatAgentDialogLlmStatus(llmConfigStatus, selectedAgent)
|
||||
: null;
|
||||
|
||||
function showEarlierConversationMessages() {
|
||||
setConversationVisibleCount((current) =>
|
||||
@@ -10017,32 +10077,39 @@ export function App() {
|
||||
</div>
|
||||
</header>
|
||||
<div className="agent-status-list">
|
||||
{agentStatusCards.map((agent) => (
|
||||
<button
|
||||
key={agent.id}
|
||||
type="button"
|
||||
disabled={!localProject}
|
||||
onClick={() => openAgentConversation(agent)}
|
||||
>
|
||||
<strong>{agent.title}</strong>
|
||||
<span>{`${taskGroupLabels[agent.group]} / ${agent.role} · ${taskStatusLabels[agent.status]}`}</span>
|
||||
{agent.pass !== null || agent.phase ? (
|
||||
<small>{`pass ${agent.pass ?? '-'} · ${agent.phase ?? '-'}`}</small>
|
||||
) : null}
|
||||
{agent.lifecycleStatus ? (
|
||||
<small>{`run: ${agent.lifecycleStatus}`}</small>
|
||||
) : null}
|
||||
{agent.taskGraphState ? (
|
||||
<small>{`编排:${
|
||||
agentTaskGraphStateLabels[agent.taskGraphState]
|
||||
}`}</small>
|
||||
) : null}
|
||||
{!agent.hasRecentEvidence ? (
|
||||
<small>暂无最近运行证据</small>
|
||||
) : null}
|
||||
<small>{agent.summary}</small>
|
||||
</button>
|
||||
))}
|
||||
{agentStatusCards.map((agent) => {
|
||||
const agentLlmStatus = formatAgentCardLlmStatus(
|
||||
llmConfigStatus,
|
||||
agent,
|
||||
);
|
||||
return (
|
||||
<button
|
||||
key={agent.id}
|
||||
type="button"
|
||||
disabled={!localProject}
|
||||
onClick={() => openAgentConversation(agent)}
|
||||
>
|
||||
<strong>{agent.title}</strong>
|
||||
<span>{`${taskGroupLabels[agent.group]} / ${agent.role} · ${taskStatusLabels[agent.status]}`}</span>
|
||||
{agentLlmStatus ? <small>{agentLlmStatus}</small> : null}
|
||||
{agent.pass !== null || agent.phase ? (
|
||||
<small>{`pass ${agent.pass ?? '-'} · ${agent.phase ?? '-'}`}</small>
|
||||
) : null}
|
||||
{agent.lifecycleStatus ? (
|
||||
<small>{`run: ${agent.lifecycleStatus}`}</small>
|
||||
) : null}
|
||||
{agent.taskGraphState ? (
|
||||
<small>{`编排:${
|
||||
agentTaskGraphStateLabels[agent.taskGraphState]
|
||||
}`}</small>
|
||||
) : null}
|
||||
{!agent.hasRecentEvidence ? (
|
||||
<small>暂无最近运行证据</small>
|
||||
) : null}
|
||||
<small>{agent.summary}</small>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
@@ -10098,9 +10165,18 @@ export function App() {
|
||||
}`}
|
||||
</p>
|
||||
) : null}
|
||||
{selectedAgentLlmStatus ? (
|
||||
<p className="status-line">{selectedAgentLlmStatus}</p>
|
||||
) : null}
|
||||
<p className="status-line">{selectedAgent.summary}</p>
|
||||
</div>
|
||||
<div className="panel-actions">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void executeLlmConfigStatus()}
|
||||
>
|
||||
LLM状态
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void openAgentConversation(selectedAgent)}
|
||||
|
||||
@@ -1812,6 +1812,53 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
if (command === 'get_limited_local_commands') {
|
||||
return [{ id: 'game.static_smoke', title: '静态入口自检' }];
|
||||
}
|
||||
if (command === 'check_game_creator_llm_config') {
|
||||
return {
|
||||
configured: true,
|
||||
apiKeyPresent: true,
|
||||
baseUrl: 'https://llm.example.test/v1',
|
||||
model: 'gpt-main',
|
||||
apiKind: 'openai_responses',
|
||||
stream: false,
|
||||
error: null,
|
||||
agents: [
|
||||
{
|
||||
agentId: 'planner',
|
||||
label: 'Planner',
|
||||
configured: true,
|
||||
apiKeyPresent: true,
|
||||
baseUrl: 'https://planner.example.test/v1',
|
||||
model: 'planner-model',
|
||||
apiKind: 'anthropic',
|
||||
stream: true,
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
agentId: 'art-asset-plan',
|
||||
label: '美术组 / Asset',
|
||||
configured: true,
|
||||
apiKeyPresent: true,
|
||||
baseUrl: 'https://art.example.test/v1',
|
||||
model: 'art-model',
|
||||
apiKind: 'openai_chat',
|
||||
stream: true,
|
||||
error: null,
|
||||
},
|
||||
{
|
||||
agentId: 'audio-asset-plan',
|
||||
label: '音乐组 / SFX',
|
||||
configured: false,
|
||||
apiKeyPresent: false,
|
||||
baseUrl: 'https://audio.example.test/v1',
|
||||
model: 'audio-model',
|
||||
apiKind: 'openai_chat',
|
||||
stream: false,
|
||||
error:
|
||||
'LLM 未配置:请在 agentLlm.audio-asset-plan.apiKey 中设置 API Key',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
if (command === 'list_local_project_files') {
|
||||
return {
|
||||
projectPath: String(args?.projectPath ?? ''),
|
||||
@@ -1878,6 +1925,14 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
exists: true,
|
||||
};
|
||||
}
|
||||
if (command === 'read_local_agent_memory') {
|
||||
return {
|
||||
taskId: String(args?.taskId ?? ''),
|
||||
path: 'memory/agents/art/asset.md',
|
||||
content: '# 美术私有记忆\n- 使用厨房像素素材\n',
|
||||
exists: true,
|
||||
};
|
||||
}
|
||||
if (command === 'read_local_project_file') {
|
||||
if (args?.relativePath === 'game/index.html') {
|
||||
return {
|
||||
@@ -1947,6 +2002,38 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
);
|
||||
expect(screen.queryByLabelText('开发环境')).toBeNull();
|
||||
expect(screen.queryByText('编排 Trace')).toBeNull();
|
||||
expect(screen.queryByText(/LLM:已配置 · art-model/)).toBeNull();
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'LLM状态' }));
|
||||
expect(await screen.findByText(/LLM 已配置:gpt-main/)).not.toBeNull();
|
||||
const agentStatusPane = screen.getByLabelText('Agent 状态');
|
||||
expect(
|
||||
within(agentStatusPane).getByText(
|
||||
'LLM:已配置 · art-model · openai_chat · 流式开 · Key已读',
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(
|
||||
within(agentStatusPane).getByText(
|
||||
'LLM:未就绪 · audio-model · openai_chat · 流式关 · Key未读',
|
||||
),
|
||||
).not.toBeNull();
|
||||
expect(screen.queryByText(/planner-secret/)).toBeNull();
|
||||
expect(screen.queryByText(/art-secret/)).toBeNull();
|
||||
|
||||
fireEvent.click(
|
||||
within(agentStatusPane).getByRole('button', {
|
||||
name: /规划美术资产/,
|
||||
}),
|
||||
);
|
||||
const agentDialog = await screen.findByRole('dialog', {
|
||||
name: 'Agent 对话',
|
||||
});
|
||||
expect(
|
||||
within(agentDialog).getByText(
|
||||
'LLM:已配置,art-model @ https://art.example.test/v1,openai_chat,流式 开启,API Key 已读取',
|
||||
),
|
||||
).not.toBeNull();
|
||||
fireEvent.click(within(agentDialog).getByRole('button', { name: '关闭' }));
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '能力' }));
|
||||
expect(await screen.findByText(/Native Runtime 能力/)).not.toBeNull();
|
||||
|
||||
@@ -3856,6 +3856,7 @@
|
||||
- 2026-06-26 调整,2026-07-03 更新:AI 游戏创作 App 借鉴 Harbour 的控制平面思想,但不搬 Harbour 后台。最近 run 在 `.agent/run.latest.json` 增加可选 `lifecycleStatus`,并通过 `/agent-status`、`/agent-kill`、`/agent-retry`、`/agent-resume [说明]` 控制本地生命周期,写入 `.agent/activity.jsonl`、`.agent/output.jsonl` 和 `.agent/context.bundle.json`;聊天里的状态 / 控制结果可填入 `/read .agent/output.jsonl` 草稿继续查看 run 输出,但不直接读取文件或绕过 `file.read` 策略。v1 的 kill/retry/resume 只更新本地状态和上下文包,不伪装成能中断已发出的上游 LLM 请求;后续引入独立 runner 后再把 `pending` 接入 claim。
|
||||
- 2026-07-03 调整:主窗口 Agent 状态栏新增“继续说明”,只把 `/agent-resume ` 填入聊天输入框,让用户补充说明后再走原确认流;策略快捷入口新增 conversation.read / conversation.write 确认草稿,同样只填输入框,不直接写 `.agent/policy.json`。
|
||||
- 2026-07-03 调整:主窗口 header 常驻项目摘要只从当前已加载的 manifest / trace 派生任务完成数、ready 数、资产来源分布和最近命令结果;未选择工作区时不显示,不为了摘要额外触发 Tauri 读取或写入,也不把任务、文件、run history 或预览开发面板搬进普通用户窗口。
|
||||
- 2026-07-03 调整:`/llm-status` 读取到的 agent 级 LLM 配置状态可回填到主窗口 Agent 状态列表和单 Agent 对话头部,显示 provider 类型、模型、流式开关和 API Key 是否已读取;密钥本体仍不能进入聊天、状态列表、manifest、trace 或本地项目文件。
|
||||
- 2026-06-25 调整:本地 HTTP 预览静态 `HEAD` 必须返回与 `GET` 相同的真实 `Content-Length`,但不返回 body;浏览器、图片、音频和视频探测不能拿到 `Content-Length: 0` 的假响应。
|
||||
- 2026-06-25 调整:普通用户通过聊天输入 `/run` 触发待确认 `game.run_local`,确认后只能复用白名单 `game.static_smoke` 自检当前 `game/index.html`,通过后启动 `127.0.0.1` 本地 HTTP 预览。独立执行 `game.static_smoke` 时如果已有 `.agent/run.latest.json`,必须追加 `Playtest / game.static_smoke` trace step,避免“运行了代码但编排 trace 不可见”。
|
||||
- 2026-06-25 调整:普通用户通过聊天输入 `/trace` 触发只读 `agent.trace_read`,读取 `.agent/run.latest.json` 并在聊天里摘要 loop 轮次、stopReason、nextStep、active / carry-over 任务、repairRoutes、agent 建议命令和最近 step。trace 面板仍只在开发窗口展示,普通用户窗口不新增面板。
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user