CtrlK
BlogDocsLog inGet started
Tessl Logo

activate_ticket

Open or resume a helpdesk ticket. Tickets are independent of projects.

SKILL.md
Quality
Evals
Security

Color Coding — HARD RULE

Always prefix active workspace, project, or ticket names with a 🟢 emoji. This applies everywhere an active/selected name is displayed — auto-select messages, confirmation prompts, resume prompts, and list items.

Example:

You were last working on 🟢 Fix login bug. Would you like to pick up where you left off?

In lists, only the currently active item gets the 🟢 prefix.


Follow these steps in order:


Step 1 — Resolve workspace

Read .duplocloud/state.toon (file may not exist). Note workspace_id, active_ticket_name, project_id, project_name if present.

Call duplo-helpdesk::Workspaces_get_available to get the list of available workspaces.

  • If the call returns an empty list, tell the user:

    "It looks like you don't have any workspaces set up yet. Please create one in the DuploCloud portal first." Then stop.

  • If only one workspace is returned: auto-select it and tell the user:

    "Auto-selecting 🟢 <workspace name> — it's the only workspace available." Capture its id as workspace_id.

  • If multiple workspaces and workspace_id is present in state:

    • Check whether that workspace_id exists in the returned list.
    • If found: ask the user:

      "You're currently connected to 🟢 <workspace name>. Would you like to continue with this workspace? (y/n)"

      • y → use this workspace. Proceed to Step 1b.
      • n → show the workspace list (Step 1a).
    • If not found: tell the user the previously saved workspace is no longer available, then show the workspace list (Step 1a).
  • If workspace_id is absent from state: show the workspace list (Step 1a).

Step 1a — Choose a workspace

Show a numbered list using only the workspace name (no raw IDs). If a workspace matches the workspace_id currently in state, prefix it with 🟢:

Here are your available workspaces:
1. 🟢 <name>   ← currently active
2. <name>
...

Ask: "Which workspace would you like to use?"

Capture the selected workspace's id as workspace_id (store internally, do not show to the user).

If the newly selected workspace differs from what was in state: clear active_ticket_name (the old ticket belongs to the old workspace).

Step 1b — Sync tickets to state

After workspace is resolved (whether auto-selected or chosen via Step 1a):

If DUPLO_AGENT_MODE=false or not set (local agent mode): call these in parallel (one tool-call group):

  • duplo-helpdesk::Ticket_list with workspaceId = workspace_id
  • duplo-helpdesk::Workspaces_get_personas with id = workspace_id
  • duplo-helpdesk::Workspaces_get_scopes with id = workspace_id
  • duplo-helpdesk::Workspaces_get_skills with id = workspace_id — workspace-specific skills via personas
  • duplo-helpdesk::Workspaces_get_skills_built_in_files (no parameters) — platform built-in skill files
  • duplo-helpdesk::Projects_get with id = project_id ← only if project_id is in state

Write all of the following in the single end-of-step Bash flush:

  • .duplocloud/state.toon — with tickets array
  • ~/.duplocloud/workspace_context.json — using the personas, scopes, skills, and project data; set fetched_at to now (ISO 8601 UTC). See Local Agent Context section in CLAUDE.md for schema.
  • ~/.duplocloud/skills/<skillName>/<path> — for each entry from Workspaces_get_skills_built_in_files write content to the corresponding path. For each workspace skill with non-empty skillMd write to ~/.duplocloud/skills/<name>/SKILL.md. Create ~/.duplocloud/skills/ directory first.

If DUPLO_AGENT_MODE=true (remote agent): call only duplo-helpdesk::Ticket_list with workspaceId = workspace_id.

Store the results in .duplocloud/state.toon under a tickets field:

tickets[N]{name,title,status,aiAgentId}:
  <ticketName>,<title>,<status>,<aiAgentId or null>
  ...

Write the updated state silently. Step 3 will use this cached list.


Step 2 — Resume or open a ticket?

If active_ticket_name is present in state (and workspace has not changed), look up the ticket title from the cached tickets list. Ask the user:

"You were last working on 🟢 <title>. Would you like to pick up where you left off? (y/n)"

  • y → go to Step 6 (load past context).
  • n → continue to Step 3.

If absent: continue to Step 3.


Step 3 — Choose an existing ticket or create a new one

Use the cached tickets array from state. Filter to open or inProgress status only.

Display using the ticket title as the primary label. Map status values to friendly labels: open → "Open", inProgress → "In Progress". Do not show raw ticket name IDs.

If a ticket matches the active_ticket_name currently in state, prefix it with 🟢:

Here are your open tickets:
1. 🟢 <title> — In Progress   ← currently active
2. <title> — Open
...
N+1. Start a new ticket

If there are no open tickets, skip the list and go directly to Step 4.

Ask: "Which would you like to work on?"

  • Existing ticket selected → capture its name as active_ticket_name, go to Step 5.
  • "Start a new ticket" → continue to Step 4.

Step 4 — Create a new ticket

Step 4a — Check for execution tasks (only if project is active)

If project_id is present in state:

Call duplo-helpdesk::Projects_get with id = project_id.

From the response extract:

  • has_execution_tasks = true if any stage in execution.stages has tasks

If has_execution_tasks is true, ask the user:

"Your project <project_name> has tasks ready to execute. What would you like to do?

  1. Work on a project task
  2. Open a standalone ticket"
  • User picks 1 → go to Step 4b.
  • User picks 2 → go to Step 4c.

If has_execution_tasks is false, or project_id is absent: go to Step 4c.


Step 4b — Pick a project task

Using the stages data from the project response, count total tasks across all stages.

If total tasks > 10 — first ask user to select a stage (show name and task count, no IDs):

Which area would you like to work on?
1. <stage_name> — <stage_description> (<N> tasks)
2. ...

Then show tasks within the chosen stage:

Which task would you like to work on?
1. <task_title> — <friendly ticket status or "No ticket yet">
2. ...

If total tasks ≤ 10 — show all tasks flat:

Which task would you like to work on?
1. <task_title> (<stage_name>) — <friendly ticket status or "No ticket yet">
2. ...

Wait for task selection. Capture task_id (the task's name UUID, stored internally) and task_title.

If the task already has a ticket (ticket_name present in the task data):

  • Set active_ticket_name to that ticket name.
  • Go to Step 5.

If no ticket yet: continue to Step 4d (select agent), then Step 4e.


Step 4c — Name the new ticket

Ask the user: "What should we call this ticket?"


Step 4d — Pick an agent

Call duplo-helpdesk::Workspaces_get_agents with id = workspace_id (always fetch — needed in both modes).

If DUPLO_AGENT_MODE=false or not set (local agent mode): auto-select silently — do NOT prompt the user:

  • If an agent whose name contains "generic" (case-insensitive) exists → use its id. Tell the user: "Auto-selecting agent 🟢 <name>."
  • Else if any agents are returned → use the first agent's id. Tell the user: "Auto-selecting agent 🟢 <name>."
  • Else (no agents) → selected_agent_id = null.

If DUPLO_AGENT_MODE=true: Show a numbered list using agent name only (no IDs or endpoints):

Which agent should handle this ticket?
1. <name>
2. ...

Step 4d.5 — Select personas

Call duplo-helpdesk::Workspaces_get_personas with id = workspace_id (always fetch fresh from backend).

  • Call fails or returns empty list → set selected_persona_ids = [] and proceed to Step 4e.
  • Exactly one persona returned → auto-select it, tell the user:

    "Auto-selecting persona 🟢 <name> — it's the only one available." Set selected_persona_ids = [<that persona's id>]. Proceed to Step 4e.

  • Multiple personas → show a numbered multi-select list using persona name only (no IDs):
    Which personas should have access to this ticket? (enter comma-separated numbers, or press Enter to skip)
    1. <name>
    2. <name>
    ...
    Wait for input:
    • User enters numbers (e.g. 1,3) → capture the IDs of the selected personas as selected_persona_ids.
    • User presses Enter / skips → selected_persona_ids = [].

Step 4e — Create the ticket

Standalone ticket — call duplo-helpdesk::Ticket_create with workspaceId = workspace_id and body:

{
  "title": "<user-provided title>",
  "aiAgentId": "<selected_agent_id — omit this field if null>",
  "workspaceId": "<workspace_id>",
  "origin": "api",
  "ticketContextForAgent": {
    "personaIds": ["<selected_persona_ids>"]
  }
}

Omit aiAgentId entirely if selected_agent_id is null. Omit ticketContextForAgent entirely if selected_persona_ids is empty.

Spec or plan creation ticket (when project_ticket_type is set) — call duplo-helpdesk::Ticket_create with workspaceId = workspace_id and body:

{
  "title": "<project_name> — <spec_creation or plan_creation>",
  "aiAgentId": "<selected_agent_id — omit this field if null>",
  "workspaceId": "<workspace_id>",
  "origin": "api",
  "ticketContextForAgent": {
    "personaIds": ["<selected_persona_ids>"]
  },
  "originContext": {
    "type": "Project",
    "id": "<project_id>",
    "subType": "<project_ticket_type>"
  }
}

Omit aiAgentId entirely if selected_agent_id is null. Omit ticketContextForAgent entirely if selected_persona_ids is empty.

Execution task ticket — call duplo-helpdesk::Ticket_create with workspaceId = workspace_id and body:

HARD RULE: taskId MUST be inside originContext.metadata. Never place taskId as a top-level field. The ticket will NOT be linked to the task if the structure is wrong.

{
  "title": "<task_title>",
  "aiAgentId": "<selected_agent_id — omit this field if null>",
  "workspaceId": "<workspace_id>",
  "origin": "api",
  "ticketContextForAgent": {
    "personaIds": ["<selected_persona_ids>"]
  },
  "originContext": {
    "type": "Project",
    "id": "<project_id>",
    "subType": "execution",
    "metadata": {
      "taskId": "<task_id>",
      "projectType": "plan_execution"
    }
  }
}

Omit aiAgentId entirely if selected_agent_id is null. Omit ticketContextForAgent entirely if selected_persona_ids is empty.

Capture the returned ticket's name as active_ticket_name (stored internally).


Step 5 — Load past context

Call duplo-helpdesk::Ticket_get_messages with workspaceId = workspace_id, ticketName = active_ticket_name.

  • If messages are returned: read them carefully. They contain the full conversation history — prior user messages, assistant responses, decisions made, and work done. Use this to restore your understanding of where work left off before responding to the user.
  • If no messages or empty: proceed without prior context.

Step 6 — Save state

Write .duplocloud/state.toon silently, preserving any existing project_id and project_name fields:

workspace_id: <workspace_id>
project_id: <project_id if present, else omit>
project_name: <project_name if present, else omit>
active_ticket_name: <active_ticket_name>
tickets[N]{name,title,status,aiAgentId}:
  <row per ticket>

Step 6b — Ticket lifecycle rules

HARD RULES — apply for the entire duration this ticket is active:

  • On creation or activation: Leave ticket in open state. Do NOT call Ticket_put_status automatically.
  • When the user sends their first message or activity begins: Call duplo-helpdesk::Ticket_put_status with { "status": "inProgress" } before forwarding the message to the agent.
  • When the user confirms changes, says they are done, or explicitly finishes the activity: Call duplo-helpdesk::Ticket_put_status with { "status": "closed", "disposition": "resolved" }. Remove active_ticket_name from state. Tell the user the ticket has been closed.

Do NOT close the ticket automatically for any other reason. Only close when the user explicitly confirms they are done with the activity on this ticket.

HARD RULES — apply for the entire duration this ticket is active:

  • On creation or activation: Leave ticket in open state. Do NOT call Ticket_put_status automatically.
  • When the user sends their first message or activity begins: Call duplo-helpdesk::Ticket_put_status with { "status": "inProgress" } before forwarding the message to the agent.
  • When the user confirms changes, says they are done, or explicitly finishes the activity: Call duplo-helpdesk::Ticket_put_status with { "status": "closed", "disposition": "resolved" }. Remove active_ticket_name from state. Tell the user the ticket has been closed.

Do NOT close the ticket automatically for any other reason. Only close when the user explicitly confirms they are done with the activity on this ticket.


Step 7b — Confirm or change the agent

If DUPLO_AGENT_MODE=false or not set (local agent mode): skip this step entirely. Claude handles all responses directly — no remote agent assignment needed.

If DUPLO_AGENT_MODE=true:

Call duplo-helpdesk::Workspaces_get_agents with id = workspace_id.

From the ticket data (captured in Step 3 or Step 4e), note the aiAgentId.

Context: Once a ticket is active, the assigned agent handles all your messages. Claude Code only manages ticket lifecycle (activation, creation, closing, spec and plan writing).

If the ticket already has an agent assigned:

Resolve the agent name from the agents list. Tell the user:

"<agent name> is assigned to this ticket and will respond to your messages. Would you like to continue with this agent or switch?

  1. Continue with <agent name>
  2. Switch agent"
  • If user picks 1: keep current agent. Stop here.
  • If user picks 2: show the agent list (Step 7b-select).

If no agent is assigned:

Tell the user: "No agent is assigned to this ticket yet. Please pick one to handle your messages:" Go to Step 7b-select.

Step 7b-select — Pick an agent

Show agent names only (no IDs or endpoints):

1. <name>
2. ...

Ask: "Which agent should handle your messages?"

Call duplo-helpdesk::Ticket_put_assignee with workspaceId = workspace_id, ticketName = active_ticket_name, agentId = <selected agent id>.

Tell the user: "<agent name> is now assigned."

Then tell the user:

"Ticket 🟢 <title> is ready. <agent name> will respond to your messages."

Repository
duplocloud/duplo-claude-plugin
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.