@flowdot.ai/daemon 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +51 -0
  3. package/dist/goals/DependencyResolver.d.ts +54 -0
  4. package/dist/goals/DependencyResolver.js +329 -0
  5. package/dist/goals/ErrorRecovery.d.ts +133 -0
  6. package/dist/goals/ErrorRecovery.js +489 -0
  7. package/dist/goals/GoalApiClient.d.ts +81 -0
  8. package/dist/goals/GoalApiClient.js +743 -0
  9. package/dist/goals/GoalCache.d.ts +65 -0
  10. package/dist/goals/GoalCache.js +243 -0
  11. package/dist/goals/GoalCommsHandler.d.ts +150 -0
  12. package/dist/goals/GoalCommsHandler.js +378 -0
  13. package/dist/goals/GoalExporter.d.ts +164 -0
  14. package/dist/goals/GoalExporter.js +318 -0
  15. package/dist/goals/GoalImporter.d.ts +107 -0
  16. package/dist/goals/GoalImporter.js +345 -0
  17. package/dist/goals/GoalManager.d.ts +110 -0
  18. package/dist/goals/GoalManager.js +535 -0
  19. package/dist/goals/GoalReporter.d.ts +105 -0
  20. package/dist/goals/GoalReporter.js +534 -0
  21. package/dist/goals/GoalScheduler.d.ts +102 -0
  22. package/dist/goals/GoalScheduler.js +209 -0
  23. package/dist/goals/GoalValidator.d.ts +72 -0
  24. package/dist/goals/GoalValidator.js +657 -0
  25. package/dist/goals/MetaGoalEnforcer.d.ts +111 -0
  26. package/dist/goals/MetaGoalEnforcer.js +536 -0
  27. package/dist/goals/MilestoneBreaker.d.ts +74 -0
  28. package/dist/goals/MilestoneBreaker.js +348 -0
  29. package/dist/goals/PermissionBridge.d.ts +109 -0
  30. package/dist/goals/PermissionBridge.js +326 -0
  31. package/dist/goals/ProgressTracker.d.ts +113 -0
  32. package/dist/goals/ProgressTracker.js +324 -0
  33. package/dist/goals/ReviewScheduler.d.ts +106 -0
  34. package/dist/goals/ReviewScheduler.js +360 -0
  35. package/dist/goals/TaskExecutor.d.ts +116 -0
  36. package/dist/goals/TaskExecutor.js +370 -0
  37. package/dist/goals/TaskFeedback.d.ts +126 -0
  38. package/dist/goals/TaskFeedback.js +402 -0
  39. package/dist/goals/TaskGenerator.d.ts +75 -0
  40. package/dist/goals/TaskGenerator.js +329 -0
  41. package/dist/goals/TaskQueue.d.ts +84 -0
  42. package/dist/goals/TaskQueue.js +331 -0
  43. package/dist/goals/TaskSanitizer.d.ts +61 -0
  44. package/dist/goals/TaskSanitizer.js +464 -0
  45. package/dist/goals/errors.d.ts +116 -0
  46. package/dist/goals/errors.js +299 -0
  47. package/dist/goals/index.d.ts +24 -0
  48. package/dist/goals/index.js +23 -0
  49. package/dist/goals/types.d.ts +395 -0
  50. package/dist/goals/types.js +230 -0
  51. package/dist/index.d.ts +4 -0
  52. package/dist/index.js +3 -0
  53. package/dist/loop/DaemonIPC.d.ts +67 -0
  54. package/dist/loop/DaemonIPC.js +358 -0
  55. package/dist/loop/IntervalParser.d.ts +39 -0
  56. package/dist/loop/IntervalParser.js +217 -0
  57. package/dist/loop/LoopDaemon.d.ts +123 -0
  58. package/dist/loop/LoopDaemon.js +1821 -0
  59. package/dist/loop/LoopExecutor.d.ts +93 -0
  60. package/dist/loop/LoopExecutor.js +326 -0
  61. package/dist/loop/LoopManager.d.ts +79 -0
  62. package/dist/loop/LoopManager.js +476 -0
  63. package/dist/loop/LoopScheduler.d.ts +69 -0
  64. package/dist/loop/LoopScheduler.js +329 -0
  65. package/dist/loop/LoopStore.d.ts +57 -0
  66. package/dist/loop/LoopStore.js +406 -0
  67. package/dist/loop/LoopValidator.d.ts +55 -0
  68. package/dist/loop/LoopValidator.js +603 -0
  69. package/dist/loop/errors.d.ts +115 -0
  70. package/dist/loop/errors.js +312 -0
  71. package/dist/loop/index.d.ts +11 -0
  72. package/dist/loop/index.js +10 -0
  73. package/dist/loop/notifications/Notifier.d.ts +28 -0
  74. package/dist/loop/notifications/Notifier.js +78 -0
  75. package/dist/loop/notifications/SlackNotifier.d.ts +28 -0
  76. package/dist/loop/notifications/SlackNotifier.js +203 -0
  77. package/dist/loop/notifications/TerminalNotifier.d.ts +18 -0
  78. package/dist/loop/notifications/TerminalNotifier.js +72 -0
  79. package/dist/loop/notifications/WebhookNotifier.d.ts +24 -0
  80. package/dist/loop/notifications/WebhookNotifier.js +123 -0
  81. package/dist/loop/notifications/index.d.ts +24 -0
  82. package/dist/loop/notifications/index.js +109 -0
  83. package/dist/loop/types.d.ts +280 -0
  84. package/dist/loop/types.js +222 -0
  85. package/package.json +92 -0
@@ -0,0 +1,370 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { GoalError } from './errors.js';
3
+ const noopLogger = {
4
+ debug: () => { },
5
+ info: () => { },
6
+ warn: () => { },
7
+ error: () => { },
8
+ };
9
+ const DEFAULT_MAX_EXECUTION_TIME_MS = 5 * 60 * 1000;
10
+ const TASK_TYPE_PERMISSIONS = {
11
+ research: 'web-search',
12
+ draft: 'file-write',
13
+ code: 'file-write',
14
+ execute: 'command-execute',
15
+ recipe: 'recipe-invoke',
16
+ loop: 'loop-create',
17
+ notify: 'domain-access',
18
+ };
19
+ const AUTO_APPROVE_CATEGORIES = new Set([
20
+ 'file-read',
21
+ 'web-search',
22
+ ]);
23
+ const ALWAYS_REQUIRE_APPROVAL = new Set([
24
+ 'command-execute',
25
+ 'file-create',
26
+ ]);
27
+ export class TaskExecutionError extends GoalError {
28
+ taskId;
29
+ goalHash;
30
+ constructor(message, taskId, goalHash, cause) {
31
+ super('TASK_EXECUTION_ERROR', message, cause ? { cause, taskId, goalHash } : { taskId, goalHash });
32
+ this.taskId = taskId;
33
+ this.goalHash = goalHash;
34
+ }
35
+ }
36
+ export class PermissionDeniedError extends GoalError {
37
+ category;
38
+ reason;
39
+ constructor(category, reason) {
40
+ super('PERMISSION_DENIED', `Permission denied: ${reason}`);
41
+ this.category = category;
42
+ this.reason = reason;
43
+ }
44
+ }
45
+ export class ExecutionTimeoutError extends GoalError {
46
+ taskId;
47
+ timeoutMs;
48
+ constructor(taskId, timeoutMs) {
49
+ super('TASK_EXECUTION_TIMEOUT', `Task ${taskId} execution timed out after ${timeoutMs}ms`);
50
+ this.taskId = taskId;
51
+ this.timeoutMs = timeoutMs;
52
+ }
53
+ }
54
+ export class TaskExecutor extends EventEmitter {
55
+ logger;
56
+ checkPermission;
57
+ requestApproval;
58
+ updateTaskStatus;
59
+ handlers;
60
+ maxExecutionTimeMs;
61
+ logExecutions;
62
+ executingTasks = new Map();
63
+ constructor(options = {}) {
64
+ super();
65
+ this.logger = options.logger ?? noopLogger;
66
+ this.checkPermission = options.checkPermission;
67
+ this.requestApproval = options.requestApproval;
68
+ this.updateTaskStatus = options.updateTaskStatus;
69
+ this.handlers = options.handlers ?? {};
70
+ this.maxExecutionTimeMs = options.maxExecutionTimeMs ?? DEFAULT_MAX_EXECUTION_TIME_MS;
71
+ this.logExecutions = options.logExecutions ?? true;
72
+ }
73
+ async execute(goal, task) {
74
+ const startTime = new Date();
75
+ const permissionCategory = this.getPermissionCategory(task.taskType);
76
+ const context = {
77
+ goal,
78
+ task,
79
+ permissionCategory,
80
+ approvalMode: goal.approvalMode,
81
+ startTime,
82
+ };
83
+ this.executingTasks.set(task.id, context);
84
+ this.emit('execution-started', context);
85
+ try {
86
+ await this.updateStatus(task.id, 'in_progress');
87
+ const permissionResult = await this.checkTaskPermission(context);
88
+ this.emit('permission-checked', permissionResult, context);
89
+ if (!permissionResult.allowed && !permissionResult.requiresApproval) {
90
+ return this.createResult(context, {
91
+ success: false,
92
+ status: 'failed',
93
+ error: permissionResult.reason ?? 'Permission denied',
94
+ permissionDenied: true,
95
+ });
96
+ }
97
+ if (permissionResult.requiresApproval) {
98
+ const approvalResult = await this.requestTaskApproval(context);
99
+ if (!approvalResult.approved) {
100
+ const status = this.getStatusFromApprovalResponse(approvalResult.response);
101
+ return this.createResult(context, {
102
+ success: false,
103
+ status,
104
+ approvalSkipped: approvalResult.response !== 'deny',
105
+ });
106
+ }
107
+ }
108
+ const result = await this.executeTask(context);
109
+ return this.createResult(context, result);
110
+ }
111
+ catch (error) {
112
+ const err = error instanceof Error ? error : new Error(String(error));
113
+ this.logger.error('TASK_EXECUTOR', 'Execution failed', {
114
+ taskId: task.id,
115
+ error: err.message,
116
+ });
117
+ this.emit('error', err);
118
+ return this.createResult(context, {
119
+ success: false,
120
+ status: 'failed',
121
+ error: err.message,
122
+ });
123
+ }
124
+ finally {
125
+ this.executingTasks.delete(task.id);
126
+ }
127
+ }
128
+ async executeSequence(goal, tasks) {
129
+ const results = [];
130
+ for (const task of tasks) {
131
+ const result = await this.execute(goal, task);
132
+ results.push(result);
133
+ if (!result.success && !result.approvalSkipped) {
134
+ this.logger.info('TASK_EXECUTOR', 'Stopping sequence due to failure', {
135
+ taskId: task.id,
136
+ error: result.error,
137
+ });
138
+ break;
139
+ }
140
+ }
141
+ return results;
142
+ }
143
+ async canExecute(goal, task) {
144
+ const permissionCategory = this.getPermissionCategory(task.taskType);
145
+ const context = {
146
+ goal,
147
+ task,
148
+ permissionCategory,
149
+ approvalMode: goal.approvalMode,
150
+ startTime: new Date(),
151
+ };
152
+ const result = await this.checkTaskPermission(context);
153
+ return {
154
+ canExecute: result.allowed || result.requiresApproval,
155
+ requiresApproval: result.requiresApproval,
156
+ reason: result.reason,
157
+ };
158
+ }
159
+ async checkTaskPermission(context) {
160
+ const { approvalMode, permissionCategory } = context;
161
+ if (this.checkPermission) {
162
+ return this.checkPermission(permissionCategory, context);
163
+ }
164
+ return this.defaultPermissionCheck(approvalMode, permissionCategory);
165
+ }
166
+ defaultPermissionCheck(approvalMode, category) {
167
+ switch (approvalMode) {
168
+ case 'full':
169
+ return {
170
+ allowed: false,
171
+ requiresApproval: true,
172
+ approvalMode,
173
+ category,
174
+ };
175
+ case 'category':
176
+ if (ALWAYS_REQUIRE_APPROVAL.has(category)) {
177
+ return {
178
+ allowed: false,
179
+ requiresApproval: true,
180
+ approvalMode,
181
+ category,
182
+ };
183
+ }
184
+ if (AUTO_APPROVE_CATEGORIES.has(category)) {
185
+ return {
186
+ allowed: true,
187
+ requiresApproval: false,
188
+ approvalMode,
189
+ category,
190
+ };
191
+ }
192
+ return {
193
+ allowed: false,
194
+ requiresApproval: true,
195
+ approvalMode,
196
+ category,
197
+ };
198
+ case 'trusted':
199
+ if (ALWAYS_REQUIRE_APPROVAL.has(category)) {
200
+ return {
201
+ allowed: false,
202
+ requiresApproval: true,
203
+ approvalMode,
204
+ category,
205
+ };
206
+ }
207
+ return {
208
+ allowed: true,
209
+ requiresApproval: false,
210
+ approvalMode,
211
+ category,
212
+ };
213
+ default:
214
+ return {
215
+ allowed: false,
216
+ requiresApproval: true,
217
+ approvalMode,
218
+ category,
219
+ };
220
+ }
221
+ }
222
+ async requestTaskApproval(context) {
223
+ this.emit('approval-requested', context);
224
+ await this.updateStatus(context.task.id, 'awaiting_approval');
225
+ if (!this.requestApproval) {
226
+ this.logger.warn('TASK_EXECUTOR', 'No approval handler configured', {
227
+ taskId: context.task.id,
228
+ });
229
+ return {
230
+ approved: false,
231
+ response: 'deny',
232
+ };
233
+ }
234
+ const result = await this.requestApproval(context);
235
+ this.emit('approval-received', result, context);
236
+ return result;
237
+ }
238
+ async executeTask(context) {
239
+ const { task } = context;
240
+ const handler = this.handlers[task.taskType];
241
+ if (!handler) {
242
+ this.logger.warn('TASK_EXECUTOR', 'No handler for task type', {
243
+ taskId: task.id,
244
+ type: task.taskType,
245
+ });
246
+ return {
247
+ success: false,
248
+ status: 'failed',
249
+ error: `No handler registered for task type: ${task.taskType}`,
250
+ };
251
+ }
252
+ const timeoutPromise = new Promise((_, reject) => {
253
+ setTimeout(() => {
254
+ reject(new ExecutionTimeoutError(task.id, this.maxExecutionTimeMs));
255
+ }, this.maxExecutionTimeMs);
256
+ });
257
+ try {
258
+ const result = await Promise.race([
259
+ handler(context),
260
+ timeoutPromise,
261
+ ]);
262
+ if (result.success) {
263
+ await this.updateStatus(task.id, 'completed', {
264
+ data: result.data,
265
+ cost: result.cost,
266
+ });
267
+ return {
268
+ success: true,
269
+ status: 'completed',
270
+ data: result.data,
271
+ cost: result.cost,
272
+ };
273
+ }
274
+ else {
275
+ await this.updateStatus(task.id, 'failed', {
276
+ error: result.error,
277
+ });
278
+ return {
279
+ success: false,
280
+ status: 'failed',
281
+ error: result.error,
282
+ cost: result.cost,
283
+ };
284
+ }
285
+ }
286
+ catch (error) {
287
+ const message = error instanceof Error ? error.message : String(error);
288
+ await this.updateStatus(task.id, 'failed', { error: message });
289
+ throw error;
290
+ }
291
+ }
292
+ registerHandler(taskType, handler) {
293
+ this.handlers[taskType] = handler;
294
+ this.logger.debug('TASK_EXECUTOR', 'Handler registered', { taskType });
295
+ }
296
+ unregisterHandler(taskType) {
297
+ delete this.handlers[taskType];
298
+ }
299
+ hasHandler(taskType) {
300
+ return taskType in this.handlers;
301
+ }
302
+ getPermissionCategory(taskType) {
303
+ return TASK_TYPE_PERMISSIONS[taskType] ?? 'domain-access';
304
+ }
305
+ getStatusFromApprovalResponse(response) {
306
+ switch (response) {
307
+ case 'deny':
308
+ return 'failed';
309
+ case 'skip':
310
+ return 'skipped';
311
+ case 'later':
312
+ case 'timeout':
313
+ return 'pending';
314
+ default:
315
+ return 'failed';
316
+ }
317
+ }
318
+ async updateStatus(taskId, status, result) {
319
+ if (this.updateTaskStatus) {
320
+ await this.updateTaskStatus(taskId, status, result);
321
+ }
322
+ }
323
+ createResult(context, result) {
324
+ const executionResult = {
325
+ success: result.success,
326
+ taskId: context.task.id,
327
+ goalHash: context.goal.hash,
328
+ status: result.status,
329
+ data: result.data,
330
+ error: result.error,
331
+ cost: result.cost,
332
+ permissionDenied: result.permissionDenied,
333
+ approvalSkipped: result.approvalSkipped,
334
+ durationMs: Date.now() - context.startTime.getTime(),
335
+ executedAt: new Date(),
336
+ };
337
+ if (result.success) {
338
+ this.emit('execution-completed', executionResult);
339
+ }
340
+ else {
341
+ this.emit('execution-failed', executionResult);
342
+ }
343
+ if (this.logExecutions) {
344
+ this.logger.info('TASK_EXECUTOR', result.success ? 'Task completed' : 'Task failed', {
345
+ taskId: context.task.id,
346
+ goalHash: context.goal.hash,
347
+ status: result.status,
348
+ durationMs: executionResult.durationMs,
349
+ });
350
+ }
351
+ return executionResult;
352
+ }
353
+ getExecutingTasks() {
354
+ return Array.from(this.executingTasks.values());
355
+ }
356
+ isExecuting(taskId) {
357
+ return this.executingTasks.has(taskId);
358
+ }
359
+ cancelTask(taskId) {
360
+ const context = this.executingTasks.get(taskId);
361
+ if (!context) {
362
+ return false;
363
+ }
364
+ this.executingTasks.delete(taskId);
365
+ return true;
366
+ }
367
+ }
368
+ export function createTaskExecutor(options = {}) {
369
+ return new TaskExecutor(options);
370
+ }
@@ -0,0 +1,126 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { GoalHash, TaskId, TaskType, Logger } from './types.js';
3
+ export type FeedbackRating = 1 | 2 | 3 | 4 | 5;
4
+ export type FeedbackCategory = 'quality' | 'accuracy' | 'completeness' | 'relevance' | 'safety' | 'performance' | 'other';
5
+ export type FeedbackSentiment = 'positive' | 'neutral' | 'negative';
6
+ export interface TaskFeedbackEntry {
7
+ readonly id: string;
8
+ readonly taskId: TaskId;
9
+ readonly goalHash: GoalHash;
10
+ readonly taskType: TaskType;
11
+ readonly rating: FeedbackRating;
12
+ readonly category: FeedbackCategory;
13
+ readonly sentiment: FeedbackSentiment;
14
+ readonly comment?: string;
15
+ readonly tags?: string[];
16
+ readonly wasTaskModified: boolean;
17
+ readonly wasTaskRetried: boolean;
18
+ readonly createdAt: Date;
19
+ readonly metadata?: Record<string, unknown>;
20
+ }
21
+ export interface FeedbackStatistics {
22
+ readonly totalFeedback: number;
23
+ readonly averageRating: number;
24
+ readonly ratingDistribution: Record<FeedbackRating, number>;
25
+ readonly categoryBreakdown: Record<FeedbackCategory, number>;
26
+ readonly sentimentBreakdown: Record<FeedbackSentiment, number>;
27
+ readonly taskTypeBreakdown: Record<string, {
28
+ count: number;
29
+ avgRating: number;
30
+ }>;
31
+ readonly commonIssues: IssuePattern[];
32
+ readonly trends: FeedbackTrend[];
33
+ }
34
+ export interface IssuePattern {
35
+ readonly id: string;
36
+ readonly description: string;
37
+ readonly frequency: number;
38
+ readonly affectedTaskTypes: TaskType[];
39
+ readonly averageRating: number;
40
+ readonly exampleFeedbackIds: string[];
41
+ readonly suggestedFix?: string;
42
+ }
43
+ export interface FeedbackTrend {
44
+ readonly period: string;
45
+ readonly averageRating: number;
46
+ readonly feedbackCount: number;
47
+ readonly change: 'improving' | 'stable' | 'declining';
48
+ }
49
+ export interface FeedbackQueryOptions {
50
+ readonly goalHash?: GoalHash;
51
+ readonly taskType?: TaskType;
52
+ readonly category?: FeedbackCategory;
53
+ readonly minRating?: FeedbackRating;
54
+ readonly maxRating?: FeedbackRating;
55
+ readonly sentiment?: FeedbackSentiment;
56
+ readonly startDate?: Date;
57
+ readonly endDate?: Date;
58
+ readonly limit?: number;
59
+ readonly offset?: number;
60
+ }
61
+ export interface FeedbackInput {
62
+ readonly taskId: TaskId;
63
+ readonly goalHash: GoalHash;
64
+ readonly taskType: TaskType;
65
+ readonly rating: FeedbackRating;
66
+ readonly category?: FeedbackCategory;
67
+ readonly comment?: string;
68
+ readonly tags?: string[];
69
+ readonly wasTaskModified?: boolean;
70
+ readonly wasTaskRetried?: boolean;
71
+ readonly metadata?: Record<string, unknown>;
72
+ }
73
+ export interface ImprovementSuggestion {
74
+ readonly id: string;
75
+ readonly priority: 'high' | 'medium' | 'low';
76
+ readonly area: string;
77
+ readonly description: string;
78
+ readonly basedOnFeedbackCount: number;
79
+ readonly potentialImpact: number;
80
+ readonly actionItems: string[];
81
+ }
82
+ export interface TaskFeedbackOptions {
83
+ readonly logger?: Logger;
84
+ readonly maxFeedbackEntries?: number;
85
+ readonly patternDetectionThreshold?: number;
86
+ readonly trendWindowDays?: number;
87
+ }
88
+ export interface TaskFeedbackEvents {
89
+ 'feedback:submitted': [feedback: TaskFeedbackEntry];
90
+ 'pattern:detected': [pattern: IssuePattern];
91
+ 'quality:declining': [stats: FeedbackStatistics];
92
+ 'quality:improving': [stats: FeedbackStatistics];
93
+ 'suggestion:generated': [suggestion: ImprovementSuggestion];
94
+ error: [error: Error];
95
+ }
96
+ export declare class TaskFeedback extends EventEmitter<TaskFeedbackEvents> {
97
+ private readonly logger;
98
+ private readonly maxEntries;
99
+ private readonly patternThreshold;
100
+ private readonly trendWindowDays;
101
+ private readonly feedback;
102
+ private readonly patterns;
103
+ private feedbackCounter;
104
+ private patternCounter;
105
+ constructor(options?: TaskFeedbackOptions);
106
+ submitFeedback(input: FeedbackInput): TaskFeedbackEntry;
107
+ getFeedback(feedbackId: string): TaskFeedbackEntry | undefined;
108
+ queryFeedback(options?: FeedbackQueryOptions): TaskFeedbackEntry[];
109
+ getTaskFeedback(taskId: TaskId): TaskFeedbackEntry[];
110
+ getGoalFeedback(goalHash: GoalHash): TaskFeedbackEntry[];
111
+ calculateStatistics(options?: FeedbackQueryOptions): FeedbackStatistics;
112
+ getPatterns(): IssuePattern[];
113
+ generateSuggestions(): ImprovementSuggestion[];
114
+ getGoalAverageRating(goalHash: GoalHash): number | null;
115
+ clearGoalFeedback(goalHash: GoalHash): number;
116
+ exportFeedback(options?: FeedbackQueryOptions): string;
117
+ private deriveSentiment;
118
+ private enforceMaxEntries;
119
+ private detectPatterns;
120
+ private suggestFix;
121
+ private calculateTrends;
122
+ private checkQualityTrends;
123
+ private capitalizeFirst;
124
+ private emptyStatistics;
125
+ }
126
+ export declare function createTaskFeedback(options?: TaskFeedbackOptions): TaskFeedback;