openclaw-observability 2026.4.1 → 2026.4.21
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 +4 -4
- package/dist/cloud/api-key-auth.d.ts.map +1 -1
- package/dist/cloud/api-key-auth.js +4 -9
- package/dist/cloud/api-key-auth.js.map +1 -1
- package/dist/cloud/types.d.ts +2 -3
- package/dist/cloud/types.d.ts.map +1 -1
- package/dist/config.d.ts +34 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +35 -2
- package/dist/config.js.map +1 -1
- package/dist/gateway/register-observability-gateway.d.ts +6 -4
- package/dist/gateway/register-observability-gateway.d.ts.map +1 -1
- package/dist/gateway/register-observability-gateway.js +105 -2
- package/dist/gateway/register-observability-gateway.js.map +1 -1
- package/dist/hooks/messages.d.ts +4 -3
- package/dist/hooks/messages.d.ts.map +1 -1
- package/dist/hooks/messages.js +23 -1
- package/dist/hooks/messages.js.map +1 -1
- package/dist/hooks/session.d.ts +4 -3
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +9 -4
- package/dist/hooks/session.js.map +1 -1
- package/dist/hooks/subagent.d.ts +4 -3
- package/dist/hooks/subagent.d.ts.map +1 -1
- package/dist/hooks/subagent.js +4 -1
- package/dist/hooks/subagent.js.map +1 -1
- package/dist/hooks/tools.d.ts +3 -3
- package/dist/hooks/tools.d.ts.map +1 -1
- package/dist/hooks/tools.js +122 -4
- package/dist/hooks/tools.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +472 -118
- package/dist/index.js.map +1 -1
- package/dist/llm/replay-runtime.d.ts +16 -0
- package/dist/llm/replay-runtime.d.ts.map +1 -0
- package/dist/llm/replay-runtime.js +596 -0
- package/dist/llm/replay-runtime.js.map +1 -0
- package/dist/llm/replay.d.ts +3 -0
- package/dist/llm/replay.d.ts.map +1 -1
- package/dist/llm/replay.js.map +1 -1
- package/dist/redaction.d.ts +1 -1
- package/dist/redaction.js +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +3 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/session-context.d.ts +4 -3
- package/dist/runtime/session-context.d.ts.map +1 -1
- package/dist/runtime/session-context.js +37 -17
- package/dist/runtime/session-context.js.map +1 -1
- package/dist/security/chain-detector.d.ts +4 -4
- package/dist/security/chain-detector.d.ts.map +1 -1
- package/dist/security/chain-detector.js.map +1 -1
- package/dist/security/rules.d.ts +2 -2
- package/dist/security/rules.d.ts.map +1 -1
- package/dist/security/rules.js +9 -2
- package/dist/security/rules.js.map +1 -1
- package/dist/security/scanner.d.ts +8 -3
- package/dist/security/scanner.d.ts.map +1 -1
- package/dist/security/scanner.js +85 -7
- package/dist/security/scanner.js.map +1 -1
- package/dist/security/types.d.ts +3 -0
- package/dist/security/types.d.ts.map +1 -1
- package/dist/storage/buffer.d.ts +7 -7
- package/dist/storage/buffer.d.ts.map +1 -1
- package/dist/storage/buffer.js +2 -2
- package/dist/storage/buffer.js.map +1 -1
- package/dist/storage/cloud-export-writer.d.ts +23 -0
- package/dist/storage/cloud-export-writer.d.ts.map +1 -0
- package/dist/storage/cloud-export-writer.js +202 -0
- package/dist/storage/cloud-export-writer.js.map +1 -0
- package/dist/storage/duckdb-local-writer.d.ts +19 -3
- package/dist/storage/duckdb-local-writer.d.ts.map +1 -1
- package/dist/storage/duckdb-local-writer.js +261 -81
- package/dist/storage/duckdb-local-writer.js.map +1 -1
- package/dist/storage/duckdb-observability-forwarder.d.ts +16 -0
- package/dist/storage/duckdb-observability-forwarder.d.ts.map +1 -0
- package/dist/storage/duckdb-observability-forwarder.js +289 -0
- package/dist/storage/duckdb-observability-forwarder.js.map +1 -0
- package/dist/storage/mysql-writer.d.ts +35 -6
- package/dist/storage/mysql-writer.d.ts.map +1 -1
- package/dist/storage/mysql-writer.js +251 -32
- package/dist/storage/mysql-writer.js.map +1 -1
- package/dist/storage/schema.d.ts +2 -2
- package/dist/storage/schema.d.ts.map +1 -1
- package/dist/storage/schema.js +181 -53
- package/dist/storage/schema.js.map +1 -1
- package/dist/storage/structured-model.d.ts +11 -2
- package/dist/storage/structured-model.d.ts.map +1 -1
- package/dist/storage/structured-model.js +183 -5
- package/dist/storage/structured-model.js.map +1 -1
- package/dist/storage/writer.d.ts +14 -2
- package/dist/storage/writer.d.ts.map +1 -1
- package/dist/types.d.ts +28 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -1
- package/dist/web/api.d.ts +80 -2
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +917 -113
- package/dist/web/api.js.map +1 -1
- package/dist/web/routes.d.ts +22 -2
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +264 -21
- package/dist/web/routes.js.map +1 -1
- package/dist/web/ui.d.ts +3 -1
- package/dist/web/ui.d.ts.map +1 -1
- package/dist/web/ui.js +2678 -633
- package/dist/web/ui.js.map +1 -1
- package/openclaw.plugin.json +145 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -53,6 +53,7 @@ const types_1 = require("./types");
|
|
|
53
53
|
const redaction_1 = require("./redaction");
|
|
54
54
|
const mysql_writer_1 = require("./storage/mysql-writer");
|
|
55
55
|
const duckdb_local_writer_1 = require("./storage/duckdb-local-writer");
|
|
56
|
+
const cloud_export_writer_1 = require("./storage/cloud-export-writer");
|
|
56
57
|
const buffer_1 = require("./storage/buffer");
|
|
57
58
|
const routes_1 = require("./web/routes");
|
|
58
59
|
const scanner_1 = require("./security/scanner");
|
|
@@ -62,6 +63,7 @@ const os = __importStar(require("node:os"));
|
|
|
62
63
|
const node_crypto_1 = require("node:crypto");
|
|
63
64
|
const runtime_1 = require("./runtime");
|
|
64
65
|
const register_observability_gateway_1 = require("./gateway/register-observability-gateway");
|
|
66
|
+
const replay_runtime_1 = require("./llm/replay-runtime");
|
|
65
67
|
const hooks_1 = require("./hooks");
|
|
66
68
|
/* ------------------------------------------------------------------ */
|
|
67
69
|
/* Runtime state */
|
|
@@ -82,6 +84,100 @@ const pendingRuntimeEvents = new Map();
|
|
|
82
84
|
const runtimeTextSeen = new Set();
|
|
83
85
|
const replayedAssistantMessages = new Map();
|
|
84
86
|
const sessionTranscriptPathCache = new Map();
|
|
87
|
+
const runParentLinks = new Map();
|
|
88
|
+
function normalizeOtlpBasePath(raw) {
|
|
89
|
+
const trimmed = String(raw || '').trim();
|
|
90
|
+
if (!trimmed)
|
|
91
|
+
return '/plugins/observability/api/otel';
|
|
92
|
+
const withLeading = trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
|
|
93
|
+
return withLeading.replace(/\/+$/, '') || '/plugins/observability/api/otel';
|
|
94
|
+
}
|
|
95
|
+
function resolveOpenclawStateDir() {
|
|
96
|
+
const envDir = String(process.env.OPENCLAW_HOME || process.env.OPENCLAW_STATE_DIR || '').trim();
|
|
97
|
+
if (envDir)
|
|
98
|
+
return path.resolve(envDir);
|
|
99
|
+
// Runtime layout: <stateDir>/extensions/openclaw-observability/dist/index.js
|
|
100
|
+
const fromModule = path.resolve(__dirname, '..', '..', '..');
|
|
101
|
+
if (path.basename(fromModule).startsWith('.openclaw'))
|
|
102
|
+
return fromModule;
|
|
103
|
+
return path.join(os.homedir(), '.openclaw');
|
|
104
|
+
}
|
|
105
|
+
function ensureDiagnosticsOtelBootstrap(params) {
|
|
106
|
+
const { configPath, otlpPath, token } = params;
|
|
107
|
+
try {
|
|
108
|
+
if (!fs.existsSync(configPath))
|
|
109
|
+
return;
|
|
110
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
111
|
+
const parsed = JSON.parse(raw);
|
|
112
|
+
const gateway = (parsed.gateway && typeof parsed.gateway === 'object') ? parsed.gateway : {};
|
|
113
|
+
const gatewayPort = Number(gateway.port);
|
|
114
|
+
const port = Number.isFinite(gatewayPort) && gatewayPort > 0 ? Math.floor(gatewayPort) : 18789;
|
|
115
|
+
const endpoint = `http://127.0.0.1:${port}${normalizeOtlpBasePath(otlpPath)}`;
|
|
116
|
+
const diagnostics = (parsed.diagnostics && typeof parsed.diagnostics === 'object') ? parsed.diagnostics : {};
|
|
117
|
+
const currentOtel = (diagnostics.otel && typeof diagnostics.otel === 'object') ? diagnostics.otel : {};
|
|
118
|
+
const headers = (currentOtel.headers && typeof currentOtel.headers === 'object')
|
|
119
|
+
? { ...currentOtel.headers }
|
|
120
|
+
: {};
|
|
121
|
+
if (token)
|
|
122
|
+
headers.Authorization = `Bearer ${token}`;
|
|
123
|
+
const nextOtel = {
|
|
124
|
+
...currentOtel,
|
|
125
|
+
enabled: true,
|
|
126
|
+
endpoint,
|
|
127
|
+
protocol: 'http/protobuf',
|
|
128
|
+
headers,
|
|
129
|
+
serviceName: String(currentOtel.serviceName || 'openclaw'),
|
|
130
|
+
traces: false,
|
|
131
|
+
metrics: true,
|
|
132
|
+
logs: false,
|
|
133
|
+
flushIntervalMs: Number(currentOtel.flushIntervalMs) > 0 ? Number(currentOtel.flushIntervalMs) : 5000,
|
|
134
|
+
};
|
|
135
|
+
const next = {
|
|
136
|
+
...parsed,
|
|
137
|
+
diagnostics: {
|
|
138
|
+
...diagnostics,
|
|
139
|
+
enabled: true,
|
|
140
|
+
otel: nextOtel,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
if (JSON.stringify(parsed) === JSON.stringify(next))
|
|
144
|
+
return;
|
|
145
|
+
fs.writeFileSync(configPath, JSON.stringify(next, null, 2), 'utf8');
|
|
146
|
+
console.log(`[openclaw-observability] diagnostics.otel bootstrap enabled -> ${endpoint}/v1/metrics`);
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
console.warn('[openclaw-observability] Failed to bootstrap diagnostics.otel:', e.message);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function persistScopeTokenToOpenclawConfig(params) {
|
|
153
|
+
const { configPath, scopeId, scopeToken } = params;
|
|
154
|
+
try {
|
|
155
|
+
if (!configPath || !scopeToken)
|
|
156
|
+
return;
|
|
157
|
+
if (!fs.existsSync(configPath))
|
|
158
|
+
return;
|
|
159
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
160
|
+
const parsed = JSON.parse(raw);
|
|
161
|
+
const plugins = (parsed.plugins && typeof parsed.plugins === 'object') ? parsed.plugins : {};
|
|
162
|
+
const entries = (plugins.entries && typeof plugins.entries === 'object') ? plugins.entries : {};
|
|
163
|
+
const obs = (entries['openclaw-observability'] && typeof entries['openclaw-observability'] === 'object')
|
|
164
|
+
? entries['openclaw-observability']
|
|
165
|
+
: { enabled: true, config: {} };
|
|
166
|
+
const cfg = (obs.config && typeof obs.config === 'object') ? obs.config : {};
|
|
167
|
+
if (String(cfg.scopeToken || '') === scopeToken && String(cfg.scopeId || '') === scopeId)
|
|
168
|
+
return;
|
|
169
|
+
cfg.scopeId = scopeId;
|
|
170
|
+
cfg.scopeToken = scopeToken;
|
|
171
|
+
obs.config = cfg;
|
|
172
|
+
entries['openclaw-observability'] = obs;
|
|
173
|
+
plugins.entries = entries;
|
|
174
|
+
parsed.plugins = plugins;
|
|
175
|
+
fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf8');
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
console.warn('[openclaw-observability] Failed to persist scope token to openclaw.json:', e.message);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
85
181
|
/* ------------------------------------------------------------------ */
|
|
86
182
|
/* Fetch interceptor — inject stream_options to capture token usage */
|
|
87
183
|
/* ------------------------------------------------------------------ */
|
|
@@ -237,7 +333,7 @@ function resolveSessionTranscriptPath(sessionId) {
|
|
|
237
333
|
return sessionTranscriptPathCache.get(sessionId) ?? null;
|
|
238
334
|
}
|
|
239
335
|
try {
|
|
240
|
-
const agentsRoot = path.join(
|
|
336
|
+
const agentsRoot = path.join(resolveOpenclawStateDir(), 'agents');
|
|
241
337
|
const agentDirs = fs.readdirSync(agentsRoot, { withFileTypes: true });
|
|
242
338
|
for (const dir of agentDirs) {
|
|
243
339
|
if (!dir.isDirectory())
|
|
@@ -364,6 +460,147 @@ function makeToolTimingKey(sessionId, runId, toolName, toolCallId) {
|
|
|
364
460
|
return `tc:id:${toolCallId}`;
|
|
365
461
|
return `tc:fallback:${sessionId}:${runId ?? 'norun'}:${toolName}`;
|
|
366
462
|
}
|
|
463
|
+
function toObject(value) {
|
|
464
|
+
if (value && typeof value === 'object' && !Array.isArray(value))
|
|
465
|
+
return value;
|
|
466
|
+
return {};
|
|
467
|
+
}
|
|
468
|
+
function pickFirstString(...values) {
|
|
469
|
+
for (const v of values) {
|
|
470
|
+
if (typeof v !== 'string')
|
|
471
|
+
continue;
|
|
472
|
+
const s = v.trim();
|
|
473
|
+
if (s)
|
|
474
|
+
return s;
|
|
475
|
+
}
|
|
476
|
+
return '';
|
|
477
|
+
}
|
|
478
|
+
function parseJsonObjectFromText(value) {
|
|
479
|
+
if (typeof value !== 'string')
|
|
480
|
+
return null;
|
|
481
|
+
const t = value.trim();
|
|
482
|
+
if (!t || (t[0] !== '{' && t[0] !== '['))
|
|
483
|
+
return null;
|
|
484
|
+
try {
|
|
485
|
+
const parsed = JSON.parse(t);
|
|
486
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function getActionRunIdFromPayload(action) {
|
|
493
|
+
return pickFirstString(action.runId, toObject(action.inputParams).runId, toObject(action.outputResult).runId, toObject(action.inputParams).run_id, toObject(action.outputResult).run_id);
|
|
494
|
+
}
|
|
495
|
+
function parseAnnounceChildRunId(runId) {
|
|
496
|
+
if (!runId.startsWith('announce:v1:'))
|
|
497
|
+
return '';
|
|
498
|
+
const pos = runId.lastIndexOf(':');
|
|
499
|
+
if (pos < 0 || pos >= runId.length - 1)
|
|
500
|
+
return '';
|
|
501
|
+
const tail = runId.slice(pos + 1).trim();
|
|
502
|
+
return /^[0-9a-fA-F-]{16,}$/.test(tail) ? tail : '';
|
|
503
|
+
}
|
|
504
|
+
function extractSpawnChildRunId(action) {
|
|
505
|
+
if (action.actionName !== 'tool_call:sessions_spawn')
|
|
506
|
+
return '';
|
|
507
|
+
const out = toObject(action.outputResult);
|
|
508
|
+
const details = toObject(out.details);
|
|
509
|
+
let childRunId = pickFirstString(details.runId, details.run_id);
|
|
510
|
+
if (childRunId)
|
|
511
|
+
return childRunId;
|
|
512
|
+
const content = out.content;
|
|
513
|
+
if (!Array.isArray(content))
|
|
514
|
+
return '';
|
|
515
|
+
for (const item of content) {
|
|
516
|
+
if (!item || typeof item !== 'object')
|
|
517
|
+
continue;
|
|
518
|
+
const block = item;
|
|
519
|
+
if (block.type !== 'text')
|
|
520
|
+
continue;
|
|
521
|
+
const parsed = parseJsonObjectFromText(block.text);
|
|
522
|
+
if (!parsed)
|
|
523
|
+
continue;
|
|
524
|
+
childRunId = pickFirstString(parsed.runId, parsed.run_id);
|
|
525
|
+
if (childRunId)
|
|
526
|
+
return childRunId;
|
|
527
|
+
}
|
|
528
|
+
return '';
|
|
529
|
+
}
|
|
530
|
+
function enrichRunLineage(action) {
|
|
531
|
+
const runId = getActionRunIdFromPayload(action);
|
|
532
|
+
if (runId && !action.runId)
|
|
533
|
+
action.runId = runId;
|
|
534
|
+
const effectiveRunId = action.runId || '';
|
|
535
|
+
const inputObj = toObject(action.inputParams);
|
|
536
|
+
const outputObj = toObject(action.outputResult);
|
|
537
|
+
const now = Date.now();
|
|
538
|
+
if (effectiveRunId) {
|
|
539
|
+
const link = runParentLinks.get(effectiveRunId);
|
|
540
|
+
if (link)
|
|
541
|
+
link.seenAt = now;
|
|
542
|
+
}
|
|
543
|
+
if (action.actionName === 'tool_call:sessions_spawn') {
|
|
544
|
+
const childRunId = extractSpawnChildRunId(action);
|
|
545
|
+
const toolCallId = pickFirstString(inputObj.toolCallId, outputObj.toolCallId, inputObj.callId, outputObj.callId, inputObj.tool_call_id, outputObj.tool_call_id);
|
|
546
|
+
const parentObservationId = effectiveRunId && toolCallId ? `tool:${effectiveRunId}:${toolCallId}` : '';
|
|
547
|
+
if (effectiveRunId && childRunId && childRunId !== effectiveRunId) {
|
|
548
|
+
runParentLinks.set(childRunId, {
|
|
549
|
+
parentRunId: effectiveRunId,
|
|
550
|
+
parentObservationId: parentObservationId || undefined,
|
|
551
|
+
seenAt: now,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (!action.parentRunId && effectiveRunId) {
|
|
556
|
+
const announceChildRunId = parseAnnounceChildRunId(effectiveRunId);
|
|
557
|
+
if (announceChildRunId) {
|
|
558
|
+
const link = runParentLinks.get(announceChildRunId);
|
|
559
|
+
if (link?.parentRunId && link.parentRunId !== effectiveRunId) {
|
|
560
|
+
action.parentRunId = link.parentRunId;
|
|
561
|
+
if (!inputObj.parentRunId && !inputObj.parent_run_id)
|
|
562
|
+
inputObj.parentRunId = link.parentRunId;
|
|
563
|
+
if (!outputObj.parentRunId && !outputObj.parent_run_id)
|
|
564
|
+
outputObj.parentRunId = link.parentRunId;
|
|
565
|
+
if (!action.parentObservationId && link.parentObservationId) {
|
|
566
|
+
action.parentObservationId = link.parentObservationId;
|
|
567
|
+
}
|
|
568
|
+
if (link.parentObservationId) {
|
|
569
|
+
if (!inputObj.parentObservationId && !inputObj.parent_observation_id)
|
|
570
|
+
inputObj.parentObservationId = link.parentObservationId;
|
|
571
|
+
if (!outputObj.parentObservationId && !outputObj.parent_observation_id)
|
|
572
|
+
outputObj.parentObservationId = link.parentObservationId;
|
|
573
|
+
}
|
|
574
|
+
link.seenAt = now;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
const direct = runParentLinks.get(effectiveRunId);
|
|
579
|
+
if (direct?.parentRunId && direct.parentRunId !== effectiveRunId) {
|
|
580
|
+
action.parentRunId = direct.parentRunId;
|
|
581
|
+
if (!inputObj.parentRunId && !inputObj.parent_run_id)
|
|
582
|
+
inputObj.parentRunId = direct.parentRunId;
|
|
583
|
+
if (!outputObj.parentRunId && !outputObj.parent_run_id)
|
|
584
|
+
outputObj.parentRunId = direct.parentRunId;
|
|
585
|
+
if (!action.parentObservationId && direct.parentObservationId) {
|
|
586
|
+
action.parentObservationId = direct.parentObservationId;
|
|
587
|
+
}
|
|
588
|
+
if (direct.parentObservationId) {
|
|
589
|
+
if (!inputObj.parentObservationId && !inputObj.parent_observation_id)
|
|
590
|
+
inputObj.parentObservationId = direct.parentObservationId;
|
|
591
|
+
if (!outputObj.parentObservationId && !outputObj.parent_observation_id)
|
|
592
|
+
outputObj.parentObservationId = direct.parentObservationId;
|
|
593
|
+
}
|
|
594
|
+
direct.seenAt = now;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (Object.keys(inputObj).length > 0)
|
|
599
|
+
action.inputParams = inputObj;
|
|
600
|
+
if (Object.keys(outputObj).length > 0)
|
|
601
|
+
action.outputResult = outputObj;
|
|
602
|
+
return action;
|
|
603
|
+
}
|
|
367
604
|
/**
|
|
368
605
|
* Periodically clean up stale Map entries to prevent memory leaks when llm_input fires but llm_output does not
|
|
369
606
|
*/
|
|
@@ -418,6 +655,17 @@ function cleanupStaleMaps() {
|
|
|
418
655
|
replayedAssistantMessages.delete(key);
|
|
419
656
|
}
|
|
420
657
|
}
|
|
658
|
+
for (const [childRunId, link] of runParentLinks) {
|
|
659
|
+
if (!link || now - link.seenAt > MAP_ENTRY_TTL_MS) {
|
|
660
|
+
runParentLinks.delete(childRunId);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (runParentLinks.size > MAX_MAP_ENTRIES) {
|
|
664
|
+
const entries = Array.from(runParentLinks.entries()).sort((a, b) => a[1].seenAt - b[1].seenAt);
|
|
665
|
+
const drop = entries.length - MAX_MAP_ENTRIES;
|
|
666
|
+
for (let i = 0; i < drop; i++)
|
|
667
|
+
runParentLinks.delete(entries[i][0]);
|
|
668
|
+
}
|
|
421
669
|
}
|
|
422
670
|
/**
|
|
423
671
|
* Extract image/media metadata from historyMessages
|
|
@@ -461,8 +709,8 @@ function extractMediaMeta(historyMessages) {
|
|
|
461
709
|
const sessionStatsMap = new Map();
|
|
462
710
|
/** Max session stats entries */
|
|
463
711
|
const MAX_SESSION_STATS = 200;
|
|
464
|
-
/** Update session statistics and return
|
|
465
|
-
function updateSessionStats(sessionId, modelName, userId, tokens) {
|
|
712
|
+
/** Update session statistics and return ObservabilitySession */
|
|
713
|
+
function updateSessionStats(sessionId, modelName, userId, parentSessionId, tokens, organizationId, scopeId, environment) {
|
|
466
714
|
const sctx = (0, runtime_1.getSessionCtx)(sessionId);
|
|
467
715
|
let stats = sessionStatsMap.get(sessionId);
|
|
468
716
|
if (!stats) {
|
|
@@ -471,7 +719,11 @@ function updateSessionStats(sessionId, modelName, userId, tokens) {
|
|
|
471
719
|
lastSeen: new Date(),
|
|
472
720
|
modelName,
|
|
473
721
|
userId,
|
|
722
|
+
parentSessionId,
|
|
474
723
|
channelId: sctx.channelId,
|
|
724
|
+
organizationId,
|
|
725
|
+
scopeId,
|
|
726
|
+
environment,
|
|
475
727
|
totalActions: 0,
|
|
476
728
|
totalTokens: 0,
|
|
477
729
|
};
|
|
@@ -484,22 +736,41 @@ function updateSessionStats(sessionId, modelName, userId, tokens) {
|
|
|
484
736
|
stats.modelName = modelName;
|
|
485
737
|
if (userId)
|
|
486
738
|
stats.userId = userId;
|
|
739
|
+
if (parentSessionId)
|
|
740
|
+
stats.parentSessionId = parentSessionId;
|
|
741
|
+
if (organizationId)
|
|
742
|
+
stats.organizationId = organizationId;
|
|
743
|
+
if (scopeId)
|
|
744
|
+
stats.scopeId = scopeId;
|
|
745
|
+
if (environment)
|
|
746
|
+
stats.environment = environment;
|
|
487
747
|
if (sctx.channelId)
|
|
488
748
|
stats.channelId = sctx.channelId;
|
|
749
|
+
if (sctx.parentSessionId)
|
|
750
|
+
stats.parentSessionId = sctx.parentSessionId;
|
|
489
751
|
return {
|
|
490
752
|
sessionId,
|
|
491
753
|
userId: stats.userId,
|
|
754
|
+
parentSessionId: stats.parentSessionId,
|
|
492
755
|
modelName: stats.modelName,
|
|
493
756
|
channelId: stats.channelId,
|
|
757
|
+
organizationId: stats.organizationId,
|
|
758
|
+
scopeId: stats.scopeId,
|
|
759
|
+
environment: stats.environment,
|
|
494
760
|
startTime: stats.firstSeen,
|
|
495
761
|
endTime: stats.lastSeen,
|
|
496
762
|
totalActions: stats.totalActions,
|
|
497
763
|
totalTokens: stats.totalTokens,
|
|
498
764
|
};
|
|
499
765
|
}
|
|
500
|
-
/** Helper to build an
|
|
766
|
+
/** Helper to build an ObservabilityAction (auto-fills defaults) */
|
|
501
767
|
function makeAction(sessionId, overrides) {
|
|
502
768
|
const sctx = (0, runtime_1.getSessionCtx)(sessionId);
|
|
769
|
+
const inputObj = toObject(overrides.inputParams);
|
|
770
|
+
const outputObj = toObject(overrides.outputResult);
|
|
771
|
+
const runId = pickFirstString(overrides.runId, inputObj.runId, outputObj.runId, inputObj.run_id, outputObj.run_id);
|
|
772
|
+
const parentRunId = pickFirstString(overrides.parentRunId, inputObj.parentRunId, outputObj.parentRunId, inputObj.parent_run_id, outputObj.parent_run_id);
|
|
773
|
+
const parentObservationId = pickFirstString(overrides.parentObservationId, inputObj.parentObservationId, outputObj.parentObservationId, inputObj.parent_observation_id, outputObj.parent_observation_id);
|
|
503
774
|
return {
|
|
504
775
|
sessionId,
|
|
505
776
|
actionType: overrides.actionType,
|
|
@@ -511,7 +782,14 @@ function makeAction(sessionId, overrides) {
|
|
|
511
782
|
completionTokens: overrides.completionTokens ?? null,
|
|
512
783
|
durationMs: overrides.durationMs ?? null,
|
|
513
784
|
userId: overrides.userId ?? sctx.userId,
|
|
785
|
+
parentSessionId: overrides.parentSessionId ?? sctx.parentSessionId,
|
|
786
|
+
runId: runId || undefined,
|
|
787
|
+
parentRunId: parentRunId || undefined,
|
|
788
|
+
parentObservationId: parentObservationId || undefined,
|
|
514
789
|
channelId: overrides.channelId ?? sctx.channelId,
|
|
790
|
+
organizationId: overrides.organizationId ?? _defaultTenantScope.organizationId,
|
|
791
|
+
scopeId: overrides.scopeId ?? _defaultTenantScope.scopeId,
|
|
792
|
+
environment: overrides.environment ?? _defaultTenantScope.environment,
|
|
515
793
|
createdAt: overrides.createdAt ?? new Date(),
|
|
516
794
|
};
|
|
517
795
|
}
|
|
@@ -534,9 +812,23 @@ let _mapCleanupTimer = null;
|
|
|
534
812
|
let _checkpointTimer = null;
|
|
535
813
|
let _metricsCleanupTimer = null;
|
|
536
814
|
let _unsubscribeAgentEvents = null;
|
|
815
|
+
let _defaultTenantScope = {
|
|
816
|
+
organizationId: 'local',
|
|
817
|
+
scopeId: 'local',
|
|
818
|
+
environment: 'prod',
|
|
819
|
+
};
|
|
537
820
|
function activate(api) {
|
|
538
821
|
const rawConfig = (api.pluginConfig || api.config || {});
|
|
539
822
|
const config = (0, config_1.resolveConfig)(rawConfig);
|
|
823
|
+
const stateDir = resolveOpenclawStateDir();
|
|
824
|
+
const openclawConfigPath = path.join(stateDir, 'openclaw.json');
|
|
825
|
+
const effectiveScopeId = String(process.env.OPENCLAW_OBSERVABILITY_SCOPE_ID || config.scopeId || 'local');
|
|
826
|
+
const effectiveScopeToken = String(process.env.OPENCLAW_OBSERVABILITY_SCOPE_TOKEN || config.scopeToken || '');
|
|
827
|
+
_defaultTenantScope = {
|
|
828
|
+
organizationId: String(process.env.OPENCLAW_OBSERVABILITY_ORG_ID || config.organizationId || 'local'),
|
|
829
|
+
scopeId: effectiveScopeId,
|
|
830
|
+
environment: String(process.env.OPENCLAW_OBSERVABILITY_ENV || config.environment || 'prod'),
|
|
831
|
+
};
|
|
540
832
|
const logInfo = (message) => {
|
|
541
833
|
if (typeof api.logger?.info === 'function') {
|
|
542
834
|
api.logger.info(message);
|
|
@@ -548,10 +840,26 @@ function activate(api) {
|
|
|
548
840
|
const isFirstActivation = !_writer;
|
|
549
841
|
if (!_writer) {
|
|
550
842
|
if (config.mode === 'remote') {
|
|
551
|
-
_writer = new mysql_writer_1.MySQLWriter(config.mysql
|
|
843
|
+
_writer = new mysql_writer_1.MySQLWriter(config.mysql, {
|
|
844
|
+
scopeId: effectiveScopeId,
|
|
845
|
+
scopeToken: effectiveScopeToken,
|
|
846
|
+
dataRetentionDays: config.dataRetentionDays,
|
|
847
|
+
onTokenIssued: (token) => {
|
|
848
|
+
persistScopeTokenToOpenclawConfig({
|
|
849
|
+
configPath: openclawConfigPath,
|
|
850
|
+
scopeId: effectiveScopeId,
|
|
851
|
+
scopeToken: token,
|
|
852
|
+
});
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
else if (config.mode === 'cloud') {
|
|
857
|
+
_writer = new cloud_export_writer_1.CloudExportWriter(config.cloud);
|
|
552
858
|
}
|
|
553
859
|
else {
|
|
554
|
-
_writer = new duckdb_local_writer_1.DuckDBLocalWriter(config.duckdb
|
|
860
|
+
_writer = new duckdb_local_writer_1.DuckDBLocalWriter(config.duckdb, {
|
|
861
|
+
dataRetentionDays: config.dataRetentionDays,
|
|
862
|
+
});
|
|
555
863
|
}
|
|
556
864
|
}
|
|
557
865
|
if (!_buffer) {
|
|
@@ -570,7 +878,6 @@ function activate(api) {
|
|
|
570
878
|
(0, runtime_1.installFetchInterceptor)();
|
|
571
879
|
}
|
|
572
880
|
const redactor = new redaction_1.Redactor(config.redaction);
|
|
573
|
-
const stateDir = path.join(os.homedir(), '.openclaw');
|
|
574
881
|
const metricsRetentionDaysRaw = Number(config.metrics.retentionDays);
|
|
575
882
|
const metricsRetentionDays = Number.isFinite(metricsRetentionDaysRaw)
|
|
576
883
|
? Math.max(0, Math.floor(metricsRetentionDaysRaw))
|
|
@@ -657,6 +964,14 @@ function activate(api) {
|
|
|
657
964
|
metricsCleanupRunning = false;
|
|
658
965
|
}
|
|
659
966
|
}
|
|
967
|
+
async function runStartupMaintenance() {
|
|
968
|
+
if (typeof writer.runStartupMaintenance !== 'function')
|
|
969
|
+
return;
|
|
970
|
+
const ready = await writer.ensureReady();
|
|
971
|
+
if (!ready)
|
|
972
|
+
return;
|
|
973
|
+
await writer.runStartupMaintenance();
|
|
974
|
+
}
|
|
660
975
|
function startServices() {
|
|
661
976
|
if (_servicesStarted)
|
|
662
977
|
return;
|
|
@@ -700,12 +1015,29 @@ function activate(api) {
|
|
|
700
1015
|
}
|
|
701
1016
|
// Lazy initialization: database file will be created on first write or first API query
|
|
702
1017
|
// This avoids creating empty database files when plugin is installed but never used
|
|
703
|
-
const dbConfigured = config.mode === '
|
|
704
|
-
|
|
1018
|
+
const dbConfigured = config.mode === 'cloud'
|
|
1019
|
+
? Boolean(config.cloud.endpoint && config.cloud.apiKey)
|
|
1020
|
+
: (config.mode === 'local'
|
|
1021
|
+
|| (config.mysql.host !== 'localhost' || config.mysql.password !== ''));
|
|
705
1022
|
const dbStatus = dbConfigured ? 'ready' : 'not-configured';
|
|
1023
|
+
// Pre-warm database initialization as early as possible to reduce
|
|
1024
|
+
// first-request latency after gateway/plugin startup.
|
|
1025
|
+
if (dbConfigured) {
|
|
1026
|
+
writer.ensureReady()
|
|
1027
|
+
.then((ok) => {
|
|
1028
|
+
if (ok) {
|
|
1029
|
+
void runStartupMaintenance().catch((err) => {
|
|
1030
|
+
console.warn('[openclaw-observability] Startup maintenance failed:', err.message);
|
|
1031
|
+
});
|
|
1032
|
+
startServices();
|
|
1033
|
+
}
|
|
1034
|
+
})
|
|
1035
|
+
.catch((err) => {
|
|
1036
|
+
console.warn('[openclaw-observability] Early DB warmup failed (will retry lazily):', err.message);
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
706
1039
|
// UI shell stays directly open; legacy HTTP APIs still accept gateway token
|
|
707
1040
|
// for backward compatibility. WS data access is authenticated at gateway connect.
|
|
708
|
-
const openclawConfigPath = path.join(stateDir, 'openclaw.json');
|
|
709
1041
|
let observabilityToken;
|
|
710
1042
|
try {
|
|
711
1043
|
if (fs.existsSync(openclawConfigPath)) {
|
|
@@ -726,12 +1058,24 @@ function activate(api) {
|
|
|
726
1058
|
catch (e) {
|
|
727
1059
|
console.warn('[openclaw-observability] Failed to read gateway token from config:', e.message);
|
|
728
1060
|
}
|
|
1061
|
+
ensureDiagnosticsOtelBootstrap({
|
|
1062
|
+
configPath: openclawConfigPath,
|
|
1063
|
+
otlpPath: config.metrics.otlpPath,
|
|
1064
|
+
token: observabilityToken,
|
|
1065
|
+
});
|
|
729
1066
|
const tokenCompatEnabled = Boolean(observabilityToken);
|
|
730
1067
|
// Always register HTTP routes on the current api (the latest api serves HTTP)
|
|
731
1068
|
const registerHttpRoute = api.registerHttpRoute;
|
|
732
1069
|
const httpRouteEnabled = typeof registerHttpRoute === 'function';
|
|
733
1070
|
if (httpRouteEnabled) {
|
|
734
|
-
|
|
1071
|
+
const runReplay = async (params) => {
|
|
1072
|
+
return (0, replay_runtime_1.runReplayRequestViaRuntime)({
|
|
1073
|
+
configPath: openclawConfigPath,
|
|
1074
|
+
runtime: api.runtime,
|
|
1075
|
+
...params,
|
|
1076
|
+
});
|
|
1077
|
+
};
|
|
1078
|
+
(0, routes_1.registerObservabilityRoutes)(registerHttpRoute.bind(api), writer, observabilityToken, {
|
|
735
1079
|
getConfig: () => securityScanner.getConfig(),
|
|
736
1080
|
updateConfig: (patch) => {
|
|
737
1081
|
const next = securityScanner.updateConfig(patch);
|
|
@@ -742,7 +1086,18 @@ function activate(api) {
|
|
|
742
1086
|
`customRegexRules=${next.customRegexRules.length}`);
|
|
743
1087
|
return next;
|
|
744
1088
|
},
|
|
745
|
-
}, config.metrics
|
|
1089
|
+
}, config.metrics, {
|
|
1090
|
+
showTenantScope: config.mode !== 'local',
|
|
1091
|
+
}, {
|
|
1092
|
+
runReplay,
|
|
1093
|
+
}, {
|
|
1094
|
+
enabled: config.mode === 'cloud' || config.cloud.enabled,
|
|
1095
|
+
secretSalt: process.env.OPENCLAW_OBSERVABILITY_API_KEY_SALT || '',
|
|
1096
|
+
}, {
|
|
1097
|
+
openclawRootDir: stateDir,
|
|
1098
|
+
openclawConfigPath,
|
|
1099
|
+
workspaceMediaRoot: path.join(stateDir, 'workspace', 'media'),
|
|
1100
|
+
});
|
|
746
1101
|
}
|
|
747
1102
|
else {
|
|
748
1103
|
console.warn('[openclaw-observability] registerHttpRoute not available — observability UI disabled');
|
|
@@ -756,6 +1111,8 @@ function activate(api) {
|
|
|
756
1111
|
config,
|
|
757
1112
|
securityScanner,
|
|
758
1113
|
persistSecurityConfig,
|
|
1114
|
+
openclawConfigPath,
|
|
1115
|
+
runtime: api.runtime,
|
|
759
1116
|
});
|
|
760
1117
|
}
|
|
761
1118
|
else {
|
|
@@ -764,12 +1121,13 @@ function activate(api) {
|
|
|
764
1121
|
// ====================== Helper functions ======================
|
|
765
1122
|
/** Record action, update session stats, and run security scan */
|
|
766
1123
|
function recordAction(action, tokens = 0) {
|
|
767
|
-
|
|
768
|
-
|
|
1124
|
+
const enriched = enrichRunLineage(action);
|
|
1125
|
+
void buffer.addAction(enriched);
|
|
1126
|
+
const session = updateSessionStats(enriched.sessionId, enriched.modelName, enriched.userId, enriched.parentSessionId || '', tokens, enriched.organizationId || _defaultTenantScope.organizationId, enriched.scopeId || _defaultTenantScope.scopeId, enriched.environment || _defaultTenantScope.environment);
|
|
769
1127
|
void buffer.addSession(session);
|
|
770
1128
|
// --- Security scan ---
|
|
771
1129
|
try {
|
|
772
|
-
const alerts = securityScanner.scan(
|
|
1130
|
+
const alerts = securityScanner.scan(enriched);
|
|
773
1131
|
if (alerts.length > 0) {
|
|
774
1132
|
// Prevent _alertBuffer from growing unbounded
|
|
775
1133
|
if (_alertBuffer.length + alerts.length > MAX_ALERT_BUFFER) {
|
|
@@ -953,52 +1311,8 @@ function activate(api) {
|
|
|
953
1311
|
// =====================================================================
|
|
954
1312
|
// 1. Agent lifecycle
|
|
955
1313
|
// =====================================================================
|
|
956
|
-
// before_model_resolve
|
|
957
|
-
|
|
958
|
-
try {
|
|
959
|
-
const e = event;
|
|
960
|
-
const c = ctx;
|
|
961
|
-
const sid = (0, runtime_1.resolveSessionId)(undefined, c?.sessionId);
|
|
962
|
-
const sctx = (0, runtime_1.getSessionCtx)(sid);
|
|
963
|
-
if (c?.agentId)
|
|
964
|
-
sctx.userId = c.agentId;
|
|
965
|
-
recordAction(makeAction(sid, {
|
|
966
|
-
actionType: types_1.ActionType.ModelResolve,
|
|
967
|
-
actionName: 'before_model_resolve',
|
|
968
|
-
userId: c?.agentId,
|
|
969
|
-
inputParams: redactor.redact({ prompt: e.prompt }),
|
|
970
|
-
}));
|
|
971
|
-
console.log(`[openclaw-observability] before_model_resolve: session=${sid}`);
|
|
972
|
-
}
|
|
973
|
-
catch (err) {
|
|
974
|
-
console.error('[openclaw-observability] Error in before_model_resolve:', err);
|
|
975
|
-
}
|
|
976
|
-
});
|
|
977
|
-
// before_prompt_build: before prompt construction
|
|
978
|
-
api.on('before_prompt_build', (event, ctx) => {
|
|
979
|
-
try {
|
|
980
|
-
const e = event;
|
|
981
|
-
const c = ctx;
|
|
982
|
-
const sid = (0, runtime_1.resolveSessionId)(undefined, c?.sessionId);
|
|
983
|
-
const summary = summarizePromptMessages(e.messages);
|
|
984
|
-
recordAction(makeAction(sid, {
|
|
985
|
-
actionType: types_1.ActionType.PromptBuild,
|
|
986
|
-
actionName: 'before_prompt_build',
|
|
987
|
-
userId: c?.agentId,
|
|
988
|
-
inputParams: redactor.redact({
|
|
989
|
-
prompt: e.prompt,
|
|
990
|
-
messageCount: summary.messageCount,
|
|
991
|
-
messageRoleCounts: summary.roleCounts,
|
|
992
|
-
firstMessageRoles: summary.firstRoles,
|
|
993
|
-
messages: (0, runtime_1.sanitizePayloadForStorage)(e.messages),
|
|
994
|
-
}),
|
|
995
|
-
}));
|
|
996
|
-
console.log(`[openclaw-observability] before_prompt_build: session=${sid} msgs=${Array.isArray(e.messages) ? e.messages.length : '?'}`);
|
|
997
|
-
}
|
|
998
|
-
catch (err) {
|
|
999
|
-
console.error('[openclaw-observability] Error in before_prompt_build:', err);
|
|
1000
|
-
}
|
|
1001
|
-
});
|
|
1314
|
+
// before_model_resolve / before_prompt_build intentionally not collected:
|
|
1315
|
+
// these hooks are high-frequency and overlap with llm_input/llm_output data.
|
|
1002
1316
|
// before_agent_start: skipped — legacy hook that fires twice
|
|
1003
1317
|
// and fully overlaps with before_model_resolve + before_prompt_build
|
|
1004
1318
|
// agent_end: Agent run finished
|
|
@@ -1006,17 +1320,25 @@ function activate(api) {
|
|
|
1006
1320
|
try {
|
|
1007
1321
|
const e = event;
|
|
1008
1322
|
const c = ctx;
|
|
1009
|
-
const sid = (0, runtime_1.
|
|
1323
|
+
const sid = (0, runtime_1.resolveSessionIdWithRun)({
|
|
1324
|
+
ctxSessionId: c?.sessionId,
|
|
1325
|
+
runId: e.runId || c?.runId,
|
|
1326
|
+
allowGlobalFallback: false,
|
|
1327
|
+
runSessionIds,
|
|
1328
|
+
});
|
|
1329
|
+
if (sid === 'unknown')
|
|
1330
|
+
return;
|
|
1010
1331
|
recordAction(makeAction(sid, {
|
|
1011
1332
|
actionType: types_1.ActionType.AgentEnd,
|
|
1012
1333
|
actionName: 'agent_end',
|
|
1013
|
-
userId: c?.agentId,
|
|
1014
1334
|
outputResult: redactor.redact({
|
|
1015
1335
|
success: e.success,
|
|
1016
1336
|
error: e.error,
|
|
1017
1337
|
messageCount: Array.isArray(e.messages) ? e.messages.length : 0,
|
|
1338
|
+
runId: e.runId || c?.runId,
|
|
1018
1339
|
}),
|
|
1019
1340
|
durationMs: e.durationMs ?? null,
|
|
1341
|
+
runId: e.runId || c?.runId,
|
|
1020
1342
|
}));
|
|
1021
1343
|
console.log(`[openclaw-observability] agent_end: session=${sid} success=${e.success} duration=${e.durationMs}ms`);
|
|
1022
1344
|
}
|
|
@@ -1033,6 +1355,8 @@ function activate(api) {
|
|
|
1033
1355
|
const e = event;
|
|
1034
1356
|
const c = ctx;
|
|
1035
1357
|
const sid = (0, runtime_1.resolveSessionId)(e.sessionId, c?.sessionId);
|
|
1358
|
+
if (sid === 'unknown')
|
|
1359
|
+
return;
|
|
1036
1360
|
const sctx = (0, runtime_1.getSessionCtx)(sid);
|
|
1037
1361
|
// Parse image/media metadata from historyMessages
|
|
1038
1362
|
const media = extractMediaMeta(e.historyMessages ?? []);
|
|
@@ -1065,7 +1389,6 @@ function activate(api) {
|
|
|
1065
1389
|
inputParams: redactor.redact(replayMeta),
|
|
1066
1390
|
outputResult: redactor.redact({ text: assistantText, length: assistantText.length }),
|
|
1067
1391
|
createdAt: new Date(replayAt.getTime() + 1),
|
|
1068
|
-
userId: c?.agentId,
|
|
1069
1392
|
}));
|
|
1070
1393
|
}
|
|
1071
1394
|
}
|
|
@@ -1076,6 +1399,15 @@ function activate(api) {
|
|
|
1076
1399
|
prompt: e.prompt,
|
|
1077
1400
|
});
|
|
1078
1401
|
const sanitizedHistory = (0, runtime_1.sanitizePayloadForStorage)(e.historyMessages);
|
|
1402
|
+
const isReplayRun = ((typeof e.runId === 'string' && e.runId.startsWith('replay:'))
|
|
1403
|
+
|| sid.startsWith('replay-'));
|
|
1404
|
+
const replayMeta = isReplayRun
|
|
1405
|
+
? {
|
|
1406
|
+
isReplay: true,
|
|
1407
|
+
replaySessionId: sid,
|
|
1408
|
+
replayRunId: e.runId,
|
|
1409
|
+
}
|
|
1410
|
+
: undefined;
|
|
1079
1411
|
llmRunStartTimes.set(e.runId, Date.now());
|
|
1080
1412
|
runSessionIds.set(e.runId, sid);
|
|
1081
1413
|
runInputs.set(e.runId, {
|
|
@@ -1085,9 +1417,10 @@ function activate(api) {
|
|
|
1085
1417
|
imagesCount: e.imagesCount,
|
|
1086
1418
|
media: media.length > 0 ? media : undefined,
|
|
1087
1419
|
historyMessages: Array.isArray(sanitizedHistory) ? sanitizedHistory : undefined,
|
|
1420
|
+
replayMeta,
|
|
1088
1421
|
});
|
|
1089
1422
|
}
|
|
1090
|
-
if (c?.agentId)
|
|
1423
|
+
if (c?.agentId && !sctx.userId)
|
|
1091
1424
|
sctx.userId = c.agentId;
|
|
1092
1425
|
// Identify channel from multiple sources
|
|
1093
1426
|
const identifiedChannel = (0, runtime_1.identifyChannel)(c);
|
|
@@ -1121,6 +1454,8 @@ function activate(api) {
|
|
|
1121
1454
|
runInputs.delete(e.runId);
|
|
1122
1455
|
// Token usage: multiple extraction strategies
|
|
1123
1456
|
const sid = (0, runtime_1.resolveSessionId)(e.sessionId, c?.sessionId);
|
|
1457
|
+
if (sid === 'unknown')
|
|
1458
|
+
return;
|
|
1124
1459
|
let promptTokens = e.usage?.input ?? null;
|
|
1125
1460
|
let completionTokens = e.usage?.output ?? null;
|
|
1126
1461
|
let cacheRead = e.usage?.cacheRead ?? null;
|
|
@@ -1188,13 +1523,17 @@ function activate(api) {
|
|
|
1188
1523
|
inputData.historyMessages = cachedInput.historyMessages;
|
|
1189
1524
|
inputData.historyMessageCount = cachedInput.historyMessages.length;
|
|
1190
1525
|
}
|
|
1526
|
+
if (cachedInput?.replayMeta?.isReplay) {
|
|
1527
|
+
inputData.replay = cachedInput.replayMeta;
|
|
1528
|
+
}
|
|
1191
1529
|
const sctx = (0, runtime_1.getSessionCtx)(sid);
|
|
1192
|
-
if (c?.agentId)
|
|
1530
|
+
if (c?.agentId && !sctx.userId)
|
|
1193
1531
|
sctx.userId = c.agentId;
|
|
1194
1532
|
sctx.modelName = modelName;
|
|
1533
|
+
const isReplayCall = cachedInput?.replayMeta?.isReplay === true;
|
|
1195
1534
|
recordAction(makeAction(sid, {
|
|
1196
|
-
actionType: types_1.ActionType.Message,
|
|
1197
|
-
actionName: `llm_call:${modelName}`,
|
|
1535
|
+
actionType: isReplayCall ? types_1.ActionType.Replay : types_1.ActionType.Message,
|
|
1536
|
+
actionName: isReplayCall ? `replay_call:${modelName}` : `llm_call:${modelName}`,
|
|
1198
1537
|
modelName,
|
|
1199
1538
|
inputParams: redactor.redact(inputData),
|
|
1200
1539
|
outputResult: redactor.redact({
|
|
@@ -1212,53 +1551,61 @@ function activate(api) {
|
|
|
1212
1551
|
promptTokens,
|
|
1213
1552
|
completionTokens,
|
|
1214
1553
|
durationMs,
|
|
1215
|
-
userId: c?.agentId,
|
|
1216
1554
|
}), totalTokens);
|
|
1217
1555
|
const endedAt = Date.now();
|
|
1218
1556
|
const syntheticTs = endedAt;
|
|
1219
1557
|
if (e.runId) {
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
const
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
const
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1558
|
+
// Transcript replay is a fallback-only path for providers/runtimes that
|
|
1559
|
+
// fail to expose assistant stream/thinking in llm_output payloads.
|
|
1560
|
+
// If we already have direct content for this run, replay can pull in a
|
|
1561
|
+
// neighboring toolUse message and mis-attach it to the current run.
|
|
1562
|
+
const shouldReplayFromTranscript = assistantTexts.length === 0 &&
|
|
1563
|
+
fallbackArtifacts.thinking.length === 0;
|
|
1564
|
+
if (!shouldReplayFromTranscript) {
|
|
1565
|
+
// skip transcript replay for this run
|
|
1566
|
+
}
|
|
1567
|
+
else {
|
|
1568
|
+
const transcriptToolUseMessages = extractToolUseAssistantMessagesFromTranscript({
|
|
1569
|
+
sessionId: sid,
|
|
1570
|
+
runStartedAt,
|
|
1571
|
+
runEndedAt: endedAt,
|
|
1572
|
+
});
|
|
1573
|
+
for (const msg of transcriptToolUseMessages) {
|
|
1574
|
+
const replayKey = `${sid}:${e.runId}:${msg.messageId}`;
|
|
1575
|
+
if (replayedAssistantMessages.has(replayKey))
|
|
1576
|
+
continue;
|
|
1577
|
+
replayedAssistantMessages.set(replayKey, Date.now());
|
|
1578
|
+
const replayMeta = {
|
|
1579
|
+
runId: e.runId,
|
|
1580
|
+
source: 'transcript_message',
|
|
1581
|
+
synthetic: true,
|
|
1582
|
+
stopReason: msg.stopReason ?? 'toolUse',
|
|
1583
|
+
messageId: msg.messageId,
|
|
1584
|
+
toolCallIds: msg.toolCallIds,
|
|
1585
|
+
};
|
|
1586
|
+
const replayAt = new Date(msg.timestampMs);
|
|
1587
|
+
if (msg.thinking.length > 0) {
|
|
1588
|
+
const thinkingText = msg.thinking.join('\n\n');
|
|
1589
|
+
recordAction(makeAction(sid, {
|
|
1590
|
+
actionType: types_1.ActionType.Thinking,
|
|
1591
|
+
actionName: 'thinking',
|
|
1592
|
+
modelName,
|
|
1593
|
+
inputParams: redactor.redact(replayMeta),
|
|
1594
|
+
outputResult: redactor.redact({ text: thinkingText, length: thinkingText.length }),
|
|
1595
|
+
createdAt: replayAt,
|
|
1596
|
+
}));
|
|
1597
|
+
}
|
|
1598
|
+
if (msg.texts.length > 0) {
|
|
1599
|
+
const assistantText = msg.texts.join('\n\n');
|
|
1600
|
+
recordAction(makeAction(sid, {
|
|
1601
|
+
actionType: types_1.ActionType.AssistantStream,
|
|
1602
|
+
actionName: 'assistant_stream',
|
|
1603
|
+
modelName,
|
|
1604
|
+
inputParams: redactor.redact(replayMeta),
|
|
1605
|
+
outputResult: redactor.redact({ text: assistantText, length: assistantText.length }),
|
|
1606
|
+
createdAt: new Date(msg.timestampMs + 1),
|
|
1607
|
+
}));
|
|
1608
|
+
}
|
|
1262
1609
|
}
|
|
1263
1610
|
}
|
|
1264
1611
|
if (assistantTexts.length > 0 &&
|
|
@@ -1271,7 +1618,6 @@ function activate(api) {
|
|
|
1271
1618
|
inputParams: redactor.redact({ runId: e.runId, stream: 'assistant', synthetic: true }),
|
|
1272
1619
|
outputResult: redactor.redact({ text: assistantText, length: assistantText.length }),
|
|
1273
1620
|
createdAt: new Date(syntheticTs - 1),
|
|
1274
|
-
userId: c?.agentId,
|
|
1275
1621
|
}));
|
|
1276
1622
|
}
|
|
1277
1623
|
if (fallbackArtifacts.thinking.length > 0 &&
|
|
@@ -1304,7 +1650,6 @@ function activate(api) {
|
|
|
1304
1650
|
outputResult: redactor.redact({ text: thinkingText, length: thinkingText.length }),
|
|
1305
1651
|
createdAt: new Date(thinkingEndAt),
|
|
1306
1652
|
durationMs: inferredThinkingDuration,
|
|
1307
|
-
userId: c?.agentId,
|
|
1308
1653
|
}));
|
|
1309
1654
|
}
|
|
1310
1655
|
}
|
|
@@ -1333,10 +1678,11 @@ function activate(api) {
|
|
|
1333
1678
|
const e = event;
|
|
1334
1679
|
const c = ctx;
|
|
1335
1680
|
const sid = (0, runtime_1.resolveSessionId)(undefined, c?.sessionId);
|
|
1681
|
+
if (sid === 'unknown')
|
|
1682
|
+
return;
|
|
1336
1683
|
recordAction(makeAction(sid, {
|
|
1337
1684
|
actionType: types_1.ActionType.Compaction,
|
|
1338
1685
|
actionName: 'before_compaction',
|
|
1339
|
-
userId: c?.agentId,
|
|
1340
1686
|
inputParams: redactor.redact({
|
|
1341
1687
|
messageCount: e.messageCount,
|
|
1342
1688
|
compactingCount: e.compactingCount,
|
|
@@ -1357,10 +1703,11 @@ function activate(api) {
|
|
|
1357
1703
|
const e = event;
|
|
1358
1704
|
const c = ctx;
|
|
1359
1705
|
const sid = (0, runtime_1.resolveSessionId)(undefined, c?.sessionId);
|
|
1706
|
+
if (sid === 'unknown')
|
|
1707
|
+
return;
|
|
1360
1708
|
recordAction(makeAction(sid, {
|
|
1361
1709
|
actionType: types_1.ActionType.Compaction,
|
|
1362
1710
|
actionName: 'after_compaction',
|
|
1363
|
-
userId: c?.agentId,
|
|
1364
1711
|
outputResult: redactor.redact({
|
|
1365
1712
|
messageCount: e.messageCount,
|
|
1366
1713
|
compactedCount: e.compactedCount,
|
|
@@ -1380,10 +1727,11 @@ function activate(api) {
|
|
|
1380
1727
|
const e = event;
|
|
1381
1728
|
const c = ctx;
|
|
1382
1729
|
const sid = (0, runtime_1.resolveSessionId)(undefined, c?.sessionId);
|
|
1730
|
+
if (sid === 'unknown')
|
|
1731
|
+
return;
|
|
1383
1732
|
recordAction(makeAction(sid, {
|
|
1384
1733
|
actionType: types_1.ActionType.Reset,
|
|
1385
1734
|
actionName: 'before_reset',
|
|
1386
|
-
userId: c?.agentId,
|
|
1387
1735
|
inputParams: redactor.redact({
|
|
1388
1736
|
reason: e.reason,
|
|
1389
1737
|
messageCount: Array.isArray(e.messages) ? e.messages.length : 0,
|
|
@@ -1407,6 +1755,7 @@ function activate(api) {
|
|
|
1407
1755
|
recordAction,
|
|
1408
1756
|
identifyChannel: runtime_1.identifyChannel,
|
|
1409
1757
|
saveChannelToContext: runtime_1.saveChannelToContext,
|
|
1758
|
+
saveUserToContext: runtime_1.saveUserToContext,
|
|
1410
1759
|
resolveSessionIdWithRun: (args) => (0, runtime_1.resolveSessionIdWithRun)({ ...args, runSessionIds }),
|
|
1411
1760
|
});
|
|
1412
1761
|
// =====================================================================
|
|
@@ -1448,6 +1797,7 @@ function activate(api) {
|
|
|
1448
1797
|
redactor,
|
|
1449
1798
|
makeAction,
|
|
1450
1799
|
recordAction,
|
|
1800
|
+
bindParentSession: runtime_1.bindParentSession,
|
|
1451
1801
|
resolveSessionIdWithRun: (args) => (0, runtime_1.resolveSessionIdWithRun)({ ...args, runSessionIds }),
|
|
1452
1802
|
});
|
|
1453
1803
|
// =====================================================================
|
|
@@ -1460,7 +1810,9 @@ function activate(api) {
|
|
|
1460
1810
|
// Pre-initialize DuckDB during startup (not lazily on first request).
|
|
1461
1811
|
// This ensures WAL recovery completes before any user requests arrive,
|
|
1462
1812
|
// preventing 503 errors on the first request after restart.
|
|
1463
|
-
writer.ensureReady()
|
|
1813
|
+
writer.ensureReady()
|
|
1814
|
+
.then(() => runStartupMaintenance())
|
|
1815
|
+
.catch((err) => {
|
|
1464
1816
|
console.error('[openclaw-observability] Pre-init DuckDB failed (will retry on first write):', err);
|
|
1465
1817
|
});
|
|
1466
1818
|
recordAction(makeAction('system', {
|
|
@@ -1497,7 +1849,9 @@ function activate(api) {
|
|
|
1497
1849
|
});
|
|
1498
1850
|
const dbTarget = config.mode === 'remote'
|
|
1499
1851
|
? `${config.mysql.host}/${config.mysql.database}`
|
|
1500
|
-
: config.
|
|
1852
|
+
: config.mode === 'cloud'
|
|
1853
|
+
? config.cloud.endpoint
|
|
1854
|
+
: config.duckdb.path;
|
|
1501
1855
|
logInfo(`[Observability] Plugin activated (mode: ${config.mode}, db: ${dbStatus}:${dbTarget}, ui: ${httpRouteEnabled ? '/plugins/observability/' : 'disabled'}, metrics: ${config.metrics.enabled ? `${config.metrics.otlpPath}/v1/metrics` : 'disabled'}, metricsRetentionDays: ${metricsRetentionDays > 0 ? metricsRetentionDays : 'off'}, rpc: ${gatewayRpcEnabled ? 'enabled' : 'disabled'}, runtimeEvents: ${runtimeEventsStatus}, tokenCompat: ${tokenCompatEnabled ? 'on' : 'off'})`);
|
|
1502
1856
|
// =====================================================================
|
|
1503
1857
|
// Return deactivate function for cleanup
|