@geravant/sinain 1.6.5 → 1.6.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/cli.js +159 -0
- package/launcher.js +46 -88
- package/package.json +1 -1
- package/sense_client/__main__.py +12 -24
- package/sense_client/requirements.txt +0 -1
- package/setup-overlay.js +20 -39
- package/sinain-core/src/agent/loop.ts +4 -87
- package/sinain-core/src/index.ts +3 -16
package/cli.js
CHANGED
|
@@ -7,6 +7,9 @@ import path from "path";
|
|
|
7
7
|
|
|
8
8
|
const cmd = process.argv[2];
|
|
9
9
|
const IS_WINDOWS = os.platform() === "win32";
|
|
10
|
+
const HOME = os.homedir();
|
|
11
|
+
const SINAIN_DIR = path.join(HOME, ".sinain");
|
|
12
|
+
const PKG_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
10
13
|
|
|
11
14
|
switch (cmd) {
|
|
12
15
|
case "start":
|
|
@@ -52,6 +55,14 @@ switch (cmd) {
|
|
|
52
55
|
await import("./install.js");
|
|
53
56
|
break;
|
|
54
57
|
|
|
58
|
+
case "export-knowledge":
|
|
59
|
+
await exportKnowledge();
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
case "import-knowledge":
|
|
63
|
+
await importKnowledge();
|
|
64
|
+
break;
|
|
65
|
+
|
|
55
66
|
default:
|
|
56
67
|
printUsage();
|
|
57
68
|
break;
|
|
@@ -349,6 +360,152 @@ function isProcessRunning(pattern) {
|
|
|
349
360
|
}
|
|
350
361
|
}
|
|
351
362
|
|
|
363
|
+
// ── Knowledge export/import ──────────────────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
function findWorkspace() {
|
|
366
|
+
const candidates = [
|
|
367
|
+
process.env.SINAIN_WORKSPACE,
|
|
368
|
+
path.join(HOME, ".openclaw/workspace"),
|
|
369
|
+
path.join(HOME, ".sinain/workspace"),
|
|
370
|
+
].filter(Boolean);
|
|
371
|
+
for (const dir of candidates) {
|
|
372
|
+
const resolved = dir.replace(/^~/, HOME);
|
|
373
|
+
if (fs.existsSync(resolved)) return resolved;
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function exportKnowledge() {
|
|
379
|
+
const BOLD = "\x1b[1m", GREEN = "\x1b[32m", RED = "\x1b[31m", DIM = "\x1b[2m", RESET = "\x1b[0m";
|
|
380
|
+
|
|
381
|
+
const workspace = findWorkspace();
|
|
382
|
+
if (!workspace) {
|
|
383
|
+
console.error(`${RED}✗${RESET} No knowledge workspace found.`);
|
|
384
|
+
console.error(` Checked: SINAIN_WORKSPACE env, ~/.openclaw/workspace, ~/.sinain/workspace`);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const outputIdx = process.argv.indexOf("--output");
|
|
389
|
+
const outputPath = outputIdx !== -1 && process.argv[outputIdx + 1]
|
|
390
|
+
? path.resolve(process.argv[outputIdx + 1])
|
|
391
|
+
: path.join(HOME, "sinain-knowledge-export.tar.gz");
|
|
392
|
+
|
|
393
|
+
// Collect files that exist
|
|
394
|
+
const includes = [];
|
|
395
|
+
const check = (rel) => {
|
|
396
|
+
const full = path.join(workspace, rel);
|
|
397
|
+
if (fs.existsSync(full)) { includes.push(rel); return true; }
|
|
398
|
+
return false;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
check("modules");
|
|
402
|
+
check("memory/sinain-playbook.md");
|
|
403
|
+
check("memory/knowledge-graph.db");
|
|
404
|
+
check("memory/playbook-base.md");
|
|
405
|
+
check("memory/playbook.md");
|
|
406
|
+
check("memory/sinain-knowledge.md");
|
|
407
|
+
|
|
408
|
+
if (includes.length === 0) {
|
|
409
|
+
console.error(`${RED}✗${RESET} No knowledge files found in ${workspace}`);
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
console.log(`${BOLD}[export]${RESET} Exporting from ${DIM}${workspace}${RESET}`);
|
|
414
|
+
for (const inc of includes) {
|
|
415
|
+
console.log(` ${GREEN}+${RESET} ${inc}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
execSync(
|
|
420
|
+
`tar czf "${outputPath}" --exclude="memory/triplestore.db" ${includes.map(i => `"${i}"`).join(" ")}`,
|
|
421
|
+
{ cwd: workspace, stdio: "pipe" }
|
|
422
|
+
);
|
|
423
|
+
} catch (e) {
|
|
424
|
+
console.error(`${RED}✗${RESET} tar failed: ${e.message}`);
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const size = fs.statSync(outputPath).size;
|
|
429
|
+
const sizeStr = size < 1024 * 1024
|
|
430
|
+
? `${(size / 1024).toFixed(1)} KB`
|
|
431
|
+
: `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
432
|
+
|
|
433
|
+
console.log(`\n${GREEN}✓${RESET} Exported to ${BOLD}${outputPath}${RESET} (${sizeStr})`);
|
|
434
|
+
console.log(` Transfer to another machine and run: ${BOLD}sinain import-knowledge ${path.basename(outputPath)}${RESET}`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function importKnowledge() {
|
|
438
|
+
const BOLD = "\x1b[1m", GREEN = "\x1b[32m", RED = "\x1b[31m", YELLOW = "\x1b[33m", DIM = "\x1b[2m", RESET = "\x1b[0m";
|
|
439
|
+
|
|
440
|
+
const filePath = process.argv[3];
|
|
441
|
+
if (!filePath) {
|
|
442
|
+
console.error(`${RED}✗${RESET} Usage: sinain import-knowledge <file.tar.gz>`);
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const resolved = path.resolve(filePath.replace(/^~/, HOME));
|
|
447
|
+
if (!fs.existsSync(resolved)) {
|
|
448
|
+
console.error(`${RED}✗${RESET} File not found: ${resolved}`);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const targetWorkspace = path.join(HOME, ".sinain/workspace");
|
|
453
|
+
fs.mkdirSync(targetWorkspace, { recursive: true });
|
|
454
|
+
|
|
455
|
+
console.log(`${BOLD}[import]${RESET} Importing to ${DIM}${targetWorkspace}${RESET}`);
|
|
456
|
+
|
|
457
|
+
// Extract
|
|
458
|
+
try {
|
|
459
|
+
execSync(`tar xzf "${resolved}" -C "${targetWorkspace}"`, { stdio: "inherit" });
|
|
460
|
+
} catch (e) {
|
|
461
|
+
console.error(`${RED}✗${RESET} Extraction failed: ${e.message}`);
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Symlink sinain-memory scripts from npm package
|
|
466
|
+
const srcMemory = path.join(PKG_DIR, "sinain-memory");
|
|
467
|
+
const dstMemory = path.join(targetWorkspace, "sinain-memory");
|
|
468
|
+
if (fs.existsSync(srcMemory)) {
|
|
469
|
+
try { fs.rmSync(dstMemory, { recursive: true, force: true }); } catch {}
|
|
470
|
+
fs.symlinkSync(srcMemory, dstMemory, IS_WINDOWS ? "junction" : undefined);
|
|
471
|
+
console.log(` ${GREEN}✓${RESET} sinain-memory scripts linked`);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Update ~/.sinain/.env
|
|
475
|
+
const envPath = path.join(SINAIN_DIR, ".env");
|
|
476
|
+
const envVars = {
|
|
477
|
+
SINAIN_WORKSPACE: targetWorkspace,
|
|
478
|
+
OPENCLAW_WORKSPACE_DIR: targetWorkspace,
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
if (fs.existsSync(envPath)) {
|
|
482
|
+
let content = fs.readFileSync(envPath, "utf-8");
|
|
483
|
+
for (const [key, val] of Object.entries(envVars)) {
|
|
484
|
+
const regex = new RegExp(`^#?\\s*${key}=.*$`, "m");
|
|
485
|
+
if (regex.test(content)) {
|
|
486
|
+
content = content.replace(regex, `${key}=${val}`);
|
|
487
|
+
} else {
|
|
488
|
+
content += `\n${key}=${val}`;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
fs.writeFileSync(envPath, content);
|
|
492
|
+
} else {
|
|
493
|
+
fs.mkdirSync(SINAIN_DIR, { recursive: true });
|
|
494
|
+
const lines = Object.entries(envVars).map(([k, v]) => `${k}=${v}`);
|
|
495
|
+
fs.writeFileSync(envPath, lines.join("\n") + "\n");
|
|
496
|
+
}
|
|
497
|
+
console.log(` ${GREEN}✓${RESET} SINAIN_WORKSPACE set in ${DIM}~/.sinain/.env${RESET}`);
|
|
498
|
+
|
|
499
|
+
// Summary
|
|
500
|
+
const items = [];
|
|
501
|
+
if (fs.existsSync(path.join(targetWorkspace, "modules"))) items.push("modules");
|
|
502
|
+
if (fs.existsSync(path.join(targetWorkspace, "memory/sinain-playbook.md"))) items.push("playbook");
|
|
503
|
+
if (fs.existsSync(path.join(targetWorkspace, "memory/knowledge-graph.db"))) items.push("knowledge graph");
|
|
504
|
+
|
|
505
|
+
console.log(`\n${GREEN}✓${RESET} Knowledge imported: ${items.join(", ")}`);
|
|
506
|
+
console.log(` Workspace: ${BOLD}${targetWorkspace}${RESET}`);
|
|
507
|
+
}
|
|
508
|
+
|
|
352
509
|
// ── Usage ─────────────────────────────────────────────────────────────────────
|
|
353
510
|
|
|
354
511
|
function printUsage() {
|
|
@@ -362,6 +519,8 @@ Usage:
|
|
|
362
519
|
sinain setup Run interactive setup wizard (~/.sinain/.env)
|
|
363
520
|
sinain setup-overlay Download pre-built overlay app
|
|
364
521
|
sinain setup-sck-capture Download sck-capture audio binary (macOS)
|
|
522
|
+
sinain export-knowledge Export knowledge for transfer to another machine
|
|
523
|
+
sinain import-knowledge <file> Import knowledge from export file
|
|
365
524
|
sinain install Install OpenClaw plugin (server-side)
|
|
366
525
|
|
|
367
526
|
Start options:
|
package/launcher.js
CHANGED
|
@@ -35,13 +35,11 @@ let skipSense = false;
|
|
|
35
35
|
let skipOverlay = false;
|
|
36
36
|
let skipAgent = false;
|
|
37
37
|
let agentName = null;
|
|
38
|
-
let forceSetup = false;
|
|
39
38
|
|
|
40
39
|
for (const arg of args) {
|
|
41
40
|
if (arg === "--no-sense") { skipSense = true; continue; }
|
|
42
41
|
if (arg === "--no-overlay") { skipOverlay = true; continue; }
|
|
43
42
|
if (arg === "--no-agent") { skipAgent = true; continue; }
|
|
44
|
-
if (arg === "--setup") { forceSetup = true; continue; }
|
|
45
43
|
if (arg.startsWith("--agent=")) { agentName = arg.split("=")[1]; continue; }
|
|
46
44
|
console.error(`Unknown flag: ${arg}`);
|
|
47
45
|
process.exit(1);
|
|
@@ -62,9 +60,9 @@ async function main() {
|
|
|
62
60
|
await preflight();
|
|
63
61
|
console.log();
|
|
64
62
|
|
|
65
|
-
// Run setup wizard on first launch (no ~/.sinain/.env)
|
|
63
|
+
// Run setup wizard on first launch (no ~/.sinain/.env)
|
|
66
64
|
const userEnvPath = path.join(SINAIN_DIR, ".env");
|
|
67
|
-
if (
|
|
65
|
+
if (!fs.existsSync(userEnvPath)) {
|
|
68
66
|
await setupWizard(userEnvPath);
|
|
69
67
|
}
|
|
70
68
|
|
|
@@ -129,10 +127,7 @@ async function main() {
|
|
|
129
127
|
const scDir = path.join(PKG_DIR, "sense_client");
|
|
130
128
|
// Check if key package is importable to skip pip
|
|
131
129
|
try {
|
|
132
|
-
|
|
133
|
-
? 'python3 -c "import PIL; import skimage"'
|
|
134
|
-
: 'python3 -c "import PIL; import skimage; import Quartz; import Vision"';
|
|
135
|
-
execSync(depCheck, { stdio: "pipe" });
|
|
130
|
+
execSync('python3 -c "import PIL; import skimage"', { stdio: "pipe" });
|
|
136
131
|
} catch {
|
|
137
132
|
log("Installing sense_client Python dependencies...");
|
|
138
133
|
try {
|
|
@@ -218,41 +213,7 @@ async function main() {
|
|
|
218
213
|
warn("flutter not found — overlay source found but can't build");
|
|
219
214
|
}
|
|
220
215
|
} else {
|
|
221
|
-
|
|
222
|
-
log("overlay not found — downloading from GitHub Releases...");
|
|
223
|
-
try {
|
|
224
|
-
const { downloadOverlay } = await import("./setup-overlay.js");
|
|
225
|
-
const success = await downloadOverlay({ silent: false });
|
|
226
|
-
if (success) {
|
|
227
|
-
// Re-find and launch the freshly downloaded overlay
|
|
228
|
-
const freshOverlay = findOverlay();
|
|
229
|
-
if (freshOverlay?.type === "prebuilt") {
|
|
230
|
-
if (!IS_WINDOWS) {
|
|
231
|
-
try {
|
|
232
|
-
execSync(`xattr -cr "${freshOverlay.path}"`, { stdio: "pipe" });
|
|
233
|
-
} catch { /* no quarantine */ }
|
|
234
|
-
}
|
|
235
|
-
log("Starting overlay (pre-built)...");
|
|
236
|
-
const binary = IS_WINDOWS
|
|
237
|
-
? freshOverlay.path
|
|
238
|
-
: path.join(freshOverlay.path, "Contents/MacOS/sinain_hud");
|
|
239
|
-
startProcess("overlay", binary, [], { color: MAGENTA });
|
|
240
|
-
await sleep(2000);
|
|
241
|
-
const overlayChild = children.find(c => c.name === "overlay");
|
|
242
|
-
if (overlayChild && !overlayChild.proc.killed && overlayChild.proc.exitCode === null) {
|
|
243
|
-
ok(`overlay running (pid:${overlayChild.pid})`);
|
|
244
|
-
overlayStatus = "running";
|
|
245
|
-
} else {
|
|
246
|
-
warn("overlay exited early — check logs above");
|
|
247
|
-
overlayStatus = "failed";
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
warn("overlay auto-download failed — run: sinain setup-overlay");
|
|
252
|
-
}
|
|
253
|
-
} catch (e) {
|
|
254
|
-
warn(`overlay auto-download failed: ${e.message}`);
|
|
255
|
-
}
|
|
216
|
+
warn("overlay not found — run: sinain setup-overlay");
|
|
256
217
|
}
|
|
257
218
|
}
|
|
258
219
|
|
|
@@ -376,20 +337,9 @@ async function setupWizard(envPath) {
|
|
|
376
337
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
377
338
|
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
378
339
|
|
|
379
|
-
// Load existing .env values as defaults (for re-configuration)
|
|
380
|
-
const existing = {};
|
|
381
|
-
if (fs.existsSync(envPath)) {
|
|
382
|
-
for (const line of fs.readFileSync(envPath, "utf-8").split("\n")) {
|
|
383
|
-
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
|
384
|
-
if (m) existing[m[1]] = m[2];
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
const hasExisting = Object.keys(existing).length > 0;
|
|
388
|
-
|
|
389
340
|
console.log();
|
|
390
|
-
console.log(`${BOLD}──
|
|
341
|
+
console.log(`${BOLD}── First-time setup ────────────────────${RESET}`);
|
|
391
342
|
console.log(` Configuring ${DIM}~/.sinain/.env${RESET}`);
|
|
392
|
-
if (hasExisting) console.log(` ${DIM}Press Enter to keep current values shown in [brackets]${RESET}`);
|
|
393
343
|
console.log();
|
|
394
344
|
|
|
395
345
|
const vars = {};
|
|
@@ -441,13 +391,10 @@ async function setupWizard(envPath) {
|
|
|
441
391
|
|
|
442
392
|
// 2. OpenRouter API key (if cloud backend or for vision/OCR)
|
|
443
393
|
if (transcriptionBackend === "openrouter") {
|
|
444
|
-
const existingKey = existing.OPENROUTER_API_KEY;
|
|
445
|
-
const keyHint = existingKey ? ` [${existingKey.slice(0, 8)}...${existingKey.slice(-4)}]` : "";
|
|
446
394
|
let key = "";
|
|
447
395
|
while (!key) {
|
|
448
|
-
key = await ask(` OpenRouter API key (sk-or-...)
|
|
396
|
+
key = await ask(` OpenRouter API key (sk-or-...): `);
|
|
449
397
|
key = key.trim();
|
|
450
|
-
if (!key && existingKey) { key = existingKey; break; }
|
|
451
398
|
if (key && !key.startsWith("sk-or-")) {
|
|
452
399
|
console.log(` ${YELLOW}⚠${RESET} Key should start with sk-or-. Try again or press Enter to skip.`);
|
|
453
400
|
const retry = await ask(` Use this key anyway? [y/N]: `);
|
|
@@ -461,17 +408,13 @@ async function setupWizard(envPath) {
|
|
|
461
408
|
if (key) vars.OPENROUTER_API_KEY = key;
|
|
462
409
|
} else {
|
|
463
410
|
// Still ask for OpenRouter key (needed for vision/OCR)
|
|
464
|
-
const
|
|
465
|
-
const keyHint = existingKey ? ` [${existingKey.slice(0, 8)}...${existingKey.slice(-4)}]` : "";
|
|
466
|
-
const key = await ask(` OpenRouter API key for vision/OCR (optional, Enter to skip)${keyHint}: `);
|
|
411
|
+
const key = await ask(` OpenRouter API key for vision/OCR (optional, Enter to skip): `);
|
|
467
412
|
if (key.trim()) vars.OPENROUTER_API_KEY = key.trim();
|
|
468
|
-
else if (existingKey) vars.OPENROUTER_API_KEY = existingKey;
|
|
469
413
|
}
|
|
470
414
|
|
|
471
415
|
// 3. Agent selection
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
vars.SINAIN_AGENT = agentChoice.trim().toLowerCase() || defaultAgent;
|
|
416
|
+
const agentChoice = await ask(` Agent? [${BOLD}claude${RESET}/codex/goose/junie/aider]: `);
|
|
417
|
+
vars.SINAIN_AGENT = agentChoice.trim().toLowerCase() || "claude";
|
|
475
418
|
|
|
476
419
|
// 3b. Local vision (Ollama)
|
|
477
420
|
const IS_MACOS = os.platform() === "darwin";
|
|
@@ -526,44 +469,59 @@ async function setupWizard(envPath) {
|
|
|
526
469
|
console.log(` selective — score-based (errors, questions trigger it)`);
|
|
527
470
|
console.log(` focus — always escalate every tick`);
|
|
528
471
|
console.log(` rich — always escalate with maximum context`);
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
vars.ESCALATION_MODE = escMode.trim().toLowerCase() || defaultEsc;
|
|
472
|
+
const escMode = await ask(` Escalation mode? [off/${BOLD}selective${RESET}/focus/rich]: `);
|
|
473
|
+
vars.ESCALATION_MODE = escMode.trim().toLowerCase() || "selective";
|
|
532
474
|
|
|
533
475
|
// 5. OpenClaw gateway
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
:
|
|
540
|
-
if (wantsGateway) {
|
|
541
|
-
const defaultWs = existing.OPENCLAW_WS_URL || "ws://localhost:18789";
|
|
542
|
-
const wsUrl = await ask(` Gateway WebSocket URL [${defaultWs}]: `);
|
|
543
|
-
vars.OPENCLAW_WS_URL = wsUrl.trim() || defaultWs;
|
|
544
|
-
|
|
545
|
-
const existingToken = existing.OPENCLAW_WS_TOKEN;
|
|
546
|
-
const tokenHint = existingToken ? ` [${existingToken.slice(0, 6)}...${existingToken.slice(-4)}]` : "";
|
|
547
|
-
const wsToken = await ask(` Gateway auth token (48-char hex)${tokenHint}: `);
|
|
476
|
+
const hasGateway = await ask(` Do you have an OpenClaw gateway? [y/N]: `);
|
|
477
|
+
if (hasGateway.trim().toLowerCase() === "y") {
|
|
478
|
+
const wsUrl = await ask(` Gateway WebSocket URL [ws://localhost:18789]: `);
|
|
479
|
+
vars.OPENCLAW_WS_URL = wsUrl.trim() || "ws://localhost:18789";
|
|
480
|
+
|
|
481
|
+
const wsToken = await ask(` Gateway auth token (48-char hex): `);
|
|
548
482
|
if (wsToken.trim()) {
|
|
549
483
|
vars.OPENCLAW_WS_TOKEN = wsToken.trim();
|
|
550
484
|
vars.OPENCLAW_HTTP_TOKEN = wsToken.trim();
|
|
551
|
-
} else if (existingToken) {
|
|
552
|
-
vars.OPENCLAW_WS_TOKEN = existingToken;
|
|
553
|
-
vars.OPENCLAW_HTTP_TOKEN = existing.OPENCLAW_HTTP_TOKEN || existingToken;
|
|
554
485
|
}
|
|
555
486
|
|
|
556
487
|
// Derive HTTP URL from WS URL
|
|
557
488
|
const httpBase = vars.OPENCLAW_WS_URL.replace(/^ws/, "http");
|
|
558
489
|
vars.OPENCLAW_HTTP_URL = `${httpBase}/hooks/agent`;
|
|
559
|
-
vars.OPENCLAW_SESSION_KEY =
|
|
490
|
+
vars.OPENCLAW_SESSION_KEY = "agent:main:sinain";
|
|
560
491
|
} else {
|
|
561
492
|
// No gateway — disable WS connection attempts
|
|
562
493
|
vars.OPENCLAW_WS_URL = "";
|
|
563
494
|
vars.OPENCLAW_HTTP_URL = "";
|
|
564
495
|
}
|
|
565
496
|
|
|
566
|
-
// 6.
|
|
497
|
+
// 6. Knowledge import (for standalone machines)
|
|
498
|
+
console.log();
|
|
499
|
+
const wantImport = await ask(` Import knowledge from another machine? [y/N]: `);
|
|
500
|
+
if (wantImport.trim().toLowerCase() === "y") {
|
|
501
|
+
const filePath = await ask(` Path to knowledge export (.tar.gz): `);
|
|
502
|
+
const resolved = filePath.trim().replace(/^~/, HOME);
|
|
503
|
+
if (resolved && fs.existsSync(resolved)) {
|
|
504
|
+
const targetWorkspace = path.join(HOME, ".sinain/workspace");
|
|
505
|
+
fs.mkdirSync(targetWorkspace, { recursive: true });
|
|
506
|
+
try {
|
|
507
|
+
execSync(`tar xzf "${resolved}" -C "${targetWorkspace}"`, { stdio: "inherit" });
|
|
508
|
+
// Symlink sinain-memory scripts from npm package
|
|
509
|
+
const srcMemory = path.join(PKG_DIR, "sinain-memory");
|
|
510
|
+
const dstMemory = path.join(targetWorkspace, "sinain-memory");
|
|
511
|
+
try { fs.rmSync(dstMemory, { recursive: true }); } catch {}
|
|
512
|
+
fs.symlinkSync(srcMemory, dstMemory);
|
|
513
|
+
vars.SINAIN_WORKSPACE = targetWorkspace;
|
|
514
|
+
vars.OPENCLAW_WORKSPACE_DIR = targetWorkspace;
|
|
515
|
+
ok(`Knowledge imported to ${targetWorkspace}`);
|
|
516
|
+
} catch (e) {
|
|
517
|
+
warn(`Import failed: ${e.message}`);
|
|
518
|
+
}
|
|
519
|
+
} else if (resolved) {
|
|
520
|
+
warn(`File not found: ${resolved}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// 7. Agent-specific defaults
|
|
567
525
|
vars.SINAIN_POLL_INTERVAL = "5";
|
|
568
526
|
vars.SINAIN_HEARTBEAT_INTERVAL = "900";
|
|
569
527
|
vars.PRIVACY_MODE = "standard";
|
package/package.json
CHANGED
package/sense_client/__main__.py
CHANGED
|
@@ -12,7 +12,6 @@ if sys.platform == "win32":
|
|
|
12
12
|
|
|
13
13
|
import argparse
|
|
14
14
|
import concurrent.futures
|
|
15
|
-
import copy
|
|
16
15
|
import json
|
|
17
16
|
import os
|
|
18
17
|
import time
|
|
@@ -29,7 +28,7 @@ from .capture import ScreenCapture, create_capture
|
|
|
29
28
|
from .change_detector import ChangeDetector
|
|
30
29
|
from .roi_extractor import ROIExtractor
|
|
31
30
|
from .ocr import OCRResult, create_ocr
|
|
32
|
-
from .gate import DecisionGate,
|
|
31
|
+
from .gate import DecisionGate, SenseObservation
|
|
33
32
|
from .sender import SenseSender, package_full_frame, package_roi
|
|
34
33
|
from .app_detector import AppDetector
|
|
35
34
|
from .config import load_config
|
|
@@ -129,7 +128,6 @@ def main():
|
|
|
129
128
|
)
|
|
130
129
|
app_detector = AppDetector()
|
|
131
130
|
ocr_pool = concurrent.futures.ThreadPoolExecutor(max_workers=4)
|
|
132
|
-
vision_pool = concurrent.futures.ThreadPoolExecutor(max_workers=1, thread_name_prefix="vision")
|
|
133
131
|
|
|
134
132
|
# Vision provider — routes to Ollama (local) or OpenRouter (cloud) based on config/privacy
|
|
135
133
|
vision_cfg = config.get("vision", {})
|
|
@@ -357,28 +355,18 @@ def main():
|
|
|
357
355
|
title=title, subtitle=subtitle, facts=facts,
|
|
358
356
|
)
|
|
359
357
|
|
|
360
|
-
# Vision scene analysis
|
|
358
|
+
# Vision scene analysis (throttled, non-blocking on failure)
|
|
361
359
|
if vision_provider and time.time() - last_vision_time >= vision_throttle_s:
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if scene:
|
|
373
|
-
log(f"vision: {scene[:80]}...")
|
|
374
|
-
ctx_ev = SenseEvent(type="context", ts=ts)
|
|
375
|
-
ctx_ev.observation = SenseObservation(scene=scene)
|
|
376
|
-
ctx_ev.meta = meta
|
|
377
|
-
ctx_ev.roi = package_full_frame(frame)
|
|
378
|
-
sender.send(ctx_ev)
|
|
379
|
-
except Exception as e:
|
|
380
|
-
log(f"vision error: {e}")
|
|
381
|
-
vision_pool.submit(_do_vision, _v_frame, _v_meta, _v_ts, _v_prompt)
|
|
360
|
+
try:
|
|
361
|
+
from PIL import Image as PILImage
|
|
362
|
+
pil_frame = PILImage.fromarray(use_frame) if isinstance(use_frame, np.ndarray) else use_frame
|
|
363
|
+
scene = vision_provider.describe(pil_frame, prompt=vision_prompt or None)
|
|
364
|
+
if scene:
|
|
365
|
+
event.observation.scene = scene
|
|
366
|
+
last_vision_time = time.time()
|
|
367
|
+
log(f"vision: {scene[:80]}...")
|
|
368
|
+
except Exception as e:
|
|
369
|
+
log(f"vision error: {e}")
|
|
382
370
|
|
|
383
371
|
# Send small thumbnail for ALL event types (agent uses vision)
|
|
384
372
|
# Privacy matrix: gate image sending based on PRIVACY_IMAGES_OPENROUTER
|
|
@@ -4,7 +4,6 @@ numpy>=1.24
|
|
|
4
4
|
pytesseract>=0.3
|
|
5
5
|
requests>=2.31
|
|
6
6
|
pyobjc-framework-Quartz>=10.0; sys_platform == "darwin"
|
|
7
|
-
pyobjc-framework-Vision>=10.0; sys_platform == "darwin"
|
|
8
7
|
mss>=9.0; sys_platform == "win32"
|
|
9
8
|
psutil>=5.9; sys_platform == "win32"
|
|
10
9
|
winrt-Windows.Media.Ocr>=2.0; sys_platform == "win32"
|
package/setup-overlay.js
CHANGED
|
@@ -37,36 +37,25 @@ function ok(msg) { console.log(`${BOLD}[setup-overlay]${RESET} ${GREEN}✓${RE
|
|
|
37
37
|
function warn(msg) { console.log(`${BOLD}[setup-overlay]${RESET} ${YELLOW}⚠${RESET} ${msg}`); }
|
|
38
38
|
function fail(msg) { console.error(`${BOLD}[setup-overlay]${RESET} ${RED}✗${RESET} ${msg}`); process.exit(1); }
|
|
39
39
|
|
|
40
|
-
// ──
|
|
40
|
+
// ── Parse flags ──────────────────────────────────────────────────────────────
|
|
41
41
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
42
|
+
const args = process.argv.slice(2);
|
|
43
|
+
const fromSource = args.includes("--from-source");
|
|
44
|
+
const forceUpdate = args.includes("--update");
|
|
46
45
|
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (fromSource) {
|
|
53
|
-
await buildFromSource();
|
|
54
|
-
} else {
|
|
55
|
-
await downloadOverlay({ forceUpdate });
|
|
56
|
-
}
|
|
46
|
+
if (fromSource) {
|
|
47
|
+
await buildFromSource();
|
|
48
|
+
} else {
|
|
49
|
+
await downloadPrebuilt();
|
|
57
50
|
}
|
|
58
51
|
|
|
59
52
|
// ── Download pre-built .app ──────────────────────────────────────────────────
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
const _log = silent ? () => {} : log;
|
|
63
|
-
const _ok = silent ? () => {} : ok;
|
|
64
|
-
const _warn = silent ? () => {} : warn;
|
|
65
|
-
|
|
54
|
+
async function downloadPrebuilt() {
|
|
66
55
|
fs.mkdirSync(APP_DIR, { recursive: true });
|
|
67
56
|
|
|
68
57
|
// Find latest overlay release
|
|
69
|
-
|
|
58
|
+
log("Checking for latest overlay release...");
|
|
70
59
|
let release;
|
|
71
60
|
try {
|
|
72
61
|
const res = await fetch(`${RELEASES_API}?per_page=20`, {
|
|
@@ -78,7 +67,6 @@ export async function downloadOverlay({ silent = false, forceUpdate = false } =
|
|
|
78
67
|
release = releases.find(r => r.tag_name?.startsWith("overlay-v"));
|
|
79
68
|
if (!release) throw new Error("No overlay release found");
|
|
80
69
|
} catch (e) {
|
|
81
|
-
if (silent) return false;
|
|
82
70
|
fail(`Failed to fetch releases: ${e.message}\n Try: sinain setup-overlay --from-source`);
|
|
83
71
|
}
|
|
84
72
|
|
|
@@ -90,22 +78,21 @@ export async function downloadOverlay({ silent = false, forceUpdate = false } =
|
|
|
90
78
|
try {
|
|
91
79
|
const local = JSON.parse(fs.readFileSync(VERSION_FILE, "utf-8"));
|
|
92
80
|
if (local.tag === tag) {
|
|
93
|
-
|
|
94
|
-
return
|
|
81
|
+
ok(`Overlay already up-to-date (${version})`);
|
|
82
|
+
return;
|
|
95
83
|
}
|
|
96
|
-
|
|
84
|
+
log(`Updating: ${local.tag} → ${tag}`);
|
|
97
85
|
} catch { /* corrupt version file — re-download */ }
|
|
98
86
|
}
|
|
99
87
|
|
|
100
88
|
// Find the .zip asset for this platform
|
|
101
89
|
const zipAsset = release.assets?.find(a => a.name === ASSET_NAME);
|
|
102
90
|
if (!zipAsset) {
|
|
103
|
-
if (silent) return false;
|
|
104
91
|
fail(`Release ${tag} has no ${ASSET_NAME} asset.\n Try: sinain setup-overlay --from-source`);
|
|
105
92
|
}
|
|
106
93
|
|
|
107
94
|
// Download with progress
|
|
108
|
-
|
|
95
|
+
log(`Downloading overlay ${version} for ${IS_WINDOWS ? "Windows" : "macOS"} (${formatBytes(zipAsset.size)})...`);
|
|
109
96
|
const zipPath = path.join(APP_DIR, ASSET_NAME);
|
|
110
97
|
|
|
111
98
|
try {
|
|
@@ -125,18 +112,17 @@ export async function downloadOverlay({ silent = false, forceUpdate = false } =
|
|
|
125
112
|
if (done) break;
|
|
126
113
|
chunks.push(value);
|
|
127
114
|
downloaded += value.length;
|
|
128
|
-
if (total > 0
|
|
115
|
+
if (total > 0) {
|
|
129
116
|
const pct = Math.round((downloaded / total) * 100);
|
|
130
117
|
process.stdout.write(`\r${BOLD}[setup-overlay]${RESET} ${DIM}${pct}% (${formatBytes(downloaded)} / ${formatBytes(total)})${RESET}`);
|
|
131
118
|
}
|
|
132
119
|
}
|
|
133
|
-
|
|
120
|
+
process.stdout.write("\n");
|
|
134
121
|
|
|
135
122
|
const buffer = Buffer.concat(chunks);
|
|
136
123
|
fs.writeFileSync(zipPath, buffer);
|
|
137
|
-
|
|
124
|
+
ok(`Downloaded ${formatBytes(buffer.length)}`);
|
|
138
125
|
} catch (e) {
|
|
139
|
-
if (silent) return false;
|
|
140
126
|
fail(`Download failed: ${e.message}`);
|
|
141
127
|
}
|
|
142
128
|
|
|
@@ -146,7 +132,7 @@ export async function downloadOverlay({ silent = false, forceUpdate = false } =
|
|
|
146
132
|
}
|
|
147
133
|
|
|
148
134
|
// Extract
|
|
149
|
-
|
|
135
|
+
log("Extracting...");
|
|
150
136
|
if (IS_WINDOWS) {
|
|
151
137
|
try {
|
|
152
138
|
execSync(
|
|
@@ -154,7 +140,6 @@ export async function downloadOverlay({ silent = false, forceUpdate = false } =
|
|
|
154
140
|
{ stdio: "pipe" }
|
|
155
141
|
);
|
|
156
142
|
} catch (e) {
|
|
157
|
-
if (silent) return false;
|
|
158
143
|
fail(`Extraction failed: ${e.message}`);
|
|
159
144
|
}
|
|
160
145
|
} else {
|
|
@@ -165,7 +150,6 @@ export async function downloadOverlay({ silent = false, forceUpdate = false } =
|
|
|
165
150
|
try {
|
|
166
151
|
execSync(`unzip -o -q "${zipPath}" -d "${APP_DIR}"`, { stdio: "pipe" });
|
|
167
152
|
} catch (e) {
|
|
168
|
-
if (silent) return false;
|
|
169
153
|
fail(`Extraction failed: ${e.message}`);
|
|
170
154
|
}
|
|
171
155
|
}
|
|
@@ -186,15 +170,12 @@ export async function downloadOverlay({ silent = false, forceUpdate = false } =
|
|
|
186
170
|
// Clean up zip
|
|
187
171
|
fs.unlinkSync(zipPath);
|
|
188
172
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.log(`
|
|
173
|
+
ok(`Overlay ${version} installed`);
|
|
174
|
+
console.log(`
|
|
192
175
|
${GREEN}✓${RESET} Overlay ready!
|
|
193
176
|
Location: ${APP_PATH}
|
|
194
177
|
The overlay will auto-start with: ${BOLD}sinain start${RESET}
|
|
195
178
|
`);
|
|
196
|
-
}
|
|
197
|
-
return true;
|
|
198
179
|
}
|
|
199
180
|
|
|
200
181
|
// ── Build from source (legacy) ───────────────────────────────────────────────
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
|
-
import fs from "node:fs";
|
|
3
2
|
import type { FeedBuffer } from "../buffers/feed-buffer.js";
|
|
4
3
|
import type { SenseBuffer } from "../buffers/sense-buffer.js";
|
|
5
|
-
import type { AgentConfig, AgentEntry, ContextWindow, EscalationMode, ContextRichness, RecorderStatus
|
|
4
|
+
import type { AgentConfig, AgentEntry, ContextWindow, EscalationMode, ContextRichness, RecorderStatus } from "../types.js";
|
|
6
5
|
import type { Profiler } from "../profiler.js";
|
|
7
|
-
import { buildContextWindow
|
|
6
|
+
import { buildContextWindow } from "./context-window.js";
|
|
8
7
|
import { analyzeContext } from "./analyzer.js";
|
|
9
8
|
import { writeSituationMd } from "./situation-writer.js";
|
|
10
9
|
import { calculateEscalationScore } from "../escalation/scorer.js";
|
|
@@ -36,10 +35,6 @@ export interface AgentLoopDeps {
|
|
|
36
35
|
traitEngine?: TraitEngine;
|
|
37
36
|
/** Directory to write per-day trait log JSONL files. */
|
|
38
37
|
traitLogDir?: string;
|
|
39
|
-
/** Optional: path to sinain-knowledge.md for startup recap. */
|
|
40
|
-
getKnowledgeDocPath?: () => string | null;
|
|
41
|
-
/** Optional: feedback store for startup recap context. */
|
|
42
|
-
feedbackStore?: { queryRecent(n: number): FeedbackRecord[] };
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
export interface TraceContext {
|
|
@@ -74,7 +69,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
74
69
|
private lastRunTs = 0;
|
|
75
70
|
private running = false;
|
|
76
71
|
private started = false;
|
|
77
|
-
private firstTick = true;
|
|
78
72
|
|
|
79
73
|
private lastPushedHud = "";
|
|
80
74
|
private agentNextId = 1;
|
|
@@ -118,9 +112,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
118
112
|
}, this.deps.agentConfig.maxIntervalMs);
|
|
119
113
|
|
|
120
114
|
log(TAG, `loop started (debounce=${this.deps.agentConfig.debounceMs}ms, max=${this.deps.agentConfig.maxIntervalMs}ms, cooldown=${this.deps.agentConfig.cooldownMs}ms, model=${this.deps.agentConfig.model})`);
|
|
121
|
-
|
|
122
|
-
// Fire recap tick: immediate HUD from persistent knowledge (no sense data needed)
|
|
123
|
-
this.fireRecapTick().catch(e => debug(TAG, "recap skipped:", String(e)));
|
|
124
115
|
}
|
|
125
116
|
|
|
126
117
|
/** Stop the agent loop. */
|
|
@@ -140,13 +131,12 @@ export class AgentLoop extends EventEmitter {
|
|
|
140
131
|
onNewContext(): void {
|
|
141
132
|
if (!this.started) return;
|
|
142
133
|
|
|
143
|
-
//
|
|
144
|
-
const delay = this.firstTick ? 500 : this.deps.agentConfig.debounceMs;
|
|
134
|
+
// Debounce: wait N ms after last event before running
|
|
145
135
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
146
136
|
this.debounceTimer = setTimeout(() => {
|
|
147
137
|
this.debounceTimer = null;
|
|
148
138
|
this.run().catch(err => error(TAG, "debounce tick error:", err.message));
|
|
149
|
-
},
|
|
139
|
+
}, this.deps.agentConfig.debounceMs);
|
|
150
140
|
}
|
|
151
141
|
|
|
152
142
|
/** Get agent results history (newest first). */
|
|
@@ -408,80 +398,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
408
398
|
traceCtx?.finish({ totalLatencyMs: Date.now() - Date.now(), llmLatencyMs: 0, llmInputTokens: 0, llmOutputTokens: 0, llmCost: 0, escalated: false, escalationScore: 0, contextScreenEvents: 0, contextAudioEntries: 0, contextRichness: richness, digestLength: 0, hudChanged: false });
|
|
409
399
|
} finally {
|
|
410
400
|
this.running = false;
|
|
411
|
-
this.firstTick = false;
|
|
412
401
|
this.lastRunTs = Date.now();
|
|
413
402
|
}
|
|
414
403
|
}
|
|
415
|
-
|
|
416
|
-
// ── Private: startup recap tick from persistent knowledge ──
|
|
417
|
-
|
|
418
|
-
private async fireRecapTick(): Promise<void> {
|
|
419
|
-
if (this.running) return;
|
|
420
|
-
this.running = true;
|
|
421
|
-
|
|
422
|
-
try {
|
|
423
|
-
const sections: string[] = [];
|
|
424
|
-
const startTs = Date.now();
|
|
425
|
-
|
|
426
|
-
// 1. sinain-knowledge.md (established patterns, user preferences)
|
|
427
|
-
const knowledgePath = this.deps.getKnowledgeDocPath?.();
|
|
428
|
-
if (knowledgePath) {
|
|
429
|
-
const content = await fs.promises.readFile(knowledgePath, "utf-8").catch(() => "");
|
|
430
|
-
if (content.length > 50) sections.push(content.slice(0, 2000));
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// 2. SITUATION.md digest (if fresh — less than 5 minutes old)
|
|
434
|
-
try {
|
|
435
|
-
const stat = await fs.promises.stat(this.deps.situationMdPath);
|
|
436
|
-
if (Date.now() - stat.mtimeMs < 5 * 60_000) {
|
|
437
|
-
const sit = await fs.promises.readFile(this.deps.situationMdPath, "utf-8");
|
|
438
|
-
const digestMatch = sit.match(/## Digest\n([\s\S]*?)(?=\n##|$)/);
|
|
439
|
-
if (digestMatch?.[1]?.trim()) {
|
|
440
|
-
sections.push(`Last session digest:\n${digestMatch[1].trim()}`);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
} catch { /* SITUATION.md missing — fine */ }
|
|
444
|
-
|
|
445
|
-
// 3. Recent feedback records (last 5 escalation summaries)
|
|
446
|
-
const records = this.deps.feedbackStore?.queryRecent(5) ?? [];
|
|
447
|
-
if (records.length > 0) {
|
|
448
|
-
const recaps = records.slice(0, 5).map(r => `- ${r.currentApp}: ${r.hud}`).join("\n");
|
|
449
|
-
sections.push(`Recent activity:\n${recaps}`);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (sections.length === 0) { return; }
|
|
453
|
-
|
|
454
|
-
const recapContext = sections.join("\n\n");
|
|
455
|
-
|
|
456
|
-
// Build synthetic ContextWindow with knowledge as screen entry
|
|
457
|
-
const recapWindow: ContextWindow = {
|
|
458
|
-
audio: [],
|
|
459
|
-
screen: [{
|
|
460
|
-
ts: Date.now(),
|
|
461
|
-
ocr: recapContext,
|
|
462
|
-
meta: { app: "sinain-recap", windowTitle: "startup" },
|
|
463
|
-
type: "context",
|
|
464
|
-
} as unknown as SenseEvent],
|
|
465
|
-
images: [],
|
|
466
|
-
currentApp: "sinain-recap",
|
|
467
|
-
appHistory: [],
|
|
468
|
-
audioCount: 0,
|
|
469
|
-
screenCount: 1,
|
|
470
|
-
windowMs: 0,
|
|
471
|
-
newestEventTs: Date.now(),
|
|
472
|
-
preset: RICHNESS_PRESETS.lean,
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
const result = await analyzeContext(recapWindow, this.deps.agentConfig, null);
|
|
476
|
-
if (result?.hud && result.hud !== "—" && result.hud !== "Idle") {
|
|
477
|
-
this.deps.onHudUpdate(result.hud);
|
|
478
|
-
log(TAG, `recap tick (${Date.now() - startTs}ms, ${result.tokensIn}in+${result.tokensOut}out tok) hud="${result.hud}"`);
|
|
479
|
-
}
|
|
480
|
-
} catch (err: any) {
|
|
481
|
-
debug(TAG, "recap tick error:", err.message || err);
|
|
482
|
-
} finally {
|
|
483
|
-
this.running = false;
|
|
484
|
-
// Do NOT update lastRunTs — normal cooldown should not be affected by recap
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
404
|
}
|
package/sinain-core/src/index.ts
CHANGED
|
@@ -25,12 +25,6 @@ import { initPrivacy, levelFor, applyLevel } from "./privacy/index.js";
|
|
|
25
25
|
|
|
26
26
|
const TAG = "core";
|
|
27
27
|
|
|
28
|
-
/** Resolve workspace path, expanding leading ~ to HOME. */
|
|
29
|
-
function resolveWorkspace(): string {
|
|
30
|
-
const raw = process.env.SINAIN_WORKSPACE || `${process.env.HOME}/.openclaw/workspace`;
|
|
31
|
-
return raw.startsWith("~") ? raw.replace("~", process.env.HOME || "") : raw;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
28
|
async function main() {
|
|
35
29
|
log(TAG, "sinain-core starting...");
|
|
36
30
|
|
|
@@ -86,7 +80,7 @@ async function main() {
|
|
|
86
80
|
profiler,
|
|
87
81
|
feedbackStore: feedbackStore ?? undefined,
|
|
88
82
|
queryKnowledgeFacts: async (entities: string[], maxFacts: number) => {
|
|
89
|
-
const workspace =
|
|
83
|
+
const workspace = process.env.SINAIN_WORKSPACE || `${process.env.HOME}/.openclaw/workspace`;
|
|
90
84
|
const dbPath = `${workspace}/memory/knowledge-graph.db`;
|
|
91
85
|
const scriptPath = `${workspace}/sinain-memory/graph_query.py`;
|
|
92
86
|
try {
|
|
@@ -162,13 +156,6 @@ async function main() {
|
|
|
162
156
|
} : undefined,
|
|
163
157
|
traitEngine,
|
|
164
158
|
traitLogDir: config.traitConfig.logDir,
|
|
165
|
-
getKnowledgeDocPath: () => {
|
|
166
|
-
const workspace = resolveWorkspace();
|
|
167
|
-
const p = `${workspace}/memory/sinain-knowledge.md`;
|
|
168
|
-
try { if (existsSync(p)) return p; } catch {}
|
|
169
|
-
return null;
|
|
170
|
-
},
|
|
171
|
-
feedbackStore: feedbackStore ?? undefined,
|
|
172
159
|
});
|
|
173
160
|
|
|
174
161
|
// ── Wire learning signal collector (needs agentLoop) ──
|
|
@@ -413,13 +400,13 @@ async function main() {
|
|
|
413
400
|
|
|
414
401
|
// Knowledge graph integration
|
|
415
402
|
getKnowledgeDocPath: () => {
|
|
416
|
-
const workspace =
|
|
403
|
+
const workspace = process.env.SINAIN_WORKSPACE || `${process.env.HOME}/.openclaw/workspace`;
|
|
417
404
|
const p = `${workspace}/memory/sinain-knowledge.md`;
|
|
418
405
|
try { if (existsSync(p)) return p; } catch {}
|
|
419
406
|
return null;
|
|
420
407
|
},
|
|
421
408
|
queryKnowledgeFacts: async (entities: string[], maxFacts: number) => {
|
|
422
|
-
const workspace =
|
|
409
|
+
const workspace = process.env.SINAIN_WORKSPACE || `${process.env.HOME}/.openclaw/workspace`;
|
|
423
410
|
const dbPath = `${workspace}/memory/knowledge-graph.db`;
|
|
424
411
|
const scriptPath = `${workspace}/sinain-memory/graph_query.py`;
|
|
425
412
|
try {
|