Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 49 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,45 @@ const plugin: TuiPluginModule = {
let leaderPending = false;
let leaderTimer: ReturnType<typeof setTimeout> | 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<string, number>();

// 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.
Expand Down Expand Up @@ -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;
}
}

Expand Down
Loading