ocuclaw 0.1.0
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 +25 -0
- package/dist/config/runtime-config.js +165 -0
- package/dist/domain/activity-status-adapter.js +1041 -0
- package/dist/domain/conversation-state.js +516 -0
- package/dist/domain/debug-store.js +700 -0
- package/dist/domain/message-emoji-filter.js +249 -0
- package/dist/domain/readability-system-prompt.js +17 -0
- package/dist/even-ai/even-ai-endpoint.js +938 -0
- package/dist/even-ai/even-ai-model-hook.js +80 -0
- package/dist/even-ai/even-ai-router.js +98 -0
- package/dist/even-ai/even-ai-run-waiter.js +265 -0
- package/dist/even-ai/even-ai-settings-store.js +365 -0
- package/dist/gateway/gateway-bridge.js +175 -0
- package/dist/gateway/openclaw-client.js +1570 -0
- package/dist/index.js +38 -0
- package/dist/runtime/downstream-handler.js +2747 -0
- package/dist/runtime/downstream-server.js +1565 -0
- package/dist/runtime/ocuclaw-settings-store.js +237 -0
- package/dist/runtime/protocol-adapter.js +378 -0
- package/dist/runtime/relay-core.js +1977 -0
- package/dist/runtime/relay-service.js +146 -0
- package/dist/runtime/session-service.js +1026 -0
- package/dist/runtime/upstream-runtime.js +931 -0
- package/openclaw.plugin.json +95 -0
- package/package.json +36 -0
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
const DEFAULT_DEBUG_CATEGORIES = Object.freeze([
|
|
2
|
+
"relay.transport",
|
|
3
|
+
"relay.protocol",
|
|
4
|
+
"relay.session",
|
|
5
|
+
"openclaw.run",
|
|
6
|
+
"openclaw.seq",
|
|
7
|
+
"openclaw.history",
|
|
8
|
+
"sdk.frames",
|
|
9
|
+
"sdk.results",
|
|
10
|
+
"sdk.events",
|
|
11
|
+
"sdk.events.summary",
|
|
12
|
+
"sdk.events.raw",
|
|
13
|
+
"app.timeline",
|
|
14
|
+
"app.lifecycle",
|
|
15
|
+
"probe.runtime.main_thread",
|
|
16
|
+
"probe.runtime.memory",
|
|
17
|
+
"probe.runtime.bridge",
|
|
18
|
+
"probe.runtime.bridge_timing",
|
|
19
|
+
"probe.runtime.screen_off",
|
|
20
|
+
"probe.perf.conversation_upgrade",
|
|
21
|
+
"app.state.diff",
|
|
22
|
+
"render.ownership",
|
|
23
|
+
"render.virtual_pager",
|
|
24
|
+
"render.virtual_pager.summary",
|
|
25
|
+
"render.virtual_pager.diagnostics",
|
|
26
|
+
"screen.nav",
|
|
27
|
+
"screen.dim",
|
|
28
|
+
"probe.webview.trace",
|
|
29
|
+
"session.timeline",
|
|
30
|
+
"approvals.timeline",
|
|
31
|
+
"approvals.state",
|
|
32
|
+
"voice.timeline",
|
|
33
|
+
"voice.transport",
|
|
34
|
+
"evenai",
|
|
35
|
+
"audio.pipeline",
|
|
36
|
+
"settings.loadsave",
|
|
37
|
+
"config.timeline",
|
|
38
|
+
"workflow.profile",
|
|
39
|
+
"workflow.run",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const DEBUG_CATEGORY_ALIASES = Object.freeze({
|
|
43
|
+
"app.timeline": Object.freeze([
|
|
44
|
+
"app.timeline",
|
|
45
|
+
"app.lifecycle",
|
|
46
|
+
"probe.runtime.main_thread",
|
|
47
|
+
"probe.runtime.memory",
|
|
48
|
+
"probe.runtime.bridge",
|
|
49
|
+
"probe.runtime.bridge_timing",
|
|
50
|
+
"probe.runtime.screen_off",
|
|
51
|
+
"probe.perf.conversation_upgrade",
|
|
52
|
+
]),
|
|
53
|
+
"voice.timeline": Object.freeze([
|
|
54
|
+
"voice.timeline",
|
|
55
|
+
"voice.transport",
|
|
56
|
+
]),
|
|
57
|
+
"sdk.events": Object.freeze([
|
|
58
|
+
"sdk.events",
|
|
59
|
+
"sdk.events.summary",
|
|
60
|
+
"sdk.events.raw",
|
|
61
|
+
]),
|
|
62
|
+
"render.virtual_pager": Object.freeze([
|
|
63
|
+
"render.virtual_pager",
|
|
64
|
+
"render.virtual_pager.summary",
|
|
65
|
+
"render.virtual_pager.diagnostics",
|
|
66
|
+
]),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const DEFAULT_NOISY_CATEGORY_POLICIES = Object.freeze({
|
|
70
|
+
"sdk.frames": Object.freeze({
|
|
71
|
+
sampleEvery: 5,
|
|
72
|
+
dedupeWindowMs: 150,
|
|
73
|
+
alwaysAllow: Object.freeze([
|
|
74
|
+
"coalescing_summary",
|
|
75
|
+
"stream_first_visible_latency_v1",
|
|
76
|
+
]),
|
|
77
|
+
}),
|
|
78
|
+
"audio.pipeline": Object.freeze({
|
|
79
|
+
sampleEvery: 5,
|
|
80
|
+
dedupeWindowMs: 150,
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const REDACTION_SAFE = "safe";
|
|
85
|
+
const REDACTION_FULL = "full";
|
|
86
|
+
const DEFAULT_REDACTION_MODE = REDACTION_SAFE;
|
|
87
|
+
const SUPPORTED_REDACTION_MODES = new Set([
|
|
88
|
+
REDACTION_SAFE,
|
|
89
|
+
REDACTION_FULL,
|
|
90
|
+
]);
|
|
91
|
+
const SENSITIVE_KEY_PATTERN =
|
|
92
|
+
/(?:api[-_]?key|auth(?:orization)?|bearer|cookie|credential|jwt|pass(?:word|phrase)?|private[-_]?key|secret|signature|token)/i;
|
|
93
|
+
const FULL_ONLY_KEY_PATTERN =
|
|
94
|
+
/^(?:renderedtext|fulltext|screentext)$/i;
|
|
95
|
+
const SECRET_VALUE_PATTERN =
|
|
96
|
+
/(?:^sk-[A-Za-z0-9_-]{8,}$)|(?:^Bearer\s+[^\s]+$)|(?:eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})/;
|
|
97
|
+
const MAX_REDACTION_DEPTH = 6;
|
|
98
|
+
|
|
99
|
+
function clampInt(value, min, max, fallback) {
|
|
100
|
+
const n = Number(value);
|
|
101
|
+
if (!Number.isFinite(n)) return fallback;
|
|
102
|
+
const rounded = Math.floor(n);
|
|
103
|
+
if (rounded < min) return min;
|
|
104
|
+
if (rounded > max) return max;
|
|
105
|
+
return rounded;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizeCategoryList(raw) {
|
|
109
|
+
if (!Array.isArray(raw)) return [];
|
|
110
|
+
const dedup = new Set();
|
|
111
|
+
for (const entry of raw) {
|
|
112
|
+
if (typeof entry !== "string") continue;
|
|
113
|
+
const cat = entry.trim();
|
|
114
|
+
if (!cat) continue;
|
|
115
|
+
dedup.add(cat);
|
|
116
|
+
}
|
|
117
|
+
return Array.from(dedup.values());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function expandCategoryAliases(list) {
|
|
121
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
const dedup = new Set();
|
|
125
|
+
for (const category of list) {
|
|
126
|
+
const expanded = DEBUG_CATEGORY_ALIASES[category] || [category];
|
|
127
|
+
for (const entry of expanded) {
|
|
128
|
+
if (typeof entry !== "string" || !entry) continue;
|
|
129
|
+
dedup.add(entry);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return Array.from(dedup.values());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizeRedactionMode(raw) {
|
|
136
|
+
if (raw === undefined || raw === null) return DEFAULT_REDACTION_MODE;
|
|
137
|
+
if (typeof raw !== "string") return null;
|
|
138
|
+
const mode = raw.trim().toLowerCase();
|
|
139
|
+
if (!mode) return DEFAULT_REDACTION_MODE;
|
|
140
|
+
if (!SUPPORTED_REDACTION_MODES.has(mode)) return null;
|
|
141
|
+
return mode;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function createDebugStore(opts) {
|
|
145
|
+
const options = opts || {};
|
|
146
|
+
const capacity = clampInt(options.capacity, 1, 50000, 5000);
|
|
147
|
+
const payloadMaxBytes = clampInt(options.payloadMaxBytes, 256, 65536, 2048);
|
|
148
|
+
const defaultTtlMs = clampInt(options.defaultTtlMs, 1, 600000, 120000);
|
|
149
|
+
const maxTtlMs = clampInt(options.maxTtlMs, 1, 3600000, 600000);
|
|
150
|
+
const dumpDefaultLimit = clampInt(options.dumpDefaultLimit, 1, 2000, 300);
|
|
151
|
+
const dumpMaxLimit = clampInt(options.dumpMaxLimit, 1, 10000, 2000);
|
|
152
|
+
const nowFn = typeof options.now === "function" ? options.now : () => Date.now();
|
|
153
|
+
|
|
154
|
+
const configuredCategories =
|
|
155
|
+
Array.isArray(options.categories) && options.categories.length > 0
|
|
156
|
+
? normalizeCategoryList(options.categories)
|
|
157
|
+
: DEFAULT_DEBUG_CATEGORIES;
|
|
158
|
+
const categories = new Set(configuredCategories);
|
|
159
|
+
|
|
160
|
+
const noisyPolicies = {
|
|
161
|
+
...DEFAULT_NOISY_CATEGORY_POLICIES,
|
|
162
|
+
...(options.noisyPolicies || {}),
|
|
163
|
+
};
|
|
164
|
+
const safeStringTailChars = clampInt(
|
|
165
|
+
options.safeStringTailChars,
|
|
166
|
+
16,
|
|
167
|
+
2048,
|
|
168
|
+
Math.max(64, Math.min(payloadMaxBytes, 160)),
|
|
169
|
+
);
|
|
170
|
+
const fullStringTailChars = clampInt(
|
|
171
|
+
options.fullStringTailChars,
|
|
172
|
+
safeStringTailChars,
|
|
173
|
+
4096,
|
|
174
|
+
Math.max(256, Math.min(payloadMaxBytes * 2, 1024)),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
/** @type {Map<string, number>} */
|
|
178
|
+
const enabledUntil = new Map();
|
|
179
|
+
|
|
180
|
+
/** @type {Map<string, number>} */
|
|
181
|
+
const noisyCounters = new Map();
|
|
182
|
+
|
|
183
|
+
/** @type {Map<string, { key: string, ts: number }>} */
|
|
184
|
+
const noisyLast = new Map();
|
|
185
|
+
|
|
186
|
+
/** @type {Array<object>} */
|
|
187
|
+
const ring = new Array(capacity);
|
|
188
|
+
let ringWrite = 0;
|
|
189
|
+
let ringSize = 0;
|
|
190
|
+
let seq = 0;
|
|
191
|
+
|
|
192
|
+
function isSensitiveKeyName(keyName) {
|
|
193
|
+
if (typeof keyName !== "string" || !keyName) return false;
|
|
194
|
+
return SENSITIVE_KEY_PATTERN.test(keyName);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function isFullOnlyKeyName(keyName) {
|
|
198
|
+
if (typeof keyName !== "string" || !keyName) return false;
|
|
199
|
+
return FULL_ONLY_KEY_PATTERN.test(keyName);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function redactInlineSecrets(value) {
|
|
203
|
+
if (typeof value !== "string" || !value) return value;
|
|
204
|
+
return value
|
|
205
|
+
.replace(/(Bearer\s+)[^\s"']+/gi, "$1[REDACTED]")
|
|
206
|
+
.replace(
|
|
207
|
+
/((?:api[-_]?key|token|secret|password|passphrase)\s*(?:=|:)\s*)([^,\s"']+)/gi,
|
|
208
|
+
"$1[REDACTED]",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function redactStringValue(value, mode, keyName) {
|
|
213
|
+
const sensitiveByKey = isSensitiveKeyName(keyName);
|
|
214
|
+
if (mode === REDACTION_SAFE) {
|
|
215
|
+
if (isFullOnlyKeyName(keyName)) {
|
|
216
|
+
return {
|
|
217
|
+
_fullOnly: true,
|
|
218
|
+
_chars: value.length,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (sensitiveByKey || SECRET_VALUE_PATTERN.test(value)) {
|
|
222
|
+
return "[REDACTED]";
|
|
223
|
+
}
|
|
224
|
+
const redactedInline = redactInlineSecrets(value);
|
|
225
|
+
if (redactedInline.length <= safeStringTailChars) {
|
|
226
|
+
return redactedInline;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
_truncated: true,
|
|
230
|
+
_chars: redactedInline.length,
|
|
231
|
+
_tail: redactedInline.slice(-safeStringTailChars),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (value.length <= fullStringTailChars) {
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
_truncated: true,
|
|
240
|
+
_chars: value.length,
|
|
241
|
+
_tail: value.slice(-fullStringTailChars),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function redactDataByMode(value, mode, keyName, depth) {
|
|
246
|
+
if (depth > MAX_REDACTION_DEPTH) {
|
|
247
|
+
return { _truncated: true, _depth: depth };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (typeof value === "string") {
|
|
251
|
+
return redactStringValue(value, mode, keyName);
|
|
252
|
+
}
|
|
253
|
+
if (
|
|
254
|
+
value === null ||
|
|
255
|
+
typeof value === "number" ||
|
|
256
|
+
typeof value === "boolean"
|
|
257
|
+
) {
|
|
258
|
+
return value;
|
|
259
|
+
}
|
|
260
|
+
if (Array.isArray(value)) {
|
|
261
|
+
return value.map((entry) =>
|
|
262
|
+
redactDataByMode(entry, mode, keyName, depth + 1),
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (typeof value === "object") {
|
|
266
|
+
const out = {};
|
|
267
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
268
|
+
out[key] = redactDataByMode(entry, mode, key, depth + 1);
|
|
269
|
+
}
|
|
270
|
+
return out;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return { value };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function buildRedactedDataModes(data) {
|
|
277
|
+
const safe = redactDataByMode(data, REDACTION_SAFE, null, 0);
|
|
278
|
+
const full = redactDataByMode(data, REDACTION_FULL, null, 0);
|
|
279
|
+
let safeSerialized;
|
|
280
|
+
try {
|
|
281
|
+
safeSerialized = JSON.stringify(safe);
|
|
282
|
+
} catch {
|
|
283
|
+
safeSerialized = "{\"_serializationError\":true}";
|
|
284
|
+
}
|
|
285
|
+
return { safe, full, safeSerialized };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function pruneExpired(nowMs) {
|
|
289
|
+
for (const [cat, expiresAt] of enabledUntil) {
|
|
290
|
+
if (expiresAt <= nowMs) {
|
|
291
|
+
enabledUntil.delete(cat);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function getEnabledCategories(nowMs) {
|
|
297
|
+
const ts = Number.isFinite(nowMs) ? nowMs : nowFn();
|
|
298
|
+
pruneExpired(ts);
|
|
299
|
+
return Array.from(enabledUntil.entries())
|
|
300
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
301
|
+
.map(([cat, expiresAtMs]) => ({ cat, expiresAtMs }));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function isEnabled(category, nowMs) {
|
|
305
|
+
if (typeof category !== "string" || !category) return false;
|
|
306
|
+
const ts = Number.isFinite(nowMs) ? nowMs : nowFn();
|
|
307
|
+
const expiresAt = enabledUntil.get(category);
|
|
308
|
+
if (!expiresAt) return false;
|
|
309
|
+
if (expiresAt <= ts) {
|
|
310
|
+
enabledUntil.delete(category);
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function unknownCategories(list) {
|
|
317
|
+
const unknown = [];
|
|
318
|
+
for (const cat of list) {
|
|
319
|
+
if (!categories.has(cat)) unknown.push(cat);
|
|
320
|
+
}
|
|
321
|
+
return unknown;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function setCategories(request) {
|
|
325
|
+
const req = request || {};
|
|
326
|
+
const enable = expandCategoryAliases(normalizeCategoryList(req.enable));
|
|
327
|
+
const disable = expandCategoryAliases(normalizeCategoryList(req.disable));
|
|
328
|
+
const nowMs = nowFn();
|
|
329
|
+
|
|
330
|
+
if (enable.length === 0 && disable.length === 0) {
|
|
331
|
+
return {
|
|
332
|
+
ok: false,
|
|
333
|
+
error: "debug-set requires at least one category in enable or disable",
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const overlap = enable.filter((cat) => disable.includes(cat));
|
|
338
|
+
if (overlap.length > 0) {
|
|
339
|
+
return {
|
|
340
|
+
ok: false,
|
|
341
|
+
error: `debug-set category cannot be both enabled and disabled: ${overlap.join(", ")}`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const unknown = unknownCategories([...enable, ...disable]);
|
|
346
|
+
if (unknown.length > 0) {
|
|
347
|
+
return {
|
|
348
|
+
ok: false,
|
|
349
|
+
error: `Unknown debug categories: ${unknown.join(", ")}`,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
let ttlMs = null;
|
|
354
|
+
let expiresAtMs = null;
|
|
355
|
+
if (enable.length > 0) {
|
|
356
|
+
const rawTtl = req.ttlMs === undefined || req.ttlMs === null ? defaultTtlMs : Number(req.ttlMs);
|
|
357
|
+
if (!Number.isFinite(rawTtl) || rawTtl <= 0) {
|
|
358
|
+
return {
|
|
359
|
+
ok: false,
|
|
360
|
+
error: "debug-set ttlMs must be a positive number",
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
ttlMs = Math.min(Math.floor(rawTtl), maxTtlMs);
|
|
364
|
+
expiresAtMs = nowMs + ttlMs;
|
|
365
|
+
for (const cat of enable) {
|
|
366
|
+
enabledUntil.set(cat, expiresAtMs);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (disable.length > 0) {
|
|
371
|
+
for (const cat of disable) {
|
|
372
|
+
enabledUntil.delete(cat);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
ok: true,
|
|
378
|
+
nowMs,
|
|
379
|
+
ttlMs,
|
|
380
|
+
expiresAtMs,
|
|
381
|
+
applied: { enable, disable },
|
|
382
|
+
enabled: getEnabledCategories(nowMs),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function normalizeData(data) {
|
|
387
|
+
let normalized = data;
|
|
388
|
+
if (normalized === undefined) {
|
|
389
|
+
normalized = {};
|
|
390
|
+
} else if (
|
|
391
|
+
normalized === null ||
|
|
392
|
+
typeof normalized !== "object" ||
|
|
393
|
+
Array.isArray(normalized)
|
|
394
|
+
) {
|
|
395
|
+
normalized = { value: normalized };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
let serialized;
|
|
399
|
+
try {
|
|
400
|
+
serialized = JSON.stringify(normalized);
|
|
401
|
+
} catch {
|
|
402
|
+
normalized = { _serializationError: true };
|
|
403
|
+
serialized = JSON.stringify(normalized);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const bytes = Buffer.byteLength(serialized, "utf8");
|
|
407
|
+
if (bytes <= payloadMaxBytes) {
|
|
408
|
+
return { data: normalized, serialized };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const tailMaxChars = Math.max(64, Math.min(payloadMaxBytes, 1024));
|
|
412
|
+
const truncated = {
|
|
413
|
+
_truncated: true,
|
|
414
|
+
_bytes: bytes,
|
|
415
|
+
_tail: serialized.slice(-tailMaxChars),
|
|
416
|
+
};
|
|
417
|
+
return {
|
|
418
|
+
data: truncated,
|
|
419
|
+
serialized: JSON.stringify(truncated),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function allowByNoisyPolicy(cat, eventName, serializedData, ts) {
|
|
424
|
+
const rawPolicy = noisyPolicies[cat];
|
|
425
|
+
if (!rawPolicy) return true;
|
|
426
|
+
|
|
427
|
+
const alwaysAllow = rawPolicy.alwaysAllow;
|
|
428
|
+
if (Array.isArray(alwaysAllow) && alwaysAllow.includes(eventName)) {
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const sampleEvery = clampInt(rawPolicy.sampleEvery, 1, 1000, 1);
|
|
433
|
+
const dedupeWindowMs = clampInt(rawPolicy.dedupeWindowMs, 0, 60000, 0);
|
|
434
|
+
|
|
435
|
+
const nextCount = (noisyCounters.get(cat) || 0) + 1;
|
|
436
|
+
noisyCounters.set(cat, nextCount);
|
|
437
|
+
if (sampleEvery > 1 && nextCount % sampleEvery !== 1) {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (dedupeWindowMs > 0) {
|
|
442
|
+
const key = `${eventName}|${serializedData.slice(0, 160)}`;
|
|
443
|
+
const prev = noisyLast.get(cat);
|
|
444
|
+
if (prev && prev.key === key && ts - prev.ts <= dedupeWindowMs) {
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
noisyLast.set(cat, { key, ts });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function append(event) {
|
|
454
|
+
ring[ringWrite] = event;
|
|
455
|
+
ringWrite = (ringWrite + 1) % capacity;
|
|
456
|
+
if (ringSize < capacity) {
|
|
457
|
+
ringSize += 1;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function emit(event) {
|
|
462
|
+
const raw = event || {};
|
|
463
|
+
const cat = typeof raw.cat === "string" ? raw.cat.trim() : "";
|
|
464
|
+
if (!cat) return false;
|
|
465
|
+
if (!isEnabled(cat)) return false;
|
|
466
|
+
|
|
467
|
+
const ts = Number.isFinite(raw.ts) ? Math.floor(raw.ts) : nowFn();
|
|
468
|
+
const eventName =
|
|
469
|
+
typeof raw.event === "string" && raw.event.trim()
|
|
470
|
+
? raw.event.trim()
|
|
471
|
+
: "event";
|
|
472
|
+
const severity =
|
|
473
|
+
raw.severity === "info" ||
|
|
474
|
+
raw.severity === "warn" ||
|
|
475
|
+
raw.severity === "error"
|
|
476
|
+
? raw.severity
|
|
477
|
+
: "debug";
|
|
478
|
+
|
|
479
|
+
const normalized = normalizeData(raw.data);
|
|
480
|
+
const redactedDataModes = buildRedactedDataModes(normalized.data);
|
|
481
|
+
if (!allowByNoisyPolicy(cat, eventName, redactedDataModes.safeSerialized, ts)) {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const out = {
|
|
486
|
+
ts,
|
|
487
|
+
cat,
|
|
488
|
+
event: eventName,
|
|
489
|
+
severity,
|
|
490
|
+
seq: ++seq,
|
|
491
|
+
dataSafe: redactedDataModes.safe,
|
|
492
|
+
dataFull: redactedDataModes.full,
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
if (typeof raw.sessionKey === "string" && raw.sessionKey) {
|
|
496
|
+
out.sessionKey = raw.sessionKey;
|
|
497
|
+
}
|
|
498
|
+
if (typeof raw.runId === "string" && raw.runId) {
|
|
499
|
+
out.runId = raw.runId;
|
|
500
|
+
}
|
|
501
|
+
if (typeof raw.screen === "string" && raw.screen) {
|
|
502
|
+
out.screen = raw.screen;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
append(out);
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function getAllEvents() {
|
|
510
|
+
if (ringSize === 0) return [];
|
|
511
|
+
const out = [];
|
|
512
|
+
const oldest = (ringWrite - ringSize + capacity) % capacity;
|
|
513
|
+
for (let i = 0; i < ringSize; i += 1) {
|
|
514
|
+
out.push(ring[(oldest + i) % capacity]);
|
|
515
|
+
}
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function formatEventForDump(evt, redactionMode) {
|
|
520
|
+
const out = {
|
|
521
|
+
ts: evt.ts,
|
|
522
|
+
cat: evt.cat,
|
|
523
|
+
event: evt.event,
|
|
524
|
+
severity: evt.severity,
|
|
525
|
+
seq: evt.seq,
|
|
526
|
+
data:
|
|
527
|
+
redactionMode === REDACTION_FULL
|
|
528
|
+
? evt.dataFull
|
|
529
|
+
: evt.dataSafe,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
if (typeof evt.sessionKey === "string" && evt.sessionKey) {
|
|
533
|
+
out.sessionKey = evt.sessionKey;
|
|
534
|
+
}
|
|
535
|
+
if (typeof evt.runId === "string" && evt.runId) {
|
|
536
|
+
out.runId = evt.runId;
|
|
537
|
+
}
|
|
538
|
+
if (typeof evt.screen === "string" && evt.screen) {
|
|
539
|
+
out.screen = evt.screen;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return out;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function dump(request) {
|
|
546
|
+
const req = request || {};
|
|
547
|
+
const nowMs = nowFn();
|
|
548
|
+
const redaction = normalizeRedactionMode(req.redaction);
|
|
549
|
+
if (!redaction) {
|
|
550
|
+
return {
|
|
551
|
+
ok: false,
|
|
552
|
+
error: "debug-dump redaction must be one of: safe, full",
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
const categoriesFilter = expandCategoryAliases(
|
|
556
|
+
normalizeCategoryList(req.categories),
|
|
557
|
+
);
|
|
558
|
+
const unknown = unknownCategories(categoriesFilter);
|
|
559
|
+
if (unknown.length > 0) {
|
|
560
|
+
return {
|
|
561
|
+
ok: false,
|
|
562
|
+
error: `Unknown debug categories: ${unknown.join(", ")}`,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (
|
|
567
|
+
req.limit !== undefined &&
|
|
568
|
+
(!Number.isFinite(Number(req.limit)) || Number(req.limit) <= 0)
|
|
569
|
+
) {
|
|
570
|
+
return {
|
|
571
|
+
ok: false,
|
|
572
|
+
error: "debug-dump limit must be a positive number",
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (
|
|
577
|
+
req.sinceMs !== undefined &&
|
|
578
|
+
(!Number.isFinite(Number(req.sinceMs)) || Number(req.sinceMs) < 0)
|
|
579
|
+
) {
|
|
580
|
+
return {
|
|
581
|
+
ok: false,
|
|
582
|
+
error: "debug-dump sinceMs must be a non-negative number",
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (
|
|
587
|
+
req.sinceAgeMs !== undefined &&
|
|
588
|
+
(!Number.isFinite(Number(req.sinceAgeMs)) || Number(req.sinceAgeMs) < 0)
|
|
589
|
+
) {
|
|
590
|
+
return {
|
|
591
|
+
ok: false,
|
|
592
|
+
error: "debug-dump sinceAgeMs must be a non-negative number",
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (
|
|
597
|
+
req.untilMs !== undefined &&
|
|
598
|
+
(!Number.isFinite(Number(req.untilMs)) || Number(req.untilMs) < 0)
|
|
599
|
+
) {
|
|
600
|
+
return {
|
|
601
|
+
ok: false,
|
|
602
|
+
error: "debug-dump untilMs must be a non-negative number",
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const limit = clampInt(req.limit, 1, dumpMaxLimit, dumpDefaultLimit);
|
|
607
|
+
const sinceMs =
|
|
608
|
+
req.sinceMs !== undefined
|
|
609
|
+
? Math.floor(Number(req.sinceMs))
|
|
610
|
+
: req.sinceAgeMs !== undefined
|
|
611
|
+
? nowMs - Math.floor(Number(req.sinceAgeMs))
|
|
612
|
+
: null;
|
|
613
|
+
const untilMs =
|
|
614
|
+
req.untilMs !== undefined ? Math.floor(Number(req.untilMs)) : null;
|
|
615
|
+
|
|
616
|
+
if (
|
|
617
|
+
sinceMs !== null &&
|
|
618
|
+
untilMs !== null &&
|
|
619
|
+
Number.isFinite(sinceMs) &&
|
|
620
|
+
Number.isFinite(untilMs) &&
|
|
621
|
+
untilMs < sinceMs
|
|
622
|
+
) {
|
|
623
|
+
return {
|
|
624
|
+
ok: false,
|
|
625
|
+
error: "debug-dump untilMs must be greater than or equal to sinceMs",
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const categorySet =
|
|
630
|
+
categoriesFilter.length > 0 ? new Set(categoriesFilter) : null;
|
|
631
|
+
const filtered = [];
|
|
632
|
+
const all = getAllEvents();
|
|
633
|
+
for (const evt of all) {
|
|
634
|
+
if (categorySet && !categorySet.has(evt.cat)) continue;
|
|
635
|
+
if (sinceMs !== null && evt.ts < sinceMs) continue;
|
|
636
|
+
if (untilMs !== null && evt.ts > untilMs) continue;
|
|
637
|
+
filtered.push(evt);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const events =
|
|
641
|
+
filtered.length > limit
|
|
642
|
+
? filtered.slice(filtered.length - limit)
|
|
643
|
+
: filtered;
|
|
644
|
+
const formattedEvents = events.map((evt) =>
|
|
645
|
+
formatEventForDump(evt, redaction),
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
return {
|
|
649
|
+
ok: true,
|
|
650
|
+
nowMs,
|
|
651
|
+
sinceMs,
|
|
652
|
+
untilMs,
|
|
653
|
+
redaction,
|
|
654
|
+
categories: categoriesFilter,
|
|
655
|
+
limit,
|
|
656
|
+
totalMatched: filtered.length,
|
|
657
|
+
returned: formattedEvents.length,
|
|
658
|
+
dropped: Math.max(0, filtered.length - formattedEvents.length),
|
|
659
|
+
enabled: getEnabledCategories(nowMs),
|
|
660
|
+
events: formattedEvents,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function getSnapshot(nowMs) {
|
|
665
|
+
const serverNowMs = Number.isFinite(nowMs) ? Math.floor(nowMs) : nowFn();
|
|
666
|
+
return {
|
|
667
|
+
serverNowMs,
|
|
668
|
+
enabled: getEnabledCategories(serverNowMs),
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
return {
|
|
673
|
+
setCategories,
|
|
674
|
+
dump,
|
|
675
|
+
emit,
|
|
676
|
+
isEnabled,
|
|
677
|
+
getSnapshot,
|
|
678
|
+
getEnabledCategories,
|
|
679
|
+
getKnownCategories() {
|
|
680
|
+
return Array.from(categories.values()).sort();
|
|
681
|
+
},
|
|
682
|
+
getConfig() {
|
|
683
|
+
return {
|
|
684
|
+
capacity,
|
|
685
|
+
payloadMaxBytes,
|
|
686
|
+
defaultTtlMs,
|
|
687
|
+
maxTtlMs,
|
|
688
|
+
dumpDefaultLimit,
|
|
689
|
+
dumpMaxLimit,
|
|
690
|
+
defaultRedaction: DEFAULT_REDACTION_MODE,
|
|
691
|
+
};
|
|
692
|
+
},
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
export {
|
|
697
|
+
createDebugStore,
|
|
698
|
+
DEFAULT_DEBUG_CATEGORIES,
|
|
699
|
+
DEFAULT_NOISY_CATEGORY_POLICIES,
|
|
700
|
+
};
|