@yeaft/webchat-agent 0.1.539 → 0.1.541
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/package.json +1 -1
- package/unify/engine.js +11 -1
- package/unify/memory/recompression.js +122 -0
- package/unify/session.js +16 -0
- package/unify/threads/engine-instance.js +2 -0
package/package.json
CHANGED
package/unify/engine.js
CHANGED
|
@@ -122,6 +122,9 @@ export class Engine {
|
|
|
122
122
|
/** @type {import('./memory/store.js').MemoryStore|null} */
|
|
123
123
|
#memoryStore;
|
|
124
124
|
|
|
125
|
+
/** @type {object|null} — R6 memory shard store (task-334f) */
|
|
126
|
+
#memoryShardStore;
|
|
127
|
+
|
|
125
128
|
/** @type {import('./tools/registry.js').ToolRegistry|null} */
|
|
126
129
|
#toolRegistry;
|
|
127
130
|
|
|
@@ -176,7 +179,7 @@ export class Engine {
|
|
|
176
179
|
* yeaftDir?: string,
|
|
177
180
|
* }} params
|
|
178
181
|
*/
|
|
179
|
-
constructor({ adapter, trace, config, conversationStore, memoryStore, toolRegistry, skillManager, mcpManager, yeaftDir }) {
|
|
182
|
+
constructor({ adapter, trace, config, conversationStore, memoryStore, memoryShardStore, toolRegistry, skillManager, mcpManager, yeaftDir }) {
|
|
180
183
|
this.#adapter = adapter;
|
|
181
184
|
this.#trace = trace;
|
|
182
185
|
this.#config = config;
|
|
@@ -184,6 +187,7 @@ export class Engine {
|
|
|
184
187
|
this.#traceId = randomUUID();
|
|
185
188
|
this.#conversationStore = conversationStore || null;
|
|
186
189
|
this.#memoryStore = memoryStore || null;
|
|
190
|
+
this.#memoryShardStore = memoryShardStore || null;
|
|
187
191
|
this.#toolRegistry = toolRegistry || null;
|
|
188
192
|
this.#skillManager = skillManager || null;
|
|
189
193
|
this.#mcpManager = mcpManager || null;
|
|
@@ -330,6 +334,7 @@ export class Engine {
|
|
|
330
334
|
mcpManager: this.#mcpManager,
|
|
331
335
|
skillManager: this.#skillManager,
|
|
332
336
|
memoryStore: this.#memoryStore,
|
|
337
|
+
memoryShardStore: this.#memoryShardStore,
|
|
333
338
|
conversationStore: this.#conversationStore,
|
|
334
339
|
adapter: this.#adapter,
|
|
335
340
|
config: this.#config,
|
|
@@ -954,6 +959,11 @@ export class Engine {
|
|
|
954
959
|
return this.#memoryStore;
|
|
955
960
|
}
|
|
956
961
|
|
|
962
|
+
/** @returns {object|null} — R6 memory shard store (task-334f) */
|
|
963
|
+
get memoryShardStore() {
|
|
964
|
+
return this.#memoryShardStore;
|
|
965
|
+
}
|
|
966
|
+
|
|
957
967
|
/** @returns {import('./tools/registry.js').ToolRegistry|null} */
|
|
958
968
|
get toolRegistry() { return this.#toolRegistry; }
|
|
959
969
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recompression.js — task-334f Re-compression hook.
|
|
3
|
+
*
|
|
4
|
+
* Provides `checkRecompression(memoryShardStore)` which inspects each shard's
|
|
5
|
+
* utilization (live entry bytes vs total shard file bytes). When utilization
|
|
6
|
+
* drops below 50% (configurable), the shard is compacted in-place.
|
|
7
|
+
*
|
|
8
|
+
* This is designed to be called:
|
|
9
|
+
* - After `remove()` calls (which leave tombstone gaps)
|
|
10
|
+
* - After `supersede()` chains (old entries inflate shard size)
|
|
11
|
+
* - By dream (334g) on its periodic sweep
|
|
12
|
+
*
|
|
13
|
+
* The hook does NOT make deletion decisions — it only reclaims dead space.
|
|
14
|
+
* Dream owns the "what to delete" logic; this module owns "when to defrag".
|
|
15
|
+
*
|
|
16
|
+
* Reference: §Δ17.5 Compact job / §Δ26.3 soft-cap semantics.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Default utilization threshold below which a shard gets compacted. */
|
|
20
|
+
export const DEFAULT_UTILIZATION_THRESHOLD = 0.5;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Inspect all shards in a memory shard store and compact any whose
|
|
24
|
+
* utilization ratio (live entry bytes / total shard bytes) is below
|
|
25
|
+
* the threshold.
|
|
26
|
+
*
|
|
27
|
+
* @param {object} store — opened via `openMemoryShardStore()`
|
|
28
|
+
* @param {{ threshold?: number }} [opts]
|
|
29
|
+
* @returns {{ compacted: string[], skipped: string[], stats: Record<string, { entries: number, bytes: number, liveBytes: number, utilization: number }> }}
|
|
30
|
+
*/
|
|
31
|
+
export function checkRecompression(store, opts = {}) {
|
|
32
|
+
if (!store || typeof store.stats !== 'function') {
|
|
33
|
+
return { compacted: [], skipped: [], stats: {} };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const threshold = opts.threshold ?? DEFAULT_UTILIZATION_THRESHOLD;
|
|
37
|
+
const { shards, count } = store.stats();
|
|
38
|
+
const compacted = [];
|
|
39
|
+
const skipped = [];
|
|
40
|
+
const shardStats = {};
|
|
41
|
+
|
|
42
|
+
for (const [name, bucket] of Object.entries(shards)) {
|
|
43
|
+
const totalBytes = bucket.bytes || 0;
|
|
44
|
+
const entryCount = bucket.entries || 0;
|
|
45
|
+
|
|
46
|
+
// Estimate live bytes from the index: sum of all entry byteLen for this shard.
|
|
47
|
+
// The inner store's query returns records with meta but not byteLen directly.
|
|
48
|
+
// Use the stats bucket which tracks entry count and total file bytes.
|
|
49
|
+
// A shard with 0 entries but >0 bytes is 0% utilization → compact.
|
|
50
|
+
// A shard with entries but totalBytes=0 is fine (no file yet).
|
|
51
|
+
if (totalBytes === 0) {
|
|
52
|
+
shardStats[name] = { entries: entryCount, bytes: 0, liveBytes: 0, utilization: 1.0 };
|
|
53
|
+
skipped.push(name);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// For utilization, we use inner store's index to sum live entry byte lengths.
|
|
58
|
+
const inner = store._innerForTest;
|
|
59
|
+
let liveBytes = 0;
|
|
60
|
+
if (inner && typeof inner.getIndex === 'function') {
|
|
61
|
+
const index = inner.getIndex();
|
|
62
|
+
for (const rec of index.entries) {
|
|
63
|
+
if (rec.shard === name) liveBytes += (rec.byteLen || 0);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// Fallback: assume fully utilized if we can't inspect
|
|
67
|
+
liveBytes = totalBytes;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const utilization = liveBytes / totalBytes;
|
|
71
|
+
shardStats[name] = { entries: entryCount, bytes: totalBytes, liveBytes, utilization };
|
|
72
|
+
|
|
73
|
+
if (utilization < threshold && entryCount > 0) {
|
|
74
|
+
// Compact via the underlying shard store
|
|
75
|
+
if (inner && typeof inner.compact === 'function') {
|
|
76
|
+
inner.compact(name);
|
|
77
|
+
compacted.push(name);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
skipped.push(name);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { compacted, skipped, stats: shardStats };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if any shard needs recompression without actually doing it.
|
|
89
|
+
* Returns the list of shard names that would be compacted.
|
|
90
|
+
*
|
|
91
|
+
* @param {object} store
|
|
92
|
+
* @param {{ threshold?: number }} [opts]
|
|
93
|
+
* @returns {string[]} — shard names below utilization threshold
|
|
94
|
+
*/
|
|
95
|
+
export function needsRecompression(store, opts = {}) {
|
|
96
|
+
if (!store || typeof store.stats !== 'function') return [];
|
|
97
|
+
|
|
98
|
+
const threshold = opts.threshold ?? DEFAULT_UTILIZATION_THRESHOLD;
|
|
99
|
+
const { shards } = store.stats();
|
|
100
|
+
const result = [];
|
|
101
|
+
|
|
102
|
+
const inner = store._innerForTest;
|
|
103
|
+
if (!inner || typeof inner.getIndex !== 'function') return [];
|
|
104
|
+
|
|
105
|
+
const index = inner.getIndex();
|
|
106
|
+
|
|
107
|
+
for (const [name, bucket] of Object.entries(shards)) {
|
|
108
|
+
const totalBytes = bucket.bytes || 0;
|
|
109
|
+
if (totalBytes === 0 || (bucket.entries || 0) === 0) continue;
|
|
110
|
+
|
|
111
|
+
let liveBytes = 0;
|
|
112
|
+
for (const rec of index.entries) {
|
|
113
|
+
if (rec.shard === name) liveBytes += (rec.byteLen || 0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (liveBytes / totalBytes < threshold) {
|
|
117
|
+
result.push(name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result;
|
|
122
|
+
}
|
package/unify/session.js
CHANGED
|
@@ -19,6 +19,7 @@ import { createTrace } from './debug-trace.js';
|
|
|
19
19
|
import { createLLMAdapter } from './llm/adapter.js';
|
|
20
20
|
import { ConversationStore } from './conversation/persist.js';
|
|
21
21
|
import { MemoryStore } from './memory/store.js';
|
|
22
|
+
import { openMemoryShardStore } from './memory/shard-store.js';
|
|
22
23
|
import { SkillManager, createSkillManager } from './skills.js';
|
|
23
24
|
import { MCPManager } from './mcp.js';
|
|
24
25
|
import { createFullRegistry } from './tools/index.js';
|
|
@@ -151,6 +152,18 @@ export async function loadSession(options = {}) {
|
|
|
151
152
|
const conversationStore = new ConversationStore(yeaftDir);
|
|
152
153
|
const memoryStore = new MemoryStore(yeaftDir);
|
|
153
154
|
|
|
155
|
+
// ─── 5-shard. Open R6 memory shard store (task-334f) ──────
|
|
156
|
+
// VP-level memory shard store rooted at ~/.yeaft/memory/vp/default.
|
|
157
|
+
// The 'default' VP matches the single-user Unify mode; R6 multi-VP
|
|
158
|
+
// callers open per-VP stores via openMemoryShardStore() directly.
|
|
159
|
+
const memoryShardDir = join(yeaftDir, 'memory', 'vp', 'default');
|
|
160
|
+
let memoryShardStore = null;
|
|
161
|
+
try {
|
|
162
|
+
memoryShardStore = openMemoryShardStore(memoryShardDir, 'vp');
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.warn(`[Yeaft] Failed to open R6 memory shard store: ${err?.message || err}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
154
167
|
// ─── 5a. Initialize task store ─────────────────────────
|
|
155
168
|
initTaskStore(yeaftDir, { readOnly: config._readOnly || false });
|
|
156
169
|
|
|
@@ -212,6 +225,7 @@ export async function loadSession(options = {}) {
|
|
|
212
225
|
config,
|
|
213
226
|
conversationStore,
|
|
214
227
|
memoryStore,
|
|
228
|
+
memoryShardStore,
|
|
215
229
|
toolRegistry,
|
|
216
230
|
skillManager,
|
|
217
231
|
mcpManager,
|
|
@@ -229,6 +243,7 @@ export async function loadSession(options = {}) {
|
|
|
229
243
|
config,
|
|
230
244
|
conversationStore,
|
|
231
245
|
memoryStore,
|
|
246
|
+
memoryShardStore,
|
|
232
247
|
toolRegistry,
|
|
233
248
|
skillManager,
|
|
234
249
|
mcpManager,
|
|
@@ -297,6 +312,7 @@ export async function loadSession(options = {}) {
|
|
|
297
312
|
config,
|
|
298
313
|
conversationStore,
|
|
299
314
|
memoryStore,
|
|
315
|
+
memoryShardStore,
|
|
300
316
|
skillManager,
|
|
301
317
|
mcpManager,
|
|
302
318
|
toolRegistry,
|
|
@@ -192,6 +192,7 @@ export function createEngineInstance(deps) {
|
|
|
192
192
|
config,
|
|
193
193
|
conversationStore,
|
|
194
194
|
memoryStore,
|
|
195
|
+
memoryShardStore,
|
|
195
196
|
toolRegistry,
|
|
196
197
|
skillManager,
|
|
197
198
|
mcpManager,
|
|
@@ -204,6 +205,7 @@ export function createEngineInstance(deps) {
|
|
|
204
205
|
config,
|
|
205
206
|
conversationStore,
|
|
206
207
|
memoryStore,
|
|
208
|
+
memoryShardStore,
|
|
207
209
|
toolRegistry,
|
|
208
210
|
skillManager,
|
|
209
211
|
mcpManager,
|