@muyichengshayu/promptx 0.2.8 → 0.2.9

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.
@@ -602,3 +602,139 @@ export function listKnownOpenCodeSessions(options = {}) {
602
602
 
603
603
  return sortAndLimitCandidates(items, options)
604
604
  }
605
+
606
+ function getKimiHomeDir(options = {}) {
607
+ return normalizeText(options.kimiHome || process.env.KIMI_HOME)
608
+ || path.join(os.homedir(), '.kimi')
609
+ }
610
+
611
+ function readKimiJson(options = {}) {
612
+ const kimiJsonPath = path.join(getKimiHomeDir(options), 'kimi.json')
613
+ return parseJson(safeReadFile(kimiJsonPath)) || {}
614
+ }
615
+
616
+ function readKimiContextPreview(contextPath = '') {
617
+ const content = safeReadFile(contextPath, 256 * 1024)
618
+ if (!content) {
619
+ return ''
620
+ }
621
+
622
+ const lines = content.replace(/\r\n/g, '\n').split('\n').slice(0, 30)
623
+ for (const line of lines) {
624
+ const event = parseJson(line)
625
+ if (!event) {
626
+ continue
627
+ }
628
+
629
+ const role = normalizeText(event.role).toLowerCase()
630
+ if (role !== 'user') {
631
+ continue
632
+ }
633
+
634
+ const text = extractMessageText(event)
635
+ if (text) {
636
+ return text.replace(/\s+/g, ' ').slice(0, MAX_PREVIEW_LENGTH)
637
+ }
638
+ }
639
+
640
+ return ''
641
+ }
642
+
643
+ export function listKnownKimiCodeSessions(options = {}) {
644
+ const kimiHome = getKimiHomeDir(options)
645
+ const items = []
646
+
647
+ const kimiJson = readKimiJson(options)
648
+ const cwdBySessionId = new Map()
649
+
650
+ if (kimiJson?.work_dirs && Array.isArray(kimiJson.work_dirs)) {
651
+ kimiJson.work_dirs.forEach((entry) => {
652
+ const id = normalizeText(entry?.last_session_id)
653
+ const cwd = normalizeText(entry?.path)
654
+ if (!id || !cwd) {
655
+ return
656
+ }
657
+
658
+ cwdBySessionId.set(id, cwd)
659
+ items.push({
660
+ id,
661
+ engine: AGENT_ENGINES.KIMI_CODE,
662
+ label: path.basename(cwd) || id,
663
+ cwd,
664
+ updatedAt: safeStat(path.join(kimiHome, 'kimi.json'))?.mtime,
665
+ source: 'kimi_json',
666
+ })
667
+ })
668
+ }
669
+
670
+ const sessionsDir = path.join(kimiHome, 'sessions')
671
+ if (safeStat(sessionsDir)?.isDirectory()) {
672
+ let hashDirs = []
673
+ try {
674
+ hashDirs = fs.readdirSync(sessionsDir, { withFileTypes: true })
675
+ .filter((entry) => entry.isDirectory())
676
+ .map((entry) => entry.name)
677
+ } catch {
678
+ hashDirs = []
679
+ }
680
+
681
+ for (const hashDir of hashDirs) {
682
+ const hashDirPath = path.join(sessionsDir, hashDir)
683
+ let sessionDirs = []
684
+ try {
685
+ sessionDirs = fs.readdirSync(hashDirPath, { withFileTypes: true })
686
+ .filter((entry) => entry.isDirectory())
687
+ .map((entry) => entry.name)
688
+ } catch {
689
+ continue
690
+ }
691
+
692
+ for (const sessionId of sessionDirs) {
693
+ if (cwdBySessionId.has(sessionId)) {
694
+ const statePath = path.join(hashDirPath, sessionId, 'state.json')
695
+ const state = parseJson(safeReadFile(statePath))
696
+ if (state?.archived) {
697
+ const index = items.findIndex((item) => item.id === sessionId)
698
+ if (index >= 0) {
699
+ items.splice(index, 1)
700
+ }
701
+ continue
702
+ }
703
+
704
+ const contextPath = path.join(hashDirPath, sessionId, 'context.jsonl')
705
+ const preview = readKimiContextPreview(contextPath)
706
+ const stateStat = safeStat(statePath)
707
+ const existing = items.find((item) => item.id === sessionId)
708
+ if (existing) {
709
+ existing.label = normalizeText(state?.custom_title) || preview || existing.label
710
+ if (stateStat?.mtime && getSortTime(stateStat.mtime) > getSortTime(existing.updatedAt)) {
711
+ existing.updatedAt = toIsoDate(stateStat.mtime)
712
+ existing.updatedAtSource = 'explicit'
713
+ }
714
+ }
715
+ continue
716
+ }
717
+
718
+ const statePath = path.join(hashDirPath, sessionId, 'state.json')
719
+ const state = parseJson(safeReadFile(statePath))
720
+ if (state?.archived) {
721
+ continue
722
+ }
723
+
724
+ const contextPath = path.join(hashDirPath, sessionId, 'context.jsonl')
725
+ const preview = readKimiContextPreview(contextPath)
726
+
727
+ items.push({
728
+ id: sessionId,
729
+ engine: AGENT_ENGINES.KIMI_CODE,
730
+ label: normalizeText(state?.custom_title) || preview || sessionId,
731
+ cwd: '',
732
+ updatedAt: safeStat(statePath)?.mtime,
733
+ source: 'kimi_sessions',
734
+ })
735
+ }
736
+ }
737
+ }
738
+
739
+ return sortAndLimitCandidates(items, options)
740
+ }
@@ -7,6 +7,7 @@ import test from 'node:test'
7
7
  import {
8
8
  decodeClaudeProjectPath,
9
9
  listKnownClaudeCodeSessions,
10
+ listKnownKimiCodeSessions,
10
11
  listKnownOpenCodeSessions,
11
12
  } from './agentSessionDiscovery.js'
12
13
 
@@ -125,3 +126,61 @@ test('listKnownOpenCodeSessions discovers sessions from desktop dat files', () =
125
126
  fs.rmSync(tempRoot, { recursive: true, force: true })
126
127
  }
127
128
  })
129
+
130
+ test('listKnownKimiCodeSessions merges kimi.json work dirs with session state', () => {
131
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-kimi-discovery-'))
132
+ const kimiHome = path.join(tempRoot, '.kimi')
133
+ const sessionDir = path.join(kimiHome, 'sessions', 'hash-1', 'kimi-session-1')
134
+ fs.mkdirSync(sessionDir, { recursive: true })
135
+
136
+ const kimiJsonPath = path.join(kimiHome, 'kimi.json')
137
+ const statePath = path.join(sessionDir, 'state.json')
138
+ const contextPath = path.join(sessionDir, 'context.jsonl')
139
+ const kimiJsonTime = new Date('2026-04-13T08:00:00.000Z')
140
+ const stateTime = new Date('2026-04-13T09:00:00.000Z')
141
+
142
+ fs.writeFileSync(kimiJsonPath, JSON.stringify({
143
+ work_dirs: [
144
+ {
145
+ path: '/Users/bravf/code/promptx',
146
+ last_session_id: 'kimi-session-1',
147
+ },
148
+ ],
149
+ }))
150
+ fs.writeFileSync(statePath, JSON.stringify({
151
+ custom_title: '继续 Kimi 项目',
152
+ }))
153
+ fs.writeFileSync(contextPath, `${JSON.stringify({ role: 'user', content: '帮我修复 Kimi 接入' })}\n`)
154
+ fs.utimesSync(kimiJsonPath, kimiJsonTime, kimiJsonTime)
155
+ fs.utimesSync(statePath, stateTime, stateTime)
156
+
157
+ try {
158
+ const items = listKnownKimiCodeSessions({
159
+ kimiHome,
160
+ limit: 10,
161
+ cwd: '/Users/bravf/code/promptx',
162
+ })
163
+
164
+ assert.equal(items.length, 1)
165
+ assert.deepEqual(
166
+ {
167
+ id: items[0].id,
168
+ label: items[0].label,
169
+ cwd: items[0].cwd,
170
+ updatedAt: items[0].updatedAt,
171
+ updatedAtSource: items[0].updatedAtSource,
172
+ matchedCwd: items[0].matchedCwd,
173
+ },
174
+ {
175
+ id: 'kimi-session-1',
176
+ label: '继续 Kimi 项目',
177
+ cwd: '/Users/bravf/code/promptx',
178
+ updatedAt: stateTime.toISOString(),
179
+ updatedAtSource: 'explicit',
180
+ matchedCwd: true,
181
+ }
182
+ )
183
+ } finally {
184
+ fs.rmSync(tempRoot, { recursive: true, force: true })
185
+ }
186
+ })
@@ -7,6 +7,7 @@ import {
7
7
  import { codexRunner } from './codexRunner.js'
8
8
  import { claudeCodeRunner } from './claudeCodeRunner.js'
9
9
  import { openCodeRunner } from './openCodeRunner.js'
10
+ import { kimiCodeRunner } from './kimiCodeRunner.js'
10
11
 
11
12
  const SHELL_ENGINE = 'shell'
12
13
  const shellRunner = {
@@ -18,6 +19,7 @@ const runnerRegistry = new Map([
18
19
  [codexRunner.engine, codexRunner],
19
20
  [claudeCodeRunner.engine, claudeCodeRunner],
20
21
  [openCodeRunner.engine, openCodeRunner],
22
+ [kimiCodeRunner.engine, kimiCodeRunner],
21
23
  [shellRunner.engine, shellRunner],
22
24
  ])
23
25