@unblocklabs/unblock-memory 0.3.28 → 0.3.29
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/dist/src/agent-transcript.d.ts +8 -0
- package/dist/src/agent-transcript.js +29 -0
- package/dist/src/date-option.d.ts +1 -0
- package/dist/src/date-option.js +8 -0
- package/dist/src/manager.js +30 -57
- package/dist/src/memory-whisperer.js +1 -2
- package/dist/src/response-episodes.js +4 -11
- package/dist/src/response-runtime.js +1 -8
- package/dist/src/session-sync.js +8 -22
- package/dist/src/skill-whisperer.d.ts +0 -1
- package/dist/src/skill-whisperer.js +1 -9
- package/dist/src/training-input.js +4 -11
- package/dist/src/training-runtime.js +1 -8
- package/dist/src/whisperer-context.d.ts +1 -0
- package/dist/src/whisperer-context.js +9 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
/** The OpenClaw agent database versions whose transcript layout we read. */
|
|
3
|
+
export declare function agentTranscriptSchemaVersion(db: DatabaseSync, errorMessage?: string): number;
|
|
4
|
+
export declare function assertAgentTranscriptIdentity(db: DatabaseSync, agentId: string, version: number, errorMessage?: string): void;
|
|
5
|
+
export declare function assertAgentTranscriptSchema(db: DatabaseSync, agentId: string, errorMessage?: string): void;
|
|
6
|
+
export declare const ACTIVE_EVENTS_FROM = "FROM session_transcript_active_events a\n JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq";
|
|
7
|
+
export declare const ACTIVE_EVENT_COUNT_SQL = "SELECT COUNT(*) n,COALESCE(SUM(length(e.event_json)),0) bytes\n FROM session_transcript_active_events a\n JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq WHERE a.session_id=?";
|
|
8
|
+
export declare const ACTIVE_EVENT_ROWS_SQL = "SELECT e.seq,e.event_json eventJson,e.created_at createdAt\n FROM session_transcript_active_events a\n JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq WHERE a.session_id=? ORDER BY a.active_position";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The OpenClaw agent database versions whose transcript layout we read. */
|
|
2
|
+
export function agentTranscriptSchemaVersion(db, errorMessage) {
|
|
3
|
+
const version = db.prepare("PRAGMA user_version").get()?.user_version;
|
|
4
|
+
if (typeof version !== "number" || ![17, 18, 19].includes(version)) {
|
|
5
|
+
throw new Error(errorMessage ?? "unsupported OpenClaw agent database schema: expected one of 17, 18, 19, " +
|
|
6
|
+
`found ${String(version ?? "unknown")}`);
|
|
7
|
+
}
|
|
8
|
+
return version;
|
|
9
|
+
}
|
|
10
|
+
export function assertAgentTranscriptIdentity(db, agentId, version, errorMessage) {
|
|
11
|
+
const fail = (message) => { throw new Error(errorMessage ?? message); };
|
|
12
|
+
const meta = db.prepare("SELECT role, schema_version AS schemaVersion, agent_id AS agentId " +
|
|
13
|
+
"FROM schema_meta WHERE meta_key = 'primary' LIMIT 1").get();
|
|
14
|
+
if (meta?.role !== "agent" || meta.schemaVersion !== version) {
|
|
15
|
+
fail("unsupported OpenClaw agent database primary schema metadata");
|
|
16
|
+
}
|
|
17
|
+
if (meta?.agentId !== agentId) {
|
|
18
|
+
fail(`OpenClaw agent database belongs to ${String(meta?.agentId)}, not ${agentId}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function assertAgentTranscriptSchema(db, agentId, errorMessage) {
|
|
22
|
+
assertAgentTranscriptIdentity(db, agentId, agentTranscriptSchemaVersion(db, errorMessage), errorMessage);
|
|
23
|
+
}
|
|
24
|
+
export const ACTIVE_EVENTS_FROM = `FROM session_transcript_active_events a
|
|
25
|
+
JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq`;
|
|
26
|
+
export const ACTIVE_EVENT_COUNT_SQL = `SELECT COUNT(*) n,COALESCE(SUM(length(e.event_json)),0) bytes
|
|
27
|
+
${ACTIVE_EVENTS_FROM} WHERE a.session_id=?`;
|
|
28
|
+
export const ACTIVE_EVENT_ROWS_SQL = `SELECT e.seq,e.event_json eventJson,e.created_at createdAt
|
|
29
|
+
${ACTIVE_EVENTS_FROM} WHERE a.session_id=? ORDER BY a.active_position`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function dateOption(value: string | undefined, fallback: number): number;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function dateOption(value, fallback) {
|
|
2
|
+
if (value === undefined)
|
|
3
|
+
return fallback;
|
|
4
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value) {
|
|
5
|
+
throw new Error("Dates must be valid YYYY-MM-DD UTC dates");
|
|
6
|
+
}
|
|
7
|
+
return Date.parse(value);
|
|
8
|
+
}
|
package/dist/src/manager.js
CHANGED
|
@@ -487,50 +487,43 @@ export class QmdMemoryManager {
|
|
|
487
487
|
.filter((source) => selected.has(source.corpus))
|
|
488
488
|
.map((source) => source.collection);
|
|
489
489
|
}
|
|
490
|
+
async #updateAndEmbed(store, collection, force) {
|
|
491
|
+
const update = await store.update({ collections: collection ? [collection] : [] });
|
|
492
|
+
this.#cleanupRemovedDocuments?.(update.updated + update.removed);
|
|
493
|
+
const analysisStore = store;
|
|
494
|
+
const changed = update.indexed + update.updated + update.removed > 0 || update.needsEmbedding > 0;
|
|
495
|
+
if ((changed || force) && analysisStore.internal)
|
|
496
|
+
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
497
|
+
const embed = await store.embed({
|
|
498
|
+
...(collection ? { collection } : {}),
|
|
499
|
+
force,
|
|
500
|
+
chunkStrategy: "semantic",
|
|
501
|
+
});
|
|
502
|
+
this.#recordEmbedding(embed);
|
|
503
|
+
const chunksEmbedded = completedEmbeddingCount(embed);
|
|
504
|
+
if (!changed && !force && chunksEmbedded > 0 && analysisStore.internal) {
|
|
505
|
+
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
506
|
+
}
|
|
507
|
+
return chunksEmbedded;
|
|
508
|
+
}
|
|
509
|
+
async #refreshIndexStatus(store) {
|
|
510
|
+
const status = await store.getStatus();
|
|
511
|
+
const collections = await store.listCollections();
|
|
512
|
+
this.#files = collections.reduce((total, collection) => total + collection.active_count, 0);
|
|
513
|
+
this.#dirty = status.needsEmbedding > 0;
|
|
514
|
+
}
|
|
490
515
|
sync(params) {
|
|
491
516
|
const run = async () => {
|
|
492
517
|
const store = await this.#getStore();
|
|
493
518
|
this.#dirty = true;
|
|
494
|
-
const analysisStore = store;
|
|
495
519
|
const collections = this.#qmdSources().filter((source) => source.kind !== "sessions");
|
|
496
|
-
let analysisMarkedStale = false;
|
|
497
|
-
const markAnalysisStale = () => {
|
|
498
|
-
if (analysisMarkedStale || !analysisStore.internal)
|
|
499
|
-
return;
|
|
500
|
-
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
501
|
-
analysisMarkedStale = true;
|
|
502
|
-
};
|
|
503
520
|
if (collections.length === 0) {
|
|
504
|
-
|
|
505
|
-
this.#cleanupRemovedDocuments?.(update.updated + update.removed);
|
|
506
|
-
if (update.indexed + update.updated + update.removed > 0 ||
|
|
507
|
-
update.needsEmbedding > 0 || params?.force === true) {
|
|
508
|
-
markAnalysisStale();
|
|
509
|
-
}
|
|
510
|
-
const embed = await store.embed({ force: params?.force, chunkStrategy: "semantic" });
|
|
511
|
-
this.#recordEmbedding(embed);
|
|
512
|
-
if (completedEmbeddingCount(embed) > 0)
|
|
513
|
-
markAnalysisStale();
|
|
521
|
+
await this.#updateAndEmbed(store, undefined, params?.force);
|
|
514
522
|
}
|
|
515
523
|
for (const source of collections) {
|
|
516
|
-
|
|
517
|
-
this.#cleanupRemovedDocuments?.(update.updated + update.removed);
|
|
518
|
-
const changed = update.indexed + update.updated + update.removed > 0 || update.needsEmbedding > 0;
|
|
519
|
-
if (changed || params?.force === true)
|
|
520
|
-
markAnalysisStale();
|
|
521
|
-
const embed = await store.embed({
|
|
522
|
-
collection: source.collection,
|
|
523
|
-
force: params?.force,
|
|
524
|
-
chunkStrategy: "semantic",
|
|
525
|
-
});
|
|
526
|
-
this.#recordEmbedding(embed);
|
|
527
|
-
if (completedEmbeddingCount(embed) > 0)
|
|
528
|
-
markAnalysisStale();
|
|
524
|
+
await this.#updateAndEmbed(store, source.collection, params?.force);
|
|
529
525
|
}
|
|
530
|
-
|
|
531
|
-
const indexedCollections = await store.listCollections();
|
|
532
|
-
this.#files = indexedCollections.reduce((total, collection) => total + collection.active_count, 0);
|
|
533
|
-
this.#dirty = status.needsEmbedding > 0;
|
|
526
|
+
await this.#refreshIndexStatus(store);
|
|
534
527
|
};
|
|
535
528
|
return this.#enqueue(run);
|
|
536
529
|
}
|
|
@@ -548,24 +541,7 @@ export class QmdMemoryManager {
|
|
|
548
541
|
index: async () => {
|
|
549
542
|
onPhase?.("indexing");
|
|
550
543
|
const store = await this.#getStore();
|
|
551
|
-
|
|
552
|
-
this.#cleanupRemovedDocuments?.(update.updated + update.removed);
|
|
553
|
-
const analysisStore = store;
|
|
554
|
-
const invalidatesAnalysis = update.indexed + update.updated + update.removed > 0 ||
|
|
555
|
-
update.needsEmbedding > 0;
|
|
556
|
-
if (invalidatesAnalysis && analysisStore.internal) {
|
|
557
|
-
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
558
|
-
}
|
|
559
|
-
const embed = await store.embed({
|
|
560
|
-
collection: sessions.collection,
|
|
561
|
-
chunkStrategy: "semantic",
|
|
562
|
-
});
|
|
563
|
-
const chunksEmbedded = completedEmbeddingCount(embed);
|
|
564
|
-
this.#recordEmbedding(embed);
|
|
565
|
-
if (!invalidatesAnalysis && chunksEmbedded > 0 && analysisStore.internal) {
|
|
566
|
-
markMemoryAnalysisStale(analysisStore.internal.db);
|
|
567
|
-
}
|
|
568
|
-
return chunksEmbedded;
|
|
544
|
+
return this.#updateAndEmbed(store, sessions.collection);
|
|
569
545
|
},
|
|
570
546
|
});
|
|
571
547
|
this.#sessionMetadata = sessionMetadataByPath(synced.manifest);
|
|
@@ -573,10 +549,7 @@ export class QmdMemoryManager {
|
|
|
573
549
|
if (synced.result.skipReason)
|
|
574
550
|
return synced.result;
|
|
575
551
|
const store = await this.#getStore();
|
|
576
|
-
|
|
577
|
-
const collections = await store.listCollections();
|
|
578
|
-
this.#files = collections.reduce((total, collection) => total + collection.active_count, 0);
|
|
579
|
-
this.#dirty = status.needsEmbedding > 0;
|
|
552
|
+
await this.#refreshIndexStatus(store);
|
|
580
553
|
return synced.result;
|
|
581
554
|
});
|
|
582
555
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { buildSkillWhispererQuery } from "./skill-whisperer.js";
|
|
3
2
|
import { judgeTypeSafeMemories } from "./typesafe.js";
|
|
4
3
|
import { resolveTypeSafeApiKey, TypeSafeRequestError } from "./typesafe-client.js";
|
|
5
|
-
import { memoryConversation } from "./whisperer-context.js";
|
|
4
|
+
import { buildSkillWhispererQuery, memoryConversation } from "./whisperer-context.js";
|
|
6
5
|
import { complementaryIndices, reviewMemoryRedundancy } from "./typesafe-review.js";
|
|
7
6
|
import { MlxQueryGenerator, queryConversation } from "./mlx-query.js";
|
|
8
7
|
import { judgeTrainingInput, TRAINING_GATE_THRESHOLD } from "./training-gate.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { ACTIVE_EVENT_COUNT_SQL, ACTIVE_EVENT_ROWS_SQL, assertAgentTranscriptSchema } from "./agent-transcript.js";
|
|
3
4
|
import { messageText } from "./whisperer-context.js";
|
|
4
5
|
import { responseUserText } from "./response-text.js";
|
|
5
6
|
export const RESPONSE_EXTRACTOR_VERSION = 5;
|
|
@@ -182,11 +183,7 @@ export class ResponseTranscriptReader {
|
|
|
182
183
|
this.#db = new DatabaseSync(path, { readOnly: true });
|
|
183
184
|
try {
|
|
184
185
|
this.#db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000");
|
|
185
|
-
|
|
186
|
-
const meta = this.#db.prepare("SELECT role,agent_id,schema_version FROM schema_meta WHERE meta_key='primary'").get();
|
|
187
|
-
if (![17, 18, 19].includes(Number(version)) || meta?.role !== "agent" || meta.agent_id !== agentId || meta.schema_version !== version) {
|
|
188
|
-
throw new Error("Unsupported response-audit transcript schema or agent");
|
|
189
|
-
}
|
|
186
|
+
assertAgentTranscriptSchema(this.#db, agentId, "Unsupported response-audit transcript schema or agent");
|
|
190
187
|
}
|
|
191
188
|
catch (error) {
|
|
192
189
|
this.#db.close();
|
|
@@ -219,14 +216,10 @@ export class ResponseTranscriptReader {
|
|
|
219
216
|
return null;
|
|
220
217
|
const session = { sessionId, accountId: String(window.accountId),
|
|
221
218
|
conversationId: String(window.conversationId), chatType: String(window.chatType) };
|
|
222
|
-
const count = this.#db.prepare(
|
|
223
|
-
FROM session_transcript_active_events a JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq
|
|
224
|
-
WHERE a.session_id=?`).get(session.sessionId);
|
|
219
|
+
const count = this.#db.prepare(ACTIVE_EVENT_COUNT_SQL).get(session.sessionId);
|
|
225
220
|
if (Number(count.n) > MAX_EVENTS || Number(count.bytes) > MAX_SESSION_BYTES)
|
|
226
221
|
return undefined;
|
|
227
|
-
const rows = this.#db.prepare(
|
|
228
|
-
FROM session_transcript_active_events a JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq
|
|
229
|
-
WHERE a.session_id=? ORDER BY a.active_position`).all(session.sessionId);
|
|
222
|
+
const rows = this.#db.prepare(ACTIVE_EVENT_ROWS_SQL).all(session.sessionId);
|
|
230
223
|
// Exact active content catches in-place edits and branch changes even when writer
|
|
231
224
|
// watermarks are absent. Raw text is never retained in the checkpoint database.
|
|
232
225
|
const revision = hash([RESPONSE_EXTRACTOR_VERSION, session, config.historyMessages, [...config.senderIds].sort(), rows]);
|
|
@@ -6,14 +6,7 @@ import { normalizeAgentIdStrict } from "openclaw/plugin-sdk/routing";
|
|
|
6
6
|
import { resolveSources } from "./sources.js";
|
|
7
7
|
import { auditResponses, responseCohort } from "./response-audit.js";
|
|
8
8
|
import { ResponseAuditStore } from "./response-store.js";
|
|
9
|
-
|
|
10
|
-
if (value === undefined)
|
|
11
|
-
return fallback;
|
|
12
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value) {
|
|
13
|
-
throw new Error("Dates must be valid YYYY-MM-DD UTC dates");
|
|
14
|
-
}
|
|
15
|
-
return Date.parse(value);
|
|
16
|
-
}
|
|
9
|
+
import { dateOption } from "./date-option.js";
|
|
17
10
|
export function registerResponseAudit(api, config) {
|
|
18
11
|
const options = (cfg, id) => {
|
|
19
12
|
const normalized = normalizeAgentIdStrict(id);
|
package/dist/src/session-sync.js
CHANGED
|
@@ -3,10 +3,10 @@ import { existsSync, lstatSync, readFileSync, statSync } from "node:fs";
|
|
|
3
3
|
import { chmod, mkdir, readFile, rename, unlink, utimes, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { DatabaseSync } from "node:sqlite";
|
|
6
|
+
import { ACTIVE_EVENTS_FROM, agentTranscriptSchemaVersion, assertAgentTranscriptIdentity } from "./agent-transcript.js";
|
|
6
7
|
import { projectSessionDocument, sessionDocumentPath, } from "./session-projector.js";
|
|
7
8
|
const MANIFEST_VERSION = 1;
|
|
8
9
|
export const PROJECTOR_VERSION = 7;
|
|
9
|
-
const SUPPORTED_SCHEMA_VERSIONS = new Set([17, 18, 19]);
|
|
10
10
|
// Source lives in src/, published code in dist/src/. Read our own pinned dependency
|
|
11
11
|
// metadata, not QMD internals (which may also be substituted by runtime inspectors).
|
|
12
12
|
const sourcePackage = new URL("../package.json", import.meta.url);
|
|
@@ -76,12 +76,7 @@ function projectionPath(outputDir, documentPath) {
|
|
|
76
76
|
return target;
|
|
77
77
|
}
|
|
78
78
|
function assertSchema(db, expectedAgentId) {
|
|
79
|
-
const
|
|
80
|
-
const schemaVersion = pragma?.user_version;
|
|
81
|
-
if (typeof schemaVersion !== "number" || !SUPPORTED_SCHEMA_VERSIONS.has(schemaVersion)) {
|
|
82
|
-
throw new Error("unsupported OpenClaw agent database schema: expected one of 17, 18, 19, " +
|
|
83
|
-
`found ${String(schemaVersion ?? "unknown")}`);
|
|
84
|
-
}
|
|
79
|
+
const version = agentTranscriptSchemaVersion(db);
|
|
85
80
|
for (const [table, required] of Object.entries(REQUIRED_COLUMNS)) {
|
|
86
81
|
const columns = new Set(db.prepare(`PRAGMA table_info(${table})`).all()
|
|
87
82
|
.map((column) => column.name));
|
|
@@ -89,14 +84,7 @@ function assertSchema(db, expectedAgentId) {
|
|
|
89
84
|
if (missing)
|
|
90
85
|
throw new Error(`unsupported OpenClaw agent database: missing ${table}.${missing}`);
|
|
91
86
|
}
|
|
92
|
-
|
|
93
|
-
"FROM schema_meta WHERE meta_key = 'primary' LIMIT 1").get();
|
|
94
|
-
if (meta?.role !== "agent" || meta.schemaVersion !== schemaVersion) {
|
|
95
|
-
throw new Error("unsupported OpenClaw agent database primary schema metadata");
|
|
96
|
-
}
|
|
97
|
-
if (meta.agentId !== expectedAgentId) {
|
|
98
|
-
throw new Error(`OpenClaw agent database belongs to ${String(meta.agentId)}, not ${expectedAgentId}`);
|
|
99
|
-
}
|
|
87
|
+
assertAgentTranscriptIdentity(db, expectedAgentId, version);
|
|
100
88
|
}
|
|
101
89
|
function readSnapshot(params) {
|
|
102
90
|
const db = new DatabaseSync(params.databasePath, { readOnly: true });
|
|
@@ -129,13 +117,11 @@ function readSnapshot(params) {
|
|
|
129
117
|
ORDER BY window.created_at, window.session_id
|
|
130
118
|
`).all(...params.chatTypes);
|
|
131
119
|
const readEvents = db.prepare(`
|
|
132
|
-
SELECT
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
WHERE active.session_id = ?
|
|
138
|
-
ORDER BY active.active_position
|
|
120
|
+
SELECT a.session_id AS sessionId, e.event_json AS eventJson,
|
|
121
|
+
e.created_at AS createdAt
|
|
122
|
+
${ACTIVE_EVENTS_FROM}
|
|
123
|
+
WHERE a.session_id = ?
|
|
124
|
+
ORDER BY a.active_position
|
|
139
125
|
`);
|
|
140
126
|
const events = new Map();
|
|
141
127
|
const changed = new Set();
|
|
@@ -12,6 +12,5 @@ type SkillWhispererRuntime = {
|
|
|
12
12
|
agentId: string;
|
|
13
13
|
}, path: string): string | undefined;
|
|
14
14
|
};
|
|
15
|
-
export declare function buildSkillWhispererQuery(prompt: string, messages: readonly unknown[], historyMessages: number): string;
|
|
16
15
|
export declare function registerSkillWhisperer(api: OpenClawPluginApi, runtime: SkillWhispererRuntime, config: UnblockMemoryConfig["skillWhisperer"], typesafe: UnblockMemoryConfig["typesafe"], diagnostics?: WhispererDiagnostics): Parameters<typeof api.on<"before_prompt_build">>[1] | undefined;
|
|
17
16
|
export {};
|
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import { basename } from "node:path";
|
|
2
2
|
import { selectTypeSafeSkill } from "./typesafe.js";
|
|
3
3
|
import { resolveTypeSafeApiKey, TypeSafeRequestError } from "./typesafe-client.js";
|
|
4
|
-
import { messageText } from "./whisperer-context.js";
|
|
4
|
+
import { buildSkillWhispererQuery, messageText } from "./whisperer-context.js";
|
|
5
5
|
const CANDIDATE_LIMIT = 10;
|
|
6
6
|
const MAX_QUERY_CHARS = 12_000;
|
|
7
7
|
const TYPESAFE_CANDIDATE_LIMIT = 3;
|
|
8
8
|
function isRecord(value) {
|
|
9
9
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
10
|
}
|
|
11
|
-
export function buildSkillWhispererQuery(prompt, messages, historyMessages) {
|
|
12
|
-
const availableHistory = messages.flatMap((message) => {
|
|
13
|
-
const parsed = messageText(message);
|
|
14
|
-
return parsed ? [`${parsed.role}: ${parsed.text}`] : [];
|
|
15
|
-
});
|
|
16
|
-
const history = historyMessages === 0 ? [] : availableHistory.slice(-historyMessages);
|
|
17
|
-
return [...history, `user: ${prompt.trim()}`].join("\n\n").slice(-MAX_QUERY_CHARS);
|
|
18
|
-
}
|
|
19
11
|
function typeSafeConversation(prompt, messages, historyMessages) {
|
|
20
12
|
const currentRequest = prompt.trim().slice(-MAX_QUERY_CHARS);
|
|
21
13
|
let remaining = MAX_QUERY_CHARS - currentRequest.length;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { ACTIVE_EVENT_COUNT_SQL, ACTIVE_EVENT_ROWS_SQL, assertAgentTranscriptSchema } from "./agent-transcript.js";
|
|
3
4
|
import { messageText } from "./whisperer-context.js";
|
|
4
5
|
import { conversationUserText } from "./response-text.js";
|
|
5
6
|
// Identical serialized inputs keep their checkpoints when eligibility broadens.
|
|
@@ -133,11 +134,7 @@ export class TrainingTranscriptReader {
|
|
|
133
134
|
this.#db = new DatabaseSync(path, { readOnly: true });
|
|
134
135
|
try {
|
|
135
136
|
this.#db.exec("PRAGMA query_only=ON; PRAGMA busy_timeout=1000");
|
|
136
|
-
|
|
137
|
-
const meta = this.#db.prepare("SELECT role,agent_id,schema_version FROM schema_meta WHERE meta_key='primary'").get();
|
|
138
|
-
if (![17, 18, 19].includes(Number(version)) || meta?.role !== "agent" || meta.agent_id !== agentId || meta.schema_version !== version) {
|
|
139
|
-
throw new Error("Unsupported training transcript schema or agent");
|
|
140
|
-
}
|
|
137
|
+
assertAgentTranscriptSchema(this.#db, agentId, "Unsupported training transcript schema or agent");
|
|
141
138
|
const columns = this.#db.prepare("PRAGMA table_info(session_windows)").all().map(c => c.name);
|
|
142
139
|
this.#lineage = ["parent_session_key", "spawned_by", "plugin_owner_id", "hook_external_content_source"].every(c => columns.includes(c));
|
|
143
140
|
}
|
|
@@ -160,14 +157,10 @@ export class TrainingTranscriptReader {
|
|
|
160
157
|
/:(?:cron|subagent|heartbeat|hook)(?::|$)/i.test(String(session.session_key)) ||
|
|
161
158
|
session.parent_session_key || session.spawned_by || session.plugin_owner_id || session.hook_external_content_source)
|
|
162
159
|
return null;
|
|
163
|
-
const size = this.#db.prepare(
|
|
164
|
-
FROM session_transcript_active_events a JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq
|
|
165
|
-
WHERE a.session_id=?`).get(sessionId);
|
|
160
|
+
const size = this.#db.prepare(ACTIVE_EVENT_COUNT_SQL).get(sessionId);
|
|
166
161
|
if (Number(size.n) > 50_000 || Number(size.bytes) > 32_000_000)
|
|
167
162
|
return { oversized: true };
|
|
168
|
-
const rows = this.#db.prepare(
|
|
169
|
-
FROM session_transcript_active_events a JOIN transcript_events e ON e.session_id=a.session_id AND e.seq=a.event_seq
|
|
170
|
-
WHERE a.session_id=? ORDER BY a.active_position`).iterate(sessionId);
|
|
163
|
+
const rows = this.#db.prepare(ACTIVE_EVENT_ROWS_SQL).iterate(sessionId);
|
|
171
164
|
return trainingExamples(rows);
|
|
172
165
|
}
|
|
173
166
|
finally {
|
|
@@ -7,14 +7,7 @@ import { TrainingStore } from "./training-store.js";
|
|
|
7
7
|
import { TRAINING_GATE_THRESHOLD } from "./training-gate.js";
|
|
8
8
|
import { collectTraining, runTraining } from "./training.js";
|
|
9
9
|
import { generateTrainingQueries, evaluateTrainingQueries, exportQueryTraining, TRAINING_EVALUATION_CONCURRENCY } from "./training-queries.js";
|
|
10
|
-
|
|
11
|
-
if (value === undefined)
|
|
12
|
-
return fallback;
|
|
13
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value) {
|
|
14
|
-
throw new Error("Dates must be valid YYYY-MM-DD UTC dates");
|
|
15
|
-
}
|
|
16
|
-
return Date.parse(value);
|
|
17
|
-
}
|
|
10
|
+
import { dateOption } from "./date-option.js";
|
|
18
11
|
function thresholdOption(value) {
|
|
19
12
|
const result = Number(value);
|
|
20
13
|
if (!value.trim() || !Number.isFinite(result) || result < 0 || result > 1)
|
|
@@ -3,6 +3,7 @@ export declare function messageText(message: unknown): {
|
|
|
3
3
|
role: "user" | "assistant";
|
|
4
4
|
text: string;
|
|
5
5
|
} | undefined;
|
|
6
|
+
export declare function buildSkillWhispererQuery(prompt: string, messages: readonly unknown[], historyMessages: number): string;
|
|
6
7
|
export declare function memoryConversation(prompt: string, messages: readonly unknown[]): {
|
|
7
8
|
currentRequest: string;
|
|
8
9
|
history: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const MAX_SKILL_QUERY_CHARS = 12_000;
|
|
1
2
|
/** Only visible user/assistant text; never system, tool, image, or thinking blocks. */
|
|
2
3
|
export function messageText(message) {
|
|
3
4
|
if (!message || typeof message !== "object" || !("role" in message) || !("content" in message) ||
|
|
@@ -10,6 +11,14 @@ export function messageText(message) {
|
|
|
10
11
|
}).join("\n").trim() : "";
|
|
11
12
|
return text ? { role: message.role, text } : undefined;
|
|
12
13
|
}
|
|
14
|
+
export function buildSkillWhispererQuery(prompt, messages, historyMessages) {
|
|
15
|
+
const availableHistory = messages.flatMap(message => {
|
|
16
|
+
const parsed = messageText(message);
|
|
17
|
+
return parsed ? [`${parsed.role}: ${parsed.text}`] : [];
|
|
18
|
+
});
|
|
19
|
+
const history = historyMessages === 0 ? [] : availableHistory.slice(-historyMessages);
|
|
20
|
+
return [...history, `user: ${prompt.trim()}`].join("\n\n").slice(-MAX_SKILL_QUERY_CHARS);
|
|
21
|
+
}
|
|
13
22
|
export function memoryConversation(prompt, messages) {
|
|
14
23
|
const currentRequest = prompt.trim().slice(-16_000);
|
|
15
24
|
let remaining = 16_000 - currentRequest.length;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "unblock-memory",
|
|
3
3
|
"name": "Unblock Memory",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.29",
|
|
5
5
|
"description": "Indexes, retrieves, and analyzes configured workspace memory with existing QMD vectors.",
|
|
6
6
|
"kind": "memory",
|
|
7
7
|
"activation": { "onStartup": true },
|