@sylphx/flow 1.0.1 → 1.0.3
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 +12 -0
- package/package.json +10 -9
- package/src/commands/codebase-command.ts +168 -0
- package/src/commands/flow-command.ts +1137 -0
- package/src/commands/flow-orchestrator.ts +296 -0
- package/src/commands/hook-command.ts +444 -0
- package/src/commands/init-command.ts +92 -0
- package/src/commands/init-core.ts +322 -0
- package/src/commands/knowledge-command.ts +161 -0
- package/src/commands/run-command.ts +120 -0
- package/src/components/benchmark-monitor.tsx +331 -0
- package/src/components/reindex-progress.tsx +261 -0
- package/src/composables/functional/index.ts +14 -0
- package/src/composables/functional/useEnvironment.ts +171 -0
- package/src/composables/functional/useFileSystem.ts +139 -0
- package/src/composables/index.ts +5 -0
- package/src/composables/useEnv.ts +13 -0
- package/src/composables/useRuntimeConfig.ts +27 -0
- package/src/composables/useTargetConfig.ts +45 -0
- package/src/config/ai-config.ts +376 -0
- package/src/config/constants.ts +35 -0
- package/src/config/index.ts +27 -0
- package/src/config/rules.ts +43 -0
- package/src/config/servers.ts +371 -0
- package/src/config/targets.ts +126 -0
- package/src/core/agent-loader.ts +141 -0
- package/src/core/agent-manager.ts +174 -0
- package/src/core/ai-sdk.ts +603 -0
- package/src/core/app-factory.ts +381 -0
- package/src/core/builtin-agents.ts +9 -0
- package/src/core/command-system.ts +550 -0
- package/src/core/config-system.ts +550 -0
- package/src/core/connection-pool.ts +390 -0
- package/src/core/di-container.ts +155 -0
- package/src/core/error-handling.ts +519 -0
- package/src/core/formatting/bytes.test.ts +115 -0
- package/src/core/formatting/bytes.ts +64 -0
- package/src/core/functional/async.ts +313 -0
- package/src/core/functional/either.ts +109 -0
- package/src/core/functional/error-handler.ts +135 -0
- package/src/core/functional/error-types.ts +311 -0
- package/src/core/functional/index.ts +19 -0
- package/src/core/functional/option.ts +142 -0
- package/src/core/functional/pipe.ts +189 -0
- package/src/core/functional/result.ts +204 -0
- package/src/core/functional/validation.ts +138 -0
- package/src/core/headless-display.ts +96 -0
- package/src/core/index.ts +6 -0
- package/src/core/installers/file-installer.ts +303 -0
- package/src/core/installers/mcp-installer.ts +213 -0
- package/src/core/interfaces/index.ts +22 -0
- package/src/core/interfaces/repository.interface.ts +91 -0
- package/src/core/interfaces/service.interface.ts +133 -0
- package/src/core/interfaces.ts +129 -0
- package/src/core/loop-controller.ts +200 -0
- package/src/core/result.ts +351 -0
- package/src/core/rule-loader.ts +147 -0
- package/src/core/rule-manager.ts +240 -0
- package/src/core/service-config.ts +252 -0
- package/src/core/session-service.ts +121 -0
- package/src/core/state-detector.ts +389 -0
- package/src/core/storage-factory.ts +115 -0
- package/src/core/stream-handler.ts +288 -0
- package/src/core/target-manager.ts +161 -0
- package/src/core/type-utils.ts +427 -0
- package/src/core/unified-storage.ts +456 -0
- package/src/core/upgrade-manager.ts +300 -0
- package/src/core/validation/limit.test.ts +155 -0
- package/src/core/validation/limit.ts +46 -0
- package/src/core/validation/query.test.ts +44 -0
- package/src/core/validation/query.ts +20 -0
- package/src/db/auto-migrate.ts +322 -0
- package/src/db/base-database-client.ts +144 -0
- package/src/db/cache-db.ts +218 -0
- package/src/db/cache-schema.ts +75 -0
- package/src/db/database.ts +70 -0
- package/src/db/index.ts +252 -0
- package/src/db/memory-db.ts +153 -0
- package/src/db/memory-schema.ts +29 -0
- package/src/db/schema.ts +289 -0
- package/src/db/session-repository.ts +733 -0
- package/src/domains/codebase/index.ts +5 -0
- package/src/domains/codebase/tools.ts +139 -0
- package/src/domains/index.ts +8 -0
- package/src/domains/knowledge/index.ts +10 -0
- package/src/domains/knowledge/resources.ts +537 -0
- package/src/domains/knowledge/tools.ts +174 -0
- package/src/domains/utilities/index.ts +6 -0
- package/src/domains/utilities/time/index.ts +5 -0
- package/src/domains/utilities/time/tools.ts +291 -0
- package/src/index.ts +211 -0
- package/src/services/agent-service.ts +273 -0
- package/src/services/claude-config-service.ts +252 -0
- package/src/services/config-service.ts +258 -0
- package/src/services/evaluation-service.ts +271 -0
- package/src/services/functional/evaluation-logic.ts +296 -0
- package/src/services/functional/file-processor.ts +273 -0
- package/src/services/functional/index.ts +12 -0
- package/src/services/index.ts +13 -0
- package/src/services/mcp-service.ts +432 -0
- package/src/services/memory.service.ts +476 -0
- package/src/services/search/base-indexer.ts +156 -0
- package/src/services/search/codebase-indexer-types.ts +38 -0
- package/src/services/search/codebase-indexer.ts +647 -0
- package/src/services/search/embeddings-provider.ts +455 -0
- package/src/services/search/embeddings.ts +316 -0
- package/src/services/search/functional-indexer.ts +323 -0
- package/src/services/search/index.ts +27 -0
- package/src/services/search/indexer.ts +380 -0
- package/src/services/search/knowledge-indexer.ts +422 -0
- package/src/services/search/semantic-search.ts +244 -0
- package/src/services/search/tfidf.ts +559 -0
- package/src/services/search/unified-search-service.ts +888 -0
- package/src/services/smart-config-service.ts +385 -0
- package/src/services/storage/cache-storage.ts +487 -0
- package/src/services/storage/drizzle-storage.ts +581 -0
- package/src/services/storage/index.ts +15 -0
- package/src/services/storage/lancedb-vector-storage.ts +494 -0
- package/src/services/storage/memory-storage.ts +268 -0
- package/src/services/storage/separated-storage.ts +467 -0
- package/src/services/storage/vector-storage.ts +13 -0
- package/src/shared/agents/index.ts +63 -0
- package/src/shared/files/index.ts +99 -0
- package/src/shared/index.ts +32 -0
- package/src/shared/logging/index.ts +24 -0
- package/src/shared/processing/index.ts +153 -0
- package/src/shared/types/index.ts +25 -0
- package/src/targets/claude-code.ts +574 -0
- package/src/targets/functional/claude-code-logic.ts +185 -0
- package/src/targets/functional/index.ts +6 -0
- package/src/targets/opencode.ts +529 -0
- package/src/types/agent.types.ts +32 -0
- package/src/types/api/batch.ts +108 -0
- package/src/types/api/errors.ts +118 -0
- package/src/types/api/index.ts +55 -0
- package/src/types/api/requests.ts +76 -0
- package/src/types/api/responses.ts +180 -0
- package/src/types/api/websockets.ts +85 -0
- package/src/types/api.types.ts +9 -0
- package/src/types/benchmark.ts +49 -0
- package/src/types/cli.types.ts +87 -0
- package/src/types/common.types.ts +35 -0
- package/src/types/database.types.ts +510 -0
- package/src/types/mcp-config.types.ts +448 -0
- package/src/types/mcp.types.ts +69 -0
- package/src/types/memory-types.ts +63 -0
- package/src/types/provider.types.ts +28 -0
- package/src/types/rule.types.ts +24 -0
- package/src/types/session.types.ts +214 -0
- package/src/types/target-config.types.ts +295 -0
- package/src/types/target.types.ts +140 -0
- package/src/types/todo.types.ts +25 -0
- package/src/types.ts +40 -0
- package/src/utils/advanced-tokenizer.ts +191 -0
- package/src/utils/agent-enhancer.ts +114 -0
- package/src/utils/ai-model-fetcher.ts +19 -0
- package/src/utils/async-file-operations.ts +516 -0
- package/src/utils/audio-player.ts +345 -0
- package/src/utils/cli-output.ts +266 -0
- package/src/utils/codebase-helpers.ts +211 -0
- package/src/utils/console-ui.ts +79 -0
- package/src/utils/database-errors.ts +140 -0
- package/src/utils/debug-logger.ts +49 -0
- package/src/utils/error-handler.ts +53 -0
- package/src/utils/file-operations.ts +310 -0
- package/src/utils/file-scanner.ts +259 -0
- package/src/utils/functional/array.ts +355 -0
- package/src/utils/functional/index.ts +15 -0
- package/src/utils/functional/object.ts +279 -0
- package/src/utils/functional/string.ts +281 -0
- package/src/utils/functional.ts +543 -0
- package/src/utils/help.ts +20 -0
- package/src/utils/immutable-cache.ts +106 -0
- package/src/utils/index.ts +78 -0
- package/src/utils/jsonc.ts +158 -0
- package/src/utils/logger.ts +396 -0
- package/src/utils/mcp-config.ts +249 -0
- package/src/utils/memory-tui.ts +414 -0
- package/src/utils/models-dev.ts +91 -0
- package/src/utils/notifications.ts +169 -0
- package/src/utils/object-utils.ts +51 -0
- package/src/utils/parallel-operations.ts +487 -0
- package/src/utils/paths.ts +143 -0
- package/src/utils/process-manager.ts +155 -0
- package/src/utils/prompts.ts +120 -0
- package/src/utils/search-tool-builder.ts +214 -0
- package/src/utils/secret-utils.ts +179 -0
- package/src/utils/security.ts +537 -0
- package/src/utils/session-manager.ts +168 -0
- package/src/utils/session-title.ts +87 -0
- package/src/utils/settings.ts +182 -0
- package/src/utils/simplified-errors.ts +410 -0
- package/src/utils/sync-utils.ts +159 -0
- package/src/utils/target-config.ts +570 -0
- package/src/utils/target-utils.ts +394 -0
- package/src/utils/template-engine.ts +94 -0
- package/src/utils/test-audio.ts +71 -0
- package/src/utils/todo-context.ts +46 -0
- package/src/utils/token-counter.ts +288 -0
- package/dist/index.d.ts +0 -10
- package/dist/index.js +0 -59554
- package/dist/lancedb.linux-x64-gnu-b7f0jgsz.node +0 -0
- package/dist/lancedb.linux-x64-musl-tgcv22rx.node +0 -0
- package/dist/shared/chunk-25dwp0dp.js +0 -89
- package/dist/shared/chunk-3pjb6063.js +0 -208
- package/dist/shared/chunk-4d6ydpw7.js +0 -2854
- package/dist/shared/chunk-4wjcadjk.js +0 -225
- package/dist/shared/chunk-5j4w74t6.js +0 -30
- package/dist/shared/chunk-5j8m3dh3.js +0 -58
- package/dist/shared/chunk-5thh3qem.js +0 -91
- package/dist/shared/chunk-6g9xy73m.js +0 -252
- package/dist/shared/chunk-7eq34c42.js +0 -23
- package/dist/shared/chunk-c2gwgx3r.js +0 -115
- package/dist/shared/chunk-cjd3mk4c.js +0 -1320
- package/dist/shared/chunk-g5cv6703.js +0 -368
- package/dist/shared/chunk-hpkhykhq.js +0 -574
- package/dist/shared/chunk-m2322pdk.js +0 -122
- package/dist/shared/chunk-nd5fdvaq.js +0 -26
- package/dist/shared/chunk-pgd3m6zf.js +0 -108
- package/dist/shared/chunk-qk8n91hw.js +0 -494
- package/dist/shared/chunk-rkkn8szp.js +0 -16855
- package/dist/shared/chunk-t16rfxh0.js +0 -61
- package/dist/shared/chunk-t4fbfa5v.js +0 -19
- package/dist/shared/chunk-t77h86w6.js +0 -276
- package/dist/shared/chunk-v0ez4aef.js +0 -71
- package/dist/shared/chunk-v29j2r3s.js +0 -32051
- package/dist/shared/chunk-vfbc6ew5.js +0 -765
- package/dist/shared/chunk-vmeqwm1c.js +0 -204
- package/dist/shared/chunk-x66eh37x.js +0 -137
|
@@ -0,0 +1,273 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Configuration Service
|
|
3
|
+
* Handles Claude Code provider configuration with layered settings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { ConfigService } from './config-service.js';
|
|
9
|
+
import { loadAllAgents } from '../core/agent-loader.js';
|
|
10
|
+
|
|
11
|
+
export interface ClaudeConfig {
|
|
12
|
+
claudeProvider?: string;
|
|
13
|
+
claudeProviderConfig?: {
|
|
14
|
+
ANTHROPIC_BASE_URL: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
claudeApiKey?: string;
|
|
18
|
+
defaultAgent?: string;
|
|
19
|
+
target?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ClaudeConfigService {
|
|
23
|
+
/**
|
|
24
|
+
* Load layered Claude configuration from all sources
|
|
25
|
+
*/
|
|
26
|
+
static async loadConfig(): Promise<ClaudeConfig> {
|
|
27
|
+
// API keys are in home directory
|
|
28
|
+
const userSettings = await ConfigService.loadHomeSettings();
|
|
29
|
+
|
|
30
|
+
// Other settings are in project directory
|
|
31
|
+
const projectSettings = await ConfigService.loadProjectSettings();
|
|
32
|
+
|
|
33
|
+
// Merge with project settings taking precedence over home (except API key)
|
|
34
|
+
const merged = {
|
|
35
|
+
claudeProvider: userSettings.claudeProvider || projectSettings.claudeProvider,
|
|
36
|
+
claudeProviderConfig: userSettings.claudeProviderConfig || projectSettings.claudeProviderConfig,
|
|
37
|
+
claudeApiKey: userSettings.claudeApiKey,
|
|
38
|
+
defaultAgent: projectSettings.defaultAgent,
|
|
39
|
+
target: projectSettings.target,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Local settings have highest priority for everything except API key
|
|
43
|
+
const localSettings = await ConfigService.loadLocalSettings();
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...merged,
|
|
47
|
+
...localSettings,
|
|
48
|
+
// Keep API key from user settings
|
|
49
|
+
claudeApiKey: merged.claudeApiKey,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Save user-specific config (API keys to home, project settings to project)
|
|
55
|
+
*/
|
|
56
|
+
static async saveConfig(config: ClaudeConfig): Promise<void> {
|
|
57
|
+
// Separate user-specific settings (API keys)
|
|
58
|
+
const userSettings = {
|
|
59
|
+
claudeApiKey: config.claudeApiKey,
|
|
60
|
+
claudeProvider: config.claudeProvider,
|
|
61
|
+
claudeProviderConfig: config.claudeProviderConfig,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Other settings go to project (shareable)
|
|
65
|
+
const projectSettings = {
|
|
66
|
+
claudeProvider: config.claudeProvider,
|
|
67
|
+
defaultAgent: config.defaultAgent,
|
|
68
|
+
target: config.target,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Save API keys to home directory (never commit)
|
|
72
|
+
if (userSettings.claudeApiKey) {
|
|
73
|
+
await ConfigService.saveHomeSettings(userSettings);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Save project settings
|
|
77
|
+
await ConfigService.saveProjectSettings(projectSettings);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Configure Claude provider interactively - saves API keys to home dir
|
|
82
|
+
*/
|
|
83
|
+
static async configureProvider(verbose: boolean = false): Promise<ClaudeConfig> {
|
|
84
|
+
const config = await this.loadConfig();
|
|
85
|
+
|
|
86
|
+
// Check if we already have API key configured
|
|
87
|
+
if (!config.claudeApiKey || !config.claudeProvider || verbose) {
|
|
88
|
+
console.log(chalk.cyan('📋 Claude Code Configuration\n'));
|
|
89
|
+
|
|
90
|
+
const providerAnswer = await inquirer.prompt([
|
|
91
|
+
{
|
|
92
|
+
type: 'list',
|
|
93
|
+
name: 'provider',
|
|
94
|
+
message: 'Select Claude API Provider:',
|
|
95
|
+
choices: [
|
|
96
|
+
{ name: 'Anthropic (Official)', value: 'anthropic' },
|
|
97
|
+
{ name: 'Z.ai (Recommended)', value: 'z.ai' },
|
|
98
|
+
{ name: 'Kimi', value: 'kimi' },
|
|
99
|
+
],
|
|
100
|
+
default: config.claudeProvider || 'z.ai',
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
// Ask for API Key
|
|
105
|
+
const keyAnswer = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: 'password',
|
|
108
|
+
name: 'apiKey',
|
|
109
|
+
message: `Enter API Key for ${providerAnswer.provider}:`,
|
|
110
|
+
mask: '*',
|
|
111
|
+
validate: (input) => input.length > 10 || 'API Key appears too short',
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
// Provider configurations
|
|
116
|
+
const providerEnvs = {
|
|
117
|
+
'anthropic': {
|
|
118
|
+
ANTHROPIC_BASE_URL: 'https://api.anthropic.com',
|
|
119
|
+
description: 'Anthropic Official API',
|
|
120
|
+
},
|
|
121
|
+
'z.ai': {
|
|
122
|
+
ANTHROPIC_BASE_URL: 'https://api.z.ai/api/anthropic',
|
|
123
|
+
description: 'Z.ai Proxy',
|
|
124
|
+
},
|
|
125
|
+
'kimi': {
|
|
126
|
+
ANTHROPIC_BASE_URL: 'https://api.kimi.com/coding/',
|
|
127
|
+
description: 'Kimi Proxy',
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const providerConfig = providerEnvs[providerAnswer.provider as keyof typeof providerEnvs];
|
|
132
|
+
|
|
133
|
+
// Save API keys to home directory (never commit)
|
|
134
|
+
await ConfigService.saveHomeSettings({
|
|
135
|
+
claudeProvider: providerAnswer.provider,
|
|
136
|
+
claudeProviderConfig: providerConfig,
|
|
137
|
+
claudeApiKey: keyAnswer.apiKey,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
console.log(chalk.green(`✓ API Key saved to ~/.sylphx-flow/settings.json (secure)\n`));
|
|
141
|
+
console.log(chalk.dim(` Provider: ${providerConfig.description}`));
|
|
142
|
+
console.log(chalk.dim(` API Key: ${keyAnswer.apiKey.slice(0, 5)}...${keyAnswer.apiKey.slice(-4)}\n`));
|
|
143
|
+
|
|
144
|
+
// Update config for return
|
|
145
|
+
config.claudeProvider = providerAnswer.provider;
|
|
146
|
+
config.claudeProviderConfig = providerConfig;
|
|
147
|
+
config.claudeApiKey = keyAnswer.apiKey;
|
|
148
|
+
|
|
149
|
+
return config;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (verbose) {
|
|
153
|
+
console.log(chalk.green('✓ Claude provider already configured\n'));
|
|
154
|
+
console.log(chalk.dim(` Provider: ${config.claudeProviderConfig?.description}\n`));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return config;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Configure agent interactively - saves to project config (shareable)
|
|
162
|
+
* Dynamically loads available agents instead of hardcoded list
|
|
163
|
+
*/
|
|
164
|
+
static async configureAgent(verbose: boolean = false): Promise<string> {
|
|
165
|
+
const config = await this.loadConfig();
|
|
166
|
+
|
|
167
|
+
if (!config.defaultAgent || verbose) {
|
|
168
|
+
try {
|
|
169
|
+
// Dynamically load all available agents
|
|
170
|
+
const agents = await loadAllAgents(process.cwd());
|
|
171
|
+
|
|
172
|
+
if (agents.length === 0) {
|
|
173
|
+
console.log(chalk.yellow('⚠ No agents found. Defaulting to "coder".\n'));
|
|
174
|
+
const defaultAgent = 'coder';
|
|
175
|
+
await ConfigService.saveProjectSettings({
|
|
176
|
+
defaultAgent,
|
|
177
|
+
});
|
|
178
|
+
return defaultAgent;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Create choices from dynamically loaded agents
|
|
182
|
+
const choices = agents.map(agent => ({
|
|
183
|
+
name: agent.metadata.name || agent.id,
|
|
184
|
+
value: agent.id,
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
const agentAnswer = await inquirer.prompt([
|
|
188
|
+
{
|
|
189
|
+
type: 'list',
|
|
190
|
+
name: 'agent',
|
|
191
|
+
message: 'Select default Agent:',
|
|
192
|
+
choices,
|
|
193
|
+
default: config.defaultAgent || (agents.find(a => a.id === 'coder')?.id || agents[0].id),
|
|
194
|
+
},
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
// Save to project-level config (shareable)
|
|
198
|
+
await ConfigService.saveProjectSettings({
|
|
199
|
+
defaultAgent: agentAnswer.agent,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const selectedAgent = agents.find(a => a.id === agentAnswer.agent);
|
|
203
|
+
const displayName = selectedAgent?.metadata.name || agentAnswer.agent;
|
|
204
|
+
console.log(chalk.green(`✓ Agent set to: ${displayName}\n`));
|
|
205
|
+
|
|
206
|
+
return agentAnswer.agent;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.log(chalk.yellow('⚠ Failed to load agents. Defaulting to "coder".\n'));
|
|
209
|
+
console.error(error);
|
|
210
|
+
|
|
211
|
+
const defaultAgent = 'coder';
|
|
212
|
+
await ConfigService.saveProjectSettings({
|
|
213
|
+
defaultAgent,
|
|
214
|
+
});
|
|
215
|
+
return defaultAgent;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return config.defaultAgent;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Setup environment variables for Claude Code
|
|
224
|
+
*/
|
|
225
|
+
static async setupEnvironment(verbose: boolean = false): Promise<void> {
|
|
226
|
+
const config = await this.loadConfig();
|
|
227
|
+
|
|
228
|
+
if (!config.claudeProviderConfig) {
|
|
229
|
+
throw new Error('Provider not configured. Run configureProvider() first.');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Set environment variables
|
|
233
|
+
process.env.ANTHROPIC_BASE_URL = config.claudeProviderConfig.ANTHROPIC_BASE_URL;
|
|
234
|
+
|
|
235
|
+
if (config.claudeApiKey) {
|
|
236
|
+
process.env.ANTHROPIC_API_KEY = config.claudeApiKey;
|
|
237
|
+
|
|
238
|
+
if (verbose) {
|
|
239
|
+
console.log(chalk.dim(` Provider: ${config.claudeProviderConfig.description}`));
|
|
240
|
+
console.log(chalk.dim(` API Key: ${config.claudeApiKey.slice(0, 5)}...${config.claudeApiKey.slice(-4)}\n`));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get the default agent
|
|
247
|
+
*/
|
|
248
|
+
static async getDefaultAgent(): Promise<string> {
|
|
249
|
+
const config = await this.loadConfig();
|
|
250
|
+
return config.defaultAgent || 'coder';
|
|
251
|
+
}
|
|
252
|
+
}
|