@oh-my-pi/pi-coding-agent 5.5.0 → 5.6.70
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 +105 -0
- package/docs/python-repl.md +77 -0
- package/examples/hooks/snake.ts +7 -7
- package/package.json +5 -5
- package/src/bun-imports.d.ts +6 -0
- package/src/cli/args.ts +7 -0
- package/src/cli/setup-cli.ts +231 -0
- package/src/cli.ts +2 -0
- package/src/core/agent-session.ts +118 -15
- package/src/core/bash-executor.ts +3 -84
- package/src/core/compaction/compaction.ts +10 -5
- package/src/core/extensions/index.ts +2 -0
- package/src/core/extensions/loader.ts +13 -1
- package/src/core/extensions/runner.ts +50 -2
- package/src/core/extensions/types.ts +67 -2
- package/src/core/keybindings.ts +51 -1
- package/src/core/prompt-templates.ts +15 -0
- package/src/core/python-executor-display.test.ts +42 -0
- package/src/core/python-executor-lifecycle.test.ts +99 -0
- package/src/core/python-executor-mapping.test.ts +41 -0
- package/src/core/python-executor-per-call.test.ts +49 -0
- package/src/core/python-executor-session.test.ts +103 -0
- package/src/core/python-executor-streaming.test.ts +77 -0
- package/src/core/python-executor-timeout.test.ts +35 -0
- package/src/core/python-executor.lifecycle.test.ts +139 -0
- package/src/core/python-executor.result.test.ts +49 -0
- package/src/core/python-executor.test.ts +180 -0
- package/src/core/python-executor.ts +313 -0
- package/src/core/python-gateway-coordinator.ts +832 -0
- package/src/core/python-kernel-display.test.ts +54 -0
- package/src/core/python-kernel-env.test.ts +138 -0
- package/src/core/python-kernel-session.test.ts +87 -0
- package/src/core/python-kernel-ws.test.ts +104 -0
- package/src/core/python-kernel.lifecycle.test.ts +249 -0
- package/src/core/python-kernel.test.ts +461 -0
- package/src/core/python-kernel.ts +1182 -0
- package/src/core/python-modules.test.ts +102 -0
- package/src/core/python-modules.ts +110 -0
- package/src/core/python-prelude.py +889 -0
- package/src/core/python-prelude.test.ts +140 -0
- package/src/core/python-prelude.ts +3 -0
- package/src/core/sdk.ts +24 -6
- package/src/core/session-manager.ts +174 -82
- package/src/core/settings-manager-python.test.ts +23 -0
- package/src/core/settings-manager.ts +202 -0
- package/src/core/streaming-output.test.ts +26 -0
- package/src/core/streaming-output.ts +100 -0
- package/src/core/system-prompt.python.test.ts +17 -0
- package/src/core/system-prompt.ts +3 -1
- package/src/core/timings.ts +1 -1
- package/src/core/tools/bash.ts +13 -2
- package/src/core/tools/edit-diff.ts +9 -1
- package/src/core/tools/index.test.ts +50 -23
- package/src/core/tools/index.ts +83 -1
- package/src/core/tools/python-execution.test.ts +68 -0
- package/src/core/tools/python-fallback.test.ts +72 -0
- package/src/core/tools/python-renderer.test.ts +36 -0
- package/src/core/tools/python-tool-mode.test.ts +43 -0
- package/src/core/tools/python.test.ts +121 -0
- package/src/core/tools/python.ts +760 -0
- package/src/core/tools/renderers.ts +2 -0
- package/src/core/tools/schema-validation.test.ts +1 -0
- package/src/core/tools/task/executor.ts +146 -3
- package/src/core/tools/task/worker-protocol.ts +32 -2
- package/src/core/tools/task/worker.ts +182 -15
- package/src/index.ts +6 -0
- package/src/main.ts +136 -40
- package/src/modes/interactive/components/custom-editor.ts +16 -31
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +5 -16
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -13
- package/src/modes/interactive/components/history-search.ts +5 -8
- package/src/modes/interactive/components/hook-editor.ts +3 -4
- package/src/modes/interactive/components/hook-input.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +5 -15
- package/src/modes/interactive/components/index.ts +1 -0
- package/src/modes/interactive/components/keybinding-hints.ts +66 -0
- package/src/modes/interactive/components/model-selector.ts +53 -66
- package/src/modes/interactive/components/oauth-selector.ts +5 -5
- package/src/modes/interactive/components/session-selector.ts +29 -23
- package/src/modes/interactive/components/settings-defs.ts +404 -196
- package/src/modes/interactive/components/settings-selector.ts +14 -10
- package/src/modes/interactive/components/status-line-segment-editor.ts +7 -7
- package/src/modes/interactive/components/tool-execution.ts +8 -0
- package/src/modes/interactive/components/tree-selector.ts +29 -23
- package/src/modes/interactive/components/user-message-selector.ts +6 -17
- package/src/modes/interactive/controllers/command-controller.ts +86 -37
- package/src/modes/interactive/controllers/event-controller.ts +8 -0
- package/src/modes/interactive/controllers/extension-ui-controller.ts +51 -0
- package/src/modes/interactive/controllers/input-controller.ts +42 -6
- package/src/modes/interactive/interactive-mode.ts +56 -30
- package/src/modes/interactive/theme/theme-schema.json +2 -2
- package/src/modes/interactive/types.ts +6 -1
- package/src/modes/interactive/utils/ui-helpers.ts +2 -1
- package/src/modes/print-mode.ts +23 -0
- package/src/modes/rpc/rpc-mode.ts +21 -0
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/system/system-prompt.md +32 -1
- package/src/prompts/tools/python.md +91 -0
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import {
|
|
3
3
|
Container,
|
|
4
|
-
|
|
5
|
-
isArrowRight,
|
|
6
|
-
isEscape,
|
|
7
|
-
isShiftTab,
|
|
8
|
-
isTab,
|
|
4
|
+
matchesKey,
|
|
9
5
|
type SelectItem,
|
|
10
6
|
SelectList,
|
|
11
7
|
type SettingItem,
|
|
@@ -118,7 +114,10 @@ class SelectSubmenu extends Container {
|
|
|
118
114
|
type TabId = string;
|
|
119
115
|
|
|
120
116
|
const SETTINGS_TABS: Tab[] = [
|
|
121
|
-
{ id: "
|
|
117
|
+
{ id: "behavior", label: "Behavior" },
|
|
118
|
+
{ id: "tools", label: "Tools" },
|
|
119
|
+
{ id: "display", label: "Display" },
|
|
120
|
+
{ id: "voice", label: "Voice" },
|
|
122
121
|
{ id: "status", label: "Status" },
|
|
123
122
|
{ id: "lsp", label: "LSP" },
|
|
124
123
|
{ id: "exa", label: "Exa" },
|
|
@@ -172,7 +171,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
172
171
|
private pluginComponent: PluginSettingsComponent | null = null;
|
|
173
172
|
private statusPreviewContainer: Container | null = null;
|
|
174
173
|
private statusPreviewText: Text | null = null;
|
|
175
|
-
private currentTabId: TabId = "
|
|
174
|
+
private currentTabId: TabId = "behavior";
|
|
176
175
|
|
|
177
176
|
private settingsManager: SettingsManager;
|
|
178
177
|
private context: SettingsRuntimeContext;
|
|
@@ -199,7 +198,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
199
198
|
this.addChild(new Spacer(1));
|
|
200
199
|
|
|
201
200
|
// Initialize with first tab
|
|
202
|
-
this.switchToTab("
|
|
201
|
+
this.switchToTab("behavior");
|
|
203
202
|
|
|
204
203
|
// Add bottom border
|
|
205
204
|
this.addChild(new DynamicBorder());
|
|
@@ -524,13 +523,18 @@ export class SettingsSelectorComponent extends Container {
|
|
|
524
523
|
|
|
525
524
|
handleInput(data: string): void {
|
|
526
525
|
// Handle tab switching first (tab, shift+tab, or left/right arrows)
|
|
527
|
-
if (
|
|
526
|
+
if (
|
|
527
|
+
matchesKey(data, "tab") ||
|
|
528
|
+
matchesKey(data, "shift+tab") ||
|
|
529
|
+
matchesKey(data, "left") ||
|
|
530
|
+
matchesKey(data, "right")
|
|
531
|
+
) {
|
|
528
532
|
this.tabBar.handleInput(data);
|
|
529
533
|
return;
|
|
530
534
|
}
|
|
531
535
|
|
|
532
536
|
// Escape at top level cancels
|
|
533
|
-
if (
|
|
537
|
+
if ((matchesKey(data, "escape") || matchesKey(data, "esc")) && !this.currentSubmenu) {
|
|
534
538
|
this.callbacks.onCancel();
|
|
535
539
|
return;
|
|
536
540
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Live preview shown in the actual status line above
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { Container,
|
|
12
|
+
import { Container, matchesKey } from "@oh-my-pi/pi-tui";
|
|
13
13
|
import type { StatusLineSegmentId } from "../../../core/settings-manager";
|
|
14
14
|
import { theme } from "../theme/theme";
|
|
15
15
|
import { ALL_SEGMENT_IDS } from "./status-line/segments";
|
|
@@ -109,7 +109,7 @@ export class StatusLineSegmentEditorComponent extends Container {
|
|
|
109
109
|
handleInput(data: string): void {
|
|
110
110
|
const columnSegments = this.getCurrentColumnSegments();
|
|
111
111
|
|
|
112
|
-
if (
|
|
112
|
+
if (matchesKey(data, "up") || data === "k") {
|
|
113
113
|
// Move selection up within column, or jump to previous column
|
|
114
114
|
if (this.selectedIndex > 0) {
|
|
115
115
|
this.selectedIndex--;
|
|
@@ -135,7 +135,7 @@ export class StatusLineSegmentEditorComponent extends Container {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
-
} else if (
|
|
138
|
+
} else if (matchesKey(data, "down") || data === "j") {
|
|
139
139
|
// Move selection down within column, or jump to next column
|
|
140
140
|
if (this.selectedIndex < columnSegments.length - 1) {
|
|
141
141
|
this.selectedIndex++;
|
|
@@ -161,7 +161,7 @@ export class StatusLineSegmentEditorComponent extends Container {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
-
} else if (
|
|
164
|
+
} else if (matchesKey(data, "tab")) {
|
|
165
165
|
// Cycle segment: left → right → disabled → left
|
|
166
166
|
const seg = columnSegments[this.selectedIndex];
|
|
167
167
|
if (seg) {
|
|
@@ -180,7 +180,7 @@ export class StatusLineSegmentEditorComponent extends Container {
|
|
|
180
180
|
this.recompactColumn(oldColumn);
|
|
181
181
|
this.triggerPreview();
|
|
182
182
|
}
|
|
183
|
-
} else if (
|
|
183
|
+
} else if (matchesKey(data, "shift+tab")) {
|
|
184
184
|
// Reverse cycle: left ← right ← disabled ← left
|
|
185
185
|
const seg = columnSegments[this.selectedIndex];
|
|
186
186
|
if (seg) {
|
|
@@ -235,11 +235,11 @@ export class StatusLineSegmentEditorComponent extends Container {
|
|
|
235
235
|
this.selectedIndex++;
|
|
236
236
|
this.triggerPreview();
|
|
237
237
|
}
|
|
238
|
-
} else if (
|
|
238
|
+
} else if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
|
|
239
239
|
const left = this.getSegmentsForColumn("left").map((s) => s.id);
|
|
240
240
|
const right = this.getSegmentsForColumn("right").map((s) => s.id);
|
|
241
241
|
this.callbacks.onSave(left, right);
|
|
242
|
-
} else if (
|
|
242
|
+
} else if (matchesKey(data, "escape") || matchesKey(data, "esc")) {
|
|
243
243
|
this.callbacks.onCancel();
|
|
244
244
|
}
|
|
245
245
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import stripAnsi from "strip-ansi";
|
|
14
14
|
import { BASH_DEFAULT_PREVIEW_LINES } from "../../../core/tools/bash";
|
|
15
15
|
import { computeEditDiff, type EditDiffError, type EditDiffResult } from "../../../core/tools/edit-diff";
|
|
16
|
+
import { PYTHON_DEFAULT_PREVIEW_LINES } from "../../../core/tools/python";
|
|
16
17
|
import { toolRenderers } from "../../../core/tools/renderers";
|
|
17
18
|
import { convertToPng } from "../../../utils/image-convert";
|
|
18
19
|
import { sanitizeBinaryOutput } from "../../../utils/shell";
|
|
@@ -507,6 +508,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
507
508
|
context.output = output;
|
|
508
509
|
context.expanded = this.expanded;
|
|
509
510
|
context.previewLines = BASH_DEFAULT_PREVIEW_LINES;
|
|
511
|
+
context.timeout = typeof this.args?.timeout === "number" ? this.args.timeout : undefined;
|
|
512
|
+
} else if (this.toolName === "python" && this.result) {
|
|
513
|
+
const output = this.getTextOutput().trim();
|
|
514
|
+
context.output = output;
|
|
515
|
+
context.expanded = this.expanded;
|
|
516
|
+
context.previewLines = PYTHON_DEFAULT_PREVIEW_LINES;
|
|
517
|
+
context.timeout = typeof this.args?.timeout === "number" ? this.args.timeout : undefined;
|
|
510
518
|
} else if (this.toolName === "edit") {
|
|
511
519
|
// Edit needs diff preview and renderDiff function
|
|
512
520
|
context.editDiffPreview = this.editDiffPreview;
|
|
@@ -2,16 +2,7 @@ import {
|
|
|
2
2
|
type Component,
|
|
3
3
|
Container,
|
|
4
4
|
Input,
|
|
5
|
-
|
|
6
|
-
isArrowLeft,
|
|
7
|
-
isArrowRight,
|
|
8
|
-
isArrowUp,
|
|
9
|
-
isBackspace,
|
|
10
|
-
isCtrlC,
|
|
11
|
-
isCtrlO,
|
|
12
|
-
isEnter,
|
|
13
|
-
isEscape,
|
|
14
|
-
isShiftCtrlO,
|
|
5
|
+
matchesKey,
|
|
15
6
|
Spacer,
|
|
16
7
|
Text,
|
|
17
8
|
TruncatedText,
|
|
@@ -660,43 +651,58 @@ class TreeList implements Component {
|
|
|
660
651
|
}
|
|
661
652
|
|
|
662
653
|
handleInput(keyData: string): void {
|
|
663
|
-
if (
|
|
654
|
+
if (matchesKey(keyData, "up")) {
|
|
664
655
|
this.selectedIndex = this.selectedIndex === 0 ? this.filteredNodes.length - 1 : this.selectedIndex - 1;
|
|
665
|
-
} else if (
|
|
656
|
+
} else if (matchesKey(keyData, "down")) {
|
|
666
657
|
this.selectedIndex = this.selectedIndex === this.filteredNodes.length - 1 ? 0 : this.selectedIndex + 1;
|
|
667
|
-
} else if (
|
|
658
|
+
} else if (matchesKey(keyData, "left")) {
|
|
668
659
|
// Page up
|
|
669
660
|
this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisibleLines);
|
|
670
|
-
} else if (
|
|
661
|
+
} else if (matchesKey(keyData, "right")) {
|
|
671
662
|
// Page down
|
|
672
663
|
this.selectedIndex = Math.min(this.filteredNodes.length - 1, this.selectedIndex + this.maxVisibleLines);
|
|
673
|
-
} else if (
|
|
664
|
+
} else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
674
665
|
const selected = this.filteredNodes[this.selectedIndex];
|
|
675
666
|
if (selected && this.onSelect) {
|
|
676
667
|
this.onSelect(selected.node.entry.id);
|
|
677
668
|
}
|
|
678
|
-
} else if (
|
|
669
|
+
} else if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc")) {
|
|
679
670
|
if (this.searchQuery) {
|
|
680
671
|
this.searchQuery = "";
|
|
681
672
|
this.applyFilter();
|
|
682
673
|
} else {
|
|
683
674
|
this.onCancel?.();
|
|
684
675
|
}
|
|
685
|
-
} else if (
|
|
676
|
+
} else if (matchesKey(keyData, "ctrl+c")) {
|
|
686
677
|
this.onCancel?.();
|
|
687
|
-
} else if (
|
|
678
|
+
} else if (matchesKey(keyData, "shift+ctrl+o") || matchesKey(keyData, "ctrl+shift+o")) {
|
|
688
679
|
// Cycle filter backwards
|
|
689
680
|
const modes: FilterMode[] = ["default", "no-tools", "user-only", "labeled-only", "all"];
|
|
690
681
|
const currentIndex = modes.indexOf(this.filterMode);
|
|
691
682
|
this.filterMode = modes[(currentIndex - 1 + modes.length) % modes.length];
|
|
692
683
|
this.applyFilter();
|
|
693
|
-
} else if (
|
|
684
|
+
} else if (matchesKey(keyData, "ctrl+o")) {
|
|
694
685
|
// Cycle filter forwards: default → no-tools → user-only → labeled-only → all → default
|
|
695
686
|
const modes: FilterMode[] = ["default", "no-tools", "user-only", "labeled-only", "all"];
|
|
696
687
|
const currentIndex = modes.indexOf(this.filterMode);
|
|
697
688
|
this.filterMode = modes[(currentIndex + 1) % modes.length];
|
|
698
689
|
this.applyFilter();
|
|
699
|
-
} else if (
|
|
690
|
+
} else if (matchesKey(keyData, "alt+d")) {
|
|
691
|
+
this.filterMode = "default";
|
|
692
|
+
this.applyFilter();
|
|
693
|
+
} else if (matchesKey(keyData, "alt+t")) {
|
|
694
|
+
this.filterMode = "no-tools";
|
|
695
|
+
this.applyFilter();
|
|
696
|
+
} else if (matchesKey(keyData, "alt+u")) {
|
|
697
|
+
this.filterMode = "user-only";
|
|
698
|
+
this.applyFilter();
|
|
699
|
+
} else if (matchesKey(keyData, "alt+l")) {
|
|
700
|
+
this.filterMode = "labeled-only";
|
|
701
|
+
this.applyFilter();
|
|
702
|
+
} else if (matchesKey(keyData, "alt+a")) {
|
|
703
|
+
this.filterMode = "all";
|
|
704
|
+
this.applyFilter();
|
|
705
|
+
} else if (matchesKey(keyData, "backspace")) {
|
|
700
706
|
if (this.searchQuery.length > 0) {
|
|
701
707
|
this.searchQuery = this.searchQuery.slice(0, -1);
|
|
702
708
|
this.applyFilter();
|
|
@@ -764,10 +770,10 @@ class LabelInput implements Component {
|
|
|
764
770
|
}
|
|
765
771
|
|
|
766
772
|
handleInput(keyData: string): void {
|
|
767
|
-
if (
|
|
773
|
+
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
768
774
|
const value = this.input.getValue().trim();
|
|
769
775
|
this.onSubmit?.(this.entryId, value || undefined);
|
|
770
|
-
} else if (
|
|
776
|
+
} else if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc")) {
|
|
771
777
|
this.onCancel?.();
|
|
772
778
|
} else {
|
|
773
779
|
this.input.handleInput(keyData);
|
|
@@ -815,7 +821,7 @@ export class TreeSelectorComponent extends Container {
|
|
|
815
821
|
new TruncatedText(
|
|
816
822
|
theme.fg(
|
|
817
823
|
"muted",
|
|
818
|
-
" Up/Down: move. Left/Right: page. l: label. Ctrl+O/Shift+Ctrl+O: filter. Type to search",
|
|
824
|
+
" Up/Down: move. Left/Right: page. l: label. Ctrl+O/Shift+Ctrl+O: filter. Alt+D/T/U/L/A: filter. Type to search",
|
|
819
825
|
),
|
|
820
826
|
0,
|
|
821
827
|
0,
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Component,
|
|
3
|
-
Container,
|
|
4
|
-
isArrowDown,
|
|
5
|
-
isArrowUp,
|
|
6
|
-
isCtrlC,
|
|
7
|
-
isEnter,
|
|
8
|
-
isEscape,
|
|
9
|
-
Spacer,
|
|
10
|
-
Text,
|
|
11
|
-
truncateToWidth,
|
|
12
|
-
} from "@oh-my-pi/pi-tui";
|
|
1
|
+
import { type Component, Container, matchesKey, Spacer, Text, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
13
2
|
import { theme } from "../theme/theme";
|
|
14
3
|
import { DynamicBorder } from "./dynamic-border";
|
|
15
4
|
|
|
@@ -90,28 +79,28 @@ class UserMessageList implements Component {
|
|
|
90
79
|
|
|
91
80
|
handleInput(keyData: string): void {
|
|
92
81
|
// Up arrow - go to previous (older) message, wrap to bottom when at top
|
|
93
|
-
if (
|
|
82
|
+
if (matchesKey(keyData, "up")) {
|
|
94
83
|
this.selectedIndex = this.selectedIndex === 0 ? this.messages.length - 1 : this.selectedIndex - 1;
|
|
95
84
|
}
|
|
96
85
|
// Down arrow - go to next (newer) message, wrap to top when at bottom
|
|
97
|
-
else if (
|
|
86
|
+
else if (matchesKey(keyData, "down")) {
|
|
98
87
|
this.selectedIndex = this.selectedIndex === this.messages.length - 1 ? 0 : this.selectedIndex + 1;
|
|
99
88
|
}
|
|
100
89
|
// Enter - select message and branch
|
|
101
|
-
else if (
|
|
90
|
+
else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
102
91
|
const selected = this.messages[this.selectedIndex];
|
|
103
92
|
if (selected && this.onSelect) {
|
|
104
93
|
this.onSelect(selected.id);
|
|
105
94
|
}
|
|
106
95
|
}
|
|
107
96
|
// Escape - cancel
|
|
108
|
-
else if (
|
|
97
|
+
else if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc")) {
|
|
109
98
|
if (this.onCancel) {
|
|
110
99
|
this.onCancel();
|
|
111
100
|
}
|
|
112
101
|
}
|
|
113
102
|
// Ctrl+C - cancel
|
|
114
|
-
else if (
|
|
103
|
+
else if (matchesKey(keyData, "ctrl+c")) {
|
|
115
104
|
if (this.onCancel) {
|
|
116
105
|
this.onCancel();
|
|
117
106
|
}
|
|
@@ -5,7 +5,9 @@ import { Loader, Markdown, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
|
5
5
|
import { nanoid } from "nanoid";
|
|
6
6
|
import { getDebugLogPath } from "../../../config";
|
|
7
7
|
import { loadCustomShare } from "../../../core/custom-share";
|
|
8
|
+
import type { CompactOptions } from "../../../core/extensions/types";
|
|
8
9
|
import { createCompactionSummaryMessage } from "../../../core/messages";
|
|
10
|
+
import { getGatewayStatus } from "../../../core/python-gateway-coordinator";
|
|
9
11
|
import type { TruncationResult } from "../../../core/tools/truncate";
|
|
10
12
|
import { getChangelogPath, parseChangelog } from "../../../utils/changelog";
|
|
11
13
|
import { copyToClipboard } from "../../../utils/clipboard";
|
|
@@ -172,40 +174,37 @@ export class CommandController {
|
|
|
172
174
|
};
|
|
173
175
|
|
|
174
176
|
try {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const stdoutReader = (proc.stdout as ReadableStream<Uint8Array>).getReader();
|
|
184
|
-
const stderrReader = (proc.stderr as ReadableStream<Uint8Array>).getReader();
|
|
177
|
+
proc = Bun.spawn(["gh", "gist", "create", "--public=false", tmpFile], {
|
|
178
|
+
stdout: "pipe",
|
|
179
|
+
stderr: "pipe",
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const readStream = async (stream: ReadableStream<Uint8Array> | null): Promise<string> => {
|
|
183
|
+
if (!stream) return "";
|
|
184
|
+
const reader = stream.getReader();
|
|
185
185
|
const decoder = new TextDecoder();
|
|
186
|
+
let output = "";
|
|
187
|
+
try {
|
|
188
|
+
while (true) {
|
|
189
|
+
const { done, value } = await reader.read();
|
|
190
|
+
if (done) break;
|
|
191
|
+
output += decoder.decode(value, { stream: true });
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
// Ignore read errors
|
|
195
|
+
} finally {
|
|
196
|
+
output += decoder.decode();
|
|
197
|
+
reader.releaseLock();
|
|
198
|
+
}
|
|
199
|
+
return output;
|
|
200
|
+
};
|
|
186
201
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
} catch {}
|
|
195
|
-
})();
|
|
196
|
-
|
|
197
|
-
(async () => {
|
|
198
|
-
try {
|
|
199
|
-
while (true) {
|
|
200
|
-
const { done, value } = await stderrReader.read();
|
|
201
|
-
if (done) break;
|
|
202
|
-
stderr += decoder.decode(value);
|
|
203
|
-
}
|
|
204
|
-
} catch {}
|
|
205
|
-
})();
|
|
206
|
-
|
|
207
|
-
proc.exited.then((code) => resolve({ stdout, stderr, code }));
|
|
208
|
-
});
|
|
202
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
203
|
+
readStream(proc.stdout as ReadableStream<Uint8Array> | null),
|
|
204
|
+
readStream(proc.stderr as ReadableStream<Uint8Array> | null),
|
|
205
|
+
proc.exited.catch(() => 1),
|
|
206
|
+
]);
|
|
207
|
+
const result = { stdout, stderr, code };
|
|
209
208
|
|
|
210
209
|
if (loader.signal.aborted) return;
|
|
211
210
|
|
|
@@ -275,7 +274,47 @@ export class CommandController {
|
|
|
275
274
|
|
|
276
275
|
if (stats.cost > 0) {
|
|
277
276
|
info += `\n${theme.bold("Cost")}\n`;
|
|
278
|
-
info += `${theme.fg("dim", "Total:")} ${stats.cost.toFixed(4)}`;
|
|
277
|
+
info += `${theme.fg("dim", "Total:")} ${stats.cost.toFixed(4)}\n`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const gateway = getGatewayStatus();
|
|
281
|
+
info += `\n${theme.bold("Python Gateway")}\n`;
|
|
282
|
+
if (gateway.active) {
|
|
283
|
+
const mode = gateway.shared ? "Shared" : "Local";
|
|
284
|
+
info += `${theme.fg("dim", "Status:")} ${theme.fg("success", `Active (${mode})`)}\n`;
|
|
285
|
+
info += `${theme.fg("dim", "URL:")} ${gateway.url}\n`;
|
|
286
|
+
info += `${theme.fg("dim", "PID:")} ${gateway.pid}\n`;
|
|
287
|
+
info += `${theme.fg("dim", "Clients:")} ${gateway.refCount}\n`;
|
|
288
|
+
if (gateway.uptime !== null) {
|
|
289
|
+
const uptimeSec = Math.floor(gateway.uptime / 1000);
|
|
290
|
+
const mins = Math.floor(uptimeSec / 60);
|
|
291
|
+
const secs = uptimeSec % 60;
|
|
292
|
+
info += `${theme.fg("dim", "Uptime:")} ${mins}m ${secs}s\n`;
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
info += `${theme.fg("dim", "Status:")} ${theme.fg("dim", "Inactive")}\n`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (this.ctx.lspServers && this.ctx.lspServers.length > 0) {
|
|
299
|
+
info += `\n${theme.bold("LSP Servers")}\n`;
|
|
300
|
+
for (const server of this.ctx.lspServers) {
|
|
301
|
+
const statusColor = server.status === "ready" ? "success" : "error";
|
|
302
|
+
info += `${theme.fg("dim", `${server.name}:`)} ${theme.fg(statusColor, server.status)} ${theme.fg("dim", `(${server.fileTypes.join(", ")})`)}\n`;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (this.ctx.mcpManager) {
|
|
307
|
+
const mcpServers = this.ctx.mcpManager.getConnectedServers();
|
|
308
|
+
info += `\n${theme.bold("MCP Servers")}\n`;
|
|
309
|
+
if (mcpServers.length === 0) {
|
|
310
|
+
info += `${theme.fg("dim", "None connected")}\n`;
|
|
311
|
+
} else {
|
|
312
|
+
for (const name of mcpServers) {
|
|
313
|
+
const conn = this.ctx.mcpManager.getConnection(name);
|
|
314
|
+
const toolCount = conn?.tools?.length ?? 0;
|
|
315
|
+
info += `${theme.fg("dim", `${name}:`)} ${theme.fg("success", "connected")} ${theme.fg("dim", `(${toolCount} tools)`)}\n`;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
279
318
|
}
|
|
280
319
|
|
|
281
320
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
@@ -305,6 +344,7 @@ export class CommandController {
|
|
|
305
344
|
}
|
|
306
345
|
|
|
307
346
|
handleHotkeysCommand(): void {
|
|
347
|
+
const expandToolsKey = this.ctx.keybindings.getDisplayString("expandTools") || "Ctrl+O";
|
|
308
348
|
const hotkeys = `
|
|
309
349
|
**Navigation**
|
|
310
350
|
| Key | Action |
|
|
@@ -337,7 +377,7 @@ export class CommandController {
|
|
|
337
377
|
| \`Alt+P\` | Select model (temporary) |
|
|
338
378
|
| \`Ctrl+L\` | Select model (set roles) |
|
|
339
379
|
| \`Ctrl+R\` | Search prompt history |
|
|
340
|
-
| \`
|
|
380
|
+
| \`${expandToolsKey}\` | Toggle tool output expansion |
|
|
341
381
|
| \`Ctrl+T\` | Toggle todo list expansion |
|
|
342
382
|
| \`Ctrl+G\` | Edit message in external editor |
|
|
343
383
|
| \`/\` | Slash commands |
|
|
@@ -481,14 +521,18 @@ export class CommandController {
|
|
|
481
521
|
try {
|
|
482
522
|
const content = fs.readFileSync(skillPath, "utf-8");
|
|
483
523
|
const body = content.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
|
|
484
|
-
const
|
|
524
|
+
const metaLines = [`Skill: ${skillPath}`];
|
|
525
|
+
if (args) {
|
|
526
|
+
metaLines.push(`User: ${args}`);
|
|
527
|
+
}
|
|
528
|
+
const message = `${body}\n\n---\n\n${metaLines.join("\n")}`;
|
|
485
529
|
await this.ctx.session.prompt(message);
|
|
486
530
|
} catch (err) {
|
|
487
531
|
this.ctx.showError(`Failed to load skill: ${err instanceof Error ? err.message : String(err)}`);
|
|
488
532
|
}
|
|
489
533
|
}
|
|
490
534
|
|
|
491
|
-
async executeCompaction(
|
|
535
|
+
async executeCompaction(customInstructionsOrOptions?: string | CompactOptions, isAuto = false): Promise<void> {
|
|
492
536
|
if (this.ctx.loadingAnimation) {
|
|
493
537
|
this.ctx.loadingAnimation.stop();
|
|
494
538
|
this.ctx.loadingAnimation = undefined;
|
|
@@ -513,7 +557,12 @@ export class CommandController {
|
|
|
513
557
|
this.ctx.ui.requestRender();
|
|
514
558
|
|
|
515
559
|
try {
|
|
516
|
-
const
|
|
560
|
+
const instructions = typeof customInstructionsOrOptions === "string" ? customInstructionsOrOptions : undefined;
|
|
561
|
+
const options =
|
|
562
|
+
customInstructionsOrOptions && typeof customInstructionsOrOptions === "object"
|
|
563
|
+
? customInstructionsOrOptions
|
|
564
|
+
: undefined;
|
|
565
|
+
const result = await this.ctx.session.compact(instructions, options);
|
|
517
566
|
|
|
518
567
|
this.ctx.rebuildChatFromMessages();
|
|
519
568
|
|
|
@@ -12,6 +12,7 @@ import type { InteractiveModeContext, TodoItem } from "../types";
|
|
|
12
12
|
export class EventController {
|
|
13
13
|
private lastReadGroup: ReadToolGroupComponent | undefined = undefined;
|
|
14
14
|
private lastThinkingCount = 0;
|
|
15
|
+
private renderedCustomMessages = new Set<string>();
|
|
15
16
|
|
|
16
17
|
constructor(private ctx: InteractiveModeContext) {}
|
|
17
18
|
|
|
@@ -73,6 +74,11 @@ export class EventController {
|
|
|
73
74
|
|
|
74
75
|
case "message_start":
|
|
75
76
|
if (event.message.role === "hookMessage" || event.message.role === "custom") {
|
|
77
|
+
const signature = `${event.message.role}:${event.message.customType}:${event.message.timestamp}`;
|
|
78
|
+
if (this.renderedCustomMessages.has(signature)) {
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
this.renderedCustomMessages.add(signature);
|
|
76
82
|
this.resetReadGroup();
|
|
77
83
|
this.ctx.addMessageToChat(event.message);
|
|
78
84
|
this.ctx.ui.requestRender();
|
|
@@ -324,6 +330,8 @@ export class EventController {
|
|
|
324
330
|
});
|
|
325
331
|
this.ctx.statusLine.invalidate();
|
|
326
332
|
this.ctx.updateEditorTopBorder();
|
|
333
|
+
} else {
|
|
334
|
+
this.ctx.showWarning("Auto-compaction failed; continuing without compaction");
|
|
327
335
|
}
|
|
328
336
|
await this.ctx.flushCompactionQueue({ willRetry: event.willRetry });
|
|
329
337
|
this.ctx.ui.requestRender();
|
|
@@ -87,6 +87,9 @@ export class ExtensionUiController {
|
|
|
87
87
|
appendEntry: (customType, data) => {
|
|
88
88
|
this.ctx.sessionManager.appendCustomEntry(customType, data);
|
|
89
89
|
},
|
|
90
|
+
setLabel: (targetId, label) => {
|
|
91
|
+
this.ctx.sessionManager.appendLabelChange(targetId, label);
|
|
92
|
+
},
|
|
90
93
|
getActiveTools: () => this.ctx.session.getActiveToolNames(),
|
|
91
94
|
getAllTools: () => this.ctx.session.getAllToolNames(),
|
|
92
95
|
setActiveTools: (toolNames) => this.ctx.session.setActiveToolsByName(toolNames),
|
|
@@ -107,8 +110,16 @@ export class ExtensionUiController {
|
|
|
107
110
|
shutdown: () => {
|
|
108
111
|
// Signal shutdown request (will be handled by main loop)
|
|
109
112
|
},
|
|
113
|
+
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
114
|
+
compact: async (instructionsOrOptions) => {
|
|
115
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
116
|
+
const options =
|
|
117
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
118
|
+
await this.ctx.session.compact(instructions, options);
|
|
119
|
+
},
|
|
110
120
|
};
|
|
111
121
|
const commandActions: ExtensionCommandContextActions = {
|
|
122
|
+
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
112
123
|
waitForIdle: () => this.ctx.session.agent.waitForIdle(),
|
|
113
124
|
newSession: async (options) => {
|
|
114
125
|
// Stop any loading animation
|
|
@@ -178,6 +189,16 @@ export class ExtensionUiController {
|
|
|
178
189
|
|
|
179
190
|
return { cancelled: false };
|
|
180
191
|
},
|
|
192
|
+
compact: async (instructionsOrOptions) => {
|
|
193
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
194
|
+
const options =
|
|
195
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
196
|
+
if (this.ctx.isBackgrounded) {
|
|
197
|
+
await this.ctx.session.compact(instructions, options);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
await this.ctx.executeCompaction(instructionsOrOptions, false);
|
|
201
|
+
},
|
|
181
202
|
};
|
|
182
203
|
|
|
183
204
|
extensionRunner.initialize(actions, contextActions, commandActions, uiContext);
|
|
@@ -235,6 +256,9 @@ export class ExtensionUiController {
|
|
|
235
256
|
appendEntry: (customType, data) => {
|
|
236
257
|
this.ctx.sessionManager.appendCustomEntry(customType, data);
|
|
237
258
|
},
|
|
259
|
+
setLabel: (targetId, label) => {
|
|
260
|
+
this.ctx.sessionManager.appendLabelChange(targetId, label);
|
|
261
|
+
},
|
|
238
262
|
getActiveTools: () => this.ctx.session.getActiveToolNames(),
|
|
239
263
|
getAllTools: () => this.ctx.session.getAllToolNames(),
|
|
240
264
|
setActiveTools: (toolNames) => this.ctx.session.setActiveToolsByName(toolNames),
|
|
@@ -255,8 +279,16 @@ export class ExtensionUiController {
|
|
|
255
279
|
shutdown: () => {
|
|
256
280
|
// Signal shutdown request (will be handled by main loop)
|
|
257
281
|
},
|
|
282
|
+
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
283
|
+
compact: async (instructionsOrOptions) => {
|
|
284
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
285
|
+
const options =
|
|
286
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
287
|
+
await this.ctx.session.compact(instructions, options);
|
|
288
|
+
},
|
|
258
289
|
};
|
|
259
290
|
const commandActions: ExtensionCommandContextActions = {
|
|
291
|
+
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
260
292
|
waitForIdle: () => this.ctx.session.agent.waitForIdle(),
|
|
261
293
|
newSession: async (options) => {
|
|
262
294
|
if (this.ctx.isBackgrounded) {
|
|
@@ -335,6 +367,16 @@ export class ExtensionUiController {
|
|
|
335
367
|
|
|
336
368
|
return { cancelled: false };
|
|
337
369
|
},
|
|
370
|
+
compact: async (instructionsOrOptions) => {
|
|
371
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
372
|
+
const options =
|
|
373
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object" ? instructionsOrOptions : undefined;
|
|
374
|
+
if (this.ctx.isBackgrounded) {
|
|
375
|
+
await this.ctx.session.compact(instructions, options);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
await this.ctx.executeCompaction(instructionsOrOptions, false);
|
|
379
|
+
},
|
|
338
380
|
};
|
|
339
381
|
|
|
340
382
|
extensionRunner.initialize(actions, contextActions, commandActions, uiContext);
|
|
@@ -382,6 +424,15 @@ export class ExtensionUiController {
|
|
|
382
424
|
try {
|
|
383
425
|
await registeredTool.definition.onSession(event, {
|
|
384
426
|
ui: uiContext,
|
|
427
|
+
getContextUsage: () => this.ctx.session.getContextUsage(),
|
|
428
|
+
compact: async (instructionsOrOptions) => {
|
|
429
|
+
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
430
|
+
const options =
|
|
431
|
+
instructionsOrOptions && typeof instructionsOrOptions === "object"
|
|
432
|
+
? instructionsOrOptions
|
|
433
|
+
: undefined;
|
|
434
|
+
await this.ctx.session.compact(instructions, options);
|
|
435
|
+
},
|
|
385
436
|
hasUI: !this.ctx.isBackgrounded,
|
|
386
437
|
cwd: this.ctx.sessionManager.getCwd(),
|
|
387
438
|
sessionManager: this.ctx.session.sessionManager,
|