digital-tasks 2.1.1 → 2.3.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 +18 -0
- package/README.md +560 -0
- package/package.json +20 -5
- package/src/client.ts +268 -0
- package/src/index.ts +12 -10
- package/src/markdown.ts +63 -48
- package/src/project.ts +57 -42
- package/src/queue.ts +76 -37
- package/src/task.ts +132 -75
- package/src/types.ts +177 -40
- package/src/worker.ts +959 -0
- package/test/project.test.ts +28 -84
- package/test/queue.test.ts +51 -24
- package/test/task.test.ts +80 -27
- package/test/worker.test.ts +1158 -0
- package/tsconfig.json +2 -13
- package/vitest.config.ts +48 -0
- package/wrangler.jsonc +44 -0
- package/.turbo/turbo-build.log +0 -5
- package/dist/function-task.d.ts +0 -319
- package/dist/function-task.d.ts.map +0 -1
- package/dist/function-task.js +0 -286
- package/dist/function-task.js.map +0 -1
- package/dist/index.d.ts +0 -72
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -74
- package/dist/index.js.map +0 -1
- package/dist/markdown.d.ts +0 -112
- package/dist/markdown.d.ts.map +0 -1
- package/dist/markdown.js +0 -510
- package/dist/markdown.js.map +0 -1
- package/dist/project.d.ts +0 -259
- package/dist/project.d.ts.map +0 -1
- package/dist/project.js +0 -397
- package/dist/project.js.map +0 -1
- package/dist/queue.d.ts +0 -17
- package/dist/queue.d.ts.map +0 -1
- package/dist/queue.js +0 -347
- package/dist/queue.js.map +0 -1
- package/dist/task.d.ts +0 -69
- package/dist/task.d.ts.map +0 -1
- package/dist/task.js +0 -321
- package/dist/task.js.map +0 -1
- package/dist/types.d.ts +0 -292
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -15
- package/dist/types.js.map +0 -1
- package/src/index.js +0 -73
- package/src/markdown.js +0 -509
- package/src/project.js +0 -396
- package/src/queue.js +0 -346
- package/src/task.js +0 -320
- package/src/types.js +0 -14
package/src/project.ts
CHANGED
|
@@ -59,10 +59,7 @@ export type ExecutionMode = 'parallel' | 'sequential'
|
|
|
59
59
|
/**
|
|
60
60
|
* Task node in a workflow - can be a single task or a group
|
|
61
61
|
*/
|
|
62
|
-
export type TaskNode =
|
|
63
|
-
| TaskDefinition
|
|
64
|
-
| ParallelGroup
|
|
65
|
-
| SequentialGroup
|
|
62
|
+
export type TaskNode = TaskDefinition | ParallelGroup | SequentialGroup
|
|
66
63
|
|
|
67
64
|
/**
|
|
68
65
|
* Function type for the DSL
|
|
@@ -107,12 +104,7 @@ export interface SequentialGroup {
|
|
|
107
104
|
/**
|
|
108
105
|
* Project status
|
|
109
106
|
*/
|
|
110
|
-
export type ProjectStatus =
|
|
111
|
-
| 'draft'
|
|
112
|
-
| 'active'
|
|
113
|
-
| 'paused'
|
|
114
|
-
| 'completed'
|
|
115
|
-
| 'cancelled'
|
|
107
|
+
export type ProjectStatus = 'draft' | 'active' | 'paused' | 'completed' | 'cancelled'
|
|
116
108
|
|
|
117
109
|
/**
|
|
118
110
|
* Project definition
|
|
@@ -261,19 +253,28 @@ function generateProjectId(): string {
|
|
|
261
253
|
*/
|
|
262
254
|
export function createProject(options: CreateProjectOptions): Project {
|
|
263
255
|
const now = new Date()
|
|
264
|
-
|
|
256
|
+
const project: Project = {
|
|
265
257
|
id: generateProjectId(),
|
|
266
258
|
name: options.name,
|
|
267
|
-
description: options.description,
|
|
268
259
|
status: 'draft',
|
|
269
260
|
tasks: options.tasks || [],
|
|
270
261
|
defaultMode: options.defaultMode || 'sequential',
|
|
271
|
-
owner: options.owner,
|
|
272
|
-
tags: options.tags,
|
|
273
262
|
createdAt: now,
|
|
274
263
|
updatedAt: now,
|
|
275
|
-
metadata: options.metadata,
|
|
276
264
|
}
|
|
265
|
+
if (options.description !== undefined) {
|
|
266
|
+
project.description = options.description
|
|
267
|
+
}
|
|
268
|
+
if (options.owner !== undefined) {
|
|
269
|
+
project.owner = options.owner
|
|
270
|
+
}
|
|
271
|
+
if (options.tags !== undefined) {
|
|
272
|
+
project.tags = options.tags
|
|
273
|
+
}
|
|
274
|
+
if (options.metadata !== undefined) {
|
|
275
|
+
project.metadata = options.metadata
|
|
276
|
+
}
|
|
277
|
+
return project
|
|
277
278
|
}
|
|
278
279
|
|
|
279
280
|
// ============================================================================
|
|
@@ -325,7 +326,10 @@ export function workflow(name: string, description?: string) {
|
|
|
325
326
|
*/
|
|
326
327
|
then(...nodes: TaskNode[]) {
|
|
327
328
|
if (nodes.length === 1) {
|
|
328
|
-
|
|
329
|
+
const node = nodes[0]
|
|
330
|
+
if (node !== undefined) {
|
|
331
|
+
tasks.push(node)
|
|
332
|
+
}
|
|
329
333
|
} else {
|
|
330
334
|
tasks.push(sequential(...nodes))
|
|
331
335
|
}
|
|
@@ -344,12 +348,15 @@ export function workflow(name: string, description?: string) {
|
|
|
344
348
|
* Build the project
|
|
345
349
|
*/
|
|
346
350
|
build(options?: Partial<Omit<CreateProjectOptions, 'name' | 'tasks'>>): Project {
|
|
347
|
-
|
|
351
|
+
const projectOptions: CreateProjectOptions = {
|
|
348
352
|
name,
|
|
349
|
-
description,
|
|
350
353
|
tasks,
|
|
351
354
|
...options,
|
|
352
|
-
}
|
|
355
|
+
}
|
|
356
|
+
if (description !== undefined) {
|
|
357
|
+
projectOptions.description = description
|
|
358
|
+
}
|
|
359
|
+
return createProject(projectOptions)
|
|
353
360
|
},
|
|
354
361
|
}
|
|
355
362
|
|
|
@@ -380,9 +387,7 @@ export async function materializeProject(
|
|
|
380
387
|
const taskId = `${project.id}_task_${taskIndex++}`
|
|
381
388
|
|
|
382
389
|
// Create dependencies based on mode (as string array for CreateTaskOptions)
|
|
383
|
-
const dependencies = mode === 'sequential' && previousIds.length > 0
|
|
384
|
-
? previousIds
|
|
385
|
-
: undefined
|
|
390
|
+
const dependencies = mode === 'sequential' && previousIds.length > 0 ? previousIds : undefined
|
|
386
391
|
|
|
387
392
|
// Create a FunctionDefinition from the task definition
|
|
388
393
|
// Default to generative function type for DSL tasks
|
|
@@ -394,19 +399,28 @@ export async function materializeProject(
|
|
|
394
399
|
output: 'string',
|
|
395
400
|
} as FunctionDefinition
|
|
396
401
|
|
|
397
|
-
const
|
|
398
|
-
|
|
402
|
+
const createTaskOptions: CreateTaskOptions = {
|
|
403
|
+
tool: functionDef,
|
|
399
404
|
priority: taskDef.priority || 'normal',
|
|
400
|
-
assignTo: taskDef.assignTo,
|
|
401
|
-
tags: taskDef.tags,
|
|
402
|
-
parentId,
|
|
403
405
|
projectId: project.id,
|
|
404
|
-
dependencies,
|
|
405
406
|
metadata: {
|
|
406
407
|
...taskDef.metadata,
|
|
407
408
|
_taskNodeIndex: taskIndex - 1,
|
|
408
409
|
},
|
|
409
|
-
}
|
|
410
|
+
}
|
|
411
|
+
if (taskDef.assignTo !== undefined) {
|
|
412
|
+
createTaskOptions.assignTo = taskDef.assignTo
|
|
413
|
+
}
|
|
414
|
+
if (taskDef.tags !== undefined) {
|
|
415
|
+
createTaskOptions.tags = taskDef.tags
|
|
416
|
+
}
|
|
417
|
+
if (parentId !== undefined) {
|
|
418
|
+
createTaskOptions.parentId = parentId
|
|
419
|
+
}
|
|
420
|
+
if (dependencies !== undefined) {
|
|
421
|
+
createTaskOptions.dependencies = dependencies
|
|
422
|
+
}
|
|
423
|
+
const newTask = await createBaseTask(createTaskOptions)
|
|
410
424
|
|
|
411
425
|
// Override the generated ID with our predictable one
|
|
412
426
|
;(newTask as AnyTask).id = taskId
|
|
@@ -455,7 +469,12 @@ export async function materializeProject(
|
|
|
455
469
|
// Process all root-level tasks
|
|
456
470
|
let previousIds: string[] = []
|
|
457
471
|
for (const node of project.tasks) {
|
|
458
|
-
previousIds = await processNode(
|
|
472
|
+
previousIds = await processNode(
|
|
473
|
+
node,
|
|
474
|
+
undefined,
|
|
475
|
+
previousIds,
|
|
476
|
+
project.defaultMode || 'sequential'
|
|
477
|
+
)
|
|
459
478
|
}
|
|
460
479
|
|
|
461
480
|
return { project, tasks }
|
|
@@ -469,8 +488,8 @@ export async function materializeProject(
|
|
|
469
488
|
* Get all tasks that depend on a given task (dependants)
|
|
470
489
|
*/
|
|
471
490
|
export function getDependants(taskId: string, allTasks: AnyTask[]): AnyTask[] {
|
|
472
|
-
return allTasks.filter(t =>
|
|
473
|
-
t.dependencies?.some(d => d.taskId === taskId && d.type === 'blocked_by')
|
|
491
|
+
return allTasks.filter((t) =>
|
|
492
|
+
t.dependencies?.some((d) => d.taskId === taskId && d.type === 'blocked_by')
|
|
474
493
|
)
|
|
475
494
|
}
|
|
476
495
|
|
|
@@ -480,25 +499,21 @@ export function getDependants(taskId: string, allTasks: AnyTask[]): AnyTask[] {
|
|
|
480
499
|
export function getDependencies(task: AnyTask, allTasks: AnyTask[]): AnyTask[] {
|
|
481
500
|
if (!task.dependencies) return []
|
|
482
501
|
|
|
483
|
-
const depIds = task.dependencies
|
|
484
|
-
.filter(d => d.type === 'blocked_by')
|
|
485
|
-
.map(d => d.taskId)
|
|
502
|
+
const depIds = task.dependencies.filter((d) => d.type === 'blocked_by').map((d) => d.taskId)
|
|
486
503
|
|
|
487
|
-
return allTasks.filter(t => depIds.includes(t.id))
|
|
504
|
+
return allTasks.filter((t) => depIds.includes(t.id))
|
|
488
505
|
}
|
|
489
506
|
|
|
490
507
|
/**
|
|
491
508
|
* Get tasks that are ready to execute (no unsatisfied dependencies)
|
|
492
509
|
*/
|
|
493
510
|
export function getReadyTasks(allTasks: AnyTask[]): AnyTask[] {
|
|
494
|
-
return allTasks.filter(t => {
|
|
511
|
+
return allTasks.filter((t) => {
|
|
495
512
|
if (t.status !== 'queued' && t.status !== 'pending') return false
|
|
496
513
|
|
|
497
514
|
if (!t.dependencies || t.dependencies.length === 0) return true
|
|
498
515
|
|
|
499
|
-
return t.dependencies
|
|
500
|
-
.filter(d => d.type === 'blocked_by')
|
|
501
|
-
.every(d => d.satisfied)
|
|
516
|
+
return t.dependencies.filter((d) => d.type === 'blocked_by').every((d) => d.satisfied)
|
|
502
517
|
})
|
|
503
518
|
}
|
|
504
519
|
|
|
@@ -513,7 +528,7 @@ export function hasCycles(allTasks: AnyTask[]): boolean {
|
|
|
513
528
|
visited.add(taskId)
|
|
514
529
|
recStack.add(taskId)
|
|
515
530
|
|
|
516
|
-
const task = allTasks.find(t => t.id === taskId)
|
|
531
|
+
const task = allTasks.find((t) => t.id === taskId)
|
|
517
532
|
if (task?.dependencies) {
|
|
518
533
|
for (const dep of task.dependencies) {
|
|
519
534
|
if (dep.type === 'blocked_by') {
|
|
@@ -554,7 +569,7 @@ export function sortTasks(allTasks: AnyTask[]): AnyTask[] {
|
|
|
554
569
|
if (task.dependencies) {
|
|
555
570
|
for (const dep of task.dependencies) {
|
|
556
571
|
if (dep.type === 'blocked_by') {
|
|
557
|
-
const depTask = allTasks.find(t => t.id === dep.taskId)
|
|
572
|
+
const depTask = allTasks.find((t) => t.id === dep.taskId)
|
|
558
573
|
if (depTask) visit(depTask)
|
|
559
574
|
}
|
|
560
575
|
}
|
package/src/queue.ts
CHANGED
|
@@ -49,12 +49,14 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async add(task: AnyTask): Promise<void> {
|
|
52
|
+
// Read the underlying Tool, tolerating the legacy `function` alias.
|
|
53
|
+
const tool = task.tool ?? task.function
|
|
52
54
|
// Add created event
|
|
53
55
|
const event: TaskEvent = {
|
|
54
56
|
id: `evt_${Date.now()}`,
|
|
55
57
|
type: 'created',
|
|
56
58
|
timestamp: new Date(),
|
|
57
|
-
message: `Task created: ${task.
|
|
59
|
+
message: `Task created: ${tool?.name ?? task.id}`,
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
const taskWithEvent = {
|
|
@@ -88,10 +90,16 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
88
90
|
if (options.status && options.status !== task.status) {
|
|
89
91
|
events.push({
|
|
90
92
|
id: `evt_${Date.now()}_status`,
|
|
91
|
-
type:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
type:
|
|
94
|
+
options.status === 'completed'
|
|
95
|
+
? 'completed'
|
|
96
|
+
: options.status === 'failed'
|
|
97
|
+
? 'failed'
|
|
98
|
+
: options.status === 'in_progress'
|
|
99
|
+
? 'started'
|
|
100
|
+
: options.status === 'blocked'
|
|
101
|
+
? 'blocked'
|
|
102
|
+
: 'progress',
|
|
95
103
|
timestamp: new Date(),
|
|
96
104
|
message: `Status changed from ${task.status} to ${options.status}`,
|
|
97
105
|
})
|
|
@@ -102,12 +110,25 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
102
110
|
if (options.progress) {
|
|
103
111
|
progressUpdate = {
|
|
104
112
|
percent: options.progress.percent ?? task.progress?.percent ?? 0,
|
|
105
|
-
step: options.progress.step ?? task.progress?.step,
|
|
106
|
-
totalSteps: options.progress.totalSteps ?? task.progress?.totalSteps,
|
|
107
|
-
currentStep: options.progress.currentStep ?? task.progress?.currentStep,
|
|
108
|
-
estimatedTimeRemaining: options.progress.estimatedTimeRemaining ?? task.progress?.estimatedTimeRemaining,
|
|
109
113
|
updatedAt: new Date(),
|
|
110
114
|
}
|
|
115
|
+
const step = options.progress.step ?? task.progress?.step
|
|
116
|
+
if (step !== undefined) {
|
|
117
|
+
progressUpdate.step = step
|
|
118
|
+
}
|
|
119
|
+
const totalSteps = options.progress.totalSteps ?? task.progress?.totalSteps
|
|
120
|
+
if (totalSteps !== undefined) {
|
|
121
|
+
progressUpdate.totalSteps = totalSteps
|
|
122
|
+
}
|
|
123
|
+
const currentStep = options.progress.currentStep ?? task.progress?.currentStep
|
|
124
|
+
if (currentStep !== undefined) {
|
|
125
|
+
progressUpdate.currentStep = currentStep
|
|
126
|
+
}
|
|
127
|
+
const estTime =
|
|
128
|
+
options.progress.estimatedTimeRemaining ?? task.progress?.estimatedTimeRemaining
|
|
129
|
+
if (estTime !== undefined) {
|
|
130
|
+
progressUpdate.estimatedTimeRemaining = estTime
|
|
131
|
+
}
|
|
111
132
|
}
|
|
112
133
|
|
|
113
134
|
const updated: AnyTask = {
|
|
@@ -145,9 +166,9 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
145
166
|
results = results.filter((t) => priorities.includes(t.priority))
|
|
146
167
|
}
|
|
147
168
|
|
|
148
|
-
// Filter by function type
|
|
169
|
+
// Filter by function type (reads `tool` with fallback to legacy `function`)
|
|
149
170
|
if (options.functionType) {
|
|
150
|
-
results = results.filter((t) => t.function
|
|
171
|
+
results = results.filter((t) => (t.tool ?? t.function)?.type === options.functionType)
|
|
151
172
|
}
|
|
152
173
|
|
|
153
174
|
// Filter by assigned worker
|
|
@@ -157,9 +178,7 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
157
178
|
|
|
158
179
|
// Filter by tags
|
|
159
180
|
if (options.tags && options.tags.length > 0) {
|
|
160
|
-
results = results.filter(
|
|
161
|
-
(t) => t.tags && options.tags!.some((tag) => t.tags!.includes(tag))
|
|
162
|
-
)
|
|
181
|
+
results = results.filter((t) => t.tags && options.tags!.some((tag) => t.tags!.includes(tag)))
|
|
163
182
|
}
|
|
164
183
|
|
|
165
184
|
// Filter by project
|
|
@@ -172,14 +191,17 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
172
191
|
results = results.filter((t) => t.parentId === options.parentId)
|
|
173
192
|
}
|
|
174
193
|
|
|
175
|
-
// Text search
|
|
194
|
+
// Text search (reads `tool` with fallback to legacy `function`)
|
|
176
195
|
if (options.search) {
|
|
177
196
|
const search = options.search.toLowerCase()
|
|
178
|
-
results = results.filter(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
197
|
+
results = results.filter((t) => {
|
|
198
|
+
const tool = t.tool ?? t.function
|
|
199
|
+
if (!tool) return false
|
|
200
|
+
return (
|
|
201
|
+
tool.name.toLowerCase().includes(search) ||
|
|
202
|
+
tool.description?.toLowerCase().includes(search)
|
|
203
|
+
)
|
|
204
|
+
})
|
|
183
205
|
}
|
|
184
206
|
|
|
185
207
|
// Sort
|
|
@@ -232,7 +254,11 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
232
254
|
// Find first task the worker can handle
|
|
233
255
|
for (const task of queuedTasks) {
|
|
234
256
|
// Check if worker type is allowed
|
|
235
|
-
if (
|
|
257
|
+
if (
|
|
258
|
+
task.allowedWorkers &&
|
|
259
|
+
!task.allowedWorkers.includes(worker.type) &&
|
|
260
|
+
!task.allowedWorkers.includes('any')
|
|
261
|
+
) {
|
|
236
262
|
continue
|
|
237
263
|
}
|
|
238
264
|
|
|
@@ -289,14 +315,17 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
289
315
|
const task = await this.get(taskId)
|
|
290
316
|
if (!task) return
|
|
291
317
|
|
|
318
|
+
const event: Omit<TaskEvent, 'id' | 'timestamp'> = {
|
|
319
|
+
type: 'completed',
|
|
320
|
+
message: 'Task completed successfully',
|
|
321
|
+
data: { output },
|
|
322
|
+
}
|
|
323
|
+
if (task.assignment?.worker !== undefined) {
|
|
324
|
+
event.actor = task.assignment.worker
|
|
325
|
+
}
|
|
292
326
|
await this.update(taskId, {
|
|
293
327
|
status: 'completed',
|
|
294
|
-
event
|
|
295
|
-
type: 'completed',
|
|
296
|
-
actor: task.assignment?.worker,
|
|
297
|
-
message: 'Task completed successfully',
|
|
298
|
-
data: { output },
|
|
299
|
-
},
|
|
328
|
+
event,
|
|
300
329
|
})
|
|
301
330
|
|
|
302
331
|
// Update task output separately since it's not in UpdateTaskOptions
|
|
@@ -328,7 +357,9 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
328
357
|
})
|
|
329
358
|
|
|
330
359
|
// Unblock if all dependencies satisfied
|
|
331
|
-
const allSatisfied = updatedDeps
|
|
360
|
+
const allSatisfied = updatedDeps
|
|
361
|
+
.filter((d) => d.type === 'blocked_by')
|
|
362
|
+
.every((d) => d.satisfied)
|
|
332
363
|
if (allSatisfied && otherTask.status === 'blocked') {
|
|
333
364
|
await this.update(otherTask.id, {
|
|
334
365
|
status: 'queued',
|
|
@@ -347,14 +378,17 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
347
378
|
const task = await this.get(taskId)
|
|
348
379
|
if (!task) return
|
|
349
380
|
|
|
381
|
+
const event: Omit<TaskEvent, 'id' | 'timestamp'> = {
|
|
382
|
+
type: 'failed',
|
|
383
|
+
message: `Task failed: ${error}`,
|
|
384
|
+
data: { error },
|
|
385
|
+
}
|
|
386
|
+
if (task.assignment?.worker !== undefined) {
|
|
387
|
+
event.actor = task.assignment.worker
|
|
388
|
+
}
|
|
350
389
|
await this.update(taskId, {
|
|
351
390
|
status: 'failed',
|
|
352
|
-
event
|
|
353
|
-
type: 'failed',
|
|
354
|
-
actor: task.assignment?.worker,
|
|
355
|
-
message: `Task failed: ${error}`,
|
|
356
|
-
data: { error },
|
|
357
|
-
},
|
|
391
|
+
event,
|
|
358
392
|
})
|
|
359
393
|
}
|
|
360
394
|
|
|
@@ -401,13 +435,18 @@ class InMemoryTaskQueue implements TaskQueue {
|
|
|
401
435
|
}
|
|
402
436
|
}
|
|
403
437
|
|
|
404
|
-
|
|
438
|
+
const stats: TaskQueueStats = {
|
|
405
439
|
total: tasks.length,
|
|
406
440
|
byStatus,
|
|
407
441
|
byPriority,
|
|
408
|
-
avgWaitTime: waitTimeCount > 0 ? totalWaitTime / waitTimeCount : undefined,
|
|
409
|
-
avgCompletionTime: completionTimeCount > 0 ? totalCompletionTime / completionTimeCount : undefined,
|
|
410
442
|
}
|
|
443
|
+
if (waitTimeCount > 0) {
|
|
444
|
+
stats.avgWaitTime = totalWaitTime / waitTimeCount
|
|
445
|
+
}
|
|
446
|
+
if (completionTimeCount > 0) {
|
|
447
|
+
stats.avgCompletionTime = totalCompletionTime / completionTimeCount
|
|
448
|
+
}
|
|
449
|
+
return stats
|
|
411
450
|
}
|
|
412
451
|
}
|
|
413
452
|
|