/**
 * Owns conversation-level direct shell semantics.
 *
 * Shell-process tools own command policy and execution. This service owns what
 * `!command` means inside a chat session: lease-held session recording,
 * policy preflight, confirmed execution, live activity, compaction, and
 * persisted result text shared by web-v2 and cli-v2. Direct-shell confirmation
 * is a host UX concern; this service only enforces the preflight result.
 */
import { HeddleEventType } from '../../../../core/event-types.js';
import { ConversationCompactionService } from '../../../../core/chat/engine/compaction/index.js';
import { classifyShellCommandPolicy, DEFAULT_INSPECT_RULES, DEFAULT_MUTATE_RULES, runShellCommand, } from '../../../../core/tools/toolkits/shell-process/index.js';
export class ConversationDirectShellService {
    static preflight(commandInput) {
        const command = commandInput.trim();
        if (!command) {
            return {
                command,
                risk: 'blocked',
                reason: 'Direct shell command cannot be empty.',
            };
        }
        const inspectPolicy = classifyShellCommandPolicy(command, {
            toolName: 'run_shell_inspect',
            rules: DEFAULT_INSPECT_RULES,
            allowUnknown: false,
        });
        if (!('error' in inspectPolicy)) {
            return {
                command,
                risk: 'safe',
                tool: 'run_shell_inspect',
                reason: inspectPolicy.reason,
            };
        }
        const mutatePolicy = classifyShellCommandPolicy(command, {
            toolName: 'run_shell_mutate',
            rules: DEFAULT_MUTATE_RULES,
            allowUnknown: true,
        });
        if ('error' in mutatePolicy) {
            return {
                command,
                risk: 'blocked',
                tool: 'run_shell_mutate',
                reason: mutatePolicy.error,
            };
        }
        return {
            command,
            risk: mutatePolicy.risk === 'low' ? 'safe' : 'confirmRequired',
            tool: 'run_shell_mutate',
            reason: mutatePolicy.reason,
        };
    }
    static async execute(input) {
        const command = input.command.trim();
        if (!command) {
            throw new Error('Direct shell command cannot be empty.');
        }
        const preflight = ConversationDirectShellService.preflight(command);
        if (preflight.risk === 'blocked') {
            return ConversationDirectShellService.createPreflightStopResult(preflight, 'blocked');
        }
        if (preflight.risk === 'confirmRequired' && !input.riskAccepted) {
            return ConversationDirectShellService.createPreflightStopResult(preflight, 'confirmation_required');
        }
        const session = input.sessions.require(input.sessionId);
        const shellDisplay = `!${command}`;
        input.sessions.appendMessage(input.sessionId, {
            id: `direct-shell-user-${input.runId}`,
            role: 'user',
            text: shellDisplay,
        });
        input.sessions.setLastContinuePrompt(input.sessionId, undefined);
        const chosenCall = ConversationDirectShellService.createCall(input.runId, preflight.tool ?? 'run_shell_inspect', command);
        const options = chosenCall.tool === 'run_shell_inspect' ? {
            toolName: chosenCall.tool,
            rules: DEFAULT_INSPECT_RULES,
            allowUnknown: false,
            cwd: input.workspaceRoot,
        } : {
            toolName: chosenCall.tool,
            rules: DEFAULT_MUTATE_RULES,
            allowUnknown: true,
            cwd: input.workspaceRoot,
        };
        input.onActivity?.(ConversationDirectShellService.createStartedActivity(input, chosenCall));
        const shellRun = await ConversationDirectShellService.runShellWithDuration(chosenCall.input, {
            ...options,
        }, input.abortSignal);
        const chosenResult = shellRun.result;
        const chosenDurationMs = shellRun.durationMs;
        const resultFacts = ConversationDirectShellService.buildResultFacts(chosenCall.tool, command, chosenResult, chosenDurationMs);
        const directShellHistory = [
            ...session.history,
            { role: 'user', content: shellDisplay },
            { role: 'assistant', content: JSON.stringify(resultFacts) },
        ];
        const compacted = await ConversationCompactionService.compact({
            history: directShellHistory,
            runtime: {
                model: input.model,
                stateRoot: input.stateRoot,
                systemContext: input.systemContext,
            },
            session,
            request: {
                toolNames: [chosenCall.tool],
                goal: shellDisplay,
            },
            summarizer: {
                credentialSource: input.credentialSource,
            },
            onStatusChange: input.onCompactionStatus,
        });
        input.sessions.applyCompactionResult(input.sessionId, compacted);
        input.onActivity?.(ConversationDirectShellService.createCompletedActivity(input, chosenCall, chosenResult, chosenDurationMs));
        return {
            outcome: chosenResult.ok ? 'done' : 'error',
            summary: resultFacts.outcome,
            command,
            shellDisplay,
            tool: chosenCall.tool,
            result: resultFacts,
        };
    }
    static createCall(runId, tool, command) {
        return {
            id: `${runId}-${tool}`,
            tool,
            input: { command },
        };
    }
    static createStartedActivity(input, call) {
        return {
            source: 'direct-shell',
            type: HeddleEventType.directShellStarted,
            runId: input.runId,
            command: ConversationDirectShellService.readCommand(call),
            tool: call.tool,
            timestamp: new Date().toISOString(),
        };
    }
    static createCompletedActivity(input, call, result, durationMs) {
        return {
            source: 'direct-shell',
            type: HeddleEventType.directShellCompleted,
            runId: input.runId,
            command: ConversationDirectShellService.readCommand(call),
            tool: call.tool,
            result,
            durationMs,
            timestamp: new Date().toISOString(),
        };
    }
    static extractTextOutput(value, field) {
        const text = ConversationDirectShellService.readStringField(value, field);
        return text?.trim() ? text.trim() : undefined;
    }
    static async runShellWithDuration(raw, options, signal) {
        const startedAt = Date.now();
        const result = await runShellCommand(raw, options, signal);
        return {
            result,
            durationMs: Date.now() - startedAt,
        };
    }
    static readCommand(call) {
        return ConversationDirectShellService.readStringField(call.input, 'command') ?? '';
    }
    static readStringField(value, field) {
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return undefined;
        }
        const candidate = value[field];
        return typeof candidate === 'string' ? candidate : undefined;
    }
    static buildResultFacts(tool, command, result, durationMs) {
        return {
            kind: 'direct_shell_result',
            command,
            tool,
            outcome: result.ok ? 'done' : 'error',
            exitCode: ConversationDirectShellService.readNumberField(result.output, 'exitCode'),
            stdout: ConversationDirectShellService.extractTextOutput(result.output, 'stdout'),
            stderr: ConversationDirectShellService.extractTextOutput(result.output, 'stderr'),
            error: result.error,
            durationMs,
            policy: ConversationDirectShellService.extractPolicy(result.output),
        };
    }
    static extractPolicy(value) {
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return undefined;
        }
        const policy = value.policy;
        if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
            return undefined;
        }
        const candidate = policy;
        return {
            binary: ConversationDirectShellService.readStringField(candidate, 'binary'),
            scope: ConversationDirectShellService.readStringField(candidate, 'scope'),
            risk: ConversationDirectShellService.readStringField(candidate, 'risk'),
            capability: ConversationDirectShellService.readStringField(candidate, 'capability'),
            reason: ConversationDirectShellService.readStringField(candidate, 'reason'),
        };
    }
    static readNumberField(value, field) {
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return undefined;
        }
        const candidate = value[field];
        return typeof candidate === 'number' ? candidate : undefined;
    }
    static createPreflightStopResult(preflight, outcome) {
        const summary = outcome === 'confirmation_required'
            ? `Direct shell command requires confirmation: ${preflight.reason ?? preflight.command}`
            : `Direct shell command blocked: ${preflight.reason ?? preflight.command}`;
        return {
            outcome,
            summary,
            command: preflight.command,
            shellDisplay: `!${preflight.command}`,
            tool: preflight.tool,
        };
    }
}
//# sourceMappingURL=service.js.map