Provider およびモデル名を出力
This commit is contained in:
parent
fc1dfcc3c0
commit
69bd77ab62
@ -27,14 +27,18 @@ const { mockInterruptAllQueries, MockPieceEngine } = vi.hoisted(() => {
|
||||
class MockPieceEngine extends EE {
|
||||
private abortRequested = false;
|
||||
private runResolve: ((value: { status: string; iteration: number }) => void) | null = null;
|
||||
static lastOptions: { abortSignal?: AbortSignal } | null = null;
|
||||
|
||||
constructor(
|
||||
_config: unknown,
|
||||
_cwd: string,
|
||||
_task: string,
|
||||
_options: unknown,
|
||||
options: unknown,
|
||||
) {
|
||||
super();
|
||||
if (options && typeof options === 'object') {
|
||||
MockPieceEngine.lastOptions = options as { abortSignal?: AbortSignal };
|
||||
}
|
||||
}
|
||||
|
||||
abort(): void {
|
||||
@ -170,6 +174,7 @@ describe('executePiece: SIGINT handler integration', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
MockPieceEngine.lastOptions = null;
|
||||
tmpDir = join(tmpdir(), `takt-sigint-it-${randomUUID()}`);
|
||||
mkdirSync(tmpDir, { recursive: true });
|
||||
mkdirSync(join(tmpDir, '.takt', 'reports'), { recursive: true });
|
||||
@ -243,6 +248,30 @@ describe('executePiece: SIGINT handler integration', () => {
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('should abort provider signal on first SIGINT', async () => {
|
||||
const config = makeConfig();
|
||||
|
||||
const resultPromise = executePiece(config, 'test task', tmpDir, {
|
||||
projectCwd: tmpDir,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
const signal = MockPieceEngine.lastOptions?.abortSignal;
|
||||
expect(signal).toBeDefined();
|
||||
expect(signal!.aborted).toBe(false);
|
||||
|
||||
const allListeners = process.rawListeners('SIGINT') as ((...args: unknown[]) => void)[];
|
||||
const newListener = allListeners.find((l) => !savedSigintListeners.includes(l));
|
||||
expect(newListener).toBeDefined();
|
||||
newListener!();
|
||||
|
||||
expect(signal!.aborted).toBe(true);
|
||||
|
||||
const result = await resultPromise;
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('should register EPIPE handler before calling interruptAllQueries', async () => {
|
||||
const config = makeConfig();
|
||||
|
||||
|
||||
@ -18,9 +18,11 @@ const { MockPieceEngine, mockLoadPersonaSessions, mockLoadWorktreeSessions } = v
|
||||
class MockPieceEngine extends EE {
|
||||
static lastInstance: MockPieceEngine;
|
||||
readonly receivedOptions: Record<string, unknown>;
|
||||
private readonly config: PieceConfig;
|
||||
|
||||
constructor(config: PieceConfig, _cwd: string, _task: string, options: Record<string, unknown>) {
|
||||
super();
|
||||
this.config = config;
|
||||
this.receivedOptions = options;
|
||||
MockPieceEngine.lastInstance = this;
|
||||
}
|
||||
@ -28,6 +30,10 @@ const { MockPieceEngine, mockLoadPersonaSessions, mockLoadWorktreeSessions } = v
|
||||
abort(): void {}
|
||||
|
||||
async run(): Promise<{ status: string; iteration: number }> {
|
||||
const firstStep = this.config.movements[0];
|
||||
if (firstStep) {
|
||||
this.emit('movement:start', firstStep, 1, firstStep.instructionTemplate);
|
||||
}
|
||||
this.emit('piece:complete', { status: 'completed', iteration: 1 });
|
||||
return { status: 'completed', iteration: 1 };
|
||||
}
|
||||
@ -124,6 +130,7 @@ vi.mock('../shared/exitCodes.js', () => ({
|
||||
}));
|
||||
|
||||
import { executePiece } from '../features/tasks/execute/pieceExecution.js';
|
||||
import { info } from '../shared/ui/index.js';
|
||||
|
||||
function makeConfig(): PieceConfig {
|
||||
return {
|
||||
@ -218,4 +225,27 @@ describe('executePiece session loading', () => {
|
||||
// Then: sessions are loaded
|
||||
expect(mockLoadPersonaSessions).toHaveBeenCalledWith('/tmp/project', 'claude');
|
||||
});
|
||||
|
||||
it('should log provider and model per movement with global defaults', async () => {
|
||||
await executePiece(makeConfig(), 'task', '/tmp/project', {
|
||||
projectCwd: '/tmp/project',
|
||||
});
|
||||
|
||||
const mockInfo = vi.mocked(info);
|
||||
expect(mockInfo).toHaveBeenCalledWith('Provider: claude');
|
||||
expect(mockInfo).toHaveBeenCalledWith('Model: (default)');
|
||||
});
|
||||
|
||||
it('should log provider and model per movement with overrides', async () => {
|
||||
await executePiece(makeConfig(), 'task', '/tmp/project', {
|
||||
projectCwd: '/tmp/project',
|
||||
provider: 'codex',
|
||||
model: 'gpt-5',
|
||||
personaProviders: { coder: 'opencode' },
|
||||
});
|
||||
|
||||
const mockInfo = vi.mocked(info);
|
||||
expect(mockInfo).toHaveBeenCalledWith('Provider: opencode');
|
||||
expect(mockInfo).toHaveBeenCalledWith('Model: gpt-5');
|
||||
});
|
||||
});
|
||||
|
||||
@ -11,6 +11,7 @@ import type { RunAgentOptions } from '../../../agents/runner.js';
|
||||
import type { PhaseRunnerContext } from '../phase-runner.js';
|
||||
import type { PieceEngineOptions, PhaseName } from '../types.js';
|
||||
import { buildSessionKey } from '../session-key.js';
|
||||
import { resolveMovementProviderModel } from '../provider-resolution.js';
|
||||
|
||||
export class OptionsBuilder {
|
||||
constructor(
|
||||
@ -30,13 +31,19 @@ export class OptionsBuilder {
|
||||
const movements = this.getPieceMovements();
|
||||
const currentIndex = movements.findIndex((m) => m.name === step.name);
|
||||
const currentPosition = currentIndex >= 0 ? `${currentIndex + 1}/${movements.length}` : '?/?';
|
||||
const resolved = resolveMovementProviderModel({
|
||||
step,
|
||||
provider: this.engineOptions.provider,
|
||||
model: this.engineOptions.model,
|
||||
personaProviders: this.engineOptions.personaProviders,
|
||||
});
|
||||
|
||||
return {
|
||||
cwd: this.getCwd(),
|
||||
abortSignal: this.engineOptions.abortSignal,
|
||||
personaPath: step.personaPath,
|
||||
provider: step.provider ?? this.engineOptions.personaProviders?.[step.personaDisplayName] ?? this.engineOptions.provider,
|
||||
model: step.model ?? this.engineOptions.model,
|
||||
provider: resolved.provider,
|
||||
model: resolved.model,
|
||||
permissionMode: step.permissionMode,
|
||||
language: this.getLanguage(),
|
||||
onStream: this.engineOptions.onStream,
|
||||
|
||||
24
src/core/piece/provider-resolution.ts
Normal file
24
src/core/piece/provider-resolution.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import type { PieceMovement } from '../models/types.js';
|
||||
|
||||
export type ProviderType = 'claude' | 'codex' | 'opencode' | 'mock';
|
||||
|
||||
export interface MovementProviderModelInput {
|
||||
step: Pick<PieceMovement, 'provider' | 'model' | 'personaDisplayName'>;
|
||||
provider?: ProviderType;
|
||||
model?: string;
|
||||
personaProviders?: Record<string, ProviderType>;
|
||||
}
|
||||
|
||||
export interface MovementProviderModelOutput {
|
||||
provider?: ProviderType;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export function resolveMovementProviderModel(input: MovementProviderModelInput): MovementProviderModelOutput {
|
||||
return {
|
||||
provider: input.step.provider
|
||||
?? input.personaProviders?.[input.step.personaDisplayName]
|
||||
?? input.provider,
|
||||
model: input.step.model ?? input.model,
|
||||
};
|
||||
}
|
||||
@ -63,6 +63,7 @@ import { selectOption, promptInput } from '../../../shared/prompt/index.js';
|
||||
import { getLabel } from '../../../shared/i18n/index.js';
|
||||
import { installSigIntHandler } from './sigintHandler.js';
|
||||
import { buildRunPaths } from '../../../core/piece/run/run-paths.js';
|
||||
import { resolveMovementProviderModel } from '../../../core/piece/provider-resolution.js';
|
||||
import { writeFileAtomic, ensureDir } from '../../../infra/config/index.js';
|
||||
|
||||
const log = createLogger('piece');
|
||||
@ -396,10 +397,11 @@ export async function executePiece(
|
||||
let onAbortSignal: (() => void) | undefined;
|
||||
let sigintCleanup: (() => void) | undefined;
|
||||
let onEpipe: ((err: NodeJS.ErrnoException) => void) | undefined;
|
||||
const runAbortController = new AbortController();
|
||||
|
||||
try {
|
||||
engine = new PieceEngine(pieceConfig, cwd, task, {
|
||||
abortSignal: options.abortSignal,
|
||||
abortSignal: runAbortController.signal,
|
||||
onStream: streamHandler,
|
||||
onUserInput,
|
||||
initialSessions: savedSessions,
|
||||
@ -482,6 +484,16 @@ export async function executePiece(
|
||||
movementIteration,
|
||||
});
|
||||
out.info(`[${iteration}/${pieceConfig.maxMovements}] ${step.name} (${step.personaDisplayName})`);
|
||||
const resolved = resolveMovementProviderModel({
|
||||
step,
|
||||
provider: options.provider,
|
||||
model: options.model,
|
||||
personaProviders: options.personaProviders,
|
||||
});
|
||||
const movementProvider = resolved.provider ?? currentProvider;
|
||||
const movementModel = resolved.model ?? globalConfig.model ?? '(default)';
|
||||
out.info(`Provider: ${movementProvider}`);
|
||||
out.info(`Model: ${movementModel}`);
|
||||
|
||||
// Log prompt content for debugging
|
||||
if (instruction) {
|
||||
@ -686,6 +698,9 @@ export async function executePiece(
|
||||
if (!engine || !onEpipe) {
|
||||
throw new Error('Abort handler invoked before PieceEngine initialization');
|
||||
}
|
||||
if (!runAbortController.signal.aborted) {
|
||||
runAbortController.abort();
|
||||
}
|
||||
process.on('uncaughtException', onEpipe);
|
||||
interruptAllQueries();
|
||||
engine.abort();
|
||||
@ -695,7 +710,11 @@ export async function executePiece(
|
||||
const useExternalAbort = Boolean(options.abortSignal);
|
||||
if (useExternalAbort) {
|
||||
onAbortSignal = abortEngine;
|
||||
options.abortSignal!.addEventListener('abort', onAbortSignal, { once: true });
|
||||
if (options.abortSignal!.aborted) {
|
||||
abortEngine();
|
||||
} else {
|
||||
options.abortSignal!.addEventListener('abort', onAbortSignal, { once: true });
|
||||
}
|
||||
} else {
|
||||
const handler = installSigIntHandler(abortEngine);
|
||||
sigintCleanup = handler.cleanup;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user