Merge pull request #4203 from code-yeongyu/fix/continuation-prompt-dispatch-20260520051014

fix(plugin): dispatch synthetic idle hooks
This commit is contained in:
YeonGyu-Kim
2026-05-20 15:23:10 +09:00
committed by GitHub
3 changed files with 69 additions and 9 deletions
+10 -5
View File
@@ -37,10 +37,6 @@ export function saveInjectedRules(
sessionID: string,
data: { contentHashes: Set<string>; realPaths: Set<string> }
): void {
if (!existsSync(RULES_INJECTOR_STORAGE)) {
mkdirSync(RULES_INJECTOR_STORAGE, { recursive: true });
}
const storageData: InjectedRulesData = {
sessionID,
injectedHashes: [...data.contentHashes],
@@ -48,7 +44,16 @@ export function saveInjectedRules(
updatedAt: Date.now(),
};
writeFileSync(getStoragePath(sessionID), JSON.stringify(storageData, null, 2));
mkdirSync(RULES_INJECTOR_STORAGE, { recursive: true });
try {
writeFileSync(getStoragePath(sessionID), JSON.stringify(storageData, null, 2));
} catch (error) {
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
throw error;
}
mkdirSync(RULES_INJECTOR_STORAGE, { recursive: true });
writeFileSync(getStoragePath(sessionID), JSON.stringify(storageData, null, 2));
}
}
export function clearInjectedRules(sessionID: string): void {
+49
View File
@@ -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
View File
@@ -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") {