Skip to content

Commit c14d91e

Browse files
committed
clawdbot-a29: add qmd session artifact identity mapping
1 parent b2ce1bc commit c14d91e

4 files changed

Lines changed: 684 additions & 17 deletions

File tree

extensions/memory-core/src/memory/qmd-manager.ts

Lines changed: 106 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import {
2222
buildSessionEntry,
2323
deriveQmdScopeChannel,
2424
deriveQmdScopeChatType,
25+
isSessionArchiveArtifactName,
2526
isQmdScopeAllowed,
2627
listSessionFilesForAgent,
28+
parseCanonicalSessionSyncTargetFromPath,
2729
parseQmdQueryJson,
2830
resolveCliSpawnInvocation,
2931
runCliCommand,
@@ -54,12 +56,21 @@ import {
5456
isFutureDateTimestampMs,
5557
resolveExpiresAtMsFromDurationMs,
5658
} from "openclaw/plugin-sdk/number-runtime";
59+
import { formatSessionTranscriptMemoryHitKey } from "openclaw/plugin-sdk/session-transcript-hit";
5760
import {
5861
localeLowercasePreservingWhitespace,
5962
normalizeLowercaseStringOrEmpty,
6063
uniqueValues,
6164
} from "openclaw/plugin-sdk/string-coerce-runtime";
6265
import { asRecord } from "../dreaming-shared.js";
66+
import {
67+
attachQmdSessionArtifactHit,
68+
copyQmdSessionArtifactHit,
69+
refreshQmdSessionArtifactDocIds,
70+
replaceQmdSessionArtifactMappings,
71+
resolveQmdSessionArtifactIdentity,
72+
type QmdSessionArtifactMapping,
73+
} from "../qmd-session-artifacts.js";
6374
import { resolveQmdCollectionPatternFlags, type QmdCollectionPatternFlag } from "./qmd-compat.js";
6475
import {
6576
recordMemoryWatchEventPath,
@@ -239,6 +250,14 @@ type CollectionRoot = {
239250
kind: MemorySource;
240251
};
241252

253+
type DocLocation = {
254+
abs: string;
255+
collection: string;
256+
collectionRelativePath: string;
257+
rel: string;
258+
source: MemorySource;
259+
};
260+
242261
type SessionExporterConfig = {
243262
dir: string;
244263
retentionMs?: number;
@@ -345,10 +364,7 @@ export class QmdMemoryManager implements MemorySearchManager {
345364
private readonly managedCollectionNames: string[];
346365
private readonly collectionRoots = new Map<string, CollectionRoot>();
347366
private readonly sources = new Set<MemorySource>();
348-
private readonly docPathCache = new Map<
349-
string,
350-
{ rel: string; abs: string; source: MemorySource }
351-
>();
367+
private readonly docPathCache = new Map<string, DocLocation>();
352368
private readonly exportedSessionState = new Map<
353369
string,
354370
{
@@ -1298,14 +1314,27 @@ export class QmdMemoryManager implements MemorySearchManager {
12981314
if (score < minScore) {
12991315
continue;
13001316
}
1301-
results.push({
1317+
const result = {
13021318
path: doc.rel,
13031319
startLine: lines.startLine,
13041320
endLine: lines.endLine,
13051321
score,
13061322
snippet,
13071323
source: doc.source,
1308-
});
1324+
} satisfies MemorySearchResult;
1325+
const artifactIdentity =
1326+
doc.source === "sessions"
1327+
? resolveQmdSessionArtifactIdentity({
1328+
artifactPath: doc.collectionRelativePath,
1329+
collection: doc.collection,
1330+
docid: entry.docid?.trim() || undefined,
1331+
indexPath: this.indexPath,
1332+
searchPath: doc.rel,
1333+
})
1334+
: null;
1335+
results.push(
1336+
artifactIdentity ? attachQmdSessionArtifactHit(result, artifactIdentity) : result,
1337+
);
13091338
}
13101339
opts?.onDebug?.({
13111340
backend: "qmd",
@@ -1543,6 +1572,9 @@ export class QmdMemoryManager implements MemorySearchManager {
15431572
await this.exportSessions();
15441573
}
15451574
await this.runQmdUpdateWithRetry(reason);
1575+
if (this.sessionExporter) {
1576+
this.refreshSessionArtifactDocIds();
1577+
}
15461578
this.dirty = false;
15471579
});
15481580
if (this.closed) {
@@ -2353,6 +2385,7 @@ export class QmdMemoryManager implements MemorySearchManager {
23532385
const files = await listSessionFilesForAgent(this.agentId);
23542386
const keep = new Set<string>();
23552387
const tracked = new Set<string>();
2388+
const artifactMappings: QmdSessionArtifactMapping[] = [];
23562389
const cutoff = this.sessionExporter.retentionMs
23572390
? Date.now() - this.sessionExporter.retentionMs
23582391
: null;
@@ -2367,6 +2400,10 @@ export class QmdMemoryManager implements MemorySearchManager {
23672400
const targetName = `${path.basename(sessionFile, ".jsonl")}.md`;
23682401
const target = path.join(exportDir, targetName);
23692402
tracked.add(sessionFile);
2403+
const identity = this.buildSessionArtifactMapping(sessionFile, targetName, target);
2404+
if (identity) {
2405+
artifactMappings.push(identity);
2406+
}
23702407
const state = this.exportedSessionState.get(sessionFile);
23712408
if (!state || state.hash !== entry.hash || state.mtimeMs !== entry.mtimeMs) {
23722409
await exportRoot.write(targetName, this.renderSessionMarkdown(entry), {
@@ -2395,6 +2432,56 @@ export class QmdMemoryManager implements MemorySearchManager {
23952432
this.exportedSessionState.delete(sessionFile);
23962433
}
23972434
}
2435+
replaceQmdSessionArtifactMappings({
2436+
collection: this.sessionExporter.collectionName,
2437+
indexPath: this.indexPath,
2438+
mappings: artifactMappings,
2439+
});
2440+
}
2441+
2442+
private buildSessionArtifactMapping(
2443+
sessionFile: string,
2444+
artifactPath: string,
2445+
target: string,
2446+
): QmdSessionArtifactMapping | null {
2447+
if (!this.sessionExporter) {
2448+
return null;
2449+
}
2450+
const identity = parseCanonicalSessionSyncTargetFromPath(sessionFile);
2451+
if (!identity?.agentId) {
2452+
return null;
2453+
}
2454+
return {
2455+
agentId: identity.agentId,
2456+
archived: isSessionArchiveArtifactName(path.basename(sessionFile)),
2457+
artifactPath,
2458+
collection: this.sessionExporter.collectionName,
2459+
memoryKey: formatSessionTranscriptMemoryHitKey({
2460+
agentId: identity.agentId,
2461+
sessionId: identity.sessionId,
2462+
}),
2463+
searchPath: this.buildSearchPath(
2464+
this.sessionExporter.collectionName,
2465+
artifactPath,
2466+
path.relative(this.workspaceDir, target),
2467+
target,
2468+
),
2469+
sessionId: identity.sessionId,
2470+
};
2471+
}
2472+
2473+
private refreshSessionArtifactDocIds(): void {
2474+
if (!this.sessionExporter) {
2475+
return;
2476+
}
2477+
try {
2478+
refreshQmdSessionArtifactDocIds({
2479+
collection: this.sessionExporter.collectionName,
2480+
indexPath: this.indexPath,
2481+
});
2482+
} catch (err) {
2483+
log.warn(`failed to refresh qmd session artifact identity docids: ${String(err)}`);
2484+
}
23982485
}
23992486

24002487
private renderSessionMarkdown(entry: SessionFileEntry): string {
@@ -2427,7 +2514,7 @@ export class QmdMemoryManager implements MemorySearchManager {
24272514
private async resolveDocLocation(
24282515
docid?: string,
24292516
hints?: { preferredCollection?: string; preferredFile?: string },
2430-
): Promise<{ rel: string; abs: string; source: MemorySource } | null> {
2517+
): Promise<DocLocation | null> {
24312518
const normalizedHints = this.normalizeDocHints(hints);
24322519
if (!docid) {
24332520
return this.resolveDocLocationFromHints(normalizedHints);
@@ -2473,7 +2560,7 @@ export class QmdMemoryManager implements MemorySearchManager {
24732560
private resolveDocLocationFromHints(hints: {
24742561
preferredCollection?: string;
24752562
preferredFile?: string;
2476-
}): { rel: string; abs: string; source: MemorySource } | null {
2563+
}): DocLocation | null {
24772564
if (!hints.preferredCollection || !hints.preferredFile) {
24782565
return null;
24792566
}
@@ -2497,7 +2584,7 @@ export class QmdMemoryManager implements MemorySearchManager {
24972584
private resolveIndexedDocLocationFromHint(
24982585
collection: string,
24992586
preferredFile: string,
2500-
): { rel: string; abs: string; source: MemorySource } | null {
2587+
): DocLocation | null {
25012588
const trimmedCollection = collection.trim();
25022589
const trimmedFile = preferredFile.trim();
25032590
if (!trimmedCollection || !trimmedFile) {
@@ -2599,7 +2686,7 @@ export class QmdMemoryManager implements MemorySearchManager {
25992686
private pickDocLocation(
26002687
rows: Array<{ collection: string; path: string }>,
26012688
hints?: { preferredCollection?: string; preferredFile?: string },
2602-
): { rel: string; abs: string; source: MemorySource } | null {
2689+
): DocLocation | null {
26032690
if (hints?.preferredCollection) {
26042691
for (const row of rows) {
26052692
if (row.collection !== hints.preferredCollection) {
@@ -2794,10 +2881,7 @@ export class QmdMemoryManager implements MemorySearchManager {
27942881
return isQmdScopeAllowed(this.qmd.scope, sessionKey);
27952882
}
27962883

2797-
private toDocLocation(
2798-
collection: string,
2799-
collectionRelativePath: string,
2800-
): { rel: string; abs: string; source: MemorySource } | null {
2884+
private toDocLocation(collection: string, collectionRelativePath: string): DocLocation | null {
28012885
const rootEntry = this.collectionRoots.get(collection);
28022886
if (!rootEntry) {
28032887
return null;
@@ -2811,7 +2895,13 @@ export class QmdMemoryManager implements MemorySearchManager {
28112895
relativeToWorkspace,
28122896
absPath,
28132897
);
2814-
return { rel: relPath, abs: absPath, source: rootEntry.kind };
2898+
return {
2899+
rel: relPath,
2900+
abs: absPath,
2901+
collection,
2902+
collectionRelativePath: normalizedRelative,
2903+
source: rootEntry.kind,
2904+
};
28152905
}
28162906

28172907
private buildSearchPath(
@@ -2950,7 +3040,7 @@ export class QmdMemoryManager implements MemorySearchManager {
29503040
remaining -= snippet.length;
29513041
} else {
29523042
const trimmed = snippet.slice(0, Math.max(0, remaining));
2953-
clamped.push({ ...entry, snippet: trimmed });
3043+
clamped.push(copyQmdSessionArtifactHit(entry, { ...entry, snippet: trimmed }));
29543044
break;
29553045
}
29563046
}

0 commit comments

Comments
 (0)