@ai-devkit/agent-manager 0.35.0 → 0.36.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/__tests__/adapters/AntigravityCliAdapter.test.js +381 -0
- package/dist/__tests__/adapters/AntigravityCliAdapter.test.js.map +1 -0
- package/dist/__tests__/adapters/KiroAdapter.test.js +607 -0
- package/dist/__tests__/adapters/KiroAdapter.test.js.map +1 -0
- package/dist/__tests__/capacity/claude.test.js +485 -0
- package/dist/__tests__/capacity/claude.test.js.map +1 -0
- package/dist/__tests__/capacity/openai.test.js +672 -0
- package/dist/__tests__/capacity/openai.test.js.map +1 -0
- package/dist/__tests__/capacity/wham.test.js +144 -0
- package/dist/__tests__/capacity/wham.test.js.map +1 -0
- package/dist/__tests__/utils/agents.test.js +6 -0
- package/dist/__tests__/utils/agents.test.js.map +1 -1
- package/dist/adapters/AgentAdapter.d.ts +1 -1
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/AgentAdapter.js.map +1 -1
- package/dist/adapters/AntigravityCliAdapter.d.ts +57 -0
- package/dist/adapters/AntigravityCliAdapter.d.ts.map +1 -0
- package/dist/adapters/AntigravityCliAdapter.js +273 -0
- package/dist/adapters/AntigravityCliAdapter.js.map +1 -0
- package/dist/adapters/KiroAdapter.d.ts +46 -0
- package/dist/adapters/KiroAdapter.d.ts.map +1 -0
- package/dist/adapters/KiroAdapter.js +344 -0
- package/dist/adapters/KiroAdapter.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 +2 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/capacity/claude.d.ts +17 -0
- package/dist/capacity/claude.d.ts.map +1 -0
- package/dist/capacity/claude.js +271 -0
- package/dist/capacity/claude.js.map +1 -0
- package/dist/capacity/codex.d.ts +1 -2
- package/dist/capacity/codex.d.ts.map +1 -1
- package/dist/capacity/codex.js +5 -47
- package/dist/capacity/codex.js.map +1 -1
- package/dist/capacity/index.d.ts +11 -0
- package/dist/capacity/index.d.ts.map +1 -1
- package/dist/capacity/index.js +17 -0
- package/dist/capacity/index.js.map +1 -1
- package/dist/capacity/openai.d.ts +48 -0
- package/dist/capacity/openai.d.ts.map +1 -0
- package/dist/capacity/openai.js +312 -0
- package/dist/capacity/openai.js.map +1 -0
- package/dist/capacity/wham.d.ts +16 -0
- package/dist/capacity/wham.d.ts.map +1 -0
- package/dist/capacity/wham.js +66 -0
- package/dist/capacity/wham.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/readiness/AgentReadiness.d.ts +1 -1
- package/dist/readiness/AgentReadiness.d.ts.map +1 -1
- package/dist/readiness/AgentReadiness.js.map +1 -1
- package/dist/runtime/ManagedAgentRuntime.js +3 -1
- package/dist/runtime/ManagedAgentRuntime.js.map +1 -1
- package/dist/runtime/herdr/HerdrAgentDiscovery.js +10 -0
- package/dist/runtime/herdr/HerdrAgentDiscovery.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 +11 -0
- package/dist/utils/agents.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/adapters/AntigravityCliAdapter.test.ts +324 -0
- package/src/__tests__/adapters/KiroAdapter.test.ts +534 -0
- package/src/__tests__/capacity/claude.test.ts +467 -0
- package/src/__tests__/capacity/fixtures/claude-credentials.json +7 -0
- package/src/__tests__/capacity/fixtures/claude-usage-malformed.json +1 -0
- package/src/__tests__/capacity/fixtures/claude-usage-partial.json +15 -0
- package/src/__tests__/capacity/fixtures/claude-usage.json +63 -0
- package/src/__tests__/capacity/fixtures/openai-auth.json +6 -0
- package/src/__tests__/capacity/fixtures/openai-codex-auth.json +9 -0
- package/src/__tests__/capacity/fixtures/openai-usage-completions.json +67 -0
- package/src/__tests__/capacity/fixtures/openai-usage-responses.json +20 -0
- package/src/__tests__/capacity/fixtures/wham-usage.json +32 -0
- package/src/__tests__/capacity/openai.test.ts +570 -0
- package/src/__tests__/capacity/wham.test.ts +104 -0
- package/src/__tests__/utils/agents.test.ts +7 -0
- package/src/adapters/AgentAdapter.ts +2 -0
- package/src/adapters/AntigravityCliAdapter.ts +356 -0
- package/src/adapters/KiroAdapter.ts +456 -0
- package/src/adapters/index.ts +2 -0
- package/src/capacity/claude.ts +313 -0
- package/src/capacity/codex.ts +25 -134
- package/src/capacity/index.ts +33 -4
- package/src/capacity/openai.ts +361 -0
- package/src/capacity/wham.ts +84 -0
- package/src/index.ts +10 -1
- package/src/readiness/AgentReadiness.ts +4 -1
- package/src/runtime/ManagedAgentRuntime.ts +2 -0
- package/src/runtime/herdr/HerdrAgentDiscovery.ts +5 -1
- package/src/utils/agents.ts +11 -1
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kiro Adapter
|
|
3
|
+
*
|
|
4
|
+
* Detects running Kiro agents by matching process IDs from
|
|
5
|
+
* ~/.kiro/sessions/cli/<session-id>.lock to the sibling metadata and transcript files.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import type {
|
|
10
|
+
AgentAdapter,
|
|
11
|
+
AgentInfo,
|
|
12
|
+
ProcessInfo,
|
|
13
|
+
ConversationMessage,
|
|
14
|
+
SessionSummary,
|
|
15
|
+
ListSessionsOptions,
|
|
16
|
+
AgentDetectionContext,
|
|
17
|
+
} from "./AgentAdapter.js";
|
|
18
|
+
import { AgentStatus } from "./AgentAdapter.js";
|
|
19
|
+
import {
|
|
20
|
+
captureProcessSnapshot,
|
|
21
|
+
executableBasename,
|
|
22
|
+
filterByProcessNames,
|
|
23
|
+
} from "../utils/process.js";
|
|
24
|
+
import { isDirectory, safeReadFile, safeReaddir, safeStat } from "../utils/session.js";
|
|
25
|
+
import { generateAgentName } from "../utils/matching.js";
|
|
26
|
+
|
|
27
|
+
type KiroRecord = Record<string, unknown>;
|
|
28
|
+
|
|
29
|
+
interface KiroMetadata {
|
|
30
|
+
sessionId: string;
|
|
31
|
+
cwd: string;
|
|
32
|
+
title: string;
|
|
33
|
+
createdAt: Date | null;
|
|
34
|
+
updatedAt: Date | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface KiroLine {
|
|
38
|
+
kind?: string;
|
|
39
|
+
timestamp?: string;
|
|
40
|
+
data?: KiroRecord;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface KiroSession {
|
|
44
|
+
sessionId: string;
|
|
45
|
+
projectPath: string;
|
|
46
|
+
summary: string;
|
|
47
|
+
firstUserMessage: string;
|
|
48
|
+
sessionStart: Date;
|
|
49
|
+
lastActive: Date;
|
|
50
|
+
lastEventKind?: string;
|
|
51
|
+
lastAssistantHasToolUse: boolean;
|
|
52
|
+
filePath: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface KiroLock {
|
|
56
|
+
sessionId: string;
|
|
57
|
+
pid: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const IDLE_THRESHOLD_MINUTES = 5;
|
|
61
|
+
const KIRO_BASENAMES = new Set(["kiro-cli", "kiro"]);
|
|
62
|
+
const SCRIPT_RUNTIMES = new Set(["node", "bun"]);
|
|
63
|
+
|
|
64
|
+
export class KiroAdapter implements AgentAdapter {
|
|
65
|
+
readonly type = "kiro" as const;
|
|
66
|
+
readonly processNames = ["kiro-cli", "kiro", "kiro-cli-chat", "node"] as const;
|
|
67
|
+
|
|
68
|
+
private kiroSessionsDir: string;
|
|
69
|
+
|
|
70
|
+
constructor() {
|
|
71
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
72
|
+
this.kiroSessionsDir = path.join(homeDir, ".kiro", "sessions", "cli");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
canHandle(processInfo: ProcessInfo): boolean {
|
|
76
|
+
return this.isKiroExecutable(processInfo.command);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async detectAgents(context?: AgentDetectionContext): Promise<AgentInfo[]> {
|
|
80
|
+
const snapshot = context?.processes ?? (await captureProcessSnapshot(this.processNames));
|
|
81
|
+
const relevant = filterByProcessNames(snapshot, this.processNames);
|
|
82
|
+
const processes = relevant.filter((process) => this.canHandle(process));
|
|
83
|
+
if (processes.length === 0) return [];
|
|
84
|
+
|
|
85
|
+
const collectedByPid = new Map(relevant.map((proc) => [proc.pid, proc]));
|
|
86
|
+
const matchedPids = new Set<number>();
|
|
87
|
+
const agents: AgentInfo[] = [];
|
|
88
|
+
|
|
89
|
+
for (const lock of this.discoverActiveLocks()) {
|
|
90
|
+
const proc = this.resolveLockProcess(lock.pid, collectedByPid, processes);
|
|
91
|
+
if (!proc) continue;
|
|
92
|
+
|
|
93
|
+
const session = this.readSession(lock.sessionId, proc.cwd);
|
|
94
|
+
if (!session) continue;
|
|
95
|
+
|
|
96
|
+
agents.push(this.mapSessionToAgent(session, proc));
|
|
97
|
+
matchedPids.add(proc.pid);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const proc of processes) {
|
|
101
|
+
if (!matchedPids.has(proc.pid)) {
|
|
102
|
+
agents.push(this.mapProcessOnlyAgent(proc));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return agents;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {
|
|
110
|
+
return this.entriesToMessages(this.readJsonl(sessionFilePath), options?.verbose ?? false);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {
|
|
114
|
+
if (!isDirectory(this.kiroSessionsDir)) return [];
|
|
115
|
+
|
|
116
|
+
const summaries: SessionSummary[] = [];
|
|
117
|
+
for (const entry of safeReaddir(this.kiroSessionsDir)) {
|
|
118
|
+
if (!entry.endsWith(".jsonl")) continue;
|
|
119
|
+
|
|
120
|
+
const sessionId = entry.slice(0, -".jsonl".length);
|
|
121
|
+
const session = this.readSession(sessionId);
|
|
122
|
+
if (!session) continue;
|
|
123
|
+
if (opts?.cwd !== undefined && session.projectPath !== opts.cwd) continue;
|
|
124
|
+
|
|
125
|
+
summaries.push({
|
|
126
|
+
type: this.type,
|
|
127
|
+
sessionId: session.sessionId,
|
|
128
|
+
cwd: session.projectPath,
|
|
129
|
+
firstUserMessage: session.firstUserMessage,
|
|
130
|
+
lastActive: session.lastActive,
|
|
131
|
+
startedAt: session.sessionStart,
|
|
132
|
+
sessionFilePath: session.filePath,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return summaries;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private discoverActiveLocks(): KiroLock[] {
|
|
139
|
+
if (!isDirectory(this.kiroSessionsDir)) return [];
|
|
140
|
+
|
|
141
|
+
const locks: KiroLock[] = [];
|
|
142
|
+
for (const entry of safeReaddir(this.kiroSessionsDir)) {
|
|
143
|
+
if (!entry.endsWith(".lock")) continue;
|
|
144
|
+
|
|
145
|
+
const content = safeReadFile(path.join(this.kiroSessionsDir, entry));
|
|
146
|
+
if (content === undefined) continue;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(content) as unknown;
|
|
150
|
+
const record = this.asRecord(parsed);
|
|
151
|
+
const pid = this.toPid(record?.pid);
|
|
152
|
+
if (pid === null) continue;
|
|
153
|
+
|
|
154
|
+
locks.push({
|
|
155
|
+
sessionId: entry.slice(0, -".lock".length),
|
|
156
|
+
pid,
|
|
157
|
+
});
|
|
158
|
+
} catch {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return locks;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private readSession(sessionId: string, fallbackCwd = ""): KiroSession | null {
|
|
166
|
+
const filePath = path.join(this.kiroSessionsDir, `${sessionId}.jsonl`);
|
|
167
|
+
const stat = safeStat(filePath);
|
|
168
|
+
if (!stat?.isFile()) return null;
|
|
169
|
+
|
|
170
|
+
const entries = this.readJsonl(filePath);
|
|
171
|
+
const metadata = this.readMetadata(sessionId);
|
|
172
|
+
const messages = this.entriesToMessages(entries, false);
|
|
173
|
+
const userMessages = messages.filter((message) => message.role === "user");
|
|
174
|
+
const timestamps = entries
|
|
175
|
+
.map((entry) => this.entryDate(entry))
|
|
176
|
+
.filter((value): value is Date => value !== null);
|
|
177
|
+
const sessionStart = metadata.createdAt ?? timestamps[0] ?? stat.birthtime ?? stat.mtime;
|
|
178
|
+
const lastActive = metadata.updatedAt ?? timestamps[timestamps.length - 1] ?? stat.mtime;
|
|
179
|
+
const lastEntry = entries[entries.length - 1];
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
sessionId: metadata.sessionId || sessionId,
|
|
183
|
+
projectPath: metadata.cwd || fallbackCwd,
|
|
184
|
+
summary: this.truncate(
|
|
185
|
+
userMessages.at(-1)?.content || metadata.title || "Kiro session active",
|
|
186
|
+
120,
|
|
187
|
+
),
|
|
188
|
+
firstUserMessage: userMessages[0]?.content ?? "",
|
|
189
|
+
sessionStart,
|
|
190
|
+
lastActive,
|
|
191
|
+
lastEventKind: lastEntry?.kind,
|
|
192
|
+
lastAssistantHasToolUse:
|
|
193
|
+
lastEntry?.kind === "AssistantMessage" && this.hasContentKind(lastEntry, "toolUse"),
|
|
194
|
+
filePath,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private readMetadata(sessionId: string): KiroMetadata {
|
|
199
|
+
const empty: KiroMetadata = {
|
|
200
|
+
sessionId,
|
|
201
|
+
cwd: "",
|
|
202
|
+
title: "",
|
|
203
|
+
createdAt: null,
|
|
204
|
+
updatedAt: null,
|
|
205
|
+
};
|
|
206
|
+
const content = safeReadFile(path.join(this.kiroSessionsDir, `${sessionId}.json`));
|
|
207
|
+
if (content === undefined) return empty;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const parsed = this.asRecord(JSON.parse(content));
|
|
211
|
+
if (!parsed) return empty;
|
|
212
|
+
return {
|
|
213
|
+
sessionId: this.firstString(parsed.session_id, parsed.sessionId) ?? sessionId,
|
|
214
|
+
cwd: this.firstString(parsed.cwd) ?? "",
|
|
215
|
+
title: this.firstString(parsed.title) ?? "",
|
|
216
|
+
createdAt: this.parseDate(parsed.created_at ?? parsed.createdAt),
|
|
217
|
+
updatedAt: this.parseDate(parsed.updated_at ?? parsed.updatedAt),
|
|
218
|
+
};
|
|
219
|
+
} catch {
|
|
220
|
+
return empty;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private readJsonl(filePath: string): KiroLine[] {
|
|
225
|
+
const content = safeReadFile(filePath);
|
|
226
|
+
if (content === undefined) return [];
|
|
227
|
+
|
|
228
|
+
const entries: KiroLine[] = [];
|
|
229
|
+
for (const line of content.split(/\r?\n/)) {
|
|
230
|
+
const trimmed = line.trim();
|
|
231
|
+
if (!trimmed) continue;
|
|
232
|
+
try {
|
|
233
|
+
const parsed = this.asRecord(JSON.parse(trimmed));
|
|
234
|
+
if (parsed) entries.push(parsed as KiroLine);
|
|
235
|
+
} catch {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return entries;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private entriesToMessages(entries: KiroLine[], verbose: boolean): ConversationMessage[] {
|
|
243
|
+
const messages: ConversationMessage[] = [];
|
|
244
|
+
for (const entry of entries) {
|
|
245
|
+
const message = this.entryToMessage(entry, verbose);
|
|
246
|
+
if (message) messages.push(message);
|
|
247
|
+
}
|
|
248
|
+
return messages;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private entryToMessage(entry: KiroLine, verbose: boolean): ConversationMessage | null {
|
|
252
|
+
let role: ConversationMessage["role"];
|
|
253
|
+
let content: string;
|
|
254
|
+
|
|
255
|
+
if (entry.kind === "Prompt") {
|
|
256
|
+
role = "user";
|
|
257
|
+
content = this.textContent(entry);
|
|
258
|
+
} else if (entry.kind === "AssistantMessage") {
|
|
259
|
+
role = "assistant";
|
|
260
|
+
const parts = [this.textContent(entry)];
|
|
261
|
+
if (verbose) parts.push(...this.toolUseContent(entry));
|
|
262
|
+
content = parts.filter(Boolean).join("\n");
|
|
263
|
+
} else if (entry.kind === "ToolResults" && verbose) {
|
|
264
|
+
role = "system";
|
|
265
|
+
content = this.toolResultContent(entry).join("\n");
|
|
266
|
+
} else {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!content) return null;
|
|
271
|
+
return {
|
|
272
|
+
role,
|
|
273
|
+
content,
|
|
274
|
+
timestamp: this.entryTimestamp(entry),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private textContent(entry: KiroLine): string {
|
|
279
|
+
return this.contentBlocks(entry)
|
|
280
|
+
.filter((block) => block.kind === "text")
|
|
281
|
+
.map((block) => (typeof block.data === "string" ? block.data : ""))
|
|
282
|
+
.filter(Boolean)
|
|
283
|
+
.join("");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private toolUseContent(entry: KiroLine): string[] {
|
|
287
|
+
return this.contentBlocks(entry)
|
|
288
|
+
.filter((block) => block.kind === "toolUse")
|
|
289
|
+
.map((block) => {
|
|
290
|
+
const data = this.asRecord(block.data);
|
|
291
|
+
const name = this.firstString(data?.name) ?? "unknown";
|
|
292
|
+
const input = this.formatValue(data?.input);
|
|
293
|
+
return `[Tool: ${name}]${input ? ` ${input}` : ""}`;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private toolResultContent(entry: KiroLine): string[] {
|
|
298
|
+
return this.contentBlocks(entry)
|
|
299
|
+
.filter((block) => block.kind === "toolResult")
|
|
300
|
+
.map((block) => {
|
|
301
|
+
const data = this.asRecord(block.data);
|
|
302
|
+
const prefix = data?.status === "error" ? "[Tool Error]" : "[Tool Result]";
|
|
303
|
+
const result = this.formatValue(data?.result ?? data?.results ?? data?.content);
|
|
304
|
+
return `${prefix}${result ? ` ${result}` : ""}`;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private contentBlocks(entry: KiroLine): Array<{ kind?: string; data?: unknown }> {
|
|
309
|
+
const content = entry.data?.content;
|
|
310
|
+
if (!Array.isArray(content)) return [];
|
|
311
|
+
return content
|
|
312
|
+
.map((block) => this.asRecord(block))
|
|
313
|
+
.filter((block): block is KiroRecord => block !== null);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private hasContentKind(entry: KiroLine, kind: string): boolean {
|
|
317
|
+
return this.contentBlocks(entry).some((block) => block.kind === kind);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private entryTimestamp(entry: KiroLine): string | undefined {
|
|
321
|
+
const direct = this.firstString(entry.timestamp);
|
|
322
|
+
if (direct) return direct;
|
|
323
|
+
|
|
324
|
+
const meta = this.asRecord(entry.data?.meta);
|
|
325
|
+
const parsed = this.parseDate(meta?.timestamp);
|
|
326
|
+
return parsed?.toISOString();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private entryDate(entry: KiroLine): Date | null {
|
|
330
|
+
return this.parseDate(this.entryTimestamp(entry));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private mapSessionToAgent(session: KiroSession, processInfo: ProcessInfo): AgentInfo {
|
|
334
|
+
const projectPath = session.projectPath || processInfo.cwd || "";
|
|
335
|
+
return {
|
|
336
|
+
name: generateAgentName(projectPath, processInfo.pid),
|
|
337
|
+
type: this.type,
|
|
338
|
+
status: this.determineStatus(session),
|
|
339
|
+
summary: session.summary,
|
|
340
|
+
pid: processInfo.pid,
|
|
341
|
+
projectPath,
|
|
342
|
+
sessionId: session.sessionId,
|
|
343
|
+
lastActive: session.lastActive,
|
|
344
|
+
sessionFilePath: session.filePath,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private mapProcessOnlyAgent(processInfo: ProcessInfo): AgentInfo {
|
|
349
|
+
return {
|
|
350
|
+
name: generateAgentName(processInfo.cwd || "", processInfo.pid),
|
|
351
|
+
type: this.type,
|
|
352
|
+
status: AgentStatus.RUNNING,
|
|
353
|
+
summary: "Kiro process running",
|
|
354
|
+
pid: processInfo.pid,
|
|
355
|
+
projectPath: processInfo.cwd || "",
|
|
356
|
+
sessionId: `pid-${processInfo.pid}`,
|
|
357
|
+
lastActive: new Date(),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private determineStatus(session: KiroSession): AgentStatus {
|
|
362
|
+
const diffMinutes = (Date.now() - session.lastActive.getTime()) / 60000;
|
|
363
|
+
if (diffMinutes > IDLE_THRESHOLD_MINUTES) return AgentStatus.IDLE;
|
|
364
|
+
if (session.lastEventKind === "AssistantMessage" && !session.lastAssistantHasToolUse) {
|
|
365
|
+
return AgentStatus.WAITING;
|
|
366
|
+
}
|
|
367
|
+
return AgentStatus.RUNNING;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private resolveLockProcess(
|
|
371
|
+
lockPid: number,
|
|
372
|
+
collectedByPid: Map<number, ProcessInfo>,
|
|
373
|
+
topLevel: ProcessInfo[],
|
|
374
|
+
): ProcessInfo | null {
|
|
375
|
+
let current = collectedByPid.get(lockPid);
|
|
376
|
+
const seen = new Set<number>();
|
|
377
|
+
let resolved: ProcessInfo | null = null;
|
|
378
|
+
|
|
379
|
+
while (current && !seen.has(current.pid)) {
|
|
380
|
+
seen.add(current.pid);
|
|
381
|
+
if (this.canHandle(current)) resolved = current;
|
|
382
|
+
|
|
383
|
+
const parentPid = current.ppid;
|
|
384
|
+
current = parentPid === undefined ? undefined : collectedByPid.get(parentPid);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (resolved) return resolved;
|
|
388
|
+
return this.matchSoleTopLevelOnTty(collectedByPid.get(lockPid)?.tty, topLevel);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private matchSoleTopLevelOnTty(
|
|
392
|
+
tty: string | undefined,
|
|
393
|
+
topLevel: readonly ProcessInfo[],
|
|
394
|
+
): ProcessInfo | null {
|
|
395
|
+
if (!tty || tty === "??" || tty === "?") return null;
|
|
396
|
+
const matches = topLevel.filter((proc) => proc.tty === tty);
|
|
397
|
+
return matches.length === 1 ? matches[0] : null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private isKiroExecutable(command: string): boolean {
|
|
401
|
+
const tokens = command.trim().split(/\s+/).filter(Boolean);
|
|
402
|
+
if (tokens.length === 0) return false;
|
|
403
|
+
|
|
404
|
+
const executable = this.kiroBasename(tokens[0]);
|
|
405
|
+
if (KIRO_BASENAMES.has(executable)) return true;
|
|
406
|
+
if (!SCRIPT_RUNTIMES.has(executable)) return false;
|
|
407
|
+
|
|
408
|
+
const script = tokens.slice(1).find((token) => !token.startsWith("-"));
|
|
409
|
+
return script !== undefined && KIRO_BASENAMES.has(this.kiroBasename(script));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private kiroBasename(token: string): string {
|
|
413
|
+
return executableBasename(token).replace(/\.(exe|js)$/, "");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private toPid(value: unknown): number | null {
|
|
417
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) return value;
|
|
418
|
+
if (typeof value !== "string" || !/^\d+$/.test(value)) return null;
|
|
419
|
+
const parsed = Number(value);
|
|
420
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private parseDate(value: unknown): Date | null {
|
|
424
|
+
if (typeof value === "number") {
|
|
425
|
+
const date = new Date(value < 1_000_000_000_000 ? value * 1000 : value);
|
|
426
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
427
|
+
}
|
|
428
|
+
if (typeof value !== "string" || !value) return null;
|
|
429
|
+
const date = new Date(value);
|
|
430
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
private firstString(...values: unknown[]): string | undefined {
|
|
434
|
+
return values.find((value): value is string => typeof value === "string" && value.length > 0);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private asRecord(value: unknown): KiroRecord | null {
|
|
438
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
439
|
+
return value as KiroRecord;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private formatValue(value: unknown): string {
|
|
443
|
+
if (typeof value === "string") return value;
|
|
444
|
+
if (value === undefined || value === null) return "";
|
|
445
|
+
try {
|
|
446
|
+
return JSON.stringify(value);
|
|
447
|
+
} catch {
|
|
448
|
+
return "";
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private truncate(value: string, maxLength: number): string {
|
|
453
|
+
if (value.length <= maxLength) return value;
|
|
454
|
+
return `${value.slice(0, maxLength - 3)}...`;
|
|
455
|
+
}
|
|
456
|
+
}
|
package/src/adapters/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { CodexAdapter } from "../providers/codex/CodexAdapter.js";
|
|
|
3
3
|
export { CopilotAdapter } from "../providers/copilot/CopilotAdapter.js";
|
|
4
4
|
export { GeminiCliAdapter } from "../providers/gemini/GeminiCliAdapter.js";
|
|
5
5
|
export { GrokCliAdapter } from "../providers/grok/GrokCliAdapter.js";
|
|
6
|
+
export { AntigravityCliAdapter } from "./AntigravityCliAdapter.js";
|
|
7
|
+
export { KiroAdapter } from "./KiroAdapter.js";
|
|
6
8
|
export { OpenCodeAdapter } from "../providers/opencode/OpenCodeAdapter.js";
|
|
7
9
|
export { PiAdapter } from "../providers/pi/PiAdapter.js";
|
|
8
10
|
export { AgentStatus } from "./AgentAdapter.js";
|