Skip to content

feat(copilot): preserve mothership chat when opening workflow#4521

Open
stylessh wants to merge 4 commits intosimstudioai:stagingfrom
stylessh:stylessh/copilot-preserve-chat-from-mothership
Open

feat(copilot): preserve mothership chat when opening workflow#4521
stylessh wants to merge 4 commits intosimstudioai:stagingfrom
stylessh:stylessh/copilot-preserve-chat-from-mothership

Conversation

@stylessh
Copy link
Copy Markdown
Contributor

@stylessh stylessh commented May 8, 2026

Summary

Clicking Open Workflow from a Mothership task now deep-links the
originating chat into the workflow page's copilot panel, so the
conversation that produced the workflow stays visible — and is fully
continuable.

When the selected chat has type: 'mothership', the workflow panel
swaps its useChat options to the Mothership branch (no workflowId
in the request), so sends route to the broader Mothership agent that
originally produced the chat instead of the workflow-scoped copilot.
Resources created during continuation still only render in Mothership;
the workflow panel shows the conversation only.

Changes

  • Contract (lib/api/contracts/copilot.ts): copilotChatListItemSchema
    now exposes optional type ('mothership' | 'copilot') and
    resources so the client can identify mothership chats and chats
    that reference a workflow via resources even when
    chat.workflowId is null.
  • Route (/api/copilot/chats): selects type and resources from
    the existing columns.
  • Client filter (hooks/queries/copilot-chats.ts):
    useCopilotChats(workflowId) now also includes chats whose
    resources contain a { type: 'workflow', id } matching the
    workflow.
  • Open Workflow button (resource-content.tsx): appends
    ?chatId=<currentChat> to the URL. chatId is threaded through
    MothershipViewResourceActionsEmbeddedWorkflowActions.
  • Workflow panel (panel.tsx):
    • Reads ?chatId= from the URL, prefers it over the most-recent
      auto-select on first list arrival, and switches the panel to the
      copilot tab on first mount when the param is present.
    • Computes isMothershipChat from selectedChat.type and swaps the
      useChat options between getMothershipUseChatOptions and
      getWorkflowCopilotUseChatOptions accordingly.

Known limitation

Resources (workflows, files, tables) spawned by the agent during
continuation from the workflow panel are persisted on the chat row but
not rendered in the workflow panel — that side panel only exists in
the Mothership view. Users see the conversation; to inspect or
interact with new resources, they go back to Mothership.

Test plan

  • In Mothership, ask the copilot to create a workflow and wait for
    the Open Workflow button to appear in the resource panel.
  • Click Open Workflow. Verify a new tab opens with
    ?chatId=<id> in the URL and the copilot panel is open with the
    original conversation visible.
  • Type a follow-up like "rename this workflow to X". Verify the
    response uses the Mothership agent (broader tools, can still
    create new resources) — and that a network request to
    /api/mothership/chat goes out without workflowId in the
    body.
  • Click + New chat in the copilot panel. The new chat is a
    regular workflow-copilot chat — sends include workflowId.
  • Switch back to the original mothership chat via the chat history
    dropdown — sends drop workflowId again.
  • On a workflow page with no ?chatId= param, behavior is
    unchanged: most recent workflow chat auto-selects.
  • Refresh on ?chatId=<unknown>: falls through to the most-recent
    auto-select gracefully.

Clicking "Open Workflow" from a Mothership task now deep-links the
originating chat into the workflow page's copilot panel as read-only
history, so users don't lose the conversation that produced the
workflow.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 8, 2026 9:34pm

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 8, 2026

PR Summary

Medium Risk
Moderate risk because it changes chat list filtering/selection and switches request routing based on chat type, which could surface wrong conversations or send messages via the wrong backend path if edge cases aren’t handled.

Overview
Clicking Open workflow from the Mothership resource panel now navigates to the workflow page with ?chatId=..., and the workflow Panel prefers that chat (and auto-switches to the Copilot tab) so the originating conversation is immediately visible.

The copilot chat list contract/endpoint now includes optional type and resources, useCopilotChats(workflowId) also includes chats that reference the workflow via resources, and the workflow panel routes useChat through getMothershipUseChatOptions when the selected chat is a Mothership chat (vs workflow-scoped options otherwise).

Reviewed by Cursor Bugbot for commit bc431ff. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 8, 2026

Greptile Summary

This PR deep-links an originating Mothership chat into the workflow page's copilot panel when "Open Workflow" is clicked, preserving context from the chat that generated the workflow. It threads a ?chatId= URL param from the Mothership resource button all the way to the panel, adds a readOnly prop to MothershipChat to hide the input, and extends the useCopilotChats filter to include chats whose resources reference the current workflow.

  • Contract/API: copilotChatListItemSchema now exposes the resources jsonb column, and a pre-existing copilotChatResourceSchema is hoisted to be shared across both the add-resource body and the new list-item schema.
  • Panel: reads ?chatId= from useSearchParams, prefers it on first auto-select, opens the copilot tab automatically, and marks a chat read-only when its workflowId differs from the active workflow.
  • Resource actions: EmbeddedWorkflowActions appends ?chatId=<currentChat> to the window.open URL, with chatId threaded up through ResourceActionsMothershipView.

Confidence Score: 4/5

Safe to merge — the core deep-link flow is correct and no chat data is at risk; two loading-state edge cases in the panel are cosmetic.

The contract, API, filter, and prop-threading changes are all straightforward and correct. The two items flagged are both in panel.tsx: the input footer briefly flashes before isCopilotChatReadOnly settles, and in-session back-navigation to the same workflow silently ignores ?chatId= because autoSelectAttemptedForRef is never cleared. Neither causes data loss or broken functionality — the read-only restriction is enforced as soon as the chat list resolves — but they are visible UX inconsistencies.

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx — auto-select timing and autoSelectAttemptedForRef reset behaviour around in-session workflow navigation.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx Most logic lives here: adds useSearchParams, derives urlChatIdParam, computes isCopilotChatReadOnly, wires both into the auto-select effect and MothershipChat. The read-only logic and auto-select timing are correct; minor brief loading-state window where readOnly is false before the chat list arrives.
apps/sim/hooks/queries/copilot-chats.ts Filter extended to include chats whose resources contain a matching {type:'workflow', id} entry; ?.some() correctly handles optional/undefined resources.
apps/sim/lib/api/contracts/copilot.ts copilotChatResourceSchema hoisted to be shared; resources added as .optional() to copilotChatListItemSchema. The DB column is notNull().default('[]') so the field is always present, making .optional() a TypeScript convenience rather than a correctness concern.
apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx Threads chatId through ResourceActionsEmbeddedWorkflowActions; handleOpenWorkflow conditionally appends ?chatId=encodeURIComponent(chatId). Clean and straightforward.
apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx Adds readOnly prop (default false) that wraps the footer in {!readOnly && ...}. Minimal and correct.
apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx Passes chatId down to ResourceActions; change is a one-liner prop forward with no logic changes.
apps/sim/app/api/copilot/chats/route.ts Adds resources column to the SELECT projection. The column is notNull().default('[]') in the DB schema so it never returns null.

Reviews (1): Last reviewed commit: "feat(copilot): preserve mothership chat ..." | Re-trigger Greptile

Comment on lines +269 to +274
const isCopilotChatReadOnly = useMemo(() => {
if (!copilotChatId || !activeWorkflowId) return false
const chat = copilotChatList.find((c) => c.id === copilotChatId)
if (!chat) return false
return chat.workflowId !== activeWorkflowId
}, [copilotChatId, copilotChatList, activeWorkflowId])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Input footer briefly visible before read-only state resolves

isCopilotChatReadOnly returns false whenever copilotChatId is falsy, which is the case during the window between mount and the first auto-select (i.e., while copilotChatList is still loading). When the page opens with ?chatId=, the copilot tab is activated immediately by chatIdParamHandledRef, but copilotChatId isn't set until the list arrives and the auto-select effect fires. During that gap MothershipChat renders with readOnly={false}, briefly showing the input footer before it disappears once the list loads and copilotChatId is set to the Mothership chat. This could be prevented by initialising copilotChatId directly from urlChatIdParam instead of waiting for the list, or by letting isCopilotChatReadOnly return true optimistically when urlChatIdParam is set and copilotChatId is still unresolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moot now — the read-only behavior was removed in 6be5374 in favor of full continuation (panel swaps useChat to the Mothership branch when the selected chat is type: 'mothership'). There is no readOnly flicker because there is no readOnly path anymore.

Comment on lines 288 to +306
@@ -278,8 +298,12 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel
if (autoSelectAttemptedForRef.current.has(activeWorkflowId)) return
if (copilotChatList.length === 0) return
autoSelectAttemptedForRef.current.add(activeWorkflowId)
setCopilotChatId(copilotChatList[0].id)
}, [copilotChatList, copilotChatId, activeWorkflowId, setCopilotChatId])
const preferred =
urlChatIdParam && copilotChatList.find((c) => c.id === urlChatIdParam)
? urlChatIdParam
: copilotChatList[0].id
setCopilotChatId(preferred)
}, [copilotChatList, copilotChatId, activeWorkflowId, setCopilotChatId, urlChatIdParam])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 autoSelectAttemptedForRef prevents re-honoring ?chatId= after in-session workflow navigation

autoSelectAttemptedForRef is keyed by activeWorkflowId and never cleared. If the user opens the workflow with ?chatId=<mothership_id>, navigates to a different workflow (via the sidebar), then navigates back to the same workflow in the same session, the ref still contains the original activeWorkflowId so the urlChatIdParam is silently ignored on the return visit. The most-recently-used chat is auto-selected instead of the linked Mothership chat. This is a subtle divergence from the intended deep-link behaviour, most likely to appear in SPAs where the component stays mounted across workflow switches.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in bc431ff. Replaced the once-per-workflow Set guard for URL-driven selection with a consumedUrlChatIdRef keyed by the chatId value itself: any time urlChatIdParam changes to a value that is in the list, we honor it. Returning to the same workflow with a fresh ?chatId= now re-applies. The auto-select-most-recent guard still runs once per workflow when no URL param is present. Same fix applied to the copilot-tab activation effect.

Switch the workflow-panel useChat options to the Mothership branch when
the selected chat has type 'mothership'. Sends from the workflow page
then go through the broader Mothership agent surface that originally
produced the chat, instead of the workflow-scoped copilot. Removes the
read-only fallback since both modes are now continuable; resources
spawned during continuation still only render in Mothership.
* that resources spawned during continuation only show up in the
* Mothership view; this panel shows the conversation only.
*/
const isMothershipChat = selectedCopilotChat?.type === 'mothership'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Mothership chat read-only mode never applied to UI

High Severity

isMothershipChat is computed but only used to switch copilotChatOptions between routing branches — it is never used to hide the input footer. MothershipChat has no readOnly prop, and none is passed at the call site in panel.tsx. As a result, users viewing a Mothership chat in the workflow copilot panel will see an active input footer and can submit messages, directly contradicting the PR description's stated behavior ("the input footer is hidden") and the test-plan step "Verify the input footer is hidden (read-only)."

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6be5374. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Stale flag on this thread. The "input footer is hidden / read-only" wording was from an earlier iteration — this commit (and the PR description) deliberately switched to continuation: the panel swaps useChat to the Mothership branch (getMothershipUseChatOptions, no workflowId in the request) so users can keep talking. isMothershipChat is what drives that swap. No readOnly is intended.

Comment thread apps/sim/lib/api/contracts/copilot.ts Outdated
Track the consumed URL chatId per value (not once per session) so
returning to a workflow with a fresh `?chatId=` re-applies it instead
of being shadowed by the once-per-workflow auto-select guard. Same
treatment for the copilot-tab activation effect.

Make `type` and `resources` on the chat list contract `.nullable()` to
tolerate null jsonb reads from older rows even though the columns are
declared NOT NULL with defaults — avoids a Zod parse failure silently
emptying the chat list on the client.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit bc431ff. Configure here.

const url = chatId
? `/workspace/${workspaceId}/w/${workflowId}?chatId=${encodeURIComponent(chatId)}`
: `/workspace/${workspaceId}/w/${workflowId}`
router.push(url)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Open Workflow navigates same tab instead of new tab

High Severity

handleOpenWorkflow was changed from window.open(url, '_blank') (opens a new tab) to router.push(url) (same-tab navigation). This means clicking "Open Workflow" from a Mothership task now navigates away from the Mothership view, causing the user to lose their in-progress conversation context. The PR test plan explicitly expects "a new tab opens," confirming this is unintended. The original window.open call just needs the ?chatId= query string appended while keeping '_blank'.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit bc431ff. Configure here.

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