1045 Commits

Author SHA1 Message Date
Kai (Tam Nhu) Tran 8730a90acb fix(exec): allow uploaded files in active workspaces (#748)
Shell-aware command parsing, dynamic workspace exemptions, and symlink canonicalization for exec path denial. Fixes #739.
2026-04-08 13:35:19 +07:00
Plateau Nguyen e9155e0c5d fix(permissions): use cron-specific permission check for cron tool (#725)
* fix(security): harden exec path exemption matching (#721)

- Add absolute path exemption for dataDir/skills-store/ (fixes skill
  scripts using absolute paths like /app/data/skills-store/ being denied)
- Strip surrounding quotes before prefix matching (LLMs often quote paths)
- Reject path traversal ("..") in exempt fields to prevent escape
- Switch from "any field exempt → skip" to per-field matching: only exempt
  if ALL fields that match the deny pattern are individually exempt
- Closes pipe/comment bypass vectors where an exempt path in one argument
  would exempt the entire command including non-exempt paths

Includes 27 test cases covering: legitimate access, quoted paths,
path traversal, unicode bypass, pipe/comment bypass, mixed args.

* fix(permissions): use cron-specific permission check for cron tool

Cron tool was hardcoded to check `file_writer` configType via
CheckFileWriterPermission(), ignoring the `cron` configType that
the UI actually saves when granting cron permissions. This caused
agents in group chats to be denied cron access even with correct
permission configured.

Add ConfigTypeCron constant and CheckCronPermission() that checks
`cron` configType first, falling back to `file_writer`.

---------

Co-authored-by: Viet Tran <viettranx@gmail.com>
2026-04-07 12:41:18 +07:00
Kai (Tam Nhu) Tran e22a870cba fix(chat): load message history on first conversation click (#730)
* fix(chat): load message history when selecting existing conversation from clean state

The skipNextHistoryRef was unconditionally set when sessionKey transitioned
from empty to non-empty. This prevented loadHistory() from running when
clicking an existing conversation from the initial /chat page. The skip
was only intended for the new-chat send flow where the optimistic message
is already displayed.

Guard the skip with expectingRunRef so it only activates when a message
send is in flight.

Closes #729

* docs: add UI diff evidence for PR #730

Before/after screenshots and HTML comparison report showing
first conversation click behavior fix.
2026-04-07 12:18:57 +07:00
Duc Nguyen 0db1e93abf feat(whatsapp): add native WhatsApp channel with whatsmeow (#720)
Replace Node.js Baileys bridge with native go.mau.fi/whatsmeow — zero
external dependencies. QR auth, media support, markdown formatting,
typing indicators, dual JID/LID identity, group policies, pairing.

Resolves #703
2026-04-07 12:12:44 +07:00
viettranx 20c4478fe1 fix(security): harden exec path exemption matching
- Add absolute path exemption for dataDir/skills-store/ (fixes skill
  scripts using absolute paths like /app/data/skills-store/ being denied)
- Strip surrounding quotes before prefix matching (LLMs often quote paths)
- Reject path traversal ("..") in exempt fields to prevent escape
- Switch from "any field exempt → skip" to per-field matching: only exempt
  if ALL fields that match the deny pattern are individually exempt
- Closes pipe/comment bypass vectors where an exempt path in one argument
  would exempt the entire command including non-exempt paths

Includes 27 test cases covering: legitimate access, quoted paths,
path traversal, unicode bypass, pipe/comment bypass, mixed args.
2026-04-06 13:16:52 +07:00
viettranx 0a27e1247a Merge remote-tracking branch 'origin/main' into dev 2026-04-06 12:56:44 +07:00
Viet Tran e88686b13a fix: deterministic prompt ordering for LLM cache hit (#719)
Sort all non-deterministic map iterations that affect system prompt and
tool definitions sent to LLM APIs. Go map iteration order is random,
causing prompt prefix to change every turn — breaking Anthropic/OpenAI
prompt caching (cache by exact prefix match).

Fixed 5 sources of non-deterministic ordering:
- Registry.List(): sort canonical tool names
- Registry.ProviderDefs(): sort tools + aliases before building defs
- PolicyEngine.FilterTools(): sort alias iteration (single Aliases() call)
- buildMCPToolsInlineSection(): sort MCP tool names in system prompt
- GetAgentContextFiles/GetUserContextFiles: ORDER BY file_name (PG+SQLite)

Based on PR #718 by @therichardngai-code with additional fixes:
- Context files from DB now deterministic (ORDER BY file_name)
- FilterTools() calls registry.Aliases() once instead of 3 times
2026-04-06 12:54:44 +07:00
viettranx 85d44e8471 fix(ui): improve traces table layout and readability
Compact columns: status as icon-only, merge time+duration into one column.
Truncate long user IDs, clean <media:*> tags from preview, move badges to second line.
2026-04-06 10:42:47 +07:00
viettranx 6ddc112940 fix(prompt): skip credentialed CLI context when exec tool is denied
Agents with exec in their deny list cannot run CLI commands, so
injecting wrangler/gh credential context is misleading — the LLM
sees instructions for tools it cannot use. Gate the section on
exec being present in the filtered tool list.
2026-04-05 22:27:16 +07:00
viettranx 41e6c8f5cc feat(infra): tracing recovery, browser cleanup, CLI fixes, UI workspace split (#709)
- Tracing: recover stale running traces/spans on startup (PG + SQLite)
- Browser: Chrome orphan cleanup via launcher PID, timeouts, Leakless
- Claude CLI: WaitDelay 5s + context-cancel early exit
- Agent loop: safety-net defer to finalize orphan root traces
- UI: split workspace sharing into separate Memory and KG toggles
- Minor: for-range idiom, min() builtin
2026-04-05 21:32:59 +07:00
Duy /zuey/ 7d7b716074 fix(tools): quote-aware shell operator detection in credentialed exec (#700) (#702)
* fix(tools): quote-aware shell operator detection in credentialed exec (#700)

- Replace detectShellOperators with detectUnquotedShellOperators in
  credentialed exec path — respects single/double quoting so that
  characters like | inside argument values (e.g. --jq '.[0] | .name')
  are not falsely flagged as shell operators
- Pass raw command string (preserving quotes) to executeCredentialed
  instead of reconstructing from parsed args
- Downgrade "no credential found" log from Warn to Debug (fires for
  every non-credentialed command, too noisy at Warn)
- Add extractUnquotedSegments() helper with comprehensive tests

* fix(tools): handle backslash escape outside quotes in shell operator detection

extractUnquotedSegments did not handle \ as an escape character outside
of quotes, causing \" to incorrectly enter double-quote mode. This hid
subsequent shell operators from detection (e.g. gh \"arg\" | env would
not detect the unquoted pipe).

Add backslash escape handling in the unquoted state to match
go-shellwords parsing behavior. Both \ and the escaped character are
emitted as unquoted content so operator detection still catches them.

---------

Co-authored-by: viettranx <viettranx@gmail.com>
2026-04-05 20:36:34 +07:00
Duy /zuey/ 2801f0a978 feat(providers): add OpenRouter identification headers (#705)
* fix(ci): skip CI condition in semantic-release for main branch

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.

* docs: document CI/CD pipelines, release flow, and v2.66.0 changelog

- CLAUDE.md: add CI/CD & Releases section with workflow table, tag
  patterns, Docker variants, beta/desktop release commands
- CONTRIBUTING.md: expand Releases section with standard (auto),
  beta (manual tag), and desktop release workflows
- docs/17-changelog.md: add v2.66.0 entry covering IDOR fix, BytePlus
  provider, per-agent grants, beta pipeline, and CI fixes

* fix(telegram): handle group-to-supergroup migration seamlessly

When a Telegram group upgrades to a supergroup, the chat ID changes and
all existing references become stale. This caused send failures (400),
orphaned sessions, and required manual re-pairing.

Add dual-path migration handling:
- Proactive: intercept inbound MigrateToChatID before isServiceMessage
- Reactive: detect 400 + MigrateToChatID on send, migrate DB, retry

DB migration updates in a single transaction (scoped by tenant + channel):
- paired_devices: sender_id, chat_id
- sessions: session_key, user_id
- channel_contacts: sender_id
- channel_pending_messages: history_key

Also invalidates in-memory caches (approvedGroups, pairingReplySent,
groupHistory) and handles media sends via migration retry in Send().

* feat(providers): add OpenRouter identification headers (#704)

Add HTTP-Referer and X-Title headers to OpenRouter API requests
for rankings and analytics visibility on openrouter.ai.

---------

Co-authored-by: viettranx <viettranx@gmail.com>
2026-04-05 18:51:17 +07:00
viettranx 289a7e3680 Merge remote-tracking branch 'origin/main' into dev 2026-04-05 13:13:41 +07:00
Viet Tran 5987349b0d fix(telegram): handle group-to-supergroup migration (#698)
* fix(ci): skip CI condition in semantic-release for main branch

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.

* docs: document CI/CD pipelines, release flow, and v2.66.0 changelog

- CLAUDE.md: add CI/CD & Releases section with workflow table, tag
  patterns, Docker variants, beta/desktop release commands
- CONTRIBUTING.md: expand Releases section with standard (auto),
  beta (manual tag), and desktop release workflows
- docs/17-changelog.md: add v2.66.0 entry covering IDOR fix, BytePlus
  provider, per-agent grants, beta pipeline, and CI fixes

* fix(telegram): handle group-to-supergroup migration seamlessly

When a Telegram group upgrades to a supergroup, the chat ID changes and
all existing references become stale. This caused send failures (400),
orphaned sessions, and required manual re-pairing.

Add dual-path migration handling:
- Proactive: intercept inbound MigrateToChatID before isServiceMessage
- Reactive: detect 400 + MigrateToChatID on send, migrate DB, retry

DB migration updates in a single transaction (scoped by tenant + channel):
- paired_devices: sender_id, chat_id
- sessions: session_key, user_id
- channel_contacts: sender_id
- channel_pending_messages: history_key

Also invalidates in-memory caches (approvedGroups, pairingReplySent,
groupHistory) and handles media sends via migration retry in Send().
2026-04-05 13:13:02 +07:00
viettranx 473679d28d fix(telegram): handle group-to-supergroup migration seamlessly
When a Telegram group upgrades to a supergroup, the chat ID changes and
all existing references become stale. This caused send failures (400),
orphaned sessions, and required manual re-pairing.

Add dual-path migration handling:
- Proactive: intercept inbound MigrateToChatID before isServiceMessage
- Reactive: detect 400 + MigrateToChatID on send, migrate DB, retry

DB migration updates in a single transaction (scoped by tenant + channel):
- paired_devices: sender_id, chat_id
- sessions: session_key, user_id
- channel_contacts: sender_id
- channel_pending_messages: history_key

Also invalidates in-memory caches (approvedGroups, pairingReplySent,
groupHistory) and handles media sends via migration retry in Send().
2026-04-05 13:12:33 +07:00
viettranx 1dd5635f2b docs: document CI/CD pipelines, release flow, and v2.66.0 changelog
- CLAUDE.md: add CI/CD & Releases section with workflow table, tag
  patterns, Docker variants, beta/desktop release commands
- CONTRIBUTING.md: expand Releases section with standard (auto),
  beta (manual tag), and desktop release workflows
- docs/17-changelog.md: add v2.66.0 entry covering IDOR fix, BytePlus
  provider, per-agent grants, beta pipeline, and CI fixes
2026-04-05 11:22:30 +07:00
viettranx 56155ce53e Merge remote-tracking branch 'origin/main' into dev 2026-04-05 11:18:17 +07:00
viettranx c083622f9b fix(ci): skip CI condition in semantic-release for main branch
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.
2026-04-05 11:12:32 +07:00
viettranx af3a5e0297 fix(ci): skip CI condition in semantic-release for main branch
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.
2026-04-05 11:12:05 +07:00
Viet Tran f663fd05fa Merge pull request #696 from nextlevelbuilder/dev
release: merge dev into main
2026-04-05 11:03:22 +07:00
viettranx 52cb4fc3c6 fix(ci): prevent scheduler test hang + add beta release workflow
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)
2026-04-05 11:00:03 +07:00
viettranx 0c320be7b1 merge: resolve main into dev (favor dev for conflicts)
Conflicts in Dockerfile (pnpm install flags) and secure_cli.go
(duplicated aliased column constant) resolved keeping dev versions.
2026-04-05 09:28:28 +07:00
viettranx 8a042ed201 ci: remove redundant docker-publish workflow
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.
2026-04-05 09:26:22 +07:00
Duc Nguyen 3039ce93e6 fix(scheduler): drain queued runs in DropOldPolicy test to prevent CI hang (#678)
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
2026-04-05 09:00:30 +07:00
Kai (Tam Nhu) Tran 29afd18e67 fix(ui): prevent first message disappearing in new chat session (#695)
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
2026-04-05 08:50:38 +07:00
viettranx b65b23b212 fix(pool): strip stale member names from settings on save
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.
2026-04-05 08:47:10 +07:00
Kai (Tam Nhu) Tran e9733e08c4 fix(pool): improve pool management UX — clickable affordance, stale ref handling, managed-by banner (#671)
* 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>
2026-04-05 08:43:24 +07:00
viettranx 8aa351b02a fix(ui): refresh providers on dialog open and unblock agent creation
- 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
2026-04-05 08:29:57 +07:00
viettranx 489572ab63 fix(gateway): add session ownership check to chat.send
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.
2026-04-05 07:29:01 +07:00
Duc Nguyen e85545dc1b fix(gateway): add session ownership checks to chat.* WS methods (#676)
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).
2026-04-05 07:25:55 +07:00
Kai (Tam Nhu) Tran 3d9c71a8a2 fix(ui): restore tenant selection state from localStorage on reload (#693)
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
2026-04-05 07:19:11 +07:00
viettranx aa5158d4a2 feat(providers): add BytePlus ModelArk provider with Seedream/Seedance media gen
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
2026-04-04 23:07:42 +07:00
viettranx 7a266aee36 fix(openai): Together-compatible requests with reasoning/stream/vision gating
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
2026-04-04 21:17:47 +07:00
viettranx 156b2dd96c feat(secure-cli): per-agent grants with setting overrides
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
2026-04-04 13:18:57 +07:00
viettranx 29f06cc24b fix(desktop): replace MarkdownRenderer with plain pre in trace span preview
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.
2026-04-04 08:02:37 +07:00
viettranx 46b93a6b5f fix(desktop): guard slugify against undefined input
watch() can return undefined before form reset() runs, causing
'undefined is not an object (evaluating input.normalize)' crash.
2026-04-04 07:58:53 +07:00
viettranx 3dd3162c26 chore: add language matching instruction to CLAUDE.md, promote go.mod direct deps 2026-04-04 07:57:01 +07:00
viettranx 3466df726c fix(desktop): address code review findings — deduplicate slugify, restore moveFile guard, fix name collision
- 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
2026-04-04 07:54:25 +07:00
viettranx 6be88f4178 refactor(desktop): react-arch audit — schemas, RHF forms, file splits, services, store cleanup, dir restructure
- Phase 1: add zod, react-hook-form, @hookform/resolvers; create 8 Zod schemas
- Phase 2: migrate 6 form dialogs from useState to useForm+zodResolver
- Phase 3: split 24 files >200L into ~40 focused modules (0 files >300L remaining)
- Phase 4: create services/ layer (8 service files), eliminate direct ws.call in components
- Phase 5: split chat-store into chat-message-store + chat-activity-store
- Phase 6: flatten settings/ directory — move 9 feature subdirs to top-level components/
2026-04-04 07:47:02 +07:00
Tuan-TC 4d9c286aae fix(openai): normalize Mistral tool call IDs (#669)
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
2026-04-04 07:26:04 +07:00
Duc Nguyen 2b1180f59d fix(http): allow localhost URLs for local provider types (Ollama, Claude CLI) (#674)
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
2026-04-04 07:06:34 +07:00
Songlin Yang 5e680a7f25 fix(188): didn't call Stop when tsnet Server Listen failed (#226) 2026-04-04 06:58:15 +07:00
viettranx bed309a364 fix(ui,kg): fix graph canvas sizing on agent selection
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).
2026-04-03 23:42:43 +07:00
viettranx 4b6538839f refactor(ui): gap closure Phase 2 — extract memory-documents-table
Split memory-page.tsx (314 → 244) by extracting the documents table
into memory-documents-table.tsx.

Remaining 14 files >300 lines assessed — no clean extraction
boundaries found (dense forms, tab routers, DnD contexts).
2026-04-03 23:32:16 +07:00
viettranx f6bca7003a refactor(ui): gap closure — extract form field groups from bloated RHF dialogs
Split 4 RHF form dialogs that grew after Phase 3 Zod refactoring:
- channel-instance-form-dialog: 445 → 292 (+ form-step 196)
- cli-credential-form-dialog: 417 → 277 (+ binary-fields 151, scope-fields 72)
- mcp-form-dialog: 359 → 209 (+ connection-fields 100, settings-fields 95)
- agent-create-dialog: 351 → 165 (+ identity-model-fields 166, description 90)

Skipped: heartbeat-config (already decomposed), provider-form (no 50+ line boundary).

Files >300 lines: 20 → 16. Build passes.
2026-04-03 23:26:26 +07:00
viettranx e962a8ec7a refactor(ui): Phase 5 — Zustand persist middleware + constants split
Stores:
- use-auth-store: manual localStorage → persist() with partialize
  (token, userId, senderID)
- use-ui-store: manual localStorage → persist() with partialize
  (theme, language, timezone, sidebarCollapsed)
- use-team-event-store: manual persist helpers → persist() with
  partialize (last 20 events), onRehydrateStorage seeds counter

Constants:
- Split lib/constants.ts (144 lines) into:
  - lib/routes.ts (46 lines) — route paths
  - lib/timezone-utils.ts (74 lines) — IANA timezone data
  - lib/constants.ts (30 lines) — barrel re-export

ForwardRef: skipped — all UI primitives already use React 19
ref-as-prop pattern.

No behavior changes. Build passes.
2026-04-03 23:08:34 +07:00
viettranx 2ed810b854 refactor(ui): Phase 4 complete — provider-pool adapter eliminates DRY violation
Create adapters/provider-pool.adapter.ts with 6 pure functions that
replace duplicated CodexPoolEntry construction in 3 files:
- provider-overview.tsx (inline poolEntries useMemo)
- agent-codex-pool-page.tsx (35-line buildEntries callback)
- provider-pool-activity-section.tsx (30-line entries useMemo)

Also centralizes resolveProviderAvailability (was copied in 3 places).
2026-04-03 22:59:25 +07:00
viettranx 1313f02cf4 refactor(ui): Phase 4 — adapter layer for data transformations
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.
2026-04-03 22:54:40 +07:00
viettranx 7e62623123 refactor(ui): Phase 3 complete — 5 more forms to RHF+Zod (11 total)
Add 5 more Zod schemas: mcp, mcp-credentials, memory,
cron-advanced, team-settings.

Refactor 5 more form dialogs:
- mcp-form-dialog: 15 → 4 useState
- mcp-user-credentials-dialog: 11 → 7 useState
- memory-create-dialog: 10 → 2 useState
- cron-advanced-dialog: 10 → 2 useState
- team-settings-tab: 22 → 1 useState

agent-advanced-dialog skipped (complex typed objects, no field
validation needed — RHF adds indirection with zero benefit).

Total Phase 3: 11 schemas, 11 forms refactored, ~120 useState removed.
2026-04-03 22:50:04 +07:00
viettranx 7a6a944622 refactor(ui): Phase 3 — Zod schemas + React Hook Form for 6 form dialogs
Add zod@4.3.6, react-hook-form@7.72.1, @hookform/resolvers@5.2.2.

Create src/schemas/ with 6 validation schemas:
- agent.schema.ts, provider.schema.ts, cron.schema.ts
- channel.schema.ts, credential.schema.ts, heartbeat.schema.ts

Refactor 6 form dialogs from manual useState chains to RHF + Zod:
- agent-create-dialog: 12 → 2 useState
- provider-form-dialog: 8 → 0 useState
- cron-form-dialog: 7 → 0 useState
- channel-instance-form-dialog: 11 → 6 useState
- cli-credential-form-dialog: 18 → 9 useState
- heartbeat-config-dialog: 19 → 4 useState

Total: 75 useState removed, replaced with type-safe form state.
Async side-effects kept as hooks. No behavior changes.
2026-04-03 22:35:06 +07:00