@oh-my-pi/pi-coding-agent 17.0.1 → 17.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +162 -40
- package/dist/cli.js +4559 -4543
- package/dist/types/advisor/config.d.ts +14 -6
- package/dist/types/advisor/runtime.d.ts +20 -11
- package/dist/types/autolearn/controller.d.ts +1 -0
- package/dist/types/config/model-discovery.d.ts +17 -0
- package/dist/types/config/model-resolver.d.ts +6 -2
- package/dist/types/config/settings-schema.d.ts +39 -10
- package/dist/types/config/settings.d.ts +50 -0
- package/dist/types/cursor.d.ts +11 -0
- package/dist/types/discovery/helpers.d.ts +5 -2
- package/dist/types/eval/agent-bridge.d.ts +7 -19
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +18 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +55 -1
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/extensibility/utils.d.ts +2 -2
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/lsp/types.d.ts +3 -0
- package/dist/types/mcp/transports/stdio.d.ts +42 -1
- package/dist/types/modes/components/advisor-config.d.ts +8 -1
- package/dist/types/modes/components/agent-hub.d.ts +15 -0
- package/dist/types/modes/components/hook-editor.d.ts +7 -0
- package/dist/types/modes/components/model-hub.d.ts +5 -2
- package/dist/types/modes/components/session-selector.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +8 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/print-mode.d.ts +1 -1
- package/dist/types/modes/warp-events.d.ts +24 -0
- package/dist/types/modes/warp-events.test.d.ts +1 -0
- package/dist/types/plan-mode/model-transition.d.ts +47 -0
- package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
- package/dist/types/registry/agent-lifecycle.d.ts +26 -1
- package/dist/types/registry/persisted-agents.d.ts +3 -0
- package/dist/types/sdk.d.ts +27 -5
- package/dist/types/session/agent-session.d.ts +34 -10
- package/dist/types/session/session-entries.d.ts +6 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-manager.d.ts +19 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
- package/dist/types/task/executor.d.ts +26 -1
- package/dist/types/task/index.d.ts +1 -1
- package/dist/types/task/label.d.ts +1 -1
- package/dist/types/task/parallel.d.ts +14 -0
- package/dist/types/task/structured-subagent.d.ts +111 -0
- package/dist/types/task/types.d.ts +51 -0
- package/dist/types/telemetry-export.d.ts +34 -9
- package/dist/types/tools/approval.d.ts +8 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/essential-tools.d.ts +29 -0
- package/dist/types/tools/fetch.d.ts +4 -5
- package/dist/types/tools/hub/messaging.d.ts +5 -1
- package/dist/types/tools/image-gen.d.ts +2 -1
- package/dist/types/tools/index.d.ts +17 -1
- package/dist/types/tools/todo.d.ts +31 -0
- package/dist/types/tools/xdev.d.ts +11 -2
- package/dist/types/tui/tree-list.d.ts +7 -0
- package/dist/types/utils/markit.d.ts +11 -0
- package/dist/types/utils/title-generator.d.ts +2 -1
- package/dist/types/web/search/providers/kimi.d.ts +4 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +21 -16
- package/src/advisor/__tests__/advisor.test.ts +1304 -42
- package/src/advisor/__tests__/config.test.ts +58 -2
- package/src/advisor/config.ts +76 -24
- package/src/advisor/runtime.ts +445 -92
- package/src/autolearn/controller.ts +23 -28
- package/src/cli/file-processor.ts +1 -2
- package/src/cli.ts +5 -1
- package/src/config/model-discovery.ts +81 -21
- package/src/config/model-registry.ts +25 -6
- package/src/config/model-resolver.ts +14 -7
- package/src/config/settings-schema.ts +46 -9
- package/src/config/settings.ts +405 -25
- package/src/cursor.ts +20 -3
- package/src/debug/report-bundle.ts +40 -4
- package/src/discovery/helpers.ts +28 -5
- package/src/eval/__tests__/agent-bridge.test.ts +133 -47
- package/src/eval/__tests__/prelude-agent.test.ts +29 -0
- package/src/eval/agent-bridge.ts +104 -477
- package/src/eval/jl/prelude.jl +7 -6
- package/src/eval/js/shared/prelude.txt +5 -4
- package/src/eval/py/prelude.py +11 -39
- package/src/eval/rb/prelude.rb +5 -6
- package/src/exec/bash-executor.ts +14 -5
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/extensibility/extensions/loader.ts +3 -3
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/runner.ts +26 -0
- package/src/extensibility/extensions/types.ts +18 -0
- package/src/extensibility/extensions/wrapper.ts +2 -1
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +149 -8
- package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
- package/src/extensibility/plugins/manager.ts +2 -2
- package/src/extensibility/shared-events.ts +6 -0
- package/src/extensibility/utils.ts +91 -25
- package/src/irc/bus.ts +22 -3
- package/src/launch/broker.ts +3 -2
- package/src/launch/client.ts +2 -2
- package/src/launch/presence.ts +2 -2
- package/src/lsp/client.ts +58 -1
- package/src/lsp/index.ts +62 -6
- package/src/lsp/types.ts +3 -0
- package/src/main.ts +11 -8
- package/src/mcp/manager.ts +9 -3
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/mcp/transports/stdio.test.ts +269 -1
- package/src/mcp/transports/stdio.ts +255 -24
- package/src/modes/components/advisor-config.ts +65 -3
- package/src/modes/components/agent-hub.ts +1 -72
- package/src/modes/components/ask-dialog.ts +1 -1
- package/src/modes/components/bash-execution.ts +7 -3
- package/src/modes/components/eval-execution.ts +3 -1
- package/src/modes/components/hook-editor.ts +18 -3
- package/src/modes/components/model-hub.ts +138 -42
- package/src/modes/components/session-selector.ts +4 -0
- package/src/modes/components/status-line/component.test.ts +1 -0
- package/src/modes/components/status-line/segments.ts +21 -6
- package/src/modes/controllers/command-controller.ts +167 -47
- package/src/modes/controllers/event-controller.ts +5 -0
- package/src/modes/controllers/extension-ui-controller.ts +4 -22
- package/src/modes/controllers/input-controller.ts +12 -12
- package/src/modes/controllers/selector-controller.ts +191 -31
- package/src/modes/interactive-mode.ts +169 -62
- package/src/modes/print-mode.ts +3 -3
- package/src/modes/rpc/host-tools.ts +2 -1
- package/src/modes/rpc/rpc-mode.ts +19 -4
- package/src/modes/warp-events.test.ts +794 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/plan-mode/model-transition.test.ts +60 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/tools/eval.md +5 -2
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/task.md +12 -8
- package/src/prompts/tools/write.md +1 -1
- package/src/registry/agent-lifecycle.ts +133 -18
- package/src/registry/persisted-agents.ts +74 -0
- package/src/sdk.ts +343 -122
- package/src/session/agent-session.ts +1359 -368
- package/src/session/session-entries.ts +6 -1
- package/src/session/session-history-format.ts +20 -5
- package/src/session/session-manager.ts +57 -0
- package/src/session/streaming-output.ts +41 -1
- package/src/slash-commands/builtin-registry.ts +7 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/executor.ts +100 -22
- package/src/task/index.ts +258 -429
- package/src/task/label.ts +2 -0
- package/src/task/parallel.ts +43 -0
- package/src/task/persisted-revive.ts +19 -7
- package/src/task/structured-subagent.ts +642 -0
- package/src/task/types.ts +58 -0
- package/src/telemetry-export.ts +453 -97
- package/src/tools/__tests__/eval-description.test.ts +1 -1
- package/src/tools/approval.ts +11 -0
- package/src/tools/bash.ts +71 -38
- package/src/tools/essential-tools.ts +45 -0
- package/src/tools/fetch.ts +28 -105
- package/src/tools/gh.ts +169 -2
- package/src/tools/hub/messaging.ts +16 -3
- package/src/tools/image-gen.ts +69 -7
- package/src/tools/index.ts +50 -15
- package/src/tools/path-utils.ts +1 -0
- package/src/tools/read.ts +62 -29
- package/src/tools/todo.ts +126 -13
- package/src/tools/write.ts +22 -4
- package/src/tools/xdev.ts +14 -3
- package/src/tui/tree-list.ts +39 -0
- package/src/utils/markit.ts +12 -0
- package/src/utils/title-generator.ts +15 -4
- package/src/utils/zip.ts +16 -1
- package/src/web/search/providers/kimi.ts +18 -12
- package/src/web/search/types.ts +6 -1
|
@@ -13,7 +13,11 @@ import {
|
|
|
13
13
|
saveWatchdogConfigFile,
|
|
14
14
|
} from "../../advisor";
|
|
15
15
|
import { reset as resetCapabilities } from "../../capability";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
formatModelSelectorValue,
|
|
18
|
+
resolveAdvisorRoleSelection,
|
|
19
|
+
resolveModelRoleValue,
|
|
20
|
+
} from "../../config/model-resolver";
|
|
17
21
|
import { getRoleInfo } from "../../config/model-roles";
|
|
18
22
|
import { settings } from "../../config/settings";
|
|
19
23
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
@@ -46,7 +50,12 @@ import {
|
|
|
46
50
|
type ResetUsageAccount,
|
|
47
51
|
toResetUsageAccounts,
|
|
48
52
|
} from "../../slash-commands/helpers/reset-usage";
|
|
49
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
AUTO_THINKING,
|
|
55
|
+
type ConfiguredThinkingLevel,
|
|
56
|
+
concreteThinkingLevel,
|
|
57
|
+
parseConfiguredThinkingLevel,
|
|
58
|
+
} from "../../thinking";
|
|
50
59
|
import {
|
|
51
60
|
isImageProviderPreference,
|
|
52
61
|
isSearchProviderId,
|
|
@@ -68,7 +77,7 @@ import { ExtensionDashboard } from "../components/extensions";
|
|
|
68
77
|
import { HistorySearchComponent } from "../components/history-search";
|
|
69
78
|
import { LoginDialogComponent } from "../components/login-dialog";
|
|
70
79
|
import { LogoutAccountSelectorComponent } from "../components/logout-account-selector";
|
|
71
|
-
import { ModelHubComponent } from "../components/model-hub";
|
|
80
|
+
import { ModelHubComponent, type ModelRoleSelectionScope } from "../components/model-hub";
|
|
72
81
|
import { ModelPickerComponent } from "../components/model-picker";
|
|
73
82
|
import { OAuthSelectorComponent } from "../components/oauth-selector";
|
|
74
83
|
import { PluginSelectorComponent } from "../components/plugin-selector";
|
|
@@ -87,6 +96,15 @@ const MANUAL_LOGIN_PROMPT = "Paste the authorization code (or full redirect URL)
|
|
|
87
96
|
|
|
88
97
|
export class SelectorController {
|
|
89
98
|
constructor(private ctx: InteractiveModeContext) {}
|
|
99
|
+
#defaultRoleMutationTail = Promise.resolve();
|
|
100
|
+
|
|
101
|
+
async #acquireDefaultRoleMutation(): Promise<() => void> {
|
|
102
|
+
const previous = this.#defaultRoleMutationTail;
|
|
103
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
104
|
+
this.#defaultRoleMutationTail = previous.then(() => promise);
|
|
105
|
+
await previous;
|
|
106
|
+
return resolve;
|
|
107
|
+
}
|
|
90
108
|
|
|
91
109
|
async #refreshOAuthProviderAuthState(): Promise<void> {
|
|
92
110
|
const oauthProviders = getOAuthProviders();
|
|
@@ -280,6 +298,13 @@ export class SelectorController {
|
|
|
280
298
|
close: done,
|
|
281
299
|
requestRender: () => this.ctx.ui.requestRender(),
|
|
282
300
|
notify: message => this.ctx.showStatus(message),
|
|
301
|
+
getAdvisorStats: () => this.ctx.session.getAdvisorStats().advisors,
|
|
302
|
+
getUsageReports: async () => this.ctx.session.fetchUsageReports?.() ?? null,
|
|
303
|
+
resolveActiveAccount: (provider, sessionId) =>
|
|
304
|
+
this.ctx.session.modelRegistry.authStorage.getOAuthAccountIdentity(
|
|
305
|
+
provider,
|
|
306
|
+
sessionId ?? this.ctx.session.sessionId,
|
|
307
|
+
),
|
|
283
308
|
});
|
|
284
309
|
overlayHandle = this.ctx.ui.showOverlay(overlay, {
|
|
285
310
|
anchor: "bottom-center",
|
|
@@ -710,55 +735,173 @@ export class SelectorController {
|
|
|
710
735
|
this.ctx.session.modelRegistry,
|
|
711
736
|
this.ctx.session.scopedModels,
|
|
712
737
|
{
|
|
713
|
-
onAssign: async (model, role, thinkingLevel, selector) => {
|
|
738
|
+
onAssign: async (model, role, thinkingLevel, selector, scope?: ModelRoleSelectionScope) => {
|
|
739
|
+
const releaseDefaultMutation = role === "default" ? await this.#acquireDefaultRoleMutation() : undefined;
|
|
740
|
+
const configuredStorage = this.ctx.settings.get("modelRoleStorage");
|
|
741
|
+
const targetScope = configuredStorage === "project" ? (scope ?? "project") : "global";
|
|
714
742
|
// `auto` is session-global: never baked into a per-role model value
|
|
715
743
|
// (it can't round-trip through `model:<level>`). Apply it to the session
|
|
716
744
|
// separately and persist via `defaultThinkingLevel`.
|
|
717
745
|
const isAuto = thinkingLevel === AUTO_THINKING;
|
|
718
746
|
const concreteThinking = isAuto || thinkingLevel === undefined ? undefined : thinkingLevel;
|
|
719
747
|
const selectorValue = selector ?? `${model.provider}/${model.id}`;
|
|
748
|
+
const scopeLabel =
|
|
749
|
+
configuredStorage === "project" ? `${targetScope === "project" ? "Project" : "Global"} ` : "";
|
|
750
|
+
const defaultStatusLabel = configuredStorage === "project" ? `${scopeLabel}default` : "Default";
|
|
720
751
|
try {
|
|
721
752
|
if (role === "default") {
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
753
|
+
const effectiveProvenance = this.ctx.settings.getModelRoleProvenance("default");
|
|
754
|
+
const shadowedGlobal =
|
|
755
|
+
configuredStorage === "project" &&
|
|
756
|
+
targetScope === "global" &&
|
|
757
|
+
(effectiveProvenance === "project" ||
|
|
758
|
+
effectiveProvenance === "overlay" ||
|
|
759
|
+
(effectiveProvenance === "runtime" &&
|
|
760
|
+
this.ctx.settings.isProjectModelRoleRuntimeOverrideActive("default")));
|
|
761
|
+
const shadowedProject =
|
|
762
|
+
configuredStorage === "project" &&
|
|
763
|
+
targetScope === "project" &&
|
|
764
|
+
effectiveProvenance === "overlay";
|
|
765
|
+
if (shadowedGlobal) {
|
|
766
|
+
this.ctx.settings.setModelRole(
|
|
767
|
+
"default",
|
|
768
|
+
formatModelSelectorValue(selectorValue, concreteThinking),
|
|
769
|
+
);
|
|
770
|
+
if (isAuto) {
|
|
732
771
|
this.ctx.settings.set("defaultThinkingLevel", AUTO_THINKING);
|
|
733
772
|
}
|
|
734
|
-
} else if (
|
|
735
|
-
this.ctx.
|
|
736
|
-
|
|
737
|
-
|
|
773
|
+
} else if (shadowedProject) {
|
|
774
|
+
this.ctx.settings.setProjectModelRole(
|
|
775
|
+
"default",
|
|
776
|
+
formatModelSelectorValue(selectorValue, concreteThinking),
|
|
777
|
+
);
|
|
778
|
+
if (isAuto) {
|
|
779
|
+
this.ctx.settings.set("defaultThinkingLevel", AUTO_THINKING);
|
|
780
|
+
}
|
|
781
|
+
} else {
|
|
782
|
+
const { switched } = await this.ctx.session.setModel(model, role, {
|
|
783
|
+
selector,
|
|
784
|
+
thinkingLevel: isAuto ? ThinkingLevel.Inherit : concreteThinking,
|
|
785
|
+
persist: targetScope === "global",
|
|
786
|
+
currentContextTokens,
|
|
787
|
+
});
|
|
788
|
+
if (!switched) return;
|
|
789
|
+
if (targetScope === "project") {
|
|
790
|
+
this.ctx.settings.setProjectModelRole(
|
|
791
|
+
"default",
|
|
792
|
+
formatModelSelectorValue(selectorValue, concreteThinking),
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
if (isAuto) {
|
|
796
|
+
this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
|
|
797
|
+
} else if (concreteThinking && concreteThinking !== ThinkingLevel.Inherit) {
|
|
798
|
+
this.ctx.session.setThinkingLevel(concreteThinking);
|
|
799
|
+
}
|
|
738
800
|
this.ctx.statusLine.invalidate();
|
|
739
801
|
this.ctx.updateEditorBorderColor();
|
|
740
802
|
}
|
|
741
|
-
this.ctx.showStatus(
|
|
803
|
+
this.ctx.showStatus(`${defaultStatusLabel} model: ${selector ?? model.id}`);
|
|
742
804
|
} else {
|
|
743
805
|
// Other roles (smol, slow, custom): update settings, not the current model.
|
|
744
|
-
|
|
806
|
+
const modelRoleValue = formatModelSelectorValue(selectorValue, concreteThinking);
|
|
807
|
+
if (targetScope === "project") {
|
|
808
|
+
this.ctx.settings.setProjectModelRole(role, modelRoleValue);
|
|
809
|
+
} else {
|
|
810
|
+
this.ctx.settings.setModelRole(role, modelRoleValue);
|
|
811
|
+
}
|
|
745
812
|
if (isAuto) {
|
|
746
813
|
this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
|
|
747
814
|
}
|
|
748
815
|
const roleInfo = getRoleInfo(role, settings);
|
|
749
|
-
this.ctx.showStatus(
|
|
816
|
+
this.ctx.showStatus(
|
|
817
|
+
`${scopeLabel}${roleInfo?.tag ?? roleInfo?.name ?? role} model: ${selector ?? model.id}`,
|
|
818
|
+
);
|
|
750
819
|
}
|
|
751
820
|
} catch (error) {
|
|
752
821
|
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
822
|
+
} finally {
|
|
823
|
+
releaseDefaultMutation?.();
|
|
824
|
+
hub?.refreshAfterExternalMutation();
|
|
753
825
|
}
|
|
754
826
|
},
|
|
755
|
-
onUnassign: role => {
|
|
827
|
+
onUnassign: async (role, scope?: ModelRoleSelectionScope) => {
|
|
828
|
+
const releaseDefaultMutation = role === "default" ? await this.#acquireDefaultRoleMutation() : undefined;
|
|
829
|
+
const configuredStorage = this.ctx.settings.get("modelRoleStorage");
|
|
830
|
+
const targetScope = configuredStorage === "project" ? (scope ?? "project") : "global";
|
|
831
|
+
const scopeLabel =
|
|
832
|
+
configuredStorage === "project" ? `${targetScope === "project" ? "Project" : "Global"} ` : "";
|
|
756
833
|
try {
|
|
757
|
-
|
|
834
|
+
const previousEffectiveRoleValue =
|
|
835
|
+
role === "default" ? this.ctx.settings.getModelRole("default") : undefined;
|
|
836
|
+
if (targetScope === "project") {
|
|
837
|
+
this.ctx.settings.clearProjectModelRole(role);
|
|
838
|
+
} else {
|
|
839
|
+
this.ctx.settings.setModelRole(role, undefined);
|
|
840
|
+
}
|
|
758
841
|
const roleInfo = getRoleInfo(role, settings);
|
|
759
|
-
this.ctx.showStatus(
|
|
842
|
+
this.ctx.showStatus(
|
|
843
|
+
`${scopeLabel}${roleInfo?.tag ?? roleInfo?.name ?? role} role cleared — auto-selection applies`,
|
|
844
|
+
);
|
|
845
|
+
// Clearing either persisted scope can also remove a captured
|
|
846
|
+
// runtime override. When that changes the effective default,
|
|
847
|
+
// resolve the newly exposed persisted layer and switch the live
|
|
848
|
+
// session without writing it back to global settings. Overlay
|
|
849
|
+
// and runtime provenance remain authoritative and session-neutral.
|
|
850
|
+
if (role === "default") {
|
|
851
|
+
const fallbackRoleValue = this.ctx.settings.getModelRole("default");
|
|
852
|
+
const fallbackProvenance = this.ctx.settings.getModelRoleProvenance("default");
|
|
853
|
+
const exposesPersistedFallback =
|
|
854
|
+
fallbackProvenance === "project" || fallbackProvenance === "global";
|
|
855
|
+
if (
|
|
856
|
+
fallbackRoleValue &&
|
|
857
|
+
fallbackRoleValue !== previousEffectiveRoleValue &&
|
|
858
|
+
exposesPersistedFallback
|
|
859
|
+
) {
|
|
860
|
+
const scopedModels = this.ctx.session.scopedModels.map(sm => sm.model);
|
|
861
|
+
const availableModels =
|
|
862
|
+
scopedModels.length > 0 ? scopedModels : this.ctx.session.getAvailableModels();
|
|
863
|
+
const resolved = resolveModelRoleValue(fallbackRoleValue, availableModels, {
|
|
864
|
+
settings: this.ctx.settings,
|
|
865
|
+
});
|
|
866
|
+
if (resolved.model) {
|
|
867
|
+
const fallbackModel = resolved.model;
|
|
868
|
+
const isAuto = resolved.thinkingLevel === AUTO_THINKING;
|
|
869
|
+
let concreteThinking = concreteThinkingLevel(resolved.thinkingLevel);
|
|
870
|
+
let isAutoFromDefault = false;
|
|
871
|
+
if (!resolved.explicitThinkingLevel && !concreteThinking) {
|
|
872
|
+
const defaultLevel = parseConfiguredThinkingLevel(
|
|
873
|
+
this.ctx.settings.get("defaultThinkingLevel"),
|
|
874
|
+
);
|
|
875
|
+
if (defaultLevel === AUTO_THINKING) {
|
|
876
|
+
isAutoFromDefault = true;
|
|
877
|
+
} else if (defaultLevel) {
|
|
878
|
+
concreteThinking = defaultLevel;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const effectiveIsAuto = isAuto || isAutoFromDefault;
|
|
882
|
+
const { switched } = await this.ctx.session.setModel(fallbackModel, "default", {
|
|
883
|
+
persist: false,
|
|
884
|
+
thinkingLevel: effectiveIsAuto
|
|
885
|
+
? ThinkingLevel.Inherit
|
|
886
|
+
: (concreteThinking ?? ThinkingLevel.Inherit),
|
|
887
|
+
currentContextTokens,
|
|
888
|
+
});
|
|
889
|
+
if (!switched) return;
|
|
890
|
+
if (effectiveIsAuto) {
|
|
891
|
+
this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
|
|
892
|
+
} else if (concreteThinking && concreteThinking !== ThinkingLevel.Inherit) {
|
|
893
|
+
this.ctx.session.setThinkingLevel(concreteThinking);
|
|
894
|
+
}
|
|
895
|
+
this.ctx.statusLine.invalidate();
|
|
896
|
+
this.ctx.updateEditorBorderColor();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
760
900
|
} catch (error) {
|
|
761
901
|
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
902
|
+
} finally {
|
|
903
|
+
releaseDefaultMutation?.();
|
|
904
|
+
hub?.refreshAfterExternalMutation();
|
|
762
905
|
}
|
|
763
906
|
},
|
|
764
907
|
onFallbackChainChange: (role, chain) => {
|
|
@@ -773,8 +916,8 @@ export class SelectorController {
|
|
|
773
916
|
const roleInfo = getRoleInfo(role, settings);
|
|
774
917
|
this.ctx.showStatus(
|
|
775
918
|
chain.length > 0
|
|
776
|
-
? `${roleInfo?.name ?? role} fallbacks: ${chain.join(" → ")}`
|
|
777
|
-
: `${roleInfo?.name ?? role} fallbacks cleared`,
|
|
919
|
+
? `${roleInfo?.tag ?? roleInfo?.name ?? role} fallbacks: ${chain.join(" → ")}`
|
|
920
|
+
: `${roleInfo?.tag ?? roleInfo?.name ?? role} fallbacks cleared`,
|
|
778
921
|
);
|
|
779
922
|
} catch (error) {
|
|
780
923
|
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
@@ -1124,10 +1267,16 @@ export class SelectorController {
|
|
|
1124
1267
|
sessions,
|
|
1125
1268
|
async (session: SessionInfo) => {
|
|
1126
1269
|
selector.lockInput();
|
|
1270
|
+
let keepOpen = false;
|
|
1127
1271
|
try {
|
|
1128
|
-
await this.handleResumeSession(session.path);
|
|
1272
|
+
const success = await this.handleResumeSession(session.path);
|
|
1273
|
+
if (!success) {
|
|
1274
|
+
keepOpen = true;
|
|
1275
|
+
selector.unlockInput();
|
|
1276
|
+
this.ctx.ui.requestRender();
|
|
1277
|
+
}
|
|
1129
1278
|
} finally {
|
|
1130
|
-
done();
|
|
1279
|
+
if (!keepOpen) done();
|
|
1131
1280
|
}
|
|
1132
1281
|
},
|
|
1133
1282
|
() => {
|
|
@@ -1205,13 +1354,23 @@ export class SelectorController {
|
|
|
1205
1354
|
return true;
|
|
1206
1355
|
}
|
|
1207
1356
|
|
|
1208
|
-
async handleResumeSession(sessionPath: string): Promise<
|
|
1209
|
-
this.ctx.clearTransientSessionUi();
|
|
1210
|
-
|
|
1357
|
+
async handleResumeSession(sessionPath: string, options?: { settingsFlushed?: boolean }): Promise<boolean> {
|
|
1211
1358
|
const previousCwd = this.ctx.sessionManager.getCwd();
|
|
1359
|
+
// Flush pending settings writes before switching sessions so a save
|
|
1360
|
+
// failure leaves the session, process project dir, and Settings in the
|
|
1361
|
+
// source scope — the switch below mutates the SessionManager cwd.
|
|
1362
|
+
if (!options?.settingsFlushed) {
|
|
1363
|
+
try {
|
|
1364
|
+
await this.ctx.settings.flush();
|
|
1365
|
+
} catch (err) {
|
|
1366
|
+
this.ctx.showError(`Failed to save pending settings: ${err instanceof Error ? err.message : String(err)}`);
|
|
1367
|
+
return false;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1212
1370
|
// Switch session via AgentSession (emits hook and tool session events). The
|
|
1213
1371
|
// SessionManager adopts the resumed session's own cwd when it differs.
|
|
1214
1372
|
await this.ctx.session.switchSession(sessionPath);
|
|
1373
|
+
this.ctx.clearTransientSessionUi();
|
|
1215
1374
|
const newCwd = this.ctx.sessionManager.getCwd();
|
|
1216
1375
|
const movedProject = normalizePathForComparison(newCwd) !== normalizePathForComparison(previousCwd);
|
|
1217
1376
|
if (movedProject) {
|
|
@@ -1226,6 +1385,7 @@ export class SelectorController {
|
|
|
1226
1385
|
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|
|
1227
1386
|
await this.ctx.reloadTodos();
|
|
1228
1387
|
this.ctx.showStatus(movedProject ? `Resumed session in ${shortenPath(newCwd)}` : "Resumed session");
|
|
1388
|
+
return true;
|
|
1229
1389
|
}
|
|
1230
1390
|
|
|
1231
1391
|
async handleSessionDeleteCommand(): Promise<void> {
|
|
@@ -56,8 +56,15 @@ import { reset as resetCapabilities } from "../capability";
|
|
|
56
56
|
import type { CollabGuestLink } from "../collab/guest";
|
|
57
57
|
import type { CollabHost } from "../collab/host";
|
|
58
58
|
import { KeybindingsManager } from "../config/keybindings";
|
|
59
|
+
import type { ResolvedModelRoleValue } from "../config/model-resolver";
|
|
59
60
|
import { applyProviderGlobalsFromSettings } from "../config/provider-globals";
|
|
60
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
isSettingsInitialized,
|
|
63
|
+
onModelRolesChanged,
|
|
64
|
+
onStatusLineSessionAccentChanged,
|
|
65
|
+
Settings,
|
|
66
|
+
settings,
|
|
67
|
+
} from "../config/settings";
|
|
61
68
|
import { clearClaudePluginRootsCache } from "../discovery/helpers";
|
|
62
69
|
import type {
|
|
63
70
|
AutocompleteProviderFactory,
|
|
@@ -88,6 +95,7 @@ import {
|
|
|
88
95
|
resolveApprovedPlan,
|
|
89
96
|
resolvePlanTitle,
|
|
90
97
|
} from "../plan-mode/approved-plan";
|
|
98
|
+
import { resolvePlanModelTransition } from "../plan-mode/model-transition";
|
|
91
99
|
import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
|
|
92
100
|
import planModeCompactInstructionsPrompt from "../prompts/system/plan-mode-compact-instructions.md" with {
|
|
93
101
|
type: "text",
|
|
@@ -115,7 +123,12 @@ import type { LspStartupServerInfo } from "../tools";
|
|
|
115
123
|
import { normalizeLocalScheme } from "../tools/path-utils";
|
|
116
124
|
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../tools/render-utils";
|
|
117
125
|
import { setAutoQaConsentHandler } from "../tools/report-tool-issue";
|
|
118
|
-
import {
|
|
126
|
+
import {
|
|
127
|
+
formatPhaseDisplayName,
|
|
128
|
+
selectCollapsedTodos,
|
|
129
|
+
setActiveTodoDescriptionsProvider,
|
|
130
|
+
todoMatchesAnyDescription,
|
|
131
|
+
} from "../tools/todo";
|
|
119
132
|
import { ToolError } from "../tools/tool-errors";
|
|
120
133
|
import { vocalizer } from "../tts/vocalizer";
|
|
121
134
|
import { renderTreeList } from "../tui/tree-list";
|
|
@@ -539,6 +552,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
539
552
|
#goalSuppressNextContinuation = false;
|
|
540
553
|
#planModePreviousModelState: { model: Model; thinkingLevel?: ConfiguredThinkingLevel } | undefined;
|
|
541
554
|
#pendingModelSwitch: { model: Model; thinkingLevel?: ConfiguredThinkingLevel } | undefined;
|
|
555
|
+
/** Whether #pendingModelSwitch was queued by the live plan-role reconciler. */
|
|
556
|
+
#pendingPlanModelSwitch = false;
|
|
542
557
|
#planModeHasEntered = false;
|
|
543
558
|
#planReviewOverlay: PlanReviewOverlay | undefined;
|
|
544
559
|
#planReviewOverlayHandle: OverlayHandle | undefined;
|
|
@@ -941,6 +956,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
941
956
|
this.#observerRegistry.onChange(kind => {
|
|
942
957
|
this.#scheduleObserverUiSync(kind);
|
|
943
958
|
});
|
|
959
|
+
// Let the transient todo tool result light up pending todos executed by a
|
|
960
|
+
// live subagent, matching the sticky HUD's active set (#5873).
|
|
961
|
+
setActiveTodoDescriptionsProvider(() => this.#getActiveSubagentDescriptions());
|
|
944
962
|
|
|
945
963
|
// Load initial todos
|
|
946
964
|
await this.#loadTodoList();
|
|
@@ -1023,6 +1041,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1023
1041
|
this.#handleSessionAccentInputsChanged();
|
|
1024
1042
|
}),
|
|
1025
1043
|
);
|
|
1044
|
+
this.#eventBusUnsubscribers.push(
|
|
1045
|
+
onModelRolesChanged(() => {
|
|
1046
|
+
void this.#reapplyPlanModeModelOnRoleChange();
|
|
1047
|
+
}),
|
|
1048
|
+
);
|
|
1026
1049
|
this.#eventBusUnsubscribers.push(
|
|
1027
1050
|
this.session.subscribeCommandMetadataChanged(() => {
|
|
1028
1051
|
const retainedCommands = this.#pendingSlashCommands.filter(command => !command.name.startsWith("skill:"));
|
|
@@ -1880,15 +1903,29 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1880
1903
|
const isMatched = (todo: TodoItem): boolean =>
|
|
1881
1904
|
activeDescs.length > 0 && todoMatchesAnyDescription(todo.content, activeDescs);
|
|
1882
1905
|
|
|
1883
|
-
// Task subtree for a phase. Collapsed
|
|
1884
|
-
//
|
|
1885
|
-
//
|
|
1906
|
+
// Task subtree for a phase. Collapsed runs the shared walking-viewport
|
|
1907
|
+
// policy (completed/abandoned omitted, active work pulled to the head,
|
|
1908
|
+
// then following pending tasks) so the HUD and the transient tool result
|
|
1909
|
+
// can never disagree about the current work (#5873). Expanded lists all.
|
|
1886
1910
|
const renderTasks = (phase: TodoPhase): string[] => {
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1911
|
+
if (expanded) {
|
|
1912
|
+
return renderTreeList(
|
|
1913
|
+
{
|
|
1914
|
+
items: phase.tasks,
|
|
1915
|
+
expanded: true,
|
|
1916
|
+
renderItem: todo => this.#formatTodoLine(todo, "", isMatched(todo)),
|
|
1917
|
+
},
|
|
1918
|
+
theme,
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
const selection = selectCollapsedTodos(phase.tasks, isMatched, activeTaskCap);
|
|
1890
1922
|
return renderTreeList(
|
|
1891
|
-
{
|
|
1923
|
+
{
|
|
1924
|
+
items: selection.items,
|
|
1925
|
+
itemType: "task",
|
|
1926
|
+
trailingSummary: selection.summary,
|
|
1927
|
+
renderItem: todo => this.#formatTodoLine(todo, "", isMatched(todo)),
|
|
1928
|
+
},
|
|
1892
1929
|
theme,
|
|
1893
1930
|
);
|
|
1894
1931
|
};
|
|
@@ -2080,35 +2117,81 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2080
2117
|
if (!resolved.model) return;
|
|
2081
2118
|
|
|
2082
2119
|
const currentModel = this.session.model;
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2120
|
+
// Capture the pre-plan model so #exitPlanMode can restore it. Only the
|
|
2121
|
+
// entry path records this — a mid-planning role change (below) leaves the
|
|
2122
|
+
// active model on the plan role, so overwriting here would restore the old
|
|
2123
|
+
// plan model instead of the user's real pre-plan model.
|
|
2086
2124
|
this.#planModePreviousModelState = currentModel
|
|
2087
2125
|
? { model: currentModel, thinkingLevel: this.session.configuredThinkingLevel() }
|
|
2088
2126
|
: undefined;
|
|
2089
2127
|
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2128
|
+
await this.#applyPlanModelTransition(currentModel, resolved);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
/**
|
|
2132
|
+
* Re-resolve the `plan` role and move the active model onto it. Fires when
|
|
2133
|
+
* the plan role is reassigned while plan mode is active: the active model IS
|
|
2134
|
+
* the plan model there, so a settings-only change would otherwise leave the
|
|
2135
|
+
* current turn on the model plan mode was entered with (issue #5657). No-op
|
|
2136
|
+
* outside plan mode — role reassignment for an inactive role only touches
|
|
2137
|
+
* settings.
|
|
2138
|
+
*/
|
|
2139
|
+
async #reapplyPlanModeModelOnRoleChange(): Promise<void> {
|
|
2140
|
+
if (!this.planModeEnabled) return;
|
|
2141
|
+
const resolved = this.session.resolveRoleModelWithThinking("plan");
|
|
2142
|
+
if (!resolved.model) {
|
|
2143
|
+
this.#clearPendingPlanModelSwitch();
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
await this.#applyPlanModelTransition(this.session.model, resolved);
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
/**
|
|
2150
|
+
* Drop a stale deferred switch that was queued for a previous plan-role
|
|
2151
|
+
* assignment. Other deferred switches (such as restoring the pre-plan
|
|
2152
|
+
* model) remain intact.
|
|
2153
|
+
*/
|
|
2154
|
+
#clearPendingPlanModelSwitch(): void {
|
|
2155
|
+
if (!this.#pendingPlanModelSwitch) return;
|
|
2156
|
+
this.#pendingModelSwitch = undefined;
|
|
2157
|
+
this.#pendingPlanModelSwitch = false;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
/** Apply (or defer) the model/thinking change implied by the resolved plan role. */
|
|
2161
|
+
async #applyPlanModelTransition(currentModel: Model | undefined, resolved: ResolvedModelRoleValue): Promise<void> {
|
|
2162
|
+
const transition = resolvePlanModelTransition(currentModel, resolved, this.session.isStreaming);
|
|
2163
|
+
if (transition.kind !== "apply" || !transition.deferred) {
|
|
2164
|
+
this.#clearPendingPlanModelSwitch();
|
|
2165
|
+
}
|
|
2166
|
+
switch (transition.kind) {
|
|
2167
|
+
case "none":
|
|
2168
|
+
return;
|
|
2169
|
+
case "thinking":
|
|
2170
|
+
this.session.setThinkingLevel(transition.thinkingLevel);
|
|
2171
|
+
return;
|
|
2172
|
+
case "apply":
|
|
2173
|
+
if (transition.deferred) {
|
|
2174
|
+
this.#pendingModelSwitch = { model: transition.model, thinkingLevel: transition.thinkingLevel };
|
|
2175
|
+
this.#pendingPlanModelSwitch = true;
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
try {
|
|
2179
|
+
await this.session.setModelTemporary(transition.model, transition.thinkingLevel);
|
|
2180
|
+
} catch (error) {
|
|
2181
|
+
this.showWarning(
|
|
2182
|
+
`Failed to switch to plan model for plan mode: ${error instanceof Error ? error.message : String(error)}`,
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2093
2185
|
return;
|
|
2094
|
-
}
|
|
2095
|
-
try {
|
|
2096
|
-
await this.session.setModelTemporary(resolved.model, planThinkingLevel);
|
|
2097
|
-
} catch (error) {
|
|
2098
|
-
this.showWarning(
|
|
2099
|
-
`Failed to switch to plan model for plan mode: ${error instanceof Error ? error.message : String(error)}`,
|
|
2100
|
-
);
|
|
2101
|
-
}
|
|
2102
|
-
} else if (planThinkingLevel) {
|
|
2103
|
-
this.session.setThinkingLevel(planThinkingLevel);
|
|
2104
2186
|
}
|
|
2105
2187
|
}
|
|
2106
2188
|
|
|
2107
2189
|
/** Apply any deferred model switch after the current stream ends. */
|
|
2108
2190
|
async flushPendingModelSwitch(): Promise<void> {
|
|
2109
2191
|
const pending = this.#pendingModelSwitch;
|
|
2110
|
-
if (!pending) return;
|
|
2111
2192
|
this.#pendingModelSwitch = undefined;
|
|
2193
|
+
this.#pendingPlanModelSwitch = false;
|
|
2194
|
+
if (!pending) return;
|
|
2112
2195
|
try {
|
|
2113
2196
|
await this.session.setModelTemporary(pending.model, pending.thinkingLevel);
|
|
2114
2197
|
} catch (error) {
|
|
@@ -2120,19 +2203,23 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2120
2203
|
|
|
2121
2204
|
async #clearTransientModeState(): Promise<void> {
|
|
2122
2205
|
if (this.planModeEnabled || this.planModePaused) {
|
|
2123
|
-
if (this.#planModePreviousTools !== undefined) {
|
|
2124
|
-
await this.session.setActiveToolsByName(this.#planModePreviousTools);
|
|
2125
|
-
}
|
|
2126
|
-
this.session.setPlanProposalHandler?.(null);
|
|
2127
2206
|
this.session.setPlanModeState(undefined);
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2207
|
+
try {
|
|
2208
|
+
if (this.#planModePreviousTools !== undefined) {
|
|
2209
|
+
await this.session.setActiveToolsByName(this.#planModePreviousTools);
|
|
2210
|
+
}
|
|
2211
|
+
} finally {
|
|
2212
|
+
this.session.setPlanProposalHandler?.(null);
|
|
2213
|
+
this.planModeEnabled = false;
|
|
2214
|
+
this.planModePaused = false;
|
|
2215
|
+
this.planModePlanFilePath = undefined;
|
|
2216
|
+
this.#planModePreviousTools = undefined;
|
|
2217
|
+
this.#planModePreviousModelState = undefined;
|
|
2218
|
+
this.#pendingModelSwitch = undefined;
|
|
2219
|
+
this.#pendingPlanModelSwitch = false;
|
|
2220
|
+
this.#planModeHasEntered = false;
|
|
2221
|
+
this.#updatePlanModeStatus();
|
|
2222
|
+
}
|
|
2136
2223
|
}
|
|
2137
2224
|
|
|
2138
2225
|
if (this.goalModeEnabled || this.goalModePaused) {
|
|
@@ -2314,6 +2401,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2314
2401
|
this.session.setThinkingLevel(prev.thinkingLevel);
|
|
2315
2402
|
} else if (this.session.isStreaming) {
|
|
2316
2403
|
this.#pendingModelSwitch = { model: prev.model, thinkingLevel: prev.thinkingLevel };
|
|
2404
|
+
this.#pendingPlanModelSwitch = false;
|
|
2317
2405
|
} else {
|
|
2318
2406
|
await this.session.setModelTemporary(prev.model, prev.thinkingLevel);
|
|
2319
2407
|
}
|
|
@@ -2345,33 +2433,30 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2345
2433
|
return;
|
|
2346
2434
|
}
|
|
2347
2435
|
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
if (!options?.deferModelRestore) {
|
|
2354
|
-
await this.#restorePlanPreviousModel(this.#planModePreviousModelState);
|
|
2436
|
+
const planModeState = this.session.getPlanModeState();
|
|
2437
|
+
this.session.setPlanModeState(undefined);
|
|
2438
|
+
try {
|
|
2439
|
+
if (this.#planModePreviousTools !== undefined) {
|
|
2440
|
+
await this.session.setActiveToolsByName(this.#planModePreviousTools);
|
|
2355
2441
|
}
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
// session on the plan-role model after the user has exited plan mode
|
|
2360
|
-
// (issue #816). This runs even when deferModelRestore is set
|
|
2361
|
-
// (compact-approval path): otherwise the stale plan switch survives and
|
|
2362
|
-
// flushPendingModelSwitch() later clobbers the restored/execution model.
|
|
2363
|
-
// Only clear when the pending target matches the plan-role model — leave
|
|
2364
|
-
// any unrelated user-queued switch intact.
|
|
2365
|
-
const pending = this.#pendingModelSwitch;
|
|
2366
|
-
if (pending) {
|
|
2367
|
-
const planResolution = this.session.resolveRoleModelWithThinking("plan");
|
|
2368
|
-
if (planResolution.model && modelsAreEqual(pending.model, planResolution.model)) {
|
|
2369
|
-
this.#pendingModelSwitch = undefined;
|
|
2442
|
+
if (this.#planModePreviousModelState) {
|
|
2443
|
+
if (!options?.deferModelRestore) {
|
|
2444
|
+
await this.#restorePlanPreviousModel(this.#planModePreviousModelState);
|
|
2370
2445
|
}
|
|
2446
|
+
// If #applyPlanModeModel queued a deferred switch to the plan-role model
|
|
2447
|
+
// (because the session was streaming on entry), drop it now: we are
|
|
2448
|
+
// leaving plan mode, so flushing it on the next agent_end would land the
|
|
2449
|
+
// session on the plan-role model after the user has exited plan mode
|
|
2450
|
+
// (issue #816). This runs even when deferModelRestore is set
|
|
2451
|
+
// (compact-approval path): otherwise the stale plan switch survives and
|
|
2452
|
+
// flushPendingModelSwitch() later clobbers the restored/execution model.
|
|
2453
|
+
this.#clearPendingPlanModelSwitch();
|
|
2371
2454
|
}
|
|
2455
|
+
} catch (error) {
|
|
2456
|
+
this.session.setPlanModeState(planModeState);
|
|
2457
|
+
throw error;
|
|
2372
2458
|
}
|
|
2373
2459
|
this.session.setPlanProposalHandler?.(null);
|
|
2374
|
-
this.session.setPlanModeState(undefined);
|
|
2375
2460
|
this.planModeEnabled = false;
|
|
2376
2461
|
// Suppress cache-miss marker on the next turn: plan exit changes the system
|
|
2377
2462
|
// prompt, which predictably invalidates the cache.
|
|
@@ -2551,6 +2636,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2551
2636
|
maxHeight: "100%",
|
|
2552
2637
|
margin: 0,
|
|
2553
2638
|
fullscreen: true,
|
|
2639
|
+
mouseTracking: false,
|
|
2554
2640
|
});
|
|
2555
2641
|
this.ui.setFocus(overlay);
|
|
2556
2642
|
this.ui.requestRender();
|
|
@@ -2881,6 +2967,18 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2881
2967
|
planFilePath: options.planFilePath,
|
|
2882
2968
|
contextPreserved: options.preserveContext === true,
|
|
2883
2969
|
});
|
|
2970
|
+
// Close the review overlay only now — after the async title write and plan
|
|
2971
|
+
// prompt are prepared, immediately before the execution turn is queued. The
|
|
2972
|
+
// synthetic prompt below blocks in `session.prompt` for the whole run, so
|
|
2973
|
+
// hiding here (rather than after #approvePlan returns) keeps the operator off
|
|
2974
|
+
// the stale plan-review screen (issue #5688) while #5319's stale-buffer guard
|
|
2975
|
+
// stays intact. Deferring the hide past the awaited `setSessionName` also
|
|
2976
|
+
// prevents restored editor focus from letting operator keystrokes submit a
|
|
2977
|
+
// normal turn ahead of the approved execution turn (PR #5689 review).
|
|
2978
|
+
// `#hidePlanReview` is idempotent, so the caller's trailing `closePlanReview()`
|
|
2979
|
+
// — and the cancelled/error early returns above — stay safe no-ops.
|
|
2980
|
+
this.#hidePlanReview();
|
|
2981
|
+
this.ui.requestRender();
|
|
2884
2982
|
// A user turn queued during compaction was already fired by
|
|
2885
2983
|
// `flushCompactionQueue` before we returned from `handleCompactCommand`; the
|
|
2886
2984
|
// old abort-then-prompt path would have discarded that operator turn AND
|
|
@@ -4310,11 +4408,20 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4310
4408
|
this.#selectorController.showSessionSelector();
|
|
4311
4409
|
}
|
|
4312
4410
|
|
|
4313
|
-
handleResumeSession(sessionPath: string): Promise<void> {
|
|
4411
|
+
async handleResumeSession(sessionPath: string): Promise<void> {
|
|
4412
|
+
// Flush pending settings writes *before* disposing controllers or resetting
|
|
4413
|
+
// observers: a save failure must leave the session, process project dir,
|
|
4414
|
+
// and Settings in the source scope with all UI intact.
|
|
4415
|
+
try {
|
|
4416
|
+
await this.settings.flush();
|
|
4417
|
+
} catch (err) {
|
|
4418
|
+
this.showError(`Failed to save pending settings: ${err instanceof Error ? err.message : String(err)}`);
|
|
4419
|
+
return;
|
|
4420
|
+
}
|
|
4314
4421
|
this.#btwController.dispose();
|
|
4315
4422
|
this.#omfgController.dispose();
|
|
4316
4423
|
this.resetObserverRegistry();
|
|
4317
|
-
|
|
4424
|
+
await this.#selectorController.handleResumeSession(sessionPath, { settingsFlushed: true });
|
|
4318
4425
|
}
|
|
4319
4426
|
|
|
4320
4427
|
handleSessionDeleteCommand(): Promise<void> {
|