Domain: Integrations — Function Reference

Webhooks, notifications, and git auto-commit runtime settings. State persisted as JSON at data/state/integrations.json. Uses a file-level lock (withLock) plus an in-process serial write queue (stateWriteQueue) for safe concurrent mutations. Outbound HTTP uses fetch with timeout and exponential-backoff retry. Diagnostic hooks (emit metric/event/error) are injectable for observability.

MCP Tools Exposed

Exposed through unified controller. Key tool names: integrations_state, integration_webhook_list, integration_webhook_register, integration_webhook_delete, integration_git_auto_commit_get, integration_git_auto_commit_set, integration_notify, integration_emit_event.

Tool Name Description Key Inputs Returns
integrations_state Get full integrations state none State object with webhooks, git_auto_commit, notifications
integration_webhook_list List webhooks none Array of webhook summaries (no secrets)
integration_webhook_register Register or update a webhook url, events?, secret? { created, webhook }
integration_webhook_delete Delete a webhook webhookId: string { removed: boolean, webhook_id }
integration_git_auto_commit_get Get git auto-commit config none { enabled, on_events[] }
integration_git_auto_commit_set Update git auto-commit config enabled?, on_events? { enabled, on_events[] }
integration_notify Send a notification channel, message, priority?, webhook_url?, attachments?, timeout_ms?, retry_count? { success, notification_id, delivered, channel, priority, delivery }
integration_emit_event Fire webhooks for an event eventName, payload? { sent, failed, skipped, deliveries[] }

Core Functions

setDiagnosticHooks(hooks)

Purpose: Inject observability hooks (emitMetric, emitEvent, emitError) for metrics/tracing. Parameters: hooks: { emitMetric?, emitEvent?, emitError? } Side effects: Sets module-level diagnosticHooks.

getIntegrationState()

Purpose: Read and return the full normalized integrations state. Returns: State object (webhooks[], git_auto_commit, notifications[]).

listWebhooks()

Purpose: Return all webhooks without secrets. Returns: Array of { id, url, events[], has_secret, created_at, updated_at }.

registerWebhook({ url, events, secret })

Purpose: Register a new webhook or update an existing one (matched by URL). Parameters: url: string (http/https required), events?: string[], secret?: string Returns: Promise<{ created: boolean, webhook }> Side effects: Writes state atomically via updateState. Emits metrics/events. Notes for rewrite: If URL already exists, updates events/secret/updated_at. Does NOT create a duplicate.

deleteWebhook(webhookId)

Purpose: Remove a webhook by ID. Parameters: webhookId: string Returns: Promise<{ removed: boolean, webhook_id }>

getGitAutoCommitConfig()

Purpose: Return current git auto-commit settings. Returns: { enabled: boolean, on_events: string[] }

setGitAutoCommitConfig({ enabled, on_events })

Purpose: Update git auto-commit enabled flag and/or trigger events. Parameters: enabled?: boolean, on_events?: string[] Returns: Promise<{ enabled, on_events[] }>

sendNotification(options)

Purpose: Send a notification via webhook URL (for slack/discord/webhook channels) and log to history. Parameters: options: { channel: slack|discord|email|webhook, message, priority?, webhook_url?, attachments?, timeout_ms?, retry_count? } Returns: Promise<{ success, notification_id, delivered, channel, priority, delivery }> Notes for rewrite: Channels email has no outbound delivery logic — only stored in history. Delivery uses postJsonWithRetry with timeout + backoff.

emitWebhookEvent(eventName, payload)

Purpose: Fire all registered webhooks subscribed to a given event. Parameters: eventName: string, payload: object Returns: Promise<{ sent, failed, skipped, deliveries[] }> Notes for rewrite: Signs payload with HMAC-SHA256(secret, body) if webhook has a secret. Header: X-AMS-Signature.

resolveEventName(toolName, args, result)

Purpose: Map a tool call to an integration event name. Parameters: toolName: string, args?: object, result?: object Returns: string|null Mapping table: | Tool | Event | |——|——-| | audit_session_start | session.start | | audit_session_end | session.end | | task_create, task_create_batch | task.created | | task_update (status=done) | task.completed | | task_sync_to_gsd | task.sync | | roadmap_export_to_gsd | roadmap.exported | | all others | null |

Database Operations

None. All state is persisted in data/state/integrations.json.

Key Algorithms

Concurrency-Safe State Mutation (updateState)

enqueueStateMutation(task):
  stateWriteQueue = stateWriteQueue.then(task, task)  // serial promise chain
  return operation

inside task:
  return withStateFileLock(async () => {
    state = readState()
    result = await mutator(state)      // caller mutates state in-place
    nextState = writeState(state)
    return { state: nextState, result }
  }, timeout=5000ms)

Two-layer protection: in-process queue (single writer) + file lock (multi-process safety).

Atomic Write (writeState)

serialized = JSON.stringify(normalizeState(state), null, 2) + "\n"
tempPath = STATE_PATH + "." + pid + "." + Date.now() + ".tmp"
writeFileSync(tempPath, serialized)
try:
  renameSync(tempPath, STATE_PATH)    // atomic on POSIX
catch EPERM/EEXIST:
  copyFileSync(tempPath, STATE_PATH)  // Windows fallback
finally:
  rmSync(tempPath, { force: true })

HTTP with Timeout + Exponential Backoff (postJsonWithRetry)

for attempt in 0..safeRetryCount:
  try:
    response = fetchWithTimeout(url, { method: POST, headers, body }, timeoutMs)
    if response.status not retryable or attempt >= maxRetry: return { response }
  catch retryable error:
    if attempt >= maxRetry: return { error }
  delay = 100ms * 2^attempt
  sleep(delay)
return exhausted error

Retryable HTTP statuses: 408, 429, 5xx. Retryable errors: AbortError, ETIMEDOUT, ECONNRESET, ECONNREFUSED, EAI_AGAIN, ENETUNREACH, timeout/network/fetch-failed messages.

Webhook Signature

signature = HMAC-SHA256(webhook.secret, bodyString).hex()
header: X-AMS-Signature: {signature}

No signature sent if webhook.secret is empty.

Notification Channel Payload Shape

slack:   { text: message, attachments: [] }
discord: { content: message, embeds: [] }
default: { message, attachments: [] }

State Shape

{
  "version": 1,
  "updated_at": "ISO8601",
  "webhooks": [{ "id", "url", "events", "secret", "created_at", "updated_at" }],
  "git_auto_commit": { "enabled": false, "on_events": ["session.end", "roadmap.exported", "task.sync"] },
  "notifications": [{ "id", "channel", "message", "priority", "delivered", "created_at", "meta" }]
}

Max notification history: 200 entries (pruned on write).

INTEGRATION_EVENTS (all valid event names)

session.start, session.end, task.created, task.completed, roadmap.exported, task.sync, error

AUTO_COMMIT_DEFAULT_EVENTS

session.end, roadmap.exported, task.sync


Back to top

Colibri — documentation-first MCP runtime. Apache 2.0 + Commons Clause.

This site uses Just the Docs, a documentation theme for Jekyll.