@oh-my-pi/pi-coding-agent 17.0.1 → 17.0.2
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 +92 -20
- package/dist/cli.js +3485 -3448
- 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 +32 -7
- 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/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 +40 -0
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/extensibility/utils.d.ts +2 -2
- package/dist/types/mcp/transports/stdio.d.ts +13 -1
- package/dist/types/modes/components/advisor-config.d.ts +8 -1
- 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/sdk.d.ts +13 -2
- package/dist/types/session/agent-session.d.ts +34 -10
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-manager.d.ts +14 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
- package/dist/types/task/label.d.ts +1 -1
- 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/image-gen.d.ts +2 -1
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/xdev.d.ts +11 -2
- 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.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 +42 -6
- 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/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 +96 -1
- 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 +1 -1
- package/src/main.ts +11 -8
- package/src/mcp/manager.ts +9 -3
- package/src/mcp/transports/stdio.ts +103 -23
- package/src/modes/components/advisor-config.ts +65 -3
- package/src/modes/components/ask-dialog.ts +1 -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 +139 -54
- 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/registry/agent-lifecycle.ts +133 -18
- package/src/sdk.ts +221 -42
- package/src/session/agent-session.ts +1285 -348
- package/src/session/session-history-format.ts +20 -5
- package/src/session/session-manager.ts +48 -0
- package/src/slash-commands/builtin-registry.ts +7 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
- package/src/task/executor.ts +1 -1
- package/src/task/label.ts +2 -0
- package/src/telemetry-export.ts +453 -97
- 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/gh.ts +169 -2
- package/src/tools/image-gen.ts +69 -7
- package/src/tools/index.ts +7 -5
- package/src/tools/read.ts +48 -3
- package/src/tools/write.ts +22 -4
- package/src/tools/xdev.ts +14 -3
- package/src/utils/title-generator.ts +15 -4
- package/src/web/search/providers/kimi.ts +18 -12
- package/src/web/search/types.ts +6 -1
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
assistantUsageIsBilled,
|
|
37
37
|
splitAssistantMessageToolTimeline,
|
|
38
38
|
} from "../utils/transcript-render-helpers";
|
|
39
|
+
import { isWarpCliAgentProtocolActive } from "../warp-events";
|
|
39
40
|
import { StreamingRevealController } from "./streaming-reveal";
|
|
40
41
|
import { streamingStringKeysForTool, ToolArgsRevealController } from "./tool-args-reveal";
|
|
41
42
|
|
|
@@ -1564,6 +1565,10 @@ export class EventController {
|
|
|
1564
1565
|
const notify = settings.get("completion.notify");
|
|
1565
1566
|
if (notify === "off") return;
|
|
1566
1567
|
|
|
1568
|
+
// Warp structured OSC 777 already drives native completion UX when the
|
|
1569
|
+
// protocol is negotiated — avoid a second legacy desktop/OSC-9 toast.
|
|
1570
|
+
if (isWarpCliAgentProtocolActive()) return;
|
|
1571
|
+
|
|
1567
1572
|
// Skip when the turn was aborted (e.g. ask cancelled with Ctrl+C) or
|
|
1568
1573
|
// errored — those are not "Task complete" events. Mirrors the gate
|
|
1569
1574
|
// already used by #currentContextTokens, #handleMessageEnd, and the
|
|
@@ -21,7 +21,6 @@ import type {
|
|
|
21
21
|
TerminalInputHandler,
|
|
22
22
|
} from "../../extensibility/extensions";
|
|
23
23
|
import { getSessionSlashCommands } from "../../extensibility/extensions/get-commands-handler";
|
|
24
|
-
import { createExtensionModelQuery } from "../../extensibility/extensions/model-api";
|
|
25
24
|
import { AskDialogComponent, boundPromptTitle } from "../../modes/components/ask-dialog";
|
|
26
25
|
import { HookEditorComponent } from "../../modes/components/hook-editor";
|
|
27
26
|
import { HookInputComponent } from "../../modes/components/hook-input";
|
|
@@ -494,32 +493,15 @@ export class ExtensionUiController {
|
|
|
494
493
|
if (!uiContext) {
|
|
495
494
|
return;
|
|
496
495
|
}
|
|
497
|
-
|
|
496
|
+
const runner = this.ctx.session.extensionRunner;
|
|
497
|
+
for (const registeredTool of runner?.getAllRegisteredTools() ?? []) {
|
|
498
498
|
if (registeredTool.definition.onSession) {
|
|
499
499
|
try {
|
|
500
500
|
await registeredTool.definition.onSession(event, {
|
|
501
|
+
...runner!.createContext(),
|
|
501
502
|
ui: uiContext,
|
|
502
|
-
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
503
|
-
compact: instructionsOrOptions => this.#compactSession(instructionsOrOptions),
|
|
504
503
|
hasUI: true,
|
|
505
|
-
|
|
506
|
-
sessionManager: this.ctx.session.sessionManager,
|
|
507
|
-
modelRegistry: this.ctx.session.modelRegistry,
|
|
508
|
-
model: this.ctx.session.model,
|
|
509
|
-
models: createExtensionModelQuery(
|
|
510
|
-
this.ctx.session.modelRegistry,
|
|
511
|
-
this.ctx.session.settings,
|
|
512
|
-
() => this.ctx.session.model,
|
|
513
|
-
),
|
|
514
|
-
isIdle: () => !this.ctx.session.isStreaming,
|
|
515
|
-
hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
|
|
516
|
-
abort: () => {
|
|
517
|
-
this.ctx.session.abort({ reason: USER_INTERRUPT_LABEL });
|
|
518
|
-
},
|
|
519
|
-
shutdown: () => {
|
|
520
|
-
// Signal shutdown request
|
|
521
|
-
},
|
|
522
|
-
getSystemPrompt: () => this.ctx.session.systemPrompt,
|
|
504
|
+
compact: instructionsOrOptions => this.#compactSession(instructionsOrOptions),
|
|
523
505
|
});
|
|
524
506
|
} catch (err) {
|
|
525
507
|
this.showToolError(registeredTool.definition.name, err instanceof Error ? err.message : String(err));
|
|
@@ -35,7 +35,6 @@ import { EnhancedPasteController } from "../../utils/enhanced-paste";
|
|
|
35
35
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
36
36
|
import { ensureSupportedImageInput, ImageInputTooLargeError, loadImageInput } from "../../utils/image-loading";
|
|
37
37
|
import { resizeImage } from "../../utils/image-resize";
|
|
38
|
-
import { generateSessionTitle } from "../../utils/title-generator";
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
40
|
* Slash commands that may carry secrets in their arguments should never be
|
|
@@ -392,7 +391,16 @@ export class InputController {
|
|
|
392
391
|
this.ctx.editor.onClear = () => this.handleCtrlC();
|
|
393
392
|
this.ctx.editor.setActionKeys("app.exit", this.ctx.keybindings.getKeys("app.exit"));
|
|
394
393
|
this.ctx.editor.setActionKeys("app.display.reset", this.ctx.keybindings.getKeys("app.display.reset"));
|
|
395
|
-
this.ctx.editor.onDisplayReset = () =>
|
|
394
|
+
this.ctx.editor.onDisplayReset = () => {
|
|
395
|
+
// Explicit user gesture (Ctrl+L): re-query the terminal background once
|
|
396
|
+
// so a mid-session light/dark switch is picked up even on terminals
|
|
397
|
+
// without an end-to-end Mode 2031 notification path (#5352). The
|
|
398
|
+
// appearance callback re-evaluates the auto theme; the repaint below
|
|
399
|
+
// then renders the resolved palette. Bounded to one OSC 11 probe per
|
|
400
|
+
// gesture — no timers, no periodic polling.
|
|
401
|
+
this.ctx.ui.terminal.refreshAppearance?.();
|
|
402
|
+
this.ctx.ui.resetDisplay();
|
|
403
|
+
};
|
|
396
404
|
this.ctx.editor.onExit = () => this.handleCtrlD();
|
|
397
405
|
this.ctx.editor.setActionKeys("app.suspend", this.ctx.keybindings.getKeys("app.suspend"));
|
|
398
406
|
this.ctx.editor.onSuspend = () => this.handleCtrlZ();
|
|
@@ -821,16 +829,8 @@ export class InputController {
|
|
|
821
829
|
// chance, so titling defers past "hi" instead of latching onto it.
|
|
822
830
|
if (!this.ctx.sessionManager.getSessionName() && !$env.PI_NO_TITLE && !isLowSignalTitleInput(text)) {
|
|
823
831
|
this.#showTinyTitleDownloadProgress(this.ctx.settings.get("providers.tinyModel"));
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
text,
|
|
827
|
-
registry,
|
|
828
|
-
this.ctx.settings,
|
|
829
|
-
this.ctx.session.sessionId,
|
|
830
|
-
this.ctx.session.model,
|
|
831
|
-
provider => this.ctx.session.agent.metadataForProvider(provider),
|
|
832
|
-
this.ctx.session.titleSystemPrompt,
|
|
833
|
-
)
|
|
832
|
+
this.ctx.session
|
|
833
|
+
.generateTitle(text)
|
|
834
834
|
.then(async title => {
|
|
835
835
|
// Re-check: a concurrent attempt for an earlier message may have
|
|
836
836
|
// already named the session. Don't clobber it. Terminal title and
|
|
@@ -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> {
|