@ai-devkit/agent-manager 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.swcrc +27 -0
- package/dist/AgentManager.js +72 -74
- package/dist/AgentManager.js.map +1 -1
- package/dist/__tests__/AgentManager.test.js +564 -0
- package/dist/__tests__/AgentManager.test.js.map +1 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js +2157 -0
- package/dist/__tests__/adapters/ClaudeCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js +1419 -0
- package/dist/__tests__/adapters/CodexAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js +500 -0
- package/dist/__tests__/adapters/CopilotAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js +1229 -0
- package/dist/__tests__/adapters/GeminiCliAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js +626 -0
- package/dist/__tests__/adapters/OpenCodeAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/PiAdapter.test.js +590 -0
- package/dist/__tests__/adapters/PiAdapter.test.js.map +1 -0
- package/dist/__tests__/terminal/TmuxManager.test.js +159 -0
- package/dist/__tests__/terminal/TmuxManager.test.js.map +1 -0
- package/dist/__tests__/terminal/TtyWriter.test.js +162 -0
- package/dist/__tests__/terminal/TtyWriter.test.js.map +1 -0
- package/dist/__tests__/utils/AgentRegistry.test.js +282 -0
- package/dist/__tests__/utils/AgentRegistry.test.js.map +1 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js +312 -0
- package/dist/__tests__/utils/ClaudeSessionParser.test.js.map +1 -0
- package/dist/__tests__/utils/matching.test.js +324 -0
- package/dist/__tests__/utils/matching.test.js.map +1 -0
- package/dist/__tests__/utils/process.test.js +177 -0
- package/dist/__tests__/utils/process.test.js.map +1 -0
- package/dist/__tests__/utils/session.test.js +158 -0
- package/dist/__tests__/utils/session.test.js.map +1 -0
- package/dist/adapters/AgentAdapter.d.ts +1 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js +8 -7
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +66 -77
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +13 -0
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +225 -141
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/adapters/CopilotAdapter.d.ts +44 -0
- package/dist/adapters/CopilotAdapter.d.ts.map +1 -0
- package/dist/adapters/CopilotAdapter.js +314 -0
- package/dist/adapters/CopilotAdapter.js.map +1 -0
- package/dist/adapters/GeminiCliAdapter.d.ts +2 -0
- package/dist/adapters/GeminiCliAdapter.d.ts.map +1 -1
- package/dist/adapters/GeminiCliAdapter.js +138 -156
- package/dist/adapters/GeminiCliAdapter.js.map +1 -1
- package/dist/adapters/OpenCodeAdapter.js +60 -69
- package/dist/adapters/OpenCodeAdapter.js.map +1 -1
- package/dist/adapters/PiAdapter.d.ts +62 -0
- package/dist/adapters/PiAdapter.d.ts.map +1 -0
- package/dist/adapters/PiAdapter.js +450 -0
- package/dist/adapters/PiAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/terminal/TerminalFocusManager.js +62 -49
- package/dist/terminal/TerminalFocusManager.js.map +1 -1
- package/dist/terminal/TmuxManager.js +59 -34
- package/dist/terminal/TmuxManager.js.map +1 -1
- package/dist/terminal/TtyWriter.js +37 -16
- package/dist/terminal/TtyWriter.js.map +1 -1
- package/dist/terminal/index.js +1 -0
- package/dist/terminal/index.js.map +1 -1
- package/dist/utils/AgentRegistry.js +41 -32
- package/dist/utils/AgentRegistry.js.map +1 -1
- package/dist/utils/ClaudeSessionParser.js +46 -87
- package/dist/utils/ClaudeSessionParser.js.map +1 -1
- package/dist/utils/agents.d.ts +1 -1
- package/dist/utils/agents.d.ts.map +1 -1
- package/dist/utils/agents.js +22 -11
- package/dist/utils/agents.js.map +1 -1
- package/dist/utils/applescript.js +3 -6
- package/dist/utils/applescript.js.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/matching.js +21 -29
- package/dist/utils/matching.js.map +1 -1
- package/dist/utils/process.js +77 -56
- package/dist/utils/process.js.map +1 -1
- package/dist/utils/session.js +20 -34
- package/dist/utils/session.js.map +1 -1
- package/dist/utils/sortAgents.js +6 -8
- package/dist/utils/sortAgents.js.map +1 -1
- package/package.json +11 -7
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +2 -0
- package/src/__tests__/adapters/CodexAdapter.test.ts +145 -0
- package/src/__tests__/adapters/CopilotAdapter.test.ts +411 -0
- package/src/__tests__/adapters/GeminiCliAdapter.test.ts +74 -1
- package/src/__tests__/adapters/PiAdapter.test.ts +435 -0
- package/src/adapters/AgentAdapter.ts +1 -1
- package/src/adapters/CodexAdapter.ts +164 -6
- package/src/adapters/CopilotAdapter.ts +453 -0
- package/src/adapters/GeminiCliAdapter.ts +42 -3
- package/src/adapters/PiAdapter.ts +597 -0
- package/src/adapters/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/utils/agents.ts +1 -1
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
* 4. 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)
|
|
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
|
|
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)
|
|
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 [
|
|
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)
|
|
84
|
-
|
|
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 {
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
346
|
-
if (
|
|
347
|
-
|
|
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
|
-
|
|
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
|
-
|
|
435
|
-
|
|
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
|
-
|
|
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
|