@ai-setting/roy-agent-core 1.3.10 → 1.3.11
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/dist/config/index.js +1647 -0
- package/dist/index.js +12579 -89691
- package/package.json +19 -56
- package/src/config/config-component.test.ts +0 -627
- package/src/config/config-component.ts +0 -906
- package/src/config/config-parser.test.ts +0 -319
- package/src/config/config-parser.ts +0 -203
- package/src/config/decentralized-config.test.ts +0 -740
- package/src/config/env-key.ts +0 -210
- package/src/config/env-source.test.ts +0 -252
- package/src/config/env-source.ts +0 -301
- package/src/config/file-source.test.ts +0 -357
- package/src/config/file-source.ts +0 -421
- package/src/config/index.ts +0 -24
- package/src/config/protocol-resolver.test.ts +0 -217
- package/src/config/protocol-resolver.ts +0 -228
- package/src/env/agent/agent-component.abort.test.ts +0 -511
- package/src/env/agent/agent-component.record-session.test.ts +0 -349
- package/src/env/agent/agent-component.test.ts +0 -1389
- package/src/env/agent/agent-component.tool-error.test.ts +0 -327
- package/src/env/agent/agent-component.ts +0 -1711
- package/src/env/agent/agent-config-registration.test.ts +0 -226
- package/src/env/agent/agent-config-registration.ts +0 -46
- package/src/env/agent/agent-reminder-plugin.integration.test.ts +0 -243
- package/src/env/agent/index.ts +0 -10
- package/src/env/agent/summary-agent.parse-hint.test.ts +0 -360
- package/src/env/agent/summary-agent.ts +0 -508
- package/src/env/agent/types.ts +0 -536
- package/src/env/commands/commands-component.test.ts +0 -364
- package/src/env/commands/commands-component.ts +0 -604
- package/src/env/commands/commands-config-registration.test.ts +0 -198
- package/src/env/commands/commands-config-registration.ts +0 -38
- package/src/env/commands/index.ts +0 -21
- package/src/env/commands/parser.test.ts +0 -203
- package/src/env/commands/parser.ts +0 -115
- package/src/env/commands/types.ts +0 -184
- package/src/env/commands-prompt-integration.test.ts +0 -243
- package/src/env/component-env.test.ts +0 -119
- package/src/env/component.ts +0 -335
- package/src/env/constants.test.ts +0 -72
- package/src/env/constants.ts +0 -123
- package/src/env/debug/debug-component.test.ts +0 -114
- package/src/env/debug/debug-component.ts +0 -547
- package/src/env/debug/formatters/index.ts +0 -9
- package/src/env/debug/formatters/repl-formatter.test.ts +0 -139
- package/src/env/debug/formatters/repl-formatter.ts +0 -358
- package/src/env/debug/formatters/trace-formatter.test.ts +0 -119
- package/src/env/debug/formatters/trace-formatter.ts +0 -191
- package/src/env/debug/formatters/tree-formatter.test.ts +0 -107
- package/src/env/debug/formatters/tree-formatter.ts +0 -325
- package/src/env/debug/index.ts +0 -38
- package/src/env/debug/parser/regex-parser.test.ts +0 -201
- package/src/env/debug/parser/regex-parser.ts +0 -196
- package/src/env/debug/parser/span-builder.test.ts +0 -241
- package/src/env/debug/parser/span-builder.ts +0 -386
- package/src/env/debug/reader/log-reader.test.ts +0 -170
- package/src/env/debug/reader/log-reader.ts +0 -186
- package/src/env/debug/reader/span-db-reader.test.ts +0 -118
- package/src/env/debug/reader/span-db-reader.ts +0 -201
- package/src/env/debug/types.test.ts +0 -187
- package/src/env/debug/types.ts +0 -171
- package/src/env/environment-init.test.ts +0 -183
- package/src/env/environment-lifecycle.test.ts +0 -516
- package/src/env/environment-service.test.ts +0 -332
- package/src/env/environment.handle-query.test.ts +0 -96
- package/src/env/environment.test.ts +0 -232
- package/src/env/environment.ts +0 -708
- package/src/env/errors.test.ts +0 -165
- package/src/env/errors.ts +0 -157
- package/src/env/event-source/event-source-agent-handler.test.ts +0 -193
- package/src/env/event-source/event-source-agent-handler.ts +0 -111
- package/src/env/event-source/event-source-component.process-cleanup.test.ts +0 -236
- package/src/env/event-source/event-source-component.stop.test.ts +0 -346
- package/src/env/event-source/event-source-component.test.ts +0 -1207
- package/src/env/event-source/event-source-component.ts +0 -1379
- package/src/env/event-source/event-source-config-registration.test.ts +0 -242
- package/src/env/event-source/event-source-config-registration.ts +0 -37
- package/src/env/event-source/event-source-integration.test.ts +0 -320
- package/src/env/event-source/event-source-platform.test.ts +0 -630
- package/src/env/event-source/types.ts +0 -298
- package/src/env/hook/global-hook-manager.ts +0 -162
- package/src/env/hook/hook-manager.test.ts +0 -374
- package/src/env/hook/hook-manager.ts +0 -309
- package/src/env/hook/index.ts +0 -38
- package/src/env/hook/types.ts +0 -138
- package/src/env/index.ts +0 -144
- package/src/env/interface.ts +0 -203
- package/src/env/llm/hooks.test.ts +0 -293
- package/src/env/llm/hooks.ts +0 -316
- package/src/env/llm/index.ts +0 -61
- package/src/env/llm/invoke-threshold-check.test.ts +0 -88
- package/src/env/llm/invoke-timeout.test.ts +0 -54
- package/src/env/llm/invoke.test.ts +0 -71
- package/src/env/llm/invoke.ts +0 -1039
- package/src/env/llm/llm-config.test.ts +0 -523
- package/src/env/llm/llm.test.ts +0 -233
- package/src/env/llm/llm.ts +0 -568
- package/src/env/llm/provider.test.ts +0 -182
- package/src/env/llm/provider.ts +0 -108
- package/src/env/llm/transform.test.ts +0 -251
- package/src/env/llm/transform.ts +0 -286
- package/src/env/llm/types.test.ts +0 -580
- package/src/env/llm/types.ts +0 -424
- package/src/env/log-trace/decorator-otel.test.ts +0 -182
- package/src/env/log-trace/decorator.ts +0 -230
- package/src/env/log-trace/index.ts +0 -79
- package/src/env/log-trace/log-trace-component.test.ts +0 -242
- package/src/env/log-trace/log-trace-component.ts +0 -497
- package/src/env/log-trace/log-trace-config-registration.test.ts +0 -348
- package/src/env/log-trace/log-trace-config-registration.ts +0 -45
- package/src/env/log-trace/logger.test.ts +0 -149
- package/src/env/log-trace/logger.ts +0 -522
- package/src/env/log-trace/opentelemetry/cli-propagation.test.ts +0 -147
- package/src/env/log-trace/opentelemetry/cli-propagation.ts +0 -194
- package/src/env/log-trace/opentelemetry/integration.test.ts +0 -668
- package/src/env/log-trace/opentelemetry/mod.ts +0 -25
- package/src/env/log-trace/opentelemetry/propagation-env.test.ts +0 -181
- package/src/env/log-trace/opentelemetry/propagation-env.ts +0 -136
- package/src/env/log-trace/opentelemetry/propagation.test.ts +0 -259
- package/src/env/log-trace/opentelemetry/propagation.ts +0 -215
- package/src/env/log-trace/opentelemetry/tracer-provider-context.test.ts +0 -166
- package/src/env/log-trace/opentelemetry/tracer-provider.test.ts +0 -379
- package/src/env/log-trace/opentelemetry/tracer-provider.ts +0 -612
- package/src/env/log-trace/span-storage.test.ts +0 -145
- package/src/env/log-trace/span-storage.ts +0 -230
- package/src/env/log-trace/trace-context.test.ts +0 -187
- package/src/env/log-trace/trace-context.ts +0 -162
- package/src/env/log-trace/types.test.ts +0 -63
- package/src/env/log-trace/types.ts +0 -172
- package/src/env/mcp/README.md +0 -244
- package/src/env/mcp/__integration__/mcp-component.integration.test.ts +0 -373
- package/src/env/mcp/config.test.ts +0 -74
- package/src/env/mcp/config.ts +0 -116
- package/src/env/mcp/index.ts +0 -41
- package/src/env/mcp/loader.test.ts +0 -161
- package/src/env/mcp/loader.ts +0 -209
- package/src/env/mcp/mcp-component.test.ts +0 -111
- package/src/env/mcp/mcp-component.ts +0 -358
- package/src/env/mcp/mcp-config-registration.test.ts +0 -304
- package/src/env/mcp/mcp-config-registration.ts +0 -50
- package/src/env/mcp/scanner.test.ts +0 -170
- package/src/env/mcp/scanner.ts +0 -246
- package/src/env/mcp/tool/adapter.test.ts +0 -520
- package/src/env/mcp/tool/adapter.ts +0 -521
- package/src/env/mcp/tool/index.ts +0 -5
- package/src/env/mcp/types.test.ts +0 -171
- package/src/env/mcp/types.ts +0 -79
- package/src/env/memory/README.md +0 -177
- package/src/env/memory/built-in/index.ts +0 -59
- package/src/env/memory/built-in/recall-memory.ts +0 -103
- package/src/env/memory/built-in/record-memory.ts +0 -148
- package/src/env/memory/index.ts +0 -20
- package/src/env/memory/memory-component.test.ts +0 -239
- package/src/env/memory/memory-component.ts +0 -503
- package/src/env/memory/memory-config-registration.test.ts +0 -67
- package/src/env/memory/memory-config-registration.ts +0 -48
- package/src/env/memory/memory-config.ts +0 -45
- package/src/env/memory/memory-file.test.ts +0 -268
- package/src/env/memory/plugin/index.ts +0 -48
- package/src/env/memory/plugin/memory-agent.test.ts +0 -249
- package/src/env/memory/plugin/memory-agent.ts +0 -365
- package/src/env/memory/plugin/memory-manager.ts +0 -198
- package/src/env/memory/plugin/memory-plugin-agent.test.ts +0 -145
- package/src/env/memory/plugin/memory-plugin.ts +0 -210
- package/src/env/memory/plugin/plugin-simplified.test.ts +0 -51
- package/src/env/memory/plugin/recall-memory.test.ts +0 -106
- package/src/env/memory/plugin/recall-memory.ts +0 -53
- package/src/env/memory/plugin/types.ts +0 -101
- package/src/env/memory/tools/memory-agent-tools.ts +0 -228
- package/src/env/memory/types.ts +0 -85
- package/src/env/paths.ts +0 -118
- package/src/env/prompt/index.ts +0 -18
- package/src/env/prompt/memory-prompts.test.ts +0 -91
- package/src/env/prompt/prompt-component.test.ts +0 -491
- package/src/env/prompt/prompt-component.ts +0 -619
- package/src/env/prompt/prompt-config-registration.test.ts +0 -213
- package/src/env/prompt/prompt-config-registration.ts +0 -39
- package/src/env/prompt/prompts-index.ts +0 -504
- package/src/env/prompt/renderer.ts +0 -67
- package/src/env/prompt/types.ts +0 -136
- package/src/env/session/hooks.ts +0 -18
- package/src/env/session/index.ts +0 -37
- package/src/env/session/search-query-parser.test.ts +0 -425
- package/src/env/session/search-query-parser.ts +0 -171
- package/src/env/session/session-checkpoint.test.ts +0 -523
- package/src/env/session/session-component.extract-recent-messages.test.ts +0 -209
- package/src/env/session/session-component.test.ts +0 -132
- package/src/env/session/session-component.ts +0 -1249
- package/src/env/session/session-config-registration.test.ts +0 -138
- package/src/env/session/session-config-registration.ts +0 -52
- package/src/env/session/session-message-converter.test.ts +0 -763
- package/src/env/session/session-message-converter.ts +0 -415
- package/src/env/session/session-message-e2e.test.ts +0 -448
- package/src/env/session/session-search.test.ts +0 -391
- package/src/env/session/session-store.test.ts +0 -362
- package/src/env/session/session-store.ts +0 -141
- package/src/env/session/storage/index.ts +0 -6
- package/src/env/session/storage/memory.ts +0 -502
- package/src/env/session/storage/sqlite.ts +0 -794
- package/src/env/session/types.ts +0 -742
- package/src/env/skill/config.ts +0 -39
- package/src/env/skill/index.ts +0 -6
- package/src/env/skill/parser.test.ts +0 -116
- package/src/env/skill/parser.ts +0 -77
- package/src/env/skill/scanner.test.ts +0 -211
- package/src/env/skill/scanner.ts +0 -119
- package/src/env/skill/skill-component.test.ts +0 -234
- package/src/env/skill/skill-component.ts +0 -352
- package/src/env/skill/skill-config-registration.test.ts +0 -60
- package/src/env/skill/skill-config-registration.ts +0 -43
- package/src/env/skill/tool/index.ts +0 -1
- package/src/env/skill/tool/skill-tool.test.ts +0 -100
- package/src/env/skill/tool/skill-tool.ts +0 -72
- package/src/env/skill/types.ts +0 -64
- package/src/env/task/delegate/delegate-tool.test.ts +0 -498
- package/src/env/task/delegate/delegate-tool.ts +0 -1014
- package/src/env/task/delegate/index.ts +0 -18
- package/src/env/task/delegate/stop-tool.test.ts +0 -140
- package/src/env/task/delegate/stop-tool.ts +0 -119
- package/src/env/task/delegate/task-events.test.ts +0 -178
- package/src/env/task/delegate/task-events.ts +0 -143
- package/src/env/task/hooks/contexts.test.ts +0 -92
- package/src/env/task/hooks/contexts.ts +0 -192
- package/src/env/task/hooks/index.ts +0 -23
- package/src/env/task/hooks/task-hook-points.test.ts +0 -32
- package/src/env/task/hooks/task-hook-points.ts +0 -54
- package/src/env/task/index.ts +0 -7
- package/src/env/task/plugins/index.ts +0 -13
- package/src/env/task/plugins/task-plugin.test.ts +0 -74
- package/src/env/task/plugins/task-plugin.ts +0 -89
- package/src/env/task/plugins/task-tag-plugin.test.ts +0 -377
- package/src/env/task/plugins/task-tag-plugin.ts +0 -319
- package/src/env/task/plugins/task-workflow-extractor.integration.test.ts +0 -226
- package/src/env/task/plugins/workflow-extractor-agent.test.ts +0 -107
- package/src/env/task/plugins/workflow-extractor-agent.ts +0 -225
- package/src/env/task/storage/index.ts +0 -6
- package/src/env/task/storage/sqlite-task-store.test.ts +0 -283
- package/src/env/task/storage/sqlite-task-store.ts +0 -903
- package/src/env/task/storage/task-search.test.ts +0 -291
- package/src/env/task/tag-service.test.ts +0 -198
- package/src/env/task/tag-service.ts +0 -264
- package/src/env/task/task-component.test.ts +0 -193
- package/src/env/task/task-component.ts +0 -658
- package/src/env/task/task-config-registration.test.ts +0 -57
- package/src/env/task/task-config-registration.ts +0 -37
- package/src/env/task/task-types.test.ts +0 -137
- package/src/env/task/tools/complete-tool.ts +0 -44
- package/src/env/task/tools/create-tool.ts +0 -49
- package/src/env/task/tools/delete-tool.ts +0 -43
- package/src/env/task/tools/get-tool.ts +0 -59
- package/src/env/task/tools/index.ts +0 -10
- package/src/env/task/tools/list-tool.ts +0 -40
- package/src/env/task/tools/operation/create-tool.ts +0 -48
- package/src/env/task/tools/operation/delete-tool.ts +0 -43
- package/src/env/task/tools/operation/get-tool.ts +0 -43
- package/src/env/task/tools/operation/index.ts +0 -9
- package/src/env/task/tools/operation/list-tool.ts +0 -40
- package/src/env/task/tools/operation/operation-tools.test.ts +0 -274
- package/src/env/task/tools/operation/operation-types.ts +0 -75
- package/src/env/task/tools/operation/update-tool.ts +0 -47
- package/src/env/task/tools/task-tools.test.ts +0 -203
- package/src/env/task/tools/task-types.test.ts +0 -75
- package/src/env/task/tools/task-types.ts +0 -68
- package/src/env/task/tools/update-tool.ts +0 -70
- package/src/env/task/types.ts +0 -160
- package/src/env/tool/built-in/bash.ts +0 -201
- package/src/env/tool/built-in/echo.ts +0 -29
- package/src/env/tool/built-in/edit-file.test.ts +0 -136
- package/src/env/tool/built-in/edit-file.ts +0 -92
- package/src/env/tool/built-in/glob.test.ts +0 -94
- package/src/env/tool/built-in/glob.ts +0 -65
- package/src/env/tool/built-in/grep.test.ts +0 -122
- package/src/env/tool/built-in/grep.ts +0 -108
- package/src/env/tool/built-in/index.ts +0 -44
- package/src/env/tool/built-in/read-file.test.ts +0 -84
- package/src/env/tool/built-in/read-file.ts +0 -75
- package/src/env/tool/built-in/write-file.test.ts +0 -119
- package/src/env/tool/built-in/write-file.ts +0 -68
- package/src/env/tool/index.ts +0 -24
- package/src/env/tool/registry.test.ts +0 -257
- package/src/env/tool/registry.ts +0 -167
- package/src/env/tool/tool-component.test.ts +0 -559
- package/src/env/tool/tool-component.ts +0 -563
- package/src/env/tool/tool-config-registration.test.ts +0 -249
- package/src/env/tool/tool-config-registration.ts +0 -46
- package/src/env/tool/types.ts +0 -267
- package/src/env/tool/validator.test.ts +0 -143
- package/src/env/tool/validator.ts +0 -44
- package/src/env/types.ts +0 -180
- package/src/env/workflow/ask-user-tool-registration.test.ts +0 -216
- package/src/env/workflow/complex-workflow.integration.test.ts +0 -1900
- package/src/env/workflow/decorators/decorator-node.ts +0 -229
- package/src/env/workflow/decorators/decorator.test.ts +0 -196
- package/src/env/workflow/decorators/edge.ts +0 -82
- package/src/env/workflow/decorators/index.ts +0 -31
- package/src/env/workflow/decorators/node-as.ts +0 -98
- package/src/env/workflow/decorators/workflow.ts +0 -54
- package/src/env/workflow/engine/dag-manager.test.ts +0 -570
- package/src/env/workflow/engine/dag-manager.ts +0 -594
- package/src/env/workflow/engine/engine.ts +0 -1422
- package/src/env/workflow/engine/event-bus.test.ts +0 -359
- package/src/env/workflow/engine/event-bus.ts +0 -156
- package/src/env/workflow/engine/executor-agent-session.test.ts +0 -84
- package/src/env/workflow/engine/executor.test.ts +0 -619
- package/src/env/workflow/engine/executor.ts +0 -593
- package/src/env/workflow/engine/index.ts +0 -24
- package/src/env/workflow/engine/node-registry.test.ts +0 -560
- package/src/env/workflow/engine/node-registry.ts +0 -289
- package/src/env/workflow/engine/resume-removed.test.ts +0 -22
- package/src/env/workflow/engine/scheduler.test.ts +0 -715
- package/src/env/workflow/engine/scheduler.ts +0 -318
- package/src/env/workflow/engine/workflow-engine.test.ts +0 -815
- package/src/env/workflow/extractor/workflow-converter.ts +0 -306
- package/src/env/workflow/fixtures.ts +0 -380
- package/src/env/workflow/index.ts +0 -38
- package/src/env/workflow/integration/run-resume-unified.test.ts +0 -186
- package/src/env/workflow/integration/service-integration.test.ts +0 -267
- package/src/env/workflow/metadata/keys.ts +0 -12
- package/src/env/workflow/nodes/agent-component-adapter.test.ts +0 -318
- package/src/env/workflow/nodes/agent-component-adapter.ts +0 -448
- package/src/env/workflow/nodes/agent-node.test.ts +0 -371
- package/src/env/workflow/nodes/agent-node.ts +0 -598
- package/src/env/workflow/nodes/ask-user-node.ts +0 -113
- package/src/env/workflow/nodes/condition-node.ts +0 -200
- package/src/env/workflow/nodes/index.ts +0 -9
- package/src/env/workflow/nodes/merge-node.ts +0 -141
- package/src/env/workflow/nodes/skill-node.test.ts +0 -253
- package/src/env/workflow/nodes/skill-node.ts +0 -393
- package/src/env/workflow/nodes/tool-node.test.ts +0 -251
- package/src/env/workflow/nodes/tool-node.ts +0 -493
- package/src/env/workflow/nodes/workflow-llm-history.test.ts +0 -455
- package/src/env/workflow/nodes/workflow-node.test.ts +0 -315
- package/src/env/workflow/nodes/workflow-node.ts +0 -311
- package/src/env/workflow/service/index.ts +0 -27
- package/src/env/workflow/service/registry.test.ts +0 -133
- package/src/env/workflow/service/registry.ts +0 -71
- package/src/env/workflow/service/workflow-service.test.ts +0 -310
- package/src/env/workflow/service/workflow-service.ts +0 -393
- package/src/env/workflow/storage/index.ts +0 -28
- package/src/env/workflow/storage/mock-repositories.ts +0 -385
- package/src/env/workflow/storage/sqlite.test.ts +0 -179
- package/src/env/workflow/storage/sqlite.ts +0 -163
- package/src/env/workflow/storage/workflow-repo.test.ts +0 -780
- package/src/env/workflow/storage/workflow-repo.ts +0 -342
- package/src/env/workflow/tools/ask-user-tool.ts +0 -82
- package/src/env/workflow/tools/index.ts +0 -26
- package/src/env/workflow/tools/run-workflow.test.ts +0 -352
- package/src/env/workflow/tools/run-workflow.ts +0 -214
- package/src/env/workflow/types/context.ts +0 -18
- package/src/env/workflow/types/decorators-types.ts +0 -198
- package/src/env/workflow/types/event.test.ts +0 -515
- package/src/env/workflow/types/event.ts +0 -193
- package/src/env/workflow/types/index.ts +0 -49
- package/src/env/workflow/types/run.test.ts +0 -437
- package/src/env/workflow/types/run.ts +0 -173
- package/src/env/workflow/types/workflow-hil.ts +0 -114
- package/src/env/workflow/types/workflow-message.test.ts +0 -138
- package/src/env/workflow/types/workflow-message.ts +0 -196
- package/src/env/workflow/types/workflow-session.test.ts +0 -95
- package/src/env/workflow/types/workflow-session.ts +0 -59
- package/src/env/workflow/types/workflow.test.ts +0 -495
- package/src/env/workflow/types/workflow.ts +0 -195
- package/src/env/workflow/types_compat.ts +0 -51
- package/src/env/workflow/utils/create-workflow.ts +0 -47
- package/src/env/workflow/utils/execution-state.ts +0 -245
- package/src/env/workflow/utils/index.ts +0 -18
- package/src/env/workflow/utils/node-registry-helper.ts +0 -58
- package/src/env/workflow/utils/recovery-validator.test.ts +0 -460
- package/src/env/workflow/utils/recovery-validator.ts +0 -377
- package/src/env/workflow/utils/session-parser.test.ts +0 -111
- package/src/env/workflow/utils/session-parser.ts +0 -94
- package/src/env/workflow/utils/session-recovery.test.ts +0 -334
- package/src/env/workflow/utils/session-recovery.ts +0 -188
- package/src/env/workflow/utils/template-resolver.test.ts +0 -258
- package/src/env/workflow/utils/template-resolver.ts +0 -436
- package/src/env/workflow/utils/validation-rules.ts +0 -149
- package/src/env/workflow/workflow-component.ts +0 -544
- package/src/index.ts +0 -422
- package/src/utils/id.ts +0 -21
|
@@ -1,1422 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview WorkflowEngine - Session-based workflow execution engine
|
|
3
|
-
*
|
|
4
|
-
* 基于 Session 实现 run/resume 机制:
|
|
5
|
-
* - Session 直接作为 workflow run 的实例
|
|
6
|
-
* - 使用 workflow.node.* 消息记录节点执行状态
|
|
7
|
-
* - resume 时从 Session 消息恢复状态
|
|
8
|
-
*
|
|
9
|
-
* @version v2.0
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { EventEmitter } from 'events';
|
|
13
|
-
import type {
|
|
14
|
-
WorkflowDefinition,
|
|
15
|
-
Workflow,
|
|
16
|
-
RunOptions,
|
|
17
|
-
RunResult,
|
|
18
|
-
RunStatus,
|
|
19
|
-
NodeExecutionContext,
|
|
20
|
-
} from '../types/index';
|
|
21
|
-
import { createWorkflowEvent, WorkflowEvent } from '../types/event';
|
|
22
|
-
import { createNodeInterruptEvent, AskUserError } from '../types/workflow-hil';
|
|
23
|
-
import { DAGManager } from './dag-manager';
|
|
24
|
-
import { EventBus } from './event-bus';
|
|
25
|
-
import { Scheduler } from './scheduler';
|
|
26
|
-
import { Executor, ExecutorOptions } from './executor';
|
|
27
|
-
import { NodeRegistry } from './node-registry';
|
|
28
|
-
import type { SessionComponent } from '../../session/session-component';
|
|
29
|
-
import type { WorkflowSessionMetadata, AgentSessionRef } from '../types/workflow-message';
|
|
30
|
-
import type { Environment } from '../../interface';
|
|
31
|
-
import { createLogger } from '../../log-trace/logger';
|
|
32
|
-
import { TracedAs } from '../../log-trace/decorator';
|
|
33
|
-
|
|
34
|
-
// Logger
|
|
35
|
-
const logger = createLogger("workflow:engine");
|
|
36
|
-
|
|
37
|
-
// ============================================================================
|
|
38
|
-
// Type Definitions
|
|
39
|
-
// ============================================================================
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Agent Session Recovery Info
|
|
43
|
-
*/
|
|
44
|
-
export interface AgentSessionRecoveryInfo {
|
|
45
|
-
nodeId: string;
|
|
46
|
-
agentSessionId: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Options for creating WorkflowEngine via static factory
|
|
51
|
-
*/
|
|
52
|
-
export interface WorkflowEngineCreateOptions {
|
|
53
|
-
env?: {
|
|
54
|
-
getComponent<T>(name: string): T | undefined;
|
|
55
|
-
};
|
|
56
|
-
toolRegistry?: any;
|
|
57
|
-
skillRegistry?: any;
|
|
58
|
-
workflowRunner?: any;
|
|
59
|
-
sessionComponent?: SessionComponent;
|
|
60
|
-
/** Repository for loading workflow definitions during resume */
|
|
61
|
-
workflowRepository?: WorkflowRepository;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Workflow Repository interface (abstraction)
|
|
66
|
-
*/
|
|
67
|
-
export interface WorkflowRepository {
|
|
68
|
-
getById(id: string): Workflow | null;
|
|
69
|
-
getByName(name: string): Workflow | null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Active Session State
|
|
74
|
-
* 内存中追踪正在运行的 workflow session
|
|
75
|
-
*/
|
|
76
|
-
interface ActiveSessionState {
|
|
77
|
-
sessionId: string;
|
|
78
|
-
workflowId: string;
|
|
79
|
-
workflowName: string;
|
|
80
|
-
status: RunStatus;
|
|
81
|
-
startedAt: Date;
|
|
82
|
-
eventBus: EventBus;
|
|
83
|
-
dagManager: DAGManager;
|
|
84
|
-
scheduler: Scheduler;
|
|
85
|
-
executor: Executor;
|
|
86
|
-
nodeRegistry: NodeRegistry;
|
|
87
|
-
nodeOutputs: Map<string, any>;
|
|
88
|
-
config: WorkflowEngineConfig;
|
|
89
|
-
abortController: AbortController;
|
|
90
|
-
completedPromise: Promise<RunResult>;
|
|
91
|
-
resolveCompleted: (result: RunResult) => void;
|
|
92
|
-
rejectCompleted: (error: Error) => void;
|
|
93
|
-
// LLM history for agent nodes
|
|
94
|
-
workflowHistory: Array<{
|
|
95
|
-
role: 'user' | 'assistant' | 'tool';
|
|
96
|
-
content: string;
|
|
97
|
-
toolCallId?: string;
|
|
98
|
-
toolName?: string;
|
|
99
|
-
nodeId?: string;
|
|
100
|
-
}>;
|
|
101
|
-
// Agent sessions reference
|
|
102
|
-
agentSessions: Map<string, string>; // nodeId -> agentSessionId
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* WorkflowEngine Configuration
|
|
107
|
-
*/
|
|
108
|
-
export interface WorkflowEngineConfig {
|
|
109
|
-
parallelLimit: number | null;
|
|
110
|
-
timeout: number | null;
|
|
111
|
-
debug: boolean;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Runtime State from Session Messages
|
|
116
|
-
* 从 Session 消息恢复的状态
|
|
117
|
-
*/
|
|
118
|
-
interface RuntimeState {
|
|
119
|
-
nodeOutputs: Map<string, unknown>;
|
|
120
|
-
pendingNodeId: string | null;
|
|
121
|
-
waitingForUser: boolean;
|
|
122
|
-
agentSessionId?: string;
|
|
123
|
-
lastInterruptMessage?: any;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ============================================================================
|
|
127
|
-
// WorkflowEngine Class
|
|
128
|
-
// ============================================================================
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* WorkflowEngine
|
|
132
|
-
*
|
|
133
|
-
* Session-based workflow execution engine.
|
|
134
|
-
* Uses Session to manage workflow run state and node execution history.
|
|
135
|
-
*/
|
|
136
|
-
export class WorkflowEngine extends EventEmitter {
|
|
137
|
-
/** 活跃的 session 状态,key 为 sessionId */
|
|
138
|
-
private activeSessions: Map<string, ActiveSessionState> = new Map();
|
|
139
|
-
|
|
140
|
-
/** Session ID 计数器 */
|
|
141
|
-
private sessionIdCounter = 0;
|
|
142
|
-
|
|
143
|
-
/** Session component */
|
|
144
|
-
private sessionComponent?: SessionComponent;
|
|
145
|
-
|
|
146
|
-
/** Node registry */
|
|
147
|
-
private nodeRegistry: NodeRegistry;
|
|
148
|
-
|
|
149
|
-
/** Workflow repository for loading definitions during resume */
|
|
150
|
-
private workflowRepository?: WorkflowRepository;
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Create a new WorkflowEngine
|
|
154
|
-
*/
|
|
155
|
-
constructor(
|
|
156
|
-
nodeRegistry: NodeRegistry,
|
|
157
|
-
sessionComponent?: SessionComponent,
|
|
158
|
-
workflowRepository?: WorkflowRepository
|
|
159
|
-
) {
|
|
160
|
-
super();
|
|
161
|
-
this.nodeRegistry = nodeRegistry;
|
|
162
|
-
this.sessionComponent = sessionComponent;
|
|
163
|
-
this.workflowRepository = workflowRepository;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Create a WorkflowEngine with automatic AgentComponent integration
|
|
168
|
-
*/
|
|
169
|
-
static async create(options: WorkflowEngineCreateOptions): Promise<WorkflowEngine> {
|
|
170
|
-
const agentComponent = options.env?.getComponent<any>('agent');
|
|
171
|
-
const sessionComponent = options.sessionComponent;
|
|
172
|
-
|
|
173
|
-
const nodeRegistry = new NodeRegistry({
|
|
174
|
-
toolRegistry: options.toolRegistry,
|
|
175
|
-
skillRegistry: options.skillRegistry,
|
|
176
|
-
agentComponent,
|
|
177
|
-
workflowRunner: options.workflowRunner,
|
|
178
|
-
sessionComponent,
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
return new WorkflowEngine(nodeRegistry, sessionComponent, options.workflowRepository);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// =========================================================================
|
|
185
|
-
// Session ID Utilities
|
|
186
|
-
// =========================================================================
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Generate a unique run ID
|
|
190
|
-
*/
|
|
191
|
-
private generateRunId(): string {
|
|
192
|
-
const random = Math.random().toString(36).substring(2, 8);
|
|
193
|
-
return `run_${Date.now()}_${++this.sessionIdCounter}_${random}`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Extract runId from sessionId
|
|
198
|
-
* sessionId format: workflow_{runId}
|
|
199
|
-
*/
|
|
200
|
-
private getRunIdFromSessionId(sessionId: string): string {
|
|
201
|
-
return sessionId.replace(/^workflow_/, '');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Get session ID for a run
|
|
206
|
-
*/
|
|
207
|
-
getSessionId(runId: string): string {
|
|
208
|
-
return `workflow_${runId}`;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// =========================================================================
|
|
212
|
-
// Public API: Session Management
|
|
213
|
-
// =========================================================================
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Create a new workflow session
|
|
217
|
-
*
|
|
218
|
-
* @param workflow - Workflow definition or entity
|
|
219
|
-
* @param options - Optional configuration
|
|
220
|
-
* @returns Promise resolving to sessionId (format: workflow_{runId})
|
|
221
|
-
*/
|
|
222
|
-
@TracedAs("workflow.engine.createSession", { recordParams: true, recordResult: true, log: true })
|
|
223
|
-
async createSession(
|
|
224
|
-
workflow: Workflow | WorkflowDefinition,
|
|
225
|
-
options?: RunOptions
|
|
226
|
-
): Promise<string> {
|
|
227
|
-
const definition = 'definition' in workflow
|
|
228
|
-
? workflow.definition
|
|
229
|
-
: workflow;
|
|
230
|
-
|
|
231
|
-
const workflowId = 'id' in workflow
|
|
232
|
-
? workflow.id
|
|
233
|
-
: `inline_${definition.name}`;
|
|
234
|
-
|
|
235
|
-
const workflowName = definition.name;
|
|
236
|
-
|
|
237
|
-
// Generate run ID and session ID
|
|
238
|
-
const runId = this.generateRunId();
|
|
239
|
-
const sessionId = this.getSessionId(runId);
|
|
240
|
-
|
|
241
|
-
// Create Session
|
|
242
|
-
const metadata: WorkflowSessionMetadata = {
|
|
243
|
-
type: 'workflow',
|
|
244
|
-
workflowId,
|
|
245
|
-
workflowName,
|
|
246
|
-
workflowVersion: definition.version,
|
|
247
|
-
status: 'running',
|
|
248
|
-
input: options?.input,
|
|
249
|
-
agentSessions: [],
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
if (this.sessionComponent) {
|
|
253
|
-
await this.sessionComponent.create({
|
|
254
|
-
id: sessionId,
|
|
255
|
-
title: `Workflow: ${workflowName} (${runId})`,
|
|
256
|
-
metadata,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
logger.info(`[WorkflowEngine] Created session: ${sessionId}`);
|
|
261
|
-
|
|
262
|
-
return sessionId;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Run a workflow by sessionId (unified entry point)
|
|
267
|
-
*
|
|
268
|
-
* This method supports both:
|
|
269
|
-
* 1. First run: session doesn't exist → create session, start from entry node
|
|
270
|
-
* 2. Resume: session exists with messages → infer next node from messages
|
|
271
|
-
*
|
|
272
|
-
* @param sessionId - Session ID (format: workflow_{runId} or any string)
|
|
273
|
-
* @param options - Optional configuration
|
|
274
|
-
* @returns Promise resolving to RunResult
|
|
275
|
-
*/
|
|
276
|
-
@TracedAs("workflow.engine.run", { recordParams: true, recordResult: true, log: true })
|
|
277
|
-
async run(
|
|
278
|
-
sessionId: string,
|
|
279
|
-
options?: RunOptions & { workflowId?: string }
|
|
280
|
-
): Promise<RunResult> {
|
|
281
|
-
// 1. Try to get existing session
|
|
282
|
-
let session = this.sessionComponent
|
|
283
|
-
? await this.sessionComponent.get(sessionId)
|
|
284
|
-
: null;
|
|
285
|
-
|
|
286
|
-
let workflowDef: WorkflowDefinition | null = null;
|
|
287
|
-
let workflowId: string = '';
|
|
288
|
-
let workflowName: string = '';
|
|
289
|
-
|
|
290
|
-
if (session) {
|
|
291
|
-
// Session exists - load workflow definition
|
|
292
|
-
const metadata = session.metadata as WorkflowSessionMetadata;
|
|
293
|
-
if (metadata.type === 'workflow' && metadata.workflowId) {
|
|
294
|
-
workflowDef = this.findDefinitionForWorkflow(metadata.workflowId);
|
|
295
|
-
workflowId = metadata.workflowId;
|
|
296
|
-
workflowName = metadata.workflowName;
|
|
297
|
-
}
|
|
298
|
-
if (!workflowDef && metadata.workflowName) {
|
|
299
|
-
workflowDef = this.findDefinitionForWorkflow(metadata.workflowName);
|
|
300
|
-
workflowId = metadata.workflowName;
|
|
301
|
-
workflowName = metadata.workflowName;
|
|
302
|
-
}
|
|
303
|
-
} else {
|
|
304
|
-
// No session - this is a new run
|
|
305
|
-
if (!options?.workflowId) {
|
|
306
|
-
throw new Error('workflowId required for new session');
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
workflowDef = this.findDefinitionForWorkflow(options.workflowId);
|
|
310
|
-
if (!workflowDef) {
|
|
311
|
-
throw new Error(`Workflow not found: ${options.workflowId}`);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
workflowId = options.workflowId;
|
|
315
|
-
workflowName = workflowDef.name;
|
|
316
|
-
|
|
317
|
-
// Create session
|
|
318
|
-
if (this.sessionComponent) {
|
|
319
|
-
const sessionMetadata: WorkflowSessionMetadata = {
|
|
320
|
-
type: 'workflow',
|
|
321
|
-
workflowId,
|
|
322
|
-
workflowName,
|
|
323
|
-
workflowVersion: workflowDef.version,
|
|
324
|
-
status: 'running',
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
await this.sessionComponent.create({
|
|
328
|
-
id: sessionId,
|
|
329
|
-
title: `Workflow: ${workflowName}`,
|
|
330
|
-
metadata: sessionMetadata,
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
session = await this.sessionComponent.get(sessionId);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (!session || !workflowDef) {
|
|
338
|
-
throw new Error(`Cannot start workflow: session or workflow not found`);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// 2. Check session status
|
|
342
|
-
const metadata = session.metadata as WorkflowSessionMetadata;
|
|
343
|
-
if (metadata.status === 'completed') {
|
|
344
|
-
return { runId: this.getRunIdFromSessionId(sessionId), status: 'completed' };
|
|
345
|
-
}
|
|
346
|
-
if (metadata.status === 'failed') {
|
|
347
|
-
return { runId: this.getRunIdFromSessionId(sessionId), status: 'failed' };
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// 3. Load messages and infer next node
|
|
351
|
-
const messages = this.sessionComponent
|
|
352
|
-
? await this.sessionComponent.getMessages(sessionId)
|
|
353
|
-
: [];
|
|
354
|
-
|
|
355
|
-
// 4. Import and use session-recovery utilities
|
|
356
|
-
const { inferNextNode } = await import('../utils/session-recovery');
|
|
357
|
-
|
|
358
|
-
// Get entry node from workflow definition
|
|
359
|
-
const entry = workflowDef.entry;
|
|
360
|
-
const entryNode = Array.isArray(entry) ? entry[0] : entry;
|
|
361
|
-
|
|
362
|
-
const resumePoint = inferNextNode(messages, {
|
|
363
|
-
entryNode: entryNode !== '__default_entry__' ? entryNode : undefined,
|
|
364
|
-
edges: undefined, // edges not available in WorkflowDefinition, derived at runtime
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// 5. Initialize session state and execute
|
|
368
|
-
const sessionState = await this.initializeSessionState(
|
|
369
|
-
sessionId,
|
|
370
|
-
workflowDef,
|
|
371
|
-
workflowId,
|
|
372
|
-
workflowName,
|
|
373
|
-
options
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
this.activeSessions.set(sessionId, sessionState);
|
|
377
|
-
|
|
378
|
-
// 6. Run with resume point
|
|
379
|
-
return this.runWithResume(sessionId, resumePoint, options);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Initialize session state
|
|
384
|
-
*/
|
|
385
|
-
private async initializeSessionState(
|
|
386
|
-
sessionId: string,
|
|
387
|
-
definition: WorkflowDefinition,
|
|
388
|
-
workflowId: string,
|
|
389
|
-
workflowName: string,
|
|
390
|
-
options?: RunOptions
|
|
391
|
-
): Promise<ActiveSessionState> {
|
|
392
|
-
// Create DAG manager
|
|
393
|
-
const dagManager = new DAGManager(definition);
|
|
394
|
-
|
|
395
|
-
// Validate DAG
|
|
396
|
-
const validation = dagManager.validate();
|
|
397
|
-
if (!validation.valid) {
|
|
398
|
-
throw new Error(`Invalid workflow: ${validation.errors.join(', ')}`);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Get parallel limit
|
|
402
|
-
const parallelLimit = options?.parallelLimit
|
|
403
|
-
?? definition.config?.parallel_limit
|
|
404
|
-
?? null;
|
|
405
|
-
|
|
406
|
-
// Create scheduler
|
|
407
|
-
const scheduler = new Scheduler(dagManager, { parallelLimit });
|
|
408
|
-
|
|
409
|
-
// Create event bus
|
|
410
|
-
const eventBus = new EventBus();
|
|
411
|
-
|
|
412
|
-
// Create executor
|
|
413
|
-
const executorOptions: ExecutorOptions = {
|
|
414
|
-
globalTimeout: options?.timeout ?? definition.config?.timeout ?? null,
|
|
415
|
-
globalRetry: definition.config?.retry ?? null,
|
|
416
|
-
debug: options?.debug ?? definition.config?.debug ?? false,
|
|
417
|
-
};
|
|
418
|
-
const executor = new Executor(this.nodeRegistry, eventBus, executorOptions, this.sessionComponent);
|
|
419
|
-
|
|
420
|
-
// Create engine config
|
|
421
|
-
const config: WorkflowEngineConfig = {
|
|
422
|
-
parallelLimit,
|
|
423
|
-
timeout: options?.timeout ?? definition.config?.timeout ?? null,
|
|
424
|
-
debug: options?.debug ?? definition.config?.debug ?? false,
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
// Create abort controller
|
|
428
|
-
const abortController = new AbortController();
|
|
429
|
-
|
|
430
|
-
// Create completion promise
|
|
431
|
-
let resolveCompleted!: (result: RunResult) => void;
|
|
432
|
-
let rejectCompleted!: (error: Error) => void;
|
|
433
|
-
const completedPromise = new Promise<RunResult>((resolve, reject) => {
|
|
434
|
-
resolveCompleted = resolve;
|
|
435
|
-
rejectCompleted = reject;
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// Create session state
|
|
439
|
-
const sessionState: ActiveSessionState = {
|
|
440
|
-
sessionId,
|
|
441
|
-
workflowId,
|
|
442
|
-
workflowName,
|
|
443
|
-
status: 'running',
|
|
444
|
-
startedAt: new Date(),
|
|
445
|
-
eventBus,
|
|
446
|
-
dagManager,
|
|
447
|
-
scheduler,
|
|
448
|
-
executor,
|
|
449
|
-
nodeRegistry: this.nodeRegistry,
|
|
450
|
-
nodeOutputs: new Map(),
|
|
451
|
-
config,
|
|
452
|
-
abortController,
|
|
453
|
-
completedPromise,
|
|
454
|
-
resolveCompleted,
|
|
455
|
-
rejectCompleted,
|
|
456
|
-
workflowHistory: [],
|
|
457
|
-
agentSessions: new Map(),
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
// Setup event handlers
|
|
461
|
-
this.setupEventHandlers(sessionState);
|
|
462
|
-
|
|
463
|
-
return sessionState;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* Run with resume point
|
|
468
|
-
*/
|
|
469
|
-
private async runWithResume(
|
|
470
|
-
sessionId: string,
|
|
471
|
-
resumePoint: { type: string; nodeId?: string; nodeIds?: string[]; agentSessionId?: string },
|
|
472
|
-
options?: RunOptions & { input?: unknown }
|
|
473
|
-
): Promise<RunResult> {
|
|
474
|
-
const sessionState = this.activeSessions.get(sessionId);
|
|
475
|
-
if (!sessionState) {
|
|
476
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const runId = this.getRunIdFromSessionId(sessionId);
|
|
480
|
-
|
|
481
|
-
// Publish workflow started event
|
|
482
|
-
await sessionState.eventBus.publish(
|
|
483
|
-
createWorkflowEvent('workflow.started', runId, {
|
|
484
|
-
workflow_name: sessionState.workflowName,
|
|
485
|
-
input: options?.input,
|
|
486
|
-
})
|
|
487
|
-
);
|
|
488
|
-
|
|
489
|
-
// Determine what to execute based on resume point
|
|
490
|
-
let pendingNodeId: string | undefined;
|
|
491
|
-
let agentSessionId: string | undefined;
|
|
492
|
-
|
|
493
|
-
switch (resumePoint.type) {
|
|
494
|
-
case 'entry_node':
|
|
495
|
-
case 'next_nodes':
|
|
496
|
-
// Normal execution - scheduler will handle it
|
|
497
|
-
pendingNodeId = undefined;
|
|
498
|
-
break;
|
|
499
|
-
case 'resume_node':
|
|
500
|
-
case 'ask_user':
|
|
501
|
-
// Resume specific node
|
|
502
|
-
pendingNodeId = resumePoint.nodeId;
|
|
503
|
-
agentSessionId = resumePoint.agentSessionId;
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Start scheduling
|
|
508
|
-
// Note: userResponse is extracted from input if it's a string (for resume)
|
|
509
|
-
const userResponse = typeof options?.input === 'string' ? options.input as string : undefined;
|
|
510
|
-
this.scheduleAndExecute(sessionState, {
|
|
511
|
-
input: options?.input as Record<string, any>,
|
|
512
|
-
pendingNodeId,
|
|
513
|
-
agentSessionId,
|
|
514
|
-
userResponse,
|
|
515
|
-
}).catch((error) => {
|
|
516
|
-
logger.error(`Workflow ${sessionId} scheduling error:`, error);
|
|
517
|
-
this.failWorkflow(sessionState, error);
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
// If sync mode, wait for completion
|
|
521
|
-
if (options?.sync !== false) {
|
|
522
|
-
return this.waitForCompletion(sessionId, options?.timeout);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return { runId, status: 'running' };
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Convenience method to run a workflow directly
|
|
530
|
-
*/
|
|
531
|
-
@TracedAs("workflow.engine.runWorkflow", { recordParams: true, recordResult: true, log: true })
|
|
532
|
-
async runWorkflow(
|
|
533
|
-
workflow: Workflow | WorkflowDefinition,
|
|
534
|
-
options?: RunOptions
|
|
535
|
-
): Promise<RunResult> {
|
|
536
|
-
const definition = 'definition' in workflow
|
|
537
|
-
? workflow.definition
|
|
538
|
-
: workflow;
|
|
539
|
-
|
|
540
|
-
const workflowId = 'id' in workflow
|
|
541
|
-
? workflow.id
|
|
542
|
-
: `inline_${definition.name}`;
|
|
543
|
-
|
|
544
|
-
const workflowName = definition.name;
|
|
545
|
-
|
|
546
|
-
// Create session
|
|
547
|
-
const sessionId = await this.createSession(workflow, options);
|
|
548
|
-
|
|
549
|
-
// Initialize session state
|
|
550
|
-
const sessionState = await this.initializeSessionState(
|
|
551
|
-
sessionId,
|
|
552
|
-
definition,
|
|
553
|
-
workflowId,
|
|
554
|
-
workflowName,
|
|
555
|
-
options
|
|
556
|
-
);
|
|
557
|
-
|
|
558
|
-
this.activeSessions.set(sessionId, sessionState);
|
|
559
|
-
|
|
560
|
-
// Run with default entry node
|
|
561
|
-
return this.runWithResume(sessionId, { type: 'entry_node' }, options);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* Pause a running workflow
|
|
566
|
-
*/
|
|
567
|
-
@TracedAs("workflow.engine.pause", { recordParams: true, recordResult: true, log: true })
|
|
568
|
-
async pause(sessionId: string): Promise<void> {
|
|
569
|
-
const sessionState = this.activeSessions.get(sessionId);
|
|
570
|
-
|
|
571
|
-
if (!sessionState) {
|
|
572
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (sessionState.status !== 'running') {
|
|
576
|
-
throw new Error(`Session ${sessionId} is not running (current: ${sessionState.status})`);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
sessionState.status = 'paused';
|
|
580
|
-
sessionState.abortController.abort();
|
|
581
|
-
sessionState.executor.cancelAll();
|
|
582
|
-
|
|
583
|
-
// Update session metadata
|
|
584
|
-
if (this.sessionComponent) {
|
|
585
|
-
const metadata = await this.getSessionMetadata(sessionId);
|
|
586
|
-
await this.sessionComponent.update(sessionId, {
|
|
587
|
-
metadata: { ...metadata, status: 'paused' },
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Publish pause event
|
|
592
|
-
await sessionState.eventBus.publish(
|
|
593
|
-
createWorkflowEvent('workflow.paused', this.getRunIdFromSessionId(sessionId), {})
|
|
594
|
-
);
|
|
595
|
-
|
|
596
|
-
logger.info(`[WorkflowEngine] Workflow paused: ${sessionId}`);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Resume a paused workflow
|
|
601
|
-
*/
|
|
602
|
-
@TracedAs("workflow.engine.resume", { recordParams: true, recordResult: true, log: true })
|
|
603
|
-
async resume(
|
|
604
|
-
sessionId: string,
|
|
605
|
-
options?: { response?: string }
|
|
606
|
-
): Promise<RunResult> {
|
|
607
|
-
const sessionState = this.activeSessions.get(sessionId);
|
|
608
|
-
|
|
609
|
-
if (!sessionState) {
|
|
610
|
-
// Session not in memory, try to restore from database
|
|
611
|
-
logger.info(`[WorkflowEngine] Session not in memory, attempting to restore from DB: ${sessionId}`);
|
|
612
|
-
return this.resumeFromDatabase(sessionId, options);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Write resume message
|
|
616
|
-
if (options?.response) {
|
|
617
|
-
await this.writeNodeResume(sessionId, options.response);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (sessionState.status === 'running') {
|
|
621
|
-
// Already running
|
|
622
|
-
return { runId: this.getRunIdFromSessionId(sessionId), status: 'running' };
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
if (sessionState.status !== 'paused') {
|
|
626
|
-
throw new Error(`Session ${sessionId} is not paused (current: ${sessionState.status})`);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// Resume state
|
|
630
|
-
sessionState.status = 'running';
|
|
631
|
-
sessionState.abortController = new AbortController();
|
|
632
|
-
|
|
633
|
-
// Update session metadata
|
|
634
|
-
if (this.sessionComponent) {
|
|
635
|
-
const metadata = await this.getSessionMetadata(sessionId);
|
|
636
|
-
await this.sessionComponent.update(sessionId, {
|
|
637
|
-
metadata: { ...metadata, status: 'running' },
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Publish resume event
|
|
642
|
-
await sessionState.eventBus.publish(
|
|
643
|
-
createWorkflowEvent('workflow.resumed', this.getRunIdFromSessionId(sessionId), {
|
|
644
|
-
userResponse: options?.response,
|
|
645
|
-
})
|
|
646
|
-
);
|
|
647
|
-
|
|
648
|
-
// Restore runtime state from messages and continue execution
|
|
649
|
-
const runtimeState = await this.restoreRuntimeState(sessionId);
|
|
650
|
-
|
|
651
|
-
this.scheduleAndExecute(sessionState, {
|
|
652
|
-
pendingNodeId: runtimeState.pendingNodeId || undefined,
|
|
653
|
-
agentSessionId: runtimeState.agentSessionId,
|
|
654
|
-
userResponse: options?.response,
|
|
655
|
-
restoredOutputs: runtimeState.nodeOutputs,
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
return {
|
|
659
|
-
runId: this.getRunIdFromSessionId(sessionId),
|
|
660
|
-
status: 'running',
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* Resume from database when CLI restarts
|
|
666
|
-
*/
|
|
667
|
-
@TracedAs("workflow.engine.resumeFromDatabase", { recordParams: true, recordResult: true, log: true })
|
|
668
|
-
private async resumeFromDatabase(
|
|
669
|
-
sessionId: string,
|
|
670
|
-
options?: { response?: string }
|
|
671
|
-
): Promise<RunResult> {
|
|
672
|
-
// Get session
|
|
673
|
-
const session = await this.sessionComponent?.get(sessionId);
|
|
674
|
-
if (!session) {
|
|
675
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
const metadata = session.metadata as WorkflowSessionMetadata;
|
|
679
|
-
if (metadata.type !== 'workflow') {
|
|
680
|
-
throw new Error(`Session is not a workflow session: ${sessionId}`);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
if (metadata.status !== 'paused') {
|
|
684
|
-
throw new Error(`Workflow is not paused: ${sessionId}, status: ${metadata.status}`);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Restore runtime state from messages
|
|
688
|
-
const runtimeState = await this.restoreRuntimeState(sessionId);
|
|
689
|
-
|
|
690
|
-
if (!runtimeState.pendingNodeId) {
|
|
691
|
-
throw new Error(`No pending node found in session: ${sessionId}`);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Write resume message
|
|
695
|
-
if (options?.response) {
|
|
696
|
-
await this.writeNodeResume(sessionId, options.response);
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Update session metadata to running
|
|
700
|
-
if (this.sessionComponent) {
|
|
701
|
-
await this.sessionComponent.update(sessionId, {
|
|
702
|
-
metadata: { ...metadata, status: 'running' },
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
logger.info(`[WorkflowEngine] Resumed from database: ${sessionId}, pendingNode: ${runtimeState.pendingNodeId}`);
|
|
707
|
-
|
|
708
|
-
// Note: Cannot fully restore without workflow definition
|
|
709
|
-
// This is a current design limitation
|
|
710
|
-
throw new Error(
|
|
711
|
-
`Cannot resume from database without workflow definition. ` +
|
|
712
|
-
`Session ${sessionId} is paused at node "${runtimeState.pendingNodeId}". ` +
|
|
713
|
-
`Please ensure the workflow is registered before resuming.`
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Restore runtime state from Session messages
|
|
719
|
-
*/
|
|
720
|
-
@TracedAs("workflow.engine.restoreRuntimeState", { recordParams: true, recordResult: true, log: true })
|
|
721
|
-
private async restoreRuntimeState(sessionId: string): Promise<RuntimeState> {
|
|
722
|
-
const messages = this.sessionComponent
|
|
723
|
-
? await this.sessionComponent.getMessages(sessionId)
|
|
724
|
-
: [];
|
|
725
|
-
|
|
726
|
-
const nodeOutputs = new Map<string, unknown>();
|
|
727
|
-
let lastCallNodeId: string | null = null;
|
|
728
|
-
let lastInterruptMessage: any = null;
|
|
729
|
-
let agentSessionId: string | undefined;
|
|
730
|
-
|
|
731
|
-
for (const msg of messages) {
|
|
732
|
-
const msgMetadata = msg.metadata as any;
|
|
733
|
-
|
|
734
|
-
if (msgMetadata?.type === 'workflow.node.call') {
|
|
735
|
-
lastCallNodeId = msgMetadata.workflowNodeId;
|
|
736
|
-
} else if (msgMetadata?.type === 'workflow.node.result') {
|
|
737
|
-
if (lastCallNodeId) {
|
|
738
|
-
try {
|
|
739
|
-
nodeOutputs.set(lastCallNodeId, JSON.parse(msg.content));
|
|
740
|
-
} catch {
|
|
741
|
-
nodeOutputs.set(lastCallNodeId, msg.content);
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
lastCallNodeId = null;
|
|
745
|
-
} else if (msgMetadata?.type === 'workflow.node.interrupt') {
|
|
746
|
-
lastInterruptMessage = msg;
|
|
747
|
-
lastCallNodeId = msgMetadata.workflowNodeId;
|
|
748
|
-
agentSessionId = msgMetadata.agentSessionId;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
return {
|
|
753
|
-
nodeOutputs,
|
|
754
|
-
pendingNodeId: lastCallNodeId,
|
|
755
|
-
waitingForUser: !!lastInterruptMessage,
|
|
756
|
-
agentSessionId,
|
|
757
|
-
lastInterruptMessage,
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Write node resume message
|
|
763
|
-
*/
|
|
764
|
-
@TracedAs("workflow.engine.writeNodeResume", { recordParams: true, recordResult: true, log: true })
|
|
765
|
-
private async writeNodeResume(sessionId: string, response: string): Promise<void> {
|
|
766
|
-
if (this.sessionComponent) {
|
|
767
|
-
await this.sessionComponent.addMessage(sessionId, {
|
|
768
|
-
role: 'workflow.node.resume',
|
|
769
|
-
content: response,
|
|
770
|
-
metadata: {
|
|
771
|
-
type: 'workflow.node.resume',
|
|
772
|
-
response,
|
|
773
|
-
timestamp: Date.now(),
|
|
774
|
-
},
|
|
775
|
-
});
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Stop a running or paused workflow
|
|
781
|
-
*/
|
|
782
|
-
@TracedAs("workflow.engine.stop", { recordParams: true, recordResult: true, log: true })
|
|
783
|
-
async stop(sessionId: string, reason?: string): Promise<void> {
|
|
784
|
-
const sessionState = this.activeSessions.get(sessionId);
|
|
785
|
-
|
|
786
|
-
if (!sessionState) {
|
|
787
|
-
// Not in memory, try to update database
|
|
788
|
-
if (this.sessionComponent) {
|
|
789
|
-
await this.sessionComponent.update(sessionId, {
|
|
790
|
-
metadata: { status: 'stopped' } as any,
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
sessionState.status = 'stopped';
|
|
797
|
-
sessionState.abortController.abort();
|
|
798
|
-
sessionState.executor.cancelAll();
|
|
799
|
-
|
|
800
|
-
// Update session metadata
|
|
801
|
-
if (this.sessionComponent) {
|
|
802
|
-
const metadata = await this.getSessionMetadata(sessionId);
|
|
803
|
-
await this.sessionComponent.update(sessionId, {
|
|
804
|
-
metadata: { ...metadata, status: 'stopped' },
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
// Publish stop event
|
|
809
|
-
await sessionState.eventBus.publish(
|
|
810
|
-
createWorkflowEvent('workflow.stopped', this.getRunIdFromSessionId(sessionId), {
|
|
811
|
-
reason: reason ?? 'User requested stop',
|
|
812
|
-
})
|
|
813
|
-
);
|
|
814
|
-
|
|
815
|
-
// Cleanup
|
|
816
|
-
this.cleanupSession(sessionState);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
/**
|
|
820
|
-
* Get session status
|
|
821
|
-
*/
|
|
822
|
-
getSessionStatus(sessionId: string): RunStatus | null {
|
|
823
|
-
const sessionState = this.activeSessions.get(sessionId);
|
|
824
|
-
return sessionState?.status ?? null;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
/**
|
|
828
|
-
* Check if session is active
|
|
829
|
-
*/
|
|
830
|
-
isSessionActive(sessionId: string): boolean {
|
|
831
|
-
return this.activeSessions.has(sessionId);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// =========================================================================
|
|
835
|
-
// Private Methods
|
|
836
|
-
// =========================================================================
|
|
837
|
-
|
|
838
|
-
/**
|
|
839
|
-
* Setup event handlers for a run
|
|
840
|
-
*/
|
|
841
|
-
private setupEventHandlers(sessionState: ActiveSessionState): void {
|
|
842
|
-
const { sessionId, eventBus, scheduler } = sessionState;
|
|
843
|
-
const runId = this.getRunIdFromSessionId(sessionId);
|
|
844
|
-
|
|
845
|
-
// Handle node completed
|
|
846
|
-
eventBus.on('node.completed', async (event: WorkflowEvent) => {
|
|
847
|
-
if (event.type !== 'node.completed') return;
|
|
848
|
-
|
|
849
|
-
sessionState.nodeOutputs.set(event.node_id, event.output);
|
|
850
|
-
scheduler.markCompleted(event.node_id);
|
|
851
|
-
|
|
852
|
-
// Accumulate workflow history
|
|
853
|
-
if (event.output && typeof event.output === 'object' && 'workflowHistory' in event.output) {
|
|
854
|
-
const messages = (event.output as any).workflowHistory;
|
|
855
|
-
if (Array.isArray(messages)) {
|
|
856
|
-
sessionState.workflowHistory.push(...messages);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Check if workflow is complete
|
|
861
|
-
this.checkAndFinalize(sessionState);
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
// Handle node failed
|
|
865
|
-
eventBus.on('node.failed', async (event: WorkflowEvent) => {
|
|
866
|
-
if (event.type !== 'node.failed') return;
|
|
867
|
-
|
|
868
|
-
scheduler.markFailed(event.node_id);
|
|
869
|
-
await this.failWorkflow(sessionState, new Error(event.error.message));
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
// Handle node interrupt (ask_user pause)
|
|
873
|
-
eventBus.on('node.interrupt', async (event: WorkflowEvent) => {
|
|
874
|
-
if (event.type !== 'node.interrupt') return;
|
|
875
|
-
|
|
876
|
-
logger.info(`Workflow paused at node "${event.node_id}" - ask_user pending`);
|
|
877
|
-
|
|
878
|
-
sessionState.status = 'paused';
|
|
879
|
-
sessionState.abortController.abort();
|
|
880
|
-
|
|
881
|
-
// Update session metadata
|
|
882
|
-
if (this.sessionComponent) {
|
|
883
|
-
const metadata = await this.getSessionMetadata(sessionId);
|
|
884
|
-
await this.sessionComponent.update(sessionId, {
|
|
885
|
-
metadata: { ...metadata, status: 'paused' },
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Publish workflow.paused event
|
|
890
|
-
await eventBus.publish(
|
|
891
|
-
createWorkflowEvent('workflow.paused', runId, {
|
|
892
|
-
pendingNodeId: event.node_id,
|
|
893
|
-
query: (event as any).query,
|
|
894
|
-
})
|
|
895
|
-
);
|
|
896
|
-
|
|
897
|
-
// Write interrupt message to session
|
|
898
|
-
await this.writeNodeInterrupt(sessionId, event.node_id, event.node_type, (event as any).query, (event as any).agent_session_id);
|
|
899
|
-
|
|
900
|
-
// Resolve with paused status
|
|
901
|
-
sessionState.resolveCompleted({
|
|
902
|
-
runId,
|
|
903
|
-
status: 'paused',
|
|
904
|
-
pendingNodeId: event.node_id,
|
|
905
|
-
query: (event as any).query,
|
|
906
|
-
agentSessionId: (event as any).agent_session_id,
|
|
907
|
-
});
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
/**
|
|
912
|
-
* Write node interrupt message
|
|
913
|
-
*/
|
|
914
|
-
@TracedAs("workflow.engine.writeNodeInterrupt", { recordParams: true, recordResult: true, log: true })
|
|
915
|
-
@TracedAs("workflow.engine.writeNodeInterrupt", { recordParams: true, recordResult: true, log: true })
|
|
916
|
-
private async writeNodeInterrupt(
|
|
917
|
-
sessionId: string,
|
|
918
|
-
nodeId: string,
|
|
919
|
-
nodeType: string,
|
|
920
|
-
query: string,
|
|
921
|
-
agentSessionId?: string
|
|
922
|
-
): Promise<void> {
|
|
923
|
-
if (this.sessionComponent) {
|
|
924
|
-
await this.sessionComponent.addMessage(sessionId, {
|
|
925
|
-
role: 'workflow.node.interrupt',
|
|
926
|
-
content: query,
|
|
927
|
-
metadata: {
|
|
928
|
-
type: 'workflow.node.interrupt',
|
|
929
|
-
workflowNodeId: nodeId,
|
|
930
|
-
workflowNodeType: nodeType,
|
|
931
|
-
query,
|
|
932
|
-
agentSessionId,
|
|
933
|
-
timestamp: Date.now(),
|
|
934
|
-
},
|
|
935
|
-
});
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
/**
|
|
940
|
-
* Main scheduling loop
|
|
941
|
-
*/
|
|
942
|
-
@TracedAs("workflow.engine.scheduleAndExecute", { recordParams: true, recordResult: true, log: true })
|
|
943
|
-
@TracedAs("workflow.engine.scheduleAndExecute", { recordParams: true, recordResult: true, log: true })
|
|
944
|
-
private async scheduleAndExecute(
|
|
945
|
-
sessionState: ActiveSessionState,
|
|
946
|
-
options?: {
|
|
947
|
-
input?: Record<string, any>;
|
|
948
|
-
pendingNodeId?: string;
|
|
949
|
-
agentSessionId?: string;
|
|
950
|
-
userResponse?: string;
|
|
951
|
-
restoredOutputs?: Map<string, unknown>;
|
|
952
|
-
}
|
|
953
|
-
): Promise<void> {
|
|
954
|
-
const { scheduler, executor, eventBus, abortController, nodeOutputs } = sessionState;
|
|
955
|
-
const { input: globalInput, pendingNodeId, agentSessionId, userResponse, restoredOutputs } = options || {};
|
|
956
|
-
|
|
957
|
-
// Merge restored outputs if any
|
|
958
|
-
if (restoredOutputs) {
|
|
959
|
-
for (const [nodeId, output] of restoredOutputs) {
|
|
960
|
-
nodeOutputs.set(nodeId, output);
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
// Helper to schedule available nodes
|
|
965
|
-
const scheduleAvailableNodes = async (): Promise<boolean> => {
|
|
966
|
-
const state = scheduler.getState();
|
|
967
|
-
const completedNodes = new Set(state.completed);
|
|
968
|
-
|
|
969
|
-
const readyNodes = scheduler.getReadyNodes(completedNodes);
|
|
970
|
-
const canStart = scheduler.canStartMore();
|
|
971
|
-
|
|
972
|
-
if (readyNodes.length === 0 || !canStart) {
|
|
973
|
-
return false;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
let scheduled = 0;
|
|
977
|
-
for (const nodeId of readyNodes) {
|
|
978
|
-
if (!scheduler.canStartMore()) break;
|
|
979
|
-
if (scheduled >= (sessionState.config.parallelLimit ?? Infinity)) break;
|
|
980
|
-
|
|
981
|
-
await this.startNode(sessionState, nodeId, globalInput);
|
|
982
|
-
scheduled++;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
return true;
|
|
986
|
-
};
|
|
987
|
-
|
|
988
|
-
// Initial scheduling
|
|
989
|
-
if (pendingNodeId) {
|
|
990
|
-
// Resume mode: only schedule the pending node
|
|
991
|
-
await this.resumeNode(sessionState, pendingNodeId, {
|
|
992
|
-
agentSessionId,
|
|
993
|
-
userResponse,
|
|
994
|
-
});
|
|
995
|
-
} else {
|
|
996
|
-
await scheduleAvailableNodes();
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
// Event-driven loop
|
|
1000
|
-
while (sessionState.status === 'running' && !abortController.signal.aborted) {
|
|
1001
|
-
const state = scheduler.getState();
|
|
1002
|
-
if (state.pending.length === 0 && state.running.length === 0) {
|
|
1003
|
-
break;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
try {
|
|
1007
|
-
await this.waitForNextNodeEvent(sessionState, abortController.signal);
|
|
1008
|
-
} catch {
|
|
1009
|
-
break;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
await scheduleAvailableNodes();
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
/**
|
|
1017
|
-
* Wait for next node event
|
|
1018
|
-
*/
|
|
1019
|
-
@TracedAs("workflow.engine.waitForNextNodeEvent", { recordParams: true, recordResult: true, log: true })
|
|
1020
|
-
private waitForNextNodeEvent(
|
|
1021
|
-
sessionState: ActiveSessionState,
|
|
1022
|
-
signal: AbortSignal
|
|
1023
|
-
): Promise<void> {
|
|
1024
|
-
return new Promise((resolve) => {
|
|
1025
|
-
const timeoutId = setTimeout(() => {
|
|
1026
|
-
cleanup();
|
|
1027
|
-
resolve();
|
|
1028
|
-
}, 100);
|
|
1029
|
-
|
|
1030
|
-
const cleanup = () => {
|
|
1031
|
-
clearTimeout(timeoutId);
|
|
1032
|
-
sessionState.eventBus.off('node.completed', onCompleted);
|
|
1033
|
-
sessionState.eventBus.off('node.failed', onFailed);
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
const onCompleted = (event: WorkflowEvent) => {
|
|
1037
|
-
if (event.type === 'node.completed') {
|
|
1038
|
-
cleanup();
|
|
1039
|
-
resolve();
|
|
1040
|
-
}
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
|
-
const onFailed = (event: WorkflowEvent) => {
|
|
1044
|
-
if (event.type === 'node.failed') {
|
|
1045
|
-
cleanup();
|
|
1046
|
-
resolve();
|
|
1047
|
-
}
|
|
1048
|
-
};
|
|
1049
|
-
|
|
1050
|
-
sessionState.eventBus.on('node.completed', onCompleted);
|
|
1051
|
-
sessionState.eventBus.on('node.failed', onFailed);
|
|
1052
|
-
|
|
1053
|
-
if (signal.aborted) {
|
|
1054
|
-
cleanup();
|
|
1055
|
-
resolve();
|
|
1056
|
-
}
|
|
1057
|
-
});
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
/**
|
|
1061
|
-
* Start execution of a single node
|
|
1062
|
-
*/
|
|
1063
|
-
@TracedAs("workflow.engine.startNode", { recordParams: true, recordResult: true, log: true })
|
|
1064
|
-
@TracedAs("workflow.engine.startNode", { recordParams: true, recordResult: true, log: true })
|
|
1065
|
-
private async startNode(
|
|
1066
|
-
sessionState: ActiveSessionState,
|
|
1067
|
-
nodeId: string,
|
|
1068
|
-
input?: Record<string, any>
|
|
1069
|
-
): Promise<void> {
|
|
1070
|
-
const { scheduler, executor, eventBus, dagManager } = sessionState;
|
|
1071
|
-
const sessionId = sessionState.sessionId;
|
|
1072
|
-
const runId = this.getRunIdFromSessionId(sessionId);
|
|
1073
|
-
|
|
1074
|
-
scheduler.markStarted(nodeId);
|
|
1075
|
-
|
|
1076
|
-
// Publish scheduled event
|
|
1077
|
-
await eventBus.publish(
|
|
1078
|
-
createWorkflowEvent('node.scheduled', runId, {
|
|
1079
|
-
node_id: nodeId,
|
|
1080
|
-
})
|
|
1081
|
-
);
|
|
1082
|
-
|
|
1083
|
-
// Get node definition
|
|
1084
|
-
const nodeDef = dagManager.getNode(nodeId);
|
|
1085
|
-
if (!nodeDef) {
|
|
1086
|
-
scheduler.markFailed(nodeId);
|
|
1087
|
-
return;
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
// Write workflow.node.call message
|
|
1091
|
-
await this.writeNodeCall(sessionId, nodeId, nodeDef.type, input);
|
|
1092
|
-
|
|
1093
|
-
// Create execution context
|
|
1094
|
-
const context = this.createExecutionContext(sessionState, nodeId, input);
|
|
1095
|
-
|
|
1096
|
-
// Execute node
|
|
1097
|
-
executor.executeNode(nodeDef, context).catch(async (error) => {
|
|
1098
|
-
if (error instanceof AskUserError) {
|
|
1099
|
-
await eventBus.publish(createNodeInterruptEvent(
|
|
1100
|
-
runId,
|
|
1101
|
-
nodeId,
|
|
1102
|
-
nodeDef.type,
|
|
1103
|
-
error.query,
|
|
1104
|
-
error.agentSessionId
|
|
1105
|
-
));
|
|
1106
|
-
return;
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
scheduler.markFailed(nodeId);
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
/**
|
|
1114
|
-
* Write node call message
|
|
1115
|
-
*/
|
|
1116
|
-
@TracedAs("workflow.engine.writeNodeCall", { recordParams: true, recordResult: true, log: true })
|
|
1117
|
-
@TracedAs("workflow.engine.writeNodeCall", { recordParams: true, recordResult: true, log: true })
|
|
1118
|
-
private async writeNodeCall(
|
|
1119
|
-
sessionId: string,
|
|
1120
|
-
nodeId: string,
|
|
1121
|
-
nodeType: string,
|
|
1122
|
-
input?: any
|
|
1123
|
-
): Promise<void> {
|
|
1124
|
-
if (this.sessionComponent) {
|
|
1125
|
-
await this.sessionComponent.addMessage(sessionId, {
|
|
1126
|
-
role: 'workflow.node.call',
|
|
1127
|
-
content: JSON.stringify({ input }),
|
|
1128
|
-
metadata: {
|
|
1129
|
-
type: 'workflow.node.call',
|
|
1130
|
-
workflowNodeId: nodeId,
|
|
1131
|
-
workflowNodeType: nodeType,
|
|
1132
|
-
timestamp: Date.now(),
|
|
1133
|
-
},
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
/**
|
|
1139
|
-
* Resume execution of a paused node
|
|
1140
|
-
*/
|
|
1141
|
-
@TracedAs("workflow.engine.resumeNode", { recordParams: true, recordResult: true, log: true })
|
|
1142
|
-
@TracedAs("workflow.engine.resumeNode", { recordParams: true, recordResult: true, log: true })
|
|
1143
|
-
private async resumeNode(
|
|
1144
|
-
sessionState: ActiveSessionState,
|
|
1145
|
-
nodeId: string,
|
|
1146
|
-
options?: {
|
|
1147
|
-
agentSessionId?: string;
|
|
1148
|
-
userResponse?: string;
|
|
1149
|
-
}
|
|
1150
|
-
): Promise<void> {
|
|
1151
|
-
const { scheduler, executor, eventBus, dagManager } = sessionState;
|
|
1152
|
-
const sessionId = sessionState.sessionId;
|
|
1153
|
-
const runId = this.getRunIdFromSessionId(sessionId);
|
|
1154
|
-
|
|
1155
|
-
scheduler.markStarted(nodeId);
|
|
1156
|
-
|
|
1157
|
-
// Publish resume event
|
|
1158
|
-
await eventBus.publish(
|
|
1159
|
-
createWorkflowEvent('node.started', runId, {
|
|
1160
|
-
node_id: nodeId,
|
|
1161
|
-
agentSessionId: options?.agentSessionId,
|
|
1162
|
-
userResponse: options?.userResponse,
|
|
1163
|
-
})
|
|
1164
|
-
);
|
|
1165
|
-
|
|
1166
|
-
// Get node definition
|
|
1167
|
-
const nodeDef = dagManager.getNode(nodeId);
|
|
1168
|
-
if (!nodeDef) {
|
|
1169
|
-
scheduler.markFailed(nodeId);
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
// Create execution context
|
|
1174
|
-
const context = this.createExecutionContext(sessionState, nodeId, undefined);
|
|
1175
|
-
|
|
1176
|
-
// Add resume options to context
|
|
1177
|
-
if (options?.agentSessionId || options?.userResponse) {
|
|
1178
|
-
(context as any).agentSessionId = options.agentSessionId;
|
|
1179
|
-
(context as any).userResponse = options.userResponse;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// Execute node
|
|
1183
|
-
executor.executeNode(nodeDef, context).catch((error) => {
|
|
1184
|
-
if (error instanceof AskUserError) {
|
|
1185
|
-
const event = createNodeInterruptEvent(
|
|
1186
|
-
runId,
|
|
1187
|
-
nodeId,
|
|
1188
|
-
nodeDef.type,
|
|
1189
|
-
error.query,
|
|
1190
|
-
error.agentSessionId
|
|
1191
|
-
);
|
|
1192
|
-
eventBus.publish(event);
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
scheduler.markFailed(nodeId);
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
/**
|
|
1200
|
-
* Create execution context for a node
|
|
1201
|
-
*/
|
|
1202
|
-
private createExecutionContext(
|
|
1203
|
-
sessionState: ActiveSessionState,
|
|
1204
|
-
nodeId: string,
|
|
1205
|
-
globalInput?: Record<string, any>
|
|
1206
|
-
): NodeExecutionContext {
|
|
1207
|
-
const nodeDef = sessionState.dagManager.getNode(nodeId)!;
|
|
1208
|
-
const runId = this.getRunIdFromSessionId(sessionState.sessionId);
|
|
1209
|
-
|
|
1210
|
-
// Collect inputs from dependencies
|
|
1211
|
-
const deps = nodeDef.depends_on || [];
|
|
1212
|
-
const input: Record<string, any> = {};
|
|
1213
|
-
|
|
1214
|
-
for (const depId of deps) {
|
|
1215
|
-
const depOutput = sessionState.nodeOutputs.get(depId);
|
|
1216
|
-
if (depOutput !== undefined) {
|
|
1217
|
-
input[depId] = depOutput;
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
// Merge global input if this is an entry node
|
|
1222
|
-
const analysis = sessionState.dagManager.analyze();
|
|
1223
|
-
if (analysis.entryNodes.includes(nodeId) && globalInput) {
|
|
1224
|
-
Object.assign(input, globalInput);
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
// Ask user function
|
|
1228
|
-
const askUser = (query: string): never => {
|
|
1229
|
-
throw new AskUserError(
|
|
1230
|
-
runId,
|
|
1231
|
-
sessionState.sessionId,
|
|
1232
|
-
nodeId,
|
|
1233
|
-
nodeDef.type,
|
|
1234
|
-
query
|
|
1235
|
-
);
|
|
1236
|
-
};
|
|
1237
|
-
|
|
1238
|
-
return {
|
|
1239
|
-
runId,
|
|
1240
|
-
sessionId: sessionState.sessionId,
|
|
1241
|
-
workflowName: sessionState.workflowName,
|
|
1242
|
-
nodeId,
|
|
1243
|
-
input,
|
|
1244
|
-
previousOutputs: sessionState.nodeOutputs,
|
|
1245
|
-
config: nodeDef.config ?? {},
|
|
1246
|
-
debug: sessionState.config.debug,
|
|
1247
|
-
eventBus: sessionState.eventBus,
|
|
1248
|
-
nodeOutputs: sessionState.nodeOutputs,
|
|
1249
|
-
workflowHistory: sessionState.workflowHistory,
|
|
1250
|
-
sessionComponent: this.sessionComponent,
|
|
1251
|
-
askUser,
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
/**
|
|
1256
|
-
* Check if workflow is complete and finalize
|
|
1257
|
-
*/
|
|
1258
|
-
@TracedAs("workflow.engine.checkAndFinalize", { recordParams: true, recordResult: true, log: true })
|
|
1259
|
-
private checkAndFinalize(sessionState: ActiveSessionState): void {
|
|
1260
|
-
const state = sessionState.scheduler.getState();
|
|
1261
|
-
|
|
1262
|
-
if (state.pending.length === 0 &&
|
|
1263
|
-
state.running.length === 0 &&
|
|
1264
|
-
state.ready.length === 0) {
|
|
1265
|
-
|
|
1266
|
-
if (state.failed.length > 0) {
|
|
1267
|
-
this.failWorkflow(sessionState, new Error(`Nodes failed: ${state.failed.join(', ')}`));
|
|
1268
|
-
} else {
|
|
1269
|
-
this.completeWorkflow(sessionState);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
/**
|
|
1275
|
-
* Complete a workflow successfully
|
|
1276
|
-
*/
|
|
1277
|
-
@TracedAs("workflow.engine.completeWorkflow", { recordParams: true, recordResult: true, log: true })
|
|
1278
|
-
private async completeWorkflow(sessionState: ActiveSessionState): Promise<void> {
|
|
1279
|
-
const durationMs = Date.now() - sessionState.startedAt.getTime();
|
|
1280
|
-
const sessionId = sessionState.sessionId;
|
|
1281
|
-
const runId = this.getRunIdFromSessionId(sessionId);
|
|
1282
|
-
|
|
1283
|
-
// Collect output
|
|
1284
|
-
const output: Record<string, any> = {};
|
|
1285
|
-
for (const [nodeId, nodeOutput] of sessionState.nodeOutputs) {
|
|
1286
|
-
output[nodeId] = nodeOutput;
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
// Update session metadata
|
|
1290
|
-
if (this.sessionComponent) {
|
|
1291
|
-
const metadata = await this.getSessionMetadata(sessionId);
|
|
1292
|
-
await this.sessionComponent.update(sessionId, {
|
|
1293
|
-
metadata: { ...metadata, status: 'completed' },
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
// Publish completed event
|
|
1298
|
-
await sessionState.eventBus.publish(
|
|
1299
|
-
createWorkflowEvent('workflow.completed', runId, {
|
|
1300
|
-
result: output,
|
|
1301
|
-
duration_ms: durationMs,
|
|
1302
|
-
}),
|
|
1303
|
-
true
|
|
1304
|
-
);
|
|
1305
|
-
|
|
1306
|
-
// Resolve completion
|
|
1307
|
-
sessionState.resolveCompleted({
|
|
1308
|
-
runId,
|
|
1309
|
-
status: 'completed',
|
|
1310
|
-
output,
|
|
1311
|
-
durationMs,
|
|
1312
|
-
});
|
|
1313
|
-
|
|
1314
|
-
// Cleanup
|
|
1315
|
-
this.cleanupSession(sessionState);
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
/**
|
|
1319
|
-
* Fail a workflow
|
|
1320
|
-
*/
|
|
1321
|
-
@TracedAs("workflow.engine.failWorkflow", { recordParams: true, recordResult: true, log: true })
|
|
1322
|
-
private async failWorkflow(sessionState: ActiveSessionState, error: Error): Promise<void> {
|
|
1323
|
-
const durationMs = Date.now() - sessionState.startedAt.getTime();
|
|
1324
|
-
const sessionId = sessionState.sessionId;
|
|
1325
|
-
const runId = this.getRunIdFromSessionId(sessionId);
|
|
1326
|
-
|
|
1327
|
-
sessionState.executor.cancelAll();
|
|
1328
|
-
|
|
1329
|
-
// Update session metadata
|
|
1330
|
-
if (this.sessionComponent) {
|
|
1331
|
-
const metadata = await this.getSessionMetadata(sessionId);
|
|
1332
|
-
await this.sessionComponent.update(sessionId, {
|
|
1333
|
-
metadata: { ...metadata, status: 'failed' },
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// Publish failed event
|
|
1338
|
-
await sessionState.eventBus.publish(
|
|
1339
|
-
createWorkflowEvent('workflow.failed', runId, {
|
|
1340
|
-
error: { message: error.message, stack: error.stack },
|
|
1341
|
-
failed_at: new Date().toISOString(),
|
|
1342
|
-
}),
|
|
1343
|
-
true
|
|
1344
|
-
);
|
|
1345
|
-
|
|
1346
|
-
// Resolve with error
|
|
1347
|
-
sessionState.resolveCompleted({
|
|
1348
|
-
runId,
|
|
1349
|
-
status: 'failed',
|
|
1350
|
-
error: error.message,
|
|
1351
|
-
durationMs,
|
|
1352
|
-
});
|
|
1353
|
-
|
|
1354
|
-
// Cleanup
|
|
1355
|
-
this.cleanupSession(sessionState);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
/**
|
|
1359
|
-
* Cleanup session resources
|
|
1360
|
-
*/
|
|
1361
|
-
private cleanupSession(sessionState: ActiveSessionState): void {
|
|
1362
|
-
sessionState.eventBus.clear();
|
|
1363
|
-
this.activeSessions.delete(sessionState.sessionId);
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
/**
|
|
1367
|
-
* Wait for a run to complete
|
|
1368
|
-
*/
|
|
1369
|
-
private async waitForCompletion(
|
|
1370
|
-
sessionId: string,
|
|
1371
|
-
timeout?: number | null
|
|
1372
|
-
): Promise<RunResult> {
|
|
1373
|
-
const sessionState = this.activeSessions.get(sessionId);
|
|
1374
|
-
|
|
1375
|
-
if (!sessionState) {
|
|
1376
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
const timeoutMs = timeout ?? 300000;
|
|
1380
|
-
|
|
1381
|
-
const timeoutPromise = new Promise<RunResult>((_, reject) => {
|
|
1382
|
-
setTimeout(() => {
|
|
1383
|
-
reject(new Error(`Workflow execution timeout: ${timeoutMs}ms`));
|
|
1384
|
-
}, timeoutMs);
|
|
1385
|
-
});
|
|
1386
|
-
|
|
1387
|
-
return Promise.race([
|
|
1388
|
-
sessionState.completedPromise,
|
|
1389
|
-
timeoutPromise,
|
|
1390
|
-
]);
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
/**
|
|
1394
|
-
* Get session metadata
|
|
1395
|
-
*/
|
|
1396
|
-
private async getSessionMetadata(sessionId: string): Promise<WorkflowSessionMetadata> {
|
|
1397
|
-
if (!this.sessionComponent) {
|
|
1398
|
-
return { type: 'workflow', workflowId: '', workflowName: '', status: 'running' };
|
|
1399
|
-
}
|
|
1400
|
-
const session = await this.sessionComponent.get(sessionId);
|
|
1401
|
-
return (session?.metadata as WorkflowSessionMetadata) || { type: 'workflow', workflowId: '', workflowName: '', status: 'running' };
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
/**
|
|
1405
|
-
* Find workflow definition from repository
|
|
1406
|
-
*/
|
|
1407
|
-
private findDefinitionForWorkflow(workflowIdOrName: string): WorkflowDefinition | null {
|
|
1408
|
-
if (!this.workflowRepository) {
|
|
1409
|
-
return null;
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
// Try by ID first
|
|
1413
|
-
let workflow = this.workflowRepository.getById(workflowIdOrName);
|
|
1414
|
-
if (workflow) return workflow.definition;
|
|
1415
|
-
|
|
1416
|
-
// Try by name
|
|
1417
|
-
workflow = this.workflowRepository.getByName(workflowIdOrName);
|
|
1418
|
-
if (workflow) return workflow.definition;
|
|
1419
|
-
|
|
1420
|
-
return null;
|
|
1421
|
-
}
|
|
1422
|
-
}
|