删除 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:
2026-09-21 11:15:02 +08:00
parent 1c830e6fe6
commit 0224ef7b9d
6 changed files with 3 additions and 1919 deletions
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;
@@ -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',