clawmem 0.1.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/AGENTS.md +660 -0
- package/CLAUDE.md +660 -0
- package/LICENSE +21 -0
- package/README.md +993 -0
- package/SKILL.md +717 -0
- package/bin/clawmem +75 -0
- package/package.json +72 -0
- package/src/amem.ts +797 -0
- package/src/beads.ts +263 -0
- package/src/clawmem.ts +1849 -0
- package/src/collections.ts +405 -0
- package/src/config.ts +178 -0
- package/src/consolidation.ts +123 -0
- package/src/directory-context.ts +248 -0
- package/src/errors.ts +41 -0
- package/src/formatter.ts +427 -0
- package/src/graph-traversal.ts +247 -0
- package/src/hooks/context-surfacing.ts +317 -0
- package/src/hooks/curator-nudge.ts +89 -0
- package/src/hooks/decision-extractor.ts +639 -0
- package/src/hooks/feedback-loop.ts +214 -0
- package/src/hooks/handoff-generator.ts +345 -0
- package/src/hooks/postcompact-inject.ts +226 -0
- package/src/hooks/precompact-extract.ts +314 -0
- package/src/hooks/pretool-inject.ts +79 -0
- package/src/hooks/session-bootstrap.ts +324 -0
- package/src/hooks/staleness-check.ts +130 -0
- package/src/hooks.ts +367 -0
- package/src/indexer.ts +327 -0
- package/src/intent.ts +294 -0
- package/src/limits.ts +26 -0
- package/src/llm.ts +1175 -0
- package/src/mcp.ts +2138 -0
- package/src/memory.ts +336 -0
- package/src/mmr.ts +93 -0
- package/src/observer.ts +269 -0
- package/src/openclaw/engine.ts +283 -0
- package/src/openclaw/index.ts +221 -0
- package/src/openclaw/plugin.json +83 -0
- package/src/openclaw/shell.ts +207 -0
- package/src/openclaw/tools.ts +304 -0
- package/src/profile.ts +346 -0
- package/src/promptguard.ts +218 -0
- package/src/retrieval-gate.ts +106 -0
- package/src/search-utils.ts +127 -0
- package/src/server.ts +783 -0
- package/src/splitter.ts +325 -0
- package/src/store.ts +4062 -0
- package/src/validation.ts +67 -0
- package/src/watcher.ts +58 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight input validation helpers for module boundaries.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ClawMemError } from "./errors.ts";
|
|
6
|
+
import { MAX_PATH_LENGTH } from "./limits.ts";
|
|
7
|
+
|
|
8
|
+
export function assertNonEmptyString(value: unknown, name: string): asserts value is string {
|
|
9
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
10
|
+
throw new ClawMemError("INVALID_INPUT", `${name} must be a non-empty string`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function assertMaxLength(value: string, max: number, name: string): void {
|
|
15
|
+
if (value.length > max) {
|
|
16
|
+
throw new ClawMemError("INPUT_TOO_LONG", `${name} exceeds max length ${max} (got ${value.length})`, {
|
|
17
|
+
max,
|
|
18
|
+
actual: value.length,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function assertFiniteNumber(value: unknown, name: string): asserts value is number {
|
|
24
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
25
|
+
throw new ClawMemError("INVALID_NUMBER", `${name} must be a finite number (got ${value})`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function assertBounds(value: number, min: number, max: number, name: string): void {
|
|
30
|
+
if (!Number.isFinite(value) || value < min || value > max) {
|
|
31
|
+
throw new ClawMemError("OUT_OF_BOUNDS", `${name} must be between ${min} and ${max} (got ${value})`, {
|
|
32
|
+
min,
|
|
33
|
+
max,
|
|
34
|
+
actual: value,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function assertArrayLengthMatch(a: unknown[], b: unknown[], nameA: string, nameB: string): void {
|
|
40
|
+
if (a.length !== b.length) {
|
|
41
|
+
throw new ClawMemError(
|
|
42
|
+
"LENGTH_MISMATCH",
|
|
43
|
+
`${nameA} length (${a.length}) must match ${nameB} length (${b.length})`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function assertSafePath(filepath: string, name: string = "path"): void {
|
|
49
|
+
if (filepath.length > MAX_PATH_LENGTH) {
|
|
50
|
+
throw new ClawMemError("PATH_TOO_LONG", `${name} exceeds max length ${MAX_PATH_LENGTH}`);
|
|
51
|
+
}
|
|
52
|
+
if (filepath.includes("\0")) {
|
|
53
|
+
throw new ClawMemError("INVALID_PATH", `${name} contains null bytes`);
|
|
54
|
+
}
|
|
55
|
+
// Normalize and check for traversal
|
|
56
|
+
const normalized = filepath.replace(/\\/g, "/");
|
|
57
|
+
const segments = normalized.split("/");
|
|
58
|
+
if (segments.some((s) => s === "..")) {
|
|
59
|
+
throw new ClawMemError("PATH_TRAVERSAL", `${name} contains path traversal (..)`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Clamp a number to bounds (for cases where clamping is the existing behavior). */
|
|
64
|
+
export function clampBounds(value: number, min: number, max: number): number {
|
|
65
|
+
if (!Number.isFinite(value)) return min;
|
|
66
|
+
return Math.max(min, Math.min(max, value));
|
|
67
|
+
}
|
package/src/watcher.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawMem File Watcher - fs.watch with debounce for incremental reindex
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { watch, type WatchEventType } from "fs";
|
|
6
|
+
import { shouldExclude } from "./indexer.ts";
|
|
7
|
+
|
|
8
|
+
export type WatcherOptions = {
|
|
9
|
+
debounceMs?: number;
|
|
10
|
+
onChanged: (path: string, event: WatchEventType) => Promise<void>;
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function startWatcher(
|
|
15
|
+
directories: string[],
|
|
16
|
+
options: WatcherOptions
|
|
17
|
+
): { close: () => void } {
|
|
18
|
+
const { debounceMs = 2000, onChanged, onError } = options;
|
|
19
|
+
const pending = new Map<string, ReturnType<typeof setTimeout>>();
|
|
20
|
+
const watchers: ReturnType<typeof watch>[] = [];
|
|
21
|
+
|
|
22
|
+
for (const dir of directories) {
|
|
23
|
+
try {
|
|
24
|
+
const watcher = watch(dir, { recursive: true }, (event, filename) => {
|
|
25
|
+
if (!filename) return;
|
|
26
|
+
if (!filename.endsWith(".md") && !filename.endsWith(".jsonl")) return;
|
|
27
|
+
if (shouldExclude(filename)) return;
|
|
28
|
+
|
|
29
|
+
const fullPath = `${dir}/${filename}`;
|
|
30
|
+
const existing = pending.get(fullPath);
|
|
31
|
+
if (existing) clearTimeout(existing);
|
|
32
|
+
|
|
33
|
+
pending.set(fullPath, setTimeout(async () => {
|
|
34
|
+
pending.delete(fullPath);
|
|
35
|
+
try {
|
|
36
|
+
await onChanged(fullPath, event);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
39
|
+
}
|
|
40
|
+
}, debounceMs));
|
|
41
|
+
});
|
|
42
|
+
watcher.on("error", (err) => {
|
|
43
|
+
onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
44
|
+
});
|
|
45
|
+
watchers.push(watcher);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
onError?.(err instanceof Error ? err : new Error(`Failed to watch ${dir}: ${err}`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
close: () => {
|
|
53
|
+
for (const w of watchers) w.close();
|
|
54
|
+
for (const t of pending.values()) clearTimeout(t);
|
|
55
|
+
pending.clear();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|