@polderlabs/bizar 4.5.1 → 4.7.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/bizar-dash/dist/assets/__vite-browser-external-BIHI7g3E.js +0 -1
- package/bizar-dash/dist/assets/main-DAlLdW8I.css +1 -0
- package/bizar-dash/dist/assets/main-DGGq-iZI.js +361 -0
- package/bizar-dash/dist/assets/main-DGGq-iZI.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-O6ANdD4W.js → mobile-CWqPoGaT.js} +18 -19
- package/bizar-dash/dist/assets/mobile-CWqPoGaT.js.map +1 -0
- package/bizar-dash/dist/assets/{mobile-DQLFCjwJ.js → mobile-i4Uv9eW8.js} +1 -2
- package/bizar-dash/dist/assets/{mobile-DQLFCjwJ.js.map → mobile-i4Uv9eW8.js.map} +1 -1
- package/bizar-dash/dist/index.html +3 -3
- package/bizar-dash/dist/mobile.html +2 -2
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/bizar-dash/src/server/headroom.mjs +37 -35
- package/bizar-dash/src/server/logger.mjs +71 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +84 -37
- package/bizar-dash/src/server/metrics.mjs +193 -0
- package/bizar-dash/src/server/mods-loader.mjs +13 -5
- package/bizar-dash/src/server/providers-store.mjs +73 -1
- package/bizar-dash/src/server/routes/chat.mjs +41 -0
- package/bizar-dash/src/server/routes/lightrag.mjs +7 -2
- package/bizar-dash/src/server/routes/memory.mjs +13 -4
- package/bizar-dash/src/server/routes/misc.mjs +2 -1
- package/bizar-dash/src/server/routes/overview.mjs +2 -1
- package/bizar-dash/src/server/schedules-runner.mjs +4 -4
- package/bizar-dash/src/server/server.mjs +101 -37
- package/bizar-dash/src/server/watcher.mjs +2 -2
- package/bizar-dash/src/web/App.tsx +40 -6
- package/bizar-dash/src/web/components/SearchModal.tsx +3 -2
- package/bizar-dash/src/web/components/Toast.tsx +1 -1
- package/bizar-dash/src/web/components/Topbar.tsx +1 -1
- package/bizar-dash/src/web/components/VirtualList.tsx +53 -0
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +17 -11
- package/bizar-dash/src/web/hooks/useI18n.ts +13 -0
- package/bizar-dash/src/web/lib/api.ts +12 -11
- package/bizar-dash/src/web/lib/i18n.ts +25 -0
- package/bizar-dash/src/web/locales/en.json +52 -0
- package/bizar-dash/src/web/main.tsx +5 -0
- package/bizar-dash/src/web/styles/chat.css +2 -0
- package/bizar-dash/src/web/styles/main.css +52 -23
- package/bizar-dash/src/web/styles/tasks.css +2 -0
- package/bizar-dash/src/web/views/Activity.tsx +24 -17
- package/bizar-dash/src/web/views/History.tsx +90 -75
- package/bizar-dash/src/web/views/Memory.tsx +4 -3
- package/bizar-dash/src/web/views/MiniMaxUsage.tsx +3 -2
- package/bizar-dash/src/web/views/Overview.tsx +15 -10
- package/bizar-dash/src/web/views/Settings.tsx +9 -2
- package/bizar-dash/src/web/views/Skills.tsx +3 -2
- package/bizar-dash/src/web/views/Tasks.tsx +4 -3
- package/bizar-dash/src/web/views/memory/MemoryOverview.tsx +8 -6
- package/bizar-dash/tests/cli-bugfixes.test.mjs +151 -0
- package/bizar-dash/tests/cli-refactor.test.mjs +184 -0
- package/bizar-dash/tests/components/Button.test.tsx +41 -0
- package/bizar-dash/tests/components/Card.test.tsx +42 -0
- package/bizar-dash/tests/components/Modal.test.tsx +104 -0
- package/bizar-dash/tests/components/Spinner.test.tsx +32 -0
- package/bizar-dash/tests/components/StatusBadge.test.tsx +35 -0
- package/bizar-dash/tests/components/Toast.test.tsx +108 -0
- package/bizar-dash/tests/frontend-bugfixes.test.mjs +151 -0
- package/bizar-dash/tests/hooks/useModal.test.tsx +84 -0
- package/bizar-dash/tests/hooks/useToast.test.tsx +50 -0
- package/bizar-dash/tests/lib/i18n.test.ts +46 -0
- package/bizar-dash/tests/lib/utils.test.ts +194 -0
- package/bizar-dash/tests/logger.test.mjs +207 -0
- package/bizar-dash/tests/metrics.test.mjs +183 -0
- package/bizar-dash/tests/server-bugfixes.test.mjs +318 -0
- package/bizar-dash/tests/setup.ts +7 -0
- package/bizar-dash/vitest.config.ts +13 -0
- package/cli/artifact-cli.mjs +605 -0
- package/cli/artifact-render.mjs +621 -0
- package/cli/artifact-server.mjs +847 -0
- package/cli/artifact.mjs +38 -2089
- package/cli/bg.mjs +5 -13
- package/cli/bin.mjs +170 -1348
- package/cli/commands/artifact.mjs +20 -0
- package/cli/commands/dash.mjs +160 -0
- package/cli/commands/headroom.mjs +204 -0
- package/cli/commands/install.mjs +169 -0
- package/cli/commands/memory.mjs +25 -0
- package/cli/commands/minimax.mjs +265 -0
- package/cli/commands/mod.mjs +185 -0
- package/cli/commands/service.mjs +65 -0
- package/cli/commands/usage.mjs +109 -0
- package/cli/commands/util.mjs +306 -0
- package/cli/doctor.mjs +6 -16
- package/cli/memory.mjs +65 -21
- package/cli/provision.mjs +10 -15
- package/cli/service-controller.mjs +1 -11
- package/cli/service.mjs +1 -11
- package/cli/utils.mjs +41 -1
- package/install.sh +1 -2
- package/package.json +8 -3
- package/bizar-dash/dist/assets/main-eWZ4NlCL.css +0 -1
- package/bizar-dash/dist/assets/main-usWhlPWa.js +0 -362
- package/bizar-dash/dist/assets/main-usWhlPWa.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-O6ANdD4W.js.map +0 -1
|
@@ -1,2 +1 @@
|
|
|
1
|
-
import{
|
|
2
|
-
//# sourceMappingURL=mobile-DQLFCjwJ.js.map
|
|
1
|
+
import{am as r,j as o,r as e,an as n}from"./mobile-CWqPoGaT.js";const t=document.getElementById("root");if(!t)throw new Error("Root element #root not found");r(t).render(o.jsx(e.StrictMode,{children:o.jsx(n,{})}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mobile-
|
|
1
|
+
{"version":3,"file":"mobile-i4Uv9eW8.js","sources":["../../src/web/mobile.tsx"],"sourcesContent":["// src/mobile.tsx — entry point for mobile dashboard.\nimport { StrictMode } from 'react';\nimport { createRoot } from 'react-dom/client';\nimport { MobileApp } from './MobileApp';\nimport './styles/mobile.css';\n\nconst root = document.getElementById('root');\nif (!root) throw new Error('Root element #root not found');\ncreateRoot(root).render(\n <StrictMode>\n <MobileApp />\n </StrictMode>,\n);\n"],"names":["root","createRoot","jsx","StrictMode","MobileApp"],"mappings":"gEAMA,MAAMA,EAAO,SAAS,eAAe,MAAM,EAC3C,GAAI,CAACA,EAAM,MAAM,IAAI,MAAM,8BAA8B,EACzDC,EAAWD,CAAI,EAAE,OACfE,EAAAA,IAACC,EAAAA,WAAA,CACC,SAAAD,EAAAA,IAACE,EAAA,CAAA,CAAU,CAAA,CACb,CACF"}
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
}
|
|
30
30
|
})();
|
|
31
31
|
</script>
|
|
32
|
-
<script type="module" crossorigin src="/assets/main-
|
|
33
|
-
<link rel="modulepreload" crossorigin href="/assets/mobile-
|
|
32
|
+
<script type="module" crossorigin src="/assets/main-DGGq-iZI.js"></script>
|
|
33
|
+
<link rel="modulepreload" crossorigin href="/assets/mobile-CWqPoGaT.js">
|
|
34
34
|
<link rel="stylesheet" crossorigin href="/assets/mobile-CsZQAswA.css">
|
|
35
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
35
|
+
<link rel="stylesheet" crossorigin href="/assets/main-DAlLdW8I.css">
|
|
36
36
|
</head>
|
|
37
37
|
<body>
|
|
38
38
|
<div id="root"></div>
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
13
13
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
14
14
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
|
15
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
16
|
-
<link rel="modulepreload" crossorigin href="/assets/mobile-
|
|
15
|
+
<script type="module" crossorigin src="/assets/mobile-i4Uv9eW8.js"></script>
|
|
16
|
+
<link rel="modulepreload" crossorigin href="/assets/mobile-CWqPoGaT.js">
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/mobile-CsZQAswA.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"4.1.9","results":[[":tests/lib/utils.test.ts",{"duration":20.207488999999953,"failed":false}],[":tests/components/Button.test.tsx",{"duration":65.99081000000001,"failed":false}],[":tests/components/Card.test.tsx",{"duration":24.894168999999977,"failed":false}],[":tests/hooks/useModal.test.tsx",{"duration":91.24809300000004,"failed":false}],[":tests/components/Toast.test.tsx",{"duration":112.40757200000002,"failed":false}],[":tests/components/Modal.test.tsx",{"duration":113.472669,"failed":false}],[":tests/lib/i18n.test.ts",{"duration":3.547411000000011,"failed":false}],[":tests/components/StatusBadge.test.tsx",{"duration":25.80152300000003,"failed":false}],[":tests/hooks/useToast.test.tsx",{"duration":74.72992599999998,"failed":false}],[":tests/components/Spinner.test.tsx",{"duration":43.51148800000004,"failed":false}]]}
|
|
@@ -115,42 +115,44 @@ async function findProxyPid(port) {
|
|
|
115
115
|
}
|
|
116
116
|
} catch { /* ignore */ }
|
|
117
117
|
|
|
118
|
-
// Try to find process listening on the port via /proc/net/tcp (Linux)
|
|
118
|
+
// Try to find process listening on the port via /proc/net/tcp (Linux only).
|
|
119
|
+
// On macOS/Windows /proc does not exist — return null early to avoid noisy errors.
|
|
120
|
+
if (process.platform !== 'linux') {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
119
123
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} catch { /* skip */ }
|
|
153
|
-
}
|
|
124
|
+
const netTcp = readFileSync('/proc/net/tcp', 'utf8');
|
|
125
|
+
const portHex = port.toString(16).toUpperCase().padStart(4, '0');
|
|
126
|
+
const lines = netTcp.split('\n');
|
|
127
|
+
for (const line of lines.slice(1)) {
|
|
128
|
+
// Format: sl local_address rem_address st tx_queue rx_queue ... inode
|
|
129
|
+
const parts = line.trim().split(/\s+/);
|
|
130
|
+
if (parts.length < 10) continue;
|
|
131
|
+
const local = parts[1];
|
|
132
|
+
const localPortHex = local.split(':')[1];
|
|
133
|
+
if (localPortHex?.toUpperCase() === portHex) {
|
|
134
|
+
// Try to get PID from the socket inode
|
|
135
|
+
const inode = parts[9];
|
|
136
|
+
if (inode && inode !== '0') {
|
|
137
|
+
// Scan /proc/*/fd/* for socket inodes
|
|
138
|
+
const { readdirSync, readlinkSync } = await import('node:fs');
|
|
139
|
+
const procDir = '/proc';
|
|
140
|
+
const pids = readdirSync(procDir).filter(
|
|
141
|
+
(n) => /^\d+$/.test(n) && !isNaN(parseInt(n, 10)),
|
|
142
|
+
);
|
|
143
|
+
for (const pid of pids) {
|
|
144
|
+
try {
|
|
145
|
+
const fdDir = join(procDir, pid, 'fd');
|
|
146
|
+
const fds = readdirSync(fdDir);
|
|
147
|
+
for (const fd of fds) {
|
|
148
|
+
try {
|
|
149
|
+
const link = readlinkSync(join(fdDir, fd));
|
|
150
|
+
if (link.includes(`socket:[${inode}]`)) {
|
|
151
|
+
return parseInt(pid, 10);
|
|
152
|
+
}
|
|
153
|
+
} catch { /* skip */ }
|
|
154
|
+
}
|
|
155
|
+
} catch { /* skip */ }
|
|
154
156
|
}
|
|
155
157
|
}
|
|
156
158
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/logger.mjs
|
|
3
|
+
*
|
|
4
|
+
* v4.7.0 — Structured JSON logger with level filtering.
|
|
5
|
+
*
|
|
6
|
+
* Replaces ad-hoc `console.log('[module] message')` calls across the
|
|
7
|
+
* server with a single, level-gated logger that emits one JSON object
|
|
8
|
+
* per line. Output goes to the matching console.* stream (log/warn/
|
|
9
|
+
* error) so existing terminal capture keeps working, but the body is
|
|
10
|
+
* machine-parseable.
|
|
11
|
+
*
|
|
12
|
+
* Level selection: BIZAR_LOG_LEVEL env var (debug | info | warn | error).
|
|
13
|
+
* Default `info` matches the existing behaviour — everything but debug
|
|
14
|
+
* is visible. Set `debug` to surface verbose tracing during development.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* import { info, warn, error, child } from './logger.mjs';
|
|
18
|
+
* info('server started', { port: 4317 });
|
|
19
|
+
* warn('rate limited', { module: 'lightrag', err: e.message });
|
|
20
|
+
* const log = child({ module: 'overview' });
|
|
21
|
+
* log.info('snapshot built', { size: 1234 });
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
25
|
+
const ENV_LEVEL = process.env.BIZAR_LOG_LEVEL;
|
|
26
|
+
const currentLevel = LEVELS[ENV_LEVEL] !== undefined ? ENV_LEVEL : 'info';
|
|
27
|
+
|
|
28
|
+
function format(level, msg, ctx) {
|
|
29
|
+
const entry = {
|
|
30
|
+
ts: new Date().toISOString(),
|
|
31
|
+
level,
|
|
32
|
+
msg,
|
|
33
|
+
...(ctx && typeof ctx === 'object' ? ctx : {}),
|
|
34
|
+
};
|
|
35
|
+
return JSON.stringify(entry);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function shouldLog(level) {
|
|
39
|
+
return LEVELS[currentLevel] <= LEVELS[level];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function debug(msg, ctx) {
|
|
43
|
+
if (shouldLog('debug')) console.log(format('debug', msg, ctx));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function info(msg, ctx) {
|
|
47
|
+
if (shouldLog('info')) console.log(format('info', msg, ctx));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function warn(msg, ctx) {
|
|
51
|
+
if (shouldLog('warn')) console.warn(format('warn', msg, ctx));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function error(msg, ctx) {
|
|
55
|
+
if (shouldLog('error')) console.error(format('error', msg, ctx));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build a child logger that binds a context object to every log call.
|
|
60
|
+
* Useful for tagging a series of related entries with a module/session id.
|
|
61
|
+
*/
|
|
62
|
+
export function child(ctx) {
|
|
63
|
+
return {
|
|
64
|
+
debug: (m, c) => debug(m, { ...ctx, ...(c && typeof c === 'object' ? c : {}) }),
|
|
65
|
+
info: (m, c) => info(m, { ...ctx, ...(c && typeof c === 'object' ? c : {}) }),
|
|
66
|
+
warn: (m, c) => warn(m, { ...ctx, ...(c && typeof c === 'object' ? c : {}) }),
|
|
67
|
+
error: (m, c) => error(m, { ...ctx, ...(c && typeof c === 'object' ? c : {}) }),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { currentLevel };
|
|
@@ -52,9 +52,32 @@ import https from 'node:https';
|
|
|
52
52
|
|
|
53
53
|
import { atomicWriteJson } from './routes/_shared.mjs';
|
|
54
54
|
import { parseFrontmatter } from './yaml.mjs';
|
|
55
|
+
import { warn as logWarn } from './logger.mjs';
|
|
55
56
|
|
|
56
57
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* v4.7.0 — Per-site log rate limiter for hot-path catches.
|
|
61
|
+
*
|
|
62
|
+
* Several empty catches below sit on stdout/stderr `data` handlers
|
|
63
|
+
* that can fire dozens of times per second when LightRAG is chatty
|
|
64
|
+
* (or the disk is slow). Without rate-limiting, a transient disk
|
|
65
|
+
* hiccup floods the log with thousands of identical warnings and
|
|
66
|
+
* hides the real problem underneath.
|
|
67
|
+
*
|
|
68
|
+
* Returns true the first time it's called for a key within `intervalMs`,
|
|
69
|
+
* then false until the window passes. Default 60s — matches the
|
|
70
|
+
* "if it's still broken, say it again a minute later" pattern.
|
|
71
|
+
*/
|
|
72
|
+
const _lastLog = new Map();
|
|
73
|
+
function _rateLimit(key, intervalMs = 60_000) {
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
const last = _lastLog.get(key) || 0;
|
|
76
|
+
if (now - last < intervalMs) return false;
|
|
77
|
+
_lastLog.set(key, now);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
58
81
|
function deepMerge(target, source) {
|
|
59
82
|
const out = { ...target };
|
|
60
83
|
if (source === null || source === undefined) return out;
|
|
@@ -84,7 +107,8 @@ function readLogTail(filePath, n = 30) {
|
|
|
84
107
|
const content = readFileSync(filePath, 'utf8');
|
|
85
108
|
const lines = content.split('\n');
|
|
86
109
|
return lines.slice(-n);
|
|
87
|
-
} catch {
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.warn('[lightrag] swallowed in readLogTail:', err?.message || err);
|
|
88
112
|
return [];
|
|
89
113
|
}
|
|
90
114
|
}
|
|
@@ -206,7 +230,8 @@ export function resolveLightRAGConfig(projectRoot) {
|
|
|
206
230
|
if (existsSync(path)) {
|
|
207
231
|
try {
|
|
208
232
|
userCfg = JSON.parse(readFileSync(path, 'utf8')).lightrag || {};
|
|
209
|
-
} catch {
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.warn('[lightrag] swallowed in resolveLightRAGConfig.readMemoryJson:', err?.message || err);
|
|
210
235
|
userCfg = {};
|
|
211
236
|
}
|
|
212
237
|
}
|
|
@@ -269,8 +294,9 @@ export function findLightragBinary() {
|
|
|
269
294
|
timeout: 3000,
|
|
270
295
|
}).trim();
|
|
271
296
|
if (path) return path;
|
|
272
|
-
} catch {
|
|
297
|
+
} catch (err) {
|
|
273
298
|
// command returned non-zero — fall through
|
|
299
|
+
console.warn('[lightrag] swallowed in findLightragBinary.which:', err?.message || err);
|
|
274
300
|
}
|
|
275
301
|
return null;
|
|
276
302
|
}
|
|
@@ -336,7 +362,8 @@ export async function isRunning(config) {
|
|
|
336
362
|
try {
|
|
337
363
|
const res = await httpGet(`http://${config.host}:${config.port}/health`, 3000);
|
|
338
364
|
return res.status === 200;
|
|
339
|
-
} catch {
|
|
365
|
+
} catch (err) {
|
|
366
|
+
console.warn('[lightrag] swallowed in isRunning.healthCheck:', err?.message || err);
|
|
340
367
|
return false;
|
|
341
368
|
}
|
|
342
369
|
}
|
|
@@ -361,7 +388,8 @@ function readPidAlive(pidFile) {
|
|
|
361
388
|
if (!Number.isFinite(pid) || pid <= 0) return { pid: null, alive: false };
|
|
362
389
|
const alive = killPid(pid, 0); // signal 0 = probe
|
|
363
390
|
return { pid, alive };
|
|
364
|
-
} catch {
|
|
391
|
+
} catch (err) {
|
|
392
|
+
console.warn('[lightrag] swallowed in readPidAlive:', err?.message || err);
|
|
365
393
|
return { pid: null, alive: false };
|
|
366
394
|
}
|
|
367
395
|
}
|
|
@@ -387,7 +415,9 @@ export async function startServer(config, { logger } = {}) {
|
|
|
387
415
|
if (existing && !existingAlive) {
|
|
388
416
|
try {
|
|
389
417
|
unlinkSync(pidFile);
|
|
390
|
-
} catch {
|
|
418
|
+
} catch (err) {
|
|
419
|
+
console.warn('[lightrag] swallowed in startServer.unlinkStalePid:', err?.message || err);
|
|
420
|
+
}
|
|
391
421
|
}
|
|
392
422
|
|
|
393
423
|
if (!findLightragBinary()) {
|
|
@@ -437,11 +467,23 @@ export async function startServer(config, { logger } = {}) {
|
|
|
437
467
|
let fd = -1;
|
|
438
468
|
try {
|
|
439
469
|
fd = openSync(logFile, 'a');
|
|
440
|
-
child.stdout?.on('data', (d) => {
|
|
441
|
-
|
|
470
|
+
child.stdout?.on('data', (d) => {
|
|
471
|
+
try { writeSync(fd, d); } catch (err) {
|
|
472
|
+
if (_rateLimit('lightrag-write-stdout')) logWarn('lightrag stream write failed (stdout)', { module: 'lightrag', err: err?.message || String(err) });
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
child.stderr?.on('data', (d) => {
|
|
476
|
+
try { writeSync(fd, d); } catch (err) {
|
|
477
|
+
if (_rateLimit('lightrag-write-stderr')) logWarn('lightrag stream write failed (stderr)', { module: 'lightrag', err: err?.message || String(err) });
|
|
478
|
+
}
|
|
479
|
+
});
|
|
442
480
|
child.on('exit', (code, signal) => {
|
|
443
|
-
try { closeSync(fd); } catch {
|
|
444
|
-
|
|
481
|
+
try { closeSync(fd); } catch (err) {
|
|
482
|
+
if (_rateLimit('lightrag-close-fd')) logWarn('lightrag close fd failed', { module: 'lightrag', err: err?.message || String(err) });
|
|
483
|
+
}
|
|
484
|
+
try { unlinkSync(pidFile); } catch (err) {
|
|
485
|
+
if (_rateLimit('lightrag-exit-unlink')) logWarn('lightrag pid unlink failed', { module: 'lightrag', err: err?.message || String(err) });
|
|
486
|
+
}
|
|
445
487
|
log(`lightrag-server exited (code=${code}, signal=${signal})`);
|
|
446
488
|
});
|
|
447
489
|
} catch (openErr) {
|
|
@@ -476,7 +518,9 @@ export async function stopServer(config, { logger } = {}) {
|
|
|
476
518
|
const pidFile = join(config.workingDir, 'lightrag.pid');
|
|
477
519
|
const { pid, alive } = readPidAlive(pidFile);
|
|
478
520
|
if (!pid || !alive) {
|
|
479
|
-
try { unlinkSync(pidFile); } catch {
|
|
521
|
+
try { unlinkSync(pidFile); } catch (err) {
|
|
522
|
+
if (_rateLimit('lightrag-stop-notrunning-unlink')) logWarn('lightrag pid unlink failed (not running)', { module: 'lightrag', err: err?.message || String(err) });
|
|
523
|
+
}
|
|
480
524
|
return { ok: true, message: 'not running' };
|
|
481
525
|
}
|
|
482
526
|
log(`stopping lightrag-server (pid ${pid})`);
|
|
@@ -486,7 +530,9 @@ export async function stopServer(config, { logger } = {}) {
|
|
|
486
530
|
log(`escalating to SIGKILL (pid ${pid})`);
|
|
487
531
|
killPid(pid, 'SIGKILL');
|
|
488
532
|
}
|
|
489
|
-
try { unlinkSync(pidFile); } catch {
|
|
533
|
+
try { unlinkSync(pidFile); } catch (err) {
|
|
534
|
+
if (_rateLimit('lightrag-stop-unlink')) logWarn('lightrag pid unlink failed (stop)', { module: 'lightrag', err: err?.message || String(err) });
|
|
535
|
+
}
|
|
490
536
|
return { ok: true, message: 'stopped' };
|
|
491
537
|
}
|
|
492
538
|
|
|
@@ -818,7 +864,8 @@ export async function stats(projectRoot) {
|
|
|
818
864
|
try {
|
|
819
865
|
process.kill(parsed, 0);
|
|
820
866
|
pid = parsed;
|
|
821
|
-
} catch {
|
|
867
|
+
} catch (err) {
|
|
868
|
+
console.warn('[lightrag] swallowed in stats.pidProbe:', err?.message || err);
|
|
822
869
|
pid = null;
|
|
823
870
|
}
|
|
824
871
|
}
|
|
@@ -835,8 +882,8 @@ export async function stats(projectRoot) {
|
|
|
835
882
|
lastReindexOk = m.ok === true;
|
|
836
883
|
lastReindexInserted = typeof m.inserted === 'number' ? m.inserted : null;
|
|
837
884
|
lastReindexFailed = typeof m.failed === 'number' ? m.failed : null;
|
|
838
|
-
} catch {
|
|
839
|
-
|
|
885
|
+
} catch (err) {
|
|
886
|
+
console.warn('[lightrag] swallowed in stats.markerRead:', err?.message || err);
|
|
840
887
|
}
|
|
841
888
|
}
|
|
842
889
|
|
|
@@ -853,13 +900,13 @@ export async function stats(projectRoot) {
|
|
|
853
900
|
try {
|
|
854
901
|
const obj = JSON.parse(readFileSync(p, 'utf8'));
|
|
855
902
|
indexedApprox += Object.keys(obj || {}).length;
|
|
856
|
-
} catch {
|
|
857
|
-
|
|
903
|
+
} catch (err) {
|
|
904
|
+
console.warn('[lightrag] swallowed in stats.kvStoreRead:', err?.message || err);
|
|
858
905
|
}
|
|
859
906
|
}
|
|
860
907
|
}
|
|
861
|
-
} catch {
|
|
862
|
-
|
|
908
|
+
} catch (err) {
|
|
909
|
+
console.warn('[lightrag] swallowed in stats.kvStoreScan:', err?.message || err);
|
|
863
910
|
}
|
|
864
911
|
|
|
865
912
|
// Note count = listNotes from the vault.
|
|
@@ -895,8 +942,8 @@ export async function stats(projectRoot) {
|
|
|
895
942
|
walk(vaultRoot);
|
|
896
943
|
}
|
|
897
944
|
}
|
|
898
|
-
} catch {
|
|
899
|
-
|
|
945
|
+
} catch (err) {
|
|
946
|
+
console.warn('[lightrag] swallowed in stats.noteCount:', err?.message || err);
|
|
900
947
|
}
|
|
901
948
|
|
|
902
949
|
// Query stats: best-effort — LightRAG's /query endpoint doesn't expose
|
|
@@ -920,12 +967,12 @@ export async function stats(projectRoot) {
|
|
|
920
967
|
counted++;
|
|
921
968
|
}
|
|
922
969
|
}
|
|
923
|
-
} catch {
|
|
924
|
-
|
|
970
|
+
} catch (err) {
|
|
971
|
+
console.warn('[lightrag] swallowed in stats.queryLogLine:', err?.message || err);
|
|
925
972
|
}
|
|
926
973
|
}
|
|
927
|
-
} catch {
|
|
928
|
-
|
|
974
|
+
} catch (err) {
|
|
975
|
+
console.warn('[lightrag] swallowed in stats.queryLogRead:', err?.message || err);
|
|
929
976
|
}
|
|
930
977
|
}
|
|
931
978
|
const avgResponseMs = counted > 0 ? Math.round(totalResponseMs / counted) : null;
|
|
@@ -974,24 +1021,24 @@ export function recordQuery(projectRoot, durationMs) {
|
|
|
974
1021
|
const keep = lines.slice(-50000).join('\n');
|
|
975
1022
|
writeFileSync(path, keep);
|
|
976
1023
|
}
|
|
977
|
-
} catch {
|
|
978
|
-
|
|
1024
|
+
} catch (err) {
|
|
1025
|
+
console.warn('[lightrag] swallowed in recordQuery.truncate:', err?.message || err);
|
|
979
1026
|
}
|
|
980
|
-
} catch {
|
|
981
|
-
|
|
1027
|
+
} catch (err) {
|
|
1028
|
+
console.warn('[lightrag] swallowed in recordQuery:', err?.message || err);
|
|
982
1029
|
}
|
|
983
1030
|
}
|
|
984
1031
|
|
|
985
1032
|
function appendFileSafe(path, content) {
|
|
986
1033
|
try {
|
|
987
1034
|
appendFileSync(path, content);
|
|
988
|
-
} catch {
|
|
989
|
-
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
console.warn('[lightrag] appendFileSync failed, falling back to read+write:', err?.message || err);
|
|
990
1037
|
try {
|
|
991
1038
|
const cur = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
992
1039
|
writeFileSync(path, cur + content);
|
|
993
|
-
} catch {
|
|
994
|
-
|
|
1040
|
+
} catch (err2) {
|
|
1041
|
+
console.warn('[lightrag] swallowed in appendFileSafe.manualWrite:', err2?.message || err2);
|
|
995
1042
|
}
|
|
996
1043
|
}
|
|
997
1044
|
}
|
|
@@ -1024,13 +1071,13 @@ export async function rebuildGraph(projectRoot, opts = {}) {
|
|
|
1024
1071
|
for (const e of readdirSync(config.workingDir)) {
|
|
1025
1072
|
try {
|
|
1026
1073
|
unlinkSync(join(config.workingDir, e));
|
|
1027
|
-
} catch {
|
|
1028
|
-
|
|
1074
|
+
} catch (err) {
|
|
1075
|
+
console.warn('[lightrag] swallowed in rebuildGraph.unlinkEntry:', err?.message || err);
|
|
1029
1076
|
}
|
|
1030
1077
|
}
|
|
1031
1078
|
}
|
|
1032
|
-
} catch {
|
|
1033
|
-
|
|
1079
|
+
} catch (err) {
|
|
1080
|
+
console.warn('[lightrag] swallowed in rebuildGraph.wipe:', err?.message || err);
|
|
1034
1081
|
}
|
|
1035
1082
|
// Reindex from scratch.
|
|
1036
1083
|
return reindexVault(projectRoot, opts);
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/server/metrics.mjs
|
|
3
|
+
*
|
|
4
|
+
* v4.7.0 — Tiny Prometheus-style metrics registry.
|
|
5
|
+
*
|
|
6
|
+
* Implements the three primitive metric types we actually use:
|
|
7
|
+
* - counter(name, help) → { inc(labels) }
|
|
8
|
+
* - gauge(name, help) → { set(value, labels) }
|
|
9
|
+
* - histogram(name, help, opts) → { observe(value, labels) }
|
|
10
|
+
*
|
|
11
|
+
* Storage is in-process: counters/gauges/histograms are kept in Maps
|
|
12
|
+
* keyed by metric name → label-set signature → values. Labels are
|
|
13
|
+
* JSON-stringified to produce a stable key (sorted-keys would be
|
|
14
|
+
* nicer but JSON.stringify of a literal object preserves insertion
|
|
15
|
+
* order, which is good enough for our use — every caller writes the
|
|
16
|
+
* same label shape for the same metric).
|
|
17
|
+
*
|
|
18
|
+
* `render()` returns the Prometheus text exposition format
|
|
19
|
+
* (Content-Type: text/plain; version=0.0.4) so it can be scraped by
|
|
20
|
+
* Prometheus or read by humans. The output includes the `# HELP` and
|
|
21
|
+
* `# TYPE` headers for every metric.
|
|
22
|
+
*
|
|
23
|
+
* `reset()` clears the registry — only intended for tests.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const counters = new Map();
|
|
27
|
+
const gauges = new Map();
|
|
28
|
+
const histograms = new Map();
|
|
29
|
+
|
|
30
|
+
// Default Prometheus-style histogram buckets in seconds (suitable for
|
|
31
|
+
// HTTP latency). Override per-metric via opts.buckets.
|
|
32
|
+
const DEFAULT_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
|
|
33
|
+
|
|
34
|
+
function labelsKey(labels) {
|
|
35
|
+
if (!labels || typeof labels !== 'object') return '{}';
|
|
36
|
+
const keys = Object.keys(labels);
|
|
37
|
+
if (keys.length === 0) return '{}';
|
|
38
|
+
const ordered = {};
|
|
39
|
+
for (const k of keys.sort()) ordered[k] = labels[k];
|
|
40
|
+
return JSON.stringify(ordered);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function labelsToString(labels) {
|
|
44
|
+
if (!labels || typeof labels !== 'object') return '';
|
|
45
|
+
const keys = Object.keys(labels);
|
|
46
|
+
if (keys.length === 0) return '';
|
|
47
|
+
return keys
|
|
48
|
+
.sort()
|
|
49
|
+
.map((k) => `${k}="${escapeLabelValue(String(labels[k]))}"`)
|
|
50
|
+
.join(',');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function escapeLabelValue(v) {
|
|
54
|
+
return v.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/"/g, '\\"');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function counter(name, help) {
|
|
58
|
+
if (!name) throw new Error('counter(name, help): name is required');
|
|
59
|
+
return {
|
|
60
|
+
inc(labels = {}) {
|
|
61
|
+
const key = labelsKey(labels);
|
|
62
|
+
let entry = counters.get(name);
|
|
63
|
+
if (!entry) {
|
|
64
|
+
entry = { help: help || '', values: new Map() };
|
|
65
|
+
counters.set(name, entry);
|
|
66
|
+
}
|
|
67
|
+
const v = entry.values.get(key) || { labels, value: 0 };
|
|
68
|
+
v.value += 1;
|
|
69
|
+
entry.values.set(key, v);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function gauge(name, help) {
|
|
75
|
+
if (!name) throw new Error('gauge(name, help): name is required');
|
|
76
|
+
return {
|
|
77
|
+
set(value, labels = {}) {
|
|
78
|
+
const key = labelsKey(labels);
|
|
79
|
+
let entry = gauges.get(name);
|
|
80
|
+
if (!entry) {
|
|
81
|
+
entry = { help: help || '', values: new Map() };
|
|
82
|
+
gauges.set(name, entry);
|
|
83
|
+
}
|
|
84
|
+
entry.values.set(key, { labels, value: Number(value) });
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function histogram(name, help, opts = {}) {
|
|
90
|
+
if (!name) throw new Error('histogram(name, help, opts): name is required');
|
|
91
|
+
const buckets = Array.isArray(opts.buckets) && opts.buckets.length > 0
|
|
92
|
+
? [...opts.buckets].sort((a, b) => a - b)
|
|
93
|
+
: DEFAULT_BUCKETS;
|
|
94
|
+
return {
|
|
95
|
+
observe(value, labels = {}) {
|
|
96
|
+
const key = labelsKey(labels);
|
|
97
|
+
let entry = histograms.get(name);
|
|
98
|
+
if (!entry) {
|
|
99
|
+
entry = {
|
|
100
|
+
help: help || '',
|
|
101
|
+
buckets,
|
|
102
|
+
values: new Map(),
|
|
103
|
+
};
|
|
104
|
+
histograms.set(name, entry);
|
|
105
|
+
}
|
|
106
|
+
const v = entry.values.get(key) || {
|
|
107
|
+
labels,
|
|
108
|
+
count: 0,
|
|
109
|
+
sum: 0,
|
|
110
|
+
bucketCounts: buckets.map(() => 0),
|
|
111
|
+
};
|
|
112
|
+
v.count += 1;
|
|
113
|
+
v.sum += Number(value);
|
|
114
|
+
for (let i = 0; i < buckets.length; i += 1) {
|
|
115
|
+
if (Number(value) <= buckets[i]) v.bucketCounts[i] += 1;
|
|
116
|
+
}
|
|
117
|
+
entry.values.set(key, v);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function renderCounters() {
|
|
123
|
+
const lines = [];
|
|
124
|
+
for (const [name, entry] of counters) {
|
|
125
|
+
lines.push(`# HELP ${name} ${entry.help}`);
|
|
126
|
+
lines.push(`# TYPE ${name} counter`);
|
|
127
|
+
for (const v of entry.values.values()) {
|
|
128
|
+
const labels = labelsToString(v.labels);
|
|
129
|
+
lines.push(`${name}${labels ? '{' + labels + '}' : ''} ${v.value}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return lines;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function renderGauges() {
|
|
136
|
+
const lines = [];
|
|
137
|
+
for (const [name, entry] of gauges) {
|
|
138
|
+
lines.push(`# HELP ${name} ${entry.help}`);
|
|
139
|
+
lines.push(`# TYPE ${name} gauge`);
|
|
140
|
+
for (const v of entry.values.values()) {
|
|
141
|
+
const labels = labelsToString(v.labels);
|
|
142
|
+
lines.push(`${name}${labels ? '{' + labels + '}' : ''} ${v.value}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return lines;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function renderHistograms() {
|
|
149
|
+
const lines = [];
|
|
150
|
+
for (const [name, entry] of histograms) {
|
|
151
|
+
lines.push(`# HELP ${name} ${entry.help}`);
|
|
152
|
+
lines.push(`# TYPE ${name} histogram`);
|
|
153
|
+
for (const v of entry.values.values()) {
|
|
154
|
+
const baseLabels = labelsToString(v.labels);
|
|
155
|
+
for (let i = 0; i < entry.buckets.length; i += 1) {
|
|
156
|
+
const le = entry.buckets[i];
|
|
157
|
+
const bucketLabels = baseLabels
|
|
158
|
+
? `${baseLabels},le="${le}"`
|
|
159
|
+
: `le="${le}"`;
|
|
160
|
+
lines.push(`${name}_bucket{${bucketLabels}} ${v.bucketCounts[i]}`);
|
|
161
|
+
}
|
|
162
|
+
const infLabels = baseLabels ? `${baseLabels},le="+Inf"` : 'le="+Inf"';
|
|
163
|
+
lines.push(`${name}_bucket{${infLabels}} ${v.count}`);
|
|
164
|
+
const sumLabels = baseLabels;
|
|
165
|
+
lines.push(
|
|
166
|
+
`${name}_sum${sumLabels ? '{' + sumLabels + '}' : ''} ${v.sum}`,
|
|
167
|
+
);
|
|
168
|
+
lines.push(
|
|
169
|
+
`${name}_count${sumLabels ? '{' + sumLabels + '}' : ''} ${v.count}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return lines;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function render() {
|
|
177
|
+
const parts = [
|
|
178
|
+
...renderCounters(),
|
|
179
|
+
...renderGauges(),
|
|
180
|
+
...renderHistograms(),
|
|
181
|
+
];
|
|
182
|
+
return parts.length === 0 ? '' : `${parts.join('\n')}\n`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Reset every metric. Intended for tests — never call this from a
|
|
187
|
+
* long-running server.
|
|
188
|
+
*/
|
|
189
|
+
export function reset() {
|
|
190
|
+
counters.clear();
|
|
191
|
+
gauges.clear();
|
|
192
|
+
histograms.clear();
|
|
193
|
+
}
|