@ai-devkit/agent-manager 0.15.0 → 0.17.0

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.
Files changed (112) hide show
  1. package/.swcrc +27 -0
  2. package/dist/AgentManager.d.ts +4 -0
  3. package/dist/AgentManager.d.ts.map +1 -1
  4. package/dist/AgentManager.js +97 -64
  5. package/dist/AgentManager.js.map +1 -1
  6. package/dist/__tests__/AgentManager.test.js +564 -0
  7. package/dist/__tests__/AgentManager.test.js.map +1 -0
  8. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +2157 -0
  9. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -0
  10. package/dist/__tests__/adapters/CodexAdapter.test.js +1354 -0
  11. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -0
  12. package/dist/__tests__/adapters/CopilotAdapter.test.js +500 -0
  13. package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -0
  14. package/dist/__tests__/adapters/GeminiCliAdapter.test.js +1229 -0
  15. package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -0
  16. package/dist/__tests__/adapters/OpenCodeAdapter.test.js +626 -0
  17. package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -0
  18. package/dist/__tests__/terminal/TmuxManager.test.js +159 -0
  19. package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -0
  20. package/dist/__tests__/terminal/TtyWriter.test.js +162 -0
  21. package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -0
  22. package/dist/__tests__/utils/AgentRegistry.test.js +282 -0
  23. package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -0
  24. package/dist/__tests__/utils/ClaudeSessionParser.test.js +312 -0
  25. package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -0
  26. package/dist/__tests__/utils/matching.test.js +324 -0
  27. package/dist/__tests__/utils/matching.test.js.map +1 -0
  28. package/dist/__tests__/utils/process.test.js +177 -0
  29. package/dist/__tests__/utils/process.test.js.map +1 -0
  30. package/dist/__tests__/utils/session.test.js +158 -0
  31. package/dist/__tests__/utils/session.test.js.map +1 -0
  32. package/dist/adapters/AgentAdapter.d.ts +1 -1
  33. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  34. package/dist/adapters/AgentAdapter.js +8 -7
  35. package/dist/adapters/AgentAdapter.js.map +1 -1
  36. package/dist/adapters/ClaudeCodeAdapter.js +66 -77
  37. package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
  38. package/dist/adapters/CodexAdapter.d.ts +13 -1
  39. package/dist/adapters/CodexAdapter.d.ts.map +1 -1
  40. package/dist/adapters/CodexAdapter.js +208 -132
  41. package/dist/adapters/CodexAdapter.js.map +1 -1
  42. package/dist/adapters/CopilotAdapter.d.ts +44 -0
  43. package/dist/adapters/CopilotAdapter.d.ts.map +1 -0
  44. package/dist/adapters/CopilotAdapter.js +314 -0
  45. package/dist/adapters/CopilotAdapter.js.map +1 -0
  46. package/dist/adapters/GeminiCliAdapter.d.ts +6 -1
  47. package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
  48. package/dist/adapters/GeminiCliAdapter.js +160 -149
  49. package/dist/adapters/GeminiCliAdapter.js.map +1 -1
  50. package/dist/adapters/OpenCodeAdapter.js +60 -69
  51. package/dist/adapters/OpenCodeAdapter.js.map +1 -1
  52. package/dist/adapters/index.d.ts +1 -0
  53. package/dist/adapters/index.d.ts.map +1 -1
  54. package/dist/adapters/index.js +2 -0
  55. package/dist/adapters/index.js.map +1 -1
  56. package/dist/index.d.ts +6 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +5 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/terminal/TerminalFocusManager.js +62 -49
  61. package/dist/terminal/TerminalFocusManager.js.map +1 -1
  62. package/dist/terminal/TmuxManager.d.ts +28 -0
  63. package/dist/terminal/TmuxManager.d.ts.map +1 -0
  64. package/dist/terminal/TmuxManager.js +137 -0
  65. package/dist/terminal/TmuxManager.js.map +1 -0
  66. package/dist/terminal/TtyWriter.js +37 -16
  67. package/dist/terminal/TtyWriter.js.map +1 -1
  68. package/dist/terminal/index.js +1 -0
  69. package/dist/terminal/index.js.map +1 -1
  70. package/dist/utils/AgentRegistry.d.ts +35 -0
  71. package/dist/utils/AgentRegistry.d.ts.map +1 -0
  72. package/dist/utils/AgentRegistry.js +123 -0
  73. package/dist/utils/AgentRegistry.js.map +1 -0
  74. package/dist/utils/ClaudeSessionParser.d.ts.map +1 -1
  75. package/dist/utils/ClaudeSessionParser.js +62 -93
  76. package/dist/utils/ClaudeSessionParser.js.map +1 -1
  77. package/dist/utils/agents.d.ts +15 -0
  78. package/dist/utils/agents.d.ts.map +1 -0
  79. package/dist/utils/agents.js +41 -0
  80. package/dist/utils/agents.js.map +1 -0
  81. package/dist/utils/applescript.js +3 -6
  82. package/dist/utils/applescript.js.map +1 -1
  83. package/dist/utils/index.js +1 -0
  84. package/dist/utils/index.js.map +1 -1
  85. package/dist/utils/matching.js +21 -29
  86. package/dist/utils/matching.js.map +1 -1
  87. package/dist/utils/process.js +77 -56
  88. package/dist/utils/process.js.map +1 -1
  89. package/dist/utils/session.js +20 -34
  90. package/dist/utils/session.js.map +1 -1
  91. package/dist/utils/sortAgents.js +6 -8
  92. package/dist/utils/sortAgents.js.map +1 -1
  93. package/package.json +11 -7
  94. package/src/AgentManager.ts +40 -0
  95. package/src/__tests__/AgentManager.test.ts +135 -0
  96. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +3 -2
  97. package/src/__tests__/adapters/CodexAdapter.test.ts +199 -0
  98. package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
  99. package/src/__tests__/adapters/GeminiCliAdapter.test.ts +185 -1
  100. package/src/__tests__/terminal/TmuxManager.test.ts +175 -0
  101. package/src/__tests__/utils/AgentRegistry.test.ts +223 -0
  102. package/src/__tests__/utils/ClaudeSessionParser.test.ts +71 -0
  103. package/src/adapters/AgentAdapter.ts +1 -1
  104. package/src/adapters/CodexAdapter.ts +155 -8
  105. package/src/adapters/CopilotAdapter.ts +453 -0
  106. package/src/adapters/GeminiCliAdapter.ts +84 -6
  107. package/src/adapters/index.ts +1 -0
  108. package/src/index.ts +7 -0
  109. package/src/terminal/TmuxManager.ts +118 -0
  110. package/src/utils/AgentRegistry.ts +139 -0
  111. package/src/utils/ClaudeSessionParser.ts +21 -10
  112. package/src/utils/agents.ts +41 -0
@@ -8,56 +8,174 @@
8
8
  * 4. Setting resolvedCwd from session_meta first line
9
9
  * 5. Matching sessions to processes via shared matchProcessesToSessions()
10
10
  * 6. Extracting summary from last event entry in session JSONL
11
- */
12
- import * as fs from 'fs';
11
+ */ import * as fs from 'fs';
13
12
  import * as path from 'path';
14
13
  import { AgentStatus } from './AgentAdapter.js';
15
14
  import { listAgentProcesses, enrichProcesses } from '../utils/process.js';
16
15
  import { batchGetSessionFileBirthtimes, isDirectory, safeReadFile, safeReaddir, safeStat } from '../utils/session.js';
17
16
  import { matchProcessesToSessions, generateAgentName } from '../utils/matching.js';
17
+ import { AgentRegistry } from '../utils/AgentRegistry.js';
18
18
  export class CodexAdapter {
19
19
  type = 'codex';
20
20
  static IDLE_THRESHOLD_MINUTES = 5;
21
- /** Include session files around process start day to recover long-lived processes. */
22
- static PROCESS_START_DAY_WINDOW_DAYS = 1;
21
+ /** Include session files around process start day to recover long-lived processes. */ static PROCESS_START_DAY_WINDOW_DAYS = 1;
23
22
  codexSessionsDir;
24
- constructor() {
23
+ registry;
24
+ constructor(registry = AgentRegistry.default()){
25
25
  const homeDir = process.env.HOME || process.env.USERPROFILE || '';
26
26
  this.codexSessionsDir = path.join(homeDir, '.codex', 'sessions');
27
+ this.registry = registry;
27
28
  }
28
29
  canHandle(processInfo) {
29
30
  return this.isCodexExecutable(processInfo.command);
30
31
  }
31
32
  /**
32
33
  * Detect running Codex agents
33
- */
34
- async detectAgents() {
34
+ */ async detectAgents() {
35
35
  const processes = enrichProcesses(listAgentProcesses('codex'));
36
- if (processes.length === 0)
37
- return [];
38
- const { sessions, contentCache } = this.discoverSessions(processes);
36
+ if (processes.length === 0) return [];
37
+ const { cachedAgents, remaining } = this.tryRegistryCache(processes);
38
+ if (remaining.length === 0) return cachedAgents;
39
+ const { direct, fallback } = this.tryResumeMatching(remaining);
40
+ const directResult = this.mapDirectMatches(direct);
41
+ const { sessions, contentCache } = this.discoverSessions(fallback);
39
42
  if (sessions.length === 0) {
40
- return processes.map((p) => this.mapProcessOnlyAgent(p));
43
+ return [
44
+ ...cachedAgents,
45
+ ...directResult.agents,
46
+ ...directResult.failedProcesses.map((p)=>this.mapProcessOnlyAgent(p)),
47
+ ...fallback.map((p)=>this.mapProcessOnlyAgent(p))
48
+ ];
41
49
  }
42
- const matches = matchProcessesToSessions(processes, sessions);
43
- const matchedPids = new Set(matches.map((m) => m.process.pid));
44
- const agents = [];
45
- for (const match of matches) {
50
+ const matches = matchProcessesToSessions(fallback, sessions);
51
+ const matchedPids = new Set([
52
+ ...directResult.agents.map((a)=>a.pid),
53
+ ...matches.map((m)=>m.process.pid)
54
+ ]);
55
+ const agents = [
56
+ ...directResult.agents
57
+ ];
58
+ for (const match of matches){
46
59
  const cachedContent = contentCache.get(match.session.filePath);
47
60
  const sessionData = this.parseSession(cachedContent, match.session.filePath);
48
61
  if (sessionData) {
49
62
  agents.push(this.mapSessionToAgent(sessionData, match.process, match.session.filePath));
50
- }
51
- else {
63
+ } else {
52
64
  matchedPids.delete(match.process.pid);
53
65
  }
54
66
  }
55
- for (const proc of processes) {
67
+ for (const proc of fallback){
56
68
  if (!matchedPids.has(proc.pid)) {
57
69
  agents.push(this.mapProcessOnlyAgent(proc));
58
70
  }
59
71
  }
60
- return agents;
72
+ for (const proc of directResult.failedProcesses){
73
+ agents.push(this.mapProcessOnlyAgent(proc));
74
+ }
75
+ return [
76
+ ...cachedAgents,
77
+ ...agents
78
+ ];
79
+ }
80
+ tryRegistryCache(processes) {
81
+ const cachedAgents = [];
82
+ const remaining = [];
83
+ const byPid = new Map(this.registry.list().map((e)=>[
84
+ e.pid,
85
+ e
86
+ ]));
87
+ for (const proc of processes){
88
+ const entry = byPid.get(proc.pid);
89
+ if (!entry || entry.type !== this.type || !entry.sessionFilePath || !fs.existsSync(entry.sessionFilePath)) {
90
+ remaining.push(proc);
91
+ continue;
92
+ }
93
+ const content = safeReadFile(entry.sessionFilePath);
94
+ const sessionData = this.parseSession(content, entry.sessionFilePath);
95
+ if (!sessionData) {
96
+ remaining.push(proc);
97
+ continue;
98
+ }
99
+ cachedAgents.push(this.mapSessionToAgent(sessionData, proc, entry.sessionFilePath));
100
+ }
101
+ return {
102
+ cachedAgents,
103
+ remaining
104
+ };
105
+ }
106
+ /**
107
+ * Match processes via `codex resume <uuid>` in their command line.
108
+ * Resumed sessions predate the process, so birth-time proximity cannot
109
+ * reliably pair them with the running PID.
110
+ */ tryResumeMatching(processes) {
111
+ const direct = [];
112
+ const fallback = [];
113
+ for (const proc of processes){
114
+ const sessionId = this.extractResumeSessionId(proc.command);
115
+ if (!sessionId) {
116
+ fallback.push(proc);
117
+ continue;
118
+ }
119
+ const sessionFile = this.findSessionFileById(sessionId);
120
+ if (!sessionFile) {
121
+ fallback.push(proc);
122
+ continue;
123
+ }
124
+ direct.push({
125
+ process: proc,
126
+ sessionFile
127
+ });
128
+ }
129
+ return {
130
+ direct,
131
+ fallback
132
+ };
133
+ }
134
+ extractResumeSessionId(command) {
135
+ const match = command.match(/(?:^|\s)resume\s+([0-9a-f-]{36})(?:\s|$)/i);
136
+ return match?.[1] ?? null;
137
+ }
138
+ findSessionFileById(sessionId) {
139
+ for (const filePath of this.collectAllSessionFiles()){
140
+ if (!path.basename(filePath).includes(sessionId)) continue;
141
+ const content = safeReadFile(filePath);
142
+ const firstLine = content?.split('\n')[0]?.trim();
143
+ if (!firstLine) continue;
144
+ try {
145
+ const parsed = JSON.parse(firstLine);
146
+ if (parsed.type !== 'session_meta' || parsed.payload?.id !== sessionId) {
147
+ continue;
148
+ }
149
+ const stat = safeStat(filePath);
150
+ if (!stat) continue;
151
+ return {
152
+ sessionId,
153
+ filePath,
154
+ projectDir: path.dirname(filePath),
155
+ birthtimeMs: stat.birthtimeMs,
156
+ resolvedCwd: parsed.payload.cwd || ''
157
+ };
158
+ } catch {
159
+ continue;
160
+ }
161
+ }
162
+ return null;
163
+ }
164
+ mapDirectMatches(matches) {
165
+ const agents = [];
166
+ const failedProcesses = [];
167
+ for (const match of matches){
168
+ const sessionData = this.parseSession(undefined, match.sessionFile.filePath);
169
+ if (sessionData) {
170
+ agents.push(this.mapSessionToAgent(sessionData, match.process, match.sessionFile.filePath));
171
+ } else {
172
+ failedProcesses.push(match.process);
173
+ }
174
+ }
175
+ return {
176
+ agents,
177
+ failedProcesses
178
+ };
61
179
  }
62
180
  /**
63
181
  * Discover session files for the given processes.
@@ -66,18 +184,18 @@ export class CodexAdapter {
66
184
  * to scan (±1 day window), then batches stat calls across all directories.
67
185
  * Reads each file once and caches content for later parsing by parseSession().
68
186
  * Sets resolvedCwd from session_meta first line.
69
- */
70
- discoverSessions(processes) {
71
- const empty = { sessions: [], contentCache: new Map() };
72
- if (!fs.existsSync(this.codexSessionsDir))
73
- return empty;
187
+ */ discoverSessions(processes) {
188
+ const empty = {
189
+ sessions: [],
190
+ contentCache: new Map()
191
+ };
192
+ if (!fs.existsSync(this.codexSessionsDir)) return empty;
74
193
  const dateDirs = this.getDateDirs(processes);
75
- if (dateDirs.length === 0)
76
- return empty;
194
+ if (dateDirs.length === 0) return empty;
77
195
  const files = batchGetSessionFileBirthtimes(dateDirs);
78
196
  const contentCache = new Map();
79
197
  // Read each file once: extract CWD for matching, cache content for later parsing
80
- for (const file of files) {
198
+ for (const file of files){
81
199
  try {
82
200
  const content = fs.readFileSync(file.filePath, 'utf-8');
83
201
  contentCache.set(file.filePath, content);
@@ -88,37 +206,37 @@ export class CodexAdapter {
88
206
  file.resolvedCwd = parsed.payload?.cwd || '';
89
207
  }
90
208
  }
91
- }
92
- catch {
93
- // Skip unreadable files
209
+ } catch {
210
+ // Skip unreadable files
94
211
  }
95
212
  }
96
- return { sessions: files, contentCache };
213
+ return {
214
+ sessions: files,
215
+ contentCache
216
+ };
97
217
  }
98
218
  /**
99
219
  * Determine which date directories to scan based on process start times.
100
220
  * Returns only directories that actually exist.
101
- */
102
- getDateDirs(processes) {
221
+ */ getDateDirs(processes) {
103
222
  const dayKeys = new Set();
104
223
  const window = CodexAdapter.PROCESS_START_DAY_WINDOW_DAYS;
105
- for (const proc of processes) {
224
+ for (const proc of processes){
106
225
  const startTime = proc.startTime || new Date();
107
- for (let offset = -window; offset <= window; offset++) {
226
+ for(let offset = -window; offset <= window; offset++){
108
227
  const day = new Date(startTime.getTime());
109
228
  day.setDate(day.getDate() + offset);
110
229
  dayKeys.add(this.toSessionDayKey(day));
111
230
  }
112
231
  }
113
232
  const dirs = [];
114
- for (const dayKey of dayKeys) {
233
+ for (const dayKey of dayKeys){
115
234
  const dayDir = path.join(this.codexSessionsDir, dayKey);
116
235
  try {
117
236
  if (fs.statSync(dayDir).isDirectory()) {
118
237
  dirs.push(dayDir);
119
238
  }
120
- }
121
- catch {
239
+ } catch {
122
240
  continue;
123
241
  }
124
242
  }
@@ -133,56 +251,47 @@ export class CodexAdapter {
133
251
  /**
134
252
  * Parse session file content into CodexSession.
135
253
  * Uses cached content if available, otherwise reads from disk.
136
- */
137
- parseSession(cachedContent, filePath) {
254
+ */ parseSession(cachedContent, filePath) {
138
255
  let content;
139
256
  if (cachedContent !== undefined) {
140
257
  content = cachedContent;
141
- }
142
- else {
258
+ } else {
143
259
  try {
144
260
  content = fs.readFileSync(filePath, 'utf-8');
145
- }
146
- catch {
261
+ } catch {
147
262
  return null;
148
263
  }
149
264
  }
150
265
  const allLines = content.trim().split('\n');
151
- if (!allLines[0])
152
- return null;
266
+ if (!allLines[0]) return null;
153
267
  let metaEntry;
154
268
  try {
155
269
  metaEntry = JSON.parse(allLines[0]);
156
- }
157
- catch {
270
+ } catch {
158
271
  return null;
159
272
  }
160
273
  if (metaEntry.type !== 'session_meta' || !metaEntry.payload?.id) {
161
274
  return null;
162
275
  }
163
276
  const entries = [];
164
- for (const line of allLines) {
277
+ for (const line of allLines){
165
278
  try {
166
279
  entries.push(JSON.parse(line));
167
- }
168
- catch {
280
+ } catch {
169
281
  continue;
170
282
  }
171
283
  }
172
284
  const lastEntry = this.findLastEventEntry(entries);
173
285
  const lastPayloadType = lastEntry?.payload?.type;
174
- const lastActive = this.parseTimestamp(lastEntry?.timestamp) ||
175
- this.parseTimestamp(metaEntry.payload.timestamp) ||
176
- fs.statSync(filePath).mtime;
177
- const sessionStart = this.parseTimestamp(metaEntry.payload.timestamp) ||
178
- lastActive;
286
+ const lastActive = this.parseTimestamp(lastEntry?.timestamp) || this.parseTimestamp(metaEntry.payload.timestamp) || fs.statSync(filePath).mtime;
287
+ const sessionStart = this.parseTimestamp(metaEntry.payload.timestamp) || lastActive;
179
288
  return {
180
289
  sessionId: metaEntry.payload.id,
181
290
  projectPath: metaEntry.payload.cwd || '',
182
291
  summary: this.extractSummary(entries),
183
292
  sessionStart,
184
293
  lastActive,
185
- lastPayloadType,
294
+ lastPayloadType
186
295
  };
187
296
  }
188
297
  mapSessionToAgent(session, processInfo, filePath) {
@@ -195,7 +304,7 @@ export class CodexAdapter {
195
304
  projectPath: session.projectPath || processInfo.cwd || '',
196
305
  sessionId: session.sessionId,
197
306
  lastActive: session.lastActive,
198
- sessionFilePath: filePath,
307
+ sessionFilePath: filePath
199
308
  };
200
309
  }
201
310
  mapProcessOnlyAgent(processInfo) {
@@ -207,11 +316,11 @@ export class CodexAdapter {
207
316
  pid: processInfo.pid,
208
317
  projectPath: processInfo.cwd || '',
209
318
  sessionId: `pid-${processInfo.pid}`,
210
- lastActive: new Date(),
319
+ lastActive: new Date()
211
320
  };
212
321
  }
213
322
  findLastEventEntry(entries) {
214
- for (let i = entries.length - 1; i >= 0; i--) {
323
+ for(let i = entries.length - 1; i >= 0; i--){
215
324
  const entry = entries[i];
216
325
  if (entry && typeof entry.type === 'string') {
217
326
  return entry;
@@ -220,8 +329,7 @@ export class CodexAdapter {
220
329
  return undefined;
221
330
  }
222
331
  parseTimestamp(value) {
223
- if (!value)
224
- return null;
332
+ if (!value) return null;
225
333
  const timestamp = new Date(value);
226
334
  return Number.isNaN(timestamp.getTime()) ? null : timestamp;
227
335
  }
@@ -231,15 +339,13 @@ export class CodexAdapter {
231
339
  if (diffMinutes > CodexAdapter.IDLE_THRESHOLD_MINUTES) {
232
340
  return AgentStatus.IDLE;
233
341
  }
234
- if (session.lastPayloadType === 'agent_message' ||
235
- session.lastPayloadType === 'task_complete' ||
236
- session.lastPayloadType === 'turn_aborted') {
342
+ if (session.lastPayloadType === 'agent_message' || session.lastPayloadType === 'task_complete' || session.lastPayloadType === 'turn_aborted') {
237
343
  return AgentStatus.WAITING;
238
344
  }
239
345
  return AgentStatus.RUNNING;
240
346
  }
241
347
  extractSummary(entries) {
242
- for (let i = entries.length - 1; i >= 0; i--) {
348
+ for(let i = entries.length - 1; i >= 0; i--){
243
349
  const message = entries[i]?.payload?.message;
244
350
  if (typeof message === 'string' && message.trim().length > 0) {
245
351
  return this.truncate(message.trim(), 120);
@@ -248,8 +354,7 @@ export class CodexAdapter {
248
354
  return 'Codex session active';
249
355
  }
250
356
  truncate(value, maxLength) {
251
- if (value.length <= maxLength)
252
- return value;
357
+ if (value.length <= maxLength) return value;
253
358
  return `${value.slice(0, maxLength - 3)}...`;
254
359
  }
255
360
  isCodexExecutable(command) {
@@ -261,62 +366,50 @@ export class CodexAdapter {
261
366
  * Read the full conversation from a Codex session JSONL file.
262
367
  *
263
368
  * Codex entries use payload.type to indicate message role and payload.message for content.
264
- */
265
- getConversation(sessionFilePath, options) {
369
+ */ getConversation(sessionFilePath, options) {
266
370
  const verbose = options?.verbose ?? false;
267
371
  const content = safeReadFile(sessionFilePath);
268
- if (content === undefined)
269
- return [];
372
+ if (content === undefined) return [];
270
373
  const lines = content.trim().split('\n');
271
374
  const messages = [];
272
- for (const line of lines) {
375
+ for (const line of lines){
273
376
  let entry;
274
377
  try {
275
378
  entry = JSON.parse(line);
276
- }
277
- catch {
379
+ } catch {
278
380
  continue;
279
381
  }
280
- if (entry.type === 'session_meta')
281
- continue;
382
+ if (entry.type === 'session_meta') continue;
282
383
  const payloadType = entry.payload?.type;
283
- if (!payloadType)
284
- continue;
384
+ if (!payloadType) continue;
285
385
  let role;
286
386
  if (payloadType === 'user_message') {
287
387
  role = 'user';
288
- }
289
- else if (payloadType === 'agent_message' || payloadType === 'task_complete') {
388
+ } else if (payloadType === 'agent_message' || payloadType === 'task_complete') {
290
389
  role = 'assistant';
291
- }
292
- else if (verbose) {
390
+ } else if (verbose) {
293
391
  role = 'system';
294
- }
295
- else {
392
+ } else {
296
393
  continue;
297
394
  }
298
395
  const text = entry.payload?.message?.trim();
299
- if (!text)
300
- continue;
396
+ if (!text) continue;
301
397
  messages.push({
302
398
  role,
303
399
  content: text,
304
- timestamp: entry.timestamp,
400
+ timestamp: entry.timestamp
305
401
  });
306
402
  }
307
403
  return messages;
308
404
  }
309
405
  async listSessions(opts) {
310
- if (!isDirectory(this.codexSessionsDir))
311
- return [];
406
+ if (!isDirectory(this.codexSessionsDir)) return [];
312
407
  const files = this.collectAllSessionFiles();
313
408
  const summaries = [];
314
- for (const filePath of files) {
409
+ for (const filePath of files){
315
410
  const summary = this.fileToSessionSummary(filePath);
316
- if (!summary)
317
- continue;
318
- if (opts?.cwd !== undefined && summary.cwd !== opts.cwd)
319
- continue;
411
+ if (!summary) continue;
412
+ if (opts?.cwd !== undefined && summary.cwd !== opts.cwd) continue;
320
413
  summaries.push(summary);
321
414
  }
322
415
  return summaries;
@@ -325,24 +418,19 @@ export class CodexAdapter {
325
418
  * Walk every YYYY/MM/DD directory under `codexSessionsDir` and return
326
419
  * absolute paths of `.jsonl` files. Tolerates malformed layouts
327
420
  * (skips entries that aren't directories at the expected depth).
328
- */
329
- collectAllSessionFiles() {
421
+ */ collectAllSessionFiles() {
330
422
  const out = [];
331
- for (const yearEntry of safeReaddir(this.codexSessionsDir)) {
423
+ for (const yearEntry of safeReaddir(this.codexSessionsDir)){
332
424
  const yearDir = path.join(this.codexSessionsDir, yearEntry);
333
- if (!isDirectory(yearDir))
334
- continue;
335
- for (const monthEntry of safeReaddir(yearDir)) {
425
+ if (!isDirectory(yearDir)) continue;
426
+ for (const monthEntry of safeReaddir(yearDir)){
336
427
  const monthDir = path.join(yearDir, monthEntry);
337
- if (!isDirectory(monthDir))
338
- continue;
339
- for (const dayEntry of safeReaddir(monthDir)) {
428
+ if (!isDirectory(monthDir)) continue;
429
+ for (const dayEntry of safeReaddir(monthDir)){
340
430
  const dayDir = path.join(monthDir, dayEntry);
341
- if (!isDirectory(dayDir))
342
- continue;
343
- for (const fileEntry of safeReaddir(dayDir)) {
344
- if (!fileEntry.endsWith('.jsonl'))
345
- continue;
431
+ if (!isDirectory(dayDir)) continue;
432
+ for (const fileEntry of safeReaddir(dayDir)){
433
+ if (!fileEntry.endsWith('.jsonl')) continue;
346
434
  out.push(path.join(dayDir, fileEntry));
347
435
  }
348
436
  }
@@ -354,19 +442,15 @@ export class CodexAdapter {
354
442
  * Read a Codex session JSONL file and produce a {@link SessionSummary}.
355
443
  * Returns null when the file is unreadable, has no `session_meta`, or
356
444
  * lacks a session id.
357
- */
358
- fileToSessionSummary(filePath) {
445
+ */ fileToSessionSummary(filePath) {
359
446
  const content = safeReadFile(filePath);
360
- if (content === undefined)
361
- return null;
447
+ if (content === undefined) return null;
362
448
  const allLines = content.trim().split('\n');
363
- if (!allLines[0])
364
- return null;
449
+ if (!allLines[0]) return null;
365
450
  let metaEntry;
366
451
  try {
367
452
  metaEntry = JSON.parse(allLines[0]);
368
- }
369
- catch {
453
+ } catch {
370
454
  return null;
371
455
  }
372
456
  if (metaEntry.type !== 'session_meta' || !metaEntry.payload?.id) {
@@ -374,30 +458,21 @@ export class CodexAdapter {
374
458
  }
375
459
  let firstUserMessage = '';
376
460
  let lastTimestamp = null;
377
- for (let i = 1; i < allLines.length; i++) {
461
+ for(let i = 1; i < allLines.length; i++){
378
462
  let entry;
379
463
  try {
380
464
  entry = JSON.parse(allLines[i]);
381
- }
382
- catch {
465
+ } catch {
383
466
  continue;
384
467
  }
385
468
  const ts = this.parseTimestamp(entry.timestamp);
386
- if (ts)
387
- lastTimestamp = ts;
388
- if (!firstUserMessage &&
389
- entry.payload?.type === 'user_message' &&
390
- typeof entry.payload.message === 'string' &&
391
- entry.payload.message.trim().length > 0) {
469
+ if (ts) lastTimestamp = ts;
470
+ if (!firstUserMessage && entry.payload?.type === 'user_message' && typeof entry.payload.message === 'string' && entry.payload.message.trim().length > 0) {
392
471
  firstUserMessage = entry.payload.message.trim();
393
472
  }
394
473
  }
395
474
  const stat = safeStat(filePath);
396
- const startedAt = this.parseTimestamp(metaEntry.payload.timestamp) ||
397
- lastTimestamp ||
398
- stat?.birthtime ||
399
- stat?.mtime ||
400
- new Date();
475
+ const startedAt = this.parseTimestamp(metaEntry.payload.timestamp) || lastTimestamp || stat?.birthtime || stat?.mtime || new Date();
401
476
  const lastActive = lastTimestamp || startedAt;
402
477
  return {
403
478
  type: 'codex',
@@ -406,8 +481,9 @@ export class CodexAdapter {
406
481
  firstUserMessage,
407
482
  lastActive,
408
483
  startedAt,
409
- sessionFilePath: filePath,
484
+ sessionFilePath: filePath
410
485
  };
411
486
  }
412
487
  }
488
+
413
489
  //# sourceMappingURL=CodexAdapter.js.map