@tekmidian/pai 0.66.6 → 0.66.7
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/dist/{aibroker-client-WCZi_tn-.mjs → aibroker-client-BevXY1Zz.mjs} +3 -3
- package/dist/{aibroker-client-WCZi_tn-.mjs.map → aibroker-client-BevXY1Zz.mjs.map} +1 -1
- package/dist/{aibroker-client-Ck3p3k_-.mjs → aibroker-client-CxTpL_u7.mjs} +1 -1
- package/dist/{auto-route-BnizyALK.mjs → auto-route-BVeAp0fl.mjs} +1 -1
- package/dist/{auto-route-BnizyALK.mjs.map → auto-route-BVeAp0fl.mjs.map} +1 -1
- package/dist/{auto-route-D6fW1Q8z.mjs → auto-route-CboBqsN3.mjs} +1 -1
- package/dist/{chain-2_uoiEdD.mjs → chain-8hFKr23C.mjs} +3 -3
- package/dist/{chain-2_uoiEdD.mjs.map → chain-8hFKr23C.mjs.map} +1 -1
- package/dist/cli/index.mjs +16 -15
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/program.mjs +15 -14
- package/dist/{config-CnsDMETf.mjs → config-UWtzLpXi.mjs} +1 -1
- package/dist/{config-Ddc4DIPk.mjs → config-sfaArCsl.mjs} +12 -3
- package/dist/config-sfaArCsl.mjs.map +1 -0
- package/dist/{context-handover-cache-SFkWucuT.mjs → context-handover-cache-Bb-H_mTa.mjs} +7 -8
- package/dist/{context-handover-cache-SFkWucuT.mjs.map → context-handover-cache-Bb-H_mTa.mjs.map} +1 -1
- package/dist/daemon/index.mjs +14 -13
- package/dist/daemon/index.mjs.map +1 -1
- package/dist/daemon-BXTn2EgV.mjs +19 -0
- package/dist/{daemon-vLtT3yaI.mjs → daemon-CkbX-hV7.mjs} +757 -20
- package/dist/daemon-CkbX-hV7.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +12 -11
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/embedding-gate-BVrr0BgS.mjs +378 -0
- package/dist/embedding-gate-BVrr0BgS.mjs.map +1 -0
- package/dist/embedding-gate-Crj8awk7.mjs +5 -0
- package/dist/embeddings-BuA7v4lk.mjs +3 -0
- package/dist/{embeddings-DMVzpvRZ.mjs → embeddings-DFl88_y-.mjs} +16 -2
- package/dist/{embeddings-DMVzpvRZ.mjs.map → embeddings-DFl88_y-.mjs.map} +1 -1
- package/dist/{env-DiolswKQ.mjs → env-DqTjeKoA.mjs} +1 -1
- package/dist/{env-DiolswKQ.mjs.map → env-DqTjeKoA.mjs.map} +1 -1
- package/dist/factory-XrH66erA.mjs +9 -0
- package/dist/{factory-CXFHwvcv.mjs → factory-mNBitkL_.mjs} +28 -6
- package/dist/factory-mNBitkL_.mjs.map +1 -0
- package/dist/{fallback-DNzd3jJc.mjs → fallback-DdC1_s2d.mjs} +4 -4
- package/dist/{fallback-DNzd3jJc.mjs.map → fallback-DdC1_s2d.mjs.map} +1 -1
- package/dist/hooks/block-sleep-poll.mjs +7 -2
- package/dist/hooks/block-sleep-poll.mjs.map +3 -3
- package/dist/hooks/context-compression-hook.mjs +2 -0
- package/dist/hooks/context-compression-hook.mjs.map +2 -2
- package/dist/hooks/load-core-context.mjs +5 -5
- package/dist/hooks/load-core-context.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +2 -0
- package/dist/hooks/load-project-context.mjs.map +2 -2
- package/dist/hooks/post-compact-inject.mjs +2 -0
- package/dist/hooks/post-compact-inject.mjs.map +2 -2
- package/dist/hooks/route-agents-to-worker.mjs +2 -0
- package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
- package/dist/hooks/security-validator.mjs +2 -0
- package/dist/hooks/security-validator.mjs.map +2 -2
- package/dist/hooks/whisper-rules.mjs +2 -0
- package/dist/hooks/whisper-rules.mjs.map +2 -2
- package/dist/hooks/worker-guard.mjs +82 -5
- package/dist/hooks/worker-guard.mjs.map +4 -4
- package/dist/hooks/worker-proxy.mjs +2 -0
- package/dist/hooks/worker-proxy.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +7 -2
- package/dist/hooks/worker-status-line.mjs.map +3 -3
- package/dist/hooks/worker-supervision.mjs +2 -0
- package/dist/hooks/worker-supervision.mjs.map +2 -2
- package/dist/{ipc-client-DkxRay-o.mjs → ipc-client-BgSzY9QJ.mjs} +2 -2
- package/dist/{ipc-client-DkxRay-o.mjs.map → ipc-client-BgSzY9QJ.mjs.map} +1 -1
- package/dist/main-resolver-CobQGm2B.mjs +7 -0
- package/dist/{main-resolver-SfGGg1DB.mjs → main-resolver-DEXky_Km.mjs} +6 -6
- package/dist/{main-resolver-SfGGg1DB.mjs.map → main-resolver-DEXky_Km.mjs.map} +1 -1
- package/dist/{planner-C-gzg7ye.mjs → planner-DBoB5GtC.mjs} +10 -9
- package/dist/{planner-C-gzg7ye.mjs.map → planner-DBoB5GtC.mjs.map} +1 -1
- package/dist/{postgres-BxmyQHoA.mjs → postgres-D-96lgE1.mjs} +84 -3
- package/dist/postgres-D-96lgE1.mjs.map +1 -0
- package/dist/postgres-TedJP3yF.mjs +7 -0
- package/dist/{program-CfG1T_ko.mjs → program-BXns_5kO.mjs} +211 -63
- package/dist/program-BXns_5kO.mjs.map +1 -0
- package/dist/{query-feedback-BPdapXKd.mjs → query-feedback-VI7Cl-Lr.mjs} +2 -2
- package/dist/{query-feedback-BPdapXKd.mjs.map → query-feedback-VI7Cl-Lr.mjs.map} +1 -1
- package/dist/router-CKn9e0fo.mjs +3 -0
- package/dist/{router-BaTbc9VX.mjs → router-dfjlaOMI.mjs} +2 -2
- package/dist/{router-BaTbc9VX.mjs.map → router-dfjlaOMI.mjs.map} +1 -1
- package/dist/{run-ChlIbDJ0.mjs → run-DPwQkRwh.mjs} +107 -9
- package/dist/run-DPwQkRwh.mjs.map +1 -0
- package/dist/{run-env-CQRnxNht.mjs → run-env-CkAmx3lb.mjs} +3 -3
- package/dist/{run-env-CQRnxNht.mjs.map → run-env-CkAmx3lb.mjs.map} +1 -1
- package/dist/{pai-home-UncxWxlX.mjs → runtime-paths-BiEi_gaG.mjs} +50 -2
- package/dist/runtime-paths-BiEi_gaG.mjs.map +1 -0
- package/dist/{sources-CM2g-CLT.mjs → sources-BxXbzpAJ.mjs} +1 -1
- package/dist/{sources-CM2g-CLT.mjs.map → sources-BxXbzpAJ.mjs.map} +1 -1
- package/dist/{zettelkasten-G0-ZTybr.mjs → zettelkasten-CzcPzEsL.mjs} +5 -3
- package/dist/{zettelkasten-BRIaHzFm.mjs → zettelkasten-wh2Yy1nS.mjs} +5 -4
- package/dist/zettelkasten-wh2Yy1nS.mjs.map +1 -0
- package/docker/docker-compose.yml +4 -0
- package/docs/commands/README.md +6 -0
- package/docs/commands/memory.md +52 -0
- package/docs/commands/worker.md +22 -0
- package/docs/install-linux.md +1 -0
- package/docs/memory.md +69 -0
- package/package.json +1 -1
- package/src/hooks/ts/lib/sleep-poll-gate.test.ts +2 -2
- package/src/hooks/ts/lib/sleep-poll-gate.ts +4 -4
- package/src/hooks/ts/lib/worker-guard-longwait.test.ts +48 -0
- package/src/hooks/ts/lib/worker-guard.ts +50 -6
- package/src/hooks/ts/pre-tool-use/block-sleep-poll.test.ts +1 -1
- package/src/hooks/ts/session-start/load-core-context.ts +11 -8
- package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +34 -2
- package/dist/config-Ddc4DIPk.mjs.map +0 -1
- package/dist/daemon-BHQoEjt0.mjs +0 -18
- package/dist/daemon-vLtT3yaI.mjs.map +0 -1
- package/dist/embeddings-D0WOQpYR.mjs +0 -3
- package/dist/factory-CXFHwvcv.mjs.map +0 -1
- package/dist/factory-CnKsU6zX.mjs +0 -8
- package/dist/main-resolver-DrHtx8iW.mjs +0 -7
- package/dist/pai-home-UncxWxlX.mjs.map +0 -1
- package/dist/postgres-B2BEBT-J.mjs +0 -5
- package/dist/postgres-BxmyQHoA.mjs.map +0 -1
- package/dist/program-CfG1T_ko.mjs.map +0 -1
- package/dist/router-BK-hFeQ6.mjs +0 -3
- package/dist/run-ChlIbDJ0.mjs.map +0 -1
- package/dist/runtime-paths-Bw3Jk8qJ.mjs +0 -52
- package/dist/runtime-paths-Bw3Jk8qJ.mjs.map +0 -1
- package/dist/zettelkasten-BRIaHzFm.mjs.map +0 -1
|
@@ -2,23 +2,28 @@ import { c as yieldToEventLoop, f as fileContentHash, l as buildOutline, n as ch
|
|
|
2
2
|
import { n as TITLE_STOP_WORDS, t as STOP_WORDS } from "./stop-words-DtxaTWU_.mjs";
|
|
3
3
|
import { i as populateSlugs, t as applyRecencyBoost } from "./search-CNAGTiJP.mjs";
|
|
4
4
|
import { n as rerankResults } from "./reranker-3lnggwgq.mjs";
|
|
5
|
-
import { n as embedChunksWithBackend } from "./postgres-
|
|
6
|
-
import { d as getBackendOutage, n as createRegistryBackend, r as createStorageBackend } from "./factory-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { S as DEFAULT_NOTIFICATION_CONFIG, d as readMainConfigRaw, f as writeMainConfigRaw } from "./config-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { G as
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
5
|
+
import { c as resolveFromModule, n as embedChunksWithBackend } from "./postgres-D-96lgE1.mjs";
|
|
6
|
+
import { d as getBackendOutage, n as createRegistryBackend, r as createStorageBackend } from "./factory-mNBitkL_.mjs";
|
|
7
|
+
import { a as requireQueryEmbedding, g as mismatchReason, h as bindingMatches, l as getConfiguredBackend, n as embedQuery } from "./embedding-gate-BVrr0BgS.mjs";
|
|
8
|
+
import { i as deserializeEmbedding, n as configureEmbeddingModel, r as cosineSimilarity$1 } from "./embeddings-DFl88_y-.mjs";
|
|
9
|
+
import { S as DEFAULT_NOTIFICATION_CONFIG, d as readMainConfigRaw, f as writeMainConfigRaw } from "./config-sfaArCsl.mjs";
|
|
10
|
+
import { u as paiHomePath } from "./runtime-paths-BiEi_gaG.mjs";
|
|
11
|
+
import { t as routeNotification } from "./router-dfjlaOMI.mjs";
|
|
12
|
+
import { $ as shutdownRequested, A as indexInProgress, B as setEmbedInProgress, C as handleSessionSummary, D as daemonConfig, E as cacheKeepaliveTimer, F as notificationConfig, G as setLastIndexTime, H as setIndexInProgress, I as passFailures, It as formatDetectionJson, J as setRegistryBackend, K as setLastVaultIndexTime, L as registryBackend, M as lastEmbedTime, N as lastIndexTime, O as embedInProgress, P as lastVaultIndexTime, Pt as detectProject, Q as setVaultIndexInProgress, R as setCacheKeepaliveTimer, S as getGitContext, T as spawnSummarizer, Tt as performScan, U as setIndexSchedulerTimer, V as setEmbedSchedulerTimer, W as setLastEmbedTime, X as setStartTime, Y as setShutdownRequested, Z as setStorageBackend, _ as markCompleted, _t as buildWakeupContext, at as updateTodoContinue, b as contentToText$2, c as removeDaemonPidfile, d as cleanup, dt as addWorkToSessionNote, et as startTime, f as dequeue, ft as appendCheckpoint, g as loadQueue, gt as findNotesDir, h as hasPendingOrProcessingOfType, ht as archiveSessionFilesToSessionsDir, i as runSessionKeepaliveTick, it as sessionIdFromTranscript, j as indexSchedulerTimer, k as embedSchedulerTimer, m as getStats, mt as getCurrentNotePath, n as writeContextHandoverCache, nt as vaultIndexInProgress, p as enqueue, pt as finalizeSessionNote, q as setNotificationConfig, t as readContextHandoverCache, tt as storageBackend, u as writeDaemonPidfile, v as markFailed, x as findLatestJsonl, xt as runPassChild, yt as PassSpawnError, z as setDaemonConfig } from "./context-handover-cache-Bb-H_mTa.mjs";
|
|
13
|
+
import { D as readWorkersSection } from "./run-env-CkAmx3lb.mjs";
|
|
14
|
+
import { C as itermForClaudeSession, H as alive, J as loadStatus, K as isChatPane, R as appendLedger, Y as loadStatuses, Z as nowStamp, ht as workersLogDir, mt as ledgerPath, n as runWorker, pt as eventsPath } from "./run-DPwQkRwh.mjs";
|
|
15
|
+
import { r as saveQueryResult } from "./query-feedback-VI7Cl-Lr.mjs";
|
|
16
|
+
import { i as sendToSession } from "./aibroker-client-BevXY1Zz.mjs";
|
|
17
|
+
import { n as formatAutoRouteJson, r as detectTopicShift, t as autoRoute } from "./auto-route-BVeAp0fl.mjs";
|
|
18
|
+
import { a as zettelThemes, c as zettelExplore, i as zettelHealth, n as zettelGodNotes, o as zettelConverse, r as zettelSuggest, s as zettelSurprise, t as zettelCommunities } from "./zettelkasten-wh2Yy1nS.mjs";
|
|
17
19
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
18
20
|
import { setPriority } from "node:os";
|
|
19
21
|
import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
22
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
20
23
|
import { connect, createServer } from "node:net";
|
|
21
|
-
import {
|
|
24
|
+
import { createServer as createServer$1 } from "node:http";
|
|
25
|
+
import { promisify } from "node:util";
|
|
26
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
22
27
|
|
|
23
28
|
//#region src/notifications/config.ts
|
|
24
29
|
/**
|
|
@@ -2152,10 +2157,15 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
|
|
|
2152
2157
|
maxResults: params.limit ?? searchDefaults?.defaultLimit ?? 5
|
|
2153
2158
|
};
|
|
2154
2159
|
let results;
|
|
2160
|
+
let embedNote = "";
|
|
2155
2161
|
if (mode === "keyword") results = await federation.searchKeyword(params.query, searchOpts);
|
|
2156
2162
|
else if (mode === "semantic" || mode === "hybrid") {
|
|
2157
|
-
const
|
|
2158
|
-
|
|
2163
|
+
const q = await embedQuery(federation, params.query);
|
|
2164
|
+
const queryEmbedding = q.vec;
|
|
2165
|
+
if (!queryEmbedding) {
|
|
2166
|
+
embedNote = `\nNote: ${q.note}`;
|
|
2167
|
+
results = await federation.searchKeyword(params.query, searchOpts);
|
|
2168
|
+
} else if (mode === "semantic") results = await federation.searchSemantic(queryEmbedding, searchOpts);
|
|
2159
2169
|
else {
|
|
2160
2170
|
const [kwResults, semResults] = await Promise.all([federation.searchKeyword(params.query, {
|
|
2161
2171
|
...searchOpts,
|
|
@@ -2192,7 +2202,7 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
|
|
|
2192
2202
|
const withSlugs = await populateSlugs(results, registry);
|
|
2193
2203
|
if (withSlugs.length === 0) return { content: [{
|
|
2194
2204
|
type: "text",
|
|
2195
|
-
text: `No results found for query: "${params.query}" (mode: ${mode})`
|
|
2205
|
+
text: `No results found for query: "${params.query}" (mode: ${mode})${embedNote}`
|
|
2196
2206
|
}] };
|
|
2197
2207
|
const rerankLabel = shouldRerank ? " +rerank" : "";
|
|
2198
2208
|
const useCompact = params.format === "compact";
|
|
@@ -2216,7 +2226,7 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
|
|
|
2216
2226
|
} catch {}
|
|
2217
2227
|
return { content: [{
|
|
2218
2228
|
type: "text",
|
|
2219
|
-
text: `Found ${withSlugs.length} result(s) for "${params.query}" (mode: ${mode}${rerankLabel})
|
|
2229
|
+
text: `Found ${withSlugs.length} result(s) for "${params.query}" (mode: ${mode}${rerankLabel}):${embedNote}\n\n${formatted}`
|
|
2220
2230
|
}] };
|
|
2221
2231
|
} catch (e) {
|
|
2222
2232
|
return {
|
|
@@ -3419,10 +3429,12 @@ async function bfsExpand(federation, entityNames, hops, projectId) {
|
|
|
3419
3429
|
*/
|
|
3420
3430
|
async function rerankTriples(triples, queryVec, maxTriples) {
|
|
3421
3431
|
if (triples.length === 0) return [];
|
|
3432
|
+
const embedder = getConfiguredBackend();
|
|
3422
3433
|
return (await Promise.all(triples.map(async (triple) => {
|
|
3423
3434
|
const text = `${triple.subject} ${triple.predicate} ${triple.object}`;
|
|
3424
3435
|
try {
|
|
3425
|
-
const
|
|
3436
|
+
const [vec] = await embedder.embed([text]);
|
|
3437
|
+
const score = cosineSimilarity$1(queryVec, vec);
|
|
3426
3438
|
return {
|
|
3427
3439
|
...triple,
|
|
3428
3440
|
relevanceScore: score
|
|
@@ -3511,7 +3523,7 @@ async function toolMemoryKgSearch(federation, params) {
|
|
|
3511
3523
|
}],
|
|
3512
3524
|
isError: true
|
|
3513
3525
|
};
|
|
3514
|
-
const result = await graphCompletionSearch(federation, await
|
|
3526
|
+
const result = await graphCompletionSearch(federation, await requireQueryEmbedding(federation, params.query), {
|
|
3515
3527
|
seedCount: params.wide_k ?? 50,
|
|
3516
3528
|
hops: params.neighborhood_depth ?? 1,
|
|
3517
3529
|
maxTriples: params.top_k ?? 20,
|
|
@@ -4361,6 +4373,7 @@ async function handleRequest(request, socket) {
|
|
|
4361
4373
|
lastVaultIndexTime: lastVaultIndexTime ? new Date(lastVaultIndexTime).toISOString() : null,
|
|
4362
4374
|
vaultPath: daemonConfig.vaultPath ?? null,
|
|
4363
4375
|
workQueue: getStats(),
|
|
4376
|
+
dashboard: daemonConfig.dashboard.enabled ? `http://${daemonConfig.dashboard.bind}:${daemonConfig.dashboard.port}/` : null,
|
|
4364
4377
|
passFailures: Object.fromEntries([...passFailures].map(([k, v]) => [k, {
|
|
4365
4378
|
...v,
|
|
4366
4379
|
at: new Date(v.at).toISOString()
|
|
@@ -4819,6 +4832,725 @@ async function handleRequest(request, socket) {
|
|
|
4819
4832
|
socket.end();
|
|
4820
4833
|
}
|
|
4821
4834
|
|
|
4835
|
+
//#endregion
|
|
4836
|
+
//#region src/daemon/dashboard-page.ts
|
|
4837
|
+
/** The dashboard page: one self-contained file (inline SVG + CSS), updated in place with textContent/attributes only. */
|
|
4838
|
+
const DASHBOARD_HTML = `<!doctype html>
|
|
4839
|
+
<html lang="en"><head><meta charset="utf-8">
|
|
4840
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
4841
|
+
<title>PAI memory</title>
|
|
4842
|
+
<style>
|
|
4843
|
+
:root { color-scheme: light dark; --green:#2e9e5b; --amber:#d99a1c; --red:#d64545; --grey:#8a8f98;
|
|
4844
|
+
--bg:#f2f3f5; --card:#fff; --fg:#1b1d21; --mute:#6b7280; --track:rgba(128,128,128,.25); --new:#2e9e5b; --old:#d99a1c; --miss:#8a8f98; }
|
|
4845
|
+
@media (prefers-color-scheme: dark) { :root { --bg:#111317; --card:#1b1e24; --fg:#e8eaed; --mute:#9aa0a8; } }
|
|
4846
|
+
* { box-sizing: border-box; }
|
|
4847
|
+
body { font: 15px/1.4 -apple-system, system-ui, sans-serif; margin: 0; padding: 12px; background: var(--bg); color: var(--fg); }
|
|
4848
|
+
main { max-width: 1000px; margin: 0 auto; }
|
|
4849
|
+
header { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
4850
|
+
h1 { font-size: 20px; margin: 0; }
|
|
4851
|
+
.pill { color: #fff; font-weight: 700; font-size: 13px; text-transform: uppercase; letter-spacing: .05em; padding: 4px 12px; border-radius: 999px; background: var(--grey); }
|
|
4852
|
+
.pill.green { background: var(--green); } .pill.amber { background: var(--amber); } .pill.red { background: var(--red); }
|
|
4853
|
+
#reason { color: var(--mute); flex: 1 1 200px; }
|
|
4854
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
4855
|
+
.wide { grid-column: 1 / -1; }
|
|
4856
|
+
@media (max-width: 700px) { .grid { grid-template-columns: 1fr; } }
|
|
4857
|
+
.card { background: var(--card); border-radius: 12px; padding: 12px 14px; border-top: 4px solid var(--grey); box-shadow: 0 1px 3px rgba(0,0,0,.12); min-width: 0; }
|
|
4858
|
+
.card.green { border-color: var(--green); } .card.amber { border-color: var(--amber); } .card.red { border-color: var(--red); }
|
|
4859
|
+
h2 { font-size: 12px; margin: 0 0 8px; text-transform: uppercase; letter-spacing: .06em; color: var(--mute); }
|
|
4860
|
+
.k { color: var(--mute); } .bad { color: var(--red); font-weight: 600; }
|
|
4861
|
+
.big { font-size: 34px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
|
|
4862
|
+
.unit { font-size: 14px; font-weight: 400; color: var(--mute); }
|
|
4863
|
+
.job { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
|
|
4864
|
+
.ring { width: 150px; height: 150px; flex: none; }
|
|
4865
|
+
.ring .bg { stroke: var(--track); } .ring .fg { stroke: var(--green); transition: stroke-dashoffset .6s; }
|
|
4866
|
+
.ring text { fill: var(--fg); text-anchor: middle; }
|
|
4867
|
+
.stats { flex: 1 1 200px; min-width: 0; display: grid; gap: 6px; }
|
|
4868
|
+
svg { display: block; }
|
|
4869
|
+
.spark { width: 100%; height: 56px; }
|
|
4870
|
+
.spark polyline { fill: none; stroke: var(--green); stroke-width: 2; vector-effect: non-scaling-stroke; }
|
|
4871
|
+
.spark polygon { fill: var(--green); opacity: .15; }
|
|
4872
|
+
.stack { width: 100%; height: 22px; border-radius: 6px; }
|
|
4873
|
+
.legend { display: grid; gap: 4px; margin: 8px 0; }
|
|
4874
|
+
.legend div { display: flex; align-items: center; gap: 8px; }
|
|
4875
|
+
.legend i { width: 12px; height: 12px; border-radius: 3px; flex: none; }
|
|
4876
|
+
.legend b { margin-left: auto; font-variant-numeric: tabular-nums; }
|
|
4877
|
+
.dot { width: 12px; height: 12px; border-radius: 50%; background: var(--grey); display: inline-block; flex: none; }
|
|
4878
|
+
.dot.green { background: var(--green); } .dot.amber { background: var(--amber); } .dot.red { background: var(--red); }
|
|
4879
|
+
.line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
4880
|
+
.gauge { width: 100%; height: 14px; border-radius: 7px; }
|
|
4881
|
+
.pass { padding: 6px 0; border-top: 1px solid var(--track); }
|
|
4882
|
+
.pass:first-of-type { border-top: 0; }
|
|
4883
|
+
.pass .tl { width: 100%; height: 16px; }
|
|
4884
|
+
.pass .tl line { stroke: var(--track); stroke-width: 3; stroke-linecap: round; }
|
|
4885
|
+
.times { display: flex; justify-content: space-between; gap: 8px; color: var(--mute); font-size: 13px; flex-wrap: wrap; }
|
|
4886
|
+
.err { color: var(--red); font-size: 13px; word-break: break-word; }
|
|
4887
|
+
.kv { display: flex; flex-wrap: wrap; gap: 4px 18px; }
|
|
4888
|
+
.kv span span { color: var(--mute); margin-right: 4px; }
|
|
4889
|
+
#foot { color: var(--mute); font-size: 12px; margin-top: 12px; }
|
|
4890
|
+
[hidden] { display: none !important; }
|
|
4891
|
+
</style></head><body><main>
|
|
4892
|
+
<header><h1>PAI memory</h1><span class="pill" id="pill">…</span><span id="reason">loading…</span></header>
|
|
4893
|
+
<div class="grid">
|
|
4894
|
+
<section class="card wide" id="jobs-empty"><h2>Re-embed job</h2><span class="k">no job running</span></section>
|
|
4895
|
+
<div class="wide" id="jobs" style="display:contents"></div>
|
|
4896
|
+
|
|
4897
|
+
<section class="card" id="index"><h2>Index</h2>
|
|
4898
|
+
<div class="line"><span class="big" id="ix-cov">?</span><span class="unit" id="ix-chunks"></span></div>
|
|
4899
|
+
<svg class="stack" id="ix-stack" viewBox="0 0 100 10" preserveAspectRatio="none" role="img" aria-label="vector distribution">
|
|
4900
|
+
<rect id="sg-new" x="0" y="0" width="0" height="10" fill="var(--new)"/>
|
|
4901
|
+
<rect id="sg-old" x="0" y="0" width="0" height="10" fill="var(--old)"/>
|
|
4902
|
+
<rect id="sg-miss" x="0" y="0" width="0" height="10" fill="var(--miss)"/>
|
|
4903
|
+
</svg>
|
|
4904
|
+
<div class="legend">
|
|
4905
|
+
<div id="lg-new"><i style="background:var(--new)"></i><span>new backend</span><b id="n-new">0</b></div>
|
|
4906
|
+
<div id="lg-old"><i style="background:var(--old)"></i><span id="t-old">embedded</span><b id="n-old">0</b></div>
|
|
4907
|
+
<div><i style="background:var(--miss)"></i><span>missing</span><b id="n-miss">0</b></div>
|
|
4908
|
+
</div>
|
|
4909
|
+
<div><span class="k">files</span> <span id="ix-files">?</span></div>
|
|
4910
|
+
<div><span class="k">bound to</span> <span id="ix-bound">?</span>, <span class="k">configured</span> <span id="ix-conf">?</span></div>
|
|
4911
|
+
<div class="bad" id="ix-hint" hidden></div>
|
|
4912
|
+
</section>
|
|
4913
|
+
|
|
4914
|
+
<section class="card" id="backend"><h2>Backend</h2>
|
|
4915
|
+
<div class="line"><span class="dot" id="be-dot"></span><b id="be-id">?</b><span id="be-state" class="k"></span></div>
|
|
4916
|
+
<div class="k" id="be-reason"></div>
|
|
4917
|
+
<div style="margin-top:10px"><span class="k">loaded model</span> <b id="be-model">none</b></div>
|
|
4918
|
+
<div class="line" style="margin-top:8px"><span class="big" id="gpu-pct">?</span><span class="unit">GPU</span></div>
|
|
4919
|
+
<svg class="gauge" viewBox="0 0 100 10" preserveAspectRatio="none" role="img" aria-label="GPU share">
|
|
4920
|
+
<rect x="0" y="0" width="100" height="10" fill="var(--track)"/>
|
|
4921
|
+
<rect id="gpu-bar" x="0" y="0" width="0" height="10" fill="var(--green)"/>
|
|
4922
|
+
</svg>
|
|
4923
|
+
</section>
|
|
4924
|
+
|
|
4925
|
+
<section class="card" id="passes"><h2>Passes</h2><div id="pass-list"></div></section>
|
|
4926
|
+
|
|
4927
|
+
<section class="card" id="daemon"><h2>Daemon</h2>
|
|
4928
|
+
<div class="kv">
|
|
4929
|
+
<span><span>pid</span><b id="d-pid">?</b></span><span><span>uptime</span><b id="d-up">?</b></span>
|
|
4930
|
+
<span><span>version</span><b id="d-ver">?</b></span><span><span>storage</span><b id="d-sto">?</b></span>
|
|
4931
|
+
</div>
|
|
4932
|
+
</section>
|
|
4933
|
+
</div>
|
|
4934
|
+
<div id="foot"></div>
|
|
4935
|
+
|
|
4936
|
+
<template id="job-tpl"><section class="card">
|
|
4937
|
+
<h2></h2>
|
|
4938
|
+
<div class="job">
|
|
4939
|
+
<svg class="ring" viewBox="0 0 120 120" role="img" aria-label="progress">
|
|
4940
|
+
<circle class="bg" cx="60" cy="60" r="52" fill="none" stroke-width="12"/>
|
|
4941
|
+
<circle class="fg" cx="60" cy="60" r="52" fill="none" stroke-width="12" stroke-linecap="round" transform="rotate(-90 60 60)" stroke-dasharray="326.73" stroke-dashoffset="326.73"/>
|
|
4942
|
+
<text class="pct" x="60" y="68" font-size="24" font-weight="700">0%</text>
|
|
4943
|
+
</svg>
|
|
4944
|
+
<div class="stats">
|
|
4945
|
+
<div><span class="big rate">?</span> <span class="unit">chunks/s</span></div>
|
|
4946
|
+
<div><span class="k">done</span> <b class="done">?</b></div>
|
|
4947
|
+
<div><span class="k">ETA</span> <b class="eta">?</b> <span class="k fin"></span></div>
|
|
4948
|
+
<div class="k phase"></div>
|
|
4949
|
+
<div class="bad stall" hidden>STALLED</div>
|
|
4950
|
+
</div>
|
|
4951
|
+
</div>
|
|
4952
|
+
<div class="k" style="margin-top:8px;font-size:12px">rate, last hour</div>
|
|
4953
|
+
<svg class="spark" viewBox="0 0 240 56" preserveAspectRatio="none" role="img" aria-label="rate sparkline">
|
|
4954
|
+
<polygon points=""/><polyline points=""/>
|
|
4955
|
+
</svg>
|
|
4956
|
+
</section></template>
|
|
4957
|
+
|
|
4958
|
+
<script>
|
|
4959
|
+
const $ = (id) => document.getElementById(id);
|
|
4960
|
+
const n = (x) => (x === null || x === undefined ? "?" : Number(x).toLocaleString());
|
|
4961
|
+
const when = (t) => (t ? new Date(t).toLocaleString([], { dateStyle: "short", timeStyle: "short" }) : "never");
|
|
4962
|
+
const clock = (t) => new Date(t).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
4963
|
+
function dur(secs) {
|
|
4964
|
+
if (secs === null || secs === undefined) return "?";
|
|
4965
|
+
const h = Math.floor(secs / 3600), m = Math.floor((secs % 3600) / 60);
|
|
4966
|
+
return h >= 24 ? Math.floor(h / 24) + "d " + (h % 24) + "h" : h > 0 ? h + "h " + m + "m" : m + "m";
|
|
4967
|
+
}
|
|
4968
|
+
const set = (id, text) => { const e = $(id); if (e.textContent !== text) e.textContent = text; };
|
|
4969
|
+
const lvl = (e, base, level) => { e.className = base + " " + (level || ""); };
|
|
4970
|
+
const CIRC = 326.73;
|
|
4971
|
+
|
|
4972
|
+
function sparkPoints(history) {
|
|
4973
|
+
const r = [];
|
|
4974
|
+
for (let i = 1; i < history.length; i++) {
|
|
4975
|
+
const dt = (history[i].t - history[i - 1].t) / 1000;
|
|
4976
|
+
r.push(dt > 0 ? Math.max(0, (history[i].done - history[i - 1].done) / dt) : 0);
|
|
4977
|
+
}
|
|
4978
|
+
if (r.length < 2) return ["", ""];
|
|
4979
|
+
const max = Math.max(...r, 0.001);
|
|
4980
|
+
const pts = r.map((v, i) => (i / (r.length - 1) * 240).toFixed(1) + "," + (52 - (v / max) * 48).toFixed(1));
|
|
4981
|
+
return [pts.join(" "), "0,56 " + pts.join(" ") + " 240,56"];
|
|
4982
|
+
}
|
|
4983
|
+
|
|
4984
|
+
const jobEls = new Map();
|
|
4985
|
+
function renderJobs(s) {
|
|
4986
|
+
const names = new Set(s.jobs.map((j) => j.name));
|
|
4987
|
+
for (const [name, e] of jobEls) if (!names.has(name)) { e.remove(); jobEls.delete(name); }
|
|
4988
|
+
$("jobs-empty").hidden = s.jobs.length > 0;
|
|
4989
|
+
for (const j of s.jobs) {
|
|
4990
|
+
let e = jobEls.get(j.name);
|
|
4991
|
+
if (!e) { e = $("job-tpl").content.firstElementChild.cloneNode(true); $("jobs").append(e); jobEls.set(j.name, e); }
|
|
4992
|
+
const q = (c) => e.querySelector(c);
|
|
4993
|
+
const pct = j.total > 0 ? Math.min(100, (j.done / j.total) * 100) : 0;
|
|
4994
|
+
e.className = "card wide " + j.level;
|
|
4995
|
+
q("h2").textContent = "Re-embed job " + j.name;
|
|
4996
|
+
q(".fg").setAttribute("stroke-dashoffset", String(CIRC * (1 - pct / 100)));
|
|
4997
|
+
q(".pct").textContent = pct.toFixed(1) + "%";
|
|
4998
|
+
q(".rate").textContent = j.rate === null ? "?" : j.rate.toFixed(1);
|
|
4999
|
+
q(".done").textContent = n(j.done) + " / " + n(j.total);
|
|
5000
|
+
q(".eta").textContent = j.etaSecs === null ? "?" : dur(j.etaSecs);
|
|
5001
|
+
q(".fin").textContent = j.etaSecs === null ? "" : "done about " + clock(s.generatedAt + j.etaSecs * 1000);
|
|
5002
|
+
q(".phase").textContent = "phase " + j.phase + ", last progress " + when(j.lastProgressAt);
|
|
5003
|
+
q(".stall").hidden = !j.stalled;
|
|
5004
|
+
const [line, area] = sparkPoints(j.history || []);
|
|
5005
|
+
q("polyline").setAttribute("points", line);
|
|
5006
|
+
q("polygon").setAttribute("points", area);
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
|
|
5010
|
+
function renderIndex(s) {
|
|
5011
|
+
const i = s.index, g = i.segments;
|
|
5012
|
+
$("index").className = "card " + i.level;
|
|
5013
|
+
set("ix-cov", i.coverage === null ? "?" : (i.coverage * 100).toFixed(1) + "%");
|
|
5014
|
+
set("ix-chunks", "embedded of " + n(i.chunks) + (i.countsExact ? "" : " (est.)") + " chunks");
|
|
5015
|
+
set("ix-files", n(i.files));
|
|
5016
|
+
const total = g ? g.fresh + g.old + g.missing : 0;
|
|
5017
|
+
const w = (v) => (total > 0 ? (v / total) * 100 : 0);
|
|
5018
|
+
const a = w(g ? g.fresh : 0), b = w(g ? g.old : 0), c = w(g ? g.missing : 0);
|
|
5019
|
+
const setRect = (id, x, width) => { $(id).setAttribute("x", String(x)); $(id).setAttribute("width", String(width)); };
|
|
5020
|
+
setRect("sg-new", 0, a); setRect("sg-old", a, b); setRect("sg-miss", a + b, c);
|
|
5021
|
+
const reembed = !!g && s.jobs.some((j) => j.phase !== "done") && g.fresh > 0;
|
|
5022
|
+
$("lg-new").hidden = !reembed;
|
|
5023
|
+
set("t-old", reembed ? "old backend, still to replace" : "embedded");
|
|
5024
|
+
set("n-new", n(g && g.fresh)); set("n-old", n(g && g.old)); set("n-miss", n(g && g.missing));
|
|
5025
|
+
const bound = i.binding ? i.binding.backend + (i.binding.model ? " / " + i.binding.model : "") : "none";
|
|
5026
|
+
set("ix-bound", bound);
|
|
5027
|
+
set("ix-conf", i.configured.backend + " / " + i.configured.model);
|
|
5028
|
+
const msg = i.error ? "binding error: " + i.error : i.hint || "";
|
|
5029
|
+
$("ix-hint").hidden = !msg; set("ix-hint", msg);
|
|
5030
|
+
$("ix-hint").style.color = i.level === "amber" ? "var(--amber)" : "";
|
|
5031
|
+
}
|
|
5032
|
+
|
|
5033
|
+
function renderBackend(s) {
|
|
5034
|
+
const b = s.backend, m = b.loaded && b.loaded[0];
|
|
5035
|
+
$("backend").className = "card " + b.level;
|
|
5036
|
+
lvl($("be-dot"), "dot", b.available === null ? "" : b.available ? "green" : "red");
|
|
5037
|
+
set("be-id", b.id);
|
|
5038
|
+
set("be-state", b.available === null ? "unknown" : b.available ? "available" : "unavailable");
|
|
5039
|
+
set("be-reason", b.reason || "");
|
|
5040
|
+
set("be-model", m ? m.name : b.model + " (not loaded)");
|
|
5041
|
+
const share = m && m.gpuShare !== null ? m.gpuShare : null;
|
|
5042
|
+
set("gpu-pct", share === null ? "?" : (share * 100).toFixed(0) + "%");
|
|
5043
|
+
$("gpu-bar").setAttribute("width", String(share === null ? 0 : Math.min(100, share * 100)));
|
|
5044
|
+
$("gpu-bar").setAttribute("fill", share !== null && share < 1 ? "var(--amber)" : "var(--green)");
|
|
5045
|
+
}
|
|
5046
|
+
|
|
5047
|
+
const passEls = new Map();
|
|
5048
|
+
const NS = "http://www.w3.org/2000/svg";
|
|
5049
|
+
function passRow(name) {
|
|
5050
|
+
const e = document.createElement("div"); e.className = "pass";
|
|
5051
|
+
e.innerHTML = '<div class="line"><span class="dot"></span><b></b><span class="k st"></span></div>' +
|
|
5052
|
+
'<svg class="tl" viewBox="0 0 100 16" preserveAspectRatio="none"><line x1="4" y1="8" x2="96" y2="8"/></svg>' +
|
|
5053
|
+
'<div class="times"><span class="last"></span><span class="next"></span></div><div class="err" hidden></div>';
|
|
5054
|
+
e.querySelector("b").textContent = name;
|
|
5055
|
+
const svg = e.querySelector("svg");
|
|
5056
|
+
const mk = (cls, cx) => { const c = document.createElementNS(NS, "ellipse"); c.setAttribute("class", cls); c.setAttribute("cx", cx); c.setAttribute("cy", "8"); c.setAttribute("rx", "2.2"); c.setAttribute("ry", "5"); svg.append(c); return c; };
|
|
5057
|
+
mk("m-last", "4"); mk("m-next", "96");
|
|
5058
|
+
return e;
|
|
5059
|
+
}
|
|
5060
|
+
function renderPasses(s) {
|
|
5061
|
+
$("passes").className = "card " + (s.passes.some((p) => p.level === "red") ? "red" : "green");
|
|
5062
|
+
for (const p of s.passes) {
|
|
5063
|
+
let e = passEls.get(p.name);
|
|
5064
|
+
if (!e) { e = passRow(p.name); $("pass-list").append(e); passEls.set(p.name, e); }
|
|
5065
|
+
const q = (c) => e.querySelector(c);
|
|
5066
|
+
const state = p.failure ? "red" : p.running ? "amber" : "green";
|
|
5067
|
+
lvl(q(".dot"), "dot", state);
|
|
5068
|
+
q(".st").textContent = p.failure ? "failed" : p.running ? "running" : "ok";
|
|
5069
|
+
q(".last").textContent = "last " + (p.lastEnd ? when(p.lastEnd) : "none since start");
|
|
5070
|
+
q(".next").textContent = p.nextAt ? "next " + when(p.nextAt) : "";
|
|
5071
|
+
const col = "var(--" + state + ")";
|
|
5072
|
+
q(".m-last").setAttribute("fill", col);
|
|
5073
|
+
q(".m-next").setAttribute("fill", "none"); q(".m-next").setAttribute("stroke", "var(--grey)"); q(".m-next").setAttribute("stroke-width", "1.5");
|
|
5074
|
+
q(".m-next").style.display = p.nextAt ? "" : "none";
|
|
5075
|
+
const err = q(".err");
|
|
5076
|
+
err.hidden = !p.failure;
|
|
5077
|
+
if (p.failure) err.textContent = p.failure.error + " (" + (p.failure.gaveUp ? "gave up until next scheduled run" : "retry " + when(p.failure.retryAt)) + ")";
|
|
5078
|
+
}
|
|
5079
|
+
}
|
|
5080
|
+
|
|
5081
|
+
function render(s) {
|
|
5082
|
+
lvl($("pill"), "pill", s.level);
|
|
5083
|
+
set("pill", s.level === "green" ? "healthy" : s.level === "amber" ? "attention" : "problem");
|
|
5084
|
+
set("reason", s.reason);
|
|
5085
|
+
renderJobs(s); renderIndex(s); renderBackend(s); renderPasses(s);
|
|
5086
|
+
set("d-pid", String(s.daemon.pid)); set("d-up", dur(s.daemon.uptimeSecs));
|
|
5087
|
+
set("d-ver", s.daemon.version); set("d-sto", s.daemon.storage);
|
|
5088
|
+
set("foot", "updated " + new Date(s.generatedAt).toLocaleTimeString() + " · refreshes every 10 s");
|
|
5089
|
+
}
|
|
5090
|
+
async function load() {
|
|
5091
|
+
try {
|
|
5092
|
+
const r = await fetch("/api/status", { cache: "no-store" });
|
|
5093
|
+
if (!r.ok) throw new Error("HTTP " + r.status);
|
|
5094
|
+
render(await r.json());
|
|
5095
|
+
} catch (e) {
|
|
5096
|
+
lvl($("pill"), "pill", "red"); set("pill", "unreachable");
|
|
5097
|
+
set("reason", "daemon unreachable: " + e.message);
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
load();
|
|
5101
|
+
setInterval(load, 10000);
|
|
5102
|
+
<\/script></main></body></html>
|
|
5103
|
+
`;
|
|
5104
|
+
|
|
5105
|
+
//#endregion
|
|
5106
|
+
//#region src/daemon/dashboard.ts
|
|
5107
|
+
/**
|
|
5108
|
+
* dashboard.ts — read-only memory status dashboard served by the daemon.
|
|
5109
|
+
*
|
|
5110
|
+
* Embedding and index passes run unattended for hours; a crashed embed pass
|
|
5111
|
+
* went unnoticed for 7 h. This puts what is running and whether it is healthy
|
|
5112
|
+
* on one page (and /api/status) reachable from a phone on the tailnet.
|
|
5113
|
+
*
|
|
5114
|
+
* Nothing here computes anything per request: a background tick (30 s) samples
|
|
5115
|
+
* the job table, binding and backend health, and real row counts are refreshed
|
|
5116
|
+
* at most every 5 min. Requests only assemble the cached values.
|
|
5117
|
+
*/
|
|
5118
|
+
const TICK_MS = 3e4;
|
|
5119
|
+
const COUNTS_TTL_MS = 5 * 6e4;
|
|
5120
|
+
const STALL_MS = 5 * 6e4;
|
|
5121
|
+
const RATE_WINDOW_MS = 5 * 6e4;
|
|
5122
|
+
const HISTORY_MAX = 120;
|
|
5123
|
+
const HISTORY_PERSIST_MS = 3e4;
|
|
5124
|
+
const HISTORY_PRUNE_AGE_MS = 120 * 6e4;
|
|
5125
|
+
const RANK = {
|
|
5126
|
+
green: 0,
|
|
5127
|
+
amber: 1,
|
|
5128
|
+
red: 2
|
|
5129
|
+
};
|
|
5130
|
+
const worst = (ls) => ls.reduce((a, b) => RANK[b] > RANK[a] ? b : a, "green");
|
|
5131
|
+
/** Host header without port; IPv6 literals keep their brackets stripped. */
|
|
5132
|
+
function hostName(header) {
|
|
5133
|
+
if (!header) return "";
|
|
5134
|
+
const h = header.trim().toLowerCase();
|
|
5135
|
+
if (h.startsWith("[")) return h.slice(1, h.indexOf("]"));
|
|
5136
|
+
return h.replace(/:\d+$/, "");
|
|
5137
|
+
}
|
|
5138
|
+
function hostAllowed(header, allowed) {
|
|
5139
|
+
const h = hostName(header);
|
|
5140
|
+
return h !== "" && new Set([...allowed].map((a) => a.toLowerCase())).has(h);
|
|
5141
|
+
}
|
|
5142
|
+
/** Tailnet DNS name and IPs of this machine from `tailscale status --json`; empty when unavailable. */
|
|
5143
|
+
async function tailnetHosts() {
|
|
5144
|
+
try {
|
|
5145
|
+
const { stdout } = await promisify(execFile)("tailscale", ["status", "--json"], { timeout: 3e3 });
|
|
5146
|
+
const self = JSON.parse(stdout).Self;
|
|
5147
|
+
return [self?.DNSName?.replace(/\.$/, ""), ...self?.TailscaleIPs ?? []].filter((x) => !!x);
|
|
5148
|
+
} catch {
|
|
5149
|
+
return [];
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
/** Stacked-bar numbers: done/embedded/total in, three non-negative segments out. `done` null = no re-embed. */
|
|
5153
|
+
function indexSegments(total, embedded, done) {
|
|
5154
|
+
const missing = Math.max(0, total - embedded);
|
|
5155
|
+
if (done === null) return {
|
|
5156
|
+
fresh: 0,
|
|
5157
|
+
old: Math.max(0, embedded),
|
|
5158
|
+
missing
|
|
5159
|
+
};
|
|
5160
|
+
return {
|
|
5161
|
+
fresh: done,
|
|
5162
|
+
old: Math.max(0, embedded - done),
|
|
5163
|
+
missing
|
|
5164
|
+
};
|
|
5165
|
+
}
|
|
5166
|
+
function phaseOf(s) {
|
|
5167
|
+
if (s.finished) return "done";
|
|
5168
|
+
if (s.embedDone) return "finish";
|
|
5169
|
+
if (s.carryDone) return "embed";
|
|
5170
|
+
if (s.prepared) return "carry";
|
|
5171
|
+
return "prepare";
|
|
5172
|
+
}
|
|
5173
|
+
var DashboardState = class {
|
|
5174
|
+
cache = {
|
|
5175
|
+
counts: null,
|
|
5176
|
+
binding: null,
|
|
5177
|
+
bindingError: null,
|
|
5178
|
+
jobs: [],
|
|
5179
|
+
backend: null,
|
|
5180
|
+
loaded: null
|
|
5181
|
+
};
|
|
5182
|
+
samples = /* @__PURE__ */ new Map();
|
|
5183
|
+
lastExactAt;
|
|
5184
|
+
exactRunning = false;
|
|
5185
|
+
lastPersistAt;
|
|
5186
|
+
historyPath;
|
|
5187
|
+
constructor(src, now = Date.now, historyPath) {
|
|
5188
|
+
this.src = src;
|
|
5189
|
+
this.now = now;
|
|
5190
|
+
this.lastExactAt = now();
|
|
5191
|
+
this.lastPersistAt = now();
|
|
5192
|
+
this.historyPath = historyPath ?? paiHomePath("state", "dashboard-history.json");
|
|
5193
|
+
}
|
|
5194
|
+
async loadHistory() {
|
|
5195
|
+
try {
|
|
5196
|
+
if (!existsSync(this.historyPath)) return;
|
|
5197
|
+
const content = await readFile(this.historyPath, "utf8");
|
|
5198
|
+
const data = JSON.parse(content);
|
|
5199
|
+
const now = this.now();
|
|
5200
|
+
for (const [name, hist] of Object.entries(data.jobs ?? {})) {
|
|
5201
|
+
const pruned = hist.filter((s) => now - s.t <= HISTORY_PRUNE_AGE_MS);
|
|
5202
|
+
if (pruned.length > 0) {
|
|
5203
|
+
const s = this.samples.get(name) ?? {
|
|
5204
|
+
list: [],
|
|
5205
|
+
history: [],
|
|
5206
|
+
progressAt: now,
|
|
5207
|
+
lastDone: 0
|
|
5208
|
+
};
|
|
5209
|
+
s.history = pruned.slice(-HISTORY_MAX);
|
|
5210
|
+
this.samples.set(name, s);
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
} catch {}
|
|
5214
|
+
}
|
|
5215
|
+
async persistHistory() {
|
|
5216
|
+
const t = this.now();
|
|
5217
|
+
if (t - this.lastPersistAt < HISTORY_PERSIST_MS) return;
|
|
5218
|
+
this.lastPersistAt = t;
|
|
5219
|
+
try {
|
|
5220
|
+
const jobs = {};
|
|
5221
|
+
for (const [name, s] of this.samples) if (s.history.length > 0) jobs[name] = s.history;
|
|
5222
|
+
const data = { jobs };
|
|
5223
|
+
await mkdir(join(this.historyPath, ".."), { recursive: true });
|
|
5224
|
+
await writeFile(this.historyPath + ".tmp", JSON.stringify(data), "utf8");
|
|
5225
|
+
await writeFile(this.historyPath, JSON.stringify(data), "utf8");
|
|
5226
|
+
} catch {}
|
|
5227
|
+
}
|
|
5228
|
+
/** Sample everything once. Never throws: a failing source shows up in the status instead. */
|
|
5229
|
+
async tick() {
|
|
5230
|
+
const t = this.now();
|
|
5231
|
+
const c = this.cache;
|
|
5232
|
+
const run = async (f, onErr) => {
|
|
5233
|
+
try {
|
|
5234
|
+
return await f();
|
|
5235
|
+
} catch (e) {
|
|
5236
|
+
onErr(e);
|
|
5237
|
+
return;
|
|
5238
|
+
}
|
|
5239
|
+
};
|
|
5240
|
+
if (!c.counts) {
|
|
5241
|
+
const est = await run(() => this.src.estimate(), () => {});
|
|
5242
|
+
if (est) c.counts = {
|
|
5243
|
+
...est,
|
|
5244
|
+
exact: false,
|
|
5245
|
+
at: t
|
|
5246
|
+
};
|
|
5247
|
+
}
|
|
5248
|
+
this.refreshExact(t);
|
|
5249
|
+
c.bindingError = null;
|
|
5250
|
+
const b = await run(() => this.src.binding(), (e) => {
|
|
5251
|
+
c.bindingError = e instanceof Error ? e.message : String(e);
|
|
5252
|
+
});
|
|
5253
|
+
if (b !== void 0) c.binding = b;
|
|
5254
|
+
const jobs = await run(() => this.src.jobs(), () => {});
|
|
5255
|
+
if (jobs) {
|
|
5256
|
+
c.jobs = jobs;
|
|
5257
|
+
for (const j of jobs) this.sample(j, t);
|
|
5258
|
+
}
|
|
5259
|
+
const be = this.src.backend();
|
|
5260
|
+
c.backend = await run(() => be.available(), () => {}) ?? {
|
|
5261
|
+
ok: false,
|
|
5262
|
+
reason: "availability probe threw"
|
|
5263
|
+
};
|
|
5264
|
+
c.loaded = be.id.startsWith("ollama") ? await run(() => this.src.loadedModels(), () => {}) ?? null : null;
|
|
5265
|
+
await this.persistHistory();
|
|
5266
|
+
}
|
|
5267
|
+
/** Real counts at most every COUNTS_TTL_MS, in the background; the tick never waits for them. */
|
|
5268
|
+
refreshExact(t) {
|
|
5269
|
+
if (this.exactRunning || t - this.lastExactAt < COUNTS_TTL_MS) return;
|
|
5270
|
+
this.exactRunning = true;
|
|
5271
|
+
this.lastExactAt = t;
|
|
5272
|
+
this.src.exact().then((n) => {
|
|
5273
|
+
this.cache.counts = {
|
|
5274
|
+
...n,
|
|
5275
|
+
exact: true,
|
|
5276
|
+
at: this.now()
|
|
5277
|
+
};
|
|
5278
|
+
}, () => {}).finally(() => {
|
|
5279
|
+
this.exactRunning = false;
|
|
5280
|
+
});
|
|
5281
|
+
}
|
|
5282
|
+
sample(j, t) {
|
|
5283
|
+
const done = Number(j.state.done ?? 0);
|
|
5284
|
+
const s = this.samples.get(j.name) ?? {
|
|
5285
|
+
list: [],
|
|
5286
|
+
history: [],
|
|
5287
|
+
progressAt: t,
|
|
5288
|
+
lastDone: done
|
|
5289
|
+
};
|
|
5290
|
+
if (done !== s.lastDone) {
|
|
5291
|
+
s.progressAt = t;
|
|
5292
|
+
s.lastDone = done;
|
|
5293
|
+
}
|
|
5294
|
+
s.list.push({
|
|
5295
|
+
t,
|
|
5296
|
+
done
|
|
5297
|
+
});
|
|
5298
|
+
s.history.push({
|
|
5299
|
+
t,
|
|
5300
|
+
done
|
|
5301
|
+
});
|
|
5302
|
+
if (s.history.length > HISTORY_MAX) s.history.shift();
|
|
5303
|
+
while (s.list.length > 2 && t - s.list[0].t > RATE_WINDOW_MS) s.list.shift();
|
|
5304
|
+
this.samples.set(j.name, s);
|
|
5305
|
+
}
|
|
5306
|
+
jobView(j, t) {
|
|
5307
|
+
const total = Number(j.state.total ?? 0);
|
|
5308
|
+
const done = Number(j.state.done ?? 0);
|
|
5309
|
+
const phase = phaseOf(j.state);
|
|
5310
|
+
const s = this.samples.get(j.name);
|
|
5311
|
+
const first = s?.list[0];
|
|
5312
|
+
const last = s?.list[s.list.length - 1];
|
|
5313
|
+
const rate = first && last && last.t > first.t ? Math.max(0, (last.done - first.done) / ((last.t - first.t) / 1e3)) : null;
|
|
5314
|
+
const etaSecs = rate && rate > 0 && total > done ? Math.round((total - done) / rate) : null;
|
|
5315
|
+
const lastProgressAt = s?.progressAt ?? t;
|
|
5316
|
+
const stalled = phase !== "done" && t - lastProgressAt >= STALL_MS;
|
|
5317
|
+
return {
|
|
5318
|
+
name: j.name,
|
|
5319
|
+
phase,
|
|
5320
|
+
done,
|
|
5321
|
+
total,
|
|
5322
|
+
rate,
|
|
5323
|
+
etaSecs,
|
|
5324
|
+
lastProgressAt,
|
|
5325
|
+
stalled,
|
|
5326
|
+
level: stalled ? "red" : "green",
|
|
5327
|
+
history: s?.history ?? []
|
|
5328
|
+
};
|
|
5329
|
+
}
|
|
5330
|
+
status() {
|
|
5331
|
+
const t = this.now();
|
|
5332
|
+
const c = this.cache;
|
|
5333
|
+
const d = this.src.daemon();
|
|
5334
|
+
const be = this.src.backend();
|
|
5335
|
+
const jobs = c.jobs.filter((j) => Object.keys(j.state).length > 0).map((j) => this.jobView(j, t));
|
|
5336
|
+
const mismatch = !!c.binding && !bindingMatches(c.binding, be);
|
|
5337
|
+
const counts = c.counts;
|
|
5338
|
+
const reembedJob = this.findReembedJob(c.jobs, be.id);
|
|
5339
|
+
const reembedActive = reembedJob && !["done"].includes(phaseOf(reembedJob.state));
|
|
5340
|
+
const indexLevel = mismatch && !reembedActive || c.bindingError ? "red" : reembedActive ? "amber" : "green";
|
|
5341
|
+
const backendLevel = c.backend && !c.backend.ok ? "red" : c.backend ? "green" : "amber";
|
|
5342
|
+
const passes = this.src.passes().map((p) => ({
|
|
5343
|
+
...p,
|
|
5344
|
+
level: p.failure ? "red" : "green"
|
|
5345
|
+
}));
|
|
5346
|
+
let hint = null;
|
|
5347
|
+
if (mismatch && c.binding) if (reembedActive) hint = `re-embed to ${this.deriveBackendFromJobName(reembedJob.name)} in progress, semantic search keyword-only until it finishes`;
|
|
5348
|
+
else hint = mismatchReason(c.binding, be);
|
|
5349
|
+
const level = worst([
|
|
5350
|
+
indexLevel,
|
|
5351
|
+
backendLevel,
|
|
5352
|
+
...jobs.map((j) => j.level),
|
|
5353
|
+
...passes.map((p) => p.level)
|
|
5354
|
+
]);
|
|
5355
|
+
const reason = level === "green" ? "all systems healthy" : c.bindingError ? `binding check failed: ${c.bindingError}` : jobs.find((j) => j.stalled) ? `job ${jobs.find((j) => j.stalled).name} stalled` : c.backend && !c.backend.ok ? `backend unavailable: ${c.backend.reason}` : passes.find((p) => p.failure) ? `pass ${passes.find((p) => p.failure).name} failed` : hint ?? (reembedActive ? `re-embed ${reembedJob.name} running` : c.backend ? "degraded" : "backend not probed yet");
|
|
5356
|
+
const activeJob = reembedActive ? jobs.find((j) => j.name === reembedJob.name) : void 0;
|
|
5357
|
+
const segments = counts && counts.embedded !== null ? indexSegments(counts.chunks, counts.embedded, activeJob ? activeJob.done : null) : null;
|
|
5358
|
+
return {
|
|
5359
|
+
generatedAt: t,
|
|
5360
|
+
level,
|
|
5361
|
+
reason,
|
|
5362
|
+
daemon: {
|
|
5363
|
+
pid: d.pid,
|
|
5364
|
+
uptimeSecs: Math.floor((t - d.startTime) / 1e3),
|
|
5365
|
+
version: d.version,
|
|
5366
|
+
storage: this.src.storage
|
|
5367
|
+
},
|
|
5368
|
+
index: {
|
|
5369
|
+
level: indexLevel,
|
|
5370
|
+
files: counts?.files ?? null,
|
|
5371
|
+
chunks: counts?.chunks ?? null,
|
|
5372
|
+
embedded: counts?.embedded ?? null,
|
|
5373
|
+
coverage: counts && counts.embedded !== null && counts.chunks > 0 ? Math.min(1, counts.embedded / counts.chunks) : null,
|
|
5374
|
+
countsExact: counts?.exact ?? false,
|
|
5375
|
+
countsAt: counts?.at ?? null,
|
|
5376
|
+
segments,
|
|
5377
|
+
binding: c.binding,
|
|
5378
|
+
configured: {
|
|
5379
|
+
backend: be.id,
|
|
5380
|
+
model: be.model,
|
|
5381
|
+
dims: be.dims
|
|
5382
|
+
},
|
|
5383
|
+
mismatch,
|
|
5384
|
+
hint,
|
|
5385
|
+
error: c.bindingError
|
|
5386
|
+
},
|
|
5387
|
+
jobs,
|
|
5388
|
+
passes,
|
|
5389
|
+
backend: {
|
|
5390
|
+
level: backendLevel,
|
|
5391
|
+
id: be.id,
|
|
5392
|
+
model: be.model,
|
|
5393
|
+
available: c.backend?.ok ?? null,
|
|
5394
|
+
reason: c.backend?.reason ?? null,
|
|
5395
|
+
loaded: c.loaded?.map((m) => ({
|
|
5396
|
+
name: m.name,
|
|
5397
|
+
size: m.size,
|
|
5398
|
+
sizeVram: m.sizeVram,
|
|
5399
|
+
gpuShare: m.size > 0 ? m.sizeVram / m.size : null
|
|
5400
|
+
})) ?? null
|
|
5401
|
+
}
|
|
5402
|
+
};
|
|
5403
|
+
}
|
|
5404
|
+
findReembedJob(jobs, configuredBackendId) {
|
|
5405
|
+
return jobs.find((j) => j.name.endsWith("-reembed") && this.deriveBackendFromJobName(j.name) === configuredBackendId);
|
|
5406
|
+
}
|
|
5407
|
+
deriveBackendFromJobName(jobName) {
|
|
5408
|
+
const match = jobName.match(/^(.+)-reembed$/);
|
|
5409
|
+
return match ? match[1] : jobName;
|
|
5410
|
+
}
|
|
5411
|
+
};
|
|
5412
|
+
async function startDashboard(cfg, src, opts = {}) {
|
|
5413
|
+
const state = new DashboardState(src, Date.now, opts.historyPath);
|
|
5414
|
+
await state.loadHistory();
|
|
5415
|
+
const allowed = [
|
|
5416
|
+
cfg.bind,
|
|
5417
|
+
"localhost",
|
|
5418
|
+
"127.0.0.1",
|
|
5419
|
+
"::1",
|
|
5420
|
+
...opts.extraHosts ?? await tailnetHosts()
|
|
5421
|
+
];
|
|
5422
|
+
const server = createServer$1((req, res) => {
|
|
5423
|
+
const deny = (code, msg) => {
|
|
5424
|
+
res.writeHead(code, { "content-type": "text/plain" });
|
|
5425
|
+
res.end(msg);
|
|
5426
|
+
};
|
|
5427
|
+
if (!hostAllowed(req.headers.host, allowed)) return deny(403, "forbidden host");
|
|
5428
|
+
if (req.method !== "GET" && req.method !== "HEAD") return deny(405, "read-only");
|
|
5429
|
+
const path = (req.url ?? "/").split("?")[0];
|
|
5430
|
+
if (path === "/api/status") {
|
|
5431
|
+
res.writeHead(200, {
|
|
5432
|
+
"content-type": "application/json",
|
|
5433
|
+
"cache-control": "no-store"
|
|
5434
|
+
});
|
|
5435
|
+
return res.end(JSON.stringify(state.status()));
|
|
5436
|
+
}
|
|
5437
|
+
if (path === "/") {
|
|
5438
|
+
res.writeHead(200, {
|
|
5439
|
+
"content-type": "text/html; charset=utf-8",
|
|
5440
|
+
"cache-control": "no-store"
|
|
5441
|
+
});
|
|
5442
|
+
return res.end(DASHBOARD_HTML);
|
|
5443
|
+
}
|
|
5444
|
+
deny(404, "not found");
|
|
5445
|
+
});
|
|
5446
|
+
await state.tick();
|
|
5447
|
+
const timer = setInterval(() => void state.tick(), TICK_MS);
|
|
5448
|
+
timer.unref();
|
|
5449
|
+
await new Promise((resolve, reject) => {
|
|
5450
|
+
server.once("error", reject);
|
|
5451
|
+
server.listen(cfg.port, cfg.bind, resolve);
|
|
5452
|
+
});
|
|
5453
|
+
return {
|
|
5454
|
+
server,
|
|
5455
|
+
url: `http://${cfg.bind}:${cfg.port}/`,
|
|
5456
|
+
stop: () => {
|
|
5457
|
+
clearInterval(timer);
|
|
5458
|
+
server.close();
|
|
5459
|
+
}
|
|
5460
|
+
};
|
|
5461
|
+
}
|
|
5462
|
+
async function ollamaPs(baseUrl) {
|
|
5463
|
+
try {
|
|
5464
|
+
const r = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/ps`, { signal: AbortSignal.timeout(2e3) });
|
|
5465
|
+
if (!r.ok) return null;
|
|
5466
|
+
return ((await r.json()).models ?? []).map((m) => ({
|
|
5467
|
+
name: m.name,
|
|
5468
|
+
size: m.size,
|
|
5469
|
+
sizeVram: m.size_vram
|
|
5470
|
+
}));
|
|
5471
|
+
} catch {
|
|
5472
|
+
return null;
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5475
|
+
function liveSource(deps) {
|
|
5476
|
+
const st = deps.storage;
|
|
5477
|
+
const sqliteCounts = async () => ({
|
|
5478
|
+
...await deps.storage.getStats(),
|
|
5479
|
+
embedded: null
|
|
5480
|
+
});
|
|
5481
|
+
return {
|
|
5482
|
+
storage: deps.storage.backendType,
|
|
5483
|
+
daemon: deps.daemon,
|
|
5484
|
+
estimate: st.estimateCounts?.bind(st) ?? sqliteCounts,
|
|
5485
|
+
exact: st.exactCounts?.bind(st) ?? sqliteCounts,
|
|
5486
|
+
binding: () => deps.storage.getEmbeddingBinding(),
|
|
5487
|
+
jobs: st.embeddingJobs?.bind(st) ?? (async () => []),
|
|
5488
|
+
backend: deps.backend,
|
|
5489
|
+
loadedModels: () => ollamaPs(deps.ollamaBaseUrl),
|
|
5490
|
+
passes: deps.passes
|
|
5491
|
+
};
|
|
5492
|
+
}
|
|
5493
|
+
|
|
5494
|
+
//#endregion
|
|
5495
|
+
//#region src/daemon/daemon/dashboard-live.ts
|
|
5496
|
+
/**
|
|
5497
|
+
* Wires the status dashboard to the daemon's live state (state.ts, scheduler
|
|
5498
|
+
* bookkeeping) and the configured embedding backend.
|
|
5499
|
+
*/
|
|
5500
|
+
function packageVersion() {
|
|
5501
|
+
try {
|
|
5502
|
+
return JSON.parse(readFileSync(resolveFromModule(import.meta.url, "package.json"), "utf8")).version ?? "0.0.0";
|
|
5503
|
+
} catch {
|
|
5504
|
+
return "0.0.0";
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
/** The index/embed/vault passes as the dashboard shows them, from the existing daemon state. */
|
|
5508
|
+
function livePasses(now = Date.now()) {
|
|
5509
|
+
const cfg = daemonConfig;
|
|
5510
|
+
const next = (last, intervalSecs) => cfg.maintenanceHour !== void 0 ? now + msUntilNextAnchor(cfg.maintenanceHour, intervalSecs * 1e3) : (last || now) + intervalSecs * 1e3;
|
|
5511
|
+
const snap = (name, running, lastEnd, nextAt) => {
|
|
5512
|
+
const f = passFailures.get(name);
|
|
5513
|
+
const delay = f ? PASS_RETRY_DELAYS_MS[f.attempts - 1] : void 0;
|
|
5514
|
+
return {
|
|
5515
|
+
name,
|
|
5516
|
+
running,
|
|
5517
|
+
lastEnd,
|
|
5518
|
+
failure: f ? {
|
|
5519
|
+
...f,
|
|
5520
|
+
retryAt: f.gaveUp || delay === void 0 ? null : f.at + delay
|
|
5521
|
+
} : null,
|
|
5522
|
+
nextAt
|
|
5523
|
+
};
|
|
5524
|
+
};
|
|
5525
|
+
return [
|
|
5526
|
+
snap("index", indexInProgress, lastIndexTime, next(lastIndexTime, cfg.indexIntervalSecs)),
|
|
5527
|
+
snap("embed", embedInProgress, lastEmbedTime, next(lastEmbedTime, cfg.embedIntervalSecs)),
|
|
5528
|
+
snap("vault", vaultIndexInProgress, lastVaultIndexTime, null)
|
|
5529
|
+
];
|
|
5530
|
+
}
|
|
5531
|
+
/** Start the dashboard when enabled; a failure to bind is logged, never fatal for the daemon. */
|
|
5532
|
+
async function startLiveDashboard(config) {
|
|
5533
|
+
if (!config.dashboard.enabled) return null;
|
|
5534
|
+
try {
|
|
5535
|
+
const handle = await startDashboard(config.dashboard, liveSource({
|
|
5536
|
+
storage: storageBackend,
|
|
5537
|
+
backend: () => getConfiguredBackend(config),
|
|
5538
|
+
ollamaBaseUrl: config.embedding?.ollama?.baseUrl ?? "http://127.0.0.1:11434",
|
|
5539
|
+
daemon: () => ({
|
|
5540
|
+
pid: process.pid,
|
|
5541
|
+
startTime,
|
|
5542
|
+
version: packageVersion()
|
|
5543
|
+
}),
|
|
5544
|
+
passes: livePasses
|
|
5545
|
+
}));
|
|
5546
|
+
process.stderr.write(`[pai-daemon] Dashboard: ${handle.url}\n`);
|
|
5547
|
+
return handle;
|
|
5548
|
+
} catch (e) {
|
|
5549
|
+
process.stderr.write(`[pai-daemon] Dashboard not started: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
5550
|
+
return null;
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
|
|
4822
5554
|
//#endregion
|
|
4823
5555
|
//#region src/storage/postgres/fts-migration.ts
|
|
4824
5556
|
const DONE_MARKER = "fts:simple";
|
|
@@ -4993,6 +5725,9 @@ async function serve(config) {
|
|
|
4993
5725
|
setStorageBackend(backend);
|
|
4994
5726
|
process.stderr.write(`[pai-daemon] Federation backend: ${backend.backendType}\n`);
|
|
4995
5727
|
startIndexScheduler();
|
|
5728
|
+
startLiveDashboard(config).then((h) => {
|
|
5729
|
+
dashboard = h;
|
|
5730
|
+
});
|
|
4996
5731
|
if (backend.supportsPostgresFeatures) {
|
|
4997
5732
|
startEmbedScheduler();
|
|
4998
5733
|
migrateFtsConfig(backend).catch((e) => {
|
|
@@ -5007,6 +5742,7 @@ async function serve(config) {
|
|
|
5007
5742
|
process.stderr.write(`[pai-daemon] Fatal: Could not open federation storage: ${msg}\n`);
|
|
5008
5743
|
process.exit(1);
|
|
5009
5744
|
});
|
|
5745
|
+
let dashboard = null;
|
|
5010
5746
|
const shutdown = async (signal) => {
|
|
5011
5747
|
process.stderr.write(`\n[pai-daemon] ${signal} received. Stopping.\n`);
|
|
5012
5748
|
setShutdownRequested(true);
|
|
@@ -5014,6 +5750,7 @@ async function serve(config) {
|
|
|
5014
5750
|
if (embedSchedulerTimer) clearInterval(embedSchedulerTimer);
|
|
5015
5751
|
if (cacheKeepaliveTimer) clearInterval(cacheKeepaliveTimer);
|
|
5016
5752
|
stopWorker();
|
|
5753
|
+
dashboard?.stop();
|
|
5017
5754
|
server.close();
|
|
5018
5755
|
const SHUTDOWN_TIMEOUT_MS = 1e4;
|
|
5019
5756
|
const POLL_INTERVAL_MS = 100;
|
|
@@ -5045,4 +5782,4 @@ async function serve(config) {
|
|
|
5045
5782
|
|
|
5046
5783
|
//#endregion
|
|
5047
5784
|
export { serve as t };
|
|
5048
|
-
//# sourceMappingURL=daemon-
|
|
5785
|
+
//# sourceMappingURL=daemon-CkbX-hV7.mjs.map
|