@ai-devkit/agent-manager 0.5.0 → 0.6.1

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.
@@ -12,7 +12,7 @@
12
12
 
13
13
  import * as fs from 'fs';
14
14
  import * as path from 'path';
15
- import type { AgentAdapter, AgentInfo, ProcessInfo } from './AgentAdapter';
15
+ import type { AgentAdapter, AgentInfo, ProcessInfo, ConversationMessage } from './AgentAdapter';
16
16
  import { AgentStatus } from './AgentAdapter';
17
17
  import { listAgentProcesses, enrichProcesses } from '../utils/process';
18
18
  import { batchGetSessionFileBirthtimes } from '../utils/session';
@@ -78,7 +78,7 @@ export class CodexAdapter implements AgentAdapter {
78
78
  const cachedContent = contentCache.get(match.session.filePath);
79
79
  const sessionData = this.parseSession(cachedContent, match.session.filePath);
80
80
  if (sessionData) {
81
- agents.push(this.mapSessionToAgent(sessionData, match.process));
81
+ agents.push(this.mapSessionToAgent(sessionData, match.process, match.session.filePath));
82
82
  } else {
83
83
  matchedPids.delete(match.process.pid);
84
84
  }
@@ -234,7 +234,7 @@ export class CodexAdapter implements AgentAdapter {
234
234
  };
235
235
  }
236
236
 
237
- private mapSessionToAgent(session: CodexSession, processInfo: ProcessInfo): AgentInfo {
237
+ private mapSessionToAgent(session: CodexSession, processInfo: ProcessInfo, filePath: string): AgentInfo {
238
238
  return {
239
239
  name: generateAgentName(session.projectPath || processInfo.cwd || '', processInfo.pid),
240
240
  type: this.type,
@@ -244,6 +244,7 @@ export class CodexAdapter implements AgentAdapter {
244
244
  projectPath: session.projectPath || processInfo.cwd || '',
245
245
  sessionId: session.sessionId,
246
246
  lastActive: session.lastActive,
247
+ sessionFilePath: filePath,
247
248
  };
248
249
  }
249
250
 
@@ -316,4 +317,59 @@ export class CodexAdapter implements AgentAdapter {
316
317
  const base = path.basename(executable).toLowerCase();
317
318
  return base === 'codex' || base === 'codex.exe';
318
319
  }
320
+
321
+ /**
322
+ * Read the full conversation from a Codex session JSONL file.
323
+ *
324
+ * Codex entries use payload.type to indicate message role and payload.message for content.
325
+ */
326
+ getConversation(sessionFilePath: string, options?: { verbose?: boolean }): ConversationMessage[] {
327
+ const verbose = options?.verbose ?? false;
328
+
329
+ let content: string;
330
+ try {
331
+ content = fs.readFileSync(sessionFilePath, 'utf-8');
332
+ } catch {
333
+ return [];
334
+ }
335
+
336
+ const lines = content.trim().split('\n');
337
+ const messages: ConversationMessage[] = [];
338
+
339
+ for (const line of lines) {
340
+ let entry: CodexEventEntry;
341
+ try {
342
+ entry = JSON.parse(line);
343
+ } catch {
344
+ continue;
345
+ }
346
+
347
+ if (entry.type === 'session_meta') continue;
348
+
349
+ const payloadType = entry.payload?.type;
350
+ if (!payloadType) continue;
351
+
352
+ let role: ConversationMessage['role'];
353
+ if (payloadType === 'user_message') {
354
+ role = 'user';
355
+ } else if (payloadType === 'agent_message' || payloadType === 'task_complete') {
356
+ role = 'assistant';
357
+ } else if (verbose) {
358
+ role = 'system';
359
+ } else {
360
+ continue;
361
+ }
362
+
363
+ const text = entry.payload?.message?.trim();
364
+ if (!text) continue;
365
+
366
+ messages.push({
367
+ role,
368
+ content: text,
369
+ timestamp: entry.timestamp,
370
+ });
371
+ }
372
+
373
+ return messages;
374
+ }
319
375
  }
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@ export { AgentManager } from './AgentManager';
3
3
  export { ClaudeCodeAdapter } from './adapters/ClaudeCodeAdapter';
4
4
  export { CodexAdapter } from './adapters/CodexAdapter';
5
5
  export { AgentStatus } from './adapters/AgentAdapter';
6
- export type { AgentAdapter, AgentType, AgentInfo, ProcessInfo } from './adapters/AgentAdapter';
6
+ export type { AgentAdapter, AgentType, AgentInfo, ProcessInfo, ConversationMessage } from './adapters/AgentAdapter';
7
7
 
8
8
  export { TerminalFocusManager, TerminalType } from './terminal/TerminalFocusManager';
9
9
  export type { TerminalLocation } from './terminal/TerminalFocusManager';
@@ -46,25 +46,47 @@ export class TtyWriter {
46
46
  }
47
47
 
48
48
  private static async sendViaTmux(identifier: string, message: string): Promise<void> {
49
- await execFileAsync('tmux', ['send-keys', '-t', identifier, message, 'Enter']);
49
+ // Send text and Enter as two separate calls so that Enter arrives
50
+ // outside of bracketed paste mode. When the inner application (e.g.
51
+ // Claude Code) has bracketed paste enabled, tmux wraps the send-keys
52
+ // payload in paste brackets — if Enter is included, it gets swallowed
53
+ // as part of the paste instead of acting as a submit action.
54
+ await execFileAsync('tmux', ['send-keys', '-t', identifier, '-l', message]);
55
+ await new Promise((resolve) => setTimeout(resolve, 150));
56
+ await execFileAsync('tmux', ['send-keys', '-t', identifier, 'Enter']);
50
57
  }
51
58
 
52
59
  private static async sendViaITerm2(tty: string, message: string): Promise<void> {
53
60
  const escaped = escapeAppleScript(message);
61
+ // Send text WITHOUT a trailing newline to avoid the newline being swallowed
62
+ // by bracketed paste mode. Then simulate pressing Return separately so that
63
+ // Claude Code (and other interactive TUIs) treat it as a real submit action.
54
64
  const script = `
55
65
  tell application "iTerm"
66
+ set targetSession to missing value
56
67
  repeat with w in windows
57
68
  repeat with t in tabs of w
58
69
  repeat with s in sessions of t
59
70
  if tty of s is "${tty}" then
60
- tell s to write text "${escaped}"
61
- return "ok"
71
+ set targetSession to s
72
+ exit repeat
62
73
  end if
63
74
  end repeat
75
+ if targetSession is not missing value then exit repeat
64
76
  end repeat
77
+ if targetSession is not missing value then exit repeat
65
78
  end repeat
79
+ if targetSession is missing value then return "not_found"
80
+ tell targetSession to write text "${escaped}" newline no
66
81
  end tell
67
- return "not_found"`;
82
+ tell application "iTerm" to activate
83
+ delay 0.15
84
+ tell application "System Events"
85
+ tell process "iTerm2"
86
+ key code 36
87
+ end tell
88
+ end tell
89
+ return "ok"`;
68
90
 
69
91
  const { stdout } = await execFileAsync('osascript', ['-e', script]);
70
92
  if (stdout.trim() !== 'ok') {
@@ -77,6 +99,9 @@ return "not_found"`;
77
99
  // Use System Events keystroke to type into the foreground process,
78
100
  // NOT Terminal.app's "do script" which runs a new shell command.
79
101
  // First activate Terminal and select the correct tab, then type via System Events.
102
+ // Send the text first, then wait for the paste/input to complete before pressing
103
+ // Return separately — this ensures interactive TUIs (like Claude Code) see the
104
+ // Return as a real submit action, not part of a bracketed paste.
80
105
  const script = `
81
106
  tell application "Terminal"
82
107
  set targetFound to false
@@ -99,6 +124,11 @@ delay 0.1
99
124
  tell application "System Events"
100
125
  tell process "Terminal"
101
126
  keystroke "${escaped}"
127
+ end tell
128
+ end tell
129
+ delay 0.15
130
+ tell application "System Events"
131
+ tell process "Terminal"
102
132
  key code 36
103
133
  end tell
104
134
  end tell
@@ -84,9 +84,13 @@ export function matchProcessesToSessions(
84
84
  /**
85
85
  * Generate a deterministic agent name from CWD and PID.
86
86
  *
87
- * Format: "folderName (pid)"
87
+ * Format: "folder-name-pid" (lowercase kebab-case)
88
88
  */
89
89
  export function generateAgentName(cwd: string, pid: number): string {
90
90
  const folderName = path.basename(cwd) || 'unknown';
91
- return `${folderName} (${pid})`;
91
+ const kebab = folderName
92
+ .toLowerCase()
93
+ .replace(/[^a-z0-9]+/g, '-')
94
+ .replace(/^-+|-+$/g, '');
95
+ return `${kebab || 'unknown'}-${pid}`;
92
96
  }