@juspay/neurolink 9.40.0 → 9.41.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 (56) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/browser/neurolink.min.js +440 -433
  3. package/dist/cli/commands/task.d.ts +56 -0
  4. package/dist/cli/commands/task.js +835 -0
  5. package/dist/cli/parser.js +4 -1
  6. package/dist/lib/neurolink.d.ts +16 -0
  7. package/dist/lib/neurolink.js +119 -2
  8. package/dist/lib/tasks/backends/bullmqBackend.d.ts +32 -0
  9. package/dist/lib/tasks/backends/bullmqBackend.js +189 -0
  10. package/dist/lib/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
  11. package/dist/lib/tasks/backends/nodeTimeoutBackend.js +141 -0
  12. package/dist/lib/tasks/backends/taskBackendRegistry.d.ts +31 -0
  13. package/dist/lib/tasks/backends/taskBackendRegistry.js +66 -0
  14. package/dist/lib/tasks/errors.d.ts +31 -0
  15. package/dist/lib/tasks/errors.js +18 -0
  16. package/dist/lib/tasks/store/fileTaskStore.d.ts +43 -0
  17. package/dist/lib/tasks/store/fileTaskStore.js +179 -0
  18. package/dist/lib/tasks/store/redisTaskStore.d.ts +42 -0
  19. package/dist/lib/tasks/store/redisTaskStore.js +189 -0
  20. package/dist/lib/tasks/taskExecutor.d.ts +21 -0
  21. package/dist/lib/tasks/taskExecutor.js +166 -0
  22. package/dist/lib/tasks/taskManager.d.ts +60 -0
  23. package/dist/lib/tasks/taskManager.js +393 -0
  24. package/dist/lib/tasks/tools/taskTools.d.ts +135 -0
  25. package/dist/lib/tasks/tools/taskTools.js +274 -0
  26. package/dist/lib/types/configTypes.d.ts +3 -0
  27. package/dist/lib/types/generateTypes.d.ts +13 -0
  28. package/dist/lib/types/index.d.ts +1 -0
  29. package/dist/lib/types/taskTypes.d.ts +275 -0
  30. package/dist/lib/types/taskTypes.js +37 -0
  31. package/dist/neurolink.d.ts +16 -0
  32. package/dist/neurolink.js +119 -2
  33. package/dist/tasks/backends/bullmqBackend.d.ts +32 -0
  34. package/dist/tasks/backends/bullmqBackend.js +188 -0
  35. package/dist/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
  36. package/dist/tasks/backends/nodeTimeoutBackend.js +140 -0
  37. package/dist/tasks/backends/taskBackendRegistry.d.ts +31 -0
  38. package/dist/tasks/backends/taskBackendRegistry.js +65 -0
  39. package/dist/tasks/errors.d.ts +31 -0
  40. package/dist/tasks/errors.js +17 -0
  41. package/dist/tasks/store/fileTaskStore.d.ts +43 -0
  42. package/dist/tasks/store/fileTaskStore.js +178 -0
  43. package/dist/tasks/store/redisTaskStore.d.ts +42 -0
  44. package/dist/tasks/store/redisTaskStore.js +188 -0
  45. package/dist/tasks/taskExecutor.d.ts +21 -0
  46. package/dist/tasks/taskExecutor.js +165 -0
  47. package/dist/tasks/taskManager.d.ts +60 -0
  48. package/dist/tasks/taskManager.js +392 -0
  49. package/dist/tasks/tools/taskTools.d.ts +135 -0
  50. package/dist/tasks/tools/taskTools.js +273 -0
  51. package/dist/types/configTypes.d.ts +3 -0
  52. package/dist/types/generateTypes.d.ts +13 -0
  53. package/dist/types/index.d.ts +1 -0
  54. package/dist/types/taskTypes.d.ts +275 -0
  55. package/dist/types/taskTypes.js +36 -0
  56. package/package.json +3 -1
@@ -0,0 +1,166 @@
1
+ /**
2
+ * TaskExecutor — Runs a single task execution against NeuroLink.generate().
3
+ *
4
+ * Handles:
5
+ * - Isolated mode: fresh generate() call with no history
6
+ * - Continuation mode: loads conversation history, appends new exchange
7
+ * - Retry with exponential backoff for transient errors
8
+ * - Run result construction and logging
9
+ */
10
+ import { nanoid } from "nanoid";
11
+ import { logger } from "../utils/logger.js";
12
+ /** Errors that are transient and should be retried */
13
+ const TRANSIENT_PATTERNS = [
14
+ "rate limit",
15
+ "rate_limit",
16
+ "too many requests",
17
+ "429",
18
+ "503",
19
+ "502",
20
+ "504",
21
+ "timeout",
22
+ "econnreset",
23
+ "econnrefused",
24
+ "network",
25
+ "overloaded",
26
+ ];
27
+ function isTransientError(error) {
28
+ const msg = String(error).toLowerCase();
29
+ return TRANSIENT_PATTERNS.some((p) => msg.includes(p));
30
+ }
31
+ export class TaskExecutor {
32
+ neurolink;
33
+ store;
34
+ constructor(neurolink, store) {
35
+ this.neurolink = neurolink;
36
+ this.store = store;
37
+ }
38
+ /**
39
+ * Execute a task once. Called by the backend on each scheduled tick.
40
+ * Returns the run result (success or error).
41
+ */
42
+ async execute(task) {
43
+ const runId = `run_${nanoid(12)}`;
44
+ const startTime = Date.now();
45
+ let lastError;
46
+ for (let attempt = 1; attempt <= task.retry.maxAttempts; attempt++) {
47
+ try {
48
+ const result = await this.executeOnce(task, runId);
49
+ return result;
50
+ }
51
+ catch (err) {
52
+ lastError = String(err);
53
+ const willRetry = attempt < task.retry.maxAttempts && isTransientError(err);
54
+ logger.warn("[TaskExecutor] Execution attempt failed", {
55
+ taskId: task.id,
56
+ runId,
57
+ attempt,
58
+ willRetry,
59
+ error: lastError,
60
+ });
61
+ if (!willRetry) {
62
+ break;
63
+ }
64
+ // Backoff before retry
65
+ const backoffIndex = Math.min(attempt - 1, task.retry.backoffMs.length - 1);
66
+ const delay = task.retry.backoffMs[backoffIndex];
67
+ await sleep(delay);
68
+ }
69
+ }
70
+ // All retries exhausted or permanent error
71
+ const errorResult = {
72
+ taskId: task.id,
73
+ runId,
74
+ status: "error",
75
+ error: lastError,
76
+ durationMs: Date.now() - startTime,
77
+ timestamp: new Date().toISOString(),
78
+ };
79
+ return errorResult;
80
+ }
81
+ // ── Internal ──────────────────────────────────────────
82
+ async executeOnce(task, runId) {
83
+ const startTime = Date.now();
84
+ // Build generate options
85
+ const generateOptions = {
86
+ input: { text: task.prompt },
87
+ ...(task.provider ? { provider: task.provider } : {}),
88
+ ...(task.model ? { model: task.model } : {}),
89
+ ...(task.systemPrompt ? { systemPrompt: task.systemPrompt } : {}),
90
+ ...(task.maxTokens ? { maxTokens: task.maxTokens } : {}),
91
+ ...(task.temperature !== undefined
92
+ ? { temperature: task.temperature }
93
+ : {}),
94
+ ...(task.timeout ? { timeout: task.timeout } : {}),
95
+ ...(!task.tools ? { disableTools: true } : {}),
96
+ };
97
+ // Thinking level
98
+ if (task.thinkingLevel) {
99
+ generateOptions.thinkingConfig = { thinkingLevel: task.thinkingLevel };
100
+ }
101
+ // Continuation mode: pass conversation history as proper multi-turn messages
102
+ if (task.mode === "continuation" && task.sessionId) {
103
+ const history = await this.store.getHistory(task.id);
104
+ // Pass history as proper role-based conversation messages
105
+ if (history.length > 0) {
106
+ generateOptions.conversationMessages = history.map((entry, i) => ({
107
+ id: `${task.sessionId}_${i}`,
108
+ role: entry.role,
109
+ content: entry.content,
110
+ timestamp: entry.timestamp,
111
+ }));
112
+ }
113
+ // Add continuation context to system prompt
114
+ const runCount = Math.floor(history.length / 2);
115
+ const continuationHint = runCount > 0
116
+ ? `This is a continuation task (run ${runCount + 1}). Your previous ${runCount} exchange(s) are provided as conversation history.`
117
+ : "This is a continuation task. This is the first execution — no prior history exists yet.";
118
+ generateOptions.systemPrompt = task.systemPrompt
119
+ ? `${task.systemPrompt}\n\n${continuationHint}`
120
+ : continuationHint;
121
+ }
122
+ // Execute
123
+ const result = await this.neurolink.generate(generateOptions);
124
+ // Build run result
125
+ const runResult = {
126
+ taskId: task.id,
127
+ runId,
128
+ status: "success",
129
+ output: result.content,
130
+ toolCalls: result.toolExecutions?.map((te) => ({
131
+ name: te.name,
132
+ input: te.input,
133
+ output: te.output,
134
+ })),
135
+ tokensUsed: result.usage
136
+ ? {
137
+ input: result.usage.input ?? 0,
138
+ output: result.usage.output ?? 0,
139
+ }
140
+ : undefined,
141
+ durationMs: Date.now() - startTime,
142
+ timestamp: new Date().toISOString(),
143
+ };
144
+ // Continuation mode: append this exchange to history
145
+ if (task.mode === "continuation" && task.sessionId) {
146
+ const newEntries = [
147
+ {
148
+ role: "user",
149
+ content: task.prompt,
150
+ timestamp: new Date(startTime).toISOString(),
151
+ },
152
+ {
153
+ role: "assistant",
154
+ content: result.content,
155
+ timestamp: runResult.timestamp,
156
+ },
157
+ ];
158
+ await this.store.appendHistory(task.id, newEntries);
159
+ }
160
+ return runResult;
161
+ }
162
+ }
163
+ function sleep(ms) {
164
+ return new Promise((resolve) => setTimeout(resolve, ms));
165
+ }
166
+ //# sourceMappingURL=taskExecutor.js.map
@@ -0,0 +1,60 @@
1
+ /**
2
+ * TaskManager — Main orchestrator for scheduled and self-running tasks.
3
+ *
4
+ * Manages the full task lifecycle: create, schedule, execute, pause, resume, delete.
5
+ * Auto-selects TaskStore and TaskBackend based on config.
6
+ *
7
+ * Usage:
8
+ * const neurolink = new NeuroLink({ tasks: { backend: "bullmq" } });
9
+ * await neurolink.tasks.create({ name: "monitor", prompt: "...", schedule: { type: "interval", every: 60000 } });
10
+ */
11
+ import { type NeuroLinkExecutable, type Task, type TaskDefinition, type TaskManagerConfig, type TaskRunResult, type TaskStatus } from "../types/taskTypes.js";
12
+ export declare class TaskManager {
13
+ private neurolink;
14
+ private config;
15
+ private store;
16
+ private backend;
17
+ private executor;
18
+ private initialized;
19
+ private initPromise;
20
+ /** In-memory callback registry (not serializable to store) */
21
+ private callbacks;
22
+ /** Emitter reference — set by NeuroLink on integration */
23
+ private emitter?;
24
+ constructor(neurolink: NeuroLinkExecutable, config?: TaskManagerConfig);
25
+ /** Set the event emitter (called by NeuroLink during integration) */
26
+ setEmitter(emitter: {
27
+ emit(event: string, ...args: unknown[]): boolean;
28
+ }): void;
29
+ private ensureInitialized;
30
+ private doInitialize;
31
+ create(definition: TaskDefinition): Promise<Task>;
32
+ get(taskId: string): Promise<Task | null>;
33
+ list(filter?: {
34
+ status?: TaskStatus;
35
+ }): Promise<Task[]>;
36
+ update(taskId: string, updates: Partial<TaskDefinition>): Promise<Task>;
37
+ /** Run a task immediately (outside of its schedule) */
38
+ run(taskId: string): Promise<TaskRunResult>;
39
+ pause(taskId: string): Promise<Task>;
40
+ resume(taskId: string): Promise<Task>;
41
+ delete(taskId: string): Promise<void>;
42
+ runs(taskId: string, options?: {
43
+ limit?: number;
44
+ status?: string;
45
+ }): Promise<TaskRunResult[]>;
46
+ shutdown(): Promise<void>;
47
+ /** Check if the backend is healthy */
48
+ isHealthy(): Promise<boolean>;
49
+ /**
50
+ * Called by the backend on each scheduled tick.
51
+ * Executes the task, updates state, fires callbacks/events.
52
+ */
53
+ private onTaskTick;
54
+ /**
55
+ * Re-schedule all active tasks from store.
56
+ * Called on initialization to handle process restarts.
57
+ */
58
+ private rescheduleActiveTasks;
59
+ private emit;
60
+ }
@@ -0,0 +1,393 @@
1
+ /**
2
+ * TaskManager — Main orchestrator for scheduled and self-running tasks.
3
+ *
4
+ * Manages the full task lifecycle: create, schedule, execute, pause, resume, delete.
5
+ * Auto-selects TaskStore and TaskBackend based on config.
6
+ *
7
+ * Usage:
8
+ * const neurolink = new NeuroLink({ tasks: { backend: "bullmq" } });
9
+ * await neurolink.tasks.create({ name: "monitor", prompt: "...", schedule: { type: "interval", every: 60000 } });
10
+ */
11
+ import { nanoid } from "nanoid";
12
+ import { logger } from "../utils/logger.js";
13
+ import { TaskBackendRegistry } from "./backends/taskBackendRegistry.js";
14
+ import { TaskError } from "./errors.js";
15
+ import { TaskExecutor } from "./taskExecutor.js";
16
+ import { TASK_DEFAULTS, } from "../types/taskTypes.js";
17
+ export class TaskManager {
18
+ neurolink;
19
+ config;
20
+ store = null;
21
+ backend = null;
22
+ executor = null;
23
+ initialized = false;
24
+ initPromise = null;
25
+ /** In-memory callback registry (not serializable to store) */
26
+ callbacks = new Map();
27
+ /** Emitter reference — set by NeuroLink on integration */
28
+ emitter;
29
+ constructor(neurolink, config) {
30
+ this.neurolink = neurolink;
31
+ this.config = { ...config };
32
+ }
33
+ /** Set the event emitter (called by NeuroLink during integration) */
34
+ setEmitter(emitter) {
35
+ this.emitter = emitter;
36
+ }
37
+ // ── Initialization ──────────────────────────────────────
38
+ async ensureInitialized() {
39
+ if (this.initialized) {
40
+ return;
41
+ }
42
+ if (this.initPromise) {
43
+ return this.initPromise;
44
+ }
45
+ this.initPromise = this.doInitialize();
46
+ await this.initPromise;
47
+ }
48
+ async doInitialize() {
49
+ const backendName = this.config.backend ?? TASK_DEFAULTS.backend;
50
+ // Create store based on backend
51
+ if (backendName === "bullmq") {
52
+ const { RedisTaskStore } = await import("./store/redisTaskStore.js");
53
+ this.store = new RedisTaskStore(this.config);
54
+ }
55
+ else {
56
+ const { FileTaskStore } = await import("./store/fileTaskStore.js");
57
+ this.store = new FileTaskStore(this.config);
58
+ }
59
+ await this.store.initialize();
60
+ // Create backend
61
+ this.backend = await TaskBackendRegistry.create(backendName, this.config);
62
+ await this.backend.initialize();
63
+ // Create executor
64
+ this.executor = new TaskExecutor(this.neurolink, this.store);
65
+ // Re-schedule active tasks from store (handles restarts)
66
+ await this.rescheduleActiveTasks();
67
+ this.initialized = true;
68
+ logger.info("[TaskManager] Initialized", {
69
+ backend: backendName,
70
+ store: this.store.type,
71
+ });
72
+ }
73
+ // ── Public API ────────────────────────────────────────
74
+ async create(definition) {
75
+ if (this.config.enabled === false) {
76
+ throw TaskError.create("TASK_DISABLED", "TaskManager is disabled. Set tasks.enabled to true in config.");
77
+ }
78
+ await this.ensureInitialized();
79
+ // Enforce maximum task limit to prevent unbounded task creation
80
+ const maxTasks = this.config.maxTasks ?? TASK_DEFAULTS.maxTasks;
81
+ const existingTasks = await this.store.list();
82
+ if (existingTasks.length >= maxTasks) {
83
+ throw TaskError.create("TASK_LIMIT_REACHED", `Task limit reached (${maxTasks}). Delete existing tasks or increase maxTasks config.`);
84
+ }
85
+ const now = new Date().toISOString();
86
+ const task = {
87
+ id: `task_${nanoid(12)}`,
88
+ name: definition.name,
89
+ prompt: definition.prompt,
90
+ schedule: definition.schedule,
91
+ mode: definition.mode ?? TASK_DEFAULTS.mode,
92
+ status: "active",
93
+ tools: definition.tools ?? TASK_DEFAULTS.tools,
94
+ timeout: definition.timeout ?? TASK_DEFAULTS.timeout,
95
+ retry: {
96
+ maxAttempts: definition.retry?.maxAttempts ?? TASK_DEFAULTS.retry.maxAttempts,
97
+ backoffMs: definition.retry?.backoffMs ?? [
98
+ ...TASK_DEFAULTS.retry.backoffMs,
99
+ ],
100
+ },
101
+ runCount: 0,
102
+ createdAt: now,
103
+ updatedAt: now,
104
+ // Optional overrides
105
+ ...(definition.provider ? { provider: definition.provider } : {}),
106
+ ...(definition.model ? { model: definition.model } : {}),
107
+ ...(definition.thinkingLevel
108
+ ? { thinkingLevel: definition.thinkingLevel }
109
+ : {}),
110
+ ...(definition.systemPrompt
111
+ ? { systemPrompt: definition.systemPrompt }
112
+ : {}),
113
+ ...(definition.maxTokens ? { maxTokens: definition.maxTokens } : {}),
114
+ ...(definition.temperature !== undefined
115
+ ? { temperature: definition.temperature }
116
+ : {}),
117
+ ...(definition.maxRuns !== undefined
118
+ ? { maxRuns: definition.maxRuns }
119
+ : {}),
120
+ ...(definition.metadata ? { metadata: definition.metadata } : {}),
121
+ };
122
+ // Generate session ID for continuation mode
123
+ if (task.mode === "continuation") {
124
+ task.sessionId = `session_${nanoid(12)}`;
125
+ }
126
+ // Save to store
127
+ await this.store.save(task);
128
+ // Register callbacks (in-memory only)
129
+ if (definition.onSuccess || definition.onError || definition.onComplete) {
130
+ this.callbacks.set(task.id, {
131
+ onSuccess: definition.onSuccess,
132
+ onError: definition.onError,
133
+ onComplete: definition.onComplete,
134
+ });
135
+ }
136
+ // Schedule
137
+ try {
138
+ await this.backend.schedule(task, (t) => this.onTaskTick(t));
139
+ }
140
+ catch (err) {
141
+ await this.store.delete(task.id);
142
+ throw err;
143
+ }
144
+ this.emit("task:created", task);
145
+ logger.info("[TaskManager] Task created", {
146
+ taskId: task.id,
147
+ name: task.name,
148
+ schedule: task.schedule.type,
149
+ mode: task.mode,
150
+ });
151
+ return task;
152
+ }
153
+ async get(taskId) {
154
+ await this.ensureInitialized();
155
+ return this.store.get(taskId);
156
+ }
157
+ async list(filter) {
158
+ await this.ensureInitialized();
159
+ return this.store.list(filter);
160
+ }
161
+ async update(taskId, updates) {
162
+ await this.ensureInitialized();
163
+ const existing = await this.store.get(taskId);
164
+ if (!existing) {
165
+ throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
166
+ }
167
+ // Apply allowed scalar updates via whitelist
168
+ const ALLOWED_UPDATE_FIELDS = [
169
+ "name",
170
+ "prompt",
171
+ "schedule",
172
+ "mode",
173
+ "provider",
174
+ "model",
175
+ "systemPrompt",
176
+ "maxTokens",
177
+ "temperature",
178
+ "timeout",
179
+ "tools",
180
+ "maxRuns",
181
+ "metadata",
182
+ "thinkingLevel",
183
+ ];
184
+ const taskUpdates = {};
185
+ for (const field of ALLOWED_UPDATE_FIELDS) {
186
+ if (updates[field] !== undefined) {
187
+ taskUpdates[field] = updates[field];
188
+ }
189
+ }
190
+ // Special-case: mode changes require sessionId handling
191
+ if (updates.mode !== undefined) {
192
+ if (updates.mode === "continuation" && !existing.sessionId) {
193
+ taskUpdates.sessionId = `session_${nanoid(12)}`;
194
+ }
195
+ else if (updates.mode !== "continuation") {
196
+ taskUpdates.sessionId = undefined;
197
+ await this.store.clearHistory(taskId);
198
+ }
199
+ }
200
+ const updated = await this.store.update(taskId, taskUpdates);
201
+ // Re-schedule if schedule changed and task is active
202
+ if (updates.schedule && updated.status === "active") {
203
+ await this.backend.cancel(taskId);
204
+ await this.backend.schedule(updated, (t) => this.onTaskTick(t));
205
+ }
206
+ return updated;
207
+ }
208
+ /** Run a task immediately (outside of its schedule) */
209
+ async run(taskId) {
210
+ await this.ensureInitialized();
211
+ const task = await this.store.get(taskId);
212
+ if (!task) {
213
+ throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
214
+ }
215
+ return this.onTaskTick(task);
216
+ }
217
+ async pause(taskId) {
218
+ await this.ensureInitialized();
219
+ const task = await this.store.get(taskId);
220
+ if (!task) {
221
+ throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
222
+ }
223
+ if (task.status !== "active") {
224
+ throw TaskError.create("INVALID_TASK_STATUS", `Cannot pause task with status: ${task.status}`);
225
+ }
226
+ await this.backend.pause(taskId);
227
+ const updated = await this.store.update(taskId, { status: "paused" });
228
+ this.emit("task:paused", updated);
229
+ return updated;
230
+ }
231
+ async resume(taskId) {
232
+ await this.ensureInitialized();
233
+ const task = await this.store.get(taskId);
234
+ if (!task) {
235
+ throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
236
+ }
237
+ if (task.status !== "paused") {
238
+ throw TaskError.create("INVALID_TASK_STATUS", `Cannot resume task with status: ${task.status}`);
239
+ }
240
+ const updated = await this.store.update(taskId, { status: "active" });
241
+ await this.backend.schedule(updated, (t) => this.onTaskTick(t));
242
+ this.emit("task:resumed", updated);
243
+ return updated;
244
+ }
245
+ async delete(taskId) {
246
+ await this.ensureInitialized();
247
+ await this.backend.cancel(taskId);
248
+ await this.store.delete(taskId);
249
+ this.callbacks.delete(taskId);
250
+ this.emit("task:deleted", taskId);
251
+ }
252
+ async runs(taskId, options) {
253
+ await this.ensureInitialized();
254
+ return this.store.getRuns(taskId, options);
255
+ }
256
+ async shutdown() {
257
+ if (this.backend) {
258
+ await this.backend.shutdown();
259
+ }
260
+ if (this.store) {
261
+ await this.store.shutdown();
262
+ }
263
+ this.callbacks.clear();
264
+ this.initialized = false;
265
+ this.initPromise = null;
266
+ logger.info("[TaskManager] Shut down");
267
+ }
268
+ /** Check if the backend is healthy */
269
+ async isHealthy() {
270
+ if (!this.backend) {
271
+ return false;
272
+ }
273
+ return this.backend.isHealthy();
274
+ }
275
+ // ── Internal ──────────────────────────────────────────
276
+ /**
277
+ * Called by the backend on each scheduled tick.
278
+ * Executes the task, updates state, fires callbacks/events.
279
+ */
280
+ async onTaskTick(task) {
281
+ this.emit("task:started", task);
282
+ // Re-read latest task state (may have been updated/paused since scheduling)
283
+ const current = await this.store.get(task.id);
284
+ if (!current || current.status !== "active") {
285
+ logger.debug("[TaskManager] Skipping tick for non-active task", {
286
+ taskId: task.id,
287
+ status: current?.status,
288
+ });
289
+ return {
290
+ taskId: task.id,
291
+ runId: "skipped",
292
+ status: "error",
293
+ error: "Task is not active",
294
+ durationMs: 0,
295
+ timestamp: new Date().toISOString(),
296
+ };
297
+ }
298
+ const result = await this.executor.execute(current);
299
+ // Log the run
300
+ await this.store.appendRun(task.id, result);
301
+ // Update task tracking
302
+ const updates = {
303
+ runCount: current.runCount + 1,
304
+ lastRunAt: result.timestamp,
305
+ };
306
+ // Check if task should complete
307
+ if (current.maxRuns && current.runCount + 1 >= current.maxRuns) {
308
+ updates.status = "completed";
309
+ await this.backend.cancel(task.id);
310
+ }
311
+ // Mark successful once tasks as completed
312
+ if (result.status === "success" && current.schedule.type === "once") {
313
+ updates.status = "completed";
314
+ await this.backend.cancel(task.id);
315
+ }
316
+ // Mark as failed on permanent error
317
+ if (result.status === "error" && current.schedule.type === "once") {
318
+ updates.status = "failed";
319
+ }
320
+ await this.store.update(task.id, updates);
321
+ // Fire callbacks
322
+ const cbs = this.callbacks.get(task.id);
323
+ if (cbs) {
324
+ try {
325
+ if (result.status === "success" && cbs.onSuccess) {
326
+ await cbs.onSuccess(result);
327
+ }
328
+ if (result.status === "error" && cbs.onError) {
329
+ await cbs.onError({
330
+ taskId: task.id,
331
+ runId: result.runId,
332
+ error: result.error ?? "Unknown error",
333
+ attempt: 1, // Executor handles retries internally and returns final result
334
+ maxAttempts: current.retry.maxAttempts,
335
+ willRetry: false,
336
+ timestamp: result.timestamp,
337
+ });
338
+ }
339
+ if (updates.status === "completed" || updates.status === "failed") {
340
+ const finalTask = await this.store.get(task.id);
341
+ if (finalTask && cbs.onComplete) {
342
+ await cbs.onComplete(finalTask);
343
+ }
344
+ }
345
+ }
346
+ catch (cbErr) {
347
+ logger.error("[TaskManager] Callback error", {
348
+ taskId: task.id,
349
+ error: String(cbErr),
350
+ });
351
+ }
352
+ }
353
+ // Emit events
354
+ if (result.status === "success") {
355
+ this.emit("task:completed", result);
356
+ }
357
+ else {
358
+ this.emit("task:failed", result);
359
+ }
360
+ return result;
361
+ }
362
+ /**
363
+ * Re-schedule all active tasks from store.
364
+ * Called on initialization to handle process restarts.
365
+ */
366
+ async rescheduleActiveTasks() {
367
+ const activeTasks = await this.store.list({ status: "active" });
368
+ for (const task of activeTasks) {
369
+ try {
370
+ await this.backend.schedule(task, (t) => this.onTaskTick(t));
371
+ logger.debug("[TaskManager] Re-scheduled task", {
372
+ taskId: task.id,
373
+ name: task.name,
374
+ });
375
+ }
376
+ catch (err) {
377
+ logger.error("[TaskManager] Failed to re-schedule task", {
378
+ taskId: task.id,
379
+ error: String(err),
380
+ });
381
+ }
382
+ }
383
+ if (activeTasks.length > 0) {
384
+ logger.info("[TaskManager] Re-scheduled active tasks", {
385
+ count: activeTasks.length,
386
+ });
387
+ }
388
+ }
389
+ emit(event, ...args) {
390
+ this.emitter?.emit(event, ...args);
391
+ }
392
+ }
393
+ //# sourceMappingURL=taskManager.js.map