@cognee/cognee-openclaw 2026.5.21 → 2026.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +226 -97
- package/dist/src/breaker.d.ts +20 -0
- package/dist/src/breaker.js +80 -0
- package/dist/src/breaker.js.map +1 -0
- package/dist/src/client.d.ts +42 -1
- package/dist/src/client.js +80 -5
- package/dist/src/client.js.map +1 -1
- package/dist/src/config.d.ts +6 -2
- package/dist/src/config.js +25 -6
- package/dist/src/config.js.map +1 -1
- package/dist/src/persistence.d.ts +13 -1
- package/dist/src/persistence.js +59 -0
- package/dist/src/persistence.js.map +1 -1
- package/dist/src/plugin.js +816 -145
- package/dist/src/plugin.js.map +1 -1
- package/dist/src/scope.d.ts +18 -0
- package/dist/src/scope.js +40 -1
- package/dist/src/scope.js.map +1 -1
- package/dist/src/server.d.ts +66 -0
- package/dist/src/server.js +416 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/sync.d.ts +14 -2
- package/dist/src/sync.js +72 -12
- package/dist/src/sync.js.map +1 -1
- package/dist/src/types.d.ts +31 -1
- package/dist/src/types.js.map +1 -1
- package/openclaw.plugin.json +46 -6
- package/package.json +4 -3
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { runPluginCommandWithTimeout } from "openclaw/plugin-sdk/sandbox";
|
|
6
|
+
const COGNEE_PLUGIN_BASE = join(homedir(), ".cognee-plugin");
|
|
7
|
+
const API_KEY_CACHE_PATH = join(COGNEE_PLUGIN_BASE, "api_key.json");
|
|
8
|
+
// Combined install-and-boot script written to ~/.cognee-plugin/ensure_and_boot.py on first use.
|
|
9
|
+
// Handles: (1) creating the venv + installing cognee if absent, (2) booting uvicorn.
|
|
10
|
+
// Self-daemonizes so runPluginCommandWithTimeout returns in < 1 s regardless of install time.
|
|
11
|
+
// Inlining avoids import.meta.url path resolution issues in ts-jest.
|
|
12
|
+
const ENSURE_SCRIPT_PATH = join(COGNEE_PLUGIN_BASE, "ensure_and_boot.py");
|
|
13
|
+
const ENSURE_SCRIPT_CONTENT = [
|
|
14
|
+
"import subprocess, sys, os, json, time",
|
|
15
|
+
"",
|
|
16
|
+
"BASE = os.path.join(os.path.expanduser('~'), '.cognee-plugin')",
|
|
17
|
+
"VENV_DIR = os.path.join(BASE, 'venv')",
|
|
18
|
+
"_bin = 'Scripts' if os.name == 'nt' else 'bin'",
|
|
19
|
+
"_ext = '.exe' if os.name == 'nt' else ''",
|
|
20
|
+
"VENV_PYTHON = os.path.join(VENV_DIR, _bin, 'python' + _ext)",
|
|
21
|
+
"PIP = os.path.join(VENV_DIR, _bin, 'pip' + _ext)",
|
|
22
|
+
"UV_DIR = os.path.join(BASE, 'uv')",
|
|
23
|
+
"UV_BIN = os.path.join(UV_DIR, 'uv' + _ext)",
|
|
24
|
+
"READY_MARKER = os.path.join(BASE, '.venv-ready.json')",
|
|
25
|
+
"INSTALL_LOCK = os.path.join(BASE, 'venv-install.lock')",
|
|
26
|
+
"COGNEE_VERSION = '1.2.2.dev3'",
|
|
27
|
+
"",
|
|
28
|
+
"# Self-daemonize so the caller returns immediately.",
|
|
29
|
+
"if '--daemon' not in sys.argv:",
|
|
30
|
+
" port_arg = sys.argv[1] if len(sys.argv) > 1 else '8011'",
|
|
31
|
+
" p = subprocess.Popen(",
|
|
32
|
+
" [sys.executable, __file__, port_arg, '--daemon'],",
|
|
33
|
+
" start_new_session=True, close_fds=True,",
|
|
34
|
+
" stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,",
|
|
35
|
+
" )",
|
|
36
|
+
" print(p.pid)",
|
|
37
|
+
" sys.exit(0)",
|
|
38
|
+
"",
|
|
39
|
+
"PORT = next((a for a in sys.argv[1:] if a != '--daemon'), '8011')",
|
|
40
|
+
"",
|
|
41
|
+
"def pid_alive(pid):",
|
|
42
|
+
" if pid <= 1: return False",
|
|
43
|
+
" try: os.kill(pid, 0); return True",
|
|
44
|
+
" except ProcessLookupError: return False",
|
|
45
|
+
" except PermissionError: return True",
|
|
46
|
+
" except Exception: return False",
|
|
47
|
+
"",
|
|
48
|
+
"def acquire_lock(path, stale_secs=720):",
|
|
49
|
+
" os.makedirs(os.path.dirname(path) or '.', exist_ok=True)",
|
|
50
|
+
" now = time.time()",
|
|
51
|
+
" if os.path.exists(path):",
|
|
52
|
+
" try:",
|
|
53
|
+
" d = json.loads(open(path).read())",
|
|
54
|
+
" pid = int(d.get('pid', 0) or 0)",
|
|
55
|
+
" created = float(d.get('created_at', 0) or 0)",
|
|
56
|
+
" if pid_alive(pid) and now - created < stale_secs: return False",
|
|
57
|
+
" os.unlink(path)",
|
|
58
|
+
" except Exception: pass",
|
|
59
|
+
" try:",
|
|
60
|
+
" fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)",
|
|
61
|
+
" with os.fdopen(fd, 'w') as f: json.dump({'pid': os.getpid(), 'created_at': now}, f)",
|
|
62
|
+
" return True",
|
|
63
|
+
" except FileExistsError: return False",
|
|
64
|
+
"",
|
|
65
|
+
"def release_lock(path):",
|
|
66
|
+
" try: os.unlink(path)",
|
|
67
|
+
" except Exception: pass",
|
|
68
|
+
"",
|
|
69
|
+
"def install_cognee():",
|
|
70
|
+
" os.makedirs(BASE, exist_ok=True)",
|
|
71
|
+
" if not acquire_lock(INSTALL_LOCK):",
|
|
72
|
+
" deadline = time.monotonic() + 720",
|
|
73
|
+
" while time.monotonic() < deadline:",
|
|
74
|
+
" if os.path.exists(VENV_PYTHON): return True",
|
|
75
|
+
" time.sleep(0.5)",
|
|
76
|
+
" return os.path.exists(VENV_PYTHON)",
|
|
77
|
+
" try:",
|
|
78
|
+
" import shutil",
|
|
79
|
+
" uv = UV_BIN if os.path.exists(UV_BIN) else (shutil.which('uv') or '')",
|
|
80
|
+
" if uv:",
|
|
81
|
+
" uv_env = os.environ.copy()",
|
|
82
|
+
" if not os.path.exists(VENV_PYTHON):",
|
|
83
|
+
" subprocess.run(",
|
|
84
|
+
" [uv, 'venv', VENV_DIR, '--python', '3.12'],",
|
|
85
|
+
" env=uv_env, check=True, capture_output=True, timeout=300,",
|
|
86
|
+
" )",
|
|
87
|
+
" subprocess.run(",
|
|
88
|
+
" [uv, 'pip', 'install', '--upgrade', '--python', VENV_PYTHON,",
|
|
89
|
+
" f'cognee=={COGNEE_VERSION}'],",
|
|
90
|
+
" env=uv_env, check=True, capture_output=True, timeout=600,",
|
|
91
|
+
" )",
|
|
92
|
+
" elif not os.path.exists(VENV_PYTHON):",
|
|
93
|
+
" subprocess.run(",
|
|
94
|
+
" [sys.executable, '-m', 'venv', VENV_DIR],",
|
|
95
|
+
" check=True, capture_output=True, timeout=120,",
|
|
96
|
+
" )",
|
|
97
|
+
" subprocess.run(",
|
|
98
|
+
" [PIP, 'install', '--upgrade', f'cognee=={COGNEE_VERSION}'],",
|
|
99
|
+
" check=True, capture_output=True, timeout=600,",
|
|
100
|
+
" )",
|
|
101
|
+
" tmp = READY_MARKER + '.tmp'",
|
|
102
|
+
" with open(tmp, 'w') as f:",
|
|
103
|
+
" json.dump({'cognee_version': COGNEE_VERSION, 'python': VENV_PYTHON,",
|
|
104
|
+
" 'updated_at': time.time()}, f)",
|
|
105
|
+
" os.replace(tmp, READY_MARKER)",
|
|
106
|
+
" return True",
|
|
107
|
+
" except Exception: return False",
|
|
108
|
+
" finally: release_lock(INSTALL_LOCK)",
|
|
109
|
+
"",
|
|
110
|
+
"def boot_server():",
|
|
111
|
+
" if not os.path.exists(VENV_PYTHON): return",
|
|
112
|
+
" home = os.path.expanduser('~')",
|
|
113
|
+
" env = dict(os.environ)",
|
|
114
|
+
" env['COGNEE_AGENT_MODE'] = 'true'",
|
|
115
|
+
" env['AUTO_FEEDBACK'] = 'true'",
|
|
116
|
+
" env['CACHING'] = 'true'",
|
|
117
|
+
" env['SYSTEM_ROOT_DIRECTORY'] = os.path.join(home, '.cognee', 'system')",
|
|
118
|
+
" env['DATA_ROOT_DIRECTORY'] = os.path.join(home, '.cognee', 'data')",
|
|
119
|
+
" env['CACHE_ROOT_DIRECTORY'] = os.path.join(home, '.cognee', 'cache')",
|
|
120
|
+
" subprocess.Popen(",
|
|
121
|
+
" [VENV_PYTHON, '-m', 'uvicorn', 'cognee.api.client:app', '--port', PORT],",
|
|
122
|
+
" env=env, start_new_session=True, close_fds=True,",
|
|
123
|
+
" stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,",
|
|
124
|
+
" )",
|
|
125
|
+
"",
|
|
126
|
+
"def needs_install():",
|
|
127
|
+
" if not os.path.exists(VENV_PYTHON): return True",
|
|
128
|
+
" if os.path.exists(READY_MARKER):",
|
|
129
|
+
" try:",
|
|
130
|
+
" d = json.loads(open(READY_MARKER).read())",
|
|
131
|
+
" if d.get('cognee_version') == COGNEE_VERSION: return False",
|
|
132
|
+
" except Exception: pass",
|
|
133
|
+
" return True",
|
|
134
|
+
"",
|
|
135
|
+
"if needs_install():",
|
|
136
|
+
" install_cognee()",
|
|
137
|
+
"boot_server()",
|
|
138
|
+
].join("\n");
|
|
139
|
+
// System Python candidates for running ensure_and_boot.py on a cold machine
|
|
140
|
+
// (before the plugin venv exists). Only stdlib is needed so any Python 3 works.
|
|
141
|
+
const SYSTEM_PYTHON_CANDIDATES = [
|
|
142
|
+
"/usr/bin/python3", // macOS Xcode CLT + most Linux
|
|
143
|
+
"/usr/local/bin/python3", // some Linux, older Homebrew
|
|
144
|
+
"/opt/homebrew/bin/python3", // Homebrew on Apple Silicon
|
|
145
|
+
];
|
|
146
|
+
function findSystemPython() {
|
|
147
|
+
for (const p of SYSTEM_PYTHON_CANDIDATES) {
|
|
148
|
+
if (existsSync(p))
|
|
149
|
+
return p;
|
|
150
|
+
}
|
|
151
|
+
return "python3"; // PATH fallback
|
|
152
|
+
}
|
|
153
|
+
async function saveApiKeyCache(baseUrl, key) {
|
|
154
|
+
try {
|
|
155
|
+
await mkdir(COGNEE_PLUGIN_BASE, { recursive: true });
|
|
156
|
+
await writeFile(API_KEY_CACHE_PATH, JSON.stringify({ base_url: baseUrl, api_key: key, updated_at: new Date().toISOString() }), "utf-8");
|
|
157
|
+
}
|
|
158
|
+
catch { /* best-effort */ }
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Resolve a permanent Cognee API key for this deployment, using the same
|
|
162
|
+
* strategy as the claude-code and codex integrations:
|
|
163
|
+
* 1. COGNEE_API_KEY env
|
|
164
|
+
* 2. Cached key in ~/.cognee-plugin/api_key.json
|
|
165
|
+
* 3. Existing key returned by GET /api/v1/auth/api-keys
|
|
166
|
+
* 4. Mint a new one via POST /api/v1/auth/api-keys and cache it
|
|
167
|
+
*
|
|
168
|
+
* The client's ensureAuth() has already run before this is called, so
|
|
169
|
+
* the HTTP calls go out authenticated. Returns "" if every path fails
|
|
170
|
+
* (e.g. older Cognee that doesn't expose the api-keys endpoints).
|
|
171
|
+
*/
|
|
172
|
+
export async function resolveOrMintApiKey(client, logger) {
|
|
173
|
+
const envKey = (process.env["COGNEE_API_KEY"] ?? "").trim();
|
|
174
|
+
if (envKey)
|
|
175
|
+
return envKey;
|
|
176
|
+
try {
|
|
177
|
+
const cache = JSON.parse(await readFile(API_KEY_CACHE_PATH, "utf-8"));
|
|
178
|
+
const key = typeof cache.api_key === "string" ? cache.api_key.trim() : "";
|
|
179
|
+
if (key)
|
|
180
|
+
return key;
|
|
181
|
+
}
|
|
182
|
+
catch { /* cache miss */ }
|
|
183
|
+
try {
|
|
184
|
+
const keys = await client.listApiKeys();
|
|
185
|
+
const key = Array.isArray(keys) ? (keys[0]?.key ?? "").trim() : "";
|
|
186
|
+
if (key) {
|
|
187
|
+
await saveApiKeyCache(client.baseUrl, key);
|
|
188
|
+
return key;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
logger.warn?.(`cognee-openclaw: list API keys failed: ${String(e)}`);
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const { key } = await client.createApiKey("openclaw-bootstrap");
|
|
196
|
+
const trimmed = (key ?? "").trim();
|
|
197
|
+
if (trimmed) {
|
|
198
|
+
await saveApiKeyCache(client.baseUrl, trimmed);
|
|
199
|
+
logger.info?.("cognee-openclaw: minted new API key");
|
|
200
|
+
return trimmed;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
logger.warn?.(`cognee-openclaw: create API key failed: ${String(e)}`);
|
|
205
|
+
}
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
export function isLocalUrl(url) {
|
|
209
|
+
try {
|
|
210
|
+
const parsed = new URL(url);
|
|
211
|
+
return ["localhost", "127.0.0.1", "0.0.0.0", "::1"].includes(parsed.hostname);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Write ensure_and_boot.py and run it via the plugin sandbox.
|
|
219
|
+
* The script self-daemonizes, so this returns in < 1 s regardless of whether
|
|
220
|
+
* cognee needs to be installed from scratch.
|
|
221
|
+
*/
|
|
222
|
+
export async function bootServerIfNeeded(baseUrl, logger) {
|
|
223
|
+
if (!isLocalUrl(baseUrl))
|
|
224
|
+
return;
|
|
225
|
+
let port = 8011;
|
|
226
|
+
try {
|
|
227
|
+
const parsed = new URL(baseUrl);
|
|
228
|
+
if (parsed.port) {
|
|
229
|
+
const p = parseInt(parsed.port, 10);
|
|
230
|
+
if (Number.isFinite(p) && p > 0)
|
|
231
|
+
port = p;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch { /* keep default */ }
|
|
235
|
+
const python = findSystemPython();
|
|
236
|
+
await mkdir(COGNEE_PLUGIN_BASE, { recursive: true });
|
|
237
|
+
await writeFile(ENSURE_SCRIPT_PATH, ENSURE_SCRIPT_CONTENT, "utf-8");
|
|
238
|
+
const result = await runPluginCommandWithTimeout({
|
|
239
|
+
argv: [python, ENSURE_SCRIPT_PATH, String(port)],
|
|
240
|
+
timeoutMs: 5_000,
|
|
241
|
+
});
|
|
242
|
+
if (result.code !== 0) {
|
|
243
|
+
logger.warn?.(`cognee-openclaw: boot script exited ${result.code}: ${result.stderr}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// Exit-watcher: detached Python process that deregisters an agent session
|
|
248
|
+
// when the OpenClaw gateway process dies (by any means: Ctrl+C, SIGKILL,
|
|
249
|
+
// crash, clean exit). One watcher per registration, all watching the same
|
|
250
|
+
// gateway PID. State lives in ~/.openclaw/cognee/ separate from the shared
|
|
251
|
+
// Cognee venv infrastructure in ~/.cognee-plugin/.
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
const OPENCLAW_STATE_DIR = join(homedir(), ".openclaw", "cognee");
|
|
254
|
+
const EXIT_WATCHER_SCRIPT_PATH = join(OPENCLAW_STATE_DIR, "exit-watcher.py");
|
|
255
|
+
const EXIT_WATCHERS_DIR = join(OPENCLAW_STATE_DIR, "exit-watchers");
|
|
256
|
+
const EXIT_WATCHER_CONTENT = [
|
|
257
|
+
"import json, os, sys, time, urllib.request",
|
|
258
|
+
"",
|
|
259
|
+
"POLL = 2.0",
|
|
260
|
+
"LOG_PATH = os.path.join(os.path.expanduser('~'), '.openclaw', 'cognee', 'exit-watcher.log')",
|
|
261
|
+
"",
|
|
262
|
+
"def log(msg):",
|
|
263
|
+
" try:",
|
|
264
|
+
" with open(LOG_PATH, 'a') as f:",
|
|
265
|
+
" f.write(time.strftime('%Y-%m-%dT%H:%M:%S') + ' ' + str(msg) + '\\n')",
|
|
266
|
+
" except Exception: pass",
|
|
267
|
+
"",
|
|
268
|
+
"def pid_alive(pid):",
|
|
269
|
+
" if pid <= 1: return False",
|
|
270
|
+
" try: os.kill(pid, 0); return True",
|
|
271
|
+
" except ProcessLookupError: return False",
|
|
272
|
+
" except PermissionError: return True",
|
|
273
|
+
" except Exception: return False",
|
|
274
|
+
"",
|
|
275
|
+
"def owns_pidfile(path):",
|
|
276
|
+
" try: return int(open(path).read().strip()) == os.getpid()",
|
|
277
|
+
" except Exception: return False",
|
|
278
|
+
"",
|
|
279
|
+
"def post_json(base_url, path, payload, api_key, timeout):",
|
|
280
|
+
" url = base_url.rstrip('/') + path",
|
|
281
|
+
" data = json.dumps(payload).encode()",
|
|
282
|
+
" req = urllib.request.Request(url, data=data, method='POST',",
|
|
283
|
+
" headers={'Content-Type': 'application/json'})",
|
|
284
|
+
" # X-Api-Key only: an API key is not a JWT, and a bogus Bearer can be",
|
|
285
|
+
" # rejected by JWT-validating servers before the key is considered.",
|
|
286
|
+
" if api_key:",
|
|
287
|
+
" req.add_header('X-Api-Key', api_key)",
|
|
288
|
+
" with urllib.request.urlopen(req, timeout=timeout) as r:",
|
|
289
|
+
" return r.read()",
|
|
290
|
+
"",
|
|
291
|
+
"def bridge_session(base_url, dataset_name, session_id, api_key):",
|
|
292
|
+
" # Bridge the server-side session cache into the permanent graph BEFORE",
|
|
293
|
+
" # unregistering: unregister may drop activeAgents to 0, and in",
|
|
294
|
+
" # COGNEE_AGENT_MODE the server then shuts down mid-pipeline.",
|
|
295
|
+
" # run_in_background=false so the call returns only when the bridge is done.",
|
|
296
|
+
" try:",
|
|
297
|
+
" body = post_json(base_url, '/api/v1/improve',",
|
|
298
|
+
" {'dataset_name': dataset_name, 'session_ids': [session_id],",
|
|
299
|
+
" 'run_in_background': False}, api_key, 120)",
|
|
300
|
+
" log(f'improve ok session={session_id} body={body.decode()[:200]}')",
|
|
301
|
+
" except Exception as e:",
|
|
302
|
+
" log(f'improve error session={session_id} type={type(e).__name__} err={e}')",
|
|
303
|
+
"",
|
|
304
|
+
"def deregister(base_url, name, api_key):",
|
|
305
|
+
" try:",
|
|
306
|
+
" body = post_json(base_url, '/api/v1/agents/unregister',",
|
|
307
|
+
" {'agent_session_name': name}, api_key, 10)",
|
|
308
|
+
" log(f'deregister ok name={name} body={body.decode()[:200]}')",
|
|
309
|
+
" except Exception as e:",
|
|
310
|
+
" log(f'deregister error name={name} type={type(e).__name__} err={e}')",
|
|
311
|
+
"",
|
|
312
|
+
"if '--daemon' not in sys.argv:",
|
|
313
|
+
" import subprocess",
|
|
314
|
+
" subprocess.Popen([sys.executable, __file__, sys.argv[1], '--daemon'],",
|
|
315
|
+
" start_new_session=True, close_fds=True,",
|
|
316
|
+
" stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)",
|
|
317
|
+
" sys.exit(0)",
|
|
318
|
+
"",
|
|
319
|
+
"try: a = json.loads(next(x for x in sys.argv[1:] if x != '--daemon'))",
|
|
320
|
+
"except Exception as e: log(f'arg parse error: {e}'); sys.exit(0)",
|
|
321
|
+
"",
|
|
322
|
+
"gw_pid = int(a.get('gateway_pid', 0))",
|
|
323
|
+
"name = str(a.get('agent_session_name', ''))",
|
|
324
|
+
"base_url = str(a.get('base_url', 'http://localhost:8011'))",
|
|
325
|
+
"api_key = str(a.get('api_key', '') or '')",
|
|
326
|
+
"pidfile = str(a.get('pidfile', ''))",
|
|
327
|
+
"dataset_name = str(a.get('dataset_name', '') or '')",
|
|
328
|
+
"cognee_session_id = str(a.get('cognee_session_id', '') or '')",
|
|
329
|
+
"",
|
|
330
|
+
"if not gw_pid or not name or not pidfile:",
|
|
331
|
+
" log(f'bad args gw_pid={gw_pid} name={name} pidfile={pidfile}'); sys.exit(0)",
|
|
332
|
+
"",
|
|
333
|
+
"try:",
|
|
334
|
+
" os.makedirs(os.path.dirname(pidfile) or '.', exist_ok=True)",
|
|
335
|
+
" open(pidfile, 'w').write(str(os.getpid()))",
|
|
336
|
+
"except Exception as e:",
|
|
337
|
+
" log(f'pidfile write error: {e}'); sys.exit(0)",
|
|
338
|
+
"",
|
|
339
|
+
"log(f'started pid={os.getpid()} gw_pid={gw_pid} name={name}')",
|
|
340
|
+
"",
|
|
341
|
+
"while owns_pidfile(pidfile) and pid_alive(gw_pid):",
|
|
342
|
+
" time.sleep(POLL)",
|
|
343
|
+
"",
|
|
344
|
+
"if owns_pidfile(pidfile):",
|
|
345
|
+
" log(f'gateway dead, deregistering name={name}')",
|
|
346
|
+
" if dataset_name and cognee_session_id:",
|
|
347
|
+
" bridge_session(base_url, dataset_name, cognee_session_id, api_key)",
|
|
348
|
+
" deregister(base_url, name, api_key)",
|
|
349
|
+
" try: os.unlink(pidfile)",
|
|
350
|
+
" except Exception: pass",
|
|
351
|
+
"else:",
|
|
352
|
+
" log(f'pidfile gone (clean exit) name={name}')",
|
|
353
|
+
].join("\n");
|
|
354
|
+
/** Returns the pidfile path for an exit-watcher tracking the given session name. */
|
|
355
|
+
export function exitWatcherPidfilePath(agentSessionName) {
|
|
356
|
+
const safe = agentSessionName.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 200);
|
|
357
|
+
return join(EXIT_WATCHERS_DIR, `${safe}.pid`);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Spawn a detached exit-watcher that fires when the gateway process
|
|
361
|
+
* (gatewayPid) exits for any reason. If datasetName + cogneeSessionId are
|
|
362
|
+
* given, it first bridges that session's cache into the graph via
|
|
363
|
+
* /api/v1/improve, then calls /api/v1/agents/unregister for agentSessionName.
|
|
364
|
+
* Returns immediately — the actual watcher runs as a separate OS process.
|
|
365
|
+
* Signal clean deregistration by deleting the pidfile (exitWatcherPidfilePath);
|
|
366
|
+
* the watcher detects the missing pidfile and self-exits without making an HTTP call.
|
|
367
|
+
*/
|
|
368
|
+
export async function spawnExitWatcher(params) {
|
|
369
|
+
try {
|
|
370
|
+
await mkdir(EXIT_WATCHERS_DIR, { recursive: true });
|
|
371
|
+
await writeFile(EXIT_WATCHER_SCRIPT_PATH, EXIT_WATCHER_CONTENT, "utf-8");
|
|
372
|
+
const args = JSON.stringify({
|
|
373
|
+
gateway_pid: params.gatewayPid,
|
|
374
|
+
agent_session_name: params.agentSessionName,
|
|
375
|
+
base_url: params.baseUrl,
|
|
376
|
+
api_key: params.apiKey ?? "",
|
|
377
|
+
pidfile: params.pidfilePath,
|
|
378
|
+
dataset_name: params.datasetName ?? "",
|
|
379
|
+
cognee_session_id: params.cogneeSessionId ?? "",
|
|
380
|
+
});
|
|
381
|
+
const python = findSystemPython();
|
|
382
|
+
const result = await runPluginCommandWithTimeout({
|
|
383
|
+
argv: [python, EXIT_WATCHER_SCRIPT_PATH, args],
|
|
384
|
+
timeoutMs: 5_000,
|
|
385
|
+
});
|
|
386
|
+
if (result.code !== 0) {
|
|
387
|
+
params.logger.warn?.(`cognee-openclaw: exit-watcher spawn failed (exit ${result.code}): ${result.stderr}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch (e) {
|
|
391
|
+
params.logger.warn?.(`cognee-openclaw: exit-watcher spawn error: ${String(e)}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Poll the Cognee /health endpoint until it returns 200 or the timeout elapses.
|
|
396
|
+
* Returns a Promise so callers can chain deferred work without blocking.
|
|
397
|
+
* Rejects with an Error on timeout.
|
|
398
|
+
*/
|
|
399
|
+
export async function waitForServerHealth(baseUrl, timeoutMs = 600_000) {
|
|
400
|
+
const healthUrl = `${baseUrl.replace(/\/$/, "")}/health`;
|
|
401
|
+
const deadline = Date.now() + timeoutMs;
|
|
402
|
+
while (Date.now() < deadline) {
|
|
403
|
+
await new Promise((r) => setTimeout(r, 2_000));
|
|
404
|
+
try {
|
|
405
|
+
const ctrl = new AbortController();
|
|
406
|
+
const t = setTimeout(() => ctrl.abort(), 2_000);
|
|
407
|
+
const resp = await fetch(healthUrl, { signal: ctrl.signal });
|
|
408
|
+
clearTimeout(t);
|
|
409
|
+
if (resp.ok)
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
catch { /* not ready yet */ }
|
|
413
|
+
}
|
|
414
|
+
throw new Error(`Cognee server not healthy after ${Math.round(timeoutMs / 1000)}s`);
|
|
415
|
+
}
|
|
416
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/src/sync.d.ts
CHANGED
|
@@ -3,13 +3,25 @@ import type { CogneePluginConfig, MemoryFile, MemoryScope, ScopedSyncIndexes, Sy
|
|
|
3
3
|
export declare function syncFiles(client: CogneeHttpClient, changedFiles: MemoryFile[], fullFiles: MemoryFile[], syncIndex: SyncIndex, cfg: Required<CogneePluginConfig>, logger: {
|
|
4
4
|
info?: (msg: string) => void;
|
|
5
5
|
warn?: (msg: string) => void;
|
|
6
|
-
}, overrideDatasetName?: string
|
|
6
|
+
}, overrideDatasetName?: string,
|
|
7
|
+
/**
|
|
8
|
+
* Persist the mutated syncIndex to the legacy single-scope file. Callers that
|
|
9
|
+
* own their own persistence (scoped sync, per-agent sync) pass false so they
|
|
10
|
+
* don't clobber sync-index.json. Defaults to true for legacy single-scope use.
|
|
11
|
+
*/
|
|
12
|
+
persistIndex?: boolean): Promise<SyncResult & {
|
|
7
13
|
datasetId?: string;
|
|
8
14
|
}>;
|
|
9
15
|
export declare function syncFilesScoped(client: CogneeHttpClient, changedFiles: MemoryFile[], fullFiles: MemoryFile[], scopedIndexes: ScopedSyncIndexes, cfg: Required<CogneePluginConfig>, logger: {
|
|
10
16
|
info?: (msg: string) => void;
|
|
11
17
|
warn?: (msg: string) => void;
|
|
12
|
-
}, runtimeAgentId?: string
|
|
18
|
+
}, runtimeAgentId?: string,
|
|
19
|
+
/**
|
|
20
|
+
* Restrict processing to these scopes. Used to sync only the shared scopes
|
|
21
|
+
* (`company`/`user`) from the default workspace when per-agent memory owns
|
|
22
|
+
* the `agent` scope. When omitted, all scopes are processed (legacy behavior).
|
|
23
|
+
*/
|
|
24
|
+
onlyScopes?: MemoryScope[]): Promise<SyncResult & {
|
|
13
25
|
datasetIds: Record<MemoryScope, string | undefined>;
|
|
14
26
|
}>;
|
|
15
27
|
export declare let COGNIFY_POLL_INTERVAL_MS: number;
|
package/dist/src/sync.js
CHANGED
|
@@ -10,10 +10,37 @@ import { datasetNameForScope, routeFileToScope } from "./scope.js";
|
|
|
10
10
|
// counterpart, and the existing 1.0.3 update endpoint already triggers a
|
|
11
11
|
// follow-up cognify internally, so the fan-out is the same).
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
|
-
export async function syncFiles(client, changedFiles, fullFiles, syncIndex, cfg, logger, overrideDatasetName
|
|
13
|
+
export async function syncFiles(client, changedFiles, fullFiles, syncIndex, cfg, logger, overrideDatasetName,
|
|
14
|
+
/**
|
|
15
|
+
* Persist the mutated syncIndex to the legacy single-scope file. Callers that
|
|
16
|
+
* own their own persistence (scoped sync, per-agent sync) pass false so they
|
|
17
|
+
* don't clobber sync-index.json. Defaults to true for legacy single-scope use.
|
|
18
|
+
*/
|
|
19
|
+
persistIndex = true) {
|
|
14
20
|
const result = { added: 0, updated: 0, skipped: 0, errors: 0, deleted: 0 };
|
|
15
21
|
const dsName = overrideDatasetName || cfg.datasetName;
|
|
16
22
|
let datasetId = syncIndex.datasetId;
|
|
23
|
+
if (datasetId && syncIndex.datasetName && syncIndex.datasetName !== dsName) {
|
|
24
|
+
logger.info?.(`cognee-openclaw: cached datasetId belongs to "${syncIndex.datasetName}" but current dataset is "${dsName}" — ignoring stale datasetId`);
|
|
25
|
+
datasetId = undefined;
|
|
26
|
+
syncIndex.datasetId = undefined;
|
|
27
|
+
}
|
|
28
|
+
function isDatasetAccessError(message) {
|
|
29
|
+
const msg = message.toLowerCase();
|
|
30
|
+
return (msg.includes("(401)") ||
|
|
31
|
+
msg.includes("(403)") ||
|
|
32
|
+
msg.includes("(404)") ||
|
|
33
|
+
msg.includes("unauthorizeddataaccesserror") ||
|
|
34
|
+
msg.includes("not accessible") ||
|
|
35
|
+
msg.includes("permission denied"));
|
|
36
|
+
}
|
|
37
|
+
function isUpdateFallbackError(message) {
|
|
38
|
+
const msg = message.toLowerCase();
|
|
39
|
+
return (msg.includes("404") ||
|
|
40
|
+
msg.includes("409") ||
|
|
41
|
+
msg.includes("not found") ||
|
|
42
|
+
isDatasetAccessError(message));
|
|
43
|
+
}
|
|
17
44
|
// Partition changed files into "needs add" vs "needs update".
|
|
18
45
|
// We process updates per-file (PATCH /update) and batch all adds into a
|
|
19
46
|
// single /remember call at the end of the loop.
|
|
@@ -47,7 +74,11 @@ export async function syncFiles(client, changedFiles, fullFiles, syncIndex, cfg,
|
|
|
47
74
|
}
|
|
48
75
|
catch (updateError) {
|
|
49
76
|
const errorMsg = updateError instanceof Error ? updateError.message : String(updateError);
|
|
50
|
-
if (
|
|
77
|
+
if (isUpdateFallbackError(errorMsg)) {
|
|
78
|
+
if (isDatasetAccessError(errorMsg)) {
|
|
79
|
+
datasetId = undefined;
|
|
80
|
+
syncIndex.datasetId = undefined;
|
|
81
|
+
}
|
|
51
82
|
logger.info?.(`cognee-openclaw: update failed for ${file.path}, falling back to remember`);
|
|
52
83
|
// fall through to add path
|
|
53
84
|
}
|
|
@@ -61,11 +92,11 @@ export async function syncFiles(client, changedFiles, fullFiles, syncIndex, cfg,
|
|
|
61
92
|
toAdd.push(file);
|
|
62
93
|
}
|
|
63
94
|
if (toAdd.length > 0) {
|
|
64
|
-
|
|
95
|
+
const rememberBatch = async (resolvedDatasetId) => {
|
|
65
96
|
const rememberResponse = await client.remember({
|
|
66
97
|
files: toAdd.map((f) => ({ filePath: f.path, data: wrapWithMetadata(f) })),
|
|
67
98
|
datasetName: dsName,
|
|
68
|
-
datasetId,
|
|
99
|
+
datasetId: resolvedDatasetId,
|
|
69
100
|
});
|
|
70
101
|
if (rememberResponse.datasetId && rememberResponse.datasetId !== datasetId) {
|
|
71
102
|
datasetId = rememberResponse.datasetId;
|
|
@@ -85,13 +116,32 @@ export async function syncFiles(client, changedFiles, fullFiles, syncIndex, cfg,
|
|
|
85
116
|
}
|
|
86
117
|
syncIndex.datasetId = datasetId;
|
|
87
118
|
syncIndex.datasetName = dsName;
|
|
119
|
+
};
|
|
120
|
+
try {
|
|
121
|
+
await rememberBatch(datasetId);
|
|
88
122
|
}
|
|
89
123
|
catch (error) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
124
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
125
|
+
if (isDatasetAccessError(errorMsg)) {
|
|
126
|
+
datasetId = undefined;
|
|
127
|
+
syncIndex.datasetId = undefined;
|
|
128
|
+
try {
|
|
129
|
+
await rememberBatch(undefined);
|
|
130
|
+
}
|
|
131
|
+
catch (retryError) {
|
|
132
|
+
for (const file of toAdd) {
|
|
133
|
+
result.errors++;
|
|
134
|
+
logger.warn?.(`cognee-openclaw: failed to sync ${file.path}: ${retryError instanceof Error ? retryError.message : String(retryError)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// One failed batch fails every queued file — surface them all so the
|
|
140
|
+
// caller's error count reflects the actual workload, not just one entry.
|
|
141
|
+
for (const file of toAdd) {
|
|
142
|
+
result.errors++;
|
|
143
|
+
logger.warn?.(`cognee-openclaw: failed to sync ${file.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
144
|
+
}
|
|
95
145
|
}
|
|
96
146
|
}
|
|
97
147
|
}
|
|
@@ -124,13 +174,20 @@ export async function syncFiles(client, changedFiles, fullFiles, syncIndex, cfg,
|
|
|
124
174
|
}
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
|
-
|
|
177
|
+
if (persistIndex)
|
|
178
|
+
await saveSyncIndex(syncIndex);
|
|
128
179
|
return { ...result, datasetId };
|
|
129
180
|
}
|
|
130
181
|
// ---------------------------------------------------------------------------
|
|
131
182
|
// Multi-scope sync
|
|
132
183
|
// ---------------------------------------------------------------------------
|
|
133
|
-
export async function syncFilesScoped(client, changedFiles, fullFiles, scopedIndexes, cfg, logger, runtimeAgentId
|
|
184
|
+
export async function syncFilesScoped(client, changedFiles, fullFiles, scopedIndexes, cfg, logger, runtimeAgentId,
|
|
185
|
+
/**
|
|
186
|
+
* Restrict processing to these scopes. Used to sync only the shared scopes
|
|
187
|
+
* (`company`/`user`) from the default workspace when per-agent memory owns
|
|
188
|
+
* the `agent` scope. When omitted, all scopes are processed (legacy behavior).
|
|
189
|
+
*/
|
|
190
|
+
onlyScopes) {
|
|
134
191
|
const totalResult = { added: 0, updated: 0, skipped: 0, errors: 0, deleted: 0 };
|
|
135
192
|
const datasetIds = { company: undefined, user: undefined, agent: undefined };
|
|
136
193
|
// Group changed files by scope
|
|
@@ -150,11 +207,14 @@ export async function syncFilesScoped(client, changedFiles, fullFiles, scopedInd
|
|
|
150
207
|
fullByScope.set(scope, list);
|
|
151
208
|
}
|
|
152
209
|
// Determine which scopes need processing
|
|
210
|
+
const scopeFilter = onlyScopes ? new Set(onlyScopes) : null;
|
|
153
211
|
const allScopes = new Set([
|
|
154
212
|
...changedByScope.keys(),
|
|
155
213
|
...Object.keys(scopedIndexes),
|
|
156
214
|
]);
|
|
157
215
|
for (const scope of allScopes) {
|
|
216
|
+
if (scopeFilter && !scopeFilter.has(scope))
|
|
217
|
+
continue;
|
|
158
218
|
const dsName = datasetNameForScope(scope, cfg, runtimeAgentId);
|
|
159
219
|
const scopeChanged = changedByScope.get(scope) ?? [];
|
|
160
220
|
const scopeFull = fullByScope.get(scope) ?? [];
|
|
@@ -167,7 +227,7 @@ export async function syncFilesScoped(client, changedFiles, fullFiles, scopedInd
|
|
|
167
227
|
if (scopeChanged.length === 0 && !hasDeletedFiles)
|
|
168
228
|
continue;
|
|
169
229
|
logger.info?.(`cognee-openclaw: [${scope}] syncing ${scopeChanged.length} changed file(s) to dataset "${dsName}"${hasDeletedFiles ? " + deletions" : ""}`);
|
|
170
|
-
const result = await syncFiles(client, scopeChanged, scopeFull, scopeIndex, cfg, logger, dsName);
|
|
230
|
+
const result = await syncFiles(client, scopeChanged, scopeFull, scopeIndex, cfg, logger, dsName, false);
|
|
171
231
|
totalResult.added += result.added;
|
|
172
232
|
totalResult.updated += result.updated;
|
|
173
233
|
totalResult.skipped += result.skipped;
|