@ai-devkit/agent-manager 0.2.0 → 0.4.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.
@@ -208,7 +208,13 @@ describe('CodexAdapter', () => {
208
208
 
209
209
  const agents = await adapter.detectAgents();
210
210
  expect(agents).toHaveLength(1);
211
- expect(agents[0].pid).toBe(105);
211
+ expect(agents[0]).toMatchObject({
212
+ pid: 105,
213
+ name: 'repo-x',
214
+ summary: 'Codex process running',
215
+ projectPath: '/repo-x',
216
+ });
217
+ expect(agents[0].sessionId).toBe('pid-105');
212
218
  });
213
219
 
214
220
  it('should list process when session metadata is unavailable', async () => {
@@ -0,0 +1,154 @@
1
+ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
2
+ import { TtyWriter } from '../../terminal/TtyWriter';
3
+ import { TerminalType } from '../../terminal/TerminalFocusManager';
4
+ import type { TerminalLocation } from '../../terminal/TerminalFocusManager';
5
+ import { execFile } from 'child_process';
6
+
7
+ jest.mock('child_process', () => {
8
+ const actual = jest.requireActual<typeof import('child_process')>('child_process');
9
+ return {
10
+ ...actual,
11
+ execFile: jest.fn(),
12
+ };
13
+ });
14
+
15
+ const mockedExecFile = execFile as unknown as jest.Mock;
16
+
17
+ function mockExecFileSuccess(stdout = '') {
18
+ mockedExecFile.mockImplementation((...args: unknown[]) => {
19
+ const cb = args[args.length - 1] as (err: Error | null, result: { stdout: string }, stderr: string) => void;
20
+ cb(null, { stdout }, '');
21
+ });
22
+ }
23
+
24
+ function mockExecFileError(message: string) {
25
+ mockedExecFile.mockImplementation((...args: unknown[]) => {
26
+ const cb = args[args.length - 1] as (err: Error | null, result: null, stderr: string) => void;
27
+ cb(new Error(message), null, '');
28
+ });
29
+ }
30
+
31
+ describe('TtyWriter', () => {
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ });
35
+
36
+ describe('tmux', () => {
37
+ const location: TerminalLocation = {
38
+ type: TerminalType.TMUX,
39
+ identifier: 'main:0.1',
40
+ tty: '/dev/ttys030',
41
+ };
42
+
43
+ it('sends message via tmux send-keys', async () => {
44
+ mockExecFileSuccess();
45
+
46
+ await TtyWriter.send(location, 'continue');
47
+
48
+ expect(mockedExecFile).toHaveBeenCalledWith(
49
+ 'tmux',
50
+ ['send-keys', '-t', 'main:0.1', 'continue', 'Enter'],
51
+ expect.any(Function),
52
+ );
53
+ });
54
+
55
+ it('throws on tmux failure', async () => {
56
+ mockExecFileError('tmux not running');
57
+
58
+ await expect(TtyWriter.send(location, 'hello'))
59
+ .rejects.toThrow('tmux not running');
60
+ });
61
+ });
62
+
63
+ describe('iTerm2', () => {
64
+ const location: TerminalLocation = {
65
+ type: TerminalType.ITERM2,
66
+ identifier: '/dev/ttys030',
67
+ tty: '/dev/ttys030',
68
+ };
69
+
70
+ it('sends message via osascript with execFile (no shell)', async () => {
71
+ mockExecFileSuccess('ok');
72
+
73
+ await TtyWriter.send(location, 'hello');
74
+
75
+ expect(mockedExecFile).toHaveBeenCalledWith(
76
+ 'osascript',
77
+ ['-e', expect.stringContaining('write text "hello"')],
78
+ expect.any(Function),
79
+ );
80
+ });
81
+
82
+ it('escapes special characters in message', async () => {
83
+ mockExecFileSuccess('ok');
84
+
85
+ await TtyWriter.send(location, 'say "hi" \\ there');
86
+
87
+ expect(mockedExecFile).toHaveBeenCalledWith(
88
+ 'osascript',
89
+ ['-e', expect.stringContaining('write text "say \\"hi\\" \\\\ there"')],
90
+ expect.any(Function),
91
+ );
92
+ });
93
+
94
+ it('throws when session not found', async () => {
95
+ mockExecFileSuccess('not_found');
96
+
97
+ await expect(TtyWriter.send(location, 'test'))
98
+ .rejects.toThrow('iTerm2 session not found');
99
+ });
100
+ });
101
+
102
+ describe('Terminal.app', () => {
103
+ const location: TerminalLocation = {
104
+ type: TerminalType.TERMINAL_APP,
105
+ identifier: '/dev/ttys030',
106
+ tty: '/dev/ttys030',
107
+ };
108
+
109
+ it('sends message via System Events keystroke (not do script)', async () => {
110
+ mockExecFileSuccess('ok');
111
+
112
+ await TtyWriter.send(location, 'hello');
113
+
114
+ const scriptArg = (mockedExecFile.mock.calls[0] as unknown[])[1] as string[];
115
+ const script = scriptArg[1];
116
+ // Must use keystroke, NOT do script
117
+ expect(script).toContain('keystroke "hello"');
118
+ expect(script).toContain('key code 36');
119
+ expect(script).not.toContain('do script');
120
+ });
121
+
122
+ it('uses execFile to avoid shell injection', async () => {
123
+ mockExecFileSuccess('ok');
124
+
125
+ await TtyWriter.send(location, "don't stop");
126
+
127
+ expect(mockedExecFile).toHaveBeenCalledWith(
128
+ 'osascript',
129
+ ['-e', expect.any(String)],
130
+ expect.any(Function),
131
+ );
132
+ });
133
+
134
+ it('throws when tab not found', async () => {
135
+ mockExecFileSuccess('not_found');
136
+
137
+ await expect(TtyWriter.send(location, 'test'))
138
+ .rejects.toThrow('Terminal.app tab not found');
139
+ });
140
+ });
141
+
142
+ describe('unsupported terminal', () => {
143
+ it('throws for unknown terminal type', async () => {
144
+ const location: TerminalLocation = {
145
+ type: TerminalType.UNKNOWN,
146
+ identifier: '',
147
+ tty: '/dev/ttys030',
148
+ };
149
+
150
+ await expect(TtyWriter.send(location, 'test'))
151
+ .rejects.toThrow('Cannot send input: unsupported terminal type');
152
+ });
153
+ });
154
+ });