@oh-my-pi/pi-coding-agent 11.8.2 → 11.9.0
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 +42 -0
- package/docs/tui.md +9 -9
- package/package.json +7 -7
- package/src/capability/mcp.ts +9 -0
- package/src/cli/file-processor.ts +8 -13
- package/src/cli/oclif-help.ts +1 -1
- package/src/cli.ts +14 -0
- package/src/commit/git/index.ts +16 -16
- package/src/config/file-lock.ts +1 -1
- package/src/config/keybindings.ts +11 -11
- package/src/config/model-registry.ts +31 -66
- package/src/config/settings.ts +88 -95
- package/src/config.ts +2 -2
- package/src/cursor.ts +4 -4
- package/src/debug/index.ts +28 -28
- package/src/discovery/builtin.ts +48 -0
- package/src/discovery/codex.ts +5 -13
- package/src/discovery/cursor.ts +2 -7
- package/src/discovery/mcp-json.ts +33 -0
- package/src/exa/mcp-client.ts +2 -2
- package/src/exa/websets.ts +2 -2
- package/src/export/html/index.ts +3 -3
- package/src/export/ttsr.ts +27 -27
- package/src/extensibility/custom-tools/loader.ts +9 -9
- package/src/extensibility/extensions/runner.ts +64 -64
- package/src/extensibility/hooks/runner.ts +46 -46
- package/src/extensibility/plugins/manager.ts +49 -49
- package/src/extensibility/slash-commands.ts +1 -0
- package/src/index.ts +0 -3
- package/src/internal-urls/router.ts +5 -5
- package/src/ipy/kernel.ts +61 -57
- package/src/lsp/client.ts +1 -1
- package/src/lsp/clients/biome-client.ts +2 -2
- package/src/lsp/clients/lsp-linter-client.ts +7 -7
- package/src/lsp/index.ts +9 -9
- package/src/mcp/config-writer.ts +194 -0
- package/src/mcp/config.ts +20 -6
- package/src/mcp/index.ts +4 -0
- package/src/mcp/loader.ts +6 -0
- package/src/mcp/manager.ts +139 -50
- package/src/mcp/oauth-discovery.ts +274 -0
- package/src/mcp/oauth-flow.ts +229 -0
- package/src/mcp/tool-bridge.ts +20 -20
- package/src/mcp/transports/http.ts +107 -66
- package/src/mcp/transports/stdio.ts +74 -59
- package/src/mcp/types.ts +15 -1
- package/src/modes/components/assistant-message.ts +25 -25
- package/src/modes/components/bash-execution.ts +51 -51
- package/src/modes/components/bordered-loader.ts +7 -7
- package/src/modes/components/branch-summary-message.ts +7 -7
- package/src/modes/components/compaction-summary-message.ts +7 -7
- package/src/modes/components/countdown-timer.ts +15 -15
- package/src/modes/components/custom-editor.ts +22 -22
- package/src/modes/components/custom-message.ts +21 -21
- package/src/modes/components/dynamic-border.ts +3 -3
- package/src/modes/components/extensions/extension-dashboard.ts +72 -72
- package/src/modes/components/extensions/extension-list.ts +99 -97
- package/src/modes/components/extensions/inspector-panel.ts +26 -26
- package/src/modes/components/footer.ts +36 -36
- package/src/modes/components/history-search.ts +52 -52
- package/src/modes/components/hook-editor.ts +20 -20
- package/src/modes/components/hook-input.ts +20 -20
- package/src/modes/components/hook-message.ts +22 -22
- package/src/modes/components/hook-selector.ts +52 -52
- package/src/modes/components/index.ts +0 -1
- package/src/modes/components/login-dialog.ts +57 -57
- package/src/modes/components/mcp-add-wizard.ts +1286 -0
- package/src/modes/components/model-selector.ts +173 -173
- package/src/modes/components/oauth-selector.ts +45 -45
- package/src/modes/components/plugin-settings.ts +52 -52
- package/src/modes/components/python-execution.ts +53 -53
- package/src/modes/components/queue-mode-selector.ts +7 -7
- package/src/modes/components/read-tool-group.ts +23 -23
- package/src/modes/components/session-selector.ts +40 -37
- package/src/modes/components/settings-selector.ts +80 -80
- package/src/modes/components/show-images-selector.ts +7 -7
- package/src/modes/components/skill-message.ts +27 -27
- package/src/modes/components/status-line-segment-editor.ts +81 -81
- package/src/modes/components/status-line.ts +73 -73
- package/src/modes/components/theme-selector.ts +11 -11
- package/src/modes/components/thinking-selector.ts +7 -7
- package/src/modes/components/todo-display.ts +19 -19
- package/src/modes/components/todo-reminder.ts +9 -9
- package/src/modes/components/tool-execution.ts +212 -216
- package/src/modes/components/tree-selector.ts +144 -144
- package/src/modes/components/ttsr-notification.ts +17 -17
- package/src/modes/components/user-message-selector.ts +18 -18
- package/src/modes/components/welcome.ts +10 -10
- package/src/modes/controllers/command-controller.ts +0 -7
- package/src/modes/controllers/event-controller.ts +23 -23
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +12 -9
- package/src/modes/controllers/mcp-command-controller.ts +1223 -0
- package/src/modes/interactive-mode.ts +240 -241
- package/src/modes/rpc/rpc-client.ts +77 -77
- package/src/modes/rpc/rpc-mode.ts +5 -5
- package/src/modes/theme/theme.ts +113 -113
- package/src/modes/types.ts +1 -1
- package/src/patch/index.ts +45 -45
- package/src/prompts/tools/task.md +22 -2
- package/src/sdk.ts +1 -0
- package/src/session/agent-session.ts +512 -476
- package/src/session/agent-storage.ts +72 -75
- package/src/session/auth-storage.ts +186 -252
- package/src/session/history-storage.ts +36 -38
- package/src/session/session-manager.ts +300 -299
- package/src/session/session-storage.ts +65 -90
- package/src/ssh/connection-manager.ts +9 -9
- package/src/system-prompt.ts +2 -3
- package/src/task/agents.ts +1 -1
- package/src/task/executor.ts +28 -40
- package/src/task/index.ts +13 -12
- package/src/task/subprocess-tool-registry.ts +5 -5
- package/src/task/worktree.ts +8 -5
- package/src/tools/ask.ts +7 -7
- package/src/tools/bash.ts +15 -10
- package/src/tools/browser.ts +130 -127
- package/src/tools/calculator.ts +46 -46
- package/src/tools/context.ts +9 -9
- package/src/tools/exit-plan-mode.ts +5 -5
- package/src/tools/fetch.ts +5 -5
- package/src/tools/find.ts +16 -16
- package/src/tools/grep.ts +12 -24
- package/src/tools/index.ts +1 -1
- package/src/tools/notebook.ts +6 -6
- package/src/tools/output-meta.ts +10 -2
- package/src/tools/python.ts +12 -11
- package/src/tools/read.ts +17 -17
- package/src/tools/ssh.ts +9 -9
- package/src/tools/submit-result.ts +13 -13
- package/src/tools/todo-write.ts +6 -6
- package/src/tools/write.ts +10 -10
- package/src/tui/output-block.ts +6 -6
- package/src/tui/utils.ts +9 -9
- package/src/utils/event-bus.ts +13 -11
- package/src/utils/frontmatter.ts +1 -1
- package/src/utils/ignore-files.ts +1 -1
- package/src/web/search/index.ts +5 -5
- package/src/web/search/providers/anthropic.ts +7 -2
- package/examples/hooks/snake.ts +0 -342
- package/src/modes/components/armin.ts +0 -379
|
@@ -7,8 +7,8 @@ import { theme } from "../../modes/theme/theme";
|
|
|
7
7
|
* Shows when a rule violation is detected and the stream is being rewound.
|
|
8
8
|
*/
|
|
9
9
|
export class TtsrNotificationComponent extends Container {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
#box: Box;
|
|
11
|
+
#expanded = false;
|
|
12
12
|
|
|
13
13
|
constructor(private readonly rules: Rule[]) {
|
|
14
14
|
super();
|
|
@@ -16,25 +16,25 @@ export class TtsrNotificationComponent extends Container {
|
|
|
16
16
|
this.addChild(new Spacer(1));
|
|
17
17
|
|
|
18
18
|
// Use inverse warning color for yellow background effect
|
|
19
|
-
this
|
|
20
|
-
this.addChild(this
|
|
19
|
+
this.#box = new Box(1, 1, t => theme.inverse(theme.fg("warning", t)));
|
|
20
|
+
this.addChild(this.#box);
|
|
21
21
|
|
|
22
|
-
this
|
|
22
|
+
this.#rebuild();
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
setExpanded(expanded: boolean): void {
|
|
26
|
-
if (this
|
|
27
|
-
this
|
|
28
|
-
this
|
|
26
|
+
if (this.#expanded !== expanded) {
|
|
27
|
+
this.#expanded = expanded;
|
|
28
|
+
this.#rebuild();
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
isExpanded(): boolean {
|
|
33
|
-
return this
|
|
33
|
+
return this.#expanded;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
this
|
|
36
|
+
#rebuild(): void {
|
|
37
|
+
this.#box.clear();
|
|
38
38
|
|
|
39
39
|
// Build header: warning symbol + rule name + rewind icon
|
|
40
40
|
const ruleNames = this.rules.map(r => theme.bold(r.name)).join(", ");
|
|
@@ -44,16 +44,16 @@ export class TtsrNotificationComponent extends Container {
|
|
|
44
44
|
// Create header with rewind icon on the right
|
|
45
45
|
const rewindIcon = theme.icon.rewind;
|
|
46
46
|
|
|
47
|
-
this
|
|
47
|
+
this.#box.addChild(new Text(`${header} ${rewindIcon}`, 0, 0));
|
|
48
48
|
|
|
49
49
|
// Show description(s) - italic and truncated
|
|
50
50
|
for (const rule of this.rules) {
|
|
51
51
|
const desc = rule.description || rule.content;
|
|
52
52
|
if (desc) {
|
|
53
|
-
this
|
|
53
|
+
this.#box.addChild(new Spacer(1));
|
|
54
54
|
|
|
55
55
|
let displayText = desc.trim();
|
|
56
|
-
if (!this
|
|
56
|
+
if (!this.#expanded) {
|
|
57
57
|
// Truncate to first 2 lines
|
|
58
58
|
const lines = displayText.split("\n");
|
|
59
59
|
if (lines.length > 2) {
|
|
@@ -62,18 +62,18 @@ export class TtsrNotificationComponent extends Container {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Use italic for subtle distinction (fg colors conflict with inverse)
|
|
65
|
-
this
|
|
65
|
+
this.#box.addChild(new Text(theme.italic(displayText), 0, 0));
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// Show expand hint if collapsed and there's more content
|
|
70
|
-
if (!this
|
|
70
|
+
if (!this.#expanded) {
|
|
71
71
|
const hasMoreContent = this.rules.some(r => {
|
|
72
72
|
const desc = r.description || r.content;
|
|
73
73
|
return desc && desc.split("\n").length > 2;
|
|
74
74
|
});
|
|
75
75
|
if (hasMoreContent) {
|
|
76
|
-
this
|
|
76
|
+
this.#box.addChild(new Text(theme.italic(" (ctrl+o to expand)"), 0, 0));
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
}
|
|
@@ -12,15 +12,15 @@ interface UserMessageItem {
|
|
|
12
12
|
* Custom user message list component with selection
|
|
13
13
|
*/
|
|
14
14
|
class UserMessageList implements Component {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
#selectedIndex: number = 0;
|
|
16
|
+
onSelect?: (entryId: string) => void;
|
|
17
|
+
onCancel?: () => void;
|
|
18
|
+
#maxVisible: number = 10; // Max messages visible
|
|
19
19
|
|
|
20
20
|
constructor(private readonly messages: UserMessageItem[]) {
|
|
21
21
|
// Store messages in chronological order (oldest to newest)
|
|
22
22
|
// Start with the last (most recent) message selected
|
|
23
|
-
this
|
|
23
|
+
this.#selectedIndex = Math.max(0, this.messages.length - 1);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
invalidate(): void {
|
|
@@ -38,14 +38,14 @@ class UserMessageList implements Component {
|
|
|
38
38
|
// Calculate visible range with scrolling
|
|
39
39
|
const startIndex = Math.max(
|
|
40
40
|
0,
|
|
41
|
-
Math.min(this
|
|
41
|
+
Math.min(this.#selectedIndex - Math.floor(this.#maxVisible / 2), this.messages.length - this.#maxVisible),
|
|
42
42
|
);
|
|
43
|
-
const endIndex = Math.min(startIndex + this
|
|
43
|
+
const endIndex = Math.min(startIndex + this.#maxVisible, this.messages.length);
|
|
44
44
|
|
|
45
45
|
// Render visible messages (2 lines per message + blank line)
|
|
46
46
|
for (let i = startIndex; i < endIndex; i++) {
|
|
47
47
|
const message = this.messages[i];
|
|
48
|
-
const isSelected = i === this
|
|
48
|
+
const isSelected = i === this.#selectedIndex;
|
|
49
49
|
|
|
50
50
|
// Normalize message to single line
|
|
51
51
|
const normalizedMessage = message.text.replace(/\n/g, " ").trim();
|
|
@@ -68,7 +68,7 @@ class UserMessageList implements Component {
|
|
|
68
68
|
|
|
69
69
|
// Add scroll indicator if needed
|
|
70
70
|
if (startIndex > 0 || endIndex < this.messages.length) {
|
|
71
|
-
const scrollInfo = theme.fg("muted", ` (${this
|
|
71
|
+
const scrollInfo = theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.messages.length})`);
|
|
72
72
|
lines.push(scrollInfo);
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -78,15 +78,15 @@ class UserMessageList implements Component {
|
|
|
78
78
|
handleInput(keyData: string): void {
|
|
79
79
|
// Up arrow - go to previous (older) message, wrap to bottom when at top
|
|
80
80
|
if (matchesKey(keyData, "up")) {
|
|
81
|
-
this
|
|
81
|
+
this.#selectedIndex = this.#selectedIndex === 0 ? this.messages.length - 1 : this.#selectedIndex - 1;
|
|
82
82
|
}
|
|
83
83
|
// Down arrow - go to next (newer) message, wrap to top when at bottom
|
|
84
84
|
else if (matchesKey(keyData, "down")) {
|
|
85
|
-
this
|
|
85
|
+
this.#selectedIndex = this.#selectedIndex === this.messages.length - 1 ? 0 : this.#selectedIndex + 1;
|
|
86
86
|
}
|
|
87
87
|
// Enter - select message and branch
|
|
88
88
|
else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
89
|
-
const selected = this.messages[this
|
|
89
|
+
const selected = this.messages[this.#selectedIndex];
|
|
90
90
|
if (selected && this.onSelect) {
|
|
91
91
|
this.onSelect(selected.id);
|
|
92
92
|
}
|
|
@@ -110,7 +110,7 @@ class UserMessageList implements Component {
|
|
|
110
110
|
* Component that renders a user message selector for branching
|
|
111
111
|
*/
|
|
112
112
|
export class UserMessageSelectorComponent extends Container {
|
|
113
|
-
|
|
113
|
+
#messageList: UserMessageList;
|
|
114
114
|
|
|
115
115
|
constructor(messages: UserMessageItem[], onSelect: (entryId: string) => void, onCancel: () => void) {
|
|
116
116
|
super();
|
|
@@ -124,11 +124,11 @@ export class UserMessageSelectorComponent extends Container {
|
|
|
124
124
|
this.addChild(new Spacer(1));
|
|
125
125
|
|
|
126
126
|
// Create message list
|
|
127
|
-
this
|
|
128
|
-
this
|
|
129
|
-
this
|
|
127
|
+
this.#messageList = new UserMessageList(messages);
|
|
128
|
+
this.#messageList.onSelect = onSelect;
|
|
129
|
+
this.#messageList.onCancel = onCancel;
|
|
130
130
|
|
|
131
|
-
this.addChild(this
|
|
131
|
+
this.addChild(this.#messageList);
|
|
132
132
|
|
|
133
133
|
// Add bottom border
|
|
134
134
|
this.addChild(new Spacer(1));
|
|
@@ -141,6 +141,6 @@ export class UserMessageSelectorComponent extends Container {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
getMessageList(): UserMessageList {
|
|
144
|
-
return this
|
|
144
|
+
return this.#messageList;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
@@ -53,17 +53,17 @@ export class WelcomeComponent implements Component {
|
|
|
53
53
|
const piLogo = ["▀████████████▀", " ╘███ ███ ", " ███ ███ ", " ███ ███ ", " ▄███▄ ▄███▄ "];
|
|
54
54
|
|
|
55
55
|
// Apply gradient to logo
|
|
56
|
-
const logoColored = piLogo.map(line => this
|
|
56
|
+
const logoColored = piLogo.map(line => this.#gradientLine(line));
|
|
57
57
|
|
|
58
58
|
// Left column - centered content
|
|
59
59
|
const leftLines = [
|
|
60
60
|
"",
|
|
61
|
-
this
|
|
61
|
+
this.#centerText(theme.bold("Welcome back!"), leftCol),
|
|
62
62
|
"",
|
|
63
|
-
...logoColored.map(l => this
|
|
63
|
+
...logoColored.map(l => this.#centerText(l, leftCol)),
|
|
64
64
|
"",
|
|
65
|
-
this
|
|
66
|
-
this
|
|
65
|
+
this.#centerText(theme.fg("muted", this.modelName), leftCol),
|
|
66
|
+
this.#centerText(theme.fg("borderMuted", this.providerName), leftCol),
|
|
67
67
|
];
|
|
68
68
|
|
|
69
69
|
// Right column separator
|
|
@@ -138,8 +138,8 @@ export class WelcomeComponent implements Component {
|
|
|
138
138
|
// Content rows
|
|
139
139
|
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
140
140
|
for (let i = 0; i < maxRows; i++) {
|
|
141
|
-
const left = this
|
|
142
|
-
const right = this
|
|
141
|
+
const left = this.#fitToWidth(leftLines[i] ?? "", leftCol);
|
|
142
|
+
const right = this.#fitToWidth(rightLines[i] ?? "", rightCol);
|
|
143
143
|
lines.push(v + left + v + right + v);
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -150,7 +150,7 @@ export class WelcomeComponent implements Component {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/** Center text within a given width */
|
|
153
|
-
|
|
153
|
+
#centerText(text: string, width: number): string {
|
|
154
154
|
const visLen = visibleWidth(text);
|
|
155
155
|
if (visLen >= width) {
|
|
156
156
|
return truncateToWidth(text, width);
|
|
@@ -161,7 +161,7 @@ export class WelcomeComponent implements Component {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/** Apply magenta→cyan gradient to a string */
|
|
164
|
-
|
|
164
|
+
#gradientLine(line: string): string {
|
|
165
165
|
const colors = [
|
|
166
166
|
"\x1b[38;5;199m", // bright magenta
|
|
167
167
|
"\x1b[38;5;171m", // magenta-purple
|
|
@@ -191,7 +191,7 @@ export class WelcomeComponent implements Component {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/** Fit string to exact width with ANSI-aware truncation/padding */
|
|
194
|
-
|
|
194
|
+
#fitToWidth(str: string, width: number): string {
|
|
195
195
|
const visLen = visibleWidth(str);
|
|
196
196
|
if (visLen > width) {
|
|
197
197
|
const ellipsis = "…";
|
|
@@ -9,7 +9,6 @@ import { $ } from "bun";
|
|
|
9
9
|
import { loadCustomShare } from "../../export/custom-share";
|
|
10
10
|
import type { CompactOptions } from "../../extensibility/extensions/types";
|
|
11
11
|
import { getGatewayStatus } from "../../ipy/gateway-coordinator";
|
|
12
|
-
import { ArminComponent } from "../../modes/components/armin";
|
|
13
12
|
import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
14
13
|
import { BorderedLoader } from "../../modes/components/bordered-loader";
|
|
15
14
|
import { DynamicBorder } from "../../modes/components/dynamic-border";
|
|
@@ -448,12 +447,6 @@ export class CommandController {
|
|
|
448
447
|
this.ctx.ui.requestRender();
|
|
449
448
|
}
|
|
450
449
|
|
|
451
|
-
handleArminSaysHi(): void {
|
|
452
|
-
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
453
|
-
this.ctx.chatContainer.addChild(new ArminComponent(this.ctx.ui));
|
|
454
|
-
this.ctx.ui.requestRender();
|
|
455
|
-
}
|
|
456
|
-
|
|
457
450
|
async handleBashCommand(command: string, excludeFromContext = false): Promise<void> {
|
|
458
451
|
const isDeferred = this.ctx.session.isStreaming;
|
|
459
452
|
this.ctx.bashComponent = new BashExecutionComponent(command, this.ctx.ui, excludeFromContext);
|
|
@@ -11,25 +11,25 @@ import type { AgentSessionEvent } from "../../session/agent-session";
|
|
|
11
11
|
import type { ExitPlanModeDetails } from "../../tools";
|
|
12
12
|
|
|
13
13
|
export class EventController {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
#lastReadGroup: ReadToolGroupComponent | undefined = undefined;
|
|
15
|
+
#lastThinkingCount = 0;
|
|
16
|
+
#renderedCustomMessages = new Set<string>();
|
|
17
17
|
|
|
18
18
|
constructor(private ctx: InteractiveModeContext) {}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
this
|
|
20
|
+
#resetReadGroup(): void {
|
|
21
|
+
this.#lastReadGroup = undefined;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
if (!this
|
|
24
|
+
#getReadGroup(): ReadToolGroupComponent {
|
|
25
|
+
if (!this.#lastReadGroup) {
|
|
26
26
|
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
27
27
|
const group = new ReadToolGroupComponent();
|
|
28
28
|
group.setExpanded(this.ctx.toolOutputExpanded);
|
|
29
29
|
this.ctx.chatContainer.addChild(group);
|
|
30
|
-
this
|
|
30
|
+
this.#lastReadGroup = group;
|
|
31
31
|
}
|
|
32
|
-
return this
|
|
32
|
+
return this.#lastReadGroup;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
subscribeToAgent(): void {
|
|
@@ -76,15 +76,15 @@ export class EventController {
|
|
|
76
76
|
case "message_start":
|
|
77
77
|
if (event.message.role === "hookMessage" || event.message.role === "custom") {
|
|
78
78
|
const signature = `${event.message.role}:${event.message.customType}:${event.message.timestamp}`;
|
|
79
|
-
if (this
|
|
79
|
+
if (this.#renderedCustomMessages.has(signature)) {
|
|
80
80
|
break;
|
|
81
81
|
}
|
|
82
|
-
this
|
|
83
|
-
this
|
|
82
|
+
this.#renderedCustomMessages.add(signature);
|
|
83
|
+
this.#resetReadGroup();
|
|
84
84
|
this.ctx.addMessageToChat(event.message);
|
|
85
85
|
this.ctx.ui.requestRender();
|
|
86
86
|
} else if (event.message.role === "user") {
|
|
87
|
-
this
|
|
87
|
+
this.#resetReadGroup();
|
|
88
88
|
this.ctx.addMessageToChat(event.message);
|
|
89
89
|
if (!event.message.synthetic) {
|
|
90
90
|
this.ctx.editor.setText("");
|
|
@@ -92,12 +92,12 @@ export class EventController {
|
|
|
92
92
|
}
|
|
93
93
|
this.ctx.ui.requestRender();
|
|
94
94
|
} else if (event.message.role === "fileMention") {
|
|
95
|
-
this
|
|
95
|
+
this.#resetReadGroup();
|
|
96
96
|
this.ctx.addMessageToChat(event.message);
|
|
97
97
|
this.ctx.ui.requestRender();
|
|
98
98
|
} else if (event.message.role === "assistant") {
|
|
99
|
-
this
|
|
100
|
-
this
|
|
99
|
+
this.#lastThinkingCount = 0;
|
|
100
|
+
this.#resetReadGroup();
|
|
101
101
|
this.ctx.streamingComponent = new AssistantMessageComponent(undefined, this.ctx.hideThinkingBlock);
|
|
102
102
|
this.ctx.streamingMessage = event.message;
|
|
103
103
|
this.ctx.chatContainer.addChild(this.ctx.streamingComponent);
|
|
@@ -114,9 +114,9 @@ export class EventController {
|
|
|
114
114
|
const thinkingCount = this.ctx.streamingMessage.content.filter(
|
|
115
115
|
content => content.type === "thinking" && content.thinking.trim(),
|
|
116
116
|
).length;
|
|
117
|
-
if (thinkingCount > this
|
|
118
|
-
this
|
|
119
|
-
this
|
|
117
|
+
if (thinkingCount > this.#lastThinkingCount) {
|
|
118
|
+
this.#resetReadGroup();
|
|
119
|
+
this.#lastThinkingCount = thinkingCount;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
for (const content of this.ctx.streamingMessage.content) {
|
|
@@ -124,13 +124,13 @@ export class EventController {
|
|
|
124
124
|
|
|
125
125
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
126
126
|
if (content.name === "read") {
|
|
127
|
-
const group = this
|
|
127
|
+
const group = this.#getReadGroup();
|
|
128
128
|
group.updateArgs(content.arguments, content.id);
|
|
129
129
|
this.ctx.pendingTools.set(content.id, group);
|
|
130
130
|
continue;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
this
|
|
133
|
+
this.#resetReadGroup();
|
|
134
134
|
this.ctx.chatContainer.addChild(new Text("", 0, 0));
|
|
135
135
|
const tool = this.ctx.session.getToolByName(content.name);
|
|
136
136
|
const component = new ToolExecutionComponent(
|
|
@@ -198,14 +198,14 @@ export class EventController {
|
|
|
198
198
|
case "tool_execution_start": {
|
|
199
199
|
if (!this.ctx.pendingTools.has(event.toolCallId)) {
|
|
200
200
|
if (event.toolName === "read") {
|
|
201
|
-
const group = this
|
|
201
|
+
const group = this.#getReadGroup();
|
|
202
202
|
group.updateArgs(event.args, event.toolCallId);
|
|
203
203
|
this.ctx.pendingTools.set(event.toolCallId, group);
|
|
204
204
|
this.ctx.ui.requestRender();
|
|
205
205
|
break;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
this
|
|
208
|
+
this.#resetReadGroup();
|
|
209
209
|
const tool = this.ctx.session.getToolByName(event.toolName);
|
|
210
210
|
const component = new ToolExecutionComponent(
|
|
211
211
|
event.toolName,
|
|
@@ -18,10 +18,10 @@ import type { InteractiveModeContext } from "../../modes/types";
|
|
|
18
18
|
import { setTerminalTitle } from "../../utils/title-generator";
|
|
19
19
|
|
|
20
20
|
export class ExtensionUiController {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
#hookSelectorOverlay: OverlayHandle | undefined;
|
|
22
|
+
#hookInputOverlay: OverlayHandle | undefined;
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
readonly #dialogOverlayOptions = {
|
|
25
25
|
anchor: "bottom-center",
|
|
26
26
|
width: "80%",
|
|
27
27
|
minWidth: 40,
|
|
@@ -555,8 +555,8 @@ export class ExtensionUiController {
|
|
|
555
555
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
556
556
|
): Promise<string | undefined> {
|
|
557
557
|
const { promise, resolve } = Promise.withResolvers<string | undefined>();
|
|
558
|
-
this
|
|
559
|
-
this
|
|
558
|
+
this.#hookSelectorOverlay?.hide();
|
|
559
|
+
this.#hookSelectorOverlay = undefined;
|
|
560
560
|
const maxVisible = Math.max(4, Math.min(15, this.ctx.ui.terminal.rows - 12));
|
|
561
561
|
this.ctx.hookSelector = new HookSelectorComponent(
|
|
562
562
|
title,
|
|
@@ -577,7 +577,7 @@ export class ExtensionUiController {
|
|
|
577
577
|
maxVisible,
|
|
578
578
|
},
|
|
579
579
|
);
|
|
580
|
-
this
|
|
580
|
+
this.#hookSelectorOverlay = this.ctx.ui.showOverlay(this.ctx.hookSelector, this.#dialogOverlayOptions);
|
|
581
581
|
return promise;
|
|
582
582
|
}
|
|
583
583
|
|
|
@@ -586,8 +586,8 @@ export class ExtensionUiController {
|
|
|
586
586
|
*/
|
|
587
587
|
hideHookSelector(): void {
|
|
588
588
|
this.ctx.hookSelector?.dispose();
|
|
589
|
-
this
|
|
590
|
-
this
|
|
589
|
+
this.#hookSelectorOverlay?.hide();
|
|
590
|
+
this.#hookSelectorOverlay = undefined;
|
|
591
591
|
this.ctx.hookSelector = undefined;
|
|
592
592
|
this.ctx.ui.setFocus(this.ctx.editor);
|
|
593
593
|
this.ctx.ui.requestRender();
|
|
@@ -606,8 +606,8 @@ export class ExtensionUiController {
|
|
|
606
606
|
*/
|
|
607
607
|
showHookInput(title: string, placeholder?: string): Promise<string | undefined> {
|
|
608
608
|
const { promise, resolve } = Promise.withResolvers<string | undefined>();
|
|
609
|
-
this
|
|
610
|
-
this
|
|
609
|
+
this.#hookInputOverlay?.hide();
|
|
610
|
+
this.#hookInputOverlay = undefined;
|
|
611
611
|
this.ctx.hookInput = new HookInputComponent(
|
|
612
612
|
title,
|
|
613
613
|
placeholder,
|
|
@@ -620,7 +620,7 @@ export class ExtensionUiController {
|
|
|
620
620
|
resolve(undefined);
|
|
621
621
|
},
|
|
622
622
|
);
|
|
623
|
-
this
|
|
623
|
+
this.#hookInputOverlay = this.ctx.ui.showOverlay(this.ctx.hookInput, this.#dialogOverlayOptions);
|
|
624
624
|
return promise;
|
|
625
625
|
}
|
|
626
626
|
|
|
@@ -629,8 +629,8 @@ export class ExtensionUiController {
|
|
|
629
629
|
*/
|
|
630
630
|
hideHookInput(): void {
|
|
631
631
|
this.ctx.hookInput?.dispose();
|
|
632
|
-
this
|
|
633
|
-
this
|
|
632
|
+
this.#hookInputOverlay?.hide();
|
|
633
|
+
this.#hookInputOverlay = undefined;
|
|
634
634
|
this.ctx.hookInput = undefined;
|
|
635
635
|
this.ctx.ui.setFocus(this.ctx.editor);
|
|
636
636
|
this.ctx.ui.requestRender();
|
|
@@ -322,11 +322,6 @@ export class InputController {
|
|
|
322
322
|
this.ctx.editor.setText("");
|
|
323
323
|
return;
|
|
324
324
|
}
|
|
325
|
-
if (text === "/arminsayshi") {
|
|
326
|
-
this.ctx.handleArminSaysHi();
|
|
327
|
-
this.ctx.editor.setText("");
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
325
|
if (text === "/resume") {
|
|
331
326
|
this.ctx.showSessionSelector();
|
|
332
327
|
this.ctx.editor.setText("");
|
|
@@ -338,6 +333,14 @@ export class InputController {
|
|
|
338
333
|
return;
|
|
339
334
|
}
|
|
340
335
|
|
|
336
|
+
// Handle MCP server management commands
|
|
337
|
+
if (text === "/mcp" || text.startsWith("/mcp ")) {
|
|
338
|
+
this.ctx.editor.addToHistory(text);
|
|
339
|
+
this.ctx.editor.setText("");
|
|
340
|
+
await this.ctx.handleMCPCommand(text);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
341
344
|
// Handle skill commands (/skill:name [args])
|
|
342
345
|
if (text.startsWith("/skill:")) {
|
|
343
346
|
const spaceIndex = text.indexOf(" ");
|
|
@@ -722,15 +725,15 @@ export class InputController {
|
|
|
722
725
|
this.ctx.showStatus(`Thinking blocks: ${this.ctx.hideThinkingBlock ? "hidden" : "visible"}`);
|
|
723
726
|
}
|
|
724
727
|
|
|
725
|
-
|
|
728
|
+
#getEditorTerminalPath(): string | null {
|
|
726
729
|
if (process.platform === "win32") {
|
|
727
730
|
return null;
|
|
728
731
|
}
|
|
729
732
|
return "/dev/tty";
|
|
730
733
|
}
|
|
731
734
|
|
|
732
|
-
|
|
733
|
-
const terminalPath = this
|
|
735
|
+
async #openEditorTerminalHandle(): Promise<fs.FileHandle | null> {
|
|
736
|
+
const terminalPath = this.#getEditorTerminalPath();
|
|
734
737
|
if (!terminalPath) {
|
|
735
738
|
return null;
|
|
736
739
|
}
|
|
@@ -752,7 +755,7 @@ export class InputController {
|
|
|
752
755
|
|
|
753
756
|
let ttyHandle: fs.FileHandle | null = null;
|
|
754
757
|
try {
|
|
755
|
-
ttyHandle = await this
|
|
758
|
+
ttyHandle = await this.#openEditorTerminalHandle();
|
|
756
759
|
this.ctx.ui.stop();
|
|
757
760
|
|
|
758
761
|
const stdio: [number | "inherit", number | "inherit", number | "inherit"] = ttyHandle
|