fix(coding-agent): ACP factory re-resolves TITLE_SYSTEM.md per session cwd

createAcpSessionFactory spreads baseOptions into every per-session
createAgentSession call. baseOptions.titleSystemPrompt was resolved
once at launch from main.ts, so opening session/new for a client-
supplied workspace different from the launch cwd would carry the
launch repo's TITLE_SYSTEM.md into the new session and bias replan
title refreshes against the wrong policy.

The factory now calls discoverTitleSystemPromptFile(cwd) +
resolvePromptInput per session and passes the result after the spread,
overriding the launch-time value. When the target workspace has no
override, undefined wins and the bundled prompt is used as expected.

Regression test asserts that a per-session cwd with a project-level
TITLE_SYSTEM.md beats the launch-cwd prompt threaded through
baseOptions.
This commit is contained in:
roboomp
2026-06-28 16:39:12 +00:00
parent 482091dc73
commit d97d48827f
3 changed files with 71 additions and 1 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
### Fixed
- Fixed replan-driven session title refresh (`title.refreshOnReplan`, triggered after `todo init`) ignoring the user's `TITLE_SYSTEM.md` override and silently falling back to the bundled prompt — most visible in Plan Mode, where early todo init replans would overwrite the auto title against the configured policy. `AgentSession` now owns the resolved title prompt (threaded through `CreateAgentSessionOptions.titleSystemPrompt`) and both first-input titling and replan refresh read from the same source ([#3734](https://github.com/can1357/oh-my-pi/issues/3734)).
- Fixed replan-driven session title refresh (`title.refreshOnReplan`, triggered after `todo init`) ignoring the user's `TITLE_SYSTEM.md` override and silently falling back to the bundled prompt — most visible in Plan Mode, where early todo init replans would overwrite the auto title against the configured policy. `AgentSession` now owns the resolved title prompt (threaded through `CreateAgentSessionOptions.titleSystemPrompt`), the ACP per-`session/new` factory re-resolves `TITLE_SYSTEM.md` against each client-supplied workspace cwd, and both first-input titling and replan refresh read from the same source ([#3734](https://github.com/can1357/oh-my-pi/issues/3734)).
## [16.2.3] - 2026-06-28
+8
View File
@@ -361,6 +361,13 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
const nextSettings = await args.settings.cloneForCwd(cwd);
const nextSessionManager = SessionManager.create(cwd, args.sessionDir);
const agentId = `acp:${nextSessionManager.getSessionId()}`;
// `baseOptions.titleSystemPrompt` is resolved from the launch cwd; an ACP
// host can open `session/new` for any client-supplied workspace, so
// re-discover `TITLE_SYSTEM.md` against THIS session's `cwd` to keep the
// replan-driven title refresh consistent with the target project's
// policy (PR #3736 follow-up).
const titleSystemPromptSource = discoverTitleSystemPromptFile(cwd);
const titleSystemPrompt = await resolvePromptInput(titleSystemPromptSource, "title system prompt");
const { session: nextSession } = await args.createSession({
...args.baseOptions,
cwd,
@@ -371,6 +378,7 @@ export function createAcpSessionFactory(args: AcpSessionFactoryOptions): AcpSess
agentId,
hasUI: false,
enableMCP: false,
titleSystemPrompt,
});
if (args.parsedArgs.apiKey && !args.baseOptions.model && nextSession.model) {
args.authStorage.setRuntimeApiKey(nextSession.model.provider, args.parsedArgs.apiKey);
@@ -75,3 +75,65 @@ describe("createAcpSessionFactory MCP isolation (issue #1234)", () => {
}
});
});
describe("createAcpSessionFactory TITLE_SYSTEM.md per-cwd resolution (PR #3736)", () => {
it("re-resolves the title prompt for the per-session cwd instead of inheriting the launch cwd's override", async () => {
const tempDir = TempDir.createSync("@pi-acp-title-prompt-");
let authStorage: AuthStorage | undefined;
try {
authStorage = await AuthStorage.create(tempDir.join("auth.db"));
const modelRegistry = new ModelRegistry(authStorage);
const settings = Settings.isolated({});
const projectDir = tempDir.join("project");
await Bun.write(`${projectDir}/.omp/TITLE_SYSTEM.md`, "Project-specific title policy.");
const fakeSession = {} as AgentSession;
const captured: CreateAgentSessionOptions[] = [];
const createSession = async (options: CreateAgentSessionOptions): Promise<CreateAgentSessionResult> => {
captured.push(options);
return {
session: fakeSession,
extensionsResult: {
extensions: [],
errors: [],
runner: undefined,
} as unknown as CreateAgentSessionResult["extensionsResult"],
setToolUIContext: () => {},
eventBus: {
emit: () => {},
on: () => () => {},
off: () => {},
} as unknown as CreateAgentSessionResult["eventBus"],
};
};
// baseOptions carries the LAUNCH cwd's prompt; the factory must
// override it with the per-session cwd's `TITLE_SYSTEM.md`.
const factory = createAcpSessionFactory({
baseOptions: {
titleSystemPrompt: "Launch-cwd policy that must not leak.",
} as CreateAgentSessionOptions,
settings,
sessionDir: tempDir.join("sessions"),
authStorage,
modelRegistry,
parsedArgs: {},
rawArgs: [],
createSession,
});
await factory(projectDir);
expect(captured).toHaveLength(1);
expect(captured[0].titleSystemPrompt).toBe("Project-specific title policy.");
} finally {
try {
authStorage?.close();
} finally {
await Bun.sleep(0);
await tempDir.remove();
}
}
});
});