@ai-devkit/agent-manager 0.1.0 → 0.2.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/dist/adapters/ClaudeCodeAdapter.d.ts +12 -0
- package/dist/adapters/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +208 -50
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +52 -0
- package/dist/adapters/CodexAdapter.d.ts.map +1 -0
- package/dist/adapters/CodexAdapter.js +432 -0
- package/dist/adapters/CodexAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +3 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.d.ts +7 -1
- package/dist/terminal/TerminalFocusManager.d.ts.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +15 -8
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/terminal/index.d.ts +1 -0
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +3 -1
- package/dist/terminal/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +120 -2
- package/src/__tests__/adapters/CodexAdapter.test.ts +319 -0
- package/src/adapters/ClaudeCodeAdapter.ts +309 -56
- package/src/adapters/CodexAdapter.ts +584 -0
- package/src/adapters/index.ts +1 -0
- package/src/index.ts +2 -1
- package/src/terminal/TerminalFocusManager.ts +15 -8
- package/src/terminal/index.ts +1 -0
|
@@ -19,11 +19,21 @@ interface SessionsIndex {
|
|
|
19
19
|
originalPath: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
enum SessionEntryType {
|
|
23
|
+
ASSISTANT = 'assistant',
|
|
24
|
+
USER = 'user',
|
|
25
|
+
PROGRESS = 'progress',
|
|
26
|
+
THINKING = 'thinking',
|
|
27
|
+
SYSTEM = 'system',
|
|
28
|
+
MESSAGE = 'message',
|
|
29
|
+
TEXT = 'text',
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
/**
|
|
23
33
|
* Entry in session JSONL file
|
|
24
34
|
*/
|
|
25
35
|
interface SessionEntry {
|
|
26
|
-
type?:
|
|
36
|
+
type?: SessionEntryType;
|
|
27
37
|
timestamp?: string;
|
|
28
38
|
slug?: string;
|
|
29
39
|
cwd?: string;
|
|
@@ -54,12 +64,15 @@ interface HistoryEntry {
|
|
|
54
64
|
interface ClaudeSession {
|
|
55
65
|
sessionId: string;
|
|
56
66
|
projectPath: string;
|
|
67
|
+
lastCwd?: string;
|
|
57
68
|
slug?: string;
|
|
58
69
|
sessionLogPath: string;
|
|
59
70
|
lastEntry?: SessionEntry;
|
|
60
71
|
lastActive?: Date;
|
|
61
72
|
}
|
|
62
73
|
|
|
74
|
+
type SessionMatchMode = 'cwd' | 'project-parent';
|
|
75
|
+
|
|
63
76
|
/**
|
|
64
77
|
* Claude Code Adapter
|
|
65
78
|
*
|
|
@@ -98,75 +111,274 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
98
111
|
* Detect running Claude Code agents
|
|
99
112
|
*/
|
|
100
113
|
async detectAgents(): Promise<AgentInfo[]> {
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
const claudeProcesses = listProcesses({ namePattern: 'claude' }).filter((processInfo) =>
|
|
115
|
+
this.canHandle(processInfo),
|
|
116
|
+
);
|
|
103
117
|
|
|
104
118
|
if (claudeProcesses.length === 0) {
|
|
105
119
|
return [];
|
|
106
120
|
}
|
|
107
121
|
|
|
108
|
-
// 2. Read all sessions
|
|
109
122
|
const sessions = this.readSessions();
|
|
110
|
-
|
|
111
|
-
// 3. Read history for summaries
|
|
112
123
|
const history = this.readHistory();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
const list = processesByCwd.get(p.cwd) || [];
|
|
118
|
-
list.push(p);
|
|
119
|
-
processesByCwd.set(p.cwd, list);
|
|
124
|
+
const historyByProjectPath = this.indexHistoryByProjectPath(history);
|
|
125
|
+
const historyBySessionId = new Map<string, HistoryEntry>();
|
|
126
|
+
for (const entry of history) {
|
|
127
|
+
historyBySessionId.set(entry.sessionId, entry);
|
|
120
128
|
}
|
|
121
129
|
|
|
122
|
-
|
|
130
|
+
const sortedSessions = [...sessions].sort((a, b) => {
|
|
131
|
+
const timeA = a.lastActive?.getTime() || 0;
|
|
132
|
+
const timeB = b.lastActive?.getTime() || 0;
|
|
133
|
+
return timeB - timeA;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const usedSessionIds = new Set<string>();
|
|
137
|
+
const assignedPids = new Set<number>();
|
|
123
138
|
const agents: AgentInfo[] = [];
|
|
124
139
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
this.assignSessionsForMode(
|
|
141
|
+
'cwd',
|
|
142
|
+
claudeProcesses,
|
|
143
|
+
sortedSessions,
|
|
144
|
+
usedSessionIds,
|
|
145
|
+
assignedPids,
|
|
146
|
+
historyBySessionId,
|
|
147
|
+
agents,
|
|
148
|
+
);
|
|
149
|
+
this.assignHistoryEntriesForExactProcessCwd(
|
|
150
|
+
claudeProcesses,
|
|
151
|
+
assignedPids,
|
|
152
|
+
historyByProjectPath,
|
|
153
|
+
usedSessionIds,
|
|
154
|
+
agents,
|
|
155
|
+
);
|
|
156
|
+
this.assignSessionsForMode(
|
|
157
|
+
'project-parent',
|
|
158
|
+
claudeProcesses,
|
|
159
|
+
sortedSessions,
|
|
160
|
+
usedSessionIds,
|
|
161
|
+
assignedPids,
|
|
162
|
+
historyBySessionId,
|
|
163
|
+
agents,
|
|
164
|
+
);
|
|
165
|
+
for (const processInfo of claudeProcesses) {
|
|
166
|
+
if (assignedPids.has(processInfo.pid)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
assignedPids.add(processInfo.pid);
|
|
171
|
+
agents.push(this.mapProcessOnlyAgent(processInfo, agents, historyByProjectPath, usedSessionIds));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return agents;
|
|
175
|
+
}
|
|
128
176
|
|
|
129
|
-
|
|
177
|
+
private assignHistoryEntriesForExactProcessCwd(
|
|
178
|
+
claudeProcesses: ProcessInfo[],
|
|
179
|
+
assignedPids: Set<number>,
|
|
180
|
+
historyByProjectPath: Map<string, HistoryEntry[]>,
|
|
181
|
+
usedSessionIds: Set<string>,
|
|
182
|
+
agents: AgentInfo[],
|
|
183
|
+
): void {
|
|
184
|
+
for (const processInfo of claudeProcesses) {
|
|
185
|
+
if (assignedPids.has(processInfo.pid)) {
|
|
130
186
|
continue;
|
|
131
187
|
}
|
|
132
188
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return timeB - timeA;
|
|
138
|
-
});
|
|
189
|
+
const historyEntry = this.selectHistoryForProcess(processInfo.cwd || '', historyByProjectPath, usedSessionIds);
|
|
190
|
+
if (!historyEntry) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
139
193
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
194
|
+
assignedPids.add(processInfo.pid);
|
|
195
|
+
usedSessionIds.add(historyEntry.sessionId);
|
|
196
|
+
agents.push(this.mapHistoryToAgent(processInfo, historyEntry, agents));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
143
199
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
200
|
+
private assignSessionsForMode(
|
|
201
|
+
mode: SessionMatchMode,
|
|
202
|
+
claudeProcesses: ProcessInfo[],
|
|
203
|
+
sessions: ClaudeSession[],
|
|
204
|
+
usedSessionIds: Set<string>,
|
|
205
|
+
assignedPids: Set<number>,
|
|
206
|
+
historyBySessionId: Map<string, HistoryEntry>,
|
|
207
|
+
agents: AgentInfo[],
|
|
208
|
+
): void {
|
|
209
|
+
for (const processInfo of claudeProcesses) {
|
|
210
|
+
if (assignedPids.has(processInfo.pid)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
147
213
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const summary = historyEntry?.display || 'Session started';
|
|
152
|
-
const status = this.determineStatus(session);
|
|
153
|
-
const agentName = this.generateAgentName(session, agents); // Pass currently built agents for collision checks
|
|
154
|
-
|
|
155
|
-
agents.push({
|
|
156
|
-
name: agentName,
|
|
157
|
-
type: this.type,
|
|
158
|
-
status,
|
|
159
|
-
summary,
|
|
160
|
-
pid: process.pid,
|
|
161
|
-
projectPath: session.projectPath,
|
|
162
|
-
sessionId: session.sessionId,
|
|
163
|
-
slug: session.slug,
|
|
164
|
-
lastActive: session.lastActive || new Date(),
|
|
165
|
-
});
|
|
214
|
+
const session = this.selectBestSession(processInfo, sessions, usedSessionIds, mode);
|
|
215
|
+
if (!session) {
|
|
216
|
+
continue;
|
|
166
217
|
}
|
|
218
|
+
|
|
219
|
+
usedSessionIds.add(session.sessionId);
|
|
220
|
+
assignedPids.add(processInfo.pid);
|
|
221
|
+
agents.push(this.mapSessionToAgent(session, processInfo, historyBySessionId, agents));
|
|
167
222
|
}
|
|
223
|
+
}
|
|
168
224
|
|
|
169
|
-
|
|
225
|
+
private selectBestSession(
|
|
226
|
+
processInfo: ProcessInfo,
|
|
227
|
+
sessions: ClaudeSession[],
|
|
228
|
+
usedSessionIds: Set<string>,
|
|
229
|
+
mode: SessionMatchMode,
|
|
230
|
+
): ClaudeSession | null {
|
|
231
|
+
const candidates = sessions.filter((session) => {
|
|
232
|
+
if (usedSessionIds.has(session.sessionId)) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (mode === 'cwd') {
|
|
237
|
+
return this.pathEquals(processInfo.cwd, session.lastCwd)
|
|
238
|
+
|| this.pathEquals(processInfo.cwd, session.projectPath);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (mode === 'project-parent') {
|
|
242
|
+
return this.isChildPath(processInfo.cwd, session.projectPath)
|
|
243
|
+
|| this.isChildPath(processInfo.cwd, session.lastCwd);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return false;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
if (candidates.length === 0) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (mode !== 'project-parent') {
|
|
254
|
+
return candidates[0];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return candidates.sort((a, b) => {
|
|
258
|
+
const depthA = Math.max(this.pathDepth(a.projectPath), this.pathDepth(a.lastCwd));
|
|
259
|
+
const depthB = Math.max(this.pathDepth(b.projectPath), this.pathDepth(b.lastCwd));
|
|
260
|
+
if (depthA !== depthB) {
|
|
261
|
+
return depthB - depthA;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const lastActiveA = a.lastActive?.getTime() || 0;
|
|
265
|
+
const lastActiveB = b.lastActive?.getTime() || 0;
|
|
266
|
+
return lastActiveB - lastActiveA;
|
|
267
|
+
})[0];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private mapSessionToAgent(
|
|
271
|
+
session: ClaudeSession,
|
|
272
|
+
processInfo: ProcessInfo,
|
|
273
|
+
historyBySessionId: Map<string, HistoryEntry>,
|
|
274
|
+
existingAgents: AgentInfo[],
|
|
275
|
+
): AgentInfo {
|
|
276
|
+
const historyEntry = historyBySessionId.get(session.sessionId);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
name: this.generateAgentName(session, existingAgents),
|
|
280
|
+
type: this.type,
|
|
281
|
+
status: this.determineStatus(session),
|
|
282
|
+
summary: historyEntry?.display || 'Session started',
|
|
283
|
+
pid: processInfo.pid,
|
|
284
|
+
projectPath: session.projectPath || processInfo.cwd || '',
|
|
285
|
+
sessionId: session.sessionId,
|
|
286
|
+
slug: session.slug,
|
|
287
|
+
lastActive: session.lastActive || new Date(),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private mapProcessOnlyAgent(
|
|
292
|
+
processInfo: ProcessInfo,
|
|
293
|
+
existingAgents: AgentInfo[],
|
|
294
|
+
historyByProjectPath: Map<string, HistoryEntry[]>,
|
|
295
|
+
usedSessionIds: Set<string>,
|
|
296
|
+
): AgentInfo {
|
|
297
|
+
const projectPath = processInfo.cwd || '';
|
|
298
|
+
const historyEntry = this.selectHistoryForProcess(projectPath, historyByProjectPath, usedSessionIds);
|
|
299
|
+
const sessionId = historyEntry?.sessionId || `pid-${processInfo.pid}`;
|
|
300
|
+
const lastActive = historyEntry ? new Date(historyEntry.timestamp) : new Date();
|
|
301
|
+
if (historyEntry) {
|
|
302
|
+
usedSessionIds.add(historyEntry.sessionId);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const processSession: ClaudeSession = {
|
|
306
|
+
sessionId,
|
|
307
|
+
projectPath,
|
|
308
|
+
lastCwd: projectPath,
|
|
309
|
+
sessionLogPath: '',
|
|
310
|
+
lastActive,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
name: this.generateAgentName(processSession, existingAgents),
|
|
315
|
+
type: this.type,
|
|
316
|
+
status: AgentStatus.RUNNING,
|
|
317
|
+
summary: historyEntry?.display || 'Claude process running',
|
|
318
|
+
pid: processInfo.pid,
|
|
319
|
+
projectPath,
|
|
320
|
+
sessionId: processSession.sessionId,
|
|
321
|
+
lastActive: processSession.lastActive || new Date(),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private mapHistoryToAgent(
|
|
326
|
+
processInfo: ProcessInfo,
|
|
327
|
+
historyEntry: HistoryEntry,
|
|
328
|
+
existingAgents: AgentInfo[],
|
|
329
|
+
): AgentInfo {
|
|
330
|
+
const projectPath = processInfo.cwd || historyEntry.project;
|
|
331
|
+
const historySession: ClaudeSession = {
|
|
332
|
+
sessionId: historyEntry.sessionId,
|
|
333
|
+
projectPath,
|
|
334
|
+
lastCwd: projectPath,
|
|
335
|
+
sessionLogPath: '',
|
|
336
|
+
lastActive: new Date(historyEntry.timestamp),
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
name: this.generateAgentName(historySession, existingAgents),
|
|
341
|
+
type: this.type,
|
|
342
|
+
status: AgentStatus.RUNNING,
|
|
343
|
+
summary: historyEntry.display || 'Claude process running',
|
|
344
|
+
pid: processInfo.pid,
|
|
345
|
+
projectPath,
|
|
346
|
+
sessionId: historySession.sessionId,
|
|
347
|
+
lastActive: historySession.lastActive || new Date(),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private indexHistoryByProjectPath(historyEntries: HistoryEntry[]): Map<string, HistoryEntry[]> {
|
|
352
|
+
const grouped = new Map<string, HistoryEntry[]>();
|
|
353
|
+
|
|
354
|
+
for (const entry of historyEntries) {
|
|
355
|
+
const key = this.normalizePath(entry.project);
|
|
356
|
+
const list = grouped.get(key) || [];
|
|
357
|
+
list.push(entry);
|
|
358
|
+
grouped.set(key, list);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
for (const [key, list] of grouped.entries()) {
|
|
362
|
+
grouped.set(
|
|
363
|
+
key,
|
|
364
|
+
[...list].sort((a, b) => b.timestamp - a.timestamp),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return grouped;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private selectHistoryForProcess(
|
|
372
|
+
processCwd: string,
|
|
373
|
+
historyByProjectPath: Map<string, HistoryEntry[]>,
|
|
374
|
+
usedSessionIds: Set<string>,
|
|
375
|
+
): HistoryEntry | undefined {
|
|
376
|
+
if (!processCwd) {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const candidates = historyByProjectPath.get(this.normalizePath(processCwd)) || [];
|
|
381
|
+
return candidates.find((entry) => !usedSessionIds.has(entry.sessionId));
|
|
170
382
|
}
|
|
171
383
|
|
|
172
384
|
/**
|
|
@@ -214,6 +426,7 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
214
426
|
sessions.push({
|
|
215
427
|
sessionId,
|
|
216
428
|
projectPath: sessionsIndex.originalPath,
|
|
429
|
+
lastCwd: sessionData.lastCwd,
|
|
217
430
|
slug: sessionData.slug,
|
|
218
431
|
sessionLogPath,
|
|
219
432
|
lastEntry: sessionData.lastEntry,
|
|
@@ -237,12 +450,14 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
237
450
|
slug?: string;
|
|
238
451
|
lastEntry?: SessionEntry;
|
|
239
452
|
lastActive?: Date;
|
|
453
|
+
lastCwd?: string;
|
|
240
454
|
} {
|
|
241
455
|
const lines = readLastLines(logPath, 100);
|
|
242
456
|
|
|
243
457
|
let slug: string | undefined;
|
|
244
458
|
let lastEntry: SessionEntry | undefined;
|
|
245
459
|
let lastActive: Date | undefined;
|
|
460
|
+
let lastCwd: string | undefined;
|
|
246
461
|
|
|
247
462
|
for (const line of lines) {
|
|
248
463
|
try {
|
|
@@ -257,12 +472,16 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
257
472
|
if (entry.timestamp) {
|
|
258
473
|
lastActive = new Date(entry.timestamp);
|
|
259
474
|
}
|
|
475
|
+
|
|
476
|
+
if (typeof entry.cwd === 'string' && entry.cwd.trim().length > 0) {
|
|
477
|
+
lastCwd = entry.cwd;
|
|
478
|
+
}
|
|
260
479
|
} catch (error) {
|
|
261
480
|
continue;
|
|
262
481
|
}
|
|
263
482
|
}
|
|
264
483
|
|
|
265
|
-
return { slug, lastEntry, lastActive };
|
|
484
|
+
return { slug, lastEntry, lastActive, lastCwd };
|
|
266
485
|
}
|
|
267
486
|
|
|
268
487
|
/**
|
|
@@ -289,12 +508,12 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
289
508
|
return AgentStatus.IDLE;
|
|
290
509
|
}
|
|
291
510
|
|
|
292
|
-
if (entryType ===
|
|
511
|
+
if (entryType === SessionEntryType.USER) {
|
|
293
512
|
// Check if user interrupted manually - this puts agent back in waiting state
|
|
294
513
|
const content = session.lastEntry.message?.content;
|
|
295
514
|
if (Array.isArray(content)) {
|
|
296
515
|
const isInterrupted = content.some(c =>
|
|
297
|
-
(c.type ===
|
|
516
|
+
(c.type === SessionEntryType.TEXT && c.text?.includes('[Request interrupted')) ||
|
|
298
517
|
(c.type === 'tool_result' && c.content?.includes('[Request interrupted'))
|
|
299
518
|
);
|
|
300
519
|
if (isInterrupted) return AgentStatus.WAITING;
|
|
@@ -302,11 +521,11 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
302
521
|
return AgentStatus.RUNNING;
|
|
303
522
|
}
|
|
304
523
|
|
|
305
|
-
if (entryType ===
|
|
524
|
+
if (entryType === SessionEntryType.PROGRESS || entryType === SessionEntryType.THINKING) {
|
|
306
525
|
return AgentStatus.RUNNING;
|
|
307
|
-
} else if (entryType ===
|
|
526
|
+
} else if (entryType === SessionEntryType.ASSISTANT) {
|
|
308
527
|
return AgentStatus.WAITING;
|
|
309
|
-
} else if (entryType ===
|
|
528
|
+
} else if (entryType === SessionEntryType.SYSTEM) {
|
|
310
529
|
return AgentStatus.IDLE;
|
|
311
530
|
}
|
|
312
531
|
|
|
@@ -318,7 +537,7 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
318
537
|
* Uses project basename, appends slug if multiple sessions for same project
|
|
319
538
|
*/
|
|
320
539
|
private generateAgentName(session: ClaudeSession, existingAgents: AgentInfo[]): string {
|
|
321
|
-
const projectName = path.basename(session.projectPath);
|
|
540
|
+
const projectName = path.basename(session.projectPath) || 'claude';
|
|
322
541
|
|
|
323
542
|
const sameProjectAgents = existingAgents.filter(
|
|
324
543
|
a => a.projectPath === session.projectPath
|
|
@@ -341,4 +560,38 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
341
560
|
return `${projectName} (${session.sessionId.slice(0, 8)})`;
|
|
342
561
|
}
|
|
343
562
|
|
|
563
|
+
private pathEquals(a?: string, b?: string): boolean {
|
|
564
|
+
if (!a || !b) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return this.normalizePath(a) === this.normalizePath(b);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private isChildPath(child?: string, parent?: string): boolean {
|
|
572
|
+
if (!child || !parent) {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const normalizedChild = this.normalizePath(child);
|
|
577
|
+
const normalizedParent = this.normalizePath(parent);
|
|
578
|
+
return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${path.sep}`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
private normalizePath(value: string): string {
|
|
582
|
+
const resolved = path.resolve(value);
|
|
583
|
+
if (resolved.length > 1 && resolved.endsWith(path.sep)) {
|
|
584
|
+
return resolved.slice(0, -1);
|
|
585
|
+
}
|
|
586
|
+
return resolved;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private pathDepth(value?: string): number {
|
|
590
|
+
if (!value) {
|
|
591
|
+
return 0;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return this.normalizePath(value).split(path.sep).filter(Boolean).length;
|
|
595
|
+
}
|
|
596
|
+
|
|
344
597
|
}
|