@oh-my-pi/pi-coding-agent 1.340.0 → 2.0.1337
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 +115 -1
- package/README.md +1 -1
- package/examples/custom-tools/subagent/index.ts +1 -1
- package/package.json +5 -3
- package/src/cli/args.ts +13 -6
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +2 -2
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/session-picker.ts +2 -2
- package/src/cli.ts +1 -1
- package/src/config.ts +3 -3
- package/src/core/agent-session.ts +189 -29
- package/src/core/bash-executor.ts +50 -10
- package/src/core/compaction/branch-summarization.ts +5 -5
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/compaction/index.ts +3 -3
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +232 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +3 -3
- package/src/core/custom-tools/loader.ts +10 -8
- package/src/core/custom-tools/types.ts +11 -6
- package/src/core/custom-tools/wrapper.ts +2 -1
- package/src/core/exec.ts +22 -12
- package/src/core/export-html/index.ts +5 -5
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +5 -5
- package/src/core/hooks/loader.ts +21 -16
- package/src/core/hooks/runner.ts +6 -6
- package/src/core/hooks/tool-wrapper.ts +2 -2
- package/src/core/hooks/types.ts +12 -15
- package/src/core/index.ts +6 -6
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +3 -3
- package/src/core/mcp/config.ts +1 -1
- package/src/core/mcp/index.ts +12 -12
- package/src/core/mcp/loader.ts +2 -2
- package/src/core/mcp/manager.ts +6 -6
- package/src/core/mcp/tool-bridge.ts +3 -3
- package/src/core/mcp/transports/http.ts +1 -1
- package/src/core/mcp/transports/index.ts +2 -2
- package/src/core/mcp/transports/stdio.ts +1 -1
- package/src/core/messages.ts +22 -0
- package/src/core/model-registry.ts +2 -2
- package/src/core/model-resolver.ts +103 -2
- package/src/core/plugins/doctor.ts +1 -1
- package/src/core/plugins/index.ts +6 -6
- package/src/core/plugins/installer.ts +4 -4
- package/src/core/plugins/loader.ts +4 -9
- package/src/core/plugins/manager.ts +5 -5
- package/src/core/plugins/paths.ts +3 -3
- package/src/core/sdk.ts +127 -52
- package/src/core/session-manager.ts +123 -20
- package/src/core/settings-manager.ts +106 -22
- package/src/core/skills.ts +5 -5
- package/src/core/slash-commands.ts +60 -45
- package/src/core/system-prompt.ts +6 -6
- package/src/core/title-generator.ts +94 -0
- package/src/core/tools/bash.ts +33 -157
- package/src/core/tools/context.ts +2 -2
- package/src/core/tools/edit-diff.ts +5 -5
- package/src/core/tools/edit.ts +60 -9
- package/src/core/tools/exa/company.ts +3 -3
- package/src/core/tools/exa/index.ts +16 -17
- package/src/core/tools/exa/linkedin.ts +3 -3
- package/src/core/tools/exa/mcp-client.ts +9 -9
- package/src/core/tools/exa/render.ts +5 -5
- package/src/core/tools/exa/researcher.ts +3 -3
- package/src/core/tools/exa/search.ts +6 -5
- package/src/core/tools/exa/types.ts +5 -6
- package/src/core/tools/exa/websets.ts +3 -3
- package/src/core/tools/find.ts +3 -3
- package/src/core/tools/grep.ts +6 -5
- package/src/core/tools/index.ts +114 -40
- package/src/core/tools/ls.ts +4 -4
- package/src/core/tools/lsp/client.ts +204 -108
- package/src/core/tools/lsp/config.ts +709 -35
- package/src/core/tools/lsp/edits.ts +2 -2
- package/src/core/tools/lsp/index.ts +432 -30
- package/src/core/tools/lsp/render.ts +2 -2
- package/src/core/tools/lsp/rust-analyzer.ts +3 -3
- package/src/core/tools/lsp/types.ts +5 -0
- package/src/core/tools/lsp/utils.ts +1 -1
- package/src/core/tools/notebook.ts +1 -1
- package/src/core/tools/output.ts +175 -0
- package/src/core/tools/read.ts +7 -7
- package/src/core/tools/renderers.ts +92 -13
- package/src/core/tools/review.ts +268 -0
- package/src/core/tools/task/agents.ts +1 -1
- package/src/core/tools/task/bundled-agents/explore.md +1 -1
- package/src/core/tools/task/bundled-agents/reviewer.md +53 -38
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +145 -28
- package/src/core/tools/task/index.ts +78 -30
- package/src/core/tools/task/model-resolver.ts +72 -13
- package/src/core/tools/task/parallel.ts +1 -1
- package/src/core/tools/task/render.ts +219 -30
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +36 -2
- package/src/core/tools/web-fetch.ts +5 -3
- package/src/core/tools/web-search/auth.ts +1 -1
- package/src/core/tools/web-search/index.ts +17 -15
- package/src/core/tools/web-search/providers/anthropic.ts +2 -2
- package/src/core/tools/web-search/providers/exa.ts +3 -5
- package/src/core/tools/web-search/providers/perplexity.ts +1 -1
- package/src/core/tools/web-search/render.ts +3 -3
- package/src/core/tools/write.ts +70 -7
- package/src/index.ts +33 -17
- package/src/main.ts +60 -34
- package/src/migrations.ts +3 -3
- package/src/modes/index.ts +5 -5
- package/src/modes/interactive/components/armin.ts +1 -1
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/bash-execution.ts +4 -4
- package/src/modes/interactive/components/bordered-loader.ts +2 -2
- package/src/modes/interactive/components/branch-summary-message.ts +2 -2
- package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
- package/src/modes/interactive/components/diff.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/footer.ts +5 -5
- package/src/modes/interactive/components/hook-editor.ts +2 -2
- package/src/modes/interactive/components/hook-input.ts +2 -2
- package/src/modes/interactive/components/hook-message.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +2 -2
- package/src/modes/interactive/components/model-selector.ts +341 -41
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/plugin-settings.ts +4 -4
- package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
- package/src/modes/interactive/components/session-selector.ts +24 -11
- package/src/modes/interactive/components/settings-defs.ts +51 -3
- package/src/modes/interactive/components/settings-selector.ts +13 -16
- package/src/modes/interactive/components/show-images-selector.ts +2 -2
- package/src/modes/interactive/components/theme-selector.ts +2 -2
- package/src/modes/interactive/components/thinking-selector.ts +2 -2
- package/src/modes/interactive/components/tool-execution.ts +44 -8
- package/src/modes/interactive/components/tree-selector.ts +5 -5
- package/src/modes/interactive/components/user-message-selector.ts +2 -2
- package/src/modes/interactive/components/user-message.ts +1 -1
- package/src/modes/interactive/components/welcome.ts +42 -5
- package/src/modes/interactive/interactive-mode.ts +169 -48
- package/src/modes/interactive/theme/theme.ts +8 -7
- package/src/modes/print-mode.ts +4 -3
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/modes/rpc/rpc-mode.ts +21 -11
- package/src/modes/rpc/rpc-types.ts +3 -3
- package/src/utils/changelog.ts +2 -2
- package/src/utils/clipboard.ts +1 -1
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +93 -13
- package/src/utils/tools-manager.ts +1 -1
- package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
- package/src/core/tools/exa/logger.ts +0 -56
|
@@ -23,39 +23,40 @@ import {
|
|
|
23
23
|
TUI,
|
|
24
24
|
visibleWidth,
|
|
25
25
|
} from "@oh-my-pi/pi-tui";
|
|
26
|
-
import { getAuthPath, getDebugLogPath } from "../../config
|
|
27
|
-
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session
|
|
28
|
-
import type { CustomToolSessionEvent, LoadedCustomTool } from "../../core/custom-tools/index
|
|
29
|
-
import type { HookUIContext } from "../../core/hooks/index
|
|
30
|
-
import { createCompactionSummaryMessage } from "../../core/messages
|
|
31
|
-
import { type SessionContext, SessionManager } from "../../core/session-manager
|
|
32
|
-
import { loadSkills } from "../../core/skills
|
|
33
|
-
import { loadProjectContextFiles } from "../../core/system-prompt
|
|
34
|
-
import
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
26
|
+
import { getAuthPath, getDebugLogPath } from "../../config";
|
|
27
|
+
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session";
|
|
28
|
+
import type { CustomToolSessionEvent, LoadedCustomTool } from "../../core/custom-tools/index";
|
|
29
|
+
import type { HookUIContext } from "../../core/hooks/index";
|
|
30
|
+
import { createCompactionSummaryMessage } from "../../core/messages";
|
|
31
|
+
import { getRecentSessions, type SessionContext, SessionManager } from "../../core/session-manager";
|
|
32
|
+
import { loadSkills } from "../../core/skills";
|
|
33
|
+
import { loadProjectContextFiles } from "../../core/system-prompt";
|
|
34
|
+
import { generateSessionTitle, setTerminalTitle } from "../../core/title-generator";
|
|
35
|
+
import type { TruncationResult } from "../../core/tools/truncate";
|
|
36
|
+
import { getChangelogPath, parseChangelog } from "../../utils/changelog";
|
|
37
|
+
import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
38
|
+
import { ArminComponent } from "./components/armin";
|
|
39
|
+
import { AssistantMessageComponent } from "./components/assistant-message";
|
|
40
|
+
import { BashExecutionComponent } from "./components/bash-execution";
|
|
41
|
+
import { BorderedLoader } from "./components/bordered-loader";
|
|
42
|
+
import { BranchSummaryMessageComponent } from "./components/branch-summary-message";
|
|
43
|
+
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message";
|
|
44
|
+
import { CustomEditor } from "./components/custom-editor";
|
|
45
|
+
import { DynamicBorder } from "./components/dynamic-border";
|
|
46
|
+
import { FooterComponent } from "./components/footer";
|
|
47
|
+
import { HookEditorComponent } from "./components/hook-editor";
|
|
48
|
+
import { HookInputComponent } from "./components/hook-input";
|
|
49
|
+
import { HookMessageComponent } from "./components/hook-message";
|
|
50
|
+
import { HookSelectorComponent } from "./components/hook-selector";
|
|
51
|
+
import { ModelSelectorComponent } from "./components/model-selector";
|
|
52
|
+
import { OAuthSelectorComponent } from "./components/oauth-selector";
|
|
53
|
+
import { SessionSelectorComponent } from "./components/session-selector";
|
|
54
|
+
import { SettingsSelectorComponent } from "./components/settings-selector";
|
|
55
|
+
import { ToolExecutionComponent } from "./components/tool-execution";
|
|
56
|
+
import { TreeSelectorComponent } from "./components/tree-selector";
|
|
57
|
+
import { UserMessageComponent } from "./components/user-message";
|
|
58
|
+
import { UserMessageSelectorComponent } from "./components/user-message-selector";
|
|
59
|
+
import { WelcomeComponent } from "./components/welcome";
|
|
59
60
|
import {
|
|
60
61
|
getAvailableThemes,
|
|
61
62
|
getEditorTheme,
|
|
@@ -64,7 +65,7 @@ import {
|
|
|
64
65
|
setTheme,
|
|
65
66
|
type Theme,
|
|
66
67
|
theme,
|
|
67
|
-
} from "./theme/theme
|
|
68
|
+
} from "./theme/theme";
|
|
68
69
|
|
|
69
70
|
/** Interface for components that can be expanded/collapsed */
|
|
70
71
|
interface Expandable {
|
|
@@ -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;
|
|
@@ -178,7 +185,7 @@ export class InteractiveMode {
|
|
|
178
185
|
const slashCommands: SlashCommand[] = [
|
|
179
186
|
{ name: "settings", description: "Open settings menu" },
|
|
180
187
|
{ name: "model", description: "Select model (opens selector UI)" },
|
|
181
|
-
{ name: "export", description: "Export session to HTML file" },
|
|
188
|
+
{ name: "export", description: "Export session to HTML file or clipboard (--copy)" },
|
|
182
189
|
{ name: "share", description: "Share session as a secret GitHub gist" },
|
|
183
190
|
{ name: "copy", description: "Copy last agent message to clipboard" },
|
|
184
191
|
{ name: "session", description: "Show session info and stats" },
|
|
@@ -209,9 +216,15 @@ export class InteractiveMode {
|
|
|
209
216
|
description: cmd.description ?? "(hook command)",
|
|
210
217
|
}));
|
|
211
218
|
|
|
219
|
+
// Convert custom commands (TypeScript) to SlashCommand format
|
|
220
|
+
const customCommands: SlashCommand[] = this.session.customCommands.map((loaded) => ({
|
|
221
|
+
name: loaded.command.name,
|
|
222
|
+
description: `${loaded.command.description} (${loaded.source})`,
|
|
223
|
+
}));
|
|
224
|
+
|
|
212
225
|
// Setup autocomplete
|
|
213
226
|
const autocompleteProvider = new CombinedAutocompleteProvider(
|
|
214
|
-
[...slashCommands, ...fileSlashCommands, ...hookCommands],
|
|
227
|
+
[...slashCommands, ...fileSlashCommands, ...hookCommands, ...customCommands],
|
|
215
228
|
process.cwd(),
|
|
216
229
|
fdPath,
|
|
217
230
|
);
|
|
@@ -225,8 +238,29 @@ export class InteractiveMode {
|
|
|
225
238
|
const modelName = this.session.model?.name ?? "Unknown";
|
|
226
239
|
const providerName = this.session.model?.provider ?? "Unknown";
|
|
227
240
|
|
|
241
|
+
// Get recent sessions
|
|
242
|
+
const recentSessions = getRecentSessions(this.sessionManager.getSessionDir()).map((s) => ({
|
|
243
|
+
name: s.name,
|
|
244
|
+
timeAgo: s.timeAgo,
|
|
245
|
+
}));
|
|
246
|
+
|
|
247
|
+
// Convert LSP servers to welcome format
|
|
248
|
+
const lspServerInfo =
|
|
249
|
+
this.lspServers?.map((s) => ({
|
|
250
|
+
name: s.name,
|
|
251
|
+
status: s.status as "ready" | "error" | "connecting",
|
|
252
|
+
fileTypes: s.fileTypes,
|
|
253
|
+
})) ?? [];
|
|
254
|
+
|
|
228
255
|
// Add welcome header
|
|
229
|
-
const welcome = new WelcomeComponent(this.version, modelName, providerName);
|
|
256
|
+
const welcome = new WelcomeComponent(this.version, modelName, providerName, recentSessions, lspServerInfo);
|
|
257
|
+
|
|
258
|
+
// Set terminal title if session already has one (resumed session)
|
|
259
|
+
const existingTitle = this.sessionManager.getSessionTitle();
|
|
260
|
+
if (existingTitle) {
|
|
261
|
+
setTerminalTitle(`pi: ${existingTitle}`);
|
|
262
|
+
this.titleGenerationAttempted = true; // Don't try to generate again
|
|
263
|
+
}
|
|
230
264
|
|
|
231
265
|
// Setup UI layout
|
|
232
266
|
this.ui.addChild(new Spacer(1));
|
|
@@ -717,7 +751,7 @@ export class InteractiveMode {
|
|
|
717
751
|
return;
|
|
718
752
|
}
|
|
719
753
|
if (text.startsWith("/export")) {
|
|
720
|
-
this.handleExportCommand(text);
|
|
754
|
+
await this.handleExportCommand(text);
|
|
721
755
|
this.editor.setText("");
|
|
722
756
|
return;
|
|
723
757
|
}
|
|
@@ -839,6 +873,19 @@ export class InteractiveMode {
|
|
|
839
873
|
}
|
|
840
874
|
}
|
|
841
875
|
|
|
876
|
+
// Custom commands (TypeScript slash commands) - route through session.prompt()
|
|
877
|
+
if (text.startsWith("/") && this.session.customCommands.length > 0) {
|
|
878
|
+
const spaceIndex = text.indexOf(" ");
|
|
879
|
+
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
880
|
+
const hasCustomCommand = this.session.customCommands.some((c) => c.command.name === commandName);
|
|
881
|
+
if (hasCustomCommand) {
|
|
882
|
+
this.editor.addToHistory(text);
|
|
883
|
+
this.editor.setText("");
|
|
884
|
+
await this.session.prompt(text);
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
842
889
|
// Queue regular messages if agent is streaming
|
|
843
890
|
if (this.session.isStreaming) {
|
|
844
891
|
await this.session.queueMessage(text);
|
|
@@ -1025,6 +1072,12 @@ export class InteractiveMode {
|
|
|
1025
1072
|
}
|
|
1026
1073
|
this.pendingTools.clear();
|
|
1027
1074
|
this.ui.requestRender();
|
|
1075
|
+
|
|
1076
|
+
// Generate session title after first turn (if not already titled)
|
|
1077
|
+
if (!this.titleGenerationAttempted && !this.sessionManager.getSessionTitle()) {
|
|
1078
|
+
this.titleGenerationAttempted = true;
|
|
1079
|
+
this.maybeGenerateTitle();
|
|
1080
|
+
}
|
|
1028
1081
|
break;
|
|
1029
1082
|
|
|
1030
1083
|
case "auto_compaction_start": {
|
|
@@ -1198,6 +1251,14 @@ export class InteractiveMode {
|
|
|
1198
1251
|
this.chatContainer.addChild(component);
|
|
1199
1252
|
break;
|
|
1200
1253
|
}
|
|
1254
|
+
case "fileMention": {
|
|
1255
|
+
// Render compact file mention display
|
|
1256
|
+
for (const file of message.files) {
|
|
1257
|
+
const text = `${theme.fg("dim", "⎿ ")}${theme.fg("muted", "Read")} ${theme.fg("accent", file.path)} ${theme.fg("dim", `(${file.lineCount} lines)`)}`;
|
|
1258
|
+
this.chatContainer.addChild(new Text(text, 0, 0));
|
|
1259
|
+
}
|
|
1260
|
+
break;
|
|
1261
|
+
}
|
|
1201
1262
|
case "user": {
|
|
1202
1263
|
const textContent = this.getUserMessageText(message);
|
|
1203
1264
|
if (textContent) {
|
|
@@ -1552,6 +1613,42 @@ export class InteractiveMode {
|
|
|
1552
1613
|
this.ui.requestRender();
|
|
1553
1614
|
}
|
|
1554
1615
|
|
|
1616
|
+
/**
|
|
1617
|
+
* Generate a title for the session based on the first user message.
|
|
1618
|
+
* Runs in background, doesn't block UI.
|
|
1619
|
+
*/
|
|
1620
|
+
private maybeGenerateTitle(): void {
|
|
1621
|
+
// Find the first user message
|
|
1622
|
+
const messages = this.agent.state.messages;
|
|
1623
|
+
const firstUserMessage = messages.find((m) => m.role === "user");
|
|
1624
|
+
if (!firstUserMessage) return;
|
|
1625
|
+
|
|
1626
|
+
// Extract text content
|
|
1627
|
+
let messageText = "";
|
|
1628
|
+
for (const content of firstUserMessage.content) {
|
|
1629
|
+
if (typeof content === "string") {
|
|
1630
|
+
messageText += content;
|
|
1631
|
+
} else if (content.type === "text") {
|
|
1632
|
+
messageText += content.text;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (!messageText.trim()) return;
|
|
1636
|
+
|
|
1637
|
+
// Generate title in background
|
|
1638
|
+
const registry = this.session.modelRegistry;
|
|
1639
|
+
const smolModel = this.settingsManager.getModelRole("smol");
|
|
1640
|
+
generateSessionTitle(messageText, registry, smolModel)
|
|
1641
|
+
.then((title) => {
|
|
1642
|
+
if (title) {
|
|
1643
|
+
this.sessionManager.setSessionTitle(title);
|
|
1644
|
+
setTerminalTitle(`pi: ${title}`);
|
|
1645
|
+
}
|
|
1646
|
+
})
|
|
1647
|
+
.catch(() => {
|
|
1648
|
+
// Ignore title generation errors
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1555
1652
|
private updatePendingMessagesDisplay(): void {
|
|
1556
1653
|
this.pendingMessagesContainer.clear();
|
|
1557
1654
|
const queuedMessages = this.session.getQueuedMessages();
|
|
@@ -1641,6 +1738,9 @@ export class InteractiveMode {
|
|
|
1641
1738
|
case "queueMode":
|
|
1642
1739
|
this.session.setQueueMode(value as "all" | "one-at-a-time");
|
|
1643
1740
|
break;
|
|
1741
|
+
case "interruptMode":
|
|
1742
|
+
this.session.setInterruptMode(value as "immediate" | "wait");
|
|
1743
|
+
break;
|
|
1644
1744
|
case "thinkingLevel":
|
|
1645
1745
|
this.session.setThinkingLevel(value as ThinkingLevel);
|
|
1646
1746
|
this.footer.invalidate();
|
|
@@ -1687,17 +1787,21 @@ export class InteractiveMode {
|
|
|
1687
1787
|
this.settingsManager,
|
|
1688
1788
|
this.session.modelRegistry,
|
|
1689
1789
|
this.session.scopedModels,
|
|
1690
|
-
async (model) => {
|
|
1790
|
+
async (model, role) => {
|
|
1691
1791
|
try {
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1792
|
+
// Only update agent state for default role
|
|
1793
|
+
if (role === "default") {
|
|
1794
|
+
await this.session.setModel(model, role);
|
|
1795
|
+
this.footer.invalidate();
|
|
1796
|
+
this.updateEditorBorderColor();
|
|
1797
|
+
}
|
|
1798
|
+
// For other roles (small), just show status - settings already updated by selector
|
|
1799
|
+
const roleLabel = role === "default" ? "Default" : role === "smol" ? "Smol" : role;
|
|
1800
|
+
this.showStatus(`${roleLabel} model: ${model.id}`);
|
|
1697
1801
|
} catch (error) {
|
|
1698
|
-
done();
|
|
1699
1802
|
this.showError(error instanceof Error ? error.message : String(error));
|
|
1700
1803
|
}
|
|
1804
|
+
// Don't call done() - selector stays open
|
|
1701
1805
|
},
|
|
1702
1806
|
() => {
|
|
1703
1807
|
done();
|
|
@@ -2004,12 +2108,29 @@ export class InteractiveMode {
|
|
|
2004
2108
|
// Command handlers
|
|
2005
2109
|
// =========================================================================
|
|
2006
2110
|
|
|
2007
|
-
private handleExportCommand(text: string): void {
|
|
2111
|
+
private async handleExportCommand(text: string): Promise<void> {
|
|
2008
2112
|
const parts = text.split(/\s+/);
|
|
2009
|
-
const
|
|
2113
|
+
const arg = parts.length > 1 ? parts[1] : undefined;
|
|
2114
|
+
|
|
2115
|
+
// Check for clipboard export
|
|
2116
|
+
if (arg === "--copy" || arg === "clipboard" || arg === "copy") {
|
|
2117
|
+
try {
|
|
2118
|
+
const formatted = this.session.formatSessionAsText();
|
|
2119
|
+
if (!formatted) {
|
|
2120
|
+
this.showError("No messages to export yet.");
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
await copyToClipboard(formatted);
|
|
2124
|
+
this.showStatus("Session copied to clipboard");
|
|
2125
|
+
} catch (error: unknown) {
|
|
2126
|
+
this.showError(`Failed to copy session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
2127
|
+
}
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2010
2130
|
|
|
2131
|
+
// HTML file export
|
|
2011
2132
|
try {
|
|
2012
|
-
const filePath = this.session.exportToHtml(
|
|
2133
|
+
const filePath = this.session.exportToHtml(arg);
|
|
2013
2134
|
this.showStatus(`Session exported to: ${filePath}`);
|
|
2014
2135
|
} catch (error: unknown) {
|
|
2015
2136
|
this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -5,7 +5,8 @@ import { type Static, Type } from "@sinclair/typebox";
|
|
|
5
5
|
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { highlight, supportsLanguage } from "cli-highlight";
|
|
8
|
-
import { getCustomThemesDir, getThemesDir } from "../../../config
|
|
8
|
+
import { getCustomThemesDir, getThemesDir } from "../../../config";
|
|
9
|
+
import { logger } from "../../../core/logger";
|
|
9
10
|
|
|
10
11
|
// ============================================================================
|
|
11
12
|
// Types & Schema
|
|
@@ -590,8 +591,8 @@ export function initTheme(themeName?: string, enableWatcher: boolean = false): v
|
|
|
590
591
|
if (enableWatcher) {
|
|
591
592
|
startThemeWatcher();
|
|
592
593
|
}
|
|
593
|
-
} catch (
|
|
594
|
-
|
|
594
|
+
} catch (err) {
|
|
595
|
+
logger.debug("Theme loading failed, falling back to dark theme", { error: String(err) });
|
|
595
596
|
currentThemeName = "dark";
|
|
596
597
|
theme = loadTheme("dark");
|
|
597
598
|
// Don't start watcher for fallback theme
|
|
@@ -654,8 +655,8 @@ function startThemeWatcher(): void {
|
|
|
654
655
|
if (onThemeChangeCallback) {
|
|
655
656
|
onThemeChangeCallback();
|
|
656
657
|
}
|
|
657
|
-
} catch (
|
|
658
|
-
|
|
658
|
+
} catch (err) {
|
|
659
|
+
logger.debug("Theme reload error during file change", { error: String(err) });
|
|
659
660
|
}
|
|
660
661
|
}, 100);
|
|
661
662
|
} else if (eventType === "rename") {
|
|
@@ -675,8 +676,8 @@ function startThemeWatcher(): void {
|
|
|
675
676
|
}, 100);
|
|
676
677
|
}
|
|
677
678
|
});
|
|
678
|
-
} catch (
|
|
679
|
-
|
|
679
|
+
} catch (err) {
|
|
680
|
+
logger.debug("Failed to start theme watcher", { error: String(err) });
|
|
680
681
|
}
|
|
681
682
|
}
|
|
682
683
|
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
|
|
10
|
-
import type { AgentSession } from "../core/agent-session
|
|
10
|
+
import type { AgentSession } from "../core/agent-session";
|
|
11
|
+
import { logger } from "../core/logger";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Run in print (single-shot) mode.
|
|
@@ -70,8 +71,8 @@ export async function runPrintMode(
|
|
|
70
71
|
},
|
|
71
72
|
},
|
|
72
73
|
);
|
|
73
|
-
} catch (
|
|
74
|
-
|
|
74
|
+
} catch (err) {
|
|
75
|
+
logger.warn("Tool onSession error", { error: String(err) });
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
}
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import type { Subprocess } from "bun";
|
|
10
|
-
import type { SessionStats } from "../../core/agent-session
|
|
11
|
-
import type { BashResult } from "../../core/bash-executor
|
|
12
|
-
import type { CompactionResult } from "../../core/compaction/index
|
|
13
|
-
import type { RpcCommand, RpcResponse, RpcSessionState } from "./rpc-types
|
|
10
|
+
import type { SessionStats } from "../../core/agent-session";
|
|
11
|
+
import type { BashResult } from "../../core/bash-executor";
|
|
12
|
+
import type { CompactionResult } from "../../core/compaction/index";
|
|
13
|
+
import type { RpcCommand, RpcResponse, RpcSessionState } from "./rpc-types";
|
|
14
14
|
|
|
15
15
|
// ============================================================================
|
|
16
16
|
// Types
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
* - Hook UI: Hook UI requests are emitted, client responds with hook_ui_response
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type { AgentSession } from "../../core/agent-session
|
|
15
|
-
import type { HookUIContext } from "../../core/hooks/index
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
14
|
+
import type { AgentSession } from "../../core/agent-session";
|
|
15
|
+
import type { HookUIContext } from "../../core/hooks/index";
|
|
16
|
+
import { logger } from "../../core/logger";
|
|
17
|
+
import { theme } from "../interactive/theme/theme";
|
|
18
|
+
import type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from "./rpc-types";
|
|
18
19
|
|
|
19
20
|
// Re-export types for consumers
|
|
20
|
-
export type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from "./rpc-types
|
|
21
|
+
export type { RpcCommand, RpcHookUIRequest, RpcHookUIResponse, RpcResponse, RpcSessionState } from "./rpc-types";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Run in RPC mode.
|
|
@@ -220,8 +221,8 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
220
221
|
},
|
|
221
222
|
},
|
|
222
223
|
);
|
|
223
|
-
} catch (
|
|
224
|
-
|
|
224
|
+
} catch (err) {
|
|
225
|
+
logger.warn("Tool onSession error", { error: String(err) });
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
}
|
|
@@ -231,6 +232,9 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
231
232
|
output(event);
|
|
232
233
|
});
|
|
233
234
|
|
|
235
|
+
// Serialize prompt commands to prevent concurrent execution
|
|
236
|
+
let activePrompt: Promise<void> | null = null;
|
|
237
|
+
|
|
234
238
|
// Handle a single command
|
|
235
239
|
const handleCommand = async (command: RpcCommand): Promise<RpcResponse> => {
|
|
236
240
|
const id = command.id;
|
|
@@ -241,13 +245,18 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
241
245
|
// =================================================================
|
|
242
246
|
|
|
243
247
|
case "prompt": {
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
// Serialize prompts to prevent concurrent execution
|
|
249
|
+
if (activePrompt) {
|
|
250
|
+
await activePrompt;
|
|
251
|
+
}
|
|
252
|
+
activePrompt = session
|
|
247
253
|
.prompt(command.message, {
|
|
248
254
|
images: command.images,
|
|
249
255
|
})
|
|
250
|
-
.catch((e) => output(error(id, "prompt", e.message)))
|
|
256
|
+
.catch((e) => output(error(id, "prompt", e.message)))
|
|
257
|
+
.finally(() => {
|
|
258
|
+
activePrompt = null;
|
|
259
|
+
});
|
|
251
260
|
return success(id, "prompt");
|
|
252
261
|
}
|
|
253
262
|
|
|
@@ -462,6 +471,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
462
471
|
const response = parsed as RpcHookUIResponse;
|
|
463
472
|
const pending = pendingHookRequests.get(response.id);
|
|
464
473
|
if (pending) {
|
|
474
|
+
// Atomic delete: remove before resolve to prevent double-resolution
|
|
465
475
|
pendingHookRequests.delete(response.id);
|
|
466
476
|
pending.resolve(response);
|
|
467
477
|
}
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
9
9
|
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
10
|
-
import type { SessionStats } from "../../core/agent-session
|
|
11
|
-
import type { BashResult } from "../../core/bash-executor
|
|
12
|
-
import type { CompactionResult } from "../../core/compaction/index
|
|
10
|
+
import type { SessionStats } from "../../core/agent-session";
|
|
11
|
+
import type { BashResult } from "../../core/bash-executor";
|
|
12
|
+
import type { CompactionResult } from "../../core/compaction/index";
|
|
13
13
|
|
|
14
14
|
// ============================================================================
|
|
15
15
|
// RPC Commands (stdin)
|
package/src/utils/changelog.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
|
|
3
3
|
export interface ChangelogEntry {
|
|
4
4
|
major: number;
|
|
@@ -96,4 +96,4 @@ export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): C
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// Re-export getChangelogPath from paths.ts for convenience
|
|
99
|
-
export { getChangelogPath } from "../config
|
|
99
|
+
export { getChangelogPath } from "../config";
|
package/src/utils/clipboard.ts
CHANGED