@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.
- package/LICENSE +45 -0
- package/README.md +51 -0
- package/dist/goals/DependencyResolver.d.ts +54 -0
- package/dist/goals/DependencyResolver.js +329 -0
- package/dist/goals/ErrorRecovery.d.ts +133 -0
- package/dist/goals/ErrorRecovery.js +489 -0
- package/dist/goals/GoalApiClient.d.ts +81 -0
- package/dist/goals/GoalApiClient.js +743 -0
- package/dist/goals/GoalCache.d.ts +65 -0
- package/dist/goals/GoalCache.js +243 -0
- package/dist/goals/GoalCommsHandler.d.ts +150 -0
- package/dist/goals/GoalCommsHandler.js +378 -0
- package/dist/goals/GoalExporter.d.ts +164 -0
- package/dist/goals/GoalExporter.js +318 -0
- package/dist/goals/GoalImporter.d.ts +107 -0
- package/dist/goals/GoalImporter.js +345 -0
- package/dist/goals/GoalManager.d.ts +110 -0
- package/dist/goals/GoalManager.js +535 -0
- package/dist/goals/GoalReporter.d.ts +105 -0
- package/dist/goals/GoalReporter.js +534 -0
- package/dist/goals/GoalScheduler.d.ts +102 -0
- package/dist/goals/GoalScheduler.js +209 -0
- package/dist/goals/GoalValidator.d.ts +72 -0
- package/dist/goals/GoalValidator.js +657 -0
- package/dist/goals/MetaGoalEnforcer.d.ts +111 -0
- package/dist/goals/MetaGoalEnforcer.js +536 -0
- package/dist/goals/MilestoneBreaker.d.ts +74 -0
- package/dist/goals/MilestoneBreaker.js +348 -0
- package/dist/goals/PermissionBridge.d.ts +109 -0
- package/dist/goals/PermissionBridge.js +326 -0
- package/dist/goals/ProgressTracker.d.ts +113 -0
- package/dist/goals/ProgressTracker.js +324 -0
- package/dist/goals/ReviewScheduler.d.ts +106 -0
- package/dist/goals/ReviewScheduler.js +360 -0
- package/dist/goals/TaskExecutor.d.ts +116 -0
- package/dist/goals/TaskExecutor.js +370 -0
- package/dist/goals/TaskFeedback.d.ts +126 -0
- package/dist/goals/TaskFeedback.js +402 -0
- package/dist/goals/TaskGenerator.d.ts +75 -0
- package/dist/goals/TaskGenerator.js +329 -0
- package/dist/goals/TaskQueue.d.ts +84 -0
- package/dist/goals/TaskQueue.js +331 -0
- package/dist/goals/TaskSanitizer.d.ts +61 -0
- package/dist/goals/TaskSanitizer.js +464 -0
- package/dist/goals/errors.d.ts +116 -0
- package/dist/goals/errors.js +299 -0
- package/dist/goals/index.d.ts +24 -0
- package/dist/goals/index.js +23 -0
- package/dist/goals/types.d.ts +395 -0
- package/dist/goals/types.js +230 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/loop/DaemonIPC.d.ts +67 -0
- package/dist/loop/DaemonIPC.js +358 -0
- package/dist/loop/IntervalParser.d.ts +39 -0
- package/dist/loop/IntervalParser.js +217 -0
- package/dist/loop/LoopDaemon.d.ts +123 -0
- package/dist/loop/LoopDaemon.js +1821 -0
- package/dist/loop/LoopExecutor.d.ts +93 -0
- package/dist/loop/LoopExecutor.js +326 -0
- package/dist/loop/LoopManager.d.ts +79 -0
- package/dist/loop/LoopManager.js +476 -0
- package/dist/loop/LoopScheduler.d.ts +69 -0
- package/dist/loop/LoopScheduler.js +329 -0
- package/dist/loop/LoopStore.d.ts +57 -0
- package/dist/loop/LoopStore.js +406 -0
- package/dist/loop/LoopValidator.d.ts +55 -0
- package/dist/loop/LoopValidator.js +603 -0
- package/dist/loop/errors.d.ts +115 -0
- package/dist/loop/errors.js +312 -0
- package/dist/loop/index.d.ts +11 -0
- package/dist/loop/index.js +10 -0
- package/dist/loop/notifications/Notifier.d.ts +28 -0
- package/dist/loop/notifications/Notifier.js +78 -0
- package/dist/loop/notifications/SlackNotifier.d.ts +28 -0
- package/dist/loop/notifications/SlackNotifier.js +203 -0
- package/dist/loop/notifications/TerminalNotifier.d.ts +18 -0
- package/dist/loop/notifications/TerminalNotifier.js +72 -0
- package/dist/loop/notifications/WebhookNotifier.d.ts +24 -0
- package/dist/loop/notifications/WebhookNotifier.js +123 -0
- package/dist/loop/notifications/index.d.ts +24 -0
- package/dist/loop/notifications/index.js +109 -0
- package/dist/loop/types.d.ts +280 -0
- package/dist/loop/types.js +222 -0
- package/package.json +92 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { Loop, LoopRun, LoopRunId, LoopId, LoopRunError, LoopConfig, Logger } from './types.js';
|
|
3
|
+
export interface ExecutionResult {
|
|
4
|
+
readonly run: LoopRun;
|
|
5
|
+
readonly success: boolean;
|
|
6
|
+
readonly output: string | null;
|
|
7
|
+
readonly error: LoopRunError | null;
|
|
8
|
+
readonly duration: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ExecutionContext {
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly conversationHistory: Array<{
|
|
13
|
+
role: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}>;
|
|
16
|
+
readonly linkedRecipes: Array<{
|
|
17
|
+
alias: string;
|
|
18
|
+
hash: string;
|
|
19
|
+
}>;
|
|
20
|
+
readonly onOutput?: (text: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface LoopExecutorOptions {
|
|
23
|
+
defaultTimeoutMs?: number;
|
|
24
|
+
onStart?: (loopId: LoopId, runId: LoopRunId) => void;
|
|
25
|
+
onOutput?: (loopId: LoopId, runId: LoopRunId, text: string) => void;
|
|
26
|
+
onComplete?: (loopId: LoopId, result: ExecutionResult) => void;
|
|
27
|
+
onError?: (loopId: LoopId, runId: LoopRunId, error: Error) => void;
|
|
28
|
+
getAgenticResponse?: AgentResponseFunction;
|
|
29
|
+
executeAction?: ActionExecutorFunction;
|
|
30
|
+
logger?: Logger;
|
|
31
|
+
}
|
|
32
|
+
export type ExecutorOptions = LoopExecutorOptions;
|
|
33
|
+
export type AgentResponseFunction = (input: string, conversationHistory: Array<{
|
|
34
|
+
role: string;
|
|
35
|
+
content: string;
|
|
36
|
+
}>, linkedRecipes: Array<{
|
|
37
|
+
alias: string;
|
|
38
|
+
hash: string;
|
|
39
|
+
}>, options?: {
|
|
40
|
+
modelTier?: string;
|
|
41
|
+
maxTokens?: number;
|
|
42
|
+
onToken?: (tokenDelta: number) => void;
|
|
43
|
+
}) => Promise<AgentResponse>;
|
|
44
|
+
export interface AgentResponse {
|
|
45
|
+
message: string;
|
|
46
|
+
action: AgentAction;
|
|
47
|
+
}
|
|
48
|
+
export interface AgentAction {
|
|
49
|
+
type: string;
|
|
50
|
+
payload?: unknown;
|
|
51
|
+
}
|
|
52
|
+
export type ActionExecutorFunction = (action: AgentAction, context: ExecutionContext) => Promise<ActionResult>;
|
|
53
|
+
export interface ActionResult {
|
|
54
|
+
success: boolean;
|
|
55
|
+
error?: string;
|
|
56
|
+
data?: unknown;
|
|
57
|
+
permissionDenied?: boolean;
|
|
58
|
+
requiresPermission?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export interface ExecutorEvents {
|
|
61
|
+
start: [loopId: LoopId, runId: LoopRunId];
|
|
62
|
+
output: [loopId: LoopId, runId: LoopRunId, text: string];
|
|
63
|
+
complete: [loopId: LoopId, result: ExecutionResult];
|
|
64
|
+
error: [loopId: LoopId, runId: LoopRunId, error: Error];
|
|
65
|
+
timeout: [loopId: LoopId, runId: LoopRunId, timeoutMs: number];
|
|
66
|
+
}
|
|
67
|
+
export declare class LoopExecutor extends EventEmitter {
|
|
68
|
+
private readonly maxRunDurationMs;
|
|
69
|
+
private readonly getAgenticResponseFn;
|
|
70
|
+
private readonly executeActionFn;
|
|
71
|
+
private readonly logger;
|
|
72
|
+
private readonly runningExecutions;
|
|
73
|
+
constructor(options?: LoopExecutorOptions);
|
|
74
|
+
on<K extends keyof ExecutorEvents>(event: K, listener: (...args: ExecutorEvents[K]) => void): this;
|
|
75
|
+
emit<K extends keyof ExecutorEvents>(event: K, ...args: ExecutorEvents[K]): boolean;
|
|
76
|
+
off<K extends keyof ExecutorEvents>(event: K, listener: (...args: ExecutorEvents[K]) => void): this;
|
|
77
|
+
execute(loop: Loop): Promise<ExecutionResult>;
|
|
78
|
+
cancel(runId: LoopRunId): boolean;
|
|
79
|
+
cancelAll(loopId: LoopId): number;
|
|
80
|
+
cancelAllExecutions(): number;
|
|
81
|
+
isRunning(runId: LoopRunId): boolean;
|
|
82
|
+
getRunningExecutionIds(): LoopRunId[];
|
|
83
|
+
getRunningExecutionsForLoop(loopId: LoopId): LoopRunId[];
|
|
84
|
+
getRunningCount(): number;
|
|
85
|
+
private executePrompt;
|
|
86
|
+
private createTimeoutPromise;
|
|
87
|
+
private wrapExecutionError;
|
|
88
|
+
}
|
|
89
|
+
export declare function createLoopExecutor(options?: LoopExecutorOptions): LoopExecutor;
|
|
90
|
+
export declare function createLoopExecutorWithConfig(config: LoopConfig, options?: Omit<LoopExecutorOptions, 'maxRunDurationMs'>): LoopExecutor;
|
|
91
|
+
export declare function createEmptyRun(loopId: LoopId): LoopRun;
|
|
92
|
+
export declare function createSuccessfulRun(loopId: LoopId, output: string, durationMs: number): LoopRun;
|
|
93
|
+
export declare function createFailedRun(loopId: LoopId, error: LoopRunError, output: string | null, durationMs: number): LoopRun;
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
import msPkg from 'ms';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { LoopExecutionError, LoopExecutionTimeoutError, } from './errors.js';
|
|
5
|
+
function parseDurationString(value) {
|
|
6
|
+
return msPkg(value);
|
|
7
|
+
}
|
|
8
|
+
const DEFAULT_MAX_RUN_DURATION_MS = 30 * 60 * 1000;
|
|
9
|
+
const MAX_OUTPUT_BUFFER_SIZE = 1024 * 1024;
|
|
10
|
+
const noopLogger = {
|
|
11
|
+
debug: () => { },
|
|
12
|
+
info: () => { },
|
|
13
|
+
warn: () => { },
|
|
14
|
+
error: () => { },
|
|
15
|
+
};
|
|
16
|
+
export class LoopExecutor extends EventEmitter {
|
|
17
|
+
maxRunDurationMs;
|
|
18
|
+
getAgenticResponseFn;
|
|
19
|
+
executeActionFn;
|
|
20
|
+
logger;
|
|
21
|
+
runningExecutions = new Map();
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
super();
|
|
24
|
+
this.maxRunDurationMs = options.defaultTimeoutMs ?? DEFAULT_MAX_RUN_DURATION_MS;
|
|
25
|
+
this.getAgenticResponseFn = options.getAgenticResponse ?? null;
|
|
26
|
+
this.executeActionFn = options.executeAction ?? null;
|
|
27
|
+
this.logger = options.logger ?? noopLogger;
|
|
28
|
+
if (options.onStart) {
|
|
29
|
+
this.on('start', options.onStart);
|
|
30
|
+
}
|
|
31
|
+
if (options.onOutput) {
|
|
32
|
+
this.on('output', options.onOutput);
|
|
33
|
+
}
|
|
34
|
+
if (options.onComplete) {
|
|
35
|
+
this.on('complete', options.onComplete);
|
|
36
|
+
}
|
|
37
|
+
if (options.onError) {
|
|
38
|
+
this.on('error', options.onError);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
on(event, listener) {
|
|
42
|
+
return super.on(event, listener);
|
|
43
|
+
}
|
|
44
|
+
emit(event, ...args) {
|
|
45
|
+
return super.emit(event, ...args);
|
|
46
|
+
}
|
|
47
|
+
off(event, listener) {
|
|
48
|
+
return super.off(event, listener);
|
|
49
|
+
}
|
|
50
|
+
async execute(loop) {
|
|
51
|
+
const runId = uuidv4();
|
|
52
|
+
const startedAt = new Date();
|
|
53
|
+
const abortController = new AbortController();
|
|
54
|
+
const run = {
|
|
55
|
+
id: runId,
|
|
56
|
+
loopId: loop.id,
|
|
57
|
+
startedAt,
|
|
58
|
+
completedAt: null,
|
|
59
|
+
status: 'running',
|
|
60
|
+
duration: null,
|
|
61
|
+
output: null,
|
|
62
|
+
error: null,
|
|
63
|
+
};
|
|
64
|
+
this.runningExecutions.set(runId, {
|
|
65
|
+
loopId: loop.id,
|
|
66
|
+
abortController,
|
|
67
|
+
startedAt,
|
|
68
|
+
});
|
|
69
|
+
this.emit('start', loop.id, runId);
|
|
70
|
+
const outputBuffer = [];
|
|
71
|
+
let outputSize = 0;
|
|
72
|
+
const handleOutput = (text) => {
|
|
73
|
+
if (outputSize + text.length <= MAX_OUTPUT_BUFFER_SIZE) {
|
|
74
|
+
outputBuffer.push(text);
|
|
75
|
+
outputSize += text.length;
|
|
76
|
+
}
|
|
77
|
+
this.emit('output', loop.id, runId, text);
|
|
78
|
+
};
|
|
79
|
+
const timeoutPromise = this.createTimeoutPromise(loop.id, runId, this.maxRunDurationMs, abortController);
|
|
80
|
+
try {
|
|
81
|
+
const executionPromise = this.executePrompt(loop, runId, handleOutput, abortController.signal);
|
|
82
|
+
const result = await Promise.race([executionPromise, timeoutPromise]);
|
|
83
|
+
const completedAt = new Date();
|
|
84
|
+
const duration = completedAt.getTime() - startedAt.getTime();
|
|
85
|
+
const finalRun = {
|
|
86
|
+
...run,
|
|
87
|
+
completedAt,
|
|
88
|
+
status: result.success ? 'success' : 'error',
|
|
89
|
+
duration,
|
|
90
|
+
output: outputBuffer.join('') || null,
|
|
91
|
+
error: result.error,
|
|
92
|
+
};
|
|
93
|
+
const executionResult = {
|
|
94
|
+
run: finalRun,
|
|
95
|
+
success: result.success,
|
|
96
|
+
output: finalRun.output,
|
|
97
|
+
error: result.error,
|
|
98
|
+
duration,
|
|
99
|
+
};
|
|
100
|
+
if (!result.success && result.error && this.listenerCount('error') > 0) {
|
|
101
|
+
const loopError = new LoopExecutionError(loop.id, runId, result.error.message);
|
|
102
|
+
this.emit('error', loop.id, runId, loopError);
|
|
103
|
+
}
|
|
104
|
+
this.emit('complete', loop.id, executionResult);
|
|
105
|
+
return executionResult;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
const completedAt = new Date();
|
|
109
|
+
const duration = completedAt.getTime() - startedAt.getTime();
|
|
110
|
+
const loopError = this.wrapExecutionError(error, loop.id, runId);
|
|
111
|
+
const runError = {
|
|
112
|
+
code: loopError.code,
|
|
113
|
+
message: loopError.message,
|
|
114
|
+
stack: loopError.stack ?? null,
|
|
115
|
+
};
|
|
116
|
+
const finalRun = {
|
|
117
|
+
...run,
|
|
118
|
+
completedAt,
|
|
119
|
+
status: 'error',
|
|
120
|
+
duration,
|
|
121
|
+
output: outputBuffer.join('') || null,
|
|
122
|
+
error: runError,
|
|
123
|
+
};
|
|
124
|
+
const executionResult = {
|
|
125
|
+
run: finalRun,
|
|
126
|
+
success: false,
|
|
127
|
+
output: finalRun.output,
|
|
128
|
+
error: runError,
|
|
129
|
+
duration,
|
|
130
|
+
};
|
|
131
|
+
if (this.listenerCount('error') > 0) {
|
|
132
|
+
this.emit('error', loop.id, runId, loopError);
|
|
133
|
+
}
|
|
134
|
+
this.emit('complete', loop.id, executionResult);
|
|
135
|
+
return executionResult;
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
this.runningExecutions.delete(runId);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
cancel(runId) {
|
|
142
|
+
const execution = this.runningExecutions.get(runId);
|
|
143
|
+
if (!execution) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
execution.abortController.abort();
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
cancelAll(loopId) {
|
|
150
|
+
let cancelled = 0;
|
|
151
|
+
for (const [, execution] of this.runningExecutions) {
|
|
152
|
+
if (execution.loopId === loopId) {
|
|
153
|
+
execution.abortController.abort();
|
|
154
|
+
cancelled++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return cancelled;
|
|
158
|
+
}
|
|
159
|
+
cancelAllExecutions() {
|
|
160
|
+
let cancelled = 0;
|
|
161
|
+
for (const [, execution] of this.runningExecutions) {
|
|
162
|
+
execution.abortController.abort();
|
|
163
|
+
cancelled++;
|
|
164
|
+
}
|
|
165
|
+
return cancelled;
|
|
166
|
+
}
|
|
167
|
+
isRunning(runId) {
|
|
168
|
+
return this.runningExecutions.has(runId);
|
|
169
|
+
}
|
|
170
|
+
getRunningExecutionIds() {
|
|
171
|
+
return Array.from(this.runningExecutions.keys());
|
|
172
|
+
}
|
|
173
|
+
getRunningExecutionsForLoop(loopId) {
|
|
174
|
+
const result = [];
|
|
175
|
+
for (const [runId, execution] of this.runningExecutions) {
|
|
176
|
+
if (execution.loopId === loopId) {
|
|
177
|
+
result.push(runId);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
getRunningCount() {
|
|
183
|
+
return this.runningExecutions.size;
|
|
184
|
+
}
|
|
185
|
+
async executePrompt(loop, runId, onOutput, signal) {
|
|
186
|
+
if (signal.aborted) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
error: {
|
|
190
|
+
code: 'LOOP_EXECUTION_ERROR',
|
|
191
|
+
message: 'Execution was cancelled',
|
|
192
|
+
stack: null,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const context = {
|
|
197
|
+
cwd: loop.cwd,
|
|
198
|
+
conversationHistory: [],
|
|
199
|
+
linkedRecipes: [],
|
|
200
|
+
onOutput,
|
|
201
|
+
};
|
|
202
|
+
try {
|
|
203
|
+
if (!this.getAgenticResponseFn) {
|
|
204
|
+
throw new Error('Agent response function not configured. Inject via constructor options.');
|
|
205
|
+
}
|
|
206
|
+
const response = await this.getAgenticResponseFn(loop.prompt, context.conversationHistory, context.linkedRecipes, {
|
|
207
|
+
modelTier: loop.options.model,
|
|
208
|
+
});
|
|
209
|
+
if (signal.aborted) {
|
|
210
|
+
return {
|
|
211
|
+
success: false,
|
|
212
|
+
error: {
|
|
213
|
+
code: 'LOOP_EXECUTION_ERROR',
|
|
214
|
+
message: 'Execution was cancelled',
|
|
215
|
+
stack: null,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (response.message) {
|
|
220
|
+
onOutput(response.message);
|
|
221
|
+
}
|
|
222
|
+
if (response.action && response.action.type !== 'none') {
|
|
223
|
+
if (!this.executeActionFn) {
|
|
224
|
+
throw new Error('Action executor function not configured. Inject via constructor options.');
|
|
225
|
+
}
|
|
226
|
+
const actionResult = await this.executeActionFn(response.action, context);
|
|
227
|
+
if (!actionResult.success) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
error: {
|
|
231
|
+
code: 'LOOP_EXECUTION_ERROR',
|
|
232
|
+
message: actionResult.error ?? 'Action execution failed',
|
|
233
|
+
stack: null,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return { success: true, error: null };
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
242
|
+
const errorStack = error instanceof Error ? error.stack : null;
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
error: {
|
|
246
|
+
code: 'LOOP_EXECUTION_ERROR',
|
|
247
|
+
message: errorMessage,
|
|
248
|
+
stack: errorStack ?? null,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
createTimeoutPromise(loopId, runId, timeoutMs, abortController) {
|
|
254
|
+
return new Promise((_, reject) => {
|
|
255
|
+
const timer = setTimeout(() => {
|
|
256
|
+
abortController.abort();
|
|
257
|
+
this.emit('timeout', loopId, runId, timeoutMs);
|
|
258
|
+
reject(new LoopExecutionTimeoutError(loopId, runId, timeoutMs));
|
|
259
|
+
}, timeoutMs);
|
|
260
|
+
abortController.signal.addEventListener('abort', () => {
|
|
261
|
+
clearTimeout(timer);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
wrapExecutionError(error, loopId, runId) {
|
|
266
|
+
if (error instanceof LoopExecutionTimeoutError) {
|
|
267
|
+
return error;
|
|
268
|
+
}
|
|
269
|
+
if (error instanceof LoopExecutionError) {
|
|
270
|
+
return error;
|
|
271
|
+
}
|
|
272
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
273
|
+
return new LoopExecutionError(loopId, runId, cause.message, cause);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
export function createLoopExecutor(options) {
|
|
277
|
+
return new LoopExecutor(options);
|
|
278
|
+
}
|
|
279
|
+
export function createLoopExecutorWithConfig(config, options) {
|
|
280
|
+
const maxRunDurationMs = parseDurationString(config.maxRunDuration);
|
|
281
|
+
if (maxRunDurationMs === undefined) {
|
|
282
|
+
throw new Error(`Invalid maxRunDuration config: ${config.maxRunDuration}`);
|
|
283
|
+
}
|
|
284
|
+
return new LoopExecutor({
|
|
285
|
+
...options,
|
|
286
|
+
defaultTimeoutMs: maxRunDurationMs,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
export function createEmptyRun(loopId) {
|
|
290
|
+
return {
|
|
291
|
+
id: uuidv4(),
|
|
292
|
+
loopId,
|
|
293
|
+
startedAt: new Date(),
|
|
294
|
+
completedAt: null,
|
|
295
|
+
status: 'running',
|
|
296
|
+
duration: null,
|
|
297
|
+
output: null,
|
|
298
|
+
error: null,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
export function createSuccessfulRun(loopId, output, durationMs) {
|
|
302
|
+
const startedAt = new Date(Date.now() - durationMs);
|
|
303
|
+
return {
|
|
304
|
+
id: uuidv4(),
|
|
305
|
+
loopId,
|
|
306
|
+
startedAt,
|
|
307
|
+
completedAt: new Date(),
|
|
308
|
+
status: 'success',
|
|
309
|
+
duration: durationMs,
|
|
310
|
+
output,
|
|
311
|
+
error: null,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
export function createFailedRun(loopId, error, output, durationMs) {
|
|
315
|
+
const startedAt = new Date(Date.now() - durationMs);
|
|
316
|
+
return {
|
|
317
|
+
id: uuidv4(),
|
|
318
|
+
loopId,
|
|
319
|
+
startedAt,
|
|
320
|
+
completedAt: new Date(),
|
|
321
|
+
status: 'error',
|
|
322
|
+
duration: durationMs,
|
|
323
|
+
output,
|
|
324
|
+
error,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { Loop, LoopId, LoopRun, LoopRunId, LoopStatus, LoopConfig, LoopCreateInput, Logger } from './types.js';
|
|
3
|
+
import type { AgentResponseFunction, ActionExecutorFunction } from './LoopExecutor.js';
|
|
4
|
+
import { LoopScheduler } from './LoopScheduler.js';
|
|
5
|
+
import { type ExecutionResult } from './LoopExecutor.js';
|
|
6
|
+
export interface ManagerEvents {
|
|
7
|
+
created: [loop: Loop];
|
|
8
|
+
started: [loop: Loop];
|
|
9
|
+
paused: [loop: Loop];
|
|
10
|
+
resumed: [loop: Loop];
|
|
11
|
+
stopped: [loop: Loop];
|
|
12
|
+
deleted: [loopId: LoopId];
|
|
13
|
+
expired: [loop: Loop];
|
|
14
|
+
runStarted: [loop: Loop, runId: LoopRunId];
|
|
15
|
+
runCompleted: [loop: Loop, result: ExecutionResult];
|
|
16
|
+
runFailed: [loop: Loop, error: Error];
|
|
17
|
+
autoPaused: [loop: Loop, reason: string];
|
|
18
|
+
error: [error: Error, loopId?: LoopId];
|
|
19
|
+
}
|
|
20
|
+
export interface LoopManagerOptions {
|
|
21
|
+
baseDir?: string;
|
|
22
|
+
config?: Partial<LoopConfig>;
|
|
23
|
+
getAgenticResponse?: AgentResponseFunction;
|
|
24
|
+
executeAction?: ActionExecutorFunction;
|
|
25
|
+
autoStart?: boolean;
|
|
26
|
+
logger?: Logger;
|
|
27
|
+
}
|
|
28
|
+
export interface ManagerStats {
|
|
29
|
+
totalLoops: number;
|
|
30
|
+
activeLoops: number;
|
|
31
|
+
pausedLoops: number;
|
|
32
|
+
errorLoops: number;
|
|
33
|
+
totalRuns: number;
|
|
34
|
+
successfulRuns: number;
|
|
35
|
+
failedRuns: number;
|
|
36
|
+
schedulerRunning: boolean;
|
|
37
|
+
schedulerStats: ReturnType<LoopScheduler['getStats']>;
|
|
38
|
+
}
|
|
39
|
+
export declare class LoopManager extends EventEmitter {
|
|
40
|
+
private readonly store;
|
|
41
|
+
private readonly scheduler;
|
|
42
|
+
private readonly executor;
|
|
43
|
+
private readonly validator;
|
|
44
|
+
private readonly intervalParser;
|
|
45
|
+
private readonly config;
|
|
46
|
+
private readonly logger;
|
|
47
|
+
private initialized;
|
|
48
|
+
constructor(options?: LoopManagerOptions);
|
|
49
|
+
initialize(): Promise<void>;
|
|
50
|
+
start(): void;
|
|
51
|
+
stop(): void;
|
|
52
|
+
shutdown(): Promise<void>;
|
|
53
|
+
createLoop(input: LoopCreateInput): Promise<Loop>;
|
|
54
|
+
getLoop(loopId: LoopId): Promise<Loop>;
|
|
55
|
+
getLoopByName(name: string): Promise<Loop | null>;
|
|
56
|
+
getAllLoops(): Promise<Loop[]>;
|
|
57
|
+
getLoopsByStatus(status: LoopStatus): Promise<Loop[]>;
|
|
58
|
+
pauseLoop(loopId: LoopId): Promise<Loop>;
|
|
59
|
+
resumeLoop(loopId: LoopId): Promise<Loop>;
|
|
60
|
+
stopLoop(loopId: LoopId): Promise<Loop>;
|
|
61
|
+
deleteLoop(loopId: LoopId): Promise<void>;
|
|
62
|
+
runNow(loopId: LoopId): Promise<LoopRun>;
|
|
63
|
+
getRunHistory(loopId: LoopId, limit?: number): Promise<LoopRun[]>;
|
|
64
|
+
getRun(loopId: LoopId, runId: LoopRunId): Promise<LoopRun>;
|
|
65
|
+
getStats(): Promise<ManagerStats>;
|
|
66
|
+
getConfig(): Readonly<LoopConfig>;
|
|
67
|
+
isInitialized(): boolean;
|
|
68
|
+
isRunning(): boolean;
|
|
69
|
+
private setupEventHandlers;
|
|
70
|
+
private executeLoop;
|
|
71
|
+
private updateLoopStats;
|
|
72
|
+
private handleErrorThreshold;
|
|
73
|
+
private handleExpiredLoop;
|
|
74
|
+
private sendNotification;
|
|
75
|
+
private calculateExpiration;
|
|
76
|
+
private isLoopExpired;
|
|
77
|
+
private ensureInitialized;
|
|
78
|
+
}
|
|
79
|
+
export declare function createLoopManager(options?: LoopManagerOptions): LoopManager;
|