mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-25 16:46:17 +00:00
fix(agent-core-v2): scope interaction turn-end cancellation to the ending agent
turnId is an Agent-scope counter starting at 0 per agent, but cancelPendingForTurn matched pending interactions by turnId alone, so any subagent ending its own turn N cancelled approvals/questions parked by other agents on the same turn number. Match on (turnId, agentId) instead, treating an origin without agentId as main.
This commit is contained in:
parent
eaa3969dd3
commit
f032c088c3
6 changed files with 40 additions and 6 deletions
5
.changeset/fix-cross-agent-approval-cancel.md
Normal file
5
.changeset/fix-cross-agent-approval-cancel.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
Fix pending approval prompts being dismissed when a background subagent finished its own turn.
|
||||
|
|
@ -100,7 +100,7 @@ export class AgentLifecycleService extends Disposable implements IAgentLifecycle
|
|||
if (this.interactionBusDisposables.has(handle.id)) return;
|
||||
const d = handle.accessor
|
||||
.get(IEventBus)
|
||||
.subscribe(TurnEnded, (e) => this.interaction.cancelPendingForTurn(e.turnId));
|
||||
.subscribe(TurnEnded, (e) => this.interaction.cancelPendingForTurn(e.turnId, handle.id));
|
||||
this.interactionBusDisposables.set(handle.id, d);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export interface ISessionInteractionService {
|
|||
respond(id: string, response: unknown): void;
|
||||
listPending(kind?: InteractionKind): readonly Interaction[];
|
||||
isRecentlyResolved(id: string): boolean;
|
||||
cancelPendingForTurn(turnId: number): void;
|
||||
cancelPendingForTurn(turnId: number, agentId?: string): void;
|
||||
readonly onDidChangePending: Event<InteractionPendingChangedEvent>;
|
||||
readonly onDidResolve: Event<InteractionResolution>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,10 +76,12 @@ export class SessionInteractionService extends Service implements ISessionIntera
|
|||
this.states.set(interactionNextIdKey, value);
|
||||
}
|
||||
|
||||
cancelPendingForTurn(turnId: number): void {
|
||||
cancelPendingForTurn(turnId: number, agentId: string = MAIN_AGENT_ID): void {
|
||||
let changed = false;
|
||||
for (const [id, entry] of this.pending) {
|
||||
if (entry.interaction.origin?.turnId !== turnId) continue;
|
||||
const origin = entry.interaction.origin;
|
||||
if (origin?.turnId !== turnId) continue;
|
||||
if ((origin.agentId ?? MAIN_AGENT_ID) !== agentId) continue;
|
||||
this.pending.delete(id);
|
||||
this.rememberResolved(id);
|
||||
const response = { cancelled: true, reason: 'turn_ended' };
|
||||
|
|
|
|||
|
|
@ -2052,9 +2052,11 @@ export class AgentTestContext {
|
|||
: interactions.filter((interaction) => interaction.kind === kind);
|
||||
},
|
||||
isRecentlyResolved: () => false,
|
||||
cancelPendingForTurn: (turnId: number) => {
|
||||
cancelPendingForTurn: (turnId: number, agentId: string = 'main') => {
|
||||
for (const [id, interaction] of pending) {
|
||||
if (interaction.origin?.turnId === turnId) pending.delete(id);
|
||||
if (interaction.origin?.turnId !== turnId) continue;
|
||||
if ((interaction.origin?.agentId ?? 'main') !== agentId) continue;
|
||||
pending.delete(id);
|
||||
}
|
||||
},
|
||||
onDidChangePending: Event.None as Event<InteractionPendingChangedEvent>,
|
||||
|
|
|
|||
|
|
@ -215,6 +215,31 @@ describe('SessionInteractionService', () => {
|
|||
expect(svc.listPending()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('cancelPendingForTurn only cancels interactions from the ending agent, not same-numbered turns of other agents', () => {
|
||||
const svc = ix.get(ISessionInteractionService);
|
||||
|
||||
svc.enqueue({ id: 'm1', kind: 'approval', payload: {}, origin: { agentId: 'main', turnId: 3 } });
|
||||
svc.enqueue({ id: 's1', kind: 'approval', payload: {}, origin: { agentId: 'agent-1', turnId: 3 } });
|
||||
|
||||
svc.cancelPendingForTurn(3, 'agent-1');
|
||||
expect(svc.listPending().map((i) => i.id)).toEqual(['m1']);
|
||||
|
||||
svc.cancelPendingForTurn(3, 'main');
|
||||
expect(svc.listPending()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('cancelPendingForTurn treats an interaction without origin agentId as belonging to main', () => {
|
||||
const svc = ix.get(ISessionInteractionService);
|
||||
|
||||
svc.enqueue({ id: 'a1', kind: 'approval', payload: {}, origin: { turnId: 5 } });
|
||||
|
||||
svc.cancelPendingForTurn(5, 'agent-1');
|
||||
expect(svc.listPending()).toHaveLength(1);
|
||||
|
||||
svc.cancelPendingForTurn(5);
|
||||
expect(svc.listPending()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('request journals an interaction.request op to the origin agent wire', () => {
|
||||
const sub = makeFakeAgent('agent-1');
|
||||
agents.set('agent-1', sub);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue