@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.
- package/.swcrc +27 -0
- package/dist/AgentManager.js +72 -74
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +564 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +2157 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js +1419 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js +500 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js +1229 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +626 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/PiAdapter.test.js +590 -0
- package/dist/__tests__/adapters/PiAdapter.test.js.map +1 -0
- package/dist/__tests__/terminal/TmuxManager.test.js +159 -0
- package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -0
- package/dist/__tests__/terminal/TtyWriter.test.js +162 -0
- package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -0
- package/dist/__tests__/utils/AgentRegistry.test.js +282 -0
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js +312 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -0
- package/dist/__tests__/utils/matching.test.js +324 -0
- package/dist/__tests__/utils/matching.test.js.map +1 -0
- package/dist/__tests__/utils/process.test.js +177 -0
- package/dist/__tests__/utils/process.test.js.map +1 -0
- package/dist/__tests__/utils/session.test.js +158 -0
- package/dist/__tests__/utils/session.test.js.map +1 -0
- package/dist/adapters/AgentAdapter.d.ts +1 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js +8 -7
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +66 -77
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +13 -0
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +225 -141
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/adapters/CopilotAdapter.d.ts +44 -0
- package/dist/adapters/CopilotAdapter.d.ts.map +1 -0
- package/dist/adapters/CopilotAdapter.js +314 -0
- package/dist/adapters/CopilotAdapter.js.map +1 -0
- package/dist/adapters/GeminiCliAdapter.d.ts +2 -0
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GeminiCliAdapter.js +138 -156
- package/dist/adapters/GeminiCliAdapter.js.map +1 -1
- package/dist/adapters/OpenCodeAdapter.js +60 -69
- package/dist/adapters/OpenCodeAdapter.js.map +1 -1
- package/dist/adapters/PiAdapter.d.ts +62 -0
- package/dist/adapters/PiAdapter.d.ts.map +1 -0
- package/dist/adapters/PiAdapter.js +450 -0
- package/dist/adapters/PiAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +62 -49
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/terminal/TmuxManager.js +59 -34
- package/dist/terminal/TmuxManager.js.map +1 -1
- package/dist/terminal/TtyWriter.js +37 -16
- package/dist/terminal/TtyWriter.js.map +1 -1
- package/dist/terminal/index.js +1 -0
- package/dist/terminal/index.js.map +1 -1
- package/dist/utils/AgentRegistry.js +41 -32
- package/dist/utils/AgentRegistry.js.map +1 -1
- package/dist/utils/ClaudeSessionParser.js +46 -87
- package/dist/utils/ClaudeSessionParser.js.map +1 -1
- package/dist/utils/agents.d.ts +1 -1
- package/dist/utils/agents.d.ts.map +1 -1
- package/dist/utils/agents.js +22 -11
- package/dist/utils/agents.js.map +1 -1
- package/dist/utils/applescript.js +3 -6
- package/dist/utils/applescript.js.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/matching.js +21 -29
- package/dist/utils/matching.js.map +1 -1
- package/dist/utils/process.js +77 -56
- package/dist/utils/process.js.map +1 -1
- package/dist/utils/session.js +20 -34
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/sortAgents.js +6 -8
- package/dist/utils/sortAgents.js.map +1 -1
- package/package.json +11 -7
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +2 -0
- package/src/__tests__/adapters/CodexAdapter.test.ts +145 -0
- package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +74 -1
- package/src/__tests__/adapters/PiAdapter.test.ts +435 -0
- package/src/adapters/AgentAdapter.ts +1 -1
- package/src/adapters/CodexAdapter.ts +164 -6
- package/src/adapters/CopilotAdapter.ts +453 -0
- package/src/adapters/GeminiCliAdapter.ts +42 -3
- package/src/adapters/PiAdapter.ts +597 -0
- package/src/adapters/index.ts +2 -0
- package/src/index.ts +2 -0
- 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
|
-
|
|
44
|
-
const
|
|
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 [
|
|
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(
|
|
49
|
-
const matchedPids = new Set(
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
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
|
-
|
|
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)
|
|
72
|
-
|
|
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 {
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
122
|
-
// Skip unreadable files
|
|
216
|
+
} catch {
|
|
217
|
+
// Skip unreadable files
|
|
123
218
|
}
|
|
124
219
|
}
|
|
125
|
-
return {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
|
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
|
-
|
|
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
|