4 Commits
Author SHA1 Message Date
c1b89e96e1 chore: merge main into dev (#1370)
* fix: unify NO_REPLY detection (#1233)

Co-authored-by: GoClaw Operator <operator@goclaw>

* feat(acp): surface session/update tool_call notifications at Info level (#1141)

session/update notifications carrying a ToolCall (or inline tool_call /
tool_call_update kind) were only dumped at Debug level inside the params
blob. Operators could see `security.tool_granted` (permission granted) but
had no way to tell whether the tool actually executed successfully — both
"permission granted then failed silently" and "permission granted and
succeeded" looked identical in journalctl.

Add a structured Info log emitting toolCallId, name/title, status, and a
content preview (truncated at 400 chars) whenever the notification
contains tool-call state. This is what made it possible to diagnose the
recent .goclaw/-path-deny regression — `status=failed` immediately after
`security.tool_granted` revealed the gap that the granted-only log hid.

No behavior change beyond logging volume; preview is bounded.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(exec): exempt venv python interpreter from .goclaw/ path deny (#1140)

The ExecTool path-deny rule blocks any token containing `.goclaw/` unless
it matches one of the AllowPathExemptions prefixes (skills-store, tenants).
This silently rejected legitimate commands invoking the goclaw-managed
Python interpreter via its absolute path:

    /home/user/.goclaw/venv/bin/python3 .../script.py

The first token `/home/user/.goclaw/venv/bin/python3` matched the deny
pattern but no exemption, so the entire command was denied.

Naive exemption (".goclaw/venv/bin/") does not work: matchesAnyPathExemption
resolves both tokens and exemption candidates via EvalSymlinks, and the
venv's python3 is a symlink into the host's python cellar (e.g. linuxbrew).
The token canonicalizes to /home/linuxbrew/.../python3.14 while a literal
".goclaw/venv/bin/" prefix never gets touched.

Fix: resolve venv/bin/python3 once at startup and exempt the dirname of
the resolved target. Failure to resolve (no venv present) silently falls
through.

Without this, ACP-driven agents either fail outright or work only via
fragile heuristics (cwd-local symlinks generated on the fly by the LLM).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cron): invert stateless gate to match UI label (#1139)

The cron job handler reset the session when stateless=false and skipped
reset when stateless=true — the opposite of what every UI locale labels
the field (en/ko/zh/vi all describe stateless as "each run starts fresh
without loading previous messages").

The buggy gate caused stateless=true crons to silently accumulate session
history across every execution, leading to context bloat and increasing
the chance of LLMs short-circuiting tool calls in favor of replaying
prior assistant turns. One affected daily ETL cron grew to 38 messages
over 18 days before the regression was noticed.

Fix: gate the Reset on `if job.Stateless` so the runtime matches the UI
contract. No DB migration is required — existing values were set by users
based on the UI label, so they already encode the intended behavior.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(providers): retry transient Codex response failures (#1332)

* fix(providers): retry transient Codex response failures

* test(cron): tolerate nil session store in handler tests

* fix(background): clear stale provider alerts (#1362)

Co-authored-by: Collective Developer <man@collective.dev>

---------

Co-authored-by: Duy /zuey/ <duy@wearetopgroup.com>
Co-authored-by: nguyenha935 <nguyenthanhha935@gmail.com>
Co-authored-by: GoClaw Operator <operator@goclaw>
Co-authored-by: codebit0 <34156842+codebit0@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Zezae Oh <zezaeoh@gmail.com>
Co-authored-by: Collective Developer <man@collective.dev>
2026-07-06 10:49:54 +07:00
040b0f1944 fix(acp): Gemini ACP protocol fixes and multi-session architecture (#901)
* fix(cli): add missing X-GoClaw-User-Id header to gateway client

* feat(acp): comprehensive integration with Gemini ACP protocol

- Support nested JSON-RPC notification structures
- Add robust streaming text collection and mapping
- Increase handshake timeout to 60s for heavy model initialization
- Fix WebSocket user_id authentication and schema v47 compatibility
- Allow Google/GCP environment variables for ACP subprocesses

* refactor(acp): multi-session architecture with session tracing and temp session cleanup

- One shared Gemini process, multiple ACP sessions per process (one per goclaw conversation)
- resolveSession: per-key mutex prevents TOCTOU race on concurrent session creation
- Respawn detection via proc pointer comparison; session/load fallback after crash
- sessionReaper: purges ACP sessions idle >30min; temp- sessions purged immediately on completion
- WithGoclawSession context propagation: goclaw session key appears alongside ACP sid in all logs
- dispatchUpdate: Gemini agent_message_chunk protocol mapping normalized here
- session_test.go: full coverage of multi-session API (Initialize, NewSession, Prompt, Cancel, dispatch)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(acp): align with ACP protocol standard (SDK v0.16.1)

- InitializeRequest: "capabilities" → "clientCapabilities" (standard field name)
- LoadSessionRequest: add mcpServers field (required by standard)
- mapStopReason: add standard stop reasons (max_tokens, cancelled)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(acp): address review findings from PR #901

- B1: Restore RequiredSchemaVersion to 55 (was incorrectly set to 47)
- S1: Move X-GoClaw-User-Id header outside token check
- S2: Add env gate (ACP_GEMINI_E2E) to Gemini E2E test
- S3: Fix tab indentation in jsonrpc.go writeMessage
- Build: Extract Pdeathsig to platform-specific files for cross-platform build

* fix(acp): address S4-S7 review findings

S4/S5: Send session/cancel before purging sessions locally
- purgeSession() now cancels ACP session before deleting map entry
- sessionReaper() sends cancel notification for idle sessions
- Updated comments to reflect actual behavior

S6: Tighten GOOGLE_/GCP_ env var filtering
- Add GOOGLE_, GCP_ back to sensitiveEnvPrefixes
- Add allowedEnvExact for safe vars: GOOGLE_API_KEY,
  GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_CLOUD_PROJECT, GCP_PROJECT

S7: Propagate ACP errors to callers
- Chat/ChatStream now return err alongside ChatResponse
- Enables upstream retry logic and error metrics

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: viettranx <viettranx@gmail.com>
2026-04-18 12:17:23 +07:00
viettranx adf3eab01b test(acp): extract processCloseTimeout var for fast-test override
TestProcessPool_Close_TimesOutSlowProcess was burning 5s of wall
time per run to exercise the "fake process never exits" path,
because Close() used a hard-coded `time.After(5 * time.Second)`.

Extract the timeout as a package var `processCloseTimeout` (default
5s — production unchanged) so the test can override to 20ms via
t.Cleanup-scoped swap. Same pattern as the recent cron/vault/
facebook speedups in commit 0c44149f.

Result: acp race suite 17s → 1.6s. No production behavior change.
2026-04-12 07:17:48 +07:00
Goonandviettranx 5e2fa395c7 feat(providers): add ACP provider for external coding agents (#190)
* feat(providers): add ACP provider for orchestrating external coding agents (#189)

Implement native Go ACP (Agent Client Protocol) client as a new Provider.
Enables GoClaw to orchestrate any ACP-compatible agent (Claude Code, Codex
CLI, Gemini CLI) as a subprocess via JSON-RPC 2.0 over stdio.

- Add bidirectional JSON-RPC 2.0 transport over stdio pipes
- Add subprocess process pool with idle TTL reaping and crash recovery
- Add ACP session lifecycle (initialize, session/new, session/prompt)
- Add tool bridge for agent-initiated fs/terminal/permission requests
- Add workspace sandboxing, shell deny patterns, and env var filtering
- Wire config-based and DB-based provider registration paths
- Export DefaultDenyPatterns from tools package for reuse

* feat(providers): add changelog entry for ACP provider integration

* fix(tools): prevent workspace traversal bypass via /tmp/ fallback in resolveMediaPath

Reject paths containing ".." in the isInTempDir fallback to prevent
workspace escape where traversal path still resolves inside /tmp/.

* fix(tools): block workspace-sibling paths in resolveMediaPath /tmp/ fallback

When workspace is inside /tmp/, traversal paths like workspace/../X
resolve to /tmp/ siblings that pass isInTempDir. Reject paths inside
the workspace parent directory to prevent this escape.

* feat(providers): add ACP provider web UI and live reload via pubsub

Web UI for creating/editing ACP providers with dedicated form fields
(binary, args, idle TTL, permission mode, work directory). ACP providers
now update immediately without gateway restart via cache invalidation
pubsub pattern.

Frontend:
- New ACPSection form component with i18n (en/vi/zh)
- Provider form dialog integration with ACP state management
- ACP type badge on providers list page
- Settings field added to provider TypeScript types

Backend:
- ACP models handler (claude/codex/gemini) without API key requirement
- Binary path validation + LookPath verification in verify handler
- Provider CRUD emits cache.invalidate events via msgBus
- Subscriber in gateway_managed.go re-registers ACP providers from DB
- ACP core improvements from code review (helpers, jsonrpc, process,
  terminal, tool_bridge)

---------

Co-authored-by: viettranx <viettranx@gmail.com>
2026-03-14 16:16:08 +07:00