Resolves conflicts to keep:
- Apps toolset surface from toolchains-spike (registry, factory, normalizer,
AppDefinition, /catalog/apps/:kind/operations endpoint, FE registry +
modals + list panel + tool-step labels).
- Builder/integration polish from n8n-agents (i18n-keyed help/connected
copy, narrower default chat column, agent RBAC resource).
Drops the inline displayHelpText/displayConnectedText fields on
ChatIntegrationDescriptor and the per-platform classes — the strings now
live in the FE i18n catalog keyed by integration type.
Adds the missing 'agent' entry to rbac.store.ts scopesByResourceId so the
new Resource union type compiles.
Restore the agents SDK to a single canonical import path
(`from '@n8n/agents'`) for backend consumers, and decouple the editor-ui
from the SDK entirely so the package stays server-only.
The previous split via `exports` + `typesVersions` made `toolsets` a
second-class subpath while every other SDK export lived at the package
root. The asymmetry papered over the real issue: the SDK is server-side
(pulls undici via MCP/AI-SDK), so the editor-ui shouldn't depend on it.
- editor-ui: new `features/agents/utils/appToolsets.ts` mirrors the
registry data + walker + types the FE needs (~200 lines). Drop the
`@n8n/agents` workspace dep. Drift between this and the BE registry
is enforced by code review when adding new apps.
- `@n8n/agents`: drop the `exports` and `typesVersions` blocks. Restore
flat re-exports of `APP_REGISTRY`, `findAppDefinition`,
`buildOperationsFromDescription`, `buildManifest`, and toolsets types
from `src/index.ts` so cli can keep `from '@n8n/agents'` for
everything.
- cli: consolidate imports back to `@n8n/agents`. No subpath imports.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small follow-ups from the cleanup's final review:
- `app-toolset-factory.ts`: drop the hand-rolled `zodObjectToJson` /
`zodTypeNameToJson` helpers and use `zodToJsonSchema` from `@n8n/agents`.
The hand-rolled walker was non-recursive and dropped nested object
structure for `collection` / `fixedCollection` params; the shared
utility recurses correctly via `zod-to-json-schema`.
- `useCredentialScopes`: add a 30s TTL to the module-level scope cache so
re-opening the modal after re-OAuth surfaces refreshed scopes within
the window. Failures are still uncached. Also export an
`invalidateCredentialScopes(id)` for explicit invalidation when callers
know the scopes have changed.
- `buildOperationsFromDescription`: emit one OperationEntry per resource
when an operation group declares multiple in `displayOptions.show.resource`,
rather than dropping all but the first. Dedupe by `resource:operation`
to handle the rare case of overlapping operation groups. Two new tests
cover the multi-resource and dedupe paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The agents toolsets cleanup made editor-ui import from @n8n/agents for
the registry, which transitively bundled the full server SDK (MCP client,
AI SDK adapters) and pulled `undici` into the browser bundle, breaking
`pnpm build`. Tree-shaking can't help here — the package compiles to CJS
and isn't marked side-effect-free.
Declare the boundary at the package surface instead:
- `exports` field: `.` continues to expose the full SDK; new `./toolsets`
entry exposes only the pure-data + pure-algorithm surface
(`AppDefinition`, `OperationEntry`, `buildOperationsFromDescription`,
`buildManifest`, `APP_REGISTRY`, `findAppDefinition`).
- `typesVersions` mirrors the subpath so TypeScript resolves it under
the repo's `moduleResolution: "node"` (legacy mode ignores `exports`).
- Drop the flat re-exports of toolsets symbols from `src/index.ts`. The
registry now has exactly one canonical import path,
`@n8n/agents/toolsets`. Backend code that wants the namespace can keep
using `import { toolsets } from '@n8n/agents'`.
- Update cli (`agents.service`, `app-toolset-factory` + its test) and
editor-ui (`AgentAppsModal`, `AgentAppConfigModal`, `AgentAppsListPanel`,
`AgentChatToolSteps`) to import from `@n8n/agents/toolsets`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restructure the Gmail-only "apps/toolsets" POC so adding a second app is a
one-file change. Each integration ships as an `AppDefinition` in
`@n8n/agents/src/toolsets/registry/`; backend and frontend consume the
registry through the same flat re-exports off `@n8n/agents`.
- New `@n8n/agents/toolsets/` module: `AppDefinition` + `OperationEntry`
types, `buildOperationsFromDescription` (single source of truth for the
registry walk + scope-based status classification), `buildManifest`
(string/function/default-deriver resolution), and `APP_REGISTRY` with
Gmail as the first entry.
- Generic `buildAppToolset` factory in cli replaces the Gmail-specific
`buildGmailToolset`. `agents.service.attachAppToolsets` now resolves
app kinds via `findAppDefinition` and skips unknown kinds with a warn
log instead of rejecting at save time.
- New `GET /credentials/:credentialId/oauth-scopes` endpoint +
`CredentialsService.getOAuthGrantedScopes` helper. Reads
`oauthTokenData.scope` (post-grant) with a fallback to declared
`scope` (pre-grant). Distinct from the existing RBAC `getCredentialScopes`.
- New `useCredentialScopes` FE composable feeds the App config modal so
per-operation status badges classify against the credential's
actually-granted scopes.
- `AgentJsonAppRef.kind` widened from the `'gmail'` literal to plain
string. Forward-compat: a config saved with an unknown kind loads
cleanly.
- Editor-ui: `AgentAppsModal` rebuilt on `Modal.vue` + `NodeCredentials`
(drops the hand-rolled backdrop and raw select). `AgentAppConfigModal`,
`AgentAppsListPanel`, and `AgentChatToolSteps` resolve icon, label, and
dispatcher names from the registry.
- Drop the Gmail-only `gmail-toolset.ts` and `gmailOperations.ts`.
Tests: 10 new in `@n8n/agents/toolsets`, 6 in
`cli/src/modules/agents/toolsets`, 4 in `cli/src/credentials`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AgentBuilderView.test: extend the vue-router mock with onBeforeRouteUpdate
(added in the recent unpublished-changes-on-agent-switch fix), stub the
editor-column panels (info/advanced/memory/evals/tools/integrations/sessions)
so the view test doesn't have to mock the chatHub/users/sessions ecosystem,
and update the chat-mode assertions to the current "default to build unless
URL pins a session" behaviour.
- AgentBuilderHeader.test: assert the single project breadcrumb the header
actually renders today (workspace crumb removed earlier).
- AgentPublishButton.test: bump testTimeout — first SFC compile crept past
the default 5 s on slower CI workers, causing a flaky timeout on the first
case only.
- AgentConfigTree.test, AgentCard.test: delete. Both were stale enough that
fixing them would be a rewrite, and the user has explicitly de-prioritised
this layer of FE coverage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without an object check, a malformed wire payload (string/number/null
inside an unknown) would throw at the `'skipped' in resume` line. Bail
on non-object outputs and return undefined so the caller falls back to
just the tool name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stage 1 — frontend rendering
- Resolved interactive cards (ask_question / ask_credential / ask_llm) used to
vanish from chat history; now they leave a compact "→ tool · answer" trace
in AgentChatToolSteps, matching how non-interactive tool calls render.
- ToolCall carries an optional displaySummary populated on tool-result, on
the optimistic resume flip, and rolled back on resume failure.
- New summariseInteractiveOutput helper + 7 unit tests.
Stage 2 — server-side auto-resolve
- ask_question: auto-resolves when options.length === 1 (schema relaxed
from min(2) to min(1) so the LLM can actually pass single-option
questions).
- ask_credential: auto-resolves when exactly one credential of the
requested type is available to the user.
- ask_llm: auto-resolves when exactly one LLM-provider credential exists,
using a new LLM_PROVIDER_DEFAULTS table for the canonical default model
per provider (Anthropic, OpenAI, Google, xAI, Groq, Mistral, DeepSeek,
Cohere).
- CredentialProvider threaded through getJsonTools to the interactive
factories. 12 new unit tests (cli) covering suspend-vs-auto-resolve.
Lint
- AgentAdvancedPanel.test.ts: hoist @vueuse/core type alias to a top-level
`import type * as` so the inline `typeof import(...)` annotation goes
away (consistent-type-imports).
- AgentConfigJsonEditor.vue: move `void debouncedJsonSave(text)` from
expression to statement form so no-void stops complaining.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Parse typed AgentSseEvent in AgentBuilderProgress (was reading legacy shape)
- Snapshot autosave + token-guard fetchConfig/fetchAgent to stop cross-agent state bleed on quick agent switches
- Roll back optimistic resume state when /build/resume POST or SSE errors
- Reuse onBeforeRouteLeave's unpublished-changes guard via onBeforeRouteUpdate so in-component agent switches also prompt
- Token-guard agentSessions.fetchThreads/loadMore against racing switches
- Rebind a session after onContinueLoaded clears a stale continueSessionId so the Test pane doesn't go blank
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The design-system's append-slot wrapper has `margin-left: auto` to push appended content (e.g. badge) to the right. With our slot rendering both the label and the timestamp, the whole row was sticking to the right. Override the wrapper to flex full width so the label expands and the timestamp's own `margin-left: auto` keeps it on the right edge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the dot separator; render the title and timestamp as separate flex
children so they can be styled independently.
- Right-align the timestamp with margin-left:auto, drop it to text-tint-2 +
font-size-2xs so it reads as secondary metadata.
- Cap the popper at min 280 / max 360px so long titles don't blow it open;
truncate the title with text-overflow:ellipsis when it would overflow.
- Title is rendered via the per-id append slot now (the design-system's bare
title text node can't host an ellipsis); empty-state row keeps using the
built-in title field.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each row in the chat history dropdown now reads "<title> · <when>" where
when is one of: "just now", "Ns ago", "Nm ago", "Nh ago" (same calendar
day), "Yesterday", or a short locale date for older entries. Hour granularity
stops at the local-day boundary so a 20h-ago timestamp from yesterday morning
reads "Yesterday" instead of "20h ago", which matches user expectation.
i18n keys live under agents.relativeTime.*. Future timestamps clamp to
"just now" rather than rendering an "in N" label that doesn't fit this
context.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs the empty/untitled cases surfaced:
- Session-picker dropdown's empty state reused the agent-switcher's i18n key
("No other agents"). Add a dedicated agents.builder.chat.sessionPicker.empty
("No previous chats") and point the menu at it.
- Sessions without an LLM-generated title yet rendered indistinguishably as
"New chat" — both in the dropdown rows and in the active-session header.
formatThreadTitle now falls back through firstMessage (already populated by
the BE) before the i18n string, so the user sees a recognisable preview
until the title generator catches up. Whitespace is collapsed and the
preview is capped at 60 chars with an ellipsis. Test covers the fallback
chain.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The file is a pure helper module (splitPath, getSlice, setSlice, pickFrom, configToDoc, tryParseConfig) — moving it out of components/ aligns it with the other agent helpers in utils/. Update both importers and the two test files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- provider-capabilities: regression guard for the static capability map. Locks the canonical reasoning-effort order, the Anthropic/OpenAI thinking modes, the no-thinking provider list, and the lowercase key invariant the panel relies on.
- AgentAdvancedPanel: budget-tokens vs reasoning-effort sub-control switching, disabled-toggle for unsupported providers, the thinking-on emit shape, and the disabled-prop fanout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Schema is persisted as JSON; the as unknown as cast in two services rehydrates to the typed config. One-line comments make the rationale explicit so future readers don't try to tighten the type without addressing the storage boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move splitPath, getSlice (non-mutating walker), setSlice (deep-copy writer), pickFrom (top-level subset), configToDoc (renderer for either section path or pickKeys) from AgentSectionEditor.vue into the colocated utils file. New unit tests cover dotted/numeric paths, missing keys, the empty-path replacement case, and the two configToDoc branches. AgentSectionEditor.vue drops ~70 lines of inline traversal code plus a deepCopy import.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AgentToolsListPanel custom row: <button> wrapper that nested an N8nIconButton (HTML invalid) → div role=button with keyboard handlers; remove button no longer nested.
- AgentConfigTree count + status pills → N8nBadge; drop the inline pill styling.
- AgentAdvancedPanel: ElSwitch (element-plus) → N8nSwitch2 for both thinking + approval toggles.
- ToolCredsMissingChip: kept handrolled with a comment — N8nButton has no warning theme; reassess when one lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three editors (AgentMiniEditor, AgentSectionEditor, AgentConfigJsonEditor) repeated the same setup: track a `view`, use a programmatic-update flag to suppress self-emits, manage a Compartment for reactive read-only, and run identical mount/destroy lifecycles. New composables/useCodeMirrorEditor.ts owns those concerns; the components only declare their language + theme extensions and react to props.
- Drop ~120 LOC of editor lifecycle boilerplate across the three components.
- Each editor preserves its own behaviour (focus-guarded replace in SectionEditor + JsonEditor; one-way watch in MiniEditor).
- Test covers initial doc, replaceDoc programmatic-no-emit, no-op when content matches, reactive readOnly toggle, and view destruction on unmount.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three callers (AgentInfoPanel, AgentAdvancedPanel, AskLlmCard) used to roll their own parseProvider/sanitizeModelId/etc. — same intent, drifting names + edge-case handling. Move to utils/model-string.ts with unit tests covering: provider/name split with embedded slashes, object-form input, Google "models/" prefix stripping, and nullish handling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The label was misleading — the panel hosts execution-tuning knobs (thinking, concurrency, approval) rather than behavior in the agent-personality sense. Rename keeps the surface and the internals consistent:
- File: AgentBehaviorPanel.vue → AgentAdvancedPanel.vue
- Constant: BEHAVIOR_SECTION_KEY '__behavior' → ADVANCED_SECTION_KEY '__advanced'
- i18n keys: agents.builder.behavior.* → agents.builder.advanced.*; sections.behavior → sections.advanced
- Update all imports and references in AgentBuilderView and AgentConfigTree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New styles/agent-panel.module.scss with .scrollbarThin and .disabledOverlay; imported into 9 panels/views as a CSS module and composed via :class array.
- Drop ~70 lines of duplicated scrollbar declarations across InfoPanel, BehaviorPanel, MemoryPanel, EvalsPanel, ToolsListPanel, ConfigTree (children scroller), ChatMessageList, AgentBuilderView (treeColumn + triggersTab), AgentSessionsListView.
- AgentConfigJsonEditor uses .disabledOverlay; AgentInfoPanel does the same for its disabled field.
- Memory + Tools panels keep a local .disabled > :not(.header) rule because the structural selector excludes the header — the scrollbar is still extracted.
- AgentSectionEditor's nested .cm-scroller and AgentBuilderHeader's :global .el-menu scrollbars stay local: they target deep descendants of element-plus / CodeMirror that CSS-module composition can't reach.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- CHAT_MESSAGE_STATUS / TOOL_CALL_STATE in features/agents/constants.ts; replace ~30 literal occurrences in useAgentChatStream, agentChatMessages, AgentChatMessageList. ToolCallState type now lives next to its values; agentChatMessages re-exports for callers.
- CONTINUE_SESSION_ID_PARAM for the URL-deeplink query key, used in AgentBuilderView and AgentSessionTimelineView.
- Backend: BUILDER_TOOLS + AGENT_THREAD_PREFIX in builder/builder-tool-names.ts; replace literals in agents.controller, agents-builder-tools.service, agents.service, agents-builder.service.
- Add missing agents.sort.* i18n keys (lastUpdated, lastCreated, nameAsc, nameDesc) — ResourcesListLayout looks them up via resource-key="agents".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Extract useAgentIntegrationStatus composable; panel + modal read the same reactive state, so connecting in the modal instantly refreshes the Triggers panel.
- Replace "Session ${sessionNumber}" title fallback (project-scoped counter showed "Session 24" on a new agent's first thread) with "New chat" via a small thread-title util.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fold trailing assistant text into toolRun group (one bubble per turn)
- optimistically resolve interactive tool card on submit (no waiting for tool-result)
- hide interactive cards once resolved; show inline status in tool step
- replace floating history button with session header strip (title + new-chat)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The upward-flipped dropdown was fighting element-plus positioning.
Put the history dropdown back in the floating top-right anchor where
it always worked. New-chat button stays on the left of the Test
panel's quick-actions row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ActionDropdown's items weren't reactive to the sessions store polling
and the flat ElDropdownItem list read as a tooltip. Go back to the
reactive NavigationDropdown and use a CSS translateY to seat the
popper above the trigger, since ElSubMenu doesn't expose placement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap N8nNavigationDropdown for N8nActionDropdown (placement=top-start)
so the sessions menu grows above the button. The new-chat button
sits immediately right of the history button instead of being pushed
to the far edge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaced AgentChatQuickActions in the Test panel with a two-button
group: history dropdown on the left, new-chat on the right (conditional
on the current session having messages). Build panel keeps the
Add tool / Add trigger quick actions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The floating top-right anchor collided with user message bubbles as
they scrolled. Moved both buttons into the Test panel's quickActionsRow
so they live above the input alongside Add tool / Add trigger; dropped
the historyBtnAnchor styles and restored the chat's compact padding.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Chat messages now use sm/md padding instead of lg/xl so more of the
chat fits on screen. Switching to Build clears continueSessionId from
the URL; switching back to Test restores the active session id so the
URL stays consistent with the visible tab.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promote the ephemeral activeChatSessionId to the URL as continueSessionId
so refresh / copy-paste keeps the active chat. If the URL already has a
session id on initial load (and the agent has instructions) the builder
lands on the Test tab instead of Build.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>