- Fix workspace dir ownership in Docker entrypoint: chown dirs not owned
by goclaw on startup (handles dirs created by root in previous lifecycle)
- Add symlink check on .uploads/ via os.Lstat before file creation to
prevent symlink-based attacks replacing .uploads with link to sensitive dir
Summoning failures (e.g. provider timeout) left agents stuck in summoning because failure paths called setAgentStatus(context.Background(), ...) without tenant scope, causing agent not found on update. Pass tenantID to setAgentStatus and re-attach tenant context when missing.
- Add Tool Call Style section with narration minimalism + non-disclosure
rule (from TS reference): agents must never expose tool names to users
- Consolidate 3 redundant memory recall reminders into 1 dedicated section
- Remove "tell the user you checked but found nothing" instruction that
caused agents to describe internal tool mechanics in responses
- Remove 11 tool aliases from system prompt listing (~300 tokens saved);
aliases still work via provider definitions
- Filter alias tool names out of system prompt ToolNames in loop_history
- Update AGENTS.md: remove tool name references from Memory section,
add group chat framing from V1 ("participant, not their proxy")
- Fix race condition where session-change effect cleared runIdRef after
run.started already captured it, causing chunk events to be filtered
out (user saw "thinking" but no streamed tokens on new chats)
- Add SessionRunID to router + return runId in session status response
as backup restoration for event filtering
- Require explicit agent selection before chat input is shown
- Redesign ChatInput: attach icon inside input container, aligned send
- Port desktop UX: wobble animation for tool calls, auto-expand thinking
block on stream start, amber icon for streaming, iteration step count
- Add internal/webui/ package with //go:build embedui tag for optional
SPA embedding (handler.go serves static files with SPA fallback)
- Add internal/version/ shared semver comparison (DRY: extracted from
gateway/update_check.go and updater/updater.go)
- Enhance UpdateChecker: release notes, ETag caching, filter lite-v* tags
- Add web UI build stage to Dockerfile with ENABLE_EMBEDUI build arg
- Simplify CI: 7 Docker variants → 4 (base, latest, full, otel)
- Add SHA256 checksums job to release workflow
- Add Makefile build-full target (embeds web UI in Go binary)
- Default make up now embeds web UI (no separate nginx needed)
- Add WITH_WEB_NGINX=1 flag for optional nginx reverse proxy
- Update README + 30 translated READMEs: make up, port 18790
- Update docker-compose comments and prepare-env.sh
- About dialog: show release notes with markdown rendering
- Health card: amber badge for available updates
BREAKING: Default Docker setup no longer requires selfservice overlay.
Web dashboard served at :18790 (same port as API).
- Remove auto-add logic that granted file_writer permission to the first
group/guild member who chatted with the bot
- Add ConfigTypeFileWriter and ConfigTypeHeartbeat constants, replace all
hardcoded config_type strings across callers
- Add bootstrap exception: /addwriter and !addwriter allow first writer
to be added when no writers exist yet
- Optimize writer commands: reuse cached ListFileWriters result for both
permission check and last-writer guard, reducing DB queries per command
- Add freshness directive to file writer system prompt so bot prioritizes
current list over stale references in conversation history
Prevent SSRF bypass via provider_type change on update — ACP skips URL
validation, so changing type post-creation could circumvent the check.
Add table-driven unit tests for argString() covering all JSON type
coercion paths (float64, int, NaN, json.Number, nil).
* fix(providers): skip URL validation for ACP provider api_base
ACP uses api_base to store agent command/binary, so enforcing http/https URL validation rejects valid ACP configs. Bypass provider URL checks for ACP while keeping URL validation for HTTP-based providers.
* fix(message): enhance target handling in message tool to support numeric chat IDs
- Introduced argString function to correctly parse numeric chat IDs from tool arguments, ensuring they are treated as strings when necessary.
- Added a new test case to verify that numeric targets are correctly processed and sent via the Telegram channel.
Replace hardcoded 20-entry IANA_TIMEZONES with getAllIanaTimezones()
using Intl.supportedValuesOf (~400 zones). Switch Select dropdowns
to searchable Combobox in cron, heartbeat, and system config.
Add defense-in-depth timezone validation:
- Backend: validate in heartbeat.set handler and SetDefaultTimezone()
- Frontend: isValidIanaTimezone() guard before save in all 3 dialogs
Closes#614
- Add "low confidence" instruction to memory_search tool description
to prevent models from fabricating memories when no results found
- Add dedicated ## Memory Recall section in system prompt (supplements
recency reminder) with clear instructions for memory_search/memory_get
- Update flush prompts: replace YYYY-MM-DD with actual date at runtime,
cleaner append-only wording
- Update AGENTS.md memory privacy section for multi-tenant: remove
implementation details (per-user scoping), keep group chat output
guardrails that work for both shared and isolated memory configs
- RegenerateDialog now handles its own progress via WS events instead of
opening SummoningModal (inline spinner + auto-close on completion)
- Clean up SummoningModal: remove mode/isRegenerate prop, summon-only
- Agent create: default to Predefined, collapse Open type behind toggle
with warning banner explaining per-user context trade-off
- Add 4 new agent presets: Coder, Support, Writer, Translator (en/vi/zh)
- Remove 15 dead summoning.regenerate* i18n keys
- Add providerTypeOf() to extract provider_type via type assertion
(e.g. "chatgpt_oauth") instead of config name (user-set "openai")
- Exclude "compat" providers (openai_compat → OpenRouter/DeepSeek/Groq)
from strict mode and SOUL echo — they proxy to non-OpenAI models
- Fix isOpenAIStrict matching openai_compat incorrectly
GPT models have strong recency bias and lose persona in long prompts.
Extract Style/Vibe sections from SOUL.md and echo them at the end of
the system prompt (~200 chars each) so GPT sees personality traits right
before generating. Only applies to OpenAI/Codex — Claude respects early
system prompt instructions well and doesn't need this.
Single transient errors (e.g. 504 from upstream proxy) no longer
instantly disconnect MCP servers. Requires healthFailThreshold (3)
consecutive failures before setting connected=false and triggering
reconnect. Applies to both Manager.healthLoop and poolHealthLoop.
Prevent invalid UTF-8 from being persisted when auto-setting followups by sanitizing message content and truncating by rune count. Add regression tests for emoji truncation and malformed byte sequences.
Instead of all-or-nothing when MCP tool count exceeds threshold,
keep first 40 tools registered inline and only defer the excess
to BM25 search via mcp_tool_search. System prompt now shows both
inline descriptions and search guidance in hybrid mode.
Also raises skill inline count from 40 to 60 (token limit is the
real bottleneck for skills).
- Add mode prop to SummoningModal (summon vs regenerate) so Edit with AI
shows appropriate text instead of summoning language
- Fix memory document and KG entity detail dialogs using sm:max-w-* to
properly override base sm:max-w-lg from DialogContent
- Expose isFetching from useMCP hook so refresh button animation works
Combine forward+reverse traversal into single recursive branch using
CASE to fix SQLSTATE 42P19 (PostgreSQL parses triple UNION ALL as
left-associative, putting recursive ref in non-recursive term).
Also widen entity detail dialog from max-w-5xl to max-w-7xl.
Replace replaceLastMediaTag with replaceFirstMediaTag across all 5 media
enrichment functions. Forward iteration + first-match produces natural
positional pairing, fixing reversed tag alignment when multiple media
refs exist in one message.
Also fixes same latent bug in enrichDocumentPaths, enrichAudioIDs, and
enrichVideoIDs. Supersedes #608.
Zero temperature was too rigid, causing LLM to miss implied entities
and relations. 0.2 allows picking up contextual connections while
staying deterministic for structured JSON output.
Add 3 new entity types: technology, product, document — reducing
concept catch-all bucket. Add 4 new relation types: authored,
references, provides, requires. Improve prompt with disambiguation
guide between similar types, stricter related_to usage, and varied
confidence examples. Update graph view colors and mass for new types.
Add reverse-edge UNION ALL to recursive CTE so traversal follows both
source→target and target→source edges. Reverse edges prefixed with ~
in via field (e.g. ~manages). Tool output shows directional arrows.
* fix(discord): preserve attachment source URL in media tags (#602)
- Add SourceURL field to MediaInfo struct
- Populate SourceURL from Discord attachment URL in resolveMedia()
- Emit <media:image url="..."> in BuildMediaTags() when SourceURL is set
- Refactor enrichImageIDs/enrichImagePaths with helper functions
- Add comprehensive unit tests for media URL handling
* fix: update system prompt for url attribute + add enrichment regression tests
- System prompt now documents the url attribute in <media:image> tags
- Add TestEnrichImageIDs_BareTag for non-Discord channel enrichment
- Add TestEnrichImageIDs_SkipsAlreadyEnriched for double-enrichment safety
- Add TestEnrichImagePaths_NoDoubleEnrich for historical message safety
- Add TestEnrichImagePaths_AttributeOrderIndependence for url-before-id tags
---------
Co-authored-by: viettranx <viettranx@gmail.com>
grok-imagine-video and grok-2-image were not in the isNonChatModel()
allowlist, causing verify to call Chat API which fails with malformed
error. Also fix friendlyVerifyError() fallback splitting inside JSON
values via LastIndex.
Three layered bugs caused OpenAI-compatible providers to silently
produce empty tool call arguments when max_tokens was hit mid-JSON:
1. FinishReason override: all providers unconditionally overwrote
"length" → "tool_calls" when tool calls existed, preventing the
agent loop's truncation guard from firing.
2. Silent parse failure: JSON unmarshal errors were logged but args
stayed as empty map with no signal to the caller.
3. No fallback for unreliable providers: some proxies don't emit
finish_reason:"length" at all, leaving no detection path.
Fixes:
- Add ParseError field to ToolCall struct for explicit error signal
- Guard FinishReason override with `!= "length"` in OpenAI, Codex
- Set ParseError in all provider parsers (OpenAI, Anthropic, Codex)
- Add hasParseErrors() fallback guard in agent loop for EC-5 scenario
- Cap consecutive truncation retries (maxTruncationRetries=3) to
prevent burning all iterations when max_tokens is persistently low
Closes#605
- Use senderID (id|username) instead of userID in group no-mention path,
preventing duplicate contacts for the same Telegram user
- Use full name (FirstName + LastName) in both contact insert paths
- Show contact_type (User/Group) instead of peer_kind (Direct/Group) in
contacts table TYPE column and filter dropdown
- Add contact_type filter support in HTTP handler, PG and SQLite stores
- Revert leader workspace override: leader keeps personal workspace as
default, team workspace accessible via ToolTeamWorkspaceFromCtx
- Auto-copy: when leader creates team_tasks, scan subject+description
for file paths → copy from personal to team workspace so members can
access them
- Safety: Lstat (reject symlinks), 10MB size cap, .env excluded from
allowed extensions, path traversal blocked
- Prompt hint: clarify members can only access team workspace files,
referenced files are auto-copied
- New /subagents and /subagent <id> commands for viewing subagent tasks
from the persistent DB table
- Inline keyboard with sa: callback prefix for detail view
- Refactor telegram.New() to functional options pattern (WithAgentStore,
WithTeamStore, WithSubagentTaskStore, WithPendingMessageStore)
- Wire SubagentTaskStore via WithSubagentTaskStore option
- Token cost tracking: accumulate input/output tokens per subagent,
include in announce messages and persist to DB
- Per-edition rate limits: MaxSubagentConcurrent/Depth on Edition struct,
tenant-scoped concurrency enforcement in Spawn/RunSync
- WaitAll action: spawn(action=wait, timeout=N) blocks until all
children complete, returns merged summary
- Auto-retry: configurable MaxRetries (default 2) with linear backoff
for transient LLM failures
- Producer-consumer announce queue: merges staggered subagent results
into single LLM run (same pattern as team task announces)
- Raw metadata in bus messages to prevent double-formatting
- Fire-and-forget DB persistence with detached context + tenant scope
- Split oversized files for <200 line compliance
- Migration 000034: subagent_tasks table with tenant scope, JSONB
metadata + GIN index, partial index for archival candidates
- SubagentTaskStore interface with Create/Get/UpdateStatus/List/Archive
- PG implementation with parameterized queries and tenant isolation
- SQLite schema v3→4 migration + no-op stub for Lite edition
- Wire into store.Stores and factories
Compaction summaries were too generic ("provide a concise summary"),
causing loss of task progress, decisions, and identifier corruption
after summarization.
Port from OpenClaw TS (compaction.ts):
- Structured MUST PRESERVE sections: active tasks, progress, last
request, decisions, TODOs, commitments
- Identifier preservation: preserve UUIDs, hashes, URLs, file names
exactly as written (no shortening/reconstruction)
- Prioritize recent context over older history
- Shared prompt constant used by both mid-loop and background
compaction paths
Problem: Agent sessions accumulated 71K+ input tokens (83% history)
because read_file and exec had no output limits. SOUL personality
drowned by massive context.
Changes:
- read_file: add offset/limit params + 50K char output cap with
pagination hints (model can re-read with offset)
- exec/shell: cap output at 30K chars with smart head+tail truncation
(preserves errors/summaries at tail)
- pruning: add per-result 30% context guard, tune softTrimRatio
0.3→0.25 and softTrimMaxChars 4K→3K, add tail-aware soft trim
- mid-loop: allow pruning to re-trigger each iteration (was one-shot)
Design: cap at source, preserve full data in session, prune at
consumption time. read_file offset/limit enables recovery of
truncated content.
Closes#532
- Replace prefix truncation with SHA-256 hash-based shortening for oversized tool call IDs (40-char OpenAI/Azure limit)
- Normalize provider-prefixed model IDs (e.g. openai/o3-mini) before capability checks for temperature and max_completion_tokens
- Add regression tests for ID collision, correlation, and prefixed model routing
* feat(reasoning): add capability-aware effort resolution
- resolve requested reasoning levels against exact model capabilities
- persist requested effort on agents and expose effective effort in traces
- add backend tests for provider models, agent store, and resolution logic
Refs #591
* feat(ui): gate reasoning controls by model capabilities
- only show supported reasoning levels when provider model metadata is available
- preserve expert reasoning selections during async model loading
- surface effective reasoning details in trace dialogs and localized copy
Refs #591
* docs(api): document capability-aware reasoning controls
- describe exact-match capability lookup and downgrade behavior
- update provider model metadata and trace response documentation
- refresh the generated OpenAPI spec for the new reasoning fields
Refs #591
* feat: add provider-first reasoning controls
* docs: refresh PR 593 UI evidence callouts
* refactor: deduplicate reasoning normalize functions and remove PR evidence
- Export NormalizeReasoningEffort/NormalizeReasoningFallback from providers
package; store package now delegates instead of duplicating
- Store reasoning fallback constants alias providers canonical definitions
- Export deriveLegacyThinkingLevel from types/provider.ts; remove local
copies from agent-advanced-dialog and provider-overview
- Remove unused _providerType param from useProviderModels hook
- Fix reasoning debug log to fire for all cases with a reason (not just
non-off efforts)
- Remove docs/pr-593-evidence/ binary screenshots from repo
---------
Co-authored-by: viettranx <viettranx@gmail.com>
Port missing group chat style guidance from OpenClaw TS:
- "Write like a human" — prevents robotic/formal GPT responses
- "Avoid Markdown tables" — GPT tends to spam tables in groups
- "Use real line breaks sparingly"
These instructions exist in OpenClaw TS (groups.ts buildGroupIntro)
but were missing in GoClaw's group prompt.
GPT-4o+ models prioritize "developer" messages over "system" for
instruction adherence. GoClaw was sending "system" for all providers,
causing GPT models to poorly follow SOUL/system prompts.
Map "system" → "developer" only for native OpenAI endpoints
(api.openai.com). Non-OpenAI backends keep "system" role unchanged.
Ported from OpenClaw TS: model-compat.ts → isOpenAINativeEndpoint()
Session tools (sessions_list, session_status, sessions_history, sessions_send)
were using resolveAgentIDString(ctx) which returns the agent UUID, but session
keys are built using agent_key. This caused all session tool operations to fail
silently or return "access denied" for every channel.
Replace resolveAgentIDString() with ToolAgentKeyFromCtx() in all four session
tools. Add fail-closed guard for empty agent key.
* fix(ui): restore provider-owned codex pool inherit state
* docs(pr): add before-after UI evidence
* refactor: remove dead hasProviderDefaults param and harden pool rendering
- Remove unused _hasProviderDefaults parameter from buildDraftRouting
and routingDraftSignature, clean up all call sites and useMemo deps
- Filter deleted providers from selectedPoolProviderNames to prevent
ghost entries when a saved pool member no longer exists
- Add symmetric backend test for inherit + non-nil provider defaults
---------
Co-authored-by: viettranx <viettranx@gmail.com>
Upload handler previously archived skills immediately when deps were
missing. Now calls InstallDeps() first (owner/master tenant only) and
falls back to archive on failure.
Changes:
- Auto-install missing deps during skill upload (same flow as seeder)
- Atomic DB persist: deps state written with CreateSkillManaged in one call
- Per-slug upload mutex prevents concurrent race conditions
- Frontend: warning state (amber triangle) instead of throwing error
- Non-cancellable context for DB write after dep install
- SQLite StoreMissingDeps now works for custom skills (not just system)
- Comprehensive unit + integration tests
Closes#468