删除 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:
2026-09-19 21:41:37 +08:00
parent 99be1d76fe
commit 876529e668
117 changed files with 1772 additions and 36585 deletions
@@ -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(())
}
}
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.')),
@@ -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:&lt;port&gt;</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