@calmo/task-runner 3.2.0 → 3.4.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/.jules/backlog_maniac.md +3 -0
- package/AGENTS.md +4 -0
- package/CHANGELOG.md +26 -0
- package/coverage/coverage-final.json +8 -7
- package/coverage/index.html +9 -9
- package/coverage/lcov-report/index.html +9 -9
- package/coverage/lcov-report/src/EventBus.ts.html +4 -4
- package/coverage/lcov-report/src/TaskGraphValidationError.ts.html +121 -0
- package/coverage/lcov-report/src/TaskGraphValidator.ts.html +38 -38
- package/coverage/lcov-report/src/TaskRunner.ts.html +34 -19
- package/coverage/lcov-report/src/TaskRunnerBuilder.ts.html +1 -1
- package/coverage/lcov-report/src/TaskRunnerExecutionConfig.ts.html +17 -2
- package/coverage/lcov-report/src/TaskStateManager.ts.html +35 -35
- package/coverage/lcov-report/src/WorkflowExecutor.ts.html +94 -34
- package/coverage/lcov-report/src/contracts/RunnerEvents.ts.html +1 -1
- package/coverage/lcov-report/src/contracts/index.html +1 -1
- package/coverage/lcov-report/src/index.html +23 -8
- package/coverage/lcov-report/src/strategies/DryRunExecutionStrategy.ts.html +1 -1
- package/coverage/lcov-report/src/strategies/RetryingExecutionStrategy.ts.html +5 -5
- package/coverage/lcov-report/src/strategies/StandardExecutionStrategy.ts.html +3 -3
- package/coverage/lcov-report/src/strategies/index.html +1 -1
- package/coverage/lcov.info +284 -258
- package/coverage/src/EventBus.ts.html +4 -4
- package/coverage/src/TaskGraphValidationError.ts.html +121 -0
- package/coverage/src/TaskGraphValidator.ts.html +38 -38
- package/coverage/src/TaskRunner.ts.html +34 -19
- package/coverage/src/TaskRunnerBuilder.ts.html +1 -1
- package/coverage/src/TaskRunnerExecutionConfig.ts.html +17 -2
- package/coverage/src/TaskStateManager.ts.html +35 -35
- package/coverage/src/WorkflowExecutor.ts.html +94 -34
- package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
- package/coverage/src/contracts/index.html +1 -1
- package/coverage/src/index.html +23 -8
- package/coverage/src/strategies/DryRunExecutionStrategy.ts.html +1 -1
- package/coverage/src/strategies/RetryingExecutionStrategy.ts.html +5 -5
- package/coverage/src/strategies/StandardExecutionStrategy.ts.html +3 -3
- package/coverage/src/strategies/index.html +1 -1
- package/dist/TaskGraphValidationError.d.ts +9 -0
- package/dist/TaskGraphValidationError.js +13 -0
- package/dist/TaskGraphValidationError.js.map +1 -0
- package/dist/TaskRunner.js +3 -2
- package/dist/TaskRunner.js.map +1 -1
- package/dist/TaskRunnerExecutionConfig.d.ts +5 -0
- package/dist/WorkflowExecutor.d.ts +4 -1
- package/dist/WorkflowExecutor.js +19 -4
- package/dist/WorkflowExecutor.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/openspec/changes/archive/2026-01-18-add-concurrency-control/tasks.md +9 -0
- package/openspec/changes/archive/2026-01-18-add-task-retry-policy/tasks.md +10 -0
- package/openspec/project.md +1 -0
- package/package.json +1 -1
- package/src/TaskGraphValidationError.ts +12 -0
- package/src/TaskRunner.ts +7 -2
- package/src/TaskRunnerExecutionConfig.ts +5 -0
- package/src/WorkflowExecutor.ts +24 -4
- package/src/index.ts +1 -0
- package/test-report.xml +114 -102
- package/openspec/changes/add-concurrency-control/tasks.md +0 -9
- package/openspec/changes/add-task-retry-policy/tasks.md +0 -10
- /package/openspec/changes/{add-concurrency-control → archive/2026-01-18-add-concurrency-control}/proposal.md +0 -0
- /package/openspec/changes/{add-task-retry-policy → archive/2026-01-18-add-task-retry-policy}/proposal.md +0 -0
package/dist/TaskRunner.js
CHANGED
|
@@ -2,6 +2,7 @@ import { TaskGraphValidator } from "./TaskGraphValidator.js";
|
|
|
2
2
|
import { EventBus } from "./EventBus.js";
|
|
3
3
|
import { WorkflowExecutor } from "./WorkflowExecutor.js";
|
|
4
4
|
import { TaskStateManager } from "./TaskStateManager.js";
|
|
5
|
+
import { TaskGraphValidationError } from "./TaskGraphValidationError.js";
|
|
5
6
|
import { StandardExecutionStrategy } from "./strategies/StandardExecutionStrategy.js";
|
|
6
7
|
import { RetryingExecutionStrategy } from "./strategies/RetryingExecutionStrategy.js";
|
|
7
8
|
import { DryRunExecutionStrategy } from "./strategies/DryRunExecutionStrategy.js";
|
|
@@ -105,14 +106,14 @@ export class TaskRunner {
|
|
|
105
106
|
};
|
|
106
107
|
const validationResult = this.validator.validate(taskGraph);
|
|
107
108
|
if (!validationResult.isValid) {
|
|
108
|
-
throw new
|
|
109
|
+
throw new TaskGraphValidationError(validationResult, this.validator.createErrorMessage(validationResult));
|
|
109
110
|
}
|
|
110
111
|
const stateManager = new TaskStateManager(this.eventBus);
|
|
111
112
|
let strategy = this.executionStrategy;
|
|
112
113
|
if (config?.dryRun) {
|
|
113
114
|
strategy = new DryRunExecutionStrategy();
|
|
114
115
|
}
|
|
115
|
-
const executor = new WorkflowExecutor(this.context, this.eventBus, stateManager, strategy);
|
|
116
|
+
const executor = new WorkflowExecutor(this.context, this.eventBus, stateManager, strategy, config?.concurrency);
|
|
116
117
|
// We need to handle the timeout cleanup properly.
|
|
117
118
|
if (config?.timeout !== undefined) {
|
|
118
119
|
const controller = new AbortController();
|
package/dist/TaskRunner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskRunner.js","sourceRoot":"","sources":["../src/TaskRunner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"TaskRunner.js","sourceRoot":"","sources":["../src/TaskRunner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAKlF;;;;GAIG;AACH,MAAM,OAAO,UAAU;IAUD;IATZ,QAAQ,GAAG,IAAI,QAAQ,EAAY,CAAC;IACpC,SAAS,GAAG,IAAI,kBAAkB,EAAE,CAAC;IACrC,iBAAiB,GAAiC,IAAI,yBAAyB,CACrF,IAAI,yBAAyB,EAAE,CAChC,CAAC;IAEF;;OAEG;IACH,YAAoB,OAAiB;QAAjB,YAAO,GAAP,OAAO,CAAU;IAAG,CAAC;IAEzC;;;;OAIG;IACI,EAAE,CACP,KAAQ,EACR,QAA0C;QAE1C,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACI,GAAG,CACR,KAAQ,EACR,QAA0C;QAE1C,sBAAsB;QACtB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED;;;;OAIG;IACI,oBAAoB,CAAC,QAAsC;QAChE,sBAAsB;QACtB,IAAI,CAAC,iBAAiB,GAAG,QAAQ,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACI,MAAM,CAAC,eAAe,CAAI,KAAoB;QACnD,MAAM,UAAU,GAAG,CAAC,UAAU,CAAC,CAAC;QAEhC,uDAAuD;QACvD,+EAA+E;QAC/E,6EAA6E;QAC7E,0EAA0E;QAC1E,0DAA0D;QAC1D,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAGhE,2CAA2C;QAC3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,qDAAqD;YACrD,sBAAsB;YACtB,wFAAwF;YACxF,UAAU,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpE,CAAC;QAED,YAAY;QACZ,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACtB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;oBACpC,UAAU,CAAC,IAAI,CACb,KAAK,QAAQ,CAAC,GAAG,CAAC,QAAQ,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChD,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED;;;;OAIG;IACK,MAAM,CAAC,iBAAiB,CAAC,EAAU;QACzC,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC9E,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,OAAO,CACX,KAA2B,EAC3B,MAAkC;QAElC,2CAA2C;QAC3C,MAAM,SAAS,GAAc;YAC3B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC1B,EAAE,EAAE,IAAI,CAAC,IAAI;gBACb,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE;aACtC,CAAC,CAAC;SACJ,CAAC;QAEF,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC;YAC9B,MAAM,IAAI,wBAAwB,CAChC,gBAAgB,EAChB,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CACpD,CAAC;QACJ,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEzD,IAAI,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC;QACtC,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;YACnB,QAAQ,GAAG,IAAI,uBAAuB,EAAY,CAAC;QACrD,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CACnC,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,QAAQ,EACb,YAAY,EACZ,QAAQ,EACR,MAAM,EAAE,WAAW,CACpB,CAAC;QAEF,kDAAkD;QAClD,IAAI,MAAM,EAAE,OAAO,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;gBAChC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;YAC9E,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAEnB,IAAI,eAAe,GAAG,UAAU,CAAC,MAAM,CAAC;YACxC,IAAI,OAAiC,CAAC;YAEtC,qDAAqD;YACrD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBACjB,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBACzB,6EAA6E;oBAC7E,oCAAoC;oBACpC,YAAY,CAAC,SAAS,CAAC,CAAC;oBACxB,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC;gBACnC,CAAC;qBAAM,CAAC;oBACL,gDAAgD;oBAChD,OAAO,GAAG,GAAG,EAAE;wBACZ,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;oBAC3C,CAAC,CAAC;oBACF,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACpD,CAAC;YACJ,CAAC;YAED,IAAI,CAAC;gBACH,OAAO,MAAM,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;YACxD,CAAC;oBAAS,CAAC;gBACT,YAAY,CAAC,SAAS,CAAC,CAAC;gBACxB,IAAI,MAAM,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC;oBAC5B,MAAM,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACvD,CAAC;YACH,CAAC;QACJ,CAAC;aAAM,CAAC;YACL,OAAO,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;CACF"}
|
|
@@ -15,4 +15,9 @@ export interface TaskRunnerExecutionConfig {
|
|
|
15
15
|
* Useful for verifying the execution order and graph structure.
|
|
16
16
|
*/
|
|
17
17
|
dryRun?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The maximum number of tasks to run concurrently.
|
|
20
|
+
* If undefined, all ready tasks will be run in parallel.
|
|
21
|
+
*/
|
|
22
|
+
concurrency?: number;
|
|
18
23
|
}
|
|
@@ -12,13 +12,16 @@ export declare class WorkflowExecutor<TContext> {
|
|
|
12
12
|
private eventBus;
|
|
13
13
|
private stateManager;
|
|
14
14
|
private strategy;
|
|
15
|
+
private concurrency?;
|
|
16
|
+
private readyQueue;
|
|
15
17
|
/**
|
|
16
18
|
* @param context The shared context object.
|
|
17
19
|
* @param eventBus The event bus to emit events.
|
|
18
20
|
* @param stateManager Manages execution state.
|
|
19
21
|
* @param strategy Execution strategy.
|
|
22
|
+
* @param concurrency Maximum number of concurrent tasks.
|
|
20
23
|
*/
|
|
21
|
-
constructor(context: TContext, eventBus: EventBus<TContext>, stateManager: TaskStateManager<TContext>, strategy: IExecutionStrategy<TContext
|
|
24
|
+
constructor(context: TContext, eventBus: EventBus<TContext>, stateManager: TaskStateManager<TContext>, strategy: IExecutionStrategy<TContext>, concurrency?: number | undefined);
|
|
22
25
|
/**
|
|
23
26
|
* Executes the given steps.
|
|
24
27
|
* @param steps The list of steps to execute.
|
package/dist/WorkflowExecutor.js
CHANGED
|
@@ -7,17 +7,21 @@ export class WorkflowExecutor {
|
|
|
7
7
|
eventBus;
|
|
8
8
|
stateManager;
|
|
9
9
|
strategy;
|
|
10
|
+
concurrency;
|
|
11
|
+
readyQueue = [];
|
|
10
12
|
/**
|
|
11
13
|
* @param context The shared context object.
|
|
12
14
|
* @param eventBus The event bus to emit events.
|
|
13
15
|
* @param stateManager Manages execution state.
|
|
14
16
|
* @param strategy Execution strategy.
|
|
17
|
+
* @param concurrency Maximum number of concurrent tasks.
|
|
15
18
|
*/
|
|
16
|
-
constructor(context, eventBus, stateManager, strategy) {
|
|
19
|
+
constructor(context, eventBus, stateManager, strategy, concurrency) {
|
|
17
20
|
this.context = context;
|
|
18
21
|
this.eventBus = eventBus;
|
|
19
22
|
this.stateManager = stateManager;
|
|
20
23
|
this.strategy = strategy;
|
|
24
|
+
this.concurrency = concurrency;
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
27
|
* Executes the given steps.
|
|
@@ -82,9 +86,18 @@ export class WorkflowExecutor {
|
|
|
82
86
|
* Logic to identify tasks that can be started and run them.
|
|
83
87
|
*/
|
|
84
88
|
processLoop(executingPromises, signal) {
|
|
85
|
-
const
|
|
86
|
-
//
|
|
87
|
-
for (const
|
|
89
|
+
const newlyReady = this.stateManager.processDependencies();
|
|
90
|
+
// Add newly ready tasks to the queue
|
|
91
|
+
for (const task of newlyReady) {
|
|
92
|
+
this.readyQueue.push(task);
|
|
93
|
+
}
|
|
94
|
+
// Execute ready tasks while respecting concurrency limit
|
|
95
|
+
while (this.readyQueue.length > 0) {
|
|
96
|
+
if (this.concurrency !== undefined &&
|
|
97
|
+
executingPromises.size >= this.concurrency) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
const step = this.readyQueue.shift();
|
|
88
101
|
this.stateManager.markRunning(step);
|
|
89
102
|
const taskPromise = this.strategy.execute(step, this.context, signal)
|
|
90
103
|
.then((result) => {
|
|
@@ -92,6 +105,8 @@ export class WorkflowExecutor {
|
|
|
92
105
|
})
|
|
93
106
|
.finally(() => {
|
|
94
107
|
executingPromises.delete(taskPromise);
|
|
108
|
+
// When a task finishes, we try to run more
|
|
109
|
+
this.processLoop(executingPromises, signal);
|
|
95
110
|
});
|
|
96
111
|
executingPromises.add(taskPromise);
|
|
97
112
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkflowExecutor.js","sourceRoot":"","sources":["../src/WorkflowExecutor.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,OAAO,gBAAgB;
|
|
1
|
+
{"version":3,"file":"WorkflowExecutor.js","sourceRoot":"","sources":["../src/WorkflowExecutor.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { TaskRunner } from "./TaskRunner.js";
|
|
2
2
|
export { TaskRunnerBuilder } from "./TaskRunnerBuilder.js";
|
|
3
3
|
export { TaskStateManager } from "./TaskStateManager.js";
|
|
4
|
+
export { TaskGraphValidationError } from "./TaskGraphValidationError.js";
|
|
4
5
|
export { StandardExecutionStrategy } from "./strategies/StandardExecutionStrategy.js";
|
|
5
6
|
export { RetryingExecutionStrategy } from "./strategies/RetryingExecutionStrategy.js";
|
|
6
7
|
export type { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { TaskRunner } from "./TaskRunner.js";
|
|
2
2
|
export { TaskRunnerBuilder } from "./TaskRunnerBuilder.js";
|
|
3
3
|
export { TaskStateManager } from "./TaskStateManager.js";
|
|
4
|
+
export { TaskGraphValidationError } from "./TaskGraphValidationError.js";
|
|
4
5
|
export { StandardExecutionStrategy } from "./strategies/StandardExecutionStrategy.js";
|
|
5
6
|
export { RetryingExecutionStrategy } from "./strategies/RetryingExecutionStrategy.js";
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
## Implementation
|
|
2
|
+
- [x] 1.1 Update `TaskRunnerExecutionConfig` to include an optional `concurrency` property.
|
|
3
|
+
- [x] 1.2 Update `WorkflowExecutor` to accept the `concurrency` limit.
|
|
4
|
+
- [x] 1.3 Implement a task queueing mechanism in `WorkflowExecutor` to manage pending tasks.
|
|
5
|
+
- [x] 1.4 Update execution logic to check available concurrency slots before starting a task.
|
|
6
|
+
- [x] 1.5 Ensure task completion triggers the execution of queued tasks.
|
|
7
|
+
- [x] 1.6 Verify that unlimited concurrency (default behavior) is preserved when no limit is set.
|
|
8
|
+
- [x] 1.7 Add unit tests for concurrency limits (e.g., ensure no more than N tasks run at once).
|
|
9
|
+
- [x] 1.8 Add integration tests with mixed dependencies and concurrency limits.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## Implementation
|
|
2
|
+
- [x] 1.1 Create `TaskRetryConfig` interface with `attempts`, `delay`, and `backoff`.
|
|
3
|
+
- [x] 1.2 Update `TaskStep` interface to include optional `retry: TaskRetryConfig`.
|
|
4
|
+
- [x] 1.3 Update execution logic to catch task failures.
|
|
5
|
+
- [x] 1.4 Implement retry loop/recursion checking `attempts` count.
|
|
6
|
+
- [x] 1.5 Implement delay logic with support for `fixed` and `exponential` backoff.
|
|
7
|
+
- [x] 1.6 Ensure `TaskResult` reflects the final status after retries (success if eventually succeeds, failure if all attempts fail).
|
|
8
|
+
- [x] 1.7 Add unit tests for successful retry after failure.
|
|
9
|
+
- [x] 1.8 Add unit tests for exhaustion of retry attempts (final failure).
|
|
10
|
+
- [x] 1.9 Add unit tests for backoff timing (mock timers).
|
package/openspec/project.md
CHANGED
|
@@ -23,6 +23,7 @@ The project follows a modular architecture with distinct components for managing
|
|
|
23
23
|
- **Commit Messages:** Follows conventional commits enforced by Commitlint.
|
|
24
24
|
- **Git Hooks:** Utilizes Husky for pre-commit and commit-msg hooks.
|
|
25
25
|
- **Testing:** Uses Vitest for unit and integration testing.
|
|
26
|
+
- **Atomic Commits:** When working on complex multi-task features, commit after each distinct task, ensuring build, lint, and test success to establish safe rollback points.
|
|
26
27
|
|
|
27
28
|
## Build/Test/Run Commands
|
|
28
29
|
- **Install Dependencies:** `pnpm install`
|
package/package.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ValidationResult } from "./contracts/ValidationResult.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Error thrown when a task graph fails validation.
|
|
5
|
+
* Contains the validation result with detailed error information.
|
|
6
|
+
*/
|
|
7
|
+
export class TaskGraphValidationError extends Error {
|
|
8
|
+
constructor(public result: ValidationResult, message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "TaskGraphValidationError";
|
|
11
|
+
}
|
|
12
|
+
}
|
package/src/TaskRunner.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { EventBus } from "./EventBus.js";
|
|
|
7
7
|
import { WorkflowExecutor } from "./WorkflowExecutor.js";
|
|
8
8
|
import { TaskRunnerExecutionConfig } from "./TaskRunnerExecutionConfig.js";
|
|
9
9
|
import { TaskStateManager } from "./TaskStateManager.js";
|
|
10
|
+
import { TaskGraphValidationError } from "./TaskGraphValidationError.js";
|
|
10
11
|
import { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
|
|
11
12
|
import { StandardExecutionStrategy } from "./strategies/StandardExecutionStrategy.js";
|
|
12
13
|
import { RetryingExecutionStrategy } from "./strategies/RetryingExecutionStrategy.js";
|
|
@@ -138,7 +139,10 @@ export class TaskRunner<TContext> {
|
|
|
138
139
|
|
|
139
140
|
const validationResult = this.validator.validate(taskGraph);
|
|
140
141
|
if (!validationResult.isValid) {
|
|
141
|
-
throw new
|
|
142
|
+
throw new TaskGraphValidationError(
|
|
143
|
+
validationResult,
|
|
144
|
+
this.validator.createErrorMessage(validationResult)
|
|
145
|
+
);
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
const stateManager = new TaskStateManager(this.eventBus);
|
|
@@ -152,7 +156,8 @@ export class TaskRunner<TContext> {
|
|
|
152
156
|
this.context,
|
|
153
157
|
this.eventBus,
|
|
154
158
|
stateManager,
|
|
155
|
-
strategy
|
|
159
|
+
strategy,
|
|
160
|
+
config?.concurrency
|
|
156
161
|
);
|
|
157
162
|
|
|
158
163
|
// We need to handle the timeout cleanup properly.
|
|
@@ -15,4 +15,9 @@ export interface TaskRunnerExecutionConfig {
|
|
|
15
15
|
* Useful for verifying the execution order and graph structure.
|
|
16
16
|
*/
|
|
17
17
|
dryRun?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* The maximum number of tasks to run concurrently.
|
|
20
|
+
* If undefined, all ready tasks will be run in parallel.
|
|
21
|
+
*/
|
|
22
|
+
concurrency?: number;
|
|
18
23
|
}
|
package/src/WorkflowExecutor.ts
CHANGED
|
@@ -9,17 +9,21 @@ import { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
|
|
|
9
9
|
* @template TContext The shape of the shared context object.
|
|
10
10
|
*/
|
|
11
11
|
export class WorkflowExecutor<TContext> {
|
|
12
|
+
private readyQueue: TaskStep<TContext>[] = [];
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* @param context The shared context object.
|
|
14
16
|
* @param eventBus The event bus to emit events.
|
|
15
17
|
* @param stateManager Manages execution state.
|
|
16
18
|
* @param strategy Execution strategy.
|
|
19
|
+
* @param concurrency Maximum number of concurrent tasks.
|
|
17
20
|
*/
|
|
18
21
|
constructor(
|
|
19
22
|
private context: TContext,
|
|
20
23
|
private eventBus: EventBus<TContext>,
|
|
21
24
|
private stateManager: TaskStateManager<TContext>,
|
|
22
|
-
private strategy: IExecutionStrategy<TContext
|
|
25
|
+
private strategy: IExecutionStrategy<TContext>,
|
|
26
|
+
private concurrency?: number
|
|
23
27
|
) {}
|
|
24
28
|
|
|
25
29
|
/**
|
|
@@ -100,10 +104,24 @@ export class WorkflowExecutor<TContext> {
|
|
|
100
104
|
executingPromises: Set<Promise<void>>,
|
|
101
105
|
signal?: AbortSignal
|
|
102
106
|
): void {
|
|
103
|
-
const
|
|
107
|
+
const newlyReady = this.stateManager.processDependencies();
|
|
108
|
+
|
|
109
|
+
// Add newly ready tasks to the queue
|
|
110
|
+
for (const task of newlyReady) {
|
|
111
|
+
this.readyQueue.push(task);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Execute ready tasks while respecting concurrency limit
|
|
115
|
+
while (this.readyQueue.length > 0) {
|
|
116
|
+
if (
|
|
117
|
+
this.concurrency !== undefined &&
|
|
118
|
+
executingPromises.size >= this.concurrency
|
|
119
|
+
) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const step = this.readyQueue.shift()!;
|
|
104
124
|
|
|
105
|
-
// Execute ready tasks
|
|
106
|
-
for (const step of toRun) {
|
|
107
125
|
this.stateManager.markRunning(step);
|
|
108
126
|
|
|
109
127
|
const taskPromise = this.strategy.execute(step, this.context, signal)
|
|
@@ -112,6 +130,8 @@ export class WorkflowExecutor<TContext> {
|
|
|
112
130
|
})
|
|
113
131
|
.finally(() => {
|
|
114
132
|
executingPromises.delete(taskPromise);
|
|
133
|
+
// When a task finishes, we try to run more
|
|
134
|
+
this.processLoop(executingPromises, signal);
|
|
115
135
|
});
|
|
116
136
|
|
|
117
137
|
executingPromises.add(taskPromise);
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { TaskRunner } from "./TaskRunner.js";
|
|
2
2
|
export { TaskRunnerBuilder } from "./TaskRunnerBuilder.js";
|
|
3
3
|
export { TaskStateManager } from "./TaskStateManager.js";
|
|
4
|
+
export { TaskGraphValidationError } from "./TaskGraphValidationError.js";
|
|
4
5
|
export { StandardExecutionStrategy } from "./strategies/StandardExecutionStrategy.js";
|
|
5
6
|
export { RetryingExecutionStrategy } from "./strategies/RetryingExecutionStrategy.js";
|
|
6
7
|
export type { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
|