fix(openclaw): register reply correlation from runtime dispatch
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
|
||||
import * as openclawModule from "../index"
|
||||
import * as sessionRegistryModule from "../session-registry"
|
||||
import { dispatchOpenClawEvent } from "../runtime-dispatch"
|
||||
import type { OpenClawConfig } from "../types"
|
||||
|
||||
function createConfig(hooks: OpenClawConfig["hooks"]): OpenClawConfig {
|
||||
return {
|
||||
enabled: true,
|
||||
gateways: {
|
||||
gateway: {
|
||||
type: "http",
|
||||
url: "https://example.com",
|
||||
method: "POST",
|
||||
},
|
||||
},
|
||||
hooks,
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
describe("dispatchOpenClawEvent", () => {
|
||||
test("falls back from raw session.created to canonical session-start", async () => {
|
||||
const wakeSpy = spyOn(openclawModule, "wakeOpenClaw")
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce({ gateway: "gateway", success: true })
|
||||
|
||||
await dispatchOpenClawEvent({
|
||||
config: createConfig({
|
||||
"session-start": { enabled: true, gateway: "gateway", instruction: "hi" },
|
||||
}),
|
||||
rawEvent: "session.created",
|
||||
context: { sessionId: "ses-1", projectPath: "/tmp/project", tmuxPaneId: "%1", tmuxSession: "main" },
|
||||
})
|
||||
|
||||
expect(wakeSpy.mock.calls.map((call) => call[1])).toEqual(["session.created", "session-start"])
|
||||
})
|
||||
|
||||
test("registers reply correlation when wake returns outbound metadata", async () => {
|
||||
spyOn(openclawModule, "wakeOpenClaw").mockResolvedValue({
|
||||
gateway: "gateway",
|
||||
success: true,
|
||||
messageId: "msg-1",
|
||||
platform: "discord",
|
||||
channelId: "chan-1",
|
||||
threadId: "thread-1",
|
||||
})
|
||||
const registerSpy = spyOn(sessionRegistryModule, "registerMessage").mockReturnValue(true)
|
||||
|
||||
await dispatchOpenClawEvent({
|
||||
config: createConfig({
|
||||
"session.created": { enabled: true, gateway: "gateway", instruction: "hi" },
|
||||
}),
|
||||
rawEvent: "session.created",
|
||||
context: {
|
||||
sessionId: "ses-1",
|
||||
projectPath: "/tmp/project",
|
||||
tmuxPaneId: "%7",
|
||||
tmuxSession: "session-1",
|
||||
},
|
||||
})
|
||||
|
||||
const [mapping] = registerSpy.mock.calls[0] ?? []
|
||||
expect(mapping).toMatchObject({
|
||||
sessionId: "ses-1",
|
||||
tmuxPaneId: "%7",
|
||||
tmuxSession: "session-1",
|
||||
projectPath: "/tmp/project",
|
||||
platform: "discord-bot",
|
||||
messageId: "msg-1",
|
||||
channelId: "chan-1",
|
||||
threadId: "thread-1",
|
||||
})
|
||||
})
|
||||
|
||||
test("cleans up session mappings on session.deleted", async () => {
|
||||
spyOn(openclawModule, "wakeOpenClaw").mockResolvedValue(null)
|
||||
const removeSpy = spyOn(sessionRegistryModule, "removeSession").mockImplementation(() => {})
|
||||
|
||||
await dispatchOpenClawEvent({
|
||||
config: createConfig({}),
|
||||
rawEvent: "session.deleted",
|
||||
context: { sessionId: "ses-2", projectPath: "/tmp/project" },
|
||||
})
|
||||
|
||||
expect(removeSpy).toHaveBeenCalledWith("ses-2")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,89 @@
|
||||
import * as openclaw from "./index"
|
||||
import { registerMessage, removeSession } from "./session-registry"
|
||||
import { getCurrentTmuxSession } from "./tmux"
|
||||
import type { OpenClawConfig, WakeResult } from "./types"
|
||||
|
||||
interface DispatchOpenClawContext {
|
||||
sessionId?: string
|
||||
projectPath?: string
|
||||
tmuxPaneId?: string
|
||||
tmuxSession?: string
|
||||
replyChannel?: string
|
||||
replyTarget?: string
|
||||
replyThread?: string
|
||||
}
|
||||
|
||||
interface DispatchOpenClawEventParams {
|
||||
config: OpenClawConfig
|
||||
rawEvent: string
|
||||
context: DispatchOpenClawContext
|
||||
}
|
||||
|
||||
function mapRawEventToOpenClawEvents(rawEvent: string): string[] {
|
||||
const aliases: Record<string, string> = {
|
||||
"session.created": "session-start",
|
||||
"session.deleted": "session-end",
|
||||
"session.idle": "stop",
|
||||
}
|
||||
|
||||
const mapped = aliases[rawEvent]
|
||||
return Array.from(new Set([rawEvent, mapped].filter((value): value is string => Boolean(value))))
|
||||
}
|
||||
|
||||
function normalizePlatform(platform?: string): string | undefined {
|
||||
if (!platform) return undefined
|
||||
if (platform === "discord") return "discord-bot"
|
||||
return platform
|
||||
}
|
||||
|
||||
function shouldRegisterReplyCorrelation(result: WakeResult, params: DispatchOpenClawEventParams): boolean {
|
||||
if (params.rawEvent === "session.deleted") return false
|
||||
if (!result.success) return false
|
||||
if (!result.messageId || !result.platform) return false
|
||||
if (!params.context.sessionId || !params.context.projectPath || !params.context.tmuxPaneId) return false
|
||||
return true
|
||||
}
|
||||
|
||||
export async function dispatchOpenClawEvent(
|
||||
params: DispatchOpenClawEventParams,
|
||||
): Promise<WakeResult | null> {
|
||||
let result: WakeResult | null = null
|
||||
|
||||
if (params.config.enabled) {
|
||||
for (const event of mapRawEventToOpenClawEvents(params.rawEvent)) {
|
||||
result = await openclaw.wakeOpenClaw(params.config, event, {
|
||||
sessionId: params.context.sessionId,
|
||||
projectPath: params.context.projectPath,
|
||||
tmuxSession: params.context.tmuxSession,
|
||||
replyChannel: params.context.replyChannel,
|
||||
replyTarget: params.context.replyTarget,
|
||||
replyThread: params.context.replyThread,
|
||||
})
|
||||
if (result !== null) break
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRegisterReplyCorrelation(result ?? { gateway: "", success: false }, params)) {
|
||||
const tmuxSession = params.context.tmuxSession ?? getCurrentTmuxSession()
|
||||
const platform = normalizePlatform(result?.platform)
|
||||
if (tmuxSession && platform && params.context.sessionId && params.context.projectPath && params.context.tmuxPaneId) {
|
||||
registerMessage({
|
||||
sessionId: params.context.sessionId,
|
||||
tmuxSession,
|
||||
tmuxPaneId: params.context.tmuxPaneId,
|
||||
projectPath: params.context.projectPath,
|
||||
platform,
|
||||
messageId: result!.messageId!,
|
||||
channelId: result?.channelId,
|
||||
threadId: result?.threadId,
|
||||
createdAt: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (params.rawEvent === "session.deleted" && params.context.sessionId) {
|
||||
removeSession(params.context.sessionId)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user