clawvault 3.2.1 → 3.3.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.
- package/README.md +56 -16
- package/bin/clawvault.js +0 -2
- package/bin/command-registration.test.js +13 -1
- package/bin/help-contract.test.js +14 -0
- package/bin/register-core-commands.js +88 -0
- package/bin/register-core-commands.test.js +80 -0
- package/bin/register-maintenance-commands.js +57 -6
- package/bin/register-query-commands.js +10 -28
- package/bin/test-helpers/cli-command-fixtures.js +1 -0
- package/dist/chunk-2PKBIKDH.js +130 -0
- package/dist/{chunk-U67V476Y.js → chunk-2ZDO52B4.js} +18 -1
- package/dist/{chunk-ZZA73MFY.js → chunk-33DOSHTA.js} +176 -36
- package/dist/{chunk-AZYOKJYC.js → chunk-4PY655YM.js} +13 -1
- package/dist/{chunk-2JQ3O2YL.js → chunk-5EFSWZO6.js} +3 -3
- package/dist/{chunk-Y3TIJEBP.js → chunk-7SWP5FKU.js} +34 -613
- package/dist/{chunk-4VQTUVH7.js → chunk-7YZWHM36.js} +52 -26
- package/dist/{chunk-URXDAUVH.js → chunk-AXSJIFOJ.js} +174 -1
- package/dist/{chunk-4ITRXIVT.js → chunk-BLQXXX7Q.js} +6 -6
- package/dist/chunk-CSHO3PJB.js +684 -0
- package/dist/{chunk-S5OJEGFG.js → chunk-DOIUYIXV.js} +2 -2
- package/dist/{chunk-YXQCA6B7.js → chunk-DVOUSOR3.js} +112 -7
- package/dist/{chunk-YDWHS4LJ.js → chunk-ECGJYWNA.js} +205 -33
- package/dist/{chunk-QMHPQYUV.js → chunk-EL6UBSX5.js} +7 -6
- package/dist/chunk-FZ5I2NF7.js +352 -0
- package/dist/{chunk-WJVWINEM.js → chunk-GFCHWMGD.js} +55 -6
- package/dist/{chunk-GNJL4YGR.js → chunk-GJO3CFUN.js} +30 -6
- package/dist/chunk-H3JZIB5O.js +322 -0
- package/dist/chunk-HEHO7SMV.js +51 -0
- package/dist/{chunk-UCQAOZHW.js → chunk-HGDDW24U.js} +3 -3
- package/dist/chunk-J3YUXVID.js +907 -0
- package/dist/{chunk-Y6VJKXGL.js → chunk-KCYWJDDW.js} +1 -1
- package/dist/{chunk-P5EPF6MB.js → chunk-MW5C6ZQA.js} +110 -13
- package/dist/{chunk-YNIPYN4F.js → chunk-OFOCU2V4.js} +6 -5
- package/dist/{chunk-42MXU7A6.js → chunk-P62WHA27.js} +58 -47
- package/dist/chunk-PTWPPVC7.js +972 -0
- package/dist/{chunk-FAKNOB7Y.js → chunk-QFWERBDP.js} +2 -2
- package/dist/{chunk-IIOU45CK.js → chunk-S7N7HI5E.js} +2 -2
- package/dist/{chunk-ECRZL5XR.js → chunk-T7E764W3.js} +23 -7
- package/dist/chunk-TDWFBDAQ.js +1016 -0
- package/dist/{chunk-MNPUYCHQ.js → chunk-TWMI3SNN.js} +6 -5
- package/dist/{chunk-2RAZ4ZFE.js → chunk-VBILES4B.js} +1 -1
- package/dist/{chunk-PI4WMLMG.js → chunk-VXAGOLDP.js} +1 -1
- package/dist/chunk-YCUVAOFC.js +158 -0
- package/dist/{chunk-SS4B7P7V.js → chunk-YIDV4VV2.js} +1 -1
- package/dist/chunk-ZKWPCBYT.js +600 -0
- package/dist/cli/index.js +27 -21
- package/dist/commands/archive.js +3 -3
- package/dist/commands/backlog.js +1 -1
- package/dist/commands/benchmark.d.ts +12 -0
- package/dist/commands/benchmark.js +12 -0
- package/dist/commands/blocked.js +1 -1
- package/dist/commands/canvas.js +2 -2
- package/dist/commands/checkpoint.js +1 -1
- package/dist/commands/compat.js +1 -1
- package/dist/commands/context.js +8 -7
- package/dist/commands/doctor.d.ts +8 -3
- package/dist/commands/doctor.js +8 -22
- package/dist/commands/embed.js +6 -5
- package/dist/commands/entities.js +2 -2
- package/dist/commands/graph.js +4 -4
- package/dist/commands/inbox.d.ts +23 -0
- package/dist/commands/inbox.js +11 -0
- package/dist/commands/inject.d.ts +1 -1
- package/dist/commands/inject.js +5 -5
- package/dist/commands/kanban.js +1 -1
- package/dist/commands/link.js +9 -9
- package/dist/commands/maintain.d.ts +32 -0
- package/dist/commands/maintain.js +12 -0
- package/dist/commands/migrate-observations.js +3 -3
- package/dist/commands/observe.js +11 -10
- package/dist/commands/project.js +2 -2
- package/dist/commands/rebuild-embeddings.js +48 -17
- package/dist/commands/rebuild.js +9 -8
- package/dist/commands/recover.js +1 -1
- package/dist/commands/reflect.js +6 -6
- package/dist/commands/repair-session.js +1 -1
- package/dist/commands/replay.js +10 -9
- package/dist/commands/session-recap.js +1 -1
- package/dist/commands/setup.js +4 -3
- package/dist/commands/shell-init.js +1 -1
- package/dist/commands/sleep.d.ts +1 -1
- package/dist/commands/sleep.js +20 -18
- package/dist/commands/status.js +40 -26
- package/dist/commands/sync-bd.js +3 -3
- package/dist/commands/tailscale.js +3 -3
- package/dist/commands/task.js +1 -1
- package/dist/commands/template.js +1 -1
- package/dist/commands/wake.d.ts +1 -1
- package/dist/commands/wake.js +10 -9
- package/dist/index.d.ts +175 -16
- package/dist/index.js +277 -108
- package/dist/{inject-DYUrDqQO.d.ts → inject-DEb_jpLi.d.ts} +3 -1
- package/dist/lib/auto-linker.js +2 -2
- package/dist/lib/canvas-layout.js +1 -1
- package/dist/lib/config.js +2 -2
- package/dist/lib/entity-index.js +1 -1
- package/dist/lib/project-utils.js +2 -2
- package/dist/lib/session-repair.js +1 -1
- package/dist/lib/session-utils.js +1 -1
- package/dist/lib/tailscale.js +1 -1
- package/dist/lib/task-utils.js +1 -1
- package/dist/lib/template-engine.js +1 -1
- package/dist/lib/webdav.js +1 -1
- package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
- package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
- package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
- package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
- package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
- package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
- package/dist/openclaw-plugin.d.ts +8 -0
- package/dist/openclaw-plugin.js +14 -0
- package/dist/transformers.node-A2ZRORSQ.js +46775 -0
- package/dist/{types-BbWJoC1c.d.ts → types-DslKvCaj.d.ts} +51 -1
- package/hooks/clawvault/HOOK.md +25 -8
- package/hooks/clawvault/handler.js +215 -78
- package/hooks/clawvault/handler.test.js +109 -43
- package/hooks/clawvault/integrity.js +112 -0
- package/hooks/clawvault/integrity.test.js +32 -0
- package/hooks/clawvault/openclaw.plugin.json +133 -15
- package/openclaw.plugin.json +131 -203
- package/package.json +10 -7
- package/bin/register-workgraph-commands.js +0 -451
- package/dist/chunk-5PJ4STIC.js +0 -465
- package/dist/chunk-ERNE2FZ5.js +0 -189
- package/dist/chunk-HR4KN6S2.js +0 -152
- package/dist/chunk-IJBFGPCS.js +0 -33
- package/dist/chunk-K7PNYS45.js +0 -93
- package/dist/chunk-NTOPJI7W.js +0 -207
- package/dist/chunk-PG56HX5T.js +0 -154
- package/dist/chunk-QPDDIHXE.js +0 -501
- package/dist/chunk-WIOLLGAD.js +0 -190
- package/dist/chunk-WMGIIABP.js +0 -15
- package/dist/ledger-B7g7jhqG.d.ts +0 -44
- package/dist/plugin/index.d.ts +0 -352
- package/dist/plugin/index.js +0 -4264
- package/dist/registry-BR4326o0.d.ts +0 -30
- package/dist/store-CA-6sKCJ.d.ts +0 -34
- package/dist/thread-B9LhXNU0.d.ts +0 -41
- package/dist/workgraph/index.d.ts +0 -5
- package/dist/workgraph/index.js +0 -23
- package/dist/workgraph/ledger.d.ts +0 -2
- package/dist/workgraph/ledger.js +0 -25
- package/dist/workgraph/registry.d.ts +0 -2
- package/dist/workgraph/registry.js +0 -19
- package/dist/workgraph/store.d.ts +0 -2
- package/dist/workgraph/store.js +0 -25
- package/dist/workgraph/thread.d.ts +0 -2
- package/dist/workgraph/thread.js +0 -25
- package/dist/workgraph/types.d.ts +0 -54
- package/dist/workgraph/types.js +0 -7
|
@@ -14,6 +14,8 @@ interface VaultConfig {
|
|
|
14
14
|
qmdRoot?: string;
|
|
15
15
|
/** Custom templates path (optional) */
|
|
16
16
|
templatesPath?: string;
|
|
17
|
+
/** Search configuration */
|
|
18
|
+
search?: VaultSearchConfig;
|
|
17
19
|
}
|
|
18
20
|
interface VaultMeta {
|
|
19
21
|
name: string;
|
|
@@ -26,6 +28,37 @@ interface VaultMeta {
|
|
|
26
28
|
qmdCollection?: string;
|
|
27
29
|
/** Root path for qmd collection (defaults to vault path) */
|
|
28
30
|
qmdRoot?: string;
|
|
31
|
+
/** Search configuration */
|
|
32
|
+
search?: VaultSearchConfig;
|
|
33
|
+
}
|
|
34
|
+
type SearchBackend = 'in-process' | 'qmd';
|
|
35
|
+
type EmbeddingProvider = 'none' | 'openai' | 'gemini' | 'ollama';
|
|
36
|
+
type RerankProvider = 'none' | 'jina' | 'voyage' | 'siliconflow' | 'pinecone';
|
|
37
|
+
interface VaultSearchConfig {
|
|
38
|
+
/**
|
|
39
|
+
* Default backend used for search commands.
|
|
40
|
+
* in-process is default and recommended for constrained devices.
|
|
41
|
+
*/
|
|
42
|
+
backend?: SearchBackend;
|
|
43
|
+
/** Allow qmd fallback when installed and in-process search fails */
|
|
44
|
+
qmdFallback?: boolean;
|
|
45
|
+
/** Chunk size in characters for in-process BM25 indexing */
|
|
46
|
+
chunkSize?: number;
|
|
47
|
+
/** Chunk overlap in characters for in-process BM25 indexing */
|
|
48
|
+
chunkOverlap?: number;
|
|
49
|
+
embeddings?: {
|
|
50
|
+
provider?: EmbeddingProvider;
|
|
51
|
+
model?: string;
|
|
52
|
+
baseUrl?: string;
|
|
53
|
+
apiKey?: string;
|
|
54
|
+
};
|
|
55
|
+
rerank?: {
|
|
56
|
+
provider?: RerankProvider;
|
|
57
|
+
model?: string;
|
|
58
|
+
endpoint?: string;
|
|
59
|
+
apiKey?: string;
|
|
60
|
+
weight?: number;
|
|
61
|
+
};
|
|
29
62
|
}
|
|
30
63
|
interface Document {
|
|
31
64
|
/** Unique ID (relative path without extension) */
|
|
@@ -89,6 +122,23 @@ interface StoreOptions {
|
|
|
89
122
|
/** Optional qmd index name override (defaults to configured/global index) */
|
|
90
123
|
qmdIndexName?: string;
|
|
91
124
|
}
|
|
125
|
+
type PatchMode = 'append' | 'replace' | 'content';
|
|
126
|
+
interface PatchOptions {
|
|
127
|
+
/** Document id/path to patch (e.g. decisions/my-note or decisions/my-note.md) */
|
|
128
|
+
idOrPath: string;
|
|
129
|
+
/** Patch mode */
|
|
130
|
+
mode: PatchMode;
|
|
131
|
+
/** Text to append when mode=append */
|
|
132
|
+
append?: string;
|
|
133
|
+
/** Search text when mode=replace */
|
|
134
|
+
replace?: string;
|
|
135
|
+
/** Replacement text when mode=replace */
|
|
136
|
+
with?: string;
|
|
137
|
+
/** Replacement body when mode=content */
|
|
138
|
+
content?: string;
|
|
139
|
+
/** Optional markdown section heading (without #) to scope the patch */
|
|
140
|
+
section?: string;
|
|
141
|
+
}
|
|
92
142
|
interface SyncOptions {
|
|
93
143
|
/** Target directory to sync to */
|
|
94
144
|
target: string;
|
|
@@ -159,4 +209,4 @@ interface SessionRecap {
|
|
|
159
209
|
}
|
|
160
210
|
declare const DEFAULT_CONFIG: Partial<VaultConfig>;
|
|
161
211
|
|
|
162
|
-
export { type Category as C, type Document as D, type HandoffDocument as H, type MemoryType as M, type
|
|
212
|
+
export { type Category as C, type Document as D, type EmbeddingProvider as E, type HandoffDocument as H, type MemoryType as M, type PatchOptions as P, type RerankProvider as R, type StoreOptions as S, TYPE_TO_CATEGORY as T, type VaultConfig as V, type SearchOptions as a, type SearchResult as b, type SyncOptions as c, type SyncResult as d, type SessionRecap as e, type VaultSearchConfig as f, DEFAULT_CATEGORIES as g, DEFAULT_CONFIG as h, MEMORY_TYPES as i, type PatchMode as j, type SearchBackend as k, type VaultMeta as l };
|
package/hooks/clawvault/HOOK.md
CHANGED
|
@@ -81,33 +81,50 @@ Configure the plugin via OpenClaw's config system:
|
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
83
|
# Set vault path
|
|
84
|
-
openclaw config set plugins.clawvault.config.vaultPath ~/my-vault
|
|
84
|
+
openclaw config set plugins.entries.clawvault.config.vaultPath ~/my-vault
|
|
85
85
|
|
|
86
86
|
# View current config
|
|
87
|
-
openclaw config get plugins.clawvault
|
|
87
|
+
openclaw config get plugins.entries.clawvault
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
Available configuration options:
|
|
90
|
+
Available configuration options (all privileged actions are opt-in):
|
|
91
91
|
|
|
92
92
|
| Key | Type | Default | Description |
|
|
93
93
|
|-----|------|---------|-------------|
|
|
94
94
|
| `vaultPath` | string | (auto-detected) | Path to the ClawVault vault directory |
|
|
95
|
-
| `
|
|
95
|
+
| `agentVaults` | object | `{}` | Per-agent vault mapping |
|
|
96
|
+
| `allowClawvaultExec` | boolean | `false` | Required gate for all `child_process` calls |
|
|
97
|
+
| `clawvaultBinaryPath` | string | (PATH lookup) | Optional absolute path to `clawvault` binary |
|
|
98
|
+
| `clawvaultBinarySha256` | string | (unset) | Optional SHA-256 executable integrity check |
|
|
99
|
+
| `allowEnvAccess` | boolean | `false` | Allow env fallbacks (`OPENCLAW_*`, `CLAWVAULT_PATH`) |
|
|
100
|
+
| `enableStartupRecovery` | boolean | `false` | Enable `gateway:startup` recovery check |
|
|
101
|
+
| `enableSessionContextInjection` | boolean | `false` | Enable `session:start` recap/context injection |
|
|
102
|
+
| `enableAutoCheckpoint` | boolean | `false` | Enable checkpoint on `command:new` |
|
|
103
|
+
| `enableObserveOnNew` | boolean | `false` | Enable observer flush on `command:new` |
|
|
104
|
+
| `enableHeartbeatObservation` | boolean | `false` | Enable heartbeat-driven observation |
|
|
105
|
+
| `enableCompactionObservation` | boolean | `false` | Enable observer flush on compaction |
|
|
106
|
+
| `enableWeeklyReflection` | boolean | `false` | Enable weekly reflection cron |
|
|
107
|
+
| `enableFactExtraction` | boolean | `false` | Enable local fact extraction/entity graph updates |
|
|
108
|
+
| `autoCheckpoint` | boolean | `false` | Deprecated alias for `enableAutoCheckpoint` |
|
|
96
109
|
| `contextProfile` | string | `"auto"` | Default context profile (`default`, `planning`, `incident`, `handoff`, `auto`) |
|
|
97
110
|
| `maxContextResults` | integer | `4` | Maximum context results to inject on session start |
|
|
98
|
-
| `observeOnHeartbeat` | boolean | `
|
|
99
|
-
| `weeklyReflection` | boolean | `
|
|
111
|
+
| `observeOnHeartbeat` | boolean | `false` | Deprecated alias for `enableHeartbeatObservation` |
|
|
112
|
+
| `weeklyReflection` | boolean | `false` | Deprecated alias for `enableWeeklyReflection` |
|
|
113
|
+
|
|
114
|
+
Security details and threat model: see [SECURITY.md](../../SECURITY.md).
|
|
100
115
|
|
|
101
116
|
### Vault Path Resolution
|
|
102
117
|
|
|
103
|
-
|
|
118
|
+
When `allowEnvAccess=true`, the hook resolves the vault path in this order:
|
|
104
119
|
|
|
105
|
-
1. Plugin config (`plugins.clawvault.config.vaultPath` set via `openclaw config`)
|
|
120
|
+
1. Plugin config (`plugins.entries.clawvault.config.vaultPath` set via `openclaw config`)
|
|
106
121
|
2. `OPENCLAW_PLUGIN_CLAWVAULT_VAULTPATH` environment variable
|
|
107
122
|
3. `CLAWVAULT_PATH` environment variable
|
|
108
123
|
4. Walking up from cwd to find `.clawvault.json`
|
|
109
124
|
5. Checking `memory/` subdirectory (OpenClaw convention)
|
|
110
125
|
|
|
126
|
+
When `allowEnvAccess=false` (default), steps 2 and 3 are skipped.
|
|
127
|
+
|
|
111
128
|
### Troubleshooting
|
|
112
129
|
|
|
113
130
|
If `openclaw hooks enable clawvault` fails with hook-not-found, run `openclaw hooks install clawvault` first and verify discovery with `openclaw hooks list --verbose`.
|
|
@@ -16,6 +16,11 @@ import { createHash, randomUUID } from 'crypto';
|
|
|
16
16
|
import * as fs from 'fs';
|
|
17
17
|
import * as os from 'os';
|
|
18
18
|
import * as path from 'path';
|
|
19
|
+
import {
|
|
20
|
+
resolveExecutablePath,
|
|
21
|
+
sanitizeExecArgs,
|
|
22
|
+
verifyExecutableIntegrity
|
|
23
|
+
} from './integrity.js';
|
|
19
24
|
|
|
20
25
|
const MAX_CONTEXT_RESULTS = 4;
|
|
21
26
|
const MAX_CONTEXT_PROMPT_LENGTH = 500;
|
|
@@ -36,6 +41,7 @@ const MAX_FACT_TEXT_LENGTH = 600;
|
|
|
36
41
|
const FACT_SENTENCE_SPLIT_RE = /[.!?]+\s+|\r?\n+/;
|
|
37
42
|
const EXCLUSIVE_FACT_RELATIONS = new Set(['lives_in', 'works_at', 'age']);
|
|
38
43
|
const ENTITY_TARGET_RELATIONS = new Set(['works_at', 'lives_in', 'partner_name', 'dog_name', 'parent_name']);
|
|
44
|
+
const CLAWVAULT_EXECUTABLE = 'clawvault';
|
|
39
45
|
|
|
40
46
|
// Sanitize string for safe display (prevent prompt injection via control chars)
|
|
41
47
|
function sanitizeForDisplay(str) {
|
|
@@ -107,15 +113,17 @@ function normalizeAbsoluteEnvPath(value) {
|
|
|
107
113
|
return resolved;
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
function getOpenClawAgentsDir() {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
function getOpenClawAgentsDir(pluginConfig) {
|
|
117
|
+
if (allowsEnvAccess(pluginConfig)) {
|
|
118
|
+
const stateDir = normalizeAbsoluteEnvPath(process.env.OPENCLAW_STATE_DIR);
|
|
119
|
+
if (stateDir) {
|
|
120
|
+
return path.join(stateDir, 'agents');
|
|
121
|
+
}
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
const openClawHome = normalizeAbsoluteEnvPath(process.env.OPENCLAW_HOME);
|
|
124
|
+
if (openClawHome) {
|
|
125
|
+
return path.join(openClawHome, 'agents');
|
|
126
|
+
}
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
return path.join(os.homedir(), '.openclaw', 'agents');
|
|
@@ -155,8 +163,8 @@ function getScaledObservationThresholdBytes(fileSizeBytes) {
|
|
|
155
163
|
return LARGE_SESSION_THRESHOLD_BYTES;
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
function parseSessionIndex(agentId) {
|
|
159
|
-
const sessionsDir = path.join(getOpenClawAgentsDir(), agentId, 'sessions');
|
|
166
|
+
function parseSessionIndex(agentId, pluginConfig) {
|
|
167
|
+
const sessionsDir = path.join(getOpenClawAgentsDir(pluginConfig), agentId, 'sessions');
|
|
160
168
|
const sessionsJsonPath = path.join(sessionsDir, 'sessions.json');
|
|
161
169
|
if (!fs.existsSync(sessionsJsonPath)) {
|
|
162
170
|
return { sessionsDir, index: {} };
|
|
@@ -173,9 +181,9 @@ function parseSessionIndex(agentId) {
|
|
|
173
181
|
}
|
|
174
182
|
}
|
|
175
183
|
|
|
176
|
-
function shouldObserveActiveSessions(vaultPath, agentId) {
|
|
184
|
+
function shouldObserveActiveSessions(vaultPath, agentId, pluginConfig) {
|
|
177
185
|
const cursors = loadObserveCursors(vaultPath);
|
|
178
|
-
const { sessionsDir, index } = parseSessionIndex(agentId);
|
|
186
|
+
const { sessionsDir, index } = parseSessionIndex(agentId, pluginConfig);
|
|
179
187
|
const entries = Object.entries(index);
|
|
180
188
|
if (entries.length === 0) {
|
|
181
189
|
return false;
|
|
@@ -457,6 +465,8 @@ function extractPluginConfig(event) {
|
|
|
457
465
|
const candidates = [
|
|
458
466
|
event?.pluginConfig,
|
|
459
467
|
event?.context?.pluginConfig,
|
|
468
|
+
event?.config?.plugins?.entries?.clawvault?.config,
|
|
469
|
+
event?.context?.config?.plugins?.entries?.clawvault?.config,
|
|
460
470
|
event?.config?.plugins?.clawvault?.config,
|
|
461
471
|
event?.context?.config?.plugins?.clawvault?.config
|
|
462
472
|
];
|
|
@@ -470,6 +480,31 @@ function extractPluginConfig(event) {
|
|
|
470
480
|
return {};
|
|
471
481
|
}
|
|
472
482
|
|
|
483
|
+
function isOptInEnabled(pluginConfig, ...keys) {
|
|
484
|
+
for (const key of keys) {
|
|
485
|
+
if (pluginConfig?.[key] === true) return true;
|
|
486
|
+
}
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function allowsEnvAccess(pluginConfig) {
|
|
491
|
+
return isOptInEnabled(pluginConfig, 'allowEnvAccess');
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function getConfiguredExecutablePath(pluginConfig) {
|
|
495
|
+
const value = pluginConfig?.clawvaultBinaryPath;
|
|
496
|
+
if (typeof value !== 'string') return null;
|
|
497
|
+
const trimmed = value.trim();
|
|
498
|
+
return trimmed || null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function getConfiguredExecutableSha256(pluginConfig) {
|
|
502
|
+
const value = pluginConfig?.clawvaultBinarySha256;
|
|
503
|
+
if (typeof value !== 'string') return null;
|
|
504
|
+
const trimmed = value.trim().toLowerCase();
|
|
505
|
+
return trimmed || null;
|
|
506
|
+
}
|
|
507
|
+
|
|
473
508
|
// Resolve vault path for a specific agent from agentVaults config
|
|
474
509
|
function resolveAgentVaultPath(pluginConfig, agentId) {
|
|
475
510
|
if (!agentId || typeof agentId !== 'string') return null;
|
|
@@ -487,11 +522,9 @@ function resolveAgentVaultPath(pluginConfig, agentId) {
|
|
|
487
522
|
|
|
488
523
|
// Find vault by walking up directories
|
|
489
524
|
// Supports per-agent vault paths via agentVaults config
|
|
490
|
-
function findVaultPath(event, options = {}) {
|
|
491
|
-
const pluginConfig = extractPluginConfig(event);
|
|
492
|
-
|
|
525
|
+
function findVaultPath(event, pluginConfig, options = {}) {
|
|
493
526
|
// Determine agent ID for per-agent vault resolution
|
|
494
|
-
const agentId = options.agentId || resolveAgentIdForEvent(event);
|
|
527
|
+
const agentId = options.agentId || resolveAgentIdForEvent(event, pluginConfig);
|
|
495
528
|
|
|
496
529
|
// Check agentVaults first (per-agent vault paths)
|
|
497
530
|
if (agentId) {
|
|
@@ -508,15 +541,17 @@ function findVaultPath(event, options = {}) {
|
|
|
508
541
|
if (validated) return validated;
|
|
509
542
|
}
|
|
510
543
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
544
|
+
if (allowsEnvAccess(pluginConfig)) {
|
|
545
|
+
// Check OPENCLAW_PLUGIN_CLAWVAULT_VAULTPATH env (injected by OpenClaw from plugin config)
|
|
546
|
+
if (process.env.OPENCLAW_PLUGIN_CLAWVAULT_VAULTPATH) {
|
|
547
|
+
const validated = validateVaultPath(process.env.OPENCLAW_PLUGIN_CLAWVAULT_VAULTPATH);
|
|
548
|
+
if (validated) return validated;
|
|
549
|
+
}
|
|
516
550
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
551
|
+
// Check CLAWVAULT_PATH env
|
|
552
|
+
if (process.env.CLAWVAULT_PATH) {
|
|
553
|
+
return validateVaultPath(process.env.CLAWVAULT_PATH);
|
|
554
|
+
}
|
|
520
555
|
}
|
|
521
556
|
|
|
522
557
|
// Walk up from cwd
|
|
@@ -539,16 +574,54 @@ function findVaultPath(event, options = {}) {
|
|
|
539
574
|
}
|
|
540
575
|
|
|
541
576
|
// Run clawvault command safely (no shell)
|
|
542
|
-
function runClawvault(args, options = {}) {
|
|
577
|
+
function runClawvault(args, pluginConfig, options = {}) {
|
|
578
|
+
if (!isOptInEnabled(pluginConfig, 'allowClawvaultExec')) {
|
|
579
|
+
return {
|
|
580
|
+
success: false,
|
|
581
|
+
skipped: true,
|
|
582
|
+
output: 'ClawVault CLI execution is disabled. Set allowClawvaultExec=true to enable.',
|
|
583
|
+
code: 0
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
543
587
|
const timeoutMs = Number.isFinite(options.timeoutMs) ? Math.max(1000, Number(options.timeoutMs)) : 15000;
|
|
588
|
+
const executablePath = resolveExecutablePath(CLAWVAULT_EXECUTABLE, {
|
|
589
|
+
explicitPath: getConfiguredExecutablePath(pluginConfig)
|
|
590
|
+
});
|
|
591
|
+
if (!executablePath) {
|
|
592
|
+
return {
|
|
593
|
+
success: false,
|
|
594
|
+
output: 'Unable to resolve clawvault executable path. Set clawvaultBinaryPath to an absolute executable path.',
|
|
595
|
+
code: 1
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const expectedSha256 = getConfiguredExecutableSha256(pluginConfig);
|
|
600
|
+
const integrityResult = verifyExecutableIntegrity(executablePath, expectedSha256);
|
|
601
|
+
if (!integrityResult.ok) {
|
|
602
|
+
return {
|
|
603
|
+
success: false,
|
|
604
|
+
output: `Executable integrity verification failed for ${executablePath}.`,
|
|
605
|
+
code: 1
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let sanitizedArgs;
|
|
544
610
|
try {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
611
|
+
sanitizedArgs = sanitizeExecArgs(args);
|
|
612
|
+
} catch (err) {
|
|
613
|
+
return {
|
|
614
|
+
success: false,
|
|
615
|
+
output: err?.message || 'Invalid command arguments',
|
|
616
|
+
code: 1
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
try {
|
|
621
|
+
const output = execFileSync(executablePath, sanitizedArgs, {
|
|
548
622
|
encoding: 'utf-8',
|
|
549
623
|
timeout: timeoutMs,
|
|
550
624
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
551
|
-
// Explicitly no shell
|
|
552
625
|
shell: false
|
|
553
626
|
});
|
|
554
627
|
return { success: true, output: output.trim(), code: 0 };
|
|
@@ -586,23 +659,29 @@ function parseRecoveryOutput(output) {
|
|
|
586
659
|
return { hadDeath, workingOn };
|
|
587
660
|
}
|
|
588
661
|
|
|
589
|
-
function resolveAgentIdForEvent(event) {
|
|
662
|
+
function resolveAgentIdForEvent(event, pluginConfig) {
|
|
590
663
|
const fromSessionKey = extractAgentIdFromSessionKey(extractSessionKey(event));
|
|
591
664
|
if (fromSessionKey) return fromSessionKey;
|
|
592
665
|
|
|
593
|
-
|
|
594
|
-
|
|
666
|
+
if (allowsEnvAccess(pluginConfig)) {
|
|
667
|
+
const fromEnv = sanitizeAgentId(process.env.OPENCLAW_AGENT_ID);
|
|
668
|
+
if (fromEnv) return fromEnv;
|
|
669
|
+
}
|
|
595
670
|
|
|
596
671
|
return 'main';
|
|
597
672
|
}
|
|
598
673
|
|
|
599
|
-
function runObserverCron(vaultPath, agentId, options = {}) {
|
|
674
|
+
function runObserverCron(vaultPath, agentId, pluginConfig, options = {}) {
|
|
600
675
|
const args = ['observe', '--cron', '--agent', agentId, '-v', vaultPath];
|
|
601
676
|
if (Number.isFinite(options.minNewBytes) && Number(options.minNewBytes) > 0) {
|
|
602
677
|
args.push('--min-new', String(Math.floor(Number(options.minNewBytes))));
|
|
603
678
|
}
|
|
604
679
|
|
|
605
|
-
const result = runClawvault(args, { timeoutMs: 120000 });
|
|
680
|
+
const result = runClawvault(args, pluginConfig, { timeoutMs: 120000 });
|
|
681
|
+
if (result.skipped) {
|
|
682
|
+
console.log('[clawvault] Observer cron skipped: allowClawvaultExec is disabled');
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
606
685
|
if (!result.success) {
|
|
607
686
|
console.warn(`[clawvault] Observer cron failed (${options.reason || 'unknown reason'})`);
|
|
608
687
|
return false;
|
|
@@ -1312,7 +1391,12 @@ function isSundayMidnightUtc(date) {
|
|
|
1312
1391
|
}
|
|
1313
1392
|
|
|
1314
1393
|
async function handleWeeklyReflect(event) {
|
|
1315
|
-
const
|
|
1394
|
+
const pluginConfig = extractPluginConfig(event);
|
|
1395
|
+
if (!isOptInEnabled(pluginConfig, 'enableWeeklyReflection', 'weeklyReflection')) {
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const vaultPath = findVaultPath(event, pluginConfig);
|
|
1316
1400
|
if (!vaultPath) {
|
|
1317
1401
|
console.log('[clawvault] No vault found, skipping weekly reflection');
|
|
1318
1402
|
return;
|
|
@@ -1324,7 +1408,11 @@ async function handleWeeklyReflect(event) {
|
|
|
1324
1408
|
return;
|
|
1325
1409
|
}
|
|
1326
1410
|
|
|
1327
|
-
const result = runClawvault(['reflect', '-v', vaultPath], { timeoutMs: 120000 });
|
|
1411
|
+
const result = runClawvault(['reflect', '-v', vaultPath], pluginConfig, { timeoutMs: 120000 });
|
|
1412
|
+
if (result.skipped) {
|
|
1413
|
+
console.log('[clawvault] Weekly reflection skipped: allowClawvaultExec is disabled');
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1328
1416
|
if (!result.success) {
|
|
1329
1417
|
console.warn('[clawvault] Weekly reflection failed');
|
|
1330
1418
|
return;
|
|
@@ -1334,7 +1422,12 @@ async function handleWeeklyReflect(event) {
|
|
|
1334
1422
|
|
|
1335
1423
|
// Handle gateway startup - check for context death
|
|
1336
1424
|
async function handleStartup(event) {
|
|
1337
|
-
const
|
|
1425
|
+
const pluginConfig = extractPluginConfig(event);
|
|
1426
|
+
if (!isOptInEnabled(pluginConfig, 'enableStartupRecovery')) {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
const vaultPath = findVaultPath(event, pluginConfig);
|
|
1338
1431
|
if (!vaultPath) {
|
|
1339
1432
|
console.log('[clawvault] No vault found, skipping recovery check');
|
|
1340
1433
|
return;
|
|
@@ -1343,7 +1436,11 @@ async function handleStartup(event) {
|
|
|
1343
1436
|
console.log(`[clawvault] Checking for context death`);
|
|
1344
1437
|
|
|
1345
1438
|
// Pass vault path as separate argument (not interpolated)
|
|
1346
|
-
const result = runClawvault(['recover', '--clear', '-v', vaultPath]);
|
|
1439
|
+
const result = runClawvault(['recover', '--clear', '-v', vaultPath], pluginConfig);
|
|
1440
|
+
if (result.skipped) {
|
|
1441
|
+
console.log('[clawvault] Recovery check skipped: allowClawvaultExec is disabled');
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1347
1444
|
|
|
1348
1445
|
if (!result.success) {
|
|
1349
1446
|
console.warn('[clawvault] Recovery check failed');
|
|
@@ -1373,7 +1470,15 @@ async function handleStartup(event) {
|
|
|
1373
1470
|
|
|
1374
1471
|
// Handle /new command - auto-checkpoint before reset
|
|
1375
1472
|
async function handleNew(event) {
|
|
1376
|
-
const
|
|
1473
|
+
const pluginConfig = extractPluginConfig(event);
|
|
1474
|
+
const autoCheckpointEnabled = isOptInEnabled(pluginConfig, 'enableAutoCheckpoint', 'autoCheckpoint');
|
|
1475
|
+
const observerOnNewEnabled = isOptInEnabled(pluginConfig, 'enableObserveOnNew');
|
|
1476
|
+
const factExtractionEnabled = isOptInEnabled(pluginConfig, 'enableFactExtraction');
|
|
1477
|
+
if (!autoCheckpointEnabled && !observerOnNewEnabled && !factExtractionEnabled) {
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
const vaultPath = findVaultPath(event, pluginConfig);
|
|
1377
1482
|
if (!vaultPath) {
|
|
1378
1483
|
console.log('[clawvault] No vault found, skipping auto-checkpoint');
|
|
1379
1484
|
return;
|
|
@@ -1387,33 +1492,44 @@ async function handleNew(event) {
|
|
|
1387
1492
|
? event.context.commandSource.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 50)
|
|
1388
1493
|
: 'cli';
|
|
1389
1494
|
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
]);
|
|
1495
|
+
if (autoCheckpointEnabled) {
|
|
1496
|
+
console.log('[clawvault] Auto-checkpoint before /new');
|
|
1497
|
+
const result = runClawvault([
|
|
1498
|
+
'checkpoint',
|
|
1499
|
+
'--working-on', `Session reset via /new from ${source}`,
|
|
1500
|
+
'--focus', `Pre-reset checkpoint, session: ${sessionKey}`,
|
|
1501
|
+
'-v', vaultPath
|
|
1502
|
+
], pluginConfig);
|
|
1399
1503
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1504
|
+
if (result.skipped) {
|
|
1505
|
+
console.log('[clawvault] Auto-checkpoint skipped: allowClawvaultExec is disabled');
|
|
1506
|
+
} else if (result.success) {
|
|
1507
|
+
console.log('[clawvault] Auto-checkpoint created');
|
|
1508
|
+
} else {
|
|
1509
|
+
console.warn('[clawvault] Auto-checkpoint failed');
|
|
1510
|
+
}
|
|
1404
1511
|
}
|
|
1405
1512
|
|
|
1406
|
-
const agentId = resolveAgentIdForEvent(event);
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1513
|
+
const agentId = resolveAgentIdForEvent(event, pluginConfig);
|
|
1514
|
+
if (observerOnNewEnabled) {
|
|
1515
|
+
runObserverCron(vaultPath, agentId, pluginConfig, {
|
|
1516
|
+
minNewBytes: 1,
|
|
1517
|
+
reason: 'command:new flush'
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
if (factExtractionEnabled) {
|
|
1521
|
+
runFactExtractionForEvent(vaultPath, event, 'command:new');
|
|
1522
|
+
}
|
|
1412
1523
|
}
|
|
1413
1524
|
|
|
1414
1525
|
// Handle session start - inject dynamic context for first prompt
|
|
1415
1526
|
async function handleSessionStart(event) {
|
|
1416
|
-
const
|
|
1527
|
+
const pluginConfig = extractPluginConfig(event);
|
|
1528
|
+
if (!isOptInEnabled(pluginConfig, 'enableSessionContextInjection')) {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
const vaultPath = findVaultPath(event, pluginConfig);
|
|
1417
1533
|
if (!vaultPath) {
|
|
1418
1534
|
console.log('[clawvault] No vault found, skipping context injection');
|
|
1419
1535
|
return;
|
|
@@ -1432,11 +1548,14 @@ async function handleSessionStart(event) {
|
|
|
1432
1548
|
recapArgs.push('--agent', agentId);
|
|
1433
1549
|
}
|
|
1434
1550
|
|
|
1435
|
-
const recapResult = runClawvault(recapArgs);
|
|
1436
|
-
if (
|
|
1437
|
-
console.
|
|
1438
|
-
}
|
|
1551
|
+
const recapResult = runClawvault(recapArgs, pluginConfig);
|
|
1552
|
+
if (recapResult.skipped) {
|
|
1553
|
+
console.log('[clawvault] Session recap skipped: allowClawvaultExec is disabled');
|
|
1554
|
+
}
|
|
1555
|
+
if (recapResult.success) {
|
|
1439
1556
|
recapEntries = parseSessionRecapJson(recapResult.output);
|
|
1557
|
+
} else if (!recapResult.skipped) {
|
|
1558
|
+
console.warn('[clawvault] Session recap lookup failed');
|
|
1440
1559
|
}
|
|
1441
1560
|
} else {
|
|
1442
1561
|
console.log('[clawvault] No session key found, skipping session recap');
|
|
@@ -1450,12 +1569,14 @@ async function handleSessionStart(event) {
|
|
|
1450
1569
|
'--format', 'json',
|
|
1451
1570
|
'--profile', 'auto',
|
|
1452
1571
|
'-v', vaultPath
|
|
1453
|
-
]);
|
|
1572
|
+
], pluginConfig);
|
|
1454
1573
|
|
|
1455
|
-
if (
|
|
1456
|
-
console.warn('[clawvault] Context lookup failed');
|
|
1457
|
-
} else {
|
|
1574
|
+
if (contextResult.success) {
|
|
1458
1575
|
memoryEntries = parseContextJson(contextResult.output);
|
|
1576
|
+
} else if (contextResult.skipped) {
|
|
1577
|
+
console.log('[clawvault] Context lookup skipped: allowClawvaultExec is disabled');
|
|
1578
|
+
} else {
|
|
1579
|
+
console.warn('[clawvault] Context lookup failed');
|
|
1459
1580
|
}
|
|
1460
1581
|
} else {
|
|
1461
1582
|
console.log('[clawvault] No initial prompt, skipping vault memory lookup');
|
|
@@ -1475,35 +1596,51 @@ async function handleSessionStart(event) {
|
|
|
1475
1596
|
|
|
1476
1597
|
// Handle heartbeat events - cheap stat-based trigger for active observation
|
|
1477
1598
|
async function handleHeartbeat(event) {
|
|
1478
|
-
const
|
|
1599
|
+
const pluginConfig = extractPluginConfig(event);
|
|
1600
|
+
if (!isOptInEnabled(pluginConfig, 'enableHeartbeatObservation', 'observeOnHeartbeat')) {
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
const vaultPath = findVaultPath(event, pluginConfig);
|
|
1479
1605
|
if (!vaultPath) {
|
|
1480
1606
|
console.log('[clawvault] No vault found, skipping heartbeat observation check');
|
|
1481
1607
|
return;
|
|
1482
1608
|
}
|
|
1483
1609
|
|
|
1484
|
-
const agentId = resolveAgentIdForEvent(event);
|
|
1485
|
-
if (!shouldObserveActiveSessions(vaultPath, agentId)) {
|
|
1610
|
+
const agentId = resolveAgentIdForEvent(event, pluginConfig);
|
|
1611
|
+
if (!shouldObserveActiveSessions(vaultPath, agentId, pluginConfig)) {
|
|
1486
1612
|
console.log('[clawvault] Heartbeat: no sessions crossed active-observe threshold');
|
|
1487
1613
|
return;
|
|
1488
1614
|
}
|
|
1489
1615
|
|
|
1490
|
-
runObserverCron(vaultPath, agentId, { reason: 'heartbeat threshold crossed' });
|
|
1616
|
+
runObserverCron(vaultPath, agentId, pluginConfig, { reason: 'heartbeat threshold crossed' });
|
|
1491
1617
|
}
|
|
1492
1618
|
|
|
1493
1619
|
// Handle context compaction - force flush any pending session deltas
|
|
1494
1620
|
async function handleContextCompaction(event) {
|
|
1495
|
-
const
|
|
1621
|
+
const pluginConfig = extractPluginConfig(event);
|
|
1622
|
+
const compactionObserveEnabled = isOptInEnabled(pluginConfig, 'enableCompactionObservation');
|
|
1623
|
+
const factExtractionEnabled = isOptInEnabled(pluginConfig, 'enableFactExtraction');
|
|
1624
|
+
if (!compactionObserveEnabled && !factExtractionEnabled) {
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
const vaultPath = findVaultPath(event, pluginConfig);
|
|
1496
1629
|
if (!vaultPath) {
|
|
1497
1630
|
console.log('[clawvault] No vault found, skipping compaction observation');
|
|
1498
1631
|
return;
|
|
1499
1632
|
}
|
|
1500
1633
|
|
|
1501
|
-
const agentId = resolveAgentIdForEvent(event);
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1634
|
+
const agentId = resolveAgentIdForEvent(event, pluginConfig);
|
|
1635
|
+
if (compactionObserveEnabled) {
|
|
1636
|
+
runObserverCron(vaultPath, agentId, pluginConfig, {
|
|
1637
|
+
minNewBytes: 1,
|
|
1638
|
+
reason: 'context compaction'
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
if (factExtractionEnabled) {
|
|
1642
|
+
runFactExtractionForEvent(vaultPath, event, 'compaction:memoryFlush');
|
|
1643
|
+
}
|
|
1507
1644
|
}
|
|
1508
1645
|
|
|
1509
1646
|
// Main handler - route events
|