clawvault 3.1.0 → 3.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 (289) hide show
  1. package/README.md +422 -141
  2. package/bin/clawvault.js +10 -2
  3. package/bin/command-registration.test.js +3 -1
  4. package/bin/command-runtime.js +9 -1
  5. package/bin/register-core-commands.js +23 -28
  6. package/bin/register-maintenance-commands.js +39 -3
  7. package/bin/register-query-commands.js +58 -29
  8. package/bin/register-tailscale-commands.js +106 -0
  9. package/bin/register-task-commands.js +18 -1
  10. package/bin/register-task-commands.test.js +16 -0
  11. package/bin/register-vault-operations-commands.js +29 -1
  12. package/bin/register-workgraph-commands.js +1368 -0
  13. package/dashboard/lib/graph-diff.js +104 -0
  14. package/dashboard/lib/graph-diff.test.js +75 -0
  15. package/dashboard/lib/vault-parser.js +556 -0
  16. package/dashboard/lib/vault-parser.test.js +254 -0
  17. package/dashboard/public/app.js +796 -0
  18. package/dashboard/public/index.html +52 -0
  19. package/dashboard/public/styles.css +221 -0
  20. package/dashboard/server.js +374 -0
  21. package/dist/{chunk-F2JEUD4J.js → chunk-23YDQ3QU.js} +6 -8
  22. package/dist/{chunk-C7OK5WKP.js → chunk-2JQ3O2YL.js} +4 -4
  23. package/dist/{chunk-VR5NE7PZ.js → chunk-2RAZ4ZFE.js} +1 -1
  24. package/dist/chunk-2ZDO52B4.js +52 -0
  25. package/dist/{chunk-ZZA73MFY.js → chunk-33DOSHTA.js} +176 -36
  26. package/dist/chunk-33VSQP4J.js +37 -0
  27. package/dist/chunk-4BQTQMJP.js +93 -0
  28. package/dist/{chunk-GUKMRGM7.js → chunk-4OXMU5S2.js} +1 -1
  29. package/dist/{chunk-62YTUT6J.js → chunk-4PY655YM.js} +15 -3
  30. package/dist/chunk-6FH3IULF.js +352 -0
  31. package/dist/{chunk-3NSBOUT3.js → chunk-77Q5CSPJ.js} +404 -80
  32. package/dist/{chunk-4VQTUVH7.js → chunk-7YZWHM36.js} +52 -26
  33. package/dist/chunk-BSJ6RIT7.js +447 -0
  34. package/dist/chunk-BUEW6IIK.js +364 -0
  35. package/dist/{chunk-LI4O6NVK.js → chunk-CLJTREDS.js} +74 -14
  36. package/dist/chunk-EK6S23ZB.js +469 -0
  37. package/dist/{chunk-LNJA2UGL.js → chunk-ESFLMDRB.js} +9 -86
  38. package/dist/{chunk-H34S76MB.js → chunk-ESVS6K2B.js} +6 -6
  39. package/dist/{chunk-WAZ3NLWL.js → chunk-F55HGNU4.js} +0 -47
  40. package/dist/{chunk-QK3UCXWL.js → chunk-FHFUXL6G.js} +2 -2
  41. package/dist/{chunk-H62BP7RI.js → chunk-GAOWA7GR.js} +212 -46
  42. package/dist/chunk-GGA32J2R.js +784 -0
  43. package/dist/chunk-GNJL4YGR.js +79 -0
  44. package/dist/chunk-IVRIKYFE.js +520 -0
  45. package/dist/chunk-MDIH26GC.js +183 -0
  46. package/dist/{chunk-LYHGEHXG.js → chunk-MFAWT5O5.js} +0 -1
  47. package/dist/chunk-MM6QGW3P.js +207 -0
  48. package/dist/{chunk-P5EPF6MB.js → chunk-MW5C6ZQA.js} +110 -13
  49. package/dist/chunk-NCKFNBHJ.js +257 -0
  50. package/dist/{chunk-QBLMXKF2.js → chunk-OIWVQYQF.js} +1 -1
  51. package/dist/{chunk-42MXU7A6.js → chunk-P62WHA27.js} +58 -47
  52. package/dist/chunk-PBACDKKP.js +66 -0
  53. package/dist/{chunk-VGLOTGAS.js → chunk-QSHD36LH.js} +2 -2
  54. package/dist/{chunk-OZ7RIXTO.js → chunk-QSRRMEYM.js} +2 -2
  55. package/dist/chunk-QVEERJSP.js +152 -0
  56. package/dist/{chunk-N2AXRYLC.js → chunk-QWQ3TIKS.js} +1 -1
  57. package/dist/{chunk-3DHXQHYG.js → chunk-R2MIW5G7.js} +1 -1
  58. package/dist/{chunk-SJSFRIYS.js → chunk-SLXOR3CC.js} +2 -2
  59. package/dist/chunk-SS4B7P7V.js +99 -0
  60. package/dist/{chunk-JY6FYXIT.js → chunk-STCQGCEQ.js} +6 -11
  61. package/dist/chunk-TIGW564L.js +628 -0
  62. package/dist/chunk-U4O6C46S.js +154 -0
  63. package/dist/{chunk-ITPEXLHA.js → chunk-URXDAUVH.js} +24 -5
  64. package/dist/chunk-VSL7KY3M.js +189 -0
  65. package/dist/{chunk-U55BGUAU.js → chunk-W4SPAEE7.js} +6 -6
  66. package/dist/chunk-WMGIIABP.js +15 -0
  67. package/dist/{chunk-33UGEQRT.js → chunk-X3SPPUFG.js} +151 -64
  68. package/dist/chunk-Y6VJKXGL.js +373 -0
  69. package/dist/{chunk-3WRJEKN4.js → chunk-ZN54U2OZ.js} +123 -10
  70. package/dist/cli/index.js +34 -24
  71. package/dist/commands/archive.js +3 -3
  72. package/dist/commands/backlog.js +3 -3
  73. package/dist/commands/blocked.js +3 -3
  74. package/dist/commands/canvas.d.ts +15 -0
  75. package/dist/commands/canvas.js +200 -0
  76. package/dist/commands/checkpoint.js +2 -2
  77. package/dist/commands/compat.js +2 -2
  78. package/dist/commands/context.js +8 -6
  79. package/dist/commands/doctor.d.ts +11 -7
  80. package/dist/commands/doctor.js +18 -16
  81. package/dist/commands/embed.js +5 -6
  82. package/dist/commands/entities.js +2 -2
  83. package/dist/commands/graph.js +4 -4
  84. package/dist/commands/inject.d.ts +1 -1
  85. package/dist/commands/inject.js +5 -6
  86. package/dist/commands/kanban.js +4 -4
  87. package/dist/commands/link.js +5 -5
  88. package/dist/commands/migrate-observations.js +4 -4
  89. package/dist/commands/observe.d.ts +0 -1
  90. package/dist/commands/observe.js +14 -13
  91. package/dist/commands/project.js +5 -5
  92. package/dist/commands/rebuild-embeddings.d.ts +21 -0
  93. package/dist/commands/rebuild-embeddings.js +91 -0
  94. package/dist/commands/rebuild.js +12 -11
  95. package/dist/commands/recover.js +3 -3
  96. package/dist/commands/reflect.js +6 -7
  97. package/dist/commands/repair-session.js +1 -1
  98. package/dist/commands/replay.js +14 -14
  99. package/dist/commands/session-recap.js +1 -1
  100. package/dist/commands/setup.d.ts +2 -89
  101. package/dist/commands/setup.js +3 -21
  102. package/dist/commands/shell-init.js +1 -1
  103. package/dist/commands/sleep.d.ts +1 -1
  104. package/dist/commands/sleep.js +20 -19
  105. package/dist/commands/status.d.ts +2 -0
  106. package/dist/commands/status.js +57 -35
  107. package/dist/commands/sync-bd.d.ts +10 -0
  108. package/dist/commands/sync-bd.js +10 -0
  109. package/dist/commands/tailscale.d.ts +52 -0
  110. package/dist/commands/tailscale.js +26 -0
  111. package/dist/commands/task.js +4 -4
  112. package/dist/commands/template.js +2 -2
  113. package/dist/commands/wake.d.ts +1 -1
  114. package/dist/commands/wake.js +11 -10
  115. package/dist/commands/workgraph.d.ts +124 -0
  116. package/dist/commands/workgraph.js +38 -0
  117. package/dist/index.d.ts +341 -191
  118. package/dist/index.js +446 -116
  119. package/dist/{inject-Bzi5E-By.d.ts → inject-DYUrDqQO.d.ts} +3 -3
  120. package/dist/ledger-B7g7jhqG.d.ts +44 -0
  121. package/dist/lib/auto-linker.js +2 -2
  122. package/dist/lib/canvas-layout.d.ts +115 -0
  123. package/dist/lib/canvas-layout.js +35 -0
  124. package/dist/lib/config.d.ts +27 -3
  125. package/dist/lib/config.js +4 -2
  126. package/dist/lib/entity-index.js +1 -1
  127. package/dist/lib/project-utils.js +4 -4
  128. package/dist/lib/session-repair.js +1 -1
  129. package/dist/lib/session-utils.js +1 -1
  130. package/dist/lib/tailscale.d.ts +225 -0
  131. package/dist/lib/tailscale.js +50 -0
  132. package/dist/lib/task-utils.js +3 -3
  133. package/dist/lib/template-engine.js +1 -1
  134. package/dist/lib/webdav.d.ts +109 -0
  135. package/dist/lib/webdav.js +35 -0
  136. package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
  137. package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
  138. package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
  139. package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
  140. package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
  141. package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
  142. package/dist/openclaw-plugin.d.ts +8 -0
  143. package/dist/openclaw-plugin.js +14 -0
  144. package/dist/registry-BR4326o0.d.ts +30 -0
  145. package/dist/store-CA-6sKCJ.d.ts +34 -0
  146. package/dist/thread-B9LhXNU0.d.ts +41 -0
  147. package/dist/transformers.node-A2ZRORSQ.js +46775 -0
  148. package/dist/{types-Y2_Um2Ls.d.ts → types-BbWJoC1c.d.ts} +1 -44
  149. package/dist/workgraph/index.d.ts +5 -0
  150. package/dist/workgraph/index.js +23 -0
  151. package/dist/workgraph/ledger.d.ts +2 -0
  152. package/dist/workgraph/ledger.js +25 -0
  153. package/dist/workgraph/registry.d.ts +2 -0
  154. package/dist/workgraph/registry.js +19 -0
  155. package/dist/workgraph/store.d.ts +2 -0
  156. package/dist/workgraph/store.js +25 -0
  157. package/dist/workgraph/thread.d.ts +2 -0
  158. package/dist/workgraph/thread.js +25 -0
  159. package/dist/workgraph/types.d.ts +54 -0
  160. package/dist/workgraph/types.js +7 -0
  161. package/hooks/clawvault/HOOK.md +113 -0
  162. package/hooks/clawvault/handler.js +1561 -0
  163. package/hooks/clawvault/handler.test.js +510 -0
  164. package/hooks/clawvault/openclaw.plugin.json +72 -0
  165. package/openclaw.plugin.json +65 -38
  166. package/package.json +25 -22
  167. package/dist/chunk-3RG5ZIWI.js +0 -10
  168. package/dist/chunk-3ZIH425O.js +0 -871
  169. package/dist/chunk-6U6MK36V.js +0 -205
  170. package/dist/chunk-CMB7UL7C.js +0 -327
  171. package/dist/chunk-D2H45LON.js +0 -1074
  172. package/dist/chunk-E7MFQB6D.js +0 -163
  173. package/dist/chunk-GQSLDZTS.js +0 -560
  174. package/dist/chunk-MFM6K7PU.js +0 -374
  175. package/dist/chunk-MXSSG3QU.js +0 -42
  176. package/dist/chunk-OCGVIN3L.js +0 -88
  177. package/dist/chunk-PAH27GSN.js +0 -108
  178. package/dist/chunk-YCUNCH2I.js +0 -78
  179. package/dist/cli/index.cjs +0 -8584
  180. package/dist/cli/index.d.cts +0 -5
  181. package/dist/commands/archive.cjs +0 -287
  182. package/dist/commands/archive.d.cts +0 -11
  183. package/dist/commands/backlog.cjs +0 -721
  184. package/dist/commands/backlog.d.cts +0 -53
  185. package/dist/commands/blocked.cjs +0 -204
  186. package/dist/commands/blocked.d.cts +0 -26
  187. package/dist/commands/checkpoint.cjs +0 -244
  188. package/dist/commands/checkpoint.d.cts +0 -41
  189. package/dist/commands/compat.cjs +0 -294
  190. package/dist/commands/compat.d.cts +0 -28
  191. package/dist/commands/context.cjs +0 -2990
  192. package/dist/commands/context.d.cts +0 -2
  193. package/dist/commands/doctor.cjs +0 -2986
  194. package/dist/commands/doctor.d.cts +0 -21
  195. package/dist/commands/embed.cjs +0 -232
  196. package/dist/commands/embed.d.cts +0 -17
  197. package/dist/commands/entities.cjs +0 -141
  198. package/dist/commands/entities.d.cts +0 -7
  199. package/dist/commands/graph.cjs +0 -501
  200. package/dist/commands/graph.d.cts +0 -21
  201. package/dist/commands/inject.cjs +0 -1636
  202. package/dist/commands/inject.d.cts +0 -2
  203. package/dist/commands/kanban.cjs +0 -884
  204. package/dist/commands/kanban.d.cts +0 -63
  205. package/dist/commands/link.cjs +0 -965
  206. package/dist/commands/link.d.cts +0 -11
  207. package/dist/commands/migrate-observations.cjs +0 -362
  208. package/dist/commands/migrate-observations.d.cts +0 -19
  209. package/dist/commands/observe.cjs +0 -4099
  210. package/dist/commands/observe.d.cts +0 -23
  211. package/dist/commands/project.cjs +0 -1341
  212. package/dist/commands/project.d.cts +0 -85
  213. package/dist/commands/rebuild.cjs +0 -3136
  214. package/dist/commands/rebuild.d.cts +0 -11
  215. package/dist/commands/recover.cjs +0 -361
  216. package/dist/commands/recover.d.cts +0 -38
  217. package/dist/commands/reflect.cjs +0 -1008
  218. package/dist/commands/reflect.d.cts +0 -11
  219. package/dist/commands/repair-session.cjs +0 -457
  220. package/dist/commands/repair-session.d.cts +0 -38
  221. package/dist/commands/replay.cjs +0 -4103
  222. package/dist/commands/replay.d.cts +0 -16
  223. package/dist/commands/session-recap.cjs +0 -353
  224. package/dist/commands/session-recap.d.cts +0 -27
  225. package/dist/commands/setup.cjs +0 -1278
  226. package/dist/commands/setup.d.cts +0 -99
  227. package/dist/commands/shell-init.cjs +0 -75
  228. package/dist/commands/shell-init.d.cts +0 -7
  229. package/dist/commands/sleep.cjs +0 -6029
  230. package/dist/commands/sleep.d.cts +0 -36
  231. package/dist/commands/status.cjs +0 -2737
  232. package/dist/commands/status.d.cts +0 -52
  233. package/dist/commands/task.cjs +0 -1236
  234. package/dist/commands/task.d.cts +0 -97
  235. package/dist/commands/template.cjs +0 -457
  236. package/dist/commands/template.d.cts +0 -36
  237. package/dist/commands/wake.cjs +0 -2627
  238. package/dist/commands/wake.d.cts +0 -22
  239. package/dist/context-BUGaWpyL.d.cts +0 -46
  240. package/dist/index.cjs +0 -12373
  241. package/dist/index.d.cts +0 -854
  242. package/dist/inject-Bzi5E-By.d.cts +0 -137
  243. package/dist/lib/auto-linker.cjs +0 -176
  244. package/dist/lib/auto-linker.d.cts +0 -26
  245. package/dist/lib/config.cjs +0 -78
  246. package/dist/lib/config.d.cts +0 -11
  247. package/dist/lib/entity-index.cjs +0 -84
  248. package/dist/lib/entity-index.d.cts +0 -26
  249. package/dist/lib/project-utils.cjs +0 -864
  250. package/dist/lib/project-utils.d.cts +0 -97
  251. package/dist/lib/session-repair.cjs +0 -239
  252. package/dist/lib/session-repair.d.cts +0 -110
  253. package/dist/lib/session-utils.cjs +0 -209
  254. package/dist/lib/session-utils.d.cts +0 -63
  255. package/dist/lib/task-utils.cjs +0 -1137
  256. package/dist/lib/task-utils.d.cts +0 -208
  257. package/dist/lib/template-engine.cjs +0 -47
  258. package/dist/lib/template-engine.d.cts +0 -11
  259. package/dist/plugin/index.cjs +0 -1907
  260. package/dist/plugin/index.d.cts +0 -36
  261. package/dist/plugin/index.d.ts +0 -36
  262. package/dist/plugin/index.js +0 -572
  263. package/dist/plugin/inject.cjs +0 -356
  264. package/dist/plugin/inject.d.cts +0 -54
  265. package/dist/plugin/inject.d.ts +0 -54
  266. package/dist/plugin/inject.js +0 -17
  267. package/dist/plugin/observe.cjs +0 -631
  268. package/dist/plugin/observe.d.cts +0 -39
  269. package/dist/plugin/observe.d.ts +0 -39
  270. package/dist/plugin/observe.js +0 -18
  271. package/dist/plugin/templates.cjs +0 -593
  272. package/dist/plugin/templates.d.cts +0 -52
  273. package/dist/plugin/templates.d.ts +0 -52
  274. package/dist/plugin/templates.js +0 -25
  275. package/dist/plugin/types.cjs +0 -18
  276. package/dist/plugin/types.d.cts +0 -209
  277. package/dist/plugin/types.d.ts +0 -209
  278. package/dist/plugin/types.js +0 -0
  279. package/dist/plugin/vault.cjs +0 -927
  280. package/dist/plugin/vault.d.cts +0 -68
  281. package/dist/plugin/vault.d.ts +0 -68
  282. package/dist/plugin/vault.js +0 -22
  283. package/dist/types-Y2_Um2Ls.d.cts +0 -205
  284. package/templates/memory-event.md +0 -67
  285. package/templates/party.md +0 -63
  286. package/templates/primitive-registry.yaml +0 -551
  287. package/templates/run.md +0 -68
  288. package/templates/trigger.md +0 -68
  289. package/templates/workspace.md +0 -50
@@ -0,0 +1,510 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+
6
+ const { execFileSyncMock } = vi.hoisted(() => ({
7
+ execFileSyncMock: vi.fn()
8
+ }));
9
+
10
+ vi.mock('child_process', () => ({
11
+ execFileSync: execFileSyncMock
12
+ }));
13
+
14
+ function makeVaultFixture() {
15
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'clawvault-hook-'));
16
+ fs.writeFileSync(path.join(root, '.clawvault.json'), JSON.stringify({ name: 'test' }), 'utf-8');
17
+ return root;
18
+ }
19
+
20
+ function makeOpenClawSessionFixture(agentId, sessionId, transcriptBytes = 0) {
21
+ const stateRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'clawvault-openclaw-'));
22
+ const sessionsDir = path.join(stateRoot, 'agents', agentId, 'sessions');
23
+ fs.mkdirSync(sessionsDir, { recursive: true });
24
+ fs.writeFileSync(
25
+ path.join(sessionsDir, 'sessions.json'),
26
+ JSON.stringify({
27
+ [`agent:${agentId}:main`]: {
28
+ sessionId,
29
+ updatedAt: Date.now()
30
+ }
31
+ }),
32
+ 'utf-8'
33
+ );
34
+ const transcriptPath = path.join(sessionsDir, `${sessionId}.jsonl`);
35
+ const payload = transcriptBytes > 0 ? 'x'.repeat(transcriptBytes) : '';
36
+ fs.writeFileSync(transcriptPath, payload, 'utf-8');
37
+ return { stateRoot, sessionsDir, transcriptPath };
38
+ }
39
+
40
+ async function loadHandler() {
41
+ vi.resetModules();
42
+ const mod = await import('./handler.js');
43
+ return mod.default;
44
+ }
45
+
46
+ afterEach(() => {
47
+ vi.clearAllMocks();
48
+ delete process.env.CLAWVAULT_PATH;
49
+ delete process.env.OPENCLAW_STATE_DIR;
50
+ delete process.env.OPENCLAW_HOME;
51
+ delete process.env.OPENCLAW_AGENT_ID;
52
+ });
53
+
54
+ describe('clawvault hook handler', () => {
55
+ it('injects recovery warning on gateway startup when death detected', async () => {
56
+ const vaultPath = makeVaultFixture();
57
+ process.env.CLAWVAULT_PATH = vaultPath;
58
+
59
+ execFileSyncMock.mockImplementation((_command, args) => {
60
+ if (args[0] === 'recover') {
61
+ return '⚠️ CONTEXT DEATH DETECTED\nWorking on: ship memory graph';
62
+ }
63
+ return '';
64
+ });
65
+
66
+ const handler = await loadHandler();
67
+ const event = {
68
+ type: 'gateway',
69
+ action: 'startup',
70
+ messages: [{ role: 'user', content: 'hello' }]
71
+ };
72
+
73
+ await handler(event);
74
+
75
+ expect(execFileSyncMock).toHaveBeenCalledWith(
76
+ 'clawvault',
77
+ expect.arrayContaining(['recover', '--clear', '-v', vaultPath]),
78
+ expect.objectContaining({ shell: false })
79
+ );
80
+ const injected = event.messages.find((message) => message.role === 'system');
81
+ expect(injected?.content).toContain('Context death detected');
82
+ expect(injected?.content).toContain('ship memory graph');
83
+
84
+ fs.rmSync(vaultPath, { recursive: true, force: true });
85
+ });
86
+
87
+ it('supports alias event names for command:new', async () => {
88
+ const vaultPath = makeVaultFixture();
89
+ process.env.CLAWVAULT_PATH = vaultPath;
90
+ execFileSyncMock.mockReturnValue('');
91
+
92
+ const handler = await loadHandler();
93
+ await handler({
94
+ event: 'command:new',
95
+ sessionKey: 'agent:clawdious:main',
96
+ context: { commandSource: 'cli' }
97
+ });
98
+
99
+ expect(execFileSyncMock).toHaveBeenCalledWith(
100
+ 'clawvault',
101
+ expect.arrayContaining(['checkpoint', '--working-on']),
102
+ expect.objectContaining({ shell: false })
103
+ );
104
+
105
+ fs.rmSync(vaultPath, { recursive: true, force: true });
106
+ });
107
+
108
+ it('injects recap and memory context on session start alias event', async () => {
109
+ const vaultPath = makeVaultFixture();
110
+ process.env.CLAWVAULT_PATH = vaultPath;
111
+
112
+ execFileSyncMock.mockImplementation((_command, args) => {
113
+ if (args[0] === 'session-recap') {
114
+ return JSON.stringify({
115
+ messages: [
116
+ { role: 'user', text: 'Need a migration plan.' },
117
+ { role: 'assistant', text: 'Suggested phased rollout.' }
118
+ ]
119
+ });
120
+ }
121
+ if (args[0] === 'context') {
122
+ return JSON.stringify({
123
+ context: [
124
+ {
125
+ title: 'Use Postgres',
126
+ age: '1 day ago',
127
+ snippet: 'Selected Postgres for durability.'
128
+ }
129
+ ]
130
+ });
131
+ }
132
+ return '';
133
+ });
134
+
135
+ const handler = await loadHandler();
136
+ const event = {
137
+ eventName: 'session:start',
138
+ sessionKey: 'agent:clawdious:main',
139
+ context: { initialPrompt: 'Need migration plan' },
140
+ messages: [{ role: 'user', content: 'Need migration plan' }]
141
+ };
142
+
143
+ await handler(event);
144
+
145
+ const contextCall = execFileSyncMock.mock.calls.find((call) => call[1]?.[0] === 'context');
146
+ expect(contextCall?.[1]).toEqual(expect.arrayContaining(['--profile', 'auto']));
147
+
148
+ const injected = event.messages.find((message) => message.role === 'system');
149
+ expect(injected?.content).toContain('Session context restored');
150
+ expect(injected?.content).toContain('Recent conversation');
151
+ expect(injected?.content).toContain('Relevant memories');
152
+ expect(injected?.content).toContain('Use Postgres');
153
+
154
+ fs.rmSync(vaultPath, { recursive: true, force: true });
155
+ });
156
+
157
+ it('delegates profile selection to context auto mode for urgent prompts', async () => {
158
+ const vaultPath = makeVaultFixture();
159
+ process.env.CLAWVAULT_PATH = vaultPath;
160
+
161
+ execFileSyncMock.mockImplementation((_command, args) => {
162
+ if (args[0] === 'session-recap') {
163
+ return JSON.stringify({ messages: [] });
164
+ }
165
+ if (args[0] === 'context') {
166
+ return JSON.stringify({ context: [] });
167
+ }
168
+ return '';
169
+ });
170
+
171
+ const handler = await loadHandler();
172
+ await handler({
173
+ eventName: 'session:start',
174
+ sessionKey: 'agent:clawdious:main',
175
+ context: { initialPrompt: 'URGENT outage: rollback failed in production' },
176
+ messages: [{ role: 'user', content: 'URGENT outage: rollback failed in production' }]
177
+ });
178
+
179
+ const contextCall = execFileSyncMock.mock.calls.find((call) => call[1]?.[0] === 'context');
180
+ expect(contextCall?.[1]).toEqual(expect.arrayContaining(['--profile', 'auto']));
181
+
182
+ fs.rmSync(vaultPath, { recursive: true, force: true });
183
+ });
184
+
185
+ it('triggers active observation on heartbeat when threshold is crossed', async () => {
186
+ const vaultPath = makeVaultFixture();
187
+ const sessionId = 'heartbeat-session-1';
188
+ const openClawFixture = makeOpenClawSessionFixture('main', sessionId, 70 * 1024);
189
+ process.env.CLAWVAULT_PATH = vaultPath;
190
+ process.env.OPENCLAW_STATE_DIR = openClawFixture.stateRoot;
191
+
192
+ fs.mkdirSync(path.join(vaultPath, '.clawvault'), { recursive: true });
193
+ fs.writeFileSync(
194
+ path.join(vaultPath, '.clawvault', 'observe-cursors.json'),
195
+ JSON.stringify({
196
+ [sessionId]: {
197
+ lastObservedOffset: 0,
198
+ lastObservedAt: '2026-02-14T00:00:00.000Z',
199
+ sessionKey: 'agent:main:main',
200
+ lastFileSize: 0
201
+ }
202
+ }),
203
+ 'utf-8'
204
+ );
205
+
206
+ execFileSyncMock.mockReturnValue('');
207
+
208
+ const handler = await loadHandler();
209
+ await handler({
210
+ type: 'gateway',
211
+ action: 'heartbeat'
212
+ });
213
+
214
+ expect(execFileSyncMock).toHaveBeenCalledWith(
215
+ 'clawvault',
216
+ expect.arrayContaining(['observe', '--cron', '--agent', 'main']),
217
+ expect.objectContaining({ shell: false })
218
+ );
219
+
220
+ fs.rmSync(vaultPath, { recursive: true, force: true });
221
+ fs.rmSync(openClawFixture.stateRoot, { recursive: true, force: true });
222
+ });
223
+
224
+ it('forces active observation flush on compaction events', async () => {
225
+ const vaultPath = makeVaultFixture();
226
+ process.env.CLAWVAULT_PATH = vaultPath;
227
+ execFileSyncMock.mockReturnValue('');
228
+
229
+ const handler = await loadHandler();
230
+ await handler({
231
+ eventName: 'compaction:memoryFlush',
232
+ sessionKey: 'agent:clawdious:main'
233
+ });
234
+
235
+ expect(execFileSyncMock).toHaveBeenCalledWith(
236
+ 'clawvault',
237
+ expect.arrayContaining(['observe', '--cron', '--min-new', '1']),
238
+ expect.objectContaining({ shell: false })
239
+ );
240
+
241
+ fs.rmSync(vaultPath, { recursive: true, force: true });
242
+ });
243
+
244
+ it('runs weekly reflection on cron.weekly at Sunday midnight', async () => {
245
+ const vaultPath = makeVaultFixture();
246
+ process.env.CLAWVAULT_PATH = vaultPath;
247
+ execFileSyncMock.mockReturnValue('');
248
+
249
+ const handler = await loadHandler();
250
+ await handler({
251
+ eventName: 'cron.weekly',
252
+ timestamp: '2026-02-15T00:00:00.000Z'
253
+ });
254
+
255
+ expect(execFileSyncMock).toHaveBeenCalledWith(
256
+ 'clawvault',
257
+ expect.arrayContaining(['reflect', '-v', vaultPath]),
258
+ expect.objectContaining({ shell: false })
259
+ );
260
+
261
+ fs.rmSync(vaultPath, { recursive: true, force: true });
262
+ });
263
+
264
+ it('uses vaultPath from plugin config when provided in event', async () => {
265
+ const vaultPath = makeVaultFixture();
266
+
267
+ execFileSyncMock.mockImplementation((_command, args) => {
268
+ if (args[0] === 'recover') {
269
+ return 'Clean startup';
270
+ }
271
+ return '';
272
+ });
273
+
274
+ const handler = await loadHandler();
275
+ const event = {
276
+ type: 'gateway',
277
+ action: 'startup',
278
+ pluginConfig: {
279
+ vaultPath
280
+ },
281
+ messages: []
282
+ };
283
+
284
+ await handler(event);
285
+
286
+ expect(execFileSyncMock).toHaveBeenCalledWith(
287
+ 'clawvault',
288
+ expect.arrayContaining(['recover', '--clear', '-v', vaultPath]),
289
+ expect.objectContaining({ shell: false })
290
+ );
291
+
292
+ fs.rmSync(vaultPath, { recursive: true, force: true });
293
+ });
294
+
295
+ it('uses vaultPath from context.pluginConfig when provided', async () => {
296
+ const vaultPath = makeVaultFixture();
297
+
298
+ execFileSyncMock.mockImplementation((_command, args) => {
299
+ if (args[0] === 'recover') {
300
+ return 'Clean startup';
301
+ }
302
+ return '';
303
+ });
304
+
305
+ const handler = await loadHandler();
306
+ const event = {
307
+ type: 'gateway',
308
+ action: 'startup',
309
+ context: {
310
+ pluginConfig: {
311
+ vaultPath
312
+ }
313
+ },
314
+ messages: []
315
+ };
316
+
317
+ await handler(event);
318
+
319
+ expect(execFileSyncMock).toHaveBeenCalledWith(
320
+ 'clawvault',
321
+ expect.arrayContaining(['recover', '--clear', '-v', vaultPath]),
322
+ expect.objectContaining({ shell: false })
323
+ );
324
+
325
+ fs.rmSync(vaultPath, { recursive: true, force: true });
326
+ });
327
+
328
+ it('uses vaultPath from OPENCLAW_PLUGIN_CLAWVAULT_VAULTPATH env var', async () => {
329
+ const vaultPath = makeVaultFixture();
330
+ process.env.OPENCLAW_PLUGIN_CLAWVAULT_VAULTPATH = vaultPath;
331
+
332
+ execFileSyncMock.mockImplementation((_command, args) => {
333
+ if (args[0] === 'recover') {
334
+ return 'Clean startup';
335
+ }
336
+ return '';
337
+ });
338
+
339
+ const handler = await loadHandler();
340
+ const event = {
341
+ type: 'gateway',
342
+ action: 'startup',
343
+ messages: []
344
+ };
345
+
346
+ await handler(event);
347
+
348
+ expect(execFileSyncMock).toHaveBeenCalledWith(
349
+ 'clawvault',
350
+ expect.arrayContaining(['recover', '--clear', '-v', vaultPath]),
351
+ expect.objectContaining({ shell: false })
352
+ );
353
+
354
+ delete process.env.OPENCLAW_PLUGIN_CLAWVAULT_VAULTPATH;
355
+ fs.rmSync(vaultPath, { recursive: true, force: true });
356
+ });
357
+
358
+ it('uses per-agent vault path from agentVaults config', async () => {
359
+ const agent1Vault = makeVaultFixture();
360
+ const agent2Vault = makeVaultFixture();
361
+ const fallbackVault = makeVaultFixture();
362
+
363
+ execFileSyncMock.mockImplementation((_command, args) => {
364
+ if (args[0] === 'recover') {
365
+ return 'Clean startup';
366
+ }
367
+ return '';
368
+ });
369
+
370
+ const handler = await loadHandler();
371
+ const event = {
372
+ type: 'gateway',
373
+ action: 'startup',
374
+ sessionKey: 'agent:agent1:main',
375
+ pluginConfig: {
376
+ vaultPath: fallbackVault,
377
+ agentVaults: {
378
+ agent1: agent1Vault,
379
+ agent2: agent2Vault
380
+ }
381
+ },
382
+ messages: []
383
+ };
384
+
385
+ await handler(event);
386
+
387
+ expect(execFileSyncMock).toHaveBeenCalledWith(
388
+ 'clawvault',
389
+ expect.arrayContaining(['recover', '--clear', '-v', agent1Vault]),
390
+ expect.objectContaining({ shell: false })
391
+ );
392
+
393
+ fs.rmSync(agent1Vault, { recursive: true, force: true });
394
+ fs.rmSync(agent2Vault, { recursive: true, force: true });
395
+ fs.rmSync(fallbackVault, { recursive: true, force: true });
396
+ });
397
+
398
+ it('falls back to vaultPath when agent not in agentVaults', async () => {
399
+ const agent1Vault = makeVaultFixture();
400
+ const fallbackVault = makeVaultFixture();
401
+
402
+ execFileSyncMock.mockImplementation((_command, args) => {
403
+ if (args[0] === 'recover') {
404
+ return 'Clean startup';
405
+ }
406
+ return '';
407
+ });
408
+
409
+ const handler = await loadHandler();
410
+ const event = {
411
+ type: 'gateway',
412
+ action: 'startup',
413
+ sessionKey: 'agent:unknown-agent:main',
414
+ pluginConfig: {
415
+ vaultPath: fallbackVault,
416
+ agentVaults: {
417
+ agent1: agent1Vault
418
+ }
419
+ },
420
+ messages: []
421
+ };
422
+
423
+ await handler(event);
424
+
425
+ expect(execFileSyncMock).toHaveBeenCalledWith(
426
+ 'clawvault',
427
+ expect.arrayContaining(['recover', '--clear', '-v', fallbackVault]),
428
+ expect.objectContaining({ shell: false })
429
+ );
430
+
431
+ fs.rmSync(agent1Vault, { recursive: true, force: true });
432
+ fs.rmSync(fallbackVault, { recursive: true, force: true });
433
+ });
434
+
435
+ it('uses agentVaults from context.pluginConfig', async () => {
436
+ const agent1Vault = makeVaultFixture();
437
+ const fallbackVault = makeVaultFixture();
438
+
439
+ execFileSyncMock.mockImplementation((_command, args) => {
440
+ if (args[0] === 'recover') {
441
+ return 'Clean startup';
442
+ }
443
+ return '';
444
+ });
445
+
446
+ const handler = await loadHandler();
447
+ const event = {
448
+ type: 'gateway',
449
+ action: 'startup',
450
+ sessionKey: 'agent:agent1:main',
451
+ context: {
452
+ pluginConfig: {
453
+ vaultPath: fallbackVault,
454
+ agentVaults: {
455
+ agent1: agent1Vault
456
+ }
457
+ }
458
+ },
459
+ messages: []
460
+ };
461
+
462
+ await handler(event);
463
+
464
+ expect(execFileSyncMock).toHaveBeenCalledWith(
465
+ 'clawvault',
466
+ expect.arrayContaining(['recover', '--clear', '-v', agent1Vault]),
467
+ expect.objectContaining({ shell: false })
468
+ );
469
+
470
+ fs.rmSync(agent1Vault, { recursive: true, force: true });
471
+ fs.rmSync(fallbackVault, { recursive: true, force: true });
472
+ });
473
+
474
+ it('uses OPENCLAW_AGENT_ID env var for agent resolution when session key not available', async () => {
475
+ const agent1Vault = makeVaultFixture();
476
+ const fallbackVault = makeVaultFixture();
477
+ process.env.OPENCLAW_AGENT_ID = 'agent1';
478
+
479
+ execFileSyncMock.mockImplementation((_command, args) => {
480
+ if (args[0] === 'recover') {
481
+ return 'Clean startup';
482
+ }
483
+ return '';
484
+ });
485
+
486
+ const handler = await loadHandler();
487
+ const event = {
488
+ type: 'gateway',
489
+ action: 'startup',
490
+ pluginConfig: {
491
+ vaultPath: fallbackVault,
492
+ agentVaults: {
493
+ agent1: agent1Vault
494
+ }
495
+ },
496
+ messages: []
497
+ };
498
+
499
+ await handler(event);
500
+
501
+ expect(execFileSyncMock).toHaveBeenCalledWith(
502
+ 'clawvault',
503
+ expect.arrayContaining(['recover', '--clear', '-v', agent1Vault]),
504
+ expect.objectContaining({ shell: false })
505
+ );
506
+
507
+ fs.rmSync(agent1Vault, { recursive: true, force: true });
508
+ fs.rmSync(fallbackVault, { recursive: true, force: true });
509
+ });
510
+ });
@@ -0,0 +1,72 @@
1
+ {
2
+ "id": "clawvault",
3
+ "name": "ClawVault",
4
+ "version": "2.6.1",
5
+ "description": "Structured memory system for AI agents with context death resilience",
6
+ "kind": "memory",
7
+ "configSchema": {
8
+ "type": "object",
9
+ "properties": {
10
+ "vaultPath": {
11
+ "type": "string",
12
+ "description": "Path to the ClawVault vault directory. If not set, auto-discovered from CLAWVAULT_PATH or by walking up from cwd."
13
+ },
14
+ "autoCheckpoint": {
15
+ "type": "boolean",
16
+ "description": "Enable automatic checkpointing on session events",
17
+ "default": true
18
+ },
19
+ "contextProfile": {
20
+ "type": "string",
21
+ "enum": ["default", "planning", "incident", "handoff", "auto"],
22
+ "description": "Default context profile for session start injection",
23
+ "default": "auto"
24
+ },
25
+ "maxContextResults": {
26
+ "type": "integer",
27
+ "minimum": 1,
28
+ "maximum": 20,
29
+ "description": "Maximum number of context results to inject on session start",
30
+ "default": 4
31
+ },
32
+ "observeOnHeartbeat": {
33
+ "type": "boolean",
34
+ "description": "Enable observation threshold checks on gateway heartbeat",
35
+ "default": true
36
+ },
37
+ "weeklyReflection": {
38
+ "type": "boolean",
39
+ "description": "Enable weekly reflection on Sunday midnight UTC",
40
+ "default": true
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ },
45
+ "uiHints": {
46
+ "vaultPath": {
47
+ "label": "Vault Path",
48
+ "placeholder": "~/my-vault",
49
+ "description": "Path to your ClawVault memory vault"
50
+ },
51
+ "autoCheckpoint": {
52
+ "label": "Auto Checkpoint",
53
+ "description": "Automatically checkpoint before session resets"
54
+ },
55
+ "contextProfile": {
56
+ "label": "Context Profile",
57
+ "description": "Profile used for context injection at session start"
58
+ },
59
+ "maxContextResults": {
60
+ "label": "Max Context Results",
61
+ "description": "Number of vault memories to inject"
62
+ },
63
+ "observeOnHeartbeat": {
64
+ "label": "Observe on Heartbeat",
65
+ "description": "Check observation thresholds during heartbeat events"
66
+ },
67
+ "weeklyReflection": {
68
+ "label": "Weekly Reflection",
69
+ "description": "Run weekly reflection on Sunday midnight UTC"
70
+ }
71
+ }
72
+ }
@@ -1,56 +1,83 @@
1
1
  {
2
2
  "id": "clawvault",
3
- "kind": "memory",
4
- "uiHints": {
5
- "vaultPath": {
6
- "label": "Vault Path",
7
- "placeholder": "~/clawvault",
8
- "help": "Path to your ClawVault vault directory"
9
- },
10
- "collection": {
11
- "label": "QMD Collection",
12
- "placeholder": "clawvault",
13
- "help": "Name of the qmd search collection",
14
- "advanced": true
15
- },
16
- "autoRecall": {
17
- "label": "Auto-Recall",
18
- "help": "Automatically inject relevant memories before each agent turn"
19
- },
20
- "autoCapture": {
21
- "label": "Auto-Capture",
22
- "help": "Automatically observe and capture important information from conversations"
23
- },
24
- "recallLimit": {
25
- "label": "Recall Limit",
26
- "help": "Max memories to inject per turn (default: 5)",
27
- "advanced": true
28
- }
29
- },
3
+ "name": "ClawVault",
4
+ "version": "2.6.5",
5
+ "description": "Structured memory system for AI agents with context death resilience",
30
6
  "configSchema": {
31
7
  "type": "object",
32
- "additionalProperties": false,
33
8
  "properties": {
34
9
  "vaultPath": {
35
- "type": "string"
10
+ "type": "string",
11
+ "description": "Path to the ClawVault vault directory. If not set, auto-discovered from CLAWVAULT_PATH or by walking up from cwd. Used as fallback when agentVaults is not set or agent not found."
36
12
  },
37
- "collection": {
38
- "type": "string"
13
+ "agentVaults": {
14
+ "type": "object",
15
+ "description": "Mapping of agent names to vault paths. Allows each agent to have its own vault. Falls back to vaultPath if agent not found.",
16
+ "additionalProperties": {
17
+ "type": "string",
18
+ "description": "Path to the vault directory for this agent"
19
+ }
39
20
  },
40
- "autoRecall": {
21
+ "autoCheckpoint": {
41
22
  "type": "boolean",
23
+ "description": "Enable automatic checkpointing on session events",
42
24
  "default": true
43
25
  },
44
- "autoCapture": {
45
- "type": "boolean",
46
- "default": true
26
+ "contextProfile": {
27
+ "type": "string",
28
+ "enum": ["default", "planning", "incident", "handoff", "auto"],
29
+ "description": "Default context profile for session start injection",
30
+ "default": "auto"
47
31
  },
48
- "recallLimit": {
49
- "type": "number",
32
+ "maxContextResults": {
33
+ "type": "integer",
50
34
  "minimum": 1,
51
35
  "maximum": 20,
52
- "default": 5
36
+ "description": "Maximum number of context results to inject on session start",
37
+ "default": 4
38
+ },
39
+ "observeOnHeartbeat": {
40
+ "type": "boolean",
41
+ "description": "Enable observation threshold checks on gateway heartbeat",
42
+ "default": true
43
+ },
44
+ "weeklyReflection": {
45
+ "type": "boolean",
46
+ "description": "Enable weekly reflection on Sunday midnight UTC",
47
+ "default": true
53
48
  }
49
+ },
50
+ "additionalProperties": false
51
+ },
52
+ "uiHints": {
53
+ "vaultPath": {
54
+ "label": "Vault Path",
55
+ "placeholder": "~/my-vault",
56
+ "description": "Path to your ClawVault memory vault (fallback when agentVaults not set)"
57
+ },
58
+ "agentVaults": {
59
+ "label": "Agent Vaults",
60
+ "description": "Per-agent vault paths (e.g., {\"agent1\": \"/path/to/vault1\", \"agent2\": \"/path/to/vault2\"})"
61
+ },
62
+ "autoCheckpoint": {
63
+ "label": "Auto Checkpoint",
64
+ "description": "Automatically checkpoint before session resets"
65
+ },
66
+ "contextProfile": {
67
+ "label": "Context Profile",
68
+ "description": "Profile used for context injection at session start"
69
+ },
70
+ "maxContextResults": {
71
+ "label": "Max Context Results",
72
+ "description": "Number of vault memories to inject"
73
+ },
74
+ "observeOnHeartbeat": {
75
+ "label": "Observe on Heartbeat",
76
+ "description": "Check observation thresholds during heartbeat events"
77
+ },
78
+ "weeklyReflection": {
79
+ "label": "Weekly Reflection",
80
+ "description": "Run weekly reflection on Sunday midnight UTC"
54
81
  }
55
82
  }
56
83
  }