@psiclawops/hypermem 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.
Files changed (94) hide show
  1. package/ARCHITECTURE.md +296 -0
  2. package/LICENSE +190 -0
  3. package/README.md +243 -0
  4. package/dist/background-indexer.d.ts +117 -0
  5. package/dist/background-indexer.d.ts.map +1 -0
  6. package/dist/background-indexer.js +732 -0
  7. package/dist/compaction-fence.d.ts +89 -0
  8. package/dist/compaction-fence.d.ts.map +1 -0
  9. package/dist/compaction-fence.js +153 -0
  10. package/dist/compositor.d.ts +139 -0
  11. package/dist/compositor.d.ts.map +1 -0
  12. package/dist/compositor.js +1109 -0
  13. package/dist/cross-agent.d.ts +57 -0
  14. package/dist/cross-agent.d.ts.map +1 -0
  15. package/dist/cross-agent.js +254 -0
  16. package/dist/db.d.ts +131 -0
  17. package/dist/db.d.ts.map +1 -0
  18. package/dist/db.js +398 -0
  19. package/dist/desired-state-store.d.ts +100 -0
  20. package/dist/desired-state-store.d.ts.map +1 -0
  21. package/dist/desired-state-store.js +212 -0
  22. package/dist/doc-chunk-store.d.ts +115 -0
  23. package/dist/doc-chunk-store.d.ts.map +1 -0
  24. package/dist/doc-chunk-store.js +278 -0
  25. package/dist/doc-chunker.d.ts +99 -0
  26. package/dist/doc-chunker.d.ts.map +1 -0
  27. package/dist/doc-chunker.js +324 -0
  28. package/dist/episode-store.d.ts +48 -0
  29. package/dist/episode-store.d.ts.map +1 -0
  30. package/dist/episode-store.js +135 -0
  31. package/dist/fact-store.d.ts +57 -0
  32. package/dist/fact-store.d.ts.map +1 -0
  33. package/dist/fact-store.js +175 -0
  34. package/dist/fleet-store.d.ts +144 -0
  35. package/dist/fleet-store.d.ts.map +1 -0
  36. package/dist/fleet-store.js +276 -0
  37. package/dist/hybrid-retrieval.d.ts +60 -0
  38. package/dist/hybrid-retrieval.d.ts.map +1 -0
  39. package/dist/hybrid-retrieval.js +340 -0
  40. package/dist/index.d.ts +611 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +1042 -0
  43. package/dist/knowledge-graph.d.ts +110 -0
  44. package/dist/knowledge-graph.d.ts.map +1 -0
  45. package/dist/knowledge-graph.js +305 -0
  46. package/dist/knowledge-store.d.ts +72 -0
  47. package/dist/knowledge-store.d.ts.map +1 -0
  48. package/dist/knowledge-store.js +241 -0
  49. package/dist/library-schema.d.ts +22 -0
  50. package/dist/library-schema.d.ts.map +1 -0
  51. package/dist/library-schema.js +717 -0
  52. package/dist/message-store.d.ts +76 -0
  53. package/dist/message-store.d.ts.map +1 -0
  54. package/dist/message-store.js +273 -0
  55. package/dist/preference-store.d.ts +54 -0
  56. package/dist/preference-store.d.ts.map +1 -0
  57. package/dist/preference-store.js +109 -0
  58. package/dist/preservation-gate.d.ts +82 -0
  59. package/dist/preservation-gate.d.ts.map +1 -0
  60. package/dist/preservation-gate.js +150 -0
  61. package/dist/provider-translator.d.ts +40 -0
  62. package/dist/provider-translator.d.ts.map +1 -0
  63. package/dist/provider-translator.js +349 -0
  64. package/dist/rate-limiter.d.ts +76 -0
  65. package/dist/rate-limiter.d.ts.map +1 -0
  66. package/dist/rate-limiter.js +179 -0
  67. package/dist/redis.d.ts +188 -0
  68. package/dist/redis.d.ts.map +1 -0
  69. package/dist/redis.js +534 -0
  70. package/dist/schema.d.ts +15 -0
  71. package/dist/schema.d.ts.map +1 -0
  72. package/dist/schema.js +203 -0
  73. package/dist/secret-scanner.d.ts +51 -0
  74. package/dist/secret-scanner.d.ts.map +1 -0
  75. package/dist/secret-scanner.js +248 -0
  76. package/dist/seed.d.ts +108 -0
  77. package/dist/seed.d.ts.map +1 -0
  78. package/dist/seed.js +177 -0
  79. package/dist/system-store.d.ts +73 -0
  80. package/dist/system-store.d.ts.map +1 -0
  81. package/dist/system-store.js +182 -0
  82. package/dist/topic-store.d.ts +45 -0
  83. package/dist/topic-store.d.ts.map +1 -0
  84. package/dist/topic-store.js +136 -0
  85. package/dist/types.d.ts +329 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +9 -0
  88. package/dist/vector-store.d.ts +132 -0
  89. package/dist/vector-store.d.ts.map +1 -0
  90. package/dist/vector-store.js +498 -0
  91. package/dist/work-store.d.ts +112 -0
  92. package/dist/work-store.d.ts.map +1 -0
  93. package/dist/work-store.js +273 -0
  94. package/package.json +57 -0
package/dist/schema.js ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * HyperMem Agent Message Schema
3
+ *
4
+ * Per-agent database: ~/.openclaw/hypermem/agents/{agentId}/messages.db
5
+ * Write-heavy, temporal, rotatable.
6
+ * Contains ONLY conversation data — structured knowledge lives in library.db.
7
+ */
8
+ export const LATEST_SCHEMA_VERSION = 5;
9
+ function nowIso() {
10
+ return new Date().toISOString();
11
+ }
12
+ /**
13
+ * V1–V3: Legacy schema (monolithic agent DB with facts/knowledge/episodes).
14
+ * Kept for migration detection — if we open an old DB, we know what version it is.
15
+ */
16
+ /**
17
+ * V4: Messages-only schema.
18
+ * Facts, knowledge, episodes, topics moved to library.db.
19
+ * Agent DB now contains only conversations, messages, summaries, and agent metadata.
20
+ */
21
+ function applyV4MessagesOnly(db) {
22
+ // -- Agent metadata (kept here for self-identification) --
23
+ db.exec(`
24
+ CREATE TABLE IF NOT EXISTS agent_meta (
25
+ id TEXT PRIMARY KEY,
26
+ display_name TEXT,
27
+ tier TEXT,
28
+ org TEXT,
29
+ config TEXT,
30
+ created_at TEXT NOT NULL,
31
+ updated_at TEXT NOT NULL
32
+ )
33
+ `);
34
+ // -- Conversations (sessions) --
35
+ db.exec(`
36
+ CREATE TABLE IF NOT EXISTS conversations (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ session_key TEXT NOT NULL UNIQUE,
39
+ session_id TEXT,
40
+ agent_id TEXT NOT NULL,
41
+ channel_type TEXT NOT NULL,
42
+ channel_id TEXT,
43
+ provider TEXT,
44
+ model TEXT,
45
+ status TEXT DEFAULT 'active',
46
+ message_count INTEGER DEFAULT 0,
47
+ token_count_in INTEGER DEFAULT 0,
48
+ token_count_out INTEGER DEFAULT 0,
49
+ created_at TEXT NOT NULL,
50
+ updated_at TEXT NOT NULL,
51
+ ended_at TEXT
52
+ )
53
+ `);
54
+ db.exec('CREATE INDEX IF NOT EXISTS idx_conv_agent ON conversations(agent_id, status, updated_at DESC)');
55
+ db.exec('CREATE INDEX IF NOT EXISTS idx_conv_channel ON conversations(agent_id, channel_type, channel_id)');
56
+ // -- Messages (provider-neutral, structured) --
57
+ db.exec(`
58
+ CREATE TABLE IF NOT EXISTS messages (
59
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
60
+ conversation_id INTEGER NOT NULL REFERENCES conversations(id),
61
+ agent_id TEXT NOT NULL,
62
+ role TEXT NOT NULL,
63
+ text_content TEXT,
64
+ tool_calls TEXT,
65
+ tool_results TEXT,
66
+ metadata TEXT,
67
+ token_count INTEGER,
68
+ message_index INTEGER NOT NULL,
69
+ is_heartbeat INTEGER DEFAULT 0,
70
+ created_at TEXT NOT NULL
71
+ )
72
+ `);
73
+ db.exec('CREATE INDEX IF NOT EXISTS idx_msg_conv ON messages(conversation_id, message_index)');
74
+ db.exec('CREATE INDEX IF NOT EXISTS idx_msg_agent_time ON messages(agent_id, created_at DESC)');
75
+ // -- FTS5 for message full-text search --
76
+ db.exec(`
77
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
78
+ text_content,
79
+ content='messages',
80
+ content_rowid='id'
81
+ )
82
+ `);
83
+ db.exec(`
84
+ CREATE TRIGGER IF NOT EXISTS msg_fts_ai AFTER INSERT ON messages BEGIN
85
+ INSERT INTO messages_fts(rowid, text_content) VALUES (new.id, new.text_content);
86
+ END
87
+ `);
88
+ db.exec(`
89
+ CREATE TRIGGER IF NOT EXISTS msg_fts_ad AFTER DELETE ON messages BEGIN
90
+ INSERT INTO messages_fts(messages_fts, rowid, text_content) VALUES('delete', old.id, old.text_content);
91
+ END
92
+ `);
93
+ db.exec(`
94
+ CREATE TRIGGER IF NOT EXISTS msg_fts_au AFTER UPDATE ON messages BEGIN
95
+ INSERT INTO messages_fts(messages_fts, rowid, text_content) VALUES('delete', old.id, old.text_content);
96
+ INSERT INTO messages_fts(rowid, text_content) VALUES (new.id, new.text_content);
97
+ END
98
+ `);
99
+ // -- Summaries (hierarchical compaction) --
100
+ db.exec(`
101
+ CREATE TABLE IF NOT EXISTS summaries (
102
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
103
+ conversation_id INTEGER NOT NULL REFERENCES conversations(id),
104
+ agent_id TEXT NOT NULL,
105
+ depth INTEGER NOT NULL DEFAULT 0,
106
+ content TEXT NOT NULL,
107
+ token_count INTEGER,
108
+ created_at TEXT NOT NULL,
109
+ updated_at TEXT NOT NULL
110
+ )
111
+ `);
112
+ db.exec('CREATE INDEX IF NOT EXISTS idx_summaries_conv ON summaries(conversation_id, depth)');
113
+ db.exec(`
114
+ CREATE TABLE IF NOT EXISTS summary_messages (
115
+ summary_id INTEGER NOT NULL REFERENCES summaries(id),
116
+ message_id INTEGER NOT NULL REFERENCES messages(id),
117
+ PRIMARY KEY (summary_id, message_id)
118
+ )
119
+ `);
120
+ db.exec(`
121
+ CREATE TABLE IF NOT EXISTS summary_parents (
122
+ parent_summary_id INTEGER NOT NULL REFERENCES summaries(id),
123
+ child_summary_id INTEGER NOT NULL REFERENCES summaries(id),
124
+ PRIMARY KEY (parent_summary_id, child_summary_id)
125
+ )
126
+ `);
127
+ // -- Index events (for tracking what's been vectorized) --
128
+ db.exec(`
129
+ CREATE TABLE IF NOT EXISTS index_events (
130
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
131
+ agent_id TEXT NOT NULL,
132
+ event_type TEXT NOT NULL,
133
+ target_table TEXT,
134
+ target_id INTEGER,
135
+ details TEXT,
136
+ created_at TEXT NOT NULL
137
+ )
138
+ `);
139
+ db.exec('CREATE INDEX IF NOT EXISTS idx_index_events ON index_events(agent_id, created_at DESC)');
140
+ }
141
+ /**
142
+ * Run migrations on an agent message database.
143
+ */
144
+ export function migrate(db) {
145
+ db.exec(`
146
+ CREATE TABLE IF NOT EXISTS schema_version (
147
+ version INTEGER PRIMARY KEY,
148
+ applied_at TEXT NOT NULL
149
+ )
150
+ `);
151
+ const row = db
152
+ .prepare('SELECT MAX(version) AS version FROM schema_version')
153
+ .get();
154
+ const currentVersion = typeof row?.version === 'number' ? row.version : 0;
155
+ if (currentVersion > LATEST_SCHEMA_VERSION) {
156
+ console.warn(`[hypermem] Database schema version (${currentVersion}) is newer than this engine (${LATEST_SCHEMA_VERSION}).`);
157
+ return;
158
+ }
159
+ // For fresh DBs (version 0), jump straight to v4 (messages-only).
160
+ // For existing DBs (v1–v3), we skip the old schema — those tables will be
161
+ // left in place but unused. Data migration to library is a separate step.
162
+ if (currentVersion < 4) {
163
+ if (currentVersion === 0) {
164
+ // Fresh DB — create messages-only schema directly
165
+ applyV4MessagesOnly(db);
166
+ }
167
+ else {
168
+ // Existing DB with old schema (v1–v3).
169
+ // The old tables (facts, knowledge, episodes, topics, agents) remain
170
+ // but are no longer written to. New tables get created alongside.
171
+ applyV4MessagesOnly(db);
172
+ // Note: old tables like 'facts', 'knowledge', etc. are left in place
173
+ // for data migration. A separate migration tool will move them to library.db.
174
+ }
175
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)')
176
+ .run(4, nowIso());
177
+ }
178
+ // v4 → v5: add cursor columns to conversations table for dual-write durability
179
+ if (currentVersion < 5) {
180
+ // ALTER TABLE ADD COLUMN is safe on existing rows — all default to NULL
181
+ const cols = db.prepare('PRAGMA table_info(conversations)').all()
182
+ .map(r => r.name);
183
+ if (!cols.includes('cursor_last_sent_id')) {
184
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_last_sent_id INTEGER');
185
+ }
186
+ if (!cols.includes('cursor_last_sent_index')) {
187
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_last_sent_index INTEGER');
188
+ }
189
+ if (!cols.includes('cursor_last_sent_at')) {
190
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_last_sent_at TEXT');
191
+ }
192
+ if (!cols.includes('cursor_window_size')) {
193
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_window_size INTEGER');
194
+ }
195
+ if (!cols.includes('cursor_token_count')) {
196
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_token_count INTEGER');
197
+ }
198
+ db.prepare('INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (?, ?)')
199
+ .run(5, nowIso());
200
+ }
201
+ }
202
+ export { LATEST_SCHEMA_VERSION as SCHEMA_VERSION };
203
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * HyperMem Secret Scanner
3
+ *
4
+ * Lightweight regex-based gate to prevent secrets from leaking into
5
+ * shared memory (visibility >= 'org'). Runs before any write that
6
+ * promotes content to org/council/fleet visibility.
7
+ *
8
+ * Design principles:
9
+ * - Fast: regex-only, no LLM dependency
10
+ * - Conservative: false positives are okay; false negatives are not
11
+ * - Auditable: each hit includes the rule name and matched region (redacted)
12
+ * - Not a full DLP system: blocks the obvious leaks; does not guarantee clean content
13
+ *
14
+ * Patterns sourced from:
15
+ * - gitleaks ruleset (https://github.com/gitleaks/gitleaks)
16
+ * - trufflehog common patterns
17
+ * - Manual additions for OpenClaw-specific secrets
18
+ */
19
+ export interface ScanResult {
20
+ clean: boolean;
21
+ hits: ScanHit[];
22
+ }
23
+ export interface ScanHit {
24
+ rule: string;
25
+ description: string;
26
+ /** Redacted match — only first/last 3 chars of the matched value */
27
+ redactedMatch: string;
28
+ /** Character offset of the match start */
29
+ offset: number;
30
+ }
31
+ /**
32
+ * Scan content for secrets before promoting to shared visibility.
33
+ *
34
+ * Returns { clean: true } when no secrets found.
35
+ * Returns { clean: false, hits } when secrets are detected.
36
+ *
37
+ * Callers must NOT write content with visibility >= 'org' when clean is false.
38
+ */
39
+ export declare function scanForSecrets(content: string): ScanResult;
40
+ /**
41
+ * Returns true when content is safe to promote to shared visibility (>= 'org').
42
+ * Convenience wrapper around scanForSecrets.
43
+ */
44
+ export declare function isSafeForSharedVisibility(content: string): boolean;
45
+ /**
46
+ * Check whether a visibility level requires secret scanning.
47
+ * Scanning is required for 'org', 'council', and 'fleet'.
48
+ * 'private' content stays with the owner — no cross-agent risk.
49
+ */
50
+ export declare function requiresScan(visibility: string): boolean;
51
+ //# sourceMappingURL=secret-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-scanner.d.ts","sourceRoot":"","sources":["../src/secret-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB;AAmMD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CA+B1D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAExD"}
@@ -0,0 +1,248 @@
1
+ /**
2
+ * HyperMem Secret Scanner
3
+ *
4
+ * Lightweight regex-based gate to prevent secrets from leaking into
5
+ * shared memory (visibility >= 'org'). Runs before any write that
6
+ * promotes content to org/council/fleet visibility.
7
+ *
8
+ * Design principles:
9
+ * - Fast: regex-only, no LLM dependency
10
+ * - Conservative: false positives are okay; false negatives are not
11
+ * - Auditable: each hit includes the rule name and matched region (redacted)
12
+ * - Not a full DLP system: blocks the obvious leaks; does not guarantee clean content
13
+ *
14
+ * Patterns sourced from:
15
+ * - gitleaks ruleset (https://github.com/gitleaks/gitleaks)
16
+ * - trufflehog common patterns
17
+ * - Manual additions for OpenClaw-specific secrets
18
+ */
19
+ const RULES = [
20
+ // ── API Keys ──────────────────────────────────────────────
21
+ {
22
+ id: 'anthropic-api-key',
23
+ description: 'Anthropic API key',
24
+ pattern: /\bsk-ant-[a-zA-Z0-9\-_]{20,}\b/g,
25
+ },
26
+ {
27
+ id: 'openai-api-key',
28
+ description: 'OpenAI API key',
29
+ pattern: /\bsk-[a-zA-Z0-9\-_]{20,}\b/g,
30
+ },
31
+ {
32
+ id: 'openai-org-id',
33
+ description: 'OpenAI organization ID',
34
+ pattern: /\borg-[a-zA-Z0-9]{16,}\b/g,
35
+ },
36
+ {
37
+ id: 'github-pat-classic',
38
+ description: 'GitHub personal access token (classic)',
39
+ pattern: /\bghp_[a-zA-Z0-9]{36}\b/g,
40
+ },
41
+ {
42
+ id: 'github-pat-fine',
43
+ description: 'GitHub fine-grained personal access token',
44
+ pattern: /\bgithub_pat_[a-zA-Z0-9_]{82}\b/g,
45
+ },
46
+ {
47
+ id: 'github-oauth-token',
48
+ description: 'GitHub OAuth token',
49
+ pattern: /\bgho_[a-zA-Z0-9]{36}\b/g,
50
+ },
51
+ {
52
+ id: 'github-app-token',
53
+ description: 'GitHub App installation token',
54
+ pattern: /\bghs_[a-zA-Z0-9]{36}\b/g,
55
+ },
56
+ {
57
+ id: 'github-refresh-token',
58
+ description: 'GitHub refresh token',
59
+ pattern: /\bghr_[a-zA-Z0-9]{76}\b/g,
60
+ },
61
+ {
62
+ id: 'aws-access-key',
63
+ description: 'AWS access key ID',
64
+ pattern: /\b(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\b/g,
65
+ },
66
+ {
67
+ id: 'aws-secret-key',
68
+ description: 'AWS secret access key (heuristic)',
69
+ pattern: /(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)["\s:=]+([A-Za-z0-9\/+]{40})\b/gi,
70
+ },
71
+ {
72
+ id: 'google-api-key',
73
+ description: 'Google API key',
74
+ pattern: /\bAIza[0-9A-Za-z_\-]{35}\b/g,
75
+ },
76
+ {
77
+ id: 'google-oauth',
78
+ description: 'Google OAuth client secret',
79
+ pattern: /\bGOCSPX-[0-9A-Za-z_\-]{28}\b/g,
80
+ },
81
+ {
82
+ id: 'slack-token',
83
+ description: 'Slack API token',
84
+ pattern: /\bxox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24,32}\b/g,
85
+ },
86
+ {
87
+ id: 'slack-webhook',
88
+ description: 'Slack incoming webhook URL',
89
+ pattern: /https:\/\/hooks\.slack\.com\/services\/[A-Z0-9]{9,11}\/[A-Z0-9]{9,11}\/[a-zA-Z0-9]{24}/g,
90
+ },
91
+ {
92
+ id: 'discord-token',
93
+ description: 'Discord bot token',
94
+ pattern: /\b[MNO][a-zA-Z0-9]{23}\.[a-zA-Z0-9_\-]{6}\.[a-zA-Z0-9_\-]{27}\b/g,
95
+ },
96
+ {
97
+ id: 'discord-webhook',
98
+ description: 'Discord webhook URL',
99
+ pattern: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]{17,19}\/[a-zA-Z0-9_\-]{68}/g,
100
+ },
101
+ {
102
+ id: 'stripe-key',
103
+ description: 'Stripe API key',
104
+ pattern: /\b(?:sk|pk|rk)_(?:live|test)_[a-zA-Z0-9]{24,99}\b/g,
105
+ },
106
+ {
107
+ id: 'sendgrid-key',
108
+ description: 'SendGrid API key',
109
+ pattern: /\bSG\.[a-zA-Z0-9\-_]{22,}\.[a-zA-Z0-9\-_]{43,}\b/g,
110
+ },
111
+ {
112
+ id: 'twilio-account-sid',
113
+ description: 'Twilio Account SID',
114
+ pattern: /\bAC[0-9a-fA-F]{32}\b/g,
115
+ },
116
+ {
117
+ id: 'twilio-auth-token',
118
+ description: 'Twilio Auth Token (heuristic)',
119
+ pattern: /(?:twilio[_\s\-]*(?:auth[_\s\-]*)?token)["\s:=]+([0-9a-f]{32})\b/gi,
120
+ },
121
+ // ── Private Keys / Certificates ───────────────────────────
122
+ {
123
+ id: 'pem-private-key',
124
+ description: 'PEM private key block',
125
+ pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY-----/g,
126
+ },
127
+ {
128
+ id: 'pem-certificate',
129
+ description: 'PEM certificate (may contain private data)',
130
+ pattern: /-----BEGIN CERTIFICATE-----/g,
131
+ },
132
+ // ── Passwords in common config patterns ───────────────────
133
+ {
134
+ id: 'password-assignment',
135
+ description: 'Password in config-like assignment',
136
+ // Matches: password=<value>, "password": "<value>", PASSWORD=<value>
137
+ pattern: /(?:^|[,{;\n])\s*(?:password|passwd|secret|token|api_key|apikey|api-key|access_token|auth_token|client_secret)\s*[=:]\s*["']([^"'\n]{8,})["']/gim,
138
+ minEntropy: 2.5,
139
+ },
140
+ // ── Database connection strings ────────────────────────────
141
+ {
142
+ id: 'db-connection-string',
143
+ description: 'Database connection string with credentials',
144
+ pattern: /(?:postgres|postgresql|mysql|mongodb|redis):\/\/[^:]+:[^@]{6,}@/gi,
145
+ },
146
+ // ── Bearer tokens ─────────────────────────────────────────
147
+ {
148
+ id: 'bearer-token-header',
149
+ description: 'Bearer token in Authorization header value',
150
+ pattern: /Authorization[:\s]+Bearer\s+([a-zA-Z0-9\-_=.]{20,})/gi,
151
+ },
152
+ // ── JWT ───────────────────────────────────────────────────
153
+ {
154
+ id: 'jwt-token',
155
+ description: 'JSON Web Token',
156
+ pattern: /\beyJ[a-zA-Z0-9\-_]+\.eyJ[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\b/g,
157
+ minEntropy: 3.0,
158
+ },
159
+ // ── OpenClaw-specific ──────────────────────────────────────
160
+ {
161
+ id: 'openclaw-api-key',
162
+ description: 'OpenClaw API key',
163
+ pattern: /\boc_[a-zA-Z0-9\-_]{24,}\b/g,
164
+ },
165
+ ];
166
+ // ─── Entropy Calculation ─────────────────────────────────────────
167
+ /**
168
+ * Calculate Shannon entropy of a string (bits per character, base 2).
169
+ * Used to filter out low-entropy false positives like "password=example".
170
+ */
171
+ function shannonEntropy(s) {
172
+ if (s.length === 0)
173
+ return 0;
174
+ const freq = new Map();
175
+ for (const ch of s) {
176
+ freq.set(ch, (freq.get(ch) || 0) + 1);
177
+ }
178
+ let entropy = 0;
179
+ for (const count of freq.values()) {
180
+ const p = count / s.length;
181
+ entropy -= p * Math.log2(p);
182
+ }
183
+ return entropy;
184
+ }
185
+ // ─── Redaction ───────────────────────────────────────────────────
186
+ /**
187
+ * Redact a matched value: show first 3 and last 3 characters, mask the middle.
188
+ */
189
+ function redact(value) {
190
+ if (value.length <= 8)
191
+ return '[REDACTED]';
192
+ const prefix = value.slice(0, 3);
193
+ const suffix = value.slice(-3);
194
+ const masked = '*'.repeat(Math.min(value.length - 6, 20));
195
+ return `${prefix}${masked}${suffix}`;
196
+ }
197
+ // ─── Public API ──────────────────────────────────────────────────
198
+ /**
199
+ * Scan content for secrets before promoting to shared visibility.
200
+ *
201
+ * Returns { clean: true } when no secrets found.
202
+ * Returns { clean: false, hits } when secrets are detected.
203
+ *
204
+ * Callers must NOT write content with visibility >= 'org' when clean is false.
205
+ */
206
+ export function scanForSecrets(content) {
207
+ const hits = [];
208
+ for (const rule of RULES) {
209
+ // Reset lastIndex for global regexes
210
+ rule.pattern.lastIndex = 0;
211
+ let match;
212
+ while ((match = rule.pattern.exec(content)) !== null) {
213
+ const matched = match[1] ?? match[0]; // prefer capture group 1 if present
214
+ // Entropy filter
215
+ if (rule.minEntropy && shannonEntropy(matched) < rule.minEntropy) {
216
+ continue;
217
+ }
218
+ hits.push({
219
+ rule: rule.id,
220
+ description: rule.description,
221
+ redactedMatch: redact(matched),
222
+ offset: match.index,
223
+ });
224
+ // Cap at 10 hits per content to avoid O(n²) explosion on adversarial input
225
+ if (hits.length >= 10)
226
+ break;
227
+ }
228
+ if (hits.length >= 10)
229
+ break;
230
+ }
231
+ return { clean: hits.length === 0, hits };
232
+ }
233
+ /**
234
+ * Returns true when content is safe to promote to shared visibility (>= 'org').
235
+ * Convenience wrapper around scanForSecrets.
236
+ */
237
+ export function isSafeForSharedVisibility(content) {
238
+ return scanForSecrets(content).clean;
239
+ }
240
+ /**
241
+ * Check whether a visibility level requires secret scanning.
242
+ * Scanning is required for 'org', 'council', and 'fleet'.
243
+ * 'private' content stays with the owner — no cross-agent risk.
244
+ */
245
+ export function requiresScan(visibility) {
246
+ return visibility === 'org' || visibility === 'council' || visibility === 'fleet';
247
+ }
248
+ //# sourceMappingURL=secret-scanner.js.map
package/dist/seed.d.ts ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * HyperMem Workspace Seeder
3
+ *
4
+ * Reads ACA workspace files, chunks them by logical section, and indexes
5
+ * into HyperMem for demand-loaded retrieval (ACA offload).
6
+ *
7
+ * Usage:
8
+ * const seeder = new WorkspaceSeeder(hypermem);
9
+ * const result = await seeder.seedWorkspace('/path/to/workspace', { agentId: 'forge' });
10
+ *
11
+ * Idempotent: skips files whose source hash hasn't changed since last index.
12
+ * Atomic: each file's chunks are swapped in a single transaction.
13
+ *
14
+ * Files seeded (from ACA_COLLECTIONS):
15
+ * POLICY.md → governance/policy (shared-fleet)
16
+ * CHARTER.md → governance/charter (per-tier)
17
+ * COMMS.md → governance/comms (shared-fleet)
18
+ * AGENTS.md → operations/agents (per-tier)
19
+ * TOOLS.md → operations/tools (per-agent)
20
+ * SOUL.md → identity/soul (per-agent)
21
+ * JOB.md → identity/job (per-agent)
22
+ * MOTIVATIONS.md → identity/motivations(per-agent)
23
+ * MEMORY.md → memory/decisions (per-agent)
24
+ * memory/*.md → memory/daily (per-agent)
25
+ */
26
+ import { type IndexResult } from './doc-chunk-store.js';
27
+ export interface SeedOptions {
28
+ /** Agent ID for per-agent scoped chunks */
29
+ agentId?: string;
30
+ /** Tier for per-tier scoped chunks (council/director) */
31
+ tier?: string;
32
+ /** Whether to force re-index even if hash unchanged */
33
+ force?: boolean;
34
+ /** Only seed specific collections (e.g., ['governance/policy']) */
35
+ collections?: string[];
36
+ /** Whether to include daily memory files (memory/YYYY-MM-DD.md) */
37
+ includeDailyMemory?: boolean;
38
+ /** Max daily memory files to include (most recent first) */
39
+ dailyMemoryLimit?: number;
40
+ }
41
+ export interface SeedFileResult {
42
+ filePath: string;
43
+ collection: string;
44
+ result: IndexResult;
45
+ }
46
+ export interface SeedResult {
47
+ /** Files successfully processed */
48
+ files: SeedFileResult[];
49
+ /** Total new chunks inserted */
50
+ totalInserted: number;
51
+ /** Total stale chunks deleted */
52
+ totalDeleted: number;
53
+ /** Files that were up to date (skipped) */
54
+ skipped: number;
55
+ /** Files that were re-indexed */
56
+ reindexed: number;
57
+ /** Files that had errors */
58
+ errors: Array<{
59
+ filePath: string;
60
+ error: string;
61
+ }>;
62
+ }
63
+ export declare class WorkspaceSeeder {
64
+ private db;
65
+ private chunkStore;
66
+ constructor(db: import('node:sqlite').DatabaseSync);
67
+ /**
68
+ * Seed all ACA files from a workspace directory.
69
+ */
70
+ seedWorkspace(workspaceDir: string, opts?: SeedOptions): Promise<SeedResult>;
71
+ /**
72
+ * Seed a single file explicitly.
73
+ */
74
+ seedFile(filePath: string, collection: string, opts?: SeedOptions): SeedFileResult;
75
+ /**
76
+ * Check which workspace files need re-indexing.
77
+ */
78
+ checkStaleness(workspaceDir: string, opts?: SeedOptions): Array<{
79
+ filePath: string;
80
+ collection: string;
81
+ needsReindex: boolean;
82
+ }>;
83
+ /**
84
+ * Get stats about what's currently indexed.
85
+ */
86
+ getIndexStats(): {
87
+ collection: string;
88
+ count: number;
89
+ sources: number;
90
+ totalTokens: number;
91
+ }[];
92
+ /**
93
+ * Query indexed chunks by collection.
94
+ */
95
+ queryChunks(collection: string, opts?: {
96
+ agentId?: string;
97
+ tier?: string;
98
+ limit?: number;
99
+ keyword?: string;
100
+ }): import("./doc-chunk-store.js").DocChunkRow[];
101
+ private discoverFiles;
102
+ }
103
+ /**
104
+ * Seed a workspace directly from a DatabaseSync instance.
105
+ * Convenience wrapper for use in the hook handler and CLI.
106
+ */
107
+ export declare function seedWorkspace(db: import('node:sqlite').DatabaseSync, workspaceDir: string, opts?: SeedOptions): Promise<SeedResult>;
108
+ //# sourceMappingURL=seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../src/seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAIvE,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,mEAAmE;IACnE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,mCAAmC;IACnC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,gCAAgC;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,iCAAiC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,MAAM,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpD;AAID,qBAAa,eAAe;IAGd,OAAO,CAAC,EAAE;IAFtB,OAAO,CAAC,UAAU,CAAgB;gBAEd,EAAE,EAAE,OAAO,aAAa,EAAE,YAAY;IAI1D;;OAEG;IACG,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IAmDtF;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,cAAc;IAmBtF;;OAEG;IACH,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,KAAK,CAAC;QAClE,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;IAeF;;OAEG;IACH,aAAa;;;;;;IAIb;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAO;IAMhH,OAAO,CAAC,aAAa;CAuCtB;AAID;;;GAGG;AACH,wBAAsB,aAAa,CACjC,EAAE,EAAE,OAAO,aAAa,EAAE,YAAY,EACtC,YAAY,EAAE,MAAM,EACpB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,UAAU,CAAC,CAGrB"}