@oh-my-pi/pi-coding-agent 1.337.1 → 1.340.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 +32 -0
- package/package.json +3 -3
- package/src/cli/args.ts +14 -8
- package/src/core/export-html/index.ts +48 -15
- package/src/core/export-html/template.html +3 -11
- package/src/core/mcp/client.ts +43 -16
- package/src/core/mcp/config.ts +152 -6
- package/src/core/mcp/index.ts +6 -2
- package/src/core/mcp/loader.ts +30 -3
- package/src/core/mcp/manager.ts +69 -10
- package/src/core/mcp/types.ts +9 -3
- package/src/core/sdk.ts +19 -3
- package/src/core/settings-manager.ts +34 -0
- package/src/modes/interactive/components/settings-defs.ts +229 -0
- package/src/modes/interactive/components/settings-selector.ts +156 -234
- package/src/modes/interactive/interactive-mode.ts +58 -75
- package/src/utils/shell.ts +12 -4
- package/src/utils/tools-manager.ts +5 -14
- package/src/core/export-html/vendor/highlight.min.js +0 -1213
- package/src/core/export-html/vendor/marked.min.js +0 -6
package/src/core/mcp/types.ts
CHANGED
|
@@ -41,8 +41,14 @@ export type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcRespo
|
|
|
41
41
|
// MCP Server Configuration (.mcp.json format)
|
|
42
42
|
// =============================================================================
|
|
43
43
|
|
|
44
|
+
/** Base server config with shared options */
|
|
45
|
+
interface MCPServerConfigBase {
|
|
46
|
+
/** Connection timeout in milliseconds (default: 30000) */
|
|
47
|
+
timeout?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
/** Stdio server configuration */
|
|
45
|
-
export interface MCPStdioServerConfig {
|
|
51
|
+
export interface MCPStdioServerConfig extends MCPServerConfigBase {
|
|
46
52
|
type?: "stdio"; // Default if not specified
|
|
47
53
|
command: string;
|
|
48
54
|
args?: string[];
|
|
@@ -51,14 +57,14 @@ export interface MCPStdioServerConfig {
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
/** HTTP server configuration (Streamable HTTP transport) */
|
|
54
|
-
export interface MCPHttpServerConfig {
|
|
60
|
+
export interface MCPHttpServerConfig extends MCPServerConfigBase {
|
|
55
61
|
type: "http";
|
|
56
62
|
url: string;
|
|
57
63
|
headers?: Record<string, string>;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
/** SSE server configuration (deprecated, use HTTP) */
|
|
61
|
-
export interface MCPSseServerConfig {
|
|
67
|
+
export interface MCPSseServerConfig extends MCPServerConfigBase {
|
|
62
68
|
type: "sse";
|
|
63
69
|
url: string;
|
|
64
70
|
headers?: Record<string, string>;
|
package/src/core/sdk.ts
CHANGED
|
@@ -596,10 +596,24 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
596
596
|
let mcpManager: MCPManager | undefined;
|
|
597
597
|
const enableMCP = options.enableMCP ?? true;
|
|
598
598
|
if (enableMCP) {
|
|
599
|
-
const mcpResult = await discoverAndLoadMCPTools(cwd
|
|
599
|
+
const mcpResult = await discoverAndLoadMCPTools(cwd, {
|
|
600
|
+
onConnecting: (serverNames) => {
|
|
601
|
+
if (options.hasUI && serverNames.length > 0) {
|
|
602
|
+
process.stderr.write(`\x1b[90mConnecting to MCP servers: ${serverNames.join(", ")}...\x1b[0m\n`);
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
enableProjectConfig: settingsManager.getMCPProjectConfigEnabled(),
|
|
606
|
+
// Always filter Exa - we have native integration
|
|
607
|
+
filterExa: true,
|
|
608
|
+
});
|
|
600
609
|
time("discoverAndLoadMCPTools");
|
|
601
610
|
mcpManager = mcpResult.manager;
|
|
602
611
|
|
|
612
|
+
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
|
|
613
|
+
if (mcpResult.exaApiKeys.length > 0 && !process.env.EXA_API_KEY) {
|
|
614
|
+
process.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
|
|
615
|
+
}
|
|
616
|
+
|
|
603
617
|
// Log MCP errors
|
|
604
618
|
for (const { path, error } of mcpResult.errors) {
|
|
605
619
|
console.error(`MCP "${path}": ${error}`);
|
|
@@ -664,8 +678,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
664
678
|
let allToolsArray: Tool[] = [...builtInTools, ...wrappedCustomTools];
|
|
665
679
|
time("combineTools");
|
|
666
680
|
|
|
667
|
-
// Apply bash interception to redirect common shell patterns to proper tools
|
|
668
|
-
|
|
681
|
+
// Apply bash interception to redirect common shell patterns to proper tools (if enabled)
|
|
682
|
+
if (settingsManager.getBashInterceptorEnabled()) {
|
|
683
|
+
allToolsArray = applyBashInterception(allToolsArray);
|
|
684
|
+
}
|
|
669
685
|
time("applyBashInterception");
|
|
670
686
|
|
|
671
687
|
if (hookRunner) {
|
|
@@ -43,6 +43,14 @@ export interface ExaSettings {
|
|
|
43
43
|
enableWebsets?: boolean; // default: false
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export interface BashInterceptorSettings {
|
|
47
|
+
enabled?: boolean; // default: false (blocks shell commands that have dedicated tools)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface MCPSettings {
|
|
51
|
+
enableProjectConfig?: boolean; // default: true (load .mcp.json from project root)
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
export interface Settings {
|
|
47
55
|
lastChangelogVersion?: string;
|
|
48
56
|
defaultProvider?: string;
|
|
@@ -62,6 +70,8 @@ export interface Settings {
|
|
|
62
70
|
terminal?: TerminalSettings;
|
|
63
71
|
enabledModels?: string[]; // Model patterns for cycling (same format as --models CLI flag)
|
|
64
72
|
exa?: ExaSettings;
|
|
73
|
+
bashInterceptor?: BashInterceptorSettings;
|
|
74
|
+
mcp?: MCPSettings;
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
/** Deep merge settings: project/overrides take precedence, nested objects merge recursively */
|
|
@@ -440,4 +450,28 @@ export class SettingsManager {
|
|
|
440
450
|
this.globalSettings.exa.enableWebsets = enabled;
|
|
441
451
|
this.save();
|
|
442
452
|
}
|
|
453
|
+
|
|
454
|
+
getBashInterceptorEnabled(): boolean {
|
|
455
|
+
return this.settings.bashInterceptor?.enabled ?? false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
setBashInterceptorEnabled(enabled: boolean): void {
|
|
459
|
+
if (!this.globalSettings.bashInterceptor) {
|
|
460
|
+
this.globalSettings.bashInterceptor = {};
|
|
461
|
+
}
|
|
462
|
+
this.globalSettings.bashInterceptor.enabled = enabled;
|
|
463
|
+
this.save();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
getMCPProjectConfigEnabled(): boolean {
|
|
467
|
+
return this.settings.mcp?.enableProjectConfig ?? true;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
setMCPProjectConfigEnabled(enabled: boolean): void {
|
|
471
|
+
if (!this.globalSettings.mcp) {
|
|
472
|
+
this.globalSettings.mcp = {};
|
|
473
|
+
}
|
|
474
|
+
this.globalSettings.mcp.enableProjectConfig = enabled;
|
|
475
|
+
this.save();
|
|
476
|
+
}
|
|
443
477
|
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative settings definitions.
|
|
3
|
+
*
|
|
4
|
+
* Each setting is defined once here and the UI is generated automatically.
|
|
5
|
+
* To add a new setting:
|
|
6
|
+
* 1. Add it to SettingsManager (getter/setter)
|
|
7
|
+
* 2. Add the definition here
|
|
8
|
+
* 3. Add the handler in interactive-mode.ts settingsHandlers
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
12
|
+
import { getCapabilities } from "@oh-my-pi/pi-tui";
|
|
13
|
+
import type { SettingsManager } from "../../../core/settings-manager.js";
|
|
14
|
+
|
|
15
|
+
// Setting value types
|
|
16
|
+
export type SettingValue = boolean | string;
|
|
17
|
+
|
|
18
|
+
// Base definition for all settings
|
|
19
|
+
interface BaseSettingDef {
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
description: string;
|
|
23
|
+
tab: "config" | "exa";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Boolean toggle setting
|
|
27
|
+
export interface BooleanSettingDef extends BaseSettingDef {
|
|
28
|
+
type: "boolean";
|
|
29
|
+
get: (sm: SettingsManager) => boolean;
|
|
30
|
+
set: (sm: SettingsManager, value: boolean) => void;
|
|
31
|
+
/** If provided, setting is only shown when this returns true */
|
|
32
|
+
condition?: () => boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Enum setting (inline toggle between values)
|
|
36
|
+
export interface EnumSettingDef extends BaseSettingDef {
|
|
37
|
+
type: "enum";
|
|
38
|
+
values: readonly string[];
|
|
39
|
+
get: (sm: SettingsManager) => string;
|
|
40
|
+
set: (sm: SettingsManager, value: string) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Submenu setting (opens a selection list)
|
|
44
|
+
export interface SubmenuSettingDef extends BaseSettingDef {
|
|
45
|
+
type: "submenu";
|
|
46
|
+
get: (sm: SettingsManager) => string;
|
|
47
|
+
set: (sm: SettingsManager, value: string) => void;
|
|
48
|
+
/** Get available options dynamically */
|
|
49
|
+
getOptions: (sm: SettingsManager) => Array<{ value: string; label: string; description?: string }>;
|
|
50
|
+
/** Called when selection changes (for preview) */
|
|
51
|
+
onPreview?: (value: string) => void;
|
|
52
|
+
/** Called when submenu is cancelled (to restore preview) */
|
|
53
|
+
onPreviewCancel?: (originalValue: string) => void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type SettingDef = BooleanSettingDef | EnumSettingDef | SubmenuSettingDef;
|
|
57
|
+
|
|
58
|
+
const THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {
|
|
59
|
+
off: "No reasoning",
|
|
60
|
+
minimal: "Very brief reasoning (~1k tokens)",
|
|
61
|
+
low: "Light reasoning (~2k tokens)",
|
|
62
|
+
medium: "Moderate reasoning (~8k tokens)",
|
|
63
|
+
high: "Deep reasoning (~16k tokens)",
|
|
64
|
+
xhigh: "Maximum reasoning (~32k tokens)",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* All settings definitions.
|
|
69
|
+
* Order determines display order within each tab.
|
|
70
|
+
*/
|
|
71
|
+
export const SETTINGS_DEFS: SettingDef[] = [
|
|
72
|
+
// Config tab
|
|
73
|
+
{
|
|
74
|
+
id: "autoCompact",
|
|
75
|
+
tab: "config",
|
|
76
|
+
type: "boolean",
|
|
77
|
+
label: "Auto-compact",
|
|
78
|
+
description: "Automatically compact context when it gets too large",
|
|
79
|
+
get: (sm) => sm.getCompactionEnabled(),
|
|
80
|
+
set: (sm, v) => sm.setCompactionEnabled(v), // Also handled in session
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "showImages",
|
|
84
|
+
tab: "config",
|
|
85
|
+
type: "boolean",
|
|
86
|
+
label: "Show images",
|
|
87
|
+
description: "Render images inline in terminal",
|
|
88
|
+
get: (sm) => sm.getShowImages(),
|
|
89
|
+
set: (sm, v) => sm.setShowImages(v),
|
|
90
|
+
condition: () => !!getCapabilities().images,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "queueMode",
|
|
94
|
+
tab: "config",
|
|
95
|
+
type: "enum",
|
|
96
|
+
label: "Queue mode",
|
|
97
|
+
description: "How to process queued messages while agent is working",
|
|
98
|
+
values: ["one-at-a-time", "all"],
|
|
99
|
+
get: (sm) => sm.getQueueMode(),
|
|
100
|
+
set: (sm, v) => sm.setQueueMode(v as "all" | "one-at-a-time"), // Also handled in session
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: "hideThinking",
|
|
104
|
+
tab: "config",
|
|
105
|
+
type: "boolean",
|
|
106
|
+
label: "Hide thinking",
|
|
107
|
+
description: "Hide thinking blocks in assistant responses",
|
|
108
|
+
get: (sm) => sm.getHideThinkingBlock(),
|
|
109
|
+
set: (sm, v) => sm.setHideThinkingBlock(v),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "collapseChangelog",
|
|
113
|
+
tab: "config",
|
|
114
|
+
type: "boolean",
|
|
115
|
+
label: "Collapse changelog",
|
|
116
|
+
description: "Show condensed changelog after updates",
|
|
117
|
+
get: (sm) => sm.getCollapseChangelog(),
|
|
118
|
+
set: (sm, v) => sm.setCollapseChangelog(v),
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "bashInterceptor",
|
|
122
|
+
tab: "config",
|
|
123
|
+
type: "boolean",
|
|
124
|
+
label: "Bash interceptor",
|
|
125
|
+
description: "Block shell commands that have dedicated tools (grep, cat, etc.)",
|
|
126
|
+
get: (sm) => sm.getBashInterceptorEnabled(),
|
|
127
|
+
set: (sm, v) => sm.setBashInterceptorEnabled(v),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "mcpProjectConfig",
|
|
131
|
+
tab: "config",
|
|
132
|
+
type: "boolean",
|
|
133
|
+
label: "MCP project config",
|
|
134
|
+
description: "Load .mcp.json/mcp.json from project root",
|
|
135
|
+
get: (sm) => sm.getMCPProjectConfigEnabled(),
|
|
136
|
+
set: (sm, v) => sm.setMCPProjectConfigEnabled(v),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "thinkingLevel",
|
|
140
|
+
tab: "config",
|
|
141
|
+
type: "submenu",
|
|
142
|
+
label: "Thinking level",
|
|
143
|
+
description: "Reasoning depth for thinking-capable models",
|
|
144
|
+
get: (sm) => sm.getDefaultThinkingLevel() ?? "off",
|
|
145
|
+
set: (sm, v) => sm.setDefaultThinkingLevel(v as ThinkingLevel), // Also handled in session
|
|
146
|
+
getOptions: () =>
|
|
147
|
+
(["off", "minimal", "low", "medium", "high", "xhigh"] as ThinkingLevel[]).map((level) => ({
|
|
148
|
+
value: level,
|
|
149
|
+
label: level,
|
|
150
|
+
description: THINKING_DESCRIPTIONS[level],
|
|
151
|
+
})),
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "theme",
|
|
155
|
+
tab: "config",
|
|
156
|
+
type: "submenu",
|
|
157
|
+
label: "Theme",
|
|
158
|
+
description: "Color theme for the interface",
|
|
159
|
+
get: (sm) => sm.getTheme() ?? "dark",
|
|
160
|
+
set: (sm, v) => sm.setTheme(v),
|
|
161
|
+
getOptions: () => [], // Filled dynamically from context
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// Exa tab
|
|
165
|
+
{
|
|
166
|
+
id: "exaEnabled",
|
|
167
|
+
tab: "exa",
|
|
168
|
+
type: "boolean",
|
|
169
|
+
label: "Exa enabled",
|
|
170
|
+
description: "Master toggle for all Exa search tools",
|
|
171
|
+
get: (sm) => sm.getExaSettings().enabled,
|
|
172
|
+
set: (sm, v) => sm.setExaEnabled(v),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "exaSearch",
|
|
176
|
+
tab: "exa",
|
|
177
|
+
type: "boolean",
|
|
178
|
+
label: "Exa search",
|
|
179
|
+
description: "Basic search, deep search, code search, crawl",
|
|
180
|
+
get: (sm) => sm.getExaSettings().enableSearch,
|
|
181
|
+
set: (sm, v) => sm.setExaSearchEnabled(v),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "exaLinkedin",
|
|
185
|
+
tab: "exa",
|
|
186
|
+
type: "boolean",
|
|
187
|
+
label: "Exa LinkedIn",
|
|
188
|
+
description: "Search LinkedIn for people and companies",
|
|
189
|
+
get: (sm) => sm.getExaSettings().enableLinkedin,
|
|
190
|
+
set: (sm, v) => sm.setExaLinkedinEnabled(v),
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "exaCompany",
|
|
194
|
+
tab: "exa",
|
|
195
|
+
type: "boolean",
|
|
196
|
+
label: "Exa company",
|
|
197
|
+
description: "Comprehensive company research tool",
|
|
198
|
+
get: (sm) => sm.getExaSettings().enableCompany,
|
|
199
|
+
set: (sm, v) => sm.setExaCompanyEnabled(v),
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: "exaResearcher",
|
|
203
|
+
tab: "exa",
|
|
204
|
+
type: "boolean",
|
|
205
|
+
label: "Exa researcher",
|
|
206
|
+
description: "AI-powered deep research tasks",
|
|
207
|
+
get: (sm) => sm.getExaSettings().enableResearcher,
|
|
208
|
+
set: (sm, v) => sm.setExaResearcherEnabled(v),
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: "exaWebsets",
|
|
212
|
+
tab: "exa",
|
|
213
|
+
type: "boolean",
|
|
214
|
+
label: "Exa websets",
|
|
215
|
+
description: "Webset management and enrichment tools",
|
|
216
|
+
get: (sm) => sm.getExaSettings().enableWebsets,
|
|
217
|
+
set: (sm, v) => sm.setExaWebsetsEnabled(v),
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
|
|
221
|
+
/** Get settings for a specific tab */
|
|
222
|
+
export function getSettingsForTab(tab: "config" | "exa"): SettingDef[] {
|
|
223
|
+
return SETTINGS_DEFS.filter((def) => def.tab === tab);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Get a setting definition by id */
|
|
227
|
+
export function getSettingDef(id: string): SettingDef | undefined {
|
|
228
|
+
return SETTINGS_DEFS.find((def) => def.id === id);
|
|
229
|
+
}
|