Alibaba Model Studio's context-cache doc bills cached input tokens at 20% of the
input price (implicit hits, International endpoint). Most of the qwen catalog
already used 20%; align the outliers and guard against future drift.
- qwen3.7-max 1.25 -> 0.5, qwen3.7-plus 0.2 -> 0.08: corrects an earlier change
that read a "50% discount" off the model-pricing page; the authoritative rate
is 20%, so the original 0.5 for qwen3.7-max was already correct.
- qwen3.6-{max-preview,plus,flash}, qwen3.5-{plus,flash}: 10% -> 20% stale
outliers, with matching config.yml agent-price updates.
Add TestAgentConfigPricesMatchCatalog: GetPriceInfoForType returns the agent
price with no catalog fallback, so config.yml drift silently mis-prices cost.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Verified per the OpenAI model pages (reasoning.effort support):
- gpt-5.4-mini, gpt-5.4-nano, gpt-5.2: [low, medium, high, xhigh]
- gpt-5.2-pro: [medium, high, xhigh] (no low)
gpt-5 / gpt-5.1 keep the default [low, medium, high] (no xhigh; gpt-5 uses
"minimal", not representable in the UI enum). Codex variants are unverified
and left for a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The settings effort dropdown is driven by a model's reasoning.efforts and
falls back to [low, medium, high] when absent, so xhigh/max were never
selectable for OpenAI-compatible models even though the backend accepts them.
Declare the verified accepted levels:
- glm-5.2: [high, max] (Z.AI: GLM-5.2 reasoning_effort accepts only high/max,
so the default low/medium were also wrong for it)
- gpt-5.4: [low, medium, high, xhigh] (OpenAI GPT-5.4/5.5 effort enum)
Other openai/gpt-5.x entries still need per-model effort verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cache_read was 0.5; the Alibaba Model Studio official price (International)
is 1.25 per 1M tokens (a 50% discount on the 2.5 input rate). Pre-existing
inaccuracy, unrelated to the qwen3.7-plus addition.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the placeholder estimates with verified list prices (USD per 1M tokens):
- kimi-k2.7-code: 0.95 / 4.00 / 0.19 cache (platform.kimi.ai official) —
identical input/output to k2.6, only the cache rate differs.
- qwen3.7-plus: 0.4 / 1.6 / 0.2 cache (Alibaba Model Studio official,
International base <=256K tier) — the earlier estimate conflated it with
the qwen3.7-max tier and was ~2.5x too high.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"the only GLM model that honors reasoning_effort" is an unenforceable
prose claim; state the capability the entry actually carries instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The tail reader's no-trailing-newline branch (a torn/truncated final
append, the case M3's fsync defends) and the over-window error branch
had no coverage. Add cases for a newline-free last line, a torn final
append that must be rejected, and a single line exceeding the 64KiB
window.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The M7 pin only checked models actually assigned to an agent type, so an
adaptive reasoning mode declared on an unassigned catalog model would pass
silently. Add a second loop over every catalog model so any openaicompat
provider that declares adaptive/adaptive-only fails loudly — matching the
guard's stated intent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The comment narrated the replaced sync.Map design and defended the
choice; the fixed-stripe array plus FNV indexing is self-evident and
the rationale already lives in commit d12b984.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- glm/models.yml: add glm-5.2 (only GLM model supporting reasoning_effort=high/max)
- kimi/models.yml: add kimi-k2.7-code (code-specialist above k2.6)
- qwen/models.yml: add qwen3.7-plus (cost-efficient tier below qwen3.7-max)
Prices for kimi-k2.7-code and qwen3.7-plus are estimated from adjacent
catalog entries and should be verified against official pricing pages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The per-agent options builder only emitted WithReasoning for low/medium/high,
and the langchaingo fork clamped xhigh/max to high in GetEffort (treating them
as adaptive-only). OpenAI GPT-5.5 and GLM-5.2 now expose xhigh/max as real
reasoning_effort levels, so that clamp encodes a stale invariant.
Extend the effort switch to pass xhigh/max through; the fork's GetEffort clamp
is removed in the local langchaingo (propagated via vendor) so the OpenAI
transport emits the real level. Adaptive (anthropic/bedrock) and budget paths
are unaffected — GetEffort is called only by the OpenAI transport. Validity is
gated per model by ModelReasoningInfo.Efforts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adaptive thinking (thinking.type=adaptive + output_config.effort) is an
Anthropic-protocol mode the langchaingo OpenAI transport cannot emit, and
the openaicompat Call path never threads PrepareAdaptiveCallOptions. So an
adaptive reasoning mode declared in an openaicompat provider's models.yml
would silently no-op.
Assert that none of the openaicompat-backed providers (qwen, glm, deepseek,
kimi, minimax) resolve to adaptive thinking for any agent type. The day
someone adds an adaptive model/mode to one of their configs, this fails
loudly instead of dropping the thinking budget in silence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
record() called readLastEvidenceReceiptHash on every append, which re-read
and re-hashed the entire receipts.jsonl just to get the last hash and verify
the whole chain — O(N^2) over a flow's receipts, on the tool-call hot path
under the per-path lock (M2).
Read only the last line instead (windowed ReadAt from the end), failing
closed if that line is missing, wrong-schema, or hash-mismatched. This is
O(1) per append and stays correct across the multiple recorder instances
that write one path (L6): the file stays the single source of truth, so
there is no per-instance cache to go stale. Full-chain verification belongs
in a separate read-time verify tool, not on every write.
Off by default (EVIDENCE_RECEIPTS_ENABLED=false). Measured: 1024 concurrent
appends under -race dropped from ~49s to ~7.5s.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The append wrote the receipt line and returned without flushing, so a crash
between the write and the OS flushing its page cache could lose or truncate
the last receipt, after which the fail-closed chain check halts all further
writes until the file is repaired (M3). Sync the file before returning.
Off by default (EVIDENCE_RECEIPTS_ENABLED=false), so standard deployments are
unaffected; the cost lands only when the feature is explicitly enabled.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three guards for the L6 sharded mutex pool (d12b984):
- bounded: 100k distinct flows resolve to <= 256 mutexes, so reverting to a
per-path map (the original unbounded leak) fails the test.
- high concurrency: 16 cross-executor writers on one path keep the hash chain
intact under -race.
- stripe collision: two flows that hash to the same stripe keep their own
chains intact, confirming shared stripes add contention but not corruption.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A sync.Map holding one *sync.Mutex per flow path grew unbounded for the
whole server uptime. A fixed 256-stripe array keyed by FNV-32a hash of
the path caps memory at a constant cost while preserving per-path append
serialization required by the hash-chain invariant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added langfuse observability support to the graphiti search tool, allowing for detailed tracking of search operations.
- Introduced a mapping of search types to human-readable titles for better traceability in logs.
- Implemented a new method to build the input payload for langfuse retrievers, encapsulating relevant search parameters.
- Enhanced error handling to include retriever status updates on success and failure, improving observability and debugging capabilities.
- Introduced a process-level singleton for the anonymizer replacer to enhance performance and ensure thread safety.
- The replacer is built once using patterns loaded at startup, allowing it to be shared across all flow executor instances.
- Updated the flow tools executor to utilize the shared replacer, simplifying the creation process and improving efficiency.
- Replaced expirable LRU cache with non-expirable lru.Cache in multiple files for improved performance and reduced resource usage.
- Introduced a new function, newSummarizerCache, to create a fixed-size LRU cache for summarizer results, eliminating the need for background goroutines.
- Updated related code to ensure compatibility with the new cache implementation.
The OAuth callback links/creates an account by the email from ResolveEmail, and
Google/GitHub only ever return a verified address — but that invariant lived
inside each provider and was invisible at the callback. A future OAuth provider
that omitted the verified check would let an attacker register a victim's email
there (unverified), "sign in", and be linked straight into the victim's account:
instant takeover.
Make verification part of the contract: ResolveEmail now returns
(email, verified, err), and the callback refuses to proceed when !verified.
Because Go's bool zero value is false and the compiler forces the new return, a
provider that forgets to report verification fails closed (its own login breaks)
rather than opening a takeover. Google reports claims.EmailVerified; GitHub only
selects verified addresses, so reports true. Added a callback test: a provider
reporting an unverified email is rejected (no link, no session) — red before the
gate, green after.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EmailChange.Mail validated with `vmail`, which accepts not just a real address
but also the literal "admin" and any UUID — escape hatches that exist so the
seeded admin row (mail "admin") passes User.Mail's Valid(). The account's
change-email form (added this branch) reaches it, so a user could save "admin"
or a UUID as their own email — a non-deliverable value (no privilege gain: roles
come from role_id, and UNIQUE(mail) blocks colliding with the real admin row).
Add a strict `realemail` validator (same address regex as vmail, minus the
hatches) and use it for EmailChange.Mail; User.Mail keeps `vmail` so the seeded
admin still validates. Tests cover the validator (admin/UUID rejected, real
address accepted) and the handler (changing to a UUID or "admin" now 400s).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ChangeEmailCurrentUser was the only path that lowercased the email
(strings.ToLower), while local login, the OAuth callback and CreateUser all
store/compare it raw against a case-sensitive UNIQUE(mail). That lone
normalization let a changed address (now lowercased) miss a later raw-case
login, and let the uniqueness pre-check (run on the lowercased value) skip an
existing mixed-case row. Drop the ToLower so every path is consistently
case-sensitive again — the pre-branch invariant. The email validator already
rejects surrounding whitespace, so the paired TrimSpace was dead.
Update the test that codified the old lowercasing to assert case preservation.
A fully case-insensitive scheme (normalize everywhere, or citext /
UNIQUE(lower(mail))) is tracked separately as a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NewProviderController loaded every provider's default config (NewConfig) in an
ungated loop that treated any error as fatal, while actual provider construction
(New) is correctly gated on Enabled. A disabled provider pointed at an unreadable
BEDROCK_CONFIG_PATH (or the equivalent custom/ollama external config) therefore
aborted startup, even though that provider was never going to be used.
Extract the config-loading loop into buildDefaultConfigs and gate the failure on
Enabled: a disabled provider whose external config can't be read is logged and
skipped; an enabled provider still fails loudly. Tests cover both paths
(disabled + bad path non-fatal, enabled + bad path fatal).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DetailNavigationSheet keeps a local input mirror (localQuery) seeded once from
controller.searchQuery — a deliberate perf decouple (92a9e59) so typing in the
sheet doesn't re-render the whole detail page. But in the controller's documented
controlled mode (a page-level search box that owns searchQuery), an external
change made while the sheet was closed left the mirror stale: reopening the sheet
showed the old text.
Re-seed the mirror from the controller on each open, folded into the existing
open-transition reconciliation. The sheet is modal, so searchQuery can only
change externally while closed — on-open re-seeding suffices and never clobbers
in-progress typing. Add a controlled-mode test that reproduces the stale input
(red before, green after).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
renderTextWithCJK selected the CJK font via `bold ? 'NotoSansSC' : 'NotoSansSC'`
— both branches identical, so the ternary was dead. Collapse it to the single
family. The CJK detection regex matches Han, kana, Hangul and Bopomofo, but the
only registered CJK font (NotoSansSC) lacks Hangul — verified with fontkit — so
Korean renders as missing-glyph boxes; document that limitation and the
NotoSansKR fix path.
splitByCJK and renderTextWithCJK had no tests; add a suite covering segmentation
(mixed Latin/CJK, kana, hangul, CJK punctuation, empty input) and per-segment
font assignment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The DetailNavigationSheet virtualized branch (items.length > 100) was never
exercised — the fixture had 4 items, always below the threshold. Add a 150-item
suite covering the virtualized path: a windowed subset of options, the roving
tabIndex on the current option, and in-window ArrowDown navigation. The
@tanstack virtualizer can't measure a viewport under jsdom, so the hook is mocked
to a fixed window (the windowing math is @tanstack's own coverage).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provider model field is an Input + dropdown combobox. Picking from the
dropdown (or "Use as custom") fires onOptionSelect, which resets price/reasoning
to the new model's catalog values — but typing the model name only updated the
field, leaving stale price/reasoning from the previously-selected model. Fire
onOptionSelect from the Input's onChange when the typed value exactly matches a
catalog option, so a typed known model behaves like a picked one.
Live-verified: typing us.anthropic.claude-sonnet-4-5-... into an agent on
gpt-oss reset Input Price 0.15->3, Output 0.6->15, cache 0->0.3/3.75.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
From a doc-grounded review against @apollo/client 4.2.3 + graphql-ws 6 (all
findings low/info, adversarially verified):
- streaming link: fold the throttle timestamp into the LRU StreamingLogEntry so
it is evicted with its log — removes the unbounded lastUpdateTimestamps Map
that leaked for streams that never receive a closing (non-appendPart) message.
- errorLink: also dispatch auth:refresh on a ServerParseError 401/403 (the narrow
application/graphql-response+json path that ServerError.is() does not match).
- WS retryWait: add jitter so a mass reconnect (e.g. backend restart) does not
thundering-herd the server.
- cache: log instead of silently swallowing a storeFieldName parse failure in
matchesCacheVariant, so a future Apollo serialization change is observable.
- v4 deprecations: onError -> new ErrorLink, createHttpLink -> new HttpLink,
FetchResult -> ApolloLink.Result, Operation -> ApolloLink.Operation.
Verified: tsc -b + vite build, 604 vitest pass, and a live smoke on the docker
stand (queries + subscription WS connect, no auth/JS errors).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The subscription WebSocket retries forever on a 403 handshake (expired/invalid
cookie), but its error handler only escalated to auth:refresh when the error
*message* contained "403"/"auth required" — and browsers never expose the
handshake HTTP status on a WebSocket error event, so that detection never fired.
An idle page with active subscriptions therefore looped on 403 with no redirect
to login (the redirect only fired once a REST/axios call hit a 403). Dispatch
auth:refresh unconditionally on any WS error and let /info decide: it redirects
on a real 401/403 and is a no-op while the session holds. Live-verified both
ways: an invalid cookie now redirects to login from an idle page, and a
transient WS drop on a valid session keeps the user logged in. Pre-existing (the
WS handler is unchanged from main); surfaced by a tester.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Frontend - Install dependencies" step ran pnpm install --frozen-lockfile
with continue-on-error: true, swallowing the ERR_PNPM_OUTDATED_LOCKFILE that the
flag exists to raise. The rest of the frontend pipeline (prettier, lint,
type-check, test) already gates strictly, so a stale pnpm-lock.yaml would only
surface later as a confusing downstream "command not found". Drop the flag so
lockfile drift fails loudly at the install step. Backend steps left as-is.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A type present in only one of the two hand-maintained lists fails silently:
accepted by the API then erroring "unknown provider type" at construction, or
rejected 422 despite working. Assert set-equality and canonical names so drift
fails in CI instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
UsesAdaptiveThinking/PrepareAdaptiveCallOptions had no test coverage after the
native-langchaingo migration removed the old transport-layer tests. Add table
tests: an adaptive-only model forces adaptive over an agent's budget choice or an
absent reasoning block, while budget-only models stay untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The four Zip* builders deferred zw.Close() with an unnamed return, dropping the
error from the central-directory flush. After the resources download moved to
direct socket streaming, a dropped connection during that final flush could
return a truncated archive under HTTP 200. Capture Close() via a named return so
streamZipArchive logs and aborts the partial response. Regression tests in both
packages drive the Close() error path with a failing writer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- settings-provider: handleTest/handleTestAgent/onSaveAndLeave now reset
submitError before re-validating. The error toast is driven by a
submitError-change effect, so a repeated identical validation failure
previously set the same string and never re-fired; the await between the
reset and the re-set splits the React batch so the toast shows every time.
- settings-prompt: the variables palette marks "used" chips with a leading
check icon, giving a non-color signal alongside the green variant (the
used/available distinction was conveyed by hue alone for sighted users).
- settings-sidebar: drop the unused `export` on the MenuItem interface — it has
no importers; the extraction from settings-layout was the moment to clean it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provider and prompt edit pages each carried a bespoke unsaved-changes
mechanism: a synthetic history.pushState + popstate listener that only caught
the browser back gesture, a "Discard changes?" ConfirmationDialog, and (after
the in-page Cancel button was dropped in the redesign) a chunk of unreachable
leave-handler code. In-app sidebar navigation while dirty silently discarded
edits, and the discard dialog rendered a Trash icon via confirmIcon={undefined}.
Both pages now use the same useUnsavedChangesGuard + UnsavedChangesDialog the
knowledge editor uses:
- useBlocker intercepts in-app router navigation (the sidebar links), and
beforeunload covers reload/tab-close — not just the back gesture.
- The dialog offers Cancel / Discard / Save & leave (the old one was Stay /
Leave with no save path).
- Forms switch to mode: 'onTouched' so isValid drives the dialog's Save button.
- Provider keeps "Save navigates to the list" via the guard's skipNextBlock;
prompt's Save & leave saves every dirty tab (snapshotting both before the
refetch-driven reset can clobber the other tab).
Removes the popstate/pushState refs, the dead handleConfirmLeave/-OpenChange
branches, and the misleading Trash icon. Resolves review findings on the
removed Cancel affordance, the dead leave-guard code, and the discard icon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A code review of the "Available variables" block surfaced several real issues:
- Used-detection only matched bare {{.X}} interpolations, so variables used
only in conditionals/ranges/nested access ({{if .X}}, {{range .X}}, {{.X.Y}})
showed as "available" and clicking them inserted a stray bare {{.X}} instead
of jumping to the existing usage. Detection and the click-to-locate logic now
share one regex matching a top-level field in any {{ }} action, mirroring the
backend's variable extraction; clicking a used variable selects its real
occurrence.
- Clicking a used variable scrolled to it by counting logical newlines, which
undershot badly once the textarea soft-wraps long lines (the default
templates lay out ~40% more visual lines than logical), so the jumped-to
variable landed off-screen. Measure the true pixel offset through a hidden
mirror element and center it, so the selection is always scrolled into view.
- Inserting a variable via a chip called setValue without { shouldDirty: true },
so the change never marked the form dirty and the unsaved-changes guard stayed
silent. Both insert paths now mark the form dirty, matching keyboard typing.
- The chips were click-only <div>s (no keyboard/screen-reader access). They are
now role="button" with tabIndex, Enter/Space handling, and a state-aware
aria-label/title ("Insert …" / "Go to …"), which also activates the Badge's
existing focus ring.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Available variables" block merged into the background on desktop and the
hand-rolled chips used light-theme greens (bg-green-100/text-green-800) that
clashed with the dark UI. Rebuild it on the project's primitives:
- Container: a delineated `rounded-lg border bg-card` panel with a `border-b`
header (title + "click to insert" hint); the chips sit in a recessed
`bg-background` tray below it, so the section reads as a distinct group with
depth instead of a faint box.
- Chips: the shared `Badge` component — `green` (theme-aware) for variables
already used in the template, `secondary` for the rest; monospace, normal
weight, clickable.
- Badge: give the color variants (blue/green/orange/pink/purple/red/yellow)
hover states to match default/secondary/destructive, so consumers don't
hand-roll hover colors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract the "Available Variables" panel out of promptMeta into its own
`variablesPanel`. Desktop keeps it in the left pane under the tab selector
(reference beside the editor); mobile now renders it after the editor instead of
between the tabs and the editor, so the template editor sits directly under the
tabs on small screens.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provider, knowledge and prompt edit pages share one 2-pane shell, but
mobile and the intro block had drifted. Align them:
- Mobile: drop the wrapping Card on provider + knowledge (knowledge-form-layout,
settings-provider) so all three render the stacked form directly in
`flex min-h-0 flex-1 flex-col gap-4 p-4` (matches the prompt page; gap-4 to
match the p-4 edge padding).
- Intro: use a `flex flex-col gap-2 text-center` wrapper with
`<h2 class="text-2xl font-semibold">` + a `text-muted-foreground` description
everywhere — knowledge was an `<h1>`, the prompt used `items-center gap-1`, and
both leaned on a `mt-2` margin instead of the container gap.
Desktop shell (root, ResizablePanelGroup 45/55, left Card + CardContent py-6,
grip handle, per-pane scroll) was already uniform. The right-pane content stays
page-specific (accordion / markdown editor / textarea).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the knowledge/provider edit pages: replace the single-column form +
sticky footer with a responsive 2-pane layout (desktop ResizablePanelGroup,
mobile single column). Left pane = centered intro + prompt identity + the
System/Human tab selector + the variables palette; right pane = the template
editor that fills the panel.
Move actions into the AppHeader: Validate + Save, plus an ellipsis menu (Reset,
Diff) shown only when a user override exists — no Cancel. Convert the inline
error alerts to a single submitError-driven toast, and the loading/error/
not-found branches to StatusCard. Drop the now-unused handleBack.
Shared UI tweaks supporting the editor:
- Textarea: add an `autoSize` opt-out (default true) so the editor can flex-fill
its parent (the auto-size hook's inline max-height otherwise clamps it).
- TabsTrigger: add `gap-2` (matches Button) so an icon + label aren't glued.
Align the panel/Card structure with the knowledge layout (py-6 intro,
overflow-y-auto left, overflow-hidden fill-editor right, grip handle, 45/55
split); style the tab bar like the dashboard analytics tabs (bg-background list
+ bg-card active, full-width flex-1 triggers).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a shared AppHeader compound family (AppHeader / AppHeaderAction /
AppHeaderActions / AppHeaderContent / AppHeaderTitle) in a single module and
fold the page-header action button into it as AppHeaderAction (renamed from the
former HeaderButton). Every page header now composes this family instead of
copy-pasting the sticky <header> shell:
- settings pages (account, api-tokens, prompt(s), provider(s))
- main-app lists (dashboard, flows, templates, resources, knowledges, new-flow)
- detail headers (flow, template, knowledge) keep their bespoke breadcrumb
content as AppHeaderContent children; their action clusters move into
AppHeaderActions; sibling sheets/dialogs stay outside the header
This removes the duplicated shell across ~15 sites and the drift between copies
(the family owns the one canonical sticky shell + the SidebarTrigger/Separator).
Reorganize components/layouts into per-area subfolders:
- app/ app-layout + the AppHeader family (app-header.tsx)
- main/ main-layout, main-sidebar (+ test)
- settings/ settings-layout, settings-sidebar (+ test)
- flows/ flows-layout
Delete settings-page-header.tsx (superseded by AppHeader).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebuild the provider settings page to match the knowledge editor:
- responsive 2-pane layout — left Card with the centered intro + Type/Name,
right scrollable panel with the agent-config accordion; mobile collapses
to a single-column Card (via useBreakpoint). Each pane scrolls
independently inside a fixed-height root (no whole-page scroll).
- move Save / Test / Delete into the page header (SettingsPageHeader actions);
drop the sticky-bottom action bar and the now-unused Cancel/handleBack.
- surface mutation/validation errors as sonner toasts instead of inline
Alerts (single submitError-driven effect).
- redesign the test-results modal: shrink-0 status icon, result/metadata as
badges, wrapping monospace error block, colored per-agent pass count.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the main app layout: SettingsLayout now renders only
SidebarProvider + SettingsSidebar + SidebarInset + Outlet, with no shared
header. Each settings page renders its own header via a new shared
SettingsPageHeader (sticky h-12 bar with an optional actions slot). The
sidebar is extracted into a self-contained SettingsSidebar component that
computes the "Back to App" returnUrl from location.state; its behavior is
covered by the new settings-sidebar.test (migrated from the removed
settings-layout.test). settings-account.test now wraps the page in a
SidebarProvider since the page renders its own SidebarTrigger.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adaptive thinking (Claude Opus 4.7+) is now implemented natively in the
vxcontrol/langchaingo fork (v0.1.14-update.6): the anthropic and bedrock clients
emit thinking.type=adaptive + output_config.effort and omit the sampling params
adaptive models reject. PentAGI no longer patches the serialized request body.
- pconfig.PrepareAdaptiveCallOptions appends llms.WithAdaptiveReasoning; the
ctx-effort plumbing (WithAdaptiveEffort/AdaptiveEffortFromContext) is removed.
- Delete the bedrock smithy Build middleware and anthropic http RoundTripper
(adaptive_thinking.go + tests in both packages) that rewrote the request body.
- Bump the langchaingo require to v0.1.14-update.6; refresh llms_how_to.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pnpm 11 installs globally-added binaries to $PNPM_HOME/bin and treats that
directory being absent from PATH as a hard error, so `pnpm add -g license-checker`
in the frontend-compiler stage failed ("global bin directory is not in PATH")
whenever the frontend layer cache was invalidated. Prepend $PNPM_HOME/bin to PATH.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
settings-providers.tsx redeclared its own ProviderType→icon map (and imported all
11 icon components) duplicating the registry already in provider-icon.tsx. Export
that map and consume it here, dropping the duplicate map and the 11 icon imports.
providerLabels/providerTypes stay (labels, single-use). No visual change — the
icons still render monochrome at their existing sizes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The provider controller repeated every provider type across five hand-written
blocks (default-config wiring, key-gated instantiation, GetProvider fallback,
NewProvider, buildProviderFromConfig) plus the API-layer Valid() whitelist.
Introduce pkg/providers/registry.go: a providerRegistry table whose entries hold
the per-type constructors and credential gating, with small adapter helpers
(ignoreConfig/fromData) absorbing the signature variance (bedrock/ollama/custom
take *config.Config; the rest don't). The controller now wires and looks up
providers in loops over the table. Valid() validates against the new canonical
provider.AllProviderTypes list (no heavyweight import, no cycle).
Adding a provider's backend wiring drops from ~6 edits across these functions to
one registry entry. providers.go shrinks ~300 lines; behavior is unchanged
(same exported funcs, same gating) and all provider/server/graph tests stay green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>