@oh-my-pi/pi-coding-agent 16.2.2 → 16.2.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 +49 -0
- package/dist/cli.js +3624 -3568
- package/dist/types/advisor/__tests__/config.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +8 -4
- package/dist/types/advisor/config.d.ts +88 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/transcript-recorder.d.ts +13 -2
- package/dist/types/advisor/watchdog.d.ts +20 -0
- package/dist/types/collab/guest.d.ts +29 -0
- package/dist/types/config/settings-schema.d.ts +81 -0
- package/dist/types/debug/log-viewer.d.ts +1 -0
- package/dist/types/debug/raw-sse.d.ts +1 -0
- package/dist/types/edit/hashline/diff.d.ts +0 -11
- package/dist/types/extensibility/tool-event-input.d.ts +7 -0
- package/dist/types/extensibility/utils.d.ts +12 -0
- package/dist/types/mcp/transports/index.d.ts +1 -0
- package/dist/types/mcp/transports/sse.d.ts +20 -0
- package/dist/types/modes/components/advisor-config.d.ts +59 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +9 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/component.d.ts +30 -1
- package/dist/types/modes/components/status-line/types.d.ts +13 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -4
- package/dist/types/modes/skill-command.d.ts +32 -0
- package/dist/types/modes/types.d.ts +7 -2
- package/dist/types/session/agent-session.d.ts +58 -10
- package/dist/types/session/indexed-session-storage.d.ts +7 -1
- package/dist/types/session/messages.d.ts +26 -0
- package/dist/types/session/messages.test.d.ts +1 -0
- package/dist/types/session/session-entries.d.ts +31 -3
- package/dist/types/session/session-history-format.d.ts +6 -0
- package/dist/types/session/session-loader.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +6 -4
- package/dist/types/session/session-storage.d.ts +11 -0
- package/dist/types/session/session-title-slot.d.ts +19 -0
- package/dist/types/ssh/connection-manager.d.ts +47 -0
- package/dist/types/ssh/utils.d.ts +16 -0
- package/dist/types/task/executor.d.ts +3 -16
- package/dist/types/task/render.d.ts +0 -5
- package/dist/types/task/renderer.d.ts +13 -0
- package/dist/types/task/types.d.ts +16 -0
- package/dist/types/task/yield-assembly.d.ts +28 -0
- package/dist/types/tiny/text.d.ts +8 -0
- package/dist/types/tools/render-utils.d.ts +2 -0
- package/dist/types/tools/review.d.ts +6 -4
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +6 -0
- package/dist/types/tools/yield.d.ts +8 -3
- package/dist/types/utils/thinking-display.d.ts +4 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +242 -10
- package/src/advisor/__tests__/config.test.ts +173 -0
- package/src/advisor/advise-tool.ts +11 -6
- package/src/advisor/config.ts +256 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +12 -2
- package/src/advisor/transcript-recorder.ts +25 -2
- package/src/advisor/watchdog.ts +57 -31
- package/src/autoresearch/index.ts +7 -2
- package/src/cli/gc-cli.ts +17 -10
- package/src/collab/guest.ts +43 -7
- package/src/config/model-registry.ts +80 -18
- package/src/config/settings-schema.ts +77 -0
- package/src/debug/index.ts +32 -7
- package/src/debug/log-viewer.ts +111 -53
- package/src/debug/raw-sse.ts +68 -48
- package/src/discovery/codex.ts +13 -5
- package/src/edit/hashline/diff.ts +57 -4
- package/src/eval/js/shared/local-module-loader.ts +23 -1
- package/src/export/html/template.js +13 -7
- package/src/extensibility/extensions/loader.ts +5 -3
- package/src/extensibility/extensions/wrapper.ts +9 -3
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/hooks/tool-wrapper.ts +13 -4
- package/src/extensibility/plugins/manager.ts +2 -1
- package/src/extensibility/tool-event-input.ts +23 -0
- package/src/extensibility/utils.ts +74 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/mcp/client.ts +3 -1
- package/src/mcp/manager.ts +12 -5
- package/src/mcp/transports/index.ts +1 -0
- package/src/mcp/transports/sse.ts +377 -0
- package/src/memories/index.ts +15 -6
- package/src/modes/components/advisor-config.ts +555 -0
- package/src/modes/components/advisor-message.ts +9 -2
- package/src/modes/components/agent-hub.ts +9 -4
- package/src/modes/components/index.ts +2 -0
- package/src/modes/components/model-selector.ts +79 -48
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/component.ts +144 -5
- package/src/modes/components/status-line/segments.ts +46 -21
- package/src/modes/components/status-line/types.ts +13 -1
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/controllers/command-controller.ts +23 -2
- package/src/modes/controllers/event-controller.ts +106 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +61 -61
- package/src/modes/controllers/selector-controller.ts +100 -9
- package/src/modes/interactive-mode.ts +65 -5
- package/src/modes/skill-command.ts +116 -0
- package/src/modes/types.ts +7 -2
- package/src/modes/utils/ui-helpers.ts +41 -23
- package/src/prompts/agents/reviewer.md +11 -10
- package/src/prompts/review-custom-request.md +1 -2
- package/src/prompts/review-request.md +1 -2
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/subagent-system-prompt.md +8 -5
- package/src/prompts/system/subagent-yield-reminder.md +6 -5
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/irc.md +2 -2
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +28 -24
- package/src/session/agent-session.ts +867 -428
- package/src/session/indexed-session-storage.ts +86 -13
- package/src/session/messages.test.ts +125 -0
- package/src/session/messages.ts +172 -9
- package/src/session/redis-session-storage.ts +49 -2
- package/src/session/session-entries.ts +39 -2
- package/src/session/session-history-format.ts +29 -2
- package/src/session/session-listing.ts +54 -24
- package/src/session/session-loader.ts +66 -3
- package/src/session/session-manager.ts +113 -19
- package/src/session/session-persistence.ts +95 -1
- package/src/session/session-storage.ts +36 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/sql-session-storage.ts +71 -11
- package/src/slash-commands/builtin-registry.ts +16 -3
- package/src/ssh/__tests__/connection-manager-args.test.ts +123 -1
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +55 -18
- package/src/ssh/connection-manager.ts +139 -12
- package/src/ssh/file-transfer.ts +23 -18
- package/src/ssh/ssh-executor.ts +2 -13
- package/src/ssh/utils.ts +19 -0
- package/src/task/executor.ts +21 -23
- package/src/task/render.ts +162 -20
- package/src/task/renderer.ts +14 -0
- package/src/task/types.ts +17 -0
- package/src/task/yield-assembly.ts +207 -0
- package/src/tiny/text.ts +23 -0
- package/src/tools/ask.ts +55 -4
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/renderers.ts +8 -2
- package/src/tools/review.ts +17 -7
- package/src/tools/ssh.ts +8 -4
- package/src/tools/todo.ts +17 -1
- package/src/tools/yield.ts +140 -31
- package/src/utils/thinking-display.ts +15 -0
|
@@ -360,6 +360,27 @@ export class CommandController {
|
|
|
360
360
|
]);
|
|
361
361
|
return;
|
|
362
362
|
}
|
|
363
|
+
if (stats.advisors.length > 1) {
|
|
364
|
+
let info = `${theme.bold("Advisor Status")} (${stats.advisors.length} advisors)\n`;
|
|
365
|
+
for (const a of stats.advisors) {
|
|
366
|
+
const ctx =
|
|
367
|
+
a.contextWindow > 0
|
|
368
|
+
? `${a.contextTokens.toLocaleString()} / ${a.contextWindow.toLocaleString()} (${Math.round((a.contextTokens / a.contextWindow) * 100)}%)`
|
|
369
|
+
: `${a.contextTokens.toLocaleString()}`;
|
|
370
|
+
info += `\n${theme.bold(a.name)}\n`;
|
|
371
|
+
info += `${theme.fg("dim", "Model:")} ${a.model.provider}/${a.model.id}\n`;
|
|
372
|
+
info += `${theme.fg("dim", "Context:")} ${ctx}\n`;
|
|
373
|
+
info += `${theme.fg("dim", "Messages:")} ${a.messages.total.toLocaleString()}\n`;
|
|
374
|
+
info += `${theme.fg("dim", "Spend:")} ${a.tokens.input.toLocaleString()} in / ${a.tokens.output.toLocaleString()} out`;
|
|
375
|
+
if (a.cost > 0) info += `, $${a.cost.toFixed(4)}`;
|
|
376
|
+
info += "\n";
|
|
377
|
+
}
|
|
378
|
+
info += `\n${theme.bold("Totals")}\n`;
|
|
379
|
+
info += `${theme.fg("dim", "Tokens:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
380
|
+
if (stats.cost > 0) info += `${theme.fg("dim", "Cost:")} $${stats.cost.toFixed(4)}\n`;
|
|
381
|
+
this.ctx.present([new Spacer(1), new Text(info, 1, 0)]);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
363
384
|
const model = stats.model!;
|
|
364
385
|
let info = `${theme.bold("Advisor Status")}\n\n`;
|
|
365
386
|
info += `${theme.bold("Provider")}\n`;
|
|
@@ -846,7 +867,7 @@ export class CommandController {
|
|
|
846
867
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
847
868
|
|
|
848
869
|
this.ctx.statusLine.invalidate();
|
|
849
|
-
this.ctx.statusLine.
|
|
870
|
+
this.ctx.statusLine.resetActiveTime();
|
|
850
871
|
this.ctx.updateEditorTopBorder();
|
|
851
872
|
this.ctx.updateEditorBorderColor();
|
|
852
873
|
this.ctx.chatContainer.clear();
|
|
@@ -1018,7 +1039,7 @@ export class CommandController {
|
|
|
1018
1039
|
this.ctx.streamingMessage = undefined;
|
|
1019
1040
|
this.ctx.pendingTools.clear();
|
|
1020
1041
|
this.ctx.statusLine.invalidate();
|
|
1021
|
-
this.ctx.statusLine.
|
|
1042
|
+
this.ctx.statusLine.resetActiveTime();
|
|
1022
1043
|
this.ctx.updateEditorTopBorder();
|
|
1023
1044
|
this.ctx.updateEditorBorderColor();
|
|
1024
1045
|
await this.ctx.reloadTodos();
|
|
@@ -2,6 +2,7 @@ import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
|
2
2
|
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
3
3
|
import { getStreamingPartialJson } from "@oh-my-pi/pi-ai/utils/block-symbols";
|
|
4
4
|
import { type Component, Loader, TERMINAL } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
5
6
|
import { INTENT_FIELD } from "@oh-my-pi/pi-wire";
|
|
6
7
|
import { extractTextContent } from "../../commit/utils";
|
|
7
8
|
import { settings } from "../../config/settings";
|
|
@@ -20,9 +21,12 @@ import { createUsageRowBlock } from "../../modes/components/usage-row";
|
|
|
20
21
|
import { getSymbolTheme, theme } from "../../modes/theme/theme";
|
|
21
22
|
import type { InteractiveModeContext, TodoPhase } from "../../modes/types";
|
|
22
23
|
import type { PlanApprovalDetails } from "../../plan-mode/approved-plan";
|
|
24
|
+
import idleRecapPrompt from "../../prompts/system/recap-user.md" with { type: "text" };
|
|
23
25
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
24
26
|
import { isSilentAbort, readQueueChipText, resolveAbortLabel } from "../../session/messages";
|
|
27
|
+
import { previewLine, TRUNCATE_LENGTHS } from "../../tools/render-utils";
|
|
25
28
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
29
|
+
import { nextActionableTask } from "../../tools/todo";
|
|
26
30
|
import { vocalizer } from "../../tts/vocalizer";
|
|
27
31
|
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
28
32
|
import { interruptHint } from "../shared";
|
|
@@ -43,6 +47,8 @@ const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
|
43
47
|
* oldest live-region card retires as soon as a new one would exceed the cap.
|
|
44
48
|
*/
|
|
45
49
|
const MAX_LIVE_IRC_CARDS = 4;
|
|
50
|
+
const IDLE_RECAP_MIN_SECONDS = 1;
|
|
51
|
+
const IDLE_RECAP_MAX_SECONDS = 3600;
|
|
46
52
|
|
|
47
53
|
type AgentSessionEventHandlers = {
|
|
48
54
|
[E in AgentSessionEventKind]: (event: Extract<AgentSessionEvent, { type: E }>) => Promise<void>;
|
|
@@ -69,6 +75,10 @@ export class EventController {
|
|
|
69
75
|
// #handleMessageEnd / #handleAgentStart).
|
|
70
76
|
#pinnedErrorComponent: AssistantMessageComponent | undefined = undefined;
|
|
71
77
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
78
|
+
#idleRecapTimer?: NodeJS.Timeout;
|
|
79
|
+
// In-flight ephemeral recap turn; aborted by #cancelIdleRecap when any
|
|
80
|
+
// activity (new turn, compaction, editor draft) supersedes the idle recap.
|
|
81
|
+
#idleRecapAbort?: AbortController;
|
|
72
82
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
73
83
|
// Insertion-ordered IRC cards not yet retired; values are the transcript
|
|
74
84
|
// components each card contributed (see #retireIrcCard for the guard).
|
|
@@ -157,6 +167,7 @@ export class EventController {
|
|
|
157
167
|
this.#streamingReveal.stop();
|
|
158
168
|
this.#toolArgsReveal.stop();
|
|
159
169
|
this.#cancelIdleCompaction();
|
|
170
|
+
this.#cancelIdleRecap();
|
|
160
171
|
this.#setTerminalProgress(false);
|
|
161
172
|
for (const timer of this.#ircExpiryTimers.values()) {
|
|
162
173
|
clearTimeout(timer);
|
|
@@ -248,6 +259,7 @@ export class EventController {
|
|
|
248
259
|
this.#lastAssistantComponent = undefined;
|
|
249
260
|
this.#pinnedErrorComponent = undefined;
|
|
250
261
|
this.#cancelIdleCompaction();
|
|
262
|
+
this.#cancelIdleRecap();
|
|
251
263
|
for (const timer of this.#ircExpiryTimers.values()) {
|
|
252
264
|
clearTimeout(timer);
|
|
253
265
|
}
|
|
@@ -302,6 +314,8 @@ export class EventController {
|
|
|
302
314
|
this.ctx.statusContainer.clear();
|
|
303
315
|
}
|
|
304
316
|
this.#cancelIdleCompaction();
|
|
317
|
+
this.#cancelIdleRecap();
|
|
318
|
+
this.ctx.statusLine.markActivityStart();
|
|
305
319
|
this.#setTerminalProgress(true);
|
|
306
320
|
this.ctx.ensureLoadingAnimation();
|
|
307
321
|
this.ctx.ui.requestRender();
|
|
@@ -554,6 +568,11 @@ export class EventController {
|
|
|
554
568
|
this.#ensureWorkingLoaderWhileStreaming();
|
|
555
569
|
this.#vocalizeDelta(event);
|
|
556
570
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
571
|
+
const unlockedThinkingVisibility = this.ctx.noteDisplayableThinkingContent(event.message);
|
|
572
|
+
if (unlockedThinkingVisibility) {
|
|
573
|
+
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.effectiveHideThinkingBlock);
|
|
574
|
+
this.#streamingReveal.resyncVisibility();
|
|
575
|
+
}
|
|
557
576
|
this.ctx.streamingMessage = event.message;
|
|
558
577
|
this.#streamingReveal.setTarget(this.ctx.streamingMessage);
|
|
559
578
|
|
|
@@ -681,6 +700,12 @@ export class EventController {
|
|
|
681
700
|
|
|
682
701
|
async #handleMessageEnd(event: Extract<AgentSessionEvent, { type: "message_end" }>): Promise<void> {
|
|
683
702
|
if (event.message.role === "user") return;
|
|
703
|
+
const unlockedThinkingVisibility =
|
|
704
|
+
event.message.role === "assistant" && this.ctx.noteDisplayableThinkingContent(event.message);
|
|
705
|
+
if (unlockedThinkingVisibility && this.ctx.streamingComponent) {
|
|
706
|
+
this.ctx.streamingComponent.setHideThinkingBlock(this.ctx.effectiveHideThinkingBlock);
|
|
707
|
+
this.#streamingReveal.resyncVisibility();
|
|
708
|
+
}
|
|
684
709
|
if (event.message.role === "assistant" && settings.get("speech.enabled")) {
|
|
685
710
|
if (event.message.stopReason === "aborted") {
|
|
686
711
|
// Esc / Ctrl+C / interrupt: stop speaking now and drop the trailing partial.
|
|
@@ -967,6 +992,7 @@ export class EventController {
|
|
|
967
992
|
|
|
968
993
|
async #finishAgentEnd(): Promise<void> {
|
|
969
994
|
this.#setTerminalProgress(false);
|
|
995
|
+
this.ctx.statusLine.markActivityEnd();
|
|
970
996
|
this.#streamingReveal.stop();
|
|
971
997
|
this.#toolArgsReveal.flushAll();
|
|
972
998
|
if (this.ctx.loadingAnimation) {
|
|
@@ -1008,6 +1034,7 @@ export class EventController {
|
|
|
1008
1034
|
this.#lastAssistantComponent = undefined;
|
|
1009
1035
|
this.ctx.ui.requestRender();
|
|
1010
1036
|
this.#scheduleIdleCompaction();
|
|
1037
|
+
this.#scheduleIdleRecap();
|
|
1011
1038
|
this.sendCompletionNotification();
|
|
1012
1039
|
}
|
|
1013
1040
|
|
|
@@ -1053,6 +1080,7 @@ export class EventController {
|
|
|
1053
1080
|
event: Extract<AgentSessionEvent, { type: "auto_compaction_start" }>,
|
|
1054
1081
|
): Promise<void> {
|
|
1055
1082
|
this.#cancelIdleCompaction();
|
|
1083
|
+
this.#cancelIdleRecap();
|
|
1056
1084
|
this.#setTerminalProgress(true);
|
|
1057
1085
|
this.#stopWorkingLoader();
|
|
1058
1086
|
this.ctx.statusContainer.clear();
|
|
@@ -1085,6 +1113,7 @@ export class EventController {
|
|
|
1085
1113
|
|
|
1086
1114
|
async #handleAutoCompactionEnd(event: Extract<AgentSessionEvent, { type: "auto_compaction_end" }>): Promise<void> {
|
|
1087
1115
|
this.#cancelIdleCompaction();
|
|
1116
|
+
this.#cancelIdleRecap();
|
|
1088
1117
|
this.#setTerminalProgress(false);
|
|
1089
1118
|
if (this.ctx.autoCompactionLoader) {
|
|
1090
1119
|
this.ctx.autoCompactionLoader.stop();
|
|
@@ -1237,6 +1266,17 @@ export class EventController {
|
|
|
1237
1266
|
}
|
|
1238
1267
|
}
|
|
1239
1268
|
|
|
1269
|
+
#cancelIdleRecap(): void {
|
|
1270
|
+
if (this.#idleRecapTimer) {
|
|
1271
|
+
clearTimeout(this.#idleRecapTimer);
|
|
1272
|
+
this.#idleRecapTimer = undefined;
|
|
1273
|
+
}
|
|
1274
|
+
if (this.#idleRecapAbort) {
|
|
1275
|
+
this.#idleRecapAbort.abort();
|
|
1276
|
+
this.#idleRecapAbort = undefined;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1240
1280
|
#scheduleIdleCompaction(): void {
|
|
1241
1281
|
this.#cancelIdleCompaction();
|
|
1242
1282
|
// Don't schedule idle work while context maintenance is already running; the
|
|
@@ -1267,6 +1307,72 @@ export class EventController {
|
|
|
1267
1307
|
this.#idleCompactionTimer.unref?.();
|
|
1268
1308
|
}
|
|
1269
1309
|
|
|
1310
|
+
#scheduleIdleRecap(): void {
|
|
1311
|
+
this.#cancelIdleRecap();
|
|
1312
|
+
if (this.ctx.viewSession.isCompacting) return;
|
|
1313
|
+
|
|
1314
|
+
const recapSettings = settings.getGroup("recap");
|
|
1315
|
+
if (!recapSettings.enabled) return;
|
|
1316
|
+
if (this.ctx.editor.getText().trim()) return;
|
|
1317
|
+
|
|
1318
|
+
const timeoutMs =
|
|
1319
|
+
Math.max(IDLE_RECAP_MIN_SECONDS, Math.min(IDLE_RECAP_MAX_SECONDS, recapSettings.idleSeconds)) * 1000;
|
|
1320
|
+
this.#idleRecapTimer = setTimeout(() => {
|
|
1321
|
+
this.#idleRecapTimer = undefined;
|
|
1322
|
+
void this.#runIdleRecap();
|
|
1323
|
+
}, timeoutMs);
|
|
1324
|
+
this.#idleRecapTimer.unref?.();
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Generate the idle recap with an ephemeral side-channel turn over the
|
|
1329
|
+
* current conversation (same pipeline as `/btw`) and surface it as a status
|
|
1330
|
+
* line. Live goal/title and the active todo task are passed as anchoring
|
|
1331
|
+
* hints because the snapshot only carries conversation history, not the
|
|
1332
|
+
* controller's todo/goal state. The request is abortable: any activity
|
|
1333
|
+
* cancels it via #cancelIdleRecap, and idle conditions are re-checked after
|
|
1334
|
+
* the reply lands so a stale recap never paints over fresh work.
|
|
1335
|
+
*/
|
|
1336
|
+
async #runIdleRecap(): Promise<void> {
|
|
1337
|
+
if (!this.#idleConditionsHold()) return;
|
|
1338
|
+
if (!this.ctx.viewSession.model) return;
|
|
1339
|
+
if (this.ctx.viewSession.messages.length === 0) return;
|
|
1340
|
+
|
|
1341
|
+
const promptText = prompt.render(idleRecapPrompt, {
|
|
1342
|
+
goal: this.#idleRecapGoalText() ?? "",
|
|
1343
|
+
task: nextActionableTask(this.ctx.todoPhases)?.content ?? "",
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
const abort = new AbortController();
|
|
1347
|
+
this.#idleRecapAbort = abort;
|
|
1348
|
+
try {
|
|
1349
|
+
const { replyText } = await this.ctx.viewSession.runEphemeralTurn({ promptText, signal: abort.signal });
|
|
1350
|
+
if (this.#idleRecapAbort !== abort || abort.signal.aborted || !this.#idleConditionsHold()) return;
|
|
1351
|
+
const recap = previewLine(replyText, TRUNCATE_LENGTHS.RECAP);
|
|
1352
|
+
if (!recap) return;
|
|
1353
|
+
this.ctx.showStatus(theme.fg("dim", theme.italic(`※ recap: ${recap}`)), { dim: false });
|
|
1354
|
+
} catch (error) {
|
|
1355
|
+
if (!abort.signal.aborted) logger.debug("Idle recap turn failed", { error: String(error) });
|
|
1356
|
+
} finally {
|
|
1357
|
+
if (this.#idleRecapAbort === abort) this.#idleRecapAbort = undefined;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/** Idle gate shared by the recap timer fire and its post-reply re-check. */
|
|
1362
|
+
#idleConditionsHold(): boolean {
|
|
1363
|
+
if (this.ctx.viewSession.isStreaming) return false;
|
|
1364
|
+
if (this.ctx.viewSession.isCompacting) return false;
|
|
1365
|
+
if (this.ctx.editor.getText().trim()) return false;
|
|
1366
|
+
return true;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
#idleRecapGoalText(): string | undefined {
|
|
1370
|
+
const goal = this.ctx.viewSession.getGoalModeState?.()?.goal.objective.trim();
|
|
1371
|
+
if (goal) return goal;
|
|
1372
|
+
const title = this.ctx.sessionManager.getSessionName()?.trim();
|
|
1373
|
+
return title || undefined;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1270
1376
|
#currentContextTokens(): number {
|
|
1271
1377
|
return this.ctx.viewSession.getContextUsage()?.tokens ?? 0;
|
|
1272
1378
|
}
|
|
@@ -170,7 +170,7 @@ export class ExtensionUiController {
|
|
|
170
170
|
|
|
171
171
|
// Reset and update status line
|
|
172
172
|
this.ctx.statusLine.invalidate();
|
|
173
|
-
this.ctx.statusLine.
|
|
173
|
+
this.ctx.statusLine.resetActiveTime();
|
|
174
174
|
this.ctx.updateEditorTopBorder();
|
|
175
175
|
this.ctx.ui.requestRender();
|
|
176
176
|
|
|
@@ -13,9 +13,10 @@ import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-
|
|
|
13
13
|
import { expandEmoticons } from "../../modes/emoji-autocomplete";
|
|
14
14
|
import { materializeImageReferenceLinks, shiftImageMarkers } from "../../modes/image-references";
|
|
15
15
|
import { createPromptActionAutocompleteProvider } from "../../modes/prompt-action-autocomplete";
|
|
16
|
+
import { invokeSkillCommandFromText, isKnownSkillCommand } from "../../modes/skill-command";
|
|
16
17
|
import type { InteractiveModeContext } from "../../modes/types";
|
|
17
18
|
import manualContinuePrompt from "../../prompts/system/manual-continue.md" with { type: "text" };
|
|
18
|
-
import {
|
|
19
|
+
import { USER_INTERRUPT_LABEL } from "../../session/messages";
|
|
19
20
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
20
21
|
import { isTinyTitleLocalModelKey } from "../../tiny/models";
|
|
21
22
|
import { isLowSignalTitleInput } from "../../tiny/text";
|
|
@@ -715,11 +716,18 @@ export class InputController {
|
|
|
715
716
|
}
|
|
716
717
|
|
|
717
718
|
// Handle skill commands (/skill:name [args]). Enter ⇒ steer (matches the
|
|
718
|
-
// free-text Enter semantics
|
|
719
|
-
//
|
|
720
|
-
//
|
|
721
|
-
if (text && (
|
|
722
|
-
|
|
719
|
+
// free-text Enter semantics below); Ctrl+Enter routes through `handleFollowUp`.
|
|
720
|
+
// During compaction, queue immediately so bash/python/loop-mode branches do
|
|
721
|
+
// not consume the skill before the compaction-resume path re-parses it.
|
|
722
|
+
if (text && isKnownSkillCommand(this.ctx, text)) {
|
|
723
|
+
if (this.ctx.session.isCompacting) {
|
|
724
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
725
|
+
this.ctx.queueCompactionMessage(text, "steer", images);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (await this.#invokeSkillCommand(text, "steer", inputImages, inputImageLinks)) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
723
731
|
}
|
|
724
732
|
|
|
725
733
|
// Handle bash command (! for normal, !! for excluded from context)
|
|
@@ -1074,59 +1082,52 @@ export class InputController {
|
|
|
1074
1082
|
|
|
1075
1083
|
/**
|
|
1076
1084
|
* Dispatch a `/skill:<name> [args]` invocation through `promptCustomMessage`
|
|
1077
|
-
* using the supplied `streamingBehavior`. Returns
|
|
1078
|
-
*
|
|
1079
|
-
*
|
|
1080
|
-
*
|
|
1081
|
-
* handling at that point would double-submit. Returns false when the text
|
|
1082
|
-
* isn't a `/skill:` prefix or the command name isn't a registered skill,
|
|
1083
|
-
* so the caller can fall through to plain-text handling (this branch
|
|
1084
|
-
* leaves the editor state untouched). `streamingBehavior` is only consulted
|
|
1085
|
-
* while the agent is streaming; the idle path of `promptCustomMessage`
|
|
1086
|
-
* ignores it.
|
|
1085
|
+
* using the supplied `streamingBehavior`. Returns false when the text is not
|
|
1086
|
+
* a registered skill command and leaves the editor state untouched. Registered
|
|
1087
|
+
* skills consume the full composer draft (text plus pending images) before
|
|
1088
|
+
* dispatch; if dispatch rejects, the draft is restored so the user can retry.
|
|
1087
1089
|
*/
|
|
1088
|
-
async #invokeSkillCommand(
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
if (!
|
|
1095
|
-
|
|
1096
|
-
|
|
1090
|
+
async #invokeSkillCommand(
|
|
1091
|
+
text: string,
|
|
1092
|
+
streamingBehavior: "steer" | "followUp",
|
|
1093
|
+
images?: ImageContent[],
|
|
1094
|
+
imageLinks?: (string | undefined)[],
|
|
1095
|
+
): Promise<boolean> {
|
|
1096
|
+
if (!isKnownSkillCommand(this.ctx, text)) return false;
|
|
1097
|
+
const draftImages = images && images.length > 0 ? [...images] : undefined;
|
|
1098
|
+
const draftImageLinks = draftImages && imageLinks && imageLinks.length > 0 ? [...imageLinks] : undefined;
|
|
1099
|
+
const restoreDraft = () => {
|
|
1100
|
+
this.ctx.editor.setText(text);
|
|
1101
|
+
if (draftImages && draftImages.length > 0) {
|
|
1102
|
+
this.ctx.editor.pendingImages = [...draftImages];
|
|
1103
|
+
this.ctx.editor.pendingImageLinks = draftImageLinks
|
|
1104
|
+
? [...draftImageLinks]
|
|
1105
|
+
: draftImages.map(() => undefined);
|
|
1106
|
+
this.ctx.editor.imageLinks = this.ctx.editor.pendingImageLinks;
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
|
|
1110
|
+
this.ctx.editor.clearDraft(text);
|
|
1097
1111
|
try {
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1112
|
+
const handled = await invokeSkillCommandFromText(this.ctx, text, streamingBehavior, {
|
|
1113
|
+
images: draftImages,
|
|
1114
|
+
propagateErrors: true,
|
|
1115
|
+
});
|
|
1116
|
+
if (!handled) {
|
|
1117
|
+
restoreDraft();
|
|
1118
|
+
return false;
|
|
1103
1119
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
lineCount: body ? body.split("\n").length : 0,
|
|
1111
|
-
};
|
|
1112
|
-
await this.ctx.session.promptCustomMessage(
|
|
1113
|
-
{
|
|
1114
|
-
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
1115
|
-
content: message,
|
|
1116
|
-
display: true,
|
|
1117
|
-
details,
|
|
1118
|
-
attribution: "user",
|
|
1119
|
-
},
|
|
1120
|
-
{ streamingBehavior, queueChipText: text },
|
|
1121
|
-
);
|
|
1120
|
+
return true;
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
restoreDraft();
|
|
1123
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
1124
|
+
return true;
|
|
1125
|
+
} finally {
|
|
1122
1126
|
if (this.ctx.session.isStreaming) {
|
|
1123
1127
|
this.ctx.updatePendingMessagesDisplay();
|
|
1124
1128
|
this.ctx.ui.requestRender();
|
|
1125
1129
|
}
|
|
1126
|
-
} catch (err) {
|
|
1127
|
-
this.ctx.showError(`Failed to load skill: ${err instanceof Error ? err.message : String(err)}`);
|
|
1128
1130
|
}
|
|
1129
|
-
return true;
|
|
1130
1131
|
}
|
|
1131
1132
|
|
|
1132
1133
|
async handleRetry(): Promise<void> {
|
|
@@ -1159,9 +1160,8 @@ export class InputController {
|
|
|
1159
1160
|
// Compaction first: while compacting, free text gets queued via
|
|
1160
1161
|
// `queueCompactionMessage`, and `/skill:*` rides the same queue so a
|
|
1161
1162
|
// skill typed during compaction is not lost or short-circuited through
|
|
1162
|
-
// `promptCustomMessage`. The
|
|
1163
|
-
//
|
|
1164
|
-
// separate concern owned by the compaction-resume path.
|
|
1163
|
+
// `promptCustomMessage`. The compaction-resume path re-parses the
|
|
1164
|
+
// queued text into a user-attributed skill invocation before delivery.
|
|
1165
1165
|
if (this.ctx.session.isCompacting) {
|
|
1166
1166
|
const images = this.ctx.editor.pendingImages.length > 0 ? [...this.ctx.editor.pendingImages] : undefined;
|
|
1167
1167
|
this.ctx.queueCompactionMessage(text, "followUp", images);
|
|
@@ -1187,7 +1187,7 @@ export class InputController {
|
|
|
1187
1187
|
// Skill commands invoke through the custom-message path regardless of
|
|
1188
1188
|
// which keybinding submitted them. Enter routes them as `steer`;
|
|
1189
1189
|
// Ctrl+Enter (this handler) routes them as `followUp`.
|
|
1190
|
-
if (text && (await this.#invokeSkillCommand(text, "followUp"))) {
|
|
1190
|
+
if (text && (await this.#invokeSkillCommand(text, "followUp", images, imageLinks))) {
|
|
1191
1191
|
return;
|
|
1192
1192
|
}
|
|
1193
1193
|
|
|
@@ -1739,14 +1739,14 @@ export class InputController {
|
|
|
1739
1739
|
}
|
|
1740
1740
|
|
|
1741
1741
|
toggleThinkingBlockVisibility(): void {
|
|
1742
|
-
// When thinking is "off"
|
|
1743
|
-
//
|
|
1744
|
-
//
|
|
1745
|
-
//
|
|
1746
|
-
//
|
|
1742
|
+
// When thinking is "off" and the session has not produced reasoning
|
|
1743
|
+
// content, thinking blocks stay auto-hidden; the toggle would only corrupt
|
|
1744
|
+
// the persisted preference. OpenAI-compatible servers can stream reasoning
|
|
1745
|
+
// without advertising model support, so observed thinking content unlocks
|
|
1746
|
+
// the display toggle.
|
|
1747
1747
|
const thinkingOff =
|
|
1748
1748
|
((this.ctx.viewSession ?? this.ctx.session)?.thinkingLevel ?? ThinkingLevel.Off) === ThinkingLevel.Off;
|
|
1749
|
-
if (thinkingOff) {
|
|
1749
|
+
if (thinkingOff && !this.ctx.hasDisplayableThinkingContent) {
|
|
1750
1750
|
this.ctx.showStatus("Thinking is off — enable thinking to show blocks");
|
|
1751
1751
|
return;
|
|
1752
1752
|
}
|
|
@@ -4,8 +4,15 @@ import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
|
4
4
|
import type { OAuthProvider } from "@oh-my-pi/pi-ai/oauth/types";
|
|
5
5
|
import type { Component, OverlayHandle } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Input, Loader, Spacer, setTuiTight, Text } from "@oh-my-pi/pi-tui";
|
|
7
|
-
import { getAgentDbPath, getProjectDir, normalizePathForComparison } from "@oh-my-pi/pi-utils";
|
|
8
|
-
import {
|
|
7
|
+
import { getAgentDbPath, getAgentDir, getProjectDir, normalizePathForComparison } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import {
|
|
9
|
+
type AdvisorConfigScope,
|
|
10
|
+
discoverAdvisorConfigs,
|
|
11
|
+
loadWatchdogConfigFile,
|
|
12
|
+
resolveAdvisorConfigEditPath,
|
|
13
|
+
saveWatchdogConfigFile,
|
|
14
|
+
} from "../../advisor";
|
|
15
|
+
import { formatModelSelectorValue, resolveAdvisorRoleSelection } from "../../config/model-resolver";
|
|
9
16
|
import { getRoleInfo } from "../../config/model-roles";
|
|
10
17
|
import { settings } from "../../config/settings";
|
|
11
18
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
@@ -49,7 +56,9 @@ import {
|
|
|
49
56
|
} from "../../tools";
|
|
50
57
|
import { shortenPath } from "../../tools/render-utils";
|
|
51
58
|
import { copyToClipboard } from "../../utils/clipboard";
|
|
59
|
+
import { repo } from "../../utils/git";
|
|
52
60
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
61
|
+
import { type AdvisorConfigDeps, AdvisorConfigOverlayComponent } from "../components/advisor-config";
|
|
53
62
|
import { AgentDashboard } from "../components/agent-dashboard";
|
|
54
63
|
import { AgentHubOverlayComponent } from "../components/agent-hub";
|
|
55
64
|
import { AssistantMessageComponent } from "../components/assistant-message";
|
|
@@ -163,6 +172,7 @@ export class SelectorController {
|
|
|
163
172
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
164
173
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
165
174
|
transparent: settings.get("statusLine.transparent"),
|
|
175
|
+
compactThinkingLevel: settings.get("statusLine.compactThinkingLevel"),
|
|
166
176
|
...previewSettings,
|
|
167
177
|
});
|
|
168
178
|
this.ctx.updateEditorTopBorder();
|
|
@@ -191,6 +201,7 @@ export class SelectorController {
|
|
|
191
201
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
192
202
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
193
203
|
transparent: settings.get("statusLine.transparent"),
|
|
204
|
+
compactThinkingLevel: settings.get("statusLine.compactThinkingLevel"),
|
|
194
205
|
});
|
|
195
206
|
this.ctx.updateEditorTopBorder();
|
|
196
207
|
this.ctx.ui.requestRender();
|
|
@@ -209,6 +220,78 @@ export class SelectorController {
|
|
|
209
220
|
});
|
|
210
221
|
}
|
|
211
222
|
|
|
223
|
+
showAdvisorConfigure(): void {
|
|
224
|
+
const cwd = this.ctx.sessionManager.getCwd();
|
|
225
|
+
const agentDir = getAgentDir() ?? getProjectDir();
|
|
226
|
+
const initialScope: AdvisorConfigScope = "project";
|
|
227
|
+
void (async () => {
|
|
228
|
+
// "Project" scope edits the repo-root WATCHDOG.yml (the project-level file
|
|
229
|
+
// discovery walks), not the launch subdir — `getProjectDir()` is only cwd.
|
|
230
|
+
let projectDir = cwd;
|
|
231
|
+
try {
|
|
232
|
+
projectDir = (await repo.root(cwd)) ?? cwd;
|
|
233
|
+
} catch {
|
|
234
|
+
projectDir = cwd;
|
|
235
|
+
}
|
|
236
|
+
const dirs = { projectDir, agentDir };
|
|
237
|
+
const initialDoc = await loadWatchdogConfigFile(await resolveAdvisorConfigEditPath(initialScope, dirs));
|
|
238
|
+
// Fullscreen editor on the alternate screen (the /settings idiom): the
|
|
239
|
+
// overlay holds the alt buffer + mouse tracking; the transcript stays put.
|
|
240
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
241
|
+
const done = () => {
|
|
242
|
+
overlayHandle?.hide();
|
|
243
|
+
this.focusActiveEditorArea();
|
|
244
|
+
this.ctx.ui.requestRender();
|
|
245
|
+
};
|
|
246
|
+
// Label the seeded implicit-default row with the actual advisor-role model
|
|
247
|
+
// (NOT the first live advisor, which may be a named advisor from another scope).
|
|
248
|
+
const advisorRoleSel = resolveAdvisorRoleSelection(
|
|
249
|
+
this.ctx.settings,
|
|
250
|
+
this.ctx.session.modelRegistry.getAvailable(),
|
|
251
|
+
this.ctx.session.modelRegistry,
|
|
252
|
+
);
|
|
253
|
+
const defaultAdvisorModel = advisorRoleSel?.model;
|
|
254
|
+
const deps: AdvisorConfigDeps = {
|
|
255
|
+
modelRegistry: this.ctx.session.modelRegistry,
|
|
256
|
+
settings: this.ctx.settings,
|
|
257
|
+
scopedModels: this.ctx.session.scopedModels,
|
|
258
|
+
availableToolNames: this.ctx.session.getAdvisorAvailableToolNames(),
|
|
259
|
+
defaultModelLabel: defaultAdvisorModel
|
|
260
|
+
? `${defaultAdvisorModel.provider}/${defaultAdvisorModel.id}`
|
|
261
|
+
: undefined,
|
|
262
|
+
};
|
|
263
|
+
const overlay = new AdvisorConfigOverlayComponent(this.ctx.ui, deps, initialScope, initialDoc, {
|
|
264
|
+
loadDoc: async scope => loadWatchdogConfigFile(await resolveAdvisorConfigEditPath(scope, dirs)),
|
|
265
|
+
save: async (scope, doc) => {
|
|
266
|
+
await saveWatchdogConfigFile(await resolveAdvisorConfigEditPath(scope, dirs), doc);
|
|
267
|
+
// Re-discover the merged roster (project + user) so the live advisors
|
|
268
|
+
// reflect cross-level precedence, not just the edited file.
|
|
269
|
+
const discovered = await discoverAdvisorConfigs(cwd, agentDir);
|
|
270
|
+
const count = this.ctx.session.applyAdvisorConfigs(discovered.advisors, discovered.sharedInstructions);
|
|
271
|
+
this.ctx.statusLine.invalidate();
|
|
272
|
+
this.ctx.showStatus(
|
|
273
|
+
count > 0
|
|
274
|
+
? `Saved ${scope} WATCHDOG.yml — ${count} advisor${count === 1 ? "" : "s"} active.`
|
|
275
|
+
: `Saved ${scope} WATCHDOG.yml. Run /advisor on to activate the configured advisors.`,
|
|
276
|
+
);
|
|
277
|
+
this.ctx.ui.requestRender();
|
|
278
|
+
},
|
|
279
|
+
close: done,
|
|
280
|
+
requestRender: () => this.ctx.ui.requestRender(),
|
|
281
|
+
notify: message => this.ctx.showStatus(message),
|
|
282
|
+
});
|
|
283
|
+
overlayHandle = this.ctx.ui.showOverlay(overlay, {
|
|
284
|
+
anchor: "bottom-center",
|
|
285
|
+
width: "100%",
|
|
286
|
+
maxHeight: "100%",
|
|
287
|
+
margin: 0,
|
|
288
|
+
fullscreen: true,
|
|
289
|
+
});
|
|
290
|
+
this.ctx.ui.setFocus(overlay);
|
|
291
|
+
this.ctx.ui.requestRender();
|
|
292
|
+
})();
|
|
293
|
+
}
|
|
294
|
+
|
|
212
295
|
showHistorySearch(): void {
|
|
213
296
|
const historyStorage = this.ctx.historyStorage;
|
|
214
297
|
if (!historyStorage) return;
|
|
@@ -451,6 +534,7 @@ export class SelectorController {
|
|
|
451
534
|
case "statusLine.showHookStatus":
|
|
452
535
|
case "statusLine.sessionAccent":
|
|
453
536
|
case "statusLine.transparent":
|
|
537
|
+
case "statusLine.compactThinkingLevel":
|
|
454
538
|
case "statusLineSegments":
|
|
455
539
|
case "statusLineModelThinking":
|
|
456
540
|
case "statusLinePathAbbreviate":
|
|
@@ -471,6 +555,7 @@ export class SelectorController {
|
|
|
471
555
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
472
556
|
transparent: settings.get("statusLine.transparent"),
|
|
473
557
|
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
558
|
+
compactThinkingLevel: settings.get("statusLine.compactThinkingLevel"),
|
|
474
559
|
};
|
|
475
560
|
this.ctx.statusLine.updateSettings(statusLineSettings);
|
|
476
561
|
this.ctx.updateEditorTopBorder();
|
|
@@ -536,19 +621,25 @@ export class SelectorController {
|
|
|
536
621
|
done();
|
|
537
622
|
this.ctx.ui.requestRender();
|
|
538
623
|
} else if (role === "default") {
|
|
539
|
-
|
|
540
|
-
await this.ctx.session.setModel(model, role, {
|
|
624
|
+
const { switched } = await this.ctx.session.setModel(model, role, {
|
|
541
625
|
selector,
|
|
542
626
|
thinkingLevel: concreteThinking,
|
|
543
627
|
persist: true,
|
|
628
|
+
currentContextTokens,
|
|
544
629
|
});
|
|
545
630
|
if (isAuto) {
|
|
546
|
-
|
|
547
|
-
|
|
631
|
+
if (switched) {
|
|
632
|
+
this.ctx.session.setThinkingLevel(AUTO_THINKING, true);
|
|
633
|
+
} else {
|
|
634
|
+
this.ctx.settings.set("defaultThinkingLevel", AUTO_THINKING);
|
|
635
|
+
}
|
|
636
|
+
} else if (switched && concreteThinking && concreteThinking !== ThinkingLevel.Inherit) {
|
|
548
637
|
this.ctx.session.setThinkingLevel(concreteThinking);
|
|
549
638
|
}
|
|
550
|
-
|
|
551
|
-
|
|
639
|
+
if (switched) {
|
|
640
|
+
this.ctx.statusLine.invalidate();
|
|
641
|
+
this.ctx.updateEditorBorderColor();
|
|
642
|
+
}
|
|
552
643
|
this.ctx.showStatus(`Default model: ${selector ?? model.id}`);
|
|
553
644
|
// Don't call done() - selector stays open for role assignment
|
|
554
645
|
} else {
|
|
@@ -933,7 +1024,7 @@ export class SelectorController {
|
|
|
933
1024
|
|
|
934
1025
|
this.ctx.clearTransientSessionUi();
|
|
935
1026
|
this.ctx.statusLine.invalidate();
|
|
936
|
-
this.ctx.statusLine.
|
|
1027
|
+
this.ctx.statusLine.resetActiveTime();
|
|
937
1028
|
this.ctx.updateEditorTopBorder();
|
|
938
1029
|
this.ctx.updateEditorBorderColor();
|
|
939
1030
|
this.ctx.renderInitialMessages({ clearTerminalHistory: true });
|