Skip to content

feat(frontend): Tools and Triggers extracted to @agenta/settings-ui, off antd - #5890

Draft
ardaerzin wants to merge 10 commits into
pkg/settings-org-pagesfrom
pkg/settings-tools-triggers
Draft

feat(frontend): Tools and Triggers extracted to @agenta/settings-ui, off antd#5890
ardaerzin wants to merge 10 commits into
pkg/settings-org-pagesfrom
pkg/settings-tools-triggers

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

Trigger connections, scheduled runs and event subscriptions move into @agenta/settings-ui, and
all seven Tools files follow. Both sections are off antd as a result.

/m gains Tools and Triggers here, read-only — they become writable two lanes up, once the form
engine migration lands.
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/settings-org-pages; 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 11, 2026 5:11am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

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: 4afefe52-335f-410c-95e4-6d90fbd4d23b

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

    • Added voice dictation and audio-message recording, including waveform feedback, microphone permissions, and attachment handling.
    • Added mobile Account and Preferences settings, including account deletion, theme selection, voice input, and inspector controls.
    • Added mobile tools and trigger settings for connections, subscriptions, schedules, and read-only views.
    • Added shared tools and trigger management interfaces, including connection creation, refresh, deletion, and integration browsing.
  • Bug Fixes

    • Improved logout cleanup and authentication redirection.
    • Corrected error-state colors and microphone permission messaging.

Walkthrough

The PR centralizes user-scoped state, adds shared voice-input components and hooks, introduces shared tools and trigger settings, and integrates these capabilities into mobile and OSS interfaces.

Changes

Shared user state and mobile settings

Layer / File(s) Summary
User state, authentication, and account settings
web/packages/agenta-shared/..., web/mobile/src/features/app/..., web/mobile/src/features/auth/..., web/mobile/src/features/settings/..., web/oss/src/lib/onboarding/..., web/packages/agenta-entities/...
Shared per-user feature flags now use activeUserIdAtom. Mobile settings add account deletion, preferences, tools, and triggers. Logout logic is centralized.
Mobile settings wiring
web/mobile/src/features/settings/SettingsScreen.tsx, web/oss/src/components/pages/settings/...
Mobile settings render account and preference tabs plus read-only tools and trigger sections. OSS settings use shared profile and feature-flag APIs.

Shared voice input

Layer / File(s) Summary
Recording and dictation hooks
web/packages/agenta-chat/src/hooks/*
MediaRecorder and Web Speech API hooks manage permissions, recording state, audio files, transcripts, errors, limits, and cleanup.
Voice composer controls
web/packages/agenta-chat/src/components/*, web/mobile/src/features/chat/Composer.tsx
Voice mode selection, dictation, audio recording, waveform rendering, recording takeover UI, attachment guards, and voice-message submission are integrated into the composer.
Shared exports and migration
web/packages/agenta-chat/src/index.ts, web/oss/src/components/AgentChatSlice/*
Voice components and hooks are publicly exported. OSS chat imports shared implementations and removes the former local voice feature helpers.

Shared tools and triggers settings

Layer / File(s) Summary
Tools data and connection flows
web/packages/agenta-settings-ui/src/tools/*, web/packages/agenta-settings-ui/src/index.ts, web/packages/agenta-settings-ui/package.json
Shared tools queries, connection mutations, catalog views, connection lists, connection creation, and read-only gateway tool rendering are added and exported.
Trigger management sections
web/packages/agenta-settings-ui/src/triggers/*
Shared trigger connection, schedule, and subscription sections support loading, mutation, notifications, drawers, and read-only rendering.
OSS settings replacement
web/oss/src/components/pages/settings/Tools/*, web/oss/src/components/pages/settings/Triggers/*
OSS tools and trigger pages now use shared settings components. Former local tools and trigger implementations are removed.

Mobile error styling

Layer / File(s) Summary
Error token generation and usage
web/mobile/scripts/generate-shadcn-tokens.ts, web/mobile/src/styles/globals.css, web/packages/agenta-chat/src/components/MicPermissionNotice.tsx
Mobile CSS generation and theme mappings add error background and border tokens. The microphone notice uses the token-based classes.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant VoiceInputButton
  participant useVoiceInput
  participant useAudioRecorder
  participant MobileComposer
  User->>VoiceInputButton: choose dictation or audio recording
  VoiceInputButton->>useVoiceInput: start or stop dictation
  VoiceInputButton->>useAudioRecorder: start or stop recording
  useVoiceInput->>MobileComposer: update transcript text
  useAudioRecorder->>MobileComposer: provide voice message file
  MobileComposer->>MobileComposer: upload files and submit message
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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
Description check ✅ Passed The description accurately summarizes the Tools and Triggers extraction, Ant Design removal, read-only mobile settings, validation, and review scope.
Title check ✅ Passed The title clearly identifies the primary change: extracting Tools and Triggers into @agenta/settings-ui and removing Ant Design usage.
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.
✨ 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 pkg/settings-tools-triggers

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 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Rate Limit Exceeded

@ardaerzin have exceeded the limit for the number of chat messages per hour. Please wait 52 minutes and 58 seconds before sending another message.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
✅ 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 left a comment

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.

Actionable comments posted: 13

🧹 Nitpick comments (5)
web/mobile/src/features/settings/PreferencesTab.tsx (1)

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

Memoize the flags prop.

Lines 18-35 allocate a new object array on every render. Create flags with useMemo and pass that value to PreferencesPage.

As per coding guidelines, “Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders.”

Source: Coding guidelines

web/packages/agenta-settings-ui/src/triggers/TriggerSubscriptionsSection.tsx (1)

203-246: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Wrap the actions render prop in useCallback.

actions is a new function on every render, and it builds a fresh array of objects containing JSX for each row. Any row-level memoization inside DataTable cannot hold. The columns and rows above already use useMemo, so this prop is the remaining unstable input.

♻️ Proposed memoization
+    const actions = useCallback(
+        (record: SubscriptionRow) => [
+            /* move the existing entries here unchanged */
+        ],
+        [readOnly, openDeliveries, handleEdit, handleRefresh, handleRevoke, handleDelete],
+    )

Then pass actions={actions} to DataTable.

As per coding guidelines: "Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders."

Source: Coding guidelines

web/packages/agenta-settings-ui/src/index.ts (1)

33-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Export the props types for both trigger sections.

Both modules export their props types, but the barrel exports only the default components. Re-export TriggerSchedulesSectionProps and TriggerSubscriptionsSectionProps so consumers can type wrappers around these sections.

web/packages/agenta-settings-ui/package.json (1)

24-26: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move jotai and jotai-tanstack-query to peer dependencies.

The current lockfile deduplicates Jotai to 2.20.0, but the dependency declarations can allow separate instances for consumers. Declare both packages as peers and keep them in devDependencies. jotai-tanstack-query also requires the host's @tanstack/react-query and Jotai instances.

web/packages/agenta-settings-ui/src/triggers/TriggerConnectionsSection.tsx (1)

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

Export one shared type for the confirm callback.

This file declares onOk: () => Promise<void>. GatewayToolsSection.tsx line 41 and ConnectionsList.tsx line 16 declare the same callback as onOk: () => void | Promise<void>. Three inline copies with two signatures will drift, and the narrower signature here rejects a synchronous onOk from a caller that shares one adapter across sections. Declare the type once in the package and import it in all three files.

♻️ Proposed refactor
// web/packages/agenta-settings-ui/src/types.ts
export interface ConfirmArgs {
    title: string
    message: string
    onOk: () => void | Promise<void>
}

export type ConfirmFn = (args: ConfirmArgs) => void
+import type {ConfirmFn} from "../types"
+
 export interface TriggerConnectionsSectionProps {
     /** Destructive confirmation — the desktop's AlertPopup, a sheet elsewhere. */
-    confirm?: (args: {title: string; message: string; onOk: () => Promise<void>}) => void
+    confirm?: ConfirmFn
     /** Hides connect/refresh and skips the catalog drawer. */
     readOnly?: boolean
 }

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 444dd719-48da-441d-b629-64c4df45d309

📥 Commits

Reviewing files that changed from the base of the PR and between b303148 and f8a5737.

⛔ Files ignored due to path filters (2)
  • web/mobile/src/styles/theme.generated.css is excluded by !**/*.generated.*
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (62)
  • web/mobile/scripts/generate-shadcn-tokens.ts
  • web/mobile/src/features/app/ContextSync.tsx
  • web/mobile/src/features/auth/useLogout.ts
  • web/mobile/src/features/chat/Composer.tsx
  • web/mobile/src/features/nav/DrawerProjectSwitcher.tsx
  • web/mobile/src/features/settings/AccountTab.tsx
  • web/mobile/src/features/settings/PreferencesTab.tsx
  • web/mobile/src/features/settings/SettingsScreen.tsx
  • web/mobile/src/styles/globals.css
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/oss/src/components/AgentChatSlice/assets/constants.ts
  • web/oss/src/components/AgentChatSlice/components/AgentComposerDock.tsx
  • web/oss/src/components/AgentChatSlice/components/ConnectModelBanner.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/InspectSessionButton.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.ts
  • web/oss/src/components/AgentChatSlice/hooks/useTurnInspector.ts
  • web/oss/src/components/SessionInspector/PanelSessionInspectorButton.tsx
  • web/oss/src/components/pages/settings/Account/DeleteAccount.tsx
  • web/oss/src/components/pages/settings/Preferences/Preferences.tsx
  • web/oss/src/components/pages/settings/Tools/Tools.tsx
  • web/oss/src/components/pages/settings/Tools/components/ActionsList.tsx
  • web/oss/src/components/pages/settings/Tools/components/AgentaToolsPlaceholder.tsx
  • web/oss/src/components/pages/settings/Tools/components/ConnectModal.tsx
  • web/oss/src/components/pages/settings/Tools/components/ConnectionsList.tsx
  • web/oss/src/components/pages/settings/Triggers/Triggers.tsx
  • web/oss/src/components/pages/settings/Triggers/components/GatewaySchedulesSection.tsx
  • web/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsx
  • web/oss/src/components/pages/settings/Triggers/components/GatewayTriggersSection.tsx
  • web/oss/src/lib/onboarding/atoms.ts
  • web/oss/src/services/profile/index.ts
  • web/oss/src/state/settings/featureFlags.ts
  • web/packages/agenta-chat/src/assets/index.ts
  • web/packages/agenta-chat/src/assets/voice.ts
  • web/packages/agenta-chat/src/components/MicPermissionNotice.tsx
  • web/packages/agenta-chat/src/components/RecordingBar.tsx
  • web/packages/agenta-chat/src/components/RecordingWaveform.tsx
  • web/packages/agenta-chat/src/components/RevealCollapse.tsx
  • web/packages/agenta-chat/src/components/VoiceInputButton.tsx
  • web/packages/agenta-chat/src/components/index.ts
  • web/packages/agenta-chat/src/hooks/index.ts
  • web/packages/agenta-chat/src/hooks/useAudioRecorder.ts
  • web/packages/agenta-chat/src/hooks/useVoiceComposer.ts
  • web/packages/agenta-chat/src/hooks/useVoiceInput.ts
  • web/packages/agenta-chat/tests/unit/assets/voice.test.ts
  • web/packages/agenta-entities/src/profile/index.ts
  • web/packages/agenta-settings-ui/package.json
  • web/packages/agenta-settings-ui/src/index.ts
  • web/packages/agenta-settings-ui/src/tools/ActionsList.tsx
  • web/packages/agenta-settings-ui/src/tools/AgentaToolsPlaceholder.tsx
  • web/packages/agenta-settings-ui/src/tools/ConnectModal.tsx
  • web/packages/agenta-settings-ui/src/tools/ConnectionsList.tsx
  • web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx
  • web/packages/agenta-settings-ui/src/tools/IntegrationDetail.tsx
  • web/packages/agenta-settings-ui/src/tools/IntegrationGrid.tsx
  • web/packages/agenta-settings-ui/src/tools/hooks/useIntegrationDetail.ts
  • web/packages/agenta-settings-ui/src/tools/hooks/useToolsConnections.ts
  • web/packages/agenta-settings-ui/src/tools/hooks/useToolsIntegrations.ts
  • web/packages/agenta-settings-ui/src/triggers/TriggerConnectionsSection.tsx
  • web/packages/agenta-settings-ui/src/triggers/TriggerSchedulesSection.tsx
  • web/packages/agenta-settings-ui/src/triggers/TriggerSubscriptionsSection.tsx
  • web/packages/agenta-shared/src/state/featureFlags.ts
  • web/packages/agenta-shared/src/state/index.ts
💤 Files with no reviewable changes (9)
  • web/oss/src/components/pages/settings/Tools/components/AgentaToolsPlaceholder.tsx
  • web/oss/src/state/settings/featureFlags.ts
  • web/oss/src/components/AgentChatSlice/assets/constants.ts
  • web/oss/src/components/pages/settings/Triggers/components/GatewaySchedulesSection.tsx
  • web/oss/src/components/pages/settings/Tools/components/ConnectionsList.tsx
  • web/oss/src/components/pages/settings/Tools/components/ConnectModal.tsx
  • web/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsx
  • web/oss/src/components/pages/settings/Tools/components/ActionsList.tsx
  • web/oss/src/services/profile/index.ts

Comment on lines +17 to +23
// Per-user preferences (the Experiments switches) are scoped by this id, and they are read
// far from Settings — the chat composer asks whether voice is on. Written only once the
// profile answers: clearing it on a signed-out render would hand the next person on this
// browser a blank slate instead of their own settings.
useEffect(() => {
if (user?.id) setActiveUserId(user.id)
}, [user?.id, setActiveUserId])

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clear the active-user preference scope when authentication ends.

The previous user ID remains in storage after logout because ContextSync only writes non-null IDs and useLogout does not reset the atom. A later user can read or modify preferences in the previous user scope before profile synchronization completes.

  • web/mobile/src/features/app/ContextSync.tsx#L17-L23: after useProfile resolves, write user?.id ?? null to clear a stale scope for unauthenticated sessions.
  • web/mobile/src/features/auth/useLogout.ts#L17-L21: reset activeUserIdAtom before redirecting to /auth.
📍 Affects 2 files
  • web/mobile/src/features/app/ContextSync.tsx#L17-L23 (this comment)
  • web/mobile/src/features/auth/useLogout.ts#L17-L21

Comment on lines +54 to 77
const submit = async (text: string, extraFiles: File[] = []) => {
const staged = attachments.files
const uploadedExtras = extraFiles.length
? await attachments.uploadExtraFiles(extraFiles)
: []
// A failed upload adopts the take into the tray; hold the send so nothing is lost.
if (!uploadedExtras) return
const outbound = [...staged, ...uploadedExtras]
try {
// `stagedFilesToParts` THROWS on a file whose upload hasn't settled — reachable via
// Enter, which the send button's `sendDisabled` guard doesn't cover.
const parts = staged.length > 0 ? stagedFilesToParts(staged, sessionId) : undefined
const parts = outbound.length > 0 ? stagedFilesToParts(outbound, sessionId) : undefined
await onSend({text, parts})
attachments.clearAttachments(staged.map((file) => file.uid))
} catch {
// Nothing consumes this promise (RichChatInput's submit is fire-and-forget), so an
// uncaught rejection would leave the user with no message, no error, and no idea a
// send even failed. Keep the attachments staged, put the text back, and say so
// through the composer's own inline channel — the same rejections strip the desktop
// uses when a staged file can't ride the send.
inputRef.current?.setMarkdown(text)
// through the composer's own inline channel.
richInputRef.current?.setMarkdown(text)
attachments.setRejections([{name: "Message", reason: "wasn't sent — try again."}])
attachments.setAttachmentsOpen(true)
}
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate runWithInFlightSubmit and check whether it is exported for cross-package use.
rg -nP --type=ts --type=tsx '\brunWithInFlightSubmit\b' web | head -50
fd -t f -e ts -e tsx . web/packages/agenta-chat/src | xargs -r rg -n 'InFlightSubmit'

Repository: Agenta-AI/agenta

Length of output: 190


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- symbol references ---'
rg -n 'runWithInFlightSubmit' web 2>/dev/null | head -100 || true
printf '%s\n' '--- candidate chat package files ---'
if [ -d web/packages/agenta-chat/src ]; then
  fd -t f -e ts -e tsx . web/packages/agenta-chat/src | head -100
else
  printf '%s\n' 'web/packages/agenta-chat/src not found'
fi
printf '%s\n' '--- target file ---'
sed -n '1,140p' web/mobile/src/features/chat/Composer.tsx

Repository: Agenta-AI/agenta

Length of output: 10379


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- in-flight helper ---'
cat -n web/oss/src/components/AgentChatSlice/assets/inFlightSubmit.ts
printf '%s\n' '--- desktop usage ---'
sed -n '420,455p' web/oss/src/components/AgentChatSlice/AgentConversation.tsx
printf '%s\n' '--- ChatComposer submit wiring ---'
rg -n -C 8 'onSubmit|submit\\(' web/packages/agenta-chat/src/components/ChatComposer.tsx web/packages/agenta-chat/src/hooks/useVoiceComposer.ts web/packages/agenta-chat/src/hooks/useComposerAttachments.ts
printf '%s\n' '--- attachment state and uploads ---'
rg -n -C 8 'files|uploadExtraFiles|clearAttachments|addFiles' web/packages/agenta-chat/src/hooks/useComposerAttachments.ts web/packages/agenta-chat/src/hooks/useAttachmentUploads.ts web/packages/agenta-chat/src/model/attachments.ts

Repository: Agenta-AI/agenta

Length of output: 38039


🏁 Script executed:

#!/bin/bash
set -u
python3 - <<'PY'
from pathlib import Path

paths = [
    Path("web/oss/src/components/AgentChatSlice/assets/inFlightSubmit.ts"),
    Path("web/packages/agenta-chat/src/components/ChatComposer.tsx"),
    Path("web/packages/agenta-chat/src/hooks/useVoiceComposer.ts"),
    Path("web/packages/agenta-chat/src/hooks/useComposerAttachments.ts"),
    Path("web/packages/agenta-chat/src/hooks/useAttachmentUploads.ts"),
]
for path in paths:
    print(f"--- {path} ---")
    if not path.exists():
        print("MISSING")
        continue
    lines = path.read_text().splitlines()
    for i, line in enumerate(lines, 1):
        if path.name == "inFlightSubmit.ts" or any(k in line for k in (
            "onSubmit", "submit", "uploadExtraFiles", "clearAttachments",
            "files", "addFiles", "onSendVoiceMessage", "voice",
        )):
            lo, hi = max(1, i - 3), min(len(lines), i + 8)
            for n in range(lo, hi + 1):
                print(f"{n:4}: {lines[n-1]}")
            print()
PY

Repository: Agenta-AI/agenta

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- exact uploadExtraFiles implementation ---'
sed -n '177,217p' web/packages/agenta-chat/src/hooks/useComposerAttachments.ts
printf '%s\n' '--- rich input implementation files ---'
fd -i -t f -e ts -e tsx . web | rg '/(RichChatInput|rich-chat-input|richChatInput)' | head -30
printf '%s\n' '--- exports for the helper and chat package ---'
cat -n web/oss/src/components/AgentChatSlice/assets/index.ts 2>/dev/null || true
cat -n web/packages/agenta-chat/src/hooks/index.ts
cat -n web/packages/agenta-chat/src/index.ts
printf '%s\n' '--- mobile package imports and package boundaries ---'
rg -n 'from "`@agenta/oss`|from "`@agenta/chat`|inFlightSubmit|AgentConversation' web/mobile web/packages/agenta-chat web/oss | head -100

Repository: Agenta-AI/agenta

Length of output: 16444


🏁 Script executed:

#!/bin/bash
set -u
python3 - <<'PY'
import asyncio

async def simulate():
    files = [{"uid": "staged-1"}]
    clear_calls = []
    sends = []

    async def upload_extra_files():
        await asyncio.sleep(0)
        return []

    async def on_send(payload):
        sends.append(payload)
        await asyncio.sleep(0)

    async def submit(text):
        staged = files[:]  # each invocation captures the same current tray
        await upload_extra_files()
        outbound = staged
        await on_send({"text": text, "parts": outbound})
        clear_calls.append([f["uid"] for f in staged])

    await asyncio.gather(submit("first"), submit("second"))
    print({"send_count": len(sends), "sent_uids": [[f["uid"] for f in p["parts"]] for p in sends],
           "clear_calls": clear_calls})

asyncio.run(simulate())
PY

Repository: Agenta-AI/agenta

Length of output: 260


🏁 Script executed:

#!/bin/bash
set -u
python3 - <<'PY'
import asyncio

async def simulate():
    files = [{"uid": "staged-1"}]
    clear_calls = []
    sends = []

    async def upload_extra_files():
        await asyncio.sleep(0)
        return []

    async def on_send(payload):
        sends.append(payload)
        await asyncio.sleep(0)

    async def submit(text):
        staged = files[:]
        await upload_extra_files()
        await on_send({"text": text, "parts": staged})
        clear_calls.append([f["uid"] for f in staged])

    await asyncio.gather(submit("first"), submit("second"))
    print({
        "send_count": len(sends),
        "sent_uids": [[f["uid"] for f in p["parts"]] for p in sends],
        "clear_calls": clear_calls,
    })

asyncio.run(simulate())
PY

Repository: Agenta-AI/agenta

Length of output: 260


🏁 Script executed:

#!/bin/bash
set -u
printf '%s\n' '--- submit plugin and rich input event path ---'
cat -n web/packages/agenta-ui/src/RichChatInput/plugins/SubmitPlugin.ts
sed -n '1,240p' web/packages/agenta-ui/src/RichChatInput/RichChatInput.tsx
printf '%s\n' '--- send button gating ---'
cat -n web/packages/agenta-ui/src/RichChatInput/ComposerSendButton.tsx
cat -n web/packages/agenta-ui/src/RichChatInput/plugins/SendButton.tsx
printf '%s\n' '--- helper package metadata and aliases ---'
rg -n -C 3 '"`@agenta/`(chat|oss)"|agenta-chat|paths' web/tsconfig*.json web/*/tsconfig*.json web/package.json web/pnpm-workspace.yaml 2>/dev/null | head -120

Repository: Agenta-AI/agenta

Length of output: 21139


Guard submit against concurrent sends.

Enter and voice submissions can overlap while uploadExtraFiles or onSend awaits. Both calls can send the same staged files and clear them twice. Move runWithInFlightSubmit from the OSS-only path into @agenta/chat, export it, and wrap this submit with it.

Comment on lines 7 to 9
// gap-8: sections are separated by whitespace, never a rule.
return (
<div className="flex flex-col">

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The comment and the class list disagree about the section gap.

Line 7 documents gap-8. Line 9 sets className="flex flex-col" with no gap utility, so the three trigger sections render flush against each other. The sibling page web/oss/src/components/pages/settings/Tools/Tools.tsx uses flex flex-col gap-8. Restore the gap, or remove the stale comment if the sections now own their own spacing.

🐛 Proposed fix
     // gap-8: sections are separated by whitespace, never a rule.
     return (
-        <div className="flex flex-col">
+        <div className="flex flex-col gap-8">
📝 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
// gap-8: sections are separated by whitespace, never a rule.
return (
<div className="flex flex-col">
// gap-8: sections are separated by whitespace, never a rule.
return (
<div className="flex flex-col gap-8">

Comment on lines +16 to +18
export const deleteAccount = async (): Promise<void> => {
await axios.delete(`${getAgentaApiUrl()}/profile`)
}

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline web/packages/agenta-sdk/src/resources.ts --items all
rg -n -C 3 'profile|axios\.(get|delete)' \
  web/packages/agenta-sdk/src/resources.ts \
  web/packages/agenta-entities/src/profile/index.ts

Repository: Agenta-AI/agenta

Length of output: 5476


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- profile entity module ---'
cat -n web/packages/agenta-entities/src/profile/index.ts

printf '%s\n' '--- SDK resource accessors ---'
cat -n web/packages/agenta-sdk/src/resources.ts

printf '%s\n' '--- profile client and endpoint definitions ---'
rg -n -C 4 'Profile|profile|deleteAccount|delete.*profile|/profile' \
  web/packages/agenta-sdk web/packages/agenta-entities web/packages \
  -g '*.ts' -g '*.tsx' -g '*.json' | head -n 300

Repository: Agenta-AI/agenta

Length of output: 37935


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- tracked API-client files ---'
git ls-files | rg '(^|/)(api-client|generated|resources)(/|\\.)|agentaai/api-client|openapi|fern' | head -n 300

printf '%s\n' '--- package references and workspace metadata ---'
rg -n -C 3 '`@agentaai/api-client`|`@agenta/sdk/resources`|agenta-sdk|profile' \
  web/package.json web/pnpm-lock.yaml web/packages/*/package.json \
  pnpm-workspace.yaml package.json 2>/dev/null | head -n 300

printf '%s\n' '--- all tracked profile/API endpoint references ---'
git grep -n -E '(/profile|deleteAccount|ProfileClient|profileClient)' -- \
  ':!web/packages/agenta-entities/src/profile/index.ts' \
  ':!web/packages/agenta-sdk/src/resources.ts' | head -n 300

Repository: Agenta-AI/agenta

Length of output: 30185


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- generated resource directories ---'
git ls-files 'web/packages/agenta-api-client/src/generated/api/resources/*' \
  | sed -n '1,500p' \
  | awk -F'/resources/' 'NF > 1 {split($2, a, "/"); print a[1]}' \
  | sort -u

printf '%s\n' '--- profile matches in generated client and SDK metadata ---'
rg -n -i -C 4 'profile|delete.*account|account.*delete|/profile' \
  web/packages/agenta-api-client \
  web/packages/agenta-sdk \
  web/package.json \
  web/packages/agenta-api-client/package.json \
  web/packages/agenta-sdk/package.json \
  2>/dev/null || true

printf '%s\n' '--- relevant package exports and generation scripts ---'
cat -n web/packages/agenta-api-client/package.json
cat -n web/packages/agenta-sdk/package.json

Repository: Agenta-AI/agenta

Length of output: 25990


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- users resource exports ---'
cat -n web/packages/agenta-api-client/src/generated/api/resources/users/index.ts
cat -n web/packages/agenta-api-client/src/generated/api/resources/users/exports.ts
cat -n web/packages/agenta-api-client/src/generated/api/resources/index.ts

printf '%s\n' '--- SDK config and accessor conventions ---'
cat -n web/packages/agenta-sdk/src/config.ts
sed -n '1,140p' web/packages/agenta-sdk/src/resources.ts

printf '%s\n' '--- users client implementation ---'
sed -n '1,145p' web/packages/agenta-api-client/src/generated/api/resources/users/client/Client.ts

Repository: Agenta-AI/agenta

Length of output: 19608


Use the Fern users resource accessor.

Expose getUsersClient() from web/packages/agenta-sdk/src/resources.ts and call getUsersClient().deleteUserAccount() instead of raw Axios.

Source: Coding guidelines

Comment on lines +46 to +49
const result = await handleRefresh(connection.id)
const redirectUrl = getRedirectUrl(result.connection)

if (!redirectUrl) return

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle a rejected refresh request.

handleRefresh calls the gateway API without a try/catch here. The caller at line 114 uses void onRefresh(record), so a rejection becomes an unhandled promise rejection and the user receives no feedback. Wrap the call and report the failure, as GatewayToolsSection does.

🛠️ Proposed fix
-        const result = await handleRefresh(connection.id)
-        const redirectUrl = getRedirectUrl(result.connection)
+        let redirectUrl: string | undefined
+        try {
+            const result = await handleRefresh(connection.id)
+            redirectUrl = getRedirectUrl(result.connection)
+        } catch {
+            message.error("Failed to refresh connection")
+            return
+        }
📝 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
const result = await handleRefresh(connection.id)
const redirectUrl = getRedirectUrl(result.connection)
if (!redirectUrl) return
let redirectUrl: string | undefined
try {
const result = await handleRefresh(connection.id)
redirectUrl = getRedirectUrl(result.connection)
} catch {
message.error("Failed to refresh connection")
return
}
if (!redirectUrl) return

Comment on lines 153 to +155
const confirmDelete = useCallback(
(connection: ToolConnection) => {
AlertPopup({
confirm?.({

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The new confirm prop is captured but missing from both dependency arrays. The migration replaced the directly imported AlertPopup with the injected confirm prop, so both memoized handlers now close over a prop that is not declared as a dependency. A new confirm identity from the parent leaves the stale reference in place and the confirmation dialog stops opening. TriggerConnectionsSection.tsx lists confirm correctly at lines 107 and 127.

  • web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx#L153-L155: change the confirmDelete dependency array at line 170 to [confirm, handleDelete].
  • web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx#L173-L175: change the confirmRevoke dependency array at line 190 to [confirm, handleRevoke].
📍 Affects 1 file
  • web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx#L153-L155 (this comment)
  • web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx#L173-L175

Comment on lines +43 to +48
<Input
placeholder="Search integrations…"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-9"
/>

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.

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

Give the search input an accessible name.

The input carries a placeholder only. The adjacent MagnifyingGlass icon is decorative and pointer-events-none, so no label is associated with the field. Screen reader users receive no stable name. Add aria-label.

♿ Proposed fix
                 <Input
+                    aria-label="Search integrations"
                     placeholder="Search integrations…"
                     value={search}
                     onChange={(e) => setSearch(e.target.value)}
                     className="pl-9"
                 />
📝 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
<Input
placeholder="Search integrations…"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-9"
/>
<Input
aria-label="Search integrations"
placeholder="Search integrations…"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-9"
/>

Comment on lines +76 to +80
<button
type="button"
onClick={onClick}
className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary"
>

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.

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

Add a visible focus state to the integration card button.

The card is now a native button, so keyboard users can reach it. The class list defines a hover border only. Without a focus style, keyboard users cannot see the current card. Add a focus-visible ring that works in light and dark themes.

♿ Proposed fix
-            className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary"
+            className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary focus-visible:border-colorPrimary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-colorPrimary"

As per coding guidelines: "Implement light and dark appearance and interaction states for every added or changed UI element, and verify both themes."

📝 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
<button
type="button"
onClick={onClick}
className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary"
>
<button
type="button"
onClick={onClick}
className="cursor-pointer rounded-lg border border-solid border-colorBorderSecondary bg-colorBgContainer p-3 text-left hover:border-colorPrimary focus-visible:border-colorPrimary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-colorPrimary"
>

Source: Coding guidelines

Comment on lines +210 to +217
{
key: "delete",
label: "Delete",
icon: <Trash size={16} />,
danger: true,
hidden: readOnly,
onClick: () => handleDelete(record),
},

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require confirmation before a schedule is deleted.

The delete action calls handleDelete(record) directly, which removes the schedule immediately. The operation is irreversible. GatewayToolsSection and TriggerConnectionsSection both route delete through an injected confirm callback and hide the action when confirm is absent. Add the same confirm prop here and apply it to delete.

🛡️ Proposed fix
 export interface TriggerSchedulesSectionProps {
+    /** Destructive confirmation — the desktop's AlertPopup, a sheet elsewhere. */
+    confirm?: (args: {title: string; message: string; onOk: () => void | Promise<void>}) => void
     /** Hides create/edit and skips the drawer, which is still antd-backed. */
     readOnly?: boolean
 }
                         {
                             key: "delete",
                             label: "Delete",
                             icon: <Trash size={16} />,
                             danger: true,
-                            hidden: readOnly,
-                            onClick: () => handleDelete(record),
+                            hidden: readOnly || !confirm,
+                            onClick: () =>
+                                confirm?.({
+                                    title: "Delete Schedule",
+                                    message:
+                                        "Are you sure you want to delete this schedule? This action is irreversible.",
+                                    onOk: () => handleDelete(record),
+                                }),
                         },

Comment on lines +103 to +114
const handleDelete = useCallback(
async (record: TriggerSubscription) => {
if (!record.id) return
try {
await remove(record.id)
message.success("Subscription deleted")
} catch {
message.error("Failed to delete subscription")
}
},
[remove],
)

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Add a confirmation step before delete and revoke.

handleDelete calls remove(record.id) immediately, and handleRevoke calls revoke(record.id) immediately. The row actions at Lines 230-245 invoke them on a single click. A user can permanently delete a subscription with no confirmation and no undo.

The sibling sections take a host-supplied confirm dialog for exactly this reason. TriggerConnectionsSection and GatewayToolsSection both accept a confirm prop, and OSS passes AlertPopup in web/oss/src/components/pages/settings/Triggers/components/GatewayTriggersSection.tsx and web/oss/src/components/pages/settings/Tools/Tools.tsx. This section accepts only readOnly, so the destructive-action contract is inconsistent across the migrated trigger sections.

Add an optional confirm prop with the same shape the other sections use. If a host passes no confirm, hide the destructive actions, which matches the behavior described for GatewayToolsSection.

🛡️ Proposed confirm prop wiring
 export interface TriggerSubscriptionsSectionProps {
     /** Hides create/edit and skips the drawer, whose form is still antd-backed. */
     readOnly?: boolean
+    /** Host confirm dialog. Destructive actions stay hidden when a host brings none. */
+    confirm?: (options: {
+        title: string
+        message: string
+        okText?: string
+        cancelText?: string
+        type?: "warning" | "error" | "info"
+        onOk: () => void | Promise<void>
+    }) => void
 }

 export default function TriggerSubscriptionsSection({
     readOnly,
+    confirm,
 }: TriggerSubscriptionsSectionProps = {}) {
     const handleDelete = useCallback(
-        async (record: TriggerSubscription) => {
-            if (!record.id) return
-            try {
-                await remove(record.id)
-                message.success("Subscription deleted")
-            } catch {
-                message.error("Failed to delete subscription")
-            }
-        },
-        [remove],
+        (record: TriggerSubscription) => {
+            if (!record.id || !confirm) return
+            const id = record.id
+            confirm({
+                title: "Delete subscription",
+                message: "This deletes the subscription permanently.",
+                okText: "Delete",
+                type: "warning",
+                onOk: async () => {
+                    try {
+                        await remove(id)
+                        message.success("Subscription deleted")
+                    } catch {
+                        message.error("Failed to delete subscription")
+                    }
+                },
+            })
+        },
+        [remove, confirm],
     )

Apply the same pattern to handleRevoke, then hide both actions when no confirm is supplied:

                         {
                             key: "revoke",
-                            hidden: readOnly,
+                            hidden: readOnly || !confirm,
                             label: "Revoke",
                         {
                             key: "delete",
                             label: "Delete",
                             icon: <Trash size={16} />,
                             danger: true,
-                            hidden: readOnly,
+                            hidden: readOnly || !confirm,
                             onClick: () => handleDelete(record),
                         },

Run the following script to confirm that the replaced OSS implementation used a confirmation dialog:

#!/bin/bash
# Description: Check the confirm-prop contract across the migrated settings sections and their OSS bindings.
set -euo pipefail

echo "== confirm prop across settings-ui sections =="
rg -n -C 4 'confirm' web/packages/agenta-settings-ui/src/triggers web/packages/agenta-settings-ui/src/tools

echo "== OSS bindings passing AlertPopup =="
rg -n -C 3 'AlertPopup' web/oss/src/components/pages/settings

echo "== history of the replaced subscriptions section =="
git log --oneline -n 20 -- 'web/oss/src/components/pages/settings/Triggers/**'

@coderabbitai coderabbitai Bot left a comment

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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 13

🧹 Nitpick comments (5)
web/mobile/src/features/settings/PreferencesTab.tsx (1)

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

Memoize the flags prop.

Lines 18-35 allocate a new object array on every render. Create flags with useMemo and pass that value to PreferencesPage.

As per coding guidelines, “Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders.”

Source: Coding guidelines

web/packages/agenta-settings-ui/src/triggers/TriggerSubscriptionsSection.tsx (1)

203-246: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Wrap the actions render prop in useCallback.

actions is a new function on every render, and it builds a fresh array of objects containing JSX for each row. Any row-level memoization inside DataTable cannot hold. The columns and rows above already use useMemo, so this prop is the remaining unstable input.

♻️ Proposed memoization
+    const actions = useCallback(
+        (record: SubscriptionRow) => [
+            /* move the existing entries here unchanged */
+        ],
+        [readOnly, openDeliveries, handleEdit, handleRefresh, handleRevoke, handleDelete],
+    )

Then pass actions={actions} to DataTable.

As per coding guidelines: "Memoize inline arrays containing objects or JSX when passing them as props to avoid unnecessary rerenders."

Source: Coding guidelines

web/packages/agenta-settings-ui/src/index.ts (1)

33-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Export the props types for both trigger sections.

Both modules export their props types, but the barrel exports only the default components. Re-export TriggerSchedulesSectionProps and TriggerSubscriptionsSectionProps so consumers can type wrappers around these sections.

web/packages/agenta-settings-ui/package.json (1)

24-26: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move jotai and jotai-tanstack-query to peer dependencies.

The current lockfile deduplicates Jotai to 2.20.0, but the dependency declarations can allow separate instances for consumers. Declare both packages as peers and keep them in devDependencies. jotai-tanstack-query also requires the host's @tanstack/react-query and Jotai instances.

web/packages/agenta-settings-ui/src/triggers/TriggerConnectionsSection.tsx (1)

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

Export one shared type for the confirm callback.

This file declares onOk: () => Promise<void>. GatewayToolsSection.tsx line 41 and ConnectionsList.tsx line 16 declare the same callback as onOk: () => void | Promise<void>. Three inline copies with two signatures will drift, and the narrower signature here rejects a synchronous onOk from a caller that shares one adapter across sections. Declare the type once in the package and import it in all three files.

♻️ Proposed refactor
// web/packages/agenta-settings-ui/src/types.ts
export interface ConfirmArgs {
    title: string
    message: string
    onOk: () => void | Promise<void>
}

export type ConfirmFn = (args: ConfirmArgs) => void
+import type {ConfirmFn} from "../types"
+
 export interface TriggerConnectionsSectionProps {
     /** Destructive confirmation — the desktop's AlertPopup, a sheet elsewhere. */
-    confirm?: (args: {title: string; message: string; onOk: () => Promise<void>}) => void
+    confirm?: ConfirmFn
     /** Hides connect/refresh and skips the catalog drawer. */
     readOnly?: boolean
 }

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 444dd719-48da-441d-b629-64c4df45d309

📥 Commits

Reviewing files that changed from the base of the PR and between b303148 and f8a5737.

⛔ Files ignored due to path filters (2)
  • web/mobile/src/styles/theme.generated.css is excluded by !**/*.generated.*
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (62)
  • web/mobile/scripts/generate-shadcn-tokens.ts
  • web/mobile/src/features/app/ContextSync.tsx
  • web/mobile/src/features/auth/useLogout.ts
  • web/mobile/src/features/chat/Composer.tsx
  • web/mobile/src/features/nav/DrawerProjectSwitcher.tsx
  • web/mobile/src/features/settings/AccountTab.tsx
  • web/mobile/src/features/settings/PreferencesTab.tsx
  • web/mobile/src/features/settings/SettingsScreen.tsx
  • web/mobile/src/styles/globals.css
  • web/oss/src/components/AgentChatSlice/AgentConversation.tsx
  • web/oss/src/components/AgentChatSlice/assets/constants.ts
  • web/oss/src/components/AgentChatSlice/components/AgentComposerDock.tsx
  • web/oss/src/components/AgentChatSlice/components/ConnectModelBanner.tsx
  • web/oss/src/components/AgentChatSlice/components/Inspector/InspectSessionButton.tsx
  • web/oss/src/components/AgentChatSlice/components/clientTools/useConnectFlow.ts
  • web/oss/src/components/AgentChatSlice/hooks/useTurnInspector.ts
  • web/oss/src/components/SessionInspector/PanelSessionInspectorButton.tsx
  • web/oss/src/components/pages/settings/Account/DeleteAccount.tsx
  • web/oss/src/components/pages/settings/Preferences/Preferences.tsx
  • web/oss/src/components/pages/settings/Tools/Tools.tsx
  • web/oss/src/components/pages/settings/Tools/components/ActionsList.tsx
  • web/oss/src/components/pages/settings/Tools/components/AgentaToolsPlaceholder.tsx
  • web/oss/src/components/pages/settings/Tools/components/ConnectModal.tsx
  • web/oss/src/components/pages/settings/Tools/components/ConnectionsList.tsx
  • web/oss/src/components/pages/settings/Triggers/Triggers.tsx
  • web/oss/src/components/pages/settings/Triggers/components/GatewaySchedulesSection.tsx
  • web/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsx
  • web/oss/src/components/pages/settings/Triggers/components/GatewayTriggersSection.tsx
  • web/oss/src/lib/onboarding/atoms.ts
  • web/oss/src/services/profile/index.ts
  • web/oss/src/state/settings/featureFlags.ts
  • web/packages/agenta-chat/src/assets/index.ts
  • web/packages/agenta-chat/src/assets/voice.ts
  • web/packages/agenta-chat/src/components/MicPermissionNotice.tsx
  • web/packages/agenta-chat/src/components/RecordingBar.tsx
  • web/packages/agenta-chat/src/components/RecordingWaveform.tsx
  • web/packages/agenta-chat/src/components/RevealCollapse.tsx
  • web/packages/agenta-chat/src/components/VoiceInputButton.tsx
  • web/packages/agenta-chat/src/components/index.ts
  • web/packages/agenta-chat/src/hooks/index.ts
  • web/packages/agenta-chat/src/hooks/useAudioRecorder.ts
  • web/packages/agenta-chat/src/hooks/useVoiceComposer.ts
  • web/packages/agenta-chat/src/hooks/useVoiceInput.ts
  • web/packages/agenta-chat/tests/unit/assets/voice.test.ts
  • web/packages/agenta-entities/src/profile/index.ts
  • web/packages/agenta-settings-ui/package.json
  • web/packages/agenta-settings-ui/src/index.ts
  • web/packages/agenta-settings-ui/src/tools/ActionsList.tsx
  • web/packages/agenta-settings-ui/src/tools/AgentaToolsPlaceholder.tsx
  • web/packages/agenta-settings-ui/src/tools/ConnectModal.tsx
  • web/packages/agenta-settings-ui/src/tools/ConnectionsList.tsx
  • web/packages/agenta-settings-ui/src/tools/GatewayToolsSection.tsx
  • web/packages/agenta-settings-ui/src/tools/IntegrationDetail.tsx
  • web/packages/agenta-settings-ui/src/tools/IntegrationGrid.tsx
  • web/packages/agenta-settings-ui/src/tools/hooks/useIntegrationDetail.ts
  • web/packages/agenta-settings-ui/src/tools/hooks/useToolsConnections.ts
  • web/packages/agenta-settings-ui/src/tools/hooks/useToolsIntegrations.ts
  • web/packages/agenta-settings-ui/src/triggers/TriggerConnectionsSection.tsx
  • web/packages/agenta-settings-ui/src/triggers/TriggerSchedulesSection.tsx
  • web/packages/agenta-settings-ui/src/triggers/TriggerSubscriptionsSection.tsx
  • web/packages/agenta-shared/src/state/featureFlags.ts
  • web/packages/agenta-shared/src/state/index.ts
💤 Files with no reviewable changes (9)
  • web/oss/src/components/pages/settings/Tools/components/AgentaToolsPlaceholder.tsx
  • web/oss/src/state/settings/featureFlags.ts
  • web/oss/src/components/AgentChatSlice/assets/constants.ts
  • web/oss/src/components/pages/settings/Triggers/components/GatewaySchedulesSection.tsx
  • web/oss/src/components/pages/settings/Tools/components/ConnectionsList.tsx
  • web/oss/src/components/pages/settings/Tools/components/ConnectModal.tsx
  • web/oss/src/components/pages/settings/Triggers/components/GatewaySubscriptionsSection.tsx
  • web/oss/src/components/pages/settings/Tools/components/ActionsList.tsx
  • web/oss/src/services/profile/index.ts
🛑 Comments failed to post (1)
web/packages/agenta-chat/src/hooks/useAudioRecorder.ts (1)

138-143: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard start against re-entry while the permission request is pending.

The guard only checks recRef.current. During status === "requesting" that ref is still null, so a second start() call issues a second getUserMedia. The first resolved stream then overwrites streamRef.current, and its tracks are never stopped. The microphone stays open until the page unloads, and two MediaRecorder instances can exist.

🔒 Proposed fix using a pending ref
     const cancelledRef = useRef(false)
+    // `recRef` is still null while the permission prompt is up, so it cannot block a second start.
+    const requestingRef = useRef(false)
     const start = useCallback(() => {
-        if (!supported || recRef.current) return
+        if (!supported || recRef.current || requestingRef.current) return
         setError(null)
         cancelledRef.current = false
         erroredRef.current = false
+        requestingRef.current = true
         setStatus("requesting")

Clear the flag in both settlement paths:

             .then((stream) => {
+                requestingRef.current = false
                 if (cancelledRef.current) {
             .catch((e: unknown) => {
+                requestingRef.current = false
                 teardown()
📝 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.

    const start = useCallback(() => {
        if (!supported || recRef.current || requestingRef.current) return
        setError(null)
        cancelledRef.current = false
        erroredRef.current = false
        requestingRef.current = true
        setStatus("requesting")

Tools and Triggers turned out to be nearly free of app-layer coupling — between
them they reach @/oss for AlertPopup and one URL helper, nothing else. Their data
already comes from @agenta/entities and @agenta/entity-ui, so antd was the only
thing keeping them out of the package.

The connections list moves first. AlertPopup becomes a `confirm` prop, and the
two actions that need it hide themselves when a host does not pass one, rather
than opening nothing.

OSS keeps a four-line binding at the old path so the Triggers page is unchanged.

Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile.
Second of the three Triggers sections. Same conversion as the connections list:
DataTable, @agenta/ui primitives, no antd.

The cron column's hover tooltip becomes a title attribute — a Radix tooltip per
row is a popper per row, and the raw cron string is a hint, not a control.

Triggers.tsx imports this one from the package directly rather than through a
re-export stub at the old path, which OSS lint forbids.

Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile.
Last of the three Triggers sections. Triggers.tsx now composes two shared
sections and one thin OSS binding, which exists only because the connections
list needs AlertPopup.

The Subscribe button's tooltip moves onto a wrapping span: a disabled button
fires no pointer events, so antd's Tooltip-on-disabled-child behaviour has to be
reproduced deliberately or the "Connect an app first" hint never appears — which
is the one case where it matters.

Also drops `fixed: "left"` from the columns carried over in all three sections.
It was an antd frozen-column setting; DataTable does not freeze, so it was inert.

Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile.
The whole Tools tree moves: the section, the integration grid and detail pane,
the connections and actions lists, the connect dialog, and the three hooks. OSS
keeps a Tools.tsx that passes AlertPopup as `confirm`.

Three conversions worth naming:

ConnectModal dropped antd's Form for controlled state and Field. antd's
validateFields both validated and read the values; with one required field that
is a trim check, so the form instance bought nothing a package can use.

The integration cards were antd Card with onClick — now real buttons, so they
are keyboard-reachable and announce as controls, which a clickable div never did.

Catalog logos drop next/image for img. The URLs are remote and arbitrary, and
next/image needs every host allow-listed in next.config — configuration a
package cannot own and should not require of its consumers.

useToolsConnections had one consumer outside settings (the chat client-tools
connect flow), which now imports it from the package.

Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile. No antd left in
either the tools or triggers directories.
Both were extracted last commit but wiring them as-is would have shipped the bug
from the white settings tables again. The lists are antd-free now, but the
create/edit drawers are not: TriggerSubscriptionDrawer and the tool execution
drawer render SubscriptionForm and SchemaForm, 2306 lines still built on antd
Form. With no ConfigProvider on /m those come out light on a dark page.

So the four sections take `readOnly`, which hides create/edit/run and does not
mount the drawers at all — nothing to open, rather than something that opens
broken. /m passes it; OSS does not and is unchanged.

canShowTools/canShowTriggers flip to true in the mobile access object, which was
the only thing keeping both tabs off the rail.

Gates: lint 24/24, tsc 0 across settings-ui, oss, ee and mobile.
… and a failure

Three defects the extraction introduced or carried over:

- `confirmDelete`/`confirmRevoke` closed over the injected `confirm` prop without
  listing it, so a new `confirm` identity from the host left the stale closure in
  place and the delete/revoke dialog silently stopped opening. The sibling
  `TriggerConnectionsSection` already lists it; match it.
- The OAuth popup poll lived inside the async handler with no cleanup path, so an
  unmount mid-flow left an interval running for the life of the page, then called
  `invalidate()`, `handleClose()` and setState against a dead component. Hold the
  timer in a ref and clear it on unmount as well as on popup-close.
- A rejected create (ConnectModal) and a rejected refresh (ConnectionsList) told
  the user nothing — the modal just sat there. Both now report through
  `@agenta/ui/app-message`, the channel the section already uses.
The search input had no accessible name (a placeholder is not one), and the
integration cards — buttons — had a hover border but no focus state, so a
keyboard user could not see where they were in the grid. The cards take the
package's shared focus ring (the one Button, Segmented and Checkbox use): a 4px
token-driven outline on :focus-visible only, correct in both themes and absent
on a mouse click.
The comment says "gap-8: sections are separated by whitespace, never a rule",
but the extraction dropped the class along with the old gap-6 header block, so
the three sections butted straight into each other. Every sibling settings page
that composes sections this way — Tools, Secrets, Vault — uses gap-8, so the
comment is the correct half and the class is the one to fix.
Nothing guarded `submit` against re-entry, and Enter is not covered by the send
button's disabled state — a second Enter during the upload/send round-trip
re-read the same staged tray and posted the message twice. A ref guard released
in `finally` covers both outcomes, so the recently added failure path (which
restores the draft and reports through the attachments tray) still unlocks the
composer instead of wedging it.
`activeUserIdAtom` was written when a profile resolved and never cleared, so the
Experiments switches and the composer's voice setting could carry into the next
person to use the browser.

The file's existing intent is kept: a *falsy* profile is not a finished one —
`user` is null while the query is in flight (the id is storage-backed precisely
so preferences resolve before that lands) and null when the request failed, and
clearing on either would hand a returning user a blank slate mid-load. Only a
settled answer — not pending, no error, still null — is the 401 that means the
session is over, and that clears the scope.

Sign-out is the unambiguous signal, so it clears there too, and drops the cached
profile with it: that entry is the ended session's identity, and leaving it would
let the stale answer write the old id straight back. Clearing removes only the
pointer — each user's flags stay under their own `agenta:settings:<id>:*` keys.
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