go-semantic-release auto-detects the default branch from GitHub API
(which is dev), but releases are cut from main. The CI condition
rejects runs on non-default branches. Use --no-ci to bypass this
check since the workflow already gates on push to main.
go-semantic-release auto-detects the default branch from GitHub API
(which is dev), but releases are cut from main. The CI condition
rejects runs on non-default branches. Use --no-ci to bypass this
check since the workflow already gates on push to main.
Scheduler tests hung on CI when t.Fatal fired before close(blockCh) —
defer sched.Stop() called wg.Wait() on goroutines still blocked on
blockCh. Fix: defer close(blockCh) after defer Stop() (LIFO order
ensures blockCh closes first).
Add release-beta.yaml for dev branch beta releases:
- Triggers on v*-beta* and v*-rc* tags
- Builds Linux binaries + Docker images (latest, full variants)
- Creates GitHub prerelease
- Docker tags: version-specific + "beta" rolling tag
- No overlap with release.yaml (branch-triggered, clean semver)
Update release-desktop.yaml:
- Auto-detect prerelease from tag name (beta/rc → prerelease: true)
release.yaml already builds and pushes Docker images (4 backend variants
+ web) to GHCR and Docker Hub when semantic-release creates a new
version. docker-publish.yaml triggered on v*.*.* tags, causing
duplicate Docker builds on every release.
The test closed blockCh then immediately exited, racing defer sched.Stop()
against scheduleNext(). Under -race on CI, Stop() could call wg.Wait()
while a goroutine was still being submitted, causing a 600s timeout hang.
Fix: wait for queued runs to complete before allowing Stop() to run.
Closes#677
When starting a new chat, loadHistory() races with chat.send — if
history is fetched before the server persists the user message, it
returns empty results and overwrites the optimistic message.
Skip loadHistory() on the "" → newKey session transition since the
optimistic user message is already in state. All other session
transitions (switch, refresh) continue loading history normally.
Closes#694
When a pool member provider is deleted/disabled, its name persists in
the owner's extra_provider_names config. Validation already skips stale
refs, but the names accumulated as garbage in the DB.
Now stripStalePoolMembers() runs after validation passes, removing
member names that don't exist in the active provider set before the
settings are written to the database.
* fix(secure-cli): resolve ambiguous column in LookupByBinary JOIN query (#641)
LookupByBinary uses LEFT JOIN with secure_cli_user_credentials but
SELECT columns lacked table alias prefix, causing PostgreSQL error:
"column reference 'id' is ambiguous (SQLSTATE 42702)"
This silently broke ALL credentialed CLI exec — commands fell through
to regular shell exec without injected env vars.
Fix: use b.-prefixed column names for JOIN queries.
Also add diagnostic logging to lookupCredentialedBinary for future debugging.
* fix(agent): defer warning messages after parallel tool results (#644)
When parallel tool calls trigger loop detection warnings, the warning
messages (role="user") were inserted between tool result messages
(role="tool"). This breaks the Anthropic API when routed through
OpenAI-compatible proxies (e.g. LiteLLM): the proxy groups consecutive
tool messages into a single user message with tool_result blocks, but
an intervening user warning splits the group, causing orphaned
tool_results and HTTP 400 "tool_use ids without tool_result blocks".
Fix: accumulate warning messages during parallel result processing and
append them after all tool results, preserving the consecutive grouping.
Closes#642
* fix(docker): resolve @rollup/rollup-linux-arm64-musl missing on Alpine (#647)
Added ui/web/.npmrc with supportedArchitectures for musl+glibc/arm64+x64.
Updated Dockerfile to use --no-frozen-lockfile so pnpm fetches native rollup
binding compatible with Alpine's musl libc. Lockfile still pinned by copy order.
* docs(README): add history stars (#462)
* fix(pool): skip stale pool member references during validation
Unknown pool member references (deleted or disabled providers) now
continue instead of returning an error. Prevents stale data from
blocking provider saves.
Closes#670
* fix(ui): redesign pool member selector and add managed-by banner
Pool member selector:
- Replace invisible outline button with custom element using dashed
primary border, + icon badge, and "Click to add" hint text
- Visible in both light and dark themes; hover transitions to solid
border with shadow; active press scales down for tactile feedback
Managed-by banner:
- Show "Pool Defaults" section on pool members with info banner
explaining which provider owns the pool, plus a Link navigation
- Previously this section was completely hidden with no explanation
i18n: add poolManagedByDescription and clickToAdd keys (en/vi/zh)
* docs: add before/after UI evidence for PR #671
Annotated screenshots with red callout borders marking review areas.
Self-contained HTML comparison report with dark/light theme toggle.
* feat(ui): add pool discovery badges and setup wizard
Replace verbose info banner with per-card "Pool available" badge on
unpooled ChatGPT OAuth providers. Clicking the badge opens a new
pool setup wizard dialog where users select owner, members, and
strategy in one step.
* docs: update UI evidence with pool discovery before/after
* fix(ui): hide pool members from provider selector in agent forms
Pool member providers are managed via the pool owner's routing config.
Showing them as standalone options in the agent Provider dropdown is
confusing — users may select a member directly instead of the owner,
bypassing pool routing entirely.
Filter out providers that exist in ownerByMember from the enabled
providers list in ProviderModelSelect.
* fix(ui): hide pool members from provider selector and add Pool badge
Pool member providers are filtered out of the agent Provider dropdown
in both the Create Agent dialog and the shared ProviderModelSelect
component. Pool owners display a "Pool" badge so users know the
provider routes to multiple accounts automatically.
* docs: add provider selector before/after evidence
* fix: revert stale merge in secure_cli.go and fix hardcoded i18n strings
- Revert secureCLISelectColsAliased: b.agent_id → b.is_global
(agent_id was dropped in migration 36, stale merge conflict artifact)
- Replace hardcoded "Pool" badge text with t("providers:list.poolBadge")
in provider-model-select and agent-identity-and-model-fields
- Replace hardcoded "Disabled" with t("common:disabled") in pool wizard
- Add list.poolBadge key to en/vi/zh locale files
---------
Co-authored-by: Viet Tran <viettranx@gmail.com>
Co-authored-by: Plateau Nguyen <nguyennlt.ncc@gmail.com>
Co-authored-by: DNT <ducconit@gmail.com>
- Refresh provider list when AgentCreateDialog, HeartbeatConfigDialog,
and AgentAdvancedDialog open (fixes stale cache from 60s staleTime)
- Remove verify-blocking from agent creation flow; verify remains as
optional manual check but no longer gates the Create button
- Aligns with existing pattern in TeamCreateDialog
chat.send accepted any sessionKey without verifying the caller owns the
session. A non-admin user could write messages into — or inject into an
active run of — another user's session by supplying their sessionKey.
Add the same canSeeAll + UserID guard used by chat.history/inject/abort.
New sessions (Get returns nil) are allowed through so first-message
creation is not blocked.
Closes the last IDOR vector identified during #676 review.
chat.history, chat.inject, chat.abort, and chat.session.status accepted
any sessionKey without verifying the caller owns the session. A non-admin
user could read, write, or disrupt another user's conversations by
supplying their sessionKey.
Apply the same requireSessionOwner() guard already used by sessions.*
methods: canSeeAll() bypass for admin/owner, sess.UserID match for
regular users. Extracted shared helper to access.go to reduce duplication.
Also fixes: handleSessionStatus i18n compliance (was hardcoded English),
and closes runId-only abort gap (non-admin must provide sessionKey).
The Zustand persist migration excluded tenantSelected from the
partialize whitelist, causing it to reset to false on page reload.
This created a redirect loop: require-auth saw the intermediate
state (connected + tenants loaded + tenantSelected=false) and
redirected to /select-tenant before the WS auto-select callback
could fire.
Restore the original localStorage-derived initialization so
tenantSelected is true immediately on rehydration when a tenant
scope was previously saved.
Closes#692
Add BytePlus ModelArk as a new OpenAI-compatible provider for Seed 2.0
models (chat, vision). Two provider types: standard API and Coding Plan
(separate base URLs, same auth).
Integrate Seedream image generation (sync API) and Seedance video
generation (async polling) into the builtin media tool chain, following
the established DashScope/Gemini patterns.
- Add WithAuthPrefix option to OpenAIProvider for future non-standard auth
- Add ProviderBytePlus/ProviderBytePlusCoding store constants and config
- Register provider from config.json and llm_providers DB table
- Add BytePlus to media chain routing, priority lists, and dispatch
- Create create_image_byteplus.go (Seedream, sync response)
- Create create_video_byteplus.go (Seedance, async poll with 5min timeout)
- Add BytePlus to web and desktop UI provider type dropdowns
- Update provider docs with BytePlus entries
Closes#686
Port PR #685 fixes for HTTP 400 on Together AI and strict OpenAI-compat hosts,
with additional improvements:
- Gate reasoning_content on assistant history to allowlisted models only
(OpenAI o-series/GPT-5, DeepSeek, Kimi) — prevents HTTP 400 on Together/Qwen
- Gate reasoning_effort to models that support it (OpenAI reasoning family)
- Skip stream_options for Together endpoints (causes HTTP 400)
- Scope DashScope enable_thinking/thinking_budget to DashScope providers only
- Reorder multimodal parts: text before images (Together/Qwen preferred order)
- Add redacted_thinking tag to sanitization patterns with early-exit guard fix
- Upgrade Together detection from URL-only to URL + providerType + name fallback
(mirrors dashScopePassthroughKeys pattern for reverse-proxy compatibility)
- Add comprehensive tests for all new behavior
Replace agent_id column on secure_cli_binaries with is_global flag
and new secure_cli_agent_grants table for per-agent access control
with optional deny_args, deny_verbose, timeout_seconds, tips overrides.
- Migration 000036: create grants table, migrate agent-specific rows,
dedup binaries, drop agent_id, add is_global
- Store layer: SecureCLIAgentGrantStore interface + PG implementation,
LookupByBinary with LEFT JOIN grant merge, ListForAgent
- HTTP API: CRUD endpoints at /v1/cli-credentials/{id}/agent-grants
- Agent loop: buildCredentialCLIContext uses ListForAgent for scoped
system prompt (agents only see authorized CLIs)
- Web UI: grants dialog with card list + inline form, is_global toggle
replaces agent dropdown, i18n for en/vi/zh
TraceContentPreview was rendering JSON through CodeBlock with oneDark theme,
causing black background spans in light mode. Use plain <pre> with JSON
pretty-print instead — matches the original trace detail rendering.
- H1: rename McpTestResult component import to McpTestResultDisplay (avoid type collision)
- H2: replace local slugify with shared lib/slug import in McpFormDialog
- H3: restore same-path no-op guard in storageService.moveFile
- H4: deduplicate StorageFileContent interface (use service as source of truth)
- M1: replace inline import() syntax with standard import in use-storage
Fix Mistral tool call ID formatting on the outbound OpenAI-compatible payload.
For provider `mistral` only, normalizes both `tool_calls[].id` and `tool`
message `tool_call_id` to a 9-character hex string (SHA-256 based) before
sending the request. Other providers keep the existing `truncateToolCallID`
behavior unchanged.
- Hash full ID via SHA-256 to avoid prefix-dependent collisions
- Detect Mistral via both provider name and providerType (DB-loaded support)
- Add collision uniqueness and DB provider detection tests
SSRF validation in validateProviderURL() blocked all localhost/loopback
addresses, preventing local providers like Ollama from being configured
with http://localhost:11434/v1. Introduce localProviderTypes map to skip
SSRF checks for inherently local provider types (ollama, claude_cli, acp).
Closes#673
ForceGraph2D was rendering at wrong default dimensions when the
KG page first loaded after selecting an agent. Root cause: early
return for empty state removed the container div from DOM, so
useLayoutEffect couldn't read dimensions on first mount.
Fix: always render container div (flex-1 gives it height from
parent), show empty state inside it. Use useLayoutEffect for
synchronous initial measurement + ResizeObserver for ongoing
changes. Gate ForceGraph2D render behind `ready` flag.
Also extracted memory-documents-table from memory-page (gap closure).
Create src/adapters/ with 3 adapter files:
- kg-graph.adapter.ts: KGEntity/KGRelation → GraphData (nodes, links,
degree computation, color mapping, entity limiter)
- chat-message.adapter.ts: moved from chat/hooks/chat-message-transformer.ts
- trace.adapter.ts: buildSpanTree extracted from trace-span-tree-node
Skipped team-task (already in board-utils) and provider-pool (<10 lines,
single call site).
Inline transforms in kg-graph-view.tsx and trace-detail-dialog.tsx
replaced with adapter calls. No behavior changes.
- Add node limit selector (100/200/300/500) in graph stats bar
- Graph API fetches 500 (server max), frontend clips by degree centrality
- Remove auto zoom-to-fit on engine stop (was overriding user zoom/pan)
- Keep manual zoom-to-fit button in stats bar
- Default view mode changed from table to graph
- Replace ReactFlow (SVG, lags at 80+ nodes) with react-force-graph-2d
(Canvas/WebGL, handles 500+ nodes at 60fps)
- Custom nodeCanvasObject: colored circle by entity type + name label
- Single click: highlight node + neighbors + connected edges with
directional particles; dim unconnected nodes to 20% opacity
- Double click (timer-based 280ms): open entity detail modal
- Edge labels shown only for selected node's connections (background pill)
- Increase GRAPH_LIMIT from 80 to 150 (Canvas renderer handles it)
- ResizeObserver for responsive container sizing
- Zoom-to-fit button in stats bar
- Auto zoom-to-fit on engine stop
- Dark/light theme support via isDark conditional colors
- Remove ReactFlow + D3 force imports (packages kept in package.json)
Trace spans and cost calculations were recording the agent's default
model/provider instead of the effective overridden values from heartbeat
requests. Introduced functional options pattern (spanOption) so all span
emitters resolve the correct model/provider.
- Fix filter not reloading: remove placeholderData from entity list and
graph queries so data clears immediately on filter change; add key prop
to force remount on filter change
- Fix graph lag/crash: reduce GRAPH_LIMIT to 80, increase Barnes-Hut theta
for faster approximation, increase alphaDecay for faster convergence
- Move entity type stats to separate line below toolbar (better readability)
- Change node click: single-click highlights edges, double-click opens detail
- Increase entity list limit to 200 (table view only, graph stays at 80)
- Fix MCP/CLI credential dialogs: separate search text from selected user ID
to prevent keystroke-as-selection bug (use onSelect pattern)
- Fix Combobox: suppress value-sync after selection to prevent label flicker
- Fix MCP dialog: prevent full-page spinner flash when switching users
- Fix KG page filter: replace session-based scope with stats-based user_ids
from KG entities (more reliable user list)
- Increase KG graph visualization limit from 50 to 200 nodes with optimized
D3 force layout breakpoints for 100+/150+ nodes
- Cap graph API server-side limit at 500 to prevent unbounded queries
- Increase KG traversal max_depth from 3 to 5 (HTTP API + agent tool)
- Add dedicated Zustand store for KG detail dialog (isolated from main graph)
- Add depth selector UI (2-5 hops) in entity detail dialog
- Replace `team_message` with `spawn` for subagent orchestration
- Update team communication description to reflect current implementation
- Align docs with actual tool capabilities
Related: Team coordination updates for agent orchestration
- Write-time normalization via normalizeOllamaAPIBase() on create/update
- Read-time safety net in resolveAPIBase() for pre-existing DB records
- Covers both ProviderOllama and ProviderOllamaCloud
- Stop hardcoding +"/v1" in registerInMemory/startup registration
Closes#654
- Add `reasoning` JSON alias to openAIMessage and openAIStreamDelta structs
- Coalesce reasoning/reasoning_content in streaming and non-streaming parse paths
- reasoning_content remains canonical; reasoning is Ollama-specific fallback
Closes#655
- Ollama/OllamaCloud providers now use native /api/tags for richer metadata
- Display name built from family + parameter_size + quantization_level
- Strips /v1 suffix, DockerLocalhost support, graceful fallback on error
- 7 comprehensive tests covering all edge cases
Closes#656
Fireworks AI requires stream=true for max_tokens > 4096. Proactively
clamp non-streaming requests to avoid 400 errors (their error format
doesn't match the generic clampMaxTokensFromError retry regex).
Closes#660