@falai/agent 3.4.3 → 3.4.4

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.
Files changed (110) hide show
  1. package/dist/cjs/adapters/MemoryAdapter.d.ts +47 -0
  2. package/dist/cjs/adapters/MemoryAdapter.d.ts.map +1 -0
  3. package/dist/cjs/adapters/MemoryAdapter.js +208 -0
  4. package/dist/cjs/adapters/MemoryAdapter.js.map +1 -0
  5. package/dist/cjs/adapters/MongoAdapter.d.ts +97 -0
  6. package/dist/cjs/adapters/MongoAdapter.d.ts.map +1 -0
  7. package/dist/cjs/adapters/MongoAdapter.js +200 -0
  8. package/dist/cjs/adapters/MongoAdapter.js.map +1 -0
  9. package/dist/cjs/adapters/OpenSearchAdapter.d.ts +169 -0
  10. package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -0
  11. package/dist/cjs/adapters/OpenSearchAdapter.js +475 -0
  12. package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -0
  13. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +85 -0
  14. package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -0
  15. package/dist/cjs/adapters/PostgreSQLAdapter.js +312 -0
  16. package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -0
  17. package/dist/cjs/adapters/PrismaAdapter.d.ts +115 -0
  18. package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -0
  19. package/dist/cjs/adapters/PrismaAdapter.js +410 -0
  20. package/dist/cjs/adapters/PrismaAdapter.js.map +1 -0
  21. package/dist/cjs/adapters/RedisAdapter.d.ts +72 -0
  22. package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -0
  23. package/dist/cjs/adapters/RedisAdapter.js +290 -0
  24. package/dist/cjs/adapters/RedisAdapter.js.map +1 -0
  25. package/dist/cjs/adapters/SQLiteAdapter.d.ts +86 -0
  26. package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -0
  27. package/dist/cjs/adapters/SQLiteAdapter.js +341 -0
  28. package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -0
  29. package/dist/cjs/adapters/index.d.ts +17 -0
  30. package/dist/cjs/adapters/index.d.ts.map +1 -0
  31. package/dist/cjs/adapters/index.js +21 -0
  32. package/dist/cjs/adapters/index.js.map +1 -0
  33. package/dist/cjs/adapters/sessionRow.d.ts +22 -0
  34. package/dist/cjs/adapters/sessionRow.d.ts.map +1 -0
  35. package/dist/cjs/adapters/sessionRow.js +52 -0
  36. package/dist/cjs/adapters/sessionRow.js.map +1 -0
  37. package/dist/cjs/constants/index.d.ts +1 -0
  38. package/dist/cjs/constants/index.d.ts.map +1 -0
  39. package/dist/cjs/constants/index.js +4 -0
  40. package/dist/cjs/constants/index.js.map +1 -0
  41. package/dist/cjs/core/Agent.d.ts +382 -0
  42. package/dist/cjs/core/Agent.d.ts.map +1 -0
  43. package/dist/cjs/core/Agent.js +1198 -0
  44. package/dist/cjs/core/Agent.js.map +1 -0
  45. package/dist/cjs/core/ResponseModal.d.ts +305 -0
  46. package/dist/cjs/core/ResponseModal.d.ts.map +1 -0
  47. package/dist/cjs/core/ResponseModal.js +1414 -0
  48. package/dist/cjs/core/ResponseModal.js.map +1 -0
  49. package/dist/cjs/core/ToolLoopExecutor.d.ts +133 -0
  50. package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -0
  51. package/dist/cjs/core/ToolLoopExecutor.js +568 -0
  52. package/dist/cjs/core/ToolLoopExecutor.js.map +1 -0
  53. package/dist/cjs/core/createAgent.d.ts +35 -0
  54. package/dist/cjs/core/createAgent.d.ts.map +1 -0
  55. package/dist/cjs/core/createAgent.js +39 -0
  56. package/dist/cjs/core/createAgent.js.map +1 -0
  57. package/dist/cjs/index.d.ts +61 -0
  58. package/dist/cjs/index.d.ts.map +1 -0
  59. package/dist/cjs/index.js +113 -0
  60. package/dist/cjs/index.js.map +1 -0
  61. package/dist/cjs/package.json +1 -0
  62. package/dist/cjs/providers/AnthropicProvider.d.ts +46 -0
  63. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -0
  64. package/dist/cjs/providers/AnthropicProvider.js +50 -0
  65. package/dist/cjs/providers/AnthropicProvider.js.map +1 -0
  66. package/dist/cjs/providers/DeepSeekProvider.d.ts +47 -0
  67. package/dist/cjs/providers/DeepSeekProvider.d.ts.map +1 -0
  68. package/dist/cjs/providers/DeepSeekProvider.js +42 -0
  69. package/dist/cjs/providers/DeepSeekProvider.js.map +1 -0
  70. package/dist/cjs/providers/FallbackAiProvider.d.ts +26 -0
  71. package/dist/cjs/providers/FallbackAiProvider.d.ts.map +1 -0
  72. package/dist/cjs/providers/FallbackAiProvider.js +54 -0
  73. package/dist/cjs/providers/FallbackAiProvider.js.map +1 -0
  74. package/dist/cjs/providers/GeminiProvider.d.ts +51 -0
  75. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -0
  76. package/dist/cjs/providers/GeminiProvider.js +53 -0
  77. package/dist/cjs/providers/GeminiProvider.js.map +1 -0
  78. package/dist/cjs/providers/GenericOpenAICompatibleProvider.d.ts +80 -0
  79. package/dist/cjs/providers/GenericOpenAICompatibleProvider.d.ts.map +1 -0
  80. package/dist/cjs/providers/GenericOpenAICompatibleProvider.js +89 -0
  81. package/dist/cjs/providers/GenericOpenAICompatibleProvider.js.map +1 -0
  82. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +61 -0
  83. package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  84. package/dist/cjs/providers/OpenAICompatibleProvider.js +58 -0
  85. package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -0
  86. package/dist/cjs/providers/OpenAIProvider.d.ts +40 -0
  87. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -0
  88. package/dist/cjs/providers/OpenAIProvider.js +42 -0
  89. package/dist/cjs/providers/OpenAIProvider.js.map +1 -0
  90. package/dist/cjs/providers/OpenRouterProvider.d.ts +56 -0
  91. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -0
  92. package/dist/cjs/providers/OpenRouterProvider.js +45 -0
  93. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -0
  94. package/dist/cjs/providers/ProviderAdapter.d.ts +126 -0
  95. package/dist/cjs/providers/ProviderAdapter.d.ts.map +1 -0
  96. package/dist/cjs/providers/ProviderAdapter.js +313 -0
  97. package/dist/cjs/providers/ProviderAdapter.js.map +1 -0
  98. package/dist/cjs/providers/ZaiProvider.d.ts +45 -0
  99. package/dist/cjs/providers/ZaiProvider.d.ts.map +1 -0
  100. package/dist/cjs/providers/ZaiProvider.js +51 -0
  101. package/dist/cjs/providers/ZaiProvider.js.map +1 -0
  102. package/dist/cjs/providers/index.d.ts +26 -0
  103. package/dist/cjs/providers/index.d.ts.map +1 -0
  104. package/dist/cjs/providers/index.js +30 -0
  105. package/dist/cjs/providers/index.js.map +1 -0
  106. package/dist/cjs/utils/streamingMessage.d.ts +48 -0
  107. package/dist/cjs/utils/streamingMessage.d.ts.map +1 -0
  108. package/dist/cjs/utils/streamingMessage.js +210 -0
  109. package/dist/cjs/utils/streamingMessage.js.map +1 -0
  110. package/package.json +1 -1
@@ -0,0 +1,1198 @@
1
+ "use strict";
2
+ /**
3
+ * Core Agent implementation
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Agent = void 0;
7
+ const errors_js_1 = require("../types/errors.js");
8
+ const SignalProcessor_js_1 = require("./SignalProcessor.js");
9
+ const SignalEvaluator_js_1 = require("./SignalEvaluator.js");
10
+ const index_js_1 = require("../utils/index.js");
11
+ const Flow_js_1 = require("./Flow.js");
12
+ const Step_js_1 = require("./Step.js");
13
+ const PersistenceManager_js_1 = require("./PersistenceManager.js");
14
+ const SessionManager_js_1 = require("./SessionManager.js");
15
+ const FlowRouter_js_1 = require("./FlowRouter.js");
16
+ const PromptSectionCache_js_1 = require("./PromptSectionCache.js");
17
+ const ResponseModal_js_1 = require("./ResponseModal.js");
18
+ const ToolManager_js_1 = require("./ToolManager.js");
19
+ const CompactionEngine_js_1 = require("./CompactionEngine.js");
20
+ /**
21
+ * Error thrown when data validation fails
22
+ */
23
+ class DataValidationError extends Error {
24
+ constructor(errors, message) {
25
+ super(message || "Data validation failed");
26
+ this.errors = errors;
27
+ this.name = "DataValidationError";
28
+ }
29
+ }
30
+ /**
31
+ * Error thrown when flow configuration is invalid
32
+ */
33
+ class FlowConfigurationError extends Error {
34
+ constructor(flowTitle, invalidFields, message) {
35
+ super(message || `Flow configuration error in '${flowTitle}'`);
36
+ this.flowTitle = flowTitle;
37
+ this.invalidFields = invalidFields;
38
+ this.name = "FlowConfigurationError";
39
+ }
40
+ }
41
+ /**
42
+ * Main Agent class with generic context and data support
43
+ */
44
+ class Agent {
45
+ constructor(options) {
46
+ this.options = options;
47
+ this._terms = [];
48
+ this._instructions = [];
49
+ this._tools = [];
50
+ this._flows = [];
51
+ this._knowledgeBase = {};
52
+ /**
53
+ * Staging buffer for data set before any session exists (initialData and
54
+ * pre-session updateCollectedData calls). Consumed when a session is
55
+ * created; once a session exists, `session.data` is the single source of
56
+ * truth and this buffer stays empty.
57
+ */
58
+ this._pendingData = {};
59
+ this.maxAutoStepsPerTurn = options.maxAutoStepsPerTurn ?? 10;
60
+ this.maxDirectiveChain = options.maxDirectiveChain ?? 10;
61
+ // Validate routerMode reservation — only 'ai' is supported in v2.0
62
+ if (options.routerMode !== undefined && options.routerMode !== 'ai') {
63
+ throw new errors_js_1.NotImplementedError(`[NotImplementedError] routerMode "${String(options.routerMode)}" is not implemented: only "ai" is supported in v2.0. ` +
64
+ `Set routerMode to "ai" or omit the option.`);
65
+ }
66
+ // ─── Signal construction-time validation (Requirements 1.4, 1.5, 1.6, 1.9, 2.3) ───
67
+ const rawSignals = options.signals ?? [];
68
+ // Auto-generate stable ids for entries without `id`
69
+ for (let i = 0; i < rawSignals.length; i++) {
70
+ if (!rawSignals[i].id) {
71
+ rawSignals[i] = {
72
+ ...rawSignals[i],
73
+ id: (0, index_js_1.generateSignalId)(rawSignals[i].title, rawSignals[i].description, i),
74
+ };
75
+ }
76
+ }
77
+ // Validate unique ids (Requirement 1.4)
78
+ const idCounts = new Map();
79
+ for (const signal of rawSignals) {
80
+ const id = signal.id;
81
+ idCounts.set(id, (idCounts.get(id) ?? 0) + 1);
82
+ }
83
+ const duplicateIds = [...idCounts.entries()]
84
+ .filter(([, count]) => count > 1)
85
+ .map(([id]) => id);
86
+ if (duplicateIds.length > 0) {
87
+ throw new Step_js_1.FlowConfigurationError(`[FlowConfigurationError] Duplicate signal ids: ${duplicateIds.join(', ')}. ` +
88
+ `Each signal must have a unique id.`);
89
+ }
90
+ // Validate signalBatchSize (positive integer when set)
91
+ if (options.signalBatchSize !== undefined) {
92
+ if (!Number.isInteger(options.signalBatchSize) ||
93
+ options.signalBatchSize <= 0) {
94
+ throw new Step_js_1.FlowConfigurationError(`[FlowConfigurationError] signalBatchSize must be a positive integer, got: ${options.signalBatchSize}.`);
95
+ }
96
+ }
97
+ // Validate each signal's configuration
98
+ for (const signal of rawSignals) {
99
+ // Requirement 1.5: cooldown without cooldownMs → debug warning, treat as 'always'
100
+ if (signal.behavior === 'cooldown' && signal.cooldownMs == null) {
101
+ index_js_1.logger.debug(`[Agent] Signal "${signal.id}" has behavior 'cooldown' but no cooldownMs. Treating as 'always'.`);
102
+ signal.behavior = 'always';
103
+ }
104
+ // Requirement 1.9: validate extract schema is a JSON Schema object
105
+ if (signal.extract !== undefined) {
106
+ if (signal.extract === null ||
107
+ typeof signal.extract !== 'object' ||
108
+ Array.isArray(signal.extract)) {
109
+ throw new Step_js_1.FlowConfigurationError(`[FlowConfigurationError] Signal "${signal.id}" has an invalid extract schema. ` +
110
+ `Expected a JSON Schema object, got: ${typeof signal.extract}.`);
111
+ }
112
+ }
113
+ }
114
+ this._signals = rawSignals;
115
+ // Requirement 2.3: Only instantiate SignalProcessor when signals are present
116
+ if (rawSignals.length > 0) {
117
+ const evaluator = new SignalEvaluator_js_1.SignalEvaluator(options.provider);
118
+ this.signalProcessor = new SignalProcessor_js_1.SignalProcessor(rawSignals, options.provider, evaluator, { batchSize: options.signalBatchSize ?? 10 });
119
+ }
120
+ else {
121
+ this.signalProcessor = undefined;
122
+ }
123
+ // Set log level based on debug option. NOTE: loglevel's default logger is
124
+ // process-global — one agent enabling debug turns on DEBUG for every agent
125
+ // in the process. Warned so multi-tenant embedders aren't surprised.
126
+ if (options.debug) {
127
+ index_js_1.logger.warn(`[Agent] "${options.name}" enabled debug logging via the PROCESS-GLOBAL loglevel level. ` +
128
+ `Every agent in this process now logs at DEBUG. Scope logging in your host if needed.`);
129
+ index_js_1.logger.setLevel(index_js_1.LoggerLevel.DEBUG);
130
+ }
131
+ // Validate context configuration
132
+ if (options.context !== undefined && options.contextProvider) {
133
+ throw new Error("Cannot provide both 'context' and 'contextProvider'. Choose one.");
134
+ }
135
+ // Initialize and validate agent-level schema if provided
136
+ if (options.schema) {
137
+ this._schema = options.schema;
138
+ this.validateSchema(this._schema);
139
+ index_js_1.logger.debug("[Agent] Agent-level schema initialized and validated");
140
+ }
141
+ // Initialize context if provided
142
+ this._context = options.context;
143
+ // Initialize collected data with initial data if provided
144
+ if (options.initialData) {
145
+ if (this._schema) {
146
+ const validation = this.validateData(options.initialData);
147
+ if (!validation.valid) {
148
+ throw new Error(`Initial data validation failed: ${validation.errors.map(e => e.message).join(', ')}`);
149
+ }
150
+ }
151
+ this._pendingData = { ...options.initialData };
152
+ index_js_1.logger.debug("[Agent] Initial data set:", this._pendingData);
153
+ }
154
+ // Initialize prompt section cache
155
+ this._promptSectionCache = new PromptSectionCache_js_1.PromptSectionCache(options.promptCache);
156
+ // Initialize flow router
157
+ this._routingEngine = new FlowRouter_js_1.FlowRouter({
158
+ flowSwitchMargin: options.flowSwitchMargin,
159
+ onFlowSwitch: () => this.invalidateFlowSections(),
160
+ promptSectionCache: this._promptSectionCache,
161
+ });
162
+ // Initialize tool manager BEFORE ResponseModal (it reads agent.tool in constructor)
163
+ this.tool = new ToolManager_js_1.ToolManager(this);
164
+ // Initialize ResponseModal for handling all response generation
165
+ this._responseModal = new ResponseModal_js_1.ResponseModal(this, {
166
+ maxToolLoops: options.maxToolLoops,
167
+ });
168
+ // Initialize persistence if configured
169
+ if (options.persistence) {
170
+ try {
171
+ // Validate persistence configuration
172
+ if (!options.persistence.adapter) {
173
+ throw new Error("Persistence adapter is required when persistence is configured");
174
+ }
175
+ if (!options.persistence.adapter.sessionRepository) {
176
+ throw new Error("Persistence adapter must provide a sessionRepository");
177
+ }
178
+ if (!options.persistence.adapter.messageRepository) {
179
+ throw new Error("Persistence adapter must provide a messageRepository");
180
+ }
181
+ this._persistenceManager = new PersistenceManager_js_1.PersistenceManager(options.persistence);
182
+ // Initialize the adapter if it has an initialize method
183
+ if (options.persistence.adapter.initialize) {
184
+ options.persistence.adapter.initialize().catch((error) => {
185
+ index_js_1.logger.error("[Agent] Persistence adapter initialization failed:", error instanceof Error ? error.message : String(error));
186
+ });
187
+ }
188
+ }
189
+ catch (error) {
190
+ const errorMessage = error instanceof Error ? error.message : String(error);
191
+ index_js_1.logger.error("[Agent] Failed to initialize persistence:", errorMessage);
192
+ throw new Error(`Failed to initialize persistence: ${errorMessage}`);
193
+ }
194
+ }
195
+ // Initialize from options - use create methods for consistency
196
+ if (options.terms) {
197
+ options.terms.forEach((term) => {
198
+ this.createTerm(term);
199
+ });
200
+ }
201
+ // Initialize instructions (new unified form)
202
+ if (options.instructions) {
203
+ options.instructions.forEach((instruction) => {
204
+ this.createInstruction(instruction);
205
+ });
206
+ }
207
+ if (options.tools) {
208
+ options.tools.forEach((tool) => {
209
+ this.addTool(tool);
210
+ });
211
+ }
212
+ if (options.flows) {
213
+ options.flows.forEach((flowOptions) => {
214
+ this.createFlow(flowOptions);
215
+ });
216
+ }
217
+ // Validate deferred branch `then` string references against the flow registry.
218
+ // This catches strings that don't match a local step id AND don't match any flow id/title.
219
+ this.validateBranchReferences();
220
+ // Initialize knowledge base
221
+ if (options.knowledgeBase) {
222
+ this._knowledgeBase = { ...options.knowledgeBase };
223
+ }
224
+ // Initialize compaction options if configured
225
+ if (options.compaction && options.compaction.enabled !== false) {
226
+ const compactionOptions = {
227
+ maxTokens: options.compaction.maxTokens,
228
+ compactionThreshold: options.compaction.compactionThreshold ?? 0.8,
229
+ preserveRecentCount: options.compaction.preserveRecentCount ?? 4,
230
+ maxToolResultChars: options.compaction.maxToolResultChars ?? 5000,
231
+ provider: options.provider,
232
+ };
233
+ CompactionEngine_js_1.CompactionEngine.validateOptions(compactionOptions);
234
+ this._compactionOptions = compactionOptions;
235
+ index_js_1.logger.debug("[Agent] Compaction options initialized and validated");
236
+ }
237
+ // Initialize session manager — the single owner of the live session
238
+ this.session = new SessionManager_js_1.SessionManager(this._persistenceManager, this);
239
+ // Adopt an explicitly provided session
240
+ if (options.session) {
241
+ this.session.syncSession(options.session);
242
+ }
243
+ // Store sessionId for later use in getOrCreate calls
244
+ if (options.sessionId) {
245
+ this.session.setDefaultSessionId(options.sessionId);
246
+ // The session will be loaded on first getOrCreate call; session.data
247
+ // is the source of truth, so no data sync is needed here
248
+ this.session.getOrCreate(options.sessionId).catch((err) => {
249
+ index_js_1.logger.error("Failed to start session", err);
250
+ });
251
+ }
252
+ }
253
+ /**
254
+ * Drain the pre-session data staging buffer.
255
+ * @internal Called by SessionManager when a session is created or loaded.
256
+ */
257
+ consumePendingData() {
258
+ const pending = this._pendingData;
259
+ this._pendingData = {};
260
+ return pending;
261
+ }
262
+ /**
263
+ * Validate the agent-level schema structure
264
+ * @private
265
+ */
266
+ validateSchema(schema) {
267
+ if (!schema || typeof schema !== 'object') {
268
+ throw new Error("Agent schema must be a valid JSON Schema object. " +
269
+ "Provide a schema with 'type': 'object' and 'properties' to define the data structure.");
270
+ }
271
+ if (schema.type !== 'object') {
272
+ throw new Error(`Agent schema must be of type 'object', but received '${String(schema.type)}'. ` +
273
+ "Agent-level schemas must define object structures for data collection.");
274
+ }
275
+ if (!schema.properties || typeof schema.properties !== 'object') {
276
+ throw new Error("Agent schema must have a 'properties' field defining the data fields. " +
277
+ "Example: { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string' } } }");
278
+ }
279
+ index_js_1.logger.debug("[Agent] Schema validation passed");
280
+ }
281
+ /**
282
+ * Walk every flow's steps and resolve deferred string `then` values in branches
283
+ * against the agent's flow registry. Strings that match neither a local step id
284
+ * nor any flow id/title throw FlowConfigurationError.
285
+ * @private
286
+ */
287
+ validateBranchReferences() {
288
+ for (const flow of this._flows) {
289
+ this.validateFlowBranchReferences(flow);
290
+ }
291
+ }
292
+ /**
293
+ * Validate branch `then` string references for a single flow against the agent's
294
+ * flow registry. Throws FlowConfigurationError for unresolved references.
295
+ * @private
296
+ */
297
+ validateFlowBranchReferences(flow) {
298
+ const steps = flow.getAllSteps();
299
+ const localStepIds = new Set(steps.map(s => s.id));
300
+ for (const step of steps) {
301
+ if (!step.branches)
302
+ continue;
303
+ for (const entry of step.branches) {
304
+ if (typeof entry.then !== 'string')
305
+ continue;
306
+ // Already matches a local step id — no deferred resolution needed
307
+ if (localStepIds.has(entry.then))
308
+ continue;
309
+ // Check against the agent's flow registry (id or title)
310
+ const matchesFlow = this._flows.some(f => f.id === entry.then || f.title === entry.then);
311
+ if (!matchesFlow) {
312
+ throw new Step_js_1.FlowConfigurationError(`[FlowConfigurationError] Unresolved branch target: "${entry.then}" in ${flow.id}.${step.id} does not match any step in the flow or any flow in the agent. ` +
313
+ `Fix the branch "then" value to reference a valid step id or flow id/title.`);
314
+ }
315
+ }
316
+ }
317
+ }
318
+ /**
319
+ * Validate that every step's `collect` fields in a flow reference valid keys
320
+ * from the agent-level schema. Throws FlowConfigurationError at construction
321
+ * time if any collect field is not a valid schema key.
322
+ *
323
+ * This enforces Requirement 14.5: generic inference is preserved AND every
324
+ * `collect` field reference is a valid key of the inferred TData.
325
+ * @private
326
+ */
327
+ validateFlowCollectFields(flow) {
328
+ const schemaKeys = Object.keys(this._schema.properties);
329
+ const schemaKeySet = new Set(schemaKeys);
330
+ const steps = flow.getAllSteps();
331
+ for (const step of steps) {
332
+ if (!step.collect || step.collect.length === 0)
333
+ continue;
334
+ const invalidFields = step.collect.filter(field => !schemaKeySet.has(String(field)));
335
+ if (invalidFields.length > 0) {
336
+ throw new Step_js_1.FlowConfigurationError(`[FlowConfigurationError] Step "${step.id}" in flow "${flow.title}" references invalid collect fields: ${invalidFields.map(f => String(f)).join(', ')}. ` +
337
+ `Must be valid keys from agent schema. Available fields: ${schemaKeys.join(', ')}.`);
338
+ }
339
+ }
340
+ }
341
+ /**
342
+ * Validate data against the agent-level schema.
343
+ *
344
+ * A field the schema does not declare is a warning, not an error: a model
345
+ * can extract a key nobody asked for, and `updateCollectedData` drops it
346
+ * instead of failing the turn. `valid` is false only when `errors` is not empty.
347
+ */
348
+ validateData(data) {
349
+ if (!this._schema) {
350
+ // No schema defined, consider all data valid
351
+ return { valid: true, errors: [], warnings: [] };
352
+ }
353
+ const errors = [];
354
+ const warnings = [];
355
+ // Undeclared fields are warnings — updateCollectedData drops them
356
+ if (this._schema.properties) {
357
+ for (const [key, value] of Object.entries(data)) {
358
+ if (!(key in this._schema.properties)) {
359
+ warnings.push({
360
+ field: key,
361
+ value,
362
+ message: `Field '${key}' is not defined in agent schema`,
363
+ schemaPath: `properties.${key}`
364
+ });
365
+ }
366
+ }
367
+ }
368
+ // Check required fields if specified
369
+ if (this._schema.required && Array.isArray(this._schema.required)) {
370
+ for (const requiredField of this._schema.required) {
371
+ if (!(requiredField in data) || data[requiredField] === undefined) {
372
+ warnings.push({
373
+ field: requiredField,
374
+ value: undefined,
375
+ message: `Required field '${requiredField}' is missing`,
376
+ schemaPath: `required`
377
+ });
378
+ }
379
+ }
380
+ }
381
+ return {
382
+ valid: errors.length === 0,
383
+ errors,
384
+ warnings
385
+ };
386
+ }
387
+ /**
388
+ * Check if a field is valid according to the agent schema
389
+ * @param field - The field key to validate
390
+ * @returns true if field exists in schema or no schema is defined, false otherwise
391
+ */
392
+ isValidSchemaField(field) {
393
+ if (!this._schema || !this._schema.properties) {
394
+ // No schema defined, consider all fields valid
395
+ return true;
396
+ }
397
+ return field in this._schema.properties;
398
+ }
399
+ /**
400
+ * Get the current collected data.
401
+ * Reads from the live session when one exists; otherwise from the
402
+ * pre-session staging buffer.
403
+ */
404
+ getCollectedData() {
405
+ const session = this.session.current;
406
+ return session ? { ...session.data } : { ...this._pendingData };
407
+ }
408
+ /**
409
+ * Update collected data with validation.
410
+ * Writes to the live session when one exists; otherwise stages the data
411
+ * for the session that will be created. Fields the agent schema does not
412
+ * declare are dropped with a warning, never stored.
413
+ */
414
+ async updateCollectedData(updates) {
415
+ const declaredUpdates = (0, index_js_1.dropUndeclaredFields)(updates, this._schema);
416
+ // Validate the updates
417
+ const validation = this.validateData(declaredUpdates);
418
+ if (!validation.valid) {
419
+ const errorMessages = validation.errors.map(e => e.message).join(', ');
420
+ throw new DataValidationError(validation.errors, `[DataValidationError] Data validation failed: fields [${errorMessages}] did not pass schema validation. Fix the offending values to match the declared schema.`);
421
+ }
422
+ // Log warnings if any
423
+ if (validation.warnings.length > 0) {
424
+ const warningMessages = validation.warnings.map(w => w.message).join(', ');
425
+ index_js_1.logger.warn(`[Agent] Data validation warnings: ${warningMessages}`);
426
+ }
427
+ const session = this.session.current;
428
+ const previousData = session ? { ...session.data } : { ...this._pendingData };
429
+ let newData = {
430
+ ...previousData,
431
+ ...declaredUpdates
432
+ };
433
+ // Trigger agent-level lifecycle hook if configured
434
+ if (this.options.hooks?.onDataUpdate) {
435
+ newData = await this.options.hooks.onDataUpdate(newData, previousData);
436
+ }
437
+ if (session) {
438
+ session.data = newData;
439
+ session.metadata.lastUpdatedAt = new Date();
440
+ }
441
+ else {
442
+ this._pendingData = newData;
443
+ }
444
+ index_js_1.logger.debug("[Agent] Collected data updated:", declaredUpdates);
445
+ }
446
+ // ---------------------------------------------------------------------------
447
+ // Property accessors (get / set)
448
+ // ---------------------------------------------------------------------------
449
+ /**
450
+ * Get agent name
451
+ */
452
+ get name() {
453
+ return this.options.name;
454
+ }
455
+ /**
456
+ * Set agent name
457
+ */
458
+ set name(value) {
459
+ this.options.name = value;
460
+ }
461
+ /**
462
+ * Get agent persona
463
+ */
464
+ get persona() {
465
+ return this.options.persona;
466
+ }
467
+ /**
468
+ * Set agent persona
469
+ */
470
+ set persona(value) {
471
+ this.options.persona = value;
472
+ }
473
+ /**
474
+ * Get agent goal
475
+ */
476
+ get goal() {
477
+ return this.options.goal;
478
+ }
479
+ /**
480
+ * Set agent goal
481
+ */
482
+ set goal(value) {
483
+ this.options.goal = value;
484
+ }
485
+ /**
486
+ * Get whether debug mode is enabled
487
+ */
488
+ get debug() {
489
+ return this.options.debug ?? false;
490
+ }
491
+ /**
492
+ * Set debug mode (also updates logger level)
493
+ */
494
+ set debug(value) {
495
+ this.options.debug = value;
496
+ index_js_1.logger.setLevel(value ? index_js_1.LoggerLevel.DEBUG : index_js_1.LoggerLevel.INFO);
497
+ }
498
+ /**
499
+ * Get the AI provider
500
+ */
501
+ get provider() {
502
+ return this.options.provider;
503
+ }
504
+ /**
505
+ * Set the AI provider
506
+ */
507
+ set provider(value) {
508
+ this.options.provider = value;
509
+ }
510
+ /**
511
+ * Get the flow switch margin
512
+ * @default 15
513
+ */
514
+ get flowSwitchMargin() {
515
+ return this.options.flowSwitchMargin ?? 15;
516
+ }
517
+ /**
518
+ * Set the flow switch margin
519
+ */
520
+ set flowSwitchMargin(value) {
521
+ this.options.flowSwitchMargin = value;
522
+ }
523
+ /**
524
+ * Get the prompt section cache instance
525
+ */
526
+ get promptSectionCache() {
527
+ return this._promptSectionCache;
528
+ }
529
+ /**
530
+ * Get all terms
531
+ */
532
+ get terms() {
533
+ return [...this._terms];
534
+ }
535
+ /**
536
+ * Get all instructions
537
+ */
538
+ get instructions() {
539
+ return [...this._instructions];
540
+ }
541
+ /**
542
+ * Get all tools
543
+ */
544
+ get tools() {
545
+ return [...this._tools];
546
+ }
547
+ /**
548
+ * Get all flows
549
+ */
550
+ get flows() {
551
+ return [...this._flows];
552
+ }
553
+ /**
554
+ * Get current schema
555
+ */
556
+ get schema() {
557
+ return this._schema;
558
+ }
559
+ /**
560
+ * Set schema (validates structure)
561
+ */
562
+ set schema(value) {
563
+ if (value) {
564
+ this.validateSchema(value);
565
+ }
566
+ this._schema = value;
567
+ }
568
+ /**
569
+ * Get the configured signals.
570
+ */
571
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
572
+ get signals() {
573
+ return this._signals;
574
+ }
575
+ /**
576
+ * Get the agent's knowledge base
577
+ */
578
+ get knowledgeBase() {
579
+ return { ...this._knowledgeBase };
580
+ }
581
+ /**
582
+ * Set the agent's knowledge base
583
+ */
584
+ set knowledgeBase(value) {
585
+ this._knowledgeBase = { ...value };
586
+ }
587
+ /**
588
+ * Get the current session (if set). Delegates to the SessionManager —
589
+ * the single owner of the live session.
590
+ */
591
+ get currentSession() {
592
+ return this.session.current;
593
+ }
594
+ /**
595
+ * Set the current session for convenience methods
596
+ * Set to undefined to clear the current session
597
+ */
598
+ set currentSession(value) {
599
+ this.session.syncSession(value);
600
+ this._promptSectionCache.invalidateAll();
601
+ }
602
+ /**
603
+ * Get all flows
604
+ */
605
+ getFlows() {
606
+ return this.flows;
607
+ }
608
+ /**
609
+ * Get all terms
610
+ */
611
+ getTerms() {
612
+ return this.terms;
613
+ }
614
+ /**
615
+ * Get all tools
616
+ */
617
+ getTools() {
618
+ return this.tools;
619
+ }
620
+ /**
621
+ * Get all instructions
622
+ */
623
+ getInstructions() {
624
+ return this.instructions;
625
+ }
626
+ /**
627
+ * Invalidate flow-dependent prompt cache sections.
628
+ * Called automatically when the active flow changes.
629
+ */
630
+ invalidateFlowSections() {
631
+ this._promptSectionCache.invalidate('activeFlows');
632
+ this._promptSectionCache.invalidate('flowKnowledgeBase');
633
+ this._promptSectionCache.invalidate('instructionsFlow');
634
+ }
635
+ /**
636
+ * Get the persistence manager (if configured)
637
+ */
638
+ getPersistenceManager() {
639
+ return this._persistenceManager;
640
+ }
641
+ /**
642
+ * Check if persistence is enabled
643
+ */
644
+ hasPersistence() {
645
+ return this._persistenceManager !== undefined;
646
+ }
647
+ /**
648
+ * Get the resolved compaction options (if compaction is configured)
649
+ */
650
+ getCompactionOptions() {
651
+ return this._compactionOptions;
652
+ }
653
+ // ---------------------------------------------------------------------------
654
+ // Core methods
655
+ // ---------------------------------------------------------------------------
656
+ /**
657
+ * Create a new flow (journey) using agent-level data type
658
+ */
659
+ createFlow(options) {
660
+ // Validate that requiredFields exist in agent schema
661
+ if (options.requiredFields && this._schema?.properties) {
662
+ const invalidRequiredFields = options.requiredFields.filter(field => !(String(field) in this._schema.properties));
663
+ if (invalidRequiredFields.length > 0) {
664
+ throw new FlowConfigurationError(options.title, invalidRequiredFields.map(f => String(f)), `[FlowConfigurationError] Invalid required fields in flow "${options.title}": [${invalidRequiredFields.join(', ')}] are not declared in the agent schema. ` +
665
+ `Use valid schema keys. Available fields: ${Object.keys(this._schema.properties).join(', ')}.`);
666
+ }
667
+ }
668
+ // Validate that optionalFields exist in agent schema
669
+ if (options.optionalFields && this._schema?.properties) {
670
+ const invalidOptionalFields = options.optionalFields.filter(field => !(String(field) in this._schema.properties));
671
+ if (invalidOptionalFields.length > 0) {
672
+ throw new FlowConfigurationError(options.title, invalidOptionalFields.map(f => String(f)), `[FlowConfigurationError] Invalid optional fields in flow "${options.title}": [${invalidOptionalFields.join(', ')}] are not declared in the agent schema. ` +
673
+ `Use valid schema keys. Available fields: ${Object.keys(this._schema.properties).join(', ')}.`);
674
+ }
675
+ }
676
+ // Overlap detection: warn (don't throw) when the incoming flow's
677
+ // requiredFields intersect another registered flow's — the schema is
678
+ // agent-level, so both flows complete together and one is silently
679
+ // excluded from routing.
680
+ if (options.requiredFields && options.requiredFields.length > 0) {
681
+ const incoming = new Set(options.requiredFields.map(String));
682
+ for (const existing of this._flows) {
683
+ const shared = (existing.requiredFields ?? [])
684
+ .map(String)
685
+ .filter((f) => incoming.has(f));
686
+ if (shared.length > 0) {
687
+ index_js_1.logger.warn(`[FlowConfigurationError] Overlapping requiredFields: flows "${existing.title}" and "${options.title}" share [${shared.join(', ')}]. ` +
688
+ `The schema is agent-level, so data collected for one flow marks the other complete and excludes it from routing. ` +
689
+ `Give each flow distinct requiredFields, or set \`reentrant: true\` on flows that legitimately share fields.`);
690
+ }
691
+ }
692
+ }
693
+ const flow = new Flow_js_1.Flow(options, this);
694
+ // Validate that step collect fields reference valid schema keys
695
+ if (this._schema?.properties) {
696
+ this.validateFlowCollectFields(flow);
697
+ }
698
+ this._flows.push(flow);
699
+ return flow;
700
+ }
701
+ /**
702
+ * Create a domain term for the glossary
703
+ */
704
+ createTerm(term) {
705
+ this._terms.push(term);
706
+ return this;
707
+ }
708
+ /**
709
+ * Create an instruction (unified behavioral primitive).
710
+ */
711
+ createInstruction(instruction) {
712
+ const instructionWithId = {
713
+ ...instruction,
714
+ kind: instruction.kind || 'should',
715
+ id: instruction.id || `instruction_${this._instructions.length}`,
716
+ enabled: instruction.enabled !== false, // Default to true
717
+ };
718
+ this._instructions.push(instructionWithId);
719
+ this._promptSectionCache.invalidate('instructionsGlobal');
720
+ return this;
721
+ }
722
+ /**
723
+ * Add a tool to the agent using the unified Tool interface
724
+ * Creates and adds the tool to agent scope in one operation
725
+ */
726
+ addTool(tool) {
727
+ // Validate tool before adding
728
+ if (!tool || !tool.id || !tool.handler) {
729
+ throw new Error('Invalid tool: must have id and handler properties');
730
+ }
731
+ // Add directly to agent's tools array, preserving the TResult type
732
+ this._tools.push(tool);
733
+ index_js_1.logger.debug(`[Agent] Added tool to agent scope: ${tool.id}`);
734
+ return this;
735
+ }
736
+ /**
737
+ * Register multiple tools at the agent level
738
+ */
739
+ registerTools(tools) {
740
+ tools.forEach((tool) => {
741
+ // Validate each tool before adding
742
+ if (!tool || !tool.id || !tool.handler) {
743
+ throw new Error(`Invalid tool in batch: must have id and handler properties (tool: ${tool?.id || 'unknown'})`);
744
+ }
745
+ this._tools.push(tool);
746
+ });
747
+ index_js_1.logger.debug(`[Agent] Registered ${tools.length} tools`);
748
+ return this;
749
+ }
750
+ /**
751
+ * Update the agent's context
752
+ * Triggers both agent-level and flow-specific onContextUpdate lifecycle hooks if configured
753
+ */
754
+ async updateContext(updates) {
755
+ const previousContext = this._context;
756
+ // Merge updates with current context
757
+ this._context = {
758
+ ...this._context,
759
+ ...updates,
760
+ };
761
+ // Trigger flow-specific lifecycle hook if configured and session has current flow
762
+ const activeSession = this.session.current;
763
+ if (activeSession?.currentFlow) {
764
+ const currentFlow = this._flows.find((r) => r.id === activeSession.currentFlow?.id);
765
+ if (currentFlow?.hooks?.onContextUpdate &&
766
+ previousContext !== undefined) {
767
+ await currentFlow.handleContextUpdate(this._context, previousContext);
768
+ }
769
+ }
770
+ // Trigger agent-level lifecycle hook if configured
771
+ if (this.options.hooks?.onContextUpdate && previousContext !== undefined) {
772
+ await this.options.hooks.onContextUpdate(this._context, previousContext);
773
+ }
774
+ // Invalidate context-dependent prompt cache sections
775
+ this._promptSectionCache.invalidate('agentMeta');
776
+ this._promptSectionCache.invalidate('knowledgeBase');
777
+ this._promptSectionCache.invalidate('instructionsGlobal');
778
+ }
779
+ /**
780
+ * Update collected data in session with lifecycle hook support
781
+ * Triggers both agent-level and flow-specific onDataUpdate lifecycle hooks if configured
782
+ * @internal
783
+ */
784
+ async updateData(session, dataUpdate) {
785
+ const previousCollected = { ...session.data };
786
+ // Merge new collected data
787
+ let newCollected = {
788
+ ...session.data,
789
+ ...dataUpdate,
790
+ };
791
+ // Trigger flow-specific lifecycle hook if configured and session has a current flow
792
+ if (session.currentFlow) {
793
+ const currentFlow = this._flows.find((r) => r.id === session.currentFlow?.id);
794
+ if (currentFlow?.hooks?.onDataUpdate) {
795
+ newCollected = await currentFlow.handleDataUpdate(newCollected, previousCollected);
796
+ }
797
+ }
798
+ // Trigger agent-level lifecycle hook if configured
799
+ if (this.options.hooks?.onDataUpdate) {
800
+ newCollected = (await this.options.hooks.onDataUpdate(newCollected, previousCollected));
801
+ }
802
+ // Return updated session — session.data is the single source of truth,
803
+ // so no agent-side copy is kept
804
+ return (0, index_js_1.mergeCollected)(session, newCollected);
805
+ }
806
+ /**
807
+ * Get current context (fetches from provider if configured)
808
+ */
809
+ async getContext() {
810
+ // If context provider is configured, use it to fetch fresh context
811
+ if (this.options.contextProvider) {
812
+ return await this.options.contextProvider();
813
+ }
814
+ // Otherwise return the stored context
815
+ return this._context;
816
+ }
817
+ /**
818
+ * Generate a response based on history and context as a stream
819
+ */
820
+ async *respondStream(params) {
821
+ // Delegate to ResponseModal
822
+ yield* this._responseModal.respondStream(params);
823
+ }
824
+ /**
825
+ * Generate a response based on history and context
826
+ */
827
+ async respond(params) {
828
+ // Delegate to ResponseModal
829
+ return this._responseModal.respond(params);
830
+ }
831
+ /**
832
+ * Get agent options
833
+ * @internal Used by ResponseModal
834
+ */
835
+ getAgentOptions() {
836
+ return this.options;
837
+ }
838
+ /**
839
+ * Get flow router
840
+ * @internal Used by ResponseModal
841
+ */
842
+ getFlowRouter() {
843
+ return this._routingEngine;
844
+ }
845
+ /**
846
+ * Get the updateData method bound to this agent
847
+ * @internal Used by ResponseModal
848
+ */
849
+ getUpdateDataMethod() {
850
+ return this.updateData.bind(this);
851
+ }
852
+ /**
853
+ * Execute a prepare or finalize function/tool
854
+ * @internal Used by ResponseModal
855
+ */
856
+ async executePrepareFinalize(prepareOrFinalize, context, data, flow, step) {
857
+ if (!prepareOrFinalize)
858
+ return;
859
+ if (typeof prepareOrFinalize === "function") {
860
+ // It's a function - call it directly
861
+ await prepareOrFinalize(context, data);
862
+ }
863
+ else {
864
+ // It's a tool reference - find and execute the tool
865
+ let tool;
866
+ if (typeof prepareOrFinalize === "string") {
867
+ // Tool ID - use ToolManager to find it across all scopes
868
+ tool = this.tool.find(prepareOrFinalize, undefined, step, flow);
869
+ }
870
+ else {
871
+ // Tool object - validate it has required properties
872
+ if (prepareOrFinalize.id && typeof prepareOrFinalize.handler === 'function') {
873
+ tool = prepareOrFinalize;
874
+ }
875
+ else {
876
+ index_js_1.logger.error(`[Agent] Invalid tool object for prepare/finalize: missing id or invalid handler`);
877
+ return;
878
+ }
879
+ }
880
+ if (tool) {
881
+ // Use ToolManager for execution
882
+ const result = await this.tool.executeTool({
883
+ tool,
884
+ context,
885
+ updateContext: this.updateContext.bind(this),
886
+ updateData: this.updateCollectedData.bind(this),
887
+ history: [], // Empty history for prepare/finalize
888
+ data,
889
+ });
890
+ if (!result.success) {
891
+ index_js_1.logger.error(`[Agent] Tool execution failed in prepare/finalize: ${result.error}`);
892
+ throw new Error(`Tool execution failed: ${result.error}`);
893
+ }
894
+ }
895
+ else {
896
+ index_js_1.logger.warn(`[Agent] Tool not found for prepare/finalize: ${typeof prepareOrFinalize === "string"
897
+ ? prepareOrFinalize
898
+ : "inline tool"}`);
899
+ }
900
+ }
901
+ }
902
+ /**
903
+ * Get collected data from the current session (or the pre-session staging
904
+ * buffer when no session exists yet). Alias of getCollectedData().
905
+ */
906
+ getData() {
907
+ return this.getCollectedData();
908
+ }
909
+ /**
910
+ * Dispatch a directive (or a flow shorthand) into a session.
911
+ * Sets `pendingDirective` on the session without triggering a `respond()` call.
912
+ * The directive will be applied at the start of the next turn.
913
+ *
914
+ * String form desugars to `{ goTo: target }`.
915
+ *
916
+ * Durability: with a persistence adapter and autoSave configured (the
917
+ * defaults), the queued directive is persisted immediately — safe for
918
+ * out-of-process callers like webhooks or cron. Without an adapter it is
919
+ * memory-only, as is `persistence.autoSave: false` (then persisting before
920
+ * the next turn is the caller's job).
921
+ *
922
+ * @param target - Flow ID/title string (desugars to `{ goTo: target }`) or a full Directive
923
+ * @param session - Session to update (uses current session if not provided)
924
+ * @returns Updated session with `pendingDirective` set
925
+ *
926
+ * @throws FlowConfigurationError if the string target doesn't match any flow
927
+ * @throws FlowConfigurationError if the directive fails validation
928
+ * @throws SessionConflictError when persistence is enabled and another writer
929
+ * moved the stored session since this copy was loaded
930
+ *
931
+ * @example
932
+ * // String shorthand — desugars to { goTo: 'Feedback' }
933
+ * const updated = await agent.dispatch('Feedback', session);
934
+ *
935
+ * @example
936
+ * // Full directive
937
+ * const updated = await agent.dispatch({ goTo: 'Billing', reply: 'Transferring you now.' }, session);
938
+ */
939
+ async dispatch(target, session) {
940
+ const targetSession = session || this.session.current;
941
+ if (!targetSession) {
942
+ throw new Error("No session provided and no current session available. Please provide a session to dispatch into.");
943
+ }
944
+ // Desugar string form to { goTo: target }
945
+ const directive = typeof target === 'string'
946
+ ? { goTo: target }
947
+ : target;
948
+ // Validate the directive: check for multiple position fields, empty goTo, etc.
949
+ this.validateDirective(directive);
950
+ // If goTo is a string, validate it references a known flow
951
+ if (typeof directive.goTo === 'string') {
952
+ const flowTarget = directive.goTo;
953
+ const matchesFlow = this._flows.some(f => f.id === flowTarget || f.title === flowTarget);
954
+ if (!matchesFlow) {
955
+ throw new Step_js_1.FlowConfigurationError(`[FlowConfigurationError] Unknown flow: "${flowTarget}" does not match any flow id or title. ` +
956
+ `Available flows: ${this._flows.map(f => f.title).join(', ')}.`);
957
+ }
958
+ }
959
+ else if (directive.goTo && typeof directive.goTo === 'object' && directive.goTo.flow) {
960
+ const flowTarget = directive.goTo.flow;
961
+ const matchesFlow = this._flows.some(f => f.id === flowTarget || f.title === flowTarget);
962
+ if (!matchesFlow) {
963
+ throw new Step_js_1.FlowConfigurationError(`[FlowConfigurationError] Unknown flow: "${flowTarget}" does not match any flow id or title. ` +
964
+ `Available flows: ${this._flows.map(f => f.title).join(', ')}.`);
965
+ }
966
+ }
967
+ // Strip pre-LLM-only fields before storing
968
+ const stripped = this.stripPreDirectiveFields(directive);
969
+ // Set pendingDirective on the session without applying it
970
+ const updatedSession = {
971
+ ...targetSession,
972
+ pendingDirective: stripped,
973
+ metadata: {
974
+ ...targetSession.metadata,
975
+ lastUpdatedAt: new Date(),
976
+ },
977
+ };
978
+ // Durability: with an adapter + autoSave configured, dispatch persists
979
+ // immediately — webhooks/cron run out-of-process from the responder, and a
980
+ // memory-only queue would evaporate with this process. The save stamps the
981
+ // new version back onto updatedSession, so the next turn's auto-save CAS
982
+ // stays clean. Persisting BEFORE the in-memory sync keeps the existing
983
+ // invariant: a throwing dispatch leaves the session untouched.
984
+ if (this._persistenceManager && this.options.persistence?.autoSave !== false) {
985
+ await this._persistenceManager.saveSessionState(updatedSession.id, updatedSession);
986
+ index_js_1.logger.debug(`[Agent] Dispatched directive persisted to adapter for session ${updatedSession.id}`);
987
+ }
988
+ // Update current session in place if no explicit session was passed
989
+ if (!session && this.session.current) {
990
+ this.session.syncSession(updatedSession);
991
+ }
992
+ index_js_1.logger.debug(`[Agent] Dispatched directive: pendingDirective set on session ${updatedSession.id}`);
993
+ return updatedSession;
994
+ }
995
+ /**
996
+ * Apply a directive synchronously to a session without invoking `respond()`.
997
+ * Performs in-place application: updates flow/step position, merges state writes.
998
+ *
999
+ * This is the synchronous counterpart to `dispatch` — it applies immediately
1000
+ * rather than deferring to the next turn.
1001
+ *
1002
+ * @param directive - The directive to apply
1003
+ * @param session - The session to apply the directive to
1004
+ * @returns The updated session with the directive applied
1005
+ */
1006
+ applyDirective(directive, session) {
1007
+ // Validate the directive
1008
+ this.validateDirective(directive);
1009
+ let updatedSession = { ...session };
1010
+ const now = new Date();
1011
+ // Apply state writes
1012
+ if (directive.contextUpdate) {
1013
+ // Context updates are applied to the agent, not the session
1014
+ this._context = {
1015
+ ...this._context,
1016
+ ...directive.contextUpdate,
1017
+ };
1018
+ }
1019
+ if (directive.dataUpdate) {
1020
+ updatedSession = {
1021
+ ...updatedSession,
1022
+ data: {
1023
+ ...updatedSession.data,
1024
+ ...directive.dataUpdate,
1025
+ },
1026
+ };
1027
+ }
1028
+ // Apply position control
1029
+ if (directive.goTo) {
1030
+ const flowTarget = typeof directive.goTo === 'string'
1031
+ ? directive.goTo
1032
+ : directive.goTo.flow;
1033
+ if (flowTarget) {
1034
+ const targetFlow = this._flows.find(f => f.id === flowTarget || f.title === flowTarget);
1035
+ if (targetFlow) {
1036
+ // Merge goTo.data if present
1037
+ if (typeof directive.goTo === 'object' && directive.goTo.data) {
1038
+ updatedSession = {
1039
+ ...updatedSession,
1040
+ data: {
1041
+ ...updatedSession.data,
1042
+ ...directive.goTo.data,
1043
+ },
1044
+ };
1045
+ }
1046
+ updatedSession = (0, index_js_1.enterFlow)(updatedSession, targetFlow.id, targetFlow.title);
1047
+ // If a specific step is targeted
1048
+ if (typeof directive.goTo === 'object' && directive.goTo.step) {
1049
+ updatedSession = (0, index_js_1.enterStep)(updatedSession, directive.goTo.step);
1050
+ }
1051
+ }
1052
+ }
1053
+ }
1054
+ else if (directive.goToStep) {
1055
+ const stepTarget = typeof directive.goToStep === 'string'
1056
+ ? directive.goToStep
1057
+ : directive.goToStep.step;
1058
+ // Merge goToStep.data if present
1059
+ if (typeof directive.goToStep === 'object' && directive.goToStep.data) {
1060
+ updatedSession = {
1061
+ ...updatedSession,
1062
+ data: {
1063
+ ...updatedSession.data,
1064
+ ...directive.goToStep.data,
1065
+ },
1066
+ };
1067
+ }
1068
+ updatedSession = (0, index_js_1.enterStep)(updatedSession, stepTarget);
1069
+ }
1070
+ else if (directive.complete) {
1071
+ updatedSession = (0, index_js_1.completeCurrentFlow)(updatedSession);
1072
+ // If complete carries a chained directive, set it as pendingDirective
1073
+ if (typeof directive.complete === 'object' && directive.complete.next) {
1074
+ updatedSession = {
1075
+ ...updatedSession,
1076
+ pendingDirective: directive.complete.next,
1077
+ };
1078
+ }
1079
+ }
1080
+ else if (directive.abort) {
1081
+ const clearSession = typeof directive.abort === 'object'
1082
+ ? directive.abort.clearSession !== false
1083
+ : true;
1084
+ if (clearSession) {
1085
+ updatedSession = {
1086
+ ...updatedSession,
1087
+ currentFlow: undefined,
1088
+ currentStep: undefined,
1089
+ data: {},
1090
+ };
1091
+ }
1092
+ else {
1093
+ updatedSession = {
1094
+ ...updatedSession,
1095
+ currentFlow: undefined,
1096
+ currentStep: undefined,
1097
+ };
1098
+ }
1099
+ }
1100
+ else if (directive.reset) {
1101
+ const currentFlowId = updatedSession.currentFlow?.id;
1102
+ const currentFlowTitle = updatedSession.currentFlow?.title;
1103
+ if (currentFlowId && currentFlowTitle) {
1104
+ // Clear data if requested
1105
+ if (typeof directive.reset === 'object' && directive.reset.clearData) {
1106
+ const currentFlow = this._flows.find(f => f.id === currentFlowId);
1107
+ if (currentFlow) {
1108
+ const ownedFields = [
1109
+ ...(currentFlow.requiredFields || []),
1110
+ ...(currentFlow.optionalFields || []),
1111
+ ];
1112
+ updatedSession = (0, index_js_1.completeCurrentFlow)(updatedSession, { clearOwnedFields: ownedFields });
1113
+ // Re-enter the same flow
1114
+ updatedSession = (0, index_js_1.enterFlow)(updatedSession, currentFlowId, currentFlowTitle);
1115
+ }
1116
+ }
1117
+ else {
1118
+ // Re-enter the flow from the beginning (or specified step)
1119
+ updatedSession = (0, index_js_1.enterFlow)(updatedSession, currentFlowId, currentFlowTitle);
1120
+ }
1121
+ // If a specific step is targeted for reset
1122
+ if (typeof directive.reset === 'object' && directive.reset.step) {
1123
+ updatedSession = (0, index_js_1.enterStep)(updatedSession, directive.reset.step);
1124
+ }
1125
+ }
1126
+ }
1127
+ // Update metadata
1128
+ updatedSession = {
1129
+ ...updatedSession,
1130
+ metadata: {
1131
+ ...updatedSession.metadata,
1132
+ lastUpdatedAt: now,
1133
+ },
1134
+ };
1135
+ return updatedSession;
1136
+ }
1137
+ /**
1138
+ * Validate a directive for structural correctness.
1139
+ * Throws FlowConfigurationError for invalid combinations.
1140
+ * @private
1141
+ */
1142
+ validateDirective(directive) {
1143
+ // Check for multiple position fields
1144
+ const positionFields = ['goTo', 'goToStep', 'complete', 'abort', 'reset'];
1145
+ const setPositionFields = positionFields.filter(field => directive[field] !== undefined);
1146
+ if (setPositionFields.length > 1) {
1147
+ throw new Step_js_1.FlowConfigurationError(`[FlowConfigurationError] Multiple position fields: a Directive may set at most one position field. ` +
1148
+ `Found: ${setPositionFields.join(', ')}. Remove all but one.`);
1149
+ }
1150
+ // Check for empty goTo object
1151
+ if (directive.goTo && typeof directive.goTo === 'object') {
1152
+ const goToObj = directive.goTo;
1153
+ if (!goToObj.flow && !goToObj.step) {
1154
+ throw new Step_js_1.FlowConfigurationError(`[FlowConfigurationError] Empty goTo: "goTo" requires at least a "flow" field. ` +
1155
+ `Provide { goTo: { flow: '<id>' } } or use the string shorthand { goTo: '<id>' }.`);
1156
+ }
1157
+ }
1158
+ }
1159
+ /**
1160
+ * Strip pre-LLM-only fields (appendPrompt, injectTools, halt) from a directive.
1161
+ * These fields are transient (one-turn lifetime) and must not be persisted.
1162
+ * @private
1163
+ */
1164
+ stripPreDirectiveFields(directive) {
1165
+ const raw = directive;
1166
+ if (!raw.appendPrompt && !raw.injectTools && raw.halt === undefined) {
1167
+ return directive;
1168
+ }
1169
+ const { appendPrompt, injectTools, halt, ...rest } = raw;
1170
+ if (appendPrompt || injectTools || halt !== undefined) {
1171
+ index_js_1.logger.warn(`[Agent] Ignoring pre-LLM-only fields on pendingDirective (these only take effect in onEnter/prepare hooks): ` +
1172
+ `${[appendPrompt && 'appendPrompt', injectTools && 'injectTools', halt !== undefined && 'halt'].filter(Boolean).join(', ')}`);
1173
+ }
1174
+ return rest;
1175
+ }
1176
+ /**
1177
+ * Simplified respond method using SessionManager
1178
+ * Automatically manages conversation history through the session
1179
+ */
1180
+ async chat(message, options) {
1181
+ // Delegate to ResponseModal.generate()
1182
+ return this._responseModal.generate(message, options);
1183
+ }
1184
+ /**
1185
+ * Modern streaming API - simple interface like chat() but returns a stream
1186
+ * Automatically manages conversation history through the session
1187
+ */
1188
+ async *stream(message, options) {
1189
+ // Delegate to ResponseModal with the same options structure as chat()
1190
+ yield* this._responseModal.stream(message, {
1191
+ history: options?.history,
1192
+ contextOverride: options?.contextOverride,
1193
+ signal: options?.signal,
1194
+ });
1195
+ }
1196
+ }
1197
+ exports.Agent = Agent;
1198
+ //# sourceMappingURL=Agent.js.map