@@ -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" ;
5760import {
5861 localeLowercasePreservingWhitespace ,
5962 normalizeLowercaseStringOrEmpty ,
6063 uniqueValues ,
6164} from "openclaw/plugin-sdk/string-coerce-runtime" ;
6265import { 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" ;
6374import { resolveQmdCollectionPatternFlags , type QmdCollectionPatternFlag } from "./qmd-compat.js" ;
6475import {
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+
242261type 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