Skip to content

Commit aaebb6e

Browse files
authored
docs: sync with goclaw 392f0fda..d85bf171 (56 commits) — EN+VI+ZH (#65)
1 parent 60a21e3 commit aaebb6e

81 files changed

Lines changed: 9959 additions & 365 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464

6565
- [Channel Overview](channels/overview.md)
6666
- [Telegram](channels/telegram.md)
67+
- [Bitrix24](channels/bitrix24.md)
6768
- [Discord](channels/discord.md)
6869
- [Feishu / Lark](channels/feishu.md)
6970
- [Larksuite](channels/larksuite.md)

advanced/browser-automation.md

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,91 @@ The snapshot returns an accessibility tree. Use `interactive: true` to see only
199199

200200
---
201201

202+
## Selected Cookie Sync
203+
204+
Server-side browser sessions start with no login state. **Selected cookie sync** lets a user pick specific cookies from a site they are logged into and copy them into GoClaw, so an agent's browser can act as that signed-in session — without sharing a password.
205+
206+
A small Chrome extension (`chrome-selected-cookie-sync`) does the picking. There is **no automatic background sync**: the user opens the extension on the active tab, checks the exact cookies to share, and clicks **Sync**. GoClaw stores the values encrypted and replays them into the agent's browser only for matching domains and paths.
207+
208+
```mermaid
209+
flowchart LR
210+
USER["User on logged-in site"] --> EXT["chrome-selected-cookie-sync\nextension"]
211+
EXT -->|"POST /v1/browser/cookies/sync"| GW["GoClaw gateway"]
212+
GW -->|"AES-256-GCM encrypt"| DB[("browser_cookies\ntable")]
213+
DB -->|"decrypt + domain/path match"| AGENT["Agent browser session"]
214+
```
215+
216+
### Endpoints
217+
218+
All three endpoints require **operator** auth (gateway token, API key, or paired-browser auth).
219+
220+
| Method | Path | Purpose |
221+
|--------|------|---------|
222+
| `POST` | `/v1/browser/cookies/sync` | Upsert selected cookies for an agent |
223+
| `GET` | `/v1/browser/cookies?agent_id=&domain=&name=&path=` | List synced cookie **metadata** (never values) |
224+
| `DELETE` | `/v1/browser/cookies?agent_id=&domain=&name=&path=` | Revoke synced cookies |
225+
226+
The client only ever chooses `agent_id`. **Tenant and user are derived from the auth context**, not from the request body — a client cannot spoof another user's cookies. Sync is rejected when the auth context has no user, or when no `agent_id` is supplied.
227+
228+
**Sync request body:**
229+
230+
```json
231+
{
232+
"agent_id": "default",
233+
"source": "chrome-selected-cookie-sync",
234+
"cookies": [
235+
{
236+
"domain": "example.com",
237+
"name": "session",
238+
"path": "/",
239+
"value": "REDACTED",
240+
"secure": true,
241+
"httpOnly": true,
242+
"sameSite": "lax",
243+
"expirationDate": 1789999999
244+
}
245+
]
246+
}
247+
```
248+
249+
**Response:** `{ "synced": 1 }`. Limits: max 200 cookies per request, 16 KB per cookie value, 1 MB total body.
250+
251+
The `GET` response returns metadata only — `domain`, `name`, `path`, `secure`, `httpOnly`, `sameSite`, `expiresAt`, `source`, `updatedAt`. Cookie **values are never returned**.
252+
253+
### Scope and uniqueness
254+
255+
Each stored cookie is keyed by `(tenant_id, user_id, agent_id, domain, path, name)`. Re-syncing the same cookie updates the existing row (upsert). This scope is what keeps one user's cookies from leaking into another user's or another agent's browser session.
256+
257+
### Security
258+
259+
- **Encrypted at rest**: cookie values are encrypted with AES-256-GCM before being written to the `browser_cookies` table. Requires the `GOCLAW_ENCRYPTION_KEY` environment variable — **sync and list fail closed (HTTP 503) when it is unset**, so cookies are never persisted in plaintext.
260+
- **Write-only values**: the list endpoint and audit logs return metadata only. Cookie values never appear in API responses or logs.
261+
- **Scoped replay**: the agent browser receives a cookie only when the requested URL's host and path match the stored cookie's domain/path, the cookie has not expired, and the tenant/user/agent scope matches.
262+
- **Explicit selection**: the extension reads cookies only after the user grants host permission for the active site, and sends only the cookies the user checked.
263+
- **Revocation**: delete from the extension or call `DELETE /v1/browser/cookies?agent_id=<agent>&domain=<domain>` to remove synced cookies. Omitting `domain` removes all cookies for that agent.
264+
265+
### How the agent consumes synced cookies
266+
267+
When the agent's browser navigates to an `http(s)` URL, GoClaw's cookie provider looks up cookies for the current browser scope (`tenant_id` / `user_id` / `agent_id`), decrypts them, and injects only those whose domain and path match the target URL (and that have not expired). Non-HTTP schemes get no cookies. The agent never sees raw values — they are applied directly to the Chrome session via CDP.
268+
269+
### Install the extension
270+
271+
The extension lives in the GoClaw repo at `extensions/chrome-selected-cookie-sync/`.
272+
273+
1. Open `chrome://extensions`, enable **Developer mode**, click **Load unpacked**, and select the `extensions/chrome-selected-cookie-sync/` folder.
274+
2. Open a tab on the site you are logged into, then click the extension icon.
275+
3. Fill in the popup:
276+
- **Gateway URL** — e.g. `http://localhost:18790`
277+
- **Token** — an operator token (sent as `Authorization: Bearer <token>`)
278+
- **User ID** — sent as the `X-GoClaw-User-Id` header
279+
- **Agent ID** — e.g. `default`
280+
4. Click **Grant access** to give the extension host permission for the current site, then **Refresh** to list the site's cookies.
281+
5. Check the cookies you want to share (or **Select all**), then click **Sync**. The popup confirms `Synced N cookies.`
282+
283+
Settings are saved in `chrome.storage.local`. The extension requests gateway-origin permission before sending, and asks for active-tab host permission before reading cookies.
284+
285+
---
286+
202287
## Security Considerations
203288

204289
- **SSRF protection**: GoClaw applies SSRF filtering to tool inputs — agents cannot be trivially directed to internal network addresses.
@@ -256,4 +341,4 @@ Returns:
256341
- [Exec Approval](/exec-approval) — require human sign-off before running commands
257342
- [Hooks & Quality Gates](/hooks-quality-gates) — add pre/post checks to agent actions
258343

259-
<!-- goclaw-source: 050aafc9 | updated: 2026-04-09 -->
344+
<!-- goclaw-source: d85bf171 | updated: 2026-06-07 -->

advanced/cli-credentials.md

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ A `400` response on create/update includes the rejected key names in `rejected_k
167167
"error": "env keys denied: LD_PRELOAD, PATH",
168168
"rejected_keys": "LD_PRELOAD,PATH"
169169
}
170+
```
170171

171172
## REST API
172173

@@ -246,6 +247,126 @@ POST /v1/cli-credentials/{id}/agent-grants/{grantId}/env:reveal
246247

247248
Returns the decrypted plaintext env vars. Rate-limited to 10 calls/minute per user. See [Revealing Decrypted Env Vars](#revealing-decrypted-env-vars) for full details.
248249

250+
## Typed Credential Adapters
251+
252+
The sections above describe the **legacy env-paste model** — you paste arbitrary environment variables and GoClaw injects them verbatim into the child process. That works for tools that read auth from a single stable env var (`GH_TOKEN`, `AWS_ACCESS_KEY_ID`, …), but it fails for tools like `git` that read credentials from config files, credential helpers, or per-remote URLs — pasting a PAT into `GIT_TOKEN` does nothing.
253+
254+
**Typed credential adapters** solve this. Instead of pasting raw env vars, you choose a credential *type*, and GoClaw routes the credential through a server-side adapter that knows how to inject it correctly and securely for that specific tool.
255+
256+
### Credential types
257+
258+
A user credential row carries a `credential_type` (migration `000073`):
259+
260+
| `credential_type` | Meaning |
261+
|-------------------|---------|
262+
| `NULL` / `env` | Legacy env passthrough — env vars injected verbatim, exactly as before. No host scoping. |
263+
| `pat` | Personal Access Token, for HTTPS git remotes (GitHub/GitLab/Gitea). Requires a `host_scope`. |
264+
| `ssh_key` | SSH private key (PEM), for git over SSH. Requires a `host_scope`. |
265+
266+
`NULL`/`env` rows are never migrated — existing legacy credentials keep working unchanged. Typed adapters are opt-in per credential.
267+
268+
### User credentials vs binary/system credentials
269+
270+
Typed adapters operate on **user credentials**, not the binary-level env defaults:
271+
272+
- **Binary/system credentials** — the binary definition + its default env vars (and agent-grant overrides) described above. Shared across the binary's grants.
273+
- **User credentials** — per-user typed secrets stored in `secure_cli_user_credentials`, scoped to a single hostname.
274+
275+
Manage user credentials in the dashboard under **Settings → CLI Credentials → User Credentials**. Click **Add**, select the user, choose the credential type (`Personal Access Token` or `SSH Private Key`), enter the **Host Scope**, and paste the secret. The stored secret is AES-256-GCM encrypted and can never be read back — editing the row shows a `••••••••` placeholder; leaving the secret field blank preserves the stored value, typing a new value replaces it.
276+
277+
### The git adapter
278+
279+
The `git` adapter is the first shipped typed adapter. It injects credentials **only** for network subcommands:
280+
281+
```
282+
clone fetch pull push submodule
283+
```
284+
285+
Any other subcommand (`status`, `log`, `diff`, `commit`, `branch`, …) is a local operation and runs **uncredentialed** — no injection, no audit-log line.
286+
287+
**PAT flow.** The token is injected through environment variables, never on `argv`:
288+
289+
```
290+
GIT_CONFIG_COUNT=1
291+
GIT_CONFIG_KEY_0=http.https://<host>/.extraheader
292+
GIT_CONFIG_VALUE_0=Authorization: Bearer <token>
293+
```
294+
295+
Because the token lives in an env value (not a command-line flag), it never appears in `ps`, `/proc/<pid>/cmdline`, or shell history. The injected vars are scoped to the spawned `git` process only — GoClaw's own environment and sibling exec calls never see them.
296+
297+
**SSH flow.** The PEM key is written to a `0600`-mode tmpfile in the system temp dir (prefix `goclaw-gitkey-*`), and `GIT_SSH_COMMAND` is set to:
298+
299+
```
300+
ssh -i <tmpfile> -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new
301+
```
302+
303+
`StrictHostKeyChecking=accept-new` accepts unknown host keys on **first contact (TOFU)**. Pre-seed `~/.ssh/known_hosts` to close the window (see [Security Hardening](/deploy-security)). The tmpfile is removed after exec via a deferred cleanup. **Passphrase-protected SSH keys are rejected at save time** — re-export your key without a passphrase, or use a dedicated deploy key.
304+
305+
### Host scope
306+
307+
Both `pat` and `ssh_key` require a **`host_scope`** — the exact ASCII `host` or `host:port` the credential is valid for. It is normalized to lowercase ASCII (via `idna.ToASCII`) and matched **exactly**. v1 has **no wildcards**, and the port is part of the key:
308+
309+
| Stored `host_scope` | `github.com` | `api.github.com` | `github.com:8443` |
310+
|---------------------|:---:|:---:|:---:|
311+
| `github.com` ||||
312+
313+
If you run a self-hosted server on the scheme's default port (443 HTTPS, 22 SSH), omit the port; if on a non-default port, include it (e.g. `gitea.internal:8443`). When no stored credential matches the resolved remote host, the adapter falls through to the uncredentialed path and the remote rejects the operation if it requires auth.
314+
315+
### Env visibility: sensitive vs non-sensitive
316+
317+
Stored env entries now carry a `kind`. When the dashboard or admin API reads a credential back, the response masks values according to kind:
318+
319+
| `kind` | In API response |
320+
|--------|-----------------|
321+
| `sensitive` (default; legacy string maps decode here) | `value: null`, `masked: true` |
322+
| `value` (explicitly non-sensitive, e.g. a region or profile name) | plain value returned, `masked: false` |
323+
324+
This lets operators see non-secret context (e.g. `AWS_DEFAULT_REGION=us-west-2`) in the UI while secrets stay masked. Secrets are still never returned except via the dedicated `env:reveal` endpoint.
325+
326+
### Migrating from legacy env credentials
327+
328+
There is no forced migration. A row with `credential_type IS NULL` or `= 'env'` keeps emitting its env vars exactly as before. To upgrade a git credential, open the user-credentials dialog, pick **Personal Access Token** or **SSH Private Key**, enter the host scope, paste the secret, and save — the legacy row is replaced atomically.
329+
330+
### v1 limitations
331+
332+
- **No wildcard hosts** — one credential per exact `host[:port]`; `*.github.com` is not supported.
333+
- **No passphrase-protected SSH keys** — rejected at validation time.
334+
- **No sandbox propagation** — the adapter mutates the forked child's environment, which is incompatible with the bind-mount Docker sandbox path. Credentialed exec runs on the host only in v1.
335+
- **No host-key pinning** — SSH uses TOFU (`accept-new`); pre-seed `known_hosts`.
336+
337+
### Google Workspace CLI (gws)
338+
339+
GoClaw ships a `gws` preset for the Google Workspace CLI (`@googleworkspace/cli`).
340+
341+
**Availability.** The `gws` binary is preinstalled **only in the published `full` Docker image**. On `latest`/`base` images, install `@googleworkspace/cli` from the Packages page (requires a Node-enabled build, `ENABLE_NODE=true`; Node.js 18+).
342+
343+
**Credentials.** Create a SecureCLI credential from the `gws` preset and provide at least one auth source:
344+
345+
| Env var | Purpose |
346+
|---------|---------|
347+
| `GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` | Path to exported `gws` credentials or an OAuth credentials JSON file |
348+
| `GOOGLE_WORKSPACE_CLI_TOKEN` | Pre-obtained Google OAuth access token (optional) |
349+
| `GOOGLE_WORKSPACE_CLI_CLIENT_ID` | OAuth client ID for manual auth flows (optional) |
350+
| `GOOGLE_WORKSPACE_CLI_CLIENT_SECRET` | OAuth client secret for manual auth flows (optional) |
351+
352+
**Blocked commands.** The preset blocks interactive and credential-exporting auth flows:
353+
354+
```
355+
gws auth setup gws auth login gws auth export gws auth logout
356+
```
357+
358+
Run those flows outside agent execution, then store the resulting token or credentials-file path in SecureCLI.
359+
360+
**Usage.** Default usage is read-oriented. Use `--params` for query parameters, `--json` for request bodies, and `--page-all` for paginated reads:
361+
362+
```sh
363+
gws drive files list --params '{"pageSize": 10}'
364+
gws gmail users messages list --params '{"userId": "me", "maxResults": 10}'
365+
gws calendar events list --params '{"calendarId": "primary", "maxResults": 10}'
366+
```
367+
368+
> **Write caution.** Write commands can modify Workspace data. Keep the default preset read-oriented and create a separate, reviewed SecureCLI config for any approved write workflow.
369+
249370
## Common Patterns
250371

251372
### Allow only one agent to use a sensitive CLI tool
@@ -277,4 +398,4 @@ Update the grant: `{"enabled": false}`. The binary remains accessible to other a
277398
- [API Keys & RBAC](/api-keys-rbac)
278399
- [Security Hardening](/deploy-security)
279400

280-
<!-- goclaw-source: 392f0fda | updated: 2026-05-21 -->
401+
<!-- goclaw-source: d85bf171 | updated: 2026-06-07 -->

advanced/cost-tracking.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,69 @@ monthly budget exceeded ($5.02 / $5.00)
232232

233233
The check runs once per request, before any LLM calls. Sub-agent delegations run under their own agent records with their own budgets.
234234

235+
> The agent's `budget_monthly_cents` is also mirrored into the [AI Budget Usage Caps](/usage-quota) system as a managed `month`-window cost-cap policy, so the same limit is enforced there too.
236+
237+
---
238+
239+
## Model Pricing (Catalog & Overrides)
240+
241+
The static `telemetry.model_pricing` map above is the simplest way to price models. For dynamic, database-backed pricing — used by the [AI Budget Usage Caps](/usage-quota) cost caps — GoClaw also keeps a **pricing catalog** and per-tenant **overrides** in PostgreSQL (migration `000070`).
242+
243+
### OpenRouter catalog sync
244+
245+
GoClaw can pull a full model price list from [OpenRouter](https://openrouter.ai) and store it in the `usage_pricing_catalog` table. Each entry captures per-unit prices for input, output, cache read/write, reasoning, request, image, and web-search units, stored as high-precision decimals.
246+
247+
```bash
248+
curl -X POST -H "Authorization: Bearer your-token" \
249+
"http://localhost:8080/v1/model-pricing/sync-openrouter"
250+
```
251+
252+
This endpoint requires **master scope** (it updates the shared global catalog). The catalog is upserted by `model_id`, so re-running the sync refreshes prices in place.
253+
254+
### Per-model price overrides
255+
256+
When you want different prices for a specific tenant + provider + model (e.g. a negotiated rate, or a model OpenRouter doesn't list), set an **override** in the `usage_pricing_overrides` table. Overrides are scoped to one tenant and take priority over the global catalog.
257+
258+
```bash
259+
# List the synced catalog (optionally filter by model)
260+
curl -H "Authorization: Bearer your-token" \
261+
"http://localhost:8080/v1/model-pricing?model=claude-sonnet-4-5"
262+
263+
# Set a tenant override
264+
curl -X PUT -H "Authorization: Bearer your-token" \
265+
-H "Content-Type: application/json" \
266+
"http://localhost:8080/v1/model-pricing/overrides" \
267+
-d '{
268+
"provider_id": "22222222-2222-2222-2222-222222222222",
269+
"provider_type": "anthropic",
270+
"model_id": "claude-sonnet-4-5",
271+
"pricing": { "input": "0.000003", "output": "0.000015" }
272+
}'
273+
274+
# List / delete overrides
275+
curl -H "Authorization: Bearer your-token" \
276+
"http://localhost:8080/v1/model-pricing/overrides"
277+
curl -X DELETE -H "Authorization: Bearer your-token" \
278+
"http://localhost:8080/v1/model-pricing/overrides/{id}"
279+
```
280+
281+
| Method & path | Scope | Description |
282+
|---------------|-------|-------------|
283+
| `POST /v1/model-pricing/sync-openrouter` | master | Refresh the global price catalog from OpenRouter |
284+
| `GET /v1/model-pricing` | admin | List the synced catalog (`?model=`, `?limit=`) |
285+
| `PUT /v1/model-pricing/overrides` | tenant-admin | Create or update a per-tenant model price override |
286+
| `GET /v1/model-pricing/overrides` | admin | List overrides (`?provider_id=`) |
287+
| `DELETE /v1/model-pricing/overrides/{id}` | tenant-admin | Remove an override |
288+
289+
### How pricing resolves
290+
291+
When a cost cap needs a price for a model, GoClaw resolves in this order:
292+
293+
1. **Tenant override** for the matching provider + model (highest priority)
294+
2. **Global OpenRouter catalog** entry by `model_id` / canonical id
295+
296+
If neither is found, a cost-capped call is blocked with reason `pricing_unknown`. Prices are stored as decimal USD **per token/unit** and converted to micro-dollars when caps compute cost.
297+
235298
---
236299

237300
## Common Issues
@@ -252,4 +315,4 @@ The check runs once per request, before any LLM calls. Sub-agent delegations run
252315
- [Observability](/deploy-observability) — OpenTelemetry export for spans including cost fields
253316
- [Configuration Reference](/config-reference) — full `telemetry` config options
254317

255-
<!-- goclaw-source: 050aafc9 | updated: 2026-04-09 -->
318+
<!-- goclaw-source: d85bf171 | updated: 2026-06-07 -->

advanced/custom-tools.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ curl -X POST http://localhost:8080/v1/tools/custom \
6767
| `agent_id` | UUID | null | Scope to one agent; omit for global |
6868
| `enabled` | bool | true | Disable without deleting |
6969

70+
> **Three distinct timeouts — don't confuse them.** This per-custom-tool `timeout_seconds` (default 60) applies only to this one custom tool's command. It is separate from the host `exec` builtin tool's own `timeout_seconds` setting (also default 60, max 3600 — see [Tools Overview → Execution Timeout](/tools-overview)) and from the sandbox `sandbox_config.timeout_sec` (agent-level, default 300 — see [Sandbox](/sandbox)). A custom tool routed through the sandbox is still bounded by the sandbox timeout.
71+
7072
### Command templates
7173

7274
Use `{{.paramName}}` placeholders. GoClaw replaces them with shell-escaped values using simple string replacement — not Go's `text/template` engine, so template functions and pipelines are not supported. Every substituted value is single-quoted with embedded single-quotes escaped, so even a malicious LLM cannot break out of the argument.
@@ -250,4 +252,4 @@ Returns all documents that link to the specified path. Respects team boundaries
250252
- [Exec Approval](/exec-approval) — require human approval before commands run
251253
- [Sandbox](/sandbox) — run commands inside Docker for extra isolation
252254

253-
<!-- goclaw-source: 29457bb3 | updated: 2026-04-25 -->
255+
<!-- goclaw-source: d85bf171 | updated: 2026-06-07 -->

0 commit comments

Comments
 (0)