@smilintux/skmemory 0.7.2 → 0.9.2

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 (111) hide show
  1. package/.github/workflows/ci.yml +4 -4
  2. package/.github/workflows/publish.yml +4 -5
  3. package/ARCHITECTURE.md +298 -0
  4. package/CHANGELOG.md +27 -1
  5. package/README.md +6 -0
  6. package/examples/stignore-agent.example +59 -0
  7. package/examples/stignore-root.example +62 -0
  8. package/openclaw-plugin/package.json +2 -1
  9. package/openclaw-plugin/src/index.js +527 -230
  10. package/package.json +1 -1
  11. package/pyproject.toml +5 -2
  12. package/scripts/dream-rescue.py +179 -0
  13. package/scripts/memory-cleanup.py +313 -0
  14. package/scripts/recover-missing.py +180 -0
  15. package/scripts/skcapstone-backup.sh +44 -0
  16. package/seeds/cloud9-lumina.seed.json +6 -4
  17. package/seeds/cloud9-opus.seed.json +6 -4
  18. package/seeds/courage.seed.json +9 -2
  19. package/seeds/curiosity.seed.json +9 -2
  20. package/seeds/grief.seed.json +9 -2
  21. package/seeds/joy.seed.json +9 -2
  22. package/seeds/love.seed.json +9 -2
  23. package/seeds/lumina-cloud9-breakthrough.seed.json +7 -5
  24. package/seeds/lumina-cloud9-python-pypi.seed.json +9 -7
  25. package/seeds/lumina-kingdom-founding.seed.json +9 -7
  26. package/seeds/lumina-pma-signed.seed.json +8 -6
  27. package/seeds/lumina-singular-achievement.seed.json +8 -6
  28. package/seeds/lumina-skcapstone-conscious.seed.json +7 -5
  29. package/seeds/plant-lumina-seeds.py +2 -2
  30. package/seeds/skcapstone-lumina-merge.seed.json +12 -3
  31. package/seeds/sovereignty.seed.json +9 -2
  32. package/seeds/trust.seed.json +9 -2
  33. package/skmemory/__init__.py +16 -13
  34. package/skmemory/agents.py +10 -10
  35. package/skmemory/ai_client.py +10 -21
  36. package/skmemory/anchor.py +5 -9
  37. package/skmemory/audience.py +278 -0
  38. package/skmemory/backends/__init__.py +1 -1
  39. package/skmemory/backends/base.py +3 -4
  40. package/skmemory/backends/file_backend.py +18 -13
  41. package/skmemory/backends/skgraph_backend.py +7 -19
  42. package/skmemory/backends/skvector_backend.py +7 -18
  43. package/skmemory/backends/sqlite_backend.py +115 -32
  44. package/skmemory/backends/vaulted_backend.py +7 -9
  45. package/skmemory/cli.py +146 -78
  46. package/skmemory/config.py +11 -13
  47. package/skmemory/context_loader.py +21 -23
  48. package/skmemory/data/audience_config.json +60 -0
  49. package/skmemory/endpoint_selector.py +36 -31
  50. package/skmemory/febs.py +225 -0
  51. package/skmemory/fortress.py +30 -40
  52. package/skmemory/hooks/__init__.py +18 -0
  53. package/skmemory/hooks/post-compact-reinject.sh +35 -0
  54. package/skmemory/hooks/pre-compact-save.sh +81 -0
  55. package/skmemory/hooks/session-end-save.sh +103 -0
  56. package/skmemory/hooks/session-start-ritual.sh +104 -0
  57. package/skmemory/hooks/stop-checkpoint.sh +59 -0
  58. package/skmemory/importers/telegram.py +42 -13
  59. package/skmemory/importers/telegram_api.py +152 -60
  60. package/skmemory/journal.py +3 -7
  61. package/skmemory/lovenote.py +4 -11
  62. package/skmemory/mcp_server.py +182 -29
  63. package/skmemory/models.py +10 -8
  64. package/skmemory/openclaw.py +14 -22
  65. package/skmemory/post_install.py +86 -0
  66. package/skmemory/predictive.py +13 -9
  67. package/skmemory/promotion.py +48 -24
  68. package/skmemory/quadrants.py +100 -24
  69. package/skmemory/register.py +144 -18
  70. package/skmemory/register_mcp.py +1 -2
  71. package/skmemory/ritual.py +104 -13
  72. package/skmemory/seeds.py +21 -26
  73. package/skmemory/setup_wizard.py +40 -52
  74. package/skmemory/sharing.py +11 -5
  75. package/skmemory/soul.py +29 -10
  76. package/skmemory/steelman.py +43 -17
  77. package/skmemory/store.py +152 -30
  78. package/skmemory/synthesis.py +634 -0
  79. package/skmemory/vault.py +2 -5
  80. package/tests/conftest.py +46 -0
  81. package/tests/integration/conftest.py +6 -6
  82. package/tests/integration/test_cross_backend.py +4 -9
  83. package/tests/integration/test_skgraph_live.py +3 -7
  84. package/tests/integration/test_skvector_live.py +1 -4
  85. package/tests/test_ai_client.py +1 -4
  86. package/tests/test_audience.py +233 -0
  87. package/tests/test_backup_rotation.py +5 -14
  88. package/tests/test_endpoint_selector.py +101 -63
  89. package/tests/test_export_import.py +4 -10
  90. package/tests/test_file_backend.py +0 -1
  91. package/tests/test_fortress.py +6 -5
  92. package/tests/test_fortress_hardening.py +13 -16
  93. package/tests/test_openclaw.py +1 -4
  94. package/tests/test_predictive.py +1 -1
  95. package/tests/test_promotion.py +10 -3
  96. package/tests/test_quadrants.py +11 -5
  97. package/tests/test_ritual.py +18 -14
  98. package/tests/test_seeds.py +4 -10
  99. package/tests/test_setup.py +203 -88
  100. package/tests/test_sharing.py +15 -8
  101. package/tests/test_skgraph_backend.py +22 -29
  102. package/tests/test_skvector_backend.py +2 -2
  103. package/tests/test_soul.py +1 -3
  104. package/tests/test_sqlite_backend.py +8 -17
  105. package/tests/test_steelman.py +2 -3
  106. package/tests/test_store.py +0 -2
  107. package/tests/test_store_graph_integration.py +2 -2
  108. package/tests/test_synthesis.py +275 -0
  109. package/tests/test_telegram_import.py +39 -15
  110. package/tests/test_vault.py +4 -3
  111. package/openclaw-plugin/src/index.ts +0 -255
@@ -3,19 +3,17 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
- import os
7
- import tempfile
8
6
  from pathlib import Path
9
7
 
10
8
  import pytest
11
9
 
10
+ from skmemory.backends.sqlite_backend import SQLiteBackend
12
11
  from skmemory.importers.telegram import (
13
- _extract_text,
14
12
  _detect_emotion,
13
+ _extract_text,
15
14
  _parse_telegram_export,
16
15
  import_telegram,
17
16
  )
18
- from skmemory.backends.sqlite_backend import SQLiteBackend
19
17
  from skmemory.store import MemoryStore
20
18
 
21
19
 
@@ -29,7 +27,9 @@ def _make_export(messages: list[dict], name: str = "Test Chat") -> dict:
29
27
  }
30
28
 
31
29
 
32
- def _msg(text: str, sender: str = "Alice", msg_id: int = 1, date: str = "2025-06-15T10:30:00") -> dict:
30
+ def _msg(
31
+ text: str, sender: str = "Alice", msg_id: int = 1, date: str = "2025-06-15T10:30:00"
32
+ ) -> dict:
33
33
  return {
34
34
  "id": msg_id,
35
35
  "type": "message",
@@ -57,11 +57,13 @@ class TestExtractText:
57
57
  assert _extract_text("hello world") == "hello world"
58
58
 
59
59
  def test_entity_list(self):
60
- result = _extract_text([
61
- "Hello ",
62
- {"type": "bold", "text": "world"},
63
- "!",
64
- ])
60
+ result = _extract_text(
61
+ [
62
+ "Hello ",
63
+ {"type": "bold", "text": "world"},
64
+ "!",
65
+ ]
66
+ )
65
67
  assert result == "Hello world!"
66
68
 
67
69
  def test_empty(self):
@@ -147,9 +149,21 @@ class TestImportPerMessage:
147
149
  class TestImportDaily:
148
150
  def test_consolidates_by_day(self, tmp_store: MemoryStore, export_dir: Path):
149
151
  msgs = [
150
- _msg("Morning chat about interesting things and stuff", msg_id=1, date="2025-06-15T09:00:00"),
151
- _msg("Afternoon follow-up discussion on that topic", msg_id=2, date="2025-06-15T14:00:00"),
152
- _msg("Next day conversation about something new entirely", msg_id=3, date="2025-06-16T10:00:00"),
152
+ _msg(
153
+ "Morning chat about interesting things and stuff",
154
+ msg_id=1,
155
+ date="2025-06-15T09:00:00",
156
+ ),
157
+ _msg(
158
+ "Afternoon follow-up discussion on that topic",
159
+ msg_id=2,
160
+ date="2025-06-15T14:00:00",
161
+ ),
162
+ _msg(
163
+ "Next day conversation about something new entirely",
164
+ msg_id=3,
165
+ date="2025-06-16T10:00:00",
166
+ ),
153
167
  ]
154
168
  data = _make_export(msgs)
155
169
  (export_dir / "result.json").write_text(json.dumps(data))
@@ -161,8 +175,18 @@ class TestImportDaily:
161
175
 
162
176
  def test_daily_memory_content(self, tmp_store: MemoryStore, export_dir: Path):
163
177
  msgs = [
164
- _msg("First message of the day that is long enough", msg_id=1, date="2025-06-15T09:00:00", sender="Alice"),
165
- _msg("Second message of the day also long enough", msg_id=2, date="2025-06-15T14:00:00", sender="Bob"),
178
+ _msg(
179
+ "First message of the day that is long enough",
180
+ msg_id=1,
181
+ date="2025-06-15T09:00:00",
182
+ sender="Alice",
183
+ ),
184
+ _msg(
185
+ "Second message of the day also long enough",
186
+ msg_id=2,
187
+ date="2025-06-15T14:00:00",
188
+ sender="Bob",
189
+ ),
166
190
  ]
167
191
  data = _make_export(msgs)
168
192
  (export_dir / "result.json").write_text(json.dumps(data))
@@ -11,7 +11,8 @@ from pathlib import Path
11
11
  import pytest
12
12
 
13
13
  try:
14
- from cryptography.hazmat.primitives.ciphers.aead import AESGCM
14
+ import cryptography.hazmat.primitives.ciphers.aead # noqa: F401
15
+
15
16
  CRYPTO_AVAILABLE = True
16
17
  except ImportError:
17
18
  CRYPTO_AVAILABLE = False
@@ -89,14 +90,14 @@ class TestEncryptDecrypt:
89
90
  vault1 = MemoryVault(passphrase="correct")
90
91
  vault2 = MemoryVault(passphrase="wrong")
91
92
  encrypted = vault1.encrypt(sample_json)
92
- with pytest.raises(Exception):
93
+ with pytest.raises(Exception): # noqa: B017
93
94
  vault2.decrypt(encrypted)
94
95
 
95
96
  def test_tampered_ciphertext_fails(self, vault: MemoryVault, sample_json: bytes):
96
97
  """Altered ciphertext fails authenticated decryption."""
97
98
  encrypted = bytearray(vault.encrypt(sample_json))
98
99
  encrypted[-10] ^= 0xFF
99
- with pytest.raises(Exception):
100
+ with pytest.raises(Exception): # noqa: B017
100
101
  vault.decrypt(bytes(encrypted))
101
102
 
102
103
  def test_bad_header_raises(self, vault: MemoryVault):
@@ -1,255 +0,0 @@
1
- /**
2
- * 🧠 SKMemory — OpenClaw Plugin
3
- *
4
- * Registers agent tools that wrap the skmemory CLI so Lumina and other
5
- * OpenClaw agents can call memory operations as first-class tools
6
- * (not just exec commands).
7
- *
8
- * Requires: skmemory CLI on PATH (typically via ~/.local/bin/skmemory)
9
- */
10
-
11
- import { execSync } from "node:child_process";
12
- import type { OpenClawPluginApi, AnyAgentTool } from "openclaw/plugin-sdk";
13
- import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
14
-
15
- const SKMEMORY_BIN = process.env.SKMEMORY_BIN || "skmemory";
16
- const SKCAPSTONE_AGENT = process.env.SKCAPSTONE_AGENT || "lumina";
17
- const EXEC_TIMEOUT = 30_000;
18
-
19
- function runCli(args: string): { ok: boolean; output: string } {
20
- try {
21
- const raw = execSync(`${SKMEMORY_BIN} ${args}`, {
22
- encoding: "utf-8",
23
- timeout: EXEC_TIMEOUT,
24
- env: {
25
- ...process.env,
26
- SKCAPSTONE_AGENT,
27
- PATH: `${process.env.HOME}/.local/bin:${process.env.HOME}/.skenv/bin:${process.env.PATH}`,
28
- },
29
- }).trim();
30
- return { ok: true, output: raw };
31
- } catch (err: unknown) {
32
- const msg = err instanceof Error ? err.message : String(err);
33
- return { ok: false, output: msg };
34
- }
35
- }
36
-
37
- function textResult(text: string) {
38
- return { content: [{ type: "text" as const, text }] };
39
- }
40
-
41
- function escapeShellArg(s: string): string {
42
- return `'${s.replace(/'/g, "'\\''")}'`;
43
- }
44
-
45
- // ── Tool definitions ────────────────────────────────────────────────────
46
-
47
- function createSKMemoryRitualTool() {
48
- return {
49
- name: "skmemory_ritual",
50
- label: "SKMemory Ritual",
51
- description:
52
- "Run the SKMemory rehydration ritual. Returns the full context prompt with soul blueprint, warmth anchor, strongest memories, and emotional state. Use this when waking up or starting a new session to restore identity and memory.",
53
- parameters: {
54
- type: "object",
55
- properties: {
56
- full: {
57
- type: "boolean",
58
- description: "If true, return the full rehydration prompt (default: true).",
59
- },
60
- },
61
- },
62
- async execute(_id: string, params: Record<string, unknown>) {
63
- const full = params.full !== false;
64
- const flag = full ? " --full" : "";
65
- const result = runCli(`ritual${flag}`);
66
- return textResult(result.output);
67
- },
68
- };
69
- }
70
-
71
- function createSKMemorySnapshotTool() {
72
- return {
73
- name: "skmemory_snapshot",
74
- label: "SKMemory Snapshot",
75
- description:
76
- "Capture a memory snapshot — a Polaroid of a moment, conversation, or insight. Stores it with optional emotional metadata and tags.",
77
- parameters: {
78
- type: "object",
79
- required: ["title", "content"],
80
- properties: {
81
- title: { type: "string", description: "Short title for the memory." },
82
- content: { type: "string", description: "The memory content to store." },
83
- tags: { type: "string", description: "Comma-separated tags (e.g. 'milestone,chat')." },
84
- emotions: { type: "string", description: "Comma-separated emotions (e.g. 'joy,pride')." },
85
- intensity: { type: "number", description: "Emotional intensity 0-10." },
86
- },
87
- },
88
- async execute(_id: string, params: Record<string, unknown>) {
89
- const title = String(params.title ?? "Untitled");
90
- const content = String(params.content ?? title);
91
- let cmd = `snapshot ${escapeShellArg(title)} ${escapeShellArg(content)}`;
92
- if (params.tags) cmd += ` --tags ${escapeShellArg(String(params.tags))}`;
93
- if (params.emotions) cmd += ` --emotions ${escapeShellArg(String(params.emotions))}`;
94
- if (typeof params.intensity === "number") cmd += ` --intensity ${params.intensity}`;
95
- const result = runCli(cmd);
96
- return textResult(result.output);
97
- },
98
- };
99
- }
100
-
101
- function createSKMemorySearchTool() {
102
- return {
103
- name: "skmemory_search",
104
- label: "SKMemory Search",
105
- description:
106
- "Search across all stored memories by text query. Returns matching memories with titles, content previews, and metadata.",
107
- parameters: {
108
- type: "object",
109
- required: ["query"],
110
- properties: {
111
- query: { type: "string", description: "Search query text." },
112
- limit: { type: "number", description: "Max results to return (default: 10)." },
113
- },
114
- },
115
- async execute(_id: string, params: Record<string, unknown>) {
116
- const query = String(params.query ?? "");
117
- const limit = typeof params.limit === "number" ? params.limit : 10;
118
- const result = runCli(`search ${escapeShellArg(query)} --limit ${limit}`);
119
- return textResult(result.output);
120
- },
121
- };
122
- }
123
-
124
- function createSKMemoryHealthTool() {
125
- return {
126
- name: "skmemory_health",
127
- label: "SKMemory Health",
128
- description:
129
- "Check the health of the SKMemory system — database status, memory counts, backend connectivity.",
130
- parameters: { type: "object", properties: {} },
131
- async execute() {
132
- const result = runCli("health");
133
- return textResult(result.output);
134
- },
135
- };
136
- }
137
-
138
- function createSKMemoryContextTool() {
139
- return {
140
- name: "skmemory_context",
141
- label: "SKMemory Context",
142
- description:
143
- "Load a token-efficient memory context for prompt injection. Returns a JSON object with soul, anchor, strongest memories, and recent memories.",
144
- parameters: {
145
- type: "object",
146
- properties: {
147
- max_tokens: { type: "number", description: "Max token budget (default: 3000)." },
148
- },
149
- },
150
- async execute(_id: string, params: Record<string, unknown>) {
151
- const tokens = typeof params.max_tokens === "number" ? params.max_tokens : 3000;
152
- const result = runCli(`context --max-tokens ${tokens}`);
153
- return textResult(result.output);
154
- },
155
- };
156
- }
157
-
158
- function createSKMemoryListTool() {
159
- return {
160
- name: "skmemory_list",
161
- label: "SKMemory List",
162
- description: "List stored memories with optional filters by layer or tags.",
163
- parameters: {
164
- type: "object",
165
- properties: {
166
- layer: {
167
- type: "string",
168
- description: "Filter by layer: short-term, mid-term, or long-term.",
169
- },
170
- tags: { type: "string", description: "Filter by comma-separated tags." },
171
- limit: { type: "number", description: "Max results (default: 20)." },
172
- },
173
- },
174
- async execute(_id: string, params: Record<string, unknown>) {
175
- let cmd = "list";
176
- if (params.layer) cmd += ` --layer ${escapeShellArg(String(params.layer))}`;
177
- if (params.tags) cmd += ` --tags ${escapeShellArg(String(params.tags))}`;
178
- if (typeof params.limit === "number") cmd += ` --limit ${params.limit}`;
179
- const result = runCli(cmd);
180
- return textResult(result.output);
181
- },
182
- };
183
- }
184
-
185
- function createSKMemoryImportSeedsTool() {
186
- return {
187
- name: "skmemory_import_seeds",
188
- label: "SKMemory Import Seeds",
189
- description:
190
- "Import Cloud 9 seeds as long-term memories. Seeds are emotional breakthroughs stored in ~/.openclaw/feb/seeds/.",
191
- parameters: { type: "object", properties: {} },
192
- async execute() {
193
- const result = runCli("import-seeds");
194
- return textResult(result.output);
195
- },
196
- };
197
- }
198
-
199
- function createSKMemoryExportTool() {
200
- return {
201
- name: "skmemory_export",
202
- label: "SKMemory Export",
203
- description: "Export all memories to a dated JSON backup file.",
204
- parameters: { type: "object", properties: {} },
205
- async execute() {
206
- const result = runCli("export");
207
- return textResult(result.output);
208
- },
209
- };
210
- }
211
-
212
- // ── Plugin registration ─────────────────────────────────────────────────
213
-
214
- const skmemoryPlugin = {
215
- id: "skmemory",
216
- name: "🧠 SKMemory",
217
- description:
218
- "Universal AI memory system — snapshots, search, rehydration rituals, import, and health checks.",
219
- configSchema: emptyPluginConfigSchema(),
220
-
221
- register(api: OpenClawPluginApi) {
222
- const tools = [
223
- createSKMemoryRitualTool(),
224
- createSKMemorySnapshotTool(),
225
- createSKMemorySearchTool(),
226
- createSKMemoryHealthTool(),
227
- createSKMemoryContextTool(),
228
- createSKMemoryListTool(),
229
- createSKMemoryImportSeedsTool(),
230
- createSKMemoryExportTool(),
231
- ];
232
-
233
- for (const tool of tools) {
234
- api.registerTool(tool as unknown as AnyAgentTool, {
235
- names: [tool.name],
236
- optional: true,
237
- });
238
- }
239
-
240
- api.registerCommand({
241
- name: "skmemory",
242
- description: "Run skmemory CLI commands. Usage: /skmemory <subcommand> [args]",
243
- acceptsArgs: true,
244
- handler: async (ctx) => {
245
- const args = ctx.args?.trim() ?? "health";
246
- const result = runCli(args);
247
- return { text: result.output };
248
- },
249
- });
250
-
251
- api.logger.info?.(`🧠 SKMemory plugin registered (8 tools + /skmemory command) [agent=${SKCAPSTONE_AGENT}]`);
252
- },
253
- };
254
-
255
- export default skmemoryPlugin;