mem-os-python 1.6.27__py3-none-any.whl
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.
- mem_os_python-1.6.27.dist-info/METADATA +1032 -0
- mem_os_python-1.6.27.dist-info/RECORD +253 -0
- mem_os_python-1.6.27.dist-info/WHEEL +4 -0
- mem_os_python-1.6.27.dist-info/entry_points.txt +2 -0
- mem_os_python-1.6.27.dist-info/licenses/LICENSE +21 -0
- memos/__init__.py +16 -0
- memos/_js/adapters/anthropic.d.ts +279 -0
- memos/_js/adapters/anthropic.d.ts.map +1 -0
- memos/_js/adapters/anthropic.js +348 -0
- memos/_js/adapters/anthropic.js.map +1 -0
- memos/_js/adapters/base.d.ts +10 -0
- memos/_js/adapters/base.d.ts.map +1 -0
- memos/_js/adapters/base.js +2 -0
- memos/_js/adapters/base.js.map +1 -0
- memos/_js/adapters/openai.d.ts +264 -0
- memos/_js/adapters/openai.d.ts.map +1 -0
- memos/_js/adapters/openai.js +342 -0
- memos/_js/adapters/openai.js.map +1 -0
- memos/_js/apps/explorer.d.ts +244 -0
- memos/_js/apps/explorer.d.ts.map +1 -0
- memos/_js/apps/explorer.js +738 -0
- memos/_js/apps/explorer.js.map +1 -0
- memos/_js/citations.d.ts +65 -0
- memos/_js/citations.d.ts.map +1 -0
- memos/_js/citations.js +143 -0
- memos/_js/citations.js.map +1 -0
- memos/_js/cli-browse.d.ts +3 -0
- memos/_js/cli-browse.d.ts.map +1 -0
- memos/_js/cli-browse.js +129 -0
- memos/_js/cli-browse.js.map +1 -0
- memos/_js/cli-completion.d.ts +31 -0
- memos/_js/cli-completion.d.ts.map +1 -0
- memos/_js/cli-completion.js +340 -0
- memos/_js/cli-completion.js.map +1 -0
- memos/_js/cli-help.d.ts +18 -0
- memos/_js/cli-help.d.ts.map +1 -0
- memos/_js/cli-help.js +357 -0
- memos/_js/cli-help.js.map +1 -0
- memos/_js/cli.d.ts +28 -0
- memos/_js/cli.d.ts.map +1 -0
- memos/_js/cli.js +2880 -0
- memos/_js/cli.js.map +1 -0
- memos/_js/compression.d.ts +25 -0
- memos/_js/compression.d.ts.map +1 -0
- memos/_js/compression.js +319 -0
- memos/_js/compression.js.map +1 -0
- memos/_js/confidence-machine.d.ts +99 -0
- memos/_js/confidence-machine.d.ts.map +1 -0
- memos/_js/confidence-machine.js +222 -0
- memos/_js/confidence-machine.js.map +1 -0
- memos/_js/context-pack.d.ts +506 -0
- memos/_js/context-pack.d.ts.map +1 -0
- memos/_js/context-pack.js +1435 -0
- memos/_js/context-pack.js.map +1 -0
- memos/_js/contradictions.d.ts +160 -0
- memos/_js/contradictions.d.ts.map +1 -0
- memos/_js/contradictions.js +255 -0
- memos/_js/contradictions.js.map +1 -0
- memos/_js/dashboard.d.ts +30 -0
- memos/_js/dashboard.d.ts.map +1 -0
- memos/_js/dashboard.js +302 -0
- memos/_js/dashboard.js.map +1 -0
- memos/_js/ebbinghaus.d.ts +29 -0
- memos/_js/ebbinghaus.d.ts.map +1 -0
- memos/_js/ebbinghaus.js +32 -0
- memos/_js/ebbinghaus.js.map +1 -0
- memos/_js/embedding-queue.d.ts +190 -0
- memos/_js/embedding-queue.d.ts.map +1 -0
- memos/_js/embedding-queue.js +316 -0
- memos/_js/embedding-queue.js.map +1 -0
- memos/_js/embeddings.d.ts +187 -0
- memos/_js/embeddings.d.ts.map +1 -0
- memos/_js/embeddings.js +633 -0
- memos/_js/embeddings.js.map +1 -0
- memos/_js/entity-extraction.d.ts +54 -0
- memos/_js/entity-extraction.d.ts.map +1 -0
- memos/_js/entity-extraction.js +325 -0
- memos/_js/entity-extraction.js.map +1 -0
- memos/_js/entity-index.d.ts +93 -0
- memos/_js/entity-index.d.ts.map +1 -0
- memos/_js/entity-index.js +165 -0
- memos/_js/entity-index.js.map +1 -0
- memos/_js/event-memory.d.ts +147 -0
- memos/_js/event-memory.d.ts.map +1 -0
- memos/_js/event-memory.js +290 -0
- memos/_js/event-memory.js.map +1 -0
- memos/_js/external-import.d.ts +98 -0
- memos/_js/external-import.d.ts.map +1 -0
- memos/_js/external-import.js +669 -0
- memos/_js/external-import.js.map +1 -0
- memos/_js/fidelity.d.ts +367 -0
- memos/_js/fidelity.d.ts.map +1 -0
- memos/_js/fidelity.js +911 -0
- memos/_js/fidelity.js.map +1 -0
- memos/_js/graph-expansion.d.ts +119 -0
- memos/_js/graph-expansion.d.ts.map +1 -0
- memos/_js/graph-expansion.js +213 -0
- memos/_js/graph-expansion.js.map +1 -0
- memos/_js/graph-mermaid.d.ts +13 -0
- memos/_js/graph-mermaid.d.ts.map +1 -0
- memos/_js/graph-mermaid.js +32 -0
- memos/_js/graph-mermaid.js.map +1 -0
- memos/_js/graph.d.ts +170 -0
- memos/_js/graph.d.ts.map +1 -0
- memos/_js/graph.js +482 -0
- memos/_js/graph.js.map +1 -0
- memos/_js/harness.d.ts +63 -0
- memos/_js/harness.d.ts.map +1 -0
- memos/_js/harness.js +93 -0
- memos/_js/harness.js.map +1 -0
- memos/_js/importance.d.ts +118 -0
- memos/_js/importance.d.ts.map +1 -0
- memos/_js/importance.js +144 -0
- memos/_js/importance.js.map +1 -0
- memos/_js/index.d.ts +51 -0
- memos/_js/index.d.ts.map +1 -0
- memos/_js/index.js +53 -0
- memos/_js/index.js.map +1 -0
- memos/_js/mcp.d.ts +58 -0
- memos/_js/mcp.d.ts.map +1 -0
- memos/_js/mcp.js +1853 -0
- memos/_js/mcp.js.map +1 -0
- memos/_js/memory.d.ts +1762 -0
- memos/_js/memory.d.ts.map +1 -0
- memos/_js/memory.js +6283 -0
- memos/_js/memory.js.map +1 -0
- memos/_js/procedural.d.ts +74 -0
- memos/_js/procedural.d.ts.map +1 -0
- memos/_js/procedural.js +125 -0
- memos/_js/procedural.js.map +1 -0
- memos/_js/project-config.d.ts +68 -0
- memos/_js/project-config.d.ts.map +1 -0
- memos/_js/project-config.js +59 -0
- memos/_js/project-config.js.map +1 -0
- memos/_js/proof-of-forgetting.d.ts +57 -0
- memos/_js/proof-of-forgetting.d.ts.map +1 -0
- memos/_js/proof-of-forgetting.js +80 -0
- memos/_js/proof-of-forgetting.js.map +1 -0
- memos/_js/provenance.d.ts +122 -0
- memos/_js/provenance.d.ts.map +1 -0
- memos/_js/provenance.js +182 -0
- memos/_js/provenance.js.map +1 -0
- memos/_js/quarantine-commands.d.ts +23 -0
- memos/_js/quarantine-commands.d.ts.map +1 -0
- memos/_js/quarantine-commands.js +42 -0
- memos/_js/quarantine-commands.js.map +1 -0
- memos/_js/quarantine.d.ts +120 -0
- memos/_js/quarantine.d.ts.map +1 -0
- memos/_js/quarantine.js +873 -0
- memos/_js/quarantine.js.map +1 -0
- memos/_js/redact.d.ts +12 -0
- memos/_js/redact.d.ts.map +1 -0
- memos/_js/redact.js +26 -0
- memos/_js/redact.js.map +1 -0
- memos/_js/repl.d.ts +57 -0
- memos/_js/repl.d.ts.map +1 -0
- memos/_js/repl.js +295 -0
- memos/_js/repl.js.map +1 -0
- memos/_js/rerank-local.d.ts +61 -0
- memos/_js/rerank-local.d.ts.map +1 -0
- memos/_js/rerank-local.js +210 -0
- memos/_js/rerank-local.js.map +1 -0
- memos/_js/retain-filter.d.ts +98 -0
- memos/_js/retain-filter.d.ts.map +1 -0
- memos/_js/retain-filter.js +232 -0
- memos/_js/retain-filter.js.map +1 -0
- memos/_js/retrieval.d.ts +265 -0
- memos/_js/retrieval.d.ts.map +1 -0
- memos/_js/retrieval.js +1018 -0
- memos/_js/retrieval.js.map +1 -0
- memos/_js/revert.d.ts +77 -0
- memos/_js/revert.d.ts.map +1 -0
- memos/_js/revert.js +252 -0
- memos/_js/revert.js.map +1 -0
- memos/_js/scope.d.ts +30 -0
- memos/_js/scope.d.ts.map +1 -0
- memos/_js/scope.js +71 -0
- memos/_js/scope.js.map +1 -0
- memos/_js/semantic-screen.d.ts +152 -0
- memos/_js/semantic-screen.d.ts.map +1 -0
- memos/_js/semantic-screen.js +290 -0
- memos/_js/semantic-screen.js.map +1 -0
- memos/_js/session-hooks.d.ts +127 -0
- memos/_js/session-hooks.d.ts.map +1 -0
- memos/_js/session-hooks.js +482 -0
- memos/_js/session-hooks.js.map +1 -0
- memos/_js/storage/sqlite.d.ts +427 -0
- memos/_js/storage/sqlite.d.ts.map +1 -0
- memos/_js/storage/sqlite.js +2428 -0
- memos/_js/storage/sqlite.js.map +1 -0
- memos/_js/sync.d.ts +213 -0
- memos/_js/sync.d.ts.map +1 -0
- memos/_js/sync.js +389 -0
- memos/_js/sync.js.map +1 -0
- memos/_js/tamper-log.d.ts +98 -0
- memos/_js/tamper-log.d.ts.map +1 -0
- memos/_js/tamper-log.js +327 -0
- memos/_js/tamper-log.js.map +1 -0
- memos/_js/temporal.d.ts +92 -0
- memos/_js/temporal.d.ts.map +1 -0
- memos/_js/temporal.js +536 -0
- memos/_js/temporal.js.map +1 -0
- memos/_js/time-travel.d.ts +38 -0
- memos/_js/time-travel.d.ts.map +1 -0
- memos/_js/time-travel.js +46 -0
- memos/_js/time-travel.js.map +1 -0
- memos/_js/timestamp-format.d.ts +18 -0
- memos/_js/timestamp-format.d.ts.map +1 -0
- memos/_js/timestamp-format.js +52 -0
- memos/_js/timestamp-format.js.map +1 -0
- memos/_js/triple-extraction.d.ts +31 -0
- memos/_js/triple-extraction.d.ts.map +1 -0
- memos/_js/triple-extraction.js +116 -0
- memos/_js/triple-extraction.js.map +1 -0
- memos/_js/types.d.ts +2474 -0
- memos/_js/types.d.ts.map +1 -0
- memos/_js/types.js +20 -0
- memos/_js/types.js.map +1 -0
- memos/_js/updater.d.ts +95 -0
- memos/_js/updater.d.ts.map +1 -0
- memos/_js/updater.js +268 -0
- memos/_js/updater.js.map +1 -0
- memos/_js/version.d.ts +17 -0
- memos/_js/version.d.ts.map +1 -0
- memos/_js/version.js +34 -0
- memos/_js/version.js.map +1 -0
- memos/_js/vision/clip-query-cache.d.ts +58 -0
- memos/_js/vision/clip-query-cache.d.ts.map +1 -0
- memos/_js/vision/clip-query-cache.js +79 -0
- memos/_js/vision/clip-query-cache.js.map +1 -0
- memos/_js/vision/clip.d.ts +113 -0
- memos/_js/vision/clip.d.ts.map +1 -0
- memos/_js/vision/clip.js +224 -0
- memos/_js/vision/clip.js.map +1 -0
- memos/_js/vision/fake-clip.d.ts +55 -0
- memos/_js/vision/fake-clip.d.ts.map +1 -0
- memos/_js/vision/fake-clip.js +122 -0
- memos/_js/vision/fake-clip.js.map +1 -0
- memos/_js/vision/index.d.ts +15 -0
- memos/_js/vision/index.d.ts.map +1 -0
- memos/_js/vision/index.js +13 -0
- memos/_js/vision/index.js.map +1 -0
- memos/_js/vision/store.d.ts +137 -0
- memos/_js/vision/store.d.ts.map +1 -0
- memos/_js/vision/store.js +383 -0
- memos/_js/vision/store.js.map +1 -0
- memos/adapters/__init__.py +0 -0
- memos/adapters/crewai.py +385 -0
- memos/adapters/langchain.py +301 -0
- memos/adapters/ollama.py +396 -0
- memos/server/__init__.py +0 -0
- memos/server/main.py +547 -0
- memos/server/routes.py +360 -0
memos/_js/dashboard.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local dashboard — a browser UI for the MemOS memory store.
|
|
3
|
+
*
|
|
4
|
+
* `memos dashboard` starts a local HTTP server (no external dependencies)
|
|
5
|
+
* serving a single-page dashboard for browsing, searching, and visualizing
|
|
6
|
+
* the memory graph. No MCP Apps host required.
|
|
7
|
+
*
|
|
8
|
+
* API:
|
|
9
|
+
* GET /api/memories?limit=50&offset=0 — list memories
|
|
10
|
+
* GET /api/search?q=...&limit=20 — search memories
|
|
11
|
+
* GET /api/memory/:id — get one memory
|
|
12
|
+
* GET /api/graph — graph nodes + edges
|
|
13
|
+
* GET /api/stats — store statistics
|
|
14
|
+
*
|
|
15
|
+
* @module @memos/dashboard
|
|
16
|
+
*/
|
|
17
|
+
import { createServer, } from "node:http";
|
|
18
|
+
import { redactSecrets } from "./redact.js";
|
|
19
|
+
const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<head>
|
|
22
|
+
<meta charset="UTF-8">
|
|
23
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
24
|
+
<title>MemOS Dashboard</title>
|
|
25
|
+
<style>
|
|
26
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
27
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0f1115; color: #e6e6e6; }
|
|
28
|
+
header { background: #1a1d24; padding: 1rem 2rem; border-bottom: 1px solid #2a2e38; display: flex; align-items: center; gap: 1rem; }
|
|
29
|
+
header h1 { font-size: 1.25rem; font-weight: 600; }
|
|
30
|
+
header .badge { background: #2d6a4f; color: #fff; padding: 0.15rem 0.6rem; border-radius: 1rem; font-size: 0.75rem; }
|
|
31
|
+
nav { display: flex; gap: 0.5rem; padding: 1rem 2rem; background: #14161b; border-bottom: 1px solid #2a2e38; }
|
|
32
|
+
nav button { background: #23262e; color: #e6e6e6; border: 1px solid #2a2e38; padding: 0.5rem 1rem; border-radius: 0.375rem; cursor: pointer; }
|
|
33
|
+
nav button.active { background: #2d6a4f; border-color: #2d6a4f; }
|
|
34
|
+
nav button:hover { background: #2e323c; }
|
|
35
|
+
nav button.active:hover { background: #2d6a4f; }
|
|
36
|
+
main { padding: 2rem; max-width: 1200px; margin: 0 auto; }
|
|
37
|
+
.search-bar { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; }
|
|
38
|
+
.search-bar input { flex: 1; background: #1a1d24; border: 1px solid #2a2e38; color: #e6e6e6; padding: 0.75rem 1rem; border-radius: 0.375rem; font-size: 1rem; }
|
|
39
|
+
.search-bar button { background: #2d6a4f; color: #fff; border: none; padding: 0.75rem 1.5rem; border-radius: 0.375rem; cursor: pointer; font-size: 1rem; }
|
|
40
|
+
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
|
|
41
|
+
.stat-card { background: #1a1d24; border: 1px solid #2a2e38; border-radius: 0.5rem; padding: 1.25rem; }
|
|
42
|
+
.stat-card .value { font-size: 2rem; font-weight: 700; color: #52b788; }
|
|
43
|
+
.stat-card .label { color: #888; font-size: 0.875rem; margin-top: 0.25rem; }
|
|
44
|
+
.memory-list { display: flex; flex-direction: column; gap: 0.75rem; }
|
|
45
|
+
.memory-card { background: #1a1d24; border: 1px solid #2a2e38; border-radius: 0.5rem; padding: 1rem 1.25rem; }
|
|
46
|
+
.memory-card .meta { display: flex; gap: 0.75rem; align-items: center; margin-bottom: 0.5rem; font-size: 0.8rem; color: #888; }
|
|
47
|
+
.memory-card .type { background: #2a2e38; padding: 0.15rem 0.5rem; border-radius: 0.25rem; }
|
|
48
|
+
.memory-card .content { line-height: 1.5; }
|
|
49
|
+
.memory-card .id { font-family: monospace; font-size: 0.75rem; }
|
|
50
|
+
#graph-canvas { width: 100%; height: 600px; background: #1a1d24; border: 1px solid #2a2e38; border-radius: 0.5rem; }
|
|
51
|
+
.empty { text-align: center; color: #666; padding: 3rem; }
|
|
52
|
+
.loading { text-align: center; color: #888; padding: 2rem; }
|
|
53
|
+
</style>
|
|
54
|
+
</head>
|
|
55
|
+
<body>
|
|
56
|
+
<header>
|
|
57
|
+
<h1>MemOS Dashboard</h1>
|
|
58
|
+
<span class="badge">local</span>
|
|
59
|
+
</header>
|
|
60
|
+
<nav>
|
|
61
|
+
<button data-view="browse" class="active">Browse</button>
|
|
62
|
+
<button data-view="search">Search</button>
|
|
63
|
+
<button data-view="graph">Graph</button>
|
|
64
|
+
<button data-view="stats">Stats</button>
|
|
65
|
+
</nav>
|
|
66
|
+
<main>
|
|
67
|
+
<div id="view-browse">
|
|
68
|
+
<div class="memory-list" id="memory-list"><div class="loading">Loading...</div></div>
|
|
69
|
+
</div>
|
|
70
|
+
<div id="view-search" style="display:none">
|
|
71
|
+
<div class="search-bar">
|
|
72
|
+
<input type="text" id="search-input" placeholder="Search memories...">
|
|
73
|
+
<button onclick="doSearch()">Search</button>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="memory-list" id="search-results"></div>
|
|
76
|
+
</div>
|
|
77
|
+
<div id="view-graph" style="display:none">
|
|
78
|
+
<canvas id="graph-canvas"></canvas>
|
|
79
|
+
</div>
|
|
80
|
+
<div id="view-stats" style="display:none">
|
|
81
|
+
<div class="stats" id="stats-grid"><div class="loading">Loading...</div></div>
|
|
82
|
+
</div>
|
|
83
|
+
</main>
|
|
84
|
+
<script>
|
|
85
|
+
const views = ["browse", "search", "graph", "stats"];
|
|
86
|
+
document.querySelectorAll("nav button").forEach(btn => {
|
|
87
|
+
btn.addEventListener("click", () => {
|
|
88
|
+
document.querySelectorAll("nav button").forEach(b => b.classList.remove("active"));
|
|
89
|
+
btn.classList.add("active");
|
|
90
|
+
const view = btn.dataset.view;
|
|
91
|
+
views.forEach(v => document.getElementById("view-" + v).style.display = v === view ? "block" : "none");
|
|
92
|
+
if (view === "browse") loadMemories();
|
|
93
|
+
if (view === "graph") loadGraph();
|
|
94
|
+
if (view === "stats") loadStats();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
function memoryCard(m) {
|
|
99
|
+
const date = m.createdAt ? new Date(m.createdAt).toLocaleString() : "";
|
|
100
|
+
return '<div class="memory-card">' +
|
|
101
|
+
'<div class="meta"><span class="type">' + esc(m.type || "note") + '</span>' +
|
|
102
|
+
'<span class="id">' + esc((m.id || "").slice(0, 8)) + '</span>' +
|
|
103
|
+
'<span>' + esc(date) + '</span></div>' +
|
|
104
|
+
'<div class="content">' + esc(m.content || "") + '</div></div>';
|
|
105
|
+
}
|
|
106
|
+
function esc(s) {
|
|
107
|
+
return String(s).replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c]));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function loadMemories() {
|
|
111
|
+
const el = document.getElementById("memory-list");
|
|
112
|
+
try {
|
|
113
|
+
const r = await fetch("/api/memories?limit=50");
|
|
114
|
+
const data = await r.json();
|
|
115
|
+
el.innerHTML = data.memories.length
|
|
116
|
+
? data.memories.map(memoryCard).join("")
|
|
117
|
+
: '<div class="empty">No memories yet. Use <code>memos store</code> to add some.</div>';
|
|
118
|
+
} catch (e) { el.innerHTML = '<div class="empty">Failed to load: ' + esc(e.message) + '</div>'; }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function doSearch() {
|
|
122
|
+
const q = document.getElementById("search-input").value.trim();
|
|
123
|
+
const el = document.getElementById("search-results");
|
|
124
|
+
if (!q) { el.innerHTML = ""; return; }
|
|
125
|
+
el.innerHTML = '<div class="loading">Searching...</div>';
|
|
126
|
+
try {
|
|
127
|
+
const r = await fetch("/api/search?q=" + encodeURIComponent(q) + "&limit=20");
|
|
128
|
+
const data = await r.json();
|
|
129
|
+
el.innerHTML = data.results.length
|
|
130
|
+
? data.results.map(x => memoryCard(x.node || x)).join("")
|
|
131
|
+
: '<div class="empty">No results.</div>';
|
|
132
|
+
} catch (e) { el.innerHTML = '<div class="empty">Search failed: ' + esc(e.message) + '</div>'; }
|
|
133
|
+
}
|
|
134
|
+
document.getElementById("search-input").addEventListener("keydown", e => {
|
|
135
|
+
if (e.key === "Enter") doSearch();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
async function loadGraph() {
|
|
139
|
+
const canvas = document.getElementById("graph-canvas");
|
|
140
|
+
const ctx = canvas.getContext("2d");
|
|
141
|
+
canvas.width = canvas.offsetWidth; canvas.height = 600;
|
|
142
|
+
ctx.fillStyle = "#888"; ctx.font = "14px sans-serif";
|
|
143
|
+
ctx.fillText("Loading graph...", 20, 30);
|
|
144
|
+
try {
|
|
145
|
+
const r = await fetch("/api/graph");
|
|
146
|
+
const data = await r.json();
|
|
147
|
+
drawGraph(ctx, canvas, data);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
ctx.fillText("Failed: " + e.message, 20, 30);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function drawGraph(ctx, canvas, data) {
|
|
154
|
+
const nodes = data.nodes || [];
|
|
155
|
+
const edges = data.edges || [];
|
|
156
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
157
|
+
if (!nodes.length) {
|
|
158
|
+
ctx.fillStyle = "#666"; ctx.fillText("No memories to graph.", 20, 30);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// Simple radial layout
|
|
162
|
+
const cx = canvas.width / 2, cy = canvas.height / 2;
|
|
163
|
+
const radius = Math.min(cx, cy) - 60;
|
|
164
|
+
const pos = {};
|
|
165
|
+
nodes.forEach((n, i) => {
|
|
166
|
+
const angle = (2 * Math.PI * i) / nodes.length;
|
|
167
|
+
pos[n.id] = { x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle) };
|
|
168
|
+
});
|
|
169
|
+
// Edges
|
|
170
|
+
ctx.strokeStyle = "#2a2e38"; ctx.lineWidth = 1;
|
|
171
|
+
edges.forEach(e => {
|
|
172
|
+
const a = pos[e.source] || pos[e.from], b = pos[e.target] || pos[e.to];
|
|
173
|
+
if (a && b) { ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke(); }
|
|
174
|
+
});
|
|
175
|
+
// Nodes
|
|
176
|
+
nodes.forEach(n => {
|
|
177
|
+
const p = pos[n.id];
|
|
178
|
+
ctx.fillStyle = "#52b788";
|
|
179
|
+
ctx.beginPath(); ctx.arc(p.x, p.y, 8, 0, 2 * Math.PI); ctx.fill();
|
|
180
|
+
ctx.fillStyle = "#aaa"; ctx.font = "11px sans-serif";
|
|
181
|
+
const label = (n.content || n.id || "").slice(0, 24);
|
|
182
|
+
ctx.fillText(label, p.x + 12, p.y + 4);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function loadStats() {
|
|
187
|
+
const el = document.getElementById("stats-grid");
|
|
188
|
+
try {
|
|
189
|
+
const r = await fetch("/api/stats");
|
|
190
|
+
const s = await r.json();
|
|
191
|
+
el.innerHTML =
|
|
192
|
+
statCard(s.total ?? 0, "Total memories") +
|
|
193
|
+
statCard(s.byType ? Object.keys(s.byType).length : 0, "Memory types") +
|
|
194
|
+
statCard(s.edges ?? 0, "Graph edges");
|
|
195
|
+
} catch (e) { el.innerHTML = '<div class="empty">Failed: ' + esc(e.message) + '</div>'; }
|
|
196
|
+
}
|
|
197
|
+
function statCard(value, label) {
|
|
198
|
+
return '<div class="stat-card"><div class="value">' + value + '</div><div class="label">' + esc(label) + '</div></div>';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
loadMemories();
|
|
202
|
+
</script>
|
|
203
|
+
</body>
|
|
204
|
+
</html>`;
|
|
205
|
+
/**
|
|
206
|
+
* Start the local dashboard server.
|
|
207
|
+
* Returns the server instance and the URL.
|
|
208
|
+
*/
|
|
209
|
+
export async function startDashboard(memos, opts = {}) {
|
|
210
|
+
const port = opts.port ?? 0; // 0 = random available port
|
|
211
|
+
const server = createServer(async (req, res) => {
|
|
212
|
+
// CORS for local dev
|
|
213
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
214
|
+
const url = new URL(req.url ?? "/", `http://localhost`);
|
|
215
|
+
try {
|
|
216
|
+
// API routes
|
|
217
|
+
if (url.pathname === "/api/memories") {
|
|
218
|
+
const limit = parseInt(url.searchParams.get("limit") ?? "50", 10);
|
|
219
|
+
const offset = parseInt(url.searchParams.get("offset") ?? "0", 10);
|
|
220
|
+
const graph = await memos.getGraph();
|
|
221
|
+
const nodes = graph.nodes ?? [];
|
|
222
|
+
const memories = nodes.slice(offset, offset + limit);
|
|
223
|
+
sendJson(res, { memories, total: nodes.length });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (url.pathname === "/api/search") {
|
|
227
|
+
const q = url.searchParams.get("q") ?? "";
|
|
228
|
+
const limit = parseInt(url.searchParams.get("limit") ?? "20", 10);
|
|
229
|
+
const results = q ? (await memos.search(q)).slice(0, limit) : [];
|
|
230
|
+
sendJson(res, { results, query: q });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (url.pathname.startsWith("/api/memory/")) {
|
|
234
|
+
const id = decodeURIComponent(url.pathname.slice("/api/memory/".length));
|
|
235
|
+
const node = await memos.retrieve(id);
|
|
236
|
+
if (!node) {
|
|
237
|
+
sendJson(res, { error: "not found" }, 404);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
sendJson(res, { memory: node });
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (url.pathname === "/api/graph") {
|
|
245
|
+
const graph = await memos.getGraph();
|
|
246
|
+
sendJson(res, {
|
|
247
|
+
nodes: graph.nodes ?? [],
|
|
248
|
+
edges: graph.edges ?? [],
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (url.pathname === "/api/stats") {
|
|
253
|
+
const graph = await memos.getGraph();
|
|
254
|
+
const nodes = graph.nodes ?? [];
|
|
255
|
+
const byType = {};
|
|
256
|
+
for (const n of nodes) {
|
|
257
|
+
const t = n.type ?? "note";
|
|
258
|
+
byType[t] = (byType[t] ?? 0) + 1;
|
|
259
|
+
}
|
|
260
|
+
sendJson(res, {
|
|
261
|
+
total: nodes.length,
|
|
262
|
+
byType,
|
|
263
|
+
edges: (graph.edges ?? []).length,
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Serve the dashboard HTML for all other routes
|
|
268
|
+
if (url.pathname === "/" || !url.pathname.startsWith("/api/")) {
|
|
269
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
270
|
+
res.end(DASHBOARD_HTML);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
sendJson(res, { error: "not found" }, 404);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
// Never echo raw error text to the HTTP client: it can carry stack
|
|
277
|
+
// traces or SQL fragments (CodeQL js/stack-trace-exposure). Log it
|
|
278
|
+
// server-side (localhost operator's own terminal, redacted) and
|
|
279
|
+
// return a generic 500.
|
|
280
|
+
console.error("[dashboard] request failed:", redactSecrets(err instanceof Error ? err.message : String(err)));
|
|
281
|
+
sendJson(res, { error: "internal error" }, 500);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
await new Promise((resolve, reject) => {
|
|
285
|
+
server.once("error", reject);
|
|
286
|
+
server.listen(port, "127.0.0.1", () => resolve());
|
|
287
|
+
});
|
|
288
|
+
const address = server.address();
|
|
289
|
+
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
290
|
+
const url = `http://127.0.0.1:${actualPort}`;
|
|
291
|
+
return {
|
|
292
|
+
url,
|
|
293
|
+
close: () => new Promise((resolve, reject) => {
|
|
294
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
295
|
+
}),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function sendJson(res, data, status = 200) {
|
|
299
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
300
|
+
res.end(JSON.stringify(data));
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=dashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ebbinghaus forgetting-curve reinforcement for retrieval scoring.
|
|
3
|
+
*
|
|
4
|
+
* From MemoryBank (AAAI 2024): score memories by R = e^(-Δt/S), where
|
|
5
|
+
* Δt is days since last access and S is the access count (strength).
|
|
6
|
+
* Frequently-recalled memories stay strong; untouched old ones fade
|
|
7
|
+
* exponentially. Fixes stale preferences overriding recent corrections.
|
|
8
|
+
*
|
|
9
|
+
* Pure arithmetic, no LLM. Blended as a small additive term in scoring.
|
|
10
|
+
* Opt-in via `experimental.ebbinghaus`.
|
|
11
|
+
*/
|
|
12
|
+
export interface EbbinghausNode {
|
|
13
|
+
accessCount: number;
|
|
14
|
+
lastAccessed: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compute the Ebbinghaus recall strength for a node.
|
|
18
|
+
* Returns a value in (0, 1]: 1.0 for just-accessed, decaying exponentially.
|
|
19
|
+
*
|
|
20
|
+
* @param node - Node with accessCount and lastAccessed
|
|
21
|
+
* @param now - Current time in epoch ms (defaults to Date.now())
|
|
22
|
+
*/
|
|
23
|
+
export declare function recallStrength(node: EbbinghausNode, now?: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* Weight for blending Ebbinghaus score into fused results.
|
|
26
|
+
* Small by default to be safe; the fused score dominates.
|
|
27
|
+
*/
|
|
28
|
+
export declare const EBBINGHAUS_WEIGHT = 0.1;
|
|
29
|
+
//# sourceMappingURL=ebbinghaus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ebbinghaus.d.ts","sourceRoot":"","sources":["../src/ebbinghaus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,cAAc,EACpB,GAAG,GAAE,MAAmB,GACvB,MAAM,CASR;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC"}
|
memos/_js/ebbinghaus.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ebbinghaus forgetting-curve reinforcement for retrieval scoring.
|
|
3
|
+
*
|
|
4
|
+
* From MemoryBank (AAAI 2024): score memories by R = e^(-Δt/S), where
|
|
5
|
+
* Δt is days since last access and S is the access count (strength).
|
|
6
|
+
* Frequently-recalled memories stay strong; untouched old ones fade
|
|
7
|
+
* exponentially. Fixes stale preferences overriding recent corrections.
|
|
8
|
+
*
|
|
9
|
+
* Pure arithmetic, no LLM. Blended as a small additive term in scoring.
|
|
10
|
+
* Opt-in via `experimental.ebbinghaus`.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Compute the Ebbinghaus recall strength for a node.
|
|
14
|
+
* Returns a value in (0, 1]: 1.0 for just-accessed, decaying exponentially.
|
|
15
|
+
*
|
|
16
|
+
* @param node - Node with accessCount and lastAccessed
|
|
17
|
+
* @param now - Current time in epoch ms (defaults to Date.now())
|
|
18
|
+
*/
|
|
19
|
+
export function recallStrength(node, now = Date.now()) {
|
|
20
|
+
const accessCount = Math.max(1, node.accessCount || 1);
|
|
21
|
+
const lastAccessed = node.lastAccessed || now;
|
|
22
|
+
// Δt in days
|
|
23
|
+
const deltaDays = Math.max(0, (now - lastAccessed) / (1000 * 60 * 60 * 24));
|
|
24
|
+
// R = e^(-Δt/S)
|
|
25
|
+
return Math.exp(-deltaDays / accessCount);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Weight for blending Ebbinghaus score into fused results.
|
|
29
|
+
* Small by default to be safe; the fused score dominates.
|
|
30
|
+
*/
|
|
31
|
+
export const EBBINGHAUS_WEIGHT = 0.1;
|
|
32
|
+
//# sourceMappingURL=ebbinghaus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ebbinghaus.js","sourceRoot":"","sources":["../src/ebbinghaus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAoB,EACpB,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IAExB,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,GAAG,CAAC;IAE9C,aAAa;IACb,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IAE5E,gBAAgB;IAChB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,GAAG,WAAW,CAAC,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmbeddingQueue — bounded, retryable, event-emitting background worker
|
|
3
|
+
* pool for embedding generation.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists: the previous MemOS engine called `provider.embed()` inline
|
|
6
|
+
* from `store()` and `update()`. For local-hash that costs nothing, but the
|
|
7
|
+
* moment a user points MemOS at Ollama, LM Studio, or any OpenAI-compatible
|
|
8
|
+
* endpoint, every write blocks for the full round-trip. This queue lets
|
|
9
|
+
* `store()` return as soon as the node is persisted, with the embedding work
|
|
10
|
+
* happening in the background under a bounded concurrency cap.
|
|
11
|
+
*
|
|
12
|
+
* Design:
|
|
13
|
+
* - One shared queue per MemOS instance.
|
|
14
|
+
* - `enqueue()` is fire-and-forget — returns a job id immediately.
|
|
15
|
+
* - Workers are pulled in FIFO order. Concurrency is bounded by
|
|
16
|
+
* `EmbeddingQueueConfig.concurrency` (default 2).
|
|
17
|
+
* - When `batchSize` > 1 and the provider implements `batchEmbed`,
|
|
18
|
+
* a worker drains up to `batchSize` pending jobs into one batched
|
|
19
|
+
* call; a failed batch falls back to per-job retries.
|
|
20
|
+
* - Failures are retried with exponential backoff up to `maxRetries`
|
|
21
|
+
* (default 3) before the job is marked `failed` and an
|
|
22
|
+
* `embedding:failed` event fires.
|
|
23
|
+
* - Backpressure: if `maxQueueSize` is exceeded, `enqueue()` throws
|
|
24
|
+
* instead of growing memory unbounded. Callers can `await flush()`
|
|
25
|
+
* to make room.
|
|
26
|
+
* - Lifecycle events: `embedding:queued`, `embedding:started`,
|
|
27
|
+
* `embedding:complete`, `embedding:failed`, `embedding:retry`.
|
|
28
|
+
*
|
|
29
|
+
* The queue does not know about storage. It only knows how to call the
|
|
30
|
+
* `EmbeddingProvider`. Persistence of the resulting vector is the
|
|
31
|
+
* responsibility of the caller (a `persist` callback) so this module
|
|
32
|
+
* stays storage-agnostic and easy to test.
|
|
33
|
+
*
|
|
34
|
+
* @module @memos/embedding-queue
|
|
35
|
+
*/
|
|
36
|
+
import type { EmbeddingProvider, EmbeddingVector } from "./types.js";
|
|
37
|
+
/** Lifecycle states an embedding job can be in. */
|
|
38
|
+
export type EmbeddingJobStatus = "queued" | "running" | "complete" | "failed" | "retrying";
|
|
39
|
+
/** Reasons a job can be marked failed. */
|
|
40
|
+
export type EmbeddingFailureReason = "max_retries" | "aborted" | "provider_error";
|
|
41
|
+
/** A single embedding job, exposed for observability. */
|
|
42
|
+
export interface EmbeddingJob {
|
|
43
|
+
/** Stable id assigned at enqueue time. */
|
|
44
|
+
id: string;
|
|
45
|
+
/** Memory node this embedding is for. */
|
|
46
|
+
nodeId: string;
|
|
47
|
+
/** Text to embed. */
|
|
48
|
+
text: string;
|
|
49
|
+
/** Current status. */
|
|
50
|
+
status: EmbeddingJobStatus;
|
|
51
|
+
/** Number of times we have called the provider for this job. */
|
|
52
|
+
attempts: number;
|
|
53
|
+
/** Most recent error, if any. */
|
|
54
|
+
lastError: string | null;
|
|
55
|
+
/** Timestamp the job was enqueued (ms). */
|
|
56
|
+
enqueuedAt: number;
|
|
57
|
+
/** Timestamp of the most recent status change (ms). */
|
|
58
|
+
updatedAt: number;
|
|
59
|
+
/** Computed vector, set when status is `complete`. */
|
|
60
|
+
vector: EmbeddingVector | null;
|
|
61
|
+
/** Model id, set when status is `complete`. */
|
|
62
|
+
model: string | null;
|
|
63
|
+
}
|
|
64
|
+
/** Persist callback — the queue does not know how to write the vector. */
|
|
65
|
+
export type PersistEmbeddingFn = (nodeId: string, vector: EmbeddingVector, model: string) => Promise<void>;
|
|
66
|
+
/** Status-change callback — used by MemOS to track per-node status. */
|
|
67
|
+
export type EmbeddingStatusChangeFn = (nodeId: string, status: EmbeddingJobStatus, error: string | null) => void | Promise<void>;
|
|
68
|
+
/** Configuration for EmbeddingQueue. */
|
|
69
|
+
export interface EmbeddingQueueConfig {
|
|
70
|
+
/** Embedding provider. Required. */
|
|
71
|
+
provider: EmbeddingProvider;
|
|
72
|
+
/** Maximum number of concurrent in-flight embed calls. Default 2. */
|
|
73
|
+
concurrency?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Maximum number of pending jobs drained into a single `batchEmbed()`
|
|
76
|
+
* call when the provider supports batching (e.g. Ollama and
|
|
77
|
+
* OpenAI-compatible servers accept array input). Default 1 = one
|
|
78
|
+
* `embed()` call per job (legacy behavior). Groups that fail as a batch
|
|
79
|
+
* fall back to the per-job retry path.
|
|
80
|
+
*/
|
|
81
|
+
batchSize?: number;
|
|
82
|
+
/** Maximum number of jobs buffered when workers are saturated. Default 10000. */
|
|
83
|
+
maxQueueSize?: number;
|
|
84
|
+
/** Maximum number of retry attempts per job. Default 3. */
|
|
85
|
+
maxRetries?: number;
|
|
86
|
+
/** Base backoff in ms; retries use exponential backoff. Default 250. */
|
|
87
|
+
retryBackoffMs?: number;
|
|
88
|
+
/**
|
|
89
|
+
* Coalescing delay in ms before a fully-idle queue picks up newly-enqueued
|
|
90
|
+
* work. Default 5.
|
|
91
|
+
*
|
|
92
|
+
* Why: `schedule()` used to defer pickup with a bare `queueMicrotask`,
|
|
93
|
+
* so sequential `await store()` calls each formed a single-job group and
|
|
94
|
+
* `batchEmbed` was never called — one provider round trip per memory.
|
|
95
|
+
* A short linger lets rapid sequential enqueues coalesce into full
|
|
96
|
+
* `batchSize` groups (N round trips become N/batchSize). The linger only
|
|
97
|
+
* applies when no worker is busy; while workers run, pending jobs
|
|
98
|
+
* accumulate on their own and are picked up immediately. Ordering and
|
|
99
|
+
* completion guarantees are unchanged; `flush()` still drains
|
|
100
|
+
* everything. Set to 0 to restore the immediate microtask pickup.
|
|
101
|
+
*/
|
|
102
|
+
batchLingerMs?: number;
|
|
103
|
+
/** Persist callback — invoked once per job on success. Optional. */
|
|
104
|
+
onPersist?: PersistEmbeddingFn;
|
|
105
|
+
/** Status-change callback — fires on every state transition. Optional. */
|
|
106
|
+
onStatusChange?: EmbeddingStatusChangeFn;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Bounded, retryable, non-blocking embedding queue.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const queue = new EmbeddingQueue({
|
|
114
|
+
* provider,
|
|
115
|
+
* concurrency: 4,
|
|
116
|
+
* onPersist: async (nodeId, vector, model) => {
|
|
117
|
+
* await storage.saveEmbedding(nodeId, vector, model);
|
|
118
|
+
* },
|
|
119
|
+
* });
|
|
120
|
+
*
|
|
121
|
+
* const jobId = queue.enqueue(node.id, text);
|
|
122
|
+
* await queue.flush();
|
|
123
|
+
* await queue.close();
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export declare class EmbeddingQueue {
|
|
127
|
+
private readonly provider;
|
|
128
|
+
private readonly concurrency;
|
|
129
|
+
private readonly batchSize;
|
|
130
|
+
private readonly maxQueueSize;
|
|
131
|
+
private readonly maxRetries;
|
|
132
|
+
private readonly retryBackoffMs;
|
|
133
|
+
private readonly batchLingerMs;
|
|
134
|
+
private readonly onPersist?;
|
|
135
|
+
private readonly onStatusChange?;
|
|
136
|
+
/** Pending jobs not yet picked up by a worker. */
|
|
137
|
+
private pending;
|
|
138
|
+
/** Currently running job groups, by worker slot index. */
|
|
139
|
+
private readonly running;
|
|
140
|
+
/** Job index by id, for status queries. */
|
|
141
|
+
private readonly jobs;
|
|
142
|
+
/** Resolvers for `flush()` callers, resolved when the queue drains. */
|
|
143
|
+
private readonly drainWaiters;
|
|
144
|
+
/** Set true after `close()` so enqueue can reject and workers exit. */
|
|
145
|
+
private closed;
|
|
146
|
+
/** Guards one-at-a-time deferred group pickup (see schedule()). */
|
|
147
|
+
private scheduleQueued;
|
|
148
|
+
constructor(config: EmbeddingQueueConfig);
|
|
149
|
+
/**
|
|
150
|
+
* Enqueue an embedding job. Returns a job id immediately.
|
|
151
|
+
*
|
|
152
|
+
* @throws if the queue is full or has been closed.
|
|
153
|
+
*/
|
|
154
|
+
enqueue(nodeId: string, text: string): string;
|
|
155
|
+
/** Number of jobs currently waiting or running. */
|
|
156
|
+
size(): number;
|
|
157
|
+
/** Number of jobs currently being processed. */
|
|
158
|
+
activeCount(): number;
|
|
159
|
+
/** Return a snapshot of a job by id, or null if unknown. */
|
|
160
|
+
getJob(id: string): EmbeddingJob | null;
|
|
161
|
+
/** Return all jobs that are not in a terminal state. */
|
|
162
|
+
pendingJobs(): EmbeddingJob[];
|
|
163
|
+
/**
|
|
164
|
+
* Wait for the queue to drain. Resolves when both `pending` and
|
|
165
|
+
* `running` are empty. If the queue is already drained, resolves on
|
|
166
|
+
* the next microtask.
|
|
167
|
+
*/
|
|
168
|
+
flush(): Promise<void>;
|
|
169
|
+
/**
|
|
170
|
+
* Permanently stop the queue. In-flight jobs are allowed to finish
|
|
171
|
+
* (and their persist callbacks run) but no new work is accepted.
|
|
172
|
+
*/
|
|
173
|
+
close(): Promise<void>;
|
|
174
|
+
private notifyStatus;
|
|
175
|
+
/**
|
|
176
|
+
* Pull pending jobs into idle worker slots. The actual pickup is
|
|
177
|
+
* deferred by one microtask so a synchronous enqueue burst (e.g. an
|
|
178
|
+
* ingestion loop storing N memories back-to-back) coalesces into
|
|
179
|
+
* full `batchSize` groups instead of starting a half-empty group per
|
|
180
|
+
* enqueue.
|
|
181
|
+
*/
|
|
182
|
+
private schedule;
|
|
183
|
+
private scheduleNow;
|
|
184
|
+
private runGroup;
|
|
185
|
+
private runJobWithRetries;
|
|
186
|
+
/** After a worker slot frees, schedule the next pending job and
|
|
187
|
+
* resolve any flush() waiters that are now satisfied. */
|
|
188
|
+
private afterJob;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=embedding-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-queue.d.ts","sourceRoot":"","sources":["../src/embedding-queue.ts"],"names":[],"mappings":"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"}
|