feat(opencode): add observability logging to push relay and session status

Add structured logging to the push notification pipeline so events
can be traced end-to-end when --print-logs is enabled:
- push-relay map(): log every classification decision and skip reason
- push-relay dedupe(): log suppressed duplicates with elapsed time
- push-relay start()/stop(): log init failures and teardown
- session/status: log every idle/busy/retry transition
This commit is contained in:
Ryan Vogel 2026-04-08 15:30:58 +00:00
parent d034a61635
commit f29cb81c7d
2 changed files with 66 additions and 12 deletions

View file

@ -230,24 +230,54 @@ function map(event: Event): { type: Type; sessionID: string } | undefined {
const sessionID = str(event.properties.sessionID)
if (!sessionID) return
const route = routeSession(sessionID)
log.info("map: matched permission.asked", {
eventType: event.type,
sessionID: route.sessionID,
originalSessionID: sessionID,
subagent: route.subagent,
})
return { type: "permission", sessionID: route.sessionID }
}
if (event.type === "session.error") {
const sessionID = str(event.properties.sessionID)
if (!sessionID) return
if (routeSession(sessionID).subagent) return
if (!shouldNotifyError(event.properties.error)) return
const route = routeSession(sessionID)
if (route.subagent) {
log.info("map: skipped session.error (subagent)", { sessionID })
return
}
if (!shouldNotifyError(event.properties.error)) {
log.info("map: skipped session.error (suppressed error type)", {
sessionID,
errorName: obj(event.properties.error) ? str(event.properties.error.name) : undefined,
})
return
}
log.info("map: matched session.error", { sessionID })
return { type: "error", sessionID }
}
if (event.type !== "session.status") return
const sessionID = str(event.properties.sessionID)
if (!sessionID) return
if (!obj(event.properties.status)) return
if (event.properties.status.type !== "idle") return
if (routeSession(sessionID).subagent) return
return { type: "complete", sessionID }
if (event.type === "session.status") {
const sessionID = str(event.properties.sessionID)
if (!sessionID) return
if (!obj(event.properties.status)) return
const statusType = str(event.properties.status.type)
if (statusType !== "idle") {
log.info("map: skipped session.status (non-idle)", { sessionID, statusType })
return
}
const route = routeSession(sessionID)
if (route.subagent) {
log.info("map: skipped session.status idle (subagent)", { sessionID })
return
}
log.info("map: matched session.status idle", { sessionID })
return { type: "complete", sessionID }
}
// not a push-eligible event type
return
}
function text(input: string) {
@ -366,7 +396,15 @@ function dedupe(input: { type: Type; sessionID: string }) {
const prev = next.seen.get(key)
next.seen.set(key, now)
if (!prev) return false
return now - prev < 5_000
const isDupe = now - prev < 5_000
if (isDupe) {
log.info("dedupe: suppressed duplicate", {
type: input.type,
sessionID: input.sessionID,
elapsedMs: now - prev,
})
}
return isDupe
}
async function post(input: { type: Type; sessionID: string }) {
@ -436,8 +474,14 @@ export namespace PushRelay {
export function start(input: Input) {
const relayURL = norm(input.relayURL.trim())
const relaySecret = input.relaySecret.trim()
if (!relayURL) return
if (!relaySecret) return
if (!relayURL) {
log.warn("start: relay URL is empty, push relay disabled")
return
}
if (!relaySecret) {
log.warn("start: relay secret is empty, push relay disabled")
return
}
stop()
@ -480,6 +524,7 @@ export namespace PushRelay {
export function stop() {
const next = state
if (!next) return
log.info("stopping push relay")
state = undefined
next.stop()
}

View file

@ -2,10 +2,13 @@ import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { InstanceState } from "@/effect/instance-state"
import { makeRuntime } from "@/effect/run-service"
import { Log } from "@/util/log"
import { SessionID } from "./schema"
import { Effect, Layer, ServiceMap } from "effect"
import z from "zod"
const log = Log.create({ service: "session-status" })
export namespace SessionStatus {
export const Info = z
.union([
@ -72,6 +75,12 @@ export namespace SessionStatus {
const set = Effect.fn("SessionStatus.set")(function* (sessionID: SessionID, status: Info) {
const data = yield* InstanceState.get(state)
const prev = data.get(sessionID)
log.info("session status change", {
sessionID,
from: prev?.type ?? "idle",
to: status.type,
})
yield* bus.publish(Event.Status, { sessionID, status })
if (status.type === "idle") {
yield* bus.publish(Event.Idle, { sessionID })