From a8dfd15ea010b072afad748f55b909177778d5e5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 1 Jun 2026 04:41:05 +0000 Subject: [PATCH] chore: sync public mirror from internal --- src/agent/compaction-markers.ts | 53 ++++++ src/agent/compaction.ts | 61 ++----- src/agent/session-start-hooks.ts | 189 ++++++++++++++++++++++ src/app-server/plugin-bundle-api.ts | 13 +- src/app-server/session-api.ts | 6 +- src/cli/commands/exec-constants.ts | 2 + src/cli/commands/exec.ts | 4 +- src/main.ts | 81 ++++++---- src/session/session-context.ts | 2 +- test/app-server/plugin-bundle-api.test.ts | 43 +---- test/cli/cli.integration.test.ts | 9 +- test/cli/main-import-boundary.test.ts | 22 +++ 12 files changed, 356 insertions(+), 129 deletions(-) create mode 100644 src/agent/compaction-markers.ts create mode 100644 src/agent/session-start-hooks.ts create mode 100644 src/cli/commands/exec-constants.ts diff --git a/src/agent/compaction-markers.ts b/src/agent/compaction-markers.ts new file mode 100644 index 000000000..91d337038 --- /dev/null +++ b/src/agent/compaction-markers.ts @@ -0,0 +1,53 @@ +import type { AppMessage } from "./types.js"; + +export const COMPACTION_RESUME_PROMPT = + "Use the above summary to resume the plan from where we left off."; + +function extractMessageText(message: AppMessage): string { + const content = + message.role === "assistant" || message.role === "user" + ? message.content + : undefined; + if (typeof content === "string") { + return content; + } + if (!Array.isArray(content)) { + return ""; + } + return content + .map((part) => { + if (part.type === "text") return part.text; + if (part.type === "thinking") return part.thinking; + return ""; + }) + .filter((part): part is string => Boolean(part)) + .join(" "); +} + +export function isDecoratedCompactionSummaryText(text: string): boolean { + const normalized = text.trim(); + if (!normalized) return false; + return ( + normalized.includes( + "Another language model started to solve this problem", + ) || + normalized.includes("(Compacted") || + normalized.includes("_Local summary of prior discussion") + ); +} + +export function isDecoratedCompactionSummaryMessage( + message: AppMessage, +): boolean { + if (message.role !== "assistant") return false; + return isDecoratedCompactionSummaryText(extractMessageText(message)); +} + +export function isCompactionResumePromptText(text: string): boolean { + return text.trim() === COMPACTION_RESUME_PROMPT; +} + +export function isCompactionResumePromptMessage(message: AppMessage): boolean { + if (message.role !== "user") return false; + return isCompactionResumePromptText(extractMessageText(message)); +} diff --git a/src/agent/compaction.ts b/src/agent/compaction.ts index 60ac85859..0a419ef26 100644 --- a/src/agent/compaction.ts +++ b/src/agent/compaction.ts @@ -82,6 +82,20 @@ import { PLAN_FILE_COMPACTION_CUSTOM_TYPE, PLAN_MODE_COMPACTION_CUSTOM_TYPE, } from "./compaction-restoration.js"; +export { + COMPACTION_RESUME_PROMPT, + isCompactionResumePromptMessage, + isCompactionResumePromptText, + isDecoratedCompactionSummaryMessage, + isDecoratedCompactionSummaryText, +} from "./compaction-markers.js"; +import { + COMPACTION_RESUME_PROMPT, + isCompactionResumePromptMessage, + isCompactionResumePromptText, + isDecoratedCompactionSummaryMessage, + isDecoratedCompactionSummaryText, +} from "./compaction-markers.js"; import { isContextOverflow as isCompactionOverflowMessage, isOverflowErrorMessage, @@ -111,13 +125,6 @@ const logger = createLogger("agent:compaction"); // Types // ============================================================================ -/** - * Internal user prompt appended after compaction so the model resumes from the - * summarized context on the next turn. - */ -export const COMPACTION_RESUME_PROMPT = - "Use the above summary to resume the plan from where we left off."; - const MAX_COMPACTION_OVERFLOW_RETRIES = 3; const PREVIOUS_SUMMARY_PREFIX = "Previous session summary:\n"; const COMPACTION_OVERFLOW_RETRY_MARKER = @@ -1492,46 +1499,6 @@ export function decorateSummaryText( return `${handoffPrefix}${summaryText}\n\n(Compacted ${compactedCount} messages on ${new Date().toLocaleString()})`; } -/** - * Check whether text matches the decorated compaction summary format. - */ -export function isDecoratedCompactionSummaryText(text: string): boolean { - const normalized = text.trim(); - if (!normalized) return false; - return ( - normalized.includes( - "Another language model started to solve this problem", - ) || - normalized.includes("(Compacted") || - normalized.includes("_Local summary of prior discussion") - ); -} - -/** - * Check whether a message is a decorated assistant compaction summary. - */ -export function isDecoratedCompactionSummaryMessage( - message: AppMessage, -): boolean { - if (message.role !== "assistant") return false; - return isDecoratedCompactionSummaryText(extractMessageText(message)); -} - -/** - * Check whether text matches the internal post-compaction resume prompt. - */ -export function isCompactionResumePromptText(text: string): boolean { - return text.trim() === COMPACTION_RESUME_PROMPT; -} - -/** - * Check whether a message is the internal post-compaction resume prompt. - */ -export function isCompactionResumePromptMessage(message: AppMessage): boolean { - if (message.role !== "user") return false; - return isCompactionResumePromptText(extractMessageText(message)); -} - /** * Merge a history summary with a turn prefix summary for split turn compaction. * diff --git a/src/agent/session-start-hooks.ts b/src/agent/session-start-hooks.ts new file mode 100644 index 000000000..31fa65dca --- /dev/null +++ b/src/agent/session-start-hooks.ts @@ -0,0 +1,189 @@ +import { createSessionHookService } from "../hooks/session-integration.js"; +import { createLogger } from "../utils/logger.js"; +import type { Agent } from "./agent.js"; +import { createHookMessage } from "./custom-messages.js"; +import { SESSION_START_INITIAL_USER_METADATA_KIND } from "./session-start-metadata.js"; +import type { AppMessage, HookMessage, UserMessage } from "./types.js"; + +type SessionStartSessionManager = { + getSessionId?: () => string | undefined; + saveMessage?: (message: AppMessage) => void; +}; +type SessionStartHookDelivery = "queue" | "persistHistory"; + +interface SessionStartHookOutputs { + systemMessage?: string; + additionalContext?: string; + initialUserMessage?: string; +} + +const logger = createLogger("session-start-hooks"); + +function buildSessionStartHookContextMessage(text: string): HookMessage { + return createHookMessage( + "SessionStart", + text, + true, + undefined, + new Date().toISOString(), + ); +} + +function buildSessionStartInitialUserMessage( + text: string, + source?: string, +): UserMessage { + return { + role: "user", + content: text, + metadata: { + kind: SESSION_START_INITIAL_USER_METADATA_KIND, + source, + }, + timestamp: Date.now(), + }; +} + +function buildSessionStartHookSystemGuidance(text: string): string { + return `SessionStart hook system guidance:\n${text}`; +} + +function buildPersistedSessionStartHookSystemMessage( + text: string, +): HookMessage { + return createHookMessage( + "SessionStart", + buildSessionStartHookSystemGuidance(text), + true, + undefined, + new Date().toISOString(), + ); +} + +async function runSessionStartHooksInternal(params: { + sessionManager: SessionStartSessionManager; + cwd: string; + source: string; + signal?: AbortSignal; +}): Promise { + const service = createSessionHookService({ + cwd: params.cwd, + sessionId: params.sessionManager.getSessionId?.(), + }); + if (!service.hasHooks("SessionStart")) { + return null; + } + + const result = await service.runSessionStartHooks( + params.source, + params.signal, + ); + if (result.blocked || result.preventContinuation) { + logger.warn( + "SessionStart hook attempted to stop session startup; ignoring control flow request", + { + source: params.source, + blocked: result.blocked, + preventContinuation: result.preventContinuation, + reason: result.blockReason ?? result.stopReason, + }, + ); + } + + return { + systemMessage: result.systemMessage?.trim(), + additionalContext: result.additionalContext?.trim(), + initialUserMessage: result.initialUserMessage?.trim(), + }; +} + +function buildPersistedSessionStartHookMessages( + outputs: SessionStartHookOutputs | null, + source: string, +): AppMessage[] { + if (!outputs) { + return []; + } + + const persistedMessages: AppMessage[] = []; + if (outputs.systemMessage) { + persistedMessages.push( + buildPersistedSessionStartHookSystemMessage(outputs.systemMessage), + ); + } + if (outputs.additionalContext) { + persistedMessages.push( + buildSessionStartHookContextMessage(outputs.additionalContext), + ); + } + if (outputs.initialUserMessage) { + persistedMessages.push( + buildSessionStartInitialUserMessage(outputs.initialUserMessage, source), + ); + } + return persistedMessages; +} + +export async function collectPersistedSessionStartHookMessages(params: { + sessionManager: SessionStartSessionManager; + cwd: string; + source: string; + signal?: AbortSignal; +}): Promise { + return buildPersistedSessionStartHookMessages( + await runSessionStartHooksInternal(params), + params.source, + ); +} + +export async function applySessionStartHooks(params: { + agent: Agent; + sessionManager: SessionStartSessionManager; + cwd: string; + source: string; + signal?: AbortSignal; + delivery?: SessionStartHookDelivery; +}): Promise { + if (params.delivery === "persistHistory") { + const persistedMessages = await collectPersistedSessionStartHookMessages({ + sessionManager: params.sessionManager, + cwd: params.cwd, + source: params.source, + signal: params.signal, + }); + for (const message of persistedMessages) { + params.agent.appendMessage(message); + params.sessionManager.saveMessage?.(message); + } + return; + } + + const outputs = await runSessionStartHooksInternal({ + sessionManager: params.sessionManager, + cwd: params.cwd, + source: params.source, + signal: params.signal, + }); + if (!outputs) { + return; + } + + if (outputs.systemMessage) { + params.agent.queueNextRunSystemPromptAddition( + buildSessionStartHookSystemGuidance(outputs.systemMessage), + ); + } + if (outputs.additionalContext) { + params.agent.queueNextRunHistoryMessage( + buildSessionStartHookContextMessage(outputs.additionalContext), + ); + } + if (outputs.initialUserMessage) { + params.agent.queueNextRunHistoryMessage( + buildSessionStartInitialUserMessage( + outputs.initialUserMessage, + params.source, + ), + ); + } +} diff --git a/src/app-server/plugin-bundle-api.ts b/src/app-server/plugin-bundle-api.ts index 1b4eed063..9b493ebac 100644 --- a/src/app-server/plugin-bundle-api.ts +++ b/src/app-server/plugin-bundle-api.ts @@ -70,9 +70,12 @@ function paramsRecord(params: unknown): UnknownRecord { } function projectRootFromParams( - _options: MaestroAppServerPluginBundleApiOptions, + params: UnknownRecord, + options: MaestroAppServerPluginBundleApiOptions, ): string { - return resolve(_options.projectRoot ?? process.cwd()); + return resolve( + stringValue(params.projectRoot) ?? options.projectRoot ?? process.cwd(), + ); } function scopeFromParams(params: UnknownRecord): WritablePackageScope { @@ -221,7 +224,7 @@ export function createMaestroAppServerPluginBundleApi( return { async listBundles(params = {}) { const normalizedParams = paramsRecord(params); - const projectRoot = projectRootFromParams(options); + const projectRoot = projectRootFromParams(normalizedParams, options); const resources = loadConfiguredPackageResources(projectRoot); return { bundles: loadConfiguredPackageSpecs(projectRoot).map((entry) => ({ @@ -241,7 +244,7 @@ export function createMaestroAppServerPluginBundleApi( async installBundle(params = {}) { const normalizedParams = paramsRecord(params); - const projectRoot = projectRootFromParams(options); + const projectRoot = projectRootFromParams(normalizedParams, options); const spec = packageSpecFromParams(normalizedParams); const source = sourceString(spec); const scope = scopeFromParams(normalizedParams); @@ -282,7 +285,7 @@ export function createMaestroAppServerPluginBundleApi( async removeBundle(params = {}) { const normalizedParams = paramsRecord(params); - const projectRoot = projectRootFromParams(options); + const projectRoot = projectRootFromParams(normalizedParams, options); const spec = packageSpecFromParams(normalizedParams); const scope = normalizedParams.scope === undefined diff --git a/src/app-server/session-api.ts b/src/app-server/session-api.ts index d6a16b6ff..43327896e 100644 --- a/src/app-server/session-api.ts +++ b/src/app-server/session-api.ts @@ -895,11 +895,8 @@ export function createMaestroAppServerSessionApi( options.sandboxCheck === false ? undefined : (options.sandboxCheck ?? createMaestroAppServerSandboxCheck()); - const canMutateSessionPersistence = store.canCreateSession?.() ?? true; const pluginBundles = - options.pluginBundles === false || - !canMutateSessionPersistence || - !hostControl + options.pluginBundles === false ? undefined : (options.pluginBundles ?? createMaestroAppServerPluginBundleApi()); const daemonLifecycle = @@ -927,6 +924,7 @@ export function createMaestroAppServerSessionApi( const canUseThreadGoals = Boolean( store.setSessionAppServerGoal && store.loadEntries, ); + const canMutateSessionPersistence = store.canCreateSession?.() ?? true; const externalAgentImport = options.externalAgentImport === false || !canMutateSessionPersistence ? undefined diff --git a/src/cli/commands/exec-constants.ts b/src/cli/commands/exec-constants.ts new file mode 100644 index 000000000..3f041be9d --- /dev/null +++ b/src/cli/commands/exec-constants.ts @@ -0,0 +1,2 @@ +/** Prefix added to exec session summaries for identification. */ +export const EXEC_SESSION_SUMMARY_PREFIX = "[exec]"; diff --git a/src/cli/commands/exec.ts b/src/cli/commands/exec.ts index b0467a5ee..142985256 100644 --- a/src/cli/commands/exec.ts +++ b/src/cli/commands/exec.ts @@ -65,9 +65,7 @@ import { emitThreadStart, emitUserTurn as emitUserTurnEvent, } from "../jsonl-writer.js"; - -/** Prefix added to exec session summaries for identification */ -export const EXEC_SESSION_SUMMARY_PREFIX = "[exec]"; +import { EXEC_SESSION_SUMMARY_PREFIX } from "./exec-constants.js"; interface ExecCommandOptions { agent: Agent; diff --git a/src/main.ts b/src/main.ts index 3a19fa990..e617f3945 100644 --- a/src/main.ts +++ b/src/main.ts @@ -92,14 +92,9 @@ import type { ApprovalMode, } from "./agent/action-approval.js"; import type { Agent, Api, Model } from "./agent/index.js"; -import { loadScriptedScenarioFromSource } from "./agent/providers/scripted.js"; -import { scenarioSourceLabel } from "./agent/scenario-source.js"; +import { applySessionStartHooks } from "./agent/session-start-hooks.js"; import { type ToolRetryMode, ToolRetryService } from "./agent/tool-retry.js"; import type { ClientToolExecutionService } from "./agent/transport.js"; -import { - applySessionStartHooks, - runUserPromptWithRecovery, -} from "./agent/user-prompt-runtime.js"; import { PlatformBackedActionApprovalService } from "./approvals/platform-action-approval.js"; import { createAuthSetup, validateCodexFlags } from "./bootstrap/auth-setup.js"; import { @@ -108,10 +103,7 @@ import { } from "./checkpoints/index.js"; import type { TuiClientToolService } from "./cli-tui/client-tools/local-client-tool-service.js"; import { type Mode, parseArgs } from "./cli/args.js"; -import { - EXEC_SESSION_SUMMARY_PREFIX, - runExecCommand, -} from "./cli/commands/exec.js"; +import { EXEC_SESSION_SUMMARY_PREFIX } from "./cli/commands/exec-constants.js"; import { isHeadlessModeRequested, recordHeadlessRuntimeSelection, @@ -136,7 +128,6 @@ import { getPackageVersion } from "./package-metadata.js"; import { resolveMaestroSystemPrompt } from "./prompts/system-prompt.js"; import type { AuthMode } from "./providers/auth.js"; import { configureSafeMode } from "./safety/safe-mode.js"; -import { LocalSandbox } from "./sandbox/index.js"; import { SessionManager } from "./session/manager.js"; import { beaconTimeoutMs } from "./telemetry/beacon.js"; import { recordStagedRolloutSurfaceUsageLazy } from "./telemetry/staged-rollout-lazy.js"; @@ -417,6 +408,9 @@ async function runSingleShotMode( // Adapter translates agent events to JSONL format const adapter = jsonlWriter && createAgentJsonlAdapter(jsonlWriter, nextTurnId); + const { runUserPromptWithRecovery } = await import( + "./agent/user-prompt-runtime.js" + ); // In JSON mode, emit thread start and subscribe to all events if (jsonlWriter) { @@ -510,13 +504,36 @@ function resolveSessionStartHookSource(params: { return "cli"; } -async function readReplayScenarioId(source: string): Promise { +async function readReplayScenarioMetadata( + source: string, +): Promise<{ scenarioId: string; sourceLabel: string }> { + const { scenarioSourceLabel } = await import("./agent/scenario-source.js"); + const sourceLabel = scenarioSourceLabel(source); try { - return (await loadScriptedScenarioFromSource(source)).id; + const { loadScriptedScenarioFromSource } = await import( + "./agent/providers/scripted.js" + ); + return { + scenarioId: (await loadScriptedScenarioFromSource(source)).id, + sourceLabel, + }; } catch { // The scripted provider surfaces schema and file errors during streaming. } - return scenarioSourceLabel(source); + return { scenarioId: sourceLabel, sourceLabel }; +} + +async function resolveConstraintSandboxMode(params: { + sandbox: unknown; + sandboxMode: string | undefined; +}): Promise<"none" | "local" | string | null> { + if (!params.sandbox) { + return "none"; + } + const { LocalSandbox } = await import("./sandbox/local-sandbox.js"); + return params.sandbox instanceof LocalSandbox + ? "local" + : (params.sandboxMode ?? null); } /** @@ -668,14 +685,24 @@ export async function main(args: string[]) { const replayScenarioPath = parsed.replayScenarioPath ?? process.env.MAESTRO_SCENARIO_PATH; + let scenarioReplay: + | { + path: string; + scenarioId: string; + } + | undefined; if (replayScenarioPath) { parsed.replayScenarioPath = replayScenarioPath; process.env.MAESTRO_SCENARIO_PATH = replayScenarioPath; - process.env.MAESTRO_SCENARIO_ID = - await readReplayScenarioId(replayScenarioPath); + const replayScenario = await readReplayScenarioMetadata(replayScenarioPath); + process.env.MAESTRO_SCENARIO_ID = replayScenario.scenarioId; process.env.MAESTRO_MODE = "replay"; parsed.provider = "scripted-replay"; parsed.model = "maestro-replay-v1"; + scenarioReplay = { + path: replayScenario.sourceLabel, + scenarioId: replayScenario.scenarioId, + }; } // Handle `maestro hosted-runner` before importing web-server so hosted @@ -1128,6 +1155,9 @@ export async function main(args: string[]) { return; } + const execCommandModulePromise = + parsed.command === "exec" ? import("./cli/commands/exec.js") : undefined; + // Handle cost commands if (parsed.command === "cost") { const { handleCostSummary, handleCostClear, handleCostBreakdown } = @@ -1527,11 +1557,10 @@ export async function main(args: string[]) { // Build the system prompt with project context after sandbox setup so runtime // constraint fragments reflect the resolved sandbox state, not just the // requested CLI mode. - const resolvedConstraintSandboxMode = sandbox - ? sandbox instanceof LocalSandbox - ? "local" - : (sandboxMode ?? null) - : "none"; + const resolvedConstraintSandboxMode = await resolveConstraintSandboxMode({ + sandbox, + sandboxMode, + }); const systemPromptToolNames = parsed.tools ?? (model.api === "openai-codex-app-server" @@ -1733,13 +1762,7 @@ export async function main(args: string[]) { cwd: process.cwd(), enterpriseContext, automaticMemoryExtraction: automaticMemory.extraction, - scenarioReplay: - parsed.replayScenarioPath && process.env.MAESTRO_SCENARIO_ID - ? { - path: scenarioSourceLabel(parsed.replayScenarioPath), - scenarioId: process.env.MAESTRO_SCENARIO_ID, - } - : undefined, + scenarioReplay, scenarioRecorder, }); @@ -1807,6 +1830,8 @@ export async function main(args: string[]) { ); } else if (parsed.command === "exec") { startupProfiler.terminal("exec:ready"); + const { runExecCommand } = await (execCommandModulePromise ?? + import("./cli/commands/exec.js")); await runExecCommand({ agent, sessionManager, diff --git a/src/session/session-context.ts b/src/session/session-context.ts index 959d16d2e..0d3145fbb 100644 --- a/src/session/session-context.ts +++ b/src/session/session-context.ts @@ -7,7 +7,7 @@ import type { Stats } from "node:fs"; import { existsSync, readFileSync } from "node:fs"; import { v4 as uuidv4 } from "uuid"; -import { isDecoratedCompactionSummaryMessage } from "../agent/compaction.js"; +import { isDecoratedCompactionSummaryMessage } from "../agent/compaction-markers.js"; import { createBranchSummaryMessage, createCompactionSummaryMessage, diff --git a/test/app-server/plugin-bundle-api.test.ts b/test/app-server/plugin-bundle-api.test.ts index 329842c1f..9c5b2c67a 100644 --- a/test/app-server/plugin-bundle-api.test.ts +++ b/test/app-server/plugin-bundle-api.test.ts @@ -4,7 +4,6 @@ import { join } from "node:path"; import { Value } from "@sinclair/typebox/value"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { MaestroAppServerResponseSchema } from "../../packages/contracts/src/maestro-app-server.js"; -import { createMaestroAppServerPluginBundleApi } from "../../src/app-server/plugin-bundle-api.js"; import { createMaestroAppServerSessionApi, handleMaestroAppServerRequest, @@ -73,41 +72,13 @@ describe("Maestro app-server plugin bundle lifecycle API", () => { }); }); - it("does not expose plugin bundle lifecycle when session persistence is disabled", async () => { - const manager = new SessionManager(false, undefined, { - sessionDir: join(testDir, "disabled-plugin-sessions"), - }); - manager.disable(); - const api = createMaestroAppServerSessionApi(manager); - - expect(api.initialize()).toMatchObject({ - capabilities: { - pluginBundles: false, - }, - }); - - const response = await handleMaestroAppServerRequest(api, { - jsonrpc: "2.0", - id: "plugin-install-disabled", - method: "pluginBundle/install", - params: { source: "npm:@scope/package" }, - }); - - expect(response.error).toEqual({ - code: -32601, - message: "Plugin bundle lifecycle is not available", - }); - }); - it("installs, lists, loads, and removes a local plugin bundle", async () => { const projectRoot = join(testDir, "project"); const packageDir = writePluginBundle(projectRoot); const manager = new SessionManager(false, undefined, { sessionDir: join(testDir, "sessions"), }); - const api = createMaestroAppServerSessionApi(manager, { - pluginBundles: createMaestroAppServerPluginBundleApi({ projectRoot }), - }); + const api = createMaestroAppServerSessionApi(manager); const installed = await handleMaestroAppServerRequest(api, { jsonrpc: "2.0", @@ -215,9 +186,7 @@ describe("Maestro app-server plugin bundle lifecycle API", () => { const manager = new SessionManager(false, undefined, { sessionDir: join(testDir, "dry-run-sessions"), }); - const api = createMaestroAppServerSessionApi(manager, { - pluginBundles: createMaestroAppServerPluginBundleApi({ projectRoot }), - }); + const api = createMaestroAppServerSessionApi(manager); const planned = await handleMaestroAppServerRequest(api, { jsonrpc: "2.0", @@ -245,9 +214,7 @@ describe("Maestro app-server plugin bundle lifecycle API", () => { const manager = new SessionManager(false, undefined, { sessionDir: join(testDir, "malformed-source-sessions"), }); - const api = createMaestroAppServerSessionApi(manager, { - pluginBundles: createMaestroAppServerPluginBundleApi({ projectRoot }), - }); + const api = createMaestroAppServerSessionApi(manager); const rejected = await handleMaestroAppServerRequest(api, { jsonrpc: "2.0", @@ -272,9 +239,7 @@ describe("Maestro app-server plugin bundle lifecycle API", () => { const manager = new SessionManager(false, undefined, { sessionDir: join(testDir, "invalid-sessions"), }); - const api = createMaestroAppServerSessionApi(manager, { - pluginBundles: createMaestroAppServerPluginBundleApi({ projectRoot }), - }); + const api = createMaestroAppServerSessionApi(manager); const invalidScope = await handleMaestroAppServerRequest(api, { jsonrpc: "2.0", diff --git a/test/cli/cli.integration.test.ts b/test/cli/cli.integration.test.ts index cc8f501ab..b04d5f73a 100644 --- a/test/cli/cli.integration.test.ts +++ b/test/cli/cli.integration.test.ts @@ -1104,8 +1104,13 @@ describe("CLI integration", () => { it("runs SessionEnd hooks after maestro exec completes", async () => { let sessionEndInput: Record | undefined; + const [{ registerHook: registerCurrentHook }, { main: currentMain }] = + await Promise.all([ + import("../../src/hooks/index.js"), + import("../../src/main.js"), + ]); - registerHook("SessionEnd", { + registerCurrentHook("SessionEnd", { type: "callback", callback: async (input) => { sessionEndInput = input as Record; @@ -1113,7 +1118,7 @@ describe("CLI integration", () => { }, }); - await main(["exec", "Summarize release notes"]); + await currentMain(["exec", "Summarize release notes"]); expect(sessionEndInput).toMatchObject({ hook_event_name: "SessionEnd", diff --git a/test/cli/main-import-boundary.test.ts b/test/cli/main-import-boundary.test.ts index d9eb98c89..f3fe67fbd 100644 --- a/test/cli/main-import-boundary.test.ts +++ b/test/cli/main-import-boundary.test.ts @@ -23,6 +23,23 @@ describe("main import boundary", () => { "../../src/tools/ask-user-client.js", failIfImported("ask_user client tool"), ); + vi.doMock( + "../../src/cli/commands/exec.js", + failIfImported("exec command runtime"), + ); + vi.doMock( + "../../src/agent/providers/scripted.js", + failIfImported("scripted replay provider"), + ); + vi.doMock( + "../../src/agent/scenario-source.js", + failIfImported("scenario source helpers"), + ); + vi.doMock("../../src/sandbox/index.js", failIfImported("sandbox runtime")); + vi.doMock( + "../../src/sandbox/local-sandbox.js", + failIfImported("local sandbox runtime"), + ); try { const mainModule = await import("../../src/main.js"); @@ -32,6 +49,11 @@ describe("main import boundary", () => { vi.doUnmock("../../src/server/client-tools-service.js"); vi.doUnmock("../../src/server/tool-retry-service.js"); vi.doUnmock("../../src/tools/ask-user-client.js"); + vi.doUnmock("../../src/cli/commands/exec.js"); + vi.doUnmock("../../src/agent/providers/scripted.js"); + vi.doUnmock("../../src/agent/scenario-source.js"); + vi.doUnmock("../../src/sandbox/index.js"); + vi.doUnmock("../../src/sandbox/local-sandbox.js"); vi.resetModules(); } });