修复游戏聊天续写来源匹配
Project CI / Repository checks (push) Successful in 1m56s
Project CI / Frontend tests (push) Successful in 3m45s
Project CI / Backend tests (push) Successful in 4m41s
Project CI / Native shell tests (push) Failing after 9m45s

Steer 目标增加 Runtime source 精确匹配

游戏聊天仅续写同来源 Run 并保留跨入口任务隔离

补充模型与 AppSurface 回归夹具和项目记忆
This commit is contained in:
2026-08-04 22:02:32 +08:00
parent cd96db3fb6
commit 838feaeae9
6 changed files with 36 additions and 11 deletions
+1
View File
@@ -5628,6 +5628,7 @@ export function App({
PROJECT_SUPERVISOR_AGENT_ID,
sessionId,
submissionRunProfile,
'project-supervisor-game-chat',
)
: null;
let autoPreviewAfterRevision = 0;
@@ -725,6 +725,7 @@ export function matchingAgentRuntimeForSteer(
requestedRunProfile: NonNullable<
AgentRuntimeState['runProfile']
> = 'standard',
requestedSource?: string,
) {
if (!sessionId) {
return null;
@@ -735,6 +736,7 @@ export function matchingAgentRuntimeForSteer(
runtime?.agentId === agentId &&
runtime.sessionId === sessionId &&
(runtime.runProfile ?? 'standard') === requestedRunProfile &&
(!requestedSource || runtime.source === requestedSource) &&
isAgentRuntimeSteerableState(runtime),
) ?? null
);
@@ -834,6 +836,7 @@ export async function submitProjectSupervisorRuntimeTask({
PROJECT_SUPERVISOR_AGENT_ID,
sessionId,
runProfile,
source,
);
if (steerRuntime) {
const steer = await invoke<AgentRuntimeSteerResult>(
@@ -1330,7 +1333,9 @@ export function projectSupervisorCollaboratingAgentRuntimes(
const runtimesByAgentId = new Map<string, AgentRuntimeState>();
for (const runtime of Object.values(runtimeByAgentId)) {
const isVisibleChildSource =
['agent-delegate', 'agent-delegate-retry'].includes(runtime?.source ?? '') ||
['agent-delegate', 'agent-delegate-retry'].includes(
runtime?.source ?? '',
) ||
(supervisorRuntime.source === 'project-supervisor-game-chat' &&
runtime?.source === 'agent-ready-task-scheduler');
if (
@@ -82,7 +82,8 @@ describe('Game Chat stream identity and source', () => {
eventPath: 'events.jsonl',
} satisfies AgentRuntimeResult;
const invoke = vi.fn(
async (_command: string, _args?: Record<string, unknown>) => runtimeResult,
async (_command: string, _args?: Record<string, unknown>) =>
runtimeResult,
);
await submitProjectSupervisorRuntimeTask({
@@ -116,6 +117,7 @@ describe('Game Chat stream identity and source', () => {
...runtimeResult.state,
runId: 'steer-run',
runProfile: 'autonomous-game-build',
source: 'project-supervisor-game-chat',
status: 'running',
phase: 'planning',
} satisfies AgentRuntimeState;
@@ -157,6 +159,24 @@ describe('Game Chat stream identity and source', () => {
runProfile: 'autonomous-game-build',
});
expect(invoke.mock.calls[0]?.[1]).not.toHaveProperty('source');
invoke.mockClear();
await submitProjectSupervisorRuntimeTask({
invoke,
projectPath: '/tmp/game-chat',
sessionId: 'session-provider-retry',
prompt: 'continue from the app',
runtime: {
...steerRuntime,
source: 'project-supervisor-cli',
},
runProfile: 'autonomous-game-build',
source: 'project-supervisor-game-chat',
});
expect(invoke).toHaveBeenCalledWith(
'start_game_creator_supervisor_runtime_task',
expect.objectContaining({ source: 'project-supervisor-game-chat' }),
);
});
test('game-chat hydration keeps identical text from a different runtime response identity', () => {
@@ -178,10 +198,9 @@ describe('Game Chat stream identity and source', () => {
},
];
expect(mergeGameChatRuntimeResponseMessagesIntoHistory(history, pending)).toEqual([
pending[0],
history[0],
]);
expect(
mergeGameChatRuntimeResponseMessagesIntoHistory(history, pending),
).toEqual([pending[0], history[0]]);
expect(
mergeGameChatRuntimeResponseMessagesIntoHistory(history, [
{ ...pending[0], messageId: 'persisted-message-1' },
@@ -471,9 +471,7 @@ function createProjectSupervisorRuntimeHarness({
currentProjectMessages.push({
schemaVersion: 'game-creator-conversation.v1',
...message,
...(args?.messageId
? { messageId: String(args.messageId) }
: {}),
...(args?.messageId ? { messageId: String(args.messageId) } : {}),
updatedAt:
typeof message.updatedAt === 'number'
? message.updatedAt
@@ -517,6 +515,7 @@ function createProjectSupervisorRuntimeHarness({
currentRuntime = runtimeState({
runId,
runProfile: args?.runProfile,
source: args?.source,
status: 'running',
phase: 'planning',
currentTask: String(args?.task ?? ''),
@@ -92,7 +92,7 @@ function gameChatRuntimeEvent({
runId,
source:
agentId === 'project-supervisor'
? 'project-supervisor'
? 'project-supervisor-game-chat'
: 'agent-delegate',
eventId,
eventType,
@@ -114,7 +114,7 @@ function gameChatRuntimeState(
taskId: 'project-supervisor',
sessionId: 'game-chat-supervisor-session',
runId: 'game-chat-supervisor-run',
source: 'project-supervisor',
source: 'project-supervisor-game-chat',
status: 'running',
phase: 'execution',
currentTask: '生成首版游戏',
@@ -3772,6 +3772,7 @@
- Provider action 安全持久化补充:pending / provider action 的泄密检测不能因裸自然语言短语 `api key` 直接拒绝,否则 `agent.delegate` 中“不要暴露 External Editor API Key”等安全约束会被误报并阻断首批协作。赋值形式只允许完整匹配受控的“未配置 / 不可用 / 禁止读取”等状态或固定无密钥降级说明,不能用 `starts_with` 放行 `none-but-secret``not configured; actual value ...` 等安全前缀后的凭据;`**API Key**:`、`` `API Key`: ``、`API Key(生产):` 等装饰或限定标签也必须识别为赋值。结构化字段标记 `apiKey / api_key`、`Authorization / Cookie`、`token / Bearer` 以及已知 secret token 形状仍必须检测并失败关闭。
- Windows retry 扫描补充:`Path::strip_prefix(root)` 在 Windows 上得到的相对 `Path` 转字符串后使用反斜杠,不能直接传给只接受 portable `/` 的 Runtime JSON sidecar 读取器;否则 Runner 重启或显式 `--agent-resume` 扫描已到期 retry 时会报“项目文件路径不能包含反斜杠”,任务持续停在 `waiting-for-provider-retry`。目录扫描应按路径组件重组成 `/` 分隔的 UTF-8 相对路径,不要放宽全局路径校验。
- 恢复交互:`needs-reconciliation` 即使没有 `pendingToolAction`,也必须提供显式“已核对,结束旧任务”;它只取消旧 run,不直接 retry。若取消后仍有 pending task,由 Runner 自动继续;只有队列为空且旧 run 已取消时,才允许创建新的 retry run,避免重复执行同一用户输入。自主构建 Supervisor 的 retry 不能改写为普通 `agent-background-task` source,必须从已验证的原 Run Profile 绑定恢复 `project-supervisor-gui / project-supervisor-cli` 可信来源;不得只信可追加的 task journal。
- Steer source:前端选择可 steer Runtime 时不能只比较 Agent、Session 和 Run Profile,还必须在调用方声明 source 时精确比较持久 `source`。例如 game-chat 只能 steer `project-supervisor-game-chat`,不能把同 Session/Profile 的 `project-supervisor-cli` run 当成目标;source 不一致时应按当前入口新建或排队自己的 run,不能先调用后端再把“steer source 与当前 Run 不一致”暴露给用户。
- 验证:前端回归同时覆盖零历史、无 Session 的初始空态、无 active Session 索引但存在持久 `needs-reconciliation` 总控 Runtime 的恢复展示,以及“先取消、队列为空后才重试”;真实 Windows 运行全部 tool-plan handoff 测试,确保相对句柄 rename、覆盖安装、回读和清理均通过。Responses 回归覆盖 system / user / assistant 文本分别序列化,并保留 user `input_text + input_image`;Runtime 回归覆盖“无效计划 → repair transport 等待 → steer → 新 cursor 再修复”,断言 cursor `0 / 1` 各有一条审计且不冲突。
## 固定画布产物返工不能变成任意覆盖,design-foundation 不能越权修程序