@ai-devkit/agent-manager 0.12.0 → 0.13.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.
@@ -28,14 +28,29 @@ interface PidFileEntry {
28
28
  startedAt: number;
29
29
  kind: string;
30
30
  entrypoint: string;
31
+ /**
32
+ * Authoritative live status published by the Claude Code process
33
+ * (e.g., 'running', 'waiting', 'idle'). Preferred over JSONL-derived
34
+ * status because trailing entries like 'permission-mode' / 'ai-title'
35
+ * can mask the real conversational state.
36
+ */
37
+ status?: string;
38
+ /** Short description of what the agent is waiting on (e.g., "approve Read"). */
39
+ waitingFor?: string;
31
40
  }
32
41
 
33
42
  /**
34
43
  * A process directly matched to a session via PID file (authoritative path).
44
+ *
45
+ * When the matching PID file also exposes live status/waitingFor metadata,
46
+ * those values are carried here so `mapSessionToAgent` can prefer them
47
+ * over the JSONL-derived heuristic.
35
48
  */
36
49
  interface DirectMatch {
37
50
  process: ProcessInfo;
38
51
  sessionFile: SessionFile;
52
+ pidStatus?: AgentStatus;
53
+ waitingFor?: string;
39
54
  }
40
55
 
41
56
  /** Maximum allowed delta (ms) between process start time and PID file startedAt. */
@@ -106,10 +121,14 @@ export class ClaudeCodeAdapter implements AgentAdapter {
106
121
  const agents: AgentInfo[] = [];
107
122
 
108
123
  // Build agents from direct (resume + PID-file) matches
109
- for (const { process: proc, sessionFile } of direct) {
124
+ for (const match of direct) {
125
+ const { process: proc, sessionFile } = match;
110
126
  const sessionData = this.parser.readSession(sessionFile.filePath, sessionFile.resolvedCwd);
111
127
  if (sessionData) {
112
- agents.push(this.mapSessionToAgent(sessionData, proc, sessionFile));
128
+ agents.push(this.mapSessionToAgent(sessionData, proc, sessionFile, {
129
+ pidStatus: match.pidStatus,
130
+ waitingFor: match.waitingFor,
131
+ }));
113
132
  } else {
114
133
  matchedPids.delete(proc.pid);
115
134
  }
@@ -203,6 +222,12 @@ export class ClaudeCodeAdapter implements AgentAdapter {
203
222
  continue;
204
223
  }
205
224
 
225
+ // Best-effort: the PID file (if present for this proc) is the
226
+ // authoritative source of live status. We still match the session
227
+ // via --resume, but we read the PID file alongside to capture
228
+ // status/waitingFor.
229
+ const pidEntry = this.readMatchingPidFile(proc.pid, proc.startTime);
230
+
206
231
  direct.push({
207
232
  process: proc,
208
233
  sessionFile: {
@@ -212,6 +237,8 @@ export class ClaudeCodeAdapter implements AgentAdapter {
212
237
  birthtimeMs: stat.birthtimeMs,
213
238
  resolvedCwd: proc.cwd,
214
239
  },
240
+ pidStatus: this.mapPidStatus(pidEntry?.status),
241
+ waitingFor: pidEntry?.waitingFor,
215
242
  });
216
243
  }
217
244
 
@@ -223,6 +250,54 @@ export class ClaudeCodeAdapter implements AgentAdapter {
223
250
  return match?.[1] ?? null;
224
251
  }
225
252
 
253
+ /**
254
+ * Read and parse ~/.claude/sessions/<pid>.json, returning null on any
255
+ * I/O / parse failure or when the file is stale relative to the live
256
+ * process.
257
+ *
258
+ * "Stale" means the PID file's startedAt diverges from the process's
259
+ * start time by more than {@link PID_FILE_STALENESS_MS} — typically
260
+ * a previous Claude Code process recycled the same PID without cleanup.
261
+ */
262
+ private readMatchingPidFile(pid: number, procStartTime?: Date): PidFileEntry | null {
263
+ const pidFilePath = path.join(this.sessionsDir, `${pid}.json`);
264
+ try {
265
+ const entry = JSON.parse(
266
+ fs.readFileSync(pidFilePath, 'utf-8'),
267
+ ) as PidFileEntry;
268
+
269
+ if (procStartTime) {
270
+ const deltaMs = Math.abs(procStartTime.getTime() - entry.startedAt);
271
+ if (deltaMs > PID_FILE_STALENESS_MS) {
272
+ return null;
273
+ }
274
+ }
275
+
276
+ return entry;
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Map the PID file's live status string to {@link AgentStatus}.
284
+ *
285
+ * Returns undefined for missing / unrecognized values so the caller
286
+ * can fall back to JSONL-derived heuristics.
287
+ */
288
+ private mapPidStatus(status: string | undefined): AgentStatus | undefined {
289
+ switch (status) {
290
+ case 'running':
291
+ return AgentStatus.RUNNING;
292
+ case 'waiting':
293
+ return AgentStatus.WAITING;
294
+ case 'idle':
295
+ return AgentStatus.IDLE;
296
+ default:
297
+ return undefined;
298
+ }
299
+ }
300
+
226
301
  /**
227
302
  * Attempt to match each process to its session via ~/.claude/sessions/<pid>.json.
228
303
  *
@@ -241,43 +316,32 @@ export class ClaudeCodeAdapter implements AgentAdapter {
241
316
  const fallback: ProcessInfo[] = [];
242
317
 
243
318
  for (const proc of processes) {
244
- const pidFilePath = path.join(this.sessionsDir, `${proc.pid}.json`);
245
- try {
246
- const entry = JSON.parse(
247
- fs.readFileSync(pidFilePath, 'utf-8'),
248
- ) as PidFileEntry;
249
-
250
- // Stale-file guard: reject PID files from a previous process with the same PID
251
- if (proc.startTime) {
252
- const deltaMs = Math.abs(proc.startTime.getTime() - entry.startedAt);
253
- if (deltaMs > PID_FILE_STALENESS_MS) {
254
- fallback.push(proc);
255
- continue;
256
- }
257
- }
258
-
259
- const projectDir = this.getProjectDir(entry.cwd);
260
- const jsonlPath = path.join(projectDir, `${entry.sessionId}.jsonl`);
319
+ const entry = this.readMatchingPidFile(proc.pid, proc.startTime);
320
+ if (!entry) {
321
+ fallback.push(proc);
322
+ continue;
323
+ }
261
324
 
262
- if (!fs.existsSync(jsonlPath)) {
263
- fallback.push(proc);
264
- continue;
265
- }
325
+ const projectDir = this.getProjectDir(entry.cwd);
326
+ const jsonlPath = path.join(projectDir, `${entry.sessionId}.jsonl`);
266
327
 
267
- direct.push({
268
- process: proc,
269
- sessionFile: {
270
- sessionId: entry.sessionId,
271
- filePath: jsonlPath,
272
- projectDir,
273
- birthtimeMs: entry.startedAt,
274
- resolvedCwd: entry.cwd,
275
- },
276
- });
277
- } catch {
278
- // PID file absent, unreadable, or malformed — fall back per-process
328
+ if (!fs.existsSync(jsonlPath)) {
279
329
  fallback.push(proc);
330
+ continue;
280
331
  }
332
+
333
+ direct.push({
334
+ process: proc,
335
+ sessionFile: {
336
+ sessionId: entry.sessionId,
337
+ filePath: jsonlPath,
338
+ projectDir,
339
+ birthtimeMs: entry.startedAt,
340
+ resolvedCwd: entry.cwd,
341
+ },
342
+ pidStatus: this.mapPidStatus(entry.status),
343
+ waitingFor: entry.waitingFor,
344
+ });
281
345
  }
282
346
 
283
347
  return { direct, fallback };
@@ -286,11 +350,18 @@ export class ClaudeCodeAdapter implements AgentAdapter {
286
350
  /**
287
351
  * Derive the Claude Code project directory for a given CWD.
288
352
  *
289
- * Claude Code encodes paths by replacing '/' with '-':
290
- * /Users/foo/bar ~/.claude/projects/-Users-foo-bar/
353
+ * Claude Code encodes paths by replacing every non-alphanumeric
354
+ * character with '-', so '/', '_', '.', spaces, etc. all collapse:
355
+ * /Users/foo/bar → -Users-foo-bar
356
+ * /Users/foo/my_project → -Users-foo-my-project
357
+ * /Users/foo/.worktrees/x → -Users-foo--worktrees-x
358
+ *
359
+ * The encoding is lossy — multiple real paths can collide on the
360
+ * same encoded dir. Callers that need to disambiguate must read the
361
+ * `cwd` field inside each session JSONL.
291
362
  */
292
363
  private getProjectDir(cwd: string): string {
293
- const encoded = cwd.replace(/\//g, '-');
364
+ const encoded = cwd.replace(/[^a-zA-Z0-9]/g, '-');
294
365
  return path.join(this.projectsDir, encoded);
295
366
  }
296
367
 
@@ -298,12 +369,22 @@ export class ClaudeCodeAdapter implements AgentAdapter {
298
369
  session: ClaudeSession,
299
370
  processInfo: ProcessInfo,
300
371
  sessionFile: SessionFile,
372
+ liveInfo?: { pidStatus?: AgentStatus; waitingFor?: string },
301
373
  ): AgentInfo {
374
+ // Live PID-file status is authoritative when present — JSONL-derived
375
+ // status mis-classifies sessions whose latest entry is a UI-state
376
+ // event like `permission-mode` or `ai-title`.
377
+ const status = liveInfo?.pidStatus ?? this.parser.determineStatus(session);
378
+ const baseSummary = session.lastUserMessage || 'Session started';
379
+ const summary = status === AgentStatus.WAITING && liveInfo?.waitingFor
380
+ ? `${baseSummary} — waiting for ${liveInfo.waitingFor}`
381
+ : baseSummary;
382
+
302
383
  return {
303
384
  name: generateAgentName(processInfo.cwd, processInfo.pid),
304
385
  type: this.type,
305
- status: this.parser.determineStatus(session),
306
- summary: session.lastUserMessage || 'Session started',
386
+ status,
387
+ summary,
307
388
  pid: processInfo.pid,
308
389
  projectPath: sessionFile.resolvedCwd || processInfo.cwd || '',
309
390
  sessionId: sessionFile.sessionId,
@@ -0,0 +1,340 @@
1
+ /**
2
+ * OpenCode Adapter
3
+ *
4
+ * Detects running OpenCode agents by:
5
+ * 1. Finding running opencode processes via shared listAgentProcesses()
6
+ * 2. Enriching with CWD and start times via shared enrichProcesses()
7
+ * 3. Querying OpenCode's SQLite DB (~/.local/share/opencode/opencode.db) to
8
+ * find the session matching each process's CWD and read status from message.time.completed
9
+ *
10
+ * sessionFilePath encodes "<dbPath>::<sessionId>" so getConversation() can open the right
11
+ * DB row without extending the AgentAdapter interface.
12
+ */
13
+
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import Database from 'better-sqlite3';
17
+ import type {
18
+ AgentAdapter,
19
+ AgentInfo,
20
+ ProcessInfo,
21
+ ConversationMessage,
22
+ SessionSummary,
23
+ ListSessionsOptions,
24
+ } from './AgentAdapter';
25
+ import { AgentStatus } from './AgentAdapter';
26
+ import { listAgentProcesses, enrichProcesses } from '../utils/process';
27
+ import { generateAgentName } from '../utils/matching';
28
+
29
+ const SESSION_REF_SEP = '::';
30
+
31
+ function encodeSessionRef(dbPath: string, sessionId: string): string {
32
+ return `${dbPath}${SESSION_REF_SEP}${sessionId}`;
33
+ }
34
+
35
+ function decodeSessionRef(ref: string): { dbPath: string; sessionId: string } | null {
36
+ const idx = ref.lastIndexOf(SESSION_REF_SEP);
37
+ if (idx === -1) return null;
38
+ return { dbPath: ref.slice(0, idx), sessionId: ref.slice(idx + SESSION_REF_SEP.length) };
39
+ }
40
+
41
+ interface OpenCodeSession {
42
+ sessionId: string;
43
+ directory: string;
44
+ timeCreated: number;
45
+ }
46
+
47
+ interface OpenCodeSessionStats {
48
+ lastRole: string | null;
49
+ lastTimeUpdated: number;
50
+ /** OpenCode writes `time.completed` on the assistant message only when the turn finishes. */
51
+ lastAssistantCompleted: boolean;
52
+ lastAssistantErrored: boolean;
53
+ summary: string;
54
+ }
55
+
56
+ export class OpenCodeAdapter implements AgentAdapter {
57
+ readonly type = 'opencode' as const;
58
+
59
+ private static readonly IDLE_THRESHOLD_MINUTES = 5;
60
+
61
+ private readonly dbPath: string;
62
+ private db: Database.Database | null = null;
63
+
64
+ constructor() {
65
+ this.dbPath = OpenCodeAdapter.resolveDbPath();
66
+ const cleanup = (): void => this.close();
67
+ process.once('exit', cleanup);
68
+ process.once('SIGINT', cleanup);
69
+ process.once('SIGTERM', cleanup);
70
+ }
71
+
72
+ close(): void {
73
+ if (this.db) {
74
+ try { this.db.close(); } catch { /* ignore */ }
75
+ this.db = null;
76
+ }
77
+ }
78
+
79
+ private static resolveDbPath(): string {
80
+ const xdg = process.env.XDG_DATA_HOME;
81
+ const home = process.env.HOME || process.env.USERPROFILE || '';
82
+ const base = xdg || path.join(home, '.local', 'share');
83
+ return path.join(base, 'opencode', 'opencode.db');
84
+ }
85
+
86
+ canHandle(processInfo: ProcessInfo): boolean {
87
+ const exe = (processInfo.command.trim().split(/\s+/)[0] || '').toLowerCase();
88
+ const base = path.basename(exe);
89
+ return base === 'opencode' || base === 'opencode.exe';
90
+ }
91
+
92
+ async detectAgents(): Promise<AgentInfo[]> {
93
+ const processes = enrichProcesses(listAgentProcesses('opencode'));
94
+ if (processes.length === 0) return [];
95
+
96
+ const db = this.openDb();
97
+ if (!db) return processes.map((p) => this.mapProcessOnlyAgent(p));
98
+
99
+ const agents: AgentInfo[] = [];
100
+ for (const proc of processes) {
101
+ if (!proc.cwd) {
102
+ agents.push(this.mapProcessOnlyAgent(proc));
103
+ continue;
104
+ }
105
+
106
+ const session = this.findSessionForDirectory(db, proc.cwd);
107
+ if (!session) {
108
+ agents.push(this.mapProcessOnlyAgent(proc));
109
+ continue;
110
+ }
111
+
112
+ const stats = this.getSessionStats(db, session.sessionId);
113
+ agents.push(this.mapSessionToAgent(session, stats, proc));
114
+ }
115
+
116
+ return agents;
117
+ }
118
+
119
+ getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {
120
+ const verbose = options?.verbose ?? false;
121
+ const ref = decodeSessionRef(sessionFilePath);
122
+ if (!ref) return [];
123
+
124
+ const db = this.openDb();
125
+ if (!db) return [];
126
+
127
+ try {
128
+ const rows = db.prepare<[string], { role: string; partData: string; timeCreated: number }>(`
129
+ SELECT json_extract(m.data, '$.role') AS role,
130
+ p.data AS partData,
131
+ p.time_created AS timeCreated
132
+ FROM part p
133
+ JOIN message m ON p.message_id = m.id
134
+ WHERE p.session_id = ?
135
+ ORDER BY p.time_created ASC
136
+ `).all(ref.sessionId);
137
+
138
+ const messages: ConversationMessage[] = [];
139
+
140
+ for (const row of rows) {
141
+ let partData: { type?: string; text?: string; reasoning?: string; tool?: string } = {};
142
+ try {
143
+ partData = JSON.parse(row.partData);
144
+ } catch {
145
+ continue;
146
+ }
147
+
148
+ const role = row.role === 'user' ? 'user' : 'assistant';
149
+
150
+ if (partData.type === 'text' && partData.text) {
151
+ messages.push({ role, content: partData.text });
152
+ } else if (partData.type === 'reasoning' && verbose) {
153
+ const text = partData.reasoning || partData.text || '';
154
+ if (text) messages.push({ role: 'assistant', content: `[thinking] ${text}` });
155
+ } else if (partData.type === 'tool' && verbose) {
156
+ const toolName = partData.tool || 'tool';
157
+ messages.push({ role: 'assistant', content: `[tool: ${toolName}]` });
158
+ }
159
+ }
160
+
161
+ return messages;
162
+ } catch {
163
+ this.close();
164
+ return [];
165
+ }
166
+ }
167
+
168
+ async listSessions(opts?: ListSessionsOptions): Promise<SessionSummary[]> {
169
+ const db = this.openDb();
170
+ if (!db) return [];
171
+
172
+ try {
173
+ const rows = db.prepare<[], { id: string; directory: string; timeCreated: number }>(`
174
+ SELECT id, directory, time_created AS timeCreated
175
+ FROM session
176
+ ORDER BY time_created DESC
177
+ `).all();
178
+
179
+ const summaries: SessionSummary[] = [];
180
+
181
+ for (const row of rows) {
182
+ if (opts?.cwd !== undefined && row.directory !== opts.cwd) continue;
183
+
184
+ const stats = this.getSessionStats(db, row.id);
185
+ const lastActive = stats.lastTimeUpdated > 0
186
+ ? new Date(stats.lastTimeUpdated)
187
+ : new Date(row.timeCreated);
188
+ const startedAt = new Date(row.timeCreated);
189
+
190
+ summaries.push({
191
+ type: 'opencode',
192
+ sessionId: row.id,
193
+ cwd: row.directory,
194
+ firstUserMessage: stats.summary,
195
+ lastActive,
196
+ startedAt,
197
+ sessionFilePath: encodeSessionRef(this.dbPath, row.id),
198
+ });
199
+ }
200
+
201
+ return summaries;
202
+ } catch {
203
+ this.close();
204
+ return [];
205
+ }
206
+ }
207
+
208
+ private findSessionForDirectory(db: Database.Database, directory: string): OpenCodeSession | null {
209
+ try {
210
+ const row = db.prepare<[string], { id: string; directory: string; time_created: number }>(`
211
+ SELECT id, directory, time_created
212
+ FROM session
213
+ WHERE directory = ?
214
+ ORDER BY time_created DESC
215
+ LIMIT 1
216
+ `).get(directory);
217
+
218
+ if (!row) return null;
219
+ return { sessionId: row.id, directory: row.directory, timeCreated: row.time_created };
220
+ } catch {
221
+ return null;
222
+ }
223
+ }
224
+
225
+ private getSessionStats(db: Database.Database, sessionId: string): OpenCodeSessionStats {
226
+ const empty: OpenCodeSessionStats = {
227
+ lastRole: null,
228
+ lastTimeUpdated: 0,
229
+ lastAssistantCompleted: false,
230
+ lastAssistantErrored: false,
231
+ summary: '',
232
+ };
233
+
234
+ try {
235
+ // Order by time_created — time_updated can lag when OpenCode appends
236
+ // metadata (e.g. summary diffs) to user messages after a turn finishes.
237
+ const last = db.prepare<[string], { role: string; timeUpdated: number }>(`
238
+ SELECT json_extract(data, '$.role') AS role,
239
+ time_updated AS timeUpdated
240
+ FROM message
241
+ WHERE session_id = ?
242
+ ORDER BY time_created DESC
243
+ LIMIT 1
244
+ `).get(sessionId);
245
+
246
+ const heartbeat = db.prepare<[string], { maxUpdated: number }>(`
247
+ SELECT MAX(time_updated) AS maxUpdated FROM message WHERE session_id = ?
248
+ `).get(sessionId);
249
+
250
+ const lastAssistant = db.prepare<[string], {
251
+ completed: number | null;
252
+ errored: number | null;
253
+ }>(`
254
+ SELECT json_extract(data, '$.time.completed') AS completed,
255
+ json_extract(data, '$.time.error') AS errored
256
+ FROM message
257
+ WHERE session_id = ? AND json_extract(data, '$.role') = 'assistant'
258
+ ORDER BY time_created DESC
259
+ LIMIT 1
260
+ `).get(sessionId);
261
+
262
+ const first = db.prepare<[string], { text: string }>(`
263
+ SELECT json_extract(p.data, '$.text') AS text
264
+ FROM part p
265
+ JOIN message m ON p.message_id = m.id
266
+ WHERE p.session_id = ?
267
+ AND json_extract(m.data, '$.role') = 'user'
268
+ AND json_extract(p.data, '$.type') = 'text'
269
+ AND json_extract(p.data, '$.text') IS NOT NULL
270
+ ORDER BY p.time_created ASC
271
+ LIMIT 1
272
+ `).get(sessionId);
273
+
274
+ return {
275
+ lastRole: last?.role ?? null,
276
+ lastTimeUpdated: heartbeat?.maxUpdated ?? last?.timeUpdated ?? 0,
277
+ lastAssistantCompleted: lastAssistant?.completed != null,
278
+ lastAssistantErrored: lastAssistant?.errored != null,
279
+ summary: first?.text?.trim() ?? '',
280
+ };
281
+ } catch {
282
+ return empty;
283
+ }
284
+ }
285
+
286
+ private mapSessionToAgent(
287
+ session: OpenCodeSession,
288
+ stats: OpenCodeSessionStats,
289
+ proc: ProcessInfo,
290
+ ): AgentInfo {
291
+ const lastActive = stats.lastTimeUpdated > 0
292
+ ? new Date(stats.lastTimeUpdated)
293
+ : new Date(session.timeCreated);
294
+
295
+ return {
296
+ name: generateAgentName(session.directory || proc.cwd || '', proc.pid),
297
+ type: this.type,
298
+ status: this.determineStatus(stats, lastActive),
299
+ summary: stats.summary || 'OpenCode session active',
300
+ pid: proc.pid,
301
+ projectPath: session.directory || proc.cwd || '',
302
+ sessionId: session.sessionId,
303
+ lastActive,
304
+ sessionFilePath: encodeSessionRef(this.dbPath, session.sessionId),
305
+ };
306
+ }
307
+
308
+ private mapProcessOnlyAgent(proc: ProcessInfo): AgentInfo {
309
+ return {
310
+ name: generateAgentName(proc.cwd || '', proc.pid),
311
+ type: this.type,
312
+ status: AgentStatus.RUNNING,
313
+ summary: 'OpenCode process running',
314
+ pid: proc.pid,
315
+ projectPath: proc.cwd || '',
316
+ sessionId: `pid-${proc.pid}`,
317
+ lastActive: new Date(),
318
+ };
319
+ }
320
+
321
+ private determineStatus(stats: OpenCodeSessionStats, lastActive: Date): AgentStatus {
322
+ const ageMin = (Date.now() - lastActive.getTime()) / 60000;
323
+ if (ageMin > OpenCodeAdapter.IDLE_THRESHOLD_MINUTES) return AgentStatus.IDLE;
324
+
325
+ if (stats.lastRole === 'assistant' && !stats.lastAssistantCompleted) return AgentStatus.RUNNING;
326
+ if (stats.lastRole === 'assistant') return AgentStatus.WAITING;
327
+ return AgentStatus.RUNNING;
328
+ }
329
+
330
+ private openDb(): Database.Database | null {
331
+ if (this.db) return this.db;
332
+ if (!fs.existsSync(this.dbPath)) return null;
333
+ try {
334
+ this.db = new Database(this.dbPath, { readonly: true });
335
+ return this.db;
336
+ } catch {
337
+ return null;
338
+ }
339
+ }
340
+ }
@@ -1,5 +1,6 @@
1
1
  export { ClaudeCodeAdapter } from './ClaudeCodeAdapter';
2
2
  export { CodexAdapter } from './CodexAdapter';
3
3
  export { GeminiCliAdapter } from './GeminiCliAdapter';
4
+ export { OpenCodeAdapter } from './OpenCodeAdapter';
4
5
  export { AgentStatus } from './AgentAdapter';
5
6
  export type { AgentAdapter, AgentType, AgentInfo, ProcessInfo } from './AgentAdapter';
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export { AgentManager } from './AgentManager';
3
3
  export { ClaudeCodeAdapter } from './adapters/ClaudeCodeAdapter';
4
4
  export { CodexAdapter } from './adapters/CodexAdapter';
5
5
  export { GeminiCliAdapter } from './adapters/GeminiCliAdapter';
6
+ export { OpenCodeAdapter } from './adapters/OpenCodeAdapter';
6
7
  export { AgentStatus } from './adapters/AgentAdapter';
7
8
  export type {
8
9
  AgentAdapter,