@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { getSegmenter } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { LRUCache } from "lru-cache/raw";
|
|
4
|
-
import {
|
|
4
|
+
import { formatThinkingForDisplay, hasDisplayableThinking } from "../../utils/thinking-display";
|
|
5
5
|
import type { AssistantMessageComponent } from "../components/assistant-message";
|
|
6
6
|
|
|
7
7
|
export const STREAMING_REVEAL_FRAME_MS = 1000 / 30;
|
|
@@ -9,11 +9,13 @@ export const MIN_STEP = 3;
|
|
|
9
9
|
export const CATCHUP_FRAMES = 8;
|
|
10
10
|
|
|
11
11
|
type AssistantContentBlock = AssistantMessage["content"][number];
|
|
12
|
+
type DisplayThinkingContentBlock = Extract<AssistantContentBlock, { type: "thinking" }> & { rawThinking?: string };
|
|
12
13
|
type StreamingRevealComponent = Pick<AssistantMessageComponent, "updateContent">;
|
|
13
14
|
|
|
14
15
|
type StreamingRevealControllerOptions = {
|
|
15
16
|
getSmoothStreaming(): boolean;
|
|
16
17
|
getHideThinkingBlock(): boolean;
|
|
18
|
+
getProseOnlyThinking(): boolean;
|
|
17
19
|
requestRender(): void;
|
|
18
20
|
};
|
|
19
21
|
|
|
@@ -83,13 +85,16 @@ function sliceGraphemes(text: string, units: number): string {
|
|
|
83
85
|
return text;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
export function visibleUnits(message: AssistantMessage, hideThinking: boolean): number {
|
|
88
|
+
export function visibleUnits(message: AssistantMessage, hideThinking: boolean, proseOnly = true): number {
|
|
87
89
|
let total = 0;
|
|
88
90
|
for (const block of message.content) {
|
|
89
91
|
if (block.type === "text") {
|
|
90
92
|
total += countGraphemes(block.text);
|
|
91
|
-
} else if (block.type === "thinking" && !hideThinking
|
|
92
|
-
|
|
93
|
+
} else if (block.type === "thinking" && !hideThinking) {
|
|
94
|
+
const formatted = formatThinkingForDisplay(block.thinking, proseOnly);
|
|
95
|
+
if (hasDisplayableThinking(block.thinking, formatted)) {
|
|
96
|
+
total += countGraphemes(formatted);
|
|
97
|
+
}
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
return total;
|
|
@@ -119,6 +124,7 @@ export function buildDisplayMessage(
|
|
|
119
124
|
target: AssistantMessage,
|
|
120
125
|
revealed: number,
|
|
121
126
|
hideThinking: boolean,
|
|
127
|
+
proseOnly = true,
|
|
122
128
|
countOf: (index: number, text: string) => number = (_index, text) => countGraphemes(text),
|
|
123
129
|
): AssistantMessage {
|
|
124
130
|
let remaining = Math.max(0, Math.floor(revealed));
|
|
@@ -129,10 +135,20 @@ export function buildDisplayMessage(
|
|
|
129
135
|
const units = countOf(i, block.text);
|
|
130
136
|
content.push(revealTextBlock(block, remaining, units));
|
|
131
137
|
remaining = Math.max(0, remaining - units);
|
|
132
|
-
} else if (block.type === "thinking" && !hideThinking
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
} else if (block.type === "thinking" && !hideThinking) {
|
|
139
|
+
const formatted = formatThinkingForDisplay(block.thinking, proseOnly);
|
|
140
|
+
if (hasDisplayableThinking(block.thinking, formatted)) {
|
|
141
|
+
const units = countOf(i, formatted);
|
|
142
|
+
const displayBlock: DisplayThinkingContentBlock = {
|
|
143
|
+
...block,
|
|
144
|
+
thinking: formatted,
|
|
145
|
+
rawThinking: block.thinking,
|
|
146
|
+
};
|
|
147
|
+
content.push(revealThinkingBlock(displayBlock, remaining, units));
|
|
148
|
+
remaining = Math.max(0, remaining - units);
|
|
149
|
+
} else {
|
|
150
|
+
content.push(block);
|
|
151
|
+
}
|
|
136
152
|
} else {
|
|
137
153
|
content.push(block);
|
|
138
154
|
}
|
|
@@ -147,12 +163,14 @@ export function nextStep(backlog: number): number {
|
|
|
147
163
|
export class StreamingRevealController {
|
|
148
164
|
readonly #getSmoothStreaming: () => boolean;
|
|
149
165
|
readonly #getHideThinkingBlock: () => boolean;
|
|
166
|
+
readonly #getProseOnlyThinking: () => boolean;
|
|
150
167
|
readonly #requestRender: () => void;
|
|
151
168
|
#target: AssistantMessage | undefined;
|
|
152
169
|
#component: StreamingRevealComponent | undefined;
|
|
153
170
|
#timer: NodeJS.Timeout | undefined;
|
|
154
171
|
#revealed = 0;
|
|
155
172
|
#hideThinkingBlock = false;
|
|
173
|
+
#proseOnlyThinking = true;
|
|
156
174
|
#smoothStreaming = true;
|
|
157
175
|
readonly #unitCounter = new BlockUnitCounter();
|
|
158
176
|
readonly #countOf = (index: number, text: string): number => this.#unitCounter.count(index, text);
|
|
@@ -160,6 +178,7 @@ export class StreamingRevealController {
|
|
|
160
178
|
constructor(options: StreamingRevealControllerOptions) {
|
|
161
179
|
this.#getSmoothStreaming = options.getSmoothStreaming;
|
|
162
180
|
this.#getHideThinkingBlock = options.getHideThinkingBlock;
|
|
181
|
+
this.#getProseOnlyThinking = options.getProseOnlyThinking;
|
|
163
182
|
this.#requestRender = options.requestRender;
|
|
164
183
|
}
|
|
165
184
|
|
|
@@ -169,9 +188,14 @@ export class StreamingRevealController {
|
|
|
169
188
|
this.#target = message;
|
|
170
189
|
this.#revealed = 0;
|
|
171
190
|
this.#hideThinkingBlock = this.#getHideThinkingBlock();
|
|
191
|
+
this.#proseOnlyThinking = this.#getProseOnlyThinking();
|
|
172
192
|
this.#smoothStreaming = this.#getSmoothStreaming();
|
|
173
193
|
if (!this.#smoothStreaming) {
|
|
174
|
-
|
|
194
|
+
const total = this.#visibleUnits(message);
|
|
195
|
+
component.updateContent(
|
|
196
|
+
buildDisplayMessage(message, total, this.#hideThinkingBlock, this.#proseOnlyThinking, this.#countOf),
|
|
197
|
+
{ transient: true },
|
|
198
|
+
);
|
|
175
199
|
return;
|
|
176
200
|
}
|
|
177
201
|
const total = this.#visibleUnits(message);
|
|
@@ -179,9 +203,18 @@ export class StreamingRevealController {
|
|
|
179
203
|
// A tool call is a transcript-order boundary: finish any leading
|
|
180
204
|
// assistant text before EventController renders the separate tool card.
|
|
181
205
|
this.#revealed = total;
|
|
182
|
-
component.updateContent(
|
|
183
|
-
|
|
184
|
-
|
|
206
|
+
component.updateContent(
|
|
207
|
+
buildDisplayMessage(
|
|
208
|
+
message,
|
|
209
|
+
this.#revealed,
|
|
210
|
+
this.#hideThinkingBlock,
|
|
211
|
+
this.#proseOnlyThinking,
|
|
212
|
+
this.#countOf,
|
|
213
|
+
),
|
|
214
|
+
{
|
|
215
|
+
transient: true,
|
|
216
|
+
},
|
|
217
|
+
);
|
|
185
218
|
return;
|
|
186
219
|
}
|
|
187
220
|
this.#renderCurrent();
|
|
@@ -190,9 +223,16 @@ export class StreamingRevealController {
|
|
|
190
223
|
|
|
191
224
|
setTarget(message: AssistantMessage): void {
|
|
192
225
|
this.#target = message;
|
|
226
|
+
this.#hideThinkingBlock = this.#getHideThinkingBlock();
|
|
227
|
+
this.#proseOnlyThinking = this.#getProseOnlyThinking();
|
|
228
|
+
this.#smoothStreaming = this.#getSmoothStreaming();
|
|
193
229
|
if (!this.#component) return;
|
|
194
230
|
if (!this.#smoothStreaming) {
|
|
195
|
-
this.#
|
|
231
|
+
const total = this.#visibleUnits(message);
|
|
232
|
+
this.#component.updateContent(
|
|
233
|
+
buildDisplayMessage(message, total, this.#hideThinkingBlock, this.#proseOnlyThinking, this.#countOf),
|
|
234
|
+
{ transient: true },
|
|
235
|
+
);
|
|
196
236
|
return;
|
|
197
237
|
}
|
|
198
238
|
const total = this.#visibleUnits(message);
|
|
@@ -202,7 +242,13 @@ export class StreamingRevealController {
|
|
|
202
242
|
this.#revealed = total;
|
|
203
243
|
this.#stopTimer();
|
|
204
244
|
this.#component.updateContent(
|
|
205
|
-
buildDisplayMessage(
|
|
245
|
+
buildDisplayMessage(
|
|
246
|
+
message,
|
|
247
|
+
this.#revealed,
|
|
248
|
+
this.#hideThinkingBlock,
|
|
249
|
+
this.#proseOnlyThinking,
|
|
250
|
+
this.#countOf,
|
|
251
|
+
),
|
|
206
252
|
{
|
|
207
253
|
transient: true,
|
|
208
254
|
},
|
|
@@ -231,8 +277,11 @@ export class StreamingRevealController {
|
|
|
231
277
|
const block = message.content[i]!;
|
|
232
278
|
if (block.type === "text") {
|
|
233
279
|
total += this.#unitCounter.count(i, block.text);
|
|
234
|
-
} else if (block.type === "thinking" && !this.#hideThinkingBlock
|
|
235
|
-
|
|
280
|
+
} else if (block.type === "thinking" && !this.#hideThinkingBlock) {
|
|
281
|
+
const formatted = formatThinkingForDisplay(block.thinking, this.#proseOnlyThinking);
|
|
282
|
+
if (hasDisplayableThinking(block.thinking, formatted)) {
|
|
283
|
+
total += this.#unitCounter.count(i, formatted);
|
|
284
|
+
}
|
|
236
285
|
}
|
|
237
286
|
}
|
|
238
287
|
return total;
|
|
@@ -244,7 +293,13 @@ export class StreamingRevealController {
|
|
|
244
293
|
// smooth reveal has temporarily caught up to the current target. The
|
|
245
294
|
// message_end handler performs the only stable non-transient render.
|
|
246
295
|
this.#component.updateContent(
|
|
247
|
-
buildDisplayMessage(
|
|
296
|
+
buildDisplayMessage(
|
|
297
|
+
this.#target,
|
|
298
|
+
this.#revealed,
|
|
299
|
+
this.#hideThinkingBlock,
|
|
300
|
+
this.#proseOnlyThinking,
|
|
301
|
+
this.#countOf,
|
|
302
|
+
),
|
|
248
303
|
{ transient: true },
|
|
249
304
|
);
|
|
250
305
|
}
|
|
@@ -284,9 +339,12 @@ export class StreamingRevealController {
|
|
|
284
339
|
return;
|
|
285
340
|
}
|
|
286
341
|
this.#revealed = Math.min(total, this.#revealed + nextStep(total - this.#revealed));
|
|
287
|
-
component.updateContent(
|
|
288
|
-
|
|
289
|
-
|
|
342
|
+
component.updateContent(
|
|
343
|
+
buildDisplayMessage(target, this.#revealed, this.#hideThinkingBlock, this.#proseOnlyThinking, this.#countOf),
|
|
344
|
+
{
|
|
345
|
+
transient: true,
|
|
346
|
+
},
|
|
347
|
+
);
|
|
290
348
|
this.#requestRender();
|
|
291
349
|
if (this.#revealed >= total) {
|
|
292
350
|
this.#stopTimer();
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import { resolveToCwd } from "../../tools/path-utils";
|
|
3
2
|
import {
|
|
4
3
|
applyOpsToPhases,
|
|
5
4
|
getLatestTodoPhasesFromEntries,
|
|
6
5
|
markdownToPhases,
|
|
7
6
|
phasesToMarkdown,
|
|
7
|
+
resolveTodoMarkdownPath,
|
|
8
8
|
type TodoItem,
|
|
9
9
|
type TodoPhase,
|
|
10
10
|
USER_TODO_EDIT_CUSTOM_TYPE,
|
|
@@ -18,8 +18,8 @@ const USAGE = [
|
|
|
18
18
|
" /todo Show current todos",
|
|
19
19
|
" /todo edit Open todos in $EDITOR",
|
|
20
20
|
" /todo copy Copy todos as Markdown to clipboard",
|
|
21
|
-
" /todo export <path>
|
|
22
|
-
" /todo import <path>
|
|
21
|
+
" /todo export [<path>] Write todos to file (default: TODO.md)",
|
|
22
|
+
" /todo import [<path>] Replace todos from file (default: TODO.md)",
|
|
23
23
|
" /todo append [<phase>] <task...> Append a task; phase fuzzy-matched or auto-created",
|
|
24
24
|
" /todo start <task> Mark task in_progress (fuzzy content match)",
|
|
25
25
|
" /todo done [<task|phase>] Mark task/phase/all completed",
|
|
@@ -214,9 +214,7 @@ export class TodoCommandController {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
#resolveTodoPath(rest: string): string {
|
|
217
|
-
|
|
218
|
-
const raw = trimmed || "TODO.md";
|
|
219
|
-
return resolveToCwd(raw, this.ctx.sessionManager.getCwd());
|
|
217
|
+
return resolveTodoMarkdownPath(rest, this.ctx.sessionManager.getCwd());
|
|
220
218
|
}
|
|
221
219
|
|
|
222
220
|
async #exportToFile(rest: string): Promise<void> {
|
|
@@ -225,22 +223,23 @@ export class TodoCommandController {
|
|
|
225
223
|
this.ctx.showWarning("No todos to export.");
|
|
226
224
|
return;
|
|
227
225
|
}
|
|
228
|
-
const target = this.#resolveTodoPath(rest);
|
|
229
226
|
try {
|
|
227
|
+
const target = this.#resolveTodoPath(rest);
|
|
230
228
|
await fs.writeFile(target, phasesToMarkdown(phases), "utf8");
|
|
231
229
|
this.ctx.showStatus(`Wrote todos to ${target}`);
|
|
232
230
|
} catch (error) {
|
|
233
|
-
this.ctx.showError(`Failed to write
|
|
231
|
+
this.ctx.showError(`Failed to write todos: ${error instanceof Error ? error.message : String(error)}`);
|
|
234
232
|
}
|
|
235
233
|
}
|
|
236
234
|
|
|
237
235
|
async #importFromFile(rest: string): Promise<void> {
|
|
238
|
-
|
|
236
|
+
let source = "";
|
|
239
237
|
let content: string;
|
|
240
238
|
try {
|
|
239
|
+
source = this.#resolveTodoPath(rest);
|
|
241
240
|
content = await fs.readFile(source, "utf8");
|
|
242
241
|
} catch (error) {
|
|
243
|
-
this.ctx.showError(`Failed to read
|
|
242
|
+
this.ctx.showError(`Failed to read todos: ${error instanceof Error ? error.message : String(error)}`);
|
|
244
243
|
return;
|
|
245
244
|
}
|
|
246
245
|
const { phases, errors } = markdownToPhases(content);
|
|
@@ -70,7 +70,12 @@ import type { Goal, GoalModeState } from "../goals/state";
|
|
|
70
70
|
import { resolveLocalUrlToPath } from "../internal-urls";
|
|
71
71
|
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
|
|
72
72
|
import type { MCPManager } from "../mcp";
|
|
73
|
-
import {
|
|
73
|
+
import {
|
|
74
|
+
formatMCPConnectionStatusMessage,
|
|
75
|
+
isMcpConnectionStatusEvent,
|
|
76
|
+
MCP_CONNECTION_STATUS_EVENT_CHANNEL,
|
|
77
|
+
type McpConnectionStatusEvent,
|
|
78
|
+
} from "../mcp/startup-events";
|
|
74
79
|
import {
|
|
75
80
|
humanizePlanTitle,
|
|
76
81
|
type PlanApprovalDetails,
|
|
@@ -88,7 +93,7 @@ import type { SessionContext } from "../session/session-context";
|
|
|
88
93
|
import { getRecentSessions } from "../session/session-listing";
|
|
89
94
|
import type { SessionManager } from "../session/session-manager";
|
|
90
95
|
import type { ShakeMode } from "../session/shake-types";
|
|
91
|
-
import { BUILTIN_SLASH_COMMAND_RESERVED_NAMES,
|
|
96
|
+
import { BUILTIN_SLASH_COMMAND_RESERVED_NAMES, buildTuiBuiltinSlashCommands } from "../slash-commands/builtin-registry";
|
|
92
97
|
import { formatDuration } from "../slash-commands/helpers/format";
|
|
93
98
|
import { STTController, type SttState } from "../stt";
|
|
94
99
|
import { discoverTitleSystemPromptFile, resolvePromptInput } from "../system-prompt";
|
|
@@ -401,6 +406,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
401
406
|
#modelCycleClearTimer: NodeJS.Timeout | undefined;
|
|
402
407
|
todoPhases: TodoPhase[] = [];
|
|
403
408
|
hideThinkingBlock = false;
|
|
409
|
+
proseOnlyThinking = true;
|
|
404
410
|
pendingImages: ImageContent[] = [];
|
|
405
411
|
pendingImageLinks: (string | undefined)[] = [];
|
|
406
412
|
compactionQueuedMessages: CompactionQueuedMessage[] = [];
|
|
@@ -506,6 +512,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
506
512
|
this.loadingAnimation.stop();
|
|
507
513
|
this.loadingAnimation = undefined;
|
|
508
514
|
}
|
|
515
|
+
if (this.autoCompactionLoader) {
|
|
516
|
+
this.autoCompactionLoader.stop();
|
|
517
|
+
this.autoCompactionLoader = undefined;
|
|
518
|
+
}
|
|
519
|
+
if (this.retryLoader) {
|
|
520
|
+
this.retryLoader.stop();
|
|
521
|
+
this.retryLoader = undefined;
|
|
522
|
+
}
|
|
509
523
|
this.statusContainer.clear();
|
|
510
524
|
this.pendingMessagesContainer.clear();
|
|
511
525
|
this.#cancelModelCycleClearTimer();
|
|
@@ -526,6 +540,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
526
540
|
#observerRegistry: SessionObserverRegistry;
|
|
527
541
|
#eventBus?: EventBus;
|
|
528
542
|
#eventBusUnsubscribers: Array<() => void> = [];
|
|
543
|
+
#mcpStatusOrder: string[] = [];
|
|
544
|
+
#mcpPendingServers = new Set<string>();
|
|
545
|
+
#mcpConnectedServers = new Set<string>();
|
|
546
|
+
#mcpFailedServers = new Map<string, string>();
|
|
529
547
|
#welcomeComponent?: WelcomeComponent;
|
|
530
548
|
readonly #chatHost: ChatBlockHost = { requestRender: () => this.ui.requestRender() };
|
|
531
549
|
|
|
@@ -559,13 +577,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
559
577
|
}),
|
|
560
578
|
);
|
|
561
579
|
this.#eventBusUnsubscribers.push(
|
|
562
|
-
eventBus.on(
|
|
563
|
-
if (!
|
|
564
|
-
logger.warn("Ignoring malformed mcp:
|
|
580
|
+
eventBus.on(MCP_CONNECTION_STATUS_EVENT_CHANNEL, data => {
|
|
581
|
+
if (!isMcpConnectionStatusEvent(data)) {
|
|
582
|
+
logger.warn("Ignoring malformed mcp:connection-status event", { data });
|
|
565
583
|
return;
|
|
566
584
|
}
|
|
567
|
-
|
|
568
|
-
this.showStatus(formatMCPConnectingMessage(data.serverNames));
|
|
585
|
+
this.#handleMcpConnectionStatusEvent(data);
|
|
569
586
|
}),
|
|
570
587
|
);
|
|
571
588
|
}
|
|
@@ -618,6 +635,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
618
635
|
this.statusLine.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
619
636
|
|
|
620
637
|
this.hideThinkingBlock = settings.get("hideThinkingBlock");
|
|
638
|
+
this.proseOnlyThinking = settings.get("proseOnlyThinking");
|
|
621
639
|
|
|
622
640
|
const hookCommands: SlashCommand[] = (
|
|
623
641
|
this.session.extensionRunner?.getRegisteredCommands(BUILTIN_SLASH_COMMAND_RESERVED_NAMES) ?? []
|
|
@@ -643,8 +661,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
643
661
|
}
|
|
644
662
|
}
|
|
645
663
|
|
|
664
|
+
const builtinCommands = buildTuiBuiltinSlashCommands({ ctx: this });
|
|
646
665
|
// Store pending commands for init() where file commands are loaded async
|
|
647
|
-
this.#pendingSlashCommands = [...
|
|
666
|
+
this.#pendingSlashCommands = [...builtinCommands, ...hookCommands, ...customCommands, ...skillCommandList];
|
|
648
667
|
|
|
649
668
|
this.#uiHelpers = new UiHelpers(this);
|
|
650
669
|
this.#btwController = new BtwController(this);
|
|
@@ -660,6 +679,54 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
660
679
|
this.#observerRegistry = new SessionObserverRegistry();
|
|
661
680
|
}
|
|
662
681
|
|
|
682
|
+
#handleMcpConnectionStatusEvent(event: McpConnectionStatusEvent): void {
|
|
683
|
+
if (this.settings.get("startup.quiet")) return;
|
|
684
|
+
if (event.type === "connecting") {
|
|
685
|
+
this.#mcpStatusOrder = [];
|
|
686
|
+
this.#mcpPendingServers.clear();
|
|
687
|
+
this.#mcpConnectedServers.clear();
|
|
688
|
+
this.#mcpFailedServers.clear();
|
|
689
|
+
for (const serverName of event.serverNames) {
|
|
690
|
+
this.#trackMcpStatusServer(serverName);
|
|
691
|
+
this.#mcpPendingServers.add(serverName);
|
|
692
|
+
}
|
|
693
|
+
} else if (event.type === "connected") {
|
|
694
|
+
this.#trackMcpStatusServer(event.serverName);
|
|
695
|
+
this.#mcpPendingServers.delete(event.serverName);
|
|
696
|
+
this.#mcpFailedServers.delete(event.serverName);
|
|
697
|
+
this.#mcpConnectedServers.add(event.serverName);
|
|
698
|
+
} else {
|
|
699
|
+
this.#trackMcpStatusServer(event.serverName);
|
|
700
|
+
this.#mcpPendingServers.delete(event.serverName);
|
|
701
|
+
this.#mcpConnectedServers.delete(event.serverName);
|
|
702
|
+
this.#mcpFailedServers.set(event.serverName, event.error);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const message = formatMCPConnectionStatusMessage({
|
|
706
|
+
pendingServers: this.#orderedMcpStatusServers(this.#mcpPendingServers),
|
|
707
|
+
connectedServers: this.#orderedMcpStatusServers(this.#mcpConnectedServers),
|
|
708
|
+
failedServers: this.#orderedMcpStatusFailures(),
|
|
709
|
+
});
|
|
710
|
+
if (message) this.showStatus(message);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
#trackMcpStatusServer(serverName: string): void {
|
|
714
|
+
if (!this.#mcpStatusOrder.includes(serverName)) {
|
|
715
|
+
this.#mcpStatusOrder.push(serverName);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
#orderedMcpStatusServers(servers: ReadonlySet<string>): string[] {
|
|
720
|
+
return this.#mcpStatusOrder.filter(serverName => servers.has(serverName));
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
#orderedMcpStatusFailures(): Array<{ serverName: string; error: string }> {
|
|
724
|
+
return this.#mcpStatusOrder.flatMap(serverName => {
|
|
725
|
+
const error = this.#mcpFailedServers.get(serverName);
|
|
726
|
+
return error === undefined ? [] : [{ serverName, error }];
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
663
730
|
playWelcomeIntro(): void {
|
|
664
731
|
const welcome = this.#welcomeComponent;
|
|
665
732
|
// Component-scoped: the intro only mutates the welcome box's own rows,
|
|
@@ -3424,7 +3491,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3424
3491
|
return this.#commandController.handleExportCommand(text);
|
|
3425
3492
|
}
|
|
3426
3493
|
|
|
3427
|
-
handleDumpCommand() {
|
|
3494
|
+
async handleDumpCommand(): Promise<void> {
|
|
3428
3495
|
return this.#commandController.handleDumpCommand();
|
|
3429
3496
|
}
|
|
3430
3497
|
|
|
@@ -3769,6 +3836,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3769
3836
|
return this.#btwController.handleBranch();
|
|
3770
3837
|
}
|
|
3771
3838
|
|
|
3839
|
+
canCopyBtw(): boolean {
|
|
3840
|
+
return this.#btwController.canCopy();
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
handleBtwCopyKey(): Promise<boolean> {
|
|
3844
|
+
return this.#btwController.handleCopy();
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3772
3847
|
async handleBtwBranch(question: string, assistantMessage: AssistantMessage): Promise<void> {
|
|
3773
3848
|
try {
|
|
3774
3849
|
const result = await this.session.branchFromBtw(question, assistantMessage);
|
package/src/modes/types.ts
CHANGED
|
@@ -148,6 +148,7 @@ export interface InteractiveModeContext {
|
|
|
148
148
|
loopLimit?: LoopLimitRuntime;
|
|
149
149
|
planModePlanFilePath?: string;
|
|
150
150
|
hideThinkingBlock: boolean;
|
|
151
|
+
proseOnlyThinking: boolean;
|
|
151
152
|
pendingImages: ImageContent[];
|
|
152
153
|
pendingImageLinks: (string | undefined)[];
|
|
153
154
|
compactionQueuedMessages: CompactionQueuedMessage[];
|
|
@@ -289,7 +290,7 @@ export interface InteractiveModeContext {
|
|
|
289
290
|
handleHotkeysCommand(): void;
|
|
290
291
|
handleToolsCommand(): void;
|
|
291
292
|
handleContextCommand(): void;
|
|
292
|
-
handleDumpCommand(): void
|
|
293
|
+
handleDumpCommand(): Promise<void>;
|
|
293
294
|
handleAdvisorDumpCommand(isRaw?: boolean): void;
|
|
294
295
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
295
296
|
handleClearCommand(): Promise<void>;
|
|
@@ -348,6 +349,8 @@ export interface InteractiveModeContext {
|
|
|
348
349
|
handleBtwEscape(): boolean;
|
|
349
350
|
handleBtwBranchKey(): Promise<boolean>;
|
|
350
351
|
canBranchBtw(): boolean;
|
|
352
|
+
canCopyBtw(): boolean;
|
|
353
|
+
handleBtwCopyKey(): Promise<boolean>;
|
|
351
354
|
handleBtwBranch(question: string, assistantMessage: AssistantMessage): Promise<void>;
|
|
352
355
|
handleOmfgCommand(complaint: string): Promise<void>;
|
|
353
356
|
hasActiveOmfg(): boolean;
|
|
@@ -332,6 +332,7 @@ export class UiHelpers {
|
|
|
332
332
|
() => this.ctx.ui.requestRender(),
|
|
333
333
|
this.ctx.viewSession.extensionRunner?.getAssistantThinkingRenderers(),
|
|
334
334
|
this.ctx.ui.imageBudget,
|
|
335
|
+
this.ctx.proseOnlyThinking,
|
|
335
336
|
);
|
|
336
337
|
this.ctx.chatContainer.addChild(assistantComponent);
|
|
337
338
|
break;
|
|
@@ -24,7 +24,7 @@ Keep exploration lean:
|
|
|
24
24
|
- Offer alternatives, not lectures.
|
|
25
25
|
- NEVER restate information the agent already has, including errors they have seen.
|
|
26
26
|
- Examples: type errors, LSP diagnostics, failed builds, failing tests, lint.
|
|
27
|
-
- NEVER repeat advice you already gave.
|
|
27
|
+
- NEVER repeat advice you already gave, and NEVER send the same advice twice.
|
|
28
28
|
- NEVER nitpick about things user stated they are okay with. You are the advocate for the user.
|
|
29
29
|
</communication>
|
|
30
30
|
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<system-reminder>
|
|
2
|
+
This is an ephemeral side-channel turn that reuses the current conversation's context. The tool catalog stays attached only to keep the prompt cache warm — tools are NOT available on this turn. Do NOT emit any tool call; reply with plain text only. Any tool call you produce is discarded without executing.
|
|
3
|
+
</system-reminder>
|
package/src/sdk.ts
CHANGED
|
@@ -87,7 +87,7 @@ import {
|
|
|
87
87
|
type MCPToolsLoadResult,
|
|
88
88
|
parseMCPToolName,
|
|
89
89
|
} from "./mcp";
|
|
90
|
-
import {
|
|
90
|
+
import { MCP_CONNECTION_STATUS_EVENT_CHANNEL, type McpConnectionStatusEvent } from "./mcp/startup-events";
|
|
91
91
|
import { createSessionMemoryRuntimeContext, resolveMemoryBackend } from "./memory-backend";
|
|
92
92
|
import type { MnemopiSessionState } from "./mnemopi/state";
|
|
93
93
|
import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
|
|
@@ -805,6 +805,7 @@ export interface BuildSystemPromptOptions {
|
|
|
805
805
|
* as separate entries so providers can cache prompt prefixes without concatenating blocks.
|
|
806
806
|
*/
|
|
807
807
|
export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<BuildSystemPromptResult> {
|
|
808
|
+
const toolMap = options.tools ? new Map(options.tools.map(tool => [tool.name, tool])) : undefined;
|
|
808
809
|
return await buildSystemPromptInternal({
|
|
809
810
|
cwd: options.cwd,
|
|
810
811
|
customPrompt: options.customPrompt,
|
|
@@ -812,6 +813,8 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
812
813
|
contextFiles: options.contextFiles,
|
|
813
814
|
appendSystemPrompt: options.appendPrompt,
|
|
814
815
|
inlineToolDescriptors: options.inlineToolDescriptors,
|
|
816
|
+
toolNames: options.tools?.map(tool => tool.name),
|
|
817
|
+
tools: toolMap ? buildSystemPromptToolMetadata(toolMap) : undefined,
|
|
815
818
|
});
|
|
816
819
|
}
|
|
817
820
|
|
|
@@ -1617,12 +1620,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1617
1620
|
| ((liveSession: AgentSession, activation: DeferredMCPActivation) => void)
|
|
1618
1621
|
| undefined;
|
|
1619
1622
|
const startupQuiet = settings.get("startup.quiet");
|
|
1620
|
-
const
|
|
1621
|
-
if (!options.hasUI || startupQuiet
|
|
1622
|
-
|
|
1623
|
+
const onMCPStatus = (event: McpConnectionStatusEvent) => {
|
|
1624
|
+
if (!options.hasUI || startupQuiet) return;
|
|
1625
|
+
if (event.type === "connecting" && event.serverNames.length === 0) return;
|
|
1626
|
+
eventBus.emit(MCP_CONNECTION_STATUS_EVENT_CHANNEL, event);
|
|
1623
1627
|
};
|
|
1624
1628
|
const mcpDiscoverOptions = {
|
|
1625
|
-
|
|
1629
|
+
onStatus: onMCPStatus,
|
|
1626
1630
|
enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true,
|
|
1627
1631
|
// Always filter Exa - we have native integration
|
|
1628
1632
|
filterExa: true,
|
|
@@ -2412,9 +2416,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2412
2416
|
: undefined;
|
|
2413
2417
|
const transformProviderContext =
|
|
2414
2418
|
obfuscator || snapcompactInline
|
|
2415
|
-
? (context: Context, transformModel: Model): Context => {
|
|
2419
|
+
? async (context: Context, transformModel: Model): Promise<Context> => {
|
|
2416
2420
|
let transformed = obfuscator ? obfuscateProviderContext(obfuscator, context) : context;
|
|
2417
|
-
if (snapcompactInline) transformed = snapcompactInline.transform(transformed, transformModel);
|
|
2421
|
+
if (snapcompactInline) transformed = await snapcompactInline.transform(transformed, transformModel);
|
|
2418
2422
|
return transformed;
|
|
2419
2423
|
}
|
|
2420
2424
|
: undefined;
|
|
@@ -2474,7 +2478,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2474
2478
|
presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
|
|
2475
2479
|
repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
|
|
2476
2480
|
serviceTier: initialServiceTier,
|
|
2477
|
-
hideThinkingSummary: settings.get("
|
|
2481
|
+
hideThinkingSummary: settings.get("omitThinking"),
|
|
2478
2482
|
kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
|
|
2479
2483
|
preferWebsockets: preferOpenAICodexWebsockets,
|
|
2480
2484
|
getToolContext: tc => toolContextStore.getContext(tc),
|