Оркестраторы
Orchestrator превращает граф во что-то исполняемое и запускает его. Объявите его в contributes.orchestrators, затем зарегистрируйте реализацию:
register(id: string, orchestrator: IOrchestrator): IDisposable
Передайте тот же короткий id, что и в манифесте. Fragmenta добавит к нему id вашего расширения, так что полный id оркестратора имеет вид: <publisher>.<name>.<id>. Если манифест не объявляет оркестратор с таким id, реализация не подключается: register всё равно вернёт disposable, а ошибка появится только в логе хоста расширения.
Вызов dispose() для регистрации отключает реализацию. То же происходит при деактивации расширения.
Контракт IOrchestrator
interface IOrchestrator {
start?(options?: OrchestratorStartOptions): Promise<OrchestratorStartResult>
build?(options?: OrchestratorBuildOptions): Promise<OrchestratorBuildResult>
run(options?: OrchestratorRunOptions): Promise<OrchestratorRunResult>
dsl(options?: OrchestratorDslOptions): Promise<OrchestratorDslResult>
}
Fragmenta проверяет, какие методы реализует ваш объект, один раз, при вызове register — методы, добавленные в объект позже, не учитываются. Fragmenta вызывает эти методы, когда пользователь работает с оркестратором, и отслеживает статус для каждого оркестратора:
type OrchestratorStatus = 'idle' | 'starting' | 'ready' | 'building' | 'built' | 'running' | 'error'
| Метод | Обязателен | Во время выполнения | При успехе |
| --- | --- | --- | --- |
| start | нет | starting | ready — результат может вернуть config.orchestratorUrl, Fragmenta смёржит его в конфиг оркестратора. |
| build | нет | building | built |
| run | да | — | running |
| dsl | да | — | статус не меняется — возвращает { dsl }. |
Каждый результат может также содержать status. Если он задан, он заменяет статус из колонки При успехе — и для dsl тоже. Возврат status: 'error' переводит оркестратор в состояние ошибки, а вызов считается неудачным; config, возвращённый из start, при этом всё равно объединяется.
Кнопки запуска
Кнопка запуска в правом верхнем углу редактора вызывает start, build и run — но не dsl. Метод dsl нужен инструментам, которым граф требуется в формате вашего оркестратора. Кнопка показывает одно действие за раз, выбранное из методов, реализованных вашим объектом:
| Статус | Предлагаемое действие |
| --- | --- |
| idle | Первый реализованный из start, build, run. |
| error | Действие, которое упало; если его нет — как для idle. |
| ready | Первый реализованный из build, run, start. |
| built, running | Первый реализованный из run, build, start. |
| starting, building | Нет — кнопка показывает загрузку. |
Пока вызов выполняется, кнопка превращается в кнопку остановки, которая его прерывает.
Опции и результаты
Все объекты опций содержат необязательный abortSignal; build дополнительно принимает force, а dsl может получить явные nodes / edges для построения вместо текущего состояния редактора.
| Поле | Где | Описание |
| --- | --- | --- |
| abortSignal?: AbortSignal | все опции | Срабатывает, когда пользователь останавливает вызов. Прекратите работу и отклоните промис. |
| force?: boolean | OrchestratorBuildOptions | Запрос полной пересборки. Как его трактовать, решает ваш оркестратор; кнопка запуска его не передаёт. |
| nodes?: Node[], edges?: Edge[] | OrchestratorDslOptions | Граф, из которого собирается DSL. Если не переданы, необходимо вручную читать состояние редактора. |
| status?: OrchestratorStatus | все результаты | Заменяет статус при успехе (см. выше). |
| config?: Partial<OrchestratorConfig> | OrchestratorStartResult | Поверхностно объединяется в конфиг оркестратора поверх url из манифеста. |
| dsl: unknown | OrchestratorDslResult | Граф в формате вашего оркестратора. |
OrchestratorStartOptions
interface OrchestratorStartOptions {
readonly abortSignal?: AbortSignal
}
OrchestratorBuildOptions
interface OrchestratorBuildOptions {
readonly abortSignal?: AbortSignal
readonly force?: boolean
}
OrchestratorRunOptions
interface OrchestratorRunOptions {
readonly abortSignal?: AbortSignal
}
OrchestratorDslOptions
interface OrchestratorDslOptions {
readonly abortSignal?: AbortSignal
readonly nodes?: Node[]
readonly edges?: Edge[]
}
OrchestratorStartResult
interface OrchestratorStartResult {
readonly status?: OrchestratorStatus
readonly config?: Partial<OrchestratorConfig>
}
interface OrchestratorConfig {
orchestratorUrl?: Url
}
OrchestratorBuildResult
interface OrchestratorBuildResult {
readonly status?: OrchestratorStatus
}
OrchestratorRunResult
interface OrchestratorRunResult {
readonly status?: OrchestratorStatus
}
OrchestratorDslResult
interface OrchestratorDslResult {
readonly status?: OrchestratorStatus
readonly dsl: unknown
}
Ошибки и отмена
Неудачный вызов — ошибка, отклонённый промис илиstatus: 'error' в результате — переводит оркестратор в состояние ошибки.
Пример
Локальный оркестратор обычно реализует start, поднимая свой бэкенд, и возвращает URL. В свобю очередь hosted-вариант декларирует url в манифесте и обходится без start — именно так воркфлоу-расширение поставляет Workflow Cloud и Workflow Local.
export function activate(context: ExtensionContext) {
context.subscriptions.push(
sdk.orchestrator.register('workflow-local', {
async start() {
const orchestratorUrl = await bootLocalBackend()
return { status: 'ready', config: { orchestratorUrl } }
},
async build() {
await deployNodes()
return {}
},
async run() {
await execute()
return {}
},
async dsl() {
const nodes = await sdk.project.nodeEditor.getNormalizedNodes()
const edges = await sdk.project.nodeEditor.getNormalizedEdges()
return { dsl: buildDsl(nodes, edges) }
},
}),
)
}
Во время выполнения сообщайте о прогрессе обратно в граф с помощью setNodeExecutionState передавайте логи в output-канал.
Выражения в DSL
Поле может содержать ссылку вместо литерала — {{Fetch Orders.summary.count}}. В проекте она хранится с именем ноды, потому что именно этим и оперируют пользователи. Среда выполнения должна получить id, потому что он переживает переименование. Превратить одно в другое — задача оркестратора при сборке DSL:
const nodes = await sdk.project.nodeEditor.getNormalizedNodes()
const byName = new Map(nodes.map((node) => [node.metadata.displayName, node.id]))
// "Fetch Orders.summary.count" → "{{c0ffee-…summary.count}}"
Где хранится текст: port.template для входного порта, parameter.template для параметра. В value и defaultValue хранится литерал, а не выражение — поле может содержать и то, и другое, и пользователь переключается между ними.
Обе формы DSL допустимы, и оркестратор обычно хочет явно указать, какую из них он выдаёт:
| Связывание | Что в поле | Кто разрешает |
| --- | --- | --- |
| позднее | {{nodeId.path}} | рантайм, на каждом прогоне |
| раннее | значение прошлого прогона | никто, оно вморожено |
Выражение также является зависимостью: нода, на которую ссылается поле, должна выполниться раньше, даже если между ними нет связи. Собирайте dependsOn из связей и из выражений, иначе порядок будет неверным.
Сообщайте обратно, что произошло: setNodeOutputs — что выдала нода, setNodeInputsResolved — результат выражения, тогда редактор покажет значение под полем.
extensions/example-workflow делает всё это в одном месте: грамматику и подсказки, обе привязки DSL и запуск, который возвращает значения в поля.