删除 Project Supervisor 聊天遗留的 pendingCommand 死链
- 删除 App.tsx 里 pendingCommand 状态、确认/取消处理与 21 个 execute* 分发分支 - 删除随该链路一起不可达的 21 个 execute* 实现与 requireChatProjectForUserAction - 删除只服务该链路的 PendingCommand 类型、pendingCommandPresentation.ts 与 resolvePendingCommandProjectPath - PlanningChatView 去掉 pendingCommand 面板相关 props、渲染分支与导入 - rememberCommand.test.ts 只保留现役 helper(path/remember/needsInitializedChatProject/agent 卡片)的用例
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1186,96 +1186,6 @@ export interface AgentStatusCard {
|
||||
|
||||
export type AgentTaskGraphState = 'active' | 'carried' | 'ready';
|
||||
|
||||
export type PendingCommand =
|
||||
| {
|
||||
id: 'game.generate_draft';
|
||||
prompt: string;
|
||||
}
|
||||
| {
|
||||
id: 'game.run_local';
|
||||
}
|
||||
| {
|
||||
id: 'asset.upload';
|
||||
file: File;
|
||||
}
|
||||
| {
|
||||
id: 'asset.register';
|
||||
localPath: string;
|
||||
kind: string;
|
||||
mediaType: string;
|
||||
}
|
||||
| {
|
||||
id: 'command.run_limited';
|
||||
commandId: string;
|
||||
}
|
||||
| {
|
||||
id: 'project.create';
|
||||
projectPath: string;
|
||||
}
|
||||
| {
|
||||
id: 'project.checkpoint';
|
||||
}
|
||||
| {
|
||||
id: 'project.export_package';
|
||||
}
|
||||
| {
|
||||
id: 'project.index';
|
||||
}
|
||||
| {
|
||||
id: 'project.restore';
|
||||
checkpointId: string;
|
||||
}
|
||||
| {
|
||||
id: 'project.policy_write';
|
||||
policy: ProjectPermissionPolicy;
|
||||
}
|
||||
| {
|
||||
id: 'preview.start';
|
||||
}
|
||||
| {
|
||||
id: 'preview.open';
|
||||
}
|
||||
| {
|
||||
id: 'agent.kill' | 'agent.retry' | 'agent.resume';
|
||||
detail?: string;
|
||||
}
|
||||
| {
|
||||
id: 'memory.write';
|
||||
scope: MemoryScope;
|
||||
content: string;
|
||||
mode?: MemoryWriteMode;
|
||||
}
|
||||
| {
|
||||
id: 'memory.delete';
|
||||
scope: MemoryScope;
|
||||
}
|
||||
| {
|
||||
id: 'canvas.project_open';
|
||||
canvasProjectId: string;
|
||||
}
|
||||
| {
|
||||
id: 'canvas.project_sync';
|
||||
canvasProjectId: string;
|
||||
}
|
||||
| {
|
||||
id: 'canvas.asset_import';
|
||||
localPath: string;
|
||||
canvasProjectId: string;
|
||||
canvasAssetId: string;
|
||||
canvasAssetObjectId?: string;
|
||||
kind: string;
|
||||
mediaType: string;
|
||||
}
|
||||
| {
|
||||
id: 'canvas.asset_generate';
|
||||
prompt: string;
|
||||
}
|
||||
| {
|
||||
id: 'canvas.export_import';
|
||||
exportPath: string;
|
||||
canvasProjectId: string;
|
||||
};
|
||||
|
||||
export interface PendingUiConfirmation {
|
||||
commandId: GameCreationAppCommandDescriptor['id'];
|
||||
detail: string;
|
||||
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
import { GAME_CREATION_APP_LIMITED_RUN_COMMANDS } from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import type { PendingCommand } from '../../app/types';
|
||||
import { formatAgentPolicySummary } from '../project-summary/agentPresentation';
|
||||
import {
|
||||
formatCanvasAssetSource,
|
||||
formatProjectPolicyCommandList,
|
||||
} from '../project-summary/projectSummary';
|
||||
import { memoryScopeLabel, memoryScopePath } from './memoryCommands';
|
||||
|
||||
export function pendingCommandTitle(command: PendingCommand) {
|
||||
if (command.id === 'asset.upload') {
|
||||
return `${command.id} · ${command.file.name}`;
|
||||
}
|
||||
if (command.id === 'asset.register') {
|
||||
return `${command.id} · ${command.localPath}`;
|
||||
}
|
||||
if (command.id === 'command.run_limited') {
|
||||
const descriptor = GAME_CREATION_APP_LIMITED_RUN_COMMANDS.find(
|
||||
(limitedCommand) => limitedCommand.id === command.commandId,
|
||||
);
|
||||
return `${command.id} · ${descriptor?.title ?? command.commandId}`;
|
||||
}
|
||||
if (command.id === 'game.run_local') {
|
||||
return command.id;
|
||||
}
|
||||
if (command.id === 'project.create') {
|
||||
return command.id;
|
||||
}
|
||||
if (command.id === 'project.checkpoint') {
|
||||
return command.id;
|
||||
}
|
||||
if (command.id === 'project.export_package') {
|
||||
return command.id;
|
||||
}
|
||||
if (command.id === 'project.index') {
|
||||
return command.id;
|
||||
}
|
||||
if (command.id === 'project.restore') {
|
||||
return `${command.id} · ${command.checkpointId}`;
|
||||
}
|
||||
if (command.id === 'project.policy_write') {
|
||||
return command.id;
|
||||
}
|
||||
if (
|
||||
command.id === 'agent.kill' ||
|
||||
command.id === 'agent.retry' ||
|
||||
command.id === 'agent.resume'
|
||||
) {
|
||||
return command.id;
|
||||
}
|
||||
if (command.id === 'memory.write' || command.id === 'memory.delete') {
|
||||
return `${command.id} · ${memoryScopeLabel(command.scope)}`;
|
||||
}
|
||||
if (command.id === 'canvas.project_open') {
|
||||
return `${command.id} · ${command.canvasProjectId}`;
|
||||
}
|
||||
if (command.id === 'canvas.project_sync') {
|
||||
return `${command.id} · ${command.canvasProjectId}`;
|
||||
}
|
||||
if (command.id === 'canvas.asset_import') {
|
||||
return `${command.id} · ${command.localPath}`;
|
||||
}
|
||||
if (command.id === 'canvas.asset_generate') {
|
||||
return command.id;
|
||||
}
|
||||
if (command.id === 'canvas.export_import') {
|
||||
return `${command.id} · ${command.exportPath}`;
|
||||
}
|
||||
return command.id;
|
||||
}
|
||||
|
||||
export function pendingCommandDetail(
|
||||
command: PendingCommand,
|
||||
projectPath: string,
|
||||
directCodex = false,
|
||||
) {
|
||||
if (command.id === 'asset.upload') {
|
||||
return `保存到 ${projectPath}/assets/uploads/`;
|
||||
}
|
||||
if (command.id === 'asset.register') {
|
||||
return `登记 ${projectPath}/${command.localPath} · ${command.kind} · ${command.mediaType}`;
|
||||
}
|
||||
if (command.id === 'command.run_limited') {
|
||||
return `运行 ${command.commandId} 于 ${projectPath}`;
|
||||
}
|
||||
if (command.id === 'game.run_local') {
|
||||
return directCodex
|
||||
? `启动 ${projectPath}/game/`
|
||||
: `运行自检并启动 ${projectPath}/game/`;
|
||||
}
|
||||
if (command.id === 'game.generate_draft') {
|
||||
return `调用 LLM Planner / Generator,编排 6 组角色 brief,写入 ${projectPath}/game、assets、memory、exports,通过 Evaluator 和自检后启动本地 HTTP 预览并载入客户端运行视图`;
|
||||
}
|
||||
if (command.id === 'project.create') {
|
||||
return `创建 ${command.projectPath}`;
|
||||
}
|
||||
if (command.id === 'project.checkpoint') {
|
||||
return `保存 ${projectPath} 当前项目快照`;
|
||||
}
|
||||
if (command.id === 'project.export_package') {
|
||||
return `从 ${projectPath}/game、assets 和 exports/README.md 导出本地试玩包`;
|
||||
}
|
||||
if (command.id === 'project.index') {
|
||||
return `刷新 ${projectPath}/.agent/project.index.json`;
|
||||
}
|
||||
if (command.id === 'project.restore') {
|
||||
return `从 ${command.checkpointId} 恢复 ${projectPath} 的已跟踪项目文件`;
|
||||
}
|
||||
if (command.id === 'project.policy_write') {
|
||||
return `写入 ${projectPath}/.agent/policy.json · 拒绝:${formatProjectPolicyCommandList(
|
||||
command.policy.deniedCommands,
|
||||
)} · 确认:${formatProjectPolicyCommandList(
|
||||
command.policy.confirmCommands,
|
||||
)} · Agent:${formatAgentPolicySummary(command.policy)}`;
|
||||
}
|
||||
if (command.id === 'preview.start') {
|
||||
return `启动 ${projectPath}/game/ 并载入客户端运行视图`;
|
||||
}
|
||||
if (command.id === 'preview.open') {
|
||||
return `打开 ${projectPath} 的当前本地预览`;
|
||||
}
|
||||
if (command.id === 'agent.kill') {
|
||||
return `标记 ${projectPath}/.agent/run.latest.json 为 killed,并写入 activity/output`;
|
||||
}
|
||||
if (command.id === 'agent.retry') {
|
||||
return `使用 ${projectPath}/.agent/run.latest.json 的目标重新运行一次`;
|
||||
}
|
||||
if (command.id === 'agent.resume') {
|
||||
return `附加说明${
|
||||
command.detail ? `「${command.detail}」` : ''
|
||||
},继续运行 ${projectPath}/.agent/run.latest.json 的目标`;
|
||||
}
|
||||
if (command.id === 'memory.write') {
|
||||
return `${
|
||||
command.mode === 'replace' ? '覆盖保存到' : '追加到'
|
||||
} ${projectPath}/${memoryScopePath(command.scope)}`;
|
||||
}
|
||||
if (command.id === 'memory.delete') {
|
||||
return `删除 ${projectPath}/${memoryScopePath(command.scope)}`;
|
||||
}
|
||||
if (command.id === 'canvas.project_open') {
|
||||
return `打开本机画板项目 ${command.canvasProjectId}`;
|
||||
}
|
||||
if (command.id === 'canvas.project_sync') {
|
||||
return `从画板项目 ${command.canvasProjectId} 同步资源到 ${projectPath}/assets/canvas-sync/`;
|
||||
}
|
||||
if (command.id === 'canvas.asset_import') {
|
||||
return `导入 ${projectPath}/${command.localPath} · 画板 ${formatCanvasAssetSource(
|
||||
command,
|
||||
)} · ${command.kind} · ${command.mediaType}`;
|
||||
}
|
||||
if (command.id === 'canvas.asset_generate') {
|
||||
return `通过平台 External Editor API 生成美术素材并写入 ${projectPath}/assets/canvas-generated/`;
|
||||
}
|
||||
if (command.id === 'canvas.export_import') {
|
||||
return `导入 ${command.exportPath} 到 ${projectPath}/assets/canvas-imports/ · 画板 ${command.canvasProjectId}`;
|
||||
}
|
||||
return `写入 ${projectPath}`;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { GameCreationAppCommandDescriptor } from '../../../../../packages/shared/src/contracts/gameCreationApp';
|
||||
import type { PendingCommand } from '../../app/types';
|
||||
import {
|
||||
isAbsoluteProjectPath,
|
||||
projectPathHasControlCharacter,
|
||||
@@ -60,17 +59,3 @@ export function needsInitializedChatProject(
|
||||
'conversation.write',
|
||||
].includes(commandId);
|
||||
}
|
||||
|
||||
export function resolvePendingCommandProjectPath(
|
||||
command: PendingCommand,
|
||||
localProject: { projectPath: string } | null,
|
||||
draftProjectPath: string,
|
||||
) {
|
||||
if (command.id === 'project.create') {
|
||||
return command.projectPath;
|
||||
}
|
||||
if (needsInitializedChatProject(command.id)) {
|
||||
return resolveChatProjectPath(localProject) ?? draftProjectPath;
|
||||
}
|
||||
return draftProjectPath;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import type {
|
||||
DesignClarificationRequest,
|
||||
DesignReasoningEntry,
|
||||
DesignView,
|
||||
PendingCommand,
|
||||
PendingUiConfirmation,
|
||||
PlanGddDecisionAction,
|
||||
PlanGddStateViewV1,
|
||||
@@ -19,11 +18,6 @@ import {
|
||||
projectNameFromPath,
|
||||
projectRuntimeVisibleError,
|
||||
} from '../../../features/agent-runtime';
|
||||
import {
|
||||
pendingCommandDetail,
|
||||
pendingCommandTitle,
|
||||
} from '../../../features/project-workspace/pendingCommandPresentation';
|
||||
import { resolvePendingCommandProjectPath } from '../../../features/project-workspace/projectCommandPolicy';
|
||||
import {
|
||||
ResourceReferenceInput,
|
||||
type ResourceReferenceInputHandle,
|
||||
@@ -103,10 +97,8 @@ type PlanningChatViewProps = {
|
||||
messagesRef: RefObject<HTMLDivElement | null>;
|
||||
needsUserInput: boolean;
|
||||
onCancelConfirmation: () => void;
|
||||
onCancelPendingCommand: () => void;
|
||||
onChatInputChange: (draft: ChatComposerDraft) => void;
|
||||
onConfirmConfirmation: () => void;
|
||||
onConfirmPendingCommand: () => void;
|
||||
onScroll: UIEventHandler<HTMLDivElement>;
|
||||
onShowEarlierMessages: () => void;
|
||||
onSubmit: FormEventHandler<HTMLFormElement>;
|
||||
@@ -116,7 +108,6 @@ type PlanningChatViewProps = {
|
||||
answers: Record<string, string>,
|
||||
) => void | Promise<void>;
|
||||
pendingConfirmation: PendingUiConfirmation | null;
|
||||
pendingCommand: PendingCommand | null;
|
||||
projectPath: string;
|
||||
runtime: AgentRuntimeState | null;
|
||||
transientReply: string;
|
||||
@@ -161,16 +152,13 @@ export function PlanningChatView({
|
||||
messagesRef,
|
||||
needsUserInput,
|
||||
onCancelConfirmation,
|
||||
onCancelPendingCommand,
|
||||
onChatInputChange,
|
||||
onConfirmConfirmation,
|
||||
onConfirmPendingCommand,
|
||||
onScroll,
|
||||
onShowEarlierMessages,
|
||||
onSubmit,
|
||||
onUserInput,
|
||||
pendingConfirmation,
|
||||
pendingCommand,
|
||||
projectPath,
|
||||
runtime,
|
||||
transientReply,
|
||||
@@ -346,30 +334,6 @@ export function PlanningChatView({
|
||||
controlBusy={controlBusy}
|
||||
onUserInput={onUserInput}
|
||||
/>
|
||||
{pendingCommand ? (
|
||||
<div className="pending-command">
|
||||
<span>
|
||||
{pendingCommandTitle(pendingCommand)}
|
||||
<small>
|
||||
{pendingCommandDetail(
|
||||
pendingCommand,
|
||||
resolvePendingCommandProjectPath(
|
||||
pendingCommand,
|
||||
{ projectPath },
|
||||
projectPath,
|
||||
),
|
||||
false,
|
||||
)}
|
||||
</small>
|
||||
</span>
|
||||
<button type="button" onClick={onCancelPendingCommand}>
|
||||
取消
|
||||
</button>
|
||||
<button type="button" onClick={onConfirmPendingCommand}>
|
||||
确认
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
{pendingConfirmation ? (
|
||||
<div className="pending-command">
|
||||
<span>
|
||||
|
||||
@@ -10,9 +10,7 @@ import {
|
||||
isAbsoluteProjectPath,
|
||||
needsInitializedChatProject,
|
||||
parseRememberInput,
|
||||
pendingCommandDetail,
|
||||
resolveChatProjectPath,
|
||||
resolvePendingCommandProjectPath,
|
||||
} from '../src/App';
|
||||
|
||||
describe('AI 游戏创作聊天记忆命令', () => {
|
||||
@@ -50,43 +48,7 @@ describe('AI 游戏创作聊天记忆命令', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('describes append and replace memory writes before confirmation', () => {
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{
|
||||
id: 'memory.write',
|
||||
scope: 'long',
|
||||
content: '保留厨房主题',
|
||||
mode: 'append',
|
||||
},
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe('追加到 /tmp/game/memory/project.md');
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{
|
||||
id: 'memory.write',
|
||||
scope: 'short',
|
||||
content: '覆盖本轮上下文',
|
||||
mode: 'replace',
|
||||
},
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe('覆盖保存到 /tmp/game/memory/session.md');
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{
|
||||
id: 'memory.write',
|
||||
scope: 'blackboard',
|
||||
content: '共享美术约束',
|
||||
mode: 'append',
|
||||
},
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe('追加到 /tmp/game/memory/blackboard.md');
|
||||
});
|
||||
|
||||
it('requires an initialized local project before chat memory writes', () => {
|
||||
it('requires an initialized local project path before chat memory commands', () => {
|
||||
expect(resolveChatProjectPath(null)).toBeNull();
|
||||
expect(resolveChatProjectPath({ projectPath: '/tmp/game' })).toBe(
|
||||
'/tmp/game',
|
||||
@@ -103,47 +65,6 @@ describe('AI 游戏创作聊天记忆命令', () => {
|
||||
scope: 'short',
|
||||
content: '覆盖本轮上下文',
|
||||
});
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{
|
||||
id: 'memory.delete',
|
||||
scope: 'short',
|
||||
},
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe('删除 /tmp/game/memory/session.md');
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{
|
||||
id: 'memory.delete',
|
||||
scope: 'blackboard',
|
||||
},
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe('删除 /tmp/game/memory/blackboard.md');
|
||||
});
|
||||
|
||||
it('describes local generation side effects before confirmation', () => {
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{
|
||||
id: 'game.generate_draft',
|
||||
prompt: '做一个厨房弹幕游戏',
|
||||
},
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe(
|
||||
'调用 LLM Planner / Generator,编排 6 组角色 brief,写入 /tmp/game/game、assets、memory、exports,通过 Evaluator 和自检后启动本地 HTTP 预览并载入客户端运行视图',
|
||||
);
|
||||
});
|
||||
|
||||
it('describes preview side effects before confirmation', () => {
|
||||
expect(pendingCommandDetail({ id: 'preview.start' }, '/tmp/game')).toBe(
|
||||
'启动 /tmp/game/game/ 并载入客户端运行视图',
|
||||
);
|
||||
expect(pendingCommandDetail({ id: 'preview.open' }, '/tmp/game')).toBe(
|
||||
'打开 /tmp/game 的当前本地预览',
|
||||
);
|
||||
});
|
||||
|
||||
it('requires a project before chat commands write or run local artifacts', () => {
|
||||
@@ -166,97 +87,6 @@ describe('AI 游戏创作聊天记忆命令', () => {
|
||||
expect(needsInitializedChatProject('canvas.project_open')).toBe(false);
|
||||
});
|
||||
|
||||
it('describes project safety commands before confirmation', () => {
|
||||
expect(
|
||||
pendingCommandDetail({ id: 'project.checkpoint' }, '/tmp/game'),
|
||||
).toBe('保存 /tmp/game 当前项目快照');
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{ id: 'project.restore', checkpointId: 'checkpoint-1' },
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe('从 checkpoint-1 恢复 /tmp/game 的已跟踪项目文件');
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{
|
||||
id: 'project.policy_write',
|
||||
policy: {
|
||||
deniedCommands: ['file.write'],
|
||||
confirmCommands: [],
|
||||
},
|
||||
},
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe(
|
||||
'写入 /tmp/game/.agent/policy.json · 拒绝:file.write · 确认:无 · Agent:无 Agent 独立策略',
|
||||
);
|
||||
});
|
||||
|
||||
it('describes canvas asset imports before confirmation', () => {
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{
|
||||
id: 'canvas.asset_import',
|
||||
localPath: 'assets/hero.png',
|
||||
canvasProjectId: 'canvas-project-1',
|
||||
canvasAssetId: '',
|
||||
canvasAssetObjectId: 'asset-object-1',
|
||||
kind: 'character',
|
||||
mediaType: 'image/png',
|
||||
},
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe(
|
||||
'导入 /tmp/game/assets/hero.png · 画板 canvas-project-1 / object:asset-object-1 · character · image/png',
|
||||
);
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{
|
||||
id: 'canvas.export_import',
|
||||
exportPath: '/tmp/canvas-export.zip',
|
||||
canvasProjectId: 'canvas-project-1',
|
||||
},
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe(
|
||||
'导入 /tmp/canvas-export.zip 到 /tmp/game/assets/canvas-imports/ · 画板 canvas-project-1',
|
||||
);
|
||||
});
|
||||
|
||||
it('describes agent run lifecycle controls before confirmation', () => {
|
||||
expect(pendingCommandDetail({ id: 'agent.kill' }, '/tmp/game')).toBe(
|
||||
'标记 /tmp/game/.agent/run.latest.json 为 killed,并写入 activity/output',
|
||||
);
|
||||
expect(pendingCommandDetail({ id: 'agent.retry' }, '/tmp/game')).toBe(
|
||||
'使用 /tmp/game/.agent/run.latest.json 的目标重新运行一次',
|
||||
);
|
||||
expect(
|
||||
pendingCommandDetail(
|
||||
{ id: 'agent.resume', detail: '继续修复输入监听' },
|
||||
'/tmp/game',
|
||||
),
|
||||
).toBe(
|
||||
'附加说明「继续修复输入监听」,继续运行 /tmp/game/.agent/run.latest.json 的目标',
|
||||
);
|
||||
});
|
||||
|
||||
it('shows the authorized project path in pending write command details', () => {
|
||||
expect(
|
||||
resolvePendingCommandProjectPath(
|
||||
{ id: 'game.generate_draft', prompt: '做一个厨房弹幕游戏' },
|
||||
{ projectPath: '/authorized/game' },
|
||||
'/draft/dev-input',
|
||||
),
|
||||
).toBe('/authorized/game');
|
||||
expect(
|
||||
resolvePendingCommandProjectPath(
|
||||
{ id: 'project.create', projectPath: '/new/game' },
|
||||
{ projectPath: '/authorized/game' },
|
||||
'/draft/dev-input',
|
||||
),
|
||||
).toBe('/new/game');
|
||||
});
|
||||
|
||||
it('derives per-agent status cards from manifest and latest trace steps', () => {
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'local-project-draft',
|
||||
|
||||
Reference in New Issue
Block a user