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:
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user