@oh-my-pi/pi-coding-agent 1.341.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 +73 -0
- package/README.md +1 -1
- package/examples/custom-tools/subagent/index.ts +1 -1
- package/package.json +5 -3
- package/src/cli/args.ts +5 -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 +157 -15
- 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 +2 -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 +77 -35
- package/src/core/session-manager.ts +6 -6
- package/src/core/settings-manager.ts +16 -3
- 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 +2 -2
- package/src/core/tools/bash.ts +32 -155
- package/src/core/tools/context.ts +2 -2
- package/src/core/tools/edit-diff.ts +3 -3
- package/src/core/tools/edit.ts +18 -5
- 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 +3 -3
- package/src/core/tools/index.ts +48 -34
- package/src/core/tools/ls.ts +4 -4
- package/src/core/tools/lsp/client.ts +161 -90
- package/src/core/tools/lsp/config.ts +1 -1
- package/src/core/tools/lsp/edits.ts +2 -2
- package/src/core/tools/lsp/index.ts +15 -13
- package/src/core/tools/lsp/render.ts +2 -2
- package/src/core/tools/lsp/rust-analyzer.ts +3 -3
- 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/reviewer.md +52 -37
- 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 +30 -20
- 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 +4 -4
- package/src/index.ts +29 -18
- package/src/main.ts +37 -32
- 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 +281 -59
- 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 +4 -4
- package/src/modes/interactive/components/settings-defs.ts +1 -1
- package/src/modes/interactive/components/settings-selector.ts +5 -5
- 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 +26 -8
- package/src/modes/interactive/components/tree-selector.ts +3 -3
- 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 +2 -2
- package/src/modes/interactive/interactive-mode.ts +85 -41
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Component, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
2
|
-
import { APP_NAME } from "../../../config
|
|
3
|
-
import { theme } from "../theme/theme
|
|
2
|
+
import { APP_NAME } from "../../../config";
|
|
3
|
+
import { theme } from "../theme/theme";
|
|
4
4
|
|
|
5
5
|
export interface RecentSession {
|
|
6
6
|
name: string;
|
|
@@ -23,40 +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 { 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
|
|
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";
|
|
60
60
|
import {
|
|
61
61
|
getAvailableThemes,
|
|
62
62
|
getEditorTheme,
|
|
@@ -65,7 +65,7 @@ import {
|
|
|
65
65
|
setTheme,
|
|
66
66
|
type Theme,
|
|
67
67
|
theme,
|
|
68
|
-
} from "./theme/theme
|
|
68
|
+
} from "./theme/theme";
|
|
69
69
|
|
|
70
70
|
/** Interface for components that can be expanded/collapsed */
|
|
71
71
|
interface Expandable {
|
|
@@ -185,7 +185,7 @@ export class InteractiveMode {
|
|
|
185
185
|
const slashCommands: SlashCommand[] = [
|
|
186
186
|
{ name: "settings", description: "Open settings menu" },
|
|
187
187
|
{ name: "model", description: "Select model (opens selector UI)" },
|
|
188
|
-
{ name: "export", description: "Export session to HTML file" },
|
|
188
|
+
{ name: "export", description: "Export session to HTML file or clipboard (--copy)" },
|
|
189
189
|
{ name: "share", description: "Share session as a secret GitHub gist" },
|
|
190
190
|
{ name: "copy", description: "Copy last agent message to clipboard" },
|
|
191
191
|
{ name: "session", description: "Show session info and stats" },
|
|
@@ -216,9 +216,15 @@ export class InteractiveMode {
|
|
|
216
216
|
description: cmd.description ?? "(hook command)",
|
|
217
217
|
}));
|
|
218
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
|
+
|
|
219
225
|
// Setup autocomplete
|
|
220
226
|
const autocompleteProvider = new CombinedAutocompleteProvider(
|
|
221
|
-
[...slashCommands, ...fileSlashCommands, ...hookCommands],
|
|
227
|
+
[...slashCommands, ...fileSlashCommands, ...hookCommands, ...customCommands],
|
|
222
228
|
process.cwd(),
|
|
223
229
|
fdPath,
|
|
224
230
|
);
|
|
@@ -745,7 +751,7 @@ export class InteractiveMode {
|
|
|
745
751
|
return;
|
|
746
752
|
}
|
|
747
753
|
if (text.startsWith("/export")) {
|
|
748
|
-
this.handleExportCommand(text);
|
|
754
|
+
await this.handleExportCommand(text);
|
|
749
755
|
this.editor.setText("");
|
|
750
756
|
return;
|
|
751
757
|
}
|
|
@@ -867,6 +873,19 @@ export class InteractiveMode {
|
|
|
867
873
|
}
|
|
868
874
|
}
|
|
869
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
|
+
|
|
870
889
|
// Queue regular messages if agent is streaming
|
|
871
890
|
if (this.session.isStreaming) {
|
|
872
891
|
await this.session.queueMessage(text);
|
|
@@ -1232,6 +1251,14 @@ export class InteractiveMode {
|
|
|
1232
1251
|
this.chatContainer.addChild(component);
|
|
1233
1252
|
break;
|
|
1234
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
|
+
}
|
|
1235
1262
|
case "user": {
|
|
1236
1263
|
const textContent = this.getUserMessageText(message);
|
|
1237
1264
|
if (textContent) {
|
|
@@ -2081,12 +2108,29 @@ export class InteractiveMode {
|
|
|
2081
2108
|
// Command handlers
|
|
2082
2109
|
// =========================================================================
|
|
2083
2110
|
|
|
2084
|
-
private handleExportCommand(text: string): void {
|
|
2111
|
+
private async handleExportCommand(text: string): Promise<void> {
|
|
2085
2112
|
const parts = text.split(/\s+/);
|
|
2086
|
-
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
|
+
}
|
|
2087
2130
|
|
|
2131
|
+
// HTML file export
|
|
2088
2132
|
try {
|
|
2089
|
-
const filePath = this.session.exportToHtml(
|
|
2133
|
+
const filePath = this.session.exportToHtml(arg);
|
|
2090
2134
|
this.showStatus(`Session exported to: ${filePath}`);
|
|
2091
2135
|
} catch (error: unknown) {
|
|
2092
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
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell environment snapshot for preserving user aliases, functions, and options.
|
|
3
|
+
*
|
|
4
|
+
* Creates a snapshot file that captures the user's shell environment from their
|
|
5
|
+
* .bashrc/.zshrc, which can be sourced before each command to provide a familiar
|
|
6
|
+
* shell experience.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
10
|
+
import { homedir, tmpdir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
let cachedSnapshotPath: string | null = null;
|
|
14
|
+
let cleanupRegistered = false;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the user's shell config file path.
|
|
18
|
+
*/
|
|
19
|
+
function getShellConfigFile(shell: string): string {
|
|
20
|
+
const home = homedir();
|
|
21
|
+
if (shell.includes("zsh")) return join(home, ".zshrc");
|
|
22
|
+
if (shell.includes("bash")) return join(home, ".bashrc");
|
|
23
|
+
return join(home, ".profile");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate the snapshot creation script.
|
|
28
|
+
* This script sources the user's rc file and extracts functions, aliases, and options.
|
|
29
|
+
* Matches Claude Code's snapshot generation logic.
|
|
30
|
+
*/
|
|
31
|
+
function generateSnapshotScript(shell: string, snapshotPath: string, rcFile: string): string {
|
|
32
|
+
const hasRcFile = existsSync(rcFile);
|
|
33
|
+
const isZsh = shell.includes("zsh");
|
|
34
|
+
|
|
35
|
+
// Escape the snapshot path for shell
|
|
36
|
+
const escapedPath = snapshotPath.replace(/'/g, "'\\''");
|
|
37
|
+
|
|
38
|
+
// Function extraction differs between bash and zsh
|
|
39
|
+
const functionScript = isZsh
|
|
40
|
+
? `
|
|
41
|
+
echo "# Functions" >> "$SNAPSHOT_FILE"
|
|
42
|
+
# Force autoload all functions first
|
|
43
|
+
typeset -f > /dev/null 2>&1
|
|
44
|
+
# Get user function names - filter system/private ones
|
|
45
|
+
typeset +f 2>/dev/null | grep -vE '^(_|__)' | while read func; do
|
|
46
|
+
typeset -f "$func" >> "$SNAPSHOT_FILE" 2>/dev/null
|
|
47
|
+
done
|
|
48
|
+
`
|
|
49
|
+
: `
|
|
50
|
+
echo "# Functions" >> "$SNAPSHOT_FILE"
|
|
51
|
+
# Force autoload all functions first
|
|
52
|
+
declare -f > /dev/null 2>&1
|
|
53
|
+
# Get user function names - filter system/private ones, use base64 for special chars
|
|
54
|
+
declare -F 2>/dev/null | cut -d' ' -f3 | grep -vE '^(_|__)' | while read func; do
|
|
55
|
+
encoded_func=$(declare -f "$func" | base64)
|
|
56
|
+
echo "eval \\"\\$(echo '$encoded_func' | base64 -d)\\" > /dev/null 2>&1" >> "$SNAPSHOT_FILE"
|
|
57
|
+
done
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
// Shell options extraction
|
|
61
|
+
const optionsScript = isZsh
|
|
62
|
+
? `
|
|
63
|
+
echo "# Shell Options" >> "$SNAPSHOT_FILE"
|
|
64
|
+
setopt 2>/dev/null | sed 's/^/setopt /' | head -n 1000 >> "$SNAPSHOT_FILE"
|
|
65
|
+
`
|
|
66
|
+
: `
|
|
67
|
+
echo "# Shell Options" >> "$SNAPSHOT_FILE"
|
|
68
|
+
shopt -p 2>/dev/null | head -n 1000 >> "$SNAPSHOT_FILE"
|
|
69
|
+
set -o 2>/dev/null | grep "on" | awk '{print "set -o " $1}' | head -n 1000 >> "$SNAPSHOT_FILE"
|
|
70
|
+
echo "shopt -s expand_aliases" >> "$SNAPSHOT_FILE"
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
return `
|
|
74
|
+
SNAPSHOT_FILE='${escapedPath}'
|
|
75
|
+
|
|
76
|
+
# Source user's rc file if it exists
|
|
77
|
+
${hasRcFile ? `source "${rcFile}" < /dev/null 2>/dev/null` : "# No user config file to source"}
|
|
78
|
+
|
|
79
|
+
# Create/clear the snapshot file
|
|
80
|
+
echo "# Shell snapshot - generated by pi agent" >| "$SNAPSHOT_FILE"
|
|
81
|
+
|
|
82
|
+
# Unalias everything first to avoid conflicts when sourced
|
|
83
|
+
echo "unalias -a 2>/dev/null || true" >> "$SNAPSHOT_FILE"
|
|
84
|
+
|
|
85
|
+
${functionScript}
|
|
86
|
+
|
|
87
|
+
${optionsScript}
|
|
88
|
+
|
|
89
|
+
# Export aliases (limit to 1000)
|
|
90
|
+
echo "# Aliases" >> "$SNAPSHOT_FILE"
|
|
91
|
+
# Filter out winpty aliases on Windows to avoid "stdin is not a tty" errors
|
|
92
|
+
if [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
|
|
93
|
+
alias 2>/dev/null | grep -v "='winpty " | sed 's/^alias //g' | sed 's/^/alias -- /' | head -n 1000 >> "$SNAPSHOT_FILE"
|
|
94
|
+
else
|
|
95
|
+
alias 2>/dev/null | sed 's/^alias //g' | sed 's/^/alias -- /' | head -n 1000 >> "$SNAPSHOT_FILE"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Export PATH
|
|
99
|
+
echo "export PATH='$PATH'" >> "$SNAPSHOT_FILE"
|
|
100
|
+
|
|
101
|
+
# Verify snapshot was created
|
|
102
|
+
if [ ! -f "$SNAPSHOT_FILE" ]; then
|
|
103
|
+
echo "Error: Snapshot file was not created" >&2
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
`.trim();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create a shell snapshot, caching the result.
|
|
111
|
+
* Returns the path to the snapshot file, or null if creation failed.
|
|
112
|
+
*/
|
|
113
|
+
export async function getOrCreateSnapshot(
|
|
114
|
+
shell: string,
|
|
115
|
+
env: Record<string, string | undefined>,
|
|
116
|
+
): Promise<string | null> {
|
|
117
|
+
// Return cached snapshot if valid
|
|
118
|
+
if (cachedSnapshotPath && existsSync(cachedSnapshotPath)) {
|
|
119
|
+
return cachedSnapshotPath;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Skip on Windows (no .bashrc in standard location)
|
|
123
|
+
if (process.platform === "win32") {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const rcFile = getShellConfigFile(shell);
|
|
128
|
+
|
|
129
|
+
// Create snapshot directory
|
|
130
|
+
const snapshotDir = join(tmpdir(), "pi-shell-snapshots");
|
|
131
|
+
try {
|
|
132
|
+
mkdirSync(snapshotDir, { recursive: true });
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Generate unique snapshot path
|
|
138
|
+
const timestamp = Date.now();
|
|
139
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
140
|
+
const shellName = shell.includes("zsh") ? "zsh" : shell.includes("bash") ? "bash" : "sh";
|
|
141
|
+
const snapshotPath = join(snapshotDir, `snapshot-${shellName}-${timestamp}-${random}.sh`);
|
|
142
|
+
|
|
143
|
+
// Generate and execute snapshot script
|
|
144
|
+
const script = generateSnapshotScript(shell, snapshotPath, rcFile);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const result = Bun.spawnSync([shell, "-l", "-c", script], {
|
|
148
|
+
stdin: "ignore",
|
|
149
|
+
stdout: "pipe",
|
|
150
|
+
stderr: "pipe",
|
|
151
|
+
env,
|
|
152
|
+
timeout: 10000, // 10 second timeout
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (result.exitCode === 0 && existsSync(snapshotPath)) {
|
|
156
|
+
cachedSnapshotPath = snapshotPath;
|
|
157
|
+
registerCleanup();
|
|
158
|
+
return snapshotPath;
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
// Snapshot creation failed, proceed without it
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get the command prefix to source the snapshot.
|
|
169
|
+
* Returns empty string if no snapshot available.
|
|
170
|
+
*/
|
|
171
|
+
export function getSnapshotSourceCommand(snapshotPath: string | null): string {
|
|
172
|
+
if (!snapshotPath) return "";
|
|
173
|
+
// Escape for shell
|
|
174
|
+
const escaped = snapshotPath.replace(/'/g, "'\\''");
|
|
175
|
+
return `source '${escaped}' 2>/dev/null && `;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Register cleanup handler to delete snapshot on process exit.
|
|
180
|
+
*/
|
|
181
|
+
function registerCleanup(): void {
|
|
182
|
+
if (cleanupRegistered) return;
|
|
183
|
+
cleanupRegistered = true;
|
|
184
|
+
|
|
185
|
+
const cleanup = () => {
|
|
186
|
+
if (cachedSnapshotPath && existsSync(cachedSnapshotPath)) {
|
|
187
|
+
try {
|
|
188
|
+
unlinkSync(cachedSnapshotPath);
|
|
189
|
+
} catch {
|
|
190
|
+
// Ignore cleanup errors
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
process.on("exit", cleanup);
|
|
196
|
+
process.on("SIGINT", () => {
|
|
197
|
+
cleanup();
|
|
198
|
+
process.exit(130);
|
|
199
|
+
});
|
|
200
|
+
process.on("SIGTERM", () => {
|
|
201
|
+
cleanup();
|
|
202
|
+
process.exit(143);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Clear the cached snapshot (for testing or forced refresh).
|
|
208
|
+
*/
|
|
209
|
+
export function clearSnapshotCache(): void {
|
|
210
|
+
if (cachedSnapshotPath && existsSync(cachedSnapshotPath)) {
|
|
211
|
+
try {
|
|
212
|
+
unlinkSync(cachedSnapshotPath);
|
|
213
|
+
} catch {
|
|
214
|
+
// Ignore
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
cachedSnapshotPath = null;
|
|
218
|
+
}
|