@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.
@@ -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
- allToolsArray = applyBashInterception(allToolsArray);
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
+ }