fix(coding-agent): gate print-mode plan arming on settings before session context
- runPrintMode dereferenced session.sessionManager before checking whether plan.defaultOnStartup was even enabled, crashing partial-session harnesses; the settings check now short-circuits first. - Extended the print-mode session fakes with the settings/sessionManager members every real session provides. - Scoped the rpc-stdin-lock stderr assertion to non-load-notice lines: the adversarial fixture's load failure is now legitimately surfaced (#4954) and mentions the locked stream by design.
This commit is contained in:
@@ -35,6 +35,8 @@ describe("print-mode error exit disposes the session before exit", () => {
|
||||
const session = {
|
||||
extensionRunner: undefined,
|
||||
subscribe: () => {},
|
||||
settings: { get: () => false },
|
||||
sessionManager: { buildSessionContext: () => ({ messages: [] }), getEntries: () => [] },
|
||||
state: { messages: [errorMsg] },
|
||||
getLastAssistantMessage: () => errorMsg,
|
||||
prepareForHeadlessAdvisorDrain: () => {},
|
||||
|
||||
@@ -115,14 +115,12 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
||||
// the first prompt; persisting the mode_change also lets a later interactive
|
||||
// attachment restore and review the generated plan.
|
||||
let abortAfterPlanProposal = false;
|
||||
const hasConversationContext = session.sessionManager.buildSessionContext().messages.length > 0;
|
||||
const hasExplicitMode = session.sessionManager.getEntries().some(entry => entry.type === "mode_change");
|
||||
if (
|
||||
!hasConversationContext &&
|
||||
!hasExplicitMode &&
|
||||
const planDefaultArmed =
|
||||
session.settings.get("plan.defaultOnStartup") &&
|
||||
session.settings.get("plan.enabled")
|
||||
) {
|
||||
session.settings.get("plan.enabled") &&
|
||||
session.sessionManager.buildSessionContext().messages.length === 0 &&
|
||||
!session.sessionManager.getEntries().some(entry => entry.type === "mode_change");
|
||||
if (planDefaultArmed) {
|
||||
const planFilePath = session.getPlanReferencePath() || "local://PLAN.md";
|
||||
const previousTools = session.getEnabledToolNames();
|
||||
const planTools = session.hasBuiltInTool("write") ? [...new Set([...previousTools, "write"])] : previousTools;
|
||||
|
||||
@@ -46,7 +46,12 @@ async function expectRpcModeOwnsStdin(mode: "rpc" | "rpc-ui"): Promise<void> {
|
||||
}
|
||||
|
||||
const stderr = await stderrPromise;
|
||||
expect(stderr).not.toContain("ReadableStream is locked");
|
||||
// The adversarial fixture is EXPECTED to fail loading — RPC claimed stdin
|
||||
// first — and its surfaced load notice (#4954) mentions the locked stream.
|
||||
// Any OTHER "ReadableStream is locked" line means RPC lost stdin ownership.
|
||||
for (const line of stderr.split("\n").filter(l => l.includes("ReadableStream is locked"))) {
|
||||
expect(line).toContain("Failed to load extension");
|
||||
}
|
||||
expect(stateResponse?.success).toBe(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,11 @@ function createMockSession(
|
||||
return {
|
||||
state: { messages },
|
||||
getLastAssistantMessage: () => messages.findLast(message => message.role === "assistant"),
|
||||
settings: { get: () => false },
|
||||
sessionManager: {
|
||||
getHeader: () => undefined,
|
||||
buildSessionContext: () => ({ messages: [] }),
|
||||
getEntries: () => [],
|
||||
},
|
||||
extensionRunner: undefined,
|
||||
subscribe: () => () => {},
|
||||
|
||||
Reference in New Issue
Block a user