Extends the app so a Contact Center AI agent can close SS tickets
end-to-end (customer-confirmed fix, cancellation, duplicate) without a
human touch — the natural next step after ticket creation landed.
The SS Resolved (161) workflow validator requires four fields on the
ticket at execution time (components + 3 CMDB customfields for Business
Service / System / Cause). The transition screen itself only exposes
resolution, so the closer has to PUT these into the ticket first and
*then* fire the transition. This change wires up the whole flow with
sensible per-subType defaults, so the AI can close a typical ticket
with just { subType, comment }.
Design (see Forgejo #10 for the full write-up):
- Generalized services/jira/storesCache into a reusable factory
createAssetsObjectCache. Same in-memory Map + on-disk JSON snapshot +
startAt/maxResults pagination + personal-PAT auth. Store cache becomes
one instance; three new caches join it for the CMDB validator fields
(businessServices=100, systems=103, causes=107 — all in schema 68, so
the same permission workaround from Forgejo #8 keeps working).
- Boot-time init warms all four caches in parallel; a new
services/jira/caches.js barrel exposes them for lifecycle (init) and
by-name lookup (admin routes). Full sync takes ~5s in the tenant
(2661 stores dominate — the three CMDB caches together hold ~653
entries and finish in ~1s).
- Admin surface rebuilt around a plural /admin/caches path:
GET /admin/caches — status of all four
GET /admin/caches/:name/status — one cache
POST /admin/caches/:name/refresh — force resync of one
POST /admin/caches/refreshAll — parallel refresh
Old /admin/storesCache/{status,refresh} are kept as aliases.
- Config renamed: config.storesCache -> config.caches (dir instead of
path; filenames auto-derived per cache). Env vars renamed to
CACHES_* (dir/refresh-hours/stale-hours/page-size/max-pages).
.env.example updated.
- ssCloseDefaults.js maps every supported SS subType to a
{component, businessService, system, cause} tuple. Values verified
against the live caches so they resolve at runtime. Falls back to a
safe __default__ tuple (Help Desk / Store Technology / "I can't find
my option - Misc" / Unknown) for un-mapped subTypes — those catch-all
values are the tenant's designed "I don't know" escape valves.
- issues.js closeTicket completely reworked:
* setSSValidatorFields helper: reads current ticket state, resolves
each of the four fields via caller > existing-on-ticket > default,
PUTs them using the Cloud CMDB shape [{id: "<ws>:<objectId>"}]
(same fix as Forgejo #8 for Store Number).
* Only writes fields that need writing; preserves human triage by
default (preserveExistingClassification=true).
* Fixes Forgejo #9: only sends the resolution field when the
chosen transition targets statusCategory=done. Non-done
transitions (e.g. "Waiting for customer") no longer 400 on
"Field 'resolution' cannot be set."
- closeHelpers.js adds three CC-agent intent wrappers:
confirmFixed(key, {subType, ...}) → resolution=Done
customerCancelled(key, {subType, ...}) → resolution=Won't Do
markDuplicate(key, {primaryKey, ...}) → resolution=Duplicate +
formal Duplicate issueLink
to the primary
Each posts a standardized internal audit-trail comment ("Closed via
WxCC AI agent: …") that documents the automated action for the
humans who inherit the ticket.
- New routes: POST /ticket/:key/{confirmFixed,customerCancelled,
duplicate} plus an expanded body on the existing /close route.
Verified end-to-end: POST /ticket/SS-20948/confirmFixed with just
subType=Report a Technology issue transitioned the ticket to Resolved
with all four validator fields populated from defaults (Help Desk /
Store Technology / I can't find my option - Misc / Unknown), plus
resolution=Done. Direct Jira REST GET confirms every field persisted.
Closes Forgejo #9. Refs Forgejo #10.
Co-authored-by: Cursor <cursoragent@cursor.com>
81 lines
3.4 KiB
Text
81 lines
3.4 KiB
Text
# =============================================
|
|
# .env.example
|
|
# Copy to .env and fill in real values. DO NOT commit .env.
|
|
# =============================================
|
|
|
|
# --- Server ---
|
|
PORT=1866
|
|
NODE_ENV=development
|
|
|
|
# --- Jira ---
|
|
# Preferred: use the JIRA_CLOUD_ID gateway form. When set, requests go to
|
|
# https://api.atlassian.com/ex/jira/{cloudId}. If unset, JIRA_BASE_URL is used
|
|
# directly (e.g. https://your-site.atlassian.net).
|
|
JIRA_CLOUD_ID=
|
|
JIRA_BASE_URL=https://your-site.atlassian.net
|
|
|
|
# Auth. 'basic' = email + API token (Atlassian API tokens).
|
|
# 'bearer' = OAuth bearer token in Authorization header.
|
|
JIRA_AUTH_TYPE=basic
|
|
JIRA_EMAIL=service-account@example.com
|
|
JIRA_API_TOKEN=REPLACE_ME
|
|
|
|
# JSM Service Desk (Store Support). Numeric service desk id.
|
|
JIRA_SERVICE_DESK_ID=170
|
|
|
|
# Role name used to restrict visibility on comments posted by this service.
|
|
# Common values: "Administrators", "Service Desk Team".
|
|
JIRA_COMMENT_VISIBILITY_ROLE=Service Desk Team
|
|
|
|
# --- Jira Assets (Store Number -> Assets object resolution) ---
|
|
# Workspace id for Jira Assets. If unset, the app tries to auto-discover via
|
|
# /rest/servicedeskapi/assets/workspace, but setting it explicitly is safer
|
|
# on tenants with more than one Assets workspace.
|
|
JIRA_ASSETS_WORKSPACE_ID=
|
|
|
|
# Numeric object schema and object type id for the Stores schema in Assets.
|
|
JIRA_ASSETS_STORE_SCHEMA_ID=68
|
|
JIRA_ASSETS_STORE_OBJECT_TYPE_ID=109
|
|
|
|
# The attribute name (as shown in the Assets UI) holding the store number.
|
|
JIRA_ASSETS_STORE_NUMBER_ATTRIBUTE=Store Number
|
|
# Optional. If set, the app will also try attribute[<id>]=... form in AQL.
|
|
JIRA_ASSETS_STORE_NUMBER_ATTRIBUTE_ID=
|
|
|
|
# The custom field on the JSM request that holds the Store Assets reference.
|
|
JIRA_STORE_CUSTOM_FIELD_ID=customfield_10261
|
|
|
|
# --- Assets Stores cache (personal-PAT sync workaround) ---
|
|
# The service account is silently filtered out of Object Type 109 (see
|
|
# Forgejo issue #1). Until that's fixed, the app populates a local store-number
|
|
# -> objectId cache using a *personal* Atlassian PAT that has the right role.
|
|
# This PAT is used ONLY for reading the Stores schema; nothing that mutates
|
|
# Jira state uses it.
|
|
#
|
|
# The .env file is the primary supported storage for the PAT (the bot runs on
|
|
# a Linux host; macOS Keychain isn't available there). Because .env stays out
|
|
# of source control (.gitignore) and app.log no longer echoes auth headers,
|
|
# the cleartext token here is scoped to whoever has filesystem access on the
|
|
# deploy host — lock the file down with `chmod 600 .env` and rotate the token
|
|
# if that trust changes.
|
|
#
|
|
# For local dev on macOS you can instead source the token from Keychain via
|
|
# bin/load-assets-sync-secret.sh (see README) and leave ASSETS_SYNC_TOKEN out
|
|
# of .env entirely.
|
|
ASSETS_SYNC_EMAIL=you@ae.com
|
|
ASSETS_SYNC_TOKEN=REPLACE_ME
|
|
|
|
# Shared settings for all Assets object caches (stores, business services,
|
|
# systems, causes). Each cache is a Map<key, entry> backed by a JSON file at
|
|
# ${CACHES_DIR}/${cacheName}.json. Regenerable via
|
|
# POST /api/wxccai/admin/caches/refreshAll (or refresh a single cache).
|
|
CACHES_DIR=./data
|
|
# How often to run a full resync per cache (hours). 0 disables the scheduler.
|
|
CACHES_REFRESH_HOURS=24
|
|
# Cache is considered "stale" after this many hours; boot-time refresh fires
|
|
# if the on-disk snapshot is older than this.
|
|
CACHES_STALE_AFTER_HOURS=48
|
|
|
|
# --- xAI (Grok) ---
|
|
XAI_API_KEY=REPLACE_ME
|
|
XAI_BASE_URL=https://api.x.ai/v1
|