fix: suppress subagent APN completion and error events
parent
1d68cd288c
commit
d1d3d420bf
|
|
@ -28,6 +28,7 @@ type State = {
|
||||||
pair: Pair
|
pair: Pair
|
||||||
stop: () => void
|
stop: () => void
|
||||||
seen: Map<string, number>
|
seen: Map<string, number>
|
||||||
|
parent: Map<string, string | undefined>
|
||||||
gc: number
|
gc: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,6 +78,60 @@ function serverID(input: { relayURL: string; relaySecret: string }) {
|
||||||
return createHash("sha256").update(`${input.relayURL}|${input.relaySecret}`).digest("hex").slice(0, 16)
|
return createHash("sha256").update(`${input.relayURL}|${input.relaySecret}`).digest("hex").slice(0, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function recordSession(event: Event) {
|
||||||
|
if (!obj(event.properties)) return
|
||||||
|
const next = state
|
||||||
|
if (!next) return
|
||||||
|
|
||||||
|
if (event.type !== "session.created" && event.type !== "session.updated" && event.type !== "session.deleted") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = obj(event.properties.info) ? event.properties.info : undefined
|
||||||
|
const id = str(info?.id)
|
||||||
|
if (!id) return
|
||||||
|
|
||||||
|
if (event.type === "session.deleted") {
|
||||||
|
next.parent.delete(id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.parent.set(id, str(info?.parentID))
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeSession(sessionID: string) {
|
||||||
|
const next = state
|
||||||
|
if (!next) {
|
||||||
|
return {
|
||||||
|
sessionID,
|
||||||
|
subagent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const visited = new Set<string>()
|
||||||
|
let current = sessionID
|
||||||
|
let target = sessionID
|
||||||
|
let subagent = false
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (visited.has(current)) break
|
||||||
|
visited.add(current)
|
||||||
|
|
||||||
|
if (!next.parent.has(current)) break
|
||||||
|
const parentID = next.parent.get(current)
|
||||||
|
if (!parentID) break
|
||||||
|
|
||||||
|
subagent = true
|
||||||
|
target = parentID
|
||||||
|
current = parentID
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sessionID: target,
|
||||||
|
subagent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classify an IPv4 address into a reachability tier.
|
* Classify an IPv4 address into a reachability tier.
|
||||||
* Lower number = more likely reachable from an external/overlay network device.
|
* Lower number = more likely reachable from an external/overlay network device.
|
||||||
|
|
@ -167,17 +222,21 @@ function list(hostname: string, port: number, advertised: string[] = []) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function map(event: Event): { type: Type; sessionID: string } | undefined {
|
function map(event: Event): { type: Type; sessionID: string } | undefined {
|
||||||
|
recordSession(event)
|
||||||
|
|
||||||
if (!obj(event.properties)) return
|
if (!obj(event.properties)) return
|
||||||
|
|
||||||
if (event.type === "permission.asked") {
|
if (event.type === "permission.asked") {
|
||||||
const sessionID = str(event.properties.sessionID)
|
const sessionID = str(event.properties.sessionID)
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
return { type: "permission", sessionID }
|
const route = routeSession(sessionID)
|
||||||
|
return { type: "permission", sessionID: route.sessionID }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.type === "session.error") {
|
if (event.type === "session.error") {
|
||||||
const sessionID = str(event.properties.sessionID)
|
const sessionID = str(event.properties.sessionID)
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
|
if (routeSession(sessionID).subagent) return
|
||||||
if (!shouldNotifyError(event.properties.error)) return
|
if (!shouldNotifyError(event.properties.error)) return
|
||||||
return { type: "error", sessionID }
|
return { type: "error", sessionID }
|
||||||
}
|
}
|
||||||
|
|
@ -187,6 +246,7 @@ function map(event: Event): { type: Type; sessionID: string } | undefined {
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
if (!obj(event.properties.status)) return
|
if (!obj(event.properties.status)) return
|
||||||
if (event.properties.status.type !== "idle") return
|
if (event.properties.status.type !== "idle") return
|
||||||
|
if (routeSession(sessionID).subagent) return
|
||||||
return { type: "complete", sessionID }
|
return { type: "complete", sessionID }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,6 +465,7 @@ export namespace PushRelay {
|
||||||
pair,
|
pair,
|
||||||
stop: unsub,
|
stop: unsub,
|
||||||
seen: new Map(),
|
seen: new Map(),
|
||||||
|
parent: new Map(),
|
||||||
gc: 0,
|
gc: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,16 @@ function emit(type: string, properties: unknown) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function created(sessionID: string, parentID?: string) {
|
||||||
|
emit("session.created", {
|
||||||
|
sessionID,
|
||||||
|
info: {
|
||||||
|
id: sessionID,
|
||||||
|
parentID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function waitForCalls(count: number) {
|
async function waitForCalls(count: number) {
|
||||||
for (let i = 0; i < 50; i++) {
|
for (let i = 0; i < 50; i++) {
|
||||||
if (fetchMock.mock.calls.length >= count) return
|
if (fetchMock.mock.calls.length >= count) return
|
||||||
|
|
@ -101,4 +111,43 @@ describe("push relay event mapping", () => {
|
||||||
await waitForCalls(1)
|
await waitForCalls(1)
|
||||||
expect(callBody()?.eventType).toBe("permission")
|
expect(callBody()?.eventType).toBe("permission")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("does not relay subagent completion events", async () => {
|
||||||
|
created("ses_root")
|
||||||
|
created("ses_subagent", "ses_root")
|
||||||
|
|
||||||
|
emit("session.status", {
|
||||||
|
sessionID: "ses_subagent",
|
||||||
|
status: { type: "idle" },
|
||||||
|
})
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 40))
|
||||||
|
expect(fetchMock.mock.calls.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("does not relay subagent errors", async () => {
|
||||||
|
created("ses_root")
|
||||||
|
created("ses_subagent", "ses_root")
|
||||||
|
|
||||||
|
emit("session.error", {
|
||||||
|
sessionID: "ses_subagent",
|
||||||
|
error: { name: "UnknownError", data: { message: "boom" } },
|
||||||
|
})
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 40))
|
||||||
|
expect(fetchMock.mock.calls.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("relays subagent permission prompts to parent session", async () => {
|
||||||
|
created("ses_root")
|
||||||
|
created("ses_subagent", "ses_root")
|
||||||
|
|
||||||
|
emit("permission.asked", {
|
||||||
|
sessionID: "ses_subagent",
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitForCalls(1)
|
||||||
|
expect(callBody()?.eventType).toBe("permission")
|
||||||
|
expect(callBody()?.sessionID).toBe("ses_root")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue