fix(coding-agent): made advisor toggle session-local and refreshed the status line
- SetAdvisorEnabled now used settings.override for advisor.enabled when enabling or disabling, keeping advisor toggles session-local. - /advisor on|off handlers now called refreshStatusLine after each toggle, and the status line updates immediately in the UI. - A regression test was added to assert setAdvisorEnabled invokes override with both values and does not call set.
This commit is contained in:
@@ -83,6 +83,7 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed the advisor auto-resuming a run after the user deliberately interrupts it (Esc, or a cancel from collab/ACP/RPC/SDK/extension). A user interrupt now suppresses advisor `concern`/`blocker` auto-resume until the user next resumes (a typed message, `.`/`c` continue, or a steer/follow-up); the concern is still recorded as a visible, persisted advisor card — including one already steered into the run or arriving mid-abort — so it re-enters context on resume instead of being discarded. Natural yields are unchanged: the advisor can still steer and resume a stalled run.
|
||||
- Fixed `/advisor on|off` not being session-local by overriding the setting instead of modifying global configuration, and fixed changes not updating the TUI status line immediately.
|
||||
- Fixed `plan.defaultOnStartup` setting schema configuration missing the required `ui.group` property.
|
||||
- Fixed auto-retry regenerating a large `write` call after the provider stream timed out mid-tool-call ([#2683](https://github.com/can1357/oh-my-pi/issues/2683)).
|
||||
- Fixed soft-expired `issue://` and `pr://` reads to refresh live before returning stale state, with an explicit stale warning when the live refresh fails ([#2684](https://github.com/can1357/oh-my-pi/issues/2684)).
|
||||
|
||||
@@ -1008,6 +1008,7 @@ export class AgentSession {
|
||||
#goalModeState: GoalModeState | undefined;
|
||||
#goalRuntime: GoalRuntime;
|
||||
#advisorRuntime?: AdvisorRuntime;
|
||||
#advisorEnabled = false;
|
||||
/** The advisor's own agent, retained so `/dump advisor` can serialize its transcript. Undefined when no advisor is active. */
|
||||
#advisorAgent?: Agent;
|
||||
#advisorReadOnlyTools?: AgentTool[];
|
||||
@@ -1492,7 +1493,8 @@ export class AgentSession {
|
||||
},
|
||||
});
|
||||
|
||||
if (this.settings.get("advisor.enabled")) this.#buildAdvisorRuntime();
|
||||
this.#advisorEnabled = this.settings.get("advisor.enabled") as boolean;
|
||||
if (this.#advisorEnabled) this.#buildAdvisorRuntime();
|
||||
|
||||
// Always subscribe to agent events for internal handling
|
||||
// (session persistence, hooks, auto-compaction, retry logic)
|
||||
@@ -1506,7 +1508,7 @@ export class AgentSession {
|
||||
#buildAdvisorRuntime(seedToCurrent = false): boolean {
|
||||
if (this.#isDisposed) return false;
|
||||
if (this.#advisorRuntime) return true;
|
||||
if (!this.settings.get("advisor.enabled")) return false;
|
||||
if (!this.#advisorEnabled) return false;
|
||||
if (this.#agentKind !== "main" && !this.settings.get("advisor.subagents")) return false;
|
||||
|
||||
const advisorSel = resolveRoleSelection(
|
||||
@@ -11293,18 +11295,16 @@ export class AgentSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable the advisor for this session. The setting is persisted,
|
||||
* Enable or disable the advisor for this session. The setting is overridden for the session,
|
||||
* and the runtime is started or stopped to match.
|
||||
*
|
||||
* @returns true when the advisor is actively running after the call.
|
||||
*/
|
||||
setAdvisorEnabled(enabled: boolean): boolean {
|
||||
this.#advisorEnabled = enabled;
|
||||
if (enabled) {
|
||||
this.settings.clearOverride("advisor.enabled");
|
||||
this.settings.set("advisor.enabled", true);
|
||||
return this.#buildAdvisorRuntime(true);
|
||||
}
|
||||
this.settings.set("advisor.enabled", false);
|
||||
this.#stopAdvisorRuntime();
|
||||
return false;
|
||||
}
|
||||
@@ -11315,7 +11315,14 @@ export class AgentSession {
|
||||
* @returns true when the advisor is actively running after the call.
|
||||
*/
|
||||
toggleAdvisorEnabled(): boolean {
|
||||
return this.setAdvisorEnabled(!this.settings.get("advisor.enabled"));
|
||||
return this.setAdvisorEnabled(!this.#advisorEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the advisor setting is enabled for this session.
|
||||
*/
|
||||
isAdvisorEnabled(): boolean {
|
||||
return this.#advisorEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -11332,7 +11339,7 @@ export class AgentSession {
|
||||
* Return structured advisor stats for the status command and TUI panel.
|
||||
*/
|
||||
getAdvisorStats(): AdvisorStats {
|
||||
const configured = this.settings.get("advisor.enabled") as boolean;
|
||||
const configured = this.#advisorEnabled;
|
||||
const advisor = this.#advisorAgent;
|
||||
if (!advisor) {
|
||||
return {
|
||||
|
||||
@@ -435,7 +435,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
||||
const { verb, rest } = parseSubcommand(command.args);
|
||||
if (!verb || verb === "toggle") {
|
||||
const active = runtime.session.toggleAdvisorEnabled();
|
||||
const configured = runtime.session.settings.get("advisor.enabled") as boolean;
|
||||
const configured = runtime.session.isAdvisorEnabled();
|
||||
if (active) {
|
||||
await runtime.output("Advisor enabled.");
|
||||
} else if (configured) {
|
||||
@@ -473,7 +473,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
||||
const { verb, rest } = parseSubcommand(command.args);
|
||||
if (!verb || verb === "toggle") {
|
||||
const active = runtime.ctx.session.toggleAdvisorEnabled();
|
||||
const configured = runtime.ctx.session.settings.get("advisor.enabled") as boolean;
|
||||
const configured = runtime.ctx.session.isAdvisorEnabled();
|
||||
if (active) {
|
||||
runtime.ctx.showStatus("Advisor enabled.");
|
||||
} else if (configured) {
|
||||
@@ -481,6 +481,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
||||
} else {
|
||||
runtime.ctx.showStatus("Advisor disabled.");
|
||||
}
|
||||
refreshStatusLine(runtime.ctx);
|
||||
runtime.ctx.editor.setText("");
|
||||
return;
|
||||
}
|
||||
@@ -489,12 +490,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
||||
runtime.ctx.showStatus(
|
||||
active ? "Advisor enabled." : "Advisor setting enabled, but no model is assigned to the 'advisor' role.",
|
||||
);
|
||||
refreshStatusLine(runtime.ctx);
|
||||
runtime.ctx.editor.setText("");
|
||||
return;
|
||||
}
|
||||
if (verb === "off") {
|
||||
runtime.ctx.session.setAdvisorEnabled(false);
|
||||
runtime.ctx.showStatus("Advisor disabled.");
|
||||
refreshStatusLine(runtime.ctx);
|
||||
runtime.ctx.editor.setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ describe("AgentSession advisor toggle", () => {
|
||||
|
||||
it("starts with advisor disabled", () => {
|
||||
expect(session.isAdvisorActive()).toBe(false);
|
||||
expect(session.settings.get("advisor.enabled")).toBe(false);
|
||||
expect(session.isAdvisorEnabled()).toBe(false);
|
||||
expect(session.formatAdvisorStatus()).toBe("Advisor is disabled.");
|
||||
});
|
||||
|
||||
@@ -76,19 +76,28 @@ describe("AgentSession advisor toggle", () => {
|
||||
const active = session.toggleAdvisorEnabled();
|
||||
expect(active).toBe(true);
|
||||
expect(session.isAdvisorActive()).toBe(true);
|
||||
expect(session.settings.get("advisor.enabled")).toBe(true);
|
||||
expect(session.isAdvisorEnabled()).toBe(true);
|
||||
expect(session.formatAdvisorStatus()).toContain("Advisor is enabled (anthropic/claude-sonnet-4-5)");
|
||||
});
|
||||
|
||||
it("explicit enable clears protocol default-off override", () => {
|
||||
it("explicit enable overrides default-off setting for the session only", () => {
|
||||
session.settings.setModelRole("advisor", "anthropic/claude-sonnet-4-5");
|
||||
session.settings.override("advisor.enabled", false);
|
||||
const customSession = new AgentSession({
|
||||
agent: session.agent,
|
||||
sessionManager,
|
||||
settings: session.settings,
|
||||
modelRegistry,
|
||||
advisorReadOnlyTools: [],
|
||||
});
|
||||
expect(customSession.isAdvisorEnabled()).toBe(false);
|
||||
|
||||
const active = session.setAdvisorEnabled(true);
|
||||
const active = customSession.setAdvisorEnabled(true);
|
||||
|
||||
expect(active).toBe(true);
|
||||
expect(session.isAdvisorActive()).toBe(true);
|
||||
expect(session.settings.get("advisor.enabled")).toBe(true);
|
||||
expect(customSession.isAdvisorActive()).toBe(true);
|
||||
expect(customSession.isAdvisorEnabled()).toBe(true);
|
||||
expect(customSession.settings.get("advisor.enabled")).toBe(false);
|
||||
});
|
||||
|
||||
it("toggle disables the advisor and runtime", () => {
|
||||
@@ -97,16 +106,60 @@ describe("AgentSession advisor toggle", () => {
|
||||
const active = session.toggleAdvisorEnabled();
|
||||
expect(active).toBe(false);
|
||||
expect(session.isAdvisorActive()).toBe(false);
|
||||
expect(session.settings.get("advisor.enabled")).toBe(false);
|
||||
expect(session.isAdvisorEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("setAdvisorEnabled reports inactive when no advisor model is assigned", () => {
|
||||
const active = session.setAdvisorEnabled(true);
|
||||
expect(active).toBe(false);
|
||||
expect(session.isAdvisorActive()).toBe(false);
|
||||
expect(session.settings.get("advisor.enabled")).toBe(true);
|
||||
expect(session.isAdvisorEnabled()).toBe(true);
|
||||
expect(session.formatAdvisorStatus()).toBe(
|
||||
"Advisor setting is enabled, but no model is assigned to the 'advisor' role.",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps sessions isolated when sharing a Settings instance", async () => {
|
||||
const sharedSettings = Settings.isolated({ "compaction.enabled": false });
|
||||
sharedSettings.setModelRole("advisor", "anthropic/claude-sonnet-4-5");
|
||||
expect(sharedSettings.get("advisor.enabled")).toBe(false);
|
||||
|
||||
const sessionA = new AgentSession({
|
||||
agent: session.agent,
|
||||
sessionManager,
|
||||
settings: sharedSettings,
|
||||
modelRegistry,
|
||||
advisorReadOnlyTools: [],
|
||||
});
|
||||
const sessionB = new AgentSession({
|
||||
agent: session.agent,
|
||||
sessionManager,
|
||||
settings: sharedSettings,
|
||||
modelRegistry,
|
||||
advisorReadOnlyTools: [],
|
||||
});
|
||||
|
||||
expect(sessionA.isAdvisorEnabled()).toBe(false);
|
||||
expect(sessionB.isAdvisorEnabled()).toBe(false);
|
||||
|
||||
const activeA = sessionA.setAdvisorEnabled(true);
|
||||
expect(activeA).toBe(true);
|
||||
expect(sessionA.isAdvisorEnabled()).toBe(true);
|
||||
expect(sessionA.isAdvisorActive()).toBe(true);
|
||||
|
||||
expect(sessionB.isAdvisorEnabled()).toBe(false);
|
||||
expect(sessionB.isAdvisorActive()).toBe(false);
|
||||
expect(sessionB.formatAdvisorStatus()).toBe("Advisor is disabled.");
|
||||
|
||||
const activeB = sessionB.toggleAdvisorEnabled();
|
||||
expect(activeB).toBe(true);
|
||||
expect(sessionB.isAdvisorEnabled()).toBe(true);
|
||||
|
||||
sessionA.setAdvisorEnabled(false);
|
||||
expect(sessionA.isAdvisorEnabled()).toBe(false);
|
||||
expect(sessionA.isAdvisorActive()).toBe(false);
|
||||
|
||||
expect(sessionB.isAdvisorEnabled()).toBe(true);
|
||||
expect(sessionB.isAdvisorActive()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user