diff --git a/CHANGELOG.md b/CHANGELOG.md index a32c811..c73b4da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version ## [Unreleased] +### Fixed + +- Enter/Escape now work on subagent permission dialogs instead of being consumed by vimcome ([#47](https://github.com/oribarilan/vimcode/issues/47)). + ## [0.15.0] — 2026-06-16 ### Fixed diff --git a/src/index.ts b/src/index.ts index 3171c51..6238b91 100644 --- a/src/index.ts +++ b/src/index.ts @@ -43,6 +43,45 @@ const plugin: TuiPluginModule = { let leaderPending = false; let leaderTimer: ReturnType | null = null; + // Track pending permissions/questions from child sessions via events. + // permission()/question() only covers one session ID, but subagent + // prompts live on child IDs. Events fire globally; we aggregate by root. + const pendingChildPrompts = new Map(); + + // biome-ignore lint/suspicious/noExplicitAny: event shape is untyped in the plugin API + function trackPromptEvent(event: any, delta: number) { + const sessionID = event?.properties?.sessionID ?? event?.sessionID; + if (!sessionID) return; + const session = api.state?.session?.get?.(sessionID); + const rootId = session?.parentID ?? sessionID; + const count = (pendingChildPrompts.get(rootId) ?? 0) + delta; + if (count <= 0) pendingChildPrompts.delete(rootId); + else pendingChildPrompts.set(rootId, count); + } + + // biome-ignore lint/suspicious/noExplicitAny: event shape is untyped in the plugin API + const unsubPermsAsked = api.event?.on?.("permission.asked", (e: any) => trackPromptEvent(e, 1)); + // biome-ignore lint/suspicious/noExplicitAny: event shape is untyped in the plugin API + const unsubPermsReplied = api.event?.on?.("permission.replied", (e: any) => trackPromptEvent(e, -1)); + // biome-ignore lint/suspicious/noExplicitAny: event shape is untyped in the plugin API + const unsubQuestAsked = api.event?.on?.("question.asked", (e: any) => trackPromptEvent(e, 1)); + // biome-ignore lint/suspicious/noExplicitAny: event shape is untyped in the plugin API + const unsubQuestReplied = api.event?.on?.("question.replied", (e: any) => trackPromptEvent(e, -1)); + api.lifecycle?.onDispose?.(() => { + unsubPermsAsked?.(); + unsubPermsReplied?.(); + unsubQuestAsked?.(); + unsubQuestReplied?.(); + }); + + function hasActivePrompts(sid: string): boolean { + const q = api.state.session.question(sid); + if (q && q.length > 0) return true; + const p = api.state.session.permission(sid); + if (p && p.length > 0) return true; + return (pendingChildPrompts.get(sid) ?? 0) > 0; + } + // Snapshots for single-step undo of vim changes. // The host editor's undo system splits repeated commands into multiple // entries, so we save/restore the buffer ourselves. @@ -255,21 +294,17 @@ const plugin: TuiPluginModule = { const route = api.route.current; if (route.name === "session") { const sid = route.params?.sessionID; - if (sid) { - const q = api.state.session.question(sid); - const p = api.state.session.permission(sid); - if ((q && q.length > 0) || (p && p.length > 0)) { - // Consume the leader key so dispatchLayers() doesn't - // match it as a leader token, which would enter pending- - // sequence state instead of typing a space. - const matched = findMatchingLeader(ctx.event, leaderKeys); - if (matched) { - ctx.consume(); - const ch = leaderChar(matched); - if (ch) api.renderer?.currentFocusedEditor?.insertText?.(ch); - } - return; + if (sid && hasActivePrompts(sid)) { + // Consume the leader key so dispatchLayers() doesn't + // match it as a leader token, which would enter pending- + // sequence state instead of typing a space. + const matched = findMatchingLeader(ctx.event, leaderKeys); + if (matched) { + ctx.consume(); + const ch = leaderChar(matched); + if (ch) api.renderer?.currentFocusedEditor?.insertText?.(ch); } + return; } }