@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.
- package/dist/adapters/AgentAdapter.d.ts +19 -2
- package/dist/adapters/AgentAdapter.d.ts.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.d.ts +15 -1
- package/dist/adapters/ClaudeCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/ClaudeCodeAdapter.js +94 -6
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -1
- package/dist/adapters/CodexAdapter.d.ts +9 -1
- package/dist/adapters/CodexAdapter.d.ts.map +1 -1
- package/dist/adapters/CodexAdapter.js +56 -2
- package/dist/adapters/CodexAdapter.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/terminal/TtyWriter.d.ts.map +1 -1
- package/dist/terminal/TtyWriter.js +34 -4
- package/dist/terminal/TtyWriter.js.map +1 -1
- package/dist/utils/matching.d.ts +1 -1
- package/dist/utils/matching.d.ts.map +1 -1
- package/dist/utils/matching.js +6 -2
- package/dist/utils/matching.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/AgentManager.test.ts +5 -2
- package/src/__tests__/adapters/ClaudeCodeAdapter.test.ts +177 -6
- package/src/__tests__/adapters/CodexAdapter.test.ts +115 -0
- package/src/__tests__/terminal/TtyWriter.test.ts +13 -17
- package/src/__tests__/utils/matching.test.ts +13 -5
- package/src/adapters/AgentAdapter.ts +20 -4
- package/src/adapters/ClaudeCodeAdapter.ts +111 -15
- package/src/adapters/CodexAdapter.ts +59 -3
- package/src/index.ts +1 -1
- package/src/terminal/TtyWriter.ts +34 -4
- package/src/utils/matching.ts +6 -2
|
@@ -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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
package/src/utils/matching.ts
CHANGED
|
@@ -84,9 +84,13 @@ export function matchProcessesToSessions(
|
|
|
84
84
|
/**
|
|
85
85
|
* Generate a deterministic agent name from CWD and PID.
|
|
86
86
|
*
|
|
87
|
-
* Format: "
|
|
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
|
-
|
|
91
|
+
const kebab = folderName
|
|
92
|
+
.toLowerCase()
|
|
93
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
94
|
+
.replace(/^-+|-+$/g, '');
|
|
95
|
+
return `${kebab || 'unknown'}-${pid}`;
|
|
92
96
|
}
|