@psiclawops/hypermem 0.1.0 → 0.5.1
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/ARCHITECTURE.md +4 -3
- package/README.md +457 -174
- package/dist/background-indexer.d.ts +19 -4
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +329 -17
- package/dist/cache.d.ts +110 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +495 -0
- package/dist/compaction-fence.d.ts +1 -1
- package/dist/compaction-fence.js +1 -1
- package/dist/compositor.d.ts +114 -27
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +1678 -229
- package/dist/content-type-classifier.d.ts +41 -0
- package/dist/content-type-classifier.d.ts.map +1 -0
- package/dist/content-type-classifier.js +181 -0
- package/dist/cross-agent.d.ts +5 -0
- package/dist/cross-agent.d.ts.map +1 -1
- package/dist/cross-agent.js +5 -0
- package/dist/db.d.ts +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +6 -2
- package/dist/desired-state-store.d.ts +1 -1
- package/dist/desired-state-store.d.ts.map +1 -1
- package/dist/desired-state-store.js +15 -5
- package/dist/doc-chunk-store.d.ts +26 -1
- package/dist/doc-chunk-store.d.ts.map +1 -1
- package/dist/doc-chunk-store.js +114 -1
- package/dist/doc-chunker.d.ts +1 -1
- package/dist/doc-chunker.js +1 -1
- package/dist/dreaming-promoter.d.ts +86 -0
- package/dist/dreaming-promoter.d.ts.map +1 -0
- package/dist/dreaming-promoter.js +381 -0
- package/dist/episode-store.d.ts +2 -1
- package/dist/episode-store.d.ts.map +1 -1
- package/dist/episode-store.js +4 -4
- package/dist/fact-store.d.ts +19 -1
- package/dist/fact-store.d.ts.map +1 -1
- package/dist/fact-store.js +64 -3
- package/dist/fleet-store.d.ts +1 -1
- package/dist/fleet-store.js +1 -1
- package/dist/fos-mod.d.ts +178 -0
- package/dist/fos-mod.d.ts.map +1 -0
- package/dist/fos-mod.js +416 -0
- package/dist/hybrid-retrieval.d.ts +5 -1
- package/dist/hybrid-retrieval.d.ts.map +1 -1
- package/dist/hybrid-retrieval.js +7 -3
- package/dist/image-eviction.d.ts +49 -0
- package/dist/image-eviction.d.ts.map +1 -0
- package/dist/image-eviction.js +251 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -43
- package/dist/keystone-scorer.d.ts +51 -0
- package/dist/keystone-scorer.d.ts.map +1 -0
- package/dist/keystone-scorer.js +52 -0
- package/dist/knowledge-graph.d.ts +1 -1
- package/dist/knowledge-graph.js +1 -1
- package/dist/knowledge-lint.d.ts +29 -0
- package/dist/knowledge-lint.d.ts.map +1 -0
- package/dist/knowledge-lint.js +116 -0
- package/dist/knowledge-store.d.ts +1 -1
- package/dist/knowledge-store.d.ts.map +1 -1
- package/dist/knowledge-store.js +8 -2
- package/dist/library-schema.d.ts +3 -3
- package/dist/library-schema.d.ts.map +1 -1
- package/dist/library-schema.js +324 -3
- package/dist/message-store.d.ts +15 -2
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +51 -1
- package/dist/metrics-dashboard.d.ts +114 -0
- package/dist/metrics-dashboard.d.ts.map +1 -0
- package/dist/metrics-dashboard.js +260 -0
- package/dist/obsidian-exporter.d.ts +57 -0
- package/dist/obsidian-exporter.d.ts.map +1 -0
- package/dist/obsidian-exporter.js +274 -0
- package/dist/obsidian-watcher.d.ts +147 -0
- package/dist/obsidian-watcher.d.ts.map +1 -0
- package/dist/obsidian-watcher.js +403 -0
- package/dist/open-domain.d.ts +46 -0
- package/dist/open-domain.d.ts.map +1 -0
- package/dist/open-domain.js +125 -0
- package/dist/preference-store.d.ts +1 -1
- package/dist/preference-store.js +1 -1
- package/dist/preservation-gate.d.ts +1 -1
- package/dist/preservation-gate.js +1 -1
- package/dist/proactive-pass.d.ts +63 -0
- package/dist/proactive-pass.d.ts.map +1 -0
- package/dist/proactive-pass.js +239 -0
- package/dist/profiles.d.ts +44 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +227 -0
- package/dist/provider-translator.d.ts +13 -3
- package/dist/provider-translator.d.ts.map +1 -1
- package/dist/provider-translator.js +63 -9
- package/dist/rate-limiter.d.ts +1 -1
- package/dist/rate-limiter.js +1 -1
- package/dist/repair-tool-pairs.d.ts +38 -0
- package/dist/repair-tool-pairs.d.ts.map +1 -0
- package/dist/repair-tool-pairs.js +138 -0
- package/dist/retrieval-policy.d.ts +51 -0
- package/dist/retrieval-policy.d.ts.map +1 -0
- package/dist/retrieval-policy.js +77 -0
- package/dist/schema.d.ts +2 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +28 -2
- package/dist/secret-scanner.d.ts +1 -1
- package/dist/secret-scanner.js +1 -1
- package/dist/seed.d.ts +2 -2
- package/dist/seed.js +2 -2
- package/dist/session-flusher.d.ts +53 -0
- package/dist/session-flusher.d.ts.map +1 -0
- package/dist/session-flusher.js +69 -0
- package/dist/session-topic-map.d.ts +41 -0
- package/dist/session-topic-map.d.ts.map +1 -0
- package/dist/session-topic-map.js +77 -0
- package/dist/spawn-context.d.ts +54 -0
- package/dist/spawn-context.d.ts.map +1 -0
- package/dist/spawn-context.js +159 -0
- package/dist/system-store.d.ts +1 -1
- package/dist/system-store.js +1 -1
- package/dist/temporal-store.d.ts +80 -0
- package/dist/temporal-store.d.ts.map +1 -0
- package/dist/temporal-store.js +149 -0
- package/dist/topic-detector.d.ts +35 -0
- package/dist/topic-detector.d.ts.map +1 -0
- package/dist/topic-detector.js +249 -0
- package/dist/topic-store.d.ts +1 -1
- package/dist/topic-store.js +1 -1
- package/dist/topic-synthesizer.d.ts +51 -0
- package/dist/topic-synthesizer.d.ts.map +1 -0
- package/dist/topic-synthesizer.js +315 -0
- package/dist/trigger-registry.d.ts +63 -0
- package/dist/trigger-registry.d.ts.map +1 -0
- package/dist/trigger-registry.js +163 -0
- package/dist/types.d.ts +214 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/vector-store.d.ts +43 -5
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +189 -10
- package/dist/version.d.ts +34 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +34 -0
- package/dist/wiki-page-emitter.d.ts +65 -0
- package/dist/wiki-page-emitter.d.ts.map +1 -0
- package/dist/wiki-page-emitter.js +258 -0
- package/dist/work-store.d.ts +1 -1
- package/dist/work-store.js +1 -1
- package/package.json +15 -5
- package/dist/redis.d.ts +0 -188
- package/dist/redis.d.ts.map +0 -1
- package/dist/redis.js +0 -534
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Retrieval Policy
|
|
3
|
+
*
|
|
4
|
+
* Single enforced policy layer for scope-based access control during retrieval.
|
|
5
|
+
* Called by the compositor to filter items before they are injected into context.
|
|
6
|
+
*
|
|
7
|
+
* Scope rules:
|
|
8
|
+
* 'agent' (default / null / undefined): allowed if agentId matches OR is null/undefined (global)
|
|
9
|
+
* 'session': allowed if both agentId AND sessionKey match
|
|
10
|
+
* 'user': allowed if agentId matches
|
|
11
|
+
* 'global': always allowed
|
|
12
|
+
* any other value: denied with reason 'ambiguous_scope'
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Check whether a single item is accessible in the given retrieval context.
|
|
16
|
+
*
|
|
17
|
+
* @param itemScope The scope stored on the item (null/undefined → defaults to 'agent')
|
|
18
|
+
* @param itemAgentId The agentId stored on the item (null/undefined → global)
|
|
19
|
+
* @param itemSessionKey The sessionKey stored on the item (null/undefined → any)
|
|
20
|
+
* @param ctx The requester's retrieval context
|
|
21
|
+
*/
|
|
22
|
+
export function checkScope(itemScope, itemAgentId, itemSessionKey, ctx) {
|
|
23
|
+
const scope = itemScope ?? 'agent';
|
|
24
|
+
switch (scope) {
|
|
25
|
+
case 'agent':
|
|
26
|
+
// Global agent facts (null/undefined agentId) are readable by all agents.
|
|
27
|
+
// Agent-specific facts are only readable by the owning agent.
|
|
28
|
+
if (itemAgentId == null || itemAgentId === ctx.agentId) {
|
|
29
|
+
return { allowed: true, reason: 'allowed' };
|
|
30
|
+
}
|
|
31
|
+
return { allowed: false, reason: 'scope_filtered' };
|
|
32
|
+
case 'session':
|
|
33
|
+
// Session-scoped: both agentId AND sessionKey must match.
|
|
34
|
+
if ((itemAgentId == null || itemAgentId === ctx.agentId) &&
|
|
35
|
+
(itemSessionKey == null || itemSessionKey === ctx.sessionKey)) {
|
|
36
|
+
return { allowed: true, reason: 'allowed' };
|
|
37
|
+
}
|
|
38
|
+
return { allowed: false, reason: 'scope_filtered' };
|
|
39
|
+
case 'user':
|
|
40
|
+
// User-scoped: agentId must match (user prefs are keyed by agent).
|
|
41
|
+
if (itemAgentId == null || itemAgentId === ctx.agentId) {
|
|
42
|
+
return { allowed: true, reason: 'allowed' };
|
|
43
|
+
}
|
|
44
|
+
return { allowed: false, reason: 'scope_filtered' };
|
|
45
|
+
case 'global':
|
|
46
|
+
// Global: always accessible.
|
|
47
|
+
return { allowed: true, reason: 'allowed' };
|
|
48
|
+
default:
|
|
49
|
+
// Unknown scope — deny with ambiguous_scope.
|
|
50
|
+
return { allowed: false, reason: 'ambiguous_scope' };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Filter an array of items by scope, returning allowed items and a filtered count.
|
|
55
|
+
*
|
|
56
|
+
* Items are expected to have optional `agentId`, `sessionKey`, and `scope` fields.
|
|
57
|
+
* Null/undefined fields are treated as "unset" (permissive for their slot).
|
|
58
|
+
*
|
|
59
|
+
* @param items Array of items to filter
|
|
60
|
+
* @param ctx The requester's retrieval context
|
|
61
|
+
* @returns { allowed: T[], filteredCount: number }
|
|
62
|
+
*/
|
|
63
|
+
export function filterByScope(items, ctx) {
|
|
64
|
+
const allowed = [];
|
|
65
|
+
let filteredCount = 0;
|
|
66
|
+
for (const item of items) {
|
|
67
|
+
const result = checkScope(item.scope, item.agentId, item.sessionKey, ctx);
|
|
68
|
+
if (result.allowed) {
|
|
69
|
+
allowed.push(item);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
filteredCount++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { allowed, filteredCount };
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=retrieval-policy.js.map
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem Agent Message Schema
|
|
3
3
|
*
|
|
4
4
|
* Per-agent database: ~/.openclaw/hypermem/agents/{agentId}/messages.db
|
|
5
5
|
* Write-heavy, temporal, rotatable.
|
|
6
6
|
* Contains ONLY conversation data — structured knowledge lives in library.db.
|
|
7
7
|
*/
|
|
8
8
|
import type { DatabaseSync } from 'node:sqlite';
|
|
9
|
-
export declare const LATEST_SCHEMA_VERSION =
|
|
9
|
+
export declare const LATEST_SCHEMA_VERSION = 6;
|
|
10
10
|
/**
|
|
11
11
|
* Run migrations on an agent message database.
|
|
12
12
|
*/
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAgJvC;;GAEG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAgJvC;;GAEG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA4F9C;AAED,OAAO,EAAE,qBAAqB,IAAI,cAAc,EAAE,CAAC"}
|
package/dist/schema.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem Agent Message Schema
|
|
3
3
|
*
|
|
4
4
|
* Per-agent database: ~/.openclaw/hypermem/agents/{agentId}/messages.db
|
|
5
5
|
* Write-heavy, temporal, rotatable.
|
|
6
6
|
* Contains ONLY conversation data — structured knowledge lives in library.db.
|
|
7
7
|
*/
|
|
8
|
-
export const LATEST_SCHEMA_VERSION =
|
|
8
|
+
export const LATEST_SCHEMA_VERSION = 6;
|
|
9
9
|
function nowIso() {
|
|
10
10
|
return new Date().toISOString();
|
|
11
11
|
}
|
|
@@ -198,6 +198,32 @@ export function migrate(db) {
|
|
|
198
198
|
db.prepare('INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (?, ?)')
|
|
199
199
|
.run(5, nowIso());
|
|
200
200
|
}
|
|
201
|
+
// v5 → v6: add topic_id to messages, add per-session topics table
|
|
202
|
+
if (currentVersion < 6) {
|
|
203
|
+
// Add topic_id column to messages (nullable TEXT)
|
|
204
|
+
const msgCols = db.prepare('PRAGMA table_info(messages)').all()
|
|
205
|
+
.map(r => r.name);
|
|
206
|
+
if (!msgCols.includes('topic_id')) {
|
|
207
|
+
db.exec('ALTER TABLE messages ADD COLUMN topic_id TEXT');
|
|
208
|
+
}
|
|
209
|
+
// Add index on messages.topic_id
|
|
210
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_messages_topic_id ON messages(topic_id)');
|
|
211
|
+
// Add per-session topics table (in messages.db for session-scoped topic tracking)
|
|
212
|
+
db.exec(`
|
|
213
|
+
CREATE TABLE IF NOT EXISTS topics (
|
|
214
|
+
id TEXT PRIMARY KEY,
|
|
215
|
+
session_key TEXT NOT NULL,
|
|
216
|
+
name TEXT NOT NULL,
|
|
217
|
+
created_at INTEGER NOT NULL,
|
|
218
|
+
last_active_at INTEGER NOT NULL,
|
|
219
|
+
message_count INTEGER DEFAULT 0,
|
|
220
|
+
metadata TEXT
|
|
221
|
+
)
|
|
222
|
+
`);
|
|
223
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_topics_session_key ON topics(session_key)');
|
|
224
|
+
db.prepare('INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (?, ?)')
|
|
225
|
+
.run(6, nowIso());
|
|
226
|
+
}
|
|
201
227
|
}
|
|
202
228
|
export { LATEST_SCHEMA_VERSION as SCHEMA_VERSION };
|
|
203
229
|
//# sourceMappingURL=schema.js.map
|
package/dist/secret-scanner.d.ts
CHANGED
package/dist/secret-scanner.js
CHANGED
package/dist/seed.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem Workspace Seeder
|
|
3
3
|
*
|
|
4
4
|
* Reads ACA workspace files, chunks them by logical section, and indexes
|
|
5
|
-
* into
|
|
5
|
+
* into hypermem for demand-loaded retrieval (ACA offload).
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* const seeder = new WorkspaceSeeder(hypermem);
|
package/dist/seed.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem Workspace Seeder
|
|
3
3
|
*
|
|
4
4
|
* Reads ACA workspace files, chunks them by logical section, and indexes
|
|
5
|
-
* into
|
|
5
|
+
* into hypermem for demand-loaded retrieval (ACA offload).
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* const seeder = new WorkspaceSeeder(hypermem);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-flusher.ts
|
|
3
|
+
*
|
|
4
|
+
* Provides a clean, operator-safe way to flush a session's hot cache from Redis
|
|
5
|
+
* without touching long-term memory (facts, vectors, episodes, knowledge graph).
|
|
6
|
+
*
|
|
7
|
+
* Used to implement the /fresh slash command — lets users start a new unwarmed
|
|
8
|
+
* session on demand without requiring a gateway restart.
|
|
9
|
+
*
|
|
10
|
+
* Long-term memory (facts, vectors, episodes) is intentionally preserved.
|
|
11
|
+
* It will re-warm naturally on the next session bootstrap.
|
|
12
|
+
*/
|
|
13
|
+
import type { CacheLayer } from './cache.js';
|
|
14
|
+
export interface FlushSessionOptions {
|
|
15
|
+
/** If true, also clears topic-level Redis keys for this session */
|
|
16
|
+
includeTopics?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface FlushSessionResult {
|
|
19
|
+
success: boolean;
|
|
20
|
+
agentId: string;
|
|
21
|
+
sessionKey: string;
|
|
22
|
+
/** ISO timestamp of the flush */
|
|
23
|
+
flushedAt: string;
|
|
24
|
+
/** What was cleared */
|
|
25
|
+
cleared: string[];
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Flush a session's cache hot layer.
|
|
30
|
+
*
|
|
31
|
+
* Safe to call at any time. Long-term stores (SQLite facts, vectors, episodes,
|
|
32
|
+
* knowledge graph) are not touched. The next session bootstrap will re-warm
|
|
33
|
+
* from those stores naturally.
|
|
34
|
+
*
|
|
35
|
+
* @param cache Connected CacheLayer instance
|
|
36
|
+
* @param agentId Agent identifier (e.g. "forge")
|
|
37
|
+
* @param sessionKey Full session key (e.g. "agent:forge:webchat:scratchpad")
|
|
38
|
+
* @param opts Optional flags
|
|
39
|
+
*/
|
|
40
|
+
export declare function flushSession(cache: CacheLayer, agentId: string, sessionKey: string, opts?: FlushSessionOptions): Promise<FlushSessionResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Convenience class for operator tooling that holds a bound agentId.
|
|
43
|
+
*/
|
|
44
|
+
export declare class SessionFlusher {
|
|
45
|
+
private readonly cache;
|
|
46
|
+
private readonly agentId;
|
|
47
|
+
constructor(cache: CacheLayer, agentId: string);
|
|
48
|
+
/**
|
|
49
|
+
* Flush the hot cache for a specific session.
|
|
50
|
+
*/
|
|
51
|
+
flush(sessionKey: string, opts?: FlushSessionOptions): Promise<FlushSessionResult>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=session-flusher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-flusher.d.ts","sourceRoot":"","sources":["../src/session-flusher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,mBAAmB;IAClC,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,kBAAkB,CAAC,CA2B7B;AAED;;GAEG;AACH,qBAAa,cAAc;IAEvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,MAAM;IAGlC;;OAEG;IACG,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAGzF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-flusher.ts
|
|
3
|
+
*
|
|
4
|
+
* Provides a clean, operator-safe way to flush a session's hot cache from Redis
|
|
5
|
+
* without touching long-term memory (facts, vectors, episodes, knowledge graph).
|
|
6
|
+
*
|
|
7
|
+
* Used to implement the /fresh slash command — lets users start a new unwarmed
|
|
8
|
+
* session on demand without requiring a gateway restart.
|
|
9
|
+
*
|
|
10
|
+
* Long-term memory (facts, vectors, episodes) is intentionally preserved.
|
|
11
|
+
* It will re-warm naturally on the next session bootstrap.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Flush a session's cache hot layer.
|
|
15
|
+
*
|
|
16
|
+
* Safe to call at any time. Long-term stores (SQLite facts, vectors, episodes,
|
|
17
|
+
* knowledge graph) are not touched. The next session bootstrap will re-warm
|
|
18
|
+
* from those stores naturally.
|
|
19
|
+
*
|
|
20
|
+
* @param cache Connected CacheLayer instance
|
|
21
|
+
* @param agentId Agent identifier (e.g. "forge")
|
|
22
|
+
* @param sessionKey Full session key (e.g. "agent:forge:webchat:scratchpad")
|
|
23
|
+
* @param opts Optional flags
|
|
24
|
+
*/
|
|
25
|
+
export async function flushSession(cache, agentId, sessionKey, opts = {}) {
|
|
26
|
+
const cleared = [];
|
|
27
|
+
try {
|
|
28
|
+
// Core eviction — clears: system, identity, history, window, cursor,
|
|
29
|
+
// context, facts, tools, meta — and removes from active sessions set
|
|
30
|
+
await cache.evictSession(agentId, sessionKey);
|
|
31
|
+
cleared.push('system', 'identity', 'history', 'window', 'cursor', 'context', 'facts', 'tools', 'meta', 'active-sessions');
|
|
32
|
+
return {
|
|
33
|
+
success: true,
|
|
34
|
+
agentId,
|
|
35
|
+
sessionKey,
|
|
36
|
+
flushedAt: new Date().toISOString(),
|
|
37
|
+
cleared,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
agentId,
|
|
45
|
+
sessionKey,
|
|
46
|
+
flushedAt: new Date().toISOString(),
|
|
47
|
+
cleared,
|
|
48
|
+
error,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Convenience class for operator tooling that holds a bound agentId.
|
|
54
|
+
*/
|
|
55
|
+
export class SessionFlusher {
|
|
56
|
+
cache;
|
|
57
|
+
agentId;
|
|
58
|
+
constructor(cache, agentId) {
|
|
59
|
+
this.cache = cache;
|
|
60
|
+
this.agentId = agentId;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Flush the hot cache for a specific session.
|
|
64
|
+
*/
|
|
65
|
+
async flush(sessionKey, opts) {
|
|
66
|
+
return flushSession(this.cache, this.agentId, sessionKey, opts);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=session-flusher.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionTopicMap (P3.3)
|
|
3
|
+
*
|
|
4
|
+
* Manages per-session topic state in messages.db.
|
|
5
|
+
* Topics table: id(TEXT), session_key, name, created_at, last_active_at,
|
|
6
|
+
* message_count, metadata
|
|
7
|
+
*/
|
|
8
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
9
|
+
export declare class SessionTopicMap {
|
|
10
|
+
private db;
|
|
11
|
+
constructor(db: DatabaseSync);
|
|
12
|
+
/**
|
|
13
|
+
* Get the active topic for a session (most recently active).
|
|
14
|
+
*/
|
|
15
|
+
getActiveTopic(sessionKey: string): {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
} | null;
|
|
19
|
+
/**
|
|
20
|
+
* Activate a topic: update last_active_at to now.
|
|
21
|
+
*/
|
|
22
|
+
activateTopic(sessionKey: string, topicId: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Create a new topic and activate it. Returns the new topicId.
|
|
25
|
+
*/
|
|
26
|
+
createTopic(sessionKey: string, name: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* List all topics for a session, ordered by last_active_at DESC.
|
|
29
|
+
*/
|
|
30
|
+
listTopics(sessionKey: string): Array<{
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
messageCount: number;
|
|
34
|
+
lastActiveAt: number;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Increment message count for the given topic.
|
|
38
|
+
*/
|
|
39
|
+
incrementMessageCount(topicId: string): void;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=session-topic-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-topic-map.d.ts","sourceRoot":"","sources":["../src/session-topic-map.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,qBAAa,eAAe;IACd,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAEpC;;OAEG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAYvE;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAQxD;;OAEG;IACH,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAUrD;;OAEG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,CAAC;QACpC,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IAqBF;;OAEG;IACH,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAO7C"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionTopicMap (P3.3)
|
|
3
|
+
*
|
|
4
|
+
* Manages per-session topic state in messages.db.
|
|
5
|
+
* Topics table: id(TEXT), session_key, name, created_at, last_active_at,
|
|
6
|
+
* message_count, metadata
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
export class SessionTopicMap {
|
|
10
|
+
db;
|
|
11
|
+
constructor(db) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the active topic for a session (most recently active).
|
|
16
|
+
*/
|
|
17
|
+
getActiveTopic(sessionKey) {
|
|
18
|
+
const row = this.db.prepare(`
|
|
19
|
+
SELECT id, name
|
|
20
|
+
FROM topics
|
|
21
|
+
WHERE session_key = ?
|
|
22
|
+
ORDER BY last_active_at DESC
|
|
23
|
+
LIMIT 1
|
|
24
|
+
`).get(sessionKey);
|
|
25
|
+
return row ?? null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Activate a topic: update last_active_at to now.
|
|
29
|
+
*/
|
|
30
|
+
activateTopic(sessionKey, topicId) {
|
|
31
|
+
this.db.prepare(`
|
|
32
|
+
UPDATE topics
|
|
33
|
+
SET last_active_at = ?
|
|
34
|
+
WHERE id = ? AND session_key = ?
|
|
35
|
+
`).run(Date.now(), topicId, sessionKey);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a new topic and activate it. Returns the new topicId.
|
|
39
|
+
*/
|
|
40
|
+
createTopic(sessionKey, name) {
|
|
41
|
+
const id = randomUUID();
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
this.db.prepare(`
|
|
44
|
+
INSERT INTO topics (id, session_key, name, created_at, last_active_at, message_count, metadata)
|
|
45
|
+
VALUES (?, ?, ?, ?, ?, 0, NULL)
|
|
46
|
+
`).run(id, sessionKey, name.slice(0, 40), now, now);
|
|
47
|
+
return id;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* List all topics for a session, ordered by last_active_at DESC.
|
|
51
|
+
*/
|
|
52
|
+
listTopics(sessionKey) {
|
|
53
|
+
const rows = this.db.prepare(`
|
|
54
|
+
SELECT id, name, message_count, last_active_at
|
|
55
|
+
FROM topics
|
|
56
|
+
WHERE session_key = ?
|
|
57
|
+
ORDER BY last_active_at DESC
|
|
58
|
+
`).all(sessionKey);
|
|
59
|
+
return rows.map(r => ({
|
|
60
|
+
id: r.id,
|
|
61
|
+
name: r.name,
|
|
62
|
+
messageCount: r.message_count,
|
|
63
|
+
lastActiveAt: r.last_active_at,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Increment message count for the given topic.
|
|
68
|
+
*/
|
|
69
|
+
incrementMessageCount(topicId) {
|
|
70
|
+
this.db.prepare(`
|
|
71
|
+
UPDATE topics
|
|
72
|
+
SET message_count = message_count + 1
|
|
73
|
+
WHERE id = ?
|
|
74
|
+
`).run(topicId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=session-topic-map.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Spawn Context — Subagent Context Inheritance
|
|
3
|
+
*
|
|
4
|
+
* Provides tools to snapshot a parent session's working context and make it
|
|
5
|
+
* available to a spawned subagent at compose time.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const ctx = await buildSpawnContext(messageStore, docChunkStore, agentId, {
|
|
9
|
+
* parentSessionKey: 'agent:forge:webchat:main',
|
|
10
|
+
* workingSnapshot: 10,
|
|
11
|
+
* documents: ['/path/to/spec.md'],
|
|
12
|
+
* });
|
|
13
|
+
* // Inject ctx.parentContextBlock into the subagent task prompt.
|
|
14
|
+
* // Pass ctx.sessionKey as ComposeRequest.parentSessionKey for doc chunk retrieval.
|
|
15
|
+
* // When done: docChunkStore.clearSessionChunks(ctx.sessionKey);
|
|
16
|
+
*/
|
|
17
|
+
import type { MessageStore } from './message-store.js';
|
|
18
|
+
import type { DocChunkStore } from './doc-chunk-store.js';
|
|
19
|
+
export interface SpawnContextOptions {
|
|
20
|
+
/** Parent session key to snapshot working context from */
|
|
21
|
+
parentSessionKey: string;
|
|
22
|
+
/** Number of recent turns to include (default 25, max 50) */
|
|
23
|
+
workingSnapshot?: number;
|
|
24
|
+
/** File paths to chunk and inject as session-scoped doc chunks */
|
|
25
|
+
documents?: string[];
|
|
26
|
+
/** Token budget for the entire spawn context (default 4000) */
|
|
27
|
+
budgetTokens?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface SpawnContext {
|
|
30
|
+
/** Formatted parent context block for injection into subagent task prompt */
|
|
31
|
+
parentContextBlock: string | null;
|
|
32
|
+
/** Session key to use for querying injected doc chunks at compose time */
|
|
33
|
+
sessionKey: string;
|
|
34
|
+
/** Summary of what was injected (for logging) */
|
|
35
|
+
summary: {
|
|
36
|
+
turnsIncluded: number;
|
|
37
|
+
documentsIndexed: number;
|
|
38
|
+
documentsSkipped: string[];
|
|
39
|
+
tokenEstimate: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build a spawn context for a subagent by snapshotting the parent session.
|
|
44
|
+
*
|
|
45
|
+
* 1. Generates a unique session key for this spawn.
|
|
46
|
+
* 2. Pulls recent turns from the parent session (L2 messages).
|
|
47
|
+
* 3. Formats them into a compact block for injection into the task prompt.
|
|
48
|
+
* 4. Optionally chunks and indexes documents as session-scoped doc chunks.
|
|
49
|
+
* 5. Returns the context block, spawn session key, and a summary.
|
|
50
|
+
*
|
|
51
|
+
* Errors are handled gracefully — this function never throws.
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildSpawnContext(messageStore: MessageStore, docChunkStore: DocChunkStore, agentId: string, options: SpawnContextOptions): Promise<SpawnContext>;
|
|
54
|
+
//# sourceMappingURL=spawn-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn-context.d.ts","sourceRoot":"","sources":["../src/spawn-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,gBAAgB,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,6EAA6E;IAC7E,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,OAAO,EAAE;QACP,aAAa,EAAE,MAAM,CAAC;QACtB,gBAAgB,EAAE,MAAM,CAAC;QACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;QAC3B,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AA6DD;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,YAAY,EAC1B,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,YAAY,CAAC,CA4FvB"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Spawn Context — Subagent Context Inheritance
|
|
3
|
+
*
|
|
4
|
+
* Provides tools to snapshot a parent session's working context and make it
|
|
5
|
+
* available to a spawned subagent at compose time.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const ctx = await buildSpawnContext(messageStore, docChunkStore, agentId, {
|
|
9
|
+
* parentSessionKey: 'agent:forge:webchat:main',
|
|
10
|
+
* workingSnapshot: 10,
|
|
11
|
+
* documents: ['/path/to/spec.md'],
|
|
12
|
+
* });
|
|
13
|
+
* // Inject ctx.parentContextBlock into the subagent task prompt.
|
|
14
|
+
* // Pass ctx.sessionKey as ComposeRequest.parentSessionKey for doc chunk retrieval.
|
|
15
|
+
* // When done: docChunkStore.clearSessionChunks(ctx.sessionKey);
|
|
16
|
+
*/
|
|
17
|
+
import { readFile } from 'node:fs/promises';
|
|
18
|
+
// ─── Constants ──────────────────────────────────────────────────
|
|
19
|
+
const DEFAULT_WORKING_SNAPSHOT = 25;
|
|
20
|
+
const MAX_WORKING_SNAPSHOT = 50;
|
|
21
|
+
const DEFAULT_BUDGET_TOKENS = 4000;
|
|
22
|
+
const MAX_CHUNK_CHARS = 500;
|
|
23
|
+
const CHUNK_OVERLAP_CHARS = 50;
|
|
24
|
+
// ─── Helpers ────────────────────────────────────────────────────
|
|
25
|
+
function estimateTokens(text) {
|
|
26
|
+
return Math.ceil(text.length / 4);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Split text on double-newlines, max 500 chars per chunk, with overlap.
|
|
30
|
+
*/
|
|
31
|
+
function chunkText(text) {
|
|
32
|
+
const paragraphs = text.split(/\n\n+/);
|
|
33
|
+
const chunks = [];
|
|
34
|
+
let current = '';
|
|
35
|
+
for (const para of paragraphs) {
|
|
36
|
+
const trimmed = para.trim();
|
|
37
|
+
if (!trimmed)
|
|
38
|
+
continue;
|
|
39
|
+
if (current.length === 0) {
|
|
40
|
+
current = trimmed;
|
|
41
|
+
}
|
|
42
|
+
else if (current.length + trimmed.length + 2 <= MAX_CHUNK_CHARS) {
|
|
43
|
+
current += '\n\n' + trimmed;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Flush current chunk
|
|
47
|
+
chunks.push(current);
|
|
48
|
+
// Start new chunk with overlap from end of previous chunk
|
|
49
|
+
const overlap = current.slice(-CHUNK_OVERLAP_CHARS);
|
|
50
|
+
current = (overlap.trim() ? overlap.trim() + '\n\n' : '') + trimmed;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (current.trim()) {
|
|
54
|
+
chunks.push(current.trim());
|
|
55
|
+
}
|
|
56
|
+
// If text had no double-newlines or a single large paragraph, split by chars
|
|
57
|
+
if (chunks.length === 0 && text.trim()) {
|
|
58
|
+
let pos = 0;
|
|
59
|
+
const raw = text.trim();
|
|
60
|
+
while (pos < raw.length) {
|
|
61
|
+
const end = Math.min(pos + MAX_CHUNK_CHARS, raw.length);
|
|
62
|
+
chunks.push(raw.slice(pos, end));
|
|
63
|
+
pos += MAX_CHUNK_CHARS - CHUNK_OVERLAP_CHARS;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return chunks;
|
|
67
|
+
}
|
|
68
|
+
// ─── Main Function ───────────────────────────────────────────────
|
|
69
|
+
/**
|
|
70
|
+
* Build a spawn context for a subagent by snapshotting the parent session.
|
|
71
|
+
*
|
|
72
|
+
* 1. Generates a unique session key for this spawn.
|
|
73
|
+
* 2. Pulls recent turns from the parent session (L2 messages).
|
|
74
|
+
* 3. Formats them into a compact block for injection into the task prompt.
|
|
75
|
+
* 4. Optionally chunks and indexes documents as session-scoped doc chunks.
|
|
76
|
+
* 5. Returns the context block, spawn session key, and a summary.
|
|
77
|
+
*
|
|
78
|
+
* Errors are handled gracefully — this function never throws.
|
|
79
|
+
*/
|
|
80
|
+
export async function buildSpawnContext(messageStore, docChunkStore, agentId, options) {
|
|
81
|
+
const sessionKey = `spawn:${agentId}:${Date.now()}`;
|
|
82
|
+
const budgetTokens = options.budgetTokens ?? DEFAULT_BUDGET_TOKENS;
|
|
83
|
+
const snapshotN = Math.min(options.workingSnapshot ?? DEFAULT_WORKING_SNAPSHOT, MAX_WORKING_SNAPSHOT);
|
|
84
|
+
const summary = {
|
|
85
|
+
turnsIncluded: 0,
|
|
86
|
+
documentsIndexed: 0,
|
|
87
|
+
documentsSkipped: [],
|
|
88
|
+
tokenEstimate: 0,
|
|
89
|
+
};
|
|
90
|
+
// ── Step 1: Get recent turns from parent session ─────────────
|
|
91
|
+
let parentContextBlock = null;
|
|
92
|
+
try {
|
|
93
|
+
let turns = messageStore.getRecentTurns(options.parentSessionKey, snapshotN);
|
|
94
|
+
if (turns.length > 0) {
|
|
95
|
+
// Format turns into compact block
|
|
96
|
+
const formatBlock = (ts) => {
|
|
97
|
+
const lines = [
|
|
98
|
+
`## Parent Session Context`,
|
|
99
|
+
`[Last ${ts.length} turns from session ${options.parentSessionKey}]`,
|
|
100
|
+
'',
|
|
101
|
+
];
|
|
102
|
+
for (const turn of ts) {
|
|
103
|
+
const roleLabel = turn.role === 'user' ? 'User' : 'Assistant';
|
|
104
|
+
const content = turn.content?.trim() || '(empty)';
|
|
105
|
+
lines.push(`${roleLabel}: ${content}`);
|
|
106
|
+
}
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
};
|
|
109
|
+
let block = formatBlock(turns);
|
|
110
|
+
let tokenEst = estimateTokens(block);
|
|
111
|
+
// If over 60% of budget, truncate from oldest end
|
|
112
|
+
const maxContextTokens = Math.floor(budgetTokens * 0.6);
|
|
113
|
+
if (tokenEst > maxContextTokens) {
|
|
114
|
+
// Binary-ish approach: drop turns from the front until we fit
|
|
115
|
+
while (turns.length > 1 && estimateTokens(formatBlock(turns)) > maxContextTokens) {
|
|
116
|
+
turns = turns.slice(1);
|
|
117
|
+
}
|
|
118
|
+
block = formatBlock(turns);
|
|
119
|
+
tokenEst = estimateTokens(block);
|
|
120
|
+
}
|
|
121
|
+
if (turns.length > 0) {
|
|
122
|
+
parentContextBlock = block;
|
|
123
|
+
summary.turnsIncluded = turns.length;
|
|
124
|
+
summary.tokenEstimate += tokenEst;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
console.warn('[hypermem:spawn-context] Failed to get recent turns:', err.message);
|
|
130
|
+
}
|
|
131
|
+
// ── Step 2: Index documents as session-scoped doc chunks ─────
|
|
132
|
+
const documents = options.documents ?? [];
|
|
133
|
+
for (const docPath of documents) {
|
|
134
|
+
try {
|
|
135
|
+
const content = await readFile(docPath, 'utf8');
|
|
136
|
+
const chunks = chunkText(content);
|
|
137
|
+
if (chunks.length === 0) {
|
|
138
|
+
summary.documentsSkipped.push(docPath);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
docChunkStore.indexDocChunks(agentId, docPath, chunks, { sessionKey });
|
|
142
|
+
summary.documentsIndexed++;
|
|
143
|
+
// Add rough token estimate for documents
|
|
144
|
+
summary.tokenEstimate += estimateTokens(content);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
// File not found or unreadable — skip gracefully
|
|
148
|
+
const msg = err.message;
|
|
149
|
+
console.warn(`[hypermem:spawn-context] Skipping document "${docPath}": ${msg}`);
|
|
150
|
+
summary.documentsSkipped.push(docPath);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
parentContextBlock,
|
|
155
|
+
sessionKey,
|
|
156
|
+
summary,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=spawn-context.js.map
|
package/dist/system-store.d.ts
CHANGED
package/dist/system-store.js
CHANGED