Skip to content

refactor(frontend): the OSS chat slice runs on @agenta/chat, off antd and antd-x - #5871

Draft
ardaerzin wants to merge 2 commits into
pkg/chat-enginefrom
oss/chat-on-shared-engine
Draft

refactor(frontend): the OSS chat slice runs on @agenta/chat, off antd and antd-x#5871
ardaerzin wants to merge 2 commits into
pkg/chat-enginefrom
oss/chat-on-shared-engine

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

The OSS chat slice stops owning a chat runtime and composes @agenta/chat instead. That also
removes its antd and antd-x dependency — the slice renders on the shared, antd-free components.

Largest diff in the lower half of the stack (100 files) but almost all of it is deletion and
re-pointing: the logic moved in the lane below, this lane just stops duplicating it.
Not run in a browser — static gates only (pnpm lint-fix 24/24, tsc --noEmit clean
for @agenta/shared, ui, entities, entity-ui, settings-ui, oss, ee, mobile).

Stacked on pkg/chat-engine; review only this lane's diff.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
agenta-documentation Error Error Aug 10, 2026 2:58pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 3e730d1d-9c30-400a-a6cc-bf34a9b292a7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Refreshed agent chat experience with updated controls, composer, approvals, tool activity, inspector, and session navigation.
    • Added session pinning with pinned items displayed first.
    • Added improved approval details, including workspace manifest information.
    • Added streaming-aware Markdown with enhanced code blocks, math rendering, themes, and copy controls.
    • Improved split-panel resizing, collapsing, and session switching.
  • Bug Fixes

    • Improved transcript restoration so conversations remain positioned near the latest message.
    • Preserved live conversation state when switching sessions.

Walkthrough

The agent chat now uses shared Agenta chat, state, skin, and UI packages. It replaces local markdown, attachment, approval, session, and Ant Design implementations while updating session mounting, split-pane behavior, message rendering, and application-level styling.

Changes

Agent chat migration

Layer / File(s) Summary
Session layout and conversation mounting
web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx, web/oss/src/components/AgentChatSlice/components/RightPanel/RightPanelSplit.tsx
Sessions use controlled SplitPane layouts. Visited conversations remain mounted and inactive conversations are hidden.
Shared conversation and markdown rendering
web/oss/src/components/AgentChatSlice/AgentConversation.tsx, web/oss/src/components/AgentChatSlice/assets/markdown.tsx, web/packages/agenta-chat/src/assets/transcriptToMessages.ts
Conversation code consumes shared chat modules. Markdown uses Streamdown with streaming parsing, Shiki themes, KaTeX, copy controls, and sanitization. Approval manifests replay as message parts.
Composer, attachments, approvals, and tools
web/oss/src/components/AgentChatSlice/components/AgentComposerDock.tsx, web/oss/src/components/AgentChatSlice/components/ApprovalDock.tsx, web/oss/src/components/AgentChatSlice/components/approvals/*, web/oss/src/components/AgentChatSlice/components/clientTools/*, web/packages/agenta-chat/src/model/*, web/packages/agenta-chat/src/skin/*
The desktop chat delegates composer, attachment, approval, and client-tool behavior to shared implementations. Approval manifests and canonical tool names are supported.
Chat and session UI migration
web/oss/src/components/AgentChatSlice/components/*, web/oss/src/components/AgentChatSlice/components/Inspector/*
Chat, transcript, session, inspector, client-tool, and interaction controls use Agenta UI primitives instead of Ant Design components.
State and application integration
web/oss/src/components/AgentChatSlice/state/*, web/oss/src/components/AgentChatSlice/hooks/*, web/oss/src/components/Layout/*, web/oss/src/styles/globals.css, web/oss/src/pages/_app.tsx
Local session responsibilities move to shared state packages. App messaging and Streamdown styles mount at application level. Authentication styles load from @agenta/auth-ui.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AgentChatPanel
  participant AgentConversation
  participant ChatComposer
  participant ChatSkin
  User->>AgentChatPanel: select session
  AgentChatPanel->>AgentConversation: reveal visited conversation
  AgentConversation->>ChatComposer: render shared composer
  User->>ChatComposer: submit message or approval response
  ChatComposer->>AgentConversation: send chat action
  AgentConversation->>ChatSkin: resolve shared tool or approval rendering
  ChatSkin-->>AgentConversation: return rendered interaction
Loading

Possibly related PRs

  • Agenta-AI/agenta#5394: Modifies the same Markdown renderer that this PR replaces with Streamdown.
  • Agenta-AI/agenta#5470: Adds approval batch behavior that this PR moves into shared ApprovalCard handling.
  • Agenta-AI/agenta#5619: Introduces attachment modules that this PR removes from the app layer and replaces with shared chat implementations.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main refactor to use @agenta/chat and remove antd and antd-x from the OSS chat slice.
Description check ✅ Passed The description directly explains the migration to @agenta/chat, removal of duplicated runtime logic, and removal of antd dependencies.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch oss/chat-on-shared-engine

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx (1)

100-110: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Confirm the pending-session effect cannot double-fire.

The ref-based consumption guard was removed. The effect now calls adoptSession or addSession and then setPendingOpen(null). Under React StrictMode the mount effect runs twice with the same render closure, so pendingOpenForScope is still non-null on the second run. In the no-id branch this calls addSession() twice and can leave a stray blank tab. The comment on Line 95 still states "Consumed once".

🛡️ Proposed guard
+    const consumedPendingRef = useRef<string | null>(null)
     useEffect(() => {
         if (!pendingOpenForScope) return
+        const key = `${pendingOpenForScope.appId}:${pendingOpenForScope.sessionId ?? ""}`
+        if (consumedPendingRef.current === key) return
+        consumedPendingRef.current = key
         if (pendingOpenForScope.sessionId) {
web/oss/src/components/AgentChatSlice/hooks/useFirstRunSeed.ts (1)

107-124: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore an attachment gate, or the seeded first turn can be dropped.

attachmentsSettled no longer gates the auto-send. Seed files are staged asynchronously at Line 57 through onSeedFiles, which is attachments.addFiles in AgentConversation. The auto-send now fires as soon as the model and overlay conditions hold, while those files are still uploading.

The consumer then drops the turn:

  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx Line 430: if (!attachmentsSettled) return returns early and schedules no retry.
  • Line 115 here sets autoStartedSeedRef.current = true before the submit, so the effect never fires again.

The path is reachable: useStartAgentSession sends seedFiles with autoSend: true. The result is a silently lost first message.

Either restore the parameter and gate, or set the latch only after the submit is accepted.

🐛 Proposed fix — re-gate on attachment settlement
     handleSubmitRef,
     onSeedFiles,
+    attachmentsSettled = true,
 }: {
@@
     onSeedFiles?: (files: File[]) => void
+    /** Seeded files are staged asynchronously; hold the auto-send until they settle. */
+    attachmentsSettled?: boolean
 }) => {
@@
         if ((!seedWasBlockedRef.current && !firstRunAutoSend) || messagesCount > 0) return
         // Hold the auto-send until the build-kit overlay settles (or the 10s bound elapses).
         if (!overlayReady && !overlayWaitElapsed) return
+        // Seeded files upload after staging; sending now would be refused by `handleSubmit`.
+        if (!attachmentsSettled) return
         autoStartedSeedRef.current = true
         handleSubmitRef.current(firstRunPrompt)
     }, [
         firstRunPrompt,
         firstRunAutoSend,
         modelBlocked,
         messagesCount,
         overlayReady,
         overlayWaitElapsed,
+        attachmentsSettled,
     ])

Then pass it back in web/oss/src/components/AgentChatSlice/AgentConversation.tsx:

         modelBlocked,
         handleSubmitRef,
+        attachmentsSettled,
🧹 Nitpick comments (4)
web/oss/src/components/AgentChatSlice/assets/markdown.tsx (1)

211-215: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Memoize Markdown or remove the memoization claim.

Markdown is a plain component. During a streamed message update, settled Markdown parts can render again and invoke Streamdown with unchanged content and className. Wrap Markdown in React.memo so the optimization described here is active.

Verify with React Profiler that settled message parts do not re-render for each text delta.

As per coding guidelines, “Minimize React re-renders with useMemo, useCallback, and React.memo where appropriate.”

Source: Coding guidelines

web/oss/src/components/AgentChatSlice/components/AgentTranscript.tsx (1)

100-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace static inline styles with Tailwind utilities.

height and flexShrink are constants. Use className="h-14 shrink-0" at both sites.

Proposed change
-<div style={{height: 56, flexShrink: 0}} />
+<div className="h-14 shrink-0" />

As per coding guidelines, “Prefer Tailwind utility classes over CSS-in-JS or separate CSS files.”

Also applies to: 146-146

Source: Coding guidelines

web/packages/agenta-chat/tests/unit/assets/transcriptToMessages.test.ts (1)

45-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider asserting the round trip through getPendingApprovals.

The test verifies that replay emits the data-approval-manifest part. It does not verify that getPendingApprovals reads that part back onto PendingApproval.manifest. The two halves are the actual contract this change introduces. One extra assertion locks both sides against a key rename in data.

♻️ Proposed additional assertion
         expect(data).toBeDefined()
         expect(data.toolCallId).toBe("tool-1")
         expect(data.manifest).toEqual(manifest)
+        // The dock reads the manifest back off the sibling part — lock that key contract too.
+        expect(getPendingApprovals(messages ?? [])[0]?.manifest).toEqual(manifest)
     })

This requires importing getPendingApprovals from the model module.

web/oss/src/components/AgentChatSlice/AgentConversation.tsx (1)

591-592: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the stale modal-holder comment.

The Ant Design modal context holder was removed here. The comment still states that themed confirm dialogs mount through this holder, and it now sits above quickLookHost, which is unrelated. Delete or reword the comment.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e545c563-bdfe-4f63-b462-72b0a6f6d1ff

📥 Commits

Reviewing files that changed from the base of the PR and between 9a75085 and 92cc9cf.

📒 Files selected for processing (100)
  • web/ee/src/pages/_app.tsx
  • web/oss/src/components/AgentChatSlice/AgentChatPanel.tsx
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/oss/src/components/AgentChatSlice/assets/AgentChatTransport.ts
  • web/oss/src/components/AgentChatSlice/assets/attachmentMedia.ts
  • web/oss/src/components/AgentChatSlice/assets/attachmentTransport.test.ts
  • web/oss/src/components/AgentChatSlice/assets/attachmentTransport.ts
  • web/oss/src/components/AgentChatSlice/assets/attachments.ts
  • web/oss/src/components/AgentChatSlice/assets/contextBudget.ts
  • web/oss/src/components/AgentChatSlice/assets/files.test.ts
  • web/oss/src/components/AgentChatSlice/assets/files.ts
  • web/oss/src/components/AgentChatSlice/assets/loadSession.ts
  • web/oss/src/components/AgentChatSlice/assets/markdown.tsx
  • web/oss/src/components/AgentChatSlice/assets/messageParts.ts
  • web/oss/src/components/AgentChatSlice/assets/rewind.ts
  • web/oss/src/components/AgentChatSlice/assets/runError.ts
  • web/oss/src/components/AgentChatSlice/assets/sessionOpenTarget.test.ts
  • web/oss/src/components/AgentChatSlice/assets/sessionOpenTarget.ts
  • web/oss/src/components/AgentChatSlice/assets/toolDisplay.ts
  • web/oss/src/components/AgentChatSlice/assets/toolFormat.ts
  • web/oss/src/components/AgentChatSlice/assets/trace.ts
  • web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.test.ts
  • web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.ts
  • web/oss/src/components/AgentChatSlice/components/AgentChatEmptyState.tsx
  • web/oss/src/components/AgentChatSlice/components/AgentChatSkeleton.tsx
  • web/oss/src/components/AgentChatSlice/components/AgentComposerDock.tsx
  • web/oss/src/components/AgentChatSlice/components/AgentMessage.tsx
  • web/oss/src/components/AgentChatSlice/components/AgentTranscript.tsx
  • web/oss/src/components/AgentChatSlice/components/AgentTurn.tsx
  • web/oss/src/components/AgentChatSlice/components/ApprovalDock.tsx
  • web/oss/src/components/AgentChatSlice/components/AttachmentViewerDrawer.tsx
  • web/oss/src/components/AgentChatSlice/components/AudioPlayer.tsx
  • web/oss/src/components/AgentChatSlice/components/ComposerAttachments.tsx
  • web/oss/src/components/AgentChatSlice/components/ConnectModelBanner.tsx
  • web/oss/src/components/AgentChatSlice/components/ContextBudgetIndicator.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/EventRow.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/InspectSessionButton.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/Inspector.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/InspectorDrawer.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/LensRail.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/lenses/ContextLens.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/lenses/ResponseLens.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/lenses/TimelineLens.tsx
  • web/oss/src/components/AgentChatSlice/components/InteractionDock.tsx
  • web/oss/src/components/AgentChatSlice/components/QueuedMessages.tsx
  • web/oss/src/components/AgentChatSlice/components/RecordingBar.tsx
  • web/oss/src/components/AgentChatSlice/components/RightPanel/RightPanelSplit.tsx
  • web/oss/src/components/AgentChatSlice/components/SessionHistoryMenu.tsx
  • web/oss/src/components/AgentChatSlice/components/SessionRail.tsx
  • web/oss/src/components/AgentChatSlice/components/SessionTabLabel.tsx
  • web/oss/src/components/AgentChatSlice/components/SessionTagBar.tsx
  • web/oss/src/components/AgentChatSlice/components/ToolActivity.tsx
  • web/oss/src/components/AgentChatSlice/components/TranscriptPlaceholder.tsx
  • web/oss/src/components/AgentChatSlice/components/VoiceInputButton.tsx
  • web/oss/src/components/AgentChatSlice/components/approvals/registry.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/ConnectToolWidget.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/ElicitationWidget.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/UnhandledClientTool.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/index.ts
  • web/oss/src/components/AgentChatSlice/components/clientTools/meta.ts
  • web/oss/src/components/AgentChatSlice/components/clientTools/registry.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/types.ts
  • web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatQueue.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatSession.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentModelKeyStatus.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAttachmentUploads.test.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAttachmentUploads.ts
  • web/oss/src/components/AgentChatSlice/hooks/useComposerAttachments.ts
  • web/oss/src/components/AgentChatSlice/hooks/useComposerDraft.ts
  • web/oss/src/components/AgentChatSlice/hooks/useFileActivityDetector.ts
  • web/oss/src/components/AgentChatSlice/hooks/useFirstRunSeed.ts
  • web/oss/src/components/AgentChatSlice/hooks/useOnboardingChat.ts
  • web/oss/src/components/AgentChatSlice/hooks/useOpenAgentSession.ts
  • web/oss/src/components/AgentChatSlice/hooks/useSessionActions.tsx
  • web/oss/src/components/AgentChatSlice/hooks/useSessionHydration.ts
  • web/oss/src/components/AgentChatSlice/hooks/useStartAgentSession.ts
  • web/oss/src/components/AgentChatSlice/hooks/useTurnInspector.ts
  • web/oss/src/components/AgentChatSlice/hooks/useVirtuosoTranscript.tsx
  • web/oss/src/components/AgentChatSlice/state/expandState.ts
  • web/oss/src/components/AgentChatSlice/state/liveness.ts
  • web/oss/src/components/AgentChatSlice/state/pendingSessionOpen.ts
  • web/oss/src/components/AgentChatSlice/state/sessionEphemera.ts
  • web/oss/src/components/AgentChatSlice/state/sessions.ts
  • web/oss/src/components/Drives/ContextRail.tsx
  • web/oss/src/components/Drives/configDrive.ts
  • web/oss/src/components/Layout/ErrorFallback.tsx
  • web/oss/src/components/Layout/Layout.tsx
  • web/oss/src/components/SessionInspector/tabs/StatesTab.tsx
  • web/oss/src/components/SessionInspector/tabs/StreamsTab.tsx
  • web/oss/src/hooks/useAlwaysAllowTool.tsx
  • web/oss/src/pages/_app.tsx
  • web/oss/src/pages/w/[workspace_id]/p/[project_id]/apps/[app_id]/overview/index.tsx
  • web/oss/src/styles/auth.css
  • web/oss/src/styles/globals.css
  • web/packages/agenta-chat/src/assets/transcriptToMessages.ts
  • web/packages/agenta-chat/src/model/approvals.ts
  • web/packages/agenta-chat/src/skin/registry.ts
  • web/packages/agenta-chat/src/skin/types.ts
  • web/packages/agenta-chat/tests/unit/assets/transcriptToMessages.test.ts
💤 Files with no reviewable changes (28)
  • web/oss/src/components/AgentChatSlice/components/AudioPlayer.tsx
  • web/oss/src/components/AgentChatSlice/assets/files.test.ts
  • web/oss/src/components/AgentChatSlice/components/clientTools/types.ts
  • web/oss/src/components/AgentChatSlice/assets/toolFormat.ts
  • web/oss/src/components/AgentChatSlice/components/clientTools/index.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentModelKeyStatus.ts
  • web/oss/src/components/AgentChatSlice/state/pendingSessionOpen.ts
  • web/oss/src/components/AgentChatSlice/assets/loadSession.ts
  • web/oss/src/components/AgentChatSlice/assets/rewind.ts
  • web/oss/src/components/AgentChatSlice/assets/messageParts.ts
  • web/oss/src/components/AgentChatSlice/state/expandState.ts
  • web/oss/src/components/AgentChatSlice/assets/sessionOpenTarget.ts
  • web/oss/src/components/AgentChatSlice/assets/AgentChatTransport.ts
  • web/oss/src/components/AgentChatSlice/assets/files.ts
  • web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.test.ts
  • web/oss/src/components/AgentChatSlice/assets/runError.ts
  • web/oss/src/components/AgentChatSlice/hooks/useComposerAttachments.ts
  • web/oss/src/components/AgentChatSlice/assets/sessionOpenTarget.test.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAgentChatQueue.ts
  • web/oss/src/components/AgentChatSlice/assets/attachmentTransport.ts
  • web/oss/src/components/AgentChatSlice/assets/trace.ts
  • web/oss/src/styles/auth.css
  • web/oss/src/components/AgentChatSlice/assets/transcriptToMessages.ts
  • web/oss/src/components/AgentChatSlice/components/ComposerAttachments.tsx
  • web/oss/src/components/AgentChatSlice/assets/attachments.ts
  • web/oss/src/components/AgentChatSlice/assets/attachmentTransport.test.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAttachmentUploads.ts
  • web/oss/src/components/AgentChatSlice/hooks/useAttachmentUploads.test.ts

Comment on lines +288 to +290
) : TEMPLATE_STRIP_MODE ? null : ( // Strip era: the composer-docked strip replaces the starter pills.
<div className="flex flex-col items-start gap-1.5">
<Text type="secondary" className="!text-xs">
Try
</Text>
<span className="text-[11px] text-colorTextSecondary">Try</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep starter actions when the template strip is absent.

When TEMPLATE_STRIP_MODE is enabled, Line 288 removes BUILD_STARTERS in every build-mode empty state. AgentComposerDock renders the replacement strip only for isFreshAgentRevision and removes it after a commit. A blank session for an existing agent then has no template strip and no starter actions.

Pass explicit strip visibility or eligibility to this component. Render BUILD_STARTERS when the strip is not rendered.

Comment on lines +97 to +100
{/* Hard end-of-conversation clearance: the LAST message always
has 200px of real content below it, so its action lane can
never rest against the bottom edge or inside the fade. */}
<div style={{height: 56, flexShrink: 0}} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the end-clearance comment.

The comment states that the last message has 200px below it. This element reserves 56px, and the surrounding pb-24 adds 96px. Update the comment to one short and accurate line.

As per coding guidelines, “Keep in-code comments to at most one short line.”

Source: Coding guidelines

Comment on lines +22 to +33
<SimpleTooltip title={open ? "Hide inspector" : "Inspect session"}>
<Button
type={open ? "primary" : "text"}
size="small"
icon={<MagnifyingGlass size={14} />}
variant={open ? "default" : "ghost"}
size="icon-sm"
disabled={!sessionId}
onClick={() => sessionId && toggleSession(sessionId)}
aria-label="Inspect session"
aria-pressed={open}
/>
</Tooltip>
>
<MagnifyingGlass size={14} />
</Button>
</SimpleTooltip>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep an enabled tooltip trigger for the disabled button.

When sessionId is null, Button is disabled. A disabled button cannot trigger SimpleTooltip. Wrap the button in an enabled span, as SessionRail.tsx does for its disabled new-session action.

Proposed fix
 <SimpleTooltip title={open ? "Hide inspector" : "Inspect session"}>
+    <span className="inline-flex">
         <Button
             variant={open ? "default" : "ghost"}
             size="icon-sm"
             disabled={!sessionId}
             onClick={() => sessionId && toggleSession(sessionId)}
             aria-label="Inspect session"
             aria-pressed={open}
         >
             <MagnifyingGlass size={14} />
         </Button>
+    </span>
 </SimpleTooltip>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<SimpleTooltip title={open ? "Hide inspector" : "Inspect session"}>
<Button
type={open ? "primary" : "text"}
size="small"
icon={<MagnifyingGlass size={14} />}
variant={open ? "default" : "ghost"}
size="icon-sm"
disabled={!sessionId}
onClick={() => sessionId && toggleSession(sessionId)}
aria-label="Inspect session"
aria-pressed={open}
/>
</Tooltip>
>
<MagnifyingGlass size={14} />
</Button>
</SimpleTooltip>
<SimpleTooltip title={open ? "Hide inspector" : "Inspect session"}>
<span className="inline-flex">
<Button
variant={open ? "default" : "ghost"}
size="icon-sm"
disabled={!sessionId}
onClick={() => sessionId && toggleSession(sessionId)}
aria-label="Inspect session"
aria-pressed={open}
>
<MagnifyingGlass size={14} />
</Button>
</span>
</SimpleTooltip>

Comment on lines +1 to +5
/**
* "Always allow this tool" for the approval card.
*
* Config write-through into the draft agent config; `buildAgentRequest` reads the draft, so a grant
* takes effect on the paused run's resume and every future run, and a commit carries it to triggers.
* Two fields, routed by tool class:
* - a gateway / custom-function tool has a `tools[]` entry → per-tool `permission: "allow"`
* (`specPermission`, the highest-precedence gate). Checked FIRST — it outranks any rule, and a
* verbatim rule pattern would otherwise also match its slug.
* - any other harness tool (`bash`, `Terminal`, `Write`, …) has no enforceable per-tool permission
* → an allow-rule in `harness.permissions.allow`, keyed by the gate name VERBATIM: the runner
* matches `pattern === gate.toolName`, and that string is exactly what the card shows (the
* runner stamps it as `resolvedName`, which the egress prefers). Never canonicalize it, except
* the seven Pi built-ins, which the runner matches case-insensitively (see `gateRulePattern`).
* Platform ops (`commit_revision`, schedules), client tools, and MCP tools return `eligible: false`
* and always stay gated (see `gateRulePattern`).
*
* On grant we raise a single draft-change signal that the config pane consumes two ways: the section
* it landed in pulses for attention, and a contained banner (`AlwaysAllowedNotice`) offers Undo —
* both kept inside the config panel where the change is, rather than a floating toast. `revoke` is
* the exact inverse (`"ask"` for tools, `allowed:false` for harness rules).
* Moved to the chat package so mobile shares the identical always-allow behavior; this path
* survives as a re-export for the app-layer call sites.
*/
export function useAlwaysAllowTool(entityId?: string) {
const config = useAtomValue(
useMemo(() => workflowMolecule.selectors.configuration(entityId ?? ""), [entityId]),
)
// Latest config for the deferred Undo click, so it never reverts against a stale snapshot.
const configRef = useRef(config)
configRef.current = config
const setConfiguration = useSetAtom(workflowMolecule.actions.updateConfiguration)
// Marks the config section this grant lands in so it can pulse for attention — the user
// acted here in the dock, but the write shows up over in the (maybe off-screen) config pane.
const raiseDraftSignal = useSetAtom(draftConfigChangeSignalAtom)

const infoFor = useCallback(
(toolName: string): ToolGrantInfo => {
if (!entityId) return INELIGIBLE
// Gateway / custom-function tools carry a per-tool `permission` in `tools[]`. First:
// it outranks a rule, and a verbatim rule pattern would also match its slug.
const tool = findGrantableTool(config, toolName)
if (tool) return {eligible: true, alreadyAllowed: tool.permission === "allow"}
// Any other harness tool (bash, Terminal, Write, …) → `harness.permissions.allow`.
const harnessTool = findGrantableHarnessTool(config, toolName)
if (harnessTool) return {eligible: true, alreadyAllowed: harnessTool.allowed}
// Platform ops (commit_revision, schedules), client tools, MCP → never grantable.
return INELIGIBLE
},
[entityId, config],
)

// Inverse of grant: put the tool back to gated. Reads the LATEST config (ref), since Undo fires
// seconds after the grant and the draft may have moved on.
const revoke = useCallback(
(toolName: string): boolean => {
if (!entityId) return false
const cfg = configRef.current
// Same routing as `grant` — `tools[]` first, then the harness allow-rule.
const tool = findGrantableTool(cfg, toolName)
const pattern = tool ? null : gateRulePattern(toolName)
const next = tool
? withToolPermission(cfg, toolName, "ask")
: pattern
? withHarnessToolAllow(cfg, pattern, false)
: null
if (!next) return false
setConfiguration(entityId, next)
return true
},
[entityId, setConfiguration],
)

const grant = useCallback(
(toolName: string): boolean => {
if (!entityId) return false
// Route to the field that matches the gate's tool class (see infoFor). `tools[]` first:
// its per-tool permission outranks a rule, and a verbatim pattern would match its slug.
const tool = findGrantableTool(config, toolName)
const pattern = tool ? null : gateRulePattern(toolName)
const next = tool
? withToolPermission(config, toolName, "allow")
: pattern
? withHarnessToolAllow(config, pattern, true)
: null
if (!next) return false
setConfiguration(entityId, next)
// A harness allow-rule writes `harness.permissions`, which surfaces in the Advanced →
// Permissions group (and classifies as an "advanced" draft change); gateway/custom-function
// tools write `tools[]`, surfaced in the Tools section. Pulse the section the change lands in.
raiseDraftSignal({
revisionId: entityId,
sectionKeys: [tool ? "tools" : "advanced"],
origin: "approval-dock",
summary: `Always allow ${toolName}`,
// Friendly display (matches the approval card) — a gateway tool's raw name is a slug.
label: resolveToolDisplay(toolName).label,
toolName,
at: Date.now(),
})
return true
},
[entityId, config, setConfiguration, raiseDraftSignal],
)

return {infoFor, grant, revoke}
}
export {useAlwaysAllowTool, type ToolGrantInfo} from "@agenta/chat/hooks"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Shorten the compatibility comment.

Keep this comment to one short line. The re-export does not need a multi-line migration explanation.

Proposed change
-/**
- * Moved to the chat package so mobile shares the identical always-allow behavior; this path
- * survives as a re-export for the app-layer call sites.
- */
+/** Compatibility re-export for app-layer call sites. */

As per coding guidelines, keep in-code comments to at most one short line unless they document a surprising constraint.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* "Always allow this tool" for the approval card.
*
* Config write-through into the draft agent config; `buildAgentRequest` reads the draft, so a grant
* takes effect on the paused run's resume and every future run, and a commit carries it to triggers.
* Two fields, routed by tool class:
* - a gateway / custom-function tool has a `tools[]` entry per-tool `permission: "allow"`
* (`specPermission`, the highest-precedence gate). Checked FIRST it outranks any rule, and a
* verbatim rule pattern would otherwise also match its slug.
* - any other harness tool (`bash`, `Terminal`, `Write`, ) has no enforceable per-tool permission
* an allow-rule in `harness.permissions.allow`, keyed by the gate name VERBATIM: the runner
* matches `pattern === gate.toolName`, and that string is exactly what the card shows (the
* runner stamps it as `resolvedName`, which the egress prefers). Never canonicalize it, except
* the seven Pi built-ins, which the runner matches case-insensitively (see `gateRulePattern`).
* Platform ops (`commit_revision`, schedules), client tools, and MCP tools return `eligible: false`
* and always stay gated (see `gateRulePattern`).
*
* On grant we raise a single draft-change signal that the config pane consumes two ways: the section
* it landed in pulses for attention, and a contained banner (`AlwaysAllowedNotice`) offers Undo
* both kept inside the config panel where the change is, rather than a floating toast. `revoke` is
* the exact inverse (`"ask"` for tools, `allowed:false` for harness rules).
* Moved to the chat package so mobile shares the identical always-allow behavior; this path
* survives as a re-export for the app-layer call sites.
*/
export function useAlwaysAllowTool(entityId?: string) {
const config = useAtomValue(
useMemo(() => workflowMolecule.selectors.configuration(entityId ?? ""), [entityId]),
)
// Latest config for the deferred Undo click, so it never reverts against a stale snapshot.
const configRef = useRef(config)
configRef.current = config
const setConfiguration = useSetAtom(workflowMolecule.actions.updateConfiguration)
// Marks the config section this grant lands in so it can pulse for attention — the user
// acted here in the dock, but the write shows up over in the (maybe off-screen) config pane.
const raiseDraftSignal = useSetAtom(draftConfigChangeSignalAtom)
const infoFor = useCallback(
(toolName: string): ToolGrantInfo => {
if (!entityId) return INELIGIBLE
// Gateway / custom-function tools carry a per-tool `permission` in `tools[]`. First:
// it outranks a rule, and a verbatim rule pattern would also match its slug.
const tool = findGrantableTool(config, toolName)
if (tool) return {eligible: true, alreadyAllowed: tool.permission === "allow"}
// Any other harness tool (bash, Terminal, Write, …) → `harness.permissions.allow`.
const harnessTool = findGrantableHarnessTool(config, toolName)
if (harnessTool) return {eligible: true, alreadyAllowed: harnessTool.allowed}
// Platform ops (commit_revision, schedules), client tools, MCP → never grantable.
return INELIGIBLE
},
[entityId, config],
)
// Inverse of grant: put the tool back to gated. Reads the LATEST config (ref), since Undo fires
// seconds after the grant and the draft may have moved on.
const revoke = useCallback(
(toolName: string): boolean => {
if (!entityId) return false
const cfg = configRef.current
// Same routing as `grant` — `tools[]` first, then the harness allow-rule.
const tool = findGrantableTool(cfg, toolName)
const pattern = tool ? null : gateRulePattern(toolName)
const next = tool
? withToolPermission(cfg, toolName, "ask")
: pattern
? withHarnessToolAllow(cfg, pattern, false)
: null
if (!next) return false
setConfiguration(entityId, next)
return true
},
[entityId, setConfiguration],
)
const grant = useCallback(
(toolName: string): boolean => {
if (!entityId) return false
// Route to the field that matches the gate's tool class (see infoFor). `tools[]` first:
// its per-tool permission outranks a rule, and a verbatim pattern would match its slug.
const tool = findGrantableTool(config, toolName)
const pattern = tool ? null : gateRulePattern(toolName)
const next = tool
? withToolPermission(config, toolName, "allow")
: pattern
? withHarnessToolAllow(config, pattern, true)
: null
if (!next) return false
setConfiguration(entityId, next)
// A harness allow-rule writes `harness.permissions`, which surfaces in the Advanced →
// Permissions group (and classifies as an "advanced" draft change); gateway/custom-function
// tools write `tools[]`, surfaced in the Tools section. Pulse the section the change lands in.
raiseDraftSignal({
revisionId: entityId,
sectionKeys: [tool ? "tools" : "advanced"],
origin: "approval-dock",
summary: `Always allow ${toolName}`,
// Friendly display (matches the approval card) — a gateway tool's raw name is a slug.
label: resolveToolDisplay(toolName).label,
toolName,
at: Date.now(),
})
return true
},
[entityId, config, setConfiguration, raiseDraftSignal],
)
return {infoFor, grant, revoke}
}
export {useAlwaysAllowTool, type ToolGrantInfo} from "@agenta/chat/hooks"
/** Compatibility re-export for app-layer call sites. */
export {useAlwaysAllowTool, type ToolGrantInfo} from "`@agenta/chat/hooks`"

Source: Coding guidelines

The composer-docked template strip and the empty state's starter pills each
re-derived their own visibility, and the two conditions did not agree: the
empty state dropped the pills for the whole of TEMPLATE_STRIP_MODE, while the
strip also required a fresh agent revision. A blank session on an existing
agent (version >= 2), and every session while the revision query is pending,
therefore rendered neither — an empty state with nothing to click.

AgentConversation now owns the one flag and hands it to both surfaces, so
exactly one of the strip and the pills is up.

Also: the disabled "Inspect session" button gets the enabled-span tooltip
trigger the session rail already uses (a disabled button fires no pointer
events, so its tooltip never opened), the transcript's end-of-conversation
spacer comment states the real numbers, and the always-allow re-export loses
its three-line preamble.
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