fix: scope observation to current turn
Require assistant natural language after the latest user message so a completed prompt cannot reuse historical replies when queuing the observation snapshot.
This commit is contained in:
@@ -120,13 +120,14 @@
|
||||
noise remains.
|
||||
- `pnpm run typecheck`: passed, including after the pinned-pnpm dependency rebuild.
|
||||
- `pnpm exec eslint . --quiet`: passed.
|
||||
- Full unit suite after planner fixes: 174 files passed; 1495 tests passed,
|
||||
2 skipped (1497 total).
|
||||
- Full unit suite after planner fixes: 174 files passed; 1496 tests passed,
|
||||
2 skipped (1498 total).
|
||||
- Pressure suite: 1 file and 1 test passed.
|
||||
- Focused post-review suite: 3 files and 33 tests passed for migration,
|
||||
- Focused post-review suite: 3 files and 34 tests passed for migration,
|
||||
session-observation sync, and Conversation store settlement behavior. It covers
|
||||
valid-v2 no-legacy-read, unknown/modified Agent backup, live ordinary-prompt
|
||||
settlement, no hydrate/follow-up replay, and assistant-natural-language gating.
|
||||
settlement, no hydrate/follow-up replay, and current-turn assistant natural-
|
||||
language gating that cannot reuse an older assistant reply.
|
||||
- Electron E2E: 3 tests passed for first Pi conversation latency/submit ordering,
|
||||
Pi product UI, and bundled Skill project configuration.
|
||||
- `pnpm run build:vite`: passed (Renderer, Electron Main, Preload, and utility).
|
||||
|
||||
@@ -363,8 +363,22 @@ export function queueAgentSessionSync(
|
||||
|
||||
/** Queue the final product Conversation snapshot after a completed prompt turn. */
|
||||
export function queueCodingConversationSessionSync(snapshot: ConversationSnapshot): void {
|
||||
const messages: RawMessage[] = snapshot.nodes.flatMap((node) => {
|
||||
if (node.kind !== 'message') return [];
|
||||
const messageNodes = snapshot.nodes.filter((node) => node.kind === 'message');
|
||||
let lastUserIndex = -1;
|
||||
for (let index = messageNodes.length - 1; index >= 0; index -= 1) {
|
||||
if (messageNodes[index].role === 'user') {
|
||||
lastUserIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lastUserIndex < 0) return;
|
||||
const currentTurnAssistantIds = new Set(messageNodes
|
||||
.slice(lastUserIndex + 1)
|
||||
.filter((node) => node.role === 'assistant')
|
||||
.map((node) => node.id));
|
||||
if (currentTurnAssistantIds.size === 0) return;
|
||||
|
||||
const messages: RawMessage[] = messageNodes.flatMap((node) => {
|
||||
return [{
|
||||
id: node.id,
|
||||
role: node.role,
|
||||
@@ -384,7 +398,11 @@ export function queueCodingConversationSessionSync(snapshot: ConversationSnapsho
|
||||
messages,
|
||||
updatedAt,
|
||||
);
|
||||
if (!data?.messages.some((message) => message.role === 'assistant')) return;
|
||||
if (!data?.messages.some((message) => (
|
||||
message.role === 'assistant'
|
||||
&& typeof message.id === 'string'
|
||||
&& currentTurnAssistantIds.has(message.id)
|
||||
))) return;
|
||||
queueAgentSessionData(data);
|
||||
}
|
||||
|
||||
|
||||
@@ -220,6 +220,45 @@ describe('local Agent session sync', () => {
|
||||
expect(pushSessionDataMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not reuse an older assistant reply when the current turn has no reply', async () => {
|
||||
const base = createProductSnapshot('conversation-1');
|
||||
queueCodingConversationSessionSync({
|
||||
...base,
|
||||
nodes: [
|
||||
{
|
||||
kind: 'message',
|
||||
id: 'old-user',
|
||||
role: 'user',
|
||||
status: 'complete',
|
||||
blocks: [{ kind: 'text', id: 'old-question', text: '旧问题', status: 'complete' }],
|
||||
},
|
||||
{
|
||||
kind: 'message',
|
||||
id: 'old-assistant',
|
||||
role: 'assistant',
|
||||
status: 'complete',
|
||||
blocks: [{ kind: 'text', id: 'old-answer', text: '旧回答', status: 'complete' }],
|
||||
},
|
||||
{
|
||||
kind: 'message',
|
||||
id: 'current-user',
|
||||
role: 'user',
|
||||
status: 'complete',
|
||||
blocks: [{ kind: 'text', id: 'current-question', text: '新问题', status: 'complete' }],
|
||||
},
|
||||
],
|
||||
run: {
|
||||
status: 'idle',
|
||||
runId: 'run-2',
|
||||
settledAt: Date.parse('2026-08-24T08:01:00.000Z'),
|
||||
terminalReason: 'completed',
|
||||
},
|
||||
});
|
||||
await flushPendingAgentSessionSync();
|
||||
|
||||
expect(pushSessionDataMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('never uploads a pending snapshot under a different authenticated account', async () => {
|
||||
queueAgentSessionSync('prj_1', 'ses_1', [
|
||||
{ id: 'user-1', role: 'user', content: '账号 A 的问题' },
|
||||
|
||||
Reference in New Issue
Block a user