@falai/agent 2.6.0 → 2.7.0
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 +1 -1
- package/dist/adapters/MemoryAdapter.js +29 -33
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +16 -11
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.d.ts +1 -0
- package/dist/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/adapters/RedisAdapter.js +75 -25
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +7 -29
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/adapters/sessionRow.d.ts +22 -0
- package/dist/adapters/sessionRow.d.ts.map +1 -0
- package/dist/adapters/sessionRow.js +48 -0
- package/dist/adapters/sessionRow.js.map +1 -0
- package/dist/cjs/adapters/MemoryAdapter.js +29 -33
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +16 -11
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.d.ts +1 -0
- package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.js +75 -25
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +7 -29
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/sessionRow.d.ts +22 -0
- package/dist/cjs/adapters/sessionRow.d.ts.map +1 -0
- package/dist/cjs/adapters/sessionRow.js +52 -0
- package/dist/cjs/adapters/sessionRow.js.map +1 -0
- package/dist/cjs/core/Agent.d.ts +8 -0
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +43 -3
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts +8 -18
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.js +23 -26
- package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
- package/dist/cjs/core/CompactionEngine.d.ts +14 -1
- package/dist/cjs/core/CompactionEngine.d.ts.map +1 -1
- package/dist/cjs/core/CompactionEngine.js +30 -6
- package/dist/cjs/core/CompactionEngine.js.map +1 -1
- package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
- package/dist/cjs/core/FlowRouter.js +32 -10
- package/dist/cjs/core/FlowRouter.js.map +1 -1
- package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -1
- package/dist/cjs/core/ResponseGenerationError.js +3 -5
- package/dist/cjs/core/ResponseGenerationError.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts +29 -0
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +141 -18
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +36 -6
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +208 -73
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -1
- package/dist/cjs/core/SessionFinalizer.js +30 -3
- package/dist/cjs/core/SessionFinalizer.js.map +1 -1
- package/dist/cjs/core/SessionManager.d.ts +10 -1
- package/dist/cjs/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/core/SessionManager.js +43 -16
- package/dist/cjs/core/SessionManager.js.map +1 -1
- package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
- package/dist/cjs/core/SignalProcessor.js +5 -77
- package/dist/cjs/core/SignalProcessor.js.map +1 -1
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +50 -2
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/StepLifecycle.d.ts +16 -6
- package/dist/cjs/core/StepLifecycle.d.ts.map +1 -1
- package/dist/cjs/core/StepLifecycle.js +97 -14
- package/dist/cjs/core/StepLifecycle.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.js +28 -4
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
- package/dist/cjs/core/ToolLoopExecutor.d.ts +5 -1
- package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -1
- package/dist/cjs/core/ToolLoopExecutor.js +156 -64
- package/dist/cjs/core/ToolLoopExecutor.js.map +1 -1
- package/dist/cjs/core/ToolManager.d.ts +1 -1
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +40 -17
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/core/flow-namespace.d.ts +15 -0
- package/dist/cjs/core/flow-namespace.d.ts.map +1 -1
- package/dist/cjs/core/flow-namespace.js +22 -0
- package/dist/cjs/core/flow-namespace.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +5 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +10 -3
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +54 -73
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +9 -3
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +24 -68
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +9 -0
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.js +101 -74
- package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts +1 -1
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +1 -1
- package/dist/cjs/providers/errorClassification.d.ts +7 -0
- package/dist/cjs/providers/errorClassification.d.ts.map +1 -1
- package/dist/cjs/providers/errorClassification.js +27 -0
- package/dist/cjs/providers/errorClassification.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +35 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/flow.d.ts +49 -32
- package/dist/cjs/types/flow.d.ts.map +1 -1
- package/dist/cjs/types/index.d.ts +1 -1
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/tool.d.ts +2 -0
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/types/tool.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +4 -4
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +10 -1
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/retry.d.ts +99 -1
- package/dist/cjs/utils/retry.d.ts.map +1 -1
- package/dist/cjs/utils/retry.js +140 -3
- package/dist/cjs/utils/retry.js.map +1 -1
- package/dist/cjs/utils/serialize.d.ts +17 -0
- package/dist/cjs/utils/serialize.d.ts.map +1 -1
- package/dist/cjs/utils/serialize.js +33 -0
- package/dist/cjs/utils/serialize.js.map +1 -1
- package/dist/cjs/utils/session.d.ts +21 -1
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +36 -4
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/core/Agent.d.ts +8 -0
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +43 -3
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/AutoChainExecutor.d.ts +8 -18
- package/dist/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/core/AutoChainExecutor.js +23 -26
- package/dist/core/AutoChainExecutor.js.map +1 -1
- package/dist/core/CompactionEngine.d.ts +14 -1
- package/dist/core/CompactionEngine.d.ts.map +1 -1
- package/dist/core/CompactionEngine.js +30 -6
- package/dist/core/CompactionEngine.js.map +1 -1
- package/dist/core/FlowRouter.d.ts.map +1 -1
- package/dist/core/FlowRouter.js +32 -10
- package/dist/core/FlowRouter.js.map +1 -1
- package/dist/core/ResponseGenerationError.d.ts.map +1 -1
- package/dist/core/ResponseGenerationError.js +3 -5
- package/dist/core/ResponseGenerationError.js.map +1 -1
- package/dist/core/ResponseModal.d.ts +29 -0
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +142 -19
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +36 -6
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +208 -73
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/SessionFinalizer.d.ts.map +1 -1
- package/dist/core/SessionFinalizer.js +31 -4
- package/dist/core/SessionFinalizer.js.map +1 -1
- package/dist/core/SessionManager.d.ts +10 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +44 -17
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SignalProcessor.d.ts.map +1 -1
- package/dist/core/SignalProcessor.js +5 -77
- package/dist/core/SignalProcessor.js.map +1 -1
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +50 -2
- package/dist/core/Step.js.map +1 -1
- package/dist/core/StepLifecycle.d.ts +16 -6
- package/dist/core/StepLifecycle.d.ts.map +1 -1
- package/dist/core/StepLifecycle.js +97 -14
- package/dist/core/StepLifecycle.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/core/StreamingToolExecutor.js +28 -4
- package/dist/core/StreamingToolExecutor.js.map +1 -1
- package/dist/core/ToolLoopExecutor.d.ts +5 -1
- package/dist/core/ToolLoopExecutor.d.ts.map +1 -1
- package/dist/core/ToolLoopExecutor.js +156 -64
- package/dist/core/ToolLoopExecutor.js.map +1 -1
- package/dist/core/ToolManager.d.ts +1 -1
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +41 -18
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/core/flow-namespace.d.ts +15 -0
- package/dist/core/flow-namespace.d.ts.map +1 -1
- package/dist/core/flow-namespace.js +22 -0
- package/dist/core/flow-namespace.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +10 -3
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +56 -75
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +9 -3
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +26 -70
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.d.ts +9 -0
- package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.js +103 -76
- package/dist/providers/OpenAICompatibleProvider.js.map +1 -1
- package/dist/providers/OpenAIProvider.d.ts +1 -1
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +1 -1
- package/dist/providers/errorClassification.d.ts +7 -0
- package/dist/providers/errorClassification.d.ts.map +1 -1
- package/dist/providers/errorClassification.js +26 -0
- package/dist/providers/errorClassification.js.map +1 -1
- package/dist/types/agent.d.ts +35 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/flow.d.ts +49 -32
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/tool.d.ts +2 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/index.d.ts +4 -4
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/retry.d.ts +99 -1
- package/dist/utils/retry.d.ts.map +1 -1
- package/dist/utils/retry.js +137 -3
- package/dist/utils/retry.js.map +1 -1
- package/dist/utils/serialize.d.ts +17 -0
- package/dist/utils/serialize.d.ts.map +1 -1
- package/dist/utils/serialize.js +31 -0
- package/dist/utils/serialize.js.map +1 -1
- package/dist/utils/session.d.ts +21 -1
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +33 -4
- package/dist/utils/session.js.map +1 -1
- package/docs/concepts/architecture.md +3 -3
- package/docs/concepts/directives.md +1 -1
- package/docs/guides/error-handling.md +46 -45
- package/docs/guides/flow-control.md +8 -1
- package/docs/guides/instructions.md +15 -6
- package/docs/guides/persistence.md +12 -5
- package/docs/guides/streaming.md +10 -0
- package/docs/migration/README.md +4 -0
- package/docs/migration/v2-3-to-v2-4.md +4 -0
- package/docs/migration/v2-6-to-v2-7.md +246 -0
- package/docs/reference/adapters.md +15 -1
- package/docs/reference/create-agent.md +30 -0
- package/docs/reference/directive.md +1 -1
- package/docs/reference/errors.md +29 -31
- package/docs/reference/providers.md +23 -19
- package/docs/reference/step.md +28 -21
- package/docs/reference/tool.md +14 -5
- package/docs/start/02-first-agent.md +8 -4
- package/docs/start/03-collect-data.md +19 -10
- package/examples/01-quickstart.ts +1 -1
- package/examples/02-data-extraction.ts +1 -1
- package/examples/03-tools.ts +1 -1
- package/examples/04-instructions.ts +1 -1
- package/examples/05-branching.ts +1 -1
- package/examples/06-flow-control.ts +3 -3
- package/examples/07-streaming.ts +1 -1
- package/examples/08-persistence.ts +1 -1
- package/examples/09-signals.ts +1 -1
- package/package.json +2 -2
- package/src/adapters/MemoryAdapter.ts +29 -33
- package/src/adapters/PostgreSQLAdapter.ts +23 -18
- package/src/adapters/RedisAdapter.ts +81 -34
- package/src/adapters/SQLiteAdapter.ts +10 -31
- package/src/adapters/sessionRow.ts +57 -0
- package/src/core/Agent.ts +51 -3
- package/src/core/AutoChainExecutor.ts +37 -50
- package/src/core/CompactionEngine.ts +40 -6
- package/src/core/FlowRouter.ts +38 -12
- package/src/core/ResponseGenerationError.ts +3 -6
- package/src/core/ResponseModal.ts +185 -19
- package/src/core/ResponsePipeline.ts +259 -85
- package/src/core/SessionFinalizer.ts +35 -5
- package/src/core/SessionManager.ts +55 -21
- package/src/core/SignalProcessor.ts +5 -85
- package/src/core/Step.ts +79 -2
- package/src/core/StepLifecycle.ts +125 -22
- package/src/core/StreamingToolExecutor.ts +31 -6
- package/src/core/ToolLoopExecutor.ts +201 -75
- package/src/core/ToolManager.ts +45 -18
- package/src/core/flow-namespace.ts +29 -0
- package/src/index.ts +4 -1
- package/src/providers/AnthropicProvider.ts +83 -131
- package/src/providers/GeminiProvider.ts +42 -116
- package/src/providers/OpenAICompatibleProvider.ts +132 -133
- package/src/providers/OpenAIProvider.ts +2 -2
- package/src/providers/errorClassification.ts +34 -0
- package/src/types/agent.ts +34 -0
- package/src/types/flow.ts +37 -39
- package/src/types/index.ts +1 -0
- package/src/types/tool.ts +2 -0
- package/src/utils/index.ts +22 -3
- package/src/utils/retry.ts +251 -2
- package/src/utils/serialize.ts +38 -0
- package/src/utils/session.ts +41 -4
- package/dist/cjs/core/DirectiveBus.d.ts +0 -88
- package/dist/cjs/core/DirectiveBus.d.ts.map +0 -1
- package/dist/cjs/core/DirectiveBus.js +0 -196
- package/dist/cjs/core/DirectiveBus.js.map +0 -1
- package/dist/core/DirectiveBus.d.ts +0 -88
- package/dist/core/DirectiveBus.d.ts.map +0 -1
- package/dist/core/DirectiveBus.js +0 -192
- package/dist/core/DirectiveBus.js.map +0 -1
- package/src/core/DirectiveBus.ts +0 -248
package/src/core/Agent.ts
CHANGED
|
@@ -205,8 +205,14 @@ export class Agent<TContext = unknown, TData = unknown> implements ResponseModal
|
|
|
205
205
|
this.signalProcessor = undefined;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
// Set log level based on debug option
|
|
208
|
+
// Set log level based on debug option. NOTE: loglevel's default logger is
|
|
209
|
+
// process-global — one agent enabling debug turns on DEBUG for every agent
|
|
210
|
+
// in the process. Warned so multi-tenant embedders aren't surprised.
|
|
209
211
|
if (options.debug) {
|
|
212
|
+
logger.warn(
|
|
213
|
+
`[Agent] "${options.name}" enabled debug logging via the PROCESS-GLOBAL loglevel level. ` +
|
|
214
|
+
`Every agent in this process now logs at DEBUG. Scope logging in your host if needed.`
|
|
215
|
+
);
|
|
210
216
|
logger.setLevel(LoggerLevel.DEBUG);
|
|
211
217
|
}
|
|
212
218
|
|
|
@@ -255,7 +261,9 @@ export class Agent<TContext = unknown, TData = unknown> implements ResponseModal
|
|
|
255
261
|
this.tool = new ToolManager<TContext, TData>(this);
|
|
256
262
|
|
|
257
263
|
// Initialize ResponseModal for handling all response generation
|
|
258
|
-
this._responseModal = new ResponseModal<TContext, TData>(this
|
|
264
|
+
this._responseModal = new ResponseModal<TContext, TData>(this, {
|
|
265
|
+
maxToolLoops: options.maxToolLoops,
|
|
266
|
+
});
|
|
259
267
|
|
|
260
268
|
// Initialize persistence if configured
|
|
261
269
|
if (options.persistence) {
|
|
@@ -866,6 +874,26 @@ export class Agent<TContext = unknown, TData = unknown> implements ResponseModal
|
|
|
866
874
|
}
|
|
867
875
|
}
|
|
868
876
|
|
|
877
|
+
// Overlap detection: warn (don't throw) when the incoming flow's
|
|
878
|
+
// requiredFields intersect another registered flow's — the schema is
|
|
879
|
+
// agent-level, so both flows complete together and one is silently
|
|
880
|
+
// excluded from routing.
|
|
881
|
+
if (options.requiredFields && options.requiredFields.length > 0) {
|
|
882
|
+
const incoming = new Set(options.requiredFields.map(String));
|
|
883
|
+
for (const existing of this._flows) {
|
|
884
|
+
const shared = (existing.requiredFields ?? [])
|
|
885
|
+
.map(String)
|
|
886
|
+
.filter((f) => incoming.has(f));
|
|
887
|
+
if (shared.length > 0) {
|
|
888
|
+
logger.warn(
|
|
889
|
+
`[FlowConfigurationError] Overlapping requiredFields: flows "${existing.title}" and "${options.title}" share [${shared.join(', ')}]. ` +
|
|
890
|
+
`The schema is agent-level, so data collected for one flow marks the other complete and excludes it from routing. ` +
|
|
891
|
+
`Give each flow distinct requiredFields, or set \`reentrant: true\` on flows that legitimately share fields.`
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
869
897
|
const flow = new Flow<TContext, TData>(options, this);
|
|
870
898
|
|
|
871
899
|
// Validate that step collect fields reference valid schema keys
|
|
@@ -1149,12 +1177,20 @@ export class Agent<TContext = unknown, TData = unknown> implements ResponseModal
|
|
|
1149
1177
|
*
|
|
1150
1178
|
* String form desugars to `{ goTo: target }`.
|
|
1151
1179
|
*
|
|
1180
|
+
* Durability: with a persistence adapter and autoSave configured (the
|
|
1181
|
+
* defaults), the queued directive is persisted immediately — safe for
|
|
1182
|
+
* out-of-process callers like webhooks or cron. Without an adapter it is
|
|
1183
|
+
* memory-only, as is `persistence.autoSave: false` (then persisting before
|
|
1184
|
+
* the next turn is the caller's job).
|
|
1185
|
+
*
|
|
1152
1186
|
* @param target - Flow ID/title string (desugars to `{ goTo: target }`) or a full Directive
|
|
1153
1187
|
* @param session - Session to update (uses current session if not provided)
|
|
1154
1188
|
* @returns Updated session with `pendingDirective` set
|
|
1155
1189
|
*
|
|
1156
1190
|
* @throws FlowConfigurationError if the string target doesn't match any flow
|
|
1157
1191
|
* @throws FlowConfigurationError if the directive fails validation
|
|
1192
|
+
* @throws SessionConflictError when persistence is enabled and another writer
|
|
1193
|
+
* moved the stored session since this copy was loaded
|
|
1158
1194
|
*
|
|
1159
1195
|
* @example
|
|
1160
1196
|
* // String shorthand — desugars to { goTo: 'Feedback' }
|
|
@@ -1164,7 +1200,6 @@ export class Agent<TContext = unknown, TData = unknown> implements ResponseModal
|
|
|
1164
1200
|
* // Full directive
|
|
1165
1201
|
* const updated = await agent.dispatch({ goTo: 'Billing', reply: 'Transferring you now.' }, session);
|
|
1166
1202
|
*/
|
|
1167
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1168
1203
|
async dispatch(
|
|
1169
1204
|
target: string | Directive<TContext, TData>,
|
|
1170
1205
|
session?: SessionState<TData>
|
|
@@ -1223,6 +1258,19 @@ export class Agent<TContext = unknown, TData = unknown> implements ResponseModal
|
|
|
1223
1258
|
},
|
|
1224
1259
|
};
|
|
1225
1260
|
|
|
1261
|
+
// Durability: with an adapter + autoSave configured, dispatch persists
|
|
1262
|
+
// immediately — webhooks/cron run out-of-process from the responder, and a
|
|
1263
|
+
// memory-only queue would evaporate with this process. The save stamps the
|
|
1264
|
+
// new version back onto updatedSession, so the next turn's auto-save CAS
|
|
1265
|
+
// stays clean. Persisting BEFORE the in-memory sync keeps the existing
|
|
1266
|
+
// invariant: a throwing dispatch leaves the session untouched.
|
|
1267
|
+
if (this._persistenceManager && this.options.persistence?.autoSave !== false) {
|
|
1268
|
+
await this._persistenceManager.saveSessionState(updatedSession.id, updatedSession);
|
|
1269
|
+
logger.debug(
|
|
1270
|
+
`[Agent] Dispatched directive persisted to adapter for session ${updatedSession.id}`
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1226
1274
|
// Update current session in place if no explicit session was passed
|
|
1227
1275
|
if (!session && this.session.current) {
|
|
1228
1276
|
this.session.syncSession(updatedSession);
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
* Implements Algorithm 1 from `.kiro/specs/auto-steps/design.md`.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { SessionState } from "../types";
|
|
18
|
+
import type { Directive, SessionState } from "../types";
|
|
19
19
|
import type { Event } from "../types/history";
|
|
20
20
|
import { FlowConfigurationError, Step } from "./Step";
|
|
21
21
|
import { Flow } from "./Flow";
|
|
22
|
+
import { flow } from "./flow-namespace";
|
|
22
23
|
import { enterStep, mergeCollected, logger } from "../utils";
|
|
23
24
|
import { createTemplateContext } from "../utils/template";
|
|
24
25
|
import type {
|
|
@@ -31,20 +32,13 @@ import { evaluateIfPredicates } from "../utils/condition";
|
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* The directive-like object that `prepare` may return on auto-steps.
|
|
34
|
-
*
|
|
35
|
+
* A structural subset of Directive (pre-LLM + position fields), so results
|
|
36
|
+
* merge through the canonical Algorithm 4 (`flow.merge`).
|
|
35
37
|
*/
|
|
36
|
-
export
|
|
37
|
-
|
|
38
|
-
contextUpdate
|
|
39
|
-
|
|
40
|
-
reply?: string;
|
|
41
|
-
/** Position-changing: jump to a step within the current flow. */
|
|
42
|
-
goToStep?: string;
|
|
43
|
-
/** Position-changing: jump to another flow. */
|
|
44
|
-
goTo?: string;
|
|
45
|
-
/** Position-changing: mark the flow as complete. */
|
|
46
|
-
complete?: boolean;
|
|
47
|
-
}
|
|
38
|
+
export type AutoStepPrepareResult<TContext = unknown, TData = unknown> = Pick<
|
|
39
|
+
Directive<TContext, TData>,
|
|
40
|
+
"dataUpdate" | "contextUpdate" | "halt" | "reply" | "goToStep" | "goTo" | "complete"
|
|
41
|
+
>;
|
|
48
42
|
|
|
49
43
|
/**
|
|
50
44
|
* Result of running the auto-chain.
|
|
@@ -53,7 +47,7 @@ export interface AutoChainResult<TContext = unknown, TData = unknown> {
|
|
|
53
47
|
/** The interactive step to hand off to the LLM path (undefined if chain ended without one). */
|
|
54
48
|
resolvedStep?: Step<TContext, TData>;
|
|
55
49
|
/** The merged directive from the halting auto-step's prepare (only set when stoppedReason = 'halt'). */
|
|
56
|
-
mergedDirective?: AutoStepPrepareResult
|
|
50
|
+
mergedDirective?: AutoStepPrepareResult<TContext, TData>;
|
|
57
51
|
/** Why the chain stopped, if it didn't reach an interactive step normally. */
|
|
58
52
|
stoppedReason?: StoppedReason;
|
|
59
53
|
/** Updated session after all auto-step state writes. */
|
|
@@ -192,10 +186,24 @@ export class AutoChainExecutor<TContext = unknown, TData = unknown> {
|
|
|
192
186
|
|
|
193
187
|
// STEP 5: position-changing directive (goToStep, goTo, complete)
|
|
194
188
|
if (merged.goToStep) {
|
|
195
|
-
|
|
189
|
+
// String form targets the current flow; object form carrying
|
|
190
|
+
// a different `flow` defers to the pipeline for cross-flow
|
|
191
|
+
// resolution (same as goTo).
|
|
192
|
+
const goTarget = typeof merged.goToStep === "string"
|
|
193
|
+
? { step: merged.goToStep, flowId: undefined }
|
|
194
|
+
: { step: merged.goToStep.step, flowId: merged.goToStep.flow };
|
|
195
|
+
if (goTarget.flowId && goTarget.flowId !== flow.id) {
|
|
196
|
+
return {
|
|
197
|
+
resolvedStep: step,
|
|
198
|
+
mergedDirective: merged,
|
|
199
|
+
stoppedReason: 'goto',
|
|
200
|
+
session,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const targetStep = flow.getStep(goTarget.step);
|
|
196
204
|
if (!targetStep) {
|
|
197
205
|
throw new FlowConfigurationError(
|
|
198
|
-
`[FlowConfigurationError] Auto-step "${step.id}" goToStep targets unknown step: "${
|
|
206
|
+
`[FlowConfigurationError] Auto-step "${step.id}" goToStep targets unknown step: "${goTarget.step}" does not exist in the current flow. ` +
|
|
199
207
|
`Check the step id or use goTo to target a different flow.`
|
|
200
208
|
);
|
|
201
209
|
}
|
|
@@ -263,26 +271,15 @@ export class AutoChainExecutor<TContext = unknown, TData = unknown> {
|
|
|
263
271
|
}
|
|
264
272
|
|
|
265
273
|
/**
|
|
266
|
-
* Run
|
|
274
|
+
* Run the prepare hook for an auto-step, collecting any
|
|
267
275
|
* Directive return values (pre-LLM fields honored).
|
|
268
276
|
*/
|
|
269
277
|
private async runStepHooks(
|
|
270
278
|
step: Step<TContext, TData>,
|
|
271
279
|
session: SessionState<TData>,
|
|
272
280
|
context: TContext
|
|
273
|
-
): Promise<AutoStepPrepareResult | undefined> {
|
|
274
|
-
let merged: AutoStepPrepareResult | undefined;
|
|
275
|
-
|
|
276
|
-
// onEnter (future hook — not yet in StepOptions, but handle if present)
|
|
277
|
-
const stepWithHooks = step as Step<TContext, TData> & {
|
|
278
|
-
onEnter?: (context: TContext, data: Partial<TData> | undefined) => Promise<unknown>;
|
|
279
|
-
};
|
|
280
|
-
if (typeof stepWithHooks.onEnter === 'function') {
|
|
281
|
-
const onEnterResult = await stepWithHooks.onEnter(context, session.data);
|
|
282
|
-
if (onEnterResult && typeof onEnterResult === 'object') {
|
|
283
|
-
merged = this.mergeDirectives(merged, onEnterResult as AutoStepPrepareResult);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
281
|
+
): Promise<AutoStepPrepareResult<TContext, TData> | undefined> {
|
|
282
|
+
let merged: AutoStepPrepareResult<TContext, TData> | undefined;
|
|
286
283
|
|
|
287
284
|
// prepare hook — for auto-steps, prepare may return a Directive with pre-LLM fields
|
|
288
285
|
if (step.prepare) {
|
|
@@ -296,7 +293,7 @@ export class AutoChainExecutor<TContext = unknown, TData = unknown> {
|
|
|
296
293
|
);
|
|
297
294
|
// prepare may return void or a directive-like object
|
|
298
295
|
if (prepareResult && typeof prepareResult === 'object') {
|
|
299
|
-
merged = this.mergeDirectives(merged, prepareResult as AutoStepPrepareResult);
|
|
296
|
+
merged = this.mergeDirectives(merged, prepareResult as AutoStepPrepareResult<TContext, TData>);
|
|
300
297
|
}
|
|
301
298
|
} else {
|
|
302
299
|
// Tool reference (string or Tool object) — for auto-steps, tool-based
|
|
@@ -312,27 +309,17 @@ export class AutoChainExecutor<TContext = unknown, TData = unknown> {
|
|
|
312
309
|
}
|
|
313
310
|
|
|
314
311
|
/**
|
|
315
|
-
* Merge two directive-like objects
|
|
316
|
-
*
|
|
312
|
+
* Merge two directive-like objects via the canonical Algorithm 4 merge —
|
|
313
|
+
* one algorithm shared with tools, hooks and signals.
|
|
317
314
|
*/
|
|
318
315
|
private mergeDirectives(
|
|
319
|
-
base: AutoStepPrepareResult | undefined,
|
|
320
|
-
incoming: AutoStepPrepareResult
|
|
321
|
-
): AutoStepPrepareResult {
|
|
316
|
+
base: AutoStepPrepareResult<TContext, TData> | undefined,
|
|
317
|
+
incoming: AutoStepPrepareResult<TContext, TData>
|
|
318
|
+
): AutoStepPrepareResult<TContext, TData> {
|
|
322
319
|
if (!base) return { ...incoming };
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
: base.dataUpdate,
|
|
327
|
-
contextUpdate: incoming.contextUpdate
|
|
328
|
-
? { ...(base.contextUpdate || {}), ...incoming.contextUpdate }
|
|
329
|
-
: base.contextUpdate,
|
|
330
|
-
halt: incoming.halt ?? base.halt,
|
|
331
|
-
reply: incoming.reply ?? base.reply,
|
|
332
|
-
goTo: incoming.goTo ?? base.goTo,
|
|
333
|
-
goToStep: incoming.goToStep ?? base.goToStep,
|
|
334
|
-
complete: incoming.complete ?? base.complete,
|
|
335
|
-
};
|
|
320
|
+
// AutoStepPrepareResult is a structural subset of Directive, so both
|
|
321
|
+
// sides flow through the canonical merge directly — no casts needed.
|
|
322
|
+
return flow.merge<TContext, TData>(base, incoming);
|
|
336
323
|
}
|
|
337
324
|
|
|
338
325
|
/**
|
|
@@ -180,6 +180,27 @@ export class CompactionEngine {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Adjust the left edge of a preserved window so it never begins with an
|
|
185
|
+
* orphaned tool result — a role:'tool' message whose assistant tool_calls
|
|
186
|
+
* parent lies outside the window. Providers reject such histories at the
|
|
187
|
+
* next request.
|
|
188
|
+
*
|
|
189
|
+
* The preserve count is a target, not a hard guarantee: when the naive
|
|
190
|
+
* message-count boundary would split an assistant/tool pair, the window
|
|
191
|
+
* grows left just enough to keep the pair together.
|
|
192
|
+
*/
|
|
193
|
+
private static alignPreserveStart(
|
|
194
|
+
history: HistoryItem[],
|
|
195
|
+
start: number
|
|
196
|
+
): number {
|
|
197
|
+
let aligned = Math.max(0, Math.min(start, history.length));
|
|
198
|
+
while (aligned > 0 && history[aligned].role === "tool") {
|
|
199
|
+
aligned--;
|
|
200
|
+
}
|
|
201
|
+
return aligned;
|
|
202
|
+
}
|
|
203
|
+
|
|
183
204
|
/**
|
|
184
205
|
* Aggressive truncation fallback: remove oldest messages (no LLM needed).
|
|
185
206
|
* Keeps only the most recent messages that fit within the token budget.
|
|
@@ -191,11 +212,16 @@ export class CompactionEngine {
|
|
|
191
212
|
const threshold = options.maxTokens * options.compactionThreshold;
|
|
192
213
|
const preserveCount = options.preserveRecentCount;
|
|
193
214
|
|
|
194
|
-
// Always preserve the last preserveRecentCount messages
|
|
195
|
-
|
|
215
|
+
// Always preserve the last ~preserveRecentCount messages (aligned so
|
|
216
|
+
// the window never opens on an orphaned tool result)
|
|
217
|
+
const preserveStart = CompactionEngine.alignPreserveStart(
|
|
218
|
+
history,
|
|
219
|
+
Math.max(0, history.length - preserveCount)
|
|
220
|
+
);
|
|
221
|
+
const preserved = history.slice(preserveStart);
|
|
196
222
|
|
|
197
223
|
// Try to keep as many older messages as fit within budget
|
|
198
|
-
const older = history.slice(0,
|
|
224
|
+
const older = history.slice(0, preserveStart);
|
|
199
225
|
const result: HistoryItem[] = [];
|
|
200
226
|
|
|
201
227
|
// Add older messages from most recent backwards until we'd exceed budget
|
|
@@ -219,7 +245,9 @@ export class CompactionEngine {
|
|
|
219
245
|
* Layer 3 (micro_compact): Compress verbose tool outputs inline
|
|
220
246
|
* Layer 4 (auto_compact): Summarize old messages via LLM provider
|
|
221
247
|
*
|
|
222
|
-
* The last `preserveRecentCount` messages are NEVER modified or removed
|
|
248
|
+
* The last `preserveRecentCount` messages are NEVER modified or removed
|
|
249
|
+
* (the preserved window may extend slightly further left so it never
|
|
250
|
+
* splits an assistant/tool pair at its boundary).
|
|
223
251
|
*/
|
|
224
252
|
static async checkAndCompact(
|
|
225
253
|
history: HistoryItem[],
|
|
@@ -277,8 +305,14 @@ export class CompactionEngine {
|
|
|
277
305
|
}
|
|
278
306
|
|
|
279
307
|
// Layer 4: Auto-compact (summarize old messages via LLM)
|
|
280
|
-
|
|
281
|
-
|
|
308
|
+
// preserveRecentCount is a target, not a hard guarantee: the window's
|
|
309
|
+
// left edge is aligned so it never opens on an orphaned tool result.
|
|
310
|
+
const preserveStart = CompactionEngine.alignPreserveStart(
|
|
311
|
+
microCompacted,
|
|
312
|
+
Math.max(0, microCompacted.length - preserveCount)
|
|
313
|
+
);
|
|
314
|
+
const oldMessages = microCompacted.slice(0, preserveStart);
|
|
315
|
+
const recentMessages = microCompacted.slice(preserveStart);
|
|
282
316
|
|
|
283
317
|
const summary = await CompactionEngine.summarizeMessages(
|
|
284
318
|
oldMessages,
|
package/src/core/FlowRouter.ts
CHANGED
|
@@ -919,6 +919,18 @@ export class FlowRouter<TContext = unknown, TData = unknown> {
|
|
|
919
919
|
logger.debug(`[FlowRouter] Selected route: ${selectedFlow.title}`);
|
|
920
920
|
updatedSession = this.enterFlowIfNeeded(updatedSession, selectedFlow);
|
|
921
921
|
}
|
|
922
|
+
} else {
|
|
923
|
+
// Routing LLM output could not be parsed into the expected structured
|
|
924
|
+
// shape (no `flows` payload). The turn degrades to a fallback response
|
|
925
|
+
// with the flow position untouched — surface that loudly instead of
|
|
926
|
+
// failing silently.
|
|
927
|
+
const rawSnippet = (routingResult.message ?? "").trim().slice(0, 120);
|
|
928
|
+
logger.warn(
|
|
929
|
+
`[FlowRouter] Routing output unparseable: routing LLM returned no structured "flows" payload` +
|
|
930
|
+
`${rawSnippet ? `; raw output: "${rawSnippet}"` : ""}. ` +
|
|
931
|
+
`This turn degrades to a generic fallback response and the flow position is left unchanged. ` +
|
|
932
|
+
`Check the provider's structured-output/JSON support or inspect the routing prompt for schema violations.`
|
|
933
|
+
);
|
|
922
934
|
}
|
|
923
935
|
|
|
924
936
|
return {
|
|
@@ -1041,25 +1053,39 @@ export class FlowRouter<TContext = unknown, TData = unknown> {
|
|
|
1041
1053
|
}
|
|
1042
1054
|
|
|
1043
1055
|
// Apply sticky routing: if there's a current route, only switch if the
|
|
1044
|
-
// best alternative exceeds the current flow's score by the configured margin
|
|
1056
|
+
// best alternative exceeds the current flow's score by the configured margin.
|
|
1057
|
+
// The margin applies symmetrically: even when the current flow has no
|
|
1058
|
+
// weighted entry (the AI scored it 0 or omitted it), switching away
|
|
1059
|
+
// requires clearing the margin over a zero baseline — shared agent-level
|
|
1060
|
+
// data can otherwise zero out a mid-conversation flow.
|
|
1045
1061
|
if (currentRouteId) {
|
|
1046
1062
|
const currentEntry = weightedScores.find(e => e.route.id === currentRouteId);
|
|
1047
1063
|
const bestEntry = weightedScores[0];
|
|
1048
1064
|
|
|
1049
|
-
if (
|
|
1050
|
-
|
|
1065
|
+
if (bestEntry && bestEntry.route.id !== currentRouteId) {
|
|
1066
|
+
const currentScore = currentEntry ? currentEntry.score : 0;
|
|
1067
|
+
|
|
1068
|
+
if (bestEntry.score < currentScore + switchMargin) {
|
|
1069
|
+
const currentRoute =
|
|
1070
|
+
currentEntry?.route ?? routes.find(r => r.id === currentRouteId);
|
|
1071
|
+
if (currentRoute) {
|
|
1072
|
+
logger.debug(
|
|
1073
|
+
`[FlowRouter] Staying on current flow: ${currentRoute.title} ` +
|
|
1074
|
+
`(current: ${currentScore}, best alternative: ${bestEntry.score}, ` +
|
|
1075
|
+
`margin required: ${switchMargin})`
|
|
1076
|
+
);
|
|
1077
|
+
return currentRoute;
|
|
1078
|
+
}
|
|
1051
1079
|
logger.debug(
|
|
1052
|
-
`[FlowRouter]
|
|
1053
|
-
|
|
1054
|
-
|
|
1080
|
+
`[FlowRouter] Current flow ${currentRouteId} is no longer routable — selecting best alternative: ${bestEntry.route.title}`
|
|
1081
|
+
);
|
|
1082
|
+
} else {
|
|
1083
|
+
logger.debug(
|
|
1084
|
+
`[FlowRouter] Switching flow: ${currentEntry?.route.title ?? currentRouteId} → ${bestEntry.route.title} ` +
|
|
1085
|
+
`(current: ${currentScore}, alternative: ${bestEntry.score}, ` +
|
|
1086
|
+
`margin: ${switchMargin})`
|
|
1055
1087
|
);
|
|
1056
|
-
return currentEntry.route;
|
|
1057
1088
|
}
|
|
1058
|
-
logger.debug(
|
|
1059
|
-
`[FlowRouter] Switching flow: ${currentEntry.route.title} → ${bestEntry.route.title} ` +
|
|
1060
|
-
`(current: ${currentEntry.score}, alternative: ${bestEntry.score}, ` +
|
|
1061
|
-
`margin: ${switchMargin})`
|
|
1062
|
-
);
|
|
1063
1089
|
}
|
|
1064
1090
|
}
|
|
1065
1091
|
|
|
@@ -21,13 +21,10 @@ export class ResponseGenerationError extends Error {
|
|
|
21
21
|
message: string,
|
|
22
22
|
public readonly details?: ResponseGenerationErrorDetails
|
|
23
23
|
) {
|
|
24
|
-
|
|
24
|
+
// Native `cause` chain: `err.cause` is the original error, so
|
|
25
|
+
// consumers can reach ProviderError.code etc. without string matching.
|
|
26
|
+
super(message, { cause: details?.originalError });
|
|
25
27
|
this.name = 'ResponseGenerationError';
|
|
26
|
-
|
|
27
|
-
// Preserve stack trace from original error if available
|
|
28
|
-
if (details?.originalError instanceof Error && details.originalError.stack) {
|
|
29
|
-
this.stack = `${this.stack}\nCaused by: ${details.originalError.stack}`;
|
|
30
|
-
}
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
/**
|