@ai-devkit/agent-manager 0.16.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.
- 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 +1354 -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__/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 +9 -0
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +188 -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/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +94 -0
- package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +74 -1
- package/src/adapters/AgentAdapter.ts +1 -1
- package/src/adapters/CodexAdapter.ts +114 -6
- package/src/adapters/CopilotAdapter.ts +453 -0
- package/src/adapters/GeminiCliAdapter.ts +42 -3
- package/src/adapters/index.ts +1 -0
- package/src/index.ts +1 -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,84 @@ 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.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
|
+
};
|
|
90
179
|
}
|
|
91
180
|
/**
|
|
92
181
|
* Discover session files for the given processes.
|
|
@@ -95,18 +184,18 @@ export class CodexAdapter {
|
|
|
95
184
|
* to scan (±1 day window), then batches stat calls across all directories.
|
|
96
185
|
* Reads each file once and caches content for later parsing by parseSession().
|
|
97
186
|
* Sets resolvedCwd from session_meta first line.
|
|
98
|
-
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
187
|
+
*/ discoverSessions(processes) {
|
|
188
|
+
const empty = {
|
|
189
|
+
sessions: [],
|
|
190
|
+
contentCache: new Map()
|
|
191
|
+
};
|
|
192
|
+
if (!fs.existsSync(this.codexSessionsDir)) return empty;
|
|
103
193
|
const dateDirs = this.getDateDirs(processes);
|
|
104
|
-
if (dateDirs.length === 0)
|
|
105
|
-
return empty;
|
|
194
|
+
if (dateDirs.length === 0) return empty;
|
|
106
195
|
const files = batchGetSessionFileBirthtimes(dateDirs);
|
|
107
196
|
const contentCache = new Map();
|
|
108
197
|
// Read each file once: extract CWD for matching, cache content for later parsing
|
|
109
|
-
for (const file of files)
|
|
198
|
+
for (const file of files){
|
|
110
199
|
try {
|
|
111
200
|
const content = fs.readFileSync(file.filePath, 'utf-8');
|
|
112
201
|
contentCache.set(file.filePath, content);
|
|
@@ -117,37 +206,37 @@ export class CodexAdapter {
|
|
|
117
206
|
file.resolvedCwd = parsed.payload?.cwd || '';
|
|
118
207
|
}
|
|
119
208
|
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Skip unreadable files
|
|
209
|
+
} catch {
|
|
210
|
+
// Skip unreadable files
|
|
123
211
|
}
|
|
124
212
|
}
|
|
125
|
-
return {
|
|
213
|
+
return {
|
|
214
|
+
sessions: files,
|
|
215
|
+
contentCache
|
|
216
|
+
};
|
|
126
217
|
}
|
|
127
218
|
/**
|
|
128
219
|
* Determine which date directories to scan based on process start times.
|
|
129
220
|
* Returns only directories that actually exist.
|
|
130
|
-
*/
|
|
131
|
-
getDateDirs(processes) {
|
|
221
|
+
*/ getDateDirs(processes) {
|
|
132
222
|
const dayKeys = new Set();
|
|
133
223
|
const window = CodexAdapter.PROCESS_START_DAY_WINDOW_DAYS;
|
|
134
|
-
for (const proc of processes)
|
|
224
|
+
for (const proc of processes){
|
|
135
225
|
const startTime = proc.startTime || new Date();
|
|
136
|
-
for
|
|
226
|
+
for(let offset = -window; offset <= window; offset++){
|
|
137
227
|
const day = new Date(startTime.getTime());
|
|
138
228
|
day.setDate(day.getDate() + offset);
|
|
139
229
|
dayKeys.add(this.toSessionDayKey(day));
|
|
140
230
|
}
|
|
141
231
|
}
|
|
142
232
|
const dirs = [];
|
|
143
|
-
for (const dayKey of dayKeys)
|
|
233
|
+
for (const dayKey of dayKeys){
|
|
144
234
|
const dayDir = path.join(this.codexSessionsDir, dayKey);
|
|
145
235
|
try {
|
|
146
236
|
if (fs.statSync(dayDir).isDirectory()) {
|
|
147
237
|
dirs.push(dayDir);
|
|
148
238
|
}
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
239
|
+
} catch {
|
|
151
240
|
continue;
|
|
152
241
|
}
|
|
153
242
|
}
|
|
@@ -162,56 +251,47 @@ export class CodexAdapter {
|
|
|
162
251
|
/**
|
|
163
252
|
* Parse session file content into CodexSession.
|
|
164
253
|
* Uses cached content if available, otherwise reads from disk.
|
|
165
|
-
*/
|
|
166
|
-
parseSession(cachedContent, filePath) {
|
|
254
|
+
*/ parseSession(cachedContent, filePath) {
|
|
167
255
|
let content;
|
|
168
256
|
if (cachedContent !== undefined) {
|
|
169
257
|
content = cachedContent;
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
258
|
+
} else {
|
|
172
259
|
try {
|
|
173
260
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
261
|
+
} catch {
|
|
176
262
|
return null;
|
|
177
263
|
}
|
|
178
264
|
}
|
|
179
265
|
const allLines = content.trim().split('\n');
|
|
180
|
-
if (!allLines[0])
|
|
181
|
-
return null;
|
|
266
|
+
if (!allLines[0]) return null;
|
|
182
267
|
let metaEntry;
|
|
183
268
|
try {
|
|
184
269
|
metaEntry = JSON.parse(allLines[0]);
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
270
|
+
} catch {
|
|
187
271
|
return null;
|
|
188
272
|
}
|
|
189
273
|
if (metaEntry.type !== 'session_meta' || !metaEntry.payload?.id) {
|
|
190
274
|
return null;
|
|
191
275
|
}
|
|
192
276
|
const entries = [];
|
|
193
|
-
for (const line of allLines)
|
|
277
|
+
for (const line of allLines){
|
|
194
278
|
try {
|
|
195
279
|
entries.push(JSON.parse(line));
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
280
|
+
} catch {
|
|
198
281
|
continue;
|
|
199
282
|
}
|
|
200
283
|
}
|
|
201
284
|
const lastEntry = this.findLastEventEntry(entries);
|
|
202
285
|
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;
|
|
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;
|
|
208
288
|
return {
|
|
209
289
|
sessionId: metaEntry.payload.id,
|
|
210
290
|
projectPath: metaEntry.payload.cwd || '',
|
|
211
291
|
summary: this.extractSummary(entries),
|
|
212
292
|
sessionStart,
|
|
213
293
|
lastActive,
|
|
214
|
-
lastPayloadType
|
|
294
|
+
lastPayloadType
|
|
215
295
|
};
|
|
216
296
|
}
|
|
217
297
|
mapSessionToAgent(session, processInfo, filePath) {
|
|
@@ -224,7 +304,7 @@ export class CodexAdapter {
|
|
|
224
304
|
projectPath: session.projectPath || processInfo.cwd || '',
|
|
225
305
|
sessionId: session.sessionId,
|
|
226
306
|
lastActive: session.lastActive,
|
|
227
|
-
sessionFilePath: filePath
|
|
307
|
+
sessionFilePath: filePath
|
|
228
308
|
};
|
|
229
309
|
}
|
|
230
310
|
mapProcessOnlyAgent(processInfo) {
|
|
@@ -236,11 +316,11 @@ export class CodexAdapter {
|
|
|
236
316
|
pid: processInfo.pid,
|
|
237
317
|
projectPath: processInfo.cwd || '',
|
|
238
318
|
sessionId: `pid-${processInfo.pid}`,
|
|
239
|
-
lastActive: new Date()
|
|
319
|
+
lastActive: new Date()
|
|
240
320
|
};
|
|
241
321
|
}
|
|
242
322
|
findLastEventEntry(entries) {
|
|
243
|
-
for
|
|
323
|
+
for(let i = entries.length - 1; i >= 0; i--){
|
|
244
324
|
const entry = entries[i];
|
|
245
325
|
if (entry && typeof entry.type === 'string') {
|
|
246
326
|
return entry;
|
|
@@ -249,8 +329,7 @@ export class CodexAdapter {
|
|
|
249
329
|
return undefined;
|
|
250
330
|
}
|
|
251
331
|
parseTimestamp(value) {
|
|
252
|
-
if (!value)
|
|
253
|
-
return null;
|
|
332
|
+
if (!value) return null;
|
|
254
333
|
const timestamp = new Date(value);
|
|
255
334
|
return Number.isNaN(timestamp.getTime()) ? null : timestamp;
|
|
256
335
|
}
|
|
@@ -260,15 +339,13 @@ export class CodexAdapter {
|
|
|
260
339
|
if (diffMinutes > CodexAdapter.IDLE_THRESHOLD_MINUTES) {
|
|
261
340
|
return AgentStatus.IDLE;
|
|
262
341
|
}
|
|
263
|
-
if (session.lastPayloadType === 'agent_message' ||
|
|
264
|
-
session.lastPayloadType === 'task_complete' ||
|
|
265
|
-
session.lastPayloadType === 'turn_aborted') {
|
|
342
|
+
if (session.lastPayloadType === 'agent_message' || session.lastPayloadType === 'task_complete' || session.lastPayloadType === 'turn_aborted') {
|
|
266
343
|
return AgentStatus.WAITING;
|
|
267
344
|
}
|
|
268
345
|
return AgentStatus.RUNNING;
|
|
269
346
|
}
|
|
270
347
|
extractSummary(entries) {
|
|
271
|
-
for
|
|
348
|
+
for(let i = entries.length - 1; i >= 0; i--){
|
|
272
349
|
const message = entries[i]?.payload?.message;
|
|
273
350
|
if (typeof message === 'string' && message.trim().length > 0) {
|
|
274
351
|
return this.truncate(message.trim(), 120);
|
|
@@ -277,8 +354,7 @@ export class CodexAdapter {
|
|
|
277
354
|
return 'Codex session active';
|
|
278
355
|
}
|
|
279
356
|
truncate(value, maxLength) {
|
|
280
|
-
if (value.length <= maxLength)
|
|
281
|
-
return value;
|
|
357
|
+
if (value.length <= maxLength) return value;
|
|
282
358
|
return `${value.slice(0, maxLength - 3)}...`;
|
|
283
359
|
}
|
|
284
360
|
isCodexExecutable(command) {
|
|
@@ -290,62 +366,50 @@ export class CodexAdapter {
|
|
|
290
366
|
* Read the full conversation from a Codex session JSONL file.
|
|
291
367
|
*
|
|
292
368
|
* Codex entries use payload.type to indicate message role and payload.message for content.
|
|
293
|
-
*/
|
|
294
|
-
getConversation(sessionFilePath, options) {
|
|
369
|
+
*/ getConversation(sessionFilePath, options) {
|
|
295
370
|
const verbose = options?.verbose ?? false;
|
|
296
371
|
const content = safeReadFile(sessionFilePath);
|
|
297
|
-
if (content === undefined)
|
|
298
|
-
return [];
|
|
372
|
+
if (content === undefined) return [];
|
|
299
373
|
const lines = content.trim().split('\n');
|
|
300
374
|
const messages = [];
|
|
301
|
-
for (const line of lines)
|
|
375
|
+
for (const line of lines){
|
|
302
376
|
let entry;
|
|
303
377
|
try {
|
|
304
378
|
entry = JSON.parse(line);
|
|
305
|
-
}
|
|
306
|
-
catch {
|
|
379
|
+
} catch {
|
|
307
380
|
continue;
|
|
308
381
|
}
|
|
309
|
-
if (entry.type === 'session_meta')
|
|
310
|
-
continue;
|
|
382
|
+
if (entry.type === 'session_meta') continue;
|
|
311
383
|
const payloadType = entry.payload?.type;
|
|
312
|
-
if (!payloadType)
|
|
313
|
-
continue;
|
|
384
|
+
if (!payloadType) continue;
|
|
314
385
|
let role;
|
|
315
386
|
if (payloadType === 'user_message') {
|
|
316
387
|
role = 'user';
|
|
317
|
-
}
|
|
318
|
-
else if (payloadType === 'agent_message' || payloadType === 'task_complete') {
|
|
388
|
+
} else if (payloadType === 'agent_message' || payloadType === 'task_complete') {
|
|
319
389
|
role = 'assistant';
|
|
320
|
-
}
|
|
321
|
-
else if (verbose) {
|
|
390
|
+
} else if (verbose) {
|
|
322
391
|
role = 'system';
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
392
|
+
} else {
|
|
325
393
|
continue;
|
|
326
394
|
}
|
|
327
395
|
const text = entry.payload?.message?.trim();
|
|
328
|
-
if (!text)
|
|
329
|
-
continue;
|
|
396
|
+
if (!text) continue;
|
|
330
397
|
messages.push({
|
|
331
398
|
role,
|
|
332
399
|
content: text,
|
|
333
|
-
timestamp: entry.timestamp
|
|
400
|
+
timestamp: entry.timestamp
|
|
334
401
|
});
|
|
335
402
|
}
|
|
336
403
|
return messages;
|
|
337
404
|
}
|
|
338
405
|
async listSessions(opts) {
|
|
339
|
-
if (!isDirectory(this.codexSessionsDir))
|
|
340
|
-
return [];
|
|
406
|
+
if (!isDirectory(this.codexSessionsDir)) return [];
|
|
341
407
|
const files = this.collectAllSessionFiles();
|
|
342
408
|
const summaries = [];
|
|
343
|
-
for (const filePath of files)
|
|
409
|
+
for (const filePath of files){
|
|
344
410
|
const summary = this.fileToSessionSummary(filePath);
|
|
345
|
-
if (!summary)
|
|
346
|
-
|
|
347
|
-
if (opts?.cwd !== undefined && summary.cwd !== opts.cwd)
|
|
348
|
-
continue;
|
|
411
|
+
if (!summary) continue;
|
|
412
|
+
if (opts?.cwd !== undefined && summary.cwd !== opts.cwd) continue;
|
|
349
413
|
summaries.push(summary);
|
|
350
414
|
}
|
|
351
415
|
return summaries;
|
|
@@ -354,24 +418,19 @@ export class CodexAdapter {
|
|
|
354
418
|
* Walk every YYYY/MM/DD directory under `codexSessionsDir` and return
|
|
355
419
|
* absolute paths of `.jsonl` files. Tolerates malformed layouts
|
|
356
420
|
* (skips entries that aren't directories at the expected depth).
|
|
357
|
-
*/
|
|
358
|
-
collectAllSessionFiles() {
|
|
421
|
+
*/ collectAllSessionFiles() {
|
|
359
422
|
const out = [];
|
|
360
|
-
for (const yearEntry of safeReaddir(this.codexSessionsDir))
|
|
423
|
+
for (const yearEntry of safeReaddir(this.codexSessionsDir)){
|
|
361
424
|
const yearDir = path.join(this.codexSessionsDir, yearEntry);
|
|
362
|
-
if (!isDirectory(yearDir))
|
|
363
|
-
|
|
364
|
-
for (const monthEntry of safeReaddir(yearDir)) {
|
|
425
|
+
if (!isDirectory(yearDir)) continue;
|
|
426
|
+
for (const monthEntry of safeReaddir(yearDir)){
|
|
365
427
|
const monthDir = path.join(yearDir, monthEntry);
|
|
366
|
-
if (!isDirectory(monthDir))
|
|
367
|
-
|
|
368
|
-
for (const dayEntry of safeReaddir(monthDir)) {
|
|
428
|
+
if (!isDirectory(monthDir)) continue;
|
|
429
|
+
for (const dayEntry of safeReaddir(monthDir)){
|
|
369
430
|
const dayDir = path.join(monthDir, dayEntry);
|
|
370
|
-
if (!isDirectory(dayDir))
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (!fileEntry.endsWith('.jsonl'))
|
|
374
|
-
continue;
|
|
431
|
+
if (!isDirectory(dayDir)) continue;
|
|
432
|
+
for (const fileEntry of safeReaddir(dayDir)){
|
|
433
|
+
if (!fileEntry.endsWith('.jsonl')) continue;
|
|
375
434
|
out.push(path.join(dayDir, fileEntry));
|
|
376
435
|
}
|
|
377
436
|
}
|
|
@@ -383,19 +442,15 @@ export class CodexAdapter {
|
|
|
383
442
|
* Read a Codex session JSONL file and produce a {@link SessionSummary}.
|
|
384
443
|
* Returns null when the file is unreadable, has no `session_meta`, or
|
|
385
444
|
* lacks a session id.
|
|
386
|
-
*/
|
|
387
|
-
fileToSessionSummary(filePath) {
|
|
445
|
+
*/ fileToSessionSummary(filePath) {
|
|
388
446
|
const content = safeReadFile(filePath);
|
|
389
|
-
if (content === undefined)
|
|
390
|
-
return null;
|
|
447
|
+
if (content === undefined) return null;
|
|
391
448
|
const allLines = content.trim().split('\n');
|
|
392
|
-
if (!allLines[0])
|
|
393
|
-
return null;
|
|
449
|
+
if (!allLines[0]) return null;
|
|
394
450
|
let metaEntry;
|
|
395
451
|
try {
|
|
396
452
|
metaEntry = JSON.parse(allLines[0]);
|
|
397
|
-
}
|
|
398
|
-
catch {
|
|
453
|
+
} catch {
|
|
399
454
|
return null;
|
|
400
455
|
}
|
|
401
456
|
if (metaEntry.type !== 'session_meta' || !metaEntry.payload?.id) {
|
|
@@ -403,30 +458,21 @@ export class CodexAdapter {
|
|
|
403
458
|
}
|
|
404
459
|
let firstUserMessage = '';
|
|
405
460
|
let lastTimestamp = null;
|
|
406
|
-
for
|
|
461
|
+
for(let i = 1; i < allLines.length; i++){
|
|
407
462
|
let entry;
|
|
408
463
|
try {
|
|
409
464
|
entry = JSON.parse(allLines[i]);
|
|
410
|
-
}
|
|
411
|
-
catch {
|
|
465
|
+
} catch {
|
|
412
466
|
continue;
|
|
413
467
|
}
|
|
414
468
|
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) {
|
|
469
|
+
if (ts) lastTimestamp = ts;
|
|
470
|
+
if (!firstUserMessage && entry.payload?.type === 'user_message' && typeof entry.payload.message === 'string' && entry.payload.message.trim().length > 0) {
|
|
421
471
|
firstUserMessage = entry.payload.message.trim();
|
|
422
472
|
}
|
|
423
473
|
}
|
|
424
474
|
const stat = safeStat(filePath);
|
|
425
|
-
const startedAt = this.parseTimestamp(metaEntry.payload.timestamp) ||
|
|
426
|
-
lastTimestamp ||
|
|
427
|
-
stat?.birthtime ||
|
|
428
|
-
stat?.mtime ||
|
|
429
|
-
new Date();
|
|
475
|
+
const startedAt = this.parseTimestamp(metaEntry.payload.timestamp) || lastTimestamp || stat?.birthtime || stat?.mtime || new Date();
|
|
430
476
|
const lastActive = lastTimestamp || startedAt;
|
|
431
477
|
return {
|
|
432
478
|
type: 'codex',
|
|
@@ -435,8 +481,9 @@ export class CodexAdapter {
|
|
|
435
481
|
firstUserMessage,
|
|
436
482
|
lastActive,
|
|
437
483
|
startedAt,
|
|
438
|
-
sessionFilePath: filePath
|
|
484
|
+
sessionFilePath: filePath
|
|
439
485
|
};
|
|
440
486
|
}
|
|
441
487
|
}
|
|
488
|
+
|
|
442
489
|
//# sourceMappingURL=CodexAdapter.js.map
|