@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,329 @@
1
+ import { EventEmitter } from 'node:events';
2
+ function getPriorityWeight(priority) {
3
+ switch (priority) {
4
+ case 'high': return 10;
5
+ case 'medium': return 5;
6
+ case 'low': return 1;
7
+ default: return 5;
8
+ }
9
+ }
10
+ import { TASK_TYPES } from './types.js';
11
+ import { GoalError } from './errors.js';
12
+ const DEFAULT_MAX_TASKS = 5;
13
+ const DEFAULT_MODEL_TIER = 'standard';
14
+ const noopLogger = {
15
+ debug: () => { },
16
+ info: () => { },
17
+ warn: () => { },
18
+ error: () => { },
19
+ };
20
+ export class TaskGenerationError extends GoalError {
21
+ constructor(message, cause) {
22
+ super('TASK_GENERATION_ERROR', message, cause ? { cause } : {});
23
+ }
24
+ }
25
+ export class LLMParseError extends GoalError {
26
+ constructor(message, cause) {
27
+ super('TASK_GENERATION_ERROR', message, cause ? { cause } : {});
28
+ }
29
+ }
30
+ export class TaskGenerator extends EventEmitter {
31
+ llmFunction;
32
+ defaultModelTier;
33
+ defaultMaxTasks;
34
+ logger;
35
+ constructor(options) {
36
+ super();
37
+ this.llmFunction = options.llmFunction;
38
+ this.defaultModelTier = options.defaultModelTier ?? DEFAULT_MODEL_TIER;
39
+ this.defaultMaxTasks = options.defaultMaxTasks ?? DEFAULT_MAX_TASKS;
40
+ this.logger = options.logger ?? noopLogger;
41
+ }
42
+ async generateTasks(goal, milestones, existingTasks, options = {}) {
43
+ const maxTasks = options.maxTasks ?? this.defaultMaxTasks;
44
+ const modelTier = options.modelTier ?? this.defaultModelTier;
45
+ this.logger.info('TASK_GENERATOR', 'Generating tasks for goal', {
46
+ goalHash: goal.hash,
47
+ goalName: goal.name,
48
+ maxTasks,
49
+ });
50
+ this.emit('generationStarted', goal.hash, options);
51
+ try {
52
+ const prompt = this.buildGenerationPrompt(goal, milestones, existingTasks, options);
53
+ const response = await this.llmFunction(prompt, {
54
+ modelTier,
55
+ maxTokens: 4000,
56
+ temperature: 0.7,
57
+ });
58
+ const result = this.parseGenerationResponse(response, goal, options);
59
+ for (const suggestion of result.suggestions) {
60
+ this.emit('taskSuggested', goal.hash, suggestion);
61
+ }
62
+ this.logger.info('TASK_GENERATOR', 'Task generation completed', {
63
+ goalHash: goal.hash,
64
+ taskCount: result.suggestions.length,
65
+ });
66
+ this.emit('generationCompleted', goal.hash, result);
67
+ return result;
68
+ }
69
+ catch (err) {
70
+ const error = err instanceof Error ? err : new Error(String(err));
71
+ this.logger.error('TASK_GENERATOR', 'Task generation failed', {
72
+ goalHash: goal.hash,
73
+ error: error.message,
74
+ });
75
+ this.emit('generationFailed', goal.hash, error);
76
+ throw new TaskGenerationError(`Failed to generate tasks for goal: ${error.message}`, error);
77
+ }
78
+ }
79
+ async generateDailyTasks(goal, milestones, existingTasks, options = {}) {
80
+ const dailyContext = `
81
+ Focus on tasks that can be completed TODAY. Consider:
82
+ - Current progress: ${goal.progress}%
83
+ - Priority: ${goal.priority}
84
+ - Generate small, actionable tasks that make incremental progress
85
+ - Avoid tasks that require extended periods of work
86
+ - Prioritize tasks that unblock other work
87
+ `;
88
+ return this.generateTasks(goal, milestones, existingTasks, {
89
+ ...options,
90
+ maxTasks: options.maxTasks ?? 3,
91
+ additionalContext: dailyContext + (options.additionalContext ?? ''),
92
+ });
93
+ }
94
+ async suggestNextTask(goal, milestones, pendingTasks, options = {}) {
95
+ if (pendingTasks.length > 0) {
96
+ const sorted = [...pendingTasks].sort((a, b) => getPriorityWeight(b.priority) - getPriorityWeight(a.priority));
97
+ const topTask = sorted[0];
98
+ return {
99
+ title: topTask.title,
100
+ description: topTask.description ?? '',
101
+ type: topTask.taskType,
102
+ priority: getPriorityWeight(topTask.priority),
103
+ reasoning: 'Highest priority pending task',
104
+ milestoneId: topTask.milestoneId ?? undefined,
105
+ };
106
+ }
107
+ const result = await this.generateTasks(goal, milestones, [], {
108
+ ...options,
109
+ maxTasks: 1,
110
+ additionalContext: 'Generate the single most impactful next task for this goal.',
111
+ });
112
+ return result.suggestions[0] ?? null;
113
+ }
114
+ suggestionToInput(suggestion, _goalHash) {
115
+ return {
116
+ taskType: suggestion.type,
117
+ title: suggestion.title,
118
+ description: suggestion.description,
119
+ milestoneId: suggestion.milestoneId,
120
+ scheduledFor: suggestion.scheduledFor,
121
+ };
122
+ }
123
+ buildGenerationPrompt(goal, milestones, existingTasks, options) {
124
+ const maxTasks = options.maxTasks ?? this.defaultMaxTasks;
125
+ const taskTypesStr = options.taskTypes
126
+ ? options.taskTypes.join(', ')
127
+ : TASK_TYPES.join(', ');
128
+ let milestoneContext = '';
129
+ if (milestones.length > 0) {
130
+ milestoneContext = '\n## Milestones\n';
131
+ for (const m of milestones) {
132
+ const status = m.completedAt ? '✓ COMPLETED' : 'PENDING';
133
+ milestoneContext += `- [${status}] ${m.title}`;
134
+ if (m.description)
135
+ milestoneContext += `: ${m.description}`;
136
+ if (m.targetDate)
137
+ milestoneContext += ` (target: ${m.targetDate})`;
138
+ milestoneContext += `\n ID: ${m.id}\n`;
139
+ }
140
+ }
141
+ let existingTasksContext = '';
142
+ if (existingTasks.length > 0) {
143
+ existingTasksContext = '\n## Existing Tasks\n';
144
+ for (const t of existingTasks) {
145
+ existingTasksContext += `- [${t.status}] ${t.title} (${t.taskType})\n`;
146
+ }
147
+ existingTasksContext +=
148
+ '\nDo not duplicate existing tasks. Build on completed work.\n';
149
+ }
150
+ let metaGoalContext = '';
151
+ if (options.metaGoals && options.metaGoals.length > 0) {
152
+ metaGoalContext = '\n## Meta-Goal Constraints\n';
153
+ for (const mg of options.metaGoals) {
154
+ metaGoalContext += `- [Priority ${mg.priority}]: ${mg.description}\n`;
155
+ }
156
+ }
157
+ let targetMilestone = '';
158
+ if (options.milestoneId) {
159
+ const milestone = milestones.find((m) => m.id === options.milestoneId);
160
+ if (milestone) {
161
+ targetMilestone = `\n## Focus Milestone\nGenerate tasks specifically for: "${milestone.title}"\n`;
162
+ }
163
+ }
164
+ return `You are a task planning assistant. Generate actionable tasks for the following goal.
165
+
166
+ # Goal
167
+ Name: ${goal.name}
168
+ Description: ${goal.description ?? 'No description provided'}
169
+ Status: ${goal.status}
170
+ Priority: ${goal.priority}
171
+ Progress: ${goal.progress}%
172
+ Target Date: ${goal.deadline ?? 'No target date'}
173
+ ${milestoneContext}${existingTasksContext}${metaGoalContext}${targetMilestone}
174
+ ${options.additionalContext ?? ''}
175
+
176
+ # Instructions
177
+ Generate up to ${maxTasks} tasks that will help achieve this goal.
178
+
179
+ ## Task Types Available
180
+ ${taskTypesStr}
181
+
182
+ - **research**: Web search, file reading, information gathering
183
+ - **draft**: Content creation, writing, documentation
184
+ - **code**: Code analysis, changes, refactoring
185
+ - **execute**: Command execution, running scripts
186
+ - **recipe**: FlowDot recipe invocation
187
+ - **loop**: Create recurring task
188
+ - **notify**: Send notification
189
+
190
+ ## Output Format
191
+ Respond with a JSON object containing:
192
+ {
193
+ "planSummary": "Brief overview of the task plan",
194
+ "suggestions": [
195
+ {
196
+ "title": "Clear, actionable task title",
197
+ "description": "Detailed description of what to do",
198
+ "type": "research|draft|code|execute|recipe|loop|notify",
199
+ "priority": 1-10,
200
+ "milestoneId": number or null,
201
+ "reasoning": "Why this task is important",
202
+ "scheduledFor": "ISO date or null for ASAP",
203
+ "expiresAt": "ISO date or null for no expiry",
204
+ "estimatedCost": number or null,
205
+ "dependsOnTasks": ["task titles this depends on"]
206
+ }
207
+ ],
208
+ "warnings": ["Any concerns or notes"],
209
+ "estimatedEffort": "e.g., '2-3 hours' or '1 day'"
210
+ }
211
+
212
+ Generate tasks that are:
213
+ 1. Specific and actionable
214
+ 2. Appropriate for the current progress level
215
+ 3. Ordered by priority and dependency
216
+ 4. Realistic for the target date (if set)
217
+
218
+ Respond ONLY with valid JSON, no markdown code blocks or other text.`;
219
+ }
220
+ parseGenerationResponse(response, goal, options) {
221
+ try {
222
+ let cleanResponse = response.trim();
223
+ if (cleanResponse.startsWith('```json')) {
224
+ cleanResponse = cleanResponse.slice(7);
225
+ }
226
+ else if (cleanResponse.startsWith('```')) {
227
+ cleanResponse = cleanResponse.slice(3);
228
+ }
229
+ if (cleanResponse.endsWith('```')) {
230
+ cleanResponse = cleanResponse.slice(0, -3);
231
+ }
232
+ cleanResponse = cleanResponse.trim();
233
+ const parsed = JSON.parse(cleanResponse);
234
+ const suggestions = [];
235
+ if (Array.isArray(parsed.suggestions)) {
236
+ for (const s of parsed.suggestions) {
237
+ const suggestion = this.validateSuggestion(s);
238
+ if (suggestion) {
239
+ suggestions.push(suggestion);
240
+ }
241
+ }
242
+ }
243
+ return {
244
+ suggestions,
245
+ planSummary: parsed.planSummary ?? 'Task plan generated',
246
+ warnings: Array.isArray(parsed.warnings) ? parsed.warnings : [],
247
+ estimatedEffort: parsed.estimatedEffort,
248
+ metadata: {
249
+ generatedAt: new Date().toISOString(),
250
+ modelTier: options.modelTier ?? this.defaultModelTier,
251
+ },
252
+ };
253
+ }
254
+ catch (err) {
255
+ this.logger.error('TASK_GENERATOR', 'Failed to parse LLM response', {
256
+ response: response.substring(0, 500),
257
+ error: err instanceof Error ? err.message : String(err),
258
+ });
259
+ throw new LLMParseError(`Failed to parse task generation response: ${err instanceof Error ? err.message : String(err)}`);
260
+ }
261
+ }
262
+ validateSuggestion(raw) {
263
+ if (!raw || typeof raw !== 'object') {
264
+ return null;
265
+ }
266
+ const s = raw;
267
+ const title = typeof s.title === 'string' ? s.title.trim() : '';
268
+ const description = typeof s.description === 'string' ? s.description.trim() : '';
269
+ const type = this.validateTaskType(s.type);
270
+ if (!title || !type) {
271
+ this.logger.warn('TASK_GENERATOR', 'Invalid suggestion - missing fields', {
272
+ hasTitle: !!title,
273
+ hasType: !!type,
274
+ });
275
+ return null;
276
+ }
277
+ let priority = 5;
278
+ if (typeof s.priority === 'number') {
279
+ priority = Math.max(1, Math.min(10, Math.round(s.priority)));
280
+ }
281
+ return {
282
+ title,
283
+ description,
284
+ type,
285
+ priority,
286
+ reasoning: typeof s.reasoning === 'string' ? s.reasoning : '',
287
+ scheduledFor: typeof s.scheduledFor === 'string' ? s.scheduledFor : undefined,
288
+ expiresAt: typeof s.expiresAt === 'string' ? s.expiresAt : undefined,
289
+ milestoneId: typeof s.milestoneId === 'number' ? s.milestoneId : undefined,
290
+ estimatedCost: typeof s.estimatedCost === 'number' ? s.estimatedCost : undefined,
291
+ dependsOnTasks: Array.isArray(s.dependsOnTasks)
292
+ ? s.dependsOnTasks.filter((d) => typeof d === 'string')
293
+ : undefined,
294
+ };
295
+ }
296
+ validateTaskType(raw) {
297
+ if (typeof raw !== 'string') {
298
+ return null;
299
+ }
300
+ const normalized = raw.toLowerCase().trim();
301
+ if (TASK_TYPES.includes(normalized)) {
302
+ return normalized;
303
+ }
304
+ const mappings = {
305
+ search: 'research',
306
+ read: 'research',
307
+ investigate: 'research',
308
+ write: 'draft',
309
+ create: 'draft',
310
+ document: 'draft',
311
+ coding: 'code',
312
+ implement: 'code',
313
+ develop: 'code',
314
+ run: 'execute',
315
+ command: 'execute',
316
+ script: 'execute',
317
+ workflow: 'recipe',
318
+ automation: 'recipe',
319
+ recurring: 'loop',
320
+ schedule: 'loop',
321
+ alert: 'notify',
322
+ message: 'notify',
323
+ };
324
+ return mappings[normalized] ?? null;
325
+ }
326
+ }
327
+ export function createTaskGenerator(options) {
328
+ return new TaskGenerator(options);
329
+ }
@@ -0,0 +1,84 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { Task, TaskStatus, TaskType, GoalHash, Logger } from './types.js';
3
+ import { GoalError } from './errors.js';
4
+ export interface QueuedTask {
5
+ readonly task: Task;
6
+ readonly goalHash: GoalHash;
7
+ readonly queuedAt: Date;
8
+ readonly priorityScore: number;
9
+ readonly isReady: boolean;
10
+ readonly notReadyReason?: string;
11
+ }
12
+ export interface TaskQueueOptions {
13
+ readonly maxTasksPerDay?: number;
14
+ readonly quietHours?: {
15
+ readonly start: number;
16
+ readonly end: number;
17
+ };
18
+ readonly enforceQuietHours?: boolean;
19
+ readonly typeWeights?: Partial<Record<TaskType, number>>;
20
+ readonly logger?: Logger;
21
+ }
22
+ export interface TaskQueueStats {
23
+ readonly totalPending: number;
24
+ readonly readyCount: number;
25
+ readonly scheduledCount: number;
26
+ readonly expiredCount: number;
27
+ readonly executedToday: number;
28
+ readonly remainingQuota: number;
29
+ readonly inQuietHours: boolean;
30
+ readonly byGoal: Map<GoalHash, number>;
31
+ readonly byType: Map<TaskType, number>;
32
+ }
33
+ export interface TaskQueueEvents {
34
+ taskAdded: [task: Task, goalHash: GoalHash];
35
+ taskRemoved: [taskId: number, goalHash: GoalHash, reason: string];
36
+ taskExpired: [task: Task, goalHash: GoalHash];
37
+ queueCleared: [goalHash?: GoalHash];
38
+ quietHoursStarted: [];
39
+ quietHoursEnded: [];
40
+ quotaReached: [executedCount: number];
41
+ }
42
+ export interface TaskFilterOptions {
43
+ readonly goalHash?: GoalHash;
44
+ readonly type?: TaskType;
45
+ readonly status?: TaskStatus;
46
+ readonly readyOnly?: boolean;
47
+ readonly limit?: number;
48
+ }
49
+ export declare class TaskQueueError extends GoalError {
50
+ constructor(message: string, cause?: Error);
51
+ }
52
+ export declare class QuotaExceededError extends GoalError {
53
+ constructor(executed: number, max: number);
54
+ }
55
+ export declare class TaskQueue extends EventEmitter<TaskQueueEvents> {
56
+ private readonly maxTasksPerDay;
57
+ private readonly quietHours;
58
+ private readonly enforceQuietHours;
59
+ private readonly typeWeights;
60
+ private readonly logger;
61
+ private readonly tasks;
62
+ private executedToday;
63
+ private lastResetDate;
64
+ private quietHoursTimer;
65
+ constructor(options?: TaskQueueOptions);
66
+ addTask(task: Task, goalHash: GoalHash): void;
67
+ addTasks(tasks: Task[], goalHash: GoalHash): void;
68
+ removeTask(taskId: number, goalHash: GoalHash, reason?: string): boolean;
69
+ getNextTask(options?: TaskFilterOptions): QueuedTask | null;
70
+ getTasks(options?: TaskFilterOptions): QueuedTask[];
71
+ getReadyTasks(options?: Omit<TaskFilterOptions, 'readyOnly'>): QueuedTask[];
72
+ markTaskStarted(taskId: number, goalHash: GoalHash): void;
73
+ clearGoal(goalHash: GoalHash): number;
74
+ clearAll(): number;
75
+ getStats(): TaskQueueStats;
76
+ isInQuietHours(): boolean;
77
+ hasQuota(): boolean;
78
+ stop(): void;
79
+ private createQueuedTask;
80
+ private calculatePriorityScore;
81
+ private pruneExpiredTasks;
82
+ private resetDailyCounterIfNeeded;
83
+ }
84
+ export declare function createTaskQueue(options?: TaskQueueOptions): TaskQueue;