新增总控Agent纯聊天开发窗口

新增开发态独立窗口路由与专业Agent开发页入口
固定复用project-supervisor活动会话、Runtime和持久对话
提供纯消息区、流式等待状态、输入框与运行时设置
补齐窗口编码、前端交互、开发态门禁和原生壳验证
同步AI游戏创作实施计划与项目决策记录
This commit is contained in:
AIGameCreator App
2026-07-20 17:25:58 +08:00
parent a3337f4493
commit 36734e158e
11 changed files with 607 additions and 11 deletions
@@ -646,6 +646,10 @@ for (const snippet of [
'open_developer_window(app.handle())?',
'tauri::WebviewWindowBuilder::new(app, "developer", developer_window_url())',
'index.html?agent-chat',
'supervisorChatMode',
'supervisorChatOnly',
'open_project_supervisor_chat_window',
'index.html?supervisor-chat&projectPath=',
]) {
if (!`${appEntrypointSource}\n${tauriRustSource}`.includes(snippet)) {
throw new Error(
@@ -1749,6 +1749,7 @@ 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,
open_local_game_preview,
stop_local_game_preview,
@@ -48860,6 +48860,14 @@ fn developer_window_uses_agent_chat_route() {
assert_eq!(developer_window_url().to_string(), "index.html?agent-chat");
}
#[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"
);
}
#[test]
fn workspace_window_url_carries_encoded_project_path() {
assert_eq!(
@@ -15,6 +15,13 @@ pub(crate) fn developer_window_url() -> tauri::WebviewUrl {
tauri::WebviewUrl::App(PathBuf::from("index.html?agent-chat"))
}
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() {
@@ -81,6 +88,37 @@ pub(crate) fn open_game_creator_launcher_window(
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") {
existing.close().map_err(|error| error.to_string())?;
}
tauri::WebviewWindowBuilder::new(
&app,
"supervisor-chat",
supervisor_chat_window_url(project_path),
)
.title("项目总控 Agent 对话")
.inner_size(820.0, 720.0)
.min_inner_size(560.0, 480.0)
.build()
.map_err(|error| error.to_string())?;
Ok(())
}
}
#[cfg(all(debug_assertions, not(test)))]
pub(crate) fn open_developer_window(app: &tauri::AppHandle) -> Result<(), String> {
if let Some(existing) = app.get_webview_window("developer") {
+210 -1
View File
@@ -1642,6 +1642,22 @@ function agentRuntimeConversationStatus(runtime: AgentRuntimeState) {
return waitingOn ? `Agent 正在运行,等待${waitingOn}` : 'Agent 正在运行';
}
function projectSupervisorChatRuntimeStatus(runtime: AgentRuntimeState) {
if (runtime.status === 'idle' || runtime.phase === 'idle') {
return '等待输入';
}
if (isAgentRuntimeTerminalState(runtime)) {
if (runtime.status === 'failed' || runtime.phase === 'failed') {
return runtime.error || 'Agent 运行失败';
}
if (runtime.status === 'cancelled' || runtime.phase === 'cancelled') {
return '本轮已取消';
}
return '本轮已完成';
}
return agentRuntimeConversationStatus(runtime);
}
function formatAgentRuntimeEvent(event: AgentRuntimeEventRecord) {
const summary = event.summary || event.detail || event.runId;
const detail =
@@ -6561,6 +6577,32 @@ export function WorkspaceLauncher({
agentChatGoalDialog !== null && !agentChatBackgroundBusy,
);
async function handleOpenProjectSupervisorChatWindow() {
if (agentChatBusy || agentChatBackgroundBusy) {
return;
}
const projectPathForChat = validateAgentChatProjectPath();
if (!projectPathForChat) {
return;
}
const invoke = resolveTauriInvoke();
if (!invoke) {
setAgentChatStatus('需要在 Tauri App 内运行');
return;
}
setAgentChatStatus('正在打开项目总控对话');
try {
await invoke('open_project_supervisor_chat_window', {
projectPath: projectPathForChat,
});
setAgentChatStatus('已打开项目总控对话');
} catch (error) {
setAgentChatStatus(
error instanceof Error ? error.message : String(error),
);
}
}
async function handleAgentChatPickProjectDirectory() {
if (agentChatBusy || agentChatBackgroundBusy) {
return;
@@ -8896,6 +8938,13 @@ export function WorkspaceLauncher({
<p>{agentChatStatus}</p>
</div>
<div className="launcher-project-list-actions">
<button
type="button"
disabled={agentChatBusy || agentChatBackgroundBusy}
onClick={() => void handleOpenProjectSupervisorChatWindow()}
>
</button>
<button
type="button"
disabled={agentChatBusy || agentChatBackgroundBusy}
@@ -16974,12 +17023,14 @@ type AppProps = {
initialProjectPath?: string;
initialProjectManifest?: GameCreationAppManifest;
projectSupervisorOnly?: boolean;
supervisorChatOnly?: boolean;
};
export function App({
initialProjectPath: initialProjectPathOverride = '',
initialProjectManifest,
projectSupervisorOnly = false,
supervisorChatOnly = false,
}: AppProps = {}) {
const [devMode] = useState(() =>
projectSupervisorOnly ? false : isDeveloperMode(),
@@ -17022,6 +17073,8 @@ export function App({
const [projectSupervisorRuntimeError, setProjectSupervisorRuntimeError] =
useState('');
const chatInputRef = useRef<HTMLInputElement | null>(null);
const supervisorChatMessagesRef = useRef<HTMLDivElement | null>(null);
const supervisorChatShouldFollowLatestRef = useRef(true);
const [assetStatus, setAssetStatus] = useState('未上传');
const [uploadedAssets, setUploadedAssets] = useState<
UploadLocalAssetResult[]
@@ -17178,7 +17231,7 @@ export function App({
const pendingUiConfirmationActionRef = useRef<(() => void) | null>(null);
function requestRuntimeConfigOpen() {
if (projectSupervisorOnly) {
if (projectSupervisorOnly && !supervisorChatOnly) {
return;
}
setRuntimeConfigOpen(true);
@@ -17543,6 +17596,22 @@ export function App({
projectSupervisorRuntime?.status,
]);
useLayoutEffect(() => {
if (!supervisorChatOnly || !supervisorChatShouldFollowLatestRef.current) {
return;
}
const messageList = supervisorChatMessagesRef.current;
if (messageList) {
messageList.scrollTop = messageList.scrollHeight;
}
}, [
messages,
projectSupervisorResponseStream?.sequence,
projectSupervisorRuntime?.updatedAt,
projectSupervisorRuntimeError,
supervisorChatOnly,
]);
useEffect(() => {
latestMessagesRef.current = messages;
const invoke = resolveTauriInvoke();
@@ -26599,6 +26668,17 @@ export function App({
}
}
function handleSupervisorChatScroll(event: UIEvent<HTMLDivElement>) {
handleConversationScroll(event);
const messageList = event.currentTarget;
const distanceFromBottom =
messageList.scrollHeight -
messageList.scrollTop -
messageList.clientHeight;
supervisorChatShouldFollowLatestRef.current =
distanceFromBottom <= AGENT_CHAT_SCROLL_BOTTOM_THRESHOLD;
}
function handleAgentConversationScroll(event: UIEvent<HTMLElement>) {
if (hiddenAgentConversationCount === 0) {
return;
@@ -26636,6 +26716,9 @@ export function App({
if (!prompt || chatAgentBusy) {
return;
}
if (supervisorChatOnly) {
supervisorChatShouldFollowLatestRef.current = true;
}
setChatInput('');
setMessages((current) => [
...current,
@@ -26650,6 +26733,132 @@ export function App({
(agent.runtimeStatus !== null || agent.hasRecentEvidence),
);
if (projectSupervisorOnly && supervisorChatOnly) {
const projectSupervisorChatRunning = Boolean(
chatAgentBusy ||
(projectSupervisorRuntime &&
!isAgentRuntimeTerminalState(projectSupervisorRuntime)),
);
const projectSupervisorChatStatus = projectSupervisorRuntimeError
? projectSupervisorRuntimeError
: projectSupervisorRuntime
? projectSupervisorChatRuntimeStatus(projectSupervisorRuntime)
: workspaceStatus;
const supervisorProjectPath =
localProject?.projectPath || initialProjectPath || projectPath;
return (
<>
<main
className="supervisor-chat-only-shell"
aria-label="项目总控 Agent 纯聊天"
>
<header className="supervisor-chat-only-header">
<div>
<strong> Agent</strong>
<span>{projectNameFromPath(supervisorProjectPath)}</span>
</div>
<div className="supervisor-chat-only-header-status">
<span aria-live="polite">{projectSupervisorChatStatus}</span>
<button
type="button"
aria-label="设置"
title="设置"
onClick={() => setRuntimeConfigOpen(true)}
>
<Settings size={17} aria-hidden="true" />
</button>
</div>
</header>
<div
ref={supervisorChatMessagesRef}
className="message-list supervisor-chat-only-message-list"
aria-label="项目总控消息"
onScroll={handleSupervisorChatScroll}
>
{hiddenConversationCount > 0 ? (
<button
type="button"
className="message-history-more"
onClick={showEarlierConversationMessages}
>
{`显示更早 · 还有 ${hiddenConversationCount} 条对话`}
</button>
) : null}
{visibleMessages.map((message, index) => (
<p
key={message.messageId ?? `${message.role}-${index}`}
className={`message message--${message.role}`}
>
{message.text}
</p>
))}
{projectSupervisorTransientReply ? (
<p
className="message message--assistant supervisor-chat-only-stream"
aria-label="项目总控 Agent 实时回复"
aria-live="polite"
data-runtime-owned="true"
>
{projectSupervisorTransientReply}
</p>
) : null}
{projectSupervisorChatRunning &&
!projectSupervisorTransientReply ? (
<div
className="launcher-agent-chat-waiting supervisor-chat-only-waiting"
role="status"
aria-live="polite"
>
<span aria-hidden="true" />
<div>
<strong>{projectSupervisorChatStatus}</strong>
<small> Agent </small>
</div>
</div>
) : null}
</div>
<form
className="supervisor-chat-only-composer"
onSubmit={handleProjectSupervisorOnlySubmit}
>
<textarea
aria-label="项目总控对话内容"
disabled={chatAgentBusy || projectSupervisorNeedsUserInput}
rows={3}
value={chatInput}
placeholder="给项目总控 Agent 发消息"
onChange={(event) => setChatInput(event.currentTarget.value)}
onKeyDown={(event) => {
if (
event.key === 'Enter' &&
!event.shiftKey &&
!event.nativeEvent.isComposing
) {
event.preventDefault();
event.currentTarget.form?.requestSubmit();
}
}}
/>
<button
type="submit"
aria-label="发送"
title="发送"
disabled={chatAgentBusy || projectSupervisorNeedsUserInput}
>
<Send size={18} aria-hidden="true" />
</button>
</form>
</main>
{runtimeConfigOpen ? (
<RuntimeConfigDialog
projectPath={supervisorProjectPath}
onClose={() => setRuntimeConfigOpen(false)}
/>
) : null}
</>
);
}
if (projectSupervisorOnly) {
return (
<>
+23 -10
View File
@@ -1,12 +1,17 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { AuthenticatedClient, WorkspaceLauncher } from './App';
import { App, AuthenticatedClient, WorkspaceLauncher } from './App';
import './styles.css';
const initialSearchParams = new URLSearchParams(window.location.search);
const supervisorChatMode =
import.meta.env.DEV && initialSearchParams.has('supervisor-chat');
const supervisorChatProjectPath =
initialSearchParams.get('projectPath')?.trim() ?? '';
function resolveInitialLauncherView() {
return import.meta.env.DEV &&
new URLSearchParams(window.location.search).has('agent-chat')
return import.meta.env.DEV && initialSearchParams.has('agent-chat')
? 'agent-chat'
: 'home';
}
@@ -16,13 +21,21 @@ const initialLauncherView = resolveInitialLauncherView();
createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<AuthenticatedClient>
{({ user, logout }) => (
<WorkspaceLauncher
currentUser={user}
initialView={initialLauncherView}
onLogout={logout}
/>
)}
{({ user, logout }) =>
supervisorChatMode ? (
<App
initialProjectPath={supervisorChatProjectPath}
projectSupervisorOnly
supervisorChatOnly
/>
) : (
<WorkspaceLauncher
currentUser={user}
initialView={initialLauncherView}
onLogout={logout}
/>
)
}
</AuthenticatedClient>
</React.StrictMode>,
);
+182
View File
@@ -1480,6 +1480,188 @@ textarea {
font-size: 12px;
}
.supervisor-chat-only-shell {
display: grid;
grid-template-rows: 64px minmax(0, 1fr) auto;
width: 100%;
height: 100vh;
min-width: 0;
min-height: 480px;
overflow: hidden;
background: #f5f6f8;
color: #111827;
}
.supervisor-chat-only-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
min-width: 0;
padding: 0 18px;
border-bottom: 1px solid #e1e5eb;
background: #fff;
}
.supervisor-chat-only-header > div,
.supervisor-chat-only-header-status {
display: flex;
align-items: center;
min-width: 0;
}
.supervisor-chat-only-header > div:first-child {
gap: 10px;
}
.supervisor-chat-only-header strong {
flex: 0 0 auto;
font-size: 15px;
}
.supervisor-chat-only-header span {
min-width: 0;
overflow: hidden;
color: #6b7280;
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.supervisor-chat-only-header-status {
justify-content: flex-end;
gap: 10px;
max-width: 52%;
}
.supervisor-chat-only-header-status button {
display: grid;
width: 34px;
height: 34px;
flex: 0 0 auto;
padding: 0;
border: 1px solid #d8dde5;
border-radius: 6px;
background: #fff;
color: #374151;
place-items: center;
}
.supervisor-chat-only-message-list {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 0;
min-height: 0;
padding: 22px clamp(16px, 4vw, 40px);
border: 0;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
}
.supervisor-chat-only-message-list .message {
align-self: flex-start;
width: fit-content;
max-width: min(720px, 86%);
margin: 0;
padding: 10px 12px;
border: 1px solid #dfe3e9;
border-radius: 8px;
background: #fff;
color: #263142;
line-height: 1.55;
}
.supervisor-chat-only-message-list .message--user {
align-self: flex-end;
border-color: #cfd6df;
background: #e9edf2;
color: #111827;
}
.supervisor-chat-only-message-list .supervisor-chat-only-stream {
border-color: #b9d8c5;
}
.supervisor-chat-only-waiting {
position: static;
align-self: flex-start;
}
.supervisor-chat-only-composer {
display: grid;
grid-template-columns: minmax(0, 1fr) 42px;
align-items: end;
gap: 10px;
padding: 14px clamp(16px, 4vw, 40px) 18px;
border-top: 1px solid #e1e5eb;
background: #fff;
}
.supervisor-chat-only-composer textarea {
width: 100%;
min-width: 0;
height: 74px;
max-height: 74px;
padding: 11px 12px;
overflow-y: auto;
resize: none;
border: 1px solid #cfd6df;
border-radius: 8px;
background: #fff;
color: #111827;
font: inherit;
line-height: 1.5;
}
.supervisor-chat-only-composer textarea:focus-visible {
border-color: #6b7280;
outline: 2px solid rgb(107 114 128 / 18%);
outline-offset: 1px;
}
.supervisor-chat-only-composer button {
display: grid;
width: 42px;
height: 42px;
padding: 0;
border: 0;
border-radius: 8px;
background: #111827;
color: #fff;
place-items: center;
}
.supervisor-chat-only-composer button:disabled,
.supervisor-chat-only-composer textarea:disabled {
cursor: not-allowed;
opacity: 0.55;
}
@media (max-width: 600px) {
.supervisor-chat-only-header {
padding: 0 12px;
}
.supervisor-chat-only-header > div:first-child > span {
display: none;
}
.supervisor-chat-only-header-status {
max-width: 58%;
}
.supervisor-chat-only-message-list .message {
max-width: 92%;
}
.supervisor-chat-only-composer {
padding-right: 12px;
padding-left: 12px;
}
}
.project-supervisor-surface {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(220px, 280px);
@@ -1623,6 +1623,45 @@ describe('AI 游戏创作 App 界面边界', () => {
);
});
it('opens the standalone Project Supervisor chat window with the absolute project path', async () => {
const projectPath = '/tmp/supervisor-chat-window-game';
const invoke = vi.fn(async (command: string) => {
if (command === 'check_game_creator_llm_config') {
return {
configured: true,
apiKeyPresent: true,
baseUrl: 'https://llm.example.test/v1',
model: 'gpt-test',
apiKind: 'openai_responses',
reasoningEffort: 'high',
stream: true,
webSearchEnabled: false,
error: null,
agents: [],
};
}
if (command === 'open_project_supervisor_chat_window') {
return null;
}
throw new Error(`unexpected invoke ${command}`);
});
window.__TAURI__ = { core: { invoke } };
renderLauncherAgentChatAt('/?agent-chat');
fireEvent.change(screen.getByLabelText('Agent 聊天项目目录'), {
target: { value: projectPath },
});
fireEvent.click(screen.getByRole('button', { name: '总控对话' }));
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith(
'open_project_supervisor_chat_window',
{ projectPath },
);
});
expect(screen.getByText('已打开项目总控对话')).not.toBeNull();
});
it('persists and shows an upstream stream error without issuing a normal retry', async () => {
const persistedMessages: Array<{
role: 'user' | 'assistant' | 'tool';
@@ -6065,6 +6104,81 @@ describe('AI 游戏创作 App 界面边界', () => {
);
});
it('loads and continues the active Project Supervisor Session in the standalone chat surface', async () => {
const projectPath = '/tmp/supervisor-chat-only-game';
const historyMessage = '已持久化的项目总控历史';
const harness = createProjectSupervisorRuntimeHarness({
projectPath,
supervisorMessages: [
{
schemaVersion: 'game-creator-conversation.v1',
role: 'assistant',
content: historyMessage,
agentId: 'project-supervisor',
messageId: 'supervisor-chat-only-history',
updatedAt: 2000,
},
],
});
window.__TAURI__ = {
core: { invoke: harness.invoke },
event: { listen: harness.listen },
};
render(
React.createElement(App, {
initialProjectPath: projectPath,
projectSupervisorOnly: true,
supervisorChatOnly: true,
}),
);
const surface = await screen.findByLabelText('项目总控 Agent 纯聊天');
const messageList = within(surface).getByLabelText('项目总控消息');
expect(await within(messageList).findByText(historyMessage)).not.toBeNull();
expect(harness.invoke).toHaveBeenCalledWith(
'list_game_creator_agent_sessions',
{
projectPath,
agentId: 'project-supervisor',
},
);
expect(harness.invoke).toHaveBeenCalledWith('read_local_conversation', {
projectPath,
agentId: 'project-supervisor',
sessionId: harness.sessionId,
});
expect(within(surface).getByRole('button', { name: '设置' })).not.toBeNull();
expect(screen.queryByLabelText('选择 Agent')).toBeNull();
expect(screen.queryByLabelText('项目总控 Agent 状态')).toBeNull();
expect(screen.queryByLabelText('专业 Agent 协作状态')).toBeNull();
fireEvent.change(within(surface).getByLabelText('项目总控对话内容'), {
target: { value: '继续完成可玩原型' },
});
fireEvent.click(within(surface).getByRole('button', { name: '发送' }));
await waitFor(() => {
expect(harness.invoke).toHaveBeenCalledWith(
'start_game_creator_agent_runtime_task',
{
projectPath,
agentId: 'project-supervisor',
sessionId: harness.sessionId,
task: '继续完成可玩原型',
runId: expect.stringMatching(/^project-supervisor-task-/),
},
);
});
expect(harness.invoke).not.toHaveBeenCalledWith(
'create_game_creator_agent_session',
expect.anything(),
);
expect(harness.invoke).not.toHaveBeenCalledWith(
'chat_with_game_creator_agent',
expect.anything(),
);
});
it('opens an existing project into the active Supervisor Session, restores history, then starts and steers the same run', async () => {
const projectPath = '/tmp/launcher-supervisor-game';
const manifest = createGameCreationAppManifest(
@@ -5118,3 +5118,11 @@
- 决策:旧创作模板、旧创作入口及其专属运行服务进入下线范围,不再为跳一跳、抓大鹅 Match3D、儿童动作 Demo 等旧链路修复兼容问题、补生成脚本或维持专属门禁。
- 边界:共享账号、钱包、资产、图片编辑器、公开作品、通用 HostBridge、API、SpacetimeDB、发布运维和安全能力不属于旧链路,仍需维持正式门禁。历史文档只作为背景材料,不再作为旧入口继续运行的依据。
- 清理方式:允许直接删除已经失效的旧素材生成命令、入口路由、专属服务和对应测试;删除工程链路时仍需核对是否被当前共享能力引用,不能连带移除仍在使用的公共契约或持久化事实。
## 2026-07-20 开发态 Project Supervisor 纯聊天独立窗口
- 背景:开发人员需要一个不依赖正式产品布局的最小 GUI,用于直接验证 `project-supervisor` 的持久多轮对话与 Runtime 行为。
- 入口决策:仅 debug 构建提供独立 Tauri 窗口,使用 `index.html?supervisor-chat&projectPath=...` 路由,并从现有 `index.html?agent-chat` 开发入口打开;`projectPath` 是 URL 编码后的项目绝对路径。
- 复用边界:窗口固定使用 `project-supervisor`,继续复用现有 active Session、External Runner、Agent Runtime、AppData 配置与持久 conversation;不创建新聊天后端、本地 HTTP 服务、数据库或平行配置体系。
- UI 边界:只显示持久消息区、输入框、必要的等待 / 错误状态和设置;不显示 Agent picker、Session / Goal / Runtime 面板或专业 Agent 协作栏。Session 控制面可隐藏,但对话仍按 `project-supervisor` active Session 持久化。
- 产品边界:该窗口只是开发验证入口,不替换、不修改正式用户 `client` 窗口及其登录、首页和项目开发流程。
@@ -14,6 +14,13 @@
- Run 控制参考:借鉴 Harbour 的控制平面思想,只吸收 `run lifecycle`、activity/output stream、context bundle、kill/retry/resume 等本地运行治理能力;不引入 Harbour 的多租户后台、调度 UI、通用 shell workflow 或远程 runner 作为 v1 依赖。
- 本地工程参考:借鉴 Godcoder 的本地产物 checkpoint / diff / restore、上下文安全过滤、轻量项目索引、项目级写锁和项目级权限策略;不引入通用 IDE 插件、云工作区或任意代码代理。
## 开发态 Project Supervisor 纯聊天独立窗口
- 入口边界:仅 debug 构建提供独立 Tauri 窗口,路由为 `index.html?supervisor-chat&projectPath=...`,其中 `projectPath` 传入 URL 编码后的项目绝对路径。窗口从现有 `index.html?agent-chat` 开发入口打开,不增加正式用户入口。
- Runtime 边界:窗口固定对话 Agent 为 `project-supervisor`,复用现有 active Session、External Runner、Agent Runtime、AppData 配置和持久 conversation;不新建平行会话库、Runner 或配置存储。
- 界面边界:只显示持久消息区、输入框、必要的等待 / 错误状态和设置入口;不显示 Agent picker、Session 面板、Goal 面板、Runtime 面板或专业 Agent 协作栏。会话历史仍绑定 `project-supervisor` 的 active Session 持久化,不因隐藏 Session 控制面而变为临时聊天。
- 产品边界:正式用户 `client` 窗口、登录后首页和项目开发流程保持不变,不暴露该开发验证面。
## Runtime 边界
V1.11 的受保护仓库控制目录同时包含 `.git / .agent / .agents / .codex / .hermes`;其中 `.agent` 对项目命令隐藏,其余控制目录只读。
+12
View File
@@ -1986,6 +1986,18 @@ function assertAiGameCreatorShellUserDevBoundary() {
);
}
}
for (const snippet of [
'fn open_project_supervisor_chat_window(',
'#[cfg(not(debug_assertions))]',
'项目总控对话窗口仅在开发构建中可用',
'index.html?supervisor-chat&projectPath=',
]) {
if (!aiGameCreatorShellTauriSource.includes(snippet)) {
throw new Error(
`AI game creator supervisor chat window must stay developer-only: ${snippet}`,
);
}
}
const workspaceWindowCommandIndex = aiGameCreatorShellTauriSource.indexOf(
'fn open_game_creator_workspace_window(',