* feat(ollama): auto-detect context window, allow user override
Query Ollama API (/api/show) to fetch model's actual context_length
on provider startup/hot-registration. Use queried value by default,
but allow user to override via num_ctx field in provider settings.
- backend: query API, store in provider config, use in requests
- frontend: add num_ctx input field to Ollama provider dialog (4 langs)
- fallback: 131072 tokens (128K) if API unreachable
Fixes context-window errors when using large-context models.
* fix(ollama): route to /api/chat endpoint to honor num_ctx
Ollama's /v1/chat/completions (OpenAI-compat shim) ignores options.num_ctx.
Route to /api/chat (native endpoint) which respects num_ctx.
Request body is identical, only endpoint URL changes:
- Before: /v1/chat/completions (ignores options.num_ctx)
- After: /api/chat (honors options.num_ctx)
Fixes context-window errors when using models with large context windows.
* refactor(providers): replace OpenAI-wrapped Ollama with native SDK
Implement dedicated OllamaProvider using github.com/ollama/ollama/api
official client library. Replaces the broken approach of wrapping
Ollama behind OpenAI-compatible parsing.
Benefits:
- Proper response parsing (Ollama native format, not OpenAI)
- Automatic /v1 suffix stripping
- Built-in num_ctx support (injected into every request)
- Tool call support via Ollama's native ToolCall schema
- Token usage mapping from Ollama Metrics
- Clean streaming implementation
Fixes context-window errors by using Ollama native API directly.
Removes dependency on OpenAI response format hacks.
- new: internal/providers/ollama.go (OllamaProvider)
- updated: cmd/gateway_providers.go, internal/http/providers.go
- updated: go.mod (added github.com/ollama/ollama)
* fix(ollama): fetch num_ctx for config-registered providers
Config-file-registered Ollama providers (cfg.Providers.Ollama.Host)
were hardcoding numCtx=nil, so num_ctx was never sent to Ollama.
Add FetchOllamaModelContext call for config providers, same as
DB-registered providers. Now config Ollama providers also auto-detect
and send the model's context window to the Ollama API.
Fixes context-window errors when using config-registered Ollama.
* fix(ollama): always send num_ctx to prevent context window errors
OllamaProvider.buildRequest only injected options.num_ctx when an
explicit override was configured. When resolveOllamaNumCtx saw that
/api/show returned the OllamaDefaultNumCtx value (131072), it returned
nil, causing the provider to omit num_ctx entirely. Ollama then fell
back to the model's baked-in default (4096 tokens), causing requests
with long system prompts to fail with "request exceeds context size".
Fix both layers:
- resolveOllamaNumCtx now always returns &numCtx instead of nil when
/api/show returns the default, so the explicit value is always passed.
- OllamaProvider.buildRequest falls back to OllamaDefaultNumCtx (131072)
when p.numCtx is nil, ensuring every request carries a large context
window even if the startup query was skipped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Bruno Clermont <bruno.clermont@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(skills): display structured security violations in upload error
When skill upload fails due to security violations, show each violation
as a clear list with line number and reason in the error display.
Changes:
- Add ApiError.getViolations() method to extract violations from response
- Update HTTP upload client to capture violations in error details
- Add violations field to SkillEntry type
- Display violations as bulleted list in SkillSubtitle when present
- Add i18n keys for security violations display (en, vi, zh, ko)
New UI behavior: Instead of a generic "Upload failed" message, users
now see structured violation details:
Security violations:
- Line 5: SQL DROP TABLE
- Line 12: curl pipe to shell
Backward compatible - falls back to generic error message if violations
array is absent from response.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(skills): return structured JSON violations array in upload error response
Replace flat string formatting with a structured violations array (line, reason)
so clients can display per-line security rule details instead of a generic error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Bruno Clermont <bruno.clermont@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- 백엔드: LocaleKO 상수, IsSupported에 ko 추가, catalog_ko.go 185개 문자열
- 웹 UI: 38개 네임스페이스 JSON 파일 (locales/ko/)
- constants.ts SUPPORTED_LANGUAGES에 ko 추가
- index.ts에 ko 리소스 등록 및 getInitialLanguage에 ko 감지
- en/topbar.json languages에 한국어 옵션 추가
- webui handler: index.html에 no-cache 헤더 추가하여 배포 후 캐시 문제 방지
Add total-count pagination to the webhook admin endpoints and the web UI.
Store:
- WebhookStore/WebhookCallStore gain Count; WebhookListFilter gains
IncludeRevoked + Query (PG + SQLite, parameterized, tenant-scoped)
API:
- GET /v1/webhooks and GET /v1/webhooks/{id}/calls return
{items, total, limit, offset} with server-side search + revoked filtering
Web UI:
- server-driven list pager + search/revoked filter; call-history dialog uses
the real total (fixes the full-page "has more" boundary bug)
- i18n pager labels (en/vi/zh)
Tests: store pagination integration test; mock stores implement Count.
No schema migration (read-only COUNT).
The agent config UI (tools-profile-section) and i18n already expose a
per-agent "Rate Limit (per hour)" field saved into tools_config, but the
backend ignored it — the tool rate limiter only ever used the global
tools.rate_limit_per_hour. Wire the field through: ToolPolicySpec gains
RateLimitPerHour; the agent loop threads it into the tool-execution
context; the registry passes it to the limiter, which uses it in place
of the global max when > 0 (0 inherits the global).
* feat(mcp): MCP OAuth 2.1 client — full implementation with tests
Implements a complete MCP OAuth 2.1 authorization flow for tool servers that
require user-delegated access, covering all layers from DB to UI.
- discovery.go: RFC 9728 protected-resource → RFC 8414 AS metadata → OIDC
fallback chain with 5-min in-memory cache and InvalidateCache()
- dcr.go: RFC 7591 Dynamic Client Registration with response size guard
- flow.go: PKCE (S256) authorization code flow — StartFlow(), ExchangeCode(),
ClientCredentials(), auto-cleanup of expired flows; carries AS issuer through
PendingFlow for status display
- refresher.go: OAuthTokenProvider with in-memory token cache, automatic refresh
on expiry, per-user vs global slot isolation, InvalidateCache/InvalidateServer
- migrations/000074 + SQLite schema: mcp_oauth_tokens with AES-256-GCM encrypted
access/refresh tokens, partial unique index for global vs per-user rows,
ON DELETE CASCADE from mcp_servers
- store.MCPOAuthTokenStore: Upsert, Get/GetUser, Delete/DeleteUser, and
DeleteServerOAuthTokens (purge all rows for a server)
- PostgreSQL + SQLite implementations
- POST /v1/mcp/oauth/start — discovery + optional DCR + PKCE redirect URL;
client_credentials completes server-side (no redirect) and returns completed=true
- GET /v1/mcp/oauth/callback — exchange code, persist token, publish WS event;
payload built via json.Marshal (no reflected XSS via error_description)
- GET /v1/mcp/oauth/status/{id}, DELETE /v1/mcp/oauth/token/{id} — admin-gated
- POST /v1/mcp/oauth/discover/{id} — on-demand discovery probe
- All outbound calls go through the SSRF-safe client with pinned IPs
- pkg/protocol/mcp_events.go: EventMCPOAuthComplete routed only to the initiating
user (admins in-tenant included); fail-closed across tenants
- getUserMCPTools() injects Authorization: Bearer from OAuthTokenProvider; on a
401 for OAuth servers it purges the cached token so the next turn re-resolves
- handleUpdateServer purges all OAuth tokens (global + per-user), drops the
refresher cache, and evicts the pool when a server's URL or OAuth config
(client_id / endpoints / grant_type / scope / auth_type) changes — so the
status UI and agent never use a token minted for the old resource/AS
- MCPOAuthDialog (WS-driven), unified user-credentials dialog, OAuth settings
fields; handles the no-redirect client_credentials completion
- internal/mcp/oauth/*_test.go: discovery cache, PKCE, DCR, refresher
- internal/http/mcp_oauth_test.go + mcp_update_oauth_purge_test.go: routes, auth
gating, WS event, purge-on-URL/OAuth-config-change
- tests/integration: store + encryption + tenant isolation, E2E start→callback,
DeleteServerOAuthTokens
- internal/gateway/event_filter_test.go, internal/agent/loop_mcp_user_test.go
* fix(mcp): return 400 on OAuth callback with code but missing state
The callback handler rendered a 200 HTML page whenever code or state was
absent. An auth code WITH a missing state is a malformed / CSRF-risk
callback (state is the CSRF token), so reject that case with HTTP 400.
A bare hit with neither code nor state (user opening the URL directly),
provider errors, and exchange failures keep their 200 HTML popup page.
Adds a status code parameter to writeCallbackHTML. Fixes the
TestOAuthCallbackMissingState integration regression while keeping
TestHandleCallbackMissingCodeAndState (no params -> 200) green.
* fix(mcp): scope-based OAuth auth + honor manual OAuth endpoints
Addresses the two MCP/OAuth security-review findings.
Finding 1 — authorization. mcp_oauth_tokens is tenant-scoped, but
start/status/revoke were gated only by requireAuth(RoleAdmin), an RBAC
role check, not tenant membership, so a RoleAdmin caller could act on a
tenant they don't administer. A blanket requireTenantAdmin would have
broken per-user self-service, which the UI exposes (the per-user
MCPUserCredentialsDialog shows an "Authorize" button to regular users for
their own credentials). Instead mirror the existing per-user MCP
credentials model (resolveTargetUserID in mcp_user_credentials.go):
- start/status/revoke accept any authenticated user; each handler calls
authorizeOAuthScope.
- a caller may manage their OWN per-user token (self-service); the
global/server token (user_id="") and other users' tokens require
tenant-admin (owner bypass), so a RoleAdmin that is not a tenant admin
is rejected.
- discover stays admin-only (it only previews AS metadata for a server).
Add a TenantStore dependency. Tests cover self-service, on-behalf-of-
another (403), and global-by-non-tenant-admin (403).
Finding 2 — honor manual OAuth config end-to-end. The UI sent use_dcr /
auth_endpoint / token_endpoint and the update path fingerprinted them for
purge, but handleStart always discovered + DCR'd and ignored them. Now:
- use_dcr=false (a *bool, so legacy/absent stays discover+DCR) skips
discovery/registration and uses the operator endpoints, SSRF-validated.
- token_endpoint is always required; auth_endpoint only for auth-code
grants — client_credentials needs no authorization URL, matching the UI
which hides that field for that grant.
- the refresher already refreshes against the stored token_endpoint and
the callback persists it, so manual-mode tokens refresh correctly.
- oauthFingerprint includes use_dcr (nil normalized to true) so toggling
DCR mode purges stale tokens.
- the web form only serializes manual endpoints when use_dcr is off.
Audited all MCP dialogs (form, global OAuth, per-user credentials, grants,
tools): OAuth dialogs handle completed/auth_url identically and read
config from stored server settings; runtime connect uses the stored token
via the refresher (no re-discovery).
Tests: manual auth-code + client_credentials endpoints, missing/SSRF
endpoints, and the full self/global/on-behalf authorization matrix.
* feat(webhooks): add webhook management UI with delivery history & test
Webhooks admin page on the web dashboard for managing inbound HTTP webhooks
(llm + message kinds), backed by new admin endpoints. No schema changes
(reuses migrations 000059-000061).
Backend (internal/http):
- GET /v1/webhooks/{id}/calls - paginated delivery history (trimmed
DTO; status/limit/offset filters; ownership + tenant scoped)
- GET /v1/webhooks/{id}/calls/{callId} - full single-call detail (request
payload, full response, callback URL, idempotency key, timestamps)
- POST /v1/webhooks/{id}/test - server-side test invocation using the
admin session (no secret); dispatched by kind via RunTest() on the llm/message
handlers; message tester nil-guarded (403 on Lite)
Injects WebhookCallStore + SetTesters() in cmd/gateway_http_wiring.go, adds i18n
key webhook.message_test_requires_standard (en/vi/zh), and unit tests for
list/detail (filter, tenant isolation) and test (success/error/edition gate).
Frontend (ui/web):
- /webhooks admin-only page: list with revoked filter + badge, create/edit form
(edition-gated message kind, Lite localhost_only lock), show-once secret dialog
(create + rotate), test dialog, and delivery-history dialog with server-side
pagination plus a click-through full call-detail dialog.
- use-webhooks hooks, query keys, types, routing, sidebar entry (Cable icon to
distinguish from the existing event "Hooks" page), webhooks i18n namespace
(en/vi/zh).
* fix(webhooks): validate admin message tests
---------
Co-authored-by: Goon <duy@wearetopgroup.com>
Use the existing relative-time guard when deciding whether to show channel diagnostics, so Go zero-value timestamps do not make healthy channels look broken.
Add regression coverage for zero-value first_failed_at and active failure states.
Co-authored-by: evgyur <evgyur@gmail.com>
Moonshot's Kimi Coding endpoint is OpenAI-compatible on the wire but
has two non-standard rules:
1. Every request must carry `User-Agent: claude-code/0.1.0` — without
it the upstream rejects the call outright.
2. `temperature` is locked to the server default; passing any other
value returns HTTP 400 `invalid temperature: only 1 is allowed for
this model`.
Rather than special-case either, this commit generalises both:
- WithExtraHeaders on OpenAIProvider — static headers attached to
every outgoing request. Reusable by any future provider that needs
pinned identity headers; mirrored in adapter_openai.ToRequest so
callers using the adapter path see the same shape.
- The existing skipTemp branch in openai_request.go gets a
provider_type check — kimi_coding joins o1/o3/o4/gpt-5-mini in
omitting `temperature` from the request body.
Provider wiring:
- store.ProviderKimiCoding constant + ValidProviderTypes entry +
KimiCoding{DefaultAPIBase,DefaultModel,RequiredUserAgent}.
- case store.ProviderKimiCoding in both registration switches
(cmd/gateway_providers.go and internal/http/providers.go).
- UI dropdown entry with the API base pre-filled.
5 unit tests cover: real outgoing header injection, adapter-path
header mirroring, empty-map WithExtraHeaders no-op, kimi_coding
strips temperature, and the negative control (other providers still
forward temperature).
Admin flow: Providers → Add → "Kimi Coding (Moonshot)" → paste API
key → save.
- Change outer container from items-center to items-start sm:items-center for mobile-first layout
- Add overflow-y-auto and max-h-dvh constraints to inner content div on mobile
- Desktop (sm breakpoint) retains centered layout with no scroll constraint
- Fixes issue where setup page content exceeds viewport on mobile without zoom
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add validateSelfHostedDomain() with SSRF protection (blocked CIDRs,
hostname resolution, localhost/.local/.localhost rejection)
- Validate port range 1-65535 for self-hosted domains with ports
- Mirror SSRF + port validation in frontend form
- Add comprehensive tests: SSRF blocked IPs, invalid ports, valid public
domains
Quick Ack and Intermediate Replies now require generated delivery text in generated modes, while fixed-template mode remains explicit. Includes provider/model resolution coverage and UI copy updates.
Use shared provider/model selector UX for Delivery Behavior settings across workspace, agent, and channel configuration. Also closes disabled combobox dropdowns.