fix(agent-switch): make handoff durable and sync CLI TUI selection
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
export function isTerminalFinishValue(finish: unknown): boolean {
|
||||
if (typeof finish === "boolean") {
|
||||
return finish
|
||||
}
|
||||
|
||||
if (typeof finish === "string") {
|
||||
const normalized = finish.toLowerCase()
|
||||
return normalized !== "" && normalized !== "tool-calls" && normalized !== "unknown"
|
||||
}
|
||||
|
||||
if (typeof finish === "object" && finish !== null) {
|
||||
const record = finish as Record<string, unknown>
|
||||
const kind = record.type ?? record.reason
|
||||
if (typeof kind === "string") {
|
||||
const normalized = kind.toLowerCase()
|
||||
return normalized !== "" && normalized !== "tool-calls" && normalized !== "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function isTerminalStepFinishPart(part: unknown): boolean {
|
||||
if (typeof part !== "object" || part === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
const record = part as Record<string, unknown>
|
||||
if (record.type !== "step-finish") {
|
||||
return false
|
||||
}
|
||||
|
||||
return isTerminalFinishValue(record.reason)
|
||||
}
|
||||
|
||||
export function extractTextPartsFromMessageResponse(response: unknown): string {
|
||||
if (typeof response !== "object" || response === null) return ""
|
||||
const data = (response as Record<string, unknown>).data
|
||||
if (typeof data !== "object" || data === null) return ""
|
||||
const parts = (data as Record<string, unknown>).parts
|
||||
if (!Array.isArray(parts)) return ""
|
||||
|
||||
return parts
|
||||
.map((part) => {
|
||||
if (typeof part !== "object" || part === null) return ""
|
||||
const partRecord = part as Record<string, unknown>
|
||||
if (partRecord.type !== "text") return ""
|
||||
return typeof partRecord.text === "string" ? partRecord.text : ""
|
||||
})
|
||||
.filter((text) => text.length > 0)
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
export function detectFallbackHandoffTarget(messageText: string): "atlas" | "prometheus" | undefined {
|
||||
if (!messageText) return undefined
|
||||
|
||||
const normalized = messageText.toLowerCase()
|
||||
|
||||
if (/switching\s+to\s+\*{0,2}\s*prometheus\b/.test(normalized) || /handing\s+off\s+to\s+\*{0,2}\s*prometheus\b/.test(normalized)) {
|
||||
return "prometheus"
|
||||
}
|
||||
|
||||
if (/switching\s+to\s+\*{0,2}\s*atlas\b/.test(normalized) || /handing\s+off\s+to\s+\*{0,2}\s*atlas\b/.test(normalized)) {
|
||||
return "atlas"
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function buildFallbackContext(target: "atlas" | "prometheus"): string {
|
||||
if (target === "prometheus") {
|
||||
return "Athena indicated handoff to Prometheus. Continue from the current session context and produce the requested phased plan based on the council findings already gathered."
|
||||
}
|
||||
return "Athena indicated handoff to Atlas. Continue from the current session context and implement the agreed fixes from the council findings."
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { beforeEach, describe, expect, test } from "bun:test"
|
||||
import { createAgentSwitchHook } from "./hook"
|
||||
import {
|
||||
_resetForTesting,
|
||||
getPendingSwitch,
|
||||
setPendingSwitch,
|
||||
} from "../../features/agent-switch"
|
||||
import { _resetApplierForTesting, clearPendingSwitchRuntime } from "../../features/agent-switch/applier"
|
||||
|
||||
describe("agent-switch hook", () => {
|
||||
beforeEach(() => {
|
||||
_resetForTesting()
|
||||
_resetApplierForTesting()
|
||||
})
|
||||
|
||||
test("consumes pending switch only after successful promptAsync", async () => {
|
||||
const promptAsyncCalls: Array<Record<string, unknown>> = []
|
||||
let switched = false
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: async (args: Record<string, unknown>) => {
|
||||
promptAsyncCalls.push(args)
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Prometheus (Plan Builder)" } }] })
|
||||
: ({ data: [] }),
|
||||
message: async () => ({ data: { parts: [] } }),
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
setPendingSwitch("ses-1", "prometheus", "plan this")
|
||||
const hook = createAgentSwitchHook(ctx)
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.idle",
|
||||
properties: { sessionID: "ses-1" },
|
||||
},
|
||||
})
|
||||
|
||||
expect(promptAsyncCalls).toHaveLength(1)
|
||||
expect(getPendingSwitch("ses-1")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("keeps pending switch when promptAsync fails", async () => {
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: async () => {
|
||||
throw new Error("temporary failure")
|
||||
},
|
||||
messages: async () => ({ data: [] }),
|
||||
message: async () => ({ data: { parts: [] } }),
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
setPendingSwitch("ses-2", "atlas", "fix this")
|
||||
const hook = createAgentSwitchHook(ctx)
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.idle",
|
||||
properties: { sessionID: "ses-2" },
|
||||
},
|
||||
})
|
||||
|
||||
expect(getPendingSwitch("ses-2")).toEqual({
|
||||
agent: "atlas",
|
||||
context: "fix this",
|
||||
})
|
||||
|
||||
clearPendingSwitchRuntime("ses-2")
|
||||
})
|
||||
|
||||
test("retries after transient failure and eventually clears pending switch", async () => {
|
||||
let attempts = 0
|
||||
let switched = false
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: async () => {
|
||||
attempts += 1
|
||||
if (attempts === 1) {
|
||||
throw new Error("temporary failure")
|
||||
}
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Prometheus (Plan Builder)" } }] })
|
||||
: ({ data: [] }),
|
||||
message: async () => ({ data: { parts: [] } }),
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
setPendingSwitch("ses-3", "prometheus", "plan this")
|
||||
const hook = createAgentSwitchHook(ctx)
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.idle",
|
||||
properties: { sessionID: "ses-3" },
|
||||
},
|
||||
})
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 350))
|
||||
|
||||
expect(attempts).toBe(2)
|
||||
expect(getPendingSwitch("ses-3")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("clears pending switch on session.deleted", async () => {
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: async () => {},
|
||||
messages: async () => ({ data: [] }),
|
||||
message: async () => ({ data: { parts: [] } }),
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
setPendingSwitch("ses-4", "atlas", "fix this")
|
||||
const hook = createAgentSwitchHook(ctx)
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.deleted",
|
||||
properties: { info: { id: "ses-4" } },
|
||||
},
|
||||
})
|
||||
|
||||
expect(getPendingSwitch("ses-4")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("recovers missing switch_agent tool call from Athena handoff text", async () => {
|
||||
const promptAsyncCalls: Array<Record<string, unknown>> = []
|
||||
let switched = false
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: async (args: Record<string, unknown>) => {
|
||||
promptAsyncCalls.push(args)
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Prometheus (Plan Builder)" } }] })
|
||||
: ({ data: [] }),
|
||||
message: async () => ({
|
||||
data: {
|
||||
parts: [
|
||||
{
|
||||
type: "text",
|
||||
text: "Switching to **Prometheus** now — they'll take it from here and craft a plan for you!",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
const hook = createAgentSwitchHook(ctx)
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: {
|
||||
id: "msg-athena-1",
|
||||
sessionID: "ses-5",
|
||||
role: "assistant",
|
||||
agent: "Athena (Council)",
|
||||
finish: "stop",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(promptAsyncCalls).toHaveLength(1)
|
||||
const body = promptAsyncCalls[0]?.body as { agent?: string } | undefined
|
||||
expect(body?.agent).toBe("Prometheus (Plan Builder)")
|
||||
expect(getPendingSwitch("ses-5")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("applies queued pending switch on terminal message.updated", async () => {
|
||||
const promptAsyncCalls: Array<Record<string, unknown>> = []
|
||||
let switched = false
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: async (args: Record<string, unknown>) => {
|
||||
promptAsyncCalls.push(args)
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Atlas (Plan Executor)" } }] })
|
||||
: ({ data: [] }),
|
||||
message: async () => ({ data: { parts: [] } }),
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
setPendingSwitch("ses-6", "atlas", "fix now")
|
||||
const hook = createAgentSwitchHook(ctx)
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: {
|
||||
id: "msg-6",
|
||||
sessionID: "ses-6",
|
||||
role: "assistant",
|
||||
agent: "Athena (Council)",
|
||||
finish: "stop",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(promptAsyncCalls).toHaveLength(1)
|
||||
const body = promptAsyncCalls[0]?.body as { agent?: string } | undefined
|
||||
expect(body?.agent).toBe("Atlas (Plan Executor)")
|
||||
expect(getPendingSwitch("ses-6")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("applies queued pending switch on terminal message.updated even when role is missing", async () => {
|
||||
const promptAsyncCalls: Array<Record<string, unknown>> = []
|
||||
let switched = false
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: async (args: Record<string, unknown>) => {
|
||||
promptAsyncCalls.push(args)
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Atlas (Plan Executor)" } }] })
|
||||
: ({ data: [] }),
|
||||
message: async () => ({ data: { parts: [] } }),
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
setPendingSwitch("ses-8", "atlas", "fix now")
|
||||
const hook = createAgentSwitchHook(ctx)
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: {
|
||||
id: "msg-8",
|
||||
sessionID: "ses-8",
|
||||
agent: "Athena (Council)",
|
||||
finish: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(promptAsyncCalls).toHaveLength(1)
|
||||
const body = promptAsyncCalls[0]?.body as { agent?: string } | undefined
|
||||
expect(body?.agent).toBe("Atlas (Plan Executor)")
|
||||
expect(getPendingSwitch("ses-8")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("applies queued pending switch on terminal message.part.updated step-finish", async () => {
|
||||
const promptAsyncCalls: Array<Record<string, unknown>> = []
|
||||
let switched = false
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: async (args: Record<string, unknown>) => {
|
||||
promptAsyncCalls.push(args)
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Atlas (Plan Executor)" } }] })
|
||||
: ({ data: [] }),
|
||||
message: async () => ({ data: { parts: [] } }),
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
setPendingSwitch("ses-7", "atlas", "fix now")
|
||||
const hook = createAgentSwitchHook(ctx)
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
info: {
|
||||
sessionID: "ses-7",
|
||||
role: "assistant",
|
||||
},
|
||||
part: {
|
||||
id: "part-finish-1",
|
||||
sessionID: "ses-7",
|
||||
type: "step-finish",
|
||||
reason: "stop",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(promptAsyncCalls).toHaveLength(1)
|
||||
const body = promptAsyncCalls[0]?.body as { agent?: string } | undefined
|
||||
expect(body?.agent).toBe("Atlas (Plan Executor)")
|
||||
expect(getPendingSwitch("ses-7")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("applies queued pending switch on session.status idle", async () => {
|
||||
const promptAsyncCalls: Array<Record<string, unknown>> = []
|
||||
let switched = false
|
||||
const ctx = {
|
||||
client: {
|
||||
session: {
|
||||
promptAsync: async (args: Record<string, unknown>) => {
|
||||
promptAsyncCalls.push(args)
|
||||
switched = true
|
||||
},
|
||||
messages: async () => switched
|
||||
? ({ data: [{ info: { role: "user", agent: "Atlas (Plan Executor)" } }] })
|
||||
: ({ data: [] }),
|
||||
message: async () => ({ data: { parts: [] } }),
|
||||
},
|
||||
},
|
||||
} as any
|
||||
|
||||
setPendingSwitch("ses-9", "atlas", "fix now")
|
||||
const hook = createAgentSwitchHook(ctx)
|
||||
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.status",
|
||||
properties: {
|
||||
sessionID: "ses-9",
|
||||
status: {
|
||||
type: "idle",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(promptAsyncCalls).toHaveLength(1)
|
||||
const body = promptAsyncCalls[0]?.body as { agent?: string } | undefined
|
||||
expect(body?.agent).toBe("Atlas (Plan Executor)")
|
||||
expect(getPendingSwitch("ses-9")).toBeUndefined()
|
||||
})
|
||||
})
|
||||
+178
-20
@@ -1,36 +1,194 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import { consumePendingSwitch } from "../../features/agent-switch"
|
||||
import { getPendingSwitch, setPendingSwitch } from "../../features/agent-switch"
|
||||
import { applyPendingSwitch, clearPendingSwitchRuntime } from "../../features/agent-switch/applier"
|
||||
import { getAgentConfigKey } from "../../shared/agent-display-names"
|
||||
import { log } from "../../shared/logger"
|
||||
import {
|
||||
buildFallbackContext,
|
||||
detectFallbackHandoffTarget,
|
||||
extractTextPartsFromMessageResponse,
|
||||
isTerminalFinishValue,
|
||||
isTerminalStepFinishPart,
|
||||
} from "./fallback-handoff"
|
||||
|
||||
const HOOK_NAME = "agent-switch" as const
|
||||
const processedFallbackMessages = new Set<string>()
|
||||
|
||||
function getSessionIDFromStatusEvent(input: { event: { properties?: Record<string, unknown> } }): string | undefined {
|
||||
const props = input.event.properties as Record<string, unknown> | undefined
|
||||
const fromProps = typeof props?.sessionID === "string" ? props.sessionID : undefined
|
||||
if (fromProps) {
|
||||
return fromProps
|
||||
}
|
||||
|
||||
const status = props?.status as Record<string, unknown> | undefined
|
||||
const fromStatus = typeof status?.sessionID === "string" ? status.sessionID : undefined
|
||||
return fromStatus
|
||||
}
|
||||
|
||||
function getStatusTypeFromEvent(input: { event: { properties?: Record<string, unknown> } }): string | undefined {
|
||||
const props = input.event.properties as Record<string, unknown> | undefined
|
||||
const directType = typeof props?.type === "string" ? props.type : undefined
|
||||
if (directType) {
|
||||
return directType
|
||||
}
|
||||
|
||||
const status = props?.status as Record<string, unknown> | undefined
|
||||
const statusType = typeof status?.type === "string" ? status.type : undefined
|
||||
return statusType
|
||||
}
|
||||
|
||||
export function createAgentSwitchHook(ctx: PluginInput) {
|
||||
return {
|
||||
event: async (input: { event: { type: string; properties?: Record<string, unknown> } }): Promise<void> => {
|
||||
if (input.event.type !== "session.idle") return
|
||||
if (input.event.type === "session.deleted") {
|
||||
const props = input.event.properties as Record<string, unknown> | undefined
|
||||
const info = props?.info as Record<string, unknown> | undefined
|
||||
const deletedSessionID = info?.id
|
||||
if (typeof deletedSessionID === "string") {
|
||||
clearPendingSwitchRuntime(deletedSessionID)
|
||||
for (const key of Array.from(processedFallbackMessages)) {
|
||||
if (key.startsWith(`${deletedSessionID}:`)) {
|
||||
processedFallbackMessages.delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const props = input.event.properties as Record<string, unknown> | undefined
|
||||
const sessionID = props?.sessionID as string | undefined
|
||||
if (!sessionID) return
|
||||
if (input.event.type === "message.updated") {
|
||||
const props = input.event.properties as Record<string, unknown> | undefined
|
||||
const info = props?.info as Record<string, unknown> | undefined
|
||||
const sessionID = typeof info?.sessionID === "string" ? info.sessionID : undefined
|
||||
const messageID = typeof info?.id === "string" ? info.id : undefined
|
||||
const agent = typeof info?.agent === "string" ? info.agent : undefined
|
||||
const finish = info?.finish
|
||||
|
||||
const pending = consumePendingSwitch(sessionID)
|
||||
if (!pending) return
|
||||
if (!sessionID) {
|
||||
return
|
||||
}
|
||||
|
||||
log(`[${HOOK_NAME}] Switching to ${pending.agent}`, { sessionID })
|
||||
const isTerminalAssistantUpdate = isTerminalFinishValue(finish)
|
||||
if (!isTerminalAssistantUpdate) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await ctx.client.session.promptAsync({
|
||||
path: { id: sessionID },
|
||||
body: {
|
||||
agent: pending.agent,
|
||||
parts: [{ type: "text", text: pending.context }],
|
||||
},
|
||||
query: { directory: ctx.directory },
|
||||
// Primary path: if switch_agent queued a pending switch, apply it as soon as
|
||||
// assistant turn is terminal (no reliance on session.idle timing).
|
||||
if (getPendingSwitch(sessionID)) {
|
||||
await applyPendingSwitch({
|
||||
sessionID,
|
||||
client: ctx.client,
|
||||
source: "message-updated",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!messageID) {
|
||||
return
|
||||
}
|
||||
|
||||
if (getAgentConfigKey(agent ?? "") !== "athena") {
|
||||
return
|
||||
}
|
||||
|
||||
const marker = `${sessionID}:${messageID}`
|
||||
if (processedFallbackMessages.has(marker)) {
|
||||
return
|
||||
}
|
||||
processedFallbackMessages.add(marker)
|
||||
|
||||
// If switch_agent already queued a handoff, do not synthesize fallback behavior.
|
||||
if (getPendingSwitch(sessionID)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await ctx.client.session.message({
|
||||
path: { id: sessionID, messageID },
|
||||
})
|
||||
const text = extractTextPartsFromMessageResponse(response)
|
||||
const target = detectFallbackHandoffTarget(text)
|
||||
if (!target) {
|
||||
return
|
||||
}
|
||||
|
||||
setPendingSwitch(sessionID, target, buildFallbackContext(target))
|
||||
log("[agent-switch] Recovered missing switch_agent tool call from Athena handoff text", {
|
||||
sessionID,
|
||||
messageID,
|
||||
target,
|
||||
})
|
||||
|
||||
await applyPendingSwitch({
|
||||
sessionID,
|
||||
client: ctx.client,
|
||||
source: "athena-message-fallback",
|
||||
})
|
||||
} catch (error) {
|
||||
log("[agent-switch] Failed to recover fallback handoff from Athena message", {
|
||||
sessionID,
|
||||
messageID,
|
||||
error: String(error),
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (input.event.type === "message.part.updated") {
|
||||
const props = input.event.properties as Record<string, unknown> | undefined
|
||||
const part = props?.part
|
||||
const info = props?.info as Record<string, unknown> | undefined
|
||||
const sessionIDFromPart = typeof (part as Record<string, unknown> | undefined)?.sessionID === "string"
|
||||
? ((part as Record<string, unknown>).sessionID as string)
|
||||
: undefined
|
||||
const sessionIDFromInfo = typeof info?.sessionID === "string" ? info.sessionID : undefined
|
||||
const sessionID = sessionIDFromPart ?? sessionIDFromInfo
|
||||
if (!sessionID) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!isTerminalStepFinishPart(part)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!getPendingSwitch(sessionID)) {
|
||||
return
|
||||
}
|
||||
|
||||
await applyPendingSwitch({
|
||||
sessionID,
|
||||
client: ctx.client,
|
||||
source: "message-part-step-finish",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
log(`[${HOOK_NAME}] Switch to ${pending.agent} complete`, { sessionID })
|
||||
} catch (err) {
|
||||
log(`[${HOOK_NAME}] Switch failed`, { sessionID, error: String(err) })
|
||||
if (input.event.type === "session.idle") {
|
||||
const props = input.event.properties as Record<string, unknown> | undefined
|
||||
const sessionID = props?.sessionID as string | undefined
|
||||
if (!sessionID) return
|
||||
|
||||
await applyPendingSwitch({
|
||||
sessionID,
|
||||
client: ctx.client,
|
||||
source: "idle",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (input.event.type === "session.status") {
|
||||
const sessionID = getSessionIDFromStatusEvent(input)
|
||||
const statusType = getStatusTypeFromEvent(input)
|
||||
if (!sessionID || statusType !== "idle") {
|
||||
return
|
||||
}
|
||||
|
||||
await applyPendingSwitch({
|
||||
sessionID,
|
||||
client: ctx.client,
|
||||
source: "status-idle",
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user