fix(plugin): run idle hooks for synthetic status idle
OpenCode now treats session.status idle as the durable completion boundary, but the plugin only dispatched the synthetic session.idle through the main hook chain. Idle-only side effects such as tmux forwarding and team member idle continuations were skipped. Route synthetic idle through the same idle-only hook path used by real session.idle events and pin the behavior with a regression test. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -208,6 +208,55 @@ describe("createEventHandler - idle deduplication", () => {
|
||||
expect(onEvent.mock.calls[0]?.[0]).toEqual(idleEvent.event)
|
||||
})
|
||||
|
||||
it("#given tmux integration enabled #when session.status reports idle #then synthetic idle forwards to tmuxSessionManager.onEvent", async () => {
|
||||
//#given
|
||||
const onEvent = mock<(event: EventInput["event"]) => void>(() => {})
|
||||
const eventHandler = createEventHandler({
|
||||
ctx: asEventHandlerContext({
|
||||
directory: "/tmp",
|
||||
client: {
|
||||
session: {},
|
||||
},
|
||||
}),
|
||||
pluginConfig: asPluginConfig({
|
||||
tmux: { enabled: true },
|
||||
}),
|
||||
firstMessageVariantGate: {
|
||||
markSessionCreated: () => {},
|
||||
clear: () => {},
|
||||
},
|
||||
managers: createEventHandlerManagers({
|
||||
tmuxSessionManager: {
|
||||
onEvent,
|
||||
onSessionCreated: async () => {},
|
||||
onSessionDeleted: async () => {},
|
||||
},
|
||||
}),
|
||||
hooks: createEventHandlerHooks({}),
|
||||
})
|
||||
|
||||
//#when
|
||||
await eventHandler(asEventHandlerInput({
|
||||
event: {
|
||||
type: "session.status",
|
||||
properties: {
|
||||
sessionID: "ses_tmux_synthetic_idle",
|
||||
status: { type: "idle" },
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
//#then
|
||||
expect(onEvent).toHaveBeenCalledTimes(1)
|
||||
expect(onEvent.mock.calls[0]?.[0]).toEqual({
|
||||
type: "session.idle",
|
||||
properties: {
|
||||
sessionID: "ses_tmux_synthetic_idle",
|
||||
synthetic: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("#given a readiness retry is pending #when session.idle arrives through the plugin handler #then tmux retry spawns the pane", async () => {
|
||||
//#given
|
||||
const sessionStatusData: Record<string, { type: string }> = {}
|
||||
|
||||
+10
-4
@@ -394,6 +394,12 @@ export function createEventHandler(args: {
|
||||
return hooks.sessionRecovery.handleInterruptedToolResultsOnIdle(sessionID);
|
||||
};
|
||||
|
||||
const dispatchIdleOnlyHooks = async (input: EventInput): Promise<void> => {
|
||||
managers.tmuxSessionManager?.onEvent?.(input.event);
|
||||
await runEventHookSafely("teamIdleWakeHint", teamIdleWakeHint, input);
|
||||
await runEventHookSafely("teamMemberStatusHandler", teamMemberStatusHandler, input);
|
||||
};
|
||||
|
||||
const getFallbackContinuationKeys = (fallbackContext?: FallbackContinuationContext): FallbackContinuationDedupeKeys => {
|
||||
const agentKey = fallbackContext?.agentName
|
||||
? getAgentConfigKey(fallbackContext.agentName).trim().toLowerCase()
|
||||
@@ -628,7 +634,8 @@ export function createEventHandler(args: {
|
||||
if (!shouldDispatchIdleEvent(sessionID, now)) {
|
||||
return;
|
||||
}
|
||||
await dispatchToHooks(syntheticIdle as EventInput);
|
||||
const syntheticIdleInput = syntheticIdle as EventInput;
|
||||
await dispatchToHooks(syntheticIdleInput);
|
||||
if (pluginConfig.openclaw) {
|
||||
await dispatchOpenClawEvent({
|
||||
config: pluginConfig.openclaw,
|
||||
@@ -640,6 +647,7 @@ export function createEventHandler(args: {
|
||||
},
|
||||
});
|
||||
}
|
||||
await dispatchIdleOnlyHooks(syntheticIdleInput);
|
||||
}
|
||||
|
||||
const { event } = input;
|
||||
@@ -761,9 +769,7 @@ export function createEventHandler(args: {
|
||||
}
|
||||
|
||||
if (event.type === "session.idle") {
|
||||
managers.tmuxSessionManager?.onEvent?.(event);
|
||||
await runEventHookSafely("teamIdleWakeHint", teamIdleWakeHint, input);
|
||||
await runEventHookSafely("teamMemberStatusHandler", teamMemberStatusHandler, input);
|
||||
await dispatchIdleOnlyHooks(input);
|
||||
}
|
||||
|
||||
if (event.type === "message.updated") {
|
||||
|
||||
Reference in New Issue
Block a user