@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.
- package/.swcrc +27 -0
- package/dist/AgentManager.d.ts +4 -0
- package/dist/AgentManager.d.ts.map +1 -1
- package/dist/AgentManager.js +97 -64
- 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 +13 -1
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +208 -132
- 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 +6 -1
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GeminiCliAdapter.js +160 -149
- 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 +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -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.d.ts +28 -0
- package/dist/terminal/TmuxManager.d.ts.map +1 -0
- package/dist/terminal/TmuxManager.js +137 -0
- package/dist/terminal/TmuxManager.js.map +1 -0
- 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.d.ts +35 -0
- package/dist/utils/AgentRegistry.d.ts.map +1 -0
- package/dist/utils/AgentRegistry.js +123 -0
- package/dist/utils/AgentRegistry.js.map +1 -0
- package/dist/utils/ClaudeSessionParser.d.ts.map +1 -1
- package/dist/utils/ClaudeSessionParser.js +62 -93
- package/dist/utils/ClaudeSessionParser.js.map +1 -1
- package/dist/utils/agents.d.ts +15 -0
- package/dist/utils/agents.d.ts.map +1 -0
- package/dist/utils/agents.js +41 -0
- package/dist/utils/agents.js.map +1 -0
- 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/AgentManager.ts +40 -0
- package/src/__tests__/AgentManager.test.ts +135 -0
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +3 -2
- package/src/__tests__/adapters/CodexAdapter.test.ts +199 -0
- package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +185 -1
- package/src/__tests__/terminal/TmuxManager.test.ts +175 -0
- package/src/__tests__/utils/AgentRegistry.test.ts +223 -0
- package/src/__tests__/utils/ClaudeSessionParser.test.ts +71 -0
- package/src/adapters/AgentAdapter.ts +1 -1
- package/src/adapters/CodexAdapter.ts +155 -8
- package/src/adapters/CopilotAdapter.ts +453 -0
- package/src/adapters/GeminiCliAdapter.ts +84 -6
- package/src/adapters/index.ts +1 -0
- package/src/index.ts +7 -0
- package/src/terminal/TmuxManager.ts +118 -0
- package/src/utils/AgentRegistry.ts +139 -0
- package/src/utils/ClaudeSessionParser.ts +21 -10
- 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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
|
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(
|
|
43
|
-
const matchedPids = new Set(
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
93
|
-
// Skip unreadable files
|
|
209
|
+
} catch {
|
|
210
|
+
// Skip unreadable files
|
|
94
211
|
}
|
|
95
212
|
}
|
|
96
|
-
return {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
343
|
-
|
|
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
|
|
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
|
-
|
|
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
|