@juspay/neurolink 9.40.0 → 9.42.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/CHANGELOG.md +12 -0
- package/README.md +7 -1
- package/dist/auth/anthropicOAuth.d.ts +18 -3
- package/dist/auth/anthropicOAuth.js +137 -4
- package/dist/auth/providers/firebase.js +5 -1
- package/dist/auth/providers/jwt.js +5 -1
- package/dist/auth/providers/workos.js +5 -1
- package/dist/auth/sessionManager.d.ts +1 -1
- package/dist/auth/sessionManager.js +58 -27
- package/dist/browser/neurolink.min.js +471 -445
- package/dist/cli/commands/mcp.js +3 -0
- package/dist/cli/commands/proxy.d.ts +2 -1
- package/dist/cli/commands/proxy.js +279 -16
- package/dist/cli/commands/task.d.ts +56 -0
- package/dist/cli/commands/task.js +838 -0
- package/dist/cli/factories/commandFactory.d.ts +2 -0
- package/dist/cli/factories/commandFactory.js +38 -0
- package/dist/cli/parser.js +8 -4
- package/dist/client/aiSdkAdapter.js +3 -0
- package/dist/client/streamingClient.js +30 -10
- package/dist/core/modules/GenerationHandler.js +3 -2
- package/dist/core/redisConversationMemoryManager.js +7 -3
- package/dist/evaluation/BatchEvaluator.js +4 -1
- package/dist/evaluation/hooks/observabilityHooks.js +5 -3
- package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
- package/dist/evaluation/pipeline/evaluationPipeline.js +20 -8
- package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
- package/dist/lib/auth/anthropicOAuth.js +137 -4
- package/dist/lib/auth/providers/firebase.js +5 -1
- package/dist/lib/auth/providers/jwt.js +5 -1
- package/dist/lib/auth/providers/workos.js +5 -1
- package/dist/lib/auth/sessionManager.d.ts +1 -1
- package/dist/lib/auth/sessionManager.js +58 -27
- package/dist/lib/client/aiSdkAdapter.js +3 -0
- package/dist/lib/client/streamingClient.js +30 -10
- package/dist/lib/core/modules/GenerationHandler.js +3 -2
- package/dist/lib/core/redisConversationMemoryManager.js +7 -3
- package/dist/lib/evaluation/BatchEvaluator.js +4 -1
- package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
- package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
- package/dist/lib/evaluation/pipeline/evaluationPipeline.js +20 -8
- package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
- package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
- package/dist/lib/neurolink.d.ts +18 -1
- package/dist/lib/neurolink.js +367 -484
- package/dist/lib/observability/otelBridge.d.ts +2 -2
- package/dist/lib/observability/otelBridge.js +12 -3
- package/dist/lib/providers/amazonBedrock.js +2 -4
- package/dist/lib/providers/anthropic.d.ts +9 -5
- package/dist/lib/providers/anthropic.js +19 -14
- package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
- package/dist/lib/providers/anthropicBaseProvider.js +5 -4
- package/dist/lib/providers/azureOpenai.d.ts +1 -1
- package/dist/lib/providers/azureOpenai.js +5 -4
- package/dist/lib/providers/googleAiStudio.js +30 -1
- package/dist/lib/providers/googleVertex.js +28 -6
- package/dist/lib/providers/huggingFace.d.ts +3 -3
- package/dist/lib/providers/huggingFace.js +6 -8
- package/dist/lib/providers/litellm.js +41 -29
- package/dist/lib/providers/mistral.js +2 -1
- package/dist/lib/providers/ollama.js +80 -23
- package/dist/lib/providers/openAI.js +3 -2
- package/dist/lib/providers/openRouter.js +2 -1
- package/dist/lib/providers/openaiCompatible.d.ts +4 -4
- package/dist/lib/providers/openaiCompatible.js +4 -4
- package/dist/lib/proxy/claudeFormat.d.ts +3 -2
- package/dist/lib/proxy/claudeFormat.js +25 -20
- package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
- package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
- package/dist/lib/proxy/modelRouter.js +3 -0
- package/dist/lib/proxy/oauthFetch.d.ts +1 -1
- package/dist/lib/proxy/oauthFetch.js +65 -72
- package/dist/lib/proxy/proxyConfig.js +44 -24
- package/dist/lib/proxy/proxyEnv.d.ts +19 -0
- package/dist/lib/proxy/proxyEnv.js +73 -0
- package/dist/lib/proxy/proxyFetch.js +50 -4
- package/dist/lib/proxy/proxyTracer.d.ts +133 -0
- package/dist/lib/proxy/proxyTracer.js +645 -0
- package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
- package/dist/lib/proxy/rawStreamCapture.js +83 -0
- package/dist/lib/proxy/requestLogger.d.ts +32 -5
- package/dist/lib/proxy/requestLogger.js +406 -37
- package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
- package/dist/lib/proxy/sseInterceptor.js +402 -0
- package/dist/lib/proxy/usageStats.d.ts +4 -3
- package/dist/lib/proxy/usageStats.js +25 -12
- package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
- package/dist/lib/rag/chunking/markdownChunker.js +15 -6
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +7 -2
- package/dist/lib/server/routes/claudeProxyRoutes.js +1737 -508
- package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/lib/services/server/ai/observability/instrumentation.js +240 -40
- package/dist/lib/tasks/backends/bullmqBackend.d.ts +33 -0
- package/dist/lib/tasks/backends/bullmqBackend.js +196 -0
- package/dist/lib/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
- package/dist/lib/tasks/backends/nodeTimeoutBackend.js +141 -0
- package/dist/lib/tasks/backends/taskBackendRegistry.d.ts +31 -0
- package/dist/lib/tasks/backends/taskBackendRegistry.js +66 -0
- package/dist/lib/tasks/errors.d.ts +31 -0
- package/dist/lib/tasks/errors.js +18 -0
- package/dist/lib/tasks/store/fileTaskStore.d.ts +43 -0
- package/dist/lib/tasks/store/fileTaskStore.js +179 -0
- package/dist/lib/tasks/store/redisTaskStore.d.ts +43 -0
- package/dist/lib/tasks/store/redisTaskStore.js +197 -0
- package/dist/lib/tasks/taskExecutor.d.ts +21 -0
- package/dist/lib/tasks/taskExecutor.js +166 -0
- package/dist/lib/tasks/taskManager.d.ts +63 -0
- package/dist/lib/tasks/taskManager.js +426 -0
- package/dist/lib/tasks/tools/taskTools.d.ts +135 -0
- package/dist/lib/tasks/tools/taskTools.js +274 -0
- package/dist/lib/telemetry/index.d.ts +2 -1
- package/dist/lib/telemetry/index.js +2 -1
- package/dist/lib/telemetry/telemetryService.d.ts +3 -0
- package/dist/lib/telemetry/telemetryService.js +65 -5
- package/dist/lib/types/cli.d.ts +10 -0
- package/dist/lib/types/configTypes.d.ts +3 -0
- package/dist/lib/types/generateTypes.d.ts +13 -0
- package/dist/lib/types/index.d.ts +1 -0
- package/dist/lib/types/proxyTypes.d.ts +37 -5
- package/dist/lib/types/streamTypes.d.ts +25 -3
- package/dist/lib/types/taskTypes.d.ts +275 -0
- package/dist/lib/types/taskTypes.js +37 -0
- package/dist/lib/utils/messageBuilder.js +3 -2
- package/dist/lib/utils/providerHealth.d.ts +18 -0
- package/dist/lib/utils/providerHealth.js +240 -9
- package/dist/lib/utils/providerUtils.js +14 -8
- package/dist/lib/utils/toolChoice.d.ts +4 -0
- package/dist/lib/utils/toolChoice.js +7 -0
- package/dist/neurolink.d.ts +18 -1
- package/dist/neurolink.js +367 -484
- package/dist/observability/otelBridge.d.ts +2 -2
- package/dist/observability/otelBridge.js +12 -3
- package/dist/providers/amazonBedrock.js +2 -4
- package/dist/providers/anthropic.d.ts +9 -5
- package/dist/providers/anthropic.js +19 -14
- package/dist/providers/anthropicBaseProvider.d.ts +3 -3
- package/dist/providers/anthropicBaseProvider.js +5 -4
- package/dist/providers/azureOpenai.d.ts +1 -1
- package/dist/providers/azureOpenai.js +5 -4
- package/dist/providers/googleAiStudio.js +30 -1
- package/dist/providers/googleVertex.js +28 -6
- package/dist/providers/huggingFace.d.ts +3 -3
- package/dist/providers/huggingFace.js +6 -7
- package/dist/providers/litellm.js +41 -29
- package/dist/providers/mistral.js +2 -1
- package/dist/providers/ollama.js +80 -23
- package/dist/providers/openAI.js +3 -2
- package/dist/providers/openRouter.js +2 -1
- package/dist/providers/openaiCompatible.d.ts +4 -4
- package/dist/providers/openaiCompatible.js +4 -3
- package/dist/proxy/claudeFormat.d.ts +3 -2
- package/dist/proxy/claudeFormat.js +25 -20
- package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
- package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
- package/dist/proxy/modelRouter.js +3 -0
- package/dist/proxy/oauthFetch.d.ts +1 -1
- package/dist/proxy/oauthFetch.js +65 -72
- package/dist/proxy/proxyConfig.js +44 -24
- package/dist/proxy/proxyEnv.d.ts +19 -0
- package/dist/proxy/proxyEnv.js +72 -0
- package/dist/proxy/proxyFetch.js +50 -4
- package/dist/proxy/proxyTracer.d.ts +133 -0
- package/dist/proxy/proxyTracer.js +644 -0
- package/dist/proxy/rawStreamCapture.d.ts +10 -0
- package/dist/proxy/rawStreamCapture.js +82 -0
- package/dist/proxy/requestLogger.d.ts +32 -5
- package/dist/proxy/requestLogger.js +406 -37
- package/dist/proxy/sseInterceptor.d.ts +97 -0
- package/dist/proxy/sseInterceptor.js +401 -0
- package/dist/proxy/usageStats.d.ts +4 -3
- package/dist/proxy/usageStats.js +25 -12
- package/dist/rag/chunkers/MarkdownChunker.js +13 -5
- package/dist/rag/chunking/markdownChunker.js +15 -6
- package/dist/server/routes/claudeProxyRoutes.d.ts +7 -2
- package/dist/server/routes/claudeProxyRoutes.js +1737 -508
- package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
- package/dist/services/server/ai/observability/instrumentation.js +240 -40
- package/dist/tasks/backends/bullmqBackend.d.ts +33 -0
- package/dist/tasks/backends/bullmqBackend.js +195 -0
- package/dist/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
- package/dist/tasks/backends/nodeTimeoutBackend.js +140 -0
- package/dist/tasks/backends/taskBackendRegistry.d.ts +31 -0
- package/dist/tasks/backends/taskBackendRegistry.js +65 -0
- package/dist/tasks/errors.d.ts +31 -0
- package/dist/tasks/errors.js +17 -0
- package/dist/tasks/store/fileTaskStore.d.ts +43 -0
- package/dist/tasks/store/fileTaskStore.js +178 -0
- package/dist/tasks/store/redisTaskStore.d.ts +43 -0
- package/dist/tasks/store/redisTaskStore.js +196 -0
- package/dist/tasks/taskExecutor.d.ts +21 -0
- package/dist/tasks/taskExecutor.js +165 -0
- package/dist/tasks/taskManager.d.ts +63 -0
- package/dist/tasks/taskManager.js +425 -0
- package/dist/tasks/tools/taskTools.d.ts +135 -0
- package/dist/tasks/tools/taskTools.js +273 -0
- package/dist/telemetry/index.d.ts +2 -1
- package/dist/telemetry/index.js +2 -1
- package/dist/telemetry/telemetryService.d.ts +3 -0
- package/dist/telemetry/telemetryService.js +65 -5
- package/dist/types/cli.d.ts +10 -0
- package/dist/types/configTypes.d.ts +3 -0
- package/dist/types/generateTypes.d.ts +13 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/proxyTypes.d.ts +37 -5
- package/dist/types/streamTypes.d.ts +25 -3
- package/dist/types/taskTypes.d.ts +275 -0
- package/dist/types/taskTypes.js +36 -0
- package/dist/utils/messageBuilder.js +3 -2
- package/dist/utils/providerHealth.d.ts +18 -0
- package/dist/utils/providerHealth.js +240 -9
- package/dist/utils/providerUtils.js +14 -8
- package/dist/utils/toolChoice.d.ts +4 -0
- package/dist/utils/toolChoice.js +6 -0
- package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
- package/docs/changelog.md +252 -0
- package/package.json +19 -1
- package/scripts/observability/check-proxy-telemetry.mjs +235 -0
- package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
- package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
- package/scripts/observability/manage-local-openobserve.sh +184 -0
- package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
- package/scripts/observability/proxy-observability.env.example +23 -0
|
@@ -0,0 +1,425 @@
|
|
|
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
|
+
getStore() {
|
|
74
|
+
if (!this.store) {
|
|
75
|
+
throw TaskError.create("BACKEND_NOT_INITIALIZED", "[TaskManager] Store not initialized. Call initialize() first.");
|
|
76
|
+
}
|
|
77
|
+
return this.store;
|
|
78
|
+
}
|
|
79
|
+
getBackend() {
|
|
80
|
+
if (!this.backend) {
|
|
81
|
+
throw TaskError.create("BACKEND_NOT_INITIALIZED", "[TaskManager] Backend not initialized. Call initialize() first.");
|
|
82
|
+
}
|
|
83
|
+
return this.backend;
|
|
84
|
+
}
|
|
85
|
+
getExecutor() {
|
|
86
|
+
if (!this.executor) {
|
|
87
|
+
throw TaskError.create("BACKEND_NOT_INITIALIZED", "[TaskManager] Executor not initialized. Call initialize() first.");
|
|
88
|
+
}
|
|
89
|
+
return this.executor;
|
|
90
|
+
}
|
|
91
|
+
// ── Public API ────────────────────────────────────────
|
|
92
|
+
async create(definition) {
|
|
93
|
+
if (this.config.enabled === false) {
|
|
94
|
+
throw TaskError.create("TASK_DISABLED", "TaskManager is disabled. Set tasks.enabled to true in config.");
|
|
95
|
+
}
|
|
96
|
+
await this.ensureInitialized();
|
|
97
|
+
const store = this.getStore();
|
|
98
|
+
const backend = this.getBackend();
|
|
99
|
+
// Enforce maximum task limit to prevent unbounded task creation
|
|
100
|
+
const maxTasks = this.config.maxTasks ?? TASK_DEFAULTS.maxTasks;
|
|
101
|
+
const existingTasks = await store.list();
|
|
102
|
+
if (existingTasks.length >= maxTasks) {
|
|
103
|
+
throw TaskError.create("TASK_LIMIT_REACHED", `Task limit reached (${maxTasks}). Delete existing tasks or increase maxTasks config.`);
|
|
104
|
+
}
|
|
105
|
+
const now = new Date().toISOString();
|
|
106
|
+
const task = {
|
|
107
|
+
id: `task_${nanoid(12)}`,
|
|
108
|
+
name: definition.name,
|
|
109
|
+
prompt: definition.prompt,
|
|
110
|
+
schedule: definition.schedule,
|
|
111
|
+
mode: definition.mode ?? TASK_DEFAULTS.mode,
|
|
112
|
+
status: "active",
|
|
113
|
+
tools: definition.tools ?? TASK_DEFAULTS.tools,
|
|
114
|
+
timeout: definition.timeout ?? TASK_DEFAULTS.timeout,
|
|
115
|
+
retry: {
|
|
116
|
+
maxAttempts: definition.retry?.maxAttempts ?? TASK_DEFAULTS.retry.maxAttempts,
|
|
117
|
+
backoffMs: definition.retry?.backoffMs ?? [
|
|
118
|
+
...TASK_DEFAULTS.retry.backoffMs,
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
runCount: 0,
|
|
122
|
+
createdAt: now,
|
|
123
|
+
updatedAt: now,
|
|
124
|
+
// Optional overrides
|
|
125
|
+
...(definition.provider ? { provider: definition.provider } : {}),
|
|
126
|
+
...(definition.model ? { model: definition.model } : {}),
|
|
127
|
+
...(definition.thinkingLevel
|
|
128
|
+
? { thinkingLevel: definition.thinkingLevel }
|
|
129
|
+
: {}),
|
|
130
|
+
...(definition.systemPrompt
|
|
131
|
+
? { systemPrompt: definition.systemPrompt }
|
|
132
|
+
: {}),
|
|
133
|
+
...(definition.maxTokens ? { maxTokens: definition.maxTokens } : {}),
|
|
134
|
+
...(definition.temperature !== undefined
|
|
135
|
+
? { temperature: definition.temperature }
|
|
136
|
+
: {}),
|
|
137
|
+
...(definition.maxRuns !== undefined
|
|
138
|
+
? { maxRuns: definition.maxRuns }
|
|
139
|
+
: {}),
|
|
140
|
+
...(definition.metadata ? { metadata: definition.metadata } : {}),
|
|
141
|
+
};
|
|
142
|
+
// Generate session ID for continuation mode
|
|
143
|
+
if (task.mode === "continuation") {
|
|
144
|
+
task.sessionId = `session_${nanoid(12)}`;
|
|
145
|
+
}
|
|
146
|
+
// Save to store
|
|
147
|
+
await store.save(task);
|
|
148
|
+
// Register callbacks (in-memory only)
|
|
149
|
+
if (definition.onSuccess || definition.onError || definition.onComplete) {
|
|
150
|
+
this.callbacks.set(task.id, {
|
|
151
|
+
onSuccess: definition.onSuccess,
|
|
152
|
+
onError: definition.onError,
|
|
153
|
+
onComplete: definition.onComplete,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// Schedule
|
|
157
|
+
try {
|
|
158
|
+
await backend.schedule(task, (t) => this.onTaskTick(t));
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
await store.delete(task.id);
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
this.emit("task:created", task);
|
|
165
|
+
logger.info("[TaskManager] Task created", {
|
|
166
|
+
taskId: task.id,
|
|
167
|
+
name: task.name,
|
|
168
|
+
schedule: task.schedule.type,
|
|
169
|
+
mode: task.mode,
|
|
170
|
+
});
|
|
171
|
+
return task;
|
|
172
|
+
}
|
|
173
|
+
async get(taskId) {
|
|
174
|
+
await this.ensureInitialized();
|
|
175
|
+
return this.getStore().get(taskId);
|
|
176
|
+
}
|
|
177
|
+
async list(filter) {
|
|
178
|
+
await this.ensureInitialized();
|
|
179
|
+
return this.getStore().list(filter);
|
|
180
|
+
}
|
|
181
|
+
async update(taskId, updates) {
|
|
182
|
+
await this.ensureInitialized();
|
|
183
|
+
const store = this.getStore();
|
|
184
|
+
const backend = this.getBackend();
|
|
185
|
+
const existing = await store.get(taskId);
|
|
186
|
+
if (!existing) {
|
|
187
|
+
throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
|
|
188
|
+
}
|
|
189
|
+
// Apply allowed scalar updates via whitelist
|
|
190
|
+
const ALLOWED_UPDATE_FIELDS = [
|
|
191
|
+
"name",
|
|
192
|
+
"prompt",
|
|
193
|
+
"schedule",
|
|
194
|
+
"mode",
|
|
195
|
+
"provider",
|
|
196
|
+
"model",
|
|
197
|
+
"systemPrompt",
|
|
198
|
+
"maxTokens",
|
|
199
|
+
"temperature",
|
|
200
|
+
"timeout",
|
|
201
|
+
"tools",
|
|
202
|
+
"maxRuns",
|
|
203
|
+
"metadata",
|
|
204
|
+
"thinkingLevel",
|
|
205
|
+
];
|
|
206
|
+
const taskUpdates = {};
|
|
207
|
+
for (const field of ALLOWED_UPDATE_FIELDS) {
|
|
208
|
+
if (updates[field] !== undefined) {
|
|
209
|
+
taskUpdates[field] = updates[field];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Special-case: mode changes require sessionId handling
|
|
213
|
+
if (updates.mode !== undefined) {
|
|
214
|
+
if (updates.mode === "continuation" && !existing.sessionId) {
|
|
215
|
+
taskUpdates.sessionId = `session_${nanoid(12)}`;
|
|
216
|
+
}
|
|
217
|
+
else if (updates.mode !== "continuation") {
|
|
218
|
+
taskUpdates.sessionId = undefined;
|
|
219
|
+
await store.clearHistory(taskId);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const updated = await store.update(taskId, taskUpdates);
|
|
223
|
+
// Re-schedule if schedule changed and task is active
|
|
224
|
+
if (updates.schedule && updated.status === "active") {
|
|
225
|
+
await backend.cancel(taskId);
|
|
226
|
+
await backend.schedule(updated, (t) => this.onTaskTick(t));
|
|
227
|
+
}
|
|
228
|
+
return updated;
|
|
229
|
+
}
|
|
230
|
+
/** Run a task immediately (outside of its schedule) */
|
|
231
|
+
async run(taskId) {
|
|
232
|
+
await this.ensureInitialized();
|
|
233
|
+
const task = await this.getStore().get(taskId);
|
|
234
|
+
if (!task) {
|
|
235
|
+
throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
|
|
236
|
+
}
|
|
237
|
+
return this.onTaskTick(task);
|
|
238
|
+
}
|
|
239
|
+
async pause(taskId) {
|
|
240
|
+
await this.ensureInitialized();
|
|
241
|
+
const store = this.getStore();
|
|
242
|
+
const backend = this.getBackend();
|
|
243
|
+
const task = await store.get(taskId);
|
|
244
|
+
if (!task) {
|
|
245
|
+
throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
|
|
246
|
+
}
|
|
247
|
+
if (task.status !== "active") {
|
|
248
|
+
throw TaskError.create("INVALID_TASK_STATUS", `Cannot pause task with status: ${task.status}`);
|
|
249
|
+
}
|
|
250
|
+
await backend.pause(taskId);
|
|
251
|
+
const updated = await store.update(taskId, { status: "paused" });
|
|
252
|
+
this.emit("task:paused", updated);
|
|
253
|
+
return updated;
|
|
254
|
+
}
|
|
255
|
+
async resume(taskId) {
|
|
256
|
+
await this.ensureInitialized();
|
|
257
|
+
const store = this.getStore();
|
|
258
|
+
const backend = this.getBackend();
|
|
259
|
+
const task = await store.get(taskId);
|
|
260
|
+
if (!task) {
|
|
261
|
+
throw TaskError.create("TASK_NOT_FOUND", `Task not found: ${taskId}`);
|
|
262
|
+
}
|
|
263
|
+
if (task.status !== "paused") {
|
|
264
|
+
throw TaskError.create("INVALID_TASK_STATUS", `Cannot resume task with status: ${task.status}`);
|
|
265
|
+
}
|
|
266
|
+
const updated = await store.update(taskId, { status: "active" });
|
|
267
|
+
await backend.schedule(updated, (t) => this.onTaskTick(t));
|
|
268
|
+
this.emit("task:resumed", updated);
|
|
269
|
+
return updated;
|
|
270
|
+
}
|
|
271
|
+
async delete(taskId) {
|
|
272
|
+
await this.ensureInitialized();
|
|
273
|
+
const backend = this.getBackend();
|
|
274
|
+
const store = this.getStore();
|
|
275
|
+
await backend.cancel(taskId);
|
|
276
|
+
await store.delete(taskId);
|
|
277
|
+
this.callbacks.delete(taskId);
|
|
278
|
+
this.emit("task:deleted", taskId);
|
|
279
|
+
}
|
|
280
|
+
async runs(taskId, options) {
|
|
281
|
+
await this.ensureInitialized();
|
|
282
|
+
return this.getStore().getRuns(taskId, options);
|
|
283
|
+
}
|
|
284
|
+
async shutdown() {
|
|
285
|
+
if (this.backend) {
|
|
286
|
+
await this.backend.shutdown();
|
|
287
|
+
}
|
|
288
|
+
if (this.store) {
|
|
289
|
+
await this.store.shutdown();
|
|
290
|
+
}
|
|
291
|
+
this.callbacks.clear();
|
|
292
|
+
this.initialized = false;
|
|
293
|
+
this.initPromise = null;
|
|
294
|
+
logger.info("[TaskManager] Shut down");
|
|
295
|
+
}
|
|
296
|
+
/** Check if the backend is healthy */
|
|
297
|
+
async isHealthy() {
|
|
298
|
+
if (!this.backend) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
return this.backend.isHealthy();
|
|
302
|
+
}
|
|
303
|
+
// ── Internal ──────────────────────────────────────────
|
|
304
|
+
/**
|
|
305
|
+
* Called by the backend on each scheduled tick.
|
|
306
|
+
* Executes the task, updates state, fires callbacks/events.
|
|
307
|
+
*/
|
|
308
|
+
async onTaskTick(task) {
|
|
309
|
+
this.emit("task:started", task);
|
|
310
|
+
const store = this.getStore();
|
|
311
|
+
const backend = this.getBackend();
|
|
312
|
+
const executor = this.getExecutor();
|
|
313
|
+
// Re-read latest task state (may have been updated/paused since scheduling)
|
|
314
|
+
const current = await store.get(task.id);
|
|
315
|
+
if (!current || current.status !== "active") {
|
|
316
|
+
logger.debug("[TaskManager] Skipping tick for non-active task", {
|
|
317
|
+
taskId: task.id,
|
|
318
|
+
status: current?.status,
|
|
319
|
+
});
|
|
320
|
+
return {
|
|
321
|
+
taskId: task.id,
|
|
322
|
+
runId: "skipped",
|
|
323
|
+
status: "error",
|
|
324
|
+
error: "Task is not active",
|
|
325
|
+
durationMs: 0,
|
|
326
|
+
timestamp: new Date().toISOString(),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const result = await executor.execute(current);
|
|
330
|
+
// Log the run
|
|
331
|
+
await store.appendRun(task.id, result);
|
|
332
|
+
// Update task tracking
|
|
333
|
+
const updates = {
|
|
334
|
+
runCount: current.runCount + 1,
|
|
335
|
+
lastRunAt: result.timestamp,
|
|
336
|
+
};
|
|
337
|
+
// Check if task should complete
|
|
338
|
+
if (current.maxRuns && current.runCount + 1 >= current.maxRuns) {
|
|
339
|
+
updates.status = "completed";
|
|
340
|
+
await backend.cancel(task.id);
|
|
341
|
+
}
|
|
342
|
+
// Mark successful once tasks as completed
|
|
343
|
+
if (result.status === "success" && current.schedule.type === "once") {
|
|
344
|
+
updates.status = "completed";
|
|
345
|
+
await backend.cancel(task.id);
|
|
346
|
+
}
|
|
347
|
+
// Mark as failed on permanent error
|
|
348
|
+
if (result.status === "error" && current.schedule.type === "once") {
|
|
349
|
+
updates.status = "failed";
|
|
350
|
+
}
|
|
351
|
+
await store.update(task.id, updates);
|
|
352
|
+
// Fire callbacks
|
|
353
|
+
const cbs = this.callbacks.get(task.id);
|
|
354
|
+
if (cbs) {
|
|
355
|
+
try {
|
|
356
|
+
if (result.status === "success" && cbs.onSuccess) {
|
|
357
|
+
await cbs.onSuccess(result);
|
|
358
|
+
}
|
|
359
|
+
if (result.status === "error" && cbs.onError) {
|
|
360
|
+
await cbs.onError({
|
|
361
|
+
taskId: task.id,
|
|
362
|
+
runId: result.runId,
|
|
363
|
+
error: result.error ?? "Unknown error",
|
|
364
|
+
attempt: 1, // Executor handles retries internally and returns final result
|
|
365
|
+
maxAttempts: current.retry.maxAttempts,
|
|
366
|
+
willRetry: false,
|
|
367
|
+
timestamp: result.timestamp,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (updates.status === "completed" || updates.status === "failed") {
|
|
371
|
+
const finalTask = await store.get(task.id);
|
|
372
|
+
if (finalTask && cbs.onComplete) {
|
|
373
|
+
await cbs.onComplete(finalTask);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch (cbErr) {
|
|
378
|
+
logger.error("[TaskManager] Callback error", {
|
|
379
|
+
taskId: task.id,
|
|
380
|
+
error: String(cbErr),
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Emit events
|
|
385
|
+
if (result.status === "success") {
|
|
386
|
+
this.emit("task:completed", result);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
this.emit("task:failed", result);
|
|
390
|
+
}
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Re-schedule all active tasks from store.
|
|
395
|
+
* Called on initialization to handle process restarts.
|
|
396
|
+
*/
|
|
397
|
+
async rescheduleActiveTasks() {
|
|
398
|
+
const store = this.getStore();
|
|
399
|
+
const backend = this.getBackend();
|
|
400
|
+
const activeTasks = await store.list({ status: "active" });
|
|
401
|
+
for (const task of activeTasks) {
|
|
402
|
+
try {
|
|
403
|
+
await backend.schedule(task, (t) => this.onTaskTick(t));
|
|
404
|
+
logger.debug("[TaskManager] Re-scheduled task", {
|
|
405
|
+
taskId: task.id,
|
|
406
|
+
name: task.name,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
logger.error("[TaskManager] Failed to re-schedule task", {
|
|
411
|
+
taskId: task.id,
|
|
412
|
+
error: String(err),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (activeTasks.length > 0) {
|
|
417
|
+
logger.info("[TaskManager] Re-scheduled active tasks", {
|
|
418
|
+
count: activeTasks.length,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
emit(event, ...args) {
|
|
423
|
+
this.emitter?.emit(event, ...args);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in agent tools for TaskManager.
|
|
3
|
+
*
|
|
4
|
+
* These tools allow the AI to self-schedule, manage, and inspect tasks
|
|
5
|
+
* during conversations. Created per-instance via `createTaskTools()` factory,
|
|
6
|
+
* following the same pattern as `createFileTools()` in files/fileTools.ts.
|
|
7
|
+
*
|
|
8
|
+
* @module tasks/tools/taskTools
|
|
9
|
+
*/
|
|
10
|
+
import type { TaskManager } from "../taskManager.js";
|
|
11
|
+
import type { TaskSchedule } from "../../types/taskTypes.js";
|
|
12
|
+
/**
|
|
13
|
+
* Create task management tools bound to a TaskManager instance.
|
|
14
|
+
*
|
|
15
|
+
* These tools follow the same factory pattern as `createFileTools()` in
|
|
16
|
+
* `src/lib/files/fileTools.ts`. The `manager` is captured via closure,
|
|
17
|
+
* eliminating the need for module-level singleton state.
|
|
18
|
+
*
|
|
19
|
+
* @param manager - The TaskManager instance to bind to
|
|
20
|
+
* @returns Record of tool name to tool definition
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const manager = new TaskManager(neurolink, config);
|
|
25
|
+
* const tools = createTaskTools(manager);
|
|
26
|
+
* // tools.createTask, tools.listTasks, tools.getTaskRuns, etc.
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function createTaskTools(manager: TaskManager): {
|
|
30
|
+
createTask: import("ai").Tool<{
|
|
31
|
+
name: string;
|
|
32
|
+
prompt: string;
|
|
33
|
+
schedule: {
|
|
34
|
+
type: "once" | "cron" | "interval";
|
|
35
|
+
expression?: string | undefined;
|
|
36
|
+
timezone?: string | undefined;
|
|
37
|
+
every?: number | undefined;
|
|
38
|
+
at?: string | undefined;
|
|
39
|
+
};
|
|
40
|
+
mode?: "isolated" | "continuation" | undefined;
|
|
41
|
+
}, {
|
|
42
|
+
success: boolean;
|
|
43
|
+
taskId: string;
|
|
44
|
+
name: string;
|
|
45
|
+
status: import("../../index.js").TaskStatus;
|
|
46
|
+
mode: import("../../index.js").TaskExecutionMode;
|
|
47
|
+
nextRunAt: string | undefined;
|
|
48
|
+
schedule: TaskSchedule;
|
|
49
|
+
error?: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
success: boolean;
|
|
52
|
+
error: string;
|
|
53
|
+
taskId?: undefined;
|
|
54
|
+
name?: undefined;
|
|
55
|
+
status?: undefined;
|
|
56
|
+
mode?: undefined;
|
|
57
|
+
nextRunAt?: undefined;
|
|
58
|
+
schedule?: undefined;
|
|
59
|
+
}>;
|
|
60
|
+
listTasks: import("ai").Tool<{
|
|
61
|
+
status?: "failed" | "pending" | "active" | "paused" | "completed" | "cancelled" | undefined;
|
|
62
|
+
}, {
|
|
63
|
+
success: boolean;
|
|
64
|
+
count: number;
|
|
65
|
+
tasks: {
|
|
66
|
+
taskId: string;
|
|
67
|
+
name: string;
|
|
68
|
+
status: import("../../index.js").TaskStatus;
|
|
69
|
+
mode: import("../../index.js").TaskExecutionMode;
|
|
70
|
+
schedule: TaskSchedule;
|
|
71
|
+
runCount: number;
|
|
72
|
+
lastRunAt: string | undefined;
|
|
73
|
+
nextRunAt: string | undefined;
|
|
74
|
+
}[];
|
|
75
|
+
error?: undefined;
|
|
76
|
+
} | {
|
|
77
|
+
success: boolean;
|
|
78
|
+
error: string;
|
|
79
|
+
count?: undefined;
|
|
80
|
+
tasks?: undefined;
|
|
81
|
+
}>;
|
|
82
|
+
getTaskRuns: import("ai").Tool<{
|
|
83
|
+
taskId: string;
|
|
84
|
+
limit?: number | undefined;
|
|
85
|
+
}, {
|
|
86
|
+
success: boolean;
|
|
87
|
+
taskId: string;
|
|
88
|
+
count: number;
|
|
89
|
+
runs: {
|
|
90
|
+
runId: string;
|
|
91
|
+
status: "error" | "success";
|
|
92
|
+
output: string | undefined;
|
|
93
|
+
durationMs: number;
|
|
94
|
+
timestamp: string;
|
|
95
|
+
error: string | undefined;
|
|
96
|
+
}[];
|
|
97
|
+
error?: undefined;
|
|
98
|
+
} | {
|
|
99
|
+
success: boolean;
|
|
100
|
+
error: string;
|
|
101
|
+
taskId?: undefined;
|
|
102
|
+
count?: undefined;
|
|
103
|
+
runs?: undefined;
|
|
104
|
+
}>;
|
|
105
|
+
deleteTask: import("ai").Tool<{
|
|
106
|
+
taskId: string;
|
|
107
|
+
}, {
|
|
108
|
+
success: boolean;
|
|
109
|
+
error: string;
|
|
110
|
+
deletedTask?: undefined;
|
|
111
|
+
taskId?: undefined;
|
|
112
|
+
} | {
|
|
113
|
+
success: boolean;
|
|
114
|
+
deletedTask: string;
|
|
115
|
+
taskId: string;
|
|
116
|
+
error?: undefined;
|
|
117
|
+
}>;
|
|
118
|
+
runTaskNow: import("ai").Tool<{
|
|
119
|
+
taskId: string;
|
|
120
|
+
}, {
|
|
121
|
+
success: boolean;
|
|
122
|
+
runId: string;
|
|
123
|
+
status: "error" | "success";
|
|
124
|
+
output: string | undefined;
|
|
125
|
+
durationMs: number;
|
|
126
|
+
error: string | undefined;
|
|
127
|
+
} | {
|
|
128
|
+
success: boolean;
|
|
129
|
+
error: string;
|
|
130
|
+
runId?: undefined;
|
|
131
|
+
status?: undefined;
|
|
132
|
+
output?: undefined;
|
|
133
|
+
durationMs?: undefined;
|
|
134
|
+
}>;
|
|
135
|
+
};
|