删除 Project Supervisor 前端链路并将项目对话收敛到 DirectProject 与立项策划
- 删除 ProjectSupervisorView、SupervisorChatOnlyView、ProjectWorkspaceChatPane、AgentConversationOverlay、DeveloperProjectPanels、DeveloperRuntimePanels 与 features/agent-runtime/panels.tsx - 删除 Supervisor 独立调试窗口:windows.rs 的 supervisor_chat_window_url / open_project_supervisor_chat_window、main.rs 的 invoke 注册、?supervisor-chat 与 ?agent-chat 前端入口、developer.json capability 和对应 Rust 用例 - 删除工作台壳的开发者 Agent 面板:DeveloperAgentPanel、useDeveloperAgentPanel、useDeveloperAgentState、developerAgentControls - App.tsx 删除只服务退役面板的 state/ref/effect/handler(agentConversation*、文件/记忆/资产/画板/预览面板处理、commandLog、llmConfigStatus、editorBaseUrl、回放历史与 trace 面板状态、selectedAgent 等)以及由此产生的空分支,并把 requestRuntimeConfigOpen 接回本地 RuntimeConfigDialog - 立项策划独立成模块:Design Agent 与 Planning V2 的容器和表现移到 view/project-development/planning/(PlanningChatView、PlanningUserInputCard、GddApprovalCard、DesignAgentSurface、PlanningLaneRuntimeStrip、planningLane、planningSessionV2、planningSessionContract) - 改名到中性概念:ProjectSupervisorComponentProps→ProjectChatComponentProps、WorkspaceLauncherShellProps.ProjectSupervisor→ProjectChat、ProjectDevelopmentView.supervisor→chat、orchestrationMode→agentDockVisible、initialSupervisorMessageClaims→initialTurnClaims、ProjectManifestSnapshotSource 'supervisor'→'chat'、CSS project-supervisor-*/project-planning-*→project-chat-* - 删除 PROJECT_SUPERVISOR_AGENT_ID 与 PROJECT_SUPERVISOR_PLAN_SOURCE 常量 - 工作台壳自己订阅 game-creator-manifest-invalidated,用 revision→清单→revision 配对读(source: asset-event)并入 currentProjectContext,且不重新检查项目目录;测试夹具改为按监听器集合广播该事件 - 删除只钉住退役界面的 appSurface 用例(旧调试窗口、开发者面板、Agent 对话浮层、运行历史面板) - 同步文档:ADR、DirectProject 聊天模块抽离实施计划与里程碑、Provider 推理里程碑、策划会话 RuntimeV2、AI 游戏创作实施计划,并在 decision-log 新增 2026-09-19 条 - 删除随退役面板失去调用方的模块与导出:projectSummaryCommands.ts(1054 行旧 Supervisor 斜杠命令处理器)、AGENT_RUN_HISTORY_* 常量、agent-runtime/model.ts 与 project-summary/agentPresentation.ts 里的死导出,以及 agentRunTrace / memoryCommands / projectCommandPolicy 中无调用方的校验与解析函数 - 更新 scripts/check-config.mjs 与 scripts/check-native-shells.mjs 的窗口与命令守卫
This commit is contained in:
@@ -86,10 +86,6 @@ const appInvokeSources = readSourceFiles(
|
||||
new URL('../src/', import.meta.url),
|
||||
new Set(['.ts', '.tsx']),
|
||||
);
|
||||
const appEntrypointSource = fs.readFileSync(
|
||||
new URL('../src/main.tsx', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const tauriHandlerSource = fs.readFileSync(
|
||||
new URL('../src-tauri/src/main.rs', import.meta.url),
|
||||
'utf8',
|
||||
@@ -114,6 +110,48 @@ const rustSharedContractSource = fs.readFileSync(
|
||||
);
|
||||
const allowedUncalledTauriCommands = [
|
||||
'append_direct_project_conversation_message',
|
||||
// Supervisor 调试窗口、开发者面板、专业 Agent 对话与旧命令聊天的前端调用方已随
|
||||
// Supervisor 前端链路整体删除;命令本身仍注册在 Rust 侧并由 native Runtime、CLI
|
||||
// swarm 与 Rust 测试使用,保留 present,仅不再出现在 App 前端源码里。
|
||||
'answer_game_creator_agent_runtime_user_input',
|
||||
'cancel_game_creator_agent_runtime_task',
|
||||
'chat_with_game_creator_role_agent',
|
||||
'chat_with_game_creator_role_agent_stream',
|
||||
'check_game_creator_llm_config',
|
||||
'confirm_game_creator_agent_runtime_task',
|
||||
'delete_local_game_memory',
|
||||
'delete_local_project_file',
|
||||
'diff_local_project_checkpoint',
|
||||
'get_game_creation_agent_capabilities',
|
||||
'get_limited_local_commands',
|
||||
'list_local_project_export_packages',
|
||||
'pick_local_file',
|
||||
'read_game_creator_agent_runtime',
|
||||
'read_local_agent_memory',
|
||||
'read_local_game_memory',
|
||||
'reject_game_creator_agent_runtime_task',
|
||||
'retry_game_creator_agent_runtime_task',
|
||||
'schedule_game_creator_agent_ready_tasks',
|
||||
'start_game_creator_agent_runtime_task',
|
||||
'steer_game_creator_agent_runtime_task',
|
||||
'write_local_agent_memory',
|
||||
'write_local_game_memory',
|
||||
'write_local_project_file',
|
||||
// Agent 运行时会话 / 目标 / 协作命令由 native 侧与 CLI swarm 驱动,前端没有调用方。
|
||||
'archive_game_creator_agent_session',
|
||||
'clear_game_creator_agent_goal',
|
||||
'compact_game_creator_agent_runtime_context',
|
||||
'confirm_retry_game_creator_agent_runtime_task',
|
||||
'create_game_creator_agent_session',
|
||||
'edit_game_creator_agent_goal',
|
||||
'fork_game_creator_agent_session',
|
||||
'list_game_creator_agent_sessions',
|
||||
'pause_game_creator_agent_goal',
|
||||
'read_game_creator_agent_goal',
|
||||
'resume_game_creator_agent_goal',
|
||||
'set_active_game_creator_agent_session',
|
||||
'start_game_creator_agent_goal',
|
||||
'start_game_creator_supervisor_runtime_task',
|
||||
// TODO: Remove the retired binding command after the legacy runtime path is removed.
|
||||
'bind_components',
|
||||
'chat_with_game_creator_agent',
|
||||
@@ -1412,13 +1450,7 @@ const eventCapability = JSON.parse(
|
||||
);
|
||||
const eventCapabilityWindows = new Set(eventCapability.windows ?? []);
|
||||
const eventCapabilityPermissions = new Set(eventCapability.permissions ?? []);
|
||||
for (const windowLabel of [
|
||||
'client',
|
||||
'developer',
|
||||
'main',
|
||||
'launcher',
|
||||
'supervisor-chat',
|
||||
]) {
|
||||
for (const windowLabel of ['client', 'main', 'launcher']) {
|
||||
if (!eventCapabilityWindows.has(windowLabel)) {
|
||||
throw new Error(
|
||||
`AI game creator shell event capability missing window: ${windowLabel}`,
|
||||
@@ -1801,20 +1833,6 @@ if (
|
||||
);
|
||||
}
|
||||
|
||||
for (const snippet of [
|
||||
'import.meta.env.DEV',
|
||||
'supervisorChatMode',
|
||||
'supervisorChatOnly',
|
||||
'open_project_supervisor_chat_window',
|
||||
'index.html?supervisor-chat&projectPath=',
|
||||
]) {
|
||||
if (!`${appEntrypointSource}\n${tauriRustSource}`.includes(snippet)) {
|
||||
throw new Error(
|
||||
`AI game creator shell developer window guardrail drifted: ${snippet}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (tauriHandlerSource.includes('open_developer_window(app.handle())?')) {
|
||||
throw new Error(
|
||||
'AI game creator normal startup must not automatically open the developer window',
|
||||
@@ -1853,25 +1871,16 @@ for (const snippet of [
|
||||
'function needsInitializedChatProject',
|
||||
'function resolvePendingCommandProjectPath',
|
||||
'resolveChatProjectPath(localProject) ?? draftProjectPath',
|
||||
'`permission.cancel ${command.id} missing-project`',
|
||||
"'/remember [short|long|blackboard] 内容:追加短期、长期或黑板记忆'",
|
||||
"'/memory-set [short|long|blackboard] 内容:覆盖保存对应记忆'",
|
||||
'function parseRememberInput',
|
||||
"'/trace 或 /loop:查看最近一次 Agent loop trace'",
|
||||
'async function executeAgentTraceChat',
|
||||
"relativePath: '.agent/logs/command.log'",
|
||||
"'permission.pending'",
|
||||
"'permission.confirm'",
|
||||
"'permission.cancel'",
|
||||
"'command.auto'",
|
||||
"'agent.run_status'",
|
||||
'function summarizeAgentRunTrace',
|
||||
'工具调用:${agentRunTrace.toolCallCount}/${agentRunTrace.maxToolCalls}',
|
||||
'agentRunTrace.error ?',
|
||||
'className="trace-error"',
|
||||
'agentRunTrace.taskGraph.repairRoutes.map',
|
||||
"in: ${step.inputPaths.join(', ') || 'none'}",
|
||||
"out: ${step.outputPaths.join(', ') || 'none'}",
|
||||
]) {
|
||||
if (!appSource.includes(snippet)) {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "developer",
|
||||
"description": "开发窗口允许打开本地素材选择对话框。",
|
||||
"windows": ["developer"],
|
||||
"permissions": ["dialog:allow-open"]
|
||||
}
|
||||
@@ -2,6 +2,6 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "events",
|
||||
"description": "允许客户端窗口订阅并取消订阅 Rust Runtime 事件。",
|
||||
"windows": ["client", "developer", "main", "launcher", "supervisor-chat"],
|
||||
"windows": ["client", "main", "launcher"],
|
||||
"permissions": ["core:event:allow-listen", "core:event:allow-unlisten"]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "window-chrome",
|
||||
"description": "自绘标题栏允许执行当前窗口的基础控制和拖拽。",
|
||||
"windows": ["client", "developer", "main", "launcher", "supervisor-chat"],
|
||||
"windows": ["client", "main", "launcher"],
|
||||
"permissions": [
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-is-maximized",
|
||||
|
||||
@@ -2665,7 +2665,6 @@ fn main() {
|
||||
write_project_permission_policy,
|
||||
open_game_creator_workspace_window,
|
||||
open_game_creator_launcher_window,
|
||||
open_project_supervisor_chat_window,
|
||||
start_local_game_preview,
|
||||
activate_local_game_preview,
|
||||
stop_local_game_preview,
|
||||
|
||||
@@ -451,18 +451,6 @@ fn dispatch_static_delegate_plain_repair(
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supervisor_chat_window_carries_encoded_project_path() {
|
||||
assert_eq!(
|
||||
supervisor_chat_window_url("/tmp/AI Game 项目").to_string(),
|
||||
"index.html?supervisor-chat&projectPath=%2Ftmp%2FAI%20Game%20%E9%A1%B9%E7%9B%AE"
|
||||
);
|
||||
assert_eq!(
|
||||
supervisor_chat_window_url("/tmp/a&b?#%+c").to_string(),
|
||||
"index.html?supervisor-chat&projectPath=%2Ftmp%2Fa%26b%3F%23%25%2Bc"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn project_supervisor_runtime_id_is_normalized_and_collected() {
|
||||
let root = unique_project_path();
|
||||
|
||||
@@ -119,13 +119,6 @@ pub(crate) fn launcher_window_url() -> tauri::WebviewUrl {
|
||||
tauri::WebviewUrl::App(PathBuf::from("index.html?launcher"))
|
||||
}
|
||||
|
||||
pub(crate) fn supervisor_chat_window_url(project_path: &str) -> tauri::WebviewUrl {
|
||||
tauri::WebviewUrl::App(PathBuf::from(format!(
|
||||
"index.html?supervisor-chat&projectPath={}",
|
||||
percent_encode_query_value(project_path)
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) fn validate_workspace_window_project_path(project_path: &str) -> Result<&str, String> {
|
||||
let project_path = project_path.trim();
|
||||
if project_path.is_empty() {
|
||||
@@ -193,52 +186,3 @@ pub(crate) fn open_game_creator_launcher_window(
|
||||
window.close().map_err(|error| error.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn open_project_supervisor_chat_window(
|
||||
app: tauri::AppHandle,
|
||||
project_path: String,
|
||||
) -> Result<(), String> {
|
||||
let project_path = validate_workspace_window_project_path(&project_path)?;
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
let _ = app;
|
||||
let _ = project_path;
|
||||
return Err("项目总控对话窗口仅在开发构建中可用".to_string());
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
if let Some(existing) = app.get_webview_window("supervisor-chat") {
|
||||
let mut current_url = existing.url().map_err(|error| error.to_string())?;
|
||||
let current_project_path = current_url
|
||||
.query_pairs()
|
||||
.find_map(|(key, value)| (key == "projectPath").then(|| value.into_owned()));
|
||||
if current_project_path.as_deref() != Some(project_path) {
|
||||
current_url.set_query(Some(&format!(
|
||||
"supervisor-chat&projectPath={}",
|
||||
percent_encode_query_value(project_path)
|
||||
)));
|
||||
current_url.set_fragment(None);
|
||||
existing
|
||||
.navigate(current_url)
|
||||
.map_err(|error| error.to_string())?;
|
||||
}
|
||||
existing.show().map_err(|error| error.to_string())?;
|
||||
existing.unminimize().map_err(|error| error.to_string())?;
|
||||
existing.set_focus().map_err(|error| error.to_string())?;
|
||||
return Ok(());
|
||||
}
|
||||
tauri::WebviewWindowBuilder::new(
|
||||
&app,
|
||||
"supervisor-chat",
|
||||
supervisor_chat_window_url(project_path),
|
||||
)
|
||||
.title("项目总控 Agent 对话")
|
||||
.decorations(false)
|
||||
.inner_size(820.0, 720.0)
|
||||
.min_inner_size(560.0, 480.0)
|
||||
.build()
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+248
-7885
File diff suppressed because it is too large
Load Diff
@@ -9,24 +9,11 @@ export function createLocalProjectId(): string {
|
||||
return `local-project-${crypto.randomUUID()}`;
|
||||
}
|
||||
|
||||
export const AGENT_RUN_HISTORY_MAX_COUNT = 100;
|
||||
export const AGENT_RUN_HISTORY_INITIAL_VISIBLE_COUNT = 20;
|
||||
export const AGENT_RUN_HISTORY_VISIBLE_STEP = 20;
|
||||
export const CONVERSATION_INITIAL_VISIBLE_COUNT = 20;
|
||||
export const CONVERSATION_VISIBLE_STEP = 20;
|
||||
/** 一次翻页操作最多连拉几页:见 ADR「分页锚点取原始条目 id」。 */
|
||||
export const DIRECT_HISTORY_MAX_PAGES_PER_ACTION = 5;
|
||||
export const AGENT_CHAT_SCROLL_BOTTOM_THRESHOLD = 48;
|
||||
export const PROJECT_SUPERVISOR_AGENT_ID = 'project-supervisor';
|
||||
/**
|
||||
* 立项策划链路的 run `source`。
|
||||
*
|
||||
* 这是全前端唯一的字面量出处:`AgentRuntimeState.source` 在类型上只是 `string`,
|
||||
* 改名不会有任何编译期提示,所以判据必须收敛到这一个常量上。`as const` 让它同时
|
||||
* 能充当 `ProjectSupervisorRuntimeSubmission['source']` 的成员。
|
||||
*/
|
||||
export const PROJECT_SUPERVISOR_PLAN_SOURCE =
|
||||
'project-supervisor-plan' as const;
|
||||
export const launcherNotifications: Array<{
|
||||
label: string;
|
||||
detail: string;
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 入口首轮需求的认领记录。
|
||||
*
|
||||
* 首页/立项链路把同一条首轮需求带进工作台,Supervisor、Design Agent、Planning V2 与
|
||||
* DirectProject 是同一页面上的不同入口;认领按页面保存并按「项目路径 + claimScope」
|
||||
* 去重,保证同一条需求只被一个入口发出。
|
||||
*/
|
||||
const initialSupervisorMessageClaimsByPage = new WeakMap<Window, Set<string>>();
|
||||
|
||||
export function claimInitialSupervisorMessageForPage(
|
||||
projectPath: string,
|
||||
scope = '',
|
||||
) {
|
||||
let claimedProjectPaths = initialSupervisorMessageClaimsByPage.get(window);
|
||||
if (!claimedProjectPaths) {
|
||||
claimedProjectPaths = new Set<string>();
|
||||
initialSupervisorMessageClaimsByPage.set(window, claimedProjectPaths);
|
||||
}
|
||||
const claimKey = `${projectPath}\u0000${scope}`;
|
||||
if (claimedProjectPaths.has(claimKey)) {
|
||||
return false;
|
||||
}
|
||||
claimedProjectPaths.add(claimKey);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 入口首轮需求的认领记录。
|
||||
*
|
||||
* 首页/立项链路把同一条首轮需求带进工作台,DirectProject 与立项策划是同一页面上的
|
||||
* 不同入口;认领按页面保存并按「项目路径 + claimScope」去重,保证同一条需求只被一个
|
||||
* 入口发出。
|
||||
*/
|
||||
const initialTurnClaimsByPage = new WeakMap<Window, Set<string>>();
|
||||
|
||||
export function claimInitialTurnForPage(projectPath: string, scope = '') {
|
||||
let claimedProjectPaths = initialTurnClaimsByPage.get(window);
|
||||
if (!claimedProjectPaths) {
|
||||
claimedProjectPaths = new Set<string>();
|
||||
initialTurnClaimsByPage.set(window, claimedProjectPaths);
|
||||
}
|
||||
const claimKey = `${projectPath}\u0000${scope}`;
|
||||
if (claimedProjectPaths.has(claimKey)) {
|
||||
return false;
|
||||
}
|
||||
claimedProjectPaths.add(claimKey);
|
||||
return true;
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
export * from './model';
|
||||
export * from './panels';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,10 @@ import {
|
||||
useWindowChrome,
|
||||
WINDOW_CHROME_DEFAULT_TITLE,
|
||||
} from '../../components/windowChromeContext';
|
||||
import {
|
||||
canSubscribeTauriEvents,
|
||||
subscribeTauriEvent,
|
||||
} from '../../services/tauriEventSubscription';
|
||||
import HomeView from '../../view/home';
|
||||
import { type LauncherView, Sidebar } from '../../view/layout';
|
||||
import ProjectDevelopmentView from '../../view/project-development';
|
||||
@@ -34,17 +38,13 @@ import {
|
||||
} from '../../view/project-development/projectResourceLiveUpdateModel';
|
||||
import TemplateLibraryView from '../../view/template-library';
|
||||
import { useDirectActiveTurns } from '../agent-runtime/directActiveTurns';
|
||||
import { projectPathsMatchForInvalidation } from '../project-summary/projectPath';
|
||||
import { RuntimeConfigDialog } from '../runtime-config/RuntimeConfigDialog';
|
||||
import { useTemplateLibrary } from '../template-library/useTemplateLibrary';
|
||||
import { AccountWalletBar, AccountWalletDialogs } from './AccountWallet';
|
||||
import {
|
||||
DeveloperAgentDialogs,
|
||||
DeveloperAgentPanel,
|
||||
} from './DeveloperAgentPanel';
|
||||
import type { WorkspaceLauncherShellProps } from './model';
|
||||
import { NonEmptyProjectDialog, ProjectsPage } from './ProjectCreation';
|
||||
import { useAccountWallet } from './useAccountWallet';
|
||||
import { useDeveloperAgentPanel } from './useDeveloperAgentPanel';
|
||||
import {
|
||||
DESIGN_ARTIFACTS_BUILD_PROMPT,
|
||||
useHomeProjectCreation,
|
||||
@@ -55,7 +55,7 @@ export function WorkspaceLauncherShell({
|
||||
currentUser,
|
||||
onLogout,
|
||||
initialView = 'home',
|
||||
ProjectSupervisor,
|
||||
ProjectChat,
|
||||
}: WorkspaceLauncherShellProps) {
|
||||
const {
|
||||
isWindowChrome,
|
||||
@@ -85,11 +85,9 @@ export function WorkspaceLauncherShell({
|
||||
projectPath: string;
|
||||
createdAt: number;
|
||||
} | null>(null);
|
||||
const developerAgent = useDeveloperAgentPanel(launcherView);
|
||||
const homeProject = useHomeProjectCreation({
|
||||
setStatus,
|
||||
setLauncherView,
|
||||
setAgentChatProjectPath: developerAgent.setAgentChatProjectPath,
|
||||
rememberRecentWorkspace,
|
||||
});
|
||||
const templateLibrary = useTemplateLibrary({
|
||||
@@ -480,7 +478,7 @@ export function WorkspaceLauncherShell({
|
||||
projectId: manifest.projectId,
|
||||
revision: status.revision,
|
||||
manifest,
|
||||
source: 'supervisor',
|
||||
source: 'chat',
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -496,6 +494,87 @@ export function WorkspaceLauncherShell({
|
||||
[applyManifestSnapshot, setCurrentProjectContext],
|
||||
);
|
||||
|
||||
/**
|
||||
* 运行态(Runtime 回合、素材命令)把「清单已变」推给壳时,壳自己把清单重读回来。
|
||||
*
|
||||
* 只做一次配对读:`revision → 清单 → revision`,三次读对不上就放弃本次,等下一次
|
||||
* 事件或回合结束。事件**不**触发重新检查项目目录,所以画布上的资源刷新不会让工作台
|
||||
* 再走一遍打开项目的流程。
|
||||
*/
|
||||
const refreshActiveProjectManifestFromDisk = useCallback(
|
||||
async (targetProjectPath: string, targetProjectId: string) => {
|
||||
const invoke = resolveTauriInvoke();
|
||||
if (!invoke) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fresh = await rereadAuthoritativeProjectManifestSnapshot({
|
||||
projectPath: targetProjectPath,
|
||||
projectId: targetProjectId,
|
||||
readRevision: async () => {
|
||||
const status = await invoke<LocalGameProjectRevisionStatus>(
|
||||
'get_local_game_project_revision',
|
||||
{ projectPath: targetProjectPath },
|
||||
);
|
||||
return status.revision;
|
||||
},
|
||||
readManifest: () =>
|
||||
invoke<NonNullable<typeof currentProjectContext>['manifest']>(
|
||||
'get_local_game_manifest',
|
||||
{ projectPath: targetProjectPath },
|
||||
),
|
||||
});
|
||||
if (fresh) {
|
||||
applyManifestSnapshot({ ...fresh, source: 'asset-event' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[manifest-merge] 清单失效事件的重读失败', error);
|
||||
}
|
||||
},
|
||||
[applyManifestSnapshot],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canSubscribeTauriEvents()) {
|
||||
return;
|
||||
}
|
||||
let disposed = false;
|
||||
let unsubscribe: (() => void) | null = null;
|
||||
void subscribeTauriEvent<{ projectPath?: string }>(
|
||||
'game-creator-manifest-invalidated',
|
||||
(event) => {
|
||||
const active = activeProjectContextRef.current;
|
||||
if (
|
||||
!active ||
|
||||
!projectPathsMatchForInvalidation(
|
||||
event.payload?.projectPath ?? '',
|
||||
active.projectPath,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
void refreshActiveProjectManifestFromDisk(
|
||||
active.projectPath,
|
||||
active.manifest.projectId,
|
||||
);
|
||||
},
|
||||
)
|
||||
.then((release) => {
|
||||
if (disposed) {
|
||||
release();
|
||||
return;
|
||||
}
|
||||
unsubscribe = release;
|
||||
})
|
||||
.catch(() => {
|
||||
// 事件订阅不可用时,清单仍在素材命令与回合结束后刷新。
|
||||
});
|
||||
return () => {
|
||||
disposed = true;
|
||||
unsubscribe?.();
|
||||
};
|
||||
}, [refreshActiveProjectManifestFromDisk]);
|
||||
|
||||
const requestCurrentProjectPlay = useCallback((nextProjectPath: string) => {
|
||||
playRequestIdRef.current += 1;
|
||||
setPlayRequest({
|
||||
@@ -618,11 +697,6 @@ export function WorkspaceLauncherShell({
|
||||
controller={templateLibrary}
|
||||
onBack={() => setLauncherView('home')}
|
||||
/>
|
||||
) : launcherView === 'agent-chat' ? (
|
||||
<DeveloperAgentPanel
|
||||
controller={developerAgent}
|
||||
onRuntimeConfigOpen={() => setRuntimeConfigOpen(true)}
|
||||
/>
|
||||
) : launcherView === 'project-development' && currentProjectContext ? (
|
||||
<>
|
||||
{manifestMergeNotice ? (
|
||||
@@ -674,7 +748,7 @@ export function WorkspaceLauncherShell({
|
||||
</div>
|
||||
) : null}
|
||||
<ProjectDevelopmentView
|
||||
orchestrationMode="single-supervisor"
|
||||
agentDockVisible={false}
|
||||
projectName={currentProjectContext.projectName}
|
||||
projectPath={currentProjectContext.projectPath}
|
||||
manifest={currentProjectContext.manifest}
|
||||
@@ -701,13 +775,13 @@ export function WorkspaceLauncherShell({
|
||||
onManifestChange={syncActiveProjectManifest}
|
||||
onHomeOpen={() => setLauncherView('home')}
|
||||
onProjectsOpen={() => setLauncherView('projects')}
|
||||
supervisor={
|
||||
<ProjectSupervisor
|
||||
chat={
|
||||
<ProjectChat
|
||||
key={`${currentProjectContext.projectPath}:${agentRuntimeMode}`}
|
||||
initialProjectPath={currentProjectContext.projectPath}
|
||||
initialProjectManifest={currentProjectContext.manifest}
|
||||
initialProjectKind={currentProjectContext.projectKind}
|
||||
initialSupervisorMessage={
|
||||
initialPlanningPrompt={
|
||||
switchedToGameRuntime
|
||||
? DESIGN_ARTIFACTS_BUILD_PROMPT
|
||||
: !omitOriginalPlanningTurnInputs
|
||||
@@ -726,12 +800,10 @@ export function WorkspaceLauncherShell({
|
||||
? currentProjectContext.attachments
|
||||
: []
|
||||
}
|
||||
initialSupervisorMessageClaimScope={
|
||||
initialPlanningPromptClaimScope={
|
||||
switchedToGameRuntime ? 'approved-design-build' : ''
|
||||
}
|
||||
activeVersionId={activeVersionId}
|
||||
orchestrationMode="single-supervisor"
|
||||
projectSupervisorOnly
|
||||
planningStartMode={planningStartMode}
|
||||
playRequest={playRequest}
|
||||
onPlayRequestHandled={handlePlayRequestHandled}
|
||||
@@ -765,11 +837,7 @@ export function WorkspaceLauncherShell({
|
||||
</section>
|
||||
{runtimeConfigOpen ? (
|
||||
<RuntimeConfigDialog
|
||||
projectPath={
|
||||
currentProjectContext?.projectPath ||
|
||||
developerAgent.agentChatProjectPath ||
|
||||
projectPath
|
||||
}
|
||||
projectPath={currentProjectContext?.projectPath || projectPath}
|
||||
onClose={() => setRuntimeConfigOpen(false)}
|
||||
/>
|
||||
) : null}
|
||||
@@ -782,7 +850,6 @@ export function WorkspaceLauncherShell({
|
||||
) : isWindowChrome ? null : (
|
||||
<AccountWalletBar controller={accountWallet} />
|
||||
)}
|
||||
<DeveloperAgentDialogs controller={developerAgent} />
|
||||
{launcherNotice ? (
|
||||
<div
|
||||
className="launcher-dialog-backdrop"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,8 +21,6 @@ import { isAbsoluteProjectPath } from '../project-summary/projectSummary';
|
||||
|
||||
const RECENT_WORKSPACES_STORAGE_KEY =
|
||||
'genarrative-ai-game-creator.recent-workspaces.v1';
|
||||
const SUPERVISOR_CHAT_DRAFT_STORAGE_PREFIX =
|
||||
'genarrative.supervisor-chat.draft';
|
||||
|
||||
export type WorkspaceLauncherProps = {
|
||||
currentUser: AuthUser;
|
||||
@@ -30,19 +28,17 @@ export type WorkspaceLauncherProps = {
|
||||
initialView?: LauncherView;
|
||||
};
|
||||
|
||||
export type ProjectSupervisorComponentProps = {
|
||||
export type ProjectChatComponentProps = {
|
||||
initialProjectPath?: string;
|
||||
initialProjectManifest?: GameCreationAppManifest;
|
||||
initialProjectKind?: 'web' | 'godot' | 'cocos';
|
||||
initialSupervisorMessage?: string;
|
||||
initialSupervisorMessageClaimScope?: string;
|
||||
initialPlanningPrompt?: string;
|
||||
initialPlanningPromptClaimScope?: string;
|
||||
initialCreationType?: HomeCreationType | null;
|
||||
initialAttachments?: LauncherImportedAttachment[];
|
||||
orchestrationMode?: 'single-supervisor' | 'professional-dag';
|
||||
projectSupervisorOnly?: boolean;
|
||||
planningStartMode?: boolean;
|
||||
/**
|
||||
* C7 当前游戏版本:由工作台壳持有,supervisor 里的 `@` 面板按它切「当前版本素材」。
|
||||
* C7 当前游戏版本:由工作台壳持有,策划聊天里的 `@` 面板按它切「当前版本素材」。
|
||||
* `null` 表示回退到 manifest 中最新的版本。
|
||||
*/
|
||||
activeVersionId?: string | null;
|
||||
@@ -66,7 +62,7 @@ export type ProjectSupervisorComponentProps = {
|
||||
};
|
||||
|
||||
export type WorkspaceLauncherShellProps = WorkspaceLauncherProps & {
|
||||
ProjectSupervisor: ComponentType<ProjectSupervisorComponentProps>;
|
||||
ProjectChat: ComponentType<ProjectChatComponentProps>;
|
||||
};
|
||||
|
||||
export type RecentProjectRow = {
|
||||
@@ -173,56 +169,11 @@ export function latestVisibleItems<T>(items: T[], visibleCount: number) {
|
||||
return items.slice(items.length - visibleCount);
|
||||
}
|
||||
|
||||
export function isDeveloperMode() {
|
||||
if (!import.meta.env.DEV) {
|
||||
return false;
|
||||
}
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.has('dev') || window.location.hash === '#dev';
|
||||
}
|
||||
|
||||
export function readInitialProjectPath() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
return params.get('projectPath') ?? '';
|
||||
}
|
||||
|
||||
function supervisorChatDraftStorageKey(projectPath: string) {
|
||||
return `${SUPERVISOR_CHAT_DRAFT_STORAGE_PREFIX}:${projectPath}`;
|
||||
}
|
||||
|
||||
export function readSupervisorChatDraft(projectPath: string) {
|
||||
const normalizedProjectPath = projectPath.trim();
|
||||
if (!normalizedProjectPath) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
return (
|
||||
window.sessionStorage.getItem(
|
||||
supervisorChatDraftStorageKey(normalizedProjectPath),
|
||||
) ?? ''
|
||||
);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function persistSupervisorChatDraft(projectPath: string, draft: string) {
|
||||
const normalizedProjectPath = projectPath.trim();
|
||||
if (!normalizedProjectPath) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const storageKey = supervisorChatDraftStorageKey(normalizedProjectPath);
|
||||
if (draft) {
|
||||
window.sessionStorage.setItem(storageKey, draft);
|
||||
} else {
|
||||
window.sessionStorage.removeItem(storageKey);
|
||||
}
|
||||
} catch {
|
||||
// A disabled session store must not block the development chat surface.
|
||||
}
|
||||
}
|
||||
|
||||
export function buildRecentProjectRows(
|
||||
recentWorkspaces: string[],
|
||||
recentWorkspaceStatuses: Record<
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,165 +0,0 @@
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import { seedManifest } from '../../app/constants';
|
||||
import type {
|
||||
AgentBackgroundSubmitMode,
|
||||
AgentChatInteractionMode,
|
||||
AgentChatPendingRuntimeRun,
|
||||
AgentChatReplyPhase,
|
||||
AgentConversationSessionRecord,
|
||||
AgentGoalDialogState,
|
||||
AgentGoalRecord,
|
||||
AgentRuntimeState,
|
||||
GameCreatorLlmConfigStatus,
|
||||
LocalConversationMessageRecord,
|
||||
TauriInvoke,
|
||||
} from '../../app/types';
|
||||
import { deriveAgentStatusCards } from '../project-summary/agentPresentation';
|
||||
|
||||
export function useDeveloperAgentState() {
|
||||
const launcherAgentChatAgents = deriveAgentStatusCards(seedManifest, null);
|
||||
const [agentChatProjectPath, setAgentChatProjectPath] = useState('');
|
||||
const [agentChatSelectedAgentId, setAgentChatSelectedAgentId] = useState(
|
||||
launcherAgentChatAgents[0]?.id ?? '',
|
||||
);
|
||||
const [agentChatMessages, setAgentChatMessages] = useState<
|
||||
LocalConversationMessageRecord[]
|
||||
>([]);
|
||||
const [agentChatSessions, setAgentChatSessions] = useState<
|
||||
AgentConversationSessionRecord[]
|
||||
>([]);
|
||||
const [agentChatSelectedSessionId, setAgentChatSelectedSessionId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [agentChatActiveSessionId, setAgentChatActiveSessionId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [agentChatLegacySessionMode, setAgentChatLegacySessionMode] =
|
||||
useState(false);
|
||||
const [agentChatConversationPath, setAgentChatConversationPath] =
|
||||
useState('');
|
||||
const [agentChatSessionStatus, setAgentChatSessionStatus] = useState('');
|
||||
const [agentChatInput, setAgentChatInput] = useState('');
|
||||
const [agentChatInteractionMode, setAgentChatInteractionMode] =
|
||||
useState<AgentChatInteractionMode>('run');
|
||||
const [agentChatRunSubmitMode, setAgentChatRunSubmitMode] =
|
||||
useState<AgentBackgroundSubmitMode>('steer');
|
||||
const [agentChatReplyPhase, setAgentChatReplyPhase] =
|
||||
useState<AgentChatReplyPhase>('idle');
|
||||
const [agentChatStatus, setAgentChatStatus] = useState('请选择项目和 Agent');
|
||||
const [agentChatBusy, setAgentChatBusy] = useState(false);
|
||||
const agentChatMessagesRef = useRef<HTMLDivElement | null>(null);
|
||||
const agentChatShouldFollowLatestRef = useRef(true);
|
||||
const [agentChatBackgroundBusy, setAgentChatBackgroundBusy] = useState(false);
|
||||
const [agentChatPendingRuntimeRun, setAgentChatPendingRuntimeRunState] =
|
||||
useState<AgentChatPendingRuntimeRun | null>(null);
|
||||
const [agentChatLlmConfigStatus, setAgentChatLlmConfigStatus] =
|
||||
useState<GameCreatorLlmConfigStatus | null>(null);
|
||||
const [agentChatLlmStatus, setAgentChatLlmStatus] =
|
||||
useState('尚未检查 LLM 配置');
|
||||
const [agentChatRuntime, setAgentChatRuntime] =
|
||||
useState<AgentRuntimeState | null>(null);
|
||||
const [agentChatActiveRuntime, setAgentChatActiveRuntime] =
|
||||
useState<AgentRuntimeState | null>(null);
|
||||
const [agentChatRuntimeError, setAgentChatRuntimeError] = useState('');
|
||||
const [agentChatGoal, setAgentChatGoal] = useState<AgentGoalRecord | null>(
|
||||
null,
|
||||
);
|
||||
const [agentChatGoalError, setAgentChatGoalError] = useState('');
|
||||
const [agentChatGoalDialog, setAgentChatGoalDialog] =
|
||||
useState<AgentGoalDialogState | null>(null);
|
||||
const [agentChatGoalDialogError, setAgentChatGoalDialogError] = useState('');
|
||||
const [agentChatResumeConfirmation, setAgentChatResumeConfirmation] =
|
||||
useState<{ projectPath: string; detail: string } | null>(null);
|
||||
const agentChatLoadVersionRef = useRef(0);
|
||||
const agentChatRuntimeResumeProjectPathRef = useRef<string | null>(null);
|
||||
const agentChatProjectPathRef = useRef(agentChatProjectPath);
|
||||
agentChatProjectPathRef.current = agentChatProjectPath;
|
||||
const agentChatSelectedAgentIdRef = useRef(agentChatSelectedAgentId);
|
||||
agentChatSelectedAgentIdRef.current = agentChatSelectedAgentId;
|
||||
const agentChatSelectedSessionIdRef = useRef(agentChatSelectedSessionId);
|
||||
agentChatSelectedSessionIdRef.current = agentChatSelectedSessionId;
|
||||
const agentChatActiveSessionIdRef = useRef(agentChatActiveSessionId);
|
||||
agentChatActiveSessionIdRef.current = agentChatActiveSessionId;
|
||||
const agentChatPendingRuntimeRunRef =
|
||||
useRef<AgentChatPendingRuntimeRun | null>(null);
|
||||
const agentChatRuntimeSyncingRunIdsRef = useRef(new Set<string>());
|
||||
const agentChatRuntimeSyncConversationRef = useRef<
|
||||
| ((
|
||||
invoke: TauriInvoke,
|
||||
pendingRun: AgentChatPendingRuntimeRun,
|
||||
) => Promise<void>)
|
||||
| null
|
||||
>(null);
|
||||
|
||||
return {
|
||||
launcherAgentChatAgents,
|
||||
agentChatProjectPath,
|
||||
setAgentChatProjectPath,
|
||||
agentChatSelectedAgentId,
|
||||
setAgentChatSelectedAgentId,
|
||||
agentChatMessages,
|
||||
setAgentChatMessages,
|
||||
agentChatSessions,
|
||||
setAgentChatSessions,
|
||||
agentChatSelectedSessionId,
|
||||
setAgentChatSelectedSessionId,
|
||||
agentChatActiveSessionId,
|
||||
setAgentChatActiveSessionId,
|
||||
agentChatLegacySessionMode,
|
||||
setAgentChatLegacySessionMode,
|
||||
agentChatConversationPath,
|
||||
setAgentChatConversationPath,
|
||||
agentChatSessionStatus,
|
||||
setAgentChatSessionStatus,
|
||||
agentChatInput,
|
||||
setAgentChatInput,
|
||||
agentChatInteractionMode,
|
||||
setAgentChatInteractionMode,
|
||||
agentChatRunSubmitMode,
|
||||
setAgentChatRunSubmitMode,
|
||||
agentChatReplyPhase,
|
||||
setAgentChatReplyPhase,
|
||||
agentChatStatus,
|
||||
setAgentChatStatus,
|
||||
agentChatBusy,
|
||||
setAgentChatBusy,
|
||||
agentChatMessagesRef,
|
||||
agentChatShouldFollowLatestRef,
|
||||
agentChatBackgroundBusy,
|
||||
setAgentChatBackgroundBusy,
|
||||
agentChatPendingRuntimeRun,
|
||||
setAgentChatPendingRuntimeRunState,
|
||||
agentChatLlmConfigStatus,
|
||||
setAgentChatLlmConfigStatus,
|
||||
agentChatLlmStatus,
|
||||
setAgentChatLlmStatus,
|
||||
agentChatRuntime,
|
||||
setAgentChatRuntime,
|
||||
agentChatActiveRuntime,
|
||||
setAgentChatActiveRuntime,
|
||||
agentChatRuntimeError,
|
||||
setAgentChatRuntimeError,
|
||||
agentChatGoal,
|
||||
setAgentChatGoal,
|
||||
agentChatGoalError,
|
||||
setAgentChatGoalError,
|
||||
agentChatGoalDialog,
|
||||
setAgentChatGoalDialog,
|
||||
agentChatGoalDialogError,
|
||||
setAgentChatGoalDialogError,
|
||||
agentChatResumeConfirmation,
|
||||
setAgentChatResumeConfirmation,
|
||||
agentChatLoadVersionRef,
|
||||
agentChatRuntimeResumeProjectPathRef,
|
||||
agentChatProjectPathRef,
|
||||
agentChatSelectedAgentIdRef,
|
||||
agentChatSelectedSessionIdRef,
|
||||
agentChatActiveSessionIdRef,
|
||||
agentChatPendingRuntimeRunRef,
|
||||
agentChatRuntimeSyncingRunIdsRef,
|
||||
agentChatRuntimeSyncConversationRef,
|
||||
};
|
||||
}
|
||||
|
||||
export type DeveloperAgentState = ReturnType<typeof useDeveloperAgentState>;
|
||||
@@ -62,7 +62,6 @@ function homeDraftPromptText() {
|
||||
type UseHomeProjectCreationOptions = {
|
||||
setStatus: Dispatch<SetStateAction<string>>;
|
||||
setLauncherView: Dispatch<SetStateAction<LauncherView>>;
|
||||
setAgentChatProjectPath: Dispatch<SetStateAction<string>>;
|
||||
rememberRecentWorkspace: (projectPath: string) => void;
|
||||
};
|
||||
|
||||
@@ -184,7 +183,6 @@ function resolveRecoverableHomeProjectPath(
|
||||
export function useHomeProjectCreation({
|
||||
setStatus,
|
||||
setLauncherView,
|
||||
setAgentChatProjectPath,
|
||||
rememberRecentWorkspace,
|
||||
}: UseHomeProjectCreationOptions) {
|
||||
const [projectPath, setProjectPathState] = useState('');
|
||||
@@ -335,7 +333,6 @@ export function useHomeProjectCreation({
|
||||
setAgentRuntimeSummaries([]);
|
||||
setAgentResults([]);
|
||||
setProjectPath(context.projectPath);
|
||||
setAgentChatProjectPath(context.projectPath);
|
||||
setLauncherView('project-development');
|
||||
rememberRecentWorkspace(context.projectPath);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
import {
|
||||
GAME_CREATION_AGENT_CAPABILITIES,
|
||||
GAME_CREATION_APP_COMMANDS,
|
||||
type GameCreationAgentCapabilityDescriptor,
|
||||
type GameCreationAgentRunStep,
|
||||
type GameCreationAgentRunTrace,
|
||||
type GameCreationAgentToolCallTrace,
|
||||
type GameCreationAppAgentGroup,
|
||||
type GameCreationAppLimitedRunCommandDescriptor,
|
||||
type GameCreationAppManifest,
|
||||
type GameCreationAppTaskState,
|
||||
type GameCreationAppTaskStatus,
|
||||
selectGameCreationAppReadyTasks,
|
||||
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import { PROJECT_SUPERVISOR_AGENT_ID } from '../../app/constants';
|
||||
import type {
|
||||
AgentRunHistoryItem,
|
||||
AgentRuntimeState,
|
||||
AgentRuntimeTaskQueueSummary,
|
||||
AgentRuntimeTaskRecord,
|
||||
AgentStatusCard,
|
||||
AgentTaskGraphState,
|
||||
GameCreatorAgentLlmConfigStatus,
|
||||
GameCreatorAgentMode,
|
||||
GameCreatorLlmConfigStatus,
|
||||
LocalProjectFileEntry,
|
||||
ProjectPermissionPolicy,
|
||||
@@ -33,9 +27,7 @@ import {
|
||||
agentRuntimeNextStepFromPhase,
|
||||
agentRuntimePlanStepText,
|
||||
agentRuntimeWaitingOnFromPhase,
|
||||
formatAgentRecentRuntimeTask,
|
||||
formatAgentRuntimePlanStep,
|
||||
formatAgentRuntimeTaskQueue,
|
||||
isAgentRuntimeTerminalState,
|
||||
projectProfessionalAgentLabel,
|
||||
projectRuntimeVisibleCurrentWork,
|
||||
@@ -43,8 +35,6 @@ import {
|
||||
taskRowsFromManifest,
|
||||
} from '../agent-runtime';
|
||||
import {
|
||||
agentTaskGraphStateLabels,
|
||||
capabilityAreaLabels,
|
||||
formatAgentRunStatus,
|
||||
formatProjectPolicyCommandList,
|
||||
formatTraceRepairRoutes,
|
||||
@@ -53,44 +43,10 @@ import {
|
||||
isSafeProjectRelativePath,
|
||||
previewStatusLabels,
|
||||
readableArtifactPathFromAgentRunTrace,
|
||||
readableArtifactsFromAgentRunTrace,
|
||||
taskGroupLabels,
|
||||
taskStatusLabels,
|
||||
} from './projectSummary';
|
||||
|
||||
export function summarizeAgentCapabilities(
|
||||
capabilities: readonly GameCreationAgentCapabilityDescriptor[] = GAME_CREATION_AGENT_CAPABILITIES,
|
||||
) {
|
||||
const lines = ['Agent 能力清单:'];
|
||||
for (const area of Object.keys(capabilityAreaLabels) as Array<
|
||||
keyof typeof capabilityAreaLabels
|
||||
>) {
|
||||
const areaCapabilities = capabilities.filter(
|
||||
(capability) => capability.area === area,
|
||||
);
|
||||
if (areaCapabilities.length === 0) {
|
||||
continue;
|
||||
}
|
||||
lines.push(
|
||||
`${capabilityAreaLabels[area]}:${areaCapabilities
|
||||
.map((capability) => capability.title)
|
||||
.join('、')}`,
|
||||
);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function summarizeLimitedLocalCommands(
|
||||
commands: readonly GameCreationAppLimitedRunCommandDescriptor[],
|
||||
) {
|
||||
if (commands.length === 0) {
|
||||
return '当前没有可运行的受限命令。';
|
||||
}
|
||||
return `可运行受限命令:\n${commands
|
||||
.map((command) => `- ${command.id} · ${command.title}`)
|
||||
.join('\n')}`;
|
||||
}
|
||||
|
||||
export function taskStatusFromTraceStep(
|
||||
stepStatus: string | undefined,
|
||||
fallback: GameCreationAppTaskStatus,
|
||||
@@ -245,10 +201,10 @@ export function deriveAgentStatusCards(
|
||||
|
||||
export function projectAgentRuntimeSummaries(
|
||||
nextManifest: GameCreationAppManifest,
|
||||
supervisorRuntime: AgentRuntimeState | null,
|
||||
rootRuntime: AgentRuntimeState | null,
|
||||
runtimeByAgentId: Record<string, AgentRuntimeState | undefined>,
|
||||
): ProjectAgentRuntimeSummary[] {
|
||||
if (!supervisorRuntime?.runId) {
|
||||
if (!rootRuntime?.runId) {
|
||||
return [];
|
||||
}
|
||||
const manifestTasks = taskRowsFromManifest(nextManifest);
|
||||
@@ -301,8 +257,7 @@ export function projectAgentRuntimeSummaries(
|
||||
['agent-delegate', 'agent-delegate-retry'].includes(
|
||||
runtime.source,
|
||||
) &&
|
||||
runtime.parentAgentId === PROJECT_SUPERVISOR_AGENT_ID &&
|
||||
runtime.parentRunId === supervisorRuntime.runId,
|
||||
runtime.parentRunId === rootRuntime.runId,
|
||||
),
|
||||
)
|
||||
.sort(
|
||||
@@ -409,55 +364,6 @@ export function sameStringArray(left: string[], right: string[]) {
|
||||
);
|
||||
}
|
||||
|
||||
export function sameAgentStatusCard(
|
||||
left: AgentStatusCard,
|
||||
right: AgentStatusCard,
|
||||
) {
|
||||
return (
|
||||
left.id === right.id &&
|
||||
left.taskId === right.taskId &&
|
||||
left.title === right.title &&
|
||||
left.group === right.group &&
|
||||
left.role === right.role &&
|
||||
left.status === right.status &&
|
||||
left.summary === right.summary &&
|
||||
left.pass === right.pass &&
|
||||
left.phase === right.phase &&
|
||||
left.lifecycleStatus === right.lifecycleStatus &&
|
||||
left.runtimeStatus === right.runtimeStatus &&
|
||||
left.runtimePhase === right.runtimePhase &&
|
||||
left.runtimeGoal === right.runtimeGoal &&
|
||||
left.runtimeAction === right.runtimeAction &&
|
||||
left.runtimeWaitingOn === right.runtimeWaitingOn &&
|
||||
left.runtimeNextStep === right.runtimeNextStep &&
|
||||
left.runtimeTask === right.runtimeTask &&
|
||||
left.runtimeError === right.runtimeError &&
|
||||
left.runtimeRunId === right.runtimeRunId &&
|
||||
left.runtimeLoopIteration === right.runtimeLoopIteration &&
|
||||
left.runtimeMaxLoopIterations === right.runtimeMaxLoopIterations &&
|
||||
left.runtimeToolActionBudget === right.runtimeToolActionBudget &&
|
||||
left.runtimeActivePlanStep === right.runtimeActivePlanStep &&
|
||||
sameAgentRuntimeTaskQueue(left.runtimeTaskQueue, right.runtimeTaskQueue) &&
|
||||
left.hasRecentEvidence === right.hasRecentEvidence &&
|
||||
left.taskGraphState === right.taskGraphState &&
|
||||
sameStringArray(left.inputPaths, right.inputPaths) &&
|
||||
sameStringArray(left.outputPaths, right.outputPaths) &&
|
||||
sameAgentRuntimeTasks(left.runtimeRecentTasks, right.runtimeRecentTasks) &&
|
||||
left.toolCalls.length === right.toolCalls.length &&
|
||||
left.toolCalls.every((toolCall, index) => {
|
||||
const other = right.toolCalls[index];
|
||||
return (
|
||||
other &&
|
||||
toolCall.toolId === other.toolId &&
|
||||
toolCall.status === other.status &&
|
||||
toolCall.summary === other.summary &&
|
||||
sameStringArray(toolCall.inputPaths, other.inputPaths) &&
|
||||
sameStringArray(toolCall.outputPaths, other.outputPaths)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function sameAgentRuntimeTaskQueue(
|
||||
left: AgentRuntimeTaskQueueSummary | null,
|
||||
right: AgentRuntimeTaskQueueSummary | null,
|
||||
@@ -761,14 +667,6 @@ export function formatAgentRunControlError(action: string, message: string) {
|
||||
: '暂无可控制的 Agent run。先生成一次游戏草案后再操作。';
|
||||
}
|
||||
|
||||
export function isCodexAgentMode(mode: GameCreatorAgentMode | undefined) {
|
||||
return mode === 'codex_app_server' || mode === 'codex_cli';
|
||||
}
|
||||
|
||||
export function formatCodexAgentModeLabel(mode: GameCreatorAgentMode) {
|
||||
return mode === 'codex_app_server' ? '官方智能服务' : '官方智能服务';
|
||||
}
|
||||
|
||||
export function formatCodexRuntimeCapabilities(
|
||||
status: Pick<
|
||||
GameCreatorLlmConfigStatus,
|
||||
@@ -783,71 +681,6 @@ export function formatCodexRuntimeCapabilities(
|
||||
].join(',');
|
||||
}
|
||||
|
||||
export function formatLlmAgentStatusLine(
|
||||
agent: GameCreatorAgentLlmConfigStatus,
|
||||
) {
|
||||
const parts = [
|
||||
`${agent.label}:${agent.configured ? '已连接' : '未就绪'}`,
|
||||
`流式 ${agent.stream ? '开启' : '关闭'}`,
|
||||
`联网检索 ${agent.webSearchEnabled ? '开启' : '关闭'}`,
|
||||
`账号状态 ${visibleLlmCredentialState(agent.accountCredentialState)}`,
|
||||
];
|
||||
if (!agent.configured && agent.error) {
|
||||
parts.push(`提示:${visibleLlmError(agent.accountCredentialState)}`);
|
||||
}
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
export function formatLlmRouteEndpoint(
|
||||
status: Pick<
|
||||
GameCreatorLlmConfigStatus,
|
||||
| 'configured'
|
||||
| 'agentMode'
|
||||
| 'reasoningEffort'
|
||||
| 'stream'
|
||||
| 'webSearchEnabled'
|
||||
| 'accountCredentialState'
|
||||
>,
|
||||
) {
|
||||
return `官方智能服务:${status.configured === false ? '未就绪' : '已连接'},流式 ${
|
||||
status.stream ? '开启' : '关闭'
|
||||
},联网检索 ${
|
||||
status.webSearchEnabled ? '开启' : '关闭'
|
||||
},账号状态 ${visibleLlmCredentialState(status.accountCredentialState)}`;
|
||||
}
|
||||
|
||||
export function isSameResolvedLlmRouteAsGlobal(
|
||||
globalStatus: GameCreatorLlmConfigStatus,
|
||||
agentStatus: GameCreatorAgentLlmConfigStatus,
|
||||
) {
|
||||
return (
|
||||
agentStatus.agentMode === globalStatus.agentMode &&
|
||||
agentStatus.reasoningEffort === globalStatus.reasoningEffort &&
|
||||
agentStatus.stream === globalStatus.stream &&
|
||||
agentStatus.webSearchEnabled === globalStatus.webSearchEnabled
|
||||
);
|
||||
}
|
||||
|
||||
export function summarizeAgentLlmRoutes(status: GameCreatorLlmConfigStatus) {
|
||||
const agents = status.agents ?? [];
|
||||
const readyCount = agents.filter((agent) => agent.configured).length;
|
||||
const gapAgents = agents.filter((agent) => !agent.configured);
|
||||
const draftCommand = gapAgents.length > 0 ? '/config' : '/llm-status';
|
||||
|
||||
return {
|
||||
text: [
|
||||
'Agent 智能服务状态:',
|
||||
`- 总体:${status.configured ? '已连接' : '未就绪'} · ${readyCount}/${agents.length} 个 Agent 可用 · ${gapAgents.length} 个待处理`,
|
||||
`- 账号状态:${visibleLlmCredentialState(status.accountCredentialState)}`,
|
||||
`- 输出方式:流式${status.stream ? '开启' : '关闭'} · 联网检索${status.webSearchEnabled ? '开启' : '关闭'}`,
|
||||
'- 所有 Agent 使用统一的官方智能服务',
|
||||
`- 建议:${draftCommand}`,
|
||||
].join('\n'),
|
||||
draftCommand,
|
||||
draftCommandLabel: gapAgents.length > 0 ? '打开配置' : '查看状态',
|
||||
};
|
||||
}
|
||||
|
||||
export function llmStatusForAgentCard(
|
||||
status: GameCreatorLlmConfigStatus | null,
|
||||
agent: Pick<AgentStatusCard, 'id' | 'taskId'>,
|
||||
@@ -860,19 +693,6 @@ export function llmStatusForAgentCard(
|
||||
);
|
||||
}
|
||||
|
||||
export function formatAgentLlmConfigWarning(
|
||||
status: GameCreatorLlmConfigStatus | null,
|
||||
agent: Pick<AgentStatusCard, 'id' | 'taskId' | 'title'>,
|
||||
) {
|
||||
const agentStatus = llmStatusForAgentCard(status, agent);
|
||||
if (!agentStatus || agentStatus.configured) {
|
||||
return null;
|
||||
}
|
||||
return `当前 Agent 智能服务未就绪:${visibleLlmError(
|
||||
agentStatus.accountCredentialState,
|
||||
)}`;
|
||||
}
|
||||
|
||||
export function formatAgentCardLlmStatus(
|
||||
status: GameCreatorLlmConfigStatus | null,
|
||||
agent: AgentStatusCard,
|
||||
@@ -941,26 +761,6 @@ export function formatAgentPolicySummary(policy: ProjectPermissionPolicy) {
|
||||
.join(';');
|
||||
}
|
||||
|
||||
export function formatAgentDialogLlmStatus(
|
||||
status: GameCreatorLlmConfigStatus | null,
|
||||
agent: AgentStatusCard,
|
||||
) {
|
||||
const agentStatus = llmStatusForAgentCard(status, agent);
|
||||
if (!agentStatus) {
|
||||
return null;
|
||||
}
|
||||
const parts = [
|
||||
`官方智能服务:${agentStatus.configured ? '已连接' : '未就绪'}`,
|
||||
`流式 ${agentStatus.stream ? '开启' : '关闭'}`,
|
||||
`联网检索 ${agentStatus.webSearchEnabled ? '开启' : '关闭'}`,
|
||||
`账号状态 ${visibleLlmCredentialState(agentStatus.accountCredentialState)}`,
|
||||
];
|
||||
if (!agentStatus.configured && agentStatus.error) {
|
||||
parts.push(`提示:${visibleLlmError(agentStatus.accountCredentialState)}`);
|
||||
}
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
function visibleLlmCredentialState(state: string | undefined) {
|
||||
switch (state) {
|
||||
case 'ready':
|
||||
@@ -977,64 +777,6 @@ function visibleLlmCredentialState(state: string | undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
function visibleLlmError(state: string | undefined) {
|
||||
switch (state) {
|
||||
case 'login_required':
|
||||
return '请登录或重新登录后重试';
|
||||
case 'permission_denied':
|
||||
return '当前账号没有使用智能服务的权限';
|
||||
case 'revoked':
|
||||
return '账号授权已失效,请重新登录';
|
||||
default:
|
||||
return '官方智能服务暂不可用,请稍后重试';
|
||||
}
|
||||
}
|
||||
|
||||
export function summarizeAgentStatusCardsForChat(
|
||||
agents: AgentStatusCard[],
|
||||
status: GameCreatorLlmConfigStatus | null,
|
||||
) {
|
||||
if (agents.length === 0) {
|
||||
return 'Agent 状态:暂无 Agent';
|
||||
}
|
||||
return `Agent 状态:\n${agents
|
||||
.map((agent) => {
|
||||
const parts = [
|
||||
`${taskGroupLabels[agent.group]} / ${agent.role}`,
|
||||
agent.title,
|
||||
taskStatusLabels[agent.status],
|
||||
agent.pass !== null || agent.phase
|
||||
? `pass ${agent.pass ?? '-'} · ${agent.phase ?? '-'}`
|
||||
: null,
|
||||
agent.lifecycleStatus ? `run ${agent.lifecycleStatus}` : null,
|
||||
agent.taskGraphState
|
||||
? `编排 ${agentTaskGraphStateLabels[agent.taskGraphState]}`
|
||||
: null,
|
||||
formatAgentCardLlmStatus(status, agent),
|
||||
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,
|
||||
agent.runtimeActivePlanStep
|
||||
? ` 当前计划步骤:${agent.runtimeActivePlanStep}`
|
||||
: null,
|
||||
runtimeTaskQueue ? ` ${runtimeTaskQueue}` : null,
|
||||
latestRuntimeTask
|
||||
? ` 最近任务:${formatAgentRecentRuntimeTask(latestRuntimeTask)}`
|
||||
: null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n');
|
||||
})
|
||||
.join('\n')}`;
|
||||
}
|
||||
|
||||
export const agentRoleMemoryFileNames: Record<string, string> = {
|
||||
Director: 'director.md',
|
||||
Gameplay: 'gameplay.md',
|
||||
@@ -1068,21 +810,6 @@ export function agentMemoryReadDraftsFromManifest(
|
||||
.filter((draft): draft is NonNullable<typeof draft> => draft !== null);
|
||||
}
|
||||
|
||||
export function summarizeAgentMemoryReadDrafts(
|
||||
nextManifest: GameCreationAppManifest,
|
||||
) {
|
||||
const drafts = agentMemoryReadDraftsFromManifest(nextManifest);
|
||||
if (drafts.length === 0) {
|
||||
return 'Agent 私有记忆:暂无可读取记忆路径';
|
||||
}
|
||||
return `Agent 私有记忆读取命令:\n${drafts
|
||||
.map(
|
||||
({ task, path }) =>
|
||||
`- ${taskGroupLabels[task.group]} / ${task.role} · ${task.title}:/read ${path}`,
|
||||
)
|
||||
.join('\n')}`;
|
||||
}
|
||||
|
||||
export function agentConversationReadDraftFromTask(
|
||||
task: GameCreationAppTaskState,
|
||||
) {
|
||||
@@ -1100,21 +827,6 @@ export function agentConversationReadDraftsFromManifest(
|
||||
);
|
||||
}
|
||||
|
||||
export function summarizeAgentConversationReadDrafts(
|
||||
nextManifest: GameCreationAppManifest,
|
||||
) {
|
||||
const drafts = agentConversationReadDraftsFromManifest(nextManifest);
|
||||
if (drafts.length === 0) {
|
||||
return 'Agent 对话记录:暂无可读取对话路径';
|
||||
}
|
||||
return `Agent 对话读取命令:\n${drafts
|
||||
.map(
|
||||
({ task, path }) =>
|
||||
`- ${taskGroupLabels[task.group]} / ${task.role} · ${task.title}:/read ${path}`,
|
||||
)
|
||||
.join('\n')}`;
|
||||
}
|
||||
|
||||
export function formatTraceTaskWaves(
|
||||
waves: string[][],
|
||||
tasks: GameCreationAppTaskState[],
|
||||
@@ -1179,65 +891,6 @@ export function readablePassArtifactsFromAgentRunTrace(
|
||||
);
|
||||
}
|
||||
|
||||
export function summarizeRunArtifactReadDrafts(
|
||||
trace: GameCreationAgentRunTrace | null,
|
||||
) {
|
||||
if (!trace) {
|
||||
return '最近 Run 产物:暂无最近 trace';
|
||||
}
|
||||
const artifacts = readableArtifactsFromAgentRunTrace(trace);
|
||||
if (artifacts.length === 0) {
|
||||
return '最近 Run 产物:暂无可读取产物';
|
||||
}
|
||||
return `最近 Run 产物读取命令:\n${artifacts
|
||||
.map(
|
||||
(artifact) =>
|
||||
`- ${artifact.path} · ${artifact.sizeBytes}B · ${artifact.checksum}:/read ${artifact.path}`,
|
||||
)
|
||||
.join('\n')}`;
|
||||
}
|
||||
|
||||
export function summarizeAgentPassArtifactReadDrafts(
|
||||
trace: GameCreationAgentRunTrace | null,
|
||||
) {
|
||||
if (!trace) {
|
||||
return 'Agent 轮次产物:暂无最近 trace';
|
||||
}
|
||||
const artifacts = readablePassArtifactsFromAgentRunTrace(trace);
|
||||
if (artifacts.length === 0) {
|
||||
return 'Agent 轮次产物:暂无可读取轮次产物';
|
||||
}
|
||||
return `Agent 轮次产物读取命令:\n${artifacts
|
||||
.map(
|
||||
(artifact) =>
|
||||
`- ${artifact.path} · ${artifact.sizeBytes}B · ${artifact.checksum}:/read ${artifact.path}`,
|
||||
)
|
||||
.join('\n')}`;
|
||||
}
|
||||
|
||||
export function summarizeAgentRunHistoryReadDrafts(
|
||||
trace: GameCreationAgentRunTrace | null,
|
||||
history: AgentRunHistoryItem[],
|
||||
) {
|
||||
const lines: string[] = [];
|
||||
if (trace) {
|
||||
lines.push(
|
||||
`- 当前指针 ${trace.runId} · ${formatAgentRunStatus(trace)}:/trace`,
|
||||
);
|
||||
}
|
||||
history.slice(0, 5).forEach((item) => {
|
||||
lines.push(
|
||||
`- ${item.trace.runId} · ${formatAgentRunStatus(item.trace)} · ${item.path} · ${item.size}B:/read ${item.path}`,
|
||||
);
|
||||
});
|
||||
if (history.length > 5) {
|
||||
lines.push(`- 还有 ${history.length - 5} 个已加载历史 run`);
|
||||
}
|
||||
return lines.length > 0
|
||||
? `Run 历史读取命令:\n${lines.join('\n')}`
|
||||
: 'Run 历史读取命令:暂无已加载 run';
|
||||
}
|
||||
|
||||
export function summarizeLlmConversation(trace: GameCreationAgentRunTrace) {
|
||||
const llmSteps = trace.steps.filter((step) =>
|
||||
step.toolCalls.some((toolCall) => toolCall.toolId.startsWith('llm.')),
|
||||
|
||||
-440
@@ -1,440 +0,0 @@
|
||||
import type {
|
||||
Dispatch,
|
||||
FormEventHandler,
|
||||
SetStateAction,
|
||||
UIEventHandler,
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
closeDialogOnBackdropMouseDown,
|
||||
closeDialogOnEscape,
|
||||
} from '../../app/dialogs';
|
||||
import type {
|
||||
AgentBackgroundSubmitMode,
|
||||
AgentRuntimeState,
|
||||
AgentRuntimeUserInputRequest,
|
||||
AgentStatusCard,
|
||||
LocalConversationMessageRecord,
|
||||
} from '../../app/types';
|
||||
import {
|
||||
AgentRuntimeStatusPanel,
|
||||
projectProfessionalAgentLabel,
|
||||
projectSupervisorVisibleConversationText,
|
||||
} from '../agent-runtime';
|
||||
import { commandDraftFromSuggestedToolCall } from '../project-summary/agentPresentation';
|
||||
import {
|
||||
agentTaskGraphStateLabels,
|
||||
isSafeProjectRelativePath,
|
||||
taskGroupLabels,
|
||||
taskStatusLabels,
|
||||
} from '../project-summary/projectSummary';
|
||||
|
||||
type SuggestedToolCall = AgentStatusCard['toolCalls'][number];
|
||||
|
||||
type AgentConversationOverlayProps = {
|
||||
agentConversationBackgroundBusy: boolean;
|
||||
agentConversationInput: string;
|
||||
agentConversationMessages: LocalConversationMessageRecord[];
|
||||
agentConversationRunSubmitMode: AgentBackgroundSubmitMode;
|
||||
agentConversationRuntime: AgentRuntimeState | null;
|
||||
agentConversationRuntimeError: string;
|
||||
agentConversationSaving: boolean;
|
||||
agentConversationStatus: string;
|
||||
agentMemoryContent: string;
|
||||
agentMemoryStatus: string;
|
||||
answerSelectedAgentRuntimeUserInput: (
|
||||
agent: AgentStatusCard,
|
||||
request: AgentRuntimeUserInputRequest,
|
||||
responseId: string,
|
||||
answers: Record<string, string>,
|
||||
) => void | Promise<void>;
|
||||
cancelSelectedAgentRuntimeTask: (
|
||||
agent: AgentStatusCard,
|
||||
runId: string,
|
||||
) => void | Promise<void>;
|
||||
closeAgentConversation: () => void;
|
||||
confirmSelectedAgentRuntimeTask: (
|
||||
agent: AgentStatusCard,
|
||||
runId: string,
|
||||
actionId: string,
|
||||
) => void | Promise<void>;
|
||||
executeLlmConfigStatus: () => void | Promise<void>;
|
||||
handleAgentBackgroundTaskSubmit: () => void;
|
||||
handleAgentConversationScroll: UIEventHandler<HTMLDivElement>;
|
||||
handleAgentConversationSubmit: FormEventHandler<HTMLFormElement>;
|
||||
handleAgentPrivateMemorySubmit: () => void;
|
||||
hiddenAgentConversationCount: number;
|
||||
openAgentConversation: (
|
||||
agent: AgentStatusCard,
|
||||
preserveInput?: boolean,
|
||||
forceRefresh?: boolean,
|
||||
) => void | Promise<void>;
|
||||
prepareAgentEvidenceReadDraft: (path: string) => void;
|
||||
prepareSuggestedToolCommandDraft: (toolCall: SuggestedToolCall) => void;
|
||||
rejectSelectedAgentRuntimeTask: (
|
||||
agent: AgentStatusCard,
|
||||
runId: string,
|
||||
actionId: string,
|
||||
) => void | Promise<void>;
|
||||
retrySelectedAgentRuntimeTask: (
|
||||
agent: AgentStatusCard,
|
||||
runId: string,
|
||||
) => void | Promise<void>;
|
||||
selectedAgent: AgentStatusCard;
|
||||
selectedAgentLlmStatus: string | null;
|
||||
selectedAgentLlmWarning: string | null;
|
||||
selectedAgentNeedsUserInput: boolean;
|
||||
selectedAgentSteerRuntime: AgentRuntimeState | null;
|
||||
setAgentConversationInput: Dispatch<SetStateAction<string>>;
|
||||
setAgentConversationRunSubmitMode: Dispatch<
|
||||
SetStateAction<AgentBackgroundSubmitMode>
|
||||
>;
|
||||
setRuntimeConfigOpen: Dispatch<SetStateAction<boolean>>;
|
||||
showEarlierAgentConversationMessages: () => void;
|
||||
visibleAgentConversationMessages: LocalConversationMessageRecord[];
|
||||
};
|
||||
|
||||
export function AgentConversationOverlay({
|
||||
agentConversationBackgroundBusy,
|
||||
agentConversationInput,
|
||||
agentConversationMessages,
|
||||
agentConversationRunSubmitMode,
|
||||
agentConversationRuntime,
|
||||
agentConversationRuntimeError,
|
||||
agentConversationSaving,
|
||||
agentConversationStatus,
|
||||
agentMemoryContent,
|
||||
agentMemoryStatus,
|
||||
answerSelectedAgentRuntimeUserInput,
|
||||
cancelSelectedAgentRuntimeTask,
|
||||
closeAgentConversation,
|
||||
confirmSelectedAgentRuntimeTask,
|
||||
executeLlmConfigStatus,
|
||||
handleAgentBackgroundTaskSubmit,
|
||||
handleAgentConversationScroll,
|
||||
handleAgentConversationSubmit,
|
||||
handleAgentPrivateMemorySubmit,
|
||||
hiddenAgentConversationCount,
|
||||
openAgentConversation,
|
||||
prepareAgentEvidenceReadDraft,
|
||||
prepareSuggestedToolCommandDraft,
|
||||
rejectSelectedAgentRuntimeTask,
|
||||
retrySelectedAgentRuntimeTask,
|
||||
selectedAgent,
|
||||
selectedAgentLlmStatus,
|
||||
selectedAgentLlmWarning,
|
||||
selectedAgentNeedsUserInput,
|
||||
selectedAgentSteerRuntime,
|
||||
setAgentConversationInput,
|
||||
setAgentConversationRunSubmitMode,
|
||||
setRuntimeConfigOpen,
|
||||
showEarlierAgentConversationMessages,
|
||||
visibleAgentConversationMessages,
|
||||
}: AgentConversationOverlayProps) {
|
||||
return (
|
||||
<div
|
||||
className="settings-overlay"
|
||||
role="presentation"
|
||||
onMouseDown={(event) =>
|
||||
closeDialogOnBackdropMouseDown(event, closeAgentConversation)
|
||||
}
|
||||
>
|
||||
<section
|
||||
className="agent-conversation-panel"
|
||||
role="dialog"
|
||||
aria-label="Agent 对话"
|
||||
aria-modal="true"
|
||||
onKeyDown={(event) =>
|
||||
closeDialogOnEscape(event, closeAgentConversation)
|
||||
}
|
||||
>
|
||||
<header className="panel-header">
|
||||
<div>
|
||||
<h2>{selectedAgent.title}</h2>
|
||||
<p className="status-line">
|
||||
{`${taskGroupLabels[selectedAgent.group]} / ${
|
||||
selectedAgent.role
|
||||
} · ${taskStatusLabels[selectedAgent.status]}`}
|
||||
</p>
|
||||
{selectedAgent.pass !== null || selectedAgent.phase ? (
|
||||
<p className="status-line">
|
||||
{`pass ${selectedAgent.pass ?? '-'} · ${
|
||||
selectedAgent.phase ?? '-'
|
||||
}`}
|
||||
</p>
|
||||
) : null}
|
||||
{selectedAgent.lifecycleStatus ? (
|
||||
<p className="status-line">
|
||||
{`run: ${selectedAgent.lifecycleStatus}`}
|
||||
</p>
|
||||
) : null}
|
||||
{selectedAgent.taskGraphState ? (
|
||||
<p className="status-line">
|
||||
{`编排:${
|
||||
agentTaskGraphStateLabels[selectedAgent.taskGraphState]
|
||||
}`}
|
||||
</p>
|
||||
) : null}
|
||||
{selectedAgentLlmStatus ? (
|
||||
<p className="status-line">{selectedAgentLlmStatus}</p>
|
||||
) : null}
|
||||
<p className="status-line">{agentConversationStatus}</p>
|
||||
<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)}
|
||||
>
|
||||
刷新
|
||||
</button>
|
||||
<button type="button" onClick={closeAgentConversation}>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
{selectedAgentLlmWarning ? (
|
||||
<div className="agent-llm-warning" role="status">
|
||||
<strong>{selectedAgentLlmWarning}</strong>
|
||||
<button type="button" onClick={() => setRuntimeConfigOpen(true)}>
|
||||
打开配置
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
<AgentRuntimeStatusPanel
|
||||
runtime={agentConversationRuntime}
|
||||
error={agentConversationRuntimeError}
|
||||
controlBusy={agentConversationBackgroundBusy}
|
||||
onCancelRuntimeTask={(runId) =>
|
||||
selectedAgent
|
||||
? void cancelSelectedAgentRuntimeTask(selectedAgent, runId)
|
||||
: undefined
|
||||
}
|
||||
onRetryRuntimeTask={(runId) =>
|
||||
selectedAgent
|
||||
? void retrySelectedAgentRuntimeTask(selectedAgent, runId)
|
||||
: undefined
|
||||
}
|
||||
onConfirmRuntimeTask={(runId, actionId) =>
|
||||
selectedAgent
|
||||
? void confirmSelectedAgentRuntimeTask(
|
||||
selectedAgent,
|
||||
runId,
|
||||
actionId,
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
onRejectRuntimeTask={(runId, actionId) =>
|
||||
selectedAgent
|
||||
? void rejectSelectedAgentRuntimeTask(
|
||||
selectedAgent,
|
||||
runId,
|
||||
actionId,
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
onSubmitUserInput={(request, responseId, answers) =>
|
||||
selectedAgent
|
||||
? answerSelectedAgentRuntimeUserInput(
|
||||
selectedAgent,
|
||||
request,
|
||||
responseId,
|
||||
answers,
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
onRefreshRuntime={() =>
|
||||
selectedAgent
|
||||
? void openAgentConversation(selectedAgent, true, true)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="agent-conversation-list"
|
||||
onScroll={handleAgentConversationScroll}
|
||||
>
|
||||
{agentConversationMessages.length > 0 ? (
|
||||
<>
|
||||
{hiddenAgentConversationCount > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
className="message-history-more"
|
||||
onClick={showEarlierAgentConversationMessages}
|
||||
>
|
||||
{`显示更早 · 还有 ${hiddenAgentConversationCount} 条对话`}
|
||||
</button>
|
||||
) : null}
|
||||
{visibleAgentConversationMessages.map((message, index) => (
|
||||
<p
|
||||
key={`${message.updatedAt}-${index}`}
|
||||
className={`message message--${message.role}`}
|
||||
>
|
||||
{projectSupervisorVisibleConversationText(
|
||||
message.content,
|
||||
message.role,
|
||||
projectProfessionalAgentLabel(selectedAgent.id),
|
||||
)}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
<p className="status-line">暂无对话</p>
|
||||
)}
|
||||
</div>
|
||||
{!selectedAgent.hasRecentEvidence ||
|
||||
selectedAgent.inputPaths.length > 0 ||
|
||||
selectedAgent.outputPaths.length > 0 ||
|
||||
selectedAgent.toolCalls.length > 0 ? (
|
||||
<section className="agent-memory-box" aria-label="Agent 最近证据">
|
||||
<strong>最近证据</strong>
|
||||
{!selectedAgent.hasRecentEvidence ? (
|
||||
<small>暂无最近运行证据</small>
|
||||
) : null}
|
||||
{selectedAgent.inputPaths.length > 0 ? (
|
||||
<small>
|
||||
in:
|
||||
{selectedAgent.inputPaths.map((path) => (
|
||||
<span key={`in-${path}`}>
|
||||
{` ${path}`}
|
||||
{isSafeProjectRelativePath(path) ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`填入读取 ${path}`}
|
||||
onClick={() => prepareAgentEvidenceReadDraft(path)}
|
||||
>
|
||||
读取命令
|
||||
</button>
|
||||
) : null}
|
||||
</span>
|
||||
))}
|
||||
</small>
|
||||
) : null}
|
||||
{selectedAgent.outputPaths.length > 0 ? (
|
||||
<small>
|
||||
out:
|
||||
{selectedAgent.outputPaths.map((path) => (
|
||||
<span key={`out-${path}`}>
|
||||
{` ${path}`}
|
||||
{isSafeProjectRelativePath(path) ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`填入读取 ${path}`}
|
||||
onClick={() => prepareAgentEvidenceReadDraft(path)}
|
||||
>
|
||||
读取命令
|
||||
</button>
|
||||
) : null}
|
||||
</span>
|
||||
))}
|
||||
</small>
|
||||
) : null}
|
||||
{selectedAgent.toolCalls.slice(0, 5).map((toolCall) => {
|
||||
const commandDraft = commandDraftFromSuggestedToolCall(toolCall);
|
||||
return (
|
||||
<small key={`${toolCall.toolId}-${toolCall.status}`}>
|
||||
{[
|
||||
`tool: ${toolCall.toolId}`,
|
||||
toolCall.status,
|
||||
toolCall.summary || '无摘要',
|
||||
toolCall.inputPaths.length > 0
|
||||
? `in ${toolCall.inputPaths.join(', ')}`
|
||||
: null,
|
||||
toolCall.outputPaths.length > 0
|
||||
? `out ${toolCall.outputPaths.join(', ')}`
|
||||
: null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ')}
|
||||
{commandDraft ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => prepareSuggestedToolCommandDraft(toolCall)}
|
||||
>
|
||||
填入同步命令
|
||||
</button>
|
||||
) : null}
|
||||
</small>
|
||||
);
|
||||
})}
|
||||
{selectedAgent.toolCalls.length > 5 ? (
|
||||
<small>{`还有 ${selectedAgent.toolCalls.length - 5} 个工具调用`}</small>
|
||||
) : null}
|
||||
</section>
|
||||
) : null}
|
||||
<section className="agent-memory-box" aria-label="Agent 私有记忆">
|
||||
<strong>私有记忆</strong>
|
||||
<p className="status-line">{agentMemoryStatus}</p>
|
||||
{agentMemoryContent ? <pre>{agentMemoryContent}</pre> : null}
|
||||
</section>
|
||||
<form className="composer" onSubmit={handleAgentConversationSubmit}>
|
||||
<input
|
||||
aria-label="Agent 对话内容"
|
||||
disabled={
|
||||
agentConversationSaving ||
|
||||
agentConversationBackgroundBusy ||
|
||||
selectedAgentNeedsUserInput ||
|
||||
selectedAgentLlmWarning !== null
|
||||
}
|
||||
value={agentConversationInput}
|
||||
onChange={(event) =>
|
||||
setAgentConversationInput(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={
|
||||
agentConversationSaving ||
|
||||
agentConversationBackgroundBusy ||
|
||||
selectedAgentNeedsUserInput ||
|
||||
selectedAgentLlmWarning !== null
|
||||
}
|
||||
>
|
||||
发送
|
||||
</button>
|
||||
{selectedAgentSteerRuntime ? (
|
||||
<select
|
||||
aria-label="项目 Agent 后台任务提交方式"
|
||||
disabled={
|
||||
agentConversationBackgroundBusy || selectedAgentNeedsUserInput
|
||||
}
|
||||
value={agentConversationRunSubmitMode}
|
||||
onChange={(event) =>
|
||||
setAgentConversationRunSubmitMode(
|
||||
event.currentTarget.value as AgentBackgroundSubmitMode,
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="steer">追加当前 Run</option>
|
||||
<option value="queue">排队新任务</option>
|
||||
</select>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
disabled={
|
||||
agentConversationBackgroundBusy ||
|
||||
selectedAgentNeedsUserInput ||
|
||||
selectedAgentLlmWarning !== null
|
||||
}
|
||||
onClick={handleAgentBackgroundTaskSubmit}
|
||||
>
|
||||
{selectedAgentSteerRuntime
|
||||
? agentConversationRunSubmitMode === 'queue'
|
||||
? '排队任务'
|
||||
: '追加指令'
|
||||
: '后台运行'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={agentConversationSaving || selectedAgentNeedsUserInput}
|
||||
onClick={handleAgentPrivateMemorySubmit}
|
||||
>
|
||||
记入记忆
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,419 +0,0 @@
|
||||
import type { Dispatch, FormEventHandler, SetStateAction } from 'react';
|
||||
|
||||
import type { GameCreationAppLimitedRunCommandDescriptor } from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import type {
|
||||
InitLocalProjectResult,
|
||||
LocalPreviewResult,
|
||||
LocalProjectFileEntry,
|
||||
MemoryScope,
|
||||
UploadLocalAssetResult,
|
||||
} from '../../app/types';
|
||||
|
||||
type DeveloperProjectPanelsProps = {
|
||||
assetCanvasProjectId: string;
|
||||
assetGenerationPrompt: string;
|
||||
assetKind: string;
|
||||
assetLocalPath: string;
|
||||
assetMediaType: string;
|
||||
assetObjectId: string;
|
||||
assetResourceId: string;
|
||||
assetSourceKind: string;
|
||||
assetStatus: string;
|
||||
canvasExportPath: string;
|
||||
commandLog: string[];
|
||||
editorBaseUrl: string;
|
||||
fileDraft: string;
|
||||
filePath: string;
|
||||
fileStatus: string;
|
||||
handleAssetRegister: () => void;
|
||||
handleCanvasAssetGenerate: () => void;
|
||||
handleCanvasAssetImport: () => void;
|
||||
handleCanvasExportImport: () => void;
|
||||
handleCanvasProjectOpen: () => void;
|
||||
handleCanvasProjectSync: () => void;
|
||||
handleFileDelete: () => void;
|
||||
handleFileList: () => void | Promise<void>;
|
||||
handleFileRead: () => void | Promise<void>;
|
||||
handleFileWrite: () => void;
|
||||
handleLimitedCommandRun: (
|
||||
command: GameCreationAppLimitedRunCommandDescriptor,
|
||||
) => void;
|
||||
handleMemoryDelete: () => void;
|
||||
handleMemoryRead: () => void | Promise<void>;
|
||||
handleMemoryWrite: () => void;
|
||||
handlePreviewStart: () => void;
|
||||
handlePreviewStatus: () => void;
|
||||
handlePreviewStop: () => void;
|
||||
handleProjectInit: FormEventHandler<HTMLFormElement>;
|
||||
handleProjectLogRead: (path: string) => void | Promise<void>;
|
||||
limitedCommandStatus: string;
|
||||
limitedLocalCommands: GameCreationAppLimitedRunCommandDescriptor[];
|
||||
localProject: InitLocalProjectResult | null;
|
||||
memoryDraft: string;
|
||||
memoryScope: MemoryScope;
|
||||
memoryStatus: string;
|
||||
preview: LocalPreviewResult | null;
|
||||
previewStatus: string;
|
||||
projectFiles: LocalProjectFileEntry[];
|
||||
projectLogContent: string;
|
||||
projectLogStatus: string;
|
||||
projectPath: string;
|
||||
projectStatus: string;
|
||||
refreshLimitedLocalCommands: () => void | Promise<void>;
|
||||
setAssetCanvasProjectId: Dispatch<SetStateAction<string>>;
|
||||
setAssetGenerationPrompt: Dispatch<SetStateAction<string>>;
|
||||
setAssetKind: Dispatch<SetStateAction<string>>;
|
||||
setAssetLocalPath: Dispatch<SetStateAction<string>>;
|
||||
setAssetMediaType: Dispatch<SetStateAction<string>>;
|
||||
setAssetObjectId: Dispatch<SetStateAction<string>>;
|
||||
setAssetResourceId: Dispatch<SetStateAction<string>>;
|
||||
setAssetSourceKind: Dispatch<SetStateAction<string>>;
|
||||
setCanvasExportPath: Dispatch<SetStateAction<string>>;
|
||||
setEditorBaseUrl: Dispatch<SetStateAction<string>>;
|
||||
setFileDraft: Dispatch<SetStateAction<string>>;
|
||||
setFilePath: Dispatch<SetStateAction<string>>;
|
||||
setMemoryDraft: Dispatch<SetStateAction<string>>;
|
||||
setMemoryScope: Dispatch<SetStateAction<MemoryScope>>;
|
||||
setProjectPath: Dispatch<SetStateAction<string>>;
|
||||
uploadedAssets: UploadLocalAssetResult[];
|
||||
};
|
||||
|
||||
export function DeveloperProjectPanels({
|
||||
assetCanvasProjectId,
|
||||
assetGenerationPrompt,
|
||||
assetKind,
|
||||
assetLocalPath,
|
||||
assetMediaType,
|
||||
assetObjectId,
|
||||
assetResourceId,
|
||||
assetSourceKind,
|
||||
assetStatus,
|
||||
canvasExportPath,
|
||||
commandLog,
|
||||
editorBaseUrl,
|
||||
fileDraft,
|
||||
filePath,
|
||||
fileStatus,
|
||||
handleAssetRegister,
|
||||
handleCanvasAssetGenerate,
|
||||
handleCanvasAssetImport,
|
||||
handleCanvasExportImport,
|
||||
handleCanvasProjectOpen,
|
||||
handleCanvasProjectSync,
|
||||
handleFileDelete,
|
||||
handleFileList,
|
||||
handleFileRead,
|
||||
handleFileWrite,
|
||||
handleLimitedCommandRun,
|
||||
handleMemoryDelete,
|
||||
handleMemoryRead,
|
||||
handleMemoryWrite,
|
||||
handlePreviewStart,
|
||||
handlePreviewStatus,
|
||||
handlePreviewStop,
|
||||
handleProjectInit,
|
||||
handleProjectLogRead,
|
||||
limitedCommandStatus,
|
||||
limitedLocalCommands,
|
||||
localProject,
|
||||
memoryDraft,
|
||||
memoryScope,
|
||||
memoryStatus,
|
||||
preview,
|
||||
previewStatus,
|
||||
projectFiles,
|
||||
projectLogContent,
|
||||
projectLogStatus,
|
||||
projectPath,
|
||||
projectStatus,
|
||||
refreshLimitedLocalCommands,
|
||||
setAssetCanvasProjectId,
|
||||
setAssetGenerationPrompt,
|
||||
setAssetKind,
|
||||
setAssetLocalPath,
|
||||
setAssetMediaType,
|
||||
setAssetObjectId,
|
||||
setAssetResourceId,
|
||||
setAssetSourceKind,
|
||||
setCanvasExportPath,
|
||||
setEditorBaseUrl,
|
||||
setFileDraft,
|
||||
setFilePath,
|
||||
setMemoryDraft,
|
||||
setMemoryScope,
|
||||
setProjectPath,
|
||||
uploadedAssets,
|
||||
}: DeveloperProjectPanelsProps) {
|
||||
return (
|
||||
<>
|
||||
<section className="developer-panel asset-pane" aria-label="文件和资产">
|
||||
<h2>本地项目</h2>
|
||||
<form className="local-project-form" onSubmit={handleProjectInit}>
|
||||
<input
|
||||
aria-label="本地项目目录"
|
||||
value={projectPath}
|
||||
onChange={(event) => setProjectPath(event.currentTarget.value)}
|
||||
/>
|
||||
<button type="submit">初始化</button>
|
||||
</form>
|
||||
<p className="status-line">{projectStatus}</p>
|
||||
<p className="status-line">{assetStatus}</p>
|
||||
<div className="asset-register-form">
|
||||
<input
|
||||
aria-label="资产路径"
|
||||
value={assetLocalPath}
|
||||
onChange={(event) => setAssetLocalPath(event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
aria-label="资产类型"
|
||||
value={assetKind}
|
||||
onChange={(event) => setAssetKind(event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
aria-label="媒体类型"
|
||||
value={assetMediaType}
|
||||
onChange={(event) => setAssetMediaType(event.currentTarget.value)}
|
||||
/>
|
||||
<select
|
||||
aria-label="资产来源"
|
||||
value={assetSourceKind}
|
||||
onChange={(event) => setAssetSourceKind(event.currentTarget.value)}
|
||||
>
|
||||
<option value="generated">generated</option>
|
||||
<option value="uploaded">uploaded</option>
|
||||
<option value="canvas">canvas</option>
|
||||
</select>
|
||||
<input
|
||||
aria-label="画板项目"
|
||||
value={assetCanvasProjectId}
|
||||
onChange={(event) =>
|
||||
setAssetCanvasProjectId(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<input
|
||||
aria-label="资源 ID"
|
||||
value={assetResourceId}
|
||||
onChange={(event) => setAssetResourceId(event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
aria-label="资产对象 ID"
|
||||
value={assetObjectId}
|
||||
onChange={(event) => setAssetObjectId(event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
aria-label="编辑器地址"
|
||||
value={editorBaseUrl}
|
||||
onChange={(event) => setEditorBaseUrl(event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
aria-label="画板导出 ZIP"
|
||||
value={canvasExportPath}
|
||||
onChange={(event) => setCanvasExportPath(event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
aria-label="美术生成提示词"
|
||||
value={assetGenerationPrompt}
|
||||
onChange={(event) =>
|
||||
setAssetGenerationPrompt(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<button type="button" onClick={handleCanvasProjectOpen}>
|
||||
打开画板
|
||||
</button>
|
||||
<button type="button" onClick={handleCanvasProjectSync}>
|
||||
同步画板项目
|
||||
</button>
|
||||
<button type="button" onClick={handleAssetRegister}>
|
||||
登记资产
|
||||
</button>
|
||||
<button type="button" onClick={handleCanvasAssetGenerate}>
|
||||
生成美术资产
|
||||
</button>
|
||||
<button type="button" onClick={handleCanvasAssetImport}>
|
||||
导入画板资产
|
||||
</button>
|
||||
<button type="button" onClick={handleCanvasExportImport}>
|
||||
导入导出包
|
||||
</button>
|
||||
</div>
|
||||
<code>game/</code>
|
||||
<code>assets/</code>
|
||||
<code>memory/</code>
|
||||
<code>.agent/manifest.json</code>
|
||||
{uploadedAssets.map((asset) => (
|
||||
<code key={asset.id}>{asset.localPath}</code>
|
||||
))}
|
||||
{localProject ? (
|
||||
<p className="manifest-path">{localProject.manifestPath}</p>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="developer-panel developer-panel--wide file-pane"
|
||||
aria-label="项目文件"
|
||||
>
|
||||
<header className="panel-header">
|
||||
<h2>项目文件</h2>
|
||||
<div className="panel-actions">
|
||||
<button type="button" onClick={() => void handleFileList()}>
|
||||
列出
|
||||
</button>
|
||||
<button type="button" onClick={() => void handleFileRead()}>
|
||||
读取
|
||||
</button>
|
||||
<button type="button" onClick={handleFileWrite}>
|
||||
保存
|
||||
</button>
|
||||
<button type="button" onClick={handleFileDelete}>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<input
|
||||
aria-label="项目文件路径"
|
||||
value={filePath}
|
||||
onChange={(event) => setFilePath(event.currentTarget.value)}
|
||||
/>
|
||||
<textarea
|
||||
aria-label="项目文件内容"
|
||||
value={fileDraft}
|
||||
onChange={(event) => setFileDraft(event.currentTarget.value)}
|
||||
/>
|
||||
<p className="status-line">{fileStatus}</p>
|
||||
<div className="file-list">
|
||||
{projectFiles.map((file) => (
|
||||
<button
|
||||
key={file.path}
|
||||
type="button"
|
||||
onClick={() => setFilePath(file.path)}
|
||||
>
|
||||
{file.kind === 'directory' ? `${file.path}/` : file.path}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="developer-panel developer-panel--wide memory-pane"
|
||||
aria-label="记忆"
|
||||
>
|
||||
<header className="panel-header">
|
||||
<h2>记忆</h2>
|
||||
<div className="panel-actions">
|
||||
<select
|
||||
aria-label="记忆范围"
|
||||
value={memoryScope}
|
||||
onChange={(event) =>
|
||||
setMemoryScope(event.currentTarget.value as MemoryScope)
|
||||
}
|
||||
>
|
||||
<option value="long">长期</option>
|
||||
<option value="short">短期</option>
|
||||
<option value="blackboard">黑板</option>
|
||||
</select>
|
||||
<button type="button" onClick={() => void handleMemoryRead()}>
|
||||
读取
|
||||
</button>
|
||||
<button type="button" onClick={handleMemoryWrite}>
|
||||
保存
|
||||
</button>
|
||||
<button type="button" onClick={handleMemoryDelete}>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<textarea
|
||||
aria-label="记忆内容"
|
||||
value={memoryDraft}
|
||||
onChange={(event) => setMemoryDraft(event.currentTarget.value)}
|
||||
/>
|
||||
<p className="status-line">{memoryStatus}</p>
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="developer-panel developer-panel--wide preview-pane"
|
||||
aria-label="预览"
|
||||
>
|
||||
<header className="panel-header">
|
||||
<h2>预览</h2>
|
||||
<div className="panel-actions">
|
||||
<button type="button" onClick={handlePreviewStatus}>
|
||||
状态
|
||||
</button>
|
||||
<button type="button" onClick={handlePreviewStart}>
|
||||
启动
|
||||
</button>
|
||||
<button type="button" onClick={handlePreviewStop}>
|
||||
停止
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<p className="status-line">{previewStatus}</p>
|
||||
{preview ? (
|
||||
<iframe
|
||||
className="preview-frame"
|
||||
src={preview.url}
|
||||
title="本地游戏预览"
|
||||
/>
|
||||
) : (
|
||||
<div className="preview-frame">127.0.0.1:<port></div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="developer-panel developer-panel--wide log-pane"
|
||||
aria-label="日志"
|
||||
>
|
||||
<header className="panel-header">
|
||||
<h2>日志</h2>
|
||||
<div className="panel-actions">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
void handleProjectLogRead('.agent/logs/command.log')
|
||||
}
|
||||
>
|
||||
命令日志
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
void handleProjectLogRead('.agent/logs/preview.log')
|
||||
}
|
||||
>
|
||||
预览日志
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleProjectLogRead('.agent/logs/agent.log')}
|
||||
>
|
||||
Agent日志
|
||||
</button>
|
||||
<button type="button" onClick={refreshLimitedLocalCommands}>
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div className="limited-command-list">
|
||||
{limitedLocalCommands.map((command) => (
|
||||
<button
|
||||
key={command.id}
|
||||
type="button"
|
||||
onClick={() => handleLimitedCommandRun(command)}
|
||||
>
|
||||
{command.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p className="status-line">{limitedCommandStatus}</p>
|
||||
<p className="status-line">{projectLogStatus}</p>
|
||||
{projectLogContent ? (
|
||||
<pre className="project-log-content">{projectLogContent}</pre>
|
||||
) : null}
|
||||
{commandLog.map((entry, index) => (
|
||||
<p key={`${entry}-${index}`}>{entry}</p>
|
||||
))}
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
import type { UIEventHandler } from 'react';
|
||||
|
||||
import {
|
||||
GAME_CREATION_AGENT_CAPABILITIES,
|
||||
type GameCreationAgentRunTrace,
|
||||
type GameCreationAppManifest,
|
||||
} from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import type { AgentRunHistoryItem } from '../../app/types';
|
||||
import { taskRowsFromManifest } from '../agent-runtime';
|
||||
import { formatTraceTaskWaves } from '../project-summary/agentPresentation';
|
||||
import {
|
||||
formatAgentRunStatus,
|
||||
formatTraceRepairRoutes,
|
||||
formatTraceTaskId,
|
||||
formatTraceTaskIds,
|
||||
isSafeProjectRelativePath,
|
||||
taskGroupLabels,
|
||||
} from '../project-summary/projectSummary';
|
||||
|
||||
type DeveloperRuntimePanelsProps = {
|
||||
agentRunHistory: AgentRunHistoryItem[];
|
||||
agentRunHistoryLoadingMore: boolean;
|
||||
agentRunHistoryOmittedCount: number;
|
||||
agentRunStatus: string;
|
||||
agentRunTrace: GameCreationAgentRunTrace | null;
|
||||
canShowMoreAgentRunHistory: boolean;
|
||||
handleAgentRunHistoryScroll: UIEventHandler<HTMLDivElement>;
|
||||
handleAgentRunTraceFileOpen: (path: string) => void | Promise<void>;
|
||||
handleAgentRunTraceRefresh: () => void | Promise<void>;
|
||||
manifest: GameCreationAppManifest;
|
||||
prepareChatCommandDraft: (value: string) => void;
|
||||
refreshManifest: () => void | Promise<void>;
|
||||
showMoreAgentRunHistory: () => void | Promise<void>;
|
||||
visibleAgentRunHistory: AgentRunHistoryItem[];
|
||||
};
|
||||
|
||||
export function DeveloperRuntimePanels({
|
||||
agentRunHistory,
|
||||
agentRunHistoryLoadingMore,
|
||||
agentRunHistoryOmittedCount,
|
||||
agentRunStatus,
|
||||
agentRunTrace,
|
||||
canShowMoreAgentRunHistory,
|
||||
handleAgentRunHistoryScroll,
|
||||
handleAgentRunTraceFileOpen,
|
||||
handleAgentRunTraceRefresh,
|
||||
manifest,
|
||||
prepareChatCommandDraft,
|
||||
refreshManifest,
|
||||
showMoreAgentRunHistory,
|
||||
visibleAgentRunHistory,
|
||||
}: DeveloperRuntimePanelsProps) {
|
||||
return (
|
||||
<>
|
||||
<section className="developer-panel task-pane" aria-label="任务">
|
||||
<header className="panel-header">
|
||||
<h2>专业组</h2>
|
||||
<button type="button" onClick={() => refreshManifest()}>
|
||||
刷新
|
||||
</button>
|
||||
</header>
|
||||
{taskRowsFromManifest(manifest).map((task) => (
|
||||
<article key={task.id}>
|
||||
<strong>{taskGroupLabels[task.group]}</strong>
|
||||
<span>{`${task.title} · ${task.status}`}</span>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section className="developer-panel capability-pane" aria-label="能力">
|
||||
<h2>Agent 能力</h2>
|
||||
{GAME_CREATION_AGENT_CAPABILITIES.map((capability) => (
|
||||
<article key={capability.id}>
|
||||
<strong>{capability.title}</strong>
|
||||
<span>{capability.area}</span>
|
||||
</article>
|
||||
))}
|
||||
</section>
|
||||
|
||||
<section
|
||||
className="developer-panel trace-pane"
|
||||
aria-label="Agent run trace"
|
||||
>
|
||||
<header className="panel-header">
|
||||
<h2>编排 Trace</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleAgentRunTraceRefresh()}
|
||||
>
|
||||
刷新
|
||||
</button>
|
||||
</header>
|
||||
<p className="status-line">{agentRunStatus}</p>
|
||||
{agentRunTrace ? (
|
||||
<>
|
||||
<p className="manifest-path">{agentRunTrace.runId}</p>
|
||||
<p className="status-line">
|
||||
{`loop: ${agentRunTrace.passes}/${agentRunTrace.maxPasses} · ${agentRunTrace.stopReason} · next: ${agentRunTrace.nextStep}`}
|
||||
</p>
|
||||
<p className="status-line">
|
||||
{`工具调用:${agentRunTrace.toolCallCount}/${agentRunTrace.maxToolCalls}`}
|
||||
</p>
|
||||
{agentRunTrace.error ? (
|
||||
<p className="trace-error">{agentRunTrace.error}</p>
|
||||
) : null}
|
||||
{agentRunHistory.length > 0 ? (
|
||||
<div
|
||||
className="trace-history"
|
||||
aria-label="Agent run history"
|
||||
onScroll={handleAgentRunHistoryScroll}
|
||||
>
|
||||
{visibleAgentRunHistory.map((runFile) => (
|
||||
<button
|
||||
key={runFile.path}
|
||||
aria-current={
|
||||
agentRunTrace?.runId === runFile.trace.runId
|
||||
? 'true'
|
||||
: undefined
|
||||
}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
void handleAgentRunTraceFileOpen(runFile.path)
|
||||
}
|
||||
>
|
||||
{`${runFile.trace.runId} · ${formatAgentRunStatus(
|
||||
runFile.trace,
|
||||
)} · updated: ${runFile.trace.updatedAt} · ${
|
||||
runFile.path
|
||||
} · ${runFile.size}B`}
|
||||
</button>
|
||||
))}
|
||||
{agentRunHistoryOmittedCount > 0 ? (
|
||||
canShowMoreAgentRunHistory ? (
|
||||
<button
|
||||
type="button"
|
||||
disabled={agentRunHistoryLoadingMore}
|
||||
onClick={showMoreAgentRunHistory}
|
||||
>
|
||||
{agentRunHistoryLoadingMore
|
||||
? '加载中'
|
||||
: `显示更多 · 还有 ${agentRunHistoryOmittedCount} 个历史 run`}
|
||||
</button>
|
||||
) : (
|
||||
<small>{`还有 ${agentRunHistoryOmittedCount} 个历史 run`}</small>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="trace-artifacts" aria-label="Agent artifacts">
|
||||
{agentRunTrace.artifacts.slice(0, 8).map((artifact) => (
|
||||
<small key={artifact.path}>
|
||||
{`${artifact.path} · ${artifact.sizeBytes}B · ${artifact.checksum}`}
|
||||
{isSafeProjectRelativePath(artifact.path) ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`填入读取产物 ${artifact.path}`}
|
||||
onClick={() =>
|
||||
prepareChatCommandDraft(`/read ${artifact.path}`)
|
||||
}
|
||||
>
|
||||
读取命令
|
||||
</button>
|
||||
) : null}
|
||||
</small>
|
||||
))}
|
||||
{agentRunTrace.artifacts.length > 8 ? (
|
||||
<small>{`还有 ${agentRunTrace.artifacts.length - 8} 个产物`}</small>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="trace-task-graph" aria-label="Agent task graph">
|
||||
<small>
|
||||
{`active: ${formatTraceTaskIds(
|
||||
agentRunTrace.taskGraph.activeTaskIds,
|
||||
agentRunTrace.taskGraph.tasks,
|
||||
)}`}
|
||||
</small>
|
||||
<small>
|
||||
{`carry-over: ${formatTraceTaskIds(
|
||||
agentRunTrace.taskGraph.carriedTaskIds,
|
||||
agentRunTrace.taskGraph.tasks,
|
||||
)}`}
|
||||
</small>
|
||||
<small>
|
||||
{`ready: ${formatTraceTaskIds(
|
||||
agentRunTrace.taskGraph.readyTaskIds,
|
||||
agentRunTrace.taskGraph.tasks,
|
||||
)}`}
|
||||
</small>
|
||||
{agentRunTrace.taskGraph.repairFocus.length > 0 ? (
|
||||
<small>
|
||||
{`repair: ${agentRunTrace.taskGraph.repairFocus.join(' / ')}`}
|
||||
</small>
|
||||
) : null}
|
||||
{agentRunTrace.taskGraph.repairRoutes.map((route) => (
|
||||
<small key={`${route.reason}-${route.issue}`}>
|
||||
{`route: ${formatTraceTaskIds(
|
||||
route.taskIds,
|
||||
agentRunTrace.taskGraph.tasks,
|
||||
)} · ${route.reason}`}
|
||||
</small>
|
||||
))}
|
||||
</div>
|
||||
{agentRunTrace.passPlans.length > 0 ? (
|
||||
<div className="trace-task-graph" aria-label="Agent pass plans">
|
||||
{agentRunTrace.passPlans.map((plan) => (
|
||||
<small key={`pass-plan-${plan.pass}`}>
|
||||
{[
|
||||
`pass ${plan.pass}: ${plan.mode}`,
|
||||
`active ${formatTraceTaskIds(
|
||||
plan.activeTaskIds,
|
||||
agentRunTrace.taskGraph.tasks,
|
||||
)}`,
|
||||
`carry ${formatTraceTaskIds(
|
||||
plan.carriedTaskIds,
|
||||
agentRunTrace.taskGraph.tasks,
|
||||
)}`,
|
||||
`waves ${formatTraceTaskWaves(
|
||||
plan.dependencyWaves,
|
||||
agentRunTrace.taskGraph.tasks,
|
||||
)}`,
|
||||
plan.repairFocus.length > 0
|
||||
? `repair ${plan.repairFocus.join(';')}`
|
||||
: null,
|
||||
plan.repairRoutes.length > 0
|
||||
? `routes ${formatTraceRepairRoutes(
|
||||
plan.repairRoutes,
|
||||
agentRunTrace.taskGraph.tasks,
|
||||
)}`
|
||||
: null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ')}
|
||||
</small>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{agentRunTrace.steps.map((step, index) => (
|
||||
<article key={`${step.agent}-${step.pass}-${index}`}>
|
||||
<strong>{`${step.agent} #${step.pass}`}</strong>
|
||||
<span>{`${step.status} · ${step.summary}`}</span>
|
||||
<small>
|
||||
{[
|
||||
step.phase,
|
||||
step.taskId
|
||||
? formatTraceTaskId(
|
||||
step.taskId,
|
||||
agentRunTrace.taskGraph.tasks,
|
||||
)
|
||||
: step.group && step.role
|
||||
? `${taskGroupLabels[step.group]} / ${step.role}`
|
||||
: null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' · ')}
|
||||
</small>
|
||||
<small>
|
||||
{step.toolCalls
|
||||
.map((toolCall) => `${toolCall.toolId}:${toolCall.status}`)
|
||||
.join(', ')}
|
||||
</small>
|
||||
<small>{`in: ${step.inputPaths.join(', ') || 'none'}`}</small>
|
||||
<small>{`out: ${step.outputPaths.join(', ') || 'none'}`}</small>
|
||||
</article>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
{!agentRunTrace && agentRunHistory.length > 0 ? (
|
||||
<div
|
||||
className="trace-history"
|
||||
aria-label="Agent run history"
|
||||
onScroll={handleAgentRunHistoryScroll}
|
||||
>
|
||||
{visibleAgentRunHistory.map((runFile) => (
|
||||
<button
|
||||
key={runFile.path}
|
||||
type="button"
|
||||
onClick={() => void handleAgentRunTraceFileOpen(runFile.path)}
|
||||
>
|
||||
{`${runFile.trace.runId} · ${formatAgentRunStatus(
|
||||
runFile.trace,
|
||||
)} · updated: ${runFile.trace.updatedAt} · ${
|
||||
runFile.path
|
||||
} · ${runFile.size}B`}
|
||||
</button>
|
||||
))}
|
||||
{agentRunHistoryOmittedCount > 0 ? (
|
||||
canShowMoreAgentRunHistory ? (
|
||||
<button
|
||||
type="button"
|
||||
disabled={agentRunHistoryLoadingMore}
|
||||
onClick={showMoreAgentRunHistory}
|
||||
>
|
||||
{agentRunHistoryLoadingMore
|
||||
? '加载中'
|
||||
: `显示更多 · 还有 ${agentRunHistoryOmittedCount} 个历史 run`}
|
||||
</button>
|
||||
) : (
|
||||
<small>{`还有 ${agentRunHistoryOmittedCount} 个历史 run`}</small>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user