@falai/agent 1.2.8 → 2.0.1
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 +40 -886
- package/dist/adapters/MemoryAdapter.js +2 -2
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/MongoAdapter.js +2 -2
- package/dist/adapters/MongoAdapter.js.map +1 -1
- package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenSearchAdapter.js +9 -7
- package/dist/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts +14 -0
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +25 -9
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/PrismaAdapter.js +5 -5
- package/dist/adapters/PrismaAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.js +2 -2
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts +17 -0
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +30 -11
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/MemoryAdapter.js +2 -2
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/MongoAdapter.js +2 -2
- package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
- package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/OpenSearchAdapter.js +9 -7
- package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +14 -0
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +25 -9
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/PrismaAdapter.js +5 -5
- package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.js +2 -2
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts +17 -0
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +30 -11
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/constants/index.d.ts +0 -9
- package/dist/cjs/constants/index.d.ts.map +1 -1
- package/dist/cjs/constants/index.js +2 -11
- package/dist/cjs/constants/index.js.map +1 -1
- package/dist/cjs/core/Agent.d.ts +119 -153
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +471 -324
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts +107 -0
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -0
- package/dist/cjs/core/AutoChainExecutor.js +297 -0
- package/dist/cjs/core/AutoChainExecutor.js.map +1 -0
- package/dist/cjs/core/BranchEvaluator.d.ts +54 -0
- package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -0
- package/dist/cjs/core/BranchEvaluator.js +130 -0
- package/dist/cjs/core/BranchEvaluator.js.map +1 -0
- package/dist/cjs/core/DirectiveBus.d.ts +88 -0
- package/dist/cjs/core/DirectiveBus.d.ts.map +1 -0
- package/dist/cjs/core/DirectiveBus.js +196 -0
- package/dist/cjs/core/DirectiveBus.js.map +1 -0
- package/dist/cjs/core/DirectiveChainTracker.d.ts +49 -0
- package/dist/cjs/core/DirectiveChainTracker.d.ts.map +1 -0
- package/dist/cjs/core/DirectiveChainTracker.js +121 -0
- package/dist/cjs/core/DirectiveChainTracker.js.map +1 -0
- package/dist/cjs/core/Flow.d.ts +186 -0
- package/dist/cjs/core/Flow.d.ts.map +1 -0
- package/dist/cjs/core/Flow.js +550 -0
- package/dist/cjs/core/Flow.js.map +1 -0
- package/dist/cjs/core/FlowRouter.d.ts +182 -0
- package/dist/cjs/core/FlowRouter.d.ts.map +1 -0
- package/dist/cjs/core/{RoutingEngine.js → FlowRouter.js} +323 -306
- package/dist/cjs/core/FlowRouter.js.map +1 -0
- package/dist/cjs/core/PersistenceManager.d.ts +2 -2
- package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
- package/dist/cjs/core/PersistenceManager.js +7 -7
- package/dist/cjs/core/PersistenceManager.js.map +1 -1
- package/dist/cjs/core/PromptComposer.d.ts +21 -8
- package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/core/PromptComposer.js +182 -105
- package/dist/cjs/core/PromptComposer.js.map +1 -1
- package/dist/cjs/core/PromptSectionCache.d.ts +1 -1
- package/dist/cjs/core/PromptSectionCache.js +1 -1
- package/dist/cjs/core/ResponseEngine.d.ts +18 -8
- package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
- package/dist/cjs/core/ResponseEngine.js +38 -36
- package/dist/cjs/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts +73 -56
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +1191 -1014
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +124 -26
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +509 -136
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/SignalEvaluator.d.ts +86 -0
- package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -0
- package/dist/cjs/core/SignalEvaluator.js +333 -0
- package/dist/cjs/core/SignalEvaluator.js.map +1 -0
- package/dist/cjs/core/SignalProcessor.d.ts +152 -0
- package/dist/cjs/core/SignalProcessor.d.ts.map +1 -0
- package/dist/cjs/core/SignalProcessor.js +562 -0
- package/dist/cjs/core/SignalProcessor.js.map +1 -0
- package/dist/cjs/core/Step.d.ts +43 -32
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +221 -126
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts +2 -2
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
- package/dist/cjs/core/ToolManager.d.ts +44 -13
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +174 -91
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/core/createAgent.d.ts +35 -0
- package/dist/cjs/core/createAgent.d.ts.map +1 -0
- package/dist/cjs/core/createAgent.js +39 -0
- package/dist/cjs/core/createAgent.js.map +1 -0
- package/dist/cjs/core/flow-namespace.d.ts +49 -0
- package/dist/cjs/core/flow-namespace.d.ts.map +1 -0
- package/dist/cjs/core/flow-namespace.js +171 -0
- package/dist/cjs/core/flow-namespace.js.map +1 -0
- package/dist/cjs/index.d.ts +11 -14
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +18 -22
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +1 -1
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +1 -1
- package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +183 -54
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js +0 -6
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/ai.d.ts +3 -3
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/errors.d.ts +15 -0
- package/dist/cjs/types/errors.d.ts.map +1 -0
- package/dist/cjs/types/errors.js +22 -0
- package/dist/cjs/types/errors.js.map +1 -0
- package/dist/cjs/types/flow.d.ts +513 -0
- package/dist/cjs/types/flow.d.ts.map +1 -0
- package/dist/cjs/types/{route.js → flow.js} +2 -2
- package/dist/cjs/types/flow.js.map +1 -0
- package/dist/cjs/types/index.d.ts +7 -6
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +6 -2
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/persistence.d.ts +11 -7
- package/dist/cjs/types/persistence.d.ts.map +1 -1
- package/dist/cjs/types/routing.d.ts +1 -1
- package/dist/cjs/types/routing.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +24 -23
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/signals.d.ts +248 -0
- package/dist/cjs/types/signals.d.ts.map +1 -0
- package/dist/cjs/types/signals.js +11 -0
- package/dist/cjs/types/signals.js.map +1 -0
- package/dist/cjs/types/template.d.ts +2 -8
- package/dist/cjs/types/template.d.ts.map +1 -1
- package/dist/cjs/types/tool.d.ts +36 -29
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/types/tool.js +1 -1
- package/dist/cjs/types/tool.js.map +1 -1
- package/dist/cjs/utils/condition.d.ts +7 -1
- package/dist/cjs/utils/condition.d.ts.map +1 -1
- package/dist/cjs/utils/condition.js.map +1 -1
- package/dist/cjs/utils/id.d.ts +13 -5
- package/dist/cjs/utils/id.d.ts.map +1 -1
- package/dist/cjs/utils/id.js +24 -10
- package/dist/cjs/utils/id.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +2 -2
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +7 -3
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/session.d.ts +44 -5
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +197 -38
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/constants/index.d.ts +0 -9
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +3 -9
- package/dist/constants/index.js.map +1 -1
- package/dist/core/Agent.d.ts +119 -153
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +472 -325
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/AutoChainExecutor.d.ts +107 -0
- package/dist/core/AutoChainExecutor.d.ts.map +1 -0
- package/dist/core/AutoChainExecutor.js +293 -0
- package/dist/core/AutoChainExecutor.js.map +1 -0
- package/dist/core/BranchEvaluator.d.ts +54 -0
- package/dist/core/BranchEvaluator.d.ts.map +1 -0
- package/dist/core/BranchEvaluator.js +126 -0
- package/dist/core/BranchEvaluator.js.map +1 -0
- package/dist/core/DirectiveBus.d.ts +88 -0
- package/dist/core/DirectiveBus.d.ts.map +1 -0
- package/dist/core/DirectiveBus.js +192 -0
- package/dist/core/DirectiveBus.js.map +1 -0
- package/dist/core/DirectiveChainTracker.d.ts +49 -0
- package/dist/core/DirectiveChainTracker.d.ts.map +1 -0
- package/dist/core/DirectiveChainTracker.js +117 -0
- package/dist/core/DirectiveChainTracker.js.map +1 -0
- package/dist/core/Flow.d.ts +186 -0
- package/dist/core/Flow.d.ts.map +1 -0
- package/dist/core/Flow.js +546 -0
- package/dist/core/Flow.js.map +1 -0
- package/dist/core/FlowRouter.d.ts +182 -0
- package/dist/core/FlowRouter.d.ts.map +1 -0
- package/dist/core/{RoutingEngine.js → FlowRouter.js} +322 -305
- package/dist/core/FlowRouter.js.map +1 -0
- package/dist/core/PersistenceManager.d.ts +2 -2
- package/dist/core/PersistenceManager.d.ts.map +1 -1
- package/dist/core/PersistenceManager.js +7 -7
- package/dist/core/PersistenceManager.js.map +1 -1
- package/dist/core/PromptComposer.d.ts +21 -8
- package/dist/core/PromptComposer.d.ts.map +1 -1
- package/dist/core/PromptComposer.js +183 -106
- package/dist/core/PromptComposer.js.map +1 -1
- package/dist/core/PromptSectionCache.d.ts +1 -1
- package/dist/core/PromptSectionCache.js +1 -1
- package/dist/core/ResponseEngine.d.ts +18 -8
- package/dist/core/ResponseEngine.d.ts.map +1 -1
- package/dist/core/ResponseEngine.js +38 -36
- package/dist/core/ResponseEngine.js.map +1 -1
- package/dist/core/ResponseModal.d.ts +73 -56
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +1193 -1016
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +124 -26
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +509 -137
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/SignalEvaluator.d.ts +86 -0
- package/dist/core/SignalEvaluator.d.ts.map +1 -0
- package/dist/core/SignalEvaluator.js +326 -0
- package/dist/core/SignalEvaluator.js.map +1 -0
- package/dist/core/SignalProcessor.d.ts +152 -0
- package/dist/core/SignalProcessor.d.ts.map +1 -0
- package/dist/core/SignalProcessor.js +555 -0
- package/dist/core/SignalProcessor.js.map +1 -0
- package/dist/core/Step.d.ts +43 -32
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +220 -126
- package/dist/core/Step.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts +2 -2
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/core/StreamingToolExecutor.js.map +1 -1
- package/dist/core/ToolManager.d.ts +44 -13
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +174 -91
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/core/createAgent.d.ts +35 -0
- package/dist/core/createAgent.d.ts.map +1 -0
- package/dist/core/createAgent.js +36 -0
- package/dist/core/createAgent.js.map +1 -0
- package/dist/core/flow-namespace.d.ts +49 -0
- package/dist/core/flow-namespace.d.ts.map +1 -0
- package/dist/core/flow-namespace.js +168 -0
- package/dist/core/flow-namespace.js.map +1 -0
- package/dist/index.d.ts +11 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -12
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +1 -1
- package/dist/providers/AnthropicProvider.js +1 -1
- package/dist/providers/GeminiProvider.d.ts +1 -1
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +1 -1
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +1 -1
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/dist/types/agent.d.ts +183 -54
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js +0 -6
- package/dist/types/agent.js.map +1 -1
- package/dist/types/ai.d.ts +3 -3
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +18 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/flow.d.ts +513 -0
- package/dist/types/flow.d.ts.map +1 -0
- package/dist/types/flow.js +5 -0
- package/dist/types/flow.js.map +1 -0
- package/dist/types/index.d.ts +7 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/persistence.d.ts +11 -7
- package/dist/types/persistence.d.ts.map +1 -1
- package/dist/types/routing.d.ts +1 -1
- package/dist/types/routing.d.ts.map +1 -1
- package/dist/types/session.d.ts +24 -23
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/signals.d.ts +248 -0
- package/dist/types/signals.d.ts.map +1 -0
- package/dist/types/signals.js +10 -0
- package/dist/types/signals.js.map +1 -0
- package/dist/types/template.d.ts +2 -8
- package/dist/types/template.d.ts.map +1 -1
- package/dist/types/tool.d.ts +36 -29
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js +1 -1
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/condition.d.ts +7 -1
- package/dist/utils/condition.d.ts.map +1 -1
- package/dist/utils/condition.js.map +1 -1
- package/dist/utils/id.d.ts +13 -5
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +22 -9
- package/dist/utils/id.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/session.d.ts +44 -5
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +193 -37
- package/dist/utils/session.js.map +1 -1
- package/docs/README.md +22 -200
- package/docs/concepts/architecture.md +281 -0
- package/docs/concepts/directives.md +400 -0
- package/docs/concepts/pipeline.md +399 -0
- package/docs/guides/branching.md +263 -0
- package/docs/guides/compaction.md +163 -0
- package/docs/guides/conditions.md +167 -0
- package/docs/guides/error-handling.md +176 -0
- package/docs/guides/flow-control.md +409 -0
- package/docs/guides/instructions.md +210 -0
- package/docs/guides/persistence.md +182 -0
- package/docs/guides/streaming.md +137 -0
- package/docs/migration/README.md +14 -0
- package/docs/migration/route-to-flow.md +561 -0
- package/docs/migration/v1-to-v2.md +909 -0
- package/docs/reference/adapters.md +481 -0
- package/docs/reference/branches.md +241 -0
- package/docs/reference/create-agent.md +186 -0
- package/docs/reference/directive.md +243 -0
- package/docs/reference/errors.md +122 -0
- package/docs/reference/flow.md +238 -0
- package/docs/reference/instruction.md +177 -0
- package/docs/reference/pre-directive.md +131 -0
- package/docs/reference/providers.md +227 -0
- package/docs/reference/signals.md +356 -0
- package/docs/reference/step.md +339 -0
- package/docs/reference/tool.md +269 -0
- package/docs/start/01-install.md +81 -0
- package/docs/start/02-first-agent.md +196 -0
- package/docs/start/03-collect-data.md +222 -0
- package/docs/start/04-add-tools.md +276 -0
- package/docs/start/05-go-to-production.md +216 -0
- package/examples/01-quickstart.ts +20 -0
- package/examples/02-data-extraction.ts +90 -0
- package/examples/03-tools.ts +136 -0
- package/examples/04-instructions.ts +100 -0
- package/examples/05-branching.ts +140 -0
- package/examples/06-flow-control.ts +103 -0
- package/examples/07-streaming.ts +69 -0
- package/examples/08-persistence.ts +98 -0
- package/examples/09-signals.ts +144 -0
- package/examples/tsconfig.json +30 -0
- package/package.json +2 -1
- package/src/adapters/MemoryAdapter.ts +3 -3
- package/src/adapters/MongoAdapter.ts +3 -3
- package/src/adapters/OpenSearchAdapter.ts +10 -8
- package/src/adapters/PostgreSQLAdapter.ts +26 -10
- package/src/adapters/PrismaAdapter.ts +6 -6
- package/src/adapters/RedisAdapter.ts +3 -3
- package/src/adapters/SQLiteAdapter.ts +31 -12
- package/src/constants/index.ts +2 -10
- package/src/core/Agent.ts +585 -374
- package/src/core/AutoChainExecutor.ts +440 -0
- package/src/core/BranchEvaluator.ts +167 -0
- package/src/core/DirectiveBus.ts +248 -0
- package/src/core/DirectiveChainTracker.ts +144 -0
- package/src/core/Flow.ts +666 -0
- package/src/core/{RoutingEngine.ts → FlowRouter.ts} +385 -365
- package/src/core/PersistenceManager.ts +8 -8
- package/src/core/PromptComposer.ts +209 -140
- package/src/core/PromptSectionCache.ts +1 -1
- package/src/core/ResponseEngine.ts +61 -46
- package/src/core/ResponseModal.ts +1453 -1240
- package/src/core/ResponsePipeline.ts +655 -175
- package/src/core/SignalEvaluator.ts +420 -0
- package/src/core/SignalProcessor.ts +723 -0
- package/src/core/Step.ts +279 -176
- package/src/core/StreamingToolExecutor.ts +4 -4
- package/src/core/ToolManager.ts +200 -97
- package/src/core/createAgent.ts +40 -0
- package/src/core/flow-namespace.ts +219 -0
- package/src/index.ts +42 -36
- package/src/providers/AnthropicProvider.ts +2 -2
- package/src/providers/GeminiProvider.ts +2 -2
- package/src/providers/OpenAIProvider.ts +2 -2
- package/src/types/agent.ts +182 -53
- package/src/types/ai.ts +3 -3
- package/src/types/errors.ts +18 -0
- package/src/types/flow.ts +590 -0
- package/src/types/index.ts +43 -16
- package/src/types/persistence.ts +12 -8
- package/src/types/routing.ts +1 -1
- package/src/types/session.ts +26 -23
- package/src/types/signals.ts +321 -0
- package/src/types/template.ts +3 -11
- package/src/types/tool.ts +50 -42
- package/src/utils/condition.ts +13 -4
- package/src/utils/id.ts +27 -9
- package/src/utils/index.ts +6 -2
- package/src/utils/session.ts +238 -42
- package/dist/cjs/core/BatchExecutor.d.ts +0 -359
- package/dist/cjs/core/BatchExecutor.d.ts.map +0 -1
- package/dist/cjs/core/BatchExecutor.js +0 -861
- package/dist/cjs/core/BatchExecutor.js.map +0 -1
- package/dist/cjs/core/BatchPromptBuilder.d.ts +0 -89
- package/dist/cjs/core/BatchPromptBuilder.d.ts.map +0 -1
- package/dist/cjs/core/BatchPromptBuilder.js +0 -223
- package/dist/cjs/core/BatchPromptBuilder.js.map +0 -1
- package/dist/cjs/core/Route.d.ts +0 -180
- package/dist/cjs/core/Route.d.ts.map +0 -1
- package/dist/cjs/core/Route.js +0 -542
- package/dist/cjs/core/Route.js.map +0 -1
- package/dist/cjs/core/RoutingEngine.d.ts +0 -185
- package/dist/cjs/core/RoutingEngine.d.ts.map +0 -1
- package/dist/cjs/core/RoutingEngine.js.map +0 -1
- package/dist/cjs/types/route.d.ts +0 -336
- package/dist/cjs/types/route.d.ts.map +0 -1
- package/dist/cjs/types/route.js.map +0 -1
- package/dist/core/BatchExecutor.d.ts +0 -359
- package/dist/core/BatchExecutor.d.ts.map +0 -1
- package/dist/core/BatchExecutor.js +0 -856
- package/dist/core/BatchExecutor.js.map +0 -1
- package/dist/core/BatchPromptBuilder.d.ts +0 -89
- package/dist/core/BatchPromptBuilder.d.ts.map +0 -1
- package/dist/core/BatchPromptBuilder.js +0 -219
- package/dist/core/BatchPromptBuilder.js.map +0 -1
- package/dist/core/Route.d.ts +0 -180
- package/dist/core/Route.d.ts.map +0 -1
- package/dist/core/Route.js +0 -538
- package/dist/core/Route.js.map +0 -1
- package/dist/core/RoutingEngine.d.ts +0 -185
- package/dist/core/RoutingEngine.d.ts.map +0 -1
- package/dist/core/RoutingEngine.js.map +0 -1
- package/dist/types/route.d.ts +0 -336
- package/dist/types/route.d.ts.map +0 -1
- package/dist/types/route.js +0 -5
- package/dist/types/route.js.map +0 -1
- package/docs/CONTRIBUTING.md +0 -521
- package/docs/api/README.md +0 -3299
- package/docs/api/overview.md +0 -1410
- package/docs/architecture/data-extraction-flow.md +0 -360
- package/docs/architecture/multi-step-execution.md +0 -277
- package/docs/core/agent/README.md +0 -938
- package/docs/core/agent/context-management.md +0 -796
- package/docs/core/agent/rules-and-prohibitions.md +0 -113
- package/docs/core/agent/session-management.md +0 -693
- package/docs/core/ai-integration/prompt-composition.md +0 -355
- package/docs/core/ai-integration/providers.md +0 -515
- package/docs/core/ai-integration/response-processing.md +0 -433
- package/docs/core/conversation-flows/data-collection.md +0 -772
- package/docs/core/conversation-flows/route-dsl.md +0 -509
- package/docs/core/conversation-flows/routes.md +0 -249
- package/docs/core/conversation-flows/step-transitions.md +0 -731
- package/docs/core/conversation-flows/steps.md +0 -268
- package/docs/core/error-handling.md +0 -830
- package/docs/core/persistence/adapters.md +0 -255
- package/docs/core/persistence/session-storage.md +0 -656
- package/docs/core/routing/intelligent-routing.md +0 -470
- package/docs/core/tools/enhanced-tool.md +0 -186
- package/docs/core/tools/streaming-execution.md +0 -161
- package/docs/core/tools/tool-definition.md +0 -970
- package/docs/core/tools/tool-scoping.md +0 -819
- package/docs/guides/advanced-patterns/publishing.md +0 -186
- package/docs/guides/context-compaction.md +0 -96
- package/docs/guides/error-handling-patterns.md +0 -578
- package/docs/guides/getting-started/README.md +0 -795
- package/docs/guides/migration/README.md +0 -101
- package/docs/guides/migration/flexible-routing-conditions.md +0 -375
- package/docs/guides/migration/multi-step-execution.md +0 -393
- package/docs/guides/migration/response-modal-refactor.md +0 -518
- package/docs/guides/prompt-optimization.md +0 -164
- package/examples/advanced-patterns/context-compaction.ts +0 -223
- package/examples/advanced-patterns/knowledge-based-agent.ts +0 -735
- package/examples/advanced-patterns/persistent-onboarding.ts +0 -728
- package/examples/advanced-patterns/route-lifecycle-hooks.ts +0 -556
- package/examples/advanced-patterns/streaming-responses.ts +0 -656
- package/examples/ai-providers/anthropic-integration.ts +0 -388
- package/examples/ai-providers/openai-integration.ts +0 -228
- package/examples/condition-patterns/function-only-conditions.ts +0 -365
- package/examples/condition-patterns/mixed-array-conditions.ts +0 -477
- package/examples/condition-patterns/route-skipif-patterns.ts +0 -468
- package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
- package/examples/condition-patterns/string-only-conditions.ts +0 -296
- package/examples/conversation-flows/completion-transitions.ts +0 -318
- package/examples/core-concepts/basic-agent.ts +0 -503
- package/examples/core-concepts/modern-streaming-api.ts +0 -309
- package/examples/core-concepts/schema-driven-extraction.ts +0 -332
- package/examples/core-concepts/session-management.ts +0 -494
- package/examples/integrations/database-integration.ts +0 -631
- package/examples/integrations/healthcare-integration.ts +0 -595
- package/examples/integrations/search-integration.ts +0 -530
- package/examples/integrations/server-session-management.ts +0 -307
- package/examples/persistence/custom-adapter.ts +0 -526
- package/examples/persistence/database-persistence.ts +0 -583
- package/examples/persistence/memory-sessions.ts +0 -495
- package/examples/persistence/prisma-schema.example.prisma +0 -74
- package/examples/persistence/redis-persistence.ts +0 -488
- package/examples/tools/basic-tools.ts +0 -765
- package/examples/tools/data-enrichment-tools.ts +0 -593
- package/examples/tools/enhanced-tool-metadata.ts +0 -268
- package/examples/tools/streaming-tool-execution.ts +0 -283
- package/src/core/BatchExecutor.ts +0 -1187
- package/src/core/BatchPromptBuilder.ts +0 -299
- package/src/core/Route.ts +0 -678
- package/src/types/route.ts +0 -392
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
* ResponseModal handles all response generation logic for the Agent
|
|
3
3
|
* Provides both streaming and non-streaming response generation with unified logic
|
|
4
4
|
*/
|
|
5
|
-
import { Step } from "./Step";
|
|
6
5
|
import { ResponseEngine } from "./ResponseEngine";
|
|
7
|
-
import { ResponsePipeline } from "./ResponsePipeline";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { cloneDeep, mergeCollected, enterStep, getLastMessageFromHistory, render, logger, historyToEvents, eventsToHistory, serializeToolResult } from "../utils";
|
|
6
|
+
import { ResponsePipeline, hasDirectivePositionField } from "./ResponsePipeline";
|
|
7
|
+
import { AutoChainExecutor } from "./AutoChainExecutor";
|
|
8
|
+
import { cloneDeep, mergeCollected, enterStep, enterFlow, getLastMessageFromHistory, logger, historyToEvents, eventsToHistory, serializeToolResult, completeCurrentFlow, render } from "../utils";
|
|
11
9
|
import { createTemplateContext } from "../utils/template";
|
|
12
|
-
import { END_ROUTE_ID } from "../constants";
|
|
13
10
|
/**
|
|
14
11
|
* Error class for response generation failures
|
|
15
12
|
*/
|
|
@@ -28,7 +25,8 @@ export class ResponseGenerationError extends Error {
|
|
|
28
25
|
*/
|
|
29
26
|
static fromError(error, phase, params, context) {
|
|
30
27
|
const message = error instanceof Error ? error.message : String(error);
|
|
31
|
-
return new ResponseGenerationError(`Response generation failed in ${phase}: ${message}
|
|
28
|
+
return new ResponseGenerationError(`[ResponseGenerationError] Response generation failed in ${phase}: ${message}. ` +
|
|
29
|
+
`Check provider configuration and the ${phase} phase handler.`, { originalError: error, params, phase, context });
|
|
32
30
|
}
|
|
33
31
|
/**
|
|
34
32
|
* Check if an error is a ResponseGenerationError
|
|
@@ -48,12 +46,8 @@ export class ResponseModal {
|
|
|
48
46
|
// Initialize response engine
|
|
49
47
|
this.responseEngine = new ResponseEngine(this.agent.promptSectionCache);
|
|
50
48
|
// Initialize response pipeline with agent dependencies
|
|
51
|
-
this.responsePipeline = new ResponsePipeline(this.agent.getAgentOptions(), () => this.agent.
|
|
52
|
-
this.agent.getTools(), this.agent.
|
|
53
|
-
// Initialize batch executor for multi-step execution
|
|
54
|
-
this.batchExecutor = new BatchExecutor();
|
|
55
|
-
// Initialize batch prompt builder for combined prompts
|
|
56
|
-
this.batchPromptBuilder = new BatchPromptBuilder(this.agent.promptSectionCache);
|
|
49
|
+
this.responsePipeline = new ResponsePipeline(this.agent.getAgentOptions(), () => this.agent.getFlows(), // Pass a function to get flows dynamically
|
|
50
|
+
this.agent.getTools(), this.agent.getFlowRouter(), this.agent.updateContext.bind(this.agent), this.agent.getUpdateDataMethod(), this.agent.updateCollectedData.bind(this.agent), this.getToolManager(), this.agent.signalProcessor);
|
|
57
51
|
}
|
|
58
52
|
/**
|
|
59
53
|
* Generate a non-streaming response using unified logic
|
|
@@ -69,7 +63,8 @@ export class ResponseModal {
|
|
|
69
63
|
return result;
|
|
70
64
|
}
|
|
71
65
|
catch (error) {
|
|
72
|
-
throw new ResponseGenerationError(`
|
|
66
|
+
throw new ResponseGenerationError(`[ResponseGenerationError] Response generation failed: ${error instanceof Error ? error.message : String(error)}. ` +
|
|
67
|
+
`Check provider configuration and network connectivity.`, { originalError: error, params, phase: 'response_generation' });
|
|
73
68
|
}
|
|
74
69
|
}
|
|
75
70
|
/**
|
|
@@ -222,6 +217,17 @@ export class ResponseModal {
|
|
|
222
217
|
logger.warn(`[ResponseModal] ToolManager not available on agent - tool execution will use fallback methods`);
|
|
223
218
|
return undefined;
|
|
224
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Collect scoped instructions from agent, flow, and step into a ScopedInstructions value.
|
|
222
|
+
* @private
|
|
223
|
+
*/
|
|
224
|
+
collectScopedInstructions(flow, step) {
|
|
225
|
+
return {
|
|
226
|
+
global: this.agent.instructions,
|
|
227
|
+
flow: flow ? { flowTitle: flow.title, items: flow.instructions } : undefined,
|
|
228
|
+
step: step ? { stepId: step.id, items: step.getInstructions() } : undefined,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
225
231
|
// UNIFIED RESPONSE LOGIC - Consolidates common logic between streaming and non-streaming
|
|
226
232
|
// ============================================================================
|
|
227
233
|
/**
|
|
@@ -234,7 +240,8 @@ export class ResponseModal {
|
|
|
234
240
|
const { history: simpleHistory, contextOverride, signal } = params;
|
|
235
241
|
// Validate input parameters
|
|
236
242
|
if (!simpleHistory) {
|
|
237
|
-
throw new ResponseGenerationError('
|
|
243
|
+
throw new ResponseGenerationError('[ResponseGenerationError] Missing history: history is required for response generation. ' +
|
|
244
|
+
'Pass a valid history array to the respond/stream method.', { params, phase: 'validation' });
|
|
238
245
|
}
|
|
239
246
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
240
247
|
const historyEvents = historyToEvents(simpleHistory);
|
|
@@ -246,7 +253,7 @@ export class ResponseModal {
|
|
|
246
253
|
try {
|
|
247
254
|
// Set current context and session in pipeline for consistency
|
|
248
255
|
this.responsePipeline.setContext(await this.agent.getContext());
|
|
249
|
-
this.responsePipeline.setCurrentSession(this.agent.
|
|
256
|
+
this.responsePipeline.setCurrentSession(this.agent.currentSession);
|
|
250
257
|
responseContext = await this.responsePipeline.prepareResponseContext({
|
|
251
258
|
contextOverride,
|
|
252
259
|
session: params.session ? cloneDeep(params.session) : undefined,
|
|
@@ -285,8 +292,8 @@ export class ResponseModal {
|
|
|
285
292
|
catch (error) {
|
|
286
293
|
throw ResponseGenerationError.fromError(error, 'step_preparation', params, { session, effectiveContext });
|
|
287
294
|
}
|
|
288
|
-
// PHASE 2: ROUTING + STEP SELECTION - Determine which
|
|
289
|
-
//
|
|
295
|
+
// PHASE 2: ROUTING + STEP SELECTION - Determine which flow and step to use
|
|
296
|
+
// Performs pre-extraction and step selection
|
|
290
297
|
let routingResult;
|
|
291
298
|
try {
|
|
292
299
|
routingResult = await this.handleUnifiedRoutingAndStepSelection({
|
|
@@ -303,14 +310,15 @@ export class ResponseModal {
|
|
|
303
310
|
effectiveContext,
|
|
304
311
|
session: routingResult.session,
|
|
305
312
|
history,
|
|
306
|
-
|
|
313
|
+
selectedFlow: routingResult.selectedFlow,
|
|
307
314
|
selectedStep: routingResult.selectedStep,
|
|
308
315
|
responseDirectives: routingResult.responseDirectives,
|
|
309
|
-
|
|
310
|
-
batchSteps: routingResult.batchSteps,
|
|
311
|
-
batchStoppedReason: routingResult.batchStoppedReason,
|
|
312
|
-
batchStoppedAtStep: routingResult.batchStoppedAtStep,
|
|
316
|
+
isFlowComplete: routingResult.isFlowComplete,
|
|
313
317
|
signal,
|
|
318
|
+
signalFirings: routingResult.signalFirings,
|
|
319
|
+
signalPreDirective: routingResult.signalPreDirective,
|
|
320
|
+
signalHalted: routingResult.signalHalted,
|
|
321
|
+
signalHaltReply: routingResult.signalHaltReply,
|
|
314
322
|
};
|
|
315
323
|
}
|
|
316
324
|
catch (error) {
|
|
@@ -327,27 +335,141 @@ export class ResponseModal {
|
|
|
327
335
|
*/
|
|
328
336
|
async handleUnifiedRoutingAndStepSelection(params) {
|
|
329
337
|
try {
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
//
|
|
338
|
+
// Create a fresh chain tracker for this turn (Requirement 22.1)
|
|
339
|
+
this.responsePipeline.createChainTracker();
|
|
340
|
+
// ROUTING SKIP OPTIMIZATION (Requirements 20.1, 20.2, 20.3):
|
|
341
|
+
// When the current step has collect fields AND pre-extraction populates at least
|
|
342
|
+
// one of those fields, skip FlowRouter.decideFlowAndStep for this turn.
|
|
343
|
+
const routingSkipResult = await this.attemptRoutingSkipForCollect(params);
|
|
344
|
+
if (routingSkipResult) {
|
|
345
|
+
// Even when routing is skipped, run pre-signal phase if processor is present
|
|
346
|
+
if (this.agent.signalProcessor) {
|
|
347
|
+
const signalResult = await this.responsePipeline.runPreSignalPhase(params.session, params.context, params.history);
|
|
348
|
+
// If signal halts, override the routing skip result
|
|
349
|
+
if (signalResult.mergedDirective?.halt) {
|
|
350
|
+
return {
|
|
351
|
+
...routingSkipResult,
|
|
352
|
+
session: signalResult.updatedSession,
|
|
353
|
+
signalFirings: signalResult.firings,
|
|
354
|
+
signalHalted: true,
|
|
355
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
// If signal has position fields, override routing skip result
|
|
359
|
+
if (hasDirectivePositionField(signalResult.mergedDirective)) {
|
|
360
|
+
return this.applySignalPositionDirective(signalResult, params);
|
|
361
|
+
}
|
|
362
|
+
// Non-position directive: propagate for pre-LLM augmentation
|
|
363
|
+
return {
|
|
364
|
+
...routingSkipResult,
|
|
365
|
+
session: signalResult.updatedSession,
|
|
366
|
+
signalFirings: signalResult.firings,
|
|
367
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return routingSkipResult;
|
|
371
|
+
}
|
|
372
|
+
// ── PARALLEL PRE-SIGNAL PHASE + ROUTING (Algorithm 5) ────────────────
|
|
373
|
+
// When signalProcessor is present, run pre-signals in parallel with routing.
|
|
374
|
+
// When absent, call the router directly (zero overhead, preserve current behavior).
|
|
375
|
+
if (this.agent.signalProcessor) {
|
|
376
|
+
// Run pre-signal phase in parallel with routing (Requirement 8.1)
|
|
377
|
+
const [signalResult, routingResult] = await Promise.all([
|
|
378
|
+
this.responsePipeline.runPreSignalPhase(params.session, params.context, params.history),
|
|
379
|
+
this.responsePipeline.handleRoutingAndStepSelection({
|
|
380
|
+
session: params.session,
|
|
381
|
+
history: params.history,
|
|
382
|
+
context: params.context,
|
|
383
|
+
signal: params.signal,
|
|
384
|
+
}),
|
|
385
|
+
]);
|
|
386
|
+
// ── Requirement 8.2: halt → discard routing, skip LLM ────────────
|
|
387
|
+
if (signalResult.mergedDirective?.halt) {
|
|
388
|
+
return {
|
|
389
|
+
selectedFlow: undefined,
|
|
390
|
+
selectedStep: undefined,
|
|
391
|
+
session: signalResult.updatedSession,
|
|
392
|
+
isFlowComplete: false,
|
|
393
|
+
signalFirings: signalResult.firings,
|
|
394
|
+
signalHalted: true,
|
|
395
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
// ── Requirement 8.3: position directive → discard routing, apply signal position ──
|
|
399
|
+
if (hasDirectivePositionField(signalResult.mergedDirective)) {
|
|
400
|
+
return this.applySignalPositionDirective(signalResult, params);
|
|
401
|
+
}
|
|
402
|
+
// ── Requirement 8.4: non-position directive → use routing, propagate augmentation ──
|
|
403
|
+
// ── Requirement 8.5: no directive → use routing as-is ─────────────
|
|
404
|
+
let updatedSession = signalResult.updatedSession;
|
|
405
|
+
// Apply data/context updates from signal to the routed session
|
|
406
|
+
if (signalResult.mergedDirective?.dataUpdate) {
|
|
407
|
+
updatedSession = mergeCollected(updatedSession, signalResult.mergedDirective.dataUpdate);
|
|
408
|
+
}
|
|
409
|
+
// Use routing result for flow/step, but carry signal session state
|
|
410
|
+
// Merge routing session changes on top of signal session
|
|
411
|
+
const routingSession = routingResult.session;
|
|
412
|
+
updatedSession = {
|
|
413
|
+
...updatedSession,
|
|
414
|
+
currentFlow: routingSession.currentFlow,
|
|
415
|
+
currentStep: routingSession.currentStep,
|
|
416
|
+
flowHistory: routingSession.flowHistory,
|
|
417
|
+
pendingDirective: routingSession.pendingDirective,
|
|
418
|
+
};
|
|
419
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
420
|
+
// PRE-EXTRACTION: same logic as below — extract data from user message
|
|
421
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
422
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
423
|
+
logger.debug(`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
|
|
424
|
+
const extractedData = await this.preExtractFlowData({
|
|
425
|
+
route: routingResult.selectedFlow,
|
|
426
|
+
history: params.history,
|
|
427
|
+
context: params.context,
|
|
428
|
+
session: updatedSession,
|
|
429
|
+
signal: params.signal,
|
|
430
|
+
});
|
|
431
|
+
if (extractedData && Object.keys(extractedData).length > 0) {
|
|
432
|
+
logger.debug(`[ResponseModal] Pre-extracted data:`, extractedData);
|
|
433
|
+
updatedSession = mergeCollected(updatedSession, extractedData);
|
|
434
|
+
await this.agent.updateCollectedData(extractedData);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// Determine next step
|
|
439
|
+
const stepResult = await this.responsePipeline.determineNextStep({
|
|
440
|
+
selectedFlow: routingResult.selectedFlow,
|
|
441
|
+
selectedStep: routingResult.selectedStep,
|
|
442
|
+
session: updatedSession,
|
|
443
|
+
isFlowComplete,
|
|
444
|
+
});
|
|
445
|
+
return {
|
|
446
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
447
|
+
selectedStep: stepResult.nextStep,
|
|
448
|
+
responseDirectives: routingResult.responseDirectives,
|
|
449
|
+
session: stepResult.session,
|
|
450
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
451
|
+
signalFirings: signalResult.firings,
|
|
452
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
// ── No signal processor: existing behavior (zero overhead) ────────────
|
|
333
456
|
const routingResult = await this.responsePipeline.handleRoutingAndStepSelection({
|
|
334
457
|
session: params.session,
|
|
335
|
-
history: params.history,
|
|
458
|
+
history: params.history,
|
|
336
459
|
context: params.context,
|
|
337
460
|
signal: params.signal,
|
|
338
461
|
});
|
|
339
462
|
let updatedSession = routingResult.session;
|
|
340
|
-
const
|
|
341
|
-
// PRE-EXTRACTION: If entering a
|
|
463
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
464
|
+
// PRE-EXTRACTION: If entering a flow that collects data, extract data from user message first
|
|
342
465
|
// This allows us to skip steps whose data is already provided
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
route: routingResult.selectedRoute,
|
|
466
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
467
|
+
// Always pre-extract when flow collects data (not just on new flow entry)
|
|
468
|
+
// This ensures step selection has the most up-to-date data
|
|
469
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
470
|
+
logger.debug(`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
|
|
471
|
+
const extractedData = await this.preExtractFlowData({
|
|
472
|
+
route: routingResult.selectedFlow,
|
|
351
473
|
history: params.history,
|
|
352
474
|
context: params.context,
|
|
353
475
|
session: updatedSession,
|
|
@@ -355,51 +477,27 @@ export class ResponseModal {
|
|
|
355
477
|
});
|
|
356
478
|
if (extractedData && Object.keys(extractedData).length > 0) {
|
|
357
479
|
logger.debug(`[ResponseModal] Pre-extracted data:`, extractedData);
|
|
358
|
-
//
|
|
480
|
+
// Merge pre-extracted data into session before step selection
|
|
359
481
|
updatedSession = mergeCollected(updatedSession, extractedData);
|
|
360
482
|
// Also update agent's collected data
|
|
361
483
|
await this.agent.updateCollectedData(extractedData);
|
|
362
484
|
}
|
|
363
485
|
}
|
|
364
486
|
}
|
|
365
|
-
// BATCH DETERMINATION: Use BatchExecutor to determine which steps can execute together
|
|
366
|
-
// Requirement 3.4: Pre-extraction results affect batch determination
|
|
367
|
-
let batchSteps;
|
|
368
|
-
let batchStoppedReason;
|
|
369
|
-
let batchStoppedAtStep;
|
|
370
|
-
if (routingResult.selectedRoute && !isRouteComplete) {
|
|
371
|
-
// Determine current step position for batch determination
|
|
372
|
-
const currentStep = routingResult.selectedStep ||
|
|
373
|
-
(updatedSession.currentStep ? routingResult.selectedRoute.getStep(updatedSession.currentStep.id) : undefined);
|
|
374
|
-
logger.debug(`[ResponseModal] Determining batch starting from step: ${currentStep?.id || 'initial'}`);
|
|
375
|
-
const batchResult = await this.batchExecutor.determineBatch({
|
|
376
|
-
route: routingResult.selectedRoute,
|
|
377
|
-
currentStep,
|
|
378
|
-
sessionData: updatedSession.data || {},
|
|
379
|
-
context: params.context,
|
|
380
|
-
maxSteps: this.agent.getAgentOptions().maxStepsPerBatch,
|
|
381
|
-
});
|
|
382
|
-
batchSteps = batchResult.steps;
|
|
383
|
-
batchStoppedReason = batchResult.stoppedReason;
|
|
384
|
-
batchStoppedAtStep = batchResult.stoppedAtStep;
|
|
385
|
-
logger.debug(`[ResponseModal] Batch determined: ${batchSteps.length} steps, stopped reason: ${batchStoppedReason}`);
|
|
386
|
-
}
|
|
387
487
|
// Determine next step using pipeline method for consistency
|
|
388
488
|
const stepResult = await this.responsePipeline.determineNextStep({
|
|
389
|
-
|
|
489
|
+
selectedFlow: routingResult.selectedFlow,
|
|
390
490
|
selectedStep: routingResult.selectedStep,
|
|
391
491
|
session: updatedSession, // Use updated session with pre-extracted data
|
|
392
|
-
|
|
492
|
+
isFlowComplete, // Use updated completion status
|
|
393
493
|
});
|
|
394
494
|
return {
|
|
395
|
-
|
|
495
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
396
496
|
selectedStep: stepResult.nextStep, // Use the determined next step
|
|
397
497
|
responseDirectives: routingResult.responseDirectives,
|
|
398
498
|
session: stepResult.session,
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
batchStoppedReason,
|
|
402
|
-
batchStoppedAtStep,
|
|
499
|
+
// If a branch changed the flow, the original isFlowComplete no longer applies
|
|
500
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
403
501
|
};
|
|
404
502
|
}
|
|
405
503
|
catch (error) {
|
|
@@ -407,31 +505,219 @@ export class ResponseModal {
|
|
|
407
505
|
}
|
|
408
506
|
}
|
|
409
507
|
/**
|
|
410
|
-
*
|
|
508
|
+
* Apply a signal's position directive (goTo, goToStep, complete, abort, reset).
|
|
509
|
+
* Discards routing result and uses the signal's position decision.
|
|
411
510
|
* @private
|
|
511
|
+
* @requirements 8.3
|
|
412
512
|
*/
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
513
|
+
applySignalPositionDirective(signalResult, _params) {
|
|
514
|
+
const directive = signalResult.mergedDirective;
|
|
515
|
+
let session = signalResult.updatedSession;
|
|
516
|
+
const flows = this.agent.getFlows();
|
|
517
|
+
let selectedFlow;
|
|
518
|
+
let selectedStep;
|
|
519
|
+
let isFlowComplete = false;
|
|
520
|
+
// Apply data updates if present alongside position
|
|
521
|
+
if (directive.dataUpdate) {
|
|
522
|
+
session = mergeCollected(session, directive.dataUpdate);
|
|
523
|
+
}
|
|
524
|
+
if (directive.goTo) {
|
|
525
|
+
const flowTarget = typeof directive.goTo === 'string'
|
|
526
|
+
? directive.goTo
|
|
527
|
+
: directive.goTo.flow ?? directive.goTo.step;
|
|
528
|
+
if (flowTarget) {
|
|
529
|
+
const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
|
|
530
|
+
if (targetFlow) {
|
|
531
|
+
session = enterFlow(session, targetFlow.id, targetFlow.title);
|
|
532
|
+
selectedFlow = targetFlow;
|
|
533
|
+
if (typeof directive.goTo === 'object' && directive.goTo.step) {
|
|
534
|
+
const targetStep = targetFlow.getStep(directive.goTo.step);
|
|
535
|
+
if (targetStep) {
|
|
536
|
+
session = enterStep(session, targetStep.id, targetStep.description);
|
|
537
|
+
selectedStep = targetStep;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
logger.warn(`[Signals] Pre-phase goTo target not found: "${flowTarget}". Falling back to no flow.`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
else if (directive.goToStep) {
|
|
547
|
+
const stepTarget = typeof directive.goToStep === 'string'
|
|
548
|
+
? directive.goToStep
|
|
549
|
+
: directive.goToStep.step;
|
|
550
|
+
const flowTarget = typeof directive.goToStep === 'object'
|
|
551
|
+
? directive.goToStep.flow
|
|
552
|
+
: undefined;
|
|
553
|
+
if (flowTarget) {
|
|
554
|
+
const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
|
|
555
|
+
if (targetFlow) {
|
|
556
|
+
session = enterFlow(session, targetFlow.id, targetFlow.title);
|
|
557
|
+
selectedFlow = targetFlow;
|
|
558
|
+
const targetStep = targetFlow.getStep(stepTarget);
|
|
559
|
+
if (targetStep) {
|
|
560
|
+
session = enterStep(session, targetStep.id, targetStep.description);
|
|
561
|
+
selectedStep = targetStep;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
else if (session.currentFlow) {
|
|
566
|
+
const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
|
|
567
|
+
if (currentFlow) {
|
|
568
|
+
selectedFlow = currentFlow;
|
|
569
|
+
const targetStep = currentFlow.getStep(stepTarget);
|
|
570
|
+
if (targetStep) {
|
|
571
|
+
session = enterStep(session, targetStep.id, targetStep.description);
|
|
572
|
+
selectedStep = targetStep;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
else if (directive.complete) {
|
|
578
|
+
isFlowComplete = true;
|
|
579
|
+
}
|
|
580
|
+
else if (directive.abort) {
|
|
581
|
+
// Abort — no flow, session cleared or marked
|
|
582
|
+
isFlowComplete = true;
|
|
583
|
+
}
|
|
584
|
+
else if (directive.reset) {
|
|
585
|
+
if (session.currentFlow) {
|
|
586
|
+
const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
|
|
587
|
+
if (currentFlow) {
|
|
588
|
+
selectedFlow = currentFlow;
|
|
589
|
+
const resetStep = typeof directive.reset === 'object' && directive.reset.step
|
|
590
|
+
? directive.reset.step
|
|
591
|
+
: undefined;
|
|
592
|
+
if (resetStep) {
|
|
593
|
+
const targetStep = currentFlow.getStep(resetStep);
|
|
594
|
+
if (targetStep) {
|
|
595
|
+
session = enterStep(session, targetStep.id, targetStep.description);
|
|
596
|
+
selectedStep = targetStep;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
const initialStep = currentFlow.initialStep;
|
|
601
|
+
session = enterStep(session, initialStep.id, initialStep.description);
|
|
602
|
+
selectedStep = initialStep;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
selectedFlow,
|
|
609
|
+
selectedStep,
|
|
610
|
+
session,
|
|
611
|
+
isFlowComplete,
|
|
612
|
+
signalFirings: signalResult.firings,
|
|
613
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Routing skip optimization (Requirements 20.1, 20.2, 20.3):
|
|
618
|
+
* When the current step declares `collect` fields AND pre-extraction populates
|
|
619
|
+
* at least one of those fields from the user's message, skip routing for this turn.
|
|
620
|
+
*
|
|
621
|
+
* Returns the routing result if the skip applies, or undefined to fall through
|
|
622
|
+
* to normal routing.
|
|
623
|
+
* @private
|
|
624
|
+
*/
|
|
625
|
+
async attemptRoutingSkipForCollect(params) {
|
|
626
|
+
const { session } = params;
|
|
627
|
+
// Only applies when we already have a current flow and step
|
|
628
|
+
if (!session.currentFlow || !session.currentStep) {
|
|
629
|
+
return undefined;
|
|
630
|
+
}
|
|
631
|
+
// Also skip this optimization if there's a pending directive (it takes priority)
|
|
632
|
+
if (session.pendingDirective) {
|
|
633
|
+
return undefined;
|
|
634
|
+
}
|
|
635
|
+
// Look up the actual Flow and Step objects to access `collect`
|
|
636
|
+
const currentFlow = this.agent.getFlows().find((f) => f.id === session.currentFlow?.id);
|
|
637
|
+
if (!currentFlow) {
|
|
638
|
+
return undefined;
|
|
639
|
+
}
|
|
640
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
641
|
+
if (!currentStep || !currentStep.collect || currentStep.collect.length === 0) {
|
|
642
|
+
return undefined;
|
|
643
|
+
}
|
|
644
|
+
// We have a step with collect fields. Run pre-extraction to see if the
|
|
645
|
+
// user's message populates any of them.
|
|
646
|
+
const collectFields = currentStep.collect;
|
|
647
|
+
// Snapshot current data for comparison
|
|
648
|
+
const dataBefore = { ...session.data };
|
|
649
|
+
// Run pre-extraction against the current flow
|
|
650
|
+
const extractedData = await this.preExtractFlowData({
|
|
651
|
+
route: currentFlow,
|
|
652
|
+
history: params.history,
|
|
653
|
+
context: params.context,
|
|
654
|
+
session,
|
|
655
|
+
signal: params.signal,
|
|
656
|
+
});
|
|
657
|
+
if (!extractedData || Object.keys(extractedData).length === 0) {
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
// Determine which collect fields were newly populated by pre-extraction
|
|
661
|
+
const populatedCollectFields = [];
|
|
662
|
+
for (const field of collectFields) {
|
|
663
|
+
const key = field;
|
|
664
|
+
const hadValue = dataBefore[field] !== undefined && dataBefore[field] !== null;
|
|
665
|
+
const hasNewValue = extractedData[field] !== undefined && extractedData[field] !== null;
|
|
666
|
+
if (hasNewValue && !hadValue) {
|
|
667
|
+
populatedCollectFields.push(key);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (populatedCollectFields.length === 0) {
|
|
671
|
+
// Pre-extraction didn't populate any declared collect field — no skip
|
|
672
|
+
return undefined;
|
|
673
|
+
}
|
|
674
|
+
// ROUTING SKIP: pre-extraction populated collect fields → retain current flow/step
|
|
675
|
+
logger.debug(`[ResponseModal] Routing skip: pre-extraction populated collect fields [${populatedCollectFields.join(', ')}] for step "${currentStep.id}" — skipping FlowRouter`);
|
|
676
|
+
// Merge extracted data into session
|
|
677
|
+
const updatedSession = mergeCollected(session, extractedData);
|
|
678
|
+
await this.agent.updateCollectedData(extractedData);
|
|
679
|
+
// Determine next step using pipeline method for consistency
|
|
680
|
+
// Pass the current flow/step as the routing result (retained)
|
|
681
|
+
const stepResult = await this.responsePipeline.determineNextStep({
|
|
682
|
+
selectedFlow: currentFlow,
|
|
683
|
+
selectedStep: currentStep,
|
|
684
|
+
session: updatedSession,
|
|
685
|
+
isFlowComplete: false,
|
|
686
|
+
});
|
|
687
|
+
return {
|
|
688
|
+
selectedFlow: stepResult.flowChanged || currentFlow,
|
|
689
|
+
selectedStep: stepResult.nextStep,
|
|
690
|
+
responseDirectives: undefined,
|
|
691
|
+
session: stepResult.session,
|
|
692
|
+
isFlowComplete: stepResult.flowChanged ? false : false,
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Check if a flow should pre-extract data before determining the initial step
|
|
697
|
+
* @private
|
|
698
|
+
*/
|
|
699
|
+
shouldPreExtractData(flow) {
|
|
700
|
+
// Pre-extract if flow has declared required or optional fields
|
|
701
|
+
if (flow.requiredFields && flow.requiredFields.length > 0) {
|
|
416
702
|
return true;
|
|
417
703
|
}
|
|
418
|
-
if (
|
|
704
|
+
if (flow.optionalFields && flow.optionalFields.length > 0) {
|
|
419
705
|
return true;
|
|
420
706
|
}
|
|
421
|
-
// Pre-extract if any step in the
|
|
422
|
-
const steps =
|
|
707
|
+
// Pre-extract if any step in the flow collects data
|
|
708
|
+
const steps = flow.getAllSteps();
|
|
423
709
|
const hasDataCollectionSteps = steps.some(step => step.collect && step.collect.length > 0);
|
|
424
710
|
return hasDataCollectionSteps;
|
|
425
711
|
}
|
|
426
712
|
/**
|
|
427
|
-
* Pre-extract data from user message when entering a
|
|
713
|
+
* Pre-extract data from user message when entering a flow
|
|
428
714
|
* This allows skipping steps whose data is already provided
|
|
429
715
|
* @private
|
|
430
716
|
*/
|
|
431
|
-
async
|
|
432
|
-
const { route, history, signal } = params;
|
|
433
|
-
// Build a schema for data extraction based on
|
|
434
|
-
const extractionSchema = this.agent.
|
|
717
|
+
async preExtractFlowData(params) {
|
|
718
|
+
const { route: flow, history, signal } = params;
|
|
719
|
+
// Build a schema for data extraction based on flow's fields
|
|
720
|
+
const extractionSchema = this.agent.schema;
|
|
435
721
|
if (!extractionSchema) {
|
|
436
722
|
logger.warn(`[ResponseModal] No schema available for pre-extraction`);
|
|
437
723
|
return {};
|
|
@@ -448,11 +734,11 @@ export class ResponseModal {
|
|
|
448
734
|
`Extract data for these fields if present:`,
|
|
449
735
|
];
|
|
450
736
|
// Add field descriptions
|
|
451
|
-
if (
|
|
452
|
-
extractionPrompt.push(`Required fields: ${
|
|
737
|
+
if (flow.requiredFields) {
|
|
738
|
+
extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
|
|
453
739
|
}
|
|
454
|
-
if (
|
|
455
|
-
extractionPrompt.push(`Optional fields: ${
|
|
740
|
+
if (flow.optionalFields) {
|
|
741
|
+
extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
|
|
456
742
|
}
|
|
457
743
|
extractionPrompt.push(``, `Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`);
|
|
458
744
|
// Convert Event[] to HistoryItem[] for provider call
|
|
@@ -485,98 +771,176 @@ export class ResponseModal {
|
|
|
485
771
|
* @private
|
|
486
772
|
*/
|
|
487
773
|
async generateUnifiedResponse(responseContext) {
|
|
488
|
-
const { effectiveContext, session: initialSession, history,
|
|
774
|
+
const { effectiveContext, session: initialSession, history, selectedFlow, selectedStep, responseDirectives, isFlowComplete, signal, signalFirings: preSignalFirings, signalPreDirective, signalHalted, signalHaltReply, } = responseContext;
|
|
489
775
|
let session = initialSession;
|
|
490
|
-
//
|
|
776
|
+
// Accumulator for signal firings across both phases (fire order)
|
|
777
|
+
const signalFirings = [...(preSignalFirings || [])];
|
|
778
|
+
// Get last user message (needed for both flow and completion handling)
|
|
491
779
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
492
780
|
const historyEvents = historyToEvents(history);
|
|
781
|
+
// ── SIGNAL HALT (Requirement 8.2) ─────────────────────────────────────
|
|
782
|
+
// Pre-signal phase emitted halt → skip LLM call entirely.
|
|
783
|
+
if (signalHalted) {
|
|
784
|
+
const haltMessage = signalHaltReply || '';
|
|
785
|
+
// Run post-signal phase even on halt (post-phase sees complete turn context)
|
|
786
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
|
|
787
|
+
session = postResult.updatedSession;
|
|
788
|
+
signalFirings.push(...postResult.firings);
|
|
789
|
+
// Apply post-phase position directive as pendingDirective (Requirement 9.3)
|
|
790
|
+
if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
|
|
791
|
+
session = { ...session, pendingDirective: postResult.mergedDirective };
|
|
792
|
+
}
|
|
793
|
+
await this.finalizeSession(session, effectiveContext);
|
|
794
|
+
return {
|
|
795
|
+
message: haltMessage,
|
|
796
|
+
session,
|
|
797
|
+
toolCalls: undefined,
|
|
798
|
+
isFlowComplete: false,
|
|
799
|
+
executedSteps: [],
|
|
800
|
+
stoppedReason: signalHaltReply ? 'reply' : 'halt',
|
|
801
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
802
|
+
};
|
|
803
|
+
}
|
|
493
804
|
let message;
|
|
494
805
|
let toolCalls = undefined;
|
|
495
806
|
let executedSteps;
|
|
496
807
|
let stoppedReason;
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
historyEvents,
|
|
808
|
+
let appliedInstructions;
|
|
809
|
+
if (selectedFlow && !isFlowComplete) {
|
|
810
|
+
// AUTO-CHAIN: Walk consecutive auto-steps before any LLM work.
|
|
811
|
+
// If the current step is auto, the executor advances through it (and any
|
|
812
|
+
// subsequent auto-steps) until an interactive step or terminal condition.
|
|
813
|
+
let resolvedStep = selectedStep;
|
|
814
|
+
const currentStepInstance = session.currentStep
|
|
815
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
816
|
+
: selectedStep;
|
|
817
|
+
if (currentStepInstance?.auto) {
|
|
818
|
+
const autoChainExecutor = new AutoChainExecutor({
|
|
819
|
+
maxAutoStepsPerTurn: this.agent.maxAutoStepsPerTurn,
|
|
510
820
|
});
|
|
511
|
-
|
|
512
|
-
toolCalls = batchResult.toolCalls;
|
|
513
|
-
session = batchResult.session;
|
|
514
|
-
executedSteps = batchResult.executedSteps;
|
|
515
|
-
stoppedReason = batchStoppedReason;
|
|
516
|
-
}
|
|
517
|
-
else {
|
|
518
|
-
// SINGLE STEP EXECUTION: Fall back to single-step processing
|
|
519
|
-
// This happens when batch determination returns empty (first step needs input)
|
|
520
|
-
const result = await this.processRouteResponse({
|
|
521
|
-
selectedRoute,
|
|
522
|
-
selectedStep,
|
|
523
|
-
responseDirectives,
|
|
821
|
+
const autoResult = await autoChainExecutor.run({
|
|
524
822
|
session,
|
|
525
|
-
history,
|
|
526
823
|
context: effectiveContext,
|
|
527
|
-
|
|
528
|
-
signal,
|
|
824
|
+
flow: selectedFlow,
|
|
529
825
|
});
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
executedSteps = [
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
826
|
+
session = autoResult.session;
|
|
827
|
+
// Handle halt: emit verbatim reply, persist, return — no LLM call.
|
|
828
|
+
if (autoResult.stoppedReason === 'halt') {
|
|
829
|
+
message = autoResult.mergedDirective?.reply || '';
|
|
830
|
+
stoppedReason = 'halt';
|
|
831
|
+
executedSteps = [];
|
|
832
|
+
await this.finalizeSession(session, effectiveContext);
|
|
833
|
+
return {
|
|
834
|
+
message,
|
|
835
|
+
session,
|
|
836
|
+
toolCalls: undefined,
|
|
837
|
+
isFlowComplete: false,
|
|
838
|
+
executedSteps,
|
|
839
|
+
stoppedReason,
|
|
840
|
+
};
|
|
539
841
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
842
|
+
// Handle flow completion or cross-flow redirect from auto-chain.
|
|
843
|
+
// The auto-chain ended without resolving to an interactive step.
|
|
844
|
+
// Possible reasons: last_step (no successor), completed (explicit
|
|
845
|
+
// complete directive), or goto (cross-flow redirect).
|
|
846
|
+
if (autoResult.stoppedReason === 'last_step' || autoResult.stoppedReason === 'completed' || autoResult.stoppedReason === 'goto') {
|
|
847
|
+
logger.debug(`[ResponseModal] Auto-chain ended with ${autoResult.stoppedReason}`);
|
|
848
|
+
session = await this.applyFlowCompletion({
|
|
849
|
+
selectedFlow,
|
|
850
|
+
session,
|
|
851
|
+
context: effectiveContext,
|
|
852
|
+
history,
|
|
853
|
+
});
|
|
854
|
+
await this.finalizeSession(session, effectiveContext);
|
|
855
|
+
return {
|
|
856
|
+
message: '',
|
|
857
|
+
session,
|
|
858
|
+
toolCalls: undefined,
|
|
859
|
+
isFlowComplete: true,
|
|
860
|
+
executedSteps: [],
|
|
861
|
+
stoppedReason: autoResult.stoppedReason,
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
// Normal case: auto-chain resolved to an interactive step.
|
|
865
|
+
resolvedStep = autoResult.resolvedStep;
|
|
559
866
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
867
|
+
// SINGLE STEP EXECUTION: Process the resolved interactive step.
|
|
868
|
+
// The auto-chain (if it ran) already walked auto-steps. Only the
|
|
869
|
+
// interactive step remains for the LLM call.
|
|
870
|
+
const result = await this.processFlowResponse({
|
|
871
|
+
selectedFlow,
|
|
872
|
+
selectedStep: resolvedStep,
|
|
873
|
+
responseDirectives,
|
|
874
|
+
session,
|
|
875
|
+
history,
|
|
876
|
+
context: effectiveContext,
|
|
877
|
+
historyEvents,
|
|
878
|
+
signal,
|
|
879
|
+
// Propagate signal pre-directive's appendPrompt for this turn's LLM call (Requirement 8.4)
|
|
880
|
+
transientAppendage: signalPreDirective?.appendPrompt,
|
|
881
|
+
// Merge signal pre-directive (halt/reply/injectTools) into the pre-LLM bus
|
|
882
|
+
mergedPreDirective: signalPreDirective,
|
|
883
|
+
});
|
|
884
|
+
message = result.message;
|
|
885
|
+
toolCalls = result.toolCalls;
|
|
886
|
+
session = result.session;
|
|
887
|
+
appliedInstructions = result.appliedInstructions;
|
|
888
|
+
// Track executed step for single-step execution
|
|
889
|
+
if (resolvedStep) {
|
|
890
|
+
executedSteps = [{
|
|
891
|
+
id: resolvedStep.id,
|
|
892
|
+
flowId: selectedFlow.id,
|
|
893
|
+
}];
|
|
566
894
|
}
|
|
895
|
+
// Use stoppedReason from processFlowResponse if set (halt/reply),
|
|
896
|
+
// otherwise default to 'needs_input' for normal LLM responses.
|
|
897
|
+
stoppedReason = result.stoppedReason || 'needs_input';
|
|
898
|
+
}
|
|
899
|
+
else if (isFlowComplete && selectedFlow) {
|
|
900
|
+
// Flow completion path: pure state transition, no LLM call.
|
|
901
|
+
// The framework emits no message of its own.
|
|
902
|
+
// stoppedReason is 'last_step' because this completion was detected by
|
|
903
|
+
// implicit terminus (no successor or all successors skipped), not by an
|
|
904
|
+
// explicit `complete` directive.
|
|
905
|
+
logger.debug(`[ResponseModal] Releasing session to idle for completed flow: ${selectedFlow.title}`);
|
|
906
|
+
session = await this.applyFlowCompletion({
|
|
907
|
+
selectedFlow,
|
|
908
|
+
session,
|
|
909
|
+
context: effectiveContext,
|
|
910
|
+
history,
|
|
911
|
+
});
|
|
912
|
+
message = '';
|
|
913
|
+
stoppedReason = 'last_step';
|
|
914
|
+
executedSteps = [];
|
|
567
915
|
}
|
|
568
916
|
else {
|
|
569
|
-
// Fallback: No
|
|
570
|
-
|
|
917
|
+
// Fallback: No flows defined, generate a simple response
|
|
918
|
+
const fallbackResult = await this.generateFallbackResponse({
|
|
571
919
|
history,
|
|
572
920
|
context: effectiveContext,
|
|
573
921
|
session,
|
|
574
922
|
});
|
|
923
|
+
message = fallbackResult.message;
|
|
924
|
+
appliedInstructions = fallbackResult.appliedInstructions;
|
|
575
925
|
// For fallback responses, set empty executedSteps and no stoppedReason
|
|
576
|
-
// since there's no
|
|
926
|
+
// since there's no flow/step execution happening
|
|
577
927
|
executedSteps = [];
|
|
578
928
|
stoppedReason = undefined;
|
|
579
929
|
}
|
|
930
|
+
// POST-SIGNAL PHASE (Requirement 9.1, 9.2, 9.3, 9.4)
|
|
931
|
+
// Runs after finalize/onComplete and before session persistence.
|
|
932
|
+
// Post-phase signals see the complete turn result: assistant message in
|
|
933
|
+
// history, collected data, tool results.
|
|
934
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
|
|
935
|
+
session = postResult.updatedSession;
|
|
936
|
+
// Append post-phase firings to the accumulator (preserves fire order)
|
|
937
|
+
signalFirings.push(...postResult.firings);
|
|
938
|
+
// Requirement 9.3: Post-phase position directive sets session.pendingDirective
|
|
939
|
+
// (no mid-turn re-entry per D6 decision). Pre-LLM-only fields are already
|
|
940
|
+
// dropped inside runPostSignalPhase per Phase 4.5.
|
|
941
|
+
if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
|
|
942
|
+
session = { ...session, pendingDirective: postResult.mergedDirective };
|
|
943
|
+
}
|
|
580
944
|
// Ensure response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
581
945
|
// - executedSteps: array of steps executed (empty array if none)
|
|
582
946
|
// - stoppedReason: why execution stopped (undefined for fallback)
|
|
@@ -585,400 +949,30 @@ export class ResponseModal {
|
|
|
585
949
|
message,
|
|
586
950
|
session,
|
|
587
951
|
toolCalls,
|
|
588
|
-
|
|
589
|
-
executedSteps: executedSteps || [],
|
|
590
|
-
stoppedReason,
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
* Execute a batch of steps with a single LLM call
|
|
595
|
-
*
|
|
596
|
-
* This method:
|
|
597
|
-
* 1. Executes all prepare hooks for steps in the batch (in order)
|
|
598
|
-
* 2. Builds a combined prompt using BatchPromptBuilder
|
|
599
|
-
* 3. Makes a single LLM call
|
|
600
|
-
* 4. Collects data from the response for all steps
|
|
601
|
-
* 5. Executes all finalize hooks for steps in the batch (in order)
|
|
602
|
-
*
|
|
603
|
-
* @private
|
|
604
|
-
* **Validates: Requirements 1.1, 4.4, 5.1, 5.2**
|
|
605
|
-
*/
|
|
606
|
-
async executeBatchResponse(params) {
|
|
607
|
-
const { selectedRoute, batchSteps, history, context, historyEvents, signal } = params;
|
|
608
|
-
let session = params.session;
|
|
609
|
-
logger.debug(`[ResponseModal] Starting batch execution for ${batchSteps.length} steps`);
|
|
610
|
-
// Create hook executor function
|
|
611
|
-
const executeHook = async (hook, hookContext, data, step) => {
|
|
612
|
-
// Find the route for this step
|
|
613
|
-
const route = selectedRoute;
|
|
614
|
-
// Convert StepOptions to Step if needed for executePrepareFinalize
|
|
615
|
-
const stepInstance = step?.id ? route.getStep(step.id) : undefined;
|
|
616
|
-
await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
|
|
617
|
-
};
|
|
618
|
-
// PHASE 1: Execute all prepare hooks (Requirement 5.1)
|
|
619
|
-
logger.debug(`[ResponseModal] Executing prepare hooks for batch`);
|
|
620
|
-
const prepareResult = await this.batchExecutor.executePrepareHooks({
|
|
621
|
-
steps: batchSteps,
|
|
622
|
-
context,
|
|
623
|
-
data: session.data,
|
|
624
|
-
executeHook,
|
|
625
|
-
});
|
|
626
|
-
if (!prepareResult.success) {
|
|
627
|
-
// Prepare hook failed - return error response
|
|
628
|
-
logger.error(`[ResponseModal] Prepare hook failed:`, prepareResult.error);
|
|
629
|
-
throw new ResponseGenerationError(`Prepare hook failed: ${prepareResult.error?.message}`, {
|
|
630
|
-
phase: 'prepare_hooks',
|
|
631
|
-
context: {
|
|
632
|
-
stepId: prepareResult.error?.stepId,
|
|
633
|
-
executedSteps: prepareResult.executedSteps,
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
// PHASE 2: Build combined prompt using BatchPromptBuilder (Requirement 4.4)
|
|
638
|
-
logger.debug(`[ResponseModal] Building batch prompt`);
|
|
639
|
-
const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
|
|
640
|
-
steps: batchSteps,
|
|
641
|
-
route: selectedRoute,
|
|
642
|
-
history: historyEvents,
|
|
643
|
-
context,
|
|
644
|
-
session,
|
|
645
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
646
|
-
});
|
|
647
|
-
logger.debug(`[ResponseModal] Batch prompt built with ${batchPromptResult.stepCount} steps, collecting: ${batchPromptResult.collectFields.join(', ')}`);
|
|
648
|
-
// Build response schema for batch (includes all collect fields)
|
|
649
|
-
const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
|
|
650
|
-
// Collect available tools for AI (from all steps in batch)
|
|
651
|
-
const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
|
|
652
|
-
// PHASE 3: Make single LLM call (Requirement 4.4)
|
|
653
|
-
logger.debug(`[ResponseModal] Making LLM call for batch`);
|
|
654
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
655
|
-
const result = await agentOptions.provider.generateMessage({
|
|
656
|
-
prompt: batchPromptResult.prompt,
|
|
657
|
-
history, // Use HistoryItem[] for AI provider
|
|
658
|
-
context,
|
|
659
|
-
tools: availableTools,
|
|
660
|
-
signal,
|
|
661
|
-
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_response" } : undefined,
|
|
662
|
-
});
|
|
663
|
-
let message = result.structured?.message || result.message;
|
|
664
|
-
let toolCalls = result.structured?.toolCalls;
|
|
665
|
-
logger.debug(`[ResponseModal] LLM response received for batch`);
|
|
666
|
-
// Execute tools if any
|
|
667
|
-
if (toolCalls && toolCalls.length > 0) {
|
|
668
|
-
const toolResult = await this.executeUnifiedToolLoop({
|
|
669
|
-
toolCalls,
|
|
670
|
-
context,
|
|
671
|
-
session,
|
|
672
|
-
history,
|
|
673
|
-
selectedRoute,
|
|
674
|
-
responsePrompt: batchPromptResult.prompt,
|
|
675
|
-
availableTools,
|
|
676
|
-
responseSchema,
|
|
677
|
-
signal,
|
|
678
|
-
});
|
|
679
|
-
session = toolResult.session;
|
|
680
|
-
toolCalls = toolResult.finalToolCalls;
|
|
681
|
-
if (toolResult.finalMessage) {
|
|
682
|
-
message = toolResult.finalMessage;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
// PHASE 4: Collect data from response for all steps (Requirement 6.1, 6.2, 6.3)
|
|
686
|
-
logger.debug(`[ResponseModal] Collecting batch data`);
|
|
687
|
-
const collectResult = this.batchExecutor.collectBatchData({
|
|
688
|
-
steps: batchSteps,
|
|
689
|
-
llmResponse: result.structured || {},
|
|
690
|
-
session,
|
|
691
|
-
schema: this.agent.getSchema(),
|
|
692
|
-
});
|
|
693
|
-
session = collectResult.session;
|
|
694
|
-
if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
|
|
695
|
-
// Update agent's collected data
|
|
696
|
-
await this.agent.updateCollectedData(collectResult.collectedData);
|
|
697
|
-
logger.debug(`[ResponseModal] Batch collected data:`, collectResult.collectedData);
|
|
698
|
-
}
|
|
699
|
-
if (collectResult.validationErrors && collectResult.validationErrors.length > 0) {
|
|
700
|
-
logger.warn(`[ResponseModal] Batch data validation errors:`, collectResult.validationErrors);
|
|
701
|
-
}
|
|
702
|
-
// Update session to final step position
|
|
703
|
-
const lastStep = batchSteps[batchSteps.length - 1];
|
|
704
|
-
if (lastStep?.id) {
|
|
705
|
-
session = enterStep(session, lastStep.id, lastStep.description);
|
|
706
|
-
logger.debug(`[ResponseModal] Updated session to final batch step: ${lastStep.id}`);
|
|
707
|
-
}
|
|
708
|
-
// PHASE 5: Execute all finalize hooks (Requirement 5.2)
|
|
709
|
-
logger.debug(`[ResponseModal] Executing finalize hooks for batch`);
|
|
710
|
-
const finalizeResult = await this.batchExecutor.executeFinalizeHooks({
|
|
711
|
-
steps: batchSteps,
|
|
712
|
-
context,
|
|
713
|
-
data: session.data,
|
|
714
|
-
executeHook,
|
|
715
|
-
});
|
|
716
|
-
if (finalizeResult.errors && finalizeResult.errors.length > 0) {
|
|
717
|
-
// Log finalize errors but don't fail (Requirement 5.5)
|
|
718
|
-
logger.warn(`[ResponseModal] Some finalize hooks failed:`, finalizeResult.errors);
|
|
719
|
-
}
|
|
720
|
-
// Build executed steps list
|
|
721
|
-
const executedSteps = batchSteps
|
|
722
|
-
.filter(step => step.id)
|
|
723
|
-
.map(step => ({
|
|
724
|
-
id: step.id,
|
|
725
|
-
routeId: selectedRoute.id,
|
|
726
|
-
}));
|
|
727
|
-
logger.debug(`[ResponseModal] Batch execution complete. Executed ${executedSteps.length} steps`);
|
|
728
|
-
return {
|
|
729
|
-
message,
|
|
730
|
-
toolCalls,
|
|
731
|
-
session,
|
|
732
|
-
executedSteps,
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Build response schema for batch execution
|
|
737
|
-
* @private
|
|
738
|
-
*/
|
|
739
|
-
buildBatchResponseSchema(collectFields) {
|
|
740
|
-
const properties = {
|
|
741
|
-
message: {
|
|
742
|
-
type: "string",
|
|
743
|
-
description: "Natural, conversational response directed at the user. Must NOT contain field names, raw data, or internal information.",
|
|
744
|
-
},
|
|
745
|
-
};
|
|
746
|
-
const agentSchema = this.agent.getSchema();
|
|
747
|
-
// Add collect fields to schema, using agent schema definitions when available
|
|
748
|
-
for (const field of collectFields) {
|
|
749
|
-
if (agentSchema?.properties && agentSchema.properties[field]) {
|
|
750
|
-
properties[field] = agentSchema.properties[field];
|
|
751
|
-
}
|
|
752
|
-
else {
|
|
753
|
-
// Dynamic fallback when no agent schema is defined
|
|
754
|
-
properties[field] = {
|
|
755
|
-
type: "string",
|
|
756
|
-
description: `Collected value for ${field}`,
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
return {
|
|
761
|
-
type: "object",
|
|
762
|
-
properties,
|
|
763
|
-
required: ["message"],
|
|
764
|
-
additionalProperties: true,
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Collect available tools from all steps in the batch
|
|
769
|
-
* @private
|
|
770
|
-
*/
|
|
771
|
-
collectBatchAvailableTools(route, batchSteps) {
|
|
772
|
-
const availableTools = new Map();
|
|
773
|
-
// Add agent-level tools
|
|
774
|
-
this.agent.getTools().forEach((tool) => {
|
|
775
|
-
availableTools.set(tool.id, tool);
|
|
776
|
-
});
|
|
777
|
-
// Add route-level tools
|
|
778
|
-
route.getTools().forEach((tool) => {
|
|
779
|
-
availableTools.set(tool.id, tool);
|
|
780
|
-
});
|
|
781
|
-
// Add step-level tools from all batch steps
|
|
782
|
-
for (const step of batchSteps) {
|
|
783
|
-
if (step.tools) {
|
|
784
|
-
for (const toolRef of step.tools) {
|
|
785
|
-
if (typeof toolRef === "string") {
|
|
786
|
-
// Reference to registered tool - already in availableTools
|
|
787
|
-
}
|
|
788
|
-
else if (typeof toolRef === 'object' && 'id' in toolRef && toolRef.id) {
|
|
789
|
-
// Inline tool definition
|
|
790
|
-
availableTools.set(toolRef.id, toolRef);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
// Convert to the format expected by AI providers
|
|
796
|
-
return Array.from(availableTools.values()).map((tool) => ({
|
|
797
|
-
id: tool.id,
|
|
798
|
-
name: tool.name || tool.id,
|
|
799
|
-
description: tool.description,
|
|
800
|
-
parameters: tool.parameters,
|
|
801
|
-
}));
|
|
802
|
-
}
|
|
803
|
-
/**
|
|
804
|
-
* Unified streaming response generation
|
|
805
|
-
* @private
|
|
806
|
-
*/
|
|
807
|
-
async *generateUnifiedStreamingResponse(responseContext) {
|
|
808
|
-
const { effectiveContext, session: initialSession, history, selectedRoute, selectedStep, responseDirectives, isRouteComplete, batchSteps, batchStoppedReason, } = responseContext;
|
|
809
|
-
const session = initialSession;
|
|
810
|
-
// Get last user message (needed for both route and completion handling)
|
|
811
|
-
// Convert HistoryItem[] to Event[] for internal processing
|
|
812
|
-
const historyEvents = historyToEvents(history);
|
|
813
|
-
if (selectedRoute && !isRouteComplete) {
|
|
814
|
-
// Check if we have batch steps to execute
|
|
815
|
-
if (batchSteps && batchSteps.length > 0) {
|
|
816
|
-
// BATCH EXECUTION: Execute multiple steps with streaming
|
|
817
|
-
// Note: For streaming, we still use batch execution but stream the response
|
|
818
|
-
logger.debug(`[ResponseModal] Streaming batch execution for ${batchSteps.length} steps`);
|
|
819
|
-
yield* this.streamBatchResponse({
|
|
820
|
-
selectedRoute,
|
|
821
|
-
batchSteps,
|
|
822
|
-
responseDirectives,
|
|
823
|
-
session,
|
|
824
|
-
history,
|
|
825
|
-
context: effectiveContext,
|
|
826
|
-
historyEvents,
|
|
827
|
-
batchStoppedReason,
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
else {
|
|
831
|
-
// SINGLE STEP EXECUTION: Fall back to single-step streaming
|
|
832
|
-
yield* this.processRouteStreamingResponse({
|
|
833
|
-
selectedRoute,
|
|
834
|
-
selectedStep,
|
|
835
|
-
responseDirectives,
|
|
836
|
-
session,
|
|
837
|
-
history,
|
|
838
|
-
context: effectiveContext,
|
|
839
|
-
historyEvents,
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
else if (isRouteComplete && selectedRoute) {
|
|
844
|
-
// Handle route completion streaming
|
|
845
|
-
yield* this.streamRouteCompletion({
|
|
846
|
-
selectedRoute,
|
|
847
|
-
session,
|
|
848
|
-
context: effectiveContext,
|
|
849
|
-
history,
|
|
850
|
-
historyEvents,
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
else {
|
|
854
|
-
// Fallback: No routes defined, stream a simple response
|
|
855
|
-
yield* this.streamFallbackResponse({
|
|
856
|
-
history,
|
|
857
|
-
context: effectiveContext,
|
|
858
|
-
session,
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
/**
|
|
863
|
-
* Stream a batch response with multiple steps
|
|
864
|
-
*
|
|
865
|
-
* Similar to executeBatchResponse but streams the LLM response.
|
|
866
|
-
*
|
|
867
|
-
* @private
|
|
868
|
-
*/
|
|
869
|
-
async *streamBatchResponse(params) {
|
|
870
|
-
const { selectedRoute, batchSteps, history, context, historyEvents, batchStoppedReason, signal } = params;
|
|
871
|
-
let session = params.session;
|
|
872
|
-
// Create hook executor function
|
|
873
|
-
const executeHook = async (hook, hookContext, data, step) => {
|
|
874
|
-
const route = selectedRoute;
|
|
875
|
-
const stepInstance = step?.id ? route.getStep(step.id) : undefined;
|
|
876
|
-
await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
|
|
877
|
-
};
|
|
878
|
-
// PHASE 1: Execute all prepare hooks
|
|
879
|
-
const prepareResult = await this.batchExecutor.executePrepareHooks({
|
|
880
|
-
steps: batchSteps,
|
|
881
|
-
context,
|
|
882
|
-
data: session.data,
|
|
883
|
-
executeHook,
|
|
884
|
-
});
|
|
885
|
-
if (!prepareResult.success) {
|
|
886
|
-
// Yield error chunk
|
|
887
|
-
yield {
|
|
888
|
-
delta: "",
|
|
889
|
-
accumulated: "",
|
|
890
|
-
done: true,
|
|
891
|
-
session,
|
|
892
|
-
error: new ResponseGenerationError(`Prepare hook failed: ${prepareResult.error?.message}`, { phase: 'prepare_hooks' }),
|
|
893
|
-
};
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
// PHASE 2: Build combined prompt
|
|
897
|
-
const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
|
|
898
|
-
steps: batchSteps,
|
|
899
|
-
route: selectedRoute,
|
|
900
|
-
history: historyEvents,
|
|
901
|
-
context,
|
|
902
|
-
session,
|
|
903
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
904
|
-
});
|
|
905
|
-
const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
|
|
906
|
-
const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
|
|
907
|
-
// PHASE 3: Stream LLM response
|
|
908
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
909
|
-
const stream = agentOptions.provider.generateMessageStream({
|
|
910
|
-
prompt: batchPromptResult.prompt,
|
|
911
|
-
history, // Use HistoryItem[] for AI provider
|
|
912
|
-
context,
|
|
913
|
-
tools: availableTools,
|
|
914
|
-
signal,
|
|
915
|
-
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_stream_response" } : undefined,
|
|
916
|
-
});
|
|
917
|
-
// Build executed steps list
|
|
918
|
-
const executedSteps = batchSteps
|
|
919
|
-
.filter(step => step.id)
|
|
920
|
-
.map(step => ({
|
|
921
|
-
id: step.id,
|
|
922
|
-
routeId: selectedRoute.id,
|
|
923
|
-
}));
|
|
924
|
-
// Stream chunks
|
|
925
|
-
for await (const chunk of stream) {
|
|
926
|
-
// On final chunk, collect data and execute finalize hooks
|
|
927
|
-
if (chunk.done) {
|
|
928
|
-
// Collect data from response
|
|
929
|
-
if (chunk.structured) {
|
|
930
|
-
const collectResult = this.batchExecutor.collectBatchData({
|
|
931
|
-
steps: batchSteps,
|
|
932
|
-
llmResponse: chunk.structured,
|
|
933
|
-
session,
|
|
934
|
-
schema: this.agent.getSchema(),
|
|
935
|
-
});
|
|
936
|
-
session = collectResult.session;
|
|
937
|
-
if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
|
|
938
|
-
await this.agent.updateCollectedData(collectResult.collectedData);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
// Update session to final step position
|
|
942
|
-
const lastStep = batchSteps[batchSteps.length - 1];
|
|
943
|
-
if (lastStep?.id) {
|
|
944
|
-
session = enterStep(session, lastStep.id, lastStep.description);
|
|
945
|
-
}
|
|
946
|
-
// Execute finalize hooks
|
|
947
|
-
await this.batchExecutor.executeFinalizeHooks({
|
|
948
|
-
steps: batchSteps,
|
|
949
|
-
context,
|
|
950
|
-
data: session.data,
|
|
951
|
-
executeHook,
|
|
952
|
-
});
|
|
953
|
-
// Finalize session
|
|
954
|
-
await this.finalizeSession(session, context);
|
|
955
|
-
}
|
|
956
|
-
yield {
|
|
957
|
-
delta: chunk.delta,
|
|
958
|
-
accumulated: chunk.accumulated,
|
|
959
|
-
done: chunk.done,
|
|
960
|
-
session,
|
|
961
|
-
toolCalls: chunk.structured?.toolCalls,
|
|
962
|
-
isRouteComplete: false,
|
|
963
|
-
executedSteps: chunk.done ? executedSteps : undefined,
|
|
964
|
-
stoppedReason: chunk.done ? batchStoppedReason : undefined,
|
|
965
|
-
metadata: chunk.metadata,
|
|
966
|
-
structured: chunk.structured,
|
|
967
|
-
};
|
|
968
|
-
}
|
|
952
|
+
isFlowComplete: isFlowComplete,
|
|
953
|
+
executedSteps: executedSteps || [],
|
|
954
|
+
stoppedReason,
|
|
955
|
+
appliedInstructions,
|
|
956
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
957
|
+
};
|
|
969
958
|
}
|
|
970
959
|
/**
|
|
971
960
|
* Execute prepare function for current step if available
|
|
972
961
|
* @private
|
|
973
962
|
*/
|
|
974
963
|
async executeStepPrepare(session, context) {
|
|
975
|
-
if (session.
|
|
976
|
-
const
|
|
977
|
-
if (
|
|
978
|
-
const currentStep =
|
|
964
|
+
if (session.currentFlow && session.currentStep) {
|
|
965
|
+
const currentFlow = this.agent.getFlows().find((r) => r.id === session.currentFlow?.id);
|
|
966
|
+
if (currentFlow) {
|
|
967
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
968
|
+
// Skip auto-steps — their prepare is handled by AutoChainExecutor
|
|
969
|
+
if (currentStep?.auto) {
|
|
970
|
+
logger.debug(`[ResponseModal] Skipping pre-routing prepare for auto-step: ${currentStep.id}`);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
979
973
|
if (currentStep?.prepare) {
|
|
980
974
|
logger.debug(`[ResponseModal] Executing prepare for step: ${currentStep.id}`);
|
|
981
|
-
await this.executePrepareFinalize(currentStep.prepare, context, session.data,
|
|
975
|
+
await this.executePrepareFinalize(currentStep.prepare, context, session.data, currentFlow, currentStep);
|
|
982
976
|
}
|
|
983
977
|
}
|
|
984
978
|
}
|
|
@@ -988,23 +982,23 @@ export class ResponseModal {
|
|
|
988
982
|
* @private
|
|
989
983
|
*/
|
|
990
984
|
async executeStepFinalize(session, context) {
|
|
991
|
-
if (session.
|
|
992
|
-
const
|
|
993
|
-
if (
|
|
994
|
-
const currentStep =
|
|
985
|
+
if (session.currentFlow && session.currentStep) {
|
|
986
|
+
const currentFlow = this.agent.getFlows().find((r) => r.id === session.currentFlow?.id);
|
|
987
|
+
if (currentFlow) {
|
|
988
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
995
989
|
if (currentStep?.finalize) {
|
|
996
990
|
logger.debug(`[ResponseModal] Executing finalize for step: ${currentStep.id}`);
|
|
997
|
-
await this.executePrepareFinalize(currentStep.finalize, context, session.data,
|
|
991
|
+
await this.executePrepareFinalize(currentStep.finalize, context, session.data, currentFlow, currentStep);
|
|
998
992
|
}
|
|
999
993
|
}
|
|
1000
994
|
}
|
|
1001
995
|
}
|
|
1002
996
|
/**
|
|
1003
|
-
* Process
|
|
997
|
+
* Process flow response with unified tool execution and data collection
|
|
1004
998
|
* @private
|
|
1005
999
|
*/
|
|
1006
|
-
async
|
|
1007
|
-
const {
|
|
1000
|
+
async processFlowResponse(params) {
|
|
1001
|
+
const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
|
|
1008
1002
|
let session = params.session;
|
|
1009
1003
|
// Determine next step
|
|
1010
1004
|
let nextStep;
|
|
@@ -1012,25 +1006,42 @@ export class ResponseModal {
|
|
|
1012
1006
|
nextStep = selectedStep;
|
|
1013
1007
|
}
|
|
1014
1008
|
else {
|
|
1015
|
-
// Determine current step from session if we're already in this
|
|
1016
|
-
const
|
|
1017
|
-
const currentStep =
|
|
1018
|
-
?
|
|
1009
|
+
// Determine current step from session if we're already in this flow
|
|
1010
|
+
const isInSameFlow = session.currentFlow?.id === selectedFlow.id;
|
|
1011
|
+
const currentStep = isInSameFlow && session.currentStep
|
|
1012
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1019
1013
|
: undefined;
|
|
1020
|
-
logger.debug(`[ResponseModal] Step determination:
|
|
1021
|
-
//
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1014
|
+
logger.debug(`[ResponseModal] Step determination: flow match=${isInSameFlow}, currentFlow=${session.currentFlow?.id}, selectedFlow=${selectedFlow.id}, currentStep=${currentStep?.id || 'none'}`);
|
|
1015
|
+
// STEP 1 (Algorithm 1): branches win over linear chain
|
|
1016
|
+
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
1017
|
+
const branchResult = await this.responsePipeline.evaluateStepBranches(currentStep, selectedFlow, session, context);
|
|
1018
|
+
if (branchResult) {
|
|
1019
|
+
if (branchResult.nextStep) {
|
|
1020
|
+
nextStep = branchResult.nextStep;
|
|
1021
|
+
session = branchResult.session;
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
// Flow transition or completion — no local step to render
|
|
1025
|
+
// Return empty message with updated session; caller handles flow transition
|
|
1026
|
+
return { message: '', session: branchResult.session };
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
1029
|
}
|
|
1030
|
-
|
|
1031
|
-
//
|
|
1032
|
-
|
|
1033
|
-
|
|
1030
|
+
if (!nextStep) {
|
|
1031
|
+
// Get candidate steps based on current position in the flow
|
|
1032
|
+
const flowRouter = this.agent.getFlowRouter();
|
|
1033
|
+
const candidates = await flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
|
|
1034
|
+
createTemplateContext({ data: session.data, session, context }));
|
|
1035
|
+
logger.debug(`[ResponseModal] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new flow entry)'}`);
|
|
1036
|
+
if (candidates.length > 0) {
|
|
1037
|
+
nextStep = candidates[0].step;
|
|
1038
|
+
logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
|
|
1039
|
+
}
|
|
1040
|
+
else {
|
|
1041
|
+
// Fallback to initial step even if it should be skipped
|
|
1042
|
+
nextStep = selectedFlow.initialStep;
|
|
1043
|
+
logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
|
|
1044
|
+
}
|
|
1034
1045
|
}
|
|
1035
1046
|
}
|
|
1036
1047
|
// Update session with next step
|
|
@@ -1039,14 +1050,14 @@ export class ResponseModal {
|
|
|
1039
1050
|
const sessionData = session.data || {};
|
|
1040
1051
|
const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
|
|
1041
1052
|
if (missingRequires.length > 0) {
|
|
1042
|
-
const warning = `[
|
|
1043
|
-
`missing required fields [${missingRequires.join(', ')}]. Staying at current step.`;
|
|
1053
|
+
const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
|
|
1054
|
+
`missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
|
|
1044
1055
|
logger.warn(warning);
|
|
1045
1056
|
console.warn(warning);
|
|
1046
1057
|
// Stay at the current step - don't enter the next one
|
|
1047
1058
|
const currentStepId = session.currentStep?.id;
|
|
1048
1059
|
if (currentStepId) {
|
|
1049
|
-
const currentStepInstance =
|
|
1060
|
+
const currentStepInstance = selectedFlow.getStep(currentStepId);
|
|
1050
1061
|
if (currentStepInstance) {
|
|
1051
1062
|
nextStep = currentStepInstance;
|
|
1052
1063
|
logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
|
|
@@ -1062,76 +1073,271 @@ export class ResponseModal {
|
|
|
1062
1073
|
session = enterStep(session, nextStep.id, nextStep.description);
|
|
1063
1074
|
logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
|
|
1064
1075
|
}
|
|
1065
|
-
// Build response schema for this
|
|
1066
|
-
const responseSchema = this.responseEngine.
|
|
1067
|
-
//
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1082
|
-
//
|
|
1083
|
-
|
|
1084
|
-
//
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
context,
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1076
|
+
// Build response schema for this flow (with collect fields from step)
|
|
1077
|
+
const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
|
|
1078
|
+
// ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
|
|
1079
|
+
// After pre-LLM emissions are merged, if `halt: true` then skip the LLM
|
|
1080
|
+
// call entirely. The behavior depends on whether `reply` is also set.
|
|
1081
|
+
if (mergedPreDirective?.halt) {
|
|
1082
|
+
if (mergedPreDirective.reply) {
|
|
1083
|
+
// halt + reply: emit the reply as the assistant message
|
|
1084
|
+
logger.debug(`[ResponseModal] Halt with reply — skipping LLM call for step ${nextStep.id}`);
|
|
1085
|
+
return { message: mergedPreDirective.reply, session, stoppedReason: 'reply' };
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
// halt without reply: emit empty assistant content
|
|
1089
|
+
logger.debug(`[ResponseModal] Halt without reply — skipping LLM call for step ${nextStep.id}`);
|
|
1090
|
+
return { message: '', session, stoppedReason: 'halt' };
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
// ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
|
|
1094
|
+
// A step with `reply` set emits a verbatim template response without LLM.
|
|
1095
|
+
// onEnter and prepare have already fired normally at this point.
|
|
1096
|
+
// If prepare returned a PreDirective with `reply`, that overrides
|
|
1097
|
+
// the step-declared reply (last-emission-wins per Algorithm 4).
|
|
1098
|
+
if (nextStep.reply != null) {
|
|
1099
|
+
// Determine the effective reply: prepare-emitted reply wins over step-declared
|
|
1100
|
+
const effectiveReply = mergedPreDirective?.reply ?? await render(nextStep.reply, createTemplateContext({ data: session.data || {}, context, session }));
|
|
1101
|
+
logger.debug(`[ResponseModal] Step.reply — skipping LLM call for step ${nextStep.id}`);
|
|
1102
|
+
return { message: effectiveReply, session, stoppedReason: 'reply' };
|
|
1103
|
+
}
|
|
1104
|
+
// Transient appendage: per-turn slot from PreDirective.appendPrompt.
|
|
1105
|
+
// Fresh each turn, never cached, never persisted.
|
|
1106
|
+
// Wrapped in try/finally to ensure cleanup even on abnormal termination.
|
|
1107
|
+
let turnTransientAppendage = transientAppendage;
|
|
1108
|
+
try {
|
|
1109
|
+
// Build response prompt
|
|
1110
|
+
const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
|
|
1111
|
+
flow: selectedFlow,
|
|
1112
|
+
currentStep: nextStep,
|
|
1113
|
+
rules: [],
|
|
1114
|
+
prohibitions: [],
|
|
1115
|
+
directives: responseDirectives,
|
|
1116
|
+
history: historyEvents,
|
|
1117
|
+
agentOptions: this.agent.getAgentOptions(),
|
|
1118
|
+
instructions: this.collectScopedInstructions(selectedFlow, nextStep),
|
|
1119
|
+
combinedTerms: this.agent.getTerms(),
|
|
1120
|
+
context,
|
|
1121
|
+
session,
|
|
1122
|
+
agentSchema: this.agent.schema,
|
|
1123
|
+
transientAppendage: turnTransientAppendage,
|
|
1124
|
+
});
|
|
1125
|
+
// Collect available tools for AI
|
|
1126
|
+
const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
|
|
1127
|
+
// Generate message using AI provider
|
|
1128
|
+
const agentOptions = this.agent.getAgentOptions();
|
|
1129
|
+
const result = await agentOptions.provider.generateMessage({
|
|
1130
|
+
prompt: responsePrompt,
|
|
1131
|
+
history, // Use HistoryItem[] for AI provider
|
|
1132
|
+
context,
|
|
1133
|
+
tools: availableTools,
|
|
1134
|
+
signal,
|
|
1135
|
+
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "response_output" } : undefined,
|
|
1136
|
+
});
|
|
1137
|
+
let message = result.structured?.message || result.message;
|
|
1138
|
+
let toolCalls = result.structured?.toolCalls;
|
|
1139
|
+
// Debug: Log initial AI response
|
|
1140
|
+
logger.debug(`[ResponseModal] Initial AI response:`, {
|
|
1141
|
+
hasMessage: !!message,
|
|
1142
|
+
messageLength: message?.length || 0,
|
|
1143
|
+
hasToolCalls: !!toolCalls,
|
|
1144
|
+
toolCallsCount: toolCalls?.length || 0,
|
|
1145
|
+
toolNames: toolCalls?.map(tc => tc.toolName) || [],
|
|
1146
|
+
});
|
|
1147
|
+
// Execute tools with unified loop handling
|
|
1148
|
+
const toolResult = await this.executeUnifiedToolLoop({
|
|
1149
|
+
toolCalls,
|
|
1150
|
+
context,
|
|
1151
|
+
session,
|
|
1152
|
+
history,
|
|
1153
|
+
selectedFlow,
|
|
1154
|
+
responsePrompt,
|
|
1155
|
+
availableTools,
|
|
1156
|
+
responseSchema,
|
|
1157
|
+
signal,
|
|
1158
|
+
});
|
|
1159
|
+
session = toolResult.session;
|
|
1160
|
+
toolCalls = toolResult.finalToolCalls;
|
|
1161
|
+
if (toolResult.finalMessage) {
|
|
1162
|
+
message = toolResult.finalMessage;
|
|
1163
|
+
}
|
|
1164
|
+
// Collect data from response
|
|
1165
|
+
// Use follow-up structured data from tool loop when available, fall back to original result
|
|
1166
|
+
const dataSource = toolResult.structured
|
|
1167
|
+
? { structured: toolResult.structured }
|
|
1168
|
+
: result;
|
|
1169
|
+
session = await this.collectDataFromResponse({ result: dataSource, selectedFlow, nextStep, session });
|
|
1170
|
+
return { message, toolCalls, session, appliedInstructions };
|
|
1171
|
+
}
|
|
1172
|
+
finally {
|
|
1173
|
+
// Drain the transient appendage at end of turn.
|
|
1174
|
+
// This ensures PreDirective.appendPrompt does not leak to subsequent
|
|
1175
|
+
// turns even when the turn terminates abnormally (error, abort, reject).
|
|
1176
|
+
turnTransientAppendage = undefined;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Unified streaming response generation
|
|
1181
|
+
* @private
|
|
1182
|
+
*/
|
|
1183
|
+
async *generateUnifiedStreamingResponse(responseContext) {
|
|
1184
|
+
const { effectiveContext, session: initialSession, history, selectedFlow, selectedStep, responseDirectives, isFlowComplete, signal, signalFirings: preSignalFirings, signalPreDirective, signalHalted, signalHaltReply, } = responseContext;
|
|
1185
|
+
let session = initialSession;
|
|
1186
|
+
// Accumulator for signal firings across both phases (fire order)
|
|
1187
|
+
const signalFirings = [...(preSignalFirings || [])];
|
|
1188
|
+
// Convert HistoryItem[] to Event[] for internal processing
|
|
1189
|
+
const historyEvents = historyToEvents(history);
|
|
1190
|
+
// ── SIGNAL HALT (Requirement 8.2) ─────────────────────────────────────
|
|
1191
|
+
if (signalHalted) {
|
|
1192
|
+
const haltMessage = signalHaltReply || '';
|
|
1193
|
+
// Run post-signal phase even on halt
|
|
1194
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(session, effectiveContext, historyEvents);
|
|
1195
|
+
session = postResult.updatedSession;
|
|
1196
|
+
signalFirings.push(...postResult.firings);
|
|
1197
|
+
if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
|
|
1198
|
+
session = { ...session, pendingDirective: postResult.mergedDirective };
|
|
1199
|
+
}
|
|
1200
|
+
await this.finalizeSession(session, effectiveContext);
|
|
1201
|
+
yield {
|
|
1202
|
+
delta: haltMessage,
|
|
1203
|
+
accumulated: haltMessage,
|
|
1204
|
+
done: true,
|
|
1205
|
+
session,
|
|
1206
|
+
stoppedReason: haltMessage ? 'reply' : 'halt',
|
|
1207
|
+
executedSteps: [],
|
|
1208
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
1209
|
+
};
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
// ── Determine the inner stream generator based on flow state ────────
|
|
1213
|
+
let innerStream;
|
|
1214
|
+
if (selectedFlow && !isFlowComplete) {
|
|
1215
|
+
// AUTO-CHAIN: Walk consecutive auto-steps before any LLM work (streaming path).
|
|
1216
|
+
let resolvedStep = selectedStep;
|
|
1217
|
+
const currentStepInstance = session.currentStep
|
|
1218
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1219
|
+
: selectedStep;
|
|
1220
|
+
if (currentStepInstance?.auto) {
|
|
1221
|
+
const autoChainExecutor = new AutoChainExecutor({
|
|
1222
|
+
maxAutoStepsPerTurn: this.agent.maxAutoStepsPerTurn,
|
|
1223
|
+
});
|
|
1224
|
+
const autoResult = await autoChainExecutor.run({
|
|
1225
|
+
session,
|
|
1226
|
+
context: effectiveContext,
|
|
1227
|
+
flow: selectedFlow,
|
|
1228
|
+
});
|
|
1229
|
+
session = autoResult.session;
|
|
1230
|
+
// Handle halt: emit verbatim reply as a single chunk, done.
|
|
1231
|
+
if (autoResult.stoppedReason === 'halt') {
|
|
1232
|
+
const reply = autoResult.mergedDirective?.reply || '';
|
|
1233
|
+
await this.finalizeSession(session, effectiveContext);
|
|
1234
|
+
yield {
|
|
1235
|
+
delta: reply,
|
|
1236
|
+
accumulated: reply,
|
|
1237
|
+
done: true,
|
|
1238
|
+
session,
|
|
1239
|
+
stoppedReason: 'halt',
|
|
1240
|
+
executedSteps: [],
|
|
1241
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
1242
|
+
};
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
// Handle flow completion or cross-flow redirect from auto-chain.
|
|
1246
|
+
if (autoResult.stoppedReason === 'last_step' || autoResult.stoppedReason === 'completed' || autoResult.stoppedReason === 'goto') {
|
|
1247
|
+
innerStream = this.streamFlowCompletion({
|
|
1248
|
+
selectedFlow,
|
|
1249
|
+
session,
|
|
1250
|
+
context: effectiveContext,
|
|
1251
|
+
history,
|
|
1252
|
+
historyEvents,
|
|
1253
|
+
stoppedReason: autoResult.stoppedReason,
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
else {
|
|
1257
|
+
// Normal case: resolved to an interactive step.
|
|
1258
|
+
resolvedStep = autoResult.resolvedStep;
|
|
1259
|
+
innerStream = this.processFlowStreamingResponse({
|
|
1260
|
+
selectedFlow,
|
|
1261
|
+
selectedStep: resolvedStep,
|
|
1262
|
+
responseDirectives,
|
|
1263
|
+
session,
|
|
1264
|
+
history,
|
|
1265
|
+
context: effectiveContext,
|
|
1266
|
+
historyEvents,
|
|
1267
|
+
signal,
|
|
1268
|
+
transientAppendage: signalPreDirective?.appendPrompt,
|
|
1269
|
+
mergedPreDirective: signalPreDirective,
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
else {
|
|
1274
|
+
// No auto-step: directly stream the interactive step.
|
|
1275
|
+
innerStream = this.processFlowStreamingResponse({
|
|
1276
|
+
selectedFlow,
|
|
1277
|
+
selectedStep: resolvedStep,
|
|
1278
|
+
responseDirectives,
|
|
1279
|
+
session,
|
|
1280
|
+
history,
|
|
1281
|
+
context: effectiveContext,
|
|
1282
|
+
historyEvents,
|
|
1283
|
+
signal,
|
|
1284
|
+
// Propagate signal pre-directive's appendPrompt for this turn's LLM call
|
|
1285
|
+
transientAppendage: signalPreDirective?.appendPrompt,
|
|
1286
|
+
mergedPreDirective: signalPreDirective,
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
else if (isFlowComplete && selectedFlow) {
|
|
1291
|
+
// Handle flow completion streaming — implicit terminus (no successor
|
|
1292
|
+
// or all successors skipped), so the reason is 'last_step'.
|
|
1293
|
+
innerStream = this.streamFlowCompletion({
|
|
1294
|
+
selectedFlow,
|
|
1295
|
+
session,
|
|
1296
|
+
context: effectiveContext,
|
|
1297
|
+
history,
|
|
1298
|
+
historyEvents,
|
|
1299
|
+
stoppedReason: 'last_step',
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
// Fallback: No flows defined, stream a simple response
|
|
1304
|
+
innerStream = this.streamFallbackResponse({
|
|
1305
|
+
history,
|
|
1306
|
+
context: effectiveContext,
|
|
1307
|
+
session,
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
// ── Intercept the inner stream to run post-signal phase on the final chunk ──
|
|
1311
|
+
// This mirrors the non-streaming path: post-phase runs after finalize/onComplete
|
|
1312
|
+
// and before session persistence, attaching triggeredSignals to the final chunk
|
|
1313
|
+
// (Requirement 11.2).
|
|
1314
|
+
for await (const chunk of innerStream) {
|
|
1315
|
+
if (chunk.done) {
|
|
1316
|
+
// Run post-signal phase on final chunk (Requirement 9.1, 9.2)
|
|
1317
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(chunk.session || session, effectiveContext, historyEvents);
|
|
1318
|
+
let finalSession = postResult.updatedSession;
|
|
1319
|
+
signalFirings.push(...postResult.firings);
|
|
1320
|
+
// Requirement 9.3: Post-phase position directive sets session.pendingDirective
|
|
1321
|
+
if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
|
|
1322
|
+
finalSession = { ...finalSession, pendingDirective: postResult.mergedDirective };
|
|
1323
|
+
}
|
|
1324
|
+
yield {
|
|
1325
|
+
...chunk,
|
|
1326
|
+
session: finalSession,
|
|
1327
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings : undefined,
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
yield chunk;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1128
1334
|
}
|
|
1129
1335
|
/**
|
|
1130
|
-
* Process
|
|
1336
|
+
* Process flow streaming response with unified tool execution and data collection
|
|
1131
1337
|
* @private
|
|
1132
1338
|
*/
|
|
1133
|
-
async *
|
|
1134
|
-
const {
|
|
1339
|
+
async *processFlowStreamingResponse(params) {
|
|
1340
|
+
const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
|
|
1135
1341
|
let session = params.session;
|
|
1136
1342
|
// Determine next step (same logic as non-streaming)
|
|
1137
1343
|
let nextStep;
|
|
@@ -1139,21 +1345,44 @@ export class ResponseModal {
|
|
|
1139
1345
|
nextStep = selectedStep;
|
|
1140
1346
|
}
|
|
1141
1347
|
else {
|
|
1142
|
-
// Determine current step from session if we're already in this
|
|
1143
|
-
const currentStep = session.
|
|
1144
|
-
?
|
|
1348
|
+
// Determine current step from session if we're already in this flow
|
|
1349
|
+
const currentStep = session.currentFlow?.id === selectedFlow.id && session.currentStep
|
|
1350
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1145
1351
|
: undefined;
|
|
1146
|
-
//
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1352
|
+
// STEP 1 (Algorithm 1): branches win over linear chain
|
|
1353
|
+
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
1354
|
+
const branchResult = await this.responsePipeline.evaluateStepBranches(currentStep, selectedFlow, session, context);
|
|
1355
|
+
if (branchResult) {
|
|
1356
|
+
// Branch resolved — yield a final chunk with the updated session and return
|
|
1357
|
+
if (branchResult.nextStep) {
|
|
1358
|
+
session = branchResult.session;
|
|
1359
|
+
nextStep = branchResult.nextStep;
|
|
1360
|
+
}
|
|
1361
|
+
else {
|
|
1362
|
+
// Flow transition or completion — no step to render
|
|
1363
|
+
yield {
|
|
1364
|
+
delta: '',
|
|
1365
|
+
accumulated: '',
|
|
1366
|
+
done: true,
|
|
1367
|
+
session: branchResult.session,
|
|
1368
|
+
};
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1153
1372
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1373
|
+
if (!nextStep) {
|
|
1374
|
+
// Get candidate steps based on current position in the flow
|
|
1375
|
+
const flowRouter = this.agent.getFlowRouter();
|
|
1376
|
+
const candidates = await flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
|
|
1377
|
+
createTemplateContext({ data: session.data, session, context }));
|
|
1378
|
+
if (candidates.length > 0) {
|
|
1379
|
+
nextStep = candidates[0].step;
|
|
1380
|
+
logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
|
|
1381
|
+
}
|
|
1382
|
+
else {
|
|
1383
|
+
nextStep = selectedFlow.initialStep;
|
|
1384
|
+
logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
|
|
1385
|
+
}
|
|
1157
1386
|
}
|
|
1158
1387
|
}
|
|
1159
1388
|
// Update session with next step
|
|
@@ -1162,13 +1391,13 @@ export class ResponseModal {
|
|
|
1162
1391
|
const sessionData = session.data || {};
|
|
1163
1392
|
const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
|
|
1164
1393
|
if (missingRequires.length > 0) {
|
|
1165
|
-
const warning = `[
|
|
1166
|
-
`missing required fields [${missingRequires.join(', ')}]. Staying at current step.`;
|
|
1394
|
+
const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
|
|
1395
|
+
`missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
|
|
1167
1396
|
logger.warn(warning);
|
|
1168
1397
|
console.warn(warning);
|
|
1169
1398
|
const currentStepId = session.currentStep?.id;
|
|
1170
1399
|
if (currentStepId) {
|
|
1171
|
-
const currentStepInstance =
|
|
1400
|
+
const currentStepInstance = selectedFlow.getStep(currentStepId);
|
|
1172
1401
|
if (currentStepInstance) {
|
|
1173
1402
|
nextStep = currentStepInstance;
|
|
1174
1403
|
logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
|
|
@@ -1185,142 +1414,192 @@ export class ResponseModal {
|
|
|
1185
1414
|
logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
|
|
1186
1415
|
}
|
|
1187
1416
|
// Build response schema and prompt (same as non-streaming)
|
|
1188
|
-
const responseSchema = this.responseEngine.
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1417
|
+
const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
|
|
1418
|
+
// ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
|
|
1419
|
+
// After pre-LLM emissions are merged, if `halt: true` then skip the LLM
|
|
1420
|
+
// call entirely. Emit a single done chunk with the appropriate content.
|
|
1421
|
+
if (mergedPreDirective?.halt) {
|
|
1422
|
+
const reply = mergedPreDirective.reply || '';
|
|
1423
|
+
const reason = mergedPreDirective.reply ? 'reply' : 'halt';
|
|
1424
|
+
logger.debug(`[ResponseModal] Halt (streaming) — skipping LLM call for step ${nextStep.id}, stoppedReason: ${reason}`);
|
|
1425
|
+
await this.finalizeSession(session, context);
|
|
1426
|
+
yield {
|
|
1427
|
+
delta: reply,
|
|
1428
|
+
accumulated: reply,
|
|
1429
|
+
done: true,
|
|
1430
|
+
session,
|
|
1431
|
+
stoppedReason: reason,
|
|
1432
|
+
executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
|
|
1433
|
+
};
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
// ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
|
|
1437
|
+
// A step with `reply` set emits a verbatim template response without LLM.
|
|
1438
|
+
// onEnter and prepare have already fired normally. If prepare returned
|
|
1439
|
+
// a PreDirective with `reply`, that overrides the step-declared reply.
|
|
1440
|
+
if (nextStep.reply != null) {
|
|
1441
|
+
const effectiveReply = mergedPreDirective?.reply ?? await render(nextStep.reply, createTemplateContext({ data: session.data || {}, context, session }));
|
|
1442
|
+
logger.debug(`[ResponseModal] Step.reply (streaming) — skipping LLM call for step ${nextStep.id}`);
|
|
1443
|
+
await this.finalizeSession(session, context);
|
|
1444
|
+
yield {
|
|
1445
|
+
delta: effectiveReply,
|
|
1446
|
+
accumulated: effectiveReply,
|
|
1447
|
+
done: true,
|
|
1448
|
+
session,
|
|
1449
|
+
stoppedReason: 'reply',
|
|
1450
|
+
executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
|
|
1451
|
+
};
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
// Transient appendage: per-turn slot from PreDirective.appendPrompt.
|
|
1455
|
+
// Fresh each turn, never cached, never persisted.
|
|
1456
|
+
// Wrapped in try/finally to ensure cleanup even on abnormal termination.
|
|
1457
|
+
let turnTransientAppendage = transientAppendage;
|
|
1458
|
+
try {
|
|
1459
|
+
const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
|
|
1460
|
+
flow: selectedFlow,
|
|
1461
|
+
currentStep: nextStep,
|
|
1462
|
+
rules: [],
|
|
1463
|
+
prohibitions: [],
|
|
1464
|
+
directives: responseDirectives,
|
|
1465
|
+
history: historyEvents,
|
|
1466
|
+
agentOptions: this.agent.getAgentOptions(),
|
|
1467
|
+
instructions: this.collectScopedInstructions(selectedFlow, nextStep),
|
|
1468
|
+
combinedTerms: this.agent.getTerms(),
|
|
1469
|
+
context,
|
|
1470
|
+
session,
|
|
1471
|
+
agentSchema: this.agent.schema,
|
|
1472
|
+
transientAppendage: turnTransientAppendage,
|
|
1473
|
+
});
|
|
1474
|
+
// Collect available tools for AI
|
|
1475
|
+
const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
|
|
1476
|
+
// Generate message stream using AI provider
|
|
1477
|
+
const agentOptions = this.agent.getAgentOptions();
|
|
1478
|
+
const stream = agentOptions.provider.generateMessageStream({
|
|
1479
|
+
prompt: responsePrompt,
|
|
1480
|
+
history, // Use HistoryItem[] for AI provider
|
|
1481
|
+
context,
|
|
1482
|
+
tools: availableTools,
|
|
1483
|
+
signal,
|
|
1484
|
+
parameters: { jsonSchema: responseSchema, schemaName: "response_stream_output" },
|
|
1485
|
+
});
|
|
1486
|
+
// Stream chunks with unified tool handling
|
|
1487
|
+
for await (const chunk of stream) {
|
|
1488
|
+
let toolCalls = undefined;
|
|
1489
|
+
// Extract tool calls from AI response on final chunk
|
|
1490
|
+
if (chunk.done && chunk.structured?.toolCalls) {
|
|
1491
|
+
toolCalls = chunk.structured.toolCalls;
|
|
1492
|
+
const toolManager = this.getToolManager();
|
|
1493
|
+
// Use concurrent execution for the initial batch of tool calls
|
|
1494
|
+
if (toolManager && typeof toolManager.executeWithConcurrency === 'function') {
|
|
1495
|
+
const toolCallRequests = toolCalls.map((tc, i) => ({
|
|
1496
|
+
id: `${tc.toolName}-${i}-${Date.now()}`,
|
|
1497
|
+
toolName: tc.toolName,
|
|
1498
|
+
arguments: tc.arguments,
|
|
1499
|
+
}));
|
|
1500
|
+
const historyEvents = historyToEvents(history);
|
|
1501
|
+
try {
|
|
1502
|
+
for await (const update of toolManager.executeWithConcurrency({
|
|
1503
|
+
toolCalls: toolCallRequests,
|
|
1504
|
+
context,
|
|
1505
|
+
data: session.data,
|
|
1506
|
+
history: historyEvents,
|
|
1507
|
+
signal,
|
|
1508
|
+
flow: selectedFlow,
|
|
1509
|
+
step: nextStep,
|
|
1510
|
+
})) {
|
|
1511
|
+
// Apply context updates
|
|
1512
|
+
if (update.contextUpdate) {
|
|
1513
|
+
try {
|
|
1514
|
+
await this.agent.updateContext(update.contextUpdate);
|
|
1515
|
+
}
|
|
1516
|
+
catch (error) {
|
|
1517
|
+
logger.error(`[ResponseModal] Failed to update context from concurrent tool:`, error);
|
|
1518
|
+
}
|
|
1247
1519
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1520
|
+
// Apply data updates
|
|
1521
|
+
if (update.dataUpdate) {
|
|
1522
|
+
try {
|
|
1523
|
+
const updateDataMethod = this.agent.getUpdateDataMethod();
|
|
1524
|
+
session = await updateDataMethod(session, update.dataUpdate);
|
|
1525
|
+
}
|
|
1526
|
+
catch (error) {
|
|
1527
|
+
logger.error(`[ResponseModal] Failed to update data from concurrent tool:`, error);
|
|
1528
|
+
}
|
|
1254
1529
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1530
|
+
// Yield progress updates immediately
|
|
1531
|
+
if (update.progress) {
|
|
1532
|
+
yield {
|
|
1533
|
+
delta: '',
|
|
1534
|
+
accumulated: chunk.accumulated,
|
|
1535
|
+
done: false,
|
|
1536
|
+
session,
|
|
1537
|
+
toolCalls: undefined,
|
|
1538
|
+
isFlowComplete: false,
|
|
1539
|
+
metadata: { toolProgress: update.progress, toolCallId: update.toolCallId },
|
|
1540
|
+
};
|
|
1257
1541
|
}
|
|
1258
1542
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
}
|
|
1543
|
+
logger.debug(`[ResponseModal] Concurrent tool execution completed for ${toolCallRequests.length} tools`);
|
|
1544
|
+
}
|
|
1545
|
+
catch (error) {
|
|
1546
|
+
logger.error(`[ResponseModal] Concurrent tool execution failed, falling back to sequential:`, error);
|
|
1547
|
+
// Fall back to the unified tool loop on failure
|
|
1548
|
+
const toolResult = await this.executeUnifiedToolLoop({
|
|
1549
|
+
toolCalls, context, session, history, selectedFlow,
|
|
1550
|
+
responsePrompt, availableTools, responseSchema, signal,
|
|
1551
|
+
});
|
|
1552
|
+
session = toolResult.session;
|
|
1553
|
+
toolCalls = toolResult.finalToolCalls;
|
|
1271
1554
|
}
|
|
1272
|
-
logger.debug(`[ResponseModal] Concurrent tool execution completed for ${toolCallRequests.length} tools`);
|
|
1273
1555
|
}
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
// Fall back to the unified tool loop on failure
|
|
1556
|
+
else {
|
|
1557
|
+
// Fallback: no ToolManager or no executeWithConcurrency, use unified tool loop
|
|
1277
1558
|
const toolResult = await this.executeUnifiedToolLoop({
|
|
1278
|
-
toolCalls, context, session, history,
|
|
1559
|
+
toolCalls, context, session, history, selectedFlow,
|
|
1279
1560
|
responsePrompt, availableTools, responseSchema, signal,
|
|
1280
1561
|
});
|
|
1281
1562
|
session = toolResult.session;
|
|
1282
1563
|
toolCalls = toolResult.finalToolCalls;
|
|
1283
1564
|
}
|
|
1284
1565
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1566
|
+
// Extract collected data on final chunk
|
|
1567
|
+
if (chunk.done && chunk.structured && nextStep.collect) {
|
|
1568
|
+
session = await this.collectDataFromResponse({
|
|
1569
|
+
result: { structured: chunk.structured },
|
|
1570
|
+
selectedFlow,
|
|
1571
|
+
nextStep,
|
|
1572
|
+
session,
|
|
1290
1573
|
});
|
|
1291
|
-
session = toolResult.session;
|
|
1292
|
-
toolCalls = toolResult.finalToolCalls;
|
|
1293
1574
|
}
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1575
|
+
// Handle session finalization on final chunk
|
|
1576
|
+
if (chunk.done) {
|
|
1577
|
+
await this.finalizeSession(session, context);
|
|
1578
|
+
}
|
|
1579
|
+
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
1580
|
+
// - executedSteps: single step executed in this response
|
|
1581
|
+
// - stoppedReason: 'needs_input' for single-step execution (waiting for user input)
|
|
1582
|
+
// - session.currentStep: reflects the executed step
|
|
1583
|
+
yield {
|
|
1584
|
+
delta: chunk.delta,
|
|
1585
|
+
accumulated: chunk.accumulated,
|
|
1586
|
+
done: chunk.done,
|
|
1301
1587
|
session,
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1588
|
+
toolCalls,
|
|
1589
|
+
isFlowComplete: false,
|
|
1590
|
+
executedSteps: chunk.done ? [{ id: nextStep.id, flowId: selectedFlow.id }] : undefined,
|
|
1591
|
+
stoppedReason: chunk.done ? 'needs_input' : undefined,
|
|
1592
|
+
metadata: chunk.metadata,
|
|
1593
|
+
structured: chunk.structured,
|
|
1594
|
+
appliedInstructions: chunk.done ? appliedInstructions : undefined,
|
|
1595
|
+
};
|
|
1307
1596
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
//
|
|
1311
|
-
//
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
accumulated: chunk.accumulated,
|
|
1315
|
-
done: chunk.done,
|
|
1316
|
-
session,
|
|
1317
|
-
toolCalls,
|
|
1318
|
-
isRouteComplete: false,
|
|
1319
|
-
executedSteps: chunk.done ? [{ id: nextStep.id, routeId: selectedRoute.id }] : undefined,
|
|
1320
|
-
stoppedReason: chunk.done ? 'needs_input' : undefined,
|
|
1321
|
-
metadata: chunk.metadata,
|
|
1322
|
-
structured: chunk.structured,
|
|
1323
|
-
};
|
|
1597
|
+
}
|
|
1598
|
+
finally {
|
|
1599
|
+
// Drain the transient appendage at end of turn.
|
|
1600
|
+
// This ensures PreDirective.appendPrompt does not leak to subsequent
|
|
1601
|
+
// turns even when the turn terminates abnormally (error, abort, reject).
|
|
1602
|
+
turnTransientAppendage = undefined;
|
|
1324
1603
|
}
|
|
1325
1604
|
}
|
|
1326
1605
|
/**
|
|
@@ -1330,7 +1609,7 @@ export class ResponseModal {
|
|
|
1330
1609
|
*/
|
|
1331
1610
|
async executeUnifiedToolLoop(params) {
|
|
1332
1611
|
try {
|
|
1333
|
-
const { context, history,
|
|
1612
|
+
const { context, history, selectedFlow, responsePrompt, availableTools, responseSchema, signal } = params;
|
|
1334
1613
|
let { toolCalls, session } = params;
|
|
1335
1614
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
1336
1615
|
const historyEvents = historyToEvents(history);
|
|
@@ -1342,9 +1621,9 @@ export class ResponseModal {
|
|
|
1342
1621
|
if (toolCalls && toolCalls.length > 0) {
|
|
1343
1622
|
logger.debug(`[ResponseModal] Executing ${toolCalls.length} dynamic tool calls:`, toolCalls.map(tc => tc.toolName));
|
|
1344
1623
|
for (const toolCall of toolCalls) {
|
|
1345
|
-
const tool = this.findAvailableTool(toolCall.toolName,
|
|
1624
|
+
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
1346
1625
|
if (!tool) {
|
|
1347
|
-
logger.warn(`[
|
|
1626
|
+
logger.warn(`[ToolExecutionError] Tool not found: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
|
|
1348
1627
|
continue;
|
|
1349
1628
|
}
|
|
1350
1629
|
try {
|
|
@@ -1418,7 +1697,7 @@ export class ResponseModal {
|
|
|
1418
1697
|
// Create tool result history items
|
|
1419
1698
|
const toolResultHistoryItems = [];
|
|
1420
1699
|
for (const toolCall of toolCalls || []) {
|
|
1421
|
-
const tool = this.findAvailableTool(toolCall.toolName,
|
|
1700
|
+
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
1422
1701
|
if (tool) {
|
|
1423
1702
|
// Create HistoryItem format for tool results
|
|
1424
1703
|
// Add assistant message with tool_calls
|
|
@@ -1476,9 +1755,9 @@ export class ResponseModal {
|
|
|
1476
1755
|
logger.debug(`[ResponseModal] Follow-up call produced ${followUpToolCalls.length} additional tool calls`);
|
|
1477
1756
|
// Execute the follow-up tool calls
|
|
1478
1757
|
for (const toolCall of followUpToolCalls) {
|
|
1479
|
-
const tool = this.findAvailableTool(toolCall.toolName,
|
|
1758
|
+
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
1480
1759
|
if (!tool) {
|
|
1481
|
-
logger.warn(`[
|
|
1760
|
+
logger.warn(`[ToolExecutionError] Tool not found in follow-up: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
|
|
1482
1761
|
continue;
|
|
1483
1762
|
}
|
|
1484
1763
|
try {
|
|
@@ -1547,7 +1826,7 @@ export class ResponseModal {
|
|
|
1547
1826
|
}
|
|
1548
1827
|
}
|
|
1549
1828
|
if (toolLoopCount >= MAX_TOOL_LOOPS) {
|
|
1550
|
-
logger.warn(`[
|
|
1829
|
+
logger.warn(`[ResponseGenerationError] Tool loop limit reached: ${toolLoopCount} iterations hit the cap (${MAX_TOOL_LOOPS}). Stopping tool execution. Increase MAX_TOOL_LOOPS or reduce recursive tool calls.`);
|
|
1551
1830
|
}
|
|
1552
1831
|
// If tools were executed but no final text message was produced,
|
|
1553
1832
|
// make one more LLM call to generate a proper text response from tool results.
|
|
@@ -1630,30 +1909,30 @@ export class ResponseModal {
|
|
|
1630
1909
|
*/
|
|
1631
1910
|
async collectDataFromResponse(params) {
|
|
1632
1911
|
try {
|
|
1633
|
-
const { result,
|
|
1912
|
+
const { result, selectedFlow, nextStep, session } = params;
|
|
1634
1913
|
let updatedSession = session;
|
|
1635
|
-
// Extract collected data from final response (only for
|
|
1636
|
-
if (
|
|
1914
|
+
// Extract collected data from final response (only for flow-based interactions)
|
|
1915
|
+
if (selectedFlow && result.structured) {
|
|
1637
1916
|
try {
|
|
1638
1917
|
const collectedData = {};
|
|
1639
1918
|
// AgentStructuredResponse extends Record<string, unknown>, so we can safely access properties
|
|
1640
1919
|
const structuredData = result.structured;
|
|
1641
|
-
// Collect ALL
|
|
1642
|
-
const
|
|
1643
|
-
// Add
|
|
1644
|
-
if (
|
|
1645
|
-
|
|
1920
|
+
// Collect ALL flow fields (required + optional) from structured response
|
|
1921
|
+
const allFlowFields = new Set();
|
|
1922
|
+
// Add flow required fields
|
|
1923
|
+
if (selectedFlow.requiredFields) {
|
|
1924
|
+
selectedFlow.requiredFields.forEach(field => allFlowFields.add(String(field)));
|
|
1646
1925
|
}
|
|
1647
|
-
// Add
|
|
1648
|
-
if (
|
|
1649
|
-
|
|
1926
|
+
// Add flow optional fields
|
|
1927
|
+
if (selectedFlow.optionalFields) {
|
|
1928
|
+
selectedFlow.optionalFields.forEach(field => allFlowFields.add(String(field)));
|
|
1650
1929
|
}
|
|
1651
|
-
// Also include current step's collect fields (in case they're not in
|
|
1930
|
+
// Also include current step's collect fields (in case they're not in flow fields)
|
|
1652
1931
|
if (nextStep?.collect) {
|
|
1653
|
-
nextStep.collect.forEach(field =>
|
|
1932
|
+
nextStep.collect.forEach(field => allFlowFields.add(String(field)));
|
|
1654
1933
|
}
|
|
1655
1934
|
// Extract all available fields from structured response
|
|
1656
|
-
for (const field of
|
|
1935
|
+
for (const field of allFlowFields) {
|
|
1657
1936
|
const fieldKey = String(field);
|
|
1658
1937
|
if (fieldKey in structuredData && structuredData[fieldKey] !== undefined && structuredData[fieldKey] !== null) {
|
|
1659
1938
|
collectedData[fieldKey] = structuredData[fieldKey];
|
|
@@ -1703,207 +1982,122 @@ export class ResponseModal {
|
|
|
1703
1982
|
}
|
|
1704
1983
|
}
|
|
1705
1984
|
/**
|
|
1706
|
-
*
|
|
1985
|
+
* Apply flow completion: release the session to idle state.
|
|
1986
|
+
*
|
|
1987
|
+
* This is a pure state transition. The framework emits **no message of
|
|
1988
|
+
* its own** at the completion boundary — every word delivered to the
|
|
1989
|
+
* user comes from a developer-defined step prompt. If the dev wants a
|
|
1990
|
+
* closing turn, they add a final interactive step with their own
|
|
1991
|
+
* `prompt`; the framework respects that step's natural LLM output.
|
|
1992
|
+
*
|
|
1993
|
+
* Behavior:
|
|
1994
|
+
* - Marks the active `flowHistory` entry as `completed: true` and
|
|
1995
|
+
* stamps `exitedAt`.
|
|
1996
|
+
* - Evaluates `flow.onComplete` for an explicit follow-up transition.
|
|
1997
|
+
* When set, populates `session.pendingDirective` (the next turn's
|
|
1998
|
+
* pipeline applies it). When absent, the session is fully idle.
|
|
1999
|
+
* - Clears `currentFlow` and `currentStep` to `undefined`.
|
|
2000
|
+
* - Clears owned fields when the flow is `reentrant` so subsequent
|
|
2001
|
+
* re-selections start from a clean state.
|
|
2002
|
+
*
|
|
2003
|
+
* Returns the updated session. Callers compose any reply text from
|
|
2004
|
+
* their own sources (an upstream LLM turn, a directive's `reply`, or
|
|
2005
|
+
* an empty string for silent completion).
|
|
2006
|
+
*
|
|
1707
2007
|
* @private
|
|
1708
2008
|
*/
|
|
1709
|
-
async
|
|
1710
|
-
const {
|
|
1711
|
-
//
|
|
1712
|
-
const
|
|
1713
|
-
//
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
2009
|
+
async applyFlowCompletion(params) {
|
|
2010
|
+
const { selectedFlow, session, context } = params;
|
|
2011
|
+
// 1) Evaluate onComplete first — needs the still-active session shape.
|
|
2012
|
+
const transitionConfig = await selectedFlow.evaluateOnComplete({ data: session.data }, context);
|
|
2013
|
+
// 2) Release to idle. If the flow is reentrant, scrub its owned
|
|
2014
|
+
// fields so re-selection on a future turn starts clean. When
|
|
2015
|
+
// onComplete fires we still go idle here — the next turn's
|
|
2016
|
+
// pipeline applies the pendingDirective before any routing.
|
|
2017
|
+
const ownedFields = selectedFlow.reentrant
|
|
2018
|
+
? [
|
|
2019
|
+
...(selectedFlow.requiredFields ?? []),
|
|
2020
|
+
...(selectedFlow.optionalFields ?? []),
|
|
2021
|
+
]
|
|
2022
|
+
: undefined;
|
|
2023
|
+
let nextSession = completeCurrentFlow(session, {
|
|
2024
|
+
clearOwnedFields: ownedFields,
|
|
1720
2025
|
});
|
|
1721
|
-
//
|
|
1722
|
-
const completionSchema = {
|
|
1723
|
-
type: "object",
|
|
1724
|
-
properties: {
|
|
1725
|
-
message: {
|
|
1726
|
-
type: "string",
|
|
1727
|
-
description: "A natural, warm farewell message for the user. Must NOT contain task names, field names, collected data, or any internal/technical information.",
|
|
1728
|
-
},
|
|
1729
|
-
},
|
|
1730
|
-
required: ["message"],
|
|
1731
|
-
additionalProperties: false,
|
|
1732
|
-
};
|
|
1733
|
-
const templateContext = createTemplateContext({ context, session, history: historyEvents });
|
|
1734
|
-
// Build completion response prompt using ResponseEngine
|
|
1735
|
-
// Filter out conditional guidelines - only include always-active ones
|
|
1736
|
-
const alwaysActiveGuidelines = [
|
|
1737
|
-
...this.agent.getGuidelines().filter(g => !g.condition),
|
|
1738
|
-
...selectedRoute.getGuidelines().filter(g => !g.condition),
|
|
1739
|
-
];
|
|
1740
|
-
let completitionPrompt = "Send a brief, natural farewell message. Do NOT mention internal data or task details.";
|
|
1741
|
-
if (endStepSpec.prompt) {
|
|
1742
|
-
completitionPrompt = await render(endStepSpec.prompt, templateContext);
|
|
1743
|
-
}
|
|
1744
|
-
const completionPrompt = await this.responseEngine.buildResponsePrompt({
|
|
1745
|
-
route: selectedRoute,
|
|
1746
|
-
currentStep: completionStep,
|
|
1747
|
-
rules: selectedRoute.getRules(),
|
|
1748
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1749
|
-
directives: [
|
|
1750
|
-
"The conversation task has been completed successfully",
|
|
1751
|
-
"Generate a natural, friendly farewell message for the user",
|
|
1752
|
-
"Do NOT mention task names, route names, collected data, field names, or any internal/technical information",
|
|
1753
|
-
"Do NOT list or summarize the data you collected - the user already knows what they told you",
|
|
1754
|
-
"Do NOT use words like 'tarefa', 'dados coletados', 'prospecção', 'concluída' or similar internal terms",
|
|
1755
|
-
"Keep it brief, warm, and conversational - as if ending a natural conversation",
|
|
1756
|
-
"Do NOT ask for more information - the conversation is ending",
|
|
1757
|
-
completitionPrompt,
|
|
1758
|
-
],
|
|
1759
|
-
history: historyEvents,
|
|
1760
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
1761
|
-
combinedGuidelines: alwaysActiveGuidelines, // Only non-conditional guidelines
|
|
1762
|
-
combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
|
|
1763
|
-
context,
|
|
1764
|
-
session,
|
|
1765
|
-
agentSchema: undefined, // No data collection schema for completion
|
|
1766
|
-
});
|
|
1767
|
-
// Generate completion message using AI provider
|
|
1768
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
1769
|
-
logger.debug(`[ResponseModal] Calling AI provider for completion message...`);
|
|
1770
|
-
const completionResult = await agentOptions.provider.generateMessage({
|
|
1771
|
-
prompt: completionPrompt,
|
|
1772
|
-
history, // Use HistoryItem[] for AI provider
|
|
1773
|
-
context,
|
|
1774
|
-
signal,
|
|
1775
|
-
parameters: { jsonSchema: completionSchema, schemaName: "completion_message" },
|
|
1776
|
-
});
|
|
1777
|
-
logger.debug(`[ResponseModal] AI provider returned completion result`);
|
|
1778
|
-
const message = completionResult.structured?.message || completionResult.message;
|
|
1779
|
-
logger.debug(`[ResponseModal] Generated completion message for route: ${selectedRoute.title}`);
|
|
1780
|
-
// Check for onComplete transition
|
|
1781
|
-
const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
|
|
2026
|
+
// 3) Wire pendingDirective when onComplete returned a target.
|
|
1782
2027
|
if (transitionConfig) {
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2028
|
+
const goToTarget = typeof transitionConfig.goTo === 'string'
|
|
2029
|
+
? transitionConfig.goTo
|
|
2030
|
+
: transitionConfig.goTo?.flow;
|
|
2031
|
+
const targetFlow = goToTarget ? this.agent.getFlows().find((r) => r.id === goToTarget ||
|
|
2032
|
+
r.title === goToTarget) : undefined;
|
|
2033
|
+
if (targetFlow) {
|
|
2034
|
+
nextSession = {
|
|
2035
|
+
...nextSession,
|
|
2036
|
+
pendingDirective: {
|
|
2037
|
+
goTo: targetFlow.id,
|
|
2038
|
+
},
|
|
1792
2039
|
};
|
|
1793
|
-
logger.debug(`[ResponseModal]
|
|
2040
|
+
logger.debug(`[ResponseModal] Flow ${selectedFlow.title} completed with pending directive to: ${targetFlow.title}`);
|
|
1794
2041
|
}
|
|
1795
|
-
else {
|
|
1796
|
-
logger.warn(`[
|
|
2042
|
+
else if (goToTarget) {
|
|
2043
|
+
logger.warn(`[FlowConfigurationError] onComplete target not found: flow "${selectedFlow.title}" completed but onComplete target "${goToTarget}" does not match any flow. ` +
|
|
2044
|
+
`Fix the onComplete value to reference an existing flow id/title, or remove onComplete to release the session to idle.`);
|
|
1797
2045
|
}
|
|
1798
2046
|
}
|
|
1799
|
-
|
|
2047
|
+
else {
|
|
2048
|
+
logger.debug(`[ResponseModal] Flow ${selectedFlow.title} completed; session released to idle.`);
|
|
2049
|
+
}
|
|
2050
|
+
return nextSession;
|
|
1800
2051
|
}
|
|
1801
2052
|
/**
|
|
1802
|
-
* Stream
|
|
2053
|
+
* Stream flow completion response
|
|
1803
2054
|
* @private
|
|
1804
2055
|
*/
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
const
|
|
1820
|
-
const
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
route: selectedRoute,
|
|
1824
|
-
currentStep: completionStep,
|
|
1825
|
-
rules: selectedRoute.getRules(),
|
|
1826
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1827
|
-
directives: undefined, // No directives for completion
|
|
1828
|
-
history: historyEvents,
|
|
1829
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
1830
|
-
combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
|
|
1831
|
-
combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
|
|
1832
|
-
context,
|
|
1833
|
-
session,
|
|
1834
|
-
agentSchema: this.agent.getSchema(),
|
|
1835
|
-
});
|
|
1836
|
-
// Stream completion message using AI provider
|
|
1837
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
1838
|
-
const stream = agentOptions.provider.generateMessageStream({
|
|
1839
|
-
prompt: completionPrompt,
|
|
1840
|
-
history, // Use HistoryItem[] for AI provider
|
|
2056
|
+
/**
|
|
2057
|
+
* Stream a flow completion as a single terminal chunk.
|
|
2058
|
+
*
|
|
2059
|
+
* No LLM call is made. The framework no longer authors a farewell — the
|
|
2060
|
+
* completion path is a pure state transition. The chunk emits an empty
|
|
2061
|
+
* `delta` and a `done: true` flag with the idle session attached so
|
|
2062
|
+
* downstream consumers can finalize cleanly.
|
|
2063
|
+
*
|
|
2064
|
+
* If the developer wants closing copy in a streaming response, they
|
|
2065
|
+
* should add a final interactive step whose own LLM turn delivers it.
|
|
2066
|
+
*
|
|
2067
|
+
* @private
|
|
2068
|
+
*/
|
|
2069
|
+
async *streamFlowCompletion(params) {
|
|
2070
|
+
const { selectedFlow, context, history } = params;
|
|
2071
|
+
const session = await this.applyFlowCompletion({
|
|
2072
|
+
selectedFlow,
|
|
2073
|
+
session: params.session,
|
|
1841
2074
|
context,
|
|
1842
|
-
|
|
1843
|
-
parameters: { jsonSchema: responseSchema, schemaName: "completion_message_stream" },
|
|
2075
|
+
history,
|
|
1844
2076
|
});
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
pendingTransition: {
|
|
1857
|
-
targetRouteId: targetRoute.id,
|
|
1858
|
-
condition: renderedCondition,
|
|
1859
|
-
reason: "route_complete",
|
|
1860
|
-
},
|
|
1861
|
-
};
|
|
1862
|
-
logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
|
|
1863
|
-
}
|
|
1864
|
-
else {
|
|
1865
|
-
logger.warn(`[ResponseModal] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
// Set step to END_ROUTE marker
|
|
1869
|
-
session = enterStep(session, END_ROUTE_ID, "Route completed");
|
|
1870
|
-
logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
|
|
1871
|
-
// Stream completion chunks
|
|
1872
|
-
for await (const chunk of stream) {
|
|
1873
|
-
// Update current session if we have one
|
|
1874
|
-
if (chunk.done) {
|
|
1875
|
-
await this.finalizeSession(session, context);
|
|
1876
|
-
}
|
|
1877
|
-
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
1878
|
-
// - executedSteps: empty for route completion (no new steps executed)
|
|
1879
|
-
// - stoppedReason: 'route_complete' for completed routes
|
|
1880
|
-
// - session.currentStep: set to END_ROUTE
|
|
1881
|
-
yield {
|
|
1882
|
-
delta: chunk.delta,
|
|
1883
|
-
accumulated: chunk.accumulated,
|
|
1884
|
-
done: chunk.done,
|
|
1885
|
-
session,
|
|
1886
|
-
toolCalls: undefined,
|
|
1887
|
-
isRouteComplete: true,
|
|
1888
|
-
executedSteps: chunk.done ? [] : undefined,
|
|
1889
|
-
stoppedReason: chunk.done ? 'route_complete' : undefined,
|
|
1890
|
-
metadata: chunk.metadata,
|
|
1891
|
-
structured: chunk.structured,
|
|
1892
|
-
};
|
|
1893
|
-
}
|
|
2077
|
+
await this.finalizeSession(session, context);
|
|
2078
|
+
yield {
|
|
2079
|
+
delta: '',
|
|
2080
|
+
accumulated: '',
|
|
2081
|
+
done: true,
|
|
2082
|
+
session,
|
|
2083
|
+
toolCalls: undefined,
|
|
2084
|
+
isFlowComplete: true,
|
|
2085
|
+
executedSteps: [],
|
|
2086
|
+
stoppedReason: params.stoppedReason ?? 'completed',
|
|
2087
|
+
};
|
|
1894
2088
|
}
|
|
1895
2089
|
/**
|
|
1896
|
-
* Generate fallback response when no
|
|
2090
|
+
* Generate fallback response when no flows are available
|
|
1897
2091
|
* @private
|
|
1898
2092
|
*/
|
|
1899
2093
|
async generateFallbackResponse(params) {
|
|
1900
2094
|
const { history, context, session, signal } = params;
|
|
1901
|
-
logger.debug(`[ResponseModal] No
|
|
1902
|
-
// Build basic response prompt without
|
|
1903
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
2095
|
+
logger.debug(`[ResponseModal] No flow selected, generating basic response`);
|
|
2096
|
+
// Build basic response prompt without flow context
|
|
2097
|
+
const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
|
|
1904
2098
|
agentOptions: this.agent.getAgentOptions(),
|
|
1905
2099
|
terms: this.agent.getTerms(),
|
|
1906
|
-
|
|
2100
|
+
instructions: this.collectScopedInstructions(),
|
|
1907
2101
|
context,
|
|
1908
2102
|
session,
|
|
1909
2103
|
});
|
|
@@ -1923,18 +2117,18 @@ export class ResponseModal {
|
|
|
1923
2117
|
schemaName: "fallback_response",
|
|
1924
2118
|
},
|
|
1925
2119
|
});
|
|
1926
|
-
return result.structured?.message || result.message;
|
|
2120
|
+
return { message: result.structured?.message || result.message, appliedInstructions };
|
|
1927
2121
|
}
|
|
1928
2122
|
/**
|
|
1929
|
-
* Stream fallback response when no
|
|
2123
|
+
* Stream fallback response when no flows are available
|
|
1930
2124
|
* @private
|
|
1931
2125
|
*/
|
|
1932
2126
|
async *streamFallbackResponse(params) {
|
|
1933
2127
|
const { history, context, session, signal } = params;
|
|
1934
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
2128
|
+
const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
|
|
1935
2129
|
agentOptions: this.agent.getAgentOptions(),
|
|
1936
2130
|
terms: this.agent.getTerms(),
|
|
1937
|
-
|
|
2131
|
+
instructions: this.collectScopedInstructions(),
|
|
1938
2132
|
context,
|
|
1939
2133
|
session,
|
|
1940
2134
|
});
|
|
@@ -1960,8 +2154,8 @@ export class ResponseModal {
|
|
|
1960
2154
|
await this.finalizeSession(session, context);
|
|
1961
2155
|
}
|
|
1962
2156
|
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
1963
|
-
// - executedSteps: empty for fallback (no
|
|
1964
|
-
// - stoppedReason: undefined for fallback (no
|
|
2157
|
+
// - executedSteps: empty for fallback (no flow/step execution)
|
|
2158
|
+
// - stoppedReason: undefined for fallback (no flow context)
|
|
1965
2159
|
// - session.currentStep: unchanged (no step progression)
|
|
1966
2160
|
yield {
|
|
1967
2161
|
delta: chunk.delta,
|
|
@@ -1969,11 +2163,12 @@ export class ResponseModal {
|
|
|
1969
2163
|
done: chunk.done,
|
|
1970
2164
|
session,
|
|
1971
2165
|
toolCalls: undefined,
|
|
1972
|
-
|
|
2166
|
+
isFlowComplete: false,
|
|
1973
2167
|
executedSteps: chunk.done ? [] : undefined,
|
|
1974
2168
|
stoppedReason: undefined,
|
|
1975
2169
|
metadata: chunk.metadata,
|
|
1976
2170
|
structured: chunk.structured,
|
|
2171
|
+
appliedInstructions: chunk.done ? appliedInstructions : undefined,
|
|
1977
2172
|
};
|
|
1978
2173
|
}
|
|
1979
2174
|
}
|
|
@@ -1994,52 +2189,52 @@ export class ResponseModal {
|
|
|
1994
2189
|
// Execute finalize function
|
|
1995
2190
|
await this.executeStepFinalize(session, context);
|
|
1996
2191
|
// Update current session if we have one
|
|
1997
|
-
const currentSession = this.agent.
|
|
2192
|
+
const currentSession = this.agent.currentSession;
|
|
1998
2193
|
if (currentSession) {
|
|
1999
|
-
this.agent.
|
|
2194
|
+
this.agent.currentSession = session;
|
|
2000
2195
|
}
|
|
2001
2196
|
}
|
|
2002
2197
|
// ============================================================================
|
|
2003
2198
|
// UTILITY METHODS - Helper methods for tool management and other utilities
|
|
2004
2199
|
// ============================================================================
|
|
2005
2200
|
/**
|
|
2006
|
-
* Find an available tool by name for the given
|
|
2201
|
+
* Find an available tool by name for the given flow using ToolManager
|
|
2007
2202
|
* Delegates to ToolManager for unified tool resolution
|
|
2008
2203
|
* @private
|
|
2009
2204
|
*/
|
|
2010
|
-
findAvailableTool(toolName,
|
|
2205
|
+
findAvailableTool(toolName, flow) {
|
|
2011
2206
|
// Use ToolManager for unified tool resolution
|
|
2012
2207
|
const toolManager = this.getToolManager();
|
|
2013
2208
|
if (toolManager) {
|
|
2014
|
-
return toolManager.find(toolName, undefined, undefined,
|
|
2209
|
+
return toolManager.find(toolName, undefined, undefined, flow);
|
|
2015
2210
|
}
|
|
2016
2211
|
// Fallback to legacy resolution if ToolManager not available
|
|
2017
2212
|
logger.warn(`[ResponseModal] ToolManager not available, using legacy tool resolution for: ${toolName}`);
|
|
2018
|
-
// Check
|
|
2019
|
-
if (
|
|
2020
|
-
const
|
|
2213
|
+
// Check flow-level tools first (if flow provided)
|
|
2214
|
+
if (flow) {
|
|
2215
|
+
const flowTool = flow
|
|
2021
2216
|
.getTools()
|
|
2022
|
-
.find((tool) => tool.id === toolName || tool.
|
|
2023
|
-
if (
|
|
2024
|
-
return
|
|
2217
|
+
.find((tool) => tool.id === toolName || tool.id === toolName);
|
|
2218
|
+
if (flowTool)
|
|
2219
|
+
return flowTool;
|
|
2025
2220
|
}
|
|
2026
2221
|
// Fall back to agent-level tools
|
|
2027
2222
|
const agentTools = this.agent.getTools();
|
|
2028
|
-
return agentTools.find((tool) => tool.id === toolName || tool.
|
|
2223
|
+
return agentTools.find((tool) => tool.id === toolName || tool.id === toolName);
|
|
2029
2224
|
}
|
|
2030
2225
|
/**
|
|
2031
|
-
* Collect all available tools for the given
|
|
2226
|
+
* Collect all available tools for the given flow and step context using ToolManager
|
|
2032
2227
|
* Delegates to ToolManager for unified tool resolution and deduplication
|
|
2033
2228
|
* @private
|
|
2034
2229
|
*/
|
|
2035
|
-
collectAvailableTools(
|
|
2230
|
+
collectAvailableTools(flow, step) {
|
|
2036
2231
|
// Use ToolManager for unified tool collection if available
|
|
2037
2232
|
const toolManager = this.getToolManager();
|
|
2038
2233
|
if (toolManager) {
|
|
2039
|
-
const availableTools = toolManager.getAvailable(undefined, step,
|
|
2234
|
+
const availableTools = toolManager.getAvailable(undefined, step, flow);
|
|
2040
2235
|
return availableTools.map((tool) => ({
|
|
2041
2236
|
id: tool.id,
|
|
2042
|
-
name: tool.
|
|
2237
|
+
name: tool.id || tool.id,
|
|
2043
2238
|
description: tool.description,
|
|
2044
2239
|
parameters: tool.parameters,
|
|
2045
2240
|
}));
|
|
@@ -2051,9 +2246,9 @@ export class ResponseModal {
|
|
|
2051
2246
|
this.agent.getTools().forEach((tool) => {
|
|
2052
2247
|
availableTools.set(tool.id, tool);
|
|
2053
2248
|
});
|
|
2054
|
-
// Add
|
|
2055
|
-
if (
|
|
2056
|
-
|
|
2249
|
+
// Add flow-level tools (these take precedence)
|
|
2250
|
+
if (flow) {
|
|
2251
|
+
flow.getTools().forEach((tool) => {
|
|
2057
2252
|
availableTools.set(tool.id, tool);
|
|
2058
2253
|
});
|
|
2059
2254
|
}
|
|
@@ -2096,7 +2291,7 @@ export class ResponseModal {
|
|
|
2096
2291
|
// Convert to the format expected by AI providers
|
|
2097
2292
|
return Array.from(availableTools.values()).map((tool) => ({
|
|
2098
2293
|
id: tool.id,
|
|
2099
|
-
name: tool.
|
|
2294
|
+
name: tool.id || tool.id,
|
|
2100
2295
|
description: tool.description,
|
|
2101
2296
|
parameters: tool.parameters,
|
|
2102
2297
|
}));
|
|
@@ -2105,7 +2300,7 @@ export class ResponseModal {
|
|
|
2105
2300
|
* Execute a prepare or finalize function/tool
|
|
2106
2301
|
* @private
|
|
2107
2302
|
*/
|
|
2108
|
-
async executePrepareFinalize(prepareOrFinalize, context, data,
|
|
2303
|
+
async executePrepareFinalize(prepareOrFinalize, context, data, flow, step) {
|
|
2109
2304
|
if (!prepareOrFinalize)
|
|
2110
2305
|
return;
|
|
2111
2306
|
if (typeof prepareOrFinalize === "function") {
|
|
@@ -2119,7 +2314,7 @@ export class ResponseModal {
|
|
|
2119
2314
|
// Tool ID - use ToolManager for unified resolution
|
|
2120
2315
|
const toolManager = this.getToolManager();
|
|
2121
2316
|
if (toolManager) {
|
|
2122
|
-
tool = toolManager.find(prepareOrFinalize, undefined, step,
|
|
2317
|
+
tool = toolManager.find(prepareOrFinalize, undefined, step, flow);
|
|
2123
2318
|
}
|
|
2124
2319
|
else {
|
|
2125
2320
|
// Fallback to legacy resolution if ToolManager not available
|
|
@@ -2129,9 +2324,9 @@ export class ResponseModal {
|
|
|
2129
2324
|
this.agent.getTools().forEach((t) => {
|
|
2130
2325
|
availableTools.set(t.id, t);
|
|
2131
2326
|
});
|
|
2132
|
-
// Add
|
|
2133
|
-
if (
|
|
2134
|
-
|
|
2327
|
+
// Add flow-level tools
|
|
2328
|
+
if (flow) {
|
|
2329
|
+
flow.getTools().forEach((t) => {
|
|
2135
2330
|
availableTools.set(t.id, t);
|
|
2136
2331
|
});
|
|
2137
2332
|
}
|
|
@@ -2183,23 +2378,5 @@ export class ResponseModal {
|
|
|
2183
2378
|
}
|
|
2184
2379
|
}
|
|
2185
2380
|
}
|
|
2186
|
-
/**
|
|
2187
|
-
* Merge terms with route-specific taking precedence on conflicts
|
|
2188
|
-
* @private
|
|
2189
|
-
*/
|
|
2190
|
-
mergeTerms(agentTerms, routeTerms) {
|
|
2191
|
-
const merged = new Map();
|
|
2192
|
-
// Add agent terms first
|
|
2193
|
-
agentTerms.forEach((term) => {
|
|
2194
|
-
const name = typeof term.name === "string" ? term.name : term.name.toString();
|
|
2195
|
-
merged.set(name, term);
|
|
2196
|
-
});
|
|
2197
|
-
// Add route terms (these take precedence)
|
|
2198
|
-
routeTerms.forEach((term) => {
|
|
2199
|
-
const name = typeof term.name === "string" ? term.name : term.name.toString();
|
|
2200
|
-
merged.set(name, term);
|
|
2201
|
-
});
|
|
2202
|
-
return Array.from(merged.values());
|
|
2203
|
-
}
|
|
2204
2381
|
}
|
|
2205
2382
|
//# sourceMappingURL=ResponseModal.js.map
|