@mariozechner/pi-coding-agent 0.8.5 → 0.9.1
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 +17 -1
- package/README.md +50 -11
- package/dist/export-html.d.ts +1 -1
- package/dist/export-html.d.ts.map +1 -1
- package/dist/export-html.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +135 -70
- package/dist/main.js.map +1 -1
- package/dist/session-manager.d.ts +3 -1
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +6 -0
- package/dist/session-manager.js.map +1 -1
- package/dist/settings-manager.d.ts +3 -0
- package/dist/settings-manager.d.ts.map +1 -1
- package/dist/settings-manager.js +7 -0
- package/dist/settings-manager.js.map +1 -1
- package/dist/tui/footer.d.ts +1 -1
- package/dist/tui/footer.d.ts.map +1 -1
- package/dist/tui/footer.js.map +1 -1
- package/dist/tui/thinking-selector.d.ts +1 -1
- package/dist/tui/thinking-selector.d.ts.map +1 -1
- package/dist/tui/thinking-selector.js.map +1 -1
- package/dist/tui/tui-renderer.d.ts +9 -5
- package/dist/tui/tui-renderer.d.ts.map +1 -1
- package/dist/tui/tui-renderer.js +135 -39
- package/dist/tui/tui-renderer.js.map +1 -1
- package/package.json +4 -4
package/dist/tui/tui-renderer.js
CHANGED
|
@@ -35,7 +35,6 @@ export class TuiRenderer {
|
|
|
35
35
|
isInitialized = false;
|
|
36
36
|
onInputCallback;
|
|
37
37
|
loadingAnimation = null;
|
|
38
|
-
onInterruptCallback;
|
|
39
38
|
lastSigintTime = 0;
|
|
40
39
|
changelogMarkdown = null;
|
|
41
40
|
newVersion = null;
|
|
@@ -63,6 +62,8 @@ export class TuiRenderer {
|
|
|
63
62
|
scopedModels = [];
|
|
64
63
|
// Tool output expansion state
|
|
65
64
|
toolOutputExpanded = false;
|
|
65
|
+
// Agent subscription unsubscribe function
|
|
66
|
+
unsubscribe;
|
|
66
67
|
constructor(agent, sessionManager, settingsManager, version, changelogMarkdown = null, newVersion = null, scopedModels = []) {
|
|
67
68
|
this.agent = agent;
|
|
68
69
|
this.sessionManager = sessionManager;
|
|
@@ -120,6 +121,10 @@ export class TuiRenderer {
|
|
|
120
121
|
name: "theme",
|
|
121
122
|
description: "Select color theme (opens selector UI)",
|
|
122
123
|
};
|
|
124
|
+
const clearCommand = {
|
|
125
|
+
name: "clear",
|
|
126
|
+
description: "Clear context and start a fresh session",
|
|
127
|
+
};
|
|
123
128
|
// Setup autocomplete for file paths and slash commands
|
|
124
129
|
const autocompleteProvider = new CombinedAutocompleteProvider([
|
|
125
130
|
thinkingCommand,
|
|
@@ -132,6 +137,7 @@ export class TuiRenderer {
|
|
|
132
137
|
loginCommand,
|
|
133
138
|
logoutCommand,
|
|
134
139
|
queueCommand,
|
|
140
|
+
clearCommand,
|
|
135
141
|
], process.cwd());
|
|
136
142
|
this.editor.setAutocompleteProvider(autocompleteProvider);
|
|
137
143
|
}
|
|
@@ -199,7 +205,7 @@ export class TuiRenderer {
|
|
|
199
205
|
// Set up custom key handlers on the editor
|
|
200
206
|
this.editor.onEscape = () => {
|
|
201
207
|
// Intercept Escape key when processing
|
|
202
|
-
if (this.loadingAnimation
|
|
208
|
+
if (this.loadingAnimation) {
|
|
203
209
|
// Get all queued messages
|
|
204
210
|
const queuedText = this.queuedMessages.join("\n\n");
|
|
205
211
|
// Get current editor text
|
|
@@ -214,7 +220,7 @@ export class TuiRenderer {
|
|
|
214
220
|
// Clear agent's queue too
|
|
215
221
|
this.agent.clearMessageQueue();
|
|
216
222
|
// Abort
|
|
217
|
-
this.
|
|
223
|
+
this.agent.abort();
|
|
218
224
|
}
|
|
219
225
|
};
|
|
220
226
|
this.editor.onCtrlC = () => {
|
|
@@ -296,6 +302,12 @@ export class TuiRenderer {
|
|
|
296
302
|
this.editor.setText("");
|
|
297
303
|
return;
|
|
298
304
|
}
|
|
305
|
+
// Check for /clear command
|
|
306
|
+
if (text === "/clear") {
|
|
307
|
+
this.handleClearCommand();
|
|
308
|
+
this.editor.setText("");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
299
311
|
// Normal message submission - validate model and API key first
|
|
300
312
|
const currentModel = this.agent.state.model;
|
|
301
313
|
if (!currentModel) {
|
|
@@ -337,6 +349,8 @@ export class TuiRenderer {
|
|
|
337
349
|
// Start the UI
|
|
338
350
|
this.ui.start();
|
|
339
351
|
this.isInitialized = true;
|
|
352
|
+
// Subscribe to agent events for UI updates and session saving
|
|
353
|
+
this.subscribeToAgent();
|
|
340
354
|
// Set up theme file watcher for live reload
|
|
341
355
|
onThemeChange(() => {
|
|
342
356
|
this.ui.invalidate();
|
|
@@ -344,6 +358,20 @@ export class TuiRenderer {
|
|
|
344
358
|
this.ui.requestRender();
|
|
345
359
|
});
|
|
346
360
|
}
|
|
361
|
+
subscribeToAgent() {
|
|
362
|
+
this.unsubscribe = this.agent.subscribe(async (event) => {
|
|
363
|
+
// Handle UI updates
|
|
364
|
+
await this.handleEvent(event, this.agent.state);
|
|
365
|
+
// Save messages to session
|
|
366
|
+
if (event.type === "message_end") {
|
|
367
|
+
this.sessionManager.saveMessage(event.message);
|
|
368
|
+
// Check if we should initialize session now (after first user+assistant exchange)
|
|
369
|
+
if (this.sessionManager.shouldInitializeSession(this.agent.state.messages)) {
|
|
370
|
+
this.sessionManager.startSession(this.agent.state);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
347
375
|
async handleEvent(event, state) {
|
|
348
376
|
if (!this.isInitialized) {
|
|
349
377
|
await this.init();
|
|
@@ -582,9 +610,6 @@ export class TuiRenderer {
|
|
|
582
610
|
};
|
|
583
611
|
});
|
|
584
612
|
}
|
|
585
|
-
setInterruptCallback(callback) {
|
|
586
|
-
this.onInterruptCallback = callback;
|
|
587
|
-
}
|
|
588
613
|
handleCtrlC() {
|
|
589
614
|
// Handle Ctrl+C double-press logic
|
|
590
615
|
const now = Date.now();
|
|
@@ -620,8 +645,9 @@ export class TuiRenderer {
|
|
|
620
645
|
const nextLevel = levels[nextIndex];
|
|
621
646
|
// Apply the new thinking level
|
|
622
647
|
this.agent.setThinkingLevel(nextLevel);
|
|
623
|
-
// Save thinking level change to session
|
|
648
|
+
// Save thinking level change to session and settings
|
|
624
649
|
this.sessionManager.saveThinkingLevelChange(nextLevel);
|
|
650
|
+
this.settingsManager.setDefaultThinkingLevel(nextLevel);
|
|
625
651
|
// Update border color
|
|
626
652
|
this.updateEditorBorderColor();
|
|
627
653
|
// Show brief notification
|
|
@@ -631,48 +657,88 @@ export class TuiRenderer {
|
|
|
631
657
|
}
|
|
632
658
|
async cycleModel() {
|
|
633
659
|
// Use scoped models if available, otherwise all available models
|
|
634
|
-
let modelsToUse;
|
|
635
660
|
if (this.scopedModels.length > 0) {
|
|
636
|
-
|
|
661
|
+
// Use scoped models with thinking levels
|
|
662
|
+
if (this.scopedModels.length === 1) {
|
|
663
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
664
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", "Only one model in scope"), 1, 0));
|
|
665
|
+
this.ui.requestRender();
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const currentModel = this.agent.state.model;
|
|
669
|
+
let currentIndex = this.scopedModels.findIndex((sm) => sm.model.id === currentModel?.id && sm.model.provider === currentModel?.provider);
|
|
670
|
+
// If current model not in scope, start from first
|
|
671
|
+
if (currentIndex === -1) {
|
|
672
|
+
currentIndex = 0;
|
|
673
|
+
}
|
|
674
|
+
const nextIndex = (currentIndex + 1) % this.scopedModels.length;
|
|
675
|
+
const nextEntry = this.scopedModels[nextIndex];
|
|
676
|
+
const nextModel = nextEntry.model;
|
|
677
|
+
const nextThinking = nextEntry.thinkingLevel;
|
|
678
|
+
// Validate API key
|
|
679
|
+
const apiKey = await getApiKeyForModel(nextModel);
|
|
680
|
+
if (!apiKey) {
|
|
681
|
+
this.showError(`No API key for ${nextModel.provider}/${nextModel.id}`);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
// Switch model
|
|
685
|
+
this.agent.setModel(nextModel);
|
|
686
|
+
// Save model change to session and settings
|
|
687
|
+
this.sessionManager.saveModelChange(nextModel.provider, nextModel.id);
|
|
688
|
+
this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
|
|
689
|
+
// Apply thinking level (silently use "off" if model doesn't support thinking)
|
|
690
|
+
const effectiveThinking = nextModel.reasoning ? nextThinking : "off";
|
|
691
|
+
this.agent.setThinkingLevel(effectiveThinking);
|
|
692
|
+
this.sessionManager.saveThinkingLevelChange(effectiveThinking);
|
|
693
|
+
this.settingsManager.setDefaultThinkingLevel(effectiveThinking);
|
|
694
|
+
this.updateEditorBorderColor();
|
|
695
|
+
// Show notification
|
|
696
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
697
|
+
const thinkingStr = nextModel.reasoning && nextThinking !== "off" ? ` (thinking: ${nextThinking})` : "";
|
|
698
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `Switched to ${nextModel.name || nextModel.id}${thinkingStr}`), 1, 0));
|
|
699
|
+
this.ui.requestRender();
|
|
637
700
|
}
|
|
638
701
|
else {
|
|
702
|
+
// Fallback to all available models (no thinking level changes)
|
|
639
703
|
const { models: availableModels, error } = await getAvailableModels();
|
|
640
704
|
if (error) {
|
|
641
705
|
this.showError(`Failed to load models: ${error}`);
|
|
642
706
|
return;
|
|
643
707
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
708
|
+
if (availableModels.length === 0) {
|
|
709
|
+
this.showError("No models available to cycle");
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (availableModels.length === 1) {
|
|
713
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
714
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", "Only one model available"), 1, 0));
|
|
715
|
+
this.ui.requestRender();
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const currentModel = this.agent.state.model;
|
|
719
|
+
let currentIndex = availableModels.findIndex((m) => m.id === currentModel?.id && m.provider === currentModel?.provider);
|
|
720
|
+
// If current model not in scope, start from first
|
|
721
|
+
if (currentIndex === -1) {
|
|
722
|
+
currentIndex = 0;
|
|
723
|
+
}
|
|
724
|
+
const nextIndex = (currentIndex + 1) % availableModels.length;
|
|
725
|
+
const nextModel = availableModels[nextIndex];
|
|
726
|
+
// Validate API key
|
|
727
|
+
const apiKey = await getApiKeyForModel(nextModel);
|
|
728
|
+
if (!apiKey) {
|
|
729
|
+
this.showError(`No API key for ${nextModel.provider}/${nextModel.id}`);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
// Switch model
|
|
733
|
+
this.agent.setModel(nextModel);
|
|
734
|
+
// Save model change to session and settings
|
|
735
|
+
this.sessionManager.saveModelChange(nextModel.provider, nextModel.id);
|
|
736
|
+
this.settingsManager.setDefaultModelAndProvider(nextModel.provider, nextModel.id);
|
|
737
|
+
// Show notification
|
|
651
738
|
this.chatContainer.addChild(new Spacer(1));
|
|
652
|
-
this.chatContainer.addChild(new Text(theme.fg("dim",
|
|
739
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", `Switched to ${nextModel.name || nextModel.id}`), 1, 0));
|
|
653
740
|
this.ui.requestRender();
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
const currentModel = this.agent.state.model;
|
|
657
|
-
let currentIndex = modelsToUse.findIndex((m) => m.id === currentModel?.id && m.provider === currentModel?.provider);
|
|
658
|
-
// If current model not in scope, start from first
|
|
659
|
-
if (currentIndex === -1) {
|
|
660
|
-
currentIndex = 0;
|
|
661
|
-
}
|
|
662
|
-
const nextIndex = (currentIndex + 1) % modelsToUse.length;
|
|
663
|
-
const nextModel = modelsToUse[nextIndex];
|
|
664
|
-
// Validate API key
|
|
665
|
-
const apiKey = await getApiKeyForModel(nextModel);
|
|
666
|
-
if (!apiKey) {
|
|
667
|
-
this.showError(`No API key for ${nextModel.provider}/${nextModel.id}`);
|
|
668
|
-
return;
|
|
669
741
|
}
|
|
670
|
-
// Switch model
|
|
671
|
-
this.agent.setModel(nextModel);
|
|
672
|
-
// Show notification
|
|
673
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
674
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", `Switched to ${nextModel.name || nextModel.id}`), 1, 0));
|
|
675
|
-
this.ui.requestRender();
|
|
676
742
|
}
|
|
677
743
|
toggleToolOutputExpansion() {
|
|
678
744
|
this.toolOutputExpanded = !this.toolOutputExpanded;
|
|
@@ -705,8 +771,9 @@ export class TuiRenderer {
|
|
|
705
771
|
this.thinkingSelector = new ThinkingSelectorComponent(this.agent.state.thinkingLevel, (level) => {
|
|
706
772
|
// Apply the selected thinking level
|
|
707
773
|
this.agent.setThinkingLevel(level);
|
|
708
|
-
// Save thinking level change to session
|
|
774
|
+
// Save thinking level change to session and settings
|
|
709
775
|
this.sessionManager.saveThinkingLevelChange(level);
|
|
776
|
+
this.settingsManager.setDefaultThinkingLevel(level);
|
|
710
777
|
// Update border color
|
|
711
778
|
this.updateEditorBorderColor();
|
|
712
779
|
// Show confirmation message with proper spacing
|
|
@@ -1107,6 +1174,35 @@ export class TuiRenderer {
|
|
|
1107
1174
|
this.chatContainer.addChild(new DynamicBorder());
|
|
1108
1175
|
this.ui.requestRender();
|
|
1109
1176
|
}
|
|
1177
|
+
async handleClearCommand() {
|
|
1178
|
+
// Unsubscribe first to prevent processing abort events
|
|
1179
|
+
this.unsubscribe?.();
|
|
1180
|
+
// Abort and wait for completion
|
|
1181
|
+
this.agent.abort();
|
|
1182
|
+
await this.agent.waitForIdle();
|
|
1183
|
+
// Stop loading animation
|
|
1184
|
+
if (this.loadingAnimation) {
|
|
1185
|
+
this.loadingAnimation.stop();
|
|
1186
|
+
this.loadingAnimation = null;
|
|
1187
|
+
}
|
|
1188
|
+
this.statusContainer.clear();
|
|
1189
|
+
// Reset agent and session
|
|
1190
|
+
this.agent.reset();
|
|
1191
|
+
this.sessionManager.reset();
|
|
1192
|
+
// Resubscribe to agent
|
|
1193
|
+
this.subscribeToAgent();
|
|
1194
|
+
// Clear UI state
|
|
1195
|
+
this.chatContainer.clear();
|
|
1196
|
+
this.pendingMessagesContainer.clear();
|
|
1197
|
+
this.queuedMessages = [];
|
|
1198
|
+
this.streamingComponent = null;
|
|
1199
|
+
this.pendingTools.clear();
|
|
1200
|
+
this.isFirstUserMessage = true;
|
|
1201
|
+
// Show confirmation
|
|
1202
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
1203
|
+
this.chatContainer.addChild(new Text(theme.fg("accent", "✓ Context cleared") + "\n" + theme.fg("muted", "Started fresh session"), 1, 1));
|
|
1204
|
+
this.ui.requestRender();
|
|
1205
|
+
}
|
|
1110
1206
|
updatePendingMessagesDisplay() {
|
|
1111
1207
|
this.pendingMessagesContainer.clear();
|
|
1112
1208
|
if (this.queuedMessages.length > 0) {
|