@elizaos/agent 2.0.0-alpha.192 → 2.0.0-alpha.196

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 (240) hide show
  1. package/apps/app-lifeops/src/action.d.ts.map +1 -1
  2. package/apps/app-lifeops/src/action.js +21 -2
  3. package/apps/app-lifeops/src/actions/app-blocker.d.ts +3 -1
  4. package/apps/app-lifeops/src/actions/app-blocker.d.ts.map +1 -1
  5. package/apps/app-lifeops/src/actions/app-blocker.js +2 -0
  6. package/apps/app-lifeops/src/actions/calendar.d.ts.map +1 -1
  7. package/apps/app-lifeops/src/actions/calendar.js +140 -7
  8. package/apps/app-lifeops/src/actions/computer-use.d.ts.map +1 -1
  9. package/apps/app-lifeops/src/actions/computer-use.js +248 -17
  10. package/apps/app-lifeops/src/actions/cross-channel-send.d.ts +3 -1
  11. package/apps/app-lifeops/src/actions/cross-channel-send.d.ts.map +1 -1
  12. package/apps/app-lifeops/src/actions/cross-channel-send.js +1 -0
  13. package/apps/app-lifeops/src/actions/dossier.d.ts.map +1 -1
  14. package/apps/app-lifeops/src/actions/dossier.js +51 -34
  15. package/apps/app-lifeops/src/actions/gmail.d.ts.map +1 -1
  16. package/apps/app-lifeops/src/actions/gmail.js +5 -0
  17. package/apps/app-lifeops/src/actions/inbox.d.ts.map +1 -1
  18. package/apps/app-lifeops/src/actions/inbox.js +8 -1
  19. package/apps/app-lifeops/src/actions/life.d.ts.map +1 -1
  20. package/apps/app-lifeops/src/actions/life.extractor.d.ts.map +1 -1
  21. package/apps/app-lifeops/src/actions/life.extractor.js +103 -4
  22. package/apps/app-lifeops/src/actions/life.js +8 -0
  23. package/apps/app-lifeops/src/actions/non-actionable-request.d.ts +6 -0
  24. package/apps/app-lifeops/src/actions/non-actionable-request.d.ts.map +1 -0
  25. package/apps/app-lifeops/src/actions/non-actionable-request.js +33 -0
  26. package/apps/app-lifeops/src/actions/password-manager.d.ts.map +1 -1
  27. package/apps/app-lifeops/src/actions/password-manager.js +11 -2
  28. package/apps/app-lifeops/src/actions/relationships.d.ts +3 -1
  29. package/apps/app-lifeops/src/actions/relationships.d.ts.map +1 -1
  30. package/apps/app-lifeops/src/actions/relationships.js +167 -11
  31. package/apps/app-lifeops/src/actions/remote-desktop.js +1 -1
  32. package/apps/app-lifeops/src/actions/screen-time.d.ts.map +1 -1
  33. package/apps/app-lifeops/src/actions/screen-time.js +8 -2
  34. package/apps/app-lifeops/src/actions/start-remote-session.js +1 -1
  35. package/apps/app-lifeops/src/actions/subscriptions.d.ts +5 -0
  36. package/apps/app-lifeops/src/actions/subscriptions.d.ts.map +1 -0
  37. package/apps/app-lifeops/src/actions/subscriptions.js +251 -0
  38. package/apps/app-lifeops/src/actions/twilio-call.d.ts.map +1 -1
  39. package/apps/app-lifeops/src/actions/twilio-call.js +48 -3
  40. package/apps/app-lifeops/src/actions/update-owner-profile.d.ts.map +1 -1
  41. package/apps/app-lifeops/src/actions/update-owner-profile.js +2 -1
  42. package/apps/app-lifeops/src/actions/website-blocker.d.ts +6 -2
  43. package/apps/app-lifeops/src/actions/website-blocker.d.ts.map +1 -1
  44. package/apps/app-lifeops/src/actions/website-blocker.js +38 -0
  45. package/apps/app-lifeops/src/actions/x-read.d.ts.map +1 -1
  46. package/apps/app-lifeops/src/actions/x-read.js +27 -3
  47. package/apps/app-lifeops/src/dossier/action.d.ts.map +1 -1
  48. package/apps/app-lifeops/src/dossier/action.js +8 -7
  49. package/apps/app-lifeops/src/followup/actions/listOverdueFollowups.d.ts.map +1 -1
  50. package/apps/app-lifeops/src/followup/actions/listOverdueFollowups.js +1 -0
  51. package/apps/app-lifeops/src/followup/actions/markFollowupDone.d.ts.map +1 -1
  52. package/apps/app-lifeops/src/followup/actions/markFollowupDone.js +2 -1
  53. package/apps/app-lifeops/src/followup/actions/setFollowupThreshold.d.ts.map +1 -1
  54. package/apps/app-lifeops/src/followup/actions/setFollowupThreshold.js +2 -1
  55. package/apps/app-lifeops/src/lifeops/calendly-client.d.ts.map +1 -1
  56. package/apps/app-lifeops/src/lifeops/calendly-client.js +4 -2
  57. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.d.ts.map +1 -1
  58. package/apps/app-lifeops/src/lifeops/discord-browser-scraper.js +6 -5
  59. package/apps/app-lifeops/src/lifeops/password-manager-bridge.d.ts +1 -1
  60. package/apps/app-lifeops/src/lifeops/password-manager-bridge.d.ts.map +1 -1
  61. package/apps/app-lifeops/src/lifeops/password-manager-bridge.js +84 -6
  62. package/apps/app-lifeops/src/lifeops/remote-desktop.d.ts.map +1 -1
  63. package/apps/app-lifeops/src/lifeops/remote-desktop.js +30 -0
  64. package/apps/app-lifeops/src/lifeops/repository.d.ts +15 -0
  65. package/apps/app-lifeops/src/lifeops/repository.d.ts.map +1 -1
  66. package/apps/app-lifeops/src/lifeops/repository.js +330 -1
  67. package/apps/app-lifeops/src/lifeops/service-mixin-discord.js +222 -42
  68. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.d.ts +2 -0
  69. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.d.ts.map +1 -0
  70. package/apps/app-lifeops/src/lifeops/service-mixin-subscriptions.js +763 -0
  71. package/apps/app-lifeops/src/lifeops/service.d.ts +63 -0
  72. package/apps/app-lifeops/src/lifeops/service.d.ts.map +1 -1
  73. package/apps/app-lifeops/src/lifeops/service.js +2 -1
  74. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.d.ts +54 -0
  75. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.d.ts.map +1 -0
  76. package/apps/app-lifeops/src/lifeops/subscriptions-playbooks.js +213 -0
  77. package/apps/app-lifeops/src/lifeops/subscriptions-types.d.ts +80 -0
  78. package/apps/app-lifeops/src/lifeops/subscriptions-types.d.ts.map +1 -0
  79. package/apps/app-lifeops/src/lifeops/twilio.d.ts.map +1 -1
  80. package/apps/app-lifeops/src/lifeops/twilio.js +4 -2
  81. package/apps/app-lifeops/src/lifeops/whatsapp-client.d.ts.map +1 -1
  82. package/apps/app-lifeops/src/lifeops/whatsapp-client.js +4 -2
  83. package/apps/app-lifeops/src/lifeops/x-poster.d.ts.map +1 -1
  84. package/apps/app-lifeops/src/lifeops/x-poster.js +12 -6
  85. package/apps/app-lifeops/src/lifeops/x-reader.d.ts.map +1 -1
  86. package/apps/app-lifeops/src/lifeops/x-reader.js +7 -5
  87. package/apps/app-lifeops/src/plugin.d.ts.map +1 -1
  88. package/apps/app-lifeops/src/plugin.js +31 -24
  89. package/apps/app-lifeops/src/provider.d.ts.map +1 -1
  90. package/apps/app-lifeops/src/provider.js +4 -4
  91. package/apps/app-lifeops/src/providers/inbox-triage.js +2 -2
  92. package/apps/app-lifeops/src/providers/lifeops.d.ts.map +1 -1
  93. package/apps/app-lifeops/src/providers/lifeops.js +19 -10
  94. package/apps/app-lifeops/src/providers/website-blocker.js +1 -1
  95. package/apps/app-lifeops/src/public.d.ts +4 -0
  96. package/apps/app-lifeops/src/public.d.ts.map +1 -0
  97. package/apps/app-lifeops/src/public.js +3 -0
  98. package/apps/app-lifeops/src/routes/plugin.d.ts +18 -0
  99. package/apps/app-lifeops/src/routes/plugin.d.ts.map +1 -0
  100. package/apps/app-lifeops/src/routes/plugin.js +270 -0
  101. package/apps/app-lifeops/src/website-blocker/chat-integration/actions/blockUntilTaskComplete.d.ts.map +1 -1
  102. package/apps/app-lifeops/src/website-blocker/chat-integration/actions/blockUntilTaskComplete.js +18 -2
  103. package/apps/app-training/src/core/training-orchestrator.d.ts.map +1 -1
  104. package/apps/app-training/src/core/training-orchestrator.js +4 -0
  105. package/apps/app-training/src/services/training-trigger.d.ts +32 -18
  106. package/apps/app-training/src/services/training-trigger.d.ts.map +1 -1
  107. package/apps/app-training/src/services/training-trigger.js +10 -1
  108. package/package.json +4 -4
  109. package/packages/agent/src/actions/browser-session.d.ts +3 -0
  110. package/packages/agent/src/actions/browser-session.d.ts.map +1 -0
  111. package/packages/agent/src/actions/browser-session.js +207 -0
  112. package/packages/agent/src/actions/context-signal.d.ts +1 -1
  113. package/packages/agent/src/actions/context-signal.d.ts.map +1 -1
  114. package/packages/agent/src/actions/context-signal.js +1 -1
  115. package/packages/agent/src/actions/extract-page.d.ts +3 -0
  116. package/packages/agent/src/actions/extract-page.d.ts.map +1 -0
  117. package/packages/agent/src/actions/extract-page.js +124 -0
  118. package/packages/agent/src/actions/grounded-action-reply.js +1 -1
  119. package/packages/agent/src/actions/recent-conversation-texts.d.ts +9 -0
  120. package/packages/agent/src/actions/recent-conversation-texts.d.ts.map +1 -0
  121. package/packages/agent/src/actions/recent-conversation-texts.js +81 -0
  122. package/packages/agent/src/actions/set-user-name.js +1 -1
  123. package/packages/agent/src/actions/web-search.d.ts +4 -10
  124. package/packages/agent/src/actions/web-search.d.ts.map +1 -1
  125. package/packages/agent/src/actions/web-search.js +89 -19
  126. package/packages/agent/src/api/binance-skill-helpers.d.ts.map +1 -1
  127. package/packages/agent/src/api/binance-skill-helpers.js +1 -1
  128. package/packages/agent/src/api/chat-augmentation.d.ts +1 -20
  129. package/packages/agent/src/api/chat-augmentation.d.ts.map +1 -1
  130. package/packages/agent/src/api/chat-augmentation.js +2 -110
  131. package/packages/agent/src/api/conversation-metadata.d.ts +9 -0
  132. package/packages/agent/src/api/conversation-metadata.d.ts.map +1 -0
  133. package/packages/agent/src/api/conversation-metadata.js +98 -0
  134. package/packages/agent/src/api/conversation-routes.d.ts.map +1 -1
  135. package/packages/agent/src/api/conversation-routes.js +40 -9
  136. package/packages/agent/src/api/lifeops-browser-packaging.d.ts +1 -1
  137. package/packages/agent/src/api/lifeops-browser-packaging.d.ts.map +1 -1
  138. package/packages/agent/src/api/lifeops-browser-packaging.js +1 -1
  139. package/packages/agent/src/api/permissions-routes.js +1 -1
  140. package/packages/agent/src/api/server-helpers-auth.js +1 -1
  141. package/packages/agent/src/api/server-helpers.d.ts +2 -1
  142. package/packages/agent/src/api/server-helpers.d.ts.map +1 -1
  143. package/packages/agent/src/api/server-helpers.js +2 -110
  144. package/packages/agent/src/api/server-types.d.ts +14 -0
  145. package/packages/agent/src/api/server-types.d.ts.map +1 -1
  146. package/packages/agent/src/api/server.d.ts.map +1 -1
  147. package/packages/agent/src/api/server.js +3 -13
  148. package/packages/agent/src/api/workbench-routes.d.ts.map +1 -1
  149. package/packages/agent/src/api/workbench-routes.js +0 -14
  150. package/packages/agent/src/auth/credentials.d.ts.map +1 -1
  151. package/packages/agent/src/auth/credentials.js +8 -0
  152. package/packages/agent/src/config/zod-schema.d.ts +2 -2
  153. package/packages/agent/src/config/zod-schema.providers-core.d.ts +3 -3
  154. package/packages/agent/src/providers/automation-terminal-bridge.d.ts +3 -0
  155. package/packages/agent/src/providers/automation-terminal-bridge.d.ts.map +1 -0
  156. package/packages/agent/src/providers/automation-terminal-bridge.js +71 -0
  157. package/packages/agent/src/providers/index.d.ts +1 -0
  158. package/packages/agent/src/providers/index.d.ts.map +1 -1
  159. package/packages/agent/src/providers/index.js +1 -0
  160. package/packages/agent/src/providers/recent-conversations.d.ts.map +1 -1
  161. package/packages/agent/src/providers/recent-conversations.js +5 -0
  162. package/packages/agent/src/providers/relevant-conversations.d.ts.map +1 -1
  163. package/packages/agent/src/providers/relevant-conversations.js +5 -0
  164. package/packages/agent/src/providers/user-name.d.ts.map +1 -1
  165. package/packages/agent/src/providers/user-name.js +1 -1
  166. package/packages/agent/src/runtime/eliza-plugin.d.ts.map +1 -1
  167. package/packages/agent/src/runtime/eliza-plugin.js +6 -0
  168. package/packages/agent/src/runtime/plugin-resolver.d.ts.map +1 -1
  169. package/packages/agent/src/runtime/plugin-resolver.js +115 -0
  170. package/packages/agent/src/services/browser-workspace-types.d.ts +5 -1
  171. package/packages/agent/src/services/browser-workspace-types.d.ts.map +1 -1
  172. package/packages/agent/src/services/browser-workspace.d.ts.map +1 -1
  173. package/packages/agent/src/services/browser-workspace.js +178 -7
  174. package/packages/agent/src/services/coding-agent-context.d.ts +4 -4
  175. package/packages/agent/src/services/escalation.d.ts +0 -11
  176. package/packages/agent/src/services/escalation.d.ts.map +1 -1
  177. package/packages/agent/src/services/escalation.js +19 -58
  178. package/packages/agent/src/services/hosted-tools.d.ts +70 -0
  179. package/packages/agent/src/services/hosted-tools.d.ts.map +1 -0
  180. package/packages/agent/src/services/hosted-tools.js +87 -0
  181. package/packages/agent/src/services/owner-name.d.ts +4 -0
  182. package/packages/agent/src/services/owner-name.d.ts.map +1 -0
  183. package/packages/agent/src/services/owner-name.js +46 -0
  184. package/packages/agent/src/services/registry-client-queries.d.ts +1 -1
  185. package/packages/agent/src/services/registry-client-queries.js +1 -1
  186. package/packages/agent/src/services/registry-client-types.d.ts +1 -1
  187. package/packages/agent/src/services/stream-manager.d.ts +1 -1
  188. package/packages/app-core/src/config/boot-config-store.d.ts +1 -1
  189. package/packages/app-core/src/config/boot-config-store.d.ts.map +1 -1
  190. package/packages/shared/src/contracts/lifeops.d.ts +26 -0
  191. package/packages/shared/src/contracts/lifeops.d.ts.map +1 -1
  192. package/packages/shared/src/contracts/lifeops.js +28 -0
  193. package/packages/typescript/src/actions.d.ts.map +1 -1
  194. package/packages/typescript/src/actions.js +12 -0
  195. package/packages/typescript/src/features/advanced-capabilities/actions/scheduleFollowUp.d.ts.map +1 -1
  196. package/packages/typescript/src/features/advanced-capabilities/actions/scheduleFollowUp.js +8 -2
  197. package/packages/typescript/src/features/advanced-capabilities/providers/settings.d.ts.map +1 -1
  198. package/packages/typescript/src/features/advanced-capabilities/providers/settings.js +11 -3
  199. package/packages/typescript/src/features/basic-capabilities/providers/actions.d.ts.map +1 -1
  200. package/packages/typescript/src/features/basic-capabilities/providers/actions.js +23 -1
  201. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.d.ts +5 -0
  202. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.d.ts.map +1 -0
  203. package/packages/typescript/src/features/basic-capabilities/providers/non-actionable-chatter.js +22 -0
  204. package/packages/typescript/src/features/basic-capabilities/providers/providers.d.ts.map +1 -1
  205. package/packages/typescript/src/features/basic-capabilities/providers/providers.js +10 -5
  206. package/packages/typescript/src/features/knowledge/documents-provider.d.ts.map +1 -1
  207. package/packages/typescript/src/features/knowledge/documents-provider.js +1 -0
  208. package/packages/typescript/src/features/shared/schedule-follow-up-response.d.ts +10 -0
  209. package/packages/typescript/src/features/shared/schedule-follow-up-response.d.ts.map +1 -0
  210. package/packages/typescript/src/features/shared/schedule-follow-up-response.js +49 -0
  211. package/packages/typescript/src/features/trajectories/TrajectoriesService.d.ts +1 -0
  212. package/packages/typescript/src/features/trajectories/TrajectoriesService.d.ts.map +1 -1
  213. package/packages/typescript/src/features/trajectories/TrajectoriesService.js +21 -2
  214. package/packages/typescript/src/features/trust/providers/settings.d.ts.map +1 -1
  215. package/packages/typescript/src/features/trust/providers/settings.js +11 -1
  216. package/packages/typescript/src/prompts.d.ts +4 -4
  217. package/packages/typescript/src/prompts.d.ts.map +1 -1
  218. package/packages/typescript/src/prompts.js +10 -2
  219. package/packages/typescript/src/schemas/character.d.ts +3 -3
  220. package/packages/typescript/src/services/message.d.ts +1 -0
  221. package/packages/typescript/src/services/message.d.ts.map +1 -1
  222. package/packages/typescript/src/services/message.js +483 -18
  223. package/packages/typescript/src/types/components.d.ts +6 -0
  224. package/packages/typescript/src/types/components.d.ts.map +1 -1
  225. package/packages/typescript/src/utils/context-routing.d.ts.map +1 -1
  226. package/packages/typescript/src/utils/context-routing.js +5 -1
  227. package/packages/typescript/src/utils/toon.d.ts.map +1 -1
  228. package/packages/typescript/src/utils/toon.js +23 -2
  229. package/packages/typescript/src/utils.d.ts +1 -0
  230. package/packages/typescript/src/utils.d.ts.map +1 -1
  231. package/packages/typescript/src/utils.js +1 -0
  232. package/apps/app-lifeops/src/types/app-blocker-settings-card.d.ts +0 -2
  233. package/apps/app-lifeops/src/types/app-blocker-settings-card.d.ts.map +0 -1
  234. package/apps/app-lifeops/src/types/index.d.ts +0 -3
  235. package/apps/app-lifeops/src/types/index.d.ts.map +0 -1
  236. package/apps/app-lifeops/src/types/index.js +0 -1
  237. package/apps/app-lifeops/src/types/website-blocker-settings-card.d.ts +0 -2
  238. package/apps/app-lifeops/src/types/website-blocker-settings-card.d.ts.map +0 -1
  239. package/apps/app-lifeops/src/types/website-blocker-settings-card.js +0 -1
  240. /package/apps/app-lifeops/src/{types/app-blocker-settings-card.js → lifeops/subscriptions-types.js} +0 -0
@@ -13,7 +13,7 @@ import { EventType } from "../types/events";
13
13
  import { ModelType } from "../types/model";
14
14
  import { incomingPipelineHookContext, modelStreamChunkPipelineHookContext, outgoingPipelineHookContext, parallelWithShouldRespondPipelineHookContext, preShouldRespondPipelineHookContext, } from "../types/pipeline-hooks";
15
15
  import { asUUID, ChannelType, ContentType } from "../types/primitives";
16
- import { composePromptFromState, getLocalServerUrl, parseBooleanFromText, parseKeyValueXml, truncateToCompleteSentence, } from "../utils";
16
+ import { composePromptFromState, getLocalServerUrl, parseBooleanFromText, parseJSONObjectFromText, parseKeyValueXml, truncateToCompleteSentence, } from "../utils";
17
17
  import { AVAILABLE_CONTEXTS_STATE_KEY, attachAvailableContexts, CONTEXT_ROUTING_STATE_KEY, mergeContextRouting, parseContextRoutingMetadata, setContextRoutingMetadata, } from "../utils/context-routing";
18
18
  import { createStreamingContext, MarkableExtractor, ResponseStreamExtractor, } from "../utils/streaming";
19
19
  import { extractFirstSentence, hasFirstSentence, } from "../utils/text-splitting";
@@ -28,6 +28,7 @@ export const RESERVED_XML_KEYS = new Set([
28
28
  "simple",
29
29
  "providers",
30
30
  ]);
31
+ const PLANNER_CONTROL_ACTIONS = new Set(["REPLY", "RESPOND", "IGNORE", "STOP"].map(normalizeActionIdentifier));
31
32
  function escapeRegex(value) {
32
33
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
33
34
  }
@@ -197,12 +198,157 @@ function normalizePlannerActions(parsedXml, runtime) {
197
198
  const finalActions = !runtime.isActionPlanningEnabled() && normalizedActions.length > 1
198
199
  ? [normalizedActions[0]]
199
200
  : normalizedActions;
200
- if (finalActions.length > 0) {
201
- return finalActions;
201
+ const actionLookup = buildRuntimeActionLookup(runtime);
202
+ const validActions = finalActions.filter((actionName) => {
203
+ const normalized = normalizeActionIdentifier(actionName);
204
+ if (!normalized) {
205
+ return false;
206
+ }
207
+ if (PLANNER_CONTROL_ACTIONS.has(normalized)) {
208
+ return true;
209
+ }
210
+ const resolvedAction = resolveRuntimeAction(actionLookup, actionName);
211
+ if (resolvedAction) {
212
+ return true;
213
+ }
214
+ runtime.logger.warn({
215
+ src: "service:message",
216
+ actionName,
217
+ }, "Dropping unknown planner action");
218
+ return false;
219
+ });
220
+ if (validActions.length > 0) {
221
+ return validActions;
202
222
  }
203
223
  const replyText = typeof parsedXml.text === "string" ? parsedXml.text.trim() : "";
204
224
  return replyText.length > 0 ? ["REPLY"] : ["IGNORE"];
205
225
  }
226
+ function normalizePlannerProviders(parsedXml, runtime) {
227
+ const rawProviders = parsedXml.providers;
228
+ const providerNames = (() => {
229
+ if (typeof rawProviders === "string") {
230
+ const trimmedProviders = rawProviders.trim();
231
+ if ((trimmedProviders.startsWith("[") && trimmedProviders.endsWith("]")) ||
232
+ (trimmedProviders.startsWith("{") && trimmedProviders.endsWith("}"))) {
233
+ try {
234
+ const parsedJson = JSON.parse(trimmedProviders);
235
+ if (Array.isArray(parsedJson)) {
236
+ return parsedJson
237
+ .map((providerName) => String(providerName).trim())
238
+ .filter((providerName) => providerName.length > 0);
239
+ }
240
+ if (typeof parsedJson === "object" &&
241
+ parsedJson !== null &&
242
+ Array.isArray(parsedJson.providers)) {
243
+ return (parsedJson.providers
244
+ .map((providerName) => String(providerName).trim())
245
+ .filter((providerName) => providerName.length > 0));
246
+ }
247
+ }
248
+ catch {
249
+ // Fall through to XML/comma parsing below.
250
+ }
251
+ }
252
+ if (rawProviders.includes("<provider>") ||
253
+ rawProviders.includes("<provider ")) {
254
+ const providers = Array.from(rawProviders.matchAll(/<provider>([\s\S]*?)<\/provider>/g))
255
+ .map((match) => match[1]?.trim() ?? "")
256
+ .filter((providerName) => providerName.length > 0);
257
+ if (providers.length > 0) {
258
+ return providers;
259
+ }
260
+ }
261
+ return rawProviders
262
+ .split(/[\n,;]/)
263
+ .map((providerName) => providerName.replace(/^[\s"'[\](){}]+|[\s"'[\](){}]+$/g, ""))
264
+ .map((providerName) => providerName.trim())
265
+ .filter((providerName) => providerName.length > 0);
266
+ }
267
+ if (Array.isArray(rawProviders)) {
268
+ return rawProviders
269
+ .map((providerName) => String(providerName).trim())
270
+ .filter((providerName) => providerName.length > 0);
271
+ }
272
+ return [];
273
+ })();
274
+ if (!runtime) {
275
+ return providerNames;
276
+ }
277
+ const providerLookup = new Map();
278
+ for (const provider of runtime.providers ?? []) {
279
+ const normalized = normalizeActionIdentifier(provider.name);
280
+ if (!normalized || providerLookup.has(normalized)) {
281
+ continue;
282
+ }
283
+ providerLookup.set(normalized, provider.name);
284
+ }
285
+ const normalizedProviders = providerNames
286
+ .map((providerName) => {
287
+ const canonicalProvider = providerLookup.get(normalizeActionIdentifier(providerName));
288
+ if (canonicalProvider) {
289
+ return canonicalProvider;
290
+ }
291
+ runtime.logger.warn({
292
+ src: "service:message",
293
+ providerName,
294
+ }, "Dropping unknown planner provider");
295
+ return "";
296
+ })
297
+ .filter((providerName) => providerName.length > 0);
298
+ if (normalizedProviders.length === 0) {
299
+ return normalizedProviders;
300
+ }
301
+ const providerDefinitions = new Map((runtime.providers ?? []).map((provider) => [
302
+ normalizeActionIdentifier(provider.name),
303
+ provider,
304
+ ]));
305
+ const expandedProviders = [...normalizedProviders];
306
+ const seenProviders = new Set(expandedProviders.map((providerName) => normalizeActionIdentifier(providerName)));
307
+ for (let index = 0; index < expandedProviders.length; index += 1) {
308
+ const providerName = expandedProviders[index];
309
+ const providerDefinition = providerDefinitions.get(normalizeActionIdentifier(providerName));
310
+ const companionProviders = providerDefinition?.companionProviders ?? [];
311
+ for (const companionProvider of companionProviders) {
312
+ const canonicalCompanion = providerLookup.get(normalizeActionIdentifier(companionProvider));
313
+ if (!canonicalCompanion) {
314
+ runtime.logger.warn({
315
+ src: "service:message",
316
+ providerName,
317
+ companionProvider,
318
+ }, "Dropping unknown companion provider");
319
+ continue;
320
+ }
321
+ const normalizedCompanion = normalizeActionIdentifier(canonicalCompanion);
322
+ if (seenProviders.has(normalizedCompanion)) {
323
+ continue;
324
+ }
325
+ seenProviders.add(normalizedCompanion);
326
+ expandedProviders.push(canonicalCompanion);
327
+ }
328
+ }
329
+ return expandedProviders;
330
+ }
331
+ const CORE_RESPONSE_STATE_PROVIDERS = [
332
+ "ENTITIES",
333
+ "CHARACTER",
334
+ "RECENT_MESSAGES",
335
+ "ACTIONS",
336
+ "PROVIDERS",
337
+ ];
338
+ const STRUCTURED_RESPONSE_STATE_PROVIDERS = ["ACTIONS", "PROVIDERS"];
339
+ const FOCUSED_PROVIDER_REPLY_STATE_PROVIDERS = ["CHARACTER", "RECENT_MESSAGES"];
340
+ function composeResponseState(runtime, message, skipCache = false) {
341
+ return runtime.composeState(message, CORE_RESPONSE_STATE_PROVIDERS, true, skipCache);
342
+ }
343
+ function composeStructuredResponseState(runtime, message, skipCache = false) {
344
+ return runtime.composeState(message, STRUCTURED_RESPONSE_STATE_PROVIDERS, false, skipCache);
345
+ }
346
+ function composeProviderGroundedResponseState(runtime, message, providers, skipCache = false) {
347
+ return runtime.composeState(message, [...CORE_RESPONSE_STATE_PROVIDERS, ...providers], false, skipCache);
348
+ }
349
+ function composeFocusedProviderReplyState(runtime, message, providers, skipCache = false) {
350
+ return runtime.composeState(message, [...FOCUSED_PROVIDER_REPLY_STATE_PROVIDERS, ...providers], true, skipCache);
351
+ }
206
352
  /**
207
353
  * Escape Handlebars syntax in a string to prevent template injection.
208
354
  *
@@ -334,6 +480,193 @@ function isStopResponse(responseContent) {
334
480
  function normalizeActionIdentifier(actionName) {
335
481
  return actionName.trim().toUpperCase().replace(/_/g, "");
336
482
  }
483
+ const PROVIDER_FOLLOWUP_PASSIVE_ACTIONS = new Set(["REPLY", "RESPOND", "NONE"].map(normalizeActionIdentifier));
484
+ function shouldRunProviderFollowup(responseContent) {
485
+ if (!responseContent?.providers?.length) {
486
+ return false;
487
+ }
488
+ const normalizedActions = (responseContent.actions ?? [])
489
+ .map((actionName) => typeof actionName === "string"
490
+ ? normalizeActionIdentifier(actionName)
491
+ : "")
492
+ .filter((actionName) => actionName.length > 0);
493
+ if (normalizedActions.length === 0) {
494
+ return true;
495
+ }
496
+ return normalizedActions.every((actionName) => PROVIDER_FOLLOWUP_PASSIVE_ACTIONS.has(actionName));
497
+ }
498
+ function buildProviderFollowupPrompt(basePrompt) {
499
+ return `${basePrompt}
500
+
501
+ [PROVIDER FOLLOW-UP]
502
+ The requested providers have already been executed, and their grounded results are now present in context above.
503
+ Use those provider results to produce the final reply and/or action plan for this turn.
504
+ Do not ask for the same providers again.
505
+ If the provider results fully answer the user, reply directly.
506
+ If KNOWLEDGE contains a direct answer, prefer that grounded answer even when AVAILABLE_DOCUMENTS lists multiple files.
507
+ Do not ask "which file?" when the grounded KNOWLEDGE result already resolves the request.`;
508
+ }
509
+ function shouldAttemptProviderRescue(responseContent) {
510
+ if (!responseContent) {
511
+ return false;
512
+ }
513
+ if ((responseContent.providers?.length ?? 0) > 0) {
514
+ return false;
515
+ }
516
+ const normalizedActions = (responseContent.actions ?? [])
517
+ .map((actionName) => typeof actionName === "string"
518
+ ? normalizeActionIdentifier(actionName)
519
+ : "")
520
+ .filter((actionName) => actionName.length > 0);
521
+ if (normalizedActions.length === 0) {
522
+ return true;
523
+ }
524
+ return normalizedActions.every((actionName) => PROVIDER_FOLLOWUP_PASSIVE_ACTIONS.has(actionName));
525
+ }
526
+ function buildProviderSelectionPrompt(draftReply) {
527
+ const trimmedDraftReply = draftReply?.trim() ?? "";
528
+ const draftReplySection = trimmedDraftReply.length > 0
529
+ ? `draft_reply:\n${trimmedDraftReply.replace(/<\/response>/gi, "<\\/response>")}\n\n`
530
+ : "";
531
+ const draftReplyRules = trimmedDraftReply.length > 0
532
+ ? [
533
+ "- if the draft reply asks the user to resend, restate, or clarify information that may already exist in provider context, choose the relevant providers instead of sending the draft reply as-is",
534
+ '- when the recent conversation already identifies a prior upload or knowledge-base question, prefer grounded provider lookup over asking "which file?" again',
535
+ ]
536
+ : [];
537
+ return `task: Decide whether any providers should be called before sending the assistant's reply.
538
+
539
+ available provider catalog:
540
+ {{providers}}
541
+
542
+ recent conversation:
543
+ {{recentMessages}}
544
+
545
+ ${draftReplySection}rules[${4 + draftReplyRules.length}]:
546
+ - choose providers only when they can supply grounded information needed before the assistant replies
547
+ - uploaded files, documents, prior uploads, and knowledge-base questions should use the relevant providers before asking the user to resend the material
548
+ - if the user asks about an uploaded file or document and AVAILABLE_DOCUMENTS is available, prefer AVAILABLE_DOCUMENTS together with KNOWLEDGE before sending any clarification reply
549
+ - return an empty providers field when no provider lookup is needed
550
+ - do not include actions, text, or thought in the output
551
+ ${draftReplyRules.join("\n")}
552
+
553
+ output:
554
+ Return JSON or XML containing only provider names. No prose before or after it. No <think>.
555
+
556
+ Examples:
557
+ - user asks: "what is the qa codeword from the uploaded file?"
558
+ draft reply: "Which file are you referring to?"
559
+ output: {"providers":["AVAILABLE_DOCUMENTS","KNOWLEDGE"]}
560
+ - user asks: "what is the qa codeword from the uploaded file?"
561
+ draft reply: "I don't have the file in my context. Which file contains the QA codeword?"
562
+ output: {"providers":["AVAILABLE_DOCUMENTS","KNOWLEDGE"]}
563
+ - user asks: "thanks, that's all"
564
+ draft reply: "Glad to help."
565
+ output: {"providers":[]}`;
566
+ }
567
+ async function recoverProvidersForTurn(args) {
568
+ const prompt = composePromptFromState({
569
+ state: args.state,
570
+ template: buildProviderSelectionPrompt(args.draftReply),
571
+ });
572
+ try {
573
+ const result = await args.runtime.useModel(ModelType.TEXT_LARGE, {
574
+ prompt,
575
+ ...(args.attachments ? { attachments: args.attachments } : {}),
576
+ });
577
+ const rawResponse = typeof result === "string" ? result : "";
578
+ const parsed = parseKeyValueXml(rawResponse) ??
579
+ parseJSONObjectFromText(rawResponse);
580
+ const normalizedProviders = normalizePlannerProviders(parsed ?? { providers: rawResponse }, args.runtime);
581
+ if (normalizedProviders.length > 0) {
582
+ return normalizedProviders;
583
+ }
584
+ const shouldUseKnowledge = await shouldUseKnowledgeProviders(args.runtime, args.state, args.attachments);
585
+ return shouldUseKnowledge ? ["AVAILABLE_DOCUMENTS", "KNOWLEDGE"] : [];
586
+ }
587
+ catch (error) {
588
+ args.runtime.logger.warn({
589
+ src: "service:message",
590
+ error: error instanceof Error ? error.message : String(error),
591
+ }, "Provider rescue model call failed");
592
+ return [];
593
+ }
594
+ }
595
+ function buildGroundedFallbackReplyPrompt() {
596
+ return `task: Write the next assistant reply using grounded context.
597
+
598
+ grounded context:
599
+ {{providers}}
600
+
601
+ recent conversation:
602
+ {{recentMessages}}
603
+
604
+ rules[5]:
605
+ - answer directly from grounded context when it fully answers the user
606
+ - do not ask the user to resend, rename, or specify a file if grounded document or knowledge context already answers the request
607
+ - do not say you cannot access the file when grounded context is already present above
608
+ - if KNOWLEDGE contains a direct answer, prefer that grounded answer even when AVAILABLE_DOCUMENTS lists multiple files
609
+ - if grounded context is still insufficient, say exactly what is missing
610
+ - return only the reply text
611
+
612
+ output:
613
+ Plain text only. No XML, JSON, TOON, bullets, or <think>.`;
614
+ }
615
+ function buildKnowledgeProviderDecisionPrompt() {
616
+ return `task: Decide whether the assistant should consult uploaded-document or knowledge providers before replying.
617
+
618
+ available provider catalog:
619
+ {{providers}}
620
+
621
+ recent conversation:
622
+ {{recentMessages}}
623
+
624
+ rules[5]:
625
+ - return true when the user is asking about an uploaded file, document, prior upload, or knowledge-base content
626
+ - return true when the answer is likely already stored in uploaded documents or semantic knowledge search
627
+ - when AVAILABLE_DOCUMENTS or KNOWLEDGE is available and the user refers to an uploaded file or prior upload, return true
628
+ - return false for generic chat, thanks, or requests that clearly do not depend on uploaded or knowledge-base content
629
+ - return only the structured output, with no prose
630
+
631
+ output:
632
+ Return JSON or XML only.
633
+
634
+ Examples:
635
+ - user asks: "what is the qa codeword from the uploaded file?" -> {"useKnowledgeProviders":true}
636
+ - user asks: "thanks, that's all" -> {"useKnowledgeProviders":false}`;
637
+ }
638
+ async function shouldUseKnowledgeProviders(runtime, state, attachments) {
639
+ const prompt = composePromptFromState({
640
+ state,
641
+ template: buildKnowledgeProviderDecisionPrompt(),
642
+ });
643
+ try {
644
+ const result = await runtime.useModel(ModelType.TEXT_LARGE, {
645
+ prompt,
646
+ ...(attachments ? { attachments } : {}),
647
+ });
648
+ const rawResponse = typeof result === "string" ? result : "";
649
+ const parsed = parseKeyValueXml(rawResponse) ??
650
+ parseJSONObjectFromText(rawResponse);
651
+ const value = parsed?.useKnowledgeProviders ??
652
+ parsed?.use_knowledge_providers ??
653
+ rawResponse;
654
+ if (typeof value === "boolean") {
655
+ return value;
656
+ }
657
+ if (typeof value === "string") {
658
+ return value.trim().toLowerCase() === "true";
659
+ }
660
+ return false;
661
+ }
662
+ catch (error) {
663
+ runtime.logger.warn({
664
+ src: "service:message",
665
+ error: error instanceof Error ? error.message : String(error),
666
+ }, "Knowledge provider decision model call failed");
667
+ return false;
668
+ }
669
+ }
337
670
  function buildRuntimeActionLookup(runtime) {
338
671
  const actionMap = new Map();
339
672
  for (const action of runtime.actions ?? []) {
@@ -728,6 +1061,16 @@ function prepareShouldRespondState(state) {
728
1061
  text: providersText,
729
1062
  };
730
1063
  }
1064
+ function isBenchmarkMode(state) {
1065
+ const benchmarkFlag = state.values?.benchmark_has_context;
1066
+ if (typeof benchmarkFlag === "boolean") {
1067
+ return benchmarkFlag;
1068
+ }
1069
+ if (typeof benchmarkFlag === "string") {
1070
+ return parseBooleanFromText(benchmarkFlag);
1071
+ }
1072
+ return false;
1073
+ }
731
1074
  /**
732
1075
  * Default implementation of the MessageService interface.
733
1076
  * This service handles the complete message processing pipeline including:
@@ -1322,7 +1665,7 @@ export class DefaultMessageService {
1322
1665
  }
1323
1666
  const promptAttachments = resolvePromptAttachments(message.content.attachments);
1324
1667
  // Compose initial state (after incoming hooks so providers/actions text matches this turn)
1325
- let state = await runtime.composeState(message, ["ENTITIES", "CHARACTER", "RECENT_MESSAGES", "ACTIONS"], true, false);
1668
+ let state = await composeResponseState(runtime, message);
1326
1669
  state = attachAvailableContexts(state, runtime);
1327
1670
  const metadata = typeof message.content.metadata === "object" &&
1328
1671
  message.content.metadata !== null
@@ -1392,7 +1735,7 @@ export class DefaultMessageService {
1392
1735
  runtime.stateCache.delete(message.id);
1393
1736
  runtime.stateCache.delete(`${message.id}_action_results`);
1394
1737
  }
1395
- state = await runtime.composeState(message, ["ENTITIES", "CHARACTER", "RECENT_MESSAGES", "ACTIONS"], true, false);
1738
+ state = await composeResponseState(runtime, message);
1396
1739
  state = attachAvailableContexts(state, runtime);
1397
1740
  }
1398
1741
  let responseContent = null;
@@ -1445,8 +1788,44 @@ export class DefaultMessageService {
1445
1788
  if (responseContent && message.id) {
1446
1789
  responseContent.inReplyTo = createUniqueUuid(runtime, message.id);
1447
1790
  }
1791
+ const providerStateValues = {
1792
+ [AVAILABLE_CONTEXTS_STATE_KEY]: state.values?.[AVAILABLE_CONTEXTS_STATE_KEY],
1793
+ [CONTEXT_ROUTING_STATE_KEY]: state.values?.[CONTEXT_ROUTING_STATE_KEY],
1794
+ };
1448
1795
  if (responseContent?.providers && responseContent.providers.length > 0) {
1449
- state = await runtime.composeState(message, responseContent.providers, false, false);
1796
+ state = withContextRoutingValues(await composeProviderGroundedResponseState(runtime, message, responseContent.providers), providerStateValues);
1797
+ }
1798
+ if (responseContent && shouldRunProviderFollowup(responseContent)) {
1799
+ const providerFollowupState = responseContent.providers && responseContent.providers.length > 0
1800
+ ? withContextRoutingValues(await composeFocusedProviderReplyState(runtime, message, responseContent.providers), providerStateValues)
1801
+ : state;
1802
+ runtime.logger.info({
1803
+ src: "service:message",
1804
+ providers: responseContent.providers ?? [],
1805
+ actions: responseContent.actions ?? [],
1806
+ }, "Running provider follow-up pass");
1807
+ const providerContinuation = await this.runSingleShotCore(runtime, message, providerFollowupState, opts, responseId, promptAttachments, {
1808
+ precomposedState: providerFollowupState,
1809
+ failureStage: "answering from requested provider results",
1810
+ providerFollowup: true,
1811
+ });
1812
+ responseContent = providerContinuation.responseContent;
1813
+ responseMessages = providerContinuation.responseMessages;
1814
+ state = providerContinuation.state;
1815
+ mode = providerContinuation.mode;
1816
+ if (responseContent && message.id) {
1817
+ responseContent.inReplyTo = createUniqueUuid(runtime, message.id);
1818
+ }
1819
+ runtime.logger.info({
1820
+ src: "service:message",
1821
+ finalActions: responseContent?.actions ?? [],
1822
+ finalProviders: responseContent?.providers ?? [],
1823
+ hasText: typeof responseContent?.text === "string" &&
1824
+ responseContent.text.length > 0,
1825
+ }, "Provider follow-up pass completed");
1826
+ if (responseContent?.providers && responseContent.providers.length > 0) {
1827
+ state = withContextRoutingValues(await runtime.composeState(message, responseContent.providers, false, false), providerStateValues);
1828
+ }
1450
1829
  }
1451
1830
  // Save response memory to database.
1452
1831
  // - simple mode: persists after hooks in the branch below.
@@ -1623,7 +2002,9 @@ export class DefaultMessageService {
1623
2002
  return [];
1624
2003
  }, responseMessages);
1625
2004
  await runEvaluate();
1626
- if (opts.continueAfterActions && message.id) {
2005
+ if (opts.continueAfterActions &&
2006
+ message.id &&
2007
+ !isBenchmarkMode(state)) {
1627
2008
  const taskCompletion = await runtime.getCache(getTaskCompletionCacheKey(message.id));
1628
2009
  await runtime.deleteCache(getTaskCompletionCacheKey(message.id));
1629
2010
  if (taskCompletion?.assessed &&
@@ -1935,7 +2316,6 @@ export class DefaultMessageService {
1935
2316
  shouldRespond: false,
1936
2317
  skipEvaluation: true,
1937
2318
  reason: "no room context",
1938
- primaryContext: "general",
1939
2319
  };
1940
2320
  }
1941
2321
  function normalizeEnvList(value) {
@@ -1976,7 +2356,6 @@ export class DefaultMessageService {
1976
2356
  shouldRespond: true,
1977
2357
  skipEvaluation: true,
1978
2358
  reason: `private channel: ${roomType}`,
1979
- primaryContext: "general",
1980
2359
  };
1981
2360
  }
1982
2361
  // 2. Specific sources (e.g., client_chat): always respond
@@ -1985,7 +2364,6 @@ export class DefaultMessageService {
1985
2364
  shouldRespond: true,
1986
2365
  skipEvaluation: true,
1987
2366
  reason: `whitelisted source: ${sourceStr}`,
1988
- primaryContext: "general",
1989
2367
  };
1990
2368
  }
1991
2369
  // 3. Platform mentions and replies: always respond
@@ -1996,7 +2374,6 @@ export class DefaultMessageService {
1996
2374
  shouldRespond: true,
1997
2375
  skipEvaluation: true,
1998
2376
  reason: `platform ${mentionType}`,
1999
- primaryContext: "general",
2000
2377
  };
2001
2378
  }
2002
2379
  // 4. Mixed-address messages should still reach the agent when the text
@@ -2006,7 +2383,6 @@ export class DefaultMessageService {
2006
2383
  shouldRespond: true,
2007
2384
  skipEvaluation: true,
2008
2385
  reason: "text address with tagged participants",
2009
- primaryContext: "general",
2010
2386
  };
2011
2387
  }
2012
2388
  // 5. Clear self-modification requests should bypass the ignore-biased
@@ -2262,7 +2638,7 @@ export class DefaultMessageService {
2262
2638
  responseContent.inReplyTo = createUniqueUuid(runtime, message.id);
2263
2639
  }
2264
2640
  if (responseContent.providers && responseContent.providers.length > 0) {
2265
- accumulatedState = withActionResults(withContextRoutingValues(await runtime.composeState(message, responseContent.providers, false, false), contextRoutingStateValues), traceActionResults);
2641
+ accumulatedState = withActionResults(withContextRoutingValues(await composeProviderGroundedResponseState(runtime, message, responseContent.providers), contextRoutingStateValues), traceActionResults);
2266
2642
  }
2267
2643
  else {
2268
2644
  accumulatedState = withActionResults(continuation.state, traceActionResults);
@@ -2359,7 +2735,7 @@ export class DefaultMessageService {
2359
2735
  responseContent.inReplyTo = createUniqueUuid(runtime, message.id);
2360
2736
  }
2361
2737
  if (responseContent.providers && responseContent.providers.length > 0) {
2362
- accumulatedState = withTaskCompletion(withActionResults(withContextRoutingValues(await runtime.composeState(message, responseContent.providers, false, false), contextRoutingStateValues), initialActionResults), taskCompletion);
2738
+ accumulatedState = withTaskCompletion(withActionResults(withContextRoutingValues(await composeProviderGroundedResponseState(runtime, message, responseContent.providers), contextRoutingStateValues), initialActionResults), taskCompletion);
2363
2739
  }
2364
2740
  else {
2365
2741
  accumulatedState = withTaskCompletion(withActionResults(continuation.state, initialActionResults), taskCompletion);
@@ -2439,7 +2815,7 @@ export class DefaultMessageService {
2439
2815
  async runSingleShotCore(runtime, message, state, opts, responseId, promptAttachments, overrides) {
2440
2816
  state =
2441
2817
  overrides?.precomposedState ??
2442
- (await runtime.composeState(message, ["ACTIONS"], false, false));
2818
+ (await composeStructuredResponseState(runtime, message));
2443
2819
  if (!state.values?.actionNames) {
2444
2820
  runtime.logger.warn({ src: "service:message" }, "actionNames data missing from state");
2445
2821
  }
@@ -2456,8 +2832,11 @@ export class DefaultMessageService {
2456
2832
  const optimizedResponseService = runtime.getService(OPTIMIZED_PROMPT_SERVICE);
2457
2833
  const baselineResponseTemplate = runtime.character.templates?.messageHandlerTemplate ||
2458
2834
  messageHandlerTemplate;
2459
- const prompt = overrides?.prompt ||
2835
+ let prompt = overrides?.prompt ||
2460
2836
  resolveOptimizedPrompt(optimizedResponseService, "response", baselineResponseTemplate);
2837
+ if (overrides?.providerFollowup) {
2838
+ prompt = buildProviderFollowupPrompt(prompt);
2839
+ }
2461
2840
  // Use dynamicPromptExecFromState for structured output with validation
2462
2841
  setTrajectoryPurpose("response");
2463
2842
  const parsedXml = await runtime.dynamicPromptExecFromState({
@@ -2484,6 +2863,13 @@ export class DefaultMessageService {
2484
2863
  validateField: false,
2485
2864
  streamField: false,
2486
2865
  },
2866
+ {
2867
+ field: "providers",
2868
+ description: "Optional provider names to call before the final reply or action. Use an empty field when no provider lookup is needed.",
2869
+ required: false,
2870
+ validateField: false,
2871
+ streamField: false,
2872
+ },
2487
2873
  // WHY streamField: true? This is the user-facing output - stream it!
2488
2874
  // WHY validateField default? At level 1, we want to validate text integrity
2489
2875
  {
@@ -2515,7 +2901,7 @@ export class DefaultMessageService {
2515
2901
  ...parsedXml,
2516
2902
  thought: String(parsedXml.thought || ""),
2517
2903
  actions: finalActions,
2518
- providers: [],
2904
+ providers: normalizePlannerProviders(parsedXml, runtime),
2519
2905
  text: String(parsedXml.text || ""),
2520
2906
  simple: parsedXml.simple === true || parsedXml.simple === "true",
2521
2907
  };
@@ -2590,6 +2976,10 @@ Output ONLY the continuation, starting immediately after the last character abov
2590
2976
  }
2591
2977
  else {
2592
2978
  runtime.logger.warn({ src: "service:message" }, "dynamicPromptExecFromState returned null");
2979
+ const groundedFallback = await this.tryGroundedFallbackReply(runtime, message, state, responseId, promptAttachments);
2980
+ if (groundedFallback) {
2981
+ return groundedFallback;
2982
+ }
2593
2983
  return await this.buildStructuredFailureReply(runtime, message, state, responseId, overrides?.failureStage ?? "preparing the reply");
2594
2984
  }
2595
2985
  }
@@ -2601,6 +2991,23 @@ Output ONLY the continuation, starting immediately after the last character abov
2601
2991
  mode: "none",
2602
2992
  };
2603
2993
  }
2994
+ if (!overrides?.providerFollowup &&
2995
+ shouldAttemptProviderRescue(responseContent)) {
2996
+ const rescuedProviders = await recoverProvidersForTurn({
2997
+ runtime,
2998
+ state,
2999
+ draftReply: String(responseContent.text || ""),
3000
+ attachments: promptAttachments,
3001
+ });
3002
+ if (rescuedProviders.length > 0) {
3003
+ runtime.logger.info({
3004
+ src: "service:message",
3005
+ rescuedProviders,
3006
+ originalActions: responseContent.actions ?? [],
3007
+ }, "Selected providers during reply rescue pass");
3008
+ responseContent.providers = rescuedProviders;
3009
+ }
3010
+ }
2604
3011
  // Action parameter repair (Python parity):
2605
3012
  // If the model selected actions with required parameters but omitted <params>,
2606
3013
  // do a second pass asking for ONLY a <params> block.
@@ -2669,8 +3076,8 @@ Output ONLY the continuation, starting immediately after the last character abov
2669
3076
  responseContent.params = repairParsed.params;
2670
3077
  }
2671
3078
  }
3079
+ const benchmarkMode = isBenchmarkMode(state);
2672
3080
  // Benchmark mode (Python parity): force action-based loop when benchmark context is present.
2673
- const benchmarkMode = state.values.benchmark_has_context === true;
2674
3081
  if (benchmarkMode) {
2675
3082
  if (!responseContent.actions || responseContent.actions.length === 0) {
2676
3083
  responseContent.actions = ["REPLY"];
@@ -2731,6 +3138,64 @@ Output ONLY the continuation, starting immediately after the last character abov
2731
3138
  : "actions",
2732
3139
  };
2733
3140
  }
3141
+ async tryGroundedFallbackReply(runtime, message, state, responseId, promptAttachments) {
3142
+ let groundedState = state;
3143
+ const selectedProviders = await recoverProvidersForTurn({
3144
+ runtime,
3145
+ state,
3146
+ attachments: promptAttachments,
3147
+ });
3148
+ if (selectedProviders.length > 0) {
3149
+ groundedState = await composeFocusedProviderReplyState(runtime, message, selectedProviders);
3150
+ }
3151
+ const prompt = composePromptFromState({
3152
+ state: groundedState,
3153
+ template: buildGroundedFallbackReplyPrompt(),
3154
+ });
3155
+ try {
3156
+ const result = await runtime.useModel(ModelType.TEXT_SMALL, {
3157
+ prompt,
3158
+ ...(promptAttachments ? { attachments: promptAttachments } : {}),
3159
+ });
3160
+ const text = typeof result === "string" ? result.trim() : "";
3161
+ if (!text) {
3162
+ return null;
3163
+ }
3164
+ const responseContent = {
3165
+ thought: selectedProviders.length > 0
3166
+ ? "Grounded fallback reply from selected providers"
3167
+ : "Grounded fallback reply",
3168
+ actions: ["REPLY"],
3169
+ providers: selectedProviders,
3170
+ text,
3171
+ simple: true,
3172
+ responseId,
3173
+ };
3174
+ const responseMessages = [
3175
+ {
3176
+ id: responseId,
3177
+ entityId: runtime.agentId,
3178
+ agentId: runtime.agentId,
3179
+ content: responseContent,
3180
+ roomId: message.roomId,
3181
+ createdAt: Date.now(),
3182
+ },
3183
+ ];
3184
+ return {
3185
+ responseContent,
3186
+ responseMessages,
3187
+ state: groundedState,
3188
+ mode: "simple",
3189
+ };
3190
+ }
3191
+ catch (error) {
3192
+ runtime.logger.warn({
3193
+ src: "service:message",
3194
+ error: error instanceof Error ? error.message : String(error),
3195
+ }, "Grounded fallback reply generation failed");
3196
+ return null;
3197
+ }
3198
+ }
2734
3199
  async buildStructuredFailureReply(runtime, message, state, responseId, stage) {
2735
3200
  const failure = getStructuredOutputFailure(state);
2736
3201
  const recentMessages = typeof state.values?.recentMessages === "string" &&
@@ -246,6 +246,12 @@ export interface Provider {
246
246
  * include in the planner's state composition for a given turn.
247
247
  */
248
248
  contexts?: AgentContext[];
249
+ /**
250
+ * Additional providers that should run alongside this provider when it is
251
+ * selected by the planner. Use this for provider composition, not semantic
252
+ * routing.
253
+ */
254
+ companionProviders?: string[];
249
255
  /** Data retrieval function */
250
256
  get: (runtime: IAgentRuntime, message: Memory, state: State) => Promise<ProviderResult>;
251
257
  }