Skip to content

Fix duplicate initial messages after agent creation#1222

Open
ayhanmalkoc wants to merge 8 commits into
getpaseo:mainfrom
ayhanmalkoc:fix/new-agent-initial-message-reconciliation
Open

Fix duplicate initial messages after agent creation#1222
ayhanmalkoc wants to merge 8 commits into
getpaseo:mainfrom
ayhanmalkoc:fix/new-agent-initial-message-reconciliation

Conversation

@ayhanmalkoc
Copy link
Copy Markdown
Contributor

Summary

  • keep the optimistic create prompt visible until authoritative history includes it
  • merge pending create images/attachments into the authoritative user message
  • add coverage for pending create image/attachment reconciliation

Test

  • npm run lint -- packages/app/src/panels/agent-panel.tsx packages/app/src/utils/pending-create-images.ts packages/app/src/utils/pending-create-images.test.ts
  • npm run typecheck --workspace=@getpaseo/app
  • npx vitest run packages/app/src/utils/pending-create-images.test.ts --bail=1

Reconcile the optimistic first message with the first authoritative user message when providers replace the client-generated message id during new-agent handoff. This prevents duplicate prompt rows and stale pending turn UI in both native and gateway-backed Codex sessions.

Preserve pending image and attachment merging across provider-assigned message ids, and cover the reconciliation boundary with focused tests that avoid matching later repeated prompts.
@ayhanmalkoc ayhanmalkoc marked this pull request as ready for review May 29, 2026 09:18
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 29, 2026

Greptile Summary

This PR fixes the duplicate-message symptom on agent creation by showing an optimistic user message until the authoritative server stream includes it, and by merging pending images/attachments from the create form into the first authoritative user message when it arrives.

  • pending-create-images.ts: New utility with ID-primary / text-fallback matching and reference-stable no-op returns; covered by a thorough new test file.
  • agent-panel.tsx: AgentStreamSection reads pendingCreate from useCreateFlowStore, prepends the optimistic item when the tail is still empty, then fires a finalizing effect once the authoritative message lands in the tail — merging images via setAgentStreamTail before clearing the pending entry.
  • bottom-sheet-reopen.spec.ts / providers-section.test.tsx: Minor E2E flakiness fix and type-alignment test helper tweak.

Confidence Score: 5/5

Safe to merge; the optimistic-stream and image-merge paths are well-guarded and the utility is fully unit-tested.

The streamItems selector reads directly from agentStreamTail.get(agentId) — the same slice that the setAgentStreamTail updater operates on — so the message is guaranteed to be present in the updater's previous whenever pendingCreateUserMessageIndex >= 0. No data-loss or incorrect-display path was found in the changed code.

No files require special attention.

Important Files Changed

Filename Overview
packages/app/src/utils/pending-create-images.ts New utility providing ID-then-text-fallback message matching and idempotent image/attachment merge; reference-equality short-circuits are correct throughout.
packages/app/src/utils/pending-create-images.test.ts Comprehensive unit tests covering ID match, text-based fallback, no-overwrite guards, and the repeated-later-message edge case for findPendingCreateUserMessageIndex.
packages/app/src/panels/agent-panel.tsx Adds optimistic stream display and image merge effect; streamItems is sourced directly from agentStreamTail so the setAgentStreamTail updater is consistent. The redundant streamItems entry in the useEffect dependency array causes extra no-op effect invocations but no incorrect behavior.
packages/app/e2e/bottom-sheet-reopen.spec.ts Retry loop (up to 3 × 10s) with caught intermediate assertions; final hard assertion outside the loop still catches the regression case. Acceptable flakiness fix.
packages/app/src/screens/settings/providers-section.test.tsx Trivial fix: adds missing modelGateways: {} field to the test helper to satisfy the updated MutableDaemonConfig type.

Sequence Diagram

sequenceDiagram
    participant User
    participant AgentStreamSection
    participant CreateFlowStore
    participant SessionStore

    User->>CreateFlowStore: "setPending (text, images, lifecycle=active)"
    CreateFlowStore-->>AgentStreamSection: pendingCreate (via useCreateFlowStore)
    Note over AgentStreamSection: shouldUseOptimisticStream = true
    AgentStreamSection->>AgentStreamSection: "optimisticStreamItems = [user_message w/ images]"
    AgentStreamSection->>AgentStreamSection: "mergedStreamItems = [optimistic, ...streamItems]"

    SessionStore-->>AgentStreamSection: agentStreamTail updated (authoritative msg arrives)
    AgentStreamSection->>AgentStreamSection: "pendingCreateUserMessageIndex >= 0"
    AgentStreamSection->>AgentStreamSection: "mergedStreamItems = streamItems (optimistic dropped)"

    Note over AgentStreamSection: useEffect fires (canFinalizePendingCreate=true)
    AgentStreamSection->>SessionStore: setAgentStreamTail (merge images into tail)
    AgentStreamSection->>CreateFlowStore: markLifecycle(sent)
    AgentStreamSection->>CreateFlowStore: clearPendingCreate(draftId)
    CreateFlowStore-->>AgentStreamSection: "pendingCreate = null"
    Note over AgentStreamSection: shouldUseOptimisticStream = false, streamItems now has merged images
Loading

Reviews (7): Last reviewed commit: "Harden bottom sheet reopen E2E close" | Re-trigger Greptile

Comment thread packages/app/src/panels/agent-panel.tsx
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 29, 2026

Want your agent to iterate on Greptile's feedback? Try greploops.

Comment on lines +1327 to +1354
if (agentId && (hasPendingImages || hasPendingAttachments)) {
setAgentStreamTail(serverId, (previous) => {
const current = previous.get(agentId);
if (!current) {
return previous;
}

const merged = mergePendingCreateImages({
streamItems: current,
clientMessageId: pendingCreate.clientMessageId,
text: pendingCreate.text,
images: pendingImages,
attachments: pendingAttachments,
});
if (merged === current) {
return previous;
}

const next = new Map(previous);
next.set(agentId, merged);
return next;
});
}
markPendingCreateLifecycle({
draftId: pendingCreate.draftId,
lifecycle: "sent",
});
clearPendingCreate({ draftId: pendingCreate.draftId });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Image merge can silently fail before clearPendingCreate is called

markPendingCreateLifecycle and clearPendingCreate run unconditionally after the setAgentStreamTail block. Inside that block, the updater returns early without mutating state when previous.get(agentId) is undefined. If that entry is absent at the moment the effect fires (e.g. the stream-tail map hasn't been populated yet even though streamItems already contains the authoritative message via a different selector path), images/attachments are silently dropped and the pending create is permanently cleared with no retry path. Guarding the markPendingCreateLifecycle / clearPendingCreate calls on a confirmed successful merge (or at least checking that merged !== current) would make the behavior safe-by-construction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant