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