@oh-my-pi/pi-coding-agent 1.340.0 → 1.341.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 -1
- package/package.json +1 -1
- package/src/cli/args.ts +8 -0
- package/src/core/agent-session.ts +32 -14
- package/src/core/model-resolver.ts +101 -0
- package/src/core/sdk.ts +50 -17
- package/src/core/session-manager.ts +117 -14
- package/src/core/settings-manager.ts +90 -19
- package/src/core/title-generator.ts +94 -0
- package/src/core/tools/bash.ts +1 -2
- package/src/core/tools/edit-diff.ts +2 -2
- package/src/core/tools/edit.ts +43 -5
- package/src/core/tools/grep.ts +3 -2
- package/src/core/tools/index.ts +73 -13
- package/src/core/tools/lsp/client.ts +45 -20
- package/src/core/tools/lsp/config.ts +708 -34
- package/src/core/tools/lsp/index.ts +423 -23
- package/src/core/tools/lsp/types.ts +5 -0
- package/src/core/tools/task/bundled-agents/explore.md +1 -1
- package/src/core/tools/task/bundled-agents/reviewer.md +1 -1
- package/src/core/tools/task/model-resolver.ts +52 -3
- package/src/core/tools/write.ts +67 -4
- package/src/index.ts +5 -0
- package/src/main.ts +23 -2
- package/src/modes/interactive/components/model-selector.ts +96 -18
- package/src/modes/interactive/components/session-selector.ts +20 -7
- package/src/modes/interactive/components/settings-defs.ts +50 -2
- package/src/modes/interactive/components/settings-selector.ts +8 -11
- package/src/modes/interactive/components/tool-execution.ts +18 -0
- package/src/modes/interactive/components/tree-selector.ts +2 -2
- package/src/modes/interactive/components/welcome.ts +40 -3
- package/src/modes/interactive/interactive-mode.ts +86 -9
|
@@ -7,6 +7,12 @@ export interface RecentSession {
|
|
|
7
7
|
timeAgo: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export interface LspServerInfo {
|
|
11
|
+
name: string;
|
|
12
|
+
status: "ready" | "error" | "connecting";
|
|
13
|
+
fileTypes: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
/**
|
|
11
17
|
* Premium welcome screen with block-based Pi logo and two-column layout.
|
|
12
18
|
*/
|
|
@@ -15,12 +21,20 @@ export class WelcomeComponent implements Component {
|
|
|
15
21
|
private modelName: string;
|
|
16
22
|
private providerName: string;
|
|
17
23
|
private recentSessions: RecentSession[];
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
private lspServers: LspServerInfo[];
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
version: string,
|
|
28
|
+
modelName: string,
|
|
29
|
+
providerName: string,
|
|
30
|
+
recentSessions: RecentSession[] = [],
|
|
31
|
+
lspServers: LspServerInfo[] = [],
|
|
32
|
+
) {
|
|
20
33
|
this.version = version;
|
|
21
34
|
this.modelName = modelName;
|
|
22
35
|
this.providerName = providerName;
|
|
23
36
|
this.recentSessions = recentSessions;
|
|
37
|
+
this.lspServers = lspServers;
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
invalidate(): void {}
|
|
@@ -34,6 +48,10 @@ export class WelcomeComponent implements Component {
|
|
|
34
48
|
this.recentSessions = sessions;
|
|
35
49
|
}
|
|
36
50
|
|
|
51
|
+
setLspServers(servers: LspServerInfo[]): void {
|
|
52
|
+
this.lspServers = servers;
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
render(termWidth: number): string[] {
|
|
38
56
|
// Box dimensions - responsive with min/max
|
|
39
57
|
const minWidth = 80;
|
|
@@ -76,13 +94,32 @@ export class WelcomeComponent implements Component {
|
|
|
76
94
|
}
|
|
77
95
|
}
|
|
78
96
|
|
|
97
|
+
// LSP servers content
|
|
98
|
+
const lspLines: string[] = [];
|
|
99
|
+
if (this.lspServers.length === 0) {
|
|
100
|
+
lspLines.push(` ${theme.fg("dim", "No LSP servers")}`);
|
|
101
|
+
} else {
|
|
102
|
+
for (const server of this.lspServers) {
|
|
103
|
+
const icon =
|
|
104
|
+
server.status === "ready"
|
|
105
|
+
? theme.fg("success", "●")
|
|
106
|
+
: server.status === "connecting"
|
|
107
|
+
? theme.fg("warning", "○")
|
|
108
|
+
: theme.fg("error", "●");
|
|
109
|
+
const exts = server.fileTypes.slice(0, 3).join(" ");
|
|
110
|
+
lspLines.push(` ${icon} ${theme.fg("muted", server.name)} ${theme.fg("dim", exts)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
79
114
|
// Right column
|
|
80
115
|
const rightLines = [
|
|
81
116
|
` ${theme.bold(theme.fg("accent", "Tips"))}`,
|
|
82
117
|
` ${theme.fg("dim", "?")}${theme.fg("muted", " for keyboard shortcuts")}`,
|
|
83
118
|
` ${theme.fg("dim", "/")}${theme.fg("muted", " for commands")}`,
|
|
84
119
|
` ${theme.fg("dim", "!")}${theme.fg("muted", " to run bash")}`,
|
|
85
|
-
|
|
120
|
+
separator,
|
|
121
|
+
` ${theme.bold(theme.fg("accent", "LSP Servers"))}`,
|
|
122
|
+
...lspLines,
|
|
86
123
|
separator,
|
|
87
124
|
` ${theme.bold(theme.fg("accent", "Recent sessions"))}`,
|
|
88
125
|
...sessionLines,
|
|
@@ -28,9 +28,10 @@ import type { AgentSession, AgentSessionEvent } from "../../core/agent-session.j
|
|
|
28
28
|
import type { CustomToolSessionEvent, LoadedCustomTool } from "../../core/custom-tools/index.js";
|
|
29
29
|
import type { HookUIContext } from "../../core/hooks/index.js";
|
|
30
30
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
31
|
-
import { type SessionContext, SessionManager } from "../../core/session-manager.js";
|
|
31
|
+
import { getRecentSessions, type SessionContext, SessionManager } from "../../core/session-manager.js";
|
|
32
32
|
import { loadSkills } from "../../core/skills.js";
|
|
33
33
|
import { loadProjectContextFiles } from "../../core/system-prompt.js";
|
|
34
|
+
import { generateSessionTitle, setTerminalTitle } from "../../core/title-generator.js";
|
|
34
35
|
import type { TruncationResult } from "../../core/tools/truncate.js";
|
|
35
36
|
import { getChangelogPath, parseChangelog } from "../../utils/changelog.js";
|
|
36
37
|
import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard.js";
|
|
@@ -141,6 +142,9 @@ export class InteractiveMode {
|
|
|
141
142
|
// Custom tools for custom rendering
|
|
142
143
|
private customTools: Map<string, LoadedCustomTool>;
|
|
143
144
|
|
|
145
|
+
// Title generation state
|
|
146
|
+
private titleGenerationAttempted = false;
|
|
147
|
+
|
|
144
148
|
// Convenience accessors
|
|
145
149
|
private get agent() {
|
|
146
150
|
return this.session.agent;
|
|
@@ -158,6 +162,9 @@ export class InteractiveMode {
|
|
|
158
162
|
changelogMarkdown: string | undefined = undefined,
|
|
159
163
|
customTools: LoadedCustomTool[] = [],
|
|
160
164
|
private setToolUIContext: (uiContext: HookUIContext, hasUI: boolean) => void = () => {},
|
|
165
|
+
private lspServers:
|
|
166
|
+
| Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }>
|
|
167
|
+
| undefined = undefined,
|
|
161
168
|
fdPath: string | undefined = undefined,
|
|
162
169
|
) {
|
|
163
170
|
this.session = session;
|
|
@@ -225,8 +232,29 @@ export class InteractiveMode {
|
|
|
225
232
|
const modelName = this.session.model?.name ?? "Unknown";
|
|
226
233
|
const providerName = this.session.model?.provider ?? "Unknown";
|
|
227
234
|
|
|
235
|
+
// Get recent sessions
|
|
236
|
+
const recentSessions = getRecentSessions(this.sessionManager.getSessionDir()).map((s) => ({
|
|
237
|
+
name: s.name,
|
|
238
|
+
timeAgo: s.timeAgo,
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
// Convert LSP servers to welcome format
|
|
242
|
+
const lspServerInfo =
|
|
243
|
+
this.lspServers?.map((s) => ({
|
|
244
|
+
name: s.name,
|
|
245
|
+
status: s.status as "ready" | "error" | "connecting",
|
|
246
|
+
fileTypes: s.fileTypes,
|
|
247
|
+
})) ?? [];
|
|
248
|
+
|
|
228
249
|
// Add welcome header
|
|
229
|
-
const welcome = new WelcomeComponent(this.version, modelName, providerName);
|
|
250
|
+
const welcome = new WelcomeComponent(this.version, modelName, providerName, recentSessions, lspServerInfo);
|
|
251
|
+
|
|
252
|
+
// Set terminal title if session already has one (resumed session)
|
|
253
|
+
const existingTitle = this.sessionManager.getSessionTitle();
|
|
254
|
+
if (existingTitle) {
|
|
255
|
+
setTerminalTitle(`pi: ${existingTitle}`);
|
|
256
|
+
this.titleGenerationAttempted = true; // Don't try to generate again
|
|
257
|
+
}
|
|
230
258
|
|
|
231
259
|
// Setup UI layout
|
|
232
260
|
this.ui.addChild(new Spacer(1));
|
|
@@ -1025,6 +1053,12 @@ export class InteractiveMode {
|
|
|
1025
1053
|
}
|
|
1026
1054
|
this.pendingTools.clear();
|
|
1027
1055
|
this.ui.requestRender();
|
|
1056
|
+
|
|
1057
|
+
// Generate session title after first turn (if not already titled)
|
|
1058
|
+
if (!this.titleGenerationAttempted && !this.sessionManager.getSessionTitle()) {
|
|
1059
|
+
this.titleGenerationAttempted = true;
|
|
1060
|
+
this.maybeGenerateTitle();
|
|
1061
|
+
}
|
|
1028
1062
|
break;
|
|
1029
1063
|
|
|
1030
1064
|
case "auto_compaction_start": {
|
|
@@ -1552,6 +1586,42 @@ export class InteractiveMode {
|
|
|
1552
1586
|
this.ui.requestRender();
|
|
1553
1587
|
}
|
|
1554
1588
|
|
|
1589
|
+
/**
|
|
1590
|
+
* Generate a title for the session based on the first user message.
|
|
1591
|
+
* Runs in background, doesn't block UI.
|
|
1592
|
+
*/
|
|
1593
|
+
private maybeGenerateTitle(): void {
|
|
1594
|
+
// Find the first user message
|
|
1595
|
+
const messages = this.agent.state.messages;
|
|
1596
|
+
const firstUserMessage = messages.find((m) => m.role === "user");
|
|
1597
|
+
if (!firstUserMessage) return;
|
|
1598
|
+
|
|
1599
|
+
// Extract text content
|
|
1600
|
+
let messageText = "";
|
|
1601
|
+
for (const content of firstUserMessage.content) {
|
|
1602
|
+
if (typeof content === "string") {
|
|
1603
|
+
messageText += content;
|
|
1604
|
+
} else if (content.type === "text") {
|
|
1605
|
+
messageText += content.text;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (!messageText.trim()) return;
|
|
1609
|
+
|
|
1610
|
+
// Generate title in background
|
|
1611
|
+
const registry = this.session.modelRegistry;
|
|
1612
|
+
const smolModel = this.settingsManager.getModelRole("smol");
|
|
1613
|
+
generateSessionTitle(messageText, registry, smolModel)
|
|
1614
|
+
.then((title) => {
|
|
1615
|
+
if (title) {
|
|
1616
|
+
this.sessionManager.setSessionTitle(title);
|
|
1617
|
+
setTerminalTitle(`pi: ${title}`);
|
|
1618
|
+
}
|
|
1619
|
+
})
|
|
1620
|
+
.catch(() => {
|
|
1621
|
+
// Ignore title generation errors
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1555
1625
|
private updatePendingMessagesDisplay(): void {
|
|
1556
1626
|
this.pendingMessagesContainer.clear();
|
|
1557
1627
|
const queuedMessages = this.session.getQueuedMessages();
|
|
@@ -1641,6 +1711,9 @@ export class InteractiveMode {
|
|
|
1641
1711
|
case "queueMode":
|
|
1642
1712
|
this.session.setQueueMode(value as "all" | "one-at-a-time");
|
|
1643
1713
|
break;
|
|
1714
|
+
case "interruptMode":
|
|
1715
|
+
this.session.setInterruptMode(value as "immediate" | "wait");
|
|
1716
|
+
break;
|
|
1644
1717
|
case "thinkingLevel":
|
|
1645
1718
|
this.session.setThinkingLevel(value as ThinkingLevel);
|
|
1646
1719
|
this.footer.invalidate();
|
|
@@ -1687,17 +1760,21 @@ export class InteractiveMode {
|
|
|
1687
1760
|
this.settingsManager,
|
|
1688
1761
|
this.session.modelRegistry,
|
|
1689
1762
|
this.session.scopedModels,
|
|
1690
|
-
async (model) => {
|
|
1763
|
+
async (model, role) => {
|
|
1691
1764
|
try {
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1765
|
+
// Only update agent state for default role
|
|
1766
|
+
if (role === "default") {
|
|
1767
|
+
await this.session.setModel(model, role);
|
|
1768
|
+
this.footer.invalidate();
|
|
1769
|
+
this.updateEditorBorderColor();
|
|
1770
|
+
}
|
|
1771
|
+
// For other roles (small), just show status - settings already updated by selector
|
|
1772
|
+
const roleLabel = role === "default" ? "Default" : role === "smol" ? "Smol" : role;
|
|
1773
|
+
this.showStatus(`${roleLabel} model: ${model.id}`);
|
|
1697
1774
|
} catch (error) {
|
|
1698
|
-
done();
|
|
1699
1775
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
1700
1776
|
}
|
|
1777
|
+
// Don't call done() - selector stays open
|
|
1701
1778
|
},
|
|
1702
1779
|
() => {
|
|
1703
1780
|
done();
|