openclaw-observability 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -0
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +62 -61
- package/dist/index.js.map +1 -1
- package/dist/security/scanner.js +2 -2
- package/dist/security/scanner.js.map +1 -1
- package/dist/security/types.d.ts +1 -1
- package/dist/security/types.js +1 -1
- package/dist/storage/buffer.js +2 -2
- package/dist/storage/buffer.js.map +1 -1
- package/dist/storage/duckdb-local-writer.d.ts.map +1 -1
- package/dist/storage/duckdb-local-writer.js +65 -43
- package/dist/storage/duckdb-local-writer.js.map +1 -1
- package/dist/storage/mysql-writer.js +4 -4
- package/dist/storage/mysql-writer.js.map +1 -1
- package/dist/storage/schema.js +1 -1
- package/dist/storage/schema.js.map +1 -1
- package/dist/web/api.d.ts +50 -0
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +176 -0
- package/dist/web/api.js.map +1 -1
- package/dist/web/routes.d.ts +2 -2
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +30 -22
- package/dist/web/routes.js.map +1 -1
- package/dist/web/ui.d.ts +1 -1
- package/dist/web/ui.js +440 -6
- package/dist/web/ui.js.map +1 -1
- package/openclaw.plugin.json +13 -13
- package/package.json +5 -5
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# openclaw-observability
|
|
2
|
+
|
|
3
|
+
Full-stack observability plugin for [OpenClaw](https://openclaw.ai) — automatically records every LLM call, tool invocation, and agent lifecycle event into a local DuckDB or remote MySQL database, with a built-in web dashboard for tracing, analytics, and security auditing.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- **Full-Chain Tracing** — Captures 20 OpenClaw hooks covering LLM calls, tool invocations, agent lifecycle, session management, context compaction, and gateway events
|
|
8
|
+
- **Token Usage Tracking** — Automatically injects `stream_options` via fetch interception to capture prompt/completion tokens from any OpenAI-compatible API
|
|
9
|
+
- **Dual Storage Backend** — Local mode (embedded DuckDB, zero config) or Remote mode (MySQL/RDS)
|
|
10
|
+
- **Built-in Web Dashboard** — Session list, waterfall trace view, analytics charts, and security alerts — all served from the plugin with no external dependencies
|
|
11
|
+
- **Security Scanning** — Two-layer detection engine:
|
|
12
|
+
- **L1 Rule Engine** — Regex-based real-time scanning for secrets, dangerous commands, prompt injection, and sensitive file access
|
|
13
|
+
- **L2 Chain Detector** — Cross-action behavioral analysis (e.g., read credentials → exfiltrate data)
|
|
14
|
+
- **Automatic Redaction** — Masks API keys, passwords, tokens, and other sensitive fields before storage
|
|
15
|
+
- **Async Batch Buffer** — Configurable batch size and flush interval with overflow protection
|
|
16
|
+
|
|
17
|
+
## 📦 Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
openclaw plugins install openclaw-observability
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That's it. The plugin starts in **Local mode** by default — zero configuration required.
|
|
24
|
+
|
|
25
|
+
## 🚀 Quick Start
|
|
26
|
+
|
|
27
|
+
1. Install the plugin (see above)
|
|
28
|
+
2. Restart the gateway:
|
|
29
|
+
```bash
|
|
30
|
+
openclaw gateway restart
|
|
31
|
+
```
|
|
32
|
+
3. Open the dashboard:
|
|
33
|
+
```
|
|
34
|
+
http://localhost:18789/plugins/observability/
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 🖥️ Dashboard
|
|
38
|
+
|
|
39
|
+
The built-in web UI provides four tabs:
|
|
40
|
+
|
|
41
|
+
### Dashboard (Traces)
|
|
42
|
+
- Summary stats: total sessions, actions, tokens, average latency, success rate
|
|
43
|
+
- Full-text search across sessions, actions, and content
|
|
44
|
+
- Time range filtering (30 min → all time)
|
|
45
|
+
- Click any session to open the **waterfall trace view** with nested action timeline and detailed input/output inspector
|
|
46
|
+
|
|
47
|
+
### Analytics
|
|
48
|
+
- Overview KPIs: sessions, tokens (input/output), latency, active models, security alerts
|
|
49
|
+
- Activity over time chart (auto-switches between hourly and daily granularity)
|
|
50
|
+
- Token usage by model breakdown
|
|
51
|
+
- Action type distribution
|
|
52
|
+
- Top agents by session/token count
|
|
53
|
+
|
|
54
|
+
### Security
|
|
55
|
+
- Alert statistics by severity (Critical / Warning / Info)
|
|
56
|
+
- Filterable alert list with full-text search
|
|
57
|
+
- Alert lifecycle management: Acknowledge → Resolve → False Positive
|
|
58
|
+
- Direct link from alert to the offending action in the trace view
|
|
59
|
+
|
|
60
|
+
## ⚙️ Configuration
|
|
61
|
+
|
|
62
|
+
### Storage Modes
|
|
63
|
+
|
|
64
|
+
| Mode | Backend | Config Required |
|
|
65
|
+
|------|---------|----------------|
|
|
66
|
+
| `local` (default) | Embedded DuckDB | None |
|
|
67
|
+
| `remote` | MySQL 5.7+ / 8.x / RDS | Connection info |
|
|
68
|
+
|
|
69
|
+
### Remote Mode (MySQL)
|
|
70
|
+
|
|
71
|
+
Configure via OpenClaw Dashboard (Settings → Plugins → openclaw-observability Config) or edit `~/.openclaw/openclaw.json`:
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"plugins": {
|
|
76
|
+
"entries": {
|
|
77
|
+
"openclaw-observability": {
|
|
78
|
+
"enabled": true,
|
|
79
|
+
"config": {
|
|
80
|
+
"mode": "remote",
|
|
81
|
+
"mysql": {
|
|
82
|
+
"host": "your-mysql-host.com",
|
|
83
|
+
"port": 3306,
|
|
84
|
+
"user": "username",
|
|
85
|
+
"password": "password",
|
|
86
|
+
"database": "openclaw_observability"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### All Options
|
|
96
|
+
|
|
97
|
+
| Parameter | Default | Description |
|
|
98
|
+
|-----------|---------|-------------|
|
|
99
|
+
| `mode` | `local` | Storage mode: `local` (DuckDB) or `remote` (MySQL) |
|
|
100
|
+
| `duckdb.path` | `~/.openclaw/observability.duckdb` | DuckDB database file path (local mode only) |
|
|
101
|
+
| `mysql.host` | `localhost` | MySQL host address |
|
|
102
|
+
| `mysql.port` | `3306` | MySQL port |
|
|
103
|
+
| `mysql.user` | `root` | MySQL username |
|
|
104
|
+
| `mysql.password` | `""` | MySQL password |
|
|
105
|
+
| `mysql.database` | `openclaw_observability` | MySQL database name (auto-created) |
|
|
106
|
+
| `buffer.batchSize` | `50` | Records to accumulate before batch write |
|
|
107
|
+
| `buffer.flushIntervalMs` | `5000` | Auto-flush interval in ms |
|
|
108
|
+
| `redaction.enabled` | `true` | Automatically redact sensitive fields |
|
|
109
|
+
| `redaction.patterns` | `[api_key, password, ...]` | Field name patterns to redact (case-insensitive regex) |
|
|
110
|
+
| `security.enabled` | `true` | Enable real-time security scanning |
|
|
111
|
+
| `security.rules.*` | `true` | Toggle individual rule categories |
|
|
112
|
+
| `security.domainWhitelist` | `[]` | Domains excluded from external request alerts |
|
|
113
|
+
|
|
114
|
+
## 🔒 Security Rules
|
|
115
|
+
|
|
116
|
+
### L1 — Pattern-Based Detection
|
|
117
|
+
|
|
118
|
+
| Rule | Detection | Severity |
|
|
119
|
+
|------|-----------|----------|
|
|
120
|
+
| S001 | Alibaba Cloud AccessKey leak | Critical |
|
|
121
|
+
| S002 | AWS AccessKey leak | Critical |
|
|
122
|
+
| S003 | Private key (RSA/EC/SSH) leak | Critical |
|
|
123
|
+
| S004 | JWT token leak | Warning |
|
|
124
|
+
| S005 | Database connection string leak | Warning |
|
|
125
|
+
| S006 | Generic API key leak (OpenAI, GitHub PAT, etc.) | Warning |
|
|
126
|
+
| S007 | GCP service account key | Critical |
|
|
127
|
+
| S008 | Azure connection string leak | Critical |
|
|
128
|
+
| H001 | Dangerous shell commands (`rm -rf`, `curl \| sh`, etc.) | Critical |
|
|
129
|
+
| H002 | Sensitive file path access (`.ssh/`, `.env`, etc.) | Warning |
|
|
130
|
+
| H003 | Abnormally large data output (>100KB) | Warning |
|
|
131
|
+
| H004 | Bulk environment variable access | Warning |
|
|
132
|
+
| H005 | Privilege escalation (`sudo`, `su -`, `pkexec`) | Critical |
|
|
133
|
+
| T003 | External network request (non-whitelisted domain) | Warning |
|
|
134
|
+
| T005 | Prompt injection attack patterns | Warning/Critical |
|
|
135
|
+
|
|
136
|
+
### L2 — Behavioral Chain Detection
|
|
137
|
+
|
|
138
|
+
| Chain | Pattern | Severity |
|
|
139
|
+
|-------|---------|----------|
|
|
140
|
+
| CHAIN-001 | Read sensitive file → outbound network request | Critical |
|
|
141
|
+
| CHAIN-002 | Tool returns injection → executes sensitive operation | Critical |
|
|
142
|
+
|
|
143
|
+
## 🗄️ Database Schema
|
|
144
|
+
|
|
145
|
+
The plugin automatically creates three tables:
|
|
146
|
+
|
|
147
|
+
- **`audit_actions`** — Every recorded action (LLM call, tool invocation, etc.)
|
|
148
|
+
- **`audit_sessions`** — Aggregated session summaries (auto-updated)
|
|
149
|
+
- **`audit_alerts`** — Security alert records
|
|
150
|
+
|
|
151
|
+
Schema is identical between DuckDB and MySQL backends.
|
|
152
|
+
|
|
153
|
+
## 🏗️ Architecture
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
OpenClaw Gateway
|
|
157
|
+
│
|
|
158
|
+
├── Plugin Hooks (20 hooks)
|
|
159
|
+
│ ├── llm_input / llm_output
|
|
160
|
+
│ ├── before_tool_call / after_tool_call
|
|
161
|
+
│ ├── session_start / session_end
|
|
162
|
+
│ └── ... (agent, message, context, gateway)
|
|
163
|
+
│
|
|
164
|
+
├── Fetch Interceptor
|
|
165
|
+
│ └── Injects stream_options → Parses SSE usage
|
|
166
|
+
│
|
|
167
|
+
├── Security Scanner
|
|
168
|
+
│ ├── L1: Pattern rules (15 rules)
|
|
169
|
+
│ └── L2: Chain detector (2 chains)
|
|
170
|
+
│
|
|
171
|
+
├── Async Batch Buffer
|
|
172
|
+
│ └── batchSize / flushIntervalMs / overflow protection
|
|
173
|
+
│
|
|
174
|
+
├── Storage Writer
|
|
175
|
+
│ ├── DuckDBLocalWriter (local mode)
|
|
176
|
+
│ └── MySQLWriter (remote mode)
|
|
177
|
+
│
|
|
178
|
+
└── Web Dashboard
|
|
179
|
+
├── GET /plugins/observability/ → SPA UI
|
|
180
|
+
├── GET /plugins/observability/api/stats
|
|
181
|
+
├── GET /plugins/observability/api/sessions
|
|
182
|
+
├── GET /plugins/observability/api/actions
|
|
183
|
+
├── GET /plugins/observability/api/alerts
|
|
184
|
+
└── GET /plugins/observability/api/analytics
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 📄 License
|
|
188
|
+
|
|
189
|
+
MIT
|
package/dist/config.js
CHANGED
|
@@ -41,7 +41,7 @@ exports.resolveConfig = resolveConfig;
|
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
42
|
const os = __importStar(require("os"));
|
|
43
43
|
/** Default DuckDB path */
|
|
44
|
-
exports.DEFAULT_DUCKDB_PATH = path.join(os.homedir(), '.openclaw', '
|
|
44
|
+
exports.DEFAULT_DUCKDB_PATH = path.join(os.homedir(), '.openclaw', 'observability.duckdb');
|
|
45
45
|
/** Default config */
|
|
46
46
|
exports.DEFAULT_CONFIG = {
|
|
47
47
|
mode: 'local', // default: local DuckDB, zero config
|
|
@@ -50,14 +50,14 @@ exports.DEFAULT_CONFIG = {
|
|
|
50
50
|
port: 3306,
|
|
51
51
|
user: 'root',
|
|
52
52
|
password: '',
|
|
53
|
-
database: '
|
|
53
|
+
database: 'openclaw_observability',
|
|
54
54
|
},
|
|
55
55
|
duckdb: {
|
|
56
56
|
path: exports.DEFAULT_DUCKDB_PATH,
|
|
57
57
|
},
|
|
58
58
|
buffer: {
|
|
59
59
|
batchSize: 50,
|
|
60
|
-
flushIntervalMs:
|
|
60
|
+
flushIntervalMs: 5000,
|
|
61
61
|
},
|
|
62
62
|
redaction: {
|
|
63
63
|
enabled: true,
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgIH,sCAoCC;AAlKD,2CAA6B;AAC7B,uCAAyB;AA2DzB,0BAA0B;AACb,QAAA,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAC1C,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgIH,sCAoCC;AAlKD,2CAA6B;AAC7B,uCAAyB;AA2DzB,0BAA0B;AACb,QAAA,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAC1C,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,sBAAsB,CAClD,CAAC;AAEF,qBAAqB;AACR,QAAA,cAAc,GAAsB;IAC/C,IAAI,EAAE,OAAO,EAAG,qCAAqC;IACrD,KAAK,EAAE;QACL,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,EAAE;QACZ,QAAQ,EAAE,wBAAwB;KACnC;IACD,MAAM,EAAE;QACN,IAAI,EAAE,2BAAmB;KAC1B;IACD,MAAM,EAAE;QACN,SAAS,EAAE,EAAE;QACb,eAAe,EAAE,IAAI;KACtB;IACD,SAAS,EAAE;QACT,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE;YACR,SAAS;YACT,gBAAgB;YAChB,UAAU;YACV,QAAQ;YACR,cAAc;YACd,YAAY;YACZ,eAAe;YACf,cAAc;YACd,eAAe;YACf,YAAY;YACZ,YAAY;YACZ,eAAe;YACf,aAAa;YACb,YAAY;SACb;KACF;IACD,QAAQ,EAAE;QACR,OAAO,EAAE,IAAI;QACb,KAAK,EAAE;YACL,aAAa,EAAE,IAAI;YACnB,WAAW,EAAE,IAAI;YACjB,eAAe,EAAE,IAAI;YACrB,cAAc,EAAE,IAAI;SACrB;QACD,eAAe,EAAE,EAAE;KACpB;CACF,CAAC;AAEF;;;;;GAKG;AACH,SAAS,SAAS,CAAC,UAAsC;IACvD,IAAI,UAAU,CAAC,IAAI;QAAE,OAAO,UAAU,CAAC,IAAI,CAAC;IAC5C,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC;IAC3B,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,QAAQ;QAAE,OAAO,QAAQ,CAAC;IACzE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAgB,aAAa,CAC3B,UAAkD;IAElD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,EAAE,GAAG,sBAAc,EAAE,CAAC;IAC/B,CAAC;IAED,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,UAAU,CAAC;QAC3B,KAAK,EAAE;YACL,GAAG,sBAAc,CAAC,KAAK;YACvB,GAAG,UAAU,CAAC,KAAK;SACpB;QACD,MAAM,EAAE;YACN,GAAG,sBAAc,CAAC,MAAM;YACxB,GAAG,UAAU,CAAC,MAAM;SACrB;QACD,MAAM,EAAE;YACN,GAAG,sBAAc,CAAC,MAAM;YACxB,GAAG,UAAU,CAAC,MAAM;SACrB;QACD,SAAS,EAAE;YACT,GAAG,sBAAc,CAAC,SAAS;YAC3B,GAAG,UAAU,CAAC,SAAS;YACvB,QAAQ,EAAE,UAAU,CAAC,SAAS,EAAE,QAAQ,IAAI,sBAAc,CAAC,SAAS,CAAC,QAAQ;SAC9E;QACD,QAAQ,EAAE;YACR,GAAG,sBAAc,CAAC,QAAQ;YAC1B,GAAG,UAAU,CAAC,QAAQ;YACtB,KAAK,EAAE;gBACL,GAAG,sBAAc,CAAC,QAAQ,CAAC,KAAK;gBAChC,GAAG,CAAC,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC;aAChC;YACD,eAAe,EAAE,UAAU,CAAC,QAAQ,EAAE,eAAe,IAAI,sBAAc,CAAC,QAAQ,CAAC,eAAe;SACjG;KACF,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,iBAAiB,EAAiB,MAAM,UAAU,CAAC;AAe5D,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1C,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;IACjE,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,WAAW,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,WAAW,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;QACzI,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;QAC3B,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;QAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;KAC3B,KAAK,IAAI,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAmbD,iBAAS,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG;IAAE,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,iBAAiB,EAAiB,MAAM,UAAU,CAAC;AAe5D,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC1C,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;IACjE,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,WAAW,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,WAAW,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,OAAO,GAAG,IAAI,CAAC;QACzI,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;QAC3B,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;QAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;KAC3B,KAAK,IAAI,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAmbD,iBAAS,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG;IAAE,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAu+BrE;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC;AACpB,eAAe,QAAQ,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* OpenClaw
|
|
3
|
+
* OpenClaw observability plugin entry point
|
|
4
4
|
* Registers all 24 Plugin Hooks, assembles capture -> buffer -> writer pipeline
|
|
5
5
|
*
|
|
6
6
|
* OpenClaw hooks:
|
|
@@ -164,14 +164,14 @@ function installFetchInterceptor() {
|
|
|
164
164
|
});
|
|
165
165
|
};
|
|
166
166
|
fetchInterceptorInstalled = true;
|
|
167
|
-
console.log('[
|
|
167
|
+
console.log('[openclaw-observability] Fetch interceptor installed for token usage tracking');
|
|
168
168
|
}
|
|
169
169
|
function uninstallFetchInterceptor() {
|
|
170
170
|
if (originalFetch) {
|
|
171
171
|
globalThis.fetch = originalFetch;
|
|
172
172
|
originalFetch = null;
|
|
173
173
|
fetchInterceptorInstalled = false;
|
|
174
|
-
console.log('[
|
|
174
|
+
console.log('[openclaw-observability] Fetch interceptor uninstalled');
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
@@ -351,7 +351,7 @@ function makeAction(sessionId, overrides) {
|
|
|
351
351
|
function activate(api) {
|
|
352
352
|
const rawConfig = (api.pluginConfig || api.config || {});
|
|
353
353
|
const config = (0, config_1.resolveConfig)(rawConfig);
|
|
354
|
-
console.log(`[
|
|
354
|
+
console.log(`[openclaw-observability] Config resolved: mode=${config.mode} ` +
|
|
355
355
|
(config.mode === 'remote'
|
|
356
356
|
? `mysql.host=${config.mysql.host} mysql.database=${config.mysql.database}`
|
|
357
357
|
: `duckdb.path=${config.duckdb.path}`));
|
|
@@ -366,6 +366,7 @@ function activate(api) {
|
|
|
366
366
|
}
|
|
367
367
|
const buffer = new buffer_1.AsyncBatchBuffer(config.buffer, (entries) => writer.writeBatch(entries));
|
|
368
368
|
// Install fetch interceptor for token usage tracking
|
|
369
|
+
// NOTE: Only install if not in a restart loop (check via env flag)
|
|
369
370
|
installFetchInterceptor();
|
|
370
371
|
// Security scanner
|
|
371
372
|
const securityConfig = (0, scanner_1.resolveSecurityConfig)(config.security);
|
|
@@ -385,8 +386,8 @@ function activate(api) {
|
|
|
385
386
|
.initialize()
|
|
386
387
|
.then(() => {
|
|
387
388
|
buffer.start();
|
|
388
|
-
// Start security alert flush timer (
|
|
389
|
-
alertFlushTimer = setInterval(() => void flushAlerts(),
|
|
389
|
+
// Start security alert flush timer (same interval as buffer flush)
|
|
390
|
+
alertFlushTimer = setInterval(() => void flushAlerts(), config.buffer.flushIntervalMs);
|
|
390
391
|
if (alertFlushTimer && typeof alertFlushTimer === 'object' && 'unref' in alertFlushTimer) {
|
|
391
392
|
alertFlushTimer.unref();
|
|
392
393
|
}
|
|
@@ -395,22 +396,22 @@ function activate(api) {
|
|
|
395
396
|
if (mapCleanupTimer && typeof mapCleanupTimer === 'object' && 'unref' in mapCleanupTimer) {
|
|
396
397
|
mapCleanupTimer.unref();
|
|
397
398
|
}
|
|
398
|
-
console.log(`[
|
|
399
|
+
console.log(`[openclaw-observability] Plugin activated (mode=${config.mode}, security scanner enabled)`);
|
|
399
400
|
})
|
|
400
401
|
.catch((error) => {
|
|
401
|
-
console.error('[
|
|
402
|
+
console.error('[openclaw-observability] Failed to initialize database:', error);
|
|
402
403
|
});
|
|
403
404
|
}
|
|
404
405
|
else {
|
|
405
|
-
console.log('[
|
|
406
|
-
'Please configure via Dashboard (Settings → Plugins →
|
|
406
|
+
console.log('[openclaw-observability] MySQL not configured yet — skipping database connection. ' +
|
|
407
|
+
'Please configure via Dashboard (Settings → Plugins → openclaw-observability Config) then restart.');
|
|
407
408
|
}
|
|
408
|
-
// Register
|
|
409
|
+
// Register observability panel Web UI (mounted on Gateway HTTP server)
|
|
409
410
|
if (typeof api.registerHttpRoute === 'function') {
|
|
410
411
|
(0, routes_1.registerAuditRoutes)(api.registerHttpRoute.bind(api), writer);
|
|
411
412
|
}
|
|
412
413
|
else {
|
|
413
|
-
console.warn('[
|
|
414
|
+
console.warn('[openclaw-observability] registerHttpRoute not available — observability UI disabled');
|
|
414
415
|
}
|
|
415
416
|
// ====================== Helper functions ======================
|
|
416
417
|
/** Record action, update session stats, and run security scan */
|
|
@@ -427,12 +428,12 @@ function activate(api) {
|
|
|
427
428
|
if (alertBuffer.length + alerts.length > MAX_ALERT_BUFFER) {
|
|
428
429
|
const overflow = alertBuffer.length + alerts.length - MAX_ALERT_BUFFER;
|
|
429
430
|
alertBuffer.splice(0, overflow);
|
|
430
|
-
console.warn(`[
|
|
431
|
+
console.warn(`[openclaw-observability-security] Alert buffer overflow, dropped ${overflow} oldest alerts`);
|
|
431
432
|
}
|
|
432
433
|
alertBuffer.push(...alerts);
|
|
433
434
|
for (const alert of alerts) {
|
|
434
435
|
const icon = alert.severity === 'critical' ? '🔴' : alert.severity === 'warn' ? '🟡' : 'ℹ️';
|
|
435
|
-
console.log(`[
|
|
436
|
+
console.log(`[openclaw-observability-security] ${icon} ${alert.severity.toUpperCase()} ${alert.ruleId}: ${alert.ruleName} — ${alert.finding} (session=${alert.sessionId})`);
|
|
436
437
|
}
|
|
437
438
|
// Flush immediately on CRITICAL alerts
|
|
438
439
|
if (alerts.some(a => a.severity === 'critical')) {
|
|
@@ -441,7 +442,7 @@ function activate(api) {
|
|
|
441
442
|
}
|
|
442
443
|
}
|
|
443
444
|
catch (err) {
|
|
444
|
-
console.error('[
|
|
445
|
+
console.error('[openclaw-observability-security] Scan error:', err);
|
|
445
446
|
}
|
|
446
447
|
}
|
|
447
448
|
}
|
|
@@ -454,7 +455,7 @@ function activate(api) {
|
|
|
454
455
|
await writer.writeAlerts(batch);
|
|
455
456
|
}
|
|
456
457
|
catch (err) {
|
|
457
|
-
console.error('[
|
|
458
|
+
console.error('[openclaw-observability-security] Failed to write alerts:', err);
|
|
458
459
|
// Put back into buffer
|
|
459
460
|
alertBuffer.unshift(...batch);
|
|
460
461
|
}
|
|
@@ -491,10 +492,10 @@ function activate(api) {
|
|
|
491
492
|
userId: c?.agentId,
|
|
492
493
|
inputParams: redactor.redact({ prompt: truncate(e.prompt) }),
|
|
493
494
|
}));
|
|
494
|
-
console.log(`[
|
|
495
|
+
console.log(`[openclaw-observability] before_model_resolve: session=${sid}`);
|
|
495
496
|
}
|
|
496
497
|
catch (err) {
|
|
497
|
-
console.error('[
|
|
498
|
+
console.error('[openclaw-observability] Error in before_model_resolve:', err);
|
|
498
499
|
}
|
|
499
500
|
});
|
|
500
501
|
// before_prompt_build: before prompt construction
|
|
@@ -512,10 +513,10 @@ function activate(api) {
|
|
|
512
513
|
messageCount: Array.isArray(e.messages) ? e.messages.length : 0,
|
|
513
514
|
}),
|
|
514
515
|
}));
|
|
515
|
-
console.log(`[
|
|
516
|
+
console.log(`[openclaw-observability] before_prompt_build: session=${sid} msgs=${Array.isArray(e.messages) ? e.messages.length : '?'}`);
|
|
516
517
|
}
|
|
517
518
|
catch (err) {
|
|
518
|
-
console.error('[
|
|
519
|
+
console.error('[openclaw-observability] Error in before_prompt_build:', err);
|
|
519
520
|
}
|
|
520
521
|
});
|
|
521
522
|
// before_agent_start: skipped — legacy hook that fires twice
|
|
@@ -537,10 +538,10 @@ function activate(api) {
|
|
|
537
538
|
}),
|
|
538
539
|
durationMs: e.durationMs ?? null,
|
|
539
540
|
}));
|
|
540
|
-
console.log(`[
|
|
541
|
+
console.log(`[openclaw-observability] agent_end: session=${sid} success=${e.success} duration=${e.durationMs}ms`);
|
|
541
542
|
}
|
|
542
543
|
catch (err) {
|
|
543
|
-
console.error('[
|
|
544
|
+
console.error('[openclaw-observability] Error in agent_end:', err);
|
|
544
545
|
}
|
|
545
546
|
});
|
|
546
547
|
// =====================================================================
|
|
@@ -566,13 +567,13 @@ function activate(api) {
|
|
|
566
567
|
if (c?.agentId)
|
|
567
568
|
sctx.userId = c.agentId;
|
|
568
569
|
sctx.modelName = `${e.provider}/${e.model}`;
|
|
569
|
-
console.log(`[
|
|
570
|
+
console.log(`[openclaw-observability] llm_input: session=${sid} model=${e.provider}/${e.model} runId=${e.runId} images=${e.imagesCount ?? 0} mediaParts=${media.length}`);
|
|
570
571
|
}
|
|
571
572
|
catch (err) {
|
|
572
|
-
console.error('[
|
|
573
|
+
console.error('[openclaw-observability] Error in llm_input:', err);
|
|
573
574
|
}
|
|
574
575
|
});
|
|
575
|
-
// llm_output: after LLM call — primary
|
|
576
|
+
// llm_output: after LLM call — primary observability record point
|
|
576
577
|
api.on('llm_output', (event, ctx) => {
|
|
577
578
|
try {
|
|
578
579
|
const e = event;
|
|
@@ -653,10 +654,10 @@ function activate(api) {
|
|
|
653
654
|
durationMs,
|
|
654
655
|
userId: c?.agentId,
|
|
655
656
|
}), totalTokens);
|
|
656
|
-
console.log(`[
|
|
657
|
+
console.log(`[openclaw-observability] llm_output: session=${e.sessionId} model=${e.model} duration=${durationMs}ms tokens=${promptTokens ?? '?'}/${completionTokens ?? '?'} cache_r=${cacheRead ?? '-'} cache_w=${cacheWrite ?? '-'}`);
|
|
657
658
|
}
|
|
658
659
|
catch (err) {
|
|
659
|
-
console.error('[
|
|
660
|
+
console.error('[openclaw-observability] Error in llm_output:', err);
|
|
660
661
|
}
|
|
661
662
|
});
|
|
662
663
|
// =====================================================================
|
|
@@ -678,10 +679,10 @@ function activate(api) {
|
|
|
678
679
|
tokenCount: e.tokenCount,
|
|
679
680
|
}),
|
|
680
681
|
}));
|
|
681
|
-
console.log(`[
|
|
682
|
+
console.log(`[openclaw-observability] before_compaction: session=${sid} msgs=${e.messageCount} tokens=${e.tokenCount}`);
|
|
682
683
|
}
|
|
683
684
|
catch (err) {
|
|
684
|
-
console.error('[
|
|
685
|
+
console.error('[openclaw-observability] Error in before_compaction:', err);
|
|
685
686
|
}
|
|
686
687
|
});
|
|
687
688
|
// after_compaction: after context compaction
|
|
@@ -700,10 +701,10 @@ function activate(api) {
|
|
|
700
701
|
tokenCount: e.tokenCount,
|
|
701
702
|
}),
|
|
702
703
|
}));
|
|
703
|
-
console.log(`[
|
|
704
|
+
console.log(`[openclaw-observability] after_compaction: session=${sid} compacted=${e.compactedCount}`);
|
|
704
705
|
}
|
|
705
706
|
catch (err) {
|
|
706
|
-
console.error('[
|
|
707
|
+
console.error('[openclaw-observability] Error in after_compaction:', err);
|
|
707
708
|
}
|
|
708
709
|
});
|
|
709
710
|
// before_reset: before session reset
|
|
@@ -721,10 +722,10 @@ function activate(api) {
|
|
|
721
722
|
messageCount: Array.isArray(e.messages) ? e.messages.length : 0,
|
|
722
723
|
}),
|
|
723
724
|
}));
|
|
724
|
-
console.log(`[
|
|
725
|
+
console.log(`[openclaw-observability] before_reset: session=${sid} reason=${e.reason}`);
|
|
725
726
|
}
|
|
726
727
|
catch (err) {
|
|
727
|
-
console.error('[
|
|
728
|
+
console.error('[openclaw-observability] Error in before_reset:', err);
|
|
728
729
|
}
|
|
729
730
|
});
|
|
730
731
|
// =====================================================================
|
|
@@ -750,10 +751,10 @@ function activate(api) {
|
|
|
750
751
|
}),
|
|
751
752
|
createdAt: new Date(e.timestamp || Date.now()),
|
|
752
753
|
}));
|
|
753
|
-
console.log(`[
|
|
754
|
+
console.log(`[openclaw-observability] message_received: from=${e.from} channel=${c?.channelId} len=${e.content?.length}`);
|
|
754
755
|
}
|
|
755
756
|
catch (err) {
|
|
756
|
-
console.error('[
|
|
757
|
+
console.error('[openclaw-observability] Error in message_received:', err);
|
|
757
758
|
}
|
|
758
759
|
});
|
|
759
760
|
// message_sending: before message send (interceptable/modifiable)
|
|
@@ -774,10 +775,10 @@ function activate(api) {
|
|
|
774
775
|
channelId: c?.channelId,
|
|
775
776
|
}),
|
|
776
777
|
}));
|
|
777
|
-
console.log(`[
|
|
778
|
+
console.log(`[openclaw-observability] message_sending: to=${e.to} channel=${c?.channelId}`);
|
|
778
779
|
}
|
|
779
780
|
catch (err) {
|
|
780
|
-
console.error('[
|
|
781
|
+
console.error('[openclaw-observability] Error in message_sending:', err);
|
|
781
782
|
}
|
|
782
783
|
});
|
|
783
784
|
// message_sent: message sent
|
|
@@ -800,13 +801,13 @@ function activate(api) {
|
|
|
800
801
|
channelId: c?.channelId,
|
|
801
802
|
}),
|
|
802
803
|
}));
|
|
803
|
-
console.log(`[
|
|
804
|
+
console.log(`[openclaw-observability] message_sent: to=${e.to} success=${e.success} channel=${c?.channelId}`);
|
|
804
805
|
}
|
|
805
806
|
catch (err) {
|
|
806
|
-
console.error('[
|
|
807
|
+
console.error('[openclaw-observability] Error in message_sent:', err);
|
|
807
808
|
}
|
|
808
809
|
});
|
|
809
|
-
// before_message_write: skipped — message content already fully recorded in llm_input/llm_output, no additional
|
|
810
|
+
// before_message_write: skipped — message content already fully recorded in llm_input/llm_output, no additional observability value
|
|
810
811
|
// =====================================================================
|
|
811
812
|
// 5. Tool calls
|
|
812
813
|
// =====================================================================
|
|
@@ -825,13 +826,13 @@ function activate(api) {
|
|
|
825
826
|
const sctx = getSessionCtx(sid);
|
|
826
827
|
if (c?.agentId)
|
|
827
828
|
sctx.userId = c.agentId;
|
|
828
|
-
console.log(`[
|
|
829
|
+
console.log(`[openclaw-observability] before_tool_call: tool=${e.toolName} session=${sid} callId=${callId}`);
|
|
829
830
|
}
|
|
830
831
|
catch (err) {
|
|
831
|
-
console.error('[
|
|
832
|
+
console.error('[openclaw-observability] Error in before_tool_call:', err);
|
|
832
833
|
}
|
|
833
834
|
});
|
|
834
|
-
// after_tool_call: after tool call — generate
|
|
835
|
+
// after_tool_call: after tool call — generate observability record
|
|
835
836
|
api.on('after_tool_call', (event, ctx) => {
|
|
836
837
|
try {
|
|
837
838
|
const e = event;
|
|
@@ -872,7 +873,7 @@ function activate(api) {
|
|
|
872
873
|
}
|
|
873
874
|
}
|
|
874
875
|
if (toolMedia.length > 0) {
|
|
875
|
-
// Don't write full base64 to
|
|
876
|
+
// Don't write full base64 to observability record, only record metadata
|
|
876
877
|
const sanitized = { ...raw };
|
|
877
878
|
sanitized.content = resultContent.map((part) => {
|
|
878
879
|
if (part && typeof part === 'object' && part.type === 'image') {
|
|
@@ -906,10 +907,10 @@ function activate(api) {
|
|
|
906
907
|
durationMs,
|
|
907
908
|
userId: c?.agentId,
|
|
908
909
|
}));
|
|
909
|
-
console.log(`[
|
|
910
|
+
console.log(`[openclaw-observability] after_tool_call: tool=${toolName} session=${sessionId} duration=${durationMs}ms`);
|
|
910
911
|
}
|
|
911
912
|
catch (err) {
|
|
912
|
-
console.error('[
|
|
913
|
+
console.error('[openclaw-observability] Error in after_tool_call:', err);
|
|
913
914
|
}
|
|
914
915
|
});
|
|
915
916
|
// tool_result_persist: tool result persistence (sync hook)
|
|
@@ -932,7 +933,7 @@ function activate(api) {
|
|
|
932
933
|
}));
|
|
933
934
|
}
|
|
934
935
|
catch (err) {
|
|
935
|
-
console.error('[
|
|
936
|
+
console.error('[openclaw-observability] Error in tool_result_persist:', err);
|
|
936
937
|
}
|
|
937
938
|
});
|
|
938
939
|
// =====================================================================
|
|
@@ -955,10 +956,10 @@ function activate(api) {
|
|
|
955
956
|
resumedFrom: e.resumedFrom,
|
|
956
957
|
}),
|
|
957
958
|
}));
|
|
958
|
-
console.log(`[
|
|
959
|
+
console.log(`[openclaw-observability] session_start: session=${sid} resumed=${e.resumedFrom}`);
|
|
959
960
|
}
|
|
960
961
|
catch (err) {
|
|
961
|
-
console.error('[
|
|
962
|
+
console.error('[openclaw-observability] Error in session_start:', err);
|
|
962
963
|
}
|
|
963
964
|
});
|
|
964
965
|
// session_end: session ended — force flush
|
|
@@ -980,10 +981,10 @@ function activate(api) {
|
|
|
980
981
|
void buffer.flush();
|
|
981
982
|
// Clean up session context
|
|
982
983
|
sessionContextMap.delete(sid);
|
|
983
|
-
console.log(`[
|
|
984
|
+
console.log(`[openclaw-observability] session_end: session=${sid} msgs=${e.messageCount} duration=${e.durationMs}ms`);
|
|
984
985
|
}
|
|
985
986
|
catch (err) {
|
|
986
|
-
console.error('[
|
|
987
|
+
console.error('[openclaw-observability] Error in session_end:', err);
|
|
987
988
|
}
|
|
988
989
|
});
|
|
989
990
|
// =====================================================================
|
|
@@ -1010,10 +1011,10 @@ function activate(api) {
|
|
|
1010
1011
|
requesterSessionKey: c?.requesterSessionKey,
|
|
1011
1012
|
}),
|
|
1012
1013
|
}));
|
|
1013
|
-
console.log(`[
|
|
1014
|
+
console.log(`[openclaw-observability] subagent_spawned: agent=${e.agentId} child=${e.childSessionKey}`);
|
|
1014
1015
|
}
|
|
1015
1016
|
catch (err) {
|
|
1016
|
-
console.error('[
|
|
1017
|
+
console.error('[openclaw-observability] Error in subagent_spawned:', err);
|
|
1017
1018
|
}
|
|
1018
1019
|
});
|
|
1019
1020
|
// subagent_ended: sub-agent ended
|
|
@@ -1033,10 +1034,10 @@ function activate(api) {
|
|
|
1033
1034
|
error: e.error,
|
|
1034
1035
|
}),
|
|
1035
1036
|
}));
|
|
1036
|
-
console.log(`[
|
|
1037
|
+
console.log(`[openclaw-observability] subagent_ended: target=${e.targetSessionKey} outcome=${e.outcome}`);
|
|
1037
1038
|
}
|
|
1038
1039
|
catch (err) {
|
|
1039
|
-
console.error('[
|
|
1040
|
+
console.error('[openclaw-observability] Error in subagent_ended:', err);
|
|
1040
1041
|
}
|
|
1041
1042
|
});
|
|
1042
1043
|
// =====================================================================
|
|
@@ -1053,10 +1054,10 @@ function activate(api) {
|
|
|
1053
1054
|
userId: 'system',
|
|
1054
1055
|
inputParams: { port: e.port },
|
|
1055
1056
|
}));
|
|
1056
|
-
console.log(`[
|
|
1057
|
+
console.log(`[openclaw-observability] gateway_start: port=${e.port}`);
|
|
1057
1058
|
}
|
|
1058
1059
|
catch (err) {
|
|
1059
|
-
console.error('[
|
|
1060
|
+
console.error('[openclaw-observability] Error in gateway_start:', err);
|
|
1060
1061
|
}
|
|
1061
1062
|
});
|
|
1062
1063
|
// gateway_stop: gateway stopped
|
|
@@ -1072,10 +1073,10 @@ function activate(api) {
|
|
|
1072
1073
|
}));
|
|
1073
1074
|
// Force flush on gateway stop
|
|
1074
1075
|
void buffer.flush();
|
|
1075
|
-
console.log(`[
|
|
1076
|
+
console.log(`[openclaw-observability] gateway_stop: reason=${e.reason}`);
|
|
1076
1077
|
}
|
|
1077
1078
|
catch (err) {
|
|
1078
|
-
console.error('[
|
|
1079
|
+
console.error('[openclaw-observability] Error in gateway_stop:', err);
|
|
1079
1080
|
}
|
|
1080
1081
|
});
|
|
1081
1082
|
// =====================================================================
|
|
@@ -1083,7 +1084,7 @@ function activate(api) {
|
|
|
1083
1084
|
// =====================================================================
|
|
1084
1085
|
return {
|
|
1085
1086
|
deactivate: async () => {
|
|
1086
|
-
console.log('[
|
|
1087
|
+
console.log('[openclaw-observability] Deactivating plugin...');
|
|
1087
1088
|
// Stop timers
|
|
1088
1089
|
if (alertFlushTimer) {
|
|
1089
1090
|
clearInterval(alertFlushTimer);
|
|
@@ -1106,7 +1107,7 @@ function activate(api) {
|
|
|
1106
1107
|
sessionContextMap.clear();
|
|
1107
1108
|
securityScanner.reset();
|
|
1108
1109
|
lastFallbackSessionId = 'unknown';
|
|
1109
|
-
console.log('[
|
|
1110
|
+
console.log('[openclaw-observability] Plugin deactivated');
|
|
1110
1111
|
},
|
|
1111
1112
|
};
|
|
1112
1113
|
}
|