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