@geravant/sinain 1.6.6 → 1.6.8
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 +28 -1
- package/package.json +1 -1
- package/sense_client/__main__.py +12 -24
- 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
|
@@ -587,7 +587,34 @@ async function setupWizard(envPath) {
|
|
|
587
587
|
vars.OPENCLAW_HTTP_URL = "";
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
-
// 6.
|
|
590
|
+
// 6. Knowledge import (for standalone machines)
|
|
591
|
+
console.log();
|
|
592
|
+
const wantImport = await ask(` Import knowledge from another machine? [y/N]: `);
|
|
593
|
+
if (wantImport.trim().toLowerCase() === "y") {
|
|
594
|
+
const filePath = await ask(` Path to knowledge export (.tar.gz): `);
|
|
595
|
+
const resolved = filePath.trim().replace(/^~/, HOME);
|
|
596
|
+
if (resolved && fs.existsSync(resolved)) {
|
|
597
|
+
const targetWorkspace = path.join(HOME, ".sinain/workspace");
|
|
598
|
+
fs.mkdirSync(targetWorkspace, { recursive: true });
|
|
599
|
+
try {
|
|
600
|
+
execSync(`tar xzf "${resolved}" -C "${targetWorkspace}"`, { stdio: "inherit" });
|
|
601
|
+
// Symlink sinain-memory scripts from npm package
|
|
602
|
+
const srcMemory = path.join(PKG_DIR, "sinain-memory");
|
|
603
|
+
const dstMemory = path.join(targetWorkspace, "sinain-memory");
|
|
604
|
+
try { fs.rmSync(dstMemory, { recursive: true }); } catch {}
|
|
605
|
+
fs.symlinkSync(srcMemory, dstMemory);
|
|
606
|
+
vars.SINAIN_WORKSPACE = targetWorkspace;
|
|
607
|
+
vars.OPENCLAW_WORKSPACE_DIR = targetWorkspace;
|
|
608
|
+
ok(`Knowledge imported to ${targetWorkspace}`);
|
|
609
|
+
} catch (e) {
|
|
610
|
+
warn(`Import failed: ${e.message}`);
|
|
611
|
+
}
|
|
612
|
+
} else if (resolved) {
|
|
613
|
+
warn(`File not found: ${resolved}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 7. Agent-specific defaults
|
|
591
618
|
vars.SINAIN_POLL_INTERVAL = "5";
|
|
592
619
|
vars.SINAIN_HEARTBEAT_INTERVAL = "900";
|
|
593
620
|
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
|
|
@@ -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 {
|