
























@@ -58,10 +58,6 @@ function isDreamingNarrativeSessionKeyLike(value: unknown): boolean {
5858return typeof value === "string" && isDreamingNarrativeSessionStoreKey(value);
5959}
606061-function hasCronRunSessionKey(value: unknown): boolean {
62-return typeof value === "string" && isCronRunSessionKey(value);
63-}
64-6561function normalizeComparablePath(pathname: string): string {
6662const resolved = path.resolve(pathname);
6763return process.platform === "win32" ? resolved.toLowerCase() : resolved;
@@ -163,6 +159,7 @@ function resolveSessionStoreTranscriptCorpusPath(
163159function classifySessionEntry(
164160sessionKey: string,
165161entry: SessionEntry,
162+cronGeneratedSessionKeys: ReadonlySet<string>,
166163): {
167164generatedByDreamingNarrative: boolean;
168165generatedByCronRun: boolean;
@@ -171,10 +168,69 @@ function classifySessionEntry(
171168generatedByDreamingNarrative:
172169isDreamingNarrativeSessionStoreKey(sessionKey) ||
173170isDreamingNarrativeSessionKeyLike(entry.spawnedBy),
174-generatedByCronRun: isCronRunSessionKey(sessionKey) || hasCronRunSessionKey(entry.spawnedBy),
171+generatedByCronRun: cronGeneratedSessionKeys.has(sessionKey),
175172};
176173}
177174175+function readParentSessionKeys(entry: SessionEntry | undefined): string[] {
176+const keys = new Set<string>();
177+for (const value of [entry?.parentSessionKey, entry?.spawnedBy]) {
178+if (typeof value !== "string") {
179+continue;
180+}
181+const trimmed = value.trim();
182+if (trimmed) {
183+keys.add(trimmed);
184+}
185+}
186+return [...keys];
187+}
188+189+function collectCronGeneratedSessionKeys(
190+summaries: readonly SessionEntrySummary[],
191+): ReadonlySet<string> {
192+// Build the cron-generated closure once so active entries and archive
193+// artifacts share the same lineage classification.
194+const entriesByKey = new Map(summaries.map((summary) => [summary.sessionKey, summary.entry]));
195+const cronGeneratedKeys = new Set<string>();
196+const cache = new Map<string, boolean>();
197+const resolving = new Set<string>();
198+199+const isCronGenerated = (sessionKey: string, entry: SessionEntry | undefined): boolean => {
200+if (isCronRunSessionKey(sessionKey)) {
201+cache.set(sessionKey, true);
202+cronGeneratedKeys.add(sessionKey);
203+return true;
204+}
205+const cached = cache.get(sessionKey);
206+if (cached !== undefined) {
207+return cached;
208+}
209+if (resolving.has(sessionKey)) {
210+return false;
211+}
212+213+resolving.add(sessionKey);
214+const generated = readParentSessionKeys(entry).some(
215+(parentKey) =>
216+// Parent rows can be pruned before child rows; a cron-shaped parent key
217+// still carries cron lineage without requiring a store entry.
218+isCronRunSessionKey(parentKey) || isCronGenerated(parentKey, entriesByKey.get(parentKey)),
219+);
220+resolving.delete(sessionKey);
221+cache.set(sessionKey, generated);
222+if (generated) {
223+cronGeneratedKeys.add(sessionKey);
224+}
225+return generated;
226+};
227+228+for (const summary of summaries) {
229+isCronGenerated(summary.sessionKey, summary.entry);
230+}
231+return cronGeneratedKeys;
232+}
233+178234function isRegularSessionTranscriptFile(absPath: string): boolean {
179235try {
180236return fsSync.lstatSync(absPath).isFile();
@@ -187,6 +243,7 @@ function toSessionStoreCorpusEntry(
187243agentId: string,
188244sessionsDir: string,
189245summary: SessionEntrySummary,
246+cronGeneratedSessionKeys: ReadonlySet<string>,
190247): SessionTranscriptCorpusEntry | null {
191248const sessionFile = resolveSessionStoreTranscriptCorpusPath(agentId, sessionsDir, summary.entry);
192249if (!sessionFile || !isUsageCountedSessionTranscriptFileName(path.basename(sessionFile))) {
@@ -200,7 +257,11 @@ function toSessionStoreCorpusEntry(
200257return null;
201258}
202259const sessionKey = summary.sessionKey.trim();
203-const classification = classifySessionEntry(summary.sessionKey, summary.entry);
260+const classification = classifySessionEntry(
261+summary.sessionKey,
262+summary.entry,
263+cronGeneratedSessionKeys,
264+);
204265return {
205266 agentId,
206267artifactKind: "active-session",
@@ -299,11 +360,13 @@ export function listSessionTranscriptCorpusEntriesForAgentSync(
299360const activeEntryOwnersByPath = new Map<string, string>();
300361const artifactDirsByPath = new Map<string, string>();
301362rememberArtifactDir(artifactDirsByPath, sessionsDir);
302-for (const summary of listSessionEntries({
363+const sessionEntries = listSessionEntries({
303364agentId: normalizedAgentId,
304365hydrateSkillPromptRefs: false,
305366 storePath,
306-})) {
367+});
368+const cronGeneratedSessionKeys = collectCronGeneratedSessionKeys(sessionEntries);
369+for (const summary of sessionEntries) {
307370const sessionKey = isSharedFixedStore
308371 ? summary.sessionKey
309372 : canonicalizeMainSessionAlias({
@@ -316,7 +379,12 @@ export function listSessionTranscriptCorpusEntriesForAgentSync(
316379 sessionKey,
317380 ...(isSharedFixedStore ? {} : { fallbackAgentId: normalizedAgentId }),
318381});
319-const entry = toSessionStoreCorpusEntry(ownerAgentId, sessionsDir, summary);
382+const entry = toSessionStoreCorpusEntry(
383+ownerAgentId,
384+sessionsDir,
385+summary,
386+cronGeneratedSessionKeys,
387+);
320388if (!entry) {
321389continue;
322390}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。