@ai-devkit/agent-manager 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/.swcrc +27 -0
  2. package/dist/AgentManager.js +72 -74
  3. package/dist/AgentManager.js.map +1 -1
  4. package/dist/__tests__/AgentManager.test.js +564 -0
  5. package/dist/__tests__/AgentManager.test.js.map +1 -0
  6. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +2157 -0
  7. package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -0
  8. package/dist/__tests__/adapters/CodexAdapter.test.js +1419 -0
  9. package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -0
  10. package/dist/__tests__/adapters/CopilotAdapter.test.js +500 -0
  11. package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -0
  12. package/dist/__tests__/adapters/GeminiCliAdapter.test.js +1229 -0
  13. package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -0
  14. package/dist/__tests__/adapters/OpenCodeAdapter.test.js +626 -0
  15. package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -0
  16. package/dist/__tests__/adapters/PiAdapter.test.js +590 -0
  17. package/dist/__tests__/adapters/PiAdapter.test.js.map +1 -0
  18. package/dist/__tests__/terminal/TmuxManager.test.js +159 -0
  19. package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -0
  20. package/dist/__tests__/terminal/TtyWriter.test.js +162 -0
  21. package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -0
  22. package/dist/__tests__/utils/AgentRegistry.test.js +282 -0
  23. package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -0
  24. package/dist/__tests__/utils/ClaudeSessionParser.test.js +312 -0
  25. package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -0
  26. package/dist/__tests__/utils/matching.test.js +324 -0
  27. package/dist/__tests__/utils/matching.test.js.map +1 -0
  28. package/dist/__tests__/utils/process.test.js +177 -0
  29. package/dist/__tests__/utils/process.test.js.map +1 -0
  30. package/dist/__tests__/utils/session.test.js +158 -0
  31. package/dist/__tests__/utils/session.test.js.map +1 -0
  32. package/dist/adapters/AgentAdapter.d.ts +1 -1
  33. package/dist/adapters/AgentAdapter.d.ts.map +1 -1
  34. package/dist/adapters/AgentAdapter.js +8 -7
  35. package/dist/adapters/AgentAdapter.js.map +1 -1
  36. package/dist/adapters/ClaudeCodeAdapter.js +66 -77
  37. package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
  38. package/dist/adapters/CodexAdapter.d.ts +13 -0
  39. package/dist/adapters/CodexAdapter.d.ts.map +1 -1
  40. package/dist/adapters/CodexAdapter.js +225 -141
  41. package/dist/adapters/CodexAdapter.js.map +1 -1
  42. package/dist/adapters/CopilotAdapter.d.ts +44 -0
  43. package/dist/adapters/CopilotAdapter.d.ts.map +1 -0
  44. package/dist/adapters/CopilotAdapter.js +314 -0
  45. package/dist/adapters/CopilotAdapter.js.map +1 -0
  46. package/dist/adapters/GeminiCliAdapter.d.ts +2 -0
  47. package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
  48. package/dist/adapters/GeminiCliAdapter.js +138 -156
  49. package/dist/adapters/GeminiCliAdapter.js.map +1 -1
  50. package/dist/adapters/OpenCodeAdapter.js +60 -69
  51. package/dist/adapters/OpenCodeAdapter.js.map +1 -1
  52. package/dist/adapters/PiAdapter.d.ts +62 -0
  53. package/dist/adapters/PiAdapter.d.ts.map +1 -0
  54. package/dist/adapters/PiAdapter.js +450 -0
  55. package/dist/adapters/PiAdapter.js.map +1 -0
  56. package/dist/adapters/index.d.ts +2 -0
  57. package/dist/adapters/index.d.ts.map +1 -1
  58. package/dist/adapters/index.js +3 -0
  59. package/dist/adapters/index.js.map +1 -1
  60. package/dist/index.d.ts +2 -0
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +3 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/terminal/TerminalFocusManager.js +62 -49
  65. package/dist/terminal/TerminalFocusManager.js.map +1 -1
  66. package/dist/terminal/TmuxManager.js +59 -34
  67. package/dist/terminal/TmuxManager.js.map +1 -1
  68. package/dist/terminal/TtyWriter.js +37 -16
  69. package/dist/terminal/TtyWriter.js.map +1 -1
  70. package/dist/terminal/index.js +1 -0
  71. package/dist/terminal/index.js.map +1 -1
  72. package/dist/utils/AgentRegistry.js +41 -32
  73. package/dist/utils/AgentRegistry.js.map +1 -1
  74. package/dist/utils/ClaudeSessionParser.js +46 -87
  75. package/dist/utils/ClaudeSessionParser.js.map +1 -1
  76. package/dist/utils/agents.d.ts +1 -1
  77. package/dist/utils/agents.d.ts.map +1 -1
  78. package/dist/utils/agents.js +22 -11
  79. package/dist/utils/agents.js.map +1 -1
  80. package/dist/utils/applescript.js +3 -6
  81. package/dist/utils/applescript.js.map +1 -1
  82. package/dist/utils/index.js +1 -0
  83. package/dist/utils/index.js.map +1 -1
  84. package/dist/utils/matching.js +21 -29
  85. package/dist/utils/matching.js.map +1 -1
  86. package/dist/utils/process.js +77 -56
  87. package/dist/utils/process.js.map +1 -1
  88. package/dist/utils/session.js +20 -34
  89. package/dist/utils/session.js.map +1 -1
  90. package/dist/utils/sortAgents.js +6 -8
  91. package/dist/utils/sortAgents.js.map +1 -1
  92. package/package.json +11 -7
  93. package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +2 -0
  94. package/src/__tests__/adapters/CodexAdapter.test.ts +145 -0
  95. package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
  96. package/src/__tests__/adapters/GeminiCliAdapter.test.ts +74 -1
  97. package/src/__tests__/adapters/PiAdapter.test.ts +435 -0
  98. package/src/adapters/AgentAdapter.ts +1 -1
  99. package/src/adapters/CodexAdapter.ts +164 -6
  100. package/src/adapters/CopilotAdapter.ts +453 -0
  101. package/src/adapters/GeminiCliAdapter.ts +42 -3
  102. package/src/adapters/PiAdapter.ts +597 -0
  103. package/src/adapters/index.ts +2 -0
  104. package/src/index.ts +2 -0
  105. package/src/utils/agents.ts +1 -1
@@ -8,8 +8,7 @@
8
8
  * 4. Matching sessions to processes via shared matchProcessesToSessions()
9
9
  * using sha256(cwd) === session.projectHash as the resolvedCwd source
10
10
  * 5. Extracting summary from the most recent user message in the session JSON
11
- */
12
- import * as crypto from 'crypto';
11
+ */ import * as crypto from 'crypto';
13
12
  import * as fs from 'fs';
14
13
  import * as path from 'path';
15
14
  import { AgentStatus } from './AgentAdapter.js';
@@ -25,7 +24,7 @@ export class GeminiCliAdapter {
25
24
  static TMP_DIR_NAME = 'tmp';
26
25
  geminiTmpDir;
27
26
  registry;
28
- constructor(registry = AgentRegistry.default()) {
27
+ constructor(registry = AgentRegistry.default()){
29
28
  const homeDir = process.env.HOME || process.env.USERPROFILE || '';
30
29
  this.geminiTmpDir = path.join(homeDir, '.gemini', GeminiCliAdapter.TMP_DIR_NAME);
31
30
  this.registry = registry;
@@ -44,49 +43,81 @@ export class GeminiCliAdapter {
44
43
  * argv[0] = `node`. We scan the Node process pool via the shared
45
44
  * helper and keep only those whose command line references the gemini
46
45
  * executable or script via isGeminiExecutable().
47
- */
48
- async detectAgents() {
46
+ */ async detectAgents() {
49
47
  const nodeProcesses = enrichProcesses(listAgentProcesses('node'));
50
- const processes = nodeProcesses.filter((proc) => this.isGeminiExecutable(proc.command));
51
- if (processes.length === 0)
52
- return [];
48
+ const processes = nodeProcesses.filter((proc)=>this.isGeminiExecutable(proc.command));
49
+ if (processes.length === 0) return [];
53
50
  const { cachedAgents, remaining } = this.tryRegistryCache(processes);
54
- if (remaining.length === 0)
55
- return cachedAgents;
51
+ if (remaining.length === 0) return this.deduplicateSessionAgents(cachedAgents);
56
52
  const { sessions, contentCache } = this.discoverSessions(remaining);
57
53
  if (sessions.length === 0) {
58
- return [...cachedAgents, ...remaining.map((p) => this.mapProcessOnlyAgent(p))];
54
+ return this.deduplicateSessionAgents([
55
+ ...cachedAgents,
56
+ ...remaining.map((p)=>this.mapProcessOnlyAgent(p))
57
+ ]);
59
58
  }
60
59
  const matches = matchProcessesToSessions(remaining, sessions);
61
- const matchedPids = new Set(matches.map((m) => m.process.pid));
60
+ const matchedPids = new Set(matches.map((m)=>m.process.pid));
62
61
  const agents = [];
63
- for (const match of matches) {
62
+ for (const match of matches){
64
63
  const cachedContent = contentCache.get(match.session.filePath);
65
64
  const sessionData = this.parseSession(cachedContent, match.session.filePath);
66
65
  if (sessionData) {
67
66
  agents.push(this.mapSessionToAgent(sessionData, match.process, match.session.filePath));
68
- }
69
- else {
67
+ } else {
70
68
  matchedPids.delete(match.process.pid);
71
69
  }
72
70
  }
73
- for (const proc of remaining) {
71
+ for (const proc of remaining){
74
72
  if (!matchedPids.has(proc.pid)) {
75
73
  agents.push(this.mapProcessOnlyAgent(proc));
76
74
  }
77
75
  }
78
- return [...cachedAgents, ...agents];
76
+ return this.deduplicateSessionAgents([
77
+ ...cachedAgents,
78
+ ...agents
79
+ ]);
80
+ }
81
+ deduplicateSessionAgents(agents) {
82
+ const bySession = new Map();
83
+ const result = [];
84
+ for (const agent of agents){
85
+ const sessionKey = this.sessionDeduplicationKey(agent);
86
+ if (!sessionKey) {
87
+ result.push(agent);
88
+ continue;
89
+ }
90
+ const existing = bySession.get(sessionKey);
91
+ if (!existing) {
92
+ bySession.set(sessionKey, agent);
93
+ result.push(agent);
94
+ continue;
95
+ }
96
+ if (agent.pid > existing.pid) {
97
+ bySession.set(sessionKey, agent);
98
+ const index = result.indexOf(existing);
99
+ if (index >= 0) result[index] = agent;
100
+ }
101
+ }
102
+ return result;
103
+ }
104
+ sessionDeduplicationKey(agent) {
105
+ if (agent.sessionFilePath) return `file:${agent.sessionFilePath}`;
106
+ if (agent.sessionId && !agent.sessionId.startsWith('pid-')) {
107
+ return `session:${agent.sessionId}`;
108
+ }
109
+ return null;
79
110
  }
80
111
  tryRegistryCache(processes) {
81
112
  const cachedAgents = [];
82
113
  const remaining = [];
83
- const byPid = new Map(this.registry.list().map((e) => [e.pid, e]));
84
- for (const proc of processes) {
114
+ const byPid = new Map(this.registry.list().map((e)=>[
115
+ e.pid,
116
+ e
117
+ ]));
118
+ for (const proc of processes){
85
119
  const entry = byPid.get(proc.pid);
86
- if (!entry ||
87
- entry.type !== this.type ||
88
- !entry.sessionFilePath ||
89
- !fs.existsSync(entry.sessionFilePath)) {
120
+ if (!entry || entry.type !== this.type || !entry.sessionFilePath || !fs.existsSync(entry.sessionFilePath)) {
90
121
  remaining.push(proc);
91
122
  continue;
92
123
  }
@@ -98,7 +129,10 @@ export class GeminiCliAdapter {
98
129
  }
99
130
  cachedAgents.push(this.mapSessionToAgent(sessionData, proc, entry.sessionFilePath));
100
131
  }
101
- return { cachedAgents, remaining };
132
+ return {
133
+ cachedAgents,
134
+ remaining
135
+ };
102
136
  }
103
137
  /**
104
138
  * Discover session files for the given processes.
@@ -107,40 +141,36 @@ export class GeminiCliAdapter {
107
141
  * where <shortId> is opaque (managed by a project registry). We scan every
108
142
  * shortId directory and filter by matching session.projectHash against
109
143
  * sha256(process.cwd) to bind each session to a candidate process CWD.
110
- */
111
- discoverSessions(processes) {
112
- const empty = { sessions: [], contentCache: new Map() };
113
- if (!fs.existsSync(this.geminiTmpDir))
114
- return empty;
144
+ */ discoverSessions(processes) {
145
+ const empty = {
146
+ sessions: [],
147
+ contentCache: new Map()
148
+ };
149
+ if (!fs.existsSync(this.geminiTmpDir)) return empty;
115
150
  const cwdHashMap = this.buildCwdHashMap(processes);
116
- if (cwdHashMap.size === 0)
117
- return empty;
151
+ if (cwdHashMap.size === 0) return empty;
118
152
  const contentCache = new Map();
119
153
  const sessions = [];
120
154
  let shortIdEntries;
121
155
  try {
122
156
  shortIdEntries = fs.readdirSync(this.geminiTmpDir);
123
- }
124
- catch {
157
+ } catch {
125
158
  return empty;
126
159
  }
127
- for (const shortId of shortIdEntries) {
160
+ for (const shortId of shortIdEntries){
128
161
  const chatsDir = path.join(this.geminiTmpDir, shortId, GeminiCliAdapter.CHATS_DIR_NAME);
129
162
  try {
130
- if (!fs.statSync(chatsDir).isDirectory())
131
- continue;
132
- }
133
- catch {
163
+ if (!fs.statSync(chatsDir).isDirectory()) continue;
164
+ } catch {
134
165
  continue;
135
166
  }
136
167
  let chatFiles;
137
168
  try {
138
169
  chatFiles = fs.readdirSync(chatsDir);
139
- }
140
- catch {
170
+ } catch {
141
171
  continue;
142
172
  }
143
- for (const fileName of chatFiles) {
173
+ for (const fileName of chatFiles){
144
174
  if (!fileName.startsWith(GeminiCliAdapter.SESSION_FILE_PREFIX) || !fileName.endsWith('.json')) {
145
175
  continue;
146
176
  }
@@ -148,27 +178,22 @@ export class GeminiCliAdapter {
148
178
  let content;
149
179
  try {
150
180
  content = fs.readFileSync(filePath, 'utf-8');
151
- }
152
- catch {
181
+ } catch {
153
182
  continue;
154
183
  }
155
184
  let parsed;
156
185
  try {
157
186
  parsed = JSON.parse(content);
158
- }
159
- catch {
187
+ } catch {
160
188
  continue;
161
189
  }
162
- if (!parsed.projectHash)
163
- continue;
190
+ if (!parsed.projectHash) continue;
164
191
  const resolvedCwd = cwdHashMap.get(parsed.projectHash);
165
- if (!resolvedCwd)
166
- continue;
192
+ if (!resolvedCwd) continue;
167
193
  let birthtimeMs = 0;
168
194
  try {
169
195
  birthtimeMs = fs.statSync(filePath).birthtimeMs;
170
- }
171
- catch {
196
+ } catch {
172
197
  continue;
173
198
  }
174
199
  const sessionId = parsed.sessionId || fileName.replace(/\.json$/, '');
@@ -178,24 +203,26 @@ export class GeminiCliAdapter {
178
203
  filePath,
179
204
  projectDir: chatsDir,
180
205
  birthtimeMs,
181
- resolvedCwd,
206
+ resolvedCwd
182
207
  });
183
208
  }
184
209
  }
185
- return { sessions, contentCache };
210
+ return {
211
+ sessions,
212
+ contentCache
213
+ };
186
214
  }
187
215
  buildCwdHashMap(processes) {
188
216
  const map = new Map();
189
- for (const proc of processes) {
190
- if (!proc.cwd)
191
- continue;
217
+ for (const proc of processes){
218
+ if (!proc.cwd) continue;
192
219
  // Gemini CLI resolves its project root by walking up from the
193
220
  // startup directory looking for a `.git` boundary marker. A
194
221
  // session's projectHash therefore tracks that ancestor rather
195
222
  // than the process' actual CWD. Enumerate every ancestor as a
196
223
  // candidate so subdirectory invocations still line up with the
197
224
  // session the Gemini process wrote.
198
- for (const candidate of this.candidateProjectRoots(proc.cwd)) {
225
+ for (const candidate of this.candidateProjectRoots(proc.cwd)){
199
226
  if (!map.has(this.hashProjectRoot(candidate))) {
200
227
  map.set(this.hashProjectRoot(candidate), proc.cwd);
201
228
  }
@@ -207,7 +234,7 @@ export class GeminiCliAdapter {
207
234
  const roots = [];
208
235
  let current = path.resolve(cwd);
209
236
  let parent = path.dirname(current);
210
- while (parent !== current) {
237
+ while(parent !== current){
211
238
  roots.push(current);
212
239
  current = parent;
213
240
  parent = path.dirname(current);
@@ -221,53 +248,42 @@ export class GeminiCliAdapter {
221
248
  /**
222
249
  * Parse session file content into GeminiSession.
223
250
  * Uses cached content if available, otherwise reads from disk.
224
- */
225
- parseSession(cachedContent, filePath) {
251
+ */ parseSession(cachedContent, filePath) {
226
252
  let content;
227
253
  if (cachedContent !== undefined) {
228
254
  content = cachedContent;
229
- }
230
- else {
255
+ } else {
231
256
  try {
232
257
  content = fs.readFileSync(filePath, 'utf-8');
233
- }
234
- catch {
258
+ } catch {
235
259
  return null;
236
260
  }
237
261
  }
238
262
  let parsed;
239
263
  try {
240
264
  parsed = JSON.parse(content);
241
- }
242
- catch {
265
+ } catch {
243
266
  return null;
244
267
  }
245
- if (!parsed.sessionId)
246
- return null;
268
+ if (!parsed.sessionId) return null;
247
269
  const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
248
270
  const lastEntry = messages.length > 0 ? messages[messages.length - 1] : undefined;
249
271
  let mtime = null;
250
272
  try {
251
273
  mtime = fs.statSync(filePath).mtime;
252
- }
253
- catch {
274
+ } catch {
254
275
  mtime = null;
255
276
  }
256
- const lastActive = this.parseTimestamp(parsed.lastUpdated) ||
257
- this.parseTimestamp(lastEntry?.timestamp) ||
258
- mtime ||
259
- new Date();
277
+ const lastActive = this.parseTimestamp(parsed.lastUpdated) || this.parseTimestamp(lastEntry?.timestamp) || mtime || new Date();
260
278
  const sessionStart = this.parseTimestamp(parsed.startTime) || lastActive;
261
- const projectPath = Array.isArray(parsed.directories) && parsed.directories.length > 0
262
- ? parsed.directories[0]
263
- : '';
279
+ const projectPath = Array.isArray(parsed.directories) && parsed.directories.length > 0 ? parsed.directories[0] : '';
264
280
  return {
265
281
  sessionId: parsed.sessionId,
266
282
  projectPath,
267
283
  summary: this.extractSummary(messages),
268
284
  sessionStart,
269
285
  lastActive,
270
- lastMessageType: lastEntry?.type,
286
+ lastMessageType: lastEntry?.type
271
287
  };
272
288
  }
273
289
  mapSessionToAgent(session, processInfo, filePath) {
@@ -281,7 +297,7 @@ export class GeminiCliAdapter {
281
297
  projectPath,
282
298
  sessionId: session.sessionId,
283
299
  lastActive: session.lastActive,
284
- sessionFilePath: filePath,
300
+ sessionFilePath: filePath
285
301
  };
286
302
  }
287
303
  mapProcessOnlyAgent(processInfo) {
@@ -293,12 +309,11 @@ export class GeminiCliAdapter {
293
309
  pid: processInfo.pid,
294
310
  projectPath: processInfo.cwd || '',
295
311
  sessionId: `pid-${processInfo.pid}`,
296
- lastActive: new Date(),
312
+ lastActive: new Date()
297
313
  };
298
314
  }
299
315
  parseTimestamp(value) {
300
- if (!value)
301
- return null;
316
+ if (!value) return null;
302
317
  const timestamp = new Date(value);
303
318
  return Number.isNaN(timestamp.getTime()) ? null : timestamp;
304
319
  }
@@ -314,13 +329,11 @@ export class GeminiCliAdapter {
314
329
  return AgentStatus.RUNNING;
315
330
  }
316
331
  extractSummary(messages) {
317
- for (let i = messages.length - 1; i >= 0; i--) {
332
+ for(let i = messages.length - 1; i >= 0; i--){
318
333
  const entry = messages[i];
319
- if (entry?.type !== 'user')
320
- continue;
334
+ if (entry?.type !== 'user') continue;
321
335
  const text = this.messageText(entry).trim();
322
- if (text)
323
- return this.truncate(text, 120);
336
+ if (text) return this.truncate(text, 120);
324
337
  }
325
338
  return 'Gemini CLI session active';
326
339
  }
@@ -328,11 +341,9 @@ export class GeminiCliAdapter {
328
341
  * Normalize an entry's content/displayContent into a plain string.
329
342
  * Prefers displayContent when both are present (matches Gemini CLI's
330
343
  * own rendering priority for the /chat UI).
331
- */
332
- messageText(entry) {
344
+ */ messageText(entry) {
333
345
  const displayText = this.resolveContent(entry.displayContent);
334
- if (displayText)
335
- return displayText;
346
+ if (displayText) return displayText;
336
347
  return this.resolveContent(entry.content);
337
348
  }
338
349
  /**
@@ -341,16 +352,12 @@ export class GeminiCliAdapter {
341
352
  * list (user turns carrying `[{text: "..."}]`). Non-text part
342
353
  * variants (data, file) are dropped since this helper is only used
343
354
  * for summary/conversation rendering.
344
- */
345
- resolveContent(content) {
346
- if (!content)
347
- return '';
348
- if (typeof content === 'string')
349
- return content;
350
- if (!Array.isArray(content))
351
- return '';
355
+ */ resolveContent(content) {
356
+ if (!content) return '';
357
+ if (typeof content === 'string') return content;
358
+ if (!Array.isArray(content)) return '';
352
359
  const parts = [];
353
- for (const part of content) {
360
+ for (const part of content){
354
361
  if (part && typeof part.text === 'string' && part.text) {
355
362
  parts.push(part.text);
356
363
  }
@@ -358,8 +365,7 @@ export class GeminiCliAdapter {
358
365
  return parts.join('');
359
366
  }
360
367
  truncate(value, maxLength) {
361
- if (value.length <= maxLength)
362
- return value;
368
+ if (value.length <= maxLength) return value;
363
369
  return `${value.slice(0, maxLength - 3)}...`;
364
370
  }
365
371
  isGeminiExecutable(command) {
@@ -367,7 +373,7 @@ export class GeminiCliAdapter {
367
373
  // known gemini entrypoint. This is intentionally broader than the
368
374
  // other adapters' argv[0]-only check because the Node-script
369
375
  // distribution puts the real gemini path in argv[1..], not argv[0].
370
- for (const token of command.trim().split(/\s+/)) {
376
+ for (const token of command.trim().split(/\s+/)){
371
377
  const base = path.basename(token).toLowerCase();
372
378
  if (base === 'gemini' || base === 'gemini.exe' || base === 'gemini.js') {
373
379
  return true;
@@ -380,69 +386,55 @@ export class GeminiCliAdapter {
380
386
  *
381
387
  * Gemini sessions store messages in an array with `type` field — typically
382
388
  * 'user' or 'gemini' for visible turns, with tool and system entries mixed in.
383
- */
384
- getConversation(sessionFilePath, options) {
389
+ */ getConversation(sessionFilePath, options) {
385
390
  const verbose = options?.verbose ?? false;
386
391
  const content = safeReadFile(sessionFilePath);
387
- if (content === undefined)
388
- return [];
392
+ if (content === undefined) return [];
389
393
  let parsed;
390
394
  try {
391
395
  parsed = JSON.parse(content);
392
- }
393
- catch {
396
+ } catch {
394
397
  return [];
395
398
  }
396
399
  const messages = [];
397
- if (!Array.isArray(parsed.messages))
398
- return messages;
399
- for (const entry of parsed.messages) {
400
+ if (!Array.isArray(parsed.messages)) return messages;
401
+ for (const entry of parsed.messages){
400
402
  const entryType = entry?.type;
401
- if (!entryType)
402
- continue;
403
+ if (!entryType) continue;
403
404
  let role;
404
405
  if (entryType === 'user') {
405
406
  role = 'user';
406
- }
407
- else if (entryType === 'gemini' || entryType === 'assistant') {
407
+ } else if (entryType === 'gemini' || entryType === 'assistant') {
408
408
  role = 'assistant';
409
- }
410
- else if (verbose) {
409
+ } else if (verbose) {
411
410
  role = 'system';
412
- }
413
- else {
411
+ } else {
414
412
  continue;
415
413
  }
416
414
  const text = this.messageText(entry).trim();
417
- if (!text)
418
- continue;
415
+ if (!text) continue;
419
416
  messages.push({
420
417
  role,
421
418
  content: text,
422
- timestamp: entry.timestamp,
419
+ timestamp: entry.timestamp
423
420
  });
424
421
  }
425
422
  return messages;
426
423
  }
427
424
  async listSessions(opts) {
428
- if (!isDirectory(this.geminiTmpDir))
429
- return [];
425
+ if (!isDirectory(this.geminiTmpDir)) return [];
430
426
  const summaries = [];
431
- for (const shortId of safeReaddir(this.geminiTmpDir)) {
427
+ for (const shortId of safeReaddir(this.geminiTmpDir)){
432
428
  const chatsDir = path.join(this.geminiTmpDir, shortId, GeminiCliAdapter.CHATS_DIR_NAME);
433
- if (!isDirectory(chatsDir))
434
- continue;
435
- for (const fileName of safeReaddir(chatsDir)) {
436
- if (!fileName.startsWith(GeminiCliAdapter.SESSION_FILE_PREFIX) ||
437
- !fileName.endsWith('.json')) {
429
+ if (!isDirectory(chatsDir)) continue;
430
+ for (const fileName of safeReaddir(chatsDir)){
431
+ if (!fileName.startsWith(GeminiCliAdapter.SESSION_FILE_PREFIX) || !fileName.endsWith('.json')) {
438
432
  continue;
439
433
  }
440
434
  const filePath = path.join(chatsDir, fileName);
441
435
  const summary = this.fileToSessionSummary(filePath);
442
- if (!summary)
443
- continue;
444
- if (opts?.cwd !== undefined && summary.cwd !== opts.cwd)
445
- continue;
436
+ if (!summary) continue;
437
+ if (opts?.cwd !== undefined && summary.cwd !== opts.cwd) continue;
446
438
  summaries.push(summary);
447
439
  }
448
440
  }
@@ -452,31 +444,22 @@ export class GeminiCliAdapter {
452
444
  * Read a Gemini session JSON file and produce a {@link SessionSummary}.
453
445
  * Returns null when the file is unreadable, the JSON doesn't parse,
454
446
  * or the body lacks a sessionId.
455
- */
456
- fileToSessionSummary(filePath) {
447
+ */ fileToSessionSummary(filePath) {
457
448
  const content = safeReadFile(filePath);
458
- if (content === undefined)
459
- return null;
449
+ if (content === undefined) return null;
460
450
  let parsed;
461
451
  try {
462
452
  parsed = JSON.parse(content);
463
- }
464
- catch {
453
+ } catch {
465
454
  return null;
466
455
  }
467
- if (!parsed.sessionId)
468
- return null;
456
+ if (!parsed.sessionId) return null;
469
457
  const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
470
458
  const firstUserMessage = this.extractFirstUserMessage(messages);
471
- const cwd = Array.isArray(parsed.directories) && parsed.directories.length > 0
472
- ? parsed.directories[0]
473
- : '';
459
+ const cwd = Array.isArray(parsed.directories) && parsed.directories.length > 0 ? parsed.directories[0] : '';
474
460
  const stat = safeStat(filePath);
475
461
  const lastEntryTimestamp = this.parseTimestamp(messages.length > 0 ? messages[messages.length - 1]?.timestamp : undefined);
476
- const lastActive = this.parseTimestamp(parsed.lastUpdated) ||
477
- lastEntryTimestamp ||
478
- stat?.mtime ||
479
- new Date();
462
+ const lastActive = this.parseTimestamp(parsed.lastUpdated) || lastEntryTimestamp || stat?.mtime || new Date();
480
463
  const startedAt = this.parseTimestamp(parsed.startTime) || stat?.birthtime || stat?.mtime || lastActive;
481
464
  return {
482
465
  type: 'gemini_cli',
@@ -485,18 +468,17 @@ export class GeminiCliAdapter {
485
468
  firstUserMessage,
486
469
  lastActive,
487
470
  startedAt,
488
- sessionFilePath: filePath,
471
+ sessionFilePath: filePath
489
472
  };
490
473
  }
491
474
  extractFirstUserMessage(messages) {
492
- for (const entry of messages) {
493
- if (entry?.type !== 'user')
494
- continue;
475
+ for (const entry of messages){
476
+ if (entry?.type !== 'user') continue;
495
477
  const text = this.messageText(entry).trim();
496
- if (text)
497
- return text;
478
+ if (text) return text;
498
479
  }
499
480
  return '';
500
481
  }
501
482
  }
483
+
502
484
  //# sourceMappingURL=GeminiCliAdapter.js.map