@falai/agent 3.4.5 → 4.0.0-alpha.10
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 +41 -34
- package/dist/cjs/core/Agent.d.ts +29 -378
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +113 -1178
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/CompactionEngine.d.ts.map +1 -1
- package/dist/cjs/core/CompactionEngine.js +5 -3
- package/dist/cjs/core/CompactionEngine.js.map +1 -1
- package/dist/cjs/core/FlowSpec.d.ts +136 -0
- package/dist/cjs/core/FlowSpec.d.ts.map +1 -0
- package/dist/cjs/core/FlowSpec.js +573 -0
- package/dist/cjs/core/FlowSpec.js.map +1 -0
- package/dist/cjs/core/Migrate.d.ts +38 -0
- package/dist/cjs/core/Migrate.d.ts.map +1 -0
- package/dist/cjs/core/Migrate.js +270 -0
- package/dist/cjs/core/Migrate.js.map +1 -0
- package/dist/cjs/core/Prompt.d.ts +54 -0
- package/dist/cjs/core/Prompt.d.ts.map +1 -0
- package/dist/cjs/core/Prompt.js +149 -0
- package/dist/cjs/core/Prompt.js.map +1 -0
- package/dist/cjs/core/Runner.d.ts +171 -0
- package/dist/cjs/core/Runner.d.ts.map +1 -0
- package/dist/cjs/core/Runner.js +1158 -0
- package/dist/cjs/core/Runner.js.map +1 -0
- package/dist/cjs/core/Speak.d.ts +37 -0
- package/dist/cjs/core/Speak.d.ts.map +1 -0
- package/dist/cjs/core/Speak.js +373 -0
- package/dist/cjs/core/Speak.js.map +1 -0
- package/dist/cjs/core/Understand.d.ts +28 -0
- package/dist/cjs/core/Understand.d.ts.map +1 -0
- package/dist/cjs/core/Understand.js +357 -0
- package/dist/cjs/core/Understand.js.map +1 -0
- package/dist/cjs/core/contracts.d.ts +122 -0
- package/dist/cjs/core/contracts.d.ts.map +1 -0
- package/dist/cjs/core/contracts.js +11 -0
- package/dist/cjs/core/contracts.js.map +1 -0
- package/dist/cjs/core/falai.d.ts +57 -0
- package/dist/cjs/core/falai.d.ts.map +1 -0
- package/dist/cjs/core/falai.js +43 -0
- package/dist/cjs/core/falai.js.map +1 -0
- package/dist/cjs/core/predicate.d.ts +9 -0
- package/dist/cjs/core/predicate.d.ts.map +1 -0
- package/dist/cjs/core/predicate.js +58 -0
- package/dist/cjs/core/predicate.js.map +1 -0
- package/dist/cjs/index.d.ts +26 -31
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +46 -68
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/persistence/MemoryStore.d.ts +15 -0
- package/dist/cjs/persistence/MemoryStore.d.ts.map +1 -0
- package/dist/cjs/persistence/MemoryStore.js +39 -0
- package/dist/cjs/persistence/MemoryStore.js.map +1 -0
- package/dist/cjs/persistence/MongoStore.d.ts +42 -0
- package/dist/cjs/persistence/MongoStore.d.ts.map +1 -0
- package/dist/cjs/persistence/MongoStore.js +60 -0
- package/dist/cjs/persistence/MongoStore.js.map +1 -0
- package/dist/cjs/persistence/OpenSearchStore.d.ts +86 -0
- package/dist/cjs/persistence/OpenSearchStore.d.ts.map +1 -0
- package/dist/cjs/persistence/OpenSearchStore.js +120 -0
- package/dist/cjs/persistence/OpenSearchStore.js.map +1 -0
- package/dist/cjs/persistence/PostgresStore.d.ts +41 -0
- package/dist/cjs/persistence/PostgresStore.d.ts.map +1 -0
- package/dist/cjs/persistence/PostgresStore.js +58 -0
- package/dist/cjs/persistence/PostgresStore.js.map +1 -0
- package/dist/cjs/persistence/PrismaStore.d.ts +65 -0
- package/dist/cjs/persistence/PrismaStore.d.ts.map +1 -0
- package/dist/cjs/persistence/PrismaStore.js +95 -0
- package/dist/cjs/persistence/PrismaStore.js.map +1 -0
- package/dist/cjs/persistence/RedisStore.d.ts +34 -0
- package/dist/cjs/persistence/RedisStore.d.ts.map +1 -0
- package/dist/cjs/persistence/RedisStore.js +61 -0
- package/dist/cjs/persistence/RedisStore.js.map +1 -0
- package/dist/cjs/persistence/SQLiteStore.d.ts +45 -0
- package/dist/cjs/persistence/SQLiteStore.d.ts.map +1 -0
- package/dist/cjs/persistence/SQLiteStore.js +74 -0
- package/dist/cjs/persistence/SQLiteStore.js.map +1 -0
- package/dist/cjs/persistence/sessionRow.d.ts +14 -0
- package/dist/cjs/persistence/sessionRow.d.ts.map +1 -0
- package/dist/cjs/persistence/sessionRow.js +50 -0
- package/dist/cjs/persistence/sessionRow.js.map +1 -0
- package/dist/cjs/providers/DeepSeekProvider.d.ts.map +1 -1
- package/dist/cjs/providers/DeepSeekProvider.js +8 -3
- package/dist/cjs/providers/DeepSeekProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +4 -3
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +4 -3
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +4 -0
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.js +2 -0
- package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.js +2 -4
- package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
- package/dist/cjs/providers/ProviderAdapter.d.ts +11 -6
- package/dist/cjs/providers/ProviderAdapter.d.ts.map +1 -1
- package/dist/cjs/providers/ProviderAdapter.js +33 -10
- package/dist/cjs/providers/ProviderAdapter.js.map +1 -1
- package/dist/cjs/providers/ZaiProvider.d.ts +6 -4
- package/dist/cjs/providers/ZaiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/ZaiProvider.js +6 -4
- package/dist/cjs/providers/ZaiProvider.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +163 -383
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js +1 -1
- package/dist/cjs/types/ai.d.ts +32 -1
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/compaction.d.ts +3 -1
- package/dist/cjs/types/compaction.d.ts.map +1 -1
- package/dist/cjs/types/errors.d.ts +9 -12
- package/dist/cjs/types/errors.d.ts.map +1 -1
- package/dist/cjs/types/errors.js +14 -17
- package/dist/cjs/types/errors.js.map +1 -1
- package/dist/cjs/types/flow.d.ts +265 -513
- package/dist/cjs/types/flow.d.ts.map +1 -1
- package/dist/cjs/types/flow.js +7 -1
- package/dist/cjs/types/flow.js.map +1 -1
- package/dist/cjs/types/history.d.ts +7 -18
- package/dist/cjs/types/history.d.ts.map +1 -1
- package/dist/cjs/types/history.js.map +1 -1
- package/dist/cjs/types/index.d.ts +9 -15
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +4 -14
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/session.d.ts +94 -64
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/session.js +5 -1
- package/dist/cjs/types/session.js.map +1 -1
- package/dist/cjs/types/tool.d.ts +37 -207
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/types/tool.js +5 -14
- package/dist/cjs/types/tool.js.map +1 -1
- package/dist/cjs/utils/clock.d.ts +28 -0
- package/dist/cjs/utils/clock.d.ts.map +1 -0
- package/dist/cjs/utils/clock.js +64 -0
- package/dist/cjs/utils/clock.js.map +1 -0
- package/dist/cjs/utils/duration.d.ts +11 -0
- package/dist/cjs/utils/duration.d.ts.map +1 -0
- package/dist/cjs/utils/duration.js +31 -0
- package/dist/cjs/utils/duration.js.map +1 -0
- package/dist/cjs/utils/history.d.ts +4 -1
- package/dist/cjs/utils/history.d.ts.map +1 -1
- package/dist/cjs/utils/history.js +2 -2
- package/dist/cjs/utils/history.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +4 -10
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +14 -61
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/json.d.ts +2 -0
- package/dist/cjs/utils/json.d.ts.map +1 -1
- package/dist/cjs/utils/json.js +5 -0
- package/dist/cjs/utils/json.js.map +1 -1
- package/dist/cjs/utils/outcomes.d.ts +48 -0
- package/dist/cjs/utils/outcomes.d.ts.map +1 -0
- package/dist/cjs/utils/outcomes.js +51 -0
- package/dist/cjs/utils/outcomes.js.map +1 -0
- package/dist/cjs/utils/phrases.d.ts +25 -0
- package/dist/cjs/utils/phrases.d.ts.map +1 -0
- package/dist/cjs/utils/phrases.js +38 -0
- package/dist/cjs/utils/phrases.js.map +1 -0
- package/dist/cjs/utils/schema.d.ts +50 -0
- package/dist/cjs/utils/schema.d.ts.map +1 -0
- package/dist/cjs/utils/schema.js +138 -0
- package/dist/cjs/utils/schema.js.map +1 -0
- package/dist/cjs/utils/streamingMessage.d.ts +3 -2
- package/dist/cjs/utils/streamingMessage.d.ts.map +1 -1
- package/dist/cjs/utils/streamingMessage.js +38 -4
- package/dist/cjs/utils/streamingMessage.js.map +1 -1
- package/dist/cjs/utils/template.d.ts +22 -150
- package/dist/cjs/utils/template.d.ts.map +1 -1
- package/dist/cjs/utils/template.js +64 -359
- package/dist/cjs/utils/template.js.map +1 -1
- package/dist/cjs/utils/usage.d.ts +19 -0
- package/dist/cjs/utils/usage.d.ts.map +1 -0
- package/dist/cjs/utils/usage.js +35 -0
- package/dist/cjs/utils/usage.js.map +1 -0
- package/dist/core/Agent.d.ts +29 -378
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +116 -1181
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/CompactionEngine.d.ts.map +1 -1
- package/dist/core/CompactionEngine.js +5 -3
- package/dist/core/CompactionEngine.js.map +1 -1
- package/dist/core/FlowSpec.d.ts +136 -0
- package/dist/core/FlowSpec.d.ts.map +1 -0
- package/dist/core/FlowSpec.js +567 -0
- package/dist/core/FlowSpec.js.map +1 -0
- package/dist/core/Migrate.d.ts +38 -0
- package/dist/core/Migrate.d.ts.map +1 -0
- package/dist/core/Migrate.js +264 -0
- package/dist/core/Migrate.js.map +1 -0
- package/dist/core/Prompt.d.ts +54 -0
- package/dist/core/Prompt.d.ts.map +1 -0
- package/dist/core/Prompt.js +139 -0
- package/dist/core/Prompt.js.map +1 -0
- package/dist/core/Runner.d.ts +171 -0
- package/dist/core/Runner.d.ts.map +1 -0
- package/dist/core/Runner.js +1154 -0
- package/dist/core/Runner.js.map +1 -0
- package/dist/core/Speak.d.ts +37 -0
- package/dist/core/Speak.d.ts.map +1 -0
- package/dist/core/Speak.js +369 -0
- package/dist/core/Speak.js.map +1 -0
- package/dist/core/Understand.d.ts +28 -0
- package/dist/core/Understand.d.ts.map +1 -0
- package/dist/core/Understand.js +353 -0
- package/dist/core/Understand.js.map +1 -0
- package/dist/core/contracts.d.ts +122 -0
- package/dist/core/contracts.d.ts.map +1 -0
- package/dist/core/contracts.js +10 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/falai.d.ts +57 -0
- package/dist/core/falai.d.ts.map +1 -0
- package/dist/core/falai.js +40 -0
- package/dist/core/falai.js.map +1 -0
- package/dist/core/predicate.d.ts +9 -0
- package/dist/core/predicate.d.ts.map +1 -0
- package/dist/core/predicate.js +54 -0
- package/dist/core/predicate.js.map +1 -0
- package/dist/index.d.ts +26 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -24
- package/dist/index.js.map +1 -1
- package/dist/persistence/MemoryStore.d.ts +15 -0
- package/dist/persistence/MemoryStore.d.ts.map +1 -0
- package/dist/persistence/MemoryStore.js +35 -0
- package/dist/persistence/MemoryStore.js.map +1 -0
- package/dist/persistence/MongoStore.d.ts +42 -0
- package/dist/persistence/MongoStore.d.ts.map +1 -0
- package/dist/persistence/MongoStore.js +56 -0
- package/dist/persistence/MongoStore.js.map +1 -0
- package/dist/persistence/OpenSearchStore.d.ts +86 -0
- package/dist/persistence/OpenSearchStore.d.ts.map +1 -0
- package/dist/persistence/OpenSearchStore.js +116 -0
- package/dist/persistence/OpenSearchStore.js.map +1 -0
- package/dist/persistence/PostgresStore.d.ts +41 -0
- package/dist/persistence/PostgresStore.d.ts.map +1 -0
- package/dist/persistence/PostgresStore.js +54 -0
- package/dist/persistence/PostgresStore.js.map +1 -0
- package/dist/persistence/PrismaStore.d.ts +65 -0
- package/dist/persistence/PrismaStore.d.ts.map +1 -0
- package/dist/persistence/PrismaStore.js +91 -0
- package/dist/persistence/PrismaStore.js.map +1 -0
- package/dist/persistence/RedisStore.d.ts +34 -0
- package/dist/persistence/RedisStore.d.ts.map +1 -0
- package/dist/persistence/RedisStore.js +57 -0
- package/dist/persistence/RedisStore.js.map +1 -0
- package/dist/persistence/SQLiteStore.d.ts +45 -0
- package/dist/persistence/SQLiteStore.d.ts.map +1 -0
- package/dist/persistence/SQLiteStore.js +70 -0
- package/dist/persistence/SQLiteStore.js.map +1 -0
- package/dist/persistence/sessionRow.d.ts +14 -0
- package/dist/persistence/sessionRow.d.ts.map +1 -0
- package/dist/persistence/sessionRow.js +45 -0
- package/dist/persistence/sessionRow.js.map +1 -0
- package/dist/providers/DeepSeekProvider.d.ts.map +1 -1
- package/dist/providers/DeepSeekProvider.js +8 -3
- package/dist/providers/DeepSeekProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +4 -3
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +4 -3
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.d.ts +4 -0
- package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.js +2 -0
- package/dist/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/providers/OpenRouterProvider.js +2 -4
- package/dist/providers/OpenRouterProvider.js.map +1 -1
- package/dist/providers/ProviderAdapter.d.ts +11 -6
- package/dist/providers/ProviderAdapter.d.ts.map +1 -1
- package/dist/providers/ProviderAdapter.js +34 -11
- package/dist/providers/ProviderAdapter.js.map +1 -1
- package/dist/providers/ZaiProvider.d.ts +6 -4
- package/dist/providers/ZaiProvider.d.ts.map +1 -1
- package/dist/providers/ZaiProvider.js +6 -4
- package/dist/providers/ZaiProvider.js.map +1 -1
- package/dist/types/agent.d.ts +163 -383
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js +1 -1
- package/dist/types/ai.d.ts +32 -1
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/compaction.d.ts +3 -1
- package/dist/types/compaction.d.ts.map +1 -1
- package/dist/types/errors.d.ts +9 -12
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +12 -15
- package/dist/types/errors.js.map +1 -1
- package/dist/types/flow.d.ts +265 -513
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/flow.js +7 -1
- package/dist/types/flow.js.map +1 -1
- package/dist/types/history.d.ts +7 -18
- package/dist/types/history.d.ts.map +1 -1
- package/dist/types/history.js.map +1 -1
- package/dist/types/index.d.ts +9 -15
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -7
- package/dist/types/index.js.map +1 -1
- package/dist/types/session.d.ts +94 -64
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js +5 -1
- package/dist/types/session.js.map +1 -1
- package/dist/types/tool.d.ts +37 -207
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js +6 -13
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/clock.d.ts +28 -0
- package/dist/utils/clock.d.ts.map +1 -0
- package/dist/utils/clock.js +59 -0
- package/dist/utils/clock.js.map +1 -0
- package/dist/utils/duration.d.ts +11 -0
- package/dist/utils/duration.d.ts.map +1 -0
- package/dist/utils/duration.js +26 -0
- package/dist/utils/duration.js.map +1 -0
- package/dist/utils/history.d.ts +4 -1
- package/dist/utils/history.d.ts.map +1 -1
- package/dist/utils/history.js +2 -2
- package/dist/utils/history.js.map +1 -1
- package/dist/utils/index.d.ts +4 -10
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -21
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/json.d.ts +2 -0
- package/dist/utils/json.d.ts.map +1 -1
- package/dist/utils/json.js +4 -0
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/outcomes.d.ts +48 -0
- package/dist/utils/outcomes.d.ts.map +1 -0
- package/dist/utils/outcomes.js +48 -0
- package/dist/utils/outcomes.js.map +1 -0
- package/dist/utils/phrases.d.ts +25 -0
- package/dist/utils/phrases.d.ts.map +1 -0
- package/dist/utils/phrases.js +35 -0
- package/dist/utils/phrases.js.map +1 -0
- package/dist/utils/schema.d.ts +50 -0
- package/dist/utils/schema.d.ts.map +1 -0
- package/dist/utils/schema.js +129 -0
- package/dist/utils/schema.js.map +1 -0
- package/dist/utils/streamingMessage.d.ts +3 -2
- package/dist/utils/streamingMessage.d.ts.map +1 -1
- package/dist/utils/streamingMessage.js +38 -4
- package/dist/utils/streamingMessage.js.map +1 -1
- package/dist/utils/template.d.ts +22 -150
- package/dist/utils/template.d.ts.map +1 -1
- package/dist/utils/template.js +61 -351
- package/dist/utils/template.js.map +1 -1
- package/dist/utils/usage.d.ts +19 -0
- package/dist/utils/usage.d.ts.map +1 -0
- package/dist/utils/usage.js +31 -0
- package/dist/utils/usage.js.map +1 -0
- package/docs/README.md +37 -19
- package/docs/concepts/architecture.md +117 -239
- package/docs/concepts/collection.md +170 -0
- package/docs/concepts/pipeline.md +132 -378
- package/docs/concepts/runs-and-waits.md +192 -0
- package/docs/guides/actions-and-events.md +276 -0
- package/docs/guides/branching.md +119 -208
- package/docs/guides/compaction.md +63 -158
- package/docs/guides/conditions.md +164 -128
- package/docs/guides/error-handling.md +170 -164
- package/docs/guides/flow-control.md +210 -349
- package/docs/guides/flows-from-json.md +224 -0
- package/docs/guides/instructions.md +125 -161
- package/docs/guides/persistence.md +182 -206
- package/docs/guides/streaming.md +50 -114
- package/docs/guides/testing.md +284 -0
- package/docs/guides/triggers.md +401 -0
- package/docs/migration/README.md +8 -15
- package/docs/migration/v1-to-v2.md +1 -1
- package/docs/migration/v2-3-to-v2-4.md +2 -2
- package/docs/migration/v2-6-to-v2-7.md +4 -4
- package/docs/migration/v3-to-v4.md +457 -0
- package/docs/reference/actions-events-conditions.md +396 -0
- package/docs/reference/agent.md +248 -0
- package/docs/reference/branches.md +75 -203
- package/docs/reference/errors.md +188 -144
- package/docs/reference/fields.md +125 -0
- package/docs/reference/flow-spec.md +248 -0
- package/docs/reference/flow.md +104 -192
- package/docs/reference/instruction.md +83 -137
- package/docs/reference/outcomes.md +273 -0
- package/docs/reference/providers.md +525 -302
- package/docs/reference/session.md +210 -0
- package/docs/reference/step.md +194 -312
- package/docs/reference/stores.md +496 -0
- package/docs/reference/tool.md +162 -231
- package/docs/reference/trigger.md +200 -0
- package/docs/rfc/v4-one-flow.md +477 -0
- package/docs/start/01-install.md +59 -44
- package/docs/start/02-first-agent.md +97 -147
- package/docs/start/03-collect-data.md +78 -183
- package/docs/start/04-add-tools.md +159 -227
- package/docs/start/05-go-to-production.md +181 -163
- package/examples/01-quickstart.ts +26 -16
- package/examples/02-fields.ts +75 -0
- package/examples/03-tools.ts +79 -119
- package/examples/04-instructions.ts +60 -87
- package/examples/05-branches.ts +78 -0
- package/examples/06-triggers-and-waits.ts +149 -0
- package/examples/07-streaming.ts +34 -60
- package/examples/08-store-and-migration.ts +97 -0
- package/examples/09-flows-from-json.ts +107 -0
- package/package.json +11 -6
- package/src/core/Agent.ts +126 -1512
- package/src/core/CompactionEngine.ts +7 -4
- package/src/core/FlowSpec.ts +778 -0
- package/src/core/Migrate.ts +256 -0
- package/src/core/Prompt.ts +162 -0
- package/src/core/Runner.ts +1214 -0
- package/src/core/Speak.ts +460 -0
- package/src/core/Understand.ts +423 -0
- package/src/core/contracts.ts +111 -0
- package/src/core/falai.ts +86 -0
- package/src/core/predicate.ts +56 -0
- package/src/index.ts +120 -147
- package/src/persistence/MemoryStore.ts +37 -0
- package/src/persistence/MongoStore.ts +89 -0
- package/src/persistence/OpenSearchStore.ts +153 -0
- package/src/persistence/PostgresStore.ts +89 -0
- package/src/persistence/PrismaStore.ts +127 -0
- package/src/persistence/RedisStore.ts +90 -0
- package/src/persistence/SQLiteStore.ts +103 -0
- package/src/persistence/sessionRow.ts +45 -0
- package/src/providers/DeepSeekProvider.ts +8 -3
- package/src/providers/GeminiProvider.ts +4 -3
- package/src/providers/OpenAICompatibleProvider.ts +6 -0
- package/src/providers/OpenRouterProvider.ts +2 -4
- package/src/providers/ProviderAdapter.ts +46 -13
- package/src/providers/ZaiProvider.ts +6 -4
- package/src/types/agent.ts +135 -397
- package/src/types/ai.ts +33 -1
- package/src/types/compaction.ts +3 -1
- package/src/types/errors.ts +13 -16
- package/src/types/flow.ts +249 -550
- package/src/types/history.ts +7 -20
- package/src/types/index.ts +88 -139
- package/src/types/session.ts +135 -70
- package/src/types/tool.ts +42 -267
- package/src/utils/clock.ts +70 -0
- package/src/utils/duration.ts +33 -0
- package/src/utils/history.ts +3 -2
- package/src/utils/index.ts +8 -66
- package/src/utils/json.ts +5 -0
- package/src/utils/outcomes.ts +56 -0
- package/src/utils/phrases.ts +40 -0
- package/src/utils/schema.ts +145 -0
- package/src/utils/streamingMessage.ts +34 -4
- package/src/utils/template.ts +63 -418
- package/src/utils/usage.ts +37 -0
- package/dist/adapters/MemoryAdapter.d.ts +0 -47
- package/dist/adapters/MemoryAdapter.d.ts.map +0 -1
- package/dist/adapters/MemoryAdapter.js +0 -204
- package/dist/adapters/MemoryAdapter.js.map +0 -1
- package/dist/adapters/MongoAdapter.d.ts +0 -97
- package/dist/adapters/MongoAdapter.d.ts.map +0 -1
- package/dist/adapters/MongoAdapter.js +0 -196
- package/dist/adapters/MongoAdapter.js.map +0 -1
- package/dist/adapters/OpenSearchAdapter.d.ts +0 -169
- package/dist/adapters/OpenSearchAdapter.d.ts.map +0 -1
- package/dist/adapters/OpenSearchAdapter.js +0 -471
- package/dist/adapters/OpenSearchAdapter.js.map +0 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts +0 -85
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +0 -1
- package/dist/adapters/PostgreSQLAdapter.js +0 -308
- package/dist/adapters/PostgreSQLAdapter.js.map +0 -1
- package/dist/adapters/PrismaAdapter.d.ts +0 -115
- package/dist/adapters/PrismaAdapter.d.ts.map +0 -1
- package/dist/adapters/PrismaAdapter.js +0 -406
- package/dist/adapters/PrismaAdapter.js.map +0 -1
- package/dist/adapters/RedisAdapter.d.ts +0 -72
- package/dist/adapters/RedisAdapter.d.ts.map +0 -1
- package/dist/adapters/RedisAdapter.js +0 -286
- package/dist/adapters/RedisAdapter.js.map +0 -1
- package/dist/adapters/SQLiteAdapter.d.ts +0 -86
- package/dist/adapters/SQLiteAdapter.d.ts.map +0 -1
- package/dist/adapters/SQLiteAdapter.js +0 -337
- package/dist/adapters/SQLiteAdapter.js.map +0 -1
- package/dist/adapters/index.d.ts +0 -17
- package/dist/adapters/index.d.ts.map +0 -1
- package/dist/adapters/index.js +0 -11
- package/dist/adapters/index.js.map +0 -1
- package/dist/adapters/sessionRow.d.ts +0 -22
- package/dist/adapters/sessionRow.d.ts.map +0 -1
- package/dist/adapters/sessionRow.js +0 -48
- package/dist/adapters/sessionRow.js.map +0 -1
- package/dist/cjs/adapters/MemoryAdapter.d.ts +0 -47
- package/dist/cjs/adapters/MemoryAdapter.d.ts.map +0 -1
- package/dist/cjs/adapters/MemoryAdapter.js +0 -208
- package/dist/cjs/adapters/MemoryAdapter.js.map +0 -1
- package/dist/cjs/adapters/MongoAdapter.d.ts +0 -97
- package/dist/cjs/adapters/MongoAdapter.d.ts.map +0 -1
- package/dist/cjs/adapters/MongoAdapter.js +0 -200
- package/dist/cjs/adapters/MongoAdapter.js.map +0 -1
- package/dist/cjs/adapters/OpenSearchAdapter.d.ts +0 -169
- package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +0 -1
- package/dist/cjs/adapters/OpenSearchAdapter.js +0 -475
- package/dist/cjs/adapters/OpenSearchAdapter.js.map +0 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +0 -85
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +0 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +0 -312
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +0 -1
- package/dist/cjs/adapters/PrismaAdapter.d.ts +0 -115
- package/dist/cjs/adapters/PrismaAdapter.d.ts.map +0 -1
- package/dist/cjs/adapters/PrismaAdapter.js +0 -410
- package/dist/cjs/adapters/PrismaAdapter.js.map +0 -1
- package/dist/cjs/adapters/RedisAdapter.d.ts +0 -72
- package/dist/cjs/adapters/RedisAdapter.d.ts.map +0 -1
- package/dist/cjs/adapters/RedisAdapter.js +0 -290
- package/dist/cjs/adapters/RedisAdapter.js.map +0 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts +0 -86
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +0 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +0 -341
- package/dist/cjs/adapters/SQLiteAdapter.js.map +0 -1
- package/dist/cjs/adapters/index.d.ts +0 -17
- package/dist/cjs/adapters/index.d.ts.map +0 -1
- package/dist/cjs/adapters/index.js +0 -21
- package/dist/cjs/adapters/index.js.map +0 -1
- package/dist/cjs/adapters/sessionRow.d.ts +0 -22
- package/dist/cjs/adapters/sessionRow.d.ts.map +0 -1
- package/dist/cjs/adapters/sessionRow.js +0 -52
- package/dist/cjs/adapters/sessionRow.js.map +0 -1
- package/dist/cjs/constants/index.d.ts +0 -1
- package/dist/cjs/constants/index.d.ts.map +0 -1
- package/dist/cjs/constants/index.js +0 -4
- package/dist/cjs/constants/index.js.map +0 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts +0 -97
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +0 -1
- package/dist/cjs/core/AutoChainExecutor.js +0 -288
- package/dist/cjs/core/AutoChainExecutor.js.map +0 -1
- package/dist/cjs/core/BranchEvaluator.d.ts +0 -55
- package/dist/cjs/core/BranchEvaluator.d.ts.map +0 -1
- package/dist/cjs/core/BranchEvaluator.js +0 -125
- package/dist/cjs/core/BranchEvaluator.js.map +0 -1
- package/dist/cjs/core/DirectiveChainTracker.d.ts +0 -49
- package/dist/cjs/core/DirectiveChainTracker.d.ts.map +0 -1
- package/dist/cjs/core/DirectiveChainTracker.js +0 -121
- package/dist/cjs/core/DirectiveChainTracker.js.map +0 -1
- package/dist/cjs/core/Events.d.ts +0 -26
- package/dist/cjs/core/Events.d.ts.map +0 -1
- package/dist/cjs/core/Events.js +0 -144
- package/dist/cjs/core/Events.js.map +0 -1
- package/dist/cjs/core/Flow.d.ts +0 -183
- package/dist/cjs/core/Flow.d.ts.map +0 -1
- package/dist/cjs/core/Flow.js +0 -551
- package/dist/cjs/core/Flow.js.map +0 -1
- package/dist/cjs/core/FlowRouter.d.ts +0 -183
- package/dist/cjs/core/FlowRouter.d.ts.map +0 -1
- package/dist/cjs/core/FlowRouter.js +0 -1047
- package/dist/cjs/core/FlowRouter.js.map +0 -1
- package/dist/cjs/core/PersistenceManager.d.ts +0 -114
- package/dist/cjs/core/PersistenceManager.d.ts.map +0 -1
- package/dist/cjs/core/PersistenceManager.js +0 -336
- package/dist/cjs/core/PersistenceManager.js.map +0 -1
- package/dist/cjs/core/PromptComposer.d.ts +0 -47
- package/dist/cjs/core/PromptComposer.d.ts.map +0 -1
- package/dist/cjs/core/PromptComposer.js +0 -397
- package/dist/cjs/core/PromptComposer.js.map +0 -1
- package/dist/cjs/core/PromptSectionCache.d.ts +0 -48
- package/dist/cjs/core/PromptSectionCache.d.ts.map +0 -1
- package/dist/cjs/core/PromptSectionCache.js +0 -108
- package/dist/cjs/core/PromptSectionCache.js.map +0 -1
- package/dist/cjs/core/ResponseEngine.d.ts +0 -43
- package/dist/cjs/core/ResponseEngine.d.ts.map +0 -1
- package/dist/cjs/core/ResponseEngine.js +0 -235
- package/dist/cjs/core/ResponseEngine.js.map +0 -1
- package/dist/cjs/core/ResponseGenerationError.d.ts +0 -30
- package/dist/cjs/core/ResponseGenerationError.d.ts.map +0 -1
- package/dist/cjs/core/ResponseGenerationError.js +0 -35
- package/dist/cjs/core/ResponseGenerationError.js.map +0 -1
- package/dist/cjs/core/ResponseModal.d.ts +0 -305
- package/dist/cjs/core/ResponseModal.d.ts.map +0 -1
- package/dist/cjs/core/ResponseModal.js +0 -1414
- package/dist/cjs/core/ResponseModal.js.map +0 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +0 -220
- package/dist/cjs/core/ResponsePipeline.d.ts.map +0 -1
- package/dist/cjs/core/ResponsePipeline.js +0 -1040
- package/dist/cjs/core/ResponsePipeline.js.map +0 -1
- package/dist/cjs/core/SessionFinalizer.d.ts +0 -34
- package/dist/cjs/core/SessionFinalizer.d.ts.map +0 -1
- package/dist/cjs/core/SessionFinalizer.js +0 -88
- package/dist/cjs/core/SessionFinalizer.js.map +0 -1
- package/dist/cjs/core/SessionManager.d.ts +0 -112
- package/dist/cjs/core/SessionManager.d.ts.map +0 -1
- package/dist/cjs/core/SessionManager.js +0 -308
- package/dist/cjs/core/SessionManager.js.map +0 -1
- package/dist/cjs/core/SignalCoordinator.d.ts +0 -103
- package/dist/cjs/core/SignalCoordinator.d.ts.map +0 -1
- package/dist/cjs/core/SignalCoordinator.js +0 -207
- package/dist/cjs/core/SignalCoordinator.js.map +0 -1
- package/dist/cjs/core/SignalEvaluator.d.ts +0 -86
- package/dist/cjs/core/SignalEvaluator.d.ts.map +0 -1
- package/dist/cjs/core/SignalEvaluator.js +0 -319
- package/dist/cjs/core/SignalEvaluator.js.map +0 -1
- package/dist/cjs/core/SignalProcessor.d.ts +0 -152
- package/dist/cjs/core/SignalProcessor.d.ts.map +0 -1
- package/dist/cjs/core/SignalProcessor.js +0 -505
- package/dist/cjs/core/SignalProcessor.js.map +0 -1
- package/dist/cjs/core/Step.d.ts +0 -184
- package/dist/cjs/core/Step.d.ts.map +0 -1
- package/dist/cjs/core/Step.js +0 -599
- package/dist/cjs/core/Step.js.map +0 -1
- package/dist/cjs/core/StepLifecycle.d.ts +0 -43
- package/dist/cjs/core/StepLifecycle.d.ts.map +0 -1
- package/dist/cjs/core/StepLifecycle.js +0 -180
- package/dist/cjs/core/StepLifecycle.js.map +0 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts +0 -142
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +0 -1
- package/dist/cjs/core/StreamingToolExecutor.js +0 -490
- package/dist/cjs/core/StreamingToolExecutor.js.map +0 -1
- package/dist/cjs/core/ToolLoopExecutor.d.ts +0 -133
- package/dist/cjs/core/ToolLoopExecutor.d.ts.map +0 -1
- package/dist/cjs/core/ToolLoopExecutor.js +0 -568
- package/dist/cjs/core/ToolLoopExecutor.js.map +0 -1
- package/dist/cjs/core/ToolManager.d.ts +0 -250
- package/dist/cjs/core/ToolManager.d.ts.map +0 -1
- package/dist/cjs/core/ToolManager.js +0 -1104
- package/dist/cjs/core/ToolManager.js.map +0 -1
- package/dist/cjs/core/createAgent.d.ts +0 -35
- package/dist/cjs/core/createAgent.d.ts.map +0 -1
- package/dist/cjs/core/createAgent.js +0 -39
- package/dist/cjs/core/createAgent.js.map +0 -1
- package/dist/cjs/core/flow-namespace.d.ts +0 -64
- package/dist/cjs/core/flow-namespace.d.ts.map +0 -1
- package/dist/cjs/core/flow-namespace.js +0 -182
- package/dist/cjs/core/flow-namespace.js.map +0 -1
- package/dist/cjs/core/toolGates.d.ts +0 -24
- package/dist/cjs/core/toolGates.d.ts.map +0 -1
- package/dist/cjs/core/toolGates.js +0 -52
- package/dist/cjs/core/toolGates.js.map +0 -1
- package/dist/cjs/types/persistence.d.ts +0 -254
- package/dist/cjs/types/persistence.d.ts.map +0 -1
- package/dist/cjs/types/persistence.js +0 -7
- package/dist/cjs/types/persistence.js.map +0 -1
- package/dist/cjs/types/prompt-cache.d.ts +0 -15
- package/dist/cjs/types/prompt-cache.d.ts.map +0 -1
- package/dist/cjs/types/prompt-cache.js +0 -6
- package/dist/cjs/types/prompt-cache.js.map +0 -1
- package/dist/cjs/types/signals.d.ts +0 -263
- package/dist/cjs/types/signals.d.ts.map +0 -1
- package/dist/cjs/types/signals.js +0 -11
- package/dist/cjs/types/signals.js.map +0 -1
- package/dist/cjs/types/template.d.ts +0 -84
- package/dist/cjs/types/template.d.ts.map +0 -1
- package/dist/cjs/types/template.js +0 -3
- package/dist/cjs/types/template.js.map +0 -1
- package/dist/cjs/utils/condition.d.ts +0 -63
- package/dist/cjs/utils/condition.d.ts.map +0 -1
- package/dist/cjs/utils/condition.js +0 -239
- package/dist/cjs/utils/condition.js.map +0 -1
- package/dist/cjs/utils/event.d.ts +0 -6
- package/dist/cjs/utils/event.d.ts.map +0 -1
- package/dist/cjs/utils/event.js +0 -20
- package/dist/cjs/utils/event.js.map +0 -1
- package/dist/cjs/utils/id.d.ts +0 -33
- package/dist/cjs/utils/id.d.ts.map +0 -1
- package/dist/cjs/utils/id.js +0 -84
- package/dist/cjs/utils/id.js.map +0 -1
- package/dist/cjs/utils/serialize.d.ts +0 -36
- package/dist/cjs/utils/serialize.d.ts.map +0 -1
- package/dist/cjs/utils/serialize.js +0 -77
- package/dist/cjs/utils/serialize.js.map +0 -1
- package/dist/cjs/utils/session.d.ts +0 -124
- package/dist/cjs/utils/session.d.ts.map +0 -1
- package/dist/cjs/utils/session.js +0 -396
- package/dist/cjs/utils/session.js.map +0 -1
- package/dist/constants/index.d.ts +0 -2
- package/dist/constants/index.d.ts.map +0 -1
- package/dist/constants/index.js +0 -4
- package/dist/constants/index.js.map +0 -1
- package/dist/core/AutoChainExecutor.d.ts +0 -97
- package/dist/core/AutoChainExecutor.d.ts.map +0 -1
- package/dist/core/AutoChainExecutor.js +0 -284
- package/dist/core/AutoChainExecutor.js.map +0 -1
- package/dist/core/BranchEvaluator.d.ts +0 -55
- package/dist/core/BranchEvaluator.d.ts.map +0 -1
- package/dist/core/BranchEvaluator.js +0 -121
- package/dist/core/BranchEvaluator.js.map +0 -1
- package/dist/core/DirectiveChainTracker.d.ts +0 -49
- package/dist/core/DirectiveChainTracker.d.ts.map +0 -1
- package/dist/core/DirectiveChainTracker.js +0 -117
- package/dist/core/DirectiveChainTracker.js.map +0 -1
- package/dist/core/Events.d.ts +0 -26
- package/dist/core/Events.d.ts.map +0 -1
- package/dist/core/Events.js +0 -137
- package/dist/core/Events.js.map +0 -1
- package/dist/core/Flow.d.ts +0 -183
- package/dist/core/Flow.d.ts.map +0 -1
- package/dist/core/Flow.js +0 -547
- package/dist/core/Flow.js.map +0 -1
- package/dist/core/FlowRouter.d.ts +0 -183
- package/dist/core/FlowRouter.d.ts.map +0 -1
- package/dist/core/FlowRouter.js +0 -1043
- package/dist/core/FlowRouter.js.map +0 -1
- package/dist/core/PersistenceManager.d.ts +0 -114
- package/dist/core/PersistenceManager.d.ts.map +0 -1
- package/dist/core/PersistenceManager.js +0 -332
- package/dist/core/PersistenceManager.js.map +0 -1
- package/dist/core/PromptComposer.d.ts +0 -47
- package/dist/core/PromptComposer.d.ts.map +0 -1
- package/dist/core/PromptComposer.js +0 -393
- package/dist/core/PromptComposer.js.map +0 -1
- package/dist/core/PromptSectionCache.d.ts +0 -48
- package/dist/core/PromptSectionCache.d.ts.map +0 -1
- package/dist/core/PromptSectionCache.js +0 -104
- package/dist/core/PromptSectionCache.js.map +0 -1
- package/dist/core/ResponseEngine.d.ts +0 -43
- package/dist/core/ResponseEngine.d.ts.map +0 -1
- package/dist/core/ResponseEngine.js +0 -231
- package/dist/core/ResponseEngine.js.map +0 -1
- package/dist/core/ResponseGenerationError.d.ts +0 -30
- package/dist/core/ResponseGenerationError.d.ts.map +0 -1
- package/dist/core/ResponseGenerationError.js +0 -31
- package/dist/core/ResponseGenerationError.js.map +0 -1
- package/dist/core/ResponseModal.d.ts +0 -305
- package/dist/core/ResponseModal.d.ts.map +0 -1
- package/dist/core/ResponseModal.js +0 -1410
- package/dist/core/ResponseModal.js.map +0 -1
- package/dist/core/ResponsePipeline.d.ts +0 -220
- package/dist/core/ResponsePipeline.d.ts.map +0 -1
- package/dist/core/ResponsePipeline.js +0 -1035
- package/dist/core/ResponsePipeline.js.map +0 -1
- package/dist/core/SessionFinalizer.d.ts +0 -34
- package/dist/core/SessionFinalizer.d.ts.map +0 -1
- package/dist/core/SessionFinalizer.js +0 -84
- package/dist/core/SessionFinalizer.js.map +0 -1
- package/dist/core/SessionManager.d.ts +0 -112
- package/dist/core/SessionManager.d.ts.map +0 -1
- package/dist/core/SessionManager.js +0 -301
- package/dist/core/SessionManager.js.map +0 -1
- package/dist/core/SignalCoordinator.d.ts +0 -103
- package/dist/core/SignalCoordinator.d.ts.map +0 -1
- package/dist/core/SignalCoordinator.js +0 -203
- package/dist/core/SignalCoordinator.js.map +0 -1
- package/dist/core/SignalEvaluator.d.ts +0 -86
- package/dist/core/SignalEvaluator.d.ts.map +0 -1
- package/dist/core/SignalEvaluator.js +0 -312
- package/dist/core/SignalEvaluator.js.map +0 -1
- package/dist/core/SignalProcessor.d.ts +0 -152
- package/dist/core/SignalProcessor.d.ts.map +0 -1
- package/dist/core/SignalProcessor.js +0 -498
- package/dist/core/SignalProcessor.js.map +0 -1
- package/dist/core/Step.d.ts +0 -184
- package/dist/core/Step.d.ts.map +0 -1
- package/dist/core/Step.js +0 -594
- package/dist/core/Step.js.map +0 -1
- package/dist/core/StepLifecycle.d.ts +0 -43
- package/dist/core/StepLifecycle.d.ts.map +0 -1
- package/dist/core/StepLifecycle.js +0 -176
- package/dist/core/StepLifecycle.js.map +0 -1
- package/dist/core/StreamingToolExecutor.d.ts +0 -142
- package/dist/core/StreamingToolExecutor.d.ts.map +0 -1
- package/dist/core/StreamingToolExecutor.js +0 -483
- package/dist/core/StreamingToolExecutor.js.map +0 -1
- package/dist/core/ToolLoopExecutor.d.ts +0 -133
- package/dist/core/ToolLoopExecutor.d.ts.map +0 -1
- package/dist/core/ToolLoopExecutor.js +0 -564
- package/dist/core/ToolLoopExecutor.js.map +0 -1
- package/dist/core/ToolManager.d.ts +0 -250
- package/dist/core/ToolManager.d.ts.map +0 -1
- package/dist/core/ToolManager.js +0 -1098
- package/dist/core/ToolManager.js.map +0 -1
- package/dist/core/createAgent.d.ts +0 -35
- package/dist/core/createAgent.d.ts.map +0 -1
- package/dist/core/createAgent.js +0 -36
- package/dist/core/createAgent.js.map +0 -1
- package/dist/core/flow-namespace.d.ts +0 -64
- package/dist/core/flow-namespace.d.ts.map +0 -1
- package/dist/core/flow-namespace.js +0 -179
- package/dist/core/flow-namespace.js.map +0 -1
- package/dist/core/toolGates.d.ts +0 -24
- package/dist/core/toolGates.d.ts.map +0 -1
- package/dist/core/toolGates.js +0 -49
- package/dist/core/toolGates.js.map +0 -1
- package/dist/types/persistence.d.ts +0 -254
- package/dist/types/persistence.d.ts.map +0 -1
- package/dist/types/persistence.js +0 -6
- package/dist/types/persistence.js.map +0 -1
- package/dist/types/prompt-cache.d.ts +0 -15
- package/dist/types/prompt-cache.d.ts.map +0 -1
- package/dist/types/prompt-cache.js +0 -5
- package/dist/types/prompt-cache.js.map +0 -1
- package/dist/types/signals.d.ts +0 -263
- package/dist/types/signals.d.ts.map +0 -1
- package/dist/types/signals.js +0 -10
- package/dist/types/signals.js.map +0 -1
- package/dist/types/template.d.ts +0 -84
- package/dist/types/template.d.ts.map +0 -1
- package/dist/types/template.js +0 -2
- package/dist/types/template.js.map +0 -1
- package/dist/utils/condition.d.ts +0 -63
- package/dist/utils/condition.d.ts.map +0 -1
- package/dist/utils/condition.js +0 -230
- package/dist/utils/condition.js.map +0 -1
- package/dist/utils/event.d.ts +0 -6
- package/dist/utils/event.d.ts.map +0 -1
- package/dist/utils/event.js +0 -17
- package/dist/utils/event.js.map +0 -1
- package/dist/utils/id.d.ts +0 -33
- package/dist/utils/id.d.ts.map +0 -1
- package/dist/utils/id.js +0 -77
- package/dist/utils/id.js.map +0 -1
- package/dist/utils/serialize.d.ts +0 -36
- package/dist/utils/serialize.d.ts.map +0 -1
- package/dist/utils/serialize.js +0 -72
- package/dist/utils/serialize.js.map +0 -1
- package/dist/utils/session.d.ts +0 -124
- package/dist/utils/session.d.ts.map +0 -1
- package/dist/utils/session.js +0 -379
- package/dist/utils/session.js.map +0 -1
- package/docs/concepts/directives.md +0 -369
- package/docs/reference/adapters.md +0 -543
- package/docs/reference/create-agent.md +0 -216
- package/docs/reference/directive.md +0 -242
- package/docs/reference/signals.md +0 -368
- package/examples/02-data-extraction.ts +0 -90
- package/examples/05-branching.ts +0 -140
- package/examples/06-flow-control.ts +0 -103
- package/examples/08-persistence.ts +0 -98
- package/examples/09-signals.ts +0 -144
- package/src/adapters/MemoryAdapter.ts +0 -281
- package/src/adapters/MongoAdapter.ts +0 -341
- package/src/adapters/OpenSearchAdapter.ts +0 -693
- package/src/adapters/PostgreSQLAdapter.ts +0 -487
- package/src/adapters/PrismaAdapter.ts +0 -617
- package/src/adapters/RedisAdapter.ts +0 -439
- package/src/adapters/SQLiteAdapter.ts +0 -496
- package/src/adapters/index.ts +0 -43
- package/src/adapters/sessionRow.ts +0 -57
- package/src/constants/index.ts +0 -2
- package/src/core/AutoChainExecutor.ts +0 -397
- package/src/core/BranchEvaluator.ts +0 -161
- package/src/core/DirectiveChainTracker.ts +0 -144
- package/src/core/Events.ts +0 -164
- package/src/core/Flow.ts +0 -665
- package/src/core/FlowRouter.ts +0 -1540
- package/src/core/PersistenceManager.ts +0 -446
- package/src/core/PromptComposer.ts +0 -448
- package/src/core/PromptSectionCache.ts +0 -125
- package/src/core/ResponseEngine.ts +0 -338
- package/src/core/ResponseGenerationError.ts +0 -53
- package/src/core/ResponseModal.ts +0 -1902
- package/src/core/ResponsePipeline.ts +0 -1404
- package/src/core/SessionFinalizer.ts +0 -108
- package/src/core/SessionManager.ts +0 -372
- package/src/core/SignalCoordinator.ts +0 -263
- package/src/core/SignalEvaluator.ts +0 -404
- package/src/core/SignalProcessor.ts +0 -663
- package/src/core/Step.ts +0 -782
- package/src/core/StepLifecycle.ts +0 -242
- package/src/core/StreamingToolExecutor.ts +0 -609
- package/src/core/ToolLoopExecutor.ts +0 -749
- package/src/core/ToolManager.ts +0 -1379
- package/src/core/createAgent.ts +0 -40
- package/src/core/flow-namespace.ts +0 -227
- package/src/core/toolGates.ts +0 -72
- package/src/types/persistence.ts +0 -303
- package/src/types/prompt-cache.ts +0 -17
- package/src/types/signals.ts +0 -338
- package/src/types/template.ts +0 -98
- package/src/utils/condition.ts +0 -296
- package/src/utils/event.ts +0 -16
- package/src/utils/id.ts +0 -91
- package/src/utils/serialize.ts +0 -86
- package/src/utils/session.ts +0 -501
|
@@ -0,0 +1,1214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runner: the code half of a turn. Load, Ingest, Decide, Run, Settle and
|
|
3
|
+
* Return (design §4) with one applier, `advance`. Zero provider calls live
|
|
4
|
+
* here: the Agent asks `understandRequest` and `advance` what to judge and
|
|
5
|
+
* what to speak, spends the calls, and hands the plain results back to
|
|
6
|
+
* `decide` and `settle`.
|
|
7
|
+
*
|
|
8
|
+
* begin ─▶ understandRequest ─(Understand)─▶ decide ─▶ advance ─(Speak)─▶ settle ─▶ finish
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AgentOptions, EndReason, PendingWakesInput, ScheduleEntry, TurnBase, TurnInput, TurnResult } from "../types/agent.js";
|
|
12
|
+
import type { TokenUsage } from "../types/ai.js";
|
|
13
|
+
import type { ActionResult, Branch, DoStep, Duration, Flow, IfStep, Next, Pred, PredCtx, Repeat, SayStep, Step, StepBase, TalkStep, Trigger, WaitEventStep, WaitStep } from "../types/flow.js";
|
|
14
|
+
import type { History } from "../types/history.js";
|
|
15
|
+
import type { Run, Session, StepOutcome, StepOutcomeCode, StepOutcomeKind, TriggerKind } from "../types/session.js";
|
|
16
|
+
import { cloneDeep } from "../utils/clone.js";
|
|
17
|
+
import { parseDuration } from "../utils/duration.js";
|
|
18
|
+
import { OUTCOME_MESSAGES } from "../utils/outcomes.js";
|
|
19
|
+
import { coerceField, DEFAULT_MAX_ASKS, extractMode, isKnown, pendingFields } from "../utils/schema.js";
|
|
20
|
+
import { render, renderDeep } from "../utils/template.js";
|
|
21
|
+
import { addUsage } from "../utils/usage.js";
|
|
22
|
+
import type { Deferral, IdleRequest, InputKind, SpeakOutcome, SpeakRequest, Spoken, TalkRequest, UnderstandRequest, Understanding } from "./contracts.js";
|
|
23
|
+
import { deepEqual, evaluate } from "./predicate.js";
|
|
24
|
+
|
|
25
|
+
/** A `wait` this short rides as `afterMs` on the next message instead of a real wake. */
|
|
26
|
+
const SHORT_WAIT_MS = 10_000;
|
|
27
|
+
const MAX_STEPS_PER_TURN = 50;
|
|
28
|
+
const MAX_HOP = 5;
|
|
29
|
+
/** Kept always-claims per flow and anchor, kept input ids, kept outcomes per run. */
|
|
30
|
+
const KEEP = 50;
|
|
31
|
+
/**
|
|
32
|
+
* How long a talk step waits after the provider failed, and how many times. The
|
|
33
|
+
* ladder used to be three rungs clamped by `Math.min`, which meant it never
|
|
34
|
+
* ran out: a stuck session re-ran understand AND speak every fifteen minutes
|
|
35
|
+
* for as long as it existed. It now ends after the last rung, so one outage
|
|
36
|
+
* costs at most five retries spread over seven hours instead of forever.
|
|
37
|
+
*/
|
|
38
|
+
const RETRY_BACKOFF: Duration[] = ["1m", "5m", "15m", "1h", "6h"];
|
|
39
|
+
const DEFAULT_EVENT_WAIT: Duration = "30d";
|
|
40
|
+
const ROUTE_MIN = 40;
|
|
41
|
+
const ROUTE_STICKY = 15;
|
|
42
|
+
|
|
43
|
+
/** What started the turn, already narrowed. */
|
|
44
|
+
export type What =
|
|
45
|
+
| { kind: "message"; text: string; id?: string; at: string }
|
|
46
|
+
| { kind: "wake"; key: string }
|
|
47
|
+
| { kind: "event"; name: string; payload: unknown; key: string; hop: number }
|
|
48
|
+
| { kind: "start"; flow: string; input: unknown; key: string; hop: number };
|
|
49
|
+
|
|
50
|
+
/** The mutable per-turn state. `session` is a deep copy; the input is never touched. */
|
|
51
|
+
export interface Turn<C = unknown, D = unknown> {
|
|
52
|
+
readonly input: TurnBase<C, D>;
|
|
53
|
+
readonly what: What;
|
|
54
|
+
readonly kind: InputKind;
|
|
55
|
+
/** Message id, wake key, event key or start key: the trigger key of runs started this turn. */
|
|
56
|
+
readonly triggerKey: string;
|
|
57
|
+
readonly session: Session<D>;
|
|
58
|
+
readonly original?: Session<D>;
|
|
59
|
+
readonly now: Date;
|
|
60
|
+
readonly nowIso: string;
|
|
61
|
+
readonly context: C;
|
|
62
|
+
readonly silenced?: string;
|
|
63
|
+
/** The host gate allows the understand call even though the assistant cannot speak. */
|
|
64
|
+
readonly understandAllowed: boolean;
|
|
65
|
+
/** Set by a short-circuit: the input is a no-op and nothing is saved. */
|
|
66
|
+
ignored: boolean;
|
|
67
|
+
messages: TurnResult<D>["messages"];
|
|
68
|
+
schedule: TurnResult<D>["schedule"];
|
|
69
|
+
outcomes: StepOutcome[];
|
|
70
|
+
started: TurnResult<D>["started"];
|
|
71
|
+
ended: TurnResult<D>["ended"];
|
|
72
|
+
skipped: TurnResult<D>["skipped"];
|
|
73
|
+
llmCalls: number;
|
|
74
|
+
usage?: TokenUsage;
|
|
75
|
+
// ── internals ──
|
|
76
|
+
/** The run holding the floor this turn: resolved in Ingest, routed in Decide, or the asker. */
|
|
77
|
+
floorRunId?: string;
|
|
78
|
+
/** A run took or resumed the floor in Ingest: routing is skipped. */
|
|
79
|
+
floorFromIngest: boolean;
|
|
80
|
+
/** True while Ingest runs, so a child chained there counts as taking the floor in Ingest. */
|
|
81
|
+
ingesting: boolean;
|
|
82
|
+
/** Runs that emitted a `say` or an action `spoke: true` this turn. */
|
|
83
|
+
spokeBy: Set<string>;
|
|
84
|
+
/** The assistant produced output this turn (say, spoke, ai message). */
|
|
85
|
+
spoke: boolean;
|
|
86
|
+
/** A short `wait` waiting to ride on the next message. */
|
|
87
|
+
afterMs: number;
|
|
88
|
+
talk?: TalkRequest<C, D>;
|
|
89
|
+
/** Speak already happened (or was skipped): a talk step reached now waits for the next message. */
|
|
90
|
+
speakDone: boolean;
|
|
91
|
+
/** Runs still to move this phase; `follow` appends children. */
|
|
92
|
+
queue: Run[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
type MessageTrigger<C, D> = Extract<Trigger<C, D>, { message: string[] }>;
|
|
96
|
+
type MentionTrigger<C, D> = Extract<Trigger<C, D>, { mention: string[] }>;
|
|
97
|
+
type SilenceTrigger<C, D> = Extract<Trigger<C, D>, { silence: Duration }>;
|
|
98
|
+
|
|
99
|
+
type TalkOf<C, D> = StepBase<D> & TalkStep<C, D>;
|
|
100
|
+
type SayOf<D> = StepBase<D> & SayStep;
|
|
101
|
+
type DoOf<D> = StepBase<D> & DoStep<D>;
|
|
102
|
+
type WaitOf<C, D> = StepBase<D> & WaitStep<C, D>;
|
|
103
|
+
type WaitEventOf<D> = StepBase<D> & WaitEventStep<D>;
|
|
104
|
+
type IfOf<C, D> = StepBase<D> & IfStep<C, D>;
|
|
105
|
+
|
|
106
|
+
function isTalk<C, D>(step: Step<C, D>): step is TalkOf<C, D> {
|
|
107
|
+
return "prompt" in step || "collect" in step;
|
|
108
|
+
}
|
|
109
|
+
function isSay<C, D>(step: Step<C, D>): step is SayOf<D> {
|
|
110
|
+
return "say" in step;
|
|
111
|
+
}
|
|
112
|
+
function isDo<C, D>(step: Step<C, D>): step is DoOf<D> {
|
|
113
|
+
return "do" in step;
|
|
114
|
+
}
|
|
115
|
+
function isWait<C, D>(step: Step<C, D>): step is WaitOf<C, D> {
|
|
116
|
+
return "wait" in step && typeof step.wait === "string";
|
|
117
|
+
}
|
|
118
|
+
function isWaitEvent<C, D>(step: Step<C, D>): step is WaitEventOf<D> {
|
|
119
|
+
return "wait" in step && typeof step.wait === "object";
|
|
120
|
+
}
|
|
121
|
+
function isIf<C, D>(step: Step<C, D>): step is IfOf<C, D> {
|
|
122
|
+
return "if" in step;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function triggerKind<C, D>(trigger: Trigger<C, D>): TriggerKind {
|
|
126
|
+
if ("message" in trigger) return "message";
|
|
127
|
+
if ("mention" in trigger) return "mention";
|
|
128
|
+
if ("silence" in trigger) return "silence";
|
|
129
|
+
return "event";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function defaultRepeat(kind: TriggerKind): Repeat {
|
|
133
|
+
return kind === "message" || kind === "mention" || kind === "silence" ? "once" : "always";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function talkKind(step: { collect?: readonly string[] }): StepOutcomeKind {
|
|
137
|
+
return step.collect?.length ? "collect" : "prompt";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function kindOf<C, D>(step: Step<C, D> | undefined): StepOutcomeKind {
|
|
141
|
+
if (!step) return "if";
|
|
142
|
+
if (isTalk(step)) return talkKind(step);
|
|
143
|
+
if (isSay(step)) return "say";
|
|
144
|
+
if (isDo(step)) return "do";
|
|
145
|
+
if (isIf(step)) return "if";
|
|
146
|
+
return "wait";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function describe<C, D>(input: TurnInput<C, D>, nowIso: string): What {
|
|
150
|
+
if ("message" in input) return { kind: "message", text: input.message, id: input.id, at: input.at ?? nowIso };
|
|
151
|
+
if ("wake" in input) return { kind: "wake", key: input.wake };
|
|
152
|
+
if ("event" in input) return { kind: "event", name: input.event, payload: input.payload, key: input.key, hop: input.hop ?? 0 };
|
|
153
|
+
return { kind: "start", flow: input.start.flow, input: input.start.input, key: input.start.key, hop: input.start.hop ?? 0 };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function freshSession<D>(id: string): Session<D> {
|
|
157
|
+
return { id, v: 4, version: 0, data: {}, runs: [], claims: {}, inputs: [], metadata: {} };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function keepLast<T>(list: T[], n: number): void {
|
|
161
|
+
if (list.length > n) list.splice(0, list.length - n);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class Runner<C = unknown, D = unknown> {
|
|
165
|
+
private readonly flows: Map<string, Flow<C, D>>;
|
|
166
|
+
|
|
167
|
+
constructor(private readonly options: AgentOptions<C, D>) {
|
|
168
|
+
this.flows = new Map((options.flows ?? []).map((flow) => [flow.id, flow]));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Load + Ingest ─────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
begin(input: TurnInput<C, D>): Turn<C, D> {
|
|
174
|
+
const now = this.now();
|
|
175
|
+
const turn = this.open(input, describe(input, now.toISOString()), now);
|
|
176
|
+
if (turn.what.kind === "wake" && !input.session) {
|
|
177
|
+
this.ignore(turn, "no-session", turn.what.key);
|
|
178
|
+
return turn;
|
|
179
|
+
}
|
|
180
|
+
turn.ingesting = true;
|
|
181
|
+
this.ingest(turn);
|
|
182
|
+
turn.ingesting = false;
|
|
183
|
+
return turn;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private now(): Date {
|
|
187
|
+
return (this.options.clock ?? (() => new Date()))();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private open(input: TurnBase<C, D>, what: What, now: Date): Turn<C, D> {
|
|
191
|
+
const nowIso = now.toISOString();
|
|
192
|
+
const silenced = typeof input.silenced === "string" ? input.silenced : input.silenced?.reason;
|
|
193
|
+
return {
|
|
194
|
+
input,
|
|
195
|
+
what,
|
|
196
|
+
kind: what.kind,
|
|
197
|
+
triggerKey: what.kind === "message" ? (what.id ?? what.at) : what.key,
|
|
198
|
+
session: input.session ? cloneDeep(input.session) : freshSession<D>(input.sessionId),
|
|
199
|
+
original: input.session,
|
|
200
|
+
now,
|
|
201
|
+
nowIso,
|
|
202
|
+
// `context` is optional only when C allows undefined, so this is C at every call site that compiles.
|
|
203
|
+
context: input.context as C,
|
|
204
|
+
silenced,
|
|
205
|
+
understandAllowed: typeof input.silenced === "object" && input.silenced.understand === true,
|
|
206
|
+
ignored: false,
|
|
207
|
+
messages: [],
|
|
208
|
+
schedule: [],
|
|
209
|
+
outcomes: [],
|
|
210
|
+
started: [],
|
|
211
|
+
ended: [],
|
|
212
|
+
skipped: [],
|
|
213
|
+
llmCalls: 0,
|
|
214
|
+
floorFromIngest: false,
|
|
215
|
+
ingesting: false,
|
|
216
|
+
spokeBy: new Set(),
|
|
217
|
+
spoke: false,
|
|
218
|
+
afterMs: 0,
|
|
219
|
+
speakDone: false,
|
|
220
|
+
queue: [],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Every wake the session waits on: each parked run's own, then each silence flow's since the assistant last spoke. */
|
|
225
|
+
pendingWakes(input: PendingWakesInput<C, D>): ScheduleEntry[] {
|
|
226
|
+
// No input arrives, so nothing reads `what`: the inert wake shape only fills the field.
|
|
227
|
+
const turn = this.open({ ...input, sessionId: input.session.id }, { kind: "wake", key: "" }, this.now());
|
|
228
|
+
// `park` always sets both; the type leaves them optional, so a hand-built blob without them has no wake to give.
|
|
229
|
+
const parked = turn.session.runs.flatMap(({ status, waiting }) =>
|
|
230
|
+
status === "waiting" && waiting?.key && waiting.until ? [{ key: waiting.key, at: new Date(waiting.until) }] : [],
|
|
231
|
+
);
|
|
232
|
+
return [...parked, ...this.silenceWakes(turn)];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private ingest(turn: Turn<C, D>): void {
|
|
236
|
+
const { what, session } = turn;
|
|
237
|
+
switch (what.kind) {
|
|
238
|
+
case "message": {
|
|
239
|
+
if (what.id) {
|
|
240
|
+
if (session.inputs.includes(what.id)) {
|
|
241
|
+
this.ignore(turn, "duplicate-input", what.id);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
session.inputs.push(what.id);
|
|
245
|
+
keepLast(session.inputs, KEEP);
|
|
246
|
+
}
|
|
247
|
+
session.lastUserAt = what.at;
|
|
248
|
+
this.resolveReplyWaits(turn);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
case "wake":
|
|
252
|
+
if (what.key.startsWith("silence:")) this.silenceWake(turn, what.key);
|
|
253
|
+
else this.runWake(turn, what.key);
|
|
254
|
+
return;
|
|
255
|
+
case "event": {
|
|
256
|
+
const direction = this.options.events?.[what.name]?.direction;
|
|
257
|
+
if (direction === "inbound") {
|
|
258
|
+
session.lastUserAt = turn.nowIso;
|
|
259
|
+
this.resolveReplyWaits(turn);
|
|
260
|
+
} else if (direction === "outbound") {
|
|
261
|
+
session.lastAssistantAt = turn.nowIso;
|
|
262
|
+
}
|
|
263
|
+
for (const run of [...session.runs]) {
|
|
264
|
+
if (run.waiting?.kind !== "event" || run.waiting.event !== what.name) continue;
|
|
265
|
+
const flow = this.flows.get(run.flowId);
|
|
266
|
+
const step = flow && this.stepOf(flow, run.stepId);
|
|
267
|
+
run.status = "running";
|
|
268
|
+
delete run.waiting;
|
|
269
|
+
if (!flow || !step) continue; // move() reports the missing flow or step
|
|
270
|
+
this.outcome(turn, run, { kind: "wait", status: "ok", code: "event-arrived", next: nextLabel(step.then) });
|
|
271
|
+
this.follow(turn, run, flow, step, step.then);
|
|
272
|
+
this.takeFloor(turn, run);
|
|
273
|
+
}
|
|
274
|
+
for (const flow of this.flows.values()) {
|
|
275
|
+
for (const trigger of flow.on ?? []) {
|
|
276
|
+
if (!("event" in trigger) || trigger.event !== what.name) continue;
|
|
277
|
+
if (this.startRun(turn, flow, "event", what.key, { trigger, payload: what.payload, hop: what.hop })) break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
case "start": {
|
|
283
|
+
const flow = this.flows.get(what.flow);
|
|
284
|
+
if (!flow) {
|
|
285
|
+
turn.skipped.push({ flowId: what.flow, anchor: session.id, triggerKey: what.key, code: "flow-gone", message: OUTCOME_MESSAGES["flow-gone"] });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.startRun(turn, flow, "start", what.key, { payload: what.input, hop: what.hop });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** The lead wrote: every run parked on a timer `wait` with `else` takes `else`, oldest first. */
|
|
295
|
+
private resolveReplyWaits(turn: Turn<C, D>): void {
|
|
296
|
+
for (const run of [...turn.session.runs]) {
|
|
297
|
+
if (run.status !== "waiting" || run.waiting?.kind !== "timer" || run.stepId === null) continue;
|
|
298
|
+
const flow = this.flows.get(run.flowId);
|
|
299
|
+
const step = flow && this.stepOf(flow, run.stepId);
|
|
300
|
+
if (!flow || !step || !isWait(step) || step.else === undefined) continue;
|
|
301
|
+
run.status = "running";
|
|
302
|
+
delete run.waiting;
|
|
303
|
+
const branch = step.branches?.find((b) => "if" in b && this.holds(b.if, turn, run));
|
|
304
|
+
const target = branch ? branch.then : step.else;
|
|
305
|
+
this.outcome(turn, run, { kind: "wait", status: "ok", code: "replied", next: nextLabel(target) });
|
|
306
|
+
this.follow(turn, run, flow, step, target);
|
|
307
|
+
this.takeFloor(turn, run);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Honoured only by the run whose `waiting.key` equals it (I3). */
|
|
312
|
+
private runWake(turn: Turn<C, D>, key: string): void {
|
|
313
|
+
const run = turn.session.runs.find((r) => r.waiting?.key === key);
|
|
314
|
+
if (!run) {
|
|
315
|
+
this.ignore(turn, "stale-wake", key);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const setAt = run.waiting?.setAt ?? run.startedAt;
|
|
319
|
+
run.status = "running";
|
|
320
|
+
delete run.waiting;
|
|
321
|
+
turn.floorRunId = run.id;
|
|
322
|
+
if (run.stepId === null) return; // parked on the trigger's `after`: enters its first step in advance()
|
|
323
|
+
const flow = this.flows.get(run.flowId);
|
|
324
|
+
const step = flow && this.stepOf(flow, run.stepId);
|
|
325
|
+
if (!flow || !step) return; // move() reports it
|
|
326
|
+
if (isWait(step)) {
|
|
327
|
+
// The reply beat the job: the lead wrote after the wait was set.
|
|
328
|
+
const replied = step.else !== undefined && this.leadWroteSince(turn, setAt, flow);
|
|
329
|
+
const target = replied ? step.else : step.then;
|
|
330
|
+
this.outcome(turn, run, { kind: "wait", status: "ok", code: replied ? "replied" : "no-reply", next: nextLabel(target) });
|
|
331
|
+
this.follow(turn, run, flow, step, target);
|
|
332
|
+
} else if (isWaitEvent(step)) {
|
|
333
|
+
// ponytail: an event wait that times out without `else` ends the run; nothing sensible follows an event that never came.
|
|
334
|
+
const target = step.else ?? "end";
|
|
335
|
+
this.outcome(turn, run, { kind: "wait", status: "ok", code: "no-event", next: nextLabel(target) });
|
|
336
|
+
this.follow(turn, run, flow, step, target);
|
|
337
|
+
}
|
|
338
|
+
// A deferred `do` or a retried talk step re-runs at the same visit in advance(): same key, same idempotency.
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** `silence:<flowId>:<sessionId>:<lastAssistantAtMs>`, honoured only while the blob still shows that silence. */
|
|
342
|
+
private silenceWake(turn: Turn<C, D>, key: string): void {
|
|
343
|
+
const rest = key.slice("silence:".length);
|
|
344
|
+
const flowId = rest.slice(0, rest.indexOf(":"));
|
|
345
|
+
const ms = Number(rest.slice(rest.lastIndexOf(":") + 1));
|
|
346
|
+
const { lastAssistantAt } = turn.session;
|
|
347
|
+
const flow = this.flows.get(flowId);
|
|
348
|
+
if (!flow) {
|
|
349
|
+
turn.skipped.push({ flowId, anchor: turn.session.id, triggerKey: String(ms), code: "flow-gone", message: OUTCOME_MESSAGES["flow-gone"] });
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (!lastAssistantAt || Date.parse(lastAssistantAt) !== ms || this.leadWroteSince(turn, lastAssistantAt, flow)) {
|
|
353
|
+
this.ignore(turn, "silence-broken", key);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const trigger = (flow.on ?? []).find((t) => "silence" in t);
|
|
357
|
+
const run = this.startRun(turn, flow, "silence", String(ms), { trigger });
|
|
358
|
+
if (run) turn.floorRunId = run.id;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private ignore(turn: Turn<C, D>, code: StepOutcomeCode, key: string): void {
|
|
362
|
+
turn.ignored = true;
|
|
363
|
+
turn.outcomes.push({ kind: "wait", status: "skipped", code, message: OUTCOME_MESSAGES[code], key, at: turn.nowIso });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private takeFloor(turn: Turn<C, D>, run: Run): void {
|
|
367
|
+
if (!turn.session.runs.includes(run) || turn.floorFromIngest) return;
|
|
368
|
+
turn.floorFromIngest = true;
|
|
369
|
+
turn.floorRunId = run.id;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ── Starting runs: the one start order (design §4.2) ─────────────────
|
|
373
|
+
|
|
374
|
+
private startRun(
|
|
375
|
+
turn: Turn<C, D>,
|
|
376
|
+
flow: Flow<C, D>,
|
|
377
|
+
kind: TriggerKind,
|
|
378
|
+
key: string,
|
|
379
|
+
opts: { trigger?: Trigger<C, D>; payload?: unknown; hop?: number; keepData?: boolean },
|
|
380
|
+
): Run | null {
|
|
381
|
+
const { session } = turn;
|
|
382
|
+
const anchor = this.anchorOf(turn, flow);
|
|
383
|
+
const hop = opts.hop ?? 0;
|
|
384
|
+
const repeat = opts.trigger?.repeat ?? defaultRepeat(kind);
|
|
385
|
+
const nonce = repeat === "always" ? key : "";
|
|
386
|
+
const dedupeKey = `${flow.id}:${anchor}:${nonce}`;
|
|
387
|
+
const run: Run = {
|
|
388
|
+
id: `${flow.id}#${key}`,
|
|
389
|
+
flowId: flow.id,
|
|
390
|
+
anchor,
|
|
391
|
+
dedupeKey,
|
|
392
|
+
stepId: null,
|
|
393
|
+
status: "running",
|
|
394
|
+
trigger: { kind, key, ...(opts.payload !== undefined ? { payload: opts.payload } : {}) },
|
|
395
|
+
...(opts.payload !== undefined ? { input: opts.payload } : {}),
|
|
396
|
+
hop,
|
|
397
|
+
startedAt: turn.nowIso,
|
|
398
|
+
asked: {},
|
|
399
|
+
visits: {},
|
|
400
|
+
outcomes: [],
|
|
401
|
+
};
|
|
402
|
+
const skip = (code: StepOutcomeCode): null => {
|
|
403
|
+
turn.skipped.push({ flowId: flow.id, anchor, triggerKey: key, code, message: OUTCOME_MESSAGES[code] });
|
|
404
|
+
return null;
|
|
405
|
+
};
|
|
406
|
+
if (opts.trigger?.if && !this.holds(opts.trigger.if, turn, run)) return null;
|
|
407
|
+
const heldAt = this.claimAt(turn, dedupeKey);
|
|
408
|
+
if (heldAt !== undefined) {
|
|
409
|
+
if (typeof repeat === "string") return skip("already-claimed");
|
|
410
|
+
if (turn.now.getTime() - Date.parse(heldAt) < parseDuration(repeat.cooldown)) return skip("cooldown");
|
|
411
|
+
}
|
|
412
|
+
if (hop >= MAX_HOP) return skip("hop-limit");
|
|
413
|
+
const live = session.runs.find((r) => r.flowId === flow.id && r.anchor === anchor);
|
|
414
|
+
if (live) {
|
|
415
|
+
if (live.status === "waiting" && live.stepId === null) this.endRun(turn, live, "replaced");
|
|
416
|
+
else return skip("already-running");
|
|
417
|
+
} else if (turn.input.claims?.active.includes(`${flow.id}:${anchor}`)) {
|
|
418
|
+
return skip("already-running");
|
|
419
|
+
}
|
|
420
|
+
session.claims[dedupeKey] = { at: turn.nowIso };
|
|
421
|
+
if (nonce) this.pruneAlwaysClaims(session, `${flow.id}:${anchor}:`);
|
|
422
|
+
if (!opts.keepData) {
|
|
423
|
+
const data: Record<string, unknown> = session.data;
|
|
424
|
+
for (const field of flow.clearOnStart ?? []) delete data[field];
|
|
425
|
+
}
|
|
426
|
+
session.runs.push(run);
|
|
427
|
+
turn.started.push({ runId: run.id, flowId: flow.id, anchor, dedupeKey });
|
|
428
|
+
const after = opts.trigger && "event" in opts.trigger ? opts.trigger.after : undefined;
|
|
429
|
+
if (after) {
|
|
430
|
+
const at = this.snap(turn, new Date(turn.now.getTime() + parseDuration(after)), opts.trigger && "event" in opts.trigger ? opts.trigger.businessHours : undefined);
|
|
431
|
+
this.park(turn, run, { kind: "timer", key: `${run.id}:start:${at.getTime()}` }, at);
|
|
432
|
+
this.outcome(turn, run, { kind: "wait", status: "waiting", code: "awaiting-trigger", until: at.toISOString() });
|
|
433
|
+
}
|
|
434
|
+
return run;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Always-claims keep the last 50 per flow and anchor; once/cooldown keys (empty nonce) are never pruned. */
|
|
438
|
+
private pruneAlwaysClaims(session: Session<D>, prefix: string): void {
|
|
439
|
+
const keys = Object.keys(session.claims).filter((k) => k.startsWith(prefix) && k.length > prefix.length);
|
|
440
|
+
for (const key of keys.slice(0, Math.max(0, keys.length - KEEP))) delete session.claims[key];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private anchorOf(turn: Turn<C, D>, flow: Flow<C, D>): string {
|
|
444
|
+
if (!flow.anchor || flow.anchor === "session") return turn.session.id;
|
|
445
|
+
return turn.input.anchors?.[flow.anchor]?.key ?? turn.session.id;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private claimAt(turn: Turn<C, D>, key: string): string | undefined {
|
|
449
|
+
return turn.session.claims[key]?.at ?? turn.input.claims?.held[key];
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private repeatAllows(turn: Turn<C, D>, flow: Flow<C, D>, trigger: Trigger<C, D>, key: string): boolean {
|
|
453
|
+
const repeat = trigger.repeat ?? defaultRepeat(triggerKind(trigger));
|
|
454
|
+
const heldAt = this.claimAt(turn, `${flow.id}:${this.anchorOf(turn, flow)}:${repeat === "always" ? key : ""}`);
|
|
455
|
+
if (heldAt === undefined) return true;
|
|
456
|
+
if (typeof repeat === "string") return false;
|
|
457
|
+
return turn.now.getTime() - Date.parse(heldAt) >= parseDuration(repeat.cooldown);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** A run as it would be if started now, for trigger-level predicates. */
|
|
461
|
+
private draftRun(turn: Turn<C, D>, flow: Flow<C, D>, kind: TriggerKind, key: string, payload?: unknown): Run {
|
|
462
|
+
const anchor = this.anchorOf(turn, flow);
|
|
463
|
+
return {
|
|
464
|
+
id: `${flow.id}#${key}`, flowId: flow.id, anchor, dedupeKey: `${flow.id}:${anchor}:`, stepId: null, status: "running",
|
|
465
|
+
trigger: { kind, key }, input: payload, hop: 0, startedAt: turn.nowIso, asked: {}, visits: {}, outcomes: [],
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private holds(pred: Pred<C, D>, turn: Turn<C, D>, run: Run, input: unknown = run.input): boolean {
|
|
470
|
+
return evaluate(
|
|
471
|
+
pred,
|
|
472
|
+
{ context: turn.context, data: turn.session.data, input, run, silenced: turn.silenced, now: turn.now },
|
|
473
|
+
this.options.conditions ?? {},
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private leadWroteSince(turn: Turn<C, D>, sinceIso: string, flow: Flow<C, D>): boolean {
|
|
478
|
+
const since = Date.parse(sinceIso);
|
|
479
|
+
const { lastUserAt } = turn.session;
|
|
480
|
+
if (lastUserAt && Date.parse(lastUserAt) > since) return true;
|
|
481
|
+
if (!flow.anchor || flow.anchor === "session") return false;
|
|
482
|
+
const inbound = turn.input.anchors?.[flow.anchor]?.lastInboundAt;
|
|
483
|
+
return inbound !== undefined && Date.parse(inbound) > since;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private snap(turn: Turn<C, D>, at: Date, businessHours: boolean | undefined): Date {
|
|
487
|
+
return businessHours && this.options.businessHours ? this.options.businessHours(at, { context: turn.context }) : at;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private stepOf(flow: Flow<C, D>, stepId: string | null): Step<C, D> | undefined {
|
|
491
|
+
return stepId === null ? undefined : flow.steps.find((s) => s.id === stepId);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private stepKey(run: Run, step: Step<C, D>): string {
|
|
495
|
+
return `${run.id}:${step.id}:${run.visits[step.id] ?? 0}`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private scope(turn: Turn<C, D>, run: Run): { data: unknown; context: unknown; input: unknown } {
|
|
499
|
+
return { data: turn.session.data, context: turn.context, input: run.input };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private asker(turn: Turn<C, D>): Run | undefined {
|
|
503
|
+
return turn.session.runs.find((r) => r.status === "asking");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private outcome(turn: Turn<C, D>, run: Run | undefined, line: Omit<StepOutcome, "at" | "runId" | "flowId" | "stepId" | "message"> & { stepId?: string }): void {
|
|
507
|
+
const full: StepOutcome = {
|
|
508
|
+
...(run ? { runId: run.id, flowId: run.flowId, stepId: line.stepId ?? run.stepId ?? undefined } : {}),
|
|
509
|
+
...line,
|
|
510
|
+
...(line.code ? { message: OUTCOME_MESSAGES[line.code] } : {}),
|
|
511
|
+
at: turn.nowIso,
|
|
512
|
+
};
|
|
513
|
+
if (full.stepId === undefined) delete full.stepId;
|
|
514
|
+
turn.outcomes.push(full);
|
|
515
|
+
if (run) {
|
|
516
|
+
run.outcomes.push(full);
|
|
517
|
+
keepLast(run.outcomes, KEEP);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ── Understand: what the one call may judge (design §4.3) ─────────────
|
|
522
|
+
|
|
523
|
+
understandRequest(turn: Turn<C, D>): UnderstandRequest<C, D> | null {
|
|
524
|
+
if (turn.ignored || turn.what.kind !== "message") return null;
|
|
525
|
+
if (turn.silenced !== undefined && !turn.understandAllowed) return null;
|
|
526
|
+
const floorRun = this.floorRun(turn);
|
|
527
|
+
const floorFlow = floorRun && this.flows.get(floorRun.flowId);
|
|
528
|
+
const eligible = this.eligibleMessageFlows(turn);
|
|
529
|
+
const messageFlows = !floorRun && this.startsUnscored(turn, eligible) ? [] : eligible;
|
|
530
|
+
const mentionFlows = this.mentionFlows()
|
|
531
|
+
.filter(({ flow, trigger }) => trigger.mention.length > 0 && this.repeatAllows(turn, flow, trigger, turn.triggerKey))
|
|
532
|
+
.map(({ flow }) => flow);
|
|
533
|
+
const branches: UnderstandRequest<C, D>["branches"] = [];
|
|
534
|
+
const askingStep = floorRun && floorFlow ? this.stepOf(floorFlow, floorRun.stepId) : undefined;
|
|
535
|
+
if (floorRun && askingStep && isTalk(askingStep)) {
|
|
536
|
+
askingStep.branches?.forEach((branch, index) => {
|
|
537
|
+
if ("when" in branch) branches.push({ runId: floorRun.id, stepId: askingStep.id, index, when: branch.when });
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
const fields: UnderstandRequest<C, D>["fields"] = {};
|
|
541
|
+
const data: Record<string, unknown> = turn.session.data;
|
|
542
|
+
for (const flow of [...(floorFlow ? [floorFlow] : []), ...eligible]) {
|
|
543
|
+
for (const step of flow.steps) {
|
|
544
|
+
if (!isTalk(step)) continue;
|
|
545
|
+
for (const field of step.collect ?? []) {
|
|
546
|
+
const def = this.options.fields[field];
|
|
547
|
+
if (def && !isKnown(data[field]) && extractMode(def) === "anywhere") fields[field] = def;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (!messageFlows.length && !mentionFlows.length && !branches.length && !Object.keys(fields).length) return null;
|
|
552
|
+
return {
|
|
553
|
+
text: turn.what.text,
|
|
554
|
+
history: this.historyOf(turn),
|
|
555
|
+
context: turn.context,
|
|
556
|
+
data: turn.session.data,
|
|
557
|
+
...(floorRun && floorFlow ? { floor: { run: floorRun, flow: floorFlow } } : {}),
|
|
558
|
+
messageFlows,
|
|
559
|
+
mentionFlows,
|
|
560
|
+
branches,
|
|
561
|
+
fields,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** The host's history for this turn; the session's own (playground) is the fallback. */
|
|
566
|
+
private historyOf(turn: Turn<C, D>): History {
|
|
567
|
+
return turn.input.history ?? turn.session.history ?? [];
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** The asker, or the run that took the floor in Ingest. */
|
|
571
|
+
private floorRun(turn: Turn<C, D>): Run | undefined {
|
|
572
|
+
const asking = this.asker(turn);
|
|
573
|
+
if (asking) return asking;
|
|
574
|
+
return turn.floorRunId ? turn.session.runs.find((r) => r.id === turn.floorRunId) : undefined;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** `message` flows with a non-empty list passing `if` and `repeat`, in flow order. */
|
|
578
|
+
private eligibleMessageFlows(turn: Turn<C, D>): Flow<C, D>[] {
|
|
579
|
+
return this.messageFlows(turn, (list) => list.length > 0);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* The lone eligible flow starts without a score only when a low score would have nowhere
|
|
584
|
+
* else to send the message: no catch-all passes and the idle speaker is silent (S9).
|
|
585
|
+
*/
|
|
586
|
+
private startsUnscored(turn: Turn<C, D>, eligible: Flow<C, D>[]): boolean {
|
|
587
|
+
return eligible.length === 1 && this.options.idle === "silent" && this.messageFlows(turn, (list) => list.length === 0).length === 0;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
private messageFlows(turn: Turn<C, D>, accept: (list: string[]) => boolean): Flow<C, D>[] {
|
|
591
|
+
const out: Flow<C, D>[] = [];
|
|
592
|
+
for (const flow of this.flows.values()) {
|
|
593
|
+
const trigger = (flow.on ?? []).find((t): t is MessageTrigger<C, D> => "message" in t && accept(t.message));
|
|
594
|
+
if (!trigger) continue;
|
|
595
|
+
if (trigger.if && !this.holds(trigger.if, turn, this.draftRun(turn, flow, "message", turn.triggerKey))) continue;
|
|
596
|
+
if (!this.repeatAllows(turn, flow, trigger, turn.triggerKey)) continue;
|
|
597
|
+
out.push(flow);
|
|
598
|
+
}
|
|
599
|
+
return out;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** Every `mention` flow with its trigger, in flow order. `if` and `repeat` are judged at start, when `extract` is in hand. */
|
|
603
|
+
private mentionFlows(): Array<{ flow: Flow<C, D>; trigger: MentionTrigger<C, D> }> {
|
|
604
|
+
const out: Array<{ flow: Flow<C, D>; trigger: MentionTrigger<C, D> }> = [];
|
|
605
|
+
for (const flow of this.flows.values()) {
|
|
606
|
+
const trigger = (flow.on ?? []).find((t): t is MentionTrigger<C, D> => "mention" in t);
|
|
607
|
+
if (trigger) out.push({ flow, trigger });
|
|
608
|
+
}
|
|
609
|
+
return out;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// ── Decide: apply the judgement (design §4.4) ─────────────────────────
|
|
613
|
+
|
|
614
|
+
decide(turn: Turn<C, D>, understanding: Understanding | null): void {
|
|
615
|
+
if (turn.ignored || turn.what.kind !== "message") return;
|
|
616
|
+
turn.llmCalls += understanding?.llmCalls ?? 0;
|
|
617
|
+
turn.usage = addUsage(turn.usage, understanding?.usage);
|
|
618
|
+
const key = turn.triggerKey;
|
|
619
|
+
const asker = this.asker(turn);
|
|
620
|
+
|
|
621
|
+
// Routing is decided before extraction lands so `clearOnStart` runs first, and applied after mention runs.
|
|
622
|
+
let route: Flow<C, D> | undefined;
|
|
623
|
+
if (!turn.floorFromIngest) {
|
|
624
|
+
const eligible = this.eligibleMessageFlows(turn);
|
|
625
|
+
const scores = understanding?.flows ?? {};
|
|
626
|
+
const best = (flows: Flow<C, D>[]): { flow: Flow<C, D>; score: number } | undefined =>
|
|
627
|
+
flows.map((flow) => ({ flow, score: scores[flow.id] ?? 0 })).reduce<{ flow: Flow<C, D>; score: number } | undefined>(
|
|
628
|
+
(top, cur) => (top && top.score >= cur.score ? top : cur), undefined);
|
|
629
|
+
if (asker) {
|
|
630
|
+
const current = scores[asker.flowId] ?? 0;
|
|
631
|
+
const top = best(eligible.filter((f) => f.id !== asker.flowId));
|
|
632
|
+
if (top && top.score >= current + ROUTE_STICKY && top.score >= ROUTE_MIN) route = top.flow;
|
|
633
|
+
} else if (this.startsUnscored(turn, eligible)) {
|
|
634
|
+
route = eligible[0];
|
|
635
|
+
} else {
|
|
636
|
+
const top = best(eligible);
|
|
637
|
+
route = top && top.score >= ROUTE_MIN ? top.flow : this.messageFlows(turn, (list) => list.length === 0)[0];
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// A code-only detector (`mention: []`) goes through the full start order here, so a blocked `repeat` is logged as a skip.
|
|
642
|
+
for (const { flow, trigger } of this.mentionFlows()) {
|
|
643
|
+
if (trigger.mention.length === 0) this.startRun(turn, flow, "mention", key, { trigger });
|
|
644
|
+
else if (understanding?.mentions[flow.id]) this.startRun(turn, flow, "mention", key, { trigger, payload: understanding.extract[flow.id] });
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (route) {
|
|
648
|
+
const suspended = turn.session.runs.find((r) => r.flowId === route.id && r.status === "suspended");
|
|
649
|
+
if (suspended) {
|
|
650
|
+
suspended.status = "running";
|
|
651
|
+
delete suspended.suspendedAt;
|
|
652
|
+
turn.floorRunId = suspended.id;
|
|
653
|
+
} else {
|
|
654
|
+
const trigger = (route.on ?? []).find((t) => "message" in t);
|
|
655
|
+
const run = this.startRun(turn, route, "message", key, { trigger });
|
|
656
|
+
if (run) turn.floorRunId = run.id;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
for (const [field, raw] of Object.entries(understanding?.fields ?? {})) this.writeField(turn, field, raw);
|
|
661
|
+
|
|
662
|
+
if (asker && turn.session.runs.includes(asker) && asker.status === "asking") {
|
|
663
|
+
const flow = this.flows.get(asker.flowId);
|
|
664
|
+
const step = flow && this.stepOf(flow, asker.stepId);
|
|
665
|
+
if (flow && step && isTalk(step)) this.fireBranch(turn, asker, flow, step, understanding);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
private fireBranch(turn: Turn<C, D>, run: Run, flow: Flow<C, D>, step: TalkOf<C, D>, understanding: Understanding | null): void {
|
|
670
|
+
const hit = (branch: Branch<C, D>, index: number): boolean =>
|
|
671
|
+
"if" in branch ? this.holds(branch.if, turn, run) : understanding?.branches[`${run.id}/${step.id}/${index}`] === true;
|
|
672
|
+
const index = (step.branches ?? []).findIndex(hit);
|
|
673
|
+
if (index < 0) return;
|
|
674
|
+
const branch = (step.branches ?? [])[index];
|
|
675
|
+
this.outcome(turn, run, { kind: talkKind(step), status: "ok", key: this.stepKey(run, step), code: "branch", next: nextLabel(branch.then) });
|
|
676
|
+
run.status = "running";
|
|
677
|
+
this.follow(turn, run, flow, step, branch.then);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/** Validate a raw extracted value and write it; unknown or invalid values leave an outcome line instead. */
|
|
681
|
+
private writeField(turn: Turn<C, D>, field: string, raw: unknown): void {
|
|
682
|
+
if (raw === undefined || raw === null) return;
|
|
683
|
+
const def = this.options.fields[field];
|
|
684
|
+
if (!def) {
|
|
685
|
+
this.outcome(turn, undefined, { kind: "collect", status: "skipped", code: "unknown-field", detail: field });
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const coerced = coerceField(def, raw);
|
|
689
|
+
if (!coerced.ok) {
|
|
690
|
+
this.outcome(turn, undefined, { kind: "collect", status: "skipped", code: coerced.code, detail: field });
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const data: Record<string, unknown> = turn.session.data;
|
|
694
|
+
data[field] = coerced.value;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// ── Run: move everything that can move (design §4.5) ──────────────────
|
|
698
|
+
|
|
699
|
+
async advance(turn: Turn<C, D>): Promise<TalkRequest<C, D> | IdleRequest<C, D> | null> {
|
|
700
|
+
if (turn.ignored) return null;
|
|
701
|
+
this.resumeSuspended(turn);
|
|
702
|
+
turn.queue = [...turn.session.runs];
|
|
703
|
+
await this.drain(turn);
|
|
704
|
+
return this.speaker(turn);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Everything Speak needs for this turn's one speaker. Instructions are agent,
|
|
709
|
+
* then flow, then step (idle: agent, then idle), each already judged by its
|
|
710
|
+
* `if`; tools are the step's list, else the flow's, else every agent tool.
|
|
711
|
+
*/
|
|
712
|
+
speakRequest(turn: Turn<C, D>, talk: TalkRequest<C, D> | IdleRequest<C, D>): SpeakRequest<C, D> {
|
|
713
|
+
const own = "idle" in talk
|
|
714
|
+
? { instructions: talk.idle.instructions ?? [], tools: talk.idle.tools }
|
|
715
|
+
: { instructions: [...(talk.flow.instructions ?? []), ...(talk.step.instructions ?? [])], tools: talk.step.tools ?? talk.flow.tools };
|
|
716
|
+
const ctx: PredCtx<C, D> = {
|
|
717
|
+
context: turn.context,
|
|
718
|
+
data: turn.session.data,
|
|
719
|
+
input: "idle" in talk ? undefined : talk.run.input,
|
|
720
|
+
...("idle" in talk ? {} : { run: talk.run }),
|
|
721
|
+
silenced: turn.silenced,
|
|
722
|
+
now: turn.now,
|
|
723
|
+
};
|
|
724
|
+
const conditions = this.options.conditions ?? {};
|
|
725
|
+
const instructions = [...(this.options.instructions ?? []), ...own.instructions].filter((ins) => !ins.if || evaluate(ins.if, ctx, conditions));
|
|
726
|
+
const all = this.options.tools ?? [];
|
|
727
|
+
const allowed = own.tools;
|
|
728
|
+
return {
|
|
729
|
+
talk,
|
|
730
|
+
input: turn.what.kind === "message" ? { kind: turn.kind, text: turn.what.text } : { kind: turn.kind },
|
|
731
|
+
context: turn.context,
|
|
732
|
+
data: turn.session.data,
|
|
733
|
+
history: this.historyOf(turn),
|
|
734
|
+
now: turn.now,
|
|
735
|
+
instructions,
|
|
736
|
+
tools: allowed ? all.filter((tool) => allowed.includes(tool.id)) : all,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
private speaker(turn: Turn<C, D>): TalkRequest<C, D> | IdleRequest<C, D> | null {
|
|
741
|
+
if (turn.silenced !== undefined) return null;
|
|
742
|
+
const { talk } = turn;
|
|
743
|
+
if (talk) {
|
|
744
|
+
// Another run's say (or spoke: true) already answered the lead: the floor's talk waits; the run stays asking.
|
|
745
|
+
if (turn.what.kind === "message" && [...turn.spokeBy].some((id) => id !== talk.run.id)) {
|
|
746
|
+
this.outcome(turn, talk.run, { kind: talkKind(talk.step), status: "skipped", key: this.stepKey(talk.run, talk.step), code: "another-reply" });
|
|
747
|
+
turn.talk = undefined;
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
return talk;
|
|
751
|
+
}
|
|
752
|
+
if (turn.what.kind === "message" && !this.asker(turn) && this.options.idle !== "silent" && turn.spokeBy.size === 0) {
|
|
753
|
+
return { idle: this.options.idle ?? { prompt: "" } };
|
|
754
|
+
}
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private async drain(turn: Turn<C, D>): Promise<void> {
|
|
759
|
+
for (let run = turn.queue.shift(); run; run = turn.queue.shift()) {
|
|
760
|
+
if (turn.session.runs.includes(run)) await this.move(turn, run);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private async move(turn: Turn<C, D>, run: Run): Promise<void> {
|
|
765
|
+
if (run.status === "waiting" || run.status === "suspended") return;
|
|
766
|
+
// An asker only re-speaks when the lead writes; wakes and events leave it alone.
|
|
767
|
+
if (run.status === "asking" && turn.what.kind !== "message") return;
|
|
768
|
+
const flow = this.flows.get(run.flowId);
|
|
769
|
+
if (!flow) {
|
|
770
|
+
this.endRun(turn, run, "skipped", "flow-gone");
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
if (!this.premiseHolds(turn, run, flow)) return;
|
|
774
|
+
const resuming = run.status === "asking";
|
|
775
|
+
run.status = "running";
|
|
776
|
+
if (run.stepId === null) {
|
|
777
|
+
if (!flow.steps.length) {
|
|
778
|
+
this.finishFlow(turn, run, flow);
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
this.enter(run, flow.steps[0].id);
|
|
782
|
+
}
|
|
783
|
+
for (let steps = 0; run.status === "running" && turn.session.runs.includes(run); steps++) {
|
|
784
|
+
if (steps >= MAX_STEPS_PER_TURN) {
|
|
785
|
+
this.endRun(turn, run, "failed", "step-loop");
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const step = this.stepOf(flow, run.stepId);
|
|
789
|
+
if (!step) {
|
|
790
|
+
this.endRun(turn, run, "skipped", "step-gone");
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
await this.execute(turn, run, flow, step, resuming && steps === 0);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/** `while` (default: the trigger's `if`) with fresh context; silence runs add "no lead message since the run started". */
|
|
798
|
+
private premiseHolds(turn: Turn<C, D>, run: Run, flow: Flow<C, D>): boolean {
|
|
799
|
+
// A wake-driven move only: on a message turn the lead just wrote, and the flow's `wait ... else` says what to do about it.
|
|
800
|
+
if (run.trigger.kind === "silence" && turn.what.kind === "wake" && this.leadWroteSince(turn, run.startedAt, flow)) {
|
|
801
|
+
this.endRun(turn, run, "skipped", "customer-replied");
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
let ok: boolean;
|
|
805
|
+
if (flow.while) {
|
|
806
|
+
ok = this.holds(flow.while, turn, run);
|
|
807
|
+
} else {
|
|
808
|
+
// ponytail: several triggers of one kind (two event triggers, say) are not told apart; the premise holds when any of them would fire.
|
|
809
|
+
const same = (flow.on ?? []).filter((t) => triggerKind(t) === run.trigger.kind);
|
|
810
|
+
ok = same.length === 0 || same.some((t) => !t.if || this.holds(t.if, turn, run));
|
|
811
|
+
}
|
|
812
|
+
if (!ok) this.endRun(turn, run, "skipped", "premise-changed");
|
|
813
|
+
return ok;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private async execute(turn: Turn<C, D>, run: Run, flow: Flow<C, D>, step: Step<C, D>, resuming: boolean): Promise<void> {
|
|
817
|
+
const data: Record<string, unknown> = turn.session.data;
|
|
818
|
+
const key = this.stepKey(run, step);
|
|
819
|
+
|
|
820
|
+
if (isTalk(step)) {
|
|
821
|
+
const kind = talkKind(step);
|
|
822
|
+
let pending: string[] = [];
|
|
823
|
+
if (step.collect?.length) {
|
|
824
|
+
pending = pendingFields(step, data, run.asked);
|
|
825
|
+
if (!pending.length) {
|
|
826
|
+
this.reportMaxAsks(turn, run, step);
|
|
827
|
+
this.outcome(turn, run, resuming
|
|
828
|
+
? { kind, status: "ok", key, next: nextLabel(step.then) }
|
|
829
|
+
: { kind, status: "skipped", key, code: "already-known", next: nextLabel(step.then) });
|
|
830
|
+
this.follow(turn, run, flow, step, step.then);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (turn.silenced !== undefined) {
|
|
835
|
+
if (resuming) run.status = "asking"; // the gate is closed; it speaks when the gate opens
|
|
836
|
+
else this.endRun(turn, run, "skipped", "silenced", kind, turn.silenced);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
for (const other of turn.session.runs) {
|
|
840
|
+
if (other !== run && other.status === "asking") {
|
|
841
|
+
other.status = "suspended";
|
|
842
|
+
other.suspendedAt = turn.nowIso;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
run.status = "asking";
|
|
846
|
+
if (!turn.speakDone) turn.talk = { run, flow, step, pending };
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (isSay(step)) {
|
|
851
|
+
if (turn.silenced !== undefined) {
|
|
852
|
+
this.endRun(turn, run, "skipped", "silenced", "say", turn.silenced);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
if (step.once) {
|
|
856
|
+
const claim = `${flow.id}:${step.id}:${turn.session.id}`;
|
|
857
|
+
if (this.claimAt(turn, claim) !== undefined) {
|
|
858
|
+
this.outcome(turn, run, { kind: "say", status: "skipped", key, code: "already-sent", next: nextLabel(step.then) });
|
|
859
|
+
this.follow(turn, run, flow, step, step.then);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
turn.session.claims[claim] = { at: turn.nowIso };
|
|
863
|
+
}
|
|
864
|
+
turn.messages.push({
|
|
865
|
+
text: render(step.say, this.scope(turn, run)),
|
|
866
|
+
kind: "verbatim",
|
|
867
|
+
...(step.media ? { media: step.media } : {}),
|
|
868
|
+
afterMs: turn.afterMs,
|
|
869
|
+
key,
|
|
870
|
+
runId: run.id,
|
|
871
|
+
stepId: step.id,
|
|
872
|
+
});
|
|
873
|
+
turn.afterMs = 0;
|
|
874
|
+
turn.spokeBy.add(run.id);
|
|
875
|
+
turn.spoke = true;
|
|
876
|
+
this.outcome(turn, run, { kind: "say", status: "ok", key, next: nextLabel(step.then) });
|
|
877
|
+
this.follow(turn, run, flow, step, step.then);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (isDo(step)) {
|
|
882
|
+
const action = this.options.actions?.[step.do];
|
|
883
|
+
let result: ActionResult;
|
|
884
|
+
if (!action) {
|
|
885
|
+
// `validateFlow` rejects this at agent build; a spec that reached the
|
|
886
|
+
// runner another way still names what is missing rather than failing blank.
|
|
887
|
+
result = { failed: `unknown action "${step.do}"` };
|
|
888
|
+
} else {
|
|
889
|
+
try {
|
|
890
|
+
result = await action.run(renderDeep(step.with ?? {}, this.scope(turn, run)), {
|
|
891
|
+
context: turn.context, data: turn.session.data, input: run.input, run, key, dedupeKey: run.dedupeKey,
|
|
892
|
+
silenced: turn.silenced, now: turn.now, set: (patch) => void Object.assign(turn.session.data, patch),
|
|
893
|
+
});
|
|
894
|
+
} catch (error) {
|
|
895
|
+
result = { failed: error instanceof Error ? error.message : String(error) };
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if ("ok" in result) {
|
|
899
|
+
if (result.spoke) {
|
|
900
|
+
turn.spokeBy.add(run.id);
|
|
901
|
+
turn.spoke = true;
|
|
902
|
+
}
|
|
903
|
+
this.outcome(turn, run, { kind: "do", status: "ok", key, ...(result.detail ? { detail: result.detail } : {}), next: nextLabel(step.then) });
|
|
904
|
+
this.follow(turn, run, flow, step, step.then);
|
|
905
|
+
} else if ("skipped" in result) {
|
|
906
|
+
this.outcome(turn, run, { kind: "do", status: "skipped", key, code: "action-skipped", detail: result.skipped, next: nextLabel(step.then) });
|
|
907
|
+
this.follow(turn, run, flow, step, step.then);
|
|
908
|
+
} else if ("failed" in result) {
|
|
909
|
+
const target = step.onFail ?? step.then;
|
|
910
|
+
this.outcome(turn, run, { kind: "do", status: "failed", key, code: "action-failed", detail: result.failed, next: nextLabel(target) });
|
|
911
|
+
this.follow(turn, run, flow, step, target);
|
|
912
|
+
} else {
|
|
913
|
+
const at = new Date(turn.now.getTime() + parseDuration(result.defer));
|
|
914
|
+
this.park(turn, run, { kind: "timer", key: `${run.id}:${step.id}:${at.getTime()}` }, at);
|
|
915
|
+
this.outcome(turn, run, { kind: "do", status: "deferred", key, code: "action-deferred", detail: result.detail, until: at.toISOString() });
|
|
916
|
+
}
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (isWait(step)) {
|
|
921
|
+
const ms = parseDuration(step.wait);
|
|
922
|
+
if (ms <= SHORT_WAIT_MS) {
|
|
923
|
+
// ponytail: a short wait rides on the next message only when the very next step is a say or talk step; an `if` in between is not looked through.
|
|
924
|
+
const next = this.peek(flow, step);
|
|
925
|
+
if (next && (isSay(next) || isTalk(next))) {
|
|
926
|
+
turn.afterMs += ms;
|
|
927
|
+
this.outcome(turn, run, { kind: "wait", status: "ok", key, code: "inline-delay", detail: `${ms}ms`, next: nextLabel(step.then) });
|
|
928
|
+
this.follow(turn, run, flow, step, step.then);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const at = this.snap(turn, new Date(turn.now.getTime() + ms), step.businessHours);
|
|
933
|
+
this.park(turn, run, { kind: "timer", key: `${run.id}:${step.id}:${at.getTime()}` }, at);
|
|
934
|
+
this.outcome(turn, run, { kind: "wait", status: "waiting", key, until: at.toISOString() });
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (isWaitEvent(step)) {
|
|
939
|
+
const at = new Date(turn.now.getTime() + parseDuration(step.wait.upTo ?? DEFAULT_EVENT_WAIT));
|
|
940
|
+
this.park(turn, run, { kind: "event", event: step.wait.event, key: `${run.id}:${step.id}:${at.getTime()}` }, at);
|
|
941
|
+
this.outcome(turn, run, { kind: "wait", status: "waiting", key, code: "awaiting-event", detail: step.wait.event, until: at.toISOString() });
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (isIf(step)) {
|
|
946
|
+
const target = this.holds(step.if, turn, run) ? step.then : (step.else ?? "end");
|
|
947
|
+
this.outcome(turn, run, { kind: "if", status: "ok", key, next: nextLabel(target) });
|
|
948
|
+
this.follow(turn, run, flow, step, target);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/** The step `then` leads to, when it is a step of this flow. */
|
|
953
|
+
private peek(flow: Flow<C, D>, step: Step<C, D>): Step<C, D> | undefined {
|
|
954
|
+
const { then } = step;
|
|
955
|
+
if (then === undefined) return flow.steps[flow.steps.indexOf(step) + 1];
|
|
956
|
+
if (typeof then === "string") return then === "end" ? undefined : this.stepOf(flow, then);
|
|
957
|
+
return "step" in then ? this.stepOf(flow, then.step) : undefined;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
private reportMaxAsks(turn: Turn<C, D>, run: Run, step: TalkOf<C, D>): void {
|
|
961
|
+
const data: Record<string, unknown> = turn.session.data;
|
|
962
|
+
const maxAsks = step.maxAsks ?? DEFAULT_MAX_ASKS;
|
|
963
|
+
for (const field of step.collect ?? []) {
|
|
964
|
+
const asked = run.asked[field] ?? 0;
|
|
965
|
+
if (!isKnown(data[field]) && asked >= maxAsks) {
|
|
966
|
+
this.outcome(turn, run, { kind: "collect", status: "skipped", key: this.stepKey(run, step), code: "max-asks", detail: field });
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// ── Movement primitives ───────────────────────────────────────────────
|
|
972
|
+
|
|
973
|
+
private follow(turn: Turn<C, D>, run: Run, flow: Flow<C, D>, step: Step<C, D>, target: Next<D> | undefined): void {
|
|
974
|
+
if (target === undefined) {
|
|
975
|
+
const next = flow.steps[flow.steps.indexOf(step) + 1];
|
|
976
|
+
if (next) this.enter(run, next.id);
|
|
977
|
+
else this.finishFlow(turn, run, flow);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (typeof target === "string") {
|
|
981
|
+
if (target === "end") this.finishFlow(turn, run, flow);
|
|
982
|
+
else this.jump(turn, run, flow, target);
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if ("step" in target) {
|
|
986
|
+
const data: Record<string, unknown> = turn.session.data;
|
|
987
|
+
for (const field of target.clear ?? []) delete data[field];
|
|
988
|
+
this.jump(turn, run, flow, target.step);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const childId = render(target.flow, this.scope(turn, run));
|
|
992
|
+
const key = this.stepKey(run, step);
|
|
993
|
+
this.endRun(turn, run, "flow");
|
|
994
|
+
this.chain(turn, childId, key, target.input ?? run.input, run.hop + 1);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/** Start a child flow that holds the floor and moves in this same phase. */
|
|
998
|
+
private chain(turn: Turn<C, D>, flowId: string, key: string, input: unknown, hop: number, keepData = false): void {
|
|
999
|
+
const child = this.flows.get(flowId);
|
|
1000
|
+
if (!child) {
|
|
1001
|
+
turn.skipped.push({ flowId, anchor: turn.session.id, triggerKey: key, code: "flow-gone", message: OUTCOME_MESSAGES["flow-gone"] });
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
const run = this.startRun(turn, child, "flow", key, { payload: input, hop, keepData });
|
|
1005
|
+
if (!run) return;
|
|
1006
|
+
turn.floorRunId = run.id;
|
|
1007
|
+
if (turn.ingesting) turn.floorFromIngest = true;
|
|
1008
|
+
turn.queue.push(run);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
private jump(turn: Turn<C, D>, run: Run, flow: Flow<C, D>, stepId: string): void {
|
|
1012
|
+
if (this.stepOf(flow, stepId)) this.enter(run, stepId);
|
|
1013
|
+
else this.endRun(turn, run, "skipped", "step-gone");
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/** Entering a step is what mints a new visit, and with it new action and message keys (I4). */
|
|
1017
|
+
private enter(run: Run, stepId: string): void {
|
|
1018
|
+
run.stepId = stepId;
|
|
1019
|
+
run.visits[stepId] = (run.visits[stepId] ?? 0) + 1;
|
|
1020
|
+
run.status = "running";
|
|
1021
|
+
delete run.waiting;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
private finishFlow(turn: Turn<C, D>, run: Run, flow: Flow<C, D>): void {
|
|
1025
|
+
const onEnd = flow.onEnd ?? "end";
|
|
1026
|
+
const last = flow.steps[flow.steps.length - 1];
|
|
1027
|
+
if (onEnd === "stay" && last) {
|
|
1028
|
+
// "repete o último passo": the run stays live at its last step, re-entered so the repetition mints a new key, and runs it on the next message.
|
|
1029
|
+
this.enter(run, last.id);
|
|
1030
|
+
run.status = "asking";
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (onEnd === "reset" && last) {
|
|
1034
|
+
// The run record closes (`reason: 'reset'`) and a fresh run of the same flow starts at step one, data kept.
|
|
1035
|
+
// It is a chain into itself, so it costs a hop: a code-only flow that resets forever stops at the hop cap instead of spinning.
|
|
1036
|
+
const key = this.stepKey(run, last);
|
|
1037
|
+
this.endRun(turn, run, "reset");
|
|
1038
|
+
this.chain(turn, flow.id, key, run.input, run.hop + 1, true);
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
this.endRun(turn, run, "end");
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
private endRun(turn: Turn<C, D>, run: Run, reason: EndReason, code?: StepOutcomeCode, kind?: StepOutcomeKind, detail?: string): void {
|
|
1045
|
+
const index = turn.session.runs.indexOf(run);
|
|
1046
|
+
if (index >= 0) turn.session.runs.splice(index, 1);
|
|
1047
|
+
if (code) {
|
|
1048
|
+
const flow = this.flows.get(run.flowId);
|
|
1049
|
+
this.outcome(turn, run, { kind: kind ?? kindOf(flow && this.stepOf(flow, run.stepId)), status: reason === "failed" ? "failed" : "skipped", code, ...(detail ? { detail } : {}) });
|
|
1050
|
+
}
|
|
1051
|
+
turn.ended.push({ ...run, reason });
|
|
1052
|
+
if (turn.talk?.run === run) turn.talk = undefined;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
private park(turn: Turn<C, D>, run: Run, waiting: { kind: "timer" | "event"; key: string; event?: string }, at: Date): void {
|
|
1056
|
+
run.status = "waiting";
|
|
1057
|
+
run.waiting = { ...waiting, until: at.toISOString(), setAt: turn.nowIso };
|
|
1058
|
+
turn.schedule.push({ key: waiting.key, at });
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/** When nobody asks, the most recently suspended run returns to asking. */
|
|
1062
|
+
private resumeSuspended(turn: Turn<C, D>): void {
|
|
1063
|
+
if (this.asker(turn)) return;
|
|
1064
|
+
const suspendedAt = (r: Run): string => r.suspendedAt ?? r.startedAt;
|
|
1065
|
+
const next = turn.session.runs
|
|
1066
|
+
.filter((r) => r.status === "suspended")
|
|
1067
|
+
.reduce<Run | undefined>((top, cur) => (top && suspendedAt(top) > suspendedAt(cur) ? top : cur), undefined);
|
|
1068
|
+
if (next) {
|
|
1069
|
+
next.status = "asking";
|
|
1070
|
+
delete next.suspendedAt;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// ── Settle: the one applier after Speak (design §4.7) ─────────────────
|
|
1075
|
+
|
|
1076
|
+
async settle(turn: Turn<C, D>, outcome: SpeakOutcome | null): Promise<void> {
|
|
1077
|
+
if (turn.ignored) return;
|
|
1078
|
+
turn.speakDone = true;
|
|
1079
|
+
const { talk } = turn;
|
|
1080
|
+
turn.talk = undefined;
|
|
1081
|
+
if (outcome && "deferred" in outcome) {
|
|
1082
|
+
turn.llmCalls += outcome.llmCalls;
|
|
1083
|
+
turn.usage = addUsage(turn.usage, outcome.usage);
|
|
1084
|
+
if (talk) this.deferTalk(turn, talk, outcome.deferred);
|
|
1085
|
+
else this.outcome(turn, undefined, { kind: "idle", status: "deferred", code: outcome.deferred.code, llmCalls: outcome.llmCalls });
|
|
1086
|
+
} else if (outcome) {
|
|
1087
|
+
const { spoken } = outcome;
|
|
1088
|
+
turn.llmCalls += spoken.llmCalls;
|
|
1089
|
+
turn.usage = addUsage(turn.usage, spoken.usage);
|
|
1090
|
+
if (talk) {
|
|
1091
|
+
this.applySpoken(turn, talk, spoken);
|
|
1092
|
+
await this.drain(turn);
|
|
1093
|
+
} else {
|
|
1094
|
+
if (spoken.message) {
|
|
1095
|
+
turn.messages.push({ text: spoken.message, kind: "ai", afterMs: turn.afterMs, key: `idle:${turn.triggerKey}` });
|
|
1096
|
+
turn.afterMs = 0;
|
|
1097
|
+
turn.spoke = true;
|
|
1098
|
+
}
|
|
1099
|
+
Object.assign(turn.session.data, spoken.data);
|
|
1100
|
+
this.outcome(turn, undefined, { kind: "idle", status: "ok", key: `idle:${turn.triggerKey}`, llmCalls: spoken.llmCalls });
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
if (turn.spoke) turn.session.lastAssistantAt = turn.nowIso;
|
|
1104
|
+
this.resumeSuspended(turn);
|
|
1105
|
+
this.armSilence(turn);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
private applySpoken(turn: Turn<C, D>, talk: TalkRequest<C, D>, spoken: Spoken): void {
|
|
1109
|
+
const { run, flow, step } = talk;
|
|
1110
|
+
const key = this.stepKey(run, step);
|
|
1111
|
+
if (spoken.message) {
|
|
1112
|
+
turn.messages.push({ text: spoken.message, kind: "ai", afterMs: turn.afterMs, key, runId: run.id, stepId: step.id });
|
|
1113
|
+
turn.afterMs = 0;
|
|
1114
|
+
turn.spokeBy.add(run.id);
|
|
1115
|
+
turn.spoke = true;
|
|
1116
|
+
}
|
|
1117
|
+
for (const [field, raw] of Object.entries(spoken.fields)) this.writeField(turn, field, raw);
|
|
1118
|
+
Object.assign(turn.session.data, spoken.data);
|
|
1119
|
+
this.outcome(turn, run, { kind: talkKind(step), status: "ok", key, llmCalls: spoken.llmCalls, stepId: step.id });
|
|
1120
|
+
if (!turn.session.runs.includes(run)) return;
|
|
1121
|
+
if (step.collect?.length) {
|
|
1122
|
+
const pending = pendingFields(step, turn.session.data, run.asked);
|
|
1123
|
+
if (pending.length) {
|
|
1124
|
+
for (const field of pending) run.asked[field] = (run.asked[field] ?? 0) + 1;
|
|
1125
|
+
run.status = "asking";
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
this.reportMaxAsks(turn, run, step);
|
|
1129
|
+
}
|
|
1130
|
+
run.status = "running";
|
|
1131
|
+
this.follow(turn, run, flow, step, step.then);
|
|
1132
|
+
if (turn.session.runs.includes(run) && run.status === "running") turn.queue.push(run);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/** The provider failed: park the talk step under a retry wake, or stop trying. */
|
|
1136
|
+
private deferTalk(turn: Turn<C, D>, talk: TalkRequest<C, D>, deferred: Deferral): void {
|
|
1137
|
+
const { run, step } = talk;
|
|
1138
|
+
const key = this.stepKey(run, step);
|
|
1139
|
+
let attempt = 0;
|
|
1140
|
+
for (let i = run.outcomes.length - 1; i >= 0 && run.outcomes[i].status === "deferred" && run.outcomes[i].stepId === step.id; i--) attempt++;
|
|
1141
|
+
|
|
1142
|
+
const at = this.retryAt(turn, deferred, attempt);
|
|
1143
|
+
if (!at) {
|
|
1144
|
+
// A wrong key, a prompt past the context window or a spent balance is not
|
|
1145
|
+
// going to be different in fifteen minutes. End the run and let the host
|
|
1146
|
+
// read the code, rather than burning two model calls against the same
|
|
1147
|
+
// wall until someone notices.
|
|
1148
|
+
this.outcome(turn, run, { kind: talkKind(step), status: "failed", key, code: deferred.code });
|
|
1149
|
+
this.endRun(turn, run, "failed");
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const visit = run.visits[step.id] ?? 0;
|
|
1153
|
+
this.park(turn, run, { kind: "timer", key: `${run.id}:${step.id}:${visit}:retry:${at.getTime()}` }, at);
|
|
1154
|
+
this.outcome(turn, run, { kind: talkKind(step), status: "deferred", key, code: deferred.code, until: at.toISOString() });
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/** When to try again, or `null` when trying again cannot help. */
|
|
1158
|
+
private retryAt(turn: Turn<C, D>, deferred: Deferral, attempt: number): Date | null {
|
|
1159
|
+
if (!deferred.retryable || attempt >= RETRY_BACKOFF.length) return null;
|
|
1160
|
+
const ladder = turn.now.getTime() + parseDuration(RETRY_BACKOFF[attempt]);
|
|
1161
|
+
// The provider said when its window reopens. One wake then beats climbing a
|
|
1162
|
+
// ladder measured in minutes inside a limit measured in hours.
|
|
1163
|
+
const reset = deferred.resetAtMs;
|
|
1164
|
+
return new Date(reset !== undefined && reset > ladder ? reset : ladder);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/** The assistant spoke this turn: its silence wakes replace the ones its last words set. */
|
|
1168
|
+
private armSilence(turn: Turn<C, D>): void {
|
|
1169
|
+
const previous = turn.original?.lastAssistantAt;
|
|
1170
|
+
if (turn.session.lastAssistantAt === previous) return;
|
|
1171
|
+
turn.schedule.push(...this.silenceWakes(turn, previous));
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/** The assistant spoke last: every silence flow passing `if` and `repeat` gets a wake. */
|
|
1175
|
+
private silenceWakes(turn: Turn<C, D>, previous?: string): ScheduleEntry[] {
|
|
1176
|
+
const { session } = turn;
|
|
1177
|
+
const { lastAssistantAt, lastUserAt } = session;
|
|
1178
|
+
if (!lastAssistantAt) return [];
|
|
1179
|
+
if (lastUserAt && Date.parse(lastUserAt) > Date.parse(lastAssistantAt)) return [];
|
|
1180
|
+
const ms = Date.parse(lastAssistantAt);
|
|
1181
|
+
const wakes: ScheduleEntry[] = [];
|
|
1182
|
+
for (const flow of this.flows.values()) {
|
|
1183
|
+
const trigger = (flow.on ?? []).find((t): t is SilenceTrigger<C, D> => "silence" in t);
|
|
1184
|
+
if (!trigger) continue;
|
|
1185
|
+
const key = String(ms);
|
|
1186
|
+
if (trigger.if && !this.holds(trigger.if, turn, this.draftRun(turn, flow, "silence", key))) continue;
|
|
1187
|
+
if (!this.repeatAllows(turn, flow, trigger, key)) continue;
|
|
1188
|
+
const at = this.snap(turn, new Date(ms + parseDuration(trigger.silence)), trigger.businessHours);
|
|
1189
|
+
wakes.push({
|
|
1190
|
+
key: `silence:${flow.id}:${session.id}:${ms}`,
|
|
1191
|
+
at,
|
|
1192
|
+
...(previous ? { replaces: `silence:${flow.id}:${session.id}:${Date.parse(previous)}` } : {}),
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
return wakes;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// ── Return (design §4.8) ──────────────────────────────────────────────
|
|
1199
|
+
|
|
1200
|
+
finish(turn: Turn<C, D>): TurnResult<D> {
|
|
1201
|
+
const { session, messages, schedule, outcomes, started, ended, skipped, llmCalls, usage } = turn;
|
|
1202
|
+
const changed =
|
|
1203
|
+
!turn.ignored &&
|
|
1204
|
+
(!turn.original || !deepEqual(session, turn.original) || messages.length > 0 || schedule.length > 0 || outcomes.length > 0 || skipped.length > 0);
|
|
1205
|
+
return { session, changed, messages, schedule, outcomes, started, ended, skipped, llmCalls, ...(usage ? { usage } : {}) };
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/** The `next` column of an outcome line: a step id, `end`, or the flow a `then` chains into. */
|
|
1210
|
+
function nextLabel<D>(target: Next<D> | undefined): string | undefined {
|
|
1211
|
+
if (target === undefined) return undefined;
|
|
1212
|
+
if (typeof target === "string") return target;
|
|
1213
|
+
return "step" in target ? target.step : `flow:${target.flow}`;
|
|
1214
|
+
}
|