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
When share_memory is enabled, memory queries now remove the user_id
filter entirely instead of filtering by user_id IS NULL. This matches
the existing KG shared behavior and allows all users to see all memory
documents (global + per-user from everyone).
sanitizeHistory dedup rewrites ToolCalls IDs but not RawAssistantContent.
Anthropic provider prefers RawAssistantContent → sends stale IDs → HTTP 400.
Fix: clear RawAssistantContent when dedup fires. Also merge consecutive
same-role messages (user↔user, assistant↔assistant) to fix role alternation
errors from session corruption.
- Add expires_at check to ApprovePairing SELECT (PG + SQLite) to close
race between prune DELETE and code lookup
- Add isValidSenderID regex validation to handleRequest, handleRevoke,
and handleBrowserPairingStatus (prevents log injection / bus poisoning)
- Add slog.Warn on decrypt fallback paths for downgrade detection
- Remove Slack compound senderID|displayName format; all channels now
pass plain senderID with displayName in metadata
Memory files (MEMORY.md, memory/*.md) are stored in PostgreSQL, not on
disk. When the model uses exec to run cat/ls/grep/find on memory paths,
short-circuit with a hint to use memory_search or memory_get instead.
Also append [Source: database, not filesystem] to read_file and
list_files results served from the memory interceptor.
Master tenant's base dir is the global data dir, which contains
the tenants/ cross-tenant isolation root. Without this fix, the
Storage page exposes tenant slugs, file names, and sizes.
- Extract topLevelPath() helper, remove dead code
- Add isHiddenPath() scoped to master tenant only
- Guard handleList, handleRead, handleSize with hidden path checks
- handleSize uses filepath.SkipDir to skip entire tenant subtree
- Add regression tests for list, read, size, subpath, and
non-master tenant isolation
Sandbox: add noexec/nosuid/nodev to tmpfs mounts, remove SETUID/SETGID/CHOWN
caps, add PidsLimit 256 default, keep no-new-privileges from base.
Auth: reject X-GoClaw-User-Id header spoofing in dev mode (no gateway token),
use full 32-byte HMAC for file tokens instead of truncated 16-byte.
Shell: add NFKC Unicode normalization + zero-width character stripping before
deny pattern matching, add 5 export-prefixed env var deny patterns, fix
exemption logic to check per-argument prefix instead of whole-command substring
(prevents bypass via comments while preserving skill store access).
Heavy MCP servers (FastMCP with 80+ tools, OAuth servers) take 3-5s to
start their stdin read loop. Without retries, connectAndDiscover sends
the initialize request immediately after fork/exec, gets EOF, and
permanently fails with "transport closed".
Add a retry loop (4 attempts, 2s/4s/8s backoff) for stdio transports
only. SSE/HTTP transports fail immediately since connection-refused is
definitive. The retry respects context cancellation to avoid blocking
shutdown.
Closes#385
* 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(web): allow editing Brave Search API key when masked
---------
Co-authored-by: Viet Tran <viettranx@gmail.com>
Prompt compression (Issue #613):
- Truncate skill descriptions to 200 runes in inline XML (matches mcpToolDescMaxLen)
- Lower skill inline token threshold from 5000 to 3000
- Compact tool descriptions (risk-audited: preserve behavioral hints)
- Compact boilerplate sections: media hint, safety, tool call style, spawn,
self-evolve, skill creation, team workspace
- Fix token estimator to account for description truncation
- Restore safety anti-manipulation clauses dropped during compaction
Team context injection fix (found during prod audit):
- Add IsTeamLead field to LoopConfig/Loop, plumbed from resolver
- Gate team context (TEAM.md, workspace, members) on session type:
leader inbound → team context; member-only inbound → spawn section;
team dispatch → team context
- Filter TEAM.md from context files for member-only inbound chat
- Skip team member DB query when team context not needed
- Rename HasTeam → IsTeamContext for semantic clarity
- Add 8 table-driven tests for team context injection scenarios
Widen settings dialogs from sm:max-w-2xl (672px) to sm:max-w-3xl (768px)
to prevent cramped toggle groups. Move boolean field help text to a
separate line below the switch+label for cleaner readability.
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
Both dialogs now show an amber callout explaining that chat users
(Telegram, Discord, etc.) must be merged into a tenant user via
Contacts page before they can have per-user credentials.
i18n: en/vi/zh for both cli-credentials and mcp namespaces.
Replace raw Combobox + useUserPicker with UserPickerCombobox
component (source="tenant_user") to match MCP user credentials
dialog. Both systems use the same runtime user ID from context.
Use existing useUserPicker hook + Combobox component to search
contacts + tenant_users instead of manual UUID entry.
Supports allowCustom for IDs not in the search results.
Backend:
- Add POST /v1/cli-credentials/check-binary to resolve binary via exec.LookPath
- Security: safeBinaryNameRe regex prevents filesystem probing
- Fix ambiguous column in LookupByBinary LEFT JOIN (secureCLISelectColsAliased)
- Add diagnostic logging to lookupCredentialedBinary
Frontend:
- Replace agent ID text input with Select dropdown (reuse useAgents hook)
- Add Check Binary button next to binary name (auto-fills resolved path)
- Add binary path hint explaining auto-detection from PATH
- Update i18n keys (en/vi/zh) for new UI elements
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.
- Set dev as default branch, protect main (owner-only merge)
- Add CI trigger for PRs targeting dev
- Add PR template with checklist and branch targeting guide
- Add CONTRIBUTING.md with branch strategy and review criteria
- Update README clone command to use -b main for stable