Post daily non-terminal COMPLETED digest to ops room, let /completed confirm in WO spaces with SC note and ops notification, and default approval NTE to proposal total instead of adding to current NTE. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|---|---|---|
| downloads | ||
| scripts | ||
| src | ||
| tools | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| dev-start.sh | ||
| discover-note-timestamps.js | ||
| docker-compose.prod.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| index.js | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| REFACTOR-LOG.md | ||
ServChan
Service Channel + Webex Bot for retail facilities coordination.
ServChan automatically creates dedicated Webex collaboration spaces for every ServiceChannel work order, posts rich updates (including AI-summarized descriptions), and provides useful slash commands for technicians and support teams.
The goal of this document is to help you understand how the system actually works — both architecturally (how data and actions flow) and operationally (how to run, monitor, and debug it).
How the System Works (High-Level Flow)
1. Webhook Path (ServiceChannel → Webex Spaces)
This is the core automated behavior:
-
ServiceChannel sends a webhook to
POST /webhook(handled insrc/server/app.js). -
The route immediately calls
webhookProcessor.processWebhook(payload). -
webhookProcessor(src/services/webhookProcessor.js) does the following:- Uses a per-workOrderId Mutex + queue to safely handle multiple events for the same work order arriving at the same time.
- Checks the SQLite DB (
src/db/mappings.js) to see if a Webex room already exists for this work order. - If no room exists:
- Calls
webexService.createWorkOrderRoom(...)to create a new group space under the configured team. - Adds the default list of members.
- Stores the
workOrderId ↔ roomIdmapping in the database.
- Calls
- Builds a message based on the
EventType(WorkOrderCreated,WorkOrderNoteAdded, etc.). - For
WorkOrderCreated, it callssummarizeTicketDescription()fromsrc/integrations/xai/client.jsto turn the messy ServiceChannel description into something readable. - Posts the message using
webexService.sendMarkdown(...).
-
webexService(src/services/webexService.js) is a thin wrapper. It currently delegates tobotClientbut gives us a place to add ServChan-specific logic or change the underlying client later without touching the processor.
2. Command Path (User → Bot)
Users interact with the bot via mentions in Webex:
- The Webex Framework is initialized in
src/bot/index.js. - All commands are routed through a single
Framework.hears(...)handler. - Commands are dispatched to handlers in
src/commands/(help.js,avStatus.js,woSummary.js, etc.). - Many of these handlers call the external CollabSupport service (configured via
CS_API_BASE) rather than doing heavy work locally.
Key Classes / Modules and Their Responsibilities
| Module / Class | Role |
|---|---|
index.js |
Thin bootstrap. Creates DB connection, instantiates webhookProcessor + webexService, starts the bot and Express server. |
webhookProcessor.js |
The heart of automation. Owns concurrency control (mutex/queue), room lifecycle decisions, and message construction. |
webexService.js |
Thin service layer on top of the Webex client. Preferred interface for anything that needs to talk to Webex. |
botClient.js (in integrations/webex/) |
Low-level Webex bot operations using the bot token (create rooms, post messages, add members). |
adminClient.js (in integrations/webex/) |
Separate client for privileged operations (DECT, phone lookups, etc.) that require different scopes. |
src/integrations/xai/client.js |
All Grok/xAI calls. Contains both the short initial-description summarizer and the more detailed ticket summarizer. |
src/db/mappings.js + path.js |
Database access for work order ↔ room mappings. Path resolution is centralized here for safety. |
src/server/app.js |
Express routes (/health, /webhook, /cleanup-test, etc.). |
src/commands/*.js |
Individual slash command implementations. |
Data & Action Flow Summary
- Incoming webhook →
server→webhookProcessor→ (DB +webexService+xai) → Webex room - User command → Webex Framework (
bot/) → specific command handler → (often external CollabSupport API) → response back to Webex - Space cleanup →
cleanup-testor scheduled job →spaceCleanupService→ reads DB + ServiceChannel status → acts viabotClient
All persistent state for work order rooms lives in the SQLite database (see "Database Constraint" below).
What Happens When a Work Order Is Created (End-to-End Walkthrough)
Here is a concrete trace of what occurs when ServiceChannel sends a WorkOrderCreated webhook:
-
Webhook arrives
ServiceChannel callsPOST /webhookwith a payload containingEventType: "WorkOrderCreated"and the full work order object. -
Route hands off immediately
src/server/app.jsreceives the request, logs it, returns200 OKquickly, and calls:webhookProcessor.processWebhook(payload) -
Concurrency protection
src/services/webhookProcessor.jslooks up (or creates) a Mutex for this specificworkOrderId. It also maintains a small queue so multiple rapid events for the same work order are processed in order. -
Room lookup
It queries the database (src/db/mappings.js) to see if a Webex room already exists for this work order. -
Room creation (first time only)
If no room exists:- Calls
webexService.createWorkOrderRoom(workOrder, teamId) webexServicecallsbotClient.createRoom(...)with the standard title format:ServChan WO-123456 | Store 2477 | Store Name- Adds the default members (the list currently hardcoded in
index.js) - Saves the mapping (
workOrderId → roomId) to the database
- Calls
-
Description summarization
Because this is aWorkOrderCreatedevent, it calls:summarizeTicketDescription(description, xaiToken)from
src/integrations/xai/client.js. This sends a focused prompt to Grok to turn the often messy ServiceChannel description into a clean 2–6 sentence summary. -
Message construction
The processor builds a rich Markdown message containing:- Link to the work order in ServiceChannel
- Store and trade information
- Priority, category, problem code
- Status
- The AI-generated description summary
-
Message posted
CallswebexService.sendMarkdown(roomId, text), which ultimately callsbotClient.sendMarkdown(...). -
Cleanup
The mutex is released. If the queue for this work order is now empty, the mutex and queue objects are removed from memory.
The result is a new, populated Webex space ready for the team, usually within a few seconds of the work order being created in ServiceChannel.
Directory Structure
ServChan/
├── index.js # Thin bootstrap / orchestrator
├── src/
│ ├── bot/ # Webex Framework + command routing
│ ├── server/ # Express app (routes + handlers)
│ ├── services/ # Core business logic (webhookProcessor, webexService, etc.)
│ ├── integrations/ # External system clients (webex, serviceChannel, xai)
│ ├── db/ # Database access + safe path resolution
│ ├── commands/ # Individual slash command implementations
│ ├── legacy/ # Old code kept during transition (avoid editing)
│ ├── experimental/ # Superseded device code (mostly unused now)
│ └── utils/
├── tools/ # Standalone operational scripts
├── config/ # Non-secret config (secrets still here too)
├── data/ # SQLite database (handle with care)
├── Dockerfile
├── docker-compose.yml
├── REFACTOR-LOG.md # History of major changes (reference only)
└── README.md
Operational Considerations
The Database Constraint (Very Important)
The SQLite database file used by the live production bot must never be moved, renamed, or have a second copy accidentally created.
- All code resolves the DB path through
src/db/path.js, which respectsDB_PATHfrom your environment. - The main application creates one connection in
index.jsand passes that instance around. - When debugging, always verify which database file is actually being used (check logs at startup — it prints the path).
If you ever see two different .db files being touched, stop and investigate immediately.
Remote Service Dependency
Many user-facing commands (/avStatus, /woSummary, /woAttachments, /woHistory) do not do the heavy lifting locally. They call an external service via the CS_API_BASE environment variable (currently pointing at a CollabSupport instance).
If those commands start failing but the webhook automation still works, the problem is usually with the remote service or network connectivity to it.
Webex Token Separation
- Normal bot actions (creating rooms for work orders, posting messages, adding members) use the bot token via
botClient. - Advanced lookups (DECT networks, phones assigned to stores, etc.) require a different privileged token and go through
adminClient.
Keep these two concerns separate. Do not mix the tokens.
Logging & Observability
- Application logs are written daily to
./logs/YYYYMMDD.log. - The good structured logger lives in
src/utils/logger.js. - Some older legacy paths still use simpler console + file logging.
- The
/healthendpoint is your best friend for runtime state (DB connectivity, memory, uptime).
Space Cleanup
Automated (or manual via /cleanup-test) removal of old completed work order spaces is handled by spaceCleanupService.js.
It reads work order status from ServiceChannel and then acts on the Webex rooms via the bot client. It is intentionally conservative.
Running the Application
Local
npm run dev
Docker (recommended for consistency)
Development (live reload, source mounted, node --watch):
docker compose up --build
Production (optimized image, no source mount, persistent logs volume):
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
After the first build you can usually omit --build:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
See docker-compose.prod.yml for the production-specific settings.
Important for production images:
- All secrets must come from
.env(viaenv_file). The application now enforces this viasrc/config/secrets.js. config/config.jsonmust not contain real credentials (it can still hold non-secret values like server name/port or legacy non-token webex fields for compatibility).- The production stage runs as the non-root
nodeuser (uid 1000). On Linux hosts ensure your./dataand./logshost directories are writable by uid 1000 if you see permission errors.
Production Considerations
- Use the production compose files for any long-lived or customer-facing deployment.
- Logs and the SQLite DB are the only things that need to persist across container restarts/recreates. Everything else is in the image.
- The
/healthand/healthzendpoints are suitable for container orchestrators (Kubernetes liveness/readiness, etc.). - Consider adding resource limits, secrets management (Docker secrets / Vault), and log rotation outside the container for full prod ops.
- Always run
docker compose ... configto validate before deploying.
Health Checks
GET /healthz→ Simple liveness (used by Docker healthcheck)GET /health→ Detailed JSON report including database connectivity and memory usage
Common Debugging Commands
- Check which DB file is in use → Look at the very first logs after startup.
- Test space cleanup logic →
GET /cleanup-test?dryRun=true - Force a fresh xAI summary on a new work order → Create a test work order in ServiceChannel.
Bot Commands
| Command | Context | Notes |
|---|---|---|
/help |
Anywhere | Context-aware |
/avStatus |
In WO space or with store number | Often calls remote service |
/woSummary |
In WO space or with WO number | Often calls remote service |
/woAttachments |
In WO space or with WO number | Often calls remote service |
/woHistory |
With store number | Often calls remote service |
/woApprove |
In WO space (or with WO#) | Manually posts proposal approval Adaptive Card (when status is IN PROGRESS | WAITING FOR APPROVAL). Auto-triggered on relevant webhooks too. |
Approval Cards (new): When a webhook arrives with IN PROGRESS | WAITING FOR APPROVAL (typically with a "Proposal created" note), ServChan automatically posts an Adaptive Card v1.3 in the room. The card shows proposal details/costs, prepopulates the suggested new NTE (current + proposal sums), and allows submit to PATCH the NTE in ServiceChannel + record approver attribution from the Webex user. Use /woApprove to re-trigger.
Development Guidelines
When making changes:
- Prefer dependency injection (especially passing the
dbinstance) over creating new connections. - If you need to talk to Webex for normal bot operations, go through
webexServicerather than callingbotClientdirectly. - All AI summarization should live in or be called from
src/integrations/xai/. - Respect the production database path resolution — never hardcode a
.dbpath. - If you're touching webhook behavior, be extremely careful with concurrency (the mutex/queue pattern exists for a reason).
Environment Variables
| Variable | Purpose | Notes |
|---|---|---|
DB_PATH |
SQLite database location | Critical — production uses this |
WEBEX_BOT_TOKEN |
Token for normal bot operations | Used by botClient |
CS_API_BASE |
Base URL for remote CollabSupport service | Powers many commands |
WEBEX_BOT_PERSON_ID |
Used to identify the bot during cleanup | Prevents removing itself |
WEBEX_ADMIN_TOKEN (or similar) |
Token for privileged Webex calls | Used by adminClient (if configured) |
Secrets must be provided exclusively via environment variables (loaded by src/config/secrets.js and required at startup for core functionality). config/config.json should contain only non-secret values. The code no longer depends on secrets living in the JSON file.
This document is meant to be living. Update it when you add significant new behavior or change how major flows work.