rasa-pro 3.14.0.dev6__py3-none-any.whl → 3.14.0.dev7__py3-none-any.whl

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.

Potentially problematic release.


This version of rasa-pro might be problematic. Click here for more details.

Files changed (82) hide show
  1. rasa/agents/exceptions.py +31 -1
  2. rasa/agents/protocol/a2a/a2a_agent.py +1 -1
  3. rasa/agents/protocol/mcp/mcp_base_agent.py +7 -6
  4. rasa/agents/protocol/mcp/mcp_task_agent.py +1 -1
  5. rasa/agents/utils.py +5 -0
  6. rasa/agents/validation.py +484 -0
  7. rasa/api.py +9 -6
  8. rasa/cli/arguments/train.py +2 -0
  9. rasa/cli/interactive.py +2 -0
  10. rasa/cli/train.py +2 -0
  11. rasa/cli/utils.py +85 -1
  12. rasa/core/actions/action.py +2 -6
  13. rasa/core/available_agents.py +47 -26
  14. rasa/core/channels/inspector/dist/assets/{arc-63212852.js → arc-cce7e0a8.js} +1 -1
  15. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-eecf6b13.js → blockDiagram-38ab4fdb-e2a49be7.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-8f798a9a.js → c4Diagram-3d4e48cf-3def7895.js} +1 -1
  17. rasa/core/channels/inspector/dist/assets/channel-858c2c20.js +1 -0
  18. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-df71a04c.js → classDiagram-70f12bd4-e66fe4df.js} +1 -1
  19. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-9b275968.js → classDiagram-v2-f2320105-eb874aaa.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/clone-4b80996c.js +1 -0
  21. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-1c669cad.js → createText-2e5e7dd3-cf934643.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-b1553799.js → edges-e0da2a9e-8fdf9155.js} +1 -1
  23. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-112388d6.js → erDiagram-9861fffd-6106fb96.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-fdebec47.js → flowDb-956e92f1-4c2bb040.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-6280ede1.js → flowDiagram-66a62f08-f0ff96af.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-16f09b7a.js +1 -0
  27. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-e1dc03e5.js → flowchart-elk-definition-4a651766-a21707ec.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-83f68c51.js → ganttDiagram-c361ad54-c165acb1.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-22f8666f.js → gitGraphDiagram-72cf32ee-b0564cf1.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{graph-ca9e6217.js → graph-e557e67a.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{index-3862675e-c5ceb692.js → index-3862675e-1ce60e9e.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{index-3e293924.js → index-996fe816.js} +173 -173
  33. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-faa9999b.js → infoDiagram-f8f76790-893569e2.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-c4dda8d9.js → journeyDiagram-49397b02-c29c864f.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{layout-d4307784.js → layout-649a5eae.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{line-0567aaa7.js → line-0e5685ed.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{linear-c11b95cf.js → linear-eaa320bd.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-0c7d3ca9.js → mindmap-definition-fc14e90a-f35df9e6.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-34b433fa.js → pieDiagram-8a3498a8-78339e96.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-4cab816e.js → quadrantDiagram-120e2f19-9b5f2f14.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-8c22fa9e.js → requirementDiagram-deff3bca-d05ddb3a.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-70ce9e8e.js → sankeyDiagram-04a897e0-d9be5dfd.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-fbcd7fc9.js → sequenceDiagram-704730f1-0f1c4348.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-45f05ea6.js → stateDiagram-587899a1-9ddf63b3.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-beab1ea6.js → stateDiagram-v2-d93cdb3a-bc2b81ed.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-2f29dbd5.js → styles-6aaf32cf-0a287936.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-951eac83.js → styles-9a916d00-e3941990.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-897fbfdd.js → styles-c10674c1-ce4eca24.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-d667fac1.js → svgDrawCommon-08f97a94-d822b1a8.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-e3205144.js → timeline-definition-85554ec2-e144c7a7.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-4abeb0e2.js → xychartDiagram-e933f94c-ab7f4e14.js} +1 -1
  52. rasa/core/channels/inspector/dist/index.html +1 -1
  53. rasa/core/channels/inspector/src/App.tsx +28 -4
  54. rasa/core/channels/inspector/src/components/DialogueAgentStack.tsx +108 -0
  55. rasa/core/channels/inspector/src/components/{DialogueStack.tsx → DialogueHistoryStack.tsx} +7 -8
  56. rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -0
  57. rasa/core/channels/inspector/src/helpers/utils.test.ts +127 -0
  58. rasa/core/channels/inspector/src/helpers/utils.ts +66 -1
  59. rasa/core/channels/inspector/src/types.ts +17 -0
  60. rasa/core/policies/flows/flow_executor.py +52 -31
  61. rasa/dialogue_understanding/commands/cancel_flow_command.py +2 -81
  62. rasa/dialogue_understanding/commands/start_flow_command.py +18 -113
  63. rasa/dialogue_understanding/commands/utils.py +118 -0
  64. rasa/dialogue_understanding/patterns/clarify.py +3 -14
  65. rasa/dialogue_understanding/patterns/continue_interrupted.py +185 -114
  66. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +17 -23
  67. rasa/dialogue_understanding/stack/utils.py +43 -4
  68. rasa/dialogue_understanding/utils.py +24 -4
  69. rasa/model_training.py +8 -6
  70. rasa/shared/constants.py +3 -0
  71. rasa/shared/core/constants.py +5 -6
  72. rasa/shared/utils/health_check/health_check.py +7 -3
  73. rasa/shared/utils/mcp/server_connection.py +26 -6
  74. rasa/version.py +1 -1
  75. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/METADATA +1 -1
  76. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/RECORD +79 -76
  77. rasa/core/channels/inspector/dist/assets/channel-0cd70adf.js +0 -1
  78. rasa/core/channels/inspector/dist/assets/clone-a0f9c4ed.js +0 -1
  79. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-de9cc4aa.js +0 -1
  80. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/NOTICE +0 -0
  81. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/WHEEL +0 -0
  82. {rasa_pro-3.14.0.dev6.dist-info → rasa_pro-3.14.0.dev7.dist-info}/entry_points.txt +0 -0
@@ -9,28 +9,30 @@ import { useEffect, useState, useCallback } from 'react'
9
9
  import axios from 'axios'
10
10
  import { useOurTheme } from './theme'
11
11
  import { Welcome } from './components/Welcome'
12
- import { DialogueStack } from './components/DialogueStack'
12
+ import { DialogueHistoryStack } from './components/DialogueHistoryStack.tsx'
13
13
  import { DialougeInformation } from './components/DialogueInformation'
14
14
  import { LoadingSpinner } from './components/LoadingSpinner'
15
15
  import { DiagramFlow } from './components/DiagramFlow'
16
16
  import { RecruitmentPanel } from './components/RecruitmentPanel'
17
17
  import { formatSlots } from './helpers/formatters'
18
- import { Slot, Stack, Event, Flow, SelectedStack, Tracker } from './types'
18
+ import {Slot, Stack, Event, Flow, SelectedStack, Tracker, Agent} from './types'
19
19
  import {
20
20
  createHistoricalStack,
21
- flowStepTrail,
21
+ flowStepTrail, updateAgentsStatus,
22
22
  updatedActiveFrame,
23
23
  } from './helpers/utils'
24
24
  import queryString from 'query-string'
25
25
  import { Chat } from './components/Chat'
26
26
  import { LatencyDisplay } from './components/LatencyDisplay'
27
27
  import useWebSocket, { ReadyState } from 'react-use-websocket'
28
+ import {DialogueAgentStack} from "./components/DialogueAgentStack.tsx";
28
29
 
29
30
  export function App() {
30
31
  const toast = useToast()
31
32
  const { rasaSpace, rasaRadii } = useOurTheme()
32
33
  const [rasaChatSessionId, setRasaChatSessionId] = useState<string>('')
33
34
  const [flows, setFlows] = useState<Flow[]>([])
35
+ const [agents, setAgents] = useState<Agent[]>([])
34
36
  const [slots, setSlots] = useState<Slot[]>([])
35
37
  const [events, setEvents] = useState<Event[]>([])
36
38
  const [story, setStory] = useState<string>('')
@@ -102,6 +104,23 @@ export function App() {
102
104
  })
103
105
  }, [toast])
104
106
 
107
+ useEffect(() => {
108
+ axios
109
+ .get('/sub-agents', { params: { token } })
110
+ .then((response) => setAgents((response.data)))
111
+ .catch((error) => {
112
+ if (toast.isActive('agents')) return
113
+ toast({
114
+ id: 'agents',
115
+ title: 'Agents could not be retrieved',
116
+ description: error?.message || 'An unknown error happened.',
117
+ status: 'error',
118
+ duration: 4000,
119
+ isClosable: true,
120
+ })
121
+ })
122
+ }, [toast])
123
+
105
124
  function fetchStory() {
106
125
  axios
107
126
  .get(`/conversations/${rasaChatSessionId}/story`, { params: { token } })
@@ -142,6 +161,7 @@ export function App() {
142
161
  lastJsonMessage.stack,
143
162
  lastJsonMessage.events,
144
163
  )
164
+ setAgents(prevAgents => updateAgentsStatus(prevAgents, lastJsonMessage.events))
145
165
  setStack(updatedStack)
146
166
  setFrame(updatedActiveFrame(frame, updatedStack, lastJsonMessage.events))
147
167
  setRasaChatSessionId(lastJsonMessage.sender_id)
@@ -240,12 +260,16 @@ export function App() {
240
260
  {showRecruitmentPanel && (
241
261
  <RecruitmentPanel onClose={handleCloseRecruitmentPanel} />
242
262
  )}
243
- <DialogueStack
263
+ <DialogueHistoryStack
244
264
  sx={boxSx}
245
265
  stack={stack}
246
266
  active={frame?.stack}
247
267
  onItemClick={onFrameSelected}
248
268
  />
269
+ {agents.length > 0 ? (<DialogueAgentStack
270
+ sx={boxSx}
271
+ agents={agents}
272
+ />):null}
249
273
  <DialougeInformation
250
274
  sx={boxSx}
251
275
  rasaChatSessionId={rasaChatSessionId}
@@ -0,0 +1,108 @@
1
+ import {
2
+ Box,
3
+ FlexProps,
4
+ Heading,
5
+ Table,
6
+ Thead,
7
+ Tbody,
8
+ Tr,
9
+ Th,
10
+ Td,
11
+ Text,
12
+ Flex,
13
+ } from '@chakra-ui/react'
14
+ import { useOurTheme } from '../theme'
15
+ import {Agent, Stack} from '../types'
16
+
17
+ function mapStatusToHumanReadableName(status: Agent['status']) {
18
+ switch (status) {
19
+ case 'running':
20
+ return 'Running'
21
+ case 'completed':
22
+ return 'Completed'
23
+ case 'interrupted':
24
+ return 'Interrupted'
25
+ case 'cancelled':
26
+ return 'Cancelled'
27
+ default:
28
+ return '-'
29
+ }
30
+ }
31
+
32
+ interface Props extends FlexProps {
33
+ agents: Agent[]
34
+ active?: Stack
35
+ onItemClick?: (stack: Stack) => void
36
+ }
37
+
38
+ function AgentRow({
39
+ agent,
40
+ }: {
41
+ agent: Agent
42
+ }) {
43
+
44
+ return (
45
+ <Tr>
46
+ <Td>
47
+ <Text noOfLines={1}>{agent.name}</Text>
48
+ </Td>
49
+ <Td>
50
+ <Text noOfLines={1}>{mapStatusToHumanReadableName(agent.status)}</Text>
51
+ </Td>
52
+ </Tr>
53
+ )
54
+ }
55
+
56
+ export const DialogueAgentStack = ({
57
+ sx,
58
+ agents,
59
+ active,
60
+ onItemClick,
61
+ ...props
62
+ }: Props) => {
63
+ const { rasaSpace } = useOurTheme()
64
+
65
+ const containerSx = {
66
+ ...sx,
67
+ pr: 0,
68
+ pb: 0,
69
+ flexDirection: 'column',
70
+ }
71
+ const overflowBox = {
72
+ height: '100%',
73
+ overflow: 'auto',
74
+ pr: rasaSpace[1],
75
+ pb: rasaSpace[0.5],
76
+ }
77
+
78
+ return (
79
+ <Flex sx={containerSx} {...props}>
80
+ <Flex>
81
+ <Heading size="lg" mb={rasaSpace[0.5]}>
82
+ Agents
83
+ </Heading>
84
+ <Text ml={rasaSpace[0.25]}>({agents.length} {agents.length === 1 ? 'agent' : 'agents'})</Text>
85
+ </Flex>
86
+ <Box sx={overflowBox}>
87
+ <Table width="100%" layout="fixed">
88
+ <Thead>
89
+ <Tr>
90
+ <Th>Name</Th>
91
+ <Th width="40%">Status</Th>
92
+ </Tr>
93
+ </Thead>
94
+ <Tbody>
95
+ {agents.length > 0 &&
96
+ [...agents]
97
+ .reverse()
98
+ .map((agent) => (
99
+ <AgentRow
100
+ agent={agent}
101
+ />
102
+ ))}
103
+ </Tbody>
104
+ </Table>
105
+ </Box>
106
+ </Flex>
107
+ )
108
+ }
@@ -52,8 +52,6 @@ function StackRow({
52
52
  },
53
53
  }
54
54
 
55
- const agentOrStep = stack.agent_id ?? stack.step_id;
56
-
57
55
  return (
58
56
  <Tr
59
57
  sx={
@@ -67,19 +65,19 @@ function StackRow({
67
65
  </Tooltip>
68
66
  </Td>
69
67
  <Td>
70
- {shouldShowTooltip(agentOrStep) ? (
71
- <Tooltip label={`${agentOrStep} ${stack.agent_id && `(${stack.step_id})`}`} hasArrow>
72
- <Text noOfLines={1}>{agentOrStep}</Text>
68
+ {shouldShowTooltip(stack.step_id) ? (
69
+ <Tooltip label={stack.step_id} hasArrow>
70
+ <Text noOfLines={1}>{stack.step_id}</Text>
73
71
  </Tooltip>
74
72
  ) : (
75
- <Text noOfLines={1}>{agentOrStep}</Text>
73
+ <Text noOfLines={1}>{stack.step_id}</Text>
76
74
  )}
77
75
  </Td>
78
76
  </Tr>
79
77
  )
80
78
  }
81
79
 
82
- export const DialogueStack = ({
80
+ export const DialogueHistoryStack = ({
83
81
  sx,
84
82
  stack,
85
83
  active,
@@ -107,7 +105,7 @@ export const DialogueStack = ({
107
105
  <Heading size="lg" mb={rasaSpace[0.5]}>
108
106
  History
109
107
  </Heading>
110
- <Text ml={rasaSpace[0.25]}>({stack.length} flows)</Text>
108
+ <Text ml={rasaSpace[0.25]}>({stack.length} {stack.length === 1 ? 'flow' : 'flows'})</Text>
111
109
  </Flex>
112
110
  <Box sx={overflowBox}>
113
111
  <Table width="100%" layout="fixed">
@@ -136,6 +134,7 @@ export const DialogueStack = ({
136
134
  flow_id: '-',
137
135
  step_id: '-',
138
136
  ended: false,
137
+ type: "flow"
139
138
  }}
140
139
  />
141
140
  )}
@@ -294,6 +294,7 @@ describe('helpers', () => {
294
294
  step_id: 'step_id',
295
295
  collect: fieldValue,
296
296
  ended: false,
297
+ type: 'flow',
297
298
  }),
298
299
  ).toEqual(fieldValue)
299
300
  })
@@ -307,6 +308,7 @@ describe('helpers', () => {
307
308
  flow_id: 'flow_id',
308
309
  step_id: 'step_id',
309
310
  ended: false,
311
+ type: 'flow',
310
312
  }),
311
313
  ).toEqual(fieldValue)
312
314
  })
@@ -321,6 +323,7 @@ describe('helpers', () => {
321
323
  step_id: 'step_id',
322
324
  collect: fieldValue,
323
325
  ended: false,
326
+ type: 'flow',
324
327
  }),
325
328
  ).toEqual(`${fieldValue} is not null`)
326
329
  })
@@ -335,6 +338,7 @@ describe('helpers', () => {
335
338
  step_id: 'step_id',
336
339
  collect: fieldValue,
337
340
  ended: false,
341
+ type: 'flow',
338
342
  }),
339
343
  ).toEqual(`not ${fieldValue}`)
340
344
  })
@@ -0,0 +1,127 @@
1
+ import { updateAgentsStatus } from './utils'
2
+ import type { Agent, Event } from '../types'
3
+
4
+ // Helper to clone agents (immutability check)
5
+ const clone = <T>(x: T): T => JSON.parse(JSON.stringify(x))
6
+
7
+ describe('updateAgentsStatus', () => {
8
+ test('returns empty array when agents undefined', () => {
9
+ // @ts-expect-error testing runtime behavior with undefined
10
+ expect(updateAgentsStatus(undefined, [])).toEqual([])
11
+ })
12
+
13
+ test('returns same agents when agents empty', () => {
14
+ const agents: Agent[] = []
15
+ const events: Event[] = [
16
+ { event: 'agent_started', timestamp: '1' } as any,
17
+ ]
18
+ expect(updateAgentsStatus(agents, events)).toEqual([])
19
+ })
20
+
21
+ test('returns same agents when events empty', () => {
22
+ const agents: Agent[] = [
23
+ { name: 'alpha', status: 'completed' },
24
+ ]
25
+ const events: Event[] = []
26
+ expect(updateAgentsStatus(clone(agents), events)).toEqual(agents)
27
+ })
28
+
29
+ test('ignores non-agent events', () => {
30
+ const agents: Agent[] = [
31
+ { name: 'alpha', status: 'completed' },
32
+ ]
33
+ const events: Event[] = [
34
+ { event: 'user', text: 'hi', timestamp: '1' } as any,
35
+ { event: 'bot', text: 'hey', timestamp: '2' } as any,
36
+ ]
37
+ expect(updateAgentsStatus(clone(agents), events)).toEqual(agents)
38
+ })
39
+
40
+ test('uses latest event by timestamp per agent', () => {
41
+ const agents: Agent[] = [
42
+ { name: 'alpha', status: 'completed' },
43
+ { name: 'beta', status: 'completed' },
44
+ ]
45
+
46
+ const events: Event[] = [
47
+ { event: 'agent_started', agent_id: 'alpha', timestamp: 1 } as any,
48
+ { event: 'agent_interrupted', agent_id: 'alpha', timestamp: 2 } as any,
49
+ { event: 'agent_resumed', agent_id: 'alpha', timestamp: 3 } as any,
50
+ { event: 'agent_cancelled', agent_id: 'beta', timestamp: 5 } as any,
51
+ ]
52
+
53
+ const result = updateAgentsStatus(clone(agents), events)
54
+ expect(result).toEqual([
55
+ { name: 'alpha', status: 'running' },
56
+ { name: 'beta', status: 'cancelled' },
57
+ ])
58
+ })
59
+
60
+ test('later event wins when timestamps missing', () => {
61
+ const agents: Agent[] = [
62
+ { name: 'alpha', status: 'completed' },
63
+ ]
64
+
65
+ const events: Event[] = [
66
+ { event: 'agent_started', agent_id: 'alpha', timestamp: null } as any,
67
+ { event: 'agent_interrupted', agent_id: 'alpha', timestamp: null } as any,
68
+ ]
69
+
70
+ const result = updateAgentsStatus(clone(agents), events)
71
+ expect(result).toEqual([
72
+ { name: 'alpha', status: 'interrupted' },
73
+ ])
74
+ })
75
+
76
+ test('agent_completed maps to provided status and falls back to completed', () => {
77
+ const agents: Agent[] = [
78
+ { name: 'alpha', status: 'running' },
79
+ { name: 'beta', status: 'running' },
80
+ ]
81
+
82
+ const events: Event[] = [
83
+ { event: 'agent_completed', agent_id: 'alpha', status: 'completed', timestamp: 10 } as any,
84
+ { event: 'agent_completed', agent_id: 'beta', timestamp: 10 } as any,
85
+ ]
86
+
87
+ const result = updateAgentsStatus(clone(agents), events)
88
+ expect(result).toEqual([
89
+ { name: 'alpha', status: 'completed' },
90
+ { name: 'beta', status: 'completed' },
91
+ ])
92
+ })
93
+
94
+ test('unmatched agents remain unchanged', () => {
95
+ const agents: Agent[] = [
96
+ { name: 'alpha', status: 'running' },
97
+ { name: 'gamma', status: 'interrupted' },
98
+ ]
99
+
100
+ const events: Event[] = [
101
+ { event: 'agent_resumed', agent_id: 'alpha', timestamp: 1 } as any,
102
+ { event: 'agent_cancelled', agent_id: 'beta', timestamp: 2 } as any, // beta not in agents list
103
+ ]
104
+
105
+ const result = updateAgentsStatus(clone(agents), events)
106
+ expect(result).toEqual([
107
+ { name: 'alpha', status: 'running' },
108
+ { name: 'gamma', status: 'interrupted' },
109
+ ])
110
+ })
111
+
112
+ test('handles mixed timestamp types (string/number)', () => {
113
+ const agents: Agent[] = [
114
+ { name: 'alpha', status: 'completed' },
115
+ ]
116
+
117
+ const events: Event[] = [
118
+ { event: 'agent_started', agent_id: 'alpha', timestamp: '1' } as any,
119
+ { event: 'agent_interrupted', agent_id: 'alpha', timestamp: 2 } as any,
120
+ ]
121
+
122
+ const result = updateAgentsStatus(clone(agents), events)
123
+ expect(result).toEqual([
124
+ { name: 'alpha', status: 'interrupted' },
125
+ ])
126
+ })
127
+ })
@@ -1,4 +1,4 @@
1
- import { SelectedStack, Stack, Event } from '../types'
1
+ import {SelectedStack, Stack, Event, Agent, AGENT_EVENT_TYPES} from '../types'
2
2
  import { immutableJSONPatch } from 'immutable-json-patch'
3
3
 
4
4
  export const shouldShowTooltip = (text: string) => {
@@ -146,3 +146,68 @@ export const updatedActiveFrame = (
146
146
  return previous
147
147
  }
148
148
  }
149
+
150
+ export function updateAgentsStatus(agents: Agent[], events: Event[]): Agent[] {
151
+ if (!agents || agents.length === 0 || !events || events.length === 0) {
152
+ return agents || []
153
+ }
154
+
155
+ const agentEventTypes = new Set<Event['event']>(AGENT_EVENT_TYPES as unknown as Event['event'][] )
156
+
157
+ type AgentEventLite = Agent & {
158
+ agent_id: string
159
+ event: Event['event']
160
+ timestamp?: string | number | null
161
+ }
162
+
163
+ const latestByAgent = new Map<string, AgentEventLite>()
164
+
165
+ for (let i = 0; i < events.length; i++) {
166
+ const event = events[i] as unknown as AgentEventLite
167
+ if (!event || !agentEventTypes.has(event.event)) continue
168
+
169
+ const agentId = event.agent_id
170
+ if (!agentId) continue
171
+
172
+ const previousEvent = latestByAgent.get(agentId)
173
+
174
+ const currentTimestamp = event.timestamp == null ? undefined : Number(event.timestamp)
175
+ const previousTimestamp = previousEvent?.timestamp == null ? undefined : Number(previousEvent.timestamp)
176
+
177
+ const isNewer =
178
+ previousEvent == null ||
179
+ (currentTimestamp != null && previousTimestamp != null && currentTimestamp > previousTimestamp) ||
180
+ (currentTimestamp != null && previousTimestamp == null)
181
+ // If both timestamps are missing, prefer later in the array (current)
182
+
183
+ if (isNewer || (currentTimestamp == null && previousTimestamp == null)) {
184
+ latestByAgent.set(agentId, event)
185
+ }
186
+ }
187
+
188
+ return agents.map((agent) => {
189
+ const latest = latestByAgent.get(agent.name)
190
+ if (!latest) return agent
191
+
192
+ let status = agent.status
193
+ switch (latest.event) {
194
+ case 'agent_started':
195
+ case 'agent_resumed':
196
+ status = 'running'
197
+ break
198
+ case 'agent_interrupted':
199
+ status = 'interrupted'
200
+ break
201
+ case 'agent_cancelled':
202
+ status = 'cancelled'
203
+ break
204
+ case 'agent_completed':
205
+ status = latest.status || 'completed'
206
+ break
207
+ default:
208
+ break
209
+ }
210
+
211
+ return { ...agent, status }
212
+ })
213
+ }
@@ -4,6 +4,16 @@ export interface Slot {
4
4
  value: any
5
5
  }
6
6
 
7
+ export const AGENT_EVENT_TYPES = [
8
+ 'agent_started',
9
+ 'agent_completed',
10
+ 'agent_interrupted',
11
+ 'agent_resumed',
12
+ 'agent_cancelled',
13
+ ] as const
14
+
15
+ export type AgentEvents = typeof AGENT_EVENT_TYPES[number];
16
+
7
17
  export interface Event {
8
18
  event:
9
19
  | 'user'
@@ -13,6 +23,7 @@ export interface Event {
13
23
  | 'stack'
14
24
  | 'restart'
15
25
  | 'session_ended'
26
+ | AgentEvents
16
27
  text?: string
17
28
  timestamp: string
18
29
  update?: string
@@ -40,6 +51,7 @@ export interface Stack {
40
51
  collect?: string
41
52
  utter?: string
42
53
  ended: boolean
54
+ type: "flow" | "agent"
43
55
  agent_id?: string
44
56
  state?: "waiting_for_input" | "interrupted"
45
57
  }
@@ -66,6 +78,11 @@ export interface Flow {
66
78
  steps: Step[]
67
79
  }
68
80
 
81
+ export interface Agent {
82
+ name: string
83
+ status: "running" | "completed" | "interrupted" | "cancelled"
84
+ }
85
+
69
86
  interface NextStepThen {
70
87
  action: string
71
88
  id: string
@@ -65,7 +65,10 @@ from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
65
65
  AgentState,
66
66
  FlowStackFrameType,
67
67
  )
68
- from rasa.dialogue_understanding.stack.utils import top_user_flow_frame
68
+ from rasa.dialogue_understanding.stack.utils import (
69
+ user_frames_on_the_stack,
70
+ )
71
+ from rasa.dialogue_understanding.utils import assemble_options_string
69
72
  from rasa.shared.agents.utils import get_protocol_type
70
73
  from rasa.shared.constants import RASA_PATTERN_HUMAN_HANDOFF
71
74
  from rasa.shared.core.constants import (
@@ -263,38 +266,58 @@ def trigger_pattern_continue_interrupted(
263
266
  stack: DialogueStack,
264
267
  flows: FlowsList,
265
268
  tracker: DialogueStateTracker,
266
- ) -> List[Event]:
269
+ ) -> None:
267
270
  """Trigger the pattern to continue an interrupted flow if needed."""
268
- events: List[Event] = []
269
-
270
- # get previously started user flow that will be continued
271
- interrupted_user_flow_frame = top_user_flow_frame(stack)
272
- interrupted_user_flow_step = (
273
- interrupted_user_flow_frame.step(flows) if interrupted_user_flow_frame else None
274
- )
275
- interrupted_user_flow = (
276
- interrupted_user_flow_frame.flow(flows) if interrupted_user_flow_frame else None
277
- )
278
-
271
+ # only trigger the pattern if the current frame is a user flow frame
272
+ # with a frame type of interrupt
279
273
  if (
280
- isinstance(current_frame, UserFlowStackFrame)
281
- and interrupted_user_flow_step is not None
282
- and interrupted_user_flow is not None
283
- and current_frame.frame_type == FlowStackFrameType.INTERRUPT
284
- and not is_step_end_of_flow(interrupted_user_flow_step)
274
+ not isinstance(current_frame, UserFlowStackFrame)
275
+ or current_frame.frame_type != FlowStackFrameType.INTERRUPT
285
276
  ):
286
- stack.push(
287
- ContinueInterruptedPatternFlowStackFrame(
288
- previous_flow_name=interrupted_user_flow.readable_name(
289
- language=tracker.current_language
290
- ),
291
- )
277
+ return None
278
+
279
+ # get all previously interrupted user flows
280
+ interrupted_user_flow_stack_frames = user_frames_on_the_stack(stack)
281
+
282
+ interrupted_user_flows_to_continue: List[UserFlowStackFrame] = []
283
+ # check if interrupted user flows can be continued
284
+ # i.e. the flow is not at the end of the flow
285
+ for frame in interrupted_user_flow_stack_frames:
286
+ interrupted_user_flow_step = frame.step(flows)
287
+ interrupted_user_flow = frame.flow(flows)
288
+ if (
289
+ interrupted_user_flow_step is not None
290
+ and interrupted_user_flow is not None
291
+ and not is_step_end_of_flow(interrupted_user_flow_step)
292
+ ):
293
+ interrupted_user_flows_to_continue.append(frame)
294
+
295
+ # if there are no interrupted user flows to continue,
296
+ # we don't need to trigger the pattern
297
+ if len(interrupted_user_flows_to_continue) == 0:
298
+ return None
299
+
300
+ # get the flow names and ids of the interrupted flows
301
+ # and assemble the options string
302
+ flow_names: List[str] = []
303
+ flow_ids: List[str] = []
304
+ for frame in interrupted_user_flows_to_continue:
305
+ flow_names.append(
306
+ frame.flow(flows).readable_name(language=tracker.current_language)
292
307
  )
293
- events.append(
294
- FlowResumed(interrupted_user_flow.id, interrupted_user_flow_step.id)
308
+ flow_ids.append(frame.flow_id)
309
+ options_string = assemble_options_string(flow_names)
310
+
311
+ # trigger the pattern to continue the interrupted flows
312
+ stack.push(
313
+ ContinueInterruptedPatternFlowStackFrame(
314
+ interrupted_flow_names=flow_names,
315
+ interrupted_flow_ids=flow_ids,
316
+ interrupted_flow_options=options_string,
295
317
  )
318
+ )
296
319
 
297
- return events
320
+ return None
298
321
 
299
322
 
300
323
  def trigger_pattern_completed(
@@ -707,12 +730,10 @@ def _run_end_step(
707
730
  structlogger.debug("flow.step.run.flow_end")
708
731
  current_frame = stack.pop()
709
732
  trigger_pattern_completed(current_frame, stack, flows)
710
- resumed_events = trigger_pattern_continue_interrupted(
711
- current_frame, stack, flows, tracker
712
- )
733
+ trigger_pattern_continue_interrupted(current_frame, stack, flows, tracker)
713
734
  reset_events: List[Event] = reset_scoped_slots(current_frame, flow, tracker)
714
735
  return ContinueFlowWithNextStep(
715
- events=initial_events + reset_events + resumed_events, has_flow_ended=True
736
+ events=initial_events + reset_events, has_flow_ended=True
716
737
  )
717
738
 
718
739