feat(agent): In-App Agent side panel behind the agent-in-app-experience flag#13472
feat(agent): In-App Agent side panel behind the agent-in-app-experience flag#13472CodeJuggernaut wants to merge 78 commits into
Conversation
…kage Bulk copy of the panel from Comfy-Org/comfy-inapp-agent (branch feat/agent-panel-services at b3675fc) into packages/agent-panel (@comfyorg/agent-panel), per the decision to home the panel in this repo. The package is self-contained with its own dependency set and configs; tooling versions align with the workspace catalog (vite 8, vitest 4, zod ^3.23.8, typescript 5.9, vue-tsc 3.2), so the code relocates verbatim. Excluded from the copy: the standalone project's lockfile and nested workspace marker; the root allowBuilds already carries vue-demi: false. The unused @ai-sdk/vue dependency was dropped. Contents: the comfy-agent v1 client stack (zod wire contract with live-captured fixtures, REST client for ingest /api/agent/*, WebSocket event transport and event-source adapter, server-draft store plus the useDraftCanvasApply seam, useAgentSession composition root), the fail-closed PostHog gate for the agent-in-app-experience flag, the full chat panel UI (message stream, composer, chrome, shelved safety surfaces), i18n, and a dev harness with an offline scripted mode and a live proxy mode verified against pr-4432.testenvs.comfy.org. Package gates green in the workspace: typecheck clean, 197 tests passing, build succeeds. Next (B8): mount the panel in the app, wire the host socket, JWT, PostHog client, and canvas draft-apply, then cut over from the legacy extension.
…nsion The panel now runs the way everything user-facing runs here: as an extension. src/extensions/core/agentPanel.ts (cloud-only, loaded from the core extension index) registers a sidebar tab gated by the PostHog flag agent-in-app-experience, fail-closed: the tab is not registered until the flag evaluates true and unregisters if it turns off. The tab is type custom: it mounts the panel as its own Vue app via the new mountAgentPanel entry, which keeps the package's vue-i18n major (11) and pinia instance isolated from the host (vue-i18n 9). Host wiring in the entry: workspace Cloud JWT as the REST bearer (store instance captured in host context, since pinia's active-instance global is shared with the panel app), the host /ws socket wrapped by createWebSocketEventSource, and draft apply as a schema-validated full-graph load (validateComfyWorkflow then app.loadGraphData) per the V0 tech design. To make the package importable from src, it is properly library-ized: all internal @/ alias imports rewritten to relative paths (the alias would have resolved against the HOST src when host tooling compiles the package sources), the alias config dropped, and a public entry exposed via package.json exports (mount + the host-facing seam builders and types). Root package.json declares the workspace dependency like every other @comfyorg package. Known follow-ups tracked in FE-1187: socket-reconnect rebinding for the event source, confirming agent_* frames reach raw socket listeners, the workflowId provider for draft binding, panel styling inside the tab (tailwind theme integration), and the design-alignment pass. Package gates green (typecheck, 197 tests, build); host root typecheck green with the entry included.
…subtree The panel now lives at src/workbench/extensions/agent (manager-extension pattern) on host package versions, host pinia, and host vue-i18n; the workspace package is gone. The sidebar tab becomes a plain type vue registration (AgentPanelRoot rendered by the host app) and the fail-closed PostHog flag gate in the extension entry is unchanged. AgentPanelRoot is the host-context session root: workspace Cloud JWT as the REST bearer, the host /ws socket wrapped as the event source (null socket degrades to a warned no-op source), draft apply as a schema-validated full-graph load, and the chrome the dev harness carried (history drawer, starting-point modal, onboarding coach with the same storage key and anchor) minus the dev fakes. Theme tokens route through host semantics instead of hardcoded values: the feature-owned agentTheme.css aliases every agent-* token to a host token via @theme inline (pairing table documented inline), so the panel tracks host theme flips including light mode, an intentional divergence from the dark-only Figma frames deferred to the FE-1187 design pass. Literals remain only for accent-fg, pill, and radius, each flagged as a design-review item. The design-system master is untouched; the host style entry gains one import line. Locale keys merge under a top-level agent block in the host main.json. cn comes from @comfyorg/tailwind-utils. shiki and class-variance-authority join the catalog and root deps (CodeBlock highlighting, Button variants). Dead-in-src modules are deleted rather than knip-ignored: the shelved agentCore kernel and its tests, the MinimizedBall dock (superseded by the sidebar form factor), and six never-wired components knip flagged with zero consumers; unused schema type exports trimmed likewise. Gates: typecheck clean, moved tree 147/147 (197 minus the deleted kernel's 50 cases; no surviving test lost), full unit suite 12550 passed, oxlint, eslint, oxfmt, and knip all green. Handoff invariants re-verified in the moved tree: fixture schema gate, event transport acceptance, draft monotonic adoption and canvas-apply seam, fail-closed gate, entries interleave, send/stop/new-chat paths. Known follow-ups (FE-1187): browser visual pass on the aliased tokens and light mode, socket-reconnect rebinding, workflowId provider for draft binding, design alignment.
🌐 Website E2ETip All tests passed.
|
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1696 passed, 0 failed · 1 flaky📊 Browser Reports
📦 Bundle: 7.88 MB gzip 🔴 +92.8 kBDetailsSummary
Category Glance App Entry Points — 53.2 kB (baseline 53.2 kB) • 🔴 +36 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.27 MB (baseline 1.27 MB) • 🔴 +3.69 kBGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 110 kB (baseline 110 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 14 added / 14 removed Panels & Settings — 552 kB (baseline 550 kB) • 🔴 +1.85 kBConfiguration panels, inspectors, and settings screens
Status: 25 added / 24 removed / 3 unchanged User & Accounts — 29.1 kB (baseline 29.1 kB) • 🔴 +1 BAuthentication, profile, and account management bundles
Status: 9 added / 9 removed / 1 unchanged Editors & Dialogs — 117 kB (baseline 117 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 5 added / 5 removed UI Components — 57.5 kB (baseline 57.5 kB) • ⚪ 0 BReusable component library chunks
Status: 13 added / 13 removed Data & Services — 271 kB (baseline 270 kB) • 🔴 +1.5 kBStores, services, APIs, and repositories
Status: 16 added / 15 removed / 1 unchanged Utilities & Hooks — 3.39 MB (baseline 3.39 MB) • 🔴 +9 BHelpers, composables, and utility bundles
Status: 26 added / 26 removed / 9 unchanged Vendor & Third-Party — 15.5 MB (baseline 15.3 MB) • 🔴 +241 kBExternal libraries and shared vendor chunks
Status: 3 added / 3 removed / 13 unchanged Other — 11.8 MB (baseline 11.7 MB) • 🔴 +105 kBBundles that do not match a named category
Status: 154 added / 152 removed / 13 unchanged ⚡ Performance
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a Cloud-only in-app AI Agent panel with feature gating, REST/WebSocket contracts, session and store orchestration, message rendering, theming, telemetry, and browser coverage. ChangesIn-App Agent Panel
Estimated code review effort: 5 (Critical) | ~150 minutes 🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Review fixes from the four-hat panel, each independently specified: - the event source now follows the host socket across reconnects: createReconnectingEventSource rebinds message and open/close listeners to the fresh api.socket on the host reconnected event, reports live on an already-open rebind, warns once on a null socket and attaches on the first reconnect; AgentPanelRoot uses it in place of the instance-bound adapter (the panel no longer goes deaf after a routine /ws reconnect) - the fail-closed flag gate in the extension entry gains a four-case test suite modeled on the previewAny sibling (no register while the flag is unknown, register once on true, unregister on flip-off, no double-register across toggles) - session notices forward to the host toast store (new-entries-only watch), making cancel failures and malformed-event warnings visible; covered by a mount test driving a malformed frame through a fake socket - the five staged M4 surfaces (tab context, attachments, canvas selection) carry explicit STAGED markers and the subtree gains a README recording them plus the intentional local ui-primitives decision - onboarding coach copy moves to i18n keys; the storage key and root id drop their dev-harness names (Comfy.AgentPanel.onboarded, agent-panel-root) - the composer paperclip hides behind a default-false canAttach prop until an attach flow is wired The workflowId provider (panel-to-draft binding) is intentionally NOT wired: no host surface exposes the server-side workflow uuid (the workflow class is path-keyed; the uuid appears only in the agent's own 202 ack and per-job queue fields). Recorded as an open product/plumbing decision. Typecheck clean, full suite 12561 passed, oxlint and eslint clean, no new knip findings from these files.
There was a problem hiding this comment.
Actionable comments posted: 25
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/workbench/extensions/agent/AgentPanelRoot.vue`:
- Around line 38-46: The AgentPanelRoot.vue event source is binding to the
initial api.socket instance, so a reconnect that swaps api.socket can leave the
panel deaf to agent_* events. Update the setup in AgentPanelRoot.vue so events
is recreated or otherwise kept in sync when the socket changes, using
createWebSocketEventSource and the api.socket binding path. If this behavior is
not being fixed now, ensure FE-1187 is explicitly confirmed/accepted before
broad rollout and leave the TODO tied to the relevant
AgentEventSource/api.socket logic.
- Around line 53-58: The draft-to-canvas apply flow in useDraftCanvasApply
currently has no failure handling, so errors from validateComfyWorkflow or
app.loadGraphData can become unhandled rejections. Update the async callback in
AgentPanelRoot to wrap the validateComfyWorkflow/app.loadGraphData sequence in
explicit try/catch, and add logging and/or user-facing failure handling in the
catch path so apply failures are surfaced instead of silently dropping the
draft.
- Around line 42-45: The event source initialization in AgentPanelRoot.vue mixes
a side effect into a ternary via the comma operator, which should be refactored
for clarity. Update the logic around the events/AgentEventSource setup to use an
explicit if/else so the websocket path and the fallback subscription stub are
assigned separately, and keep the console.warn call only in the fallback branch.
- Around line 69-75: The onboarding copy in AgentPanelRoot.vue is hardcoded
English and should be localized via vue-i18n. Update the coach step definition
in coachSteps to read the title and body from translation keys instead of
literals, and add those keys to src/locales/en/main.json. Keep the target
behavior the same while ensuring the user-facing strings for the “Meet the
agent” step are sourced through i18n.
In `@src/workbench/extensions/agent/components/agent/Composer.test.ts`:
- Around line 9-11: The test helper mount currently accepts overly broad props,
which weakens type safety at call sites. Update mount in Composer.test.ts to use
a precise props type based on Partial<ComponentProps<typeof Composer>> instead
of Record<string, unknown>, and keep the render(Composer, { props, global: {
plugins: [i18n] } }) usage unchanged. Use the Composer component and the mount
helper signature as the main symbols to locate and adjust the typing.
In `@src/workbench/extensions/agent/components/agent/m4HostPieces.test.ts`:
- Around line 33-41: The ActiveTabStrip test currently rerenders with tab set to
null but never verifies the empty state, so the “nothing when there is no tab”
behavior is untested. Update the ActiveTabStrip spec to add an assertion after
rerendering with tab: null, using the existing render/rerender and screen usage
in m4HostPieces.test.ts to confirm no tab text is shown or that the component
renders nothing for the null case.
In `@src/workbench/extensions/agent/components/agent/message/AgentMessage.vue`:
- Line 3: The `AgentMessage` component imports and uses `useI18n` only to
provide `t` for the template, so simplify the script by removing the `useI18n`
import and any `t` setup in the component’s script section. Update the template
to call the built-in `$t` directly wherever `t` is currently used, keeping the
localization behavior unchanged while eliminating the unnecessary script-side
i18n wiring.
In `@src/workbench/extensions/agent/components/agent/message/CodeBlock.vue`:
- Around line 18-19: The highlighted HTML is being passed to v-html without an
actual sanitization step, so update CodeBlock.vue to run shiki output through
DOMPurify before injection. Locate the fallback/highlighting flow that produces
the HTML string used by v-html, and sanitize both the shiki-rendered markup and
any other HTML path in this component (including the areas referenced by the
comment) before assigning or rendering it.
- Around line 20-40: The async highlight logic in CodeBlock.vue can let an older
codeToHtml result overwrite a newer one when code or lang changes rapidly.
Update the watch callback to invalidate stale async work by adding a
generation/token or watcher cleanup guard around the highlighted assignment in
the watch on [code, lang], so only the latest invocation can set
highlighted.value. Keep the lazy shiki import and current error handling, but
ensure any out-of-order resolution is ignored before writing the HTML.
In `@src/workbench/extensions/agent/components/agent/message/Lightbox.vue`:
- Around line 22-29: Update the video element in Lightbox.vue so autoplay works
reliably by adding muted and playsinline alongside the existing autoplay, loop,
and controls attributes on the <video> used for isVideo/url. This change should
be made in the Lightbox component’s video tag so browsers allow autoplay and iOS
Safari can play inline instead of forcing manual playback.
In `@src/workbench/extensions/agent/components/agent/message/UserMessage.vue`:
- Around line 15-22: The initials logic in UserMessage.vue uses name ??
t('agent.you'), which only falls back for null or undefined and lets empty
strings produce blank initials. Update the computed initials in the initials
declaration to treat empty-string names as missing, so it falls back to the
localized agent.you label before splitting and deriving initials.
In `@src/workbench/extensions/agent/components/agent/OnboardingCoach.vue`:
- Around line 56-75: The onboarding coach overlay in OnboardingCoach.vue needs
proper dialog accessibility handling. Add dialog semantics to the root
overlay/card (for example, role and aria-modal) and make the coach content
focusable when it opens. Implement focus trapping within the coach, restore
focus on close, and wire an Escape key handler to call skip so keyboard and
screen-reader users cannot tab into hidden content behind the backdrop.
- Around line 25-53: The onboarding coach positioning in OnboardingCoach.vue
uses a hard-coded CARD_H, so long step copy can still push the card off-screen.
Update the watchEffect positioning logic to use the card’s actual rendered
height instead of the fixed constant, and add a template ref on the positioned
card element so the code can measure it before setting cardStyle. Keep the
existing clamping behavior in sync with the measured size when computing the
top/left for currentStep.
In `@src/workbench/extensions/agent/components/agent/safety/ApprovalCard.vue`:
- Around line 30-41: The ApprovalCard component can emit answer twice on a rapid
double-click because both Buttons remain enabled while card.status is still
open. In ApprovalCard.vue, add a local “pending”/locked state around the
existing emit('answer', card.approvalId, ...) handlers so the first click
immediately disables both approve and deny actions until the parent updates
card.status away from open. Keep the fix scoped to the ApprovalCard
template/logic and use the existing card.status and emit flow to clear the lock
when the prop changes.
In `@src/workbench/extensions/agent/composables/agent/useAttachment.ts`:
- Around line 32-59: The addFiles flow in useAttachment is toggling
pending.value inside the per-file loop, causing the upload state to drop between
uploads in a multi-file batch. Move the pending state management so it stays
true for the entire addFiles() batch and is cleared only once after all files
have been processed, while preserving the existing upload/error handling and
staged results behavior in options.upload and options.onError.
In `@src/workbench/extensions/agent/composables/agent/useCloudAssets.ts`:
- Around line 20-34: The deduplication logic in load() currently uses a manual
Set-based loop, but this should follow the project guideline to use es-toolkit
utilities. Update the asset deduping in useCloudAssets.ts to replace the
explicit seen/deduped iteration with uniqBy on asset.id, then keep the existing
max slicing and loading state handling intact.
In `@src/workbench/extensions/agent/schemas/agentApiSchema.ts`:
- Around line 41-49: The zAgentMessage schema is stricter than the other agent
schemas and will reject additive server fields during getMessages parsing.
Update zAgentMessage to tolerate unknown properties the same way
zAgentTurnAccepted and the WS event schemas do by adding passthrough behavior
while keeping the existing validated fields intact. Use the zAgentMessage and
zAgentMessages schema definitions in agentApiSchema.ts as the place to make the
change.
In `@src/workbench/extensions/agent/services/agent/agentEventTransport.test.ts`:
- Around line 93-148: The replay coverage in agentEventTransport.test only
exercises ws-turn-edit and ws-turn-cancelled, leaving the remaining ws-*.jsonl
fixtures unverified. Add new test cases in agentEventTransport.test using
chatEventsFor and drive for ws-turn-denied-run.jsonl (and ws-turn-ask-run.jsonl
if applicable), and assert the distinct outcomes on the resulting message via
textParts, toolParts, parts, and token/streaming flags so the new fixture paths
in createAgentEventTransport are covered.
- Around line 196-231: Add a regression test in the agentEventTransport
settle-lifecycle suite for ingesting events after the transport has already been
settled. In the agentEventTransport tests, create a transport, call
finalize(null) or abort(), then call ingest(...) and assert it is a no-op by
verifying no additional emissions and no message state changes. Reference the
agentEventTransport() instance methods ingest(), finalize(), and abort() so the
missing settled guard is covered.
In `@src/workbench/extensions/agent/services/agent/agentRestClient.ts`:
- Around line 105-113: The agent REST request helper currently forwards init
directly through request() to fetchImpl, so stalled calls can hang indefinitely.
Update request() in agentRestClient.ts to enforce a timeout or accept and honor
a caller-provided AbortSignal, and make sure the agent session send/resync path
threads that signal through from the session boundary. Use the request() helper
and any session-layer caller that invokes it as the main places to wire in the
cancellation behavior.
In `@src/workbench/extensions/agent/stores/agent/agentChatHistoryStore.test.ts`:
- Around line 1-42: Add tests covering the `useAgentChatHistoryStore` actions
(`upsert`, `remove`, `setActive`) so the store behavior is exercised directly,
including `remove` clearing `activeId` when the active session is deleted. Also
add a DST-boundary test for `groupSessionsByRecency`/`startYesterday` using a
date near a daylight-saving transition to verify the guard works as intended.
In `@src/workbench/extensions/agent/stores/agent/agentChatHistoryStore.ts`:
- Around line 58-60: The computed `grouped` in `agentChatHistoryStore.ts` uses
`Date.now()` directly, so it only recomputes when `sessions` or `activeId`
change and won’t update at midnight. Replace the non-reactive time read in
`computed(() => groupSessionsByRecency(...))` with a reactive clock from VueUse,
such as `useNow()` or `useTimestamp()`, and use that reactive value when calling
`groupSessionsByRecency` so the recency buckets refresh automatically over time.
- Around line 29-52: Refactor groupSessionsByRecency in agentChatHistoryStore to
use es-toolkit’s groupBy from es-toolkit/array instead of the manual
if/else/push bucketing loop. Keep the existing recency precedence based on
activeId, startToday, and startYesterday by sorting into the same
current/today/yesterday/earlier keys, then default any missing buckets with ??
[] when building the returned HistoryGroups object.
In `@src/workbench/extensions/agent/stores/agent/agentDraftStore.ts`:
- Around line 19-21: The public draft state in agentDraftStore is currently
exposed as mutable refs, so consumers can bypass the store’s update methods.
Update the store export to wrap workflowId, content, and version with readonly()
in the agentDraftStore implementation, while keeping all mutations confined to
bind, applyPatch, adoptSnapshot, and reset. Use the existing ref declarations in
the store to locate the fields that need to be made read-only.
In `@src/workbench/extensions/agent/utils/agent/renderMarkdownToHtml.ts`:
- Around line 1-22: The link-rewrite hook in renderMarkdownToHtml mutates the
shared DOMPurify singleton, so every sanitize call across the app inherits this
behavior. Fix it by avoiding DOMPurify.addHook at module load; instead, use a
local DOMPurify instance inside renderMarkdownToHtml or apply the target and rel
attributes after sanitizing the marked.parse output, keeping the change scoped
to this renderer.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 3afacd9a-ddc9-4d69-9170-de3b80ce7919
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (89)
package.jsonpnpm-workspace.yamlsrc/assets/css/style.csssrc/extensions/core/agentPanel.tssrc/extensions/core/index.tssrc/locales/en/main.jsonsrc/workbench/extensions/agent/AgentPanelRoot.vuesrc/workbench/extensions/agent/agentTheme.csssrc/workbench/extensions/agent/components/agent/ActiveTabStrip.vuesrc/workbench/extensions/agent/components/agent/AgentPanel.vuesrc/workbench/extensions/agent/components/agent/ChatHistoryDrawer.vuesrc/workbench/extensions/agent/components/agent/Composer.test.tssrc/workbench/extensions/agent/components/agent/Composer.vuesrc/workbench/extensions/agent/components/agent/ConversationView.test.tssrc/workbench/extensions/agent/components/agent/ConversationView.vuesrc/workbench/extensions/agent/components/agent/EmptyState.vuesrc/workbench/extensions/agent/components/agent/OnboardingCoach.vuesrc/workbench/extensions/agent/components/agent/PanelHeader.vuesrc/workbench/extensions/agent/components/agent/StartingPointModal.vuesrc/workbench/extensions/agent/components/agent/composer/AssetTray.vuesrc/workbench/extensions/agent/components/agent/composer/AttachmentChip.vuesrc/workbench/extensions/agent/components/agent/composer/SelectionActionChips.vuesrc/workbench/extensions/agent/components/agent/composer/SuggestedPromptChip.vuesrc/workbench/extensions/agent/components/agent/m4HostPieces.test.tssrc/workbench/extensions/agent/components/agent/message/AgentMessage.vuesrc/workbench/extensions/agent/components/agent/message/AssetCard.vuesrc/workbench/extensions/agent/components/agent/message/AssetGrid.vuesrc/workbench/extensions/agent/components/agent/message/CodeBlock.vuesrc/workbench/extensions/agent/components/agent/message/Lightbox.vuesrc/workbench/extensions/agent/components/agent/message/MarkdownStream.vuesrc/workbench/extensions/agent/components/agent/message/MessageFeedback.vuesrc/workbench/extensions/agent/components/agent/message/ReasoningDisclosure.vuesrc/workbench/extensions/agent/components/agent/message/ThinkingStatus.vuesrc/workbench/extensions/agent/components/agent/message/ToolCallCard.vuesrc/workbench/extensions/agent/components/agent/message/ToolCallGroup.vuesrc/workbench/extensions/agent/components/agent/message/UserMessage.vuesrc/workbench/extensions/agent/components/agent/safety/ApprovalCard.test.tssrc/workbench/extensions/agent/components/agent/safety/ApprovalCard.vuesrc/workbench/extensions/agent/components/agent/safety/ConflictDialog.vuesrc/workbench/extensions/agent/components/agent/safety/LockBanner.vuesrc/workbench/extensions/agent/components/agent/safety/RevertButton.vuesrc/workbench/extensions/agent/components/agent/safety/safetyTypes.tssrc/workbench/extensions/agent/components/ui/Avatar.vuesrc/workbench/extensions/agent/components/ui/Button.vuesrc/workbench/extensions/agent/components/ui/CollapsibleContent.vuesrc/workbench/extensions/agent/components/ui/DialogContent.vuesrc/workbench/extensions/agent/components/ui/DropdownMenuContent.vuesrc/workbench/extensions/agent/components/ui/DropdownMenuItem.vuesrc/workbench/extensions/agent/components/ui/ScrollArea.vuesrc/workbench/extensions/agent/components/ui/Textarea.vuesrc/workbench/extensions/agent/components/ui/uiPrimitives.test.tssrc/workbench/extensions/agent/composables/agent/useAgentFeatureGate.test.tssrc/workbench/extensions/agent/composables/agent/useAgentFeatureGate.tssrc/workbench/extensions/agent/composables/agent/useAgentSession.test.tssrc/workbench/extensions/agent/composables/agent/useAgentSession.tssrc/workbench/extensions/agent/composables/agent/useAttachment.test.tssrc/workbench/extensions/agent/composables/agent/useAttachment.tssrc/workbench/extensions/agent/composables/agent/useCanvasContext.tssrc/workbench/extensions/agent/composables/agent/useCanvasSelection.test.tssrc/workbench/extensions/agent/composables/agent/useCanvasSelection.tssrc/workbench/extensions/agent/composables/agent/useCloudAssets.tssrc/workbench/extensions/agent/composables/agent/useComposer.tssrc/workbench/extensions/agent/composables/agent/useDraftCanvasApply.test.tssrc/workbench/extensions/agent/composables/agent/useDraftCanvasApply.tssrc/workbench/extensions/agent/composables/agent/useOnboarding.test.tssrc/workbench/extensions/agent/composables/agent/useOnboarding.tssrc/workbench/extensions/agent/schemas/__fixtures__/agent/rest-responses.jsonlsrc/workbench/extensions/agent/schemas/__fixtures__/agent/ws-turn-ask-run.jsonlsrc/workbench/extensions/agent/schemas/__fixtures__/agent/ws-turn-cancelled.jsonlsrc/workbench/extensions/agent/schemas/__fixtures__/agent/ws-turn-denied-run.jsonlsrc/workbench/extensions/agent/schemas/__fixtures__/agent/ws-turn-edit.jsonlsrc/workbench/extensions/agent/schemas/agentApiSchema.test.tssrc/workbench/extensions/agent/schemas/agentApiSchema.tssrc/workbench/extensions/agent/services/agent/agentEventSource.test.tssrc/workbench/extensions/agent/services/agent/agentEventSource.tssrc/workbench/extensions/agent/services/agent/agentEventTransport.test.tssrc/workbench/extensions/agent/services/agent/agentEventTransport.tssrc/workbench/extensions/agent/services/agent/agentMessageParts.tssrc/workbench/extensions/agent/services/agent/agentRestClient.test.tssrc/workbench/extensions/agent/services/agent/agentRestClient.tssrc/workbench/extensions/agent/stores/agent/agentChatHistoryStore.test.tssrc/workbench/extensions/agent/stores/agent/agentChatHistoryStore.tssrc/workbench/extensions/agent/stores/agent/agentConversationEntries.test.tssrc/workbench/extensions/agent/stores/agent/agentConversationStore.test.tssrc/workbench/extensions/agent/stores/agent/agentConversationStore.tssrc/workbench/extensions/agent/stores/agent/agentDraftStore.test.tssrc/workbench/extensions/agent/stores/agent/agentDraftStore.tssrc/workbench/extensions/agent/utils/agent/renderMarkdownToHtml.test.tssrc/workbench/extensions/agent/utils/agent/renderMarkdownToHtml.ts
Acceptance items from the approved In-App Agent design doc, plus PM-98 and the coverage-audit gaps: - suggested prompts carry the five exact approved labels - an Ask Comfy Agent top-bar button registers next to the feedback button, present only while the flag gate has the tab registered (same fail-closed source, reactive mirror ref); click toggles the sidebar tab - attach is wired end-to-end: paperclip opens a picker (image/video), files upload through rest.uploadImage via useAttachment (20MB guard, failure does not stage), staged chips carry the server ref into the message attachments; the STAGED marker drops from useAttachment - drafts bind to a session workflow: agentWorkflowBinding mints a server workflow (single-flight POST /api/workflows with the current canvas graph, tolerant id parse, degrade-to-unbound on any failure) and the root awaits ensure() before the first send so messages carry workflow_id and draft_patch events resolve against a bound draft; the FE has no host uuid surface today (path-keyed workflow classes, /api/workflows unused in src), so the binding mirrors the backend's own flow with a TODO to adopt the host id when one exists - a muted composer caption states the agent-writes/user-runs contract (copy pending design sign-off, one-line locale swap) - a scroll-to-latest pill floats over the conversation when scrolled up - history copy-as-markdown works for the active conversation via a pure transcript builder; other sessions toast the V0 limitation - message ratings capture app:agent_message_feedback through a typed telemetry method (types, registry dispatch, PostHog implementation), null vote forwarded as a retraction for the eval pipeline - coverage gaps closed: DST-boundary recency bucketing, history store upsert/remove/setActive behaviors, stopTurn network-error notice, direct useComposer suite; the session error counter moves off module scope into the instance; the dead null-tab rerender line becomes a real assertion Typecheck clean, full suite 12588 passed, oxlint, eslint, and knip green on these files. 27 tests added.
Three @cloud Playwright scenarios in browser_tests/tests/agent, using the comfyPageFixture + webSocketFixture merge and typed mocks copied from the live-captured wire fixtures: - fail-closed flag gate: no sidebar tab or top-bar button without the PostHog flag; both appear with it (flag seeded via posthog_config.bootstrap.featureFlags with flags disabled-fetch, so the gate resolves synchronously and deterministically) - a full streamed turn: composer submit posts the message, the 202 ack adopts the server message id, injected agent_thinking, tool-call, delta, and done frames render thinking, tool cards, and markdown (bold asserted as rendered strong) - the draft loop: the first send mints the session workflow (mocked POST /api/workflows), the bound draft_patch applies through validateComfyWorkflow into the canvas, asserted by node count via window.app.graph The spec sends before pushing draft frames because the draft store drops patches for an unbound workflow id, matching the binding order in AgentPanelRoot. Draft content is a minimal schema-valid v0.4 graph (the raw captured draft omits the top-level version field the validator requires first). Runs in the cloud project only (the extension is tree-shaken from non-cloud builds); listed 3/3 there and 0 in chromium. Browser typecheck, eslint, and format clean.
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/workbench/extensions/agent/composables/agent/useAgentSession.ts (1)
117-167: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winGuard
sendMessage()againstnewChat()resets.
IfnewChat()runs while the POST is in flight,stopTurn()can no-op becauseactiveTurnIdis still null. When the ack returns,sendMessage()still writesthreadIdRefand records the turn into the reset store, bringing the old message back into the new chat. Add an epoch/generation check before applying the ack.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/workbench/extensions/agent/composables/agent/useAgentSession.ts` around lines 117 - 167, sendMessage() is still applying a late POST ack after newChat()/reset can clear the store, which lets an old turn reappear in a fresh chat. Add a request epoch/generation guard in useAgentSession’s sendMessage flow: capture the current session/generation before rest.postMessage, and after the await only update threadIdRef, recordUser, and startTurn if the generation still matches. If it changed, ignore the ack and avoid mutating the reset conversationStore.
♻️ Duplicate comments (1)
src/workbench/extensions/agent/AgentPanelRoot.vue (1)
96-104: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winDraft-to-canvas apply pipeline still has no error handling.
This block was flagged in an earlier review round for the exact same gap and remains unchanged: if
validateComfyWorkflowthrows orapp.loadGraphDatarejects, the failure becomes an unhandled promise rejection with no logging or user feedback — the draft silently fails to apply. This is explicitly called out as the PR's review focus for the draft pipeline.🛡️ Proposed fix
useDraftCanvasApply((content) => { void (async () => { - const workflow = await validateComfyWorkflow(content) - if (workflow) await app.loadGraphData(workflow) + try { + const workflow = await validateComfyWorkflow(content) + if (workflow) await app.loadGraphData(workflow) + } catch (error) { + console.error('[agent-panel] failed to apply draft to canvas', error) + toast.add({ severity: 'error', summary: t('agent.draftApplyFailed') }) + } + })() })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/workbench/extensions/agent/AgentPanelRoot.vue` around lines 96 - 104, The draft-to-canvas apply flow in useDraftCanvasApply currently has no failure handling, so errors from validateComfyWorkflow or app.loadGraphData can become unhandled rejections. Wrap the async body in AgentPanelRoot.vue with explicit error handling, and use the existing workflow-apply path to log/report the failure so the user gets feedback when the draft cannot be applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@browser_tests/tests/agent/agentPanelMocks.ts`:
- Around line 178-247: The generic boot mock payloads in mockAgentBoot are
untyped ad-hoc objects, so response-shape drift for routes like /api/users,
/api/settings, /api/workspaces, and similar won’t be caught at compile time.
Tighten these fulfill calls by introducing or reusing explicit response types
for the boot mocks, keeping jsonRoute for already typed agent payloads and
wiring the surrounding route handlers to those typed shapes so the mock contract
stays aligned with the real API.
In `@src/workbench/extensions/agent/components/agent/ConversationView.test.ts`:
- Around line 152-153: The test setup in ConversationView.test.ts is mutating
Element.prototype.scrollIntoView directly, which bypasses Vitest cleanup.
Replace the direct prototype assignment with vi.spyOn on
Element.prototype.scrollIntoView so the mock is tracked and restored by the
existing vi.restoreAllMocks calls in this suite. Keep the change localized to
the scrollIntoView setup used by the ConversationView tests.
In `@src/workbench/extensions/agent/components/agent/ConversationView.vue`:
- Around line 36-44: The watch in ConversationView.vue is deep-watching the
entire entries array, which makes each streaming token update re-traverse the
full conversation tree. Update the watcher around entries/scrollToLatest to use
a narrower dependency instead of { deep: true }, such as a cheap signal derived
from entries.length and the last entry’s text, so only the changed message
drives auto-scroll updates.
In `@src/workbench/extensions/agent/composables/agent/useAttachment.test.ts`:
- Around line 45-54: Add a mixed-batch test in useAttachment.test.ts for
useAttachment/addFiles that passes both a successful file and a failing file in
one call, so the existing single-file failure case is complemented by the
“already-staged files are not lost” behavior. Verify that the good file is still
included in the staged result, that the rejected upload still triggers onError,
and that addFiles continues processing the batch rather than dropping previously
staged entries.
In `@src/workbench/extensions/agent/README.md`:
- Line 3: The opening line in README.md is a sentence fragment and needs a main
verb. Update the first sentence in the In-App Agent side panel description so it
reads as a complete sentence, keeping the same meaning while fixing the grammar.
In `@src/workbench/extensions/agent/services/agent/agentEventSource.ts`:
- Around line 69-131: Extract the duplicated reconnect-tracking flow in
createReconnectingEventSource into a shared helper used by both subscribe and
onStatus. Factor out the common attach/detach,
host.addEventListener('reconnected', ...) registration, and cleanup logic so the
only difference remains the socket event bindings (message vs open/close). Keep
the helper centered around the existing symbols createReconnectingEventSource,
subscribe, and onStatus so future reconnect behavior stays consistent in both
paths.
---
Outside diff comments:
In `@src/workbench/extensions/agent/composables/agent/useAgentSession.ts`:
- Around line 117-167: sendMessage() is still applying a late POST ack after
newChat()/reset can clear the store, which lets an old turn reappear in a fresh
chat. Add a request epoch/generation guard in useAgentSession’s sendMessage
flow: capture the current session/generation before rest.postMessage, and after
the await only update threadIdRef, recordUser, and startTurn if the generation
still matches. If it changed, ignore the ack and avoid mutating the reset
conversationStore.
---
Duplicate comments:
In `@src/workbench/extensions/agent/AgentPanelRoot.vue`:
- Around line 96-104: The draft-to-canvas apply flow in useDraftCanvasApply
currently has no failure handling, so errors from validateComfyWorkflow or
app.loadGraphData can become unhandled rejections. Wrap the async body in
AgentPanelRoot.vue with explicit error handling, and use the existing
workflow-apply path to log/report the failure so the user gets feedback when the
draft cannot be applied.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 7269e7c6-9ceb-4ebe-ac4e-be2908263730
📒 Files selected for processing (33)
browser_tests/tests/agent/agentPanel.spec.tsbrowser_tests/tests/agent/agentPanelMocks.tssrc/extensions/core/agentPanel.test.tssrc/extensions/core/agentPanel.tssrc/locales/en/main.jsonsrc/platform/telemetry/TelemetryRegistry.tssrc/platform/telemetry/providers/cloud/PostHogTelemetryProvider.tssrc/platform/telemetry/types.tssrc/workbench/extensions/agent/AgentPanelRoot.test.tssrc/workbench/extensions/agent/AgentPanelRoot.vuesrc/workbench/extensions/agent/README.mdsrc/workbench/extensions/agent/components/agent/ActiveTabStrip.vuesrc/workbench/extensions/agent/components/agent/AgentPanel.vuesrc/workbench/extensions/agent/components/agent/Composer.test.tssrc/workbench/extensions/agent/components/agent/Composer.vuesrc/workbench/extensions/agent/components/agent/ConversationView.test.tssrc/workbench/extensions/agent/components/agent/ConversationView.vuesrc/workbench/extensions/agent/components/agent/EmptyState.vuesrc/workbench/extensions/agent/components/agent/composer/AssetTray.vuesrc/workbench/extensions/agent/components/agent/composer/SelectionActionChips.vuesrc/workbench/extensions/agent/components/agent/m4HostPieces.test.tssrc/workbench/extensions/agent/composables/agent/useAgentSession.test.tssrc/workbench/extensions/agent/composables/agent/useAgentSession.tssrc/workbench/extensions/agent/composables/agent/useAttachment.test.tssrc/workbench/extensions/agent/composables/agent/useCanvasSelection.tssrc/workbench/extensions/agent/composables/agent/useComposer.test.tssrc/workbench/extensions/agent/services/agent/agentEventSource.test.tssrc/workbench/extensions/agent/services/agent/agentEventSource.tssrc/workbench/extensions/agent/services/agent/agentTranscript.test.tssrc/workbench/extensions/agent/services/agent/agentTranscript.tssrc/workbench/extensions/agent/services/agent/agentWorkflowBinding.test.tssrc/workbench/extensions/agent/services/agent/agentWorkflowBinding.tssrc/workbench/extensions/agent/stores/agent/agentChatHistoryStore.test.ts
💤 Files with no reviewable changes (1)
- src/workbench/extensions/agent/components/agent/EmptyState.vue
Findings from a four-hat panel, a QA-coverage gate, and three fail-fast gates, each fixed with its test: - Button.vue uses the existing cva (object API) instead of the branch's class-variance-authority add, matching the rest of src; the root drops that dependency (apps/website keeps its own pre-existing use). shiki stays as the genuinely new dep for CodeBlock highlighting. - copy-as-markdown now works: the active conversation registers as the one current history row, so the drawer is not empty mid-chat and the copy action has a target. Past sessions remain a documented V0 limit. - onSend warns once via a toast when the session workflow cannot be minted, so the draft loop no longer fails silently while the send still proceeds unbound. - the cloud e2e spec sources its suggested-prompt assertion from the bundled locale, so it cannot drift from the rendered prompts again. - the thread id moves into the conversation store so it survives a panel remount (a sidebar toggle unmounts the panel), and the in-flight turn is cancelled on unmount; together these stop a reopened panel from splitting the transcript across two threads or leaving a turn billing unheard. - remove dead surface: createWebSocketEventSource, the workflow binding's unused reset() and workflowName knob, an unused apply version parameter, four unreferenced i18n keys, and a stale comment. Typecheck, browser typecheck, oxlint, eslint, and knip are clean; the unit suite is 1163 passing.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/workbench/extensions/agent/AgentPanelRoot.vue (1)
96-101: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winDraft-to-canvas apply still has no error handling.
If
validateComfyWorkflowthrows orapp.loadGraphDatarejects, this becomes an unhandled promise rejection with the draft silently failing to apply. This exact gap was flagged in a prior review round on these lines and does not appear to have been addressed (unlike the sibling comments on this PR, which are marked resolved).🛡️ Proposed fix
useDraftCanvasApply((content) => { void (async () => { - const workflow = await validateComfyWorkflow(content) - if (workflow) await app.loadGraphData(workflow) + try { + const workflow = await validateComfyWorkflow(content) + if (workflow) await app.loadGraphData(workflow) + } catch (error) { + console.error('[agent-panel] failed to apply draft to canvas', error) + } })() })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/workbench/extensions/agent/AgentPanelRoot.vue` around lines 96 - 101, The draft-to-canvas apply flow in AgentPanelRoot’s useDraftCanvasApply callback still launches an unhandled async path, so wrap the validateComfyWorkflow/app.loadGraphData sequence in proper try/catch handling and surface failures instead of letting the promise reject silently. Update the async callback so both validateComfyWorkflow and app.loadGraphData failures are caught, then log or report the error through the existing panel/error handling path used in AgentPanelRoot.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/workbench/extensions/agent/AgentPanelRoot.vue`:
- Around line 104-111: The unmount cleanup in AgentPanelRoot.vue calls stop()
immediately after firing stopTurn(), which can unsubscribe the websocket before
the cancel flow receives agent_message_done and clears activeTurnId/streaming
state. Update the onBeforeUnmount cleanup to await the stopTurn() promise before
invoking stop(), so the turn can settle cleanly before the websocket is torn
down.
---
Duplicate comments:
In `@src/workbench/extensions/agent/AgentPanelRoot.vue`:
- Around line 96-101: The draft-to-canvas apply flow in AgentPanelRoot’s
useDraftCanvasApply callback still launches an unhandled async path, so wrap the
validateComfyWorkflow/app.loadGraphData sequence in proper try/catch handling
and surface failures instead of letting the promise reject silently. Update the
async callback so both validateComfyWorkflow and app.loadGraphData failures are
caught, then log or report the error through the existing panel/error handling
path used in AgentPanelRoot.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1bff3088-97a6-4428-ba69-83d0aaa6eee5
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (17)
browser_tests/tests/agent/agentPanel.spec.tspackage.jsonsrc/locales/en/main.jsonsrc/workbench/extensions/agent/AgentPanelRoot.test.tssrc/workbench/extensions/agent/AgentPanelRoot.vuesrc/workbench/extensions/agent/components/agent/AgentPanel.vuesrc/workbench/extensions/agent/components/ui/Button.vuesrc/workbench/extensions/agent/composables/agent/useAgentSession.test.tssrc/workbench/extensions/agent/composables/agent/useAgentSession.tssrc/workbench/extensions/agent/composables/agent/useDraftCanvasApply.test.tssrc/workbench/extensions/agent/composables/agent/useDraftCanvasApply.tssrc/workbench/extensions/agent/services/agent/agentEventSource.test.tssrc/workbench/extensions/agent/services/agent/agentEventSource.tssrc/workbench/extensions/agent/services/agent/agentWorkflowBinding.test.tssrc/workbench/extensions/agent/services/agent/agentWorkflowBinding.tssrc/workbench/extensions/agent/stores/agent/agentConversationStore.test.tssrc/workbench/extensions/agent/stores/agent/agentConversationStore.ts
💤 Files with no reviewable changes (2)
- package.json
- src/workbench/extensions/agent/components/agent/AgentPanel.vue
The server owns the workflow: it creates one when a message opens a thread and returns its id in the 202 ack (confirmed by the live-captured wire fixtures, where every postMessage ack carries the same workflow_id). The panel previously minted its own session workflow via POST /api/workflows before the first send, which was redundant and carried a latent bug: resuming a persisted thread would mint a fresh workflow whose id never matches the thread's real one, so every draft_patch would be foreign-dropped and the canvas would never update. Now sendMessage posts without a workflow id and binds the draft store to ack.workflow_id (bind is idempotent for an unchanged id, so re-binding per ack never wipes an in-progress draft). The whole minting subsystem goes away: agentWorkflowBinding and its tests, the ensure-before-send in the root, the mint-failure toast and its i18n key, and the /api/workflows route mock in the e2e mocks. start() no longer baselines the draft; resync still fires on reconnect and behind-heartbeats, both of which no-op until a send has bound a workflow, matching the previous effective behavior. zAgentTurnAccepted declares the optional workflow_id it always carried through passthrough. Session and root tests rebind via the ack; the root draft-binding test proves ack id -> draft_patch -> loadGraphData and that a foreign-workflow patch is ignored. Net -248 lines. Typecheck, browser typecheck, oxlint, eslint, and knip clean; 1158 unit tests pass.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/workbench/extensions/agent/composables/agent/useAgentSession.ts (1)
181-188: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winReset the draft store in
newChat()conversationStore.reset()leaves the previous workflow bound, so a latedraft_patchor heartbeat from the abandoned chat can still be applied to the canvas. AdddraftStore.reset()here too.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/workbench/extensions/agent/composables/agent/useAgentSession.ts` around lines 181 - 188, `newChat()` currently resets only `conversationStore`, which leaves the previous workflow state active and can let late `draft_patch` or heartbeat updates affect the canvas. Update `newChat()` in `useAgentSession` to also call `draftStore.reset()` alongside `conversationStore.reset()` after `stopTurn()`, so a new chat starts with both conversation and draft state cleared.
♻️ Duplicate comments (2)
src/workbench/extensions/agent/AgentPanelRoot.vue (2)
82-90: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winStill missing error handling around the draft-to-canvas apply pipeline.
If
validateComfyWorkflowthrows orapp.loadGraphDatarejects, this becomes an unhandled promise rejection with no logging or user feedback — this was already flagged in a prior review round and remains unaddressed in this diff.🛡️ Proposed fix
useDraftCanvasApply((content) => { void (async () => { - const workflow = await validateComfyWorkflow(content) - if (workflow) await app.loadGraphData(workflow) + try { + const workflow = await validateComfyWorkflow(content) + if (workflow) await app.loadGraphData(workflow) + } catch (error) { + console.error('[agent-panel] failed to apply draft to canvas', error) + } })() })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/workbench/extensions/agent/AgentPanelRoot.vue` around lines 82 - 90, The draft-to-canvas apply flow in AgentPanelRoot.vue still lacks error handling around the async pipeline. Wrap the logic inside useDraftCanvasApply with try/catch so failures from validateComfyWorkflow or app.loadGraphData are caught, then log the error and surface an appropriate user-facing failure state or notification instead of allowing an unhandled promise rejection.
92-100: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winWebsocket unsubscribe still races the cancel flow on unmount.
stop()runs synchronously right after firingvoid stopTurn(), so the socket can be torn down before the terminalagent_message_donefor the cancelled turn arrives. Since the conversation store survives unmounts, reopening the panel can findactiveTurnId/streaming stuck. Already flagged previously and still present unchanged.🛡️ Proposed fix
-onBeforeUnmount(() => { +onBeforeUnmount(async () => { // ... - void stopTurn() - stop() + await stopTurn() + stop() })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/workbench/extensions/agent/AgentPanelRoot.vue` around lines 92 - 100, The unmount cleanup in AgentPanelRoot.vue is still tearing down the websocket too early because onBeforeUnmount calls stopTurn() without waiting and then immediately calls stop(), so the final agent_message_done can be missed and activeTurnId may stay stuck. Update the unmount flow to ensure the cancel/terminal event path completes before unsubscribing the socket, either by awaiting the stopTurn() completion or by moving stop() into a post-cancel completion path. Keep the fix anchored around onBeforeUnmount, stopTurn, and stop so the conversation store can settle cleanly before the component fully unmounts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/workbench/extensions/agent/AgentPanelRoot.vue`:
- Around line 162-167: The send flow in onSend currently ignores the boolean
result from sendMessage(), which prevents failed sends from restoring the
composer state. Update onSend to forward or return the sendMessage() result so
useComposer.submit() can detect false and keep the draft and attachments intact,
and ensure the handling around sendMessage and submit preserves the composer
contents on failure.
---
Outside diff comments:
In `@src/workbench/extensions/agent/composables/agent/useAgentSession.ts`:
- Around line 181-188: `newChat()` currently resets only `conversationStore`,
which leaves the previous workflow state active and can let late `draft_patch`
or heartbeat updates affect the canvas. Update `newChat()` in `useAgentSession`
to also call `draftStore.reset()` alongside `conversationStore.reset()` after
`stopTurn()`, so a new chat starts with both conversation and draft state
cleared.
---
Duplicate comments:
In `@src/workbench/extensions/agent/AgentPanelRoot.vue`:
- Around line 82-90: The draft-to-canvas apply flow in AgentPanelRoot.vue still
lacks error handling around the async pipeline. Wrap the logic inside
useDraftCanvasApply with try/catch so failures from validateComfyWorkflow or
app.loadGraphData are caught, then log the error and surface an appropriate
user-facing failure state or notification instead of allowing an unhandled
promise rejection.
- Around line 92-100: The unmount cleanup in AgentPanelRoot.vue is still tearing
down the websocket too early because onBeforeUnmount calls stopTurn() without
waiting and then immediately calls stop(), so the final agent_message_done can
be missed and activeTurnId may stay stuck. Update the unmount flow to ensure the
cancel/terminal event path completes before unsubscribing the socket, either by
awaiting the stopTurn() completion or by moving stop() into a post-cancel
completion path. Keep the fix anchored around onBeforeUnmount, stopTurn, and
stop so the conversation store can settle cleanly before the component fully
unmounts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 56b2b4f5-3c68-4f3e-9acb-2f70236bdaf0
📒 Files selected for processing (8)
browser_tests/tests/agent/agentPanel.spec.tsbrowser_tests/tests/agent/agentPanelMocks.tssrc/locales/en/main.jsonsrc/workbench/extensions/agent/AgentPanelRoot.test.tssrc/workbench/extensions/agent/AgentPanelRoot.vuesrc/workbench/extensions/agent/composables/agent/useAgentSession.test.tssrc/workbench/extensions/agent/composables/agent/useAgentSession.tssrc/workbench/extensions/agent/schemas/agentApiSchema.ts
From a refresh of the V0 design spec (Notion "In-App Agent V0 - Design &
Requirements", Section A + B5) against the shipped panel:
- Branding: the design pins "the original Comfy mark" everywhere; the
panel used a lucide sparkles placeholder. Swap to the square Comfy
mark (icon-[comfy--comfy-c]) on all four surfaces - the sidebar tab,
the top-bar "Ask Comfy Agent" button, the panel header title row, and
the empty-state mark.
- Empty state: B5 requires a centered Comfy mark with a glow above the
greeting; it was missing. Add it (accent-tinted, currentColor glow).
- Greeting: B5/Section A want the account first name ("Hello Jo,"); the
panel rendered "Hello there," because the name was never wired. Pull
the first name from useCurrentUser and pass it through to the greeting,
falling back to the existing neutral default when there is no name.
Tests: assert the Comfy mark on both the tab registration and the top-bar
button; add a greeting-personalization test; loosen the e2e greeting
assertion to the stable "Hello" prefix so it survives personalization.
Typecheck, browser typecheck, oxlint, eslint, and knip clean; 1159 unit
tests pass.
… canvas Design B2 wants the panel to push the canvas and be drag-resizable (the ~30% / ~50% docks). The host sidebar already provides both - it is a splitter panel (LiteGraphCanvasSplitterOverlay) that pushes the canvas and drag-resizes, the same as the Assets panel. The agent panel was fighting that by capping its own content width (max-w-md / max-w-2xl, mx-auto): dragging the sidebar wider only added gutters instead of widening the panel, and the Medium/Large toggle changed the cap rather than the dock. Remove the self-imposed width cap so the panel fills the resizable sidebar, and drop the Medium/Large size toggle (the design struck the width-toggle from B3; the host's native drag-resize replaces it). The sizeMode prop drove nothing else - the empty-state layout never used it - so this is a net removal: the toggle button, its emit chain across AgentPanel and PanelHeader, the panelWidth computed, the now-unused cn import, and the orphaned expand/collapse locale keys all go. Typecheck, oxlint, eslint, and knip clean; 900 agent/core tests pass.
- Local dev visibility: the panel is gated on the PostHog flag agent-in-app-experience, but that flag lives in a cloud PostHog project the local dev build does not read, so the panel never appeared on localhost. Force the gate open when the mode is development, so the panel is visible on the dev server without wiring the flag; test and production builds still gate on it (fail-closed unchanged). - Empty-state overflow: the centered Comfy mark made the empty state taller than the panel at the narrow default width, and justify-end pushed the overflow up into the header (the greeting overlapped the title). Use a scroll container with mt-auto so the mark + greeting center when they fit and the whole thing scrolls when they do not, instead of overlapping the header. Flag-gate tests still pass with the mode guard; typecheck, oxlint, and eslint clean.
Bring the panel closer to the Figma design (it had been built to the
written requirements, not the frames):
- The Comfy mark renders in brand yellow (text-brand-yellow), not the
generic blue accent, in both the header and the centered empty-state
mark.
- The panel title is "Comfy Agent" (Figma) rather than "AI Agent" (the
written doc, which disagreed with the design).
- Suggested prompts are light rows with a leading icon instead of heavy
bordered cards, per Figma B5 ("leading icon + label"). The per-prompt
icons are best-fit lucide icons pending the exact Figma icon set.
Typecheck, oxlint, and eslint clean; agent and core tests pass.
Tailwind Preflight is disabled host-wide (PrimeVue + litegraph coexistence), so a raw <button> in the panel fell back to the browser default: a grey buttonface fill and a 2px outset border. That is what made the suggested- prompt rows, the header history/new-chat buttons, and the composer attach button look like heavy grey cards instead of the clean rows in the Figma frames. Re-establish Preflight's button normalization (background transparent, border 0 solid) in one @layer base rule scoped to #agent-panel-root and the teleported reka surfaces (dialogs, dropdowns, drawer now carry .agent-scope). The selector is wrapped in :where() so it stays at zero specificity and every button's own bg-* / border utilities still win, so filled/bordered buttons (the blue send button, bordered chips) render exactly as authored. Verified live: no grey buttonface or outset border remains on any panel button, and the send button keeps its accent background.
Against the finalized "In-App Agent V0 - Design & Requirements" doc and its Figma frames (fileKey G7ZjPEAFJB8cqb7halGEfa): - Empty state: the Comfy mark sits on a dark rounded tile; both greeting lines are bold; all five suggested prompts are pinned just above the composer (mark + greeting center above them) instead of overflowing, each a light row with its Figma leading icon (lightbulb, list, search, message-circle-question, workflow). - Composer bottom row (B6): add the @ (mention) button beside the paperclip, add the "Auto" model label, and make the send button white with a dark up-arrow when active / grey when empty (was always the blue accent). - Header (B3): ALPHA is a neutral outline badge (was blue-tinted), new chat uses the speech-bubble icon, and the history clock is removed. - Add the session bar (B4): a "New Chat" row under the header that opens Chat History (the history entry point the header clock used to be). - Rename the sidebar tab label from "AI Agent" to "Comfy Agent" to match the panel title and branding. Verified live against the Figma frames.
The Figma docks the agent on the right of the canvas (pushing the canvas
left) and opens it from the top-bar "Ask Comfy Agent" button — the left rail
keeps Assets/Nodes/etc. and has no agent icon. It was previously a left
sidebar tab.
- New agentPanelStore holds the panel's { enabled, isOpen }. agentPanel.ts no
longer registers a sidebar tab; the top-bar button toggles isOpen, and the
PostHog flag gate drives enabled (fail-closed: flag off hides the button and
closes the panel).
- GraphCanvas renders AgentPanelRoot in the host right-side-panel slot when the
panel is enabled + open (taking precedence over the node-properties panel),
and the splitter shows that offside panel while the agent is open, so the
canvas shrinks to the left. The panel's close button closes the store.
- Update the unit + e2e specs to open via the top-bar button.
Verified live: opens/closes on the right, canvas pushes left, left rail
intact, no agent icon in the rail. Typecheck + unit tests pass.
6e0e15d to
88cdef7
Compare
The thread id lived only in the in-memory store, so a reload always opened a
blank panel and the next send even split the transcript onto a new server
thread. Per the backend contract, GET /api/agent/threads/{id}/messages is the
page-load history endpoint (seq ascending; 404 = thread not found).
- Persist the thread id (localStorage) when a send's ack adopts it; clear it
on new chat.
- On session start with an empty store, restore the persisted thread and
hydrate its transcript through the new conversationStore.hydrate(): rows
pair by turn_id in seq order and arrive settled; a turn whose assistant row
is missing still renders its user prompt. A 404 forgets the stale id
silently; reopening the panel within the same page session leaves the live
conversation untouched.
- Tests: hydrate-on-start, stale-404 forget, persist-on-send/clear-on-new-
chat, no-clobber-on-reopen; the harness clears localStorage between tests.
Iterative style pass against the design prototype (visual reference only; all changes are tweaks to our own components): - Dock: the panel is now a full-viewport-height right column beside the tab bar (was a splitter percentage panel below it), 420px default <-> 960px maximized with a drag handle on its left edge; 8px inset wrapper with a rounded interface-stroke border. - Entry: the 'Ask Comfy Agent' button moves from the actionbar row into the workflow tab bar (plum-bordered ink pill, yellow Comfy-C, open state highlights); still flag-gated fail-closed. The extension is now gate-only. - Header: h-12/px-4, regular-weight title, neutral ALPHA pill (no semibold/tracking), icons message-circle-plus + maximize-2/minimize-2 + x with tooltips. - Session bar: compact h-6 rounded-sm pill (align-justify icon, truncated title, no chevron) instead of a full-width bordered row. - Empty state: ink-700 tile with plum-600 border and size-6 mark; greeting scales text-base -> text-2xl via container query; suggested prompts are rounded-full pill chips (size-3 icons) that wrap centered at >=460px; 4th prompt icon corrected to message-circle-warning. - Composer: rounded-2xl container owning all padding, focus-within border brighten, min-h-20 textarea (px-4 py-3), toolbar px-3 py-2, Auto gets its chevron, send is rounded-xl light-on-dark with opacity-50 disabled state; placeholder is now 'Ask Comfy Agent…'. - Footer: p-4 with a mx-auto max-w-[640px] column and my-0 caption. - Tokens: agent-* aliases retuned to the reference's host tokens (base-background surface, secondary-background raised/hover, muted-foreground for all secondary text, component-node-border dividers). Running mismatch list: temp/plans/fe-1187-figma-acceptance.md
Computed-style diff of our conversation vs the design reference (prototype); re-probed live after the fix for an exact match on the user bubble. - User message: right-aligned pill, no avatar (dropped the Avatar and the name/userName props that fed it) — w-fit rounded-lg bg-agent-surface-raised px-4 py-3 text-xs (was rounded-agent bg-agent-pill max-w-sm px-3 py-2 text-sm with an avatar; bubble was 14px/#303036/radius12, now 12px/rgb(38,39,41)/radius8/py-3 px-4 to match). - Markdown prose: h1 text-2xl/600, h2 text-base/600, p 14/1.625, decimal/disc lists, accent underlined links, 3px muted blockquote, bordered secondary-surface tables (were entirely missing), bordered inline code. - Tool-call summary row: wrench + 'Ran N…' summary + right chevron on a borderless h-8 rounded-md muted row (was a bordered card with a left chevron); expanded steps render as a plain gap list. - Conversation column: mx-auto max-w-[640px] p-4. Verified live on localhost against the prototype. Typecheck clean; agent tests 177/177.
Three reported bugs: - History did nothing: it was a reka Dialog teleported to <body> that opened behind the docked panel's z-999 stacking context, so it never appeared. Replace it with an in-panel Chat History screen (new ChatHistoryScreen) that the session bar swaps in over the conversation, with a back arrow; picking a chat or starting a new one returns. No teleport, no stacking fight. Deleted ChatHistoryDrawer + its DropdownMenu* deps. - No way to make a new chat: the header new-chat button opened the out-of-V0-scope 'starting point' onboarding modal instead of resetting. It now just clears to the empty state; deleted StartingPointModal. - Textarea border wrong: the panel-scoped Preflight reset in agentTheme.css covered <button> but not <textarea>/<input>/<select>, so the textarea kept its UA border/font inside the composer's own border (double border). Extend the reset to those elements (border 0, transparent bg, inherit font). Verified live: session bar -> in-panel history -> back; new-chat -> empty state; textarea border now 0 (single border on the composer container). Typecheck/knip clean; agent tests 177/177.
Kishore shipped the list-threads endpoint, so Chat History is no longer a single local 'current' row. - New listThreads() on the REST client + a tolerant zAgentThreads schema (every field optional + passthrough): the endpoint shipped after the openapi extract, so the client normalizes id / title / timestamp across the likely spellings and a shape drift degrades to a best-effort row rather than a hard zod failure that would blank the list. - useAgentSession gains listThreads() and loadThread(id): loadThread cancels any in-flight turn, adopts + persists the thread id, and hydrates its transcript (reusing the B17 resume path; a stale id 404s and is forgotten). - AgentPanelRoot fetches the list on mount and each time history opens (best-effort: a failed fetch keeps the last-known list), maps threads to ChatSession rows, tracks the active row off the live threadId, and loads a picked chat via loadThread. Removed the old local 'current row' mirror. - History store gains replaceAll() for the authoritative server list. Verified live: the panel calls GET /api/agent/threads and, because the endpoint 404s on the pr-4432 preview env (Kishore's build not deployed there yet), the history screen shows 'No conversations yet' with no crash — the graceful-degradation path. Real rows will populate on an env with the endpoint; the field mapping should be reconfirmed against the live shape. Typecheck/knip clean; agent tests 180/180 (added loadThread + listThreads + server-history-population coverage).
- ThinkingStatus: brain icon and a shimmer on the "Thinking…" label (new agent-shimmer keyframes; static muted fallback under prefers-reduced-motion and .disable-animations) - CodeBlock: bordered chrome; header gains a file-code icon, the language, and a bordered copy button; mono body - MessageFeedback: gap-0.5 row, size-6 buttons, size-3.5 icons - Composer: send button shows a loader-circle spinner while the turn is thinking, via a new submitting prop threaded AgentPanelRoot -> AgentPanel -> Composer
The server-side draft never mirrors the live canvas (it starts empty even for saved workflows), so @-tagged node_ids resolved to nothing and the agent replied that the canvas was empty. Each tagged node now rides the POST with its serialized definition from the graph the canvas is showing (root or an open subgraph), plus a context note that claims inline definitions only for tags that resolved, names the gap when some did not, and nudges a save when the tab has no server-side workflow.
postMessage now carries the serialized active graph when it changed since the last upload (2MB cap, re-upload after failed sends or thread switches), per the agreed cloud contract; current servers ignore the field. The ack binds the uploaded tab to the thread's workflow id, so agent patches apply in place on the user's own tab. Empty drafts park everywhere, and the conflict dialog still guards modified tabs.
REST failures (e.g. Bad Gateway on hydrate), malformed events, and upload failures rendered as bespoke toasts floating over the docked panel. They now share the one host error modal already used for draft-apply failures; warnings/info stay transient toasts.
Tags send bare node_ids again; they resolve against the full graph the same POST already uploads (workflow.content) once the server seeds the draft from it. No client-side stopgap.
Rename the WorkflowUpload fields so the wire contract reads plainly: content -> graph (the prompt is already the top-level content field), and base_version -> last_seen_version (the draft version the client last received; draft_version would have collided with the existing WS event type of that name).
… action bar Draft applies now re-baseline the bound tab's change tracker to the canvas as loaded and clear isModified, on both the in-place and minted-new-tab paths. A minted tab's stored baseline carries an ensureWorkflowId id the canvas never adopts, so the first capture flipped isModified and every following draft_patch raised the conflict dialog with zero user edits. Manual edits still re-arm the dialog. The thumbs up/down + copy row under a completed agent message no longer hides until hover (V0: always display).
Adds the V0 FE metric set through the existing telemetry layer (typed events, optional provider methods, cloud PostHog provider): - app:agent_panel_opened / app:agent_panel_closed with source (topbar_button | close_button | flag_disabled) and open_duration_ms, so open-vs-closed share per session is computable - app:agent_entry_button_clicked (resulting_state) and app:agent_close_button_clicked for the raw clicks - app:agent_message_sent (attachment_count, node_tag_count), app:agent_node_tagged (mention_picker), app:agent_attach_button_clicked - app:agent_workflow_applied (workflow_id, new_tab | existing_tab) for agent-created vs agent-edited workflow attribution - app:agent_message_feedback now carries workflow_id (PM-98) The panel store owns open/close state, so it emits the state events with an idempotency guard (the flag gate re-syncs close on every evaluation).
…t regressions - MarkdownStream renders through the shared markdownRendererUtil; the agent copy (and its module-load DOMPurify hook that gave every host anchor target=_blank) is deleted. - The agent event source is now a thin adapter over the api singleton's typed custom-event dispatch: no second socket follower, no re-parse of every /ws frame, no "Unknown message type" throw noise. Zod payload validation and the malformed-event notice stay. - AgentPanelRoot loads via defineAsyncComponent, keeping the flag-gated subtree out of the GraphCanvas chunk. - Prompt-error cards show details only for the agent's own error types; host cards restored to pre-PR behavior, both pinned by tests.
- The agent REST client rides api.fetchApi (Firebase init-wait, auth headers, 401 remint, Comfy-User) instead of a hand-rolled fetch with workspace-token-only auth; zod parsing and AgentApiError stay. - Agent-local Button/Textarea forks replaced by the shared UI kit with variant mappings verified pixel-equivalent through tailwind-merge; dialogs inline reka primitives (the shared kit's entrance animations and sizing cannot be neutralized). ScrollArea/CollapsibleContent wrappers inlined per the sibling idiom. - WorkflowTabs CTA uses the shared Button. - agentTheme.css no longer redefines tw-animate-css collapsible keyframes (they globally retargeted three sharing-dialog consumers). - CodeBlock keeps the framed header + copy but renders plain monospace; the shiki dependency and catalog entry are removed. MarkdownStream tokenizes fences via marked.lexer, fixing the 4-backtick gap.
- Safety UI that nothing drives (ApprovalCard, LockBanner, RevertButton and their AgentPanel plumbing); ConflictDialog stays. - Dead message-part clusters: file/asset rendering and reasoning disclosure that no transport path constructs; the parts union and AgentMessage grouping shrink to text/tool/notice. - Unwired composer pieces (AssetTray, SelectionActionChips, useCloudAssets) with zero non-test importers. - Dead exports (createThread, history.upsert, useAgentFeatureGate fn, hasDraft, onboarding multi-step machinery) - each re-verified for zero production consumers on the current head; parseAgentWsEvent / isAgentEvent kept, they are live imports since the event rework. - Write-only token pipeline, info/warning notice machinery, and the transport's no-op finalize loop removed; finalize/abort merged into one settle().
- 17 locale keys with zero usages removed from en (other locales are lobe-i18n generated and never carried them); error-catalog and design-locked keys kept. - useAttachment strings go through i18n; the 20MB label derives from the named constant (max_upload_size flag exists but has no production consumer, so the constant stays). - A failed thread-list fetch now raises the shared error modal instead of silently showing empty history, with a regression test.
Adds the V0 FE metric set through the existing telemetry layer (typed events, optional provider methods, cloud PostHog provider): - app:agent_panel_opened / app:agent_panel_closed with source (topbar_button | close_button | flag_disabled) and open_duration_ms, so open-vs-closed share per session is computable - app:agent_entry_button_clicked (resulting_state) and app:agent_close_button_clicked for the raw clicks - app:agent_message_sent (attachment_count, node_tag_count), app:agent_node_tagged (mention_picker), app:agent_attach_button_clicked - app:agent_workflow_applied (workflow_id, new_tab | existing_tab) for agent-created vs agent-edited workflow attribution - app:agent_message_feedback now carries workflow_id (PM-98) The panel store owns open/close state, so it emits the state events with an idempotency guard (the flag gate re-syncs close on every evaluation). The entry/close click events are kept alongside the state events deliberately: the V0 metrics checklist names them as PM-queryable trackers, though the state events' sources make them derivable. Also hardens autosaveAppliedDraft: the tracker re-baselines with the RAW canvas serialization. A schema-validated baseline would diff against raw captures under strict graphEqual and re-raise the conflict dialog on an unedited canvas; a test pins validateComfyWorkflow to its single loadDraft call so the baseline can never route through the schema.
The trim deleted the collapsible keyframes as duplicates of tw-animate-css, but that package defines no collapsible-* keys, so the tool-disclosure animation silently no-opd. Restored under agent-* names: the globally-imported sheet can no longer retarget the host's animate-collapsible-* utilities (undefined on main) in sharing dialogs. Adds the three review-flagged test gaps: indented code stays prose, bare fences label as text, and the oversize-attachment message pins its i18n interpolation.
- ToolCallGroup: streaming auto-open, same-name merge, failure reopen, and the AgentMessage adjacent-merge branch with pluralized header. - WorkflowTabs: the Ask Comfy Agent CTA renders when enabled and toggles the panel (mock rewired ref-backed for storeToRefs). - useAgentSession: non-object and foreign host frames drop silently mid-turn. - MarkdownStream: fence label picks the first info-string word.
autosaveAppliedDraft re-baselines with the RAW canvas serialization: a schema-validated baseline diffs against raw captures under strict graphEqual and re-raises the conflict dialog on an unedited canvas. A test pins validateComfyWorkflow to its single loadDraft call so the baseline can never route through the schema. Coverage hardening from review: entry-click resulting_state both ways, close-button click attribution through the real store, message_sent payloads (attachments and node tags), mention-pick positive plus stages-nothing negative, null open_duration_ms, flag_disabled pin.
Merges the reviewed trim (-1,189 net lines, 19 fewer files, shiki dropped, four host regressions fixed, zero functionality loss; validated by a full review pipeline) into the PR branch as a single revertible merge. Conflict resolutions favor the amended stable side where it superseded the trim: raw-serialization autosave baseline (schema-normalized baselines re-flip isModified via graphEqual), dedupe-gated mention telemetry, the richer WorkflowTabs CTA tests, and the validate-once assertion; the trim's ws event fake wins in the Root tests.
…el-fe-implementation' into nathaniel/fe-1187-agent-side-panel-fe-implementation
The client hardcoded person_profiles: 'identified_only' AFTER the posthog_config spread, silently overriding the server's explicit 'always' (live on /api/features). Under identified_only every event captured before identify() resolves is a person-less anonymous event that never attaches to the user, so early-session events (entry click, panel opened, first message) vanish from person-scoped PostHog views. The hardcode is now a default the server can override; before_send stays locked (PII stripping). Reverses the person_profiles half of the pin added in #12479, test updated accordingly. Also retries the posthog-js dynamic import once: a stale deploy can 404 the chunk, which previously disabled telemetry for the entire session after a single console.error.
The module map caches a failed module resolution, so re-importing the same specifier rejects from cache without refetching; the retry could never succeed. Stale-chunk failures are already handled app-wide by the vite:preloadError listener in App.vue.
The 2MB cap guarded a server limit that does not exist yet and silently skipped uploads for large graphs, which are normal on a video/image platform. The server owns its own limits.
The backend now seeds the thread's draft from the graph the client
sends each turn. Align to its shape and rules:
- The turn POST carries draft: { content: <serialize() save format>,
version: <last seen draft version | null> } (was the inert
workflow/graph/last_seen_version shape).
- Snapshots gate on canvas-non-empty, not workflow_id, so templates,
unsaved tabs, and new tabs seed correctly; skipping an unchanged
canvas stays as the server-blessed optimization.
- A 409 {error, version} means the agent's draft advanced: adopt the
returned version and retry the send once.
- autosaveAppliedDraft only reset the change tracker and isModified, but the tab dot renders whenever a workflow is not persisted, so agent-applied drafts always showed unsaved and lived only in memory - save through workflowService instead: temporary tabs use a silent saveWorkflowAs with a collision-safe filename (no prompt or overwrite dialog), persisted tabs re-save silently - rebind workflow_id to the tab current path in finally so service renames (collision bump, app extension mismatch) and failed saves never strand the binding on a dead path - known limit: host saveWorkflow drift handling can rarely toast or confirm on persisted tabs; reusing the canonical save path there is intentional - tests: failure isolation keeps the applied draft and surfaces no error, an app-mode collision bumps the filename and rebinds, a save that fails after rename still rebinds; binding tests now stub a valid empty threads page
Per the per-file consolidation audit (temp/plans/pr13472-file-consolidation-audit.md): safetyTypes -> ConflictDialog (SFC type export, ActiveTabStrip pattern), agentTranscript + useDraftCanvasApply -> AgentPanelRoot, ThinkingStatus -> AgentMessage, SuggestedPromptChip -> EmptyState, SelectionTagChip -> Composer, SessionBar -> AgentPanel. All moves byte-identical; net -9 files. agentTranscript's test is replaced by a stronger end-to-end copy test asserting the exact clipboard markdown. useDraftCanvasApply's tests were retired as redundant rather than merged: patch-to-canvas firing is covered end-to-end by the draft binding suite, rejection semantics are owned by agentDraftStore.test.ts, and the stop handle was unused API removed by the fold.
…JECT_TOKEN Deployed builds keep reading the token from the backend-injected __CONFIG__ (precedence unchanged); the env fallback only enables event verification from local dev servers, which otherwise have no key and silently queue forever.
- loadThread and newChat fired a server-side cancel for the in-flight
turn; the backend recorded it and the reply was lost ("Stopped
streaming..."). Panel unmount did the same. All implicit cancels are
removed; the explicit Stop button is the only cancel left
- switching away parks the live turn (message + event transport) in a
per-thread registry; events keep accumulating silently and cannot
bleed into the displayed chat; switching back re-attaches the turn
live with no lost text, or renders the finished reply from history
- a done landing during the return fetch keeps the parked entry so the
full reply still renders; overlapping or double-clicked history loads
are generation-gated; socket death and panel close settle parked
turns; remount refreshes a surviving thread from history
- draft patches from a backgrounded thread no longer drive the
displayed canvas (thread guard; version heartbeat unchanged)
- transport ignores events after settle; chat history date buckets
follow a reactive clock across midnight; onboarding coach gains
dialog semantics and Escape to dismiss; dead approval-flow fixtures
removed
Summary
In-App Agent side panel (FE-1187): the agent chat panel lands as an in-source workbench extension wired to the comfy-agent v1 backend (Comfy-Org/cloud PR 4432), fail-closed behind the PostHog flag
agent-in-app-experience. Nothing renders unless the flag is enabled; the flag exists in PostHog (staging + prod) and is off.What ships
src/workbench/extensions/agent/feature subtree: chat panel UI, a zod wire contract with live-captured fixtures, a REST client for ingest/api/agent/*, a reconnecting WS event transport on the host/ws, a server-draft store, and a schema-validated full-graph canvas apply.src/extensions/core/agentPanel.ts: registers the sidebar tab and a top-bar "Ask Comfy Agent" button only when the flag is on, unregisters on flip-off.draft_patchWS events adopt monotonically intoagentDraftStore;useDraftCanvasApplyreloads the canvas viavalidateComfyWorkflowthenapp.loadGraphData. The server owns the workflow: it creates one when a message opens a thread and returns its id in the 202 ack, and the panel binds drafts to that id (no client-side minting).shiki(lazy-imported byCodeBlockfor highlighting), catalog-pinned.Button.vueuses the existingcva.Rollout and open decisions
agent.caption) and the entry-button/tab icon (a lucide sparkles placeholder stands in for the Comfy mark).Acceptance (V0)
Every P0 bucket from the Design & Requirements doc is shipped and tested: flag-gated entry + top-bar button, the five exact suggested prompts, streamed turns, attach-to-upload, the draft-to-canvas loop, history + copy-markdown, message rating, reconnect resilience. Deferred with reason:
@-tag canvas nodes (ticket canceled), tool-call duration (the v1 wire emits no per-call duration), the minimized-ball form factor (superseded by the sidebar tab).Review focus
draft_patchadoption drives the canvas.Screenshots
Reviewer notes
Fixes FE-1187