@mariozechner/pi-coding-agent 0.42.0 → 0.42.2
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 +18 -0
- package/README.md +3 -1
- package/dist/core/extensions/types.d.ts +8 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +25 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -0
- package/dist/core/footer-data-provider.js +115 -0
- package/dist/core/footer-data-provider.js.map +1 -0
- package/dist/core/keybindings.d.ts +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +2 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +17 -3
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +7 -2
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +10 -25
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +27 -145
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +10 -2
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +6 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +110 -20
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/tui.md +10 -9
- package/examples/extensions/custom-footer.ts +33 -55
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -10,6 +10,7 @@ import { getOAuthProviders, } from "@mariozechner/pi-ai";
|
|
|
10
10
|
import { CombinedAutocompleteProvider, Container, getEditorKeybindings, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
|
|
11
11
|
import { spawn, spawnSync } from "child_process";
|
|
12
12
|
import { APP_NAME, getAuthPath, getDebugLogPath, isBunBinary, VERSION } from "../../config.js";
|
|
13
|
+
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
13
14
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
14
15
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
15
16
|
import { SessionManager } from "../../core/session-manager.js";
|
|
@@ -57,6 +58,7 @@ export class InteractiveMode {
|
|
|
57
58
|
autocompleteProvider;
|
|
58
59
|
editorContainer;
|
|
59
60
|
footer;
|
|
61
|
+
footerDataProvider;
|
|
60
62
|
keybindings;
|
|
61
63
|
version;
|
|
62
64
|
isInitialized = false;
|
|
@@ -132,7 +134,8 @@ export class InteractiveMode {
|
|
|
132
134
|
this.editor = this.defaultEditor;
|
|
133
135
|
this.editorContainer = new Container();
|
|
134
136
|
this.editorContainer.addChild(this.editor);
|
|
135
|
-
this.
|
|
137
|
+
this.footerDataProvider = new FooterDataProvider();
|
|
138
|
+
this.footer = new FooterComponent(session, this.footerDataProvider);
|
|
136
139
|
this.footer.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
137
140
|
// Load hide thinking block setting
|
|
138
141
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
@@ -201,6 +204,7 @@ export class InteractiveMode {
|
|
|
201
204
|
const toggleThinking = formatStartupKey(kb.getKeys("toggleThinking"));
|
|
202
205
|
const externalEditor = formatStartupKey(kb.getKeys("externalEditor"));
|
|
203
206
|
const followUp = formatStartupKey(kb.getKeys("followUp"));
|
|
207
|
+
const dequeue = formatStartupKey(kb.getKeys("dequeue"));
|
|
204
208
|
const instructions = theme.fg("dim", interrupt) +
|
|
205
209
|
theme.fg("muted", " to interrupt") +
|
|
206
210
|
"\n" +
|
|
@@ -249,6 +253,9 @@ export class InteractiveMode {
|
|
|
249
253
|
theme.fg("dim", followUp) +
|
|
250
254
|
theme.fg("muted", " to queue follow-up") +
|
|
251
255
|
"\n" +
|
|
256
|
+
theme.fg("dim", dequeue) +
|
|
257
|
+
theme.fg("muted", " to restore queued messages") +
|
|
258
|
+
"\n" +
|
|
252
259
|
theme.fg("dim", "ctrl+v") +
|
|
253
260
|
theme.fg("muted", " to paste image") +
|
|
254
261
|
"\n" +
|
|
@@ -280,7 +287,6 @@ export class InteractiveMode {
|
|
|
280
287
|
this.ui.addChild(this.pendingMessagesContainer);
|
|
281
288
|
this.ui.addChild(this.statusContainer);
|
|
282
289
|
this.ui.addChild(this.widgetContainer);
|
|
283
|
-
this.ui.addChild(new Spacer(1));
|
|
284
290
|
this.ui.addChild(this.editorContainer);
|
|
285
291
|
this.ui.addChild(this.footer);
|
|
286
292
|
this.ui.setFocus(this.editor);
|
|
@@ -302,8 +308,8 @@ export class InteractiveMode {
|
|
|
302
308
|
this.updateEditorBorderColor();
|
|
303
309
|
this.ui.requestRender();
|
|
304
310
|
});
|
|
305
|
-
// Set up git branch watcher
|
|
306
|
-
this.
|
|
311
|
+
// Set up git branch watcher (uses provider instead of footer)
|
|
312
|
+
this.footerDataProvider.onBranchChange(() => {
|
|
307
313
|
this.ui.requestRender();
|
|
308
314
|
});
|
|
309
315
|
}
|
|
@@ -621,7 +627,7 @@ export class InteractiveMode {
|
|
|
621
627
|
* Set extension status text in the footer.
|
|
622
628
|
*/
|
|
623
629
|
setExtensionStatus(key, text) {
|
|
624
|
-
this.
|
|
630
|
+
this.footerDataProvider.setExtensionStatus(key, text);
|
|
625
631
|
this.ui.requestRender();
|
|
626
632
|
}
|
|
627
633
|
/**
|
|
@@ -663,9 +669,11 @@ export class InteractiveMode {
|
|
|
663
669
|
return;
|
|
664
670
|
this.widgetContainer.clear();
|
|
665
671
|
if (this.extensionWidgets.size === 0) {
|
|
672
|
+
this.widgetContainer.addChild(new Spacer(1));
|
|
666
673
|
this.ui.requestRender();
|
|
667
674
|
return;
|
|
668
675
|
}
|
|
676
|
+
this.widgetContainer.addChild(new Spacer(1));
|
|
669
677
|
for (const [_key, component] of this.extensionWidgets) {
|
|
670
678
|
this.widgetContainer.addChild(component);
|
|
671
679
|
}
|
|
@@ -687,8 +695,8 @@ export class InteractiveMode {
|
|
|
687
695
|
this.ui.removeChild(this.footer);
|
|
688
696
|
}
|
|
689
697
|
if (factory) {
|
|
690
|
-
// Create and add custom footer
|
|
691
|
-
this.customFooter = factory(this.ui, theme);
|
|
698
|
+
// Create and add custom footer, passing the data provider
|
|
699
|
+
this.customFooter = factory(this.ui, theme, this.footerDataProvider);
|
|
692
700
|
this.ui.addChild(this.customFooter);
|
|
693
701
|
}
|
|
694
702
|
else {
|
|
@@ -1028,15 +1036,7 @@ export class InteractiveMode {
|
|
|
1028
1036
|
// so they work correctly regardless of which editor is active
|
|
1029
1037
|
this.defaultEditor.onEscape = () => {
|
|
1030
1038
|
if (this.loadingAnimation) {
|
|
1031
|
-
|
|
1032
|
-
const { steering, followUp } = this.session.clearQueue();
|
|
1033
|
-
const allQueued = [...steering, ...followUp];
|
|
1034
|
-
const queuedText = allQueued.join("\n\n");
|
|
1035
|
-
const currentText = this.editor.getText();
|
|
1036
|
-
const combinedText = [queuedText, currentText].filter((t) => t.trim()).join("\n\n");
|
|
1037
|
-
this.editor.setText(combinedText);
|
|
1038
|
-
this.updatePendingMessagesDisplay();
|
|
1039
|
-
this.agent.abort();
|
|
1039
|
+
this.restoreQueuedMessagesToEditor({ abort: true });
|
|
1040
1040
|
}
|
|
1041
1041
|
else if (this.session.isBashRunning) {
|
|
1042
1042
|
this.session.abortBash();
|
|
@@ -1077,6 +1077,7 @@ export class InteractiveMode {
|
|
|
1077
1077
|
this.defaultEditor.onAction("toggleThinking", () => this.toggleThinkingBlockVisibility());
|
|
1078
1078
|
this.defaultEditor.onAction("externalEditor", () => this.openExternalEditor());
|
|
1079
1079
|
this.defaultEditor.onAction("followUp", () => this.handleFollowUp());
|
|
1080
|
+
this.defaultEditor.onAction("dequeue", () => this.handleDequeue());
|
|
1080
1081
|
this.defaultEditor.onChange = (text) => {
|
|
1081
1082
|
const wasBashMode = this.isBashMode;
|
|
1082
1083
|
this.isBashMode = text.trimStart().startsWith("!");
|
|
@@ -1120,9 +1121,10 @@ export class InteractiveMode {
|
|
|
1120
1121
|
this.editor.setText("");
|
|
1121
1122
|
return;
|
|
1122
1123
|
}
|
|
1123
|
-
if (text === "/model") {
|
|
1124
|
-
|
|
1124
|
+
if (text === "/model" || text.startsWith("/model ")) {
|
|
1125
|
+
const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
|
|
1125
1126
|
this.editor.setText("");
|
|
1127
|
+
await this.handleModelCommand(searchTerm);
|
|
1126
1128
|
return;
|
|
1127
1129
|
}
|
|
1128
1130
|
if (text.startsWith("/export")) {
|
|
@@ -1754,6 +1756,15 @@ export class InteractiveMode {
|
|
|
1754
1756
|
this.editor.onSubmit(text);
|
|
1755
1757
|
}
|
|
1756
1758
|
}
|
|
1759
|
+
handleDequeue() {
|
|
1760
|
+
const restored = this.restoreQueuedMessagesToEditor();
|
|
1761
|
+
if (restored === 0) {
|
|
1762
|
+
this.showStatus("No queued messages to restore");
|
|
1763
|
+
}
|
|
1764
|
+
else {
|
|
1765
|
+
this.showStatus(`Restored ${restored} queued message${restored > 1 ? "s" : ""} to editor`);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1757
1768
|
updateEditorBorderColor() {
|
|
1758
1769
|
if (this.isBashMode) {
|
|
1759
1770
|
this.editor.borderColor = theme.getBashModeBorderColor();
|
|
@@ -1907,6 +1918,26 @@ export class InteractiveMode {
|
|
|
1907
1918
|
}
|
|
1908
1919
|
}
|
|
1909
1920
|
}
|
|
1921
|
+
restoreQueuedMessagesToEditor(options) {
|
|
1922
|
+
const { steering, followUp } = this.session.clearQueue();
|
|
1923
|
+
const allQueued = [...steering, ...followUp];
|
|
1924
|
+
if (allQueued.length === 0) {
|
|
1925
|
+
this.updatePendingMessagesDisplay();
|
|
1926
|
+
if (options?.abort) {
|
|
1927
|
+
this.agent.abort();
|
|
1928
|
+
}
|
|
1929
|
+
return 0;
|
|
1930
|
+
}
|
|
1931
|
+
const queuedText = allQueued.join("\n\n");
|
|
1932
|
+
const currentText = options?.currentText ?? this.editor.getText();
|
|
1933
|
+
const combinedText = [queuedText, currentText].filter((t) => t.trim()).join("\n\n");
|
|
1934
|
+
this.editor.setText(combinedText);
|
|
1935
|
+
this.updatePendingMessagesDisplay();
|
|
1936
|
+
if (options?.abort) {
|
|
1937
|
+
this.agent.abort();
|
|
1938
|
+
}
|
|
1939
|
+
return allQueued.length;
|
|
1940
|
+
}
|
|
1910
1941
|
queueCompactionMessage(text, mode) {
|
|
1911
1942
|
this.compactionQueuedMessages.push({ text, mode });
|
|
1912
1943
|
this.editor.addToHistory?.(text);
|
|
@@ -2106,7 +2137,63 @@ export class InteractiveMode {
|
|
|
2106
2137
|
return { component: selector, focus: selector.getSettingsList() };
|
|
2107
2138
|
});
|
|
2108
2139
|
}
|
|
2109
|
-
|
|
2140
|
+
async handleModelCommand(searchTerm) {
|
|
2141
|
+
if (!searchTerm) {
|
|
2142
|
+
this.showModelSelector();
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
const model = await this.findExactModelMatch(searchTerm);
|
|
2146
|
+
if (model) {
|
|
2147
|
+
try {
|
|
2148
|
+
await this.session.setModel(model);
|
|
2149
|
+
this.footer.invalidate();
|
|
2150
|
+
this.updateEditorBorderColor();
|
|
2151
|
+
this.showStatus(`Model: ${model.id}`);
|
|
2152
|
+
}
|
|
2153
|
+
catch (error) {
|
|
2154
|
+
this.showError(error instanceof Error ? error.message : String(error));
|
|
2155
|
+
}
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
this.showModelSelector(searchTerm);
|
|
2159
|
+
}
|
|
2160
|
+
async findExactModelMatch(searchTerm) {
|
|
2161
|
+
const term = searchTerm.trim();
|
|
2162
|
+
if (!term)
|
|
2163
|
+
return undefined;
|
|
2164
|
+
let targetProvider;
|
|
2165
|
+
let targetModelId = "";
|
|
2166
|
+
if (term.includes("/")) {
|
|
2167
|
+
const parts = term.split("/", 2);
|
|
2168
|
+
targetProvider = parts[0]?.trim().toLowerCase();
|
|
2169
|
+
targetModelId = parts[1]?.trim().toLowerCase() ?? "";
|
|
2170
|
+
}
|
|
2171
|
+
else {
|
|
2172
|
+
targetModelId = term.toLowerCase();
|
|
2173
|
+
}
|
|
2174
|
+
if (!targetModelId)
|
|
2175
|
+
return undefined;
|
|
2176
|
+
const models = await this.getModelCandidates();
|
|
2177
|
+
const exactMatches = models.filter((item) => {
|
|
2178
|
+
const idMatch = item.id.toLowerCase() === targetModelId;
|
|
2179
|
+
const providerMatch = !targetProvider || item.provider.toLowerCase() === targetProvider;
|
|
2180
|
+
return idMatch && providerMatch;
|
|
2181
|
+
});
|
|
2182
|
+
return exactMatches.length === 1 ? exactMatches[0] : undefined;
|
|
2183
|
+
}
|
|
2184
|
+
async getModelCandidates() {
|
|
2185
|
+
if (this.session.scopedModels.length > 0) {
|
|
2186
|
+
return this.session.scopedModels.map((scoped) => scoped.model);
|
|
2187
|
+
}
|
|
2188
|
+
this.session.modelRegistry.refresh();
|
|
2189
|
+
try {
|
|
2190
|
+
return await this.session.modelRegistry.getAvailable();
|
|
2191
|
+
}
|
|
2192
|
+
catch {
|
|
2193
|
+
return [];
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
showModelSelector(initialSearchInput) {
|
|
2110
2197
|
this.showSelector((done) => {
|
|
2111
2198
|
const selector = new ModelSelectorComponent(this.ui, this.session.model, this.settingsManager, this.session.modelRegistry, this.session.scopedModels, async (model) => {
|
|
2112
2199
|
try {
|
|
@@ -2123,7 +2210,7 @@ export class InteractiveMode {
|
|
|
2123
2210
|
}, () => {
|
|
2124
2211
|
done();
|
|
2125
2212
|
this.ui.requestRender();
|
|
2126
|
-
});
|
|
2213
|
+
}, initialSearchInput);
|
|
2127
2214
|
return { component: selector, focus: selector };
|
|
2128
2215
|
});
|
|
2129
2216
|
}
|
|
@@ -2592,6 +2679,7 @@ export class InteractiveMode {
|
|
|
2592
2679
|
const toggleThinking = this.getAppKeyDisplay("toggleThinking");
|
|
2593
2680
|
const externalEditor = this.getAppKeyDisplay("externalEditor");
|
|
2594
2681
|
const followUp = this.getAppKeyDisplay("followUp");
|
|
2682
|
+
const dequeue = this.getAppKeyDisplay("dequeue");
|
|
2595
2683
|
let hotkeys = `
|
|
2596
2684
|
**Navigation**
|
|
2597
2685
|
| Key | Action |
|
|
@@ -2624,6 +2712,7 @@ export class InteractiveMode {
|
|
|
2624
2712
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
2625
2713
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
2626
2714
|
| \`${followUp}\` | Queue follow-up message |
|
|
2715
|
+
| \`${dequeue}\` | Restore queued messages |
|
|
2627
2716
|
| \`Ctrl+V\` | Paste image from clipboard |
|
|
2628
2717
|
| \`/\` | Slash commands |
|
|
2629
2718
|
| \`!\` | Run bash command |
|
|
@@ -2829,6 +2918,7 @@ export class InteractiveMode {
|
|
|
2829
2918
|
this.loadingAnimation = undefined;
|
|
2830
2919
|
}
|
|
2831
2920
|
this.footer.dispose();
|
|
2921
|
+
this.footerDataProvider.dispose();
|
|
2832
2922
|
if (this.unsubscribe) {
|
|
2833
2923
|
this.unsubscribe();
|
|
2834
2924
|
}
|