@sylphx/flow 2.1.3 → 2.1.5
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 +28 -0
- package/README.md +44 -0
- package/package.json +79 -73
- package/src/commands/flow/execute-v2.ts +37 -29
- package/src/commands/flow/prompt.ts +5 -3
- package/src/commands/flow/types.ts +0 -2
- package/src/commands/flow-command.ts +20 -13
- package/src/commands/hook-command.ts +1 -3
- package/src/commands/settings/checkbox-config.ts +128 -0
- package/src/commands/settings/index.ts +6 -0
- package/src/commands/settings-command.ts +84 -156
- package/src/config/ai-config.ts +60 -41
- package/src/core/agent-loader.ts +11 -6
- package/src/core/attach/file-attacher.ts +172 -0
- package/src/core/attach/index.ts +5 -0
- package/src/core/attach-manager.ts +117 -171
- package/src/core/backup-manager.ts +35 -29
- package/src/core/cleanup-handler.ts +11 -8
- package/src/core/error-handling.ts +23 -30
- package/src/core/flow-executor.ts +58 -76
- package/src/core/formatting/bytes.ts +2 -4
- package/src/core/functional/async.ts +5 -4
- package/src/core/functional/error-handler.ts +2 -2
- package/src/core/git-stash-manager.ts +21 -10
- package/src/core/installers/file-installer.ts +0 -1
- package/src/core/installers/mcp-installer.ts +0 -1
- package/src/core/project-manager.ts +24 -18
- package/src/core/secrets-manager.ts +54 -73
- package/src/core/session-manager.ts +20 -22
- package/src/core/state-detector.ts +139 -80
- package/src/core/template-loader.ts +13 -31
- package/src/core/upgrade-manager.ts +122 -69
- package/src/index.ts +8 -5
- package/src/services/auto-upgrade.ts +1 -1
- package/src/services/config-service.ts +41 -29
- package/src/services/global-config.ts +3 -3
- package/src/services/target-installer.ts +11 -26
- package/src/targets/claude-code.ts +35 -81
- package/src/targets/opencode.ts +28 -68
- package/src/targets/shared/index.ts +7 -0
- package/src/targets/shared/mcp-transforms.ts +132 -0
- package/src/targets/shared/target-operations.ts +135 -0
- package/src/types/cli.types.ts +2 -2
- package/src/types/provider.types.ts +1 -7
- package/src/types/session.types.ts +11 -11
- package/src/types/target.types.ts +3 -1
- package/src/types/todo.types.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/__tests__/package-manager-detector.test.ts +6 -6
- package/src/utils/agent-enhancer.ts +4 -4
- package/src/utils/config/paths.ts +3 -1
- package/src/utils/config/target-utils.ts +2 -2
- package/src/utils/display/banner.ts +2 -2
- package/src/utils/display/notifications.ts +58 -45
- package/src/utils/display/status.ts +29 -12
- package/src/utils/files/file-operations.ts +1 -1
- package/src/utils/files/sync-utils.ts +38 -41
- package/src/utils/index.ts +19 -27
- package/src/utils/package-manager-detector.ts +15 -5
- package/src/utils/security/security.ts +8 -4
- package/src/utils/target-selection.ts +6 -8
- package/src/utils/version.ts +4 -2
- package/src/commands/flow-orchestrator.ts +0 -328
- package/src/commands/init-command.ts +0 -92
- package/src/commands/init-core.ts +0 -331
- package/src/core/agent-manager.ts +0 -174
- package/src/core/loop-controller.ts +0 -200
- package/src/core/rule-loader.ts +0 -147
- package/src/core/rule-manager.ts +0 -240
- package/src/services/claude-config-service.ts +0 -252
- package/src/services/first-run-setup.ts +0 -220
- package/src/services/smart-config-service.ts +0 -269
- package/src/types/api.types.ts +0 -9
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core initialization logic - extracted for reuse without UI coupling
|
|
3
|
-
* Used by both flow command (integrated) and legacy init command (standalone)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import ora from 'ora';
|
|
8
|
-
import { targetManager } from '../core/target-manager.js';
|
|
9
|
-
import { CLIError } from '../utils/error-handler.js';
|
|
10
|
-
import { projectSettings } from '../utils/config/settings.js';
|
|
11
|
-
import { validateTarget } from '../utils/config/target-config.js';
|
|
12
|
-
import { ConfigService } from '../services/config-service.js';
|
|
13
|
-
|
|
14
|
-
export interface InitOptions {
|
|
15
|
-
target?: string;
|
|
16
|
-
verbose?: boolean;
|
|
17
|
-
dryRun?: boolean;
|
|
18
|
-
clear?: boolean;
|
|
19
|
-
mcp?: boolean;
|
|
20
|
-
agents?: boolean;
|
|
21
|
-
rules?: boolean;
|
|
22
|
-
outputStyles?: boolean;
|
|
23
|
-
slashCommands?: boolean;
|
|
24
|
-
hooks?: boolean;
|
|
25
|
-
quiet?: boolean; // Suppress all output for programmatic use
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ComponentInstallResult {
|
|
29
|
-
targetId: string;
|
|
30
|
-
targetName: string;
|
|
31
|
-
installed: {
|
|
32
|
-
mcp?: number;
|
|
33
|
-
agents?: number;
|
|
34
|
-
outputStyles?: number;
|
|
35
|
-
rules?: number;
|
|
36
|
-
slashCommands?: number;
|
|
37
|
-
hooks?: number;
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Select and validate target - PURE LOGIC, no UI
|
|
43
|
-
* @returns targetId
|
|
44
|
-
*/
|
|
45
|
-
export async function selectAndValidateTarget(options: InitOptions): Promise<string> {
|
|
46
|
-
let targetId = options.target;
|
|
47
|
-
|
|
48
|
-
// Target selection (with UI prompt if needed)
|
|
49
|
-
if (!targetId) {
|
|
50
|
-
try {
|
|
51
|
-
targetId = await targetManager.promptForTargetSelection();
|
|
52
|
-
} catch (error) {
|
|
53
|
-
// User cancelled with Ctrl+C - exit gracefully
|
|
54
|
-
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
55
|
-
console.log('\n');
|
|
56
|
-
process.exit(0);
|
|
57
|
-
}
|
|
58
|
-
throw error;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Validate target
|
|
63
|
-
if (targetId) {
|
|
64
|
-
try {
|
|
65
|
-
validateTarget(targetId);
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (error instanceof Error) {
|
|
68
|
-
throw new CLIError(error.message, 'UNSUPPORTED_TARGET');
|
|
69
|
-
}
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!targetId) {
|
|
75
|
-
throw new Error('Target ID not set');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return targetId;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Preview what will be installed in dry run mode
|
|
83
|
-
*/
|
|
84
|
-
export async function previewDryRun(targetId: string, options: InitOptions): Promise<void> {
|
|
85
|
-
const targetOption = targetManager.getTarget(targetId);
|
|
86
|
-
if (targetOption._tag === 'None') {
|
|
87
|
-
throw new Error(`Target not found: ${targetId}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const target = targetOption.value;
|
|
91
|
-
|
|
92
|
-
if (options.mcp !== false && target.setupMCP) {
|
|
93
|
-
console.log(chalk.cyan.bold('MCP Tools:'));
|
|
94
|
-
console.log(chalk.dim(' ✓ MCP servers will be configured'));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (options.agents !== false && target.setupAgents) {
|
|
98
|
-
console.log(chalk.cyan.bold('\nAgents:'));
|
|
99
|
-
console.log(chalk.dim(' ✓ Development agents will be installed'));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (options.outputStyles !== false && target.setupOutputStyles) {
|
|
103
|
-
console.log(chalk.cyan.bold('\nOutput Styles:'));
|
|
104
|
-
console.log(chalk.dim(' ✓ Output styles will be installed'));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (options.rules !== false && target.setupRules) {
|
|
108
|
-
console.log(chalk.cyan.bold('\nRules:'));
|
|
109
|
-
console.log(chalk.dim(' ✓ Custom rules will be installed'));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (options.slashCommands !== false && target.setupSlashCommands) {
|
|
113
|
-
console.log(chalk.cyan.bold('\nSlash Commands:'));
|
|
114
|
-
console.log(chalk.dim(' ✓ Slash commands will be installed'));
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (options.hooks !== false && target.setupHooks) {
|
|
118
|
-
console.log(chalk.cyan.bold('\nHooks:'));
|
|
119
|
-
console.log(chalk.dim(' ✓ Hooks will be configured'));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Install all components - CORE LOGIC with minimal UI
|
|
125
|
-
*/
|
|
126
|
-
export async function installComponents(
|
|
127
|
-
targetId: string,
|
|
128
|
-
options: InitOptions
|
|
129
|
-
): Promise<ComponentInstallResult> {
|
|
130
|
-
const targetOption = targetManager.getTarget(targetId);
|
|
131
|
-
if (targetOption._tag === 'None') {
|
|
132
|
-
throw new Error(`Target not found: ${targetId}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const target = targetOption.value;
|
|
136
|
-
const quiet = options.quiet || false;
|
|
137
|
-
const result: ComponentInstallResult = {
|
|
138
|
-
targetId,
|
|
139
|
-
targetName: target.name,
|
|
140
|
-
installed: {},
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// Setup MCP servers if target supports it and MCP is enabled
|
|
144
|
-
// Note: No spinner here because MCP setup is interactive (user prompts)
|
|
145
|
-
if (target.setupMCP && options.mcp !== false) {
|
|
146
|
-
try {
|
|
147
|
-
const mcpResult = await target.setupMCP(process.cwd(), options);
|
|
148
|
-
result.installed.mcp = mcpResult.count;
|
|
149
|
-
|
|
150
|
-
if (!quiet) {
|
|
151
|
-
if (mcpResult.count > 0) {
|
|
152
|
-
console.log(
|
|
153
|
-
chalk.green(
|
|
154
|
-
`✔ Installed ${chalk.cyan(mcpResult.count)} MCP server${mcpResult.count !== 1 ? 's' : ''}`
|
|
155
|
-
)
|
|
156
|
-
);
|
|
157
|
-
} else {
|
|
158
|
-
console.log(chalk.dim('ℹ No MCP servers selected'));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
} catch (error) {
|
|
162
|
-
// If user cancels MCP setup (Ctrl+C), continue with other components
|
|
163
|
-
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
164
|
-
if (!quiet) {
|
|
165
|
-
console.log(chalk.yellow('\n⚠️ MCP setup cancelled, continuing with other components\n'));
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
if (!quiet) {
|
|
169
|
-
console.error(chalk.red('✖ Failed to setup MCP servers'));
|
|
170
|
-
}
|
|
171
|
-
throw error;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Install agents if target supports it and agents are not skipped
|
|
177
|
-
if (target.setupAgents && options.agents !== false) {
|
|
178
|
-
const agentSpinner = quiet ? null : ora({ text: 'Installing agents', color: 'cyan' }).start();
|
|
179
|
-
try {
|
|
180
|
-
const agentResult = await target.setupAgents(process.cwd(), { ...options, quiet: true, force: options.clear });
|
|
181
|
-
result.installed.agents = agentResult.count;
|
|
182
|
-
|
|
183
|
-
if (agentSpinner) {
|
|
184
|
-
agentSpinner.succeed(
|
|
185
|
-
chalk.green(`Installed ${chalk.cyan(agentResult.count)} agent${agentResult.count !== 1 ? 's' : ''}`)
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
} catch (error) {
|
|
189
|
-
if (agentSpinner) {
|
|
190
|
-
agentSpinner.fail(chalk.red('Failed to install agents'));
|
|
191
|
-
}
|
|
192
|
-
throw error;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Install output styles if target supports it and output styles are not skipped
|
|
197
|
-
if (target.setupOutputStyles && options.outputStyles !== false) {
|
|
198
|
-
const stylesSpinner = quiet ? null : ora({ text: 'Installing output styles', color: 'cyan' }).start();
|
|
199
|
-
try {
|
|
200
|
-
const stylesResult = await target.setupOutputStyles(process.cwd(), { ...options, quiet: true, force: options.clear });
|
|
201
|
-
result.installed.outputStyles = stylesResult.count;
|
|
202
|
-
|
|
203
|
-
if (stylesSpinner) {
|
|
204
|
-
if (stylesResult.count > 0) {
|
|
205
|
-
stylesSpinner.succeed(
|
|
206
|
-
chalk.green(
|
|
207
|
-
`Installed ${chalk.cyan(stylesResult.count)} output style${stylesResult.count !== 1 ? 's' : ''}`
|
|
208
|
-
)
|
|
209
|
-
);
|
|
210
|
-
} else if (stylesResult.message) {
|
|
211
|
-
stylesSpinner.info(chalk.dim(stylesResult.message));
|
|
212
|
-
} else {
|
|
213
|
-
stylesSpinner.info(chalk.dim('No output styles to install'));
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
} catch (error) {
|
|
217
|
-
if (stylesSpinner) {
|
|
218
|
-
stylesSpinner.fail(chalk.red('Failed to install output styles'));
|
|
219
|
-
}
|
|
220
|
-
throw error;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Install rules if target supports it and rules are not skipped
|
|
225
|
-
if (target.setupRules && options.rules !== false) {
|
|
226
|
-
const rulesSpinner = quiet ? null : ora({ text: 'Installing rules', color: 'cyan' }).start();
|
|
227
|
-
try {
|
|
228
|
-
const rulesResult = await target.setupRules(process.cwd(), { ...options, quiet: true, force: options.clear });
|
|
229
|
-
result.installed.rules = rulesResult.count;
|
|
230
|
-
|
|
231
|
-
if (rulesSpinner) {
|
|
232
|
-
if (rulesResult.count > 0) {
|
|
233
|
-
rulesSpinner.succeed(
|
|
234
|
-
chalk.green(
|
|
235
|
-
`Installed ${chalk.cyan(rulesResult.count)} rule${rulesResult.count !== 1 ? 's' : ''}`
|
|
236
|
-
)
|
|
237
|
-
);
|
|
238
|
-
} else if (rulesResult.message) {
|
|
239
|
-
rulesSpinner.info(chalk.dim(rulesResult.message));
|
|
240
|
-
} else {
|
|
241
|
-
rulesSpinner.info(chalk.dim('No rules to install'));
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
} catch (error) {
|
|
245
|
-
if (rulesSpinner) {
|
|
246
|
-
rulesSpinner.fail(chalk.red('Failed to install rules'));
|
|
247
|
-
}
|
|
248
|
-
throw error;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// Install slash commands if target supports it and slash commands are not skipped
|
|
253
|
-
if (target.setupSlashCommands && options.slashCommands !== false) {
|
|
254
|
-
const commandsSpinner = quiet ? null : ora({
|
|
255
|
-
text: 'Installing slash commands',
|
|
256
|
-
color: 'cyan',
|
|
257
|
-
}).start();
|
|
258
|
-
try {
|
|
259
|
-
const commandsResult = await target.setupSlashCommands(process.cwd(), { ...options, quiet: true, force: options.clear });
|
|
260
|
-
result.installed.slashCommands = commandsResult.count;
|
|
261
|
-
|
|
262
|
-
if (commandsSpinner) {
|
|
263
|
-
if (commandsResult.count > 0) {
|
|
264
|
-
commandsSpinner.succeed(
|
|
265
|
-
chalk.green(
|
|
266
|
-
`Installed ${chalk.cyan(commandsResult.count)} slash command${commandsResult.count !== 1 ? 's' : ''}`
|
|
267
|
-
)
|
|
268
|
-
);
|
|
269
|
-
} else if (commandsResult.message) {
|
|
270
|
-
commandsSpinner.info(chalk.dim(commandsResult.message));
|
|
271
|
-
} else {
|
|
272
|
-
commandsSpinner.info(chalk.dim('No slash commands to install'));
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
} catch (error) {
|
|
276
|
-
if (commandsSpinner) {
|
|
277
|
-
commandsSpinner.fail(chalk.red('Failed to install slash commands'));
|
|
278
|
-
}
|
|
279
|
-
throw error;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Setup hooks if target supports it and hooks are not skipped
|
|
284
|
-
if (target.setupHooks && options.hooks !== false) {
|
|
285
|
-
const hooksSpinner = quiet ? null : ora({ text: 'Setting up hooks', color: 'cyan' }).start();
|
|
286
|
-
try {
|
|
287
|
-
const hooksResult = await target.setupHooks(process.cwd(), { ...options, quiet: true });
|
|
288
|
-
result.installed.hooks = hooksResult.count;
|
|
289
|
-
|
|
290
|
-
if (hooksSpinner) {
|
|
291
|
-
if (hooksResult.count > 0) {
|
|
292
|
-
const message = hooksResult.message
|
|
293
|
-
? `Configured ${chalk.cyan(hooksResult.count)} hook${hooksResult.count !== 1 ? 's' : ''} - ${hooksResult.message}`
|
|
294
|
-
: `Configured ${chalk.cyan(hooksResult.count)} hook${hooksResult.count !== 1 ? 's' : ''}`;
|
|
295
|
-
hooksSpinner.succeed(chalk.green(message));
|
|
296
|
-
} else {
|
|
297
|
-
hooksSpinner.info(chalk.dim(hooksResult.message || 'No hooks to configure'));
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
} catch (error) {
|
|
301
|
-
// Don't fail entire setup if hooks fail
|
|
302
|
-
if (hooksSpinner) {
|
|
303
|
-
hooksSpinner.warn(chalk.yellow('Could not setup hooks'));
|
|
304
|
-
console.warn(chalk.dim(` ${error instanceof Error ? error.message : String(error)}`));
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Save the selected target as project default
|
|
310
|
-
try {
|
|
311
|
-
await projectSettings.setDefaultTarget(targetId);
|
|
312
|
-
|
|
313
|
-
// Save to new ConfigService for proper layered configuration
|
|
314
|
-
await ConfigService.saveProjectSettings({
|
|
315
|
-
target: targetId,
|
|
316
|
-
version: '1.0.0',
|
|
317
|
-
lastUpdated: new Date().toISOString(),
|
|
318
|
-
});
|
|
319
|
-
} catch (error) {
|
|
320
|
-
// Don't fail the entire setup if we can't save settings
|
|
321
|
-
if (!quiet) {
|
|
322
|
-
console.warn(
|
|
323
|
-
chalk.yellow(
|
|
324
|
-
`⚠ Warning: Could not save default target: ${error instanceof Error ? error.message : String(error)}`
|
|
325
|
-
)
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return result;
|
|
331
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Manager
|
|
3
|
-
* Manages agent state and operations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Agent } from '../types/agent.types.js';
|
|
7
|
-
import { loadAllAgents } from './agent-loader.js';
|
|
8
|
-
import { DEFAULT_AGENT_ID } from './builtin-agents.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Agent manager state
|
|
12
|
-
*/
|
|
13
|
-
interface AgentManagerState {
|
|
14
|
-
agents: Map<string, Agent>;
|
|
15
|
-
cwd: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
let state: AgentManagerState | null = null;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Get the app store (lazy import to avoid circular dependencies)
|
|
22
|
-
*/
|
|
23
|
-
let getAppStore: (() => any) | null = null;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Fallback agent when state is not initialized
|
|
27
|
-
*/
|
|
28
|
-
const FALLBACK_AGENT: Agent = {
|
|
29
|
-
id: DEFAULT_AGENT_ID,
|
|
30
|
-
metadata: {
|
|
31
|
-
name: 'Coder',
|
|
32
|
-
description: 'Fallback agent (agent manager not initialized)',
|
|
33
|
-
},
|
|
34
|
-
systemPrompt: 'You are a helpful coding assistant.',
|
|
35
|
-
isBuiltin: true,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Set the app store getter (called during initialization)
|
|
40
|
-
*/
|
|
41
|
-
export function setAppStoreGetter(getter: () => any): void {
|
|
42
|
-
getAppStore = getter;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Initialize agent manager
|
|
47
|
-
*/
|
|
48
|
-
export async function initializeAgentManager(cwd: string): Promise<void> {
|
|
49
|
-
const allAgents = await loadAllAgents(cwd);
|
|
50
|
-
|
|
51
|
-
const agentMap = new Map<string, Agent>();
|
|
52
|
-
for (const agent of allAgents) {
|
|
53
|
-
agentMap.set(agent.id, agent);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
state = {
|
|
57
|
-
agents: agentMap,
|
|
58
|
-
cwd,
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Initialize store with default agent if store is available
|
|
62
|
-
if (getAppStore) {
|
|
63
|
-
const store = getAppStore();
|
|
64
|
-
if (store.getState) {
|
|
65
|
-
const currentAgentId = store.getState().currentAgentId || DEFAULT_AGENT_ID;
|
|
66
|
-
// Ensure the current agent exists, fallback to default if not
|
|
67
|
-
if (!agentMap.has(currentAgentId)) {
|
|
68
|
-
store.getState().setCurrentAgentId(DEFAULT_AGENT_ID);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Get all available agents
|
|
76
|
-
*/
|
|
77
|
-
export function getAllAgents(): Agent[] {
|
|
78
|
-
if (!state) {
|
|
79
|
-
return [FALLBACK_AGENT];
|
|
80
|
-
}
|
|
81
|
-
return Array.from(state.agents.values());
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Get agent by ID
|
|
86
|
-
*/
|
|
87
|
-
export function getAgentById(id: string): Agent | null {
|
|
88
|
-
if (!state) {
|
|
89
|
-
return id === DEFAULT_AGENT_ID ? FALLBACK_AGENT : null;
|
|
90
|
-
}
|
|
91
|
-
return state.agents.get(id) || null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Get current agent
|
|
96
|
-
*/
|
|
97
|
-
export function getCurrentAgent(): Agent {
|
|
98
|
-
const currentAgentId = getCurrentAgentId();
|
|
99
|
-
|
|
100
|
-
if (!state) {
|
|
101
|
-
return FALLBACK_AGENT;
|
|
102
|
-
}
|
|
103
|
-
return state.agents.get(currentAgentId) || FALLBACK_AGENT;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Get current agent ID
|
|
108
|
-
*/
|
|
109
|
-
export function getCurrentAgentId(): string {
|
|
110
|
-
// Try to get from store first
|
|
111
|
-
if (getAppStore) {
|
|
112
|
-
const store = getAppStore();
|
|
113
|
-
if (store.getState) {
|
|
114
|
-
return store.getState().currentAgentId || DEFAULT_AGENT_ID;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
// Fallback to default
|
|
118
|
-
return DEFAULT_AGENT_ID;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Switch to a different agent
|
|
123
|
-
*/
|
|
124
|
-
export function switchAgent(agentId: string): boolean {
|
|
125
|
-
if (!state) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const agent = state.agents.get(agentId);
|
|
130
|
-
if (!agent) {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Update store if available (this triggers reactive updates)
|
|
135
|
-
if (getAppStore) {
|
|
136
|
-
const store = getAppStore();
|
|
137
|
-
if (store.getState) {
|
|
138
|
-
store.getState().setCurrentAgentId(agentId);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return true;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Reload agents from disk
|
|
147
|
-
*/
|
|
148
|
-
export async function reloadAgents(): Promise<void> {
|
|
149
|
-
if (!state) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const cwd = state.cwd;
|
|
154
|
-
const currentAgentId = getCurrentAgentId();
|
|
155
|
-
|
|
156
|
-
await initializeAgentManager(cwd);
|
|
157
|
-
|
|
158
|
-
// Restore current agent if it still exists, otherwise reset to default
|
|
159
|
-
if (state && !state.agents.has(currentAgentId)) {
|
|
160
|
-
if (getAppStore) {
|
|
161
|
-
const store = getAppStore();
|
|
162
|
-
if (store.getState) {
|
|
163
|
-
store.getState().setCurrentAgentId(DEFAULT_AGENT_ID);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Get system prompt for current agent
|
|
171
|
-
*/
|
|
172
|
-
export function getCurrentSystemPrompt(): string {
|
|
173
|
-
return getCurrentAgent().systemPrompt;
|
|
174
|
-
}
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Loop Controller - Simple continuous execution
|
|
3
|
-
*
|
|
4
|
-
* Core concept: Keep executing the same task with context persistence
|
|
5
|
-
* - First run: Fresh start
|
|
6
|
-
* - Subsequent runs: Auto-continue (builds on previous work)
|
|
7
|
-
* - Stop: Manual (Ctrl+C) or max-runs limit
|
|
8
|
-
*
|
|
9
|
-
* Use case: "Keep working on X until I stop you"
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import chalk from 'chalk';
|
|
13
|
-
import type { FlowOptions } from '../commands/flow/types.js';
|
|
14
|
-
|
|
15
|
-
export interface LoopOptions {
|
|
16
|
-
enabled: boolean;
|
|
17
|
-
interval: number; // Wait time in seconds between runs (0 = no wait)
|
|
18
|
-
maxRuns?: number; // Optional max iterations (default: infinite)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface LoopResult {
|
|
22
|
-
exitCode: number;
|
|
23
|
-
error?: Error;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface LoopState {
|
|
27
|
-
iteration: number;
|
|
28
|
-
startTime: Date;
|
|
29
|
-
successCount: number;
|
|
30
|
-
errorCount: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Controller for loop execution mode
|
|
35
|
-
*/
|
|
36
|
-
export class LoopController {
|
|
37
|
-
private state: LoopState;
|
|
38
|
-
private shouldStop: boolean = false;
|
|
39
|
-
|
|
40
|
-
constructor() {
|
|
41
|
-
this.state = {
|
|
42
|
-
iteration: 0,
|
|
43
|
-
startTime: new Date(),
|
|
44
|
-
successCount: 0,
|
|
45
|
-
errorCount: 0,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Handle graceful shutdown
|
|
49
|
-
this.setupSignalHandlers();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Execute task in loop mode
|
|
54
|
-
* Simple: Keep running same task until manual stop or max-runs
|
|
55
|
-
*/
|
|
56
|
-
async run(
|
|
57
|
-
executor: () => Promise<LoopResult>,
|
|
58
|
-
options: LoopOptions
|
|
59
|
-
): Promise<void> {
|
|
60
|
-
console.log(chalk.cyan.bold('\n━━━ 🔄 Loop Mode Activated\n'));
|
|
61
|
-
console.log(chalk.dim(` Wait time: ${options.interval}s`));
|
|
62
|
-
console.log(chalk.dim(` Max runs: ${options.maxRuns || '∞'}`));
|
|
63
|
-
console.log(chalk.dim(` Stop: Ctrl+C or max-runs limit\n`));
|
|
64
|
-
|
|
65
|
-
while (this.shouldContinue(options)) {
|
|
66
|
-
this.state.iteration++;
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
await this.executeIteration(executor, options);
|
|
70
|
-
} catch (error) {
|
|
71
|
-
this.handleError(error as Error);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Wait for next iteration
|
|
75
|
-
if (this.shouldContinue(options)) {
|
|
76
|
-
await this.waitForNextRun(options);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.printSummary();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Execute single iteration
|
|
85
|
-
* Simple: Run task, track success/error, continue
|
|
86
|
-
*/
|
|
87
|
-
private async executeIteration(
|
|
88
|
-
executor: () => Promise<LoopResult>,
|
|
89
|
-
options: LoopOptions
|
|
90
|
-
): Promise<void> {
|
|
91
|
-
const maxDisplay = options.maxRuns || '∞';
|
|
92
|
-
console.log(
|
|
93
|
-
chalk.cyan(
|
|
94
|
-
`\n🔄 Loop iteration ${this.state.iteration}/${maxDisplay}`
|
|
95
|
-
)
|
|
96
|
-
);
|
|
97
|
-
console.log(chalk.dim(`Started: ${new Date().toLocaleTimeString()}\n`));
|
|
98
|
-
|
|
99
|
-
const result = await executor();
|
|
100
|
-
|
|
101
|
-
// Update state (just count success/error)
|
|
102
|
-
if (result.error || result.exitCode !== 0) {
|
|
103
|
-
this.state.errorCount++;
|
|
104
|
-
console.log(chalk.yellow(`\n⚠️ Task encountered error (continuing...)`));
|
|
105
|
-
} else {
|
|
106
|
-
this.state.successCount++;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Handle execution error
|
|
112
|
-
* Simple: Log and continue (resilient)
|
|
113
|
-
*/
|
|
114
|
-
private handleError(error: Error): void {
|
|
115
|
-
this.state.errorCount++;
|
|
116
|
-
console.error(chalk.yellow('\n⚠️ Error occurred - continuing to next iteration'));
|
|
117
|
-
console.error(chalk.dim(`Error: ${error.message}\n`));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Wait for next iteration
|
|
122
|
-
*/
|
|
123
|
-
private async waitForNextRun(options: LoopOptions): Promise<void> {
|
|
124
|
-
const maxDisplay = options.maxRuns || '∞';
|
|
125
|
-
const progress = `${this.state.iteration}/${maxDisplay}`;
|
|
126
|
-
|
|
127
|
-
console.log(
|
|
128
|
-
chalk.dim(
|
|
129
|
-
`\n⏳ Waiting ${options.interval}s until next run... (completed: ${progress})`
|
|
130
|
-
)
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
// Countdown display (optional, can be removed if too verbose)
|
|
134
|
-
const startTime = Date.now();
|
|
135
|
-
const endTime = startTime + options.interval * 1000;
|
|
136
|
-
|
|
137
|
-
while (Date.now() < endTime && !this.shouldStop) {
|
|
138
|
-
await this.sleep(1000);
|
|
139
|
-
|
|
140
|
-
const remaining = Math.ceil((endTime - Date.now()) / 1000);
|
|
141
|
-
if (remaining > 0 && remaining % 10 === 0) {
|
|
142
|
-
process.stdout.write(chalk.dim(`\r⏳ ${remaining}s remaining...`));
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (!this.shouldStop) {
|
|
147
|
-
process.stdout.write('\r' + ' '.repeat(50) + '\r'); // Clear line
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Check if should continue looping
|
|
153
|
-
* Simple: Stop only on manual interrupt or max-runs
|
|
154
|
-
*/
|
|
155
|
-
private shouldContinue(options: LoopOptions): boolean {
|
|
156
|
-
if (this.shouldStop) return false;
|
|
157
|
-
if (options.maxRuns && this.state.iteration >= options.maxRuns) return false;
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Print execution summary
|
|
163
|
-
*/
|
|
164
|
-
private printSummary(): void {
|
|
165
|
-
const duration = Date.now() - this.state.startTime.getTime();
|
|
166
|
-
const minutes = Math.floor(duration / 60000);
|
|
167
|
-
const seconds = Math.floor((duration % 60000) / 1000);
|
|
168
|
-
|
|
169
|
-
console.log(chalk.cyan.bold('\n━━━ 🏁 Loop Summary\n'));
|
|
170
|
-
console.log(` Total iterations: ${this.state.iteration}`);
|
|
171
|
-
console.log(
|
|
172
|
-
` Successful: ${chalk.green(this.state.successCount.toString())}`
|
|
173
|
-
);
|
|
174
|
-
console.log(` Errors: ${chalk.red(this.state.errorCount.toString())}`);
|
|
175
|
-
console.log(` Duration: ${minutes}m ${seconds}s`);
|
|
176
|
-
console.log();
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Setup signal handlers for graceful shutdown
|
|
181
|
-
*/
|
|
182
|
-
private setupSignalHandlers(): void {
|
|
183
|
-
const handler = () => {
|
|
184
|
-
console.log(
|
|
185
|
-
chalk.yellow('\n\n⚠️ Interrupt received - finishing current iteration...')
|
|
186
|
-
);
|
|
187
|
-
this.shouldStop = true;
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
process.on('SIGINT', handler);
|
|
191
|
-
process.on('SIGTERM', handler);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Sleep helper
|
|
196
|
-
*/
|
|
197
|
-
private sleep(ms: number): Promise<void> {
|
|
198
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
199
|
-
}
|
|
200
|
-
}
|