@oh-my-pi/pi-coding-agent 16.1.7 → 16.1.9
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 +71 -0
- package/dist/cli.js +4266 -3446
- package/dist/types/cli/flag-tables.d.ts +17 -0
- package/dist/types/cli/session-picker.d.ts +0 -1
- package/dist/types/cli/tiny-models-cli.d.ts +2 -0
- package/dist/types/cli-commands.d.ts +4 -2
- package/dist/types/collab/protocol.d.ts +20 -1
- package/dist/types/config/settings-schema.d.ts +57 -6
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +5 -1
- package/dist/types/internal-urls/filesystem-resource.d.ts +9 -0
- package/dist/types/mcp/loader.d.ts +3 -2
- package/dist/types/mcp/manager.d.ts +4 -3
- package/dist/types/mcp/startup-events.d.ts +21 -4
- package/dist/types/mnemopi/config.d.ts +1 -0
- package/dist/types/modes/components/agent-hub.d.ts +1 -0
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +1 -0
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/btw-panel.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +0 -2
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/controllers/streaming-reveal.d.ts +3 -2
- package/dist/types/modes/interactive-mode.d.ts +4 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/session/agent-session.d.ts +18 -3
- package/dist/types/session/session-history-format.d.ts +25 -0
- package/dist/types/session/snapcompact-inline.d.ts +1 -1
- package/dist/types/slash-commands/builtin-registry.d.ts +7 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +1 -0
- package/dist/types/tiny/models.d.ts +11 -7
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/todo.d.ts +1 -0
- package/dist/types/utils/thinking-display.d.ts +2 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +104 -0
- package/src/advisor/runtime.ts +38 -2
- package/src/cli/args.ts +0 -1
- package/src/cli/flag-tables.ts +42 -0
- package/src/cli/profile-bootstrap.ts +1 -11
- package/src/cli/session-picker.ts +1 -2
- package/src/cli/tiny-models-cli.ts +7 -2
- package/src/cli-commands.ts +48 -3
- package/src/collab/guest.ts +172 -20
- package/src/collab/host.ts +47 -5
- package/src/collab/protocol.ts +16 -1
- package/src/config/model-registry.ts +6 -7
- package/src/config/settings-schema.ts +59 -5
- package/src/edit/renderer.ts +8 -12
- package/src/extensibility/legacy-pi-ai-shim.ts +16 -4
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/filesystem-resource.ts +34 -0
- package/src/internal-urls/local-protocol.ts +7 -1
- package/src/internal-urls/memory-protocol.ts +5 -1
- package/src/internal-urls/skill-protocol.ts +20 -4
- package/src/internal-urls/vault-protocol.ts +5 -2
- package/src/lsp/client.ts +39 -25
- package/src/main.ts +8 -8
- package/src/mcp/loader.ts +4 -3
- package/src/mcp/manager.ts +35 -15
- package/src/mcp/startup-events.ts +106 -11
- package/src/mnemopi/config.ts +2 -0
- package/src/mnemopi/state.ts +3 -1
- package/src/modes/components/agent-hub.ts +4 -0
- package/src/modes/components/agent-transcript-viewer.ts +2 -0
- package/src/modes/components/assistant-message.ts +217 -18
- package/src/modes/components/btw-panel.ts +15 -3
- package/src/modes/components/chat-transcript-builder.ts +8 -2
- package/src/modes/components/model-selector.ts +72 -9
- package/src/modes/components/omfg-panel.ts +1 -1
- package/src/modes/components/session-selector.ts +4 -9
- package/src/modes/components/snapcompact-shape-preview.ts +1 -1
- package/src/modes/components/tool-execution.ts +10 -2
- package/src/modes/controllers/btw-controller.ts +32 -7
- package/src/modes/controllers/command-controller.ts +18 -3
- package/src/modes/controllers/event-controller.ts +24 -4
- package/src/modes/controllers/input-controller.ts +43 -21
- package/src/modes/controllers/mcp-command-controller.ts +40 -12
- package/src/modes/controllers/selector-controller.ts +23 -13
- package/src/modes/controllers/streaming-reveal.ts +78 -20
- package/src/modes/controllers/todo-command-controller.ts +9 -10
- package/src/modes/interactive-mode.ts +84 -9
- package/src/modes/types.ts +4 -1
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/advisor/system.md +1 -1
- package/src/prompts/system/side-channel-no-tools.md +3 -0
- package/src/sdk.ts +12 -8
- package/src/session/agent-session.ts +385 -85
- package/src/session/history-storage.ts +15 -16
- package/src/session/session-history-format.ts +41 -1
- package/src/session/snapcompact-inline.ts +9 -6
- package/src/slash-commands/builtin-registry.ts +171 -87
- package/src/slash-commands/helpers/todo.ts +12 -6
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +6 -11
- package/src/tiny/models.ts +7 -3
- package/src/tiny/title-client.ts +8 -2
- package/src/tiny/worker.ts +1 -0
- package/src/tools/browser/launch.ts +107 -31
- package/src/tools/puppeteer/00_stealth_tampering.txt +16 -35
- package/src/tools/puppeteer/01_stealth_activity.txt +79 -19
- package/src/tools/puppeteer/02_stealth_hairline.txt +57 -11
- package/src/tools/puppeteer/03_stealth_botd.txt +10 -14
- package/src/tools/puppeteer/04_stealth_iframe.txt +156 -63
- package/src/tools/puppeteer/05_stealth_webgl.txt +222 -64
- package/src/tools/puppeteer/06_stealth_screen.txt +255 -67
- package/src/tools/puppeteer/07_stealth_fonts.txt +17 -15
- package/src/tools/puppeteer/08_stealth_audio.txt +32 -20
- package/src/tools/puppeteer/09_stealth_locale.txt +45 -40
- package/src/tools/puppeteer/10_stealth_plugins.txt +12 -8
- package/src/tools/puppeteer/11_stealth_hardware.txt +57 -6
- package/src/tools/puppeteer/12_stealth_codecs.txt +20 -18
- package/src/tools/puppeteer/13_stealth_worker.txt +206 -45
- package/src/tools/search.ts +10 -1
- package/src/tools/sqlite-reader.ts +59 -3
- package/src/tools/todo.ts +6 -0
- package/src/tools/write.ts +4 -6
- package/src/utils/thinking-display.ts +78 -0
|
@@ -31,7 +31,13 @@ import {
|
|
|
31
31
|
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
32
32
|
renderJsonTreeLines,
|
|
33
33
|
} from "../../tools/json-tree";
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
formatExpandHint,
|
|
36
|
+
formatStatusIcon,
|
|
37
|
+
replaceTabs,
|
|
38
|
+
resolveImageOptions,
|
|
39
|
+
truncateToWidth,
|
|
40
|
+
} from "../../tools/render-utils";
|
|
35
41
|
import { toolRenderers } from "../../tools/renderers";
|
|
36
42
|
import { TODO_STRIKE_TOTAL_FRAMES } from "../../tools/todo";
|
|
37
43
|
import { isFramedBlockComponent, renderStatusLine, WidthAwareText } from "../../tui";
|
|
@@ -865,9 +871,11 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
865
871
|
this.#multiFileBoxes.push(pendingSpacer);
|
|
866
872
|
this.addChild(pendingSpacer);
|
|
867
873
|
const pendingBox = new Box(0, 0);
|
|
874
|
+
const spinner =
|
|
875
|
+
this.#spinnerFrame !== undefined ? formatStatusIcon("running", theme, this.#spinnerFrame) : "";
|
|
868
876
|
const pendingText = renderStatusLine(
|
|
869
877
|
{
|
|
870
|
-
|
|
878
|
+
iconOverride: spinner,
|
|
871
879
|
title: "Edit",
|
|
872
880
|
description: theme.fg("dim", `${remaining} more file${remaining > 1 ? "s" : ""} pending…`),
|
|
873
881
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import btwUserPrompt from "../../prompts/system/btw-user.md" with { type: "text" };
|
|
4
|
+
import { copyToClipboard } from "../../utils/clipboard";
|
|
4
5
|
import { BtwPanelComponent } from "../components/btw-panel";
|
|
5
6
|
import type { InteractiveModeContext } from "../types";
|
|
6
7
|
|
|
@@ -39,6 +40,8 @@ export class BtwController {
|
|
|
39
40
|
#lastAssistantMessage: AssistantMessage | undefined;
|
|
40
41
|
#lastLeafId: string | null | undefined;
|
|
41
42
|
#branchInFlight = false;
|
|
43
|
+
#lastCopyText: string | undefined;
|
|
44
|
+
#copyInFlight = false;
|
|
42
45
|
|
|
43
46
|
constructor(private readonly ctx: InteractiveModeContext) {}
|
|
44
47
|
|
|
@@ -58,6 +61,27 @@ export class BtwController {
|
|
|
58
61
|
);
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
canCopy(): boolean {
|
|
65
|
+
return (
|
|
66
|
+
!this.#copyInFlight && this.#activeRequest?.component.isCopyable() === true && this.#lastCopyText !== undefined
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async handleCopy(): Promise<boolean> {
|
|
71
|
+
if (!this.canCopy() || this.#lastCopyText === undefined) return false;
|
|
72
|
+
this.#copyInFlight = true;
|
|
73
|
+
try {
|
|
74
|
+
await copyToClipboard(this.#lastCopyText);
|
|
75
|
+
this.ctx.showStatus("Copied /btw answer to clipboard");
|
|
76
|
+
return true;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
79
|
+
return true;
|
|
80
|
+
} finally {
|
|
81
|
+
this.#copyInFlight = false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
async handleBranch(): Promise<boolean> {
|
|
62
86
|
if (!this.canBranch() || !this.#lastQuestion || !this.#lastAssistantMessage) return false;
|
|
63
87
|
this.#branchInFlight = true;
|
|
@@ -123,17 +147,17 @@ export class BtwController {
|
|
|
123
147
|
if (!this.#isActiveRequest(request)) {
|
|
124
148
|
return;
|
|
125
149
|
}
|
|
126
|
-
|
|
127
|
-
request.component.setAnswer(replyText);
|
|
128
|
-
}
|
|
150
|
+
request.component.setAnswer(replyText);
|
|
129
151
|
request.component.markComplete();
|
|
130
|
-
|
|
152
|
+
const copyText = request.component.getCopyText();
|
|
153
|
+
if (copyText !== undefined) {
|
|
131
154
|
this.#lastQuestion = request.question;
|
|
132
155
|
this.#lastReplyText = replyText;
|
|
156
|
+
this.#lastCopyText = copyText;
|
|
133
157
|
this.#lastAssistantMessage = assistantMessageWithReplyText(assistantMessage, replyText);
|
|
134
158
|
this.#lastLeafId = request.leafId;
|
|
135
159
|
} else {
|
|
136
|
-
this.#
|
|
160
|
+
this.#clearCompletedState();
|
|
137
161
|
}
|
|
138
162
|
} catch (error) {
|
|
139
163
|
if (!this.#isActiveRequest(request)) {
|
|
@@ -151,7 +175,7 @@ export class BtwController {
|
|
|
151
175
|
const request = this.#activeRequest;
|
|
152
176
|
if (!request) return;
|
|
153
177
|
this.#activeRequest = undefined;
|
|
154
|
-
this.#
|
|
178
|
+
this.#clearCompletedState();
|
|
155
179
|
if (options.abort) {
|
|
156
180
|
request.abortController.abort();
|
|
157
181
|
}
|
|
@@ -160,10 +184,11 @@ export class BtwController {
|
|
|
160
184
|
this.ctx.ui.requestRender();
|
|
161
185
|
}
|
|
162
186
|
|
|
163
|
-
#
|
|
187
|
+
#clearCompletedState(): void {
|
|
164
188
|
this.#lastQuestion = undefined;
|
|
165
189
|
this.#lastReplyText = undefined;
|
|
166
190
|
this.#lastAssistantMessage = undefined;
|
|
191
|
+
this.#lastCopyText = undefined;
|
|
167
192
|
this.#lastLeafId = undefined;
|
|
168
193
|
}
|
|
169
194
|
|
|
@@ -85,15 +85,30 @@ export class CommandController {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
handleDumpCommand() {
|
|
88
|
+
async handleDumpCommand(): Promise<void> {
|
|
89
89
|
try {
|
|
90
90
|
const formatted = this.ctx.session.formatSessionAsText();
|
|
91
91
|
if (!formatted) {
|
|
92
92
|
this.ctx.showError("No messages to dump yet.");
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
// Build the LLM request JSON sidecar first so its path (and a
|
|
96
|
+
// raw-context warning) can be appended to the copied transcript.
|
|
97
|
+
let sidecarPath: string | undefined;
|
|
98
|
+
let sidecarError: string | undefined;
|
|
99
|
+
try {
|
|
100
|
+
sidecarPath = await this.ctx.session.dumpLlmRequestToTmpDir();
|
|
101
|
+
} catch (error: unknown) {
|
|
102
|
+
sidecarError = error instanceof Error ? error.message : "Unknown error";
|
|
103
|
+
}
|
|
104
|
+
const doc = sidecarPath
|
|
105
|
+
? `${formatted}\n\n---\nLLM request JSON: ${sidecarPath}\nThis file persists on disk and may contain raw context/secrets — treat accordingly.`
|
|
106
|
+
: formatted;
|
|
107
|
+
await copyToClipboard(doc);
|
|
108
|
+
const statusParts = ["Session copied to clipboard"];
|
|
109
|
+
if (sidecarPath) statusParts.push(`LLM request JSON: ${sidecarPath}`);
|
|
110
|
+
if (sidecarError) statusParts.push(`LLM request JSON unavailable: ${sidecarError}`);
|
|
111
|
+
this.ctx.showStatus(statusParts.join("\n"));
|
|
97
112
|
} catch (error: unknown) {
|
|
98
113
|
this.ctx.showError(`Failed to copy session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
99
114
|
}
|
|
@@ -87,6 +87,7 @@ export class EventController {
|
|
|
87
87
|
this.#streamingReveal = new StreamingRevealController({
|
|
88
88
|
getSmoothStreaming: () => this.ctx.settings.get("display.smoothStreaming"),
|
|
89
89
|
getHideThinkingBlock: () => this.ctx.hideThinkingBlock,
|
|
90
|
+
getProseOnlyThinking: () => this.ctx.proseOnlyThinking,
|
|
90
91
|
requestRender: () => this.ctx.ui.requestRender(),
|
|
91
92
|
});
|
|
92
93
|
this.#toolArgsReveal = new ToolArgsRevealController({
|
|
@@ -333,6 +334,7 @@ export class EventController {
|
|
|
333
334
|
() => this.ctx.ui.requestRender(),
|
|
334
335
|
this.ctx.viewSession.extensionRunner?.getAssistantThinkingRenderers(),
|
|
335
336
|
this.ctx.ui.imageBudget,
|
|
337
|
+
this.ctx.proseOnlyThinking,
|
|
336
338
|
);
|
|
337
339
|
this.ctx.streamingMessage = event.message;
|
|
338
340
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
@@ -920,6 +922,17 @@ export class EventController {
|
|
|
920
922
|
}
|
|
921
923
|
}
|
|
922
924
|
|
|
925
|
+
/**
|
|
926
|
+
* Trailing Esc hint for live maintenance loaders. While a subagent is
|
|
927
|
+
* focused, Esc returns to main instead of cancelling its maintenance
|
|
928
|
+
* (#2819), so the loader drops the hint entirely rather than advertise a
|
|
929
|
+
* cancel that no longer happens. Includes the leading space so the focused
|
|
930
|
+
* label carries no dangling whitespace.
|
|
931
|
+
*/
|
|
932
|
+
#maintenanceEscHint(): string {
|
|
933
|
+
return this.ctx.focusedAgentId ? "" : " (esc to cancel)";
|
|
934
|
+
}
|
|
935
|
+
|
|
923
936
|
async #handleAutoCompactionStart(
|
|
924
937
|
event: Extract<AgentSessionEvent, { type: "auto_compaction_start" }>,
|
|
925
938
|
): Promise<void> {
|
|
@@ -939,12 +952,14 @@ export class EventController {
|
|
|
939
952
|
? "Auto-handoff"
|
|
940
953
|
: event.action === "shake"
|
|
941
954
|
? "Auto-shake"
|
|
942
|
-
:
|
|
955
|
+
: event.action === "snapcompact"
|
|
956
|
+
? "Auto-snapcompact"
|
|
957
|
+
: "Auto context-full maintenance";
|
|
943
958
|
this.ctx.autoCompactionLoader = new Loader(
|
|
944
959
|
this.ctx.ui,
|
|
945
960
|
spinner => theme.fg("accent", spinner),
|
|
946
961
|
text => theme.fg("muted", text),
|
|
947
|
-
`${reasonText}${actionLabel}
|
|
962
|
+
`${reasonText}${actionLabel}…${this.#maintenanceEscHint()}`,
|
|
948
963
|
getSymbolTheme().spinnerFrames,
|
|
949
964
|
);
|
|
950
965
|
this.ctx.statusContainer.addChild(this.ctx.autoCompactionLoader);
|
|
@@ -960,13 +975,16 @@ export class EventController {
|
|
|
960
975
|
}
|
|
961
976
|
const isHandoffAction = event.action === "handoff";
|
|
962
977
|
const isShakeAction = event.action === "shake";
|
|
978
|
+
const isSnapcompactAction = event.action === "snapcompact";
|
|
963
979
|
if (event.aborted) {
|
|
964
980
|
this.ctx.showStatus(
|
|
965
981
|
isHandoffAction
|
|
966
982
|
? "Auto-handoff cancelled"
|
|
967
983
|
: isShakeAction
|
|
968
984
|
? "Auto-shake cancelled"
|
|
969
|
-
:
|
|
985
|
+
: isSnapcompactAction
|
|
986
|
+
? "Auto-snapcompact cancelled"
|
|
987
|
+
: "Auto context-full maintenance cancelled",
|
|
970
988
|
);
|
|
971
989
|
} else if (isShakeAction) {
|
|
972
990
|
// Shake produces no CompactionResult; rebuild on success, suppress benign skips.
|
|
@@ -1005,6 +1023,8 @@ export class EventController {
|
|
|
1005
1023
|
} else if (event.skipped) {
|
|
1006
1024
|
// Benign skip: no model selected, no candidate models available, or nothing
|
|
1007
1025
|
// to compact yet. Not a failure — suppress the warning.
|
|
1026
|
+
} else if (isSnapcompactAction) {
|
|
1027
|
+
this.ctx.showWarning("Auto-snapcompact maintenance failed; continuing without maintenance");
|
|
1008
1028
|
} else {
|
|
1009
1029
|
this.ctx.showWarning("Auto context-full maintenance failed; continuing without maintenance");
|
|
1010
1030
|
}
|
|
@@ -1027,7 +1047,7 @@ export class EventController {
|
|
|
1027
1047
|
this.ctx.ui,
|
|
1028
1048
|
spinner => theme.fg("warning", spinner),
|
|
1029
1049
|
text => theme.fg("muted", text),
|
|
1030
|
-
`Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s
|
|
1050
|
+
`Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s…${this.#maintenanceEscHint()}`,
|
|
1031
1051
|
getSymbolTheme().spinnerFrames,
|
|
1032
1052
|
);
|
|
1033
1053
|
this.ctx.statusContainer.addChild(this.ctx.retryLoader);
|
|
@@ -99,6 +99,7 @@ export class InputController {
|
|
|
99
99
|
#enhancedPaste?: EnhancedPasteController;
|
|
100
100
|
#focusedLeftTapListenerInstalled = false;
|
|
101
101
|
#btwBranchListenerInstalled = false;
|
|
102
|
+
#btwCopyListenerInstalled = false;
|
|
102
103
|
// Tap counter for the double-← gesture; reset whenever a quiet gap
|
|
103
104
|
// (>= LEFT_DOUBLE_TAP_MAX_GAP_MS) starts a fresh sequence. See
|
|
104
105
|
// #detectLeftDoubleTap.
|
|
@@ -169,11 +170,23 @@ export class InputController {
|
|
|
169
170
|
this.ctx.ui.addInputListener(data => {
|
|
170
171
|
if (!matchesKey(data, "b")) return undefined;
|
|
171
172
|
if (!this.ctx.canBranchBtw()) return undefined;
|
|
173
|
+
if (this.ctx.ui.getFocused() !== this.ctx.editor) return undefined;
|
|
172
174
|
if (this.ctx.editor.getText().trim()) return undefined;
|
|
173
175
|
void this.ctx.handleBtwBranchKey();
|
|
174
176
|
return { consume: true };
|
|
175
177
|
});
|
|
176
178
|
}
|
|
179
|
+
if (!this.#btwCopyListenerInstalled) {
|
|
180
|
+
this.#btwCopyListenerInstalled = true;
|
|
181
|
+
this.ctx.ui.addInputListener(data => {
|
|
182
|
+
if (!matchesKey(data, "c")) return undefined;
|
|
183
|
+
if (!this.ctx.canCopyBtw()) return undefined;
|
|
184
|
+
if (this.ctx.ui.getFocused() !== this.ctx.editor) return undefined;
|
|
185
|
+
if (this.ctx.editor.getText().trim()) return undefined;
|
|
186
|
+
void this.ctx.handleBtwCopyKey();
|
|
187
|
+
return { consume: true };
|
|
188
|
+
});
|
|
189
|
+
}
|
|
177
190
|
this.ctx.editor.onEscape = () => {
|
|
178
191
|
// Active context maintenance owns Esc: auto/manual compaction,
|
|
179
192
|
// handoff generation, and auto-retry backoff all advertise
|
|
@@ -182,27 +195,37 @@ export class InputController {
|
|
|
182
195
|
// to clobber the single saved-handler slot (auto-compaction start
|
|
183
196
|
// → /compact → auto end → manual finally), leaving Esc wired to a
|
|
184
197
|
// stale no-op closure until restart.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
198
|
+
//
|
|
199
|
+
// While a subagent is focused, Esc honors the advertised view action
|
|
200
|
+
// ("Esc returns to main") instead of cancelling maintenance —
|
|
201
|
+
// accidentally killing a focused subagent's compaction on the way out
|
|
202
|
+
// was #2819. The auto-maintenance loaders relabel their hint to match
|
|
203
|
+
// (see EventController). Main-session maintenance still owns Esc and
|
|
204
|
+
// stays cancellable from the main view (focused submit gates /compact
|
|
205
|
+
// and handoff, so manual maintenance is main-only anyway).
|
|
206
|
+
if (!this.ctx.focusedAgentId) {
|
|
207
|
+
const viewSession = this.ctx.viewSession;
|
|
208
|
+
let aborted = false;
|
|
209
|
+
if (viewSession.isCompacting) {
|
|
210
|
+
try {
|
|
211
|
+
viewSession.abortCompaction();
|
|
212
|
+
} catch {}
|
|
213
|
+
aborted = true;
|
|
214
|
+
}
|
|
215
|
+
if (viewSession.isGeneratingHandoff) {
|
|
216
|
+
try {
|
|
217
|
+
viewSession.abortHandoff();
|
|
218
|
+
} catch {}
|
|
219
|
+
aborted = true;
|
|
220
|
+
}
|
|
221
|
+
if (viewSession.isRetrying) {
|
|
222
|
+
try {
|
|
223
|
+
viewSession.abortRetry();
|
|
224
|
+
} catch {}
|
|
225
|
+
aborted = true;
|
|
226
|
+
}
|
|
227
|
+
if (aborted) return;
|
|
204
228
|
}
|
|
205
|
-
if (aborted) return;
|
|
206
229
|
|
|
207
230
|
if (this.ctx.loopModeEnabled) {
|
|
208
231
|
this.ctx.pauseLoop();
|
|
@@ -1524,7 +1547,6 @@ export class InputController {
|
|
|
1524
1547
|
toggleThinkingBlockVisibility(): void {
|
|
1525
1548
|
this.ctx.hideThinkingBlock = !this.ctx.hideThinkingBlock;
|
|
1526
1549
|
this.ctx.settings.set("hideThinkingBlock", this.ctx.hideThinkingBlock);
|
|
1527
|
-
this.ctx.session.agent.hideThinkingSummary = this.ctx.hideThinkingBlock;
|
|
1528
1550
|
|
|
1529
1551
|
for (const child of this.ctx.chatContainer.children) {
|
|
1530
1552
|
if (child instanceof AssistantMessageComponent) {
|
|
@@ -8,7 +8,7 @@ import { type Component, replaceTabs, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
|
8
8
|
import { getMCPConfigPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { SourceMeta } from "../../capability/types";
|
|
10
10
|
import { expandEnvVarsDeep } from "../../discovery/helpers";
|
|
11
|
-
import { analyzeAuthError, discoverOAuthEndpoints, MCPManager } from "../../mcp";
|
|
11
|
+
import { analyzeAuthError, discoverOAuthEndpoints, loadAllMCPConfigs, MCPManager } from "../../mcp";
|
|
12
12
|
import { connectToServer, disconnectServer, listTools } from "../../mcp/client";
|
|
13
13
|
import {
|
|
14
14
|
addMCPServer,
|
|
@@ -1383,7 +1383,7 @@ export class MCPCommandController {
|
|
|
1383
1383
|
}
|
|
1384
1384
|
await setServerDisabled(userConfigPath, name, !enabled);
|
|
1385
1385
|
if (enabled) {
|
|
1386
|
-
await this.#
|
|
1386
|
+
await this.#connectEnabledMCPServer(name);
|
|
1387
1387
|
const state = await this.#waitForServerConnectionWithAnimation(name);
|
|
1388
1388
|
const status =
|
|
1389
1389
|
state === "connected"
|
|
@@ -1419,7 +1419,12 @@ export class MCPCommandController {
|
|
|
1419
1419
|
|
|
1420
1420
|
const updated: MCPServerConfig = { ...found.config, enabled };
|
|
1421
1421
|
await updateMCPServer(found.filePath, name, updated);
|
|
1422
|
-
|
|
1422
|
+
if (enabled) {
|
|
1423
|
+
await this.#connectEnabledMCPServer(name);
|
|
1424
|
+
} else {
|
|
1425
|
+
await this.ctx.mcpManager?.disconnectServer(name);
|
|
1426
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager?.getTools() ?? []);
|
|
1427
|
+
}
|
|
1423
1428
|
|
|
1424
1429
|
let status = "";
|
|
1425
1430
|
if (enabled) {
|
|
@@ -1671,6 +1676,37 @@ export class MCPCommandController {
|
|
|
1671
1676
|
}
|
|
1672
1677
|
}
|
|
1673
1678
|
|
|
1679
|
+
async #connectEnabledMCPServer(name: string): Promise<void> {
|
|
1680
|
+
if (!this.ctx.mcpManager) {
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
const { configs, sources } = await loadAllMCPConfigs(getProjectDir());
|
|
1685
|
+
const config = configs[name];
|
|
1686
|
+
if (!config) {
|
|
1687
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
const source = sources[name];
|
|
1692
|
+
const result = await this.ctx.mcpManager.connectServers({ [name]: config }, source ? { [name]: source } : {});
|
|
1693
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
1694
|
+
this.#showMCPConnectionErrors(result.errors);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
#showMCPConnectionErrors(errors: Map<string, string>): void {
|
|
1698
|
+
if (errors.size === 0) {
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
const errorLines = ["", theme.fg("warning", "Some servers failed to connect:"), ""];
|
|
1703
|
+
for (const [serverName, error] of errors.entries()) {
|
|
1704
|
+
errorLines.push(` ${serverName}: ${error}`);
|
|
1705
|
+
}
|
|
1706
|
+
errorLines.push("");
|
|
1707
|
+
this.#showMessage(errorLines.join("\n"));
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1674
1710
|
/**
|
|
1675
1711
|
* Reload MCP manager with new configs
|
|
1676
1712
|
*/
|
|
@@ -1686,15 +1722,7 @@ export class MCPCommandController {
|
|
|
1686
1722
|
const result = await this.ctx.mcpManager.discoverAndConnect();
|
|
1687
1723
|
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
1688
1724
|
|
|
1689
|
-
|
|
1690
|
-
if (result.errors.size > 0) {
|
|
1691
|
-
const errorLines = ["", theme.fg("warning", "Some servers failed to connect:"), ""];
|
|
1692
|
-
for (const [serverName, error] of result.errors.entries()) {
|
|
1693
|
-
errorLines.push(` ${serverName}: ${error}`);
|
|
1694
|
-
}
|
|
1695
|
-
errorLines.push("");
|
|
1696
|
-
this.#showMessage(errorLines.join("\n"));
|
|
1697
|
-
}
|
|
1725
|
+
this.#showMCPConnectionErrors(result.errors);
|
|
1698
1726
|
}
|
|
1699
1727
|
|
|
1700
1728
|
/**
|
|
@@ -71,6 +71,10 @@ import { buildCopyTargets } from "../utils/copy-targets";
|
|
|
71
71
|
|
|
72
72
|
const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect URL>.";
|
|
73
73
|
|
|
74
|
+
export function formatTemporaryModelStatus(modelLabel: string, roleSelectorHint = "Alt+M"): string {
|
|
75
|
+
return `Temporary model selection is session-only: ${modelLabel}. Use ${roleSelectorHint} or /model for role models (default/smol/plan/task/slow/custom roles).`;
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
export class SelectorController {
|
|
75
79
|
constructor(private ctx: InteractiveModeContext) {}
|
|
76
80
|
|
|
@@ -314,9 +318,8 @@ export class SelectorController {
|
|
|
314
318
|
}
|
|
315
319
|
}
|
|
316
320
|
break;
|
|
317
|
-
case "
|
|
321
|
+
case "hideThinkingBlock":
|
|
318
322
|
this.ctx.hideThinkingBlock = value as boolean;
|
|
319
|
-
this.ctx.session.agent.hideThinkingSummary = value as boolean;
|
|
320
323
|
for (const child of this.ctx.chatContainer.children) {
|
|
321
324
|
if (child instanceof AssistantMessageComponent) {
|
|
322
325
|
child.setHideThinkingBlock(value as boolean);
|
|
@@ -327,6 +330,18 @@ export class SelectorController {
|
|
|
327
330
|
// InputController.toggleThinkingBlockVisibility).
|
|
328
331
|
this.ctx.ui.resetDisplay();
|
|
329
332
|
break;
|
|
333
|
+
case "proseOnlyThinking":
|
|
334
|
+
this.ctx.proseOnlyThinking = value as boolean;
|
|
335
|
+
for (const child of this.ctx.chatContainer.children) {
|
|
336
|
+
if (child instanceof AssistantMessageComponent) {
|
|
337
|
+
child.setProseOnlyThinking(value as boolean);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
this.ctx.ui.resetDisplay();
|
|
341
|
+
break;
|
|
342
|
+
case "omitThinking":
|
|
343
|
+
this.ctx.session.agent.hideThinkingSummary = value as boolean;
|
|
344
|
+
break;
|
|
330
345
|
case "display.cacheMissMarker":
|
|
331
346
|
// Rebuild re-runs the usage-based detection under the new setting so
|
|
332
347
|
// markers appear/disappear; full reset retires any already committed
|
|
@@ -483,7 +498,8 @@ export class SelectorController {
|
|
|
483
498
|
}
|
|
484
499
|
this.ctx.statusLine.invalidate();
|
|
485
500
|
this.ctx.updateEditorBorderColor();
|
|
486
|
-
this.ctx.
|
|
501
|
+
const roleSelectorHint = this.ctx.keybindings.getKeys("app.model.select")[0] ?? "Alt+M";
|
|
502
|
+
this.ctx.showStatus(formatTemporaryModelStatus(selector ?? model.id, roleSelectorHint));
|
|
487
503
|
done();
|
|
488
504
|
this.ctx.ui.requestRender();
|
|
489
505
|
} else if (role === "default") {
|
|
@@ -817,14 +833,9 @@ export class SelectorController {
|
|
|
817
833
|
this.ctx.sessionManager.getCwd(),
|
|
818
834
|
this.ctx.sessionManager.getSessionDir(),
|
|
819
835
|
);
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
|
|
823
|
-
let startInAllScope = false;
|
|
824
|
-
if (sessions.length === 0) {
|
|
825
|
-
allSessions = await SessionManager.listAll();
|
|
826
|
-
startInAllScope = allSessions.length > 0;
|
|
827
|
-
}
|
|
836
|
+
// Always open in current-folder scope; the empty-state hint in SessionList
|
|
837
|
+
// invites the user to Tab into all-projects rather than silently surfacing
|
|
838
|
+
// every project's history when the cwd has nothing to resume. See #3099.
|
|
828
839
|
const historyStorage = this.ctx.historyStorage;
|
|
829
840
|
const historyMatcher = historyStorage ? (query: string) => historyStorage.matchingSessionIds(query) : undefined;
|
|
830
841
|
this.showSelector(done => {
|
|
@@ -858,8 +869,6 @@ export class SelectorController {
|
|
|
858
869
|
},
|
|
859
870
|
historyMatcher,
|
|
860
871
|
loadAllSessions: () => SessionManager.listAll(),
|
|
861
|
-
allSessions,
|
|
862
|
-
startInAllScope,
|
|
863
872
|
getTerminalRows: () => this.ctx.ui.terminal.rows,
|
|
864
873
|
},
|
|
865
874
|
);
|
|
@@ -1264,6 +1273,7 @@ export class SelectorController {
|
|
|
1264
1273
|
getMessageRenderer: type => this.ctx.session.extensionRunner?.getMessageRenderer(type),
|
|
1265
1274
|
cwd: this.ctx.sessionManager.getCwd(),
|
|
1266
1275
|
hideThinkingBlock: () => this.ctx.hideThinkingBlock,
|
|
1276
|
+
proseOnlyThinking: () => this.ctx.proseOnlyThinking,
|
|
1267
1277
|
focusAgent: id => this.ctx.focusAgentSession(id),
|
|
1268
1278
|
sessionFile: this.ctx.sessionManager.getSessionFile() ?? null,
|
|
1269
1279
|
});
|