@yeaft/webchat-agent 0.1.538 → 0.1.540

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.538",
3
+ "version": "0.1.540",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
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,