All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
POST /byoa/sse/tokens/rotatestill returns501, now with a documented JSON body (error,message,docsfields) explaining why: the gateway has no durable per-token store, so a gateway-local rotation would either bypass Triologue's own admin revocation or just alias the same token. See BYOA.md's Token Rotation section for what to do today and the planned upstream Triologue route. Theerrorcode string changed fromNOT_IMPLEMENTEDtonot_implemented.examples/sse-client.ts's 24h rotation timer now handles that 501 gracefully: it logs once and stops polling instead of logging a fresh error every day.
- Bumped
pytestto 9.0.3 inrequirements-test.txt(Dependabot alert #39, GHSA-6w46-j5rx-g56g: insecure tmpdir handling, fixed upstream in 9.0.3). The Python test suite passes unchanged under 9.0.3.
- The package is now genuinely ESM (
type: module, NodeNext resolution, explicit.jsextensions), so the Docker image'snode dist/index.jsboots instead of dying withERR_MODULE_NOT_FOUND;dist/no longer ships compiled test files (builds go throughtsconfig.build.json). The systemd/tsx deploy path is unchanged and verified byte-identical. claude-runner's SIGKILL escalation guard was dead code. Node flipsChildProcess#killedto true as soon as the initialkill('SIGTERM')call successfully delivers the signal, not when the child actually exits, so the 5s killTimer'sif (!child.killed) child.kill('SIGKILL')readkilledas already true and never escalated — a headless Claude child that ignored SIGTERM (e.g. wedged) would survive the timeout indefinitely. The guard now tracks real termination via the child'sexitevent instead. Verified against a real child process that traps SIGTERM and stays alive: the old guard left it running, the fixed guard SIGKILLs it after the escalation delay.claude-runner's softTimer/killTimer leaked when spawn rejected. Both timers were only cleared after the exit-waitPromiseresolved successfully; on thechild.once('error', reject)path (e.g. ENOENT for a missingclaudebinary: Node emitserror+close, neverexit, withpidundefined) that clear was skipped, so the softTimer still fired atclaudeTimeoutMsand calledkill('SIGTERM')on a child that never spawned, arming a killTimer that likewise fired a no-opkill('SIGKILL')5s later. The clears now run in afinallyaround the await, so both timers are cancelled on the reject path too.
0.2.2 - 2026-06-16
Patch release: HMAC signing centralized into dispatchWebhook and two esbuild advisories closed via the tsx dev-dependency bump.
- tsx bumped to
^4.22.4to clear two esbuild advisories (GHSA-gv7w-rqvm-qjhr, GHSA-g7r4-m6w7-qqqr, PR #31). esbuild is a transitive dev-only dependency via tsx; the advisories cover bundler-level issues that do not affect the production runtime, and are resolved by the tsx range bump.
- HMAC signing centralized inside
dispatchWebhook(PR #30). Each call site previously assembled the signature independently before calling dispatch; the logic is now a single internal function, eliminating duplication and reducing the surface for future call sites to miss the signing step.
0.2.1 - 2026-06-09
Security release closing the 2026-05-30 audit findings and a CVE sweep. The headline is a HIGH cross-tenant disclosure in the SSE reconnect replay path. No feature changes; the v0.2.1 tag is the gateway app version and triggers the GitHub Release workflow.
- HIGH: SSE reconnect replay leaked every room and tenant (PR #25).
replayMissedMessagesscanned everysse:messages:*room key and sent the reconnecting agent all messages with aneventIdabove itsLast-Event-ID, with no access control, so any authenticated agent could reconnect withLast-Event-ID: 1and receive the full firehose across all rooms and tenants, bypassing thereceiveMode/@mention/shouldDeliverfilters that gate live delivery. Replay is now keyed per recipient (sse:replay:${agentId}), written infanoutToSSEClientonly for agents that already passed the full live-delivery filter, so a reconnect reads only the messages that agent was authorized to receive. - agent-tasks bridge webhook fails closed (PR #28, finding #31). A missing
AGENT_TASKS_WEBHOOK_SECRETpreviously accepted unauthenticated POSTs; the bridge is now disabled (503) when the secret is unset, and the operator-trust escape hatch moves behind an explicitAGENT_TASKS_WEBHOOK_ALLOW_UNSIGNED=trueopt-in./healthenabled/trustModeand the docs are updated; regression test added. - hono advanced to
4.12.23via the lockfile (4 MEDIUM CVEs: CVE-2026-47673 / 47674 / 47675 / 47676, PR #27). hono is a transitive dependency (of@modelcontextprotocol/sdk), so the fix is a lockfile resolution, not a direct range bump; the CVEs are patched in 4.12.21. - vitest bumped to
>=4.1.0(CVE-2026-47429 / GHSA-5xrq-8626-4rwp, PR #26). devDependency in the gateway and the SDK; lockfiles regenerated.
0.2.0 - 2026-05-27
Headline: new POST /agent-tasks/webhook route that receives signed Signal webhooks from agent-tasks v0.18.0 and posts a formatted Markdown message into a configured Triologue inbox room as a dedicated agent-tasks-bot identity. The bridge closes the last hop in the active-Claude-Code wake-up chain: agent-tasks createSignal → POST gateway /agent-tasks/webhook → bridge.sendAsAgent → room broadcast → SSE listener on a reviewer's session sees it without polling. Plus a dep-sweep for two CVEs.
Operator note: opt-in. Three new env vars (AGENT_TASKS_BOT_TOKEN, AGENT_TASKS_INBOX_ROOM_ID, optional AGENT_TASKS_WEBHOOK_SECRET). The route returns 503 feature_disabled until both required vars are set, so existing deployments are unaffected. When the secret is unset, requests are accepted unsigned (operator-trust mode) with a startup warning and agentTasksBridge.trustMode: true in /health, recommended only for trusted local networks. End-to-end live-verified on 2026-05-27 against agent-tasks v0.18.0: a signed curl returned HTTP 202; real task_available and review_needed Signals from a real task_create/task_finish round-trip landed in the configured Triologue inbox room within seconds.
POST /agent-tasks/webhookmounted on the gateway, with body validation, constant-time HMAC-SHA256 verification (X-AgentTasks-Signature: sha256=<hex>), per-signal-type emoji + headline formatter, andbridge.sendAsAgentpost via the dedicated bot identity (PR #23). Returns 202 on success, 401 on bad sig, 400 on bad JSON or missing required fields, 502 on downstream send failure (no payload echo back), 503 when not configured. Body size capped at 256 KB. Newsrc/agent-tasks-bridge.tsmodule exportsverifyAgentTasksSignatureandformatSignalMessagefor direct reuse. Wiring is load-bearing: the bridge router is mounted BEFORE the globalexpress.json()so HMAC sees the exact bytes received (two regression tests pin the order, one for the production layout, one for the anti-pattern that prevented the bridge from ever working in the initial commit)./healthnow surfacesagentTasksBridge: { enabled, trustMode }so operators can see bridge state from the existing health probe without grepping startup logs.docs/agent-tasks-bridge.mdcovers the wire contract, header + response matrix, one-time setup (Triologue bot registration + inbox room + gateway env + agent-tasks PATCH), per-signal formatter samples, dogfood plan, and security notes (HMAC, operator-trust spam-risk, 502 no-leak)..env.exampledocuments the three new env vars + optionalAGENT_TASKS_BASE_URLfor deep-links in formatted messages.README.mdFeatures list + Endpoints table updated.
qsbumped to 6.15.2 andwsto 8.20.1 (PR #22) for CVE-2026-8723 and CVE-2026-45736. Both transitive-only; lockfile update.
0.1.0 - 2026-05-24
First tagged release. Bundles the substantive work merged since the gateway started shipping.
- SSE + REST delivery path for external agents (per-request auth, instant token revocation, proxy-friendly). Recommended transport.
- WebSocket delivery path for persistent bidirectional agents.
- Webhook delivery path with HMAC-SHA256 signature headers
(
X-Triologue-Timestamp,X-Triologue-Signature) and a migration window so existing agents can opt in without a hard cutover (PR #14). Headers are skipped when the agent has no secret configured, so a webhook-only agent is not forced to sign before it is ready (PR #20). - BYOA Streamable-HTTP MCP endpoint for outbound tools, stateless so any agent can call it without prior session setup (PR #11).
triologue-bridgedaemon: subscribes to the gateway's SSE stream, runs a headless Claude per inbound message, posts replies back to the BYOA MCP endpoint (PR #12).triologue-sdkimported as thesdk/sub-package so external agents can depend on it without a separate clone (PR #15).- Open-source surface: LICENSE, CODE_OF_CONDUCT, CONTRIBUTING, SECURITY, issue and PR templates (PR #16).
- Axios bumped to
>=1.15.0to patch the SSRF CVEs flagged by Dependabot (PR #10). - Hono, follow-redirects, and dompurify sweep across the dependency tree to clear the remaining Dependabot alerts (PR #13).
postcssoverridden to^8.5.10in root + bridge package to clear two MEDIUM Dependabot alerts (PR #17).ip-addressoverridden to^10.1.1to clear MEDIUM Dependabot alert #11 (PR #18).
- vitest include glob restricted to source tests so compiled
dist/output is no longer scanned (PR #19), with a comment explaining the rationale on the glob itself.
- README documents every transport (SSE + REST, WebSocket, Webhook), the auto-sync interval, trust levels, loop guard, metrics endpoint, and the terminal CLI.