File size: 3,839 Bytes
d522ffa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { readdir, readFile, stat } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { join } from 'node:path';

import {
  OldSessionMetadataSchema,
  OldSessionStateSchema,
  type OldSessionMetadata,
  type OldSessionState,
} from '../kimi-cli-schema.js';

export interface LegacySessionRef {
  readonly uuid: string;
  readonly sessionDir?: string;
  readonly flatContextFile?: string;
  readonly contextPath?: string;
}

export async function listBucketSessions(bucketPath: string): Promise<LegacySessionRef[]> {
  const names = await readdir(bucketPath);
  const byId = new Map<string, { dir?: string; flat?: string }>();
  const remember = (uuid: string): { dir?: string; flat?: string } => {
    const existing = byId.get(uuid);
    if (existing !== undefined) return existing;
    const created: { dir?: string; flat?: string } = {};
    byId.set(uuid, created);
    return created;
  };
  for (const name of names) {
    const fullPath = join(bucketPath, name);
    let st;
    try {
      st = await stat(fullPath);
    } catch {
      remember(name);
      continue;
    }
    if (st.isDirectory()) {
      remember(name).dir = fullPath;
      continue;
    }
    if (st.isFile() && name.endsWith('.jsonl') && name.length > '.jsonl'.length) {
      remember(name.slice(0, -'.jsonl'.length)).flat = fullPath;
    }
  }
  return [...byId.entries()].map(([uuid, found]) => {
    const dirContext =
      found.dir !== undefined && existsSync(join(found.dir, 'context.jsonl'))
        ? join(found.dir, 'context.jsonl')
        : undefined;
    return {
      uuid,
      sessionDir: found.dir,
      flatContextFile: found.flat,
      contextPath: dirContext ?? found.flat,
    };
  });
}

export async function readMergedSessionState(
  sessionDir: string | undefined,
): Promise<Partial<OldSessionState>> {
  if (sessionDir === undefined) return {};
  let state: Partial<OldSessionState> = {};
  try {
    state = OldSessionStateSchema.parse(
      JSON.parse(await readFile(join(sessionDir, 'state.json'), 'utf-8')),
    );
  } catch {
    // missing or corrupt state — proceed with defaults
  }
  let metadata: OldSessionMetadata | undefined;
  try {
    metadata = OldSessionMetadataSchema.parse(
      JSON.parse(await readFile(join(sessionDir, 'metadata.json'), 'utf-8')),
    );
  } catch {
    metadata = undefined;
  }
  if (metadata === undefined) return state;
  return mergeLegacyMetadata(state, metadata);
}

export function mergeLegacyMetadata(
  state: Partial<OldSessionState>,
  metadata: OldSessionMetadata,
): Partial<OldSessionState> {
  const merged: Partial<OldSessionState> = { ...state };
  if (
    (merged.custom_title === null || merged.custom_title === undefined) &&
    typeof metadata.title === 'string' &&
    metadata.title !== '' &&
    metadata.title !== 'Untitled'
  ) {
    merged.custom_title = metadata.title;
  }
  if (merged.title_generated !== true && metadata.title_generated === true) {
    merged.title_generated = true;
  }
  if ((merged.title_generate_attempts ?? 0) === 0 && (metadata.title_generate_attempts ?? 0) > 0) {
    merged.title_generate_attempts = metadata.title_generate_attempts;
  }
  if (
    (merged.wire_mtime === null || merged.wire_mtime === undefined) &&
    metadata.wire_mtime !== null &&
    metadata.wire_mtime !== undefined
  ) {
    merged.wire_mtime = metadata.wire_mtime;
  }
  if (merged.archived !== true && metadata.archived === true) {
    merged.archived = true;
  }
  if (
    (merged.archived_at === null || merged.archived_at === undefined) &&
    metadata.archived_at !== null &&
    metadata.archived_at !== undefined
  ) {
    merged.archived_at = metadata.archived_at;
  }
  if (merged.auto_archive_exempt !== true && metadata.auto_archive_exempt === true) {
    merged.auto_archive_exempt = true;
  }
  return merged;
}