Files
goclaw/docs/project-changelog.md
ナムandCollective Developer fc7d183961 Enhance passive memory extraction tuning (#1393)
* feat(channelmemory): add passive extraction tuning

* feat(ui): expose passive memory tuning controls

* docs(memory): document passive extraction tuning

---------

Co-authored-by: Collective Developer <man@collective.dev>
2026-07-08 20:53:19 +07:00

1612 lines
73 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Project Changelog
Significant changes, features, and fixes in reverse chronological order.
---
## 2026-07-08
### Passive memory extraction tuning
**Features**
- Added tenant-global and per-channel custom prompt append points for passive
memory extraction without replacing the built-in privacy and JSON guards.
- Moved tenant-global passive extraction prompt tuning to `/config` and added
per-Discord-group extraction prompts keyed by history key.
- Enriched Discord passive extraction context with channel/thread/parent/category
metadata and stable display/handle/user-ID author labels.
- Web review queue now surfaces candidate `topics` and `entities` for tuning.
**Docs**
- Updated channel, memory, and HTTP API docs for the new prompt fields and
Discord extraction context behavior.
---
## 2026-07-07
### Passive channel memory extraction reliability
**Fixes**
- Fixed partial settings updates so enabling passive memory no longer clears review mode or other omitted options.
- Extraction runs now checkpoint only messages actually sent within the extraction input budget, preventing skipped channel messages.
- Pending review counts now count the full queue, retention uses the configured `retention_hours`, duplicate approvals preserve the existing episodic ID, and failed item writes mark the run as failed.
- Passive memory candidates now dedupe across runs for the same channel instance and item hash in both PostgreSQL and SQLite.
**Tests**
- Added regression coverage for config patching, checkpoint bounds, pending counts, run failure finalization, retention, duplicate approval, and SQLite cross-run dedupe.
---
## 2026-07-03
### Usage cost display precision
**Fixes**
- Usage and overview cost values now render with two decimal places for API cost
displays, matching dashboard expectations such as `$1.02` and `$5.93`.
**Tests**
- Added web formatter regression coverage for two-decimal API cost rendering.
### Usage analytics historical cost audit
**Fixes**
- Backfilled zero-cost historical `usage_snapshots` rows from the synced pricing
catalog, fixing 7-day and 30-day totals that previously showed the same cost
despite different token counts.
- Usage analytics span aggregation now includes model-using `tool_call` spans in
token and cost totals while keeping direct LLM call counts scoped to
`llm_call` spans.
- Added trace aggregate reconciliation so overview daily token/cost cards match
the same `llm_call` + `tool_call` usage scope used by usage analytics.
**Tests**
- Added PostgreSQL and SQLite regressions for cost backfill and trace aggregate
reconciliation with tool-call model usage.
### Live usage cost refresh
**Fixes**
- Usage summary and breakdown endpoints now include current-hour live trace/span
data, so dashboard cost cards and top model costs update immediately after a
completed LLM call instead of waiting for the hourly snapshot worker.
**Tests**
- Added HTTP regression coverage for current-hour summary cost and provider/model
breakdown cost when no usage snapshot exists yet.
### Usage cost backfill after pricing sync
**Fixes**
- Added startup backfill for historical `llm_call` spans with zero cost after
OpenRouter catalog sync succeeds.
- Backfill now refreshes trace totals and affected usage snapshot buckets so
dashboard cost numbers update for existing data.
- Added catalog aliases for Bailian/DashScope Qwen models and common unprefixed
OpenAI-compatible GPT/o-series model IDs.
**Tests**
- Added PG regression coverage for catalog-backed trace cost backfill.
- Extended pricing resolver coverage for custom OpenAI-compatible and Bailian
model aliases.
---
## 2026-07-02
### Automatic model pricing for usage costs
**Fixes**
- Tracing cost now resolves tenant/provider/model pricing overrides first, then
the OpenRouter catalog, before falling back to legacy telemetry config.
- OpenRouter model pricing catalog now syncs automatically at gateway startup
and once per day, while preserving the manual dashboard sync action.
**Tests**
- Added regressions for catalog-backed trace cost precedence, config fallback,
and cached-token price calculation.
### Zalo Personal nested reply context
**Fixes**
- Preserved Zalo Personal `quote` payloads on inbound messages so agents receive
quoted-message context when users reply to a reply.
- Rendered quoted text and quoted attachment placeholders into DM and group
prompts before mention gating/history flush.
- Added a channel-agnostic bounded reply-context cache so Zalo Personal can
render multi-level reply chains without DB persistence or Zalo API backfill.
- Moved Zalo Personal policy checks before attachment download/media extraction
so rejected messages cannot trigger media fetches or reply-cache writes.
**Tests**
- Added Zalo Personal protocol and handler regressions for quoted text, quoted
attachments, and nested-style replies.
- Added reply-context cache coverage for scoped lookup, multi-level chains,
fallback, depth/size caps, cycle guard, TTL expiry, and clear-on-stop behavior.
- Added a Zalo Personal regression proving denied attachment messages do not
download media before policy rejection.
### Lark MCP skill guidance
**Added**
- Added bundled `lark-pm` and `lark-playbook` skills so GoClaw agents handle
Lark/Feishu PM operations through MCP only.
- Documented schema-first LarkBase writes, writable field checks, person-field
payload shape, per-record updates, and canonical user ID verification.
**Docs**
- Updated the core skills system list to include the new Lark MCP skills.
---
## 2026-07-01
### Usage event analytics schema gate
**Fixes**
- Bumped required PostgreSQL schema version to include the usage event cache and
thinking token migration required by Resource Event Analytics.
- Added a migration-version regression test so new SQL migrations cannot ship
without updating the binary schema gate.
**Tests**
- Added `internal/upgrade` coverage for required schema version parity.
### Paired DM routing after policy checks
**Fixes**
- Policy-checked direct messages now pass the shared channel safety gate for
Zalo OA, Zalo Personal, Bitrix24, and Slack when the sender is paired but not
listed in static `allow_from`.
- Direct-message callers without an explicit policy gate still use the default
`allow_from` safety net.
**Tests**
- Added regression coverage for paired Zalo-style and Slack DMs with an
allowlist mismatch, plus guard coverage for non-policy direct callers.
---
## 2026-06-27
### Feishu/Lark group pairing stability
**Fixes**
- Group pairing now only runs after the target bot is actually mentioned, so
other agents in the same Lark group stay silent and do not create pair codes.
- Feishu group messages with explicit mentions that do not match the current
bot are now dropped before media resolution, policy checks, pairing, or agent
routing, even when `require_mention` is disabled for the channel.
- Feishu WebSocket/webhook message events are now guarded by `header.app_id`
before dispatching to a channel instance, preventing app-routing mismatch from
reaching pairing logic.
- Feishu now re-checks pairing state immediately before sending a pair code,
preventing stale policy decisions from creating duplicate requests.
- Feishu pairing and bot identity logs now include channel/chat/tenant context so
multi-agent Lark groups can be diagnosed without inferring from log order.
**Tests**
- Added Feishu regressions for non-target mentions, target mentions, and unknown
bot identity fail-closed behavior.
- Added Feishu WebSocket regressions for app-id mismatch vs match handling.
- Added Feishu regression for non-target mentions when `require_mention` is
disabled.
- Added Feishu regression coverage for already-paired group mentions.
---
## 2026-06-13
### Cron NO_REPLY delivery suppression (issue #141)
**Fixes**
- Suppressed configured cron channel delivery when final agent output contains a
standalone `NO_REPLY` sentinel anywhere in the text, while keeping normal chat
silent-reply behavior unchanged.
- Added suppression logging with cron job/channel metadata for debugging.
**Tests**
- Added cron handler regression coverage for exact, prefix, suffix,
mid-sentence, decorative, and glued-token `NO_REPLY` outputs.
---
## 2026-06-12
### Cron scheduler shutdown drain (post-merge CI follow-up)
**Fixes**
- Made cron service shutdown wait for scheduler-launched job executions to
finish so in-flight status persistence cannot outlive `Stop()`.
**Tests**
- Added regression coverage proving `Stop()` waits for a blocked in-flight job
before returning.
### Bundled GoClaw gateway administration skill (issue #175)
**Changes**
- Added a bundled `goclaw` system skill for version-aware GoClaw CLI/runtime
discovery, read-only-first diagnostics, gateway administration workflows, and
troubleshooting playbooks.
- Documented safety guidance for credentials, destructive actions,
permissions, tenant scope, and trace redaction.
**Tests**
- Added bundled-skill regression coverage to keep the default core skill set
discoverable in fresh runtimes.
### Bailian Coding qwen3.7-plus catalog (issue #169)
**Changes**
- Added `qwen3.7-plus` / `Qwen 3.7 Plus` to the hardcoded Bailian
Coding provider model catalog.
- Documented the model's advertised Text Generation, Deep Thinking, and Visual
Understanding capabilities while keeping Bailian on the existing
OpenAI-compatible provider wrapper.
**Tests**
- Added provider model endpoint coverage to verify Bailian exposes
`qwen3.7-plus` without adding unsupported OpenAI reasoning metadata.
### Multi-attachment outbound delivery (issue #172)
**Changes**
- Extended `send_file` with `attachments[]` so agents can queue multiple
existing workspace files in one tool call while preserving order and captions.
- Added caption propagation from tool result media through agent media results
into outbound channel attachments.
- Added Telegram media-group delivery for compatible batches with 2-10 item
chunks, plus ordered fallback for singleton, voice, oversized-image, or mixed
incompatible cases.
- Added explicit channel batch capability metadata for Telegram, Discord, and
ordered fallback channels.
**Tests**
- Added batch `send_file` regressions for happy path, duplicate rejection, and
all-or-nothing delivered-state handling.
- Added Telegram media-group chunking tests for compatible grouping, max-size
chunking, and non-groupable fallback.
### Operator trace CLI (issue #158)
**Changes**
- Added first-class `goclaw traces` operator commands to the main binary:
`list`, `get`, `export`, `follow`, and `timeline`.
- Added remote client overrides with `--server` and `--token`; trace commands
support trace-scoped output selection via `--output` / `-o`.
- Shared the URL/token resolver with WebSocket/RPC-backed admin commands so
`sessions`, `cron`, and `pairing` can use the same remote gateway overrides.
- Kept trace commands as thin wrappers over existing HTTP endpoints so the
server/runtime and operator CLI share one binary without new API contracts.
**Tests**
- Added command/client regressions for gateway URL and token overrides, trace
query serialization, follow scope validation, and timeline run ID handling.
### Skill self-evolution metrics and upgrades (issue #161, issue #142 foundation)
**Features**
- Added per-skill self-evolution settings, usage metrics, suggestions, immutable
applied-version records, and activity logs.
- Added trusted runtime usage recording for `use_skill` and slash skill
activations without exposing a public usage-write endpoint.
- Added skill evolution HTTP, CLI, and Web UI surfaces for settings, metrics,
suggestions, approval/apply actions, and admin-visible activity.
- Added the shared foundation needed by self-improving skills: usage evidence,
reference-file patches, versioned apply, and approval/audit surfaces. The
consolidation learning extractor and auto user-scoped overlays remain out of
this v1 scope.
**Safety**
- Blocked direct system skill mutation in the suggestion apply path.
- Reused a shared target-path validator for skill companion/reference patches.
- Sanitized failure evidence, draft patches, actor IDs, and activity details
away from non-admin surfaces.
**Tests**
- Added focused skill path validator coverage and deep-link coverage for the new
Web UI evolution tab.
### Skill lifecycle API and CLI (issue #159)
**Changes**
- Added per-skill dependency scan/check/install endpoints and CLI commands.
- Added access read/update, agent/user grant aliases, and effective-access
inspection for operator workflows.
- Dependency status now reports system, pip, npm, and GitHub release deps in
structured output.
- Skill user grants are now tenant-scoped so shared system skills can be
granted to the same user ID in different tenants without conflicts.
**Tests**
- Added HTTP, CLI, PostgreSQL, and SQLite coverage for dependency lifecycle,
access mode changes, grants, effective access, grant-driven visibility, and
tenant-scoped user grants.
### Mid-flight request preservation (issue #137)
**Fixes**
- Busy-session `chat.send` follow-ups that arrive after a no-tool final answer
now force one more pipeline iteration instead of finalizing the earlier answer.
- The earlier final answer is preserved as assistant context and the follow-up is
appended after it, so the next model turn can answer both accepted requests.
**Tests**
- Added pipeline regressions for late injected follow-ups after a final answer.
---
## 2026-06-11
### Trace search and advanced filters (issue #152)
**Changes**
- Extended `GET /v1/traces` with keyword search, partial Trace ID search, date
ranges, token ranges, tool-call filters, and agent/channel label search.
- Added equivalent PostgreSQL and SQLite query-builder behavior with tenant
guards on joined channel/span search.
- Added Web Traces search/filter controls with active filter chips.
**Tests**
- Added HTTP contract coverage for advanced trace filter parsing and user scope.
- Added PostgreSQL and SQLite where-builder coverage for search, ranges, tool
filters, tenant predicates, and wildcard escaping.
- Added Web filter serialization and trace i18n key coverage.
### Secure CLI GitHub credential runtime diagnostics (issues #138, #151)
**Fixes**
- Git remote commands now fail closed before raw `git` auth prompts when no
typed host-scoped PAT/SSH credential is selected.
- Required SecureCLI env validation now applies to preset env vars such as
`GH_TOKEN`, so credentialed `gh` commands report missing credential config
instead of raw `gh auth login` guidance.
**Tests**
- Added regressions for `git push` without typed credentials and `gh` without
`GH_TOKEN`.
---
## 2026-06-09
### Behavior UX sidecar delivery overrides (issue #144)
**Changes**
- Removed user-facing Tool Status Messages from Behavior settings and disabled
deterministic tool-status channel text.
- Added sidecar-generated Quick Acknowledgement and Intermediate Replies with
optional provider/model, timeout, token, and char caps.
- Added Channel > Agent > Workspace delivery-behavior resolution. Agent
overrides use `other_config.delivery_behavior` without a schema migration.
- Kept legacy `block_reply` readable as a default for Intermediate Replies while
removing its separate Web UI controls.
- Kept Show Reasoning as a separate debug/testing feature.
**Tests**
- Added config resolution coverage for Channel > Agent > Workspace and Quick
Ack independent from Intermediate Replies.
- Added channel event coverage for sidecar quick ack, sidecar tool progress, and
retired tool-status messages.
- Added Web UI schema coverage for sidecar delivery override fields.
---
## 2026-06-01
### Telegram Show Reasoning delivery modes (issues #132, #133)
**Fixes**
- Added `reasoning_delivery=streaming_only|always_bubbles|off` with backward compatibility for legacy `reasoning_stream`.
- HTTP and WebSocket channel-instance writes normalize explicit `reasoning_delivery` over legacy `reasoning_stream`.
- `always_bubbles` forces provider streaming internally so reasoning can be delivered as bounded channel bubbles even when Telegram live streaming is disabled.
- Stream-path final `resp.Thinking` is now emitted when no thinking chunk arrived during the stream.
- Terminal channel events preserve interim delivery state until the consumer reads the final dedup snapshot.
**UI**
- Web and desktop channel settings now expose Show Reasoning as a mode selector instead of a streaming-only boolean.
**Tests**
- Added channel delivery, config resolution, and agent stream final-thinking coverage.
---
## 2026-05-31
### CI/CD: zuey release asset race fix
**Fixes**
- Made release artifact completion wait for zuey beta deploy before refreshing GitHub Release assets with `--clobber`.
- Prevents the VPS upgrade script from seeing `linux amd64 release asset not found` while another workflow job is replacing the same asset.
**Tests**
- Updated the dev beta workflow structure test to lock the deploy-before-completion dependency.
---
### CI/CD: fast zuey beta deploy (issue #88)
**Changes**
- Split the dev beta release workflow so zuey deploy starts after the linux amd64 prerelease asset is published, instead of waiting for Docker multi-arch builds and beta alias promotion.
- Added stale beta tag guards for zuey deploy and Docker beta alias promotion so older runs cannot roll back a newer beta.
- Kept linux arm64 binaries, refreshed checksums, multi-arch Docker images, and beta aliases as required post-deploy completion jobs.
**Tests**
- Added a workflow-structure test covering the fast deploy graph, artifact completion graph, and stale-tag guards.
---
### Provider fallback content-policy recovery
**Fixes**
- Classifies provider content-policy rejections such as DashScope `data_inspection_failed` so model fallback can continue to the next configured candidate instead of stopping on `unknown`.
- Prevents Telegram runs from aborting silently when one fallback provider rejects a long/private history but another configured fallback remains available.
**Tests**
- Added provider classifier and model fallback coverage for continuing past a content-policy fallback failure.
---
### Empty Codex OAuth truncation recovery
**Fixes**
- Detects successful LLM calls that still return `finish_reason=length` with no text/tool/image output, the observed failure mode when long Codex OAuth runs spend the remaining budget on reasoning.
- Runs emergency history compaction and retries instead of finalizing empty assistant content into repeated `"..."` replies.
- Preserves configured/model context windows so future provider context upgrades do not require code changes.
**Tests**
- Added ThinkStage coverage for empty length responses, retry compaction, usage accounting, and repeated failure handling.
---
### Agent Access git credential follow-up (issue #117)
**Fixes**
- Replaced separate Agent Grants and Agent Credentials row actions with one
Agent Access dialog containing Credential and Access policy tabs, preventing
overlapping agent-access modals.
- Git PAT credentials now inject GitHub-compatible Basic auth extraheaders
instead of Bearer headers.
- SSH private keys are now checked with OpenSSH at save time when `ssh-keygen`
is available, catching keys that would later fail with `error in libcrypto`.
**Security**
- Git PAT redaction now includes the raw token, the base64 Basic auth payload,
and the full injected header value.
---
### Tool-call announcements
**Fixes**
- Intermediate Replies now preserve natural assistant progress text as-is
instead of appending a deterministic tool-name sentence.
- Empty-content tool calls no longer emit synthetic server-generated
Intermediate Reply bubbles. This prevents repeated template messages and
keeps visible progress model-generated.
- Tool-call progress keeps the `tool_announcement` source when the model does
provide assistant text, so Quick acknowledgement suppression still treats it
as explicit progress.
- The full-mode system prompt now tells the LLM to write any short progress
sentence naturally in the user's language and to describe user-visible action
instead of internal tool names.
- Quick acknowledgement off still suppresses generic first acknowledgements,
but no longer suppresses explicit model-generated tool progress.
**Tests**
- Added pipeline coverage for empty-content tool calls so they do not emit
template `block.reply` messages.
- Added prompt coverage for natural, user-language progress guidance.
- Kept channel coverage for `tool_announcement` delivery when Quick
acknowledgement is off.
---
### Channel intermediate reply gating
**Fixes**
- Quick acknowledgement off now suppresses the first pre-tool `block.reply`
even when explicit `gateway.block_reply` is enabled, so the initial
acknowledgement does not leak through the Intermediate Replies path.
- Final reply dedup now uses channel-delivered interim state instead of raw
pipeline `block.reply` emit counts, avoiding false suppression when an
interim event was skipped.
**Tests**
- Added channel event coverage for quick acknowledgement disabled and
`quick_ack.mode = "off"` with explicit intermediate replies enabled.
---
### Agent-scoped git credentials (issue #117)
**New**
- Added agent-scoped Secure CLI credentials with PostgreSQL migration `000077`
and SQLite schema version `46`.
- Added HTTP APIs under
`/v1/cli-credentials/{id}/agent-credentials/{agentId}` for listing,
reading metadata, saving, and deleting agent credentials.
- Web CLI Credentials now exposes Agent Credentials as the primary git PAT/SSH
setup path, with User Credentials renamed to advanced personal overrides.
**Security**
- Runtime credential precedence is now user override, context credential, agent
credential, then binary env defaults.
- Git adapter audit logs include `credential_source` without logging raw
secrets or plaintext host scopes.
---
## 2026-05-29
### Passive channel memory extraction (issue #64)
**New**
- Added opt-in per-channel passive memory extraction from pending group buffers, with scheduled/manual runs, redaction before LLM extraction, review queue items, and deterministic channel memory source IDs.
- Added PostgreSQL migration `000076` and SQLite schema version `45` for `channel_memory_extraction_runs` and `channel_memory_extraction_items`.
- Added tenant-admin HTTP APIs and channel detail UI controls for settings, manual run, last run status, and approve/reject/delete review actions.
**Security**
- Feature is disabled by default and group-only in v1.
- Review mode is on by default; approved items write to `episodic_summaries` with `source_type='channel'` and then enter the existing KG consolidation event path.
- Delete removes the review item and associated episodic summary when present. Existing KG nodes created before deletion may require later dedup/cleanup; raw channel messages are not copied into the new extraction tables.
---
### Channel context admin surface (issue #66)
**New**
- Added channel context APIs under `/v1/channels/instances/{id}/contexts` for stored channel/group contexts, members, and effective MCP/Secure CLI capability matrix.
- Added tenant-scoped context grants and context credentials for MCP servers and Secure CLI binaries, with PostgreSQL migration `000075` and SQLite schema version `44`.
- Runtime tool execution now carries `ChannelContextScope`; MCP and Secure CLI resolution apply context grants/credentials before user credential overrides.
- Web dashboard channel detail page now has a `Contexts` tab showing contexts, stored members, effective granted tools, and scoped credential presence without exposing secret values.
**Security**
- Context capability write APIs use tenant-admin checks.
- Credential APIs return presence/metadata only; raw MCP API keys, headers/env values, and Secure CLI env values are never serialized.
- MCP grant-check cache key includes channel scope so scoped grants do not leak across channel contexts.
---
### Group chat context in agent prompts
**Features**
- Added `## Current Chat Context` to agent system prompts for channel runs. Group chats now show platform, chat type, optional group name, group ID, and sender identity when metadata is available.
- Preserved existing `<current_reply_target>` routing guard and group reply guidance while making the human-readable chat context explicit.
- Added best-effort Discord channel title forwarding from the local `discordgo.State` cache. No hot-path REST lookup or schema change.
**Fixes**
- Normalized WhatsApp `user_name` metadata into the existing sender-name resolver so WhatsApp group prompts can include sender display names.
- Sanitized group titles and sender display names before prompt rendering to strip quotes/control whitespace and cap length.
**Tests**
- Added prompt contract coverage for group title, missing title, direct chats, and prompt-injection-shaped metadata.
- Added Discord cached-channel-title and sender-name resolver coverage.
---
### Archived run timeline (issue #76)
Adds persisted run archive timeline entries for session detail review.
**New**
- `run_timeline_items` schema for PostgreSQL and SQLite/Lite, with tenant/run
sequence uniqueness and session/run indexes.
- Best-effort agent event recorder for `activity`, `assistant.message`,
`tool.call`, `tool.result`, and `run.status` entries.
- HTTP `GET /v1/runs/{runID}/timeline` and WS `run.timeline.get` read APIs,
including tenant/user visibility filtering.
- Web session detail timeline panel using the WS API and localized labels.
**Security**
- Tool arguments/results are persisted as bounded previews only.
- Raw thinking/reasoning is not persisted.
- Non-admin reads are filtered to the connected/effective user.
**Tests**
- Added store coverage for PG and SQLite, recorder tests, HTTP/WS API tests,
permission classification coverage, and UI display mapping tests.
---
### Shell security group disabled state persistence (issue #75)
**Fixes**
- Added regression coverage proving `config.patch` preserves explicit
`tools.shellDenyGroups` values set to `false` in memory, saved config JSON,
and follow-up `config.get` responses.
- Aligned Claude CLI and ACP provider runtime registration with the saved
shell deny-group config. Provider-side deny patterns now derive from
`tools.ResolveDenyPatterns(cfg.Tools.ShellDenyGroups)` instead of static
defaults, so disabled groups stay disabled after reload/provider registration.
- Re-registers existing Claude CLI/ACP provider runtimes on config changes so
settings-page saves affect runtime enforcement without restart.
- Uses locked shell deny-group snapshots before HTTP provider registration, so
provider creation cannot race config reload while reading the override map.
- Kept missing shell deny-group keys as inherited defaults; only explicit
`false` disables a group.
**Tests**
- `go test ./internal/gateway/methods -run 'TestConfigPatchPersists(InboundDebounceMs|ShellDenyGroupsFalse)' -count=1`
- `go test ./internal/providers -run 'TestGenerateHookScript' -count=1`
- `go test ./internal/providers/... -run 'ClaudeCLI|ACP|DenyPatterns|ToolBridge' -count=1`
- `go test -race ./cmd -run 'TestShellDenyGroupsConfigReload_.*|TestConfiguredShellDenyPatternsDropsDisabledPackageInstall' -count=1`
- `go vet ./...`
- `go build ./...`
- `go build -tags sqliteonly ./...`
---
### Local-first document text extraction adapter (read_document privacy optimization)
Adds optional local extraction pipeline to the `read_document` tool, allowing
PDF and DOCX parsing via `pdftotext` (poppler-utils) and `pandoc` before
falling back to cloud vision. Reduces token costs and improves privacy for
document analysis when local binaries are available.
**New**
- `DocumentParser` interface + `LocalExtractParser` implementation in
`internal/tools/document_parser.go`. Handles PDF (`pdftotext`) and DOCX
(`pandoc`) with no-shell subprocess exec, binary detection, process-group
timeout kill (SIGTERM → 3s grace → SIGKILL), minimal subprocess environment,
and 500KB output limit.
- `DocumentParserConfig` in `internal/config/config_channels.go` under `tools` section:
`local_first` (opt-in, default false), `max_pages` (default 200), `timeout_sec`
(default 30), `min_text_len` (default 16). Config is read at tool construction;
binary availability re-checked per call for runtime installs.
- Integration in `read_document` tool: `SetLocalParser()` wired at gateway
startup (`cmd/gateway_managed.go`). On a clean extraction hit, returns text
with no LLM usage (zero tokens, no Provider/Model/Usage fields). Any miss
(disabled, unsupported mime, missing binary, timeout, empty output, exec
error) transparently falls back to the cloud vision chain unchanged.
- Security: subprocess exec uses `exec.Command` (no shell), docPath validated
at the exec boundary, extracted text treated as untrusted, minimal env
(no gateway secrets), `pandoc --sandbox` for DOCX parsing.
**Requirements**
- Requires `pdftotext` and `pandoc` on PATH for local extraction. Present in:
- Docker `full` variant (`ghcr.io/nextlevelbuilder/goclaw:vX.Y.Z-full`)
- Builds with `-tags "" -X main.enableFullSkills=true` (when available)
- Desktop Lite edition: local extraction wired but not user-configurable
(remains in code for future mobile/CLI usage; server/Docker only now).
**Docs**
- `docs/03-tools-system.md` § 5 — new `document_parser` config block with all
four fields, defaults, and fallback semantics.
- `docs/03-tools-system.md` § 4 (Media Reading) — updated `read_document`
description to mention local-first extraction option.
**Testing**
- Behavior unchanged when `local_first: false` (default).
- Verify local extraction when enabled: create test PDF/DOCX, enable config,
inspect tool output for zero-token response vs cloud vision (Gemini, etc.).
---
### Skill selected download export (issue #80)
**Features**
- Extended `GET /v1/skills/export` with selected skill IDs via repeated `id`
or comma-separated `ids`, while preserving the no-selection full export as
tenant custom-skill only by default.
- Added archive format selection: `tar.gz`, `tgz` alias, and `zip`. Direct and
SSE download paths now return matching archive filenames and content types.
- Selected exports can include system/core skills explicitly without changing
full backup defaults.
- Archive output now preserves skill directory content such as `SKILL.md`,
`references/`, `scripts/`, and `assets/`, while skipping unsafe paths and
symlinks. Nested files named `metadata.json` or `grants.jsonl` remain
exportable; only generated root archive artifacts are skipped.
- Archive assembly streams file contents into the writer and revalidates opened
files against the resolved skill root to avoid large in-memory reads and
symlink-swap escapes.
- Web Skills page now supports Download from the detail dialog and Download
selected from the bulk toolbar, with format selection and EN/VI/ZH labels.
**Tests**
- Added backend coverage for export request parsing, archive writers, safe
directory walking, selected system/custom scope, and full export defaults.
- Added web helper coverage for selected export URLs and archive filenames.
---
### Discord thread history and attachment backfill (issue #69)
**Fixes**
- Discord thread mentions now fetch recent thread messages before the triggering mention and prepend them as context for the agent run.
- Prior thread image and document attachments are downloaded immediately and passed through the existing inbound media pipeline, so `read_image` and `read_document` can use files posted earlier in the thread.
- Backfill is limited to addressed Discord threads, 25 prior messages, 15 attachments, 5 MB per backfilled file, and a 30-second timeout. Missing Discord history permission or REST failures fall back to the current message without crashing.
---
### Telegram voice transcription and read_audio fallback (issue #85)
**Fixes**
- Telegram voice/audio STT now preserves Telegram's detected MIME type, including `audio/ogg; codecs=opus`, instead of forcing every STT request to `audio/ogg`.
- Telegram STT now resolves channel-scoped legacy proxy overrides by platform type `telegram`, so DB channel instances with custom names still select the Telegram override.
- `read_audio` no longer sends unsupported audio routes as `providers.ImageContent`; unsupported provider/model combinations now fail closed with an audio-specific error that names supported routes.
**Tests**
- Added Telegram STT regression coverage for channel-type override selection and MIME preservation.
- Added `read_audio` regression coverage proving unsupported providers are not called through chat/image fallback.
---
### RapidAPI cron SecureCLI diagnostics (issue #74)
**Fixes**
- Added a built-in `rapidapi` SecureCLI preset with required `RAPIDAPI_KEY`, 60s timeout, and verbose/debug flag denials.
- Credentialed exec now validates required preset env keys before binary resolution, so missing RapidAPI credentials return a GoClaw diagnostic instead of downstream `RAPIDAPI_KEY required`.
- Added safe SecureCLI env diagnostics that log env key names/count context only, never credential values.
- Added `RAPIDAPI_KEY` to fall-through exec env scrubbing.
**Tests**
- Added RapidAPI preset contract and deny-pattern coverage.
- Added credentialed exec regressions for missing RapidAPI env and successful direct exec with injected `RAPIDAPI_KEY`.
---
### Sandbox tenant workspace isolation (issue #68)
**Security**
- Scoped Docker sandbox workspace mounts to the effective tenant/session workspace from tool context instead of the global workspace root.
- Kept the in-container UX stable: the effective workspace is mounted at `/workspace`.
- Made Docker sandbox reuse workspace/config-aware so shared or agent-scoped containers cannot cross workspace boundaries.
- Added fail-closed behavior when tenant-scoped sandbox execution has no effective workspace.
**Tests**
- Added unit coverage for effective sandbox workspace selection, `/workspace` cwd mapping, normal and credentialed sandbox exec, file-tool sandbox bridge mount selection, and Docker cache-key isolation.
---
### Human-like channel delivery MVP (issue #67)
**New**
- Added `gateway.chat_behavior` runtime config for global quick acknowledgement and safe final multi-message splitting.
- Added per-channel `chat_behavior` override support for channel instances that already participate in channel delivery settings.
- Quick acknowledgements are emitted only for non-streaming channel runs and are cancelled when a block reply or terminal event arrives.
- Final splitting applies only to non-streaming text-only final replies; unsafe Markdown, code, tables, lists, quotes, JSON, and URL-only paragraphs stay as one message.
- Added `chat_behavior.preview` RPC plus dashboard controls and per-channel override fields.
**Validation**
- Added Go coverage for config resolution, preview, conservative splitting, and non-streaming quick acknowledgement delivery.
- Verified focused Go packages, both Go builds, `go vet`, web Vitest, web production build, and `git diff --check`.
**Out of scope**
- No archive/timeline storage, renderer, share/export, or interleaved run history changes. Those remain issue #76 scope.
---
### GitHub Releases update scratch dir fallback (issue #94)
- Changed GitHub Releases package updates to prefer `{runtimeDir}/tmp` for
scratch extraction and staging, instead of deriving tmp from a release or
binary directory.
- If `packages.scratch_dir` is configured but cannot be created, the update
executor now logs a warning and falls back to runtime tmp before failing.
- Added regression tests for default scratch-dir selection and fallback from an
unusable configured scratch path.
---
### CLI Credentials git preset null-env crash (issue #93)
**Fixes**
- Stopped `/v1/cli-credentials/presets` from returning nullable preset arrays
for adapter-managed CLIs such as `git`; frontend now also normalizes older
`null` payloads defensively.
- Allowed the `git` preset to create a SecureCLI binary without legacy env
vars and persisted its `adapter_name=git`, so the PAT/SSH user credential
flow activates after creation.
- Guarded the preset env-var renderer against nullish `env_vars` to keep the
Runtime & Packages → CLI Credentials tab renderable.
**Tests**
- Added backend regression coverage for stable preset arrays and git preset
creation without legacy env.
- Added frontend normalization coverage for nullable adapter-managed preset
arrays.
---
## 2026-05-28
### CLI credential adapter framework + git adapter (issue #82)
Refactors `credentialed_exec.go` from "flat env-var injection only" to a
generic `CredentialAdapter` interface that supports argv prefix injection,
ephemeral filesystem material, system-trusted env vars, and per-injection
redaction. Existing presets (`gh`, `aws`, `gcloud`, `kubectl`, `terraform`,
`gws`) route through a passthrough adapter and keep current behavior
bit-for-bit.
**New**
- `CredentialAdapter` interface + registry in `internal/tools/credential_adapter.go`.
Default `passthrough` adapter preserves legacy behavior; named adapters
register via `init() → RegisterAdapter`.
- `git` adapter (`internal/tools/credential_adapter_git.go`) covering both
HTTPS PAT and SSH key paths. PAT injected via `GIT_CONFIG_COUNT` +
`GIT_CONFIG_KEY_*`/`GIT_CONFIG_VALUE_*` env vars (never argv → keeps token
off `ps`/`/proc/<pid>/cmdline`). SSH key materialized to 0600 tmpfile +
`GIT_SSH_COMMAND` with `IdentitiesOnly=yes` and `StrictHostKeyChecking=accept-new`.
Passphrase-protected SSH keys rejected at validation with
`git.cred_ssh_passphrase_unsupported`.
- `psql` framework-validation stub adapter (`internal/tools/credential_adapter_psql.go`)
proving the interface holds for non-git credential families.
- Shared `materializeEphemeral` helper (`internal/tools/credential_ephemeral.go`)
with idempotent cleanup latch and explicit `0600` chmod.
- Per-injection audit log: `slog.Warn("security.system_env_injection", …)`
with `adapter`, `binary`, `user_id`, sorted `env_keys` (names only),
`argv_prefix_len`, `host_scope_hash` (SHA-256 first 8 hex chars — plaintext
hostname intentionally omitted for PII safety). Schema pinned by
`TestEmitSystemEnvInjectionAudit_*`.
- Typed-credential HTTP PUT path with `{error:{code,message}, error_key}`
envelope so the web UI can drive field-level validation
(`git.cred_host_scope_required`, `git.cred_ssh_passphrase_unsupported`,
etc.).
- Web UI: `CliCredentialGitFields` extends the CLI Credentials dialog with
`Personal Access Token` / `SSH Private Key` picker, host-scope input,
CRLF→LF paste normalization, masked-secret edit flow (`••••••••`
placeholder preserves stored value on save).
- 17 i18n keys × 3 locales (en/vi/zh).
**Docs**
- New: `docs/git-credential-adapter.md` — user-facing guide (when to use PAT
vs SSH vs env, host-scope semantics, TOFU caveat with `ssh-keyscan`
mitigation, SIGKILL residual material note, operator sweep recipe).
- New: `docs/credential-adapter-playbook.md` — implementer guide with worked
mappings for `kubectl`, `docker`, `npm`, `aws`, `psql` and the three-question
interface-validation gate.
- `docs/09-security.md` § 14 — trust-boundary diagram, audit field schema,
SSH TOFU + SIGKILL caveats, v2 future-work list.
- `docs/03-tools-system.md` § 8a — adapter framework summary linking to the
playbook.
**Security**
- User-paste denylist (`ValidateGrantEnvVars`) unchanged — first line of
defense intact. Adapter path is the second, audit-trailed line; a typo in
`adapter_name` falls back to passthrough (no silent bypass).
- AES-256-GCM at rest for stored PAT/SSH bodies; secrets cannot be read back
via API/UI.
- `ScrubCredentials` redacts PAT bytes and SSH key path from stdout, stderr,
`Result.Content`, and audit log JSON.
**Known v1 limitations**
- One credential per (user, binary, host_scope).
- No multi-host wildcard (`*.github.com`).
- No persistent `known_hosts` per credential (TOFU only).
- No sandbox support (adapter is incompatible with bind-mount-based sandbox
path).
- No credential-refresh primitive (blocks future `aws sts assume-role`
adapter).
---
## 2026-05-27
### zuey VPS ops scripts: repo-tracked + CI auto-sync
**Fixes**
- Patched `/usr/local/bin/goclaw-deploy` on zuey to survive a self-loop `/opt/goclaw/current` symlink (`readlink -f` now `2>/dev/null || true`, with a warning when `previous` is empty). Without this, `set -euo pipefail` aborted before `ln -sfn` could overwrite the symlink, silently failing every `deploy_zuey_beta` CI run.
**Changes**
- Moved `scripts/goclaw-upgrade-release.sh` → `scripts/zuey/goclaw-upgrade-release.sh`.
- Added `scripts/zuey/goclaw-deploy.sh` (canonical source for the on-host `/usr/local/bin/goclaw-deploy`).
- Wired `Sync zuey ops scripts to VPS` step in `.github/workflows/dev-beta-release.yaml` to `scp + sudo install` both scripts before triggering the gateway upgrade endpoint on every beta release. Requires new repository secrets `ZUEY_SSH_PRIVATE_KEY_B64` (base64-encoded private key, single line) and `ZUEY_SUDO_PASS`; step skips with a warning if either is unset.
- Updated `docs/deployment-guide.md` with the self-loop guard rationale, manual sync recipe, and required-secrets table.
**Followup fix (run 26499549166)**
- First real `deploy_zuey_beta` run on `dev` failed with `Load key: error in libcrypto` because GitHub Secrets storage normalized newlines inside the multi-line PEM block. Switched the secret to base64 (`ZUEY_SSH_PRIVATE_KEY_B64`) and added pre-flight validation (`ssh-keygen -y -f`) that fails fast with a remediation hint if the decoded key is malformed.
---
## 2026-05-24
### Google Workspace CLI runtime integration
**Features**
- Added `gws` as a preinstalled Google Workspace CLI in the full runtime image.
- Added a SecureCLI `gws` preset with encrypted credential injection fields and guardrails for interactive auth/export commands.
- Documented Drive, Gmail, and Calendar command patterns plus live credential smoke-test requirements.
**Tests**
- Added runtime binary discovery, SecureCLI preset, deny-pattern, and Dockerfile contract coverage for Google Workspace CLI.
### Slash skill commands
**Features**
- Added explicit slash skill activation for `/<slug>`, `/use <slug-or-name>`, `/list-skills`, and `/help <slug-or-name>`.
- Added tenant settings for slash command enablement, similar-skill suggestions, partial matching, and custom prefix.
**Tests**
- Added backend coverage for parser false positives, exact/partial skill resolution, suggestions, help/list commands, and config overlays.
### Configurable skill upload limits
**Features**
- Added configurable skill ZIP upload limits with config/env, SKILL.md frontmatter, and tenant system setting support.
- Added dashboard settings and dynamic upload validation so the Web UI follows the tenant limit instead of hardcoding 20MB.
**Tests**
- Added backend coverage for limit precedence, clamping, oversized rejection, and frontend coverage for parameterized upload validation.
### CLI environment variable visibility
**Features**
- Added `sensitive` and `value` kinds for secure CLI environment variables across binary defaults, agent grant overrides, and user overrides.
- Plain value entries are visible to authorized admins for operational config review, while sensitive entries remain masked and replace-only.
**Fixes**
- Stopped per-user credential reads from returning legacy sensitive env values raw.
- Kept legacy `{"KEY":"value"}` env blobs backward-compatible by treating them as sensitive.
**Tests**
- Added backend regression coverage for env kind parsing, sanitized API responses, runtime flattening, and invalid kind rejection.
- Verified Web UI build after adding env-kind controls and warnings.
### Command keyword allowlist
**Features**
- Added scoped credentialed CLI keyword allowlist config for content arguments and positional arguments, with runtime reload and web config editing.
**Fixes**
- Kept credentialed CLI `deny_args` active for command paths such as `gh secret set` while allowing approved GitHub issue/PR prose to mention security vocabulary.
- Added security audit logging for real allowlisted pass-throughs without logging full argument values.
**Tests**
- Added regression coverage for scoped keyword masking, disabled rules, unsafe positional rules, config reload, and race-safe policy snapshots.
### Browser cookie sync and config UI
**Features**
- Added scoped browser cookie sync API, encrypted cookie store, browser runtime cookie application, dashboard browser settings, and a selected-cookie Chrome extension prototype.
### Cron SecureCLI credential context
**Fixes**
- Fixed cron-triggered agent turns so credentialed CLI lookups preserve the explicit tenant user credential identity captured when the cron job is created.
- Kept cron `user_id` ownership unchanged for group-scoped list/remove behavior while storing credential lookup identity separately in cron payload metadata.
**Tests**
- Added regression coverage for cron payload credential metadata, legacy payload compatibility, cron scheduler context injection, and SQLite cron persistence parity.
---
## 2026-05-22
### Usage Cap budget controls
**Features**
- Added Standard/PostgreSQL usage caps for AI budget control by hour, day, week, or month.
- Caps support token and USD cost ceilings at tenant, agent, provider, provider type, and model scopes.
- Added OpenRouter catalog sync plus tenant/provider/model pricing overrides for input, output, cache read/write, reasoning, request, image, and web search units.
- Enforced caps in agent, fallback model, subagent, memory flush, compaction, and media reading tools (`read_image`, `read_document`, `read_audio`, `read_video`) with preflight reservation and post-call reconciliation.
- Added non-negative validation for catalog and override pricing fields.
- Added OpenRouter alias resolution for native model IDs, cached-input accounting normalization, and partial-stream failure reconciliation.
- Bridged legacy `budget_monthly_cents` into generated monthly agent USD cap policies, including migration backfill and save-time sync.
- Added web dashboard controls on Usage and Provider detail pages.
- Added Usage page editing for manual cap policies, including enable/disable, scope clearing, and token/USD limit clearing while keeping generated agent-budget caps read-only.
- Added usage-cap decision metadata to LLM spans, including allow/skip/block reason, policy IDs, estimates, actuals, and reconcile status.
**Tests**
- Added pricing and cap service coverage.
- Verified Go builds, SQLite build compatibility, full Go test suite, integration race suite, and Web UI production build.
### Messaging debounce hardening
**Features**
- Added per-agent inbound debounce override via `other_config.inbound_debounce_ms`; unset inherits the global gateway setting.
- Added Web Chat debounce for rapid text-only `chat.send` calls using `gateway.inbound_debounce_ms`.
- Clarified shared inbound debounce behavior in docs and Web UI config help text.
**Fixes**
- Fixed inbound debounce semantics so `gateway.inbound_debounce_ms=0` means no debounce and positive values set the wait window.
- Fixed Slack `debounce_delay: 0` so it disables per-thread batching instead of falling back to the default.
**Tests**
- Added regression coverage for channel inbound debounce, Web Chat debounce, media/cancel bypass handling, and Slack debounce config defaults.
### CLI P6 backend API unblock
**Features**
- Added HTTP session branch and history follow endpoints for automation clients.
- Added read-only channel writer permission testing.
- Added split activity-log and runtime-log aggregate endpoints.
**Tests**
- Added focused HTTP, store, and runtime log coverage for branch/follow, writer test, activity aggregate, and LogTee ring aggregation.
### CI/CD: zuey beta deploy
**Features**
- Added automatic zuey VPS deployment to the `Dev CI and Beta Release` workflow after beta prerelease assets are published.
- The deploy job triggers the protected gateway upgrade endpoint with the generated `vX.Y.Z-beta.N` tag, waits for upgrade status, and verifies public `/health`.
- Beta prereleases now upload `CHECKSUMS.sha256` alongside binary assets.
**Fixes**
- Updated the host release-upgrade script to support beta asset filenames with a leading `v`.
- Added checksum fallback to GitHub release asset SHA256 digests when beta releases do not publish `CHECKSUMS.sha256`.
- Detached gateway-triggered upgrades into a transient `systemd-run` unit so stopping `goclaw` during deploy no longer kills the upgrade job.
- Allowed stale upgrade `running` status records to be superseded after timeout and made the zuey deploy wait loop tolerate transient 502s during restart.
**Tests**
- Added regression coverage for stale gateway upgrade status recovery.
---
## 2026-05-21
### Tools: configurable exec timeout
**Features**
- Added `exec.settings.timeout_seconds` for configurable host command timeout through global and tenant built-in tool settings.
- Added dashboard controls for the `exec` timeout, defaulting missing settings to 60 seconds.
- Kept Docker sandbox execution on `sandbox_config.timeout_sec`; `exec.timeout_seconds` only affects the host built-in.
**Fixes**
- Added API validation for `exec` settings so invalid, zero, negative, fractional, or excessive timeout values are rejected before persistence.
**Tests**
- Added focused runtime and HTTP API regression coverage for configured `exec` timeouts and invalid settings.
- Verified web dashboard build with the typed `exec` settings form.
---
## 2026-05-20
### HTTP API contract hardening
**Fixes**
- Fixed memory API `{agentID}` handling so agent keys are resolved before storage access and invalid IDs return structured client errors instead of leaking UUID parse failures as HTTP 500.
- Allowed system/admin API-key automation to list agents and sessions without an extra `X-GoClaw-User-Id` header while preserving user filtering for non-admin callers.
- Added structured `/v1/*` not-found responses and a read-only `GET /v1/sessions` compatibility endpoint for automation clients.
**Tests**
- Added regression coverage for memory agent-key resolution, invalid memory IDs, session list auth behavior, and structured API 404s.
---
## 2026-05-18
### Packages: GitHub installer runtime path
**Fixes**
- Fixed GitHub Releases package installs on bare-metal gateways by defaulting the GitHub binary directory to `{runtimeDir}/bin` instead of Docker-only `/app/data/.runtime/bin`.
- The fix covers installs such as `github:nextlevelbuilder/goclaw-cli@v0.4.1` on the VPS, where `/app` is not writable or present.
**Tests**
- Added default-path regression coverage and made Unix-socket apk helper tests skip cleanly on Windows environments that cannot bind Unix sockets.
---
### Tools: built-in wait delay
**Features**
- Added a built-in `wait` tool with bounded millisecond delays, cancellation support, per-agent min/max settings, and runtime policy visibility.
- Preserved same-response ordering by making `wait` a sequential tool-call barrier.
- Added Web agent settings controls so per-agent wait limits are not dropped on save.
**Tests**
- Added focused wait validation, cancellation, policy, builtin seed, config parsing, and tool-stage ordering coverage.
---
### Providers: ChatGPT OAuth GPT-5.5 default
**Changed**
- Updated ChatGPT Subscription (OAuth) default model, provider model catalog, reasoning metadata, and docs examples to prefer `gpt-5.5`.
**Tests**
- Updated focused provider/model catalog, reasoning, registry, and token-window coverage for `gpt-5.5`.
---
### Deployment: Codex CLI service-user auth
**Fixes**
- Fixed agent-controlled Codex CLI auth on the VPS by ensuring the `goclaw` systemd service user has the ChatGPT login auth file under `/var/lib/goclaw/.codex/auth.json`.
- Documented the required service-user check: `sudo -u goclaw -H codex login status`.
---
### Packages: npm workspace protocol fallback
**Fixes**
- Fixed Node package installs for registry packages published with `workspace:` dependency ranges, such as `@agenttasks/cli`.
- GoClaw now retries npm `EUNSUPPORTEDPROTOCOL workspace:` failures by packing the registry tarball, rewriting workspace dependency ranges to published package versions, and installing the sanitized package folder.
**Tests**
- Added focused coverage for workspace protocol detection and package.json dependency rewrite behavior.
---
## 2026-05-17
### Skills: agent manage grants
**Fixes**
- Added explicit per-agent skill manage grants so agents can edit/delete skills they were authorized to maintain even when `owner_id` no longer matches their current actor identity.
- Auto-granted manage permission to the creating/publishing agent for new managed skills.
**UI**
- Show custom skill owner IDs in the Skills table.
- Added Skills page controls to grant agent skill access and edit/delete permission.
**Tests**
- Added PG/SQLite grant coverage and verified Go builds plus Web UI build.
---
### Agents: provider switch save fix
**Fixes**
- Fixed agent detail save after switching provider/model when the UI clears stale ChatGPT OAuth routing; typed JSON nulls now coerce to `{}` for NOT NULL agent JSON config columns in PostgreSQL and SQLite.
**Tests**
- Added regression coverage for typed `json.RawMessage(nil)` / JSON `null` agent config updates.
---
### Deployment: VPS hybrid GoClaw setup
**Operations**
- Deployed GoClaw to a VPS using bare-metal `systemd` gateway plus Dockerized PostgreSQL 18 pgvector.
- Restored the latest private PostgreSQL backup, then upgraded schema from `57` to `65`.
- Installed Node.js 22 and Codex CLI on the host; interactive `codex --login` remains manual.
- Configured Cloudflare-proxied deployment domain and issued SSL through Certbot/Nginx.
- Added `goclaw-backup-r2.timer` to dump PostgreSQL every 6 hours, upload to private Cloudflare R2 storage, and retain the latest 20 backups.
- Added deployment runbook in `docs/deployment-guide.md`.
**Features**
- Added a protected gateway upgrade HTTP API that triggers the fixed host-local upgrade script asynchronously.
- Added `scripts/goclaw-upgrade-release.sh` and installed the VPS copy at `/usr/local/bin/goclaw-upgrade-release`; dry-run verifies the latest stable server release asset and checksum before deploy.
---
### CI/CD: dev branch beta automation
**Features**
- Added a `Dev CI and Beta Release` GitHub Actions workflow for `dev` pushes that runs Go and Web UI checks before publishing a beta prerelease.
- Added semantic-release-style beta version calculation from Conventional Commits, creating `vX.Y.Z-beta.N` tags and prereleases automatically after tests pass.
- Beta automation uploads Linux binaries and publishes `beta` Docker image tags for the same release version, with Docker Hub publishing enabled when credentials are configured.
**Fixes**
- Made beta prerelease publishing independent of a local checkout by passing the repository explicitly to GitHub CLI release commands.
---
### Agent Permissions: channel and workspace matrix
**Features**
- Added `config.permissions.check` so the UI can preview the effective allow/deny decision for an agent, scope, config type, and user.
- Added Permissions UI support for `userId="*"` to grant all members in a selected group scope.
- Documented the cross-channel agent permission matrix, including Zalo group context writes and workspace/context file boundaries.
**Security**
- Protected group context file writes now require a real sender with `context_files` or legacy `file_writer` permission.
- Group file/context/cron permission-store errors now fail closed instead of silently allowing mutation.
- Backend config permission RPCs validate config types and permission values before storing rules.
**Tests**
- Added focused store and context interceptor coverage for permission preview and protected group context writes.
---
### CLI Credentials: per-agent env vars under Packages
**Features**
- Kept `CLI Credentials` as the Packages tab at `/packages?tab=cli-credentials` and preserved the legacy `/cli-credentials` redirect.
- Removed the duplicate standalone `CLI Credentials` item from the left sidebar.
- Added focused coverage for grant env payload semantics and routing contracts.
**Fixes**
- Made agent-grant environment variable controls easier to find by labeling the row action and adding a visible Environment Variables header inside the grant form.
**Security**
- Nested agent-grant get/update/delete/reveal routes now verify the grant belongs to the binary ID in the URL.
- Grant creation now validates both the CLI binary and target agent exist in the authenticated tenant before inserting.
- Grant updates now validate env payloads before scalar writes, preventing partial state changes on 400 responses.
- Runtime env precedence is covered: per-user env overrides per-agent grant env for duplicate keys.
- Credentialed exec now fails closed if per-user env JSON is invalid.
- SQLite add-column migrations for replayed schema snapshots now skip already-present columns.
**Tests**
- Focused backend, store compile, UI unit, and web build validation pass.
- Live PostgreSQL validation skipped because `TEST_DATABASE_URL` is not set.
---
## 2026-05-16
### Agents: per-agent model fallback
**Features**
- Added per-agent `model_fallback` config with ordered provider/model candidates.
- Agent advanced config UI now supports enabling fallback, adding backup provider/model pairs, and drag-and-drop ordering.
- Runtime wraps the resolved agent provider with fallback only for normal agent execution. Explicit provider/model overrides bypass the fallback chain.
**Migrations**
- **PG:** `000065_agent_model_fallback` adds `agents.model_fallback JSONB NOT NULL DEFAULT '{}'`.
- **SQLite:** schema v33 to v34 adds `agents.model_fallback TEXT NOT NULL DEFAULT '{}'`.
**Tests**
- Focused provider, provider resolver, store tests pass. Main app builds in default and `sqliteonly` modes. Web production build passes.
---
## v3.11.3 — 2026-04-26
### Fixes
- **`goclaw providers verify`** — empty body now triggers ping mode (provider registered/reachable check) and returns `{valid:true}` for registered providers. New `--model <alias>` flag for chat-verify against a specific model. CLI response parser switched from stale `{success, models}` to `{valid, error}`. Onboard auto-verify path fixed identically (was silently printing "FAILED" on every successful provider creation). (#1034)
- **`goclaw providers delete`** — succeeds when referenced by `agent_heartbeats`. FK changed to `ON DELETE SET NULL`; `DeleteProvider` (PG + SQLite) now wraps in a transaction that also disables affected heartbeats so the next scheduler tick cannot fire stale config. `slog.Warn("heartbeat.provider_cleared")` emitted with the disabled count. (#1034)
- **`goclaw doctor`** — provider rows with empty `display_name` now render the canonical `name` instead of a blank line. Query switched from `COALESCE(display_name, name)` to `COALESCE(NULLIF(display_name, ''), name)`. (#1034)
### Migrations
- **PG:** `000057_heartbeat_provider_fk_set_null` — defensive orphan cleanup, drop existing FK by lookup, re-add with `ON DELETE SET NULL`. Brief `ACCESS EXCLUSIVE` lock on `agent_heartbeats` during ALTER (sub-second on small tables; heartbeat workers may pause briefly).
- **SQLite:** schema v25 → v26 — full table rebuild for `agent_heartbeats` with new FK clause; explicit 25-column INSERT/SELECT preserves existing rows. `idx_heartbeats_due` recreated.
### Upgrade notes
- **Docker users:** MUST pull the new image (`ghcr.io/nextlevelbuilder/goclaw:v3.11.3`) AND run `goclaw upgrade` (or `goclaw migrate up`). Stale images on v3.11.2 will fail boot with `schema version mismatch: required 57, current 56` after the migration runs.
- **Bare-metal users:** rebuild and run `./goclaw upgrade`.
### OpenAPI
- `/v1/providers/{id}/verify` — `requestBody.required: false`; `model` documented as optional with ping-mode semantics.
---
## 2026-04-24
### Tools: Config-driven shell deny-groups + read_audio routing fixes
**Features**
- **`shellDenyGroups` runtime config:** `config.tools.shellDenyGroups` (map[string]bool) allows operators to toggle shell deny-groups (e.g. `package_install`, `env_dump`) from the /config Web UI without restarting. Merged with per-agent overrides with per-key agent precedence; multi-tenant invariant preserved. Subscribed to `bus.TopicConfigChanged` for live reload.
**Fixes**
- **Credentialed CLI wording scope:** "operation requires admin approval" error wording now scoped to `[CREDENTIALED EXEC]` marker only — was over-applied to generic shell failures, causing unjustified LLM pre-refusals.
- **read_audio transcription routing:** Fixed silent fallback on missing API credentials for transcription/gemini/openai paths — now hard-errors with clear message. Fixed openai_compat providers (e.g. DashScope) not reaching `/v1/audio/transcriptions` endpoint; moved transcription model check above provider type switch.
**Tests**
- 6 unit tests for shell deny-groups merge/defensive-copy semantics.
- 3 pub/sub dispatch tests for config reload lifecycle.
- 3 regression tests for read_audio fail-fast paths.
---
## 2026-04-22
### Providers: Native image generation for Codex + OpenAI-compat
**Features**
- **Codex native track:** `CodexProvider` now attaches the `image_generation` tool object to `POST /codex/responses` when the agent permits it. Streams `response.image_generation_call.partial_image` intermediate frames + `response.output_item.done` (type `image_generation_call`) final images; non-stream path walks `response.output[]`. Deduped per `item_id`, partial frames emitted as `ImageContent{Partial:true}` for UI progressive render.
- **OpenAI-compat track:** `tools[]` serializer passes `{type:"image_generation"}` entries through natively; response parser reads `choices[0].message.images[]` / `choices[0].delta.images[]` (data URLs) into `ChatResponse.Images`.
- **Media persistence:** `internal/agent/media.go` `persistAssistantImages()` writes final images to `{workspace}/media/{sha256}.{ext}`, returns `MediaRef` entries, clears inline base64. Idempotent on hash. Wired via `pipeline.Deps.PersistAssistantImages` callback from `FinalizeStage`. Partial frames skipped.
- **Capabilities + gate:** `ProviderCapabilities.ImageGeneration` flag, set true on Codex provider. Tri-level gate in agent loop: provider capability AND `AgentConfig.AllowImageGeneration` (read from `other_config.allow_image_generation`, default true) AND request not opted-out via `x-goclaw-no-image-gen` header.
- **Web UI:** Composer "Images" toggle chip (visible only when provider supports image gen, per-agent persistence in localStorage). Streaming placeholder skeleton in `ActiveRunZone` while partials arrive. `MediaGallery` assigns `generated-{timestamp}.png` filename for assistant-generated PNGs.
**Wire format**
Implementation is evidence-backed against the native ChatGPT Responses API event shape, not the compat shim shape. Research notes in `plans/reports/`.
**i18n**
- 1 UI key (`imageGenDownloadName`) in `ui/web/src/i18n/locales/{en,vi,zh}/chat.json` — download filename for generated images.
**Tests**
- Unit tests across providers (Codex native + OpenAI-compat), agent media persistence, store config. Full test sweep: 2618 pass.
**Internal docs**
- `plans/260422-1349-goclaw-chatgpt-image-gen/` — plan + phase files.
- `plans/reports/researcher-260422-1414-codex-native-image-events.md` — native event schema.
## 2026-04-20
### Pipeline: accurate context token tracking + dynamic compaction
**Features**
- **Session token display from metadata:** `sessions.metadata` now carries `last_prompt_tokens` and `last_message_count` on finalize. List query reads from metadata; fallback to octet/rune heuristic when absent. Fixes stale token display across session re-opens.
- **Tool-schema token accounting:** `TokenCounter.CountToolSchemas(model, tools)` new method counts tool definitions serialized as JSON. Tool-schema tokens included in `OverheadTokens` at ContextStage.
- **Dynamic compaction max_tokens:** Compaction `max_tokens` now derived from `in/25` with clamp `[1024, 8192]`. Applied to both summarization flow (`loop_compact.go`) and history sanitization (`loop_history_sanitize.go`). Replaces static 4096 limit — adapts budget to context size.
**Code**
- `internal/store/pg/sessions_list.go` — read/write `last_prompt_tokens` and `last_message_count` in metadata.
- `internal/store/sqlitestore/sessions*.go` — parity SQLite store updates.
- `internal/tokencount/token_counter.go` — `CountToolSchemas` interface method + `tiktoken_counter.go` impl.
- `internal/pipeline/context_stage.go` — include tool overhead in `OverheadTokens`.
- `internal/agent/loop_compact.go` — `dynamicSummaryMax` function; apply to compaction call.
- `internal/agent/loop_history_sanitize.go` — apply dynamic max to sanitization.
**Tests**
- `internal/tokencount/count_tool_schemas_test.go` — tool schema token counting.
- `internal/agent/loop_compact_dynamic_max_test.go` — dynamic max_tokens clamping.
- `internal/pipeline/context_stage_tool_overhead_test.go` — tool overhead integration.
- `internal/store/sqlitestore/sessions_display_tokens_integration_test.go` — metadata round-trip.
---
### TTS: timeout tenant-config + Gemini text-only 400 fix
**Features & Fixes**
- **Tenant-config timeout:** HTTP `/v1/tts/synthesize` and `/v1/tts/test-connection` now read `tts.timeout_ms` from system_configs (default 120s, was hardcoded 15s/10s). Gemini client default bumped 30s→120s for end-to-end alignment.
- **Gemini text-only error recovery:** Gemini preview models occasionally emit 400 "text generation" responses. Fixed by: (1) prepending inline prefix `"Speak naturally: "` to single-voice synthesis (multi-speaker untouched), (2) 1-retry with stronger prefix `"Read the following text aloud without translating, commenting, or modifying: "`, (3) new sentinel `gemini.ErrTextOnlyResponse` preserved through fallback chain via `errors.Join`.
- **Error UX:** HTTP returns 422 with localized `MsgTtsGeminiTextOnly` message. Agent TTS tool branches on sentinel to emit locale-translated ForLLM response.
- **Model default:** Gemini default model bumped `gemini-2.5-flash-preview-tts` → `gemini-3.1-flash-tts-preview` for higher stability.
- **UI bounds:** TTS timeout input now has `max=300000` (5 min).
**i18n**
- New key `MsgTtsGeminiTextOnly` in EN/VI/ZH catalogs for HTTP 422 + agent-tool ForLLM mapping.
**Code**
- `internal/audio/tts.go` — read tenant timeout in synthesize handlers.
- `internal/audio/gemini/` — inline prefix logic, retry budget, text-only sentinel.
- `internal/tools/tts.go` — agent-tool i18n branching on sentinel.
- `internal/http/methods/tts.go` — HTTP 422 error mapping.
---
### Tools: `send_file` — explicit workspace file delivery
**Features**
- **`send_file` tool** (`internal/tools/send_file.go`): dedicated tool for sending existing workspace files as chat attachments. Takes `path` (required) and `caption` (optional). Replaces implicit `message(MEDIA:path)` convention for re-delivering already-created files. Marks `DeliveredMedia` on success to prevent duplicate delivery.
- **`DeliveredMedia` mark on `message(MEDIA:)` sends** (`internal/tools/message.go`): patched to call `IsDelivered` / mark after successful MEDIA upload — closes the cross-tool duplicate-delivery gap where a file sent via `message(MEDIA:)` was not tracked and could be re-sent by `send_file`.
- Registered as builtin tool in `cmd/gateway_tools_wiring.go` and seeded in `cmd/gateway_builtin_tools.go`.
## 2026-04-22
### Codex OAuth pool routing strategy cleanup
**Changes**
- Removed `primary_first` from the public Codex OAuth routing strategy surface. The API, OpenAPI schema, and web UI now expose only `round_robin` and `priority_order`.
- Legacy `primary_first` and `manual` routing values now normalize to `priority_order` on read in the backend store layer.
- Activity endpoints now default empty/no-pool responses to `priority_order` instead of `primary_first`.
- Agent overrides that explicitly persist `extra_provider_names: []` continue to behave as single-account-only routing after the migration.
**Docs**
- Updated `docs/02-providers.md` and `docs/18-http-api.md` to describe the two-strategy model and the compatibility migration.
---
## 2026-04-21
### Webhook fixes (post-review security & idempotency hardening)
**Fixes**
- **K1: Auth context isolation** — Webhook auth middleware now resolves secret/HMAC signature before tenant injection (eliminating 401 due to tenant scope applied too early). Unscoped store methods `GetByHashUnscoped` + `GetByIDUnscoped` added to WebhookStore interface.
- **K7: IP allowlist enforcement** — Inbound webhook calls now check `ip_allowlist` field (CIDR + exact IP) after bearer/HMAC auth. Empty list = allow all (back-compat). Rejected requests return HTTP 403 with log `security.webhook.ip_denied`.
- **K8: HMAC replay protection** — Per-process nonce cache (key = `sha256(tenant_id + "|" + signature_hex)`) with 320s TTL rejects duplicate signatures within the skew window. Single-node caveat documented. Log: `security.webhook.hmac_replay`.
- **K2: `request_payload` canonical shape** — All webhook audit rows now store `{"body_hash":"<hex64>","meta":{...}}` JSON instead of raw bytes. Idempotency checker compares body hashes to detect replays with different payloads (409 Conflict).
- **K3: Body hash extraction** — `extractBodyHash()` now parses canonical audit payload structure (previously had parsing bugs leading to missed hash validation).
- **K9: Invariant test column fix** — Webhook tenant isolation test now references correct schema columns (`encrypted_secret`, `lease_token`).
- **K4: Worker slot drain** — Fixed channel leak in webhook worker that prevented slot release on successful claims. Concurrency now scales properly under load.
- **K5: Lease-token CAS on UpdateStatus** — Stale webhook receivers can no longer overwrite delivery status. Status updates use optimistic concurrency on `lease_token` (UUID), ensuring only the owning worker can mark the call done. Prevents duplicate delivery from slow receivers.
- **K6: HMAC signing key encryption** — Raw secret (from which `hmac_signing_key = hex(SHA-256(secret))` is derived) is now encrypted at rest via AES-256-GCM using `GOCLAW_ENCRYPTION_KEY`. Database compromise no longer = HMAC key compromise. Clients receive plaintext secret once (create/rotate response) and must store securely.
- **K10: Shared rate limiter instance** — Fixed duplicate `webhookLimiter` instantiation causing doubled RPM enforcement. Single limiter now shared across all webhook endpoints.
**Migrations**
- PostgreSQL: Migration `000057` adds `lease_token` column to `webhook_calls`. Migration `000058` adds `encrypted_secret` column to `webhooks`.
- SQLite: Schema v28 includes both new columns.
**Docs**
- `docs/webhooks.md`: Section 3 clarified bearer/HMAC auth contract + IP allowlist behavior. New Section 14 explains encryption at rest, key contract, DB compromise boundary.
- `docs/00-architecture-overview.md`: Section 12 (Webhook Subsystem) updated to mention lease-token CAS semantics and secret encryption.
**Environment**
- `GOCLAW_ENCRYPTION_KEY` is now **required** for webhook HMAC auth. Same key also encrypts LLM provider credentials.
---
## 2026-04-19
### TTS: Gemini provider + ProviderCapabilities schema engine
**Features**
- **Gemini TTS provider** (`internal/audio/gemini/`): supports `gemini-2.5-flash-preview-tts` and `gemini-2.5-pro-preview-tts`. 30 prebuilt voices, 70+ languages, multi-speaker mode (up to 2 simultaneous speakers with distinct voices), audio-tag styling, WAV output via PCM-to-WAV conversion.
- **`ProviderCapabilities` schema** (`internal/audio/capabilities.go`): dynamic per-provider param descriptor. Each provider exposes `Capabilities()` returning `[]ParamSchema` (type, range, default, dependsOn conditions, hidden flag) + `CustomFeatures` flags. UI reads `GET /v1/tts/capabilities` and renders param editors without hard-coded field lists.
- **Dual-read TTS storage**: tenant config read from both legacy flat keys (`tts.provider`, `tts.voice_id`, …) and new params blob (`tts.<provider>.params` JSON). Blob wins on conflict. Allows gradual migration; no data loss on downgrade.
- **`VoiceListProvider` interface** refactor: dynamic voice fetching (ElevenLabs, MiniMax) now via `ListVoices(ctx, ListVoicesOptions)` instead of per-provider ad-hoc methods. Unified `audio.Voice` type.
- **`POST /v1/tts/test-connection`**: ephemeral provider creation from request credentials + short synthesis smoke test. Returns `{ success, latency_ms }`. No provider registration; no config mutation. Operator role required.
- **`GET /v1/tts/capabilities`**: returns `ProviderCapabilities` JSON for all registered providers.
**i18n**
- Backend sentinel error keys (`MsgTtsGeminiInvalidVoice`, `MsgTtsGeminiInvalidModel`, `MsgTtsGeminiSpeakerLimit`, `MsgTtsParamOutOfRange`, `MsgTtsParamDependsOn`, `MsgTtsMiniMaxVoicesFailed`) in all 3 catalogs (EN/VI/ZH).
- HTTP 422 responses for Gemini sentinel errors now use `i18n.T(locale, key, args...)` — locale from `Accept-Language` header.
- ~80 param `label`/`help` keys across web + desktop locale files (EN/VI/ZH); parity enforced by `ui/web/src/__tests__/i18n-tts-key-parity.test.ts`.
**Security**
- SSRF guard on `api_base` override for test-connection (`validateProviderURL()`) — blocks `127.0.0.1` / `localhost` / RFC1918 ranges.
**Docs**
- `docs/tts-provider-capabilities.md` — schema reference + per-provider param tables + storage format + "Adding a new provider" checklist.
- `docs/codebase-summary.md` — TTS subsystem section documenting manager, providers, storage, endpoints.