@sylphx/flow 1.7.0 → 1.8.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/CHANGELOG.md +78 -0
- package/assets/agents/coder.md +72 -119
- package/assets/agents/orchestrator.md +26 -90
- package/assets/agents/reviewer.md +76 -47
- package/assets/agents/writer.md +82 -63
- package/assets/output-styles/silent.md +141 -8
- package/assets/rules/code-standards.md +9 -33
- package/assets/rules/core.md +67 -59
- package/package.json +2 -12
- package/src/commands/flow/execute.ts +470 -0
- package/src/commands/flow/index.ts +11 -0
- package/src/commands/flow/prompt.ts +35 -0
- package/src/commands/flow/setup.ts +312 -0
- package/src/commands/flow/targets.ts +18 -0
- package/src/commands/flow/types.ts +47 -0
- package/src/commands/flow-command.ts +18 -967
- package/src/commands/flow-orchestrator.ts +14 -5
- package/src/commands/hook-command.ts +1 -1
- package/src/commands/init-core.ts +12 -3
- package/src/commands/run-command.ts +1 -1
- package/src/config/rules.ts +1 -1
- package/src/core/error-handling.ts +1 -1
- package/src/core/loop-controller.ts +1 -1
- package/src/core/state-detector.ts +1 -1
- package/src/core/target-manager.ts +1 -1
- package/src/index.ts +1 -1
- package/src/shared/files/index.ts +1 -1
- package/src/shared/processing/index.ts +1 -1
- package/src/targets/claude-code.ts +3 -3
- package/src/targets/opencode.ts +3 -3
- package/src/utils/agent-enhancer.ts +2 -2
- package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
- package/src/utils/{paths.ts → config/paths.ts} +1 -1
- package/src/utils/{settings.ts → config/settings.ts} +1 -1
- package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
- package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
- package/src/utils/display/banner.ts +25 -0
- package/src/utils/display/status.ts +55 -0
- package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
- package/src/utils/files/jsonc.ts +36 -0
- package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
- package/src/utils/index.ts +42 -61
- package/src/utils/version.ts +47 -0
- package/src/components/benchmark-monitor.tsx +0 -331
- package/src/components/reindex-progress.tsx +0 -261
- package/src/composables/functional/index.ts +0 -14
- package/src/composables/functional/useEnvironment.ts +0 -171
- package/src/composables/functional/useFileSystem.ts +0 -139
- package/src/composables/index.ts +0 -4
- package/src/composables/useEnv.ts +0 -13
- package/src/composables/useRuntimeConfig.ts +0 -27
- package/src/core/ai-sdk.ts +0 -603
- package/src/core/app-factory.ts +0 -381
- package/src/core/builtin-agents.ts +0 -9
- package/src/core/command-system.ts +0 -550
- package/src/core/config-system.ts +0 -550
- package/src/core/connection-pool.ts +0 -390
- package/src/core/di-container.ts +0 -155
- package/src/core/headless-display.ts +0 -96
- package/src/core/interfaces/index.ts +0 -22
- package/src/core/interfaces/repository.interface.ts +0 -91
- package/src/core/interfaces/service.interface.ts +0 -133
- package/src/core/interfaces.ts +0 -96
- package/src/core/result.ts +0 -351
- package/src/core/service-config.ts +0 -252
- package/src/core/session-service.ts +0 -121
- package/src/core/storage-factory.ts +0 -115
- package/src/core/stream-handler.ts +0 -288
- package/src/core/type-utils.ts +0 -427
- package/src/core/unified-storage.ts +0 -456
- package/src/core/validation/limit.ts +0 -46
- package/src/core/validation/query.ts +0 -20
- package/src/db/auto-migrate.ts +0 -322
- package/src/db/base-database-client.ts +0 -144
- package/src/db/cache-db.ts +0 -218
- package/src/db/cache-schema.ts +0 -75
- package/src/db/database.ts +0 -70
- package/src/db/index.ts +0 -252
- package/src/db/memory-db.ts +0 -153
- package/src/db/memory-schema.ts +0 -29
- package/src/db/schema.ts +0 -289
- package/src/db/session-repository.ts +0 -733
- package/src/domains/index.ts +0 -6
- package/src/domains/utilities/index.ts +0 -6
- package/src/domains/utilities/time/index.ts +0 -5
- package/src/domains/utilities/time/tools.ts +0 -291
- package/src/services/agent-service.ts +0 -273
- package/src/services/evaluation-service.ts +0 -271
- package/src/services/functional/evaluation-logic.ts +0 -296
- package/src/services/functional/file-processor.ts +0 -273
- package/src/services/functional/index.ts +0 -12
- package/src/services/memory.service.ts +0 -476
- package/src/types/api/batch.ts +0 -108
- package/src/types/api/errors.ts +0 -118
- package/src/types/api/index.ts +0 -55
- package/src/types/api/requests.ts +0 -76
- package/src/types/api/responses.ts +0 -180
- package/src/types/api/websockets.ts +0 -85
- package/src/types/benchmark.ts +0 -49
- package/src/types/database.types.ts +0 -510
- package/src/types/memory-types.ts +0 -63
- package/src/utils/advanced-tokenizer.ts +0 -191
- package/src/utils/ai-model-fetcher.ts +0 -19
- package/src/utils/async-file-operations.ts +0 -516
- package/src/utils/audio-player.ts +0 -345
- package/src/utils/codebase-helpers.ts +0 -211
- package/src/utils/console-ui.ts +0 -79
- package/src/utils/database-errors.ts +0 -140
- package/src/utils/debug-logger.ts +0 -49
- package/src/utils/file-scanner.ts +0 -259
- package/src/utils/help.ts +0 -20
- package/src/utils/immutable-cache.ts +0 -106
- package/src/utils/jsonc.ts +0 -158
- package/src/utils/memory-tui.ts +0 -414
- package/src/utils/models-dev.ts +0 -91
- package/src/utils/parallel-operations.ts +0 -487
- package/src/utils/process-manager.ts +0 -155
- package/src/utils/prompts.ts +0 -120
- package/src/utils/search-tool-builder.ts +0 -214
- package/src/utils/session-manager.ts +0 -168
- package/src/utils/session-title.ts +0 -87
- package/src/utils/simplified-errors.ts +0 -410
- package/src/utils/template-engine.ts +0 -94
- package/src/utils/test-audio.ts +0 -71
- package/src/utils/todo-context.ts +0 -46
- package/src/utils/token-counter.ts +0 -288
- /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
- /package/src/utils/{logger.ts → display/logger.ts} +0 -0
- /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
- /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
- /package/src/utils/{security.ts → security/security.ts} +0 -0
package/src/domains/index.ts
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
// Logger utility
|
|
6
|
-
const Logger = {
|
|
7
|
-
info: (message: string) => console.error(`[INFO] ${message}`),
|
|
8
|
-
success: (message: string) => console.error(`[SUCCESS] ${message}`),
|
|
9
|
-
error: (message: string, error?: unknown) => {
|
|
10
|
-
console.error(`[ERROR] ${message}`);
|
|
11
|
-
if (error) {
|
|
12
|
-
console.error(error);
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Helper function to validate IANA timezone
|
|
18
|
-
function isValidTimezone(timezone: string): boolean {
|
|
19
|
-
try {
|
|
20
|
-
Intl.DateTimeFormat(undefined, { timeZone: timezone });
|
|
21
|
-
return true;
|
|
22
|
-
} catch {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Helper function to validate time format (HH:MM)
|
|
28
|
-
function isValidTimeFormat(time: string): boolean {
|
|
29
|
-
const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/;
|
|
30
|
-
return timeRegex.test(time);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Get current time in a specific timezone
|
|
34
|
-
function getCurrentTime(args: { timezone: string }): CallToolResult {
|
|
35
|
-
try {
|
|
36
|
-
const { timezone } = args;
|
|
37
|
-
|
|
38
|
-
// Validate timezone
|
|
39
|
-
if (!isValidTimezone(timezone)) {
|
|
40
|
-
return {
|
|
41
|
-
content: [
|
|
42
|
-
{
|
|
43
|
-
type: 'text',
|
|
44
|
-
text: `✗ Invalid timezone: ${timezone}. Please use a valid IANA timezone name (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo').`,
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
isError: true,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Get current time in specified timezone
|
|
52
|
-
const now = new Date();
|
|
53
|
-
const timeFormatter = new Intl.DateTimeFormat('en-US', {
|
|
54
|
-
timeZone: timezone,
|
|
55
|
-
year: 'numeric',
|
|
56
|
-
month: 'long',
|
|
57
|
-
day: 'numeric',
|
|
58
|
-
hour: '2-digit',
|
|
59
|
-
minute: '2-digit',
|
|
60
|
-
second: '2-digit',
|
|
61
|
-
timeZoneName: 'long',
|
|
62
|
-
hour12: false,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const parts = timeFormatter.formatToParts(now);
|
|
66
|
-
const formatObject: Record<string, string> = {};
|
|
67
|
-
for (const part of parts) {
|
|
68
|
-
formatObject[part.type] = part.value;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const time24 = new Intl.DateTimeFormat('en-US', {
|
|
72
|
-
timeZone: timezone,
|
|
73
|
-
hour: '2-digit',
|
|
74
|
-
minute: '2-digit',
|
|
75
|
-
hour12: false,
|
|
76
|
-
}).format(now);
|
|
77
|
-
|
|
78
|
-
const isoString = now.toLocaleString('sv-SE', { timeZone: timezone });
|
|
79
|
-
|
|
80
|
-
Logger.info(`Retrieved current time for timezone: ${timezone}`);
|
|
81
|
-
return {
|
|
82
|
-
content: [
|
|
83
|
-
{
|
|
84
|
-
type: 'text',
|
|
85
|
-
text: JSON.stringify(
|
|
86
|
-
{
|
|
87
|
-
timezone,
|
|
88
|
-
current_time: {
|
|
89
|
-
date: `${formatObject.month} ${formatObject.day}, ${formatObject.year}`,
|
|
90
|
-
time_24h: time24,
|
|
91
|
-
time_with_seconds: timeFormatter.format(now),
|
|
92
|
-
timezone_name: formatObject.timeZoneName,
|
|
93
|
-
iso_format: `${isoString.replace(' ', 'T')}Z`,
|
|
94
|
-
unix_timestamp: Math.floor(now.getTime() / 1000),
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
null,
|
|
98
|
-
2
|
|
99
|
-
),
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
};
|
|
103
|
-
} catch (error: unknown) {
|
|
104
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
105
|
-
Logger.error('Error getting current time', error);
|
|
106
|
-
return {
|
|
107
|
-
content: [
|
|
108
|
-
{
|
|
109
|
-
type: 'text',
|
|
110
|
-
text: `✗ Error getting current time: ${errorMessage}`,
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
isError: true,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Convert time between timezones
|
|
119
|
-
function convertTime(args: {
|
|
120
|
-
source_timezone: string;
|
|
121
|
-
time: string;
|
|
122
|
-
target_timezone: string;
|
|
123
|
-
}): CallToolResult {
|
|
124
|
-
try {
|
|
125
|
-
const { source_timezone, time, target_timezone } = args;
|
|
126
|
-
|
|
127
|
-
// Validate timezones
|
|
128
|
-
if (!isValidTimezone(source_timezone)) {
|
|
129
|
-
return {
|
|
130
|
-
content: [
|
|
131
|
-
{
|
|
132
|
-
type: 'text',
|
|
133
|
-
text: `✗ Invalid source timezone: ${source_timezone}. Please use a valid IANA timezone name.`,
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
isError: true,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (!isValidTimezone(target_timezone)) {
|
|
141
|
-
return {
|
|
142
|
-
content: [
|
|
143
|
-
{
|
|
144
|
-
type: 'text',
|
|
145
|
-
text: `✗ Invalid target timezone: ${target_timezone}. Please use a valid IANA timezone name.`,
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
isError: true,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Validate time format
|
|
153
|
-
if (!isValidTimeFormat(time)) {
|
|
154
|
-
return {
|
|
155
|
-
content: [
|
|
156
|
-
{
|
|
157
|
-
type: 'text',
|
|
158
|
-
text: `✗ Invalid time format: ${time}. Please use 24-hour format (HH:MM).`,
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
isError: true,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Parse the time and create a date object for today in source timezone
|
|
166
|
-
const [hours, minutes] = time.split(':').map(Number);
|
|
167
|
-
const now = new Date();
|
|
168
|
-
|
|
169
|
-
// Create a date object representing the time in source timezone
|
|
170
|
-
const sourceDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes);
|
|
171
|
-
|
|
172
|
-
// Format the source time to get the correct representation
|
|
173
|
-
const sourceFormatter = new Intl.DateTimeFormat('en-US', {
|
|
174
|
-
timeZone: source_timezone,
|
|
175
|
-
year: 'numeric',
|
|
176
|
-
month: '2-digit',
|
|
177
|
-
day: '2-digit',
|
|
178
|
-
hour: '2-digit',
|
|
179
|
-
minute: '2-digit',
|
|
180
|
-
second: '2-digit',
|
|
181
|
-
hour12: false,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
const sourceParts = sourceFormatter.formatToParts(sourceDate);
|
|
185
|
-
const sourceFormatObject: Record<string, string> = {};
|
|
186
|
-
for (const part of sourceParts) {
|
|
187
|
-
sourceFormatObject[part.type] = part.value;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Convert to target timezone
|
|
191
|
-
const targetFormatter = new Intl.DateTimeFormat('en-US', {
|
|
192
|
-
timeZone: target_timezone,
|
|
193
|
-
year: 'numeric',
|
|
194
|
-
month: 'long',
|
|
195
|
-
day: 'numeric',
|
|
196
|
-
hour: '2-digit',
|
|
197
|
-
minute: '2-digit',
|
|
198
|
-
second: '2-digit',
|
|
199
|
-
timeZoneName: 'long',
|
|
200
|
-
hour12: false,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
const targetTime24 = new Intl.DateTimeFormat('en-US', {
|
|
204
|
-
timeZone: target_timezone,
|
|
205
|
-
hour: '2-digit',
|
|
206
|
-
minute: '2-digit',
|
|
207
|
-
hour12: false,
|
|
208
|
-
}).format(sourceDate);
|
|
209
|
-
|
|
210
|
-
const targetParts = targetFormatter.formatToParts(sourceDate);
|
|
211
|
-
const targetFormatObject: Record<string, string> = {};
|
|
212
|
-
for (const part of targetParts) {
|
|
213
|
-
targetFormatObject[part.type] = part.value;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const targetDate = new Date(sourceDate.toLocaleString('en-US', { timeZone: target_timezone }));
|
|
217
|
-
const timeDiffMs = targetDate.getTime() - sourceDate.getTime();
|
|
218
|
-
const timeDiffHours = Math.round(timeDiffMs / (1000 * 60 * 60));
|
|
219
|
-
|
|
220
|
-
Logger.info(`Converted time from ${source_timezone} to ${target_timezone}`);
|
|
221
|
-
return {
|
|
222
|
-
content: [
|
|
223
|
-
{
|
|
224
|
-
type: 'text',
|
|
225
|
-
text: JSON.stringify(
|
|
226
|
-
{
|
|
227
|
-
conversion: {
|
|
228
|
-
source: {
|
|
229
|
-
timezone: source_timezone,
|
|
230
|
-
time: time,
|
|
231
|
-
formatted: sourceFormatter.format(sourceDate),
|
|
232
|
-
},
|
|
233
|
-
target: {
|
|
234
|
-
timezone: target_timezone,
|
|
235
|
-
time_24h: targetTime24,
|
|
236
|
-
formatted: targetFormatter.format(sourceDate),
|
|
237
|
-
date: `${targetFormatObject.month} ${targetFormatObject.day}, ${targetFormatObject.year}`,
|
|
238
|
-
timezone_name: targetFormatObject.timeZoneName,
|
|
239
|
-
},
|
|
240
|
-
time_difference_hours: timeDiffHours,
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
null,
|
|
244
|
-
2
|
|
245
|
-
),
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
};
|
|
249
|
-
} catch (error: unknown) {
|
|
250
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
251
|
-
Logger.error('Error converting time', error);
|
|
252
|
-
return {
|
|
253
|
-
content: [
|
|
254
|
-
{
|
|
255
|
-
type: 'text',
|
|
256
|
-
text: `✗ Error converting time: ${errorMessage}`,
|
|
257
|
-
},
|
|
258
|
-
],
|
|
259
|
-
isError: true,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Register all time tools
|
|
265
|
-
export function registerTimeTools(server: McpServer) {
|
|
266
|
-
server.registerTool(
|
|
267
|
-
'get_current_time',
|
|
268
|
-
{
|
|
269
|
-
description: 'Get current time in a specific timezone or system timezone',
|
|
270
|
-
inputSchema: {
|
|
271
|
-
timezone: z
|
|
272
|
-
.string()
|
|
273
|
-
.describe("IANA timezone name (e.g., 'America/New_York', 'Europe/London')"),
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
getCurrentTime
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
server.registerTool(
|
|
280
|
-
'convert_time',
|
|
281
|
-
{
|
|
282
|
-
description: 'Convert time between timezones',
|
|
283
|
-
inputSchema: {
|
|
284
|
-
source_timezone: z.string().describe('Source IANA timezone name'),
|
|
285
|
-
time: z.string().describe('Time in 24-hour format (HH:MM)'),
|
|
286
|
-
target_timezone: z.string().describe('Target IANA timezone name'),
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
convertTime
|
|
290
|
-
);
|
|
291
|
-
}
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import type { InkMonitor } from '../components/benchmark-monitor.js';
|
|
5
|
-
import { DEFAULT_AGENTS } from '../constants/benchmark-constants.js';
|
|
6
|
-
import type { TimingData } from '../types/benchmark.js';
|
|
7
|
-
import { getAgentsDir } from '../utils/paths.js';
|
|
8
|
-
import { ProcessManager } from '../utils/process-manager.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Get list of agents to run based on user selection
|
|
12
|
-
* Pure function - no state, no side effects (except validation errors)
|
|
13
|
-
*/
|
|
14
|
-
export async function getAgentList(agentsOption: string): Promise<string[]> {
|
|
15
|
-
if (agentsOption === 'all') {
|
|
16
|
-
return DEFAULT_AGENTS;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const selectedAgents = agentsOption.split(',').map((a) => a.trim());
|
|
20
|
-
|
|
21
|
-
// Validate selected agents
|
|
22
|
-
for (const agent of selectedAgents) {
|
|
23
|
-
if (!DEFAULT_AGENTS.includes(agent)) {
|
|
24
|
-
throw new Error(`Invalid agent: ${agent}. Available agents: ${DEFAULT_AGENTS.join(', ')}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return selectedAgents;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Run a single agent with the given task
|
|
33
|
-
*/
|
|
34
|
-
export async function runAgent(
|
|
35
|
-
agentName: string,
|
|
36
|
-
outputDir: string,
|
|
37
|
-
taskFile: string,
|
|
38
|
-
contextFile: string | undefined,
|
|
39
|
-
monitor?: InkMonitor,
|
|
40
|
-
_maxRetries = 3,
|
|
41
|
-
timeout = 3600
|
|
42
|
-
): Promise<void> {
|
|
43
|
-
const agentWorkDir = path.join(outputDir, agentName);
|
|
44
|
-
await fs.mkdir(agentWorkDir, { recursive: true });
|
|
45
|
-
|
|
46
|
-
// Load agent prompt
|
|
47
|
-
const agentsDir = getAgentsDir();
|
|
48
|
-
const agentFile = path.join(agentsDir, `${agentName}.md`);
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const agentPrompt = await fs.readFile(agentFile, 'utf-8');
|
|
52
|
-
|
|
53
|
-
// Prepare task content - instruct agent to work in the temp directory
|
|
54
|
-
const taskContent = await fs.readFile(taskFile, 'utf-8');
|
|
55
|
-
let fullTask = taskContent;
|
|
56
|
-
|
|
57
|
-
if (contextFile) {
|
|
58
|
-
try {
|
|
59
|
-
const contextContent = await fs.readFile(contextFile, 'utf-8');
|
|
60
|
-
fullTask = `CONTEXT:\n${contextContent}\n\nTASK:\n${taskContent}`;
|
|
61
|
-
} catch (_error) {
|
|
62
|
-
// Silently handle context file errors
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Add instruction to work in the temp directory
|
|
67
|
-
// FUNCTIONAL: Use template string instead of +=
|
|
68
|
-
const finalTask = `${fullTask}\n\nIMPORTANT: Please implement your solution in the current working directory: ${agentWorkDir}\nThis is a temporary directory for testing, so you can create files freely without affecting any production codebase.`;
|
|
69
|
-
|
|
70
|
-
// Run Claude Code with the agent prompt
|
|
71
|
-
await runSingleAgent(agentName, agentPrompt, finalTask, agentWorkDir, monitor, timeout);
|
|
72
|
-
} catch (error) {
|
|
73
|
-
throw new Error(`Failed to load agent ${agentName}: ${error}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Internal helper: Run a single agent process
|
|
79
|
-
* Handles process spawning, monitoring, and cleanup
|
|
80
|
-
*/
|
|
81
|
-
async function runSingleAgent(
|
|
82
|
-
agentName: string,
|
|
83
|
-
agentPrompt: string,
|
|
84
|
-
fullTask: string,
|
|
85
|
-
agentWorkDir: string,
|
|
86
|
-
monitor?: InkMonitor,
|
|
87
|
-
timeout = 3600
|
|
88
|
-
): Promise<void> {
|
|
89
|
-
// Write agent prompt to a temp file to avoid command line length limits
|
|
90
|
-
const tempPromptFile = path.join(agentWorkDir, '.agent-prompt.md');
|
|
91
|
-
await fs.writeFile(tempPromptFile, agentPrompt);
|
|
92
|
-
|
|
93
|
-
return new Promise((resolve, reject) => {
|
|
94
|
-
let timeoutId: NodeJS.Timeout | undefined;
|
|
95
|
-
// Set up timeout
|
|
96
|
-
timeoutId = setTimeout(() => {
|
|
97
|
-
if (claudeProcess && !claudeProcess.killed) {
|
|
98
|
-
claudeProcess.kill('SIGTERM');
|
|
99
|
-
// Force kill if it doesn't stop after 5 seconds
|
|
100
|
-
setTimeout(() => {
|
|
101
|
-
if (!claudeProcess.killed) {
|
|
102
|
-
claudeProcess.kill('SIGKILL');
|
|
103
|
-
}
|
|
104
|
-
}, 5000);
|
|
105
|
-
}
|
|
106
|
-
// Update agent status to error due to timeout
|
|
107
|
-
monitor?.updateAgentStatus(agentName, 'error');
|
|
108
|
-
reject(new Error(`Agent ${agentName} timed out after ${timeout} seconds`));
|
|
109
|
-
}, timeout * 1000);
|
|
110
|
-
|
|
111
|
-
const claudeProcess = spawn(
|
|
112
|
-
'claude',
|
|
113
|
-
[
|
|
114
|
-
'--system-prompt',
|
|
115
|
-
`@${tempPromptFile}`,
|
|
116
|
-
'--dangerously-skip-permissions',
|
|
117
|
-
'--output-format',
|
|
118
|
-
'stream-json',
|
|
119
|
-
'--verbose',
|
|
120
|
-
fullTask,
|
|
121
|
-
],
|
|
122
|
-
{
|
|
123
|
-
cwd: agentWorkDir,
|
|
124
|
-
stdio: ['inherit', 'pipe', 'pipe'],
|
|
125
|
-
env: {
|
|
126
|
-
...process.env,
|
|
127
|
-
// Disable buffering and progress indicators for real-time output
|
|
128
|
-
FORCE_NO_PROGRESS: '1',
|
|
129
|
-
CI: '1',
|
|
130
|
-
PYTHONUNBUFFERED: '1',
|
|
131
|
-
},
|
|
132
|
-
}
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
// Set the process PID for debugging
|
|
136
|
-
if (claudeProcess.pid) {
|
|
137
|
-
monitor?.setAgentPid(agentName, claudeProcess.pid);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Track this child process for cleanup
|
|
141
|
-
ProcessManager.getInstance().trackChildProcess(claudeProcess);
|
|
142
|
-
|
|
143
|
-
// FUNCTIONAL: Use arrays for immutable buffer accumulation
|
|
144
|
-
const stdoutChunks: string[] = [];
|
|
145
|
-
const stderrChunks: string[] = [];
|
|
146
|
-
let incompleteStdoutLine = '';
|
|
147
|
-
|
|
148
|
-
claudeProcess.stdout?.on('data', (data) => {
|
|
149
|
-
const output = data.toString();
|
|
150
|
-
stdoutChunks.push(output);
|
|
151
|
-
|
|
152
|
-
// Process complete lines only - keep incomplete data in buffer
|
|
153
|
-
const combined = incompleteStdoutLine + output;
|
|
154
|
-
const lines = combined.split('\n');
|
|
155
|
-
incompleteStdoutLine = lines.pop() || ''; // Keep last incomplete line
|
|
156
|
-
|
|
157
|
-
for (const line of lines) {
|
|
158
|
-
if (!line.trim()) {
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const jsonData = JSON.parse(line);
|
|
164
|
-
|
|
165
|
-
if (jsonData.type === 'assistant' && jsonData.message?.content) {
|
|
166
|
-
// Extract text content and tool uses from assistant message
|
|
167
|
-
for (const content of jsonData.message.content) {
|
|
168
|
-
if (content.type === 'text') {
|
|
169
|
-
const textContent = content.text.trim();
|
|
170
|
-
if (textContent) {
|
|
171
|
-
monitor?.addAgentOutput(agentName, textContent);
|
|
172
|
-
}
|
|
173
|
-
} else if (content.type === 'tool_use') {
|
|
174
|
-
const toolName = content.name;
|
|
175
|
-
const params = content.input || {};
|
|
176
|
-
|
|
177
|
-
// Simple tool display for benchmark output
|
|
178
|
-
const paramsStr = JSON.stringify(params);
|
|
179
|
-
const toolDisplay = paramsStr.length > 80
|
|
180
|
-
? `${toolName}(${paramsStr.substring(0, 77)}...)`
|
|
181
|
-
: `${toolName}(${paramsStr})`;
|
|
182
|
-
|
|
183
|
-
monitor?.addAgentOutput(agentName, toolDisplay);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
} catch (_e) {
|
|
188
|
-
// Skip invalid JSON (shouldn't happen with stream-json)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Don't output directly to console when using React+Ink monitor
|
|
193
|
-
// The monitor will handle displaying relevant output
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
claudeProcess.stderr?.on('data', (data) => {
|
|
197
|
-
const output = data.toString();
|
|
198
|
-
stderrChunks.push(output);
|
|
199
|
-
|
|
200
|
-
// Add error output to monitor (with ANSI cleaning)
|
|
201
|
-
monitor?.addAgentOutput(agentName, `ERROR: ${output}`);
|
|
202
|
-
|
|
203
|
-
// Don't output directly to console when using React+Ink monitor
|
|
204
|
-
// The monitor will handle displaying relevant output
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
claudeProcess.on('close', async (code) => {
|
|
208
|
-
const endTime = Date.now();
|
|
209
|
-
|
|
210
|
-
// Update agent end time
|
|
211
|
-
const agent = monitor?.getAgents().get(agentName);
|
|
212
|
-
if (agent) {
|
|
213
|
-
agent.endTime = endTime;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Clear timeout if process completed normally
|
|
217
|
-
if (timeoutId) {
|
|
218
|
-
clearTimeout(timeoutId);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// All data should already be processed in stdout event handler
|
|
222
|
-
|
|
223
|
-
// Clean up temp prompt file
|
|
224
|
-
try {
|
|
225
|
-
await fs.unlink(tempPromptFile);
|
|
226
|
-
} catch (_error) {
|
|
227
|
-
// Ignore cleanup errors
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Write execution log with timing information
|
|
231
|
-
// FUNCTIONAL: Join arrays at the end instead of accumulating with +=
|
|
232
|
-
const stdoutFinal = stdoutChunks.join('');
|
|
233
|
-
const stderrFinal = stderrChunks.join('');
|
|
234
|
-
const executionLog = `Execution completed at: ${new Date(endTime).toISOString()}\nExit code: ${code}\n\n=== STDOUT ===\n${stdoutFinal}\n\n=== STDERR ===\n${stderrFinal}\n`;
|
|
235
|
-
await fs.writeFile(path.join(agentWorkDir, 'execution-log.txt'), executionLog);
|
|
236
|
-
|
|
237
|
-
// Write timing metadata
|
|
238
|
-
const timingData: TimingData = {
|
|
239
|
-
endTime,
|
|
240
|
-
exitCode: code,
|
|
241
|
-
stdoutLength: stdoutFinal.length,
|
|
242
|
-
stderrLength: stderrFinal.length,
|
|
243
|
-
};
|
|
244
|
-
await fs.writeFile(
|
|
245
|
-
path.join(agentWorkDir, 'timing.json'),
|
|
246
|
-
JSON.stringify(timingData, null, 2)
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
// Update agent status based on exit code
|
|
250
|
-
if (code === 0) {
|
|
251
|
-
monitor?.updateAgentStatus(agentName, 'completed');
|
|
252
|
-
resolve();
|
|
253
|
-
} else {
|
|
254
|
-
monitor?.updateAgentStatus(agentName, 'error');
|
|
255
|
-
await fs.writeFile(path.join(agentWorkDir, 'execution-error.txt'), stderrFinal);
|
|
256
|
-
|
|
257
|
-
reject(new Error(`Agent ${agentName} failed with code ${code}`));
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
claudeProcess.on('error', (error) => {
|
|
262
|
-
// Clear timeout on error
|
|
263
|
-
if (timeoutId) {
|
|
264
|
-
clearTimeout(timeoutId);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Update agent status to error
|
|
268
|
-
monitor?.updateAgentStatus(agentName, 'error');
|
|
269
|
-
|
|
270
|
-
reject(error);
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
}
|