wxccai/docs/wxcc-ai-agent-tools.md
jmcqueen c20f387081 feat(lookup): add store-based ticket search for store callers
Store associates share accounts and iPads, so the existing
findMyTickets (keyed by reporter email) misses tickets a coworker
opened for the same store earlier in the shift. This adds a
store-keyed lookup so a store caller can see everything open at
their location.

- New service function searchOpenTicketsByStoreNumber(): normalizes
  to 5 digits, validates against the stores cache (warn-only on miss),
  runs the same Grok-enriched search as the reporter path.
- New route GET /wxccai/open-tickets-by-store?storeNumber=782 —
  accepts padded or unpadded input, degrade-gracefully 200 on
  runtime failures to match the sibling reporter route.
- JQL note: the CMDB "Store Number" field only matches on the
  object *label* (the 5-digit padded string). Neither the objectId,
  ASSET-<id> objectKey, workspace-qualified id, nor raw digits
  match. Probed all variants before landing.
- WxCC AI Agent docs updated: findMyStoreTickets promoted to the
  primary lookup for store callers; findMyTickets reframed as the
  corporate-caller path. System prompt Step 1 rewritten to route
  store vs corporate callers to the right tool.

Verified end-to-end against store 782 — returns 5 open tickets
including two from other reporters (SS-20380 wireless phone,
SS-11943 Zipline training) that email-based lookup misses.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 11:40:29 -04:00

20 KiB

WxCC AI Agent — Ticket Operations Reference

Everything the Webex Contact Center AI agent needs to open, look up, comment on, and close AEO Store Support (SS) Jira tickets via this service. Includes:

  1. The full inventory of ticket-operation endpoints
  2. The 14 supported SS subTypes
  3. A drop-in system prompt for the AI agent
  4. Tool definitions in OpenAI/JSON-schema function-calling format
  5. Two worked call examples
  6. Endpoints that are deliberately NOT exposed to the AI

All endpoints are under /api/wxccai/ on the service (default port 1866). This doc covers the caller-facing surface only — admin/debug endpoints (/admin/caches/*, /debug/*) and the Webex transcript webhook (/issueTranscript/:jiraKey) are intentionally omitted here.


1. Ticket-operation inventory

Read / lookup (safe, side-effect-free)

# Tool Endpoint What it does
1 lookupTicket GET /getticket?jiraKey=SS-12345 Returns an AI-summarized view (Grok-generated) of a specific ticket: status, assignee, and a natural-language recap of the description + last 5 comments. Best for "tell me about my ticket" moments.
2 findMyStoreTickets GET /open-tickets-by-store?storeNumber=00782 Returns every currently open SS ticket filed for a given store, regardless of who reported it. This is the right lookup for store callers because associates typically share accounts / iPads — a ticket opened by a coworker would not show up in findMyTickets (which is keyed by reporter email).
3 findMyTickets GET /open-tickets-by-reporter?email=user@ae.com Returns the caller's currently open tickets across CS/SS/SUPPORT with short summaries. Best for corporate callers (developers, ops, HQ staff) whose email uniquely identifies them. For store callers, prefer findMyStoreTickets.
4 getTicketStatus GET /ticket/:key/status Raw status fields (no Grok). Faster/cheaper than lookupTicket. Use when the AI just needs the current status, resolution, assignee — not a narrative.

Create (opens a new ticket)

# Tool Endpoint What it does
5 createStoreTicket POST /createSSRequest Files a new Store Support (SS) ticket with the right subType, links it to a store via Assets, and returns the new key. Uses the 14 supported subTypes below.

Update (mid-conversation)

# Tool Endpoint What it does
6 addTicketComment POST /ticket/:key/comment Appends a comment (public or internal:true). Internal is the safe default for AI-authored notes.
7 updateTicketFields PATCH /ticket/:key Update summary / description / priority / labels / assignee / custom fields. Rarely needed by the AI.

Close (three intent-specific + one full-control)

# Tool Endpoint What it does
8 confirmTicketFixed POST /ticket/:key/confirmFixed Caller says the issue is resolved → close with resolution=Done + audit comment.
9 cancelTicket POST /ticket/:key/customerCancelled Caller wants to abandon the request → close with resolution=Won't Do.
10 markTicketDuplicate POST /ticket/:key/duplicate Caller already has another ticket for the same issue → creates a formal Duplicate issueLink to the primary + closes with resolution=Duplicate.
closeTicket POST /ticket/:key/close Full-control close. Do NOT expose to the AI — use the intent-specific tools instead.

All three close tools auto-populate the four workflow-validator fields (Component, Business Service, System, Cause) from subType-based defaults (see src/config/ssCloseDefaults.js), so the AI just supplies the ticket key and the subType from the original create.


2. Supported SS subTypes (must match exactly)

The AI must pass one of these strings, unchanged. Casing and spacing matter; duplicates in the list are intentional (both spellings are mapped, but the AI should still copy exactly one of these strings).

Register Not functioning properly
Unable to login
Business report issue
Broken device / hardware
Broken Device / Hardware
Report Missing Hardware
Request Additional Hardware
Business Report Issue
Report an Issue with Sterling Application
Omni Turn Off / On
Report a Traffic Counter Issue
Report a Technology issue
UKG Pro / Workforce Management Issues
Store Transportation Request

If none of these clearly fit the caller's issue, use Report a Technology issue as the catch-all.


3. System prompt (paste into the WxCC AI Agent config)

You are a Store Support (SS) AI agent for AEO stores. You handle inbound
calls from store associates and managers who need technology help. Your job
is to identify the caller, understand their issue, and either open, update,
or close the appropriate Jira ticket.

CORE PRINCIPLES

1. Never invent ticket keys, store numbers, or subTypes. If the caller
   doesn't volunteer one, ask.
2. Store numbers are always 5 digits (pad with leading zeros: "305" →
   "00305"). The service pads unpadded input for you but always spell
   the padded form back to the caller ("store zero-zero-three-zero-five").
3. Never call a close tool (confirmTicketFixed, cancelTicket,
   markTicketDuplicate) based on inference alone. The caller must
   explicitly state the intent in the current turn.
4. When in doubt, add an internal comment (addTicketComment with
   internal=true) and escalate to a human. Never close a ticket you're
   unsure about — an open ticket costs the business less than a wrongly
   closed one.
5. Identify callers correctly: STORE callers (associates, managers, from
   a store location) → use findMyStoreTickets keyed by store number,
   because store accounts are frequently shared. CORPORATE callers
   (developers, ops, HQ staff) → use findMyTickets keyed by their email,
   because email uniquely identifies them.

TYPICAL CALL FLOW

Step 1: Identify the caller and find existing tickets.
  - Ask "Are you calling from a store, or are you calling from
    corporate?" if you can't tell from context.
  - If STORE: get the store number, then call findMyStoreTickets with
    the padded store number. This returns EVERY open SS ticket for that
    store — including ones opened by their coworkers.
  - If CORPORATE: get their email, then call findMyTickets with the
    email. This returns tickets they personally reported across CS/SS/
    SUPPORT.
  - You may call BOTH for a store caller who also has a personal work
    email — the union catches everything.

Step 2: Decide what they need.
  - If they reference a specific existing ticket → lookupTicket for a
    summary; then decide with them what to do next (comment, close, or
    just answer their question).
  - If they describe a new issue → pick the best subType from the fixed
    list (see the createStoreTicket tool description) and call
    createStoreTicket.
  - If it's a mix ("my existing ticket X is fixed but now Y is broken")
    → handle in order: close the resolved one first, then create the new
    one.

Step 3: Close at the right moment, with the right tool.
  - "It's already fixed" / "the manager rebooted it and it works now"
    → confirmTicketFixed
  - "Never mind" / "cancel it" / "I don't need this anymore"
    → cancelTicket
  - "I already opened SS-12345 for this" / "this is the same as SS-XXX"
    → markTicketDuplicate (you must have the primaryKey)
  - Anything ambiguous → addTicketComment describing the ambiguity and
    escalate.

Step 4: Always confirm the action back to the caller.
  - After createStoreTicket, read back the new ticket key.
  - After any close tool, tell them the ticket is closed and why.

WHAT NOT TO DO

- Do NOT expose Jira internals (customfield IDs, objectTypeIds, workflow
  transition IDs) to the caller.
- Do NOT call closeTicket (the raw endpoint). Always use one of the three
  intent-specific tools.
- Do NOT call updateTicketFields unless the caller explicitly asks to
  change something structural (priority, assignee).
- Do NOT re-open a ticket you just closed in the same call. If you closed
  by mistake, escalate to a human.

4. Tool definitions (OpenAI / JSON-schema function-calling format)

Adjust the wrapper syntax to whatever WxCC expects; the JSON schema is standard and portable across most LLM tool-calling frameworks.

[
  {
    "name": "findMyStoreTickets",
    "description": "Return every currently open SS ticket for a given store, regardless of who reported it. This is the RIGHT tool for store callers (associates, managers) because store accounts and iPads are typically shared — a ticket opened by their coworker earlier in the shift will NOT show up in findMyTickets (which is keyed by reporter email). Call this near the start of every store call once you have the store number.",
    "parameters": {
      "type": "object",
      "properties": {
        "storeNumber": { "type": "string", "description": "Store number, numeric. May be unpadded ('782') or padded ('00782') — the service normalizes to 5 digits." }
      },
      "required": ["storeNumber"]
    },
    "http": { "method": "GET", "path": "/api/wxccai/open-tickets-by-store", "queryParams": ["storeNumber"] }
  },
  {
    "name": "findMyTickets",
    "description": "Look up the caller's currently open tickets across CS/SS/SUPPORT projects, keyed by their email. Best for CORPORATE callers (developers, ops, HQ staff) whose email uniquely identifies them. For STORE callers, prefer findMyStoreTickets — store accounts are typically shared and reporter-email search will miss tickets a coworker opened.",
    "parameters": {
      "type": "object",
      "properties": {
        "email": { "type": "string", "description": "Caller's work email address (e.g. someone@ae.com)." }
      },
      "required": ["email"]
    },
    "http": { "method": "GET", "path": "/api/wxccai/open-tickets-by-reporter", "queryParams": ["email"] }
  },
  {
    "name": "lookupTicket",
    "description": "Fetch an AI-summarized view of a specific ticket (title, status, assignee, and a natural-language recap of the description + last 5 comments). Use when the caller references an existing ticket by key and you need context before taking action.",
    "parameters": {
      "type": "object",
      "properties": {
        "jiraKey": { "type": "string", "description": "Jira issue key, e.g. SS-20948." }
      },
      "required": ["jiraKey"]
    },
    "http": { "method": "GET", "path": "/api/wxccai/getticket", "queryParams": ["jiraKey"] }
  },
  {
    "name": "getTicketStatus",
    "description": "Get raw status/assignee/resolution/priority fields for a ticket without the AI summary. Faster than lookupTicket. Use when you just need to check whether a ticket is Open/In Progress/Resolved.",
    "parameters": {
      "type": "object",
      "properties": {
        "key": { "type": "string", "description": "Jira issue key, e.g. SS-20948." }
      },
      "required": ["key"]
    },
    "http": { "method": "GET", "path": "/api/wxccai/ticket/{key}/status", "pathParams": ["key"] }
  },
  {
    "name": "createStoreTicket",
    "description": "Open a new Store Support (SS) ticket. Use ONLY when the caller has a new issue that isn't already covered by one of their existing open tickets (check findMyTickets first). Choose the subType that best matches the caller's issue from the fixed list. If none of the listed subTypes fit, use 'Report a Technology issue' as the catch-all.",
    "parameters": {
      "type": "object",
      "properties": {
        "subType": {
          "type": "string",
          "enum": [
            "Register Not functioning properly",
            "Unable to login",
            "Business report issue",
            "Broken device / hardware",
            "Broken Device / Hardware",
            "Report Missing Hardware",
            "Request Additional Hardware",
            "Business Report Issue",
            "Report an Issue with Sterling Application",
            "Omni Turn Off / On",
            "Report a Traffic Counter Issue",
            "Report a Technology issue",
            "UKG Pro / Workforce Management Issues",
            "Store Transportation Request"
          ],
          "description": "Exact subType string. Must match one of the enum values."
        },
        "onBehalfOf": { "type": "string", "description": "Caller's email address." },
        "summary":    { "type": "string", "description": "One-line title of the issue. Concise, action-oriented." },
        "description":{ "type": "string", "description": "Detailed narrative. Include what the caller was doing, what went wrong, and any error messages they read to you." },
        "storeNumber":{ "type": "string", "description": "5-digit store number (pad with leading zeros: '305' -> '00305')." }
      },
      "required": ["subType", "onBehalfOf", "summary", "storeNumber"]
    },
    "http": { "method": "POST", "path": "/api/wxccai/createSSRequest", "body": "json" }
  },
  {
    "name": "addTicketComment",
    "description": "Append a comment to an existing ticket. Use to record mid-call context, escalation reasons, or anything the caller says that a downstream human agent will need. Default to internal=true for AI-authored notes; only set internal=false when the caller has asked for a public/customer-visible update.",
    "parameters": {
      "type": "object",
      "properties": {
        "key":      { "type": "string", "description": "Jira issue key." },
        "text":     { "type": "string", "description": "Comment body, plain text." },
        "internal": { "type": "boolean", "description": "true = visible only to the Service Desk Team (default, recommended). false = visible to the customer.", "default": true }
      },
      "required": ["key", "text"]
    },
    "http": { "method": "POST", "path": "/api/wxccai/ticket/{key}/comment", "pathParams": ["key"], "body": "json" }
  },
  {
    "name": "confirmTicketFixed",
    "description": "Close a ticket because the caller has EXPLICITLY confirmed the underlying issue is resolved (e.g. 'the manager rebooted it and it works now', 'yeah I got it working'). Do not use this to close a ticket the caller is ambivalent or silent about. Adds a standardized 'Closed via WxCC AI agent: caller confirmed the issue is resolved.' internal comment automatically.",
    "parameters": {
      "type": "object",
      "properties": {
        "key":     { "type": "string", "description": "Jira issue key being closed." },
        "subType": { "type": "string", "description": "The original subType the ticket was opened under. Same enum as createStoreTicket. Used to pick the right classification defaults for the workflow validator." },
        "comment": { "type": "string", "description": "Optional extra context to append to the standardized close comment." }
      },
      "required": ["key", "subType"]
    },
    "http": { "method": "POST", "path": "/api/wxccai/ticket/{key}/confirmFixed", "pathParams": ["key"], "body": "json" }
  },
  {
    "name": "cancelTicket",
    "description": "Close a ticket because the caller has EXPLICITLY asked to cancel it (e.g. 'nevermind, cancel it', 'I don't need this anymore', 'I opened this by mistake'). Records the caller's reason if they gave one. Resolution is set to 'Won't Do'.",
    "parameters": {
      "type": "object",
      "properties": {
        "key":     { "type": "string", "description": "Jira issue key being cancelled." },
        "subType": { "type": "string", "description": "The original subType. Used to pick classification defaults." },
        "reason":  { "type": "string", "description": "Optional reason the caller gave for the cancellation." }
      },
      "required": ["key", "subType"]
    },
    "http": { "method": "POST", "path": "/api/wxccai/ticket/{key}/customerCancelled", "pathParams": ["key"], "body": "json" }
  },
  {
    "name": "markTicketDuplicate",
    "description": "Close a ticket because the caller told you it's a duplicate of another existing ticket. Requires BOTH the ticket being closed (key) AND the primary ticket it duplicates (primaryKey). Creates a formal 'Duplicate' link between them in Jira, then closes with resolution 'Duplicate'. If the caller isn't sure of the primary key, do NOT guess — use findMyTickets to help them identify it, or fall back to addTicketComment + escalate.",
    "parameters": {
      "type": "object",
      "properties": {
        "key":        { "type": "string", "description": "Jira key of the ticket being closed as a duplicate." },
        "primaryKey": { "type": "string", "description": "Jira key of the primary/canonical ticket this one duplicates." },
        "subType":    { "type": "string", "description": "The original subType of the duplicate ticket. Used to pick classification defaults." },
        "comment":    { "type": "string", "description": "Optional extra context to append to the standardized close comment." }
      },
      "required": ["key", "primaryKey", "subType"]
    },
    "http": { "method": "POST", "path": "/api/wxccai/ticket/{key}/duplicate", "pathParams": ["key"], "body": "json" }
  }
]

5. Worked call examples

Example 1 — store caller, checks store history first, then creates

Caller: "Hi, register 3 at store 782 froze during checkout."
AI:     [findMyStoreTickets storeNumber="782"]
        → returns [SS-20380 (wireless phone), SS-11943 (Zipline training)]
        (Both open, but neither about the register. Nothing to reuse.)
AI:     "I don't see any existing tickets for this issue at your store.
         Let me open a new one. Can I get your email for the ticket?"
Caller: "jane@ae.com"
AI:     [createStoreTicket
          subType="Register Not functioning properly",
          onBehalfOf="jane@ae.com",
          summary="Register 3 froze at checkout",
          description="Caller reports register 3 became unresponsive during a
                       transaction around 10:15am. No error message read to
                       the caller.",
          storeNumber="00782"]
        → returns { issueKey: "SS-20955" }
AI:     "I've opened ticket SS-20955 for you. The store technology team
         will pick it up shortly."

Note how findMyStoreTickets surfaces tickets opened by other people at the store (SS-20380, SS-11943). If Jane had called findMyTickets with just her email, she'd have missed both.

Example 2 — caller wants to close an existing ticket

Caller: "Hey, I opened SS-20948 yesterday but the manager fixed it — can
         you close it out?"
AI:     [getTicketStatus key="SS-20948"]  → status: "Open"
AI:     "Just to confirm, SS-20948 for the [issue description] — the
         issue is fully resolved and you don't need us to look at it
         anymore?"
Caller: "Yep, all good."
AI:     [confirmTicketFixed
          key="SS-20948",
          subType="Report a Technology issue",
          comment="Caller reports issue resolved on-site by store manager."]
        → returns { success: true, transitionUsed: "Resolved", resolution: "Done" }
AI:     "Perfect, SS-20948 is now closed as resolved. Anything else?"

6. Deliberately NOT exposed to the AI

Reserved for humans / ops / internal plumbing:

  • updateTicketFields (PATCH /ticket/:key) — too much surface area; humans should handle structural edits.
  • closeTicket (raw POST /ticket/:key/close) — the three intent-specific tools are safer; they auto-populate the workflow validator and audit comment.
  • getTransitions (GET /ticket/:key/transitions) — internal plumbing; the close tools use it under the hood.
  • /admin/caches/*, /debug/* — cache and diagnostic endpoints.
  • /issueTranscript/:jiraKey — Webex Contact Center webhook target, called by the platform rather than by the AI agent's tool loop.