# 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: 1. ServiceChannel sends a webhook to `POST /webhook` (handled in `src/server/app.js`). 2. The route immediately calls `webhookProcessor.processWebhook(payload)`. 3. `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 ↔ roomId` mapping in the database. - Builds a message based on the `EventType` (`WorkOrderCreated`, `WorkOrderNoteAdded`, etc.). - For `WorkOrderCreated`, it calls `summarizeTicketDescription()` from `src/integrations/xai/client.js` to turn the messy ServiceChannel description into something readable. - Posts the message using `webexService.sendMarkdown(...)`. 4. `webexService` (src/services/webexService.js) is a thin wrapper. It currently delegates to `botClient` but 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-test` or scheduled job → `spaceCleanupService` → reads DB + ServiceChannel status → acts via `botClient` 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: 1. **Webhook arrives** ServiceChannel calls `POST /webhook` with a payload containing `EventType: "WorkOrderCreated"` and the full work order object. 2. **Route hands off immediately** `src/server/app.js` receives the request, logs it, returns `200 OK` quickly, and calls: ```js webhookProcessor.processWebhook(payload) ``` 3. **Concurrency protection** `src/services/webhookProcessor.js` looks up (or creates) a Mutex for this specific `workOrderId`. It also maintains a small queue so multiple rapid events for the same work order are processed in order. 4. **Room lookup** It queries the database (`src/db/mappings.js`) to see if a Webex room already exists for this work order. 5. **Room creation (first time only)** If no room exists: - Calls `webexService.createWorkOrderRoom(workOrder, teamId)` - `webexService` calls `botClient.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 6. **Description summarization** Because this is a `WorkOrderCreated` event, it calls: ```js 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. 7. **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 8. **Message posted** Calls `webexService.sendMarkdown(roomId, text)`, which ultimately calls `botClient.sendMarkdown(...)`. 9. **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 respects `DB_PATH` from your environment. - The main application creates **one** connection in `index.js` and 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 `/health` endpoint 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 ```bash npm run dev ``` ### Docker (recommended for consistency) **Development** (live reload, source mounted, node --watch): ```bash docker compose up --build ``` **Production** (optimized image, no source mount, persistent logs volume): ```bash docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build ``` After the first build you can usually omit `--build`: ```bash docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d ``` See [docker-compose.prod.yml](docker-compose.prod.yml) for the production-specific settings. **Important for production images**: - All secrets must come from `.env` (via `env_file`). The application now enforces this via `src/config/secrets.js`. - `config/config.json` must **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 `node` user (uid 1000). On Linux hosts ensure your `./data` and `./logs` host 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 `/health` and `/healthz` endpoints 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 ... config` to 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 `db` instance) over creating new connections. - If you need to talk to Webex for normal bot operations, go through `webexService` rather than calling `botClient` directly. - All AI summarization should live in or be called from `src/integrations/xai/`. - Respect the production database path resolution — never hardcode a `.db` path. - 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.