@psiclawops/hypermem 0.5.6 → 0.6.2
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 +11 -54
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +26 -18
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +16 -2
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +146 -19
- package/dist/context-backfill.d.ts +46 -0
- package/dist/context-backfill.d.ts.map +1 -0
- package/dist/context-backfill.js +113 -0
- package/dist/context-store.d.ts +77 -0
- package/dist/context-store.d.ts.map +1 -0
- package/dist/context-store.js +177 -0
- package/dist/cross-agent.d.ts +12 -0
- package/dist/cross-agent.d.ts.map +1 -1
- package/dist/cross-agent.js +31 -19
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +8 -0
- package/dist/dreaming-promoter.d.ts +1 -1
- package/dist/dreaming-promoter.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -6
- package/dist/message-store.d.ts +31 -2
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +131 -17
- package/dist/preference-store.d.ts +1 -1
- package/dist/preference-store.js +1 -1
- package/dist/profiles.d.ts +3 -1
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +8 -0
- package/dist/repair-tool-pairs.d.ts.map +1 -1
- package/dist/repair-tool-pairs.js +73 -2
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +27 -1
- package/dist/seed.d.ts +1 -1
- package/dist/seed.js +1 -1
- package/dist/session-flusher.d.ts +2 -2
- package/dist/session-flusher.js +2 -2
- package/dist/spawn-context.d.ts +1 -1
- package/dist/spawn-context.js +1 -1
- package/dist/topic-synthesizer.js +1 -1
- package/dist/trigger-registry.d.ts +1 -1
- package/dist/trigger-registry.js +4 -4
- package/dist/types.d.ts +11 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-store.d.ts +10 -1
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +353 -0
- package/dist/version.d.ts +5 -5
- package/dist/version.js +5 -5
- package/package.json +3 -2
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Context Backfill
|
|
3
|
+
*
|
|
4
|
+
* One-time migration that creates context rows for existing conversations
|
|
5
|
+
* that don't yet have an active context. Designed to be idempotent — running
|
|
6
|
+
* it multiple times produces the same result without modifying existing data.
|
|
7
|
+
*
|
|
8
|
+
* Also provides parent chain backfill (Phase 2): reconstructs parent_id/depth
|
|
9
|
+
* for legacy flat conversations that were created before the Turn DAG model.
|
|
10
|
+
*/
|
|
11
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
12
|
+
/**
|
|
13
|
+
* Backfill active contexts for all existing conversations.
|
|
14
|
+
*
|
|
15
|
+
* For each conversation without an active context:
|
|
16
|
+
* 1. Creates an active context via getOrCreateActiveContext
|
|
17
|
+
* 2. If the conversation has messages, advances the head pointer
|
|
18
|
+
* to the highest message ID
|
|
19
|
+
*
|
|
20
|
+
* @returns counts of created and skipped conversations
|
|
21
|
+
*/
|
|
22
|
+
export declare function backfillContexts(db: DatabaseSync): {
|
|
23
|
+
created: number;
|
|
24
|
+
skipped: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Backfill parent_id and depth for existing messages that lack them.
|
|
28
|
+
*
|
|
29
|
+
* Reconstructs a linear chain per conversation ordered by message_index:
|
|
30
|
+
* - first message: parent_id = NULL, depth = 0
|
|
31
|
+
* - each subsequent: parent_id = previous.id, depth = previous.depth + 1
|
|
32
|
+
*
|
|
33
|
+
* Idempotent: only touches messages where parent_id IS NULL AND depth = 0
|
|
34
|
+
* AND there is more than one message or the message is not the first.
|
|
35
|
+
* In practice, we simply skip messages that already have parent_id set.
|
|
36
|
+
*
|
|
37
|
+
* Also stamps context_id on messages that lack it, using the active context
|
|
38
|
+
* for their conversation.
|
|
39
|
+
*
|
|
40
|
+
* @returns counts of messages updated and conversations processed
|
|
41
|
+
*/
|
|
42
|
+
export declare function backfillParentChains(db: DatabaseSync): {
|
|
43
|
+
conversationsProcessed: number;
|
|
44
|
+
messagesUpdated: number;
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=context-backfill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-backfill.d.ts","sourceRoot":"","sources":["../src/context-backfill.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAwCvF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,GACf;IAAE,sBAAsB,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CA+D7D"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Context Backfill
|
|
3
|
+
*
|
|
4
|
+
* One-time migration that creates context rows for existing conversations
|
|
5
|
+
* that don't yet have an active context. Designed to be idempotent — running
|
|
6
|
+
* it multiple times produces the same result without modifying existing data.
|
|
7
|
+
*
|
|
8
|
+
* Also provides parent chain backfill (Phase 2): reconstructs parent_id/depth
|
|
9
|
+
* for legacy flat conversations that were created before the Turn DAG model.
|
|
10
|
+
*/
|
|
11
|
+
import { getOrCreateActiveContext, updateContextHead } from './context-store.js';
|
|
12
|
+
/**
|
|
13
|
+
* Backfill active contexts for all existing conversations.
|
|
14
|
+
*
|
|
15
|
+
* For each conversation without an active context:
|
|
16
|
+
* 1. Creates an active context via getOrCreateActiveContext
|
|
17
|
+
* 2. If the conversation has messages, advances the head pointer
|
|
18
|
+
* to the highest message ID
|
|
19
|
+
*
|
|
20
|
+
* @returns counts of created and skipped conversations
|
|
21
|
+
*/
|
|
22
|
+
export function backfillContexts(db) {
|
|
23
|
+
let created = 0;
|
|
24
|
+
let skipped = 0;
|
|
25
|
+
const conversations = db
|
|
26
|
+
.prepare('SELECT id, agent_id, session_key FROM conversations')
|
|
27
|
+
.all();
|
|
28
|
+
for (const conv of conversations) {
|
|
29
|
+
// Check if an active context already exists for this conversation
|
|
30
|
+
const existing = db
|
|
31
|
+
.prepare("SELECT id FROM contexts WHERE conversation_id = ? AND status = 'active'")
|
|
32
|
+
.get(conv.id);
|
|
33
|
+
if (existing) {
|
|
34
|
+
skipped++;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// Find the max message ID for this conversation (may be null if no messages)
|
|
38
|
+
const maxRow = db
|
|
39
|
+
.prepare('SELECT MAX(id) as max_id FROM messages WHERE conversation_id = ?')
|
|
40
|
+
.get(conv.id);
|
|
41
|
+
const maxId = maxRow?.max_id ?? null;
|
|
42
|
+
// Create the active context
|
|
43
|
+
const context = getOrCreateActiveContext(db, conv.agent_id, conv.session_key, conv.id);
|
|
44
|
+
// If conversation has messages, advance head to the latest
|
|
45
|
+
if (maxId !== null) {
|
|
46
|
+
updateContextHead(db, context.id, maxId);
|
|
47
|
+
}
|
|
48
|
+
created++;
|
|
49
|
+
}
|
|
50
|
+
return { created, skipped };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Backfill parent_id and depth for existing messages that lack them.
|
|
54
|
+
*
|
|
55
|
+
* Reconstructs a linear chain per conversation ordered by message_index:
|
|
56
|
+
* - first message: parent_id = NULL, depth = 0
|
|
57
|
+
* - each subsequent: parent_id = previous.id, depth = previous.depth + 1
|
|
58
|
+
*
|
|
59
|
+
* Idempotent: only touches messages where parent_id IS NULL AND depth = 0
|
|
60
|
+
* AND there is more than one message or the message is not the first.
|
|
61
|
+
* In practice, we simply skip messages that already have parent_id set.
|
|
62
|
+
*
|
|
63
|
+
* Also stamps context_id on messages that lack it, using the active context
|
|
64
|
+
* for their conversation.
|
|
65
|
+
*
|
|
66
|
+
* @returns counts of messages updated and conversations processed
|
|
67
|
+
*/
|
|
68
|
+
export function backfillParentChains(db) {
|
|
69
|
+
let conversationsProcessed = 0;
|
|
70
|
+
let messagesUpdated = 0;
|
|
71
|
+
// Get all conversations that have at least one non-first message without parent_id.
|
|
72
|
+
// This is the idempotency guard: after backfill, all messages at index > 0
|
|
73
|
+
// have parent_id set. The first message (index 0) legitimately has parent_id = NULL.
|
|
74
|
+
const conversations = db
|
|
75
|
+
.prepare(`SELECT DISTINCT conversation_id
|
|
76
|
+
FROM messages
|
|
77
|
+
WHERE message_index > 0 AND parent_id IS NULL
|
|
78
|
+
ORDER BY conversation_id`)
|
|
79
|
+
.all();
|
|
80
|
+
const updateStmt = db.prepare('UPDATE messages SET parent_id = ?, depth = ?, context_id = COALESCE(context_id, ?) WHERE id = ?');
|
|
81
|
+
for (const { conversation_id: convId } of conversations) {
|
|
82
|
+
// Get active context for this conversation (if any)
|
|
83
|
+
const ctxRow = db
|
|
84
|
+
.prepare("SELECT id FROM contexts WHERE conversation_id = ? AND status = 'active' LIMIT 1")
|
|
85
|
+
.get(convId);
|
|
86
|
+
const contextId = ctxRow?.id ?? null;
|
|
87
|
+
// Get all messages for this conversation without parent_id, ordered by message_index
|
|
88
|
+
const messages = db
|
|
89
|
+
.prepare('SELECT id, message_index FROM messages WHERE conversation_id = ? AND parent_id IS NULL ORDER BY message_index ASC')
|
|
90
|
+
.all(convId);
|
|
91
|
+
if (messages.length === 0)
|
|
92
|
+
continue;
|
|
93
|
+
// Also get the last message that already HAS a parent_id (if any),
|
|
94
|
+
// so we can chain the backfilled messages after it.
|
|
95
|
+
const lastChainedRow = db
|
|
96
|
+
.prepare('SELECT id, depth FROM messages WHERE conversation_id = ? AND parent_id IS NOT NULL ORDER BY message_index DESC LIMIT 1')
|
|
97
|
+
.get(convId);
|
|
98
|
+
let prevId = lastChainedRow?.id ?? null;
|
|
99
|
+
let prevDepth = lastChainedRow?.depth ?? -1;
|
|
100
|
+
// If there's no prior chain AND the first unlinked message is truly
|
|
101
|
+
// message_index 0, start the chain from scratch
|
|
102
|
+
for (const msg of messages) {
|
|
103
|
+
const depth = prevDepth + 1;
|
|
104
|
+
updateStmt.run(prevId, depth, contextId, msg.id);
|
|
105
|
+
prevId = msg.id;
|
|
106
|
+
prevDepth = depth;
|
|
107
|
+
messagesUpdated++;
|
|
108
|
+
}
|
|
109
|
+
conversationsProcessed++;
|
|
110
|
+
}
|
|
111
|
+
return { conversationsProcessed, messagesUpdated };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=context-backfill.js.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Context Store
|
|
3
|
+
*
|
|
4
|
+
* Manages the `contexts` table — a durable record of agent conversation
|
|
5
|
+
* contexts that tracks which session is active, what the current head
|
|
6
|
+
* message is, and supports archival and forking.
|
|
7
|
+
*
|
|
8
|
+
* Each agent + session pair has at most one active context at a time.
|
|
9
|
+
* Contexts are the anchor point for the compositor: they track the
|
|
10
|
+
* head message (most recent message included in composed context) and
|
|
11
|
+
* link back to the underlying conversation.
|
|
12
|
+
*
|
|
13
|
+
* Design principles:
|
|
14
|
+
* - All functions take DatabaseSync as first arg (standalone, no classes)
|
|
15
|
+
* - Fully idempotent — safe to call on every startup
|
|
16
|
+
* - Head pointer is monotone-forward (never moves backward)
|
|
17
|
+
* - Archive is idempotent (no-op if already archived)
|
|
18
|
+
*/
|
|
19
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
20
|
+
export interface Context {
|
|
21
|
+
id: number;
|
|
22
|
+
agentId: string;
|
|
23
|
+
sessionKey: string;
|
|
24
|
+
conversationId: number;
|
|
25
|
+
headMessageId: number | null;
|
|
26
|
+
parentContextId: number | null;
|
|
27
|
+
status: 'active' | 'archived' | 'forked';
|
|
28
|
+
createdAt: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
metadataJson: string | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Add the contexts table and related indexes to an existing messages.db.
|
|
34
|
+
* Also ALTERs the messages table to add a context_id foreign key column.
|
|
35
|
+
* Idempotent — safe to call on every startup.
|
|
36
|
+
*/
|
|
37
|
+
export declare function ensureContextSchema(db: DatabaseSync): void;
|
|
38
|
+
/**
|
|
39
|
+
* Get the active context for an agent + session pair.
|
|
40
|
+
* Returns null if no active context exists.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getActiveContext(db: DatabaseSync, agentId: string, sessionKey: string): Context | null;
|
|
43
|
+
/**
|
|
44
|
+
* Get the active context for an agent + session, creating one if none exists.
|
|
45
|
+
*
|
|
46
|
+
* If an active context already exists, returns it unchanged.
|
|
47
|
+
* Otherwise INSERTs a new context with status='active', head_message_id=NULL,
|
|
48
|
+
* and the given conversationId.
|
|
49
|
+
*
|
|
50
|
+
* Idempotent — safe to call repeatedly.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getOrCreateActiveContext(db: DatabaseSync, agentId: string, sessionKey: string, conversationId: number): Context;
|
|
53
|
+
/**
|
|
54
|
+
* Update the head message pointer for a context.
|
|
55
|
+
*
|
|
56
|
+
* Monotone forward: only updates if messageId > current head_message_id
|
|
57
|
+
* (or current is NULL). This prevents accidental regression of the head
|
|
58
|
+
* pointer, matching the compaction-fence monotone progress pattern.
|
|
59
|
+
*/
|
|
60
|
+
export declare function updateContextHead(db: DatabaseSync, contextId: number, messageId: number): void;
|
|
61
|
+
/**
|
|
62
|
+
* Archive a context, setting its status to 'archived'.
|
|
63
|
+
* Idempotent — no-op if already archived.
|
|
64
|
+
*/
|
|
65
|
+
export declare function archiveContext(db: DatabaseSync, contextId: number): void;
|
|
66
|
+
/**
|
|
67
|
+
* Rotate a session's active context: archive the current active context
|
|
68
|
+
* and create a new one, optionally linking back via parent_context_id.
|
|
69
|
+
*
|
|
70
|
+
* Used on session restarts/rotations so the new context starts with a
|
|
71
|
+
* clean head pointer instead of inheriting the stale tail.
|
|
72
|
+
*
|
|
73
|
+
* Returns the newly created active context.
|
|
74
|
+
* If no active context exists, simply creates one (no archive step).
|
|
75
|
+
*/
|
|
76
|
+
export declare function rotateSessionContext(db: DatabaseSync, agentId: string, sessionKey: string, conversationId: number): Context;
|
|
77
|
+
//# sourceMappingURL=context-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-store.d.ts","sourceRoot":"","sources":["../src/context-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAyBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmC1D;AAID;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,GAAG,IAAI,CAShB;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAwBT;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,IAAI,CAeN;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CA2BT"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Context Store
|
|
3
|
+
*
|
|
4
|
+
* Manages the `contexts` table — a durable record of agent conversation
|
|
5
|
+
* contexts that tracks which session is active, what the current head
|
|
6
|
+
* message is, and supports archival and forking.
|
|
7
|
+
*
|
|
8
|
+
* Each agent + session pair has at most one active context at a time.
|
|
9
|
+
* Contexts are the anchor point for the compositor: they track the
|
|
10
|
+
* head message (most recent message included in composed context) and
|
|
11
|
+
* link back to the underlying conversation.
|
|
12
|
+
*
|
|
13
|
+
* Design principles:
|
|
14
|
+
* - All functions take DatabaseSync as first arg (standalone, no classes)
|
|
15
|
+
* - Fully idempotent — safe to call on every startup
|
|
16
|
+
* - Head pointer is monotone-forward (never moves backward)
|
|
17
|
+
* - Archive is idempotent (no-op if already archived)
|
|
18
|
+
*/
|
|
19
|
+
// ─── Internal Helpers ───────────────────────────────────────────
|
|
20
|
+
function parseContextRow(row) {
|
|
21
|
+
return {
|
|
22
|
+
id: row.id,
|
|
23
|
+
agentId: row.agent_id,
|
|
24
|
+
sessionKey: row.session_key,
|
|
25
|
+
conversationId: row.conversation_id,
|
|
26
|
+
headMessageId: row.head_message_id ?? null,
|
|
27
|
+
parentContextId: row.parent_context_id ?? null,
|
|
28
|
+
status: row.status,
|
|
29
|
+
createdAt: row.created_at,
|
|
30
|
+
updatedAt: row.updated_at,
|
|
31
|
+
metadataJson: row.metadata_json ?? null,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function nowIso() {
|
|
35
|
+
return new Date().toISOString();
|
|
36
|
+
}
|
|
37
|
+
// ─── Schema ─────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Add the contexts table and related indexes to an existing messages.db.
|
|
40
|
+
* Also ALTERs the messages table to add a context_id foreign key column.
|
|
41
|
+
* Idempotent — safe to call on every startup.
|
|
42
|
+
*/
|
|
43
|
+
export function ensureContextSchema(db) {
|
|
44
|
+
db.exec(`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS contexts (
|
|
46
|
+
id INTEGER PRIMARY KEY,
|
|
47
|
+
agent_id TEXT NOT NULL,
|
|
48
|
+
session_key TEXT NOT NULL,
|
|
49
|
+
conversation_id INTEGER REFERENCES conversations(id),
|
|
50
|
+
head_message_id INTEGER REFERENCES messages(id),
|
|
51
|
+
parent_context_id INTEGER REFERENCES contexts(id),
|
|
52
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
53
|
+
created_at TEXT NOT NULL,
|
|
54
|
+
updated_at TEXT NOT NULL,
|
|
55
|
+
metadata_json TEXT
|
|
56
|
+
)
|
|
57
|
+
`);
|
|
58
|
+
db.exec(`
|
|
59
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_contexts_active_session
|
|
60
|
+
ON contexts(agent_id, session_key, status)
|
|
61
|
+
WHERE status = 'active'
|
|
62
|
+
`);
|
|
63
|
+
db.exec(`
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_contexts_head
|
|
65
|
+
ON contexts(head_message_id)
|
|
66
|
+
`);
|
|
67
|
+
// ALTER messages table to add context_id column (PRAGMA guard)
|
|
68
|
+
const msgCols = db.prepare('PRAGMA table_info(messages)').all().map(r => r.name);
|
|
69
|
+
if (!msgCols.includes('context_id')) {
|
|
70
|
+
db.exec('ALTER TABLE messages ADD COLUMN context_id INTEGER REFERENCES contexts(id)');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// ─── Context Operations ─────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Get the active context for an agent + session pair.
|
|
76
|
+
* Returns null if no active context exists.
|
|
77
|
+
*/
|
|
78
|
+
export function getActiveContext(db, agentId, sessionKey) {
|
|
79
|
+
const row = db
|
|
80
|
+
.prepare('SELECT * FROM contexts WHERE agent_id = ? AND session_key = ? AND status = ?')
|
|
81
|
+
.get(agentId, sessionKey, 'active');
|
|
82
|
+
if (!row)
|
|
83
|
+
return null;
|
|
84
|
+
return parseContextRow(row);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get the active context for an agent + session, creating one if none exists.
|
|
88
|
+
*
|
|
89
|
+
* If an active context already exists, returns it unchanged.
|
|
90
|
+
* Otherwise INSERTs a new context with status='active', head_message_id=NULL,
|
|
91
|
+
* and the given conversationId.
|
|
92
|
+
*
|
|
93
|
+
* Idempotent — safe to call repeatedly.
|
|
94
|
+
*/
|
|
95
|
+
export function getOrCreateActiveContext(db, agentId, sessionKey, conversationId) {
|
|
96
|
+
const existing = getActiveContext(db, agentId, sessionKey);
|
|
97
|
+
if (existing)
|
|
98
|
+
return existing;
|
|
99
|
+
const now = nowIso();
|
|
100
|
+
const result = db
|
|
101
|
+
.prepare(`INSERT INTO contexts (agent_id, session_key, conversation_id, head_message_id, parent_context_id, status, created_at, updated_at, metadata_json)
|
|
102
|
+
VALUES (?, ?, ?, NULL, NULL, 'active', ?, ?, NULL)`)
|
|
103
|
+
.run(agentId, sessionKey, conversationId, now, now);
|
|
104
|
+
return {
|
|
105
|
+
id: Number(result.lastInsertRowid),
|
|
106
|
+
agentId,
|
|
107
|
+
sessionKey,
|
|
108
|
+
conversationId,
|
|
109
|
+
headMessageId: null,
|
|
110
|
+
parentContextId: null,
|
|
111
|
+
status: 'active',
|
|
112
|
+
createdAt: now,
|
|
113
|
+
updatedAt: now,
|
|
114
|
+
metadataJson: null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Update the head message pointer for a context.
|
|
119
|
+
*
|
|
120
|
+
* Monotone forward: only updates if messageId > current head_message_id
|
|
121
|
+
* (or current is NULL). This prevents accidental regression of the head
|
|
122
|
+
* pointer, matching the compaction-fence monotone progress pattern.
|
|
123
|
+
*/
|
|
124
|
+
export function updateContextHead(db, contextId, messageId) {
|
|
125
|
+
const now = nowIso();
|
|
126
|
+
const row = db
|
|
127
|
+
.prepare('SELECT head_message_id FROM contexts WHERE id = ?')
|
|
128
|
+
.get(contextId);
|
|
129
|
+
if (!row)
|
|
130
|
+
return;
|
|
131
|
+
// Monotone forward: only advance, never regress
|
|
132
|
+
if (row.head_message_id === null || messageId > row.head_message_id) {
|
|
133
|
+
db.prepare('UPDATE contexts SET head_message_id = ?, updated_at = ? WHERE id = ?').run(messageId, now, contextId);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Archive a context, setting its status to 'archived'.
|
|
138
|
+
* Idempotent — no-op if already archived.
|
|
139
|
+
*/
|
|
140
|
+
export function archiveContext(db, contextId) {
|
|
141
|
+
const now = nowIso();
|
|
142
|
+
db.prepare(`UPDATE contexts SET status = 'archived', updated_at = ? WHERE id = ? AND status != 'archived'`).run(now, contextId);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Rotate a session's active context: archive the current active context
|
|
146
|
+
* and create a new one, optionally linking back via parent_context_id.
|
|
147
|
+
*
|
|
148
|
+
* Used on session restarts/rotations so the new context starts with a
|
|
149
|
+
* clean head pointer instead of inheriting the stale tail.
|
|
150
|
+
*
|
|
151
|
+
* Returns the newly created active context.
|
|
152
|
+
* If no active context exists, simply creates one (no archive step).
|
|
153
|
+
*/
|
|
154
|
+
export function rotateSessionContext(db, agentId, sessionKey, conversationId) {
|
|
155
|
+
const existing = getActiveContext(db, agentId, sessionKey);
|
|
156
|
+
if (existing) {
|
|
157
|
+
archiveContext(db, existing.id);
|
|
158
|
+
}
|
|
159
|
+
const now = nowIso();
|
|
160
|
+
const result = db
|
|
161
|
+
.prepare(`INSERT INTO contexts (agent_id, session_key, conversation_id, head_message_id, parent_context_id, status, created_at, updated_at, metadata_json)
|
|
162
|
+
VALUES (?, ?, ?, NULL, ?, 'active', ?, ?, NULL)`)
|
|
163
|
+
.run(agentId, sessionKey, conversationId, existing?.id ?? null, now, now);
|
|
164
|
+
return {
|
|
165
|
+
id: Number(result.lastInsertRowid),
|
|
166
|
+
agentId,
|
|
167
|
+
sessionKey,
|
|
168
|
+
conversationId,
|
|
169
|
+
headMessageId: null,
|
|
170
|
+
parentContextId: existing?.id ?? null,
|
|
171
|
+
status: 'active',
|
|
172
|
+
createdAt: now,
|
|
173
|
+
updatedAt: now,
|
|
174
|
+
metadataJson: null,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=context-store.js.map
|
package/dist/cross-agent.d.ts
CHANGED
|
@@ -25,6 +25,18 @@ export interface OrgRegistry {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Default fleet org structure.
|
|
28
|
+
*
|
|
29
|
+
* ── EXAMPLE DATA ──────────────────────────────────────────────────────────
|
|
30
|
+
* The agent names below (agent1, agent2, director1, etc.) are PLACEHOLDERS.
|
|
31
|
+
* Replace them with your own agent IDs to match your fleet configuration.
|
|
32
|
+
*
|
|
33
|
+
* Single-agent installs: you don't need to edit this. Your agent ID is
|
|
34
|
+
* resolved automatically at runtime from your OpenClaw config.
|
|
35
|
+
*
|
|
36
|
+
* Multi-agent installs: edit the agents map and orgs map below, then
|
|
37
|
+
* rebuild (`npm run build`). See INSTALL.md § "Configure your fleet" for
|
|
38
|
+
* a worked example.
|
|
39
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
28
40
|
*/
|
|
29
41
|
export declare function defaultOrgRegistry(): OrgRegistry;
|
|
30
42
|
export declare function canAccess(requester: AgentIdentity, target: AgentIdentity, visibility: MemoryVisibility, registry: OrgRegistry): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-agent.d.ts","sourceRoot":"","sources":["../src/cross-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK1C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACvC;AAED
|
|
1
|
+
{"version":3,"file":"cross-agent.d.ts","sourceRoot":"","sources":["../src/cross-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,EACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK1C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACvC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,CA2BhD;AAID,wBAAgB,SAAS,CACvB,SAAS,EAAE,aAAa,EACxB,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,gBAAgB,EAC5B,QAAQ,EAAE,WAAW,GACpB,OAAO,CAaT;AASD,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,aAAa,EACxB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,WAAW,GACpB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,CA6B3F;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,YAAY,GAAG,WAAW,CAgD3E;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,+BAAyB,CAAC;AAI5D;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,eAAe,EAC1B,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,WAAW,GACpB,OAAO,EAAE,CAgCX"}
|
package/dist/cross-agent.js
CHANGED
|
@@ -19,30 +19,42 @@
|
|
|
19
19
|
import { FleetStore } from './fleet-store.js';
|
|
20
20
|
/**
|
|
21
21
|
* Default fleet org structure.
|
|
22
|
+
*
|
|
23
|
+
* ── EXAMPLE DATA ──────────────────────────────────────────────────────────
|
|
24
|
+
* The agent names below (agent1, agent2, director1, etc.) are PLACEHOLDERS.
|
|
25
|
+
* Replace them with your own agent IDs to match your fleet configuration.
|
|
26
|
+
*
|
|
27
|
+
* Single-agent installs: you don't need to edit this. Your agent ID is
|
|
28
|
+
* resolved automatically at runtime from your OpenClaw config.
|
|
29
|
+
*
|
|
30
|
+
* Multi-agent installs: edit the agents map and orgs map below, then
|
|
31
|
+
* rebuild (`npm run build`). See INSTALL.md § "Configure your fleet" for
|
|
32
|
+
* a worked example.
|
|
33
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
22
34
|
*/
|
|
23
35
|
export function defaultOrgRegistry() {
|
|
24
36
|
const agents = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
agent1: { agentId: 'agent1', tier: 'council' },
|
|
38
|
+
agent2: { agentId: 'agent2', tier: 'council' },
|
|
39
|
+
agent4: { agentId: 'agent4', tier: 'council' },
|
|
40
|
+
agent3: { agentId: 'agent3', tier: 'council' },
|
|
41
|
+
agent6: { agentId: 'agent6', tier: 'council' },
|
|
42
|
+
agent5: { agentId: 'agent5', tier: 'council' },
|
|
43
|
+
director1: { agentId: 'director1', tier: 'director', org: 'agent1-org', councilLead: 'agent1' },
|
|
44
|
+
director2: { agentId: 'director2', tier: 'director', org: 'agent1-org', councilLead: 'agent1' },
|
|
45
|
+
director3: { agentId: 'director3', tier: 'director', org: 'agent1-org', councilLead: 'agent1' },
|
|
46
|
+
director4: { agentId: 'director4', tier: 'director', org: 'agent2-org', councilLead: 'agent2' },
|
|
47
|
+
director5: { agentId: 'director5', tier: 'director', org: 'agent2-org', councilLead: 'agent2' },
|
|
48
|
+
director6: { agentId: 'director6', tier: 'director', org: 'agent2-org', councilLead: 'agent2' },
|
|
49
|
+
director7: { agentId: 'director7', tier: 'director', org: 'agent3-org', councilLead: 'agent3' },
|
|
50
|
+
director8: { agentId: 'director8', tier: 'director', org: 'agent3-org', councilLead: 'agent3' },
|
|
51
|
+
specialist1: { agentId: 'specialist1', tier: 'specialist' },
|
|
52
|
+
specialist2: { agentId: 'specialist2', tier: 'specialist' },
|
|
41
53
|
};
|
|
42
54
|
const orgs = {
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
55
|
+
'agent1-org': ['agent1', 'director1', 'director2', 'director3'],
|
|
56
|
+
'agent2-org': ['agent2', 'director4', 'director5', 'director6'],
|
|
57
|
+
'agent3-org': ['agent3', 'director7', 'director8'],
|
|
46
58
|
};
|
|
47
59
|
return { orgs, agents };
|
|
48
60
|
}
|
package/dist/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAuC3C,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD;;;GAGG;AACH,iBAAS,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI9C;AAED;;;GAGG;AACH,iBAAS,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAIvD;AAmBD,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,CAAC;AAEpD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,aAAa,CAAwB;IAE7C,6EAA6E;IAC7E,IAAI,YAAY,IAAI,OAAO,CAE1B;gBAEW,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC;IAKnD;;;OAGG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY;IAwB3C;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI;IA0BjD;;;;;;OAMG;IACH,iBAAiB,IAAI,YAAY,GAAG,IAAI;IAsBxC;;;OAGG;IACH,YAAY,IAAI,YAAY;IAa5B;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY;IAIzC;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAClC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,GAAG,IAAI;IA2BR;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA+BxB;;OAEG;IACH,UAAU,IAAI,MAAM,EAAE;IAWtB;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAKpC;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;IAQzC;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAWzC;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IA8C/C;;;;;;;OAOG;IACH,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QACnC,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GAAG;QAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA4BzE;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY;IAc9D;;OAEG;IACH,KAAK,IAAI,IAAI;CAgBd"}
|
package/dist/db.js
CHANGED
|
@@ -15,6 +15,8 @@ import path from 'node:path';
|
|
|
15
15
|
import { migrate } from './schema.js';
|
|
16
16
|
import { migrateLibrary } from './library-schema.js';
|
|
17
17
|
import { ENGINE_VERSION } from './version.js';
|
|
18
|
+
import { ensureContextSchema } from './context-store.js';
|
|
19
|
+
import { backfillContexts } from './context-backfill.js';
|
|
18
20
|
// sqlite-vec extension loading — optional dependency
|
|
19
21
|
import { createRequire } from 'node:module';
|
|
20
22
|
let sqliteVecAvailable = null;
|
|
@@ -106,6 +108,12 @@ export class DatabaseManager {
|
|
|
106
108
|
db = new DatabaseSync(dbPath);
|
|
107
109
|
applyPragmas(db);
|
|
108
110
|
migrate(db);
|
|
111
|
+
ensureContextSchema(db);
|
|
112
|
+
// Backfill contexts for existing conversations on first run after migration
|
|
113
|
+
const contextCount = db.prepare('SELECT COUNT(*) as cnt FROM contexts').get();
|
|
114
|
+
if (contextCount.cnt === 0) {
|
|
115
|
+
backfillContexts(db);
|
|
116
|
+
}
|
|
109
117
|
this.messageDbs.set(agentId, db);
|
|
110
118
|
return db;
|
|
111
119
|
}
|
|
@@ -67,7 +67,7 @@ export interface DreamerResult {
|
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Resolve the workspace directory for an agent.
|
|
70
|
-
* Council agents live at ~/.openclaw/workspace/{agentId}/
|
|
70
|
+
* Council agents live at ~/.openclaw/workspace-council/{agentId}/
|
|
71
71
|
* Other agents at ~/.openclaw/workspace/{agentId}/
|
|
72
72
|
*/
|
|
73
73
|
export declare function resolveAgentWorkspacePath(agentId: string): Promise<string | null>;
|
|
@@ -31,7 +31,7 @@ export const DEFAULT_DREAMER_CONFIG = {
|
|
|
31
31
|
// ─── Workspace path resolution ───────────────────────────────────────────────
|
|
32
32
|
/**
|
|
33
33
|
* Resolve the workspace directory for an agent.
|
|
34
|
-
* Council agents live at ~/.openclaw/workspace/{agentId}/
|
|
34
|
+
* Council agents live at ~/.openclaw/workspace-council/{agentId}/
|
|
35
35
|
* Other agents at ~/.openclaw/workspace/{agentId}/
|
|
36
36
|
*/
|
|
37
37
|
export async function resolveAgentWorkspacePath(agentId) {
|
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export type { FleetAgent, FleetOrg, AgentCapability } from './fleet-store.js';
|
|
|
25
25
|
export { SystemStore } from './system-store.js';
|
|
26
26
|
export type { SystemState, SystemEvent } from './system-store.js';
|
|
27
27
|
export { WorkStore } from './work-store.js';
|
|
28
|
+
export { ensureContextSchema, getActiveContext, getOrCreateActiveContext, updateContextHead, archiveContext, rotateSessionContext } from './context-store.js';
|
|
29
|
+
export type { Context } from './context-store.js';
|
|
28
30
|
export type { WorkItem, WorkEvent, WorkStatus } from './work-store.js';
|
|
29
31
|
export { DesiredStateStore } from './desired-state-store.js';
|
|
30
32
|
export { evictStaleContent, DEFAULT_EVICTION_CONFIG } from './image-eviction.js';
|
|
@@ -98,8 +100,8 @@ import { type OrgRegistry } from './cross-agent.js';
|
|
|
98
100
|
*
|
|
99
101
|
* Usage:
|
|
100
102
|
* const hm = await hypermem.create({ dataDir: '~/.openclaw/hypermem' });
|
|
101
|
-
* await hm.record('
|
|
102
|
-
* const result = await hm.compose({ agentId: '
|
|
103
|
+
* await hm.record('agent1', 'agent:agent1:webchat:main', userMsg);
|
|
104
|
+
* const result = await hm.compose({ agentId: 'agent1', sessionKey: '...', ... });
|
|
103
105
|
*/
|
|
104
106
|
export declare class HyperMem {
|
|
105
107
|
readonly dbManager: DatabaseManager;
|