@geravant/sinain 1.0.19 → 1.2.0

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.
Files changed (80) hide show
  1. package/README.md +10 -1
  2. package/cli.js +176 -0
  3. package/index.ts +4 -2
  4. package/install.js +89 -14
  5. package/launcher.js +622 -0
  6. package/openclaw.plugin.json +4 -0
  7. package/pack-prepare.js +48 -0
  8. package/package.json +24 -5
  9. package/sense_client/README.md +82 -0
  10. package/sense_client/__init__.py +1 -0
  11. package/sense_client/__main__.py +462 -0
  12. package/sense_client/app_detector.py +54 -0
  13. package/sense_client/app_detector_win.py +83 -0
  14. package/sense_client/capture.py +215 -0
  15. package/sense_client/capture_win.py +88 -0
  16. package/sense_client/change_detector.py +86 -0
  17. package/sense_client/config.py +64 -0
  18. package/sense_client/gate.py +145 -0
  19. package/sense_client/ocr.py +347 -0
  20. package/sense_client/privacy.py +65 -0
  21. package/sense_client/requirements.txt +13 -0
  22. package/sense_client/roi_extractor.py +84 -0
  23. package/sense_client/sender.py +173 -0
  24. package/sense_client/tests/__init__.py +0 -0
  25. package/sense_client/tests/test_stream1_optimizations.py +234 -0
  26. package/setup-overlay.js +82 -0
  27. package/sinain-agent/.env.example +17 -0
  28. package/sinain-agent/CLAUDE.md +87 -0
  29. package/sinain-agent/mcp-config.json +12 -0
  30. package/sinain-agent/run.sh +248 -0
  31. package/sinain-core/.env.example +93 -0
  32. package/sinain-core/package-lock.json +552 -0
  33. package/sinain-core/package.json +21 -0
  34. package/sinain-core/src/agent/analyzer.ts +366 -0
  35. package/sinain-core/src/agent/context-window.ts +172 -0
  36. package/sinain-core/src/agent/loop.ts +404 -0
  37. package/sinain-core/src/agent/situation-writer.ts +187 -0
  38. package/sinain-core/src/agent/traits.ts +520 -0
  39. package/sinain-core/src/audio/capture-spawner-macos.ts +44 -0
  40. package/sinain-core/src/audio/capture-spawner-win.ts +37 -0
  41. package/sinain-core/src/audio/capture-spawner.ts +14 -0
  42. package/sinain-core/src/audio/pipeline.ts +335 -0
  43. package/sinain-core/src/audio/transcription-local.ts +141 -0
  44. package/sinain-core/src/audio/transcription.ts +278 -0
  45. package/sinain-core/src/buffers/feed-buffer.ts +71 -0
  46. package/sinain-core/src/buffers/sense-buffer.ts +425 -0
  47. package/sinain-core/src/config.ts +245 -0
  48. package/sinain-core/src/escalation/escalation-slot.ts +136 -0
  49. package/sinain-core/src/escalation/escalator.ts +828 -0
  50. package/sinain-core/src/escalation/message-builder.ts +370 -0
  51. package/sinain-core/src/escalation/openclaw-ws.ts +726 -0
  52. package/sinain-core/src/escalation/scorer.ts +166 -0
  53. package/sinain-core/src/index.ts +537 -0
  54. package/sinain-core/src/learning/feedback-store.ts +253 -0
  55. package/sinain-core/src/learning/signal-collector.ts +218 -0
  56. package/sinain-core/src/log.ts +24 -0
  57. package/sinain-core/src/overlay/commands.ts +126 -0
  58. package/sinain-core/src/overlay/ws-handler.ts +267 -0
  59. package/sinain-core/src/privacy/index.ts +18 -0
  60. package/sinain-core/src/privacy/presets.ts +40 -0
  61. package/sinain-core/src/privacy/redact.ts +92 -0
  62. package/sinain-core/src/profiler.ts +181 -0
  63. package/sinain-core/src/recorder.ts +186 -0
  64. package/sinain-core/src/server.ts +456 -0
  65. package/sinain-core/src/trace/trace-store.ts +73 -0
  66. package/sinain-core/src/trace/tracer.ts +94 -0
  67. package/sinain-core/src/types.ts +427 -0
  68. package/sinain-core/src/util/dedup.ts +48 -0
  69. package/sinain-core/src/util/task-store.ts +84 -0
  70. package/sinain-core/tsconfig.json +18 -0
  71. package/sinain-knowledge/curation/engine.ts +137 -24
  72. package/sinain-knowledge/data/git-store.ts +26 -0
  73. package/sinain-knowledge/data/store.ts +117 -0
  74. package/sinain-mcp-server/index.ts +417 -0
  75. package/sinain-mcp-server/package.json +19 -0
  76. package/sinain-mcp-server/tsconfig.json +15 -0
  77. package/sinain-memory/graph_query.py +185 -0
  78. package/sinain-memory/knowledge_integrator.py +450 -0
  79. package/sinain-memory/memory-config.json +3 -1
  80. package/sinain-memory/session_distiller.py +162 -0
package/README.md CHANGED
@@ -1,4 +1,13 @@
1
- # sinain-hud OpenClaw Plugin
1
+ # @geravant/sinain
2
+
3
+ This package serves two roles:
4
+
5
+ 1. **Standalone launcher** — run `npx @geravant/sinain start` to launch the full sinain stack (core, sense, overlay, agent) on your Mac. See the [main README](../README.md#quick-start) for usage.
6
+ 2. **OpenClaw plugin** — when installed on an OpenClaw gateway server (`npx @geravant/sinain install`), it manages the sinain-hud agent lifecycle.
7
+
8
+ ---
9
+
10
+ ## OpenClaw Plugin
2
11
 
3
12
  Plugin for the [anthillnet fork of OpenClaw](https://github.com/anthillnet/openclaw) that manages the sinain-hud agent lifecycle on the server.
4
13
 
package/cli.js ADDED
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from "child_process";
3
+ import net from "net";
4
+ import os from "os";
5
+ import fs from "fs";
6
+ import path from "path";
7
+
8
+ const cmd = process.argv[2];
9
+
10
+ switch (cmd) {
11
+ case "start":
12
+ await import("./launcher.js");
13
+ break;
14
+
15
+ case "stop":
16
+ await stopServices();
17
+ break;
18
+
19
+ case "status":
20
+ await showStatus();
21
+ break;
22
+
23
+ case "setup-overlay":
24
+ await import("./setup-overlay.js");
25
+ break;
26
+
27
+ case "install":
28
+ // --if-openclaw: only run if OpenClaw is installed (for postinstall)
29
+ if (process.argv.includes("--if-openclaw")) {
30
+ const ocJson = path.join(os.homedir(), ".openclaw/openclaw.json");
31
+ if (!fs.existsSync(ocJson)) {
32
+ console.log(" OpenClaw not detected — skipping plugin install");
33
+ process.exit(0);
34
+ }
35
+ }
36
+ await import("./install.js");
37
+ break;
38
+
39
+ default:
40
+ printUsage();
41
+ break;
42
+ }
43
+
44
+ // ── Stop ──────────────────────────────────────────────────────────────────────
45
+
46
+ async function stopServices() {
47
+ let killed = false;
48
+
49
+ const patterns = [
50
+ "tsx.*src/index.ts",
51
+ "tsx watch src/index.ts",
52
+ "python3 -m sense_client",
53
+ "Python -m sense_client",
54
+ "flutter run -d macos",
55
+ "sinain_hud.app/Contents/MacOS/sinain_hud",
56
+ "sinain-agent/run.sh",
57
+ ];
58
+
59
+ for (const pat of patterns) {
60
+ try {
61
+ execSync(`pkill -f "${pat}"`, { stdio: "pipe" });
62
+ killed = true;
63
+ } catch { /* not running */ }
64
+ }
65
+
66
+ // Free port 9500
67
+ try {
68
+ const pid = execSync("lsof -i :9500 -sTCP:LISTEN -t", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
69
+ if (pid) {
70
+ execSync(`kill ${pid}`, { stdio: "pipe" });
71
+ killed = true;
72
+ }
73
+ } catch { /* port already free */ }
74
+
75
+ // Clean PID file
76
+ const pidFile = "/tmp/sinain-pids.txt";
77
+ if (fs.existsSync(pidFile)) {
78
+ fs.unlinkSync(pidFile);
79
+ }
80
+
81
+ if (killed) {
82
+ console.log("sinain services stopped.");
83
+ } else {
84
+ console.log("No sinain services were running.");
85
+ }
86
+ }
87
+
88
+ // ── Status ────────────────────────────────────────────────────────────────────
89
+
90
+ async function showStatus() {
91
+ const CYAN = "\x1b[36m";
92
+ const YELLOW = "\x1b[33m";
93
+ const MAGENTA = "\x1b[35m";
94
+ const GREEN = "\x1b[32m";
95
+ const RED = "\x1b[31m";
96
+ const BOLD = "\x1b[1m";
97
+ const DIM = "\x1b[2m";
98
+ const RESET = "\x1b[0m";
99
+
100
+ console.log(`\n${BOLD}── SinainHUD Status ────────────────────${RESET}`);
101
+
102
+ // Core: check port 9500
103
+ const coreUp = await isPortOpen(9500);
104
+ if (coreUp) {
105
+ console.log(` ${CYAN}core${RESET} :9500 ${GREEN}✓${RESET} running`);
106
+ } else {
107
+ console.log(` ${CYAN}core${RESET} :9500 ${RED}✗${RESET} stopped`);
108
+ }
109
+
110
+ // Sense: check pgrep
111
+ const senseUp = isProcessRunning("python3 -m sense_client") || isProcessRunning("Python -m sense_client");
112
+ if (senseUp) {
113
+ console.log(` ${YELLOW}sense${RESET} ${GREEN}✓${RESET} running`);
114
+ } else {
115
+ console.log(` ${YELLOW}sense${RESET} ${DIM}— stopped${RESET}`);
116
+ }
117
+
118
+ // Overlay
119
+ const overlayUp = isProcessRunning("sinain_hud.app") || isProcessRunning("flutter run -d macos");
120
+ if (overlayUp) {
121
+ console.log(` ${MAGENTA}overlay${RESET} ${GREEN}✓${RESET} running`);
122
+ } else {
123
+ console.log(` ${MAGENTA}overlay${RESET} ${DIM}— stopped${RESET}`);
124
+ }
125
+
126
+ // Agent
127
+ const agentUp = isProcessRunning("sinain-agent/run.sh");
128
+ if (agentUp) {
129
+ console.log(` ${GREEN}agent${RESET} ${GREEN}✓${RESET} running`);
130
+ } else {
131
+ console.log(` ${GREEN}agent${RESET} ${DIM}— stopped${RESET}`);
132
+ }
133
+
134
+ console.log(`${BOLD}────────────────────────────────────────${RESET}\n`);
135
+ }
136
+
137
+ function isPortOpen(port) {
138
+ return new Promise((resolve) => {
139
+ const sock = new net.Socket();
140
+ sock.setTimeout(500);
141
+ sock.on("connect", () => { sock.destroy(); resolve(true); });
142
+ sock.on("error", () => resolve(false));
143
+ sock.on("timeout", () => { sock.destroy(); resolve(false); });
144
+ sock.connect(port, "127.0.0.1");
145
+ });
146
+ }
147
+
148
+ function isProcessRunning(pattern) {
149
+ try {
150
+ execSync(`pgrep -f "${pattern}"`, { stdio: "pipe" });
151
+ return true;
152
+ } catch {
153
+ return false;
154
+ }
155
+ }
156
+
157
+ // ── Usage ─────────────────────────────────────────────────────────────────────
158
+
159
+ function printUsage() {
160
+ console.log(`
161
+ sinain — AI overlay system for macOS
162
+
163
+ Usage:
164
+ sinain start [options] Launch sinain services
165
+ sinain stop Stop all sinain services
166
+ sinain status Check what's running
167
+ sinain setup-overlay Clone and build the overlay app
168
+ sinain install Install OpenClaw plugin (server-side)
169
+
170
+ Start options:
171
+ --no-sense Skip screen capture (sense_client)
172
+ --no-overlay Skip Flutter overlay
173
+ --no-agent Skip agent poll loop
174
+ --agent=<name> Agent to use: claude, codex, goose, aider (default: claude)
175
+ `);
176
+ }
package/index.ts CHANGED
@@ -560,8 +560,10 @@ export default function sinainHudPlugin(api: OpenClawPluginApi): void {
560
560
  `sinain-hud: session summary written (${toolCount} tools, ${Math.round(durationMs / 1000)}s)`,
561
561
  );
562
562
 
563
- // Fire-and-forget: ingest session summary into triple store
564
- if (state.workspaceDir) {
563
+ // Fire-and-forget: ingest session summary into knowledge graph
564
+ // NOTE: Main knowledge integration happens in heartbeat tick (session_distiller + knowledge_integrator).
565
+ // This is a lightweight best-effort path for when agent_end fires (won't fire on kill).
566
+ if (false && state.workspaceDir) {
565
567
  api.runtime.system.runCommandWithTimeout(
566
568
  ["uv", "run", "--with", "requests", "python3",
567
569
  "sinain-memory/triple_ingest.py",
package/install.js CHANGED
@@ -3,6 +3,7 @@ import fs from "fs";
3
3
  import path from "path";
4
4
  import os from "os";
5
5
  import { execSync } from "child_process";
6
+ import { randomBytes } from "crypto";
6
7
 
7
8
  const HOME = os.homedir();
8
9
  const PLUGIN_DIR = path.join(HOME, ".openclaw/extensions/sinain-hud");
@@ -18,7 +19,9 @@ const SKILL = path.join(PKG_DIR, "SKILL.md");
18
19
 
19
20
  console.log("\nInstalling sinain plugin...");
20
21
 
21
- // 1. Stage plugin files to local ~/.openclaw (used by both paths)
22
+ // 1. Copy plugin files (remove stale extensions/sinain dir if present from old installs)
23
+ const stalePluginDir = path.join(HOME, ".openclaw/extensions/sinain");
24
+ if (fs.existsSync(stalePluginDir)) fs.rmSync(stalePluginDir, { recursive: true, force: true });
22
25
  fs.mkdirSync(PLUGIN_DIR, { recursive: true });
23
26
  fs.copyFileSync(path.join(PKG_DIR, "index.ts"), path.join(PLUGIN_DIR, "index.ts"));
24
27
  fs.copyFileSync(path.join(PKG_DIR, "openclaw.plugin.json"), path.join(PLUGIN_DIR, "openclaw.plugin.json"));
@@ -115,13 +118,15 @@ async function installNemoClaw({ sandboxName }) {
115
118
  sessionKey: "agent:main:sinain"
116
119
  }
117
120
  };
121
+ // Remove stale "sinain" entry if present from a previous install
122
+ delete cfg.plugins.entries["sinain"];
118
123
  if (!cfg.plugins.allow.includes("sinain-hud")) cfg.plugins.allow.push("sinain-hud");
119
124
  cfg.agents ??= {};
120
125
  cfg.agents.defaults ??= {};
121
126
  cfg.agents.defaults.sandbox ??= {};
122
127
  cfg.agents.defaults.sandbox.sessionToolsVisibility = "all";
123
128
  // NemoClaw: gateway bind/auth are managed by OpenShell — but compaction must be set explicitly
124
- cfg.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
129
+ cfg.agents.defaults.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
125
130
 
126
131
  const token = cfg.gateway?.auth?.token ?? "(see sandbox openclaw.json)";
127
132
 
@@ -145,6 +150,19 @@ async function installNemoClaw({ sandboxName }) {
145
150
  }
146
151
  }
147
152
 
153
+ // Knowledge snapshot repo (optional) — inside sandbox
154
+ const snapshotUrl = process.env.SINAIN_SNAPSHOT_REPO;
155
+ if (snapshotUrl) {
156
+ try {
157
+ await checkRepoPrivacy(snapshotUrl);
158
+ run(`ssh -T openshell-${sandboxName} 'mkdir -p ~/.sinain/knowledge-snapshots && cd ~/.sinain/knowledge-snapshots && ([ -d .git ] || (git init && git config user.name sinain-knowledge && git config user.email sinain@local)) && (git remote get-url origin >/dev/null 2>&1 && git remote set-url origin "${snapshotUrl}" || git remote add origin "${snapshotUrl}") && (git fetch origin && git checkout -B main origin/main) 2>/dev/null || true'`);
159
+ console.log(" ✓ Snapshot repo configured in sandbox");
160
+ } catch (e) {
161
+ console.error("\n ✗ Snapshot repo setup aborted:", e.message, "\n");
162
+ process.exit(1);
163
+ }
164
+ }
165
+
148
166
  // Restart openclaw gateway inside sandbox (kill existing PID + start fresh)
149
167
  try {
150
168
  // Find the gateway PID, kill it, then start a new instance detached
@@ -201,19 +219,32 @@ async function installLocal() {
201
219
  sessionKey: "agent:main:sinain"
202
220
  }
203
221
  };
222
+ // Remove stale "sinain" entry if present from a previous install
223
+ delete cfg.plugins.entries["sinain"];
224
+ cfg.plugins.allow ??= [];
225
+ if (!cfg.plugins.allow.includes("sinain-hud")) cfg.plugins.allow.push("sinain-hud");
204
226
  cfg.agents ??= {};
205
227
  cfg.agents.defaults ??= {};
206
228
  cfg.agents.defaults.sandbox ??= {};
207
229
  cfg.agents.defaults.sandbox.sessionToolsVisibility = "all";
208
- cfg.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
230
+ cfg.agents.defaults.compaction = { mode: "safeguard", maxHistoryShare: 0.2, reserveTokensFloor: 40000 };
209
231
  cfg.gateway ??= {};
210
232
  cfg.gateway.bind = "lan"; // allow remote Mac to connect
211
233
 
234
+ // Propagate snapshot repo path to plugin config (used for periodic git-backed knowledge backups)
235
+ if (process.env.SINAIN_SNAPSHOT_REPO) {
236
+ cfg.plugins.entries["sinain-hud"].config.snapshotRepoPath = process.env.SINAIN_SNAPSHOT_REPO;
237
+ }
238
+
212
239
  fs.mkdirSync(path.dirname(OC_JSON), { recursive: true });
213
240
  fs.writeFileSync(OC_JSON, JSON.stringify(cfg, null, 2));
214
241
  console.log(" ✓ openclaw.json patched");
215
242
 
216
243
  // Memory restore from backup repo
244
+ // Set SINAIN_BACKUP_REPO to a private GitHub repo URL to seed the workspace with
245
+ // existing memory (session summaries, playbook, triplestore, identity files).
246
+ // Expected repo structure: memory/, playbook.md, identity/, triplestore.jsonl, SITUATION.md
247
+ // The repo MUST be private — the installer verifies this via GitHub API.
217
248
  const backupUrl = process.env.SINAIN_BACKUP_REPO;
218
249
  if (backupUrl) {
219
250
  try {
@@ -232,25 +263,69 @@ async function installLocal() {
232
263
  }
233
264
  }
234
265
 
235
- // Reload gateway
266
+ // Knowledge snapshot repo (optional)
267
+ await setupSnapshotRepo();
268
+
269
+ // Start / restart gateway
236
270
  try {
237
- execSync("openclaw reload", { stdio: "pipe" });
238
- console.log(" ✓ Gateway reloaded");
271
+ execSync("openclaw gateway restart --background", { stdio: "pipe" });
272
+ console.log(" ✓ Gateway restarted");
239
273
  } catch {
240
274
  try {
241
- execSync("openclaw stop && sleep 1 && openclaw start --background", { stdio: "pipe" });
242
- console.log(" ✓ Gateway restarted");
275
+ execSync("openclaw gateway start --background", { stdio: "pipe" });
276
+ console.log(" ✓ Gateway started");
243
277
  } catch {
244
278
  console.warn(" ⚠ Could not start gateway — run: openclaw gateway");
245
279
  }
246
280
  }
247
281
 
248
- console.log(`
249
- sinain installed successfully.
250
- Plugin config: ~/.openclaw/openclaw.json
251
- Auth token: check your Brev dashboard 'Gateway Token'
252
- Then run ./setup-nemoclaw.sh on your Mac.
253
- `);
282
+ console.log("\n✓ sinain installed successfully.");
283
+ console.log(" Plugin config: ~/.openclaw/openclaw.json");
284
+ console.log(` Auth token: ${authToken}`);
285
+ console.log(" Next: run 'openclaw gateway' in a new terminal, then run ./setup-nemoclaw.sh on your Mac.\n");
286
+ }
287
+
288
+ // ── Knowledge snapshot repo setup (shared) ──────────────────────────────────
289
+
290
+ const SNAPSHOT_DIR = path.join(HOME, ".sinain", "knowledge-snapshots");
291
+
292
+ async function setupSnapshotRepo() {
293
+ const snapshotUrl = process.env.SINAIN_SNAPSHOT_REPO;
294
+ if (!snapshotUrl) return;
295
+
296
+ try {
297
+ await checkRepoPrivacy(snapshotUrl);
298
+ fs.mkdirSync(SNAPSHOT_DIR, { recursive: true });
299
+
300
+ if (!fs.existsSync(path.join(SNAPSHOT_DIR, ".git"))) {
301
+ execSync(`git init "${SNAPSHOT_DIR}"`, { stdio: "pipe" });
302
+ execSync(`git -C "${SNAPSHOT_DIR}" config user.name "sinain-knowledge"`, { stdio: "pipe" });
303
+ execSync(`git -C "${SNAPSHOT_DIR}" config user.email "sinain@local"`, { stdio: "pipe" });
304
+ }
305
+
306
+ // Set remote (add or update)
307
+ try {
308
+ run_capture(`git -C "${SNAPSHOT_DIR}" remote get-url origin`);
309
+ execSync(`git -C "${SNAPSHOT_DIR}" remote set-url origin "${snapshotUrl}"`, { stdio: "pipe" });
310
+ } catch {
311
+ execSync(`git -C "${SNAPSHOT_DIR}" remote add origin "${snapshotUrl}"`, { stdio: "pipe" });
312
+ }
313
+
314
+ // Pull existing snapshots if remote has content
315
+ try {
316
+ execSync(`git -C "${SNAPSHOT_DIR}" fetch origin`, { stdio: "pipe", timeout: 15_000 });
317
+ execSync(`git -C "${SNAPSHOT_DIR}" checkout -B main origin/main`, { stdio: "pipe" });
318
+ console.log(" ✓ Snapshot repo restored from", snapshotUrl);
319
+ } catch {
320
+ console.log(" ✓ Snapshot repo configured (empty remote)");
321
+ }
322
+ } catch (e) {
323
+ if (e.message?.startsWith("SECURITY") || e.message?.startsWith("Refusing")) {
324
+ console.error("\n ✗ Snapshot repo setup aborted:", e.message, "\n");
325
+ process.exit(1);
326
+ }
327
+ console.warn(" ⚠ Snapshot repo setup failed:", e.message);
328
+ }
254
329
  }
255
330
 
256
331
  // ── Detection ────────────────────────────────────────────────────────────────