# 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`, `woAttach.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)

**Production** (default — optimized image, no source mount, `npm start`):

```bash
docker compose up -d --build
```

After the first build you can usually omit `--build`:

```bash
docker compose up -d
```

**Development** (live reload via `node --watch` — restarts on file changes; dev only):

```bash
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
```

See [docker-compose.yml](docker-compose.yml) and [docker-compose.dev.yml](docker-compose.dev.yml) for details.

**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. |
| `/attach` / `/upload` | WO space only               | Upload a photo or file to the linked ServiceChannel work order; optional caption becomes an SC note. |
| `/createWO`      | **1:1 DM only**                | Adaptive Card to create a new work order in ServiceChannel; Webex group space is created automatically via webhook. |

**Create WO (new)**: In a direct message with ServChan, run `/createWO` to open a form (store, description, category, priority, optional problem code/NTE). Defaults come from env (`SC_CREATE_DEFAULT_*`). Admin discovery: `GET /create-wo-discovery?trade=Audio` (requires `ADMIN_TOKEN`).

**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.