@renseiai/agentfactory 0.8.6 → 0.8.7
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/README.md +2 -2
- package/dist/src/governor/decision-engine.d.ts +7 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -1
- package/dist/src/governor/decision-engine.js +59 -1
- package/dist/src/governor/governor.d.ts +5 -1
- package/dist/src/governor/governor.d.ts.map +1 -1
- package/dist/src/governor/governor.js +6 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/orchestrator/activity-emitter.d.ts +3 -3
- package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -1
- package/dist/src/orchestrator/activity-emitter.js +1 -1
- package/dist/src/orchestrator/detect-work-type.test.js +25 -16
- package/dist/src/orchestrator/index.d.ts +4 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +1 -0
- package/dist/src/orchestrator/issue-tracker-client.d.ts +103 -0
- package/dist/src/orchestrator/issue-tracker-client.d.ts.map +1 -0
- package/dist/src/orchestrator/issue-tracker-client.js +8 -0
- package/dist/src/orchestrator/log-analyzer.d.ts +19 -4
- package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -1
- package/dist/src/orchestrator/log-analyzer.js +26 -50
- package/dist/src/orchestrator/orchestrator-utils.test.js +3 -0
- package/dist/src/orchestrator/orchestrator.d.ts +4 -2
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +193 -115
- package/dist/src/orchestrator/parse-work-result.d.ts +1 -1
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
- package/dist/src/orchestrator/parse-work-result.js +1 -1
- package/dist/src/orchestrator/session-logger.d.ts +1 -1
- package/dist/src/orchestrator/session-logger.d.ts.map +1 -1
- package/dist/src/orchestrator/state-recovery.d.ts +1 -1
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -1
- package/dist/src/orchestrator/state-recovery.js +1 -0
- package/dist/src/orchestrator/state-types.d.ts +1 -1
- package/dist/src/orchestrator/state-types.d.ts.map +1 -1
- package/dist/src/orchestrator/types.d.ts +22 -2
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/orchestrator/work-types.d.ts +50 -0
- package/dist/src/orchestrator/work-types.d.ts.map +1 -0
- package/dist/src/orchestrator/work-types.js +20 -0
- package/dist/src/templates/registry.d.ts +1 -1
- package/dist/src/templates/registry.test.js +2 -2
- package/dist/src/templates/renderer.d.ts +1 -1
- package/dist/src/templates/types.d.ts +4 -2
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +1 -0
- package/dist/src/templates/types.test.js +4 -3
- package/dist/src/tools/index.d.ts +0 -3
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/index.js +0 -2
- package/dist/src/workflow/index.d.ts +14 -0
- package/dist/src/workflow/index.d.ts.map +1 -0
- package/dist/src/workflow/index.js +10 -0
- package/dist/src/workflow/transition-engine.d.ts +44 -0
- package/dist/src/workflow/transition-engine.d.ts.map +1 -0
- package/dist/src/workflow/transition-engine.js +106 -0
- package/dist/src/workflow/transition-engine.test.d.ts +2 -0
- package/dist/src/workflow/transition-engine.test.d.ts.map +1 -0
- package/dist/src/workflow/transition-engine.test.js +313 -0
- package/dist/src/workflow/workflow-loader.d.ts +21 -0
- package/dist/src/workflow/workflow-loader.d.ts.map +1 -0
- package/dist/src/workflow/workflow-loader.js +40 -0
- package/dist/src/workflow/workflow-loader.test.d.ts +2 -0
- package/dist/src/workflow/workflow-loader.test.d.ts.map +1 -0
- package/dist/src/workflow/workflow-loader.test.js +134 -0
- package/dist/src/workflow/workflow-registry.d.ts +56 -0
- package/dist/src/workflow/workflow-registry.d.ts.map +1 -0
- package/dist/src/workflow/workflow-registry.js +107 -0
- package/dist/src/workflow/workflow-registry.test.d.ts +2 -0
- package/dist/src/workflow/workflow-registry.test.d.ts.map +1 -0
- package/dist/src/workflow/workflow-registry.test.js +201 -0
- package/dist/src/workflow/workflow-types.d.ts +269 -0
- package/dist/src/workflow/workflow-types.d.ts.map +1 -0
- package/dist/src/workflow/workflow-types.js +88 -0
- package/dist/src/workflow/workflow-types.test.d.ts +2 -0
- package/dist/src/workflow/workflow-types.test.d.ts.map +1 -0
- package/dist/src/workflow/workflow-types.test.js +440 -0
- package/package.json +3 -4
- package/dist/src/linear-cli.d.ts +0 -38
- package/dist/src/linear-cli.d.ts.map +0 -1
- package/dist/src/linear-cli.js +0 -674
- package/dist/src/tools/linear-runner.d.ts +0 -34
- package/dist/src/tools/linear-runner.d.ts.map +0 -1
- package/dist/src/tools/linear-runner.js +0 -700
- package/dist/src/tools/plugins/linear.d.ts +0 -9
- package/dist/src/tools/plugins/linear.d.ts.map +0 -1
- package/dist/src/tools/plugins/linear.js +0 -138
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Registry
|
|
3
|
+
*
|
|
4
|
+
* In-memory registry that manages WorkflowDefinition resolution with
|
|
5
|
+
* layered overrides, following the same pattern as TemplateRegistry.
|
|
6
|
+
*
|
|
7
|
+
* Resolution order (later sources override earlier):
|
|
8
|
+
* 1. Built-in default (workflow/defaults/workflow.yaml)
|
|
9
|
+
* 2. Project-level override (e.g., .agentfactory/workflow.yaml)
|
|
10
|
+
* 3. Inline config override (programmatic)
|
|
11
|
+
*/
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import { loadWorkflowDefinitionFile, getBuiltinWorkflowPath } from './workflow-loader.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Fallback constants (match the hard-coded values in decision-engine.ts
|
|
16
|
+
// and agent-tracking.ts for backward compatibility)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const DEFAULT_MAX_SESSIONS_PER_ISSUE = 8;
|
|
19
|
+
const DEFAULT_MAX_SESSIONS_PER_PHASE = 3;
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Registry
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
export class WorkflowRegistry {
|
|
24
|
+
workflow = null;
|
|
25
|
+
constructor() { }
|
|
26
|
+
/**
|
|
27
|
+
* Create and initialize a registry from configuration.
|
|
28
|
+
*/
|
|
29
|
+
static create(config = {}) {
|
|
30
|
+
const registry = new WorkflowRegistry();
|
|
31
|
+
registry.initialize(config);
|
|
32
|
+
return registry;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the registry by loading workflow definition from
|
|
36
|
+
* configured sources. Later sources override earlier ones.
|
|
37
|
+
*/
|
|
38
|
+
initialize(config = {}) {
|
|
39
|
+
const { workflowPath, workflow, useBuiltinDefault = true } = config;
|
|
40
|
+
// Layer 1: Built-in default
|
|
41
|
+
if (useBuiltinDefault) {
|
|
42
|
+
const builtinPath = getBuiltinWorkflowPath();
|
|
43
|
+
if (fs.existsSync(builtinPath)) {
|
|
44
|
+
this.workflow = loadWorkflowDefinitionFile(builtinPath);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Layer 2: Project-level override
|
|
48
|
+
if (workflowPath && fs.existsSync(workflowPath)) {
|
|
49
|
+
this.workflow = loadWorkflowDefinitionFile(workflowPath);
|
|
50
|
+
}
|
|
51
|
+
// Layer 3: Inline override (highest priority)
|
|
52
|
+
if (workflow) {
|
|
53
|
+
this.workflow = workflow;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get the currently loaded workflow definition.
|
|
58
|
+
*/
|
|
59
|
+
getWorkflow() {
|
|
60
|
+
return this.workflow;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the escalation configuration, or null if none defined.
|
|
64
|
+
*/
|
|
65
|
+
getEscalation() {
|
|
66
|
+
return this.workflow?.escalation ?? null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Compute escalation strategy from the workflow's escalation ladder.
|
|
70
|
+
*
|
|
71
|
+
* Selects the highest cycle threshold that is <= the given cycleCount.
|
|
72
|
+
* Falls back to 'normal' if no match or no escalation config.
|
|
73
|
+
*/
|
|
74
|
+
getEscalationStrategy(cycleCount) {
|
|
75
|
+
const escalation = this.workflow?.escalation;
|
|
76
|
+
if (!escalation) {
|
|
77
|
+
return computeStrategyFallback(cycleCount);
|
|
78
|
+
}
|
|
79
|
+
const ladder = escalation.ladder;
|
|
80
|
+
// Sort descending by cycle so we can find the first match
|
|
81
|
+
const sorted = [...ladder].sort((a, b) => b.cycle - a.cycle);
|
|
82
|
+
const match = sorted.find(rung => cycleCount >= rung.cycle);
|
|
83
|
+
return match?.strategy ?? 'normal';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get circuit breaker limits from the workflow definition.
|
|
87
|
+
*/
|
|
88
|
+
getCircuitBreakerLimits() {
|
|
89
|
+
const cb = this.workflow?.escalation?.circuitBreaker;
|
|
90
|
+
return {
|
|
91
|
+
maxSessionsPerIssue: cb?.maxSessionsPerIssue ?? DEFAULT_MAX_SESSIONS_PER_ISSUE,
|
|
92
|
+
maxSessionsPerPhase: cb?.maxSessionsPerPhase ?? DEFAULT_MAX_SESSIONS_PER_PHASE,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Fallback (mirrors agent-tracking.ts:computeStrategy())
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
function computeStrategyFallback(cycleCount) {
|
|
100
|
+
if (cycleCount <= 1)
|
|
101
|
+
return 'normal';
|
|
102
|
+
if (cycleCount === 2)
|
|
103
|
+
return 'context-enriched';
|
|
104
|
+
if (cycleCount === 3)
|
|
105
|
+
return 'decompose';
|
|
106
|
+
return 'escalate-human';
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-registry.test.d.ts","sourceRoot":"","sources":["../../../src/workflow/workflow-registry.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { WorkflowRegistry } from './workflow-registry.js';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
function makeWorkflow(overrides) {
|
|
10
|
+
return {
|
|
11
|
+
apiVersion: 'v1.1',
|
|
12
|
+
kind: 'WorkflowDefinition',
|
|
13
|
+
metadata: { name: 'test-workflow' },
|
|
14
|
+
phases: [
|
|
15
|
+
{ name: 'development', template: 'development' },
|
|
16
|
+
{ name: 'qa', template: 'qa' },
|
|
17
|
+
],
|
|
18
|
+
transitions: [
|
|
19
|
+
{ from: 'Backlog', to: 'development' },
|
|
20
|
+
{ from: 'Finished', to: 'qa' },
|
|
21
|
+
],
|
|
22
|
+
...overrides,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Tests
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
describe('WorkflowRegistry', () => {
|
|
29
|
+
describe('create()', () => {
|
|
30
|
+
it('loads built-in default workflow', () => {
|
|
31
|
+
const registry = WorkflowRegistry.create();
|
|
32
|
+
const workflow = registry.getWorkflow();
|
|
33
|
+
expect(workflow).not.toBeNull();
|
|
34
|
+
expect(workflow.metadata.name).toBe('default-workflow');
|
|
35
|
+
});
|
|
36
|
+
it('accepts inline workflow override (highest priority)', () => {
|
|
37
|
+
const custom = makeWorkflow({ metadata: { name: 'custom' } });
|
|
38
|
+
const registry = WorkflowRegistry.create({ workflow: custom });
|
|
39
|
+
const workflow = registry.getWorkflow();
|
|
40
|
+
expect(workflow.metadata.name).toBe('custom');
|
|
41
|
+
});
|
|
42
|
+
it('project-level YAML overrides built-in default', () => {
|
|
43
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'workflow-reg-'));
|
|
44
|
+
const tmpPath = path.join(tmpDir, 'workflow.yaml');
|
|
45
|
+
fs.writeFileSync(tmpPath, [
|
|
46
|
+
'apiVersion: v1.1',
|
|
47
|
+
'kind: WorkflowDefinition',
|
|
48
|
+
'metadata:',
|
|
49
|
+
' name: project-override',
|
|
50
|
+
'phases:',
|
|
51
|
+
' - name: dev',
|
|
52
|
+
' template: development',
|
|
53
|
+
'transitions:',
|
|
54
|
+
' - from: Backlog',
|
|
55
|
+
' to: dev',
|
|
56
|
+
'escalation:',
|
|
57
|
+
' ladder:',
|
|
58
|
+
' - cycle: 1',
|
|
59
|
+
' strategy: normal',
|
|
60
|
+
' circuitBreaker:',
|
|
61
|
+
' maxSessionsPerIssue: 5',
|
|
62
|
+
].join('\n'));
|
|
63
|
+
try {
|
|
64
|
+
const registry = WorkflowRegistry.create({ workflowPath: tmpPath });
|
|
65
|
+
const workflow = registry.getWorkflow();
|
|
66
|
+
expect(workflow.metadata.name).toBe('project-override');
|
|
67
|
+
expect(workflow.escalation.circuitBreaker.maxSessionsPerIssue).toBe(5);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
it('inline override beats project-level YAML', () => {
|
|
74
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'workflow-reg-'));
|
|
75
|
+
const tmpPath = path.join(tmpDir, 'workflow.yaml');
|
|
76
|
+
fs.writeFileSync(tmpPath, [
|
|
77
|
+
'apiVersion: v1.1',
|
|
78
|
+
'kind: WorkflowDefinition',
|
|
79
|
+
'metadata:',
|
|
80
|
+
' name: project-override',
|
|
81
|
+
'phases: []',
|
|
82
|
+
'transitions: []',
|
|
83
|
+
'escalation:',
|
|
84
|
+
' ladder:',
|
|
85
|
+
' - cycle: 1',
|
|
86
|
+
' strategy: normal',
|
|
87
|
+
' circuitBreaker:',
|
|
88
|
+
' maxSessionsPerIssue: 5',
|
|
89
|
+
].join('\n'));
|
|
90
|
+
try {
|
|
91
|
+
const custom = makeWorkflow({ metadata: { name: 'inline-wins' } });
|
|
92
|
+
const registry = WorkflowRegistry.create({
|
|
93
|
+
workflowPath: tmpPath,
|
|
94
|
+
workflow: custom,
|
|
95
|
+
});
|
|
96
|
+
expect(registry.getWorkflow().metadata.name).toBe('inline-wins');
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
it('can disable built-in default', () => {
|
|
103
|
+
const registry = WorkflowRegistry.create({ useBuiltinDefault: false });
|
|
104
|
+
expect(registry.getWorkflow()).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
it('ignores non-existent project-level path', () => {
|
|
107
|
+
const registry = WorkflowRegistry.create({
|
|
108
|
+
workflowPath: '/non/existent/workflow.yaml',
|
|
109
|
+
});
|
|
110
|
+
// Falls back to built-in default
|
|
111
|
+
expect(registry.getWorkflow().metadata.name).toBe('default-workflow');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('getEscalationStrategy()', () => {
|
|
115
|
+
it('returns strategy from the built-in ladder', () => {
|
|
116
|
+
const registry = WorkflowRegistry.create();
|
|
117
|
+
expect(registry.getEscalationStrategy(0)).toBe('normal');
|
|
118
|
+
expect(registry.getEscalationStrategy(1)).toBe('normal');
|
|
119
|
+
expect(registry.getEscalationStrategy(2)).toBe('context-enriched');
|
|
120
|
+
expect(registry.getEscalationStrategy(3)).toBe('decompose');
|
|
121
|
+
expect(registry.getEscalationStrategy(4)).toBe('escalate-human');
|
|
122
|
+
expect(registry.getEscalationStrategy(10)).toBe('escalate-human');
|
|
123
|
+
});
|
|
124
|
+
it('returns strategy from custom ladder', () => {
|
|
125
|
+
const custom = makeWorkflow({
|
|
126
|
+
escalation: {
|
|
127
|
+
ladder: [
|
|
128
|
+
{ cycle: 1, strategy: 'normal' },
|
|
129
|
+
{ cycle: 5, strategy: 'custom-strategy' },
|
|
130
|
+
],
|
|
131
|
+
circuitBreaker: { maxSessionsPerIssue: 10 },
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
const registry = WorkflowRegistry.create({ workflow: custom });
|
|
135
|
+
expect(registry.getEscalationStrategy(1)).toBe('normal');
|
|
136
|
+
expect(registry.getEscalationStrategy(4)).toBe('normal');
|
|
137
|
+
expect(registry.getEscalationStrategy(5)).toBe('custom-strategy');
|
|
138
|
+
expect(registry.getEscalationStrategy(99)).toBe('custom-strategy');
|
|
139
|
+
});
|
|
140
|
+
it('falls back to hard-coded strategy when no escalation config', () => {
|
|
141
|
+
const custom = makeWorkflow(); // No escalation
|
|
142
|
+
const registry = WorkflowRegistry.create({ workflow: custom });
|
|
143
|
+
expect(registry.getEscalationStrategy(0)).toBe('normal');
|
|
144
|
+
expect(registry.getEscalationStrategy(1)).toBe('normal');
|
|
145
|
+
expect(registry.getEscalationStrategy(2)).toBe('context-enriched');
|
|
146
|
+
expect(registry.getEscalationStrategy(3)).toBe('decompose');
|
|
147
|
+
expect(registry.getEscalationStrategy(4)).toBe('escalate-human');
|
|
148
|
+
});
|
|
149
|
+
it('returns "normal" for cycle 0 when ladder starts at 1', () => {
|
|
150
|
+
const registry = WorkflowRegistry.create();
|
|
151
|
+
// The built-in ladder starts at cycle 1, so cycle 0 should still match
|
|
152
|
+
// cycle 1's strategy (normal) since 0 < 1 means no match, falling back.
|
|
153
|
+
// Actually: sorted desc [4,3,2,1], find first where 0 >= rung.cycle → none
|
|
154
|
+
// So returns 'normal' (fallback).
|
|
155
|
+
expect(registry.getEscalationStrategy(0)).toBe('normal');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('getCircuitBreakerLimits()', () => {
|
|
159
|
+
it('returns built-in limits matching hard-coded constants', () => {
|
|
160
|
+
const registry = WorkflowRegistry.create();
|
|
161
|
+
const limits = registry.getCircuitBreakerLimits();
|
|
162
|
+
expect(limits.maxSessionsPerIssue).toBe(8); // MAX_TOTAL_SESSIONS
|
|
163
|
+
expect(limits.maxSessionsPerPhase).toBe(3); // MAX_SESSION_ATTEMPTS
|
|
164
|
+
});
|
|
165
|
+
it('returns custom limits from workflow definition', () => {
|
|
166
|
+
const custom = makeWorkflow({
|
|
167
|
+
escalation: {
|
|
168
|
+
ladder: [{ cycle: 1, strategy: 'normal' }],
|
|
169
|
+
circuitBreaker: {
|
|
170
|
+
maxSessionsPerIssue: 12,
|
|
171
|
+
maxSessionsPerPhase: 5,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
const registry = WorkflowRegistry.create({ workflow: custom });
|
|
176
|
+
const limits = registry.getCircuitBreakerLimits();
|
|
177
|
+
expect(limits.maxSessionsPerIssue).toBe(12);
|
|
178
|
+
expect(limits.maxSessionsPerPhase).toBe(5);
|
|
179
|
+
});
|
|
180
|
+
it('returns defaults when no escalation config', () => {
|
|
181
|
+
const custom = makeWorkflow(); // No escalation
|
|
182
|
+
const registry = WorkflowRegistry.create({ workflow: custom });
|
|
183
|
+
const limits = registry.getCircuitBreakerLimits();
|
|
184
|
+
expect(limits.maxSessionsPerIssue).toBe(8);
|
|
185
|
+
expect(limits.maxSessionsPerPhase).toBe(3);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe('getEscalation()', () => {
|
|
189
|
+
it('returns escalation config from workflow', () => {
|
|
190
|
+
const registry = WorkflowRegistry.create();
|
|
191
|
+
const escalation = registry.getEscalation();
|
|
192
|
+
expect(escalation).not.toBeNull();
|
|
193
|
+
expect(escalation.ladder.length).toBeGreaterThanOrEqual(4);
|
|
194
|
+
});
|
|
195
|
+
it('returns null when no escalation defined', () => {
|
|
196
|
+
const custom = makeWorkflow();
|
|
197
|
+
const registry = WorkflowRegistry.create({ workflow: custom });
|
|
198
|
+
expect(registry.getEscalation()).toBeNull();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Definition Types
|
|
3
|
+
*
|
|
4
|
+
* TypeScript interfaces and Zod schemas for the WorkflowDefinition document kind.
|
|
5
|
+
* A WorkflowDefinition declares the workflow graph — phases, transitions,
|
|
6
|
+
* escalation ladder, gates, and parallelism — in YAML rather than hard-coded
|
|
7
|
+
* TypeScript.
|
|
8
|
+
*
|
|
9
|
+
* This is an additive v1.1 schema extension. Existing v1 WorkflowTemplate
|
|
10
|
+
* and PartialTemplate documents remain valid and unchanged.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
/**
|
|
14
|
+
* Escalation strategies that control how the workflow responds to failures.
|
|
15
|
+
* Mirrors the hard-coded ladder in agent-tracking.ts:computeStrategy().
|
|
16
|
+
*/
|
|
17
|
+
export type EscalationStrategy = 'normal' | 'context-enriched' | 'decompose' | 'escalate-human';
|
|
18
|
+
/**
|
|
19
|
+
* A phase in the workflow graph. Each phase references a WorkflowTemplate
|
|
20
|
+
* by name and optionally provides strategy-specific template variants.
|
|
21
|
+
*/
|
|
22
|
+
export interface PhaseDefinition {
|
|
23
|
+
/** Unique phase name (e.g., "development", "qa", "refinement") */
|
|
24
|
+
name: string;
|
|
25
|
+
/** Human-readable description */
|
|
26
|
+
description?: string;
|
|
27
|
+
/** WorkflowTemplate name to use for this phase */
|
|
28
|
+
template: string;
|
|
29
|
+
/**
|
|
30
|
+
* Strategy-specific template overrides.
|
|
31
|
+
* Maps escalation strategy name to an alternate template name.
|
|
32
|
+
* Example: { "context-enriched": "refinement-context-enriched" }
|
|
33
|
+
*/
|
|
34
|
+
variants?: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A transition edge in the workflow graph. Maps a Linear status to a phase.
|
|
38
|
+
*/
|
|
39
|
+
export interface TransitionDefinition {
|
|
40
|
+
/** Source Linear status (e.g., "Backlog", "Finished", "Rejected") */
|
|
41
|
+
from: string;
|
|
42
|
+
/** Target phase name (must reference a defined phase) */
|
|
43
|
+
to: string;
|
|
44
|
+
/**
|
|
45
|
+
* Optional condition expression. When present, the transition only fires
|
|
46
|
+
* if the condition evaluates to true. Stored as an opaque string in Phase 1;
|
|
47
|
+
* evaluated by the condition engine in Phase 3.
|
|
48
|
+
*/
|
|
49
|
+
condition?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Evaluation priority. Higher values are evaluated first.
|
|
52
|
+
* Useful when multiple transitions share the same `from` status.
|
|
53
|
+
* Default: 0
|
|
54
|
+
*/
|
|
55
|
+
priority?: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A single rung on the escalation ladder. Maps a cycle count threshold
|
|
59
|
+
* to an escalation strategy.
|
|
60
|
+
*/
|
|
61
|
+
export interface EscalationLadderRung {
|
|
62
|
+
/** Cycle count at which this strategy activates */
|
|
63
|
+
cycle: number;
|
|
64
|
+
/** Strategy to apply at this cycle count */
|
|
65
|
+
strategy: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Escalation configuration controlling retry behavior and circuit breakers.
|
|
69
|
+
*/
|
|
70
|
+
export interface EscalationConfig {
|
|
71
|
+
/**
|
|
72
|
+
* Escalation ladder. Each rung maps a cycle count to a strategy.
|
|
73
|
+
* The highest cycle count <= the current cycle is selected.
|
|
74
|
+
*/
|
|
75
|
+
ladder: EscalationLadderRung[];
|
|
76
|
+
/** Circuit breaker thresholds */
|
|
77
|
+
circuitBreaker: {
|
|
78
|
+
/** Max total sessions across all phases before halting (default: 8) */
|
|
79
|
+
maxSessionsPerIssue: number;
|
|
80
|
+
/** Max sessions per status before circuit breaker trips (default: 3) */
|
|
81
|
+
maxSessionsPerPhase?: number;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* An external gate that can pause workflow execution until a condition is met.
|
|
86
|
+
*/
|
|
87
|
+
export interface GateDefinition {
|
|
88
|
+
/** Unique gate name */
|
|
89
|
+
name: string;
|
|
90
|
+
/** Human-readable description */
|
|
91
|
+
description?: string;
|
|
92
|
+
/** Gate type: signal (external event), timer (time-based), webhook (HTTP callback) */
|
|
93
|
+
type: 'signal' | 'timer' | 'webhook';
|
|
94
|
+
/** Type-specific trigger configuration */
|
|
95
|
+
trigger: Record<string, unknown>;
|
|
96
|
+
/** Timeout configuration */
|
|
97
|
+
timeout?: {
|
|
98
|
+
/** Duration string or milliseconds */
|
|
99
|
+
duration: string;
|
|
100
|
+
/** Action to take on timeout */
|
|
101
|
+
action: 'escalate' | 'skip' | 'fail';
|
|
102
|
+
};
|
|
103
|
+
/** Phase names this gate applies to */
|
|
104
|
+
appliesTo?: string[];
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Defines a group of phases that can execute concurrently.
|
|
108
|
+
*/
|
|
109
|
+
export interface ParallelismGroupDefinition {
|
|
110
|
+
/** Unique group name */
|
|
111
|
+
name: string;
|
|
112
|
+
/** Human-readable description */
|
|
113
|
+
description?: string;
|
|
114
|
+
/** Phase names to execute in parallel */
|
|
115
|
+
phases: string[];
|
|
116
|
+
/** Parallelism strategy: fan-out, fan-in, or race */
|
|
117
|
+
strategy: 'fan-out' | 'fan-in' | 'race';
|
|
118
|
+
/** Maximum concurrent executions (default: unlimited) */
|
|
119
|
+
maxConcurrent?: number;
|
|
120
|
+
/** Whether to wait for all parallel executions to complete */
|
|
121
|
+
waitForAll?: boolean;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A declarative workflow definition. This is the primary document kind
|
|
125
|
+
* introduced in schema v1.1, replacing the hard-coded workflow graph
|
|
126
|
+
* in decision-engine.ts and agent-tracking.ts.
|
|
127
|
+
*/
|
|
128
|
+
export interface WorkflowDefinition {
|
|
129
|
+
apiVersion: 'v1.1';
|
|
130
|
+
kind: 'WorkflowDefinition';
|
|
131
|
+
metadata: {
|
|
132
|
+
name: string;
|
|
133
|
+
description?: string;
|
|
134
|
+
};
|
|
135
|
+
/** Available phases and their template bindings */
|
|
136
|
+
phases: PhaseDefinition[];
|
|
137
|
+
/** Workflow graph edges: status-to-phase transitions */
|
|
138
|
+
transitions: TransitionDefinition[];
|
|
139
|
+
/** Escalation ladder and circuit breaker configuration */
|
|
140
|
+
escalation?: EscalationConfig;
|
|
141
|
+
/** External gates that can pause workflow execution (Phase 4) */
|
|
142
|
+
gates?: GateDefinition[];
|
|
143
|
+
/** Parallelism groups for concurrent phase execution (Phase 4) */
|
|
144
|
+
parallelism?: ParallelismGroupDefinition[];
|
|
145
|
+
}
|
|
146
|
+
export declare const PhaseDefinitionSchema: z.ZodObject<{
|
|
147
|
+
name: z.ZodString;
|
|
148
|
+
description: z.ZodOptional<z.ZodString>;
|
|
149
|
+
template: z.ZodString;
|
|
150
|
+
variants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
151
|
+
}, z.core.$strip>;
|
|
152
|
+
export declare const TransitionDefinitionSchema: z.ZodObject<{
|
|
153
|
+
from: z.ZodString;
|
|
154
|
+
to: z.ZodString;
|
|
155
|
+
condition: z.ZodOptional<z.ZodString>;
|
|
156
|
+
priority: z.ZodOptional<z.ZodNumber>;
|
|
157
|
+
}, z.core.$strip>;
|
|
158
|
+
export declare const EscalationLadderRungSchema: z.ZodObject<{
|
|
159
|
+
cycle: z.ZodNumber;
|
|
160
|
+
strategy: z.ZodString;
|
|
161
|
+
}, z.core.$strip>;
|
|
162
|
+
export declare const EscalationConfigSchema: z.ZodObject<{
|
|
163
|
+
ladder: z.ZodArray<z.ZodObject<{
|
|
164
|
+
cycle: z.ZodNumber;
|
|
165
|
+
strategy: z.ZodString;
|
|
166
|
+
}, z.core.$strip>>;
|
|
167
|
+
circuitBreaker: z.ZodObject<{
|
|
168
|
+
maxSessionsPerIssue: z.ZodNumber;
|
|
169
|
+
maxSessionsPerPhase: z.ZodOptional<z.ZodNumber>;
|
|
170
|
+
}, z.core.$strip>;
|
|
171
|
+
}, z.core.$strip>;
|
|
172
|
+
export declare const GateDefinitionSchema: z.ZodObject<{
|
|
173
|
+
name: z.ZodString;
|
|
174
|
+
description: z.ZodOptional<z.ZodString>;
|
|
175
|
+
type: z.ZodEnum<{
|
|
176
|
+
signal: "signal";
|
|
177
|
+
timer: "timer";
|
|
178
|
+
webhook: "webhook";
|
|
179
|
+
}>;
|
|
180
|
+
trigger: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
181
|
+
timeout: z.ZodOptional<z.ZodObject<{
|
|
182
|
+
duration: z.ZodString;
|
|
183
|
+
action: z.ZodEnum<{
|
|
184
|
+
escalate: "escalate";
|
|
185
|
+
skip: "skip";
|
|
186
|
+
fail: "fail";
|
|
187
|
+
}>;
|
|
188
|
+
}, z.core.$strip>>;
|
|
189
|
+
appliesTo: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
190
|
+
}, z.core.$strip>;
|
|
191
|
+
export declare const ParallelismGroupDefinitionSchema: z.ZodObject<{
|
|
192
|
+
name: z.ZodString;
|
|
193
|
+
description: z.ZodOptional<z.ZodString>;
|
|
194
|
+
phases: z.ZodArray<z.ZodString>;
|
|
195
|
+
strategy: z.ZodEnum<{
|
|
196
|
+
"fan-out": "fan-out";
|
|
197
|
+
"fan-in": "fan-in";
|
|
198
|
+
race: "race";
|
|
199
|
+
}>;
|
|
200
|
+
maxConcurrent: z.ZodOptional<z.ZodNumber>;
|
|
201
|
+
waitForAll: z.ZodOptional<z.ZodBoolean>;
|
|
202
|
+
}, z.core.$strip>;
|
|
203
|
+
export declare const WorkflowDefinitionSchema: z.ZodObject<{
|
|
204
|
+
apiVersion: z.ZodLiteral<"v1.1">;
|
|
205
|
+
kind: z.ZodLiteral<"WorkflowDefinition">;
|
|
206
|
+
metadata: z.ZodObject<{
|
|
207
|
+
name: z.ZodString;
|
|
208
|
+
description: z.ZodOptional<z.ZodString>;
|
|
209
|
+
}, z.core.$strip>;
|
|
210
|
+
phases: z.ZodArray<z.ZodObject<{
|
|
211
|
+
name: z.ZodString;
|
|
212
|
+
description: z.ZodOptional<z.ZodString>;
|
|
213
|
+
template: z.ZodString;
|
|
214
|
+
variants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
215
|
+
}, z.core.$strip>>;
|
|
216
|
+
transitions: z.ZodArray<z.ZodObject<{
|
|
217
|
+
from: z.ZodString;
|
|
218
|
+
to: z.ZodString;
|
|
219
|
+
condition: z.ZodOptional<z.ZodString>;
|
|
220
|
+
priority: z.ZodOptional<z.ZodNumber>;
|
|
221
|
+
}, z.core.$strip>>;
|
|
222
|
+
escalation: z.ZodOptional<z.ZodObject<{
|
|
223
|
+
ladder: z.ZodArray<z.ZodObject<{
|
|
224
|
+
cycle: z.ZodNumber;
|
|
225
|
+
strategy: z.ZodString;
|
|
226
|
+
}, z.core.$strip>>;
|
|
227
|
+
circuitBreaker: z.ZodObject<{
|
|
228
|
+
maxSessionsPerIssue: z.ZodNumber;
|
|
229
|
+
maxSessionsPerPhase: z.ZodOptional<z.ZodNumber>;
|
|
230
|
+
}, z.core.$strip>;
|
|
231
|
+
}, z.core.$strip>>;
|
|
232
|
+
gates: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
233
|
+
name: z.ZodString;
|
|
234
|
+
description: z.ZodOptional<z.ZodString>;
|
|
235
|
+
type: z.ZodEnum<{
|
|
236
|
+
signal: "signal";
|
|
237
|
+
timer: "timer";
|
|
238
|
+
webhook: "webhook";
|
|
239
|
+
}>;
|
|
240
|
+
trigger: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
241
|
+
timeout: z.ZodOptional<z.ZodObject<{
|
|
242
|
+
duration: z.ZodString;
|
|
243
|
+
action: z.ZodEnum<{
|
|
244
|
+
escalate: "escalate";
|
|
245
|
+
skip: "skip";
|
|
246
|
+
fail: "fail";
|
|
247
|
+
}>;
|
|
248
|
+
}, z.core.$strip>>;
|
|
249
|
+
appliesTo: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
250
|
+
}, z.core.$strip>>>;
|
|
251
|
+
parallelism: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
252
|
+
name: z.ZodString;
|
|
253
|
+
description: z.ZodOptional<z.ZodString>;
|
|
254
|
+
phases: z.ZodArray<z.ZodString>;
|
|
255
|
+
strategy: z.ZodEnum<{
|
|
256
|
+
"fan-out": "fan-out";
|
|
257
|
+
"fan-in": "fan-in";
|
|
258
|
+
race: "race";
|
|
259
|
+
}>;
|
|
260
|
+
maxConcurrent: z.ZodOptional<z.ZodNumber>;
|
|
261
|
+
waitForAll: z.ZodOptional<z.ZodBoolean>;
|
|
262
|
+
}, z.core.$strip>>>;
|
|
263
|
+
}, z.core.$strip>;
|
|
264
|
+
/**
|
|
265
|
+
* Validate a parsed YAML object as a WorkflowDefinition.
|
|
266
|
+
* Throws ZodError with detailed messages on failure.
|
|
267
|
+
*/
|
|
268
|
+
export declare function validateWorkflowDefinition(data: unknown, filePath?: string): WorkflowDefinition;
|
|
269
|
+
//# sourceMappingURL=workflow-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-types.d.ts","sourceRoot":"","sources":["../../../src/workflow/workflow-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Definition Types
|
|
3
|
+
*
|
|
4
|
+
* TypeScript interfaces and Zod schemas for the WorkflowDefinition document kind.
|
|
5
|
+
* A WorkflowDefinition declares the workflow graph — phases, transitions,
|
|
6
|
+
* escalation ladder, gates, and parallelism — in YAML rather than hard-coded
|
|
7
|
+
* TypeScript.
|
|
8
|
+
*
|
|
9
|
+
* This is an additive v1.1 schema extension. Existing v1 WorkflowTemplate
|
|
10
|
+
* and PartialTemplate documents remain valid and unchanged.
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Zod Schemas
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
export const PhaseDefinitionSchema = z.object({
|
|
17
|
+
name: z.string().min(1),
|
|
18
|
+
description: z.string().optional(),
|
|
19
|
+
template: z.string().min(1),
|
|
20
|
+
variants: z.record(z.string(), z.string()).optional(),
|
|
21
|
+
});
|
|
22
|
+
export const TransitionDefinitionSchema = z.object({
|
|
23
|
+
from: z.string().min(1),
|
|
24
|
+
to: z.string().min(1),
|
|
25
|
+
condition: z.string().optional(),
|
|
26
|
+
priority: z.number().int().optional(),
|
|
27
|
+
});
|
|
28
|
+
export const EscalationLadderRungSchema = z.object({
|
|
29
|
+
cycle: z.number().int().nonnegative(),
|
|
30
|
+
strategy: z.string().min(1),
|
|
31
|
+
});
|
|
32
|
+
export const EscalationConfigSchema = z.object({
|
|
33
|
+
ladder: z.array(EscalationLadderRungSchema).min(1),
|
|
34
|
+
circuitBreaker: z.object({
|
|
35
|
+
maxSessionsPerIssue: z.number().int().positive(),
|
|
36
|
+
maxSessionsPerPhase: z.number().int().positive().optional(),
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
export const GateDefinitionSchema = z.object({
|
|
40
|
+
name: z.string().min(1),
|
|
41
|
+
description: z.string().optional(),
|
|
42
|
+
type: z.enum(['signal', 'timer', 'webhook']),
|
|
43
|
+
trigger: z.record(z.string(), z.unknown()),
|
|
44
|
+
timeout: z.object({
|
|
45
|
+
duration: z.string().min(1),
|
|
46
|
+
action: z.enum(['escalate', 'skip', 'fail']),
|
|
47
|
+
}).optional(),
|
|
48
|
+
appliesTo: z.array(z.string().min(1)).optional(),
|
|
49
|
+
});
|
|
50
|
+
export const ParallelismGroupDefinitionSchema = z.object({
|
|
51
|
+
name: z.string().min(1),
|
|
52
|
+
description: z.string().optional(),
|
|
53
|
+
phases: z.array(z.string().min(1)).min(1),
|
|
54
|
+
strategy: z.enum(['fan-out', 'fan-in', 'race']),
|
|
55
|
+
maxConcurrent: z.number().int().positive().optional(),
|
|
56
|
+
waitForAll: z.boolean().optional(),
|
|
57
|
+
});
|
|
58
|
+
export const WorkflowDefinitionSchema = z.object({
|
|
59
|
+
apiVersion: z.literal('v1.1'),
|
|
60
|
+
kind: z.literal('WorkflowDefinition'),
|
|
61
|
+
metadata: z.object({
|
|
62
|
+
name: z.string().min(1),
|
|
63
|
+
description: z.string().optional(),
|
|
64
|
+
}),
|
|
65
|
+
phases: z.array(PhaseDefinitionSchema),
|
|
66
|
+
transitions: z.array(TransitionDefinitionSchema),
|
|
67
|
+
escalation: EscalationConfigSchema.optional(),
|
|
68
|
+
gates: z.array(GateDefinitionSchema).optional(),
|
|
69
|
+
parallelism: z.array(ParallelismGroupDefinitionSchema).optional(),
|
|
70
|
+
});
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Validation Helpers
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
/**
|
|
75
|
+
* Validate a parsed YAML object as a WorkflowDefinition.
|
|
76
|
+
* Throws ZodError with detailed messages on failure.
|
|
77
|
+
*/
|
|
78
|
+
export function validateWorkflowDefinition(data, filePath) {
|
|
79
|
+
try {
|
|
80
|
+
return WorkflowDefinitionSchema.parse(data);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if (filePath && error instanceof z.ZodError) {
|
|
84
|
+
throw new Error(`Invalid workflow definition at ${filePath}: ${error.message}`, { cause: error });
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|