修复 DirectProject 对话权限与原生命令门禁
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / Frontend tests (pull_request) Successful in 2m46s
Project CI / Native shell tests (pull_request) Successful in 18m19s

补充 DirectProject 原生对话读写命令的 native-only 白名单

复用项目级 DirectProject 写权限检查,避免连续回合重复读取策略

更新 AppSurface 测试以覆盖 Rust 负责历史持久化的直连语义
This commit is contained in:
2026-09-05 19:25:20 +08:00
parent 29ab89768c
commit d1f080478e
3 changed files with 70 additions and 24 deletions
@@ -107,11 +107,13 @@ const rustSharedContractSource = fs.readFileSync(
'utf8',
);
const allowedUncalledTauriCommands = [
'append_direct_project_conversation_message',
'chat_with_game_creator_agent',
'check_ui_editor_font_glyph_coverage',
'create_ui_design_resource',
'open_game_creator_launcher_window',
'open_game_creator_workspace_window',
'read_direct_project_conversation',
'stop_local_game_preview_if_matches',
];
const sourceExtensions = new Set([
+7 -1
View File
@@ -5307,7 +5307,10 @@ export function App({
if (directProjectPath && directInvoke) {
const clientTurnId =
directConversationTurnId ?? createDirectCodexConversationTurnId();
if (!directPolicyChecked) {
if (
!directPolicyChecked &&
projectConversationWriteConfirmedRef.current !== directProjectPath
) {
try {
const policyPaused = await queueProjectPolicyConfirmationIfNeeded(
directInvoke,
@@ -5316,6 +5319,8 @@ export function App({
'写入 DirectProject 对话历史',
'DirectProject 对话写入需要确认。',
() => {
projectConversationWriteConfirmedRef.current =
directProjectPath;
void executeChatAgentReply({
prompt,
clientTurnId,
@@ -5328,6 +5333,7 @@ export function App({
if (policyPaused) {
return;
}
projectConversationWriteConfirmedRef.current = directProjectPath;
} catch (error) {
if (localProjectPathRef.current === directProjectPath) {
setProjectSupervisorRuntimeError(
@@ -2122,6 +2122,7 @@ export function registerHomeProjectCreationTests() {
'existing-project',
'已有项目',
);
const persistedMessages: Array<Record<string, unknown>> = [];
const invoke = vi.fn(
async (command: string, args?: Record<string, unknown>) => {
if (command === 'get_local_game_manifest') {
@@ -2131,14 +2132,45 @@ export function registerHomeProjectCreationTests() {
if (command === 'append_local_permission_log') {
return {};
}
if (command === 'append_local_conversation_message') {
if (command === 'hydrate_game_creator_plan_gdd_state') {
return null;
}
if (command === 'read_project_permission_policy') {
return {
path: '.agent/policy.json',
policy: { deniedCommands: [], confirmCommands: [] },
};
}
if (
command === 'read_local_conversation' ||
command === 'read_direct_project_conversation'
) {
return {
path: `${projectPath}/.agent/conversations/project.jsonl`,
agentId: null,
messages: [],
sessionId: null,
messages: [...persistedMessages],
};
}
if (command === 'append_local_conversation_message') {
throw new Error(
'DirectProject must not use browser conversation writer',
);
}
if (command === 'chat_with_game_creator_direct_codex') {
const clientTurnId = String(args?.clientTurnId ?? '');
persistedMessages.push(
{
role: 'user',
content: String(args?.prompt ?? ''),
messageId: `direct-codex:${clientTurnId}:user`,
},
{
role: 'assistant',
content: 'DIRECT_EXISTING_PROJECT_OK',
messageId: `direct-codex:${clientTurnId}:assistant`,
},
);
return 'DIRECT_EXISTING_PROJECT_OK';
}
throw new Error(`unexpected invoke ${command}`);
@@ -2165,32 +2197,18 @@ export function registerHomeProjectCreationTests() {
},
);
});
const persistedTurnCall = invoke.mock.calls.findIndex(
([command, args]) =>
command === 'append_local_conversation_message' &&
(args as Record<string, unknown> | undefined)?.messageId !== undefined,
);
const directTurnCall = invoke.mock.calls.findIndex(
([command]) => command === 'chat_with_game_creator_direct_codex',
);
expect(persistedTurnCall).toBeGreaterThanOrEqual(0);
expect(persistedTurnCall).toBeLessThan(directTurnCall);
const persistedTurnArgs = invoke.mock.calls[persistedTurnCall]?.[1] as
| Record<string, unknown>
| undefined;
const directTurnArgs = invoke.mock.calls[directTurnCall]?.[1] as
| Record<string, unknown>
| undefined;
expect(persistedTurnArgs).toEqual({
projectPath,
agentId: null,
messageId: `direct-codex:${String(directTurnArgs?.clientTurnId ?? '')}:user`,
message: {
role: 'user',
content: '继续修改已有项目',
agentId: null,
},
});
expect(directTurnCall).toBeGreaterThanOrEqual(0);
expect(invoke).not.toHaveBeenCalledWith(
'append_local_conversation_message',
expect.anything(),
);
expect(directTurnArgs?.clientTurnId).toEqual(expect.any(String));
expect(invoke).not.toHaveBeenCalledWith(
'create_automatic_local_game_project',
);
@@ -2214,7 +2232,10 @@ export function registerHomeProjectCreationTests() {
expect(args).toEqual({ projectPath });
return manifest;
}
if (command === 'read_local_conversation') {
if (
command === 'read_local_conversation' ||
command === 'read_direct_project_conversation'
) {
return {
path: `${projectPath}/.agent/conversations/project.jsonl`,
agentId: null,
@@ -2257,6 +2278,23 @@ export function registerHomeProjectCreationTests() {
};
}
if (command === 'chat_with_game_creator_direct_codex') {
const clientTurnId = String(args?.clientTurnId ?? '');
persistedMessages.push(
{
schemaVersion: 'game-creator-conversation.v1',
role: 'user',
content: String(args?.prompt ?? ''),
messageId: `direct-codex:${clientTurnId}:user`,
updatedAt: 1,
},
{
schemaVersion: 'game-creator-conversation.v1',
role: 'assistant',
content: '陶泥儿智能创作 鉴权失败,请检查 API Key 或登录态',
messageId: `direct-codex:${clientTurnId}:assistant`,
updatedAt: 2,
},
);
throw new Error('codex-app-server-error:unauthorized');
}
throw new Error(`unexpected invoke ${command}`);