Skip to content

feat(platform): external-agent BYO credentials + user env & secrets#1892

Merged
larryro merged 9 commits into
mainfrom
feat/external-agent-byo-and-user-env
Jun 16, 2026
Merged

feat(platform): external-agent BYO credentials + user env & secrets#1892
larryro merged 9 commits into
mainfrom
feat/external-agent-byo-and-user-env

Conversation

@larryro

@larryro larryro commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a bring-your-own (BYO) credentials auth mode for external agents, plus a per-user Environment & secrets store that feeds it.

By default an external agent runs in managed mode: the platform mints a per-turn Bifrost virtual key, routes the agent through its gateway, and enforces allowed models, usage metering, and the org's spend caps. The new per-agent BYO mode takes the platform out of the request path — no virtual key is minted, the agent authenticates with the user's own provider credentials, the model is a raw provider-id passthrough (no catalogue), native web tools stay enabled, and billing/limits move to the user's provider account. Credentials come from the new Environment & secrets page (scoped per (org, user), secrets encrypted at rest and write-only) and are injected into the user's sandbox at turn start — for both modes.

What's in the branch

  • Backend — auth mode: authMode: 'managed' | 'byo' on external-agent configs. The BYO run path skips VK minting, gateway hardening, model-access RBAC, and the WebSearch/WebFetch denial; the schema relaxes the ≥1-model rule for BYO agents (empty = use the credential's default). run_external_agent.ts, config.ts, chat_turn_generate.ts, schemas/agents.ts.
  • Backend — user env: new sandboxUserEnv table + upsertMyEnvVar / listMyEnv / deleteMyEnvVar CRUD and the internal resolveUserEnv injector. Secrets stored as JWE, write-only (masked on read), validation isolated in pure user_env_constants.ts (unit-tested).
  • Adapters: AgentRunSpec.authMode + optional gateway; the Claude Code adapter branches managed/BYO; OpenCode is managed-only (throws on BYO).
  • Frontend: a Credentials radio + free-text BYO model editor on the agent Instructions tab; a calm read-only BYO indicator in the chat model picker; the new /settings/environment page.
  • i18n: 38 new keys across en/de/fr (full parity, no \uXXXX escapes).
  • Docs (this session): documented BYO + Environment & secrets across en/de/fr, and corrected stale docstrings (below).

Docstring correction

The branch's authMode docstrings claimed BYO is "gated by the org external_agent_byo policy at run time." No such policy was ever implemented, and the run handler already documents the per-agent toggle as the sole control. The four docstrings are corrected to match the shipped behaviour — there is no org-level gate; configuring an agent is itself the privileged action. If an operator allowlist is wanted, that's a follow-up, not a silent contradiction.

Reviewer notes — worth scrutinising

  • BYO trust boundary: in BYO mode the user's real provider credential is decrypted and set in the sandbox container env — the same posture as the in-sandbox GitHub token — so any code the agent runs there can read it. By design (it's the user's own box), and the key difference from managed integration brokering, where the platform VK never lets the agent see a real key. Documented in external-agent.md and member/environment.md.
  • Governance bypass is intentional and broad: BYO turns skip allowed-models enforcement, usage metering, and the budget gate; cost and limits live with the user's provider. Confirm that's acceptable for the deployment.
  • No org-level gate: any member who can configure an agent can enable BYO (per the corrected docstrings).
  • Reserved env names: there is no platform-side denylist for keys like PATH / LD_PRELOAD. The BYO path explicitly unsets stale ANTHROPIC_* / model vars before injection, and sessionEnvPatch returns a runnerd-side denied list — so the reserved-name boundary lives in runnerd. Worth confirming it covers dangerous names.
  • No rate limit on the env write path beyond the 100-entry-per-user cap and length caps.

Pre-merge checklist

  • Ran bun run check. Format, lint, and typecheck are green across all 48 workspaces. The platform test task reported 5 convexTest timeouts (auto_route_cache, update_user_name, slack/http_actions, integration_processing_records) under full-suite turbo load — all 34 pass in isolation in ~3s and none touch this branch's code. The feature's own user_env_constants.test.ts passes; the docs suite (142 tests) is green; platform tsc is green after the docstring edit.
  • Updated services/platform/messages/{en,de,fr}.json — 38 keys, full en/de/fr parity.
  • Updated docs/, docs/de/, and docs/fr/ — reworked platform/agents/external-agent.md, added platform/member/environment.md, registered it in nav, and cross-linked from member/preferences.md + member/overview.md, in all three locales.
  • Ran bun run --filter @tale/docs lint and bun run --filter @tale/docs test — both green (regenerated the frontmatter.json manifest).
  • [N/A] README.md / README.de.md / README.fr.md — README lists Claude Code as a connector but documents no credential/auth detail; BYO is a platform feature surfaced in docs/, not the high-level README.
  • Loading states use <Skeletonize> + skeleton-aware leaves; the env list's placeholder rows use SkeletonText (the sanctioned primitive for variable-count rows), no magic h-[…].
Does this change need docs and translations? (decision tree)

Yes on every count — new message keys, new UI (the /settings/environment page, the Credentials toggle, the BYO model editor, the chat picker indicator), a new agent config field (authMode), and new validation rules. Docs + translations ship in this PR.

Test plan

  • Set an external agent's Credentials to BYO, store a provider key under Settings > Environment as a secret, and confirm a chat turn authenticates with it and runs with no gateway virtual key.
  • Confirm the chat model picker shows the read-only BYO indicator (model short name, or Default model when the BYO list is empty).
  • Switch the agent managed → BYO and confirm the saved platform models clear; switch back and re-select from the catalogue.
  • Add a secret, reload, and confirm it shows masked (••••••••) and is never returned in plaintext; delete it and confirm it leaves the list.
  • Paste a value with interior whitespace and confirm the non-blocking warning; confirm surrounding whitespace is trimmed on save.
  • Confirm a managed agent still routes through the gateway and is metered into Usage analytics under the org's caps.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added "Bring-Your-Own" (BYO) credentials mode for external agents, allowing users to supply their own provider credentials instead of using the platform gateway.
    • Introduced Environment & Secrets settings page for users to manage organization-scoped environment variables and encrypted secrets injected into agent sandboxes.
    • Updated external agent model selection to support raw provider model IDs in BYO mode with priority-based ordering.
  • Documentation

    • Expanded documentation for external agents to clarify credential modes, sandbox networking, model selection behavior, and billing differences.
    • Added Environment & Secrets member documentation explaining variable storage, encryption, and sandbox injection.
    • Updated member overview and preferences pages to reflect new credential and environment management features.

larryro added 9 commits June 16, 2026 15:41
…(backend)

Add a per-agent authMode ('managed' | 'byo') for external agents and a
user-level env/secrets store that auto-attaches to a user's sandboxes.

- agent schema + SerializableAgentConfig + AgentJsonConfig carry authMode;
  superRefine restricts it to external-agent.
- New org governance policy external_agent_byo (default off, fail-closed)
  gates byo at the runner; managed path is unchanged.
- New sandboxUserEnv table + CRUD: V8 mutations/queries (write-only secrets,
  masked reads) and a Node action that encrypts (JWE) on write and
  decrypts+returns env for injection. Reuses lib/crypto + the credential
  audit trail.
- Runner: for byo, skip provider provisioning / gateway hardening / VK mint /
  token insert; pass the raw model + no gateway; inject the user's env/secrets
  every turn (managed and byo). Continuation re-attaches, so it's
  mode-agnostic.
- Adapter: byo injects no gateway/key env, raw model passthrough, no alias
  pinning, and lifts the governance-motivated WebSearch/WebFetch denial
  (native tools work on the user's own credential). gateway made optional;
  OpenCode stays managed-only.
… env validators

- build-exec.test.ts: byo injects no gateway/key env, raw model passthrough,
  no alias pinning, lifts WebSearch/WebFetch denial, omits the integration
  bridge; managed unaffected; OpenCode rejects byo.
- agents.test.ts: authMode accepted on external-agent, rejected on chat.
- governance.test.ts: external_agent_byo defaults allowed=false (fail-closed).
- user_env_constants.test.ts: env key/value validators.
…ttings UI

- New user-level Environment & Secrets settings page (route + desktop rail +
  mobile menu), wired to listMyEnv / upsertMyEnvVar / deleteMyEnvVar; secrets
  are write-only (masked), inline ConvexError validation, per-row delete.
- Agent editor (instructions tab): authMode RadioGroup shown only for
  external-agent (default Managed); selecting BYO swaps the platform model
  catalog for a free-text raw-model-id editor, clears stale platform slugs on
  managed->byo, and shows the BYO note. authMode persists via saveAgent.
- i18n keys (en/de/fr); routeTree regenerated.
- Backend: rewrite two user_env list maps without object-spread (no-map-spread
  lint).
Driven by live E2E of a BYO Claude Code agent:

- Drop the org-level external_agent_byo enable switch entirely (governance
  policy + runner gate + editor). The per-agent authMode is the sole control —
  configuring an agent is already privileged.
- BYO model is optional: supportedModels defaults to [] and the >=1 rule moves
  into the superRefine (skipped for BYO); config conversion no longer throws on
  empty; the runner treats the 'default' sentinel as 'no model' (omit -> CLI
  default); the BYO model editor allows zero entries.
- Bypass platform model-access RBAC + default-model resolution for BYO in
  chat_turn_generate (raw provider ids aren't catalog entries) — this was
  blocking the turn with 'no model permitted'.
- On a BYO turn, unset stale platform LLM env (ANTHROPIC_*, alias/subagent
  pins) from the reused sandbox's session env so a prior managed turn's revoked
  VK can't shadow CLAUDE_CODE_OAUTH_TOKEN.
- Guard supportedModels against undefined in the agent editor (memoized).

E2E confirms the wiring: the user's CLAUDE_CODE_OAUTH_TOKEN is injected cleanly
with no conflicting gateway/VK env and a raw model passthrough; the turn reaches
the live LLM call.
A pasted credential commonly carries a trailing newline / leading indent (e.g.
the wrapped output of 'claude setup-token'), which silently corrupts a bearer
token and 401s. Trim leading/trailing whitespace on store; interior whitespace
is preserved so multi-line secrets (PEM keys) survive.
Pasting a token that wrapped across terminal lines embeds an interior space/
newline that silently corrupts the credential (→ 401), and it's brutal to debug
after the fact. Add hasInteriorWhitespace() + a loud inline warning on the
env/secret add form (en/de/fr). Non-blocking — legitimately multi-line secrets
(PEM keys) contain interior newlines, so the user can still proceed.
…odels warning

A BYO external agent has no platform-catalog model, so the model picker showed
a red 'No models available' / 'no usable model' warning — misleading, since BYO
uses the user's own credentials and a raw model id. Thread authMode through the
listAgents projection + ChatAgent shape, and render a non-interactive indicator
(the raw model id, or 'Default model') with a BYO tooltip. en/de/fr.
The authMode docstrings in agents.ts, file_utils.ts, run_external_agent.ts,
and the agent_chat config type claimed BYO is "gated by the org
`external_agent_byo` policy at run time." No such policy exists, and the run
handler comment already states the per-agent authMode is the sole control
(configuring an agent is itself a privileged action). Align the docstrings
with the implemented behavior so they stop describing a gate that was never
built.
Document the bring-your-own credentials mode and the per-user Environment &
secrets page added on this branch.

- Rework platform/agents/external-agent.md: scope the "model reached through
  the gateway, never a raw provider key" claim to managed mode, and add a
  "Managed and bring-your-own credentials" section plus BYO notes in the
  engines/models and cost sections. BYO bypasses the gateway, injects the
  user's own provider key into the sandbox, and moves billing and limits to
  the user's provider account.
- Add platform/member/environment.md documenting the per-(org,user) env vars
  and write-only secrets, their validation rules, and how they are injected
  into the sandbox; register it in nav.
- Cross-link from member/preferences.md and reconcile member/overview.md's
  "Settings hidden for Members" claim with the personal settings group.
- Mirror every change across en/de/fr and regenerate the frontmatter manifest.
@@ -0,0 +1,127 @@
'use node';
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces two interconnected features. First, external agents gain an authMode: 'managed' | 'byo' field: in byo mode the platform gateway is bypassed, no virtual key is minted, web-tool restrictions are lifted, and the user's own provider credentials (from a new env store) are injected into the sandbox container instead. Second, a new sandboxUserEnv Convex table and settings UI ("Environment & secrets") let members store per-organization encrypted secrets and plain variables that are decrypted and injected at sandbox turn start with audit logging. The agent instructions editor gains a RadioGroup auth-mode selector and a ByoModelEditor for ordered raw model IDs; the chat model selector renders a read-only indicator for BYO agents. Model-access RBAC and the platform default-model selection path are bypassed for BYO agents. Documentation and i18n strings are updated in EN/DE/FR.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • tale-project/tale#1885: Both PRs modify ModelSelector behavior — this PR adds a BYO non-interactive indicator path, while #1885 extends ModelSelector with model capability fetching and ModelInfoPopover.
  • tale-project/tale#1390: Both PRs touch the chat turn generation model-selection path — #1390 wires the governance default-model resolver, while this PR adds isByoExternal conditions to bypass that same governance for BYO agents.
  • tale-project/tale#1880: Both PRs modify the external-agent execution path around integration dispatch and the MCP bridge — #1880 adds integration binding plumbing that this PR then gates behind the !byo && spec.gateway condition.

Suggested reviewers

  • Israeltheminer
  • yannickmonney
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature: adding BYO credentials for external agents and a user environment & secrets store.
Description check ✅ Passed The PR description is comprehensive, addressing all template sections: summary of changes, detailed implementation breakdown, reviewer notes, and a complete pre-merge checklist with all items marked as done or N/A with justification.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/external-agent-byo-and-user-env

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


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 and usage tips.

@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: 12

Caution

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

⚠️ Outside diff range comments (1)
packages/agent_adapters/src/claude_code/adapter.ts (1)

106-119: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use authMode, not gateway presence, as the adapter mode switch. Both adapter paths currently let a leftover gateway override the requested BYO semantics.

  • packages/agent_adapters/src/claude_code/adapter.ts#L106-L119: require !byo before adding the integration MCP bridge.
  • packages/agent_adapters/src/opencode/adapter.ts#L27-L33: throw when spec.authMode === 'byo' before checking spec.gateway.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/agent_adapters/src/claude_code/adapter.ts` around lines 106 - 119,
The adapter is using gateway presence as the primary check for whether to add
the integration MCP bridge, which can incorrectly allow a leftover gateway
configuration to override BYO (bring-your-own) semantics. In
packages/agent_adapters/src/claude_code/adapter.ts (lines 106-119), add a check
for !byo or ensure authMode is not 'byo' before adding the integrations MCP
server configuration block. In packages/agent_adapters/src/opencode/adapter.ts
(lines 27-33), throw an error when spec.authMode === 'byo' before any
gateway-related checks to explicitly prevent gateway from being used in BYO
mode. Use authMode as the authoritative mode switch to ensure BYO semantics are
never overridden by gateway presence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/agent_adapters/src/types.ts`:
- Around line 32-40: The current type definition in types.ts allows authMode to
be omitted (defaulting to 'managed') while also allowing gateway to be omitted,
which violates the documented coupling requirement that gateway must be present
for managed runs and absent for byo runs. Refactor the type definition using
TypeScript discriminated unions to create two distinct type variants: one for
authMode 'managed' that requires gateway to be present, and one for authMode
'byo' that prohibits gateway. Use a union type to combine these variants,
ensuring that invalid combinations like omitting both authMode (defaulting to
managed) and gateway cannot compile and must be caught at the type level rather
than at runtime.

In
`@services/platform/app/features/settings/user-env/components/user-env-settings.tsx`:
- Around line 231-232: The <ul> elements in user-env-settings.tsx are missing
the explicit role="list" attribute required for proper accessibility with
VoiceOver semantics in Safari. Add role="list" to the <ul> element that has
className="divide-border divide-y" and aria-hidden="true" at line 231, and also
add the same role attribute to the corresponding <ul> element at line 250, to
ensure consistent accessibility compliance across all list elements in this
component.
- Around line 123-128: Remove unnecessary memoization hooks from four functions
in the user-env-settings component. Unwrap the reset function from useCallback
(lines 123–128), unwrap the handleSubmit function from useCallback (lines
130–156), unwrap the displayValue variable from useMemo (lines 275–281), and
unwrap the handleDelete function from useCallback (lines 283–293). These hooks
provide no performance benefit because the functions are either simple with no
dependencies, called inline without being passed to memoized children, or passed
to non-memoized components. Replace each memoized definition with the direct
function or variable assignment.

In `@services/platform/convex/agents/external_agent/run_external_agent.ts`:
- Around line 453-456: The sessionEnvPatch call only sets the current user
environment values but does not unset entries that have been removed or renamed,
allowing revoked credentials to persist in the session. Modify the
sessionEnvPatch invocation to include an unset operation alongside the set
operation. Identify which environment variable keys existed in the previous
session state but are no longer present in the current userEnv.env, and add
those keys to an unset array in the patch object passed to sessionEnvPatch. This
ensures that deleted or renamed secrets are properly removed from the sandbox
session environment along with the current values being set.

In `@services/platform/convex/lib/agent_chat/start_agent_chat.ts`:
- Around line 656-658: The current approach of only forwarding authMode is
insufficient because budget enforcement gates still execute before scheduling in
the start_agent_chat function. Refactor the code to separate budget enforcement
logic from feature-flag resolution: keep file-upload and feature-flag policy
checks applied to all turns, but conditionally skip the platform user/agent
budget cap enforcement when enforcedConfig.authMode indicates a BYO (Bring Your
Own) external-agent turn, since BYO turns mint no VK and should bill directly to
the user's provider account rather than consuming platform spend capacity.
Restructure the budget gate block to check authMode and bypass spending cap
validation specifically for BYO cases while preserving all other policy
enforcement.

In `@services/platform/convex/node_only/sandbox/run_agent.ts`:
- Around line 252-259: The code currently omits the spec.gateway property when
gatewayBaseUrl or gatewayToken are missing, which allows managed runs to bypass
gateway governance. Add a fail-closed validation check that requires both
args.gatewayBaseUrl and args.gatewayToken to be present when args.authMode is
undefined or set to managed. If either gateway credential is missing in a
managed run, throw an error to prevent the run from proceeding without proper
gateway configuration instead of silently omitting it.

In `@services/platform/convex/sandbox/user_env.ts`:
- Around line 83-90: Replace the `.collect()` materializations with `for await`
iteration patterns in the Convex query handlers throughout this file. At lines
83-90, instead of calling `.collect()` and then accessing `.length` on the
result, use a `for await` loop to iterate over the query results and count items
as you iterate. Apply the same pattern at lines 123-128 and 169-176 where
`.collect()` is being used to materialize Convex query results. The goal is to
avoid loading full result sets into memory and instead process results
iteratively as Convex best practices recommend.
- Around line 56-105: The mutation handler in upsertUserEnvInternal (the async
function starting at line 56) needs to re-validate organization membership
before performing any database operations. Currently, membership is only checked
in the caller upsertMyEnvVar, which creates a race condition if membership
changes between the validation and this mutation execution. Add an organization
membership validation check at the beginning of the handler, before the
ctx.db.query call for the existing environment variable lookup, to ensure the
user still has access to the organization within the same transaction as the
write operations.

In `@services/platform/lib/shared/schemas/agents.test.ts`:
- Around line 115-134: Add two new test cases to provide direct regression
coverage for the empty supportedModels array behavior across different authMode
paths. First, add a positive test case that verifies an external-agent with
authMode set to 'byo' and an empty supportedModels array passes validation (this
should succeed). Second, add a negative test case that verifies an
external-agent with authMode set to 'managed' and an empty supportedModels array
fails validation (this should have success: false). Insert these test cases
after the existing authMode validation tests (after the 'rejects authMode on a
non-external-agent' test) to ensure the conditional model rule allowing empty
supportedModels only for BYO external-agents is properly covered and
regression-protected.

In `@services/platform/messages/de.json`:
- Around line 896-897: The byoDefault label value "Standardmodell" does not
clearly communicate that this is for a user-supplied provider model and reads
like a standard catalog default instead. In the de.json file, replace the
byoDefault value with a label that explicitly indicates this is a user-supplied
or user-provided model, making it clear to users that they are using their own
credentials and model configuration rather than a platform-provided default.
- Line 4102: The whitespaceWarning message in de.json incorrectly warns about
all whitespace when the backend only flags interior whitespace (leading and
trailing whitespace is trimmed). Update the whitespaceWarning string to clarify
that only interior whitespace is problematic, while explicitly confirming that
leading/trailing whitespace is removed and multi-line secrets like PEM keys are
permitted. This will align the user-facing text with actual backend behavior.

In `@services/platform/messages/fr.json`:
- Line 898: The French UI text is using formal address pronouns (vous/votre)
instead of the informal pronouns (tu/ton/tes) required by the repository's
coding guidelines. At services/platform/messages/fr.json#L898-L898 for the BYO
tooltip, replace "vos propres identifiants" with "tes propres identifiants" and
"les vôtres" with "les tiens". At services/platform/messages/fr.json#L4103-L4103
for the whitespace warning, similarly replace all instances of vous and votre
with their informal equivalents tu and ton/tes to maintain consistent informal
French across the entire codebase.

---

Outside diff comments:
In `@packages/agent_adapters/src/claude_code/adapter.ts`:
- Around line 106-119: The adapter is using gateway presence as the primary
check for whether to add the integration MCP bridge, which can incorrectly allow
a leftover gateway configuration to override BYO (bring-your-own) semantics. In
packages/agent_adapters/src/claude_code/adapter.ts (lines 106-119), add a check
for !byo or ensure authMode is not 'byo' before adding the integrations MCP
server configuration block. In packages/agent_adapters/src/opencode/adapter.ts
(lines 27-33), throw an error when spec.authMode === 'byo' before any
gateway-related checks to explicitly prevent gateway from being used in BYO
mode. Use authMode as the authoritative mode switch to ensure BYO semantics are
never overridden by gateway presence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bbe353a1-617c-46c1-a080-54e752d66af9

📥 Commits

Reviewing files that changed from the base of the PR and between 55bc8f7 and 6ea3abe.

⛔ Files ignored due to path filters (1)
  • services/platform/convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (49)
  • docs/de/platform/agents/external-agent.md
  • docs/de/platform/member/environment.md
  • docs/de/platform/member/overview.md
  • docs/de/platform/member/preferences.md
  • docs/en/platform/agents/external-agent.md
  • docs/en/platform/member/environment.md
  • docs/en/platform/member/overview.md
  • docs/en/platform/member/preferences.md
  • docs/fr/platform/agents/external-agent.md
  • docs/fr/platform/member/environment.md
  • docs/fr/platform/member/overview.md
  • docs/fr/platform/member/preferences.md
  • docs/nav.json
  • packages/agent_adapters/src/build-exec.test.ts
  • packages/agent_adapters/src/claude_code/adapter.ts
  • packages/agent_adapters/src/opencode/adapter.ts
  • packages/agent_adapters/src/types.ts
  • services/docs/app/content/frontmatter.json
  • services/platform/app/features/agents/components/agent-navigation.tsx
  • services/platform/app/features/agents/components/byo-model-editor.tsx
  • services/platform/app/features/chat/components/model-selector.tsx
  • services/platform/app/features/chat/hooks/queries.ts
  • services/platform/app/features/settings/components/settings-rail.tsx
  • services/platform/app/features/settings/components/use-settings-menu-groups.ts
  • services/platform/app/features/settings/user-env/components/user-env-settings.tsx
  • services/platform/app/features/settings/user-env/hooks/mutations.ts
  • services/platform/app/features/settings/user-env/hooks/queries.ts
  • services/platform/app/routeTree.gen.ts
  • services/platform/app/routes/dashboard/$id/agents/$agentId/instructions.tsx
  • services/platform/app/routes/dashboard/$id/settings/environment.tsx
  • services/platform/convex/agents/chat_turn_generate.ts
  • services/platform/convex/agents/config.ts
  • services/platform/convex/agents/external_agent/run_external_agent.ts
  • services/platform/convex/agents/file_actions.ts
  • services/platform/convex/agents/file_utils.ts
  • services/platform/convex/lib/agent_chat/start_agent_chat.ts
  • services/platform/convex/lib/agent_chat/types.ts
  • services/platform/convex/node_only/sandbox/run_agent.ts
  • services/platform/convex/sandbox/sessions_schema.ts
  • services/platform/convex/sandbox/user_env.ts
  • services/platform/convex/sandbox/user_env_actions.ts
  • services/platform/convex/sandbox/user_env_constants.test.ts
  • services/platform/convex/sandbox/user_env_constants.ts
  • services/platform/convex/schema.ts
  • services/platform/lib/shared/schemas/agents.test.ts
  • services/platform/lib/shared/schemas/agents.ts
  • services/platform/messages/de.json
  • services/platform/messages/en.json
  • services/platform/messages/fr.json

Comment on lines +32 to +40
/**
* Credential mode. 'managed' (default / absent): route through the platform
* gateway with the minted virtual key. 'byo': no gateway — the agent uses the
* credentials the user injected into the session env, with a raw model
* passthrough and native web tools enabled.
*/
authMode?: 'managed' | 'byo';
/** Platform LLM gateway. Present for managed runs; ABSENT for byo. */
gateway?: GatewayTarget;

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Enforce authModegateway coupling at the type level.

authMode and gateway are documented as dependent, but the current shape allows authMode to be omitted (managed default) while also omitting gateway. That invalid state now compiles and shifts failure to runtime.

Suggested contract hardening
-export interface AgentRunSpec {
+interface AgentRunSpecBase {
   prompt: string;
   model?: string;
   agentSessionId?: string;
   maxTurns?: number;
   permissionMode?: 'plan' | 'execute';
   systemPromptAppend?: string;
-  authMode?: 'managed' | 'byo';
-  gateway?: GatewayTarget;
   integrationsBaseUrl?: string;
   workdir: string;
   browserMcp?: boolean;
   browserCdp?: boolean;
   execId?: string;
-}
+}
+
+type ManagedAuth = { authMode?: 'managed'; gateway: GatewayTarget };
+type ByoAuth = { authMode: 'byo'; gateway?: never };
+
+export type AgentRunSpec = AgentRunSpecBase & (ManagedAuth | ByoAuth);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/agent_adapters/src/types.ts` around lines 32 - 40, The current type
definition in types.ts allows authMode to be omitted (defaulting to 'managed')
while also allowing gateway to be omitted, which violates the documented
coupling requirement that gateway must be present for managed runs and absent
for byo runs. Refactor the type definition using TypeScript discriminated unions
to create two distinct type variants: one for authMode 'managed' that requires
gateway to be present, and one for authMode 'byo' that prohibits gateway. Use a
union type to combine these variants, ensuring that invalid combinations like
omitting both authMode (defaulting to managed) and gateway cannot compile and
must be caught at the type level rather than at runtime.

Comment on lines +123 to +128
const reset = useCallback(() => {
setKey('');
setValue('');
setIsSecret(false);
setError(null);
}, []);

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.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

cat -n services/platform/app/features/settings/user-env/components/user-env-settings.tsx | head -300

Repository: tale-project/tale

Length of output: 11003


🏁 Script executed:

tail -n +300 services/platform/app/features/settings/user-env/components/user-env-settings.tsx

Repository: tale-project/tale

Length of output: 1105


🏁 Script executed:

# Check if DeleteDialog or any child components used are memoized
rg "memo\(|React\.memo" services/platform/app/features/settings/user-env/components/
rg "memo\(|React\.memo" services/platform/app/components/ui/dialog/

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Check if Input, Switch, Button, or DeleteDialog components are wrapped with memo
rg "export.*memo|React\.memo" services/platform/app/components/ui/forms/input services/platform/app/components/ui/forms/switch services/platform/app/components/ui/button services/platform/app/components/ui/dialog/ 2>/dev/null | head -20

Repository: tale-project/tale

Length of output: 43


🏁 Script executed:

# Check the DeleteDialog component definition
fd -e tsx "delete-dialog" services/platform/app/components/ui/dialog/ -x cat -n {}

Repository: tale-project/tale

Length of output: 13514


🏁 Script executed:

# Check if EnvVarRow is wrapped in memo
rg "memo.*EnvVarRow|React\.memo" services/platform/app/features/settings/user-env/components/user-env-settings.tsx

Repository: tale-project/tale

Length of output: 43


Remove unprofiled useMemo/useCallback usage.

These memoization hooks wrap local logic with no demonstrated performance benefit. Per the project guidelines, use them only when profiling justifies the additional complexity.

  • Lines 123–128 (reset): Simple state reset function with no external dependencies. No memoization benefit.
  • Lines 130–156 (handleSubmit): Called via inline arrow function in form submission handler, not passed to memoized children. Memoization provides no performance gain.
  • Lines 275–281 (displayValue): Trivial ternary operation; memoization overhead exceeds any benefit.
  • Lines 283–293 (handleDelete): Passed to DeleteDialog, which is not memoized. Stable callback reference provides no optimization since the parent component rerenders regardless.

Remove all four memoizations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/platform/app/features/settings/user-env/components/user-env-settings.tsx`
around lines 123 - 128, Remove unnecessary memoization hooks from four functions
in the user-env-settings component. Unwrap the reset function from useCallback
(lines 123–128), unwrap the handleSubmit function from useCallback (lines
130–156), unwrap the displayValue variable from useMemo (lines 275–281), and
unwrap the handleDelete function from useCallback (lines 283–293). These hooks
provide no performance benefit because the functions are either simple with no
dependencies, called inline without being passed to memoized children, or passed
to non-memoized components. Replace each memoized definition with the direct
function or variable assignment.

Source: Coding guidelines

Comment on lines +231 to +232
<ul className="divide-border divide-y" aria-hidden="true">
{Array.from({ length: 3 }).map((_, i) => (

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add explicit role="list" on the <ul> elements.

For this codebase’s accessibility baseline (including Safari/VoiceOver behavior), these lists should carry role="list".

Suggested fix
-      <ul className="divide-border divide-y" aria-hidden="true">
+      <ul role="list" className="divide-border divide-y" aria-hidden="true">
@@
-    <ul className="divide-border divide-y">
+    <ul role="list" className="divide-border divide-y">

Based on learnings: keep role="list" on ul elements in TSX components for VoiceOver semantics.

Also applies to: 250-250

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/platform/app/features/settings/user-env/components/user-env-settings.tsx`
around lines 231 - 232, The <ul> elements in user-env-settings.tsx are missing
the explicit role="list" attribute required for proper accessibility with
VoiceOver semantics in Safari. Add role="list" to the <ul> element that has
className="divide-border divide-y" and aria-hidden="true" at line 231, and also
add the same role attribute to the corresponding <ul> element at line 250, to
ensure consistent accessibility compliance across all list elements in this
component.

Source: Learnings

Comment on lines +453 to +456
if (Object.keys(userEnv.env).length > 0) {
const denied = await sessionEnvPatch(sessionId, {
set: userEnv.env,
});

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Unset removed user env entries before applying the current set.

This sandbox is reused per user/org, but this patch only writes the currently resolved env values. If a member deletes or renames a secret, the previous session env value remains available to later turns, so revoked BYO credentials can keep authenticating.

🛡️ Suggested direction
-          if (Object.keys(userEnv.env).length > 0) {
+          // Have resolveUserEnv return the names previously injected for this
+          // user/session, or store a session-scoped injected-name marker, then
+          // unset names that are no longer present before setting current values.
+          const unset = userEnv.unset ?? [];
+          if (unset.length > 0 || Object.keys(userEnv.env).length > 0) {
             const denied = await sessionEnvPatch(sessionId, {
+              ...(unset.length > 0 && { unset }),
               set: userEnv.env,
             });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/convex/agents/external_agent/run_external_agent.ts` around
lines 453 - 456, The sessionEnvPatch call only sets the current user environment
values but does not unset entries that have been removed or renamed, allowing
revoked credentials to persist in the session. Modify the sessionEnvPatch
invocation to include an unset operation alongside the set operation. Identify
which environment variable keys existed in the previous session state but are no
longer present in the current userEnv.env, and add those keys to an unset array
in the patch object passed to sessionEnvPatch. This ensures that deleted or
renamed secrets are properly removed from the sandbox session environment along
with the current values being set.

Comment on lines +656 to +658
...(enforcedConfig.authMode !== undefined && {
authMode: enforcedConfig.authMode,
}),

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bypass platform spend caps for BYO external-agent turns.

Forwarding authMode here is not enough: this function still runs the platform user/agent budget gates before scheduling. A BYO turn mints no VK and bills the user's provider account, so an exhausted platform cap can incorrectly block BYO usage.

🛠️ Suggested direction
+  const isByoExternal =
+    enforcedConfig.primaryBehavior === 'external-agent' &&
+    enforcedConfig.authMode === 'byo';
+
   let enforcedConfig = agentConfig;
   let governanceMaxContextTokens: number | undefined;
-  if (userId) {
+  if (userId && !isByoExternal) {
     const { userTeamIds, userRole } = await resolveBudgetContext(

If file-upload and feature-flag policy should still apply to BYO, split budget enforcement from feature-flag resolution instead of skipping the whole block.

📝 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
...(enforcedConfig.authMode !== undefined && {
authMode: enforcedConfig.authMode,
}),
let enforcedConfig = agentConfig;
let governanceMaxContextTokens: number | undefined;
const isByoExternal =
agentConfig.primaryBehavior === 'external-agent' &&
agentConfig.authMode === 'byo';
if (userId && !isByoExternal) {
const { userTeamIds, userRole } = await resolveBudgetContext(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/convex/lib/agent_chat/start_agent_chat.ts` around lines 656
- 658, The current approach of only forwarding authMode is insufficient because
budget enforcement gates still execute before scheduling in the start_agent_chat
function. Refactor the code to separate budget enforcement logic from
feature-flag resolution: keep file-upload and feature-flag policy checks applied
to all turns, but conditionally skip the platform user/agent budget cap
enforcement when enforcedConfig.authMode indicates a BYO (Bring Your Own)
external-agent turn, since BYO turns mint no VK and should bill directly to the
user's provider account rather than consuming platform spend capacity.
Restructure the budget gate block to check authMode and bypass spending cap
validation specifically for BYO cases while preserving all other policy
enforcement.

Comment on lines +83 to +90
const count = (
await ctx.db
.query('sandboxUserEnv')
.withIndex('by_org_user', (q) =>
q.eq('organizationId', args.organizationId).eq('userId', args.userId),
)
.collect()
).length;

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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Replace .collect() with for await iteration in Convex query handlers.

This file materializes full result sets in multiple handlers. Repo guidance for Convex queries is to iterate with for await instead of .collect().

Suggested direction
-    const count = (
-      await ctx.db
-        .query('sandboxUserEnv')
-        .withIndex('by_org_user', (q) =>
-          q.eq('organizationId', args.organizationId).eq('userId', args.userId),
-        )
-        .collect()
-    ).length;
+    let count = 0;
+    for await (const _row of ctx.db
+      .query('sandboxUserEnv')
+      .withIndex('by_org_user', (q) =>
+        q.eq('organizationId', args.organizationId).eq('userId', args.userId),
+      )) {
+      count += 1;
+      if (count >= MAX_ENV_VARS_PER_USER) break;
+    }

Based on learnings: “Always use for await loops to iterate over Convex query results and avoid collecting with .collect().”

Also applies to: 123-128, 169-176

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/convex/sandbox/user_env.ts` around lines 83 - 90, Replace
the `.collect()` materializations with `for await` iteration patterns in the
Convex query handlers throughout this file. At lines 83-90, instead of calling
`.collect()` and then accessing `.length` on the result, use a `for await` loop
to iterate over the query results and count items as you iterate. Apply the same
pattern at lines 123-128 and 169-176 where `.collect()` is being used to
materialize Convex query results. The goal is to avoid loading full result sets
into memory and instead process results iteratively as Convex best practices
recommend.

Source: Learnings

Comment on lines +115 to +134
it('accepts authMode managed/byo on an external-agent', () => {
expect(
agentJsonSchema.safeParse({ ...externalBase, authMode: 'managed' })
.success,
).toBe(true);
expect(
agentJsonSchema.safeParse({ ...externalBase, authMode: 'byo' }).success,
).toBe(true);
});

it('rejects authMode on a non-external-agent (chat)', () => {
expect(
agentJsonSchema.safeParse({
displayName: 'Chat Agent',
supportedModels: ['openrouter:anthropic/claude-sonnet-4.6'],
systemInstructions: 'hi',
authMode: 'byo',
}).success,
).toBe(false);
});

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.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add direct regression coverage for empty supportedModels in BYO vs managed paths.

This suite validates authMode placement, but it does not pin the newly introduced conditional model rule ([] allowed only for BYO external-agent). Add one positive (BYO+empty) and one negative (managed/implicit managed+empty) case.

Suggested test additions
   it('accepts authMode managed/byo on an external-agent', () => {
@@
   });

+  it('accepts BYO external-agent with no supportedModels', () => {
+    expect(
+      agentJsonSchema.safeParse({
+        displayName: 'BYO Agent',
+        primaryBehavior: 'external-agent' as const,
+        authMode: 'byo',
+      }).success,
+    ).toBe(true);
+  });
+
+  it('rejects managed external-agent with no supportedModels', () => {
+    expect(
+      agentJsonSchema.safeParse({
+        displayName: 'Managed Agent',
+        primaryBehavior: 'external-agent' as const,
+        authMode: 'managed',
+      }).success,
+    ).toBe(false);
+  });
📝 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
it('accepts authMode managed/byo on an external-agent', () => {
expect(
agentJsonSchema.safeParse({ ...externalBase, authMode: 'managed' })
.success,
).toBe(true);
expect(
agentJsonSchema.safeParse({ ...externalBase, authMode: 'byo' }).success,
).toBe(true);
});
it('rejects authMode on a non-external-agent (chat)', () => {
expect(
agentJsonSchema.safeParse({
displayName: 'Chat Agent',
supportedModels: ['openrouter:anthropic/claude-sonnet-4.6'],
systemInstructions: 'hi',
authMode: 'byo',
}).success,
).toBe(false);
});
it('accepts authMode managed/byo on an external-agent', () => {
expect(
agentJsonSchema.safeParse({ ...externalBase, authMode: 'managed' })
.success,
).toBe(true);
expect(
agentJsonSchema.safeParse({ ...externalBase, authMode: 'byo' }).success,
).toBe(true);
});
it('accepts BYO external-agent with no supportedModels', () => {
expect(
agentJsonSchema.safeParse({
displayName: 'BYO Agent',
primaryBehavior: 'external-agent',
authMode: 'byo',
}).success,
).toBe(true);
});
it('rejects managed external-agent with no supportedModels', () => {
expect(
agentJsonSchema.safeParse({
displayName: 'Managed Agent',
primaryBehavior: 'external-agent',
authMode: 'managed',
}).success,
).toBe(false);
});
it('rejects authMode on a non-external-agent (chat)', () => {
expect(
agentJsonSchema.safeParse({
displayName: 'Chat Agent',
supportedModels: ['openrouter:anthropic/claude-sonnet-4.6'],
systemInstructions: 'hi',
authMode: 'byo',
}).success,
).toBe(false);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/lib/shared/schemas/agents.test.ts` around lines 115 - 134,
Add two new test cases to provide direct regression coverage for the empty
supportedModels array behavior across different authMode paths. First, add a
positive test case that verifies an external-agent with authMode set to 'byo'
and an empty supportedModels array passes validation (this should succeed).
Second, add a negative test case that verifies an external-agent with authMode
set to 'managed' and an empty supportedModels array fails validation (this
should have success: false). Insert these test cases after the existing authMode
validation tests (after the 'rejects authMode on a non-external-agent' test) to
ensure the conditional model rule allowing empty supportedModels only for BYO
external-agents is properly covered and regression-protected.

Comment on lines +896 to +897
"byoDefault": "Standardmodell",
"byoTooltip": "Dieser Agent verwendet deine eigenen Anmeldedaten (BYO) — Modell, Abrechnung und Limits liegen bei dir.",

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Rename the BYO badge.

"Standardmodell" reads like a normal catalog default, but this label is rendered for BYO agents and should clearly indicate a user-supplied provider model.

💡 Suggested copy
-      "byoDefault": "Standardmodell",
+      "byoDefault": "Eigenes Modell",
📝 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
"byoDefault": "Standardmodell",
"byoTooltip": "Dieser Agent verwendet deine eigenen Anmeldedaten (BYO) — Modell, Abrechnung und Limits liegen bei dir.",
"byoDefault": "Eigenes Modell",
"byoTooltip": "Dieser Agent verwendet deine eigenen Anmeldedaten (BYO) — Modell, Abrechnung und Limits liegen bei dir.",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/messages/de.json` around lines 896 - 897, The byoDefault
label value "Standardmodell" does not clearly communicate that this is for a
user-supplied provider model and reads like a standard catalog default instead.
In the de.json file, replace the byoDefault value with a label that explicitly
indicates this is a user-supplied or user-provided model, making it clear to
users that they are using their own credentials and model configuration rather
than a platform-provided default.

"valuePlaceholder": "Wert",
"secretLabel": "Geheimnis",
"secretHelp": "Wenn aktiviert, wird der Wert verschlüsselt und ist nur beschreibbar — nach dem Speichern wird er nie wieder angezeigt.",
"whitespaceWarning": "Dieser Wert enthält Leerzeichen oder Zeilenumbrüche. Anmeldedaten sollten das nicht — falls du ein Token eingefügt hast, das im Terminal über mehrere Zeilen umgebrochen wurde, entferne sie. (Mehrzeilige Geheimnisse wie PEM-Schlüssel dürfen sie behalten.)",

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tighten the whitespace warning.

The current copy warns about any whitespace, but the backend only flags interior whitespace; leading/trailing whitespace is trimmed, and multi-line secrets are allowed. This text should mirror that behavior.

💡 Suggested copy
-      "whitespaceWarning": "Dieser Wert enthält Leerzeichen oder Zeilenumbrüche. Anmeldedaten sollten das nicht — falls du ein Token eingefügt hast, das im Terminal über mehrere Zeilen umgebrochen wurde, entferne sie. (Mehrzeilige Geheimnisse wie PEM-Schlüssel dürfen sie behalten.)",
+      "whitespaceWarning": "Dieser Wert enthält innere Leerzeichen oder Zeilenumbrüche. Falls du ein Token eingefügt hast, das im Terminal über mehrere Zeilen umgebrochen wurde, entferne diese Zeichen. Mehrzeilige Geheimnisse wie PEM-Schlüssel sind davon ausgenommen.",
📝 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
"whitespaceWarning": "Dieser Wert enthält Leerzeichen oder Zeilenumbrüche. Anmeldedaten sollten das nicht — falls du ein Token eingefügt hast, das im Terminal über mehrere Zeilen umgebrochen wurde, entferne sie. (Mehrzeilige Geheimnisse wie PEM-Schlüssel dürfen sie behalten.)",
"whitespaceWarning": "Dieser Wert enthält innere Leerzeichen oder Zeilenumbrüche. Falls du ein Token eingefügt hast, das im Terminal über mehrere Zeilen umgebrochen wurde, entferne diese Zeichen. Mehrzeilige Geheimnisse wie PEM-Schlüssel sind davon ausgenommen.",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/messages/de.json` at line 4102, The whitespaceWarning
message in de.json incorrectly warns about all whitespace when the backend only
flags interior whitespace (leading and trailing whitespace is trimmed). Update
the whitespaceWarning string to clarify that only interior whitespace is
problematic, while explicitly confirming that leading/trailing whitespace is
removed and multi-line secrets like PEM keys are permitted. This will align the
user-facing text with actual backend behavior.

"auto": "Auto",
"autoDescription": "Tale choisit le meilleur modèle pour chaque message — équilibre qualité, vitesse et coût.",
"byoDefault": "Modèle par défaut",
"byoTooltip": "Cet agent utilise vos propres identifiants (BYO) — le modèle, la facturation et les limites sont les vôtres.",

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the French copy consistently informal.

Both strings slip into vous/votre, which breaks the repository rule to use tu across French UI text.

  • services/platform/messages/fr.json#L898-L898: rewrite the BYO tooltip with tu/ton/tes.
  • services/platform/messages/fr.json#L4103-L4103: rewrite the whitespace warning with tu/ton/tes.

As per coding guidelines, use informal form across all languages (tu in French).

📍 Affects 1 file
  • services/platform/messages/fr.json#L898-L898 (this comment)
  • services/platform/messages/fr.json#L4103-L4103
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/platform/messages/fr.json` at line 898, The French UI text is using
formal address pronouns (vous/votre) instead of the informal pronouns
(tu/ton/tes) required by the repository's coding guidelines. At
services/platform/messages/fr.json#L898-L898 for the BYO tooltip, replace "vos
propres identifiants" with "tes propres identifiants" and "les vôtres" with "les
tiens". At services/platform/messages/fr.json#L4103-L4103 for the whitespace
warning, similarly replace all instances of vous and votre with their informal
equivalents tu and ton/tes to maintain consistent informal French across the
entire codebase.

Source: Coding guidelines

@larryro larryro merged commit b133e9b into main Jun 16, 2026
35 checks passed
@larryro larryro deleted the feat/external-agent-byo-and-user-env branch June 16, 2026 13:27
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