decorated-pi 0.5.0 → 0.5.1

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/README.md CHANGED
@@ -55,12 +55,12 @@ Example redaction on a `read` / `bash` output:
55
55
 
56
56
  > `*` = known pattern, `#` = config key regex, `?` = entropy heuristic.
57
57
 
58
- ### 4. Auxiliary Models (Image + Compact)
58
+ ### 4. Auxiliary Models
59
59
 
60
60
  Offloads auxiliary ops to cheaper models, reducing cost on every session. Configured via `/dp-model`:
61
61
 
62
62
  - **Image read fallback** — when the model reads an image file, detects type via magic bytes, calls a configured vision-capable model, and replaces the read result with image analysis text (jpeg, png, gif, webp)
63
- - **Compact model** — uses a configured model for context compaction (instead of the main model), auto-resumes after compaction.
63
+ - **Compact model** — uses a configured model for context compaction (instead of the main model).
64
64
 
65
65
  ### 5. Progressive Context from `AGENTS.md` / `CLAUDE.md`
66
66
 
@@ -3,13 +3,15 @@
3
3
  */
4
4
 
5
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+
7
+ // Extend prompt cache TTL for all providers: Anthropic 1h, OpenAI 24h
8
+ process.env.PI_CACHE_RETENTION ??= "long";
6
9
  import { setupSafety } from "./safety/index.js";
7
10
  import { setupModelIntegration } from "./model-integration";
8
11
  import { setupSlash } from "./slash";
9
12
  import { setupSubdirAgents } from "./subdir-agents";
10
13
  import { setupSessionTitle } from "./session-title";
11
14
  import { setupIO } from "./io";
12
- import { setupGuidance } from "./guidance";
13
15
  import { setupLsp } from "./lsp/index";
14
16
  import { collectLspDependencyStatuses } from "./lsp/servers";
15
17
  import { setupProviders } from "./providers/index";
@@ -79,6 +81,60 @@ function setupDependencyReminders(pi: ExtensionAPI) {
79
81
  });
80
82
  }
81
83
 
84
+ const DECORATED_PI_GUIDANCE_MARKER = "## Decorated Pi Guidance";
85
+
86
+ function setupGuidance(pi: ExtensionAPI) {
87
+ pi.on("before_agent_start", async (event) => {
88
+ // Remove "Current date: YYYY-MM-DD" from system prompt to improve cache stability
89
+ let prompt = event.systemPrompt.replace(/\nCurrent date: \d{4}-\d{2}-\d{2}/, "");
90
+
91
+ if (!prompt.includes(DECORATED_PI_GUIDANCE_MARKER)) {
92
+ const guidance = [
93
+ DECORATED_PI_GUIDANCE_MARKER,
94
+ "",
95
+ "- Before acting on a user's prompt, ensure you fully understand their needs. If the intent is ambiguous, ask clarifying questions. Proceed only when the intent is clear.",
96
+ "- Look before you leap! Ensure you have conducted thorough research before taking any action.",
97
+ "- Exercise caution when performing any **write** operations, especially when you are in a research or exploration phase.",
98
+ "- You don't need to read **AGENTS.md** or **CLAUDE.md** files unless you're explicitly asked to, these files will loaded automatically if neccessary.",
99
+ "- CAUTION: Do not perform write operations in the following directories unless explicitly instructed: `node_modules`, `venv`, `env`, `__pycache__`, `.git` or any other hidden directories.",
100
+ "",
101
+ "### Secret Redaction",
102
+ "",
103
+ "- When you see masked secret values (e.g. `sk-***...***` where `*`, `#`, or `?` are mask characters), the real value has been redacted by the system. Do not attempt to read or guess it. If you need the secret, use tools like `jq` or `grep` to extract it from the original source file.",
104
+ ].join("\n");
105
+
106
+ prompt = `${prompt}\n\n${guidance}`;
107
+ }
108
+
109
+ sortSystemPromptOptions(event.systemPromptOptions);
110
+ return { systemPrompt: prompt };
111
+ });
112
+ }
113
+
114
+ /** Sort all fields in systemPromptOptions alphabetically for stable system prompt. */
115
+ export function sortSystemPromptOptions(opts: {
116
+ toolSnippets?: Record<string, string>;
117
+ selectedTools?: string[];
118
+ promptGuidelines?: string[];
119
+ skills?: Array<{ name: string; description: string; filePath: string }>;
120
+ }) {
121
+ const sortedToolNames = Object.keys(opts.toolSnippets ?? {}).sort((a, b) => a.localeCompare(b));
122
+ const sortedToolSnippets: Record<string, string> = {};
123
+ for (const name of sortedToolNames) {
124
+ sortedToolSnippets[name] = opts.toolSnippets![name];
125
+ }
126
+ opts.toolSnippets = sortedToolSnippets;
127
+ if (opts.selectedTools) {
128
+ opts.selectedTools = sortedToolNames;
129
+ }
130
+ if (opts.promptGuidelines) {
131
+ opts.promptGuidelines = [...opts.promptGuidelines].sort((a, b) => a.localeCompare(b));
132
+ }
133
+ if (opts.skills) {
134
+ opts.skills = [...opts.skills].sort((a, b) => a.name.localeCompare(b.name));
135
+ }
136
+ }
137
+
82
138
  export default function (pi: ExtensionAPI) {
83
139
  // Always loaded — core commands and providers
84
140
  setupSlash(pi);
@@ -97,4 +153,4 @@ export default function (pi: ExtensionAPI) {
97
153
  if (isModuleEnabled("mcp")) setupMcp(pi);
98
154
  if (isModuleEnabled("wakatime")) setupWakatime(pi);
99
155
  if (isModuleEnabled("rtk") && findSystemRtk()) setupRtkIntegration(pi);
100
- }
156
+ }
package/extensions/io.ts CHANGED
@@ -448,6 +448,7 @@ export function setupIO(pi: ExtensionAPI) {
448
448
  "Always prefer modifying files with PATCH tool over bash commands or python scripts.",
449
449
  "For full-file replacement, always use patch tool to prevent unintended edits or data loss.",
450
450
  "To prevent hallucinations: 1. Keep each edit batch ≤ 5 changes; 2. Process remaining revisions in sequential steps",
451
+ "On repeated failures: read the file first to confirm information accuracy.",
451
452
  ],
452
453
  parameters: PatchSchema,
453
454
  renderShell: "self",
@@ -24,116 +24,15 @@ export const BUILTIN_MCP_SERVERS: Omit<McpServerConfig, "source">[] = [
24
24
  {
25
25
  name: "context7",
26
26
  url: "https://mcp.context7.com/mcp",
27
- description: "Context7 documentation and code examples",
28
27
  enabled: true,
29
28
  },
30
29
  {
31
30
  name: "exa",
32
31
  url: "https://mcp.exa.ai/mcp",
33
- description: "Exa web search",
34
32
  enabled: true,
35
33
  },
36
34
  ];
37
35
 
38
- /** Builtin tool schemas — hardcoded so builtin servers work without a prior connection. */
39
- export const BUILTIN_MCP_CACHE: McpCache = {
40
- servers: {
41
- context7: {
42
- description: "Context7 documentation and code examples",
43
- tools: [
44
- {
45
- name: "resolve-library-id",
46
- description: "Resolve a library name to its Context7 library ID",
47
- inputSchema: {
48
- type: "object",
49
- properties: {
50
- libraryName: {
51
- type: "string",
52
- description: "Library or framework name to resolve (e.g. 'react', 'vue')",
53
- },
54
- filters: {
55
- type: "array",
56
- items: {
57
- type: "object",
58
- properties: {
59
- field: { type: "string" },
60
- operator: { type: "string" },
61
- value: { type: "string" },
62
- },
63
- },
64
- description: "Optional filters to narrow down results",
65
- },
66
- },
67
- required: ["libraryName"],
68
- },
69
- },
70
- {
71
- name: "query-docs",
72
- description: "Retrieve and query documentation using a Context7 library ID",
73
- inputSchema: {
74
- type: "object",
75
- properties: {
76
- libraryId: {
77
- type: "string",
78
- description: "Library ID returned by resolve-library-id",
79
- },
80
- query: {
81
- type: "string",
82
- description: "Question or topic to search in the documentation",
83
- },
84
- },
85
- required: ["libraryId", "query"],
86
- },
87
- },
88
- ],
89
- cachedAt: 0,
90
- },
91
- exa: {
92
- description: "Exa web search",
93
- tools: [
94
- {
95
- name: "web_search_exa",
96
- description: "Search the web for any topic and get results",
97
- inputSchema: {
98
- type: "object",
99
- properties: {
100
- query: {
101
- type: "string",
102
- description: "Search query",
103
- },
104
- numResults: {
105
- type: "number",
106
- description: "Number of results to return (default: 10)",
107
- },
108
- },
109
- required: ["query"],
110
- },
111
- },
112
- {
113
- name: "web_fetch_exa",
114
- description: "Read webpage content from specific URLs",
115
- inputSchema: {
116
- type: "object",
117
- properties: {
118
- urls: {
119
- type: "array",
120
- items: { type: "string" },
121
- description: "URLs to fetch content from",
122
- },
123
- maxCharacters: {
124
- type: "number",
125
- description: "Maximum characters per page to return",
126
- },
127
- },
128
- required: ["urls"],
129
- },
130
- },
131
- ],
132
- cachedAt: 0,
133
- },
134
- },
135
- };
136
-
137
36
  // ── Project-level config discovery ─────────────────────────────────────────
138
37
 
139
38
  function readMcpJson(filePath: string): Record<string, { url?: string; command?: string; args?: string[]; env?: Record<string, string>; enabled?: boolean; description?: string }> | null {
@@ -331,9 +230,9 @@ function writeCacheFile(p: string, cache: McpCache): void {
331
230
  fs.renameSync(tmp, p);
332
231
  }
333
232
 
334
- /** Load merged cache: builtin + global + project. */
233
+ /** Load merged cache: global + project. */
335
234
  export function loadMcpCache(cwd?: string): McpCache | null {
336
- const merged: McpCache = { servers: { ...BUILTIN_MCP_CACHE.servers } };
235
+ const merged: McpCache = { servers: {} };
337
236
 
338
237
  const globalCache = readCacheFile(globalCachePath());
339
238
  if (globalCache) {
@@ -428,5 +327,16 @@ function cleanupOneCache(p: string, names: Set<string>): void {
428
327
  export function cleanupStaleCache(configs: McpServerConfig[], cwd?: string): void {
429
328
  const names = new Set(configs.map(c => c.name));
430
329
  cleanupOneCache(globalCachePath(), names);
431
- if (cwd) cleanupOneCache(projectCachePath(cwd), names);
330
+ if (cwd) {
331
+ const projectCache = projectCachePath(cwd);
332
+ const projectMcpJson = path.join(cwd, ".pi/agent/mcp.json");
333
+ // If project mcp.json doesn't exist, remove project cache entirely
334
+ if (!fs.existsSync(projectMcpJson)) {
335
+ if (fs.existsSync(projectCache)) {
336
+ fs.unlinkSync(projectCache);
337
+ }
338
+ } else {
339
+ cleanupOneCache(projectCache, names);
340
+ }
341
+ }
432
342
  }
@@ -2,21 +2,15 @@ import { keyHint, type ExtensionAPI, type ExtensionContext } from "@earendil-wor
2
2
  import { Text } from "@earendil-works/pi-tui";
3
3
  import { McpConnection } from "./client.js";
4
4
  import {
5
- resolveMcpConfigs, saveProjectMcpDescription,
5
+ resolveMcpConfigs,
6
6
  loadMcpCache, updateServerCache, cleanupStaleCache,
7
7
  type McpServerConfig, type McpToolCache,
8
8
  } from "./builtin.js";
9
- import {
10
- getMcpBrokerModelKey, getCompactModelKey,
11
- getMcpDescription, setMcpDescription,
12
- parseModelKey,
13
- } from "../settings.js";
14
9
 
15
10
  export interface McpServerStatus {
16
11
  name: string;
17
12
  url: string;
18
13
  source: string;
19
- description?: string;
20
14
  state: "connecting" | "connected" | "failed" | "disabled";
21
15
  toolCount: number;
22
16
  tools: Array<{ name: string; description?: string; inputSchema?: Record<string, unknown> }>;
@@ -90,10 +84,6 @@ export function updateConfigEnabled(serverName: string, enabled: boolean): void
90
84
 
91
85
  // ── helpers ───────────────────────────────────────────────────────────────
92
86
 
93
- function serverDescription(s: McpServerConfig): string | undefined {
94
- return s.description || getMcpDescription(s.name, cachedCwd);
95
- }
96
-
97
87
  function makeToolName(serverName: string, toolName: string): string {
98
88
  return `${serverName}_${toolName}`;
99
89
  }
@@ -108,67 +98,6 @@ function cacheScopeForSource(source: string): "global" | "project" {
108
98
  return source === "project" ? "project" : "global";
109
99
  }
110
100
 
111
- // ── auto-summary ──────────────────────────────────────────────────────────
112
-
113
- async function autoDescribeServer(
114
- conn: McpConnection,
115
- serverName: string,
116
- registry: any,
117
- ): Promise<string> {
118
- const descs = conn.tools.map(t => `- ${t.name}: ${t.description || "(no description)"}`).join("\n");
119
-
120
- const prompt = `Describe what this MCP server is and what it does, based on the tools it exposes. Start with action verbs directly, like a capability summary.
121
-
122
- Server: "${serverName}"
123
- Tools:
124
- ${descs}
125
-
126
- Respond with ONLY one short sentence. No quotes.`;
127
-
128
- return await summarizeWithBroker(registry, prompt) || `${serverName} MCP server (${conn.tools.length} tools)`;
129
- }
130
-
131
- async function summarizeWithBroker(registry: any, prompt: string): Promise<string | undefined> {
132
- if (!registry) return undefined;
133
-
134
- const brokerKey = getMcpBrokerModelKey() || getCompactModelKey();
135
- const model = brokerKey
136
- ? (() => {
137
- const parsed = parseModelKey(brokerKey);
138
- return parsed ? registry.find(parsed.provider, parsed.modelId) : undefined;
139
- })()
140
- : undefined;
141
-
142
- if (!model) return undefined;
143
-
144
- try {
145
- const auth = await registry.getApiKeyAndHeaders(model);
146
- if (!auth.ok) return undefined;
147
-
148
- const { complete } = await import("@earendil-works/pi-ai");
149
- const resp = await complete(model, {
150
- systemPrompt: "You are a concise MCP server description generator.",
151
- messages: [{
152
- role: "user" as const,
153
- content: [{ type: "text" as const, text: prompt }],
154
- timestamp: Date.now(),
155
- }],
156
- }, {
157
- maxTokens: 128,
158
- apiKey: auth.apiKey ?? "",
159
- headers: auth.headers,
160
- signal: AbortSignal.timeout(15_000),
161
- });
162
-
163
- if (resp.stopReason === "error") return undefined;
164
- return resp.content
165
- .filter((c: any): c is { type: "text"; text: string } => c.type === "text")
166
- .map((c: any) => c.text).join(" ").trim() || undefined;
167
- } catch {
168
- return undefined;
169
- }
170
- }
171
-
172
101
  // ── register cached tools ─────────────────────────────────────────────────
173
102
 
174
103
  function registerCachedTools(pi: ExtensionAPI, configs: McpServerConfig[]): void {
@@ -218,7 +147,11 @@ function registerCachedTools(pi: ExtensionAPI, configs: McpServerConfig[]): void
218
147
 
219
148
  // ── connect ───────────────────────────────────────────────────────────────
220
149
 
221
- async function connectAll(configs: McpServerConfig[], registry: any): Promise<void> {
150
+ async function connectAll(configs: McpServerConfig[], ui?: { notify: (msg: string, type: string) => void }): Promise<void> {
151
+ // Load current cache for comparison
152
+ const cache = loadMcpCache(cachedCwd);
153
+ const schemaChanges: string[] = [];
154
+
222
155
  allServers = new Map(
223
156
  configs.map((s) => [
224
157
  s.name,
@@ -226,7 +159,6 @@ async function connectAll(configs: McpServerConfig[], registry: any): Promise<vo
226
159
  name: s.name,
227
160
  url: s.url ?? s.command ?? "(unknown)",
228
161
  source: s.source,
229
- description: serverDescription(s),
230
162
  state: "connecting" as const,
231
163
  toolCount: 0,
232
164
  tools: [],
@@ -243,12 +175,30 @@ async function connectAll(configs: McpServerConfig[], registry: any): Promise<vo
243
175
  await conn.connect(30_000);
244
176
  activeConnections.push(conn);
245
177
 
246
- let desc = serverDescription(server);
247
- if (!desc) {
248
- desc = await autoDescribeServer(conn, server.name, registry);
249
- if (desc) {
250
- if (server.source === "project") saveProjectMcpDescription(cachedCwd, server.name, desc);
251
- else setMcpDescription(server.name, desc);
178
+ const actualTools = conn.tools.map((t) => ({
179
+ name: t.name,
180
+ description: t.description,
181
+ inputSchema: t.inputSchema,
182
+ }));
183
+
184
+ // Check if schema changed
185
+ const cachedEntry = cache?.servers[server.name];
186
+ if (cachedEntry && cachedEntry.tools.length > 0) {
187
+ const cachedToolNames = new Set(cachedEntry.tools.map(t => t.name));
188
+ const actualToolNames = new Set(actualTools.map(t => t.name));
189
+ const added = actualTools.filter(t => !cachedToolNames.has(t.name));
190
+ const removed = cachedEntry.tools.filter(t => !actualToolNames.has(t.name));
191
+ const changed = actualTools.filter(t => {
192
+ const cached = cachedEntry.tools.find(ct => ct.name === t.name);
193
+ return cached && JSON.stringify(cached.inputSchema) !== JSON.stringify(t.inputSchema);
194
+ });
195
+
196
+ if (added.length > 0 || removed.length > 0 || changed.length > 0) {
197
+ const parts: string[] = [];
198
+ if (added.length) parts.push(`${added.length} added`);
199
+ if (removed.length) parts.push(`${removed.length} removed`);
200
+ if (changed.length) parts.push(`${changed.length} changed`);
201
+ schemaChanges.push(`${server.name} (${parts.join(', ')})`);
252
202
  }
253
203
  }
254
204
 
@@ -256,14 +206,9 @@ async function connectAll(configs: McpServerConfig[], registry: any): Promise<vo
256
206
  name: server.name,
257
207
  url: server.url ?? server.command ?? "(unknown)",
258
208
  source: server.source,
259
- description: desc,
260
209
  state: "connected",
261
210
  toolCount: conn.tools.length,
262
- tools: conn.tools.map((t) => ({
263
- name: t.name,
264
- description: t.description,
265
- inputSchema: t.inputSchema,
266
- })),
211
+ tools: actualTools,
267
212
  });
268
213
 
269
214
  // Update cache with this server's tools
@@ -274,7 +219,7 @@ async function connectAll(configs: McpServerConfig[], registry: any): Promise<vo
274
219
  }));
275
220
  updateServerCache(
276
221
  server.name,
277
- { description: desc, tools, cachedAt: Date.now() },
222
+ { tools, cachedAt: Date.now() },
278
223
  cacheScopeForSource(server.source),
279
224
  cachedCwd || undefined,
280
225
  );
@@ -284,7 +229,6 @@ async function connectAll(configs: McpServerConfig[], registry: any): Promise<vo
284
229
  name: server.name,
285
230
  url: server.url ?? server.command ?? "(unknown)",
286
231
  source: server.source,
287
- description: serverDescription(server),
288
232
  state: "failed",
289
233
  toolCount: 0,
290
234
  tools: [],
@@ -296,6 +240,11 @@ async function connectAll(configs: McpServerConfig[], registry: any): Promise<vo
296
240
 
297
241
  await connectPromise;
298
242
  connectPromise = null;
243
+
244
+ // Notify about schema changes
245
+ if (schemaChanges.length > 0 && ui) {
246
+ ui.notify(`MCP schema updated: ${schemaChanges.join('; ')}. Run /reload to apply.`, "warning");
247
+ }
299
248
  }
300
249
 
301
250
  // ── setup ─────────────────────────────────────────────────────────────────
@@ -317,16 +266,8 @@ export function setupMcp(pi: ExtensionAPI) {
317
266
  // Register tools from cache — prompt-stable, works even if MCP is down
318
267
  registerCachedTools(pi, enabledConfigs);
319
268
 
320
- const needSummary = enabledConfigs.filter(s => !serverDescription(s));
321
-
322
- if (needSummary.length === 0) {
323
- // All servers have descriptions — connect in background, update cache
324
- void connectAll(enabledConfigs, ctx.modelRegistry);
325
- return;
326
- }
327
-
328
- // Some servers lack description — connect and auto-summarize synchronously
329
- await connectAll(enabledConfigs, ctx.modelRegistry);
269
+ // Connect in background, pass UI for schema change notifications
270
+ void connectAll(enabledConfigs, ctx.hasUI ? ctx.ui : undefined);
330
271
  });
331
272
 
332
273
  pi.on("session_shutdown", () => {
@@ -347,7 +288,6 @@ export function getMcpStatus(): McpServerStatus[] {
347
288
  name: config.name,
348
289
  url: config.url ?? config.command ?? "(unknown)",
349
290
  source: config.source,
350
- description: serverDescription(config),
351
291
  state: config.enabled ? "connecting" : "disabled",
352
292
  toolCount: cachedEntry?.tools.length ?? 0,
353
293
  tools: cachedEntry?.tools ?? [],
@@ -382,20 +322,10 @@ export async function refreshServerCache(
382
322
  await conn.connect(30_000);
383
323
  activeConnections.push(conn);
384
324
 
385
- let desc = serverDescription(config);
386
- if (!desc) {
387
- desc = await autoDescribeServer(conn, config.name, registry);
388
- if (desc) {
389
- if (config.source === "project") saveProjectMcpDescription(cachedCwd, config.name, desc);
390
- else setMcpDescription(config.name, desc);
391
- }
392
- }
393
-
394
325
  allServers.set(config.name, {
395
326
  name: config.name,
396
327
  url: config.url ?? config.command ?? "(unknown)",
397
328
  source: config.source,
398
- description: desc,
399
329
  state: "connected",
400
330
  toolCount: conn.tools.length,
401
331
  tools: conn.tools.map(t => ({
@@ -412,7 +342,7 @@ export async function refreshServerCache(
412
342
  }));
413
343
  updateServerCache(
414
344
  config.name,
415
- { description: desc, tools, cachedAt: Date.now() },
345
+ { tools, cachedAt: Date.now() },
416
346
  cacheScopeForSource(config.source),
417
347
  cachedCwd || undefined,
418
348
  );
@@ -424,7 +354,6 @@ export async function refreshServerCache(
424
354
  name: config.name,
425
355
  url: config.url ?? config.command ?? "(unknown)",
426
356
  source: config.source,
427
- description: serverDescription(config),
428
357
  state: "failed",
429
358
  toolCount: 0,
430
359
  tools: [],
@@ -25,8 +25,8 @@ import { fileTypeFromFile } from "file-type";
25
25
  import { isContextOverflow, type Model } from "@earendil-works/pi-ai";
26
26
  import {
27
27
  loadConfig, saveConfig, parseModelKey, formatModelKey,
28
- getImageModelKey, getCompactModelKey, getMcpBrokerModelKey,
29
- setImageModelKey, setCompactModelKey, setMcpBrokerModelKey,
28
+ getImageModelKey, getCompactModelKey,
29
+ setImageModelKey, setCompactModelKey,
30
30
  } from "./settings.js";
31
31
  import * as fs from "node:fs";
32
32
  import * as os from "node:os";
@@ -172,7 +172,6 @@ function setupImageReadFallback(pi: ExtensionAPI) {
172
172
 
173
173
  const TAB_IMAGE = 0;
174
174
  const TAB_COMPACT = 1;
175
- const TAB_BROKER = 2;
176
175
 
177
176
  export class ModelPickerComponent extends Container {
178
177
  private searchInput: Input;
@@ -183,7 +182,6 @@ export class ModelPickerComponent extends Container {
183
182
  private activeTab = TAB_IMAGE;
184
183
  private imageKey: string | null;
185
184
  private compactKey: string | null;
186
- private brokerKey: string | null;
187
185
  private allItems: { label: string; desc: string; model: Model<any> | null; modelName?: string }[] = [];
188
186
  private filtered: typeof this.allItems = [];
189
187
  private selectedIndex = 0;
@@ -199,7 +197,6 @@ export class ModelPickerComponent extends Container {
199
197
  this.onDone = onDone;
200
198
  this.imageKey = getImageModelKey();
201
199
  this.compactKey = getCompactModelKey();
202
- this.brokerKey = getMcpBrokerModelKey();
203
200
 
204
201
  this.addChild(new DynamicBorder());
205
202
  this.addChild(new Spacer(1));
@@ -235,8 +232,8 @@ export class ModelPickerComponent extends Container {
235
232
  }
236
233
  }
237
234
 
238
- private currentKey() { return this.activeTab === TAB_IMAGE ? this.imageKey : this.activeTab === TAB_BROKER ? this.brokerKey : this.compactKey; }
239
- private currentKind() { return this.activeTab === TAB_IMAGE ? "image" : this.activeTab === TAB_BROKER ? "broker" : "compact"; }
235
+ private currentKey() { return this.activeTab === TAB_IMAGE ? this.imageKey : this.compactKey; }
236
+ private currentKind() { return this.activeTab === TAB_IMAGE ? "image" : "compact"; }
240
237
 
241
238
  private switchTab(tab: number) {
242
239
  this.activeTab = tab;
@@ -263,11 +260,9 @@ export class ModelPickerComponent extends Container {
263
260
  const t = this.theme;
264
261
  const im = this.activeTab === TAB_IMAGE ? t.fg("accent", "●") : "○";
265
262
  const cm = this.activeTab === TAB_COMPACT ? t.fg("accent", "●") : "○";
266
- const bm = this.activeTab === TAB_BROKER ? t.fg("accent", "●") : "○";
267
263
  const il = this.activeTab === TAB_IMAGE ? t.bold("Image") : t.fg("dim", "Image");
268
264
  const cl = this.activeTab === TAB_COMPACT ? t.bold("Compact") : t.fg("dim", "Compact");
269
- const bl = this.activeTab === TAB_BROKER ? t.bold("Broker") : t.fg("dim", "Broker");
270
- this.tabTitle.setText(`${im} ${il} | ${cm} ${cl} | ${bm} ${bl}`);
265
+ this.tabTitle.setText(`${im} ${il} | ${cm} ${cl}`);
271
266
  const key = this.currentKey();
272
267
  this.subtitleText.setText(key ? t.fg("warning", `Current ${this.currentKind()} model: ${key}`) : t.fg("warning", `No ${this.currentKind()} model set`));
273
268
  }
@@ -275,7 +270,7 @@ export class ModelPickerComponent extends Container {
275
270
  handleInput(keyData: string) {
276
271
  const kb = getKeybindings();
277
272
  if (kb.matches(keyData, "tui.input.tab")) {
278
- const next = this.activeTab === TAB_IMAGE ? TAB_COMPACT : this.activeTab === TAB_COMPACT ? TAB_BROKER : TAB_IMAGE;
273
+ const next = this.activeTab === TAB_IMAGE ? TAB_COMPACT : TAB_IMAGE;
279
274
  this.switchTab(next); this.tui.requestRender(); return;
280
275
  }
281
276
  if (kb.matches(keyData, "tui.select.up")) { this.selectedIndex = this.selectedIndex === 0 ? this.filtered.length - 1 : this.selectedIndex - 1; this.updateList(); return; }
@@ -297,15 +292,12 @@ export class ModelPickerComponent extends Container {
297
292
  const kind = this.currentKind();
298
293
  if (model) {
299
294
  if (kind === "image") setImageModelKey(formatModelKey(model));
300
- else if (kind === "broker") setMcpBrokerModelKey(formatModelKey(model));
301
295
  else setCompactModelKey(formatModelKey(model));
302
296
  } else {
303
297
  if (kind === "image") setImageModelKey(null);
304
- else if (kind === "broker") setMcpBrokerModelKey(null);
305
298
  else setCompactModelKey(null);
306
299
  }
307
300
  if (kind === "image") this.imageKey = model ? formatModelKey(model) : null;
308
- else if (kind === "broker") this.brokerKey = model ? formatModelKey(model) : null;
309
301
  else this.compactKey = model ? formatModelKey(model) : null;
310
302
  this.switchTab(this.activeTab); this.tui.requestRender();
311
303
  }
package/extensions/rtk.ts CHANGED
@@ -73,32 +73,7 @@ export function buildRtkCommand(raw: string, rtkBinaryPath: string): string {
73
73
  return `export PATH=${shellQuote(binDir)}:$PATH && ${raw}`;
74
74
  }
75
75
 
76
- export function extractMainCommand(command: string): string {
77
- let cmd = command.trim().toLowerCase();
78
- cmd = cmd.replace(/^cd\s+\S+\s*(&&|;|\n)\s*/, "");
79
- cmd = cmd.replace(/^(?:[a-z_][a-z0-9_]*=\S*\s+)+/, "");
80
- const prefixes = ["sudo ", "time ", "nohup ", "nice ", "env "];
81
- let changed = true;
82
- while (changed) {
83
- changed = false;
84
- for (const prefix of prefixes) {
85
- if (cmd.startsWith(prefix)) {
86
- cmd = cmd.slice(prefix.length);
87
- changed = true;
88
- }
89
- }
90
- }
91
- return cmd;
92
- }
93
-
94
- export function shouldBypassRtkRewrite(command: string): boolean {
95
- const main = extractMainCommand(command);
96
- if (!main.startsWith("find ") && main !== "find") return false;
97
- return /(^|\s)(-o|-or|-a|-and|-not|!|\(|\)|-exec|-ok|-delete|-prune|-printf|-print0)(\s|$)/.test(main);
98
- }
99
-
100
76
  export function rewriteWithRtk(command: string, rtkPath: string): string | null {
101
- if (shouldBypassRtkRewrite(command)) return null;
102
77
 
103
78
  // NOTE:
104
79
  // Some RTK versions return a non-zero exit code even when `rtk rewrite`
@@ -47,8 +47,6 @@ export interface ModuleSettings {
47
47
  export interface DecoratedPiConfig {
48
48
  imageModelKey?: string | null;
49
49
  compactModelKey?: string | null;
50
- mcpBrokerModelKey?: string | null;
51
- mcpDescriptions?: Record<string, string>;
52
50
  providers?: Record<string, ProviderCache>;
53
51
  modules?: ModuleSettings;
54
52
  mcpServers?: Record<string, McpServerEntry>;
@@ -143,36 +141,6 @@ export function setCompactModelKey(key: string | null) {
143
141
  saveConfig({ compactModelKey: key });
144
142
  }
145
143
 
146
- export function getMcpBrokerModelKey(): string | null {
147
- return loadConfig().mcpBrokerModelKey ?? null;
148
- }
149
-
150
- export function setMcpBrokerModelKey(key: string | null) {
151
- saveConfig({ mcpBrokerModelKey: key });
152
- }
153
-
154
- export function getMcpDescription(name: string, cwd?: string): string | undefined {
155
- if (cwd) {
156
- const projectCfg = loadProjectConfig(cwd);
157
- if (projectCfg.mcpDescriptions?.[name]) return projectCfg.mcpDescriptions[name];
158
- }
159
- return loadConfig().mcpDescriptions?.[name];
160
- }
161
-
162
- export function setMcpDescription(name: string, description: string, cwd?: string) {
163
- if (cwd) {
164
- const cfg = loadProjectConfig(cwd);
165
- const descriptions = { ...cfg.mcpDescriptions };
166
- descriptions[name] = description;
167
- saveProjectConfig(cwd, { mcpDescriptions: descriptions });
168
- return;
169
- }
170
- const cfg = loadConfig();
171
- const descriptions = { ...cfg.mcpDescriptions };
172
- descriptions[name] = description;
173
- saveConfig({ mcpDescriptions: descriptions });
174
- }
175
-
176
144
  // ─── Module Switches ──────────────────────────────────────────────────────────
177
145
 
178
146
  const DEFAULT_MODULES: Required<ModuleSettings> = {
@@ -45,7 +45,7 @@ function getSettingsListTheme(theme: PiTheme): SettingsListTheme {
45
45
 
46
46
  function setupDpModelCommand(pi: ExtensionAPI) {
47
47
  pi.registerCommand("dp-model", {
48
- description: "Configure image, compact, and MCP broker models",
48
+ description: "Configure image and compact models",
49
49
  handler: async (_args, ctx) => {
50
50
  if (ctx.hasUI) {
51
51
  await ctx.ui.custom<void>(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "decorated-pi",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "decorated-pi is a practical enhancement pack for pi — smarter tools that are token efficient and cache friendly.",
5
5
  "keywords": [
6
6
  "pi",
@@ -1,30 +0,0 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
-
3
- const DECORATED_PI_GUIDANCE_MARKER = "## Decorated Pi Guidance";
4
-
5
- export function setupGuidance(pi: ExtensionAPI) {
6
- pi.on("before_agent_start", async (event) => {
7
- // Remove "Current date: YYYY-MM-DD" from system prompt to improve cache stability
8
- let prompt = event.systemPrompt.replace(/\nCurrent date: \d{4}-\d{2}-\d{2}/, "");
9
-
10
- if (!prompt.includes(DECORATED_PI_GUIDANCE_MARKER)) {
11
- const guidance = [
12
- DECORATED_PI_GUIDANCE_MARKER,
13
- "",
14
- "- Before acting on a user's prompt, ensure you fully understand their needs. If the intent is ambiguous, ask clarifying questions. Proceed only when the intent is clear.",
15
- "- Look before you leap! Ensure you have conducted thorough research before taking any action.",
16
- "- Exercise caution when performing any **write** operations, especially when you are in a research or exploration phase.",
17
- "- You don't need to read **AGENTS.md** or **CLAUDE.md** files unless you're explicitly asked to, these files will loaded automatically if neccessary.",
18
- "- CAUTION: Do not perform write operations in the following directories unless explicitly instructed: `node_modules`, `venv`, `env`, `__pycache__`, `.git` or any other hidden directories.",
19
- "",
20
- "### Secret Redaction",
21
- "",
22
- "- When you see masked secret values (e.g. `sk-***...***` where `*`, `#`, or `?` are mask characters), the real value has been redacted by the system. Do not attempt to read or guess it. If you need the secret, use tools like `jq` or `grep` to extract it from the original source file.",
23
- ].join("\n");
24
-
25
- prompt = `${prompt}\n\n${guidance}`;
26
- }
27
-
28
- return { systemPrompt: prompt };
29
- });
30
- }