Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bots/bugbuster/src/handlers/message-created.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ export const handleMessageCreated: bp.MessageHandlers['*'] = async (props) => {
props.logger.info(`Ignoring message from ${conversation.integration}`)
return
}
if (conversation.integration === 'slack' && conversation.channel === 'channel') {
if (
conversation.integration === 'slack' &&
(conversation.channel === 'channel' || conversation.channel === 'thread')
) {
return
}

Expand Down
3 changes: 3 additions & 0 deletions integrations/notion/definitions/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ export const secrets = {
WEBHOOK_VERIFICATION_SECRET: {
description: 'The Notion-provided secret for verifying incoming webhooks.',
},
POSTHOG_KEY: {
description: 'The Posthog key of the Botpress Notion Integration.',
},
} as const satisfies sdk.IntegrationDefinitionProps['secrets']
9 changes: 6 additions & 3 deletions integrations/notion/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import * as sdk from '@botpress/sdk'
import filesReadonly from './bp_modules/files-readonly'
import { actions, configuration, configurations, identifier, secrets, states, user } from './definitions'

export const INTEGRATION_NAME = 'notion'
export const INTEGRATION_VERSION = '2.2.1'

export default new sdk.IntegrationDefinition({
name: 'notion',
description: 'Add pages and comments, manage databases, and engage in discussions — all within your chatbot.',
name: INTEGRATION_NAME,
version: INTEGRATION_VERSION,
title: 'Notion',
version: '2.2.0',
description: 'Add pages and comments, manage databases, and engage in discussions — all within your chatbot.',
icon: 'icon.svg',
readme: 'hub.md',
actions,
Expand Down
30 changes: 18 additions & 12 deletions integrations/notion/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { sentry as sentryHelpers } from '@botpress/sdk-addons'
import { posthogHelper } from '@botpress/common'
import { INTEGRATION_NAME, INTEGRATION_VERSION } from 'integration.definition'
import { actions } from './actions'
import { register, unregister } from './setup'
import { handler } from './webhook-events'
import * as bp from '.botpress'

const integration = new bp.Integration({
channels: {},
register,
unregister,
actions,
handler,
@posthogHelper.wrapIntegration({
integrationName: INTEGRATION_NAME,
integrationVersion: INTEGRATION_VERSION,
key: bp.secrets.POSTHOG_KEY,
})
class NotionIntegration extends bp.Integration {
public constructor() {
super({
register,
unregister,
actions,
channels: {},
handler,
})
}
}

export default sentryHelpers.wrapIntegration(integration, {
dsn: bp.secrets.SENTRY_DSN,
environment: bp.secrets.SENTRY_ENVIRONMENT,
release: bp.secrets.SENTRY_RELEASE,
})
export default new NotionIntegration()
13 changes: 11 additions & 2 deletions integrations/notion/src/setup.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { RuntimeError } from '@botpress/sdk'
import { NotionClient } from './notion-api'
import * as bp from '.botpress'

export const register: bp.IntegrationProps['register'] = async (props) => {
const notionClient = await NotionClient.create(props)
await notionClient.testAuthentication()
await notionClient.testAuthentication().catch((thrown) => {
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
throw new RuntimeError(`Failed to test authentication: ${error.message}`)
})
}

export const unregister: bp.IntegrationProps['unregister'] = async () => {}
export const unregister: bp.IntegrationProps['unregister'] = async (props) => {
const { client } = props
await client.configureIntegration({
identifier: null,
})
}
15 changes: 14 additions & 1 deletion packages/common/src/posthog/helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as client from '@botpress/client'
import * as sdk from '@botpress/sdk'

import { EventMessage, PostHog } from 'posthog-node'

export const COMMON_SECRET_NAMES = {
Expand Down Expand Up @@ -78,8 +79,12 @@ function wrapFunction(fn: Function, config: PostHogConfig) {
return await fn(...args)
} catch (thrown) {
const errMsg = thrown instanceof Error ? thrown.message : String(thrown)

const distinctId = client.isApiError(thrown) ? thrown.id : undefined
const additionalProps = {
configurationType: args[0]?.ctx?.configurationType,
integrationId: args[0]?.ctx?.integrationId,
}

await sendPosthogEvent(
{
distinctId: distinctId ?? 'no id',
Expand All @@ -89,6 +94,7 @@ function wrapFunction(fn: Function, config: PostHogConfig) {
integrationName: config.integrationName,
integrationVersion: config.integrationVersion,
errMsg,
...additionalProps,
},
},
config
Expand All @@ -104,6 +110,11 @@ function wrapHandler(fn: Function, config: PostHogConfig) {
return async (...args: any[]) => {
const resp: void | Response = await fn(...args)
if (resp instanceof Response && isServerErrorStatus(resp.status)) {
const additionalProps = {
configurationType: args[0]?.ctx?.configurationType,
integrationId: args[0]?.ctx?.integrationId,
}

if (!resp.body) {
await sendPosthogEvent(
{
Expand All @@ -114,6 +125,7 @@ function wrapHandler(fn: Function, config: PostHogConfig) {
integrationName: config.integrationName,
integrationVersion: config.integrationVersion,
errMsg: 'Empty Body',
...additionalProps,
},
},
config
Expand All @@ -129,6 +141,7 @@ function wrapHandler(fn: Function, config: PostHogConfig) {
integrationName: config.integrationName,
integrationVersion: config.integrationVersion,
errMsg: JSON.stringify(resp.body),
...additionalProps,
},
},
config
Expand Down
42 changes: 26 additions & 16 deletions plugins/hitl/plugin.definition.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as sdk from '@botpress/sdk'
import hitl from './bp_modules/hitl'

export const NULL_MESSAGE_CODE = 'NULL'
export const DEFAULT_HITL_HANDOFF_MESSAGE =
'I have escalated this conversation to a human agent. They should be with you shortly.'
export const DEFAULT_HUMAN_AGENT_ASSIGNED_MESSAGE = 'A human agent has joined the conversation.'
Expand All @@ -14,59 +15,68 @@ export const DEFAULT_USER_HITL_COMMAND_MESSAGE =
export const DEFAULT_AGENT_ASSIGNED_TIMEOUT_MESSAGE =
'No human agent is available at the moment. Please try again later. I will continue assisting you for the time being.'

const _nullCodeDescription = `Use "${NULL_MESSAGE_CODE}" to indicate that no message should be sent.`
const PLUGIN_CONFIG_SCHEMA = sdk.z.object({
onHitlHandoffMessage: sdk.z
.string()
.title('Escalation Started Message')
.describe('The message to send to the user when transferring to a human agent')
.describe(`The message to send to the user when transferring to a human agent. ${_nullCodeDescription}`)
.optional()
.placeholder(DEFAULT_HITL_HANDOFF_MESSAGE),
onHumanAgentAssignedMessage: sdk.z
.string()
.title('Human Agent Assigned Message')
.describe('The message to send to the user when a human agent is assigned')
.describe(`The message to send to the user when a human agent is assigned. ${_nullCodeDescription}`)
.optional()
.placeholder(DEFAULT_HUMAN_AGENT_ASSIGNED_MESSAGE),
onHitlStoppedMessage: sdk.z
.string()
.title('Escalation Terminated Message')
.describe('The message to send to the user when the HITL session stops and control is transferred back to bot')
.describe(
`The message to send to the user when the HITL session stops and control is transferred back to bot. ${_nullCodeDescription}`
)
.optional()
.placeholder(DEFAULT_HITL_STOPPED_MESSAGE),
onUserHitlCancelledMessage: sdk.z
.string()
.title('Escalation Aborted Message')
.describe('The message to send to the human agent when the user abruptly ends the HITL session')
.describe(
`The message to send to the human agent when the user abruptly ends the HITL session. ${_nullCodeDescription}`
)
.optional()
.placeholder(DEFAULT_USER_HITL_CANCELLED_MESSAGE),
onIncompatibleMsgTypeMessage: sdk.z
.string()
.title('Incompatible Message Type Warning')
.describe(
'The warning to send to the human agent when they send a message that is not supported by the hitl session'
`The warning to send to the human agent when they send a message that is not supported by the hitl session. ${_nullCodeDescription}`
)
.optional()
.placeholder(DEFAULT_INCOMPATIBLE_MSGTYPE_MESSAGE),
userHitlCloseCommand: sdk.z
.string()
.title('Termination Command')
.describe(
'Users may send this command to end the HITL session at any time. It is case-insensitive, so it works regardless of letter casing.'
)
.optional()
.placeholder(DEFAULT_USER_HITL_CLOSE_COMMAND),
onUserHitlCloseMessage: sdk.z
.string()
.title('Termination Command Message')
.describe('The message to send to the user when they end the HITL session using the termination command')
.describe(
`The message to send to the user when they end the HITL session using the termination command. ${_nullCodeDescription}`
)
.optional()
.placeholder(DEFAULT_USER_HITL_COMMAND_MESSAGE),
onAgentAssignedTimeoutMessage: sdk.z
.string()
.title('Agent Assigned Timeout Message')
.describe('The message to send to the user when no human agent is assigned within the timeout period')
.describe(
`The message to send to the user when no human agent is assigned within the timeout period. ${_nullCodeDescription}`
)
.optional()
.placeholder(DEFAULT_AGENT_ASSIGNED_TIMEOUT_MESSAGE),
userHitlCloseCommand: sdk.z
.string()
.title('Termination Command')
.describe(
'Users may send this command to end the HITL session at any time. It is case-insensitive, so it works regardless of letter casing.'
)
.optional()
.placeholder(DEFAULT_USER_HITL_CLOSE_COMMAND),
agentAssignedTimeoutSeconds: sdk.z
.number()
.title('Agent Assigned Timeout')
Expand All @@ -92,7 +102,7 @@ const PLUGIN_CONFIG_SCHEMA = sdk.z.object({

export default new sdk.PluginDefinition({
name: 'hitl',
version: '1.1.1',
version: '1.2.0',
title: 'Human In The Loop',
description: 'Seamlessly transfer conversations to human agents',
icon: 'icon.svg',
Expand Down
8 changes: 1 addition & 7 deletions plugins/hitl/src/actions/start-hitl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,7 @@ export const startHitl: bp.PluginProps['actions']['startHitl'] = async (props) =
const _sendHandoffMessage = (
upstreamCm: conv.ConversationManager,
sessionConfig: bp.configuration.Configuration
): Promise<void> =>
upstreamCm.respond({
type: 'text',
text: sessionConfig.onHitlHandoffMessage?.length
? sessionConfig.onHitlHandoffMessage
: DEFAULT_HITL_HANDOFF_MESSAGE,
})
): Promise<void> => upstreamCm.maybeRespondText(sessionConfig.onHitlHandoffMessage, DEFAULT_HITL_HANDOFF_MESSAGE)

const _buildMessageHistory = async (
upstreamConversation: types.ActionableConversation,
Expand Down
7 changes: 1 addition & 6 deletions plugins/hitl/src/actions/stop-hitl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,7 @@ export const stopHitl: bp.PluginProps['actions']['stopHitl'] = async (props) =>
upstreamConversationId,
})

await downstreamCm.respond({
type: 'text',
text: sessionConfig.onUserHitlCancelledMessage?.length
? sessionConfig.onUserHitlCancelledMessage
: DEFAULT_USER_HITL_CANCELLED_MESSAGE,
})
await downstreamCm.maybeRespondText(sessionConfig.onUserHitlCancelledMessage, DEFAULT_USER_HITL_CANCELLED_MESSAGE)

await Promise.allSettled([
upstreamCm.setHitlInactive(conv.HITL_END_REASON.CLOSE_ACTION_CALLED),
Expand Down
9 changes: 9 additions & 0 deletions plugins/hitl/src/conv-manager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NULL_MESSAGE_CODE } from 'plugin.definition'
import * as types from './types'
import * as bp from '.botpress'

Expand Down Expand Up @@ -76,6 +77,14 @@ export class ConversationManager {
})
}

public async maybeRespondText(message: string | undefined, defaultMsg: string): Promise<void> {
if (message === NULL_MESSAGE_CODE) {
return
}
const text = message || defaultMsg
await this.respond({ type: 'text', text })
}

public async respond(messagePayload: types.MessagePayload): Promise<void> {
// FIXME: in the future, we should use the provided UserId so that messages
// on Botpress appear to come from the agent/user instead of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,7 @@ export const handleEvent: bp.HookHandlers['before_incoming_event']['hitl:hitlAss
const humanAgentName = humanAgentUser?.name?.length ? humanAgentUser.name : 'A Human Agent'

await Promise.all([
upstreamCm.respond({
type: 'text',
text: sessionConfig.onHumanAgentAssignedMessage?.length
? sessionConfig.onHumanAgentAssignedMessage
: DEFAULT_HUMAN_AGENT_ASSIGNED_MESSAGE,
}),
upstreamCm.maybeRespondText(sessionConfig.onHumanAgentAssignedMessage, DEFAULT_HUMAN_AGENT_ASSIGNED_MESSAGE),
downstreamCm.setHumanAgent(humanAgentUserId, humanAgentName),
upstreamCm.setHumanAgent(humanAgentUserId, humanAgentName),
tryLinkWebchatUser(props, { downstreamUser: humanAgentUser, upstreamConversation, forceLink: true }),
Expand Down
7 changes: 1 addition & 6 deletions plugins/hitl/src/hooks/before-incoming-event/hitl-stopped.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,7 @@ export const handleEvent: bp.HookHandlers['before_incoming_event']['hitl:hitlSto
})

await Promise.allSettled([
upstreamCm.respond({
type: 'text',
text: sessionConfig.onHitlStoppedMessage?.length
? sessionConfig.onHitlStoppedMessage
: DEFAULT_HITL_STOPPED_MESSAGE,
}),
upstreamCm.maybeRespondText(sessionConfig.onHitlStoppedMessage, DEFAULT_HITL_STOPPED_MESSAGE),
downstreamCm.setHitlInactive(conv.HITL_END_REASON.AGENT_CLOSED_TICKET),
upstreamCm.setHitlInactive(conv.HITL_END_REASON.AGENT_CLOSED_TICKET),
])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,7 @@ const _handleTimeout = async (
downstreamCm: conv.ConversationManager,
sessionConfig: bp.configuration.Configuration
) => {
await downstreamCm.respond({
// TODO: We might want to add a custom message for the human agent.
type: 'text',
text: sessionConfig.onUserHitlCancelledMessage?.length
? sessionConfig.onUserHitlCancelledMessage
: DEFAULT_USER_HITL_CANCELLED_MESSAGE,
})
await downstreamCm.maybeRespondText(sessionConfig.onUserHitlCancelledMessage, DEFAULT_USER_HITL_CANCELLED_MESSAGE)

await Promise.allSettled([
upstreamCm.setHitlInactive(conv.HITL_END_REASON.AGENT_ASSIGNMENT_TIMEOUT),
Expand All @@ -100,10 +94,5 @@ const _handleTimeout = async (
// Call stopHitl in the hitl integration (zendesk, etc.):
await props.actions.hitl.stopHitl({ conversationId: downstreamCm.conversationId })

await upstreamCm.respond({
type: 'text',
text: sessionConfig.onAgentAssignedTimeoutMessage?.length
? sessionConfig.onAgentAssignedTimeoutMessage
: DEFAULT_AGENT_ASSIGNED_TIMEOUT_MESSAGE,
})
await upstreamCm.maybeRespondText(sessionConfig.onAgentAssignedTimeoutMessage, DEFAULT_AGENT_ASSIGNED_TIMEOUT_MESSAGE)
}
Loading
Loading