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
73 changes: 47 additions & 26 deletions apps/kimi-code/src/cli/v2/run-v2-print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* - `bootstrap()`s the app scope,
* - creates / resumes a session and its main agent via native services,
* - subscribes to the main agent's per-agent `IEventBus` and renders the
* native `DomainEvent` stream (payloads are already v1-protocol-shaped),
* native `Event2` stream (payloads are already v1-protocol-shaped),
* - drives a turn through `IAgentPromptService.enqueue()` and awaits
* `Turn.result` for authoritative completion,
* - applies the print-mode background policy (config-driven, v1-aligned:
Expand Down Expand Up @@ -49,14 +49,28 @@ import {
resolveLoggingConfig,
resolvePrintBackgroundMode,
setClampedTimeout,
type DomainEvent,
type Event2,
type IAgentScopeHandle,
type ISessionScopeHandle,
type LoopRunResult,
type PrintBackgroundMode,
type Scope,
} from '@moonshot-ai/agent-core-v2';
import { createKimiDefaultHeaders, createKimiDeviceId } from '@moonshot-ai/kimi-code-oauth';
import type { GoalUpdated } from '@moonshot-ai/agent-core-v2/agent/goal/goalOps';
import type { TurnEnded } from '@moonshot-ai/agent-core-v2/agent/loop/turnOps';
import type {
AssistantDelta,
ThinkingDelta,
ToolCallDelta,
} from '@moonshot-ai/agent-core-v2/agent/loop/turnEvents';
import type { TurnStepRetrying } from '@moonshot-ai/agent-core-v2/agent/stepRetry/stepRetryService';
import type { HookResult } from '@moonshot-ai/agent-core-v2/agent/externalHooks/externalHooksService';
import type {
ToolCallStarted,
ToolProgress,
ToolResultEvent,
} from '@moonshot-ai/agent-core-v2/agent/toolExecutor/toolExecutorEvents';
import { resolve } from 'pathe';

import {
Expand Down Expand Up @@ -414,12 +428,12 @@ async function runNativeTurn(
await agent.accessor.get(IAuthSummaryService).ensureReady();

const turnEndings = createPrintTurnEndings();
const subscription = agent.accessor.get(IEventBus).subscribe((event: DomainEvent) => {
const subscription = agent.accessor.get(IEventBus).subscribe((event: Event2<any>) => {
dispatchNativeEvent(writer, event, stderr);
// Arm the turn-endings collector before `turn.result` settles so a
// background-task completion that steers a new turn right after the main
// turn ends cannot have its `turn.ended` slip past the policy loop.
if (event.type === 'turn.ended') turnEndings.push(event);
if (event.type === 'turn.ended') turnEndings.push(event as TurnEnded);
});
try {
const handle = await agent.accessor.get(IAgentPromptService).enqueue({
Expand Down Expand Up @@ -511,13 +525,12 @@ async function runNativeGoal(
replace: goal.replace,
});
let completedSnapshot: { readonly status: string } | null = null;
const subscription = agent.accessor.get(IEventBus).subscribe((event: DomainEvent) => {
if (
event.type === 'goal.updated' &&
event.change?.kind === 'completion' &&
event.snapshot !== null
) {
completedSnapshot = event.snapshot;
const subscription = agent.accessor.get(IEventBus).subscribe((event: Event2<any>) => {
if (event.type === 'goal.updated') {
const updated = event as unknown as GoalUpdated;
if (updated.change?.kind === 'completion' && updated.snapshot !== null) {
completedSnapshot = updated.snapshot;
}
}
});
try {
Expand All @@ -538,7 +551,7 @@ async function runNativeGoal(

function dispatchNativeEvent(
writer: PromptTurnWriter,
event: DomainEvent,
event: Event2<any>,
stderr: PromptOutput,
): void {
switch (event.type) {
Expand All @@ -548,35 +561,43 @@ function dispatchNativeEvent(
return;
case 'turn.step.retrying':
writer.discardAssistant();
writer.writeRetrying(event);
writer.writeRetrying(event as unknown as TurnStepRetrying);
return;
case 'assistant.delta':
writer.writeAssistantDelta(event.delta);
writer.writeAssistantDelta((event as unknown as AssistantDelta).delta);
return;
case 'hook.result':
writer.writeHookResult(event);
writer.writeHookResult(event as unknown as HookResult);
return;
case 'thinking.delta':
writer.writeThinkingDelta(event.delta);
writer.writeThinkingDelta((event as unknown as ThinkingDelta).delta);
return;
case 'tool.call.started':
writer.writeToolCall(event.toolCallId, event.name, event.args);
case 'tool.call.started': {
const started = event as unknown as ToolCallStarted;
writer.writeToolCall(started.toolCallId, started.name, started.args);
return;
case 'tool.call.delta':
writer.writeToolCallDelta(event.toolCallId, event.name, event.argumentsPart);
}
case 'tool.call.delta': {
const delta = event as unknown as ToolCallDelta;
writer.writeToolCallDelta(delta.toolCallId, delta.name, delta.argumentsPart);
return;
case 'tool.result':
writer.writeToolResult(event.toolCallId, event.output);
}
case 'tool.result': {
const result = event as unknown as ToolResultEvent;
writer.writeToolResult(result.toolCallId, result.output);
return;
case 'tool.progress':
if (event.update.text !== undefined && event.update.text.length > 0) {
stderr.write(event.update.text.endsWith('\n') ? event.update.text : `${event.update.text}\n`);
}
case 'tool.progress': {
const progress = (event as unknown as ToolProgress).update;
if (progress.text !== undefined && progress.text.length > 0) {
stderr.write(progress.text.endsWith('\n') ? progress.text : `${progress.text}\n`);
}
return;
}
}
}

export type PrintTurnEnding = Extract<DomainEvent, { type: 'turn.ended' }>;
export type PrintTurnEnding = TurnEnded;

/**
* Source of `turn.ended` events for the print steer loop. `next` resolves with
Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/test/cli/run-v2-print.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function ending(
turnId: number,
reason: PrintTurnEnding['reason'] = 'completed',
): PrintTurnEnding {
return { type: 'turn.ended', turnId, reason };
return { type: 'turn.ended', turnId, reason } as unknown as PrintTurnEnding;
}

interface ScriptedEntry {
Expand Down
8 changes: 4 additions & 4 deletions apps/kimi-code/test/cli/v2-run-print.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
ISessionManager,
ITelemetryService,
type BootstrapInput,
type DomainEvent,
type Event2,
} from '@moonshot-ai/agent-core-v2';

import { runV2Print } from '../../src/cli/v2/run-v2-print';
Expand Down Expand Up @@ -123,7 +123,7 @@ function opts(overrides: Record<string, unknown> = {}) {
function makeFakeHarness() {
// Native event listeners registered on the main agent's IEventBus; the turn
// emits a streaming assistant delta before completing.
const eventListeners = new Set<(event: DomainEvent) => void>();
const eventListeners = new Set<(event: Event2<any>) => void>();
const profileState: { profileName: string | undefined } = { profileName: undefined };

const agentServices = new Map<unknown, unknown>([
Expand All @@ -141,7 +141,7 @@ function makeFakeHarness() {
[
IEventBus,
{
subscribe: vi.fn((handler: (event: DomainEvent) => void) => {
subscribe: vi.fn((handler: (event: Event2<any>) => void) => {
eventListeners.add(handler);
return { dispose: () => eventListeners.delete(handler) };
}),
Expand All @@ -153,7 +153,7 @@ function makeFakeHarness() {
enqueue: vi.fn(async () => {
// Emit a native assistant delta on the main agent bus, then complete.
for (const listener of [...eventListeners]) {
listener({ type: 'assistant.delta', turnId: 1, delta: 'hello world' } as DomainEvent);
listener({ type: 'assistant.delta', turnId: 1, delta: 'hello world' } as unknown as Event2<any>);
}
return {
launched: Promise.resolve({
Expand Down
27 changes: 15 additions & 12 deletions packages/acp-server/test/e2e-turn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

import { getLiveSessionById, IAgentLifecycleService, IEventBus } from '@moonshot-ai/agent-core-v2';
import { ToolProgress } from '@moonshot-ai/agent-core-v2/agent/toolExecutor/toolExecutorEvents';
import { afterEach, describe, expect, it } from 'vitest';

import { mapPromptLaunchError } from '../src/session';
Expand Down Expand Up @@ -508,18 +509,20 @@ describe('acp-server real prompt turn (scripted LLM)', () => {
const agentHandle = session?.accessor.get(IAgentLifecycleService).get('main');
const bus = agentHandle?.accessor.get(IEventBus);
expect(bus).toBeDefined();
bus!.publish({
type: 'tool.progress',
turnId,
toolCallId: 'call_1',
update: { kind: 'stdout', text: 'raw-stdout-bytes' },
});
bus!.publish({
type: 'tool.progress',
turnId,
toolCallId: 'call_1',
update: { kind: 'status', text: 'Still working…' },
});
bus!.publish(
new ToolProgress({
turnId,
toolCallId: 'call_1',
update: { kind: 'stdout', text: 'raw-stdout-bytes' },
}),
);
bus!.publish(
new ToolProgress({
turnId,
toolCallId: 'call_1',
update: { kind: 'status', text: 'Still working…' },
}),
);

const result = (await promptPromise) as { stopReason: string };
expect(result.stopReason).toBe('end_turn');
Expand Down
Loading
Loading