cancel intent. The chat starts with your question (“Sorry to see you go. What’s the main reason?”) and quick replies, the AI follows a task you wrote for it, and a Cancel anyway button stays on screen the whole time. One click on it closes the chat and hands back to your app.
It takes two pieces: a call in your app, and a task on the desk that defines the intent.
1. Define the intent in a task
An intent lives in a task’s frontmatter (tasks/<name>.md in your agent config). Here is the cancellation starter that jun init creates:
tasks/cancellation.md
opening, replies and exit only work with intent. The desk’s config check tells you if one is missing or too long.
The task’s body is the procedure. The AI is told the chat was opened with this intent, to follow the procedure step by step, to make at most one offer and only one the procedure (or an action) defines, and to wait for a clear yes before calling anything that changes the customer’s account. With an exit, it’s also told never to discourage, delay, argue against or obstruct that button, and to point to it when the customer still wants to go ahead.
The starter also includes actions/apply_save_offer.yaml.example. Copy it to actions/apply_save_offer.yaml and point it at your billing API to let the AI apply an accepted offer. It acts on the signed-in customer through {user.id}, so it needs identified users.
You can also create and edit tasks on the desk under Agent → Tasks (Tasks).
2. Open the chat from your app
string
required
The intent’s name, matching a task’s
intent:. A name that isn’t lowercase letters, digits, _ or - (up to 40 characters) opens a plain chat.function
Called once when the visitor clicks the exit button, after the chat closes. This is where your app carries on: show your own cancel screen, call your cancel endpoint. Required when the intent has an
exit.The exit button
This is a hard rule in the widget: a flow with an exit is never a trap.- The exit button is always on screen, outside the scrolling chat, for the whole conversation.
- It’s one click. There’s no “are you sure” step from Jun.
- The click closes the chat and calls your
onExitright away. It never waits on the desk. - If the intent has an
exitbutJunDesk.open()got noonExitfunction, the widget opens a plain chat instead and logs a console warning:intent "cancel" has an exit ("Cancel anyway") but JunDesk.open() got no onExit function: opening a plain chat.
The customer clicked "Cancel anyway" (intent: cancel) and went on in your app.
After a page reload the intent chat comes back with its exit button still showing. Your onExit function can’t survive a reload, so a click then only closes the chat (the exit is still recorded).
What visitors see
1
Your app calls JunDesk.open({ intent })
The widget fetches the intent’s opening, replies and exit from the desk. No AI call is made for this.
2
The chat opens on the opening question
The quick replies sit under it, and the exit button sits outside the scrolling messages, where it stays.
3
The visitor answers
By tapping a reply or typing. The conversation starts, tagged with the intent, and the AI follows the task.
4
The visitor stays or leaves
If the AI solves the problem or the offer lands, the chat ends like any other. If not, one click on the exit hands back to your app.
- Calling
JunDesk.open({ intent })again while that intent’s chat is on screen carries on in it. Otherwise it starts a new chat, so an intent never mixes into an unrelated conversation. - An intent that no task defines opens a plain chat, still tagged with the intent. The AI is told about it and just helps.
- With the island, an intent chat never folds itself away, and the island’s × isn’t offered: the chat ends through its exit.
For your team
Intent chats show an Intent: cancel tag in the inbox, with ”· exited” once the visitor clicked the exit. Everything else works as usual: agents can take over, and handoff rules apply.Test it
Eval cases takeintent:, so jun eval runs a case as a chat opened with that intent:
tests/cancellation.yaml
cancel intent.
Tasks
Write the procedure the AI follows.
JavaScript API
JunDesk.open and the rest.