@cmdctrl/aider 0.1.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.
Files changed (47) hide show
  1. package/dist/adapter/agentapi.d.ts +100 -0
  2. package/dist/adapter/agentapi.d.ts.map +1 -0
  3. package/dist/adapter/agentapi.js +578 -0
  4. package/dist/adapter/agentapi.js.map +1 -0
  5. package/dist/client/messages.d.ts +89 -0
  6. package/dist/client/messages.d.ts.map +1 -0
  7. package/dist/client/messages.js +6 -0
  8. package/dist/client/messages.js.map +1 -0
  9. package/dist/client/websocket.d.ts +66 -0
  10. package/dist/client/websocket.d.ts.map +1 -0
  11. package/dist/client/websocket.js +276 -0
  12. package/dist/client/websocket.js.map +1 -0
  13. package/dist/commands/register.d.ts +10 -0
  14. package/dist/commands/register.d.ts.map +1 -0
  15. package/dist/commands/register.js +175 -0
  16. package/dist/commands/register.js.map +1 -0
  17. package/dist/commands/start.d.ts +9 -0
  18. package/dist/commands/start.d.ts.map +1 -0
  19. package/dist/commands/start.js +54 -0
  20. package/dist/commands/start.js.map +1 -0
  21. package/dist/commands/status.d.ts +5 -0
  22. package/dist/commands/status.d.ts.map +1 -0
  23. package/dist/commands/status.js +37 -0
  24. package/dist/commands/status.js.map +1 -0
  25. package/dist/commands/stop.d.ts +5 -0
  26. package/dist/commands/stop.d.ts.map +1 -0
  27. package/dist/commands/stop.js +59 -0
  28. package/dist/commands/stop.js.map +1 -0
  29. package/dist/config/config.d.ts +60 -0
  30. package/dist/config/config.d.ts.map +1 -0
  31. package/dist/config/config.js +176 -0
  32. package/dist/config/config.js.map +1 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +34 -0
  36. package/dist/index.js.map +1 -0
  37. package/package.json +42 -0
  38. package/src/adapter/agentapi.ts +656 -0
  39. package/src/client/messages.ts +125 -0
  40. package/src/client/websocket.ts +317 -0
  41. package/src/commands/register.ts +201 -0
  42. package/src/commands/start.ts +70 -0
  43. package/src/commands/status.ts +45 -0
  44. package/src/commands/stop.ts +58 -0
  45. package/src/config/config.ts +146 -0
  46. package/src/index.ts +39 -0
  47. package/tsconfig.json +19 -0
@@ -0,0 +1,100 @@
1
+ export interface QuestionOption {
2
+ label: string;
3
+ description?: string;
4
+ }
5
+ type EventCallback = (taskId: string, eventType: string, data: Record<string, unknown>) => void;
6
+ export declare class AiderAdapter {
7
+ private running;
8
+ private sessionIdToTaskId;
9
+ private onEvent;
10
+ private nextPort;
11
+ constructor(onEvent: EventCallback);
12
+ /**
13
+ * Get next available port for AgentAPI
14
+ */
15
+ private getNextPort;
16
+ /**
17
+ * Start a new task by launching AgentAPI with Aider
18
+ */
19
+ startTask(taskId: string, instruction: string, projectPath?: string): Promise<void>;
20
+ /**
21
+ * Resume a task - for Aider, this just sends another message
22
+ */
23
+ resumeTask(taskId: string, sessionId: string, message: string, projectPath?: string): Promise<void>;
24
+ /**
25
+ * Cancel a running task
26
+ */
27
+ cancelTask(taskId: string): Promise<void>;
28
+ /**
29
+ * Stop all running tasks
30
+ */
31
+ stopAll(): Promise<void>;
32
+ /**
33
+ * Get list of running task IDs
34
+ */
35
+ getRunningTasks(): string[];
36
+ /**
37
+ * Get messages for a session from AgentAPI
38
+ * Returns messages if the session's AgentAPI is running, null otherwise
39
+ * @param id - Either the canonical taskId or the generated sessionId
40
+ */
41
+ getMessages(id: string): Promise<{
42
+ id: number;
43
+ role: string;
44
+ content: string;
45
+ time: string;
46
+ }[] | null>;
47
+ /**
48
+ * Get the port for a running task's AgentAPI
49
+ */
50
+ getTaskPort(taskId: string): number | null;
51
+ /**
52
+ * Wait for AgentAPI server to be ready and agent to be stable
53
+ *
54
+ * AgentAPI requires the agent status to be "stable" before accepting messages.
55
+ * When first started, Aider may be "running" while it builds the repo map.
56
+ */
57
+ private waitForAgentApi;
58
+ /**
59
+ * Connect to AgentAPI SSE event stream
60
+ *
61
+ * AgentAPI sends named events: 'message_update' and 'status_change'
62
+ * We need to listen for these specifically (onmessage only handles unnamed events)
63
+ */
64
+ private connectEventStream;
65
+ /**
66
+ * Check if a message is a spinner/progress line that should be filtered
67
+ * These lines use \r to overwrite in terminal but create noise in our UI
68
+ */
69
+ private isSpinnerLine;
70
+ /**
71
+ * Handle message_update SSE event
72
+ */
73
+ private handleMessageUpdate;
74
+ /**
75
+ * Handle status_change SSE event
76
+ */
77
+ private handleStatusChange;
78
+ /**
79
+ * Handle an event from AgentAPI
80
+ */
81
+ private handleAgentApiEvent;
82
+ /**
83
+ * Check if content looks like a question/prompt
84
+ */
85
+ private looksLikeQuestion;
86
+ /**
87
+ * Extract the question from content
88
+ */
89
+ private extractQuestion;
90
+ /**
91
+ * Send a message to AgentAPI
92
+ */
93
+ private sendMessage;
94
+ /**
95
+ * Clean up a task
96
+ */
97
+ private cleanup;
98
+ }
99
+ export {};
100
+ //# sourceMappingURL=agentapi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentapi.d.ts","sourceRoot":"","sources":["../../src/adapter/agentapi.ts"],"names":[],"mappings":"AAsFA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,KAAK,aAAa,GAAG,CACnB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,IAAI,CAAC;AAEV,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,iBAAiB,CAAkC;IAC3D,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAAiB;gBAErB,OAAO,EAAE,aAAa;IAIlC;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;IACG,SAAS,CACb,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC;IAkGhB;;OAEG;IACG,UAAU,CACd,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC;IAkBhB;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK/C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B;;OAEG;IACH,eAAe,IAAI,MAAM,EAAE;IAI3B;;;;OAIG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,GAAG,IAAI,CAAC;IAgC5G;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAK1C;;;;;OAKG;YACW,eAAe;IA8C7B;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IA+C1B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAYrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA+C3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAuD3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAOvB;;OAEG;YACW,WAAW;IAsBzB;;OAEG;IACH,OAAO,CAAC,OAAO;CAwBhB"}
@@ -0,0 +1,578 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.AiderAdapter = void 0;
37
+ const pty = __importStar(require("node-pty"));
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const crypto = __importStar(require("crypto"));
42
+ const eventsource_1 = require("eventsource");
43
+ const DEFAULT_TIMEOUT = 10 * 60 * 1000; // 10 minutes
44
+ const AGENTAPI_PORT = 3284;
45
+ const AGENTAPI_STARTUP_TIMEOUT = 60000; // 60 seconds to wait for agentapi to start (Aider needs time for repo map)
46
+ // Find agentapi binary
47
+ function findAgentApi() {
48
+ if (process.env.AGENTAPI_PATH) {
49
+ return process.env.AGENTAPI_PATH;
50
+ }
51
+ const home = os.homedir();
52
+ const commonPaths = [
53
+ path.join(home, '.local', 'bin', 'agentapi'),
54
+ '/usr/local/bin/agentapi',
55
+ '/opt/homebrew/bin/agentapi',
56
+ 'agentapi' // Fall back to PATH
57
+ ];
58
+ for (const p of commonPaths) {
59
+ if (p === 'agentapi')
60
+ return p;
61
+ try {
62
+ if (fs.existsSync(p)) {
63
+ return p;
64
+ }
65
+ }
66
+ catch {
67
+ continue;
68
+ }
69
+ }
70
+ return 'agentapi';
71
+ }
72
+ // Find aider binary
73
+ function findAider() {
74
+ if (process.env.AIDER_PATH) {
75
+ return process.env.AIDER_PATH;
76
+ }
77
+ const home = os.homedir();
78
+ const commonPaths = [
79
+ path.join(home, '.local', 'bin', 'aider'),
80
+ '/usr/local/bin/aider',
81
+ '/opt/homebrew/bin/aider',
82
+ 'aider' // Fall back to PATH
83
+ ];
84
+ for (const p of commonPaths) {
85
+ if (p === 'aider')
86
+ return p;
87
+ try {
88
+ if (fs.existsSync(p)) {
89
+ return p;
90
+ }
91
+ }
92
+ catch {
93
+ continue;
94
+ }
95
+ }
96
+ return 'aider';
97
+ }
98
+ const AGENTAPI_PATH = findAgentApi();
99
+ const AIDER_PATH = findAider();
100
+ console.log(`[AiderAdapter] Using agentapi path: ${AGENTAPI_PATH}`);
101
+ console.log(`[AiderAdapter] Using aider path: ${AIDER_PATH}`);
102
+ class AiderAdapter {
103
+ running = new Map();
104
+ sessionIdToTaskId = new Map(); // Reverse mapping: sessionId -> taskId
105
+ onEvent;
106
+ nextPort = AGENTAPI_PORT;
107
+ constructor(onEvent) {
108
+ this.onEvent = onEvent;
109
+ }
110
+ /**
111
+ * Get next available port for AgentAPI
112
+ */
113
+ getNextPort() {
114
+ // Simple port allocation - in production you'd want to check if port is free
115
+ const port = this.nextPort;
116
+ this.nextPort++;
117
+ if (this.nextPort > AGENTAPI_PORT + 100) {
118
+ this.nextPort = AGENTAPI_PORT; // Wrap around
119
+ }
120
+ return port;
121
+ }
122
+ /**
123
+ * Start a new task by launching AgentAPI with Aider
124
+ */
125
+ async startTask(taskId, instruction, projectPath) {
126
+ console.log(`[${taskId}] Starting Aider task: ${instruction.substring(0, 50)}...`);
127
+ const port = this.getNextPort();
128
+ // Generate a real session ID to resolve PENDING placeholder in database
129
+ // This allows the backend to know the session is active and fetch messages
130
+ const sessionId = `aider-${crypto.randomUUID()}`;
131
+ const rt = {
132
+ taskId,
133
+ sessionId,
134
+ context: '',
135
+ agentApiProcess: null,
136
+ eventSource: null,
137
+ timeoutHandle: null,
138
+ port,
139
+ lastStatus: 'stable',
140
+ firstSpinnerEmitted: false
141
+ };
142
+ this.running.set(taskId, rt);
143
+ this.sessionIdToTaskId.set(sessionId, taskId); // Register reverse mapping
144
+ // Determine working directory
145
+ let cwd = os.homedir();
146
+ if (projectPath && fs.existsSync(projectPath)) {
147
+ cwd = projectPath;
148
+ }
149
+ else if (projectPath) {
150
+ console.log(`[${taskId}] Warning: project path does not exist: ${projectPath}`);
151
+ this.onEvent(taskId, 'WARNING', {
152
+ warning: `Project path "${projectPath}" does not exist. Running in home directory.`
153
+ });
154
+ }
155
+ // Build AgentAPI command
156
+ // agentapi server --port <port> -- aider [aider options]
157
+ const args = [
158
+ 'server',
159
+ '--port', port.toString(),
160
+ '--type', 'aider',
161
+ '--',
162
+ AIDER_PATH,
163
+ '--yes-always' // Auto-accept changes for automation
164
+ ];
165
+ // Add model if specified in environment
166
+ if (process.env.AIDER_MODEL) {
167
+ args.push('--model', process.env.AIDER_MODEL);
168
+ }
169
+ console.log(`[${taskId}] Spawning: ${AGENTAPI_PATH} ${args.join(' ')} in ${cwd}`);
170
+ try {
171
+ // Use node-pty to spawn agentapi with a proper PTY
172
+ // This is required because agentapi uses terminal emulation internally
173
+ const proc = pty.spawn(AGENTAPI_PATH, args, {
174
+ name: 'xterm-color',
175
+ cols: 120,
176
+ rows: 40,
177
+ cwd,
178
+ env: process.env
179
+ });
180
+ rt.agentApiProcess = proc;
181
+ // Log output for debugging (PTY combines stdout/stderr)
182
+ proc.onData((data) => {
183
+ console.log(`[${taskId}] agentapi output: ${data.substring(0, 200)}`);
184
+ });
185
+ proc.onExit(({ exitCode }) => {
186
+ console.log(`[${taskId}] AgentAPI process exited with code ${exitCode}`);
187
+ this.cleanup(taskId);
188
+ });
189
+ // Wait for AgentAPI to be ready
190
+ await this.waitForAgentApi(taskId, port);
191
+ // Set up SSE event stream
192
+ this.connectEventStream(taskId, port, rt);
193
+ // Set timeout
194
+ rt.timeoutHandle = setTimeout(() => {
195
+ console.log(`[${taskId}] Task timed out`);
196
+ this.onEvent(taskId, 'ERROR', { error: 'execution timeout' });
197
+ this.cancelTask(taskId);
198
+ }, DEFAULT_TIMEOUT);
199
+ // Send the initial instruction
200
+ await this.sendMessage(taskId, port, instruction);
201
+ }
202
+ catch (err) {
203
+ console.error(`[${taskId}] Failed to start AgentAPI:`, err);
204
+ this.onEvent(taskId, 'ERROR', { error: err.message });
205
+ this.cleanup(taskId);
206
+ }
207
+ }
208
+ /**
209
+ * Resume a task - for Aider, this just sends another message
210
+ */
211
+ async resumeTask(taskId, sessionId, message, projectPath) {
212
+ console.log(`[${taskId}] Resuming Aider task with message`);
213
+ const rt = this.running.get(taskId);
214
+ if (!rt) {
215
+ // Task not running, need to restart
216
+ console.log(`[${taskId}] Task not found, starting fresh`);
217
+ return this.startTask(taskId, message, projectPath);
218
+ }
219
+ try {
220
+ await this.sendMessage(taskId, rt.port, message);
221
+ }
222
+ catch (err) {
223
+ console.error(`[${taskId}] Failed to send message:`, err);
224
+ this.onEvent(taskId, 'ERROR', { error: err.message });
225
+ }
226
+ }
227
+ /**
228
+ * Cancel a running task
229
+ */
230
+ async cancelTask(taskId) {
231
+ console.log(`[${taskId}] Cancelling task`);
232
+ this.cleanup(taskId);
233
+ }
234
+ /**
235
+ * Stop all running tasks
236
+ */
237
+ async stopAll() {
238
+ for (const taskId of this.running.keys()) {
239
+ await this.cancelTask(taskId);
240
+ }
241
+ }
242
+ /**
243
+ * Get list of running task IDs
244
+ */
245
+ getRunningTasks() {
246
+ return Array.from(this.running.keys());
247
+ }
248
+ /**
249
+ * Get messages for a session from AgentAPI
250
+ * Returns messages if the session's AgentAPI is running, null otherwise
251
+ * @param id - Either the canonical taskId or the generated sessionId
252
+ */
253
+ async getMessages(id) {
254
+ // First try as taskId (canonical ID)
255
+ let rt = this.running.get(id);
256
+ // If not found, try as sessionId using reverse mapping
257
+ if (!rt) {
258
+ const taskId = this.sessionIdToTaskId.get(id);
259
+ if (taskId) {
260
+ rt = this.running.get(taskId);
261
+ }
262
+ }
263
+ if (!rt) {
264
+ console.log(`[${id}] Cannot get messages - task not running (checked both taskId and sessionId)`);
265
+ return null;
266
+ }
267
+ try {
268
+ const response = await fetch(`http://localhost:${rt.port}/messages`);
269
+ if (!response.ok) {
270
+ console.log(`[${id}] Failed to fetch messages: ${response.status}`);
271
+ return null;
272
+ }
273
+ const data = await response.json();
274
+ return data.messages || [];
275
+ }
276
+ catch (err) {
277
+ console.error(`[${id}] Error fetching messages:`, err.message);
278
+ return null;
279
+ }
280
+ }
281
+ /**
282
+ * Get the port for a running task's AgentAPI
283
+ */
284
+ getTaskPort(taskId) {
285
+ const rt = this.running.get(taskId);
286
+ return rt ? rt.port : null;
287
+ }
288
+ /**
289
+ * Wait for AgentAPI server to be ready and agent to be stable
290
+ *
291
+ * AgentAPI requires the agent status to be "stable" before accepting messages.
292
+ * When first started, Aider may be "running" while it builds the repo map.
293
+ */
294
+ async waitForAgentApi(taskId, port) {
295
+ const startTime = Date.now();
296
+ const url = `http://localhost:${port}/status`;
297
+ // First, wait for the server to be up
298
+ let serverUp = false;
299
+ while (Date.now() - startTime < AGENTAPI_STARTUP_TIMEOUT) {
300
+ try {
301
+ const response = await fetch(url);
302
+ if (response.ok) {
303
+ serverUp = true;
304
+ break;
305
+ }
306
+ }
307
+ catch {
308
+ // Not ready yet
309
+ }
310
+ await new Promise(resolve => setTimeout(resolve, 100));
311
+ }
312
+ if (!serverUp) {
313
+ throw new Error(`AgentAPI failed to start within ${AGENTAPI_STARTUP_TIMEOUT}ms`);
314
+ }
315
+ console.log(`[${taskId}] AgentAPI server up on port ${port}, waiting for stable status...`);
316
+ // Now wait for the agent to be stable (not running/initializing)
317
+ while (Date.now() - startTime < AGENTAPI_STARTUP_TIMEOUT) {
318
+ try {
319
+ const response = await fetch(url);
320
+ if (response.ok) {
321
+ const data = await response.json();
322
+ if (data.status === 'stable') {
323
+ console.log(`[${taskId}] AgentAPI ready and stable on port ${port}`);
324
+ return;
325
+ }
326
+ console.log(`[${taskId}] AgentAPI status: ${data.status}, waiting for stable...`);
327
+ }
328
+ }
329
+ catch (err) {
330
+ console.log(`[${taskId}] Error checking status:`, err.message);
331
+ }
332
+ await new Promise(resolve => setTimeout(resolve, 500));
333
+ }
334
+ throw new Error(`Agent failed to reach stable status within ${AGENTAPI_STARTUP_TIMEOUT}ms`);
335
+ }
336
+ /**
337
+ * Connect to AgentAPI SSE event stream
338
+ *
339
+ * AgentAPI sends named events: 'message_update' and 'status_change'
340
+ * We need to listen for these specifically (onmessage only handles unnamed events)
341
+ */
342
+ connectEventStream(taskId, port, rt) {
343
+ const url = `http://localhost:${port}/events`;
344
+ console.log(`[${taskId}] Connecting to SSE stream: ${url}`);
345
+ const es = new eventsource_1.EventSource(url);
346
+ rt.eventSource = es;
347
+ // Handle message_update events (agent messages)
348
+ es.addEventListener('message_update', (event) => {
349
+ try {
350
+ const data = JSON.parse(event.data);
351
+ console.log(`[${taskId}] SSE message_update:`, JSON.stringify(data).substring(0, 100));
352
+ this.handleMessageUpdate(taskId, data, rt);
353
+ }
354
+ catch (err) {
355
+ console.error(`[${taskId}] Failed to parse message_update:`, err);
356
+ }
357
+ });
358
+ // Handle status_change events
359
+ es.addEventListener('status_change', (event) => {
360
+ try {
361
+ const data = JSON.parse(event.data);
362
+ console.log(`[${taskId}] SSE status_change:`, data.status);
363
+ this.handleStatusChange(taskId, data, rt);
364
+ }
365
+ catch (err) {
366
+ console.error(`[${taskId}] Failed to parse status_change:`, err);
367
+ }
368
+ });
369
+ // Fallback for any unnamed events
370
+ es.onmessage = (event) => {
371
+ try {
372
+ const data = JSON.parse(event.data);
373
+ console.log(`[${taskId}] SSE unnamed event:`, JSON.stringify(data).substring(0, 100));
374
+ this.handleAgentApiEvent(taskId, data, rt);
375
+ }
376
+ catch (err) {
377
+ console.error(`[${taskId}] Failed to parse SSE event:`, err);
378
+ }
379
+ };
380
+ es.onerror = (err) => {
381
+ const errorEvent = err;
382
+ console.error(`[${taskId}] SSE error:`, errorEvent.message || 'unknown error');
383
+ // Don't cleanup here - AgentAPI might still be running
384
+ };
385
+ }
386
+ /**
387
+ * Check if a message is a spinner/progress line that should be filtered
388
+ * These lines use \r to overwrite in terminal but create noise in our UI
389
+ */
390
+ isSpinnerLine(message) {
391
+ // Spinner characters
392
+ if (message.includes('░') || message.includes('█')) {
393
+ return true;
394
+ }
395
+ // Common progress patterns (partial lines without timestamps)
396
+ if (message.includes('Waiting for anthropic/') || message.includes('Updating repo map:')) {
397
+ return true;
398
+ }
399
+ return false;
400
+ }
401
+ /**
402
+ * Handle message_update SSE event
403
+ */
404
+ handleMessageUpdate(taskId, event, rt) {
405
+ if (event.role === 'agent') {
406
+ // Skip empty/whitespace-only messages (often from spinner line clears)
407
+ if (!event.message.trim()) {
408
+ return;
409
+ }
410
+ // Skip spinner/progress lines - but let the first one through for progress indication
411
+ if (this.isSpinnerLine(event.message)) {
412
+ if (rt.firstSpinnerEmitted) {
413
+ return; // Skip subsequent spinner lines
414
+ }
415
+ rt.firstSpinnerEmitted = true;
416
+ // Clean up the spinner message to just show the status text
417
+ const cleanMessage = event.message.replace(/[░█\s]+/g, '').trim();
418
+ if (cleanMessage) {
419
+ this.onEvent(taskId, 'OUTPUT', { output: cleanMessage, session_id: rt.sessionId });
420
+ }
421
+ return;
422
+ }
423
+ // Accumulate context
424
+ if (rt.context) {
425
+ rt.context += '\n\n';
426
+ }
427
+ rt.context += event.message;
428
+ // Emit output for streaming display (include session_id to resolve PENDING)
429
+ this.onEvent(taskId, 'OUTPUT', { output: event.message, session_id: rt.sessionId });
430
+ // Check if aider is asking for input
431
+ if (this.looksLikeQuestion(event.message)) {
432
+ console.log(`[${taskId}] Detected question from Aider`);
433
+ this.onEvent(taskId, 'WAIT_FOR_USER', {
434
+ session_id: rt.sessionId,
435
+ prompt: this.extractQuestion(event.message),
436
+ options: [],
437
+ context: rt.context
438
+ });
439
+ }
440
+ }
441
+ }
442
+ /**
443
+ * Handle status_change SSE event
444
+ */
445
+ handleStatusChange(taskId, event, rt) {
446
+ const newStatus = event.status;
447
+ // Detect transition from running -> stable (task completed)
448
+ if (rt.lastStatus === 'running' && newStatus === 'stable') {
449
+ console.log(`[${taskId}] Task completed (running -> stable)`);
450
+ this.onEvent(taskId, 'TASK_COMPLETE', {
451
+ session_id: rt.sessionId,
452
+ result: rt.context
453
+ });
454
+ }
455
+ rt.lastStatus = newStatus;
456
+ }
457
+ /**
458
+ * Handle an event from AgentAPI
459
+ */
460
+ handleAgentApiEvent(taskId, event, rt) {
461
+ console.log(`[${taskId}] AgentAPI event:`, JSON.stringify(event).substring(0, 200));
462
+ if (event.type === 'status') {
463
+ const newStatus = event.status;
464
+ // Detect transition from running -> stable (task completed)
465
+ if (rt.lastStatus === 'running' && newStatus === 'stable') {
466
+ console.log(`[${taskId}] Task completed (running -> stable)`);
467
+ this.onEvent(taskId, 'TASK_COMPLETE', {
468
+ session_id: rt.sessionId,
469
+ result: rt.context
470
+ });
471
+ }
472
+ rt.lastStatus = newStatus;
473
+ }
474
+ else if (event.type === 'message') {
475
+ // Message from the agent
476
+ const content = event.content || '';
477
+ const role = event.role || 'assistant';
478
+ if (role === 'assistant') {
479
+ // Skip spinner/progress lines
480
+ if (this.isSpinnerLine(content)) {
481
+ return;
482
+ }
483
+ // Accumulate context
484
+ if (rt.context) {
485
+ rt.context += '\n\n';
486
+ }
487
+ rt.context += content;
488
+ // Emit output for streaming display (include session_id to resolve PENDING)
489
+ this.onEvent(taskId, 'OUTPUT', { output: content, session_id: rt.sessionId });
490
+ // Check if aider is asking for input
491
+ // Aider typically asks questions ending with ? or prompts for y/n
492
+ if (this.looksLikeQuestion(content)) {
493
+ console.log(`[${taskId}] Detected question from Aider`);
494
+ this.onEvent(taskId, 'WAIT_FOR_USER', {
495
+ session_id: rt.sessionId,
496
+ prompt: this.extractQuestion(content),
497
+ options: [],
498
+ context: rt.context
499
+ });
500
+ }
501
+ }
502
+ }
503
+ }
504
+ /**
505
+ * Check if content looks like a question/prompt
506
+ */
507
+ looksLikeQuestion(content) {
508
+ const lower = content.toLowerCase().trim();
509
+ // Common Aider prompts
510
+ const questionPatterns = [
511
+ /\?\s*$/, // Ends with question mark
512
+ /\(y\/n\)/i, // Yes/no prompt
513
+ /\[y\/n\]/i,
514
+ /proceed\?/i,
515
+ /continue\?/i,
516
+ /confirm/i,
517
+ /would you like/i,
518
+ /do you want/i,
519
+ /should i/i
520
+ ];
521
+ return questionPatterns.some(pattern => pattern.test(lower));
522
+ }
523
+ /**
524
+ * Extract the question from content
525
+ */
526
+ extractQuestion(content) {
527
+ // Take the last few lines which typically contain the question
528
+ const lines = content.trim().split('\n');
529
+ const lastLines = lines.slice(-3).join('\n');
530
+ return lastLines.length > 200 ? lastLines.substring(0, 200) + '...' : lastLines;
531
+ }
532
+ /**
533
+ * Send a message to AgentAPI
534
+ */
535
+ async sendMessage(taskId, port, content) {
536
+ const url = `http://localhost:${port}/message`;
537
+ console.log(`[${taskId}] Sending message to AgentAPI`);
538
+ const response = await fetch(url, {
539
+ method: 'POST',
540
+ headers: {
541
+ 'Content-Type': 'application/json'
542
+ },
543
+ body: JSON.stringify({
544
+ content,
545
+ type: 'user'
546
+ })
547
+ });
548
+ if (!response.ok) {
549
+ throw new Error(`Failed to send message: ${response.status} ${response.statusText}`);
550
+ }
551
+ console.log(`[${taskId}] Message sent successfully`);
552
+ }
553
+ /**
554
+ * Clean up a task
555
+ */
556
+ cleanup(taskId) {
557
+ const rt = this.running.get(taskId);
558
+ if (!rt)
559
+ return;
560
+ if (rt.timeoutHandle) {
561
+ clearTimeout(rt.timeoutHandle);
562
+ }
563
+ if (rt.eventSource) {
564
+ rt.eventSource.close();
565
+ }
566
+ if (rt.agentApiProcess) {
567
+ rt.agentApiProcess.kill();
568
+ }
569
+ // Clean up reverse mapping
570
+ if (rt.sessionId) {
571
+ this.sessionIdToTaskId.delete(rt.sessionId);
572
+ }
573
+ this.running.delete(taskId);
574
+ console.log(`[${taskId}] Task cleaned up`);
575
+ }
576
+ }
577
+ exports.AiderAdapter = AiderAdapter;
578
+ //# sourceMappingURL=agentapi.js.map