@unblocklabs/unblock-memory 0.3.25 → 0.3.26

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.
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { createRequire } from "node:module";
5
+ import { setImmediate as yieldToEventLoop } from "node:timers/promises";
5
6
  import { readSessionManifest } from "./session-sync.js";
6
7
  import { resolveSessionSource } from "./sources.js";
7
8
  import { trainingCandidates } from "./training-candidates.js";
@@ -95,7 +96,15 @@ export async function historicalTrainingSearch(stateDir, chatTypes, cutoff, open
95
96
  FROM content_vectors cv JOIN vectors_vec v ON v.hash_seq=cv.hash||'_'||cv.seq
96
97
  WHERE cv.hash=? ORDER BY cv.seq`);
97
98
  let dimensions;
99
+ // Copy cooperatively: concurrent snapshots must not starve lease timers,
100
+ // provider responses or native-model disposal. Keep the source transaction
101
+ // open across yields so every row still comes from the same read snapshot.
102
+ let yieldAt = performance.now() + 25;
98
103
  for (const session of Object.values(manifest.sessions).sort((a, b) => a.documentPath.localeCompare(b.documentPath))) {
104
+ if (performance.now() >= yieldAt) {
105
+ await yieldToEventLoop();
106
+ yieldAt = performance.now() + 25;
107
+ }
99
108
  // Loggie projections can retroactively annotate old text using later revisions.
100
109
  // Workspace files and meetings lack immutable historical content proof here.
101
110
  if (!chatTypes.includes(session.chatType) || session.provider === "loggie" || session.startedAt >= cutoff) {
@@ -120,7 +129,11 @@ export async function historicalTrainingSearch(stateDir, chatTypes, cutoff, open
120
129
  report.sessions++;
121
130
  if (prefix.spans.length < (session.messages?.length ?? 0))
122
131
  report.truncated++;
123
- for (const chunk of chunks.all(row.hash)) {
132
+ for (const chunk of chunks.iterate(row.hash)) {
133
+ if (performance.now() >= yieldAt) {
134
+ await yieldToEventLoop();
135
+ yieldAt = performance.now() + 25;
136
+ }
124
137
  if (!Number.isSafeInteger(chunk.pos) || !Number.isSafeInteger(chunk.chunk_len) || chunk.pos < 0 || chunk.chunk_len <= 0 ||
125
138
  chunk.pos + chunk.chunk_len > prefix.body.length) {
126
139
  report.excludedChunks++;
@@ -43,6 +43,8 @@ export class TrainingStore {
43
43
  status TEXT NOT NULL CHECK(status IN ('pending','attempted','complete','failed','ambiguous')),
44
44
  result_json TEXT, error TEXT, completed_at INTEGER
45
45
  ) STRICT;
46
+ CREATE INDEX IF NOT EXISTS training_judgment_exclusions ON training_steps(json_extract(request_json,'$.identity'))
47
+ WHERE stage='judge' AND status='complete' AND json_extract(result_json,'$.excluded')=1;
46
48
  CREATE TABLE IF NOT EXISTS training_step_attempts (
47
49
  id INTEGER PRIMARY KEY, step_id TEXT NOT NULL REFERENCES training_steps(id), started_at INTEGER NOT NULL,
48
50
  finished_at INTEGER, status TEXT NOT NULL, error TEXT, result_json TEXT
@@ -128,8 +128,13 @@ No blanket failure skipping.
128
128
 
129
129
  The host must support isolated completion and grant
130
130
  `plugins.entries.unblock-memory.llm.allowModelOverride: true` with
131
- `allowedModels: ["openai/gpt-6-luna"]`. Preserve other grants. The plugin does not
132
- change its own permissions. Credentials stay with the host, never in provenance.
131
+ `allowedModels: ["openai/gpt-6-luna"]`. The operator CLI explicitly selects an
132
+ agent, so an unbound CLI runtime also requires
133
+ `plugins.entries.unblock-memory.llm.allowAgentIdOverride: true`. Preserve other
134
+ grants. The host's model catalog and selected runtime must support that exact
135
+ model with usable host-managed authentication; a model permission grant alone
136
+ does not establish model availability. The plugin does not change its own
137
+ permissions. Credentials stay with the host, never in provenance.
133
138
 
134
139
  ## Export and consolidation
135
140
 
@@ -145,3 +150,16 @@ identical-input groups together across nodes for train/validation. Use the actua
145
150
  **LFM2.5-230M-Base** tokenizer and an explicit causal-LM input/target format before
146
151
  fine-tuning; the interim byte limit is not a token count. Runtime abstention remains
147
152
  a separate TypeSafe gate; positive-only query training does not teach abstention.
153
+
154
+ The repository helper uses a pinned official tokenizer revision and chat template,
155
+ with assistant-only loss labels, a conservative 32,768-token training ceiling
156
+ (the model config supports 128,000 positions), and deterministic grouped splits:
157
+
158
+ ```sh
159
+ python scripts/prepare-query-training.py /private/node1-queries.jsonl /private/node2-queries.jsonl \
160
+ --output /private/new-prepared-directory --cache-dir /private/tokenizer-cache
161
+ ```
162
+
163
+ Install `transformers` and `jinja2` in a separate environment first. It downloads
164
+ tokenizer files only, never model weights. Regex secret screening is not an
165
+ exhaustive privacy audit; inspect quarantine references before including those rows.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "unblock-memory",
3
3
  "name": "Unblock Memory",
4
- "version": "0.3.25",
4
+ "version": "0.3.26",
5
5
  "description": "Indexes, retrieves, and analyzes configured workspace memory with existing QMD vectors.",
6
6
  "kind": "memory",
7
7
  "activation": { "onStartup": true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unblocklabs/unblock-memory",
3
- "version": "0.3.25",
3
+ "version": "0.3.26",
4
4
  "description": "Workspace-native memory for OpenClaw, powered by QMD",
5
5
  "type": "module",
6
6
  "license": "MIT",