@sylphx/flow 3.19.0 → 3.19.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +1 -3
  3. package/src/commands/flow/execute-v2.ts +126 -128
  4. package/src/commands/flow-command.ts +52 -42
  5. package/src/config/index.ts +0 -20
  6. package/src/core/agent-loader.ts +2 -2
  7. package/src/core/attach-manager.ts +5 -1
  8. package/src/core/cleanup-handler.ts +20 -16
  9. package/src/core/flow-executor.ts +93 -62
  10. package/src/core/functional/index.ts +0 -11
  11. package/src/core/index.ts +1 -1
  12. package/src/core/project-manager.ts +14 -29
  13. package/src/core/secrets-manager.ts +15 -18
  14. package/src/core/session-manager.ts +4 -8
  15. package/src/core/target-manager.ts +6 -3
  16. package/src/core/upgrade-manager.ts +1 -1
  17. package/src/index.ts +1 -1
  18. package/src/services/auto-upgrade.ts +6 -14
  19. package/src/services/config-service.ts +7 -23
  20. package/src/services/index.ts +1 -1
  21. package/src/targets/claude-code.ts +14 -8
  22. package/src/targets/opencode.ts +61 -39
  23. package/src/targets/shared/mcp-transforms.ts +20 -43
  24. package/src/types/agent.types.ts +5 -3
  25. package/src/types/mcp.types.ts +38 -1
  26. package/src/types.ts +4 -0
  27. package/src/utils/agent-enhancer.ts +1 -1
  28. package/src/utils/errors.ts +13 -0
  29. package/src/utils/files/file-operations.ts +16 -0
  30. package/src/utils/index.ts +1 -1
  31. package/src/core/error-handling.ts +0 -482
  32. package/src/core/functional/async.ts +0 -101
  33. package/src/core/functional/either.ts +0 -109
  34. package/src/core/functional/error-handler.ts +0 -135
  35. package/src/core/functional/pipe.ts +0 -189
  36. package/src/core/functional/validation.ts +0 -138
  37. package/src/types/mcp-config.types.ts +0 -448
  38. package/src/utils/error-handler.ts +0 -53
@@ -1,482 +0,0 @@
1
- /**
2
- * Error Handling - 統一錯誤處理
3
- * Functional, composable error handling system
4
- */
5
-
6
- import { logger } from '../utils/display/logger.js';
7
- import type { Result } from './result.js';
8
-
9
- /**
10
- * Base error class
11
- */
12
- export class BaseError extends Error {
13
- public readonly code: string;
14
- public readonly statusCode: number;
15
- public readonly details?: Record<string, unknown>;
16
-
17
- constructor(message: string, code: string, statusCode = 500, details?: Record<string, unknown>) {
18
- super(message);
19
- this.name = this.constructor.name;
20
- this.code = code;
21
- this.statusCode = statusCode;
22
- this.details = details;
23
-
24
- // Maintains proper stack trace for where our error was thrown
25
- Error.captureStackTrace(this, this.constructor);
26
- }
27
- }
28
-
29
- /**
30
- * Validation error
31
- */
32
- export class ValidationError extends BaseError {
33
- constructor(message: string, details?: Record<string, unknown>) {
34
- super(message, 'VALIDATION_ERROR', 400, details);
35
- }
36
- }
37
-
38
- /**
39
- * Configuration error
40
- */
41
- export class ConfigurationError extends BaseError {
42
- constructor(message: string, details?: Record<string, unknown>) {
43
- super(message, 'CONFIGURATION_ERROR', 500, details);
44
- }
45
- }
46
-
47
- /**
48
- * Storage error
49
- */
50
- export class StorageError extends BaseError {
51
- constructor(message: string, operation?: string, details?: Record<string, unknown>) {
52
- super(message, 'STORAGE_ERROR', 500, { operation, ...details });
53
- }
54
- }
55
-
56
- /**
57
- * Network error
58
- */
59
- export class NetworkError extends BaseError {
60
- constructor(message: string, details?: Record<string, unknown>) {
61
- super(message, 'NETWORK_ERROR', 503, details);
62
- }
63
- }
64
-
65
- /**
66
- * Authentication error
67
- */
68
- export class AuthenticationError extends BaseError {
69
- constructor(message: string, details?: Record<string, unknown>) {
70
- super(message, 'AUTHENTICATION_ERROR', 401, details);
71
- }
72
- }
73
-
74
- /**
75
- * Authorization error
76
- */
77
- export class AuthorizationError extends BaseError {
78
- constructor(message: string, details?: Record<string, unknown>) {
79
- super(message, 'AUTHORIZATION_ERROR', 403, details);
80
- }
81
- }
82
-
83
- /**
84
- * Not found error
85
- */
86
- export class NotFoundError extends BaseError {
87
- constructor(message: string, resource?: string, details?: Record<string, unknown>) {
88
- super(message, 'NOT_FOUND', 404, { resource, ...details });
89
- }
90
- }
91
-
92
- /**
93
- * Timeout error
94
- */
95
- export class TimeoutError extends BaseError {
96
- constructor(message: string, timeout?: number, details?: Record<string, unknown>) {
97
- super(message, 'TIMEOUT_ERROR', 408, { timeout, ...details });
98
- }
99
- }
100
-
101
- /**
102
- * Error types
103
- */
104
- export const ErrorTypes = {
105
- ValidationError,
106
- ConfigurationError,
107
- StorageError,
108
- NetworkError,
109
- AuthenticationError,
110
- AuthorizationError,
111
- NotFoundError,
112
- TimeoutError,
113
- } as const;
114
-
115
- /**
116
- * Error codes
117
- */
118
- export const ErrorCodes = {
119
- // Validation errors
120
- INVALID_INPUT: 'INVALID_INPUT',
121
- MISSING_REQUIRED_FIELD: 'MISSING_REQUIRED_FIELD',
122
- INVALID_FORMAT: 'INVALID_FORMAT',
123
-
124
- // Configuration errors
125
- MISSING_CONFIG: 'MISSING_CONFIG',
126
- INVALID_CONFIG: 'INVALID_CONFIG',
127
- CONFIG_PARSE_ERROR: 'CONFIG_PARSE_ERROR',
128
-
129
- // Storage errors
130
- STORAGE_CONNECTION_FAILED: 'STORAGE_CONNECTION_FAILED',
131
- STORAGE_OPERATION_FAILED: 'STORAGE_OPERATION_FAILED',
132
- STORAGE_TIMEOUT: 'STORAGE_TIMEOUT',
133
- STORAGE_FULL: 'STORAGE_FULL',
134
-
135
- // Network errors
136
- CONNECTION_FAILED: 'CONNECTION_FAILED',
137
- REQUEST_FAILED: 'REQUEST_FAILED',
138
- REQUEST_TIMEOUT: 'REQUEST_TIMEOUT',
139
- RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
140
-
141
- // Authentication errors
142
- UNAUTHORIZED: 'UNAUTHORIZED',
143
- INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',
144
- TOKEN_EXPIRED: 'TOKEN_EXPIRED',
145
-
146
- // Authorization errors
147
- FORBIDDEN: 'FORBIDDEN',
148
- INSUFFICIENT_PERMISSIONS: 'INSUFFICIENT_PERMISSIONS',
149
-
150
- // Not found errors
151
- RESOURCE_NOT_FOUND: 'RESOURCE_NOT_FOUND',
152
- ENDPOINT_NOT_FOUND: 'ENDPOINT_NOT_FOUND',
153
-
154
- // System errors
155
- INTERNAL_ERROR: 'INTERNAL_ERROR',
156
- SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
157
- TIMEOUT: 'TIMEOUT',
158
- } as const;
159
-
160
- /**
161
- * Error handler interface
162
- */
163
- export interface ErrorHandler {
164
- canHandle(error: Error): boolean;
165
- handle(error: Error): void | Promise<void>;
166
- }
167
-
168
- /**
169
- * Logger error handler
170
- */
171
- export class LoggerErrorHandler implements ErrorHandler {
172
- constructor(private level: 'error' | 'warn' | 'info' | 'debug' = 'error') {}
173
-
174
- canHandle(_error: Error): boolean {
175
- return true; // Logger can handle all errors
176
- }
177
-
178
- handle(error: Error): void {
179
- const errorData = {
180
- name: error.name,
181
- message: error.message,
182
- code: (error as BaseError).code,
183
- statusCode: (error as BaseError).statusCode,
184
- stack: error.stack,
185
- details: (error as BaseError).details,
186
- };
187
-
188
- switch (this.level) {
189
- case 'error':
190
- logger.error(error.message, errorData);
191
- break;
192
- case 'warn':
193
- logger.warn(error.message, errorData);
194
- break;
195
- case 'info':
196
- logger.info(error.message, errorData);
197
- break;
198
- case 'debug':
199
- logger.debug(error.message, errorData);
200
- break;
201
- }
202
- }
203
- }
204
-
205
- /**
206
- * Console error handler
207
- */
208
- export class ConsoleErrorHandler implements ErrorHandler {
209
- canHandle(_error: Error): boolean {
210
- return true; // Console can handle all errors
211
- }
212
-
213
- handle(error: Error): void {
214
- if (error instanceof BaseError) {
215
- console.error(`[${error.code}] ${error.message}`);
216
- if (error.details) {
217
- console.error('Details:', error.details);
218
- }
219
- } else {
220
- console.error(error.message);
221
- }
222
-
223
- if (process.env.NODE_ENV === 'development' && error.stack) {
224
- console.error('\nStack trace:');
225
- console.error(error.stack);
226
- }
227
- }
228
- }
229
-
230
- /**
231
- * Error handler chain
232
- */
233
- export class ErrorHandlerChain {
234
- private handlers: ErrorHandler[] = [];
235
-
236
- constructor(handlers: ErrorHandler[] = []) {
237
- this.handlers = handlers;
238
- }
239
-
240
- addHandler(handler: ErrorHandler): void {
241
- this.handlers.push(handler);
242
- }
243
-
244
- removeHandler(handler: ErrorHandler): void {
245
- const index = this.handlers.indexOf(handler);
246
- if (index > -1) {
247
- this.handlers.splice(index, 1);
248
- }
249
- }
250
-
251
- async handle(error: Error): Promise<void> {
252
- for (const handler of this.handlers) {
253
- if (handler.canHandle(error)) {
254
- await handler.handle(error);
255
- return;
256
- }
257
- }
258
-
259
- // If no handler can handle the error, use default console handler
260
- new ConsoleErrorHandler().handle(error);
261
- }
262
- }
263
-
264
- /**
265
- * Global error handler
266
- */
267
- export const globalErrorHandler = new ErrorHandlerChain([new LoggerErrorHandler('error')]);
268
-
269
- /**
270
- * Safe function wrapper with error handling
271
- */
272
- export async function withErrorHandling<T>(
273
- fn: () => Promise<T>,
274
- errorHandler?: (error: Error) => void | Promise<void>
275
- ): Promise<Result<T>> {
276
- try {
277
- const data = await fn();
278
- return { success: true, data };
279
- } catch (error) {
280
- const errorObj = error instanceof Error ? error : new Error(String(error));
281
-
282
- if (errorHandler) {
283
- await errorHandler(errorObj);
284
- } else {
285
- await globalErrorHandler.handle(errorObj);
286
- }
287
-
288
- return { success: false, error: errorObj };
289
- }
290
- }
291
-
292
- /**
293
- * Safe sync function wrapper with error handling
294
- */
295
- export function withSyncErrorHandling<T>(
296
- fn: () => T,
297
- errorHandler?: (error: Error) => void | Promise<void>
298
- ): Result<T> {
299
- try {
300
- const data = fn();
301
- return { success: true, data };
302
- } catch (error) {
303
- const errorObj = error instanceof Error ? error : new Error(String(error));
304
-
305
- if (errorHandler) {
306
- // For sync error handlers, we need to handle them asynchronously
307
- void errorHandler(errorObj);
308
- } else {
309
- void globalErrorHandler.handle(errorObj);
310
- }
311
-
312
- return { success: false, error: errorObj };
313
- }
314
- }
315
-
316
- /**
317
- * Retry function with error handling
318
- */
319
- export async function withRetry<T>(
320
- fn: () => Promise<T>,
321
- options: {
322
- maxAttempts?: number;
323
- delay?: number;
324
- backoff?: 'linear' | 'exponential';
325
- retryableErrors?: string[];
326
- onRetry?: (attempt: number, error: Error) => void;
327
- } = {}
328
- ): Promise<Result<T>> {
329
- const {
330
- maxAttempts = 3,
331
- delay = 1000,
332
- backoff = 'exponential',
333
- retryableErrors = [],
334
- onRetry,
335
- } = options;
336
-
337
- let lastError: Error = new Error('Unknown error');
338
-
339
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
340
- try {
341
- const data = await fn();
342
- return { success: true, data };
343
- } catch (error) {
344
- lastError = error instanceof Error ? error : new Error(String(error));
345
-
346
- // Check if error is retryable
347
- const isRetryable =
348
- retryableErrors.length === 0 ||
349
- retryableErrors.includes((lastError as BaseError).code) ||
350
- retryableErrors.includes(lastError.constructor.name);
351
-
352
- if (!isRetryable || attempt === maxAttempts) {
353
- await globalErrorHandler.handle(lastError);
354
- return { success: false, error: lastError };
355
- }
356
-
357
- // Call retry callback
358
- if (onRetry) {
359
- onRetry(attempt, lastError);
360
- }
361
-
362
- // Calculate delay
363
- const retryDelay = backoff === 'exponential' ? delay * 2 ** (attempt - 1) : delay * attempt;
364
-
365
- // Wait before retry
366
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
367
- }
368
- }
369
-
370
- await globalErrorHandler.handle(lastError);
371
- return { success: false, error: lastError };
372
- }
373
-
374
- /**
375
- * Timeout wrapper
376
- */
377
- export function withTimeout<T>(
378
- fn: () => Promise<T>,
379
- timeoutMs: number,
380
- timeoutMessage = 'Operation timed out'
381
- ): Promise<Result<T>> {
382
- return new Promise((resolve) => {
383
- const timeoutId = setTimeout(() => {
384
- resolve({
385
- success: false,
386
- error: new TimeoutError(timeoutMessage, timeoutMs),
387
- });
388
- }, timeoutMs);
389
-
390
- fn()
391
- .then((data) => {
392
- clearTimeout(timeoutId);
393
- resolve({ success: true, data });
394
- })
395
- .catch((error) => {
396
- clearTimeout(timeoutId);
397
- resolve({
398
- success: false,
399
- error: error instanceof Error ? error : new Error(String(error)),
400
- });
401
- });
402
- });
403
- }
404
-
405
- /**
406
- * Circuit breaker pattern
407
- */
408
- export class CircuitBreaker {
409
- private failures = 0;
410
- private lastFailureTime = 0;
411
- private state: 'closed' | 'open' | 'half-open' = 'closed';
412
- private readonly config: {
413
- failureThreshold: number;
414
- recoveryTimeMs: number;
415
- monitoringPeriodMs: number;
416
- };
417
-
418
- constructor(
419
- options: {
420
- failureThreshold?: number;
421
- recoveryTimeMs?: number;
422
- monitoringPeriodMs?: number;
423
- } = {}
424
- ) {
425
- const { failureThreshold = 5, recoveryTimeMs = 60000, monitoringPeriodMs = 10000 } = options;
426
-
427
- this.config = { failureThreshold, recoveryTimeMs, monitoringPeriodMs };
428
- }
429
-
430
- async execute<T>(fn: () => Promise<T>): Promise<Result<T>> {
431
- if (this.state === 'open') {
432
- if (Date.now() - this.lastFailureTime > this.config.recoveryTimeMs) {
433
- this.state = 'half-open';
434
- } else {
435
- return {
436
- success: false,
437
- error: new Error('Circuit breaker is open'),
438
- };
439
- }
440
- }
441
-
442
- try {
443
- const result = await fn();
444
- this.onSuccess();
445
- return { success: true, data: result };
446
- } catch (error) {
447
- this.onFailure();
448
- return {
449
- success: false,
450
- error: error instanceof Error ? error : new Error(String(error)),
451
- };
452
- }
453
- }
454
-
455
- private onSuccess(): void {
456
- this.failures = 0;
457
- this.state = 'closed';
458
- }
459
-
460
- private onFailure(): void {
461
- this.failures++;
462
- this.lastFailureTime = Date.now();
463
-
464
- if (this.failures >= this.config.failureThreshold) {
465
- this.state = 'open';
466
- }
467
- }
468
-
469
- getState(): 'closed' | 'open' | 'half-open' {
470
- return this.state;
471
- }
472
-
473
- getFailures(): number {
474
- return this.failures;
475
- }
476
-
477
- reset(): void {
478
- this.failures = 0;
479
- this.state = 'closed';
480
- this.lastFailureTime = 0;
481
- }
482
- }
@@ -1,101 +0,0 @@
1
- /**
2
- * Async/Promise utilities for functional programming
3
- * Handle promises with Result types
4
- *
5
- * DESIGN RATIONALE:
6
- * - Convert Promise rejections to Result types
7
- * - Composable async operations
8
- * - No unhandled promise rejections
9
- * - Type-safe async error handling
10
- */
11
-
12
- import type { AppError } from './error-types.js';
13
- import { toAppError } from './error-types.js';
14
- import type { Result } from './result.js';
15
- import { failure, isSuccess, success } from './result.js';
16
-
17
- /**
18
- * Async Result type
19
- */
20
- export type AsyncResult<T, E = AppError> = Promise<Result<T, E>>;
21
-
22
- /**
23
- * Convert Promise to AsyncResult
24
- * Catches rejections and converts to Result
25
- */
26
- export const fromPromise = async <T>(
27
- promise: Promise<T>,
28
- onError?: (error: unknown) => AppError
29
- ): AsyncResult<T> => {
30
- try {
31
- const value = await promise;
32
- return success(value);
33
- } catch (error) {
34
- return failure(onError ? onError(error) : toAppError(error));
35
- }
36
- };
37
-
38
- /**
39
- * Run async operation with timeout
40
- */
41
- export const withTimeout = <T>(
42
- promise: Promise<T>,
43
- timeoutMs: number,
44
- onTimeout?: () => AppError
45
- ): AsyncResult<T> => {
46
- return fromPromise(
47
- Promise.race([
48
- promise,
49
- new Promise<T>((_, reject) =>
50
- setTimeout(() => reject(new Error('Operation timed out')), timeoutMs)
51
- ),
52
- ]),
53
- onTimeout
54
- );
55
- };
56
-
57
- /**
58
- * Retry async operation
59
- */
60
- export const retry = async <T>(
61
- fn: () => Promise<T>,
62
- options: {
63
- maxAttempts: number;
64
- delayMs?: number;
65
- backoff?: number;
66
- onRetry?: (attempt: number, error: AppError) => void;
67
- }
68
- ): AsyncResult<T> => {
69
- const { maxAttempts, delayMs = 1000, backoff = 2, onRetry } = options;
70
-
71
- let lastError: AppError = { type: 'unknown', message: 'No attempts made', code: 'NO_ATTEMPTS' };
72
- let currentDelay = delayMs;
73
-
74
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
75
- const result = await fromPromise(fn());
76
-
77
- if (isSuccess(result)) {
78
- return result;
79
- }
80
-
81
- lastError = result.error;
82
-
83
- if (attempt < maxAttempts) {
84
- if (onRetry) {
85
- onRetry(attempt, lastError);
86
- }
87
-
88
- await new Promise((resolve) => setTimeout(resolve, currentDelay));
89
- currentDelay *= backoff;
90
- }
91
- }
92
-
93
- return failure(lastError);
94
- };
95
-
96
- /**
97
- * Delay execution
98
- */
99
- export const delay = (ms: number): Promise<void> => {
100
- return new Promise((resolve) => setTimeout(resolve, ms));
101
- };
@@ -1,109 +0,0 @@
1
- /**
2
- * Either type for representing values that can be one of two types
3
- * Commonly used for success/error, but more general than Result
4
- *
5
- * DESIGN RATIONALE:
6
- * - Generic sum type for two possibilities
7
- * - Left traditionally represents error/failure
8
- * - Right traditionally represents success/value
9
- * - Result is a specialized Either<Error, Value>
10
- */
11
-
12
- export type Either<L, R> = Left<L> | Right<R>;
13
-
14
- export interface Left<L> {
15
- readonly _tag: 'Left';
16
- readonly left: L;
17
- }
18
-
19
- export interface Right<R> {
20
- readonly _tag: 'Right';
21
- readonly right: R;
22
- }
23
-
24
- /**
25
- * Constructors
26
- */
27
-
28
- export const left = <L>(value: L): Left<L> => ({
29
- _tag: 'Left',
30
- left: value,
31
- });
32
-
33
- export const right = <R>(value: R): Right<R> => ({
34
- _tag: 'Right',
35
- right: value,
36
- });
37
-
38
- /**
39
- * Type guards
40
- */
41
-
42
- export const isLeft = <L, R>(either: Either<L, R>): either is Left<L> => either._tag === 'Left';
43
-
44
- export const isRight = <L, R>(either: Either<L, R>): either is Right<R> => either._tag === 'Right';
45
-
46
- /**
47
- * Transformations
48
- */
49
-
50
- export const map =
51
- <L, R, R2>(fn: (value: R) => R2) =>
52
- (either: Either<L, R>): Either<L, R2> => {
53
- if (isRight(either)) {
54
- return right(fn(either.right));
55
- }
56
- return either;
57
- };
58
-
59
- export const mapLeft =
60
- <L, L2, R>(fn: (value: L) => L2) =>
61
- (either: Either<L, R>): Either<L2, R> => {
62
- if (isLeft(either)) {
63
- return left(fn(either.left));
64
- }
65
- return either;
66
- };
67
-
68
- export const flatMap =
69
- <L, R, R2>(fn: (value: R) => Either<L, R2>) =>
70
- (either: Either<L, R>): Either<L, R2> => {
71
- if (isRight(either)) {
72
- return fn(either.right);
73
- }
74
- return either;
75
- };
76
-
77
- /**
78
- * Pattern matching
79
- */
80
- export const match =
81
- <L, R, T>(onLeft: (left: L) => T, onRight: (right: R) => T) =>
82
- (either: Either<L, R>): T => {
83
- if (isLeft(either)) {
84
- return onLeft(either.left);
85
- }
86
- return onRight(either.right);
87
- };
88
-
89
- /**
90
- * Extract value or provide default
91
- */
92
- export const getOrElse =
93
- <R>(defaultValue: R) =>
94
- <L>(either: Either<L, R>): R => {
95
- if (isRight(either)) {
96
- return either.right;
97
- }
98
- return defaultValue;
99
- };
100
-
101
- /**
102
- * Swap Left and Right
103
- */
104
- export const swap = <L, R>(either: Either<L, R>): Either<R, L> => {
105
- if (isLeft(either)) {
106
- return right(either.left);
107
- }
108
- return left(either.right);
109
- };